@sheetxl/grid-react 0.8.0-beta.1 → 0.8.0-beta.10

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 (57) hide show
  1. package/README.md +16 -5
  2. package/browser/esm/index.mjs +3419 -2810
  3. package/licenses/dependencies.txt +0 -208
  4. package/node/esm/index.mjs +3699 -2172
  5. package/package.json +8 -12
  6. package/types/src/cell/CellRenderer.d.ts +14 -3
  7. package/types/src/editor/DefaultEditable.d.ts +23 -4
  8. package/types/src/editor/EditSession.d.ts +34 -5
  9. package/types/src/editor/EditState.d.ts +6 -0
  10. package/types/src/editor/EditorInput.d.ts +7 -3
  11. package/types/src/editor/EditorLayer.d.ts +9 -4
  12. package/types/src/editor/EditorStartLayer.d.ts +32 -0
  13. package/types/src/editor/index.d.ts +1 -0
  14. package/types/src/editor/useEditorKeymap.d.ts +18 -2
  15. package/types/src/header/GridFreezeAnchor.d.ts +29 -0
  16. package/types/src/header/HeaderDividerAnchor.d.ts +29 -0
  17. package/types/src/header/HeaderFreezeAnchor.d.ts +37 -0
  18. package/types/src/header/_freezeSnap.d.ts +28 -0
  19. package/types/src/header/_useFreezeGrip.d.ts +28 -0
  20. package/types/src/header/index.d.ts +2 -0
  21. package/types/src/header/useHeaderSelectionPointer.d.ts +10 -6
  22. package/types/src/layer/GestureTip.d.ts +69 -0
  23. package/types/src/layer/GridFloatPanel.d.ts +19 -0
  24. package/types/src/layer/Layer.d.ts +6 -0
  25. package/types/src/layer/RangeFloatLayer.d.ts +143 -51
  26. package/types/src/layer/RangePrompt.d.ts +8 -8
  27. package/types/src/layer/RangeTooltip.d.ts +123 -0
  28. package/types/src/layer/RangeTooltipLayer.d.ts +53 -0
  29. package/types/src/layer/StickyAnchor.d.ts +15 -5
  30. package/types/src/layer/StickyPortal.d.ts +73 -0
  31. package/types/src/layer/TooltipProvider.d.ts +101 -0
  32. package/types/src/layer/index.d.ts +7 -4
  33. package/types/src/layer/useGridAnchor.d.ts +45 -4
  34. package/types/src/layer/useHoverIntent.d.ts +35 -8
  35. package/types/src/list/CheckboxList.d.ts +22 -1
  36. package/types/src/list/List.d.ts +11 -2
  37. package/types/src/list/ListSearch.d.ts +52 -0
  38. package/types/src/list/ListSurface.d.ts +25 -5
  39. package/types/src/list/PickList.d.ts +71 -0
  40. package/types/src/list/_measureText.d.ts +14 -0
  41. package/types/src/list/index.d.ts +2 -0
  42. package/types/src/range/DragHandle.d.ts +19 -2
  43. package/types/src/range/Draggable.d.ts +132 -7
  44. package/types/src/range/FillHandle.d.ts +33 -3
  45. package/types/src/range/RangeInsertBar.d.ts +91 -0
  46. package/types/src/range/index.d.ts +2 -0
  47. package/types/src/selection/SelectionLayer.d.ts +128 -15
  48. package/types/src/selection/SelectionStyle.d.ts +33 -0
  49. package/types/src/selection/SelectionTouchGrips.d.ts +0 -4
  50. package/types/src/selection/_SelectionUtils.d.ts +6 -0
  51. package/types/src/selection/useDragCells.d.ts +65 -9
  52. package/types/src/surface/useSurfaceState.d.ts +19 -0
  53. package/types/src/svg/_Glide.d.ts +9 -0
  54. package/browser/cjs/index.cjs +0 -3
  55. package/types/src/layer/HoverTipLayer.d.ts +0 -47
  56. package/types/src/layer/StickyFloat.d.ts +0 -68
  57. package/types/src/layer/useRangeHover.d.ts +0 -36
@@ -22,6 +22,8 @@ export interface DragHandleRenderProps {
22
22
  * repositioning under an edge auto-scroll).
23
23
  */
