import { cancelViewTransitionAnimation, getActiveViewTransition, recordParticipant, registerAnimateView, unregisterAnimateView } from "./context.mjs";
import { injectViewTransitionResetStyles } from "./reset-styles.mjs";
import { getCurrentInstance, onBeforeUnmount, onMounted, onUnmounted, onUpdated } from "vue";
//#region src/components/animate-view/use-animate-view.ts
var uidCounter = 0;
/** charCode of 'a' — suffix base for multi-root layer names (`uid-a`, `uid-b`, …) */
var SUFFIX_A = 97;
/**
* Collect the nearest host DOM nodes of a component instance.
*
* How the traversal stays inside this component's DOM range:
*
* Vue maintains two pointers on every vnode that delimit its DOM output:
*
* - `subTree.el` — the first DOM node this component rendered
* - `subTree.anchor` — the last DOM node (only set on Fragment vnodes,
* i.e. multi-root components)
*
* Vue guarantees a fragment's children are one contiguous run of siblings
* between `el` and `anchor` (its own patch/move logic relies on this), so
* walking `nextSibling` from `el` to `anchor` covers exactly this
* component's top-level DOM — never nodes before or after it.
*
* Shapes this produces:
*
* - Single root (`
`): `anchor` is absent, falls back to `el`, the
* loop stops after one node.
* - Multi-root fragment (``): every top-level sibling in the
* `el → anchor` range is collected (they get suffixed names later,
* mirroring React's `name-a`/`name-b` behavior).
* - Root is a child component: the child vnode's `el` is its own root DOM
* node, so we land on the "nearest host node" without recursing into the
* child's internals (we follow `nextSibling`, never `childNodes`).
* - `` content renders elsewhere in the document, so it is
* structurally outside the `el → anchor` range and never collected.
* - Text and comment nodes (Vue uses comments as `v-if` placeholders) are
* filtered out by the `nodeType === 1` check.
*/
function getRootElements(instance) {
const subTree = instance.subTree;
if (!subTree || !subTree.el) return [];
const elements = [];
let node = subTree.el;
const anchor = subTree.anchor ?? subTree.el;
const parent = subTree.el.parentNode;
while (node) {
if (node.nodeType === 1) elements.push(node);
if (node === anchor) break;
node = node.nextSibling;
/**
* Defensive bound: `el` and `anchor` must live in the same parent.
* If `anchor` were ever unreachable as a following sibling (a broken
* fragment invariant or a mid-patch intermediate state), stop instead
* of walking past the component's range into unrelated siblings.
*/
if (node && node.parentNode !== parent) break;
}
return elements;
}
/**
* Register the current `` instance with the view transition
* registry and wire up lifecycle-based participation detection.
*
* Names are applied lazily: `startTransition` applies layer names on every
* mounted instance right before the DOM update, and entering instances apply
* their own on mount. Outside a transition no `view-transition-name` is
* written, so idle instances never interfere with other transitions.
*/
function useAnimateView(props) {
const instance = getCurrentInstance();
const uid = `motion-view-${++uidCounter}`;
let layerNames = [];
let layerElements = [];
function computeLayerNames(count) {
const name = props.name ?? uid;
if (count <= 1) return [name];
return Array.from({ length: count }, (_, i) => `${name}-${String.fromCharCode(SUFFIX_A + i)}`);
}
function applyLayerNames() {
removeLayerNames();
layerElements = getRootElements(instance);
layerNames = computeLayerNames(layerElements.length);
layerElements.forEach((el, i) => {
el.style.setProperty("view-transition-name", layerNames[i]);
});
}
function removeLayerNames() {
for (const el of layerElements) el.style.removeProperty("view-transition-name");
layerElements = [];
layerNames = [];
}
/**
* Spread, don't enumerate: any prop added to `AnimateViewProps` later is
* captured automatically, so exit-time snapshots can't silently drop it.
*/
function getPropsSnapshot() {
return { ...props };
}
const handle = {
uid,
get layerNames() {
return layerNames;
},
applyLayerNames,
removeLayerNames,
getProps: getPropsSnapshot,
getShareName: () => props.name
};
onMounted(() => {
injectViewTransitionResetStyles();
registerAnimateView(handle);
if (getActiveViewTransition()) {
applyLayerNames();
recordParticipant("enter", handle);
}
});
onUpdated(() => {
if (!getActiveViewTransition()) return;
applyLayerNames();
recordParticipant("update", handle);
});
onBeforeUnmount(() => {
if (getActiveViewTransition()) recordParticipant("exit", handle);
});
onUnmounted(() => {
unregisterAnimateView(handle);
cancelViewTransitionAnimation(handle);
removeLayerNames();
});
}
//#endregion
export { useAnimateView };