ropav 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/dist/components/autocomplete/autocomplete-root.js +3 -1
  2. package/dist/components/autocomplete/index.d.ts +3 -0
  3. package/dist/components/breadcrumbs/breadcrumbs-item.js +1 -0
  4. package/dist/components/breadcrumbs/index.d.ts +1 -0
  5. package/dist/components/calendar/index.d.ts +5 -3
  6. package/dist/components/calendar-year-picker/index.d.ts +4 -2
  7. package/dist/components/checkbox/checkbox-content.js +1 -1
  8. package/dist/components/color-field/index.d.ts +1 -0
  9. package/dist/components/color-input-group/color-input-group-root.js +2 -0
  10. package/dist/components/color-input-group/color-input-group.types.d.ts +2 -0
  11. package/dist/components/color-input-group/index.d.ts +2 -0
  12. package/dist/components/combo-box/combo-box-root.js +9 -2
  13. package/dist/components/combo-box/combo-box.types.d.ts +3 -0
  14. package/dist/components/combo-box/index.d.ts +3 -0
  15. package/dist/components/date-field/index.d.ts +1 -0
  16. package/dist/components/date-input-group/date-input-group-root.js +2 -0
  17. package/dist/components/date-input-group/date-input-group-segment.js +1 -1
  18. package/dist/components/date-input-group/date-input-group.types.d.ts +2 -0
  19. package/dist/{composables → components/date-input-group}/use-date-segment.d.ts +2 -2
  20. package/dist/{composables → components/date-input-group}/use-date-segment.js +9 -9
  21. package/dist/components/date-range-picker/date-range-picker-root.js +1 -1
  22. package/dist/components/date-range-picker/index.d.ts +15 -14
  23. package/dist/{composables → components/date-range-picker}/use-date-range-picker.d.ts +7 -7
  24. package/dist/{composables → components/date-range-picker}/use-date-range-picker.js +10 -10
  25. package/dist/components/drawer/drawer-dialog.js +1 -1
  26. package/dist/{composables → components/drawer}/use-drawer-drag.d.ts +1 -1
  27. package/dist/{composables → components/drawer}/use-drawer-drag.js +1 -1
  28. package/dist/components/drop-zone/drop-zone-root.d.ts +23 -0
  29. package/dist/components/drop-zone/drop-zone-root.js +153 -0
  30. package/dist/components/drop-zone/drop-zone-trigger.d.ts +21 -0
  31. package/dist/components/drop-zone/drop-zone-trigger.js +20 -0
  32. package/dist/components/drop-zone/drop-zone.accept.d.ts +19 -0
  33. package/dist/components/drop-zone/drop-zone.accept.js +28 -0
  34. package/dist/components/drop-zone/drop-zone.context.d.ts +6 -0
  35. package/dist/components/drop-zone/drop-zone.context.js +5 -0
  36. package/dist/components/drop-zone/drop-zone.types.d.ts +30 -0
  37. package/dist/components/drop-zone/index.d.ts +86 -0
  38. package/dist/components/drop-zone/index.js +11 -0
  39. package/dist/components/dropdown/dropdown-menu.js +1 -0
  40. package/dist/components/dropdown/dropdown-submenu-trigger.js +1 -1
  41. package/dist/components/dropdown/index.d.ts +4 -2
  42. package/dist/{composables → components/dropdown}/use-safely-mouse-to-submenu.js +1 -1
  43. package/dist/components/icons/icon-overlay-arrow.js +1 -1
  44. package/dist/components/index.d.ts +5 -0
  45. package/dist/components/input/index.d.ts +2 -0
  46. package/dist/components/input/input-root.js +3 -0
  47. package/dist/components/input/input.types.d.ts +5 -0
  48. package/dist/components/input-group/index.d.ts +2 -0
  49. package/dist/components/input-group/input-group-root.js +3 -0
  50. package/dist/components/input-group/input-group.types.d.ts +5 -0
  51. package/dist/components/input-otp/index.d.ts +6 -4
  52. package/dist/components/link/index.d.ts +2 -0
  53. package/dist/components/link/link-root.js +22 -5
  54. package/dist/components/link/link.types.d.ts +14 -3
  55. package/dist/{composables → components/list-box}/drag-collection.d.ts +2 -2
  56. package/dist/{composables → components/list-box}/drag-collection.js +1 -1
  57. package/dist/components/list-box/index.d.ts +4 -0
  58. package/dist/components/list-box/list-box-root.js +53 -32
  59. package/dist/components/list-box/list-box.types.d.ts +2 -0
  60. package/dist/components/menu/index.d.ts +2 -0
  61. package/dist/components/menu/menu-root.js +5 -1
  62. package/dist/components/menu/menu.types.d.ts +3 -0
  63. package/dist/components/number-field/index.d.ts +3 -0
  64. package/dist/components/number-field/number-field-root.js +2 -0
  65. package/dist/components/number-field/number-field.types.d.ts +2 -0
  66. package/dist/components/overlay/overlay-arrow.js +64 -11
  67. package/dist/components/progress-bar/index.d.ts +4 -0
  68. package/dist/components/progress-bar/progress-bar-root.js +4 -0
  69. package/dist/components/progress-bar/progress-bar.types.d.ts +7 -0
  70. package/dist/components/radio/radio-content.js +1 -1
  71. package/dist/components/range-calendar/index.d.ts +18 -15
  72. package/dist/components/router-provider/index.d.ts +5 -0
  73. package/dist/components/router-provider/index.js +3 -0
  74. package/dist/components/router-provider/router-provider.context.d.ts +40 -0
  75. package/dist/components/router-provider/router-provider.context.js +16 -0
  76. package/dist/components/router-provider/router-provider.d.ts +21 -0
  77. package/dist/components/router-provider/router-provider.js +22 -0
  78. package/dist/components/router-provider/router-provider.types.d.ts +23 -0
  79. package/dist/components/scroll-shadow/use-scroll-shadow.js +25 -1
  80. package/dist/components/search-field/index.d.ts +2 -0
  81. package/dist/components/search-field/search-field-root.js +3 -1
  82. package/dist/components/search-field/search-field.types.d.ts +2 -0
  83. package/dist/{composables → components/search-field}/use-search-field.d.ts +2 -2
  84. package/dist/{composables → components/search-field}/use-search-field.js +3 -3
  85. package/dist/components/segmented-control/index.d.ts +134 -0
  86. package/dist/components/segmented-control/index.js +15 -0
  87. package/dist/components/segmented-control/segmented-control-indicator.d.ts +19 -0
  88. package/dist/components/segmented-control/segmented-control-indicator.js +36 -0
  89. package/dist/components/segmented-control/segmented-control-item.d.ts +21 -0
  90. package/dist/components/segmented-control/segmented-control-item.js +96 -0
  91. package/dist/components/segmented-control/segmented-control-root.d.ts +25 -0
  92. package/dist/components/segmented-control/segmented-control-root.js +112 -0
  93. package/dist/components/segmented-control/segmented-control-separator.d.ts +19 -0
  94. package/dist/components/segmented-control/segmented-control-separator.js +18 -0
  95. package/dist/components/segmented-control/segmented-control.context.d.ts +17 -0
  96. package/dist/components/segmented-control/segmented-control.context.js +12 -0
  97. package/dist/components/segmented-control/segmented-control.types.d.ts +67 -0
  98. package/dist/components/select/index.d.ts +3 -0
  99. package/dist/components/select/select-hidden-select.js +1 -1
  100. package/dist/components/select/select-root.js +2 -0
  101. package/dist/components/select/select.types.d.ts +2 -0
  102. package/dist/components/sidebar/index.d.ts +546 -0
  103. package/dist/components/sidebar/index.js +47 -0
  104. package/dist/components/sidebar/sidebar-collapsible-trigger.d.ts +21 -0
  105. package/dist/components/sidebar/sidebar-collapsible-trigger.js +70 -0
  106. package/dist/components/sidebar/sidebar-collapsible.d.ts +24 -0
  107. package/dist/components/sidebar/sidebar-collapsible.js +78 -0
  108. package/dist/components/sidebar/sidebar-content.d.ts +21 -0
  109. package/dist/components/sidebar/sidebar-content.js +37 -0
  110. package/dist/components/sidebar/sidebar-footer.d.ts +21 -0
  111. package/dist/components/sidebar/sidebar-footer.js +24 -0
  112. package/dist/components/sidebar/sidebar-group-label.d.ts +21 -0
  113. package/dist/components/sidebar/sidebar-group-label.js +32 -0
  114. package/dist/components/sidebar/sidebar-group.d.ts +21 -0
  115. package/dist/components/sidebar/sidebar-group.js +48 -0
  116. package/dist/components/sidebar/sidebar-header.d.ts +21 -0
  117. package/dist/components/sidebar/sidebar-header.js +24 -0
  118. package/dist/components/sidebar/sidebar-inset.d.ts +21 -0
  119. package/dist/components/sidebar/sidebar-inset.js +24 -0
  120. package/dist/components/sidebar/sidebar-item-icon.d.ts +21 -0
  121. package/dist/components/sidebar/sidebar-item-icon.js +20 -0
  122. package/dist/components/sidebar/sidebar-item-indicator.d.ts +21 -0
  123. package/dist/components/sidebar/sidebar-item-indicator.js +48 -0
  124. package/dist/components/sidebar/sidebar-item-label.d.ts +21 -0
  125. package/dist/components/sidebar/sidebar-item-label.js +28 -0
  126. package/dist/components/sidebar/sidebar-item-tooltip.d.ts +21 -0
  127. package/dist/components/sidebar/sidebar-item-tooltip.js +38 -0
  128. package/dist/components/sidebar/sidebar-item-trailing.d.ts +21 -0
  129. package/dist/components/sidebar/sidebar-item-trailing.js +28 -0
  130. package/dist/components/sidebar/sidebar-item.d.ts +23 -0
  131. package/dist/components/sidebar/sidebar-item.js +160 -0
  132. package/dist/components/sidebar/sidebar-panel.d.ts +21 -0
  133. package/dist/components/sidebar/sidebar-panel.js +88 -0
  134. package/dist/components/sidebar/sidebar-rail.d.ts +21 -0
  135. package/dist/components/sidebar/sidebar-rail.js +210 -0
  136. package/dist/components/sidebar/sidebar-root.d.ts +31 -0
  137. package/dist/components/sidebar/sidebar-root.js +140 -0
  138. package/dist/components/sidebar/sidebar-separator.d.ts +19 -0
  139. package/dist/components/sidebar/sidebar-separator.js +16 -0
  140. package/dist/components/sidebar/sidebar-sub-menu.d.ts +23 -0
  141. package/dist/components/sidebar/sidebar-sub-menu.js +40 -0
  142. package/dist/components/sidebar/sidebar-trigger.d.ts +21 -0
  143. package/dist/components/sidebar/sidebar-trigger.js +69 -0
  144. package/dist/components/sidebar/sidebar.context.d.ts +75 -0
  145. package/dist/components/sidebar/sidebar.context.js +30 -0
  146. package/dist/components/sidebar/sidebar.state.d.ts +73 -0
  147. package/dist/components/sidebar/sidebar.state.js +76 -0
  148. package/dist/components/sidebar/sidebar.storage.d.ts +8 -0
  149. package/dist/components/sidebar/sidebar.storage.js +45 -0
  150. package/dist/components/sidebar/sidebar.types.d.ts +273 -0
  151. package/dist/components/splitter/index.d.ts +143 -0
  152. package/dist/components/splitter/index.js +13 -0
  153. package/dist/components/splitter/splitter-handle.d.ts +21 -0
  154. package/dist/components/splitter/splitter-handle.js +209 -0
  155. package/dist/components/splitter/splitter-panel.d.ts +21 -0
  156. package/dist/components/splitter/splitter-panel.js +70 -0
  157. package/dist/components/splitter/splitter-root.d.ts +31 -0
  158. package/dist/components/splitter/splitter-root.js +152 -0
  159. package/dist/components/splitter/splitter.context.d.ts +15 -0
  160. package/dist/components/splitter/splitter.context.js +5 -0
  161. package/dist/components/splitter/splitter.state.d.ts +87 -0
  162. package/dist/components/splitter/splitter.state.js +322 -0
  163. package/dist/components/splitter/splitter.storage.d.ts +15 -0
  164. package/dist/components/splitter/splitter.storage.js +56 -0
  165. package/dist/components/splitter/splitter.types.d.ts +98 -0
  166. package/dist/components/switch/switch-content.js +1 -1
  167. package/dist/components/table/index.d.ts +10 -9
  168. package/dist/components/table/table-body.js +13 -13
  169. package/dist/components/table/table-cell.js +49 -46
  170. package/dist/components/table/table-column-resizer.js +1 -1
  171. package/dist/components/table/table-column.js +1 -1
  172. package/dist/components/table/table-content.js +37 -18
  173. package/dist/{composables → components/table}/table-drag-collection.d.ts +1 -1
  174. package/dist/{composables → components/table}/table-drag-collection.js +1 -1
  175. package/dist/components/table/table-drop-indicator.js +3 -1
  176. package/dist/components/table/table-resizable-container.d.ts +4 -5
  177. package/dist/components/table/table-row.js +45 -14
  178. package/dist/components/table/table-virtualizer-item.d.ts +2 -7
  179. package/dist/components/table/table-virtualizer-item.js +20 -9
  180. package/dist/components/table/table.context.d.ts +11 -3
  181. package/dist/components/table/table.types.d.ts +1 -1
  182. package/dist/{composables → components/table}/use-grid-keyboard.d.ts +3 -3
  183. package/dist/{composables → components/table}/use-grid-keyboard.js +57 -55
  184. package/dist/{composables → components/table}/use-grid-selection-announcement.d.ts +2 -2
  185. package/dist/{composables → components/table}/use-grid-selection-announcement.js +2 -2
  186. package/dist/{composables → components/table}/use-table-collection.d.ts +2 -2
  187. package/dist/{composables → components/table}/use-table-collection.js +4 -4
  188. package/dist/{composables → components/table}/use-table-column-layout.d.ts +13 -20
  189. package/dist/components/table/use-table-column-layout.js +115 -0
  190. package/dist/components/textarea/index.d.ts +2 -0
  191. package/dist/components/textarea/textarea-root.js +3 -0
  192. package/dist/components/textarea/textarea.types.d.ts +5 -0
  193. package/dist/components/textfield/index.d.ts +2 -0
  194. package/dist/components/textfield/textfield-root.js +5 -1
  195. package/dist/components/textfield/textfield.context.d.ts +2 -0
  196. package/dist/components/textfield/textfield.types.d.ts +5 -0
  197. package/dist/components/time-field/index.d.ts +1 -0
  198. package/dist/components/toast/index.d.ts +1 -0
  199. package/dist/components/toast/toast-default-content.js +1 -1
  200. package/dist/components/toolbar/index.d.ts +3 -2
  201. package/dist/components/tooltip/index.js +1 -1
  202. package/dist/components/tooltip/tooltip-root.js +1 -1
  203. package/dist/components/virtualizer/index.d.ts +3 -1
  204. package/dist/components/virtualizer/index.js +2 -1
  205. package/dist/components/virtualizer/use-virtualizer-item.d.ts +23 -0
  206. package/dist/components/virtualizer/use-virtualizer-item.js +45 -0
  207. package/dist/components/virtualizer/virtualizer-item.js +7 -41
  208. package/dist/components/virtualizer/virtualizer-scrollbar-track.d.ts +20 -0
  209. package/dist/components/virtualizer/virtualizer-scrollbar-track.js +141 -0
  210. package/dist/components/virtualizer/virtualizer-scrollbar.d.ts +18 -0
  211. package/dist/components/virtualizer/virtualizer-scrollbar.js +55 -0
  212. package/dist/components/virtualizer/virtualizer.context.d.ts +17 -1
  213. package/dist/composables/drag-manager.js +1 -13
  214. package/dist/composables/index.d.ts +3 -70
  215. package/dist/composables/use-collection.d.ts +22 -3
  216. package/dist/composables/use-collection.js +67 -14
  217. package/dist/composables/use-color-area.js +2 -2
  218. package/dist/composables/use-color-field.js +1 -1
  219. package/dist/composables/use-combo-box.js +1 -1
  220. package/dist/composables/use-date-field.js +1 -1
  221. package/dist/composables/use-drag.d.ts +6 -5
  222. package/dist/composables/use-draggable-item.d.ts +2 -2
  223. package/dist/composables/use-droppable-collection.js +3 -3
  224. package/dist/composables/use-droppable-item.js +1 -1
  225. package/dist/composables/use-form-reset.d.ts +6 -2
  226. package/dist/composables/use-form-reset.js +9 -5
  227. package/dist/composables/use-list-keyboard.js +64 -62
  228. package/dist/composables/use-number-field.js +1 -1
  229. package/dist/composables/use-select.js +2 -2
  230. package/dist/composables/use-selection-manager.js +4 -4
  231. package/dist/composables/use-single-select-list-state.d.ts +37 -0
  232. package/dist/composables/use-single-select-list-state.js +110 -0
  233. package/dist/composables/use-slider-thumb.js +2 -2
  234. package/dist/composables/use-slider.js +1 -1
  235. package/dist/composables/use-tab-list-state.d.ts +10 -29
  236. package/dist/composables/use-tab-list-state.js +10 -99
  237. package/dist/composables/use-text-field.js +1 -1
  238. package/dist/composables/use-virtualizer-scroll.d.ts +52 -2
  239. package/dist/composables/use-virtualizer-scroll.js +79 -13
  240. package/dist/composables/use-virtualizer.d.ts +24 -4
  241. package/dist/composables/use-virtualizer.js +40 -6
  242. package/dist/index.js +60 -83
  243. package/dist/utils/document-order.d.ts +14 -0
  244. package/dist/utils/document-order.js +17 -0
  245. package/dist/utils/flex-sizing.d.ts +41 -0
  246. package/dist/utils/flex-sizing.js +140 -0
  247. package/dist/utils/index.d.ts +2 -0
  248. package/dist/utils/open-link.d.ts +31 -0
  249. package/dist/utils/open-link.js +32 -0
  250. package/dist/utils/story-truncation.d.ts +20 -0
  251. package/dist/utils/story-users.d.ts +11 -0
  252. package/dist/utils/virtualizer-collection.d.ts +14 -4
  253. package/dist/utils/virtualizer-collection.js +9 -14
  254. package/dist/utils/virtualizer-layout-info.js +3 -2
  255. package/dist/utils/virtualizer-list-layout.d.ts +75 -29
  256. package/dist/utils/virtualizer-list-layout.js +182 -99
  257. package/dist/utils/virtualizer-offsets.d.ts +111 -0
  258. package/dist/utils/virtualizer-offsets.js +159 -0
  259. package/dist/utils/virtualizer-overscan.d.ts +9 -15
  260. package/dist/utils/virtualizer-overscan.js +14 -33
  261. package/dist/utils/virtualizer-table-layout.d.ts +50 -21
  262. package/dist/utils/virtualizer-table-layout.js +130 -89
  263. package/dist/version.js +1 -1
  264. package/package.json +25 -5
  265. package/dist/composables/use-table-column-layout.js +0 -214
  266. /package/dist/{composables → components/dropdown}/use-safely-mouse-to-submenu.d.ts +0 -0
