fluentui-svelte 0.6.0 → 0.7.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 (243) hide show
  1. package/dist/components/app-surface/app-surface.svelte +96 -0
  2. package/dist/components/app-surface/app-surface.svelte.d.ts +22 -0
  3. package/dist/components/app-surface/docs/llms.md +73 -0
  4. package/dist/components/app-surface/types.d.ts +19 -0
  5. package/dist/components/app-surface/types.js +1 -0
  6. package/dist/components/app-window/app-window.d.ts +3 -0
  7. package/dist/components/app-window/app-window.js +3 -0
  8. package/dist/components/app-window/app-window.svelte +193 -0
  9. package/dist/components/app-window/app-window.svelte.d.ts +25 -0
  10. package/dist/components/app-window/docs/llms.md +119 -0
  11. package/dist/components/app-window/types.d.ts +66 -0
  12. package/dist/components/app-window/types.js +1 -0
  13. package/dist/components/auto-suggest-box/auto-suggest-box.svelte +47 -31
  14. package/dist/components/auto-suggest-box/auto-suggest-box.svelte.d.ts +2 -2
  15. package/dist/components/auto-suggest-box/docs/llms.md +34 -33
  16. package/dist/components/auto-suggest-box/option.svelte +6 -4
  17. package/dist/components/auto-suggest-box/types.d.ts +13 -16
  18. package/dist/components/avatar/avatar.svelte +4 -3
  19. package/dist/components/avatar/docs/llms.md +1 -1
  20. package/dist/components/avatar/type.d.ts +1 -1
  21. package/dist/components/badge/badge-icon.svelte +2 -2
  22. package/dist/components/badge/badge.svelte +2 -2
  23. package/dist/components/badge/docs/llms.md +2 -2
  24. package/dist/components/badge/types.d.ts +2 -2
  25. package/dist/components/breadcrumb/breadcrumb-button.svelte +2 -2
  26. package/dist/components/breadcrumb/breadcrumb-divider.svelte +2 -2
  27. package/dist/components/breadcrumb/breadcrumb-item.svelte +2 -2
  28. package/dist/components/breadcrumb/breadcrumb.svelte +10 -5
  29. package/dist/components/breadcrumb/docs/llms.md +11 -11
  30. package/dist/components/breadcrumb/types.d.ts +7 -7
  31. package/dist/components/button/button.svelte +2 -2
  32. package/dist/components/button/docs/llms.md +17 -17
  33. package/dist/components/button/split-button.svelte +7 -7
  34. package/dist/components/button/types.d.ts +5 -5
  35. package/dist/components/calendar-date-picker/calendar-date-picker.svelte +98 -10
  36. package/dist/components/calendar-date-picker/calendar-date-picker.svelte.d.ts +1 -1
  37. package/dist/components/calendar-date-picker/docs/llms.md +19 -14
  38. package/dist/components/calendar-date-picker/types.d.ts +22 -8
  39. package/dist/components/calendar-view/calendar-view-days.svelte +3 -2
  40. package/dist/components/calendar-view/calendar-view-grid.js +8 -3
  41. package/dist/components/calendar-view/calendar-view-item.svelte +10 -4
  42. package/dist/components/calendar-view/calendar-view-months.svelte +3 -2
  43. package/dist/components/calendar-view/calendar-view-years.svelte +3 -2
  44. package/dist/components/calendar-view/calendar-view.svelte +11 -4
  45. package/dist/components/calendar-view/calendar-view.svelte.js +5 -0
  46. package/dist/components/calendar-view/docs/llms.md +20 -19
  47. package/dist/components/calendar-view/types.d.ts +6 -1
  48. package/dist/components/card/card-footer.svelte +2 -2
  49. package/dist/components/card/card-header.svelte +5 -4
  50. package/dist/components/card/card-preview.svelte +13 -2
  51. package/dist/components/card/card.svelte +5 -5
  52. package/dist/components/card/docs/llms.md +22 -21
  53. package/dist/components/card/types.d.ts +8 -4
  54. package/dist/components/checkbox/checkbox.svelte +47 -26
  55. package/dist/components/checkbox/checkbox.svelte.d.ts +3 -24
  56. package/dist/components/checkbox/docs/llms.md +24 -23
  57. package/dist/components/checkbox/types.d.ts +10 -23
  58. package/dist/components/date-picker/date-picker.svelte +98 -27
  59. package/dist/components/date-picker/date-picker.svelte.d.ts +1 -1
  60. package/dist/components/date-picker/docs/llms.md +23 -20
  61. package/dist/components/date-picker/types.d.ts +24 -15
  62. package/dist/components/dialog/dialog-actions.svelte +2 -2
  63. package/dist/components/dialog/dialog-content.svelte +2 -2
  64. package/dist/components/dialog/dialog-surface.svelte +102 -9
  65. package/dist/components/dialog/dialog-title.svelte +2 -2
  66. package/dist/components/dialog/dialog-trigger.svelte +2 -2
  67. package/dist/components/dialog/dialog.svelte +22 -11
  68. package/dist/components/dialog/docs/llms.md +56 -19
  69. package/dist/components/dialog/types.d.ts +25 -7
  70. package/dist/components/divider/divider.svelte +6 -4
  71. package/dist/components/divider/types.d.ts +1 -1
  72. package/dist/components/dropdown/docs/llms.md +30 -28
  73. package/dist/components/dropdown/dropdown-option.svelte +17 -5
  74. package/dist/components/dropdown/dropdown.svelte +182 -84
  75. package/dist/components/dropdown/dropdown.svelte.d.ts +1 -1
  76. package/dist/components/dropdown/types.d.ts +26 -20
  77. package/dist/components/dropdown-select/docs/llms.md +24 -22
  78. package/dist/components/dropdown-select/dropdown-select-option.svelte +2 -2
  79. package/dist/components/dropdown-select/dropdown-select.svelte +42 -7
  80. package/dist/components/dropdown-select/types.d.ts +4 -4
  81. package/dist/components/expander/docs/llms.md +3 -3
  82. package/dist/components/expander/expander.svelte +13 -8
  83. package/dist/components/expander/types.d.ts +5 -5
  84. package/dist/components/flyout/docs/llms.md +1 -1
  85. package/dist/components/flyout/flyout.svelte +17 -6
  86. package/dist/components/flyout/types.d.ts +1 -1
  87. package/dist/components/hyperlink/docs/llms.md +6 -6
  88. package/dist/components/hyperlink/hyperlink.svelte +13 -4
  89. package/dist/components/hyperlink/types.d.ts +1 -1
  90. package/dist/components/info-bar/docs/llms.md +11 -10
  91. package/dist/components/info-bar/info-bar.svelte +3 -2
  92. package/dist/components/info-bar/types.d.ts +4 -0
  93. package/dist/components/label/docs/llms.md +75 -22
  94. package/dist/components/label/label-context.d.ts +14 -0
  95. package/dist/components/label/label-context.js +10 -0
  96. package/dist/components/label/label.svelte +153 -30
  97. package/dist/components/label/label.svelte.d.ts +1 -1
  98. package/dist/components/label/types.d.ts +48 -4
  99. package/dist/components/list-view/docs/llms.md +103 -33
  100. package/dist/components/list-view/list-view-item-branch.svelte +257 -0
  101. package/dist/components/list-view/list-view-item-branch.svelte.d.ts +4 -0
  102. package/dist/components/list-view/list-view-item.svelte +164 -46
  103. package/dist/components/list-view/list-view-item.svelte.d.ts +5 -5
  104. package/dist/components/list-view/list-view.svelte +73 -25
  105. package/dist/components/list-view/types.d.ts +71 -8
  106. package/dist/components/list-view/utils.d.ts +10 -0
  107. package/dist/components/list-view/utils.js +17 -1
  108. package/dist/components/menu/docs/llms.md +98 -15
  109. package/dist/components/menu/menu-command-bar-item.svelte +61 -0
  110. package/dist/components/menu/menu-command-bar-item.svelte.d.ts +14 -0
  111. package/dist/components/menu/menu-command-bar.svelte +218 -0
  112. package/dist/components/menu/menu-command-bar.svelte.d.ts +21 -0
  113. package/dist/components/menu/menu-group.svelte +6 -3
  114. package/dist/components/menu/menu-item-checkbox.svelte +5 -4
  115. package/dist/components/menu/menu-item-radio.svelte +5 -4
  116. package/dist/components/menu/menu-item-switch.svelte +5 -4
  117. package/dist/components/menu/menu-item.svelte +5 -5
  118. package/dist/components/menu/menu-item.svelte.d.ts +2 -3
  119. package/dist/components/menu/menu-list.svelte +10 -1
  120. package/dist/components/menu/menu-popover.svelte +9 -5
  121. package/dist/components/menu/menu-trigger.svelte +2 -1
  122. package/dist/components/menu/menu.svelte.js +1 -0
  123. package/dist/components/menu/types.d.ts +62 -7
  124. package/dist/components/number-box/docs/llms.md +6 -4
  125. package/dist/components/number-box/number-box.svelte +35 -29
  126. package/dist/components/number-box/number-box.svelte.d.ts +1 -1
  127. package/dist/components/number-box/types.d.ts +1 -1
  128. package/dist/components/persona/docs/llms.md +29 -25
  129. package/dist/components/persona/persona.svelte +8 -6
  130. package/dist/components/persona/types.d.ts +4 -3
  131. package/dist/components/progress-bar/docs/llms.md +13 -13
  132. package/dist/components/progress-bar/progress-bar.svelte +6 -6
  133. package/dist/components/progress-bar/types.d.ts +4 -4
  134. package/dist/components/progress-ring/docs/llms.md +14 -14
  135. package/dist/components/progress-ring/progress-ring.svelte +5 -5
  136. package/dist/components/progress-ring/types.d.ts +3 -3
  137. package/dist/components/radio-button/docs/llms.md +11 -8
  138. package/dist/components/radio-button/radio-button.svelte +38 -12
  139. package/dist/components/radio-button/radio-button.svelte.d.ts +1 -1
  140. package/dist/components/radio-button/types.d.ts +11 -8
  141. package/dist/components/rating/docs/llms.md +15 -14
  142. package/dist/components/rating/rating.svelte +66 -29
  143. package/dist/components/rating/rating.svelte.d.ts +1 -1
  144. package/dist/components/rating/types.d.ts +8 -8
  145. package/dist/components/side-nav/docs/llms.md +110 -0
  146. package/dist/components/side-nav/side-nav-branch.svelte +43 -0
  147. package/dist/components/side-nav/side-nav-branch.svelte.d.ts +4 -0
  148. package/dist/components/side-nav/side-nav-item.svelte +75 -0
  149. package/dist/components/side-nav/side-nav-item.svelte.d.ts +25 -0
  150. package/dist/components/side-nav/side-nav-list.svelte +32 -0
  151. package/dist/components/side-nav/side-nav-list.svelte.d.ts +7 -0
  152. package/dist/components/side-nav/side-nav.d.ts +2 -0
  153. package/dist/components/side-nav/side-nav.js +2 -0
  154. package/dist/components/side-nav/side-nav.svelte +355 -0
  155. package/dist/components/side-nav/side-nav.svelte.d.ts +24 -0
  156. package/dist/components/side-nav/types.d.ts +149 -0
  157. package/dist/components/side-nav/types.js +1 -0
  158. package/dist/components/skeleton/docs/llms.md +1 -1
  159. package/dist/components/skeleton/skeleton.svelte +2 -2
  160. package/dist/components/skeleton/types.d.ts +1 -1
  161. package/dist/components/slider/docs/llms.md +31 -28
  162. package/dist/components/slider/slider.svelte +126 -61
  163. package/dist/components/slider/slider.svelte.d.ts +1 -3
  164. package/dist/components/slider/types.d.ts +21 -14
  165. package/dist/components/tab-view/docs/llms.md +7 -7
  166. package/dist/components/tab-view/tab-view-tab.svelte +16 -14
  167. package/dist/components/tab-view/tab-view.svelte +16 -9
  168. package/dist/components/tab-view/types.d.ts +6 -6
  169. package/dist/components/table/docs/llms.md +38 -33
  170. package/dist/components/table/table-body.svelte +2 -2
  171. package/dist/components/table/table-cell-actions.svelte +8 -2
  172. package/dist/components/table/table-cell-layout.svelte +2 -2
  173. package/dist/components/table/table-cell.svelte +2 -2
  174. package/dist/components/table/table-header-cell.svelte +2 -2
  175. package/dist/components/table/table-header.svelte +2 -2
  176. package/dist/components/table/table-row.svelte +2 -2
  177. package/dist/components/table/table-selection-cell.svelte +6 -6
  178. package/dist/components/table/table.svelte +2 -2
  179. package/dist/components/table/types.d.ts +11 -11
  180. package/dist/components/tag/docs/llms.md +26 -26
  181. package/dist/components/tag/tag-group.svelte +2 -2
  182. package/dist/components/tag/tag.svelte +13 -12
  183. package/dist/components/tag/types.d.ts +6 -4
  184. package/dist/components/tag-picker/docs/llms.md +61 -55
  185. package/dist/components/tag-picker/option-group.svelte +17 -4
  186. package/dist/components/tag-picker/option-group.svelte.d.ts +1 -1
  187. package/dist/components/tag-picker/option.svelte +15 -4
  188. package/dist/components/tag-picker/tag-picker.svelte +137 -65
  189. package/dist/components/tag-picker/tag-picker.svelte.d.ts +2 -2
  190. package/dist/components/tag-picker/types.d.ts +36 -16
  191. package/dist/components/text-box/docs/llms.md +31 -4
  192. package/dist/components/text-box/text-box-button.svelte +3 -3
  193. package/dist/components/text-box/text-box-button.svelte.d.ts +1 -1
  194. package/dist/components/text-box/text-box.svelte +41 -14
  195. package/dist/components/text-box/types.d.ts +5 -5
  196. package/dist/components/textarea/docs/llms.md +4 -3
  197. package/dist/components/textarea/textarea.svelte +14 -6
  198. package/dist/components/textarea/types.d.ts +2 -4
  199. package/dist/components/time-picker/docs/llms.md +18 -15
  200. package/dist/components/time-picker/time-picker.svelte +133 -33
  201. package/dist/components/time-picker/time-picker.svelte.d.ts +1 -1
  202. package/dist/components/time-picker/types.d.ts +23 -13
  203. package/dist/components/title-bar/docs/llms.md +382 -0
  204. package/dist/components/title-bar/title-bar-left-controls.svelte +215 -0
  205. package/dist/components/title-bar/title-bar-left-controls.svelte.d.ts +22 -0
  206. package/dist/components/title-bar/title-bar-right-controls.svelte +190 -0
  207. package/dist/components/title-bar/title-bar-right-controls.svelte.d.ts +27 -0
  208. package/dist/components/title-bar/title-bar.d.ts +3 -0
  209. package/dist/components/title-bar/title-bar.js +4 -0
  210. package/dist/components/title-bar/title-bar.svelte +224 -0
  211. package/dist/components/title-bar/title-bar.svelte.d.ts +26 -0
  212. package/dist/components/title-bar/types.d.ts +165 -0
  213. package/dist/components/title-bar/types.js +1 -0
  214. package/dist/components/toggle-switch/docs/llms.md +11 -9
  215. package/dist/components/toggle-switch/toggle-switch.svelte +23 -14
  216. package/dist/components/toggle-switch/toggle-switch.svelte.d.ts +1 -1
  217. package/dist/components/toggle-switch/types.d.ts +12 -10
  218. package/dist/components/tooltip/docs/llms.md +1 -1
  219. package/dist/components/tooltip/tooltip.svelte +5 -4
  220. package/dist/components/tooltip/types.d.ts +1 -1
  221. package/dist/components/top-nav/docs/llms.md +1 -1
  222. package/dist/components/top-nav/top-nav-item.svelte +2 -2
  223. package/dist/components/top-nav/top-nav.svelte +2 -2
  224. package/dist/components/top-nav/types.d.ts +2 -2
  225. package/dist/components/tree-view/docs/llms.md +11 -11
  226. package/dist/components/tree-view/tree-view-item-content.svelte +3 -3
  227. package/dist/components/tree-view/tree-view-item.svelte +13 -5
  228. package/dist/components/tree-view/tree-view.svelte +20 -5
  229. package/dist/components/tree-view/tree-view.svelte.d.ts +1 -1
  230. package/dist/components/tree-view/tree-view.svelte.js +31 -3
  231. package/dist/components/tree-view/types.d.ts +7 -3
  232. package/dist/index.d.ts +18 -0
  233. package/dist/index.js +20 -0
  234. package/dist/internal/components/dynamic-carousel/dynamic-carousel.svelte +26 -5
  235. package/dist/internal/components/dynamic-carousel/dynamic-carousel.svelte.d.ts +2 -0
  236. package/dist/internal/index.d.ts +1 -0
  237. package/dist/internal/index.js +1 -0
  238. package/dist/internal/native-field.svelte.d.ts +44 -0
  239. package/dist/internal/native-field.svelte.js +85 -0
  240. package/dist/internal/utils.d.ts +16 -2
  241. package/dist/internal/utils.js +25 -3
  242. package/dist/types/index.d.ts +3 -3
  243. package/package.json +50 -36
