@aiquants/spreadsheet 0.15.0 → 0.16.1

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/dist/view.d.ts CHANGED
@@ -13,9 +13,22 @@
13
13
  */
14
14
  export declare const clampSpreadsheetZoom: (value: number) => number;
15
15
 
16
+ /** Column index (0-based) to Excel-style name (0 → A, 26 → AA, 16383 → XFD). / 列 index → 列名。 */
17
+ export declare const columnIndexToName: (index: number) => string;
18
+
16
19
  /** The height of the column-header band in CSS px (`--aqsp-col-header-height` in the stylesheet). 列見出しの帯の高さ (CSS px。スタイルシートの `--aqsp-col-header-height`)。 */
17
20
  export declare const SPREADSHEET_COLUMN_HEADER_HEIGHT_PX = 24;
18
21
 
22
+ /** The default column width in CSS px, the width of every column the host does not size. 既定の列幅 (CSS px。ホストが寸法を決めない列の幅)。 */
23
+ export declare const SPREADSHEET_DEFAULT_COLUMN_WIDTH_PX = 100;
24
+
25
+ /**
26
+ * The default of `maxOperationCells` (ADR-12): the largest number of cells one operation (paste, fill, clear, format)
27
+ * may touch before the grid refuses it.
28
+ * `maxOperationCells` の既定値 (ADR-12)。1 つの操作 (貼り付け・フィル・消去・書式) が触れてよいセル数の上限で、超えるとグリッドが拒む。
29
+ */
30
+ export declare const SPREADSHEET_MAX_OPERATION_CELLS = 100000;
31
+
19
32
  /** The width of the row-header band in CSS px (`--aqsp-row-header-width` in the stylesheet). 行見出しの帯の幅 (CSS px。スタイルシートの `--aqsp-row-header-width`)。 */
20
33
  export declare const SPREADSHEET_ROW_HEADER_WIDTH_PX = 64;
21
34
 
@@ -33,16 +46,27 @@ export declare const SPREADSHEET_ZOOM_MAX = 4;
33
46
  export declare const SPREADSHEET_ZOOM_MIN = 0.25;
34
47
 
35
48
  /**
36
- * The 0.1-grid step of the zoom gestures (Alt+Wheel and Ctrl+Alt+= / -), unclamped and free of float drift. From an
37
- * off-grid value (the 0.25 bound or a host's 1.25) the step lands on the nearest detent in the travel direction (0.25
38
- * goes to 0.3 up and 0.2 down); rounding would skip a detent (0.25 to 0.4). Clamp the result with clampSpreadsheetZoom.
39
- * 拡大縮小の操作 (Alt+ホイールと Ctrl+Alt+= / -) の 0.1 刻み (範囲で切らず、浮動小数のずれを持ち込まない)。刻みの外の値
40
- * (下限の 0.25 やホストの 1.25) からは、進む向きの最寄りの刻みへ着く (0.25 から上は 0.3、下は 0.2)。丸めると刻みを飛ばす
41
- * (0.25 から 0.4) ため使わない。結果は clampSpreadsheetZoom で切る。
49
+ * The zoom ladder of the gestures (Alt+Wheel and Ctrl+Alt+= / -): the geometric series `2^(k/4)` for every integer `k`
50
+ * from −8 to 8, rounded to hundredths, so it runs from SPREADSHEET_ZOOM_MIN (0.25) through 1 to SPREADSHEET_ZOOM_MAX (4)
51
+ * in steps of about 19 % and every power of two is a step.
52
+ * 拡大縮小の操作 (Alt+ホイールと Ctrl+Alt+= / -) の段。−8 から 8 までの整数 `k` の等比数列 `2^(k/4)` を 100 分の 1 に丸めた
53
+ * もので、SPREADSHEET_ZOOM_MIN (0.25) から 1 を通って SPREADSHEET_ZOOM_MAX (4) まで約 19 % ずつ進み、2 の累乗はどれも段に乗る。
54
+ */
55
+ export declare const SPREADSHEET_ZOOM_STEPS: readonly number[];
56
+
57
+ /**
58
+ * The next step of SPREADSHEET_ZOOM_STEPS in the travel direction, from any finite value, including a value off the
59
+ * ladder (a host's 1.25 goes to 1.41 up and 1.19 down). The input is clamped first, as clampSpreadsheetZoom does, and
60
+ * the step is taken from the clamped value: 9 zooms out to 3.36 and 0.01 zooms in to 0.3, and at a bound the result stays
61
+ * at that bound (in from SPREADSHEET_ZOOM_MAX, out from SPREADSHEET_ZOOM_MIN). A non-finite value fails fast.
62
+ * 進む向きの SPREADSHEET_ZOOM_STEPS の次の段を返す処理。段の外の値 (ホストの 1.25 は上で 1.41、下で 1.19) を含む、どの有限の
63
+ * 値からでも求める。入力を先に clampSpreadsheetZoom と同じく範囲へ切り、切った値から一歩進む (9 からの縮小は 3.36、0.01 からの
64
+ * 拡大は 0.3)。端では端に留まる (SPREADSHEET_ZOOM_MAX からの拡大、SPREADSHEET_ZOOM_MIN からの縮小)。有限でない値はすぐに投げる。
42
65
  *
43
66
  * @param value - The current zoom. 今の拡大率。
44
67
  * @param direction - 1 to zoom in, -1 to zoom out. 拡大は 1、縮小は -1。
45
- * @returns The next detent in that direction. その向きの次の刻み。
68
+ * @returns The next ladder step in that direction from the clamped input. 範囲へ切った入力からのその向きの次の段。
69
+ * @throws {RangeError} When the value is not finite. 有限でない場合。
46
70
  */