@@ -1,6 +1,26 @@
1
+ import { sortByDocumentOrder } from "../utils/document-order.js";
1
2
  import { computed, shallowRef } from "vue";
2
3
  //#region src/composables/use-collection.ts
3
4
  /**
5
+ * `key → index` for a data-backed collection, memoized on the source itself.
6
+ *
7
+ * A source is rebuilt whenever its data changes — it is handed over out of a `computed` — so its
8
+ * identity is already the right cache key and nothing has to invalidate this by hand. Held weakly,
9
+ * so a collection that has gone away takes its map with it.
10
+ *
11
+ * This is what keeps `getIndex` off a scan on the one path that has no operation to scope to:
12
+ * `aria-posinset` and `aria-rowindex` are read during render, once per item, and a virtualized
13
+ * collection's source holds every item rather than the rendered window.
14
+ */
15
+ var sourceIndices = /* @__PURE__ */ new WeakMap();
16
+ var indicesOf = (source) => {
17
+ const cached = sourceIndices.get(source);
18
+ if (cached) return cached;
19
+ const indices = new Map(source.keys.map((key, index) => [key, index]));
20
+ sourceIndices.set(source, indices);
21
+ return indices;
22
+ };
23
+ /**
4
24
  * The set of items a listbox, menu or tag group navigates over.
5
25
  *
6
26
  * React Aria builds this by rendering the children into a hidden tree and reading the result
@@ -8,7 +28,7 @@ import { computed, shallowRef } from "vue";
8
28
  * rendering a slot is what creates its DOM, so there is no "render to inspect" pass. The flow
9
29
  * is inverted instead — each item registers itself, and the parent asks the DOM for the order.
10
30
  *
11
- * Two choices carry the weight:
31
+ * Three choices carry the weight:
12
32
  *
13
33
  * Metadata is stored as **getters**, not values, so the parent always reads an item's current
14
34
  * text and disabled state. Nothing has to re-register when a prop changes, and no
@@ -18,8 +38,20 @@ import { computed, shallowRef } from "vue";
18
38
  * invalidated by a counter bumped on register and unregister — but reordering items that are
19
39
  * already registered changes their document order without touching that counter, which would
20
40
  * silently break arrow navigation. Order is only ever read on an interaction (a keypress, a
21
- * focus change, extending a selection), never during render, so re-sorting per call is both
22
- * correct and cheap. `size` stays reactive because it *is* read during render.
41
+ * focus change, extending a selection), never during render, so `size` stays reactive because
42
+ * it *is* read during render while order deliberately stays outside reactivity.
43
+ *
44
+ * Sorting the DOM per call, however, is only cheap for a *single* read, and the callers that
45
+ * matter walk the collection one neighbour at a time: typeahead, paging, and skipping disabled
46
+ * items all ask for a neighbour in a loop. Sorting inside that loop is quadratic, and a search
47
+ * that matches nothing walks the whole collection twice. {@link UseCollectionReturn.withOrder}
48
+ * is what those callers wrap themselves in: the order is read once and every step reads that one
49
+ * snapshot.
50
+ *
51
+ * The snapshot is **scoped to one synchronous operation** rather than cached across operations,
52
+ * because caching is the very thing the second choice above rules out. Vue flushes renders on
53
+ * `nextTick`, so the DOM cannot reorder itself in the middle of a keydown handler — which is
54
+ * exactly as long as a snapshot is allowed to live. It must never become a `computed`.
23
55
  *
24
56
  * A collection that is virtualized cannot work that way at all: all but a window of its items are
25
57
  * absent from the DOM, so asking the DOM would answer for the window and call the rest
@@ -56,18 +88,38 @@ var useCollection = (options = {}) => {
56
88
  textValue: () => node.textValue ?? ""
57
89
  };
58
90
  };
59
- const orderedKeys = () => {
91
+ const domOrderedKeys = () => sortByDocumentOrder([...items.entries()], ([, meta]) => meta.element()).map(([key]) => key);
92
+ const buildOrder = () => {
60
93
  const sourced = source();
61
- if (sourced) return sourced.keys;
62
- return domOrderedKeys();
94
+ const keys = sourced ? sourced.keys : domOrderedKeys();
95
+ let indices = sourced ? indicesOf(sourced) : null;
96
+ return {
97
+ indexOf: (key) => {
98
+ indices ??= new Map(keys.map((entry, index) => [entry, index]));
99
+ return indices.get(key) ?? -1;
100
+ },
101
+ keys
102
+ };
63
103
  };
64
- const domOrderedKeys = () => [...items.entries()].filter(([, meta]) => meta.element()?.isConnected).sort(([, a], [, b]) => a.element().compareDocumentPosition(b.element()) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1).map(([key]) => key);
65
- const keyAt = (index) => {
66
- return orderedKeys()[index] ?? null;
104
+ /** The order this operation is reading, or a fresh reading for a call that stands alone. */
105
+ let scoped = null;
106
+ let depth = 0;
107
+ const order = () => scoped ?? buildOrder();
108
+ const withOrder = (operation) => {
109
+ if (depth === 0) scoped = buildOrder();
110
+ depth += 1;
111
+ try {
112
+ return operation();
113
+ } finally {
114
+ depth -= 1;
115
+ if (depth === 0) scoped = null;
116
+ }
67
117
  };
