@tailng-ui/primitives 0.54.0 → 0.56.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 (63) hide show
  1. package/package.json +2 -2
  2. package/src/lib/form/date-range-picker/__tests__/tng-date-range-picker.test-helpers.d.ts +25 -0
  3. package/src/lib/form/date-range-picker/__tests__/tng-date-range-picker.test-helpers.d.ts.map +1 -0
  4. package/src/lib/form/date-range-picker/__tests__/tng-date-range-picker.test-helpers.js +109 -0
  5. package/src/lib/form/date-range-picker/__tests__/tng-date-range-picker.test-helpers.js.map +1 -0
  6. package/src/lib/form/date-range-picker/date-range-picker.adapters.d.ts +5 -0
  7. package/src/lib/form/date-range-picker/date-range-picker.adapters.d.ts.map +1 -0
  8. package/src/lib/form/date-range-picker/date-range-picker.adapters.js +9 -0
  9. package/src/lib/form/date-range-picker/date-range-picker.adapters.js.map +1 -0
  10. package/src/lib/form/date-range-picker/date-range-picker.state.d.ts +10 -0
  11. package/src/lib/form/date-range-picker/date-range-picker.state.d.ts.map +1 -0
  12. package/src/lib/form/date-range-picker/date-range-picker.state.js +35 -0
  13. package/src/lib/form/date-range-picker/date-range-picker.state.js.map +1 -0
  14. package/src/lib/form/date-range-picker/date-range-picker.types.d.ts +280 -0
  15. package/src/lib/form/date-range-picker/date-range-picker.types.d.ts.map +1 -0
  16. package/src/lib/form/date-range-picker/date-range-picker.types.js +1 -0
  17. package/src/lib/form/date-range-picker/date-range-picker.types.js.map +1 -0
  18. package/src/lib/form/date-range-picker/date-range-picker.utils.d.ts +54 -0
  19. package/src/lib/form/date-range-picker/date-range-picker.utils.d.ts.map +1 -0
  20. package/src/lib/form/date-range-picker/date-range-picker.utils.js +37 -0
  21. package/src/lib/form/date-range-picker/date-range-picker.utils.js.map +1 -0
  22. package/src/lib/form/date-range-picker/index.d.ts +7 -0
  23. package/src/lib/form/date-range-picker/index.d.ts.map +1 -0
  24. package/src/lib/form/date-range-picker/index.js +7 -0
  25. package/src/lib/form/date-range-picker/index.js.map +1 -0
  26. package/src/lib/form/date-range-picker/tng-date-range-picker.d.ts +5 -0
  27. package/src/lib/form/date-range-picker/tng-date-range-picker.d.ts.map +1 -0
  28. package/src/lib/form/date-range-picker/tng-date-range-picker.js +1970 -0
  29. package/src/lib/form/date-range-picker/tng-date-range-picker.js.map +1 -0
  30. package/src/lib/form/date-range-picker/tng-date-range-picker.overlay.d.ts +87 -0
  31. package/src/lib/form/date-range-picker/tng-date-range-picker.overlay.d.ts.map +1 -0
  32. package/src/lib/form/date-range-picker/tng-date-range-picker.overlay.js +447 -0
  33. package/src/lib/form/date-range-picker/tng-date-range-picker.overlay.js.map +1 -0
  34. package/src/lib/form/date-range-picker/tng-date-range-picker.parts.d.ts +3155 -0
  35. package/src/lib/form/date-range-picker/tng-date-range-picker.parts.d.ts.map +1 -0
  36. package/src/lib/form/date-range-picker/tng-date-range-picker.parts.js +940 -0
  37. package/src/lib/form/date-range-picker/tng-date-range-picker.parts.js.map +1 -0
  38. package/src/lib/form/datepicker/datepicker.adapters.d.ts.map +1 -1
  39. package/src/lib/form/datepicker/datepicker.adapters.js +4 -189
  40. package/src/lib/form/datepicker/datepicker.adapters.js.map +1 -1
  41. package/src/lib/form/datepicker/datepicker.state.d.ts.map +1 -1
  42. package/src/lib/form/datepicker/datepicker.state.js +7 -86
  43. package/src/lib/form/datepicker/datepicker.state.js.map +1 -1
  44. package/src/lib/form/datepicker/datepicker.utils.d.ts +6 -28
  45. package/src/lib/form/datepicker/datepicker.utils.d.ts.map +1 -1
  46. package/src/lib/form/datepicker/datepicker.utils.js +14 -228
  47. package/src/lib/form/datepicker/datepicker.utils.js.map +1 -1
  48. package/src/lib/form/index.d.ts +2 -0
  49. package/src/lib/form/index.d.ts.map +1 -1
  50. package/src/lib/form/index.js +2 -0
  51. package/src/lib/form/index.js.map +1 -1
  52. package/src/lib/form/number-range/index.d.ts +3 -0
  53. package/src/lib/form/number-range/index.d.ts.map +1 -0
  54. package/src/lib/form/number-range/index.js +3 -0
  55. package/src/lib/form/number-range/index.js.map +1 -0
  56. package/src/lib/form/number-range/tng-number-range.d.ts +26 -0
  57. package/src/lib/form/number-range/tng-number-range.d.ts.map +1 -0
  58. package/src/lib/form/number-range/tng-number-range.js +49 -0
  59. package/src/lib/form/number-range/tng-number-range.js.map +1 -0
  60. package/src/lib/form/number-range/tng-number-range.types.d.ts +12 -0
  61. package/src/lib/form/number-range/tng-number-range.types.d.ts.map +1 -0
  62. package/src/lib/form/number-range/tng-number-range.types.js +1 -0
  63. package/src/lib/form/number-range/tng-number-range.types.js.map +1 -0
