noph-ui 0.46.1 → 0.47.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 (58) hide show
  1. package/README.md +2 -1
  2. package/dist/button/ButtonGroup.svelte +1 -3
  3. package/dist/date-picker/DockedDatePicker.svelte +2 -3
  4. package/dist/date-picker/DockedDateTimePicker.svelte +2 -3
  5. package/dist/date-picker/dateUtils.d.ts +2 -13
  6. package/dist/date-picker/dateUtils.js +4 -66
  7. package/dist/date-picker/formatters.d.ts +4 -0
  8. package/dist/date-picker/formatters.js +26 -0
  9. package/dist/date-picker/timeUtils.d.ts +34 -0
  10. package/dist/date-picker/timeUtils.js +139 -0
  11. package/dist/fab/FabMenu.svelte +1 -2
  12. package/dist/form-reset.d.ts +2 -1
  13. package/dist/form-reset.js +2 -2
  14. package/dist/icons/KeyboardIcon.svelte +5 -0
  15. package/dist/icons/KeyboardIcon.svelte.d.ts +26 -0
  16. package/dist/icons/ScheduleIcon.svelte +5 -0
  17. package/dist/icons/ScheduleIcon.svelte.d.ts +26 -0
  18. package/dist/icons/index.d.ts +2 -0
  19. package/dist/icons/index.js +2 -0
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +1 -0
  22. package/dist/keyboard-nav.js +3 -2
  23. package/dist/media.d.ts +4 -0
  24. package/dist/media.js +4 -0
  25. package/dist/menu/Menu.svelte +2 -1
  26. package/dist/progress/CircularProgress.svelte +9 -9
  27. package/dist/progress/LinearProgress.svelte +1 -2
  28. package/dist/ripple/Ripple.svelte +7 -8
  29. package/dist/select/Select.svelte +9 -10
  30. package/dist/select/VirtualList.svelte +12 -13
  31. package/dist/slider/Slider.svelte +5 -6
  32. package/dist/tabs/Tabs.svelte +21 -14
  33. package/dist/time-picker/ClockDial.svelte +528 -0
  34. package/dist/time-picker/ClockDial.svelte.d.ts +4 -0
  35. package/dist/time-picker/DockedTimePicker.svelte +378 -0
  36. package/dist/time-picker/DockedTimePicker.svelte.d.ts +7 -0
  37. package/dist/time-picker/PeriodSelector.svelte +156 -0
  38. package/dist/time-picker/PeriodSelector.svelte.d.ts +4 -0
  39. package/dist/time-picker/TimeInputs.svelte +233 -0
  40. package/dist/time-picker/TimeInputs.svelte.d.ts +4 -0
  41. package/dist/time-picker/TimePickerDialog.svelte +229 -0
  42. package/dist/time-picker/TimePickerDialog.svelte.d.ts +7 -0
  43. package/dist/time-picker/TimePickerPanel.svelte +203 -0
  44. package/dist/time-picker/TimePickerPanel.svelte.d.ts +4 -0
  45. package/dist/time-picker/TimeSelectors.svelte +157 -0
  46. package/dist/time-picker/TimeSelectors.svelte.d.ts +4 -0
  47. package/dist/time-picker/index.d.ts +4 -0
  48. package/dist/time-picker/index.js +4 -0
  49. package/dist/time-picker/timePickerState.svelte.d.ts +27 -0
  50. package/dist/time-picker/timePickerState.svelte.js +63 -0
  51. package/dist/time-picker/types.d.ts +148 -0
  52. package/dist/time-picker/types.js +1 -0
  53. package/dist/tooltip/Tooltip.svelte +2 -4
  54. package/dist/types.d.ts +1 -0
  55. package/dist/types.js +1 -0
  56. package/dist/validity.d.ts +2 -0
  57. package/dist/validity.js +4 -0
  58. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
- import { MediaQuery } from 'svelte/reactivity'
2
+ import { reducedMotion } from '../media.js'
3
+ import type { Attachment } from 'svelte/attachments'
3
4
  import type { CircularProgressProps } from './types.js'
4
5
 
5
6
  let {
@@ -44,15 +45,13 @@
44
45
  return d + 'Z'
45
46
  }
46
47
 
