noph-ui 0.44.0 → 0.45.1

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 (136) hide show
  1. package/README.md +10 -1
  2. package/dist/animation.d.ts +2 -0
  3. package/dist/animation.js +33 -0
  4. package/dist/app-bar/AppBar.svelte +230 -0
  5. package/dist/app-bar/AppBar.svelte.d.ts +4 -0
  6. package/dist/app-bar/index.d.ts +1 -0
  7. package/dist/app-bar/index.js +1 -0
  8. package/dist/app-bar/types.d.ts +13 -0
  9. package/dist/app-bar/types.js +1 -0
  10. package/dist/autocomplete/AutoComplete.svelte +10 -11
  11. package/dist/autocomplete/AutoComplete.svelte.d.ts +1 -1
  12. package/dist/autocomplete/types.d.ts +1 -1
  13. package/dist/badge/Badge.svelte +6 -1
  14. package/dist/badge/types.d.ts +0 -1
  15. package/dist/button/Button.svelte +5 -15
  16. package/dist/button/IconButton.svelte +5 -15
  17. package/dist/button/SegmentedButton.svelte +15 -1
  18. package/dist/button/SplitButton.svelte +75 -0
  19. package/dist/button/SplitButton.svelte.d.ts +4 -0
  20. package/dist/button/index.d.ts +1 -0
  21. package/dist/button/index.js +1 -0
  22. package/dist/button/types.d.ts +17 -4
  23. package/dist/card/types.d.ts +1 -1
  24. package/dist/checkbox/Checkbox.svelte +47 -58
  25. package/dist/chip/AssistChip.svelte +5 -5
  26. package/dist/chip/ChipSet.svelte +3 -11
  27. package/dist/chip/FilterChip.svelte +33 -27
  28. package/dist/chip/InputChip.svelte +13 -2
  29. package/dist/chip/SuggestionChip.svelte +29 -0
  30. package/dist/chip/SuggestionChip.svelte.d.ts +4 -0
  31. package/dist/chip/index.d.ts +1 -0
  32. package/dist/chip/index.js +1 -0
  33. package/dist/chip/types.d.ts +16 -9
  34. package/dist/date-picker/Calendar.svelte +1 -1
  35. package/dist/date-picker/DatePickerDialog.svelte +23 -11
  36. package/dist/date-picker/DatePickerDialog.svelte.d.ts +4 -1
  37. package/dist/date-picker/DateRangePicker.svelte +40 -10
  38. package/dist/date-picker/DateRangePicker.svelte.d.ts +4 -1
  39. package/dist/date-picker/DockedDatePicker.svelte +13 -10
  40. package/dist/date-picker/DockedDatePicker.svelte.d.ts +4 -1
  41. package/dist/date-picker/DockedDateTimePicker.svelte +13 -7
  42. package/dist/date-picker/DockedDateTimePicker.svelte.d.ts +4 -1
  43. package/dist/date-picker/SelectionList.svelte +6 -32
  44. package/dist/date-picker/SelectionList.svelte.d.ts +2 -12
  45. package/dist/date-picker/TimeColumn.svelte +4 -29
  46. package/dist/date-picker/exitVisibility.svelte.d.ts +5 -0
  47. package/dist/date-picker/exitVisibility.svelte.js +23 -0
  48. package/dist/date-picker/optionList.d.ts +4 -0
  49. package/dist/date-picker/optionList.js +31 -0
  50. package/dist/date-picker/types.d.ts +11 -4
  51. package/dist/dialog/Dialog.svelte +55 -68
  52. package/dist/dialog/Dialog.svelte.d.ts +3 -3
  53. package/dist/dialog/types.d.ts +4 -3
  54. package/dist/fab/ExtendedFab.svelte +255 -0
  55. package/dist/fab/ExtendedFab.svelte.d.ts +5 -0
  56. package/dist/fab/Fab.svelte +211 -0
  57. package/dist/fab/Fab.svelte.d.ts +5 -0
  58. package/dist/fab/FabMenu.svelte +207 -0
  59. package/dist/fab/FabMenu.svelte.d.ts +7 -0
  60. package/dist/fab/index.d.ts +3 -0
  61. package/dist/fab/index.js +3 -0
  62. package/dist/fab/types.d.ts +34 -0
  63. package/dist/fab/types.js +1 -0
  64. package/dist/icons/ArrowBackIcon.svelte +13 -0
  65. package/dist/icons/ArrowBackIcon.svelte.d.ts +7 -0
  66. package/dist/icons/ChevronDownIcon.svelte +3 -0
  67. package/dist/icons/ChevronDownIcon.svelte.d.ts +26 -0
  68. package/dist/icons/SearchIcon.svelte +15 -0
  69. package/dist/icons/SearchIcon.svelte.d.ts +7 -0
  70. package/dist/icons/index.d.ts +3 -0
  71. package/dist/icons/index.js +3 -0
  72. package/dist/index.d.ts +6 -0
  73. package/dist/index.js +6 -0
  74. package/dist/internal/focus-ring.css +12 -0
  75. package/dist/loading-indicator/shapes.js +0 -7
  76. package/dist/menu/Menu.svelte +12 -7
  77. package/dist/menu/Menu.svelte.d.ts +2 -2
  78. package/dist/menu/types.d.ts +0 -2
  79. package/dist/navigation-bar/NavigationBar.svelte +59 -0
  80. package/dist/navigation-bar/NavigationBar.svelte.d.ts +4 -0
  81. package/dist/navigation-bar/NavigationBarItem.svelte +239 -0
  82. package/dist/navigation-bar/NavigationBarItem.svelte.d.ts +5 -0
  83. package/dist/navigation-bar/index.d.ts +2 -0
  84. package/dist/navigation-bar/index.js +2 -0
  85. package/dist/navigation-bar/types.d.ts +19 -0
  86. package/dist/navigation-bar/types.js +1 -0
  87. package/dist/navigation-drawer/NavigationDrawer.svelte +107 -76
  88. package/dist/navigation-drawer/NavigationDrawer.svelte.d.ts +4 -1
  89. package/dist/navigation-drawer/NavigationDrawerItem.svelte +10 -3
  90. package/dist/navigation-drawer/types.d.ts +3 -1
  91. package/dist/navigation-rail/NavigationRailItem.svelte +16 -0
  92. package/dist/navigation-rail/types.d.ts +3 -0
  93. package/dist/popover.svelte.d.ts +5 -0
  94. package/dist/popover.svelte.js +22 -0
  95. package/dist/press.svelte.d.ts +6 -0
  96. package/dist/press.svelte.js +32 -0
  97. package/dist/radio/Radio.svelte +26 -7
  98. package/dist/radio/types.d.ts +3 -0
  99. package/dist/search/Search.svelte +391 -0
  100. package/dist/search/Search.svelte.d.ts +5 -0
  101. package/dist/search/index.d.ts +1 -0
  102. package/dist/search/index.js +1 -0
  103. package/dist/search/types.d.ts +19 -0
  104. package/dist/search/types.js +1 -0
  105. package/dist/select/Select.svelte +48 -87
  106. package/dist/sheet/Sheet.svelte +235 -0
  107. package/dist/sheet/Sheet.svelte.d.ts +7 -0
  108. package/dist/sheet/index.d.ts +1 -0
  109. package/dist/sheet/index.js +1 -0
  110. package/dist/sheet/types.d.ts +11 -0
  111. package/dist/sheet/types.js +1 -0
  112. package/dist/slider/Slider.svelte +118 -30
  113. package/dist/slider/types.d.ts +3 -0
  114. package/dist/snackbar/Snackbar.svelte +32 -35
  115. package/dist/snackbar/Snackbar.svelte.d.ts +2 -2
  116. package/dist/snackbar/types.d.ts +3 -2
  117. package/dist/switch/Switch.svelte +51 -20
  118. package/dist/switch/types.d.ts +3 -0
  119. package/dist/tabs/Tab.svelte +4 -12
  120. package/dist/tabs/Tabs.svelte +5 -5
  121. package/dist/text-field/TextField.svelte +2 -4
  122. package/dist/toolbar/Toolbar.svelte +121 -0
  123. package/dist/toolbar/Toolbar.svelte.d.ts +4 -0
  124. package/dist/toolbar/index.d.ts +1 -0
  125. package/dist/toolbar/index.js +1 -0
  126. package/dist/toolbar/types.d.ts +8 -0
  127. package/dist/toolbar/types.js +1 -0
  128. package/dist/tooltip/RichTooltip.svelte +18 -12
  129. package/dist/tooltip/RichTooltip.svelte.d.ts +2 -2
  130. package/dist/tooltip/Tooltip.svelte +22 -11
  131. package/dist/tooltip/Tooltip.svelte.d.ts +4 -1
  132. package/dist/types.d.ts +6 -0
  133. package/dist/types.js +6 -0
  134. package/package.json +13 -13
  135. package/dist/date-picker/enterAnimation.d.ts +0 -1
  136. package/dist/date-picker/enterAnimation.js +0 -10
