@limetech/lime-elements 33.12.1-next.5 → 33.13.0-next.2

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.
@@ -81,6 +81,145 @@ export class InputField {
81
81
  this.isModified = false;
82
82
  this.showCompletions = false;
83
83
  this.completionsList = [];
84
+ this.initialize = () => {
85
+ const element = this.limelInputField.shadowRoot.querySelector('.mdc-text-field');
86
+ if (!element) {
87
+ return;
88
+ }
89
+ this.mdcTextField = new MDCTextField(element);
90
+ this.completionsList = [...this.completions].map((item) => {
91
+ return { text: item };
92
+ });
93
+ window.addEventListener('resize', this.layout, { passive: true });
94
+ this.limelInputField.addEventListener('focus', this.setFocus);
95
+ };
96
+ this.setFocus = () => {
97
+ this.mdcTextField.focus();
98
+ };
99
+ this.getContainerClassList = () => {
100
+ const classList = {
101
+ 'mdc-text-field': true,
102
+ 'mdc-text-field--outlined': true,
103
+ 'mdc-text-field--invalid': this.isInvalid(),
104
+ 'mdc-text-field--disabled': this.disabled || this.readonly,
105
+ 'lime-text-field--readonly': this.readonly,
106
+ 'mdc-text-field--required': this.required,
107
+ 'mdc-text-field--with-trailing-icon': !!this.getTrailingIcon(),
108
+ };
109
+ if (this.type === 'textarea') {
110
+ classList['mdc-text-field--textarea'] = true;
111
+ classList['has-helper-line'] =
112
+ !!this.helperText || !!this.maxlength;
113
+ }
114
+ else {
115
+ classList['mdc-text-field--with-leading-icon'] = !!this.leadingIcon;
116
+ }
117
+ return classList;
118
+ };
119
+ this.renderInput = (properties) => {
120
+ if (this.type === 'textarea') {
121
+ return;
122
+ }
123
+ const type = this.type === 'urlAsText' ? 'text' : this.type;
124
+ return (h("input", Object.assign({}, properties, { type: type, pattern: this.pattern, onWheel: this.handleWheel, onKeyDown: this.onKeyDown, value: this.value })));
125
+ };
126
+ this.renderTextarea = (properties) => {
127
+ if (this.type !== 'textarea') {
128
+ return;
129
+ }
130
+ return (h("span", { class: "mdc-text-field__resizer" },
131
+ h("textarea", Object.assign({}, properties), this.value)));
132
+ };
133
+ this.layout = () => {
134
+ var _a;
135
+ (_a = this.mdcTextField) === null || _a === void 0 ? void 0 : _a.layout();
136
+ };
137
+ this.getAdditionalProps = () => {
138
+ const props = {};
139
+ if (this.type === 'number') {
140
+ props.step = this.step;
141
+ }
142
+ if (this.type === 'number' && Number.isInteger(this.min)) {
143
+ props.min = this.min;
144
+ }
145
+ if (this.type === 'number' && Number.isInteger(this.max)) {
146
+ props.max = this.max;
147
+ }
148
+ if (this.minlength) {
149
+ props.minlength = this.minlength;
150
+ }
151
+ if (this.maxlength) {
152
+ props.maxlength = this.maxlength;
153
+ }
154
+ return props;
155
+ };
156
+ this.onFocus = () => {
157
+ this.isFocused = true;
158
+ this.showCompletions = true;
159
+ };
160
+ this.onBlur = () => {
161
+ this.isFocused = false;
162
+ this.isModified = true;
163
+ };
164
+ this.renderHelperLine = () => {
165
+ if (!this.maxlength && !this.hasHelperText()) {
166
+ return;
167
+ }
168
+ return (h("div", { tabIndex: -1, class: "mdc-text-field-helper-line" },
169
+ this.renderHelperText(),
170
+ this.renderCharacterCounter()));
171
+ };
172
+ this.renderEmptyValueForReadonly = () => {
173
+ if (this.readonly && !this.value) {
174
+ return (h("span", { class: "lime-empty-value-for-readonly lime-looks-like-input-value" }, "\u2013"));
175
+ }
176
+ };
177
+ this.renderHelperText = () => {
178
+ if (!this.hasHelperText()) {
179
+ return;
180
+ }
181
+ const classList = {
182
+ 'mdc-text-field-helper-text': true,
183
+ 'mdc-text-field-helper-text--validation-msg': this.isInvalid(),
184
+ };
185
+ return (h("p", { class: classList, id: helperTextId }, this.helperText));
186
+ };
187
+ this.hasHelperText = () => {
188
+ return this.helperText !== null && this.helperText !== undefined;
189
+ };
190
+ this.renderCharacterCounter = () => {
191
+ if (!this.maxlength || this.type === 'number') {
192
+ return;
193
+ }
194
+ const text = this.value || '';
195
+ const label = `${text.length} / ${this.maxlength}`;
196
+ return h("div", { class: "mdc-text-field-character-counter" }, label);
197
+ };
198
+ this.isInvalid = () => {
199
+ if (this.readonly) {
200
+ // A readonly field can never be invalid.
201
+ return false;
202
+ }
203
+ if (this.invalid) {
204
+ // `this.invalid` is set by the consumer. If the consumer explicitly
205
+ // told us to consider the field invalid, we consider it invalid
206
+ // regardless of what our internal validation thinks, and regardless
207
+ // of whether the field has been modified.
208
+ return true;
209
+ }
210
+ if (!this.isModified) {
211
+ return false;
212
+ }
213
+ const element = this.getInputElement();
214
+ return !(element && element.checkValidity());
215
+ };
216
+ this.getInputElement = () => {
217
+ let elementName = 'input';
218
+ if (this.type === 'textarea') {
219
+ elementName = 'textarea';
220
+ }
221
+ return this.limelInputField.shadowRoot.querySelector(elementName);
222
+ };
84
223
  this.renderLeadingIcon = () => {
85
224
  if (this.type === 'textarea') {
86
225
  return;
@@ -104,25 +243,210 @@ export class InputField {
104
243
  }
105
244
  return html;
106
245
  };
246
+ this.hasLink = () => {
247
+ return (this.showLink &&
248
+ ['email', 'tel', 'url', 'urlAsText'].includes(this.type));
249
+ };
250
+ this.getLink = () => {
251
+ const props = { href: '' };
252
+ switch (this.type) {
253
+ case 'email':
254
+ props.href = `mailto:${this.value}`;
255
+ break;
256
+ case 'tel':
257
+ props.href = `tel:${this.value}`;
258
+ break;
259
+ default:
260
+ props.href = getHref(this.value);
261
+ props.target = getTarget(this.value);
262
+ }
263
+ return props;
264
+ };
265
+ this.renderLinkIcon = (linkProps, icon) => {
266
+ // If the trailing icon uses the class `mdc-text-field__icon--trailing`,
267
+ // MDC attaches a click handler to it, which apparently runs
268
+ // `preventDefault()` on the event. For links, we don't want that,
269
+ // so instead of `mdc-text-field__icon--trailing`, we use our own class
270
+ // `lime-trailing-icon-for-link`, which uses all the same styling. /Ads
271
+ return (h("a", Object.assign({}, linkProps, { class: "material-icons mdc-text-field__icon lime-trailing-icon-for-link", tabindex: this.disabled ? '-1' : '0', role: "button" }),
272
+ h("limel-icon", { name: icon })));
273
+ };
274
+ this.renderTrailingIcon = (icon) => {
275
+ const props = {
276
+ tabIndex: this.isInvalid() ? '-1' : '0',
277
+ };
278
+ if (!this.isInvalid()) {
279
+ props.onKeyPress = this.handleIconKeyPress;
280
+ props.onClick = this.handleIconClick;
281
+ props.role = 'button';
282
+ }
283
+ return (h("i", Object.assign({ class: "material-icons mdc-text-field__icon mdc-text-field__icon--trailing" }, props),
284
+ h("limel-icon", { name: icon })));
285
+ };
286
+ this.getTrailingIcon = () => {
287
+ if (this.isInvalid()) {
288
+ return 'high_importance';
289
+ }
290
+ if (this.trailingIcon) {
291
+ return this.trailingIcon;
292
+ }
293
+ if (this.showLink && this.type === 'email') {
294
+ return 'filled_message';
295
+ }
296
+ if (this.showLink && this.type === 'tel') {
297
+ return 'phone';
298
+ }
299
+ if (this.showLink &&
300
+ (this.type === 'url' || this.type === 'urlAsText')) {
301
+ return 'external_link';
302
+ }
303
+ };
304
+ this.renderFormattedNumber = () => {
305
+ if (this.type !== 'number') {
306
+ return;
307
+ }
308
+ let renderValue = this.value;
309
+ if (this.formatNumber && this.value) {
310
+ renderValue = new Intl.NumberFormat(navigator.language).format(Number(this.value));
311
+ }
312
+ return (h("span", { class: "lime-formatted-input lime-looks-like-input-value" }, renderValue));
313
+ };
314
+ /**
315
+ * Key handler for the input field
316
+ * Will change focus to the first/last item in the dropdown list to enable selection with the keyboard
317
+ *
318
+ * @param {KeyboardEvent} event event
319
+ * @returns {void}
320
+ */
321
+ this.onKeyDown = (event) => {
322
+ this.showCompletions = true;
323
+ const isForwardTab = (event.key === TAB || event.keyCode === TAB_KEY_CODE) &&
324
+ !event.altKey &&
325
+ !event.metaKey &&
326
+ !event.shiftKey;
327
+ const isUp = event.key === ARROW_UP || event.keyCode === ARROW_UP_KEY_CODE;
328
+ const isDown = event.key === ARROW_DOWN || event.keyCode === ARROW_DOWN_KEY_CODE;
329
+ if (event.keyCode === TAB_KEY_CODE && event.shiftKey) {
330
+ this.showCompletions = false;
331
+ }
332
+ if (!isForwardTab && !isUp && !isDown) {
333
+ return;
334
+ }
335
+ const list = document.querySelector(` #${this.portalId} limel-list`);
336
+ if (!list) {
337
+ return;
338
+ }
339
+ event.preventDefault();
340
+ if (isForwardTab || isDown) {
341
+ const listElement = list.shadowRoot.querySelector('.mdc-deprecated-list-item:first-child');
342
+ listElement.focus();
343
+ return;
344
+ }
345
+ if (isUp) {
346
+ const listElement = list.shadowRoot.querySelector('.mdc-deprecated-list-item:last-child');
347
+ listElement.focus();
348
+ }
349
+ };
350
+ this.handleCompletionChange = (event) => {
351
+ event.stopPropagation();
352
+ if (!event.detail) {
353
+ return;
354
+ }
355
+ this.showCompletions = false;
356
+ /*
357
+ This change event doesn't need to be debounced in itself, but we want
358
+ to make absolutely sure that an earlier change event that *has* been
359
+ debounced doesn't emit after this one. Therefore, we run this through
360
+ the same debounced emitter function. /Ads
361
+ */
362
+ this.changeEmitter(event.detail.text);
363
+ };
364
+ this.renderAutocompleteList = () => {
365
+ if (this.type === 'textarea' || !this.completions.length) {
366
+ return;
367
+ }
368
+ return this.renderDropdown();
369
+ };
370
+ this.renderPortal = (content = null) => {
371
+ const dropdownZIndex = getComputedStyle(this.limelInputField).getPropertyValue('--dropdown-z-index');
372
+ return (h("limel-portal", { visible: this.showCompletions, containerId: this.portalId, inheritParentWidth: true, containerStyle: { 'z-index': dropdownZIndex } },
373
+ h("limel-menu-surface", { open: this.showCompletions, allowClicksElement: this.limelInputField, style: {
374
+ '--menu-surface-width': '100%',
375
+ 'max-height': 'inherit',
376
+ display: 'flex',
377
+ }, onDismiss: this.handleCloseMenu }, content)));
378
+ };
379
+ this.renderDropdown = () => {
380
+ const content = this.renderListResult();
381
+ return this.renderPortal(content);
382
+ };
383
+ this.renderListResult = () => {
384
+ const filteredCompletions = this.filterCompletions(this.value);
385
+ if (!filteredCompletions || filteredCompletions.length === 0) {
386
+ return null;
387
+ }
388
+ return (h("limel-list", { onChange: this.handleCompletionChange, onKeyDown: this.handleKeyDownInDropdown, type: "selectable", items: filteredCompletions }));
389
+ };
390
+ this.handleKeyDownInDropdown = (event) => {
391
+ const keyFound = [TAB, ESCAPE, ENTER].includes(event.key);
392
+ const keyCodeFound = [
393
+ TAB_KEY_CODE,
394
+ ESCAPE_KEY_CODE,
395
+ ENTER_KEY_CODE,
396
+ ].includes(event.keyCode);
397
+ if (keyFound || keyCodeFound) {
398
+ this.setFocus();
399
+ }
400
+ };
401
+ this.handleCloseMenu = () => {
402
+ this.showCompletions = false;
403
+ };
107
404
  this.filterCompletions = (filter) => {
108
405
  if (!filter) {
109
406
  return this.completionsList;
110
407
  }
111
408
  return this.completionsList.filter((completion) => completion.text.toLowerCase().indexOf(filter.toLowerCase()) > -1);
112
409
  };
113
- this.handleChange = this.handleChange.bind(this);
114
- this.handleIconKeyPress = this.handleIconKeyPress.bind(this);
115
- this.handleIconClick = this.handleIconClick.bind(this);
116
- this.onFocus = this.onFocus.bind(this);
117
- this.onBlur = this.onBlur.bind(this);
118
- this.onKeyDown = this.onKeyDown.bind(this);
119
- this.handleCompletionChange = this.handleCompletionChange.bind(this);
120
- this.layout = this.layout.bind(this);
121
- this.changeEmitter = this.changeEmitter.bind(this);
122
- this.getContainerClassList = this.getContainerClassList.bind(this);
123
- this.handleCloseMenu = this.handleCloseMenu.bind(this);
124
- this.setFocus = this.setFocus.bind(this);
125
- this.handleKeyDownInDropdown = this.handleKeyDownInDropdown.bind(this);
410
+ this.handleChange = (event) => {
411
+ event.stopPropagation();
412
+ let value = event.target.value;
413
+ if (this.type === 'number') {
414
+ if (!value && event.data) {
415
+ event.stopPropagation();
416
+ return;
417
+ }
418
+ if (value) {
419
+ value = Number(value);
420
+ }
421
+ }
422
+ this.changeEmitter(value);
423
+ };
424
+ this.changeEmitter = (value) => {
425
+ this.change.emit(value);
426
+ };
427
+ this.handleIconClick = () => {
428
+ if (!this.isInvalid()) {
429
+ this.action.emit();
430
+ }
431
+ };
432
+ this.handleIconKeyPress = (event) => {
433
+ const isEnter = event.key === ENTER || event.keyCode === ENTER_KEY_CODE;
434
+ const isSpace = event.key === SPACE || event.keyCode === SPACE_KEY_CODE;
435
+ if ((isSpace || isEnter) && !this.isInvalid()) {
436
+ this.action.emit();
437
+ }
438
+ };
439
+ this.handleWheel = () => {
440
+ // This empty event handler is here to circumvent a bug.
441
+ // In some browsers (Chrome for example), hovering the input with
442
+ // the input focused, and scrolling, will both change the value
443
+ // AND scroll the page. We would prefer to never change the value
444
+ // on scroll, instead always scrolling the page, but since we
445
+ // haven't found a way to do that, this is the next best thing, as
446
+ // it prevents the page from being scrolled, but only in the
447
+ // circumstances when the value is changed by the scrolling.
448
+ // Please test THOROUGHLY if you remove this event handler 😄
449
+ };
126
450
  const debounceTimeout = 300;
127
451
  this.changeEmitter = debounce(this.changeEmitter, debounceTimeout);
128
452
  this.portalId = createRandomString();
@@ -133,18 +457,6 @@ export class InputField {
133
457
  componentDidLoad() {
134
458
  this.initialize();
135
459
  }
136
- initialize() {
137
- const element = this.limelInputField.shadowRoot.querySelector('.mdc-text-field');
138
- if (!element) {
139
- return;
140
- }
141
- this.mdcTextField = new MDCTextField(element);
142
- this.completionsList = [...this.completions].map((item) => {
143
- return { text: item };
144
- });
145
- window.addEventListener('resize', this.layout, { passive: true });
146
- this.limelInputField.addEventListener('focus', this.setFocus);
147
- }
148
460
  disconnectedCallback() {
149
461
  if (this.mdcTextField) {
150
462
  this.mdcTextField.destroy();
@@ -184,18 +496,15 @@ export class InputField {
184
496
  h("span", { class: labelClassList, id: labelId }, this.label)),
185
497
  h("span", { class: "mdc-notched-outline__trailing" })),
186
498
  this.renderLeadingIcon(),
187
- this.renderFormattedNumber(),
188
499
  this.renderEmptyValueForReadonly(),
189
500
  this.renderInput(properties),
190
501
  this.renderTextarea(properties),
502
+ this.renderFormattedNumber(),
191
503
  this.renderTrailingLinkOrButton()),
192
504
  this.renderHelperLine(),
193
505
  this.renderAutocompleteList(),
194
506
  ];
195
507
  }
196
- setFocus() {
197
- this.mdcTextField.focus();
198
- }
199
508
  valueWatcher(newValue) {
200
509
  if (!this.mdcTextField) {
201
510
  return;
@@ -204,328 +513,6 @@ export class InputField {
204
513
  this.mdcTextField.value = newValue || '';
205
514
  }
206
515
  }
207
- getContainerClassList() {
208
- const classList = {
209
- 'mdc-text-field': true,
210
- 'mdc-text-field--outlined': true,
211
- 'mdc-text-field--invalid': this.isInvalid(),
212
- 'mdc-text-field--disabled': this.disabled || this.readonly,
213
- 'lime-text-field--readonly': this.readonly,
214
- 'mdc-text-field--required': this.required,
215
- 'mdc-text-field--with-trailing-icon': !!this.getTrailingIcon(),
216
- };
217
- if (this.type === 'textarea') {
218
- classList['mdc-text-field--textarea'] = true;
219
- classList['has-helper-line'] =
220
- !!this.helperText || !!this.maxlength;
221
- }
222
- else {
223
- classList['mdc-text-field--with-leading-icon'] = !!this.leadingIcon;
224
- }
225
- return classList;
226
- }
227
- renderInput(properties) {
228
- if (this.type === 'textarea') {
229
- return;
230
- }
231
- const type = this.type === 'urlAsText' ? 'text' : this.type;
232
- return (h("input", Object.assign({}, properties, { type: type, pattern: this.pattern, onWheel: this.handleWheel, onKeyDown: this.onKeyDown, value: this.value })));
233
- }
234
- renderTextarea(properties) {
235
- if (this.type !== 'textarea') {
236
- return;
237
- }
238
- return (h("span", { class: "mdc-text-field__resizer" },
239
- h("textarea", Object.assign({}, properties), this.value)));
240
- }
241
- layout() {
242
- var _a;
243
- (_a = this.mdcTextField) === null || _a === void 0 ? void 0 : _a.layout();
244
- }
245
- getAdditionalProps() {
246
- const props = {};
247
- if (this.type === 'number') {
248
- props.step = this.step;
249
- }
250
- if (this.type === 'number' && Number.isInteger(this.min)) {
251
- props.min = this.min;
252
- }
253
- if (this.type === 'number' && Number.isInteger(this.max)) {
254
- props.max = this.max;
255
- }
256
- if (this.minlength) {
257
- props.minlength = this.minlength;
258
- }
259
- if (this.maxlength) {
260
- props.maxlength = this.maxlength;
261
- }
262
- return props;
263
- }
264
- onFocus() {
265
- this.isFocused = true;
266
- this.showCompletions = true;
267
- }
268
- onBlur() {
269
- this.isFocused = false;
270
- this.isModified = true;
271
- }
272
- renderHelperLine() {
273
- if (!this.maxlength && !this.hasHelperText()) {
274
- return;
275
- }
276
- return (h("div", { tabIndex: -1, class: "mdc-text-field-helper-line" },
277
- this.renderHelperText(),
278
- this.renderCharacterCounter()));
279
- }
280
- renderEmptyValueForReadonly() {
281
- if (this.readonly && !this.value) {
282
- return (h("span", { class: "lime-empty-value-for-readonly lime-looks-like-input-value" }, "\u2013"));
283
- }
284
- }
285
- renderHelperText() {
286
- if (!this.hasHelperText()) {
287
- return;
288
- }
289
- const classList = {
290
- 'mdc-text-field-helper-text': true,
291
- 'mdc-text-field-helper-text--validation-msg': this.isInvalid(),
292
- };
293
- return (h("p", { class: classList, id: helperTextId }, this.helperText));
294
- }
295
- hasHelperText() {
296
- return this.helperText !== null && this.helperText !== undefined;
297
- }
298
- renderCharacterCounter() {
299
- if (!this.maxlength || this.type === 'number') {
300
- return;
301
- }
302
- const text = this.value || '';
303
- const label = `${text.length} / ${this.maxlength}`;
304
- return h("div", { class: "mdc-text-field-character-counter" }, label);
305
- }
306
- isInvalid() {
307
- if (this.readonly) {
308
- // A readonly field can never be invalid.
309
- return false;
310
- }
311
- if (this.invalid) {
312
- // `this.invalid` is set by the consumer. If the consumer explicitly
313
- // told us to consider the field invalid, we consider it invalid
314
- // regardless of what our internal validation thinks, and regardless
315
- // of whether the field has been modified.
316
- return true;
317
- }
318
- if (!this.isModified) {
319
- return false;
320
- }
321
- const element = this.getInputElement();
322
- return !(element && element.checkValidity());
323
- }
324
- getInputElement() {
325
- let elementName = 'input';
326
- if (this.type === 'textarea') {
327
- elementName = 'textarea';
328
- }
329
- return this.limelInputField.shadowRoot.querySelector(elementName);
330
- }
331
- hasLink() {
332
- return (this.showLink &&
333
- ['email', 'tel', 'url', 'urlAsText'].includes(this.type));
334
- }
335
- getLink() {
336
- const props = { href: '' };
337
- switch (this.type) {
338
- case 'email':
339
- props.href = `mailto:${this.value}`;
340
- break;
341
- case 'tel':
342
- props.href = `tel:${this.value}`;
343
- break;
344
- default:
345
- props.href = getHref(this.value);
346
- props.target = getTarget(this.value);
347
- }
348
- return props;
349
- }
350
- renderLinkIcon(linkProps, icon) {
351
- // If the trailing icon uses the class `mdc-text-field__icon--trailing`,
352
- // MDC attaches a click handler to it, which apparently runs
353
- // `preventDefault()` on the event. For links, we don't want that,
354
- // so instead of `mdc-text-field__icon--trailing`, we use our own class
355
- // `lime-trailing-icon-for-link`, which uses all the same styling. /Ads
356
- return (h("a", Object.assign({}, linkProps, { class: "material-icons mdc-text-field__icon lime-trailing-icon-for-link", tabindex: this.disabled ? '-1' : '0', role: "button" }),
357
- h("limel-icon", { name: icon })));
358
- }
359
- renderTrailingIcon(icon) {
360
- const props = {
361
- tabIndex: this.isInvalid() ? '-1' : '0',
362
- };
363
- if (!this.isInvalid()) {
364
- props.onKeyPress = this.handleIconKeyPress;
365
- props.onClick = this.handleIconClick;
366
- props.role = 'button';
367
- }
368
- return (h("i", Object.assign({ class: "material-icons mdc-text-field__icon mdc-text-field__icon--trailing" }, props),
369
- h("limel-icon", { name: icon })));
370
- }
371
- getTrailingIcon() {
372
- if (this.isInvalid()) {
373
- return 'high_importance';
374
- }
375
- if (this.trailingIcon) {
376
- return this.trailingIcon;
377
- }
378
- if (this.showLink && this.type === 'email') {
379
- return 'filled_message';
380
- }
381
- if (this.showLink && this.type === 'tel') {
382
- return 'phone';
383
- }
384
- if (this.showLink &&
385
- (this.type === 'url' || this.type === 'urlAsText')) {
386
- return 'external_link';
387
- }
388
- }
389
- renderFormattedNumber() {
390
- if (this.type !== 'number' || !this.value) {
391
- return;
392
- }
393
- let renderValue = this.value;
394
- if (this.formatNumber) {
395
- renderValue = new Intl.NumberFormat(navigator.language).format(Number(this.value));
396
- }
397
- return (h("span", { class: "lime-formatted-input lime-looks-like-input-value" }, renderValue));
398
- }
399
- /**
400
- * Key handler for the input field
401
- * Will change focus to the first/last item in the dropdown list to enable selection with the keyboard
402
- *
403
- * @param {KeyboardEvent} event event
404
- * @returns {void}
405
- */
406
- onKeyDown(event) {
407
- this.showCompletions = true;
408
- const isForwardTab = (event.key === TAB || event.keyCode === TAB_KEY_CODE) &&
409
- !event.altKey &&
410
- !event.metaKey &&
411
- !event.shiftKey;
412
- const isUp = event.key === ARROW_UP || event.keyCode === ARROW_UP_KEY_CODE;
413
- const isDown = event.key === ARROW_DOWN || event.keyCode === ARROW_DOWN_KEY_CODE;
414
- if (event.keyCode === TAB_KEY_CODE && event.shiftKey) {
415
- this.showCompletions = false;
416
- }
417
- if (!isForwardTab && !isUp && !isDown) {
418
- return;
419
- }
420
- const list = document.querySelector(` #${this.portalId} limel-list`);
421
- if (!list) {
422
- return;
423
- }
424
- event.preventDefault();
425
- if (isForwardTab || isDown) {
426
- const listElement = list.shadowRoot.querySelector('.mdc-deprecated-list-item:first-child');
427
- listElement.focus();
428
- return;
429
- }
430
- if (isUp) {
431
- const listElement = list.shadowRoot.querySelector('.mdc-deprecated-list-item:last-child');
432
- listElement.focus();
433
- }
434
- }
435
- handleCompletionChange(event) {
436
- event.stopPropagation();
437
- if (!event.detail) {
438
- return;
439
- }
440
- this.showCompletions = false;
441
- /*
442
- This change event doesn't need to be debounced in itself, but we want
443
- to make absolutely sure that an earlier change event that *has* been
444
- debounced doesn't emit after this one. Therefore, we run this through
445
- the same debounced emitter function. /Ads
446
- */
447
- this.changeEmitter(event.detail.text);
448
- }
449
- renderAutocompleteList() {
450
- if (this.type === 'textarea' || !this.completions.length) {
451
- return;
452
- }
453
- return this.renderDropdown();
454
- }
455
- renderPortal(content = null) {
456
- const dropdownZIndex = getComputedStyle(this.limelInputField).getPropertyValue('--dropdown-z-index');
457
- return (h("limel-portal", { visible: this.showCompletions, containerId: this.portalId, inheritParentWidth: true, containerStyle: { 'z-index': dropdownZIndex } },
458
- h("limel-menu-surface", { open: this.showCompletions, allowClicksElement: this.limelInputField, style: {
459
- '--menu-surface-width': '100%',
460
- 'max-height': 'inherit',
461
- display: 'flex',
462
- }, onDismiss: this.handleCloseMenu }, content)));
463
- }
464
- renderDropdown() {
465
- const content = this.renderListResult();
466
- return this.renderPortal(content);
467
- }
468
- renderListResult() {
469
- const filteredCompletions = this.filterCompletions(this.value);
470
- if (!filteredCompletions || filteredCompletions.length === 0) {
471
- return null;
472
- }
473
- return (h("limel-list", { onChange: this.handleCompletionChange, onKeyDown: this.handleKeyDownInDropdown, type: "selectable", items: filteredCompletions }));
474
- }
475
- handleKeyDownInDropdown(event) {
476
- const keyFound = [TAB, ESCAPE, ENTER].includes(event.key);
477
- const keyCodeFound = [
478
- TAB_KEY_CODE,
479
- ESCAPE_KEY_CODE,
480
- ENTER_KEY_CODE,
481
- ].includes(event.keyCode);
482
- if (keyFound || keyCodeFound) {
483
- this.setFocus();
484
- }
485
- }
486
- handleCloseMenu() {
487
- this.showCompletions = false;
488
- }
489
- handleChange(event) {
490
- event.stopPropagation();
491
- let value = event.target.value;
492
- if (this.type === 'number') {
493
- if (!value && event.data) {
494
- event.stopPropagation();
495
- return;
496
- }
497
- if (value) {
498
- value = Number(value);
499
- }
500
- }
501
- this.changeEmitter(value);
502
- }
503
- changeEmitter(value) {
504
- this.change.emit(value);
505
- }
506
- handleIconClick() {
507
- if (!this.isInvalid()) {
508
- this.action.emit();
509
- }
510
- }
511
- handleIconKeyPress(event) {
512
- const isEnter = event.key === ENTER || event.keyCode === ENTER_KEY_CODE;
513
- const isSpace = event.key === SPACE || event.keyCode === SPACE_KEY_CODE;
514
- if ((isSpace || isEnter) && !this.isInvalid()) {
515
- this.action.emit();
516
- }
517
- }
518
- handleWheel() {
519
- // This empty event handler is here to circumvent a bug.
520
- // In some browsers (Chrome for example), hovering the input with
521
- // the input focused, and scrolling, will both change the value
522
- // AND scroll the page. We would prefer to never change the value
523
- // on scroll, instead always scrolling the page, but since we
524
- // haven't found a way to do that, this is the next best thing, as
525
- // it prevents the page from being scrolled, but only in the
526
- // circumstances when the value is changed by the scrolling.
527
- // Please test THOROUGHLY if you remove this event handler 😄
528
- }
529
516
  static get is() { return "limel-input-field"; }
530
517
  static get encapsulation() { return "shadow"; }
531
518
  static get originalStyleUrls() { return {