@carbon-labs/wc-date-picker 0.2.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.
- package/LICENSE +201 -0
- package/es/__stories__/date-picker.stories.d.ts +949 -0
- package/es/components/date-picker/calendar-renderer.d.ts +147 -0
- package/es/components/date-picker/calendar-renderer.js +406 -0
- package/es/components/date-picker/calendar-renderer.js.map +1 -0
- package/es/components/date-picker/date-picker-input-skeleton.d.ts +34 -0
- package/es/components/date-picker/date-picker-input-skeleton.js +77 -0
- package/es/components/date-picker/date-picker-input-skeleton.js.map +1 -0
- package/es/components/date-picker/date-picker-input.d.ts +539 -0
- package/es/components/date-picker/date-picker-input.js +413 -0
- package/es/components/date-picker/date-picker-input.js.map +1 -0
- package/es/components/date-picker/date-picker.d.ts +977 -0
- package/es/components/date-picker/date-picker.js +1047 -0
- package/es/components/date-picker/date-picker.js.map +1 -0
- package/es/components/date-picker/date-picker.scss.js +6 -0
- package/es/components/date-picker/date-picker.scss.js.map +1 -0
- package/es/components/date-picker/defs.d.ts +45 -0
- package/es/components/date-picker/defs.js +51 -0
- package/es/components/date-picker/defs.js.map +1 -0
- package/es/index.d.ts +10 -0
- package/es/index.js +5 -0
- package/es/index.js.map +1 -0
- package/es/state-machine/actions.d.ts +35 -0
- package/es/state-machine/actions.js +860 -0
- package/es/state-machine/actions.js.map +1 -0
- package/es/state-machine/adapters/web-component-adapter.d.ts +142 -0
- package/es/state-machine/adapters/web-component-adapter.js +269 -0
- package/es/state-machine/adapters/web-component-adapter.js.map +1 -0
- package/es/state-machine/effects.d.ts +35 -0
- package/es/state-machine/effects.js +92 -0
- package/es/state-machine/effects.js.map +1 -0
- package/es/state-machine/guards.d.ts +41 -0
- package/es/state-machine/guards.js +143 -0
- package/es/state-machine/guards.js.map +1 -0
- package/es/state-machine/index.d.ts +12 -0
- package/es/state-machine/machine.d.ts +92 -0
- package/es/state-machine/machine.js +272 -0
- package/es/state-machine/machine.js.map +1 -0
- package/es/state-machine/states.d.ts +89 -0
- package/es/state-machine/states.js +105 -0
- package/es/state-machine/states.js.map +1 -0
- package/es/state-machine/temporal-utils.d.ts +203 -0
- package/es/state-machine/temporal-utils.js +128 -0
- package/es/state-machine/temporal-utils.js.map +1 -0
- package/es/state-machine/types.d.ts +163 -0
- package/es/temp-imports/.storybook/templates/with-layer.d.ts +26 -0
- package/es/temp-imports/globals/decorators/carbon-element.d.ts +45 -0
- package/es/temp-imports/globals/decorators/carbon-element.js +63 -0
- package/es/temp-imports/globals/decorators/carbon-element.js.map +1 -0
- package/es/temp-imports/globals/decorators/host-listener.d.ts +17 -0
- package/es/temp-imports/globals/decorators/host-listener.js +62 -0
- package/es/temp-imports/globals/decorators/host-listener.js.map +1 -0
- package/es/temp-imports/globals/internal/collection-helpers.d.ts +41 -0
- package/es/temp-imports/globals/internal/handle.d.ts +16 -0
- package/es/temp-imports/globals/internal/icon-loader-utils.d.ts +31 -0
- package/es/temp-imports/globals/internal/icon-loader-utils.js +69 -0
- package/es/temp-imports/globals/internal/icon-loader-utils.js.map +1 -0
- package/es/temp-imports/globals/internal/icon-loader.d.ts +28 -0
- package/es/temp-imports/globals/internal/icon-loader.js +43 -0
- package/es/temp-imports/globals/internal/icon-loader.js.map +1 -0
- package/es/temp-imports/globals/mixins/focus.d.ts +369 -0
- package/es/temp-imports/globals/mixins/focus.js +38 -0
- package/es/temp-imports/globals/mixins/focus.js.map +1 -0
- package/es/temp-imports/globals/mixins/form.d.ts +379 -0
- package/es/temp-imports/globals/mixins/form.js +49 -0
- package/es/temp-imports/globals/mixins/form.js.map +1 -0
- package/es/temp-imports/globals/mixins/host-listener.d.ts +387 -0
- package/es/temp-imports/globals/mixins/host-listener.js +76 -0
- package/es/temp-imports/globals/mixins/host-listener.js.map +1 -0
- package/es/temp-imports/globals/mixins/on.d.ts +9 -0
- package/es/temp-imports/globals/mixins/on.js +19 -0
- package/es/temp-imports/globals/mixins/on.js.map +1 -0
- package/es/temp-imports/globals/settings.d.ts +13 -0
- package/es/temp-imports/globals/settings.js +76 -0
- package/es/temp-imports/globals/settings.js.map +1 -0
- package/es/temp-imports/globals/shared-enums.d.ts +19 -0
- package/es/temp-imports/globals/shared-enums.js +23 -0
- package/es/temp-imports/globals/shared-enums.js.map +1 -0
- package/lib/__stories__/date-picker.stories.d.ts +949 -0
- package/lib/components/date-picker/calendar-renderer.d.ts +147 -0
- package/lib/components/date-picker/calendar-renderer.js +408 -0
- package/lib/components/date-picker/calendar-renderer.js.map +1 -0
- package/lib/components/date-picker/date-picker-input-skeleton.d.ts +34 -0
- package/lib/components/date-picker/date-picker-input-skeleton.js +79 -0
- package/lib/components/date-picker/date-picker-input-skeleton.js.map +1 -0
- package/lib/components/date-picker/date-picker-input.d.ts +539 -0
- package/lib/components/date-picker/date-picker-input.js +422 -0
- package/lib/components/date-picker/date-picker-input.js.map +1 -0
- package/lib/components/date-picker/date-picker.d.ts +977 -0
- package/lib/components/date-picker/date-picker.js +1049 -0
- package/lib/components/date-picker/date-picker.js.map +1 -0
- package/lib/components/date-picker/date-picker.scss.js +10 -0
- package/lib/components/date-picker/date-picker.scss.js.map +1 -0
- package/lib/components/date-picker/defs.d.ts +45 -0
- package/lib/components/date-picker/defs.js +56 -0
- package/lib/components/date-picker/defs.js.map +1 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.js +8 -0
- package/lib/index.js.map +1 -0
- package/lib/state-machine/actions.d.ts +35 -0
- package/lib/state-machine/actions.js +864 -0
- package/lib/state-machine/actions.js.map +1 -0
- package/lib/state-machine/adapters/web-component-adapter.d.ts +142 -0
- package/lib/state-machine/adapters/web-component-adapter.js +271 -0
- package/lib/state-machine/adapters/web-component-adapter.js.map +1 -0
- package/lib/state-machine/effects.d.ts +35 -0
- package/lib/state-machine/effects.js +96 -0
- package/lib/state-machine/effects.js.map +1 -0
- package/lib/state-machine/guards.d.ts +41 -0
- package/lib/state-machine/guards.js +147 -0
- package/lib/state-machine/guards.js.map +1 -0
- package/lib/state-machine/index.d.ts +12 -0
- package/lib/state-machine/machine.d.ts +92 -0
- package/lib/state-machine/machine.js +274 -0
- package/lib/state-machine/machine.js.map +1 -0
- package/lib/state-machine/states.d.ts +89 -0
- package/lib/state-machine/states.js +105 -0
- package/lib/state-machine/states.js.map +1 -0
- package/lib/state-machine/temporal-utils.d.ts +203 -0
- package/lib/state-machine/temporal-utils.js +136 -0
- package/lib/state-machine/temporal-utils.js.map +1 -0
- package/lib/state-machine/types.d.ts +163 -0
- package/lib/temp-imports/.storybook/templates/with-layer.d.ts +26 -0
- package/lib/temp-imports/globals/decorators/carbon-element.d.ts +45 -0
- package/lib/temp-imports/globals/decorators/carbon-element.js +65 -0
- package/lib/temp-imports/globals/decorators/carbon-element.js.map +1 -0
- package/lib/temp-imports/globals/decorators/host-listener.d.ts +17 -0
- package/lib/temp-imports/globals/decorators/host-listener.js +66 -0
- package/lib/temp-imports/globals/decorators/host-listener.js.map +1 -0
- package/lib/temp-imports/globals/internal/collection-helpers.d.ts +41 -0
- package/lib/temp-imports/globals/internal/handle.d.ts +16 -0
- package/lib/temp-imports/globals/internal/icon-loader-utils.d.ts +31 -0
- package/lib/temp-imports/globals/internal/icon-loader-utils.js +72 -0
- package/lib/temp-imports/globals/internal/icon-loader-utils.js.map +1 -0
- package/lib/temp-imports/globals/internal/icon-loader.d.ts +28 -0
- package/lib/temp-imports/globals/internal/icon-loader.js +45 -0
- package/lib/temp-imports/globals/internal/icon-loader.js.map +1 -0
- package/lib/temp-imports/globals/mixins/focus.d.ts +369 -0
- package/lib/temp-imports/globals/mixins/focus.js +42 -0
- package/lib/temp-imports/globals/mixins/focus.js.map +1 -0
- package/lib/temp-imports/globals/mixins/form.d.ts +379 -0
- package/lib/temp-imports/globals/mixins/form.js +53 -0
- package/lib/temp-imports/globals/mixins/form.js.map +1 -0
- package/lib/temp-imports/globals/mixins/host-listener.d.ts +387 -0
- package/lib/temp-imports/globals/mixins/host-listener.js +80 -0
- package/lib/temp-imports/globals/mixins/host-listener.js.map +1 -0
- package/lib/temp-imports/globals/mixins/on.d.ts +9 -0
- package/lib/temp-imports/globals/mixins/on.js +23 -0
- package/lib/temp-imports/globals/mixins/on.js.map +1 -0
- package/lib/temp-imports/globals/settings.d.ts +13 -0
- package/lib/temp-imports/globals/settings.js +79 -0
- package/lib/temp-imports/globals/settings.js.map +1 -0
- package/lib/temp-imports/globals/shared-enums.d.ts +19 -0
- package/lib/temp-imports/globals/shared-enums.js +23 -0
- package/lib/temp-imports/globals/shared-enums.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,1049 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var tslib = require('tslib');
|
|
4
|
+
var lit = require('lit');
|
|
5
|
+
var decorators_js = require('lit/decorators.js');
|
|
6
|
+
var settings = require('../../temp-imports/globals/settings.js');
|
|
7
|
+
var form = require('../../temp-imports/globals/mixins/form.js');
|
|
8
|
+
var hostListener = require('../../temp-imports/globals/mixins/host-listener.js');
|
|
9
|
+
var hostListener$1 = require('../../temp-imports/globals/decorators/host-listener.js');
|
|
10
|
+
var webComponentAdapter = require('../../state-machine/adapters/web-component-adapter.js');
|
|
11
|
+
require('../../state-machine/machine.js');
|
|
12
|
+
var states = require('../../state-machine/states.js');
|
|
13
|
+
require('../../state-machine/guards.js');
|
|
14
|
+
require('../../state-machine/actions.js');
|
|
15
|
+
require('../../state-machine/effects.js');
|
|
16
|
+
var temporalUtils = require('../../state-machine/temporal-utils.js');
|
|
17
|
+
var datePicker = require('./date-picker.scss.js');
|
|
18
|
+
var carbonElement = require('../../temp-imports/globals/decorators/carbon-element.js');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Copyright IBM Corp. 2019, 2026
|
|
22
|
+
*
|
|
23
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
24
|
+
* LICENSE file in the root directory of this source tree.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Date picker modes.
|
|
28
|
+
*/
|
|
29
|
+
var DATE_PICKER_MODE;
|
|
30
|
+
(function (DATE_PICKER_MODE) {
|
|
31
|
+
/**
|
|
32
|
+
* Simple mode, without calendar dropdown.
|
|
33
|
+
*/
|
|
34
|
+
DATE_PICKER_MODE["SIMPLE"] = "simple";
|
|
35
|
+
/**
|
|
36
|
+
* Single date mode.
|
|
37
|
+
*/
|
|
38
|
+
DATE_PICKER_MODE["SINGLE"] = "single";
|
|
39
|
+
/**
|
|
40
|
+
* Range mode.
|
|
41
|
+
*/
|
|
42
|
+
DATE_PICKER_MODE["RANGE"] = "range";
|
|
43
|
+
})(DATE_PICKER_MODE || (DATE_PICKER_MODE = {}));
|
|
44
|
+
/**
|
|
45
|
+
* Date picker.
|
|
46
|
+
*
|
|
47
|
+
* @element cds-date-picker
|
|
48
|
+
* @fires cds-date-picker-changed - The custom event fired when the date selection changes.
|
|
49
|
+
* @fires cds-date-picker-error - The custom event fired when an error occurs.
|
|
50
|
+
*/
|
|
51
|
+
// @ts-expect-error - Mixin inheritance not fully recognized by TypeScript
|
|
52
|
+
let CDSDatePicker = class CDSDatePicker extends hostListener.default(form.default(lit.LitElement)) {
|
|
53
|
+
constructor() {
|
|
54
|
+
super(...arguments);
|
|
55
|
+
/**
|
|
56
|
+
* The slotted `<cds-date-input kind="from">`.
|
|
57
|
+
*/
|
|
58
|
+
this._dateInteractNode = null;
|
|
59
|
+
/**
|
|
60
|
+
* The adapter for Web Component integration.
|
|
61
|
+
*/
|
|
62
|
+
this._adapter = null;
|
|
63
|
+
/**
|
|
64
|
+
* Timestamp of when calendar was last closed via Tab key
|
|
65
|
+
*/
|
|
66
|
+
this._lastTabCloseTime = 0;
|
|
67
|
+
/**
|
|
68
|
+
* Handles `${prefix}-date-picker-changed` event on this element.
|
|
69
|
+
*
|
|
70
|
+
* @param {CustomEvent} root0 - The event object
|
|
71
|
+
* @param {object} root0.detail - The event detail
|
|
72
|
+
*/
|
|
73
|
+
this._handleChange = ({ detail }) => {
|
|
74
|
+
const { selectedDates } = detail;
|
|
75
|
+
if (selectedDates && Array.isArray(selectedDates)) {
|
|
76
|
+
this._value = selectedDates
|
|
77
|
+
.filter((date) => date != null) // Filter out null/undefined values
|
|
78
|
+
.map((date) => {
|
|
79
|
+
if (typeof date === 'string') {
|
|
80
|
+
return date;
|
|
81
|
+
}
|
|
82
|
+
// Handle Temporal.PlainDate
|
|
83
|
+
return date.toString();
|
|
84
|
+
})
|
|
85
|
+
.join('/');
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Handles calendar icon click event from date-picker-input
|
|
90
|
+
*
|
|
91
|
+
* @param {CustomEvent} _event - The icon click event
|
|
92
|
+
*/
|
|
93
|
+
this._handleIconClick = (_event) => {
|
|
94
|
+
if (this._adapter && !this.disabled && !this.readonly) {
|
|
95
|
+
this._adapter.send(states.DatePickerEvent.CALENDAR_ICON_CLICK);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Handles input focus event from date-picker-input
|
|
100
|
+
*
|
|
101
|
+
* @param {CustomEvent} event - The focus event
|
|
102
|
+
*/
|
|
103
|
+
this._handleInputFocus = (event) => {
|
|
104
|
+
if (this._adapter && !this.disabled && !this.readonly) {
|
|
105
|
+
const { inputType } = event.detail || {};
|
|
106
|
+
this._adapter.send(states.DatePickerEvent.INPUT_FOCUS, { inputType });
|
|
107
|
+
// Don't auto-open calendar if we JUST closed it via Tab key (within 100ms)
|
|
108
|
+
// This prevents the immediate reopen when Tab moves focus to input,
|
|
109
|
+
// but allows normal opening after that brief window
|
|
110
|
+
const timeSinceTabClose = Date.now() - this._lastTabCloseTime;
|
|
111
|
+
const shouldSkipOpen = timeSinceTabClose < 100;
|
|
112
|
+
if (!shouldSkipOpen) {
|
|
113
|
+
// Open calendar when input is focused (matching current Carbon behavior)
|
|
114
|
+
this._adapter.send(states.DatePickerEvent.CALENDAR_OPEN);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Handles input blur event from date-picker-input
|
|
120
|
+
*
|
|
121
|
+
* @param {CustomEvent} event - The blur event
|
|
122
|
+
*/
|
|
123
|
+
this._handleInputBlur = (event) => {
|
|
124
|
+
if (this._adapter) {
|
|
125
|
+
const { inputType } = event.detail || {};
|
|
126
|
+
this._adapter.send(states.DatePickerEvent.INPUT_BLUR, { inputType });
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Handles state machine state changes
|
|
131
|
+
*
|
|
132
|
+
* @param {StateTransition} transition - The state transition
|
|
133
|
+
* @param {DatePickerState} transition.from - Previous state
|
|
134
|
+
* @param {DatePickerState} transition.to - New state
|
|
135
|
+
* @param {DatePickerContext} transition.context - Current context
|
|
136
|
+
*/
|
|
137
|
+
this._handleStateChange = (transition) => {
|
|
138
|
+
var _a, _b;
|
|
139
|
+
const { to, from, context } = transition;
|
|
140
|
+
const newState = to;
|
|
141
|
+
// Update open property based on state
|
|
142
|
+
if (newState === states.DatePickerState.CALENDAR_OPEN) {
|
|
143
|
+
this.open = true;
|
|
144
|
+
// If we're staying in CALENDAR_OPEN state (keyboard navigation),
|
|
145
|
+
// trigger a re-render to update the calendar with new focusedDate/viewDate
|
|
146
|
+
if (from === states.DatePickerState.CALENDAR_OPEN) {
|
|
147
|
+
this.requestUpdate();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else if (newState === states.DatePickerState.SELECTING_END) {
|
|
151
|
+
// Keep calendar open while selecting end date in range mode
|
|
152
|
+
this.open = true;
|
|
153
|
+
// If we're staying in SELECTING_END state (keyboard navigation),
|
|
154
|
+
// trigger a re-render to update the calendar with new focusedDate/viewDate
|
|
155
|
+
if (from === states.DatePickerState.SELECTING_END) {
|
|
156
|
+
this.requestUpdate();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else if (newState === states.DatePickerState.IDLE) {
|
|
160
|
+
this.open = false;
|
|
161
|
+
}
|
|
162
|
+
else if (newState === states.DatePickerState.FOCUSED) {
|
|
163
|
+
// Only close calendar if not coming from CALENDAR_OPEN (Enter key)
|
|
164
|
+
// When coming from CALENDAR_OPEN, the action sets context.isOpen
|
|
165
|
+
if (from !== states.DatePickerState.CALENDAR_OPEN &&
|
|
166
|
+
from !== states.DatePickerState.SELECTING_END) {
|
|
167
|
+
this.open = false;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
// Use the isOpen value from context (set by ENTER_KEY action)
|
|
171
|
+
this.open = (_a = context.isOpen) !== null && _a !== void 0 ? _a : false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else if (newState === states.DatePickerState.DATE_SELECTED) {
|
|
175
|
+
// Update open property based on context
|
|
176
|
+
this.open = (_b = context.isOpen) !== null && _b !== void 0 ? _b : false;
|
|
177
|
+
// Trigger re-render to update the calendar visibility
|
|
178
|
+
this.requestUpdate();
|
|
179
|
+
}
|
|
180
|
+
// Dispatch change event and update input when dates are selected
|
|
181
|
+
// This handles DATE_SELECTED state, FOCUSED state (from ENTER_KEY), and SELECTING_END state (after start date selected)
|
|
182
|
+
const shouldUpdateInput = newState === states.DatePickerState.DATE_SELECTED ||
|
|
183
|
+
newState === states.DatePickerState.SELECTING_END ||
|
|
184
|
+
(newState === states.DatePickerState.FOCUSED &&
|
|
185
|
+
from === states.DatePickerState.CALENDAR_OPEN &&
|
|
186
|
+
context.startDate);
|
|
187
|
+
if (shouldUpdateInput) {
|
|
188
|
+
// Only include non-null dates in selectedDates array
|
|
189
|
+
const selectedDates = context.endDate
|
|
190
|
+
? [context.startDate, context.endDate].filter((date) => date != null)
|
|
191
|
+
: context.startDate
|
|
192
|
+
? [context.startDate]
|
|
193
|
+
: [];
|
|
194
|
+
// Update input field value with formatted date
|
|
195
|
+
/**
|
|
196
|
+
* Format date as MM/DD/YYYY (matching Carbon's default format)
|
|
197
|
+
*
|
|
198
|
+
* @param {Temporal.PlainDate} date - The date to format
|
|
199
|
+
* @returns {string} Formatted date string
|
|
200
|
+
*/
|
|
201
|
+
const formatDate = (date) => {
|
|
202
|
+
// Safety check - ensure date is a valid Temporal.PlainDate
|
|
203
|
+
if (!date ||
|
|
204
|
+
typeof date !== 'object' ||
|
|
205
|
+
!('month' in date) ||
|
|
206
|
+
!('day' in date) ||
|
|
207
|
+
!('year' in date)) {
|
|
208
|
+
console.error('formatDate received invalid date:', date);
|
|
209
|
+
return '';
|
|
210
|
+
}
|
|
211
|
+
const month = String(date.month).padStart(2, '0');
|
|
212
|
+
const day = String(date.day).padStart(2, '0');
|
|
213
|
+
const year = date.year;
|
|
214
|
+
return `${month}/${day}/${year}`;
|
|
215
|
+
};
|
|
216
|
+
// For range mode, update the correct input based on state
|
|
217
|
+
if (this._mode === DATE_PICKER_MODE.RANGE) {
|
|
218
|
+
const { selectorInputFrom, selectorInputTo } = this
|
|
219
|
+
.constructor;
|
|
220
|
+
// @ts-expect-error - querySelector is available from HTMLElement
|
|
221
|
+
const inputFrom = this.querySelector(selectorInputFrom);
|
|
222
|
+
// @ts-expect-error - querySelector is available from HTMLElement
|
|
223
|
+
const inputTo = this.querySelector(selectorInputTo);
|
|
224
|
+
// If we're in DATE_SELECTED state with both dates, update both inputs
|
|
225
|
+
if (newState === states.DatePickerState.DATE_SELECTED &&
|
|
226
|
+
context.startDate &&
|
|
227
|
+
context.endDate) {
|
|
228
|
+
if (inputFrom) {
|
|
229
|
+
inputFrom.value = formatDate(context.startDate);
|
|
230
|
+
}
|
|
231
|
+
if (inputTo) {
|
|
232
|
+
inputTo.value = formatDate(context.endDate);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else if (context.lastFocusedInput === 'to' &&
|
|
236
|
+
inputTo &&
|
|
237
|
+
context.endDate) {
|
|
238
|
+
// Otherwise, update based on lastFocusedInput
|
|
239
|
+
inputTo.value = formatDate(context.endDate);
|
|
240
|
+
}
|
|
241
|
+
else if (inputFrom && context.startDate) {
|
|
242
|
+
inputFrom.value = formatDate(context.startDate);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// For single mode, update the single input
|
|
247
|
+
if (this._dateInteractNode && context.startDate) {
|
|
248
|
+
this._dateInteractNode.value = formatDate(context.startDate);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
this.dispatchEvent(new CustomEvent(this.constructor.eventChange, {
|
|
252
|
+
bubbles: true,
|
|
253
|
+
composed: true,
|
|
254
|
+
detail: {
|
|
255
|
+
selectedDates,
|
|
256
|
+
value: this._value,
|
|
257
|
+
},
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* Handles calendar date selection
|
|
263
|
+
*
|
|
264
|
+
* @param {CustomEvent} event - The date select event
|
|
265
|
+
*/
|
|
266
|
+
this._handleCalendarDateSelect = (event) => {
|
|
267
|
+
const { date } = event.detail;
|
|
268
|
+
if (!this._adapter || !date) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const { _mode: mode } = this;
|
|
272
|
+
const context = this._adapter.getContext();
|
|
273
|
+
if (mode === DATE_PICKER_MODE.RANGE) {
|
|
274
|
+
// Range mode: select start or end date
|
|
275
|
+
if (!context.startDate || context.endDate) {
|
|
276
|
+
// Select start date (or restart selection)
|
|
277
|
+
this._adapter.send(states.DatePickerEvent.RANGE_START_SELECT, { date });
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
// Select end date
|
|
281
|
+
this._adapter.send(states.DatePickerEvent.RANGE_END_SELECT, { date });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
// Single mode: select date
|
|
286
|
+
this._adapter.send(states.DatePickerEvent.DATE_SELECT, { date });
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
/**
|
|
290
|
+
* Handles calendar month change
|
|
291
|
+
*
|
|
292
|
+
* @param {CustomEvent} event - The month change event
|
|
293
|
+
*/
|
|
294
|
+
this._handleCalendarMonthChange = (event) => {
|
|
295
|
+
const { month } = event.detail;
|
|
296
|
+
if (!this._adapter || !month) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
// Update viewDate in context
|
|
300
|
+
const firstDayOfMonth = month.toPlainDate({ day: 1 });
|
|
301
|
+
this._adapter.updateContext({ viewDate: firstDayOfMonth });
|
|
302
|
+
};
|
|
303
|
+
/**
|
|
304
|
+
* Allows the user to enter a date directly into the input field
|
|
305
|
+
*/
|
|
306
|
+
this.allowInput = true;
|
|
307
|
+
/**
|
|
308
|
+
* Controls whether the calendar dropdown closes upon selection.
|
|
309
|
+
*/
|
|
310
|
+
this.closeOnSelect = true;
|
|
311
|
+
/**
|
|
312
|
+
* Controls the disabled state of the input
|
|
313
|
+
*/
|
|
314
|
+
this.disabled = false;
|
|
315
|
+
/**
|
|
316
|
+
* Name for the input in the `FormData`
|
|
317
|
+
*/
|
|
318
|
+
this.name = '';
|
|
319
|
+
/**
|
|
320
|
+
* `true` if the date picker should be open.
|
|
321
|
+
*/
|
|
322
|
+
this.open = false;
|
|
323
|
+
/**
|
|
324
|
+
* Specify if the component should be read-only
|
|
325
|
+
*/
|
|
326
|
+
this.readonly = false;
|
|
327
|
+
/**
|
|
328
|
+
* Handle clicks outside the date picker to close the calendar
|
|
329
|
+
*
|
|
330
|
+
* @param {MouseEvent} event - The click event
|
|
331
|
+
*/
|
|
332
|
+
this._handleOutsideClick = (event) => {
|
|
333
|
+
var _a;
|
|
334
|
+
if (!this.open) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const target = event.target;
|
|
338
|
+
// Check if click is outside the date picker component
|
|
339
|
+
if (!this.contains(target) && !((_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.contains(target))) {
|
|
340
|
+
this.open = false;
|
|
341
|
+
if (this._adapter) {
|
|
342
|
+
this._adapter.send(states.DatePickerEvent.OUTSIDE_CLICK);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
/**
|
|
347
|
+
* Handle keyboard events for calendar navigation
|
|
348
|
+
*
|
|
349
|
+
* @param {KeyboardEvent} event - The keyboard event
|
|
350
|
+
*/
|
|
351
|
+
this._handleKeyDown = (event) => {
|
|
352
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
353
|
+
if (!this._adapter) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const { key } = event;
|
|
357
|
+
// Handle Tab key
|
|
358
|
+
if (key === 'Tab') {
|
|
359
|
+
const composedPath = event.composedPath();
|
|
360
|
+
const target = composedPath[0];
|
|
361
|
+
// Get input elements
|
|
362
|
+
const { selectorInputFrom, selectorInputTo } = this
|
|
363
|
+
.constructor;
|
|
364
|
+
const inputFrom = this.querySelector(selectorInputFrom);
|
|
365
|
+
const inputTo = this.querySelector(selectorInputTo);
|
|
366
|
+
// Check if focus is on input fields
|
|
367
|
+
const isOnFirstInput = inputFrom && composedPath.includes(inputFrom);
|
|
368
|
+
const isOnSecondInput = inputTo && composedPath.includes(inputTo);
|
|
369
|
+
// Check if the target is the calendar element or within it
|
|
370
|
+
const calendar = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('cds-date-picker-calendar');
|
|
371
|
+
const isFocusInCalendar = target === calendar ||
|
|
372
|
+
((_b = target.closest) === null || _b === void 0 ? void 0 : _b.call(target, 'cds-date-picker-calendar')) !== null ||
|
|
373
|
+
composedPath.includes(calendar);
|
|
374
|
+
// Case 1: Tab FROM first input -> Focus calendar (if open)
|
|
375
|
+
if (this.open && isOnFirstInput && !event.shiftKey) {
|
|
376
|
+
event.preventDefault();
|
|
377
|
+
event.stopPropagation();
|
|
378
|
+
const calendarElement = (_c = this.shadowRoot) === null || _c === void 0 ? void 0 : _c.querySelector('cds-date-picker-calendar');
|
|
379
|
+
const calendarDiv = (_d = calendarElement === null || calendarElement === void 0 ? void 0 : calendarElement.shadowRoot) === null || _d === void 0 ? void 0 : _d.querySelector('.cds--date-picker__calendar');
|
|
380
|
+
if (calendarDiv) {
|
|
381
|
+
setTimeout(() => {
|
|
382
|
+
calendarDiv.focus();
|
|
383
|
+
}, 0);
|
|
384
|
+
}
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
// Case 2: Tab FROM second input (range mode) -> Focus calendar (if open)
|
|
388
|
+
if (this.open &&
|
|
389
|
+
isOnSecondInput &&
|
|
390
|
+
!event.shiftKey &&
|
|
391
|
+
this._mode === DATE_PICKER_MODE.RANGE) {
|
|
392
|
+
event.preventDefault();
|
|
393
|
+
event.stopPropagation(); // Prevent input from handling the event
|
|
394
|
+
const calendarElement = (_e = this.shadowRoot) === null || _e === void 0 ? void 0 : _e.querySelector('cds-date-picker-calendar');
|
|
395
|
+
const calendarDiv = (_f = calendarElement === null || calendarElement === void 0 ? void 0 : calendarElement.shadowRoot) === null || _f === void 0 ? void 0 : _f.querySelector('.cds--date-picker__calendar');
|
|
396
|
+
if (calendarDiv) {
|
|
397
|
+
// Use setTimeout to ensure focus happens after event processing
|
|
398
|
+
setTimeout(() => {
|
|
399
|
+
calendarDiv.focus();
|
|
400
|
+
}, 0);
|
|
401
|
+
}
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
// Case 3: Shift+Tab FROM calendar -> Move to appropriate input
|
|
405
|
+
if (this.open && isFocusInCalendar && event.shiftKey) {
|
|
406
|
+
event.preventDefault();
|
|
407
|
+
// In range mode, check which input was last focused
|
|
408
|
+
if (this._mode === DATE_PICKER_MODE.RANGE) {
|
|
409
|
+
const context = (_g = this._adapter) === null || _g === void 0 ? void 0 : _g.getContext();
|
|
410
|
+
const lastFocused = context === null || context === void 0 ? void 0 : context.lastFocusedInput;
|
|
411
|
+
// If we were on the second input, go back to it
|
|
412
|
+
if (lastFocused === 'to' && inputTo) {
|
|
413
|
+
(_h = inputTo.input) === null || _h === void 0 ? void 0 : _h.focus();
|
|
414
|
+
}
|
|
415
|
+
else if (inputFrom) {
|
|
416
|
+
// Otherwise go to first input
|
|
417
|
+
(_j = inputFrom.input) === null || _j === void 0 ? void 0 : _j.focus();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
// Single mode: go to first input
|
|
422
|
+
if (inputFrom) {
|
|
423
|
+
(_k = inputFrom.input) === null || _k === void 0 ? void 0 : _k.focus();
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
// Case 4: Tab FROM calendar -> Move to second input (range) or close (single)
|
|
429
|
+
if (this.open && isFocusInCalendar && !event.shiftKey) {
|
|
430
|
+
event.preventDefault();
|
|
431
|
+
// In range mode, check which input was last focused
|
|
432
|
+
if (this._mode === DATE_PICKER_MODE.RANGE) {
|
|
433
|
+
const context = (_l = this._adapter) === null || _l === void 0 ? void 0 : _l.getContext();
|
|
434
|
+
const lastFocused = context === null || context === void 0 ? void 0 : context.lastFocusedInput;
|
|
435
|
+
// If we were on the first input, move to second input
|
|
436
|
+
if (lastFocused === 'from' && inputTo) {
|
|
437
|
+
(_m = inputTo.input) === null || _m === void 0 ? void 0 : _m.focus();
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
// Otherwise (single mode or after second input in range mode), close and move to next element
|
|
442
|
+
// Record the time when calendar was closed via Tab
|
|
443
|
+
this._lastTabCloseTime = Date.now();
|
|
444
|
+
this._adapter.send(states.DatePickerEvent.TAB_KEY);
|
|
445
|
+
// Find and focus the next tabbable element after this date picker
|
|
446
|
+
this._focusNextElement(false);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
// Only handle other keyboard events when calendar is open AND focus is in calendar
|
|
451
|
+
if (!this.open) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
// Get the actual target from the composed path (handles shadow DOM)
|
|
455
|
+
const composedPath = event.composedPath();
|
|
456
|
+
const target = composedPath[0];
|
|
457
|
+
const input = (_o = this.shadowRoot) === null || _o === void 0 ? void 0 : _o.querySelector('input');
|
|
458
|
+
// Only handle arrow keys, Page Up/Down, Home/End when focus is in calendar, not on input
|
|
459
|
+
if (target === input) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
// Check if the target has the calendar class or is within an element with that class
|
|
463
|
+
// The calendar div is inside the calendar renderer's shadow DOM
|
|
464
|
+
const isFocusInCalendar = ((_p = target.classList) === null || _p === void 0 ? void 0 : _p.contains('cds--date-picker__calendar')) ||
|
|
465
|
+
((_q = target.closest) === null || _q === void 0 ? void 0 : _q.call(target, '.cds--date-picker__calendar')) !== null;
|
|
466
|
+
if (!isFocusInCalendar) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
// Handle Escape key - close calendar
|
|
470
|
+
if (key === 'Escape') {
|
|
471
|
+
event.preventDefault();
|
|
472
|
+
this.open = false;
|
|
473
|
+
this._adapter.send(states.DatePickerEvent.ESCAPE_KEY);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
// Handle Enter key - select focused date
|
|
477
|
+
if (key === 'Enter') {
|
|
478
|
+
event.preventDefault();
|
|
479
|
+
// Get the focused date from context
|
|
480
|
+
const context = this._adapter.getContext();
|
|
481
|
+
const focusedDate = context.focusedDate;
|
|
482
|
+
if (!focusedDate) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
// Dispatch the appropriate event based on mode and current state
|
|
486
|
+
const currentState = this._adapter.getState();
|
|
487
|
+
if (this._mode === DATE_PICKER_MODE.RANGE) {
|
|
488
|
+
// In range mode, check if we're selecting start or end date
|
|
489
|
+
if (currentState === 'selecting_end') {
|
|
490
|
+
// Selecting end date
|
|
491
|
+
this._adapter.send(states.DatePickerEvent.RANGE_END_SELECT, {
|
|
492
|
+
date: focusedDate,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
// Selecting start date
|
|
497
|
+
this._adapter.send(states.DatePickerEvent.RANGE_START_SELECT, {
|
|
498
|
+
date: focusedDate,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
// Single mode - just select the date
|
|
504
|
+
this._adapter.send(states.DatePickerEvent.DATE_SELECT, {
|
|
505
|
+
date: focusedDate,
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
// Handle arrow keys - navigate dates
|
|
511
|
+
if (key === 'ArrowUp') {
|
|
512
|
+
event.preventDefault();
|
|
513
|
+
this._adapter.send(states.DatePickerEvent.ARROW_UP);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (key === 'ArrowDown') {
|
|
517
|
+
event.preventDefault();
|
|
518
|
+
this._adapter.send(states.DatePickerEvent.ARROW_DOWN);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (key === 'ArrowLeft') {
|
|
522
|
+
event.preventDefault();
|
|
523
|
+
this._adapter.send(states.DatePickerEvent.ARROW_LEFT);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (key === 'ArrowRight') {
|
|
527
|
+
event.preventDefault();
|
|
528
|
+
this._adapter.send(states.DatePickerEvent.ARROW_RIGHT);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
// Handle Page Up/Down - navigate months
|
|
532
|
+
if (key === 'PageUp') {
|
|
533
|
+
event.preventDefault();
|
|
534
|
+
this._adapter.send(states.DatePickerEvent.PAGE_UP);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
if (key === 'PageDown') {
|
|
538
|
+
event.preventDefault();
|
|
539
|
+
this._adapter.send(states.DatePickerEvent.PAGE_DOWN);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
// Handle Home/End - navigate to start/end of week
|
|
543
|
+
if (key === 'Home') {
|
|
544
|
+
event.preventDefault();
|
|
545
|
+
this._adapter.send(states.DatePickerEvent.HOME_KEY);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
if (key === 'End') {
|
|
549
|
+
event.preventDefault();
|
|
550
|
+
this._adapter.send(states.DatePickerEvent.END_KEY);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* @returns The effective date picker mode, determined by the child `<cds-date-picker-input>`.
|
|
557
|
+
*/
|
|
558
|
+
get _mode() {
|
|
559
|
+
const { selectorInputTo } = this.constructor;
|
|
560
|
+
// @ts-expect-error - querySelector from mixin
|
|
561
|
+
if (this.querySelector(selectorInputTo)) {
|
|
562
|
+
return DATE_PICKER_MODE.RANGE;
|
|
563
|
+
}
|
|
564
|
+
// @ts-expect-error - querySelector from mixin
|
|
565
|
+
if (this.querySelector(`${settings.prefix}-date-picker-input[kind="single"]`)) {
|
|
566
|
+
return DATE_PICKER_MODE.SINGLE;
|
|
567
|
+
}
|
|
568
|
+
return DATE_PICKER_MODE.SIMPLE;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Handles form data event
|
|
572
|
+
*
|
|
573
|
+
* @param {FormDataEvent} event - The form data event
|
|
574
|
+
*/
|
|
575
|
+
_handleFormdata(event) {
|
|
576
|
+
const { formData } = event;
|
|
577
|
+
const { disabled, name, value } = this;
|
|
578
|
+
if (!disabled) {
|
|
579
|
+
formData.append(name, value);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Handles `slotchange` event in the `<slot>`.
|
|
584
|
+
*
|
|
585
|
+
* @param {Event} root0 - The event object
|
|
586
|
+
* @param {EventTarget} root0.target - The event target
|
|
587
|
+
*/
|
|
588
|
+
_handleSlotChange({ target }) {
|
|
589
|
+
const { _dateInteractNode: oldDateInteractNode } = this;
|
|
590
|
+
const dateInteractNode = target
|
|
591
|
+
.assignedNodes()
|
|
592
|
+
.find((node) => node.nodeType === Node.ELEMENT_NODE &&
|
|
593
|
+
node.matches(this.constructor.selectorInputFrom));
|
|
594
|
+
// @ts-expect-error - Type comparison between mixin types
|
|
595
|
+
if (oldDateInteractNode !== dateInteractNode) {
|
|
596
|
+
this._dateInteractNode =
|
|
597
|
+
dateInteractNode;
|
|
598
|
+
this._initializeDatePicker();
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Show the calendar popover using Popover API
|
|
603
|
+
*/
|
|
604
|
+
_showCalendarPopover() {
|
|
605
|
+
var _a;
|
|
606
|
+
const popover = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector(`#${settings.prefix}-date-picker-calendar-popover`);
|
|
607
|
+
if (popover && typeof popover.showPopover === 'function') {
|
|
608
|
+
try {
|
|
609
|
+
popover.showPopover();
|
|
610
|
+
}
|
|
611
|
+
catch (e) {
|
|
612
|
+
// Popover might already be showing
|
|
613
|
+
console.warn('Could not show popover:', e);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Hide the calendar popover using Popover API
|
|
619
|
+
*/
|
|
620
|
+
_hideCalendarPopover() {
|
|
621
|
+
var _a;
|
|
622
|
+
const popover = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector(`#${settings.prefix}-date-picker-calendar-popover`);
|
|
623
|
+
if (popover && typeof popover.hidePopover === 'function') {
|
|
624
|
+
try {
|
|
625
|
+
popover.hidePopover();
|
|
626
|
+
}
|
|
627
|
+
catch (e) {
|
|
628
|
+
// Popover might already be hidden
|
|
629
|
+
console.warn('Could not hide popover:', e);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Initializes the date picker with a state machine.
|
|
635
|
+
*/
|
|
636
|
+
_initializeDatePicker() {
|
|
637
|
+
this._releaseDatePicker();
|
|
638
|
+
const { _dateInteractNode: dateInteractNode, _mode: mode } = this;
|
|
639
|
+
// Don't instantiate in simple mode
|
|
640
|
+
if (!dateInteractNode || !dateInteractNode.input || mode === 'simple') {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
// Create adapter with configuration
|
|
644
|
+
this._adapter = new webComponentAdapter.WebComponentAdapter({
|
|
645
|
+
component: this,
|
|
646
|
+
initialContext: {
|
|
647
|
+
mode: mode === DATE_PICKER_MODE.RANGE ? 'range' : 'single',
|
|
648
|
+
dateFormat: this.dateFormat || 'm/d/Y',
|
|
649
|
+
allowInput: this.allowInput,
|
|
650
|
+
closeOnSelect: this.closeOnSelect,
|
|
651
|
+
value: this.value || '',
|
|
652
|
+
startDate: null,
|
|
653
|
+
endDate: null,
|
|
654
|
+
isOpen: false,
|
|
655
|
+
isFocused: false,
|
|
656
|
+
isDisabled: this.disabled,
|
|
657
|
+
isReadonly: this.readonly,
|
|
658
|
+
isInvalid: false,
|
|
659
|
+
lastFocusedInput: null,
|
|
660
|
+
minDate: temporalUtils.parseDateToPlainDate(this.minDate),
|
|
661
|
+
maxDate: temporalUtils.parseDateToPlainDate(this.maxDate),
|
|
662
|
+
},
|
|
663
|
+
onStateChange: this._handleStateChange,
|
|
664
|
+
});
|
|
665
|
+
// Set initial value if provided
|
|
666
|
+
if (this.value) {
|
|
667
|
+
const dates = this.value.split('/').filter(Boolean);
|
|
668
|
+
if (dates.length > 0) {
|
|
669
|
+
this._adapter.send(mode === DATE_PICKER_MODE.RANGE
|
|
670
|
+
? states.DatePickerEvent.RANGE_START_SELECT
|
|
671
|
+
: states.DatePickerEvent.DATE_SELECT, { date: dates[0] });
|
|
672
|
+
if (mode === DATE_PICKER_MODE.RANGE && dates.length > 1) {
|
|
673
|
+
this._adapter.send(states.DatePickerEvent.RANGE_END_SELECT, {
|
|
674
|
+
date: dates[1],
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Releases the date picker state machine.
|
|
682
|
+
*/
|
|
683
|
+
_releaseDatePicker() {
|
|
684
|
+
if (this._adapter) {
|
|
685
|
+
this._adapter.destroy();
|
|
686
|
+
this._adapter = null;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* The date(s) in ISO8601 format (date portion only), for range mode, '/' is used for separate start/end dates.
|
|
691
|
+
*/
|
|
692
|
+
get value() {
|
|
693
|
+
return this._value;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Sets the value
|
|
697
|
+
*
|
|
698
|
+
* @param {string} value - The new value
|
|
699
|
+
*/
|
|
700
|
+
set value(value) {
|
|
701
|
+
const { _value: oldValue } = this;
|
|
702
|
+
this._value = value;
|
|
703
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
704
|
+
// @ts-expect-error - requestUpdate is available from LitElement
|
|
705
|
+
this.requestUpdate('value', oldValue);
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Find and focus the next tabbable element after the date picker
|
|
709
|
+
* @param {boolean} backwards - Whether to tab backwards (Shift+Tab)
|
|
710
|
+
*/
|
|
711
|
+
_focusNextElement(backwards = false) {
|
|
712
|
+
// Get all tabbable elements in the document
|
|
713
|
+
const tabbableSelector = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
714
|
+
const allTabbable = Array.from(document.querySelectorAll(tabbableSelector));
|
|
715
|
+
// Find the index of this date picker component
|
|
716
|
+
const currentIndex = allTabbable.findIndex((el) => this.contains(el) || el === this);
|
|
717
|
+
if (currentIndex === -1) {
|
|
718
|
+
return; // Couldn't find current element
|
|
719
|
+
}
|
|
720
|
+
// Find the next element after all elements within this date picker
|
|
721
|
+
let nextIndex = currentIndex;
|
|
722
|
+
if (backwards) {
|
|
723
|
+
// Tab backwards - find previous element before this date picker
|
|
724
|
+
for (let i = currentIndex - 1; i >= 0; i--) {
|
|
725
|
+
if (!this.contains(allTabbable[i])) {
|
|
726
|
+
nextIndex = i;
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
else {
|
|
732
|
+
// Tab forwards - find next element after this date picker
|
|
733
|
+
for (let i = currentIndex + 1; i < allTabbable.length; i++) {
|
|
734
|
+
if (!this.contains(allTabbable[i])) {
|
|
735
|
+
nextIndex = i;
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
// Focus the next element
|
|
741
|
+
if (nextIndex !== currentIndex && allTabbable[nextIndex]) {
|
|
742
|
+
allTabbable[nextIndex].focus();
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Lifecycle callback when element is connected
|
|
747
|
+
*/
|
|
748
|
+
connectedCallback() {
|
|
749
|
+
super.connectedCallback();
|
|
750
|
+
this._initializeDatePicker();
|
|
751
|
+
// Add outside click listener
|
|
752
|
+
document.addEventListener('click', this._handleOutsideClick, true);
|
|
753
|
+
// Add keyboard event listener
|
|
754
|
+
document.addEventListener('keydown', this._handleKeyDown, true);
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Lifecycle callback when element is disconnected
|
|
758
|
+
*/
|
|
759
|
+
disconnectedCallback() {
|
|
760
|
+
// Remove outside click listener
|
|
761
|
+
document.removeEventListener('click', this._handleOutsideClick, true);
|
|
762
|
+
// Remove keyboard event listener
|
|
763
|
+
document.removeEventListener('keydown', this._handleKeyDown, true);
|
|
764
|
+
this._releaseDatePicker();
|
|
765
|
+
super.disconnectedCallback();
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Lifecycle callback when properties change
|
|
769
|
+
*
|
|
770
|
+
* @param {Map<string, any>} changedProperties - Map of changed properties
|
|
771
|
+
*/
|
|
772
|
+
updated(changedProperties) {
|
|
773
|
+
if (this._adapter) {
|
|
774
|
+
if (changedProperties.has('minDate') ||
|
|
775
|
+
changedProperties.has('maxDate')) {
|
|
776
|
+
// Update context
|
|
777
|
+
this._adapter.updateContext({
|
|
778
|
+
minDate: this.minDate ? Temporal.PlainDate.from(this.minDate) : null,
|
|
779
|
+
maxDate: this.maxDate ? Temporal.PlainDate.from(this.maxDate) : null,
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
if (changedProperties.has('open')) {
|
|
783
|
+
if (this.open && !this.readonly) {
|
|
784
|
+
this._adapter.send(states.DatePickerEvent.CALENDAR_OPEN);
|
|
785
|
+
// Show popover using Popover API
|
|
786
|
+
this._showCalendarPopover();
|
|
787
|
+
}
|
|
788
|
+
else if (!this.open) {
|
|
789
|
+
this._adapter.send(states.DatePickerEvent.CALENDAR_CLOSE);
|
|
790
|
+
// Hide popover using Popover API
|
|
791
|
+
this._hideCalendarPopover();
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
if (changedProperties.has('disabled') ||
|
|
795
|
+
changedProperties.has('readonly')) {
|
|
796
|
+
const { selectorInputFrom, selectorInputTo } = this
|
|
797
|
+
.constructor;
|
|
798
|
+
const inputFrom = this.querySelector(selectorInputFrom);
|
|
799
|
+
const inputTo = this.querySelector(selectorInputTo);
|
|
800
|
+
[inputFrom, inputTo].forEach((input) => {
|
|
801
|
+
if (input) {
|
|
802
|
+
input.disabled = this.disabled;
|
|
803
|
+
input.readonly = this.readonly;
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
this._adapter.updateContext({
|
|
807
|
+
isDisabled: this.disabled,
|
|
808
|
+
isReadonly: this.readonly,
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
if (changedProperties.has('value') && this.value) {
|
|
812
|
+
const dates = this.value.split('/').filter(Boolean);
|
|
813
|
+
if (dates.length > 0) {
|
|
814
|
+
// Convert ISO string to Temporal.PlainDate
|
|
815
|
+
const startDate = temporalUtils.parseISOToPlainDate(dates[0]);
|
|
816
|
+
if (startDate) {
|
|
817
|
+
this._adapter.send(this._mode === DATE_PICKER_MODE.RANGE
|
|
818
|
+
? states.DatePickerEvent.RANGE_START_SELECT
|
|
819
|
+
: states.DatePickerEvent.DATE_SELECT, { date: startDate });
|
|
820
|
+
if (this._mode === DATE_PICKER_MODE.RANGE && dates.length > 1) {
|
|
821
|
+
const endDate = temporalUtils.parseISOToPlainDate(dates[1]);
|
|
822
|
+
if (endDate) {
|
|
823
|
+
this._adapter.send(states.DatePickerEvent.RANGE_END_SELECT, {
|
|
824
|
+
date: endDate,
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Renders the component
|
|
835
|
+
*/
|
|
836
|
+
render() {
|
|
837
|
+
var _a;
|
|
838
|
+
const { _handleSlotChange: handleSlotChange, _mode: mode, open } = this;
|
|
839
|
+
const context = (_a = this._adapter) === null || _a === void 0 ? void 0 : _a.getContext();
|
|
840
|
+
const selectedDates = (context === null || context === void 0 ? void 0 : context.endDate)
|
|
841
|
+
? [context.startDate, context.endDate].filter(Boolean)
|
|
842
|
+
: (context === null || context === void 0 ? void 0 : context.startDate)
|
|
843
|
+
? [context.startDate]
|
|
844
|
+
: [];
|
|
845
|
+
return lit.html `
|
|
846
|
+
<a
|
|
847
|
+
class="${settings.prefix}--visually-hidden"
|
|
848
|
+
href="javascript:void 0"
|
|
849
|
+
role="navigation"
|
|
850
|
+
tabindex="-1"></a>
|
|
851
|
+
<slot @slotchange="${handleSlotChange}"></slot>
|
|
852
|
+
<div
|
|
853
|
+
id="floating-menu-container"
|
|
854
|
+
class="${settings.prefix}--date-picker__calendar-container">
|
|
855
|
+
${mode !== DATE_PICKER_MODE.SIMPLE && open
|
|
856
|
+
? lit.html `
|
|
857
|
+
<cds-date-picker-calendar
|
|
858
|
+
.rangeMode="${mode === DATE_PICKER_MODE.RANGE}"
|
|
859
|
+
.dateFormat="${this.dateFormat || 'm/d/Y'}"
|
|
860
|
+
.minDate="${this.minDate
|
|
861
|
+
? Temporal.PlainDate.from(this.minDate)
|
|
862
|
+
: undefined}"
|
|
863
|
+
.maxDate="${this.maxDate
|
|
864
|
+
? Temporal.PlainDate.from(this.maxDate)
|
|
865
|
+
: undefined}"
|
|
866
|
+
.selectedDates="${selectedDates}"
|
|
867
|
+
.viewDate="${(context === null || context === void 0 ? void 0 : context.viewDate) || null}"
|
|
868
|
+
.focusedDate="${(context === null || context === void 0 ? void 0 : context.focusedDate) || null}"
|
|
869
|
+
@cds-date-picker-calendar-date-select="${this
|
|
870
|
+
._handleCalendarDateSelect}"
|
|
871
|
+
@cds-date-picker-calendar-month-change="${this
|
|
872
|
+
._handleCalendarMonthChange}">
|
|
873
|
+
</cds-date-picker-calendar>
|
|
874
|
+
`
|
|
875
|
+
: ''}
|
|
876
|
+
</div>
|
|
877
|
+
<a
|
|
878
|
+
class="${settings.prefix}--visually-hidden"
|
|
879
|
+
href="javascript:void 0"
|
|
880
|
+
role="navigation"
|
|
881
|
+
tabindex="-1"></a>
|
|
882
|
+
`;
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* The CSS class for the calendar dropdown.
|
|
886
|
+
*/
|
|
887
|
+
static get classCalendarContainer() {
|
|
888
|
+
return `${settings.prefix}--date-picker__calendar`;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* The CSS class for the month navigator.
|
|
892
|
+
*/
|
|
893
|
+
static get classMonth() {
|
|
894
|
+
return `${settings.prefix}--date-picker__month`;
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* The CSS class for the container of the weekdays.
|
|
898
|
+
*/
|
|
899
|
+
static get classWeekdays() {
|
|
900
|
+
return `${settings.prefix}--date-picker__weekdays`;
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* The CSS class for the container of the days.
|
|
904
|
+
*/
|
|
905
|
+
static get classDays() {
|
|
906
|
+
return `${settings.prefix}--date-picker__days`;
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* The CSS class applied to each weekdays.
|
|
910
|
+
*/
|
|
911
|
+
static get classWeekday() {
|
|
912
|
+
return `${settings.prefix}--date-picker__weekday`;
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* The CSS class applied to each days.
|
|
916
|
+
*/
|
|
917
|
+
static get classDay() {
|
|
918
|
+
return `${settings.prefix}--date-picker__day`;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* A selector that will return the `<input>` to enter starting date.
|
|
922
|
+
*/
|
|
923
|
+
static get selectorInputFrom() {
|
|
924
|
+
return `${settings.prefix}-date-picker-input,${settings.prefix}-date-picker-input[kind="from"]`;
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* A selector that will return the `<input>` to enter end date.
|
|
928
|
+
*/
|
|
929
|
+
static get selectorInputTo() {
|
|
930
|
+
return `${settings.prefix}-date-picker-input[kind="to"]`;
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* The name of the custom event when an error occurs.
|
|
934
|
+
*/
|
|
935
|
+
static get eventError() {
|
|
936
|
+
return `${settings.prefix}-date-picker-error`;
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* The name of the custom event fired when the date selection changes.
|
|
940
|
+
*/
|
|
941
|
+
static get eventChange() {
|
|
942
|
+
return `${settings.prefix}-date-picker-changed`;
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* The name of the custom event fired when the calendar icon is clicked.
|
|
946
|
+
*/
|
|
947
|
+
static get eventIconClick() {
|
|
948
|
+
return `${settings.prefix}-date-picker-icon-click`;
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* The name of the custom event fired when an input receives focus.
|
|
952
|
+
*/
|
|
953
|
+
static get eventInputFocus() {
|
|
954
|
+
return `${settings.prefix}-date-picker-input-focus`;
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* The name of the custom event fired when an input loses focus.
|
|
958
|
+
*/
|
|
959
|
+
static get eventInputBlur() {
|
|
960
|
+
return `${settings.prefix}-date-picker-input-blur`;
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
/**
|
|
964
|
+
* The CSS class applied to the "today" highlight if there are any dates selected.
|
|
965
|
+
*/
|
|
966
|
+
CDSDatePicker.classNoBorder = 'no-border';
|
|
967
|
+
/**
|
|
968
|
+
* The default date format.
|
|
969
|
+
*/
|
|
970
|
+
CDSDatePicker.defaultDateFormat = 'm/d/Y';
|
|
971
|
+
CDSDatePicker.styles = datePicker.default;
|
|
972
|
+
tslib.__decorate([
|
|
973
|
+
hostListener$1.default('eventChange')
|
|
974
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
975
|
+
// @ts-ignore: The decorator refers to this method, but TS thinks this method is not referred to
|
|
976
|
+
,
|
|
977
|
+
tslib.__metadata("design:type", Object)
|
|
978
|
+
], CDSDatePicker.prototype, "_handleChange", void 0);
|
|
979
|
+
tslib.__decorate([
|
|
980
|
+
hostListener$1.default('eventIconClick')
|
|
981
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
982
|
+
// @ts-ignore: The decorator refers to this method, but TS thinks this method is not referred to
|
|
983
|
+
,
|
|
984
|
+
tslib.__metadata("design:type", Object)
|
|
985
|
+
], CDSDatePicker.prototype, "_handleIconClick", void 0);
|
|
986
|
+
tslib.__decorate([
|
|
987
|
+
hostListener$1.default('eventInputFocus')
|
|
988
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
989
|
+
// @ts-ignore: The decorator refers to this method, but TS thinks this method is not referred to
|
|
990
|
+
,
|
|
991
|
+
tslib.__metadata("design:type", Object)
|
|
992
|
+
], CDSDatePicker.prototype, "_handleInputFocus", void 0);
|
|
993
|
+
tslib.__decorate([
|
|
994
|
+
hostListener$1.default('eventInputBlur')
|
|
995
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
996
|
+
// @ts-ignore: The decorator refers to this method, but TS thinks this method is not referred to
|
|
997
|
+
,
|
|
998
|
+
tslib.__metadata("design:type", Object)
|
|
999
|
+
], CDSDatePicker.prototype, "_handleInputBlur", void 0);
|
|
1000
|
+
tslib.__decorate([
|
|
1001
|
+
decorators_js.property({ type: Boolean, reflect: true, attribute: 'allow-input' }),
|
|
1002
|
+
tslib.__metadata("design:type", Object)
|
|
1003
|
+
], CDSDatePicker.prototype, "allowInput", void 0);
|
|
1004
|
+
tslib.__decorate([
|
|
1005
|
+
decorators_js.property({ type: Boolean, reflect: true, attribute: 'close-on-select' }),
|
|
1006
|
+
tslib.__metadata("design:type", Object)
|
|
1007
|
+
], CDSDatePicker.prototype, "closeOnSelect", void 0);
|
|
1008
|
+
tslib.__decorate([
|
|
1009
|
+
decorators_js.property({ attribute: 'date-format' }),
|
|
1010
|
+
tslib.__metadata("design:type", String)
|
|
1011
|
+
], CDSDatePicker.prototype, "dateFormat", void 0);
|
|
1012
|
+
tslib.__decorate([
|
|
1013
|
+
decorators_js.property({ type: Boolean, reflect: true }),
|
|
1014
|
+
tslib.__metadata("design:type", Object)
|
|
1015
|
+
], CDSDatePicker.prototype, "disabled", void 0);
|
|
1016
|
+
tslib.__decorate([
|
|
1017
|
+
decorators_js.property({ attribute: 'enabled-range' }),
|
|
1018
|
+
tslib.__metadata("design:type", String)
|
|
1019
|
+
], CDSDatePicker.prototype, "enabledRange", void 0);
|
|
1020
|
+
tslib.__decorate([
|
|
1021
|
+
decorators_js.property({ attribute: 'max-date' }),
|
|
1022
|
+
tslib.__metadata("design:type", String)
|
|
1023
|
+
], CDSDatePicker.prototype, "maxDate", void 0);
|
|
1024
|
+
tslib.__decorate([
|
|
1025
|
+
decorators_js.property({ attribute: 'min-date' }),
|
|
1026
|
+
tslib.__metadata("design:type", String)
|
|
1027
|
+
], CDSDatePicker.prototype, "minDate", void 0);
|
|
1028
|
+
tslib.__decorate([
|
|
1029
|
+
decorators_js.property(),
|
|
1030
|
+
tslib.__metadata("design:type", Object)
|
|
1031
|
+
], CDSDatePicker.prototype, "name", void 0);
|
|
1032
|
+
tslib.__decorate([
|
|
1033
|
+
decorators_js.property({ type: Boolean, reflect: true }),
|
|
1034
|
+
tslib.__metadata("design:type", Object)
|
|
1035
|
+
], CDSDatePicker.prototype, "open", void 0);
|
|
1036
|
+
tslib.__decorate([
|
|
1037
|
+
decorators_js.property({ type: Boolean, reflect: true }),
|
|
1038
|
+
tslib.__metadata("design:type", Object)
|
|
1039
|
+
], CDSDatePicker.prototype, "readonly", void 0);
|
|
1040
|
+
tslib.__decorate([
|
|
1041
|
+
decorators_js.property(),
|
|
1042
|
+
tslib.__metadata("design:type", String),
|
|
1043
|
+
tslib.__metadata("design:paramtypes", [String])
|
|
1044
|
+
], CDSDatePicker.prototype, "value", null);
|
|
1045
|
+
CDSDatePicker = tslib.__decorate([
|
|
1046
|
+
carbonElement.carbonElement(`${settings.prefix}-date-picker`)
|
|
1047
|
+
], CDSDatePicker);
|
|
1048
|
+
// Made with Bob
|
|
1049
|
+
//# sourceMappingURL=date-picker.js.map
|