Skip to content

Commit 4e0f081

Browse files
committed
refactor: simplify slotted text-field rendering and styles
1 parent 4c2ef8b commit 4e0f081

File tree

1 file changed

+90
-134
lines changed

1 file changed

+90
-134
lines changed

src/component/vcf-month-picker.ts

Lines changed: 90 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
2020
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
2121
import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';
22+
import { SlotStylesMixin } from '@vaadin/component-base/src/slot-styles-mixin.js';
2223
import { Overlay, OverlayCloseEvent } from '@vaadin/overlay/vaadin-overlay';
2324
import '@vaadin/text-field';
2425
import { TextField } from '@vaadin/text-field/vaadin-text-field';
@@ -54,8 +55,8 @@ const REF_CENTURY_DEFAULT = toRefCentury(new Date().getFullYear());
5455
*
5556
* @element vcf-month-picker
5657
*/
57-
export class VcfMonthPicker extends ElementMixin(
58-
ThemableMixin(PolylitMixin(LitElement))
58+
export class VcfMonthPicker extends SlotStylesMixin(
59+
ElementMixin(ThemableMixin(PolylitMixin(LitElement)))
5960
) {
6061
static get is() {
6162
return 'vcf-month-picker';
@@ -236,26 +237,55 @@ export class VcfMonthPicker extends ElementMixin(
236237
`;
237238
}
238239

239-
update(props: PropertyValues) {
240-
const observer = new MutationObserver(() => {
241-
this._updateSuffixStyles();
242-
});
243-
observer.observe(this.shadowRoot!, { childList: true, subtree: true });
240+
// @ts-expect-error overriding property from `SlotStylesMixinClass`
241+
override get slotStyles(): string[] {
242+
const tag = this.localName;
243+
244+
/**
245+
* These rules target a <vaadin-text-field> element with a child element
246+
* having the 'toggle-button' part. It is used to style the calendar toggle
247+
* button inside the month picker text field.
248+
*
249+
* The rules are scoped through the component selector and only applies to
250+
* the toggle button in the month picker input field.
251+
*/
252+
return [
253+
`
254+
${tag} [part="toggle-button"] {
255+
flex: none;
256+
width: 1em;
257+
height: 1em;
258+
line-height: 1;
259+
font-size: var(--vcf-month-picker-icon-size);
260+
text-align: center;
261+
color: var(--lumo-contrast-60pct);
262+
transition: 0.2s color;
263+
cursor: var(--lumo-clickable-cursor);
264+
order: 2;
265+
}
266+
267+
${tag} [part="toggle-button"]::before {
268+
display: block;
269+
font-family: var(--vcf-month-picker-icons-font-family);
270+
content: var(--vcf-month-picker-toggle-calendar-icon);
271+
}
272+
273+
${tag} [part="toggle-button"]:hover {
274+
color: var(--lumo-body-text-color);
275+
}
276+
277+
${tag}[readonly] [part="toggle-button"] {
278+
color: var(--lumo-contrast-20pct);
279+
cursor: default;
280+
}
281+
`,
282+
];
283+
}
244284

285+
update(props: PropertyValues) {
245286
super.update(props);
246287

247-
if (this.textField) {
248-
this.textField.setAttribute('value', this.inputValue ?? '');
249-
this.textField.setAttribute('label', this.label);
250-
this.textField.setAttribute('placeholder', this.placeholder);
251-
this.textField.disabled = this.disabled;
252-
this.textField.readonly = this.readonly;
253-
this.textField.invalid = this.invalid;
254-
this.textField.required = this.required;
255-
this.textField.clearButtonVisible = this.clearButtonVisible;
256-
this.textField.setAttribute('error-message', this.errorMessage);
257-
this.textField.setAttribute('helper-text', this.helperText);
258-
}
288+
this.__renderSlottedField();
259289

260290
this.overlay = this.overlay || this.shadowRoot!.querySelector('#overlay');
261291

@@ -270,112 +300,54 @@ export class VcfMonthPicker extends ElementMixin(
270300
}
271301

272302
protected firstUpdated() {
273-
this._createTextField();
303+
this.textField = this.querySelector('vaadin-text-field') as TextField;
304+
(this.textField as any)._onKeyDown = this._onKeyDown.bind(this);
305+
274306
this._tooltipController = new TooltipController(this, 'tooltip');
275307
this._tooltipController.setPosition('top');
276308
this.addController(this._tooltipController);
277309
if (this.value) {
278310
this.__boundInputValueChanged();
279311
}
312+
}
280313

281-
// Inject a <style> element into the light DOM to style the toggle button inside the month picker text field
282-
const style = document.createElement('style');
283-
style.textContent = `
284-
/*
285-
* These rules target a <vaadin-text-field> element with a child element
286-
* having the 'toggle-button' part. It is used to style the calendar toggle
287-
* button inside the month picker text field.
288-
*
289-
* The rules are scoped through the component selector and only applies to
290-
* the toggle button in the month picker input field.
291-
*/
292-
vaadin-text-field > [part="toggle-button"] {
293-
flex: none;
294-
width: 1em;
295-
height: 1em;
296-
line-height: 1;
297-
font-size: var(--vcf-month-picker-icon-size);
298-
text-align: center;
299-
color: var(--lumo-contrast-60pct);
300-
transition: 0.2s color;
301-
cursor: var(--lumo-clickable-cursor);
302-
}
303-
304-
vaadin-text-field > [part="toggle-button"]::before {
305-
display: block;
306-
font-family: var(--vcf-month-picker-icons-font-family);
307-
content: var(--vcf-month-picker-toggle-calendar-icon);
308-
}
309-
310-
vaadin-text-field > [part="toggle-button"]:hover {
311-
color: var(--lumo-body-text-color);
312-
}
313-
314-
vaadin-text-field[readonly] > [part="toggle-button"] {
315-
color: var(--lumo-contrast-20pct);
316-
cursor: default;
317-
}
318-
`;
319-
this.appendChild(style);
320-
}
321-
322-
// Creates the text field element in the slot="text-field-slot"
323-
_createTextField() {
324-
if (!this.textField) {
325-
// Create toggle button
326-
const suffixDiv = document.createElement('div');
327-
suffixDiv.setAttribute('part', 'toggle-button');
328-
suffixDiv.setAttribute('slot', 'suffix');
329-
suffixDiv.setAttribute('aria-hidden', 'true');
330-
suffixDiv.addEventListener('click', e => {
331-
this.__toggle(e);
332-
});
333-
334-
// Create text field
335-
const txtfield = document.createElement('vaadin-text-field');
336-
txtfield.setAttribute('slot', 'text-field-slot');
337-
txtfield.setAttribute('id', 'textField');
338-
txtfield.setAttribute('value', this.inputValue ?? '');
339-
txtfield.setAttribute('label', this.label);
340-
txtfield.setAttribute('placeholder', this.placeholder);
341-
txtfield.disabled = this.disabled;
342-
txtfield.readonly = this.readonly;
343-
txtfield.invalid = this.invalid;
344-
txtfield.required = this.required;
345-
txtfield.clearButtonVisible = this.clearButtonVisible;
346-
txtfield.setAttribute('error-message', this.errorMessage);
347-
txtfield.setAttribute('helper-text', this.helperText);
348-
txtfield.setAttribute('autocomplete', 'off');
349-
350-
// Add event listeners to the text field
351-
txtfield.addEventListener('click', e => {
352-
this.__boundInputClicked(e);
353-
});
354-
txtfield.addEventListener('change', () => {
355-
this.__boundInputValueChanged();
356-
});
357-
txtfield.addEventListener('blur', () => {
358-
this._onBlur();
359-
});
360-
txtfield.addEventListener('focus', () => {
361-
this._onFocus();
362-
});
363-
364-
// Accessibility attributes
365-
txtfield.setAttribute('role', 'combobox');
366-
txtfield.setAttribute('aria-haspopup', 'dialog');
367-
txtfield.setAttribute('aria-expanded', this.opened ? 'true' : 'false');
368-
(txtfield as any)._onKeyDown = this._onKeyDown.bind(this);
369-
370-
// Add toggle button to suffix slot
371-
txtfield.appendChild(suffixDiv);
372-
373-
// Store a reference to the created vaadin-text-field
374-
this.textField = txtfield as TextField;
375-
376-
// Append text field to slot
377-
this.appendChild(txtfield);
378-
}
314+
private __renderSlottedField() {
315+
render(
316+
html`
317+
<vaadin-text-field
318+
slot="text-field-slot"
319+
.label="${this.label}"
320+
.value="${this.inputValue ?? ''}"
321+
.placeholder="${this.placeholder}"
322+
.disabled="${this.disabled}"
323+
.invalid="${this.invalid}"
324+
.required="${this.required}"
325+
.clearButtonVisible="${this.clearButtonVisible}"
326+
.helperText="${this.helperText}"
327+
.errorMessage="${this.errorMessage}"
328+
@click="${this.__boundInputClicked}"
329+
@change="${this.__boundInputValueChanged}"
330+
@blur="${this._onBlur}"
331+
@focus="${this._onFocus}"
332+
>
333+
<input
334+
slot="input"
335+
role="combobox"
336+
aria-haspopup="dialog"
337+
aria-controls="overlay"
338+
aria-expanded="${this.opened ? 'true' : 'false'}"
339+
/>
340+
<div
341+
part="toggle-button"
342+
slot="suffix"
343+
aria-hidden="true"
344+
@click="${this.__toggle}"
345+
></div>
346+
</vaadin-text-field>
347+
`,
348+
this,
349+
{ host: this }
350+
);
379351
}
380352

381353
render() {
@@ -402,18 +374,6 @@ export class VcfMonthPicker extends ElementMixin(
402374
`;
403375
}
404376

405-
// This method is necessary to ensure the toggle button appears
406-
// before the clear button
407-
_updateSuffixStyles() {
408-
const suffixElement = this.textField?.shadowRoot
409-
?.querySelector('vaadin-input-container')
410-
?.shadowRoot?.querySelector('[name="suffix"]') as HTMLElement;
411-
if (suffixElement) {
412-
suffixElement.style.display = 'flex';
413-
suffixElement.style.flexDirection = 'row-reverse';
414-
}
415-
}
416-
417377
_onVaadinOverlayClose(e: OverlayCloseEvent) {
418378
if (
419379
e.detail.sourceEvent &&
@@ -730,10 +690,6 @@ export class VcfMonthPicker extends ElementMixin(
730690
private __overlayOpenedChanged(e: CustomEvent) {
731691
const opened = e.detail.value;
732692
this.opened = opened;
733-
this.textField?.setAttribute(
734-
'aria-expanded',
735-
this.opened ? 'true' : 'false'
736-
);
737693
if (opened) {
738694
this.textField?.focus();
739695
}

0 commit comments

Comments
 (0)