118
+ const orderedKeys = () => order().keys;
119
+ const keyAt = (index) => order().keys[index] ?? null;
68
120
  const neighbour = (key, step) => {
69
- const keys = orderedKeys();
70
- const index = keys.indexOf(key);
121
+ const { indexOf, keys } = order();
122
+ const index = indexOf(key);
71
123
  if (index === -1) return null;
72
124
  return keys[index + step] ?? null;
73
125
  };
@@ -76,18 +128,19 @@ var useCollection = (options = {}) => {
76
128
  return items.get(key)?.element() ?? null;
77
129
  },
78
130
  getFirstKey: () => keyAt(0),
79
- getIndex: (key) => orderedKeys().indexOf(key),
131
+ getIndex: (key) => order().indexOf(key),
80
132
  getItem: (key) => {
81
133
  return items.get(key) ?? metaFromSource(key);
82
134
  },
83
135
  getKeyAfter: (key) => neighbour(key, 1),
84
136
  getKeyBefore: (key) => neighbour(key, -1),
85
- getLastKey: () => keyAt(orderedKeys().length - 1),
137
+ getLastKey: () => order().keys.at(-1) ?? null,
86
138
  orderedKeys,
87
139
  register,
88
140
  size: computed(() => {
89
141
  return source()?.itemCount ?? items.size;
90
- })
142
+ }),
143
+ withOrder
91
144
  };
