noph-ui 0.51.2 → 0.53.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 (187) hide show
  1. package/dist/animation.d.ts +14 -1
  2. package/dist/animation.js +55 -2
  3. package/dist/app-bar/AppBar.svelte +71 -7
  4. package/dist/app-bar/types.d.ts +6 -0
  5. package/dist/autocomplete/AutoComplete.svelte +6 -31
  6. package/dist/autocomplete/AutoComplete.svelte.d.ts +1 -1
  7. package/dist/badge/Badge.svelte +12 -4
  8. package/dist/badge/description.d.ts +4 -0
  9. package/dist/badge/description.js +4 -0
  10. package/dist/button/Button.svelte +53 -30
  11. package/dist/button/ButtonGroup.svelte +139 -19
  12. package/dist/button/ButtonGroup.svelte.d.ts +1 -1
  13. package/dist/button/IconButton.svelte +51 -33
  14. package/dist/button/SegmentedButton.svelte +52 -15
  15. package/dist/button/SplitButton.svelte +77 -3
  16. package/dist/button/SplitButton.svelte.d.ts +1 -1
  17. package/dist/button/buttonGroup.d.ts +0 -4
  18. package/dist/button/buttonGroup.js +0 -9
  19. package/dist/button/groupContext.d.ts +10 -0
  20. package/dist/button/groupContext.js +4 -0
  21. package/dist/button/types.d.ts +19 -0
  22. package/dist/card/Card.svelte +3 -19
  23. package/dist/card/types.d.ts +12 -2
  24. package/dist/carousel/Carousel.svelte +77 -14
  25. package/dist/carousel/CarouselItem.svelte +26 -14
  26. package/dist/carousel/carouselMorph.js +1 -1
  27. package/dist/carousel/carouselStrategy.js +25 -27
  28. package/dist/checkbox/Checkbox.svelte +6 -8
  29. package/dist/chip/ChipSet.svelte +28 -2
  30. package/dist/chip/FilterChip.svelte +33 -17
  31. package/dist/chip/InputChip.svelte +127 -26
  32. package/dist/chip/SuggestionChip.svelte +6 -22
  33. package/dist/chip/SuggestionChip.svelte.d.ts +1 -2
  34. package/dist/chip/types.d.ts +8 -1
  35. package/dist/date-picker/Calendar.svelte +34 -31
  36. package/dist/date-picker/Calendar.svelte.d.ts +2 -0
  37. package/dist/date-picker/CalendarHeader.svelte +84 -62
  38. package/dist/date-picker/CalendarHeader.svelte.d.ts +3 -0
  39. package/dist/date-picker/DatePickerDialog.svelte +135 -75
  40. package/dist/date-picker/DatePickerDialog.svelte.d.ts +4 -3
  41. package/dist/date-picker/DateRangePicker.svelte +227 -78
  42. package/dist/date-picker/DateRangePicker.svelte.d.ts +3 -3
  43. package/dist/date-picker/DockedDatePicker.svelte +76 -457
  44. package/dist/date-picker/DockedDatePicker.svelte.d.ts +3 -3
  45. package/dist/date-picker/DockedDateTimePicker.svelte +128 -518
  46. package/dist/date-picker/DockedDateTimePicker.svelte.d.ts +3 -3
  47. package/dist/date-picker/DockedPickerField.svelte +221 -0
  48. package/dist/date-picker/DockedPickerField.svelte.d.ts +9 -0
  49. package/dist/date-picker/MonthPanel.svelte +291 -0
  50. package/dist/date-picker/MonthPanel.svelte.d.ts +12 -0
  51. package/dist/date-picker/SelectionList.svelte +7 -6
  52. package/dist/date-picker/SelectionList.svelte.d.ts +1 -0
  53. package/dist/date-picker/TimeColumn.svelte +2 -6
  54. package/dist/date-picker/TimeColumn.svelte.d.ts +1 -0
  55. package/dist/date-picker/YearGrid.svelte +6 -13
  56. package/dist/date-picker/YearGrid.svelte.d.ts +1 -0
  57. package/dist/date-picker/dateUtils.d.ts +4 -0
  58. package/dist/date-picker/dateUtils.js +15 -88
  59. package/dist/date-picker/exitVisibility.svelte.js +13 -6
  60. package/dist/date-picker/formatters.d.ts +16 -0
  61. package/dist/date-picker/formatters.js +48 -15
  62. package/dist/date-picker/internalTypes.d.ts +59 -1
  63. package/dist/date-picker/shortcuts.d.ts +11 -0
  64. package/dist/date-picker/shortcuts.js +40 -0
  65. package/dist/date-picker/timeUtils.d.ts +8 -3
  66. package/dist/date-picker/timeUtils.js +33 -51
  67. package/dist/date-picker/types.d.ts +22 -0
  68. package/dist/dialog/Dialog.svelte +171 -33
  69. package/dist/dialog/Dialog.svelte.d.ts +2 -1
  70. package/dist/dialog/types.d.ts +6 -0
  71. package/dist/divider/Divider.svelte +36 -10
  72. package/dist/divider/types.d.ts +1 -0
  73. package/dist/fab/ExtendedFab.svelte +26 -60
  74. package/dist/fab/Fab.svelte +13 -48
  75. package/dist/fab/FabMenu.svelte +102 -44
  76. package/dist/fab/FabMenu.svelte.d.ts +2 -1
  77. package/dist/fab/FabMenuItem.svelte +8 -0
  78. package/dist/fab/FabMenuItem.svelte.d.ts +4 -0
  79. package/dist/fab/index.d.ts +1 -0
  80. package/dist/fab/index.js +1 -0
  81. package/dist/fab/types.d.ts +6 -4
  82. package/dist/icons/Icon.svelte +1 -4
  83. package/dist/icons/MenuIcon.svelte +13 -0
  84. package/dist/icons/MenuIcon.svelte.d.ts +7 -0
  85. package/dist/icons/MenuOpenIcon.svelte +15 -0
  86. package/dist/icons/MenuOpenIcon.svelte.d.ts +7 -0
  87. package/dist/icons/index.d.ts +2 -0
  88. package/dist/icons/index.js +2 -0
  89. package/dist/internal/exit.css +33 -0
  90. package/dist/internal/field.css +337 -0
  91. package/dist/internal/fieldTypes.d.ts +6 -0
  92. package/dist/internal/focus-ring.css +25 -6
  93. package/dist/internal/interest.css +3 -1
  94. package/dist/internal/pop-in.css +19 -0
  95. package/dist/internal/visually-hidden.css +11 -0
  96. package/dist/keyboard-nav.d.ts +12 -1
  97. package/dist/keyboard-nav.js +104 -14
  98. package/dist/list/Item.svelte +84 -24
  99. package/dist/list/List.svelte +35 -2
  100. package/dist/list/ListItem.svelte +16 -2
  101. package/dist/list/context.d.ts +6 -0
  102. package/dist/list/context.js +4 -0
  103. package/dist/list/types.d.ts +7 -0
  104. package/dist/media.d.ts +1 -0
  105. package/dist/media.js +1 -0
  106. package/dist/menu/Menu.svelte +53 -20
  107. package/dist/menu/Menu.svelte.d.ts +3 -1
  108. package/dist/menu/MenuItem.svelte +9 -3
  109. package/dist/menu/types.d.ts +7 -1
  110. package/dist/navigation-bar/NavigationBar.svelte +9 -3
  111. package/dist/navigation-bar/NavigationBarItem.svelte +31 -39
  112. package/dist/navigation-drawer/NavigationDrawer.svelte +96 -20
  113. package/dist/navigation-drawer/NavigationDrawer.svelte.d.ts +2 -1
  114. package/dist/navigation-drawer/NavigationDrawerItem.svelte +10 -12
  115. package/dist/navigation-drawer/NavigationDrawerSection.svelte +49 -0
  116. package/dist/navigation-drawer/NavigationDrawerSection.svelte.d.ts +4 -0
  117. package/dist/navigation-drawer/index.d.ts +1 -0
  118. package/dist/navigation-drawer/index.js +1 -0
  119. package/dist/navigation-drawer/types.d.ts +12 -0
  120. package/dist/navigation-rail/NavigationRail.svelte +367 -7
  121. package/dist/navigation-rail/NavigationRail.svelte.d.ts +1 -1
  122. package/dist/navigation-rail/NavigationRailItem.svelte +185 -59
  123. package/dist/navigation-rail/NavigationRailSection.svelte +57 -0
  124. package/dist/navigation-rail/NavigationRailSection.svelte.d.ts +4 -0
  125. package/dist/navigation-rail/context.d.ts +6 -0
  126. package/dist/navigation-rail/context.js +5 -0
  127. package/dist/navigation-rail/index.d.ts +1 -0
  128. package/dist/navigation-rail/index.js +1 -0
  129. package/dist/navigation-rail/morph.d.ts +14 -0
  130. package/dist/navigation-rail/morph.js +78 -0
  131. package/dist/navigation-rail/types.d.ts +21 -0
  132. package/dist/press.svelte.d.ts +0 -1
  133. package/dist/press.svelte.js +0 -11
  134. package/dist/progress/CircularProgress.svelte +28 -82
  135. package/dist/progress/CircularProgress.svelte.d.ts +1 -0
  136. package/dist/progress/LinearProgress.svelte +67 -171
  137. package/dist/progress/LinearProgress.svelte.d.ts +1 -0
  138. package/dist/progress/four-color.css +34 -0
  139. package/dist/progress/types.d.ts +1 -0
  140. package/dist/radio/Radio.svelte +13 -10
  141. package/dist/ripple/Ripple.svelte +8 -16
  142. package/dist/search/Search.svelte +238 -58
  143. package/dist/search/Search.svelte.d.ts +1 -0
  144. package/dist/search/types.d.ts +4 -0
  145. package/dist/select/Check.svelte +8 -31
  146. package/dist/select/Check.svelte.d.ts +1 -1
  147. package/dist/select/NativeSelect.svelte +113 -374
  148. package/dist/select/NativeSelect.svelte.d.ts +1 -0
  149. package/dist/select/Option.svelte +2 -11
  150. package/dist/select/Select.svelte +170 -858
  151. package/dist/select/Select.svelte.d.ts +1 -0
  152. package/dist/select/VirtualList.svelte +16 -2
  153. package/dist/select/VirtualList.svelte.d.ts +6 -2
  154. package/dist/sheet/Sheet.svelte +229 -22
  155. package/dist/sheet/Sheet.svelte.d.ts +3 -1
  156. package/dist/sheet/types.d.ts +10 -0
  157. package/dist/slider/Slider.svelte +14 -64
  158. package/dist/slider/Slider.svelte.d.ts +1 -0
  159. package/dist/snackbar/Snackbar.svelte +3 -0
  160. package/dist/snackbar/Snackbar.svelte.d.ts +2 -1
  161. package/dist/switch/Switch.svelte +53 -19
  162. package/dist/tabs/Tab.svelte +30 -18
  163. package/dist/tabs/Tabs.svelte +28 -4
  164. package/dist/tabs/types.d.ts +3 -0
  165. package/dist/text-field/TextField.svelte +124 -616
  166. package/dist/text-field/TextField.svelte.d.ts +1 -0
  167. package/dist/time-picker/ClockDial.svelte +8 -25
  168. package/dist/time-picker/ClockDial.svelte.d.ts +2 -0
  169. package/dist/time-picker/DockedTimePicker.svelte +102 -239
  170. package/dist/time-picker/DockedTimePicker.svelte.d.ts +3 -3
  171. package/dist/time-picker/PeriodSelector.svelte +8 -7
  172. package/dist/time-picker/PeriodSelector.svelte.d.ts +1 -0
  173. package/dist/time-picker/TimeInputs.svelte +13 -2
  174. package/dist/time-picker/TimePickerDialog.svelte +33 -26
  175. package/dist/time-picker/TimePickerDialog.svelte.d.ts +3 -3
  176. package/dist/time-picker/TimePickerPanel.svelte +26 -4
  177. package/dist/time-picker/TimeSelectors.svelte +4 -9
  178. package/dist/time-picker/TimeSelectors.svelte.d.ts +1 -0
  179. package/dist/time-picker/internalTypes.d.ts +2 -0
  180. package/dist/time-picker/timePickerState.svelte.js +3 -9
  181. package/dist/toolbar/Toolbar.svelte +21 -11
  182. package/dist/toolbar/types.d.ts +1 -0
  183. package/dist/tooltip/RichTooltip.svelte +2 -23
  184. package/dist/tooltip/RichTooltip.svelte.d.ts +2 -1
  185. package/dist/tooltip/Tooltip.svelte +5 -25
  186. package/dist/tooltip/Tooltip.svelte.d.ts +2 -1
  187. package/package.json +12 -12
