From 27b9582a054e994fa32f2980a8bdf97ff500053a Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Thu, 9 Apr 2026 17:51:56 +0000 Subject: [PATCH 01/15] Docs: add basic usage snippets to framework integration guides Add idiomatic code examples to React, Angular, Vue, and Svelte integration pages showing HyperFormula initialization and reading calculated values. Each guide uses framework-specific patterns (React hooks, Angular service, Vue ref/markRaw, Svelte reactivity). Closes HF-122. --- docs/guide/integration-with-angular.md | 64 ++++++++++++++++++++++++++ docs/guide/integration-with-react.md | 56 ++++++++++++++++++++++ docs/guide/integration-with-svelte.md | 46 ++++++++++++++++++ docs/guide/integration-with-vue.md | 42 +++++++++++++++++ 4 files changed, 208 insertions(+) diff --git a/docs/guide/integration-with-angular.md b/docs/guide/integration-with-angular.md index 8f78e2097..1e6b57998 100644 --- a/docs/guide/integration-with-angular.md +++ b/docs/guide/integration-with-angular.md @@ -4,6 +4,70 @@ Installing HyperFormula in an Angular application works the same as with vanilla For more details, see the [client-side installation](client-side-installation.md) section. +## Basic usage + +### Step 1. Create a service + +Wrap HyperFormula in an Angular service. Create the instance in the constructor and expose methods for reading data. + +```typescript +import { Injectable, OnDestroy } from '@angular/core'; +import { HyperFormula } from 'hyperformula'; + +@Injectable({ providedIn: 'root' }) +export class SpreadsheetService implements OnDestroy { + private hf: HyperFormula; + private sheetId = 0; + + constructor() { + // Create a HyperFormula instance with initial data. + this.hf = HyperFormula.buildFromArray( + [ + [10, 20, '=SUM(A1:B1)'], + [30, 40, '=SUM(A2:B2)'], + ], + { licenseKey: 'gpl-v3' } + ); + } + + /** Return calculated values for the entire sheet. */ + getSheetValues(): (number | string | null)[][] { + return this.hf.getSheetValues(this.sheetId); + } + + ngOnDestroy(): void { + this.hf.destroy(); + } +} +``` + +### Step 2. Use the service in a component + +Inject the service and display the calculated values. + +```typescript +import { Component } from '@angular/core'; +import { SpreadsheetService } from './spreadsheet.service'; + +@Component({ + selector: 'app-spreadsheet', + template: ` + + + + +
{{ cell }}
+ `, +}) +export class SpreadsheetComponent { + data: (number | string | null)[][]; + + constructor(private spreadsheet: SpreadsheetService) { + this.data = this.spreadsheet.getSheetValues(); + } +} +``` + ## Demo Explore the full working example on [Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/angular-demo?v=${$page.buildDateURIEncoded}). diff --git a/docs/guide/integration-with-react.md b/docs/guide/integration-with-react.md index d4bc7fe75..1591aec85 100644 --- a/docs/guide/integration-with-react.md +++ b/docs/guide/integration-with-react.md @@ -4,6 +4,62 @@ Installing HyperFormula in a React application works the same as with vanilla Ja For more details, see the [client-side installation](client-side-installation.md) section. +## Basic usage + +### Step 1. Initialize HyperFormula + +Use `useRef` to hold the HyperFormula instance so it persists across re-renders. Initialize it inside a `useEffect` hook. + +```javascript +import { useRef, useEffect, useState } from 'react'; +import { HyperFormula } from 'hyperformula'; + +function SpreadsheetComponent() { + const hfRef = useRef(null); + const [sheetData, setSheetData] = useState([]); + + useEffect(() => { + // Create a HyperFormula instance with initial data. + hfRef.current = HyperFormula.buildFromArray( + [ + [10, 20, '=SUM(A1:B1)'], + [30, 40, '=SUM(A2:B2)'], + ], + { licenseKey: 'gpl-v3' } + ); + + // Read calculated values from the sheet. + const sheetId = 0; + setSheetData(hfRef.current.getSheetValues(sheetId)); + + return () => { + // Clean up the instance when the component unmounts. + hfRef.current?.destroy(); + }; + }, []); +``` + +### Step 2. Render the results + +Display the calculated values in a table. + +```javascript + return ( + + + {sheetData.map((row, rowIdx) => ( + + {row.map((cell, colIdx) => ( + + ))} + + ))} + +
{cell}
+ ); +} +``` + ## Demo Explore the full working example on [Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/react-demo?v=${$page.buildDateURIEncoded}). diff --git a/docs/guide/integration-with-svelte.md b/docs/guide/integration-with-svelte.md index 8b3a5f4b6..bd8aff85d 100644 --- a/docs/guide/integration-with-svelte.md +++ b/docs/guide/integration-with-svelte.md @@ -4,6 +4,52 @@ Installing HyperFormula in a Svelte application works the same as with vanilla J For more details, see the [client-side installation](client-side-installation.md) section. +## Basic usage + +### Step 1. Initialize HyperFormula + +Create the HyperFormula instance directly in the component's ` +``` + +### Step 2. Render the results + +Display the data in a table and trigger calculation with a button. + +```html + + + + {#each data as row, rowIdx} + + {#each row as cell, colIdx} + + {/each} + + {/each} +
{cell}
+``` + ## Demo Explore the full working example on [Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/svelte-demo?v=${$page.buildDateURIEncoded}). diff --git a/docs/guide/integration-with-vue.md b/docs/guide/integration-with-vue.md index eaa104e0e..68afa5a8e 100644 --- a/docs/guide/integration-with-vue.md +++ b/docs/guide/integration-with-vue.md @@ -4,6 +4,48 @@ Installing HyperFormula in a Vue application works the same as with vanilla Java For more details, see the [client-side installation](client-side-installation.md) section. +## Basic usage + +### Step 1. Initialize HyperFormula + +Wrap the HyperFormula instance in `markRaw` to prevent Vue's reactivity system from converting it into a proxy (see [Troubleshooting](#vue-reactivity-issues) below). Use `ref` for the data you want to display. + +```javascript + +``` + +### Step 2. Render the results + +Display the calculated values in a template. + +```html + +``` + ## Troubleshooting ### Vue reactivity issues From e920dcc321c647e0801649bd3467370a394862af Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Mon, 13 Apr 2026 10:15:05 +0000 Subject: [PATCH 02/15] Docs: simplify snippets and clarify Demo link wording Per feedback, snippets now focus on framework-specific integration patterns (useRef/useEffect, @Injectable, markRaw/ref, Svelte reactivity) with placeholder comments for data and configuration rather than concrete values. The Demo section link is reworded from "Explore the full working example" to "For a more advanced example" to signal that the Stackblitz demo is a richer, separate project. Part of HF-122. --- docs/guide/integration-with-angular.md | 51 ++++++++------------------ docs/guide/integration-with-react.md | 50 ++++++++----------------- docs/guide/integration-with-svelte.md | 40 +++++--------------- docs/guide/integration-with-vue.md | 39 +++++++------------- 4 files changed, 55 insertions(+), 125 deletions(-) diff --git a/docs/guide/integration-with-angular.md b/docs/guide/integration-with-angular.md index 1e6b57998..b28e1fac0 100644 --- a/docs/guide/integration-with-angular.md +++ b/docs/guide/integration-with-angular.md @@ -6,9 +6,7 @@ For more details, see the [client-side installation](client-side-installation.md ## Basic usage -### Step 1. Create a service - -Wrap HyperFormula in an Angular service. Create the instance in the constructor and expose methods for reading data. +Wrap the HyperFormula instance in an injectable service. Create it in the constructor, expose methods for reading and updating data, and destroy it in `ngOnDestroy`. ```typescript import { Injectable, OnDestroy } from '@angular/core'; @@ -17,57 +15,40 @@ import { HyperFormula } from 'hyperformula'; @Injectable({ providedIn: 'root' }) export class SpreadsheetService implements OnDestroy { private hf: HyperFormula; - private sheetId = 0; constructor() { - // Create a HyperFormula instance with initial data. this.hf = HyperFormula.buildFromArray( [ - [10, 20, '=SUM(A1:B1)'], - [30, 40, '=SUM(A2:B2)'], + // your data goes here ], - { licenseKey: 'gpl-v3' } + { + // your configuration goes here + } ); } - /** Return calculated values for the entire sheet. */ - getSheetValues(): (number | string | null)[][] { - return this.hf.getSheetValues(this.sheetId); + getSheetValues() { + return this.hf.getSheetValues(0); + } + + updateCell(row: number, col: number, value: unknown) { + this.hf.setCellContents({ sheet: 0, row, col }, value); } - ngOnDestroy(): void { + ngOnDestroy() { this.hf.destroy(); } } ``` -### Step 2. Use the service in a component - -Inject the service and display the calculated values. +Inject the service into a component and use it to read values or react to user input: ```typescript -import { Component } from '@angular/core'; -import { SpreadsheetService } from './spreadsheet.service'; +constructor(private readonly spreadsheet: SpreadsheetService) {} -@Component({ - selector: 'app-spreadsheet', - template: ` - - - - -
{{ cell }}
- `, -}) -export class SpreadsheetComponent { - data: (number | string | null)[][]; - - constructor(private spreadsheet: SpreadsheetService) { - this.data = this.spreadsheet.getSheetValues(); - } -} +values = this.spreadsheet.getSheetValues(); ``` ## 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 [Angular demo on Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/angular-demo?v=${$page.buildDateURIEncoded}). diff --git a/docs/guide/integration-with-react.md b/docs/guide/integration-with-react.md index 1591aec85..3baa407f5 100644 --- a/docs/guide/integration-with-react.md +++ b/docs/guide/integration-with-react.md @@ -6,60 +6,42 @@ For more details, see the [client-side installation](client-side-installation.md ## Basic usage -### Step 1. Initialize HyperFormula - -Use `useRef` to hold the HyperFormula instance so it persists across re-renders. Initialize it inside a `useEffect` hook. +Hold the HyperFormula instance in a `useRef` so it persists across re-renders. Initialize it inside a `useEffect` hook and destroy it in the cleanup function. ```javascript -import { useRef, useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { HyperFormula } from 'hyperformula'; function SpreadsheetComponent() { const hfRef = useRef(null); - const [sheetData, setSheetData] = useState([]); + const [values, setValues] = useState([]); useEffect(() => { - // Create a HyperFormula instance with initial data. hfRef.current = HyperFormula.buildFromArray( [ - [10, 20, '=SUM(A1:B1)'], - [30, 40, '=SUM(A2:B2)'], + // your data goes here ], - { licenseKey: 'gpl-v3' } + { + // your configuration goes here + } ); - // Read calculated values from the sheet. - const sheetId = 0; - setSheetData(hfRef.current.getSheetValues(sheetId)); + setValues(hfRef.current.getSheetValues(0)); - return () => { - // Clean up the instance when the component unmounts. - hfRef.current?.destroy(); - }; + return () => hfRef.current?.destroy(); }, []); -``` -### Step 2. Render the results + // render `values` as a table +} +``` -Display the calculated values in a table. +To update a cell and re-render with the new calculated values, call `setCellContents` and re-read the sheet: ```javascript - return ( - - - {sheetData.map((row, rowIdx) => ( - - {row.map((cell, colIdx) => ( - - ))} - - ))} - -
{cell}
- ); -} +hfRef.current.setCellContents({ sheet: 0, row, col }, newValue); +setValues(hfRef.current.getSheetValues(0)); ``` ## 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 [React demo on Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/react-demo?v=${$page.buildDateURIEncoded}). diff --git a/docs/guide/integration-with-svelte.md b/docs/guide/integration-with-svelte.md index bd8aff85d..bd20fbf07 100644 --- a/docs/guide/integration-with-svelte.md +++ b/docs/guide/integration-with-svelte.md @@ -6,50 +6,30 @@ For more details, see the [client-side installation](client-side-installation.md ## Basic usage -### Step 1. Initialize HyperFormula - -Create the HyperFormula instance directly in the component's ` ``` -### Step 2. Render the results - -Display the data in a table and trigger calculation with a button. - -```html - - - - {#each data as row, rowIdx} - - {#each row as cell, colIdx} - - {/each} - - {/each} -
{cell}
-``` - ## 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 [Svelte demo on Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/svelte-demo?v=${$page.buildDateURIEncoded}). diff --git a/docs/guide/integration-with-vue.md b/docs/guide/integration-with-vue.md index 68afa5a8e..611ca7bf8 100644 --- a/docs/guide/integration-with-vue.md +++ b/docs/guide/integration-with-vue.md @@ -6,44 +6,31 @@ For more details, see the [client-side installation](client-side-installation.md ## Basic usage -### Step 1. Initialize HyperFormula - -Wrap the HyperFormula instance in `markRaw` to prevent Vue's reactivity system from converting it into a proxy (see [Troubleshooting](#vue-reactivity-issues) below). Use `ref` for the data you want to display. +Wrap the HyperFormula instance in `markRaw` to prevent Vue from converting it into a reactive proxy (see [Troubleshooting](#vue-reactivity-issues) below). Hold derived data in `ref` so the template updates automatically. ```javascript -``` - -### Step 2. Render the results - -Display the calculated values in a template. +const values = ref(hf.getSheetValues(0)); -```html - +function updateCell(row, col, value) { + hf.setCellContents({ sheet: 0, row, col }, value); + values.value = hf.getSheetValues(0); +} + ``` ## Troubleshooting @@ -73,7 +60,7 @@ This function prevents Vue from converting the HyperFormula instance into a reac ## Demo -Explore the full working example on [Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/vue-3-demo?v=${$page.buildDateURIEncoded}). +For a more advanced example, check out the [Vue 3 demo on Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/vue-3-demo?v=${$page.buildDateURIEncoded}). ::: tip This demo uses the [Vue 3](https://v3.vuejs.org/) framework. If you are looking for an example using Vue 2, check out the [code on GitHub](https://github.com/handsontable/hyperformula-demos/tree/2.5.x/vue-demo). From 88056b724410d1d541df3bb02785d0bae9f6adc7 Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Mon, 13 Apr 2026 15:08:29 +0000 Subject: [PATCH 03/15] Docs: modernize framework integration guides with idiomatic patterns Expand React, Angular, Vue and Svelte integration guides to cover framework-specific concerns that the previous skeleton snippets omitted: - Convert all snippets to TypeScript with HyperFormula/CellValue imports - Add licenseKey: 'gpl-v3' to every buildFromArray call - Inline `npm install hyperformula` install command - React: runnable JSX with controlled inputs, try/catch around setCellContents, React.StrictMode note, Next.js App Router SSR section with 'use client' and dynamic import - Angular: modern standalone component + inject() + DestroyRef + Signals + OnPush, RxJS BehaviorSubject variant with AsyncPipe import note, NgZone runOutsideAngular section, provider-scope + DestroyRef rationale - Vue: + + + {#each values as row, r} + + {#each row as cell, c} + + {/each} + + {/each} +
+ updateCell(r, c, (e.target as HTMLInputElement).value)} + /> +
``` +### Svelte 4 + +Under Svelte 4, replace `let values = $state(...)` with plain `let values: CellValue[][] = ...`. The compiler transforms `let` reassignments into reactive updates automatically. Use `on:change` (colon syntax) instead of the Svelte 5 `onchange` attribute. Everything else — including `onDestroy` — is unchanged. + +## Server-side rendering (SvelteKit) + +HyperFormula depends on browser-only APIs. In SvelteKit, initialize the engine inside `onMount` so the code never runs during SSR: + +```svelte + +``` + +Wire `updateCell` in the template the same way as in the Basic usage section (``). For Svelte 4, replace `let values = $state([])` with `let values: CellValue[][] = []`; the rest is identical. As an alternative, guard the top-level init with `if (browser)` from `$app/environment`. + ## Demo For a more advanced example, check out the [Svelte demo on Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/svelte-demo?v=${$page.buildDateURIEncoded}). diff --git a/docs/guide/integration-with-vue.md b/docs/guide/integration-with-vue.md index 611ca7bf8..296797f87 100644 --- a/docs/guide/integration-with-vue.md +++ b/docs/guide/integration-with-vue.md @@ -1,38 +1,107 @@ # Integration with Vue -Installing HyperFormula in a Vue application works the same as with vanilla JavaScript. +The HyperFormula API is identical in a Vue 3 app and in plain JavaScript. What changes is how you keep the engine out of Vue's reactivity system (critical) and how you surface its values into the template. -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 HyperFormula instance in `markRaw` to prevent Vue from converting it into a reactive proxy (see [Troubleshooting](#vue-reactivity-issues) below). Hold derived data in `ref` so the template updates automatically. +Wrap the HyperFormula instance in `markRaw` so Vue does not convert it into a reactive proxy (see [Troubleshooting](#vue-reactivity-issues) below). Hold derived data in `ref` so the template updates when you reassign the ref's `.value`. -```javascript - + + +``` + +## Notes + +### Server-side rendering (Nuxt) + +HyperFormula depends on browser-only APIs. In Nuxt, render the spreadsheet on the client only — wrap the component with `` or instantiate the instance inside `onMounted` rather than at the top of ` ``` +### Reacting to internal changes + +If you mutate the engine from multiple places (not just one `updateCell`), subscribe to HyperFormula's `valuesUpdated` event once and refresh `values.value` from the handler rather than calling `getSheetValues` after every mutation: + +```typescript +hf.on('valuesUpdated', () => { + values.value = hf.getSheetValues(0); +}); +``` + +### Sharing the instance across components (Pinia) + +If the same engine is used from multiple components, put it in a Pinia store. Apply `markRaw` inside the store so Pinia does not proxy the instance: + +```typescript +import { defineStore } from 'pinia'; +import { markRaw, ref } from 'vue'; +import { HyperFormula, type CellValue } from 'hyperformula'; + +export const useSpreadsheetStore = defineStore('spreadsheet', () => { + const hf = markRaw(HyperFormula.buildFromArray([/* data */], { licenseKey: 'gpl-v3' })); + const values = ref(hf.getSheetValues(0)); + + function updateCell(row: number, col: number, value: unknown) { + hf.setCellContents({ sheet: 0, row, col }, value); + values.value = hf.getSheetValues(0); + } + + return { hf, values, updateCell }; +}); +``` + ## Troubleshooting ### Vue reactivity issues @@ -43,20 +112,20 @@ If you encounter an error like Uncaught TypeError: Cannot read properties of undefined (reading 'licenseKeyValidityState') ``` -it means that Vue's reactivity system tries to deeply observe the HyperFormula instance. To fix this, wrap your HyperFormula instance in Vue's [`markRaw`](https://vuejs.org/api/reactivity-advanced.html#markraw) function: +it means that Vue's reactivity system tried to deeply observe the HyperFormula instance. Vue wraps reactive objects in a `Proxy` that intercepts every property access; when that proxy reaches a non-trivial instance with its own internal state, identity checks and lazy-initialized maps break. The fix is to opt the instance out of reactivity with Vue's [`markRaw`](https://vuejs.org/api/reactivity-advanced.html#markraw): -```javascript +```typescript import { markRaw } from 'vue'; import { HyperFormula } from 'hyperformula'; const hfInstance = markRaw( HyperFormula.buildEmpty({ - licenseKey: 'internal-use-in-handsontable', + licenseKey: 'gpl-v3', }) ); ``` -This function prevents Vue from converting the HyperFormula instance into a reactive proxy, which can cause errors and performance issues. +`shallowRef` is not a substitute: it skips proxying only at the top level, so writing the instance into a nested reactive structure (Pinia state, `reactive({...})`) will still wrap it. Always pass the instance itself through `markRaw` before putting it anywhere Vue can reach. ## Demo From 9571737f5a5a0986d2458f2e644dcadd4f3012ec Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Mon, 13 Apr 2026 15:09:05 +0000 Subject: [PATCH 04/15] Docs: add Next steps cross-links to framework integration guides Point readers from each integration guide to related topics they are likely to need next: configuration options, basic operations (CRUD), advanced usage (multi-sheet, named expressions), and custom functions. Identical section in all four guides so the navigation experience is consistent when switching between frameworks. --- docs/guide/integration-with-angular.md | 7 +++++++ docs/guide/integration-with-react.md | 7 +++++++ docs/guide/integration-with-svelte.md | 7 +++++++ docs/guide/integration-with-vue.md | 7 +++++++ 4 files changed, 28 insertions(+) diff --git a/docs/guide/integration-with-angular.md b/docs/guide/integration-with-angular.md index 8bd48170b..ee6941c1a 100644 --- a/docs/guide/integration-with-angular.md +++ b/docs/guide/integration-with-angular.md @@ -124,6 +124,13 @@ updateCell(row: number, col: number, value: unknown) { } ``` +## 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 For a more advanced example, check out the [Angular demo on Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/angular-demo?v=${$page.buildDateURIEncoded}). diff --git a/docs/guide/integration-with-react.md b/docs/guide/integration-with-react.md index 5bc7223af..f5516bd64 100644 --- a/docs/guide/integration-with-react.md +++ b/docs/guide/integration-with-react.md @@ -98,6 +98,13 @@ export default function Page() { In the Pages Router, the same `dynamic(..., { ssr: false })` pattern works without `'use client'`. +## 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 For a more advanced example, check out the [React demo on Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/react-demo?v=${$page.buildDateURIEncoded}). diff --git a/docs/guide/integration-with-svelte.md b/docs/guide/integration-with-svelte.md index a8b70143f..e0ad502ea 100644 --- a/docs/guide/integration-with-svelte.md +++ b/docs/guide/integration-with-svelte.md @@ -95,6 +95,13 @@ HyperFormula depends on browser-only APIs. In SvelteKit, initialize the engine i Wire `updateCell` in the template the same way as in the Basic usage section (``). For Svelte 4, replace `let values = $state([])` with `let values: CellValue[][] = []`; the rest is identical. As an alternative, guard the top-level init with `if (browser)` from `$app/environment`. +## 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 For a more advanced example, check out the [Svelte demo on Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/svelte-demo?v=${$page.buildDateURIEncoded}). diff --git a/docs/guide/integration-with-vue.md b/docs/guide/integration-with-vue.md index 296797f87..131c63f4d 100644 --- a/docs/guide/integration-with-vue.md +++ b/docs/guide/integration-with-vue.md @@ -127,6 +127,13 @@ const hfInstance = markRaw( `shallowRef` is not a substitute: it skips proxying only at the top level, so writing the instance into a nested reactive structure (Pinia state, `reactive({...})`) will still wrap it. Always pass the instance itself through `markRaw` before putting it anywhere Vue can reach. +## 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 For a more advanced example, check out the [Vue 3 demo on Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/vue-3-demo?v=${$page.buildDateURIEncoded}). From bd84ce9cd6b8a2285aed597e26e079ae09721f23 Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Mon, 13 Apr 2026 15:09:38 +0000 Subject: [PATCH 05/15] Docs: fix $page.buildDateURIEncoded template syntax in framework guides The Stackblitz demo links used \${...} JavaScript template literal syntax, which is not interpolated by VuePress. VuePress 1 processes markdown as Vue templates, so the correct syntax for accessing the injected \$page.buildDateURIEncoded variable is Vue's {{ ... }} interpolation. config.js line 79 documents the intended syntax explicitly: // inject current HF buildDate URI encoded as {{ \$page.buildDateURIEncoded }} variable Before this fix, the cache-buster query string rendered as a literal ?v=\${\$page.buildDateURIEncoded} in the URL, breaking the cache-busting behaviour on every Stackblitz demo link. Note: the same issue exists in docs/guide/custom-functions.md:361 from an earlier change; that is left for a separate fix. --- docs/guide/integration-with-angular.md | 2 +- docs/guide/integration-with-react.md | 2 +- docs/guide/integration-with-svelte.md | 2 +- docs/guide/integration-with-vue.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/guide/integration-with-angular.md b/docs/guide/integration-with-angular.md index ee6941c1a..17aa31029 100644 --- a/docs/guide/integration-with-angular.md +++ b/docs/guide/integration-with-angular.md @@ -133,4 +133,4 @@ updateCell(row: number, col: number, value: unknown) { ## Demo -For a more advanced example, check out the [Angular demo 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 [Angular demo on Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/angular-demo?v={{ $page.buildDateURIEncoded }}). diff --git a/docs/guide/integration-with-react.md b/docs/guide/integration-with-react.md index f5516bd64..6bc6d9767 100644 --- a/docs/guide/integration-with-react.md +++ b/docs/guide/integration-with-react.md @@ -107,4 +107,4 @@ In the Pages Router, the same `dynamic(..., { ssr: false })` pattern works witho ## Demo -For a more advanced example, check out the [React demo 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 [React demo on Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/react-demo?v={{ $page.buildDateURIEncoded }}). diff --git a/docs/guide/integration-with-svelte.md b/docs/guide/integration-with-svelte.md index e0ad502ea..48ce98cee 100644 --- a/docs/guide/integration-with-svelte.md +++ b/docs/guide/integration-with-svelte.md @@ -104,4 +104,4 @@ Wire `updateCell` in the template the same way as in the Basic usage section (`< ## Demo -For a more advanced example, check out the [Svelte demo 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 [Svelte demo on Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/svelte-demo?v={{ $page.buildDateURIEncoded }}). diff --git a/docs/guide/integration-with-vue.md b/docs/guide/integration-with-vue.md index 131c63f4d..1c4b46860 100644 --- a/docs/guide/integration-with-vue.md +++ b/docs/guide/integration-with-vue.md @@ -136,7 +136,7 @@ const hfInstance = markRaw( ## Demo -For a more advanced example, check out the [Vue 3 demo on Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/vue-3-demo?v=${$page.buildDateURIEncoded}). +For a more advanced example, check out the [Vue 3 demo on Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/vue-3-demo?v={{ $page.buildDateURIEncoded }}). ::: tip This demo uses the [Vue 3](https://v3.vuejs.org/) framework. If you are looking for an example using Vue 2, check out the [code on GitHub](https://github.com/handsontable/hyperformula-demos/tree/2.5.x/vue-demo). From 87c2f4682daf456b70083d81c31580ab6064d6f7 Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Tue, 14 Apr 2026 03:48:17 +0000 Subject: [PATCH 06/15] =?UTF-8?q?Docs:=20fix=20setCellContents=20type=20in?= =?UTF-8?q?=20guide=20snippets=20(unknown=20=E2=86=92=20RawCellContent)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tsc smoke check found that updateCell(value: unknown) does not satisfy setCellContents which expects RawCellContent. Replace unknown with the correct type and add missing RawCellContent imports in Angular, Vue and Svelte guides. --- docs/guide/integration-with-angular.md | 10 +++++----- docs/guide/integration-with-svelte.md | 6 +++--- docs/guide/integration-with-vue.md | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/guide/integration-with-angular.md b/docs/guide/integration-with-angular.md index 17aa31029..802b765a5 100644 --- a/docs/guide/integration-with-angular.md +++ b/docs/guide/integration-with-angular.md @@ -11,7 +11,7 @@ Wrap the engine in an `@Injectable` service. Use `DestroyRef` for cleanup and ex ```typescript // spreadsheet.service.ts import { DestroyRef, Injectable, inject, signal } from '@angular/core'; -import { CellValue, HyperFormula } from 'hyperformula'; +import { CellValue, HyperFormula, RawCellContent } from 'hyperformula'; @Injectable({ providedIn: 'root' }) export class SpreadsheetService { @@ -33,7 +33,7 @@ export class SpreadsheetService { inject(DestroyRef).onDestroy(() => this.hf.destroy()); } - updateCell(row: number, col: number, value: unknown) { + updateCell(row: number, col: number, value: RawCellContent) { this.hf.setCellContents({ sheet: 0, row, col }, value); this.values.set(this.hf.getSheetValues(0)); } @@ -75,7 +75,7 @@ If your project uses RxJS and `async` pipes rather than signals, swap `signal` f ```typescript import { DestroyRef, Injectable, inject } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; -import { CellValue, HyperFormula } from 'hyperformula'; +import { CellValue, HyperFormula, RawCellContent } from 'hyperformula'; @Injectable({ providedIn: 'root' }) export class SpreadsheetService { @@ -86,7 +86,7 @@ export class SpreadsheetService { inject(DestroyRef).onDestroy(() => this.hf.destroy()); } - updateCell(row: number, col: number, value: unknown) { + updateCell(row: number, col: number, value: RawCellContent) { this.hf.setCellContents({ sheet: 0, row, col }, value); this.values$.next(this.hf.getSheetValues(0)); } @@ -115,7 +115,7 @@ import { NgZone } from '@angular/core'; private readonly ngZone = inject(NgZone); -updateCell(row: number, col: number, value: unknown) { +updateCell(row: number, col: number, value: RawCellContent) { this.ngZone.runOutsideAngular(() => { this.hf.setCellContents({ sheet: 0, row, col }, value); const next = this.hf.getSheetValues(0); diff --git a/docs/guide/integration-with-svelte.md b/docs/guide/integration-with-svelte.md index 48ce98cee..7bb07e1e6 100644 --- a/docs/guide/integration-with-svelte.md +++ b/docs/guide/integration-with-svelte.md @@ -15,7 +15,7 @@ Declare the engine at the top of ` + + +{#if result !== ''} +

Result: {result}

+{/if} + + + + {#each data as row, r} + + {#each row as cell, c} + + {/each} + + {/each} + +
+ {#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} +
+``` + +### Svelte 5 (runes) + +Under Svelte 5, hold the sheet values in a `$state` rune so reassignment triggers re-rendering. Replace `on:click` with the `onclick` attribute: ```svelte + + +{#if result !== ''} +

Result: {result}

+{/if} ``` -Wire `updateCell` in the template the same way as in the Basic usage section (``). For Svelte 4, replace `let values = $state([])` with `let values: CellValue[][] = []`; the rest is identical. As an alternative, guard the top-level init with `if (browser)` from `$app/environment`. +For Svelte 5 + SvelteKit, replace `let result = ''` with `let result = $state('')` and `on:click` with `onclick`. As an alternative, guard the top-level init with `if (browser)` from `$app/environment`. ## Next steps From 655274a110fc5a070b59a124c9a9cc2d99f76791 Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Wed, 15 Apr 2026 09:39:40 +0000 Subject: [PATCH 09/15] Docs: fix P0/P1 issues found in framework guide expert review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit React: fix Next.js SSR pattern — 'use client' belongs in the component file, not on page.tsx; server page uses dynamic(..., { ssr: false }) Angular: add CommonModule/standalone imports for *ngFor + async pipe; add @for template for signals variant; add inject() context note Vue: replace shallowRef with ref in Nuxt SSR snippet (shallowRef vs markRaw contradiction); add hf.off() teardown to valuesUpdated example; remove hf from Pinia store return value (devtools serialisation risk) Svelte: fix CellValue type for result variable; fix SSR snippet to use onMount return for cleanup instead of separate onDestroy Co-Authored-By: Claude Sonnet 4.6 --- docs/guide/integration-with-angular.md | 22 ++++++++++++++++++++-- docs/guide/integration-with-react.md | 11 ++++++++--- docs/guide/integration-with-svelte.md | 18 ++++++++++-------- docs/guide/integration-with-vue.md | 18 ++++++++++++------ 4 files changed, 50 insertions(+), 19 deletions(-) diff --git a/docs/guide/integration-with-angular.md b/docs/guide/integration-with-angular.md index 819b1fee4..ec42fa022 100644 --- a/docs/guide/integration-with-angular.md +++ b/docs/guide/integration-with-angular.md @@ -47,6 +47,7 @@ Consume the service from a component and bind `values$ | async` in the template: ```typescript // spreadsheet.component.ts import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; import { Observable } from 'rxjs'; import { SpreadsheetService } from './spreadsheet.service'; import { type CellValue } from 'hyperformula'; @@ -54,6 +55,10 @@ import { type CellValue } from 'hyperformula'; @Component({ selector: 'app-spreadsheet', templateUrl: './spreadsheet.component.html', + // For NgModule-based apps, declare this component in AppModule and import + // CommonModule there instead of in the component itself. + imports: [CommonModule], + standalone: true, }) export class SpreadsheetComponent { values$: Observable; @@ -100,7 +105,7 @@ export class SpreadsheetService implements OnDestroy { } ``` -For a scope-agnostic alternative, use `DestroyRef` — it fires at the right moment whether the service is root-scoped or component-scoped: +For a scope-agnostic alternative, use `DestroyRef` — it fires at the right moment whether the service is root-scoped or component-scoped. Call `inject()` synchronously inside the constructor (or as a field initializer); it cannot be called in lifecycle hooks or async callbacks: ```typescript import { DestroyRef, inject } from '@angular/core'; @@ -136,7 +141,7 @@ export class SpreadsheetService { } ``` -Use the service from a standalone component with `ChangeDetectionStrategy.OnPush`: +Use the service from a standalone component with `ChangeDetectionStrategy.OnPush`. Read `spreadsheet.values()` in the template — Angular automatically re-renders when the signal changes: ```typescript import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; @@ -153,6 +158,19 @@ export class SpreadsheetComponent { } ``` +```html + + + @for (row of spreadsheet.values(); track $index; let r = $index) { + + @for (cell of row; track $index) { + + } + + } +
{{ cell }}
+``` + ### Keeping recalculation outside the Angular zone For large sheets or frequent edits, HyperFormula's synchronous recalculation can trigger unnecessary change-detection cycles. Wrap heavy calls with `NgZone.runOutsideAngular` and re-enter the zone only when publishing new values: diff --git a/docs/guide/integration-with-react.md b/docs/guide/integration-with-react.md index 6bc6d9767..62141f839 100644 --- a/docs/guide/integration-with-react.md +++ b/docs/guide/integration-with-react.md @@ -79,13 +79,18 @@ In development, React 18 runs effects twice (mount → unmount → mount) to sur ### Server-side rendering (Next.js) -HyperFormula depends on browser-only APIs. In the Next.js App Router, mark the file with `'use client'` and load the component only on the client: +HyperFormula depends on browser-only APIs. Mark `SpreadsheetComponent.tsx` with `'use client'`, then load it lazily from a **server** page component using `dynamic(..., { ssr: false })`: ```tsx -// app/spreadsheet/page.tsx +// app/spreadsheet/SpreadsheetComponent.tsx 'use client'; +// ... component definition as above +``` +```tsx +// app/spreadsheet/page.tsx ← server component, no 'use client' import dynamic from 'next/dynamic'; + const SpreadsheetComponent = dynamic( () => import('./SpreadsheetComponent').then((m) => m.SpreadsheetComponent), { ssr: false } @@ -96,7 +101,7 @@ export default function Page() { } ``` -In the Pages Router, the same `dynamic(..., { ssr: false })` pattern works without `'use client'`. +Putting `'use client'` on `page.tsx` itself would make the entire page a client component — the point is to keep the page server-rendered and only hydrate the spreadsheet widget on the client. In the Pages Router, the same `dynamic(..., { ssr: false })` pattern works without `'use client'`. ## Next steps diff --git a/docs/guide/integration-with-svelte.md b/docs/guide/integration-with-svelte.md index b2c35e320..d9599bf83 100644 --- a/docs/guide/integration-with-svelte.md +++ b/docs/guide/integration-with-svelte.md @@ -28,7 +28,8 @@ Declare the engine at the top of ` -{#if result !== ''} +{#if result !== null}

Result: {result}

{/if} @@ -114,10 +115,11 @@ HyperFormula depends on browser-only APIs. In SvelteKit, initialize the engine i ```svelte -{#if result !== ''} +{#if result !== null}

Result: {result}

{/if} ``` -For Svelte 5 + SvelteKit, replace `let result = ''` with `let result = $state('')` and `on:click` with `onclick`. As an alternative, guard the top-level init with `if (browser)` from `$app/environment`. +For Svelte 5 + SvelteKit, replace `let result = null` with `let result: CellValue = $state(null)` and `on:click` with `onclick`. As an alternative, guard the top-level init with `if (browser)` from `$app/environment`. ## Next steps diff --git a/docs/guide/integration-with-vue.md b/docs/guide/integration-with-vue.md index 3e6970450..cc45b1362 100644 --- a/docs/guide/integration-with-vue.md +++ b/docs/guide/integration-with-vue.md @@ -53,14 +53,15 @@ HyperFormula depends on browser-only APIs. In Nuxt, render the spreadsheet on th ```vue ``` +The class keeps the HyperFormula instance as a private field, so Vue's reactivity Proxy never reaches it. This is the same pattern used in the [Vue 3 demo](#demo). + ## Notes +### Using `markRaw` instead of a class wrapper + +If you prefer to use the HyperFormula instance directly in ` +``` + +`markRaw` is essential when the instance is exposed directly — without it, Vue wraps it in a Proxy that breaks internal state. The class wrapper approach avoids this because the instance stays private. + ### Server-side rendering (Nuxt) HyperFormula depends on browser-only APIs. In Nuxt, render the spreadsheet on the client only — wrap the component with `` or instantiate the instance inside `onMounted` rather than at the top of ` - - - {#each values as row, r} - - {#each row as cell, c} - - {/each} - - {/each} -
- updateCell(r, c, (e.target as HTMLInputElement).value)} - /> -
-``` - ## Server-side rendering (SvelteKit) HyperFormula depends on browser-only APIs. In SvelteKit, initialize the engine inside `onMount` so the code never runs during SSR: @@ -146,7 +100,6 @@ HyperFormula depends on browser-only APIs. In SvelteKit, initialize the engine i {/if} ``` -For Svelte 5 + SvelteKit, replace `let result = null` with `let result: CellValue = $state(null)` and `on:click` with `onclick`. As an alternative, guard the top-level init with `if (browser)` from `$app/environment`. ## Next steps diff --git a/docs/guide/integration-with-vue.md b/docs/guide/integration-with-vue.md index a52daee07..4d851d3a3 100644 --- a/docs/guide/integration-with-vue.md +++ b/docs/guide/integration-with-vue.md @@ -72,92 +72,9 @@ The class keeps the HyperFormula instance as a private field, so Vue's reactivit ## Notes -### Using `markRaw` instead of a class wrapper - -If you prefer to use the HyperFormula instance directly in ` -``` - -`markRaw` is essential when the instance is exposed directly — without it, Vue wraps it in a Proxy that breaks internal state. The class wrapper approach avoids this because the instance stays private. - ### Server-side rendering (Nuxt) -HyperFormula depends on browser-only APIs. In Nuxt, render the spreadsheet on the client only — wrap the component with `` or instantiate the instance inside `onMounted` rather than at the top of ` -``` - -### Reacting to internal changes - -If you mutate the engine from multiple places (not just one `updateCell`), subscribe to HyperFormula's `valuesUpdated` event once and refresh `values.value` from the handler rather than calling `getSheetValues` after every mutation. Always remove the listener in `onUnmounted` to avoid stale closures: - -```typescript -function onValuesUpdated() { - values.value = hf.getSheetValues(0); -} - -hf.on('valuesUpdated', onValuesUpdated); -onUnmounted(() => hf.off('valuesUpdated', onValuesUpdated)); -``` - -### Sharing the instance across components (Pinia) - -If the same engine is used from multiple components, put it in a Pinia store. Apply `markRaw` inside the store so Pinia does not proxy the instance: - -```typescript -import { defineStore } from 'pinia'; -import { markRaw, ref } from 'vue'; -import { HyperFormula, type CellValue, type RawCellContent } from 'hyperformula'; - -export const useSpreadsheetStore = defineStore('spreadsheet', () => { - // Keep hf private — exposing the raw instance lets callers bypass the store's - // updateCell and also risks Pinia devtools trying to serialise it. - const hf = markRaw(HyperFormula.buildFromArray([/* data */], { licenseKey: 'gpl-v3' })); - const values = ref(hf.getSheetValues(0)); - - function updateCell(row: number, col: number, value: RawCellContent) { - hf.setCellContents({ sheet: 0, row, col }, value); - values.value = hf.getSheetValues(0); - } - - return { values, updateCell }; -}); -``` +HyperFormula depends on browser-only APIs. In Nuxt, render the spreadsheet on the client only by wrapping the component with ``. ## Troubleshooting From 545ea1e5dbd0f6d427dcb69b47e6f63677011aac Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Thu, 16 Apr 2026 10:36:11 +0000 Subject: [PATCH 13/15] Docs: align all guide snippets with demo interaction patterns All four Stackblitz demos use the same interaction model: "Run calculations" and "Reset" buttons with read-only table output. Aligned guides to match: React: replaced editable with button-driven calculate/reset. The onChange-per-keystroke pattern was not from the demo and caused issues with partial formula input. Angular: replaced standalone component with NgModule pattern (matching demo). Replaced updateCell with calculate/reset methods matching EmployeesService. Added buttons to template. Vue: replaced dead handleUpdate/updateCell with getCalculatedValues/ getRawFormulas matching EmployeesDataProvider. Added Run/Reset buttons. --- docs/guide/integration-with-angular.md | 32 ++++++++++--------- docs/guide/integration-with-react.md | 43 ++++++++++++++------------ docs/guide/integration-with-vue.md | 25 +++++++++------ 3 files changed, 56 insertions(+), 44 deletions(-) diff --git a/docs/guide/integration-with-angular.md b/docs/guide/integration-with-angular.md index 14e7477e0..8ea0dd776 100644 --- a/docs/guide/integration-with-angular.md +++ b/docs/guide/integration-with-angular.md @@ -12,7 +12,7 @@ Wrap the engine in an `@Injectable` service backed by a `BehaviorSubject`. Compo // spreadsheet.service.ts import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; -import { HyperFormula, type CellValue, type RawCellContent } from 'hyperformula'; +import { HyperFormula, type CellValue } from 'hyperformula'; @Injectable({ providedIn: 'root' }) export class SpreadsheetService { @@ -35,19 +35,21 @@ export class SpreadsheetService { this._values.next(this.hf.getSheetValues(0)); } - updateCell(row: number, col: number, value: RawCellContent) { - this.hf.setCellContents({ sheet: 0, row, col }, value); + 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: +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 { CommonModule } from '@angular/common'; import { Observable } from 'rxjs'; import { SpreadsheetService } from './spreadsheet.service'; import { type CellValue } from 'hyperformula'; @@ -55,10 +57,6 @@ import { type CellValue } from 'hyperformula'; @Component({ selector: 'app-spreadsheet', templateUrl: './spreadsheet.component.html', - // For NgModule-based apps, declare this component in AppModule and import - // CommonModule there instead of in the component itself. - imports: [CommonModule], - standalone: true, }) export class SpreadsheetComponent { values$: Observable; @@ -67,17 +65,23 @@ export class SpreadsheetComponent { this.values$ = this.spreadsheetService.values$; } - updateCell(row: number, col: number, value: string) { - this.spreadsheetService.updateCell(row, col, value); + runCalculations() { + this.spreadsheetService.calculate(); + } + + reset() { + this.spreadsheetService.reset(); } } ``` ```html - - - + + +
{{ cell }}
+ +
{{ cell }}
``` diff --git a/docs/guide/integration-with-react.md b/docs/guide/integration-with-react.md index fb1d25717..1a715ac42 100644 --- a/docs/guide/integration-with-react.md +++ b/docs/guide/integration-with-react.md @@ -6,11 +6,11 @@ Install with `npm install hyperformula`. For other options, see the [client-side ## 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. Mirror the computed sheet values in `useState` so React re-renders when they change. +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, CellValue, RawCellContent } from 'hyperformula'; +import { HyperFormula, CellValue } from 'hyperformula'; export function SpreadsheetComponent() { const hfRef = useRef(null); @@ -28,7 +28,6 @@ export function SpreadsheetComponent() { } ); hfRef.current = hf; - setValues(hf.getSheetValues(0)); return () => { hf.destroy(); @@ -36,29 +35,33 @@ export function SpreadsheetComponent() { }; }, []); - function handleCellEdit(row: number, col: number, newValue: RawCellContent) { + function runCalculations() { if (!hfRef.current) return; - hfRef.current.setCellContents({ sheet: 0, row, col }, newValue); setValues(hfRef.current.getSheetValues(0)); } + function reset() { + setValues([]); + } + return ( - - - {values.map((row, r) => ( - - {row.map((cell, c) => ( - + <> + + + {values.length > 0 && ( +
- handleCellEdit(r, c, e.target.value)} - /> -
+ + {values.map((row, r) => ( + + {row.map((cell, c) => ( + + ))} + ))} - - ))} - -
{String(cell ?? '')}
+ + + )} + ); } ``` diff --git a/docs/guide/integration-with-vue.md b/docs/guide/integration-with-vue.md index 4d851d3a3..92286531a 100644 --- a/docs/guide/integration-with-vue.md +++ b/docs/guide/integration-with-vue.md @@ -10,24 +10,24 @@ Wrap the HyperFormula instance inside a plain class so it stays outside Vue's re ```typescript // spreadsheet-provider.ts -import { HyperFormula, type CellValue, type RawCellContent } from 'hyperformula'; +import { HyperFormula, type CellValue } from 'hyperformula'; export class SpreadsheetProvider { private hf: HyperFormula; - constructor(data: RawCellContent[][]) { + constructor(data: (string | number | null)[][]) { this.hf = HyperFormula.buildFromArray(data, { licenseKey: 'gpl-v3', // more configuration options go here }); } - getValues(): CellValue[][] { + getCalculatedValues(): CellValue[][] { return this.hf.getSheetValues(0); } - updateCell(row: number, col: number, value: RawCellContent) { - this.hf.setCellContents({ sheet: 0, row, col }, value); + getRawFormulas(): (string | number | null)[][] { + return this.hf.getSheetSerialized(0) as (string | number | null)[][]; } destroy() { @@ -49,18 +49,23 @@ const provider = new SpreadsheetProvider([ // your data rows go here ]); -const values = ref(provider.getValues()); +const values = ref([]); -function handleUpdate(row: number, col: number, value: string) { - provider.updateCell(row, col, value); - values.value = provider.getValues(); +function runCalculations() { + values.value = provider.getCalculatedValues(); +} + +function reset() { + values.value = []; } onUnmounted(() => provider.destroy());