UNPKG

12 kBJavaScriptView Raw
1import { __decorate } from "tslib";
2import '../icon-button/app-icon-button.js';
3import { TextField } from '@material/mwc-textfield';
4import { html, nothing } from 'lit';
5import { property, queryAsync, state } from 'lit/decorators.js';
6import { ifDefined } from 'lit/directives/if-defined.js';
7import { DateTimeFormat } from '../constants.js';
8import { appDatePickerName } from '../date-picker/constants.js';
9import { appDatePickerInputSurfaceName } from '../date-picker-input-surface/constants.js';
10import { slotDatePicker } from '../helpers/slot-date-picker.js';
11import { toDateString } from '../helpers/to-date-string.js';
12import { warnUndefinedElement } from '../helpers/warn-undefined-element.js';
13import { appIconButtonName } from '../icon-button/constants.js';
14import { iconClear } from '../icons.js';
15import { keyEnter, keyEscape, keySpace, keyTab } from '../key-values.js';
16import { DatePickerMinMaxMixin } from '../mixins/date-picker-min-max-mixin.js';
17import { DatePickerMixin } from '../mixins/date-picker-mixin.js';
18import { ElementMixin } from '../mixins/element-mixin.js';
19import { baseStyling } from '../stylings.js';
20import { appDatePickerInputClearLabel, appDatePickerInputType } from './constants.js';
21import { datePickerInputStyling } from './stylings.js';
22export 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);