47
- let waveEl = $state<SVGPathElement>()
48
48
  const wavePath = buildWavePath(0, DEFAULT_STROKE_PERCENT)
49
49
 
50
- const reducedMotion = new MediaQuery('(prefers-reduced-motion: reduce)', false)
51
50
  let wave = $derived(wavy && !reducedMotion.current)
52
51
 
53
- $effect(() => {
54
- if (!wave || !waveEl) return
55
- const path = waveEl
52
+ // Attached to the path itself, so the loop starts and stops with the node the two branches render
53
+ // rather than racing a shared `bind:this` when the determinate branch swaps.
54
+ const animateWave: Attachment<SVGPathElement> = (path) => {
56
55
  const strokePercent =
57
56
  parseFloat(
58
57
  getComputedStyle(path).getPropertyValue('--np-circular-progress-active-indicator-width'),
@@ -64,7 +63,7 @@
64
63
  }
65
64
  raf = requestAnimationFrame(frame)
66
65
  return () => cancelAnimationFrame(raf)
67
- })
66
+ }
68
67
 
69
68
  const clamp = (n: number) => Math.min(100, Math.max(0, n))
70
69
  let activeLength = $derived(clamp((value / max) * 100))
@@ -93,7 +92,7 @@
93
92
  {/if}
94
93
  {#if wave}
95
94
  <path
96
- bind:this={waveEl}
95
+ {@attach animateWave}
97
96
  class="active-track spinner-arc wave"
98
97
  d={wavePath}
99
98
  pathLength="100"
@@ -108,7 +107,8 @@
108
107
  <circle class="track" pathLength="100"></circle>
109
108
  {/if}
110
109
  {#if wave}
111
- <path bind:this={waveEl} class="active-track wave" d={wavePath} pathLength="100"></path>
110
+ <path {@attach animateWave} class="active-track wave" d={wavePath} pathLength="100"
111
+ ></path>
112
112
  {:else}
113
113
  <circle class="active-track" pathLength="100"></circle>
114
114
  {/if}
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { MediaQuery } from 'svelte/reactivity'
2
+ import { reducedMotion } from '../media.js'
3
3
  import type { LinearProgressProps } from './types.js'
4
4
 
5
5
  let {
@@ -21,7 +21,6 @@
21
21
  const DETERMINATE_WAVELENGTH = 40
22
22
  const INDETERMINATE_WAVELENGTH = 20
23
23
 
24
- const reducedMotion = new MediaQuery('(prefers-reduced-motion: reduce)', false)
25
24
  let wave = $derived(wavy && !reducedMotion.current)
26
25
 
27
26
  let wavelength = $derived(indeterminate ? INDETERMINATE_WAVELENGTH : DETERMINATE_WAVELENGTH)
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
+ import { forcedColors, reducedMotion } from '../media.js'
3
+ import type { Attachment } from 'svelte/attachments'
2
4
  import { on } from 'svelte/events'
3
- import { MediaQuery } from 'svelte/reactivity'
4
5
  import type { RippleProps } from './types.js'
5
6
 
6
7
  let {
@@ -23,8 +24,6 @@
23
24
  const EASING_STANDARD = 'cubic-bezier(0.2, 0, 0, 1)'
24
25
  const TOUCH_DELAY_MS = 150
25
26
 
26
- const reducedMotion = new MediaQuery('(prefers-reduced-motion: reduce)', false)
27
-
28
27
  let rippleSize = $state('')
29
28
  let rippleScale = $state('')
30
29
  let initialSize = $state(0)
@@ -280,14 +279,13 @@
280
279
  }
281
280
  }
282
281
 
283
- $effect(() => {
284
- const forcedColors = window?.matchMedia('(forced-colors: active)')
285
- if (forcedColors.matches) return
286
- const target = forElement ?? element?.parentElement
282
+ const listen: Attachment<HTMLElement> = (node) => {
283
+ if (forcedColors.current) return
284
+ const target = forElement ?? node.parentElement
287
285
  if (target) {
288
286
  return addEvents(target)
289
287
  }
290
- })
288
+ }
291
289
  </script>
292
290
 
293
291
  <div
@@ -300,6 +298,7 @@
300
298
  attributes.class,
301
299
  ]}
