@human-kit/svelte-components 1.0.0-alpha.2 → 1.0.0-alpha.21

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 (407) hide show
  1. package/dist/FOCUS_STATE_CONTRACT.md +63 -0
  2. package/dist/FOCUS_STATE_REVIEW_TEMPLATE.md +70 -0
  3. package/dist/button/README.md +48 -0
  4. package/dist/button/TODO.md +13 -0
  5. package/dist/button/index.d.ts +5 -0
  6. package/dist/button/index.js +4 -0
  7. package/dist/button/index.parts.d.ts +1 -0
  8. package/dist/button/index.parts.js +1 -0
  9. package/dist/button/root/README.md +43 -0
  10. package/dist/button/root/button-root.svelte +393 -0
  11. package/dist/button/root/button-root.svelte.d.ts +21 -0
  12. package/dist/button/root/button-test.svelte +76 -0
  13. package/dist/button/root/button-test.svelte.d.ts +11 -0
  14. package/dist/calendar/README.md +2 -1
  15. package/dist/calendar/TODO.md +21 -107
  16. package/dist/calendar/body-cell/README.md +15 -0
  17. package/dist/calendar/body-cell/calendar-body-cell.svelte +116 -41
  18. package/dist/calendar/grid/README.md +13 -0
  19. package/dist/calendar/grid-body/README.md +13 -0
  20. package/dist/calendar/grid-header/README.md +13 -0
  21. package/dist/calendar/header-cell/README.md +14 -0
  22. package/dist/calendar/heading/README.md +13 -0
  23. package/dist/calendar/index.d.ts +3 -3
  24. package/dist/calendar/index.js +3 -3
  25. package/dist/calendar/root/README.md +24 -0
  26. package/dist/calendar/root/calendar-root-test.svelte +4 -0
  27. package/dist/calendar/root/calendar-root-test.svelte.d.ts +1 -0
  28. package/dist/calendar/root/calendar-root.svelte +3 -0
  29. package/dist/calendar/root/calendar-root.svelte.d.ts +1 -0
  30. package/dist/calendar/root/context.d.ts +4 -0
  31. package/dist/calendar/root/context.js +28 -25
  32. package/dist/calendar/root/date-utils.d.ts +1 -1
  33. package/dist/calendar/root/date-utils.js +16 -26
  34. package/dist/calendar/trigger-next/README.md +14 -0
  35. package/dist/calendar/trigger-next/calendar-trigger-next.svelte +9 -4
  36. package/dist/calendar/trigger-next/calendar-trigger-next.svelte.d.ts +2 -1
  37. package/dist/calendar/trigger-previous/README.md +14 -0
  38. package/dist/calendar/trigger-previous/calendar-trigger-previous.svelte +9 -4
  39. package/dist/calendar/trigger-previous/calendar-trigger-previous.svelte.d.ts +2 -1
  40. package/dist/checkbox/README.md +53 -0
  41. package/dist/checkbox/TODO.md +16 -0
  42. package/dist/checkbox/index.d.ts +6 -0
  43. package/dist/checkbox/index.js +6 -0
  44. package/dist/checkbox/index.parts.d.ts +2 -0
  45. package/dist/checkbox/index.parts.js +2 -0
  46. package/dist/checkbox/indicator/README.md +23 -0
  47. package/dist/checkbox/indicator/checkbox-indicator.svelte +43 -0
  48. package/dist/checkbox/indicator/checkbox-indicator.svelte.d.ts +10 -0
  49. package/dist/checkbox/root/README.md +47 -0
  50. package/dist/checkbox/root/checkbox-label-test.svelte +10 -0
  51. package/dist/checkbox/root/checkbox-label-test.svelte.d.ts +18 -0
  52. package/dist/checkbox/root/checkbox-root.svelte +386 -0
  53. package/dist/checkbox/root/checkbox-root.svelte.d.ts +29 -0
  54. package/dist/checkbox/root/checkbox-test.svelte +59 -0
  55. package/dist/checkbox/root/checkbox-test.svelte.d.ts +18 -0
  56. package/dist/checkbox/root/context.d.ts +21 -0
  57. package/dist/checkbox/root/context.js +15 -0
  58. package/dist/clock/README.md +75 -0
  59. package/dist/clock/axis/README.md +24 -0
  60. package/dist/clock/axis/clock-axis.svelte +37 -0
  61. package/dist/clock/axis/clock-axis.svelte.d.ts +8 -0
  62. package/dist/clock/hooks/use-wheel-scroll.svelte.d.ts +16 -0
  63. package/dist/clock/hooks/use-wheel-scroll.svelte.js +336 -0
  64. package/dist/clock/index.d.ts +10 -0
  65. package/dist/clock/index.js +10 -0
  66. package/dist/clock/index.parts.d.ts +4 -0
  67. package/dist/clock/index.parts.js +4 -0
  68. package/dist/clock/root/README.md +38 -0
  69. package/dist/clock/root/clock-root-test.svelte +62 -0
  70. package/dist/clock/root/clock-root-test.svelte.d.ts +14 -0
  71. package/dist/clock/root/clock-root.svelte +329 -0
  72. package/dist/clock/root/clock-root.svelte.d.ts +25 -0
  73. package/dist/clock/root/context.d.ts +22 -0
  74. package/dist/clock/root/context.js +15 -0
  75. package/dist/clock/root/resolve-visible-columns.d.ts +7 -0
  76. package/dist/clock/root/resolve-visible-columns.js +16 -0
  77. package/dist/clock/root/time-utils.d.ts +48 -0
  78. package/dist/clock/root/time-utils.js +314 -0
  79. package/dist/clock/root/wheel-options.d.ts +17 -0
  80. package/dist/clock/root/wheel-options.js +63 -0
  81. package/dist/clock/wheel-column/README.md +25 -0
  82. package/dist/clock/wheel-column/clock-wheel-column-bindable-test.svelte +16 -0
  83. package/dist/clock/wheel-column/clock-wheel-column-bindable-test.svelte.d.ts +3 -0
  84. package/dist/clock/wheel-column/clock-wheel-column-custom-snippet-test.svelte +29 -0
  85. package/dist/clock/wheel-column/clock-wheel-column-custom-snippet-test.svelte.d.ts +6 -0
  86. package/dist/clock/wheel-column/clock-wheel-column-default-height-test.svelte +11 -0
  87. package/dist/clock/wheel-column/clock-wheel-column-default-height-test.svelte.d.ts +3 -0
  88. package/dist/clock/wheel-column/clock-wheel-column-test.svelte +38 -0
  89. package/dist/clock/wheel-column/clock-wheel-column-test.svelte.d.ts +12 -0
  90. package/dist/clock/wheel-column/clock-wheel-column-tp-test.svelte +38 -0
  91. package/dist/clock/wheel-column/clock-wheel-column-tp-test.svelte.d.ts +12 -0
  92. package/dist/clock/wheel-column/clock-wheel-column-untagged-snippet-test.svelte +29 -0
  93. package/dist/clock/wheel-column/clock-wheel-column-untagged-snippet-test.svelte.d.ts +6 -0
  94. package/dist/clock/wheel-column/clock-wheel-column.svelte +499 -0
  95. package/dist/clock/wheel-column/clock-wheel-column.svelte.d.ts +17 -0
  96. package/dist/clock/wheel-item/README.md +17 -0
  97. package/dist/clock/wheel-item/clock-wheel-item.svelte +49 -0
  98. package/dist/clock/wheel-item/clock-wheel-item.svelte.d.ts +17 -0
  99. package/dist/combobox/README.md +8 -2
  100. package/dist/combobox/TODO.md +28 -175
  101. package/dist/combobox/button/README.md +8 -3
  102. package/dist/combobox/button/combobox-button-test.svelte +27 -0
  103. package/dist/combobox/button/combobox-button-test.svelte.d.ts +6 -0
  104. package/dist/combobox/button/combobox-button.svelte +10 -11
  105. package/dist/combobox/clear/README.md +21 -0
  106. package/dist/combobox/clear/combobox-clear-test.svelte +34 -0
  107. package/dist/combobox/clear/combobox-clear-test.svelte.d.ts +3 -0
  108. package/dist/combobox/clear/combobox-clear.svelte +61 -0
  109. package/dist/combobox/clear/combobox-clear.svelte.d.ts +9 -0
  110. package/dist/combobox/index.d.ts +5 -3
  111. package/dist/combobox/index.js +5 -3
  112. package/dist/combobox/index.parts.d.ts +2 -0
  113. package/dist/combobox/index.parts.js +2 -0
  114. package/dist/combobox/input/combobox-input.svelte +44 -12
  115. package/dist/combobox/item/combobox-item-implicit-text-test.svelte +1 -1
  116. package/dist/combobox/item/combobox-listboxitem.svelte +14 -11
  117. package/dist/combobox/item-indicator/combobox-item-indicator.svelte +4 -15
  118. package/dist/combobox/list/combobox-listbox.svelte +1 -0
  119. package/dist/combobox/list/combobox-listbox.svelte.d.ts +2 -1
  120. package/dist/combobox/popover/README.md +18 -4
  121. package/dist/combobox/popover/combobox-popover-props-test.svelte +38 -0
  122. package/dist/combobox/popover/combobox-popover-props-test.svelte.d.ts +11 -0
  123. package/dist/combobox/popover/combobox-popover.svelte +166 -23
  124. package/dist/combobox/popover/combobox-popover.svelte.d.ts +3 -3
  125. package/dist/combobox/popover/combobox-scrollable-list-test.svelte +23 -0
  126. package/dist/combobox/popover/combobox-scrollable-list-test.svelte.d.ts +18 -0
  127. package/dist/combobox/root/README.md +1 -0
  128. package/dist/combobox/root/combobox-multiselect-test.svelte +5 -3
  129. package/dist/combobox/root/combobox-multiselect-test.svelte.d.ts +1 -0
  130. package/dist/combobox/root/combobox-numeric-string-id-test.svelte +1 -1
  131. package/dist/combobox/root/combobox-test.svelte +23 -4
  132. package/dist/combobox/root/combobox-test.svelte.d.ts +2 -0
  133. package/dist/combobox/root/combobox.svelte +119 -13
  134. package/dist/combobox/root/combobox.svelte.d.ts +1 -0
  135. package/dist/combobox/root/context.d.ts +19 -1
  136. package/dist/combobox/tag-remove/combobox-tag-remove.svelte +3 -2
  137. package/dist/combobox/trigger/README.md +21 -0
  138. package/dist/combobox/trigger/combobox-trigger.svelte +56 -0
  139. package/dist/combobox/trigger/combobox-trigger.svelte.d.ts +9 -0
  140. package/dist/datepicker/README.md +100 -0
  141. package/dist/datepicker/TODO.md +28 -0
  142. package/dist/datepicker/calendar/README.md +19 -0
  143. package/dist/datepicker/calendar/date-picker-calendar-unsafe-props-test.svelte +60 -0
  144. package/dist/datepicker/calendar/date-picker-calendar-unsafe-props-test.svelte.d.ts +3 -0
  145. package/dist/datepicker/calendar/date-picker-calendar.svelte +65 -0
  146. package/dist/datepicker/calendar/date-picker-calendar.svelte.d.ts +10 -0
  147. package/dist/datepicker/index.d.ts +18 -0
  148. package/dist/datepicker/index.js +18 -0
  149. package/dist/datepicker/index.parts.d.ts +14 -0
  150. package/dist/datepicker/index.parts.js +14 -0
  151. package/dist/datepicker/input/README.md +15 -0
  152. package/dist/datepicker/input/date-picker-input.svelte +108 -0
  153. package/dist/datepicker/input/date-picker-input.svelte.d.ts +11 -0
  154. package/dist/datepicker/internal/strict-props.d.ts +2 -0
  155. package/dist/datepicker/internal/strict-props.js +28 -0
  156. package/dist/datepicker/popover/README.md +20 -0
  157. package/dist/datepicker/popover/date-picker-popover-handler-test.svelte +57 -0
  158. package/dist/datepicker/popover/date-picker-popover-handler-test.svelte.d.ts +3 -0
  159. package/dist/datepicker/popover/date-picker-popover-unsafe-props-test.svelte +45 -0
  160. package/dist/datepicker/popover/date-picker-popover-unsafe-props-test.svelte.d.ts +18 -0
  161. package/dist/datepicker/popover/date-picker-popover.svelte +87 -0
  162. package/dist/datepicker/popover/date-picker-popover.svelte.d.ts +7 -0
  163. package/dist/datepicker/root/README.md +38 -0
  164. package/dist/datepicker/root/context.d.ts +43 -0
  165. package/dist/datepicker/root/context.js +15 -0
  166. package/dist/datepicker/root/date-picker-bindable-empty-test.svelte +24 -0
  167. package/dist/datepicker/root/date-picker-bindable-empty-test.svelte.d.ts +3 -0
  168. package/dist/datepicker/root/date-picker-bindable-test.svelte +41 -0
  169. package/dist/datepicker/root/date-picker-bindable-test.svelte.d.ts +3 -0
  170. package/dist/datepicker/root/date-picker-empty-test.svelte +47 -0
  171. package/dist/datepicker/root/date-picker-empty-test.svelte.d.ts +3 -0
  172. package/dist/datepicker/root/date-picker-locale-typing-test.svelte +47 -0
  173. package/dist/datepicker/root/date-picker-locale-typing-test.svelte.d.ts +3 -0
  174. package/dist/datepicker/root/date-picker-open-cancel-test.svelte +54 -0
  175. package/dist/datepicker/root/date-picker-open-cancel-test.svelte.d.ts +8 -0
  176. package/dist/datepicker/root/date-picker-root.svelte +495 -0
  177. package/dist/datepicker/root/date-picker-root.svelte.d.ts +24 -0
  178. package/dist/datepicker/root/date-picker-test.svelte +86 -0
  179. package/dist/datepicker/root/date-picker-test.svelte.d.ts +13 -0
  180. package/dist/datepicker/root/date-utils.d.ts +17 -0
  181. package/dist/datepicker/root/date-utils.js +138 -0
  182. package/dist/datepicker/root/draft-evaluation.d.ts +13 -0
  183. package/dist/datepicker/root/draft-evaluation.js +56 -0
  184. package/dist/datepicker/root/focus-controller.d.ts +3 -0
  185. package/dist/datepicker/root/focus-controller.js +15 -0
  186. package/dist/datepicker/root/open-change.d.ts +5 -0
  187. package/dist/datepicker/root/open-change.js +13 -0
  188. package/dist/datepicker/root/open-controller.d.ts +7 -0
  189. package/dist/datepicker/root/open-controller.js +15 -0
  190. package/dist/datepicker/root/segment-controller.d.ts +8 -0
  191. package/dist/datepicker/root/segment-controller.js +53 -0
  192. package/dist/datepicker/root/segment-state.d.ts +18 -0
  193. package/dist/datepicker/root/segment-state.js +134 -0
  194. package/dist/datepicker/root/value-commit.d.ts +4 -0
  195. package/dist/datepicker/root/value-commit.js +8 -0
  196. package/dist/datepicker/segment/README.md +14 -0
  197. package/dist/datepicker/segment/date-picker-segment.svelte +319 -0
  198. package/dist/datepicker/segment/date-picker-segment.svelte.d.ts +9 -0
  199. package/dist/datepicker/trigger/README.md +14 -0
  200. package/dist/datepicker/trigger/date-picker-trigger.svelte +110 -0
  201. package/dist/datepicker/trigger/date-picker-trigger.svelte.d.ts +9 -0
  202. package/dist/dialog/content/dialog-content.svelte +6 -6
  203. package/dist/dialog/index.d.ts +3 -3
  204. package/dist/dialog/index.js +2 -2
  205. package/dist/dialog/root/context.d.ts +2 -1
  206. package/dist/dialog/root/dialog-root.svelte +9 -2
  207. package/dist/dialog/trigger/dialog-trigger.svelte +3 -0
  208. package/dist/hooks/use-virtual-focus.svelte.js +3 -1
  209. package/dist/index.d.ts +31 -17
  210. package/dist/index.js +31 -17
  211. package/dist/input/README.md +38 -0
  212. package/dist/input/TODO.md +12 -0
  213. package/dist/input/input-test.svelte +43 -0
  214. package/dist/input/input-test.svelte.d.ts +12 -0
  215. package/dist/input/input.svelte +151 -7
  216. package/dist/input/input.svelte.d.ts +8 -2
  217. package/dist/listbox/index.d.ts +3 -3
  218. package/dist/listbox/index.js +3 -3
  219. package/dist/listbox/item/README.md +2 -1
  220. package/dist/listbox/item/listbox-item.svelte +260 -6
  221. package/dist/listbox/item/listbox-item.svelte.d.ts +6 -0
  222. package/dist/listbox/root/context.d.ts +6 -0
  223. package/dist/listbox/root/context.js +23 -13
  224. package/dist/listbox/root/listbox-test.svelte +14 -2
  225. package/dist/listbox/root/listbox-test.svelte.d.ts +1 -0
  226. package/dist/listbox/root/listbox.svelte +49 -2
  227. package/dist/listbox/root/listbox.svelte.d.ts +4 -2
  228. package/dist/popover/README.md +10 -0
  229. package/dist/popover/content/README.md +11 -0
  230. package/dist/popover/content/popover-content-controlled-close-test.svelte +30 -0
  231. package/dist/popover/content/popover-content-controlled-close-test.svelte.d.ts +3 -0
  232. package/dist/popover/content/popover-content-standalone-test.svelte +28 -0
  233. package/dist/popover/content/popover-content-standalone-test.svelte.d.ts +6 -0
  234. package/dist/popover/content/popover-content-test.svelte +32 -2
  235. package/dist/popover/content/popover-content-test.svelte.d.ts +3 -1
  236. package/dist/popover/content/popover-content.svelte +315 -24
  237. package/dist/popover/content/popover-content.svelte.d.ts +5 -1
  238. package/dist/popover/index.d.ts +3 -3
  239. package/dist/popover/index.js +3 -5
  240. package/dist/popover/root/README.md +10 -15
  241. package/dist/popover/root/context.d.ts +16 -7
  242. package/dist/popover/root/context.js +0 -2
  243. package/dist/popover/root/focus-state.d.ts +4 -0
  244. package/dist/popover/root/focus-state.js +33 -0
  245. package/dist/popover/root/popover-root.svelte +90 -17
  246. package/dist/popover/root/popover-root.svelte.d.ts +2 -1
  247. package/dist/popover/root/popover-test.svelte +2 -1
  248. package/dist/popover/root/popover-test.svelte.d.ts +2 -1
  249. package/dist/popover/trigger/popover-trigger-button-root-test.svelte +17 -0
  250. package/dist/popover/trigger/popover-trigger-button-root-test.svelte.d.ts +18 -0
  251. package/dist/popover/trigger/popover-trigger-button.svelte +9 -7
  252. package/dist/popover/trigger/popover-trigger.svelte +17 -5
  253. package/dist/portal/portal.svelte +3 -1
  254. package/dist/primitives/click-outside.d.ts +1 -1
  255. package/dist/primitives/click-outside.js +1 -1
  256. package/dist/primitives/floating.js +12 -4
  257. package/dist/primitives/focus-trap.d.ts +7 -2
  258. package/dist/primitives/focus-trap.js +50 -17
  259. package/dist/primitives/index.d.ts +1 -0
  260. package/dist/primitives/index.js +1 -0
  261. package/dist/primitives/input-modality.d.ts +7 -0
  262. package/dist/primitives/input-modality.js +125 -0
  263. package/dist/primitives/keyboard-navigation.d.ts +1 -0
  264. package/dist/primitives/keyboard-navigation.js +17 -0
  265. package/dist/table/IMPLEMENTATION_NOTES.md +9 -0
  266. package/dist/table/PLAN-HIDDEN-COLUMNS.md +152 -0
  267. package/dist/table/PLAN.md +1336 -0
  268. package/dist/table/README.md +143 -0
  269. package/dist/table/SELECTION_CHECKBOX_PLAN.md +234 -0
  270. package/dist/table/TODO.md +138 -0
  271. package/dist/table/body/README.md +39 -0
  272. package/dist/table/body/table-body-items-test.svelte +45 -0
  273. package/dist/table/body/table-body-items-test.svelte.d.ts +18 -0
  274. package/dist/table/body/table-body.svelte +171 -0
  275. package/dist/table/body/table-body.svelte.d.ts +45 -0
  276. package/dist/table/cell/README.md +27 -0
  277. package/dist/table/cell/table-cell.svelte +253 -0
  278. package/dist/table/cell/table-cell.svelte.d.ts +4 -0
  279. package/dist/table/checkbox/README.md +40 -0
  280. package/dist/table/checkbox/table-checkbox-test.svelte +170 -0
  281. package/dist/table/checkbox/table-checkbox-test.svelte.d.ts +22 -0
  282. package/dist/table/checkbox/table-checkbox.svelte +235 -0
  283. package/dist/table/checkbox/table-checkbox.svelte.d.ts +4 -0
  284. package/dist/table/checkbox-indicator/README.md +31 -0
  285. package/dist/table/checkbox-indicator/table-checkbox-indicator.svelte +15 -0
  286. package/dist/table/checkbox-indicator/table-checkbox-indicator.svelte.d.ts +4 -0
  287. package/dist/table/column/README.md +36 -0
  288. package/dist/table/column/table-column.svelte +79 -0
  289. package/dist/table/column/table-column.svelte.d.ts +4 -0
  290. package/dist/table/column-header-cell/README.md +30 -0
  291. package/dist/table/column-header-cell/table-column-header-cell.svelte +271 -0
  292. package/dist/table/column-header-cell/table-column-header-cell.svelte.d.ts +4 -0
  293. package/dist/table/column-resizer/README.md +33 -0
  294. package/dist/table/column-resizer/table-column-resizer-fixed-width-test.svelte +57 -0
  295. package/dist/table/column-resizer/table-column-resizer-fixed-width-test.svelte.d.ts +3 -0
  296. package/dist/table/column-resizer/table-column-resizer-freeze-layout-test.svelte +52 -0
  297. package/dist/table/column-resizer/table-column-resizer-freeze-layout-test.svelte.d.ts +3 -0
  298. package/dist/table/column-resizer/table-column-resizer-narrow-min-width-test.svelte +76 -0
  299. package/dist/table/column-resizer/table-column-resizer-narrow-min-width-test.svelte.d.ts +3 -0
  300. package/dist/table/column-resizer/table-column-resizer-overflow-test.svelte +64 -0
  301. package/dist/table/column-resizer/table-column-resizer-overflow-test.svelte.d.ts +3 -0
  302. package/dist/table/column-resizer/table-column-resizer-padded-container-test.svelte +67 -0
  303. package/dist/table/column-resizer/table-column-resizer-padded-container-test.svelte.d.ts +3 -0
  304. package/dist/table/column-resizer/table-column-resizer-sandbox-overflow-test.svelte +87 -0
  305. package/dist/table/column-resizer/table-column-resizer-sandbox-overflow-test.svelte.d.ts +3 -0
  306. package/dist/table/column-resizer/table-column-resizer-selection-column-test.svelte +84 -0
  307. package/dist/table/column-resizer/table-column-resizer-selection-column-test.svelte.d.ts +3 -0
  308. package/dist/table/column-resizer/table-column-resizer-test.svelte +77 -0
  309. package/dist/table/column-resizer/table-column-resizer-test.svelte.d.ts +3 -0
  310. package/dist/table/column-resizer/table-column-resizer-three-column-relative-test.svelte +64 -0
  311. package/dist/table/column-resizer/table-column-resizer-three-column-relative-test.svelte.d.ts +3 -0
  312. package/dist/table/column-resizer/table-column-resizer.svelte +610 -0
  313. package/dist/table/column-resizer/table-column-resizer.svelte.d.ts +4 -0
  314. package/dist/table/empty-state/README.md +27 -0
  315. package/dist/table/empty-state/table-empty-state.svelte +33 -0
  316. package/dist/table/empty-state/table-empty-state.svelte.d.ts +4 -0
  317. package/dist/table/footer/README.md +26 -0
  318. package/dist/table/footer/table-footer.svelte +13 -0
  319. package/dist/table/footer/table-footer.svelte.d.ts +4 -0
  320. package/dist/table/header/README.md +26 -0
  321. package/dist/table/header/table-header.svelte +13 -0
  322. package/dist/table/header/table-header.svelte.d.ts +4 -0
  323. package/dist/table/index.d.ts +18 -0
  324. package/dist/table/index.js +17 -0
  325. package/dist/table/index.parts.d.ts +13 -0
  326. package/dist/table/index.parts.js +13 -0
  327. package/dist/table/root/README.md +66 -0
  328. package/dist/table/root/context.d.ts +233 -0
  329. package/dist/table/root/context.js +2153 -0
  330. package/dist/table/root/table-reorder-test.svelte +64 -0
  331. package/dist/table/root/table-reorder-test.svelte.d.ts +3 -0
  332. package/dist/table/root/table-root.svelte +561 -0
  333. package/dist/table/root/table-root.svelte.d.ts +4 -0
  334. package/dist/table/root/table-ssr-wrapper-column.svelte +48 -0
  335. package/dist/table/root/table-ssr-wrapper-column.svelte.d.ts +4 -0
  336. package/dist/table/root/table-ssr-wrapper-context.d.ts +11 -0
  337. package/dist/table/root/table-ssr-wrapper-context.js +13 -0
  338. package/dist/table/root/table-ssr-wrapper-test.svelte +57 -0
  339. package/dist/table/root/table-ssr-wrapper-test.svelte.d.ts +3 -0
  340. package/dist/table/root/table-test.svelte +206 -0
  341. package/dist/table/root/table-test.svelte.d.ts +29 -0
  342. package/dist/table/row/README.md +29 -0
  343. package/dist/table/row/table-row.svelte +244 -0
  344. package/dist/table/row/table-row.svelte.d.ts +4 -0
  345. package/dist/table/sort-trigger/README.md +45 -0
  346. package/dist/table/sort-trigger/table-sort-trigger.svelte +183 -0
  347. package/dist/table/sort-trigger/table-sort-trigger.svelte.d.ts +4 -0
  348. package/dist/table/types.d.ts +112 -0
  349. package/dist/table/types.js +1 -0
  350. package/dist/table/utils/handle-body-keydown.d.ts +13 -0
  351. package/dist/table/utils/handle-body-keydown.js +67 -0
  352. package/dist/table/utils/visually-hidden-style.d.ts +1 -0
  353. package/dist/table/utils/visually-hidden-style.js +1 -0
  354. package/dist/test-utils/focus-contract.d.ts +3 -0
  355. package/dist/test-utils/focus-contract.js +26 -0
  356. package/dist/timepicker/IMPLEMENTATION_PLAN.md +254 -0
  357. package/dist/timepicker/README.md +97 -0
  358. package/dist/timepicker/TODO.md +86 -0
  359. package/dist/timepicker/clock/README.md +14 -0
  360. package/dist/timepicker/clock/time-picker-clock-test.svelte +45 -0
  361. package/dist/timepicker/clock/time-picker-clock-test.svelte.d.ts +11 -0
  362. package/dist/timepicker/clock/time-picker-clock.svelte +65 -0
  363. package/dist/timepicker/clock/time-picker-clock.svelte.d.ts +10 -0
  364. package/dist/timepicker/index.d.ts +14 -0
  365. package/dist/timepicker/index.js +14 -0
  366. package/dist/timepicker/index.parts.d.ts +8 -0
  367. package/dist/timepicker/index.parts.js +8 -0
  368. package/dist/timepicker/input/README.md +15 -0
  369. package/dist/timepicker/input/time-picker-input-forwarding-test.svelte +40 -0
  370. package/dist/timepicker/input/time-picker-input-forwarding-test.svelte.d.ts +3 -0
  371. package/dist/timepicker/input/time-picker-input.svelte +109 -0
  372. package/dist/timepicker/input/time-picker-input.svelte.d.ts +11 -0
  373. package/dist/timepicker/internal/strict-props.d.ts +4 -0
  374. package/dist/timepicker/internal/strict-props.js +51 -0
  375. package/dist/timepicker/popover/README.md +20 -0
  376. package/dist/timepicker/popover/time-picker-popover-unsafe-props-test.svelte +22 -0
  377. package/dist/timepicker/popover/time-picker-popover-unsafe-props-test.svelte.d.ts +3 -0
  378. package/dist/timepicker/popover/time-picker-popover.svelte +89 -0
  379. package/dist/timepicker/popover/time-picker-popover.svelte.d.ts +7 -0
  380. package/dist/timepicker/root/README.md +42 -0
  381. package/dist/timepicker/root/context.d.ts +51 -0
  382. package/dist/timepicker/root/context.js +15 -0
  383. package/dist/timepicker/root/time-picker-12h-test.svelte +22 -0
  384. package/dist/timepicker/root/time-picker-12h-test.svelte.d.ts +3 -0
  385. package/dist/timepicker/root/time-picker-bindable-test.svelte +25 -0
  386. package/dist/timepicker/root/time-picker-bindable-test.svelte.d.ts +3 -0
  387. package/dist/timepicker/root/time-picker-empty-test.svelte +20 -0
  388. package/dist/timepicker/root/time-picker-empty-test.svelte.d.ts +3 -0
  389. package/dist/timepicker/root/time-picker-root.svelte +625 -0
  390. package/dist/timepicker/root/time-picker-root.svelte.d.ts +28 -0
  391. package/dist/timepicker/root/time-picker-test.svelte +72 -0
  392. package/dist/timepicker/root/time-picker-test.svelte.d.ts +15 -0
  393. package/dist/timepicker/root/time-utils.d.ts +1 -0
  394. package/dist/timepicker/root/time-utils.js +3 -0
  395. package/dist/timepicker/segment/README.md +14 -0
  396. package/dist/timepicker/segment/time-picker-segment.svelte +365 -0
  397. package/dist/timepicker/segment/time-picker-segment.svelte.d.ts +9 -0
  398. package/dist/timepicker/trigger/README.md +14 -0
  399. package/dist/timepicker/trigger/time-picker-trigger-forwarding-test.svelte +35 -0
  400. package/dist/timepicker/trigger/time-picker-trigger-forwarding-test.svelte.d.ts +3 -0
  401. package/dist/timepicker/trigger/time-picker-trigger.svelte +122 -0
  402. package/dist/timepicker/trigger/time-picker-trigger.svelte.d.ts +9 -0
  403. package/dist/utils/date-only.d.ts +11 -0
  404. package/dist/utils/date-only.js +53 -0
  405. package/dist/utils/index.d.ts +1 -0
  406. package/dist/utils/index.js +1 -0
  407. package/package.json +33 -2
