@dxos/react-ui-grid 0.8.4-main.b97322e → 0.8.4-main.bc2380dfbc

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/package.json CHANGED
@@ -1,57 +1,61 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-grid",
3
- "version": "0.8.4-main.b97322e",
3
+ "version": "0.8.4-main.bc2380dfbc",
4
4
  "description": "React component which manages a `dx-grid` Lit web component.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
- "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
11
+ "license": "FSL-1.1-Apache-2.0",
8
12
  "author": "DXOS.org",
9
- "sideEffects": true,
13
+ "sideEffects": false,
10
14
  "type": "module",
11
15
  "exports": {
12
16
  ".": {
17
+ "source": "./src/index.ts",
13
18
  "types": "./dist/types/src/index.d.ts",
14
- "browser": "./dist/lib/browser/index.mjs"
19
+ "browser": "./dist/lib/browser/index.mjs",
20
+ "node": "./dist/lib/browser/index.mjs"
15
21
  }
16
22
  },
17
23
  "types": "dist/types/src/index.d.ts",
18
- "typesVersions": {
19
- "*": {}
20
- },
21
24
  "files": [
22
25
  "dist",
23
26
  "src"
24
27
  ],
25
28
  "dependencies": {
26
- "@codemirror/autocomplete": "^6.18.1",
27
- "@codemirror/state": "^6.4.1",
28
- "@codemirror/view": "^6.34.1",
29
- "@lit/react": "^1.0.5",
30
- "@preact-signals/safe-react": "^0.9.0",
29
+ "@codemirror/autocomplete": "^6.19.0",
30
+ "@codemirror/state": "^6.5.2",
31
+ "@codemirror/view": "^6.38.5",
32
+ "@lit/react": "^1.0.8",
31
33
  "@radix-ui/react-context": "1.1.1",
32
34
  "@radix-ui/react-popper": "1.2.2",
33
35
  "@radix-ui/react-use-controllable-state": "1.1.0",
34
- "@dxos/lit-grid": "0.8.4-main.b97322e",
35
- "@dxos/util": "0.8.4-main.b97322e",
36
- "@dxos/react-ui-editor": "0.8.4-main.b97322e"
36
+ "@dxos/lit-grid": "0.8.4-main.bc2380dfbc",
37
+ "@dxos/util": "0.8.4-main.bc2380dfbc",
38
+ "@dxos/react-ui-editor": "0.8.4-main.bc2380dfbc",
39
+ "@dxos/ui-editor": "0.8.4-main.bc2380dfbc"
37
40
  },
38
41
  "devDependencies": {
39
- "@types/react": "~18.2.0",
40
- "@types/react-dom": "~18.2.0",
41
- "react": "~18.2.0",
42
- "react-dom": "~18.2.0",
43
- "vite": "5.4.7",
44
- "@dxos/random": "0.8.4-main.b97322e",
45
- "@dxos/react-ui": "0.8.4-main.b97322e",
46
- "@dxos/react-ui-searchlist": "0.8.4-main.b97322e",
47
- "@dxos/react-ui-theme": "0.8.4-main.b97322e",
48
- "@dxos/storybook-utils": "0.8.4-main.b97322e"
42
+ "@types/react": "~19.2.7",
43
+ "@types/react-dom": "~19.2.3",
44
+ "react": "~19.2.3",
45
+ "react-dom": "~19.2.3",
46
+ "vite": "^8.0.13",
47
+ "@dxos/random": "0.8.4-main.bc2380dfbc",
48
+ "@dxos/react-ui": "0.8.4-main.bc2380dfbc",
49
+ "@dxos/react-ui-list": "0.8.4-main.bc2380dfbc",
50
+ "@dxos/react-ui-search": "0.8.4-main.bc2380dfbc",
51
+ "@dxos/storybook-utils": "0.8.4-main.bc2380dfbc",
52
+ "@dxos/ui-theme": "0.8.4-main.bc2380dfbc"
49
53
  },
