@human-kit/svelte-components 1.0.0-alpha.16 → 1.0.0-alpha.18

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.
Files changed (32) hide show
  1. package/dist/checkbox/root/checkbox-root.svelte +9 -4
  2. package/dist/checkbox/root/checkbox-root.svelte.d.ts +4 -1
  3. package/dist/table/PLAN.md +6 -6
  4. package/dist/table/README.md +4 -2
  5. package/dist/table/body/table-body.svelte +5 -0
  6. package/dist/table/cell/table-cell.svelte +18 -1
  7. package/dist/table/checkbox/README.md +1 -1
  8. package/dist/table/checkbox/table-checkbox.svelte +5 -48
  9. package/dist/table/checkbox-indicator/README.md +1 -1
  10. package/dist/table/column/README.md +11 -11
  11. package/dist/table/column-header-cell/table-column-header-cell.svelte +20 -17
  12. package/dist/table/column-resizer/table-column-resizer-fixed-width-test.svelte +57 -0
  13. package/dist/table/column-resizer/table-column-resizer-fixed-width-test.svelte.d.ts +3 -0
  14. package/dist/table/column-resizer/table-column-resizer-freeze-layout-test.svelte +2 -1
  15. package/dist/table/column-resizer/table-column-resizer-overflow-test.svelte +64 -0
  16. package/dist/table/column-resizer/table-column-resizer-overflow-test.svelte.d.ts +3 -0
  17. package/dist/table/column-resizer/table-column-resizer-padded-container-test.svelte +67 -0
  18. package/dist/table/column-resizer/table-column-resizer-padded-container-test.svelte.d.ts +3 -0
  19. package/dist/table/column-resizer/table-column-resizer-sandbox-overflow-test.svelte +87 -0
  20. package/dist/table/column-resizer/table-column-resizer-sandbox-overflow-test.svelte.d.ts +3 -0
  21. package/dist/table/column-resizer/table-column-resizer-selection-column-test.svelte +2 -1
  22. package/dist/table/column-resizer/table-column-resizer-test.svelte +3 -3
  23. package/dist/table/column-resizer/table-column-resizer-three-column-relative-test.svelte +64 -0
  24. package/dist/table/column-resizer/table-column-resizer-three-column-relative-test.svelte.d.ts +3 -0
  25. package/dist/table/column-resizer/table-column-resizer.svelte +47 -54
  26. package/dist/table/root/README.md +12 -12
  27. package/dist/table/root/context.d.ts +24 -7
  28. package/dist/table/root/context.js +506 -43
  29. package/dist/table/root/table-root.svelte +140 -9
  30. package/dist/table/row/table-row.svelte +20 -60
  31. package/dist/table/types.d.ts +4 -4
  32. package/package.json +1 -1
