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,33 +1,37 @@
1
1
  <script lang="ts">
2
+ import { claimLabel } from '../label/label-context.js';
2
3
  import DismissFilled from 'fluentui-icons-svelte/DismissFilled.svelte';
3
4
  import SearchFilled from 'fluentui-icons-svelte/SearchFilled.svelte';
4
5
  import EyeShowFilled from 'fluentui-icons-svelte/EyeShowFilled.svelte';
5
6
  import EyeHideFilled from 'fluentui-icons-svelte/EyeHideFilled.svelte';
6
7
  import { TextBoxButton } from '../../index.js';
7
- import { RenderSoC } from '../../internal/index.js';
8
+ import { invokeHandlers, RenderSoC } from '../../internal/index.js';
8
9
  import type { TextBoxProps } from './types.js';
9
10
 
10
11
  const FALLBACK_ID = $props.id();
11
12
 
13
+ const labelContext = claimLabel(() => id);
14
+
12
15
  let {
13
- id = 'input-' + FALLBACK_ID,
16
+ id = labelContext?.fieldId ?? 'input-' + FALLBACK_ID,
14
17
  readonly,
15
18
  name = id,
16
19
  hideActionButtons,
17
20
  type = 'text',
18
- class: classes,
19
21
  wrapperAttributes,
20
- wrapperRef = $bindable(),
21
- value = $bindable(),
22
- ref = $bindable(),
22
+ wrapperRef = $bindable(null),
23
+ value = $bindable(null),
24
+ ref = $bindable(null),
23
25
  placeholder = 'Fluent TextBox',
24
26
  size = 'medium',
25
27
  contentBefore,
26
28
  contentAfter,
27
29
  querySubmitted,
28
30
  textChanged,
29
- onkeydown,
31
+ onkeyup,
30
32
  onClear,
33
+ onchange,
34
+ oninput,
31
35
  children,
32
36
  ...attributes
33
37
  }: TextBoxProps = $props();
@@ -57,12 +61,11 @@
57
61
  }
58
62
  };
59
63
 