50
54
  "peerDependencies": {
51
- "react": "~18.2.0",
52
- "react-dom": "~18.2.0",
53
- "@dxos/react-ui": "0.8.4-main.b97322e",
54
- "@dxos/react-ui-theme": "0.8.4-main.b97322e"
55
+ "react": "~19.2.3",
56
+ "react-dom": "~19.2.3",
57
+ "@dxos/react-ui": "0.8.4-main.bc2380dfbc",
58
+ "@dxos/ui-theme": "0.8.4-main.bc2380dfbc"
55
59
  },
56
60
  "publishConfig": {
57
61
  "access": "public"
@@ -2,17 +2,15 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React, { useState } from 'react';
9
7
 
10
- import { withTheme } from '@dxos/storybook-utils';
8
+ import { withTheme } from '@dxos/react-ui/testing';
11
9
 
12
10
  import { CellEditor, type CellEditorProps, type EditorKeyEvent, editorKeys } from './CellEditor';
13
11
 
14
- const DefaultStory = (args: CellEditorProps) => {
15
- const [value, setValue] = useState(args.value || 'Edit me');
12
+ const DefaultStory = (props: CellEditorProps) => {
13
+ const [value, setValue] = useState(props.value || 'Edit me');
16
14
  const [lastAction, setLastAction] = useState<string>('');
17
15
 
18
16
  const handleBlur = (newValue?: string) => {
@@ -32,7 +30,7 @@ const DefaultStory = (args: CellEditorProps) => {
32
30
  };
33
31
 
34
32
  // Create an extension with editor keys
35
- const extension = args.extension || [
33
+ const extensions = props.extensions || [
36
34
  editorKeys({
37
35
  onClose: handleKeyEvent,
38
36
  onNav: (value, event) => {
@@ -52,8 +50,8 @@ const DefaultStory = (args: CellEditorProps) => {
52
50
  <div className='relative border border-separator h-[100px] w-[300px]'>
53
51
  <CellEditor
54
52
  value={value}
55
- extension={extension}
56
- autoFocus={args.autoFocus}
53
+ extensions={extensions}
54
+ autoFocus={props.autoFocus}
57
55
  onBlur={handleBlur}
58
56
  box={{
59
57
  insetInlineStart: 10,
@@ -68,19 +66,19 @@ const DefaultStory = (args: CellEditorProps) => {
68
66
  );
69
67
  };
70
68
 
71
- const meta: Meta<CellEditorProps> = {
69
+ const meta = {
72
70
  title: 'ui/react-ui-grid/CellEditor',
73
71
  component: CellEditor,
74
72
  render: DefaultStory,
75
- decorators: [withTheme],
73
+ decorators: [withTheme()],
76
74
  parameters: {
77
75
  layout: 'centered',
78
76
  },
79
- };
77
+ } satisfies Meta<typeof CellEditor>;
80
78
 
81
79
  export default meta;
82
80
 
83
- type Story = StoryObj<CellEditorProps>;
81
+ type Story = StoryObj<typeof meta>;
84
82
 
85
83
  export const Default: Story = {
86
84
  args: {
@@ -8,15 +8,14 @@ import { EditorView, keymap } from '@codemirror/view';
8
8
  import React, { type KeyboardEvent } from 'react';
9
9
 
10
10
  import { useThemeContext } from '@dxos/react-ui';
11
+ import { type UseTextEditorProps, useTextEditor } from '@dxos/react-ui-editor';
11
12
  import {
12
- type UseTextEditorProps,
13
+ type ThemeExtensionsOptions,
13
14
  createBasicExtensions,
14
15
  createThemeExtensions,
15
- preventNewline,
16
- useTextEditor,
17
- type ThemeExtensionsOptions,
18
- } from '@dxos/react-ui-editor';
19
- import { mx } from '@dxos/react-ui-theme';
16
+ filterChars,
17
+ } from '@dxos/ui-editor';
18
+ import { mx } from '@dxos/ui-theme';
20
19
 
21
20
  import { type GridEditBox } from '../Grid';
22
21
 
@@ -116,14 +115,14 @@ export const editorKeys = ({ onNav, onClose }: EditorKeysProps): Extension => {
116
115
 
117
116
  export type CellEditorProps = {
118
117
  value?: string;
119
- extension?: Extension;
118
+ extensions?: Extension;
120
119
  box?: GridEditBox;
121
120
  gridId?: string;
122
121
  onBlur?: EditorBlurHandler;
123
122
  } & Pick<UseTextEditorProps, 'autoFocus'> &
124
123
  Pick<ThemeExtensionsOptions, 'slots'>;
125
124
 
126
- export const CellEditor = ({ value, extension, autoFocus, onBlur, box, gridId, slots }: CellEditorProps) => {
125
+ export const CellEditor = ({ value, extensions, box, gridId, autoFocus, slots, onBlur }: CellEditorProps) => {
127
126
  const { themeMode } = useThemeContext();
128
127
  const { parentRef } = useTextEditor(() => {
129
128
  return {
@@ -131,13 +130,24 @@ export const CellEditor = ({ value, extension, autoFocus, onBlur, box, gridId, s
131
130
  initialValue: value,
132
131
  selection: { anchor: value?.length ?? 0 },
133
132
  extensions: [
134
- extension ?? [],
135
- preventNewline,
136
- EditorView.focusChangeEffect.of((state, focusing) => {
137
- if (!focusing) {
138
- onBlur?.(state.doc.toString());
139
- }
140
- return null;
133
+ extensions ?? [],
134
+ filterChars(/[\n\r]+/),
135
+ // Observe the underlying blur DOM event rather than `EditorView.focusChangeEffect`. The
136
+ // focus-change facet fires asynchronously (CodeMirror schedules it on a 10ms setTimeout),
137
+ // which means in React strict mode the destroy → blur of the first EditorView runs the
138
+ // callback after the second view has mounted — committing stale data and closing the
139
+ // editor on the user's first keystroke. Deferring via `queueMicrotask` runs the check
140
+ // *after* `view.destroy()` finishes its synchronous body (which calls `dom.remove()`),
141
+ // so `view.dom.isConnected === false` reliably distinguishes a programmatic teardown
142
+ // from a real user blur. Pass `undefined` on teardown so downstream handlers can consume
143
+ // any pending suppress-next-blur flag without committing stale data.
144
+ EditorView.domEventObservers({
145
+ blur: (_event, view) => {
146
+ const doc = view.state.doc.toString();
147
+ queueMicrotask(() => {
148
+ onBlur?.(view.dom.isConnected ? doc : undefined);
149
+ });
150
+ },
141
151
  }),
142
152
  createBasicExtensions({ lineWrapping: true }),
143
153
  createThemeExtensions({
@@ -145,22 +155,24 @@ export const CellEditor = ({ value, extension, autoFocus, onBlur, box, gridId, s
145
155
  slots: {
146
156
  editor: {
147
157
  className: mx(
148
- '!min-is-full !is-min !max-is-[--dx-grid-cell-editor-max-inline-size] !min-bs-full !max-bs-[--dx-grid-cell-editor-max-block-size]',
158
+ 'min-w-full! w-min! !max-w-(--dx-grid-cell-editor-max-w-size) min-h-full! !max-h-(--dx-grid-cell-editor-max-h-size)',
149
159
  slots?.editor?.className,
150
160
  ),
151
161
  },
152
162
  scroller: {
153
163
  className: mx(
154
- '!overflow-x-hidden !plb-[max(0,calc(var(--dx-grid-cell-editor-padding-block)-1px))] !pie-0 !pis-[--dx-grid-cell-editor-padding-inline]',
164
+ 'overflow-x-hidden! !py-[max(0,calc(var(--dx-grid-cell-editor-padding-block)-1px))] pe-0! !pl-(--dx-grid-cell-editor-padding-inline)',
155
165
  slots?.scroller?.className,
156
166
  ),
157
167
  },
158
- content: { className: mx('!break-normal', slots?.content?.className) },
168
+ content: {
169
+ className: mx('break-normal!', slots?.content?.className),
170
+ },
159
171
  },
160
172
  }),
161
173
  ],
162
174
  };
163
- }, [extension, autoFocus, value, onBlur, themeMode, slots]);
175
+ }, [extensions, autoFocus, value, onBlur, themeMode, slots]);
164
176
 
165
177
  return (
166
178
  <div
@@ -172,7 +184,7 @@ export const CellEditor = ({ value, extension, autoFocus, onBlur, box, gridId, s
172
184
  insetBlockStart: box?.insetBlockStart ?? '0px',
173
185
  minInlineSize: box?.inlineSize ?? '180px',
174
186
  minBlockSize: box?.blockSize ?? '30px',
175
- ...{ '--dx-gridCellWidth': `${box?.inlineSize ?? 200}px` },
187
+ ...{ '--dx-grid-cell-width': `${box?.inlineSize ?? 200}px` },
176
188
  }}
177
189
  {...(gridId && { 'data-grid': gridId })}
178
190
  />
@@ -4,16 +4,16 @@
4
4
 
5
5
  import React, { useCallback } from 'react';
6
6
 
7
+ import { type DxGridCellIndex, type GridScopedProps, useGridContext } from '../Grid';
7
8
  import { CellEditor, type CellEditorProps } from './CellEditor';
8
- import { type GridScopedProps, useGridContext, type DxGridCellIndex } from '../Grid';
9
9
 
10
10
  export type GridCellEditorProps = GridScopedProps<
11
- Pick<CellEditorProps, 'extension' | 'onBlur' | 'slots'> & {
11
+ Pick<CellEditorProps, 'extensions' | 'onBlur' | 'slots'> & {
12
12
  getCellContent: (index: DxGridCellIndex) => string | undefined;
13
13
  }
14
14
  >;
15
15
 
16
- export const GridCellEditor = ({ extension, getCellContent, onBlur, slots, __gridScope }: GridCellEditorProps) => {
16
+ export const GridCellEditor = ({ extensions, getCellContent, onBlur, slots, __gridScope }: GridCellEditorProps) => {
17
17
  const { id, editing, setEditing, editBox } = useGridContext('GridCellEditor', __gridScope);
18
18
 
19
19
  const handleBlur = useCallback(
@@ -30,7 +30,7 @@ export const GridCellEditor = ({ extension, getCellContent, onBlur, slots, __gri
30
30
  autoFocus
31
31
  box={editBox}
32
32
  onBlur={handleBlur}
33
- extension={extension}
33
+ extensions={extensions}
34
34
  gridId={id}
35
35
  slots={slots}
36
36
  />
@@ -2,20 +2,21 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React, { type MouseEvent, type MutableRefObject, useCallback, useRef, useState } from 'react';
9
7
 
10
8
  import { defaultRowSize } from '@dxos/lit-grid';
11
- import { faker } from '@dxos/random';
9
+ import { type DxGridPlaneCells } from '@dxos/lit-grid';
10
+ import { random } from '@dxos/random';
12
11
  import { DropdownMenu } from '@dxos/react-ui';
13
- import { PopoverCombobox, type PopoverComboboxRootProps } from '@dxos/react-ui-searchlist';
14
- import { withTheme } from '@dxos/storybook-utils';
12
+ import { toPlaneCellIndex } from '@dxos/react-ui-grid';
13
+ import { Combobox, type ComboboxRootProps } from '@dxos/react-ui-list';
14
+ import { useSearchListResults } from '@dxos/react-ui-search';
15
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
15
16
 
16
- import { Grid, type GridEditing, type GridContentProps, type GridRootProps } from './Grid';
17
+ import { Grid, type GridContentProps, type GridEditing, type GridRootProps } from './Grid';
17
18
 
18
- const storybookItems = faker.helpers.uniqueArray(faker.commerce.productName, 16);
19
+ const storybookItems = random.helpers.uniqueArray(random.commerce.productName, 16);
19
20
 
20
21
  type GridStoryProps = GridContentProps & Pick<GridRootProps, 'onEditingChange'>;
21
22
 
@@ -33,7 +34,7 @@ const GridStory = ({ initialCells, ...props }: GridStoryProps) => {
33
34
  // Multiselect
34
35
  const [popoverOpen, setPopoverOpen] = useState(false);
35
36
  const [multiSelectValue, setInternalMultiselectValue] = useState('');
36
- const setMultiselectValue = useCallback<NonNullable<PopoverComboboxRootProps['onValueChange']>>((nextValue) => {
37
+ const setMultiselectValue = useCallback<NonNullable<ComboboxRootProps['onValueChange']>>((nextValue) => {
37
38
  setInternalMultiselectValue(nextValue);
38
39
  setCells((cells) => {
39
40
  // TODO(burdon): How can we get the cell address to update?
@@ -64,7 +65,7 @@ const GridStory = ({ initialCells, ...props }: GridStoryProps) => {
64
65
  }, []);
65
66
 
66
67
  return (
67
- <div role='none' className='fixed inset-0 grid'>
68
+ <div className='contents'>
68
69
  <Grid.Root id='story' editing={editing} onEditingChange={handleEditingChange}>
69
70
  {/* TODO(burdon): Why is this property not just "cells" or "values" */}
70
71
  <Grid.Content {...props} initialCells={cells} onClick={handleClick} />
@@ -80,38 +81,75 @@ const GridStory = ({ initialCells, ...props }: GridStoryProps) => {
80
81
  </DropdownMenu.Root>
81
82
 
82
83
  {/* Multiselect */}
83
- <PopoverCombobox.Root
84
+ <Combobox.Root
84
85
  open={popoverOpen}
85
86
  onOpenChange={setPopoverOpen}
86
87
  value={multiSelectValue}
87
88
  onValueChange={setMultiselectValue}
88
89
  >
89
- <PopoverCombobox.VirtualTrigger virtualRef={triggerRef} />
90
- <PopoverCombobox.Content filter={(value, search) => (value.includes(search) ? 1 : 0)}>
91
- <PopoverCombobox.Input placeholder='Search...' />
92
- <PopoverCombobox.List>
93
- {storybookItems.map((value) => (
94
- <PopoverCombobox.Item key={value}>{value}</PopoverCombobox.Item>
95
- ))}
96
- </PopoverCombobox.List>
97
- <PopoverCombobox.Arrow />
98
- </PopoverCombobox.Content>
99
- </PopoverCombobox.Root>
90
+ <Combobox.VirtualTrigger virtualRef={triggerRef} />
91
+ <ComboboxContentWithFiltering />
92
+ </Combobox.Root>
100
93
  </div>
101
94
  );
102
95
  };
103
96
 
104
- const meta: Meta<GridStoryProps> = {
97
+ const ComboboxContentWithFiltering = () => {
98
+ const { results, query, handleSearch } = useSearchListResults({
99
+ items: storybookItems,
100
+ });
101
+
102
+ return (
103
+ <Combobox.Content>
104
+ <Combobox.Input placeholder='Search...' value={query} onValueChange={handleSearch} />
105
+ <Combobox.List>
106
+ {results.map((value) => (
107
+ <Combobox.Item key={value} value={value} label={value} />
108
+ ))}
109
+ </Combobox.List>
110
+ <Combobox.Arrow />
111
+ </Combobox.Content>
112
+ );
113
+ };
114
+
115
+ const meta = {
105
116
  title: 'ui/react-ui-grid/Grid',
106
117
  component: GridStory,
107
- decorators: [withTheme],
108
- parameters: { layout: 'fullscreen' },
109
- };
118
+ decorators: [withTheme(), withLayout({ layout: 'column' })],
119
+ parameters: {
120
+ layout: 'fullscreen',
121
+ },
122
+ } satisfies Meta<typeof GridStory>;
110
123
 
111
124
  export default meta;
112
125
 
113
126
  type Story = StoryObj<typeof meta>;
114
127
 
128
+ export const Default: Story = {};
129
+
130
+ /**
131
+ * Single focusable cell — for verifying focus-ring alignment with grid lines.
132
+ */
133
+ export const SingleCell: Story = {
134
+ args: {
135
+ id: 'story',
136
+ limitColumns: 1,
137
+ limitRows: 1,
138
+ columnDefault: { grid: { size: 200, resizeable: false } },
139
+ rowDefault: { grid: { size: 32, resizeable: false } },
140
+ initialCells: {
141
+ grid: {
142
+ '0,0': { value: 'Focus me' },
143
+ },
144
+ },
145
+ },
146
+ render: (args) => (
147
+ <div className='h-full grid place-items-center'>
148
+ <GridStory {...args} />
149
+ </div>
150
+ ),
151
+ };
152
+
115
153
  export const Basic: Story = {
116
154
  args: {
117
155
  id: 'story',
@@ -141,7 +179,7 @@ export const Basic: Story = {
141
179
  '1,1': {
142
180
  value: 'Demo decoration',
143
181
  accessoryHtml: `
144
- <button class="dx-button is-6 pli-0.5 min-bs-0 absolute inset-block-1 inline-end-1" data-story-action="menu">
182
+ <button class="dx-button w-6 px-0.5 min-h-0 absolute inset-y-1 right-1" data-story-action="menu">
145
183
  <svg><use href="/icons.svg#ph--arrow-right--regular"/></svg>
146
184
  </button>
147
185
  `,
@@ -158,26 +196,47 @@ export const Basic: Story = {
158
196
  },
159
197
  };
160
198
 
161
- // TODO(burdon): How to make single-column?
162
- export const SingleColumn: Story = {
199
+ const cellSize = 40;
200
+
201
+ // TODO(burdon): Calendar.
202
+ export const Calendar: Story = {
163
203
  args: {
164
204
  id: 'story',
165
- limitColumns: 1,
205
+ limitColumns: 7,
166
206
  columnDefault: {
167
207
  grid: {
168
- size: 180,
208
+ size: cellSize,
209
+ resizeable: false,
169
210
  },
170
211
  },
171
212
  rowDefault: {
172
213
  grid: {
173
- size: defaultRowSize,
214
+ size: cellSize,
174
215
  resizeable: false,
175
216
  },
176
217
  },
177
- columns: {
178
- grid: {
179
- 0: { size: 200 },
180
- },
218
+ getCells: (range, plane) => {
219
+ const cells: DxGridPlaneCells = {};
220
+ if (plane === 'grid') {
221
+ for (let col = range.start.col; col <= range.end.col; col++) {
222
+ for (let row = range.start.row; row <= range.end.row; row++) {
223
+ // TODO(burdon): Formatting changes when cell is selected.
224
+ cells[toPlaneCellIndex({ col, row })] = {
225
+ readonly: true,
226
+ accessoryHtml: '<div class="flex h-full w-full justify-center items-center overflow-hidden">0</div>',
227
+ className: '',
228
+ };
229
+ }
230
+ }
231
+ }
232
+ return cells;
181
233
  },
182
234
  },
235
+ render: (args) => (
236
+ <div className='h-full flex justify-center'>
237
+ <div className='h-full w-[288px] border-x border-separator'>
238
+ <GridStory {...args} />
239
+ </div>
240
+ </div>
241
+ ),
183
242
  };
package/src/Grid/Grid.tsx CHANGED
@@ -2,10 +2,8 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos/lit-grid/dx-grid.pcss';
6
-
7
- import { createComponent, type EventName } from '@lit/react';
8
- import { createContextScope, type Scope } from '@radix-ui/react-context';
5
+ import { type EventName, createComponent } from '@lit/react';
6
+ import { type Scope, createContextScope } from '@radix-ui/react-context';
9
7
  import { useControllableState } from '@radix-ui/react-use-controllable-state';
10
8
  import React, {
11
9
  type ComponentProps,
@@ -16,6 +14,7 @@ import React, {
16
14
  useState,
17
15
  } from 'react';
18
16
 
17
+ import '@dxos/lit-grid/dx-grid.pcss';
19
18
  import { type DxAxisResize, type DxEditRequest, type DxGridCellsSelect, DxGrid as NaturalDxGrid } from '@dxos/lit-grid';
20
19
 
21
20
  type DxGridElement = NaturalDxGrid;
@@ -63,20 +62,23 @@ const [createGridContext, createGridScope] = createContextScope(GRID_NAME, []);
63
62
  const [GridProvider, useGridContext] = createGridContext<GridContextValue>(GRID_NAME);
64
63
 
65
64
  type GridRootProps = PropsWithChildren<
66
- { id: string } & Partial<{
65
+ {
66
+ id: string;
67
+ } & Partial<{
67
68
  editing: GridEditing;
68
69
  defaultEditing: GridEditing;
69
70
  onEditingChange: (nextEditing: GridEditing) => void;
70
71
  }>
71
72
  >;
72
73
 
74
+ // TODO(burdon): Make headless.
73
75
  const GridRoot = ({
76
+ __gridScope,
77
+ children,
74
78
  id,
75
79
  editing: propsEditing,
76
80
  defaultEditing,
77
81
  onEditingChange,
78
- children,
79
- __gridScope,
80
82
  }: GridScopedProps<GridRootProps>) => {
81
83
  const [editing = null, setEditing] = useControllableState({
82
84
  prop: propsEditing,
@@ -102,13 +104,13 @@ const GridRoot = ({
102
104
 
103
105
  GridRoot.displayName = GRID_NAME;
104
106
 
107
+ const GRID_CONTENT_NAME = 'GridContent';
108
+
105
109
  type GridContentProps = Omit<ComponentProps<typeof DxGrid>, 'onEdit'> & {
106
110
  getCells?: NaturalDxGrid['getCells'];
107
111
  activeRefs?: string;
108
112
  };
109
113
 
110
- const GRID_CONTENT_NAME = 'GridContent';
111
-
112
114
  const GridContent = forwardRef<NaturalDxGrid, GridScopedProps<GridContentProps>>((props, forwardedRef) => {
113
115
  const { id, editing, setEditBox, setEditing } = useGridContext(GRID_CONTENT_NAME, props.__gridScope);
114
116
  const [dxGrid, setDxGridInternal] = useState<NaturalDxGrid | null>(null);
@@ -150,12 +152,12 @@ GridContent.displayName = GRID_CONTENT_NAME;
150
152
  // Fragments
151
153
  //
152
154
 
153
- // NOTE(Zan): These fragments add border to inline-end and block-end of the grid using pseudo-elements.
155
+ // NOTE(Zan): These fragments add border to w-end and h-end of the grid using pseudo-elements.
154
156
  // These are offset by 1px to avoid double borders in planks.
155
157
  const gridSeparatorInlineEnd =
156
- '[&>.dx-grid]:relative [&>.dx-grid]:after:absolute [&>.dx-grid]:after:inset-block-0 [&>.dx-grid]:after:-inline-end-px [&>.dx-grid]:after:is-px [&>.dx-grid]:after:bg-subduedSeparator';
158
+ '[&>.dx-grid]:relative [&>.dx-grid]:after:absolute [&>.dx-grid]:after:inset-y-0 [&>.dx-grid]:after:-right-px [&>.dx-grid]:after:w-px [&>.dx-grid]:after:bg-subdued-separator';
157
159
  const gridSeparatorBlockEnd =
158
- '[&>.dx-grid]:relative [&>.dx-grid]:before:absolute [&>.dx-grid]:before:inset-inline-0 [&>.dx-grid]:before:-block-end-px [&>.dx-grid]:before:bs-px [&>.dx-grid]:before:bg-subduedSeparator';
160
+ '[&>.dx-grid]:relative [&>.dx-grid]:before:absolute [&>.dx-grid]:before:inset-x-0 [&>.dx-grid]:before:-bottom-px [&>.dx-grid]:before:h-px [&>.dx-grid]:before:bg-subdued-separator';
159
161
 
160
162
  //
161
163
  // Exports
@@ -166,7 +168,7 @@ export const Grid = {
166
168
  Content: GridContent,
167
169
  };
168
170
 
169
- export { GridRoot, GridContent, useGridContext, createGridScope, gridSeparatorInlineEnd, gridSeparatorBlockEnd };
171
+ export { GridRoot, GridContent, createGridScope, gridSeparatorInlineEnd, gridSeparatorBlockEnd, useGridContext };
170
172
 
171
173
  export type { GridRootProps, GridContentProps, GridEditing, GridEditBox, GridScopedProps, DxGridElement };
172
174
 
@@ -178,6 +180,7 @@ export {
178
180
  toPlaneCellIndex,
179
181
  parseCellIndex,
180
182
  cellQuery,
183
+ DxEditRequest,
181
184
  } from '@dxos/lit-grid';
182
185
 
183
186
  export type {