@@ -1,10 +1,12 @@
1
1
  <script lang="ts">
2
+ import '../internal/field.css'
2
3
  import { formReset } from '../form-reset.js'
3
4
  import ArrowDropDownIcon from '../icons/ArrowDropDownIcon.svelte'
4
5
  import Item from '../list/Item.svelte'
5
6
  import Menu from '../menu/Menu.svelte'
6
7
  import Check from './Check.svelte'
7
8
  import VirtualList from './VirtualList.svelte'
9
+ import { typeaheadBuffer, typeaheadMatch } from '../keyboard-nav.js'
8
10
  import { tick } from 'svelte'
9
11
  import type { SelectOption, SelectProps } from './types.js'
10
12
 
@@ -40,25 +42,24 @@
40
42
 
41
43
  const uid = $props.id()
42
44
  const supportingTextId = `supporting-text-${uid}`
45
+ const labelId = `label-${uid}`
43
46
  const ids = (...values: (string | undefined | null | false)[]) =>
44
47
  values.filter(Boolean).join(' ') || undefined
48
+ const defaultValue = () =>
49
+ multiple
50
+ ? options.filter((option) => option.selected).map((option) => option.value)
51
+ : options.find((option) => option.selected)?.value
52
+
45
53
  $effect(() => {
46
- if (value === undefined) {
47
- if (multiple) {
48
- value = options.filter((option) => option.selected).map((option) => option.value)
49
- } else {
50
- value = options.find((option) => option.selected)?.value
51
- }
52
- }
54
+ if (value === undefined) value = defaultValue()
53
55
  })
