neko-vue 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,262 @@
1
+ import { n as NekoStartCorner } from "./nekoPlacement-fXmlU6ys.mjs";
2
+ import { c as NekoOptions, s as NekoInstance, t as BehaviorMode } from "./index-BjAaI8iZ.mjs";
3
+ import * as _$vue from "vue";
4
+ import { MaybeRefOrGetter, PropType } from "vue";
5
+
6
+ //#region src/vue/useNeko.d.ts
7
+ /** Whether the pet chases the pointer or stays at the resolved start position. */
8
+ type NekoFollowMode = "follow" | "rest";
9
+ /**
10
+ * Options for {@link useNeko}. Includes all {@link NekoOptions} fields plus placement and
11
+ * wrapper-only flags. Omitted fields are left unset so engine defaults apply where applicable.
12
+ *
13
+ * **`behaviorMode`** is read for the **first** create and when leaving the pet-interaction gate; it
14
+ * is **not** part of the recreate fingerprint. Live mode changes come from **clicking the pet**.
15
+ * If you wrap options in **`computed(() => ({ … }))`** and include `behaviorMode`, changing it still
16
+ * invalidates that computed and may re-run internal watchers — use **`reactive`** for the options
17
+ * object if you need to mutate `behaviorMode` without that effect.
18
+ */
19
+ type UseNekoOptions = NekoOptions & {
20
+ /**
21
+ * When true (default), run the animation loop after `createNeko`.
22
+ * The engine’s `createNeko` always calls `start()` internally; when this is false or `mode` is `rest`,
23
+ * we call `stop()` immediately after creation.
24
+ */
25
+ autoStart?: boolean;
26
+ /**
27
+ * When true (default), do not load or create the pet if the user prefers reduced motion
28
+ * (see {@link prefersReducedMotion}). Set false to always run (e.g. user override).
29
+ */
30
+ respectReducedMotion?: boolean; /** Place the pet at a viewport corner unless `startX` / `startY` override that axis. */
31
+ startCorner?: NekoStartCorner;
32
+ /**
33
+ * Use this element’s top-left (viewport) for `startX`/`startY` when those are omitted.
34
+ * When set, **`createNeko` is deferred** until the element is non-null and
35
+ * `getBoundingClientRect()` has **positive width and height** (avoids a bogus spawn while the anchor is still mounting or collapsed).
36
+ */
37
+ anchorRef?: MaybeRefOrGetter<HTMLElement | null | undefined>;
38
+ /**
39
+ * `document.querySelector` (client only). Prefer `anchorRef` in `setup`.
40
+ * Uses the same **layout gate** as `anchorRef`: no create until a match exists with non-zero size.
41
+ */
42
+ anchorSelector?: string; /** `follow` = chase; `rest` = remain at resolved home position (animation loop stopped). */
43
+ mode?: MaybeRefOrGetter<NekoFollowMode>; /** When true, logs placement and lifecycle to the console with prefix `[neko-vue]`. */
44
+ debug?: boolean;
45
+ /**
46
+ * When true, the pet starts **still** (`rest`) with a one-time setup so the **first** pointer down
47
+ * on the sprite wakes **follow** (chase) **without** consuming the built-in click-to-cycle step.
48
+ * Further pointer downs use the full click cycle (through stay still and return home & stay).
49
+ *
50
+ * While waiting for that first interaction, `behaviorMode` is forced to {@link BehaviorMode.StayStill} so the
51
+ * engine idles even if a frame runs before the wrapper calls `stop()`; we intercept the first pointer instead.
52
+ * `allowBehaviorChange` is forced **true** during the wait so the sprite stays clickable.
53
+ */
54
+ restUntilFirstPetInteraction?: boolean;
55
+ };
56
+ /**
57
+ * Create a pet instance on mount and destroy it on unmount.
58
+ * Options may be reactive; when they change, the previous instance is destroyed and a new one is created.
59
+ *
60
+ * **Viewport-fixed pet:** the sprite lives on `document`, not under your component tree. This composable
61
+ * only manages **lifecycle** and **placement inputs** (`startX`/`startY`, corners, anchor); it does not
62
+ * reparent the engine’s DOM without upstream support.
63
+ *
64
+ * The engine’s `createNeko` always calls `start()`; this composable may call `stop()` right after to honor
65
+ * `autoStart`, `mode: 'rest'`, or both.
66
+ */
67
+ declare function useNeko(options?: MaybeRefOrGetter<UseNekoOptions | undefined>): {
68
+ /** Live `NekoInstance` after load and `createNeko`, or null before ready / after destroy. */instance: _$vue.ShallowRef<NekoInstance | null, NekoInstance | null>; /** Set when runtime load or `createNeko` fails; cleared on successful recreate. */
69
+ error: _$vue.Ref<Error | null, Error | null>; /** True once `createNeko` has run successfully for the current mount cycle. */
70
+ isReady: _$vue.Ref<boolean, boolean>; /** True when the pet was skipped because `respectReducedMotion` matched user preference. */
71
+ skippedForReducedMotion: _$vue.Ref<boolean, boolean>; /** Current `follow` / `rest` mode (readonly); drive with {@link setMode} or a reactive `mode` in options. */
72
+ mode: Readonly<_$vue.Ref<NekoFollowMode, NekoFollowMode>>; /** True after the first pet pointer-down when `restUntilFirstPetInteraction` is enabled. */
73
+ petInteractionAwake: Readonly<_$vue.Ref<boolean, boolean>>; /** Imperatively set `follow` or `rest` (updates internal `mode` ref used by the pet). */
74
+ setMode: (m: NekoFollowMode) => void;
75
+ restAtOrigin: () => void;
76
+ resumeFollow: () => void; /** Stop, destroy the instance, and clear ready state (does not tear down composable watchers). */
77
+ destroy: () => void;
78
+ };
79
+ //#endregion
80
+ //#region src/vue/NekoPet.d.ts
81
+ /**
82
+ * Mounts the desktop pet on the client. Renders a minimal hidden root node for Vue; the engine draws
83
+ * **viewport-fixed** on `document` — not as a child of your layout. Placement is **lifecycle + options**
84
+ * (`startX` / `startY`, `startCorner`, `anchorRef` / `anchorSelector`); a true DOM parent for the sprite
85
+ * would require upstream engine changes.
86
+ */
87
+ declare const _default: _$vue.DefineComponent<_$vue.ExtractPropTypes<{
88
+ speed: NumberConstructor;
89
+ fps: NumberConstructor;
90
+ /**
91
+ * Initial {@link BehaviorMode} at create time. Clicks on the pet change the live mode when
92
+ * {@link allowBehaviorChange} is true; updating this prop does not recreate or override the
93
+ * current mode.
94
+ */
95
+ behaviorMode: PropType<BehaviorMode | undefined>;
96
+ /**
97
+ * Distance (px) at which the pet counts as idle for behavior logic (engine default: 6).
98
+ */
99
+ idleThreshold: NumberConstructor;
100
+ /**
101
+ * Chase mode: stay at least this many pixels from the pointer (omit or 0 for default snap-to-cursor).
102
+ */
103
+ cursorStandoffPx: NumberConstructor;
104
+ /**
105
+ * When clicking the pet may cycle {@link BehaviorMode}. Omit this prop to use the engine default
106
+ * (`true`). A plain optional boolean prop would be `false` when absent; here `default: undefined`
107
+ * so `createNeko` omits the field and engine defaults apply.
108
+ */
109
+ allowBehaviorChange: {
110
+ type: BooleanConstructor;
111
+ default: undefined;
112
+ };
113
+ /**
114
+ * Pet-click mode order when {@link allowBehaviorChange} is true. Omit for the bundled default
115
+ * seven-step cycle.
116
+ */
117
+ behaviorCycle: PropType<BehaviorMode[] | undefined>; /** Initial X in viewport pixels. `0` is valid; the engine treats omitted as `0`. */
118
+ startX: NumberConstructor; /** Initial Y in viewport pixels. `0` is valid; the engine treats omitted as `0`. */
119
+ startY: NumberConstructor;
120
+ /**
121
+ * When true (default), keep the animation loop running after `createNeko`. The engine always calls
122
+ * `start()` inside `createNeko`; when false or when {@link mode} is `rest`, the wrapper calls
123
+ * `stop()` right after creation.
124
+ */
125
+ autoStart: {
126
+ type: BooleanConstructor;
127
+ default: boolean;
128
+ };
129
+ /**
130
+ * When true (default), skip loading and creating the pet if the user prefers reduced motion
131
+ * (see {@link prefersReducedMotion}). Set false to always run (e.g. after explicit user consent).
132
+ */
133
+ respectReducedMotion: {
134
+ type: BooleanConstructor;
135
+ default: boolean;
136
+ };
137
+ /**
138
+ * Place the pet at a viewport corner for any axis where {@link startX} / {@link startY} are omitted.
139
+ */
140
+ startCorner: PropType<NekoStartCorner | undefined>;
141
+ /**
142
+ * `document.querySelector` for an anchor element (client only). When set, `startX`/`startY` for
143
+ * omitted axes use the element’s top-left in viewport space. `createNeko` waits until a match exists
144
+ * with positive width and height. Prefer {@link useNeko}'s `anchorRef` in script when you have a ref.
145
+ */
146
+ anchorSelector: StringConstructor;
147
+ /**
148
+ * `follow` — chase / run behaviors (loop running). `rest` — stay at the resolved home position
149
+ * (loop stopped after each create).
150
+ */
151
+ mode: {
152
+ type: PropType<NekoFollowMode>;
153
+ default: string;
154
+ };
155
+ /**
156
+ * When true, start in `rest`; the first pointer down on the sprite switches to `follow` without
157
+ * consuming the engine’s first click-to-cycle step. Later clicks use the full cycle (including stay still and return home).
158
+ * While waiting, {@link behaviorMode} is forced to {@link BehaviorMode.StayStill} and
159
+ * {@link allowBehaviorChange} to true so the sprite stays clickable.
160
+ */
161
+ restUntilFirstPetInteraction: {
162
+ type: BooleanConstructor;
163
+ default: undefined;
164
+ }; /** Log placement and recreate steps to the console with prefix `[neko-vue]`. */
165
+ debug: {
166
+ type: BooleanConstructor;
167
+ default: boolean;
168
+ };
169
+ }>, () => _$vue.VNode<_$vue.RendererNode, _$vue.RendererElement, {
170
+ [key: string]: any;
171
+ }>, {}, {}, {}, _$vue.ComponentOptionsMixin, _$vue.ComponentOptionsMixin, {}, string, _$vue.PublicProps, Readonly<_$vue.ExtractPropTypes<{
172
+ speed: NumberConstructor;
173
+ fps: NumberConstructor;
174
+ /**
175
+ * Initial {@link BehaviorMode} at create time. Clicks on the pet change the live mode when
176
+ * {@link allowBehaviorChange} is true; updating this prop does not recreate or override the
177
+ * current mode.
178
+ */
179
+ behaviorMode: PropType<BehaviorMode | undefined>;
180
+ /**
181
+ * Distance (px) at which the pet counts as idle for behavior logic (engine default: 6).
182
+ */
183
+ idleThreshold: NumberConstructor;
184
+ /**
185
+ * Chase mode: stay at least this many pixels from the pointer (omit or 0 for default snap-to-cursor).
186
+ */
187
+ cursorStandoffPx: NumberConstructor;
188
+ /**
189
+ * When clicking the pet may cycle {@link BehaviorMode}. Omit this prop to use the engine default
190
+ * (`true`). A plain optional boolean prop would be `false` when absent; here `default: undefined`
191
+ * so `createNeko` omits the field and engine defaults apply.
192
+ */
193
+ allowBehaviorChange: {
194
+ type: BooleanConstructor;
195
+ default: undefined;
196
+ };
197
+ /**
198
+ * Pet-click mode order when {@link allowBehaviorChange} is true. Omit for the bundled default
199
+ * seven-step cycle.
200
+ */
201
+ behaviorCycle: PropType<BehaviorMode[] | undefined>; /** Initial X in viewport pixels. `0` is valid; the engine treats omitted as `0`. */
202
+ startX: NumberConstructor; /** Initial Y in viewport pixels. `0` is valid; the engine treats omitted as `0`. */
203
+ startY: NumberConstructor;
204
+ /**
205
+ * When true (default), keep the animation loop running after `createNeko`. The engine always calls
206
+ * `start()` inside `createNeko`; when false or when {@link mode} is `rest`, the wrapper calls
207
+ * `stop()` right after creation.
208
+ */
209
+ autoStart: {
210
+ type: BooleanConstructor;
211
+ default: boolean;
212
+ };
213
+ /**
214
+ * When true (default), skip loading and creating the pet if the user prefers reduced motion
215
+ * (see {@link prefersReducedMotion}). Set false to always run (e.g. after explicit user consent).
216
+ */
217
+ respectReducedMotion: {
218
+ type: BooleanConstructor;
219
+ default: boolean;
220
+ };
221
+ /**
222
+ * Place the pet at a viewport corner for any axis where {@link startX} / {@link startY} are omitted.
223
+ */
224
+ startCorner: PropType<NekoStartCorner | undefined>;
225
+ /**
226
+ * `document.querySelector` for an anchor element (client only). When set, `startX`/`startY` for
227
+ * omitted axes use the element’s top-left in viewport space. `createNeko` waits until a match exists
228
+ * with positive width and height. Prefer {@link useNeko}'s `anchorRef` in script when you have a ref.
229
+ */
230
+ anchorSelector: StringConstructor;
231
+ /**
232
+ * `follow` — chase / run behaviors (loop running). `rest` — stay at the resolved home position
233
+ * (loop stopped after each create).
234
+ */
235
+ mode: {
236
+ type: PropType<NekoFollowMode>;
237
+ default: string;
238
+ };
239
+ /**
240
+ * When true, start in `rest`; the first pointer down on the sprite switches to `follow` without
241
+ * consuming the engine’s first click-to-cycle step. Later clicks use the full cycle (including stay still and return home).
242
+ * While waiting, {@link behaviorMode} is forced to {@link BehaviorMode.StayStill} and
243
+ * {@link allowBehaviorChange} to true so the sprite stays clickable.
244
+ */
245
+ restUntilFirstPetInteraction: {
246
+ type: BooleanConstructor;
247
+ default: undefined;
248
+ }; /** Log placement and recreate steps to the console with prefix `[neko-vue]`. */
249
+ debug: {
250
+ type: BooleanConstructor;
251
+ default: boolean;
252
+ };
253
+ }>> & Readonly<{}>, {
254
+ allowBehaviorChange: boolean;
255
+ autoStart: boolean;
256
+ respectReducedMotion: boolean;
257
+ mode: NekoFollowMode;
258
+ restUntilFirstPetInteraction: boolean;
259
+ debug: boolean;
260
+ }, {}, {}, {}, string, _$vue.ComponentProvideOptions, true, {}, any>;
261
+ //#endregion
262
+ export { useNeko as i, NekoFollowMode as n, UseNekoOptions as r, _default as t };
@@ -0,0 +1,402 @@
1
+ import { t as BehaviorMode } from "./types-Ctrldouo.mjs";
2
+ import { n as resolveStartPosition, r as nekoVueDebug } from "./nekoPlacement-DUdnhZoX.mjs";
3
+ import { t as loadNekoRuntime } from "./loadNekoRuntime-CskWI70T.mjs";
4
+ import { computed, defineComponent, h, onBeforeUnmount, onMounted, readonly, ref, shallowRef, toValue, watch, watchEffect } from "vue";
5
+ //#region src/utils/prefersReducedMotion.ts
6
+ /**
7
+ * True when the user has requested less motion (OS / browser setting).
8
+ * Safe on SSR (returns false) and when `matchMedia` is missing.
9
+ */
10
+ function prefersReducedMotion() {
11
+ if (typeof window === "undefined") return false;
12
+ try {
13
+ return window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches === true;
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+ //#endregion
19
+ //#region src/vue/useNeko.ts
20
+ function pickNekoOptions(o) {
21
+ const { autoStart: _a, respectReducedMotion: _r, startCorner: _c, anchorRef: _ar, anchorSelector: _as, mode: _m, debug: _d, restUntilFirstPetInteraction: _wake, ...neko } = o;
22
+ return neko;
23
+ }
24
+ function resolveAnchorElement(o) {
25
+ if (o.anchorRef != null) {
26
+ const el = toValue(o.anchorRef);
27
+ if (el) return el;
28
+ }
29
+ if (o.anchorSelector && typeof document !== "undefined") return document.querySelector(o.anchorSelector);
30
+ return null;
31
+ }
32
+ /** User configured an anchor (ref or selector) — wait for a real, non-collapsed box before `createNeko`. */
33
+ function wantsAnchorBinding(o) {
34
+ return o.anchorRef != null || Boolean(o.anchorSelector);
35
+ }
36
+ function isAnchorLaidOutForCreate(anchorEl) {
37
+ if (!anchorEl) return false;
38
+ const r = anchorEl.getBoundingClientRect();
39
+ const w = r.width > 0 ? r.width : anchorEl.offsetWidth;
40
+ const h = r.height > 0 ? r.height : anchorEl.offsetHeight;
41
+ return w > 0 && h > 0;
42
+ }
43
+ function buildPlacementInput(o) {
44
+ return {
45
+ startX: o.startX,
46
+ startY: o.startY,
47
+ startCorner: o.startCorner,
48
+ anchorElement: resolveAnchorElement(o)
49
+ };
50
+ }
51
+ /** The engine treats a missing key as its default; strip `undefined` so we never override with implicit Vue prop values. */
52
+ function omitUndefinedNekoFields(opts) {
53
+ return Object.fromEntries(Object.entries(opts).filter(([, v]) => v !== void 0));
54
+ }
55
+ function buildCreateOptions(o, petGateClosed) {
56
+ const debug = o.debug === true;
57
+ const base = pickNekoOptions(o);
58
+ const { startX, startY } = resolveStartPosition(buildPlacementInput(o), debug);
59
+ let merged = {
60
+ ...base,
61
+ startX,
62
+ startY
63
+ };
64
+ if (petGateClosed && o.restUntilFirstPetInteraction) merged = {
65
+ ...merged,
66
+ behaviorMode: BehaviorMode.StayStill,
67
+ allowBehaviorChange: true
68
+ };
69
+ return omitUndefinedNekoFields(merged);
70
+ }
71
+ function readInstanceBehaviorMode(inst) {
72
+ if (!inst) return;
73
+ const v = inst.behaviorMode;
74
+ return typeof v === "number" ? v : void 0;
75
+ }
76
+ /**
77
+ * `behaviorMode` in options is only for **initial** create; reactive changes must not recreate.
78
+ * On recreate (anchor/layout/mode…), keep the live engine mode unless we just left the
79
+ * `restUntilFirstPetInteraction` gate (then apply the parent’s initial mode again).
80
+ */
81
+ function applyBehaviorModeForRecreate(nekoOpts, raw, petGateClosed, priorBm, leavingPetGate) {
82
+ if (petGateClosed && raw.restUntilFirstPetInteraction) return;
83
+ if (leavingPetGate) {
84
+ if (raw.behaviorMode !== void 0) nekoOpts.behaviorMode = raw.behaviorMode;
85
+ else delete nekoOpts.behaviorMode;
86
+ return;
87
+ }
88
+ if (priorBm !== void 0) nekoOpts.behaviorMode = priorBm;
89
+ }
90
+ function anchorElementRectKey(el) {
91
+ const r = el.getBoundingClientRect();
92
+ return [
93
+ r.left,
94
+ r.top,
95
+ r.width,
96
+ r.height
97
+ ].map((n) => Math.round(n * 100) / 100).join(",");
98
+ }
99
+ /**
100
+ * Create a pet instance on mount and destroy it on unmount.
101
+ * Options may be reactive; when they change, the previous instance is destroyed and a new one is created.
102
+ *
103
+ * **Viewport-fixed pet:** the sprite lives on `document`, not under your component tree. This composable
104
+ * only manages **lifecycle** and **placement inputs** (`startX`/`startY`, corners, anchor); it does not
105
+ * reparent the engine’s DOM without upstream support.
106
+ *
107
+ * The engine’s `createNeko` always calls `start()`; this composable may call `stop()` right after to honor
108
+ * `autoStart`, `mode: 'rest'`, or both.
109
+ */
110
+ function useNeko(options = {}) {
111
+ const instance = shallowRef(null);
112
+ const error = ref(null);
113
+ const isReady = ref(false);
114
+ const skippedForReducedMotion = ref(false);
115
+ const modeRef = ref("follow");
116
+ const anchorLayoutTick = ref(0);
117
+ /** After first pet pointer-down when `restUntilFirstPetInteraction` is enabled. */
118
+ const petInteractionAwake = ref(false);
119
+ /** True while the live instance was created under the pet-interaction gate (ball-chase wait). */
120
+ const petGateSpawnedInstance = ref(false);
121
+ let stopModeWatch;
122
+ let stopMainWatch;
123
+ let stopWakeOptionWatch;
124
+ let mountGen = 0;
125
+ onMounted(() => {
126
+ stopModeWatch = watch(() => toValue(options)?.mode, (m) => {
127
+ if (m !== void 0) modeRef.value = toValue(m);
128
+ }, { immediate: true });
129
+ stopWakeOptionWatch = watch(() => toValue(options)?.restUntilFirstPetInteraction === true, (enabled) => {
130
+ if (!enabled) petInteractionAwake.value = false;
131
+ });
132
+ watchEffect((onCleanup) => {
133
+ if ((toValue(options) ?? {}).restUntilFirstPetInteraction !== true || petInteractionAwake.value) return;
134
+ if (typeof document === "undefined") return;
135
+ const onPointerDown = (e) => {
136
+ const pet = document.querySelector(".neko");
137
+ const t = e.target;
138
+ if (!pet || !(t instanceof Node) || !(pet === t || pet.contains(t))) return;
139
+ e.preventDefault();
140
+ e.stopImmediatePropagation();
141
+ petInteractionAwake.value = true;
142
+ };
143
+ document.addEventListener("mousedown", onPointerDown, true);
144
+ onCleanup(() => {
145
+ document.removeEventListener("mousedown", onPointerDown, true);
146
+ });
147
+ });
148
+ watchEffect((onCleanup) => {
149
+ const raw = toValue(options) ?? {};
150
+ const el = raw.anchorRef != null ? toValue(raw.anchorRef) : null;
151
+ if (!el || typeof ResizeObserver === "undefined") return;
152
+ let lastRectKey = anchorElementRectKey(el);
153
+ const ro = new ResizeObserver(() => {
154
+ const next = anchorElementRectKey(el);
155
+ if (next === lastRectKey) return;
156
+ lastRectKey = next;
157
+ anchorLayoutTick.value++;
158
+ });
159
+ ro.observe(el);
160
+ onCleanup(() => {
161
+ ro.disconnect();
162
+ });
163
+ });
164
+ stopMainWatch = watch(() => {
165
+ const raw = toValue(options) ?? {};
166
+ const rect = resolveAnchorElement(raw)?.getBoundingClientRect();
167
+ return {
168
+ mode: modeRef.value,
169
+ anchorTick: anchorLayoutTick.value,
170
+ anchorRect: rect ? {
171
+ x: rect.x,
172
+ y: rect.y,
173
+ w: rect.width,
174
+ h: rect.height
175
+ } : null,
176
+ petInteractionAwake: petInteractionAwake.value,
177
+ restUntilFirstPetInteraction: raw.restUntilFirstPetInteraction === true,
178
+ speed: raw.speed,
179
+ fps: raw.fps,
180
+ idleThreshold: raw.idleThreshold,
181
+ cursorStandoffPx: raw.cursorStandoffPx,
182
+ behaviorCycle: JSON.stringify(raw.behaviorCycle ?? null),
183
+ allowBehaviorChange: raw.allowBehaviorChange,
184
+ startCorner: raw.startCorner,
185
+ startX: raw.startX,
186
+ startY: raw.startY,
187
+ autoStart: raw.autoStart,
188
+ respectReducedMotion: raw.respectReducedMotion,
189
+ anchorSelector: raw.anchorSelector,
190
+ debug: raw.debug
191
+ };
192
+ }, async () => {
193
+ const gen = ++mountGen;
194
+ const raw = toValue(options) ?? {};
195
+ const debug = raw.debug === true;
196
+ nekoVueDebug(debug, "recreate:enter", {
197
+ gen,
198
+ modeRef: modeRef.value,
199
+ optionsMode: raw.mode !== void 0 ? toValue(raw.mode) : void 0,
200
+ clientWidth: typeof document !== "undefined" ? document.documentElement.clientWidth : null,
201
+ innerHeight: typeof window !== "undefined" ? window.innerHeight : null
202
+ });
203
+ error.value = null;
204
+ isReady.value = false;
205
+ const priorBm = readInstanceBehaviorMode(instance.value);
206
+ const wasPetGateInstance = petGateSpawnedInstance.value;
207
+ const petGateClosed = raw.restUntilFirstPetInteraction === true && !petInteractionAwake.value;
208
+ const leavingPetGate = wasPetGateInstance && petInteractionAwake.value && priorBm === BehaviorMode.StayStill && !petGateClosed;
209
+ if (instance.value) {
210
+ instance.value.stop();
211
+ instance.value.destroy();
212
+ instance.value = null;
213
+ }
214
+ const { autoStart = true, respectReducedMotion: respectMotion = true } = raw;
215
+ if (respectMotion && prefersReducedMotion()) {
216
+ if (gen !== mountGen) return;
217
+ skippedForReducedMotion.value = true;
218
+ error.value = null;
219
+ return;
220
+ }
221
+ skippedForReducedMotion.value = false;
222
+ if (wantsAnchorBinding(raw)) {
223
+ const anchorEl = resolveAnchorElement(raw);
224
+ if (!isAnchorLaidOutForCreate(anchorEl)) {
225
+ const r = anchorEl?.getBoundingClientRect();
226
+ nekoVueDebug(debug, "recreate:defer anchor layout", {
227
+ hasEl: Boolean(anchorEl),
228
+ width: r?.width ?? null,
229
+ height: r?.height ?? null
230
+ });
231
+ return;
232
+ }
233
+ }
234
+ const effectiveMode = petGateClosed ? "rest" : modeRef.value;
235
+ const nekoOpts = buildCreateOptions(raw, petGateClosed);
236
+ applyBehaviorModeForRecreate(nekoOpts, raw, petGateClosed, priorBm, leavingPetGate);
237
+ nekoVueDebug(debug, "recreate:createNeko options (payload)", {
238
+ ...nekoOpts,
239
+ _effectiveMode: effectiveMode,
240
+ _autoStart: autoStart,
241
+ _gen: gen,
242
+ _petGateClosed: petGateClosed
243
+ });
244
+ try {
245
+ const create = await loadNekoRuntime();
246
+ if (gen !== mountGen) {
247
+ const stray = create(nekoOpts);
248
+ stray.stop();
249
+ stray.destroy();
250
+ return;
251
+ }
252
+ const nekoInstance = create(nekoOpts);
253
+ if (gen !== mountGen) {
254
+ nekoInstance.stop();
255
+ nekoInstance.destroy();
256
+ return;
257
+ }
258
+ instance.value = nekoInstance;
259
+ const shouldRun = effectiveMode === "follow" && autoStart !== false;
260
+ if (shouldRun) nekoInstance.start();
261
+ else nekoInstance.stop();
262
+ nekoVueDebug(debug, "recreate:instance ready", {
263
+ shouldRun,
264
+ startX: nekoOpts.startX,
265
+ startY: nekoOpts.startY,
266
+ behaviorMode: nekoOpts.behaviorMode
267
+ });
268
+ isReady.value = true;
269
+ petGateSpawnedInstance.value = petGateClosed;
270
+ } catch (e) {
271
+ if (gen !== mountGen) return;
272
+ error.value = e instanceof Error ? e : new Error(String(e));
273
+ }
274
+ }, {
275
+ flush: "post",
276
+ immediate: true
277
+ });
278
+ });
279
+ onBeforeUnmount(() => {
280
+ stopModeWatch?.();
281
+ stopWakeOptionWatch?.();
282
+ stopMainWatch?.();
283
+ mountGen++;
284
+ if (instance.value) {
285
+ instance.value.stop();
286
+ instance.value.destroy();
287
+ instance.value = null;
288
+ }
289
+ isReady.value = false;
290
+ skippedForReducedMotion.value = false;
291
+ petInteractionAwake.value = false;
292
+ petGateSpawnedInstance.value = false;
293
+ });
294
+ function setMode(m) {
295
+ modeRef.value = m;
296
+ }
297
+ function destroy() {
298
+ if (instance.value) {
299
+ instance.value.stop();
300
+ instance.value.destroy();
301
+ instance.value = null;
302
+ }
303
+ isReady.value = false;
304
+ }
305
+ /**
306
+ * Sets `mode` to `rest` (stops at the resolved home position after the next internal cycle).
307
+ * If you pass a reactive `mode` in options, prefer updating that ref instead; it stays in sync via watch.
308
+ */
309
+ function restAtOrigin() {
310
+ setMode("rest");
311
+ }
312
+ /** Sets `mode` to `follow`. */
313
+ function resumeFollow() {
314
+ setMode("follow");
315
+ }
316
+ return {
317
+ instance,
318
+ error,
319
+ isReady,
320
+ skippedForReducedMotion,
321
+ mode: readonly(modeRef),
322
+ petInteractionAwake: readonly(petInteractionAwake),
323
+ setMode,
324
+ restAtOrigin,
325
+ resumeFollow,
326
+ destroy
327
+ };
328
+ }
329
+ //#endregion
330
+ //#region src/vue/NekoPet.ts
331
+ /**
332
+ * Mounts the desktop pet on the client. Renders a minimal hidden root node for Vue; the engine draws
333
+ * **viewport-fixed** on `document` — not as a child of your layout. Placement is **lifecycle + options**
334
+ * (`startX` / `startY`, `startCorner`, `anchorRef` / `anchorSelector`); a true DOM parent for the sprite
335
+ * would require upstream engine changes.
336
+ */
337
+ var NekoPet_default = defineComponent({
338
+ name: "NekoPet",
339
+ props: {
340
+ speed: Number,
341
+ fps: Number,
342
+ behaviorMode: Number,
343
+ idleThreshold: Number,
344
+ cursorStandoffPx: Number,
345
+ allowBehaviorChange: {
346
+ type: Boolean,
347
+ default: void 0
348
+ },
349
+ behaviorCycle: Array,
350
+ startX: Number,
351
+ startY: Number,
352
+ autoStart: {
353
+ type: Boolean,
354
+ default: true
355
+ },
356
+ respectReducedMotion: {
357
+ type: Boolean,
358
+ default: true
359
+ },
360
+ startCorner: String,
361
+ anchorSelector: String,
362
+ mode: {
363
+ type: String,
364
+ default: "follow"
365
+ },
366
+ restUntilFirstPetInteraction: {
367
+ type: Boolean,
368
+ default: void 0
369
+ },
370
+ debug: {
371
+ type: Boolean,
372
+ default: false
373
+ }
374
+ },
375
+ setup(props, { expose }) {
376
+ const { instance } = useNeko(computed(() => ({
377
+ speed: props.speed,
378
+ fps: props.fps,
379
+ behaviorMode: props.behaviorMode,
380
+ idleThreshold: props.idleThreshold,
381
+ cursorStandoffPx: props.cursorStandoffPx,
382
+ allowBehaviorChange: props.allowBehaviorChange,
383
+ behaviorCycle: props.behaviorCycle,
384
+ startX: props.startX,
385
+ startY: props.startY,
386
+ autoStart: props.autoStart,
387
+ respectReducedMotion: props.respectReducedMotion,
388
+ startCorner: props.startCorner,
389
+ anchorSelector: props.anchorSelector || void 0,
390
+ mode: props.mode,
391
+ restUntilFirstPetInteraction: props.restUntilFirstPetInteraction,
392
+ debug: props.debug
393
+ })));
394
+ expose({ instance });
395
+ return () => h("span", {
396
+ class: "neko-vue-root",
397
+ "aria-hidden": true
398
+ });
399
+ }
400
+ });
401
+ //#endregion
402
+ export { useNeko as n, prefersReducedMotion as r, NekoPet_default as t };