noph-ui 0.35.0 → 0.37.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 (91) hide show
  1. package/README.md +14 -13
  2. package/dist/autocomplete/AutoComplete.svelte +1 -1
  3. package/dist/autocomplete/AutoComplete.svelte.d.ts +1 -1
  4. package/dist/autocomplete/types.d.ts +1 -1
  5. package/dist/button/Button.svelte +1 -2
  6. package/dist/button/IconButton.svelte +1 -1
  7. package/dist/button/SegmentedButton.svelte +1 -1
  8. package/dist/card/Card.svelte +1 -1
  9. package/dist/checkbox/Checkbox.svelte +1 -1
  10. package/dist/chip/AssistChip.svelte +1 -1
  11. package/dist/chip/ChipSet.svelte +1 -1
  12. package/dist/chip/FilterChip.svelte +1 -1
  13. package/dist/chip/FilterChip.svelte.d.ts +1 -1
  14. package/dist/chip/InputChip.svelte +1 -3
  15. package/dist/chip/types.d.ts +1 -1
  16. package/dist/date-picker/Calendar.svelte +487 -0
  17. package/dist/date-picker/Calendar.svelte.d.ts +4 -0
  18. package/dist/date-picker/CalendarHeader.svelte +229 -0
  19. package/dist/date-picker/CalendarHeader.svelte.d.ts +27 -0
  20. package/dist/date-picker/DatePickerDialog.svelte +481 -0
  21. package/dist/date-picker/DatePickerDialog.svelte.d.ts +4 -0
  22. package/dist/date-picker/DateRangePicker.svelte +351 -0
  23. package/dist/date-picker/DateRangePicker.svelte.d.ts +4 -0
  24. package/dist/date-picker/DockedDatePicker.svelte +518 -5
  25. package/dist/date-picker/DockedDatePicker.svelte.d.ts +3 -17
  26. package/dist/date-picker/SelectionList.svelte +148 -0
  27. package/dist/date-picker/SelectionList.svelte.d.ts +14 -0
  28. package/dist/date-picker/YearGrid.svelte +195 -0
  29. package/dist/date-picker/YearGrid.svelte.d.ts +11 -0
  30. package/dist/date-picker/dateUtils.d.ts +41 -0
  31. package/dist/date-picker/dateUtils.js +153 -0
  32. package/dist/date-picker/enterAnimation.d.ts +1 -0
  33. package/dist/date-picker/enterAnimation.js +10 -0
  34. package/dist/date-picker/index.d.ts +6 -0
  35. package/dist/date-picker/index.js +6 -0
  36. package/dist/date-picker/types.d.ts +107 -0
  37. package/dist/date-picker/types.js +1 -0
  38. package/dist/dialog/Dialog.svelte +17 -12
  39. package/dist/dialog/types.d.ts +1 -1
  40. package/dist/divider/Divider.svelte +1 -1
  41. package/dist/icons/ArrowDropDownIcon.svelte +3 -0
  42. package/dist/icons/ArrowDropDownIcon.svelte.d.ts +26 -0
  43. package/dist/icons/ChevronLeftIcon.svelte +3 -0
  44. package/dist/icons/ChevronLeftIcon.svelte.d.ts +26 -0
  45. package/dist/icons/ChevronRightIcon.svelte +3 -0
  46. package/dist/icons/ChevronRightIcon.svelte.d.ts +26 -0
  47. package/dist/icons/EditCalendarIcon.svelte +5 -0
  48. package/dist/icons/EditCalendarIcon.svelte.d.ts +26 -0
  49. package/dist/icons/index.d.ts +5 -1
  50. package/dist/icons/index.js +5 -1
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.js +2 -0
  53. package/dist/list/Item.svelte +1 -1
  54. package/dist/list/List.svelte +1 -1
  55. package/dist/list/ListItem.svelte +1 -1
  56. package/dist/loading-indicator/LoadingIndicator.svelte +135 -0
  57. package/dist/loading-indicator/LoadingIndicator.svelte.d.ts +4 -0
  58. package/dist/loading-indicator/index.d.ts +1 -0
  59. package/dist/loading-indicator/index.js +1 -0
  60. package/dist/loading-indicator/shapes.d.ts +6 -0
  61. package/dist/loading-indicator/shapes.js +49 -0
  62. package/dist/loading-indicator/types.d.ts +7 -0
  63. package/dist/loading-indicator/types.js +1 -0
  64. package/dist/menu/Menu.svelte +3 -3
  65. package/dist/menu/MenuItem.svelte +1 -1
  66. package/dist/menu/types.d.ts +1 -1
  67. package/dist/navigation-drawer/NavigationDrawer.svelte +1 -1
  68. package/dist/navigation-drawer/NavigationDrawerItem.svelte +2 -2
  69. package/dist/navigation-rail/NavigationRail.svelte +1 -1
  70. package/dist/navigation-rail/NavigationRailItem.svelte +1 -1
  71. package/dist/progress/CircularProgress.svelte +1 -1
  72. package/dist/progress/LinearProgress.svelte +24 -7
  73. package/dist/radio/Radio.svelte +1 -1
  74. package/dist/ripple/Ripple.svelte +1 -1
  75. package/dist/select/Option.svelte +0 -1
  76. package/dist/select/Select.svelte +1 -1
  77. package/dist/select/Select.svelte.d.ts +1 -1
  78. package/dist/select/VirtualList.svelte +3 -4
  79. package/dist/slider/Slider.svelte +83 -19
  80. package/dist/slider/Slider.svelte.d.ts +1 -1
  81. package/dist/snackbar/Snackbar.svelte +3 -3
  82. package/dist/switch/Switch.svelte +1 -1
  83. package/dist/tabs/Tab.svelte +1 -1
  84. package/dist/tabs/Tabs.svelte +1 -1
  85. package/dist/tabs/Tabs.svelte.d.ts +1 -1
  86. package/dist/text-field/TextField.svelte +3 -3
  87. package/dist/text-field/TextField.svelte.d.ts +1 -1
  88. package/dist/tooltip/Tooltip.svelte +1 -1
  89. package/dist/types.d.ts +2 -0
  90. package/dist/types.js +23 -21
  91. package/package.json +14 -11