54
56
 
55
57
  let valueArray = $derived<unknown[]>(
56
58
  Array.isArray(value) ? value : value === undefined || value === null ? [] : [value],
57
59
  )
58
60
  let selectedSet = $derived.by<Set<unknown>>(() => new Set(valueArray))
59
- let selectedOption: SelectOption[] = $derived(
60
- options.filter((o) => selectedSet.has(o.value)).map((o) => ({ ...o, selected: true })),
61
- )
61
+ let selectedOption = $derived(options.filter((o) => selectedSet.has(o.value)))
62
+ let populated = $derived(selectedOption.some((o) => o.value !== ''))
62
63
 
63
64
  let useVirtualList = $derived(options.length > virtualThreshold)
64
65
 
@@ -74,85 +75,34 @@
74
75
  let menuElement = $state<HTMLDivElement>()
75
76
  let anchorElement = $state<HTMLDivElement>()
76
77
 
77
- const restoreDefault = () =>
78
- queueMicrotask(() => {
79
- value = multiple
80
- ? options.filter((option) => option.selected).map((option) => option.value)
81
- : options.find((option) => option.selected)?.value
82
- })
78
+ const restoreDefault = () => queueMicrotask(() => (value = defaultValue()))
83
79
 
84
80
  let field = $state<HTMLDivElement>()
85
81
  let menuOpen = $state(false)
82
+ // Focus stays in the field. The active option is its aria-activedescendant, and shows as focused
83
+ // once the keys have moved to it.
86
84
  let focusIndex = $state(-1)
87
- let pendingFocus = false
88
- let typeBuffer = ''
89
- let lastTypeTime = 0
85
+ let keyboardActive = $state(false)
86
+ const collectTypeahead = typeaheadBuffer()
90
87
 
88
+ let selectedIndex = $derived(options.findIndex((o) => selectedSet.has(o.value)))
91
89
  let activeDescendantId = $derived.by<string | undefined>(() => {
92
90
  if (!menuOpen) return undefined
93
91
  if (focusIndex >= 0 && focusIndex < options.length) return `${uid}-opt-${focusIndex}`
94
- const fallbackIdx = multiple
95
- ? Array.isArray(value) && value.length
96
- ? options.findIndex((o) => o.value === value[0])
97
- : -1
98
- : options.findIndex((o) => o.value === value)
99
- return fallbackIdx >= 0 ? `${uid}-opt-${fallbackIdx}` : undefined
100
- })
101
- let selectedLabel = $derived.by<string>(() => {
102
- if (multiple) {
103
- if (value && Array.isArray(value)) {
104
- return value
105
- .map((v) => options.find((option) => option.value === v)?.label || '')
106
- .filter((o) => o)
107
- .join(', ')
108
- }
109
- return ''
110
- }
111
- return options.find((option) => option.value === value)?.label || ''
92
+ return selectedIndex >= 0 ? `${uid}-opt-${selectedIndex}` : undefined
112
93
  })
94
+ let selectedLabel = $derived(
95
+ valueArray
96
+ .map((v) => options.find((option) => option.value === v)?.label)
97
+ .filter(Boolean)
98
+ .join(', '),
99
+ )
113
100
 
114
- let cachedRowHeight = 0
115
- const ensureRowHeight = () => {
116
- if (!cachedRowHeight && menuElement) {
117
- const viewport = menuElement.querySelector(
118
- 'svelte-virtual-list-viewport',
119
- ) as HTMLElement | null
120
- if (viewport) {
121
- const firstRow = viewport.querySelector('[id^="' + uid + '-opt-"]') as HTMLElement | null
122
- cachedRowHeight = firstRow?.offsetHeight || 48
123
- }
124
- }
125
- if (!cachedRowHeight) cachedRowHeight = 48
126
- return cachedRowHeight
127
- }
101
+ let virtualList = $state<VirtualList<SelectOption>>()
128
102
  const scrollOptionIntoView = (index: number) => {
129
- if (index < 0 || !menuElement) return
130
- if (!useVirtualList) {
131
- document.getElementById(`${uid}-opt-${index}`)?.scrollIntoView({ block: 'nearest' })
132
- return
133
- }
134
- const viewport = menuElement.querySelector('svelte-virtual-list-viewport') as HTMLElement | null
135
- if (!viewport) return
136
- const rowHeight = ensureRowHeight()
137
- const top = index * rowHeight
138
- const bottom = top + rowHeight
139
- const { scrollTop, clientHeight } = viewport
140
- if (top < scrollTop) viewport.scrollTop = top
141
- else if (bottom > scrollTop + clientHeight) viewport.scrollTop = bottom - clientHeight
142
- }
143
-
144
- const defaultActiveIndex = () => {
145
- let idx = -1
146
- if (multiple) {
147
- if (Array.isArray(value) && value.length) {
148
- idx = options.findIndex((o) => value.includes(o.value) && !o.disabled)
149
- }
150
- } else {
151
- idx = options.findIndex((o) => o.value === value && !o.disabled)
152
- }
153
- if (idx < 0) idx = options.findIndex((o) => !o.disabled)
154
- if (idx < 0) idx = 0
155
- return idx
103
+ if (index < 0) return
104
+ if (useVirtualList) virtualList?.scrollToIndex(index)
105
+ else document.getElementById(`${uid}-opt-${index}`)?.scrollIntoView({ block: 'nearest' })
156
106
  }
157
107
 
158
108
  const toggleValue = (option: SelectOption) => {
@@ -175,114 +125,58 @@
175
125
  if (!multiple) menuElement?.hidePopover()
176
126
  event.preventDefault()
177
127
  await tick()
128
+ selectElement?.dispatchEvent(new Event('input', { bubbles: true }))
178
129
  selectElement?.dispatchEvent(new Event('change', { bubbles: true }))
179
130
  }
180
131
 