@@ -0,0 +1,1336 @@
1
+ <!-- markdownlint-disable MD007 MD010 MD060 -->
2
+
3
+ # Table Plan
4
+
5
+ ## Goal
6
+
7
+ Design and implement a new public `Table` component for the Svelte library, using the React Aria Components usability model as the main reference, but adapting it to the repository conventions: part-based composition, centralized state in `Root`, typed context, Svelte 5 runes, colocated tests for each part, and component/part documentation.
8
+
9
+ ## Product Goals
10
+
11
+ - Provide a composable and readable API for headless tables.
12
+ - Prioritize robust and predictable keyboard navigation.
13
+ - Support row selection and sorting in the first version.
14
+ - Keep the initial scope reasonable so the component is not blocked by advanced features.
15
+ - Establish an internal foundation that can be extended later.
16
+
17
+ ## Repository References
18
+
19
+ ### Patterns to Follow
20
+
21
+ - Namespace-style exports and named exports, as in `ListBox`.
22
+ - Explicit typed context in `root/context.ts`.
23
+ - Controlled/uncontrolled state pattern in `Root`.
24
+ - Colocated interaction and accessibility tests.
25
+ - Base component README plus a README for each public part.
26
+
27
+ ### Relevant Components and Utilities
28
+
29
+ - `packages/svelte/src/lib/listbox`
30
+ - `packages/svelte/src/lib/calendar`
31
+ - `packages/svelte/src/lib/primitives/keyboard-navigation.ts`
32
+ - `packages/svelte/src/lib/FOCUS_STATE_CONTRACT.md`
33
+ - `packages/svelte/src/lib/test-utils/focus-contract.ts`
34
+
35
+ ## Decisions Already Made
36
+
37
+ - The v1 public API will be static/composable, not dynamic.
38
+ - `Table` must support cell focus and row-derived focus state.
39
+ - `Table.Column` is added to the anatomy to solve column metadata.
40
+ - `Table.Column` is a logical component with no DOM output; it registers column metadata in context. `Table.ColumnHeaderCell` renders the `<th>`.
41
+ - `Table.EmptyState` is a dedicated part for the body empty state.
42
+ - V1 must include:
43
+ - base anatomy
44
+ - `Table.Footer`
45
+ - `Table.EmptyState`
46
+ - keyboard grid navigation
47
+ - row selection
48
+ - sorting
49
+
50
+ ## V1 Scope
51
+
52
+ ### Proposed Public Anatomy
53
+
54
+ ```svelte
55
+ <Table.Root aria-label="Users">
56
+ <Table.Header>
57
+ <Table.Row>
58
+ <!-- Column es lógico (sin DOM), solo registra metadata -->
59
+ <!-- Column is logical (no DOM), it only registers metadata -->
60
+ <Table.Column id="email" isRowHeader>
61
+ <Table.ColumnHeaderCell>Email</Table.ColumnHeaderCell>
62
+ </Table.Column>
63
+ <Table.Column id="group" allowsSorting>
64
+ <Table.ColumnHeaderCell>Group</Table.ColumnHeaderCell>
65
+ </Table.Column>
66
+ </Table.Row>
67
+ </Table.Header>
68
+
69
+ <Table.Body>
70
+ <Table.Row id="danilo">
71
+ <Table.Cell>danilo@example.com</Table.Cell>
72
+ <Table.Cell>Developer</Table.Cell>
73
+ </Table.Row>
74
+ <Table.Row id="zahra">
75
+ <Table.Cell>zahra@example.com</Table.Cell>
76
+ <Table.Cell>Admin</Table.Cell>
77
+ </Table.Row>
78
+
79
+ <!-- Only shown when Body has no rows -->
80
+ <Table.EmptyState>No users found.</Table.EmptyState>
81
+ </Table.Body>
82
+
83
+ <Table.Footer>
84
+ <Table.Row>
85
+ <Table.Cell>Total</Table.Cell>
86
+ <Table.Cell>2 users</Table.Cell>
87
+ </Table.Row>
88
+ </Table.Footer>
89
+ </Table.Root>
90
+ ```
91
+
92
+ ### Target Public Parts
93
+
94
+ - `Table.Root`
95
+ - `Table.Column` — logical, no DOM
96
+ - `Table.Header`
97
+ - `Table.Body`
98
+ - `Table.EmptyState`
99
+ - `Table.Footer`
100
+ - `Table.Row`
101
+ - `Table.ColumnHeaderCell`
102
+ - `Table.Cell`
103
+
104
+ ## Semantics and Accessibility
105
+
106
+ ### Semantic Model
107
+
108
+ - The table should behave as an interactive table, not only as a passive `<table>`.
109
+ - The primary reference is a `grid` model with directional navigation.
110
+ - `Table.Root` must require an accessible name through `aria-label` or `aria-labelledby`.
111
+ - There must be support for marking a column as `isRowHeader` to improve screen reader announcements.
112
+
113
+ ### Recommended Semantic Rendering
114
+
115
+ - `Table.Root` should render `<table role="grid">`.
116
+ - `Table.Header` should render `<thead role="rowgroup">`.
117
+ - `Table.Body` should render `<tbody role="rowgroup">`.
118
+ - `Table.Footer` should render `<tfoot role="rowgroup">`.
119
+ - `Table.Row` should render `<tr role="row">`.
120
+ - `Table.ColumnHeaderCell` should render `<th role="columnheader">`.
121
+ - `Table.Cell` should render:
122
+ - `<th scope="row" role="rowheader">` when the associated column has `isRowHeader`
123
+ - `<td role="gridcell">` in all other cases
124
+
125
+ This combination preserves real HTML semantics while allowing interactive grid behavior without inventing a structure entirely based on `div`s.
126
+
127
+ ### Focus Model
128
+
129
+ Two related levels will be implemented:
130
+
131
+ 1. **DOM focus on cell/header cell**
132
+ - `Table.ColumnHeaderCell` and `Table.Cell` are the real navigation targets.
133
+ - This keeps the behavior close to React Aria Components.
134
+
135
+ 2. **Row-derived state**
136
+ - `Table.Row` exposes derived states such as focused row, selected row, or disabled row.
137
+ - This helps with styling and simplifies the visual experience.
138
+
139
+ ### Target Keyboard Support in V1
140
+
141
+ - `Tab` enters and leaves the grid.
142
+ - Arrow keys navigate between header cells and body cells.
143
+ - `Home` / `End` move to the start/end of the current row.
144
+ - `Ctrl/Cmd + Home` and `Ctrl/Cmd + End` may be evaluated as an enhancement if the 2D engine can support them without extra complexity.
145
+ - `Enter` / `Space` should trigger row selection when appropriate.
146
+ - Sortable headers should respond to keyboard input to change sort order.
147
+
148
+ ### Recommended Tabbability Strategy
149
+
150
+ - `Table.Root` should not be tabbable under normal conditions.
151
+ - Only one `Table.ColumnHeaderCell` or `Table.Cell` should have `tabindex="0"` at a time.
152
+ - All other navigable cells should have `tabindex="-1"`.
153
+ - When entering with `Tab` from outside:
154
+ - if there is a previously focused cell, focus is restored there
155
+ - otherwise focus enters the first navigable header cell
156
+ - if no header is navigable, focus enters the first navigable body cell
157
+ - `Shift+Tab` and `Tab` should allow the browser to leave the grid naturally from the active cell.
158
+
159
+ This preserves the roving tabindex pattern and avoids making `Root` compete with cells as a focus target.
160
+
161
+ ### Decision for `Footer`
162
+
163
+ - In v1, `Table.Footer` is mainly semantic/structural.
164
+ - It will not participate in the main grid focus flow unless a clear need appears during implementation.
165
+
166
+ ## Key Types
167
+
168
+ ```ts
169
+ /** Sort direction */
170
+ type SortDirection = 'ascending' | 'descending';
171
+
172
+ /** Active sort descriptor */
173
+ type SortDescriptor = {
174
+ column: string;
175
+ direction: SortDirection;
176
+ };
177
+
178
+ /** Row selection mode */
179
+ type SelectionMode = 'none' | 'single' | 'multiple';
180
+
181
+ /** Selectable key (Row ids) */
182
+ type SelectionKey = string | number;
183
+
184
+ /** Set of selected keys (Row ids) */
185
+ type SelectionSet = Set<SelectionKey>;
186
+
187
+ /** Coordinate within the 2D grid (global, header row 0 = row 0) */
188
+ type GridCoord = {
189
+ row: number;
190
+ col: number;
191
+ };
192
+ ```
193
+
194
+ ## Proposed V1 API
195
+
196
+ ### `Table.Root`
197
+
198
+ Responsibilities:
199
+
200
+ - register columns, rows, and cells
201
+ - maintain the 2D focus cursor
202
+ - expose controlled/uncontrolled selection state
203
+ - expose controlled/uncontrolled sorting state
204
+ - coordinate ARIA attributes and focus/selection data attributes
205
+
206
+ Tentative API:
207
+
208
+ - `aria-label` / `aria-labelledby` — required accessible name
209
+ - `selectionMode?: SelectionMode` — default `'none'`
210
+ - `selectedKeys?: SelectionSet`
211
+ - `defaultSelectedKeys?: SelectionSet`
212
+ - `onSelectionChange?: (keys: SelectionSet) => void`
213
+ - `sortDescriptor?: SortDescriptor`
214
+ - `defaultSortDescriptor?: SortDescriptor`
215
+ - `onSortChange?: (descriptor: SortDescriptor) => void`
216
+ - `disabledKeys?: Set<SelectionKey>` — disabled row ids
217
+ - `children`
218
+
219
+ ### `Table.Column`
220
+
221
+ > Logical component — does not render its own DOM element. Registers column metadata in `Root` context and wraps `ColumnHeaderCell`.
222
+
223
+ Responsibilities:
224
+
225
+ - define stable column identity
226
+ - register column metadata in context (sorting, row header)
227
+ - register column width constraints in context
228
+ - serve as the anchor for sorting and row header semantics
229
+ - serve as the anchor for width/resizing behavior
230
+
231
+ Tentative API:
232
+
233
+ - `id: string` — stable column identity
234
+ - `allowsSorting?: boolean`
235
+ - `isRowHeader?: boolean`
236
+ - `textValue?: string`
237
+ - `width?: number | string`
238
+ - `defaultWidth?: number | string`
239
+ - `minWidth?: number`
240
+ - `maxWidth?: number`
241
+
242
+ ### `Table.Header`
243
+
244
+ Responsibilities:
245
+
246
+ - contain header rows
247
+ - coordinate header semantics
248
+
249
+ ### `Table.Body`
250
+
251
+ Responsibilities:
252
+
253
+ - contain data rows
254
+ - coordinate `Table.EmptyState` visibility when there are no rows
255
+
256
+ Tentative API:
257
+
258
+ - `children`
259
+
260
+ > The empty state is handled through the dedicated `Table.EmptyState` part inside `Body`, not through a prop.
261
+
262
+ ### `Table.EmptyState`
263
+
264
+ Responsibilities:
265
+
266
+ - render empty-state content when `Body` has no rows
267
+ - hide itself automatically when rows exist
268
+ - not participate in the grid navigation model
269
+
270
+ Tentative API:
271
+
272
+ - `children`
273
+
274
+ Recommended semantics:
275
+
276
+ - `Table.EmptyState` should be a convenience part that internally renders a row and an empty-state cell.
277
+ - Its output should be equivalent to:
278
+
279
+ ```svelte
280
+ <tr role="row" data-empty>
281
+ <td role="gridcell" colspan={columnCount}>
282
+ {children}
283
+ </td>
284
+ </tr>
285
+ ```
286
+
287
+ - `colspan` should be resolved automatically from the number of registered columns.
288
+ - It should not be focusable or participate in 2D navigation.
289
+ - It should only be allowed inside `Table.Body`.
290
+
291
+ This avoids invalid markup inside `<tbody>` and keeps the API convenient for consumers.
292
+
293
+ ### `Table.Footer`
294
+
295
+ Responsibilities:
296
+
297
+ - contain summary/metadata rows
298
+ - not interfere with the main v1 navigation model
299
+
300
+ ### `Table.Row`
301
+
302
+ Responsibilities:
303
+
304
+ - row identity
305
+ - disabled state
306
+ - derived selection state
307
+ - derived focus/selection styling
308
+
309
+ Tentative API:
310
+
311
+ - `id`
312
+ - `isDisabled?: boolean`
313
+ - `textValue?: string`
314
+
315
+ ### `Table.ColumnHeaderCell`
316
+
317
+ Responsibilities:
318
+
319
+ - focus target in the header
320
+ - sorting trigger when the column allows it
321
+ - apply `aria-sort` automatically from `Root.sortDescriptor` and `Column.allowsSorting`
322
+ - `aria-sort` values: `ascending` | `descending` | `none`
323
+ - host the resize affordance when the consumer composes a `Table.ColumnResizer` inside it
324
+
325
+ ### `Table.ColumnResizer`
326
+
327
+ Responsibilities:
328
+
329
+ - interactive resize handle for the current `Table.Column`
330
+ - consume column identity from `Table.Column` context rather than matching by visual position
331
+ - support pointer drag and keyboard resizing
332
+ - expose resize state for styling through data attributes
333
+
334
+ Tentative API:
335
+
336
+ - `step?: number` — keyboard delta in px, default `16`
337
+ - `shiftStep?: number` — larger keyboard delta in px, default `48`
338
+ - `children?`
339
+ - `class?`
340
+
341
+ ### `Table.Cell`
342
+
343
+ Responsibilities:
344
+
345
+ - focus target within the body
346
+ - reflect derived row selection state
347
+ - support simple textual content in v1
348
+
349
+ ## Data Attributes by Part
350
+
351
+ | Part | Attribute | Values | Description |
352
+ | ------------------ | --------------------- | ---------------------------- | ---------------------------------------- |
353
+ | `Root` | `data-selection-mode` | `none`, `single`, `multiple` | Active selection mode |
354
+ | `Header` | `data-table-header` | `''` | Semantic marker |
355
+ | `Body` | `data-table-body` | `''` | Semantic marker |
356
+ | `Body` | `data-empty` | `''` | Present when Body has no rows |
357
+ | `Footer` | `data-table-footer` | `''` | Semantic marker |
358
+ | `Row` (body) | `data-focused` | `''` | The row contains the focused cell |
359
+ | `Row` (body) | `data-selected` | `''` | Selected row |
360
+ | `Row` (body) | `data-disabled` | `''` | Disabled row |
361
+ | `ColumnHeaderCell` | `data-focused` | `''` | Focused header cell |
362
+ | `ColumnHeaderCell` | `data-sortable` | `''` | The column allows sorting |
363
+ | `ColumnHeaderCell` | `data-sort-direction` | `ascending`, `descending` | Active sort direction |
364
+ | `Cell` | `data-focused` | `''` | Focused cell |
365
+ | `Cell` | `data-row-selected` | `''` | The row containing this cell is selected |
366
+
367
+ ## ARIA Attributes by Part
368
+
369
+ | Part | Attribute | Value | Description |
370
+ | ------------------ | -------------------------------- | --------------------------------- | ------------------------------------------------ |
371
+ | `Root` | `role` | `grid` | Interactive table |
372
+ | `Root` | `aria-label` / `aria-labelledby` | string | Accessible name (required) |
373
+ | `Root` | `aria-multiselectable` | `true` | Present when `selectionMode='multiple'` |
374
+ | `Header` | `role` | `rowgroup` | Header row group |
375
+ | `Body` | `role` | `rowgroup` | Data row group |
376
+ | `Footer` | `role` | `rowgroup` | Footer row group |
377
+ | `Row` | `role` | `row` | Row |
378
+ | `Row` (body) | `aria-selected` | `true` / `false` | Selection state (when `selectionMode != 'none'`) |
379
+ | `Row` (body) | `aria-disabled` | `true` | Disabled row |
380
+ | `ColumnHeaderCell` | `role` | `columnheader` | Column header |
381
+ | `ColumnHeaderCell` | `aria-sort` | `ascending`, `descending`, `none` | Sort direction (only when `allowsSorting`) |
382
+ | `Cell` | `role` | `gridcell` or `rowheader` | `rowheader` if the column has `isRowHeader` |
383
+ | `EmptyState` | `role` | `row` + internal `gridcell` | Semantic, non-navigable empty-state row |
384
+
385
+ ## What V1 Does Not Include
386
+
387
+ ### Out of Scope
388
+
389
+ - drag and drop
390
+ - async loading / load more
391
+ - API pública dinámica con `items` y `columns`
392
+ - row links / `href`-style navigation semantics
393
+ - typeahead
394
+ - focus management para elementos interactivos dentro de `Cell`
395
+ - focus management for interactive elements inside `Cell`
396
+ - nested headers / grouped columns
397
+ - cell selection
398
+ - cell selection
399
+ - virtualización
400
+ - virtualization
401
+ - soporte complejo de `colSpan` / `rowSpan`
402
+ - complex `colSpan` / `rowSpan` support
403
+ - footer navegable como parte del grid principal
404
+ - footer navigation as part of the main grid
405
+
406
+ ## Advanced Feature Matrix
407
+
408
+ | Feature | Main Complexity | Risk | Recommendation |
409
+ | ---------------------------------- | -------------------------------------------------------- | ----------- | ------------------ |
410
+ | Column resizing | width state, handles, pointer + keyboard, persistence | high | next planned phase |
411
+ | Drag and drop | reorder, drop targets, SR + keyboard + pointer | very high | keep out of v1 |
412
+ | Async loading / load more | scroll state, sentinel rows, partial states | high | keep out of v1 |
413
+ | Dynamic `items` / `columns` API | collection, stable ids, render functions, memoization | high | defer |
414
+ | Row actions / `onRowAction` | action-selection conflicts across mouse and keyboard | medium/high | next planned phase |
415
+ | Row links / `href` semantics | HTML limitations, router integration, native link parity | high | defer |
416
+ | Interactive content inside `Cell` | focus handoff between grid and nested controls | very high | keep out of v1 |
417
+ | Typeahead | depends on stable collection and consistent `textValue` | medium | defer |
418
+ | Nested headers / column groups | spans, navigation, and complex semantics | high | keep out of v1 |
419
+ | Cell selection | changes the entire interaction model | high | keep out of v1 |
420
+ | Full `selectionBehavior="replace"` | modifiers and fine-grained focus/selection semantics | medium/high | defer |
421
+ | Virtualization | strong decoupling between collection and DOM | very high | keep out of v1 |
422
+ | Integrated select-all | useful UX but depends on mature selection behavior | medium | phase 2 |
423
+ | Complex `colSpan` / `rowSpan` | breaks the rectangular grid model | high | defer |
424
+ | Navigable footer | adds another region to the focus model | medium | avoid in v1 |
425
+
426
+ ## Proposed Internal Architecture
427
+
428
+ ## Root State
429
+
430
+ Create `packages/svelte/src/lib/table/root/context.ts` with responsibilities for:
431
+
432
+ - column registration
433
+ - row registration
434
+ - cell registration by coordinate
435
+ - current focus resolution
436
+ - 2D navigation
437
+ - row selection state
438
+ - sorting state
439
+ - derived utilities for each part
440
+
441
+ ## 2D Navigation Engine
442
+
443
+ It is not enough to depend only on `createKeyboardNavigation()` because it currently solves linear navigation. `Table` needs a dedicated engine able to:
444
+
445
+ - know rows and columns by index
446
+ - move focus in two dimensions
447
+ - distinguish header and body
448
+ - resolve missing or disabled cells
449
+ - derive the active row from the active cell
450
+
451
+ ### Proposed Internal Interface
452
+
453
+ ```ts
454
+ interface GridNavigation {
455
+ /** Currently focused coordinate (global, header row 0 = row 0) */
456
+ focusedCoord: GridCoord;
457
+
458
+ /** Moves focus in the given direction, skipping non-navigable cells */
459
+ move(direction: 'up' | 'down' | 'left' | 'right'): void;
460
+
461
+ /** Moves focus to the first cell in the current row */
462
+ moveToRowStart(): void;
463
+
464
+ /** Moves focus to the last cell in the current row */
465
+ moveToRowEnd(): void;
466
+
467
+ /** Moves focus to the first cell in the grid (header[0][0]) */
468
+ moveToGridStart(): void;
469
+
470
+ /** Moves focus to the last cell in the body */
471
+ moveToGridEnd(): void;
472
+
473
+ /** Registers a navigable cell in the grid */
474
+ register(coord: GridCoord, element: HTMLElement): void;
475
+
476
+ /** Unregisters a cell from the grid */
477
+ unregister(coord: GridCoord): void;
478
+
479
+ /** Checks whether a coordinate is navigable (exists and is not disabled) */
480
+ isNavigable(coord: GridCoord): boolean;
481
+
482
+ /** Returns the body row index from a global coordinate */
483
+ toBodyRowIndex(globalRow: number): number | null;
484
+
485
+ /** Applies DOM focus to the element at the given coordinate */
486
+ focusCell(coord: GridCoord): void;
487
+ }
488
+ ```
489
+
490
+ ### Key-to-Action Mapping
491
+
492
+ | Key | Action |
493
+ | ------------------------ | -------------------------------------------------------- |
494
+ | `ArrowUp` | `move('up')` |
495
+ | `ArrowDown` | `move('down')` |
496
+ | `ArrowLeft` | `move('left')` |
497
+ | `ArrowRight` | `move('right')` |
498
+ | `Home` | `moveToRowStart()` |
499
+ | `End` | `moveToRowEnd()` |
500
+ | `Ctrl+Home` / `Cmd+Home` | `moveToGridStart()` |
501
+ | `Ctrl+End` / `Cmd+End` | `moveToGridEnd()` |
502
+ | `Tab` | Leaves the grid (focus to the next tabbable element) |
503
+ | `Shift+Tab` | Leaves the grid (focus to the previous tabbable element) |
504
+ | `Enter` / `Space` | Select row (body) or toggle sort (sortable header) |
505
+
506
+ ## Selection
507
+
508
+ - V1 selection is row-based.
509
+ - Focus must not be equivalent to selection.
510
+ - `single` and `multiple` must work in both controlled and uncontrolled mode.
511
+ - Disabled rows must not be selectable.
512
+
513
+ ## Sorting
514
+
515
+ - Sort state should live in `Root`.
516
+ - `Column` defines whether a column allows sorting.
517
+ - `ColumnHeaderCell` triggers sort changes.
518
+ - The component reflects state, but does not necessarily mutate data automatically; that remains the consumer's responsibility.
519
+
520
+ ## Planned File Structure
521
+
522
+ ### New Component Files
523
+
524
+ - `packages/svelte/src/lib/table/index.ts`
525
+ - `packages/svelte/src/lib/table/index.parts.ts`
526
+ - `packages/svelte/src/lib/table/README.md`
527
+ - `packages/svelte/src/lib/table/TODO.md`
528
+
529
+ ### Root
530
+
531
+ - `packages/svelte/src/lib/table/root/table-root.svelte`
532
+ - `packages/svelte/src/lib/table/root/context.ts`
533
+ - `packages/svelte/src/lib/table/root/table-root.test.ts`
534
+ - `packages/svelte/src/lib/table/root/table-test.svelte`
535
+ - `packages/svelte/src/lib/table/root/README.md`
536
+
537
+ ### Public Parts
538
+
539
+ - `packages/svelte/src/lib/table/column/table-column.svelte`
540
+ - `packages/svelte/src/lib/table/column/README.md`
541
+ - `packages/svelte/src/lib/table/column/table-column.test.ts`
542
+
543
+ ## Phase 2: Column Resizing Plan
544
+
545
+ ### Resize Goal
546
+
547
+ Add column resizing in a way that follows the React Aria Components mental model while preserving the repository's existing `Table` architecture: logical `Table.Column`, state in `Table.Root`, typed context, native table semantics, and composable parts.
548
+
549
+ ### Functional Contract
550
+
551
+ - Resizing must target the column whose composition includes the resize handle.
552
+ - The resize handle must live inside the header composition for that column, not in a parallel list of handlers.
553
+ - The active column is resolved from `Table.Column` context, never by visual index guessing alone.
554
+ - Functional behavior should mirror RAC:
555
+ - a column opts into resizing via column metadata
556
+ - a dedicated resizer part provides the interactive affordance
557
+ - widths can be controlled or uncontrolled
558
+ - pointer and keyboard resizing are both supported
559
+
560
+ ### Recommended Public Composition
561
+
562
+ ```svelte
563
+ <Table.Root aria-label="Users" bind:columnWidths>
564
+ <Table.Header>
565
+ <Table.Row>
566
+ <Table.Column id="email" isRowHeader allowsSorting defaultWidth={280} minWidth={180}>
567
+ <Table.ColumnHeaderCell>
568
+ <span>Email</span>
569
+ <Table.ColumnResizer />
570
+ </Table.ColumnHeaderCell>
571
+ </Table.Column>
572
+
573
+ <Table.Column id="group" allowsSorting defaultWidth={180} minWidth={140}>
574
+ <Table.ColumnHeaderCell>
575
+ <span>Group</span>
576
+ <Table.ColumnResizer />
577
+ </Table.ColumnHeaderCell>
578
+ </Table.Column>
579
+ </Table.Row>
580
+ </Table.Header>
581
+
582
+ <Table.Body>
583
+ <!-- rows -->
584
+ </Table.Body>
585
+ </Table.Root>
586
+ ```
587
+
588
+ ### API Recommendation
589
+
590
+ #### Width Props on `Table.Column`
591
+
592
+ Add the following props:
593
+
594
+ - `width?: number | string`
595
+ - `defaultWidth?: number | string`
596
+ - `minWidth?: number`
597
+ - `maxWidth?: number`
598
+
599
+ Notes:
600
+
601
+ - `width` is the controlled width for the column.
602
+ - `defaultWidth` is the uncontrolled initial width.
603
+ - rendering `Table.ColumnResizer` is the public resize opt-in for the owning column.
604
+
605
+ #### Width State on `Table.Root`
606
+
607
+ Add root-level width state APIs:
608
+
609
+ - `columnWidths?: Map<string, number>`
610
+ - `defaultColumnWidths?: Map<string, number>`
611
+ - `onColumnWidthsChange?: (widths: Map<string, number>) => void`
612
+ - `onColumnResizeStart?: (columnId: string) => void`
613
+ - `onColumnResizeEnd?: (widths: Map<string, number>) => void`
614
+
615
+ Notes:
616
+
617
+ - Controlled/uncontrolled width state should mirror the existing `selectedKeys` and `sortDescriptor` contracts.
618
+ - Widths in root state should be normalized to px numbers even if consumer input allows string forms.
619
+
620
+ #### `Table.ColumnResizer` Part
621
+
622
+ Public part to place inside `Table.ColumnHeaderCell`.
623
+
624
+ Tentative props:
625
+
626
+ - `step?: number`
627
+ - `shiftStep?: number`
628
+ - `class?: string`
629
+ - `children?: Snippet`
630
+
631
+ No `columnId` prop should be needed; it must use `Table.Column` context.
632
+
633
+ ### Why Not a Parallel `ColumnHandler`
634
+
635
+ This API should explicitly avoid a separate sibling structure like:
636
+
637
+ ```svelte
638
+ <Table.Column id="email" />
639
+ <Table.Column id="group" />
640
+ <Table.ColumnHandler index={0} />
641
+ <Table.ColumnHandler index={1} />
642
+ ```
643
+
644
+ Reasons:
645
+
646
+ - position-based matching becomes fragile with dynamic columns
647
+ - it duplicates the concept of column identity
648
+ - it becomes harder to keep sorting, row-header semantics, and resizing anchored to the same column contract
649
+ - it does not follow the RAC model, where resizing is column-owned and the handle is colocated with the header content
650
+
651
+ ### Width Model
652
+
653
+ #### Effective Width Resolution
654
+
655
+ For each registered column, compute the effective width from highest to lowest precedence:
656
+
657
+ 1. `Table.Root.columnWidths.get(columnId)`
658
+ 2. `Table.Column.width`
659
+ 3. `Table.Root.defaultColumnWidths.get(columnId)`
660
+ 4. `Table.Column.defaultWidth`
661
+ 5. no explicit width
662
+
663
+ Then clamp the result against:
664
+
665
+ - `minWidth`
666
+ - `maxWidth`
667
+
668
+ #### Initial Implementation Constraint
669
+
670
+ For the first resizing implementation, normalize widths to px values.
671
+
672
+ - Accept `number` as px.
673
+ - Optionally accept `"123px"` and normalize it to `123`.
674
+ - Defer `%`, `fr`, and more advanced layout math until the base feature is stable.
675
+
676
+ This keeps the state model simple and reduces layout bugs.
677
+
678
+ ### Rendering Strategy
679
+
680
+ The recommended implementation is to generate a `<colgroup>` inside `Table.Root` from the registered columns and the effective widths.
681
+
682
+ Reasons:
683
+
684
+ - widths apply consistently to both header and body cells
685
+ - native table layout remains intact
686
+ - it avoids pushing per-cell width styles into every `Table.Cell`
687
+ - it scales better as the feature grows
688
+
689
+ Planned approach:
690
+
691
+ - `Table.Root` renders a managed `<colgroup>` before children
692
+ - each registered column maps to one `<col>`
693
+ - effective width is applied to the `<col>`
694
+ - body and header cells keep their semantic markup unchanged
695
+
696
+ Fallback if `<colgroup>` proves insufficient for some cases:
697
+
698
+ - apply inline width/min-width styles to header cells and derived styles to body cells by column index
699
+
700
+ But `<colgroup>` should be the default strategy.
701
+
702
+ ### Interaction Model
703
+
704
+ #### Pointer
705
+
706
+ - pointer down on `Table.ColumnResizer` starts resizing for its current column
707
+ - movement computes a new width in px relative to the starting header width
708
+ - width updates continuously during drag
709
+ - pointer up finalizes the interaction and calls `onColumnResizeEnd`
710
+
711
+ #### Keyboard
712
+
713
+ - the resizer is focusable
714
+ - `ArrowLeft` reduces width by `step`
715
+ - `ArrowRight` increases width by `step`
716
+ - `Shift+ArrowLeft` / `Shift+ArrowRight` use `shiftStep`
717
+ - resize keyboard handling should not hijack the existing table cell navigation when the resizer itself is not focused
718
+
719
+ ### Accessibility Contract
720
+
721
+ `Table.ColumnResizer` should behave like a column separator/resizer control.
722
+
723
+ Recommended attributes:
724
+
725
+ - `role="separator"`
726
+ - `aria-orientation="vertical"`
727
+ - `aria-valuenow`
728
+ - `aria-valuemin`
729
+ - `aria-valuemax`
730
+ - accessible label derived from the current column, for example `Resize Email column`
731
+
732
+ Derived data attributes:
733
+
734
+ - `data-resizing`
735
+ - `data-focused`
736
+ - `data-focus-visible`
737
+ - `data-resizable-direction="right"`
738
+
739
+ ### Internal Architecture Additions
740
+
741
+ #### `root/context.ts`
742
+
743
+ Extend column registration to include:
744
+
745
+ - `width`
746
+ - `defaultWidth`
747
+ - `minWidth`
748
+ - `maxWidth`
749
+
750
+ Add root-level APIs for:
751
+
752
+ - resolving effective column widths
753
+ - updating a column width by `columnId`
754
+ - starting/ending resize interactions
755
+ - reading resize state for a column
756
+
757
+ #### New Part Context Usage
758
+
759
+ `Table.ColumnResizer` should consume:
760
+
761
+ - `Table.Column` context for `columnId`
762
+ - `Table.Root` context for width state and resize actions
763
+
764
+ It should not require positional props like `index` or `for`.
765
+
766
+ ### Planned File Additions
767
+
768
+ - `packages/svelte/src/lib/table/column-resizer/table-column-resizer.svelte`
769
+ - `packages/svelte/src/lib/table/column-resizer/README.md`
770
+ - `packages/svelte/src/lib/table/column-resizer/table-column-resizer.test.ts`
771
+
772
+ Planned touched files:
773
+
774
+ - `packages/svelte/src/lib/table/index.parts.ts`
775
+ - `packages/svelte/src/lib/table/index.ts`
776
+ - `packages/svelte/src/lib/table/root/context.ts`
777
+ - `packages/svelte/src/lib/table/root/table-root.svelte`
778
+ - `packages/svelte/src/lib/table/column/table-column.svelte`
779
+ - `packages/svelte/src/lib/table/column-header-cell/table-column-header-cell.svelte`
780
+ - `packages/svelte/src/lib/table/root/table-root.test.ts`
781
+ - `docs/src/routes/docs/table/+page.svelte`
782
+
783
+ ### Testing Plan
784
+
785
+ Minimum regression coverage:
786
+
787
+ - renders a resize handle only for columns composed with `Table.ColumnResizer`
788
+ - dragging the resizer changes the associated column width only
789
+ - resizing one column does not corrupt neighboring column identity
790
+ - controlled `columnWidths` updates are reflected in the DOM
791
+ - uncontrolled `defaultWidth` is honored on mount
792
+ - keyboard resizing updates width in deterministic steps
793
+ - min/max constraints are enforced
794
+ - focus and pointer interactions on the resizer do not break table navigation
795
+
796
+ ### Recommended Implementation Order
797
+
798
+ 1. Add API fields to `Table.Column` and root context registration.
799
+ 2. Add root width state and effective width resolution.
800
+ 3. Render managed `<colgroup>` from `Table.Root`.
801
+ 4. Add `Table.ColumnResizer` pointer interaction.
802
+ 5. Add keyboard and ARIA support for the resizer.
803
+ 6. Add docs/demo and controlled/uncontrolled tests.
804
+
805
+ ### Non-Goals for the First Resize Release
806
+
807
+ - percentage/fr width math
808
+ - column resize persistence outside consumer-provided state
809
+ - multi-column proportional redistribution
810
+ - double-click auto-fit
811
+ - resize in nested/grouped headers
812
+ - resizable footer-specific behavior
813
+ - `packages/svelte/src/lib/table/header/table-header.svelte`
814
+ - `packages/svelte/src/lib/table/header/README.md`
815
+ - `packages/svelte/src/lib/table/header/table-header.test.ts`
816
+ - `packages/svelte/src/lib/table/body/table-body.svelte`
817
+ - `packages/svelte/src/lib/table/body/README.md`
818
+ - `packages/svelte/src/lib/table/body/table-body.test.ts`
819
+ - `packages/svelte/src/lib/table/empty-state/table-empty-state.svelte`
820
+ - `packages/svelte/src/lib/table/empty-state/README.md`
821
+ - `packages/svelte/src/lib/table/empty-state/table-empty-state.test.ts`
822
+ - `packages/svelte/src/lib/table/footer/table-footer.svelte`
823
+ - `packages/svelte/src/lib/table/footer/README.md`
824
+ - `packages/svelte/src/lib/table/footer/table-footer.test.ts`
825
+ - `packages/svelte/src/lib/table/row/table-row.svelte`
826
+ - `packages/svelte/src/lib/table/row/README.md`
827
+ - `packages/svelte/src/lib/table/row/table-row.test.ts`
828
+ - `packages/svelte/src/lib/table/column-header-cell/table-column-header-cell.svelte`
829
+ - `packages/svelte/src/lib/table/column-header-cell/README.md`
830
+ - `packages/svelte/src/lib/table/column-header-cell/table-column-header-cell.test.ts`
831
+ - `packages/svelte/src/lib/table/cell/table-cell.svelte`
832
+ - `packages/svelte/src/lib/table/cell/README.md`
833
+ - `packages/svelte/src/lib/table/cell/table-cell.test.ts`
834
+
835
+ ### Package Integrations
836
+
837
+ - `packages/svelte/src/lib/index.ts`
838
+ - `packages/svelte/package.json`
839
+ - `docs/src/routes/docs/table/+page.svelte`
840
+ - `README.md`
841
+ - `.changeset/*`
842
+
843
+ ## Implementation Strategy
844
+
845
+ ### Phase 1: Public Contract
846
+
847
+ - finalize the public anatomy
848
+ - finalize prop naming
849
+ - define exact responsibilities for each part
850
+ - document minimal examples
851
+
852
+ ### Phase 2: State and Navigation
853
+
854
+ - implement root context
855
+ - implement column/row/cell registration
856
+ - build 2D navigation
857
+ - add data attributes and focus contract
858
+
859
+ ### Phase 3: Selection and Sorting
860
+
861
+ - add controlled/uncontrolled selection
862
+ - add disabled rows
863
+ - add per-column sorting
864
+ - validate keyboard behavior
865
+
866
+ ### Phase 4: Docs and Tests
867
+
868
+ - tests for each public part
869
+ - integral harness
870
+ - base README and per-part READMEs
871
+ - docs demo page
872
+ - changeset
873
+
874
+ ## Resize Testing Plan
875
+
876
+ ### Minimum Cases
877
+
878
+ - correct semantic rendering
879
+ - required accessible label
880
+ - arrow-key navigation between cells
881
+ - row-level `Home` / `End`
882
+ - entering/leaving the grid with `Tab`
883
+ - correct focus-visible and focus attributes
884
+ - single selection
885
+ - multiple selection
886
+ - disabled rows
887
+ - sorting via keyboard and pointer
888
+ - empty state
889
+ - `Footer` renders without breaking main navigation
890
+
891
+ ### Test Inspirations
892
+
893
+ - `ListBox` tests
894
+ - `Calendar` tests
895
+ - focus contract tests
896
+
897
+ ## Main Risks
898
+
899
+ - without a clear 2D engine, the component may end up stuck between a passive table and an interactive grid
900
+ - mixing cell focus with row selection requires very explicit rules
901
+ - if `Footer` joins the focus flow too early, the model becomes unnecessarily complex
902
+ - allowing interactive content inside cells in v1 may break the keyboard experience
903
+
904
+ ## V1 Success Criteria
905
+
906
+ - the public API is clear and consistent with the rest of the library
907
+ - keyboard navigation feels close to React Aria Components in the core cases
908
+ - sorting and selection work without ambiguity
909
+ - tests cover critical behavior
910
+ - the implementation leaves real room for future phases without breaking the API
911
+
912
+ ## Phase 3: Row Actions and Disabled Behavior Plan
913
+
914
+ ### Phase 3 Goal
915
+
916
+ Add row actions in a way that matches the React Aria Components mental model closely enough for consumers to predict behavior, while still fitting the existing `Table` architecture in this repository:
917
+
918
+ - `Table.Root` remains the single owner of interaction state
919
+ - `Table.Row` and `Table.Cell` remain semantic wrappers over native table elements
920
+ - selection and actions are treated as related but distinct interactions
921
+ - disabled state becomes more explicit so selection-only disabling does not accidentally disable focus or actions
922
+
923
+ This phase is specifically about `onRowAction` and `disabledBehavior`. It does not attempt to solve full row-as-link semantics or nested interactive controls inside arbitrary cells.
924
+
925
+ ### Reference Behavior
926
+
927
+ The intended behavioral reference is React Aria's collection model for selection and item actions:
928
+
929
+ - `selectionBehavior="toggle"` keeps action as the primary row press interaction until the user enters a selection state
930
+ - `selectionBehavior="replace"` makes selection the primary pointer interaction and uses double click for row actions
931
+ - keyboard behavior separates selection from action more strictly:
932
+ - `Space` is selection-oriented
933
+ - `Enter` is action-oriented when actions are available
934
+
935
+ The goal is functional parity for the supported cases, not a byte-for-byte clone of RAC internals.
936
+
937
+ ### Public API Recommendation
938
+
939
+ #### `Table.Root` Props
940
+
941
+ Add the following props:
942
+
943
+ - `onRowAction?: (id: TableSelectionKey) => void`
944
+ - `disabledBehavior?: 'selection' | 'all'`
945
+
946
+ Prerequisite:
947
+
948
+ - `selectionBehavior?: 'toggle' | 'replace'` must already exist and be part of the stable `Table.Root` contract for this phase to make sense. This phase depends on the existing selection-behavior model rather than introducing a parallel row-press API.
949
+
950
+ Defaults:
951
+
952
+ - `onRowAction` default: `undefined`
953
+ - `disabledBehavior` default: `'all'`
954
+
955
+ Rationale:
956
+
957
+ - `onRowAction` belongs at the collection root because the component is built around row ids and centralized interaction state
958
+ - `disabledBehavior` also belongs at the root because the current disabled model is collection-driven (`disabledKeys`) and should stay consistent across row-local and root-provided disabled state
959
+
960
+ #### No New `rowPressBehavior` Prop
961
+
962
+ This phase should not add a new `rowPressBehavior` prop.
963
+
964
+ Reasons:
965
+
966
+ - the desired behavior is already derivable from the combination of:
967
+ - `onRowAction`
968
+ - `selectionMode`
969
+ - `selectionBehavior`
970
+ - React Aria already establishes a recognizable interaction contract based on those inputs
971
+ - a separate `rowPressBehavior` prop would create redundant states and undocumented invalid combinations
972
+
973
+ ### Phase 3 Interaction Model
974
+
975
+ #### Core Principle
976
+
977
+ There are now three distinct row-level concepts:
978
+
979
+ 1. row focus
980
+ 2. row selection
981
+ 3. row action
982
+
983
+ The implementation must stop treating `pressRow()` as if it were synonymous with selection. A row press should first be classified, then routed to either selection logic, action logic, or both depending on the active mode.
984
+
985
+ #### Pointer Behavior Without `onRowAction`
986
+
987
+ When `onRowAction` is not provided, the component should preserve the current selection semantics:
988
+
989
+ - `selectionMode="none"`: row/cell click does nothing selection-related
990
+ - `selectionBehavior="toggle"`: row/cell click toggles selection
991
+ - `selectionBehavior="replace"`: row/cell click replaces selection according to current replace-mode rules
992
+
993
+ This preserves backwards compatibility.
994
+
995
+ #### Pointer Behavior With `onRowAction`
996
+
997
+ ##### `selectionMode="none"`
998
+
999
+ Regardless of `selectionBehavior`:
1000
+
1001
+ - single click on row or cell executes `onRowAction(id)`
1002
+ - double click does not have special meaning beyond the browser's normal click sequence
1003
+ - no selection state is changed
1004
+
1005
+ ##### `selectionBehavior="toggle"` with selection enabled
1006
+
1007
+ When `selectionMode` is `single` or `multiple` and `onRowAction` exists:
1008
+
1009
+ - if the table currently has no selected rows:
1010
+ - single click executes `onRowAction(id)`
1011
+ - click does not change row selection
1012
+ - if the table currently has at least one selected row:
1013
+ - single click on a row follows toggle selection semantics
1014
+ - click does not execute `onRowAction`
1015
+
1016
+ This matches the RAC notion that action is the default press interaction until the user has entered a selection workflow.
1017
+
1018
+ Important documentation note:
1019
+
1020
+ - this behavior changes dynamically based on whether the table currently has an active selection
1021
+ - that dynamic switch is powerful but can also surprise consumers and end users if it is not documented clearly
1022
+ - docs should call this out explicitly and describe it as an intentional RAC-aligned interaction model rather than a bug or inconsistency
1023
+ - if future consumer feedback shows this is too implicit, a dedicated escape hatch can be evaluated later, but this phase should ship the RAC-style default first
1024
+
1025
+ ##### `selectionBehavior="replace"` with selection enabled
1026
+
1027
+ When `selectionMode` is `single` or `multiple` and `onRowAction` exists:
1028
+
1029
+ - single click selects the row using replace-mode semantics
1030
+ - double click executes `onRowAction(id)`
1031
+ - the first click of the double-click sequence still performs selection
1032
+
1033
+ Callback ordering requirement:
1034
+
1035
+ - because the first click in the double-click sequence performs selection, `onSelectionChange` must fire before `onRowAction`
1036
+ - docs should state this ordering explicitly so consumers do not assume the action callback is the first observable event in the interaction
1037
+
1038
+ This is the clearest and most familiar desktop-style interaction model for replace mode.
1039
+
1040
+ ### Keyboard Model
1041
+
1042
+ #### Rows and Cells Without `onRowAction`
1043
+
1044
+ Keep existing behavior:
1045
+
1046
+ - `Enter` and `Space` continue to use selection behavior when appropriate
1047
+
1048
+ #### Rows and Cells With `onRowAction`
1049
+
1050
+ Apply the following contract in body rows:
1051
+
1052
+ - `Enter` executes `onRowAction(id)` when the row is actionable
1053
+ - `Space` performs selection when selection is allowed for that row
1054
+ - arrow keys keep their existing focus/navigation behavior
1055
+
1056
+ This keyboard split applies even when pointer behavior differs between `toggle` and `replace`.
1057
+
1058
+ #### Detailed Keyboard Rules
1059
+
1060
+ | State | `Enter` | `Space` |
1061
+ | ------------------------------------------ | -------------------------- | -------------------------- |
1062
+ | `selectionMode="none"` + `onRowAction` | action | no-op |
1063
+ | `selectionMode="single"` + `onRowAction` | action | selection |
1064
+ | `selectionMode="multiple"` + `onRowAction` | action | selection |
1065
+ | any mode without `onRowAction` | current selection behavior | current selection behavior |
1066
+
1067
+ Notes:
1068
+
1069
+ - in `replace` mode, `Space` must not be treated as action even though pointer uses double click for action
1070
+ - `Ctrl/Cmd+Space` in `multiple` + `replace` should preserve the existing non-contiguous selection behavior
1071
+ - `Shift+ArrowUp/Down` in `replace` should continue to extend selection; `onRowAction` must not interfere with that contract
1072
+
1073
+ ### Checkbox Interaction Rules
1074
+
1075
+ `Table.Checkbox` remains the explicit selection affordance.
1076
+
1077
+ Rules:
1078
+
1079
+ - checkbox interactions must never trigger `onRowAction`
1080
+ - checkbox interaction should continue to stop propagation so row presses are not synthesized accidentally
1081
+ - when `disabledBehavior="selection"`, the checkbox is disabled even if the row remains actionable
1082
+ - checkbox semantics remain selection-only, even when `selectionBehavior="replace"`
1083
+
1084
+ This preserves a clean mental model: checkbox equals selection, row press may mean action or selection depending on state.
1085
+
1086
+ ### `disabledBehavior` Semantics
1087
+
1088
+ #### `'all'`
1089
+
1090
+ This is the current effective behavior and should remain the default.
1091
+
1092
+ When a row is disabled by `disabledKeys` or `Table.Row isDisabled` and `disabledBehavior="all"`:
1093
+
1094
+ - row cannot be selected
1095
+ - row cannot trigger `onRowAction`
1096
+ - row is skipped by focus navigation
1097
+ - row should not be tabbable directly
1098
+ - body cells in that row should not be tabbable directly
1099
+ - checkbox is disabled
1100
+
1101
+ #### `'selection'`
1102
+
1103
+ When a row is disabled and `disabledBehavior="selection"`:
1104
+
1105
+ - row cannot be selected
1106
+ - row cannot be added to or removed from selection by click
1107
+ - row cannot be selected by `Space`
1108
+ - row cannot be selected via replace-mode arrow synchronization
1109
+ - checkbox is disabled
1110
+ - row can still receive focus
1111
+ - row can still participate in keyboard navigation
1112
+ - row can still trigger `onRowAction`
1113
+
1114
+ This mode means disabled-for-selection, not disabled-for-interaction.
1115
+
1116
+ ### Disabled State Matrix
1117
+
1118
+ | Condition | Focusable | Selectable | Actionable |
1119
+ | --------------------------------------------- | --------- | ------------------------- | ------------------------------ |
1120
+ | enabled row | yes | yes, per selection config | yes, when `onRowAction` exists |
1121
+ | disabled row + `disabledBehavior="all"` | no | no | no |
1122
+ | disabled row + `disabledBehavior="selection"` | yes | no | yes, when `onRowAction` exists |
1123
+
1124
+ ### Recommended Internal Refactor
1125
+
1126
+ #### Split the Existing Disabled Model
1127
+
1128
+ The current `isRowDisabled()` helper is too coarse for the new behavior. Internally, the root context should introduce separate predicates, or equivalent derived logic, for:
1129
+
1130
+ - row disabled for focus/navigation
1131
+ - row disabled for selection
1132
+ - row disabled for action
1133
+
1134
+ The public API does not need to expose all of these separately, but the internal model should.
1135
+
1136
+ Recommended internal helpers:
1137
+
1138
+ - `isRowSelectionDisabled(id, localDisabled?)`
1139
+ - `isRowActionDisabled(id, localDisabled?)`
1140
+ - `isRowInteractionDisabled(id, localDisabled?)`
1141
+ - `canRowReceiveFocus(id, localDisabled?)`
1142
+
1143
+ These names are illustrative; exact naming can be refined during implementation.
1144
+
1145
+ #### Split the Existing Press Pipeline
1146
+
1147
+ The current `pressRow()` API is selection-oriented. This phase should replace that single concept with a more explicit pipeline.
1148
+
1149
+ Recommended root-level methods:
1150
+
1151
+ - `performRowAction(id)`
1152
+ - `pressRowSelection(id, interaction)`
1153
+ - `pressRow(id, source, interaction)` as a coordinator, or equivalent separate handlers
1154
+
1155
+ The coordinator should decide behavior using:
1156
+
1157
+ - presence of `onRowAction`
1158
+ - `selectionMode`
1159
+ - `selectionBehavior`
1160
+ - whether there is an active selection
1161
+ - whether the row is disabled for selection or for all interactions
1162
+ - whether the source was pointer single click, pointer double click, `Enter`, or `Space`
1163
+
1164
+ ### Affected Parts
1165
+
1166
+ #### `root/context.ts` Changes
1167
+
1168
+ Will need to own:
1169
+
1170
+ - `onRowAction`
1171
+ - `disabledBehavior`
1172
+ - selection-disabled vs action-disabled resolution
1173
+ - row action dispatch helpers
1174
+ - press classification helpers
1175
+
1176
+ #### `root/table-root.svelte`
1177
+
1178
+ Will need to:
1179
+
1180
+ - accept and sync the new props into context
1181
+ - expose any new root-level data attributes if useful for styling/debugging
1182
+
1183
+ #### `row/table-row.svelte`
1184
+
1185
+ Will need to:
1186
+
1187
+ - update row tabbability based on `disabledBehavior`
1188
+ - handle `Enter` as action when available
1189
+ - handle `Space` as selection when available
1190
+ - ensure row-level keydown no longer assumes `Enter` and `Space` are always equivalent
1191
+
1192
+ #### `cell/table-cell.svelte`
1193
+
1194
+ Will need to:
1195
+
1196
+ - classify pointer click behavior differently when `onRowAction` exists
1197
+ - support double-click action in `replace` mode
1198
+ - align keydown behavior with the row contract so focus target does not change semantics
1199
+
1200
+ #### `checkbox/table-checkbox.svelte`
1201
+
1202
+ Will need to:
1203
+
1204
+ - disable itself from selection-only disabled rows
1205
+ - keep stopping propagation so row actions are not accidentally fired
1206
+ - preserve explicit selection behavior independently from row-action semantics
1207
+
1208
+ ### Event Contract Recommendation
1209
+
1210
+ For the first release of this feature, `onRowAction` should receive only the row id:
1211
+
1212
+ ```ts
1213
+ onRowAction?: (id: TableSelectionKey) => void;
1214
+ ```
1215
+
1216
+ Reasons:
1217
+
1218
+ - aligns with the current root-level API style (`onSelectionChange`, `onSortChange`)
1219
+ - keeps the surface simple while the interaction model is still stabilizing
1220
+ - avoids prematurely freezing an event-detail contract that may need more nuance later
1221
+
1222
+ If consumers later need trigger metadata, a future non-breaking addition could evolve this to an object payload or add a second callback.
1223
+
1224
+ ### Data Attribute Plan
1225
+
1226
+ This phase should add row-level state markers for styling and debugging:
1227
+
1228
+ - `data-actionable="true"` when the row can trigger `onRowAction`
1229
+ - `data-disabled-behavior="selection" | "all"` on `Table.Root`
1230
+ - `data-selection-disabled="true"` on rows/cells when selection is blocked but action remains available
1231
+
1232
+ `data-actionable` should be treated as required for this phase rather than optional.
1233
+
1234
+ Reasons:
1235
+
1236
+ - consumers need a reliable styling hook for actionable rows
1237
+ - cursor styling depends on this (`cursor: pointer` vs default)
1238
+ - once row actions exist, actionable state is part of the public styling contract rather than an internal implementation detail
1239
+
1240
+ ### Accessibility Plan
1241
+
1242
+ Requirements for this phase:
1243
+
1244
+ - rows that are action-enabled but selection-disabled must still expose coherent keyboard interaction
1245
+ - `aria-disabled` should only reflect non-interactive rows under `disabledBehavior="all"`
1246
+ - rows disabled only for selection should not be presented as fully disabled if they remain actionable and focusable
1247
+ - checkbox disabled state must remain announced correctly
1248
+
1249
+ Explicit decision:
1250
+
1251
+ - rows under `disabledBehavior="selection"` should not add compensating `aria-roledescription` just to explain partial disabled state
1252
+ - the row should remain announced according to its normal table/grid semantics
1253
+ - the disabled checkbox remains the primary assistive-technology signal that selection is unavailable
1254
+ - docs should explain that selection-only disabled rows are still actionable and focusable, while accessibility semantics stay conservative rather than inventing a custom roledescription
1255
+
1256
+ This is important because reusing the current all-or-nothing `aria-disabled` contract under `disabledBehavior="selection"` would misrepresent the row to assistive technology.
1257
+
1258
+ ### Behavior Matrix
1259
+
1260
+ #### Pointer Summary
1261
+
1262
+ | `selectionMode` | `selectionBehavior` | `onRowAction` | row click | row double click |
1263
+ | --------------------- | --------------------- | ---------------------------- | ----------------- | ---------------------- |
1264
+ | `none` | `toggle` or `replace` | no | no-op | no-op |
1265
+ | `none` | `toggle` or `replace` | yes | action | same as click sequence |
1266
+ | `single` / `multiple` | `toggle` | no | selection toggle | same as click sequence |
1267
+ | `single` / `multiple` | `toggle` | yes, no active selection | action | same as click sequence |
1268
+ | `single` / `multiple` | `toggle` | yes, active selection exists | selection toggle | same as click sequence |
1269
+ | `single` / `multiple` | `replace` | no | replace selection | same as click sequence |
1270
+ | `single` / `multiple` | `replace` | yes | replace selection | action |
1271
+
1272
+ #### Keyboard Summary
1273
+
1274
+ | `selectionMode` | `onRowAction` | `Enter` | `Space` |
1275
+ | --------------------- | ------------- | --------------------------- | --------------------------- |
1276
+ | `none` | no | no-op | no-op |
1277
+ | `none` | yes | action | no-op |
1278
+ | `single` / `multiple` | no | existing selection behavior | existing selection behavior |
1279
+ | `single` / `multiple` | yes | action | selection |
1280
+
1281
+ ### Phase 3 Testing Plan
1282
+
1283
+ Minimum regression coverage:
1284
+
1285
+ - `selectionMode="none"` + `onRowAction` triggers action on row click
1286
+ - basic pointer action tests should land before double-click support so the action pipeline is validated incrementally
1287
+ - `selectionBehavior="toggle"` + `onRowAction` triggers action on click when selection is empty
1288
+ - `selectionBehavior="toggle"` + `onRowAction` toggles selection on click once a selection exists
1289
+ - `selectionBehavior="replace"` + `onRowAction` selects on click and acts on double click
1290
+ - `Enter` triggers action and `Space` triggers selection when both are available
1291
+ - `disabledBehavior="selection"` disables checkbox and selection changes but still allows action
1292
+ - `disabledBehavior="all"` blocks focus, selection, and action
1293
+ - disabled rows under `selection` are skipped by selection sync but not by pure focus navigation
1294
+ - disabled row + `disabledBehavior="selection"` + `onRowAction` + `Enter` triggers action
1295
+ - checkbox never triggers `onRowAction`
1296
+ - existing replace-mode selection extension (`Shift+Arrow`, `Ctrl/Cmd+Space`) remains intact
1297
+
1298
+ ### Documentation Plan
1299
+
1300
+ Docs and README updates should include:
1301
+
1302
+ - a new section explaining the difference between row actions and row selection
1303
+ - examples for:
1304
+ - action-only rows (`selectionMode="none"`)
1305
+ - mixed action + selection in `toggle`
1306
+ - mixed action + selection in `replace`
1307
+ - `disabledBehavior="selection"`
1308
+ - an explicit note that in `toggle` mode the meaning of row click changes when a selection becomes active
1309
+ - an explicit note that in `replace` mode double click emits callbacks in the order `onSelectionChange` then `onRowAction`
1310
+ - an explicit note that this phase does not implement full row link semantics
1311
+
1312
+ ### Explicit Non-Goals
1313
+
1314
+ This phase should not attempt to solve:
1315
+
1316
+ - `href`, `target`, or router-aware row navigation APIs
1317
+ - native-link-equivalent semantics for rows
1318
+ - nested buttons, links, inputs, or menus inside arbitrary body cells
1319
+ - touch-specific long-press selection mode switching
1320
+
1321
+ These can be revisited later, but they should not block the collection-level row action API.
1322
+
1323
+ ### Phase 3 Recommended Implementation Order
1324
+
1325
+ 1. Add `onRowAction` and `disabledBehavior` to `Table.Root` and root context.
1326
+ 2. Refactor disabled-state helpers into selection-vs-action-aware logic.
1327
+ 3. Refactor row press handling so action and selection are separate pathways, and land basic pointer single-click action tests immediately.
1328
+ 4. Update `Table.Row` and `Table.Cell` keyboard semantics (`Enter` vs `Space`).
1329
+ 5. Add pointer double-click support for `replace` mode only after the basic action pipeline is covered by tests.
1330
+ 6. Update `Table.Checkbox` for selection-only disabled rows.
1331
+ 7. Add regression tests for the full matrix, including callback ordering and disabled-row keyboard action coverage.
1332
+ 8. Document examples and caveats, especially the dynamic `toggle`-mode switch, callback ordering in `replace`, and the non-goal of row link semantics.
1333
+
1334
+ ## Recommended Next Step
1335
+
1336
+ Turn this plan into a more concrete API specification, part by part and prop by prop, before creating implementation files.