feat(primitives): media-editor components, category reorg, perf + type cleanup

Reorganize components into category folders (forms/canvas/overlays/etc.); add the
media-editor headless family (timeline, curve-editor, waveform, crop, color
picker, etc.); apply perf fixes (O(1) collection lookups, plain-object drag
state, gesture-leak teardown, shallowRef color state, rect caching) and replace
source `any` with proper types.
This commit is contained in:
2026-06-15 16:54:29 +07:00
parent 661a55719e
commit eefd7abf83
1029 changed files with 65815 additions and 9449 deletions
@@ -0,0 +1,90 @@
import type { ComputedRef, Ref, ShallowRef } from 'vue';
import type { CommandFilterFunction } from './utils';
import { useContextFactory } from '@robonen/vue';
export interface CommandItemInfo {
value: string;
/** Display/searchable text. Filtering targets this instead of the identity `value`. */
textValue: string;
keywords: string[];
disabled: boolean;
onSelect?: () => void;
}
export interface CommandContext {
/** Committed selected value (v-model). */
modelValue: Ref<string | undefined>;
setModelValue: (value: string | undefined) => void;
/** Current search term (v-model:searchTerm). */
searchTerm: Ref<string>;
setSearchTerm: (value: string) => void;
/** Currently highlighted item value (keyboard / pointer focus). */
selectedValue: Ref<string | undefined>;
setSelectedValue: (value: string | undefined) => void;
/** Behavior flags. */
shouldFilter: Ref<boolean>;
loop: Ref<boolean>;
filterFunction: Ref<CommandFilterFunction | undefined>;
/** Effective reading direction (per-root override or `ConfigProvider`). */
dir: Ref<'ltr' | 'rtl'>;
/** A11y identifiers. */
listId: Ref<string>;
labelId: Ref<string>;
getItemId: (value: string) => string;
/** Registries. */
allItems: ShallowRef<Map<string, CommandItemInfo>>;
filteredItems: ComputedRef<Map<string, number>>;
registerItem: (info: CommandItemInfo) => void;
unregisterItem: (value: string) => void;
allGroups: ShallowRef<Map<string, Set<string>>>;
registerGroup: (groupId: string) => void;
unregisterGroup: (groupId: string) => void;
registerGroupItem: (groupId: string, value: string) => void;
unregisterGroupItem: (groupId: string, value: string) => void;
/** DOM. */
listElement: ShallowRef<HTMLElement | undefined>;
setListElement: (el: HTMLElement | undefined) => void;
/** Returns selectable item values sorted by score desc, then DOM order. */
getSelectableItems: () => string[];
/** Commits the currently-highlighted item: updates modelValue + fires its select callback. */
commitSelected: () => void;
/** Clears the search term and, when `resetValue`, the committed selection too. */
clear: (resetValue?: boolean) => void;
/** Registered input element (so a clear/cancel affordance can refocus it). */
inputElement: ShallowRef<HTMLElement | undefined>;
setInputElement: (el: HTMLElement | undefined) => void;
}
export interface CommandGroupContext {
id: Ref<string>;
forceMount: Ref<boolean>;
}
export interface CommandItemContext {
/** Whether this item matches the committed `modelValue`. */
isSelected: Ref<boolean>;
}
export const {
inject: useCommandContext,
provide: provideCommandContext,
} = useContextFactory<CommandContext>('Command');
export const {
inject: useCommandGroupContext,
provide: provideCommandGroupContext,
} = useContextFactory<CommandGroupContext>('CommandGroup');
export const {
inject: useCommandItemContext,
provide: provideCommandItemContext,
} = useContextFactory<CommandItemContext>('CommandItem');