@sheetxl/grid-react 0.8.0-beta.2 → 0.8.0-beta.21

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.
Files changed (68) hide show
  1. package/LICENSE.md +8 -7
  2. package/README.md +21 -8
  3. package/browser/esm/index.mjs +3632 -3122
  4. package/licenses/dependencies.txt +0 -208
  5. package/node/esm/index.mjs +3968 -2545
  6. package/package.json +9 -13
  7. package/types/src/cell/CellRenderer.d.ts +32 -0
  8. package/types/src/cell/GridlineLayer.d.ts +39 -1
  9. package/types/src/editor/DefaultEditable.d.ts +16 -6
  10. package/types/src/editor/EditSession.d.ts +28 -5
  11. package/types/src/editor/EditorDiv.d.ts +3 -2
  12. package/types/src/editor/EditorInput.d.ts +5 -2
  13. package/types/src/editor/EditorLayer.d.ts +9 -4
  14. package/types/src/editor/EditorStartLayer.d.ts +33 -0
  15. package/types/src/editor/index.d.ts +1 -0
  16. package/types/src/editor/useEditorKeymap.d.ts +7 -2
  17. package/types/src/grid/Grid.d.ts +1 -1
  18. package/types/src/grid/IGrid.d.ts +4 -4
  19. package/types/src/header/GridFreezeAnchor.d.ts +29 -0
  20. package/types/src/header/HeaderDividerAnchor.d.ts +36 -1
  21. package/types/src/header/HeaderFreezeAnchor.d.ts +37 -0
  22. package/types/src/header/_freezeSnap.d.ts +28 -0
  23. package/types/src/header/_useFreezeGrip.d.ts +28 -0
  24. package/types/src/header/index.d.ts +2 -0
  25. package/types/src/header/useHeaderSelectionPointer.d.ts +4 -4
  26. package/types/src/layer/GestureTip.d.ts +77 -0
  27. package/types/src/layer/GridFloatPanel.d.ts +3 -2
  28. package/types/src/layer/Layer.d.ts +6 -0
  29. package/types/src/layer/RangeFloatLayer.d.ts +145 -53
  30. package/types/src/layer/RangePrompt.d.ts +8 -8
  31. package/types/src/layer/RangeTooltip.d.ts +128 -0
  32. package/types/src/layer/RangeTooltipLayer.d.ts +53 -0
  33. package/types/src/layer/StickyAnchor.d.ts +15 -5
  34. package/types/src/layer/StickyPortal.d.ts +73 -0
  35. package/types/src/layer/TooltipProvider.d.ts +104 -0
  36. package/types/src/layer/_Utils.d.ts +9 -3
  37. package/types/src/layer/index.d.ts +6 -4
  38. package/types/src/layer/useGridAnchor.d.ts +45 -4
  39. package/types/src/layer/useHoverIntent.d.ts +35 -8
  40. package/types/src/list/CheckboxList.d.ts +18 -2
  41. package/types/src/list/ListSearch.d.ts +24 -3
  42. package/types/src/list/PickList.d.ts +6 -1
  43. package/types/src/range/DragHandle.d.ts +13 -6
  44. package/types/src/range/Draggable.d.ts +29 -3
  45. package/types/src/range/FillHandle.d.ts +11 -2
  46. package/types/src/range/RangeGlide.d.ts +160 -0
  47. package/types/src/range/RangeRect.d.ts +7 -0
  48. package/types/src/range/RangeResizer.d.ts +7 -1
  49. package/types/src/range/index.d.ts +2 -0
  50. package/types/src/range/useRangeGlide.d.ts +11 -0
  51. package/types/src/selection/RangesPickerLayer.d.ts +4 -1
  52. package/types/src/selection/SelectionLayer.d.ts +71 -15
  53. package/types/src/selection/SelectionStyle.d.ts +4 -1
  54. package/types/src/selection/SelectionTouchGrips.d.ts +6 -4
  55. package/types/src/selection/_SelectionUtils.d.ts +6 -0
  56. package/types/src/selection/useDragCells.d.ts +29 -9
  57. package/types/src/surface/GridPane.d.ts +2 -0
  58. package/types/src/surface/IGridPane.d.ts +15 -2
  59. package/types/src/surface/SurfaceStateProvider.d.ts +1 -1
  60. package/types/src/surface/getPrimaryPane.d.ts +13 -0
  61. package/types/src/surface/index.d.ts +1 -0
  62. package/types/src/surface/useSurfaceState.d.ts +29 -0
  63. package/types/src/svg/RectangleSVG.d.ts +21 -4
  64. package/types/src/theme/GridTheme.d.ts +1 -1
  65. package/browser/cjs/index.cjs +0 -3
  66. package/types/src/layer/HoverTipLayer.d.ts +0 -47
  67. package/types/src/layer/StickyFloat.d.ts +0 -68
  68. package/types/src/layer/useRangeHover.d.ts +0 -36