@@ -1,8 +1,11 @@
1
1
  <script lang="ts">
2
+ import { afterTwoFrames } from '../animation.js'
2
3
  import Button from '../button/Button.svelte'
3
4
  import IconButton from '../button/IconButton.svelte'
4
5
  import Dialog from '../dialog/Dialog.svelte'
6
+ import { syncOpenEffect } from '../popover.svelte.js'
5
7
  import Divider from '../divider/Divider.svelte'
8
+ import { exitVisibility } from './exitVisibility.svelte.js'
6
9
  import ArrowDropDownIcon from '../icons/ArrowDropDownIcon.svelte'
7
10
  import CalendarToday from '../icons/CalendarToday.svelte'
8
11
  import ChevronLeftIcon from '../icons/ChevronLeftIcon.svelte'
@@ -29,7 +32,6 @@
29
32
  toISODate,
30
33
  today as getToday,
31
34
  } from './dateUtils.js'
32
- import { afterTwoFrames } from './enterAnimation.js'
33
35
  import type { DatePickerDialogProps, ISODate } from './types.js'
34
36
 
35
37
  let {
@@ -48,6 +50,7 @@
48
50
  headline,
49
51
  label = 'Date',
50
52
  supportingText,
53
+ issues,
51
54
  name,
52
55
  form,
53
56
  modeToggle = false,
@@ -77,6 +80,8 @@
77
80
  let confirming = false
78
81
  let monthBeforeOpen: ISODate | undefined
79
82
 
83
+ const exit = exitVisibility()
84
+
80
85
  let todayValue = $derived(getToday())
81
86
  let week = $derived(firstDayOfWeek ?? getFirstDayOfWeek(locale))
82
87
  let minDate = $derived(parseISODate(min))
@@ -125,12 +130,20 @@
125
130
  cancelYearEnter = afterTwoFrames(() => (yearEnter = false))
126
131
  }
