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:
@@ -0,0 +1,122 @@
|
||||
<script lang="ts">
|
||||
import type { DrawerHandleProps } from './controls';
|
||||
|
||||
export type { DrawerHandleProps } from './controls';
|
||||
|
||||
/**
|
||||
* The grab handle at the edge of the drawer. Dragging it always moves the
|
||||
* drawer (even when the root is `handleOnly`), and a tap cycles through snap
|
||||
* points — or closes a dismissible drawer once past the last one.
|
||||
*/
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, useTemplateRef, watchPostEffect } from 'vue';
|
||||
import { injectDrawerRootContext } from './context';
|
||||
|
||||
const { preventCycle = false } = defineProps<DrawerHandleProps>();
|
||||
|
||||
const LONG_HANDLE_PRESS_TIMEOUT = 250;
|
||||
const DOUBLE_TAP_TIMEOUT = 120;
|
||||
|
||||
const { onPress, onDrag, handleRef, handleOnly, isOpen, snapPoints, activeSnapPoint, isDragging, dismissible, closeDrawer }
|
||||
= injectDrawerRootContext();
|
||||
|
||||
// Mirror the element into the shared context ref. A local template ref + watch
|
||||
// is used instead of an inline function `:ref` because the inline form can't
|
||||
// reliably close over the destructured context binding under `<script setup>`.
|
||||
const handleElement = useTemplateRef('handleElement');
|
||||
|
||||
watchPostEffect(() => {
|
||||
handleRef.value = handleElement.value;
|
||||
});
|
||||
|
||||
const closeTimeoutId = ref<number | null>(null);
|
||||
const shouldCancelInteraction = ref(false);
|
||||
|
||||
function handleStartCycle() {
|
||||
// Ignore the second tap of a double-tap.
|
||||
if (shouldCancelInteraction.value) {
|
||||
handleCancelInteraction();
|
||||
return;
|
||||
}
|
||||
|
||||
globalThis.setTimeout(() => {
|
||||
handleCycleSnapPoints();
|
||||
}, DOUBLE_TAP_TIMEOUT);
|
||||
}
|
||||
|
||||
function handleCycleSnapPoints() {
|
||||
// Don't treat an accidental tap during a resize as a cycle.
|
||||
if (isDragging.value || preventCycle || shouldCancelInteraction.value) {
|
||||
handleCancelInteraction();
|
||||
return;
|
||||
}
|
||||
|
||||
handleCancelInteraction();
|
||||
|
||||
if (!snapPoints.value || snapPoints.value.length === 0) {
|
||||
if (!dismissible.value)
|
||||
closeDrawer();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const isLastSnapPoint = activeSnapPoint.value === snapPoints.value[snapPoints.value.length - 1];
|
||||
|
||||
if (isLastSnapPoint && dismissible.value) {
|
||||
closeDrawer();
|
||||
return;
|
||||
}
|
||||
|
||||
const currentSnapIndex = snapPoints.value.indexOf(activeSnapPoint.value);
|
||||
|
||||
if (currentSnapIndex === -1)
|
||||
return; // activeSnapPoint not in snapPoints
|
||||
|
||||
const nextSnapPointIndex = isLastSnapPoint ? 0 : currentSnapIndex + 1;
|
||||
activeSnapPoint.value = snapPoints.value[nextSnapPointIndex];
|
||||
}
|
||||
|
||||
function handleStartInteraction() {
|
||||
closeTimeoutId.value = globalThis.setTimeout(() => {
|
||||
// A long press cancels the tap-to-cycle.
|
||||
shouldCancelInteraction.value = true;
|
||||
}, LONG_HANDLE_PRESS_TIMEOUT);
|
||||
}
|
||||
|
||||
function handleCancelInteraction() {
|
||||
if (closeTimeoutId.value)
|
||||
globalThis.clearTimeout(closeTimeoutId.value);
|
||||
|
||||
shouldCancelInteraction.value = false;
|
||||
}
|
||||
|
||||
function handlePointerDown(event: PointerEvent) {
|
||||
if (handleOnly.value)
|
||||
onPress(event);
|
||||
handleStartInteraction();
|
||||
}
|
||||
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
if (handleOnly.value)
|
||||
onDrag(event);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="handleElement"
|
||||
:data-drawer-visible="isOpen ? 'true' : 'false'"
|
||||
data-drawer-handle
|
||||
aria-hidden="true"
|
||||
@click="handleStartCycle"
|
||||
@pointercancel="handleCancelInteraction"
|
||||
@pointerdown="handlePointerDown"
|
||||
@pointermove="handlePointerMove"
|
||||
>
|
||||
<span data-drawer-handle-hitarea aria-hidden="true">
|
||||
<slot />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user