181
- const focusActiveOption = () => {
182
- const el = document.getElementById(`${uid}-opt-${focusIndex}`)
183
- if (el) {
184
- el.focus()
185
- pendingFocus = false
186
- } else {
187
- scrollOptionIntoView(focusIndex)
188
- }
189
- }
190
-
191
- const openMenuAndFocus = async (index: number) => {
132
+ const showOption = async (index: number) => {
133
+ if (!options.length) return
192
134
  if (!menuOpen) menuElement?.showPopover()
193
135
  focusIndex = Math.min(Math.max(index, 0), options.length - 1)
194
- pendingFocus = true
136
+ keyboardActive = true
195
137
  await tick()
196
- focusActiveOption()
197
- }
198
-
199
- const moveFocus = (delta: number) => {
200
- if (!options.length) return
201
- let next = focusIndex
202
- if (next < 0) {
203
- const selIdx = Array.isArray(value)
204
- ? options.findIndex((o) => value.includes(o.value) && !o.disabled)
205
- : options.findIndex((o) => o.value === value && !o.disabled)
206
- next = selIdx >= 0 ? selIdx : 0
207
- }
208
- let attempts = 0
209
- while (attempts < options.length) {
210
- next = (next + delta + options.length) % options.length
211
- if (!options[next].disabled) {
212
- openMenuAndFocus(next)
213
- return
214
- }
215
- attempts++
216
- }
217
- }
218
-
219
- const focusEdge = (start: boolean) => {
220
- if (!options.length) {
221
- return
222
- }
223
- if (start) {
224
- for (let i = 0; i < options.length; i++) {
225
- if (!options[i].disabled) {
226
- openMenuAndFocus(i)
227
- return
228
- }
229
- }
230
- } else {
231
- for (let i = options.length - 1; i >= 0; i--) {
232
- if (!options[i].disabled) {
233
- openMenuAndFocus(i)
234
- return
235
- }
236
- }
237
- }
238
- }
239
-
240
- const optionKeydown = (event: KeyboardEvent, option: SelectOption) => {
138
+ scrollOptionIntoView(focusIndex)
139
+ }
140
+
141
+ // The first arrow press shows the active option, the next ones move from it. Disabled options
142
+ // take focus like any other, they just cannot be picked.
143
+ const moveFocus = (delta: number) =>
144
+ showOption(
145
+ keyboardActive
146
+ ? (focusIndex + delta + options.length) % options.length
147
+ : focusIndex >= 0
148
+ ? focusIndex
149
+ : Math.max(selectedIndex, 0),
150
+ )
151
+
152
+ const typeahead = (event: KeyboardEvent) => {
153
+ const query = collectTypeahead(event)
154
+ if (!query) return
155
+ const match = typeaheadMatch(
156
+ options.map((o) => o.label ?? ''),
157
+ focusIndex,
158
+ query,
159
+ )
160
+ if (match >= 0) showOption(match)
161
+ }
162
+
163
+ const handleKeydown = (event: KeyboardEvent) => {
241
164
  const key = event.key
242
- if (key === 'ArrowDown') {
243
- event.preventDefault()
244
- moveFocus(1)
245
- } else if (key === 'ArrowUp') {
246
- event.preventDefault()
247
- moveFocus(-1)
248
- } else if (key === 'Home') {
165
+ if (key === 'Tab') {
166
+ menuElement?.hidePopover()
167
+ } else if (key === 'ArrowDown' || key === 'ArrowUp') {
249
168
  event.preventDefault()
250
- focusEdge(true)
251
- } else if (key === 'End') {
169
+ moveFocus(key === 'ArrowDown' ? 1 : -1)
170
+ } else if (key === 'Home' || key === 'End') {
252
171
  event.preventDefault()
253
- focusEdge(false)
172
+ showOption(key === 'Home' ? 0 : options.length - 1)
254
173
  } else if (key === 'Enter' || key === ' ') {
255
174
  event.preventDefault()
256
- handleOptionSelect(event, option)
257
- } else if (key === 'Tab') {
258
- menuElement?.hidePopover()
259
- } else if (key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
260
- performTypeahead(key)
261
- }
262
- }
263
-
264
- const performTypeahead = (char: string) => {
265
- const now = performance.now()
266
- if (now - lastTypeTime > 700) typeBuffer = ''
267
- lastTypeTime = now
268
- typeBuffer += char.toLowerCase()
269
- if (!options.length) {
270
- return
271
- }
272
- const startIdx = focusIndex >= 0 ? (focusIndex + 1) % options.length : 0
273
- for (let i = 0; i < options.length; i++) {
274
- const idx = (startIdx + i) % options.length
275
- const label = options[idx].label?.toLowerCase?.() || ''
276
- if (label.startsWith(typeBuffer) && !options[idx].disabled) {
277
- openMenuAndFocus(idx)
278
- return
279
- }
280
- }
281
-
282
- if (typeBuffer.length > 1) {
283
- const last = typeBuffer[typeBuffer.length - 1]
284
- typeBuffer = last
285
- performTypeahead('')
175
+ const option = options[focusIndex]
176
+ if (!menuOpen) moveFocus(0)
177
+ else if (option) handleOptionSelect(event, option)
178
+ } else {
179
+ typeahead(event)
286
180
  }
287
181
  }
288
182
  </script>
@@ -293,31 +187,28 @@
293
187
  </span>
294
188
  {/snippet}
295
189
 
296
- <div
297
- style={(variant === 'outlined'
298
- ? '--top-space:1rem;--bottom-space:1rem;--floating-label-top:-0.5rem;--floating-label-left:-2.25rem;--_focus-outline-width:3px;'
299
- : !label?.length
300
- ? '--top-space:1rem;--bottom-space:1rem;'
301
- : '') + style}
302
- class={['np-text-field', attributes.class]}
303
- bind:this={element}
304
- >
190
+ <div style={style ?? undefined} class={['np-text-field', attributes.class]} bind:this={element}>
305
191
  <!-- svelte-ignore a11y_autofocus -->
306
192
  <div
307
193
  {id}
308
- class="field"
309
- class:no-label={!label?.length}
310
- class:with-start={start}
311
- class:menu-open={menuOpen}
312
- class:with-end={true}
313
- class:disabled
314
- class:outlined={variant === 'outlined'}
194
+ class={[
195
+ 'np-field',
196
+ variant === 'outlined' && 'np-field-outlined',
197
+ !label?.length && 'np-field-no-label',
198
+ start && 'np-field-with-start',
199
+ 'np-field-with-end',
200
+ disabled && 'np-field-disabled',
201
+ populated && 'np-field-populated',
202
+ menuOpen && 'np-field-open',
203
+ ]}
315
204
  role="combobox"
316
205
  aria-haspopup="listbox"
317
206
  tabindex={disabled ? -1 : tabindex}
318
207
  aria-controls="listbox-{uid}"
319
208
  aria-expanded={menuOpen}
320
- aria-label={attributes['aria-label'] || label}
209
+ aria-label={attributes['aria-label']}
210
+ aria-labelledby={attributes['aria-label'] || !label?.length ? undefined : labelId}
211
+ aria-required={required || undefined}
321
212
  aria-disabled={disabled}
322
213
  aria-activedescendant={activeDescendantId}
323
214
  {...ariaProps}
@@ -332,138 +223,63 @@
332
223
  menuElement?.showPopover()
333
224
  }
334
225
  }}