@@ -0,0 +1,87 @@
1
+ <script lang="ts">
2
+ import { Table } from '../index';
3
+ import type { TableColumnWidth } from '../root/context';
4
+
5
+ let currentColumnWidths = $state<Map<string, TableColumnWidth> | undefined>(undefined);
6
+ const rows = Array.from({ length: 24 }, (_, index) => ({
7
+ id: `row-${index + 1}`,
8
+ request: `PR-${String(index + 1).padStart(4, '0')}`,
9
+ requester: ['Ana Gomez', 'Lucas Perez', 'Mara Silva', 'Juan Torres'][index % 4],
10
+ area: ['Production', 'Logistics', 'Maintenance', 'Quality'][index % 4],
11
+ status: ['Pending', 'Review', 'Approved'][index % 3],
12
+ priority: ['Low', 'Medium', 'High'][index % 3],
13
+ total: 850 + index * 137
14
+ }));
15
+ </script>
16
+
17
+ <div
18
+ data-testid="sandbox-overflow-container"
19
+ style="width: 920px; max-height: 320px; overflow: auto;"
20
+ >
21
+ <Table.Root
22
+ aria-label="Sandbox overflow table"
23
+ selectionMode="multiple"
24
+ bind:columnWidths={currentColumnWidths}
25
+ class="min-w-full border-collapse text-left"
26
+ >
27
+ <Table.Header>
28
+ <Table.Row>
29
+ <Table.Column id="selection" textValue="Selection" width={44}>
30
+ <Table.ColumnHeaderCell data-testid="sandbox-selection-header-cell">
31
+ <Table.Checkbox />
32
+ </Table.ColumnHeaderCell>
33
+ </Table.Column>
34
+ <Table.Column id="request" textValue="Request" isRowHeader defaultWidth={350}>
35
+ <Table.ColumnHeaderCell data-testid="sandbox-request-header-cell">
36
+ <div class="flex items-center justify-between gap-3">
37
+ <span>Request</span>
38
+ <Table.ColumnResizer
39
+ data-testid="sandbox-request-resizer"
40
+ class="inline-flex w-3 cursor-col-resize justify-center"
41
+ />
42
+ </div>
43
+ </Table.ColumnHeaderCell>
44
+ </Table.Column>
45
+ <Table.Column id="requester" textValue="Requester">
46
+ <Table.ColumnHeaderCell>Requester</Table.ColumnHeaderCell>
47
+ </Table.Column>
48
+ <Table.Column id="area" textValue="Area">
49
+ <Table.ColumnHeaderCell>Area</Table.ColumnHeaderCell>
50
+ </Table.Column>
51
+ <Table.Column id="status" textValue="Status">
52
+ <Table.ColumnHeaderCell>Status</Table.ColumnHeaderCell>
53
+ </Table.Column>
54
+ <Table.Column id="priority" textValue="Priority">
55
+ <Table.ColumnHeaderCell>Priority</Table.ColumnHeaderCell>
56
+ </Table.Column>
57
+ <Table.Column id="total" textValue="Total">
58
+ <Table.ColumnHeaderCell data-testid="sandbox-total-header-cell">
59
+ <div class="flex w-full justify-end">Total</div>
60
+ </Table.ColumnHeaderCell>
61
+ </Table.Column>
62
+ </Table.Row>
63
+ </Table.Header>
64
+
65
+ <Table.Body>
66
+ {#each rows as row (row.id)}
67
+ <Table.Row id={row.id}>
68
+ <Table.Cell>
69
+ <Table.Checkbox />
70
+ </Table.Cell>
71
+ <Table.Cell>{row.request}</Table.Cell>
72
+ <Table.Cell>{row.requester}</Table.Cell>
73
+ <Table.Cell>{row.area}</Table.Cell>
74
+ <Table.Cell>{row.status}</Table.Cell>
75
+ <Table.Cell>{row.priority}</Table.Cell>
76
+ <Table.Cell>
77
+ <div class="flex w-full justify-end">${row.total.toLocaleString()}</div>
78
+ </Table.Cell>
79
+ </Table.Row>
80
+ {/each}
81
+ </Table.Body>
82
+ </Table.Root>
83
+ </div>
84
+
85
+ <output data-testid="sandbox-overflow-widths"
86
+ >{JSON.stringify(Object.fromEntries(currentColumnWidths ?? new Map()))}</output
87
+ >
@@ -0,0 +1,3 @@
1
+ declare const TableColumnResizerSandboxOverflowTest: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type TableColumnResizerSandboxOverflowTest = ReturnType<typeof TableColumnResizerSandboxOverflowTest>;
3
+ export default TableColumnResizerSandboxOverflowTest;
@@ -1,7 +1,8 @@
1
1
  <script lang="ts">
2
2
  import { Table } from '../index';
3
+ import type { TableColumnWidth } from '../root/context';
3
4
 
4
- let currentColumnWidths = $state<Map<string, number> | undefined>(undefined);
5
+ let currentColumnWidths = $state<Map<string, TableColumnWidth> | undefined>(undefined);
5
6
 
6
7
  const checkboxStyle =
7
8
  'display:inline-flex;height:20px;width:20px;align-items:center;justify-content:center;border:1px solid currentColor;border-radius:4px;';
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
2
  import { Table } from '../index';
3
- import type { TableSortDescriptor } from '../root/context';
3
+ import type { TableColumnWidth, TableSortDescriptor } from '../root/context';
4
4
 
5
- let currentColumnWidths = $state<Map<string, number>>(
5
+ let currentColumnWidths = $state<Map<string, TableColumnWidth>>(
6
6
  new Map([
7
7
  ['email', 200],
8
8
  ['group', 160]
@@ -10,7 +10,7 @@
10
10
  );
11
11
  let currentSortDescriptor = $state<TableSortDescriptor | undefined>(undefined);
12
12
  let resizeStartColumnId = $state('');
13
- let resizeEndWidths = $state<Record<string, number>>({});
13
+ let resizeEndWidths = $state<Record<string, TableColumnWidth>>({});
14
14
  </script>
15
15
 
16
16
  <Table.Root
@@ -0,0 +1,64 @@
1
+ <script lang="ts">
2
+ import { Table } from '../index';
3
+ import type { TableColumnWidth } from '../root/context';
4
+
5
+ let currentColumnWidths = $state<Map<string, TableColumnWidth> | undefined>(undefined);
6
+ </script>
7
+
8
+ <div data-testid="three-column-relative-container" style="width: 640px; overflow-x: auto;">
9
+ <Table.Root
10
+ aria-label="Three column relative resize table"
11
+ bind:columnWidths={currentColumnWidths}
12
+ class="min-w-full border-collapse text-left"
13
+ >
14
+ <Table.Header>
15
+ <Table.Row>
16
+ <Table.Column id="name" isRowHeader textValue="Name">
17
+ <Table.ColumnHeaderCell data-testid="three-column-name-header-cell">
18
+ <div class="flex items-center justify-between gap-3">
19
+ <span>Name</span>
20
+ <Table.ColumnResizer
21
+ data-testid="three-column-name-resizer"
22
+ class="inline-flex w-3 cursor-col-resize justify-center"
23
+ />
24
+ </div>
25
+ </Table.ColumnHeaderCell>
26
+ </Table.Column>
27
+ <Table.Column id="region" textValue="Region" defaultWidth={180} minWidth={140}>
28
+ <Table.ColumnHeaderCell data-testid="three-column-region-header-cell">
29
+ <div class="flex items-center justify-between gap-3">
30
+ <span>Region</span>
31
+ <Table.ColumnResizer
32
+ data-testid="three-column-region-resizer"
33
+ class="inline-flex w-3 cursor-col-resize justify-center"
34
+ />
35
+ </div>
36
+ </Table.ColumnHeaderCell>
37
+ </Table.Column>
38
+ <Table.Column id="plan" textValue="Plan">
39
+ <Table.ColumnHeaderCell data-testid="three-column-plan-header-cell">
40
+ <div class="flex items-center justify-between gap-3">
41
+ <span>Plan</span>
42
+ <Table.ColumnResizer
43
+ data-testid="three-column-plan-resizer"
44
+ class="inline-flex w-3 cursor-col-resize justify-center"
45
+ />
46
+ </div>
47
+ </Table.ColumnHeaderCell>
48
+ </Table.Column>
49
+ </Table.Row>
50
+ </Table.Header>
51
+
52
+ <Table.Body>
53
+ <Table.Row id="danilo">
54
+ <Table.Cell>Danilo Fernandez</Table.Cell>
55
+ <Table.Cell>Buenos Aires</Table.Cell>
56
+ <Table.Cell>Enterprise</Table.Cell>
57
+ </Table.Row>
58
+ </Table.Body>
59
+ </Table.Root>
60
+ </div>
61
+
62
+ <output data-testid="three-column-relative-widths"
63
+ >{JSON.stringify(Object.fromEntries(currentColumnWidths ?? new Map()))}</output
64
+ >
@@ -0,0 +1,3 @@
1
+ declare const TableColumnResizerThreeColumnRelativeTest: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type TableColumnResizerThreeColumnRelativeTest = ReturnType<typeof TableColumnResizerThreeColumnRelativeTest>;
3
+ export default TableColumnResizerThreeColumnRelativeTest;
@@ -1,6 +1,11 @@
1
1
  <script lang="ts">
2
2
  import { onDestroy } from 'svelte';
3
- import { getTableCellContext, useTableColumnContext, useTableContext } from '../root/context';
3
+ import {
4
+ DEFAULT_TABLE_COLUMN_MIN_WIDTH,
5
+ getTableCellContext,
6
+ useTableColumnContext,
7
+ useTableContext
8
+ } from '../root/context';
4
9
  import type { TableColumnResizerProps } from '../types.js';
5
10
  import { visuallyHiddenStyle } from '../utils/visually-hidden-style';
6
11
  import {
@@ -60,7 +65,7 @@
60
65
  });
61
66
  const minWidth = $derived.by(() => {
62
67
  void $widthVersion;
63
- return table.getColumnMinWidth(column.id) ?? 75;
68
+ return table.getColumnMinWidth(column.id) ?? DEFAULT_TABLE_COLUMN_MIN_WIDTH;
64
69
  });
65
70
  const maxWidth = $derived.by(() => {
66
71
  void $widthVersion;
@@ -268,16 +273,16 @@
268
273
  suppressNextDoubleClickAutofit = false;
269
274
 
270
275
  stopKeyboardResizeMode();
271
- table.startColumnResize(column.id);
272
276
 
273
- const th = element?.closest('th') as HTMLElement | null;
274
- const tableEl = th?.closest('table') as HTMLTableElement | null;
275
- const startX = event.clientX;
276
277
  const startWidth = table.getColumnWidth(column.id) ?? getHeaderWidth();
277
278
  const pointerId = event.pointerId;
278
279
  const isRTL = isRightToLeft();
280
+ const pointerStartClientX = event.clientX;
279
281
  let didDrag = false;
280
- let latestClientX = startX;
282
+ let didStartResize = false;
283
+ let latestClientX = event.clientX;
284
+ let previousClientX = event.clientX;
285
+ let dragWidth = startWidth;
281
286
  let animationFrameId: number | null = null;
282
287
 
283
288
  // Capture the pointer so we receive move/up events even if the cursor
@@ -299,30 +304,27 @@
299
304
  return clamped;
300
305
  }
301
306
 
302
- function applyTemporaryWidthToDOM(width: number) {
303
- if (th) th.style.width = `${width}px`;
304
- if (tableEl) {
305
- const allThs = tableEl.querySelectorAll<HTMLElement>('thead th[style*="width"]');
306
- let total = 0;
307
- for (const cell of allThs) {
308
- total += parseFloat(cell.style.width) || 0;
309
- }
310
- if (total > 0) {
311
- tableEl.style.width = `${total}px`;
312
- tableEl.style.minWidth = '0';
313
- }
314
- }
315
- }
316
-
317
307
  function flushPendingPointerMove() {
318
308
  if (animationFrameId !== null) {
319
309
  cancelAnimationFrame(animationFrameId);
320
310
  animationFrameId = null;
321
311
  }
322
312
 
323
- const direction = isRTL ? -1 : 1;
324
- const delta = (latestClientX - startX) * positionScale * direction;
325
- const nextWidth = clampWidth(startWidth + delta);
313
+ if (!didStartResize && latestClientX === pointerStartClientX) {
314
+ return;
315
+ }
316
+
317
+ if (!didStartResize) {
318
+ table.startColumnResize(column.id);
319
+ didStartResize = true;
320
+ previousClientX = pointerStartClientX;
321
+ }
322
+
323
+ const pointerDelta = latestClientX - previousClientX;
324
+ previousClientX = latestClientX;
325
+ const widthDelta = isRTL ? -pointerDelta : pointerDelta;
326
+ dragWidth = clampWidth(dragWidth + widthDelta);
327
+ const nextWidth = dragWidth;
326
328
  updateWidth(nextWidth);
327
329
  }
328
330
 
@@ -334,30 +336,14 @@
334
336
  });
335
337
  }
336
338
 
337
- // Measure position compensation factor.
338
- // In centered/flex layouts, growing a column shifts the table's left edge,
339
- // so the handle moves less than the mouse delta. We detect this by applying
340
- // a 1px test change and measuring how much the <th> left edge drifts.
341
- // NOTE: applyTemporaryWidthToDOM intentionally mutates the DOM synchronously
342
- // outside Svelte's reactive cycle. The mutation is immediately reverted
343
- // within the same microtask, so no observer or $effect will see it.
344
- let positionScale = 1;
345
- if (th) {
346
- const leftBefore = th.getBoundingClientRect().left;
347
- applyTemporaryWidthToDOM(startWidth + 1);
348
- const leftAfter = th.getBoundingClientRect().left;
349
- applyTemporaryWidthToDOM(startWidth);
350
- const drift = leftBefore - leftAfter;
351
- if (drift > 0.01 && drift < 0.99) {
352
- positionScale = 1 / (1 - drift);
353
- }
354
- }
355
-
356
339
  const handlePointerMove = (moveEvent: PointerEvent) => {
357
340
  if (moveEvent.pointerId !== pointerId) return;
358
341
  moveEvent.preventDefault();
359
- didDrag = true;
360
342
  latestClientX = moveEvent.clientX;
343
+ if (!didStartResize && latestClientX === pointerStartClientX) {
344
+ return;
345
+ }
346
+ didDrag = true;
361
347
  schedulePointerMove();
362
348
  };
363
349
 
@@ -386,7 +372,9 @@
386
372
  }
