@aiquants/spreadsheet 0.13.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +53 -7
- package/dist/index.cjs +8 -15
- package/dist/index.d.cts +12 -11
- package/dist/index.d.ts +12 -11
- package/dist/index.js +1176 -1255
- package/dist/styles/spreadsheet.standalone.css +1 -1
- package/dist/tsv-B6TDExlk.js +91 -0
- package/dist/tsv-BHXbWK5W.cjs +8 -0
- package/dist/tsv.cjs +1 -0
- package/dist/tsv.d.cts +13 -0
- package/dist/tsv.d.ts +13 -0
- package/dist/tsv.js +5 -0
- package/dist/view.cjs +1 -0
- package/dist/view.d.cts +49 -0
- package/dist/view.d.ts +49 -0
- package/dist/view.js +14 -0
- package/package.json +31 -7
- package/src/Spreadsheet.tsx +20 -50
- package/src/grid/GridBody.tsx +7 -6
- package/src/header/ColumnHeader.tsx +10 -6
- package/src/header/RowHeader.tsx +4 -3
- package/src/model/labels.ts +23 -22
- package/src/model/viewStore.ts +1 -4
- package/src/overlay/SelectionOverlay.tsx +3 -3
- package/src/popover/placement.ts +4 -4
- package/src/styles/spreadsheet.css +1 -1
- package/src/tsv.ts +13 -0
- package/src/view.ts +62 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@aiquants/spreadsheet` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.15.0 (2026-10-26)
|
|
6
|
+
|
|
7
|
+
グリッドがコピーと貼り付けに使う TSV の符号化を、グリッドを読み込まない副パス `@aiquants/spreadsheet/tsv` として公開する。
|
|
8
|
+
barrel も同じ関数を公開し続けるので、既存の import と描画・操作の動作は変わらない。
|
|
9
|
+
|
|
10
|
+
- **新機能 — `@aiquants/spreadsheet/tsv`**: `parseTsv` と `serializeTsv` (barrel と同じ関数で、定義は 1 つ) を、React も
|
|
11
|
+
`@aiquants/virtualscroll` も読まない入口から公開する (約 1 KB)。グリッドを遅延で読み込むホストが、最初の描画の時点で表の文字を
|
|
12
|
+
読める (サーバーで描く代わりの表と、それと同じクライアントの最初の描画)。barrel から `parseTsv` だけを読むと、束ねる道具は
|
|
13
|
+
グリッドの大半を落とせない (約 260 KB と React・virtualscroll の import が残る) ため、別の入口にした。
|
|
14
|
+
- **ビルド**: 入口は `index`・`view`・`tsv` の 3 つ。符号化のモジュールは `index` と `tsv` が共有する塊 (`tsv-<hash>.js` /
|
|
15
|
+
`.cjs`) に入り、どちらもそれを読む。
|
|
16
|
+
- **peer**: `@aiquants/virtualscroll` の peer の範囲は `^3.22.0` のまま (0.14.0 と同じ)。
|
|
17
|
+
- **検証 — 走行台帳 (census の錨は本未リリース節)**: 単体 / 統合 **94 ファイル 4,097 テスト** 全緑 —
|
|
18
|
+
0.14.0 出荷時の 93 ファイル 4,094 に v0.15 進行分 +3 (新規 `tsvEntry` 2、0.14.0 節の出荷時凍結ピンで `hxCensus` +1)。
|
|
19
|
+
barrel (`src/index.ts`) の export は変わらない。`tsc --noEmit` / biome 0。
|
|
20
|
+
|
|
21
|
+
## 0.14.0 (2026-10-26)
|
|
22
|
+
|
|
23
|
+
グリッドの固定の寸法と拡大率の規則を、グリッドを読み込まない副パス `@aiquants/spreadsheet/view` として公開する。グリッド自身も
|
|
24
|
+
同じ定義を読むので、描画と操作の動作は変わらない。
|
|
25
|
+
|
|
26
|
+
- **新機能 — `@aiquants/spreadsheet/view`**: 列見出しの帯の高さ `SPREADSHEET_COLUMN_HEADER_HEIGHT_PX` (24)、行見出しの帯の幅
|
|
27
|
+
`SPREADSHEET_ROW_HEADER_WIDTH_PX` (64)、スクロールバーの太さ `SPREADSHEET_SCROLLBAR_SIZE_PX` (12)、拡大率の範囲
|
|
28
|
+
`SPREADSHEET_ZOOM_MIN` / `SPREADSHEET_ZOOM_MAX` (0.25 / 4)、範囲へ切る `clampSpreadsheetZoom` (有限でない値は `RangeError`)、
|
|
29
|
+
操作と同じ 0.1 刻みの `stepSpreadsheetZoom` (範囲では切らない) を公開する。React も `@aiquants/virtualscroll` も読まないため、
|
|
30
|
+
グリッドを遅延で読み込むホストが、読み込む前に箱の大きさを決めたり (サーバー描画の代わりの表など)、自前の拡大縮小のボタンを
|
|
31
|
+
グリッドと同じ数で動かしたりできる。
|
|
32
|
+
- **定義は 1 か所**: グリッドの拡大率の切り詰めと刻み、スクロールバーの太さ (内部の `SCROLLBAR_WIDTH` は廃止) は `view` の定義を
|
|
33
|
+
読む。列見出しの帯の高さと行見出しの帯の幅は、スタイルシートのカスタムプロパティ (`--aqsp-col-header-height` /
|
|
34
|
+
`--aqsp-row-header-width`) と同じ値であることを試験で確かめる。
|
|
35
|
+
- **peer の下限が上がる**: `@aiquants/virtualscroll` の peer の範囲が `^3.22.0` になる (ワークスペースの版から公開時に展開する範囲。
|
|
36
|
+
0.13.1 は `^3.8.0`)。コードが 3.22 の機能を使うわけではないが、それより古い virtualscroll と組み合わせる利用者は peer の警告を
|
|
37
|
+
受けるので、virtualscroll を 3.22 へ上げる。
|
|
38
|
+
- **検証 — 走行台帳 (census の錨は本未リリース節)**: 単体 / 統合 **93 ファイル 4,094 テスト** 全緑 —
|
|
39
|
+
0.13.0 出荷時の 92 ファイル 4,083 に v0.14 進行分 +11 (新規 `view` 10、既存 `viewStore` −1 (スクロールバーの太さの試験を
|
|
40
|
+
`view` へ移した)、0.13.0 / 0.13.1 節の出荷時凍結ピンで `hxCensus` +2)。barrel (`src/index.ts`) の export は変わらない。
|
|
41
|
+
`tsc --noEmit` / biome 0。
|
|
42
|
+
|
|
5
43
|
## 0.13.1 (2026-10-03)
|
|
6
44
|
|
|
7
45
|
公開物から社内の作業資料への参照を除き、公開の経路に漏洩ガードを通す。API と描画の動作は変わらない。
|
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ deep-reach proof route.
|
|
|
50
50
|
pnpm add @aiquants/spreadsheet @aiquants/virtualscroll
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
`react` / `react-dom` (`^19.2`) and `@aiquants/virtualscroll` (
|
|
53
|
+
`react` / `react-dom` (`^19.2`) and `@aiquants/virtualscroll` are peer dependencies (the ranges are `peerDependencies` in the published `package.json`; the code itself needs virtualscroll 3.7.0 or later, whose label catalogs back the `locale` prop).
|
|
54
54
|
|
|
55
55
|
> **Browser floor**: the built-in cell display formats (v0.7) require `Intl.NumberFormat` v3
|
|
56
56
|
> (string mathematical-value input + `signDisplay: "negative"`) — **Chrome/Edge 106+, Firefox 116+,
|
|
@@ -458,8 +458,8 @@ keyboard input — this is what makes IME composition, clipboard events, and 16M
|
|
|
458
458
|
- `labels.sinkAriaLabel` labels the input surface (its catalog value explains the Escape hand-off — in English by default, in
|
|
459
459
|
Japanese under `locale="ja"`); `labels.rootAriaLabel` labels the grid container (`"Spreadsheet"` / `"スプレッドシート"`). Both
|
|
460
460
|
live in the `labels` prop (v0.8 — the former `sinkAriaLabel` / `rootAriaLabel` props were folded into it; together with the 19
|
|
461
|
-
popover strings they are the 21 package-owned keys, and the
|
|
462
|
-
empty state). The `locale` prop picks the catalog (`SPREADSHEET_LABEL_CATALOGS[locale]`), and an omitted or `undefined` key
|
|
461
|
+
popover strings they are the 21 package-owned keys, and the engine keys (every `VIRTUAL_SCROLL_LABEL_KEYS` entry) name the
|
|
462
|
+
grid scrollbars' chrome, such as the arrows and edge pills, and the empty state). The `locale` prop picks the catalog (`SPREADSHEET_LABEL_CATALOGS[locale]`), and an omitted or `undefined` key
|
|
463
463
|
keeps that catalog's value. Switching `locale` localizes both names together; if you override them, **override both**, and a
|
|
464
464
|
replacement `sinkAriaLabel` **must keep the Escape-exit notice** — the word `Escape` must appear, otherwise the grid throws a `RangeError` while rendering: Escape moves
|
|
465
465
|
focus from the sink to the root, and without the notice (or with an unlabeled landing point) screen-reader users are stranded
|
|
@@ -574,7 +574,7 @@ the zoom keys shadow that chord (the zoom token space `Ctrl+Alt` and `Ctrl+Alt+S
|
|
|
574
574
|
`preventDefault`ed are respected and ignored. In controlled mode, gestures step from the last *requested* value: a parent that rejects a
|
|
575
575
|
request without re-rendering leaves the next detent continuing from the rejected value until any re-render re-syncs it —
|
|
576
576
|
enforce a tighter range by clamping in `onZoomChange` and re-rendering with the clamped value. Wheel/key steps land on the nearest 0.1 detent in the travel direction
|
|
577
|
-
(from the 0.25 bound the first step up is 0.3); Alt+Wheel with no vertical component (tilt wheel / horizontal pan) is
|
|
577
|
+
(from the 0.25 bound the first step up is 0.3; the same step is `stepSpreadsheetZoom` of `@aiquants/spreadsheet/view`); Alt+Wheel with no vertical component (tilt wheel / horizontal pan) is
|
|
578
578
|
passed through to the browser.
|
|
579
579
|
|
|
580
580
|
Navigation is **visible-track aware**: arrows and Shift-arrows step to the next *visible* row/column (a hidden run is
|
|
@@ -734,7 +734,7 @@ The grid's own UI chrome ships in English (`"en"`, the default) and Japanese (`"
|
|
|
734
734
|
| `renderCell` | `(ctx: RenderCellContext) => ReactNode` — `RenderCellContext = { row; col; text; displayText }` (exported) | Custom cell rendering. **Must be identity-stable** (`useCallback`) — an inline arrow defeats the cell memo and re-renders every visible cell each pass. **Per-cell opt-out** (v0.12 — ADR-32): return `undefined` and that one cell falls back to the package default rendering, value-format engine included (only `undefined` opts out — `null`, `false` and any other ReactNode are custom output, i.e. an empty custom cell). `ctx.text` is the raw canonical string; `ctx.displayText` is the value-format display — the exact string the default rendering, announcements, the HTML clipboard and autofit share, so a custom cell that renders it can never drift from what gets copied. Autofit stays a whole-grid no-op while `renderCell` is set. |
|
|
735
735
|
| `maxOperationCells` | `number` | Bulk-operation ceiling (default 100,000). Row fan-out resize gates at 1/10 of it and autofit at 1/4 — see "Operation limits". |
|
|
736
736
|
| `enableResizeUI` | `boolean` | Default `true`. `false` removes resize strips **and unhide markers** — hidden tracks then have no in-package restore path (imperative API only). |
|
|
737
|
-
| `zoom` / `defaultZoom` / `onZoomChange` | `number` / `number` / `(zoom: number) => void` | Package zoom (v0.3): visual transform scale, logic stays in logical px. Controlled (`zoom`) or uncontrolled (`defaultZoom`, default 1). Gestures — Alt+Wheel and Ctrl+Alt+`=` / `+` / `-` / `0` (reset; the four keys match the produced character, Shift-agnostic; AltGraph-state chords are never claimed — legacy-shape defense in depth, see Keymap notes) — request via `onZoomChange`; Ctrl+Wheel is deliberately unbound (the browser owns it). Clamped to [0.25,
|
|
737
|
+
| `zoom` / `defaultZoom` / `onZoomChange` | `number` / `number` / `(zoom: number) => void` | Package zoom (v0.3): visual transform scale, logic stays in logical px. Controlled (`zoom`) or uncontrolled (`defaultZoom`, default 1). Gestures — Alt+Wheel and Ctrl+Alt+`=` / `+` / `-` / `0` (reset; the four keys match the produced character, Shift-agnostic; AltGraph-state chords are never claimed — legacy-shape defense in depth, see Keymap notes) — request via `onZoomChange`; Ctrl+Wheel is deliberately unbound (the browser owns it). Clamped to [`SPREADSHEET_ZOOM_MIN`, `SPREADSHEET_ZOOM_MAX`] (0.25–4, `clampSpreadsheetZoom` of `@aiquants/spreadsheet/view`); a non-finite value (NaN / ±Infinity) throws a `RangeError` (fail-fast — no silent substitution). |
|
|
738
738
|
| `enableFillHandle` | `boolean` | Default `true`. Fill writes through `applyEdits` — disable for read-only adapters. |
|
|
739
739
|
| `onSelectionChange` | `(selection: SelectionState) => void` | Fires on confirmed changes only — never on mount, and never mid-drag with one documented exception: the header subtract reservation converts to a real press on the first movement that both really changes the pointer coordinates and lands on a different header index (in-index jitter and synthetic same-position moves keep the reservation), firing the deferred press replace mid-drag (total fire count matches v0.1). |
|
|
740
740
|
| `onOperationRejected` | `(op, cellCount, reason) => void` | `op: SpreadsheetOperation` (`"copy" \| "cut" \| "delete" \| "paste" \| "fill" \| "resize" \| "format" \| "calc" \| "annotate"` — the three v0.8 names widen the callback's parameter, a 0.x-breaking change for consumers that annotated a narrower `op`), `reason: "limit" \| "bounds"` — count definitions and per-op ceilings under "Operation limits". |
|
|
@@ -744,7 +744,7 @@ The grid's own UI chrome ships in English (`"en"`, the default) and Japanese (`"
|
|
|
744
744
|
| `onUndoRequest` / `onRedoRequest` | `() => void` | `Ctrl+Z` / `Ctrl+Y` / `Ctrl+Shift+Z` relays. |
|
|
745
745
|
| `formatCellAnnouncement` / `formatSelectionAnnouncement` | formatter | Opt-in screen-reader announcements (see above). |
|
|
746
746
|
| `formatCommandAnnouncement` | `(ctx: { command: SpreadsheetCommand; state: boolean \| null }) => string` | Opt-in (v0.8) live-region text after a successful format command; `state` is the toggle's post-state (`bold` / `italic` / `underline` / `strikethrough` / colour toggles), `null` for non-toggle commands. Also makes the live region render on its own. |
|
|
747
|
-
| `labels` | `SpreadsheetLabelOverrides` (any subset of the
|
|
747
|
+
| `labels` | `SpreadsheetLabelOverrides` (any subset of the `SpreadsheetLabels` keys) | v0.8 — replaces the `sinkAriaLabel` / `rootAriaLabel` props (folded in) and names every UI chrome string: the engine keys shared with `@aiquants/virtualscroll` (every entry of the installed engine's `VIRTUAL_SCROLL_LABEL_KEYS`, such as `scrollUp`, `scrollDown`, `scrollToTop`, `noItems` — they reach the grid scrollbars and the empty state) and the 21 package keys (`sinkAriaLabel`, `rootAriaLabel`, `noteTitle`, `noteSave`, `noteEscapeHint`, `noteRejectedHint`, `noteViewerHint` (v0.10 — the note hint in the read-only viewer mode, which never promises a save), `commentTitle`, `commentReply`, `commentPost`, `commentCount` (string or `(n) => string`), `linkTitle`, `linkTextField`, `linkUrlField`, `linkOpen`, `linkSave`, `listTitle`, `listEmpty`, `listMore` (string or `(n) => string` — the validation-list picker mounts at most 500 options and announces the hidden-match count on this row), `messageTitle`, `popoverClose`). Omitted / `undefined` keys keep the value of the `locale` catalog (`SPREADSHEET_LABEL_CATALOGS[locale]`); an unknown key (including the removed `validationRejected` / `indicatorNote` / `indicatorComment` / `indicatorInvalid`) or a blank / non-string value throws a `RangeError` during render; `sinkAriaLabel` must mention `Escape` (`RangeError` during render otherwise). |
|
|
748
748
|
| `locale` | `SpreadsheetLocale` (`"en" \| "ja"`) | v0.13 — the UI chrome language, default `"en"`: selects the label catalog, the built-in validation messages (`onValidationRejected`'s `message` and the message popover) and the grid scrollbars' arrow names / empty state. Any other value (`"fr"`, `"ja-JP"`, `"EN"`, `""`, `null`) throws a `RangeError` during render, even on an empty grid — there is no language negotiation. Unrelated to `formats.locale`. See "Localization". |
|
|
749
749
|
| `shortcuts` | `ShortcutOverrides` = `{ [command]?: readonly string[] \| null }` | v0.8 — rebinds command chords (array = replace the default list, `null` = unbind); validated during render with `RangeError`s (see "Command shortcuts"). Identity-stable — consulted through a ref. |
|
|
750
750
|
| `colors` | `{ fill?: readonly string[]; font?: readonly string[] }` | v0.8 — the five `#rrggbb` slots of `fillColorCustom1..5` / `fontColorCustom1..5` (exactly 5 six-digit hex strings per axis, `RangeError` otherwise); an omitted axis uses `DEFAULT_CUSTOM_COLORS` (`#FF0000` / `#00FF00` / `#0000FF` / `#FFFF00` / `#FF00FF`, exported). |
|
|
@@ -817,9 +817,55 @@ that fails to parse `"unreadable"` — each fires `onClipboardUnavailable` and r
|
|
|
817
817
|
called; `"empty"` means nothing was written (an own payload whose identity was suppressed, every cell skipped, a mode without cell
|
|
818
818
|
aspects such as `columnWidths` — sizes may still have been applied).
|
|
819
819
|
|
|
820
|
+
## View geometry and zoom rules (`@aiquants/spreadsheet/view`)
|
|
821
|
+
|
|
822
|
+
`@aiquants/spreadsheet/view` publishes the grid's fixed geometry and its zoom rules without loading the grid (no React, no
|
|
823
|
+
`@aiquants/virtualscroll`): a host can size a box for the grid before the grid itself is loaded (a lazily imported grid, a
|
|
824
|
+
server-rendered placeholder) and drive the zoom from its own controls with exactly the numbers the grid uses. The grid reads the
|
|
825
|
+
same definitions.
|
|
826
|
+
|
|
827
|
+
| Export | Value / contract |
|
|
828
|
+
| :--- | :--- |
|
|
829
|
+
| `SPREADSHEET_COLUMN_HEADER_HEIGHT_PX` | `24` — the column-header band (`--aqsp-col-header-height`) |
|
|
830
|
+
| `SPREADSHEET_ROW_HEADER_WIDTH_PX` | `64` — the row-header band (`--aqsp-row-header-width`) |
|
|
831
|
+
| `SPREADSHEET_SCROLLBAR_SIZE_PX` | `12` — each scroll bar, reserved on both axes whether or not the grid scrolls |
|
|
832
|
+
| `SPREADSHEET_ZOOM_MIN` / `SPREADSHEET_ZOOM_MAX` | `0.25` / `4` — the bounds of `zoom` |
|
|
833
|
+
| `clampSpreadsheetZoom(value)` | Clamps into the bounds without quantizing; a non-finite value throws a `RangeError` |
|
|
834
|
+
| `stepSpreadsheetZoom(value, direction)` | The gestures' 0.1 detent step (`1` in, `-1` out), unclamped: from an off-grid value it lands on the nearest detent in the travel direction (0.25 → 0.3 / 0.2) |
|
|
835
|
+
|
|
836
|
+
At zoom 1 with uniform rows, a box that shows `n` rows exactly is
|
|
837
|
+
`SPREADSHEET_COLUMN_HEADER_HEIGHT_PX + n × defaultRowHeight + SPREADSHEET_SCROLLBAR_SIZE_PX` px tall. Zoom buttons of a host that
|
|
838
|
+
controls `zoom` step the same way the gestures do:
|
|
839
|
+
|
|
840
|
+
```tsx
|
|
841
|
+
import { clampSpreadsheetZoom, stepSpreadsheetZoom } from "@aiquants/spreadsheet/view"
|
|
842
|
+
|
|
843
|
+
const zoomIn = () => setZoom((zoom) => clampSpreadsheetZoom(stepSpreadsheetZoom(zoom, 1)))
|
|
844
|
+
// <Spreadsheet zoom={zoom} onZoomChange={setZoom} … />
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
## TSV codec (`@aiquants/spreadsheet/tsv`)
|
|
848
|
+
|
|
849
|
+
`@aiquants/spreadsheet/tsv` publishes `parseTsv` and `serializeTsv` — the codec the grid copies and pastes with — without
|
|
850
|
+
loading the grid (no React, no `@aiquants/virtualscroll`; about 1 KB). A host that lazily imports the grid can still read
|
|
851
|
+
the grid's text during the first render, for example to server-render a placeholder table of the rows the grid will show
|
|
852
|
+
(the same markup on the client, so hydration matches). The barrel exports the same two functions (one definition), so
|
|
853
|
+
code that already loads the grid keeps importing them from `@aiquants/spreadsheet`.
|
|
854
|
+
|
|
855
|
+
| Export | Contract |
|
|
856
|
+
| :--- | :--- |
|
|
857
|
+
| `parseTsv(text)` | A rectangular `string[][]`: one trailing line terminator dropped, empty text read as one empty cell (`[[""]]`), ragged rows padded with `""`; a quoted field is unquoted only when its content holds a tab or a line break (a value merely wrapped in quotes is literal text, as Excel writes it) |
|
|
858
|
+
| `serializeTsv(matrix)` | Rows joined with CRLF and a trailing CRLF; only a field with a tab or a line break is quoted (its quotes doubled) — every other field, quotes included, is written verbatim (Excel's behaviour) |
|
|
859
|
+
|
|
860
|
+
```tsx
|
|
861
|
+
import { parseTsv } from "@aiquants/spreadsheet/tsv"
|
|
862
|
+
|
|
863
|
+
const rows = parseTsv(text) // ready before `import("@aiquants/spreadsheet")` resolves
|
|
864
|
+
```
|
|
865
|
+
|
|
820
866
|
## Clipboard utilities (public)
|
|
821
867
|
|
|
822
|
-
`parseTsv` / `serializeTsv` / `estimatePasteCellCount` / `expandPaste` are exported for building custom paste UIs and
|
|
868
|
+
`parseTsv` / `serializeTsv` (also at `@aiquants/spreadsheet/tsv`) / `estimatePasteCellCount` / `expandPaste` are exported for building custom paste UIs and
|
|
823
869
|
importers with the exact same Excel-compatible semantics the grid uses. `enumerateRangeCells`, `makeRange`,
|
|
824
870
|
`rangeContains`, `rangeSize`, `columnIndexToName`, `columnNameToIndex` round out the toolkit. The named unions
|
|
825
871
|
`SpreadsheetDirection` (`"up" | "down" | "left" | "right"` — the `findDataEdge` direction), `SpreadsheetOperation` (the
|