@@ -1,16 +1,23 @@
1
- <script lang="ts" generics="T extends 'li' | 'a' | 'div' = 'li'">
1
+ <script lang="ts" generics="T extends 'li' | 'a' | 'button' | 'div' = 'li'">
2
2
  import { onMount } from 'svelte';
3
- import { COMPONENT_NAME, getListViewContext, ITEM_TAG } from './utils.js';
3
+ import {
4
+ attachTabspot,
5
+ COMPONENT_NAME,
6
+ getListViewContext,
7
+ ITEM_TAG,
8
+ LIST_VIEW_SKIP,
9
+ setListViewItemContext
10
+ } from './utils.js';
4
11
  import Checkbox from '../checkbox/checkbox.svelte';
5
12
  import { invokeHandlers, isObjectEmpty } from '../../internal/index.js';
6
13
  import type { ListViewItemProps, ListViewItemDOM } from './types.js';
7
- import { getTabspotAttributes } from 'tabspot';
14
+ import { getTabspotAttributes, type TabspotNodeOptions } from 'tabspot';
8
15
 
9
16
  const ID = $props.id();
10
17
 
11
18
  let {
12
19
  as = 'li' as T,
13
- ref = $bindable(),
20
+ ref = $bindable(null),
14
21
  shape,
15
22
  disabled,
16
23
  active: _active,
@@ -20,6 +27,8 @@
20
27
  class: classes,
21
28
  onAction,
22
29
  onfocus,
30
+ onclick,
31
+ onkeydown,
23
32
  children,
24
33
  ...attributes
25
34
  }: ListViewItemProps<T> = $props();