387
373
  // Treat system-initiated cancellation the same as Escape:
388
374
  // restore the width the column had before the drag started.
389
- updateWidth(startWidth);
375
+ if (didStartResize) {
376
+ updateWidth(startWidth);
377
+ }
390
378
  cleanupPointerListeners();
391
379
  };
392
380
 
@@ -399,7 +387,9 @@
399
387
  cancelAnimationFrame(animationFrameId);
400
388
  animationFrameId = null;
401
389
  }
402
- updateWidth(startWidth);
390
+ if (didStartResize) {
391
+ updateWidth(startWidth);
392
+ }
403
393
  cleanupPointerListeners();
404
394
  };
405
395
 
@@ -429,6 +419,7 @@
429
419
  }
430
420
 
431
421
  function handleClick(event: MouseEvent) {
422
+ if (!isResizable) return;
432
423
  event.preventDefault();
433
424
  event.stopPropagation();
434
425
  }
@@ -555,18 +546,19 @@
555
546
  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
556
547
  <div
557
548
  bind:this={element}
558
- role="separator"
549
+ role={isResizable ? 'separator' : undefined}
559
550
  tabindex={isResizable ? 0 : undefined}
560
551
  class={className}
561
- aria-label={accessibleLabel}
562
- aria-orientation="vertical"
563
- aria-valuenow={currentWidth ?? undefined}
564
- aria-valuemin={minWidth}
565
- aria-valuemax={maxWidth}
566
- aria-valuetext={accessibleValueText}
552
+ aria-label={isResizable ? accessibleLabel : undefined}
553
+ aria-orientation={isResizable ? 'vertical' : undefined}
554
+ aria-valuenow={isResizable ? (currentWidth ?? undefined) : undefined}
555
+ aria-valuemin={isResizable ? minWidth : undefined}
556
+ aria-valuemax={isResizable ? maxWidth : undefined}
557
+ aria-valuetext={isResizable ? accessibleValueText : undefined}
567
558
  data-focused={isFocused ? 'true' : undefined}
