diff --git a/src/Umbraco.Web.UI.Client/src/packages/tags/components/tags-input/tags-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tags/components/tags-input/tags-input.element.ts index 9efb1b6962..5a84f467c6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tags/components/tags-input/tags-input.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tags/components/tags-input/tags-input.element.ts @@ -1,19 +1,20 @@ import { UmbTagRepository } from '../../repository/tag.repository.js'; import { css, + customElement, html, nothing, - customElement, property, query, queryAll, - state, repeat, + state, } from '@umbraco-cms/backoffice/external/lit'; -import type { UUIInputElement, UUIInputEvent, UUITagElement } from '@umbraco-cms/backoffice/external/uui'; -import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import type { TagResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; +import type { UUIInputElement, UUIInputEvent, UUITagElement } from '@umbraco-cms/backoffice/external/uui'; @customElement('umb-tags-input') export class UmbTagsInputElement extends UUIFormControlMixin(UmbLitElement, '') { @@ -61,6 +62,9 @@ export class UmbTagsInputElement extends UUIFormControlMixin(UmbLitElement, '') @queryAll('.options') private _optionCollection?: HTMLCollectionOf; + @queryAll('.tag') + private _tagEls?: NodeListOf; + #repository = new UmbTagRepository(this); public override focus() { @@ -78,18 +82,29 @@ export class UmbTagsInputElement extends UUIFormControlMixin(UmbLitElement, '') this._matches = data.items; } - #onKeydown(e: KeyboardEvent) { - //Prevent tab away if there is a input. - if (e.key === 'Tab' && (this._tagInput.value as string).trim().length && !this._matches.length) { + #onInputKeydown(e: KeyboardEvent) { + const inputLength = (this._tagInput.value as string).trim().length; + + //Prevent tab away if there is a text in the input. + if (e.key === 'Tab' && inputLength && !this._matches.length) { e.preventDefault(); this.#createTag(); return; } + + //If the input is empty we can navigate out of it using tab + if (e.key === 'Tab' && !inputLength) { + return; + } + + //Create a new tag when enter to the input if (e.key === 'Enter') { this.#createTag(); return; } - if (e.key === 'ArrowDown' || e.key === 'Tab') { + + //This one to show option collection if there is any + if (e.key === 'ArrowDown') { e.preventDefault(); this._currentInput = this._optionCollection?.item(0)?.value ?? this._currentInput; this._optionCollection?.item(0)?.focus(); @@ -98,6 +113,54 @@ export class UmbTagsInputElement extends UUIFormControlMixin(UmbLitElement, '') this.#inputError(false); } + #focusTag(index: number) { + const tag = this._tagEls?.[index]; + if (!tag) return; + + // Find the current element with the class .tab and tabindex=0 (will be the previous tag) + const active = this.renderRoot.querySelector('.tag[tabindex="0"]'); + + // Return it is tabindex to -1 + active?.setAttribute('tabindex', '-1'); + + // Set the tabindex to 0 in the current target + tag.setAttribute('tabindex', '0'); + + tag.focus(); + } + + #onTagsWrapperKeydown(e: KeyboardEvent) { + if ((e.key === 'Enter' || e.key === 'ArrowDown') && this.items.length) { + e.preventDefault(); + this.#focusTag(0); + } + } + + #onTagKeydown(e: KeyboardEvent, idx: number) { + if (e.key === 'ArrowRight') { + e.preventDefault(); + if (idx < this.items.length - 1) { + this.#focusTag(idx + 1); + } + } + + if (e.key === 'ArrowLeft') { + e.preventDefault(); + if (idx > 0) { + this.#focusTag(idx - 1); + } + } + + if (e.key === 'Backspace' || e.key === 'Delete') { + e.preventDefault(); + if (this.#items.length - 1 === idx) { + this.#focusTag(idx - 1); + } + this.#delete(this.#items[idx]); + this.#focusTag(idx + 1); + } + } + #onInput(e: UUIInputEvent) { this._currentInput = e.target.value as string; if (!this._currentInput || !this._currentInput.length) { @@ -128,7 +191,7 @@ export class UmbTagsInputElement extends UUIFormControlMixin(UmbLitElement, '') this.items = [...this.items, newTag]; this._tagInput.value = ''; this._currentInput = ''; - this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true })); + this.dispatchEvent(new UmbChangeEvent()); } #inputError(error: boolean) { @@ -150,7 +213,7 @@ export class UmbTagsInputElement extends UUIFormControlMixin(UmbLitElement, '') } else { this.items = []; } - this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true })); + this.dispatchEvent(new UmbChangeEvent()); } /** Dropdown */ @@ -196,7 +259,7 @@ export class UmbTagsInputElement extends UUIFormControlMixin(UmbLitElement, '') override render() { return html`
- ${this.#enteredTags()} + ${this.#renderTags()}