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.
- package/CHANGELOG.md +17 -0
- package/LICENSE +27 -0
- package/README.md +526 -0
- package/dist/NekoPet-B9k9Bf1o.d.mts +262 -0
- package/dist/NekoPet-DMPjoHm0.mjs +402 -0
- package/dist/index-BjAaI8iZ.d.mts +128 -0
- package/dist/index.d.mts +17 -0
- package/dist/index.mjs +5 -0
- package/dist/loadNekoRuntime-CskWI70T.mjs +41 -0
- package/dist/loadNekoRuntime-DLd1lr8Y.d.mts +11 -0
- package/dist/nekoPlacement-DUdnhZoX.mjs +76 -0
- package/dist/nekoPlacement-fXmlU6ys.d.mts +23 -0
- package/dist/nekojsRuntime-DJI-YkCi.mjs +616 -0
- package/dist/placement.d.mts +2 -0
- package/dist/placement.mjs +2 -0
- package/dist/runtime.d.mts +2 -0
- package/dist/runtime.mjs +2 -0
- package/dist/types-Ctrldouo.mjs +50 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.mjs +2 -0
- package/dist/vue.d.mts +2 -0
- package/dist/vue.mjs +2 -0
- package/package.json +98 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-04-04
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Public API:** `useNeko`, `NekoPet`, `loadNekoRuntime`, placement helpers (`startCorner`, `anchorRef` / `anchorSelector`), and types (`BehaviorMode`, `DEFAULT_NEKO_BEHAVIOR_CYCLE`, etc.).
|
|
13
|
+
- **Bundled neko engine** (dynamic import chunk); `cursorStandoffPx`, `behaviorCycle`, `restUntilFirstPetInteraction`, `respectReducedMotion`, `mode` (`follow` / `rest`), and debug logging.
|
|
14
|
+
- **Export subpaths** (conditional `types` + `import`): `neko-vue/types`, `neko-vue/placement`, `neko-vue/runtime`, `neko-vue/vue` — for intent-specific imports alongside the root `neko-vue` barrel.
|
|
15
|
+
- **`engines.node`:** `>=24.0.0` (Active LTS).
|
|
16
|
+
|
|
17
|
+
[0.1.0]: https://github.com/AscaL/neko-vue/releases/tag/v0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 neko-vue contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
Third-party runtime: nekojs (https://github.com/louisabraham/nekojs) is
|
|
26
|
+
licensed under GNU General Public License v3.0. Using nekojs is subject to
|
|
27
|
+
that license.
|
package/README.md
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
# neko-vue
|
|
2
|
+
|
|
3
|
+
Vue 3 helpers for a classic viewport-fixed desktop pet: typed options, `loadNekoRuntime()`, `useNeko()`, and `NekoPet`. The default path uses programmatic `createNeko` only (no `data-autostart`).
|
|
4
|
+
|
|
5
|
+
The pet **engine always comes from this package** (a dynamically imported chunk in `dist/`). There is **no** supported option to load `createNeko` from a remote URL or a separate script tag—only the bundled runtime, unless you assign **`window.createNeko`** yourself first (advanced / tests).
|
|
6
|
+
|
|
7
|
+
**Peer dependency:** `vue` ^3.4 or ^3.5.
|
|
8
|
+
|
|
9
|
+
**Runtime:** Node.js **24+** (Active LTS line; see `engines` in `package.json`).
|
|
10
|
+
|
|
11
|
+
## What this package offers
|
|
12
|
+
|
|
13
|
+
| Capability | Details |
|
|
14
|
+
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| **Typed engine API** | `NekoOptions`, `NekoInstance`, `BehaviorMode`, `DEFAULT_NEKO_BEHAVIOR_CYCLE`, `BehaviorModes`, `NEKOJS_SPRITE_SIZE` — aligned with `createNeko`. |
|
|
16
|
+
| **Bundled runtime** | Typed engine ships in **`dist/`** (code-split chunk); **only** dynamic import from the package—no remote URL or `<script src>` loader. |
|
|
17
|
+
| **`<NekoPet />`** | Declarative component: speed, corner, anchor, mode (`follow` / `rest`), optional **`behavior-cycle`**, optional chase **`cursor-standoff-px`**, reduced motion, debug. |
|
|
18
|
+
| **`useNeko()`** | Same options as **`NekoPet`** (plus **`anchorRef`**): reactive **`instance` / `isReady` / `error`**, **`setMode`**, **`destroy`**, **`restUntilFirstPetInteraction`**, **`petInteractionAwake`**, **`behaviorCycle`**, **`cursorStandoffPx`**, etc. |
|
|
19
|
+
| **`loadNekoRuntime()`** | Advanced: `Promise<createNeko>` after bundled import, or reuse **`window.createNeko`** if already set; shared in-memory promise; SSR-safe error when misused. |
|
|
20
|
+
| **Placement helpers** | `startCorner`, `cornerToStartXY`, `resolveStartPosition` — viewport corners + anchor rects, `ResizeObserver` when anchored. |
|
|
21
|
+
| **Motion & a11y** | `prefersReducedMotion()`; composable and component skip the pet when user prefers reduced motion (with opt-out on `NekoPet`). |
|
|
22
|
+
| **Lifecycle** | Unmount → `stop()` + `destroy()`; bundled runtime removes listeners via **`AbortController`**. |
|
|
23
|
+
| **Debug** | `nekoVueDebug()` for optional placement / recreate logging. |
|
|
24
|
+
|
|
25
|
+
## Source layout (this repo)
|
|
26
|
+
|
|
27
|
+
Library code under **`src/`** is grouped by role (public API remains **`neko-vue`** → `dist/index.mjs` only):
|
|
28
|
+
|
|
29
|
+
| Folder | Role |
|
|
30
|
+
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
31
|
+
| **`src/types/`** | Shared TypeScript types and constants (`NekoOptions`, `BehaviorMode`, `DEFAULT_NEKO_BEHAVIOR_CYCLE`, `LoadNekoRuntimeOptions`, `Window` augmentation). |
|
|
32
|
+
| **`src/runtime/`** | Engine: `nekojsRuntime.ts`, `nekoSpritesData.ts`, `loadNekoRuntime.ts`. |
|
|
33
|
+
| **`src/vue/`** | `useNeko.ts`, `NekoPet.ts` (`defineComponent`, not an SFC). |
|
|
34
|
+
| **`src/placement/`** | Corner / anchor → `startX` / `startY` resolution. |
|
|
35
|
+
| **`src/utils/`** | `prefersReducedMotion`, `nekoVueDebug`. |
|
|
36
|
+
| **`src/index.ts`** | Public re-exports only. |
|
|
37
|
+
| **`src/env.d.ts`** | Vite / client typings. |
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install neko-vue vue
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Use the same idea with **pnpm**, **Yarn**, or **Bun** (for example `pnpm add neko-vue vue`). You must have **Vue 3.4+** or **3.5+** installed alongside `neko-vue`.
|
|
46
|
+
|
|
47
|
+
### Subpath imports
|
|
48
|
+
|
|
49
|
+
Besides the root entry (`neko-vue`), published builds expose **conditional exports** (`types` + `import`) for smaller, intent-specific entry edges:
|
|
50
|
+
|
|
51
|
+
| Import path | Contents |
|
|
52
|
+
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
53
|
+
| **`neko-vue`** | Full public API (root barrel). |
|
|
54
|
+
| **`neko-vue/types`** | `BehaviorMode`, `BehaviorModes`, `DEFAULT_NEKO_BEHAVIOR_CYCLE`, `NEKOJS_SPRITE_SIZE`, `NekoOptions`, `NekoInstance`, `CreateNekoFn`, `LoadNekoRuntimeOptions` — no Vue, no loader. |
|
|
55
|
+
| **`neko-vue/placement`** | `cornerToStartXY`, `resolveStartPosition`, `NekoStartCorner`, `NekoPlacementInput` — no Vue. |
|
|
56
|
+
| **`neko-vue/runtime`** | `loadNekoRuntime` only (pulls the bundled engine chunk when called). |
|
|
57
|
+
| **`neko-vue/vue`** | `useNeko`, `NekoPet`, `UseNekoOptions`, `NekoFollowMode` — **requires Vue**; still loads the engine when you create a pet. |
|
|
58
|
+
|
|
59
|
+
Your bundler decides how much code stays in the bundle; these subpaths make dependencies explicit and avoid importing the root barrel when you only need types or placement helpers.
|
|
60
|
+
|
|
61
|
+
## Practical usage guide
|
|
62
|
+
|
|
63
|
+
Use this section as the default path for integrating the pet. Deeper API notes follow under [Using in your project](#using-in-your-project).
|
|
64
|
+
|
|
65
|
+
### 1. Minimal setup (SPA)
|
|
66
|
+
|
|
67
|
+
The engine ships inside **`neko-vue`** (Vite/your bundler pulls a small runtime chunk). You do not add a CDN `<script>` for the pet. Mount **`NekoPet`** only in the browser (typical Vite/Vue SPA entry is already client-only):
|
|
68
|
+
|
|
69
|
+
```vue
|
|
70
|
+
<script setup lang="ts">
|
|
71
|
+
import { NekoPet } from "neko-vue";
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<template>
|
|
75
|
+
<NekoPet start-corner="bottom-right" />
|
|
76
|
+
</template>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
In templates, use **kebab-case** for multi-word props (for example `start-corner`, `behavior-mode`, `behavior-cycle`, `cursor-standoff-px`, `anchor-selector`, `respect-reduced-motion`, `rest-until-first-pet-interaction`).
|
|
80
|
+
|
|
81
|
+
### 2. Choose `<NekoPet>` or `useNeko()`
|
|
82
|
+
|
|
83
|
+
| Use | When |
|
|
84
|
+
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
85
|
+
| **`<NekoPet />`** | You are happy with props and do not need a handle from `setup`. Anchoring to another element is limited to **`anchor-selector`** (a CSS selector string). |
|
|
86
|
+
| **`useNeko()`** | You need **`anchorRef`** (template ref), **`instance` / `isReady` / `error`**, **`setMode`**, **`destroy`**, **`petInteractionAwake`**, or you want options built from **`computed()`**. |
|
|
87
|
+
|
|
88
|
+
`NekoPet` forwards props into `useNeko` internally; behavior is the same for shared options.
|
|
89
|
+
|
|
90
|
+
### 3. Start position (corner, coordinates, anchor)
|
|
91
|
+
|
|
92
|
+
- **Corner only:** `start-corner="bottom-right"` (also `top-left`, `top-right`, `bottom-left`). The pet snaps to that corner of the viewport minus the **32×32** sprite (`NEKOJS_SPRITE_SIZE`).
|
|
93
|
+
- **Mix corner + axis:** Set **`start-x`** or **`start-y`** for one axis and **`start-corner`** for the other; **`0`** is a valid coordinate.
|
|
94
|
+
- **Anchor to a DOM node:** In **`useNeko`**, pass **`anchorRef: useTemplateRef('box')`** (Vue 3.5+) or a plain **`ref`** to the element whose **top-left** should seed `startX`/`startY`. With **`NekoPet`**, pass **`anchor-selector`**. Creation waits until the element exists and has **non-zero** width and height.
|
|
95
|
+
|
|
96
|
+
Resolution per axis: **explicit `start-x` / `start-y` → anchor → corner → 0**.
|
|
97
|
+
|
|
98
|
+
### 4. `mode`: follow vs rest
|
|
99
|
+
|
|
100
|
+
- **`mode="follow"`** (default): chase and run behaviors; animation loop runs.
|
|
101
|
+
- **`mode="rest"`:** Pet stays at the **home** position (resolved once from placement options). The wrapper stops the loop after each `createNeko`.
|
|
102
|
+
|
|
103
|
+
Toggling placement-affecting options or `mode` may **destroy and recreate** the instance (there is no “teleport to x,y” on the live instance).
|
|
104
|
+
|
|
105
|
+
With **`useNeko`**, you can also call **`setMode('rest' | 'follow')`**, **`restAtOrigin()`**, or **`resumeFollow()`**, or bind a reactive **`mode`** inside **`computed(() => ({ … }))`** options.
|
|
106
|
+
|
|
107
|
+
### 5. Behavior modes (`BehaviorMode` enum)
|
|
108
|
+
|
|
109
|
+
**`behavior-mode`** / **`behaviorMode`** set the AI mode only for the **first** `createNeko` (and when the wrapper recreates for placement / `mode`, the **current** engine mode is kept — not this prop). Prefer the enum instead of raw numbers:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import { BehaviorMode } from "neko-vue";
|
|
113
|
+
// ChaseMouse, RunAwayFromMouse, RunAroundRandomly, PaceAroundScreen, BallChase, StayStill, ReturnHomeAndStay
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
After that, **only clicks on the pet** advance behavior (when **`allow-behavior-change`** is true, default when the prop is omitted on `NekoPet`). Default cycle order matches **`DEFAULT_NEKO_BEHAVIOR_CYCLE`**:
|
|
117
|
+
**chase → run away → random → pace → ball chase → stay still → return home & stay** → chase.
|
|
118
|
+
|
|
119
|
+
Set **`behaviorCycle`** in `useNeko` / `createNeko` or **`behavior-cycle`** on **`NekoPet`** to an ordered array of **`BehaviorMode`** values to replace that list (order = click order, wrapping after the last). Omitted or invalid-only arrays keep the default cycle. If the live mode is not in your list, the next click moves to the **first** entry (because `indexOf` is `-1`).
|
|
120
|
+
|
|
121
|
+
**Stay still** freezes the cat where it is. **Return home & stay** walks back to the spawn from `createNeko` (`startX` / `startY`), then holds there until the next click.
|
|
122
|
+
|
|
123
|
+
If you pass options as a **`computed(() => ({ … }))`** that closes over `behaviorMode`, changing that ref still **re-evaluates** the computed and can re-run the wrapper’s watcher — prefer a **`reactive`** options object (or keep `behaviorMode` outside the reactive object you pass to `useNeko`) if you mutate `behaviorMode` for other reasons without wanting a full reload.
|
|
124
|
+
|
|
125
|
+
### 6. Reduced motion
|
|
126
|
+
|
|
127
|
+
If the user prefers reduced motion, **`NekoPet`** and **`useNeko`** **skip** loading and creating the pet by default. From **`useNeko`**, check **`skippedForReducedMotion`**. To run anyway (only after your own consent UI), set **`:respect-reduced-motion="false"`** or **`respectReducedMotion: false`**.
|
|
128
|
+
|
|
129
|
+
### 7. SSR (Nuxt, custom SSR)
|
|
130
|
+
|
|
131
|
+
Never call **`useNeko`**, render **`NekoPet`**, or **`loadNekoRuntime`** on the server. Use a **`.client.vue`** component or **`<ClientOnly>`** (see [Nuxt 3](#4-nuxt-3) below).
|
|
132
|
+
|
|
133
|
+
### 8. When something looks wrong
|
|
134
|
+
|
|
135
|
+
Enable **`debug`** (`:debug="true"` on `NekoPet` or **`debug: true`** in `useNeko` options) for **`[neko-vue]`** logs (viewport, anchor rect, resolved coordinates, recreates). See [Troubleshooting](#6-troubleshooting).
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Using in your project
|
|
140
|
+
|
|
141
|
+
Start with the [Practical usage guide](#practical-usage-guide) above; the subsections here add framework-specific notes and reference detail.
|
|
142
|
+
|
|
143
|
+
### Requirements
|
|
144
|
+
|
|
145
|
+
- **Vue 3** (`^3.4` or `^3.5`) as a dependency; `neko-vue` lists Vue as a **peer dependency**.
|
|
146
|
+
- A **browser** environment on the code path that runs the pet. Do **not** call `useNeko`, `NekoPet`, or `loadNekoRuntime` during SSR without guarding for client-only execution (see Nuxt below).
|
|
147
|
+
|
|
148
|
+
### 1. Install and import
|
|
149
|
+
|
|
150
|
+
After installing, import the **component** and/or **composable** from the package name:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { NekoPet, useNeko } from "neko-vue";
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
TypeScript picks up types from the published `dist` typings; no extra `@types` package is needed.
|
|
157
|
+
|
|
158
|
+
### 2. Choose how you integrate
|
|
159
|
+
|
|
160
|
+
| Approach | When to use |
|
|
161
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
162
|
+
| **`<NekoPet />`** | You want declarative props (`speed`, `start-corner`, `mode`, …) and minimal setup code. |
|
|
163
|
+
| **`useNeko()`** | You need a **handle** (`setMode`, `destroy`, refs to `instance` / `isReady` / `error`) or **`anchorRef`** to tie the start position to another element. |
|
|
164
|
+
| **`loadNekoRuntime()`** | Advanced: ensure a typed `createNeko` after the bundled runtime is loaded, or reuse an existing **`window.createNeko`** (e.g. tests). |
|
|
165
|
+
|
|
166
|
+
By default, **`loadNekoRuntime()`** loads the **bundled** engine (dynamic import, no extra network hop for the pet). If **`window.createNeko`** is already a function when the loader runs, that implementation is used instead (typical in **tests** or custom setups).
|
|
167
|
+
|
|
168
|
+
### 3. Vite, Vue CLI, or a plain SPA
|
|
169
|
+
|
|
170
|
+
1. Install `neko-vue` and `vue`.
|
|
171
|
+
2. Use **`NekoPet`** in any component that only runs on the client (typical SPA entry is client-only already):
|
|
172
|
+
|
|
173
|
+
```vue
|
|
174
|
+
<script setup lang="ts">
|
|
175
|
+
import { NekoPet } from "neko-vue";
|
|
176
|
+
</script>
|
|
177
|
+
|
|
178
|
+
<template>
|
|
179
|
+
<NekoPet start-corner="bottom-right" />
|
|
180
|
+
</template>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
3. If you prefer the composable, call **`useNeko()` inside `setup()`** (or `<script setup>`) — see the [Composable](#composable) section.
|
|
184
|
+
|
|
185
|
+
### 4. Nuxt 3
|
|
186
|
+
|
|
187
|
+
1. Install: `npx nuxi module add` is not required — add **`neko-vue`** (and ensure **`vue`** is present) with your package manager.
|
|
188
|
+
|
|
189
|
+
2. Wrap usage so it **never runs on the server**:
|
|
190
|
+
- Put the pet in a **`.client.vue`** component, or
|
|
191
|
+
- Wrap with [`<ClientOnly>`](https://nuxt.com/docs/api/components/client-only).
|
|
192
|
+
|
|
193
|
+
```vue
|
|
194
|
+
<!-- components/NekoPetClient.client.vue -->
|
|
195
|
+
<script setup lang="ts">
|
|
196
|
+
import { NekoPet } from "neko-vue";
|
|
197
|
+
</script>
|
|
198
|
+
|
|
199
|
+
<template>
|
|
200
|
+
<NekoPet start-corner="bottom-right" />
|
|
201
|
+
</template>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```vue
|
|
205
|
+
<!-- pages/index.vue — import path matches your Nuxt srcDir -->
|
|
206
|
+
<script setup lang="ts">
|
|
207
|
+
import NekoPetClient from "~/components/NekoPetClient.client.vue";
|
|
208
|
+
</script>
|
|
209
|
+
|
|
210
|
+
<template>
|
|
211
|
+
<div>
|
|
212
|
+
<ClientOnly>
|
|
213
|
+
<NekoPetClient />
|
|
214
|
+
</ClientOnly>
|
|
215
|
+
</div>
|
|
216
|
+
</template>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
If you rely on **Nuxt component auto-import**, you can omit the import and keep the file as `components/NekoPetClient.client.vue`.
|
|
220
|
+
|
|
221
|
+
### 5. Global registration (optional)
|
|
222
|
+
|
|
223
|
+
If you want `NekoPet` everywhere without importing:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
import { createApp } from "vue";
|
|
227
|
+
import { NekoPet } from "neko-vue";
|
|
228
|
+
import App from "./App.vue";
|
|
229
|
+
|
|
230
|
+
const app = createApp(App);
|
|
231
|
+
app.component("NekoPet", NekoPet);
|
|
232
|
+
app.mount("#app");
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### 6. Troubleshooting
|
|
236
|
+
|
|
237
|
+
- **Nothing appears:** Open devtools → **Network** and confirm the library’s **runtime chunk** loads (status 200). Check **Console** for errors.
|
|
238
|
+
- **Reduced motion:** By default the pet is skipped when `prefers-reduced-motion: reduce` is set. Use **`respectReducedMotion: false`** (composable) or **`:respect-reduced-motion="false"`** (`NekoPet`) only if you intentionally override accessibility preferences.
|
|
239
|
+
- **Wrong start position on first paint:** Ensure the layout has non-zero width/height before the pet mounts; use **`startCorner`** / delayed mount, or **`anchorRef`** after the anchor element exists. If **`document.documentElement.clientWidth`** or **`window.innerHeight`** is **0** on the first recreate, corner math yields **`(0,0)`** until a later flush — check the console with **`debug: true`** (composable option or **`debug`** prop on **`NekoPet`**).
|
|
240
|
+
- **Debugging placement:** Set **`debug: true`** on **`useNeko` options** or **`:debug="true"`** on **`NekoPet`**. Logs are prefixed with **`[neko-vue]`** (viewport size, anchor rect, resolved `startX`/`startY`, and each recreate).
|
|
241
|
+
|
|
242
|
+
### 7. Composable + component mental model
|
|
243
|
+
|
|
244
|
+
| Idea | In **neko-vue** |
|
|
245
|
+
| ------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
246
|
+
| Core logic in a **composable** | `useNeko(options)` |
|
|
247
|
+
| Optional **declarative wrapper** | `<NekoPet>` (props mirror composable options) |
|
|
248
|
+
| Pass a **template ref** into the composable | `anchorRef: useTemplateRef('anchor')` (Vue 3.5+), or a plain `ref` on Vue 3.4 |
|
|
249
|
+
| Avoid **`window` / DOM during SSR** | Do not mount the pet on the server; use **`.client.vue`** / `<ClientOnly>` (see above) |
|
|
250
|
+
| Toggle behavior with a **small ref** | `mode` (`follow` / `rest`) or `respectReducedMotion` |
|
|
251
|
+
|
|
252
|
+
**Vue 3.5+:** Prefer [`useTemplateRef`](https://vuejs.org/api/composition-api-helpers.html#usetemplateref) for elements you pass to **`anchorRef`**. On **Vue 3.4**, use `const anchor = ref<HTMLElement | null>(null)` and `ref="anchor"` on the element.
|
|
253
|
+
|
|
254
|
+
Pass **reactive options** with `computed(() => ({ …, mode: mode.value, anchorRef: anchor }))` when values like `mode` should update the pet without remounting the whole parent manually.
|
|
255
|
+
|
|
256
|
+
The sections below document **props**, **placement**, **`mode`**, and **advanced** APIs in more detail.
|
|
257
|
+
|
|
258
|
+
To **develop and test this library** (not only consume it), see [Test this repo locally](#test-this-repo-locally) under **Development**.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Component
|
|
263
|
+
|
|
264
|
+
```vue
|
|
265
|
+
<script setup lang="ts">
|
|
266
|
+
import { BehaviorMode, NekoPet } from "neko-vue";
|
|
267
|
+
</script>
|
|
268
|
+
|
|
269
|
+
<template>
|
|
270
|
+
<NekoPet
|
|
271
|
+
:speed="24"
|
|
272
|
+
:behavior-mode="BehaviorMode.ChaseMouse"
|
|
273
|
+
start-corner="top-right"
|
|
274
|
+
mode="rest"
|
|
275
|
+
/>
|
|
276
|
+
</template>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
`NekoPet` is implemented as `defineComponent` (TypeScript) for reliable library builds; use it like any other Vue component. It **exposes** `{ instance }` (a ref to **`NekoInstance | null`**), so a template **`ref`** on **`NekoPet`** gives you the same live handle as **`useNeko()`’s `instance`**.
|
|
280
|
+
|
|
281
|
+
**`allowBehaviorChange`:** Omit the prop to keep the **engine default** (`true`). A plain optional `Boolean` prop in Vue would otherwise become `false` when unset; here the default is explicitly `undefined`, and options passed to `createNeko` omit `undefined` fields so engine defaults apply.
|
|
282
|
+
|
|
283
|
+
### Options at a glance
|
|
284
|
+
|
|
285
|
+
| Area | Keys |
|
|
286
|
+
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
287
|
+
| **Engine (`createNeko` / `NekoOptions`)** | **`speed`**, **`fps`**, **`behaviorMode`** (initial only; live mode changes via pet clicks), **`idleThreshold`**, **`cursorStandoffPx`**, **`behaviorCycle`**, **`allowBehaviorChange`**, **`startX`** / **`startY`** |
|
|
288
|
+
| **Placement (wrapper)** | **`startCorner`**, **`anchorRef`** (`useNeko` only) or **`anchorSelector`** (`NekoPet`) |
|
|
289
|
+
| **Loop & motion gate** | **`mode`** (`follow` / `rest`), **`autoStart`**, **`respectReducedMotion`**, **`restUntilFirstPetInteraction`** |
|
|
290
|
+
| **Diagnostics** | **`debug`** (`[neko-vue]` logs for placement / recreates) |
|
|
291
|
+
|
|
292
|
+
Changing most engine or placement fields (including **`behaviorCycle`** and **`cursorStandoffPx`**) **recreates** the pet from `useNeko`; **`behaviorMode`** in options does **not** (it is only used for the first create and when leaving the first-click gate).
|
|
293
|
+
|
|
294
|
+
## Composable
|
|
295
|
+
|
|
296
|
+
```vue
|
|
297
|
+
<script setup lang="ts">
|
|
298
|
+
import { BehaviorMode, useNeko } from "neko-vue";
|
|
299
|
+
import { computed, ref } from "vue";
|
|
300
|
+
|
|
301
|
+
const mode = ref<"follow" | "rest">("follow");
|
|
302
|
+
|
|
303
|
+
const {
|
|
304
|
+
setMode,
|
|
305
|
+
restAtOrigin,
|
|
306
|
+
resumeFollow,
|
|
307
|
+
mode: currentMode,
|
|
308
|
+
} = useNeko(
|
|
309
|
+
computed(() => ({
|
|
310
|
+
speed: 24,
|
|
311
|
+
behaviorMode: BehaviorMode.ChaseMouse,
|
|
312
|
+
startCorner: "bottom-right",
|
|
313
|
+
mode: mode.value,
|
|
314
|
+
})),
|
|
315
|
+
);
|
|
316
|
+
// restAtOrigin() / resumeFollow() / setMode('rest' | 'follow')
|
|
317
|
+
// readonly currentMode when driving imperatively; or toggle `mode` ref as above
|
|
318
|
+
</script>
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Custom click cycle and pointer standoff** (same options on **`NekoPet`** as props):
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
import { BehaviorMode, useNeko } from "neko-vue";
|
|
325
|
+
import { computed, ref } from "vue";
|
|
326
|
+
|
|
327
|
+
const mode = ref<"follow" | "rest">("follow");
|
|
328
|
+
|
|
329
|
+
useNeko(
|
|
330
|
+
computed(() => ({
|
|
331
|
+
startCorner: "bottom-right",
|
|
332
|
+
mode: mode.value,
|
|
333
|
+
cursorStandoffPx: 48,
|
|
334
|
+
behaviorCycle: [
|
|
335
|
+
BehaviorMode.ChaseMouse,
|
|
336
|
+
BehaviorMode.StayStill,
|
|
337
|
+
BehaviorMode.ReturnHomeAndStay,
|
|
338
|
+
],
|
|
339
|
+
})),
|
|
340
|
+
);
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**Anchor + `useTemplateRef` (Vue 3.5+):**
|
|
344
|
+
|
|
345
|
+
```vue
|
|
346
|
+
<script setup lang="ts">
|
|
347
|
+
import { useNeko } from "neko-vue";
|
|
348
|
+
import { computed, ref, useTemplateRef } from "vue";
|
|
349
|
+
|
|
350
|
+
const anchor = useTemplateRef<HTMLDivElement>("anchor");
|
|
351
|
+
const mode = ref<"follow" | "rest">("follow");
|
|
352
|
+
|
|
353
|
+
useNeko(
|
|
354
|
+
computed(() => ({
|
|
355
|
+
anchorRef: anchor,
|
|
356
|
+
mode: mode.value,
|
|
357
|
+
})),
|
|
358
|
+
);
|
|
359
|
+
</script>
|
|
360
|
+
|
|
361
|
+
<template>
|
|
362
|
+
<div ref="anchor" class="my-anchor">Pet spawns at this box’s top-left.</div>
|
|
363
|
+
</template>
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Start position
|
|
367
|
+
|
|
368
|
+
- **`startX` / `startY`** — passed through to `createNeko` (remember `0` is valid).
|
|
369
|
+
- **`startCorner`** — `top-left` | `top-right` | `bottom-left` | `bottom-right`; fills axes that `startX`/`startY` leave out, using `document.documentElement.clientWidth` and `window.innerHeight` minus `NEKOJS_SPRITE_SIZE` (32).
|
|
370
|
+
- **`anchorRef`** (in `setup`) or **`anchorSelector`** (on `NekoPet`) — use the element’s top-left in viewport space when coordinates are omitted. If you pass either one, **`createNeko` waits** until the matched element exists and has **positive width and height** (`getBoundingClientRect()`, with `offsetWidth` / `offsetHeight` as a fallback) so the pet does not spawn at a bogus position while the anchor is still mounting or collapsed.
|
|
371
|
+
|
|
372
|
+
Resolution order per axis: **explicit coordinate → anchor → corner → 0**.
|
|
373
|
+
|
|
374
|
+
### Pointer standoff (`cursorStandoffPx`)
|
|
375
|
+
|
|
376
|
+
- **`cursorStandoffPx`** on **`useNeko`** options, **`cursor-standoff-px`** on **`NekoPet`**, or the same field on **`createNeko`** / **`NekoOptions`**: when **greater than zero**, chase mode keeps at least that many CSS pixels between the pet’s movement anchor and the pointer (the cat stops short instead of sitting on the cursor). Omit the field or use **`0`** for the original snap-to-pointer behavior.
|
|
377
|
+
|
|
378
|
+
### Behavior click cycle (`behaviorCycle`)
|
|
379
|
+
|
|
380
|
+
- **`behaviorCycle`** on **`useNeko`** / **`NekoOptions`** / **`createNeko`**, or **`behavior-cycle`** on **`NekoPet`**: ordered **`BehaviorMode[]`** for pet clicks. Omit to use **`DEFAULT_NEKO_BEHAVIOR_CYCLE`**. Changing this option recreates the instance (like other engine options in the composable fingerprint).
|
|
381
|
+
- Invalid or out-of-range entries are **dropped** by the bundled runtime; if nothing valid remains, the **default** cycle is used. The same **`BehaviorMode`** may appear **more than once** if you want the pet to revisit a step.
|
|
382
|
+
|
|
383
|
+
### Follow vs rest (`mode`)
|
|
384
|
+
|
|
385
|
+
- **`follow`** — chase / run behaviors (animation loop running).
|
|
386
|
+
- **`rest`** — pet stays at the **resolved home** position (loop stopped via `stop()` after each `createNeko`).
|
|
387
|
+
|
|
388
|
+
**`createNeko` always calls `start()`**; this library immediately calls **`stop()`** when `mode === 'rest'` or `autoStart === false`, and **`start()`** when `mode === 'follow'` and `autoStart !== false` (a second `start()` is a no-op).
|
|
389
|
+
|
|
390
|
+
Returning to home after the cat has moved uses a **fresh instance** (destroy + create) whenever options or mode change, because there is **no “teleport to x,y”** API on the instance.
|
|
391
|
+
|
|
392
|
+
**`restUntilFirstPetInteraction`:** When `true` (composable option or **`rest-until-first-pet-interaction`** on **`NekoPet`**), the pet starts **still** (`rest`). The **first** pointer down on the sprite is handled so the cat **wakes into chase** without consuming the built-in click-to-cycle step; **later** pointer downs advance your **`behaviorCycle`** (or the default cycle). The composable exposes **`petInteractionAwake`** (readonly ref) so you can reflect that state in the UI. While waiting for the first interaction, **`behaviorMode`** is temporarily forced to **`BehaviorMode.StayStill`** internally; your own **`behaviorMode`** applies after wake (or the engine default chase if omitted).
|
|
393
|
+
|
|
394
|
+
## Manual load + `createNeko`
|
|
395
|
+
|
|
396
|
+
Use this when you call **`createNeko`** outside **`useNeko`** / **`NekoPet`** (e.g. a custom lifecycle).
|
|
397
|
+
|
|
398
|
+
- If **`window.createNeko`** already exists, **`loadNekoRuntime()`** resolves to it immediately (no import).
|
|
399
|
+
- Otherwise it performs a **one-time** dynamic import of the bundled runtime (same code path as **`useNeko`**).
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
import { loadNekoRuntime } from "neko-vue";
|
|
403
|
+
|
|
404
|
+
const createNeko = await loadNekoRuntime();
|
|
405
|
+
const neko = createNeko({ speed: 24 });
|
|
406
|
+
neko.start();
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**`LoadNekoRuntimeOptions`** today only supports **`timeoutMs`** (default **30000**), used when waiting on the bundled import—for example `await loadNekoRuntime({ timeoutMs: 60_000 })` on very slow devices.
|
|
410
|
+
|
|
411
|
+
## Nuxt / SSR
|
|
412
|
+
|
|
413
|
+
The pet runtime is **browser-only**. Do not run `loadNekoRuntime` or `useNeko` on the server. In Nuxt, render the pet inside [`<ClientOnly>`](https://nuxt.com/docs/api/components/client-only) or a `.client.vue` component.
|
|
414
|
+
|
|
415
|
+
## Motion preferences
|
|
416
|
+
|
|
417
|
+
By default, `useNeko` and `NekoPet` **do nothing** when the user has **prefers-reduced-motion: reduce** (no bundled runtime import and no `createNeko`). You get `skippedForReducedMotion === true` from `useNeko`, or use `:respect-reduced-motion="false"` on `NekoPet` to ignore the preference (offer your own UI consent first).
|
|
418
|
+
|
|
419
|
+
You can also call `prefersReducedMotion()` from this package to branch in templates or plugins.
|
|
420
|
+
|
|
421
|
+
## Compared to GIF “one-file neko” implementations
|
|
422
|
+
|
|
423
|
+
This package uses **`createNeko`** with fixed **32×32** sprites, not a GIF sprite sheet or arbitrary pixel sizes. Use `NEKOJS_SPRITE_SIZE` (32) when computing corners or layout.
|
|
424
|
+
|
|
425
|
+
## Bundled runtime & HMR
|
|
426
|
+
|
|
427
|
+
The library loads the engine with a **single shared** dynamic import (your app’s bundler emits a separate chunk; there is no runtime option to swap in another script URL).
|
|
428
|
+
|
|
429
|
+
On unmount, the wrapper calls **`stop()`** then **`destroy()`**. The bundled runtime’s **`destroy()`** also clears the interval and aborts document/window listeners. Hot reload may briefly leave extra instances depending on how your dev server remounts; a full refresh clears them.
|
|
430
|
+
|
|
431
|
+
**Playground:** Each demo route mounts **one** pet. If HMR leaves a stray instance after editing, refresh the page.
|
|
432
|
+
|
|
433
|
+
## Credits
|
|
434
|
+
|
|
435
|
+
Desktop pet behavior and sprites draw on **[nekojs](https://github.com/louisabraham/nekojs)** by Louis Abraham — thank you for the original idea and implementation.
|
|
436
|
+
|
|
437
|
+
## License
|
|
438
|
+
|
|
439
|
+
- **Wrapper source in this repository** is under the [MIT License](./LICENSE).
|
|
440
|
+
- **Bundled pet runtime** in `dist/` is subject to the **third-party** terms described in that file (GNU GPLv3). Read `LICENSE` before redistributing.
|
|
441
|
+
|
|
442
|
+
## Development
|
|
443
|
+
|
|
444
|
+
### Test this repo locally
|
|
445
|
+
|
|
446
|
+
Use this when you are working **in this repository** and want to verify changes before publishing.
|
|
447
|
+
|
|
448
|
+
#### 1. Automated tests and checks
|
|
449
|
+
|
|
450
|
+
From the **repo root** (after `vp install`):
|
|
451
|
+
|
|
452
|
+
```bash
|
|
453
|
+
vp test
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Runs Vitest against **`loadNekoRuntime`**, `useNeko`, and `NekoPet` (tests set **`window.createNeko`** mocks; happy-dom), including checks that optional fields are not sent as `undefined`, that **`behaviorCycle`** / **`cursorStandoffPx`** reach `createNeko` when set, that anchor `ResizeObserver` does not recreate the pet when the layout size is unchanged, and that **`createNeko` is deferred** until an anchor element has non-zero layout when `anchorRef` is used.
|
|
457
|
+
|
|
458
|
+
```bash
|
|
459
|
+
vp check
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Runs format, lint, and TypeScript checks (via Vite+).
|
|
463
|
+
|
|
464
|
+
```bash
|
|
465
|
+
vp pack
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
Produces **`dist/`** (ESM + `.d.mts`). The playground and any `file:` consumer need a successful pack.
|
|
469
|
+
|
|
470
|
+
**CI:** Pushes and pull requests against **`main`** run [`.github/workflows/ci.yml`](./.github/workflows/ci.yml): `voidzero-dev/setup-vp`, **`vp check`**, **`vp test`**, and **`vp pack`** (same bar as a clean local run).
|
|
471
|
+
|
|
472
|
+
#### 2. Visual / manual test in the browser (playground)
|
|
473
|
+
|
|
474
|
+
The **[playground](./playground/)** app imports the library through a Vite alias to the parent folder and resolves **`dist/`** via the root `package.json` `exports`. It uses **Vue Router** so each integration style has its **own route** (one pet on screen at a time):
|
|
475
|
+
|
|
476
|
+
| Route | Demo file | What it shows |
|
|
477
|
+
| ----------------- | ----------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
478
|
+
| **`/`** | `DemoNekoPet.vue` | **`<NekoPet />`**, live stats via **`ref` + exposed `instance`** |
|
|
479
|
+
| **`/composable`** | `DemoUseNekoAnchor.vue` | **`useNeko`** + **`anchorRef`** + extra composable flags in the panel |
|
|
480
|
+
| **`/customize`** | `DemoCustomize.vue` | Sandbox: placement, **`behaviorCycle`**, **`cursorStandoffPx`**, and other options — **Apply** respawns |
|
|
481
|
+
|
|
482
|
+
**One-off:**
|
|
483
|
+
|
|
484
|
+
```bash
|
|
485
|
+
vp pack
|
|
486
|
+
bun install --cwd playground
|
|
487
|
+
bun run playground:dev
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
Open **http://localhost:5175** (browser may open automatically). Use the nav links to switch demos; each page explains **still → click the cat → cycle behaviors** on the pet itself. The pet runtime is loaded from the **bundled** library chunk (no extra network request).
|
|
491
|
+
|
|
492
|
+
**While editing the library**, use two terminals:
|
|
493
|
+
|
|
494
|
+
```bash
|
|
495
|
+
# Terminal A — rebuild dist on change
|
|
496
|
+
vp pack --watch
|
|
497
|
+
|
|
498
|
+
# Terminal B — Vite dev server
|
|
499
|
+
bun run playground:dev
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Refresh the browser after each rebuild (or rely on Vite HMR for playground code only; **`dist/`** changes need a refresh).
|
|
503
|
+
|
|
504
|
+
More detail: [playground/README.md](./playground/README.md).
|
|
505
|
+
|
|
506
|
+
#### 3. Try it inside another Vue app on your machine
|
|
507
|
+
|
|
508
|
+
- **Option A — `npm pack` / `pnpm pack`:** From the repo root, run `vp pack`, then `npm pack` (or equivalent). In your app, install the generated `.tgz` (e.g. `npm install ../path/to/neko-vue-0.0.0.tgz`).
|
|
509
|
+
- **Option B — `file:` dependency:** Point your app’s `package.json` at the folder that contains a built **`dist/`** (same as publishing layout).
|
|
510
|
+
|
|
511
|
+
Ensure **`vp pack`** has been run so `exports` and types resolve.
|
|
512
|
+
|
|
513
|
+
### Package entry shape
|
|
514
|
+
|
|
515
|
+
**neko-vue** ships as a **single package** with one primary export (`"."` → `dist/index.mjs`). Some libraries use a **monorepo** or **`exports` subpaths** (e.g. `pkg/composable`) for tree-shaking; doing that here would need extra **pack** outputs and an **`exports`** map (not wired yet). A **Nuxt module** would typically live in a **separate package**.
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
### Maintainer commands (summary)
|
|
520
|
+
|
|
521
|
+
```bash
|
|
522
|
+
vp install
|
|
523
|
+
vp test
|
|
524
|
+
vp pack
|
|
525
|
+
vp check
|
|
526
|
+
```
|