335
- onkeydown={(event) => {
336
- const key = event.key
337
- if (key === 'Tab') {
338
- menuElement?.hidePopover()
339
- return
340
- }
341
- if (key === 'Escape') {
342
- menuElement?.hidePopover()
343
- return
344
- }
345
- if (key === 'ArrowDown') {
346
- event.preventDefault()
347
- moveFocus(1)
348
- return
349
- }
350
- if (key === 'ArrowUp') {
351
- event.preventDefault()
352
- moveFocus(-1)
353
- return
354
- }
355
- if (key === 'Home') {
356
- event.preventDefault()
357
- focusEdge(true)
358
- return
359
- }
360
- if (key === 'End') {
361
- event.preventDefault()
362
- focusEdge(false)
363
- return
364
- }
365
- if (key === 'Enter' || key === ' ') {
366
- event.preventDefault()
367
- if (!menuOpen) {
368
- openMenuAndFocus(focusIndex >= 0 ? focusIndex : 0)
369
- } else if (focusIndex >= 0) {
370
- const opt = options[focusIndex]
371
- if (opt && !opt.disabled) {
372
- handleOptionSelect(event, opt)
373
- }
374
- }
375
- return
376
- }
377
- if (key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
378
- performTypeahead(key)
379
- return
380
- }
381
- }}
226
+ onkeydown={handleKeydown}
382
227
  >
