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,10 +1,11 @@
1
1
  <script lang="ts">
2
+ import { claimLabel } from '../label/label-context.js';
2
3
  import { tick } from 'svelte';
3
- import { SvelteMap } from 'svelte/reactivity';
4
+ import { SvelteMap, SvelteSet } from 'svelte/reactivity';
4
5
  import { onClickOutside } from 'runed';
5
6
  import ChevronDownRegular from 'fluentui-icons-svelte/ChevronDownRegular.svelte';
6
7
  import { PREFIX } from '../../internal/constants.js';
7
- import { RenderSoC } from '../../internal/index.js';
8
+ import { dispatchFieldEvents, invokeHandlers, NativeField, RenderSoC } from '../../internal/index.js';
8
9
  import { setTagPickerContext } from './tag-picker.js';
9
10
  import { Flyout, ListView, ListViewItem, Tag, TagGroup } from '../../index.js';
10
11
  import {
@@ -16,7 +17,7 @@
16
17
  } from 'tabspot';
17
18
  import { getGlobalFSContext } from '../../providers/fluentui-svelte/fluentui-svelte.js';
18
19
  import type { TagSize } from '../tag/types.js';
19
- import type { OptionType, TagPickerContext, TagPickerProps, TagPickerSizes } from './types.js';
20
+ import type { OptionLabel, OptionType, TagPickerContext, TagPickerProps, TagPickerSizes } from './types.js';
20
21
 
21
22
  const FALLBACK_ID = $props.id();
22
23
  const ID = `${PREFIX}-tagpicker-${FALLBACK_ID}`;
@@ -25,9 +26,16 @@
25
26
  /** The tag is one step smaller than the control around it. */
26
27
  const TAG_SIZES: Record<TagPickerSizes, TagSize> = { medium: 'extra-small', large: 'small', 'extra-large': 'medium' };
27
28
 
29
+ const labelContext = claimLabel(() => id);
30
+
28
31
  let {
29
- ref = $bindable(),
30
- inputRef = $bindable(),
32
+ wrapperRef = $bindable(null),
33
+ id = labelContext?.fieldId,
34
+ name = id,
35
+ class: classes,
36
+ ref = $bindable(null),
37
+ inputRef = $bindable(null),
38
+ inputAttributes,
31
39
  selectedOptions = $bindable([]),
32
40
  value = $bindable(''),
33
41
  open = $bindable(false),
@@ -40,15 +48,28 @@
40
48
  secondaryAction,
41
49
  tag,
42
50
  tagGroupProps,
43
- inputProps,
51
+ wrapperAttributes,
52
+ onfocus,
53
+ onblur,
44
54
  onSelectionChange,
45
55
  querySubmitted,
46
- class: classes,
47
56
  children,
48
57
  ...attributes
49
58
  }: TagPickerProps = $props();
50
59
 
51
- const INPUT_ID = $derived(inputProps?.id ?? `${ID}-input`);
60
+ const INPUT_ID = $derived(inputAttributes?.id ?? `${ID}-input`);
61
+ const INPUT_NAME = $derived(inputAttributes?.name ?? INPUT_ID);
62
+
63
+ const native = new NativeField(
64
+ () => ref,
65
+ () => inputRef,
66
+ labelContext
67
+ );
68
+
69
+ const aria = $derived(native.aria(attributes, inputAttributes));
70
+
71
+ /** A `fieldset` disables the picker without its `disabled` prop, and only the native field knows. */
72
+ const blocked = () => _picker.disabled || !!ref?.matches(':disabled');
52
73
 
53
74
  /**
54
75
  * The props the invariants rule over. The markup and the context read them through here, so
@@ -66,10 +87,11 @@
66
87
  /** The options on screen, by id. */
67
88
  const options = new SvelteMap<string, OptionType>();
68
89
 
69
- /** What the tag of each value says. It outlives its option, which leaves the screen when filtered or chosen. */
70
- const labels = new SvelteMap<string, string>();
90
+ /** What the tag of each value says. The list stays in the page while closed, so every option is here, read live. */
91
+ const optionLabels = new SvelteSet<OptionLabel>();
92
+ const labels = $derived(new Map([...optionLabels].map((option) => [option.value, option.text])));
71
93
 
72
- let listViewRef = $state<HTMLUListElement>();
94
+ let listViewRef = $state<HTMLUListElement | null>(null);
73
95
 
74
96
  let roundClass = $state('bottom');
