import { isSVGElement, resolveInitialValues } from "./utils.mjs"; import { isVariantLabel } from "motion-dom"; import { invariant } from "hey-listen"; //#region src/state/motion-state.ts var mountedStates = /* @__PURE__ */ new WeakMap(); /** * Core class that manages animation state and orchestrates animations. * Handles component lifecycle methods in the correct order based on component tree position. */ var MotionState = class { constructor(options, parent, bundle = {}) { this.element = null; this.isExiting = false; this.children = /* @__PURE__ */ new Set(); this.features = /* @__PURE__ */ new Map(); this.featureCtors = []; this._context = null; this.options = options; this.parent = parent; parent?.children?.add(this); this.latestValues = resolveInitialValues(options, this.context); this.type = isSVGElement(this.options.as) ? "svg" : "html"; this.setBundle(bundle); } get context() { if (!this._context) { const handler = { get: (_target, prop) => { const value = this.options[prop]; if (isVariantLabel(value) || prop === "initial" && value === false) return value; return this.parent?.context[prop]; } }; this._context = new Proxy({}, handler); } return this._context; } /** * Declare (or asynchronously resolve, e.g. LazyMotion) the render + feature * bundle for this state, then sync — the visual element and feature * instances are created lazily inside updateFeatures(). */ setBundle(bundle) { if (bundle.renderer) this.initVisualElement(bundle.renderer); if (bundle.features?.length) this.featureCtors = bundle.features; this.updateFeatures(); } /** * Instantiate any missing features, then mount unmounted ones and update * the rest. Feature constructors dereference the visual element, so this * no-ops until setBundle() has created one. Feature mount is isMount-guarded, * so this is safe to call repeatedly and from any lifecycle stage. */ updateFeatures() { if (!this.visualElement) return; for (const FeatureCtor of this.featureCtors) { let feature = this.features.get(FeatureCtor.key); if (!feature) { feature = new FeatureCtor(this); this.features.set(FeatureCtor.key, feature); } if (!this.isMounted()) continue; if (feature.isMount) feature.update(); else { feature.mount(); feature.isMount = true; } } } updateOptions(options) { this.options = options; this.visualElement?.update({ ...this.options, whileTap: this.options.whilePress }, this.options.presenceContext ?? null); } mount(element) { invariant(Boolean(element), "Animation state must be mounted with valid Element"); mountedStates.set(element, this); this.element = element; this.features.get("layout")?.refreshRootScroll(); this.visualElement?.mount(element); this.updateFeatures(); } beforeUnmount() { this.getSnapshot(false); } unmount() { this.parent?.children?.delete(this); if (this.element) mountedStates.delete(this.element); this.features.forEach((f) => { f.unmount(); f.isMount = false; }); this.visualElement?.unmount(); this.element = null; } beforeUpdate() { this.getSnapshot(!this.isExiting); } update() { this.updateFeatures(); this.didUpdate(); } /** Access an installed feature by key, e.g. getFeature('exit') */ getFeature(key) { return this.features.get(key); } setActive(name, isActive) { if (name === "exit" && isActive) this.isExiting = true; const type = name === "whilePress" ? "whileTap" : name; this.visualElement?.animationState?.setActive(type, isActive).then(() => { if (name === "exit" && isActive) this.isExiting = false; }); } isMounted() { return Boolean(this.element); } /** * Create and attach the visual element using the given renderer. * Called by setBundle(); no-ops once created. */ initVisualElement(renderer) { if (this.visualElement) return; this.visualElement = renderer(this.options.as, { presenceContext: this.options.presenceContext ?? null, parent: this.parent?.visualElement, props: { ...this.options, whileTap: this.options.whilePress }, visualState: { renderState: { transform: {}, transformOrigin: {}, style: {}, vars: {}, attrs: {} }, latestValues: { ...this.latestValues } }, reducedMotionConfig: this.options.motionConfig?.reducedMotion }); if (this.isMounted()) this.visualElement.mount(this.element); } getSnapshot(isPresent) { this.features.get("layout")?.getSnapshot(isPresent); } didUpdate() {} }; //#endregion export { MotionState, mountedStates };