127
132
 
128
- const isOpen = () => !!element?.matches(':popover-open')
133
+ export const show = () => {
134
+ if (element && !element.open) element.showModal()
135
+ }
129
136
 
130
- $effect(() => {
131
- if (open && !isOpen()) element?.showPopover()
132
- if (!open && isOpen()) element?.hidePopover()
133
- })
137
+ export const close = () => {
138
+ if (element?.open) element.close()
139
+ }
140
+
141
+ syncOpenEffect(
142
+ () => element,
143
+ () => open,
144
+ show,
145
+ close,
146
+ )
134
147
 
135
148
  const setMonth = (date: Date) => {
136
149
  displayMonth = toISODate(startOfMonth(date))
@@ -197,6 +210,7 @@
197
210
  const nowOpen = event.newState === 'open'
198
211
  open = nowOpen
199
212
  if (nowOpen) {
213
+ exit.show()
200
214
  monthBeforeOpen = displayMonth
201
215
  confirming = false
202
216
  pending = value ?? undefined
@@ -206,14 +220,14 @@
206
220
  syncText()
207
221
  focusCalendarWhenReady()
208
222
  } else {
209
- displayMonth = monthBeforeOpen
210
223
  if (!confirming) oncancel?.()
211
224
  confirming = false
225
+ exit.scheduleExit(element, () => (displayMonth = monthBeforeOpen))
212
226
  }
213
227
  }}
214
228
  >
215
229
  <input class="np-date-picker-dialog-value" type="hidden" value={value ?? ''} {name} {form} />