package/package.json CHANGED
@@ -1,21 +1,20 @@
1
1
  {
2
2
  "name": "@sheetxl/grid-react",
3
3
  "description": "Grid React - Advanced grid component for React",
4
- "version": "0.8.0-beta.2",
5
- "license": "SEE LICENSE IN <LICENSE>",
4
+ "version": "0.8.0-beta.21",
5
+ "license": "SEE LICENSE IN LICENSE.md",
6
6
  "type": "module",
7
7
  "exports": {
8
8
  ".": {
9
9
  "types": "./types/index.d.ts",
10
10
  "browser": "./browser/esm/index.mjs",
11
11
  "node": "./node/esm/index.mjs",
12
- "import": "./browser/esm/index.mjs",
13
- "require": "./browser/cjs/index.cjs"
12
+ "default": "./browser/esm/index.mjs"
14
13
  }
15
14
  },
16
15
  "types": "./types/index.d.ts",
17
16
  "module": "./browser/esm/index.mjs",
18
- "main": "./browser/cjs/index.cjs",
17
+ "main": "./browser/esm/index.mjs",
19
18
  "sideEffects": [
20
19
  "*.css",
21
20
  "**/*.css"
@@ -41,20 +40,17 @@
41
40
  "table"
42
41
  ],
43
42
  "dependencies": {
44
- "@floating-ui/react-dom": "^2.1.9",
45
- "@sheetxl/utils": "^0.8.0-beta.2",
46
- "@sheetxl/utils-react": "^0.8.0-beta.2",
43
+ "@sheetxl/utils": "^0.8.0-beta.21",
44
+ "@sheetxl/utils-react": "^0.8.0-beta.21",
47
45
  "clsx": "^2.1.1",
48
- "react-merge-refs": "^3.0.2",
49
- "react-use": "^17.6.1",
50
- "tiny-invariant": "^1.3.3"
46
+ "react-merge-refs": "^3.0.2"
51
47
  },
52
48
  "peerDependencies": {
53
49
  "react": "^19.0.0",
54
50
  "react-dom": "^19.0.0"
55
51
  },
56
52
  "devDependencies": {
57
- "@types/react": "^19.2.17",
58
- "@types/react-dom": "^19.2.3"
53
+ "@types/react": "^19.3.0",
54
+ "@types/react-dom": "^19.3.0"
59
55
  }
60
56
  }
@@ -50,4 +50,36 @@ export interface CellRendererProps<T = any> extends React.HTMLAttributes<HTMLEle
50
50
  */
51
51
  'data-rc'?: string;
52
52
  }
53
+ /**
54
+ * The attribute that marks cell content as **interactive**: an element a rendered cell draws that
55
+ * takes the pointer itself (a checkbox, a button, a slider's thumb). Spread
56
+ * {@link CELL_INTERACTIVE_PROPS} onto that element rather than writing the attribute by hand.
57
+ *
58
+ * @remarks
59
+ * A rendered cell is `pointer-events: none`, and the selection layer above the cells starts a
60
+ * gesture on every press, so without this nothing drawn in a cell can take a press. A primary
61
+ * press on a marked element selects its cell and starts no drag; the event then reaches the
62
+ * element's own handlers, which decide what the press does (and stop it, so nothing below acts).
63
+ * A press with a modifier (Shift, Ctrl) is a selection press as usual. Layers above the selection
64
+ * still hear the press first.
65
+ */
66
+ export declare const CELL_INTERACTIVE_ATTRIBUTE = "data-cell-interactive";
67
+ /**
68
+ * The props that make an element of cell content interactive ({@link CELL_INTERACTIVE_ATTRIBUTE}):
69
+ * spread them onto the element that takes the pointer, then add its handlers (`onPointerDown`,
70
+ * `onPointerMove`, …) as on any element. The cell layer gives it `pointer-events: auto`; a drag
71
+ * captures the pointer on the element itself (`setPointerCapture`), as the Stage honors capture.
72
+ */
73
+ export declare const CELL_INTERACTIVE_PROPS: Readonly<Record<string, string>>;
74
+ /**
75
+ * Whether `target` is, or is inside, interactive cell content ({@link CELL_INTERACTIVE_ATTRIBUTE}).
76
+ */
77
+ export declare const isCellInteractiveTarget: (target: EventTarget | null) => boolean;
78
+ /**
79
+ * The cell the interactive content under `target` belongs to, read from the layer's `data-rc`
80
+ * stamp on the content's cell root, or `null` when `target` is not interactive content or the
81
+ * content carries no stamp. Content can reach past its cell (overflowing link text), so a press
82
+ * on it is about the content's cell, not the cell under the pointer.
83
+ */
84
+ export declare const getCellInteractiveCoords: (target: EventTarget | null) => CellCoords | null;
53
85
  //# sourceMappingURL=CellRenderer.d.ts.map
@@ -1,5 +1,6 @@
1
1
  import { default as React } from 'react';
2
- import { OrientationFlags } from '@sheetxl/utils';
2
+ import { RangeCoords, OrientationFlags } from '@sheetxl/utils';
3
+ import { IGridPane } from '../surface';
3
4
  import { LayerProps } from '../layer';
4
5
  import { CellModelOptions } from './_CellModel';
5
6
  /**
@@ -28,6 +29,43 @@ export interface GridlineLayerProps extends Omit<LayerProps, 'children'>, CellMo
28
29
  /** Per-`<path>` props for the vertical (column) lines. */
