@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,247 @@
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
+ useTableContext,
8
+ useTableRowContext,
9
+ type TableSelectionKey
10
+ } from '../root/context';
11
+ import {
12
+ shouldShowFocusVisible,
13
+ trackInteractionModality
14
+ } from '../../primitives/input-modality';
15
+
16
+ type TableCellProps = Omit<HTMLAttributes<HTMLTableCellElement>, 'children'> & {
17
+ children?: Snippet;
18
+ class?: string;
19
+ };
20
+
21
+ let { children, class: className = '', ...restProps }: TableCellProps = $props();
22
+
23
+ const table = useTableContext();
24
+ const row = useTableRowContext();
25
+ const key = table.createInstanceToken('cell');
26
+ const layoutVersion = table.layoutVersion;
27
+ const focusVersion = table.focusVersion;
28
+ const selectionVersion = table.selectionVersion;
29
+ const cellOrderVersion = row.cellOrderVersion;
30
+
31
+ let element = $state<HTMLElement | undefined>(undefined);
32
+ let focusDelegate = $state<(() => HTMLElement | undefined) | undefined>(undefined);
33
+ row.registerCellToken(key, () => element);
34
+ const cellIndex = $derived.by(() => {
35
+ void $cellOrderVersion;
36
+ return row.getCellIndex(key);
37
+ });
38
+
39
+ function syncCellRegistration() {
40
+ if (row.section !== 'body') return;
41
+ table.registerCell({
42
+ key,
43
+ rowToken: row.rowToken,
44
+ section: 'body',
45
+ columnIndex: cellIndex,
46
+ element,
47
+ focusDelegate
48
+ });
49
+ }
50
+
51
+ function registerFocusDelegate(getElement: () => HTMLElement | undefined) {
52
+ focusDelegate = getElement;
53
+ }
54
+
55
+ function unregisterFocusDelegate() {
56
+ focusDelegate = undefined;
57
+ }
58
+
59
+ setTableCellContext({
60
+ cellKey: key,
61
+ registerFocusDelegate,
62
+ unregisterFocusDelegate
63
+ });
64
+
65
+ syncCellRegistration();
66
+
67
+ $effect(() => {
68
+ syncCellRegistration();
69
+ });
70
+
71
+ onDestroy(() => {
72
+ row.unregisterCellToken(key);
73
+ if (row.section === 'body') {
74
+ table.unregisterCell(key);
75
+ }
76
+ });
77
+
78
+ const column = $derived.by(() => {
79
+ void $layoutVersion;
80
+ return cellIndex >= 0 ? table.getColumnAt(cellIndex) : undefined;
81
+ });
82
+ const isColumnHidden = $derived.by(() => {
83
+ void $layoutVersion;
84
+ return column ? table.isColumnHidden(column.id) : false;
85
+ });
86
+ const visibleColumnIndex = $derived.by(() => {
87
+ void $layoutVersion;
88
+ return column ? table.getVisibleColumnIndexByToken(column.token) : -1;
89
+ });
90
+ const tagName = $derived(row.section === 'body' && column?.isRowHeader ? 'th' : 'td');
91
+ const role = $derived.by(() => {
92
+ if (isColumnHidden) return undefined;
93
+ if (row.section !== 'body') return undefined;
94
+ return column?.isRowHeader ? 'rowheader' : 'gridcell';
95
+ });
96
+ const isFocused = $derived.by(() => {
97
+ void $focusVersion;
98
+ return row.section === 'body' ? table.isCellFocused(key) : false;
99
+ });
100
+ const isFocusVisible = $derived.by(() => {
101
+ void $focusVersion;
102
+ return row.section === 'body' ? isFocused && table.focusVisible : false;
103
+ });
104
+ const isRowSelected = $derived.by(() => {
105
+ void $selectionVersion;
106
+ return row.section === 'body' ? table.isRowSelected(row.rowId) : false;
107
+ });
108
+ const isRowDisabled = $derived.by(() => {
109
+ void $selectionVersion;
110
+ return row.section === 'body' ? table.isRowDisabled(row.rowId, row.isDisabled) : row.isDisabled;
111
+ });
112
+ const isCellFocusable = $derived(row.section !== 'body' || !isRowDisabled);
113
+ const cellTabIndex = $derived.by(() => {
114
+ if (row.section !== 'body') return undefined;
115
+ if (isColumnHidden) return undefined;
116
+ if (!isCellFocusable) return undefined;
117
+ if (focusDelegate) return undefined;
118
+ return table.isCellTabStop(key) ? 0 : -1;
119
+ });
120
+
121
+ function handleFocus() {
122
+ if (row.section !== 'body' || isRowDisabled) return;
123
+ table.setFocusedCell(key);
124
+ table.setFocusVisible(shouldShowFocusVisible(element ?? null));
125
+ }
126
+
127
+ function handleClick(event: MouseEvent) {
128
+ if (row.section !== 'body') return;
129
+ if (isRowDisabled) return;
130
+ table.focusCellByKey(key);
131
+ table.pressRow(row.rowId as TableSelectionKey | undefined, {
132
+ shiftKey: event.shiftKey,
133
+ ctrlKey: event.ctrlKey,
134
+ metaKey: event.metaKey,
135
+ altKey: event.altKey
136
+ });
137
+ }
138
+
139
+ function handleMouseDown(event: MouseEvent) {
140
+ trackInteractionModality(event, element ?? null);
141
+ table.setFocusVisible(false);
142
+ }
143
+
144
+ function handleKeyDown(event: KeyboardEvent) {
145
+ if (row.section !== 'body') return;
146
+ trackInteractionModality(event, element ?? null);
147
+
148
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'a') {
149
+ if (table.selectionMode === 'multiple') {
150
+ event.preventDefault();
151
+ table.selectAllRows();
152
+ }
153
+ return;
154
+ }
155
+
156
+ if ((event.ctrlKey || event.metaKey) && event.key === 'Home') {
157
+ event.preventDefault();
158
+ table.moveToGridStart();
159
+ return;
160
+ }
161
+
162
+ if ((event.ctrlKey || event.metaKey) && event.key === 'End') {
163
+ event.preventDefault();
164
+ table.moveToGridEnd();
165
+ return;
166
+ }
167
+
168
+ switch (event.key) {
169
+ case 'ArrowUp':
170
+ event.preventDefault();
171
+ table.moveFocus('up', {
172
+ shiftKey: event.shiftKey,
173
+ ctrlKey: event.ctrlKey,
174
+ metaKey: event.metaKey,
175
+ altKey: event.altKey
176
+ });
177
+ return;
178
+ case 'ArrowDown':
179
+ event.preventDefault();
180
+ table.moveFocus('down', {
181
+ shiftKey: event.shiftKey,
182
+ ctrlKey: event.ctrlKey,
183
+ metaKey: event.metaKey,
184
+ altKey: event.altKey
185
+ });
186
+ return;
187
+ case 'ArrowLeft':
188
+ event.preventDefault();
189
+ table.moveFocus('left');
190
+ return;
191
+ case 'ArrowRight':
192
+ event.preventDefault();
193
+ table.moveFocus('right');
194
+ return;
195
+ case 'Home':
196
+ event.preventDefault();
197
+ table.moveToRowStart();
198
+ return;
199
+ case 'End':
200
+ event.preventDefault();
201
+ table.moveToRowEnd();
202
+ return;
203
+ case 'Enter':
204
+ case ' ':
205
+ event.preventDefault();
206
+ if (event.repeat) {
207
+ return;
208
+ }
209
+ if (!isRowDisabled) {
210
+ table.pressRow(row.rowId as TableSelectionKey | undefined, {
211
+ shiftKey: event.shiftKey,
212
+ ctrlKey: event.ctrlKey,
213
+ metaKey: event.metaKey,
214
+ altKey: event.altKey
215
+ });
216
+ }
217
+ return;
218
+ }
219
+ }
220
+ </script>
221
+
222
+ <svelte:element
223
+ this={tagName}
224
+ bind:this={element}
225
+ {role}
226
+ class={className}
227
+ tabindex={cellTabIndex}
228
+ scope={row.section === 'body' && column?.isRowHeader ? 'row' : undefined}
229
+ aria-colindex={!isColumnHidden && visibleColumnIndex >= 0 ? visibleColumnIndex + 1 : undefined}
230
+ aria-hidden={isColumnHidden ? true : undefined}
231
+ aria-disabled={row.section === 'body' && isRowDisabled ? true : undefined}
232
+ data-focused={isFocused ? 'true' : undefined}
233
+ data-focus-visible={isFocusVisible ? 'true' : undefined}
234
+ data-row-selected={isRowSelected ? 'true' : undefined}
235
+ data-disabled={isRowDisabled || undefined}
236
+ data-column-index={visibleColumnIndex >= 0 ? visibleColumnIndex : undefined}
237
+ style:display={isColumnHidden ? 'none' : undefined}
238
+ onfocus={handleFocus}
239
+ onclick={handleClick}
240
+ onmousedown={handleMouseDown}
241
+ onkeydown={handleKeyDown}
242
+ {...restProps}
243
+ >
244
+ {#if children}
245
+ {@render children()}
246
+ {/if}
247
+ </svelte:element>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type TableCellProps = Omit<HTMLAttributes<HTMLTableCellElement>, 'children'> & {
4
+ children?: Snippet;
5
+ class?: string;
6
+ };
7
+ declare const TableCell: import("svelte").Component<TableCellProps, {}, "">;
8
+ type TableCell = ReturnType<typeof TableCell>;
9
+ export default TableCell;
@@ -0,0 +1,38 @@
1
+ # Table.Checkbox
2
+
3
+ ## API reference
4
+
5
+ ### Table.Checkbox
6
+
7
+ Name: `Table.Checkbox`
8
+ Description: Headless selection-aware checkbox root for tables. In body cells it toggles the owning row. In header cells it becomes a select-all checkbox for multiple selection mode.
9
+
10
+ | Prop | Type | Default | Description |
11
+ | ----------------- | ------------------------------------------ | ----------- | ------------------------------------------------------------------------------------------------------- |
12
+ | `id` | `string` | `undefined` | Optional id forwarded to the composed checkbox root. |
13
+ | `title` | `string` | `undefined` | Optional title forwarded to the composed checkbox root. |
14
+ | `style` | `HTMLAttributes<HTMLSpanElement>['style']` | `undefined` | Optional inline style forwarded to the composed checkbox root. |
15
+ | `data-testid` | `string` | `undefined` | Test id forwarded to the composed checkbox root. |
16
+ | `children` | `Snippet` | `undefined` | Composed child content, typically `Table.CheckboxIndicator`. |
17
+ | `class` | `string` | `''` | CSS class names for the composed checkbox root element. |
18
+ | `aria-label` | `string` | `undefined` | Accessible label override. Defaults to `Select all rows` in headers and `Select row <id>` in body rows. |
19
+ | `aria-labelledby` | `string` | `undefined` | Accessible label source id when the checkbox should be named by external content. |
20
+
21
+ ## Usage notes
22
+
23
+ - Use `Table.Checkbox` inside `Table.Cell` or `Table.ColumnHeaderCell`.
24
+ - In body rows it mirrors and toggles the row selection state.
25
+ - In header cells it only renders when `selectionMode="multiple"` and toggles all selectable rows.
26
+ - When `selectionMode="none"`, the part renders nothing.
27
+ - `Table.Checkbox` is headless and unstyled. Apply classes from the consumer or docs layer.
28
+ - The checkbox receives DOM focus directly and is the intended visible focus target for selection controls inside the table.
29
+
30
+ ```svelte
31
+ <Table.Cell>
32
+ <Table.Checkbox class="inline-flex h-5 w-5 items-center justify-center rounded border">
33
+ <Table.CheckboxIndicator>
34
+ <CheckIcon class="h-3.5 w-3.5" />
35
+ </Table.CheckboxIndicator>
36
+ </Table.Checkbox>
37
+ </Table.Cell>
38
+ ```
@@ -0,0 +1,121 @@
1
+ <script lang="ts">
2
+ import { Table } from '../index';
3
+ import type {
4
+ TableSelectionBehavior,
5
+ TableSelectionKey,
6
+ TableSelectionMode
7
+ } from '../root/context';
8
+
9
+ type DemoRow = {
10
+ id: string;
11
+ email: string;
12
+ group: string;
13
+ };
14
+
15
+ const defaultRows: DemoRow[] = [
16
+ { id: 'danilo', email: 'danilo@example.com', group: 'Developer' },
17
+ { id: 'zahra', email: 'zahra@example.com', group: 'Admin' },
18
+ { id: 'jasper', email: 'jasper@example.com', group: 'Developer' }
19
+ ];
20
+
21
+ const checkboxStyle =
22
+ 'display:inline-flex;height:20px;width:20px;align-items:center;justify-content:center;border:1px solid currentColor;border-radius:4px;';
23
+ const indicatorStyle =
24
+ 'display:inline-flex;height:14px;width:14px;align-items:center;justify-content:center;';
25
+
26
+ type CheckboxTestProps = {
27
+ rows?: DemoRow[];
28
+ selectionMode?: TableSelectionMode;
29
+ selectionBehavior?: TableSelectionBehavior;
30
+ disabledKeys?: Iterable<TableSelectionKey>;
31
+ initialSelectedKeys?: Iterable<TableSelectionKey>;
32
+ };
33
+
34
+ let {
35
+ rows = defaultRows,
36
+ selectionMode = 'multiple',
37
+ selectionBehavior = 'toggle',
38
+ disabledKeys,
39
+ initialSelectedKeys
40
+ }: CheckboxTestProps = $props();
41
+
42
+ let currentSelectedKeys = $state<Set<TableSelectionKey>>(
43
+ new Set((() => initialSelectedKeys ?? [])())
44
+ );
45
+ </script>
46
+
47
+ <Table.Root
48
+ aria-label="Users table"
49
+ {selectionMode}
50
+ {selectionBehavior}
51
+ bind:selectedKeys={currentSelectedKeys}
52
+ {disabledKeys}
53
+ >
54
+ <Table.Header>
55
+ <Table.Row>
56
+ <Table.Column id="selection" textValue="Selection">
57
+ <Table.ColumnHeaderCell data-testid="selection-header-cell">
58
+ <Table.Checkbox style={checkboxStyle} data-testid="header-checkbox">
59
+ <Table.CheckboxIndicator style={indicatorStyle}>
60
+ <svg aria-hidden="true" viewBox="0 0 16 16" class="h-3.5 w-3.5">
61
+ <path
62
+ d="M3.75 8.5 6.75 11.5 12.25 5.5"
63
+ fill="none"
64
+ stroke="currentColor"
65
+ stroke-linecap="round"
66
+ stroke-linejoin="round"
67
+ stroke-width="2"
68
+ />
69
+ </svg>
70
+ </Table.CheckboxIndicator>
71
+ </Table.Checkbox>
72
+ </Table.ColumnHeaderCell>
73
+ </Table.Column>
74
+ <Table.Column id="email" isRowHeader textValue="Email">
75
+ <Table.ColumnHeaderCell>Email</Table.ColumnHeaderCell>
76
+ </Table.Column>
77
+ <Table.Column id="group" textValue="Group">
78
+ <Table.ColumnHeaderCell>Group</Table.ColumnHeaderCell>
79
+ </Table.Column>
80
+ </Table.Row>
81
+ </Table.Header>
82
+
83
+ <Table.Body>
84
+ {#each rows as row (row.id)}
85
+ <Table.Row
86
+ id={row.id}
87
+ isDisabled={disabledKeys ? Array.from(disabledKeys).includes(row.id) : false}
88
+ >
89
+ <Table.Cell data-testid={`selection-cell-${row.id}`}>
90
+ <Table.Checkbox style={checkboxStyle} data-testid={`row-checkbox-${row.id}`}>
91
+ <Table.CheckboxIndicator style={indicatorStyle}>
92
+ <svg aria-hidden="true" viewBox="0 0 16 16" class="h-3.5 w-3.5">
93
+ <path
94
+ d="M3.75 8.5 6.75 11.5 12.25 5.5"
95
+ fill="none"
96
+ stroke="currentColor"
97
+ stroke-linecap="round"
98
+ stroke-linejoin="round"
99
+ stroke-width="2"
100
+ />
101
+ </svg>
102
+ </Table.CheckboxIndicator>
103
+ </Table.Checkbox>
104
+ </Table.Cell>
105
+ <Table.Cell data-testid={`email-cell-${row.id}`}>{row.email}</Table.Cell>
106
+ <Table.Cell data-testid={`group-cell-${row.id}`}>{row.group}</Table.Cell>
107
+ </Table.Row>
108
+ {/each}
109
+ <Table.EmptyState>No users found.</Table.EmptyState>
110
+ </Table.Body>
111
+
112
+ <Table.Footer>
113
+ <Table.Row>
114
+ <Table.Cell />
115
+ <Table.Cell>Total</Table.Cell>
116
+ <Table.Cell>{rows.length} users</Table.Cell>
117
+ </Table.Row>
118
+ </Table.Footer>
119
+ </Table.Root>
120
+
121
+ <output data-testid="selected-keys">{JSON.stringify([...currentSelectedKeys])}</output>
@@ -0,0 +1,16 @@
1
+ import type { TableSelectionBehavior, TableSelectionKey, TableSelectionMode } from '../root/context';
2
+ type DemoRow = {
3
+ id: string;
4
+ email: string;
5
+ group: string;
6
+ };
7
+ type CheckboxTestProps = {
8
+ rows?: DemoRow[];
9
+ selectionMode?: TableSelectionMode;
10
+ selectionBehavior?: TableSelectionBehavior;
11
+ disabledKeys?: Iterable<TableSelectionKey>;
12
+ initialSelectedKeys?: Iterable<TableSelectionKey>;
13
+ };
14
+ declare const TableCheckboxTest: import("svelte").Component<CheckboxTestProps, {}, "">;
15
+ type TableCheckboxTest = ReturnType<typeof TableCheckboxTest>;
16
+ export default TableCheckboxTest;
@@ -0,0 +1,274 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { Checkbox } from '../../checkbox';
5
+ import {
6
+ useTableCellContext,
7
+ useTableContext,
8
+ useTableRowContext,
9
+ useTableSectionContext
10
+ } from '../root/context';
11
+ import {
12
+ shouldShowFocusVisible,
13
+ trackInteractionModality
14
+ } from '../../primitives/input-modality';
15
+
16
+ type TableCheckboxProps = Omit<
17
+ HTMLAttributes<HTMLSpanElement>,
18
+ | 'children'
19
+ | 'class'
20
+ | 'id'
21
+ | 'role'
22
+ | 'tabindex'
23
+ | 'aria-checked'
24
+ | 'aria-disabled'
25
+ | 'onclick'
26
+ | 'onkeydown'
27
+ > & {
28
+ id?: string;
29
+ title?: string;
30
+ children?: Snippet;
31
+ class?: string;
32
+ 'aria-label'?: string;
33
+ 'aria-labelledby'?: string;
34
+ };
35
+
36
+ let {
37
+ id,
38
+ title,
39
+ children,
40
+ class: className = '',
41
+ 'aria-label': ariaLabel,
42
+ 'aria-labelledby': ariaLabelledby,
43
+ ...restProps
44
+ }: TableCheckboxProps = $props();
45
+
46
+ const table = useTableContext();
47
+ const section = useTableSectionContext();
48
+ const row = useTableRowContext();
49
+ const cell = useTableCellContext();
50
+ const selectionVersion = table.selectionVersion;
51
+ const layoutVersion = table.layoutVersion;
52
+
53
+ let wrapperElement = $state<HTMLElement | undefined>(undefined);
54
+
55
+ const isVisible = $derived.by(() => {
56
+ if (table.selectionMode === 'none') return false;
57
+ if (section.section === 'footer') return false;
58
+ if (section.section === 'header') return table.selectionMode === 'multiple';
59
+ return section.section === 'body';
60
+ });
61
+
62
+ const checkboxState = $derived.by(() => {
63
+ void $selectionVersion;
64
+ void $layoutVersion;
65
+ return section.section === 'header' ? table.getSelectionCheckboxState() : 'none';
66
+ });
67
+
68
+ const isChecked = $derived.by(() => {
69
+ void $selectionVersion;
70
+ if (section.section === 'header') {
71
+ return checkboxState === 'all';
72
+ }
73
+ return section.section === 'body' ? table.isRowSelected(row.rowId) : false;
74
+ });
75
+
76
+ const isIndeterminate = $derived(section.section === 'header' && checkboxState === 'some');
77
+
78
+ const isDisabled = $derived.by(() => {
79
+ void $selectionVersion;
80
+ void $layoutVersion;
81
+ if (!isVisible) return true;
82
+ if (section.section === 'header') {
83
+ return !table.hasSelectableRows();
84
+ }
85
+ if (section.section === 'body') {
86
+ return table.isRowDisabled(row.rowId, row.isDisabled) || row.rowId === undefined;
87
+ }
88
+ return true;
89
+ });
90
+
91
+ const tabIndex = $derived.by(() => {
92
+ if (!isVisible || isDisabled) return undefined;
93
+ return table.isCellTabStop(cell.cellKey) ? 0 : -1;
94
+ });
95
+
96
+ const accessibleLabel = $derived.by(() => {
97
+ if (ariaLabel) return ariaLabel;
98
+ if (ariaLabelledby) return undefined;
99
+ if (section.section === 'header') return 'Select all rows';
100
+ return row.rowId !== undefined ? `Select row ${String(row.rowId)}` : 'Select row';
101
+ });
102
+
103
+ function getCheckboxRootElement() {
104
+ return wrapperElement?.querySelector<HTMLElement>('[data-checkbox-root="true"]') ?? undefined;
105
+ }
106
+
107
+ $effect(() => {
108
+ if (!isVisible || isDisabled) {
109
+ cell.unregisterFocusDelegate();
110
+ return;
111
+ }
112
+
113
+ cell.registerFocusDelegate(() => getCheckboxRootElement());
114
+
115
+ return () => {
116
+ cell.unregisterFocusDelegate();
117
+ };
118
+ });
119
+
120
+ $effect(() => {
121
+ const checkboxElement = getCheckboxRootElement();
122
+ if (!checkboxElement) return;
123
+
124
+ if (!isVisible || isDisabled || tabIndex === undefined) {
125
+ checkboxElement.removeAttribute('tabindex');
126
+ return;
127
+ }
128
+
129
+ checkboxElement.tabIndex = tabIndex;
130
+ });
131
+
132
+ function applySelection(nextChecked: boolean) {
133
+ if (isDisabled) return;
134
+
135
+ if (section.section === 'header') {
136
+ if (nextChecked) {
137
+ table.selectAllRows();
138
+ } else {
139
+ table.deselectAllRows();
140
+ }
141
+ return;
142
+ }
143
+
144
+ if (section.section === 'body') {
145
+ table.toggleRowSelection(row.rowId);
146
+ }
147
+ }
148
+
149
+ function handleFocusIn(event: FocusEvent) {
150
+ const target = event.target instanceof HTMLElement ? event.target : getCheckboxRootElement();
151
+ table.setFocusedCell(cell.cellKey);
152
+ table.setFocusVisible(shouldShowFocusVisible(target ?? null));
153
+ }
154
+
155
+ function handleFocusOut(event: FocusEvent) {
156
+ const nextFocused = event.relatedTarget;
157
+ if (nextFocused instanceof Node && wrapperElement?.contains(nextFocused)) return;
158
+ }
159
+
160
+ function handleMouseDown(event: MouseEvent) {
161
+ trackInteractionModality(event, getCheckboxRootElement() ?? null);
162
+ table.setFocusVisible(false);
163
+ event.stopPropagation();
164
+ }
165
+
166
+ function handleClick(event: MouseEvent) {
167
+ event.stopPropagation();
168
+ if (!isVisible || isDisabled) return;
169
+ table.focusCellByKey(cell.cellKey);
170
+ }
171
+
172
+ function handleKeyDown(event: KeyboardEvent) {
173
+ trackInteractionModality(event, getCheckboxRootElement() ?? null);
174
+
175
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'a') {
176
+ if (table.selectionMode === 'multiple') {
177
+ event.preventDefault();
178
+ event.stopPropagation();
179
+ table.selectAllRows();
180
+ }
181
+ return;
182
+ }
183
+
184
+ if ((event.ctrlKey || event.metaKey) && event.key === 'Home') {
185
+ event.preventDefault();
186
+ event.stopPropagation();
187
+ table.moveToGridStart();
188
+ return;
189
+ }
190
+
191
+ if ((event.ctrlKey || event.metaKey) && event.key === 'End') {
192
+ event.preventDefault();
193
+ event.stopPropagation();
194
+ table.moveToGridEnd();
195
+ return;
196
+ }
197
+
198
+ switch (event.key) {
199
+ case 'ArrowUp':
200
+ event.preventDefault();
201
+ event.stopPropagation();
202
+ table.moveFocus('up', {
203
+ shiftKey: event.shiftKey,
204
+ ctrlKey: event.ctrlKey,
205
+ metaKey: event.metaKey,
206
+ altKey: event.altKey
207
+ });
208
+ return;
209
+ case 'ArrowDown':
210
+ event.preventDefault();
211
+ event.stopPropagation();
212
+ table.moveFocus('down', {
213
+ shiftKey: event.shiftKey,
214
+ ctrlKey: event.ctrlKey,
215
+ metaKey: event.metaKey,
216
+ altKey: event.altKey
217
+ });
218
+ return;
219
+ case 'ArrowLeft':
220
+ event.preventDefault();
221
+ event.stopPropagation();
222
+ table.moveFocus('left');
223
+ return;
224
+ case 'ArrowRight':
225
+ event.preventDefault();
226
+ event.stopPropagation();
227
+ table.moveFocus('right');
228
+ return;
229
+ case 'Home':
230
+ event.preventDefault();
231
+ event.stopPropagation();
232
+ table.moveToRowStart();
233
+ return;
234
+ case 'End':
235
+ event.preventDefault();
236
+ event.stopPropagation();
237
+ table.moveToRowEnd();
238
+ return;
239
+ case 'Enter':
240
+ case ' ':
241
+ event.stopPropagation();
242
+ if (event.repeat || isDisabled) return;
243
+ return;
244
+ }
245
+ }
246
+ </script>
247
+
248
+ {#if isVisible}
249
+ <div
250
+ bind:this={wrapperElement}
251
+ role="presentation"
252
+ onfocusin={handleFocusIn}
253
+ onfocusout={handleFocusOut}
254
+ onmousedown={handleMouseDown}
255
+ onclick={handleClick}
256
+ onkeydown={handleKeyDown}
257
+ >
258
+ <Checkbox.Root
259
+ {id}
260
+ {isChecked}
261
+ {isIndeterminate}
262
+ {isDisabled}
263
+ onCheckedChange={applySelection}
264
+ {title}
265
+ aria-label={accessibleLabel}
266
+ aria-labelledby={ariaLabelledby}
267
+ data-table-checkbox="true"
268
+ class={className}
269
+ {...restProps}
270
+ >
271
+ {@render children?.()}
272
+ </Checkbox.Root>
273
+ </div>
274
+ {/if}