@ornery/ui-grid-react 0.1.5 → 0.1.7
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/demo/main.tsx +1 -1
- package/demo/vite.config.ts +1 -1
- package/dist/UiGrid.d.ts +11 -0
- package/dist/UiGrid.d.ts.map +1 -0
- package/dist/gridStateMath.d.ts +8 -0
- package/dist/gridStateMath.d.ts.map +1 -0
- package/dist/index.d.ts +14 -155
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +699 -1063
- package/dist/index.mjs +594 -930
- package/dist/mountUiGrid.d.ts +4 -0
- package/dist/mountUiGrid.d.ts.map +1 -0
- package/dist/rustWasmGridEngine.d.ts +8 -0
- package/dist/rustWasmGridEngine.d.ts.map +1 -0
- package/dist/{index.d.mts → useGridState.d.ts} +6 -50
- package/dist/useGridState.d.ts.map +1 -0
- package/dist/useVirtualScroll.d.ts +20 -0
- package/dist/useVirtualScroll.d.ts.map +1 -0
- package/dist/vanillaAdapter.d.ts +14 -0
- package/dist/vanillaAdapter.d.ts.map +1 -0
- package/dist/virtualScrollMath.d.ts +17 -0
- package/dist/virtualScrollMath.d.ts.map +1 -0
- package/package.json +11 -11
- package/src/UiGrid.test.tsx +63 -25
- package/src/UiGrid.tsx +438 -187
- package/src/gridStateMath.test.ts +10 -8
- package/src/gridStateMath.ts +20 -7
- package/src/index.ts +21 -5
- package/src/mountUiGrid.tsx +10 -0
- package/src/rustWasmGridEngine.test.ts +4 -4
- package/src/rustWasmGridEngine.ts +7 -5
- package/src/ui-grid.css +200 -1
- package/src/useGridState.ts +56 -30
- package/src/useVirtualScroll.ts +2 -0
- package/src/vanillaAdapter.test.ts +33 -0
- package/src/vanillaAdapter.ts +36 -0
- package/tsconfig.build.json +6 -0
- package/tsconfig.dts.json +15 -0
- package/tsconfig.json +1 -1
- package/vitest.config.ts +1 -1
- package/CLAUDE.md +0 -283
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import type { GridColumnDef } from '@ornery/ui-grid';
|
|
2
|
+
import type { GridColumnDef } from '@ornery/ui-grid-core';
|
|
3
3
|
import {
|
|
4
4
|
buildGridTemplateColumns,
|
|
5
5
|
computeViewportHeightPx,
|
|
@@ -18,15 +18,17 @@ describe('gridStateMath', () => {
|
|
|
18
18
|
];
|
|
19
19
|
|
|
20
20
|
it('orders only visible columns by the provided column order', () => {
|
|
21
|
-
expect(
|
|
22
|
-
'customer',
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
]);
|
|
21
|
+
expect(
|
|
22
|
+
orderVisibleColumns(columns, ['customer', 'revenue', 'status', 'owner']).map(
|
|
23
|
+
(column) => column.name,
|
|
24
|
+
),
|
|
25
|
+
).toEqual(['customer', 'revenue', 'status']);
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
it('builds grid template columns deterministically', () => {
|
|
29
|
-
expect(
|
|
29
|
+
expect(
|
|
30
|
+
buildGridTemplateColumns(orderVisibleColumns(columns, ['status', 'revenue', 'customer'])),
|
|
31
|
+
).toBe('2fr 120px minmax(11rem, max-content)');
|
|
30
32
|
});
|
|
31
33
|
|
|
32
34
|
it('resolves benchmark iterations with a minimum of one', () => {
|
|
@@ -46,4 +48,4 @@ describe('gridStateMath', () => {
|
|
|
46
48
|
expect(computeViewportRows(undefined, undefined)).toBe(13);
|
|
47
49
|
expect(computeViewportRows(220, 44)).toBe(5);
|
|
48
50
|
});
|
|
49
|
-
});
|
|
51
|
+
});
|
package/src/gridStateMath.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import type { GridColumnDef } from '@ornery/ui-grid';
|
|
2
|
-
import { gridColumnWidth } from '@ornery/ui-grid';
|
|
1
|
+
import type { GridColumnDef } from '@ornery/ui-grid-core';
|
|
2
|
+
import { gridColumnWidth } from '@ornery/ui-grid-core';
|
|
3
3
|
|
|
4
|
-
export function orderVisibleColumns(
|
|
4
|
+
export function orderVisibleColumns(
|
|
5
|
+
columns: readonly GridColumnDef[],
|
|
6
|
+
order: readonly string[],
|
|
7
|
+
): GridColumnDef[] {
|
|
5
8
|
return [...columns]
|
|
6
9
|
.filter((column) => column.visible !== false)
|
|
7
10
|
.sort((left, right) => order.indexOf(left.name) - order.indexOf(right.name));
|
|
@@ -11,11 +14,18 @@ export function buildGridTemplateColumns(columns: readonly GridColumnDef[]): str
|
|
|
11
14
|
return columns.map((column) => gridColumnWidth(column)).join(' ');
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
export function resolveBenchmarkIterations(
|
|
17
|
+
export function resolveBenchmarkIterations(
|
|
18
|
+
iterations?: number,
|
|
19
|
+
configuredIterations?: number,
|
|
20
|
+
): number {
|
|
15
21
|
return Math.max(1, iterations ?? configuredIterations ?? 25);
|
|
16
22
|
}
|
|
17
23
|
|
|
18
|
-
export function formatPaginationSummary(
|
|
24
|
+
export function formatPaginationSummary(
|
|
25
|
+
totalItems: number,
|
|
26
|
+
firstRowIndex: number,
|
|
27
|
+
lastRowIndex: number,
|
|
28
|
+
): string {
|
|
19
29
|
if (totalItems === 0) {
|
|
20
30
|
return '0-0 of 0';
|
|
21
31
|
}
|
|
@@ -23,10 +33,13 @@ export function formatPaginationSummary(totalItems: number, firstRowIndex: numbe
|
|
|
23
33
|
return `${firstRowIndex + 1}-${lastRowIndex + 1} of ${totalItems}`;
|
|
24
34
|
}
|
|
25
35
|
|
|
26
|
-
export function computeViewportHeightPx(
|
|
36
|
+
export function computeViewportHeightPx(
|
|
37
|
+
viewportHeight?: number,
|
|
38
|
+
autoViewportHeight?: number | null,
|
|
39
|
+
): string {
|
|
27
40
|
return `${viewportHeight ?? autoViewportHeight ?? 560}px`;
|
|
28
41
|
}
|
|
29
42
|
|
|
30
43
|
export function computeViewportRows(viewportHeight?: number, rowHeight?: number): number {
|
|
31
44
|
return Math.max(1, Math.ceil((viewportHeight ?? 560) / (rowHeight ?? 44)));
|
|
32
|
-
}
|
|
45
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
export { UiGrid } from './UiGrid';
|
|
2
2
|
export type { UiGridProps } from './UiGrid';
|
|
3
|
+
export { mountUiGrid } from './mountUiGrid';
|
|
4
|
+
export {
|
|
5
|
+
mountUiGridCustomElement,
|
|
6
|
+
type MountUiGridCustomElementOptions,
|
|
7
|
+
type MountedUiGridCustomElement,
|
|
8
|
+
} from './vanillaAdapter';
|
|
3
9
|
export { useGridState } from './useGridState';
|
|
4
10
|
export type { UseGridStateResult } from './useGridState';
|
|
5
11
|
export { useVirtualScroll } from './useVirtualScroll';
|
|
6
12
|
export type { UseVirtualScrollOptions, UseVirtualScrollResult } from './useVirtualScroll';
|
|
7
|
-
export {
|
|
8
|
-
|
|
13
|
+
export {
|
|
14
|
+
orderVisibleColumns,
|
|
15
|
+
buildGridTemplateColumns,
|
|
16
|
+
resolveBenchmarkIterations,
|
|
17
|
+
formatPaginationSummary,
|
|
18
|
+
computeViewportHeightPx,
|
|
19
|
+
computeViewportRows,
|
|
20
|
+
} from './gridStateMath';
|
|
21
|
+
export {
|
|
22
|
+
enableReactUiGridWasmEngine,
|
|
23
|
+
registerReactUiGridWasmEngineFromModule,
|
|
24
|
+
} from './rustWasmGridEngine';
|
|
9
25
|
|
|
10
26
|
export type {
|
|
11
27
|
GridOptions,
|
|
@@ -19,7 +35,7 @@ export type {
|
|
|
19
35
|
GridBenchmarkResult,
|
|
20
36
|
GridSavedState,
|
|
21
37
|
SortState,
|
|
22
|
-
} from '@ornery/ui-grid';
|
|
38
|
+
} from '@ornery/ui-grid-core';
|
|
23
39
|
|
|
24
|
-
export type { UiGridApi } from '@ornery/ui-grid';
|
|
25
|
-
export { DEFAULT_GRID_LABELS } from '@ornery/ui-grid';
|
|
40
|
+
export type { UiGridApi } from '@ornery/ui-grid-core';
|
|
41
|
+
export { DEFAULT_GRID_LABELS } from '@ornery/ui-grid-core';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createRoot, type Root } from 'react-dom/client';
|
|
3
|
+
|
|
4
|
+
import { UiGrid, type UiGridProps } from './UiGrid';
|
|
5
|
+
|
|
6
|
+
export function mountUiGrid(container: Element | DocumentFragment, props: UiGridProps): Root {
|
|
7
|
+
const root = createRoot(container);
|
|
8
|
+
root.render(React.createElement(UiGrid, props));
|
|
9
|
+
return root;
|
|
10
|
+
}
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
activeGridEngineBackend,
|
|
4
4
|
clearRustWasmGridEngine,
|
|
5
5
|
defaultGridEngine,
|
|
6
|
-
} from '@ornery/ui-grid';
|
|
6
|
+
} from '@ornery/ui-grid-core';
|
|
7
7
|
import { registerReactUiGridWasmEngineFromModule } from './rustWasmGridEngine';
|
|
8
|
-
import { SORT_DIRECTIONS } from '@ornery/ui-grid';
|
|
9
|
-
import type { BuildGridPipelineContext, PipelineResult } from '@ornery/ui-grid';
|
|
8
|
+
import { SORT_DIRECTIONS } from '@ornery/ui-grid-core';
|
|
9
|
+
import type { BuildGridPipelineContext, PipelineResult } from '@ornery/ui-grid-core';
|
|
10
10
|
|
|
11
11
|
function createContext(): BuildGridPipelineContext {
|
|
12
12
|
return {
|
|
@@ -53,4 +53,4 @@ describe('rustWasmGridEngine', () => {
|
|
|
53
53
|
expect(defaultGridEngine.buildPipeline(createContext())).toBe(sentinel);
|
|
54
54
|
expect(activeGridEngineBackend()).toBe('rust-wasm');
|
|
55
55
|
});
|
|
56
|
-
});
|
|
56
|
+
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { BuildGridPipelineContext, PipelineResult } from '@ornery/ui-grid';
|
|
2
|
-
import { registerRustWasmGridEngine } from '@ornery/ui-grid';
|
|
1
|
+
import type { BuildGridPipelineContext, PipelineResult } from '@ornery/ui-grid-core';
|
|
2
|
+
import { registerRustWasmGridEngine } from '@ornery/ui-grid-core';
|
|
3
3
|
|
|
4
|
-
const uiGridWasmModulePath = '../../../dist/ui-grid-wasm/ui_grid_wasm.js';
|
|
4
|
+
const uiGridWasmModulePath = '../../../dist/ui-grid-wasm-web/ui_grid_wasm.js';
|
|
5
|
+
const uiGridWasmBinaryPath = '/dist/ui-grid-wasm-web/ui_grid_wasm_bg.wasm';
|
|
5
6
|
|
|
6
7
|
type UiGridWasmModule = {
|
|
7
8
|
build_pipeline_js(context: unknown): PipelineResult;
|
|
@@ -11,11 +12,12 @@ export function registerReactUiGridWasmEngineFromModule(module: UiGridWasmModule
|
|
|
11
12
|
registerRustWasmGridEngine({
|
|
12
13
|
buildPipeline(context: BuildGridPipelineContext): PipelineResult {
|
|
13
14
|
return module.build_pipeline_js(context);
|
|
14
|
-
}
|
|
15
|
+
},
|
|
15
16
|
});
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export async function enableReactUiGridWasmEngine(): Promise<void> {
|
|
19
20
|
const module = await import(/* @vite-ignore */ uiGridWasmModulePath);
|
|
21
|
+
await module.default(uiGridWasmBinaryPath);
|
|
20
22
|
registerReactUiGridWasmEngineFromModule(module);
|
|
21
|
-
}
|
|
23
|
+
}
|
package/src/ui-grid.css
CHANGED
|
@@ -20,6 +20,26 @@
|
|
|
20
20
|
--ui-grid-status-enterprise-color: var(--app-ui-grid-status-enterprise-color, #115e59);
|
|
21
21
|
--ui-grid-status-pilot-bg: var(--app-ui-grid-status-pilot-bg, rgba(234, 88, 12, 0.14));
|
|
22
22
|
--ui-grid-status-pilot-color: var(--app-ui-grid-status-pilot-color, #c2410c);
|
|
23
|
+
--ui-grid-pin-menu-open-z-index: var(--app-ui-grid-pin-menu-open-z-index, 8);
|
|
24
|
+
--ui-grid-pin-menu-z-index: var(--app-ui-grid-pin-menu-z-index, 20);
|
|
25
|
+
--ui-grid-pin-menu-gap: var(--app-ui-grid-pin-menu-gap, 0.25rem);
|
|
26
|
+
--ui-grid-pin-menu-padding: var(--app-ui-grid-pin-menu-padding, 0.25rem);
|
|
27
|
+
--ui-grid-pin-menu-radius: var(--app-ui-grid-pin-menu-radius, 999px);
|
|
28
|
+
--ui-grid-pin-menu-shadow: var(
|
|
29
|
+
--app-ui-grid-pin-menu-shadow,
|
|
30
|
+
0 10px 24px color-mix(in srgb, var(--ui-grid-cell-color) 10%, transparent)
|
|
31
|
+
);
|
|
32
|
+
--ui-grid-pin-menu-action-size: var(--app-ui-grid-pin-menu-action-size, 1.75rem);
|
|
33
|
+
--ui-grid-pin-control-collapsed-size: var(--app-ui-grid-pin-control-collapsed-size, 1px);
|
|
34
|
+
--ui-grid-pin-control-transition-duration: var(
|
|
35
|
+
--app-ui-grid-pin-control-transition-duration,
|
|
36
|
+
160ms
|
|
37
|
+
);
|
|
38
|
+
--ui-grid-pin-control-transition-easing: var(
|
|
39
|
+
--app-ui-grid-pin-control-transition-easing,
|
|
40
|
+
cubic-bezier(0.22, 1, 0.36, 1)
|
|
41
|
+
);
|
|
42
|
+
--ui-grid-pin-menu-scale-closed: var(--app-ui-grid-pin-menu-scale-closed, 0.72);
|
|
23
43
|
display: block;
|
|
24
44
|
color: var(--ui-grid-cell-color);
|
|
25
45
|
}
|
|
@@ -39,12 +59,18 @@
|
|
|
39
59
|
.grid-hero {
|
|
40
60
|
display: flex;
|
|
41
61
|
justify-content: space-between;
|
|
62
|
+
flex-wrap: wrap;
|
|
42
63
|
gap: 1.5rem;
|
|
43
|
-
align-items:
|
|
64
|
+
align-items: flex-start;
|
|
44
65
|
padding: 1rem 0;
|
|
45
66
|
color: var(--ui-grid-cell-color);
|
|
46
67
|
}
|
|
47
68
|
|
|
69
|
+
.grid-hero > :first-child {
|
|
70
|
+
flex: 1 1 28rem;
|
|
71
|
+
min-width: min(100%, 20rem);
|
|
72
|
+
}
|
|
73
|
+
|
|
48
74
|
.ui-grid-host .eyebrow {
|
|
49
75
|
margin: 0 0 0.5rem;
|
|
50
76
|
text-transform: uppercase;
|
|
@@ -70,6 +96,9 @@
|
|
|
70
96
|
gap: 1rem;
|
|
71
97
|
align-items: center;
|
|
72
98
|
flex-wrap: wrap;
|
|
99
|
+
flex: 0 1 auto;
|
|
100
|
+
margin-left: auto;
|
|
101
|
+
justify-content: flex-end;
|
|
73
102
|
}
|
|
74
103
|
|
|
75
104
|
.action {
|
|
@@ -168,6 +197,22 @@
|
|
|
168
197
|
.filter-grid,
|
|
169
198
|
.body-grid {
|
|
170
199
|
display: grid;
|
|
200
|
+
width: max-content;
|
|
201
|
+
min-width: 100%;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.header-grid,
|
|
205
|
+
.filter-grid {
|
|
206
|
+
position: sticky;
|
|
207
|
+
z-index: 3;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.header-grid {
|
|
211
|
+
top: 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.filter-grid {
|
|
215
|
+
top: var(--ui-grid-header-sticky-top, 0px);
|
|
171
216
|
}
|
|
172
217
|
|
|
173
218
|
.header-cell,
|
|
@@ -188,12 +233,36 @@
|
|
|
188
233
|
align-items: center;
|
|
189
234
|
}
|
|
190
235
|
|
|
236
|
+
.header-cell[draggable='true'] {
|
|
237
|
+
cursor: grab;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.header-cell.is-dragging {
|
|
241
|
+
opacity: 0.6;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.header-cell.is-drag-target {
|
|
245
|
+
background: color-mix(in srgb, var(--ui-grid-accent) 9%, var(--ui-grid-header-background));
|
|
246
|
+
border: 1px dashed color-mix(in srgb, var(--ui-grid-accent) 45%, var(--ui-grid-border-color));
|
|
247
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ui-grid-accent) 18%, transparent);
|
|
248
|
+
}
|
|
249
|
+
|
|
191
250
|
.header-cell.is-active {
|
|
192
251
|
background: color-mix(in srgb, var(--ui-grid-accent) 8%, var(--ui-grid-header-background));
|
|
193
252
|
}
|
|
194
253
|
|
|
254
|
+
.header-cell.is-pin-menu-open {
|
|
255
|
+
z-index: var(--ui-grid-pin-menu-open-z-index);
|
|
256
|
+
}
|
|
257
|
+
|
|
195
258
|
.header-label {
|
|
196
259
|
min-width: 0;
|
|
260
|
+
display: block;
|
|
261
|
+
line-height: 1.25;
|
|
262
|
+
white-space: nowrap;
|
|
263
|
+
overflow: hidden;
|
|
264
|
+
text-overflow: clip;
|
|
265
|
+
hyphens: none;
|
|
197
266
|
}
|
|
198
267
|
|
|
199
268
|
.header-actions {
|
|
@@ -201,6 +270,8 @@
|
|
|
201
270
|
align-items: center;
|
|
202
271
|
gap: 0.4rem;
|
|
203
272
|
justify-self: end;
|
|
273
|
+
flex-wrap: nowrap;
|
|
274
|
+
flex-shrink: 0;
|
|
204
275
|
}
|
|
205
276
|
|
|
206
277
|
.header-action,
|
|
@@ -250,14 +321,131 @@
|
|
|
250
321
|
color: var(--ui-grid-accent);
|
|
251
322
|
}
|
|
252
323
|
|
|
324
|
+
.pin-control {
|
|
325
|
+
position: relative;
|
|
326
|
+
display: inline-grid;
|
|
327
|
+
place-items: center;
|
|
328
|
+
justify-items: center;
|
|
329
|
+
min-width: 2rem;
|
|
330
|
+
overflow: hidden;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.pin-trigger {
|
|
334
|
+
grid-area: 1 / 1;
|
|
335
|
+
transform-origin: center;
|
|
336
|
+
transition:
|
|
337
|
+
width var(--ui-grid-pin-control-transition-duration)
|
|
338
|
+
var(--ui-grid-pin-control-transition-easing),
|
|
339
|
+
height var(--ui-grid-pin-control-transition-duration)
|
|
340
|
+
var(--ui-grid-pin-control-transition-easing),
|
|
341
|
+
opacity var(--ui-grid-pin-control-transition-duration)
|
|
342
|
+
var(--ui-grid-pin-control-transition-easing),
|
|
343
|
+
transform var(--ui-grid-pin-control-transition-duration)
|
|
344
|
+
var(--ui-grid-pin-control-transition-easing),
|
|
345
|
+
border-color var(--ui-grid-pin-control-transition-duration)
|
|
346
|
+
var(--ui-grid-pin-control-transition-easing),
|
|
347
|
+
background-color var(--ui-grid-pin-control-transition-duration)
|
|
348
|
+
var(--ui-grid-pin-control-transition-easing),
|
|
349
|
+
color var(--ui-grid-pin-control-transition-duration)
|
|
350
|
+
var(--ui-grid-pin-control-transition-easing);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.pin-menu {
|
|
354
|
+
grid-area: 1 / 1;
|
|
355
|
+
z-index: var(--ui-grid-pin-menu-z-index);
|
|
356
|
+
display: grid;
|
|
357
|
+
grid-auto-rows: var(--ui-grid-pin-menu-action-size);
|
|
358
|
+
gap: var(--ui-grid-pin-menu-gap);
|
|
359
|
+
padding: var(--ui-grid-pin-menu-padding);
|
|
360
|
+
border: 1px solid var(--ui-grid-border-color);
|
|
361
|
+
border-radius: var(--ui-grid-pin-menu-radius);
|
|
362
|
+
background: var(--ui-grid-surface);
|
|
363
|
+
box-shadow: var(--ui-grid-pin-menu-shadow);
|
|
364
|
+
justify-items: center;
|
|
365
|
+
align-items: center;
|
|
366
|
+
width: 2rem;
|
|
367
|
+
max-height: var(--ui-grid-pin-control-collapsed-size);
|
|
368
|
+
opacity: 0;
|
|
369
|
+
pointer-events: none;
|
|
370
|
+
transform: scale(var(--ui-grid-pin-menu-scale-closed));
|
|
371
|
+
transform-origin: center;
|
|
372
|
+
transition:
|
|
373
|
+
max-height var(--ui-grid-pin-control-transition-duration)
|
|
374
|
+
var(--ui-grid-pin-control-transition-easing),
|
|
375
|
+
opacity var(--ui-grid-pin-control-transition-duration)
|
|
376
|
+
var(--ui-grid-pin-control-transition-easing),
|
|
377
|
+
transform var(--ui-grid-pin-control-transition-duration)
|
|
378
|
+
var(--ui-grid-pin-control-transition-easing),
|
|
379
|
+
padding var(--ui-grid-pin-control-transition-duration)
|
|
380
|
+
var(--ui-grid-pin-control-transition-easing),
|
|
381
|
+
border-color var(--ui-grid-pin-control-transition-duration)
|
|
382
|
+
var(--ui-grid-pin-control-transition-easing),
|
|
383
|
+
box-shadow var(--ui-grid-pin-control-transition-duration)
|
|
384
|
+
var(--ui-grid-pin-control-transition-easing);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.pin-control-open {
|
|
388
|
+
align-items: start;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.pin-control-open .pin-trigger {
|
|
392
|
+
width: var(--ui-grid-pin-control-collapsed-size);
|
|
393
|
+
height: var(--ui-grid-pin-control-collapsed-size);
|
|
394
|
+
opacity: 0;
|
|
395
|
+
transform: scale(0.72);
|
|
396
|
+
pointer-events: none;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.pin-control-open .pin-menu {
|
|
400
|
+
max-height: calc(
|
|
401
|
+
(var(--ui-grid-pin-menu-action-size) * 2) + var(--ui-grid-pin-menu-gap) +
|
|
402
|
+
(var(--ui-grid-pin-menu-padding) * 2)
|
|
403
|
+
);
|
|
404
|
+
opacity: 1;
|
|
405
|
+
pointer-events: auto;
|
|
406
|
+
transform: scale(1);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.pin-menu-action {
|
|
410
|
+
display: inline-flex;
|
|
411
|
+
align-items: center;
|
|
412
|
+
justify-content: center;
|
|
413
|
+
width: var(--ui-grid-pin-menu-action-size);
|
|
414
|
+
height: var(--ui-grid-pin-menu-action-size);
|
|
415
|
+
border: 0;
|
|
416
|
+
border-radius: 999px;
|
|
417
|
+
background: transparent;
|
|
418
|
+
color: var(--ui-grid-cell-color);
|
|
419
|
+
cursor: pointer;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.pin-menu-action:hover,
|
|
423
|
+
.pin-menu-action:focus-visible {
|
|
424
|
+
outline: 0;
|
|
425
|
+
background: color-mix(in srgb, var(--ui-grid-accent) 12%, var(--ui-grid-surface));
|
|
426
|
+
color: var(--ui-grid-accent);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.pin-menu-action svg {
|
|
430
|
+
width: 1rem;
|
|
431
|
+
height: 1rem;
|
|
432
|
+
fill: currentColor;
|
|
433
|
+
}
|
|
434
|
+
|
|
253
435
|
.filter-cell {
|
|
254
436
|
padding: 0.75rem 1rem 1rem;
|
|
255
437
|
border-bottom: 1px solid var(--ui-grid-border-color);
|
|
256
438
|
background: var(--ui-grid-header-background);
|
|
439
|
+
overflow: hidden;
|
|
257
440
|
}
|
|
258
441
|
|
|
259
442
|
.filter-cell input {
|
|
443
|
+
display: block;
|
|
260
444
|
width: 100%;
|
|
445
|
+
min-width: 0;
|
|
446
|
+
max-width: 100%;
|
|
447
|
+
min-inline-size: 0;
|
|
448
|
+
max-inline-size: 100%;
|
|
261
449
|
border: 1px solid var(--ui-grid-border-color);
|
|
262
450
|
border-radius: var(--ui-grid-radius);
|
|
263
451
|
background: var(--ui-grid-surface);
|
|
@@ -390,6 +578,17 @@
|
|
|
390
578
|
width: 100%;
|
|
391
579
|
}
|
|
392
580
|
|
|
581
|
+
.grid-virtual-spacer {
|
|
582
|
+
position: relative;
|
|
583
|
+
width: max-content;
|
|
584
|
+
min-width: 100%;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
.grid-virtual-body {
|
|
588
|
+
position: absolute;
|
|
589
|
+
left: 0;
|
|
590
|
+
}
|
|
591
|
+
|
|
393
592
|
.body-cell.align-center {
|
|
394
593
|
text-align: center;
|
|
395
594
|
}
|
package/src/useGridState.ts
CHANGED
|
@@ -88,19 +88,10 @@ import {
|
|
|
88
88
|
FEATURE_AUTO_RESIZE,
|
|
89
89
|
FEATURE_SAVE_STATE,
|
|
90
90
|
FEATURE_PINNING,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
ExpandableItem,
|
|
96
|
-
RowItem,
|
|
97
|
-
PipelineResult,
|
|
98
|
-
GridInfiniteScrollState,
|
|
99
|
-
GridMoveDirection,
|
|
100
|
-
GridCellTemplateContext,
|
|
101
|
-
GridExpandableTemplateContext,
|
|
102
|
-
} from '@ornery/ui-grid';
|
|
103
|
-
import {
|
|
91
|
+
buildInitialPinnedState,
|
|
92
|
+
computePinnedOffset,
|
|
93
|
+
isColumnPinnable,
|
|
94
|
+
isPinningEnabled,
|
|
104
95
|
applyGridSortStateCommand,
|
|
105
96
|
updateGridFilterCommand,
|
|
106
97
|
clearGridFiltersCommand,
|
|
@@ -127,16 +118,6 @@ import {
|
|
|
127
118
|
setGridInfiniteScrollDirectionsCommand,
|
|
128
119
|
restoreGridStateCommand,
|
|
129
120
|
pinGridColumnCommand,
|
|
130
|
-
} from '../../ui-grid/src/lib/grid/ui-grid.commands';
|
|
131
|
-
import {
|
|
132
|
-
buildInitialPinnedState,
|
|
133
|
-
computePinnedOffset,
|
|
134
|
-
isColumnPinnable,
|
|
135
|
-
isPinningEnabled,
|
|
136
|
-
PinDirection,
|
|
137
|
-
PinnedColumnState,
|
|
138
|
-
} from '../../ui-grid/src/lib/grid/grid.core';
|
|
139
|
-
import {
|
|
140
121
|
raiseGridRenderingComplete,
|
|
141
122
|
raiseGridRowsRendered,
|
|
142
123
|
raiseGridRowsVisibleChanged,
|
|
@@ -145,8 +126,22 @@ import {
|
|
|
145
126
|
raiseGridScrollBegin,
|
|
146
127
|
raiseGridScrollEnd,
|
|
147
128
|
raiseGridBenchmarkComplete,
|
|
148
|
-
|
|
149
|
-
|
|
129
|
+
downloadGridCsvFile,
|
|
130
|
+
observeGridHostSize,
|
|
131
|
+
} from '@ornery/ui-grid-core';
|
|
132
|
+
import type {
|
|
133
|
+
DisplayItem,
|
|
134
|
+
GroupItem,
|
|
135
|
+
ExpandableItem,
|
|
136
|
+
RowItem,
|
|
137
|
+
PipelineResult,
|
|
138
|
+
GridInfiniteScrollState,
|
|
139
|
+
GridMoveDirection,
|
|
140
|
+
GridCellTemplateContext,
|
|
141
|
+
GridExpandableTemplateContext,
|
|
142
|
+
PinDirection,
|
|
143
|
+
PinnedColumnState,
|
|
144
|
+
} from '@ornery/ui-grid-core';
|
|
150
145
|
|
|
151
146
|
function escapeCssSelectorValue(value: string): string {
|
|
152
147
|
const nativeEscape = globalThis.CSS?.escape;
|
|
@@ -167,9 +162,7 @@ function escapeCssSelectorValue(value: string): string {
|
|
|
167
162
|
const isControlCharacter = (codePoint >= 0x0001 && codePoint <= 0x001f) || codePoint === 0x007f;
|
|
168
163
|
const startsWithDigit = index === 0 && codePoint >= 0x0030 && codePoint <= 0x0039;
|
|
169
164
|
const secondCharDigitAfterHyphen =
|
|
170
|
-
index === 1 &&
|
|
171
|
-
codePoint >= 0x0030 && codePoint <= 0x0039 &&
|
|
172
|
-
value.charCodeAt(0) === 0x002d;
|
|
165
|
+
index === 1 && codePoint >= 0x0030 && codePoint <= 0x0039 && value.charCodeAt(0) === 0x002d;
|
|
173
166
|
|
|
174
167
|
if (isControlCharacter || startsWithDigit || secondCharDigitAfterHyphen) {
|
|
175
168
|
output += `\\${codePoint.toString(16)} `;
|
|
@@ -313,6 +306,7 @@ export interface UseGridStateResult {
|
|
|
313
306
|
toggleRowExpansion: (row: GridRow, event?: React.MouseEvent) => void;
|
|
314
307
|
toggleTreeRow: (row: GridRow, event?: React.MouseEvent) => void;
|
|
315
308
|
moveColumn: (fromIndex: number, toIndex: number) => void;
|
|
309
|
+
moveVisibleColumn: (columnName: string, targetColumnName: string) => void;
|
|
316
310
|
nextPage: () => void;
|
|
317
311
|
previousPage: () => void;
|
|
318
312
|
onPageSizeChange: (value: string) => void;
|
|
@@ -398,8 +392,27 @@ export function useGridState(
|
|
|
398
392
|
const rowSize = options.rowHeight ?? 44;
|
|
399
393
|
|
|
400
394
|
const visibleColumns = useMemo(() => {
|
|
401
|
-
|
|
402
|
-
|
|
395
|
+
const orderedColumns = orderVisibleColumns(options.columnDefs, columnOrder);
|
|
396
|
+
const pinnedEntries = Object.entries(pinnedColumns);
|
|
397
|
+
if (pinnedEntries.length === 0) {
|
|
398
|
+
return orderedColumns;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const columnByName = new Map(orderedColumns.map((column) => [column.name, column]));
|
|
402
|
+
const pinnedLeft = pinnedEntries
|
|
403
|
+
.filter(([, direction]) => direction === 'left')
|
|
404
|
+
.map(([columnName]) => columnByName.get(columnName))
|
|
405
|
+
.filter((column): column is GridColumnDef => column !== undefined);
|
|
406
|
+
const pinnedRight = pinnedEntries
|
|
407
|
+
.filter(([, direction]) => direction === 'right')
|
|
408
|
+
.map(([columnName]) => columnByName.get(columnName))
|
|
409
|
+
.filter((column): column is GridColumnDef => column !== undefined);
|
|
410
|
+
const centerColumns = orderedColumns.filter(
|
|
411
|
+
(column) => pinnedColumns[column.name] === undefined,
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
return [...pinnedLeft, ...centerColumns, ...pinnedRight];
|
|
415
|
+
}, [options.columnDefs, columnOrder, pinnedColumns]);
|
|
403
416
|
|
|
404
417
|
const visibleColumnsRef = useRef(visibleColumns);
|
|
405
418
|
visibleColumnsRef.current = visibleColumns;
|
|
@@ -1559,6 +1572,18 @@ export function useGridState(
|
|
|
1559
1572
|
);
|
|
1560
1573
|
}, []);
|
|
1561
1574
|
|
|
1575
|
+
const moveVisibleColumnFn = useCallback((columnName: string, targetColumnName: string): void => {
|
|
1576
|
+
moveGridVisibleColumnCommand(
|
|
1577
|
+
gridApiRef.current!,
|
|
1578
|
+
FEATURE_COLUMN_MOVING && optionsRef.current.enableColumnMoving === true,
|
|
1579
|
+
columnOrderRef.current,
|
|
1580
|
+
visibleColumnsRef.current.map((column) => column.name),
|
|
1581
|
+
columnName,
|
|
1582
|
+
targetColumnName,
|
|
1583
|
+
(order) => setColumnOrder(order),
|
|
1584
|
+
);
|
|
1585
|
+
}, []);
|
|
1586
|
+
|
|
1562
1587
|
const nextPageFn = useCallback((): void => {
|
|
1563
1588
|
seekPageFn(getCurrentPageValueFn() + 1);
|
|
1564
1589
|
}, [seekPageFn, getCurrentPageValueFn]);
|
|
@@ -1706,6 +1731,7 @@ export function useGridState(
|
|
|
1706
1731
|
toggleRowExpansion: toggleRowExpansionFn,
|
|
1707
1732
|
toggleTreeRow: toggleTreeRowFn,
|
|
1708
1733
|
moveColumn: moveColumnFn,
|
|
1734
|
+
moveVisibleColumn: moveVisibleColumnFn,
|
|
1709
1735
|
nextPage: nextPageFn,
|
|
1710
1736
|
previousPage: previousPageFn,
|
|
1711
1737
|
onPageSizeChange: onPageSizeChangeFn,
|
package/src/useVirtualScroll.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface UseVirtualScrollResult {
|
|
|
13
13
|
totalHeight: number;
|
|
14
14
|
offsetY: number;
|
|
15
15
|
onScroll: (event: React.UIEvent<HTMLDivElement>) => void;
|
|
16
|
+
setScrollTop: (scrollTop: number) => void;
|
|
16
17
|
viewportRef: React.RefObject<HTMLDivElement | null>;
|
|
17
18
|
scrollTop: number;
|
|
18
19
|
}
|
|
@@ -39,6 +40,7 @@ export function useVirtualScroll(options: UseVirtualScrollOptions): UseVirtualSc
|
|
|
39
40
|
totalHeight: virtualWindow.totalHeight,
|
|
40
41
|
offsetY: virtualWindow.offsetY,
|
|
41
42
|
onScroll,
|
|
43
|
+
setScrollTop,
|
|
42
44
|
viewportRef,
|
|
43
45
|
scrollTop,
|
|
44
46
|
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { mountUiGridCustomElement } from './vanillaAdapter';
|
|
4
|
+
|
|
5
|
+
describe('mountUiGridCustomElement', () => {
|
|
6
|
+
it('mounts a custom element and applies grid options', async () => {
|
|
7
|
+
const host = document.createElement('div');
|
|
8
|
+
const ensureDefined = vi.fn(async (tagName: string) => {
|
|
9
|
+
if (!customElements.get(tagName)) {
|
|
10
|
+
customElements.define(tagName, class extends HTMLElement {});
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const options = {
|
|
15
|
+
id: 'react-adapter-grid',
|
|
16
|
+
data: [{ name: 'Alice' }],
|
|
17
|
+
columnDefs: [{ name: 'name' }],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const mounted = await mountUiGridCustomElement(host, {
|
|
21
|
+
options,
|
|
22
|
+
tagName: 'ui-grid-react-adapter-test',
|
|
23
|
+
ensureDefined,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
expect(ensureDefined).toHaveBeenCalledWith('ui-grid-react-adapter-test');
|
|
27
|
+
expect(host.firstElementChild).toBe(mounted.element);
|
|
28
|
+
expect(mounted.element.options).toBe(options);
|
|
29
|
+
|
|
30
|
+
mounted.unmount();
|
|
31
|
+
expect(host.childElementCount).toBe(0);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { GridOptions } from '@ornery/ui-grid-core';
|
|
2
|
+
|
|
3
|
+
export interface MountUiGridCustomElementOptions {
|
|
4
|
+
options: GridOptions;
|
|
5
|
+
tagName?: string;
|
|
6
|
+
ensureDefined?: (tagName: string) => Promise<void> | void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface MountedUiGridCustomElement {
|
|
10
|
+
element: HTMLElement & { options: GridOptions };
|
|
11
|
+
unmount: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function mountUiGridCustomElement(
|
|
15
|
+
container: Element,
|
|
16
|
+
mountOptions: MountUiGridCustomElementOptions,
|
|
17
|
+
): Promise<MountedUiGridCustomElement> {
|
|
18
|
+
const tagName = mountOptions.tagName ?? 'ui-grid-element';
|
|
19
|
+
|
|
20
|
+
if (mountOptions.ensureDefined) {
|
|
21
|
+
await mountOptions.ensureDefined(tagName);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const element = document.createElement(tagName) as HTMLElement & { options: GridOptions };
|
|
25
|
+
element.options = mountOptions.options;
|
|
26
|
+
container.replaceChildren(element);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
element,
|
|
30
|
+
unmount: () => {
|
|
31
|
+
if (container.firstElementChild === element) {
|
|
32
|
+
container.replaceChildren();
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|