1 | import { __decorate } from "tslib";
|
2 | import '../icon-button/app-icon-button.js';
|
3 | import { TextField } from '@material/mwc-textfield';
|
4 | import { html, nothing } from 'lit';
|
5 | import { property, queryAsync, state } from 'lit/decorators.js';
|
6 | import { ifDefined } from 'lit/directives/if-defined.js';
|
7 | import { DateTimeFormat } from '../constants.js';
|
8 | import { appDatePickerName } from '../date-picker/constants.js';
|
9 | import { appDatePickerInputSurfaceName } from '../date-picker-input-surface/constants.js';
|
10 | import { slotDatePicker } from '../helpers/slot-date-picker.js';
|
11 | import { toDateString } from '../helpers/to-date-string.js';
|
12 | import { warnUndefinedElement } from '../helpers/warn-undefined-element.js';
|
13 | import { appIconButtonName } from '../icon-button/constants.js';
|
14 | import { iconClear } from '../icons.js';
|
15 | import { keyEnter, keyEscape, keySpace, keyTab } from '../key-values.js';
|
16 | import { DatePickerMinMaxMixin } from '../mixins/date-picker-min-max-mixin.js';
|
17 | import { DatePickerMixin } from '../mixins/date-picker-mixin.js';
|
18 | import { ElementMixin } from '../mixins/element-mixin.js';
|
19 | import { baseStyling } from '../stylings.js';
|
20 | import { appDatePickerInputClearLabel, appDatePickerInputType } from './constants.js';
|
21 | import { datePickerInputStyling } from './stylings.js';
|
22 | export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinMaxMixin(TextField))) {
|
23 | constructor() {
|
24 | super(...arguments);
|
25 | this.iconTrailing = 'clear';
|
26 | this.type = appDatePickerInputType;
|
27 | this.clearLabel = appDatePickerInputClearLabel;
|
28 | this._disabled = false;
|
29 | this._lazyLoaded = false;
|
30 | this._open = false;
|
31 | this.#disconnect = () => undefined;
|
32 | this.#focusElement = undefined;
|
33 | this.#lazyLoading = false;
|
34 | this.#picker = undefined;
|
35 | this.#valueFormatter = this.$toValueFormatter();
|
36 | this.#lazyLoad = async () => {
|
37 | if (this._lazyLoaded || this.#lazyLoading)
|
38 | return;
|
39 | const deps = [
|
40 | appDatePickerName,
|
41 | appDatePickerInputSurfaceName,
|
42 | ];
|
43 | if (deps.some(n => globalThis.customElements.get(n) == null)) {
|
44 | this.#lazyLoading = true;
|
45 | const tasks = deps.map(n => globalThis.customElements.whenDefined(n));
|
46 | const imports = [
|
47 | import('../date-picker/app-date-picker.js'),
|
48 | import('../date-picker-input-surface/app-date-picker-input-surface.js'),
|
49 | ];
|
50 | try {
|
51 | await Promise.all(imports);
|
52 | await Promise.all(tasks);
|
53 | }
|
54 | catch (error) {
|
55 | console.error(error);
|
56 | }
|
57 | }
|
58 | await this.updateComplete;
|
59 | this.#lazyLoading = false;
|
60 | this._lazyLoaded = true;
|
61 | };
|
62 | this.#onResetClick = (ev) => {
|
63 | if (this._disabled)
|
64 | return;
|
65 | ev.preventDefault();
|
66 | this.reset();
|
67 | };
|
68 | this.#onClosed = ({ detail }) => {
|
69 | this._open = false;
|
70 | this.fire({ detail, type: 'closed' });
|
71 | };
|
72 | this.#onDatePickerDateUpdated = async (ev) => {
|
73 | const { isKeypress, key, valueAsDate, } = ev.detail;
|
74 | if (!isKeypress || (key === keyEnter || key === keySpace)) {
|
75 | this.#updateValues(valueAsDate);
|
76 | isKeypress && (await this.$inputSurface)?.close();
|
77 | }
|
78 | };
|
79 | this.#onDatePickerFirstUpdated = ({ currentTarget, detail: { focusableElements: [focusableElement], valueAsDate, }, }) => {
|
80 | this.#focusElement = focusableElement;
|
81 | this.#picker = currentTarget;
|
82 | this.#updateValues(valueAsDate);
|
83 | };
|
84 | this.#onOpened = async ({ detail }) => {
|
85 | await this.#picker?.updateComplete;
|
86 | await this.updateComplete;
|
87 | this.#focusElement?.focus();
|
88 | this.fire({ detail, type: 'opened' });
|
89 | };
|
90 | this.#updateValues = (value) => {
|
91 | if (value) {
|
92 | const valueDate = new Date(value);
|
93 | this.#valueAsDate = valueDate;
|
94 | this.value = toDateString(valueDate);
|
95 | }
|
96 | else {
|
97 | this.reset();
|
98 | }
|
99 | };
|
100 | }
|
101 | get valueAsDate() {
|
102 | return this.#valueAsDate || null;
|
103 | }
|
104 | get valueAsNumber() {
|
105 | return Number(this.#valueAsDate || NaN);
|
106 | }
|
107 | #disconnect;
|
108 | #focusElement;
|
109 | #lazyLoading;
|
110 | #picker;
|
111 | #valueAsDate;
|
112 | #valueFormatter;
|
113 | static { this.styles = [
|
114 | ...TextField.styles,
|
115 | baseStyling,
|
116 | datePickerInputStyling,
|
117 | ]; }
|
118 | disconnectedCallback() {
|
119 | super.disconnectedCallback();
|
120 | this.#disconnect();
|
121 | }
|
122 | async firstUpdated() {
|
123 | super.firstUpdated();
|
124 | const input = await this.$input;
|
125 | if (input) {
|
126 | const onBodyKeyup = async (ev) => {
|
127 | if (this._disabled)
|
128 | return;
|
129 | if (ev.key === keyEscape) {
|
130 | this.closePicker();
|
131 | }
|
132 | else if (ev.key === keyTab) {
|
133 | const inputSurface = await this.$inputSurface;
|
134 | const isTabInsideInputSurface = ev.composedPath().find(n => n.nodeType === Node.ELEMENT_NODE &&
|
135 | n.isEqualNode(inputSurface));
|
136 | if (!isTabInsideInputSurface)
|
137 | this.closePicker();
|
138 | }
|
139 | };
|
140 | const onClick = () => {
|
141 | if (this._disabled)
|
142 | return;
|
143 | this._open = true;
|
144 | };
|
145 | const onKeyup = (ev) => {
|
146 | if (this._disabled)
|
147 | return;
|
148 | if ([keySpace, keyEnter].some(n => n === ev.key)) {
|
149 | onClick();
|
150 | }
|
151 | };
|
152 | document.body.addEventListener('keyup', onBodyKeyup);
|
153 | input.addEventListener('keyup', onKeyup);
|
154 | input.addEventListener('click', onClick);
|
155 | this.#disconnect = () => {
|
156 | document.body.removeEventListener('keyup', onBodyKeyup);
|
157 | input.removeEventListener('keyup', onKeyup);
|
158 | input.removeEventListener('click', onClick);
|
159 | };
|
160 | }
|
161 | await this.outlineElement?.updateComplete;
|
162 | await this.layout();
|
163 | }
|
164 | willUpdate(changedProperties) {
|
165 | super.willUpdate(changedProperties);
|
166 | if (changedProperties.has('locale')) {
|
167 | this.locale = (this.locale || DateTimeFormat().resolvedOptions().locale);
|
168 | this.#valueFormatter = this.$toValueFormatter();
|
169 | this.#updateValues(this.value);
|
170 | }
|
171 | if (changedProperties.has('value')) {
|
172 | this.#updateValues(this.value);
|
173 | }
|
174 | if (changedProperties.has('disabled') || changedProperties.has('readOnly')) {
|
175 | this._disabled = this.disabled || this.readOnly;
|
176 | }
|
177 | }
|
178 | async updated() {
|
179 | if (this._open && this._lazyLoaded) {
|
180 | const picker = await this.$picker;
|
181 | picker?.queryAll?.(appIconButtonName).forEach(n => n.layout());
|
182 | }
|
183 | }
|
184 | render() {
|
185 | const { _lazyLoaded, _open, } = this;
|
186 | if (!_lazyLoaded && _open)
|
187 | this.#lazyLoad();
|
188 | return html `
|
189 | ${super.render()}
|
190 | ${_lazyLoaded ? this.$renderContent() : nothing}
|
191 | `;
|
192 | }
|
193 | closePicker() {
|
194 | this._open = false;
|
195 | }
|
196 | reset() {
|
197 | if (this._disabled)
|
198 | return;
|
199 | this.#valueAsDate = undefined;
|
200 | this.value = '';
|
201 | }
|
202 | showPicker() {
|
203 | if (this._disabled)
|
204 | return;
|
205 | this._open = true;
|
206 | }
|
207 | renderInput(shouldRenderHelperText) {
|
208 | const { autocapitalize, disabled, focused, helperPersistent, inputMode, isUiValid, label, name, placeholder, required, validationMessage, } = this;
|
209 | const autocapitalizeOrUndef = autocapitalize ?
|
210 | autocapitalize :
|
211 | undefined;
|
212 | const showValidationMessage = validationMessage && !isUiValid;
|
213 | const ariaLabelledbyOrUndef = label ? 'label' : undefined;
|
214 | const ariaControlsOrUndef = shouldRenderHelperText ? 'helper-text' : undefined;
|
215 | const ariaDescribedbyOrUndef = focused || helperPersistent || showValidationMessage ?
|
216 | 'helper-text' :
|
217 | undefined;
|
218 | const valueText = this.#valueAsDate ? this.#valueFormatter.format(this.#valueAsDate) : '';
|
219 | return html `
|
220 | <input
|
221 | ?disabled=${disabled}
|
222 | ?required=${required}
|
223 | .value=${valueText}
|
224 | @blur=${this.onInputBlur}
|
225 | @focus=${this.onInputFocus}
|
226 | aria-controls=${ifDefined(ariaControlsOrUndef)}
|
227 | aria-describedby=${ifDefined(ariaDescribedbyOrUndef)}
|
228 | aria-labelledby=${ifDefined(ariaLabelledbyOrUndef)}
|
229 | autocapitalize=${ifDefined(autocapitalizeOrUndef)}
|
230 | class=mdc-text-field__input
|
231 | inputmode=${ifDefined(inputMode)}
|
232 | name=${ifDefined(name || undefined)}
|
233 | placeholder=${ifDefined(placeholder)}
|
234 | readonly
|
235 | type=text
|
236 | >`;
|
237 | }
|
238 | renderTrailingIcon() {
|
239 | return html `
|
240 | <app-icon-button
|
241 | .disabled=${this._disabled}
|
242 | @click=${this.#onResetClick}
|
243 | aria-label=${this.clearLabel}
|
244 | class="mdc-text-field__icon mdc-text-field__icon--trailing"
|
245 | >
|
246 | ${iconClear}
|
247 | </app-icon-button>
|
248 | `;
|
249 | }
|
250 | $renderContent() {
|
251 | warnUndefinedElement(appDatePickerInputSurfaceName);
|
252 | return html `
|
253 | <app-date-picker-input-surface
|
254 | @opened=${this.#onOpened}
|
255 | ?open=${this._open}
|
256 | ?stayOpenOnBodyClick=${true}
|
257 | .anchor=${this}
|
258 | @closed=${this.#onClosed}
|
259 | >${this._open ? this.$renderSlot() : nothing}</app-date-picker-input-surface>
|
260 | `;
|
261 | }
|
262 | $renderSlot() {
|
263 | const { chooseMonthLabel, chooseYearLabel, disabledDates, disabledDays, firstDayOfWeek, locale, max, min, nextMonthLabel, previousMonthLabel, selectedDateLabel, selectedYearLabel, shortWeekLabel, showWeekNumber, startView, todayLabel, toyearLabel, value, weekLabel, weekNumberTemplate, weekNumberType, } = this;
|
264 | return slotDatePicker({
|
265 | chooseMonthLabel,
|
266 | chooseYearLabel,
|
267 | disabledDates,
|
268 | disabledDays,
|
269 | firstDayOfWeek,
|
270 | locale,
|
271 | max,
|
272 | min,
|
273 | nextMonthLabel,
|
274 | onDatePickerDateUpdated: this.#onDatePickerDateUpdated,
|
275 | onDatePickerFirstUpdated: this.#onDatePickerFirstUpdated,
|
276 | previousMonthLabel,
|
277 | selectedDateLabel,
|
278 | selectedYearLabel,
|
279 | shortWeekLabel,
|
280 | showWeekNumber,
|
281 | startView,
|
282 | todayLabel,
|
283 | toyearLabel,
|
284 | value,
|
285 | weekLabel,
|
286 | weekNumberTemplate,
|
287 | weekNumberType,
|
288 | });
|
289 | }
|
290 | $toValueFormatter() {
|
291 | return DateTimeFormat(this.locale, {
|
292 | year: 'numeric',
|
293 | month: 'short',
|
294 | day: 'numeric',
|
295 | });
|
296 | }
|
297 | #lazyLoad;
|
298 | #onResetClick;
|
299 | #onClosed;
|
300 | #onDatePickerDateUpdated;
|
301 | #onDatePickerFirstUpdated;
|
302 | #onOpened;
|
303 | #updateValues;
|
304 | }
|
305 | __decorate([
|
306 | property({ type: String })
|
307 | ], DatePickerInput.prototype, "clearLabel", void 0);
|
308 | __decorate([
|
309 | queryAsync('.mdc-text-field__input')
|
310 | ], DatePickerInput.prototype, "$input", void 0);
|
311 | __decorate([
|
312 | queryAsync(appDatePickerInputSurfaceName)
|
313 | ], DatePickerInput.prototype, "$inputSurface", void 0);
|
314 | __decorate([
|
315 | queryAsync(appDatePickerName)
|
316 | ], DatePickerInput.prototype, "$picker", void 0);
|
317 | __decorate([
|
318 | state()
|
319 | ], DatePickerInput.prototype, "_disabled", void 0);
|
320 | __decorate([
|
321 | state()
|
322 | ], DatePickerInput.prototype, "_lazyLoaded", void 0);
|
323 | __decorate([
|
324 | state()
|
325 | ], DatePickerInput.prototype, "_open", void 0);
|