@ex-grid/react 0.0.0-stage → 0.2.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +41 -2
- package/dist/index.d.ts +309 -0
- package/dist/index.js +371 -0
- package/dist/index.js.map +7 -0
- package/package.json +30 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AkiraMisawa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,42 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ex-grid/react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
ExGrid for React: the Script Core's Framework Adapter, which mounts an Excel-like grid for reading money and risk numbers in a React tree.
|
|
4
|
+
|
|
5
|
+
It is one of the packages of the ExGrid family: Excel-like grids, sheets, pivot tables and charts for reading
|
|
6
|
+
money and risk numbers, published to npm under `@ex-grid` and to NuGet for Blazor, at one shared version.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install @ex-grid/react react react-dom
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`react` and `react-dom` are peer dependencies: your application provides them, at the versions `package.json` names.
|
|
15
|
+
|
|
16
|
+
It depends on exactly the version of `@ex-grid/core` it was built with, so upgrade the family's packages together.
|
|
17
|
+
|
|
18
|
+
What it draws reads this stylesheet. Import it once in your application:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import '@ex-grid/core/styles.css';
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Documentation
|
|
25
|
+
|
|
26
|
+
[ExGrid on the documentation site](https://akiramisawa.github.io/ex-grid/exgrid), read for React with the switch at the top, and every
|
|
27
|
+
version's [Release Notes](https://akiramisawa.github.io/ex-grid/release-notes).
|
|
28
|
+
|
|
29
|
+
> **The API may still change.** While the version is `0.x`, it may change from one version to the next; each
|
|
30
|
+
> version's Release Notes say what changed. A prerelease, a version with a suffix such as `-rc.1`, passed every
|
|
31
|
+
> test layer in CI on the commit it was built from. A version without a suffix is published only once the
|
|
32
|
+
> project's [Definition of Done](https://github.com/AkiraMisawa/ex-grid/blob/main/docs/definition-of-done.md) is
|
|
33
|
+
> signed off on it.
|
|
34
|
+
|
|
35
|
+
## Requirements
|
|
36
|
+
|
|
37
|
+
- **Chrome or Edge.** They are the supported browsers; Safari and Firefox are out of scope.
|
|
38
|
+
- **React 19.0.0 or a later 19.x.**
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExGrid for React: the Script Core's Framework Adapter (ADR-0350). `ExGrid` renders the element the core
|
|
3
|
+
* draws into and hands the core its props; everything the grid shows and decides is the core's.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { CellEditorView } from '@ex-grid/core';
|
|
9
|
+
import type { FilterPanelView } from '@ex-grid/core';
|
|
10
|
+
import type { FindPanelView } from '@ex-grid/core';
|
|
11
|
+
import { FocusEvent as FocusEvent_2 } from 'react';
|
|
12
|
+
import type { FormulaBarTextView } from '@ex-grid/core';
|
|
13
|
+
import type { GridCellEditorSlotContext } from '@ex-grid/core';
|
|
14
|
+
import type { GridCellMessageSlotContext } from '@ex-grid/core';
|
|
15
|
+
import type { GridChrome } from '@ex-grid/core';
|
|
16
|
+
import type { GridColumn } from '@ex-grid/core';
|
|
17
|
+
import type { GridColumnMenuSlotContext } from '@ex-grid/core';
|
|
18
|
+
import type { GridContextMenuSlotContext } from '@ex-grid/core';
|
|
19
|
+
import type { GridEditorCompletionSlotContext } from '@ex-grid/core';
|
|
20
|
+
import type { GridFilterPanelSlotContext } from '@ex-grid/core';
|
|
21
|
+
import type { GridFindPanelSlotContext } from '@ex-grid/core';
|
|
22
|
+
import type { GridFormulaBarTextSlotContext } from '@ex-grid/core';
|
|
23
|
+
import { GridHandle } from '@ex-grid/core';
|
|
24
|
+
import type { GridLoadingIndicatorSlotContext } from '@ex-grid/core';
|
|
25
|
+
import type { GridMenuSlotContext } from '@ex-grid/core';
|
|
26
|
+
import type { GridNameBoxSlotContext } from '@ex-grid/core';
|
|
27
|
+
import { GridOptions } from '@ex-grid/core';
|
|
28
|
+
import type { GridSelectionSummarySlotContext } from '@ex-grid/core';
|
|
29
|
+
import type { GridTemplateCell } from '@ex-grid/core';
|
|
30
|
+
import { KeyboardEvent as KeyboardEvent_2 } from 'react';
|
|
31
|
+
import { MouseEvent as MouseEvent_2 } from 'react';
|
|
32
|
+
import { ReactElement } from 'react';
|
|
33
|
+
import { ReactNode } from 'react';
|
|
34
|
+
import { Ref } from 'react';
|
|
35
|
+
|
|
36
|
+
/** What {@link useCellEditor} hands the editor's contents. */
|
|
37
|
+
export declare interface CellEditorHandles {
|
|
38
|
+
/** What the editor shows besides its text: the mode, a Reject's sentence, a pending commit. */
|
|
39
|
+
readonly view: CellEditorView;
|
|
40
|
+
/** Takes the text field, which the core reads and writes, and which takes the keyboard as the editor opens. */
|
|
41
|
+
readonly field: (control: HTMLInputElement | HTMLTextAreaElement | null) => void;
|
|
42
|
+
/** The text the field opens holding: hand it to the field as its default, and no value, since the text is the
|
|
43
|
+
* core's to read and write (ADR-0010). */
|
|
44
|
+
readonly defaultValue: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The ExGrid's Chrome as the core takes it: each slot's render function as a mount function that renders it
|
|
48
|
+
* through the ExGrid's portals (ADR-0350 section 9). A render function handed again in another `chrome` keeps the
|
|
49
|
+
* mount function `made` holds for it, so a part the core keeps standing, the Formula Bar's fields, is not drawn again.
|
|
50
|
+
* Which slots there are is the core's to check, as it checks a mount function handed it directly; a render function
|
|
51
|
+
* that is not a function is named here, in React's terms. Exported for the family's Framework Adapters, whose products
|
|
52
|
+
* hand ExGrid's Chrome on to the grids they draw, not for a Consumer. */
|
|
53
|
+
export declare function chromeMounts<Row>(given: ExGridChrome<Row> | undefined, portals: GridPortals, made: ChromeMountsMade): GridChrome<Row> | undefined;
|
|
54
|
+
|
|
55
|
+
/** The mount functions made for an ExGrid's render functions, by slot: one handed again is mounted by the same. */
|
|
56
|
+
export declare type ChromeMountsMade = Map<string, {
|
|
57
|
+
readonly render: unknown;
|
|
58
|
+
readonly portals: GridPortals;
|
|
59
|
+
readonly mount: (slot: HTMLElement, context: unknown) => () => void;
|
|
60
|
+
}>;
|
|
61
|
+
|
|
62
|
+
/** The props {@link useEditorCompletion} hands a candidate: spread them onto the element that draws it. */
|
|
63
|
+
export declare interface CompletionOptionProps {
|
|
64
|
+
/** The id the editor names while the candidate is chosen. */
|
|
65
|
+
readonly id: string;
|
|
66
|
+
/** A candidate is an option of the list. */
|
|
67
|
+
readonly role: 'option';
|
|
68
|
+
/** Whether the candidate is the chosen one. */
|
|
69
|
+
readonly 'aria-selected': boolean;
|
|
70
|
+
/** A click accepts the candidate; the core's box takes the press, keeping the keyboard in the editor. */
|
|
71
|
+
readonly onClick: () => void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** What {@link useEditorCompletion} hands the completion list. */
|
|
75
|
+
export declare interface EditorCompletionHandles {
|
|
76
|
+
/** The props of the list: spread them onto the element holding the candidates. */
|
|
77
|
+
readonly list: {
|
|
78
|
+
readonly id: string;
|
|
79
|
+
readonly role: 'listbox';
|
|
80
|
+
};
|
|
81
|
+
/** The props of the candidate at an index. */
|
|
82
|
+
readonly option: (index: number) => CompletionOptionProps;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* An Excel-like grid. The element it renders is the grid's instance root, which the Script Core owns; every
|
|
87
|
+
* render hands the core the props again, and what did not change costs nothing (ADR-0003).
|
|
88
|
+
*/
|
|
89
|
+
export declare function ExGrid<Row>(props: ExGridProps<Row>): ReactElement;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* What a ref to an ExGrid reaches: the Script Core's grid handle, less what the adapter itself does with it
|
|
93
|
+
* (taking the props again, and destroying the grid on unmount). Asked of a grid not mounted, it throws, but for
|
|
94
|
+
* the keyboard's hand-offs: `returnKeyboard` then does nothing, and `handKeyboardTo` focuses the control itself
|
|
95
|
+
* (DC-61).
|
|
96
|
+
*/
|
|
97
|
+
export declare type ExGridApi<Row> = Omit<GridHandle<Row>, 'update' | 'destroy' | 'holdSettled'>;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The Chrome slots an ExGrid takes, each a render function of what the core hands that slot (ADR-0390 section 3).
|
|
101
|
+
* A slot not given is drawn by the built-in Chrome. A render function is read when its part opens: a part standing
|
|
102
|
+
* keeps the render it opened with. Its answer is rendered as a component of its own, so it may hold state, kept
|
|
103
|
+
* while the part stands, and a hook belongs in a component it renders.
|
|
104
|
+
*/
|
|
105
|
+
export declare interface ExGridChrome<Row> {
|
|
106
|
+
/** The column menu's commands (ADR-0010, ADR-0044), drawn with {@link useMenuItems}. */
|
|
107
|
+
readonly columnMenu?: (context: GridColumnMenuSlotContext) => ReactNode;
|
|
108
|
+
/** The Context Menu (ADR-0036), drawn with {@link useMenuItems}. */
|
|
109
|
+
readonly contextMenu?: (context: GridContextMenuSlotContext<Row>) => ReactNode;
|
|
110
|
+
/** A column's filter panel, under the column menu's commands (ADR-0009, ADR-0044), drawn with {@link useFilterPanel}. */
|
|
111
|
+
readonly filterPanel?: (context: GridFilterPanelSlotContext) => ReactNode;
|
|
112
|
+
/** The find panel and its Replace (ADR-0055, ADR-0332), drawn with {@link useFindPanel}. */
|
|
113
|
+
readonly findPanel?: (context: GridFindPanelSlotContext) => ReactNode;
|
|
114
|
+
/** The Cell Editor's field (ADR-0010), drawn with {@link useCellEditor}. */
|
|
115
|
+
readonly cellEditor?: (context: GridCellEditorSlotContext) => ReactNode;
|
|
116
|
+
/** A cell's message (ADR-0034): its text, drawn in the core's box below the cell. */
|
|
117
|
+
readonly cellMessage?: (context: GridCellMessageSlotContext) => ReactNode;
|
|
118
|
+
/** The loading indicator (ADR-0010), at the grid's foot: nothing while nothing loads. */
|
|
119
|
+
readonly loadingIndicator?: (context: GridLoadingIndicatorSlotContext) => ReactNode;
|
|
120
|
+
/** The Selection Summary's figures (ADR-0130), drawn in the core's box in the status line. */
|
|
121
|
+
readonly selectionSummary?: (context: GridSelectionSummarySlotContext) => ReactNode;
|
|
122
|
+
/** The Formula Bar's text field (ADR-0051), drawn with {@link useFormulaBarText}. Another render function draws
|
|
123
|
+
* the field again: hold the function in a constant or a memo, not inline. */
|
|
124
|
+
readonly formulaBarText?: (context: GridFormulaBarTextSlotContext) => ReactNode;
|
|
125
|
+
/** The Formula Bar's Name Box (ADR-0051), drawn with {@link useNameBox}. */
|
|
126
|
+
readonly nameBox?: (context: GridNameBoxSlotContext) => ReactNode;
|
|
127
|
+
/** The completion list and argument hint beneath the editor (ADR-0051), drawn with {@link useEditorCompletion}. */
|
|
128
|
+
readonly editorCompletion?: (context: GridEditorCompletionSlotContext) => ReactNode;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* An ExGrid's props: the Script Core's options, whole. `locale` is the browser's language where not given,
|
|
133
|
+
* read once when the grid is made (ADR-0370 section 10); the core never reads it on its own. `temporal` is
|
|
134
|
+
* never the browser's: a grid with a Date column whose filter is offered is given one on every browser
|
|
135
|
+
* alike, or refused on every browser alike (ADR-0350 section 16).
|
|
136
|
+
*/
|
|
137
|
+
export declare type ExGridProps<Row> = Omit<GridOptions<Row>, 'chrome'> & {
|
|
138
|
+
/** A ref to what the Consumer may ask of the grid it rendered. */
|
|
139
|
+
readonly ref?: Ref<ExGridApi<Row>>;
|
|
140
|
+
/** The Chrome slots a Wrapper renders, each a render function of its context (ADR-0390). */
|
|
141
|
+
readonly chrome?: ExGridChrome<Row>;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/** What {@link useFilterPanel} hands a filter panel's contents. */
|
|
145
|
+
export declare interface FilterPanelHandles {
|
|
146
|
+
/** What the panel shows now, drawn again at each change the core makes to it. */
|
|
147
|
+
readonly view: FilterPanelView;
|
|
148
|
+
/** Takes the panel's first control: Tab on a command focuses it (ADR-0044). */
|
|
149
|
+
readonly first: (control: HTMLElement | null) => void;
|
|
150
|
+
/** Takes the panel's last control: Shift+Tab on a command wraps there (ADR-0044). */
|
|
151
|
+
readonly last: (control: HTMLElement | null) => void;
|
|
152
|
+
/** Takes the field a search is typed into, Excel's E: the value list's search box, or the first condition's
|
|
153
|
+
* value where no list stands (ADR-0044). */
|
|
154
|
+
readonly search: (control: HTMLElement | null) => void;
|
|
155
|
+
/** The value list's key handler, on the element that holds its checkboxes and "(Select All)", never on a text
|
|
156
|
+
* field: Excel's letters run their command through the core, and their default is prevented (ADR-0044). */
|
|
157
|
+
readonly onValueListKeyDown: (event: KeyboardEvent_2) => void;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** What {@link useFindPanel} hands a find panel's contents. */
|
|
161
|
+
export declare interface FindPanelHandles {
|
|
162
|
+
/** What the panel shows now, drawn again at each change the core makes to it. */
|
|
163
|
+
readonly view: FindPanelView;
|
|
164
|
+
/** Takes the find field, which each request of the core's puts the keyboard on, its text selected (FD-1). */
|
|
165
|
+
readonly field: (control: HTMLElement | null) => void;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** What {@link useFormulaBarText} hands the Formula Bar's text field. */
|
|
169
|
+
export declare interface FormulaBarTextHandles {
|
|
170
|
+
/** What the field shows besides its text: whether it takes typing, and whether its text is borrowed. */
|
|
171
|
+
readonly view: FormulaBarTextView;
|
|
172
|
+
/** Takes the text field, which the core reads and writes. Draw it uncontrolled, with no value. */
|
|
173
|
+
readonly field: (control: HTMLInputElement | HTMLTextAreaElement | null) => void;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* The portals of one ExGrid. The core mounts and takes away content in its own pass, outside React; each change
|
|
178
|
+
* asks for one render of the ExGrid, made at once in the next microtask, so a slot's content stands, and its
|
|
179
|
+
* control has taken a keyboard it was asked to, before the browser hands over the next input (the spine's sixth
|
|
180
|
+
* rule).
|
|
181
|
+
*
|
|
182
|
+
* Exported for the family's Framework Adapters (`@ex-grid/sheet-react`, `@ex-grid/pivot-react`,
|
|
183
|
+
* `@ex-grid/chart-react`), whose components render a product's Chrome slots the same way, not for a Consumer: the
|
|
184
|
+
* component holds one for its lifetime, renders its {@link GridPortals.elements} beside its root, and calls
|
|
185
|
+
* {@link GridPortals.begin} and {@link GridPortals.end} as its product is made and goes.
|
|
186
|
+
*/
|
|
187
|
+
export declare class GridPortals {
|
|
188
|
+
#private;
|
|
189
|
+
/** `render` asks the component holding the portals to render again, once per change. */
|
|
190
|
+
constructor(render: () => void);
|
|
191
|
+
/** A slot's content, made once per mount: the ExGrid rendering again hands React the same element, and every
|
|
192
|
+
* slot whose content was not mounted again skips its render (ADR-0003). */
|
|
193
|
+
set(id: string, slot: HTMLElement, show: () => ReactNode): void;
|
|
194
|
+
/** Takes away the content set for `id`. */
|
|
195
|
+
delete(id: string): void;
|
|
196
|
+
/** Mounts `show` into `slot`, keyed by the slot, and answers the cleanup the core calls: the core's mount
|
|
197
|
+
* function's shape, `(slot, context) => cleanup` (ADR-0350 section 9). */
|
|
198
|
+
mount(slot: HTMLElement, show: () => ReactNode): () => void;
|
|
199
|
+
/** A grid is made, after one this component held went, as StrictMode makes it again: it renders again. */
|
|
200
|
+
begin(): void;
|
|
201
|
+
/** The grid is going, and its content with it: nothing renders for it again. */
|
|
202
|
+
end(): void;
|
|
203
|
+
/** The portals, as the ExGrid renders them beside its root. */
|
|
204
|
+
elements(): ReactElement[];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** The props {@link useMenuItems} hands each item: spread them onto the control that draws it. */
|
|
208
|
+
export declare interface MenuItemProps {
|
|
209
|
+
/** The item's role in the menu the core named. */
|
|
210
|
+
readonly role: 'menuitem';
|
|
211
|
+
/** Takes the control the item is drawn as, which the hook focuses. */
|
|
212
|
+
readonly ref: (control: HTMLElement | null) => void;
|
|
213
|
+
/** Whether the command may not run: a disabled item is shown, and never focused or run. */
|
|
214
|
+
readonly disabled: boolean;
|
|
215
|
+
/** The same, for assistive technology, on a control that is not a button. */
|
|
216
|
+
readonly 'aria-disabled': boolean;
|
|
217
|
+
/** Focusable by the menu, never a tab stop: Tab is the menu's key (ADR-0039). */
|
|
218
|
+
readonly tabIndex: -1;
|
|
219
|
+
/** Marks an enabled item as one of the grid's own controls whose click and keys it takes in turn (ADR-0240). */
|
|
220
|
+
readonly 'data-ex-claims': 'click keydown' | undefined;
|
|
221
|
+
/** Hands every key on the item to the core, and does what it answers. */
|
|
222
|
+
readonly onKeyDown: (event: KeyboardEvent_2) => void;
|
|
223
|
+
/** Runs the command, as the core runs it. */
|
|
224
|
+
readonly onClick: (event: MouseEvent_2) => void;
|
|
225
|
+
/** Tells the core the item took DOM focus, so the menu's place follows it (ADR-0039). */
|
|
226
|
+
readonly onFocus: (event: FocusEvent_2) => void;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** What {@link useNameBox} hands the Name Box. */
|
|
230
|
+
export declare interface NameBoxHandles {
|
|
231
|
+
/** Takes the text input, which the core reads and writes. Draw it uncontrolled, with no value: an input, since
|
|
232
|
+
* Enter in a textarea submits nothing. */
|
|
233
|
+
readonly field: (control: HTMLInputElement | null) => void;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* A Template Column (ADR-0020): its cells render `render`'s answer in place of their value's text, which is
|
|
238
|
+
* still read for sorting, filtering, Find and copy. `render` is handed the cell: its row, its column, and a
|
|
239
|
+
* focus request, non-zero on exactly one render after Space engaged the cell, which a control answers by
|
|
240
|
+
* handing itself to `takeKeyboard` from a layout effect (ADR-0037). A component it renders keeps its state
|
|
241
|
+
* while its cell is painted, through a new version of its row where the source has a Row Key; without one a
|
|
242
|
+
* new instance is another row (ADR-0140), rendered afresh. `render` only makes elements: a hook belongs in
|
|
243
|
+
* the component it renders. Make the column once, outside the render
|
|
244
|
+
* of the component that holds the grid, as every column: a new column is a new declaration, and every row
|
|
245
|
+
* paints again (ADR-0003).
|
|
246
|
+
*/
|
|
247
|
+
export declare function templateColumn<Row>(column: TemplateColumnOptions<Row>, render: (cell: GridTemplateCell<Row>) => ReactNode): GridColumn<Row>;
|
|
248
|
+
|
|
249
|
+
/** A Template Column's declaration: a column's, whose cells React renders in place of their value's text. */
|
|
250
|
+
export declare type TemplateColumnOptions<Row> = Omit<GridColumn<Row>, 'template' | 'actions'>;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Draws the Cell Editor's slot from its context (ADR-0390 section 3): one text field, uncontrolled, opening with the
|
|
254
|
+
* core's text, which the core reads at each commit and writes where it types itself, as from the Formula Bar. The
|
|
255
|
+
* field takes the keyboard once drawn, the caret at its end. Enter, Tab, Escape, F2 and Overwrite's arrows are the
|
|
256
|
+
* core's, so the contents draw no key handler of their own.
|
|
257
|
+
*/
|
|
258
|
+
export declare function useCellEditor(context: GridCellEditorSlotContext): CellEditorHandles;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Draws the completion slot from its context (ADR-0390 section 3): the ids the editor names, and a click on a
|
|
262
|
+
* candidate accepting it. The core's box keeps the keyboard in the editor, and ↑, ↓, Tab and Escape are the
|
|
263
|
+
* editor's, so the contents draw no key handler and take no focus.
|
|
264
|
+
*/
|
|
265
|
+
export declare function useEditorCompletion(context: GridEditorCompletionSlotContext): EditorCompletionHandles;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Draws a filter panel slot from its context (ADR-0390 section 3): the core's view, read again at each change, and
|
|
269
|
+
* the control the core asks for the keyboard. Each choice goes to the context's setter, which is what it means; the
|
|
270
|
+
* hook focuses the control a request names, through the core, once it is drawn, and answers each request once,
|
|
271
|
+
* whichever component of the contents holds the hook when it comes.
|
|
272
|
+
*/
|
|
273
|
+
export declare function useFilterPanel(context: GridFilterPanelSlotContext): FilterPanelHandles;
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Draws the find panel slot from its context (ADR-0390 section 3): the core's view, read again at each change, and
|
|
277
|
+
* the find field, which takes the keyboard at each request of the core's, answered once. Each change and step goes to
|
|
278
|
+
* the context; Enter in a field is the core's form's, so the contents draw no form and no key handler of their own.
|
|
279
|
+
*/
|
|
280
|
+
export declare function useFindPanel(context: GridFindPanelSlotContext): FindPanelHandles;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Draws the Formula Bar's text slot from its context (ADR-0390 section 3): one text field, uncontrolled, which the
|
|
284
|
+
* core fills with the Focus cell's text, names and makes read only, and whose typing is the Cell Editor's (ADR-0051).
|
|
285
|
+
* Enter, Tab, Escape and F2 are the core's, so the contents draw no key handler of their own.
|
|
286
|
+
*/
|
|
287
|
+
export declare function useFormulaBarText(context: GridFormulaBarTextSlotContext): FormulaBarTextHandles;
|
|
288
|
+
|
|
289
|
+
/** Holds an ExGrid's portals, and a render of the component that asks for one; a family adapter's component holds
|
|
290
|
+
* its product's the same way. */
|
|
291
|
+
export declare function useGridPortals(): GridPortals;
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Draws a menu slot's items from its context (ADR-0390 section 3): answers the props of the item at `index` of
|
|
295
|
+
* `context.commands`, one item per command, in that order. Every key on an item is the core's table against the
|
|
296
|
+
* place the core keeps, and its default is prevented: a move focuses the item it names, a run invokes the command,
|
|
297
|
+
* a close closes the menu, and a move into the column's filter the core has made. The first enabled item takes the
|
|
298
|
+
* keyboard at each focus request, without scrolling the menu (ADR-0039).
|
|
299
|
+
*/
|
|
300
|
+
export declare function useMenuItems(context: GridMenuSlotContext): (index: number) => MenuItemProps;
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Draws the Name Box's slot from its context (ADR-0390 section 3): one text field, uncontrolled, which the core
|
|
304
|
+
* fills with the Focus's name. Enter is the core's form's and Escape the core's, so the contents draw no form and no
|
|
305
|
+
* key handler of their own.
|
|
306
|
+
*/
|
|
307
|
+
export declare function useNameBox(context: GridNameBoxSlotContext): NameBoxHandles;
|
|
308
|
+
|
|
309
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
// src/ex-grid.ts
|
|
2
|
+
import { createElement as createElement2, Fragment, useImperativeHandle, useLayoutEffect as useLayoutEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
|
|
3
|
+
import { createGrid } from "@ex-grid/core";
|
|
4
|
+
|
|
5
|
+
// src/chrome.ts
|
|
6
|
+
import {
|
|
7
|
+
useLayoutEffect,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
useSyncExternalStore
|
|
11
|
+
} from "react";
|
|
12
|
+
import { answerMenuKey, answerValueListKey, takeFilterFocus, takeFindFocus, takeMenuFocus } from "@ex-grid/core";
|
|
13
|
+
function chromeMounts(given, portals, made) {
|
|
14
|
+
const chrome = given;
|
|
15
|
+
if (chrome === void 0) return void 0;
|
|
16
|
+
if (typeof chrome !== "object" || chrome === null) {
|
|
17
|
+
throw new TypeError("chrome is an object of Chrome slots, each a render function of its context (ADR-0390).");
|
|
18
|
+
}
|
|
19
|
+
return Object.fromEntries(Object.entries(chrome).map(([name, render]) => {
|
|
20
|
+
if (render === void 0) return [name, void 0];
|
|
21
|
+
if (typeof render !== "function") throw new TypeError(`chrome.${name} is a render function of the slot's context (ADR-0390).`);
|
|
22
|
+
const before = made.get(name);
|
|
23
|
+
if (before?.render === render && before.portals === portals) return [name, before.mount];
|
|
24
|
+
const mount = (slot, context) => portals.mount(slot, () => {
|
|
25
|
+
const node = Reflect.apply(render, void 0, [context]);
|
|
26
|
+
return node;
|
|
27
|
+
});
|
|
28
|
+
made.set(name, { render, portals, mount });
|
|
29
|
+
return [name, mount];
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
function useMenuItems(context) {
|
|
33
|
+
const items = useRef([]);
|
|
34
|
+
const answered = useRef(void 0);
|
|
35
|
+
const drawn = (index) => {
|
|
36
|
+
const item = items.current[index];
|
|
37
|
+
if (item === void 0 || item === null) {
|
|
38
|
+
throw new Error(`The menu's item ${index} is not drawn: spread useMenuItems' props onto the control of every command, and forward its ref.`);
|
|
39
|
+
}
|
|
40
|
+
return item;
|
|
41
|
+
};
|
|
42
|
+
useLayoutEffect(() => {
|
|
43
|
+
if (answered.current === context.focusRequest) return;
|
|
44
|
+
answered.current = context.focusRequest;
|
|
45
|
+
takeMenuFocus(context, drawn);
|
|
46
|
+
}, [context]);
|
|
47
|
+
return (index) => {
|
|
48
|
+
const command = context.commands[index];
|
|
49
|
+
if (command === void 0) {
|
|
50
|
+
throw new RangeError(`useMenuItems is asked for item ${index} of a menu of ${context.commands.length} commands.`);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
role: "menuitem",
|
|
54
|
+
ref: (control) => {
|
|
55
|
+
items.current[index] = control;
|
|
56
|
+
},
|
|
57
|
+
disabled: !command.enabled,
|
|
58
|
+
"aria-disabled": !command.enabled,
|
|
59
|
+
tabIndex: -1,
|
|
60
|
+
"data-ex-claims": command.enabled ? "click keydown" : void 0,
|
|
61
|
+
onKeyDown: (event) => {
|
|
62
|
+
if (within(event)) answerMenuKey(context, event, drawn);
|
|
63
|
+
},
|
|
64
|
+
onClick: (event) => {
|
|
65
|
+
if (within(event)) command.invoke();
|
|
66
|
+
},
|
|
67
|
+
onFocus: (event) => {
|
|
68
|
+
if (within(event)) context.itemFocused(index);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function within(event) {
|
|
74
|
+
return event.target instanceof Node && event.currentTarget instanceof Node && event.currentTarget.contains(event.target);
|
|
75
|
+
}
|
|
76
|
+
function useFilterPanel(context) {
|
|
77
|
+
const view = useSyncExternalStore(context.subscribe, context.view);
|
|
78
|
+
const controls = useRef({ first: null, last: null, search: null });
|
|
79
|
+
useLayoutEffect(() => {
|
|
80
|
+
if (context.focusTarget === void 0 || answeredRequests.has(context)) return;
|
|
81
|
+
answeredRequests.add(context);
|
|
82
|
+
takeFilterFocus(context, (target) => {
|
|
83
|
+
const control = controls.current[target];
|
|
84
|
+
return control === null ? void 0 : control;
|
|
85
|
+
});
|
|
86
|
+
}, [context]);
|
|
87
|
+
const [refs] = useState(() => {
|
|
88
|
+
const taking = (target) => (control) => {
|
|
89
|
+
controls.current[target] = control;
|
|
90
|
+
};
|
|
91
|
+
return { first: taking("first"), last: taking("last"), search: taking("search") };
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
view,
|
|
95
|
+
...refs,
|
|
96
|
+
onValueListKeyDown: (event) => {
|
|
97
|
+
if (within(event)) answerValueListKey(context, event.nativeEvent);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
var answeredRequests = /* @__PURE__ */ new WeakSet();
|
|
102
|
+
function useFindPanel(context) {
|
|
103
|
+
const view = useSyncExternalStore(context.subscribe, context.view);
|
|
104
|
+
const control = useRef(null);
|
|
105
|
+
useLayoutEffect(() => {
|
|
106
|
+
if (answeredFinds.has(context)) return;
|
|
107
|
+
answeredFinds.add(context);
|
|
108
|
+
if (control.current === null) throw new Error("The find panel's field is not drawn: hand useFindPanel's `field` ref to the find field.");
|
|
109
|
+
takeFindFocus(context, control.current);
|
|
110
|
+
}, [context]);
|
|
111
|
+
const [field] = useState(() => (element) => {
|
|
112
|
+
control.current = element;
|
|
113
|
+
});
|
|
114
|
+
return { view, field };
|
|
115
|
+
}
|
|
116
|
+
var answeredFinds = /* @__PURE__ */ new WeakSet();
|
|
117
|
+
function useCellEditor(context) {
|
|
118
|
+
const view = useSyncExternalStore(context.subscribe, context.view);
|
|
119
|
+
const control = useRef(null);
|
|
120
|
+
const handed = useRef(null);
|
|
121
|
+
useLayoutEffect(() => {
|
|
122
|
+
const field2 = control.current;
|
|
123
|
+
if (!answeredEditors.has(context)) {
|
|
124
|
+
answeredEditors.add(context);
|
|
125
|
+
if (field2 === null) throw new Error("The cell editor's field is not drawn: hand useCellEditor's `field` ref to the text field.");
|
|
126
|
+
} else if (field2 === null || field2 === handed.current) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
handed.current = field2;
|
|
130
|
+
context.takeFocus(field2);
|
|
131
|
+
});
|
|
132
|
+
const [field] = useState(() => (element) => {
|
|
133
|
+
control.current = element;
|
|
134
|
+
});
|
|
135
|
+
return { view, field, defaultValue: context.initialText };
|
|
136
|
+
}
|
|
137
|
+
var answeredEditors = /* @__PURE__ */ new WeakSet();
|
|
138
|
+
function useHandedField(takeField, name) {
|
|
139
|
+
const control = useRef(null);
|
|
140
|
+
const handed = useRef(null);
|
|
141
|
+
useLayoutEffect(() => {
|
|
142
|
+
const field2 = control.current;
|
|
143
|
+
if (field2 === null && handed.current === null) throw new Error(`The ${name}'s field is not drawn: hand the hook's \`field\` ref to the text field.`);
|
|
144
|
+
if (field2 === null || field2 === handed.current) return;
|
|
145
|
+
handed.current = field2;
|
|
146
|
+
takeField(field2);
|
|
147
|
+
});
|
|
148
|
+
const [field] = useState(() => (element) => {
|
|
149
|
+
control.current = element;
|
|
150
|
+
});
|
|
151
|
+
return field;
|
|
152
|
+
}
|
|
153
|
+
function useFormulaBarText(context) {
|
|
154
|
+
const view = useSyncExternalStore(context.subscribe, context.view);
|
|
155
|
+
const field = useHandedField(context.takeField, "Formula Bar's text");
|
|
156
|
+
return { view, field };
|
|
157
|
+
}
|
|
158
|
+
function useNameBox(context) {
|
|
159
|
+
return { field: useHandedField(context.takeField, "Name Box") };
|
|
160
|
+
}
|
|
161
|
+
function useEditorCompletion(context) {
|
|
162
|
+
return {
|
|
163
|
+
list: { id: context.listId, role: "listbox" },
|
|
164
|
+
option: (index) => ({
|
|
165
|
+
id: context.optionId(index),
|
|
166
|
+
role: "option",
|
|
167
|
+
"aria-selected": index === context.selected,
|
|
168
|
+
onClick: () => {
|
|
169
|
+
context.accept(index);
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/portals.ts
|
|
176
|
+
import { createElement, useReducer } from "react";
|
|
177
|
+
import { createPortal, flushSync } from "react-dom";
|
|
178
|
+
function SlotContent({ show }) {
|
|
179
|
+
return show();
|
|
180
|
+
}
|
|
181
|
+
var GridPortals = class {
|
|
182
|
+
#portals = /* @__PURE__ */ new Map();
|
|
183
|
+
#render;
|
|
184
|
+
#owed = false;
|
|
185
|
+
#live = true;
|
|
186
|
+
/** `render` asks the component holding the portals to render again, once per change. */
|
|
187
|
+
constructor(render) {
|
|
188
|
+
this.#render = render;
|
|
189
|
+
}
|
|
190
|
+
/** A slot's content, made once per mount: the ExGrid rendering again hands React the same element, and every
|
|
191
|
+
* slot whose content was not mounted again skips its render (ADR-0003). */
|
|
192
|
+
set(id, slot, show) {
|
|
193
|
+
this.#portals.set(id, createPortal(createElement(SlotContent, { show }), slot, id));
|
|
194
|
+
this.#owe();
|
|
195
|
+
}
|
|
196
|
+
/** Takes away the content set for `id`. */
|
|
197
|
+
delete(id) {
|
|
198
|
+
this.#portals.delete(id);
|
|
199
|
+
this.#owe();
|
|
200
|
+
}
|
|
201
|
+
/** Mounts `show` into `slot`, keyed by the slot, and answers the cleanup the core calls: the core's mount
|
|
202
|
+
* function's shape, `(slot, context) => cleanup` (ADR-0350 section 9). */
|
|
203
|
+
mount(slot, show) {
|
|
204
|
+
const id = slotIdOf(slot);
|
|
205
|
+
this.set(id, slot, show);
|
|
206
|
+
return () => {
|
|
207
|
+
this.delete(id);
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/** A grid is made, after one this component held went, as StrictMode makes it again: it renders again. */
|
|
211
|
+
begin() {
|
|
212
|
+
this.#live = true;
|
|
213
|
+
}
|
|
214
|
+
/** The grid is going, and its content with it: nothing renders for it again. */
|
|
215
|
+
end() {
|
|
216
|
+
this.#live = false;
|
|
217
|
+
this.#portals.clear();
|
|
218
|
+
}
|
|
219
|
+
/** The portals, as the ExGrid renders them beside its root. */
|
|
220
|
+
elements() {
|
|
221
|
+
return [...this.#portals.values()];
|
|
222
|
+
}
|
|
223
|
+
#owe() {
|
|
224
|
+
if (this.#owed || !this.#live) return;
|
|
225
|
+
this.#owed = true;
|
|
226
|
+
queueMicrotask(() => {
|
|
227
|
+
this.#owed = false;
|
|
228
|
+
if (this.#live) flushSync(this.#render);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
var portalsOf = /* @__PURE__ */ new WeakMap();
|
|
233
|
+
var slotIds = /* @__PURE__ */ new WeakMap();
|
|
234
|
+
var slots = 0;
|
|
235
|
+
function slotIdOf(slot) {
|
|
236
|
+
let id = slotIds.get(slot);
|
|
237
|
+
if (id === void 0) {
|
|
238
|
+
slots += 1;
|
|
239
|
+
id = `ex-slot-${slots}`;
|
|
240
|
+
slotIds.set(slot, id);
|
|
241
|
+
}
|
|
242
|
+
return id;
|
|
243
|
+
}
|
|
244
|
+
function useGridPortals() {
|
|
245
|
+
const [, rendered] = useReducer((count) => count + 1, 0);
|
|
246
|
+
const [portals] = useReducer((held) => held, void 0, () => new GridPortals(rendered));
|
|
247
|
+
return portals;
|
|
248
|
+
}
|
|
249
|
+
function holdPortals(root, portals) {
|
|
250
|
+
portalsOf.set(root, portals);
|
|
251
|
+
}
|
|
252
|
+
function portalsAround(slot) {
|
|
253
|
+
for (let at = slot.parentElement; at !== null; at = at.parentElement) {
|
|
254
|
+
const portals = portalsOf.get(at);
|
|
255
|
+
if (portals !== void 0) return portals;
|
|
256
|
+
}
|
|
257
|
+
return void 0;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/ex-grid.ts
|
|
261
|
+
function withLocale(props, browserLocale) {
|
|
262
|
+
return props.locale === void 0 ? { ...props, locale: browserLocale } : props;
|
|
263
|
+
}
|
|
264
|
+
function ExGrid(props) {
|
|
265
|
+
const { ref, chrome, ...options } = props;
|
|
266
|
+
const element = useRef2(null);
|
|
267
|
+
const grid = useRef2(void 0);
|
|
268
|
+
const browserLocale = useRef2(void 0);
|
|
269
|
+
const portals = useGridPortals();
|
|
270
|
+
const [made] = useState2(() => /* @__PURE__ */ new Map());
|
|
271
|
+
const mounts = useMemo(() => chromeMounts(chrome, portals, made), [chrome, portals, made]);
|
|
272
|
+
useLayoutEffect2(() => {
|
|
273
|
+
const root = element.current;
|
|
274
|
+
if (root === null) throw new Error("Internal: React mounted no element for the grid to draw into.");
|
|
275
|
+
holdPortals(root, portals);
|
|
276
|
+
browserLocale.current ??= navigator.language;
|
|
277
|
+
const taken = withLocale(mounts === void 0 ? options : { ...options, chrome: mounts }, browserLocale.current);
|
|
278
|
+
if (grid.current === void 0) {
|
|
279
|
+
portals.begin();
|
|
280
|
+
grid.current = createGrid(root, taken);
|
|
281
|
+
} else {
|
|
282
|
+
grid.current.update(taken);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
useImperativeHandle(ref, () => ({
|
|
286
|
+
placeSelection: (range, focus, rowSequenceVersion) => mounted(grid.current).placeSelection(range, focus, rowSequenceVersion),
|
|
287
|
+
placeMatch: (cell, rowSequenceVersion) => mounted(grid.current).placeMatch(cell, rowSequenceVersion),
|
|
288
|
+
readView: () => mounted(grid.current).readView(),
|
|
289
|
+
announce: (sentence) => {
|
|
290
|
+
mounted(grid.current).announce(sentence);
|
|
291
|
+
},
|
|
292
|
+
writePointedText: (text) => mounted(grid.current).writePointedText(text),
|
|
293
|
+
takeBackPointedText: () => mounted(grid.current).takeBackPointedText(),
|
|
294
|
+
showViewWith: (view, rowSequenceVersion, place) => {
|
|
295
|
+
mounted(grid.current).showViewWith(view, rowSequenceVersion, place);
|
|
296
|
+
},
|
|
297
|
+
showNewView: (rowSequenceVersion, place) => {
|
|
298
|
+
mounted(grid.current).showNewView(rowSequenceVersion, place);
|
|
299
|
+
},
|
|
300
|
+
// Before the grid is drawn no edit can be open on it.
|
|
301
|
+
readEditText: () => grid.current?.readEditText(),
|
|
302
|
+
focus: () => {
|
|
303
|
+
mounted(grid.current).focus();
|
|
304
|
+
},
|
|
305
|
+
// Before the grid is drawn there is no keyboard of its own to give back, and the control takes it by its
|
|
306
|
+
// own focus, as the C# core's does before it attaches (DC-61).
|
|
307
|
+
returnKeyboard: (from) => {
|
|
308
|
+
grid.current?.returnKeyboard(from);
|
|
309
|
+
},
|
|
310
|
+
handKeyboardTo: (control, how) => {
|
|
311
|
+
if (grid.current === void 0) control.focus({ preventScroll: how?.preventScroll === true });
|
|
312
|
+
else grid.current.handKeyboardTo(control, how);
|
|
313
|
+
},
|
|
314
|
+
keyName: (event) => mounted(grid.current).keyName(event),
|
|
315
|
+
openPopover: (content, label, closed) => mounted(grid.current).openPopover(content, label, closed),
|
|
316
|
+
closePopover: () => {
|
|
317
|
+
mounted(grid.current).closePopover();
|
|
318
|
+
},
|
|
319
|
+
get canSummarize() {
|
|
320
|
+
return mounted(grid.current).canSummarize;
|
|
321
|
+
},
|
|
322
|
+
refreshSummary: () => {
|
|
323
|
+
mounted(grid.current).refreshSummary();
|
|
324
|
+
},
|
|
325
|
+
inOrder: (handling) => mounted(grid.current).inOrder(handling),
|
|
326
|
+
cancellableInOrder: (handling, cancelledTooLate) => mounted(grid.current).cancellableInOrder(handling, cancelledTooLate)
|
|
327
|
+
}), []);
|
|
328
|
+
useLayoutEffect2(() => () => {
|
|
329
|
+
portals.end();
|
|
330
|
+
grid.current?.destroy();
|
|
331
|
+
grid.current = void 0;
|
|
332
|
+
browserLocale.current = void 0;
|
|
333
|
+
}, [portals]);
|
|
334
|
+
return createElement2(Fragment, null, createElement2("div", { ref: element }), ...portals.elements());
|
|
335
|
+
}
|
|
336
|
+
function mounted(grid) {
|
|
337
|
+
if (grid === void 0) throw new Error("This ExGrid is not mounted: a grid not drawn is asked nothing.");
|
|
338
|
+
return grid;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/template-column.ts
|
|
342
|
+
function templateColumn(column, render) {
|
|
343
|
+
if (typeof render !== "function") {
|
|
344
|
+
throw new TypeError(`Column '${column.name}': render is a function of the cell, answering what it shows.`);
|
|
345
|
+
}
|
|
346
|
+
return Object.freeze({
|
|
347
|
+
...column,
|
|
348
|
+
template: (slot, cell) => {
|
|
349
|
+
const portals = portalsAround(slot);
|
|
350
|
+
if (portals === void 0) {
|
|
351
|
+
throw new TypeError(`Column '${column.name}' is a React Template Column, which renders only inside an ExGrid of @ex-grid/react.`);
|
|
352
|
+
}
|
|
353
|
+
return portals.mount(slot, () => render(cell));
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
export {
|
|
358
|
+
ExGrid,
|
|
359
|
+
GridPortals,
|
|
360
|
+
chromeMounts,
|
|
361
|
+
templateColumn,
|
|
362
|
+
useCellEditor,
|
|
363
|
+
useEditorCompletion,
|
|
364
|
+
useFilterPanel,
|
|
365
|
+
useFindPanel,
|
|
366
|
+
useFormulaBarText,
|
|
367
|
+
useGridPortals,
|
|
368
|
+
useMenuItems,
|
|
369
|
+
useNameBox
|
|
370
|
+
};
|
|
371
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/ex-grid.ts", "../src/chrome.ts", "../src/portals.ts", "../src/template-column.ts"],
|
|
4
|
+
"sourcesContent": ["// The React Framework Adapter (ADR-0350 section 3): it renders the element the Script Core draws into,\n// hands the core its props, and unmounts it. It holds no state of the grid's, draws nothing inside the\n// instance root, and decides nothing. The rows are never React's: a component per row would repeat,\n// through `memo`, the fight with re-rendering that ADR-0003 measured.\n\nimport { createElement, Fragment, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, type ReactElement, type Ref } from 'react';\nimport { createGrid, type GridHandle, type GridOptions } from '@ex-grid/core';\nimport { chromeMounts, type ChromeMountsMade, type ExGridChrome } from './chrome';\nimport { holdPortals, useGridPortals } from './portals';\n\n/**\n * An ExGrid's props: the Script Core's options, whole. `locale` is the browser's language where not given,\n * read once when the grid is made (ADR-0370 section 10); the core never reads it on its own. `temporal` is\n * never the browser's: a grid with a Date column whose filter is offered is given one on every browser\n * alike, or refused on every browser alike (ADR-0350 section 16).\n */\nexport type ExGridProps<Row> = Omit<GridOptions<Row>, 'chrome'> & {\n /** A ref to what the Consumer may ask of the grid it rendered. */\n readonly ref?: Ref<ExGridApi<Row>>;\n /** The Chrome slots a Wrapper renders, each a render function of its context (ADR-0390). */\n readonly chrome?: ExGridChrome<Row>;\n};\n\n/**\n * What a ref to an ExGrid reaches: the Script Core's grid handle, less what the adapter itself does with it\n * (taking the props again, and destroying the grid on unmount). Asked of a grid not mounted, it throws, but for\n * the keyboard's hand-offs: `returnKeyboard` then does nothing, and `handKeyboardTo` focuses the control itself\n * (DC-61).\n */\nexport type ExGridApi<Row> = Omit<GridHandle<Row>, 'update' | 'destroy' | 'holdSettled'>;\n\n/** The props as the core takes them: the browser's language where the Consumer gave no locale. */\nfunction withLocale<Row>(props: GridOptions<Row>, browserLocale: string): GridOptions<Row> {\n return props.locale === undefined ? { ...props, locale: browserLocale } : props;\n}\n\n/**\n * An Excel-like grid. The element it renders is the grid's instance root, which the Script Core owns; every\n * render hands the core the props again, and what did not change costs nothing (ADR-0003).\n */\nexport function ExGrid<Row>(props: ExGridProps<Row>): ReactElement {\n const { ref, chrome, ...options } = props;\n const element = useRef<HTMLDivElement>(null);\n const grid = useRef<GridHandle<Row> | undefined>(undefined);\n const browserLocale = useRef<string | undefined>(undefined);\n // The Template cells' content and the Chrome slots', rendered by this component into the slots the core keeps\n // (ADR-0020, ADR-0390).\n const portals = useGridPortals();\n // Made again only when the Consumer hands another chrome: the ExGrid renders for every portal change. A render\n // function handed again keeps its mount function.\n const [made] = useState<ChromeMountsMade>(() => new Map());\n const mounts = useMemo(() => chromeMounts(chrome, portals, made), [chrome, portals, made]);\n // Drawn before the browser paints, so a mounted grid is never seen empty.\n useLayoutEffect(() => {\n const root = element.current;\n if (root === null) throw new Error('Internal: React mounted no element for the grid to draw into.');\n holdPortals(root, portals);\n browserLocale.current ??= navigator.language;\n const taken = withLocale(mounts === undefined ? options : { ...options, chrome: mounts }, browserLocale.current);\n if (grid.current === undefined) {\n portals.begin();\n grid.current = createGrid(root, taken);\n } else {\n grid.current.update(taken);\n }\n });\n useImperativeHandle(ref, () => ({\n placeSelection: (range, focus, rowSequenceVersion) => mounted(grid.current).placeSelection(range, focus, rowSequenceVersion),\n placeMatch: (cell, rowSequenceVersion) => mounted(grid.current).placeMatch(cell, rowSequenceVersion),\n readView: () => mounted(grid.current).readView(),\n announce: (sentence) => { mounted(grid.current).announce(sentence); },\n writePointedText: (text) => mounted(grid.current).writePointedText(text),\n takeBackPointedText: () => mounted(grid.current).takeBackPointedText(),\n showViewWith: (view, rowSequenceVersion, place) => { mounted(grid.current).showViewWith(view, rowSequenceVersion, place); },\n showNewView: (rowSequenceVersion, place) => { mounted(grid.current).showNewView(rowSequenceVersion, place); },\n // Before the grid is drawn no edit can be open on it.\n readEditText: () => grid.current?.readEditText(),\n focus: () => { mounted(grid.current).focus(); },\n // Before the grid is drawn there is no keyboard of its own to give back, and the control takes it by its\n // own focus, as the C# core's does before it attaches (DC-61).\n returnKeyboard: (from) => { grid.current?.returnKeyboard(from); },\n handKeyboardTo: (control, how) => {\n if (grid.current === undefined) control.focus({ preventScroll: how?.preventScroll === true });\n else grid.current.handKeyboardTo(control, how);\n },\n keyName: (event) => mounted(grid.current).keyName(event),\n openPopover: (content, label, closed) => mounted(grid.current).openPopover(content, label, closed),\n closePopover: () => { mounted(grid.current).closePopover(); },\n get canSummarize() {\n return mounted(grid.current).canSummarize;\n },\n refreshSummary: () => { mounted(grid.current).refreshSummary(); },\n inOrder: (handling) => mounted(grid.current).inOrder(handling),\n cancellableInOrder: (handling, cancelledTooLate) => mounted(grid.current).cancellableInOrder(handling, cancelledTooLate),\n }), []);\n useLayoutEffect(() => () => {\n // The content goes with the grid, and nothing renders for it again.\n portals.end();\n grid.current?.destroy();\n grid.current = undefined;\n browserLocale.current = undefined;\n }, [portals]);\n return createElement(Fragment, null, createElement('div', { ref: element }), ...portals.elements());\n}\n\nfunction mounted<Row>(grid: GridHandle<Row> | undefined): GridHandle<Row> {\n if (grid === undefined) throw new Error('This ExGrid is not mounted: a grid not drawn is asked nothing.');\n return grid;\n}\n", "// The Chrome slots for React (ADR-0350 section 3, ADR-0390 section 3): a Wrapper, or a Consumer's own design\n// system, renders a part of the grid's chrome in place of the built-in one, as a render function of what the core\n// hands the slot. The ExGrid draws it through a portal into the slot the core made. What the part means stays\n// the core's: the hooks here carry a key, a click and a focus request to the core and do what it answers, so a\n// Wrapper draws its own components and decides nothing.\n\nimport {\n useLayoutEffect, useRef, useState, useSyncExternalStore, type FocusEvent as ReactFocusEvent, type KeyboardEvent as ReactKeyboardEvent,\n type MouseEvent as ReactMouseEvent, type ReactNode, type SyntheticEvent,\n} from 'react';\nimport { answerMenuKey, answerValueListKey, takeFilterFocus, takeFindFocus, takeMenuFocus } from '@ex-grid/core';\nimport type {\n CellEditorView, FilterPanelView, FindPanelView, FormulaBarTextView, GridCellEditorSlotContext, GridCellMessageSlotContext, GridChrome,\n GridColumnMenuSlotContext, GridContextMenuSlotContext, GridEditorCompletionSlotContext, GridFilterPanelSlotContext, GridFindPanelSlotContext,\n GridFormulaBarTextSlotContext, GridLoadingIndicatorSlotContext, GridMenuSlotContext, GridNameBoxSlotContext, GridSelectionSummarySlotContext,\n} from '@ex-grid/core';\nimport type { GridPortals } from './portals';\n\n/**\n * The Chrome slots an ExGrid takes, each a render function of what the core hands that slot (ADR-0390 section 3).\n * A slot not given is drawn by the built-in Chrome. A render function is read when its part opens: a part standing\n * keeps the render it opened with. Its answer is rendered as a component of its own, so it may hold state, kept\n * while the part stands, and a hook belongs in a component it renders.\n */\nexport interface ExGridChrome<Row> {\n /** The column menu's commands (ADR-0010, ADR-0044), drawn with {@link useMenuItems}. */\n readonly columnMenu?: (context: GridColumnMenuSlotContext) => ReactNode;\n /** The Context Menu (ADR-0036), drawn with {@link useMenuItems}. */\n readonly contextMenu?: (context: GridContextMenuSlotContext<Row>) => ReactNode;\n /** A column's filter panel, under the column menu's commands (ADR-0009, ADR-0044), drawn with {@link useFilterPanel}. */\n readonly filterPanel?: (context: GridFilterPanelSlotContext) => ReactNode;\n /** The find panel and its Replace (ADR-0055, ADR-0332), drawn with {@link useFindPanel}. */\n readonly findPanel?: (context: GridFindPanelSlotContext) => ReactNode;\n /** The Cell Editor's field (ADR-0010), drawn with {@link useCellEditor}. */\n readonly cellEditor?: (context: GridCellEditorSlotContext) => ReactNode;\n /** A cell's message (ADR-0034): its text, drawn in the core's box below the cell. */\n readonly cellMessage?: (context: GridCellMessageSlotContext) => ReactNode;\n /** The loading indicator (ADR-0010), at the grid's foot: nothing while nothing loads. */\n readonly loadingIndicator?: (context: GridLoadingIndicatorSlotContext) => ReactNode;\n /** The Selection Summary's figures (ADR-0130), drawn in the core's box in the status line. */\n readonly selectionSummary?: (context: GridSelectionSummarySlotContext) => ReactNode;\n /** The Formula Bar's text field (ADR-0051), drawn with {@link useFormulaBarText}. Another render function draws\n * the field again: hold the function in a constant or a memo, not inline. */\n readonly formulaBarText?: (context: GridFormulaBarTextSlotContext) => ReactNode;\n /** The Formula Bar's Name Box (ADR-0051), drawn with {@link useNameBox}. */\n readonly nameBox?: (context: GridNameBoxSlotContext) => ReactNode;\n /** The completion list and argument hint beneath the editor (ADR-0051), drawn with {@link useEditorCompletion}. */\n readonly editorCompletion?: (context: GridEditorCompletionSlotContext) => ReactNode;\n}\n\n/** The mount functions made for an ExGrid's render functions, by slot: one handed again is mounted by the same. */\nexport type ChromeMountsMade = Map<string, {\n readonly render: unknown; readonly portals: GridPortals; readonly mount: (slot: HTMLElement, context: unknown) => () => void;\n}>;\n\n/** The ExGrid's Chrome as the core takes it: each slot's render function as a mount function that renders it\n * through the ExGrid's portals (ADR-0350 section 9). A render function handed again in another `chrome` keeps the\n * mount function `made` holds for it, so a part the core keeps standing, the Formula Bar's fields, is not drawn again.\n * Which slots there are is the core's to check, as it checks a mount function handed it directly; a render function\n * that is not a function is named here, in React's terms. Exported for the family's Framework Adapters, whose products\n * hand ExGrid's Chrome on to the grids they draw, not for a Consumer. */\nexport function chromeMounts<Row>(given: ExGridChrome<Row> | undefined, portals: GridPortals, made: ChromeMountsMade): GridChrome<Row> | undefined {\n // Checked as a script may hand it, whatever its type says.\n const chrome: unknown = given;\n if (chrome === undefined) return undefined;\n if (typeof chrome !== 'object' || chrome === null) {\n throw new TypeError('chrome is an object of Chrome slots, each a render function of its context (ADR-0390).');\n }\n return Object.fromEntries(Object.entries(chrome).map(([name, render]: [string, unknown]) => {\n if (render === undefined) return [name, undefined];\n if (typeof render !== 'function') throw new TypeError(`chrome.${name} is a render function of the slot's context (ADR-0390).`);\n const before = made.get(name);\n if (before?.render === render && before.portals === portals) return [name, before.mount];\n const mount = (slot: HTMLElement, context: unknown): (() => void) => portals.mount(slot, () => {\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- the core hands each slot's mount that slot's context, the one its render function takes\n const node = Reflect.apply(render, undefined, [context]) as ReactNode;\n return node;\n });\n made.set(name, { render, portals, mount });\n return [name, mount];\n }));\n}\n\n/** The props {@link useMenuItems} hands each item: spread them onto the control that draws it. */\nexport interface MenuItemProps {\n /** The item's role in the menu the core named. */\n readonly role: 'menuitem';\n /** Takes the control the item is drawn as, which the hook focuses. */\n readonly ref: (control: HTMLElement | null) => void;\n /** Whether the command may not run: a disabled item is shown, and never focused or run. */\n readonly disabled: boolean;\n /** The same, for assistive technology, on a control that is not a button. */\n readonly 'aria-disabled': boolean;\n /** Focusable by the menu, never a tab stop: Tab is the menu's key (ADR-0039). */\n readonly tabIndex: -1;\n /** Marks an enabled item as one of the grid's own controls whose click and keys it takes in turn (ADR-0240). */\n readonly 'data-ex-claims': 'click keydown' | undefined;\n /** Hands every key on the item to the core, and does what it answers. */\n readonly onKeyDown: (event: ReactKeyboardEvent) => void;\n /** Runs the command, as the core runs it. */\n readonly onClick: (event: ReactMouseEvent) => void;\n /** Tells the core the item took DOM focus, so the menu's place follows it (ADR-0039). */\n readonly onFocus: (event: ReactFocusEvent) => void;\n}\n\n/**\n * Draws a menu slot's items from its context (ADR-0390 section 3): answers the props of the item at `index` of\n * `context.commands`, one item per command, in that order. Every key on an item is the core's table against the\n * place the core keeps, and its default is prevented: a move focuses the item it names, a run invokes the command,\n * a close closes the menu, and a move into the column's filter the core has made. The first enabled item takes the\n * keyboard at each focus request, without scrolling the menu (ADR-0039).\n */\nexport function useMenuItems(context: GridMenuSlotContext): (index: number) => MenuItemProps {\n const items = useRef<(HTMLElement | null)[]>([]);\n const answered = useRef<number | undefined>(undefined);\n // The control item `index` is drawn as: one the contents never drew with these props can take no keyboard.\n const drawn = (index: number): HTMLElement => {\n const item = items.current[index];\n if (item === undefined || item === null) {\n throw new Error(`The menu's item ${index} is not drawn: spread useMenuItems' props onto the control of every command, and forward its ref.`);\n }\n return item;\n };\n useLayoutEffect(() => {\n if (answered.current === context.focusRequest) return;\n answered.current = context.focusRequest;\n takeMenuFocus(context, drawn);\n }, [context]);\n return (index: number): MenuItemProps => {\n const command = context.commands[index];\n if (command === undefined) {\n throw new RangeError(`useMenuItems is asked for item ${index} of a menu of ${context.commands.length} commands.`);\n }\n return {\n role: 'menuitem',\n ref: control => { items.current[index] = control; },\n disabled: !command.enabled,\n 'aria-disabled': !command.enabled,\n tabIndex: -1,\n 'data-ex-claims': command.enabled ? 'click keydown' : undefined,\n onKeyDown: event => {\n // Only the item's own: an event from a popup the item opened through a portal reaches it in React's\n // tree, but is not inside it in the page, and is that popup's.\n if (within(event)) answerMenuKey(context, event, drawn);\n },\n onClick: event => { if (within(event)) command.invoke(); },\n onFocus: event => { if (within(event)) context.itemFocused(index); },\n };\n };\n}\n\n/** Whether an event happened inside the item it reached, in the page and not only in React's tree. */\nfunction within(event: SyntheticEvent): boolean {\n return event.target instanceof Node && event.currentTarget instanceof Node && event.currentTarget.contains(event.target);\n}\n\n/** What {@link useFilterPanel} hands a filter panel's contents. */\nexport interface FilterPanelHandles {\n /** What the panel shows now, drawn again at each change the core makes to it. */\n readonly view: FilterPanelView;\n /** Takes the panel's first control: Tab on a command focuses it (ADR-0044). */\n readonly first: (control: HTMLElement | null) => void;\n /** Takes the panel's last control: Shift+Tab on a command wraps there (ADR-0044). */\n readonly last: (control: HTMLElement | null) => void;\n /** Takes the field a search is typed into, Excel's E: the value list's search box, or the first condition's\n * value where no list stands (ADR-0044). */\n readonly search: (control: HTMLElement | null) => void;\n /** The value list's key handler, on the element that holds its checkboxes and \"(Select All)\", never on a text\n * field: Excel's letters run their command through the core, and their default is prevented (ADR-0044). */\n readonly onValueListKeyDown: (event: ReactKeyboardEvent) => void;\n}\n\n/**\n * Draws a filter panel slot from its context (ADR-0390 section 3): the core's view, read again at each change, and\n * the control the core asks for the keyboard. Each choice goes to the context's setter, which is what it means; the\n * hook focuses the control a request names, through the core, once it is drawn, and answers each request once,\n * whichever component of the contents holds the hook when it comes.\n */\nexport function useFilterPanel(context: GridFilterPanelSlotContext): FilterPanelHandles {\n const view = useSyncExternalStore(context.subscribe, context.view);\n const controls = useRef<Record<FocusTarget, HTMLElement | null>>({ first: null, last: null, search: null });\n useLayoutEffect(() => {\n if (context.focusTarget === undefined || answeredRequests.has(context)) return;\n answeredRequests.add(context);\n takeFilterFocus(context, target => {\n const control = controls.current[target];\n return control === null ? undefined : control;\n });\n }, [context]);\n // Made once, so a render hands React the same refs and it does not take them away and back.\n const [refs] = useState(() => {\n const taking = (target: FocusTarget) => (control: HTMLElement | null): void => { controls.current[target] = control; };\n return { first: taking('first'), last: taking('last'), search: taking('search') };\n });\n return {\n view,\n ...refs,\n onValueListKeyDown: event => {\n if (within(event)) answerValueListKey(context, event.nativeEvent);\n },\n };\n}\n\ntype FocusTarget = NonNullable<GridFilterPanelSlotContext['focusTarget']>;\n\n/** The contexts whose focus request has been answered. Each request comes in a context of its own, so one answered is\n * never answered again: not by a component of the contents mounted after it, nor by an effect run twice. */\nconst answeredRequests = new WeakSet<GridFilterPanelSlotContext>();\n\n/** What {@link useFindPanel} hands a find panel's contents. */\nexport interface FindPanelHandles {\n /** What the panel shows now, drawn again at each change the core makes to it. */\n readonly view: FindPanelView;\n /** Takes the find field, which each request of the core's puts the keyboard on, its text selected (FD-1). */\n readonly field: (control: HTMLElement | null) => void;\n}\n\n/**\n * Draws the find panel slot from its context (ADR-0390 section 3): the core's view, read again at each change, and\n * the find field, which takes the keyboard at each request of the core's, answered once. Each change and step goes to\n * the context; Enter in a field is the core's form's, so the contents draw no form and no key handler of their own.\n */\nexport function useFindPanel(context: GridFindPanelSlotContext): FindPanelHandles {\n const view = useSyncExternalStore(context.subscribe, context.view);\n const control = useRef<HTMLElement | null>(null);\n useLayoutEffect(() => {\n if (answeredFinds.has(context)) return;\n answeredFinds.add(context);\n if (control.current === null) throw new Error(\"The find panel's field is not drawn: hand useFindPanel's `field` ref to the find field.\");\n takeFindFocus(context, control.current);\n }, [context]);\n const [field] = useState(() => (element: HTMLElement | null): void => { control.current = element; });\n return { view, field };\n}\n\n/** The find panel's contexts whose request has been answered, as {@link answeredRequests} keeps the filter panel's. */\nconst answeredFinds = new WeakSet<GridFindPanelSlotContext>();\n\n/** What {@link useCellEditor} hands the editor's contents. */\nexport interface CellEditorHandles {\n /** What the editor shows besides its text: the mode, a Reject's sentence, a pending commit. */\n readonly view: CellEditorView;\n /** Takes the text field, which the core reads and writes, and which takes the keyboard as the editor opens. */\n readonly field: (control: HTMLInputElement | HTMLTextAreaElement | null) => void;\n /** The text the field opens holding: hand it to the field as its default, and no value, since the text is the\n * core's to read and write (ADR-0010). */\n readonly defaultValue: string;\n}\n\n/**\n * Draws the Cell Editor's slot from its context (ADR-0390 section 3): one text field, uncontrolled, opening with the\n * core's text, which the core reads at each commit and writes where it types itself, as from the Formula Bar. The\n * field takes the keyboard once drawn, the caret at its end. Enter, Tab, Escape, F2 and Overwrite's arrows are the\n * core's, so the contents draw no key handler of their own.\n */\nexport function useCellEditor(context: GridCellEditorSlotContext): CellEditorHandles {\n const view = useSyncExternalStore(context.subscribe, context.view);\n const control = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null);\n const handed = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null);\n // After every render: the field drawn first is handed over for the keyboard the opening owes, and a field drawn\n // anew, an input swapped for a textarea say, is handed over too, so the core reads it and it keeps the keyboard.\n useLayoutEffect(() => {\n const field = control.current;\n if (!answeredEditors.has(context)) {\n answeredEditors.add(context);\n if (field === null) throw new Error(\"The cell editor's field is not drawn: hand useCellEditor's `field` ref to the text field.\");\n } else if (field === null || field === handed.current) {\n return;\n }\n handed.current = field;\n context.takeFocus(field);\n });\n const [field] = useState(() => (element: HTMLInputElement | HTMLTextAreaElement | null): void => { control.current = element; });\n return { view, field, defaultValue: context.initialText };\n}\n\n/** The editors whose opening request has been answered: StrictMode runs an effect twice, and the keyboard is taken\n * once. */\nconst answeredEditors = new WeakSet<GridCellEditorSlotContext>();\n\n/** Hands `takeField` the field `control` holds after every render where it is another than the one handed last: the\n * Formula Bar's fields are the core's to read and write, and take the keyboard only by the user's press. The first\n * render must draw it; a render that draws none for a while leaves the one handed last standing. */\nfunction useHandedField(takeField: (control: HTMLElement) => void, name: string): (element: EditorField | null) => void {\n const control = useRef<EditorField | null>(null);\n const handed = useRef<EditorField | null>(null);\n useLayoutEffect(() => {\n const field = control.current;\n if (field === null && handed.current === null) throw new Error(`The ${name}'s field is not drawn: hand the hook's \\`field\\` ref to the text field.`);\n if (field === null || field === handed.current) return;\n handed.current = field;\n takeField(field);\n });\n const [field] = useState(() => (element: EditorField | null): void => { control.current = element; });\n return field;\n}\n\n/** A text field the core reads and writes. */\ntype EditorField = HTMLInputElement | HTMLTextAreaElement;\n\n/** What {@link useFormulaBarText} hands the Formula Bar's text field. */\nexport interface FormulaBarTextHandles {\n /** What the field shows besides its text: whether it takes typing, and whether its text is borrowed. */\n readonly view: FormulaBarTextView;\n /** Takes the text field, which the core reads and writes. Draw it uncontrolled, with no value. */\n readonly field: (control: HTMLInputElement | HTMLTextAreaElement | null) => void;\n}\n\n/**\n * Draws the Formula Bar's text slot from its context (ADR-0390 section 3): one text field, uncontrolled, which the\n * core fills with the Focus cell's text, names and makes read only, and whose typing is the Cell Editor's (ADR-0051).\n * Enter, Tab, Escape and F2 are the core's, so the contents draw no key handler of their own.\n */\nexport function useFormulaBarText(context: GridFormulaBarTextSlotContext): FormulaBarTextHandles {\n const view = useSyncExternalStore(context.subscribe, context.view);\n const field = useHandedField(context.takeField, 'Formula Bar\\'s text');\n return { view, field };\n}\n\n/** What {@link useNameBox} hands the Name Box. */\nexport interface NameBoxHandles {\n /** Takes the text input, which the core reads and writes. Draw it uncontrolled, with no value: an input, since\n * Enter in a textarea submits nothing. */\n readonly field: (control: HTMLInputElement | null) => void;\n}\n\n/**\n * Draws the Name Box's slot from its context (ADR-0390 section 3): one text field, uncontrolled, which the core\n * fills with the Focus's name. Enter is the core's form's and Escape the core's, so the contents draw no form and no\n * key handler of their own.\n */\nexport function useNameBox(context: GridNameBoxSlotContext): NameBoxHandles {\n return { field: useHandedField(context.takeField, 'Name Box') };\n}\n\n/** The props {@link useEditorCompletion} hands a candidate: spread them onto the element that draws it. */\nexport interface CompletionOptionProps {\n /** The id the editor names while the candidate is chosen. */\n readonly id: string;\n /** A candidate is an option of the list. */\n readonly role: 'option';\n /** Whether the candidate is the chosen one. */\n readonly 'aria-selected': boolean;\n /** A click accepts the candidate; the core's box takes the press, keeping the keyboard in the editor. */\n readonly onClick: () => void;\n}\n\n/** What {@link useEditorCompletion} hands the completion list. */\nexport interface EditorCompletionHandles {\n /** The props of the list: spread them onto the element holding the candidates. */\n readonly list: { readonly id: string; readonly role: 'listbox' };\n /** The props of the candidate at an index. */\n readonly option: (index: number) => CompletionOptionProps;\n}\n\n/**\n * Draws the completion slot from its context (ADR-0390 section 3): the ids the editor names, and a click on a\n * candidate accepting it. The core's box keeps the keyboard in the editor, and \u2191, \u2193, Tab and Escape are the\n * editor's, so the contents draw no key handler and take no focus.\n */\nexport function useEditorCompletion(context: GridEditorCompletionSlotContext): EditorCompletionHandles {\n return {\n list: { id: context.listId, role: 'listbox' },\n option: index => ({\n id: context.optionId(index),\n role: 'option',\n 'aria-selected': index === context.selected,\n onClick: () => { context.accept(index); },\n }),\n };\n}\n", "// The portals of one ExGrid (ADR-0350 section 9): the core mounts content into slots it keeps, a Template cell's\n// (ADR-0020) or a Chrome slot's (ADR-0390), and this hands React each slot to render into, as a portal of the\n// ExGrid the slot stands in. A portal is keyed by its slot, so content mounted again on the same slot, for a\n// focus request or for a new version of its row, is the same component given new props, and keeps its state.\n\nimport { createElement, useReducer, type ReactElement, type ReactNode } from 'react';\nimport { createPortal, flushSync } from 'react-dom';\n\n/** What a slot shows: its render, rendered as a component of its own, so a render that throws reaches the\n * Consumer's error boundary. */\nfunction SlotContent({ show }: { readonly show: () => ReactNode }): ReactNode {\n return show();\n}\n\n/**\n * The portals of one ExGrid. The core mounts and takes away content in its own pass, outside React; each change\n * asks for one render of the ExGrid, made at once in the next microtask, so a slot's content stands, and its\n * control has taken a keyboard it was asked to, before the browser hands over the next input (the spine's sixth\n * rule).\n *\n * Exported for the family's Framework Adapters (`@ex-grid/sheet-react`, `@ex-grid/pivot-react`,\n * `@ex-grid/chart-react`), whose components render a product's Chrome slots the same way, not for a Consumer: the\n * component holds one for its lifetime, renders its {@link GridPortals.elements} beside its root, and calls\n * {@link GridPortals.begin} and {@link GridPortals.end} as its product is made and goes.\n */\nexport class GridPortals {\n readonly #portals = new Map<string, ReactElement>();\n readonly #render: () => void;\n #owed = false;\n #live = true;\n\n /** `render` asks the component holding the portals to render again, once per change. */\n constructor(render: () => void) {\n this.#render = render;\n }\n\n /** A slot's content, made once per mount: the ExGrid rendering again hands React the same element, and every\n * slot whose content was not mounted again skips its render (ADR-0003). */\n set(id: string, slot: HTMLElement, show: () => ReactNode): void {\n this.#portals.set(id, createPortal(createElement(SlotContent, { show }), slot, id));\n this.#owe();\n }\n\n /** Takes away the content set for `id`. */\n delete(id: string): void {\n this.#portals.delete(id);\n this.#owe();\n }\n\n /** Mounts `show` into `slot`, keyed by the slot, and answers the cleanup the core calls: the core's mount\n * function's shape, `(slot, context) => cleanup` (ADR-0350 section 9). */\n mount(slot: HTMLElement, show: () => ReactNode): () => void {\n const id = slotIdOf(slot);\n this.set(id, slot, show);\n return () => { this.delete(id); };\n }\n\n /** A grid is made, after one this component held went, as StrictMode makes it again: it renders again. */\n begin(): void {\n this.#live = true;\n }\n\n /** The grid is going, and its content with it: nothing renders for it again. */\n end(): void {\n this.#live = false;\n this.#portals.clear();\n }\n\n /** The portals, as the ExGrid renders them beside its root. */\n elements(): ReactElement[] {\n return [...this.#portals.values()];\n }\n\n #owe(): void {\n if (this.#owed || !this.#live) return;\n this.#owed = true;\n queueMicrotask(() => {\n this.#owed = false;\n if (this.#live) flushSync(this.#render);\n });\n }\n}\n\n/** The portals of each ExGrid, by the element it renders: the grid's instance root. */\nconst portalsOf = new WeakMap<Element, GridPortals>();\nconst slotIds = new WeakMap<HTMLElement, string>();\nlet slots = 0;\n\n/** A slot's key, the same for as long as the slot stands. */\nfunction slotIdOf(slot: HTMLElement): string {\n let id = slotIds.get(slot);\n if (id === undefined) {\n slots += 1;\n id = `ex-slot-${slots}`;\n slotIds.set(slot, id);\n }\n return id;\n}\n\n/** Holds an ExGrid's portals, and a render of the component that asks for one; a family adapter's component holds\n * its product's the same way. */\nexport function useGridPortals(): GridPortals {\n const [, rendered] = useReducer((count: number) => count + 1, 0);\n const [portals] = useReducer((held: GridPortals) => held, undefined, () => new GridPortals(rendered));\n return portals;\n}\n\n/** Ties an ExGrid's portals to the element it renders, for its slots to find. */\nexport function holdPortals(root: Element, portals: GridPortals): void {\n portalsOf.set(root, portals);\n}\n\n/** The portals of the ExGrid `slot` stands in, or undefined outside one. */\nexport function portalsAround(slot: HTMLElement): GridPortals | undefined {\n for (let at: Element | null = slot.parentElement; at !== null; at = at.parentElement) {\n const portals = portalsOf.get(at);\n if (portals !== undefined) return portals;\n }\n return undefined;\n}\n", "// A Template Column for React (ADR-0020, ADR-0350 section 16): the core mounts a cell's content into a slot it\n// keeps, and this mount hands React the slot to render into, through the ExGrid's portals. A cell mounted again,\n// for a focus request or for a new version of its row under a Row Key, is the same component given new props,\n// and keeps its state.\n\nimport type { ReactNode } from 'react';\nimport type { GridColumn, GridTemplateCell } from '@ex-grid/core';\nimport { portalsAround } from './portals';\n\n/** A Template Column's declaration: a column's, whose cells React renders in place of their value's text. */\nexport type TemplateColumnOptions<Row> = Omit<GridColumn<Row>, 'template' | 'actions'>;\n\n/**\n * A Template Column (ADR-0020): its cells render `render`'s answer in place of their value's text, which is\n * still read for sorting, filtering, Find and copy. `render` is handed the cell: its row, its column, and a\n * focus request, non-zero on exactly one render after Space engaged the cell, which a control answers by\n * handing itself to `takeKeyboard` from a layout effect (ADR-0037). A component it renders keeps its state\n * while its cell is painted, through a new version of its row where the source has a Row Key; without one a\n * new instance is another row (ADR-0140), rendered afresh. `render` only makes elements: a hook belongs in\n * the component it renders. Make the column once, outside the render\n * of the component that holds the grid, as every column: a new column is a new declaration, and every row\n * paints again (ADR-0003).\n */\nexport function templateColumn<Row>(column: TemplateColumnOptions<Row>, render: (cell: GridTemplateCell<Row>) => ReactNode): GridColumn<Row> {\n if (typeof render !== 'function') {\n throw new TypeError(`Column '${column.name}': render is a function of the cell, answering what it shows.`);\n }\n return Object.freeze({\n ...column,\n template: (slot: HTMLElement, cell: GridTemplateCell<Row>) => {\n const portals = portalsAround(slot);\n if (portals === undefined) {\n throw new TypeError(`Column '${column.name}' is a React Template Column, which renders only inside an ExGrid of @ex-grid/react.`);\n }\n return portals.mount(slot, () => render(cell));\n },\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["createElement", "useLayoutEffect", "useRef", "useState", "field", "useRef", "useState", "useLayoutEffect", "createElement"]
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ex-grid/react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0-rc.1",
|
|
4
|
+
"description": "ExGrid for React: the Script Core's Framework Adapter, which mounts an Excel-like grid for reading money and risk numbers in a React tree.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/AkiraMisawa/ex-grid.git",
|
|
9
|
+
"directory": "packages/react"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://akiramisawa.github.io/ex-grid/",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/index.js",
|
|
22
|
+
"dist/index.js.map",
|
|
23
|
+
"dist/index.d.ts"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@ex-grid/core": "0.2.0-rc.1"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"react": "^19.0.0",
|
|
30
|
+
"react-dom": "^19.0.0"
|
|
31
|
+
}
|
|
32
|
+
}
|