47
71
  export declare const stepSpreadsheetZoom: (value: number, direction: 1 | -1) => number;
48
72
 
package/dist/view.js CHANGED
@@ -1,14 +1,14 @@
1
- const e = 24, o = 64, _ = 12, S = 0.25, n = 4, s = (E) => {
2
- if (!Number.isFinite(E))
3
- throw new RangeError(`[spreadsheet] zoom must be a finite number (got ${E})`);
4
- return Math.min(4, Math.max(0.25, E));
5
- }, r = (E, t) => t > 0 ? (Math.floor(E * 10 + 1e-6) + 1) / 10 : (Math.ceil(E * 10 - 1e-6) - 1) / 10;
1
+ import { f as _, b as a, d as s, g as A, S as T, h as H, i as P, j as R, a as D, c as O, s as e } from "./view-DycJZi_t.js";
6
2
  export {
7
- e as SPREADSHEET_COLUMN_HEADER_HEIGHT_PX,
8
- o as SPREADSHEET_ROW_HEADER_WIDTH_PX,
9
- _ as SPREADSHEET_SCROLLBAR_SIZE_PX,
10
- n as SPREADSHEET_ZOOM_MAX,
11
- S as SPREADSHEET_ZOOM_MIN,
12
- s as clampSpreadsheetZoom,
13
- r as stepSpreadsheetZoom
3
+ _ as SPREADSHEET_COLUMN_HEADER_HEIGHT_PX,
4
+ a as SPREADSHEET_DEFAULT_COLUMN_WIDTH_PX,
5
+ s as SPREADSHEET_MAX_OPERATION_CELLS,
6
+ A as SPREADSHEET_ROW_HEADER_WIDTH_PX,
7
+ T as SPREADSHEET_SCROLLBAR_SIZE_PX,
8
+ H as SPREADSHEET_ZOOM_MAX,
9
+ P as SPREADSHEET_ZOOM_MIN,
10
+ R as SPREADSHEET_ZOOM_STEPS,
11
+ D as clampSpreadsheetZoom,
12
+ O as columnIndexToName,
13
+ e as stepSpreadsheetZoom
14
14
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiquants/spreadsheet",
3
- "version": "0.15.0",
3
+ "version": "0.16.1",
4
4
  "description": "Excel-grade spreadsheet grid for React: multi-cell selection, TSV/HTML clipboard, in-place editing on top of @aiquants/virtualscroll",
5
5
  "sideEffects": [
6
6
  "**/*.css"
@@ -56,12 +56,12 @@
56
56
  "LICENSE.md"
57
57
  ],
58
58
  "peerDependencies": {
59
- "@aiquants/virtualscroll": "^3.22.0",
59
+ "@aiquants/virtualscroll": "^3.22.1",
60
60
  "react": "^19.2.7",
61
61
  "react-dom": "^19.2.7"
62
62
  },
63
63
  "devDependencies": {
64
- "@aiquants/virtualscroll": "3.22.0",
64
+ "@aiquants/virtualscroll": "3.22.1",
65
65
  "@playwright/test": "^1.59.1",
66
66
  "@tailwindcss/cli": "^4.1.18",
67
67
  "@testing-library/jest-dom": "^6.9.1",
@@ -63,7 +63,7 @@ import { RowHeader } from "./header/RowHeader.tsx"
63
63
  import { useHeaderResize } from "./header/useHeaderResize.ts"
64
64
  import { useHeaderSelection } from "./header/useHeaderSelection.ts"
65
65
  import { createEnsureCellVisible } from "./keyboard/ensureCellVisible.ts"
66
- import { type ShortcutOverrides, type SpreadsheetCommand, validateShortcuts } from "./keyboard/shortcuts.ts"
66
+ import { RESIZE_COMMANDS, type ShortcutOverrides, type SpreadsheetCommand, toAriaKeyShortcut, validateShortcuts } from "./keyboard/shortcuts.ts"
67
67
  import { type CommandDispatch, type MoveActiveInput, useGridKeyboard } from "./keyboard/useGridKeyboard.ts"
68
68
  import { useAxisModel } from "./model/axisModel.ts"
69
69
  import { type CellCommentInput, normalizeAdapterHyperlink } from "./model/cellAnnotations.ts"
@@ -71,7 +71,7 @@ import { type CellCoord, type CellRange, clampCoord, makeRange, rangeSize, type
71
71
  import { type CellBorder, type CellFormat, type CellFormatPatch, formatCellValue } from "./model/cellFormat.ts"
72
72
  import { type EditPaint, IDLE_EDIT_PAINT, normalizeColor, resolveEditPaint } from "./model/cellPaint.ts"
73
73
  import { buildCfIndex, type CfIndex, type CfMemo, createCfMemo, resolvePaintedFormat } from "./model/conditionalFormat.ts"
74
- import { type CellEdit, type EditContext, type EditRejection, type EditSource, isReadOnlyCell, lazyCellText, type ReadOnlyRejection, readOnlyDeclared, resolveCellAlign, type SpreadsheetDataAdapter } from "./model/dataAdapter.ts"
74
+ import { type CellEdit, type EditContext, type EditRejection, type EditSource, isReadOnlyCell, lazyCellText, type ReadOnlyRejection, readOnlyAdapterView, readOnlyDeclared, resolveCellAlign, type SpreadsheetDataAdapter } from "./model/dataAdapter.ts"
75
75
  import { type DataValidationRule, type ValidationVerdict, validateCellText } from "./model/dataValidation.ts"
76
76
  import type { ReferenceAxis } from "./model/formulaRefs.ts"
77
77
  import { colToViewportX, rowToViewportY } from "./model/frozenPartition.ts"
@@ -85,7 +85,7 @@ import { clampSelectionToGrid, reduceActiveCycle, reduceSelectionExtend, reduceS
85
85
  import { useAutoScroller } from "./selection/useAutoScroller.ts"
86
86
  import { type PointDragOwner, usePointDrag } from "./selection/usePointDrag.ts"
87
87
  import { useSelectionEngine } from "./selection/useSelectionEngine.ts"
88
- import { clampSpreadsheetZoom, stepSpreadsheetZoom } from "./view.ts"
88
+ import { clampSpreadsheetZoom, SPREADSHEET_DEFAULT_COLUMN_WIDTH_PX, SPREADSHEET_MAX_OPERATION_CELLS, stepSpreadsheetZoom } from "./view.ts"
89
89
 
90
90
  export type { SelectionState } from "./selection/selectionState.ts"
91
91
 
@@ -187,6 +187,15 @@ export type SpreadsheetProps = {
187
187
  * 位置に依らず常時可視で、可視化の縦成分は no-op。飛行中の変更は全ドラッグを取り消す。
188
188
  */
189
189
  frozenRows?: number
190
+ /**
191
+ * The leading rows whose cells are column headers (default 0): their cells take `role="columnheader"` and the
192
+ * stylesheet's header-row paint (`--aqsp-header-row-*`), so assistive technology reads them as the headers of the
193
+ * columns below. An integer in [0, frozenRows]: header rows are always frozen rows; anything else is a RangeError
194
+ * during render.
195
+ * 先頭の見出しの行数 (既定 0)。そのセルは `role="columnheader"` とスタイルシートの見出し行の塗り (`--aqsp-header-row-*`)
196
+ * を受け、支援技術は下の列の見出しとして読む。[0, frozenRows] の整数 (見出しの行は常に凍結行)。それ以外は描画中の RangeError。
197
+ */
198
+ headerRows?: number
190
199
  /**
191
200
  * Freezes the LAST N columns at the right edge (v0.9 — the VirtualGrid frozenTrailingCols
192
201
  * pass-through; LIVE prop, default 0). Validation SSOT is the engine's fail-fast contract
@@ -382,6 +391,26 @@ export type SpreadsheetProps = {
382
391
  * region は溢れない。
383
392
  */
384
393
  formatReadOnlyAnnouncement?: (rejection: ReadOnlyRejection) => string
394
+ /**
395
+ * Makes the whole grid read-only (default false): `aria-readonly="true"` on the `role="grid"` element and on the
396
+ * hidden input, and every cell is read-only whatever the adapter's `isCellReadOnly` says, so every write is refused
397
+ * through `onReadOnlyRejected` / `formatReadOnlyAnnouncement` and the cell announcement's `readOnly` is true. View
398
+ * changes stay available: selection, copy, scrolling, zoom, resizing, hiding and highlighting.
399
+ * グリッド全体を読み取り専用にする (既定 false)。`role="grid"` の要素と隠しの入力に `aria-readonly="true"` を付け、アダプタの
400
+ * `isCellReadOnly` に関わらず全てのセルを読み取り専用にする (書き込みは全て `onReadOnlyRejected` / `formatReadOnlyAnnouncement`
401
+ * で拒まれ、セルの読み上げの `readOnly` は true)。見た目の変更 (選択・コピー・スクロール・拡大率・寸法・非表示・強調) は使える。
402
+ */
403
+ readOnly?: boolean
404
+ /**
405
+ * The id of the element that names the grid (a caption or heading of the host): the `role="grid"` element and the
406
+ * focusable root (the Escape landing point) both take `aria-labelledby` with it instead of the built-in
407
+ * `labels.rootAriaLabel`. A non-empty id without whitespace; anything else is a RangeError during render. Omitted:
408
+ * the root and the grid both carry `labels.rootAriaLabel` as `aria-label`.
409
+ * グリッドに名前を与える要素の id (ホストの表題や見出し)。`role="grid"` の要素とフォーカスを受けるルート (Escape の着地点) は
410
+ * 組み込みの `labels.rootAriaLabel` の代わりにこれを `aria-labelledby` に持つ。空白を含まない空でない id で、それ以外は描画中の
411
+ * RangeError。省略するとルートとグリッドの両方が `labels.rootAriaLabel` を `aria-label` に持つ。
412
+ */
413
+ labelledBy?: string
385
414
  /**
386
415
  * Paste outcome summary (v0.10 plan §3.1): the menu channel mirrors `paste(options)`'s
387
416
  * resolve value 1:1 (read refusals and the write-time re-gate included); the event channel
@@ -446,6 +475,15 @@ export type SpreadsheetProps = {
446
475
  * コマンドの AT 通知 (opt-in、v0.8 §1.9)。`state` はトグル後状態 (非トグルは null)。live region へ。
447
476
  */
448
477
  formatCommandAnnouncement?: (ctx: { command: SpreadsheetCommand; state: boolean | null }) => string
478
+ /**
479
+ * Live-region text after a keyboard resize (increaseColumnWidth / decreaseColumnWidth / increaseRowHeight /
480
+ * decreaseRowHeight / autoFitColumnWidth): the active track's axis, index and new size in CSS px. Omit for silence
481
+ * (no built-in strings); it shares the announcement debounce.
482
+ * キーボードの寸法の変更 (increaseColumnWidth / decreaseColumnWidth / increaseRowHeight / decreaseRowHeight /
483
+ * autoFitColumnWidth) の後の読み上げ文言。アクティブなトラックの軸・番号・新しい寸法 (CSS px) を受ける。省略で無音
484
+ * (組み込み文言なし)。読み上げのデバウンスを共有する。
485
+ */
486
+ formatResizeAnnouncement?: (ctx: { command: SpreadsheetCommand; axis: "row" | "column"; index: number; size: number }) => string
449
487
  /**
450
488
  * UI chrome language ("en" | "ja"; default "en"): selects the label catalog, the built-in
451
489
  * data-validation messages, and is forwarded to the grid scrollbars (arrow labels,
@@ -585,6 +623,34 @@ export type SpreadsheetProps = {
585
623
  * 本層所有)。undefined は宣言不在信号 (エンジン既定) で対象外。非整数・負値は RangeError (メッセージは
586
624
  * 生の入力を映す — 数値は Infinity / NaN 込みでそのまま、文字列は引用符つき — JS ホストの "5" は引用符で映る)。
587
625
  */
626
+ /**
627
+ * Validates `headerRows` during render: an integer in [0, frozenRows], since header rows are always frozen rows.
628
+ * 描画中に `headerRows` を確かめる処理。見出しの行は常に凍結行なので [0, frozenRows] の整数。
629
+ *
630
+ * @param headerRows - The prop. prop。
631
+ * @param frozenRows - The frozen leading rows. 先頭の凍結行数。
632
+ * @throws {RangeError} When the value is not an integer in [0, frozenRows]. [0, frozenRows] の整数でない場合。
633
+ */
634
+ const validateHeaderRows = (headerRows: number, frozenRows: number): void => {
635
+ // 0 は凍結行数を問わず有効: 凍結行数そのものの検証はエンジン (VirtualGrid) の単層に任せ、ここで先に投げない
636
+ if (!Number.isInteger(headerRows) || headerRows < 0 || (headerRows > 0 && headerRows > frozenRows)) {
637
+ throw new RangeError(`[spreadsheet] headerRows must be a whole number from 0 to frozenRows = ${frozenRows} (got ${headerRows})`)
638
+ }
639
+ }
640
+
641
+ /**
642
+ * Validates `labelledBy` during render: omitted, or one non-empty id without whitespace.
643
+ * 描画中に `labelledBy` を確かめる処理。省略か、空白を含まない空でない id 1 つ。
644
+ *
645
+ * @param labelledBy - The prop. prop。
646
+ * @throws {RangeError} When the value is empty or holds whitespace. 空か空白を含む場合。
647
+ */
648
+ const validateLabelledBy = (labelledBy: string | undefined): void => {
649
+ if (labelledBy !== undefined && (typeof labelledBy !== "string" || labelledBy === "" || /\s/u.test(labelledBy))) {
650
+ throw new RangeError(`[spreadsheet] labelledBy must be one non-empty id without whitespace (got ${JSON.stringify(labelledBy)})`)
651
+ }
652
+ }
653
+
588
654
  const validateOverscan = (name: "overscanRows" | "overscanCols", value: number | undefined): number | undefined => {
589
655
  if (value !== undefined && !(Number.isInteger(value) && value >= 0)) {
590
656
  throw new RangeError(`[spreadsheet] ${name} must be a non-negative integer (got ${typeof value === "string" ? JSON.stringify(value) : String(value)})`)
@@ -622,8 +688,6 @@ type SpreadsheetTestProps = {
622
688
  }
623
689
 
624
690
  const DEFAULT_ROW_HEIGHT = 24
625
- const DEFAULT_COLUMN_WIDTH = 100
626
- const DEFAULT_MAX_OPERATION_CELLS = 100_000
627
691
  /** Live region debounce in ms (§6.9-9). / live region デバウンス (ms)。 */
628
692
  const ANNOUNCE_DEBOUNCE_MS = 200
629
693
  /** Default custom colour slots (v0.8 §1.9) for `fillColorCustom1..5` / `fontColorCustom1..5`. / カスタム色 5 スロットの既定 (v0.8 §1.9)。 */
@@ -744,9 +808,12 @@ const INITIAL_SELECTION: SelectionState = {
744
808
  */
745
809
  export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & SpreadsheetTestProps>(function Spreadsheet(
746
810
  {
747
- adapter,
811
+ adapter: hostAdapter,
812
+ headerRows = 0,
813
+ readOnly = false,
814
+ labelledBy,
748
815
  defaultRowHeight = DEFAULT_ROW_HEIGHT,
749
- defaultColumnWidth = DEFAULT_COLUMN_WIDTH,
816
+ defaultColumnWidth = SPREADSHEET_DEFAULT_COLUMN_WIDTH_PX,
750
817
  frozenColumns = 0,
751
818
  frozenRows = 0,
752
819
  frozenTrailingColumns = 0,
@@ -767,12 +834,13 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
767
834
  onReadOnlyRejected,
768
835
  formatReadOnlyAnnouncement,
769
836
  dataVersion,
770
- maxOperationCells = DEFAULT_MAX_OPERATION_CELLS,
837
+ maxOperationCells = SPREADSHEET_MAX_OPERATION_CELLS,
771
838
  enableFillHandle = true,
772
839
  enableResizeUI = true,
773
840
  formatCellAnnouncement,
774
841
  formatSelectionAnnouncement,
775
842
  formatCommandAnnouncement,
843
+ formatResizeAnnouncement,
776
844
  locale,
777
845
  labels,
778
846
  shortcuts,
@@ -802,6 +870,9 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
802
870
  }: SpreadsheetProps & SpreadsheetTestProps,
803
871
  ref,
804
872
  ): ReactNode {
873
+ // 全体の読み取り専用はアダプタのビュー 1 つで表す: 全ての読み取り専用のゲートと塗りはアダプタの宣言だけを読むため、
874
+ // ゲートごとに旗を通さずに全ての書き込みが同じ経路で拒まれる。同一性はシート切替の判定 (hostAdapter) に使わない
875
+ const adapter = useMemo(() => (readOnly ? readOnlyAdapterView(hostAdapter) : hostAdapter), [hostAdapter, readOnly])
805
876
  // §6.9-1: フックは常に呼び、空グリッドはレンダー分岐でのみ返す (indexAt fail-fast の前提)
806
877
  const rowAxis = useAxisModel(adapter.rowCount, defaultRowHeight)
807
878
  const colAxis = useAxisModel(adapter.colCount, defaultColumnWidth)
@@ -822,6 +893,15 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
822
893
  const scrollLabels = useValueStableScrollLabels(pickEngineLabels(resolvedLabels))
823
894
  // コマンド表: validateShortcuts の 5 条件 (+ 未知キー) は consumer の設定誤り — render 中に表面化
824
895
  const commandTable = useMemo(() => validateShortcuts(shortcuts), [shortcuts])
896
+ // 寸法のキーボード操作を隠しの入力の aria-keyshortcuts で示す (再束縛の後の実効チョード。束縛が無ければ属性を出さない)
897
+ const resizeKeyShortcuts = useMemo(() => {
898
+ if (!enableResizeUI) {
899
+ return undefined
900
+ }
901
+ const chords = [...commandTable].filter(([, command]) => RESIZE_COMMANDS.includes(command)).sort(([, a], [, b]) => RESIZE_COMMANDS.indexOf(a) - RESIZE_COMMANDS.indexOf(b))
902
+ const names = chords.map(([chord]) => toAriaKeyShortcut(chord)).filter((name): name is string => name !== null)
903
+ return names.length === 0 ? undefined : names.join(" ")
904
+ }, [commandTable, enableResizeUI])
825
905
  // カスタム色スロット (各軸ちょうど 5 本の #rrggbb)
826
906
  const resolvedColors = useMemo(() => ({ fill: resolveColorSlots("fill", colors === undefined ? undefined : colors.fill), font: resolveColorSlots("font", colors === undefined ? undefined : colors.font) }), [colors])
827
907
  // 帯パレットの prop 時検証 (identity 安定): 宣言時だけ検証し、未宣言は undefined のまま — overlay へ渡す
@@ -831,6 +911,8 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
831
911
  if (adapter.getConditionalFormats !== undefined && adapter.getCellFormat === undefined) {
832
912
  throw new RangeError("[spreadsheet] getConditionalFormats requires getCellFormat")
833
913
  }
914
+ validateHeaderRows(headerRows, frozenRows)
915
+ validateLabelledBy(labelledBy)
834
916
 
835
917
  // ---- renderVersion (editVersion 購読 + dataVersion) と CF 索引 / memo (v0.8 §2.1) ----
836
918
  // GridBody と同じ合流購読 (rAF 合流) — 親が先に描画するため、子のスライスが読む .current は常に新
@@ -1838,6 +1920,7 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
1838
1920
  adapter,
1839
1921
  // autofit の採用条件 = renderCell 未指定 (カスタム DOM は measureText 計測を保証できない §10)
1840
1922
  autofitEnabled: renderCell === undefined,
1923
+ headerRows,
1841
1924
  defaultRowHeight,
1842
1925
  defaultColumnWidth,
1843
1926
  maxOperationCells,
@@ -2083,6 +2166,8 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
2083
2166
  }, [rowAxis, colAxis, ensureCellVisible])
2084
2167
  const commands = useCommands({
2085
2168
  adapter,
2169
+ // キーボードの寸法の変更は帯のリサイズ UI と同じ可否 (enableResizeUI が false なら利用者は寸法を変えない)
2170
+ trackResize: enableResizeUI && !isEmpty ? headerResizeMachine : undefined,
2086
2171
  getSelection,
2087
2172
  commitEdits,
2088
2173
  getActive,
@@ -2112,6 +2197,7 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
2112
2197
  now,
2113
2198
  announce,
2114
2199
  formatCommandAnnouncement,
2200
+ formatResizeAnnouncement,
2115
2201
  keyboardCellRect,
2116
2202
  // v0.10: G8–G13 / G15 の拒否漏斗 (§6.4)
2117
2203
  rejectReadOnly,
@@ -2447,10 +2533,11 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
2447
2533
  // 旧グリッド座標のレンジは新軸で out-of-bounds になり得て、repaintOverlay の sizeOf が
2448
2534
  // 例外を投げる (Fenwick は範囲外 index で fail-fast) — クランプでなくシート切替の意味論
2449
2535
  // どおり全リセット (Excel のシート切替と同じく選択は持ち越さない)
2450
- const prevAdapterRef = useRef(adapter)
2536
+ // 判定はホストのアダプタの同一性で行う (readOnly の切替が作るビューの交代はシート切替ではない)
2537
+ const prevAdapterRef = useRef(hostAdapter)
2451
2538
  useLayoutEffect(() => {
2452
- if (prevAdapterRef.current !== adapter) {
2453
- prevAdapterRef.current = adapter
2539
+ if (prevAdapterRef.current !== hostAdapter) {
2540
+ prevAdapterRef.current = hostAdapter
2454
2541
  // 飛行中ドラッグは復元なしで取り消す (各機械の dragBeforeRef は旧シート座標 — 復元経路
2455
2542
  // (Escape / lostpointercapture) から新軸へ範囲外選択が漏れるのを断つ)
2456
2543
  engine.cancelDrag()
@@ -2490,7 +2577,7 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
2490
2577
  // effect 化する将来変更でこの順序が実防壁になるため維持する
2491
2578
  onAdapterSwitchRef.current()
2492
2579
  }
2493
- }, [adapter, isEmpty, setSelection, engine, colHeaderMachine, rowHeaderMachine, headerResizeMachine, fillMachine, pointDrag, viewStore, editor.cancelEdit])
2580
+ }, [hostAdapter, isEmpty, setSelection, engine, colHeaderMachine, rowHeaderMachine, headerResizeMachine, fillMachine, pointDrag, viewStore, editor.cancelEdit])
2494
2581
 
2495
2582
  // ---- 行数 / 列数の縮小 (同一 adapter — 絞り込み / カテゴリ / 期間の変更で明細が減る) は保持中の
2496
2583
  // 座標を新グリッドへ畳む (ADR-33) ----
@@ -2696,12 +2783,15 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
2696
2783
 
2697
2784
  // live region は読み上げ prop のどれか 1 つでも宣言されたときだけ描く (コマンド通知も同じ領域へ)
2698
2785
  // v0.10: formatReadOnlyAnnouncement も live region の存在条件 (拒否読み上げは announce() 経由 — §6.4)
2699
- const hasAnnouncement = formatCellAnnouncement !== undefined || formatSelectionAnnouncement !== undefined || formatCommandAnnouncement !== undefined || formatReadOnlyAnnouncement !== undefined
2786
+ const hasAnnouncement = formatCellAnnouncement !== undefined || formatSelectionAnnouncement !== undefined || formatCommandAnnouncement !== undefined || formatReadOnlyAnnouncement !== undefined || formatResizeAnnouncement !== undefined
2787
+
2788
+ // 名前は labelledBy があればホストの要素 (aria-labelledby)、無ければ組み込みの名前 (aria-label) — グリッドも同じ規則で名前を持つ
2789
+ const rootName = labelledBy === undefined ? { "aria-label": resolvedLabels.rootAriaLabel } : { "aria-labelledby": labelledBy }
2700
2790
 
2701
2791
  return (
2702
2792
  // biome-ignore lint/a11y/useSemanticElements: fieldset はフォーム意味論であり不適。role="group" は Escape 脱出の着地点に現在地名を与えるための選択 (§6.9-7 / §10-4)
2703
2793
  // biome-ignore lint/a11y/noNoninteractiveTabindex: tabIndex=0 は WCAG 2.1.2 の脱出着地点 + グリッド再進入口 (§10-4) — 意図的な tab stop
2704
- <div ref={rootRef} className={className ? `aqsp-root ${className}` : "aqsp-root"} role="group" aria-label={resolvedLabels.rootAriaLabel} tabIndex={0} onFocus={onRootFocus} onBlur={onRootBlur} data-aqsp-root="" data-testid={testId}>
2794
+ <div ref={rootRef} className={className ? `aqsp-root ${className}` : "aqsp-root"} role="group" {...rootName} tabIndex={0} onFocus={onRootFocus} onBlur={onRootBlur} data-aqsp-root="" data-testid={testId}>
2705
2795
  {/* ズームラッパー (v0.3): 視覚は transform スケール、内側レイアウトは 100%/z の論理 px —
2706
2796
  virtualscroll の実測 (ResizeObserver / clientHeight) は論理値のまま。z=1 は無スタイル
2707
2797
  (v0.2 と byte 同一の DOM 幾何 — 既存全ゲートの不変性) */}
@@ -2783,6 +2873,10 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
2783
2873
  frozenRows={frozenRows}
2784
2874
  frozenTrailingColumns={frozenTrailingColumns}
2785
2875
  frozenTrailingRows={frozenTrailingRows}
2876
+ headerRows={headerRows}
2877
+ labelledBy={labelledBy}
2878
+ gridLabel={labelledBy === undefined ? resolvedLabels.rootAriaLabel : undefined}
2879
+ readOnly={readOnly}
2786
2880
  tapScrollCircleOptions={tapScrollCircleOptions}
2787
2881
  overscanRows={validateOverscan("overscanRows", overscanRows)}
2788
2882
  overscanCols={validateOverscan("overscanCols", overscanCols)}
@@ -2825,6 +2919,8 @@ export const Spreadsheet = forwardRef<SpreadsheetHandle, SpreadsheetProps & Spre
2825
2919
  trailingCols={frozenTrailingColumns}
2826
2920
  trailingRows={frozenTrailingRows}
2827
2921
  sinkAriaLabel={resolvedLabels.sinkAriaLabel}
2922
+ sinkReadOnly={readOnly}
2923
+ sinkKeyShortcuts={resizeKeyShortcuts}
2828
2924
  onSinkMouseDown={onSinkMouseDown}
2829
2925
  editPaintRef={editPaintRef}
2830
2926
  highlightRef={highlightRef}