92
145
  };
93
146
  //#endregion
@@ -1,3 +1,4 @@
1
+ import { useMove } from "./use-move.js";
1
2
  import { useLocale } from "./use-locale.js";
2
3
  import { useLocalizedStringFormatter } from "./use-localized-string-formatter.js";
3
4
  import { colorStrings } from "../i18n/color.js";
@@ -6,7 +7,6 @@ import { visuallyHiddenStyle } from "../utils/visually-hidden.js";
6
7
  import { useLabels } from "./use-labels.js";
7
8
  import { setFormValue } from "../utils/form-value.js";
8
9
  import { useColorAreaGradient } from "./use-color-area-gradient.js";
9
- import { useMove } from "./use-move.js";
10
10
  import { computed, onScopeDispose, shallowRef, toValue, watch } from "vue";
11
11
  //#region src/composables/use-color-area.ts
12
12
  /**
@@ -68,7 +68,7 @@ var useColorArea = (options) => {
68
68
  flush: "post",
69
69
  immediate: true
70
70
  });
71
- useFormReset(inputXEl, () => state.defaultValue.value, state.setValue);
71
+ useFormReset(inputXEl, () => state.defaultValue.value, state.setValue, options.form);
72
72
  /** Where the thumb is during a drag, in fractions of the area — not in pixels. */