24
24
  handleProps: {
25
+ /** The handle's identifying class(es) — `drag-handle` plus whatever the flavor tagged it with. */
26
+ className?: string;
25
27
  onPointerDown: (e: React.PointerEvent) => void;
26
28
  onDoubleClick?: (e: React.MouseEvent) => void;
27
29
  onPointerEnter?: (e: React.PointerEvent) => void;
@@ -52,8 +54,11 @@ export interface DragHandleProps {
52
54
  dragCursor?: string;
53
55
  /** Default-dot background, and the theme color handed to {@link DragHandleProps.renderHandle}. */
54
56
  background?: string;
55
- /** Drag started; `cell` is under the pointer at press. */
56
- onDragStart?: (cell: CellCoords, e: React.PointerEvent) => void;
57
+ /**
58
+ * Drag started; `cell` is under the pointer at press. `cancel` aborts this drag as Escape would
59
+ * ({@link DragHandleProps.onDragCancel} follows) — hand it to chrome that offers a way out.
60
+ */
61
+ onDragStart?: (cell: CellCoords, e: React.PointerEvent, cancel: () => void) => void;
57
62
  /** The pointer moved to a new cell during the drag (`from` = press cell, `to` = current). */
58
63
  onDragMove?: (from: CellCoords, to: CellCoords, e: React.PointerEvent) => void;
59
64
  /** Drag ended (`from` = press cell, `to` = last cell). */
@@ -108,6 +113,10 @@ export interface DragHandleProps {
108
113
  renderHandle?: (props: DragHandleRenderProps) => React.ReactNode;
109
114
  /** Static content inside the default dot. Ignored when `renderHandle` is set. */
110
115
  children?: React.ReactNode;
116
+ /**
117
+ * Extra class(es) for the handle element. Joined with the `drag-handle` tag every handle carries,
118
+ * so a flavor names itself (`fill-handle`, `resize-handle`) without losing the generic hook.
119
+ */
111
120
  className?: string;
112
121
  style?: React.CSSProperties;
113
122
  /**
@@ -117,6 +126,14 @@ export interface DragHandleProps {
117
126
  * it preserves whatever had focus rather than stealing it. @defaultValue false
118
127
  */
119
128
  preserveFocus?: boolean;
129
+ /**
130
+ * Also start a drag from the **secondary** (right) button — Excel's right-drag of the fill handle,
131
+ * which offers a menu on release instead of filling. The drag callbacks receive the event, so a
132
+ * consumer tells the two apart by `e.button`. The `contextmenu` the button raises (on press on some
133
+ * platforms, on release on others) is swallowed for the gesture's duration: the drag IS the intent.
134
+ * @defaultValue false
135
+ */
136
+ secondaryButton?: boolean;
120
137
  }
121
138
  /**
122
139
  * A generic, draggable handle anchored to a corner/edge of a grid range. It owns only the **gesture
@@ -1,4 +1,102 @@
1
- import { RangeCoords } from '@sheetxl/utils';
1
+ import { RangeAxis, CellCoords, RangeCoords } from '@sheetxl/utils';
2
+ import { IGridPane } from '../surface';
3
+ import { DragModifiers } from '../selection/useDragCells';
4
+ /**
5
+ * Where an **insert**-style drop would land: the dragged block placed at `origin`, with the cells there
6
+ * stepping aside along `axis`. `origin`'s edge on that axis is the grid line the I-beam is drawn on.
7
+ *
8
+ * @remarks
9
+ * An insert re-anchors where a move does not. A move keeps the grab offset (grab A1:C3 by C3 and the
10
+ * block stays under your hand); an insert has no "under whatever you grabbed" to preserve — it goes at
11
+ * the line you are pointing at, so the block's top-left is the thing that must land there. Same reason
12
+ * the I-beam starts at the pointer's cell rather than trailing off to one side of it.
13
+ */
14
+ export interface DragInsert {
15
+ /** `Row` — the cells shift down (a horizontal beam); `Column` — they shift right (a vertical one). */
16
+ axis: RangeAxis;
17
+ /**
18
+ * Where the block's TOP-LEFT lands. On the cross axis it is the cell under the pointer (the
19
+ * re-anchor); on `axis` it is the grid line nearest the pointer, which may be the FAR side of that
20
+ * cell — an index of `n` meaning the line before row/column `n`, so the two readings coincide.
21
+ */
22
+ origin: CellCoords;
23
+ }
24
+ /**
25
+ * The in-flight drag's live state, reported alongside the translated range.
26
+ *
27
+ * @remarks
28
+ * `mods` is the reason this exists: a modifier pressed with the pointer standing still changes what
29
+ * the drop will DO, and no pointer event carries that. Read the modifiers from here, never off the
30
+ * event handed to the same callback (which is the last one the *pointer* fired).
31
+ */
32
+ export interface DragMoveInfo {
33
+ /** The live Ctrl / Shift / Alt / Meta state (Excel: Ctrl = copy, Shift = insert). */
34
+ mods: DragModifiers;
35
+ /**
36
+ * Where an insert-style (Shift) drop would land, resolved from the pointer's position within its
37
+ * cell (see {@link insertAt}). Tracked whether or not Shift is down, so the I-beam appears the
38
+ * instant it goes down. `null` only when the pane could not resolve the pointer. {@link insertRange}
39
+ * turns it into a destination range.
40
+ */
41
+ insert: DragInsert | null;
42
+ /**
43
+ * Where the block was grabbed, fixed at the press. `null` only when the pane could not resolve the
44
+ * pointer.
45
+ */
46
+ grab: DragGrab | null;
47
+ }
48
+ /**
49
+ * Where a drag took hold of its block: the border nearest the press and the cell it pressed. A move keeps
50
+ * the grab offset, so the same cell of the translated block stays under the pointer — translate
51
+ * `cell` by the drag's delta to find it.
52
+ */
53
+ export interface DragGrab {
54
+ /** The border nearest the press. With `grip: 'entire'` it is still the nearest, though seldom meant. */
55
+ edge: RangeEdge;
56
+ /** The cell of the source range that was pressed (clamped into it: the ring reaches just outside). */
57
+ cell: CellCoords;
58
+ }
59
+ /**
60
+ * The insert a drop would perform from the pointer's position, as Excel resolves it — the first half
61
+ * of the drop geometry ({@link insertRange} is the second: this picks the LINE, that places the block
62
+ * on it). Two rules that are deliberately NOT symmetric:
63
+ *
64
+ * - the pointer's **horizontal** position within the cell picks the axis: within
65
+ * {@link INSERT_COLUMN_BAND} of a vertical edge it is a column insert, anywhere in the middle half a
66
+ * row insert. So rows are the easy, default direction and columns are aimed for — you go for a
67
+ * column edge to get one. Skipped entirely when `forced` names the axis (a whole row / column);
68
+ * - the line then snaps to the **nearest** grid line on that axis, which may be the far side of the
69
+ * cell (below it / to the right of it), not only its leading edge. A forced axis is therefore a
70
+ * straight 50/50 across the cell, the axis pick no longer taking a cut of it.
71
+ *
72
+ * The cross axis re-anchors to the pointer's own cell, so the beam always starts where you point.
73
+ *
74
+ * @remarks
75
+ * Both measured proportionally (a fraction of the cell), which is how Excel behaves across cell sizes
76
+ * — and necessary anyway: a row is typically a third the height of a column's width, so any pixel
77
+ * threshold would behave differently at every zoom and row height.
78
+ *
79
+ * A MERGE is IGNORED: the line falls on the true cell under the pointer, exactly as if the merge
80
+ * weren't there. It has to be resolved here rather than taken from the drag's cell, which is a
81
+ * merge's top-left (what `getCellFromPointer` reports, and the right answer for a move destination) —
82
+ * measured against that anchor the fractions describe a box the pointer is nowhere near, and the far
83
+ * line lands one cell in from it, which is how the beam ends up drawn against the top-left corner of
84
+ * a merge instead of under the cursor. Snapping to the merge's outer edge instead is no better: the
85
+ * beam then jumps to a far edge the pointer isn't near. A line that falls INSIDE a merge is a drop
86
+ * the model refuses, which the preview already says in its own way — see `MoveDrop.refused`.
87
+ */
88
+ export declare const insertAt: (pane: IGridPane, cell: CellCoords, clientX: number, clientY: number, forced: RangeAxis | null, prior: DragInsert | null) => DragInsert | null;
89
+ /**
90
+ * Where an **insert** drop of `range` lands: the block placed at {@link DragInsert.origin} — its
91
+ * top-left RE-ANCHORED on the pointer's own cell across the insert axis, and on the snapped grid line
92
+ * along it (existing cells shifting from there). Clamped into `bounds`, which can pull a block back so
93
+ * it still fits at the far edge.
94
+ *
95
+ * Unlike a move — which keeps the offset of wherever you grabbed the block — an insert drops the grab
96
+ * offset entirely: it goes where you are pointing. The same function drives the I-beam, the tip and
97
+ * the commit, so what is drawn is what lands.
98
+ */
99
+ export declare const insertRange: (range: RangeCoords, insert: DragInsert, bounds: RangeCoords) => RangeCoords;
2
100
  /**
3
101
  * Props for {@link Draggable}.
4
102
  */
@@ -42,22 +140,44 @@ export interface DraggableProps {
42
140
  * mid-drag; use this to keep a drag affordance (e.g. the thickened move border) lit for the whole drag.
43
141
  */
44
142
  onDragStateChange?: (dragging: boolean) => void;
45
- /** Drag started; `range` is the original. */
46
- onDragStart?: (range: RangeCoords, e: React.PointerEvent) => void;
143
+ /**
144
+ * Drag started; `range` is the original. `cancel` aborts this drag as Escape would
145
+ * ({@link DraggableProps.onDragStateChange} `false` follows, nothing committed) — hand it to chrome
146
+ * that offers a way out.
147
+ */
148
+ onDragStart?: (range: RangeCoords, e: React.PointerEvent, info: DragMoveInfo, cancel: () => void) => void;
47
149
  /**
48
150
  * The translated range during the drag. Wire it to update `range` (the content follows the pointer —
49
151
  * "live"), or to position a composed ghost preview. `Draggable` itself stays silent.
152
+ *
153
+ * @remarks
154
+ * Fires when the destination changes AND when the modifiers do (Ctrl / Shift pressed or released with
155
+ * the pointer standing still) — the two are one signal on purpose: everything a consumer reflects
156
+ * during a drag (a mode description, a cursor, a drop preview) depends on both, and splitting them
157
+ * into two callbacks is how one of the pair ends up stale. Since a modifier change fires no pointer
158
+ * event, `e` is then the last one the POINTER produced: read the modifiers off `info.mods`, not `e`.
50
159
  */
51
- onDragMove?: (range: RangeCoords, e: React.PointerEvent) => void;
52
- /** Drag released: commit the translated range. */
53
- onDragEnd?: (range: RangeCoords, e: React.PointerEvent) => void;
160
+ onDragMove?: (range: RangeCoords, e: React.PointerEvent, info: DragMoveInfo) => void;
161
+ /** Drag released: commit the translated range. `info` carries the modifiers held at the drop. */
162
+ onDragEnd?: (range: RangeCoords, e: React.PointerEvent, info: DragMoveInfo) => void;
54
163
  /**
55
164
  * Render a **ghost** at the in-flight translated range while a drag is in progress (nothing is rendered
56
165
  * when idle). `Draggable` owns the preview state internally, so the consumer doesn't have to stash it
57
166
  * — return e.g. a faded {@link RangeRect} to trail the pointer while the original stays put (the
58
167
  * cut-move pattern). For a "live" feel (content follows the pointer) wire `onDragMove → range` instead.
168
+ *
169
+ * `info` re-renders the preview on a modifier change, so the ghost can become an insert I-beam the
170
+ * moment Shift goes down.
59
171
  */
60
- renderMove?: (range: RangeCoords) => React.ReactNode;
172
+ renderMove?: (range: RangeCoords, info: DragMoveInfo) => React.ReactNode;
173
+ /**
174
+ * The cursor painted for the duration of the drag. The grip's element is pointer-captured, so ITS
175
+ * cursor is the one the browser shows once a drag is in flight — pass a mode-derived cursor (`copy`
176
+ * while Ctrl is held, say) to make the drop mode visible under the pointer.
177
+ *
178
+ * @defaultValue 'grabbing'
179
+ */
180
+ dragCursor?: string;
61
181
  /**
62
182
  * Keep the current focus across a move drag (`preventDefault` on pointer-down, suppressing the compat
63
183
  * `mousedown` that would move focus to the grid's `tabIndex` Stage). Used by the reference-range picker
@@ -74,6 +194,11 @@ export interface DraggableProps {
74
194
  * name, so the nearest one is still reported but rarely means anything.
75
195
  */
76
196
  onEdgeDoubleClick?: (range: RangeCoords, edge: RangeEdge, e: React.PointerEvent) => void;
197
+ /**
198
+ * Extra class(es) for the grip box. Joined with the `drag-grip` tag every grip carries, so a
199
+ * flavor names itself (`move-grip`) without losing the generic hook.
200
+ */
201
+ className?: string;
77
202
  }
78
203
  /** Which border of a range a gesture landed on. */
79
204
  export type RangeEdge = 'top' | 'bottom' | 'left' | 'right';
@@ -1,5 +1,5 @@
1
1
  import { default as React } from 'react';
2
- import { RangeCoords } from '@sheetxl/utils';
2
+ import { CellCoords, RangeCoords } from '@sheetxl/utils';
3
3
  import { VisualSVGProps } from '../svg';
4
4
  import { DragHandleRenderProps } from './DragHandle';
5
5
  /**
@@ -16,8 +16,11 @@ export interface FillHandleProps extends VisualSVGProps {
16
16
  * `enableFill` is set (or via a `singleLayer` slot); required in practice for standalone use.
17
17
  */
18
18
  range?: RangeCoords;
19
- /** Drag started; `range` is the template. */
20
- onDragStart?: (range: RangeCoords, e: React.PointerEvent) => void;
19
+ /**
20
+ * Drag started; `range` is the template. `cancel` aborts this drag as Escape would
21
+ * ({@link FillHandleProps.onDragDone} follows, no fill) — hand it to chrome that offers a way out.
22
+ */
23
+ onDragStart?: (range: RangeCoords, e: React.PointerEvent, cancel: () => void) => void;
21
24
  /**
22
25
  * Adjust the raw destination before it drives the preview + `onDrag*` — e.g. snap the fill edge to a
23
26
  * merged-cell boundary so the dashed preview and the committed fill agree (and never land on an
@@ -29,6 +32,16 @@ export interface FillHandleProps extends VisualSVGProps {
29
32
  onDragMove?: (range: RangeCoords, e: React.PointerEvent) => void;
30
33
  /** Drag released: commit the fill to this destination range. Fires only when there IS a fill. */
31
34
  onDragEnd?: (range: RangeCoords, e: React.PointerEvent) => void;
35
+ /**
36
+ * Released from a **secondary-button** (right) drag: offer a menu of fills at the pointer instead
37
+ * of committing one — Excel's right-drag of the fill handle. Fires INSTEAD of
38
+ * {@link FillHandleProps.onDragEnd} for that gesture (then {@link FillHandleProps.onDragDone} as
39
+ * usual); supplying it is what enables the right-button drag at all. Left-drag is untouched.
40
+ *
41
+ * The dashed preview STAYS on the release — the fill is still being decided — until `done` is
42
+ * called: once the menu has answered, or closed without answering. A new drag clears it regardless.
43
+ */
44
+ onMenu?: (range: RangeCoords, e: React.PointerEvent, done: () => void) => void;
32
45
  /**
33
46
  * The drag finished — fires on **every** release after a press (unlike {@link FillHandleProps.onDragEnd},
34
47
  * which fires only when a fill was drawn) AND on an abort (Escape / OS pointercancel). Use it for
@@ -49,6 +62,23 @@ export interface FillHandleProps extends VisualSVGProps {
49
62
  */
50
63
  renderHandle?: (props: DragHandleRenderProps) => React.ReactNode;
51
64
  }
65
+ /**
66
+ * The destination range when filling `template` toward `to`. Excel-style single axis: extend along
67
+ * whichever direction the pointer reaches furthest beyond the template, clamped to `bounds`. The
68
+ * destination includes the template.
69
+ *
70
+ * A pointer INSIDE the template shrinks it instead — Excel's drag of the fill handle back over the
71
+ * source, which vacates the cells past the pointer. Only the edges the handle sits on can move, the
72
+ * bottom and the right, along whichever axis the pointer pulled furthest from the corner; a whole-row
73
+ * or whole-column template has no such edge on its long axis and shrinks only on the other. Returns
74
+ * `null` when `to` is the handle's own corner (nothing to fill or vacate).
75
+ */
76
+ export declare const resolveFillDestination: (template: RangeCoords, to: CellCoords, bounds: RangeCoords) => RangeCoords | null;
77
+ /**
78
+ * The cells a shrinking `destination` vacates from `template` — the strip past its bottom or right
79
+ * edge — or `null` for an outward fill.
80
+ */
81
+ export declare const vacatedByFill: (template: RangeCoords, destination: RangeCoords) => RangeCoords | null;
52
82
  /**
53
83
  * The fill layer: a {@link DragHandle} at a corner of the `range` (bottom-right by default). It owns
54
84
  * the handle placement — including the whole-row/column special-casing (pin to bottom-left / top-right
@@ -0,0 +1,91 @@
1
+ import { RangeAxis, RangeCoords } from '@sheetxl/utils';
2
+ /**
3
+ * Props for {@link RangeInsertBar}.
4
+ */
5
+ export interface RangeInsertBarProps {
6
+ /**
7
+ * The range the insert lands on — the block's destination. Its LEADING edge on {@link axis} is the
8
+ * grid line the bar is drawn at (`insertRange` produces exactly this), and its other axis is the
9
+ * extent the bar spans, so the bar shows how wide the incoming block is.
10
+ */
11
+ range: RangeCoords;
12
+ /**
13
+ * Which way the existing cells shift: `Row` draws a horizontal bar on the range's top edge (cells
14
+ * shift down), `Column` a vertical bar on its left edge (cells shift right).
15
+ */
16
+ axis: RangeAxis;
17
+ /** The bar color. @defaultValue the grid primary. */
18
+ color?: string;
19
+ /**
20
+ * Bar thickness in DEVICE px — the same currency as a selection outline's `alignWidth`, so a bar and
21
+ * a border of the same number read as the same weight at any dpr.
22
+ *
23
+ * @defaultValue 3
24
+ */
25
+ alignWidth?: number;
26
+ /**
27
+ * How far each end cap reaches past the bar, per side, in DEVICE px. `0` draws a plain bar.
28
+ *
29
+ * @defaultValue 3
30
+ */
31
+ capExtent?: number;
32
+ /**
33
+ * How long the bar takes to travel to a new line, in ms. `0` places it instantly.
34
+ *
35
+ * @defaultValue 100
36
+ */
37
+ transitionDuration?: number;
38
+ /**
39
+ * The curve it travels on. The default is a hard decelerate — most of the distance is covered in
40
+ * the first third, then it settles — which is what reads as SNAPPING onto a gridline rather than
41
+ * sliding to a stop near one.
42
+ *
43
+ * @defaultValue `cubic-bezier(0.16, 1, 0.3, 1)`
44
+ */
45
+ transitionEasing?: string;
46
+ /**
47
+ * How long the beam takes to TURN, in ms — the outgoing orientation drawing in and the incoming one
48
+ * drawing out, together. `0` turns instantly. Shorter than
49
+ * {@link RangeInsertBarProps.transitionDuration} on purpose: a turn is a change of meaning (rows
50
+ * step aside, not columns), so it wants to read as a flip rather than as travel.
51
+ *
52
+ * @defaultValue 70
53
+ */
54
+ axisDuration?: number;
55
+ }
56
+ /**
57
+ * The **insert I-beam**: the Excel bar shown while Shift-dragging a range, marking the grid line the
58
+ * block will be inserted at (rather than dropped over). Drawn as a heavy line with a short cap at each
59
+ * end — the serifs are what separate "inserting AT this line" from "a border of something".
60
+ *
61
+ * @remarks
62
+ * Three device-aligned boxes rather than an SVG path: each is snapped independently, so the bar sits
63
+ * on the boundary pixel exactly like a gridline or a selection edge, at any dpr or zoom. It is placed
64
+ * from the destination range alone ({@link RangeInsertBarProps.range}) — the same range the drop
65
+ * commits — so the drawing and the operation can never disagree.
66
+ *
67
+ * The bar is clipped to the pane (a whole-column insert spans millions of pixels, and a box that size
68
+ * is a compositing tax for ink nobody can see), and an end cap is drawn only where the block's real
69
+ * end is on screen — a cap at a clipped edge would claim an end that isn't there.
70
+ *
71
+ * It GLIDES between lines: the three boxes transition their placement and their size together, on one
72
+ * duration and one curve, so they travel as a single object, and the color rides the same timing (a
73
+ * drop turning refused fades rather than blinks).
74
+ *
75
+ * A change of AXIS is a TURN, and both orientations are always mounted to draw it: each keeps its own
76
+ * shape forever and shows or hides by scaling, so the outgoing beam draws in while the incoming one
77
+ * draws out and something is on screen the whole way. They scale about the corner they SHARE — the
78
+ * destination's top-left, where a row beam starts running right and a column beam starts running
79
+ * down — so the pair reads as one beam swinging around that point, which is the part a rotation would
80
+ * have sold. That pivot is stated in PANE coordinates: these boxes lay out at the pane origin and
81
+ * move by transform, so their transform-origin is measured from the pane's corner, and a pivot stated
82
+ * relative to the box would scale the placement along with the beam and fly it in from the top-left.
83
+ *
84
+ * Two things this deliberately does NOT do. It does not interpolate one orientation into the other:
85
+ * width and height would cross and a bar whose width and height cross is a box at the halfway point,
86
+ * which smears. And it does not cut the old one and grow the new one, which leaves a moment with no
87
+ * beam at all — read as a flicker. (A real rotation is not something one transition can express: the
88
+ * orientations differ in LENGTH as well as angle.)
89
+ */
90
+ export declare const RangeInsertBar: import('react').MemoExoticComponent<(props: RangeInsertBarProps) => import("react").JSX.Element | null>;
91
+ //# sourceMappingURL=RangeInsertBar.d.ts.map
@@ -6,8 +6,10 @@
6
6
  */
7
7
  export * from './RangeStyle';
8
8
  export * from './RangeRect';
9
+ export * from './RangeInsertBar';
9
10
  export * from './DragHandle';
10
11
  export * from './Draggable';
11
12
  export * from './RangeResizer';
12
13
  export * from './FillHandle';
14
+ export * from './useDoubleClick';
13
15
  //# sourceMappingURL=index.d.ts.map
@@ -1,11 +1,11 @@
1
1
  import { default as React } from 'react';
2
- import { CellCoords, RangeCoords } from '@sheetxl/utils';
2
+ import { RangeAxis, CellCoords, RangeCoords } from '@sheetxl/utils';
3
3
  import { LayerProps, GridPlacement } from '../layer';
4
4
  import { VisualSVGProps } from '../svg';
5
5
  import { RangeEdge } from '../range';
6
6
  import { ISelection } from './ISelection';
7
7
  import { SelectionNavigation } from './Types';
8
- import { SelectionRangeStyle } from './SelectionStyle';
8
+ import { SelectionRangeStyle, InsertBarStyle } from './SelectionStyle';
9
9
  /**
10
10
  * The in-flight selection gesture passed to {@link SelectionLayerProps.renderTip}:
11
11
  * - `select` — a drag-select growing the active range (Excel's live R×C);
@@ -19,11 +19,44 @@ export type SelectionGesture = {
19
19
  kind: 'move';
20
20
  from: RangeCoords;
21
21
  to: RangeCoords;
22
+ drop: MoveDrop;
22
23
  } | {
23
24
  kind: 'fill';
24
25
  template: RangeCoords;
25
26
  destination: RangeCoords;
26
27
  };
28
+ /**
29
+ * What a move drag would DO if released now — the destination plus the mode its modifiers select.
30
+ * Excel's three grip gestures, as data: drag relocates, Ctrl+drag copies, Shift+drag inserts (and
31
+ * Ctrl+Shift inserts a copy).
32
+ *
33
+ * @remarks
34
+ * The mode lives here rather than being re-derived from a pointer event by each consumer, because the
35
+ * modifiers change with the pointer standing still — see {@link DragMoveInfo}.
36
+ */
37
+ export interface MoveDrop {
38
+ /**
39
+ * Where the block lands. For an insert this is the block RE-ANCHORED on the pointer's cell, not the
40
+ * drag's translation — take the destination from here rather than re-deriving it.
41
+ */
42
+ to: RangeCoords;
43
+ /** Ctrl / Cmd is held: the source is copied rather than cut. */
44
+ copy: boolean;
45
+ /**
46
+ * Shift is held: INSERT at {@link MoveDrop.to} instead of dropping over it — the cells already there
47
+ * step aside along this axis (`Row` = down, `Column` = right), which is also the edge of `to` the
48
+ * I-beam is drawn on. Absent for a plain overwrite drop.
49
+ */
50
+ insert?: RangeAxis;
51
+ /**
52
+ * The drop cannot be performed and a release does nothing: a cut INSERT whose line falls inside the
53
+ * block being dragged, or one that would shift only part of it (see `CoordUtils.classifyInsertCut`
54
+ * - the same call the model makes, so a beam drawn as legal is one the model will accept), or
55
+ * whatever else {@link SelectionLayerProps.isDropRefused} rules out. The preview draws the I-beam
56
+ * (or the ghost) in the refused color instead, and `onMove` never fires.
57
+ */
58
+ refused?: boolean;
59
+ }
27
60
  /**
28
61
  * Props for {@link SelectionLayer}.
29
62
  */
@@ -41,7 +74,7 @@ export interface SelectionLayerProps extends LayerProps {
41
74
  getContentBounds?: () => RangeCoords;
42
75
  /**
43
76
  * The bounding REGION enclosing a cell — a table's data range from a tier that knows tables (the sheet
44
- * layer), else `null` (the bare sheet). Drives Tab/Enter wrapping inside a table and the row/column
77
+ * layer), else `null` (the bare sheet). Drives Tab wrapping inside a table and the row/column
45
78
  * span of Ctrl/Shift+Space. Omit it (the dumb {@link DefaultSelection} tier) and there is no enclosing
46
79
  * region: Tab is a plain move and Ctrl/Shift+Space select the whole grid. The layer never learns it's a
47
80
  * sheet — just a `cell → bounds | null` getter; any escalation lives in the provider.
@@ -70,6 +103,35 @@ export interface SelectionLayerProps extends LayerProps {
70
103
  singleAnchorStyle?: VisualSVGProps;
71
104
  /** The Ctrl/Cmd-drag remove rubber-band. Merged (slot-by-slot) over {@link DEFAULT_REMOVE_PREVIEW_STYLE}. */
72
105
  removePreviewStyle?: SelectionRangeStyle;
106
+ /** The Shift-drag insert I-beam. Merged over {@link DEFAULT_INSERT_BAR_STYLE}. */
107
+ insertBarStyle?: InsertBarStyle;
108
+ /**
109
+ * Whether a drop would be REFUSED - drawn as a refused I-beam (or ghost), and inert on release.
110
+ * Asked for every drop, insert or not; a copy INSERT is never refused, as it leaves no gap to
111
+ * resolve. Defaults to the geometry a grid can see on its own (`CoordUtils.classifyInsertCut`: a
112
+ * cut whose gap the insert would only half close, or a line dropped inside the block itself); a
113
+ * plain drop is then never refused.
114
+ *
115
+ * @remarks
116
+ * A model knows more than the grid does - a merge the shift would tear, a table it would split, a
117
+ * merged cell a drop would land across - so a host that has one should answer from it
118
+ * (`ICellRange.canInsertFrom`, `ICellRange.getDropTarget`), which is what keeps the preview honest
119
+ * about drops the grid could not have ruled out.
120
+ */
121
+ isDropRefused?: (from: RangeCoords, drop: MoveDrop) => boolean;
122
+ /**
123
+ * Adjust where a plain (non-insert) move drop lands before it drives the ghost, the tip and the
124
+ * callbacks - e.g. onto the whole of a merged cell a single cell is dropped onto, so the ghost shows
125
+ * what the drop will cover. Given the source and the pointer-derived drop, return the destination
126
+ * to use. Omit for no snapping.
127
+ */
128
+ snapMoveDestination?: (from: RangeCoords, drop: MoveDrop) => RangeCoords;
129
+ /**
130
+ * The mute drawn over the source while a **cut** (plain) move drag is in flight — see
131
+ * {@link DEFAULT_MOVE_SOURCE_STYLE}, which it merges over. `null` draws no mute, leaving the cursor
132
+ * as the only cut-vs-copy tell.
133
+ */
134
+ moveSourceStyle?: SelectionRangeStyle | null;
73
135
  /**
74
136
  * Stacked layers (fill, move, …) rendered when a single range is selected.
75
137
  */
@@ -86,21 +148,37 @@ export interface SelectionLayerProps extends LayerProps {
86
148
  enabledMove?: boolean;
87
149
  /**
88
150
  * Called when the single range is moved by its border — `from` is the original range, `to` the new
89
- * one. The selection follows automatically; use this for the data side-effect (cut-move).
151
+ * one (`drop.to`, snapped when inserting). The selection follows automatically; use this for the data
152
+ * side-effect, which `drop` describes: a cut-move, a copy, or an insert along `drop.insert`.
153
+ */
154
+ onMove?: (from: RangeCoords, to: RangeCoords, e: React.PointerEvent, drop: MoveDrop) => void;
155
+ /**
156
+ * The move drag started (before any translation) — `from` is the range being moved. `cancel` aborts
157
+ * the drag as Escape would ({@link SelectionLayerProps.onMoveEnd} follows, nothing moved) — for a
158
+ * mode's chrome cancel.
90
159
  */
91
- onMove?: (from: RangeCoords, to: RangeCoords, e: React.PointerEvent) => void;
92
- /** The move drag started (before any translation) — `from` is the range being moved. */
93
- onMoveStart?: (from: RangeCoords, e: React.PointerEvent) => void;
160
+ onMoveStart?: (from: RangeCoords, e: React.PointerEvent, drop: MoveDrop, cancel: () => void) => void;
94
161
  /**
95
- * Live during the move drag — `to` is the in-flight translated range. `e` carries the current
96
- * modifiers (Ctrl = copy, Shift = insert), so a consumer can reflect the mode (cursor/description).
162
+ * Live during the move drag — `to` is the in-flight destination and `drop` what a release would do.
163
+ * Fires on every change, INCLUDING a modifier pressed or released with the pointer standing still
164
+ * (that is the whole point: the mode must be reflected — cursor, status description — the instant
165
+ * the key goes down). Take the mode from `drop`; `e` is merely the last pointer event.
97
166
  */
98
- onMoveUpdate?: (from: RangeCoords, to: RangeCoords, e: React.PointerEvent) => void;
167
+ onMoveUpdate?: (from: RangeCoords, to: RangeCoords, e: React.PointerEvent, drop: MoveDrop) => void;
99
168
  /**
100
169
  * The move drag ended (fires on release regardless of whether the range actually moved) — for teardown
101
170
  * of anything {@link SelectionLayerProps.onMoveStart}/`onMoveUpdate` set (e.g. clearing a drag mode).
102
171
  */
103
172
  onMoveEnd?: () => void;
173
+ /**
174
+ * The cursor to paint while the move grip is dragged — pass one derived from the live
175
+ * {@link MoveDrop} (`copy` while Ctrl is held, say) to show the drop mode under the pointer. The
176
+ * grip is pointer-captured for the duration, so this is the cursor the browser shows anywhere on
177
+ * screen, and it is the only place a mode cursor can come from during the drag.
178
+ *
179
+ * @defaultValue 'grabbing'
180
+ */
181
+ moveCursor?: string;
104
182
  /**
105
183
  * Enable the built-in fill handle on a single range (the Excel fill square at the bottom-right
106
184
  * corner; drag to fill). Opt-in (unlike the always-on move grip) because it adds a visible handle.
@@ -121,8 +199,11 @@ export interface SelectionLayerProps extends LayerProps {
121
199
  * to use. Omit for no snapping.
122
200
  */
123
201
  snapFillDestination?: (template: RangeCoords, destination: RangeCoords) => RangeCoords;
124
- /** The fill drag started — `template` is the source range. */
125
- onFillStart?: (template: RangeCoords, e: React.PointerEvent) => void;
202
+ /**
203
+ * The fill drag started — `template` is the source range. `cancel` aborts the drag as Escape would
204
+ * ({@link SelectionLayerProps.onFillEnd} follows, nothing filled) — for a mode's chrome cancel.
205
+ */
206
+ onFillStart?: (template: RangeCoords, e: React.PointerEvent, cancel: () => void) => void;
126
207
  /**
127
208
  * Live during the fill drag — `destination` is the in-flight fill range (incl. the template). Use it
128
209
  * to preview what will be filled (e.g. a value tooltip). The fill axis is `template` vs `destination`.
@@ -133,6 +214,17 @@ export interface SelectionLayerProps extends LayerProps {
133
214
  * anything {@link SelectionLayerProps.onFillPreview} set (e.g. closing the preview tooltip).
134
215
  */
135
216
  onFillEnd?: () => void;
217
+ /**
218
+ * A **right-button** fill drag was released — Excel's right-drag of the fill handle, which offers a
219
+ * menu of fills at the pointer instead of filling. Fires INSTEAD of {@link SelectionLayerProps.onFill}
220
+ * for that gesture, and the selection is NOT extended: nothing has been decided yet, so the consumer
221
+ * extends it (or not) once a menu entry is chosen. Supplying it is what enables the right-button drag;
222
+ * left-drag is untouched.
223
+ *
224
+ * The dashed fill preview stays up while the menu decides; call `done` once it has answered or closed
225
+ * without answering, and the preview clears.
226
+ */
227
+ onFillMenu?: (template: RangeCoords, destination: RangeCoords, e: React.PointerEvent, done: () => void) => void;
136
228
  /**
137
229
  * Resolve the destination for a **double-clicked** fill handle — Excel's "ride the rails" fill,
138
230
  * whose extent comes from the data flanking the template rather than from a drag. Return the
@@ -154,13 +246,34 @@ export interface SelectionLayerProps extends LayerProps {
154
246
  * {@link SelectionPointerOptions.onSelectionEnd}.
155
247
  */
156
248
  onSelectionEnd?: (e: React.PointerEvent) => void;
249
+ /**
250
+ * The pointer drag-select started (`'extend'` / `'remove'`) or ended (`null`) — for reflecting an
251
+ * in-flight selection somewhere this layer's own {@link SelectionLayerProps.renderTip} can't reach
252
+ * (the name box's live `nR x nC`). Read off the drag engine rather than a start/end callback pair,
253
+ * so it is balanced by construction: the `null` also arrives for an aborted drag (Escape /
254
+ * `pointercancel`), which never reaches a release.
255
+ */
256
+ onDragModeChange?: (dragMode: 'extend' | 'remove' | null) => void;
257
+ /**
258
+ * The active range is being grown by a drag this layer's own tap cannot see — a header drag, which
259
+ * writes through the model. The gesture tip shows for it as for a body drag-select, off the corner
260
+ * being pulled.
261
+ * @defaultValue false
262
+ */
263
+ externalDrag?: boolean;
157
264
  /**
158
265
  * Render a transient tip during a gesture — a drag-select ({@link SelectionGesture} `select`, live
159
- * R×C), a move (`move`, the destination address), or a fill (`fill`, the preview). Return a falsy node
160
- * for no tip (e.g. skip a 1×1 drag-select). The tip floats on the in-flight range (non-interactive).
266
+ * R×C), a move (`move`, the destination address), or a fill (`fill`, the preview). Return an empty
267
+ * node for no tip (e.g. skip a 1×1 drag-select). The tip is a {@link GestureTip}: it floats on the
268
+ * in-flight range in the tooltip group's bubble, and no hover tip opens while the gesture runs.
161
269
  */
162
270
  renderTip?: (gesture: SelectionGesture) => React.ReactNode;
163
- /** Placement of the gesture tip relative to the in-flight range. @defaultValue `{ side: 'b', align: 'end' }` */
271
+ /**
272
+ * Fixed placement of the gesture tip relative to the in-flight range. By default the tip follows the
273
+ * gesture: off the corner a drag-select is pulling (the one opposite the anchor cell, or the touch grip
274
+ * held), trailing the edge a fill grows from, on the far side of an insert's I-beam, and at the
275
+ * bottom-right of a plain move.
276
+ */
164
277
  tipPlacement?: GridPlacement;
165
278
  /**
166
279
  * Disable the layer's **built-in keyboard** routing (arrows → {@link ISelection.navigate}, Tab/Enter
@@ -25,4 +25,37 @@ export declare const DEFAULT_MULTI_STYLE: SelectionRangeStyle;
25
25
  export declare const DEFAULT_ANCHOR_STYLE: VisualSVGProps;
26
26
  /** The transient rubber-band shown while Ctrl/Cmd-dragging to remove an area (fill + border stack). */
27
27
  export declare const DEFAULT_REMOVE_PREVIEW_STYLE: SelectionRangeStyle;
28
+ /** The look of the insert I-beam — the `RangeInsertBar` shown while Shift-dragging the move grip. */
29
+ export interface InsertBarStyle {
30
+ /** The bar color. */
31
+ color?: string;
32
+ /**
33
+ * The bar color when the drop would be REFUSED (`MoveDrop.refused`) - the theme's warning. A refused
34
+ * plain drop draws its ghost in it too.
35
+ */
36
+ refusedColor?: string;
37
+ /** Bar thickness in DEVICE px (the same currency as a border's `alignWidth`). */
38
+ alignWidth?: number;
39
+ /** How far each end cap reaches past the bar, per side, in DEVICE px. */
40
+ capExtent?: number;
41
+ /** How long the bar takes to travel to a new line, in ms. `0` places it instantly. */
42
+ transitionDuration?: number;
43
+ /** The curve it travels on - a hard decelerate reads as a snap onto the line. */
44
+ transitionEasing?: string;
45
+ /** How long the beam takes to grow into a new ORIENTATION, in ms. `0` shows it at once. */
46
+ axisDuration?: number;
47
+ }
48
+ /**
49
+ * The insert I-beam: the selection color, heavier than the selection outline so it reads as a drop
50
+ * target rather than a border. A drop the model would refuse draws in the theme's WARNING instead
51
+ * (the same token the remove rubber-band uses) - the gesture stays visible, it just says no.
52
+ */
53
+ export declare const DEFAULT_INSERT_BAR_STYLE: InsertBarStyle;
54
+ /**
55
+ * The **mute** laid over the SOURCE of a move (cut) drag — a scrim in the grid's own surface color,
56
+ * drawn under the selection visuals so it fades the cell content while the outline stays crisp. The
57
+ * content reads as already lifted out; a Ctrl (copy) drag draws no mute, so the two modes are
58
+ * distinguishable at a glance rather than by cursor alone.
59
+ */
60
+ export declare const DEFAULT_MOVE_SOURCE_STYLE: SelectionRangeStyle;
28
61
  //# sourceMappingURL=SelectionStyle.d.ts.map
@@ -6,10 +6,6 @@ import { ISelection } from './ISelection';
6
6
  export interface SelectionTouchGripsProps {
7
7
  /** The selection the touch handles read (for placement) and extend (via `modify`). */
8
8
  selection: ISelection;
9
- /** The ring color (the fill is the surface/"window" color). @defaultValue the theme primary. */
10
- color?: string;
11
- /** Handle diameter (px). Touch-sized. @defaultValue 22 */
12
- size?: number;
13
9
  }
14
10
  /**
15
11
  * Touch selection handles: round grips at the active range's corners (or edges, for a whole row/column)