75
97
 
@@ -106,8 +128,9 @@
106
128
  },
107
129
  events: null,
108
130
  methods: {
109
- setLabel: (v, text) => {
110
- labels.set(v, text);
131
+ registerLabel: (option) => {
132
+ optionLabels.add(option);
133
+ return () => optionLabels.delete(option);
111
134
  },
112
135
  setOption: (option) => {
113
136
  options.set(option.id, option);
@@ -118,11 +141,12 @@
118
141
  chooseOption: async (e, id) => {
119
142
  const option = options.get(id);
120
143
 
121
- if (!option || option.disabled || selectedOptions.includes(option.value)) return;
144
+ if (!option || option.disabled || blocked() || selectedOptions.includes(option.value)) return;
122
145
 
123
146
  selectedOptions = _picker.single ? [option.value] : [...selectedOptions, option.value];
124
147
  value = '';
125
148
  activeOption = null;
149
+ dispatchFieldEvents(ref);
126
150
  onSelectionChange?.(e, selectedOptions);
127
151
 
128
152
  if (_picker.single) open = false;
@@ -218,6 +242,7 @@
218
242
  if (_picker.disabled || value !== '' || selectedOptions.length === 0) return;
219
243
 
220
244
  selectedOptions = selectedOptions.slice(0, -1);
245
+ dispatchFieldEvents(ref);
221
246
  onSelectionChange?.(e, selectedOptions);
222
247
  break;
223
248
  }
@@ -236,13 +261,14 @@
236
261
 
237
262
  function handleDismiss(e: MouseEvent | KeyboardEvent, data: { value: string }) {
238
263
  selectedOptions = selectedOptions.filter((v) => v !== data.value);
264
+ dispatchFieldEvents(ref);
239
265
  onSelectionChange?.(e, selectedOptions);
240
266
  inputRef?.focus();
241
267
  }
242
268
 
243
269
  function handleControlClick(e: MouseEvent) {
244
270
  // Tags and the aside have a job of their own.
245
- if (_picker.disabled || (e.target as Element).closest('.fs-tag, .fs-tagpicker-aside')) return;
271
+ if (blocked() || (e.target as Element).closest('.fs-tag, .fs-tagpicker-aside')) return;
246
272
 
247
273
  inputRef?.focus();
248
274
 
@@ -250,7 +276,7 @@
250
276
  }
251
277
 
252
278
  function handleChevronClick() {
253
- if (_picker.disabled) return;
279
+ if (blocked()) return;
254
280
 
255
281
  inputRef?.focus();
256
282
 
@@ -272,7 +298,7 @@
272
298
  }
273
299
 
274
300
  onClickOutside(
275
- () => ref,
301
+ () => wrapperRef,
276
302
  () => (open = false)
277
303
  );
278
304
 
@@ -286,16 +312,24 @@
286
312
  $effect(() => {
287
313
  if (!_picker.open || !listViewRef) return;
288
314
 
289
- setTabspotAttributes({ element: listViewRef, config: NAVIGATION });
315
+ const list = listViewRef;
290
316
 
291
- return globalContext?.state.tabspotInstance?.subscribe(listViewRef, navigate);
317
+ setTabspotAttributes({ element: list, config: NAVIGATION });
318
+
319
+ const unsubscribe = globalContext?.state.tabspotInstance?.subscribe(list, navigate);
320
+
321
+ // The list stays in the page once closed: nothing in it reads as chosen.
322
+ return () => {
323
+ clearTabspotActive(list);
324
+ unsubscribe?.();
325
+ };
292
326
  });
293
327
 
294
328
  // Tabspot does not notice an option that left the list, so it learns the options that are left.
295
329
  $effect(() => {
296
330
  void [...options.keys()];
297
331
 
298
- if (listViewRef) globalContext?.state.tabspotInstance?.rebuild(listViewRef);
332
+ if (_picker.open && listViewRef) globalContext?.state.tabspotInstance?.rebuild(listViewRef);
299
333
  });
300
334
  </script>
301
335
 
@@ -311,55 +345,91 @@ A text field and a dropdown that give people a way to choose several options fro
311
345
  let selectedOptions = $state([]);
312
346
  </script>
313
347
 
314
- <TagPicker bind:selectedOptions inputProps={{ 'aria-label': 'Fruits' }}>
348
+ <TagPicker bind:selectedOptions name="fruits" inputAttributes={{ 'aria-label': 'Fruits' }}>
315
349
  <TagPickerOption value="Apple">Apple</TagPickerOption>
316
350
  <TagPickerOption value="Banana">Banana</TagPickerOption>
317
351
  </TagPicker>
318
352
  ```
319
353
  -->
320
354
  <div
321
- class={['fs-tagpicker', `size-${size}`, { disabled: _picker.disabled, open: _picker.open }, classes]}
322
- bind:this={ref}
323
- {...attributes}
355
+ {...wrapperAttributes}
356
+ class={['fs-tagpicker', `size-${size}`, { disabled: _picker.disabled, open: _picker.open }, wrapperAttributes?.class]}
357
+ bind:this={wrapperRef}
324
358
  >
359
+ {#if _picker.hasList}
360
+ <!-- Hidden while closed, not gone: each option still tells its tag what to say, and comes before
361
+ the tags, so the server writes them too. It shows once it has the wrapper to be placed against. -->
362
+ <Flyout
363
+ floating
364
+ hidden={!_picker.open || !wrapperRef}
365
+ offset={0}
366
+ reference={wrapperRef ?? undefined}
367
+ roundCorners="bottom"
368
+ placementConfig={{ allowedPlacements: ['bottom', 'top'] }}
369
+ onPlacementChange={(place) => handlePlacementChange(place)}
370
+ class={['fs-tagpicker-flyout', roundClass === 'top' && 'top-shadow']}
371
+ >
372
+ <ListView
373
+ role="listbox"
374
+ selectionMode={_picker.single ? 'single' : 'multiselect'}
375
+ navigationMode="items"
376
+ disableTabspot
377
+ id={LIST_ID}
378
+ aria-multiselectable={_picker.single ? undefined : 'true'}
379
+ aria-label={aria['aria-label']}
380
+ aria-labelledby={aria['aria-labelledby']}
381
+ bind:ref={listViewRef}
382
+ onmousedown={(e: MouseEvent) => e.preventDefault()}
383
+ >
384
+ {#if options.size === 0}
385
+ <ListViewItem role="option" disabled>{notFoundText}</ListViewItem>
386
+ {/if}
387
+ {@render children?.()}
388
+ </ListView>
389
+ </Flyout>
390
+ {/if}
325
391
  <!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
326
392
  <div class={['control', _picker.open && `square-${roundClass}`]} onclick={handleControlClick} onmousedown={keepFocus}>
327
393
  <div class="content">
328
394
  {#if selectedOptions.length > 0}
329
395
  <TagGroup
330
- role="list"
396
+ aria-label="Selected options"
397
+ {...tagGroupProps}
331
398
  size={tagSize}
332
399
  disabled={_picker.disabled}
333
400
  dismissible
334
401
  onDismiss={handleDismiss}
335
- {...tagGroupProps}
336
402
  >
337
403
  {#each selectedOptions as v (v)}
338
404
  {@const text = labels.get(v) ?? v}
339
405
  {#if tag}
340
406
  {@render tag({ value: v, text })}
341
407
  {:else}
342
- <Tag role="listitem" value={v} title={text}>{text}</Tag>
408
+ <Tag value={v} title={text}>{text}</Tag>
343
409
  {/if}
344
410
  {/each}
345
411
  </TagGroup>
346
412
  {/if}
347
413
  <input
414
+ {...inputAttributes}
415
+ {...aria}
416
+ autocomplete="off"
348
417
  bind:this={inputRef}
349
418
  type="text"
350
- autocomplete="off"
351
419
  {placeholder}
352
420
  id={INPUT_ID}
421
+ name={INPUT_NAME}
353
422
  disabled={_picker.disabled}
354
423
  role={_picker.hasList ? 'combobox' : undefined}
355
424
  aria-expanded={_picker.hasList ? _picker.open : undefined}
356
425
  aria-haspopup={_picker.hasList ? 'listbox' : undefined}
357
426
  aria-autocomplete={_picker.hasList ? 'list' : undefined}
358
427
  aria-controls={_picker.open ? LIST_ID : undefined}
359
- {...inputProps}
360
428
  bind:value
361
- oninput={handleInput}
362
- onkeydown={handleKeyDown}
429
+ oninput={(e) => invokeHandlers(e, [disabled], [handleInput, inputAttributes?.oninput])}
430
+ onkeydown={(e) => invokeHandlers(e, [disabled], [handleKeyDown, inputAttributes?.onkeydown])}
431
+ onfocus={(e) => invokeHandlers(e, [], [onfocus, inputAttributes?.onfocus])}
432
+ onblur={(e) => invokeHandlers(e, [], [onblur, inputAttributes?.onblur])}
363
433
  />
364
434
  </div>
365
435
  {#if secondaryAction || (expandIcon && _picker.hasList)}
@@ -375,39 +445,26 @@ A text field and a dropdown that give people a way to choose several options fro
375
445
  </span>
376
446
  {/if}
377
447
  </div>
378
- {#if _picker.open && ref}
379
- <Flyout
380
- floating
381
- offset={0}
382
- reference={ref}
383
- roundCorners="bottom"
384
- placementConfig={{ allowedPlacements: ['bottom', 'top'] }}
385
- onPlacementChange={(place) => handlePlacementChange(place)}
386
- class={['fs-tagpicker-flyout', roundClass === 'top' && 'top-shadow']}
387
- >
388
- <ListView
389
- role="listbox"
390
- navigationMode="items"
391
- disableTabspot
392
- id={LIST_ID}
393
- aria-multiselectable={_picker.single ? undefined : 'true'}
394
- aria-label={inputProps?.['aria-label']}
395
- aria-labelledby={inputProps?.['aria-labelledby']}
396
- bind:ref={listViewRef}
397
- onmousedown={(e: MouseEvent) => e.preventDefault()}
398
- >
399
- {#if options.size === 0}
400
- <ListViewItem role="option" disabled>{notFoundText}</ListViewItem>
401
- {/if}
402
- {@render children?.()}
403
- </ListView>
404
- </Flyout>
405
- {:else if children}
406
- <!-- Nobody sees the options, but each one still tells its tag what to say. -->
407
- <ListView hidden>
408
- {@render children()}
409
- </ListView>
410
- {/if}
448
+ <!-- The field of the form: it holds the chosen options, never the typed text, so it is
449
+ submitted, validated and reset like any multiple select, behind the control and out of
450
+ the tab order. See `NativeField`. -->
451
+ <select
452
+ {...attributes}
453
+ {id}
454
+ {name}
455
+ class={['native-field', classes]}
456
+ aria-hidden={native.reported ? undefined : 'true'}
457
+ multiple
458
+ tabindex={-1}
459
+ disabled={_picker.disabled}
460
+ bind:value={selectedOptions}
461
+ bind:this={ref}
462
+ onclick={() => inputRef?.focus()}
463
+ >
464
+ {#each selectedOptions as option (option)}
465
+ <option value={option}>{labels.get(option) ?? option}</option>
466
+ {/each}
467
+ </select>
411
468
  </div>
412
469
 
413
470
  <style>
@@ -522,6 +579,21 @@ A text field and a dropdown that give people a way to choose several options fro
522
579
  color: var(--fs-text-tertiary);
523
580
  }
524
581
  }
582
+ .fs-tagpicker > .native-field {
583
+ position: absolute;
584
+ inset: 0;
585
+ inline-size: 100%;
586
+ block-size: 100%;
587
+ margin: 0;
588
+ box-sizing: border-box;
589
+ opacity: 0;
590
+ pointer-events: none;
591
+ }
592
+ .fs-tagpicker:has(> .native-field:is(:user-invalid, [aria-invalid='true'])) .control::after {
593
+ inline-size: calc(100% + 0.125rem);
594
+ inset-inline-start: -0.063rem;
595
+ background-color: var(--fs-system-critical);
596
+ }
525
597
 
526
598
  .content {
527
599
  display: flex;
@@ -605,7 +677,7 @@ A text field and a dropdown that give people a way to choose several options fro
605
677
  }
606
678
  }
607
679
 
608
- .disabled .control {
680
+ .fs-tagpicker:is(.disabled, :has(> .native-field:disabled)) .control {
609
681
  cursor: not-allowed;
610
682
  background: var(--fs-control-fill-disabled);
611
683
  &::before {
@@ -10,12 +10,12 @@ import type { TagPickerProps } from './types.ts';
10
10
  * let selectedOptions = $state([]);
11
11
  * </script>
12
12
  *
13
- * <TagPicker bind:selectedOptions inputProps={{ 'aria-label': 'Fruits' }}>
13
+ * <TagPicker bind:selectedOptions name="fruits" inputAttributes={{ 'aria-label': 'Fruits' }}>
14
14
  * <TagPickerOption value="Apple">Apple</TagPickerOption>
15
15
  * <TagPickerOption value="Banana">Banana</TagPickerOption>
16
16
  * </TagPicker>
17
17
  * ```
18
18
  */
19
- declare const TagPicker: import("svelte").Component<TagPickerProps, {}, "ref" | "value" | "open" | "inputRef" | "selectedOptions">;
19
+ declare const TagPicker: import("svelte").Component<TagPickerProps, {}, "ref" | "value" | "open" | "wrapperRef" | "selectedOptions" | "inputRef">;
20
20
  type TagPicker = ReturnType<typeof TagPicker>;
21
21
  export default TagPicker;
@@ -1,6 +1,6 @@
1
1
  import type { Component, Snippet } from 'svelte';
2
- import type { HTMLAttributes, HTMLInputAttributes, SVGAttributes } from 'svelte/elements';
3
- import type { FSContext } from '../../internal/index.js';
2
+ import type { HTMLAttributes, HTMLInputAttributes, HTMLSelectAttributes, SVGAttributes } from 'svelte/elements';
3
+ import type { FSContext, NativeFieldAttributes } from '../../internal/index.js';
4
4
  import type { ListViewItemProps } from '../list-view/types.ts';
5
5
  import type { TagGroupProps } from '../tag/types.ts';
6
6
  export type TagPickerSizes = 'medium' | 'large' | 'extra-large';
@@ -9,11 +9,19 @@ export type TagPickerProps = {
9
9
  /** The DOM reference of the wrapper element.
10
10
  * @bindable
11
11
  */
12
- ref?: HTMLDivElement;
13
- /** The DOM reference of the input element.
12
+ wrapperRef?: HTMLDivElement | null;
13
+ /** The DOM reference of the native multiple select that holds the selected options. The other attributes of the picker are applied to it.
14
14
  * @bindable
15
15
  */
16
- inputRef?: HTMLInputElement;
16
+ ref?: HTMLSelectElement | null;
17
+ /** The DOM reference of the text input.
18
+ * @bindable
19
+ */
20
+ inputRef?: HTMLInputElement | null;
21
+ /** The attributes to spread on the text input.
22
+ * @type HTMLInputAttributes
23
+ */
24
+ inputAttributes?: Omit<HTMLInputAttributes, 'value' | 'type' | 'role' | 'size' | 'disabled' | 'placeholder' | 'children'>;
17
25
  /** The values of the chosen options, shown as tags in the control. A value no option has is shown as it is.
18
26
  * @default []
19
27
  * @bindable
@@ -51,22 +59,21 @@ export type TagPickerProps = {
51
59
  /** A button-like element rendered at the end of the control, before the dropdown arrow. */
52
60
  secondaryAction?: Snippet;
53
61
  /** Renders the tag of a chosen option, to give it media or custom text. Falls back to a plain tag.
54
- * Render a `Tag` with the given `value`, and `role="listitem"`.
62
+ * Render a `Tag` with the given `value`.
55
63
  * @type Snippet<[{ value: string; text: string }]>
56
64
  */
57
65
  tag?: Snippet<[{
58
66
  value: string;
59
67
  text: string;
60
68
  }]>;
61
- /** The props to spread on the group holding the tags. This is where its `aria-label` goes.
69
+ /** The props to spread on the group holding the tags. This is where its `aria-label` goes, `'Selected options'` by default.
62
70
  * @type TagGroupProps
63
71
  */
64
72
  tagGroupProps?: Omit<TagGroupProps, 'children' | 'size' | 'appearance' | 'disabled' | 'dismissible' | 'selectedValues' | 'onTagSelect' | 'onDismiss'>;
65
- /** The props to spread on the input. This is where its accessible name (`aria-label`) and
66
- * `readonly` (to turn the input into a plain trigger) go.
67
- * @type HTMLInputAttributes
73
+ /** The attributes to spread on the wrapper element.
74
+ * @type HTMLAttributes
68
75
  */
69
- inputProps?: Omit<HTMLInputAttributes, 'value' | 'type' | 'role' | 'disabled' | 'oninput' | 'onkeydown'>;
76
+ wrapperAttributes?: HTMLAttributes<HTMLDivElement>;
70
77
  /** Called when an option is chosen, or a tag is removed. */
71
78
  onSelectionChange?: (e: Event, selectedOptions: string[]) => void;
72
79
  /** Called when Enter is pressed with text in the input and no option under the cursor. This is
@@ -75,19 +82,24 @@ export type TagPickerProps = {
75
82
  querySubmitted?: (e: KeyboardEvent, query: string) => void;
76
83
  /** The options. Leave it out to get a picker without a list. */
77
84
  children?: Snippet;
78
- } & Omit<HTMLAttributes<HTMLDivElement>, 'children'>;
85
+ } & NativeFieldAttributes<Omit<HTMLSelectAttributes, 'value' | 'multiple' | 'size' | 'disabled' | 'children'>>;
79
86
  export type OptionType = {
80
87
  id: string;
81
88
  value: string;
82
89
  disabled?: boolean;
83
90
  };
91
+ /** What an option tells its tag, read live. */
92
+ export type OptionLabel = {
93
+ readonly value: string;
94
+ readonly text: string;
95
+ };
84
96
  export type TagPickerContext = FSContext<null, {
85
97
  readonly query: string;
86
98
  readonly selectedOptions: string[];
87
99
  readonly open: boolean;
88
100
  }, null, {
89
- /** Remembers what the tag of `value` says. Every option calls it, on screen or not. */
90
- setLabel: (value: string, text: string) => void;
101
+ /** Tells the tag of an option what to say. Every option calls it, on screen or not. Returns the call that withdraws the option. */
102
+ registerLabel: (option: OptionLabel) => () => void;
91
103
  setOption: (option: OptionType) => void;
92
104
  deleteOption: (id: string) => void;
93
105
  chooseOption: (e: Event, id: string) => void;
@@ -105,7 +117,7 @@ export type TagPickerOptionProps = {
105
117
  /** The DOM reference of the option element.
106
118
  * @bindable
107
119
  */
108
- ref?: HTMLLIElement;
120
+ ref?: HTMLLIElement | null;
109
121
  /** Takes the option out of reach: it is shown, but the cursor steps over it. */
110
122
  disabled?: boolean;
111
123
  } & Omit<ListViewItemProps<'li'>, 'as' | 'value' | 'onAction' | 'checkmark'>;
@@ -116,7 +128,15 @@ export type TagPickerOptionGroupProps = {
116
128
  /** The DOM reference of the group element.
117
129
  * @bindable
118
130
  */
119
- ref?: HTMLUListElement;
131
+ ref?: HTMLUListElement | null;
120
132
  /** The options of the group. */
121
133
  children?: Snippet;
134
+ /** The attributes to spread on the wrapper element.
135
+ * @type HTMLAttributes
136
+ */
137
+ wrapperAttributes?: HTMLAttributes<HTMLLIElement>;
138
+ /** The DOM reference of the wrapper element.
139
+ * @bindable
140
+ */
141
+ wrapperRef?: HTMLLIElement | null;
122
142
  } & Omit<HTMLAttributes<HTMLUListElement>, 'children'>;
@@ -29,6 +29,31 @@ The `type` prop specifies the type of input element. It can be set to various ty
29
29
  <TextBox type="number" placeholder="Number" />
30
30
  ```
31
31
 
32
+ ### Size
33
+
34
+ The `size` prop sets the height, the font size and the horizontal padding of the input. It can be set to `small`, `medium` or `large`. The default is `medium`.
35
+
36
+ ```svelte
37
+ <TextBox size="small" placeholder="Small" />
38
+ <TextBox size="medium" placeholder="Medium" />
39
+ <TextBox size="large" placeholder="Large" />
40
+ ```
41
+
42
+ ### Content Before and After
43
+
44
+ `contentBefore` and `contentAfter` render content inside the wrapper, before and after the input. They accept a string, a snippet or a component such as an icon.
45
+
46
+ ```svelte
47
+ <script>
48
+ import { MailRegular } from 'fluentui-icons-svelte';
49
+ </script>
50
+
51
+ <TextBox contentBefore="https://" placeholder="Website" />
52
+ <TextBox contentAfter=".com" placeholder="Domain" />
53
+ <TextBox contentBefore="$" contentAfter=".00" type="number" placeholder="Price" />
54
+ <TextBox contentBefore={MailRegular} placeholder="Email" />
55
+ ```
56
+
32
57
  ### Input Element
33
58
 
34
59
  The `ref` prop allows you to bind a reference to the underlying HTML input element (`bind:ref`). This can be useful for direct DOM manipulations or accessing native input properties. The wrapper element can be bound via `wrapperRef`.
@@ -65,6 +90,8 @@ The `textChanged` event is triggered whenever the content of the text box change
65
90
 
66
91
  The `onClear` event is triggered when the input is cleared, allowing you to handle any necessary actions when the user removes the input value.
67
92
 
93
+ HTML attributes, including class, id and ARIA attributes, apply to the control element. Bind ref to access that control. Use wrapperAttributes and wrapperRef for the outer container.
94
+
68
95
  ## Component Props
69
96
 
70
97
  <!-- DO NOT EDIT THIS SECTION (propsmith generated) -->
@@ -74,19 +101,19 @@ The `onClear` event is triggered when the input is cleared, allowing you to hand
74
101
  | ------------------------------------ | ---------------------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
75
102
  | `type` | [`TextBoxTypes`](https://fluentui-svelte.dev/docs/types/#textboxtypes) | `'text'` | The type of the input element. |
76
103
  | `value` _bindable_ | `string` &#124; `number` | | The current value of the input. |
77
- | `wrapperRef` _bindable_ | `HTMLDivElement` | | The DOM reference of the wrapper element. |
78
- | `ref` _bindable_ | `HTMLInputElement` | | The DOM reference of the input element. |
104
+ | `wrapperRef` _bindable_ | `HTMLDivElement` &#124; `null` | | The DOM reference of the wrapper element. |
105
+ | `ref` _bindable_ | `HTMLInputElement` &#124; `null` | | The DOM reference of the input element. |
79
106
  | `placeholder` | `string` | `'Fluent TextBox'` | The placeholder text for the input. |
80
107
  | `hideActionButtons` | `boolean` | | Whether to hide the extra action buttons in the input. |
81
108
  | `justify` | `boolean` | | Make the input fill the full width of the container. |
82
109
  | `contentBefore` | `string` &#124; `Snippet` &#124; `Component` | | Content rendered before the input, inside the wrapper. |
83
110
  | `contentAfter` | `string` &#124; `Snippet` &#124; `Component` | | Content rendered after the input, inside the wrapper. |
84
111
  | `size` | `'small'` &#124; `'medium'` &#124; `'large'` | `'medium'` | The size of the input element. |
85
- | `textChanged` | `(e: InputEvent, text: string) => void` | | Occurs when content changes in the text box. |
112
+ | `textChanged` | `(e: Event & unknown, text: string) => void` | | Occurs when content changes in the text box. |
86
113
  | `querySubmitted` | `(e: MouseEvent` &#124; `KeyboardEvent, query: string) => void` | | The QuerySubmitted event occurs when: 1. The input type is 'search'. 2. While the focus is in the text box, press Enter or click the query icon. |
87
114
  | `onClear` | `(e: MouseEvent) => void` | | Occurs when the clear button is clicked. |
88
115
  | `children` | `Snippet` | | The children elements to render inside the input. |
89
- | `wrapperAttributes` | `HTMLAttributes` | | The attributes to apply to the wrapper element. |
116
+ | `wrapperAttributes` | `HTMLAttributes` | | The attributes to spread on the wrapper element. |
90
117
  | `HTMLInputAttributes` without `size` | | | |
91
118
 
92
119
  <!-- /props:TextBoxProps -->
@@ -3,14 +3,14 @@
3
3
  import type { HTMLButtonAttributes } from 'svelte/elements';
4
4
 
5
5
  let {
6
- element = $bindable(),
6
+ element = $bindable(null),
7
7
  type = 'button',
8
8
  children,
9
9
  ...attributes
10
- }: { element?: HTMLButtonElement; children: Snippet } & HTMLButtonAttributes = $props();
10
+ }: { element?: HTMLButtonElement | null; children: Snippet } & HTMLButtonAttributes = $props();
11
11
  </script>
12
12
 
13
- <button {type} bind:this={element} {...attributes}>
13
+ <button {...attributes} {type} bind:this={element}>
14
14
  {@render children?.()}
15
15
  </button>
16
16
 
@@ -1,7 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLButtonAttributes } from 'svelte/elements';
3
3
  type $$ComponentProps = {
4
- element?: HTMLButtonElement;
4
+ element?: HTMLButtonElement | null;
5
5
  children: Snippet;
6
6
  } & HTMLButtonAttributes;
7
7
  declare const TextBoxButton: import("svelte").Component<$$ComponentProps, {}, "element">;