29
30
  propsColumns?: React.SVGProps<SVGPathElement>;
30
31
  }
32
+ /**
33
+ * Options for {@link buildGridlinePath}.
34
+ */
35
+ export interface GridlinePathOptions {
36
+ /**
37
+ * Stroke width in device pixels; decides how each line center snaps.
38
+ *
39
+ * @defaultValue 1
40
+ */
41
+ width?: number;
42
+ /**
43
+ * CSS px added to each line's cross-axis position before it snaps (an even-width stroke that
44
+ * grows toward the trailing side).
45
+ *
46
+ * @defaultValue 0
47
+ */
48
+ shift?: number;
49
+ /**
50
+ * More gaps for one line, as view-relative `[start0, end0, start1, end1, ...]` along the line,
51
+ * sorted by start. `trailIndex` is the row (horizontal) or column (vertical) whose trailing edge
52
+ * the line is.
53
+ */
54
+ breaksAt?: (trailIndex: number) => readonly number[] | undefined;
55
+ }
56
+ /**
57
+ * Build a single SVG path `d` for one orientation's gridlines — each line broken around the merged
58
+ * ranges that straddle it. ONE path with many `M…L…` subpaths (vs one `<path>` per segment) keeps the
59
+ * DOM to a single node per axis; the segment count changes every scroll tick, so per-segment elements
60
+ * would thrash the DOM. Device snapping is baked into each subpath's coordinates and the stroke width
61
+ * is uniform, so a single path renders identically to N separate paths at any dpr.
62
+ *
63
+ * @param pane The pane to draw for.
64
+ * @param isHorizontal `true` for the row lines, `false` for the column lines.
65
+ * @param ranges Index-space ranges the lines break around (merges, overflow).
66
+ * @param options See {@link GridlinePathOptions}.
67
+ */
68
+ export declare const buildGridlinePath: (pane: IGridPane, isHorizontal: boolean, ranges: readonly RangeCoords[], options?: GridlinePathOptions) => string;
31
69
  /**
32
70
  * A **layer** (pane-aware): paints the gridlines for its pane, broken around merged cells, inside a
33
71
  * single full-pane {@link Layer} host box. Sources the merge spans from the merges-only
@@ -1,4 +1,4 @@
1
- import { CellCoords } from '@sheetxl/utils';
1
+ import { CellCoords, MaybePromise } from '@sheetxl/utils';
2
2
  import { ICellAnchor } from '../selection';
3
3
  import { EditCommitArgs } from './EditState';
4
4
  import { EditSession, StartEditOptions, EditModel } from './EditSession';
@@ -10,7 +10,7 @@ import { EditSession, StartEditOptions, EditModel } from './EditSession';
10
10
  * @remarks
11
11
  * An implementation detail of {@link DefaultEditable}, not part of the layer-facing contract. Collapses
12
12
  * the former `getClean` + `canEdit` + `commit` (`null` ⇒ "not editable"). A story closes over a values
13
- * map; the real app reads an `ISheet` (and returns `null` for a protected cell).
13
+ * map; the real app reads an `IWorksheet` (and returns `null` for a protected cell).
14
14
  *
15
15
  * @typeParam C - clean type. @typeParam D - dirty type. @typeParam A - commit args.
16
16
  */
@@ -25,7 +25,7 @@ export type CreateEdit<C, D = string, A = EditCommitArgs> = (coords: CellCoords)
25
25
  * (`startEdit`/`commit`/`cancel`); an app-side bridge can publish {@link getSession} onto the edit-mode
26
26
  * bus on each `subscribe` notification. It does **not** vend sessions — there is only
27
27
  * ever one active edit, held here. {@link DefaultEditable} is the default implementation; the real app
28
- * supplies its own (or reuses it with an `ISheet`-backed {@link CreateEdit}).
28
+ * supplies its own (or reuses it with an `IWorksheet`-backed {@link CreateEdit}).
29
29
  *
30
30
  * @typeParam C - clean type. @typeParam D - dirty type. @typeParam A - commit args.
31
31
  */
@@ -48,8 +48,12 @@ export interface IEditable<C, D = string, A = EditCommitArgs> {
48
48
  subscribeCommitError(listener: (error: unknown) => void): () => void;
49
49
  /** Begin an edit on `coords`. Returns `false` (and does nothing) if the cell is not editable. */
50
50
  startEdit(coords: CellCoords, options?: StartEditOptions<D>): boolean;
51
- /** Commit the current edit. On success clears the session; returns whether it succeeded. */
52
- commit(args?: A): boolean;
51
+ /**
52
+ * Commit the current edit. On success clears the session; returns whether it succeeded, or a
53
+ * promise of that while the model's {@link EditModel.validate} answers asynchronously (the session
54
+ * stays open until then).
55
+ */
56
+ commit(args?: A): MaybePromise<boolean>;
53
57
  /** Cancel the current edit (revert + clear the session). */
54
58
  cancel(): void;
55
59
  /**
@@ -79,6 +83,10 @@ export interface DefaultEditableOptions {
79
83
  * — an invalid entry keeps you in the cell); the owner — who holds the writable selection the
80
84
  * anchor reads — performs the move. When omitted, the editable **cancels** the failed edit instead
81
85
  * (the typed value is discarded, but session and selection stay in sync).
86
+ *
87
+ * A click-away whose commit is still being checked ({@link EditModel.validate} answering
88
+ * asynchronously) is held on the edited cell the same way, and once the commit succeeds this is
89
+ * called again with the cell the user went to.
82
90
  */
83
91
  revertAnchor?: (coords: CellCoords) => void;
84
92
  }
