diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..c768bd543 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,95 @@ +# AGENTS.md + +Universal instructions for AI coding assistants (Claude Code, GitHub Copilot, Cursor, Gemini CLI, and others) working with the HyperFormula codebase. + +For Claude Code specific setup see [CLAUDE.md](CLAUDE.md). + +## What is HyperFormula + +HyperFormula is a **headless spreadsheet engine** — no UI, no DOM. It parses Excel-compatible formulas, evaluates them, and returns computed values. It runs in browser and Node.js. + +Core API surface (3 operations cover 90% of use): +```typescript +import { HyperFormula } from 'hyperformula'; + +const hf = HyperFormula.buildFromArray(data, { licenseKey: 'gpl-v3' }); +const values = hf.getSheetValues(0); // CellValue[][] +hf.setCellContents({ sheet: 0, row, col }, value); // RawCellContent +``` + +`licenseKey: 'gpl-v3'` is mandatory — without it the engine throws a license warning. + +## Framework integration patterns + +When helping users integrate HyperFormula with a framework, follow these idiomatic patterns: + +| Framework | Instance storage | Cleanup | Reactive bridge | SSR guard | +|---|---|---|---|---| +| React | `useRef` | `useEffect` return | `useState` | `dynamic(..., { ssr: false })` | +| Angular | `@Injectable` service with `BehaviorSubject` | `ngOnDestroy` (component-scoped) | `async` pipe | N/A (no default SSR) | +| Vue 3 | Class wrapper with private HF field | `onUnmounted` | `ref` | `` (Nuxt) | +| Svelte | top-level `const` in ` + + + +{#if result !== null} +

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} +
+``` + +## 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 + + + + +{#if result !== null} +

Result: {result}

+{/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 Svelte demo on Stackblitz. diff --git a/docs/guide/integration-with-vue.md b/docs/guide/integration-with-vue.md index eaa104e0e..92286531a 100644 --- a/docs/guide/integration-with-vue.md +++ b/docs/guide/integration-with-vue.md @@ -1,8 +1,85 @@ # 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 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 inside a plain class so it stays outside Vue's reactivity system (see [Troubleshooting](#vue-reactivity-issues) below for why this matters). Hold derived data in `ref` so the template updates when you reassign the ref's `.value`. + +```typescript +// spreadsheet-provider.ts +import { HyperFormula, type CellValue } from 'hyperformula'; + +export class SpreadsheetProvider { + private hf: HyperFormula; + + constructor(data: (string | number | null)[][]) { + this.hf = HyperFormula.buildFromArray(data, { + licenseKey: 'gpl-v3', + // more configuration options go here + }); + } + + getCalculatedValues(): CellValue[][] { + return this.hf.getSheetValues(0); + } + + getRawFormulas(): (string | number | null)[][] { + return this.hf.getSheetSerialized(0) as (string | number | null)[][]; + } + + destroy() { + this.hf.destroy(); + } +} +``` + +Use the class from a component with ` + + +``` + +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 + +### Server-side rendering (Nuxt) + +HyperFormula depends on browser-only APIs. In Nuxt, render the spreadsheet on the client only by wrapping the component with ``. ## Troubleshooting @@ -14,24 +91,31 @@ 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. + +## 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/vue-3-demo?v=${$page.buildDateURIEncoded}). +For a more advanced example, check out the Vue 3 demo on Stackblitz. ::: 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).