568
559
  data-focus-visible={isFocusVisible ? 'true' : undefined}
569
560
  data-resizing={isResizing ? 'true' : undefined}
561
+ data-resizable={isResizable ? 'true' : undefined}
570
562
  data-table-column-resizer="true"
571
563
  data-resizable-direction="right"
572
564
  style:position="absolute"
@@ -580,6 +572,7 @@
580
572
  style:align-items="center"
581
573
  style:justify-content="center"
582
574
  style:user-select="none"
575
+ style:pointer-events={isResizable ? 'auto' : 'none'}
583
576
  style:touch-action="none"
584
577
  onpointerdown={handlePointerDown}
585
578
  ondblclick={handleDoubleClick}
@@ -23,16 +23,16 @@ Public prop type: `TableRootProps`
23
23
  | `defaultSelectedKeys` | `Iterable<string \| number>` | `undefined` | Initial selected row ids for uncontrolled usage. When `selectionMode` later becomes `none`, the internal selection is cleared. |
24
24
  | `sortDescriptor` | `{ column: string; direction: 'ascending' \| 'descending' }` | `undefined` | Controlled sort state. Supports `bind:sortDescriptor`. Setting it back to `undefined` clears the sort. |
25
25
  | `defaultSortDescriptor` | `{ column: string; direction: 'ascending' \| 'descending' }` | `undefined` | Initial sort state for uncontrolled usage. |
