Skip to content

Commit

Permalink
feat(ui5-tokenizer): enable multiline mode (#9964)
Browse files Browse the repository at this point in the history
  • Loading branch information
MapTo0 authored Nov 11, 2024
1 parent 23289d4 commit 1071746
Show file tree
Hide file tree
Showing 11 changed files with 500 additions and 45 deletions.
114 changes: 114 additions & 0 deletions packages/main/cypress/specs/Tokenizer.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { html } from "lit";
import "../../src/Tokenizer.js";
import type Tokenizer from "../../src/Tokenizer.js";

describe("Tokenizer - multi-line and Clear All", () => {
it("'Clear All' link is rendered for multi-line tokenizer and show-clear-all set to true", () => {
cy.mount(html`<ui5-tokenizer show-clear-all multi-line>
<ui5-token text="Andora"></ui5-token>
<ui5-token text="Bulgaria"></ui5-token>
<ui5-token text="Canada"></ui5-token>
<ui5-token text="Denmark"></ui5-token>
<ui5-token text="Estonia"></ui5-token>
<ui5-token text="Finland"></ui5-token>
<ui5-token text="Germany"></ui5-token>
</ui5-tokenizer>`);

cy.get<Tokenizer>("[ui5-tokenizer]")
.shadow()
.find(".ui5-tokenizer--clear-all")
.should("exist");
});

it("'Clear All' link is rendered even for 1 token when in multi-line mode", () => {
cy.mount(html`<ui5-tokenizer show-clear-all multi-line>
<ui5-token text="Andora"></ui5-token>
</ui5-tokenizer>`);

cy.get<Tokenizer>("[ui5-tokenizer]")
.shadow()
.find(".ui5-tokenizer--clear-all")
.should("exist");
});

it("'Clear All' link is not rendered for single-line tokenizer even when show-clear-all is set to true", () => {
cy.mount(html`<ui5-tokenizer show-clear-all>
<ui5-token text="Andora"></ui5-token>
</ui5-tokenizer>`);

cy.get<Tokenizer>("[ui5-tokenizer]")
.shadow()
.find(".ui5-tokenizer--clear-all")
.should("not.exist");
});

it("'Clear All' link is not rendered for multi-line tokenizer when show-clear-all is set to false", () => {
cy.mount(html`<ui5-tokenizer multi-line>
<ui5-token text="Andora"></ui5-token>
<ui5-token text="Bulgaria"></ui5-token>
<ui5-token text="Canada"></ui5-token>
<ui5-token text="Denmark"></ui5-token>
<ui5-token text="Estonia"></ui5-token>
<ui5-token text="Finland"></ui5-token>
<ui5-token text="Germany"></ui5-token>
</ui5-tokenizer>`);

cy.get<Tokenizer>("[ui5-tokenizer]")
.shadow()
.find(".ui5-tokenizer--clear-all")
.should("not.exist");
});

it("'Clear All' link is not rendered for multi-line readonly tokenizer when show-clear-all 'true'", () => {
cy.mount(html`<ui5-tokenizer multi-line show-clear-all readonly>
<ui5-token text="Andora"></ui5-token>
<ui5-token text="Bulgaria"></ui5-token>
<ui5-token text="Canada"></ui5-token>
<ui5-token text="Denmark"></ui5-token>
<ui5-token text="Estonia"></ui5-token>
<ui5-token text="Finland"></ui5-token>
<ui5-token text="Germany"></ui5-token>
</ui5-tokenizer>`);

cy.get<Tokenizer>("[ui5-tokenizer]")
.shadow()
.find(".ui5-tokenizer--clear-all")
.should("not.exist");
});

it("'n-more' link is not rendered for multi-line tokenizer", () => {
cy.mount(html`<ui5-tokenizer multi-line style="width: 100px;">
<ui5-token text="Andora"></ui5-token>
<ui5-token text="Bulgaria"></ui5-token>
<ui5-token text="Canada"></ui5-token>
<ui5-token text="Denmark"></ui5-token>
<ui5-token text="Estonia"></ui5-token>
<ui5-token text="Finland"></ui5-token>
<ui5-token text="Germany"></ui5-token>
</ui5-tokenizer>`);

cy.get<Tokenizer>("[ui5-tokenizer]")
.shadow()
.find(".ui5-tokenizer--more-text")
.should("not.exist");
});

it("Pressing 'Clear All' link fires token-delete event", () => {
cy.mount(html`<ui5-tokenizer show-clear-all multi-line>
<ui5-token text="Andora"></ui5-token>
<ui5-token text="Bulgaria"></ui5-token>
<ui5-token text="Canada"></ui5-token>
</ui5-tokenizer>`);

cy.get<Tokenizer>("[ui5-tokenizer]").then($tokenizer => $tokenizer.get(0).addEventListener("token-delete", cy.stub().as("delete")));

cy.get<Tokenizer>("[ui5-tokenizer]")
.shadow()
.find(".ui5-tokenizer--clear-all")
.eq(0)
.click();

cy.get("@delete")
.should("have.been.calledOnce");
});
});
45 changes: 28 additions & 17 deletions packages/main/src/Tokenizer.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,37 @@
@focusout="{{_onfocusout}}"
@focusin="{{_onfocusin}}"
@ui5-select="{{onTokenSelect}}"
role="listbox"
aria-label="{{tokenizerLabel}}"
aria-description="{{tokenizerAriaDescription}}"
aria-disabled="{{_ariaDisabled}}"
aria-readonly="{{_ariaReadonly}}"
>
{{#each tokens}}
<slot name="{{this._individualSlot}}"></slot>
{{/each}}
<div class="ui5-tokenizer--list"
role="listbox"
aria-label="{{tokenizerLabel}}"
aria-description="{{tokenizerAriaDescription}}"
aria-disabled="{{_ariaDisabled}}"
aria-readonly="{{_ariaReadonly}}"
>
{{#each tokens}}
<slot name="{{this._individualSlot}}"></slot>
{{/each}}
</div>

{{#if showEffectiveClearAll}}
<span
role="button"
@click={{handleClearAll}}
class="ui5-tokenizer--clear-all">
{{_clearAllText}}</span>
{{/if}}
</div>

{{#if showNMore}}
<span
role="button"
aria-haspopup="dialog"
@click="{{_handleNMoreClick}}"
class="ui5-tokenizer-more-text"
part="n-more-text"
>{{_nMoreText}}</span>
{{/if}}
{{#if showNMore}}
<span
role="button"
aria-haspopup="dialog"
@click="{{_handleNMoreClick}}"
class="ui5-tokenizer-more-text"
part="n-more-text"
>{{_nMoreText}}</span>
{{/if}}
</div>

{{>include "./TokenizerPopover.hbs"}}
91 changes: 77 additions & 14 deletions packages/main/src/Tokenizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
import event from "@ui5/webcomponents-base/dist/decorators/event.js";
import getEffectiveScrollbarStyle from "@ui5/webcomponents-base/dist/util/getEffectiveScrollbarStyle.js";
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
Expand Down Expand Up @@ -66,6 +67,7 @@ import {
TOKENIZER_ARIA_CONTAIN_ONE_TOKEN,
TOKENIZER_ARIA_CONTAIN_SEVERAL_TOKENS,
TOKENIZER_SHOW_ALL_ITEMS,
TOKENIZER_CLEAR_ALL,
} from "./generated/i18n/i18n-defaults.js";

// Styles
Expand Down Expand Up @@ -144,6 +146,7 @@ enum ClipboardDataOperation {
ResponsivePopoverCommonCss,
SuggestionsCss,
TokenizerPopoverCss,
getEffectiveScrollbarStyle(),
],
dependencies: [
ResponsivePopover,
Expand Down Expand Up @@ -211,6 +214,26 @@ class Tokenizer extends UI5Element {
@property({ type: Boolean })
readonly = false;

/**
* Defines whether tokens are displayed on multiple lines.
*
* **Note:** The `multiLine` property is in an experimental state and is a subject to change.
* @default false
* @public
*/
@property({ type: Boolean })
multiLine = false;

/**
* Defines whether "Clear All" button is present. Ensure `multiLine` is enabled, otherwise `showClearAll` will have no effect.
*
* **Note:** The `showClearAll` property is in an experimental state and is a subject to change.
* @default false
* @public
*/
@property({ type: Boolean })
showClearAll = false;

/**
* Defines whether the component is disabled.
*
Expand Down Expand Up @@ -324,7 +347,7 @@ class Tokenizer extends UI5Element {
static i18nBundle: I18nBundle;
_resizeHandler: ResizeObserverCallback;
_itemNav: ItemNavigation;
_scrollEnablement: ScrollEnablement;
_scrollEnablement: ScrollEnablement | undefined;
_expandedScrollWidth?: number;
_tokenDeleting = false;
_preventCollapse = false;
Expand All @@ -347,16 +370,23 @@ class Tokenizer extends UI5Element {
getItemsCallback: this._getVisibleTokens.bind(this),
});

this._scrollEnablement = new ScrollEnablement(this);
this._deletedDialogItems = [];
}

handleClearAll() {
this.fireDecoratorEvent<TokenizerTokenDeleteEventDetail>("token-delete", { tokens: this._tokens });
}

onBeforeRendering() {
if (!this.multiLine) {
this._scrollEnablement = new ScrollEnablement(this);
}

const tokensLength = this._tokens.length;
this._tokensCount = tokensLength;

this._tokens.forEach(token => {
token.singleToken = tokensLength === 1;
token.singleToken = (tokensLength === 1) || this.multiLine;
token.readonly = this.readonly;
});
}
Expand Down Expand Up @@ -406,13 +436,19 @@ class Tokenizer extends UI5Element {
}
}

onTokenSelect() {
onTokenSelect(e: CustomEvent) {
const tokens = this._tokens;
const firstToken = tokens[0];
const targetToken = e.target as Token;

if (tokens.length === 1 && firstToken.isTruncatable) {
this.open = firstToken.selected;
}

if (this.multiLine && targetToken.isTruncatable) {
this.opener = targetToken;
this.open = targetToken.selected;
}
}

_getVisibleTokens() {
Expand All @@ -435,7 +471,9 @@ class Tokenizer extends UI5Element {
firstToken.forcedTabIndex = "0";
}

this._scrollEnablement.scrollContainer = this.contentDom;
if (this._scrollEnablement) {
this._scrollEnablement.scrollContainer = this.contentDom;
}

if (this.expanded) {
this._expandedScrollWidth = this.contentDom.scrollWidth;
Expand Down Expand Up @@ -575,9 +613,16 @@ class Tokenizer extends UI5Element {
}

handleBeforeOpen() {
this._tokens.forEach(token => {
token._isVisible = true;
});
if (this.multiLine) {
this._resetTokensVisibility();

const focusedToken = this._tokens.find(token => token.focused);
focusedToken!._isVisible = true;
} else {
this._tokens.forEach(token => {
token._isVisible = true;
});
}

const list = this._getList();
const firstListItem = list.querySelectorAll("[ui5-li]")[0]! as ListItem;
Expand Down Expand Up @@ -938,6 +983,20 @@ class Tokenizer extends UI5Element {
}
}

_resetTokensVisibility() {
this._tokens.forEach(token => {
token._isVisible = false;
});
}

get hasTokens() {
return this._tokens.length > 0;
}

get showEffectiveClearAll() {
return this.showClearAll && this.hasTokens && this.multiLine && !this.readonly;
}

_fillClipboard(shortcutName: ClipboardDataOperation, tokens: Array<IToken>) {
const tokensTexts = tokens.filter(token => token.selected).map(token => token.text).join("\r\n");

Expand All @@ -960,8 +1019,8 @@ class Tokenizer extends UI5Element {
* @protected
*/
scrollToStart() {
if (this._scrollEnablement.scrollContainer) {
this._scrollEnablement.scrollTo(0, 0);
if (this._scrollEnablement?.scrollContainer) {
this._scrollEnablement?.scrollTo(0, 0);
}
}

Expand All @@ -972,8 +1031,8 @@ class Tokenizer extends UI5Element {
*/
scrollToEnd() {
const expandedTokenizerScrollWidth = this.contentDom && (this.effectiveDir !== "rtl" ? this.contentDom.scrollWidth : -this.contentDom.scrollWidth);
if (this._scrollEnablement.scrollContainer) {
this._scrollEnablement.scrollTo(expandedTokenizerScrollWidth, 0, 5, 10);
if (this._scrollEnablement?.scrollContainer) {
this._scrollEnablement?.scrollTo(expandedTokenizerScrollWidth, 0, 5, 10);
}
}

Expand All @@ -991,9 +1050,9 @@ class Tokenizer extends UI5Element {
const tokenContainerRect = this.contentDom.getBoundingClientRect();

if (tokenRect.left < tokenContainerRect.left) {
this._scrollEnablement.scrollTo(this.contentDom.scrollLeft - (tokenContainerRect.left - tokenRect.left + 5), 0);
this._scrollEnablement?.scrollTo(this.contentDom.scrollLeft - (tokenContainerRect.left - tokenRect.left + 5), 0);
} else if (tokenRect.right > tokenContainerRect.right) {
this._scrollEnablement.scrollTo(this.contentDom.scrollLeft + (tokenRect.right - tokenContainerRect.right + 5), 0);
this._scrollEnablement?.scrollTo(this.contentDom.scrollLeft + (tokenRect.right - tokenContainerRect.right + 5), 0);
}
}

Expand Down Expand Up @@ -1025,6 +1084,10 @@ class Tokenizer extends UI5Element {
return Tokenizer.i18nBundle.getText(TOKENIZER_SHOW_ALL_ITEMS, this._nMoreCount);
}

get _clearAllText() {
return Tokenizer.i18nBundle.getText(TOKENIZER_CLEAR_ALL);
}

get showNMore() {
return !this.expanded && !!this.overflownTokens.length;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/main/src/i18n/messagebundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,9 @@ TOKENIZER_POPOVER_REMOVE=All items
#XFLD: Token number indicator which is used to show all tokens in Tokenizer when all of the tokens are hidden
TOKENIZER_SHOW_ALL_ITEMS={0} Items

#XFLD: Clear All link text in Tokenizer
TOKENIZER_CLEAR_ALL=Clear All

#XACT: Label text for TreeListItem
TREE_ITEM_ARIA_LABEL=Tree Item

Expand Down
Loading

0 comments on commit 1071746

Please sign in to comment.