From eaba59deb5f26c75f51243d8aa895ad6eb5c1484 Mon Sep 17 00:00:00 2001 From: Nia Peeva Date: Mon, 23 Mar 2026 00:00:16 +0200 Subject: [PATCH 1/2] fix(ui5-tokenizer, ui5-multi-input,ui5-multi-combobox): Improve stylisation --- packages/ai/src/InputTemplate.tsx | 2 +- packages/main/src/InputTemplate.tsx | 2 +- packages/main/src/MultiComboBoxTemplate.tsx | 1 + packages/main/src/MultiInput.ts | 3 ++ packages/main/src/Tokenizer.ts | 37 +++++++++++++++++++ packages/main/src/themes/ComboBox.css | 4 ++ packages/main/src/themes/MultiComboBox.css | 6 ++- packages/main/src/themes/MultiInput.css | 6 ++- packages/main/src/themes/Token.css | 4 ++ packages/main/src/themes/Tokenizer.css | 2 +- .../main/src/themes/base/sizes-parameters.css | 13 +++++-- 11 files changed, 69 insertions(+), 11 deletions(-) diff --git a/packages/ai/src/InputTemplate.tsx b/packages/ai/src/InputTemplate.tsx index 62e156849e57..cb40a8d66676 100644 --- a/packages/ai/src/InputTemplate.tsx +++ b/packages/ai/src/InputTemplate.tsx @@ -42,7 +42,7 @@ export default function InputTemplate(this: Input, hooks?: { preContent: Templat style={this.styles.innerInput} type={this.inputNativeType} inner-input - inner-input-with-icon={!!this.icon.length} + inner-input-with-icon={this.iconsCount > 0} disabled={this.disabled} readonly={this._readonly || this.loading} value={this.value} diff --git a/packages/main/src/InputTemplate.tsx b/packages/main/src/InputTemplate.tsx index 39d10ed5e413..4a6ce63f14fc 100644 --- a/packages/main/src/InputTemplate.tsx +++ b/packages/main/src/InputTemplate.tsx @@ -30,7 +30,7 @@ export default function InputTemplate(this: Input, hooks?: { preContent: Templat style={this.styles.innerInput} type={this.inputNativeType} inner-input - inner-input-with-icon={!!this.icon.length} + inner-input-with-icon={this.iconsCount > 0} disabled={this.disabled} readonly={this._readonly} value={this.value} diff --git a/packages/main/src/MultiComboBoxTemplate.tsx b/packages/main/src/MultiComboBoxTemplate.tsx index 553e87ed3baa..1394870f1ed2 100644 --- a/packages/main/src/MultiComboBoxTemplate.tsx +++ b/packages/main/src/MultiComboBoxTemplate.tsx @@ -65,6 +65,7 @@ export default function MultiComboBoxTemplate(this: MultiComboBox) { id="ui5-multi-combobox-input" value={this.value} inner-input + inner-input-with-icon={this._effectiveShowClearIcon || !!this.icon || !this.readonly} placeholder={this._getPlaceholder} disabled={this.disabled} readonly={this.readonly} diff --git a/packages/main/src/MultiInput.ts b/packages/main/src/MultiInput.ts index 169fde3a86d0..d5a99d692714 100644 --- a/packages/main/src/MultiInput.ts +++ b/packages/main/src/MultiInput.ts @@ -359,6 +359,9 @@ class MultiInput extends Input implements IFormInputElement { if (this.tokenizer) { this.tokenizer.readonly = this.readonly; + + // Set the CSS variable on the tokenizer element so it's available in the shadow DOM + this.tokenizer.style.setProperty("--_ui5-input-icons-count", `${this.iconsCount}`); } } diff --git a/packages/main/src/Tokenizer.ts b/packages/main/src/Tokenizer.ts index 2a3aa200cd83..934d6b6009b6 100644 --- a/packages/main/src/Tokenizer.ts +++ b/packages/main/src/Tokenizer.ts @@ -367,6 +367,7 @@ class Tokenizer extends UI5Element implements IFormInputElement { _deletedDialogItems!: Token[]; _lastFocusedToken: Token | null = null; _isFocusSetInternally: boolean = false; + _lastMarkedToken?: Token; /** * Scroll to end when tokenizer is expanded * @private @@ -423,6 +424,12 @@ class Tokenizer extends UI5Element implements IFormInputElement { token.singleToken = (tokensLength === 1) || this.multiLine; token.readonly = this.readonly; }); + + // If expanding, immediately remove last-visible-token attribute to ensure proper spacing + if (this.expanded && this._lastMarkedToken) { + this._lastMarkedToken.removeAttribute("last-visible-token"); + this._lastMarkedToken = undefined; + } } onEnterDOM() { @@ -513,6 +520,36 @@ class Tokenizer extends UI5Element implements IFormInputElement { this._scrollToEndIfNeeded(); this._tokenDeleting = false; + this._updateLastVisibleTokenAttribute(); + } + + /** + * Updates the last-visible-token attribute on tokens. + * When collapsed with overflow, marks the last visible token for proper spacing to the n-more indicator. + * @private + */ + _updateLastVisibleTokenAttribute() { + const tokensArray = this._tokens; + const hasOverflow = this._nMoreCount > 0; + const visibleTokens = tokensArray.filter(token => !token.overflows); + const lastVisibleToken = visibleTokens.length > 0 ? visibleTokens[visibleTokens.length - 1] : undefined; + + // Only set attribute when NOT expanded AND overflow exists + const newMarkedToken = (!this.expanded && hasOverflow && lastVisibleToken) ? lastVisibleToken : undefined; + const previousToken = this._lastMarkedToken; + + // Only update DOM if state changed (prevents render loop) + if (previousToken !== newMarkedToken) { + if (previousToken) { + previousToken.removeAttribute("last-visible-token"); + } + + if (newMarkedToken) { + newMarkedToken.setAttribute("last-visible-token", ""); + } + + this._lastMarkedToken = newMarkedToken; + } } /** diff --git a/packages/main/src/themes/ComboBox.css b/packages/main/src/themes/ComboBox.css index d6c056a722aa..146004544bf9 100644 --- a/packages/main/src/themes/ComboBox.css +++ b/packages/main/src/themes/ComboBox.css @@ -8,4 +8,8 @@ width: 100%; height: 100%; border-radius: var(--_ui5_input_border_radius); +} + +:host(:not([readonly])) [inner-input] { + padding: var(--_ui5_input_inner_padding_with_icon); } \ No newline at end of file diff --git a/packages/main/src/themes/MultiComboBox.css b/packages/main/src/themes/MultiComboBox.css index b2f749bb6581..6ec29aa80300 100644 --- a/packages/main/src/themes/MultiComboBox.css +++ b/packages/main/src/themes/MultiComboBox.css @@ -25,7 +25,7 @@ .ui5-multi-combobox-tokenizer { min-width: var(--_ui5_input_tokenizer_min_width); - max-width: calc(100% - 3rem - var(--_ui5-input-icons-count) * var(--_ui5_input_icon_min_width)); + max-width: calc(100% - var(--_ui5_input_min_width_tokenizer_available) - var(--_ui5-input-icons-count) * var(--_ui5_input_icon_min_width)); border: none; width: auto; height: 100%; @@ -48,7 +48,9 @@ } :host([tokenizer-available]) [inner-input] { - padding-inline-start: var(--_ui5_input_inner_space_to_tokenizer); + padding-inline-start: 0; + padding-inline-end: var(--_ui5_input_inner_with_tokenizer_padding_inline_end); + min-width: var(--_ui5_input_min_width_tokenizer_available); } :host(:not([tokenizer-available])) .ui5-multi-combobox-tokenizer { diff --git a/packages/main/src/themes/MultiInput.css b/packages/main/src/themes/MultiInput.css index 19c1c39963a8..f7ed9a1405db 100644 --- a/packages/main/src/themes/MultiInput.css +++ b/packages/main/src/themes/MultiInput.css @@ -10,7 +10,7 @@ .ui5-multi-input-tokenizer { min-width: var(--_ui5_input_tokenizer_min_width); - max-width: calc(100% - 3rem - var(--_ui5-input-icons-count) * var(--_ui5_input_icon_min_width)); + max-width: calc(100% - var(--_ui5_input_min_width_tokenizer_available) - var(--_ui5-input-icons-count) * var(--_ui5_input_icon_min_width)); border: none; width: auto; height: 100%; @@ -29,7 +29,9 @@ } :host([tokenizer-available]) [inner-input] { - padding-inline-start: var(--_ui5_input_inner_space_to_tokenizer); + padding-inline-start: 0; + padding-inline-end: var(--_ui5_input_inner_with_tokenizer_padding_inline_end); + min-width: var(--_ui5_input_min_width_tokenizer_available); } :host(:not([tokenizer-available])) .ui5-multi-input-tokenizer { diff --git a/packages/main/src/themes/Token.css b/packages/main/src/themes/Token.css index b53e7372d7b3..67cc8a12b3a9 100644 --- a/packages/main/src/themes/Token.css +++ b/packages/main/src/themes/Token.css @@ -12,6 +12,10 @@ margin-inline-end: var(--_ui5_token_right_margin); } +:host([last-visible-token]) { + margin-inline-end: 0; +} + :host([overflows]) { display: none; } diff --git a/packages/main/src/themes/Tokenizer.css b/packages/main/src/themes/Tokenizer.css index c170ca5ceb3a..4f5131f9d31f 100644 --- a/packages/main/src/themes/Tokenizer.css +++ b/packages/main/src/themes/Tokenizer.css @@ -87,7 +87,7 @@ .ui5-tokenizer-more-text { display: inline-block; - margin-inline-start: .25rem; + margin-inline-start: var(--_ui5_token_right_margin); cursor: pointer; white-space: nowrap; font-size: var(--sapFontSize); diff --git a/packages/main/src/themes/base/sizes-parameters.css b/packages/main/src/themes/base/sizes-parameters.css index f3655350e03e..bf8e975b6781 100644 --- a/packages/main/src/themes/base/sizes-parameters.css +++ b/packages/main/src/themes/base/sizes-parameters.css @@ -74,7 +74,8 @@ --_ui5_input_inner_padding: 0 0.625rem; --_ui5_input_inner_padding_with_icon: 0 0.25rem 0 0.625rem; --_ui5_input_inner_space_to_tokenizer: 0.125rem; - --_ui5_input_inner_space_to_n_more_text: 0.1875rem; + --_ui5_input_inner_space_to_n_more_text: 0.3125rem; + --_ui5_input_inner_with_tokenizer_padding_inline_end: 0.3125rem; --_ui5_list_no_data_height: 3rem; --_ui5_list_item_cb_margin_right: 0; --_ui5_list_item_title_size: var(--sapFontLargeSize); @@ -103,7 +104,7 @@ --_ui5_popup_default_header_height: 2.75rem; --_ui5_year_picker_item_height: 3rem; - --_ui5_tokenizer_padding: 0.25rem; + --_ui5_tokenizer_padding: 0.3125rem; --_ui5_token_height: 1.625rem; --_ui5_token_icon_size: .75rem; --_ui5_token_icon_padding: 0.25rem 0.5rem; @@ -267,9 +268,10 @@ /* Input */ --_ui5_input_height: var(--sapElement_Compact_Height); --_ui5_input_inner_padding: 0 0.5rem; - --_ui5_input_inner_padding_with_icon: 0 0.2rem 0 0.5rem; + --_ui5_input_inner_padding_with_icon: 0 0.25rem 0 0.5rem; --_ui5_input_inner_space_to_tokenizer: 0.125rem; - --_ui5_input_inner_space_to_n_more_text: 0.125rem; + --_ui5_input_inner_space_to_n_more_text: 0.25rem; + --_ui5_input_inner_with_tokenizer_padding_inline_end: 0.25rem; --_ui5_input_icon_min_width: var(--_ui5_input_compact_min_width); --_ui5_input_min_width_tokenizer_available: 2rem; @@ -351,6 +353,9 @@ --_ui5-tree-toggle-box-height: 1.5rem; --_ui5-tree-toggle-icon-size: 0.8125rem; + /* Tokenizer */ + --_ui5_tokenizer_padding: 0.25rem; + /* Toolbar */ --_ui5-toolbar-separator-height: 1.5rem; --_ui5-toolbar-height: 2rem; From 3767aa407489f18490fd1eef07af565178b316af Mon Sep 17 00:00:00 2001 From: Nia Peeva Date: Tue, 24 Mar 2026 14:49:32 +0200 Subject: [PATCH 2/2] fix(ui5-tokenizer, ui5-multi-input,ui5-multi-combobox): reflect code review --- packages/main/src/Token.ts | 8 ++++++++ packages/main/src/Tokenizer.ts | 31 +++++++++---------------------- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/packages/main/src/Token.ts b/packages/main/src/Token.ts index fca069913e6d..e4b0c8faf2f8 100644 --- a/packages/main/src/Token.ts +++ b/packages/main/src/Token.ts @@ -127,6 +127,14 @@ class Token extends UI5Element implements IToken { @property({ type: Boolean }) toBeDeleted = false; + /** + * Set by the tokenizer to mark the last visible token before overflow. + * @default false + * @private + */ + @property({ type: Boolean }) + lastVisibleToken = false; + /** * Defines the tabIndex of the component. * @private diff --git a/packages/main/src/Tokenizer.ts b/packages/main/src/Tokenizer.ts index 934d6b6009b6..d291075c3a0b 100644 --- a/packages/main/src/Tokenizer.ts +++ b/packages/main/src/Tokenizer.ts @@ -367,7 +367,6 @@ class Tokenizer extends UI5Element implements IFormInputElement { _deletedDialogItems!: Token[]; _lastFocusedToken: Token | null = null; _isFocusSetInternally: boolean = false; - _lastMarkedToken?: Token; /** * Scroll to end when tokenizer is expanded * @private @@ -425,10 +424,11 @@ class Tokenizer extends UI5Element implements IFormInputElement { token.readonly = this.readonly; }); - // If expanding, immediately remove last-visible-token attribute to ensure proper spacing - if (this.expanded && this._lastMarkedToken) { - this._lastMarkedToken.removeAttribute("last-visible-token"); - this._lastMarkedToken = undefined; + // Clear lastVisibleToken when expanding to ensure proper spacing + if (this.expanded) { + this._tokens.forEach(token => { + token.lastVisibleToken = false; + }); } } @@ -524,7 +524,7 @@ class Tokenizer extends UI5Element implements IFormInputElement { } /** - * Updates the last-visible-token attribute on tokens. + * Updates the lastVisibleToken property on tokens. * When collapsed with overflow, marks the last visible token for proper spacing to the n-more indicator. * @private */ @@ -534,22 +534,9 @@ class Tokenizer extends UI5Element implements IFormInputElement { const visibleTokens = tokensArray.filter(token => !token.overflows); const lastVisibleToken = visibleTokens.length > 0 ? visibleTokens[visibleTokens.length - 1] : undefined; - // Only set attribute when NOT expanded AND overflow exists - const newMarkedToken = (!this.expanded && hasOverflow && lastVisibleToken) ? lastVisibleToken : undefined; - const previousToken = this._lastMarkedToken; - - // Only update DOM if state changed (prevents render loop) - if (previousToken !== newMarkedToken) { - if (previousToken) { - previousToken.removeAttribute("last-visible-token"); - } - - if (newMarkedToken) { - newMarkedToken.setAttribute("last-visible-token", ""); - } - - this._lastMarkedToken = newMarkedToken; - } + tokensArray.forEach(token => { + token.lastVisibleToken = (!this.expanded && hasOverflow && token === lastVisibleToken); + }); } /**