@oslokommune/punkt-elements 14.0.2 → 14.0.4
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/CHANGELOG.md +17 -0
- package/dist/calendar-BtShW7ER.cjs +90 -0
- package/dist/{calendar-Bz27nuTP.js → calendar-yxjSI4wd.js} +766 -682
- package/dist/datepicker-D0q75U1Z.js +1463 -0
- package/dist/datepicker-DDV382Uu.cjs +271 -0
- package/dist/index.d.ts +118 -83
- package/dist/pkt-calendar.cjs +1 -1
- package/dist/pkt-calendar.js +1 -1
- package/dist/pkt-datepicker.cjs +1 -1
- package/dist/pkt-datepicker.js +2 -2
- package/dist/pkt-index.cjs +1 -1
- package/dist/pkt-index.js +3 -3
- package/package.json +4 -4
- package/src/components/calendar/calendar.ts +372 -414
- package/src/components/calendar/helpers/calendar-grid.ts +93 -0
- package/src/components/calendar/helpers/date-validation.ts +86 -0
- package/src/components/calendar/helpers/index.ts +49 -0
- package/src/components/calendar/helpers/keyboard-navigation.ts +54 -0
- package/src/components/calendar/helpers/selection-manager.ts +184 -0
- package/src/components/datepicker/datepicker-base.ts +151 -0
- package/src/components/datepicker/datepicker-multiple.ts +7 -114
- package/src/components/datepicker/datepicker-range.ts +21 -141
- package/src/components/datepicker/datepicker-single.ts +7 -115
- package/src/components/datepicker/datepicker-types.ts +56 -0
- package/src/components/datepicker/datepicker-utils.test.ts +730 -0
- package/src/components/datepicker/datepicker-utils.ts +338 -9
- package/src/components/datepicker/datepicker.ts +25 -1
- package/dist/calendar-Dz1Cnzx5.cjs +0 -115
- package/dist/datepicker-CnCOXI2x.cjs +0 -289
- package/dist/datepicker-DsqM01iU.js +0 -1355
|
@@ -7,25 +7,60 @@ import {
|
|
|
7
7
|
formatReadableDate,
|
|
8
8
|
parseISODateString,
|
|
9
9
|
todayInTz,
|
|
10
|
-
isDateSelectable,
|
|
11
10
|
newDateFromDate,
|
|
12
11
|
} from 'shared-utils/date-utils'
|
|
13
|
-
import {
|
|
14
|
-
import { html, nothing, PropertyValues } from 'lit'
|
|
12
|
+
import { html, nothing, PropertyValues, TemplateResult } from 'lit'
|
|
15
13
|
import { PktElement } from '@/base-elements/element'
|
|
16
14
|
import converters from '../../helpers/converters'
|
|
17
15
|
import specs from 'componentSpecs/calendar.json'
|
|
18
16
|
import '@/components/icon'
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
import {
|
|
18
|
+
isDateExcluded,
|
|
19
|
+
isDayDisabled as checkDayDisabled,
|
|
20
|
+
isPrevMonthAllowed as checkPrevMonthAllowed,
|
|
21
|
+
isNextMonthAllowed as checkNextMonthAllowed,
|
|
22
|
+
type IDateConstraints,
|
|
23
|
+
} from './helpers/date-validation'
|
|
24
|
+
import {
|
|
25
|
+
DAYS_PER_WEEK,
|
|
26
|
+
calculateCalendarDimensions,
|
|
27
|
+
getCellType,
|
|
28
|
+
getDayNumber,
|
|
29
|
+
} from './helpers/calendar-grid'
|
|
30
|
+
import {
|
|
31
|
+
convertSelectedToDates,
|
|
32
|
+
updateRangeMap as calculateRangeMap,
|
|
33
|
+
isRangeAllowed as checkRangeAllowed,
|
|
34
|
+
addToSelection,
|
|
35
|
+
removeFromSelection,
|
|
36
|
+
toggleSelection,
|
|
37
|
+
handleRangeSelection,
|
|
38
|
+
type TDateRangeMap,
|
|
39
|
+
} from './helpers/selection-manager'
|
|
40
|
+
import {
|
|
41
|
+
shouldIgnoreKeyboardEvent,
|
|
42
|
+
findNextSelectableDate as findNextDate,
|
|
43
|
+
getKeyDirection,
|
|
44
|
+
} from './helpers/keyboard-navigation'
|
|
45
|
+
|
|
46
|
+
// Types
|
|
47
|
+
|
|
48
|
+
type TDayViewData = {
|
|
49
|
+
currentDate: Date
|
|
50
|
+
currentDateISO: string
|
|
51
|
+
isToday: boolean
|
|
52
|
+
isSelected: boolean
|
|
53
|
+
isDisabled: boolean
|
|
54
|
+
ariaLabel: string
|
|
55
|
+
tabindex: string
|
|
22
56
|
}
|
|
23
57
|
|
|
24
58
|
@customElement('pkt-calendar')
|
|
25
59
|
export class PktCalendar extends PktElement {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
60
|
+
// Selection properties
|
|
61
|
+
@property({ converter: converters.csvToArray })
|
|
62
|
+
selected: string | string[] = []
|
|
63
|
+
|
|
29
64
|
@property({ type: Boolean })
|
|
30
65
|
multiple: boolean = specs.props.multiple.default
|
|
31
66
|
|
|
@@ -35,15 +70,7 @@ export class PktCalendar extends PktElement {
|
|
|
35
70
|
@property({ type: Boolean })
|
|
36
71
|
range: boolean = specs.props.range.default
|
|
37
72
|
|
|
38
|
-
|
|
39
|
-
weeknumbers: boolean = specs.props.weeknumbers.default
|
|
40
|
-
|
|
41
|
-
@property({ type: Boolean })
|
|
42
|
-
withcontrols: boolean = specs.props.withcontrols.default
|
|
43
|
-
|
|
44
|
-
@property({ converter: converters.csvToArray })
|
|
45
|
-
selected: string | string[] = []
|
|
46
|
-
|
|
73
|
+
// Date constraints
|
|
47
74
|
@property({ type: String })
|
|
48
75
|
earliest: string | null = specs.props.earliest.default
|
|
49
76
|
|
|
@@ -56,12 +83,17 @@ export class PktCalendar extends PktElement {
|
|
|
56
83
|
@property({ converter: converters.csvToArray })
|
|
57
84
|
excludeweekdays: string[] = []
|
|
58
85
|
|
|
86
|
+
// Display options
|
|
87
|
+
@property({ type: Boolean })
|
|
88
|
+
weeknumbers: boolean = specs.props.weeknumbers.default
|
|
89
|
+
|
|
90
|
+
@property({ type: Boolean })
|
|
91
|
+
withcontrols: boolean = specs.props.withcontrols.default
|
|
92
|
+
|
|
59
93
|
@property({ converter: converters.stringToDate })
|
|
60
94
|
currentmonth: Date | null = null
|
|
61
95
|
|
|
62
|
-
|
|
63
|
-
* Strings
|
|
64
|
-
*/
|
|
96
|
+
// Localization strings
|
|
65
97
|
@property({ type: Array }) dayStrings: string[] = this.strings.dates.daysShort
|
|
66
98
|
@property({ type: Array }) dayStringsLong: string[] = this.strings.dates.days
|
|
67
99
|
@property({ type: Array }) monthStrings: string[] = this.strings.dates.months
|
|
@@ -69,29 +101,31 @@ export class PktCalendar extends PktElement {
|
|
|
69
101
|
@property({ type: String }) prevMonthString: string = this.strings.dates.prevMonth
|
|
70
102
|
@property({ type: String }) nextMonthString: string = this.strings.dates.nextMonth
|
|
71
103
|
|
|
72
|
-
|
|
73
|
-
* Private properties
|
|
74
|
-
*/
|
|
104
|
+
// Internal state - selection tracking
|
|
75
105
|
@property({ type: Array }) private _selected: Date[] = []
|
|
106
|
+
@state() private inRange: TDateRangeMap = {}
|
|
107
|
+
@property({ type: Date }) private rangeHovered: Date | null = null
|
|
108
|
+
|
|
109
|
+
// Internal state - navigation and display
|
|
76
110
|
@property({ type: Number }) private year: number = 0
|
|
77
111
|
@property({ type: Number }) private month: number = 0
|
|
78
112
|
@property({ type: Number }) private week: number = 0
|
|
79
|
-
@
|
|
113
|
+
@state() private currentmonthtouched: boolean = false
|
|
80
114
|
|
|
81
|
-
|
|
115
|
+
// Internal state - keyboard navigation and focus management
|
|
82
116
|
@state() private focusedDate: string | null = null
|
|
83
117
|
@state() private selectableDates: {
|
|
84
118
|
currentDateISO: string
|
|
85
119
|
isDisabled: boolean
|
|
86
120
|
tabindex: string
|
|
87
121
|
}[] = []
|
|
88
|
-
@state() private currentmonthtouched: boolean = false
|
|
89
122
|
@state() private tabIndexSet: number = 0
|
|
123
|
+
|
|
90
124
|
/**
|
|
91
|
-
*
|
|
125
|
+
* Lifecycle methods
|
|
92
126
|
*/
|
|
93
|
-
|
|
94
|
-
|
|
127
|
+
protected firstUpdated(_changedProperties: PropertyValues): void {
|
|
128
|
+
this.addEventListener('keydown', this.handleKeydown)
|
|
95
129
|
}
|
|
96
130
|
|
|
97
131
|
disconnectedCallback(): void {
|
|
@@ -99,13 +133,6 @@ export class PktCalendar extends PktElement {
|
|
|
99
133
|
super.disconnectedCallback()
|
|
100
134
|
}
|
|
101
135
|
|
|
102
|
-
attributeChangedCallback(name: string, _old: string | null, value: string | null): void {
|
|
103
|
-
if (name === 'selected' && value) {
|
|
104
|
-
this.convertSelected()
|
|
105
|
-
}
|
|
106
|
-
super.attributeChangedCallback(name, _old, value)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
136
|
updated(changedProperties: PropertyValues): void {
|
|
110
137
|
super.updated(changedProperties)
|
|
111
138
|
if (changedProperties.has('selected')) {
|
|
@@ -113,11 +140,10 @@ export class PktCalendar extends PktElement {
|
|
|
113
140
|
}
|
|
114
141
|
}
|
|
115
142
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
convertSelected() {
|
|
143
|
+
/**
|
|
144
|
+
* Date and selection management
|
|
145
|
+
*/
|
|
146
|
+
private convertSelected() {
|
|
121
147
|
if (typeof this.selected === 'string') {
|
|
122
148
|
this.selected = this.selected.split(',')
|
|
123
149
|
}
|
|
@@ -125,28 +151,16 @@ export class PktCalendar extends PktElement {
|
|
|
125
151
|
this.selected = []
|
|
126
152
|
}
|
|
127
153
|
|
|
128
|
-
this._selected = this.selected
|
|
129
|
-
if (this.range && this.selected.length === 2) {
|
|
130
|
-
const days = eachDayOfInterval({
|
|
131
|
-
start: this._selected[0],
|
|
132
|
-
end: this._selected[1],
|
|
133
|
-
})
|
|
154
|
+
this._selected = convertSelectedToDates(this.selected)
|
|
134
155
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const inRange: DatesInRange = {}
|
|
138
|
-
for (let i = 0; i < days.length; i++) {
|
|
139
|
-
const day = days[i]
|
|
140
|
-
const isInRange = day > this._selected[0] && day < this._selected[1]
|
|
141
|
-
inRange[formatISODate(day)] = isInRange
|
|
142
|
-
}
|
|
143
|
-
this.inRange = inRange
|
|
144
|
-
}
|
|
156
|
+
if (this.range && this.selected.length === 2) {
|
|
157
|
+
this.inRange = calculateRangeMap(this._selected[0], this._selected[1])
|
|
145
158
|
}
|
|
159
|
+
|
|
146
160
|
this.setCurrentMonth()
|
|
147
161
|
}
|
|
148
162
|
|
|
149
|
-
setCurrentMonth() {
|
|
163
|
+
private setCurrentMonth() {
|
|
150
164
|
if (this.currentmonth === null && !this.currentmonthtouched) {
|
|
151
165
|
this.currentmonthtouched = true
|
|
152
166
|
}
|
|
@@ -163,64 +177,40 @@ export class PktCalendar extends PktElement {
|
|
|
163
177
|
this.month = this.currentmonth.getMonth()
|
|
164
178
|
}
|
|
165
179
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
break
|
|
174
|
-
case 'ArrowUp':
|
|
175
|
-
this.handleArrowKey(e, -7)
|
|
176
|
-
break
|
|
177
|
-
case 'ArrowDown':
|
|
178
|
-
this.handleArrowKey(e, 7)
|
|
179
|
-
break
|
|
180
|
-
default:
|
|
181
|
-
break
|
|
180
|
+
/**
|
|
181
|
+
* Keyboard navigation
|
|
182
|
+
*/
|
|
183
|
+
private handleKeydown(e: KeyboardEvent) {
|
|
184
|
+
const direction = getKeyDirection(e.key)
|
|
185
|
+
if (direction !== null) {
|
|
186
|
+
this.handleArrowKey(e, direction)
|
|
182
187
|
}
|
|
183
188
|
}
|
|
184
189
|
|
|
185
|
-
handleArrowKey(e: KeyboardEvent, direction: number) {
|
|
186
|
-
|
|
187
|
-
if ((
|
|
188
|
-
|
|
189
|
-
(e.target as HTMLElement)?.nodeName === 'BUTTON' &&
|
|
190
|
-
!(e.target as HTMLElement)?.dataset?.date
|
|
191
|
-
)
|
|
192
|
-
return
|
|
190
|
+
private handleArrowKey(e: KeyboardEvent, direction: number) {
|
|
191
|
+
const target = e.target as HTMLElement
|
|
192
|
+
if (shouldIgnoreKeyboardEvent(target)) return
|
|
193
|
+
|
|
193
194
|
e.preventDefault()
|
|
195
|
+
|
|
194
196
|
if (!this.focusedDate) {
|
|
195
197
|
this.focusOnCurrentDate()
|
|
198
|
+
return
|
|
196
199
|
}
|
|
200
|
+
|
|
197
201
|
const date = this.focusedDate ? newDate(this.focusedDate) : newDateYMD(this.year, this.month, 1)
|
|
198
|
-
|
|
202
|
+
const nextDate = findNextDate(date, direction, this.querySelector.bind(this))
|
|
203
|
+
|
|
199
204
|
if (nextDate) {
|
|
200
|
-
|
|
201
|
-
if (el instanceof HTMLButtonElement) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
let nextElement = this.querySelector(`button[data-date="${formatISODate(nextDate)}"]`)
|
|
205
|
-
while (
|
|
206
|
-
nextElement &&
|
|
207
|
-
nextElement instanceof HTMLButtonElement &&
|
|
208
|
-
nextElement.dataset.disabled
|
|
209
|
-
) {
|
|
210
|
-
nextDate = addDays(nextDate, direction)
|
|
211
|
-
nextElement = this.querySelector(`button[data-date="${formatISODate(nextDate)}"]`)
|
|
212
|
-
}
|
|
213
|
-
el = nextElement
|
|
214
|
-
}
|
|
215
|
-
if (el instanceof HTMLButtonElement && !el.dataset.disabled) {
|
|
216
|
-
this.focusedDate = formatISODate(nextDate)
|
|
217
|
-
el.focus()
|
|
218
|
-
}
|
|
205
|
+
const el = this.querySelector(`button[data-date="${formatISODate(nextDate)}"]`)
|
|
206
|
+
if (el instanceof HTMLButtonElement && !el.dataset.disabled) {
|
|
207
|
+
this.focusedDate = formatISODate(nextDate)
|
|
208
|
+
el.focus()
|
|
219
209
|
}
|
|
220
210
|
}
|
|
221
211
|
}
|
|
222
212
|
/**
|
|
223
|
-
*
|
|
213
|
+
* Rendering methods
|
|
224
214
|
*/
|
|
225
215
|
render() {
|
|
226
216
|
return html`
|
|
@@ -235,51 +225,9 @@ export class PktCalendar extends PktElement {
|
|
|
235
225
|
}}
|
|
236
226
|
>
|
|
237
227
|
<nav class="pkt-cal-month-nav">
|
|
238
|
-
|
|
239
|
-
<button
|
|
240
|
-
type="button"
|
|
241
|
-
aria-label="${this.prevMonthString}"
|
|
242
|
-
@click=${() => this.isPrevMonthAllowed() && this.prevMonth()}
|
|
243
|
-
@keydown=${(e: KeyboardEvent) => {
|
|
244
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
245
|
-
e.preventDefault()
|
|
246
|
-
this.isPrevMonthAllowed() && this.prevMonth()
|
|
247
|
-
}
|
|
248
|
-
}}
|
|
249
|
-
class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--icon-only pkt-calendar__prev-month ${this.isPrevMonthAllowed()
|
|
250
|
-
? ''
|
|
251
|
-
: 'pkt-hide'}"
|
|
252
|
-
.data-disabled=${!this.isPrevMonthAllowed() ? 'disabled' : nothing}
|
|
253
|
-
?aria-disabled=${!this.isPrevMonthAllowed()}
|
|
254
|
-
tabindex=${this.isPrevMonthAllowed() ? '0' : '-1'}
|
|
255
|
-
>
|
|
256
|
-
<pkt-icon class="pkt-btn__icon" name="chevron-thin-left"></pkt-icon>
|
|
257
|
-
<span class="pkt-btn__text">${this.prevMonthString}</span>
|
|
258
|
-
</button>
|
|
259
|
-
</div>
|
|
228
|
+
${this.renderMonthNavButton('prev')}
|
|
260
229
|
${this.renderMonthNav()}
|
|
261
|
-
|
|
262
|
-
<button
|
|
263
|
-
type="button"
|
|
264
|
-
aria-label="${this.nextMonthString}"
|
|
265
|
-
@click=${() => this.isNextMonthAllowed() && this.nextMonth()}
|
|
266
|
-
@keydown=${(e: KeyboardEvent) => {
|
|
267
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
268
|
-
e.preventDefault()
|
|
269
|
-
this.isNextMonthAllowed() && this.nextMonth()
|
|
270
|
-
}
|
|
271
|
-
}}
|
|
272
|
-
class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--icon-only pkt-calendar__next-month ${this.isNextMonthAllowed()
|
|
273
|
-
? ''
|
|
274
|
-
: 'pkt-hide'}"
|
|
275
|
-
.data-disabled=${!this.isNextMonthAllowed() ? 'disabled' : nothing}
|
|
276
|
-
?aria-disabled=${!this.isNextMonthAllowed()}
|
|
277
|
-
tabindex=${this.isNextMonthAllowed() ? '0' : '-1'}
|
|
278
|
-
>
|
|
279
|
-
<pkt-icon class="pkt-btn__icon" name="chevron-thin-right"></pkt-icon>
|
|
280
|
-
<span class="pkt-btn__text">${this.nextMonthString}</span>
|
|
281
|
-
</button>
|
|
282
|
-
</div>
|
|
230
|
+
${this.renderMonthNavButton('next')}
|
|
283
231
|
</nav>
|
|
284
232
|
<table
|
|
285
233
|
class="pkt-cal-days pkt-txt-12-medium pkt-calendar__body"
|
|
@@ -297,11 +245,43 @@ export class PktCalendar extends PktElement {
|
|
|
297
245
|
`
|
|
298
246
|
}
|
|
299
247
|
|
|
300
|
-
private
|
|
301
|
-
const
|
|
248
|
+
private renderMonthNavButton(direction: 'prev' | 'next'): TemplateResult {
|
|
249
|
+
const isPrev = direction === 'prev'
|
|
250
|
+
const isAllowed = isPrev ? this.isPrevMonthAllowed() : this.isNextMonthAllowed()
|
|
251
|
+
const label = isPrev ? this.prevMonthString : this.nextMonthString
|
|
252
|
+
const iconName = isPrev ? 'chevron-thin-left' : 'chevron-thin-right'
|
|
253
|
+
const className = isPrev ? 'pkt-calendar__prev-month' : 'pkt-calendar__next-month'
|
|
254
|
+
const onClick = isPrev ? () => this.prevMonth() : () => this.nextMonth()
|
|
255
|
+
|
|
256
|
+
return html`<div>
|
|
257
|
+
<button
|
|
258
|
+
type="button"
|
|
259
|
+
aria-label="${label}"
|
|
260
|
+
@click=${() => isAllowed && onClick()}
|
|
261
|
+
@keydown=${(e: KeyboardEvent) => {
|
|
262
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
263
|
+
e.preventDefault()
|
|
264
|
+
isAllowed && onClick()
|
|
265
|
+
}
|
|
266
|
+
}}
|
|
267
|
+
class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--icon-only ${className} ${isAllowed ? '' : 'pkt-hide'}"
|
|
268
|
+
.data-disabled=${!isAllowed ? 'disabled' : nothing}
|
|
269
|
+
?aria-disabled=${!isAllowed}
|
|
270
|
+
tabindex=${isAllowed ? '0' : '-1'}
|
|
271
|
+
>
|
|
272
|
+
<pkt-icon class="pkt-btn__icon" name="${iconName}"></pkt-icon>
|
|
273
|
+
<span class="pkt-btn__text">${label}</span>
|
|
274
|
+
</button>
|
|
275
|
+
</div>`
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private renderDayNames(): TemplateResult {
|
|
279
|
+
const days: TemplateResult[] = []
|
|
280
|
+
|
|
302
281
|
if (this.weeknumbers) {
|
|
303
282
|
days.push(html`<th><div class="pkt-calendar__week-number">${this.weekString}</div></th>`)
|
|
304
283
|
}
|
|
284
|
+
|
|
305
285
|
for (let i = 0; i < this.dayStrings.length; i++) {
|
|
306
286
|
days.push(
|
|
307
287
|
html`<th>
|
|
@@ -311,101 +291,128 @@ export class PktCalendar extends PktElement {
|
|
|
311
291
|
</th>`,
|
|
312
292
|
)
|
|
313
293
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
</tr>`
|
|
294
|
+
|
|
295
|
+
return html`<tr class="pkt-cal-week-row">${days}</tr>`
|
|
317
296
|
}
|
|
318
297
|
|
|
319
|
-
private renderMonthNav() {
|
|
320
|
-
let monthView: any[] = []
|
|
298
|
+
private renderMonthNav(): TemplateResult {
|
|
321
299
|
if (this.withcontrols) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
} else {
|
|
356
|
-
monthView.push(
|
|
357
|
-
html`<div class="pkt-txt-16-medium pkt-calendar__month-title" aria-live="polite">
|
|
358
|
-
${this.monthStrings[this.month]} ${this.year}
|
|
359
|
-
</div>`,
|
|
360
|
-
)
|
|
300
|
+
return html`<div class="pkt-cal-month-picker">
|
|
301
|
+
<label for="${this.id}-monthnav" class="pkt-hide">${this.strings.dates.month}</label>
|
|
302
|
+
<select
|
|
303
|
+
aria-label="${this.strings.dates.month}"
|
|
304
|
+
class="pkt-input pkt-input-compact"
|
|
305
|
+
id="${this.id}-monthnav"
|
|
306
|
+
@change=${(e: Event) => {
|
|
307
|
+
e.stopImmediatePropagation()
|
|
308
|
+
const target = e.target as HTMLSelectElement
|
|
309
|
+
this.changeMonth(this.year, parseInt(target.value))
|
|
310
|
+
}}
|
|
311
|
+
>
|
|
312
|
+
${this.monthStrings.map(
|
|
313
|
+
(month, index) =>
|
|
314
|
+
html`<option value=${index} ?selected=${this.month === index}>${month}</option>`,
|
|
315
|
+
)}
|
|
316
|
+
</select>
|
|
317
|
+
<label for="${this.id}-yearnav" class="pkt-hide">${this.strings.dates.year}</label>
|
|
318
|
+
<input
|
|
319
|
+
aria-label="${this.strings.dates.year}"
|
|
320
|
+
class="pkt-input pkt-cal-input-year pkt-input-compact"
|
|
321
|
+
id="${this.id}-yearnav"
|
|
322
|
+
type="number"
|
|
323
|
+
size="4"
|
|
324
|
+
placeholder="0000"
|
|
325
|
+
@change=${(e: Event) => {
|
|
326
|
+
e.stopImmediatePropagation()
|
|
327
|
+
const target = e.target as HTMLInputElement
|
|
328
|
+
this.changeMonth(parseInt(target.value), this.month)
|
|
329
|
+
}}
|
|
330
|
+
.value=${this.year}
|
|
331
|
+
/>
|
|
332
|
+
</div>`
|
|
361
333
|
}
|
|
362
|
-
|
|
334
|
+
|
|
335
|
+
return html`<div class="pkt-txt-16-medium pkt-calendar__month-title" aria-live="polite">
|
|
336
|
+
${this.monthStrings[this.month]} ${this.year}
|
|
337
|
+
</div>`
|
|
363
338
|
}
|
|
364
339
|
|
|
365
|
-
private
|
|
340
|
+
private getDayViewData(dayCounter: number, today: Date): TDayViewData {
|
|
366
341
|
const currentDate = newDateYMD(this.year, this.month, dayCounter)
|
|
367
342
|
const currentDateISO = formatISODate(currentDate)
|
|
368
343
|
const isToday = currentDateISO === formatISODate(today)
|
|
369
344
|
const isSelected = this.selected.includes(currentDateISO)
|
|
370
|
-
const
|
|
371
|
-
const isDisabled
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
345
|
+
const isDisabled = this.isDayDisabled(currentDate, isSelected)
|
|
346
|
+
const tabindex = this.calculateTabIndex(currentDateISO, isDisabled, dayCounter)
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
currentDate,
|
|
350
|
+
currentDateISO,
|
|
351
|
+
isToday,
|
|
352
|
+
isSelected,
|
|
353
|
+
isDisabled,
|
|
354
|
+
ariaLabel: formatReadableDate(currentDate),
|
|
355
|
+
tabindex,
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private getDateConstraints(): IDateConstraints {
|
|
360
|
+
return {
|
|
361
|
+
earliest: this.earliest,
|
|
362
|
+
latest: this.latest,
|
|
363
|
+
excludedates: this.excludedates,
|
|
364
|
+
excludeweekdays: this.excludeweekdays,
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private isDayDisabled(date: Date, isSelected: boolean): boolean {
|
|
369
|
+
return checkDayDisabled(date, isSelected, this.getDateConstraints(), {
|
|
370
|
+
multiple: this.multiple,
|
|
371
|
+
maxMultiple: this.maxMultiple,
|
|
372
|
+
selectedCount: this.selected.length,
|
|
373
|
+
})
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private calculateTabIndex(dateISO: string, isDisabled: boolean, dayCounter: number): string {
|
|
377
|
+
if (this.focusedDate) {
|
|
378
|
+
return this.focusedDate === dateISO && !isDisabled ? '0' : '-1'
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (!isDisabled && this.tabIndexSet === 0) {
|
|
388
382
|
this.tabIndexSet = dayCounter
|
|
383
|
+
return '0'
|
|
389
384
|
}
|
|
390
385
|
|
|
391
|
-
this.
|
|
386
|
+
return this.tabIndexSet === dayCounter ? '0' : '-1'
|
|
387
|
+
}
|
|
392
388
|
|
|
393
|
-
|
|
389
|
+
private getDayCellClasses(data: TDayViewData) {
|
|
390
|
+
const { currentDateISO, isToday, isSelected } = data
|
|
391
|
+
const isRangeStart = this.range &&
|
|
392
|
+
(this.selected.length === 2 || this.rangeHovered !== null) &&
|
|
393
|
+
currentDateISO === this.selected[0]
|
|
394
|
+
const isRangeEnd = this.range && this.selected.length === 2 && currentDateISO === this.selected[1]
|
|
395
|
+
|
|
396
|
+
return {
|
|
394
397
|
'pkt-cal-today': isToday,
|
|
395
398
|
'pkt-cal-selected': isSelected,
|
|
396
399
|
'pkt-cal-in-range': this.inRange[currentDateISO],
|
|
397
|
-
'pkt-cal-excluded': this.isExcluded(currentDate),
|
|
398
|
-
'pkt-cal-in-range-first':
|
|
399
|
-
|
|
400
|
-
(this.selected.length === 2 || this.rangeHovered !== null) &&
|
|
401
|
-
currentDateISO === this.selected[0],
|
|
402
|
-
'pkt-cal-in-range-last':
|
|
403
|
-
this.range && this.selected.length === 2 && currentDateISO === this.selected[1],
|
|
400
|
+
'pkt-cal-excluded': this.isExcluded(data.currentDate),
|
|
401
|
+
'pkt-cal-in-range-first': isRangeStart,
|
|
402
|
+
'pkt-cal-in-range-last': isRangeEnd,
|
|
404
403
|
'pkt-cal-range-hover':
|
|
405
404
|
this.rangeHovered !== null && currentDateISO === formatISODate(this.rangeHovered),
|
|
406
405
|
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
private getDayButtonClasses(data: TDayViewData) {
|
|
409
|
+
const { currentDateISO, isToday, isSelected, isDisabled } = data
|
|
410
|
+
const isRangeStart = this.range &&
|
|
411
|
+
(this.selected.length === 2 || this.rangeHovered !== null) &&
|
|
412
|
+
currentDateISO === this.selected[0]
|
|
413
|
+
const isRangeEnd = this.range && this.selected.length === 2 && currentDateISO === this.selected[1]
|
|
407
414
|
|
|
408
|
-
|
|
415
|
+
return {
|
|
409
416
|
'pkt-calendar__date': true,
|
|
410
417
|
'pkt-calendar__date--today': isToday,
|
|
411
418
|
'pkt-calendar__date--selected': isSelected,
|
|
@@ -413,30 +420,38 @@ export class PktCalendar extends PktElement {
|
|
|
413
420
|
'pkt-calendar__date--in-range': this.inRange[currentDateISO],
|
|
414
421
|
'pkt-calendar__date--in-range-hover':
|
|
415
422
|
this.rangeHovered !== null && currentDateISO === formatISODate(this.rangeHovered),
|
|
416
|
-
'pkt-calendar__date--range-start':
|
|
417
|
-
|
|
418
|
-
(this.selected.length === 2 || this.rangeHovered !== null) &&
|
|
419
|
-
currentDateISO === this.selected[0],
|
|
420
|
-
'pkt-calendar__date--range-end':
|
|
421
|
-
this.range && this.selected.length === 2 && currentDateISO === this.selected[1],
|
|
423
|
+
'pkt-calendar__date--range-start': isRangeStart,
|
|
424
|
+
'pkt-calendar__date--range-end': isRangeEnd,
|
|
422
425
|
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private handleDayFocus(date: Date, dateISO: string): void {
|
|
429
|
+
if (this.range && !this.isExcluded(date)) {
|
|
430
|
+
this.handleRangeHover(date)
|
|
431
|
+
}
|
|
432
|
+
this.focusedDate = dateISO
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private renderDayView(dayCounter: number, today: Date) {
|
|
436
|
+
const data = this.getDayViewData(dayCounter, today)
|
|
437
|
+
const { currentDate, currentDateISO, isSelected, isDisabled, ariaLabel, tabindex } = data
|
|
423
438
|
|
|
424
|
-
|
|
439
|
+
// Track selectable dates for keyboard navigation
|
|
440
|
+
this.selectableDates.push({ currentDateISO, isDisabled, tabindex })
|
|
441
|
+
|
|
442
|
+
const cellClasses = this.getDayCellClasses(data)
|
|
443
|
+
const buttonClasses = this.getDayButtonClasses(data)
|
|
444
|
+
|
|
445
|
+
return html`<td class=${classMap(cellClasses)}>
|
|
425
446
|
<button
|
|
426
447
|
type="button"
|
|
427
448
|
aria-pressed=${isSelected ? 'true' : 'false'}
|
|
428
449
|
?disabled=${isDisabled}
|
|
429
|
-
class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--label-only ${classMap(
|
|
430
|
-
|
|
431
|
-
)}
|
|
432
|
-
@mouseover=${() =>
|
|
433
|
-
this.range && !this.isExcluded(currentDate) && this.handleRangeHover(currentDate)}
|
|
434
|
-
@focus=${() => {
|
|
435
|
-
this.range && !this.isExcluded(currentDate) && this.handleRangeHover(currentDate)
|
|
436
|
-
this.focusedDate = currentDateISO
|
|
437
|
-
}}
|
|
450
|
+
class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--label-only ${classMap(buttonClasses)}"
|
|
451
|
+
@mouseover=${() => this.range && !this.isExcluded(currentDate) && this.handleRangeHover(currentDate)}
|
|
452
|
+
@focus=${() => this.handleDayFocus(currentDate, currentDateISO)}
|
|
438
453
|
aria-label="${ariaLabel}"
|
|
439
|
-
tabindex=${
|
|
454
|
+
tabindex=${tabindex}
|
|
440
455
|
data-disabled=${isDisabled ? 'disabled' : nothing}
|
|
441
456
|
data-date=${currentDateISO}
|
|
442
457
|
@keydown=${(e: KeyboardEvent) => {
|
|
@@ -457,86 +472,73 @@ export class PktCalendar extends PktElement {
|
|
|
457
472
|
</td>`
|
|
458
473
|
}
|
|
459
474
|
|
|
475
|
+
private renderEmptyDayCell(day: number): TemplateResult {
|
|
476
|
+
return html`<td class="pkt-cal-other">
|
|
477
|
+
<div
|
|
478
|
+
class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--label-only"
|
|
479
|
+
data-disabled="disabled"
|
|
480
|
+
>
|
|
481
|
+
<span class="pkt-btn__text pkt-txt-14-light">${day}</span>
|
|
482
|
+
</div>
|
|
483
|
+
</td>`
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private renderWeekRow(cells: TemplateResult[]): TemplateResult {
|
|
487
|
+
return html`<tr class="pkt-cal-week-row" role="row">${cells}</tr>`
|
|
488
|
+
}
|
|
489
|
+
|
|
460
490
|
private renderCalendarBody() {
|
|
461
491
|
const today = todayInTz()
|
|
462
|
-
const
|
|
463
|
-
const lastDayOfMonth = newDateYMD(this.year, this.month + 1, 0)
|
|
464
|
-
const startingDay = (firstDayOfMonth.getDay() + 6) % 7
|
|
465
|
-
const numDays = lastDayOfMonth.getDate()
|
|
466
|
-
const numRows = Math.ceil((numDays + startingDay) / 7)
|
|
467
|
-
const lastDayOfPrevMonth = newDateYMD(this.year, this.month, 0)
|
|
468
|
-
const numDaysPrevMonth = lastDayOfPrevMonth.getDate()
|
|
492
|
+
const dimensions = calculateCalendarDimensions(this.year, this.month)
|
|
469
493
|
|
|
470
494
|
let dayCounter = 1
|
|
471
|
-
this.week =
|
|
495
|
+
this.week = dimensions.initialWeek
|
|
472
496
|
|
|
473
|
-
const rows:
|
|
497
|
+
const rows: TemplateResult[] = []
|
|
474
498
|
|
|
475
|
-
for (let i = 0; i < numRows; i++) {
|
|
476
|
-
const cells:
|
|
499
|
+
for (let i = 0; i < dimensions.numRows; i++) {
|
|
500
|
+
const cells: TemplateResult[] = []
|
|
477
501
|
|
|
478
|
-
|
|
502
|
+
// Add week number if enabled
|
|
503
|
+
if (this.weeknumbers) {
|
|
504
|
+
cells.push(html`<td class="pkt-cal-week">${this.week}</td>`)
|
|
505
|
+
}
|
|
479
506
|
this.week++
|
|
480
507
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
<div
|
|
487
|
-
class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--label-only"
|
|
488
|
-
data-disabled="disabled"
|
|
489
|
-
>
|
|
490
|
-
<span class="pkt-btn__text pkt-txt-14-light">${dayFromPrevMonth}</span>
|
|
491
|
-
</div>
|
|
492
|
-
</td>`,
|
|
493
|
-
)
|
|
494
|
-
} else if (dayCounter > numDays) {
|
|
495
|
-
cells.push(
|
|
496
|
-
html`<td class="pkt-cal-other">
|
|
497
|
-
<div
|
|
498
|
-
class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--label-only"
|
|
499
|
-
data-disabled="disabled"
|
|
500
|
-
>
|
|
501
|
-
<span class="pkt-btn__text pkt-txt-14-light">${dayCounter - numDays}</span>
|
|
502
|
-
</div>
|
|
503
|
-
</td>`,
|
|
504
|
-
)
|
|
505
|
-
dayCounter++
|
|
506
|
-
} else {
|
|
508
|
+
// Render each day in the week
|
|
509
|
+
for (let j = 0; j < DAYS_PER_WEEK; j++) {
|
|
510
|
+
const cellType = getCellType(i, j, dayCounter, dimensions)
|
|
511
|
+
|
|
512
|
+
if (cellType === 'current-month') {
|
|
507
513
|
cells.push(this.renderDayView(dayCounter, today))
|
|
508
514
|
dayCounter++
|
|
515
|
+
} else {
|
|
516
|
+
const dayNumber = getDayNumber(cellType, j, dayCounter, dimensions)
|
|
517
|
+
cells.push(this.renderEmptyDayCell(dayNumber))
|
|
518
|
+
if (cellType === 'next-month') {
|
|
519
|
+
dayCounter++
|
|
520
|
+
}
|
|
509
521
|
}
|
|
510
522
|
}
|
|
511
523
|
|
|
512
|
-
rows.push(
|
|
513
|
-
html`<tr class="pkt-cal-week-row" role="row">
|
|
514
|
-
${cells}
|
|
515
|
-
</tr>`,
|
|
516
|
-
)
|
|
524
|
+
rows.push(this.renderWeekRow(cells))
|
|
517
525
|
}
|
|
518
526
|
|
|
519
527
|
return rows
|
|
520
528
|
}
|
|
521
529
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
return !isDateSelectable(
|
|
528
|
-
date,
|
|
529
|
-
this.earliest,
|
|
530
|
-
this.latest,
|
|
531
|
-
excludedDatesStrings,
|
|
532
|
-
this.excludeweekdays,
|
|
533
|
-
)
|
|
530
|
+
/**
|
|
531
|
+
* Date validation
|
|
532
|
+
*/
|
|
533
|
+
private isExcluded(date: Date): boolean {
|
|
534
|
+
return isDateExcluded(date, this.getDateConstraints())
|
|
534
535
|
}
|
|
535
536
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
537
|
+
/**
|
|
538
|
+
* Month navigation
|
|
539
|
+
*/
|
|
540
|
+
isPrevMonthAllowed(): boolean {
|
|
541
|
+
return checkPrevMonthAllowed(this.year, this.month, this.earliest)
|
|
540
542
|
}
|
|
541
543
|
|
|
542
544
|
private prevMonth() {
|
|
@@ -545,14 +547,8 @@ export class PktCalendar extends PktElement {
|
|
|
545
547
|
this.changeMonth(newYear, newMonth)
|
|
546
548
|
}
|
|
547
549
|
|
|
548
|
-
isNextMonthAllowed() {
|
|
549
|
-
|
|
550
|
-
this.month === 11 ? this.year + 1 : this.year,
|
|
551
|
-
this.month === 11 ? 0 : this.month + 1,
|
|
552
|
-
1,
|
|
553
|
-
)
|
|
554
|
-
if (this.latest && newDate(this.latest) < nextMonth) return false
|
|
555
|
-
return true
|
|
550
|
+
isNextMonthAllowed(): boolean {
|
|
551
|
+
return checkNextMonthAllowed(this.year, this.month, this.latest)
|
|
556
552
|
}
|
|
557
553
|
|
|
558
554
|
private nextMonth() {
|
|
@@ -561,7 +557,7 @@ export class PktCalendar extends PktElement {
|
|
|
561
557
|
this.changeMonth(newYear, newMonth)
|
|
562
558
|
}
|
|
563
559
|
|
|
564
|
-
private changeMonth(year: number, month: number) {
|
|
560
|
+
private changeMonth(year: number, month: number): void {
|
|
565
561
|
this.year = typeof year === 'string' ? parseInt(year) : year
|
|
566
562
|
this.month = typeof month === 'string' ? parseInt(month) : month
|
|
567
563
|
this.currentmonth = newDateFromDate(new Date(this.year, this.month, 1))
|
|
@@ -570,40 +566,25 @@ export class PktCalendar extends PktElement {
|
|
|
570
566
|
this.selectableDates = []
|
|
571
567
|
}
|
|
572
568
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
start: this._selected[0],
|
|
578
|
-
end: date,
|
|
579
|
-
})
|
|
580
|
-
|
|
581
|
-
if (Array.isArray(days) && days.length) {
|
|
582
|
-
for (let i = 0; i < days.length; i++) {
|
|
583
|
-
this.excludedates.forEach((d: Date) => {
|
|
584
|
-
if (d > this._selected[0] && d < date) {
|
|
585
|
-
allowed = false
|
|
586
|
-
}
|
|
587
|
-
})
|
|
588
|
-
if (this.excludeweekdays.includes(getISODay(days[i]).toString())) {
|
|
589
|
-
allowed = false
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
return allowed
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
private emptySelected() {
|
|
569
|
+
/**
|
|
570
|
+
* Date selection logic
|
|
571
|
+
*/
|
|
572
|
+
private emptySelected(): void {
|
|
598
573
|
this.selected = []
|
|
599
574
|
this._selected = []
|
|
600
575
|
this.inRange = {}
|
|
601
576
|
}
|
|
602
577
|
|
|
603
|
-
|
|
604
|
-
if (this.selected
|
|
605
|
-
|
|
606
|
-
|
|
578
|
+
private normalizeSelected(): string[] {
|
|
579
|
+
if (typeof this.selected === 'string') {
|
|
580
|
+
return this.selected.split(',')
|
|
581
|
+
}
|
|
582
|
+
return this.selected
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
public addToSelected(selectedDate: Date): void {
|
|
586
|
+
this.selected = addToSelection(selectedDate, this.normalizeSelected())
|
|
587
|
+
this._selected = convertSelectedToDates(this.selected)
|
|
607
588
|
|
|
608
589
|
if (this.range && this.selected.length === 2) {
|
|
609
590
|
this.convertSelected()
|
|
@@ -611,83 +592,54 @@ export class PktCalendar extends PktElement {
|
|
|
611
592
|
}
|
|
612
593
|
}
|
|
613
594
|
|
|
614
|
-
public removeFromSelected(selectedDate: Date) {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
} else {
|
|
618
|
-
const selectedDateIndex = this.selected.indexOf(formatISODate(selectedDate))
|
|
619
|
-
const selectedCopy = [...this.selected]
|
|
620
|
-
const _selectedCopy = [...this._selected]
|
|
621
|
-
selectedCopy.splice(selectedDateIndex, 1)
|
|
622
|
-
_selectedCopy.splice(selectedDateIndex, 1)
|
|
623
|
-
this.selected = selectedCopy
|
|
624
|
-
this._selected = _selectedCopy
|
|
625
|
-
}
|
|
595
|
+
public removeFromSelected(selectedDate: Date): void {
|
|
596
|
+
this.selected = removeFromSelection(selectedDate, this.normalizeSelected())
|
|
597
|
+
this._selected = convertSelectedToDates(this.selected)
|
|
626
598
|
}
|
|
627
599
|
|
|
628
|
-
public toggleSelected(selectedDate: Date) {
|
|
629
|
-
|
|
630
|
-
this.selected
|
|
631
|
-
? this.removeFromSelected(selectedDate)
|
|
632
|
-
: !(this.maxMultiple && this.selected.length >= this.maxMultiple)
|
|
633
|
-
? this.addToSelected(selectedDate)
|
|
634
|
-
: null
|
|
600
|
+
public toggleSelected(selectedDate: Date): void {
|
|
601
|
+
this.selected = toggleSelection(selectedDate, this.normalizeSelected(), this.maxMultiple)
|
|
602
|
+
this._selected = convertSelectedToDates(this.selected)
|
|
635
603
|
}
|
|
636
604
|
|
|
637
|
-
private
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
if (this.selected.length === 1 && this._selected[0] > selectedDate) {
|
|
654
|
-
this.emptySelected()
|
|
655
|
-
}
|
|
656
|
-
this.addToSelected(selectedDate)
|
|
657
|
-
}
|
|
605
|
+
private isRangeAllowed(date: Date): boolean {
|
|
606
|
+
return checkRangeAllowed(date, this._selected, this.excludedates, this.excludeweekdays)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
private handleRangeSelect(selectedDate: Date): Promise<void> {
|
|
610
|
+
this.selected = handleRangeSelection(selectedDate, this.normalizeSelected(), {
|
|
611
|
+
excludedates: this.excludedates,
|
|
612
|
+
excludeweekdays: this.excludeweekdays,
|
|
613
|
+
})
|
|
614
|
+
this._selected = convertSelectedToDates(this.selected)
|
|
615
|
+
|
|
616
|
+
if (this.selected.length === 2) {
|
|
617
|
+
this.convertSelected()
|
|
618
|
+
} else if (this.selected.length === 1) {
|
|
619
|
+
// Clear inRange markers when starting a new range
|
|
620
|
+
this.inRange = {}
|
|
658
621
|
}
|
|
622
|
+
|
|
659
623
|
return Promise.resolve()
|
|
660
624
|
}
|
|
661
625
|
|
|
662
|
-
private handleRangeHover(date: Date) {
|
|
626
|
+
private handleRangeHover(date: Date): void {
|
|
663
627
|
if (
|
|
664
|
-
this.range
|
|
665
|
-
this._selected.length
|
|
666
|
-
this.isRangeAllowed(date)
|
|
667
|
-
this._selected[0]
|
|
628
|
+
!this.range ||
|
|
629
|
+
this._selected.length !== 1 ||
|
|
630
|
+
!this.isRangeAllowed(date) ||
|
|
631
|
+
this._selected[0] >= date
|
|
668
632
|
) {
|
|
669
|
-
this.rangeHovered = date
|
|
670
|
-
|
|
671
|
-
this.inRange = {}
|
|
672
|
-
const days = eachDayOfInterval({
|
|
673
|
-
start: this._selected[0],
|
|
674
|
-
end: date,
|
|
675
|
-
})
|
|
676
|
-
|
|
677
|
-
if (Array.isArray(days) && days.length) {
|
|
678
|
-
for (let i = 0; i < days.length; i++) {
|
|
679
|
-
const day = days[i]
|
|
680
|
-
const isInRange = day > this._selected[0] && day < date
|
|
681
|
-
this.inRange[formatISODate(day)] = isInRange
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
} else {
|
|
685
633
|
this.rangeHovered = null
|
|
634
|
+
return
|
|
686
635
|
}
|
|
636
|
+
|
|
637
|
+
this.rangeHovered = date
|
|
638
|
+
this.inRange = calculateRangeMap(this._selected[0], date)
|
|
687
639
|
}
|
|
688
640
|
|
|
689
|
-
public handleDateSelect(selectedDate: Date | null) {
|
|
690
|
-
if (!selectedDate) return
|
|
641
|
+
public handleDateSelect(selectedDate: Date | null): Promise<void> {
|
|
642
|
+
if (!selectedDate) return Promise.resolve()
|
|
691
643
|
if (this.range) {
|
|
692
644
|
this.handleRangeSelect(selectedDate)
|
|
693
645
|
} else if (this.multiple) {
|
|
@@ -711,27 +663,33 @@ export class PktCalendar extends PktElement {
|
|
|
711
663
|
return Promise.resolve()
|
|
712
664
|
}
|
|
713
665
|
|
|
714
|
-
|
|
666
|
+
/**
|
|
667
|
+
* Focus management and event handlers
|
|
668
|
+
*/
|
|
669
|
+
public focusOnCurrentDate(): void {
|
|
715
670
|
const currentDateISO = formatISODate(newDateFromDate(new Date()))
|
|
716
671
|
const el = this.querySelector(`button[data-date="${currentDateISO}"]`)
|
|
672
|
+
|
|
717
673
|
if (el instanceof HTMLButtonElement) {
|
|
718
674
|
this.focusedDate = currentDateISO
|
|
719
675
|
el.focus()
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
676
|
+
return
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Fall back to first selectable date
|
|
680
|
+
const firstSelectable = this.selectableDates.find((x) => !x.isDisabled)
|
|
681
|
+
if (firstSelectable) {
|
|
682
|
+
const firstSelectableEl = this.querySelector(
|
|
683
|
+
`button[data-date="${firstSelectable.currentDateISO}"]`,
|
|
684
|
+
)
|
|
685
|
+
if (firstSelectableEl instanceof HTMLButtonElement) {
|
|
686
|
+
this.focusedDate = firstSelectable.currentDateISO
|
|
687
|
+
firstSelectableEl.focus()
|
|
730
688
|
}
|
|
731
689
|
}
|
|
732
690
|
}
|
|
733
691
|
|
|
734
|
-
public closeEvent(e: FocusEvent) {
|
|
692
|
+
public closeEvent(e: FocusEvent): void {
|
|
735
693
|
if (
|
|
736
694
|
!this.contains(e.relatedTarget as Node) &&
|
|
737
695
|
!(e.target as Element).classList.contains('pkt-hide')
|
|
@@ -740,7 +698,7 @@ export class PktCalendar extends PktElement {
|
|
|
740
698
|
}
|
|
741
699
|
}
|
|
742
700
|
|
|
743
|
-
public close() {
|
|
701
|
+
public close(): void {
|
|
744
702
|
this.dispatchEvent(
|
|
745
703
|
new CustomEvent('close', {
|
|
746
704
|
detail: true,
|