26
- | `columnWidths` | `Map<string, number>` | `undefined` | Controlled column width state in px. Supports `bind:columnWidths`. |
27
- | `defaultColumnWidths` | `Iterable<[string, number]>` | `undefined` | Initial uncontrolled column widths in px. |
26
+ | `columnWidths` | `Map<string, TableColumnWidth>` | `undefined` | Controlled column width state. Supports px, `%`, and `fr` specs via `bind:columnWidths`. |
27
+ | `defaultColumnWidths` | `Iterable<[string, TableColumnWidth]>` | `undefined` | Initial uncontrolled column widths using px, `%`, or `fr` specs. |
28
28
  | `disabledKeys` | `Iterable<string \| number>` | `undefined` | Row ids that should be non-selectable. |
29
29
  | `onRowAction` | `(id: string \| number) => void` | `undefined` | Called when a row action is triggered for an actionable row. |
30
30
  | `onSelectionChange` | `(keys: Set<string \| number>) => void` | `undefined` | Called when row selection changes. |
31
31
  | `onSortChange` | `(descriptor) => void` | `undefined` | Called when sortable header state changes. |
32
- | `onColumnWidthsChange` | `(widths: Map<string, number>) => void` | `undefined` | Called when resize interactions update column widths. |
32
+ | `onColumnWidthsChange` | `(widths: Map<string, TableColumnWidth>) => void` | `undefined` | Called when resize interactions update managed column widths. |
33
33
  | `onHiddenColumnsChange` | `(columnIds: string[]) => void` | `undefined` | Called when hidden column state changes. |
