V2: Origin based animations, Gesture Builder, New Demo, non-url-based-routing. Massive improvements.
This commit is contained in:
41
packages/core-v2/src/components/OriginGesture.vue
Normal file
41
packages/core-v2/src/components/OriginGesture.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useOriginGesture } from "../gesture";
|
||||
import type { OriginGestureProps } from "../types";
|
||||
|
||||
defineOptions({ name: "OriginGesture", inheritAttrs: false });
|
||||
|
||||
const props = withDefaults(defineProps<OriginGestureProps>(), {
|
||||
as: "div",
|
||||
});
|
||||
|
||||
/*
|
||||
* This convenience component makes the declaration live exactly where the
|
||||
* developer writes it. `useOriginGesture()` is also public for components that
|
||||
* cannot accept an extra wrapper element.
|
||||
*/
|
||||
const gesture = useOriginGesture(
|
||||
props.gesture ?? {
|
||||
direction: props.direction,
|
||||
edge: props.edge,
|
||||
threshold: props.threshold,
|
||||
action: (context) => props.action?.(context),
|
||||
},
|
||||
);
|
||||
const touchStyle = computed(() => gesture.style);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="as"
|
||||
v-bind="$attrs"
|
||||
class="nvo-gesture"
|
||||
:style="touchStyle"
|
||||
@pointerdown="gesture.onPointerdown"
|
||||
@pointermove="gesture.onPointermove"
|
||||
@pointerup="gesture.onPointerup"
|
||||
@pointercancel="gesture.onPointercancel"
|
||||
>
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
||||
73
packages/core-v2/src/components/OriginGestureSurface.vue
Normal file
73
packages/core-v2/src/components/OriginGestureSurface.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useOriginGesture } from "../gesture";
|
||||
import type { OriginGestureSurfaceProps } from "../types";
|
||||
|
||||
defineOptions({ name: "OriginGestureSurface", inheritAttrs: false });
|
||||
|
||||
const props = withDefaults(defineProps<OriginGestureSurfaceProps>(), {
|
||||
as: "div",
|
||||
});
|
||||
|
||||
/*
|
||||
* All interaction policy belongs to the component that built the definitions.
|
||||
* This convenience host only installs them, combines their browser scrolling
|
||||
* requirements, and forwards a pointer sequence to every recognizer.
|
||||
*/
|
||||
const bindings = props.gestures.map((definition) =>
|
||||
useOriginGesture(definition),
|
||||
);
|
||||
|
||||
const surfaceStyle = computed(() => {
|
||||
const horizontal = props.gestures.some(
|
||||
({ direction }) => direction === "left" || direction === "right",
|
||||
);
|
||||
const vertical = props.gestures.some(
|
||||
({ direction }) => direction === "up" || direction === "down",
|
||||
);
|
||||
|
||||
return {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
touchAction:
|
||||
horizontal && vertical
|
||||
? "none"
|
||||
: horizontal
|
||||
? "pan-y"
|
||||
: vertical
|
||||
? "pan-x"
|
||||
: "auto",
|
||||
} as const;
|
||||
});
|
||||
|
||||
function pointerDown(event: PointerEvent) {
|
||||
for (const binding of bindings) binding.onPointerdown(event);
|
||||
}
|
||||
|
||||
function pointerMove(event: PointerEvent) {
|
||||
for (const binding of bindings) binding.onPointermove(event);
|
||||
}
|
||||
|
||||
function pointerUp(event: PointerEvent) {
|
||||
for (const binding of bindings) binding.onPointerup(event);
|
||||
}
|
||||
|
||||
function pointerCancel() {
|
||||
for (const binding of bindings) binding.onPointercancel();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="as"
|
||||
v-bind="$attrs"
|
||||
class="nvo-gesture"
|
||||
:style="[$attrs.style, surfaceStyle]"
|
||||
@pointerdown="pointerDown"
|
||||
@pointermove="pointerMove"
|
||||
@pointerup="pointerUp"
|
||||
@pointercancel="pointerCancel"
|
||||
>
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
||||
52
packages/core-v2/src/components/OriginNodeHost.vue
Normal file
52
packages/core-v2/src/components/OriginNodeHost.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, provide, ref, watchEffect } from "vue";
|
||||
import type { OriginScene, OriginSceneNode } from "../types";
|
||||
import { originNodeScopeKey } from "../lifecycle";
|
||||
|
||||
defineOptions({ name: "OriginNodeHost" });
|
||||
|
||||
const props = defineProps<{
|
||||
scene: OriginScene;
|
||||
node: OriginSceneNode;
|
||||
}>();
|
||||
|
||||
/*
|
||||
* This host is the stable physical home of the view component. It is keyed by
|
||||
* the scene node in OriginScene and never nested under another view. Only its
|
||||
* composed CSS style changes while operation edges are created and collapsed.
|
||||
*/
|
||||
const host = ref<HTMLElement | null>(null);
|
||||
provide(originNodeScopeKey, {
|
||||
scene: props.scene,
|
||||
nodeKey: props.node.key,
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
props.scene.registerElement(props.node.key, host.value);
|
||||
});
|
||||
onBeforeUnmount(() => props.scene.registerElement(props.node.key, null));
|
||||
|
||||
const style = computed(() => props.scene.styleForNode(props.node.key));
|
||||
const interactive = computed(() =>
|
||||
props.scene.isNodeInteractive(props.node.key),
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
ref="host"
|
||||
class="nvo-node"
|
||||
:style="style"
|
||||
:data-origin-node="node.key"
|
||||
:data-origin-view="node.view.name ?? node.view.key"
|
||||
:data-origin-state="node.state"
|
||||
:aria-hidden="interactive ? undefined : 'true'"
|
||||
:inert="interactive ? undefined : true"
|
||||
>
|
||||
<!--
|
||||
Vue owns the component lifecycle normally. Adding another origin merely
|
||||
adds effect layers to this host; it does not replace this component VNode.
|
||||
-->
|
||||
<component :is="node.view.component" v-bind="node.view.props" />
|
||||
</section>
|
||||
</template>
|
||||
38
packages/core-v2/src/components/OriginScene.vue
Normal file
38
packages/core-v2/src/components/OriginScene.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, ref, watchEffect } from "vue";
|
||||
import type { OriginSceneProps } from "../types";
|
||||
import OriginNodeHost from "./OriginNodeHost.vue";
|
||||
|
||||
defineOptions({ name: "OriginScene" });
|
||||
|
||||
const props = defineProps<OriginSceneProps>();
|
||||
|
||||
const root = ref<HTMLElement | null>(null);
|
||||
watchEffect(() => props.scene.registerContainer(root.value));
|
||||
onBeforeUnmount(() => props.scene.registerContainer(null));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!--
|
||||
All component hosts are siblings. The operation graph is intentionally not
|
||||
mirrored as DOM ancestry because promoting Y after X→Y must not remount Y.
|
||||
-->
|
||||
<main
|
||||
ref="root"
|
||||
class="nvo-scene"
|
||||
:style="{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
isolation: 'isolate',
|
||||
}"
|
||||
>
|
||||
<OriginNodeHost
|
||||
v-for="node in scene.nodes.value"
|
||||
:key="node.key"
|
||||
:scene="scene"
|
||||
:node="node"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
||||
Reference in New Issue
Block a user