@@ -0,0 +1,1970 @@
1
+ import { createActiveDescendantController, createOverlayFocusHandoffController, createOverlayRuntime, createRovingFocusController, createTngIdFactory, createTypeaheadController, resolveFocusableElements, resolveGridNavigationKeyAction, resolveNavigableGridCell, } from '@tailng-ui/cdk';
2
+ import { coerceWeekStartsOn, defaultDateRangePickerDateAdapter, normalizeDateInput, } from './date-range-picker.adapters';
3
+ import { buildMonthGrid, buildMonthOptions, buildYearOptions, clampDateToMonth, compareMonthIdentity, datesEqual, findFirstEnabledDateInMonth, hasSelectableDateInMonth, hasSelectableDateInYear, isDateValueInMonth, moveDateSkippingDisabled, normalizeRangeOrder, resolveInitialFocusedSection, resolveLocaleWeekStartsOn, resolveViewForEscape, toDateKey, } from './date-range-picker.utils';
4
+ import { clearSelectionForMode, normalizeSelectionInput, rangeIncludesDate, selectionValuesEqual, valueIncludesDate, } from './date-range-picker.state';
5
+ const createDateRangePickerId = createTngIdFactory('tng-date-range-picker');
6
+ const createDateRangePickerFocusableId = createTngIdFactory('tng-date-range-picker-focusable');
7
+ const dateRangePickerRegistry = new Set();
8
+ const dateRangePickerFocusHandoff = createOverlayFocusHandoffController();
9
+ const emptyWeekdayLabels = Object.freeze([]);
10
+ const emptyCells = Object.freeze([]);
11
+ const emptyMonths = Object.freeze([]);
12
+ const emptyYears = Object.freeze([]);
13
+ function freezeAttributes(attributes) {
14
+ const normalized = {};
15
+ for (const [key, value] of Object.entries(attributes)) {
16
+ if (value === null || value === undefined || value === '') {
17
+ continue;
18
+ }
19
+ normalized[key] = value;
20
+ }
21
+ return Object.freeze(normalized);
22
+ }
23
+ function mapOverlayDismissReason(reason) {
24
+ if (reason === 'escape-key') {
25
+ return 'escape';
26
+ }
27
+ if (reason === 'outside-pointer' || reason === 'focus-outside') {
28
+ return 'outside';
29
+ }
30
+ return 'programmatic';
31
+ }
32
+ function hasDisallowedModifiers(event) {
33
+ return event.altKey === true || event.ctrlKey === true || event.metaKey === true;
34
+ }
35
+ function normalizeView(value) {
36
+ return value === 'month' || value === 'year' ? value : 'day';
37
+ }
38
+ function normalizePosition(value) {
39
+ return value === 'center' || value === 'end' ? value : 'start';
40
+ }
41
+ function normalizeOverlayMode(value) {
42
+ return value === 'push' || value === 'side' ? value : 'overlay';
43
+ }
44
+ function normalizeDirection(value) {
45
+ return value === 'rtl' ? 'rtl' : 'ltr';
46
+ }
47
+ function resolveDefaultLocale(ownerDocument) {
48
+ const documentLocale = ownerDocument?.documentElement?.lang?.trim();
49
+ if (documentLocale !== undefined && documentLocale !== '') {
50
+ return documentLocale;
51
+ }
52
+ const navigatorLocale = ownerDocument?.defaultView?.navigator.languages[0] ??
53
+ ownerDocument?.defaultView?.navigator.language;
54
+ if (typeof navigatorLocale === 'string' && navigatorLocale.trim() !== '') {
55
+ return navigatorLocale;
56
+ }
57
+ const intlLocale = Intl.DateTimeFormat().resolvedOptions().locale;
58
+ if (intlLocale.trim() !== '') {
59
+ return intlLocale;
60
+ }
61
+ return 'en-US';
62
+ }
63
+ function ensureYearPageSize(value) {
64
+ if (!Number.isFinite(value) || value === undefined) {
65
+ return 24;
66
+ }
67
+ return Math.max(12, Math.trunc(value));
68
+ }
69
+ function isRangeSelectionValue(value) {
70
+ return (typeof value === 'object' &&
71
+ value !== null &&
72
+ !Array.isArray(value) &&
73
+ Object.prototype.hasOwnProperty.call(value, 'start') &&
74
+ Object.prototype.hasOwnProperty.call(value, 'end'));
75
+ }
76
+ function getRangeStartDate(value) {
77
+ return isRangeSelectionValue(value) ? value.start : null;
78
+ }
79
+ function getRangeEndDate(value) {
80
+ return isRangeSelectionValue(value) ? value.end : null;
81
+ }
82
+ function hasCompleteRange(value) {
83
+ return getRangeStartDate(value) !== null && getRangeEndDate(value) !== null;
84
+ }
85
+ function hasPartialRange(value) {
86
+ return getRangeStartDate(value) !== null && getRangeEndDate(value) === null;
87
+ }
88
+ function readDocument(input) {
89
+ return input;
90
+ }
91
+ class DateRangePickerController {
92
+ instanceId;
93
+ gridId;
94
+ overlayId;
95
+ monthLabelId;
96
+ listenerSet = new Set();
97
+ typeahead = createTypeaheadController({
98
+ items: [],
99
+ matchStrategy: 'start',
100
+ });
101
+ dayRovingFocus;
102
+ monthRovingFocus;
103
+ yearRovingFocus;
104
+ dayActiveDescendant;
105
+ config;
106
+ state;
107
+ destroyed = false;
108
+ version = 0;
109
+ rangeAnchorDate = null;
110
+ focusVisibleDate = null;
111
+ hoverDate = null;
112
+ yearPageStart = 0;
113
+ cachedOutputsVersion = -1;
114
+ cachedOutputs = null;
115
+ cachedWeekdayLabels = emptyWeekdayLabels;
116
+ cachedCells = emptyCells;
117
+ cachedMonthOptions = emptyMonths;
118
+ cachedYearOptions = emptyYears;
119
+ cachedGridVersion = -1;
120
+ cachedMonthVersion = -1;
121
+ cachedYearVersion = -1;
122
+ triggerElement = null;
123
+ overlayElement = null;
124
+ restoreFocusTargetId = null;
125
+ focusLayerRegistered = false;
126
+ overlayLayerRegistered = false;
127
+ constructor(config) {
128
+ this.instanceId = config.id?.trim() || createDateRangePickerId();
129
+ this.gridId = `${this.instanceId}-grid`;
130
+ this.overlayId = `${this.instanceId}-overlay`;
131
+ this.monthLabelId = `${this.instanceId}-month-label`;
132
+ this.dayRovingFocus = createRovingFocusController({ itemIds: [], loop: false });
133
+ this.monthRovingFocus = createRovingFocusController({ itemIds: [], loop: false });
134
+ this.yearRovingFocus = createRovingFocusController({ itemIds: [], loop: false });
135
+ this.dayActiveDescendant = createActiveDescendantController({
136
+ hostId: this.gridId,
137
+ itemIds: [],
138
+ loop: false,
139
+ });
140
+ this.config = this.resolveConfig(config, null);
141
+ const initialActiveDate = this.resolveInitialActiveDate(this.config.value, this.config.today);
142
+ const visibleMonth = this.config.adapter.startOfMonth(initialActiveDate);
143
+ this.state = {
144
+ activeDate: initialActiveDate,
145
+ disabled: this.config.disabled,
146
+ focusedSection: resolveInitialFocusedSection(this.config.initialView),
147
+ inputText: this.formatValueForInput(this.config.value),
148
+ lastCloseReason: null,
149
+ open: config.defaultOpen ?? false,
150
+ validationError: null,
151
+ value: this.config.value,
152
+ view: this.config.initialView,
153
+ visibleMonth,
154
+ };
155
+ this.focusVisibleDate = initialActiveDate;
156
+ this.yearPageStart = this.resolveCenteredYearPageStart(this.config.adapter.getYear(initialActiveDate));
157
+ this.rangeAnchorDate = this.extractSelectionAnchor(this.state.value);
158
+ if (this.state.open) {
159
+ const activeElement = this.resolveActiveElement();
160
+ this.restoreFocusTargetId =
161
+ activeElement === null ? null : this.ensureElementId(activeElement);
162
+ this.registerOverlayLayer();
163
+ this.activateFocusLayer();
164
+ }
165
+ dateRangePickerRegistry.add(this);
166
+ }
167
+ clear() {
168
+ if (this.destroyed) {
169
+ return;
170
+ }
171
+ const previousValue = this.state.value;
172
+ const nextValue = clearSelectionForMode(this.config.selectionMode);
173
+ if (selectionValuesEqual(this.config.adapter, this.config.selectionMode, previousValue, nextValue)) {
174
+ return;
175
+ }
176
+ this.state.value = nextValue;
177
+ this.rangeAnchorDate = null;
178
+ this.hoverDate = null;
179
+ this.state.validationError = null;
180
+ this.state.inputText = this.formatValueForInput(nextValue);
181
+ const nextActive = this.resolveInitialActiveDate(nextValue, this.config.today);
182
+ this.applyActiveDate(nextActive, 'programmatic', true);
183
+ this.bumpVersion();
184
+ this.emit({
185
+ previousValue,
186
+ trigger: 'programmatic',
187
+ type: 'valueChange',
188
+ value: nextValue,
189
+ });
190
+ }
191
+ close(reason = 'programmatic') {
192
+ if (this.destroyed || !this.state.open) {
193
+ return;
194
+ }
195
+ this.emit({ reason, type: 'closeStart' });
196
+ this.state.open = false;
197
+ this.state.lastCloseReason = reason;
198
+ this.hoverDate = null;
199
+ this.unregisterOverlayLayer();
200
+ this.deactivateFocusLayer();
201
+ this.bumpVersion();
202
+ this.emit({ reason, type: 'closed' });
203
+ }
204
+ commitInputText() {
205
+ if (this.destroyed || !this.config.allowManualInput) {
206
+ return false;
207
+ }
208
+ const inputText = this.state.inputText.trim();
209
+ const parsed = this.parseInputText(inputText);
210
+ if (parsed === null || !this.isStrictInputCommitValue(inputText, parsed)) {
211
+ this.state.validationError = 'invalid-input';
212
+ this.bumpVersion();
213
+ return false;
214
+ }
215
+ if (this.isDateDisabled(parsed)) {
216
+ this.state.validationError = 'out-of-range';
217
+ this.bumpVersion();
218
+ return false;
219
+ }
220
+ this.state.validationError = null;
221
+ this.selectDate(parsed, { trigger: 'text-input' });
222
+ return true;
223
+ }
224
+ destroy() {
225
+ if (this.destroyed) {
226
+ return;
227
+ }
228
+ this.destroyed = true;
229
+ this.unregisterOverlayLayer();
230
+ this.unregisterFocusLayer();
231
+ this.listenerSet.clear();
232
+ dateRangePickerRegistry.delete(this);
233
+ }
234
+ formatDate(date, format = 'label') {
235
+ return this.config.adapter.format(date, format, this.config.locale);
236
+ }
237
+ getOutputs() {
238
+ if (this.cachedOutputs !== null && this.cachedOutputsVersion === this.version) {
239
+ return this.cachedOutputs;
240
+ }
241
+ const outputs = Object.freeze({
242
+ activeDate: this.state.activeDate,
243
+ cells: this.getCells(),
244
+ endDate: getRangeEndDate(this.state.value),
245
+ focusedDate: this.focusVisibleDate,
246
+ focusedSection: this.state.focusedSection,
247
+ getCellAttributes: (cellOrDate) => this.resolveCellAttributes(cellOrDate),
248
+ getGridAttributes: () => this.resolveGridAttributes(),
249
+ getHostAttributes: () => this.resolveHostAttributes(),
250
+ getMonthAttributes: (monthOrOption) => this.resolveMonthAttributes(monthOrOption),
251
+ getOverlayAttributes: () => this.resolveOverlayAttributes(),
252
+ getTriggerAttributes: () => this.resolveTriggerAttributes(),
253
+ getYearAttributes: (yearOrOption) => this.resolveYearAttributes(yearOrOption),
254
+ inputText: this.state.inputText,
255
+ labelMonthYear: this.config.adapter.format(this.state.visibleMonth, 'month-year', this.config.locale),
256
+ layout: this.resolveLayout(),
257
+ monthOptions: this.getMonthOptions(),
258
+ open: this.state.open,
259
+ previewEndDate: this.hoverDate,
260
+ startDate: getRangeStartDate(this.state.value),
261
+ validationError: this.state.validationError,
262
+ value: this.state.value,
263
+ view: this.state.view,
264
+ visibleMonth: this.state.visibleMonth,
265
+ weekdayLabels: this.getWeekdayLabels(),
266
+ yearOptions: this.getYearOptions(),
267
+ });
268
+ this.cachedOutputsVersion = this.version;
269
+ this.cachedOutputs = outputs;
270
+ return outputs;
271
+ }
272
+ getState() {
273
+ return Object.freeze({
274
+ activeDate: this.state.activeDate,
275
+ disabled: this.state.disabled,
276
+ endDate: getRangeEndDate(this.state.value),
277
+ focusedDate: this.focusVisibleDate,
278
+ focusedSection: this.state.focusedSection,
279
+ inputText: this.state.inputText,
280
+ lastCloseReason: this.state.lastCloseReason,
281
+ open: this.state.open,
282
+ previewEndDate: this.hoverDate,
283
+ startDate: getRangeStartDate(this.state.value),
284
+ validationError: this.state.validationError,
285
+ value: this.state.value,
286
+ view: this.state.view,
287
+ visibleMonth: this.state.visibleMonth,
288
+ });
289
+ }
290
+ goToNextMonth() {
291
+ this.nextMonth();
292
+ }
293
+ goToPrevMonth() {
294
+ this.prevMonth();
295
+ }
296
+ handleCellClick(date, options = {}) {
297
+ this.selectDate(date, {
298
+ shiftKey: options.shiftKey,
299
+ trigger: 'pointer',
300
+ });
301
+ }
302
+ handleCellPointerEnter(date) {
303
+ if (this.destroyed || this.rangeAnchorDate === null || hasCompleteRange(this.state.value)) {
304
+ return;
305
+ }
306
+ const normalized = normalizeDateInput(this.config.adapter, date, this.config.locale);
307
+ if (normalized === null || this.isDateDisabled(normalized)) {
308
+ return;
309
+ }
310
+ const previousPreviewEndDate = this.hoverDate;
311
+ if (previousPreviewEndDate !== null &&
312
+ datesEqual(this.config.adapter, previousPreviewEndDate, normalized)) {
313
+ return;
314
+ }
315
+ this.hoverDate = normalized;
316
+ this.bumpVersion();
317
+ this.emit({
318
+ previewEndDate: normalized,
319
+ previousPreviewEndDate,
320
+ trigger: 'pointer',
321
+ type: 'previewChange',
322
+ });
323
+ }
324
+ handleGridKeyDown(event) {
325
+ if (this.destroyed || this.state.disabled) {
326
+ return;
327
+ }
328
+ if (event.key === 'Escape') {
329
+ if (this.state.open) {
330
+ event.preventDefault();
331
+ this.close('escape');
332
+ }
333
+ return;
334
+ }
335
+ if (this.handlePageNavigation(event)) {
336
+ return;
337
+ }
338
+ const action = resolveGridNavigationKeyAction(event, {
339
+ direction: this.config.direction,
340
+ });
341
+ if (action === null) {
342
+ if (this.config.enableTypeahead) {
343
+ this.handleTypeahead(event.key);
344
+ }
345
+ return;
346
+ }
347
+ if (action.preventDefault) {
348
+ event.preventDefault();
349
+ }
350
+ if (action.type === 'exit') {
351
+ return;
352
+ }
353
+ if (action.type === 'activate') {
354
+ this.selectDate(this.state.activeDate, {
355
+ shiftKey: event.shiftKey === true,
356
+ trigger: 'keyboard',
357
+ });
358
+ return;
359
+ }
360
+ this.handleResolvedDayGridAction(action.type, event.shiftKey === true);
361
+ }
362
+ handleMonthGridKeyDown(event) {
363
+ this.handlePickerGridKeyDown(event, 'month');
364
+ }
365
+ handleOverlayKeyDown(event) {
366
+ if (this.destroyed || !this.config.trapFocus || event.key !== 'Tab') {
367
+ return;
368
+ }
369
+ const overlay = this.overlayElement;
370
+ if (overlay === null || !dateRangePickerFocusHandoff.isTrapActive(this.instanceId)) {
371
+ return;
372
+ }
373
+ const focusableMemberIds = this.resolveTabbableOverlayMemberIds(overlay);
374
+ const firstMemberId = focusableMemberIds[0];
375
+ if (firstMemberId === undefined) {
376
+ return;
377
+ }
378
+ const lastMemberId = focusableMemberIds[focusableMemberIds.length - 1] ?? firstMemberId;
379
+ const activeElement = this.resolveActiveElement();
380
+ const activeElementId = activeElement !== null && overlay.contains(activeElement)
381
+ ? this.ensureElementId(activeElement)
382
+ : null;
383
+ let candidateId = null;
384
+ if (activeElementId === null) {
385
+ candidateId = event.shiftKey ? lastMemberId : firstMemberId;
386
+ }
387
+ else if (activeElementId === firstMemberId && event.shiftKey) {
388
+ candidateId = lastMemberId;
389
+ }
390
+ else if (activeElementId === lastMemberId && !event.shiftKey) {
391
+ candidateId = firstMemberId;
392
+ }
393
+ else {
394
+ dateRangePickerFocusHandoff.recordFocus(this.instanceId, activeElementId);
395
+ return;
396
+ }
397
+ const resolvedId = dateRangePickerFocusHandoff.resolveFocusCandidate(this.instanceId, candidateId);
398
+ if (resolvedId === null) {
399
+ return;
400
+ }
401
+ const nextFocusTarget = this.resolveElementById(resolvedId);
402
+ if (nextFocusTarget === null) {
403
+ return;
404
+ }
405
+ event.preventDefault();
406
+ nextFocusTarget.focus();
407
+ dateRangePickerFocusHandoff.recordFocus(this.instanceId, resolvedId);
408
+ }
409
+ handleTriggerKeyDown(event) {
410
+ if (this.destroyed || this.state.disabled) {
411
+ return;
412
+ }
413
+ if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') {
414
+ event.preventDefault();
415
+ this.open();
416
+ }
417
+ }
418
+ handleYearGridKeyDown(event) {
419
+ this.handlePickerGridKeyDown(event, 'year');
420
+ }
421
+ nextMonth() {
422
+ this.shiftVisibleMonth(1);
423
+ }
424
+ nextYear() {
425
+ this.shiftVisibleYear(1);
426
+ }
427
+ open() {
428
+ if (this.destroyed || this.state.disabled || this.state.open) {
429
+ return;
430
+ }
431
+ if (this.config.closeOthersOnOpen) {
432
+ for (const controller of dateRangePickerRegistry) {
433
+ if (controller === this) {
434
+ continue;
435
+ }
436
+ controller.close('programmatic');
437
+ }
438
+ }
439
+ this.emit({ previous: false, type: 'openStart' });
440
+ this.state.open = true;
441
+ const activeElement = this.resolveActiveElement();
442
+ if (activeElement !== null) {
443
+ this.restoreFocusTargetId = this.ensureElementId(activeElement);
444
+ }
445
+ else if (this.triggerElement !== null) {
446
+ this.restoreFocusTargetId = this.ensureElementId(this.triggerElement);
447
+ }
448
+ else {
449
+ this.restoreFocusTargetId = null;
450
+ }
451
+ this.state.lastCloseReason = null;
452
+ if (!this.config.preserveViewOnOpenClose) {
453
+ this.state.view = this.config.initialView;
454
+ this.state.focusedSection = resolveInitialFocusedSection(this.state.view);
455
+ }
456
+ this.applyActiveDate(this.resolveInitialActiveDate(this.state.value, this.config.today), 'programmatic', true);
457
+ this.registerOverlayLayer();
458
+ this.activateFocusLayer();
459
+ this.focusCurrentOverlayTarget();
460
+ this.bumpVersion();
461
+ this.emit({ next: true, previous: false, type: 'opened' });
462
+ }
463
+ parseInputText(text) {
464
+ return this.config.adapter.parse(text, this.config.locale);
465
+ }
466
+ prevMonth() {
467
+ this.shiftVisibleMonth(-1);
468
+ }
469
+ prevYear() {
470
+ this.shiftVisibleYear(-1);
471
+ }
472
+ registerOverlay(element) {
473
+ this.overlayElement = element;
474
+ if (this.state.open) {
475
+ this.registerOverlayLayer();
476
+ this.registerFocusLayer();
477
+ this.activateFocusLayer();
478
+ this.focusCurrentOverlayTarget();
479
+ this.bumpVersion();
480
+ }
481
+ else if (element === null) {
482
+ this.unregisterFocusLayer();
483
+ }
484
+ }
485
+ registerTrigger(element) {
486
+ this.triggerElement = element;
487
+ }
488
+ selectDate(date, options = {}) {
489
+ if (this.destroyed || this.state.disabled) {
490
+ return;
491
+ }
492
+ const normalized = normalizeDateInput(this.config.adapter, date, this.config.locale);
493
+ if (normalized === null) {
494
+ this.state.validationError = 'invalid-value';
495
+ this.bumpVersion();
496
+ return;
497
+ }
498
+ if (this.isDateDisabled(normalized)) {
499
+ return;
500
+ }
501
+ const trigger = options.trigger ?? 'programmatic';
502
+ const previousValue = this.state.value;
503
+ this.state.validationError = null;
504
+ const nextValue = this.resolveNextSelection(normalized, options.shiftKey === true);
505
+ if (selectionValuesEqual(this.config.adapter, this.config.selectionMode, previousValue, nextValue)) {
506
+ if (this.state.validationError !== null) {
507
+ this.bumpVersion();
508
+ }
509
+ return;
510
+ }
511
+ this.state.value = nextValue;
512
+ this.state.inputText = this.formatValueForInput(nextValue);
513
+ this.rangeAnchorDate = this.extractSelectionAnchor(nextValue) ?? normalized;
514
+ this.applyActiveDate(normalized, trigger, true);
515
+ this.hoverDate = null;
516
+ this.bumpVersion();
517
+ this.emit({
518
+ previousValue,
519
+ trigger,
520
+ type: 'valueChange',
521
+ value: nextValue,
522
+ });
523
+ if (this.config.closeOnSelect && this.state.open && hasCompleteRange(nextValue)) {
524
+ this.close('select');
525
+ }
526
+ }
527
+ selectMonth(monthIndex) {
528
+ if (this.destroyed) {
529
+ return;
530
+ }
531
+ const normalizedMonth = Math.max(0, Math.min(11, Math.trunc(monthIndex)));
532
+ const nextMonth = this.config.adapter.createDate(this.config.adapter.getYear(this.state.visibleMonth), normalizedMonth, 1);
533
+ if (!hasSelectableDateInMonth(this.config.adapter, nextMonth, (date) => this.isDateDisabled(date))) {
534
+ return;
535
+ }
536
+ const previousMonth = this.state.visibleMonth;
537
+ this.state.visibleMonth = nextMonth;
538
+ const nextActive = clampDateToMonth(this.config.adapter, this.state.activeDate, nextMonth);
539
+ this.applyActiveDate(nextActive, 'programmatic', true);
540
+ this.bumpVersion();
541
+ this.emit({
542
+ previousMonth,
543
+ type: 'monthChange',
544
+ visibleMonth: nextMonth,
545
+ });
546
+ if (this.config.autoCommitView) {
547
+ this.setView('day');
548
+ }
549
+ }
550
+ selectYear(year) {
551
+ if (this.destroyed) {
552
+ return;
553
+ }
554
+ const normalizedYear = Math.trunc(year);
555
+ if (!hasSelectableDateInYear(this.config.adapter, normalizedYear, this.state.visibleMonth, (date) => this.isDateDisabled(date))) {
556
+ return;
557
+ }
558
+ const previousMonth = this.state.visibleMonth;
559
+ const previousYear = this.config.adapter.getYear(previousMonth);
560
+ const nextMonth = this.config.adapter.createDate(normalizedYear, this.config.adapter.getMonth(previousMonth), 1);
561
+ this.state.visibleMonth = nextMonth;
562
+ this.yearPageStart = this.resolveCenteredYearPageStart(normalizedYear);
563
+ const nextActive = clampDateToMonth(this.config.adapter, this.state.activeDate, nextMonth);
564
+ this.applyActiveDate(nextActive, 'programmatic', true);
565
+ this.bumpVersion();
566
+ this.emit({
567
+ previousMonth,
568
+ type: 'monthChange',
569
+ visibleMonth: nextMonth,
570
+ });
571
+ this.emit({
572
+ previousYear,
573
+ type: 'yearChange',
574
+ year: normalizedYear,
575
+ });
576
+ if (this.config.autoCommitView) {
577
+ this.setView('day');
578
+ }
579
+ }
580
+ setActiveDate(date, trigger = 'programmatic') {
581
+ if (this.destroyed) {
582
+ return;
583
+ }
584
+ const normalized = normalizeDateInput(this.config.adapter, date, this.config.locale);
585
+ if (normalized === null) {
586
+ return;
587
+ }
588
+ this.applyActiveDate(normalized, trigger, true);
589
+ this.bumpVersion();
590
+ }
591
+ setConfig(config) {
592
+ if (this.destroyed) {
593
+ return;
594
+ }
595
+ const previousMonth = this.state.visibleMonth;
596
+ const previousYear = this.config.adapter.getYear(previousMonth);
597
+ const previousTrapFocus = this.config.trapFocus;
598
+ this.config = this.resolveConfig(config, this.config);
599
+ this.state.disabled = this.config.disabled;
600
+ this.state.value = this.coerceSelectionWithinConstraints(this.state.value);
601
+ if (!hasPartialRange(this.state.value) ||
602
+ (this.hoverDate !== null && this.isDateDisabled(this.hoverDate))) {
603
+ this.hoverDate = null;
604
+ }
605
+ this.state.activeDate = this.resolveValidDate(this.state.activeDate, this.state.visibleMonth);
606
+ if (!isDateValueInMonth(this.config.adapter, this.state.activeDate, this.state.visibleMonth)) {
607
+ this.state.visibleMonth = this.config.adapter.startOfMonth(this.state.activeDate);
608
+ }
609
+ this.yearPageStart = this.resolveCenteredYearPageStart(this.config.adapter.getYear(this.state.activeDate));
610
+ this.state.inputText = this.formatValueForInput(this.state.value);
611
+ if (this.focusLayerRegistered && previousTrapFocus !== this.config.trapFocus) {
612
+ this.unregisterFocusLayer();
613
+ this.registerFocusLayer();
614
+ if (this.state.open) {
615
+ this.activateFocusLayer();
616
+ }
617
+ }
618
+ this.bumpVersion();
619
+ if (compareMonthIdentity(this.config.adapter, previousMonth, this.state.visibleMonth) !== 0) {
620
+ this.emit({
621
+ previousMonth,
622
+ type: 'monthChange',
623
+ visibleMonth: this.state.visibleMonth,
624
+ });
625
+ }
626
+ const currentYear = this.config.adapter.getYear(this.state.visibleMonth);
627
+ if (previousYear !== currentYear) {
628
+ this.emit({
629
+ previousYear,
630
+ type: 'yearChange',
631
+ year: currentYear,
632
+ });
633
+ }
634
+ }
635
+ setDisabled(disabled) {
636
+ if (this.destroyed || this.state.disabled === disabled) {
637
+ return;
638
+ }
639
+ this.state.disabled = disabled;
640
+ this.bumpVersion();
641
+ }
642
+ setFocusedSection(section) {
643
+ if (this.destroyed || this.state.focusedSection === section) {
644
+ return;
645
+ }
646
+ this.state.focusedSection = section;
647
+ this.bumpVersion();
648
+ }
649
+ setInputText(text) {
650
+ if (this.destroyed || !this.config.allowManualInput) {
651
+ return;
652
+ }
653
+ this.state.inputText = text;
654
+ this.state.validationError = null;
655
+ if (this.config.onPartialInputCommit) {
656
+ this.commitInputText();
657
+ return;
658
+ }
659
+ this.bumpVersion();
660
+ }
661
+ setOpen(open) {
662
+ if (open) {
663
+ this.open();
664
+ return;
665
+ }
666
+ this.close('programmatic');
667
+ }
668
+ setState(patch) {
669
+ if (this.destroyed) {
670
+ return;
671
+ }
672
+ const previousView = this.state.view;
673
+ const previousMonth = this.state.visibleMonth;
674
+ const previousYear = this.config.adapter.getYear(previousMonth);
675
+ const previousActive = this.state.activeDate;
676
+ const previousValue = this.state.value;
677
+ if (patch.view !== undefined) {
678
+ this.state.view = normalizeView(patch.view);
679
+ this.state.focusedSection = resolveInitialFocusedSection(this.state.view);
680
+ }
681
+ if (patch.disabled !== undefined) {
682
+ this.state.disabled = patch.disabled;
683
+ }
684
+ if (patch.visibleMonth !== undefined) {
685
+ const normalized = normalizeDateInput(this.config.adapter, patch.visibleMonth, this.config.locale);
686
+ if (normalized !== null) {
687
+ this.state.visibleMonth = this.config.adapter.startOfMonth(normalized);
688
+ }
689
+ }
690
+ if (patch.activeDate !== undefined) {
691
+ const normalized = normalizeDateInput(this.config.adapter, patch.activeDate, this.config.locale);
692
+ if (normalized !== null) {
693
+ this.state.activeDate = this.resolveValidDate(normalized, this.state.visibleMonth);
694
+ }
695
+ }
696
+ if (patch.value !== undefined) {
697
+ const normalized = normalizeSelectionInput(this.config.adapter, this.config.selectionMode, patch.value, this.config.locale);
698
+ this.state.value = this.coerceSelectionWithinConstraints(normalized.value);
699
+ this.rangeAnchorDate = this.extractSelectionAnchor(this.state.value);
700
+ this.hoverDate = null;
701
+ }
702
+ if (patch.inputText !== undefined) {
703
+ this.state.inputText = patch.inputText;
704
+ }
705
+ else {
706
+ this.state.inputText = this.formatValueForInput(this.state.value);
707
+ }
708
+ this.bumpVersion();
709
+ if (previousView !== this.state.view) {
710
+ this.emit({
711
+ previousView,
712
+ type: 'viewChange',
713
+ view: this.state.view,
714
+ });
715
+ }
716
+ if (compareMonthIdentity(this.config.adapter, previousMonth, this.state.visibleMonth) !== 0) {
717
+ this.emit({
718
+ previousMonth,
719
+ type: 'monthChange',
720
+ visibleMonth: this.state.visibleMonth,
721
+ });
722
+ }
723
+ const currentYear = this.config.adapter.getYear(this.state.visibleMonth);
724
+ if (previousYear !== currentYear) {
725
+ this.emit({
726
+ previousYear,
727
+ type: 'yearChange',
728
+ year: currentYear,
729
+ });
730
+ }
731
+ if (!datesEqual(this.config.adapter, previousActive, this.state.activeDate)) {
732
+ this.emit({
733
+ activeDate: this.state.activeDate,
734
+ previousActiveDate: previousActive,
735
+ trigger: 'programmatic',
736
+ type: 'activeChange',
737
+ });
738
+ }
739
+ if (!selectionValuesEqual(this.config.adapter, this.config.selectionMode, previousValue, this.state.value)) {
740
+ this.emit({
741
+ previousValue,
742
+ trigger: 'programmatic',
743
+ type: 'valueChange',
744
+ value: this.state.value,
745
+ });
746
+ }
747
+ }
748
+ setValue(value) {
749
+ if (this.destroyed) {
750
+ return;
751
+ }
752
+ const normalized = normalizeSelectionInput(this.config.adapter, this.config.selectionMode, value, this.config.locale);
753
+ const previousValidationError = this.state.validationError;
754
+ if (normalized.validationError !== null) {
755
+ this.state.validationError = normalized.validationError;
756
+ }
757
+ const coerced = this.coerceSelectionWithinConstraints(normalized.value);
758
+ if (selectionValuesEqual(this.config.adapter, this.config.selectionMode, this.state.value, coerced)) {
759
+ if (previousValidationError !== this.state.validationError) {
760
+ this.bumpVersion();
761
+ }
762
+ return;
763
+ }
764
+ const previousValue = this.state.value;
765
+ this.state.value = coerced;
766
+ this.state.validationError = normalized.validationError;
767
+ this.state.inputText = this.formatValueForInput(coerced);
768
+ this.hoverDate = null;
769
+ const anchor = this.extractSelectionAnchor(coerced);
770
+ this.rangeAnchorDate = anchor;
771
+ if (anchor !== null) {
772
+ this.applyActiveDate(anchor, 'programmatic', true);
773
+ }
774
+ this.bumpVersion();
775
+ this.emit({
776
+ previousValue,
777
+ trigger: 'programmatic',
778
+ type: 'valueChange',
779
+ value: coerced,
780
+ });
781
+ }
782
+ setView(view) {
783
+ if (this.destroyed) {
784
+ return;
785
+ }
786
+ const normalizedView = normalizeView(view);
787
+ if (this.state.view === normalizedView) {
788
+ return;
789
+ }
790
+ const previousView = this.state.view;
791
+ const previousMonth = this.state.visibleMonth;
792
+ const previousYear = this.config.adapter.getYear(previousMonth);
793
+ this.state.view = normalizedView;
794
+ this.state.focusedSection = resolveInitialFocusedSection(normalizedView);
795
+ if (normalizedView === 'year') {
796
+ this.yearPageStart = this.resolveCenteredYearPageStart(this.config.adapter.getYear(this.state.activeDate));
797
+ }
798
+ if (normalizedView === 'day' &&
799
+ !isDateValueInMonth(this.config.adapter, this.state.activeDate, this.state.visibleMonth)) {
800
+ this.state.visibleMonth = this.config.adapter.startOfMonth(this.state.activeDate);
801
+ }
802
+ this.bumpVersion();
803
+ this.emit({
804
+ previousView,
805
+ type: 'viewChange',
806
+ view: normalizedView,
807
+ });
808
+ if (compareMonthIdentity(this.config.adapter, previousMonth, this.state.visibleMonth) !== 0) {
809
+ this.emit({
810
+ previousMonth,
811
+ type: 'monthChange',
812
+ visibleMonth: this.state.visibleMonth,
813
+ });
814
+ }
815
+ const currentYear = this.config.adapter.getYear(this.state.visibleMonth);
816
+ if (previousYear !== currentYear) {
817
+ this.emit({
818
+ previousYear,
819
+ type: 'yearChange',
820
+ year: currentYear,
821
+ });
822
+ }
823
+ }
824
+ setVisibleMonth(valueOrYear, month) {
825
+ if (this.destroyed) {
826
+ return;
827
+ }
828
+ let nextMonth;
829
+ if (typeof valueOrYear === 'number') {
830
+ if (month === undefined) {
831
+ return;
832
+ }
833
+ nextMonth = this.config.adapter.createDate(Math.trunc(valueOrYear), Math.trunc(month), 1);
834
+ }
835
+ else {
836
+ const normalized = normalizeDateInput(this.config.adapter, valueOrYear, this.config.locale);
837
+ nextMonth = normalized === null ? null : this.config.adapter.startOfMonth(normalized);
838
+ }
839
+ if (nextMonth === null) {
840
+ return;
841
+ }
842
+ if (!hasSelectableDateInMonth(this.config.adapter, nextMonth, (date) => this.isDateDisabled(date))) {
843
+ return;
844
+ }
845
+ const previousMonth = this.state.visibleMonth;
846
+ const previousYear = this.config.adapter.getYear(previousMonth);
847
+ this.state.visibleMonth = this.config.adapter.startOfMonth(nextMonth);
848
+ this.yearPageStart = this.resolveCenteredYearPageStart(this.config.adapter.getYear(this.state.visibleMonth));
849
+ this.state.activeDate = this.resolveValidDate(this.state.activeDate, this.state.visibleMonth);
850
+ this.bumpVersion();
851
+ if (compareMonthIdentity(this.config.adapter, previousMonth, this.state.visibleMonth) !== 0) {
852
+ this.emit({
853
+ previousMonth,
854
+ type: 'monthChange',
855
+ visibleMonth: this.state.visibleMonth,
856
+ });
857
+ }
858
+ const currentYear = this.config.adapter.getYear(this.state.visibleMonth);
859
+ if (previousYear !== currentYear) {
860
+ this.emit({
861
+ previousYear,
862
+ type: 'yearChange',
863
+ year: currentYear,
864
+ });
865
+ }
866
+ }
867
+ setVisibleYear(year) {
868
+ this.selectYear(year);
869
+ }
870
+ showDaysPanel() {
871
+ this.setView('day');
872
+ }
873
+ showMonthsPanel() {
874
+ this.setView('month');
875
+ }
876
+ showYearsPanel() {
877
+ this.setView('year');
878
+ }
879
+ subscribe(listener) {
880
+ this.listenerSet.add(listener);
881
+ return () => {
882
+ this.listenerSet.delete(listener);
883
+ };
884
+ }
885
+ toggleOpen() {
886
+ if (this.state.open) {
887
+ this.close('programmatic');
888
+ return;
889
+ }
890
+ this.open();
891
+ }
892
+ applyActiveDate(nextDate, trigger, focusVisible) {
893
+ const resolved = this.resolveValidDate(nextDate, this.state.visibleMonth);
894
+ if (datesEqual(this.config.adapter, resolved, this.state.activeDate)) {
895
+ this.focusVisibleDate = focusVisible ? resolved : null;
896
+ this.syncPreviewDateForFocus(resolved, trigger);
897
+ this.bumpVersion();
898
+ return;
899
+ }
900
+ const previousActiveDate = this.state.activeDate;
901
+ this.state.activeDate = resolved;
902
+ this.focusVisibleDate = focusVisible ? resolved : null;
903
+ this.syncPreviewDateForFocus(resolved, trigger);
904
+ if (!isDateValueInMonth(this.config.adapter, resolved, this.state.visibleMonth)) {
905
+ const previousMonth = this.state.visibleMonth;
906
+ const previousYear = this.config.adapter.getYear(previousMonth);
907
+ this.state.visibleMonth = this.config.adapter.startOfMonth(resolved);
908
+ this.emit({
909
+ previousMonth,
910
+ type: 'monthChange',
911
+ visibleMonth: this.state.visibleMonth,
912
+ });
913
+ const currentYear = this.config.adapter.getYear(this.state.visibleMonth);
914
+ if (previousYear !== currentYear) {
915
+ this.emit({
916
+ previousYear,
917
+ type: 'yearChange',
918
+ year: currentYear,
919
+ });
920
+ }
921
+ }
922
+ this.emit({
923
+ activeDate: resolved,
924
+ previousActiveDate,
925
+ trigger,
926
+ type: 'activeChange',
927
+ });
928
+ this.bumpVersion();
929
+ }
930
+ applyPickerActiveDate(nextDate, trigger, focusVisible) {
931
+ const resolved = this.resolveValidDate(nextDate, this.config.adapter.startOfMonth(nextDate));
932
+ if (datesEqual(this.config.adapter, resolved, this.state.activeDate)) {
933
+ this.focusVisibleDate = focusVisible ? resolved : null;
934
+ this.bumpVersion();
935
+ return;
936
+ }
937
+ const previousActiveDate = this.state.activeDate;
938
+ this.state.activeDate = resolved;
939
+ this.focusVisibleDate = focusVisible ? resolved : null;
940
+ this.emit({
941
+ activeDate: resolved,
942
+ previousActiveDate,
943
+ trigger,
944
+ type: 'activeChange',
945
+ });
946
+ this.bumpVersion();
947
+ }
948
+ bumpVersion() {
949
+ this.version += 1;
950
+ }
951
+ syncPreviewDateForFocus(date, trigger) {
952
+ if (trigger !== 'keyboard') {
953
+ return;
954
+ }
955
+ if (getRangeStartDate(this.state.value) === null ||
956
+ getRangeEndDate(this.state.value) !== null) {
957
+ this.hoverDate = null;
958
+ return;
959
+ }
960
+ if (!this.isDateDisabled(date)) {
961
+ this.hoverDate = date;
962
+ }
963
+ }
964
+ coerceSelectionWithinConstraints(value) {
965
+ if (value === null) {
966
+ return clearSelectionForMode(this.config.selectionMode);
967
+ }
968
+ const range = normalizeRangeOrder(this.config.adapter, value);
969
+ if (range.start === null) {
970
+ return Object.freeze({ end: null, start: null });
971
+ }
972
+ if (this.isDateDisabled(range.start, { includeComponentDisabled: false })) {
973
+ this.state.validationError = 'out-of-range';
974
+ return Object.freeze({ end: null, start: null });
975
+ }
976
+ if (range.end === null) {
977
+ return range;
978
+ }
979
+ if (this.isDateDisabled(range.end, { includeComponentDisabled: false })) {
980
+ this.state.validationError = 'range-disabled';
981
+ return Object.freeze({ end: null, start: range.start });
982
+ }
983
+ return range;
984
+ }
985
+ emit(event) {
986
+ for (const listener of this.listenerSet) {
987
+ listener(event);
988
+ }
989
+ }
990
+ extractSelectionAnchor(value) {
991
+ if (value === null) {
992
+ return null;
993
+ }
994
+ if (isRangeSelectionValue(value)) {
995
+ return value.end ?? value.start;
996
+ }
997
+ return null;
998
+ }
999
+ formatValueForInput(value) {
1000
+ if (value === null) {
1001
+ return '';
1002
+ }
1003
+ const range = value;
1004
+ if (range.start === null) {
1005
+ return '';
1006
+ }
1007
+ if (range.end === null) {
1008
+ return this.config.adapter.format(range.start, 'input', this.config.locale);
1009
+ }
1010
+ return `${this.config.adapter.format(range.start, 'input', this.config.locale)} – ${this.config.adapter.format(range.end, 'input', this.config.locale)}`;
1011
+ }
1012
+ isStrictInputCommitValue(inputText, parsed) {
1013
+ const canonicalInputText = this.config.adapter
1014
+ .format(parsed, 'input', this.config.locale)
1015
+ .trim();
1016
+ return inputText === canonicalInputText;
1017
+ }
1018
+ getCells() {
1019
+ if (this.cachedGridVersion === this.version) {
1020
+ return this.cachedCells;
1021
+ }
1022
+ const cells = buildMonthGrid({
1023
+ activeDate: this.state.activeDate,
1024
+ adapter: this.config.adapter,
1025
+ createCellId: (date) => `${this.instanceId}-cell-${toDateKey(this.config.adapter, date)}`,
1026
+ fixedWeeks: this.config.fixedWeeks,
1027
+ focusStrategy: this.config.focusStrategy,
1028
+ focusedDate: this.focusVisibleDate,
1029
+ inRange: (date) => rangeIncludesDate(this.config.adapter, this.state.value, date),
1030
+ isDisabled: (date) => this.isDateDisabled(date),
1031
+ isPreviewRange: (date) => this.isInPreviewRange(date),
1032
+ isRangeEnd: (date) => {
1033
+ const range = this.state.value;
1034
+ return (isRangeSelectionValue(range) &&
1035
+ range.end !== null &&
1036
+ datesEqual(this.config.adapter, range.end, date));
1037
+ },
1038
+ isRangeStart: (date) => {
1039
+ const range = this.state.value;
1040
+ return (isRangeSelectionValue(range) &&
1041
+ range.start !== null &&
1042
+ datesEqual(this.config.adapter, range.start, date));
1043
+ },
1044
+ isSelected: (date) => valueIncludesDate(this.config.adapter, this.config.selectionMode, this.state.value, date),
1045
+ locale: this.config.locale,
1046
+ showOutsideDays: this.config.showOutsideDays,
1047
+ today: this.config.today,
1048
+ visibleMonth: this.state.visibleMonth,
1049
+ weekStartsOn: this.config.weekStartsOn,
1050
+ });
1051
+ this.syncDayFocusControllers(cells);
1052
+ if (this.config.enableTypeahead) {
1053
+ this.typeahead.setItems(cells
1054
+ .filter((cell) => !cell.disabled && !cell.hidden)
1055
+ .map((cell) => ({ disabled: false, id: cell.id, text: cell.label })));
1056
+ }
1057
+ this.cachedCells = cells;
1058
+ this.cachedGridVersion = this.version;
1059
+ return cells;
1060
+ }
1061
+ getMonthOptions() {
1062
+ if (this.cachedMonthVersion === this.version) {
1063
+ return this.cachedMonthOptions;
1064
+ }
1065
+ const options = buildMonthOptions({
1066
+ activeDate: this.state.activeDate,
1067
+ adapter: this.config.adapter,
1068
+ createId: (month) => `${this.instanceId}-month-${month}`,
1069
+ disabledMonth: (month) => !hasSelectableDateInMonth(this.config.adapter, this.config.adapter.createDate(this.config.adapter.getYear(this.state.visibleMonth), month, 1), (date) => this.isDateDisabled(date)),
1070
+ focusedDate: this.focusVisibleDate,
1071
+ focusedSection: this.state.focusedSection,
1072
+ locale: this.config.locale,
1073
+ selectedMonth: (month) => {
1074
+ const anchor = this.extractSelectionAnchor(this.state.value);
1075
+ return anchor !== null && this.config.adapter.getMonth(anchor) === month;
1076
+ },
1077
+ visibleMonth: this.state.visibleMonth,
1078
+ });
1079
+ this.syncMonthFocusController(options);
1080
+ this.cachedMonthOptions = options;
1081
+ this.cachedMonthVersion = this.version;
1082
+ return options;
1083
+ }
1084
+ getWeekdayLabels() {
1085
+ if (this.cachedWeekdayLabels.length > 0 && this.cachedGridVersion === this.version) {
1086
+ return this.cachedWeekdayLabels;
1087
+ }
1088
+ const start = this.config.adapter.startOfWeek(this.config.today, this.config.weekStartsOn);
1089
+ const labels = Array.from({ length: 7 }, (_, index) => this.config.adapter.format(this.config.adapter.addDays(start, index), 'weekday-short', this.config.locale));
1090
+ this.cachedWeekdayLabels = Object.freeze(labels);
1091
+ return this.cachedWeekdayLabels;
1092
+ }
1093
+ getYearOptions() {
1094
+ if (this.cachedYearVersion === this.version) {
1095
+ return this.cachedYearOptions;
1096
+ }
1097
+ const totalYears = this.config.yearPageSize;
1098
+ const options = buildYearOptions({
1099
+ activeDate: this.state.activeDate,
1100
+ adapter: this.config.adapter,
1101
+ createId: (year) => `${this.instanceId}-year-${year}`,
1102
+ disabledYear: (year) => !hasSelectableDateInYear(this.config.adapter, year, this.state.visibleMonth, (date) => this.isDateDisabled(date)),
1103
+ focusedDate: this.focusVisibleDate,
1104
+ focusedSection: this.state.focusedSection,
1105
+ locale: this.config.locale,
1106
+ selectedYear: (year) => {
1107
+ const anchor = this.extractSelectionAnchor(this.state.value);
1108
+ return anchor !== null && this.config.adapter.getYear(anchor) === year;
1109
+ },
1110
+ startYear: this.yearPageStart,
1111
+ totalYears,
1112
+ visibleMonth: this.state.visibleMonth,
1113
+ });
1114
+ this.syncYearFocusController(options);
1115
+ this.cachedYearOptions = options;
1116
+ this.cachedYearVersion = this.version;
1117
+ return options;
1118
+ }
1119
+ handlePageNavigation(event) {
1120
+ if (event.key === 'PageUp' || event.key === 'PageDown') {
1121
+ event.preventDefault();
1122
+ const direction = event.key === 'PageDown' ? 1 : -1;
1123
+ const useYear = event.ctrlKey === true || event.metaKey === true || event.shiftKey === true;
1124
+ const nextActive = useYear
1125
+ ? this.config.adapter.addYears(this.state.activeDate, direction)
1126
+ : this.config.adapter.addMonths(this.state.activeDate, direction);
1127
+ this.applyActiveDate(nextActive, 'keyboard', true);
1128
+ return true;
1129
+ }
1130
+ return false;
1131
+ }
1132
+ handlePickerGridKeyDown(event, view) {
1133
+ if (this.destroyed || this.state.disabled) {
1134
+ return;
1135
+ }
1136
+ const options = view === 'month' ? this.getMonthOptions() : this.getYearOptions();
1137
+ const activeIndex = options.findIndex((option) => option.active);
1138
+ if (event.key === 'Escape') {
1139
+ const nextView = resolveViewForEscape(this.state.view);
1140
+ if (nextView === null) {
1141
+ this.close('escape');
1142
+ }
1143
+ else {
1144
+ this.setView(nextView);
1145
+ }
1146
+ event.preventDefault();
1147
+ return;
1148
+ }
1149
+ const action = resolveGridNavigationKeyAction(event, {
1150
+ direction: this.config.direction,
1151
+ });
1152
+ if (action === null) {
1153
+ return;
1154
+ }
1155
+ if (action.preventDefault) {
1156
+ event.preventDefault();
1157
+ }
1158
+ if (action.type === 'exit') {
1159
+ return;
1160
+ }
1161
+ if (action.type === 'activate') {
1162
+ const activeOption = options[Math.max(0, activeIndex)];
1163
+ if (activeOption === undefined || activeOption.disabled) {
1164
+ return;
1165
+ }
1166
+ if (view === 'month') {
1167
+ this.selectMonth(activeOption.index);
1168
+ }
1169
+ else {
1170
+ this.selectYear(activeOption.year);
1171
+ }
1172
+ return;
1173
+ }
1174
+ const currentOption = options[Math.max(0, activeIndex)];
1175
+ if (currentOption === undefined) {
1176
+ return;
1177
+ }
1178
+ const currentPosition = this.optionIndexToGridPosition(activeIndex, 4);
1179
+ const nextPosition = resolveNavigableGridCell(currentPosition, action.type, {
1180
+ bounds: {
1181
+ colCount: 4,
1182
+ rowCount: Math.ceil(options.length / 4),
1183
+ },
1184
+ cells: options.map((option, index) => ({
1185
+ col: index % 4,
1186
+ disabled: option.disabled,
1187
+ row: Math.floor(index / 4),
1188
+ })),
1189
+ });
1190
+ const yearDelta = view === 'year' ? this.resolveYearBoundaryDelta(action.type) : null;
1191
+ const monthDelta = view === 'month' ? this.resolveMonthBoundaryDelta(action.type) : null;
1192
+ const isBoundaryClamp = ((view === 'year' && yearDelta !== null) || (view === 'month' && monthDelta !== null)) &&
1193
+ nextPosition !== null &&
1194
+ nextPosition.row === currentPosition.row &&
1195
+ nextPosition.col === currentPosition.col;
1196
+ if (nextPosition === null || isBoundaryClamp) {
1197
+ if (view === 'year' && yearDelta !== null) {
1198
+ this.state.focusedSection = 'year';
1199
+ const fallbackYearOption = action.type === 'move-up' || action.type === 'move-down'
1200
+ ? this.findYearOptionInVerticalDirection(options, activeIndex, action.type)
1201
+ : null;
1202
+ if (fallbackYearOption !== null) {
1203
+ this.applyPickerActiveDate(fallbackYearOption.date, 'keyboard', true);
1204
+ return;
1205
+ }
1206
+ const targetYear = currentOption.year + yearDelta;
1207
+ if (!hasSelectableDateInYear(this.config.adapter, targetYear, this.state.visibleMonth, (date) => this.isDateDisabled(date))) {
1208
+ return;
1209
+ }
1210
+ this.shiftYearPageToInclude(targetYear);
1211
+ this.applyPickerActiveDate(this.config.adapter.createDate(targetYear, this.config.adapter.getMonth(this.state.visibleMonth), 1), 'keyboard', true);
1212
+ return;
1213
+ }
1214
+ if (view === 'month' && monthDelta !== null) {
1215
+ this.state.focusedSection = 'month';
1216
+ const targetMonth = this.config.adapter.addMonths(currentOption.date, monthDelta);
1217
+ if (!hasSelectableDateInMonth(this.config.adapter, targetMonth, (date) => this.isDateDisabled(date))) {
1218
+ return;
1219
+ }
1220
+ this.shiftMonthGridToTarget(targetMonth);
1221
+ this.applyPickerActiveDate(targetMonth, 'keyboard', true);
1222
+ }
1223
+ return;
1224
+ }
1225
+ const nextIndex = nextPosition.row * 4 + nextPosition.col;
1226
+ const nextOption = options[nextIndex];
1227
+ if (nextOption === undefined || nextOption.disabled) {
1228
+ return;
1229
+ }
1230
+ if (view === 'month') {
1231
+ this.state.focusedSection = 'month';
1232
+ }
1233
+ else {
1234
+ this.state.focusedSection = 'year';
1235
+ }
1236
+ this.applyPickerActiveDate(nextOption.date, 'keyboard', true);
1237
+ }
1238
+ findYearOptionInVerticalDirection(options, activeIndex, actionType) {
1239
+ if (actionType === 'move-up') {
1240
+ for (let index = activeIndex - 1; index >= 0; index -= 1) {
1241
+ const option = options[index];
1242
+ if (option !== undefined && !option.disabled) {
1243
+ return option;
1244
+ }
1245
+ }
1246
+ return null;
1247
+ }
1248
+ for (let index = activeIndex + 1; index < options.length; index += 1) {
1249
+ const option = options[index];
1250
+ if (option !== undefined && !option.disabled) {
1251
+ return option;
1252
+ }
1253
+ }
1254
+ return null;
1255
+ }
1256
+ handleResolvedDayGridAction(actionType, shiftKey) {
1257
+ const cells = this.getCells();
1258
+ const activeCell = cells.find((cell) => cell.active) ?? null;
1259
+ if (activeCell === null) {
1260
+ return;
1261
+ }
1262
+ if (actionType === 'move-left' ||
1263
+ actionType === 'move-right' ||
1264
+ actionType === 'move-up' ||
1265
+ actionType === 'move-down') {
1266
+ const delta = actionType === 'move-left'
1267
+ ? -1
1268
+ : actionType === 'move-right'
1269
+ ? 1
1270
+ : actionType === 'move-up'
1271
+ ? -7
1272
+ : 7;
1273
+ const nextDate = this.resolveDirectionalTarget(activeCell.date, delta);
1274
+ this.applyActiveDate(nextDate, 'keyboard', true);
1275
+ if (shiftKey && this.config.enableRangeSelection) {
1276
+ this.commitRangeFromAnchor(nextDate);
1277
+ }
1278
+ return;
1279
+ }
1280
+ const nextPosition = resolveNavigableGridCell({
1281
+ col: activeCell.colIndex,
1282
+ row: activeCell.rowIndex,
1283
+ }, actionType, {
1284
+ bounds: {
1285
+ colCount: 7,
1286
+ rowCount: Math.ceil(cells.length / 7),
1287
+ },
1288
+ cells: cells.map((cell) => ({
1289
+ col: cell.colIndex,
1290
+ disabled: cell.disabled,
1291
+ row: cell.rowIndex,
1292
+ })),
1293
+ });
1294
+ if (nextPosition === null) {
1295
+ return;
1296
+ }
1297
+ const nextCell = cells.find((cell) => cell.colIndex === nextPosition.col && cell.rowIndex === nextPosition.row);
1298
+ if (nextCell === undefined || nextCell.disabled) {
1299
+ return;
1300
+ }
1301
+ this.applyActiveDate(nextCell.date, 'keyboard', true);
1302
+ }
1303
+ handleTypeahead(key) {
1304
+ if (!/^\d$/.test(key)) {
1305
+ return;
1306
+ }
1307
+ const result = this.typeahead.handleKey(key);
1308
+ if (result.activeId === null) {
1309
+ return;
1310
+ }
1311
+ const cell = this.getCells().find((item) => item.id === result.activeId);
1312
+ if (cell === undefined) {
1313
+ return;
1314
+ }
1315
+ this.applyActiveDate(cell.date, 'keyboard', true);
1316
+ }
1317
+ isDateDisabled(date, options = {}) {
1318
+ if (options.includeComponentDisabled !== false && this.state?.disabled === true) {
1319
+ return true;
1320
+ }
1321
+ if (this.config.min !== null && this.config.adapter.compare(date, this.config.min) < 0) {
1322
+ return true;
1323
+ }
1324
+ if (this.config.max !== null && this.config.adapter.compare(date, this.config.max) > 0) {
1325
+ return true;
1326
+ }
1327
+ return this.config.disableDate?.(date) ?? false;
1328
+ }
1329
+ isInPreviewRange(date) {
1330
+ const start = getRangeStartDate(this.state.value);
1331
+ const previewEnd = this.hoverDate;
1332
+ if (start === null || previewEnd === null || getRangeEndDate(this.state.value) !== null) {
1333
+ return false;
1334
+ }
1335
+ const range = normalizeRangeOrder(this.config.adapter, {
1336
+ end: previewEnd,
1337
+ start,
1338
+ });
1339
+ if (range.start === null || range.end === null) {
1340
+ return false;
1341
+ }
1342
+ return (this.config.adapter.compare(range.start, date) <= 0 &&
1343
+ this.config.adapter.compare(range.end, date) >= 0);
1344
+ }
1345
+ isPreviewEndDate(date) {
1346
+ const start = getRangeStartDate(this.state.value);
1347
+ const previewEnd = this.hoverDate;
1348
+ return (start !== null &&
1349
+ previewEnd !== null &&
1350
+ getRangeEndDate(this.state.value) === null &&
1351
+ datesEqual(this.config.adapter, previewEnd, date));
1352
+ }
1353
+ registerOverlayLayer() {
1354
+ if (this.overlayLayerRegistered || this.config.overlayRuntime === null) {
1355
+ return;
1356
+ }
1357
+ this.config.overlayRuntime.registerLayer({
1358
+ containsTarget: (target) => (target instanceof Node && this.overlayElement?.contains(target) === true) ||
1359
+ (target instanceof Node && this.triggerElement?.contains(target) === true),
1360
+ dismissOnEscape: this.config.closeOnEscape,
1361
+ dismissOnOutsidePointer: this.config.closeOnOutsideClick,
1362
+ id: `${this.instanceId}-layer`,
1363
+ modal: false,
1364
+ onDismiss: (reason) => this.close(mapOverlayDismissReason(reason)),
1365
+ });
1366
+ this.overlayLayerRegistered = true;
1367
+ }
1368
+ activateFocusLayer() {
1369
+ this.registerFocusLayer();
1370
+ if (!this.focusLayerRegistered) {
1371
+ return;
1372
+ }
1373
+ dateRangePickerFocusHandoff.activateLayer(this.instanceId, this.restoreFocusTargetId);
1374
+ }
1375
+ deactivateFocusLayer() {
1376
+ if (!this.focusLayerRegistered) {
1377
+ return;
1378
+ }
1379
+ const restoreFocusTargetId = dateRangePickerFocusHandoff.deactivateLayer(this.instanceId);
1380
+ if (!this.config.restoreFocus || restoreFocusTargetId === null) {
1381
+ return;
1382
+ }
1383
+ const restoreFocusTarget = this.resolveElementById(restoreFocusTargetId) ?? this.triggerElement;
1384
+ restoreFocusTarget?.focus();
1385
+ }
1386
+ focusCurrentOverlayTarget() {
1387
+ if (!this.state.open || this.overlayElement === null) {
1388
+ return;
1389
+ }
1390
+ const targetId = this.resolveCurrentOverlayTargetId();
1391
+ if (targetId === null) {
1392
+ return;
1393
+ }
1394
+ const resolvedTargetId = dateRangePickerFocusHandoff.resolveFocusCandidate(this.instanceId, targetId);
1395
+ if (resolvedTargetId === null) {
1396
+ return;
1397
+ }
1398
+ const target = this.resolveElementById(resolvedTargetId);
1399
+ if (target === null) {
1400
+ return;
1401
+ }
1402
+ target.focus();
1403
+ dateRangePickerFocusHandoff.recordFocus(this.instanceId, resolvedTargetId);
1404
+ }
1405
+ resolveActiveElement() {
1406
+ const activeElement = this.config.ownerDocument?.activeElement;
1407
+ return activeElement instanceof HTMLElement ? activeElement : null;
1408
+ }
1409
+ resolveElementById(id) {
1410
+ if (id === null) {
1411
+ return null;
1412
+ }
1413
+ const element = this.config.ownerDocument?.getElementById(id);
1414
+ return element instanceof HTMLElement ? element : null;
1415
+ }
1416
+ resolveCellAttributes(cellOrDate) {
1417
+ const cell = 'id' in cellOrDate
1418
+ ? cellOrDate
1419
+ : this.getCells().find((candidate) => datesEqual(this.config.adapter, candidate.date, cellOrDate));
1420
+ if (cell === undefined) {
1421
+ return freezeAttributes({});
1422
+ }
1423
+ return freezeAttributes({
1424
+ 'aria-current': cell.today ? 'date' : null,
1425
+ 'aria-disabled': cell.disabled ? 'true' : null,
1426
+ 'aria-label': this.resolveCellAriaLabel(cell),
1427
+ 'aria-selected': cell.selected ? 'true' : 'false',
1428
+ 'data-active': cell.active ? 'true' : null,
1429
+ 'data-disabled': cell.disabled ? 'true' : null,
1430
+ 'data-focused': cell.focusVisible ? 'true' : null,
1431
+ 'data-focus-visible': cell.focusVisible ? 'true' : null,
1432
+ 'data-hidden': cell.hidden ? 'true' : null,
1433
+ 'data-in-month': cell.inMonth ? 'true' : null,
1434
+ 'data-in-range': cell.inRange ? 'true' : null,
1435
+ 'data-outside-month': cell.inMonth ? null : 'true',
1436
+ 'data-preview-end': this.isPreviewEndDate(cell.date) ? 'true' : null,
1437
+ 'data-preview-range': cell.previewRange ? 'true' : null,
1438
+ 'data-range-end': cell.rangeEnd ? 'true' : null,
1439
+ 'data-range-start': cell.rangeStart ? 'true' : null,
1440
+ 'data-selected': cell.selected ? 'true' : null,
1441
+ 'data-slot': 'date-range-picker-cell',
1442
+ 'data-today': cell.today ? 'true' : null,
1443
+ id: cell.id,
1444
+ role: 'gridcell',
1445
+ tabindex: this.config.focusStrategy === 'roving'
1446
+ ? `${this.dayRovingFocus.getActiveId() === cell.id ? 0 : -1}`
1447
+ : '-1',
1448
+ });
1449
+ }
1450
+ resolveCellAriaLabel(cell) {
1451
+ const descriptors = [cell.label];
1452
+ if (cell.rangeStart) {
1453
+ descriptors.push('selected start date');
1454
+ }
1455
+ if (cell.rangeEnd) {
1456
+ descriptors.push('selected end date');
1457
+ }
1458
+ if (cell.inRange) {
1459
+ descriptors.push('in selected range');
1460
+ }
1461
+ if (cell.previewRange) {
1462
+ descriptors.push('in preview range');
1463
+ }
1464
+ if (this.isPreviewEndDate(cell.date)) {
1465
+ descriptors.push('preview end date');
1466
+ }
1467
+ if (cell.disabled) {
1468
+ descriptors.push('unavailable');
1469
+ }
1470
+ return descriptors.join(', ');
1471
+ }
1472
+ resolveDirectionalTarget(currentDate, delta) {
1473
+ if (!this.config.skipDisabled) {
1474
+ return this.config.adapter.addDays(currentDate, delta);
1475
+ }
1476
+ return moveDateSkippingDisabled({
1477
+ adapter: this.config.adapter,
1478
+ delta,
1479
+ isDisabled: (date) => this.isDateDisabled(date),
1480
+ start: currentDate,
1481
+ });
1482
+ }
1483
+ resolveCurrentOverlayTargetId() {
1484
+ if (this.state.view === 'day') {
1485
+ if (this.config.focusStrategy === 'active-descendant') {
1486
+ return this.gridId;
1487
+ }
1488
+ return this.dayRovingFocus.getActiveId();
1489
+ }
1490
+ if (this.state.view === 'month') {
1491
+ return this.monthRovingFocus.getActiveId();
1492
+ }
1493
+ if (this.state.view === 'year') {
1494
+ return this.yearRovingFocus.getActiveId();
1495
+ }
1496
+ return null;
1497
+ }
1498
+ resolveGridAttributes() {
1499
+ const activeDescendantAttributes = this.config.focusStrategy === 'active-descendant'
1500
+ ? this.dayActiveDescendant.getHostAttributes()
1501
+ : null;
1502
+ return freezeAttributes({
1503
+ 'aria-activedescendant': activeDescendantAttributes?.['aria-activedescendant'] ?? null,
1504
+ 'aria-labelledby': this.monthLabelId,
1505
+ 'data-slot': 'date-range-picker-grid',
1506
+ id: this.gridId,
1507
+ role: 'grid',
1508
+ });
1509
+ }
1510
+ resolveHostAttributes() {
1511
+ return freezeAttributes({
1512
+ 'aria-describedby': this.config.ariaDescribedBy,
1513
+ 'aria-label': this.config.ariaLabel,
1514
+ 'aria-labelledby': this.config.ariaLabelledBy,
1515
+ 'data-disabled': this.state.disabled ? 'true' : null,
1516
+ 'data-open': this.state.open ? 'true' : 'false',
1517
+ 'data-slot': 'date-range-picker',
1518
+ 'data-view': this.state.view,
1519
+ dir: this.config.direction,
1520
+ role: 'group',
1521
+ });
1522
+ }
1523
+ resolveInitialActiveDate(value, fallback) {
1524
+ const anchor = this.extractSelectionAnchor(value);
1525
+ if (anchor !== null && !this.isDateDisabled(anchor)) {
1526
+ return anchor;
1527
+ }
1528
+ if (!this.isDateDisabled(fallback)) {
1529
+ return fallback;
1530
+ }
1531
+ const firstEnabled = findFirstEnabledDateInMonth(this.config.adapter, this.config.adapter.startOfMonth(fallback), (date) => this.isDateDisabled(date));
1532
+ if (firstEnabled !== null) {
1533
+ return firstEnabled;
1534
+ }
1535
+ if (this.config.min !== null && !this.isDateDisabled(this.config.min)) {
1536
+ return this.config.min;
1537
+ }
1538
+ if (this.config.max !== null && !this.isDateDisabled(this.config.max)) {
1539
+ return this.config.max;
1540
+ }
1541
+ return fallback;
1542
+ }
1543
+ resolveLayout() {
1544
+ if (!this.state.open || this.config.overlayMode === 'overlay') {
1545
+ return Object.freeze({
1546
+ mode: this.config.overlayMode,
1547
+ offsetX: 0,
1548
+ width: 0,
1549
+ });
1550
+ }
1551
+ const width = this.config.overlaySize;
1552
+ if (this.config.position === 'center') {
1553
+ return Object.freeze({ mode: this.config.overlayMode, offsetX: 0, width });
1554
+ }
1555
+ const startOffset = this.config.direction === 'rtl' ? -width : width;
1556
+ const endOffset = this.config.direction === 'rtl' ? width : -width;
1557
+ return Object.freeze({
1558
+ mode: this.config.overlayMode,
1559
+ offsetX: this.config.position === 'start' ? startOffset : endOffset,
1560
+ width,
1561
+ });
1562
+ }
1563
+ resolveMonthAttributes(monthOrOption) {
1564
+ const option = typeof monthOrOption === 'number'
1565
+ ? this.getMonthOptions().find((item) => item.index === monthOrOption)
1566
+ : monthOrOption;
1567
+ if (option === undefined) {
1568
+ return freezeAttributes({});
1569
+ }
1570
+ return freezeAttributes({
1571
+ 'aria-disabled': option.disabled ? 'true' : null,
1572
+ 'aria-selected': option.selected ? 'true' : 'false',
1573
+ 'data-active': option.active ? 'true' : null,
1574
+ 'data-disabled': option.disabled ? 'true' : null,
1575
+ 'data-focus-visible': option.focusVisible ? 'true' : null,
1576
+ 'data-selected': option.selected ? 'true' : null,
1577
+ 'data-slot': 'date-range-picker-month',
1578
+ id: option.id,
1579
+ role: 'gridcell',
1580
+ tabindex: `${this.monthRovingFocus.getActiveId() === option.id ? 0 : -1}`,
1581
+ });
1582
+ }
1583
+ resolveNextSelection(nextDate, shiftKey) {
1584
+ const currentRange = isRangeSelectionValue(this.state.value)
1585
+ ? this.state.value
1586
+ : Object.freeze({ end: null, start: null });
1587
+ if (shiftKey && this.config.enableRangeSelection && this.rangeAnchorDate !== null) {
1588
+ const shiftedRange = normalizeRangeOrder(this.config.adapter, {
1589
+ end: nextDate,
1590
+ start: this.rangeAnchorDate,
1591
+ });
1592
+ if (shiftedRange.start !== null && shiftedRange.end !== null) {
1593
+ return shiftedRange;
1594
+ }
1595
+ }
1596
+ if (currentRange.start === null || currentRange.end !== null) {
1597
+ return Object.freeze({ end: null, start: nextDate });
1598
+ }
1599
+ const normalizedRange = normalizeRangeOrder(this.config.adapter, Object.freeze({ end: nextDate, start: currentRange.start }));
1600
+ return normalizedRange;
1601
+ }
1602
+ resolveOverlayAttributes() {
1603
+ return freezeAttributes({
1604
+ 'aria-describedby': this.config.ariaDescribedBy,
1605
+ 'aria-label': this.config.ariaLabel,
1606
+ 'aria-labelledby': this.config.ariaLabelledBy ?? this.monthLabelId,
1607
+ 'aria-modal': this.config.trapFocus ? 'true' : null,
1608
+ 'data-open': this.state.open ? 'true' : 'false',
1609
+ 'data-position': this.config.position,
1610
+ 'data-slot': 'date-range-picker-overlay',
1611
+ id: this.overlayId,
1612
+ role: 'dialog',
1613
+ });
1614
+ }
1615
+ resolveTriggerAttributes() {
1616
+ return freezeAttributes({
1617
+ 'aria-controls': this.overlayId,
1618
+ 'aria-disabled': this.state.disabled ? 'true' : null,
1619
+ 'aria-expanded': this.state.open ? 'true' : 'false',
1620
+ 'aria-haspopup': 'dialog',
1621
+ 'data-disabled': this.state.disabled ? 'true' : null,
1622
+ 'data-open': this.state.open ? 'true' : 'false',
1623
+ 'data-slot': 'date-range-picker-trigger',
1624
+ disabled: this.state.disabled ? 'true' : null,
1625
+ });
1626
+ }
1627
+ resolveValidDate(date, monthContext) {
1628
+ const targetMonth = isDateValueInMonth(this.config.adapter, date, monthContext)
1629
+ ? monthContext
1630
+ : this.config.adapter.startOfMonth(date);
1631
+ let nextDate = date;
1632
+ if (!isDateValueInMonth(this.config.adapter, nextDate, targetMonth)) {
1633
+ nextDate = clampDateToMonth(this.config.adapter, nextDate, targetMonth);
1634
+ }
1635
+ if (!this.isDateDisabled(nextDate)) {
1636
+ return nextDate;
1637
+ }
1638
+ const fallback = findFirstEnabledDateInMonth(this.config.adapter, targetMonth, (candidate) => this.isDateDisabled(candidate));
1639
+ return fallback ?? nextDate;
1640
+ }
1641
+ resolveYearAttributes(yearOrOption) {
1642
+ const option = typeof yearOrOption === 'number'
1643
+ ? this.getYearOptions().find((item) => item.year === yearOrOption)
1644
+ : yearOrOption;
1645
+ if (option === undefined) {
1646
+ return freezeAttributes({});
1647
+ }
1648
+ return freezeAttributes({
1649
+ 'aria-disabled': option.disabled ? 'true' : null,
1650
+ 'aria-selected': option.selected ? 'true' : 'false',
1651
+ 'data-active': option.active ? 'true' : null,
1652
+ 'data-disabled': option.disabled ? 'true' : null,
1653
+ 'data-focus-visible': option.focusVisible ? 'true' : null,
1654
+ 'data-selected': option.selected ? 'true' : null,
1655
+ 'data-slot': 'date-range-picker-year',
1656
+ id: option.id,
1657
+ role: 'gridcell',
1658
+ tabindex: `${this.yearRovingFocus.getActiveId() === option.id ? 0 : -1}`,
1659
+ });
1660
+ }
1661
+ resolveConfig(nextConfig, previous) {
1662
+ const adapter = nextConfig.adapter ??
1663
+ previous?.adapter ??
1664
+ defaultDateRangePickerDateAdapter;
1665
+ const ownerDocument = nextConfig.ownerDocument ??
1666
+ previous?.ownerDocument ??
1667
+ (typeof document === 'undefined' ? null : document);
1668
+ const locale = nextConfig.locale ?? previous?.locale ?? resolveDefaultLocale(ownerDocument);
1669
+ const today = normalizeDateInput(adapter, nextConfig.today ?? previous?.today ?? null, locale) ??
1670
+ previous?.today ??
1671
+ adapter.today();
1672
+ const normalizedValue = nextConfig.value !== undefined || previous === null
1673
+ ? normalizeSelectionInput(adapter, 'range', nextConfig.value ?? null, locale).value
1674
+ : previous.value;
1675
+ return Object.freeze({
1676
+ adapter,
1677
+ allowManualInput: nextConfig.allowManualInput ?? previous?.allowManualInput ?? true,
1678
+ ariaDescribedBy: nextConfig.ariaDescribedBy ?? previous?.ariaDescribedBy ?? null,
1679
+ ariaLabel: nextConfig.ariaLabel ?? previous?.ariaLabel ?? null,
1680
+ ariaLabelledBy: nextConfig.ariaLabelledBy ?? previous?.ariaLabelledBy ?? null,
1681
+ autoCommitView: nextConfig.autoCommitView ?? previous?.autoCommitView ?? true,
1682
+ closeOnEscape: nextConfig.closeOnEscape ?? previous?.closeOnEscape ?? true,
1683
+ closeOnOutsideClick: nextConfig.closeOnOutsideClick ?? previous?.closeOnOutsideClick ?? true,
1684
+ closeOnSelect: nextConfig.closeOnSelect ?? previous?.closeOnSelect ?? false,
1685
+ closeOthersOnOpen: nextConfig.closeOthersOnOpen ?? previous?.closeOthersOnOpen ?? false,
1686
+ direction: normalizeDirection(nextConfig.direction ?? previous?.direction),
1687
+ disableDate: nextConfig.disableDate ?? previous?.disableDate ?? null,
1688
+ disabled: nextConfig.disabled ?? previous?.disabled ?? false,
1689
+ enableRangeSelection: nextConfig.enableRangeSelection ?? previous?.enableRangeSelection ?? true,
1690
+ enableTypeahead: nextConfig.enableTypeahead ?? previous?.enableTypeahead ?? true,
1691
+ fixedWeeks: nextConfig.fixedWeeks ?? previous?.fixedWeeks ?? true,
1692
+ focusStrategy: nextConfig.focusStrategy ?? previous?.focusStrategy ?? 'roving',
1693
+ id: nextConfig.id?.trim() || previous?.id || this.instanceId,
1694
+ initialView: normalizeView(nextConfig.initialView ?? previous?.initialView),
1695
+ locale,
1696
+ max: normalizeDateInput(adapter, nextConfig.maxDate ?? nextConfig.max ?? previous?.max ?? null, locale),
1697
+ min: normalizeDateInput(adapter, nextConfig.minDate ?? nextConfig.min ?? previous?.min ?? null, locale),
1698
+ onPartialInputCommit: nextConfig.onPartialInputCommit ?? previous?.onPartialInputCommit ?? false,
1699
+ overlayMode: normalizeOverlayMode(nextConfig.overlayMode ?? previous?.overlayMode),
1700
+ overlayRuntime: nextConfig.overlayRuntime ??
1701
+ previous?.overlayRuntime ??
1702
+ (ownerDocument !== null
1703
+ ? createOverlayRuntime({ documentRef: readDocument(ownerDocument) })
1704
+ : null),
1705
+ overlaySize: nextConfig.overlaySize ?? previous?.overlaySize ?? 320,
1706
+ ownerDocument,
1707
+ position: normalizePosition(nextConfig.position ?? previous?.position),
1708
+ preserveViewOnOpenClose: nextConfig.preserveViewOnOpenClose ?? previous?.preserveViewOnOpenClose ?? true,
1709
+ restoreFocus: nextConfig.restoreFocus ?? previous?.restoreFocus ?? true,
1710
+ selectionMode: 'range',
1711
+ showOutsideDays: nextConfig.showOutsideDays ?? previous?.showOutsideDays ?? true,
1712
+ skipDisabled: nextConfig.skipDisabled ?? previous?.skipDisabled ?? true,
1713
+ today,
1714
+ trapFocus: nextConfig.trapFocus ?? previous?.trapFocus ?? false,
1715
+ value: normalizedValue,
1716
+ weekStartsOn: coerceWeekStartsOn(nextConfig.weekStartsOn ?? previous?.weekStartsOn ?? resolveLocaleWeekStartsOn(locale)),
1717
+ yearPageSize: ensureYearPageSize(nextConfig.yearPageSize ?? previous?.yearPageSize),
1718
+ });
1719
+ }
1720
+ registerFocusLayer() {
1721
+ if (this.focusLayerRegistered || this.overlayElement === null) {
1722
+ return;
1723
+ }
1724
+ dateRangePickerFocusHandoff.registerLayer({
1725
+ layerId: this.instanceId,
1726
+ members: () => {
1727
+ const overlay = this.overlayElement;
1728
+ if (overlay === null) {
1729
+ return [];
1730
+ }
1731
+ return this.resolveFocusableMemberIds(overlay);
1732
+ },
1733
+ restoreFocus: this.config.restoreFocus,
1734
+ trapFocus: this.config.trapFocus,
1735
+ });
1736
+ this.focusLayerRegistered = true;
1737
+ }
1738
+ unregisterFocusLayer() {
1739
+ if (!this.focusLayerRegistered) {
1740
+ return;
1741
+ }
1742
+ dateRangePickerFocusHandoff.unregisterLayer(this.instanceId);
1743
+ this.focusLayerRegistered = false;
1744
+ }
1745
+ resolveFocusableMemberIds(container) {
1746
+ const focusableElements = resolveFocusableElements(container);
1747
+ const memberIds = [];
1748
+ const seenIds = new Set();
1749
+ const registerMember = (element) => {
1750
+ const id = this.ensureElementId(element);
1751
+ if (seenIds.has(id)) {
1752
+ return;
1753
+ }
1754
+ seenIds.add(id);
1755
+ memberIds.push(id);
1756
+ };
1757
+ registerMember(container);
1758
+ for (const element of focusableElements) {
1759
+ registerMember(element);
1760
+ }
1761
+ return memberIds;
1762
+ }
1763
+ resolveTabbableOverlayMemberIds(container) {
1764
+ const focusableElements = resolveFocusableElements(container);
1765
+ const memberIds = [];
1766
+ const seenIds = new Set();
1767
+ for (const element of focusableElements) {
1768
+ if (element.tabIndex < 0) {
1769
+ continue;
1770
+ }
1771
+ const id = this.ensureElementId(element);
1772
+ if (seenIds.has(id)) {
1773
+ continue;
1774
+ }
1775
+ seenIds.add(id);
1776
+ memberIds.push(id);
1777
+ }
1778
+ return memberIds;
1779
+ }
1780
+ ensureElementId(element) {
1781
+ const existingId = element.id.trim();
1782
+ if (existingId.length > 0) {
1783
+ return existingId;
1784
+ }
1785
+ const generatedId = createDateRangePickerFocusableId();
1786
+ element.id = generatedId;
1787
+ return generatedId;
1788
+ }
1789
+ optionIndexToGridPosition(index, columnCount) {
1790
+ return Object.freeze({
1791
+ col: Math.max(0, index) % columnCount,
1792
+ row: Math.floor(Math.max(0, index) / columnCount),
1793
+ });
1794
+ }
1795
+ resolveCenteredYearPageStart(year) {
1796
+ return year - Math.floor(this.config.yearPageSize / 2);
1797
+ }
1798
+ resolveYearBoundaryDelta(actionType) {
1799
+ if (actionType === 'move-left') {
1800
+ return -1;
1801
+ }
1802
+ if (actionType === 'move-right') {
1803
+ return 1;
1804
+ }
1805
+ if (actionType === 'move-up') {
1806
+ return -4;
1807
+ }
1808
+ if (actionType === 'move-down') {
1809
+ return 4;
1810
+ }
1811
+ return null;
1812
+ }
1813
+ resolveMonthBoundaryDelta(actionType) {
1814
+ if (actionType === 'move-left') {
1815
+ return -1;
1816
+ }
1817
+ if (actionType === 'move-right') {
1818
+ return 1;
1819
+ }
1820
+ if (actionType === 'move-up') {
1821
+ return -4;
1822
+ }
1823
+ if (actionType === 'move-down') {
1824
+ return 4;
1825
+ }
1826
+ return null;
1827
+ }
1828
+ shiftYearPageToInclude(targetYear) {
1829
+ const previousMonth = this.state.visibleMonth;
1830
+ const previousYear = this.config.adapter.getYear(previousMonth);
1831
+ let nextStart = this.yearPageStart;
1832
+ const pageSize = this.config.yearPageSize;
1833
+ while (targetYear < nextStart) {
1834
+ nextStart -= pageSize;
1835
+ }
1836
+ while (targetYear > nextStart + pageSize - 1) {
1837
+ nextStart += pageSize;
1838
+ }
1839
+ if (nextStart === this.yearPageStart) {
1840
+ return;
1841
+ }
1842
+ this.yearPageStart = nextStart;
1843
+ this.state.visibleMonth = this.config.adapter.createDate(targetYear, this.config.adapter.getMonth(this.state.visibleMonth), 1);
1844
+ this.emit({
1845
+ previousMonth,
1846
+ type: 'monthChange',
1847
+ visibleMonth: this.state.visibleMonth,
1848
+ });
1849
+ const currentYear = this.config.adapter.getYear(this.state.visibleMonth);
1850
+ if (previousYear !== currentYear) {
1851
+ this.emit({
1852
+ previousYear,
1853
+ type: 'yearChange',
1854
+ year: currentYear,
1855
+ });
1856
+ }
1857
+ }
1858
+ shiftMonthGridToTarget(targetMonth) {
1859
+ const normalizedTargetMonth = this.config.adapter.startOfMonth(targetMonth);
1860
+ if (!hasSelectableDateInMonth(this.config.adapter, normalizedTargetMonth, (date) => this.isDateDisabled(date))) {
1861
+ return;
1862
+ }
1863
+ const previousMonth = this.state.visibleMonth;
1864
+ const previousYear = this.config.adapter.getYear(previousMonth);
1865
+ this.state.visibleMonth = normalizedTargetMonth;
1866
+ this.emit({
1867
+ previousMonth,
1868
+ type: 'monthChange',
1869
+ visibleMonth: this.state.visibleMonth,
1870
+ });
1871
+ const currentYear = this.config.adapter.getYear(this.state.visibleMonth);
1872
+ if (previousYear !== currentYear) {
1873
+ this.emit({
1874
+ previousYear,
1875
+ type: 'yearChange',
1876
+ year: currentYear,
1877
+ });
1878
+ }
1879
+ }
1880
+ syncDayFocusControllers(cells) {
1881
+ const itemIds = cells.map((cell) => cell.id);
1882
+ const disabledIds = cells.filter((cell) => cell.disabled).map((cell) => cell.id);
1883
+ const activeCellId = cells.find((cell) => cell.active)?.id ?? null;
1884
+ this.dayRovingFocus.setItemIds(itemIds);
1885
+ this.dayRovingFocus.setDisabledIds(disabledIds);
1886
+ this.dayRovingFocus.setActiveId(activeCellId);
1887
+ this.dayActiveDescendant.setItemIds(itemIds);
1888
+ this.dayActiveDescendant.setDisabledIds(disabledIds);
1889
+ this.dayActiveDescendant.setActiveId(activeCellId);
1890
+ }
1891
+ syncMonthFocusController(options) {
1892
+ const itemIds = options.map((option) => option.id);
1893
+ const disabledIds = options.filter((option) => option.disabled).map((option) => option.id);
1894
+ const activeId = options.find((option) => option.active)?.id ?? null;
1895
+ this.monthRovingFocus.setItemIds(itemIds);
1896
+ this.monthRovingFocus.setDisabledIds(disabledIds);
1897
+ this.monthRovingFocus.setActiveId(activeId);
1898
+ }
1899
+ syncYearFocusController(options) {
1900
+ const itemIds = options.map((option) => option.id);
1901
+ const disabledIds = options.filter((option) => option.disabled).map((option) => option.id);
1902
+ const activeId = options.find((option) => option.active)?.id ?? null;
1903
+ this.yearRovingFocus.setItemIds(itemIds);
1904
+ this.yearRovingFocus.setDisabledIds(disabledIds);
1905
+ this.yearRovingFocus.setActiveId(activeId);
1906
+ }
1907
+ shiftVisibleMonth(direction) {
1908
+ const nextMonth = this.config.adapter.addMonths(this.state.visibleMonth, direction);
1909
+ if (!hasSelectableDateInMonth(this.config.adapter, nextMonth, (date) => this.isDateDisabled(date))) {
1910
+ return;
1911
+ }
1912
+ const previousMonth = this.state.visibleMonth;
1913
+ const previousYear = this.config.adapter.getYear(previousMonth);
1914
+ this.state.visibleMonth = this.config.adapter.startOfMonth(nextMonth);
1915
+ const nextActive = clampDateToMonth(this.config.adapter, this.state.activeDate, this.state.visibleMonth);
1916
+ this.applyActiveDate(nextActive, 'keyboard', true);
1917
+ this.bumpVersion();
1918
+ this.emit({
1919
+ previousMonth,
1920
+ type: 'monthChange',
1921
+ visibleMonth: this.state.visibleMonth,
1922
+ });
1923
+ const currentYear = this.config.adapter.getYear(this.state.visibleMonth);
1924
+ if (previousYear !== currentYear) {
1925
+ this.emit({
1926
+ previousYear,
1927
+ type: 'yearChange',
1928
+ year: currentYear,
1929
+ });
1930
+ }
1931
+ }
1932
+ shiftVisibleYear(direction) {
1933
+ const nextYear = this.config.adapter.getYear(this.state.visibleMonth) + direction;
1934
+ this.selectYear(nextYear);
1935
+ }
1936
+ commitRangeFromAnchor(endDate) {
1937
+ if (this.rangeAnchorDate === null) {
1938
+ return;
1939
+ }
1940
+ const previousValue = this.state.value;
1941
+ const nextValue = normalizeRangeOrder(this.config.adapter, {
1942
+ end: endDate,
1943
+ start: this.rangeAnchorDate,
1944
+ });
1945
+ if (nextValue.start === null || nextValue.end === null) {
1946
+ return;
1947
+ }
1948
+ this.state.value = nextValue;
1949
+ this.state.inputText = this.formatValueForInput(nextValue);
1950
+ this.bumpVersion();
1951
+ this.emit({
1952
+ previousValue,
1953
+ trigger: 'keyboard',
1954
+ type: 'valueChange',
1955
+ value: nextValue,
1956
+ });
1957
+ }
1958
+ unregisterOverlayLayer() {
1959
+ if (!this.overlayLayerRegistered || this.config.overlayRuntime === null) {
1960
+ return;
1961
+ }
1962
+ this.config.overlayRuntime.unregisterLayer(`${this.instanceId}-layer`);
1963
+ this.overlayLayerRegistered = false;
1964
+ }
1965
+ }
1966
+ export function createDateRangePickerController(config = {}) {
1967
+ return new DateRangePickerController(config);
1968
+ }
1969
+ export { defaultDateRangePickerDateAdapter } from './date-range-picker.adapters';
1970
+ //# sourceMappingURL=tng-date-range-picker.js.map