302
300
  bind:this={element}
301
+ {@attach listen}
303
302
  ></div>
304
303
 
305
304
  <style>
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { onFormReset } from '../form-reset.js'
2
+ import { formReset } from '../form-reset.js'
3
3
  import ArrowDropDownIcon from '../icons/ArrowDropDownIcon.svelte'
4
4
  import Item from '../list/Item.svelte'
5
5
  import Menu from '../menu/Menu.svelte'
@@ -63,15 +63,12 @@
63
63
  let menuElement = $state<HTMLDivElement>()
64
64
  let anchorElement = $state<HTMLDivElement>()
65
65
 
66
- $effect(() =>
67
- onFormReset(selectElement, () => {
68
- queueMicrotask(() => {
69
- value = multiple
70
- ? options.filter((option) => option.selected).map((option) => option.value)
71
- : options.find((option) => option.selected)?.value
72
- })
73
- }),
74
- )
66
+ const restoreDefault = () =>
67
+ queueMicrotask(() => {
68
+ value = multiple
69
+ ? options.filter((option) => option.selected).map((option) => option.value)
70
+ : options.find((option) => option.selected)?.value
71
+ })
75
72
 
76
73
  let field = $state<HTMLDivElement>()
77
74
  let menuOpen = $state(false)
@@ -421,6 +418,7 @@
421
418
  {oninput}
422
419
  bind:value
423
420
  bind:this={selectElement}
421
+ {@attach formReset(restoreDefault)}
424
422
  >