383
- <div class="container-overflow">
228
+ {#snippet labelText()}
229
+ <span id={labelId} class={['np-field-label', !noAsterisk && required && 'np-field-required']}
230
+ >{label}</span
231
+ >
232
+ {/snippet}
233
+ <div class="np-field-box">
384
234
  {#if variant === 'filled'}
385
- <div class="background"></div>
386
- <div class="state-layer"></div>
387
- <div class="active-indicator"></div>
388
- {/if}
389
- {#if variant === 'outlined'}
390
- <div class="np-outline">
391
- <div class="outline-start"></div>
392
- {#if label?.length}
393
- <div class="label-wrapper">
394
- <span class={['label', !noAsterisk && required && 'required']}>{label}</span>
395
- </div>
396
- <div class="outline-notch">
397
- <span class="notch" aria-hidden="true"
398
- >{label}{noAsterisk || !required ? '' : '*'}</span
399
- >
400
- </div>
401
- {/if}
402
- <div class="outline-end"></div>
403
- </div>
235
+ <div class="np-field-background"></div>
236
+ <div class="np-field-state-layer"></div>
237
+ <div class="np-field-indicator"></div>
238
+ {:else}
239
+ <fieldset class="np-field-outline" aria-hidden="true">
240
+ <legend>
241
+ {#if label?.length}
242
+ <span>{label}{noAsterisk || !required ? '' : '*'}</span>
243
+ {/if}
244
+ </legend>
245
+ </fieldset>
246
+ {#if label?.length}
247
+ <div class="np-field-label-wrapper">{@render labelText()}</div>
248
+ {/if}
404
249
  {/if}
405
- <div class="np-container" bind:this={anchorElement} style="anchor-name:--{uid};">
250
+ <div class="np-field-row" bind:this={anchorElement} style="anchor-name:--{uid};">
406
251
  {#if start}
407
- <div class="start">
408
- <span class="icon">{@render start()}</span>
252
+ <div class="np-field-start">
253
+ <span class="np-field-icon">{@render start()}</span>
409
254
  </div>
410
255
  {/if}
411
- <div class="middle">
412
- {#if variant === 'filled'}
413
- <div class="label-wrapper">
414
- {#if label?.length}
415
- <span class={['label', !noAsterisk && required && 'required']}>{label}</span>
416
- {/if}
417
- </div>
256
+ <div class="np-field-middle">
257
+ {#if variant === 'filled' && label?.length}
258
+ <div class="np-field-label-wrapper">{@render labelText()}</div>
418
259
  {/if}
419
- <div class="content">
420
- {#if multiple}
421
- <select
422
- tabindex="-1"
423
- {disabled}
424
- {required}
425
- {name}
426
- {form}
427
- multiple
428
- {onchange}
429
- {oninput}
430
- bind:value
431
- bind:this={selectElement}
432
- {@attach formReset(restoreDefault)}
433
- >
434
- {#each selectedOption as option, index (index)}
435
- <option class="np-option" value={option.value} selected={option.selected}
436
- >{option.label}</option
437
- >
438
- {/each}
439
- </select>
440
- {:else}
441
- <select
442
- tabindex="-1"
443
- {disabled}
444
- {required}
445
- {name}
446
- {form}
447
- {onchange}
448
- {oninput}
449
- bind:value
450
- bind:this={selectElement}
451
- {@attach formReset(restoreDefault)}
452
- >
453
- {#each selectedOption as option, index (index)}
454
- <option value={option.value} selected={option.selected}>{option.label}</option>
455
- {/each}
456
- </select>
457
- {/if}
458
- <div class="input">
459
- {#if selectedLabel}
460
- {selectedLabel}
461
- {/if}
462
- </div>
260
+ <div class="np-field-content">
261
+ <select
262
+ class="np-field-value"
263
+ tabindex="-1"
264
+ {disabled}
265
+ {required}
266
+ {name}
267
+ {form}
268
+ {multiple}
269
+ {onchange}
270
+ {oninput}
271
+ bind:this={selectElement}
272
+ {@attach formReset(restoreDefault)}
273
+ >
274
+ {#each selectedOption as option, index (index)}
275
+ <option value={option.value} selected>{option.label}</option>
276
+ {/each}
277
+ </select>
278
+ <div class="np-select-text">{selectedLabel}</div>
463
279
  </div>
464
280
  </div>
465
- <div class="end">
466
- <span class="icon trailing">
281
+ <div class="np-field-end">
282
+ <span class="np-field-icon">
467
283
  {#if end}
468
284
  {@render end()}
469
285
  {:else}
@@ -474,7 +290,11 @@
474
290
  </div>
475
291
  </div>
476
292
  {#if supportingText || errorText}
477
- <div id={supportingTextId} class="supporting-text" role={errorText ? 'alert' : undefined}>
293
+ <div
294
+ id={supportingTextId}
295
+ class="np-field-supporting-text"
296
+ role={errorText ? 'alert' : undefined}
297
+ >
478
298
  <span>
479
299
  {errorText ?? supportingText}
480
300
  </span>
@@ -490,18 +310,18 @@
490
310
  {/snippet}
491
311
  <Item
492
312
  id="{uid}-opt-{index}"
313
+ onpointerdown={(event) => event.preventDefault()}
493
314
  onclick={(event) => {
315
+ focusIndex = index
494
316
  handleOptionSelect(event, option)
495
- field?.focus()
496
317
  }}
497
318
  tabindex={-1}
498
319
  disabled={option.disabled}
499
320
  aria-disabled={option.disabled}
500
321
  role="option"
501
- onkeydown={(event) => optionKeydown(event, option)}
502
322
  selected={isSelected}
503
323
  aria-selected={isSelected}
504
- class={option.class}
324
+ class={[option.class, keyboardActive && index === focusIndex && 'np-focus-ring-shown']}
505
325
  style={option.style}
506
326
  start={multiple ? check : option.start}
507
327
  end={option.end}
@@ -526,31 +346,18 @@
526
346
  bind:open={menuOpen}
527
347
  ontoggle={async ({ newState }) => {
528
348
  if (newState === 'open') {
529
- if (focusIndex < 0) focusIndex = defaultActiveIndex()
349
+ if (focusIndex < 0) focusIndex = Math.max(selectedIndex, 0)
530
350
  await tick()
531
351
  scrollOptionIntoView(focusIndex)
532
352
  } else {
533
353
  focusIndex = -1
534
- pendingFocus = false
354
+ keyboardActive = false
535
355
  }
536
356
  }}
537
357
  bind:element={menuElement}
538
358
  >
539
359
  {#if useVirtualList}
540
- <VirtualList
541
- height="250px"
542
- itemHeight={48}
543
- items={options}
544
- rendered={({ start, end }) => {
545
- if (pendingFocus && focusIndex >= start && focusIndex < end) {
546
- const el = document.getElementById(`${uid}-opt-${focusIndex}`)
547
- if (el) {
548
- el.focus()
549
- pendingFocus = false
550
- }
551
- }
552
- }}
553
- >
360
+ <VirtualList bind:this={virtualList} height="250px" items={options}>
554
361
  {#snippet row(option, index)}
555
362
  {@render item(option, index)}
556
363
  {/snippet}
@@ -563,158 +370,34 @@
563
370
  </Menu>
564
371
 
565
372
  <style>
566
- .active-indicator {
567
- inset: auto 0 0 0;
568
- pointer-events: none;
569
- position: absolute;
570
- width: 100%;
571
- z-index: 1;
572
- }
573
- .field.menu-open .active-indicator::after,
574
- .field:focus .active-indicator::after {
575
- opacity: 1;
576
- }
577
- .active-indicator::after {
578
- opacity: 0;
579
- transition: opacity 150ms cubic-bezier(0.2, 0, 0, 1);
580
- }
581
- .active-indicator::before,
582
- .active-indicator::after {
583
- border-bottom: 1px solid var(--np-color-on-surface-variant);
584
- inset: auto 0 0 0;
585
- content: '';
586
- position: absolute;
587
- width: 100%;
588
- }
589
- .active-indicator::after {
590
- border-bottom-color: var(--np-color-primary);
591
- border-bottom-width: 3px;
592
- }
593
- .field:is([aria-invalid='true'], :has(select:user-invalid)) .active-indicator::before {
594
- border-bottom-color: var(--np-color-error);
595
- }
596
- .field:is([aria-invalid='true'], :has(select:user-invalid)) .active-indicator::after {
597
- border-bottom-color: var(--np-color-error);
598
- }
599
- .disabled .active-indicator::before {
600
- border-bottom-color: var(--np-color-on-surface);
601
- border-bottom-width: 1px;
602
- opacity: 0.38;
603
- }
604
- .background {
605
- background: var(
606
- --np-filled-select-text-field-container-color,
607
- var(--np-color-surface-container-highest)
608
- );
609
- }
610
- .disabled .background {
611
- background: var(--np-color-on-surface);
612
- opacity: 0.04;
613
- }
614
- .background,
615
- .state-layer {
616
- border-radius: inherit;
617
- inset: 0;
618
- pointer-events: none;
619
- position: absolute;
620
- }
621
- .np-container {
622
- align-items: center;
623
- border-radius: inherit;
624
- display: flex;
625
- flex: 1;
626
- max-height: 100%;
627
- min-height: 100%;
628
- min-width: 0;
629
- position: relative;
630
- user-select: none;
631
- }
632
- .outlined .container-overflow {
633
- border-start-start-radius: var(
634
- --np-outlined-select-text-field-container-shape,
635
- var(--np-shape-corner-extra-small)
636
- );
637
- border-start-end-radius: var(
638
- --np-outlined-select-text-field-container-shape,
639
- var(--np-shape-corner-extra-small)
640
- );
641
- border-end-end-radius: var(
642
- --np-outlined-select-text-field-container-shape,
643
- var(--np-shape-corner-extra-small)
644
- );
645
- border-end-start-radius: var(
646
- --np-outlined-select-text-field-container-shape,
647
- var(--np-shape-corner-extra-small)
648
- );
373
+ .np-text-field {
374
+ display: inline-flex;
375
+ text-align: start;
649
376
  }
650
- .container-overflow {
651
- border-start-start-radius: var(
377
+ .np-field {
378
+ --_field-shape: var(
652
379
  --np-filled-select-text-field-container-shape,
653
380
  var(--np-shape-corner-extra-small)
654
381
  );
655
- border-start-end-radius: var(
656
- --np-filled-select-text-field-container-shape,
657
- var(--np-shape-corner-extra-small)
382
+ --_field-container-color: var(
383
+ --np-filled-select-text-field-container-color,
384
+ var(--np-color-surface-container-highest)
658
385
  );
659
- border-end-end-radius: var(--np-shape-corner-none);
660
- border-end-start-radius: var(--np-shape-corner-none);
661
- display: flex;
662
- height: 100%;
663
- position: relative;
664
- }
665
- .np-text-field {
666
- display: inline-flex;
667
- resize: both;
668
- text-align: start;
669
- }
670
-
671
- .field.disabled {
672
- cursor: default;
673
- }
674
-
675
- .field {
676
- display: flex;
677
- flex: 1;
678
- flex-direction: column;
679
- writing-mode: horizontal-tb;
680
- max-width: var(--np-select-max-width, 100%);
681
386
  min-width: var(--np-select-min-width, 210px);
387
+ max-width: var(--np-select-max-width, 100%);
682
388
  outline: none;
683
389
  }
684
-
685
- .supporting-text {
686
- display: flex;
687
- gap: 1rem;
688
- font-size: 0.75rem;
689
- line-height: 1rem;
690
- color: var(--np-color-on-surface-variant);
691
- justify-content: space-between;
692
- padding: 0.25rem 1rem 0;
693
- }
694
- .field:is([aria-invalid='true'], :has(select:user-invalid)) .supporting-text {
695
- color: var(--np-color-error);
696
- }
697
- .disabled .supporting-text {
698
- color: var(--np-color-on-surface);
699
- opacity: 0.38;
390
+ .np-field-outlined {
391
+ --_field-shape: var(
392
+ --np-outlined-select-text-field-container-shape,
393
+ var(--np-shape-corner-extra-small)
394
+ );
700
395
  }
701
-
702
- .field:not(.disabled):hover .state-layer {
703
- visibility: visible;
396
+ .np-field-row {
397
+ user-select: none;
704
398
  }
705
399
 
706
- .disabled {
707
- pointer-events: none;
708
- }
709
- .field:not(.disabled):hover .state-layer {
710
- background: var(--np-color-on-surface);
711
- opacity: 0.08;
712
- }
713
- .resizable .np-container > * {
714
- top: var(--_focus-outline-width, 3px);
715
- inset-inline-start: var(--_focus-outline-width, 0);
716
- }
717
- .content * {
400
+ .np-field-content * {
718
401
  all: unset;
719
402
  color: currentColor;
720
403
  font-size: 1rem;
@@ -723,410 +406,39 @@
723
406
  white-space: revert;
724
407
  }
725
408
 
726
- .content select {
409
+ /* Holds the value for the form and its validation, never shown. */
410
+ .np-field-value {
727
411
  position: absolute;
728
412
  width: 0;
729
413
  height: 0;
730
414
  visibility: hidden;
731
415
  }
732
416
 
733
- .middle {
734
- align-items: stretch;
735
- align-self: baseline;
417
+ .np-select-text {
736
418
  flex: 1;
737
419
  min-width: 0;
738
- }
739
-
740
- .input {
741
- caret-color: var(--np-color-primary);
420
+ height: 1.5rem;
421
+ padding-top: var(--top-space);
422
+ padding-bottom: var(--bottom-space);
742
423
  overflow: hidden;
743
424
  text-overflow: ellipsis;
744
425
  white-space: nowrap;
745
426
  text-align: inherit;
746
- flex: 1;
747
- min-width: 0;
748
- height: 1.5rem;
749
-
750
- &::placeholder {
751
- color: currentColor;
752
- opacity: 1;
753
- }
754
-
755
- &::-webkit-calendar-picker-indicator {
756
- display: none;
757
- }
758
-
759
- &::-webkit-search-decoration,
760
- &::-webkit-search-cancel-button {
761
- display: none;
762
- }
763
-
764
- @media (forced-colors: active) {
765
- background: none;
766
- }
767
- }
768
-
769
- .np-option {
770
- width: 0;
771
- height: 0;
772
- display: block;
773
427
  }
774
-
775
- .no-label .content,
776
- .field.menu-open .content,
777
- .field:focus .content,
778
- .field:has(select option:checked:not([value=''])) .content {
779
- opacity: 1;
428
+ .np-field:not(.np-field-with-start) .np-select-text {
429
+ padding-inline-start: 1rem;
780
430
  }
781
431
 
782
- .icon .arrow {
432
+ .arrow {
783
433
  display: flex;
784
434
  }
785
- .field.menu-open .icon .arrow {
435
+ .np-field-open .arrow {
786
436
  rotate: 180deg;
787
437
  }
788
438
 
789
- .content {
790
- color: var(--np-color-on-surface);
791
- display: flex;
792
- flex: 1 1 0%;
793
- opacity: 1;
794
- min-width: 0;
795
- transition: opacity 83ms cubic-bezier(0.2, 0, 0, 1);
796
- }
797
- .disabled .content {
798
- color: var(--np-color-on-surface);
799
- }
800
- .field:not(.with-end) .content .input {
801
- padding-inline-end: 16px;
802
- }
803
- .outline-start,
804
- .field:not(.with-start) .content .input {
805
- padding-inline-start: 16px;
806
- }
807
-
808
- .content .input {
809
- padding-top: var(--top-space, 1.5rem);
810
- padding-bottom: var(--bottom-space, 0.5rem);
811
- }
812
-
813
- .start {
814
- color: var(--np-color-on-surface-variant);
815
- margin-inline-start: 0.75rem;
816
- margin-inline-end: 1rem;
817
- }
818
- .end {
819
- color: var(--np-color-on-surface-variant);
820
- margin-inline-start: 1rem;
821
- margin-inline-end: 0.75rem;
822
- }
823
- .field:is([aria-invalid='true'], :has(select:user-invalid)) .end {
824
- color: var(--np-color-error);
825
- }
826
- .disabled .start,
827
- .disabled .end {
828
- color: var(--np-color-on-surface);
829
- opacity: 0.38;
830
- }
831
- .start,
832
- .middle,
833
- .end {
834
- display: flex;
835
- box-sizing: border-box;
836
- height: 100%;
837
- position: relative;
838
- }
839
- .start,
840
- .end {
841
- align-items: center;
842
- justify-content: center;
843
- }
844
- .icon {
845
- display: flex;
846
- color: currentColor;
847
- align-items: center;
848
- justify-content: center;
849
- fill: currentColor;
850
- position: relative;
851
- }
852
- :global(.icon svg) {
853
- fill: currentColor;
854
- }
855
-
856
- .label-wrapper {
857
- user-select: none;
858
- pointer-events: none;
859
- inset: 0;
860
- position: absolute;
861
- text-align: initial;
862
- }
863
- .field:not(.with-end) .label-wrapper {
864
- margin-inline-end: 1rem;
865
- }
866
- .field:not(.with-start) .label-wrapper {
867
- margin-inline-start: 1rem;
868
- }
869
- .with-start .np-outline .label-wrapper {
870
- inset-inline-start: 3.25rem;
871
- }
872
- .with-end .np-outline .label-wrapper {
873
- margin-inline-end: 3.25rem;
874
- }
875
-
876
- .with-start.menu-open .label-wrapper,
877
- .with-start:has(select:focus-visible option:checked:not([value=''])) .label-wrapper,
878
- .with-start:has(select option:checked:not([value=''])) .label-wrapper,
879
- .with-start:has(select:focus-visible) .label-wrapper {
880
- inset-inline-end: -2.25rem;
881
- }
882
-
883
- .with-end.menu-open .label-wrapper,
884
- .with-end:focus:has(select option:checked:not([value=''])) .label-wrapper,
885
- .with-end:focus .label-wrapper {
886
- margin-inline-end: 1rem;
887
- }
888
- .notch {
889
- font-size: 0.75rem;
890
- line-height: 1rem;
891
- opacity: 0;
892
- }
893
-
894
- .label.required::after {
895
- content: '*';
896
- }
897
-
898
- .field:not(.menu-open):not(:focus) .label {
899
- position: absolute;
900
- top: 1rem;
901
- inset-inline-start: 0rem;
902
- }
903
-
904
- .field.menu-open .label,
905
- .field:focus:has(select option:checked:not([value=''])) .label,
906
- .field:has(select option:checked:not([value=''])) .label,
907
- .field:focus .label {
908
- font-size: 0.75rem;
909
- line-height: 1rem;
910
- transform-origin: top left;
911
- position: absolute;
912
- top: var(--floating-label-top, 0.5rem);
913
- }
914
-
915
- .with-start.menu-open .label,
916
- .with-start:focus .label,
917
- .with-start:has(select:focus-visible option:checked:not([value=''])) .label,
918
- .with-start:has(select option:checked:not([value=''])) .label,
919
- .with-start:has(select:focus-visible) .label {
920
- inset-inline-start: var(--floating-label-left, 0);
921
- }
922
- .label {
923
- transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1);
924
- box-sizing: border-box;
925
- color: var(--np-color-on-surface-variant);
926
- overflow: hidden;
927
- max-width: 100%;
928
- text-overflow: ellipsis;
929
- white-space: nowrap;
930
- z-index: 1;
931
- font-size: 1rem;
932
- line-height: 1.5rem;
933
- width: min-content;
934
- }
935
-
936
- .field.menu-open .label,
937
- .field:focus .label {
938
- color: var(--np-color-primary);
939
- }
940
- .field:is([aria-invalid='true'], :has(select:user-invalid)) .label,
941
- .field:is([aria-invalid='true'], :has(select:user-invalid)).menu-open .label,
942
- .field:is([aria-invalid='true'], :has(select:user-invalid)):focus .label {
943
- color: var(--np-color-error);
944
- }
945
- .disabled .label {
946
- color: var(--np-color-on-surface);
947
- opacity: 0.38;
948
- }
949
- .resizable:not(.disabled) .np-container {
950
- resize: inherit;
951
- overflow: hidden;
952
- }
953
- .disabled.no-label .content,
954
- .disabled:has(select option:checked:not([value=''])) .content {
955
- opacity: 0.38;
956
- }
957
- .field,
958
- .container-overflow {
959
- resize: inherit;
960
- }
961
- .resizable .np-container {
962
- bottom: 3px;
963
- inset-inline-end: var(--_focus-outline-width, 0);
964
- clip-path: inset(3px 0 0 var(--_focus-outline-width));
965
- }
966
- .outline-start,
967
- .outline-end {
968
- border: inherit;
969
- border-radius: inherit;
970
- box-sizing: border-box;
971
- position: relative;
972
- }
973
- .outline-start::before,
974
- .outline-start::after,
975
- .outline-end::before,
976
- .outline-end::after {
977
- border: inherit;
978
- content: '';
979
- inset: 0;
980
- position: absolute;
981
- }
982
- .outline-start::before,
983
- .outline-start::after {
984
- border-inline-start-style: solid;
985
- border-inline-end-style: none;
986
- border-start-start-radius: inherit;
987
- border-start-end-radius: 0;
988
- border-end-start-radius: inherit;
989
- border-end-end-radius: 0;
990
- margin-inline-end: 0.25rem;
991
- }
992
- .outline-end::before,
993
- .outline-end::after {
994
- border-inline-start-style: none;
995
- border-inline-end-style: solid;
996
- border-start-start-radius: 0;
997
- border-start-end-radius: inherit;
998
- border-end-start-radius: 0;
999
- border-end-end-radius: inherit;
1000
- }
1001
- .outline-notch::before,
1002
- .outline-notch::after {
1003
- border: inherit;
1004
- content: '';
1005
- inset: 0;
1006
- position: absolute;
1007
- }
1008
- .outline-start::before,
1009
- .outline-end::before,
1010
- .outline-notch::before {
1011
- border-width: 1px;
1012
- }
1013
- .outline-start::before,
1014
- .outline-start::after,
1015
- .outline-end::before,
1016
- .outline-end::after {
1017
- border-bottom-style: solid;
1018
- border-top-style: solid;
1019
- }
1020
- .outline-notch::after {
1021
- border-bottom-style: solid;
1022
- border-top-style: none;
1023
- }
1024
- .outline-notch::before {
1025
- border-bottom-style: solid;
1026
- border-top-style: solid;
1027
- }
1028
-
1029
- .field.menu-open .outline-notch::before,
1030
- .field:focus .outline-notch::before,
1031
- .field:has(select option:checked:not([value=''])) .outline-notch::before {
1032
- border-top-style: none;
1033
- }
1034
-
1035
- .outline-notch::before,
1036
- .outline-notch::after {
1037
- border-inline-start-style: none;
1038
- border-inline-end-style: none;
1039
- border-start-start-radius: 0;
1040
- border-start-end-radius: 0;
1041
- border-end-start-radius: 0;
1042
- border-end-end-radius: 0;
1043
- }
1044
- .outline-notch {
1045
- align-items: flex-start;
1046
- border: inherit;
1047
- display: flex;
1048
- margin-inline-start: -0.25rem;
1049
- margin-inline-end: 0.25rem;
1050
- max-width: calc(100% - 2rem);
1051
- padding: 0 0.25rem;
1052
- position: relative;
1053
- }
1054
- .outline-end {
1055
- flex-grow: 1;
1056
- margin-inline-start: calc(-1 * 4px);
1057
- }
1058
- .outline-start::after,
1059
- .outline-end::after,
1060
- .outline-notch::after {
1061
- border-width: 3px;
1062
- }
1063
- .outline-start::after,
1064
- .outline-end::after,
1065
- .outline-notch::after {
1066
- opacity: 0;
1067
- transition: opacity 150ms cubic-bezier(0.2, 0, 0, 1);
1068
- }
1069
-
1070
- .field.menu-open .outline-start::after,
1071
- .field.menu-open .outline-end::after,
1072
- .field.menu-open .outline-notch::after,
1073
- .field:focus .outline-start::after,
1074
- .field:focus .outline-end::after,
1075
- .field:focus .outline-notch::after {
1076
- opacity: 1;
1077
- }
1078
- .np-outline {
1079
- border-color: var(--np-color-outline);
1080
- border-radius: inherit;
1081
- display: flex;
1082
- pointer-events: none;
1083
- height: 100%;
1084
- position: absolute;
1085
- width: 100%;
1086
- z-index: 1;
1087
- }
1088
-
1089
- .field:not(.disabled, .menu-open, :focus):hover .np-outline {
1090
- border-color: var(--np-color-on-surface);
1091
- color: var(--np-color-on-surface);
1092
- }
1093
- .field.menu-open .np-outline,
1094
- .field:focus .np-outline {
1095
- border-color: var(--np-color-primary);
1096
- color: var(--np-color-primary);
1097
- }
1098
- .field:is([aria-invalid='true'], :has(select:user-invalid)) .np-outline,
1099
- .field:is([aria-invalid='true'], :has(select:user-invalid)).menu-open .np-outline,
1100
- .field:is([aria-invalid='true'], :has(select:user-invalid)):focus .np-outline {
1101
- border-color: var(--np-color-error);
1102
- }
1103
- .field:is([aria-invalid='true'], :has(select:user-invalid)):not(
1104
- .disabled,
1105
- .menu-open,
1106
- :focus
1107
- ):hover
1108
- .np-outline {
1109
- border-color: var(--np-color-on-error-container);
1110
- }
1111
- .disabled .np-outline {
1112
- border-color: var(--np-color-on-surface);
1113
- color: var(--np-color-on-surface);
1114
- }
1115
- .disabled .outline-start,
1116
- .disabled .outline-end,
1117
- .disabled .outline-notch {
1118
- opacity: 0.12;
1119
- }
1120
-
1121
439
  @media (prefers-reduced-motion: no-preference) {
1122
- .icon .arrow {
440
+ .arrow {
1123
441
  transition: rotate 150ms cubic-bezier(0.2, 0, 0, 1);
1124
442
  }
1125
-
1126
- .label {
1127
- transition-property: all;
1128
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1129
- transition-duration: 150ms;
1130
- }
1131
443
  }
1132
444
  </style>