@@ -30,6 +39,9 @@
30
39
  */
31
40
  const _as = $derived.by(() => {
32
41
  if (!ITEM_TAG.includes(as)) throw new Error(`Invalid tag: ${as}. Must be one of ${ITEM_TAG.join(', ')}`);
42
+ if (as === 'button' && (checkmark || role === 'row')) {
43
+ throw new Error('ListViewItem buttons cannot contain checkmarks or composite row controls. Use li or div.');
44
+ }
33
45
  return as;
34
46
  });
35
47
 
@@ -41,75 +53,176 @@
41
53
  const { state: _state } = CONTEXT;
42
54
  const { handleSelection, registerItem, unregisterItem, getChildrenRole } = CONTEXT.methods;
43
55
  const role = $derived(_role || getChildrenRole(as));
56
+ const wrapped = $derived((as === 'a' || as === 'button') && CONTEXT.config.as !== 'div');
57
+ const nativeAction = $derived(as === 'a' || as === 'button');
58
+ const interactive = $derived(
59
+ nativeAction || !!_role || CONTEXT.config.selectionMode !== 'none' || !!onAction || !!onclick
60
+ );
61
+ const tabspotDisabled = $derived(CONTEXT.config.disableTabspot);
62
+ setListViewItemContext({
63
+ get as() {
64
+ return as;
65
+ }
66
+ });
44
67
 
