From 3d5dfa78c257f186c468d8c02384d9d385daf0c0 Mon Sep 17 00:00:00 2001 From: Jeroen Zwartepoorte Date: Wed, 18 Sep 2024 15:10:16 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/combobox/src/combobox.ts | 81 ++++++++++++------- .../combobox/src/custom-option.scss | 30 +++++++ .../components/combobox/src/custom-option.ts | 8 +- packages/components/listbox/src/option.scss | 19 +++-- packages/components/listbox/src/option.ts | 8 +- packages/locales/src/nl.ts | 4 + packages/locales/src/nl.xlf | 16 ++++ 7 files changed, 125 insertions(+), 41 deletions(-) diff --git a/packages/components/combobox/src/combobox.ts b/packages/components/combobox/src/combobox.ts index 9ca9bb08a..341ef574d 100644 --- a/packages/components/combobox/src/combobox.ts +++ b/packages/components/combobox/src/combobox.ts @@ -72,9 +72,6 @@ export class Combobox extends FormControlMixin(ScopedElementsMixin( /** @internal The default margin between the popover and the viewport. */ static viewportMargin = 8; - /** The custom option element used to display the `customOption` value. */ - #customOption?: CustomOption; - /** Event controller. */ #events = new EventsController(this); @@ -125,7 +122,7 @@ export class Combobox extends FormControlMixin(ScopedElementsMixin( /** @internal The current selected options. */ @state() currentSelection: ComboboxOption[] = []; - /** @internal The custom option (used in combination with `allowCustomValues`). */ + /** @internal The custom option (used when `allowCustomValues` is set). */ @state() customOption?: ComboboxOption; /** Whether the text field is disabled; when set no interaction is possible. */ @@ -259,20 +256,6 @@ export class Combobox extends FormControlMixin(ScopedElementsMixin( } } - if (changes.has('customOption')) { - if (this.customOption) { - if (!this.#customOption) { - this.#customOption ||= this.shadowRoot!.createElement('sl-combobox-custom-option') as CustomOption; - this.#customOption.addEventListener('click', this.#onOptionsClick); - } - - this.#customOption.value = this.customOption?.value; - } else { - this.#customOption?.remove(); - this.#customOption = undefined; - } - } - if (changes.has('disabled')) { this.input.disabled = !!this.disabled; } @@ -380,7 +363,9 @@ export class Combobox extends FormControlMixin(ScopedElementsMixin( this.wrapper?.showPopover(); - let currentOption: ComboboxOption | undefined = undefined; + let currentOption: ComboboxOption | undefined = undefined, + customOption: ComboboxOption | undefined = undefined; + if ( event.inputType !== 'deleteContentBackward' && (this.autocomplete === 'inline' || this.autocomplete === 'both') @@ -390,19 +375,36 @@ export class Combobox extends FormControlMixin(ScopedElementsMixin( if (currentOption) { this.input.value = currentOption.content; this.input.setSelectionRange(value.length, currentOption.content.length); + } else if (this.allowCustomValues) { + customOption = currentOption = { + id: `sl-combobox-custom-option-${nextUniqueId++}`, + content: value, + current: false, + selected: false, + value + }; } - } else if (this.allowCustomValues) { - currentOption = { - id: `sl-combobox-custom-option-${nextUniqueId++}`, - content: value, - current: false, - selected: false, - value - }; } else { currentOption = this.options.find(option => value === option.value); + + if (this.allowCustomValues) { + if (value.length === 0) { + customOption = currentOption = undefined; + } else if (!currentOption) { + customOption = currentOption = { + id: `sl-combobox-custom-option-${nextUniqueId++}`, + content: value, + current: false, + selected: false, + value + }; + } else if (currentOption.custom) { + customOption = currentOption = { ...currentOption, value }; + } + } } + this.#updateCustomOption(customOption); this.#updateCurrent(currentOption); this.#updateFilteredOptions(value); @@ -477,7 +479,9 @@ export class Combobox extends FormControlMixin(ScopedElementsMixin( #onOptionsClick(event: Event): void { const optionElement = event.composedPath().find((el): el is Option => el instanceof Option); - if (optionElement?.id) { + if (optionElement instanceof CustomOption) { + console.log('Add custom option', optionElement.value); + } else if (optionElement?.id) { const option = this.options.find(o => o.id === optionElement.id); this.#toggleSelected(option); @@ -589,6 +593,7 @@ export class Combobox extends FormControlMixin(ScopedElementsMixin( this.currentOption.current = true; this.currentOption.element?.setAttribute('aria-current', 'true'); + // Scroll to the selected group or the current option if (this.groupSelected && this.currentSelection.includes(this.currentOption)) { this.#selectedGroup!.scrollIntoView({ block: 'nearest' }); } else { @@ -599,6 +604,25 @@ export class Combobox extends FormControlMixin(ScopedElementsMixin( } } + #updateCustomOption(customOption?: ComboboxOption): void { + if (this.customOption && !customOption) { + this.customOption.element?.remove(); + } else if (this.customOption && customOption) { + customOption.element = this.customOption.element; + } + + this.customOption = customOption; + + if (this.customOption) { + if (!this.customOption?.element) { + this.customOption.element ||= this.shadowRoot!.createElement('sl-combobox-custom-option') as CustomOption; + this.listbox?.prepend(this.customOption.element); + } + + this.customOption.element.value = this.customOption?.value; + } + } + #updateFilteredOptions(value?: string): void { this.options.forEach(option => { let match = !this.filterResults || !value; @@ -632,6 +656,7 @@ export class Combobox extends FormControlMixin(ScopedElementsMixin( element: el, content: el.textContent?.trim() || '', current: el.getAttribute('aria-current') === 'true', + custom: el instanceof CustomOption, group: el.closest('sl-option-group')?.getAttribute('label') || undefined, selected: el.getAttribute('aria-selected') === 'true', // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment diff --git a/packages/components/combobox/src/custom-option.scss b/packages/components/combobox/src/custom-option.scss index 7595af12f..6e2b20b94 100644 --- a/packages/components/combobox/src/custom-option.scss +++ b/packages/components/combobox/src/custom-option.scss @@ -1,3 +1,33 @@ +:host { + border-block-end: var(--sl-color-elevation-border-raised) solid var(--sl-size-borderWidth-default); + margin-block-end: var(--sl-space-new-sm); + padding-block-end: var(--sl-space-new-md); + user-select: none; +} + +:host([aria-current]) .container { + background: var(--sl-color-action-background-accent-subtle-hover); +} + +:host(:active) .container { + background: var(--sl-color-action-background-accent-subtle-active); +} + +.container { + align-items: center; + border-radius: var(--sl-size-border-radius-default); + color: var(--sl-color-text-default); + cursor: pointer; + display: flex; + gap: var(--sl-space-new-md); + margin-inline: var(--sl-space-new-md); + padding: var(--sl-space-new-sm) var(--sl-space-new-md); + + &:hover { + background: var(--sl-color-action-background-accent-subtle-hover); + } +} + sl-icon { visibility: visible; } diff --git a/packages/components/combobox/src/custom-option.ts b/packages/components/combobox/src/custom-option.ts index a6ca6ca2a..a5da400f0 100644 --- a/packages/components/combobox/src/custom-option.ts +++ b/packages/components/combobox/src/custom-option.ts @@ -1,4 +1,5 @@ import { faPlus } from '@fortawesome/pro-regular-svg-icons'; +import { localized, msg, str } from '@lit/localize'; import { Icon } from '@sl-design-system/icon'; import { Option } from '@sl-design-system/listbox'; import { type CSSResultGroup, type TemplateResult, html } from 'lit'; @@ -18,14 +19,17 @@ Icon.register(faPlus); * * @slot default - The option's label. */ +@localized() export class CustomOption extends Option { /** @internal */ static override styles: CSSResultGroup = [Option.styles, styles]; override render(): TemplateResult { return html` - -
Add option: ${this.value}
+
+ +
${msg(str`Create "${this.value}"`)}
+
`; } } diff --git a/packages/components/listbox/src/option.scss b/packages/components/listbox/src/option.scss index 57c3f8b75..01d7b4152 100644 --- a/packages/components/listbox/src/option.scss +++ b/packages/components/listbox/src/option.scss @@ -1,15 +1,9 @@ :host { - align-items: center; - border-radius: var(--sl-size-border-radius-default); color: var(--sl-color-text-default); - cursor: pointer; - display: flex; - gap: var(--sl-space-new-md); - padding: var(--sl-space-new-sm) var(--sl-space-new-md); user-select: none; } -:host(:where(:hover, [aria-current])) { +:host(:where(:hover, [aria-current])) [part='container'] { background: var(--sl-color-action-background-accent-subtle-hover); } @@ -17,7 +11,7 @@ visibility: visible; } -:host(:active) { +:host(:active) [part='container'] { background: var(--sl-color-action-background-accent-subtle-active); } @@ -27,6 +21,15 @@ pointer-events: none; } +[part='container'] { + align-items: center; + border-radius: var(--sl-size-border-radius-default); + cursor: pointer; + display: flex; + gap: var(--sl-space-new-md); + padding: var(--sl-space-new-sm) var(--sl-space-new-md); +} + sl-icon { visibility: hidden; } diff --git a/packages/components/listbox/src/option.ts b/packages/components/listbox/src/option.ts index 5a61ed43c..44273d362 100644 --- a/packages/components/listbox/src/option.ts +++ b/packages/components/listbox/src/option.ts @@ -43,9 +43,11 @@ export class Option extends ScopedElementsMixin(LitElement) { override render(): TemplateResult { return html` - -
- +
+ +
+ +
`; } diff --git a/packages/locales/src/nl.ts b/packages/locales/src/nl.ts index 3eaa7c196..9aa3c8d52 100644 --- a/packages/locales/src/nl.ts +++ b/packages/locales/src/nl.ts @@ -4,6 +4,7 @@ import { str } from '@lit/localize'; export const templates = { + s00a84204340a1db4: 'Geselecteerd', s091d3d07b5b3076f: 'Ok', s129fac0ae1df7c8f: 'waarschuwing', s13fdff160ffad275: 'Wis tekst', @@ -16,6 +17,7 @@ export const templates = { s5d929ff1619ac0c9: 'Zoeken', s5e10d00d437e2e21: 'Leeg', s5e8250fb85d64c23: 'Sluiten', + s602a5722e101c0d2: 'Alle opties', s606beeef09212e6c: str`${0} de opties`, s629576c6b305d539: 'optioneel', s62aa2ba3fe47ece4: 'Vink tenminste één optie aan.', @@ -23,6 +25,7 @@ export const templates = { s6abb1cd87fe0114e: 'Home', s8079b71872346425: 'Voer een waarde in.', s864c948d4629240f: 'Toon alles', + s8c1f47dc13ead0c3: str`"${0}" aanmaken`, s9cd91b99a108a45f: 'Lijst met verborgen elementen', s9f7452dd75d54d31: 'fout', sa447e2f801a7eb2d: 'Kies een optie.', @@ -37,6 +40,7 @@ export const templates = { sd244b51f6d7de8e2: 'Voer een waarde in volgens het gevraagde patroon.', sd711c88b851db6c9: 'De volgende velden zijn incorrect:', sde6884478b7cc818: str`Voer tenminste ${0} karakters in (je hebt op dit momoment ${1} karakter${2}).`, + se412fe8953013d92: 'Selecteer tenminste één optie.', sebd54694b2f83547: str`verwijder '${0}'`, see63aaad45b1b116: 'status', sf1ec4acb8d744ed9: 'Mededeling', diff --git a/packages/locales/src/nl.xlf b/packages/locales/src/nl.xlf index a512d3aa0..01cb42771 100644 --- a/packages/locales/src/nl.xlf +++ b/packages/locales/src/nl.xlf @@ -154,6 +154,22 @@ the options de opties + + Create "" + "" aanmaken + + + Selected + Geselecteerd + + + All options + Alle opties + + + Please select at least one option. + Selecteer tenminste één optie. +