34
34
  | `onColumnResizeStart` | `(columnId: string) => void` | `undefined` | Called when a column resize drag starts. |
35
- | `onColumnResizeEnd` | `(widths: Map<string, number>) => void` | `undefined` | Called when a column resize drag ends. |
35
+ | `onColumnResizeEnd` | `(widths: Map<string, TableColumnWidth>) => void` | `undefined` | Called when a column resize drag ends. |
36
36
  | `aria-label` | `string` | `undefined` | Accessible name when no external label is present. |
37
37
  | `aria-labelledby` | `string` | `undefined` | Id reference for an external label. |
38
38
  | `class` | `string` | `''` | Class names for the root table element. |
@@ -45,14 +45,14 @@ Public prop type: `TableRootProps`
45
45
  Name: Selection and sorting notes
46
46
  Description: Current v1 interaction constraints that affect consumer expectations.
47
47
 
48
- | Topic | Behavior |
49
- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
50
- | `selectionMode="none"` | Clears existing row selection internally and prevents further row selection until another mode is set. |
51
- | Text selection and copy | Browser-native text selection and `Ctrl+C` behavior are preserved; the component does not implement custom clipboard logic. |
52
- | `replace` mode blur | Clicking or tabbing outside the table clears focus state but does not clear row selection. |
53
- | Sort announcements | Sort changes are mirrored into a polite live region. Use `Table.Column.textValue` when the announced label should differ from the column `id`. |
54
- | Column resizing | Width state is normalized to px values and a `Table.ColumnResizer` only affects the `Table.Column` it is composed within. |
55
- | Row edge focus | In body rows, `ArrowLeft` before the first cell and `ArrowRight` after the last cell move focus onto the row itself; `ArrowUp` / `ArrowDown` keep that row-edge focus aligned across rows, repeating the same horizontal arrow loops back into the opposite edge cell, and `Home` / `End` jump to the first or last focusable body row while row focus is active. |
48
+ | Topic | Behavior |
49
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
50
+ | `selectionMode="none"` | Clears existing row selection internally and prevents further row selection until another mode is set. |
51
+ | Text selection and copy | Browser-native text selection and `Ctrl+C` behavior are preserved; the component does not implement custom clipboard logic. |
52
+ | `replace` mode blur | Clicking or tabbing outside the table clears focus state but does not clear row selection. |
53
+ | Sort announcements | Sort changes are mirrored into a polite live region. Use `Table.Column.textValue` when the announced label should differ from the column `id`. |
54
+ | Column resizing | Width state accepts px, `%`, and `fr` specs. Before interaction, unspecified columns behave like implicit flexible space. On the first real resize, visible columns are converted to px; the trailing column absorbs the delta until its minimum width, and additional growth overflows the table horizontally. A `Table.ColumnResizer` only affects the `Table.Column` it is composed within. |
55
+ | Row edge focus | In body rows, `ArrowLeft` before the first cell and `ArrowRight` after the last cell move focus onto the row itself; `ArrowUp` / `ArrowDown` keep that row-edge focus aligned across rows, repeating the same horizontal arrow loops back into the opposite edge cell, and `Home` / `End` jump to the first or last focusable body row while row focus is active. |
56
56
 
57
57
  ### Context utilities
58
58
 
@@ -17,7 +17,18 @@ export type TableSortDescriptor = {
17
17
  column: string;
18
18
  direction: TableSortDirection;
19
19
  };