@@ -0,0 +1,351 @@
1
+ <script lang="ts">
2
+ import Button from '../button/Button.svelte'
3
+ import IconButton from '../button/IconButton.svelte'
4
+ import Dialog from '../dialog/Dialog.svelte'
5
+ import Divider from '../divider/Divider.svelte'
6
+ import CloseIcon from '../icons/CloseIcon.svelte'
7
+ import { tick } from 'svelte'
8
+ import Calendar from './Calendar.svelte'
9
+ import {
10
+ clampDate,
11
+ compareDays,
12
+ DEFAULT_YEAR_RANGE,
13
+ formatDateMedium,
14
+ getFirstDayOfWeek,
15
+ getWeekdayLabels,
16
+ isWithin,
17
+ parseISODate,
18
+ startOfDay,
19
+ startOfMonth,
20
+ toISODate,
21
+ today as getToday,
22
+ } from './dateUtils.js'
23
+ import type { DateRange, DateRangePickerProps } from './types.js'
24
+
25
+ let {
26
+ value = $bindable({}),
27
+ open = $bindable(false),
28
+ element = $bindable(),
29
+ locale,
30
+ firstDayOfWeek,
31
+ min,
32
+ max,
33
+ yearRange = DEFAULT_YEAR_RANGE,
34
+ isDateEnabled,
35
+ adjacentMonthDays = false,
36
+ title = 'Select range',
37
+ startLabel = 'Start date',
38
+ endLabel = 'End date',
39
+ cancelLabel = 'Cancel',
40
+ confirmLabel = 'Save',
41
+ selectedDateLabel = 'selected',
42
+ onchange,
43
+ onconfirm,
44
+ oncancel,
45
+ ...attributes
46
+ }: DateRangePickerProps = $props()
47
+
48
+ const MONTHS_PER_PAGE = 4
49
+
50
+ let scroller = $state<HTMLDivElement>()
51
+ let pending = $state.raw<DateRange>({})
52
+ let focusedDay = $state<Date | undefined>(undefined)
53
+ let confirming = false
54
+
55
+ let todayValue = $derived(getToday())
56
+ let week = $derived(firstDayOfWeek ?? getFirstDayOfWeek(locale))
57
+ let minDate = $derived(parseISODate(min))
58
+ let maxDate = $derived(parseISODate(max))
59
+ let startDate = $derived(parseISODate(pending.start))
60
+ let endDate = $derived(parseISODate(pending.end))
61
+ let weekdayLabels = $derived(getWeekdayLabels(locale, week))
62
+
63
+ const monthIndex = (date: Date) => date.getFullYear() * 12 + date.getMonth()
64
+ const monthFromIndex = (index: number) => new Date(Math.floor(index / 12), index % 12, 1)
65
+
66
+ let lowerDate = $derived(minDate ?? new Date(yearRange[0], 0, 1))
67
+ let upperDate = $derived(maxDate ?? new Date(yearRange[1], 11, 31))
68
+ let lowerBound = $derived(monthIndex(lowerDate))
69
+ let upperBound = $derived(monthIndex(upperDate))
70
+
71
+ const isSelectable = (date: Date) =>
72
+ isWithin(date, minDate, maxDate) && (isDateEnabled?.(date) ?? true)
73
+
74
+ let canConfirm = $derived(
75
+ !!startDate && isSelectable(startDate) && (!endDate || isSelectable(endDate)),
76
+ )
77
+
78
+ let firstMonth = $state(0)
79
+ let lastMonth = $state(-1)
80
+
81
+ let months = $derived(
82
+ Array.from({ length: Math.max(0, lastMonth - firstMonth + 1) }, (_, index) =>
83
+ monthFromIndex(firstMonth + index),
84
+ ),
85
+ )
86
+
87
+ let tabStopDate = $derived.by(() => {
88
+ const preferred = focusedDay ?? startDate ?? todayValue
89
+ const index = monthIndex(preferred)
90
+ if (index >= firstMonth && index <= lastMonth) return preferred
91
+ return monthFromIndex(Math.min(Math.max(index, firstMonth), lastMonth))
92
+ })
93
+
94
+ const anchorMonth = () => {
95
+ const anchor = monthIndex(startOfMonth(startDate ?? todayValue))
96
+ return Math.min(Math.max(anchor, lowerBound), upperBound)
97
+ }
98
+
99
+ const resetWindow = () => {
100
+ const anchor = anchorMonth()
101
+ firstMonth = Math.max(lowerBound, anchor - MONTHS_PER_PAGE)
102
+ lastMonth = Math.min(upperBound, anchor + MONTHS_PER_PAGE)
103
+ }
104
+
105
+ const scrollToAnchor = async () => {
106
+ await tick()
107
+ const target = scroller?.querySelector<HTMLElement>(`[data-month="${anchorMonth()}"]`)
108
+ if (target && scroller) scroller.scrollTop = target.offsetTop
109
+ }
110
+
111
+ const isOpen = () => !!element?.matches(':popover-open')
112
+
113
+ $effect(() => {
114
+ if (open && !isOpen()) element?.showPopover()
115
+ if (!open && isOpen()) element?.hidePopover()
116
+ })
117
+
118
+ let prepending = false
119
+
120
+ const handleScroll = () => {
121
+ if (!scroller || prepending) return
122
+ const { scrollTop, scrollHeight, clientHeight } = scroller
123
+ if (scrollTop < clientHeight && firstMonth > lowerBound) {
124
+ prepending = true
125
+ const previousHeight = scrollHeight
126
+ firstMonth = Math.max(lowerBound, firstMonth - MONTHS_PER_PAGE)
127
+ requestAnimationFrame(() => {
128
+ if (scroller) scroller.scrollTop += scroller.scrollHeight - previousHeight
129
+ prepending = false
130
+ })
131
+ } else if (scrollHeight - scrollTop - clientHeight < clientHeight && lastMonth < upperBound) {
132
+ lastMonth = Math.min(upperBound, lastMonth + MONTHS_PER_PAGE)
133
+ }
134
+ }
135
+
136
+ const selectDay = (date: Date) => {
137
+ const iso = toISODate(date)
138
+ if (!startDate || endDate || compareDays(date, startDate) < 0) {
139
+ pending = { start: iso }
140
+ } else {
141
+ pending = { start: pending.start, end: iso }
142
+ }
143
+ focusedDay = date
144
+ }
145
+
146
+ const focusDay = (date: Date) => {
147
+ const target = startOfDay(clampDate(date, lowerDate, upperDate))
148
+ focusedDay = target
149
+ const index = monthIndex(target)
150
+ if (index < firstMonth) firstMonth = index
151
+ if (index > lastMonth) lastMonth = index
152
+ }
153
+
154
+ const cancel = () => {
155
+ open = false
156
+ }
157
+
158
+ const confirm = () => {
159
+ confirming = true
160
+ value = pending
161
+ onchange?.(pending)
162
+ onconfirm?.(pending)
163
+ open = false
164
+ }
165
+
166
+ let headlineStart = $derived(startDate ? formatDateMedium(startDate, locale) : startLabel)
167
+ let headlineEnd = $derived(endDate ? formatDateMedium(endDate, locale) : endLabel)
168
+ </script>
169
+
170
+ <Dialog
171
+ {...attributes}
172
+ bind:element
173
+ aria-label={title}
174
+ class={['np-date-range-picker', attributes.class]}
175
+ --np-dialog-container-width="100%"
176
+ --np-dialog-container-min-width="0"
177
+ --np-dialog-inset="0"
178
+ --np-dialog-padding="0"
179
+ --np-dialog-max-height="100dvh"
180
+ --np-dialog-elevation="none"
181
+ --np-dialog-container-color="var(--np-date-range-picker-container-color, var(--np-color-surface))"
182
+ --np-dialog-container-shape="var(--np-date-range-picker-container-shape, var(--np-shape-corner-none))"
183
+ ontoggle={(event) => {
184
+ const nowOpen = event.newState === 'open'
185
+ open = nowOpen
186
+ if (nowOpen) {
187
+ confirming = false
188
+ pending = { ...value }
189
+ focusedDay = undefined
190
+ resetWindow()
191
+ scrollToAnchor()
192
+ } else {
193
+ if (!confirming) oncancel?.()
194
+ confirming = false
195
+ }
196
+ }}
197
+ >
198
+ {#if open}
199
+ <div class="np-date-range-picker-content">
200
+ <div class="np-date-range-picker-header">
201
+ <div class="np-date-range-picker-actions">
202
+ <IconButton type="button" aria-label={cancelLabel} onclick={cancel}>
203
+ <CloseIcon />
204
+ </IconButton>
205
+ <Button type="button" variant="text" disabled={!canConfirm} onclick={confirm}>
206
+ {confirmLabel}
207
+ </Button>
208
+ </div>
209
+ <h2 class="np-date-range-picker-headline">
210
+ <span class={[!startDate && 'placeholder']}>{headlineStart}</span>
211
+ <span aria-hidden="true">–</span>
212
+ <span class={[!endDate && 'placeholder']}>{headlineEnd}</span>
213
+ </h2>
214
+ </div>
215
+ <Divider />
216
+
217
+ <div class="np-date-range-picker-weekdays" aria-hidden="true">
218
+ {#each weekdayLabels as weekday (weekday.long)}
219
+ <span>{weekday.narrow}</span>
220
+ {/each}
221
+ </div>
222
+
223
+ <div class="np-date-range-picker-months" bind:this={scroller} onscroll={handleScroll}>
224
+ {#each months as month (month.getTime())}
225
+ <Calendar
226
+ {month}
227
+ data-month={monthIndex(month)}
228
+ rangeStart={startDate}
229
+ rangeEnd={endDate}
230
+ min={minDate}
231
+ max={maxDate}
232
+ {locale}
233
+ firstDayOfWeek={week}
234
+ {isDateEnabled}
235
+ {adjacentMonthDays}
236
+ weekdays={false}
237
+ dynamicRows
238
+ todayDate={todayValue}
239
+ focusedDate={focusedDay}
240
+ selectedLabel={selectedDateLabel}
241
+ {tabStopDate}
242
+ focusRoot={scroller}
243
+ onselect={selectDay}
244
+ onfocusday={focusDay}
245
+ >
246
+ {#snippet monthSubhead(monthName)}
247
+ <div class="np-date-range-picker-subhead">{monthName}</div>
248
+ {/snippet}
249
+ </Calendar>
250
+ {/each}
251
+ </div>
252
+ </div>
253
+ {/if}
254
+ </Dialog>
255
+
256
+ <style>
257
+ :global(.np-date-range-picker .np-dialog) {
258
+ height: 100dvh;
259
+ }
260
+
261
+ :global(.np-date-range-picker .np-dialog-scroller) {
262
+ overflow: hidden;
263
+ }
264
+
265
+ .np-date-range-picker-content {
266
+ display: flex;
267
+ flex-direction: column;
268
+ height: 100%;
269
+ min-height: 0;
270
+ }
271
+
272
+ .np-date-range-picker-header,
273
+ .np-date-range-picker-weekdays,
274
+ .np-date-range-picker-months {
275
+ max-width: var(--np-date-range-picker-content-width, 25.5rem);
276
+ margin-inline: auto;
277
+ width: 100%;
278
+ }
279
+
280
+ .np-date-range-picker-header {
281
+ box-sizing: border-box;
282
+ min-height: 8rem;
283
+ display: flex;
284
+ flex-direction: column;
285
+ justify-content: space-between;
286
+ gap: 0.5rem;
287
+ padding: 0.75rem 0.75rem 0.75rem 1rem;
288
+ color: var(--np-color-on-surface-variant);
289
+ }
290
+
291
+ .np-date-range-picker-actions {
292
+ display: flex;
293
+ align-items: center;
294
+ justify-content: space-between;
295
+ }
296
+
297
+ .np-date-range-picker-headline {
298
+ margin: 0;
299
+ display: flex;
300
+ gap: 0.5rem;
301
+ font-size: 1.375rem;
302
+ line-height: 1.75rem;
303
+ font-weight: 400;
304
+ }
305
+
306
+ .np-date-range-picker-headline .placeholder {
307
+ color: var(--np-color-on-surface-variant);
308
+ }
309
+
310
+ .np-date-range-picker-weekdays {
311
+ display: grid;
312
+ grid-template-columns: repeat(7, 1fr);
313
+ justify-items: center;
314
+ align-items: center;
315
+ height: 3rem;
316
+ padding-inline: 0.75rem;
317
+ max-width: 25.5rem;
318
+ margin-inline: auto;
319
+ width: 100%;
320
+ box-sizing: border-box;
321
+ font-size: 1rem;
322
+ line-height: 1.5rem;
323
+ color: var(--np-color-on-surface);
324
+ }
325
+
326
+ .np-date-range-picker-months {
327
+ position: relative;
328
+ flex: 1;
329
+ min-height: 0;
330
+ overflow-y: auto;
331
+ overscroll-behavior: contain;
332
+ padding-inline: 0.75rem;
333
+ max-width: 25.5rem;
334
+ margin-inline: auto;
335
+ width: 100%;
336
+ box-sizing: border-box;
337
+ scrollbar-color: var(--np-color-on-surface-variant) transparent;
338
+ scrollbar-width: thin;
339
+ }
340
+
341
+ .np-date-range-picker-subhead {
342
+ display: flex;
343
+ align-items: center;
344
+ height: 3rem;
345
+ padding-inline: 0.75rem;
346
+ font-size: 0.875rem;
347
+ line-height: 1.25rem;
348
+ font-weight: 500;
349
+ color: var(--np-date-picker-range-month-subhead-color, var(--np-color-on-surface-variant));
350
+ }
351
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { DateRangePickerProps } from './types.ts';
2
+ declare const DateRangePicker: import("svelte").Component<DateRangePickerProps, {}, "value" | "element" | "open">;
3
+ type DateRangePicker = ReturnType<typeof DateRangePicker>;
4
+ export default DateRangePicker;