import { resolveElements } from '../../utils/resolve-elements.mjs'; let nameCount = 0; /** * Generated names live in their own namespace so we can tell a name we own * (and must clean up) from an author-defined one - and so a stale generated * name left behind by an interrupted transition is re-owned, not mistaken for * the author's and leaked. */ const generatedName = () => `motion-view-${nameCount++}`; const isGeneratedName = (name) => name.startsWith("motion-view-"); /** * Tag a captured element with a `view-transition-class` so authors can target * its generated layer from CSS (e.g. `::view-transition-group(.hero)`) without * the opaque generated name. Tracked in `classed` - separate from the generated * names in `assigned` - so cleanup removes the class without ever stripping an * author's own inline `view-transition-name`. */ function tagClass(element, className, classed) { if (!className) return; element.style?.setProperty("view-transition-class", className); classed.push(element); } /** * Set the element's `view-transition-group` so its layer reconstructs the DOM * hierarchy in the pseudo-tree (`contain`) - or stays flat (`none`). Tracked in * `grouped` for cleanup. When the element clips (any non-`visible` overflow) its * name is recorded in `clipChildren` so the caller can clip the nested children * (`::view-transition-group-children(name)`), mirroring the live clip through * the whole transition rather than only at the live-DOM handoff. * * Ignored by browsers without nested view-transition groups, where it harmlessly * degrades to the flat default. */ function applyGroup(element, name, group, grouped, clipChildren) { if (!group) return; element.style?.setProperty("view-transition-group", group); grouped.push(element); if (group !== "none" && clipChildren) { const style = getComputedStyle(element); if (style.overflowX !== "visible" || style.overflowY !== "visible") { clipChildren.add(name); } } } /** * Resolve a selector/Element to elements and ensure each one carries a * `view-transition-name` we can target from script. * * Author-defined names are reused as-is. Elements that are unnamed (or use * the browser's `auto`/`match-element`, whose generated name is not exposed * to script) are given a unique generated name, set inline so it's captured, * and tracked in `assigned` for later cleanup. * * `registry` maps each Element to its name so the same element keeps its name * across both captures (before and after the update), which is what allows a * persistent element to animate as a single `group` layer. */ function assignViewTransitionNames(definition, registry, assigned, forcedNames, className, classed = [], group, grouped = [], clipChildren) { const elements = resolveElements(definition); /** * The new end of a paired morph: give each element the matching name from * the old end (by index) so the two share one layer and morph. If the new * end resolves to *more* elements than the old end named, the extras have no * counterpart - give them a fresh name so they animate as newcomers rather * than being silently left unnamed. We return the names actually assigned * (sized to the resolved elements), not the raw `forcedNames`, so stagger * totals and the layer set stay in step with what's on the page. */ if (forcedNames) { return elements.map((element, i) => { const existing = registry.get(element); if (existing) return existing; const name = forcedNames[i] ?? generatedName(); element.style?.setProperty("view-transition-name", name); assigned.push(element); registry.set(element, name); tagClass(element, className, classed); applyGroup(element, name, group, grouped, clipChildren); return name; }); } /** * Read every current name up front, before assigning any. Interleaving the * reads with the inline `setProperty` writes below would dirty styles * between reads and force a style recalc per element; batching the reads * keeps it to one. Elements already in the registry keep their name and * need no read. */ const currentNames = elements.map((element) => registry.has(element) ? undefined : getComputedStyle(element).getPropertyValue("view-transition-name")); return elements.map((element, i) => { const existing = registry.get(element); if (existing) return existing; const current = currentNames[i]; let name; if (current && current !== "none" && current !== "auto" && current !== "match-element" && !isGeneratedName(current)) { /** * The author already named this layer - target it as-is and leave * it to them to clean up. `auto`/`match-element` are overridden * because their generated name is not exposed to script, and a * stale `motion-view-*` (e.g. left by an interrupted transition) is * re-owned rather than adopted as an author name and leaked. */ name = current; } else { name = generatedName(); element.style?.setProperty("view-transition-name", name); assigned.push(element); } registry.set(element, name); tagClass(element, className, classed); applyGroup(element, name, group, grouped, clipChildren); return name; }); } /** * Remove the `view-transition-name`s we generated and the * `view-transition-class`es we applied. Author-defined names are never touched * (they're not in `assigned`). Safe to call more than once (e.g. on both a * finished and an interrupted transition). */ function releaseViewTransitionNames(assigned, classed = [], grouped = []) { for (const element of assigned) { element.style?.removeProperty("view-transition-name"); } for (const element of classed) { element.style?.removeProperty("view-transition-class"); } for (const element of grouped) { element.style?.removeProperty("view-transition-group"); } } export { assignViewTransitionNames, releaseViewTransitionNames }; //# sourceMappingURL=assign-names.mjs.map