45
68
  let active = $derived(_state.selectedItems.includes(value));
46
- let checkmarkRef = $state<HTMLDivElement>();
69
+ let checkmarkRef = $state<HTMLDivElement | null>(null);
47
70
 
48
- // A row is a level of its own: right enters the cells, left comes back out. Anything
49
- // that is not a row navigates as a plain item of the list above it.
50
- const tabspotAttrs = $derived(
51
- (role === 'row'
52
- ? getTabspotAttributes({
71
+ // A grid cell contains the secondary controls, independently of the primary row action.
72
+ const cellConfig: TabspotNodeOptions | undefined = $derived(
73
+ role === 'row' && !tabspotDisabled
74
+ ? {
75
+ // Checkbox inputs are transparent while their visible wrappers supply the focus ring.
53
76
  mover: {
54
- axis: 'vertical'
77
+ axis: 'horizontal',
78
+ skip: LIST_VIEW_SKIP
55
79
  },
56
80
  grouper: { enterDirection: 'right', exitDirection: 'left' }
57
- })
58
- : null) ?? {}
81
+ }
82
+ : undefined
83
+ );
84
+ const cellAttrs = $derived(cellConfig && getTabspotAttributes(cellConfig));
85
+ // A roving list has one tab stop. Tabspot sets it on mount, so the server HTML gives it to the first item, as Tabspot will.
86
+ // svelte-ignore state_referenced_locally
87
+ const holdsTabStop = !CONTEXT.config.roving || (interactive && !disabled && CONTEXT.methods.claimTabStop(ID));
88
+ const focusAttributes = $derived(
89
+ nativeAction
90
+ ? disabled && as === 'a'
91
+ ? { tabindex: -1 }
92
+ : {}
93
+ : interactive && !tabspotDisabled
94
+ ? { tabindex: disabled || !holdsTabStop ? -1 : 0 }
95
+ : {}
59
96
  );
60
97
 
98
+ function handleClick(e: MouseEvent) {
99
+ const target = e.target instanceof Element ? e.target : null;
100
+ if (target?.closest('.fs-list-view-item') !== e.currentTarget) return;
101
+ const fromCheckmark = !!checkmarkRef?.contains(target);
102
+ const control = target?.closest(
103
+ 'a, button, input, select, textarea, [contenteditable="true"], [role="button"], [role="link"]'
104
+ );
105
+ if (!fromCheckmark && control && control !== e.currentTarget) return;
106
+ invokeHandlers(e, disabled, [
107
+ onclick,
108
+ (role !== 'row' || fromCheckmark) && ((event: MouseEvent) => handleSelection(event, value, fromCheckmark)),
109
+ !fromCheckmark && ((event: MouseEvent) => onAction?.(event, value))
110
+ ]);
111
+ }
112
+
113
+ function handleKeydown(e: KeyboardEvent) {
114
+ invokeHandlers(e, disabled, [onkeydown]);
115
+ if (e.defaultPrevented || nativeAction || e.target !== e.currentTarget || !['Enter', ' '].includes(e.key)) return;
116
+ e.preventDefault();
117
+ (e.currentTarget as HTMLElement).dispatchEvent(
118
+ new MouseEvent('click', {
119
+ bubbles: true,
120
+ ctrlKey: e.ctrlKey,
121
+ metaKey: e.metaKey,
122
+ shiftKey: e.shiftKey
123
+ })
124
+ );
125
+ }
126
+
61
127
  onMount(() => {
62
- registerItem?.(ID, value as string, !!disabled);
63
- return () => unregisterItem?.(ID);
128
+ registerItem(ID, () => ({ value, disabled: !!disabled, element: ref ?? null }));
129
+ return () => unregisterItem(ID);
64
130
  });
65
131
  </script>
66
132
 
67
- <svelte:element
68
- this={_as}
69
- {role}
70
- data-value={value}
71
- aria-selected={role === 'option' ? active : undefined}
72
- aria-disabled={disabled || undefined}
73
- bind:this={ref as ListViewItemDOM[T]}
74
- class={[
75
- 'fs-list-view-item',
76
- shape ? shape : _shape,
77
- classes,
78
- { disabled },
79
- role !== 'row' && 'indicator',
80
- (_active || active) && 'active',
81
- !disabled && role && 'interactive'
82
- ]}
83
- {...tabspotAttrs}
84
- onfocus={(e: FocusEvent) => invokeHandlers(e, disabled, [(event: FocusEvent) => onfocus?.(event, value || '')])}
85
- onclick={(e: MouseEvent) => {
86
- // The checkmark only toggles the selection, even on a row. The primary action is for the rest of the item.
87
- const fromCheckmark = !!checkmarkRef?.contains(e.target as Node);
88
- invokeHandlers(e, disabled, [
89
- (role !== 'row' || fromCheckmark) && ((event: MouseEvent) => handleSelection?.(event, value, fromCheckmark)),
90
- !fromCheckmark && ((event: MouseEvent) => onAction?.(event, value || ''))
91
- ]);
92
- }}
93
- {...attributes}
94
- >
133
+ {#snippet content()}
95
134
  {#if checkmark}
96
135
  <Checkbox
136
+ {...!isObjectEmpty(checkmark) ? checkmark : {}}
97
137
  {disabled}
98
138
  bind:checked={active}
99
139
  bind:wrapperRef={checkmarkRef}
100
- tabindex={-1}
101
- {...!isObjectEmpty(checkmark) ? checkmark : {}}
140
+ tabindex={tabspotDisabled ? undefined : -1}
141
+ aria-label={checkmark?.['aria-label'] ?? attributes['aria-label'] ?? value}
142
+ inert={role === 'option' || undefined}
102
143
  />
103
144
  {/if}
104
145
  {@render children?.()}
105
- </svelte:element>
146
+ {/snippet}
147
+
148
+ {#snippet item()}
149
+ <svelte:element
150
+ this={_as}
151
+ {...attributes}
152
+ role={wrapped || (nativeAction && role === 'listitem') ? undefined : role}
153
+ type={as === 'button' ? 'button' : undefined}
154
+ disabled={as === 'button' ? disabled : undefined}
155
+ data-value={value}
156
+ data-list-view-interactive={interactive || undefined}
157
+ aria-selected={!wrapped && (role === 'option' || role === 'row') ? active : undefined}
158
+ aria-current={role !== 'option' && role !== 'row' && (_active || active) ? 'true' : undefined}
159
+ aria-disabled={disabled || undefined}
160
+ bind:this={ref as ListViewItemDOM[T]}
161
+ class={[
162
+ 'fs-list-view-item',
163
+ shape ? shape : _shape,
164
+ classes,
165
+ { disabled },
166
+ role !== 'row' && 'indicator',
167
+ (_active || active) && 'active',
168
+ !disabled && interactive && 'interactive'
169
+ ]}
170
+ {...focusAttributes}
171
+ onfocus={(e: FocusEvent) => {
172
+ if (e.target === e.currentTarget) invokeHandlers(e, disabled, [() => onfocus?.(e, value)]);
173
+ }}
174
+ onclick={handleClick}
175
+ onkeydown={handleKeydown}
176
+ >
177
+ {#if role === 'row'}
178
+ <div role="gridcell" {...cellAttrs} {@attach attachTabspot(cellConfig)}>
179
+ {@render content()}
180
+ </div>
181
+ {:else}
182
+ {@render content()}
183
+ {/if}
184
+ </svelte:element>
185
+ {/snippet}
186
+
187
+ {#if wrapped}
188
+ <li {role} aria-selected={role === 'option' || role === 'row' ? active : undefined} class="fs-list-view-item-wrapper">
189
+ {@render item()}
190
+ </li>
191
+ {:else}
192
+ {@render item()}
193
+ {/if}
106
194
 
107
195
  <style>
196
+ .fs-list-view-item-wrapper {
197
+ list-style: none;
198
+ & > :global(.fs-list-view-item) {
199
+ width: 100%;
200
+ box-sizing: border-box;
201
+ text-align: start;
202
+ }
203
+ }
204
+ .fs-list-view-item-wrapper,
205
+ .fs-list-view-item:has(> :global(.fs-list-view)) {
206
+ & > :global(.fs-list-view) {
207
+ padding-inline-start: 1rem;
208
+ box-sizing: border-box;
209
+ }
210
+ }
211
+ .fs-list-view-item:has(> :global(.fs-list-view)) {
212
+ display: block;
213
+ }
214
+ [role='gridcell'] {
215
+ display: flex;
216
+ gap: 0.5rem;
217
+ align-items: center;
218
+ width: 100%;
219
+ }
108
220
  .fs-list-view-item {
109
221
  border-radius: var(--fs-control-border-radius);
110
222
  font-size: var(--fs-body2-font-size);
111
223
  color: var(--fs-text-primary);
112
224
  border: none;
225
+ background: var(--fs-subtle-fill-transparent);
113
226
  padding: 0.275rem 0.713rem 0.4rem 0.713rem;
114
227
  position: relative;
115
228
  outline: none;
@@ -174,4 +287,9 @@
174
287
  border-radius: 50vw;
175
288
  }
176
289
  }
290
+ @media (prefers-reduced-motion: reduce) {
291
+ .fs-list-view-item.interactive.indicator.active::after {
292
+ transition: none;
293
+ }
294
+ }
177
295
  </style>
@@ -1,12 +1,12 @@
1
1
  import type { ListViewItemProps } from './types.ts';
2
- declare function $$render<T extends 'li' | 'a' | 'div' = 'li'>(): {
2
+ declare function $$render<T extends 'li' | 'a' | 'button' | 'div' = 'li'>(): {
3
3
  props: ListViewItemProps<T>;
4
4
  exports: {};
5
5
  bindings: "ref";
6
6
  slots: {};
7
7
  events: {};
8
8
  };
9
- declare class __sveltets_Render<T extends 'li' | 'a' | 'div' = 'li'> {
9
+ declare class __sveltets_Render<T extends 'li' | 'a' | 'button' | 'div' = 'li'> {
10
10
  props(): ReturnType<typeof $$render<T>>['props'];
11
11
  events(): ReturnType<typeof $$render<T>>['events'];
12
12
  slots(): ReturnType<typeof $$render<T>>['slots'];
@@ -14,12 +14,12 @@ declare class __sveltets_Render<T extends 'li' | 'a' | 'div' = 'li'> {
14
14
  exports(): {};
15
15
  }
16
16
  interface $$IsomorphicComponent {
17
- new <T extends 'li' | 'a' | 'div' = 'li'>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
17
+ new <T extends 'li' | 'a' | 'button' | 'div' = 'li'>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
18
18
  $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
19
19
  } & ReturnType<__sveltets_Render<T>['exports']>;
20
- <T extends 'li' | 'a' | 'div' = 'li'>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
20
+ <T extends 'li' | 'a' | 'button' | 'div' = 'li'>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
21
21
  z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
22
22
  }
23
23
  declare const ListViewItem: $$IsomorphicComponent;
24
- type ListViewItem<T extends 'li' | 'a' | 'div' = 'li'> = InstanceType<typeof ListViewItem<T>>;
24
+ type ListViewItem<T extends 'li' | 'a' | 'button' | 'div' = 'li'> = InstanceType<typeof ListViewItem<T>>;
25
25
  export default ListViewItem;
@@ -1,19 +1,29 @@
1
1
  <script lang="ts" generics="Tag extends 'ul' | 'ol' | 'div' = 'ul'">
2
2
  import type { ListViewContext, ListViewProps, ListViewDOM } from './types.js';
3
- import { getRole, TAG, setListViewContext } from './utils.js';
4
- import { getTabspotAttributes } from 'tabspot';
3
+ import {
4
+ attachTabspot,
5
+ getListViewContext,
6
+ getListViewItemContext,
7
+ getRole,
8
+ LIST_VIEW_SKIP,
9
+ TAG,
10
+ setListViewContext
11
+ } from './utils.js';
12
+ import { getTabspotAttributes, type TabspotNodeOptions } from 'tabspot';
5
13
 
6
14
  let {
7
15
  as = 'ul' as Tag,
8
- ref = $bindable(),
16
+ ref = $bindable(null),
9
17
  selectionMode = 'none',
10
18
  navigationMode = 'items',
19
+ manageSpecialKeys = true,
11
20
  shape = 'rounded',
12
21
  selectedItems = $bindable([]),
13
22
  onSelectionChange,
14
23
  disableTabspot = false,
15
24
  toggleOnClick = false,
16
25
  children,
26
+ class: classes,
17
27
  ...attributes
18
28
  }: ListViewProps<Tag> = $props();
19
29
 
@@ -21,40 +31,69 @@
21
31
  * The tag the invariant rules over. The markup renders it through here, so it is checked
22
32
  * during SSR and on every prop update rather than only once on mount.
23
33
  */
34
+ const itemContext = getListViewItemContext();
24
35
  const _as = $derived.by(() => {
25
36
  if (!TAG.includes(as)) throw new Error(`Invalid tag: ${as}. Must be one of ${TAG.join(', ')}`);
37
+ if (itemContext?.as === 'a' || itemContext?.as === 'button') {
38
+ throw new Error('Nested ListView must be inside a li or div ListViewItem, outside links and buttons.');
39
+ }
26
40
  return as;
27
41
  });
28
42
 
29
- const ROLE = $derived(getRole(as, selectionMode, navigationMode));
43
+ const parent = getListViewContext();
44
+ const tabspotDisabled = $derived(disableTabspot || !!parent?.config.disableTabspot);
45
+ const ROLE = $derived(attributes.role ?? getRole(as, selectionMode, navigationMode));
30
46
 
31
47
  const MULTISELECTABLE = $derived(selectionMode === 'multiselect' || selectionMode === 'extended');
32
48
 
33
- const tabspotAttrs = $derived(
34
- !disableTabspot &&
35
- getTabspotAttributes({
36
- root: {
37
- manageSpecialKeys: true
38
- },
39
- mover: {
40
- axis: 'vertical',
41
- items: '.fs-list-view-item'
49
+ const tabspotConfig: TabspotNodeOptions | undefined = $derived(
50
+ tabspotDisabled
51
+ ? undefined
52
+ : {
53
+ ...(parent
54
+ ? { grouper: { enterDirection: 'right', exitDirection: 'left' } }
55
+ : { root: { manageSpecialKeys } }),
56
+ mover: {
57
+ axis: 'vertical',
58
+ items:
59
+ '.fs-list-view-item[data-list-view-interactive], .fs-list-view-item:not([data-list-view-interactive]) :is(a[href], button), .fs-list-view-item-branch-trigger',
60
+ visibilityAware: 'Visible',
61
+ skip: LIST_VIEW_SKIP,
62
+ ...(!parent && { activation: { mode: 'focus', roving: navigationMode === 'composite' } })
63
+ }
42
64
  }
43
- })
44
65
  );
66
+ const tabspotAttrs = $derived(tabspotConfig && getTabspotAttributes(tabspotConfig));
45
67
 
46
68
  let anchorIndex: number | null = $state(null);
47
69
 
48
70
  let items: {
49
71
  id: string;
50
- value: string;
51
- disabled: boolean;
72
+ getItem: () => { value: string; disabled: boolean; element: HTMLElement | null };
52
73
  }[] = $state([]);
53
74
 
75
+ // The item that holds the tab stop in the server HTML, before Tabspot runs. Plain, since only the first render reads it.
76
+ let tabStopId: string | null = null;
77
+
54
78
  const CONTEXT: ListViewContext = $state({
55
79
  config: {
56
80
  get shape() {
57
81
  return shape;
82
+ },
83
+ get as() {
84
+ return as;
85
+ },
86
+ get selectionMode() {
87
+ return selectionMode;
88
+ },
89
+ get role() {
90
+ return ROLE;
91
+ },
92
+ get disableTabspot() {
93
+ return tabspotDisabled;
94
+ },
95
+ get roving() {
96
+ return !parent && navigationMode === 'composite';
58
97
  }
59
98
  },
60
99
  state: {
@@ -73,19 +112,27 @@
73
112
  },
74
113
  events: null,
75
114
  methods: {
115
+ claimTabStop: (id) => (tabStopId ??= id) === id,
76
116
  handleSelection: (e: MouseEvent, value: string, toggle = false) => {
77
117
  if (selectionMode === 'none') return;
78
118
 
79
- const itemIndex = items.findIndex((item) => item.value === value);
119
+ // Registration order can differ from the DOM after keyed items are reordered.
120
+ const orderedItems = items
121
+ .map((item) => item.getItem())
122
+ .sort((a, b) => {
123
+ if (!a.element || !b.element) return 0;
124
+ return a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
125
+ });
126
+ const itemIndex = orderedItems.findIndex((item) => item.value === value);
80
127
  if (itemIndex === -1) return;
81
128
 
82
- const item = items[itemIndex];
129
+ const item = orderedItems[itemIndex];
83
130
  if (item.disabled) return;
84
131
 
85
132
  if (selectionMode === 'extended' && e.shiftKey && anchorIndex !== null) {
86
133
  const start = Math.min(anchorIndex, itemIndex);
87
134
  const end = Math.max(anchorIndex, itemIndex);
88
- const rangeValues = items
135
+ const rangeValues = orderedItems
89
136
  .slice(start, end + 1)
90
137
  .filter((i) => !i.disabled)
91
138
  .map((i) => i.value);
@@ -101,13 +148,13 @@
101
148
  }
102
149
  onSelectionChange?.(e, selectedItems);
103
150
  },
104
- registerItem: (id: string, value: string, disabled: boolean) => {
105
- items.push({ id, value, disabled });
151
+ registerItem: (id, getItem) => {
152
+ items.push({ id, getItem });
106
153
  },
107
154
  unregisterItem: (id: string) => {
108
155
  items = items.filter((item) => item.id !== id);
109
156
  },
110
- getChildrenRole: (_tag: 'li' | 'a' | 'div') => {
157
+ getChildrenRole: (_tag: 'li' | 'a' | 'button' | 'div') => {
111
158
  if (ROLE === 'grid') return 'row';
112
159
  if (ROLE === 'listbox') return 'option';
113
160
  return _tag !== 'li' ? 'listitem' : undefined;
@@ -120,12 +167,13 @@
120
167
 
121
168
  <svelte:element
122
169
  this={_as}
170
+ {...attributes}
123
171
  bind:this={ref as ListViewDOM[Tag]}
124
- class="fs-list-view"
172
+ class={['fs-list-view', classes]}
125
173
  role={ROLE}
126
- aria-multiselectable={MULTISELECTABLE || undefined}
174
+ aria-multiselectable={((ROLE === 'grid' || ROLE === 'listbox') && MULTISELECTABLE) || undefined}
127
175
  {...tabspotAttrs}
128
- {...attributes}
176
+ {@attach attachTabspot(tabspotConfig)}
129
177
  >
130
178
  {@render children?.()}
131
179
  </svelte:element>
@@ -1,10 +1,15 @@
1
1
  import type { CheckboxProps } from '../checkbox/types.ts';
2
2
  import type { FSContext } from '../../internal/index.js';
3
3
  import type { PolymorphicProps, Shapes } from '../../types/index.js';
4
+ import type { AriaRole, HTMLAttributes, HTMLButtonAttributes } from 'svelte/elements';
5
+ import type { Component, Snippet } from 'svelte';
6
+ import type { TabspotRootOptions } from 'tabspot';
4
7
  export type SelectionMode = 'none' | 'single' | 'multiselect' | 'extended';
5
8
  export type NavigationMode = 'items' | 'composite';
6
9
  /** @propsmith ListViewProps */
7
10
  export type ListViewProps<T extends 'ul' | 'ol' | 'div'> = {
11
+ /** Overrides the list role and the roles derived for its items. Use `list` for nested navigation with independent selection. */
12
+ role?: AriaRole;
8
13
  /** How many items can be selected, and how the selection is made.
9
14
  * @default 'none'
10
15
  */
@@ -13,6 +18,11 @@ export type ListViewProps<T extends 'ul' | 'ol' | 'div'> = {
13
18
  * @default 'items'
14
19
  */
15
20
  navigationMode?: NavigationMode;
21
+ /** Which special keys Tabspot handles at the root. Set individual keys to leave Escape to an enclosing panel. Omitted keys in an object are off.
22
+ * @type boolean | TabspotRootOptions['manageSpecialKeys']
23
+ * @default true
24
+ */
25
+ manageSpecialKeys?: TabspotRootOptions['manageSpecialKeys'];
16
26
  /** The values of the selected items.
17
27
  * @bindable
18
28
  */
@@ -36,7 +46,7 @@ export type ListViewProps<T extends 'ul' | 'ol' | 'div'> = {
36
46
  * @type HTMLUListElement | HTMLOListElement | HTMLDivElement
37
47
  * @bindable
38
48
  */
39
- ref?: ListViewDOM[T];
49
+ ref?: ListViewDOM[T] | null;
40
50
  /** Opts the list out of the tabspot focus management, to wire your own.
41
51
  * @default false
42
52
  */
@@ -48,12 +58,14 @@ export type ListViewDOM = {
48
58
  div: HTMLDivElement;
49
59
  };
50
60
  /** @propsmith ListViewItemProps */
51
- export type ListViewItemProps<T extends 'li' | 'a' | 'div' = 'li'> = {
61
+ export type ListViewItemProps<T extends 'li' | 'a' | 'button' | 'div' = 'li'> = {
52
62
  /** The HTML element to render the item as.
53
- * @type 'li' | 'a' | 'div'
63
+ * Links and buttons are wrapped in a list item when the list renders as `ul` or `ol`.
64
+ * Use `li` or `div` for nested lists and content with multiple controls. Buttons cannot contain checkmarks or composite rows.
65
+ * @type 'li' | 'a' | 'button' | 'div'
54
66
  * @default 'li'
55
67
  */
56
- as?: T | 'a' | 'div';
68
+ as?: T | 'a' | 'button' | 'div';
57
69
  /** The item can have a circular, rounded or square shape. Inherited from the list by default. */
58
70
  shape?: Shapes;
59
71
  /** The value reported to the list when the item is selected. Falls back to a generated id. */
@@ -73,18 +85,63 @@ export type ListViewItemProps<T extends 'li' | 'a' | 'div' = 'li'> = {
73
85
  /** Called when the item receives the focus. */
74
86
  onfocus?: (e: Event, value: string) => void;
75
87
  /** The DOM reference of the item element.
76
- * @type HTMLLIElement | HTMLAnchorElement | HTMLDivElement
88
+ * @type HTMLLIElement | HTMLAnchorElement | HTMLButtonElement | HTMLDivElement
77
89
  * @bindable
78
90
  */
79
91
  ref?: ListViewItemDOM[T] | null;
80
- } & PolymorphicProps<T>;
92
+ } & PolymorphicProps<T, 'onfocus'>;
81
93
  export type ListViewItemDOM = {
82
94
  li: HTMLLIElement;
83
95
  a: HTMLAnchorElement;
96
+ button: HTMLButtonElement;
84
97
  div: HTMLDivElement;
85
98
  };
99
+ /** @propsmith ListViewItemBranchProps */
100
+ export type ListViewItemBranchProps = {
101
+ /** The accessible label rendered in the disclosure button. */
102
+ label: string;
103
+ /** The decorative icon before the label.
104
+ * @type Snippet | Component
105
+ */
106
+ icon?: Snippet | Component;
107
+ /** Whether the nested content is visible. Branches only expand or collapse and are never selectable.
108
+ * @default false
109
+ * @bindable
110
+ */
111
+ expanded?: boolean;
112
+ /** Called when a user expands or collapses the branch. */
113
+ onExpandedChange?: (e: Event, expanded: boolean) => void;
114
+ /** Disables the disclosure button.
115
+ * @default false
116
+ */
117
+ disabled?: boolean;
118
+ /** The disclosure button shape. Inherited from the list by default. */
119
+ shape?: Shapes;
120
+ /** The DOM reference of the native disclosure button. Additional HTML attributes, including class and id, also target this button.
121
+ * @type HTMLButtonElement
122
+ * @bindable
123
+ */
124
+ ref?: HTMLButtonElement | null;
125
+ /** The DOM reference of the wrapper element, the list item that holds the button and the nested list.
126
+ * @type HTMLLIElement | HTMLDivElement
127
+ * @bindable
128
+ */
129
+ wrapperRef?: HTMLLIElement | HTMLDivElement | null;
130
+ /** The attributes to spread on the wrapper element.
131
+ * @type HTMLAttributes
132
+ */
133
+ wrapperAttributes?: HTMLAttributes<HTMLLIElement | HTMLDivElement>;
134
+ /** Nested ListView content, kept mounted while collapsed to preserve its selection. */
135
+ children?: Snippet;
136
+ } & Omit<HTMLButtonAttributes, 'children' | 'role' | 'type' | 'value' | 'aria-expanded' | 'aria-controls'>;
86
137
  export type ListViewContextConfig = {
87
138
  shape: Shapes;
139
+ as: 'ul' | 'ol' | 'div';
140
+ selectionMode: SelectionMode;
141
+ role: AriaRole | undefined;
142
+ disableTabspot: boolean;
143
+ /** Whether the list keeps a single tab stop that the arrow keys move. */
144
+ roving: boolean;
88
145
  };
89
146
  export type ListViewContextState = {
90
147
  selectedItems: string[];
@@ -92,8 +149,14 @@ export type ListViewContextState = {
92
149
  };
93
150
  export type ListViewContextMethods = {
94
151
  handleSelection: (e: MouseEvent, value: string, toggle?: boolean) => void;
95
- getChildrenRole: (tag: 'li' | 'a' | 'div') => 'listitem' | 'option' | 'row' | undefined;
96
- registerItem: (id: string, value: string, disabled: boolean) => void;
152
+ getChildrenRole: (tag: 'li' | 'a' | 'button' | 'div') => 'listitem' | 'option' | 'row' | undefined;
153
+ registerItem: (id: string, getItem: () => {
154
+ value: string;
155
+ disabled: boolean;
156
+ element: HTMLElement | null;
157
+ }) => void;
97
158
  unregisterItem: (id: string) => void;
159
+ /** True for the first item to ask: it holds the tab stop until Tabspot takes the stop over on mount. */
160
+ claimTabStop: (id: string) => boolean;
98
161
  };
99
162
  export type ListViewContext = FSContext<ListViewContextConfig, ListViewContextState, null, ListViewContextMethods>;
@@ -1,6 +1,16 @@
1
1
  import type { ListViewContext, NavigationMode, SelectionMode } from './types.ts';
2
+ import type { Attachment } from 'svelte/attachments';
3
+ import { type TabspotNodeOptions, type TabspotEventListener, type TabspotInstance } from 'tabspot';
2
4
  export declare const COMPONENT_NAME = "fslist-view-";
3
5
  export declare const TAG: string[];
4
6
  export declare const ITEM_TAG: string[];
7
+ export declare const LIST_VIEW_SKIP = "[disabled], [aria-disabled=\"true\"], [hidden], [hidden] *, [inert], [inert] *, [aria-hidden=\"true\"], [aria-hidden=\"true\"] *";
8
+ export declare const getListViewItemContext: () => {
9
+ readonly as: "li" | "a" | "button" | "div";
10
+ } | null, setListViewItemContext: (context: {
11
+ readonly as: "li" | "a" | "button" | "div";
12
+ }) => void;
13
+ /** Register roots mounted after the provider starts, and release managed focus on updates/unmount. */
14
+ export declare function attachTabspot(config: TabspotNodeOptions | undefined, onNavigate?: TabspotEventListener, instance?: TabspotInstance): Attachment<HTMLElement>;
5
15
  export declare function getRole(tag: 'ul' | 'ol' | 'div', selectionMode: SelectionMode, navigationMode: NavigationMode): 'grid' | 'listbox' | 'list' | undefined;
6
16
  export declare const getListViewContext: () => ListViewContext | null, setListViewContext: (context: ListViewContext) => void;
@@ -1,8 +1,24 @@
1
1
  import { PREFIX } from '../../internal/constants.js';
2
2
  import { createFSContext } from '../../internal/index.js';
3
+ import { setTabspotAttributes } from 'tabspot';
3
4
  export const COMPONENT_NAME = `${PREFIX}list-view-`;
4
5
  export const TAG = ['ul', 'ol', 'div'];
5
- export const ITEM_TAG = ['li', 'a', 'div'];
6
+ export const ITEM_TAG = ['li', 'a', 'button', 'div'];
7
+ export const LIST_VIEW_SKIP = '[disabled], [aria-disabled="true"], [hidden], [hidden] *, [inert], [inert] *, [aria-hidden="true"], [aria-hidden="true"] *';
8
+ export const [getListViewItemContext, setListViewItemContext] = createFSContext();
9
+ /** Register roots mounted after the provider starts, and release managed focus on updates/unmount. */
10
+ export function attachTabspot(config, onNavigate, instance) {
11
+ return (element) => {
12
+ if (!config)
13
+ return;
14
+ const result = setTabspotAttributes({ element, config });
15
+ const detach = onNavigate && (result.ok ? (result.instance ?? instance) : undefined)?.subscribe(onNavigate);
16
+ return () => {
17
+ detach?.();
18
+ setTabspotAttributes({ element, config: {} });
19
+ };
20
+ };
21
+ }
6
22
  export function getRole(tag, selectionMode, navigationMode) {
7
23
  if (selectionMode !== 'none') {
8
24
  return navigationMode === 'composite' ? 'grid' : 'listbox';