60
- const handleKeyDown = (e: KeyboardEvent & { currentTarget: EventTarget & HTMLInputElement }) => {
64
+ const handleKeyUp = (e: KeyboardEvent & { currentTarget: EventTarget & HTMLInputElement }) => {
61
65
  if (e.key === 'Escape') {
62
66
  e.preventDefault();
63
67
  (e.target as HTMLInputElement).blur();
64
68
  }
65
- onkeydown?.(e);
66
69
  };
67
70
  </script>
68
71
 
@@ -80,7 +83,11 @@ This is a implementation of Fluent UI TextBox component. The TextBox component i
80
83
  <TextBox type="text" placeholder="Enter your name" />
81
84
  ```
82
85
  -->
83
- <div class={['fs-textbox', id, `text-box-size-${size}`, classes]} {...wrapperAttributes} bind:this={wrapperRef}>
86
+ <div
87
+ {...wrapperAttributes}
88
+ class={['fs-textbox', `text-box-size-${size}`, wrapperAttributes?.class]}
89
+ bind:this={wrapperRef}
90
+ >
84
91
  {#if contentBefore}
85
92
  <span class="content-before">
86
93
  {#if typeof contentBefore === 'string'}
@@ -91,6 +98,7 @@ This is a implementation of Fluent UI TextBox component. The TextBox component i
91
98
  </span>
92
99
  {/if}
93
100
  <input
101
+ {...attributes}
94
102
  {type}
95
103
  {id}
96
104
  {name}
@@ -98,10 +106,9 @@ This is a implementation of Fluent UI TextBox component. The TextBox component i
98
106
  {readonly}
99
107
  bind:value
100
108
  bind:this={ref}
101
- onkeyup={handleKeyDown}
102
- oninput={(e) => textChanged?.(e as any, value)}
103
- onchange={(e) => textChanged?.(e as any, value)}
104
- {...attributes}
109
+ onkeyup={(e) => invokeHandlers(e, [], [handleKeyUp, onkeyup])}
110
+ oninput={(e) => invokeHandlers(e, [], [(_e) => textChanged?.(_e, value), oninput])}
111
+ onchange={(e) => invokeHandlers(e, [], [(_e) => textChanged?.(_e, value), onchange])}
105
112
  />
106
113
  {#if contentAfter}
107
114
  <span class="content-after">
@@ -221,6 +228,11 @@ This is a implementation of Fluent UI TextBox component. The TextBox component i
221
228
  left: -0.063rem;
222
229
  background-color: var(--fs-accent-fill-default);
223
230
  }
231
+ &:has(input:is(:user-invalid, [aria-invalid='true'])):after {
232
+ width: calc(100% + 0.125rem);
233
+ left: -0.063rem;
234
+ background-color: var(--fs-system-critical);
235
+ }
224
236
  &:has(input:focus) {
225
237
  background: var(--fs-control-fill-input-active);
226
238
  & input::placeholder {
@@ -271,12 +283,14 @@ This is a implementation of Fluent UI TextBox component. The TextBox component i
271
283
  visibility: hidden;
272
284
  }
273
285
  &.text-box-size-small {
286
+ --text-box-edge-padding: 0.5rem;
274
287
  & :is(input, .content-after, .content-before) {
275
288
  font-size: var(--fs-body2-font-size);
276
289
  padding: 0.25rem;
277
290
  }
278
291
  }
279
292
  &.text-box-size-medium {
293
+ --text-box-edge-padding: 0.6875rem;
280
294
  font-size: var(--fs-body-font-size);
281
295
  & :is(input, .content-after, .content-before) {
282
296
  padding: 0.375rem;
@@ -284,6 +298,7 @@ This is a implementation of Fluent UI TextBox component. The TextBox component i
284
298
  }
285
299
  }
286
300
  &.text-box-size-large {
301
+ --text-box-edge-padding: 0.9375rem;
287
302
  & :is(input, .content-after, .content-before) {
288
303
  padding: 0.625rem 0.625rem;
289
304
  font-size: var(--fs-subtitle2-font-size);
@@ -295,5 +310,17 @@ This is a implementation of Fluent UI TextBox component. The TextBox component i
295
310
  margin-right: 0.3125rem;
296
311
  }
297
312
  }
313
+ /* The outer horizontal edges of the control use the per-size edge padding. */
314
+ &:is(.text-box-size-small, .text-box-size-medium, .text-box-size-large) {
315
+ & input:not(.content-before + input) {
316
+ padding-left: var(--text-box-edge-padding);
317
+ }
318
+ & :is(input, .content-after):last-child {
319
+ padding-right: var(--text-box-edge-padding);
320
+ }
321
+ & .content-before {
322
+ padding-left: var(--text-box-edge-padding);
323
+ }
324
+ }
298
325
  }
299
326
  </style>
@@ -12,15 +12,15 @@ export type TextBoxProps = {
12
12
  * @type string | number
13
13
  * @bindable
14
14
  */
15
- value?: string | number;
15
+ value?: string | number | null;
16
16
  /** The DOM reference of the wrapper element.
17
17
  * @bindable
18
18
  */
19
- wrapperRef?: HTMLDivElement;
19
+ wrapperRef?: HTMLDivElement | null;
20
20
  /** The DOM reference of the input element.
21
21
  * @bindable
22
22
  */
23
- ref?: HTMLInputElement;
23
+ ref?: HTMLInputElement | null;
24
24
  /** The placeholder text for the input.
25
25
  * @default 'Fluent TextBox'
26
26
  */
@@ -42,7 +42,7 @@ export type TextBoxProps = {
42
42
  */
43
43
  size?: Sizes;
44
44
  /** Occurs when content changes in the text box. */
45
- textChanged?: (e: InputEvent, text: string) => void;
45
+ textChanged?: (e: Event & unknown, text: string) => void;
46
46
  /** The QuerySubmitted event occurs when:
47
47
  * 1. The input type is 'search'.
48
48
  * 2. While the focus is in the text box, press Enter or click the query icon.
@@ -53,7 +53,7 @@ export type TextBoxProps = {
53
53
  onClear?: (e: MouseEvent) => void;
54
54
  /** The children elements to render inside the input. */
55
55
  children?: Snippet;
56
- /** The attributes to apply to the wrapper element.
56
+ /** The attributes to spread on the wrapper element.
57
57
  * @type HTMLAttributes
58
58
  */
59
59
  wrapperAttributes?: HTMLAttributes<HTMLDivElement>;
@@ -44,6 +44,8 @@ The `onChange` event is triggered whenever the content of the textarea changes.
44
44
  <TextArea onChange={(e) => console.log(e)} placeholder="Type..." />
45
45
  ```
46
46
 
47
+ 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.
48
+
47
49
  ## Component Props
48
50
 
49
51
  <!-- DO NOT EDIT THIS SECTION (propsmith generated) -->
@@ -52,13 +54,12 @@ The `onChange` event is triggered whenever the content of the textarea changes.
52
54
  | Name | Type | Default | Description |
53
55
  | -------------------------------- | ------------------------------------------------------------------ | ------------ | ------------------------------------------------ |
54
56
  | `value` _bindable_ | `string` | `''` | The current value of the textarea. |
55
- | `ref` _bindable_ | `HTMLElement` | | The DOM reference of the textarea element. |
57
+ | `ref` _bindable_ | `HTMLTextAreaElement` &#124; `null` | | The DOM reference of the textarea element. |
56
58
  | `placeholder` | `string` | | The placeholder text for the textarea. |
57
59
  | `resize` | `'none'` &#124; `'both'` &#124; `'horizontal'` &#124; `'vertical'` | `'vertical'` | Configures the resize behavior of the textarea. |
58
- | `onChange` | `(e: Event) => void` | | Callback function for the change event. |
59
60
  | `disabled` | `boolean` | `false` | Disables the user interaction. |
60
61
  | `wrapperAttributes` | `HTMLAttributes` | | The attributes to spread on the wrapper element. |
61
- | `wrapperRef` _bindable_ | `HTMLDivElement` | | The DOM reference of the wrapper element. |
62
+ | `wrapperRef` _bindable_ | `HTMLDivElement` &#124; `null` | | The DOM reference of the wrapper element. |
62
63
  | `SvelteHTMLElements['textarea']` | | | |
63
64
 
64
65
  <!-- /props:TextAreaProps -->
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { claimLabel } from '../label/label-context.js';
2
3
  import { PREFIX } from '../../internal/constants.js';
3
4
  import { invokeHandlers } from '../../internal/index.js';
4
5
  import type { TextAreaProps } from './types.js';
@@ -7,17 +8,19 @@
7
8
  const COMPONENT_NAME = 'textarea';
8
9
  const ID = `${PREFIX}${COMPONENT_NAME}-${FALLBACK_ID}`;
9
10
 
11
+ const labelContext = claimLabel(() => id);
12
+
10
13
  let {
11
- id = ID,
14
+ id = labelContext?.fieldId ?? ID,
12
15
  name = id,
13
16
  value = $bindable(''),
14
- ref = $bindable(),
17
+ ref = $bindable(null),
15
18
  resize = 'vertical',
16
19
  style,
17
20
  wrapperAttributes,
18
21
  onchange,
19
22
  disabled = false,
20
- wrapperRef = $bindable(),
23
+ wrapperRef = $bindable(null),
21
24
  ...attributes
22
25
  }: TextAreaProps = $props();
23
26
  </script>
@@ -36,16 +39,16 @@
36
39
  <TextArea resize="none" disabled />
37
40
  ```
38
41
  -->
39
- <div class="fs-textarea" {...wrapperAttributes} bind:this={wrapperRef}>
42
+ <div {...wrapperAttributes} class={['fs-textarea', wrapperAttributes?.class]} bind:this={wrapperRef}>
40
43
  <textarea
44
+ {...attributes}
41
45
  {name}
42
46
  {id}
43
47
  {disabled}
44
48
  onchange={(e) => invokeHandlers(e, [disabled], [onchange])}
45
49
  style="resize: {resize}; {style}"
46
50
  bind:this={ref}
47
- bind:value
48
- {...attributes}></textarea>
51
+ bind:value></textarea>
49
52
  </div>
50
53
 
51
54
  <style>
@@ -128,6 +131,11 @@
128
131
  left: -0.063rem;
129
132
  background: var(--fs-accent-fill-default);
130
133
  }
134
+ &:has(textarea:is(:user-invalid, [aria-invalid='true'])):after {
135
+ width: calc(100% + 0.125rem);
136
+ left: -0.063rem;
137
+ background: var(--fs-system-critical);
138
+ }
131
139
  &:has(textarea:focus) {
132
140
  background: var(--fs-control-fill-input-active);
133
141
  & textarea::placeholder {
@@ -9,15 +9,13 @@ export type TextAreaProps = {
9
9
  /** The DOM reference of the textarea element.
10
10
  * @bindable
11
11
  */
12
- ref?: HTMLElement;
12
+ ref?: HTMLTextAreaElement | null;
13
13
  /** The placeholder text for the textarea. */
14
14
  placeholder?: string;
15
15
  /** Configures the resize behavior of the textarea.
16
16
  * @default 'vertical'
17
17
  */
18
18
  resize?: 'none' | 'both' | 'horizontal' | 'vertical';
19
- /** Callback function for the change event. */
20
- onChange?: (e: Event) => void;
21
19
  /** Disables the user interaction.
22
20
  * @default false
23
21
  */
@@ -29,5 +27,5 @@ export type TextAreaProps = {
29
27
  /** The DOM reference of the wrapper element.
30
28
  * @bindable
31
29
  */
32
- wrapperRef?: HTMLDivElement;
30
+ wrapperRef?: HTMLDivElement | null;
33
31
  } & SvelteHTMLElements['textarea'];
@@ -33,25 +33,28 @@ Use `hideHours`, `hideMinutes`, or `hideSeconds` to hide individual columns. By
33
33
  <TimePicker format={24} hideSeconds />
34
34
  ```
35
35
 
36
+ 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.
37
+
36
38
  ## Component Props
37
39
 
38
40
  <!-- DO NOT EDIT THIS SECTION (propsmith generated) -->
39
41
  <!-- props:TimePickerProps -->
40
42
 
41
- | Name | Type | Default | Description |
42
- | ----------------------------- | --------------------- | ----------------- | --------------------------------------------------------------------------------------------------- |
43
- | `value` _bindable_ | `string` | `''` | Selected time as 24h `HH:mm` or `HH:mm:ss` (matches `<input type="time">`). |
44
- | `format` | `12` &#124; `24` | `24` | Clock format: `24` (00-23) or `12` (01-12 with an AM/PM column). |
45
- | `open` _bindable_ | `boolean` | `false` | Controls the open state of the picker. |
46
- | `hideHours` | `boolean` | `false` | Removes the hour column. |
47
- | `hideMinutes` | `boolean` | `false` | Removes the minute column. |
48
- | `hideSeconds` | `boolean` | `false` | Removes the second column. |
49
- | `element` _bindable_ | `HTMLButtonElement` | | The DOM reference of the trigger button. |
50
- | `wrapperRef` _bindable_ | `HTMLDivElement` | | The DOM reference of the element wrapping the trigger and its input. |
51
- | `wrapperAttributes` | `HTMLAttributes` | | The attributes to spread on the wrapper element. |
52
- | `inputElement` _bindable_ | `HTMLInputElement` | | The DOM reference of the underlying input element. |
53
- | `inputProps` | `HTMLInputAttributes` | | HTML attributes for the underlying input element. Give it a `name` to submit the value with a form. |
54
- | `popupLabel` | `string` | `'Choose a time'` | The accessible name of the popup, which is announced as a dialog. |
55
- | Element Attributes (`button`) | | | |
43
+ | Name | Type | Default | Description |
44
+ | -------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
45
+ | `value` _bindable_ | `string` | `''` | Selected time as 24h `HH:mm` or `HH:mm:ss` (matches `<input type="time">`). |
46
+ | `format` | `12` &#124; `24` | `24` | Clock format: `24` (00-23) or `12` (01-12 with an AM/PM column). |
47
+ | `open` _bindable_ | `boolean` | `false` | Controls the open state of the picker. |
48
+ | `hideHours` | `boolean` | `false` | Removes the hour column. |
49
+ | `hideMinutes` | `boolean` | `false` | Removes the minute column. |
50
+ | `hideSeconds` | `boolean` | `false` | Removes the second column. |
51
+ | `ref` _bindable_ | `HTMLInputElement` &#124; `null` | | The DOM reference of the native time input that holds the value. The other attributes of the picker are applied to it. |
52
+ | `buttonRef` _bindable_ | `HTMLButtonElement` &#124; `null` | | The DOM reference of the button that opens the picker. |
53
+ | `buttonAttributes` | `HTMLButtonAttributes` | | The attributes to spread on the button that opens the picker. |
54
+ | `wrapperRef` _bindable_ | `HTMLDivElement` &#124; `null` | | The DOM reference of the element wrapping the button and the native input. |
55
+ | `wrapperAttributes` | `HTMLAttributes` | | The attributes to spread on the wrapper element. |
56
+ | `popupLabel` | `string` | `'Choose a time'` | The accessible name of the popup, which is announced as a dialog. |
57
+ | `columnLabels` | `{ hour, minute, second, … (4 keys) }` | `{ hour: 'Hour', minute: 'Minute', second: 'Second', meridiem: 'AM/PM' }` | The names of the hour, minute, second and AM/PM columns: shown on the trigger while there is no time, and given to the lists of the popup. |
58
+ | `NativeFieldAttributes<HTMLInputAttributes>` | | | |
56
59
 
57
60
  <!-- /props:TimePickerProps -->
@@ -1,14 +1,17 @@
1
1
  <script lang="ts">
2
+ import { claimLabel } from '../label/label-context.js';
2
3
  import type { TimePickerProps } from './types.js';
3
4
  import { Flyout, Button, Divider } from '../../index.js';
4
5
  import { on } from 'svelte/events';
5
6
  import { tick } from 'svelte';
6
7
  import { PREFIX } from '../../internal/constants.js';
7
- import { floating } from '../../internal/index.js';
8
+ import { dispatchFieldEvents, floating, invokeHandlers, NativeField } from '../../internal/index.js';
8
9
  import { flip, hide, offset, shift } from '@floating-ui/dom';
9
10
  import DynamicCarousel from '../../internal/components/dynamic-carousel/dynamic-carousel.svelte';
10
11
  import { CheckmarkRegular, DismissRegular } from 'fluentui-icons-svelte';
11
12
 
13
+ const labelContext = claimLabel(() => id);
14
+
12
15
  let {
13
16
  format = 24,
14
17
  value = $bindable(''),
@@ -16,24 +19,44 @@
16
19
  hideHours = false,
17
20
  hideMinutes = false,
18
21
  hideSeconds = false,
19
- element = $bindable(),
20
- wrapperRef = $bindable(),
22
+ id = labelContext?.fieldId,
23
+ name = id,
24
+ ref = $bindable(null),
25
+ buttonRef = $bindable(null),
26
+ buttonAttributes,
27
+ wrapperRef = $bindable(null),
21
28
  wrapperAttributes,
22
- inputElement = $bindable(),
23
- inputProps,
29
+ disabled,
30
+ readonly,
31
+ step,
32
+ onfocus,
33
+ onblur,
24
34
  popupLabel = 'Choose a time',
35
+ columnLabels,
25
36
  ...attributes
26
37
  }: TimePickerProps = $props();
27
38
 
28
39
  const FALLBACK_ID = $props.id();
29
40
  const POPUP_ID = `${PREFIX}timepicker-${FALLBACK_ID}-popup`;
30
41
 
31
- let popupRef: HTMLElement | undefined = $state();
42
+ let popupRef: HTMLElement | null = $state(null);
43
+
44
+ const native = new NativeField(
45
+ () => ref,
46
+ () => buttonRef,
47
+ labelContext
48
+ );
32
49
 
33
50
  /** Closing always hands the focus back, so the keyboard never lands nowhere. */
34
51
  function closePopup() {
35
52
  open = false;
36
- element?.focus();
53
+ buttonRef?.focus();
54
+ }
55
+
56
+ /** A label activates the native input it belongs to, which hands the activation to the button. */
57
+ function openFromField() {
58
+ buttonRef?.focus();
59
+ open = !readonly;
37
60
  }
38
61
 
39
62
  // Escape has to work from inside the popup too, and the popup is not a descendant
@@ -64,19 +87,23 @@
64
87
  const HOURS_12 = Array.from({ length: 12 }, (_, i) => pad(i === 0 ? 12 : i)); // 12, 01 … 11
65
88
  const SIXTY = Array.from({ length: 60 }, (_, i) => pad(i));
66
89
  const MERIDIEM = ['AM', 'PM'];
67
- const LABELS: Record<Column, string> = {
90
+ // Name the columns on the trigger while there is no time, and the listboxes of the popup.
91
+ const labels: Record<Column, string> = $derived({
68
92
  hour: 'Hour',
69
93
  minute: 'Minute',
70
94
  second: 'Second',
71
- meridiem: 'AM/PM'
72
- };
95
+ meridiem: 'AM/PM',
96
+ ...columnLabels
97
+ });
73
98
 
74
99
  const hour12 = $derived(format === 12);
75
100
 
76
101
  // Working selection — canonical 24h values, synced with `value` while the popup is closed.
77
- let hour = $state(new Date().getHours()); // 0-23
78
- let minute = $state(new Date().getMinutes()); // 0-59
79
- let second = $state(new Date().getSeconds()); // 0-59
102
+ // It starts from `value`, so a popup rendered open on the server shows it too.
103
+ const initial = parseValue(value);
104
+ let hour = $state(initial.h); // 0-23
105
+ let minute = $state(initial.mi); // 0-59
106
+ let second = $state(initial.s); // 0-59
80
107
 
81
108
  const isPM = $derived(hour >= 12);
82
109
  // 12h wheel index: hour % 12 (0 → "12", 1 → "01", …, 11 → "11").
@@ -94,6 +121,9 @@
94
121
 
95
122
  const hasValue = $derived(/^\d{2}:\d{2}(:\d{2})?$/.test(value ?? ''));
96
123
 
124
+ // Parsed committed value: the closed trigger reads it, since the working selection only catches up in an effect.
125
+ const committed = $derived(parseValue(value));
126
+
97
127
  function parseValue(v: string) {
98
128
  const m = /^(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(v ?? '');
99
129
  const t = new Date();
@@ -127,7 +157,11 @@
127
157
  }
128
158
 
129
159
  function confirm() {
130
- value = `${pad(hour)}:${pad(minute)}${hideSeconds ? '' : `:${pad(second)}`}`;
160
+ const next = `${pad(hour)}:${pad(minute)}${hideSeconds ? '' : `:${pad(second)}`}`;
161
+ if (next !== value) {
162
+ value = next;
163
+ dispatchFieldEvents(ref);
164
+ }
131
165
  closePopup();
132
166
  }
133
167
 
@@ -138,24 +172,29 @@
138
172
  // Trigger label per column: the live selection while open, the committed value once set,
139
173
  // otherwise the placeholder column name.
140
174
  function display(col: Column): string {
141
- if (!open && !hasValue) return LABELS[col];
142
- if (col === 'hour') return hour12 ? HOURS_12[hour12Index] : pad(hour);
143
- if (col === 'minute') return pad(minute);
144
- if (col === 'second') return pad(second);
145
- return isPM ? 'PM' : 'AM';
175
+ if (!open && !hasValue) return labels[col];
176
+ const { h, mi, s } = open ? { h: hour, mi: minute, s: second } : committed;
177
+ if (col === 'hour') return hour12 ? HOURS_12[h % 12] : pad(h);
178
+ if (col === 'minute') return pad(mi);
179
+ if (col === 'second') return pad(s);
180
+ return h >= 12 ? 'PM' : 'AM';
146
181
  }
147
182
  </script>
148
183
 
149
- <div class="fs-time-picker-wrapper" bind:this={wrapperRef} {...wrapperAttributes}>
184
+ <div {...wrapperAttributes} class={['fs-time-picker-wrapper', wrapperAttributes?.class]} bind:this={wrapperRef}>
150
185
  <button
186
+ {...buttonAttributes}
187
+ {...native.aria(attributes, buttonAttributes, 'button')}
151
188
  type="button"
152
- class="fs-time-picker"
189
+ class={['fs-time-picker', buttonAttributes?.class]}
190
+ {disabled}
153
191
  aria-haspopup="dialog"
154
192
  aria-expanded={open}
155
193
  aria-controls={open ? POPUP_ID : undefined}
156
- onclick={() => (open = !open)}
157
- bind:this={element}
158
- {...attributes}
194
+ onclick={(e) => invokeHandlers(e, [], [() => (open = !readonly && !open), buttonAttributes?.onclick])}
195
+ onfocus={(e) => invokeHandlers(e, [], [onfocus, buttonAttributes?.onfocus])}
196
+ onblur={(e) => invokeHandlers(e, [], [onblur, buttonAttributes?.onblur])}
197
+ bind:this={buttonRef}
159
198
  >
160
199
  {#each columns as col, idx (col)}
161
200
  {#if idx > 0}
@@ -165,13 +204,29 @@
165
204
  {/each}
166
205
  </button>
167
206
 
168
- <!-- Carries the value into a form. -->
169
- <input type="time" hidden step={hideSeconds ? undefined : 1} {value} bind:this={inputElement} {...inputProps} />
207
+ <!-- The field of the form: submitted, validated and reset like any time input,
208
+ behind the button and out of the tab order, since the button is what people use. See `NativeField`.
209
+ Seconds need a step of one to be a valid value. -->
210
+ <input
211
+ {...attributes}
212
+ {id}
213
+ {name}
214
+ class="native-field"
215
+ aria-hidden={native.reported ? undefined : 'true'}
216
+ type="time"
217
+ tabindex={-1}
218
+ step={step ?? (hideSeconds ? undefined : 1)}
219
+ {disabled}
220
+ {readonly}
221
+ bind:value
222
+ bind:this={ref}
223
+ onclick={openFromField}
224
+ />
170
225
  </div>
171
226
 
172
227
  {#if open}
173
228
  <Flyout
174
- {@attach floating(element, {
229
+ {@attach floating(buttonRef, {
175
230
  placement: 'bottom',
176
231
  middleware: [
177
232
  offset(({ rects }) => {
@@ -183,7 +238,7 @@
183
238
  ],
184
239
  strategy: 'fixed'
185
240
  })}
186
- reference={element}
241
+ reference={buttonRef}
187
242
  id={POPUP_ID}
188
243
  role="dialog"
189
244
  aria-modal="true"
@@ -198,16 +253,37 @@
198
253
  <div class="carousel-col col-{col}">
199
254
  {#if col === 'hour'}
200
255
  {#if hour12}
201
- <DynamicCarousel values={HOURS_12} selectedIndex={hour12Index} onSelected={(_, i) => setHour12(i)} />
256
+ <DynamicCarousel
257
+ label={labels.hour}
258
+ values={HOURS_12}
259
+ selectedIndex={hour12Index}
260
+ onSelected={(_, i) => setHour12(i)}
261
+ />
202
262
  {:else}
203
- <DynamicCarousel values={HOURS_24} selectedIndex={hour} onSelected={(_, i) => (hour = i)} />
263
+ <DynamicCarousel
264
+ label={labels.hour}
265
+ values={HOURS_24}
266
+ selectedIndex={hour}
267
+ onSelected={(_, i) => (hour = i)}
268
+ />
204
269
  {/if}
205
270
  {:else if col === 'minute'}
206
- <DynamicCarousel values={SIXTY} selectedIndex={minute} onSelected={(_, i) => (minute = i)} />
271
+ <DynamicCarousel
272
+ label={labels.minute}
273
+ values={SIXTY}
274
+ selectedIndex={minute}
275
+ onSelected={(_, i) => (minute = i)}
276
+ />
207
277
  {:else if col === 'second'}
208
- <DynamicCarousel values={SIXTY} selectedIndex={second} onSelected={(_, i) => (second = i)} />
278
+ <DynamicCarousel
279
+ label={labels.second}
280
+ values={SIXTY}
281
+ selectedIndex={second}
282
+ onSelected={(_, i) => (second = i)}
283
+ />
209
284
  {:else}
210
285
  <DynamicCarousel
286
+ label={labels.meridiem}
211
287
  values={MERIDIEM}
212
288
  selectedIndex={isPM ? 1 : 0}
213
289
  infinite={false}
@@ -230,11 +306,34 @@
230
306
  {/if}
231
307
 
232
308
  <style>
309
+ /* The box of the button, which the native field covers so its validation message points at the button. */
233
310
  .fs-time-picker-wrapper {
234
- display: contents;
311
+ display: flex;
312
+ position: relative;
313
+ inline-size: fit-content;
314
+ }
315
+ .fs-time-picker-wrapper:has(> .native-field:is(:user-invalid, [aria-invalid='true'])) {
316
+ & .picker-content {
317
+ color: var(--fs-system-critical);
318
+ }
319
+ & .fs-time-picker::after,
320
+ & :global(.fs-divider) {
321
+ background: var(--fs-system-critical);
322
+ }
323
+ }
324
+ .native-field {
325
+ position: absolute;
326
+ inset: 0;
327
+ inline-size: 100%;
328
+ block-size: 100%;
329
+ margin: 0;
330
+ box-sizing: border-box;
331
+ opacity: 0;
332
+ pointer-events: none;
235
333
  }
236
334
  .fs-time-picker {
237
335
  display: flex;
336
+ flex: 1 1 auto;
238
337
  position: relative;
239
338
  border-radius: var(--fs-control-border-radius);
240
339
  font-size: var(--fs-body-font-size);
@@ -291,6 +390,7 @@
291
390
  position: fixed !important;
292
391
  top: 0;
293
392
  left: 0;
393
+ z-index: 110;
294
394
  max-width: min-content;
295
395
  padding: 0 !important;
296
396
  & .wrapper {
@@ -1,4 +1,4 @@
1
1
  import type { TimePickerProps } from './types.ts';
2
- declare const TimePicker: import("svelte").Component<TimePickerProps, {}, "value" | "element" | "wrapperRef" | "open" | "inputElement">;
2
+ declare const TimePicker: import("svelte").Component<TimePickerProps, {}, "ref" | "value" | "open" | "wrapperRef" | "buttonRef">;
3
3
  type TimePicker = ReturnType<typeof TimePicker>;
4
4
  export default TimePicker;