@lumen-design/date-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.
@@ -0,0 +1,206 @@
1
+ <script lang="ts">
2
+ import type { DatePickerType, DatePickerSize } from './types'
3
+ import { onMount, onDestroy } from 'svelte'
4
+ import { createCenteredDropdownTransition } from '../../utils'
5
+ import Icon from '@lumen-design/icon'
6
+ import { Calendar, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, X } from 'lucide'
7
+
8
+ interface DatePickerProps {
9
+ modelValue?: Date | string | null
10
+ type?: DatePickerType
11
+ placeholder?: string
12
+ disabled?: boolean
13
+ clearable?: boolean
14
+ size?: DatePickerSize
15
+ format?: string
16
+ valueFormat?: string
17
+ disabledDate?: (date: Date) => boolean
18
+ showMenuArrow?: boolean
19
+ class?: string
20
+ }
21
+
22
+ let {
23
+ modelValue = $bindable(null),
24
+ type = 'date',
25
+ placeholder = '选择日期',
26
+ disabled = false,
27
+ clearable = false,
28
+ size = 'default',
29
+ format = 'YYYY-MM-DD',
30
+ valueFormat,
31
+ disabledDate,
32
+ showMenuArrow = true,
33
+ class: cls = '',
34
+ ...attrs
35
+ }: DatePickerProps = $props()
36
+
37
+ let visible = $state(false)
38
+ let reduceMotion = $state(false)
39
+ let pickerRef: HTMLDivElement | null = null
40
+ let currentYear = $state(new Date().getFullYear())
41
+ let currentMonth = $state(new Date().getMonth())
42
+
43
+ const weekDays = ['日', '一', '二', '三', '四', '五', '六']
44
+
45
+ // 优化: 移除冗余的箭头函数包装
46
+ const selectedDate = $derived(modelValue ? (modelValue instanceof Date ? modelValue : new Date(modelValue)) : null)
47
+
48
+ const displayValue = $derived.by(() => {
49
+ if (!selectedDate) return ''
50
+ const y = selectedDate.getFullYear()
51
+ const m = String(selectedDate.getMonth() + 1).padStart(2, '0')
52
+ const d = String(selectedDate.getDate()).padStart(2, '0')
53
+ return format.replace('YYYY', String(y)).replace('MM', m).replace('DD', d)
54
+ })
55
+
56
+ const daysInMonth = $derived.by(() => {
57
+ const firstDay = new Date(currentYear, currentMonth, 1)
58
+ const lastDay = new Date(currentYear, currentMonth + 1, 0)
59
+ const days: { date: Date; isCurrentMonth: boolean }[] = []
60
+ const startDay = firstDay.getDay()
61
+
62
+ for (let i = startDay - 1; i >= 0; i--) {
63
+ days.push({ date: new Date(currentYear, currentMonth, -i), isCurrentMonth: false })
64
+ }
65
+ for (let i = 1; i <= lastDay.getDate(); i++) {
66
+ days.push({ date: new Date(currentYear, currentMonth, i), isCurrentMonth: true })
67
+ }
68
+ const remaining = 42 - days.length
69
+ for (let i = 1; i <= remaining; i++) {
70
+ days.push({ date: new Date(currentYear, currentMonth + 1, i), isCurrentMonth: false })
71
+ }
72
+ return days
73
+ })
74
+
75
+ const classes = $derived(`lm-date-picker lm-date-picker--${size}${disabled ? ' is-disabled' : ''}${cls ? ` ${cls}` : ''}`)
76
+
77
+ const handleClickOutside = (e: MouseEvent): void => {
78
+ if (visible && pickerRef && !pickerRef.contains(e.target as Node)) visible = false
79
+ }
80
+
81
+ const toggle = (): void => {
82
+ if (disabled) return
83
+ visible = !visible
84
+ if (visible && selectedDate) {
85
+ currentYear = selectedDate.getFullYear()
86
+ currentMonth = selectedDate.getMonth()
87
+ }
88
+ }
89
+
90
+ const prevMonth = (): void => {
91
+ if (currentMonth === 0) {
92
+ currentMonth = 11
93
+ currentYear--
94
+ } else {
95
+ currentMonth--
96
+ }
97
+ }
98
+
99
+ const prevYear = (): void => {
100
+ currentYear--
101
+ }
102
+
103
+ const nextMonth = (): void => {
104
+ if (currentMonth === 11) {
105
+ currentMonth = 0
106
+ currentYear++
107
+ } else {
108
+ currentMonth++
109
+ }
110
+ }
111
+
112
+ const nextYear = (): void => {
113
+ currentYear++
114
+ }
115
+
116
+ const formatDate = (date: Date, fmt: string): string => {
117
+ const y = date.getFullYear()
118
+ const m = String(date.getMonth() + 1).padStart(2, '0')
119
+ const d = String(date.getDate()).padStart(2, '0')
120
+ return fmt.replace('YYYY', String(y)).replace('MM', m).replace('DD', d)
121
+ }
122
+
123
+ const selectDate = (date: Date): void => {
124
+ if (disabledDate?.(date)) return
125
+ modelValue = valueFormat ? formatDate(date, valueFormat) : date
126
+ visible = false
127
+ }
128
+
129
+ const isSelected = (date: Date): boolean => {
130
+ if (!selectedDate) return false
131
+ return date.getFullYear() === selectedDate.getFullYear() && date.getMonth() === selectedDate.getMonth() && date.getDate() === selectedDate.getDate()
132
+ }
133
+
134
+ const isToday = (date: Date): boolean => {
135
+ const today = new Date()
136
+ return date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate()
137
+ }
138
+
139
+ const handleClear = (e: MouseEvent): void => {
140
+ e.stopPropagation()
141
+ modelValue = null
142
+ }
143
+
144
+ const dropdownTransition = $derived(createCenteredDropdownTransition(reduceMotion))
145
+
146
+ onMount(() => {
147
+ document.addEventListener('click', handleClickOutside)
148
+ if (typeof window !== 'undefined') {
149
+ reduceMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches ?? false
150
+ }
151
+ })
152
+ onDestroy(() => document.removeEventListener('click', handleClickOutside))
153
+ </script>
154
+
155
+ <div class={classes} bind:this={pickerRef} {...attrs}>
156
+ <button type="button" class="lm-date-picker__trigger" class:is-focus={visible} onclick={toggle} {disabled}>
157
+ <Icon icon={Calendar} size={14} class="lm-date-picker__prefix" />
158
+ <span class="lm-date-picker__input" class:is-placeholder={!displayValue}>{displayValue || placeholder}</span>
159
+ {#if clearable && modelValue && !disabled}
160
+ <span
161
+ class="lm-date-picker__clear"
162
+ role="button"
163
+ tabindex="0"
164
+ onclick={handleClear}
165
+ onkeydown={(e: KeyboardEvent) => e.key === 'Enter' && handleClear(e as unknown as MouseEvent)}
166
+ aria-label="清空"><Icon icon={X} size={14} /></span
167
+ >
168
+ {/if}
169
+ </button>
170
+
171
+ {#if visible}
172
+ <div class="lm-date-picker__dropdown" transition:dropdownTransition>
173
+ {#if showMenuArrow}
174
+ <div class="lm-date-picker__menu-arrow" aria-hidden="true"></div>
175
+ {/if}
176
+ <div class="lm-date-picker__header">
177
+ <div class="lm-date-picker__nav-group">
178
+ <button type="button" class="lm-date-picker__nav" onclick={prevYear} aria-label="上一年"><Icon icon={ChevronsLeft} size={16} /></button>
179
+ <button type="button" class="lm-date-picker__nav" onclick={prevMonth} aria-label="上个月"><Icon icon={ChevronLeft} size={14} /></button>
180
+ </div>
181
+ <span class="lm-date-picker__title">{currentYear}年 {currentMonth + 1}月</span>
182
+ <div class="lm-date-picker__nav-group">
183
+ <button type="button" class="lm-date-picker__nav" onclick={nextMonth} aria-label="下个月"><Icon icon={ChevronRight} size={14} /></button>
184
+ <button type="button" class="lm-date-picker__nav" onclick={nextYear} aria-label="下一年"><Icon icon={ChevronsRight} size={16} /></button>
185
+ </div>
186
+ </div>
187
+ <div class="lm-date-picker__body">
188
+ <div class="lm-date-picker__week">
189
+ {#each weekDays as day}<span class="lm-date-picker__week-day">{day}</span>{/each}
190
+ </div>
191
+ <div class="lm-date-picker__days">
192
+ {#each daysInMonth as { date, isCurrentMonth }}
193
+ <button
194
+ type="button"
195
+ class="lm-date-picker__day{!isCurrentMonth ? ' is-other-month' : ''}{isSelected(date) ? ' is-selected' : ''}{isToday(date) ? ' is-today' : ''}{disabledDate?.(date)
196
+ ? ' is-disabled'
197
+ : ''}"
198
+ disabled={disabledDate?.(date)}
199
+ onclick={() => selectDate(date)}>{date.getDate()}</button
200
+ >
201
+ {/each}
202
+ </div>
203
+ </div>
204
+ </div>
205
+ {/if}
206
+ </div>
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import DatePicker from './DatePicker.svelte';
2
+ export { DatePicker };
3
+ export default DatePicker;
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@lumen-design/date-picker",
3
+ "version": "0.0.2",
4
+ "description": "DatePicker 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
+ }