425
423
  {#each selectedOption as option, index (index)}
426
424
  <option class="np-option" value={option.value} selected={option.selected}
@@ -440,6 +438,7 @@
440
438
  {oninput}
441
439
  bind:value
442
440
  bind:this={selectElement}
441
+ {@attach formReset(restoreDefault)}
443
442
  >
444
443
  {#each selectedOption as option, index (index)}
445
444
  <option value={option.value} selected={option.selected}>{option.label}</option>
@@ -1,5 +1,6 @@
1
1
  <script lang="ts" generics="T">
2
- import { onMount, tick, type Snippet } from 'svelte'
2
+ import { tick, type Snippet } from 'svelte'
3
+ import type { Attachment } from 'svelte/attachments'
3
4
  import type { HTMLAttributes } from 'svelte/elements'
4
5
 
5
6
  interface VirtualListProps extends HTMLAttributes<HTMLDivElement> {
@@ -28,16 +29,19 @@
28
29
 
29
30
  let rows: HTMLCollectionOf<HTMLElement> | undefined = $state()
30
31
  let viewport: HTMLElement | undefined = $state()
31
- let contents: HTMLDivElement | undefined = $state()
32
32
  let viewport_height: number = $state(0)
33
- let mounted = $state()
34
33
 
35
34
  let top = $state(0)
36
35
  let bottom = $state(0)
37
- let average_height: number = $derived(itemHeight || 0)
36
+ let measured_height = $state(0)
37
+ let average_height: number = $derived(itemHeight || measured_height)
38
+
39
+ const collectRows: Attachment<HTMLDivElement> = (node) => {
40
+ rows = node.children as HTMLCollectionOf<HTMLElement>
41
+ }
38
42
 
39
43
  $effect(() => {
40
- if (mounted) {
44
+ if (rows) {
41
45
  refresh(items, viewport_height, itemHeight)
42
46
  }
43
47
  })
@@ -77,7 +81,7 @@
77
81
  end = i
78
82
 
79
83
  const remaining = items.length - end
80
- average_height = end ? (top + content_height) / end : average_height || itemHeight || 0
84
+ measured_height = end ? (top + content_height) / end : average_height || itemHeight || 0
81
85
 
82
86
  bottom = remaining * average_height
83
87
  height_map.length = items.length
@@ -134,7 +138,7 @@
134
138
  end = i
135
139
 
136
140
  const remaining = items.length - end
137
- average_height = end ? y / end : average_height || itemHeight || 0
141
+ measured_height = end ? y / end : average_height || itemHeight || 0
138
142
 
139
143
  height_map.fill(average_height, i, items.length)
140
144
  bottom = remaining * average_height
@@ -158,11 +162,6 @@
158
162
  await tick()
159
163
  rendered?.({ start, end })
160
164
  }
161
-
162
- onMount(() => {
163
- rows = contents?.children as HTMLCollectionOf<HTMLElement>
164
- mounted = true
165
- })
166
165
  </script>
167
166
 
168
167
  <svelte-virtual-list-viewport
@@ -172,7 +171,7 @@
172
171
  tabindex="-1"
173
172
  style="height: {height};"
174
173
  >
175
- <div bind:this={contents} style="padding-top: {top}px; padding-bottom: {bottom}px;">
174
+ <div {@attach collectRows} style="padding-top: {top}px; padding-bottom: {bottom}px;">
176
175
  {#each visible as entry (entry.index)}
177
176
  {@render row(entry.data, entry.index)}
178
177
  {/each}
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import type { Attachment } from 'svelte/attachments'
2
3
  import { popoverController, syncOpenEffect } from '../popover.svelte.js'
3
4
  import type { SliderProps } from './types.js'
4
5
 
@@ -107,7 +108,6 @@
107
108
  let dragging = $state<'start' | 'end' | null>(null)
108
109
  let tracking = $state(false)
109
110
  let trackElement = $state<HTMLDivElement>()
110
- let activeLaneElement = $state<HTMLDivElement>()
111
111
  let iconElement = $state<HTMLSpanElement>()
112
112
  let iconFits = $state(true)
113
113
  let pointerFocused = $state(false)
@@ -133,9 +133,8 @@
133
133
  bindLabel(() => startLabelElement, 'start')
134
134
  bindLabel(() => endLabelElement, 'end')
135
135
 
136
- $effect(() => {
137
- if (!icon || !activeLaneElement) return
138
- const lane = activeLaneElement
136
+ const fitIcon: Attachment<HTMLDivElement> = (lane) => {
137
+ if (!icon) return
139
138
  const inlineOf = (r: DOMRect) => (orientation === 'vertical' ? r.height : r.width)
140
139
  const measure = () => {
141
140
  if (!iconElement) return
@@ -148,7 +147,7 @@
148
147
  observer.observe(lane)
149
148
  measure()
150
149
  return () => observer.disconnect()
151
- })
150
+ }
152
151
 
153
152
  const fractionFromEvent = (e: PointerEvent) => {
154
153
  if (!trackElement) return 0
@@ -279,7 +278,7 @@
279
278
  <span class="np-slider-stop np-slider-stop-start"></span>
280
279
  </div>
281
280
  {/if}
282
- <div class="np-slider-lane np-slider-active" bind:this={activeLaneElement}>
281
+ <div class="np-slider-lane np-slider-active" {@attach fitIcon}>
283
282
  {#if icon && iconFits}
284
283
  <span class="np-slider-icon" bind:this={iconElement}>{@render icon()}</span>
285
284
  {/if}
@@ -1,5 +1,4 @@
1
1
  <script lang="ts">
2
- import { untrack } from 'svelte'
3
2
  import Divider from '../divider/Divider.svelte'
4
3
  import { arrowKeyNav, rovingTabindex } from '../keyboard-nav.js'
5
4
  import { setTabsContext } from './context.js'
@@ -13,19 +12,27 @@
13
12
  ...attributes
14
13
  }: TabsProps = $props()
15
14
 
16
- let tabsContext = $state<TabsContext>({
17
- value,
18
- indicatorValue: value,
19
- variant: untrack(() => variant),
20
- })
21
- $effect(() => {
22
- value = tabsContext.value
23
- })
24
-
25
- $effect(() => {
26
- tabsContext.value = value
27
- tabsContext.variant = variant
28
- })
15
+ let indicatorValue = $state(value)
16
+ const tabsContext: TabsContext = {
17
+ get value() {
18
+ return value
19
+ },
20
+ set value(next) {
21
+ value = next
22
+ },
23
+ get indicatorValue() {
24
+ return indicatorValue
25
+ },
26
+ set indicatorValue(next) {
27
+ indicatorValue = next
28
+ },
29
+ get variant() {
30
+ return variant
31
+ },
32
+ set variant(next) {
33
+ variant = next
34
+ },
35
+ }
29
36
  setTabsContext(tabsContext)
30
37
 
31
38
  const secondaryStyle = $derived(