@ornery/ui-grid-react 0.1.9 → 1.0.0
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 +4 -2
- package/dist/UiGrid.d.ts +6 -5
- package/dist/UiGrid.d.ts.map +1 -1
- package/dist/gridStateMath.d.ts +2 -2
- package/dist/gridStateMath.d.ts.map +1 -1
- package/dist/index.d.ts +2 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +159 -2020
- package/dist/index.mjs +153 -2106
- package/dist/mountUiGrid.d.ts +2 -0
- package/dist/mountUiGrid.d.ts.map +1 -1
- package/dist/ui-grid.css +94 -15
- package/dist/useGridState.d.ts +5 -0
- package/dist/useGridState.d.ts.map +1 -1
- package/package.json +5 -3
- package/src/UiGrid.test.tsx +64 -235
- package/src/UiGrid.tsx +178 -683
- package/src/gridStateMath.test.ts +3 -3
- package/src/gridStateMath.ts +7 -4
- package/src/index.ts +2 -18
- package/src/mountUiGrid.tsx +26 -0
- package/src/test-setup.ts +13 -0
- package/src/ui-grid.css +94 -15
- package/src/useGridState.ts +173 -24
- package/tsconfig.dts.json +3 -2
- package/tsconfig.json +2 -1
- package/vitest.config.ts +2 -0
|
@@ -28,7 +28,7 @@ describe('gridStateMath', () => {
|
|
|
28
28
|
it('builds grid template columns deterministically', () => {
|
|
29
29
|
expect(
|
|
30
30
|
buildGridTemplateColumns(orderVisibleColumns(columns, ['status', 'revenue', 'customer'])),
|
|
31
|
-
).toBe('2fr 120px minmax(11rem,
|
|
31
|
+
).toBe('2fr 120px minmax(11rem, 1fr)');
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
it('resolves benchmark iterations with a minimum of one', () => {
|
|
@@ -43,9 +43,9 @@ describe('gridStateMath', () => {
|
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
it('computes viewport height and viewport rows', () => {
|
|
46
|
-
expect(computeViewportHeightPx(undefined, undefined)).toBe('
|
|
46
|
+
expect(computeViewportHeightPx(undefined, undefined)).toBe('440px');
|
|
47
47
|
expect(computeViewportHeightPx(620, 480)).toBe('620px');
|
|
48
|
-
expect(computeViewportRows(undefined, undefined)).toBe(
|
|
48
|
+
expect(computeViewportRows(undefined, undefined)).toBe(10);
|
|
49
49
|
expect(computeViewportRows(220, 44)).toBe(5);
|
|
50
50
|
});
|
|
51
51
|
});
|
package/src/gridStateMath.ts
CHANGED
|
@@ -34,12 +34,15 @@ export function formatPaginationSummary(
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export function computeViewportHeightPx(
|
|
37
|
-
viewportHeight?: number,
|
|
38
37
|
autoViewportHeight?: number | null,
|
|
38
|
+
minRowsToShow?: number,
|
|
39
|
+
rowHeight?: number,
|
|
39
40
|
): string {
|
|
40
|
-
|
|
41
|
+
const fallback = (minRowsToShow ?? 10) * (rowHeight ?? 44);
|
|
42
|
+
return `${autoViewportHeight ?? fallback}px`;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
export function computeViewportRows(
|
|
44
|
-
|
|
45
|
+
export function computeViewportRows(autoViewportHeight?: number | null, rowHeight?: number, minRowsToShow?: number): number {
|
|
46
|
+
const height = autoViewportHeight ?? (minRowsToShow ?? 10) * (rowHeight ?? 44);
|
|
47
|
+
return Math.max(1, Math.ceil(height / (rowHeight ?? 44)));
|
|
45
48
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,27 +1,11 @@
|
|
|
1
1
|
export { UiGrid } from './UiGrid';
|
|
2
|
-
export type { UiGridProps } from './UiGrid';
|
|
3
|
-
export { mountUiGrid, updateUiGrid, styledCell } from './mountUiGrid';
|
|
2
|
+
export type { UiGridProps, UiGridCellRenderers } from './UiGrid';
|
|
3
|
+
export { mountUiGrid, updateUiGrid, styledCell, datePickerCell } from './mountUiGrid';
|
|
4
4
|
export {
|
|
5
5
|
mountUiGridCustomElement,
|
|
6
6
|
type MountUiGridCustomElementOptions,
|
|
7
7
|
type MountedUiGridCustomElement,
|
|
8
8
|
} from './vanillaAdapter';
|
|
9
|
-
export { useGridState } from './useGridState';
|
|
10
|
-
export type { UseGridStateResult } from './useGridState';
|
|
11
|
-
export { useVirtualScroll } from './useVirtualScroll';
|
|
12
|
-
export type { UseVirtualScrollOptions, UseVirtualScrollResult } from './useVirtualScroll';
|
|
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';
|
|
25
9
|
|
|
26
10
|
export type {
|
|
27
11
|
GridOptions,
|
package/src/mountUiGrid.tsx
CHANGED
|
@@ -25,4 +25,30 @@ export function styledCell(
|
|
|
25
25
|
{ style: { color, fontVariantNumeric: 'tabular-nums', ...extraStyle } },
|
|
26
26
|
text,
|
|
27
27
|
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Create a date <input> React node — usable from non-TSX contexts. */
|
|
31
|
+
export function datePickerCell(
|
|
32
|
+
value: string,
|
|
33
|
+
onChange?: (newValue: string) => void,
|
|
34
|
+
extraStyle?: React.CSSProperties,
|
|
35
|
+
): React.ReactNode {
|
|
36
|
+
return React.createElement('input', {
|
|
37
|
+
type: 'date',
|
|
38
|
+
value: value || '',
|
|
39
|
+
onChange: onChange
|
|
40
|
+
? (e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)
|
|
41
|
+
: undefined,
|
|
42
|
+
style: {
|
|
43
|
+
font: 'inherit',
|
|
44
|
+
fontSize: '0.85rem',
|
|
45
|
+
border: '1px solid color-mix(in srgb, currentColor 20%, transparent)',
|
|
46
|
+
borderRadius: '6px',
|
|
47
|
+
padding: '0.2rem 0.4rem',
|
|
48
|
+
background: 'var(--ui-grid-surface, white)',
|
|
49
|
+
color: 'inherit',
|
|
50
|
+
cursor: 'pointer',
|
|
51
|
+
...extraStyle,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
28
54
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
if (typeof ShadowRoot !== 'undefined') {
|
|
2
|
+
const proto = ShadowRoot.prototype as unknown as Record<string, unknown>;
|
|
3
|
+
if (!('adoptedStyleSheets' in proto)) {
|
|
4
|
+
Object.defineProperty(proto, 'adoptedStyleSheets', {
|
|
5
|
+
get() {
|
|
6
|
+
return (this as unknown as Record<string, unknown>)['_adoptedStyleSheets'] ?? [];
|
|
7
|
+
},
|
|
8
|
+
set(sheets: CSSStyleSheet[]) {
|
|
9
|
+
(this as unknown as Record<string, unknown>)['_adoptedStyleSheets'] = sheets;
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/ui-grid.css
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
.ui-grid-host {
|
|
2
|
+
/* Stretch to fill the parent so the autoresize observer can measure a
|
|
3
|
+
deterministic height. Consumers can override these properties to opt
|
|
4
|
+
back into intrinsic sizing. */
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
min-height: 0;
|
|
8
|
+
height: 100%;
|
|
2
9
|
--_ui-grid-border-color: var(--ui-grid-border-color, var(--app-ui-grid-border-color, #d4d4d8));
|
|
3
10
|
--_ui-grid-header-background: var(
|
|
4
11
|
--ui-grid-header-background,
|
|
@@ -221,6 +228,12 @@
|
|
|
221
228
|
border: 1px solid var(--ui-grid-border-color);
|
|
222
229
|
box-shadow: var(--ui-grid-shadow);
|
|
223
230
|
overflow: hidden;
|
|
231
|
+
/* Allow the frame to grow inside the flexed host so the body grid can fill
|
|
232
|
+
the available height when no explicit viewportHeight is set. */
|
|
233
|
+
display: flex;
|
|
234
|
+
flex-direction: column;
|
|
235
|
+
flex: 1 1 auto;
|
|
236
|
+
min-height: 0;
|
|
224
237
|
}
|
|
225
238
|
|
|
226
239
|
.metrics-strip {
|
|
@@ -266,37 +279,70 @@
|
|
|
266
279
|
color: var(--ui-grid-muted-color);
|
|
267
280
|
}
|
|
268
281
|
|
|
282
|
+
/*
|
|
283
|
+
* Grid table layout. Header + filter rows live in their own
|
|
284
|
+
* non-user-scrollable strips (real scroll containers so `position: sticky`
|
|
285
|
+
* on pinned cells anchors correctly); the body has its own viewport with
|
|
286
|
+
* visible scrollbars. Horizontal scroll on the body is mirrored onto the
|
|
287
|
+
* strips imperatively via React's onScroll/onWheel handlers so the columns
|
|
288
|
+
* always stay aligned.
|
|
289
|
+
*/
|
|
269
290
|
.grid-table {
|
|
270
|
-
|
|
291
|
+
position: relative;
|
|
292
|
+
display: flex;
|
|
293
|
+
flex-direction: column;
|
|
271
294
|
min-height: 0;
|
|
272
295
|
width: 100%;
|
|
273
296
|
min-width: 0;
|
|
274
|
-
overflow-
|
|
275
|
-
overflow-y: hidden;
|
|
297
|
+
overflow-anchor: none;
|
|
276
298
|
}
|
|
277
299
|
|
|
278
|
-
.header-
|
|
279
|
-
.filter-
|
|
280
|
-
|
|
281
|
-
display: grid;
|
|
282
|
-
width: max-content;
|
|
283
|
-
min-width: 100%;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
.header-grid,
|
|
287
|
-
.filter-grid {
|
|
300
|
+
.grid-header-strip,
|
|
301
|
+
.grid-filter-strip {
|
|
302
|
+
flex: 0 0 auto;
|
|
288
303
|
position: sticky;
|
|
289
304
|
z-index: 3;
|
|
305
|
+
background: var(--ui-grid-surface);
|
|
306
|
+
/* Real scroll container so pinned sticky cells anchor to something,
|
|
307
|
+
* but we don't want the user scrolling it directly. JS drives the
|
|
308
|
+
* scrollLeft, the wheel handler forwards gestures to the body, and
|
|
309
|
+
* the scrollbar is hidden. */
|
|
310
|
+
overflow-x: scroll;
|
|
311
|
+
overflow-y: hidden;
|
|
312
|
+
scrollbar-width: none;
|
|
313
|
+
touch-action: pan-y;
|
|
314
|
+
overscroll-behavior-x: none;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.grid-header-strip::-webkit-scrollbar,
|
|
318
|
+
.grid-filter-strip::-webkit-scrollbar {
|
|
319
|
+
display: none;
|
|
290
320
|
}
|
|
291
321
|
|
|
292
|
-
.header-
|
|
322
|
+
.grid-header-strip {
|
|
293
323
|
top: 0;
|
|
294
324
|
}
|
|
295
325
|
|
|
296
|
-
.filter-
|
|
326
|
+
.grid-filter-strip {
|
|
297
327
|
top: var(--ui-grid-header-sticky-top, 0px);
|
|
298
328
|
}
|
|
299
329
|
|
|
330
|
+
.grid-body-viewport {
|
|
331
|
+
flex: 1 1 auto;
|
|
332
|
+
min-height: 0;
|
|
333
|
+
overflow-x: auto;
|
|
334
|
+
overflow-anchor: none;
|
|
335
|
+
overscroll-behavior-x: none;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.header-grid,
|
|
339
|
+
.filter-grid,
|
|
340
|
+
.body-grid {
|
|
341
|
+
display: grid;
|
|
342
|
+
width: max-content;
|
|
343
|
+
min-width: 100%;
|
|
344
|
+
}
|
|
345
|
+
|
|
300
346
|
.header-cell,
|
|
301
347
|
.filter-cell,
|
|
302
348
|
.body-cell,
|
|
@@ -313,6 +359,7 @@
|
|
|
313
359
|
background: var(--ui-grid-header-background);
|
|
314
360
|
font-weight: var(--ui-grid-header-weight);
|
|
315
361
|
align-items: center;
|
|
362
|
+
position: relative;
|
|
316
363
|
}
|
|
317
364
|
|
|
318
365
|
.header-cell[draggable='true'] {
|
|
@@ -337,6 +384,38 @@
|
|
|
337
384
|
z-index: var(--ui-grid-pin-menu-open-z-index);
|
|
338
385
|
}
|
|
339
386
|
|
|
387
|
+
.column-resizer {
|
|
388
|
+
position: absolute;
|
|
389
|
+
top: 0;
|
|
390
|
+
right: -4px;
|
|
391
|
+
width: 8px;
|
|
392
|
+
height: 100%;
|
|
393
|
+
padding: 0;
|
|
394
|
+
border: 0;
|
|
395
|
+
background: transparent;
|
|
396
|
+
cursor: col-resize;
|
|
397
|
+
z-index: 5;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.column-resizer::after {
|
|
401
|
+
content: '';
|
|
402
|
+
position: absolute;
|
|
403
|
+
top: 20%;
|
|
404
|
+
bottom: 20%;
|
|
405
|
+
left: 50%;
|
|
406
|
+
width: 2px;
|
|
407
|
+
transform: translateX(-50%);
|
|
408
|
+
border-radius: 999px;
|
|
409
|
+
background: color-mix(in srgb, var(--ui-grid-border-color, #888) 80%, transparent);
|
|
410
|
+
opacity: 0;
|
|
411
|
+
transition: opacity 120ms ease;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.header-cell:hover .column-resizer::after,
|
|
415
|
+
.column-resizer:focus-visible::after {
|
|
416
|
+
opacity: 1;
|
|
417
|
+
}
|
|
418
|
+
|
|
340
419
|
.header-label {
|
|
341
420
|
min-width: 0;
|
|
342
421
|
display: block;
|