Skip to content

Commit 44c6c09

Browse files
Merge pull request #19 from probablykasper/browse-date
Add browseWithoutSelecting prop
2 parents 2f2c070 + f9704c0 commit 44c6c09

File tree

6 files changed

+139
-122
lines changed

6 files changed

+139
-122
lines changed

src/lib/DateInput.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@
101101
export let visible = false
102102
/** Close the date popup when a date is selected */
103103
export let closeOnSelection = false
104+
/** Wait with updating the date until a date is selected */
105+
export let browseWithoutSelecting = false
104106
105107
// handle on:focusout for parent element. If the parent element loses
106108
// focus (e.g input element), visible is set to false
@@ -156,6 +158,7 @@
156158
{min}
157159
{max}
158160
{locale}
161+
{browseWithoutSelecting}
159162
/>
160163
</div>
161164
{/if}

src/lib/DatePicker.svelte

Lines changed: 110 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -7,37 +7,51 @@
77
88
const dispatch = createEventDispatcher<{ select: undefined }>()
99
10+
function cloneDate(d: Date) {
11+
return new Date(d.getTime())
12+
}
13+
1014
/** Date value. It's `null` if no date is selected */
1115
export let value: Date | null = null
16+
1217
function setValue(d: Date) {
1318
if (d.getTime() !== value?.getTime()) {
14-
value = d
19+
browseDate = clamp(d, min, max)
20+
value = cloneDate(browseDate)
1521
}
1622
}
17-
function updateValue(updater: (date: Date) => Date) {
18-
const newValue = updater(new Date(shownDate.getTime()))
19-
setValue(newValue)
23+
function browse(d: Date) {
24+
browseDate = clamp(d, min, max)
25+
if (!browseWithoutSelecting && value) {
26+
setValue(browseDate)
27+
}
2028
}
2129
2230
/** Default Date to use */
2331
const defaultDate = new Date()
2432
25-
/** The date shown in the popup, for when `value` is null */
26-
let shownDate = value ?? defaultDate
27-
$: if (value) shownDate = value
28-
29-
/** Update the shownDate. The date is only selected if a date is already selected */
30-
function updateShownDate(updater: (date: Date) => Date) {
31-
shownDate = updater(new Date(shownDate.getTime()))
32-
if (value && shownDate.getTime() !== value.getTime()) {
33-
setValue(shownDate)
34-
}
35-
}
33+
/** The date shown in the popup when none is selected */
34+
let browseDate = value ? cloneDate(value) : cloneDate(defaultDate)
3635
3736
/** The earliest year the user can select */
3837
export let min = new Date(defaultDate.getFullYear() - 20, 0, 1)
3938
/** The latest year the user can select */
4039
export let max = new Date(defaultDate.getFullYear(), 11, 31, 23, 59, 59, 999)
40+
$: if (value && value > max) {
41+
setValue(max)
42+
} else if (value && value < min) {
43+
setValue(min)
44+
}
45+
function clamp(d: Date, min: Date, max: Date) {
46+
if (browseDate > max) {
47+
return cloneDate(max)
48+
} else if (browseDate < min) {
49+
return cloneDate(min)
50+
} else {
51+
return cloneDate(d)
52+
}
53+
}
54+
4155
let years = getYears(min, max)
4256
$: years = getYears(min, max)
4357
function getYears(min: Date, max: Date) {
@@ -48,79 +62,69 @@
4862
return years
4963
}
5064
51-
$: if (shownDate > max) {
52-
updateShownDate(() => max)
53-
} else if (shownDate < min) {
54-
updateShownDate(() => min)
55-
}
56-
5765
/** Locale object for internationalization */
5866
export let locale: Locale = {}
5967
$: iLocale = getInnerLocale(locale)
68+
/** Wait with updating the date until a date is selected */
69+
export let browseWithoutSelecting = false
6070
61-
let year = shownDate.getFullYear()
62-
const getYear = (tmpPickerDate: Date) => (year = tmpPickerDate.getFullYear())
63-
function setYear(year: number) {
64-
updateShownDate((tmpPickerDate) => {
65-
tmpPickerDate.setFullYear(year)
66-
return tmpPickerDate
67-
})
71+
let browseYear = browseDate.getFullYear()
72+
function getBrowseYear(d: Date) {
73+
browseYear = d.getFullYear()
6874
}
69-
$: getYear(shownDate)
70-
$: setYear(year)
75+
$: getBrowseYear(browseDate)
76+
function setYear(newYear: number) {
77+
browseDate.setFullYear(newYear)
78+
browseDate = browseDate
79+
browse(browseDate)
80+
}
81+
$: setYear(browseYear)
7182
72-
let month = shownDate.getMonth()
73-
const getMonth = (tmpPickerDate: Date) => (month = tmpPickerDate.getMonth())
74-
function setMonth(month: number) {
75-
let newYear = year
76-
let newMonth = month
77-
if (month === 12) {
83+
let browseMonth = browseDate.getMonth()
84+
function getBrowseMonth(d: Date) {
85+
browseMonth = d.getMonth()
86+
}
87+
$: getBrowseMonth(browseDate)
88+
function setMonth(newMonth: number) {
89+
let newYear = browseDate.getFullYear()
90+
if (newMonth === 12) {
7891
newMonth = 0
7992
newYear++
80-
} else if (month === -1) {
93+
} else if (newMonth === -1) {
8194
newMonth = 11
8295
newYear--
8396
}
8497
8598
const maxDate = getMonthLength(newYear, newMonth)
86-
const newDate = Math.min(shownDate.getDate(), maxDate)
87-
updateShownDate((date) => {
88-
return new Date(
99+
const newDate = Math.min(browseDate.getDate(), maxDate)
100+
browse(
101+
new Date(
89102
newYear,
90103
newMonth,
91104
newDate,
92-
date.getHours(),
93-
date.getMinutes(),
94-
date.getSeconds(),
95-
date.getMilliseconds()
105+
browseDate.getHours(),
106+
browseDate.getMinutes(),
107+
browseDate.getSeconds(),
108+
browseDate.getMilliseconds()
96109
)
97-
})
110+
)
98111
}
99-
$: getMonth(shownDate)
100-
$: setMonth(month)
112+
$: setMonth(browseMonth)
101113
102-
let dayOfMonth = value?.getDate() || null
103-
$: dayOfMonth = value?.getDate() || null
114+
$: calendarDays = getCalendarDays(browseDate, iLocale.weekStartsOn)
104115
105-
$: calendarDays = getCalendarDays(shownDate, iLocale.weekStartsOn)
106-
107-
function setDay(calendarDay: CalendarDay) {
116+
function selectDay(calendarDay: CalendarDay) {
108117
if (dayIsInRange(calendarDay, min, max)) {
109-
updateValue((value) => {
110-
value.setFullYear(0)
111-
value.setMonth(0)
112-
value.setDate(1)
113-
value.setFullYear(calendarDay.year)
114-
value.setMonth(calendarDay.month)
115-
value.setDate(calendarDay.number)
116-
return value
117-
})
118+
browseDate.setFullYear(0)
119+
browseDate.setMonth(0)
120+
browseDate.setDate(1)
121+
browseDate.setFullYear(calendarDay.year)
122+
browseDate.setMonth(calendarDay.month)
123+
browseDate.setDate(calendarDay.number)
124+
setValue(browseDate)
125+
dispatch('select')
118126
}
119127
}
120-
function selectDay(calendarDay: CalendarDay) {
121-
setDay(calendarDay)
122-
dispatch('select')
123-
}
124128
function dayIsInRange(calendarDay: CalendarDay, min: Date, max: Date) {
125129
const date = new Date(calendarDay.year, calendarDay.month, calendarDay.number)
126130
const minDate = new Date(min.getFullYear(), min.getMonth(), min.getDate())
@@ -129,13 +133,13 @@
129133
}
130134
function shiftKeydown(e: KeyboardEvent) {
131135
if (e.shiftKey && e.key === 'ArrowUp') {
132-
setYear(year - 1)
136+
setYear(browseDate.getFullYear() - 1)
133137
} else if (e.shiftKey && e.key === 'ArrowDown') {
134-
setYear(year + 1)
138+
setYear(browseDate.getFullYear() + 1)
135139
} else if (e.shiftKey && e.key === 'ArrowLeft') {
136-
setMonth(month - 1)
140+
setMonth(browseDate.getMonth() - 1)
137141
} else if (e.shiftKey && e.key === 'ArrowRight') {
138-
setMonth(month + 1)
142+
setMonth(browseDate.getMonth() + 1)
139143
} else {
140144
return false
141145
}
@@ -148,13 +152,13 @@
148152
shiftKeydown(e)
149153
return
150154
} else if (e.key === 'ArrowUp') {
151-
setYear(year - 1)
155+
setYear(browseDate.getFullYear() - 1)
152156
} else if (e.key === 'ArrowDown') {
153-
setYear(year + 1)
157+
setYear(browseDate.getFullYear() + 1)
154158
} else if (e.key === 'ArrowLeft') {
155-
setMonth(month - 1)
159+
setMonth(browseDate.getMonth() - 1)
156160
} else if (e.key === 'ArrowRight') {
157-
setMonth(month + 1)
161+
setMonth(browseDate.getMonth() + 1)
158162
} else {
159163
shiftKeydown(e)
160164
return
@@ -167,13 +171,13 @@
167171
shiftKeydown(e)
168172
return
169173
} else if (e.key === 'ArrowUp') {
170-
setMonth(month - 1)
174+
setMonth(browseDate.getFullYear() - 1)
171175
} else if (e.key === 'ArrowDown') {
172-
setMonth(month + 1)
176+
setMonth(browseDate.getFullYear() + 1)
173177
} else if (e.key === 'ArrowLeft') {
174-
setMonth(month - 1)
178+
setMonth(browseDate.getFullYear() - 1)
175179
} else if (e.key === 'ArrowRight') {
176-
setMonth(month + 1)
180+
setMonth(browseDate.getFullYear() + 1)
177181
} else {
178182
shiftKeydown(e)
179183
return
@@ -189,25 +193,20 @@
189193
shiftKeydown(e)
190194
return
191195
} else if (e.key === 'ArrowUp') {
192-
updateValue((value) => {
193-
value.setDate(value.getDate() - 7)
194-
return value
195-
})
196+
browseDate.setDate(browseDate.getDate() - 7)
197+
setValue(browseDate)
196198
} else if (e.key === 'ArrowDown') {
197-
updateValue((value) => {
198-
value.setDate(value.getDate() + 7)
199-
return value
200-
})
199+
browseDate.setDate(browseDate.getDate() + 7)
200+
setValue(browseDate)
201201
} else if (e.key === 'ArrowLeft') {
202-
updateValue((value) => {
203-
value.setDate(value.getDate() - 1)
204-
return value
205-
})
202+
browseDate.setDate(browseDate.getDate() - 1)
203+
setValue(browseDate)
206204
} else if (e.key === 'ArrowRight') {
207-
updateValue((value) => {
208-
value.setDate(value.getDate() + 1)
209-
return value
210-
})
205+
browseDate.setDate(browseDate.getDate() + 1)
206+
setValue(browseDate)
207+
} else if (e.key === 'Enter') {
208+
setValue(browseDate)
209+
dispatch('select')
211210
} else {
212211
return
213212
}
@@ -218,7 +217,7 @@
218217
<div class="date-time-picker" on:focusout tabindex="0" on:keydown={keydown}>
219218
<div class="tab-container" tabindex="-1">
220219
<div class="top">
221-
<div class="page-button" tabindex="-1" on:click={() => setMonth(month - 1)}>
220+
<div class="page-button" tabindex="-1" on:click={() => setMonth(browseDate.getMonth() - 1)}>
222221
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
223222
><path
224223
d="M5 3l3.057-3 11.943 12-11.943 12-3.057-3 9-9z"
@@ -227,11 +226,17 @@
227226
>
228227
</div>
229228
<div class="dropdown month">
230-
<select bind:value={month} on:keydown={monthKeydown}>
229+
<select
230+
bind:value={browseMonth}
231+
on:input={() => {
232+
setMonth(browseMonth)
233+
}}
234+
on:keydown={monthKeydown}
235+
>
231236
{#each iLocale.months as monthName, i}
232237
<option
233-
disabled={new Date(year, i, getMonthLength(year, i), 23, 59, 59, 999) < min ||
234-
new Date(year, i) > max}
238+
disabled={new Date(browseYear, i, getMonthLength(browseYear, i), 23, 59, 59, 999) <
239+
min || new Date(browseYear, i) > max}
235240
value={i}>{monthName}</option
236241
>
237242
{/each}
@@ -245,30 +250,30 @@
245250
-->
246251
<select class="dummy-select" tabindex="-1">
247252
{#each iLocale.months as monthName, i}
248-
<option value={i} selected={i === month}>{monthName}</option>
253+
<option value={i} selected={i === browseMonth}>{monthName}</option>
249254
{/each}
250255
</select>
251256
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
252257
><path d="M6 0l12 12-12 12z" transform="rotate(90, 12, 12)" /></svg
253258
>
254259
</div>
255260
<div class="dropdown year">
256-
<select bind:value={year} on:keydown={yearKeydown}>
261+
<select bind:value={browseYear} on:keydown={yearKeydown}>
257262
{#each years as v}
258263
<option value={v}>{v}</option>
259264
{/each}
260265
</select>
261266
<!-- style <select> button without affecting menu popup -->
262267
<select class="dummy-select" tabindex="-1">
263268
{#each years as v}
264-
<option value={v} selected={v === year}>{v}</option>
269+
<option value={v} selected={v === browseDate.getFullYear()}>{v}</option>
265270
{/each}
266271
</select>
267272
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
268273
><path d="M6 0l12 12-12 12z" transform="rotate(90, 12, 12)" /></svg
269274
>
270275
</div>
271-
<div class="page-button" tabindex="-1" on:click={() => setMonth(month + 1)}>
276+
<div class="page-button" tabindex="-1" on:click={() => setMonth(browseDate.getMonth() + 1)}>
272277
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
273278
><path d="M5 3l3.057-3 11.943 12-11.943 12-3.057-3 9-9z" /></svg
274279
>
@@ -290,8 +295,10 @@
290295
class="cell"
291296
on:click={() => selectDay(calendarDay)}
292297
class:disabled={!dayIsInRange(calendarDay, min, max)}
293-
class:selected={calendarDay.month === month && calendarDay.number === dayOfMonth}
294-
class:other-month={calendarDay.month !== month}
298+
class:selected={calendarDay.year === value?.getFullYear() &&
299+
calendarDay.month === value?.getMonth() &&
300+
calendarDay.number === value.getDate()}
301+
class:other-month={calendarDay.month !== browseMonth}
295302
>
296303
<span>{calendarDay.number}</span>
297304
</div>

src/routes/_DateInput.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
let valid: boolean
1111
let visible: boolean
1212
let closeOnSelection: boolean
13+
let browseWithoutSelecting: boolean
1314
let format: string
1415
</script>
1516

@@ -24,6 +25,7 @@
2425
bind:format
2526
bind:visible
2627
bind:closeOnSelection
28+
bind:browseWithoutSelecting
2729
/>
2830

2931
<svelte:fragment slot="right">
@@ -36,6 +38,7 @@
3638
<Prop label="format" bind:value={format} />
3739
<Prop label="visible" bind:value={visible} />
3840
<Prop label="closeOnSelection" bind:value={closeOnSelection} />
41+
<Prop label="browseWithoutSelecting" bind:value={browseWithoutSelecting} />
3942
<Prop label="locale">Default</Prop>
4043
</svelte:fragment>
4144
</Split>

0 commit comments

Comments
 (0)