@@ -105,6 +113,7 @@ export declare class DefaultEditable<C, D = string, A = EditCommitArgs> implemen
105
113
  private readonly revertAnchor?;
106
114
  private readonly unsubscribeAnchor;
107
115
  private current;
116
+ private leaveTarget;
108
117
  private readonly listeners;
109
118
  private readonly commitErrorListeners;
110
119
  constructor(anchor: ICellAnchor, createEdit: CreateEdit<C, D, A>, options?: DefaultEditableOptions);
@@ -120,7 +129,8 @@ export declare class DefaultEditable<C, D = string, A = EditCommitArgs> implemen
120
129
  /** @inheritdoc IEditable.startEdit */
121
130
  startEdit(coords: CellCoords, options?: StartEditOptions<D>): boolean;
122
131
  /** @inheritdoc IEditable.commit */
123
- commit(args?: A): boolean;
132
+ commit(args?: A): MaybePromise<boolean>;
133
+ private end;
124
134
  /** @inheritdoc IEditable.cancel */
125
135
  cancel(): void;
126
136
  /** @inheritdoc IEditable.dispose */
@@ -1,4 +1,4 @@
1
- import { CellCoords } from '@sheetxl/utils';
1
+ import { CellCoords, MaybePromise } from '@sheetxl/utils';
2
2
  import { ICellAnchor } from '../selection';
3
3
  import { EditState, EditorMode, EditorTextSelection, EditCommitArgs } from './EditState';
4
4
  /**
@@ -36,10 +36,25 @@ export interface IInputHandle {
36
36
  * @typeParam C - clean type. @typeParam D - dirty type. @typeParam A - commit args.
37
37
  */
38
38
  export interface EditModel<C, D = string, A = EditCommitArgs> {
39
+ /**
40
+ * The cell as the model homes it, when that is more than the cell it was asked to edit (a sheet
41
+ * model adds the sheet's scope). The session reports these; omitted, it reports the cell asked for.
42
+ */
43
+ coords?: CellCoords;
39
44
  /** The clean value, captured now (edit start). */
40
45
  clean: C;
41
46
  /** Write the dirty value to the model. Return `false`, or throw, to reject (keeps the edit open). */
42
47
  commit(dirty: D, args?: A): boolean | void;
48
+ /**
49
+ * Check the dirty value before it is written. Return `false` to refuse: the edit stays open and
50
+ * the leave-key does not navigate.
51
+ *
52
+ * @remarks
53
+ * May answer with a promise, and the commit then waits for it with the edit still open. If the
54
+ * edit is cancelled, or its value changes, before the answer arrives, nothing is written. Whoever
55
+ * refuses says why: the edit only stays open.
56
+ */
57
+ validate?(dirty: D, args?: A): MaybePromise<boolean>;
43
58
  }
44
59
  /**
45
60
  * What the {@link EditSession} constructor takes: the {@link EditModel} bits plus the cell. The owning
@@ -101,6 +116,9 @@ export interface CommitContext<D = string, A = EditCommitArgs> {
101
116
  */
