@human-kit/svelte-components 1.0.0-alpha.3 → 1.0.0-alpha.5

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 (189) hide show
  1. package/dist/FOCUS_STATE_CONTRACT.md +12 -0
  2. package/dist/calendar/body-cell/README.md +15 -0
  3. package/dist/calendar/grid/README.md +13 -0
  4. package/dist/calendar/grid-body/README.md +13 -0
  5. package/dist/calendar/grid-header/README.md +13 -0
  6. package/dist/calendar/header-cell/README.md +14 -0
  7. package/dist/calendar/heading/README.md +13 -0
  8. package/dist/calendar/root/README.md +24 -0
  9. package/dist/calendar/trigger-next/README.md +14 -0
  10. package/dist/calendar/trigger-previous/README.md +14 -0
  11. package/dist/checkbox/README.md +53 -0
  12. package/dist/checkbox/TODO.md +16 -0
  13. package/dist/checkbox/index.d.ts +6 -0
  14. package/dist/checkbox/index.js +6 -0
  15. package/dist/checkbox/index.parts.d.ts +2 -0
  16. package/dist/checkbox/index.parts.js +2 -0
  17. package/dist/checkbox/indicator/README.md +23 -0
  18. package/dist/checkbox/indicator/checkbox-indicator.svelte +43 -0
  19. package/dist/checkbox/indicator/checkbox-indicator.svelte.d.ts +10 -0
  20. package/dist/checkbox/root/README.md +47 -0
  21. package/dist/checkbox/root/checkbox-label-test.svelte +10 -0
  22. package/dist/checkbox/root/checkbox-label-test.svelte.d.ts +18 -0
  23. package/dist/checkbox/root/checkbox-root.svelte +361 -0
  24. package/dist/checkbox/root/checkbox-root.svelte.d.ts +23 -0
  25. package/dist/checkbox/root/checkbox-test.svelte +59 -0
  26. package/dist/checkbox/root/checkbox-test.svelte.d.ts +18 -0
  27. package/dist/checkbox/root/context.d.ts +21 -0
  28. package/dist/checkbox/root/context.js +15 -0
  29. package/dist/clock/README.md +75 -0
  30. package/dist/clock/axis/README.md +24 -0
  31. package/dist/clock/axis/clock-axis.svelte +37 -0
  32. package/dist/clock/axis/clock-axis.svelte.d.ts +8 -0
  33. package/dist/clock/hooks/use-wheel-scroll.svelte.d.ts +16 -0
  34. package/dist/clock/hooks/use-wheel-scroll.svelte.js +336 -0
  35. package/dist/clock/index.d.ts +10 -0
  36. package/dist/clock/index.js +10 -0
  37. package/dist/clock/index.parts.d.ts +4 -0
  38. package/dist/clock/index.parts.js +4 -0
  39. package/dist/clock/root/README.md +38 -0
  40. package/dist/clock/root/clock-root-test.svelte +62 -0
  41. package/dist/clock/root/clock-root-test.svelte.d.ts +14 -0
  42. package/dist/clock/root/clock-root.svelte +329 -0
  43. package/dist/clock/root/clock-root.svelte.d.ts +25 -0
  44. package/dist/clock/root/context.d.ts +22 -0
  45. package/dist/clock/root/context.js +15 -0
  46. package/dist/clock/root/resolve-visible-columns.d.ts +7 -0
  47. package/dist/clock/root/resolve-visible-columns.js +16 -0
  48. package/dist/clock/root/time-utils.d.ts +48 -0
  49. package/dist/clock/root/time-utils.js +314 -0
  50. package/dist/clock/root/wheel-options.d.ts +17 -0
  51. package/dist/clock/root/wheel-options.js +63 -0
  52. package/dist/clock/wheel-column/README.md +25 -0
  53. package/dist/clock/wheel-column/clock-wheel-column-bindable-test.svelte +16 -0
  54. package/dist/clock/wheel-column/clock-wheel-column-bindable-test.svelte.d.ts +3 -0
  55. package/dist/clock/wheel-column/clock-wheel-column-custom-snippet-test.svelte +29 -0
  56. package/dist/clock/wheel-column/clock-wheel-column-custom-snippet-test.svelte.d.ts +6 -0
  57. package/dist/clock/wheel-column/clock-wheel-column-default-height-test.svelte +11 -0
  58. package/dist/clock/wheel-column/clock-wheel-column-default-height-test.svelte.d.ts +3 -0
  59. package/dist/clock/wheel-column/clock-wheel-column-test.svelte +38 -0
  60. package/dist/clock/wheel-column/clock-wheel-column-test.svelte.d.ts +12 -0
  61. package/dist/clock/wheel-column/clock-wheel-column-tp-test.svelte +38 -0
  62. package/dist/clock/wheel-column/clock-wheel-column-tp-test.svelte.d.ts +12 -0
  63. package/dist/clock/wheel-column/clock-wheel-column-untagged-snippet-test.svelte +29 -0
  64. package/dist/clock/wheel-column/clock-wheel-column-untagged-snippet-test.svelte.d.ts +6 -0
  65. package/dist/clock/wheel-column/clock-wheel-column.svelte +499 -0
  66. package/dist/clock/wheel-column/clock-wheel-column.svelte.d.ts +17 -0
  67. package/dist/clock/wheel-item/README.md +17 -0
  68. package/dist/clock/wheel-item/clock-wheel-item.svelte +49 -0
  69. package/dist/clock/wheel-item/clock-wheel-item.svelte.d.ts +17 -0
  70. package/dist/combobox/list/combobox-listbox.svelte.d.ts +1 -1
  71. package/dist/datepicker/TODO.md +2 -2
  72. package/dist/datepicker/calendar/README.md +19 -0
  73. package/dist/datepicker/input/README.md +15 -0
  74. package/dist/datepicker/popover/README.md +20 -0
  75. package/dist/datepicker/root/README.md +38 -0
  76. package/dist/datepicker/segment/README.md +14 -0
  77. package/dist/datepicker/trigger/README.md +14 -0
  78. package/dist/index.d.ts +9 -0
  79. package/dist/index.js +9 -0
  80. package/dist/primitives/focus-trap.js +11 -12
  81. package/dist/primitives/input-modality.js +10 -1
  82. package/dist/table/IMPLEMENTATION_NOTES.md +8 -0
  83. package/dist/table/PLAN-HIDDEN-COLUMNS.md +152 -0
  84. package/dist/table/PLAN.md +924 -0
  85. package/dist/table/README.md +116 -0
  86. package/dist/table/SELECTION_CHECKBOX_PLAN.md +234 -0
  87. package/dist/table/TODO.md +100 -0
  88. package/dist/table/body/README.md +24 -0
  89. package/dist/table/body/table-body.svelte +25 -0
  90. package/dist/table/body/table-body.svelte.d.ts +9 -0
  91. package/dist/table/cell/README.md +25 -0
  92. package/dist/table/cell/table-cell.svelte +247 -0
  93. package/dist/table/cell/table-cell.svelte.d.ts +9 -0
  94. package/dist/table/checkbox/README.md +38 -0
  95. package/dist/table/checkbox/table-checkbox-test.svelte +121 -0
  96. package/dist/table/checkbox/table-checkbox-test.svelte.d.ts +16 -0
  97. package/dist/table/checkbox/table-checkbox.svelte +274 -0
  98. package/dist/table/checkbox/table-checkbox.svelte.d.ts +13 -0
  99. package/dist/table/checkbox-indicator/README.md +29 -0
  100. package/dist/table/checkbox-indicator/table-checkbox-indicator.svelte +22 -0
  101. package/dist/table/checkbox-indicator/table-checkbox-indicator.svelte.d.ts +10 -0
  102. package/dist/table/column/README.md +32 -0
  103. package/dist/table/column/table-column.svelte +108 -0
  104. package/dist/table/column/table-column.svelte.d.ts +18 -0
  105. package/dist/table/column-header-cell/README.md +28 -0
  106. package/dist/table/column-header-cell/table-column-header-cell.svelte +281 -0
  107. package/dist/table/column-header-cell/table-column-header-cell.svelte.d.ts +9 -0
  108. package/dist/table/column-resizer/README.md +32 -0
  109. package/dist/table/column-resizer/table-column-resizer-freeze-layout-test.svelte +51 -0
  110. package/dist/table/column-resizer/table-column-resizer-freeze-layout-test.svelte.d.ts +3 -0
  111. package/dist/table/column-resizer/table-column-resizer-selection-column-test.svelte +83 -0
  112. package/dist/table/column-resizer/table-column-resizer-selection-column-test.svelte.d.ts +3 -0
  113. package/dist/table/column-resizer/table-column-resizer-test.svelte +75 -0
  114. package/dist/table/column-resizer/table-column-resizer-test.svelte.d.ts +3 -0
  115. package/dist/table/column-resizer/table-column-resizer.svelte +616 -0
  116. package/dist/table/column-resizer/table-column-resizer.svelte.d.ts +11 -0
  117. package/dist/table/empty-state/README.md +25 -0
  118. package/dist/table/empty-state/table-empty-state.svelte +38 -0
  119. package/dist/table/empty-state/table-empty-state.svelte.d.ts +8 -0
  120. package/dist/table/footer/README.md +24 -0
  121. package/dist/table/footer/table-footer.svelte +19 -0
  122. package/dist/table/footer/table-footer.svelte.d.ts +9 -0
  123. package/dist/table/header/README.md +24 -0
  124. package/dist/table/header/table-header.svelte +19 -0
  125. package/dist/table/header/table-header.svelte.d.ts +9 -0
  126. package/dist/table/index.d.ts +16 -0
  127. package/dist/table/index.js +16 -0
  128. package/dist/table/index.parts.d.ts +12 -0
  129. package/dist/table/index.parts.js +12 -0
  130. package/dist/table/root/README.md +56 -0
  131. package/dist/table/root/context.d.ts +198 -0
  132. package/dist/table/root/context.js +1426 -0
  133. package/dist/table/root/table-reorder-test.svelte +64 -0
  134. package/dist/table/root/table-reorder-test.svelte.d.ts +3 -0
  135. package/dist/table/root/table-root.svelte +410 -0
  136. package/dist/table/root/table-root.svelte.d.ts +29 -0
  137. package/dist/table/root/table-test.svelte +165 -0
  138. package/dist/table/root/table-test.svelte.d.ts +25 -0
  139. package/dist/table/row/README.md +27 -0
  140. package/dist/table/row/table-row.svelte +321 -0
  141. package/dist/table/row/table-row.svelte.d.ts +13 -0
  142. package/dist/timepicker/IMPLEMENTATION_PLAN.md +254 -0
  143. package/dist/timepicker/README.md +97 -0
  144. package/dist/timepicker/TODO.md +86 -0
  145. package/dist/timepicker/clock/README.md +14 -0
  146. package/dist/timepicker/clock/time-picker-clock-test.svelte +45 -0
  147. package/dist/timepicker/clock/time-picker-clock-test.svelte.d.ts +11 -0
  148. package/dist/timepicker/clock/time-picker-clock.svelte +65 -0
  149. package/dist/timepicker/clock/time-picker-clock.svelte.d.ts +10 -0
  150. package/dist/timepicker/index.d.ts +14 -0
  151. package/dist/timepicker/index.js +14 -0
  152. package/dist/timepicker/index.parts.d.ts +8 -0
  153. package/dist/timepicker/index.parts.js +8 -0
  154. package/dist/timepicker/input/README.md +15 -0
  155. package/dist/timepicker/input/time-picker-input-forwarding-test.svelte +40 -0
  156. package/dist/timepicker/input/time-picker-input-forwarding-test.svelte.d.ts +3 -0
  157. package/dist/timepicker/input/time-picker-input.svelte +109 -0
  158. package/dist/timepicker/input/time-picker-input.svelte.d.ts +11 -0
  159. package/dist/timepicker/internal/strict-props.d.ts +4 -0
  160. package/dist/timepicker/internal/strict-props.js +51 -0
  161. package/dist/timepicker/popover/README.md +20 -0
  162. package/dist/timepicker/popover/time-picker-popover-unsafe-props-test.svelte +22 -0
  163. package/dist/timepicker/popover/time-picker-popover-unsafe-props-test.svelte.d.ts +3 -0
  164. package/dist/timepicker/popover/time-picker-popover.svelte +89 -0
  165. package/dist/timepicker/popover/time-picker-popover.svelte.d.ts +7 -0
  166. package/dist/timepicker/root/README.md +42 -0
  167. package/dist/timepicker/root/context.d.ts +51 -0
  168. package/dist/timepicker/root/context.js +15 -0
  169. package/dist/timepicker/root/time-picker-12h-test.svelte +22 -0
  170. package/dist/timepicker/root/time-picker-12h-test.svelte.d.ts +3 -0
  171. package/dist/timepicker/root/time-picker-bindable-test.svelte +25 -0
  172. package/dist/timepicker/root/time-picker-bindable-test.svelte.d.ts +3 -0
  173. package/dist/timepicker/root/time-picker-empty-test.svelte +20 -0
  174. package/dist/timepicker/root/time-picker-empty-test.svelte.d.ts +3 -0
  175. package/dist/timepicker/root/time-picker-root.svelte +625 -0
  176. package/dist/timepicker/root/time-picker-root.svelte.d.ts +28 -0
  177. package/dist/timepicker/root/time-picker-test.svelte +72 -0
  178. package/dist/timepicker/root/time-picker-test.svelte.d.ts +15 -0
  179. package/dist/timepicker/root/time-utils.d.ts +1 -0
  180. package/dist/timepicker/root/time-utils.js +3 -0
  181. package/dist/timepicker/segment/README.md +14 -0
  182. package/dist/timepicker/segment/time-picker-segment.svelte +365 -0
  183. package/dist/timepicker/segment/time-picker-segment.svelte.d.ts +9 -0
  184. package/dist/timepicker/trigger/README.md +14 -0
  185. package/dist/timepicker/trigger/time-picker-trigger-forwarding-test.svelte +35 -0
  186. package/dist/timepicker/trigger/time-picker-trigger-forwarding-test.svelte.d.ts +3 -0
  187. package/dist/timepicker/trigger/time-picker-trigger.svelte +122 -0
  188. package/dist/timepicker/trigger/time-picker-trigger.svelte.d.ts +9 -0
  189. package/package.json +21 -1
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type TableCheckboxProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children' | 'class' | 'id' | 'role' | 'tabindex' | 'aria-checked' | 'aria-disabled' | 'onclick' | 'onkeydown'> & {
4
+ id?: string;
5
+ title?: string;
6
+ children?: Snippet;
7
+ class?: string;
8
+ 'aria-label'?: string;
9
+ 'aria-labelledby'?: string;
10
+ };
11
+ declare const TableCheckbox: import("svelte").Component<TableCheckboxProps, {}, "">;
12
+ type TableCheckbox = ReturnType<typeof TableCheckbox>;
13
+ export default TableCheckbox;
@@ -0,0 +1,29 @@
1
+ # Table.CheckboxIndicator
2
+
3
+ ## API reference
4
+
5
+ ### Table.CheckboxIndicator
6
+
7
+ Name: `Table.CheckboxIndicator`
8
+ Description: Headless presence wrapper for indicator content inside `Table.Checkbox`. It renders when the checkbox is checked or indeterminate.
9
+
10
+ | Prop | Type | Default | Description |
11
+ | -------------- | --------------------------------- | ----------- | ------------------------------------------------------------------------------- |
12
+ | `keepMounted` | `boolean` | `false` | Keeps the indicator mounted while hidden when the checkbox is unchecked. |
13
+ | `children` | `Snippet` | `undefined` | Rendered indicator content, such as a check icon or dash icon. |
14
+ | `class` | `string` | `''` | CSS class names for the indicator wrapper. |
15
+ | `...restProps` | `HTMLAttributes<HTMLSpanElement>` | `-` | Additional native attributes forwarded to the composed checkbox indicator span. |
16
+
17
+ ## Usage notes
18
+
19
+ - Use `Table.CheckboxIndicator` inside `Table.Checkbox`.
20
+ - The part is headless and unstyled.
21
+ - It mirrors the visibility behavior of `Checkbox.Indicator` and renders for checked and indeterminate states.
22
+
23
+ ```svelte
24
+ <Table.Checkbox class="inline-flex h-5 w-5 items-center justify-center rounded border">
25
+ <Table.CheckboxIndicator>
26
+ <CheckIcon class="h-3.5 w-3.5" />
27
+ </Table.CheckboxIndicator>
28
+ </Table.Checkbox>
29
+ ```
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { Checkbox } from '../../checkbox';
5
+
6
+ type TableCheckboxIndicatorProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children' | 'class'> & {
7
+ keepMounted?: boolean;
8
+ children?: Snippet;
9
+ class?: string;
10
+ };
11
+
12
+ let {
13
+ keepMounted = false,
14
+ children,
15
+ class: className = '',
16
+ ...restProps
17
+ }: TableCheckboxIndicatorProps = $props();
18
+ </script>
19
+
20
+ <Checkbox.Indicator {keepMounted} class={className} {...restProps}>
21
+ {@render children?.()}
22
+ </Checkbox.Indicator>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type TableCheckboxIndicatorProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children' | 'class'> & {
4
+ keepMounted?: boolean;
5
+ children?: Snippet;
6
+ class?: string;
7
+ };
8
+ declare const TableCheckboxIndicator: import("svelte").Component<TableCheckboxIndicatorProps, {}, "">;
9
+ type TableCheckboxIndicator = ReturnType<typeof TableCheckboxIndicator>;
10
+ export default TableCheckboxIndicator;
@@ -0,0 +1,32 @@
1
+ <!-- markdownlint-disable MD024 MD060 -->
2
+
3
+ # Table.Column
4
+
5
+ ## API reference
6
+
7
+ ### Table.Column
8
+
9
+ Name: `Table.Column`
10
+ Description: Logical metadata wrapper for a header column. It does not render DOM and is used to register stable column identity, sorting capability, row-header semantics, and resize metadata.
11
+
12
+ | Prop | Type | Default | Description |
13
+ | ---------------- | --------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------- |
14
+ | `id` | `string` | `-` | Stable identifier for the column. |
15
+ | `allowsSorting` | `boolean` | `false` | Enables sorting for the wrapped header cell. |
16
+ | `allowsResizing` | `boolean` | `false` | Legacy explicit resize opt-in. `Table.ColumnResizer` now enables resizing automatically; keep this only for backward compatibility. |
17
+ | `isRowHeader` | `boolean` | `false` | Marks the associated body column as row-header cells. |
18
+ | `textValue` | `string` | `undefined` | Optional spoken label used by `Table.Root` sort announcements when it should differ from `id`. |
19
+ | `width` | `number \| \`${number}px\`` | `undefined` | Explicit column width hint. Px numbers are the first-class format for the current resize release. |
20
+ | `defaultWidth` | `number \| \`${number}px\`` | `undefined` | Uncontrolled initial width hint for the column. |
21
+ | `minWidth` | `number` | `undefined` | Minimum width in px enforced during resize interactions. |
22
+ | `maxWidth` | `number` | `undefined` | Maximum width in px enforced during resize interactions. |
23
+ | `children` | `Snippet` | `undefined` | Usually a single `Table.ColumnHeaderCell`. |
24
+
25
+ ### Context utilities
26
+
27
+ Name: `Table.Column` column context
28
+ Description: Provides column metadata to `Table.ColumnHeaderCell`.
29
+
30
+ | Prop | Type | Default | Description |
31
+ | ----------------------- | -------------------------- | ------- | -------------------------------------------------------------------- |
32
+ | `useTableColumnContext` | `() => TableColumnContext` | `-` | Reads the current column metadata and throws outside `Table.Column`. |
@@ -0,0 +1,108 @@
1
+ <script lang="ts">
2
+ import { onDestroy } from 'svelte';
3
+ import type { Snippet } from 'svelte';
4
+ import {
5
+ setTableColumnContext,
6
+ useTableContext,
7
+ useTableSectionContext,
8
+ type TableColumnWidth
9
+ } from '../root/context';
10
+
11
+ type TableColumnProps = {
12
+ id: string;
13
+ allowsSorting?: boolean;
14
+ /** @deprecated `Table.ColumnResizer` now enables resizing automatically. */
15
+ allowsResizing?: boolean;
16
+ isRowHeader?: boolean;
17
+ textValue?: string;
18
+ width?: TableColumnWidth;
19
+ defaultWidth?: TableColumnWidth;
20
+ minWidth?: number;
21
+ maxWidth?: number;
22
+ children?: Snippet;
23
+ };
24
+
25
+ let {
26
+ id,
27
+ allowsSorting = false,
28
+ allowsResizing = false,
29
+ isRowHeader = false,
30
+ textValue,
31
+ width,
32
+ defaultWidth,
33
+ minWidth,
34
+ maxWidth,
35
+ children
36
+ }: TableColumnProps = $props();
37
+
38
+ const table = useTableContext();
39
+ const section = useTableSectionContext();
40
+ const token = table.createInstanceToken('column');
41
+
42
+ if (section.section !== 'header') {
43
+ throw new Error('`Table.Column` must be used inside `Table.Header`.');
44
+ }
45
+
46
+ setTableColumnContext({
47
+ token,
48
+ get id() {
49
+ return id;
50
+ },
51
+ get allowsSorting() {
52
+ return allowsSorting;
53
+ },
54
+ get allowsResizing() {
55
+ return allowsResizing || table.isColumnResizable(id);
56
+ },
57
+ get isHidden() {
58
+ return table.isColumnHidden(id);
59
+ },
60
+ get isRowHeader() {
61
+ return isRowHeader;
62
+ },
63
+ get textValue() {
64
+ return textValue;
65
+ },
66
+ get width() {
67
+ return width;
68
+ },
69
+ get defaultWidth() {
70
+ return defaultWidth;
71
+ },
72
+ get minWidth() {
73
+ return minWidth;
74
+ },
75
+ get maxWidth() {
76
+ return maxWidth;
77
+ }
78
+ });
79
+
80
+ function syncColumnRegistration() {
81
+ table.registerColumn({
82
+ token,
83
+ id,
84
+ allowsSorting,
85
+ allowsResizing,
86
+ isRowHeader,
87
+ textValue,
88
+ width,
89
+ defaultWidth,
90
+ minWidth,
91
+ maxWidth
92
+ });
93
+ }
94
+
95
+ syncColumnRegistration();
96
+
97
+ $effect(() => {
98
+ syncColumnRegistration();
99
+ });
100
+
101
+ onDestroy(() => {
102
+ table.unregisterColumn(token);
103
+ });
104
+ </script>
105
+
106
+ {#if children}
107
+ {@render children()}
108
+ {/if}
@@ -0,0 +1,18 @@
1
+ import type { Snippet } from 'svelte';
2
+ import { type TableColumnWidth } from '../root/context';
3
+ type TableColumnProps = {
4
+ id: string;
5
+ allowsSorting?: boolean;
6
+ /** @deprecated `Table.ColumnResizer` now enables resizing automatically. */
7
+ allowsResizing?: boolean;
8
+ isRowHeader?: boolean;
9
+ textValue?: string;
10
+ width?: TableColumnWidth;
11
+ defaultWidth?: TableColumnWidth;
12
+ minWidth?: number;
13
+ maxWidth?: number;
14
+ children?: Snippet;
15
+ };
16
+ declare const TableColumn: import("svelte").Component<TableColumnProps, {}, "">;
17
+ type TableColumn = ReturnType<typeof TableColumn>;
18
+ export default TableColumn;
@@ -0,0 +1,28 @@
1
+ <!-- markdownlint-disable MD024 -->
2
+
3
+ # Table.ColumnHeaderCell
4
+
5
+ ## API reference
6
+
7
+ ### Table.ColumnHeaderCell
8
+
9
+ Name: `Table.ColumnHeaderCell`
10
+ Description: Focusable header cell for a column. It participates in roving focus and toggles sorting when the wrapping `Table.Column` allows it.
11
+
12
+ - When a nested control like `Table.ColumnResizer` receives focus, the header exposes `data-focus-within` / `data-focus-visible-within` instead of remaining `data-focused`.
13
+
14
+ | Prop | Type | Default | Description |
15
+ | ---------- | --------- | ----------- | --------------------------------- |
16
+ | `class` | `string` | `''` | Class names for the `th` element. |
17
+ | `children` | `Snippet` | `undefined` | Header content. |
18
+
19
+ ### Context utilities
20
+
21
+ Name: `Table.ColumnHeaderCell` dependencies
22
+ Description: Consumes both column and row context to register a navigable header cell.
23
+
24
+ | Prop | Type | Default | Description |
25
+ | ----------------------- | -------------------------- | ------- | ------------------------------------ |
26
+ | `useTableColumnContext` | `() => TableColumnContext` | `-` | Reads current column metadata. |
27
+ | `useTableRowContext` | `() => TableRowContext` | `-` | Reads the current row token. |
28
+ | `useTableContext` | `() => TableContext` | `-` | Reads table focus and sorting state. |
@@ -0,0 +1,281 @@
1
+ <script lang="ts">
2
+ import { onDestroy } from 'svelte';
3
+ import type { Snippet } from 'svelte';
4
+ import type { HTMLAttributes } from 'svelte/elements';
5
+ import {
6
+ setTableCellContext,
7
+ useTableColumnContext,
8
+ useTableContext,
9
+ useTableRowContext
10
+ } from '../root/context';
11
+ import {
12
+ shouldShowFocusVisible,
13
+ trackInteractionModality
14
+ } from '../../primitives/input-modality';
15
+
16
+ type TableColumnHeaderCellProps = Omit<HTMLAttributes<HTMLTableCellElement>, 'children'> & {
17
+ children?: Snippet;
18
+ class?: string;
19
+ };
20
+
21
+ let { children, class: className = '', ...restProps }: TableColumnHeaderCellProps = $props();
22
+
23
+ const table = useTableContext();
24
+ const column = useTableColumnContext();
25
+ const row = useTableRowContext();
26
+ const key = table.createInstanceToken('header-cell');
27
+ const focusVersion = table.focusVersion;
28
+ const layoutVersion = table.layoutVersion;
29
+ const sortVersion = table.sortVersion;
30
+ const widthVersion = table.widthVersion;
31
+
32
+ let element = $state<HTMLElement | undefined>(undefined);
33
+ let focusDelegate = $state<(() => HTMLElement | undefined) | undefined>(undefined);
34
+ let isElementFocused = $state(false);
35
+ let isElementFocusVisible = $state(false);
36
+ let isFocusWithin = $state(false);
37
+ let isFocusVisibleWithin = $state(false);
38
+ row.registerCellToken(key, () => element);
39
+ function syncHeaderCellRegistration() {
40
+ table.registerCell({
41
+ key,
42
+ rowToken: row.rowToken,
43
+ section: 'header',
44
+ columnToken: column.token,
45
+ element,
46
+ focusDelegate
47
+ });
48
+ }
49
+
50
+ function registerFocusDelegate(getElement: () => HTMLElement | undefined) {
51
+ focusDelegate = getElement;
52
+ }
53
+
54
+ function unregisterFocusDelegate() {
55
+ focusDelegate = undefined;
56
+ }
57
+
58
+ function notifyResizerPresent() {
59
+ // No-op. Header cells now always provide the positioning context that
60
+ // column resizers need so the handle is available in SSR HTML too.
61
+ }
62
+
63
+ function notifyResizerRemoved() {
64
+ // No-op. See notifyResizerPresent().
65
+ }
66
+
67
+ setTableCellContext({
68
+ cellKey: key,
69
+ registerFocusDelegate,
70
+ unregisterFocusDelegate,
71
+ notifyResizerPresent,
72
+ notifyResizerRemoved
73
+ });
74
+
75
+ syncHeaderCellRegistration();
76
+
77
+ $effect(() => {
78
+ syncHeaderCellRegistration();
79
+ });
80
+
81
+ onDestroy(() => {
82
+ row.unregisterCellToken(key);
83
+ table.unregisterCell(key);
84
+ });
85
+
86
+ const isFocused = $derived.by(() => {
87
+ void $focusVersion;
88
+ return table.isCellFocused(key) && isElementFocused;
89
+ });
90
+ const isFocusVisible = $derived.by(() => {
91
+ void $focusVersion;
92
+ return isFocused && isElementFocusVisible;
93
+ });
94
+ const sortDirection = $derived.by(() => {
95
+ void $sortVersion;
96
+ return table.getSortDirection(column.id);
97
+ });
98
+ const isHidden = $derived.by(() => {
99
+ void $layoutVersion;
100
+ return column.isHidden;
101
+ });
102
+
103
+ const columnWidth = $derived.by(() => {
104
+ void $widthVersion;
105
+ return table.getColumnWidth(column.id);
106
+ });
107
+ const visibleColumnIndex = $derived.by(() => {
108
+ void $layoutVersion;
109
+ return table.getVisibleColumnIndexByToken(column.token);
110
+ });
111
+ const headerTabIndex = $derived.by(() => {
112
+ if (isHidden || focusDelegate) return undefined;
113
+ return table.isCellTabStop(key) ? 0 : -1;
114
+ });
115
+
116
+ function focusResizerInHeader(target: HTMLElement | undefined) {
117
+ const resizer = target?.querySelector<HTMLElement>('[data-table-column-resizer="true"]');
118
+ if (!resizer) return false;
119
+ resizer.focus();
120
+ return document.activeElement === resizer;
121
+ }
122
+
123
+ function getSiblingHeaderCell(direction: 'left' | 'right') {
124
+ const headerRow = element?.closest('tr');
125
+ if (!headerRow || !element) return null;
126
+
127
+ const headerCells = Array.from(
128
+ headerRow.querySelectorAll<HTMLElement>('th[role="columnheader"]')
129
+ );
130
+ const currentIndex = headerCells.indexOf(element);
131
+ if (currentIndex < 0) return null;
132
+
133
+ return headerCells[currentIndex + (direction === 'left' ? -1 : 1)] ?? null;
134
+ }
135
+
136
+ function moveFocusIntoResizeHandle(direction: 'left' | 'right') {
137
+ if (direction === 'right') {
138
+ return focusResizerInHeader(element);
139
+ }
140
+
141
+ return focusResizerInHeader(getSiblingHeaderCell('left') ?? undefined);
142
+ }
143
+
144
+ function handleFocus() {
145
+ isElementFocused = true;
146
+ isElementFocusVisible = shouldShowFocusVisible(element ?? null);
147
+ table.setFocusedCell(key);
148
+ table.setFocusVisible(isElementFocusVisible);
149
+ }
150
+
151
+ function handleBlur() {
152
+ isElementFocused = false;
153
+ isElementFocusVisible = false;
154
+ }
155
+
156
+ function handleFocusIn(event: FocusEvent) {
157
+ if (event.target === element) {
158
+ isFocusWithin = false;
159
+ isFocusVisibleWithin = false;
160
+ return;
161
+ }
162
+ isFocusWithin = true;
163
+ isFocusVisibleWithin = shouldShowFocusVisible(event.target as HTMLElement | null);
164
+ }
165
+
166
+ function handleFocusOut(event: FocusEvent) {
167
+ const nextFocused = event.relatedTarget;
168
+ if (nextFocused instanceof Node && element?.contains(nextFocused)) return;
169
+ isFocusWithin = false;
170
+ isFocusVisibleWithin = false;
171
+ }
172
+
173
+ function handleClick() {
174
+ table.focusCellByKey(key);
175
+ if (table.consumeHeaderClickSuppression()) {
176
+ return;
177
+ }
178
+ if (column.allowsSorting) {
179
+ table.toggleSort(column.id);
180
+ }
181
+ }
182
+
183
+ function handleMouseDown(event: MouseEvent) {
184
+ trackInteractionModality(event, element ?? null);
185
+ isElementFocusVisible = false;
186
+ isFocusVisibleWithin = false;
187
+ table.setFocusVisible(false);
188
+ }
189
+
190
+ function handleKeyDown(event: KeyboardEvent) {
191
+ trackInteractionModality(event, element ?? null);
192
+
193
+ if ((event.ctrlKey || event.metaKey) && event.key === 'Home') {
194
+ event.preventDefault();
195
+ table.moveToGridStart();
196
+ return;
197
+ }
198
+
199
+ if ((event.ctrlKey || event.metaKey) && event.key === 'End') {
200
+ event.preventDefault();
201
+ table.moveToGridEnd();
202
+ return;
203
+ }
204
+
205
+ switch (event.key) {
206
+ case 'ArrowUp':
207
+ event.preventDefault();
208
+ table.moveFocus('up');
209
+ return;
210
+ case 'ArrowDown':
211
+ event.preventDefault();
212
+ table.moveFocus('down');
213
+ return;
214
+ case 'ArrowLeft':
215
+ event.preventDefault();
216
+ if (moveFocusIntoResizeHandle('left')) return;
217
+ table.moveFocus('left');
218
+ return;
219
+ case 'ArrowRight':
220
+ event.preventDefault();
221
+ if (moveFocusIntoResizeHandle('right')) return;
222
+ table.moveFocus('right');
223
+ return;
224
+ case 'Home':
225
+ event.preventDefault();
226
+ table.moveToRowStart();
227
+ return;
228
+ case 'End':
229
+ event.preventDefault();
230
+ table.moveToRowEnd();
231
+ return;
232
+ case 'Enter':
233
+ case ' ':
234
+ if (!column.allowsSorting) return;
235
+ event.preventDefault();
236
+ if (event.repeat) return;
237
+ table.toggleSort(column.id);
238
+ return;
239
+ }
240
+ }
241
+ </script>
242
+
243
+ <th
244
+ bind:this={element}
245
+ role="columnheader"
246
+ class={className}
247
+ tabindex={headerTabIndex}
248
+ aria-colindex={!isHidden && visibleColumnIndex >= 0 ? visibleColumnIndex + 1 : undefined}
249
+ aria-hidden={isHidden ? true : undefined}
250
+ aria-sort={column.allowsSorting ? (sortDirection ?? 'none') : undefined}
251
+ data-focused={isFocused ? 'true' : undefined}
252
+ data-focus-visible={isFocusVisible ? 'true' : undefined}
253
+ data-focus-within={isFocusWithin ? 'true' : undefined}
254
+ data-focus-visible-within={isFocusVisibleWithin ? 'true' : undefined}
255
+ data-sortable={column.allowsSorting || undefined}
256
+ data-sort-direction={sortDirection}
257
+ data-column-index={visibleColumnIndex >= 0 ? visibleColumnIndex : undefined}
258
+ style:width={columnWidth !== undefined ? `${columnWidth}px` : undefined}
259
+ style:display={isHidden ? 'none' : undefined}
260
+ onfocusin={handleFocusIn}
261
+ onfocusout={handleFocusOut}
262
+ onfocus={handleFocus}
263
+ onblur={handleBlur}
264
+ onclick={handleClick}
265
+ onmousedown={handleMouseDown}
266
+ onkeydown={handleKeyDown}
267
+ {...restProps}
268
+ >
269
+ <div
270
+ data-table-header-content
271
+ style:overflow="visible"
272
+ style:position="relative"
273
+ style:min-width="0"
274
+ style:width="100%"
275
+ style:height="100%"
276
+ >
277
+ {#if children}
278
+ {@render children()}
279
+ {/if}
280
+ </div>
281
+ </th>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type TableColumnHeaderCellProps = Omit<HTMLAttributes<HTMLTableCellElement>, 'children'> & {
4
+ children?: Snippet;
5
+ class?: string;
6
+ };
7
+ declare const TableColumnHeaderCell: import("svelte").Component<TableColumnHeaderCellProps, {}, "">;
8
+ type TableColumnHeaderCell = ReturnType<typeof TableColumnHeaderCell>;
9
+ export default TableColumnHeaderCell;
@@ -0,0 +1,32 @@
1
+ <!-- markdownlint-disable MD024 MD060 -->
2
+
3
+ # Table.ColumnResizer
4
+
5
+ ## API reference
6
+
7
+ ### Table.ColumnResizer
8
+
9
+ Name: `Table.ColumnResizer`
10
+ Description: Interactive resize handle for the current `Table.Column`. It must be composed inside `Table.ColumnHeaderCell`, and it resizes the column that owns the surrounding `Table.Column` context.
11
+
12
+ | Prop | Type | Default | Description |
13
+ | ----------- | --------- | ----------- | -------------------------------------------------------------------- |
14
+ | `step` | `number` | `16` | Keyboard resize delta in px for `ArrowLeft` / `ArrowRight`. |
15
+ | `shiftStep` | `number` | `48` | Larger keyboard resize delta in px for `Shift+ArrowLeft/ArrowRight`. |
16
+ | `class` | `string` | `''` | Class names for the resize handle element. |
17
+ | `children` | `Snippet` | `undefined` | Optional custom resize affordance content. |
18
+
19
+ ## Usage notes
20
+
21
+ - `Table.ColumnResizer` must be used inside `Table.ColumnHeaderCell`.
22
+ - Rendering `Table.ColumnResizer` inside `Table.ColumnHeaderCell` is enough to make the owning `Table.Column` resizable.
23
+ - `Table.Column.allowsResizing` is still accepted for backward compatibility, but it is no longer required.
24
+ - The handle resolves the active column from `Table.Column` context. It does not accept a separate `columnId` prop.
25
+ - Width state lives in `Table.Root` through `columnWidths` / `defaultColumnWidths`.
26
+ - Pointer resizing uses Pointer Events, so mouse, touch, and pen interactions share the same behavior.
27
+ - Keyboard resizing uses an explicit resize mode: focus the handle, press `Enter` to capture resize, use `ArrowLeft` / `ArrowRight` (plus `Shift`) to adjust the width, `Home` to jump to the minimum width, and `End` to auto-fit the column to its content width, then press `Enter` again to commit the width and keep focus on the handle.
28
+ - While keyboard resize mode is active, pressing `Escape` restores the starting width, exits resize mode, and returns focus to the owning header cell.
29
+ - Keyboard resizing uses the same resize lifecycle callbacks as pointer resizing and announces committed widths through a polite live region.
30
+ - During pointer drag, pressing `Escape` restores the starting width and cancels the resize interaction.
31
+ - Double-click still auto-fits the column to its content width.
32
+ - In RTL layouts, `ArrowLeft` and `ArrowRight` are inverted so the logical resize direction matches the visual layout.
@@ -0,0 +1,51 @@
1
+ <script lang="ts">
2
+ import { Table } from '../index';
3
+
4
+ let currentColumnWidths = $state<Map<string, number> | undefined>(undefined);
5
+ </script>
6
+
7
+ <div style="width: 640px;">
8
+ <Table.Root
9
+ aria-label="Freeze layout table"
10
+ bind:columnWidths={currentColumnWidths}
11
+ class="min-w-full border-collapse text-left"
12
+ >
13
+ <Table.Header>
14
+ <Table.Row>
15
+ <Table.Column id="email" isRowHeader textValue="Email" minWidth={80}>
16
+ <Table.ColumnHeaderCell>
17
+ <div class="flex items-center justify-between gap-3">
18
+ <span>Email address</span>
19
+ <Table.ColumnResizer
20
+ data-testid="freeze-email-resizer"
21
+ class="inline-flex w-3 cursor-col-resize justify-center"
22
+ />
23
+ </div>
24
+ </Table.ColumnHeaderCell>
25
+ </Table.Column>
26
+ <Table.Column id="group" textValue="Group" minWidth={80}>
27
+ <Table.ColumnHeaderCell>
28
+ <div class="flex items-center justify-between gap-3">
29
+ <span>Group membership</span>
30
+ <Table.ColumnResizer
31
+ data-testid="freeze-group-resizer"
32
+ class="inline-flex w-3 cursor-col-resize justify-center"
33
+ />
34
+ </div>
35
+ </Table.ColumnHeaderCell>
36
+ </Table.Column>
37
+ </Table.Row>
38
+ </Table.Header>
39
+
40
+ <Table.Body>
41
+ <Table.Row id="danilo">
42
+ <Table.Cell>danilo@example.com</Table.Cell>
43
+ <Table.Cell>Developer relations</Table.Cell>
44
+ </Table.Row>
45
+ </Table.Body>
46
+ </Table.Root>
47
+ </div>
48
+
49
+ <output data-testid="freeze-column-widths"
50
+ >{JSON.stringify(Object.fromEntries(currentColumnWidths ?? new Map()))}</output
51
+ >
@@ -0,0 +1,3 @@
1
+ declare const TableColumnResizerFreezeLayoutTest: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type TableColumnResizerFreezeLayoutTest = ReturnType<typeof TableColumnResizerFreezeLayoutTest>;
3
+ export default TableColumnResizerFreezeLayoutTest;