20
- export type TableColumnWidth = number | `${number}px`;
20
+ export type TableColumnWidth = number | `${number}px` | `${number}%` | `${number}fr`;
21
+ export declare const DEFAULT_TABLE_COLUMN_MIN_WIDTH = 60;
22
+ export type RoundedWidthDistributionEntry = {
23
+ columnId: string;
24
+ index: number;
25
+ exactWidth: number;
26
+ width: number;
27
+ minWidth: number;
28
+ maxWidth?: number;
29
+ remainder: number;
30
+ };
31
+ export declare function distributeRoundedWidths(entries: RoundedWidthDistributionEntry[], targetTotal: number): RoundedWidthDistributionEntry[];
21
32
  export type TableGridCoord = {
22
33
  row: number;
23
34
  col: number;
@@ -58,16 +69,16 @@ export type CreateTableContextOptions = {
58
69
  disallowEmptySelection?: boolean;
59
70
  initialSelectedKeys?: Iterable<TableSelectionKey>;
60
71
  initialSortDescriptor?: TableSortDescriptor;
61
- initialColumnWidths?: Iterable<readonly [string, number]>;
72
+ initialColumnWidths?: Iterable<readonly [string, TableColumnWidth]>;
62
73
  initialHiddenColumns?: Iterable<string>;
63
74
  disabledKeys?: Iterable<TableSelectionKey>;
64
75
  onRowAction?: TableRowActionHandler;
65
76
  onSelectionChange?: (keys: Set<TableSelectionKey>) => void;
66
77
  onSortChange?: (descriptor: TableSortDescriptor | undefined) => void;
67
- onColumnWidthsChange?: (widths: Map<string, number>) => void;
78
+ onColumnWidthsChange?: (widths: Map<string, TableColumnWidth>) => void;
68
79
  onHiddenColumnsChange?: (columnIds: string[]) => void;
69
80
  onColumnResizeStart?: (columnId: string) => void;
70
- onColumnResizeEnd?: (widths: Map<string, number>) => void;
81
+ onColumnResizeEnd?: (widths: Map<string, TableColumnWidth>) => void;
71
82
  };
72
83
  export type TableContext = {
73
84
  layoutVersion: Readable<number>;
@@ -98,13 +109,18 @@ export type TableContext = {
98
109
  getVisibleColumnIndexByToken: (token: string) => number;
99
110
  getColumnTextValue: (columnId: string) => string | undefined;
100
111
  getColumnWidth: (columnId: string) => number | undefined;
112
+ getColumnWidthStyle: (columnId: string) => string | undefined;
113
+ hasAuthoredColumnWidthSpec: (columnId: string) => boolean;
101
114
  getColumnMinWidth: (columnId: string) => number | undefined;
102
115
  getColumnMaxWidth: (columnId: string) => number | undefined;
103
116
  isColumnHidden: (columnId: string) => boolean;
104
117
  isColumnResizable: (columnId: string) => boolean;
105
- getColumnWidths: () => Map<string, number>;
106
- getVisibleColumnWidths: () => Map<string, number>;
107
- setColumnWidths: (widths?: Iterable<readonly [string, number]>) => void;
118
+ getColumnWidths: () => Map<string, TableColumnWidth>;
119
+ getVisibleColumnWidths: () => Map<string, TableColumnWidth>;
120
+ getResolvedVisibleColumnWidths: () => Map<string, number>;
121
+ hasRelativeVisibleColumnWidths: () => boolean;
122
+ refreshMeasuredLayout: () => void;
123
+ setColumnWidths: (widths?: Iterable<readonly [string, TableColumnWidth]>) => void;
108
124
  setColumnWidth: (columnId: string, width: number) => void;
109
125
  setHiddenColumns: (columnIds?: Iterable<string>) => void;
110
126
  measureColumnContentWidth: (columnId: string) => number | undefined;
@@ -115,6 +131,7 @@ export type TableContext = {
115
131
  hasResizableColumns: () => boolean;
116
132
  registerRow: (row: TableRowRegistration) => void;
117
133
  unregisterRow: (token: string) => void;
134
+ markBodyRowsInitialized: () => void;
118
135
  getHeaderRowCount: () => number;
119
136
  getBodyRowCount: () => number;
120
137
  isRowSelected: (id: TableSelectionKey | undefined) => boolean;