feat(primitives): add menu, dropdown-menu, context-menu, and menubar primitives
Implements WAI-ARIA APG-compliant headless menu primitive families: - menu: base primitive with MenuRoot, MenuContent, MenuItem, MenuCheckboxItem, MenuRadioGroup/Item, MenuSub, and helpers - dropdown-menu: DropdownMenuRoot with trigger anchoring - context-menu: ContextMenuRoot with right-click virtual anchor - menubar: MenubarRoot with keyboard navigation between menus Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
<script lang="ts">
|
||||
import type { MenuItemImplEmits, MenuItemImplProps } from './MenuItemImpl.vue';
|
||||
import type { CheckedState } from './types';
|
||||
|
||||
export interface MenuCheckboxItemProps extends MenuItemImplProps {
|
||||
checked?: CheckedState;
|
||||
defaultChecked?: CheckedState;
|
||||
}
|
||||
export interface MenuCheckboxItemEmits extends MenuItemImplEmits {
|
||||
'update:checked': [value: CheckedState];
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { provideMenuItemIndicatorContext, useMenuRootContext } from './context';
|
||||
import MenuItemImpl from './MenuItemImpl.vue';
|
||||
import { ITEM_SELECT, getCheckedState, isIndeterminate } from './utils';
|
||||
|
||||
const {
|
||||
checked: checkedProp,
|
||||
defaultChecked = false,
|
||||
...itemProps
|
||||
} = defineProps<MenuCheckboxItemProps>();
|
||||
|
||||
const emit = defineEmits<MenuCheckboxItemEmits>();
|
||||
const rootCtx = useMenuRootContext();
|
||||
|
||||
const local = ref<CheckedState>(defaultChecked);
|
||||
const checkedState = computed<CheckedState>(() => checkedProp !== undefined ? checkedProp : local.value);
|
||||
|
||||
provideMenuItemIndicatorContext({ checkedState });
|
||||
|
||||
function handleSelect(event: Event) {
|
||||
const next: CheckedState = isIndeterminate(checkedState.value) ? true : !checkedState.value;
|
||||
local.value = next;
|
||||
emit('update:checked', next);
|
||||
|
||||
const selectEvent = new CustomEvent(ITEM_SELECT, { bubbles: true, cancelable: true })
|
||||
;(event.currentTarget as HTMLElement).dispatchEvent(selectEvent);
|
||||
emit('select', event);
|
||||
if (!selectEvent.defaultPrevented) rootCtx.onClose();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenuItemImpl
|
||||
v-bind="itemProps"
|
||||
role="menuitemcheckbox"
|
||||
:aria-checked="isIndeterminate(checkedState) ? 'mixed' : checkedState"
|
||||
:data-state="getCheckedState(checkedState)"
|
||||
@select="handleSelect"
|
||||
>
|
||||
<slot />
|
||||
</MenuItemImpl>
|
||||
</template>
|
||||
Reference in New Issue
Block a user