102
117
  export declare class EditSession<C, D = string, A = EditCommitArgs> implements ICellAnchor {
103
118
  private readonly commitFn;
119
+ private readonly validateFn?;
120
+ private pending;
121
+ private cancelled;
104
122
  private readonly onCommitError?;
105
123
  private state;
106
124
  private readonly listeners;
@@ -171,11 +189,16 @@ export declare class EditSession<C, D = string, A = EditCommitArgs> implements I
171
189
  /**
172
190
  * @internal Driven by {@link DefaultEditable.commit}. Runs **every** beforeCommit consumer (a
173
191
  * `veto()` or throw rejects — a throw is also logged, never silently swallowed), then the model's
174
- * write. Returns whether it succeeded. A no-op (nothing typed) skips only the model write — the
175
- * listeners still run (and may veto), so a consumer observing "commit happened" is never skipped.
176
- * Does NOT clear the owning editable's session — `editable.commit` does that on success.
192
+ * {@link EditModel.validate}, then its write. Returns whether it succeeded, or a promise of that
193
+ * while `validate` answers asynchronously. A no-op (nothing typed) skips the check and the write —
194
+ * the listeners still run (and may veto), so a consumer observing "commit happened" is never
195
+ * skipped. Does NOT clear the owning editable's session — `editable.commit` does that on success.
177
196
  */
178
- commit(args?: A): boolean;
197
+ commit(args?: A): MaybePromise<boolean>;
198
+ private write;
199
+ private reportCommitError;
200
+ /** Whether a commit is waiting on an asynchronous {@link EditModel.validate}. */
201
+ isCommitPending(): boolean;
179
202
  /** @internal Driven by {@link DefaultEditable.cancel}. Notifies cancel observers; the editable clears the session. */
180
203
  cancel(): void;
181
204
  /** Register a beforeCommit consumer (validation, peers, …). Many may register; any may veto. */
@@ -55,8 +55,9 @@ export interface EditorDivProps<C, D = string, A = EditCommitArgs> {
55
55
  *
56
56
  * @remarks
57
57
  * Unlike the layer, this is **always visible** — while idle it shows {@link EditorDivProps.idleClean}
58
- * (the active cell). It never auto-grabs focus (`autoFocus: false`): the field is focused by the
59
- * user's click, so the in-grid editor and the formula bar don't fight over focus when an edit begins.
58
+ * (the active cell). It does not grab focus for an ordinary edit: the field is focused by the user's
59
+ * click, so the in-grid editor and the formula bar don't fight over focus when an edit begins. An edit
60
+ * whose session names this host as its active editor from the start is the exception: it opens here.
60
61
  */
61
62
  export declare function EditorDiv<C, D = string, A = EditCommitArgs>(props: EditorDivProps<C, D, A>): React.ReactElement;
62
63
  export declare namespace EditorDiv {
@@ -1,4 +1,4 @@
1
- import { CellCoords, Size } from '@sheetxl/utils';
1
+ import { CellCoords, MaybePromise, Size } from '@sheetxl/utils';
2
2
  import { EditorMode, EditorTextSelection, EditCommitArgs } from './EditState';
3
3
  /**
4
4
  * Props passed to the (swappable) editor input — the visual + keyboard surface of the editor. The
@@ -69,8 +69,11 @@ export interface EditorInputProps<C, D = string> {
69
69
  *
70
70
  * `args` carries the commit "signal" — e.g. {@link EditCommitArgs.broadcast} for a Ctrl+Enter fill,
71
71
  * {@link EditCommitArgs.arrayFixed} for a Ctrl+Shift+Enter array entry.
72
+ *
73
+ * A promise means the commit is still being checked (the model's `validate`); the input stops
74
+ * the key and the session stays open until it lands.
72
75
  */
73
- onCommit(args?: EditCommitArgs): boolean;
76
+ onCommit(args?: EditCommitArgs): MaybePromise<boolean>;
74
77
  /** Cancel/revert the edit. */
75
78
  onCancel(): void;
76
79
  /**
@@ -60,10 +60,15 @@ export interface EditorLayerProps<C, D = string, A = EditCommitArgs> extends Vir
60
60
  * Always-mounted inline cell editor, as a grid layer.
61
61
  *
62
62
  * @remarks
63
- * The input is mounted for the life of the grid (hidden when idle);
64
- * starting an edit just seeds the first character **by value** into the {@link IEditable} and
65
- * focuses the input. Render it inside `renderLayers`; with frozen panes each pane instantiates one,
66
- * and only the pane containing the active cell shows it.
63
+ * The input is mounted for the life of the grid (hidden when idle); starting an edit seeds the first
64
+ * character **by value** into the {@link IEditable} and focuses the input. Mount it as the grid's LAST
65
+ * child so the box paints over everything; with frozen panes each pane instantiates one, and only the
66
+ * pane containing the active cell shows it.
67
+ *
68
+ * This layer carries no event handlers. The ready-mode starters (typed key / F2 / double-click) are
69
+ * the {@link EditorStartLayer}, mounted as the grid's FIRST child: a Layer's handlers are its slot's
70
+ * tap, heard on every layer's turn top first, so start handlers on the top layer would intercept keys
71
+ * and double-clicks aimed at any widget below it before that widget's own handlers ran.
67
72
  */
68
73
  export declare function EditorLayer<C, D = string, A = EditCommitArgs>(props: EditorLayerProps<C, D, A>): React.ReactElement;
69
74
  //# sourceMappingURL=EditorLayer.d.ts.map
@@ -0,0 +1,33 @@
1
+ import { EditCommitArgs } from './EditState';
2
+ import { IEditable } from './DefaultEditable';
3
+ /**
4
+ * Props for {@link EditorStartLayer}.
5
+ *
6
+ * @typeParam C - The clean value type. @typeParam D - The dirty value type. @typeParam A - Commit args.
7
+ */
8
+ export interface EditorStartLayerProps<C, D = string, A = EditCommitArgs> {
9
+ /** The editable to start edits on — the same instance the {@link EditorLayer} renders. */
10
+ editable: IEditable<C, D, A>;
11
+ }
12
+ /**
13
+ * The grid's **ready-mode** edit starters, as a content-less grid layer: a typed character / `F2` /
14
+ * `Backspace` opens an edit on the anchor, a double-click opens one on the cell under the pointer.
15
+ *
16
+ * @remarks
17
+ * This is deliberately a separate layer from the {@link EditorLayer} that paints the box. The box must
18
+ * sit at the **top** of the stack to paint over everything, but a Layer's handlers are its slot's tap
19
+ * — grid-wide ears that fire on every layer's turn, top first — so handlers on the top layer would
20
+ * intercept keys and double-clicks aimed at any widget below it (a chart, a note) before that widget's
21
+ * own handlers ran. Starting an edit is the grid *floor's* behavior: it belongs at the **bottom** of
22
+ * the stack, where it only hears what no widget above consumed. Mount it as the grid's first child.
23
+ *
24
+ * Keys are positionless, so the floor position alone is not enough for them: a focused widget that
25
+ * does not consume a printable key (it has no use for it, and must let keys through so its own
26
+ * commands can resolve) would otherwise still reach this tap. The key start therefore acts only when
27
+ * the key is aimed at the grid surface itself — the surface (or the pane's focus target, which
28
+ * stands in for it) is the focused element — which is what "the grid is in
29
+ * ready mode" means. A double-click needs no such guard: it has a position, and a widget that owns
30
+ * that position consumes it.
31
+ */
32
+ export declare function EditorStartLayer<C, D = string, A = EditCommitArgs>(props: EditorStartLayerProps<C, D, A>): React.ReactElement;
33
+ //# sourceMappingURL=EditorStartLayer.d.ts.map
@@ -6,5 +6,6 @@ export * from './DefaultEditable';
6
6
  export * from './EditorPlacement';
7
7
  export * from './EditorInput';
8
8
  export * from './EditorLayer';
9
+ export * from './EditorStartLayer';
9
10
  export * from './EditorDiv';
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { RefObject } from 'react';
2
- import { Direction, RangeAxis } from '@sheetxl/utils';
2
+ import { Direction, RangeAxis, MaybePromise } from '@sheetxl/utils';
3
3
  import { EditorMode, EditCommitArgs } from './EditState';
4
4
  import { StartEditOptions } from './EditSession';
5
5
  /**
@@ -29,7 +29,12 @@ export interface EditorKeymapProps {
29
29
  value: string;
30
30
  onDirtyChange(dirty: string): void;
31
31
  onModeChange(mode: EditorMode): void;
32
- onCommit(args?: EditCommitArgs): boolean;
32
+ /**
33
+ * Commit the edit. A promise means the commit is still being checked: the key is consumed, and the
34
+ * move it asks for is made through {@link onNavigate} / {@link onTraverse} once the commit lands
35
+ * (with neither supplied, the active cell stays put).
36
+ */
37
+ onCommit(args?: EditCommitArgs): MaybePromise<boolean>;
33
38
  onCancel(): void;
34
39
  /**
35
40
  * Optional. When provided, a successful **enter-mode arrow** commit **navigates here, in the input**,
@@ -3,7 +3,7 @@ import { GridProps, IGrid } from './IGrid';
3
3
  /**
4
4
  * A virtualized grid: a thin wrapper around {@link GridSurface} that re-adds the cell + gridline
5
5
  * rendering the surface itself is agnostic to. It composes a `CellLayer` (cells), a `GridlineLayer`
6
- * (gridlines) and an optional `renderBorders` slot as ordinary layers — in the conventional z-order
6
+ * (gridlines) and an optional `borderLayers` slot as ordinary layers — in the conventional z-order
7
7
  * (gridlines → borders → below-layers → cells → above-layers) — and owns the content cache token the
8
8
  * cells bust on. Everything else is forwarded to the surface.
9
9
  *
@@ -36,13 +36,13 @@ export interface GridProps extends GridSurfaceProps {
36
36
  * Layer(s) rendered BELOW the cells/borders (but above the gridlines) — the place for fills and
37
37
  * highlights. The `children` are the layers ABOVE the cells/borders.
38
38
  */
39
- renderBelow?: LayerNode;
39
+ layersBelow?: LayerNode;
40
40
  /**
41
- * Optional border layer(s), rendered just below the cells (above {@link GridProps.renderBelow} /
42
- * gridlines). A render slot for a custom border treatment — distinct from the light
41
+ * Optional border layer(s), rendered just below the cells (above {@link GridProps.layersBelow} /
42
+ * gridlines), for a custom border treatment — distinct from the light
43
43
  * {@link GridProps.showGridLines} gridlines.
44
44
  */
45
- renderBorders?: LayerNode;
45
+ borderLayers?: LayerNode;
46
46
  /**
47
47
  * Show grid lines. Useful for spreadsheets.
48
48
  */
@@ -0,0 +1,29 @@
1
+ import { RangeAxis } from '@sheetxl/utils';
2
+ import { FreezeDragPosition } from './_freezeSnap';
3
+ export interface GridFreezeAnchorProps {
4
+ /** The axis the freeze moves along: `Column` drags the vertical freeze line. */
5
+ axis: RangeAxis;
6
+ /** Disable the grip (the pointer falls through to the cells). */
7
+ disabled?: boolean;
8
+ /** The pointer has moved past the threshold and the drag has begun. */
9
+ onFreezeDragStart?: (position: FreezeDragPosition) => void;
10
+ /** Called on every move while dragging. */
11
+ onFreezeDrag?: (position: FreezeDragPosition) => void;
12
+ /** The pointer was released after a drag. */
13
+ onFreezeDragEnd?: (position: FreezeDragPosition) => void;
14
+ /** The drag was abandoned (Escape); nothing should be committed. */
15
+ onFreezeDragCancel?: () => void;
16
+ /** The grip became active (hovered or dragging) or stopped being so — e.g. to highlight the freeze line. */
17
+ onActiveChange?: (active: boolean) => void;
18
+ }
19
+ /**
20
+ * The freeze grip over the cells: a hit strip along the main grid's freeze line, dragged like
21
+ * {@link HeaderFreezeAnchor}. It draws nothing — the grid draws the line, and can highlight it from
22
+ * `onActiveChange`.
23
+ *
24
+ * @remarks
25
+ * Render it in the main grid's layers. It exists only while the axis is frozen, in the frozen panes whose
26
+ * trailing edge is the line (for columns, the left and top-left panes).
27
+ */
28
+ export declare const GridFreezeAnchor: import('react').MemoExoticComponent<(props: GridFreezeAnchorProps) => string | number | bigint | boolean | Iterable<import('react').ReactNode> | Promise<string | number | bigint | boolean | import('react').ReactPortal | import('react').ReactElement<unknown, string | import('react').JSXElementConstructor<any>> | Iterable<import('react').ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined>;
29
+ //# sourceMappingURL=GridFreezeAnchor.d.ts.map
@@ -56,15 +56,34 @@ export interface IHeaderDividerAttributes {
56
56
  export interface IHeaderDividerElement extends HTMLDivElement, IHeaderDividerAttributes {
57
57
  }
58
58
  export interface HeaderDividerRendererProps extends HeaderDividerProps {
59
+ /** The pane the divider is drawn in. */
60
+ pane: IGridPane;
61
+ /**
62
+ * The divider's line in pane coordinates — zero-width for a column divider, zero-height for a row —
63
+ * snapped to the device pixel the gridline paints. The renderer positions its element (hit area and
64
+ * visible line) around it.
65
+ */
59
66
  bounds: Bounds;
60
67
  /**
61
68
  * Handle mouse down and all dragging
62
69
  */
63
70
  onPointerDown: React.PointerEventHandler<HTMLDivElement>;
64
71
  onAutoFit: () => void;
72
+ /**
73
+ * Reveal the hidden headers this divider sits on. Present only when the divider has hidden headers
74
+ * to reveal and the consumer handles it.
75
+ */
76
+ onUnhide?: () => void;
77
+ /** The divider is pressed or being dragged. */
78
+ isActive?: boolean;
65
79
  }
66
80
  export interface HeaderDividerAnchorProps extends HeaderDividerProps {
67
- renderDivider?: (props: HeaderDividerRendererProps, pane: IGridPane) => React.ReactNode;
81
+ renderDivider?: (props: HeaderDividerRendererProps) => React.ReactNode;
82
+ /**
83
+ * The clipped bounds of the pane's visible headers, from {@link getHeaderDividerBounds}. Computed when
84
+ * omitted; pass it when rendering a pane's worth of dividers so it is computed once.
85
+ */
86
+ visibleBounds?: Bounds | null;
68
87
  /**
69
88
  * The number of pixels to mouse before resizing starts.
70
89
  * If this number is less than 1 then it will be 1.
@@ -110,11 +129,27 @@ export interface HeaderDividerAnchorProps extends HeaderDividerProps {
110
129
  * @param index
111
130
  */
112
131
  onDividerAutoFit?: (index: number) => void;
132
+ /**
133
+ * Called when the unhide button on a divider over hidden headers is clicked. Without it the divider
134
+ * shows no unhide button.
135
+ *
136
+ * @param index the index of the divider.
137
+ */
138
+ onDividerUnhide?: (index: number) => void;
113
139
  }
140
+ /**
141
+ * The class on the default divider's root element, for styling and for finding dividers in the DOM.
142
+ */
143
+ export declare const HEADER_DIVIDER_CLASS = "sxl-header-divider";
114
144
  export declare const DefaultHeaderDivider: import('react').NamedExoticComponent<Omit<HeaderDividerRendererProps, "ref"> & import('react').RefAttributes<IHeaderDividerElement>>;
115
145
  /**
116
146
  * A divider placed between headers to provider a visual cue for various actions.
117
147
  * Resize, hide/unhide, etc.
118
148
  */
119
149
  export declare const HeaderDividerAnchor: import('react').NamedExoticComponent<Omit<HeaderDividerAnchorProps, "ref"> & import('react').RefAttributes<IHeaderDividerElement>>;
150
+ /**
151
+ * The clipped bounds of a pane's visible headers on an axis — the span every divider in the pane shares —
152
+ * or `null` when none are visible.
153
+ */
154
+ export declare const getHeaderDividerBounds: (pane: IGridPane, axis: RangeAxis) => Bounds | null;
120
155
  //# sourceMappingURL=HeaderDividerAnchor.d.ts.map
@@ -0,0 +1,37 @@
1
+ import { RangeAxis } from '@sheetxl/utils';
2
+ import { FreezeDragPosition } from './_freezeSnap';
3
+ export type { FreezeDragPosition } from './_freezeSnap';
4
+ export interface HeaderFreezeAnchorProps {
5
+ /** The axis the freeze moves along: `Column` drags the vertical freeze line along the column header. */
6
+ axis: RangeAxis;
7
+ /** Disable the grip entirely (no hit area, no hover). */
8
+ disabled?: boolean;
9
+ /**
10
+ * Draw the bar in its active color although this grip is not hovered — e.g. while the freeze line's grip
11
+ * in the body is.
12
+ */
13
+ highlight?: boolean;
14
+ /** The pointer has moved past the threshold and the drag has begun. */
15
+ onFreezeDragStart?: (position: FreezeDragPosition) => void;
16
+ /** Called on every move while dragging. */
17
+ onFreezeDrag?: (position: FreezeDragPosition) => void;
18
+ /** The pointer was released after a drag. */
19
+ onFreezeDragEnd?: (position: FreezeDragPosition) => void;
20
+ /** The drag was abandoned (Escape); nothing should be committed. */
21
+ onFreezeDragCancel?: () => void;
22
+ /** The grip became active (hovered or dragging) or stopped being so — e.g. to highlight the freeze line. */
23
+ onActiveChange?: (active: boolean) => void;
24
+ }
25
+ /**
26
+ * The freeze grip: a bar in a header on the freeze line (or at the header's start when nothing is frozen
27
+ * on this axis). Dragging it snaps the freeze to the nearest header boundary on screen.
28
+ *
29
+ * @remarks
30
+ * Render it in the header's layers, above the resize dividers. It draws in one pane only: the frozen pane,
31
+ * centered on its trailing edge (frozen panes sit over the scrolling one, so the grip covers the last
32
+ * frozen header's divider), or the scrolling pane's leading edge when nothing is frozen. The drag reports
33
+ * positions only — committing the freeze is the caller's. {@link GridFreezeAnchor} is the same grip on the
34
+ * freeze line over the cells.
35
+ */
36
+ export declare const HeaderFreezeAnchor: import('react').MemoExoticComponent<(props: HeaderFreezeAnchorProps) => import("react").JSX.Element | null>;
37
+ //# sourceMappingURL=HeaderFreezeAnchor.d.ts.map
@@ -0,0 +1,28 @@
1
+ import { IGridPane } from '../surface';
2
+ /**
3
+ * Where a freeze drag would land: the snapped boundary and the raw pointer, both as CLIENT coordinates
4
+ * on the drag axis so any pane (header or body) maps them with `getPanePointFromPointer`.
5
+ */
6
+ export interface FreezeDragPosition {
7
+ /**
8
+ * The freeze count the drop commits: the index of the first track in the scrolling area. `0` ⇒
9
+ * unfreeze this axis.
10
+ */
11
+ index: number;
12
+ /** Client coordinate (x for columns, y for rows) of the snapped boundary — the solid line. */
13
+ snapClient: number;
14
+ /**
15
+ * Client coordinate of the pointer — the dashed line. Absent once the pointer is past the start of the
16
+ * header (where the drop unfreezes), so no line is drawn outside the grid.
17
+ */
18
+ pointerClient?: number;
19
+ }
20
+ /**
21
+ * Snap a client coordinate to the nearest track boundary on screen across the header's panes.
22
+ *
23
+ * Candidates are the leading edges of the visible tracks in the frozen pane and the scrolling pane, plus
24
+ * the surface's own leading edge (which unfreezes). Only boundaries actually on screen are candidates, so
25
+ * a drag can never freeze past what is visible.
26
+ */
27
+ export declare const snapToBoundary: (pane: IGridPane, isColumn: boolean, pointerClient: number, crossClient: number) => FreezeDragPosition;
28
+ //# sourceMappingURL=_freezeSnap.d.ts.map
@@ -0,0 +1,28 @@
1
+ import { RangeAxis } from '@sheetxl/utils';
2
+ import { FreezeDragPosition } from './_freezeSnap';
3
+ export interface FreezeGripOptions {
4
+ axis: RangeAxis;
5
+ disabled: boolean;
6
+ /** Whether this pane draws the grip. A pane that stops drawing it never hears the pointer leave. */
7
+ isOwner: boolean;
8
+ onFreezeDragStart?: (position: FreezeDragPosition) => void;
9
+ onFreezeDrag?: (position: FreezeDragPosition) => void;
10
+ onFreezeDragEnd?: (position: FreezeDragPosition) => void;
11
+ onFreezeDragCancel?: () => void;
12
+ onActiveChange?: (active: boolean) => void;
13
+ }
14
+ export interface FreezeGrip {
15
+ /** Hovered or dragging, in the owning pane, while enabled. */
16
+ isActive: boolean;
17
+ cursor: string;
18
+ /** Spread onto the hit area. */
19
+ hitProps: React.HTMLAttributes<HTMLDivElement>;
20
+ /** The drag backdrop (forces the cursor, shields other components); render it alongside the hit area. */
21
+ backdrop: React.ReactNode;
22
+ }
23
+ /**
24
+ * The gesture behind a freeze grip, wherever its hit area sits: press, threshold, snapped drag positions,
25
+ * Escape to abandon, and the active (hovered or dragging) state.
26
+ */
27
+ export declare const useFreezeGrip: (options: FreezeGripOptions) => FreezeGrip;
28
+ //# sourceMappingURL=_useFreezeGrip.d.ts.map
@@ -1,3 +1,5 @@
1
1
  export * from './HeaderDividerAnchor';
2
+ export * from './HeaderFreezeAnchor';
3
+ export * from './GridFreezeAnchor';
2
4
  export * from './useHeaderSelectionPointer';
3
5
  //# sourceMappingURL=index.d.ts.map