@sheetxl/grid-react 0.8.0-beta.1 → 0.8.0-beta.3
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/browser/cjs/index.cjs +3 -3
- package/browser/esm/index.mjs +1741 -1278
- package/node/esm/index.mjs +1742 -1272
- package/package.json +3 -3
- package/types/src/cell/CellRenderer.d.ts +14 -3
- package/types/src/editor/DefaultEditable.d.ts +9 -0
- package/types/src/editor/EditSession.d.ts +6 -0
- package/types/src/editor/EditState.d.ts +6 -0
- package/types/src/editor/EditorInput.d.ts +2 -1
- package/types/src/editor/useEditorKeymap.d.ts +11 -0
- package/types/src/header/useHeaderSelectionPointer.d.ts +6 -2
- package/types/src/layer/GridFloatPanel.d.ts +18 -0
- package/types/src/layer/RangeFloatLayer.d.ts +5 -0
- package/types/src/layer/index.d.ts +1 -0
- package/types/src/list/CheckboxList.d.ts +11 -0
- package/types/src/list/List.d.ts +11 -2
- package/types/src/list/ListSearch.d.ts +31 -0
- package/types/src/list/ListSurface.d.ts +25 -5
- package/types/src/list/PickList.d.ts +71 -0
- package/types/src/list/_measureText.d.ts +14 -0
- package/types/src/list/index.d.ts +2 -0
- package/types/src/range/DragHandle.d.ts +14 -0
- package/types/src/range/Draggable.d.ts +111 -6
- package/types/src/range/FillHandle.d.ts +28 -1
- package/types/src/range/RangeInsertBar.d.ts +91 -0
- package/types/src/range/index.d.ts +2 -0
- package/types/src/selection/SelectionLayer.d.ts +91 -9
- package/types/src/selection/SelectionStyle.d.ts +30 -0
- package/types/src/selection/useDragCells.d.ts +36 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sheetxl/grid-react",
|
|
3
3
|
"description": "Grid React - Advanced grid component for React",
|
|
4
|
-
"version": "0.8.0-beta.
|
|
4
|
+
"version": "0.8.0-beta.3",
|
|
5
5
|
"license": "SEE LICENSE IN <LICENSE>",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@floating-ui/react-dom": "^2.1.9",
|
|
45
|
-
"@sheetxl/utils": "^0.8.0-beta.
|
|
46
|
-
"@sheetxl/utils-react": "^0.8.0-beta.
|
|
45
|
+
"@sheetxl/utils": "^0.8.0-beta.3",
|
|
46
|
+
"@sheetxl/utils-react": "^0.8.0-beta.3",
|
|
47
47
|
"clsx": "^2.1.1",
|
|
48
48
|
"react-merge-refs": "^3.0.2",
|
|
49
49
|
"react-use": "^17.6.1",
|
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import { Bounds, CellCoords, RangeCoords } from '@sheetxl/utils';
|
|
2
|
-
import { IGridPane } from '../surface';
|
|
3
2
|
export interface CellRendererProps<T = any> extends React.HTMLAttributes<HTMLElement>, React.Attributes {
|
|
4
3
|
/**
|
|
5
4
|
* A value to render.
|
|
6
5
|
*/
|
|
7
6
|
value: T;
|
|
8
7
|
/**
|
|
9
|
-
* The
|
|
8
|
+
* The zoom {@link CellRendererProps.bounds} were measured at.
|
|
9
|
+
*
|
|
10
|
+
* @defaultValue 1
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* A renderer is handed the zoom, never the pane it came from: everything it needs to draw one cell
|
|
14
|
+
* is data (the value, the bounds, the scale), so it stays a pure function of its props and can be
|
|
15
|
+
* rendered anywhere a caller can supply bounds — a preview, a tooltip, a cell drawn outside its own
|
|
16
|
+
* column. A renderer that genuinely needs the live view (hover, scroll) reads it from context with
|
|
17
|
+
* `useGridPane`.
|
|
18
|
+
*
|
|
19
|
+
* The bounds already have the zoom applied; this is for what CSS pixels do not carry — an unscaled
|
|
20
|
+
* inset, a glyph size, a stroke width the renderer picks itself.
|
|
10
21
|
*/
|
|
11
|
-
|
|
22
|
+
zoom?: number;
|
|
12
23
|
/**
|
|
13
24
|
* The range bounds relative to the current viewport and zoom.
|
|
14
25
|
*/
|
|
@@ -40,6 +40,12 @@ export interface IEditable<C, D = string, A = EditCommitArgs> {
|
|
|
40
40
|
getSession(): EditSession<C, D, A> | null;
|
|
41
41
|
/** Subscribe to session changes (start/stop only). Returns an unsubscribe. */
|
|
42
42
|
subscribe(listener: () => void): () => void;
|
|
43
|
+
/**
|
|
44
|
+
* Subscribe to commits the model REFUSED by throwing (an expected refusal such as "You can't change
|
|
45
|
+
* part of an array", or a real fault). The edit stays open either way; this is the seam through
|
|
46
|
+
* which the reason reaches a host that can show it. Returns an unsubscribe.
|
|
47
|
+
*/
|
|
48
|
+
subscribeCommitError(listener: (error: unknown) => void): () => void;
|
|
43
49
|
/** Begin an edit on `coords`. Returns `false` (and does nothing) if the cell is not editable. */
|
|
44
50
|
startEdit(coords: CellCoords, options?: StartEditOptions<D>): boolean;
|
|
45
51
|
/** Commit the current edit. On success clears the session; returns whether it succeeded. */
|
|
@@ -100,12 +106,15 @@ export declare class DefaultEditable<C, D = string, A = EditCommitArgs> implemen
|
|
|
100
106
|
private readonly unsubscribeAnchor;
|
|
101
107
|
private current;
|
|
102
108
|
private readonly listeners;
|
|
109
|
+
private readonly commitErrorListeners;
|
|
103
110
|
constructor(anchor: ICellAnchor, createEdit: CreateEdit<C, D, A>, options?: DefaultEditableOptions);
|
|
104
111
|
private onAnchorChange;
|
|
105
112
|
/** @inheritdoc IEditable.getAnchor */
|
|
106
113
|
getAnchor(): ICellAnchor;
|
|
107
114
|
/** @inheritdoc IEditable.subscribe */
|
|
108
115
|
subscribe(listener: () => void): () => void;
|
|
116
|
+
/** @inheritdoc IEditable.subscribeCommitError */
|
|
117
|
+
subscribeCommitError(listener: (error: unknown) => void): () => void;
|
|
109
118
|
/** @inheritdoc IEditable.getSession */
|
|
110
119
|
getSession(): EditSession<C, D, A> | null;
|
|
111
120
|
/** @inheritdoc IEditable.startEdit */
|
|
@@ -49,6 +49,11 @@ export interface EditModel<C, D = string, A = EditCommitArgs> {
|
|
|
49
49
|
export interface EditSessionInit<C, D = string, A = EditCommitArgs> extends EditModel<C, D, A> {
|
|
50
50
|
/** The cell being edited. */
|
|
51
51
|
coords: CellCoords;
|
|
52
|
+
/**
|
|
53
|
+
* Hears the error a THROWING model write rejected the commit with (see {@link EditModel.commit}).
|
|
54
|
+
* The rejection itself only keeps the edit open; this is how the reason reaches whoever can show it.
|
|
55
|
+
*/
|
|
56
|
+
onCommitError?: (error: unknown) => void;
|
|
52
57
|
}
|
|
53
58
|
/**
|
|
54
59
|
* Options applied when an edit begins (`DefaultEditable.startEdit` → the `EditSession` constructor).
|
|
@@ -96,6 +101,7 @@ export interface CommitContext<D = string, A = EditCommitArgs> {
|
|
|
96
101
|
*/
|
|
97
102
|
export declare class EditSession<C, D = string, A = EditCommitArgs> implements ICellAnchor {
|
|
98
103
|
private readonly commitFn;
|
|
104
|
+
private readonly onCommitError?;
|
|
99
105
|
private state;
|
|
100
106
|
private readonly listeners;
|
|
101
107
|
private readonly commitListeners;
|
|
@@ -20,6 +20,12 @@ export interface EditCommitArgs {
|
|
|
20
20
|
* Excel's `Ctrl+Enter`. The active cell does not move (the leave-key does not navigate/traverse).
|
|
21
21
|
*/
|
|
22
22
|
broadcast?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Array-enter the value over the **current selection** — Excel's `Ctrl+Shift+Enter`: one formula
|
|
25
|
+
* at the selection's top-left whose result is fitted to the selection's extent, rather than
|
|
26
|
+
* spilling from the active cell. The active cell does not move.
|
|
27
|
+
*/
|
|
28
|
+
arrayFixed?: boolean;
|
|
23
29
|
}
|
|
24
30
|
/**
|
|
25
31
|
* A text selection range within the editor input. Mirrors the `HTMLTextAreaElement` selection API.
|
|
@@ -67,7 +67,8 @@ export interface EditorInputProps<C, D = string> {
|
|
|
67
67
|
* (a rejected/invalid value) the input keeps the session open and stops the key so the selection
|
|
68
68
|
* does not move away.
|
|
69
69
|
*
|
|
70
|
-
* `args` carries the commit "signal" — e.g. {@link EditCommitArgs.broadcast} for a Ctrl+Enter fill
|
|
70
|
+
* `args` carries the commit "signal" — e.g. {@link EditCommitArgs.broadcast} for a Ctrl+Enter fill,
|
|
71
|
+
* {@link EditCommitArgs.arrayFixed} for a Ctrl+Shift+Enter array entry.
|
|
71
72
|
*/
|
|
72
73
|
onCommit(args?: EditCommitArgs): boolean;
|
|
73
74
|
/** Cancel/revert the edit. */
|
|
@@ -54,6 +54,17 @@ export interface EditorKeymapProps {
|
|
|
54
54
|
*/
|
|
55
55
|
setCaret?(caret: number): void;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* The commit "signal" a leave key carries: `Ctrl+Shift+Enter` array-enters over the selection
|
|
59
|
+
* ({@link EditCommitArgs.arrayFixed}), `Ctrl+Enter` fills it ({@link EditCommitArgs.broadcast}), and
|
|
60
|
+
* anything else is a plain commit (`undefined`). Both modified forms keep the active cell put.
|
|
61
|
+
*/
|
|
62
|
+
export declare const commitArgsFromKey: (e: {
|
|
63
|
+
which: number;
|
|
64
|
+
}, native: {
|
|
65
|
+
ctrlKey: boolean;
|
|
66
|
+
shiftKey: boolean;
|
|
67
|
+
}) => EditCommitArgs | undefined;
|
|
57
68
|
/**
|
|
58
69
|
* The shared spreadsheet editor keymap, as a `<textarea>` `onKeyDown` handler. Both the default and
|
|
59
70
|
* the cell-aware inputs use it so the interaction contract stays identical:
|
|
@@ -55,8 +55,12 @@ export interface HeaderSelectionPointerOptions {
|
|
|
55
55
|
onPointerDownInterceptor?: (e: React.PointerEvent, cell: CellCoords) => boolean | void;
|
|
56
56
|
/** Called once at the start of a pointer gesture (before the selection changes). */
|
|
57
57
|
onDragStart?: () => void;
|
|
58
|
-
/**
|
|
59
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Called at the end of a pointer gesture — on release AND on an abort (Escape / OS `pointercancel`),
|
|
60
|
+
* so whatever {@link HeaderSelectionPointerOptions.onDragStart} armed is always disarmed. The event is
|
|
61
|
+
* absent on an abort.
|
|
62
|
+
*/
|
|
63
|
+
onDragEnd?: (e?: React.PointerEvent) => void;
|
|
60
64
|
/** Called on pointer-up, after {@link HeaderSelectionPointerOptions.onDragEnd} (e.g. refocus the grid). */
|
|
61
65
|
onPointerUp?: (e: React.PointerEvent) => void;
|
|
62
66
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { FloatPanelProps } from '@sheetxl/utils-react';
|
|
3
|
+
/**
|
|
4
|
+
* A **float stack panel positioned by the grid**: pass {@link renderGridFloatPanel} as a
|
|
5
|
+
* `useFloatStack` `renderFloatPanel`, and a {@link GridAnchor} (a cell or range) as its `anchor`.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* The stack keeps what it does for every popup — the portal, light-dismiss, Escape, focus restore and
|
|
9
|
+
* stacking. The position comes from {@link StickyFloat}, the engine grid buttons and labels use, so the
|
|
10
|
+
* popup moves with its cell as the grid scrolls (which no DOM event reports), flips against the same
|
|
11
|
+
* boundary, and closes once its cell has scrolled out of view.
|
|
12
|
+
*
|
|
13
|
+
* The stack's `propsPopper` `placement` and `offsets` are read as the grid placement and offset.
|
|
14
|
+
*/
|
|
15
|
+
export declare const GridFloatPanel: React.FC<FloatPanelProps>;
|
|
16
|
+
/** {@link GridFloatPanel} as a `useFloatStack` `renderFloatPanel`. */
|
|
17
|
+
export declare const renderGridFloatPanel: (props: FloatPanelProps) => React.ReactElement;
|
|
18
|
+
//# sourceMappingURL=GridFloatPanel.d.ts.map
|
|
@@ -29,6 +29,11 @@ export interface RangeFloatTarget<T = unknown> {
|
|
|
29
29
|
key?: string;
|
|
30
30
|
/** Payload passed to the render-prop (the message, the validation, the table, …). */
|
|
31
31
|
data: T;
|
|
32
|
+
/**
|
|
33
|
+
* Where this target's float attaches, for a layer whose targets do not all belong on one side.
|
|
34
|
+
* Defaults to the layer's {@link RangeFloatLayerProps.placement}.
|
|
35
|
+
*/
|
|
36
|
+
placement?: GridPlacement;
|
|
32
37
|
}
|
|
33
38
|
/** The context passed to a {@link RangeFloatLayerProps} render-prop. */
|
|
34
39
|
export interface RangeFloatRender<T = unknown> {
|
|
@@ -4,6 +4,7 @@ export * from './VirtualDiv';
|
|
|
4
4
|
export type { GridAnchor, GridAnchorReference, GridPlacement, GridWhenOff, GridBoundary, GridFloatSide, GridAlign, GridSlide } from './useGridAnchor';
|
|
5
5
|
export * from './StickyAnchor';
|
|
6
6
|
export * from './StickyFloat';
|
|
7
|
+
export * from './GridFloatPanel';
|
|
7
8
|
export * from './useHoverIntent';
|
|
8
9
|
export * from './useRangeHover';
|
|
9
10
|
export * from './RangeFloatLayer';
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
2
|
import { ISelection } from '../selection';
|
|
3
|
+
import { ListProps } from './List';
|
|
3
4
|
export interface CheckboxListAttributes {
|
|
4
5
|
isCheckboxList: () => true;
|
|
6
|
+
/** The highlighted row, or `-1` when the list is empty. */
|
|
7
|
+
getHighlightIndex: () => number;
|
|
8
|
+
/** Move the highlight `delta` rows (clamped to the list) and bring it into view. */
|
|
9
|
+
moveHighlight: (delta: number) => void;
|
|
10
|
+
/** Toggle the selected rows, as Space does: the anchor's opposite, applied to every one of them. */
|
|
11
|
+
toggleHighlighted: () => void;
|
|
5
12
|
}
|
|
6
13
|
export interface ICheckboxListElement extends HTMLDivElement, CheckboxListAttributes {
|
|
7
14
|
}
|
|
@@ -41,6 +48,10 @@ export interface CheckboxListProps extends React.HTMLAttributes<HTMLDivElement>
|
|
|
41
48
|
selection?: ISelection;
|
|
42
49
|
/** Content cache-bust; bump when checked state changes without `isChecked`'s identity changing. */
|
|
43
50
|
cacheToken?: unknown;
|
|
51
|
+
/** See `ListSurfaceProps.autoFocus`. */
|
|
52
|
+
autoFocus?: boolean;
|
|
53
|
+
/** See `ListSurfaceProps.viewport`. */
|
|
54
|
+
viewport?: ListProps['viewport'];
|
|
44
55
|
/** Ref for the Element. */
|
|
45
56
|
ref?: React.Ref<ICheckboxListElement>;
|
|
46
57
|
}
|
package/types/src/list/List.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
2
|
import { ISelection } from '../selection';
|
|
3
|
+
import { ListSurface } from './ListSurface';
|
|
3
4
|
export interface ListAttributes {
|
|
4
5
|
isList: () => true;
|
|
5
6
|
}
|
|
@@ -36,6 +37,14 @@ export interface ListProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
36
37
|
* *rendered* content changed without `renderItem`'s identity changing.
|
|
37
38
|
*/
|
|
38
39
|
cacheToken?: unknown;
|
|
40
|
+
/** See `ListSurfaceProps.scrollTo`. */
|
|
41
|
+
scrollTo?: {
|
|
42
|
+
row: number;
|
|
43
|
+
} | null;
|
|
44
|
+
/** See `ListSurfaceProps.autoFocus`. */
|
|
45
|
+
autoFocus?: boolean;
|
|
46
|
+
/** See `ListSurfaceProps.viewport`. */
|
|
47
|
+
viewport?: React.ComponentProps<typeof ListSurface>['viewport'];
|
|
39
48
|
/** Ref for the Element. */
|
|
40
49
|
ref?: React.Ref<IListElement>;
|
|
41
50
|
}
|
|
@@ -48,8 +57,8 @@ export interface ListProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
48
57
|
* The List's whole state is its {@link ListProps.selection}; it has no per-item callbacks and no notion
|
|
49
58
|
* of "checked" or "toggle". Pointer selection is the `SelectionLayer`'s job (it hit-tests the pointer),
|
|
50
59
|
* so the cells are inert, transparent content; the keyboard is the caller's via {@link ListProps.onKeyDown}.
|
|
51
|
-
* See {@link CheckboxList} for the checkbox-list specialization
|
|
52
|
-
*
|
|
60
|
+
* See {@link CheckboxList} for the checkbox-list specialization and {@link PickList} for the
|
|
61
|
+
* single-choice text list.
|
|
53
62
|
*/
|
|
54
63
|
export declare const List: React.ForwardRefExoticComponent<Omit<ListProps, "ref"> & React.RefAttributes<IListElement>>;
|
|
55
64
|
//# sourceMappingURL=List.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Props for {@link ListSearch}.
|
|
4
|
+
*/
|
|
5
|
+
export interface ListSearchProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange'> {
|
|
6
|
+
/** The search text. */
|
|
7
|
+
value: string;
|
|
8
|
+
/** The search text changed — including to `''` when Escape clears it. */
|
|
9
|
+
onChange: (value: string) => void;
|
|
10
|
+
/** Escape on an empty field: the search has nothing left to clear, so the host closes. */
|
|
11
|
+
onClose?: () => void;
|
|
12
|
+
/** Ref for the input. */
|
|
13
|
+
ref?: React.Ref<HTMLInputElement>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The rows of `items` whose `text` contains `query`, ignoring case. An empty query keeps every row.
|
|
17
|
+
*/
|
|
18
|
+
export declare const filterByText: <T extends {
|
|
19
|
+
text: string;
|
|
20
|
+
}>(items: readonly T[], query: string) => readonly T[];
|
|
21
|
+
/**
|
|
22
|
+
* The **search field** shown above a list ({@link PickList}, `CheckboxList`): a plain input that
|
|
23
|
+
* narrows the rows as typed. Filtering itself is the host's, with {@link filterByText}.
|
|
24
|
+
*
|
|
25
|
+
* @remarks
|
|
26
|
+
* Escape clears the text first and closes only when there is none left, so a mistyped search is
|
|
27
|
+
* one key from a full list and two from a closed one. Every other key is the host's through
|
|
28
|
+
* `onKeyDown`, which is how the arrows and Enter reach the list while the field keeps focus.
|
|
29
|
+
*/
|
|
30
|
+
export declare const ListSearch: React.ForwardRefExoticComponent<Omit<ListSearchProps, "ref"> & React.RefAttributes<HTMLInputElement>>;
|
|
31
|
+
//# sourceMappingURL=ListSearch.d.ts.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
|
+
import { Viewport } from '@sheetxl/utils-react';
|
|
2
3
|
import { CellLayerProps, CellRendererProps } from '../cell';
|
|
3
4
|
import { LayerEventProps } from '../layer';
|
|
4
5
|
import { ISelection } from '../selection';
|
|
@@ -48,6 +49,26 @@ export interface ListSurfaceProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
48
49
|
propsLayer?: LayerEventProps;
|
|
49
50
|
/** Take focus on mount, so the Arrow keys work without a click first. @defaultValue false */
|
|
50
51
|
autoFocus?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Whether the vertical scrollbar (and its gutter) is drawn. `'auto'` draws it only while the rows
|
|
54
|
+
* overflow the box — a list that fits has nothing to scroll and the gutter would only widen it,
|
|
55
|
+
* and one squeezed by its host (a popup capped to the window) scrolls as a plain list does.
|
|
56
|
+
* @defaultValue 'auto'
|
|
57
|
+
*/
|
|
58
|
+
showScrollbar?: boolean | 'auto';
|
|
59
|
+
/**
|
|
60
|
+
* Bring `row` into view. Read by identity: a new object scrolls again, the same object does not, so
|
|
61
|
+
* a caller keeps one per intent (the row highlighted on open, the row a key moved to). The scroll
|
|
62
|
+
* waits for the surface to have a size, so it can be set before the first paint.
|
|
63
|
+
*/
|
|
64
|
+
scrollTo?: {
|
|
65
|
+
row: number;
|
|
66
|
+
} | null;
|
|
67
|
+
/**
|
|
68
|
+
* The scroll state, when the caller owns it — a host that sizes the list itself (`setSizes`) or
|
|
69
|
+
* drives its scroll. A private one is created when omitted.
|
|
70
|
+
*/
|
|
71
|
+
viewport?: Viewport;
|
|
51
72
|
/** Opaque content cache-bust forwarded to the `CellLayer`. */
|
|
52
73
|
cacheToken?: unknown;
|
|
53
74
|
/** Ref for the Element. */
|
|
@@ -60,11 +81,10 @@ export interface ListSurfaceProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
60
81
|
*
|
|
61
82
|
* @remarks
|
|
62
83
|
* Not a list *component*: it renders no row content of its own, has no notion of an item, and adds no
|
|
63
|
-
* padding — {@link ListSurfaceProps.renderCell} owns the whole row box. It exists because
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* layer order — is here, once.
|
|
84
|
+
* padding — {@link ListSurfaceProps.renderCell} owns the whole row box. It exists because the lists
|
|
85
|
+
* built on it ({@link List}, whose rows are arbitrary React, and {@link PickList}, whose rows are text)
|
|
86
|
+
* differ ONLY in that renderer. Everything they share — the viewport, the measured single column, the
|
|
87
|
+
* selection styling and its rationale, the layer order — is here, once.
|
|
68
88
|
*
|
|
69
89
|
* The single column tracks the measured content width, so a row spans the full list; scrolling is
|
|
70
90
|
* vertical only.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { Viewport } from '@sheetxl/utils-react';
|
|
3
|
+
/** One row of a {@link PickList}: what it shows. */
|
|
4
|
+
export interface PickListItem {
|
|
5
|
+
/** The row's text. */
|
|
6
|
+
text: string;
|
|
7
|
+
}
|
|
8
|
+
export interface PickListAttributes {
|
|
9
|
+
isPickList: () => true;
|
|
10
|
+
/** The highlighted row, or `-1` when the list is empty. */
|
|
11
|
+
getHighlightIndex: () => number;
|
|
12
|
+
/** Move the highlight `delta` rows (clamped to the list) and bring it into view. */
|
|
13
|
+
moveHighlight: (delta: number) => void;
|
|
14
|
+
/** Choose the highlighted row, as Enter does. `false` when there is none. */
|
|
15
|
+
chooseHighlighted: () => boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface IPickListElement extends HTMLDivElement, PickListAttributes {
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Props for {@link PickList}.
|
|
21
|
+
*/
|
|
22
|
+
export interface PickListProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
23
|
+
/** The rows, in the order shown. */
|
|
24
|
+
items: readonly PickListItem[];
|
|
25
|
+
/** A row was chosen — clicked, or Enter/Space on the highlighted row. */
|
|
26
|
+
onChoose?: (index: number, item: PickListItem) => void;
|
|
27
|
+
/**
|
|
28
|
+
* The row highlighted when the list (re)appears — the current value's, so a pick from the keyboard
|
|
29
|
+
* starts from it. Out of range, or omitted, highlights the first row. Re-read when `items` changes.
|
|
30
|
+
*/
|
|
31
|
+
highlightIndex?: number;
|
|
32
|
+
/** Uniform row height in px. @defaultValue 24 */
|
|
33
|
+
itemHeight?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Rows shown before the list scrolls. The list is as tall as its rows up to this many; a caller's
|
|
36
|
+
* `style.maxHeight` caps it further (a popup fit to the space left). @defaultValue 8
|
|
37
|
+
*/
|
|
38
|
+
visibleRows?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Width (px) the rows need, when the caller already knows it. Otherwise it is measured from the
|
|
41
|
+
* widest `text` in the list's own font (see {@link PickListProps.measureText}).
|
|
42
|
+
*/
|
|
43
|
+
contentWidth?: number;
|
|
44
|
+
/**
|
|
45
|
+
* How to measure a row's text, in px. @defaultValue a canvas measure in the font `style` names
|
|
46
|
+
* (or the browser default when it names none).
|
|
47
|
+
*/
|
|
48
|
+
measureText?: (text: string) => number;
|
|
49
|
+
/** Render a row's content. @defaultValue the text, clipped with an ellipsis. */
|
|
50
|
+
renderItem?: (item: PickListItem, index: number) => React.ReactNode;
|
|
51
|
+
/** Take focus on mount, so the Arrow keys work without a click first. @defaultValue false */
|
|
52
|
+
autoFocus?: boolean;
|
|
53
|
+
/** The scroll state, when the caller owns it (see `ListSurfaceProps.viewport`). */
|
|
54
|
+
viewport?: Viewport;
|
|
55
|
+
/** Ref for the Element. */
|
|
56
|
+
ref?: React.Ref<IPickListElement>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A single-choice **pick list** of text rows over {@link ListSurface}: a dropdown's menu.
|
|
60
|
+
*
|
|
61
|
+
* @remarks
|
|
62
|
+
* The list's whole state is which row is highlighted; choosing is a callback. One row is always
|
|
63
|
+
* highlighted (the current value's on open, then wherever the keys or the pointer put it), Arrow keys
|
|
64
|
+
* move it, Enter or Space choose it, a click chooses the clicked row. The width follows the widest
|
|
65
|
+
* row's text; the height follows the rows up to {@link PickListProps.visibleRows}, then scrolls.
|
|
66
|
+
*
|
|
67
|
+
* A host with its own input (a search field above the list) drives the highlight through the element's
|
|
68
|
+
* {@link PickListAttributes} rather than moving focus into the list.
|
|
69
|
+
*/
|
|
70
|
+
export declare const PickList: React.ForwardRefExoticComponent<Omit<PickListProps, "ref"> & React.RefAttributes<IPickListElement>>;
|
|
71
|
+
//# sourceMappingURL=PickList.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CSS `font` shorthand a box's style resolves to, for measuring text the way that box draws it.
|
|
3
|
+
* Falls back to a plain sans-serif at the browser's default size when the style names no font.
|
|
4
|
+
*/
|
|
5
|
+
export declare const fontFromStyle: (style?: React.CSSProperties | null) => string;
|
|
6
|
+
/**
|
|
7
|
+
* The width (px) of `text` drawn in `font` (a CSS `font` shorthand).
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* Measured on a shared offscreen canvas. Where no 2-D context exists (a non-DOM test host) the width
|
|
11
|
+
* is estimated from the font size, so a caller sizing a box never sees `0` or `NaN`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const measureTextWidth: (text: string, font: string) => number;
|
|
14
|
+
//# sourceMappingURL=_measureText.d.ts.map
|
|
@@ -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;
|
|
@@ -108,6 +110,10 @@ export interface DragHandleProps {
|
|
|
108
110
|
renderHandle?: (props: DragHandleRenderProps) => React.ReactNode;
|
|
109
111
|
/** Static content inside the default dot. Ignored when `renderHandle` is set. */
|
|
110
112
|
children?: React.ReactNode;
|
|
113
|
+
/**
|
|
114
|
+
* Extra class(es) for the handle element. Joined with the `drag-handle` tag every handle carries,
|
|
115
|
+
* so a flavor names itself (`fill-handle`, `resize-handle`) without losing the generic hook.
|
|
116
|
+
*/
|
|
111
117
|
className?: string;
|
|
112
118
|
style?: React.CSSProperties;
|
|
113
119
|
/**
|
|
@@ -117,6 +123,14 @@ export interface DragHandleProps {
|
|
|
117
123
|
* it preserves whatever had focus rather than stealing it. @defaultValue false
|
|
118
124
|
*/
|
|
119
125
|
preserveFocus?: boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Also start a drag from the **secondary** (right) button — Excel's right-drag of the fill handle,
|
|
128
|
+
* which offers a menu on release instead of filling. The drag callbacks receive the event, so a
|
|
129
|
+
* consumer tells the two apart by `e.button`. The `contextmenu` the button raises (on press on some
|
|
130
|
+
* platforms, on release on others) is swallowed for the gesture's duration: the drag IS the intent.
|
|
131
|
+
* @defaultValue false
|
|
132
|
+
*/
|
|
133
|
+
secondaryButton?: boolean;
|
|
120
134
|
}
|
|
121
135
|
/**
|
|
122
136
|
* A generic, draggable handle anchored to a corner/edge of a grid range. It owns only the **gesture
|
|
@@ -1,4 +1,86 @@
|
|
|
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
|
+
/**
|
|
44
|
+
* The insert a drop would perform from the pointer's position, as Excel resolves it — the first half
|
|
45
|
+
* of the drop geometry ({@link insertRange} is the second: this picks the LINE, that places the block
|
|
46
|
+
* on it). Two rules that are deliberately NOT symmetric:
|
|
47
|
+
*
|
|
48
|
+
* - the pointer's **horizontal** position within the cell picks the axis: within
|
|
49
|
+
* {@link INSERT_COLUMN_BAND} of a vertical edge it is a column insert, anywhere in the middle half a
|
|
50
|
+
* row insert. So rows are the easy, default direction and columns are aimed for — you go for a
|
|
51
|
+
* column edge to get one. Skipped entirely when `forced` names the axis (a whole row / column);
|
|
52
|
+
* - the line then snaps to the **nearest** grid line on that axis, which may be the far side of the
|
|
53
|
+
* cell (below it / to the right of it), not only its leading edge. A forced axis is therefore a
|
|
54
|
+
* straight 50/50 across the cell, the axis pick no longer taking a cut of it.
|
|
55
|
+
*
|
|
56
|
+
* The cross axis re-anchors to the pointer's own cell, so the beam always starts where you point.
|
|
57
|
+
*
|
|
58
|
+
* @remarks
|
|
59
|
+
* Both measured proportionally (a fraction of the cell), which is how Excel behaves across cell sizes
|
|
60
|
+
* — and necessary anyway: a row is typically a third the height of a column's width, so any pixel
|
|
61
|
+
* threshold would behave differently at every zoom and row height.
|
|
62
|
+
*
|
|
63
|
+
* A MERGE is IGNORED: the line falls on the true cell under the pointer, exactly as if the merge
|
|
64
|
+
* weren't there. It has to be resolved here rather than taken from the drag's cell, which is a
|
|
65
|
+
* merge's top-left (what `getCellFromPointer` reports, and the right answer for a move destination) —
|
|
66
|
+
* measured against that anchor the fractions describe a box the pointer is nowhere near, and the far
|
|
67
|
+
* line lands one cell in from it, which is how the beam ends up drawn against the top-left corner of
|
|
68
|
+
* a merge instead of under the cursor. Snapping to the merge's outer edge instead is no better: the
|
|
69
|
+
* beam then jumps to a far edge the pointer isn't near. A line that falls INSIDE a merge is a drop
|
|
70
|
+
* the model refuses, which the preview already says in its own way — see `MoveDrop.refused`.
|
|
71
|
+
*/
|
|
72
|
+
export declare const insertAt: (pane: IGridPane, cell: CellCoords, clientX: number, clientY: number, forced: RangeAxis | null, prior: DragInsert | null) => DragInsert | null;
|
|
73
|
+
/**
|
|
74
|
+
* Where an **insert** drop of `range` lands: the block placed at {@link DragInsert.origin} — its
|
|
75
|
+
* top-left RE-ANCHORED on the pointer's own cell across the insert axis, and on the snapped grid line
|
|
76
|
+
* along it (existing cells shifting from there). Clamped into `bounds`, which can pull a block back so
|
|
77
|
+
* it still fits at the far edge.
|
|
78
|
+
*
|
|
79
|
+
* Unlike a move — which keeps the offset of wherever you grabbed the block — an insert drops the grab
|
|
80
|
+
* offset entirely: it goes where you are pointing. The same function drives the I-beam, the tip and
|
|
81
|
+
* the commit, so what is drawn is what lands.
|
|
82
|
+
*/
|
|
83
|
+
export declare const insertRange: (range: RangeCoords, insert: DragInsert, bounds: RangeCoords) => RangeCoords;
|
|
2
84
|
/**
|
|
3
85
|
* Props for {@link Draggable}.
|
|
4
86
|
*/
|
|
@@ -43,21 +125,39 @@ export interface DraggableProps {
|
|
|
43
125
|
*/
|
|
44
126
|
onDragStateChange?: (dragging: boolean) => void;
|
|
45
127
|
/** Drag started; `range` is the original. */
|
|
46
|
-
onDragStart?: (range: RangeCoords, e: React.PointerEvent) => void;
|
|
128
|
+
onDragStart?: (range: RangeCoords, e: React.PointerEvent, info: DragMoveInfo) => void;
|
|
47
129
|
/**
|
|
48
130
|
* The translated range during the drag. Wire it to update `range` (the content follows the pointer —
|
|
49
131
|
* "live"), or to position a composed ghost preview. `Draggable` itself stays silent.
|
|
132
|
+
*
|
|
133
|
+
* @remarks
|
|
134
|
+
* Fires when the destination changes AND when the modifiers do (Ctrl / Shift pressed or released with
|
|
135
|
+
* the pointer standing still) — the two are one signal on purpose: everything a consumer reflects
|
|
136
|
+
* during a drag (a mode description, a cursor, a drop preview) depends on both, and splitting them
|
|
137
|
+
* into two callbacks is how one of the pair ends up stale. Since a modifier change fires no pointer
|
|
138
|
+
* event, `e` is then the last one the POINTER produced: read the modifiers off `info.mods`, not `e`.
|
|
50
139
|
*/
|
|
51
|
-
onDragMove?: (range: RangeCoords, e: React.PointerEvent) => void;
|
|
52
|
-
/** Drag released: commit the translated range. */
|
|
53
|
-
onDragEnd?: (range: RangeCoords, e: React.PointerEvent) => void;
|
|
140
|
+
onDragMove?: (range: RangeCoords, e: React.PointerEvent, info: DragMoveInfo) => void;
|
|
141
|
+
/** Drag released: commit the translated range. `info` carries the modifiers held at the drop. */
|
|
142
|
+
onDragEnd?: (range: RangeCoords, e: React.PointerEvent, info: DragMoveInfo) => void;
|
|
54
143
|
/**
|
|
55
144
|
* Render a **ghost** at the in-flight translated range while a drag is in progress (nothing is rendered
|
|
56
145
|
* when idle). `Draggable` owns the preview state internally, so the consumer doesn't have to stash it
|
|
57
146
|
* — return e.g. a faded {@link RangeRect} to trail the pointer while the original stays put (the
|
|
58
147
|
* cut-move pattern). For a "live" feel (content follows the pointer) wire `onDragMove → range` instead.
|
|
148
|
+
*
|
|
149
|
+
* `info` re-renders the preview on a modifier change, so the ghost can become an insert I-beam the
|
|
150
|
+
* moment Shift goes down.
|
|
59
151
|
*/
|
|
60
|
-
renderMove?: (range: RangeCoords) => React.ReactNode;
|
|
152
|
+
renderMove?: (range: RangeCoords, info: DragMoveInfo) => React.ReactNode;
|
|
153
|
+
/**
|
|
154
|
+
* The cursor painted for the duration of the drag. The grip's element is pointer-captured, so ITS
|
|
155
|
+
* cursor is the one the browser shows once a drag is in flight — pass a mode-derived cursor (`copy`
|
|
156
|
+
* while Ctrl is held, say) to make the drop mode visible under the pointer.
|
|
157
|
+
*
|
|
158
|
+
* @defaultValue 'grabbing'
|
|
159
|
+
*/
|
|
160
|
+
dragCursor?: string;
|
|
61
161
|
/**
|
|
62
162
|
* Keep the current focus across a move drag (`preventDefault` on pointer-down, suppressing the compat
|
|
63
163
|
* `mousedown` that would move focus to the grid's `tabIndex` Stage). Used by the reference-range picker
|
|
@@ -74,6 +174,11 @@ export interface DraggableProps {
|
|
|
74
174
|
* name, so the nearest one is still reported but rarely means anything.
|
|
75
175
|
*/
|
|
76
176
|
onEdgeDoubleClick?: (range: RangeCoords, edge: RangeEdge, e: React.PointerEvent) => void;
|
|
177
|
+
/**
|
|
178
|
+
* Extra class(es) for the grip box. Joined with the `drag-grip` tag every grip carries, so a
|
|
179
|
+
* flavor names itself (`move-grip`) without losing the generic hook.
|
|
180
|
+
*/
|
|
181
|
+
className?: string;
|
|
77
182
|
}
|
|
78
183
|
/** Which border of a range a gesture landed on. */
|
|
79
184
|
export type RangeEdge = 'top' | 'bottom' | 'left' | 'right';
|