diff --git a/.changeset/good-mayflies-accept.md b/.changeset/good-mayflies-accept.md new file mode 100644 index 0000000000..ac27699fb3 --- /dev/null +++ b/.changeset/good-mayflies-accept.md @@ -0,0 +1,5 @@ +--- +'@siemens/ix': patch +--- + +Hide clear button in ix-select for disabled and readonly states. diff --git a/packages/core/src/components/select/select.tsx b/packages/core/src/components/select/select.tsx index fd61095df9..13983ee2ce 100644 --- a/packages/core/src/components/select/select.tsx +++ b/packages/core/src/components/select/select.tsx @@ -209,7 +209,7 @@ export class Select implements IxInputFieldComponent { @Event() ixBlur!: EventEmitter; @State() dropdownShow = false; - @State() selectedLabels: string[] = []; + @State() selectedLabels: (string | undefined)[] = []; @State() isDropdownEmpty = false; @State() navigationItem?: DropdownItemWrapper; @State() inputFilterText = ''; @@ -302,7 +302,7 @@ export class Select implements IxInputFieldComponent { @Watch('dropdownShow') watchDropdownShow(show: boolean) { - if (show) { + if (show && this.dropdownRef) { this.arrowFocusController = new ArrowFocusController( this.visibleNonShadowItems, this.dropdownRef, @@ -356,7 +356,7 @@ export class Select implements IxInputFieldComponent { } private focusDropdownItem(index: number) { - this.navigationItem = null; + this.navigationItem = undefined; if (index < this.visibleNonShadowItems.length) { const nestedDropdownItem = @@ -402,7 +402,7 @@ export class Select implements IxInputFieldComponent { newItem.value = value; newItem.label = value; - this.customItemsContainerRef.appendChild(newItem); + this.customItemsContainerRef?.appendChild(newItem); this.clearInput(); this.itemClick(value); @@ -452,7 +452,7 @@ export class Select implements IxInputFieldComponent { this.selectedLabels = this.selectedItems.map((item) => item.label); if (this.selectedLabels?.length && this.isSingleMode) { - this.inputValue = this.selectedLabels[0]; + this.inputValue = this.selectedLabels[0] ?? ''; } else { this.inputValue = ''; } @@ -468,7 +468,7 @@ export class Select implements IxInputFieldComponent { } if (!value) { - this.itemSelectionChange.emit(null); + this.itemSelectionChange.emit([]); } else { this.itemSelectionChange.emit(Array.isArray(value) ? value : [value]); } @@ -505,7 +505,7 @@ export class Select implements IxInputFieldComponent { this.cleanupResources(); } - private itemExists(item: string) { + private itemExists(item: string | undefined) { return this.items.find((i) => i.label === item); } @@ -519,7 +519,7 @@ export class Select implements IxInputFieldComponent { this.removeHiddenFromItems(); this.isDropdownEmpty = this.isEveryDropdownItemHidden; } else { - this.navigationItem = null; + this.navigationItem = undefined; this.updateSelection(); this.inputFilterText = ''; } @@ -551,15 +551,17 @@ export class Select implements IxInputFieldComponent { return; } + const trimmedInput = this.inputFilterText.trim(); + const itemLabel = (el as HTMLIxSelectItemElement)?.label; + if ( - !this.itemExists(this.inputFilterText.trim()) && - !this.itemExists((el as HTMLIxSelectItemElement)?.label) + this.editable && + !this.itemExists(trimmedInput) && + !this.itemExists(itemLabel) ) { - if (this.editable) { - const defaultPrevented = this.emitAddItem(this.inputFilterText.trim()); - if (defaultPrevented) { - return; - } + const defaultPrevented = this.emitAddItem(trimmedInput); + if (defaultPrevented) { + return; } } @@ -622,16 +624,14 @@ export class Select implements IxInputFieldComponent { if ( this.isAddItemVisible() && - this.addItemRef.contains( + this.addItemRef?.contains( await this.navigationItem.getDropdownItemElement() ) ) { if (moveUp) { this.applyFocusTo(this.visibleItems.pop()); - } else { - if (this.visibleItems.length) { - this.applyFocusTo(this.visibleItems.shift()); - } + } else if (this.visibleItems.length) { + this.applyFocusTo(this.visibleItems.shift()); } return; } @@ -687,7 +687,7 @@ export class Select implements IxInputFieldComponent { } private filterItemsWithTypeahead() { - this.inputFilterText = this.inputRef?.value || ''; + this.inputFilterText = this.inputRef?.value ?? ''; if (this.isSingleMode && this.inputFilterText === this.selectedLabels[0]) { return; @@ -697,7 +697,9 @@ export class Select implements IxInputFieldComponent { this.items.forEach((item) => { item.classList.remove('d-none'); if ( - !item.label.toLowerCase().includes(this.inputFilterText.toLowerCase()) + !item.label + ?.toLowerCase() + .includes(this.inputFilterText.toLowerCase()) ) { item.classList.add('d-none'); } @@ -801,7 +803,11 @@ export class Select implements IxInputFieldComponent { */ @Method() getNativeInputElement(): Promise { - return Promise.resolve(this.inputRef); + if (this.inputRef) { + return Promise.resolve(this.inputRef); + } else { + return Promise.reject(new Error('Input element not found')); + } } /** @@ -809,7 +815,10 @@ export class Select implements IxInputFieldComponent { */ @Method() async focusInput(): Promise { - return (await this.getNativeInputElement()).focus(); + const inputElement = await this.getNativeInputElement(); + if (inputElement) { + inputElement.focus(); + } } render() { @@ -880,12 +889,14 @@ export class Select implements IxInputFieldComponent { ref={(ref) => (this.inputRef = ref)} onBlur={(e) => this.onInputBlur(e)} onFocus={() => { - this.navigationItem = null; + this.navigationItem = undefined; }} onInput={() => this.filterItemsWithTypeahead()} onKeyDown={(e) => this.onKeyDown(e)} /> {this.allowClear && + !this.disabled && + !this.readonly && (this.selectedLabels?.length || this.inputFilterText) ? ( { expect(inputValue).toBe('Item 2'); }); +test('check if clear button visible in disabled', async ({ mount, page }) => { + await mount(` + + Test + Test + Test + + `); + + const selectElement = page.locator('ix-select'); + await expect(selectElement).toHaveClass(/hydrated/); + + const clearButton = page.locator('ix-icon-button.clear.btn-icon-16'); + await expect(clearButton).toBeVisible(); + + await selectElement.evaluate( + (select: HTMLIxSelectElement) => (select.disabled = true) + ); + + await expect(clearButton).not.toBeAttached(); +}); + test('type in a novel item name in multiple mode, click outside', async ({ mount, page, diff --git a/packages/storybook-docs/src/stories/select.stories.ts b/packages/storybook-docs/src/stories/select.stories.ts new file mode 100644 index 0000000000..958ec8a281 --- /dev/null +++ b/packages/storybook-docs/src/stories/select.stories.ts @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2024 Siemens AG + * + * SPDX-License-Identifier: MIT + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import type { Components } from '@siemens/ix/components'; +import type { ArgTypes, Meta, StoryObj } from '@storybook/web-components'; +import { genericRender, makeArgTypes } from './utils/generic-render'; +import { html } from 'lit'; + +type Element = Components.IxSelect; + +const meta = { + title: 'Example/Select', + tags: [], + render: (args) => genericRender('ix-select', args), + argTypes: makeArgTypes>>('ix-select'), + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/design/r2nqdNNXXZtPmWuVjIlM1Q/iX-Components---Brand-Dark?node-id=42365-175539&m=dev', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: {}, +}; + +export const editableSelect: Story = { + render: ({ value, editable, allowClear, disabled }) => { + return html` + + + + + `; + }, + args: { + value: 'Administrator', + editable: true, + allowClear: true, + disabled: false, + }, +};