73
73
  let currentPosition = null;
74
74
  /** The pointer that owns the interaction in flight, if any. */
@@ -86,7 +86,7 @@ var useColorField = (options = {}) => {
86
86
  setInputValue: state.setInputValue,
87
87
  validate: state.validate
88
88
  });
89
- useFormReset(field.element, () => state.defaultColorValue.value, state.setColorValue);
89
+ useFormReset(field.element, () => state.defaultColorValue.value, state.setColorValue, options.form);
90
90
  /**
91
91
  * Whether the input has focus, which gates the wheel.
92
92
  *
@@ -119,7 +119,7 @@ var useComboBox = (options, state) => {
119
119
  validationState: state,
120
120
  value: () => state.inputValue.value
121
121
  });
122
- useFormReset(computed(() => inputElement.value), () => state.defaultValue.value, state.setValue);
122
+ useFormReset(computed(() => inputElement.value), () => state.defaultValue.value, state.setValue, options.form);
123
123
  /**
124
124
  * Whether focus is still somewhere inside the combo box.
125
125
  *
@@ -89,7 +89,7 @@ var useDateField = (options) => {
89
89
  options.onFocusChange?.(false);
90
90
  };
91
91
  const inputValue = computed(() => toValue(options.inputValue) ?? state.value.value?.toString() ?? "");
92
- useFormReset(options.inputElement, state.defaultValue, state.setValue);
92
+ useFormReset(options.inputElement, state.defaultValue, state.setValue, options.form);
93
93
  watch([options.inputElement, inputValue], ([input, text]) => setFormValue(input, text), {
94
94
  flush: "post",
95
95
  immediate: true
@@ -31,12 +31,13 @@ export interface UseDragHandlers {
31
31
  onKeyupCapture?: (event: KeyboardEvent) => void;
32
32
  onClick?: (event: MouseEvent) => void;
33
33
  }
34
+ /** Attributes for a draggable element. Attributes only, never listeners. */
35
+ export interface DragAttrs {
36
+ draggable?: "true" | "false";
37
+ "aria-describedby"?: string;
38
+ }
34
39
  export interface UseDragReturn {
35
- /** Attributes for the draggable element. Attributes only, never listeners. */
36
- attrs: ComputedRef<{
37
- draggable: "true" | "false";
38
- "aria-describedby"?: string;
39
- }>;
40
+ attrs: ComputedRef<Required<Pick<DragAttrs, "draggable">> & DragAttrs>;
40
41
  handlers: UseDragHandlers;
41
42
  /** Attributes for the explicit drag control, when there is one. */
42
43
  dragButtonAttrs: ComputedRef<{
@@ -1,5 +1,5 @@
1
1
  import type { DragKey } from "../utils/dnd-types.js";
2
- import type { UseDragHandlers } from "./use-drag.js";
2
+ import type { DragAttrs, UseDragHandlers } from "./use-drag.js";
3
3
  import type { UseDraggableCollectionStateReturn } from "./use-draggable-collection-state.js";
4
4
  import type { PressEvent } from "./use-press.js";
5
5
  import type { ComputedRef, MaybeRefOrGetter } from "vue";
@@ -16,7 +16,7 @@ export interface UseDraggableItemOptions {
16
16
  hasAction?: MaybeRefOrGetter<boolean | undefined>;
17
17
  }
18
18
  export interface UseDraggableItemReturn {
19
- attrs: ComputedRef<Record<string, unknown>>;
19
+ attrs: ComputedRef<DragAttrs>;
20
20
  handlers: UseDragHandlers;
21
21
  dragButtonAttrs: ComputedRef<{
22
22
  "aria-describedby": string | undefined;
@@ -3,11 +3,11 @@ import { useLocale } from "./use-locale.js";
3
3
  import { DIRECTORY_DRAG_TYPE } from "../utils/dnd-constants.js";
4
4
  import { dragTypesFromSet, getTypes } from "../utils/dnd-data-transfer.js";
5
5
  import { registerDropTarget } from "./drag-manager.js";
6
- import { registerDroppableCollection } from "./droppable-collection-registry.js";
7
- import { useAutoScroll } from "./use-auto-scroll.js";
8
6
  import { clearGlobalDnDState, globalDndState, isInternalDropOperation, setDropCollectionRef } from "../utils/dnd-state.js";
9
- import { navigateDropTarget } from "../utils/dnd-drop-target-navigation.js";
10
7
  import { useDrop } from "./use-drop.js";
8
+ import { registerDroppableCollection } from "./droppable-collection-registry.js";
9
+ import { navigateDropTarget } from "../utils/dnd-drop-target-navigation.js";
10
+ import { useAutoScroll } from "./use-auto-scroll.js";
11
11
  import { computed, onScopeDispose, useId, watch } from "vue";
12
12
  //#region src/composables/use-droppable-collection.ts
13
13
  /** How long to wait for a drop handler's async work before deciding where focus lands. */
@@ -1,8 +1,8 @@
1
1
  import { getTypes } from "../utils/dnd-data-transfer.js";
2
2
  import { registerDropItem, useDragSession } from "./drag-manager.js";
3
- import { getDroppableCollectionElement } from "./droppable-collection-registry.js";
4
3
  import { globalDndState, isInternalDropOperation } from "../utils/dnd-state.js";
5
4
  import { useVirtualDrop } from "./use-virtual-drop.js";
5
+ import { getDroppableCollectionElement } from "./droppable-collection-registry.js";
6
6
  import { computed, watch } from "vue";
7
7
  //#region src/composables/use-droppable-item.ts
8
8
  /**
@@ -13,11 +13,15 @@ type ResettableElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElem
13
13
  * `initialValue` is read when the reset happens rather than when the listener is attached,
14
14
  * so a default that changes over the component's life still resets to the current one.
15
15
  *
16
+ * `form` is the `id` the caller renders as the control's `form` attribute, for a control that
17
+ * submits to a form it does not sit inside. Pass it wherever that attribute is rendered: it is
18
+ * what moves the listener when a mounted control is repointed.
19
+ *
16
20
  * @example
17
21
  * ```ts
18
22
  * const input = useTemplateRef<HTMLInputElement>("input");
19
- * useFormReset(input, () => props.defaultSelected ?? false, setSelected);
23
+ * useFormReset(input, () => props.defaultSelected ?? false, setSelected, () => props.form);
20
24
  * ```
21
25
  */
22
- export declare const useFormReset: <T>(element: Ref<ResettableElement | null | undefined>, initialValue: MaybeRefOrGetter<T>, onReset: (value: T) => void) => void;
26
+ export declare const useFormReset: <T>(element: Ref<ResettableElement | null | undefined>, initialValue: MaybeRefOrGetter<T>, onReset: (value: T) => void, form?: MaybeRefOrGetter<string | undefined>) => void;
23
27
  export {};
@@ -12,13 +12,17 @@ import { onScopeDispose, toValue, watch } from "vue";
12
12
  * `initialValue` is read when the reset happens rather than when the listener is attached,
13
13
  * so a default that changes over the component's life still resets to the current one.
14
14
  *
15
+ * `form` is the `id` the caller renders as the control's `form` attribute, for a control that
16
+ * submits to a form it does not sit inside. Pass it wherever that attribute is rendered: it is
17
+ * what moves the listener when a mounted control is repointed.
18
+ *
15
19
  * @example
16
20
  * ```ts
17
21
  * const input = useTemplateRef<HTMLInputElement>("input");
18
- * useFormReset(input, () => props.defaultSelected ?? false, setSelected);
22
+ * useFormReset(input, () => props.defaultSelected ?? false, setSelected, () => props.form);
19
23
  * ```
20
24
  */
21
- var useFormReset = (element, initialValue, onReset) => {
25
+ var useFormReset = (element, initialValue, onReset, form) => {
22
26
  let attachedForm = null;
23
27
  const onFormReset = (event) => {
24
28
  if (event.defaultPrevented) return;
@@ -28,10 +32,10 @@ var useFormReset = (element, initialValue, onReset) => {
28
32
  attachedForm?.removeEventListener("reset", onFormReset);
29
33
  attachedForm = null;
30
34
  };
31
- watch(() => element.value?.form ?? null, (form) => {
35
+ watch([() => element.value?.form ?? null, () => toValue(form)], ([owner]) => {
32
36
  detach();
33
- form?.addEventListener("reset", onFormReset);
34
- attachedForm = form;
37
+ owner?.addEventListener("reset", onFormReset);
38
+ attachedForm = owner;
35
39
  }, {
36
40
  flush: "post",
37
41
  immediate: true
@@ -169,7 +169,7 @@ var useListKeyboard = (options) => {
169
169
  }
170
170
  return next ?? (step === 1 ? getLastKey() : getFirstKey());
171
171
  };
172
- const getKeyForSearch = (search, fromKey) => {
172
+ const getKeyForSearch = (search, fromKey) => collection.withOrder(() => {
173
173
  let key = fromKey ?? getFirstKey();
174
174
  while (key != null) {
175
175
  const item = collection.getItem(key);
@@ -179,7 +179,7 @@ var useListKeyboard = (options) => {
179
179
  key = walk(collection.getKeyAfter(key), 1);
180
180
  }
181
181
  return null;
182
- };
182
+ });
183
183
  const land = (element, scroll) => {
184
184
  if (!isVirtual.value) element.focus();
185
185
  if (scroll && typeof element.scrollIntoView === "function") element.scrollIntoView({ block: "nearest" });
@@ -228,67 +228,69 @@ var useListKeyboard = (options) => {
228
228
  event.stopPropagation();
229
229
  return true;
230
230
  };
231
- switch (event.key) {
232
- case "ArrowDown":
233
- move(focused == null ? getFirstKey() : getKeyBelow(focused));
234
- return;
235
- case "ArrowUp":
236
- move(focused == null ? getLastKey() : getKeyAbove(focused));
237
- return;
238
- case "ArrowRight":
239
- if (!answersInlineAxis()) return;
240
- move(focused == null ? getFirstKey() : getKeyRightOf(focused));
241
- return;
242
- case "ArrowLeft":
243
- if (!answersInlineAxis()) return;
244
- move(focused == null ? getLastKey() : getKeyLeftOf(focused));
245
- return;
246
- case "Home":
247
- if (focused == null && event.shiftKey) return;
248
- move(getFirstKey(), false);
249
- return;
250
- case "End":
251
- if (focused == null && event.shiftKey) return;
252
- move(getLastKey(), false);
253
- return;
254
- case "PageUp":
255
- move(focused == null ? getFirstKey() : pageStep(focused, -1));
256
- return;
257
- case "PageDown":
258
- move(focused == null ? getLastKey() : pageStep(focused, 1));
259
- return;
260
- case "a":
261
- if (!(event.ctrlKey || event.metaKey) || toValue(options.disallowSelectAll)) return;
262
- if (selection.selectionMode.value !== "multiple") return;
263
- selection.selectAll();
264
- event.preventDefault();
265
- event.stopPropagation();
266
- return;
267
- case "Escape":
268
- if (escapeKeyBehavior.value !== "clearSelection" || selection.isEmpty.value) return;
269
- selection.clearSelection();
270
- event.preventDefault();
271
- event.stopPropagation();
272
- return;
273
- case "Tab": {
274
- if (isVirtual.value) return;
275
- const focusable = focusableIn(element);
276
- (event.shiftKey ? element : focusable.at(-1))?.focus();
277
- return;
231
+ collection.withOrder(() => {
232
+ switch (event.key) {
233
+ case "ArrowDown":
234
+ move(focused == null ? getFirstKey() : getKeyBelow(focused));
235
+ return;
236
+ case "ArrowUp":
237
+ move(focused == null ? getLastKey() : getKeyAbove(focused));
238
+ return;
239
+ case "ArrowRight":
240
+ if (!answersInlineAxis()) return;
241
+ move(focused == null ? getFirstKey() : getKeyRightOf(focused));
242
+ return;
243
+ case "ArrowLeft":
244
+ if (!answersInlineAxis()) return;
245
+ move(focused == null ? getLastKey() : getKeyLeftOf(focused));
246
+ return;
247
+ case "Home":
248
+ if (focused == null && event.shiftKey) return;
249
+ move(getFirstKey(), false);
250
+ return;
251
+ case "End":
252
+ if (focused == null && event.shiftKey) return;
253
+ move(getLastKey(), false);
254
+ return;
255
+ case "PageUp":
256
+ move(focused == null ? getFirstKey() : pageStep(focused, -1));
257
+ return;
258
+ case "PageDown":
259
+ move(focused == null ? getLastKey() : pageStep(focused, 1));
260
+ return;
261
+ case "a":
262
+ if (!(event.ctrlKey || event.metaKey) || toValue(options.disallowSelectAll)) return;
263
+ if (selection.selectionMode.value !== "multiple") return;
264
+ selection.selectAll();
265
+ event.preventDefault();
266
+ event.stopPropagation();
267
+ return;
268
+ case "Escape":
269
+ if (escapeKeyBehavior.value !== "clearSelection" || selection.isEmpty.value) return;
270
+ selection.clearSelection();
271
+ event.preventDefault();
272
+ event.stopPropagation();
273
+ return;
274
+ case "Tab": {
275
+ if (isVirtual.value) return;
276
+ const focusable = focusableIn(element);
277
+ (event.shiftKey ? element : focusable.at(-1))?.focus();
278
+ return;
279
+ }
280
+ case "Enter":
281
+ if (focused == null || toValue(options.disallowActivation)) return;
282
+ options.onAction?.(focused);
283
+ event.preventDefault();
284
+ return;
285
+ case " ":
286
+ if (focused == null || toValue(options.disallowActivation)) return;
287
+ if (selection.selectionMode.value === "none") options.onAction?.(focused);
288
+ else selection.select(focused, { isShiftPressed: event.shiftKey });
289
+ event.preventDefault();
290
+ return;
291
+ default: return;
278
292
  }
279
- case "Enter":
280
- if (focused == null || toValue(options.disallowActivation)) return;
281
- options.onAction?.(focused);
282
- event.preventDefault();
283
- return;
284
- case " ":
285
- if (focused == null || toValue(options.disallowActivation)) return;
286
- if (selection.selectionMode.value === "none") options.onAction?.(focused);
287
- else selection.select(focused, { isShiftPressed: event.shiftKey });
288
- event.preventDefault();
289
- return;
290
- default: return;
291
- }
293
+ });
292
294
  };
293
295
  const onFocusin = (event) => {
294
296
  if (isVirtual.value) return;
@@ -128,7 +128,7 @@ var useNumberField = (options = {}) => {
128
128
  setInputValue: state.setInputValue,
129
129
  validate: state.validate
130
130
  });
131
- useFormReset(element, () => state.defaultNumberValue.value, state.setNumberValue);
131
+ useFormReset(element, () => state.defaultNumberValue.value, state.setNumberValue, options.form);
132
132
  /**
133
133
  * Range validation borrowed from a real number input.
134
134
  *
@@ -46,7 +46,7 @@ var useSelect = (options, state) => {
46
46
  sensitivity: "base",
47
47
  usage: "search"
48
48
  });
49
- const getKeyForSearch = (search, fromKey) => {
49
+ const getKeyForSearch = (search, fromKey) => state.collection.withOrder(() => {
50
50
  let key = fromKey ?? state.collection.getFirstKey();
51
51
  while (key != null) {
52
52
  const item = state.collection.getItem(key);
@@ -56,7 +56,7 @@ var useSelect = (options, state) => {
56
56
  key = state.collection.getKeyAfter(key);
57
57
  }
58
58
  return null;
59
- };
59
+ });
60
60
  const typeahead = useTypeahead({
61
61
  focusedKey: () => state.selectedKey.value,
62
62
  getKeyForSearch,
@@ -73,13 +73,13 @@ var useSelectionManager = (options) => {
73
73
  commit(next);
74
74
  };
75
75
  /** The keys from `from` to `to` inclusive, in document order whichever way round they are. */
76
- const keyRange = (from, to) => {
76
+ const keyRange = (from, to) => collection.withOrder(() => {
77
77
  const keys = collection.orderedKeys();
78
- const start = keys.indexOf(from);
79
- const end = keys.indexOf(to);
78
+ const start = collection.getIndex(from);
79
+ const end = collection.getIndex(to);
80
80
  if (start === -1 || end === -1) return [];
81
81
  return keys.slice(Math.min(start, end), Math.max(start, end) + 1);
82
- };
82
+ });
83
83
  const extendSelection = (key) => {
84
84
  if (selectionMode.value === "none") return;
85
85
  if (selectionMode.value === "single") {
@@ -0,0 +1,37 @@
1
+ import type { CollectionKey, UseCollectionReturn } from "./use-collection.js";
2
+ import type { UseSelectionManagerReturn } from "./use-selection-manager.js";
3
+ import type { ComputedRef, MaybeRefOrGetter } from "vue";
4
+ export interface UseSingleSelectListStateOptions {
5
+ /** The selected item, when the caller drives it. */
6
+ selectedKey?: MaybeRefOrGetter<CollectionKey | undefined>;
7
+ /** The initially selected item. Falls back to the first item that is not disabled. */
8
+ defaultSelectedKey?: CollectionKey;
9
+ onSelectionChange?: (key: CollectionKey) => void;
10
+ disabledKeys?: MaybeRefOrGetter<Iterable<CollectionKey> | undefined>;
11
+ /** Whether the whole list is disabled: a selection that exists but cannot be changed. */
12
+ isDisabled?: MaybeRefOrGetter<boolean | undefined>;
13
+ }
14
+ export interface UseSingleSelectListStateReturn {
15
+ collection: UseCollectionReturn;
16
+ selection: UseSelectionManagerReturn;
17
+ /** The selected item, with the default resolved against the collection. */
18
+ selectedKey: ComputedRef<CollectionKey | null>;
19
+ setSelectedKey: (key: CollectionKey) => void;
20
+ isDisabled: ComputedRef<boolean>;
21
+ }
22
+ /**
23
+ * Which item of a collection is selected, for a list that always has exactly one.
24
+ *
25
+ * Ported from React Stately's `useSingleSelectListState`
26
+ * (`react-stately/src/list/useSingleSelectListState.ts`, react-stately 3.49.0).
27
+ *
28
+ * "Always exactly one" is `disallowEmptySelection` plus a fallback for the case where nothing says
29
+ * which. The selection behaviour is deliberately left at toggle: whether the arrow keys choose is a
30
+ * question about how activation works, asked by the caller through `selectOnFocus`, not a question
31
+ * about what a press means.
32
+ *
33
+ * The three pieces have to be built in one order — the collection, then the fallback that reads it,
34
+ * then the selection manager the fallback feeds — which is the reason this is a composable rather
35
+ * than a few lines in the root component.
36
+ */
37
+ export declare const useSingleSelectListState: (options?: UseSingleSelectListStateOptions) => UseSingleSelectListStateReturn;
@@ -0,0 +1,110 @@
1
+ import { useControllableState } from "./use-controllable-state.js";
2
+ import { useCollection } from "./use-collection.js";
3
+ import { useSelectionManager } from "./use-selection-manager.js";
4
+ import { computed, toValue, watch } from "vue";
5
+ //#region src/composables/use-single-select-list-state.ts
6
+ /**
7
+ * Which item of a collection is selected, for a list that always has exactly one.
8
+ *
9
+ * Ported from React Stately's `useSingleSelectListState`
10
+ * (`react-stately/src/list/useSingleSelectListState.ts`, react-stately 3.49.0).
11
+ *
12
+ * "Always exactly one" is `disallowEmptySelection` plus a fallback for the case where nothing says
13
+ * which. The selection behaviour is deliberately left at toggle: whether the arrow keys choose is a
14
+ * question about how activation works, asked by the caller through `selectOnFocus`, not a question
15
+ * about what a press means.
16
+ *
17
+ * The three pieces have to be built in one order — the collection, then the fallback that reads it,
18
+ * then the selection manager the fallback feeds — which is the reason this is a composable rather
19
+ * than a few lines in the root component.
20
+ */
21
+ var useSingleSelectListState = (options = {}) => {
22
+ const collection = useCollection();
23
+ const disabledKeySet = computed(() => new Set(toValue(options.disabledKeys) ?? []));
24
+ /**
25
+ * Whether an item can be selected, asked of the collection rather than of the selection manager.
26
+ *
27
+ * Two reasons, and either alone would be enough: the fallback below runs before the manager
28
+ * exists, and React Stately asks the same question the same way — an item's own disabled state
29
+ * or the group's disabled keys, with no `disabledBehavior` in it.
30
+ */
31
+ const isKeyDisabled = (key) => disabledKeySet.value.has(key) || Boolean(collection.getItem(key)?.isDisabled());
32
+ /** The first item that can be selected, or the first item when none of them can. */
33
+ const findDefaultSelectedKey = () => collection.withOrder(() => {
34
+ let key = collection.getFirstKey();
35
+ while (key != null && isKeyDisabled(key) && key !== collection.getLastKey()) key = collection.getKeyAfter(key);
36
+ if (key != null && isKeyDisabled(key) && key === collection.getLastKey()) key = collection.getFirstKey();
37
+ return key;
38
+ });
39
+ const stored = useControllableState({
40
+ defaultValue: options.defaultSelectedKey ?? null,
41
+ onValueChange: (key) => {
42
+ if (key != null) options.onSelectionChange?.(key);
43
+ },
44
+ value: () => toValue(options.selectedKey)
45
+ });
46
+ /**
47
+ * The selected item, with the fallback resolved as a read rather than written back.
48
+ *
49
+ * React Stately can resolve it before the first render, because the collection is built by a
50
+ * pass that runs before it. Rendering is what creates the collection here, so writing the
51
+ * fallback into the selection would report a selection change nobody made, on every mount of
52
+ * every uncontrolled list.
53
+ *
54
+ * `collection.size` is read for its own sake: the fallback walks the collection through
55
+ * calls that stay outside reactivity on purpose, so without it this would resolve against the
56
+ * empty collection of the first render and keep that answer.
57
+ */
58
+ const selectedKey = computed(() => {
59
+ const key = stored.state.value;
60
+ if (stored.isControlled.value) return key ?? null;
61
+ if (key != null && collection.getItem(key)) return key;
62
+ return findDefaultSelectedKey();
63
+ });
64
+ const selection = useSelectionManager({
65
+ collection,
66
+ disabledKeys: () => toValue(options.disabledKeys),
67
+ disallowEmptySelection: true,
68
+ onSelectionChange: (keys) => {
69
+ if (keys === "all") return;
70
+ const [key] = [...keys];
71
+ if (key != null) stored.setState(key);
72
+ },
73
+ selectedKeys: () => selectedKey.value != null ? [selectedKey.value] : [],
74
+ selectionMode: "single"
75
+ });
76
+ let lastSelectedKey = selectedKey.value;
77
+ /**
78
+ * The selection places focus, because on a list like this it is the only thing that can.
79
+ *
80
+ * React Aria's tab list element ends up with no tab index of its own, so the roving index on
81
+ * the selected item is the only way in — and with no focused key nothing inside is tabbable
82
+ * either, which would put the whole list out of the tab order.
83
+ *
84
+ * Post-flush and keyed on the collection's size, because setting a focused key the collection
85
+ * does not hold is refused: the first attempt runs before the items have registered, and the
86
+ * registration is what makes the retry land.
87
+ */
88
+ watch([
89
+ selectedKey,
90
+ collection.size,
91
+ () => selection.focusedKey.value == null
92
+ ], () => {
93
+ const key = selectedKey.value;
94
+ if (key == null) return;
95
+ if (selection.focusedKey.value == null || !selection.isFocused.value && key !== lastSelectedKey) selection.setFocusedKey(key);
96
+ lastSelectedKey = key;
97
+ }, {
98
+ flush: "post",
99
+ immediate: true
100
+ });
101
+ return {
102
+ collection,
103
+ isDisabled: computed(() => Boolean(toValue(options.isDisabled))),
104
+ selectedKey,
105
+ selection,
106
+ setSelectedKey: (key) => stored.setState(key)
107
+ };
108
+ };
109
+ //#endregion
110
+ export { useSingleSelectListState };