@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.
@@ -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, max-content)');
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('560px');
46
+ expect(computeViewportHeightPx(undefined, undefined)).toBe('440px');
47
47
  expect(computeViewportHeightPx(620, 480)).toBe('620px');
48
- expect(computeViewportRows(undefined, undefined)).toBe(13);
48
+ expect(computeViewportRows(undefined, undefined)).toBe(10);
49
49
  expect(computeViewportRows(220, 44)).toBe(5);
50
50
  });
51
51
  });
@@ -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
- return `${viewportHeight ?? autoViewportHeight ?? 560}px`;
41
+ const fallback = (minRowsToShow ?? 10) * (rowHeight ?? 44);
42
+ return `${autoViewportHeight ?? fallback}px`;
41
43
  }
42
44
 
43
- export function computeViewportRows(viewportHeight?: number, rowHeight?: number): number {
44
- return Math.max(1, Math.ceil((viewportHeight ?? 560) / (rowHeight ?? 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,
@@ -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
- display: grid;
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-x: auto;
275
- overflow-y: hidden;
297
+ overflow-anchor: none;
276
298
  }
277
299
 
278
- .header-grid,
279
- .filter-grid,
280
- .body-grid {
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-grid {
322
+ .grid-header-strip {
293
323
  top: 0;
294
324
  }
295
325
 
296
- .filter-grid {
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;