feat(forms): add useMaskedField and useMaskedInput composables for input masking
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { PopperAnchorProps } from '../popper';
|
||||
|
||||
/**
|
||||
* The element the popup is positioned against, typically wrapping the Input and Trigger.
|
||||
* Acts as the Popper anchor and the boundary used for the blur-to-close heuristic.
|
||||
*/
|
||||
export interface ComboboxAnchorProps extends PopperAnchorProps {}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { PopperArrowProps } from '../popper';
|
||||
|
||||
/**
|
||||
* An optional arrow that visually points from the popup back to the anchor. Renders only
|
||||
* while the combobox is open. Place inside ComboboxContent.
|
||||
*/
|
||||
export type ComboboxArrowProps = PopperArrowProps;
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { PrimitiveProps } from '../primitive';
|
||||
|
||||
/**
|
||||
* A button that clears the current search term and refocuses the input. Typically shown
|
||||
* as an "x" inside the field while the user is typing.
|
||||
*/
|
||||
export interface ComboboxCancelProps extends PrimitiveProps {}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { ComboboxContentImplEmits, ComboboxContentImplProps } from './ComboboxContentImpl.vue';
|
||||
|
||||
/**
|
||||
* The popup listbox that holds the options. Mounts only while open (via Presence) and
|
||||
* positions itself relative to the anchor. Place the Viewport, Items, and Empty inside it.
|
||||
*/
|
||||
export type ComboboxContentProps = ComboboxContentImplProps;
|
||||
export type ComboboxContentEmits = ComboboxContentImplEmits;
|
||||
</script>
|
||||
|
||||
@@ -4,6 +4,10 @@ import type { FocusScopeEmits } from '../focus-scope';
|
||||
import type { PopperContentProps } from '../popper';
|
||||
import type { PrimitiveProps } from '../primitive';
|
||||
|
||||
/**
|
||||
* Internal implementation of the content popup: wires up focus scoping, dismiss-on-outside,
|
||||
* Popper positioning, and the screen-reader result announcer. Use ComboboxContent instead.
|
||||
*/
|
||||
export interface ComboboxContentImplProps extends PrimitiveProps, /* @vue-ignore */ Partial<PopperContentProps> {
|
||||
/** Position strategy. @default 'popper' */
|
||||
position?: 'inline' | 'popper';
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { PrimitiveProps } from '../primitive';
|
||||
|
||||
/**
|
||||
* Fallback content shown when the current search term matches no items. Renders only when
|
||||
* the filtered count is zero, unless `always` is set.
|
||||
*/
|
||||
export interface ComboboxEmptyProps extends PrimitiveProps {
|
||||
/** Render even when items exist but none are filtered out. */
|
||||
always?: boolean;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { PrimitiveProps } from '../primitive';
|
||||
|
||||
/**
|
||||
* Groups related items under a shared ComboboxLabel. Hides itself automatically when none
|
||||
* of its items survive the current filter.
|
||||
*/
|
||||
export interface ComboboxGroupProps extends PrimitiveProps {}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { PrimitiveProps } from '../primitive';
|
||||
|
||||
/**
|
||||
* The text field users type into to filter options. Owns the search term, ARIA combobox
|
||||
* semantics, and keyboard navigation (arrows, Home/End, Enter to select, Escape to close).
|
||||
*/
|
||||
export interface ComboboxInputProps extends PrimitiveProps {
|
||||
/** Disable the input. */
|
||||
disabled?: boolean;
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
import type { PrimitiveProps } from '../primitive';
|
||||
import type { AcceptableValue } from './utils';
|
||||
|
||||
/**
|
||||
* A single selectable option in the list. Registers itself for filtering and keyboard
|
||||
* navigation, toggles selection on click, and highlights on pointer move.
|
||||
*/
|
||||
export interface ComboboxItemProps<T extends AcceptableValue = AcceptableValue> extends PrimitiveProps {
|
||||
/** Item value. Selected/registered identity. */
|
||||
value: T;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { PrimitiveProps } from '../primitive';
|
||||
|
||||
/**
|
||||
* Marks the selected state of its parent ComboboxItem, e.g. a checkmark. Renders only when
|
||||
* that item is selected.
|
||||
*/
|
||||
export interface ComboboxItemIndicatorProps extends PrimitiveProps {}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { PrimitiveProps } from '../primitive';
|
||||
|
||||
/**
|
||||
* An accessible label for a ComboboxGroup. Its id is referenced by the group's
|
||||
* `aria-labelledby`, so place it as a direct child of ComboboxGroup.
|
||||
*/
|
||||
export interface ComboboxLabelProps extends PrimitiveProps {}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { PortalProps } from '../teleport';
|
||||
|
||||
/**
|
||||
* Teleports the ComboboxContent into another part of the DOM (defaults to `body`) to escape
|
||||
* overflow/stacking-context clipping. Wrap ComboboxContent with it.
|
||||
*/
|
||||
export interface ComboboxPortalProps extends PortalProps {}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
import type { Direction } from '../config-provider';
|
||||
import type { AcceptableValue, ComboboxFilterFunction, ComboboxFilterItem } from './utils';
|
||||
|
||||
/**
|
||||
* An autocomplete / typeahead input that filters a list of options as the user types.
|
||||
* Combine a text input with a popup listbox, supporting single or multiple selection,
|
||||
* custom filtering, and full keyboard navigation. Reach for it when users must pick from
|
||||
* a large or searchable set of options; for a small fixed list a plain Select is simpler.
|
||||
* Wraps everything in a Popper and provides shared state to every other Combobox part.
|
||||
*/
|
||||
export interface ComboboxRootProps<T extends AcceptableValue = AcceptableValue> {
|
||||
/** Controlled selected value. Use `v-model`. */
|
||||
modelValue?: T | T[];
|
||||
@@ -69,8 +76,6 @@ const {
|
||||
by,
|
||||
} = defineProps<ComboboxRootProps<T>>();
|
||||
|
||||
const emit = defineEmits<ComboboxRootEmits<T>>();
|
||||
|
||||
const config = useConfig();
|
||||
const direction = computed(() => dir ?? config.dir.value);
|
||||
|
||||
@@ -203,7 +208,6 @@ function isSelected(v: T): boolean {
|
||||
|
||||
function commitValue(next: T | T[] | undefined) {
|
||||
value.value = next;
|
||||
emit('update:modelValue', next);
|
||||
}
|
||||
|
||||
function onValueChange(v: T) {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { PrimitiveProps } from '../primitive';
|
||||
|
||||
/**
|
||||
* A purely visual divider between items or groups inside the popup. Decorative and hidden
|
||||
* from assistive technology.
|
||||
*/
|
||||
export interface ComboboxSeparatorProps extends PrimitiveProps {}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { PrimitiveProps } from '../primitive';
|
||||
|
||||
/**
|
||||
* A button, usually a chevron next to the input, that toggles the popup open and closed.
|
||||
* Optional: typing in the Input also opens the list.
|
||||
*/
|
||||
export interface ComboboxTriggerProps extends PrimitiveProps {
|
||||
/** Disable the trigger independently from the root. */
|
||||
disabled?: boolean;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { PrimitiveProps } from '../primitive';
|
||||
|
||||
/**
|
||||
* The scrollable region inside ComboboxContent that holds the items. Provides the overflow
|
||||
* container that keeps the highlighted item scrolled into view.
|
||||
*/
|
||||
export interface ComboboxViewportProps extends PrimitiveProps {}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import {
|
||||
ComboboxAnchor,
|
||||
ComboboxCancel,
|
||||
ComboboxContent,
|
||||
ComboboxEmpty,
|
||||
ComboboxGroup,
|
||||
ComboboxInput,
|
||||
ComboboxItem,
|
||||
ComboboxItemIndicator,
|
||||
ComboboxLabel,
|
||||
ComboboxPortal,
|
||||
ComboboxRoot,
|
||||
ComboboxTrigger,
|
||||
ComboboxViewport,
|
||||
} from '@robonen/primitives';
|
||||
|
||||
interface Framework {
|
||||
value: string;
|
||||
label: string;
|
||||
group: 'JavaScript' | 'Native';
|
||||
}
|
||||
|
||||
const frameworks: Framework[] = [
|
||||
{ value: 'vue', label: 'Vue', group: 'JavaScript' },
|
||||
{ value: 'react', label: 'React', group: 'JavaScript' },
|
||||
{ value: 'svelte', label: 'Svelte', group: 'JavaScript' },
|
||||
{ value: 'solid', label: 'Solid', group: 'JavaScript' },
|
||||
{ value: 'angular', label: 'Angular', group: 'JavaScript' },
|
||||
{ value: 'swiftui', label: 'SwiftUI', group: 'Native' },
|
||||
{ value: 'compose', label: 'Jetpack Compose', group: 'Native' },
|
||||
{ value: 'flutter', label: 'Flutter', group: 'Native' },
|
||||
];
|
||||
|
||||
const selected = ref<string>();
|
||||
|
||||
function labelFor(value: string | undefined) {
|
||||
return frameworks.find(f => f.value === value)?.label ?? '';
|
||||
}
|
||||
|
||||
const groups = ['JavaScript', 'Native'] as const;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-xs flex-col gap-3">
|
||||
<ComboboxRoot
|
||||
v-model="selected"
|
||||
:display-value="labelFor"
|
||||
class="relative"
|
||||
>
|
||||
<ComboboxAnchor
|
||||
class="flex items-center gap-1 rounded-lg border border-(--border) bg-(--bg-inset) px-2 py-1.5 focus-within:border-(--accent) focus-within:ring-2 focus-within:ring-(--ring)"
|
||||
>
|
||||
<ComboboxInput
|
||||
placeholder="Search a framework..."
|
||||
open-on-click
|
||||
class="min-w-0 flex-1 bg-transparent px-1 text-sm text-(--fg) outline-none placeholder:text-(--fg-subtle)"
|
||||
/>
|
||||
|
||||
<ComboboxCancel
|
||||
class="grid size-5 place-items-center rounded text-(--fg-subtle) hover:bg-(--bg-subtle) hover:text-(--fg)"
|
||||
>
|
||||
<span aria-hidden="true" class="text-xs">✕</span>
|
||||
</ComboboxCancel>
|
||||
|
||||
<ComboboxTrigger
|
||||
class="grid size-5 place-items-center rounded text-(--fg-muted) hover:bg-(--bg-subtle) hover:text-(--fg) data-[state=open]:rotate-180"
|
||||
>
|
||||
<span aria-hidden="true" class="text-xs">▾</span>
|
||||
</ComboboxTrigger>
|
||||
</ComboboxAnchor>
|
||||
|
||||
<ComboboxPortal>
|
||||
<ComboboxContent
|
||||
:side-offset="6"
|
||||
class="z-50 w-(--popper-anchor-width) overflow-hidden rounded-lg border border-(--border) bg-(--bg-elevated) shadow-lg"
|
||||
>
|
||||
<ComboboxViewport class="max-h-60 p-1">
|
||||
<ComboboxEmpty class="px-3 py-6 text-center text-sm text-(--fg-subtle)">
|
||||
No frameworks found.
|
||||
</ComboboxEmpty>
|
||||
|
||||
<ComboboxGroup
|
||||
v-for="group in groups"
|
||||
:key="group"
|
||||
class="mb-1 last:mb-0"
|
||||
>
|
||||
<ComboboxLabel
|
||||
class="px-2 py-1 text-xs font-medium uppercase tracking-wide text-(--fg-subtle)"
|
||||
>
|
||||
{{ group }}
|
||||
</ComboboxLabel>
|
||||
|
||||
<ComboboxItem
|
||||
v-for="framework in frameworks.filter(f => f.group === group)"
|
||||
:key="framework.value"
|
||||
:value="framework.value"
|
||||
:text-value="framework.label"
|
||||
class="flex cursor-pointer items-center justify-between rounded-md px-2 py-1.5 text-sm text-(--fg) outline-none data-[highlighted]:bg-(--accent) data-[highlighted]:text-(--accent-fg) data-[disabled]:opacity-50"
|
||||
>
|
||||
<span>{{ framework.label }}</span>
|
||||
<ComboboxItemIndicator>
|
||||
<span aria-hidden="true">✓</span>
|
||||
</ComboboxItemIndicator>
|
||||
</ComboboxItem>
|
||||
</ComboboxGroup>
|
||||
</ComboboxViewport>
|
||||
</ComboboxContent>
|
||||
</ComboboxPortal>
|
||||
</ComboboxRoot>
|
||||
|
||||
<p class="text-sm text-(--fg-muted)">
|
||||
Selected:
|
||||
<span class="font-medium text-(--fg)">{{ selected ? labelFor(selected) : 'none' }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user