Skip to content

Commit

Permalink
💙
Browse files Browse the repository at this point in the history
  • Loading branch information
jpzwarte committed Sep 18, 2024
1 parent a138a4b commit 3d5dfa7
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 41 deletions.
81 changes: 53 additions & 28 deletions packages/components/combobox/src/combobox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,6 @@ export class Combobox<T = unknown> 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);

Expand Down Expand Up @@ -125,7 +122,7 @@ export class Combobox<T = unknown> 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. */
Expand Down Expand Up @@ -259,20 +256,6 @@ export class Combobox<T = unknown> 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;
}
Expand Down Expand Up @@ -380,7 +363,9 @@ export class Combobox<T = unknown> 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')
Expand All @@ -390,19 +375,36 @@ export class Combobox<T = unknown> 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);

Expand Down Expand Up @@ -477,7 +479,9 @@ export class Combobox<T = unknown> 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);
Expand Down Expand Up @@ -589,6 +593,7 @@ export class Combobox<T = unknown> 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 {
Expand All @@ -599,6 +604,25 @@ export class Combobox<T = unknown> 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;
Expand Down Expand Up @@ -632,6 +656,7 @@ export class Combobox<T = unknown> 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
Expand Down
30 changes: 30 additions & 0 deletions packages/components/combobox/src/custom-option.scss
Original file line number Diff line number Diff line change
@@ -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;
}
8 changes: 6 additions & 2 deletions packages/components/combobox/src/custom-option.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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`
<sl-icon name="far-plus"></sl-icon>
<div part="wrapper">Add option: <span>${this.value}</span></div>
<div class="container">
<sl-icon name="far-plus"></sl-icon>
<div class="wrapper">${msg(str`Create "${this.value}"`)}</div>
</div>
`;
}
}
19 changes: 11 additions & 8 deletions packages/components/listbox/src/option.scss
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
: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);
}

:host(:where([aria-selected='true'], [selected])) sl-icon {
visibility: visible;
}

:host(:active) {
:host(:active) [part='container'] {
background: var(--sl-color-action-background-accent-subtle-active);
}

Expand All @@ -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;
}
8 changes: 5 additions & 3 deletions packages/components/listbox/src/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ export class Option extends ScopedElementsMixin(LitElement) {

override render(): TemplateResult {
return html`
<sl-icon name="check"></sl-icon>
<div part="wrapper">
<slot></slot>
<div part="container">
<sl-icon name="check"></sl-icon>
<div part="wrapper">
<slot></slot>
</div>
</div>
`;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/locales/src/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { str } from '@lit/localize';

export const templates = {
s00a84204340a1db4: 'Geselecteerd',
s091d3d07b5b3076f: 'Ok',
s129fac0ae1df7c8f: 'waarschuwing',
s13fdff160ffad275: 'Wis tekst',
Expand All @@ -16,13 +17,15 @@ 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.',
s6580790b036f0c6f: 'actief',
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.',
Expand All @@ -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',
Expand Down
16 changes: 16 additions & 0 deletions packages/locales/src/nl.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,22 @@
<source><x id="0" equiv-text="${this.listbox?.matches(':popover-open') ? 'Hide' : 'Show'}"/> the options</source>
<target><x id="0" equiv-text="${this.listbox?.matches(':popover-open') ? 'Verberg' : 'Toon'}"/> de opties</target>
</trans-unit>
<trans-unit id="s8c1f47dc13ead0c3">
<source>Create "<x id="0" equiv-text="${this.value}"/>"</source>
<target>"<x id="0" equiv-text="${this.value}"/>" aanmaken</target>
</trans-unit>
<trans-unit id="s00a84204340a1db4">
<source>Selected</source>
<target>Geselecteerd</target>
</trans-unit>
<trans-unit id="s602a5722e101c0d2">
<source>All options</source>
<target>Alle opties</target>
</trans-unit>
<trans-unit id="se412fe8953013d92">
<source>Please select at least one option.</source>
<target>Selecteer tenminste één optie.</target>
</trans-unit>
</body>
</file>
</xliff>

0 comments on commit 3d5dfa7

Please sign in to comment.