From 8435a0fe1622eb5b6863b6e4751c9d2d1be36db9 Mon Sep 17 00:00:00 2001 From: Benjamin Canac Date: Tue, 8 Apr 2025 14:43:58 +0200 Subject: [PATCH] fix(InputMenu/SelectMenu): prevent `disabled` items to be selected Resolves #3474 --- src/runtime/components/InputMenu.vue | 16 +- src/runtime/components/SelectMenu.vue | 15 +- src/theme/input-menu.ts | 4 +- src/theme/select.ts | 4 +- .../__snapshots__/InputMenu-vue.spec.ts.snap | 346 +++++++------- .../__snapshots__/InputMenu.spec.ts.snap | 346 +++++++------- .../__snapshots__/Select-vue.spec.ts.snap | 390 ++++++++-------- .../__snapshots__/Select.spec.ts.snap | 390 ++++++++-------- .../__snapshots__/SelectMenu-vue.spec.ts.snap | 432 +++++++++--------- .../__snapshots__/SelectMenu.spec.ts.snap | 432 +++++++++--------- 10 files changed, 1200 insertions(+), 1175 deletions(-) diff --git a/src/runtime/components/InputMenu.vue b/src/runtime/components/InputMenu.vue index 4a80991b..17c69d04 100644 --- a/src/runtime/components/InputMenu.vue +++ b/src/runtime/components/InputMenu.vue @@ -364,10 +364,22 @@ function onRemoveTag(event: any) { } } +function onSelect(e: Event, item: InputMenuItem) { + if (!isInputItem(item)) { + return + } + + if (item.disabled) { + e.preventDefault() + return + } + + item.onSelect?.(e) +} + function isInputItem(item: InputMenuItem): item is _InputMenuItem { return typeof item === 'object' && item !== null } - defineExpose({ inputRef }) @@ -493,7 +505,7 @@ defineExpose({ :class="ui.item({ class: props.ui?.item })" :disabled="isInputItem(item) && item.disabled" :value="props.valueKey && isInputItem(item) ? get(item, String(props.valueKey)) : item" - @select="isInputItem(item) && item.onSelect?.($event)" + @select="onSelect($event, item)" > diff --git a/src/runtime/components/SelectMenu.vue b/src/runtime/components/SelectMenu.vue index 9eef2f7e..4ec588f0 100644 --- a/src/runtime/components/SelectMenu.vue +++ b/src/runtime/components/SelectMenu.vue @@ -326,6 +326,19 @@ function onUpdateOpen(value: boolean) { } } +function onSelect(e: Event, item: SelectMenuItem) { + if (!isSelectItem(item)) { + return + } + + if (item.disabled) { + e.preventDefault() + return + } + + item.onSelect?.(e) +} + function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem { return typeof item === 'object' && item !== null } @@ -417,7 +430,7 @@ function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem { :class="ui.item({ class: props.ui?.item })" :disabled="isSelectItem(item) && item.disabled" :value="props.valueKey && isSelectItem(item) ? get(item, props.valueKey as string) : item" - @select="isSelectItem(item) && item.onSelect?.($event)" + @select="onSelect($event, item)" > diff --git a/src/theme/input-menu.ts b/src/theme/input-menu.ts index 06c158d3..498c6c9d 100644 --- a/src/theme/input-menu.ts +++ b/src/theme/input-menu.ts @@ -14,8 +14,8 @@ export default (options: Required) => { empty: 'py-2 text-center text-sm text-(--ui-text-muted)', label: 'font-semibold text-(--ui-text-highlighted)', separator: '-mx-1 my-1 h-px bg-(--ui-border)', - item: ['group relative w-full flex items-center gap-1.5 p-1.5 text-sm select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-[calc(var(--ui-radius)*1.5)] data-disabled:cursor-not-allowed data-disabled:opacity-75 text-(--ui-text) data-highlighted:text-(--ui-text-highlighted) data-highlighted:before:bg-(--ui-bg-elevated)/50', options.theme.transitions && 'transition-colors before:transition-colors'], - itemLeadingIcon: ['shrink-0 text-(--ui-text-dimmed) group-data-highlighted:text-(--ui-text)', options.theme.transitions && 'transition-colors'], + item: ['group relative w-full flex items-center gap-1.5 p-1.5 text-sm select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-[calc(var(--ui-radius)*1.5)] data-disabled:cursor-not-allowed data-disabled:opacity-75 text-(--ui-text) data-highlighted:not-data-disabled:text-(--ui-text-highlighted) data-highlighted:not-data-disabled:before:bg-(--ui-bg-elevated)/50', options.theme.transitions && 'transition-colors before:transition-colors'], + itemLeadingIcon: ['shrink-0 text-(--ui-text-dimmed) group-data-highlighted:not-group-data-disabled:text-(--ui-text)', options.theme.transitions && 'transition-colors'], itemLeadingAvatar: 'shrink-0', itemLeadingAvatarSize: '', itemLeadingChip: 'shrink-0', diff --git a/src/theme/select.ts b/src/theme/select.ts index d9e01870..5c354bb6 100644 --- a/src/theme/select.ts +++ b/src/theme/select.ts @@ -17,8 +17,8 @@ export default (options: Required) => { empty: 'py-2 text-center text-sm text-(--ui-text-muted)', label: 'font-semibold text-(--ui-text-highlighted)', separator: '-mx-1 my-1 h-px bg-(--ui-border)', - item: ['group relative w-full flex items-center select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-[calc(var(--ui-radius)*1.5)] data-disabled:cursor-not-allowed data-disabled:opacity-75 text-(--ui-text) data-highlighted:text-(--ui-text-highlighted) data-highlighted:before:bg-(--ui-bg-elevated)/50', options.theme.transitions && 'transition-colors before:transition-colors'], - itemLeadingIcon: ['shrink-0 text-(--ui-text-dimmed) group-data-highlighted:text-(--ui-text)', options.theme.transitions && 'transition-colors'], + item: ['group relative w-full flex items-center select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-[calc(var(--ui-radius)*1.5)] data-disabled:cursor-not-allowed data-disabled:opacity-75 text-(--ui-text) data-highlighted:not-data-disabled:text-(--ui-text-highlighted) data-highlighted:not-data-disabled:before:bg-(--ui-bg-elevated)/50', options.theme.transitions && 'transition-colors before:transition-colors'], + itemLeadingIcon: ['shrink-0 text-(--ui-text-dimmed) group-data-highlighted:not-group-data-disabled:text-(--ui-text)', options.theme.transitions && 'transition-colors'], itemLeadingAvatar: 'shrink-0', itemLeadingAvatarSize: '', itemLeadingChip: 'shrink-0', diff --git a/test/components/__snapshots__/InputMenu-vue.spec.ts.snap b/test/components/__snapshots__/InputMenu-vue.spec.ts.snap index 5013d396..ec2a0019 100644 --- a/test/components/__snapshots__/InputMenu-vue.spec.ts.snap +++ b/test/components/__snapshots__/InputMenu-vue.spec.ts.snap @@ -10,11 +10,11 @@ exports[`InputMenu > renders with ariaLabel correctly 1`] = ` @@ -48,11 +48,11 @@ exports[`InputMenu > renders with arrow correctly 1`] = ` @@ -85,11 +85,11 @@ exports[`InputMenu > renders with as correctly 1`] = ` @@ -147,11 +147,11 @@ exports[`InputMenu > renders with class correctly 1`] = ` @@ -185,7 +185,7 @@ exports[`InputMenu > renders with create-item-label slot correctly 1`] = `