Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
27b9582
Docs: add basic usage snippets to framework integration guides
marcin-kordas-hoc Apr 9, 2026
e920dcc
Docs: simplify snippets and clarify Demo link wording
marcin-kordas-hoc Apr 13, 2026
88056b7
Docs: modernize framework integration guides with idiomatic patterns
marcin-kordas-hoc Apr 13, 2026
9571737
Docs: add Next steps cross-links to framework integration guides
marcin-kordas-hoc Apr 13, 2026
bd84ce9
Docs: fix $page.buildDateURIEncoded template syntax in framework guides
marcin-kordas-hoc Apr 13, 2026
87c2f46
Docs: fix setCellContents type in guide snippets (unknown → RawCellCo…
marcin-kordas-hoc Apr 14, 2026
2f8b2be
Docs: add AGENTS.md with universal AI assistant instructions
marcin-kordas-hoc Apr 14, 2026
2a07790
Merge branch 'develop' into feature/hf-122-framework-integration-guides
sequba Apr 14, 2026
683bc08
Docs: align Angular and Svelte guide snippets with Stackblitz demos
marcin-kordas-hoc Apr 15, 2026
655274a
Docs: fix P0/P1 issues found in framework guide expert review
marcin-kordas-hoc Apr 15, 2026
b2cb63d
Docs: align guides with Stackblitz demos and fix review findings
marcin-kordas-hoc Apr 16, 2026
76bcfdd
Docs: fix Stackblitz links using Vue v-bind instead of template inter…
marcin-kordas-hoc Apr 16, 2026
f8dfa07
Docs: remove untested framework patterns not present in demos
marcin-kordas-hoc Apr 16, 2026
545ea1e
Docs: align all guide snippets with demo interaction patterns
marcin-kordas-hoc Apr 16, 2026
af96abc
Docs: fix AGENTS.md Vue SSR guard and align Svelte button labels
marcin-kordas-hoc Apr 16, 2026
d6a316c
Merge branch 'develop' into feature/hf-122-framework-integration-guides
sequba Apr 16, 2026
3bc3333
Merge branch 'develop' into feature/hf-122-framework-integration-guides
sequba Apr 28, 2026
146142d
Docs: fix React TS import + Next.js SSR pattern + Svelte async onMoun…
marcin-kordas-hoc Apr 20, 2026
36aa583
Docs: align Vue/Svelte/AGENTS.md SSR framing with React + drop dated …
marcin-kordas-hoc May 6, 2026
3528710
Docs: hide Angular table on empty array (parity with React/Vue)
marcin-kordas-hoc May 6, 2026
b080681
Docs: actually apply sequba's verbatim review suggestions
marcin-kordas-hoc May 6, 2026
cc6626a
Merge branch 'develop' into feature/hf-122-framework-integration-guides
marcin-kordas-hoc May 6, 2026
c598774
Docs: remove AGENTS.md from HF-122 scope (belongs to HF-154)
marcin-kordas-hoc May 6, 2026
17cf8bd
Docs: correct Svelte SSR warning (HF runs on server, does not crash)
marcin-kordas-hoc May 6, 2026
1ecce54
Docs: align SSR section heading depth + add Angular Universal note
marcin-kordas-hoc May 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/guide/custom-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ it('returns a VALUE error if the range argument contains a string', () => {

## Working demo

Explore the full working example on [Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/custom-functions?v=${$page.buildDateURIEncoded}).
Explore the full working example on <a :href="'https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/custom-functions?v=' + $page.buildDateURIEncoded">Stackblitz</a>.
Comment thread
sequba marked this conversation as resolved.

This demo contains the implementation of both the
[`GREET`](#add-a-simple-custom-function) and
Expand Down
124 changes: 121 additions & 3 deletions docs/guide/integration-with-angular.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,127 @@
# Integration with Angular

Installing HyperFormula in an Angular application works the same as with vanilla JavaScript.
The HyperFormula API is identical in an Angular app and in plain JavaScript. This guide demonstrates how HyperFormula is integrated with an Angular app (typically as an injectable service), how it is cleaned up, and how you bridge its values into the change-detection cycle.

For more details, see the [client-side installation](client-side-installation.md) section.
Install with `npm install hyperformula`. For other options, see the [client-side installation](client-side-installation.md) section.

## Basic usage

Wrap the engine in an `@Injectable` service backed by a `BehaviorSubject`. Components subscribe to the observable with the `async` pipe, which handles subscription cleanup automatically.

```typescript
// spreadsheet.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { HyperFormula, type CellValue } from 'hyperformula';

@Injectable({ providedIn: 'root' })
export class SpreadsheetService {
private readonly hf: HyperFormula;

private readonly _values = new BehaviorSubject<CellValue[][]>([]);
readonly values$ = this._values.asObservable();

constructor() {
this.hf = HyperFormula.buildFromArray(
[
[1, 2, '=A1+B1'],
// your data rows go here
],
{
licenseKey: 'gpl-v3',
// more configuration options go here
}
);
this._values.next(this.hf.getSheetValues(0));
}

calculate() {
this._values.next(this.hf.getSheetValues(0));
}

reset() {
this._values.next([]);
}
}
```

Consume the service from a component and bind `values$ | async` in the template. Declare the component in your `AppModule` alongside `CommonModule`:

```typescript
// spreadsheet.component.ts
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { SpreadsheetService } from './spreadsheet.service';
import { type CellValue } from 'hyperformula';

@Component({
selector: 'app-spreadsheet',
templateUrl: './spreadsheet.component.html',
})
export class SpreadsheetComponent {
values$: Observable<CellValue[][]>;

constructor(private spreadsheetService: SpreadsheetService) {
this.values$ = this.spreadsheetService.values$;
}

runCalculations() {
this.spreadsheetService.calculate();
}

reset() {
this.spreadsheetService.reset();
}
}
```

```html
<!-- spreadsheet.component.html -->
<button (click)="runCalculations()">Run calculations</button>
<button (click)="reset()">Reset</button>
<ng-container *ngIf="(values$ | async) as values">
<table *ngIf="values.length">
<tr *ngFor="let row of values">
<td *ngFor="let cell of row">{{ cell }}</td>
</tr>
</table>
</ng-container>
```

## Notes

### Provider scope

`providedIn: 'root'` makes the service an application-wide singleton — suitable when a single HyperFormula instance is shared across the app. For per-feature or per-component instances (for example, several independent reports on one screen), provide the service at the component level via `providers: [SpreadsheetService]`; the service is then created and destroyed alongside the component.

### Cleanup

Root-scoped services live for the application's full lifetime — `ngOnDestroy` fires only at app shutdown. If you scope the service to a component (`providers: [SpreadsheetService]`), implement `OnDestroy` to release the engine:

```typescript
import { Injectable, OnDestroy } from '@angular/core';

@Injectable()
export class SpreadsheetService implements OnDestroy {
// ...

ngOnDestroy() {
this.hf.destroy();
}
}
```

## Server-side rendering (Angular Universal)

The service above is already SSR-safe — HyperFormula has no browser-only API dependency. To skip the (otherwise wasted) server-side instantiation in Angular Universal, gate the engine init with [`isPlatformBrowser`](https://angular.dev/api/common/isPlatformBrowser) from `@angular/common`.

## Next steps

- [Configuration options](configuration-options.md) — full list of `buildFromArray` / `buildEmpty` options
- [Basic operations](basic-operations.md) — CRUD on cells, rows, columns, sheets
- [Advanced usage](advanced-usage.md) — multi-sheet workbooks, named expressions
- [Custom functions](custom-functions.md) — register your own formulas

## Demo

Explore the full working example on [Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/angular-demo?v=${$page.buildDateURIEncoded}).
For a more advanced example, check out the <a :href="'https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/angular-demo?v=' + $page.buildDateURIEncoded">Angular demo on Stackblitz</a>.
114 changes: 111 additions & 3 deletions docs/guide/integration-with-react.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,117 @@
# Integration with React

Installing HyperFormula in a React application works the same as with vanilla JavaScript.
The HyperFormula API is identical in a React app and in plain JavaScript. This guide demonstrates how HyperFormula is integrated with the React component tree and how its lifecycle maps to React hooks.

For more details, see the [client-side installation](client-side-installation.md) section.
Install with `npm install hyperformula`. For other options, see the [client-side installation](client-side-installation.md) section.

## Basic usage

Hold the HyperFormula instance in a `useRef` so it survives re-renders. Initialize it inside `useEffect` and release it in the cleanup function. Use `useState` to toggle between raw formulas and computed values.

```tsx
import { useEffect, useRef, useState } from 'react';
import { HyperFormula } from 'hyperformula';
import type { CellValue } from 'hyperformula';

export default function SpreadsheetComponent() {
const hfRef = useRef<HyperFormula | null>(null);
const [values, setValues] = useState<CellValue[][]>([]);

useEffect(() => {
const hf = HyperFormula.buildFromArray(
[
[1, 2, '=A1+B1'],
// your data rows go here
],
{
licenseKey: 'gpl-v3',
// more configuration options go here
}
);
hfRef.current = hf;

return () => {
hf.destroy();
hfRef.current = null;
};
}, []);

function runCalculations() {
if (!hfRef.current) return;
setValues(hfRef.current.getSheetValues(0));
}

function reset() {
setValues([]);
}

return (
<>
<button onClick={runCalculations}>Run calculations</button>
<button onClick={reset}>Reset</button>
{values.length > 0 && (
<table>
<tbody>
{values.map((row, r) => (
<tr key={r}>
{row.map((cell, c) => (
<td key={c}>{String(cell ?? '')}</td>
))}
</tr>
))}
</tbody>
</table>
)}
</>
);
}
```

If you use JavaScript instead of TypeScript, drop the type annotations — the rest of the pattern is unchanged.

## `React.StrictMode` double invocation

In development, React runs effects twice (mount → unmount → mount) to surface cleanup bugs. The pattern above is correct for StrictMode because `destroy()` runs before the re-mount creates a new instance, so no work leaks between the two lifecycles. Do not switch to a module-scoped singleton as a workaround — it will break StrictMode semantics.

## Server-side rendering (Next.js App Router)

The component above is already SSR-safe — the engine is constructed in `useEffect`, which never runs on the server. If you still want to skip the initial bundle on the server (it is a few hundred kB), wrap it in a client-only dynamic import.

In the App Router, `dynamic(..., { ssr: false })` is only allowed inside a client component. Put the dynamic call in a `'use client'` wrapper and import the wrapper from your server page:

```tsx
// app/spreadsheet/SpreadsheetLazy.tsx
'use client';
import dynamic from 'next/dynamic';

const SpreadsheetComponent = dynamic(
() => import('./SpreadsheetComponent'),
{ ssr: false }
);

export default function SpreadsheetLazy() {
return <SpreadsheetComponent />;
}
```

```tsx
// app/spreadsheet/page.tsx ← server component, no 'use client'
import SpreadsheetLazy from './SpreadsheetLazy';

export default function Page() {
return <SpreadsheetLazy />;
}
```

In the Pages Router, the same `dynamic(..., { ssr: false })` call works directly in the page file without a wrapper.

## Next steps

- [Configuration options](configuration-options.md) — full list of `buildFromArray` / `buildEmpty` options
- [Basic operations](basic-operations.md) — CRUD on cells, rows, columns, sheets
- [Advanced usage](advanced-usage.md) — multi-sheet workbooks, named expressions
- [Custom functions](custom-functions.md) — register your own formulas

## Demo

Explore the full working example on [Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/react-demo?v=${$page.buildDateURIEncoded}).
For a more advanced example, check out the <a :href="'https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/react-demo?v=' + $page.buildDateURIEncoded">React demo on Stackblitz</a>.
123 changes: 120 additions & 3 deletions docs/guide/integration-with-svelte.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,126 @@
# Integration with Svelte

Installing HyperFormula in a Svelte application works the same as with vanilla JavaScript.
The HyperFormula API is identical in a Svelte app and in plain JavaScript. This guide demonstrates how HyperFormula integrates with the Svelte component's lifecycle and how you bridge its values into Svelte's reactivity.

For more details, see the [client-side installation](client-side-installation.md) section.
Install with `npm install hyperformula`. For other options, see the [client-side installation](client-side-installation.md) section.

::: warning SvelteKit SSR
The primary snippet below assumes a browser environment. If you use SvelteKit with default SSR, skip to [Server-side rendering](#server-side-rendering-sveltekit) — `HyperFormula.buildFromArray` at `<script>` top level will run on every server render, which is unnecessary work.
:::

## Basic usage

Declare the engine at the top of `<script>` so it lives for the component's lifetime. Call `getCellValue` on demand and display results in the template. Release the engine with `onDestroy`.

```html
<script>
import { onDestroy } from 'svelte';
import { HyperFormula } from 'hyperformula';

const data = [
[1, 2, '=A1+B1'],
// your data rows go here
];

const hf = HyperFormula.buildFromArray(data, {
licenseKey: 'gpl-v3',
// more configuration options go here
});

const sheetId = 0;
/** @type {import('hyperformula').CellValue} */
let result = null;

function calculate() {
result = hf.getCellValue({ sheet: sheetId, row: 0, col: 2 });
}

function reset() {
result = null;
}

onDestroy(() => hf.destroy());
</script>

<button on:click={calculate}>Run calculations</button>
<button on:click={reset}>Reset</button>
{#if result !== null}
<p>Result: <strong>{result}</strong></p>
{/if}

<table>
<tbody>
{#each data as row, r}
<tr>
{#each row as cell, c}
<td>
{#if hf.doesCellHaveFormula({ sheet: sheetId, row: r, col: c })}
{hf.getCellFormula({ sheet: sheetId, row: r, col: c })}
{:else}
{hf.getCellValue({ sheet: sheetId, row: r, col: c })}
{/if}
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
```

## Server-side rendering (SvelteKit)

In SvelteKit, top-level statements in `<script>` run on the server too. HyperFormula has no browser-only API dependency, but instantiating it during SSR is wasted work. Move the initialization into `onMount` so it only runs on the client:

`onMount` is allowed to be `async`, but any cleanup function returned from an async callback is silently ignored — an async function always returns a `Promise`, not the cleanup. Put the teardown in a separate `onDestroy` instead:

```html
<script>
// Svelte 4 + SvelteKit
import { onDestroy, onMount } from 'svelte';

let hf;
/** @type {import('hyperformula').CellValue} */
let result = null;

onMount(async () => {
const { HyperFormula } = await import('hyperformula');
hf = HyperFormula.buildFromArray(
[
[1, 2, '=A1+B1'],
// your data rows go here
],
{ licenseKey: 'gpl-v3' }
);
});

// Separate onDestroy — async onMount cannot return a cleanup.
onDestroy(() => hf?.destroy());

function calculate() {
if (!hf) return;
result = hf.getCellValue({ sheet: 0, row: 0, col: 2 });
}

function reset() {
result = null;
}
</script>

<button on:click={calculate}>Run calculations</button>
<button on:click={reset}>Reset</button>
{#if result !== null}
<p>Result: <strong>{result}</strong></p>
{/if}
```


## Next steps

- [Configuration options](configuration-options.md) — full list of `buildFromArray` / `buildEmpty` options
- [Basic operations](basic-operations.md) — CRUD on cells, rows, columns, sheets
- [Advanced usage](advanced-usage.md) — multi-sheet workbooks, named expressions
- [Custom functions](custom-functions.md) — register your own formulas

## Demo

Explore the full working example on [Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/svelte-demo?v=${$page.buildDateURIEncoded}).
For a more advanced example, check out the <a :href="'https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/svelte-demo?v=' + $page.buildDateURIEncoded">Svelte demo on Stackblitz</a>.
Loading
Loading