feat(forms): add useMaskedField and useMaskedInput composables for input masking

This commit is contained in:
2026-06-09 13:54:52 +07:00
parent 6de7c72fb3
commit 07937e26db
426 changed files with 12981 additions and 311 deletions
+6
View File
@@ -1,6 +1,12 @@
<script lang="ts">
import type { PopperAnchorProps } from '../popper';
/**
* An optional positioning anchor for the menu content. Render it around the
* element the menu should attach to when the open trigger is not itself the
* anchor (for example, anchoring a dropdown to a virtual element or the
* pointer). When omitted, content is positioned relative to its trigger.
*/
export interface MenuAnchorProps extends PopperAnchorProps {}
</script>
+5
View File
@@ -1,6 +1,11 @@
<script lang="ts">
import type { PopperArrowProps } from '../popper';
/**
* An optional arrow that renders inside the menu content and points back toward
* the anchor, visually tying the floating menu to its trigger. Place it as a
* child of the content.
*/
export interface MenuArrowProps extends PopperArrowProps {}
</script>
@@ -2,8 +2,16 @@
import type { MenuItemImplEmits, MenuItemImplProps } from './MenuItemImpl.vue';
import type { CheckedState } from './types';
/**
* A menu item that toggles a boolean (or indeterminate) state when selected,
* rendering with `role="menuitemcheckbox"`. Pair it with MenuItemIndicator to
* show a check mark. Bind `v-model:checked` to control its state, or leave it
* uncontrolled with `defaultChecked`.
*/
export interface MenuCheckboxItemProps extends MenuItemImplProps {
/** The controlled checked state. Use together with `update:checked`; may be `'indeterminate'`. */
checked?: CheckedState;
/** The checked state when uncontrolled. */
defaultChecked?: CheckedState;
}
export interface MenuCheckboxItemEmits extends MenuItemImplEmits {
+10
View File
@@ -1,7 +1,17 @@
<script lang="ts">
import type { MenuContentImplEmits, MenuContentImplProps } from './MenuContentImpl.vue';
/**
* The popup surface that holds the menu items. It mounts only while the menu is
* open (gated by Presence), positions itself via Popper, and switches between
* modal and non-modal behaviour based on the root's `modal` prop. Place items,
* groups, labels, separators, and submenus inside it.
*
* Set `forceMount` to keep it in the DOM when open state is driven externally
* (for example, to run exit animations).
*/
export interface MenuContentProps extends MenuContentImplProps {
/** Force mounting the content even when closed, e.g. to control presence with an external animation library. */
forceMount?: boolean;
}
export type MenuContentEmits = MenuContentImplEmits;
@@ -2,14 +2,23 @@
import type { PopperContentProps } from '../popper';
import type { PrimitiveProps } from '../primitive';
/**
* Internal shared implementation behind MenuContent and MenuSubContent. It
* composes Popper positioning, FocusScope, DismissableLayer, and a vertical
* RovingFocusGroup, and adds typeahead search and pointer grace-area handling.
* Not meant to be used directly — render MenuContent (or MenuSubContent) instead.
*/
export interface MenuContentImplProps extends PrimitiveProps, Pick<PopperContentProps,
| 'side' | 'sideOffset' | 'sideFlip' | 'align' | 'alignOffset' | 'alignFlip'
| 'avoidCollisions' | 'collisionBoundary' | 'collisionPadding' | 'arrowPadding'
| 'sticky' | 'hideWhenDetached' | 'positionStrategy' | 'updatePositionStrategy'
| 'reference' | 'prioritizePosition'
> {
/** Whether keyboard focus should wrap from the last item back to the first (and vice versa). */
loop?: boolean;
/** Whether to trap focus inside the content while open (used for modal menus). */
trapFocus?: boolean;
/** Whether to block pointer events on everything outside the content (used for modal menus). */
disableOutsidePointerEvents?: boolean;
}
+4
View File
@@ -1,6 +1,10 @@
<script lang="ts">
import type { PrimitiveProps } from '../primitive';
/**
* Groups a set of related menu items under `role="group"` for accessibility.
* Combine it with a MenuLabel to give the group an accessible name.
*/
export interface MenuGroupProps extends PrimitiveProps {}
</script>
+5
View File
@@ -1,6 +1,11 @@
<script lang="ts">
import type { MenuItemImplEmits, MenuItemImplProps } from './MenuItemImpl.vue';
/**
* A single actionable menu item that emits `select` and closes the menu when
* activated by click, Enter, or Space. Use it for ordinary commands; call
* `event.preventDefault()` in `select` to keep the menu open after selection.
*/
export interface MenuItemProps extends MenuItemImplProps {}
export type MenuItemEmits = MenuItemImplEmits;
</script>
+8
View File
@@ -1,8 +1,16 @@
<script lang="ts">
import type { PrimitiveProps } from '../primitive';
/**
* Internal base for every selectable menu item (MenuItem, MenuCheckboxItem,
* MenuRadioItem, MenuSubTrigger). It wires up roving-focus, pointer
* highlighting, disabled state, and typeahead `textValue`, and emits `select`.
* Not used directly — render one of the public item parts instead.
*/
export interface MenuItemImplProps extends PrimitiveProps {
/** Whether the item is disabled, removing it from focus and selection. */
disabled?: boolean;
/** Optional text used to match the item during typeahead; defaults to the item's trimmed text content. */
textValue?: string;
}
export interface MenuItemImplEmits {
@@ -1,7 +1,13 @@
<script lang="ts">
import type { PrimitiveProps } from '../primitive';
/**
* Renders only while its parent MenuCheckboxItem or MenuRadioItem is checked (or
* indeterminate), giving you a place to show a check or dot icon. Must be nested
* inside a checkbox or radio item, which provides its state via context.
*/
export interface MenuItemIndicatorProps extends PrimitiveProps {
/** Force mounting the indicator even when unchecked, e.g. to control presence with an external animation library. */
forceMount?: boolean;
}
</script>
+5
View File
@@ -1,6 +1,11 @@
<script lang="ts">
import type { PrimitiveProps } from '../primitive';
/**
* A non-interactive heading for a section of the menu. It is skipped by keyboard
* navigation and typeahead, so use it to title a group of items rather than as a
* selectable entry.
*/
export interface MenuLabelProps extends PrimitiveProps {}
</script>
+5
View File
@@ -1,6 +1,11 @@
<script lang="ts">
import type { PortalProps } from '../teleport';
/**
* Teleports the menu content into a different part of the DOM (the document body
* by default) so it escapes ancestor `overflow` and stacking-context clipping.
* Wrap the content in this part to render it as a top-level overlay.
*/
export interface MenuPortalProps extends PortalProps {}
</script>
@@ -1,8 +1,15 @@
<script lang="ts">
import type { PrimitiveProps } from '../primitive';
/**
* Wraps a set of MenuRadioItems so that only one can be selected at a time,
* managing the shared selected value. Bind `v-model` to control the selection,
* or supply `defaultValue` to leave it uncontrolled.
*/
export interface MenuRadioGroupProps extends PrimitiveProps {
/** The controlled selected value. Use together with `update:modelValue`. */
modelValue?: string;
/** The selected value when uncontrolled. */
defaultValue?: string;
}
export interface MenuRadioGroupEmits {
@@ -1,7 +1,14 @@
<script lang="ts">
import type { MenuItemImplEmits, MenuItemImplProps } from './MenuItemImpl.vue';
/**
* A mutually-exclusive menu item rendered with `role="menuitemradio"`. Selecting
* it sets the enclosing MenuRadioGroup's value to this item's `value`. Pair it
* with MenuItemIndicator to show which option is active. Must be used inside a
* MenuRadioGroup.
*/
export interface MenuRadioItemProps extends MenuItemImplProps {
/** The unique value this item represents within its MenuRadioGroup. */
value: string;
}
export type MenuRadioItemEmits = MenuItemImplEmits;
+16 -10
View File
@@ -1,14 +1,22 @@
<script lang="ts">
import type { Direction } from '../config-provider';
/**
* The unstyled, low-level menu engine that powers DropdownMenu, ContextMenu, and
* Menubar. It is built on Popper and wires up roving-focus keyboard navigation,
* typeahead, nested submenus, checkbox/radio items, and modal vs. non-modal
* dismissal — but it is deliberately trigger-agnostic, so consumers supply their
* own anchor and open logic.
*
* Use this directly only when composing a new menu-like primitive; for ordinary
* app menus reach for DropdownMenu or ContextMenu instead. MenuRoot owns open
* state and provides context to every part; bind `v-model:open` (or listen to
* `update:open`) to control or observe whether the menu is open.
*/
export interface MenuRootProps {
open?: boolean;
dir?: Direction;
modal?: boolean;
}
export interface MenuRootEmits {
'update:open': [value: boolean];
}
</script>
<script setup lang="ts">
@@ -20,12 +28,11 @@ import { provideMenuContext, provideMenuRootContext } from './context';
import { useIsUsingKeyboard } from './useIsUsingKeyboard';
const {
open = false,
dir: dirProp,
modal = true,
} = defineProps<MenuRootProps>();
const emit = defineEmits<MenuRootEmits>();
const open = defineModel<boolean>('open', { default: false });
defineSlots<{ default?: () => unknown }>();
@@ -33,17 +40,16 @@ const config = useConfig();
const dirRef = toRef(() => dirProp ?? config.dir.value);
const isUsingKeyboardRef = useIsUsingKeyboard();
const content = shallowRef<HTMLElement | null>(null);
const openRef = toRef(() => open);
provideMenuContext({
open: openRef,
onOpenChange: v => emit('update:open', v),
open,
onOpenChange: (v) => { open.value = v; },
content,
onContentChange: (el) => { content.value = el; },
});
provideMenuRootContext({
onClose: () => emit('update:open', false),
onClose: () => { open.value = false; },
dir: dirRef,
isUsingKeyboardRef,
modal: toRef(() => modal),
@@ -1,4 +1,7 @@
<script setup lang="ts">
// Internal modal variant of the menu content: traps focus, disables outside
// pointer events, locks body scroll, and hides sibling content from assistive
// tech. Selected by MenuContent when the root's `modal` prop is true.
import type { MenuContentImplEmits, MenuContentImplProps } from './MenuContentImpl.vue';
import { shallowRef, watchEffect } from 'vue';
@@ -1,4 +1,8 @@
<script setup lang="ts">
// Internal non-modal variant of the menu content: leaves focus untrapped and
// outside pointer events enabled, so the rest of the page stays interactive
// while the menu is open. Selected by MenuContent when the root's `modal` prop
// is false.
import type { MenuContentImplEmits, MenuContentImplProps } from './MenuContentImpl.vue';
import { shallowRef, watchEffect } from 'vue';
@@ -1,6 +1,10 @@
<script lang="ts">
import type { PrimitiveProps } from '../primitive';
/**
* A visual and semantic divider (`role="separator"`) between groups of menu
* items. It is purely decorative for navigation and is skipped by keyboard focus.
*/
export interface MenuSeparatorProps extends PrimitiveProps {}
</script>
+7
View File
@@ -1,5 +1,12 @@
<script lang="ts">
/**
* Establishes a nested submenu. It provides a fresh menu context plus a sub
* context shared by its MenuSubTrigger and MenuSubContent, owning the submenu's
* open state. Bind `v-model:open` (or listen to `update:open`) to control it;
* the default slot also exposes the current `open` value.
*/
export interface MenuSubProps {
/** The controlled open state of the submenu. Use together with `update:open`. */
open?: boolean;
}
export interface MenuSubEmits {
@@ -1,7 +1,14 @@
<script lang="ts">
import type { MenuContentImplEmits, MenuContentImplProps } from './MenuContentImpl.vue';
/**
* The popup surface for a submenu's items. It mounts while the submenu is open,
* positions itself to the side of its MenuSubTrigger (flipping for RTL), and
* always renders non-modally so the parent menu stays interactive. Must be used
* inside a MenuSub.
*/
export interface MenuSubContentProps extends MenuContentImplProps {
/** Force mounting the content even when closed, e.g. to control presence with an external animation library. */
forceMount?: boolean;
}
export type MenuSubContentEmits = MenuContentImplEmits;
@@ -1,6 +1,12 @@
<script lang="ts">
import type { MenuItemImplProps } from './MenuItemImpl.vue';
/**
* A menu item that opens its parent MenuSub's submenu. It acts as both the
* positioning anchor and the trigger, opening on hover (with a grace delay) or
* via the directional arrow key and closing on the opposite arrow. Must be used
* inside a MenuSub, alongside a MenuSubContent.
*/
export interface MenuSubTriggerProps extends MenuItemImplProps {}
</script>
+1 -1
View File
@@ -13,7 +13,7 @@ export { default as MenuLabel, type MenuLabelProps } from './MenuLabel.vue';
export { default as MenuPortal, type MenuPortalProps } from './MenuPortal.vue';
export { default as MenuRadioGroup, type MenuRadioGroupEmits, type MenuRadioGroupProps } from './MenuRadioGroup.vue';
export { default as MenuRadioItem, type MenuRadioItemEmits, type MenuRadioItemProps } from './MenuRadioItem.vue';
export { default as MenuRoot, type MenuRootEmits, type MenuRootProps } from './MenuRoot.vue';
export { default as MenuRoot, type MenuRootProps } from './MenuRoot.vue';
export { default as MenuSeparator, type MenuSeparatorProps } from './MenuSeparator.vue';
export { default as MenuSub, type MenuSubEmits, type MenuSubProps } from './MenuSub.vue';
export { default as MenuSubContent, type MenuSubContentEmits, type MenuSubContentProps } from './MenuSubContent.vue';