@lumen-design/date-time-picker 0.0.2
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.
- package/dist/DateTimePicker.svelte +387 -0
- package/dist/index.js +3 -0
- package/dist/types.js +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createDropdownTransition } from '../../utils'
|
|
3
|
+
import { onDestroy, onMount } from 'svelte'
|
|
4
|
+
import Icon from '@lumen-design/icon'
|
|
5
|
+
import { Calendar, ChevronLeft, ChevronRight, X, ChevronDown, ChevronsLeft, ChevronsRight } from 'lucide'
|
|
6
|
+
|
|
7
|
+
import type { DateTimePickerProps } from './types'
|
|
8
|
+
|
|
9
|
+
const pad2 = (n: number): string => String(n).padStart(2, '0')
|
|
10
|
+
|
|
11
|
+
const parseTimeParts = (val: string): { h: string; m: string; s: string } => {
|
|
12
|
+
if (!val) return { h: '00', m: '00', s: '00' }
|
|
13
|
+
const [h = '00', m = '00', s = '00'] = val.split(':')
|
|
14
|
+
return {
|
|
15
|
+
h: h.padStart(2, '0').slice(-2),
|
|
16
|
+
m: m.padStart(2, '0').slice(-2),
|
|
17
|
+
s: s.padStart(2, '0').slice(-2),
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const formatDate = (date: Date, fmt: string): string => {
|
|
22
|
+
const y = date.getFullYear()
|
|
23
|
+
const m = pad2(date.getMonth() + 1)
|
|
24
|
+
const d = pad2(date.getDate())
|
|
25
|
+
return fmt.replace('YYYY', String(y)).replace('MM', m).replace('DD', d)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const formatTime = (date: Date, timeFormat: string): string => {
|
|
29
|
+
const h = pad2(date.getHours())
|
|
30
|
+
const m = pad2(date.getMinutes())
|
|
31
|
+
const s = pad2(date.getSeconds())
|
|
32
|
+
return timeFormat.includes('ss') ? `${h}:${m}:${s}` : `${h}:${m}`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const normalizeTimeInput = (input: string, timeFormat: string): string => {
|
|
36
|
+
const raw = (input ?? '').trim()
|
|
37
|
+
if (!raw) return ''
|
|
38
|
+
const wantSeconds = timeFormat.includes('ss')
|
|
39
|
+
const parts = raw.split(':')
|
|
40
|
+
if (parts.length < 2 || parts.length > 3) return ''
|
|
41
|
+
|
|
42
|
+
const h = Number.parseInt(parts[0] ?? '0', 10)
|
|
43
|
+
const m = Number.parseInt(parts[1] ?? '0', 10)
|
|
44
|
+
const s = Number.parseInt(parts[2] ?? '0', 10)
|
|
45
|
+
if (!Number.isFinite(h) || !Number.isFinite(m) || (!wantSeconds && parts.length === 3 && !Number.isFinite(s))) return ''
|
|
46
|
+
if (h < 0 || h > 23 || m < 0 || m > 59) return ''
|
|
47
|
+
if (wantSeconds && (s < 0 || s > 59)) return ''
|
|
48
|
+
return wantSeconds ? `${pad2(h)}:${pad2(m)}:${pad2(s)}` : `${pad2(h)}:${pad2(m)}`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const toDate = (val: Date | string): Date => (val instanceof Date ? val : new Date(val))
|
|
52
|
+
|
|
53
|
+
const parseDateInput = (input: string): Date | null => {
|
|
54
|
+
const raw = (input ?? '').trim()
|
|
55
|
+
if (!raw) return null
|
|
56
|
+
const parts = raw.replace(/\//g, '-').split('-')
|
|
57
|
+
if (parts.length !== 3) return null
|
|
58
|
+
const y = Number.parseInt(parts[0] ?? '0', 10)
|
|
59
|
+
const m = Number.parseInt(parts[1] ?? '0', 10)
|
|
60
|
+
const d = Number.parseInt(parts[2] ?? '0', 10)
|
|
61
|
+
if (!Number.isFinite(y) || !Number.isFinite(m) || !Number.isFinite(d)) return null
|
|
62
|
+
if (y < 1000 || m < 1 || m > 12 || d < 1 || d > 31) return null
|
|
63
|
+
const dt = new Date(y, m - 1, d)
|
|
64
|
+
// reject overflow dates like 2026-02-31
|
|
65
|
+
if (dt.getFullYear() !== y || dt.getMonth() !== m - 1 || dt.getDate() !== d) return null
|
|
66
|
+
return dt
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const combine = (dateOnly: Date, t: { h: string; m: string; s: string }, timeFormat: string): Date => {
|
|
70
|
+
const next = new Date(dateOnly)
|
|
71
|
+
const h = Number.parseInt(t.h, 10)
|
|
72
|
+
const m = Number.parseInt(t.m, 10)
|
|
73
|
+
const s = Number.parseInt(t.s, 10)
|
|
74
|
+
next.setHours(Number.isFinite(h) ? h : 0, Number.isFinite(m) ? m : 0, timeFormat.includes('ss') && Number.isFinite(s) ? s : 0, 0)
|
|
75
|
+
return next
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let {
|
|
79
|
+
modelValue = $bindable<Date | string | null>(null),
|
|
80
|
+
placeholder = '选择日期时间',
|
|
81
|
+
disabled = false,
|
|
82
|
+
clearable = false,
|
|
83
|
+
readonly = false,
|
|
84
|
+
size = 'default',
|
|
85
|
+
dateFormat = 'YYYY-MM-DD',
|
|
86
|
+
datePlaceholder = '选择日期',
|
|
87
|
+
timeFormat = 'HH:mm:ss',
|
|
88
|
+
defaultTime = '00:00:00',
|
|
89
|
+
timePlaceholder = '选择时间',
|
|
90
|
+
showMenuArrow = true,
|
|
91
|
+
class: cls = '',
|
|
92
|
+
...attrs
|
|
93
|
+
}: DateTimePickerProps = $props()
|
|
94
|
+
|
|
95
|
+
const showSeconds = $derived(timeFormat.includes('ss'))
|
|
96
|
+
|
|
97
|
+
let visible = $state(false)
|
|
98
|
+
let reduceMotion = $state(false)
|
|
99
|
+
let rootRef: HTMLDivElement | null = $state(null)
|
|
100
|
+
|
|
101
|
+
let currentYear = $state(new Date().getFullYear())
|
|
102
|
+
let currentMonth = $state(new Date().getMonth())
|
|
103
|
+
|
|
104
|
+
let tempDate = $state<Date | null>(null)
|
|
105
|
+
let tempDateText = $state('')
|
|
106
|
+
let tempTime = $state('')
|
|
107
|
+
|
|
108
|
+
const dropdownId = `lm-date-time-picker-dropdown-${Math.random().toString(36).slice(2)}`
|
|
109
|
+
|
|
110
|
+
const selectedCommitted = $derived(modelValue ? toDate(modelValue) : null)
|
|
111
|
+
|
|
112
|
+
const displayValue = $derived.by(() => {
|
|
113
|
+
if (!selectedCommitted) return ''
|
|
114
|
+
const dateStr = formatDate(selectedCommitted, dateFormat)
|
|
115
|
+
const timeStr = formatTime(selectedCommitted, timeFormat)
|
|
116
|
+
return `${dateStr} ${timeStr}`
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const weekDays = ['日', '一', '二', '三', '四', '五', '六']
|
|
120
|
+
|
|
121
|
+
const daysInMonth = $derived.by(() => {
|
|
122
|
+
const firstDay = new Date(currentYear, currentMonth, 1)
|
|
123
|
+
const lastDay = new Date(currentYear, currentMonth + 1, 0)
|
|
124
|
+
const days: { date: Date; isCurrentMonth: boolean }[] = []
|
|
125
|
+
const startDay = firstDay.getDay()
|
|
126
|
+
|
|
127
|
+
for (let i = startDay - 1; i >= 0; i--) {
|
|
128
|
+
days.push({ date: new Date(currentYear, currentMonth, -i), isCurrentMonth: false })
|
|
129
|
+
}
|
|
130
|
+
for (let i = 1; i <= lastDay.getDate(); i++) {
|
|
131
|
+
days.push({ date: new Date(currentYear, currentMonth, i), isCurrentMonth: true })
|
|
132
|
+
}
|
|
133
|
+
const remaining = 42 - days.length
|
|
134
|
+
for (let i = 1; i <= remaining; i++) {
|
|
135
|
+
days.push({ date: new Date(currentYear, currentMonth + 1, i), isCurrentMonth: false })
|
|
136
|
+
}
|
|
137
|
+
return days
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
const isSelected = (date: Date): boolean => {
|
|
141
|
+
if (!tempDate) return false
|
|
142
|
+
return date.getFullYear() === tempDate.getFullYear() && date.getMonth() === tempDate.getMonth() && date.getDate() === tempDate.getDate()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const isToday = (date: Date): boolean => {
|
|
146
|
+
const today = new Date()
|
|
147
|
+
return date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const openWithCommittedOrNow = (): void => {
|
|
151
|
+
const base = selectedCommitted ?? new Date()
|
|
152
|
+
tempDate = new Date(base.getFullYear(), base.getMonth(), base.getDate())
|
|
153
|
+
tempDateText = formatDate(tempDate, dateFormat)
|
|
154
|
+
|
|
155
|
+
if (selectedCommitted) tempTime = formatTime(selectedCommitted, timeFormat)
|
|
156
|
+
else tempTime = normalizeTimeInput(defaultTime, timeFormat) || (showSeconds ? '00:00:00' : '00:00')
|
|
157
|
+
|
|
158
|
+
currentYear = base.getFullYear()
|
|
159
|
+
currentMonth = base.getMonth()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const toggle = (): void => {
|
|
163
|
+
if (disabled || readonly) return
|
|
164
|
+
const next = !visible
|
|
165
|
+
visible = next
|
|
166
|
+
if (next) openWithCommittedOrNow()
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const closeAndRevert = (): void => {
|
|
170
|
+
visible = false
|
|
171
|
+
// revert temp state next time open
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const handleKeydown = (e: KeyboardEvent): void => {
|
|
175
|
+
if (e.key === 'Escape') closeAndRevert()
|
|
176
|
+
else if (e.key === 'Enter' || e.key === ' ') toggle()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const handleClickOutside = (e: MouseEvent): void => {
|
|
180
|
+
if (visible && rootRef && !rootRef.contains(e.target as Node)) closeAndRevert()
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const handleClear = (e: MouseEvent): void => {
|
|
184
|
+
e.stopPropagation()
|
|
185
|
+
modelValue = null
|
|
186
|
+
closeAndRevert()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const prevMonth = (): void => {
|
|
190
|
+
if (currentMonth === 0) {
|
|
191
|
+
currentMonth = 11
|
|
192
|
+
currentYear--
|
|
193
|
+
} else {
|
|
194
|
+
currentMonth--
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const prevYear = (): void => {
|
|
199
|
+
currentYear--
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const nextMonth = (): void => {
|
|
203
|
+
if (currentMonth === 11) {
|
|
204
|
+
currentMonth = 0
|
|
205
|
+
currentYear++
|
|
206
|
+
} else {
|
|
207
|
+
currentMonth++
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const nextYear = (): void => {
|
|
212
|
+
currentYear++
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const handleSelectDate = (date: Date): void => {
|
|
216
|
+
tempDate = new Date(date.getFullYear(), date.getMonth(), date.getDate())
|
|
217
|
+
tempDateText = formatDate(tempDate, dateFormat)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const setNow = (): void => {
|
|
221
|
+
const now = new Date()
|
|
222
|
+
tempDate = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
|
223
|
+
tempDateText = formatDate(tempDate, dateFormat)
|
|
224
|
+
|
|
225
|
+
tempTime = formatTime(now, timeFormat)
|
|
226
|
+
|
|
227
|
+
currentYear = now.getFullYear()
|
|
228
|
+
currentMonth = now.getMonth()
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const normalizedDate = $derived.by(() => parseDateInput(tempDateText) ?? tempDate)
|
|
232
|
+
|
|
233
|
+
const isDateValid = $derived.by(() => !!normalizedDate)
|
|
234
|
+
|
|
235
|
+
const isTimeValid = $derived.by(() => {
|
|
236
|
+
const normalized = normalizeTimeInput(tempTime || defaultTime, timeFormat)
|
|
237
|
+
return !!normalized
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
const handleConfirm = (): void => {
|
|
241
|
+
const normalized = normalizeTimeInput(tempTime || defaultTime, timeFormat)
|
|
242
|
+
const dateToUse = normalizedDate
|
|
243
|
+
if (!dateToUse || !normalized) return
|
|
244
|
+
modelValue = combine(dateToUse, parseTimeParts(normalized), timeFormat)
|
|
245
|
+
visible = false
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const dropdownTransition = $derived(createDropdownTransition(reduceMotion))
|
|
249
|
+
|
|
250
|
+
$effect(() => {
|
|
251
|
+
if (visible) document.addEventListener('click', handleClickOutside)
|
|
252
|
+
return () => document.removeEventListener('click', handleClickOutside)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
onMount(() => {
|
|
257
|
+
if (typeof window !== 'undefined') {
|
|
258
|
+
reduceMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches ?? false
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
onDestroy(() => {
|
|
263
|
+
document.removeEventListener('click', handleClickOutside)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
const classes = $derived(['lm-date-time-picker', `lm-date-time-picker--${size}`, disabled && 'is-disabled', cls].filter(Boolean).join(' '))
|
|
267
|
+
</script>
|
|
268
|
+
|
|
269
|
+
<div bind:this={rootRef} class={classes} {...attrs}>
|
|
270
|
+
<div
|
|
271
|
+
class="lm-date-time-picker__trigger"
|
|
272
|
+
class:is-focus={visible}
|
|
273
|
+
role="combobox"
|
|
274
|
+
tabindex={disabled ? -1 : 0}
|
|
275
|
+
aria-controls={dropdownId}
|
|
276
|
+
aria-expanded={visible}
|
|
277
|
+
aria-disabled={disabled}
|
|
278
|
+
onclick={toggle}
|
|
279
|
+
onkeydown={handleKeydown}
|
|
280
|
+
>
|
|
281
|
+
<span class="lm-date-time-picker__prefix">
|
|
282
|
+
<Icon icon={Calendar} size={14} />
|
|
283
|
+
</span>
|
|
284
|
+
<span class="lm-date-time-picker__value" class:is-placeholder={!displayValue}>
|
|
285
|
+
{displayValue || placeholder}
|
|
286
|
+
</span>
|
|
287
|
+
|
|
288
|
+
{#if clearable && modelValue && !disabled}
|
|
289
|
+
<button type="button" class="lm-date-time-picker__clear" onclick={handleClear} aria-label="清除">
|
|
290
|
+
<Icon icon={X} size={14} />
|
|
291
|
+
</button>
|
|
292
|
+
{:else}
|
|
293
|
+
<span class="lm-date-time-picker__arrow" class:is-open={visible} aria-hidden="true">
|
|
294
|
+
<Icon icon={ChevronDown} size={14} />
|
|
295
|
+
</span>
|
|
296
|
+
{/if}
|
|
297
|
+
</div>
|
|
298
|
+
|
|
299
|
+
{#if visible}
|
|
300
|
+
<div id={dropdownId} class="lm-date-time-picker__dropdown" role="dialog" transition:dropdownTransition>
|
|
301
|
+
{#if showMenuArrow}
|
|
302
|
+
<div class="lm-date-time-picker__menu-arrow" aria-hidden="true"></div>
|
|
303
|
+
{/if}
|
|
304
|
+
|
|
305
|
+
<div class="lm-date-time-picker__panel">
|
|
306
|
+
<div class="lm-date-time-picker__date-panel">
|
|
307
|
+
<div class="lm-date-time-picker__time-inputs" aria-label="日期时间输入">
|
|
308
|
+
<input
|
|
309
|
+
class="lm-date-time-picker__date-input"
|
|
310
|
+
type="text"
|
|
311
|
+
value={tempDateText}
|
|
312
|
+
placeholder={datePlaceholder}
|
|
313
|
+
oninput={(e: Event) => (tempDateText = (e.currentTarget as HTMLInputElement).value)}
|
|
314
|
+
onblur={() => {
|
|
315
|
+
const parsed = parseDateInput(tempDateText)
|
|
316
|
+
if (parsed) {
|
|
317
|
+
tempDate = parsed
|
|
318
|
+
tempDateText = formatDate(parsed, dateFormat)
|
|
319
|
+
currentYear = parsed.getFullYear()
|
|
320
|
+
currentMonth = parsed.getMonth()
|
|
321
|
+
}
|
|
322
|
+
}}
|
|
323
|
+
/>
|
|
324
|
+
<input
|
|
325
|
+
class="lm-date-time-picker__time-input"
|
|
326
|
+
type="text"
|
|
327
|
+
value={tempTime}
|
|
328
|
+
placeholder={timePlaceholder}
|
|
329
|
+
oninput={(e: Event) => (tempTime = (e.currentTarget as HTMLInputElement).value)}
|
|
330
|
+
onblur={() => {
|
|
331
|
+
const normalized = normalizeTimeInput(tempTime, timeFormat)
|
|
332
|
+
if (normalized) tempTime = normalized
|
|
333
|
+
}}
|
|
334
|
+
/>
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
<div class="lm-date-time-picker__header">
|
|
338
|
+
<div class="lm-date-time-picker__nav-group" aria-hidden="false">
|
|
339
|
+
<button type="button" class="lm-date-time-picker__nav" onclick={prevYear} aria-label="上一年">
|
|
340
|
+
<Icon icon={ChevronsLeft} size={16} />
|
|
341
|
+
</button>
|
|
342
|
+
<button type="button" class="lm-date-time-picker__nav" onclick={prevMonth} aria-label="上个月">
|
|
343
|
+
<Icon icon={ChevronLeft} size={14} />
|
|
344
|
+
</button>
|
|
345
|
+
</div>
|
|
346
|
+
<span class="lm-date-time-picker__title">{currentYear}年 {currentMonth + 1}月</span>
|
|
347
|
+
<div class="lm-date-time-picker__nav-group" aria-hidden="false">
|
|
348
|
+
<button type="button" class="lm-date-time-picker__nav" onclick={nextMonth} aria-label="下个月">
|
|
349
|
+
<Icon icon={ChevronRight} size={14} />
|
|
350
|
+
</button>
|
|
351
|
+
<button type="button" class="lm-date-time-picker__nav" onclick={nextYear} aria-label="下一年">
|
|
352
|
+
<Icon icon={ChevronsRight} size={16} />
|
|
353
|
+
</button>
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
|
|
357
|
+
<div class="lm-date-time-picker__body">
|
|
358
|
+
<div class="lm-date-time-picker__week">
|
|
359
|
+
{#each weekDays as day}
|
|
360
|
+
<span class="lm-date-time-picker__week-day">{day}</span>
|
|
361
|
+
{/each}
|
|
362
|
+
</div>
|
|
363
|
+
<div class="lm-date-time-picker__days">
|
|
364
|
+
{#each daysInMonth as item (item.date.toISOString())}
|
|
365
|
+
<button
|
|
366
|
+
type="button"
|
|
367
|
+
class="lm-date-time-picker__day"
|
|
368
|
+
class:is-other={!item.isCurrentMonth}
|
|
369
|
+
class:is-today={isToday(item.date)}
|
|
370
|
+
class:is-selected={isSelected(item.date)}
|
|
371
|
+
onclick={() => handleSelectDate(item.date)}
|
|
372
|
+
>
|
|
373
|
+
{item.date.getDate()}
|
|
374
|
+
</button>
|
|
375
|
+
{/each}
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
|
|
381
|
+
<div class="lm-date-time-picker__footer">
|
|
382
|
+
<button type="button" class="lm-date-time-picker__btn lm-date-time-picker__btn--text" onclick={setNow}>此刻</button>
|
|
383
|
+
<button type="button" class="lm-date-time-picker__btn" onclick={handleConfirm} disabled={!isDateValid || !isTimeValid}>确定</button>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
{/if}
|
|
387
|
+
</div>
|
package/dist/index.js
ADDED
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lumen-design/date-time-picker",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "DateTimePicker component for Lumen UI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"svelte": "./dist/index.js",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "svelte-package -i src -o dist --types",
|
|
21
|
+
"build:watch": "svelte-package -i src -o dist --types -w"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@lumen-design/icon": "0.0.2",
|
|
25
|
+
"lucide": "^0.563.0",
|
|
26
|
+
"@lumen-design/utils": "0.0.2"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@sveltejs/package": "^2.5.7",
|
|
30
|
+
"svelte": "5.48.2"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"svelte": "^5.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|