216
- {#if open}
230
+ {#if exit.visible}
217
231
  <div class="np-date-picker-dialog-content">
218
232
  <div class="np-date-picker-dialog-header">
219
233
  <div class="np-date-picker-dialog-header-text">
@@ -225,9 +239,6 @@
225
239
  type="button"
226
240
  aria-label={mode === 'calendar' ? inputModeLabel : calendarModeLabel}
227
241
  onclick={() => {
228
- // The calendar moves `pending` on without touching the field, so the text has to
229
- // catch up before it is shown again. Otherwise the field contradicts the headline
230
- // and OK confirms a date the user never saw there.
231
242
  if (mode === 'calendar') syncText()
232
243
  setMode(mode === 'calendar' ? 'input' : 'calendar')
233
244
  showYearPicker(false)
@@ -247,6 +258,7 @@
247
258
  <div class={['np-date-picker-dialog-input', modeEnter && modeEnter]}>
248
259
  <TextField
249
260
  {label}
261
+ {issues}
250
262
  supportingText={supportingText ?? pattern}
251
263
  value={text}
252
264
  aria-invalid={inputInvalid ? 'true' : undefined}
@@ -1,4 +1,7 @@
1
1
  import type { DatePickerDialogProps } from './types.ts';
2
- declare const DatePickerDialog: import("svelte").Component<DatePickerDialogProps, {}, "element" | "value" | "displayMonth" | "open">;
2
+ declare const DatePickerDialog: import("svelte").Component<DatePickerDialogProps, {
3
+ show: () => void;
4
+ close: () => void;
5
+ }, "element" | "value" | "open" | "displayMonth">;
3
6
  type DatePickerDialog = ReturnType<typeof DatePickerDialog>;
4
7
  export default DatePickerDialog;
@@ -4,8 +4,10 @@
4
4
  import Dialog from '../dialog/Dialog.svelte'
5
5
  import Divider from '../divider/Divider.svelte'
6
6
  import CloseIcon from '../icons/CloseIcon.svelte'
7
+ import { syncOpenEffect } from '../popover.svelte.js'
7
8
  import { tick } from 'svelte'
8
9
  import Calendar from './Calendar.svelte'
10
+ import { exitVisibility } from './exitVisibility.svelte.js'
9
11
  import {
10
12
  clampDate,
11
13
  compareDays,
@@ -34,6 +36,10 @@
34
36
  isDateEnabled,
35
37
  adjacentMonthDays = false,
36
38
  title = 'Select range',
39
+ headline,
40
+ name,
41
+ endName,
42
+ form,
37
43
  startLabel = 'Start date',
38
44
  endLabel = 'End date',
39
45
  cancelLabel = 'Cancel',
@@ -52,6 +58,8 @@
52
58
  let focusedDay = $state<Date | undefined>(undefined)
53
59
  let confirming = false
54
60
 
61
+ const exit = exitVisibility()
62
+
55
63
  let todayValue = $derived(getToday())
56
64
  let week = $derived(firstDayOfWeek ?? getFirstDayOfWeek(locale))
57
65
  let minDate = $derived(parseISODate(min))
@@ -108,12 +116,20 @@
108
116
  if (target && scroller) scroller.scrollTop = target.offsetTop
109
117
  }
110
118
 
111
- const isOpen = () => !!element?.matches(':popover-open')
119
+ export const show = () => {
120
+ if (element && !element.open) element.showModal()
121
+ }
112
122
 
113
- $effect(() => {
114
- if (open && !isOpen()) element?.showPopover()
115
- if (!open && isOpen()) element?.hidePopover()
116
- })
123
+ export const close = () => {
124
+ if (element?.open) element.close()
125
+ }
126
+
127
+ syncOpenEffect(
128
+ () => element,
129
+ () => open,
130
+ show,
131
+ close,
132
+ )
117
133
 
118
134
  let prepending = false
119
135
 
@@ -176,6 +192,7 @@
176
192
  const nowOpen = event.newState === 'open'
177
193
  open = nowOpen
178
194
  if (nowOpen) {
195
+ exit.show()
179
196
  confirming = false
180
197
  pending = { ...value }
181
198
  focusedDay = undefined
@@ -184,10 +201,19 @@
184
201
  } else {
185
202
  if (!confirming) oncancel?.()
186
203
  confirming = false
204
+ exit.scheduleExit(element)
187
205
  }
188
206
  }}
189
207
  >
190
- {#if open}
208
+ <input class="np-date-range-picker-value" type="hidden" value={value.start ?? ''} {name} {form} />
209
+ <input
210
+ class="np-date-range-picker-value"
211
+ type="hidden"
212
+ value={value.end ?? ''}
213
+ name={endName}
214
+ {form}
215
+ />
216
+ {#if exit.visible}
191
217
  <div class="np-date-range-picker-content">
192
218
  <div class="np-date-range-picker-header">
193
219
  <div class="np-date-range-picker-top-bar">
@@ -201,9 +227,13 @@
201
227
  <div class="np-date-range-picker-header-text">
202
228
  <span class="np-date-range-picker-title">{title}</span>
203
229
  <h2 class="np-date-range-picker-headline">
204
- <span class={[!startDate && 'placeholder']}>{headlineStart}</span>
205
- <span aria-hidden="true">–</span>
206
- <span class={[!endDate && 'placeholder']}>{headlineEnd}</span>
230
+ {#if headline}
231
+ <span>{headline}</span>
232
+ {:else}
233
+ <span class={[!startDate && 'placeholder']}>{headlineStart}</span>
234
+ <span aria-hidden="true">–</span>
235
+ <span class={[!endDate && 'placeholder']}>{headlineEnd}</span>
236
+ {/if}
207
237
  </h2>
208
238
  </div>
209
239
  </div>
@@ -248,7 +278,7 @@
248
278
  {/if}
249
279
 
250
280
  {#snippet actions()}
251
- {#if open}
281
+ {#if exit.visible}
252
282
  <Button type="button" variant="text" onclick={cancel}>{cancelLabel}</Button>
253
283
  <Button type="button" variant="text" disabled={!canConfirm} onclick={confirm}>
254
284
  {confirmLabel}
@@ -1,4 +1,7 @@
1
1
  import type { DateRangePickerProps } from './types.ts';
2
- declare const DateRangePicker: import("svelte").Component<DateRangePickerProps, {}, "element" | "value" | "open">;
2
+ declare const DateRangePicker: import("svelte").Component<DateRangePickerProps, {
3
+ show: () => void;
4
+ close: () => void;
5
+ }, "element" | "value" | "open">;
3
6
  type DateRangePicker = ReturnType<typeof DateRangePicker>;
4
7
  export default DateRangePicker;
@@ -5,6 +5,7 @@
5
5
  import CalendarToday from '../icons/CalendarToday.svelte'
6
6
  import Menu from '../menu/Menu.svelte'
7
7
  import { onFormReset } from '../form-reset.js'
8
+ import { syncOpenEffect } from '../popover.svelte.js'
8
9
  import TextField from '../text-field/TextField.svelte'
9
10
  import { onMount, tick } from 'svelte'
10
11
  import Calendar from './Calendar.svelte'
@@ -34,7 +35,6 @@
34
35
  let {
35
36
  value = $bindable(),
36
37
  defaultValue,
37
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- absorbed on purpose
38
38
  type,
39
39
  'aria-invalid': ariaInvalid,
40
40
  style,
@@ -214,10 +214,18 @@
214
214
  focusCalendar()
215
215
  }
216
216
 
217
- $effect(() => {
218
- if (open) openPicker()
219
- else if (isShowing()) menuElement?.hidePopover()
220
- })
217
+ export const show = () => openPicker()
218
+
219
+ export const close = () => {
220
+ if (isShowing()) menuElement?.hidePopover()
221
+ }
222
+
223
+ syncOpenEffect(
224
+ () => menuElement,
225
+ () => open,
226
+ show,
227
+ close,
228
+ )
221
229
 
222
230
  const focusCalendar = () => {
223
231
  menuElement?.querySelector<HTMLElement>('.np-calendar-day[tabindex="0"]')?.focus()
@@ -255,7 +263,6 @@
255
263
  const parsed = usableDate(next)
256
264
  commit(parsed ? toISODate(parsed) : undefined)
257
265
  if (parsed) setMonth(parsed)
258
- // Recorded after the commit, so that it is remembered against the value it just produced.
259
266
  typed = { source: current, text: next }
260
267
  invalidText = undefined
261
268
  }
@@ -267,8 +274,6 @@
267
274
  }
268
275
  const parsed = usableDate(text)
269
276
  invalidText = parsed ? undefined : text
270
- // Leaving the field tidies a loose entry: dropping what was typed falls back to the
271
- // canonical formatting of the value it produced.
272
277
  if (parsed) typed = undefined
273
278
  }
274
279
 
@@ -285,8 +290,6 @@
285
290
  }
286
291
 
287
292
  const chooseYear = (year: number) => {
288
- // A year is offered as soon as any of its days is in range, so keeping the month the list was
289
- // opened from can land on a month where nothing at all is selectable.
290
293
  setMonth(new Date(year, clampMonthInYear(year, monthDate.getMonth(), minDate, maxDate), 1))
291
294
  leaveList()
292
295
  }
@@ -1,4 +1,7 @@
1
1
  import type { DockedDatePickerProps } from './types.ts';
2
- declare const DockedDatePicker: import("svelte").Component<DockedDatePickerProps, {}, "element" | "value" | "displayMonth" | "open">;
2
+ declare const DockedDatePicker: import("svelte").Component<DockedDatePickerProps, {
3
+ show: () => void;
4
+ close: () => void;
5
+ }, "element" | "value" | "open" | "displayMonth">;
3
6
  type DockedDatePicker = ReturnType<typeof DockedDatePicker>;
4
7
  export default DockedDatePicker;
@@ -5,6 +5,7 @@
5
5
  import CalendarToday from '../icons/CalendarToday.svelte'
6
6
  import Menu from '../menu/Menu.svelte'
7
7
  import { onFormReset } from '../form-reset.js'
8
+ import { syncOpenEffect } from '../popover.svelte.js'
8
9
  import TextField from '../text-field/TextField.svelte'
9
10
  import { onMount, tick } from 'svelte'
10
11
  import Calendar from './Calendar.svelte'
@@ -48,7 +49,6 @@
48
49
  let {
49
50
  value = $bindable(),
50
51
  defaultValue,
51
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- absorbed on purpose
52
52
  type,
53
53
  'aria-invalid': ariaInvalid,
54
54
  style,
@@ -118,8 +118,6 @@
118
118
  let week = $derived(firstDayOfWeek ?? getFirstDayOfWeek(locale))
119
119
  let minDate = $derived(parseISODateTime(min))
120
120
  let maxDate = $derived(parseISODateTime(max, MINUTES_IN_DAY - 1))
121
- // `as('datetime-local')` types its value as `string | number` and carries the last submission in
122
- // `defaultValue`, so both are funnelled into one ISO string the rest of the component reads.
123
121
  let current = $derived<string | undefined>(
124
122
  typeof value === 'string' ? value : typeof defaultValue === 'string' ? defaultValue : undefined,
125
123
  )
@@ -297,10 +295,18 @@
297
295
  focusCalendar()
298
296
  }
299
297
 
300
- $effect(() => {
301
- if (open) openPicker()
302
- else if (isShowing()) menuElement?.hidePopover()
303
- })
298
+ export const show = () => openPicker()
299
+
300
+ export const close = () => {
301
+ if (isShowing()) menuElement?.hidePopover()
302
+ }
303
+
304
+ syncOpenEffect(
305
+ () => menuElement,
306
+ () => open,
307
+ show,
308
+ close,
309
+ )
304
310
 
305
311
  const focusCalendar = () => {
306
312
  menuElement?.querySelector<HTMLElement>('.np-calendar-day[tabindex="0"]')?.focus()
@@ -1,4 +1,7 @@
1
1
  import type { DockedDateTimePickerProps } from './types.ts';
2
- declare const DockedDateTimePicker: import("svelte").Component<DockedDateTimePickerProps, {}, "element" | "value" | "displayMonth" | "open">;
2
+ declare const DockedDateTimePicker: import("svelte").Component<DockedDateTimePickerProps, {
3
+ show: () => void;
4
+ close: () => void;
5
+ }, "element" | "value" | "open" | "displayMonth">;
3
6
  type DockedDateTimePicker = ReturnType<typeof DockedDateTimePicker>;
4
7
  export default DockedDateTimePicker;
@@ -2,41 +2,15 @@
2
2
  import CheckIcon from '../icons/CheckIcon.svelte'
3
3
  import { arrowKeyNav } from '../keyboard-nav.js'
4
4
  import Ripple from '../ripple/Ripple.svelte'
5
- import type { Attachment } from 'svelte/attachments'
6
- import type { HTMLAttributes } from 'svelte/elements'
5
+ import { revealSelected, tabStop } from './optionList.js'
6
+ import type { TimeColumnProps } from './types.js'
7
7
 
8
- interface SelectionOption {
9
- value: number
10
- label: string
11
- disabled?: boolean
12
- }
13
-
14
- interface SelectionListProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onselect'> {
15
- options: SelectionOption[]
16
- value?: number
17
- onselect?: (value: number) => void
18
- }
19
-
20
- let { options, value, onselect, ...attributes }: SelectionListProps = $props()
8
+ let { options, value, onselect, ...attributes }: TimeColumnProps = $props()
21
9
 
22
10
  const handleKeydown = arrowKeyNav('[role="option"]')
11
+ const reveal = revealSelected(true)
23
12
 
24
- let tabStopValue = $derived.by(() => {
25
- if (options.some((option) => option.value === value)) return value
26
- const offered = options.filter((option) => !option.disabled)
27
- const pool = offered.length ? offered : options
28
- if (!pool.length) return undefined
29
- if (value === undefined) return pool[0].value
30
- return pool.reduce((nearest, option) =>
31
- Math.abs(option.value - value) < Math.abs(nearest.value - value) ? option : nearest,
32
- ).value
33
- })
34
-
35
- const centerInList: Attachment<HTMLElement> = (element) => {
36
- const list = element.closest<HTMLElement>('.np-date-picker-selection-list')
37
- if (!list) return
38
- list.scrollTop = element.offsetTop - list.clientHeight / 2 + element.offsetHeight / 2
39
- }
13
+ let tabStopValue = $derived(tabStop(options, value))
40
14
  </script>
41
15
 
42
16
  <div
@@ -49,7 +23,7 @@
49
23
  {#each options as option (option.value)}
50
24
  {@const selected = option.value === value}
51
25
  <button
52
- {@attach selected && centerInList}
26
+ {@attach selected && reveal}
53
27
  type="button"
54
28
  role="option"
55
29
  aria-selected={selected}
@@ -1,14 +1,4 @@
1
- import type { HTMLAttributes } from 'svelte/elements';
2
- interface SelectionOption {
3
- value: number;
4
- label: string;
5
- disabled?: boolean;
6
- }
7
- interface SelectionListProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onselect'> {
8
- options: SelectionOption[];
9
- value?: number;
10
- onselect?: (value: number) => void;
11
- }
12
- declare const SelectionList: import("svelte").Component<SelectionListProps, {}, "">;
1
+ import type { TimeColumnProps } from './types.ts';
2
+ declare const SelectionList: import("svelte").Component<TimeColumnProps, {}, "">;
13
3
  type SelectionList = ReturnType<typeof SelectionList>;
14
4
  export default SelectionList;
@@ -1,40 +1,15 @@
1
1
  <script lang="ts">
2
2
  import { arrowKeyNav } from '../keyboard-nav.js'
3
3
  import Ripple from '../ripple/Ripple.svelte'
4
- import type { Attachment } from 'svelte/attachments'
4
+ import { revealSelected, tabStop } from './optionList.js'
5
5
  import type { TimeColumnProps } from './types.js'
6
6
 
7
7
  let { options, value, onselect, ...attributes }: TimeColumnProps = $props()
8
8
 
9
9
  const handleKeydown = arrowKeyNav('[role="option"]')
10
+ const reveal = revealSelected()
10
11
 
11
- let tabStopValue = $derived.by(() => {
12
- if (options.some((option) => option.value === value)) return value
13
- const offered = options.filter((option) => !option.disabled)
14
- const pool = offered.length ? offered : options
15
- if (!pool.length) return undefined
16
- if (value === undefined) return pool[0].value
17
- return pool.reduce((nearest, option) =>
18
- Math.abs(option.value - value) < Math.abs(nearest.value - value) ? option : nearest,
19
- ).value
20
- })
21
-
22
- let centred = false
23
-
24
- const revealSelected: Attachment<HTMLElement> = (element) => {
25
- const column = element.parentElement
26
- if (!column) return
27
- const top = element.offsetTop
28
- const bottom = top + element.offsetHeight
29
- if (!centred) {
30
- centred = true
31
- column.scrollTop = top - column.clientHeight / 2 + element.offsetHeight / 2
32
- } else if (top < column.scrollTop) {
33
- column.scrollTop = top
34
- } else if (bottom > column.scrollTop + column.clientHeight) {
35
- column.scrollTop = bottom - column.clientHeight
36
- }
37
- }
12
+ let tabStopValue = $derived(tabStop(options, value))
38
13
  </script>
39
14
 
40
15
  <div
@@ -47,7 +22,7 @@
47
22
  {#each options as option (option.value)}
48
23
  {@const selected = option.value === value}
49
24
  <button
50
- {@attach selected && revealSelected}
25
+ {@attach selected && reveal}
51
26
  type="button"
52
27
  role="option"
53
28
  aria-selected={selected}
@@ -0,0 +1,5 @@
1
+ export declare const exitVisibility: () => {
2
+ readonly visible: boolean;
3
+ show: () => void;
4
+ scheduleExit: (element: Element | undefined, onDone?: () => void) => void;
5
+ };
@@ -0,0 +1,23 @@
1
+ import { afterExit } from '../animation.js';
2
+ export const exitVisibility = () => {
3
+ let visible = $state(false);
4
+ let cancelExit;
5
+ $effect(() => () => cancelExit?.());
6
+ return {
7
+ get visible() {
8
+ return visible;
9
+ },
10
+ show: () => {
11
+ cancelExit?.();
12
+ cancelExit = undefined;
13
+ visible = true;
14
+ },
15
+ scheduleExit: (element, onDone) => {
16
+ cancelExit = afterExit(element, () => {
17
+ visible = false;
18
+ onDone?.();
19
+ cancelExit = undefined;
20
+ });
21
+ },
22
+ };
23
+ };
@@ -0,0 +1,4 @@
1
+ import type { Attachment } from 'svelte/attachments';
2
+ import type { TimeOption } from './types.ts';
3
+ export declare const tabStop: (options: TimeOption[], value: number | undefined) => number | undefined;
4
+ export declare const revealSelected: (centre?: boolean) => Attachment<HTMLElement>;
@@ -0,0 +1,31 @@
1
+ export const tabStop = (options, value) => {
2
+ if (options.some((option) => option.value === value))
3
+ return value;
4
+ const offered = options.filter((option) => !option.disabled);
5
+ const pool = offered.length ? offered : options;
6
+ if (!pool.length)
7
+ return undefined;
8
+ if (value === undefined)
9
+ return pool[0].value;
10
+ return pool.reduce((nearest, option) => Math.abs(option.value - value) < Math.abs(nearest.value - value) ? option : nearest).value;
11
+ };
12
+ export const revealSelected = (centre = false) => {
13
+ let centred = false;
14
+ return (element) => {
15
+ const list = element.parentElement;
16
+ if (!list)
17
+ return;
18
+ const top = element.offsetTop;
19
+ const bottom = top + element.offsetHeight;
20
+ if (centre || !centred) {
21
+ centred = true;
22
+ list.scrollTop = top - list.clientHeight / 2 + element.offsetHeight / 2;
23
+ }
24
+ else if (top < list.scrollTop) {
25
+ list.scrollTop = top;
26
+ }
27
+ else if (bottom > list.scrollTop + list.clientHeight) {
28
+ list.scrollTop = bottom - list.clientHeight;
29
+ }
30
+ };
31
+ };
@@ -101,11 +101,11 @@ export interface TimeColumnProps extends Omit<HTMLAttributes<HTMLDivElement>, 'o
101
101
  value?: number;
102
102
  onselect?: (value: number) => void;
103
103
  }
104
- export interface DatePickerDialogProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onchange'>, DatePickerLocaleProps, DatePickerRangeProps, DatePickerCommonLabelProps, MonthStepperLabelProps {
104
+ export interface DatePickerDialogProps extends Omit<HTMLAttributes<HTMLDialogElement>, 'onchange'>, DatePickerLocaleProps, DatePickerRangeProps, DatePickerCommonLabelProps, MonthStepperLabelProps {
105
105
  value?: ISODate | null;
106
106
  displayMonth?: ISODate;
107
107
  open?: boolean;
108
- element?: HTMLDivElement;
108
+ element?: HTMLDialogElement;
109
109
  name?: string;
110
110
  form?: string;
111
111
  title?: string;
@@ -113,6 +113,9 @@ export interface DatePickerDialogProps extends Omit<HTMLAttributes<HTMLDivElemen
113
113
  modeToggle?: boolean;
114
114
  label?: string;
115
115
  supportingText?: string;
116
+ issues?: {
117
+ message: string;
118
+ }[];
116
119
  selectYearLabel?: string;
117
120
  calendarModeLabel?: string;
118
121
  inputModeLabel?: string;
@@ -120,11 +123,15 @@ export interface DatePickerDialogProps extends Omit<HTMLAttributes<HTMLDivElemen
120
123
  onconfirm?: (value: ISODate | undefined) => void;
121
124
  oncancel?: () => void;
122
125
  }
123
- export interface DateRangePickerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onchange'>, DatePickerLocaleProps, DatePickerRangeProps, DatePickerCommonLabelProps {
126
+ export interface DateRangePickerProps extends Omit<HTMLAttributes<HTMLDialogElement>, 'onchange'>, DatePickerLocaleProps, DatePickerRangeProps, DatePickerCommonLabelProps {
124
127
  value?: DateRange;
125
128
  open?: boolean;
126
- element?: HTMLDivElement;
129
+ element?: HTMLDialogElement;
127
130
  title?: string;
131
+ headline?: string;
132
+ name?: string;
133
+ endName?: string;
134
+ form?: string;
128
135
  startLabel?: string;
129
136
  endLabel?: string;
130
137
  onchange?: (value: DateRange) => void;