@usenagi/core 0.3.0 → 0.4.1
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/dist/addons/cue.es.js +1 -1
- package/dist/addons/scheduler.cjs.js +1 -1
- package/dist/addons/scheduler.es.js +26 -27
- package/dist/main.es.js +120 -118
- package/dist/main.umd.js +1 -1
- package/package.json +3 -17
- package/types/addons/cue/index.d.ts +1 -1
- package/types/addons/scheduler/_internal/pending.d.ts +11 -0
- package/types/addons/scheduler/_internal/schedule.d.ts +11 -0
- package/types/addons/scheduler/index.d.ts +10 -2
- package/types/core/_internal/addonRegistry.d.ts +10 -0
- package/types/core/_internal/component.d.ts +24 -0
- package/types/core/_internal/registry.d.ts +5 -0
- package/types/core/addon.d.ts +0 -7
- package/types/core/app.d.ts +2 -3
- package/types/core/component.d.ts +0 -22
- package/types/core/error.d.ts +2 -3
- package/types/core/runtime.d.ts +4 -3
- package/types/hooks/{useDomRef.d.ts → core/useDomRef.d.ts} +1 -1
- package/types/hooks/{useSlot.d.ts → core/useSlot.d.ts} +2 -3
- package/types/main.d.ts +7 -8
- package/types/types.d.ts +10 -8
- package/LICENSE +0 -21
- package/README.ja.md +0 -269
- package/README.md +0 -269
- package/types/addons/scheduler/addon.d.ts +0 -10
- package/types/addons/scheduler/pending.d.ts +0 -11
- package/types/addons/scheduler/scheduler.d.ts +0 -4
- package/types/addons/scheduler/task.d.ts +0 -2
- package/types/core/internal/registry.d.ts +0 -4
- package/types/hooks/domRefs.d.ts +0 -2
- package/types/utils/isAbortError.d.ts +0 -1
- /package/types/{hooks/createContext.d.ts → core/context.d.ts} +0 -0
- /package/types/{props.d.ts → core/props.d.ts} +0 -0
package/README.md
DELETED
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
**English** | [日本語](./README.ja.md)
|
|
2
|
-
|
|
3
|
-
# nagi
|
|
4
|
-
|
|
5
|
-
**Composition-style ergonomics for vanilla DOM. Bring your own mounter.**
|
|
6
|
-
|
|
7
|
-
[](https://www.npmjs.com/package/@usenagi/core)
|
|
8
|
-
[](https://bundlephobia.com/package/@usenagi/core)
|
|
9
|
-
[](./LICENSE)
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Why nagi?
|
|
14
|
-
|
|
15
|
-
**Can be added in small parts to existing HTML**
|
|
16
|
-
|
|
17
|
-
You can add `setup()`, lifecycle, and reactivity to WordPress, CMS, Webflow, static sites, etc., without introducing a virtual DOM or templates.
|
|
18
|
-
|
|
19
|
-
**Compatible with animation**
|
|
20
|
-
|
|
21
|
-
You can initialize GSAP, Lenis, IntersectionObserver, etc., in `setup()` and clean them up with `useUnmount()`.
|
|
22
|
-
|
|
23
|
-
**Does not restrict mounting strategies**
|
|
24
|
-
|
|
25
|
-
You are free to implement `[data-component]` scanning, manifests, lazy imports, MutationObserver, and so on, on the consuming side.
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## 30-second example
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
// counter.ts
|
|
33
|
-
import { create, signal, useWatch, useDomRef } from "@usenagi/core";
|
|
34
|
-
|
|
35
|
-
const { component } = create();
|
|
36
|
-
|
|
37
|
-
component({
|
|
38
|
-
name: "counter",
|
|
39
|
-
setup() {
|
|
40
|
-
const { refs } = useDomRef<{
|
|
41
|
-
count: HTMLSpanElement;
|
|
42
|
-
btn: HTMLButtonElement;
|
|
43
|
-
}>();
|
|
44
|
-
|
|
45
|
-
const n = signal(0);
|
|
46
|
-
useWatch(n, (v) => {
|
|
47
|
-
refs.count.textContent = String(v);
|
|
48
|
-
});
|
|
49
|
-
refs.btn.addEventListener("click", () => {
|
|
50
|
-
n.value++;
|
|
51
|
-
});
|
|
52
|
-
},
|
|
53
|
-
})(document.querySelector("#counter")!);
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
```html
|
|
57
|
-
<div id="counter">
|
|
58
|
-
<span data-ref="count">0</span>
|
|
59
|
-
<button data-ref="btn">+</button>
|
|
60
|
-
</div>
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## Quick start
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
npm i @usenagi/core
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### First component
|
|
72
|
-
|
|
73
|
-
```ts
|
|
74
|
-
import { create, defineComponent, signal, useWatch, useDomRef } from "@usenagi/core";
|
|
75
|
-
|
|
76
|
-
const Greeting = defineComponent({
|
|
77
|
-
name: "greeting",
|
|
78
|
-
setup(el, props) {
|
|
79
|
-
const { refs } = useDomRef<{ message: HTMLParagraphElement }>();
|
|
80
|
-
const text = signal((props.name as string) ?? "world");
|
|
81
|
-
|
|
82
|
-
useWatch(text, (v) => {
|
|
83
|
-
refs.message.textContent = `Hello, ${v}!`;
|
|
84
|
-
});
|
|
85
|
-
refs.message.textContent = `Hello, ${text.value}!`;
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
create().component(Greeting)(document.querySelector("#app")!);
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### Scheduler + deferred mount
|
|
93
|
-
|
|
94
|
-
If delayed mounting is required, add the scheduler / cue addons.
|
|
95
|
-
|
|
96
|
-
```ts
|
|
97
|
-
import { create } from "@usenagi/core";
|
|
98
|
-
import { schedulerAddon } from "@usenagi/core/addons/scheduler";
|
|
99
|
-
import { visible, idle } from "@usenagi/core/addons/cue";
|
|
100
|
-
|
|
101
|
-
const app = create().install(schedulerAddon());
|
|
102
|
-
|
|
103
|
-
// mount when the element enters the viewport
|
|
104
|
-
app.component(HeavyWidget, { when: visible() })(el);
|
|
105
|
-
|
|
106
|
-
// mount during browser idle time
|
|
107
|
-
app.component(Analytics, { when: idle() })(el);
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
With `schedulerAddon()`, `when` is a condition to wait for before `setup()`, and `priority` determines the execution timing of the mount task that includes `setup()`.
|
|
111
|
-
|
|
112
|
-
### BYO mounter recipe
|
|
113
|
-
|
|
114
|
-
An example of automatic mounting by combining `[data-component]` scanning, manifests, and cues.
|
|
115
|
-
→ [examples/recipes/byo-mounter](./examples/recipes/byo-mounter/main.ts)
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## API
|
|
120
|
-
|
|
121
|
-
### Reactivity
|
|
122
|
-
|
|
123
|
-
| API | Description |
|
|
124
|
-
| ---------------------- | ----------------------------------------------------------------- |
|
|
125
|
-
| `signal(value)` | Creates a reactive value container (`.value`) |
|
|
126
|
-
| `readonly(signal)` | Read-only wrapper around a writable `signal` |
|
|
127
|
-
| `useComputed(fn)` | Derived value that auto-tracks `signal` dependencies |
|
|
128
|
-
| `useWatch(target, cb)` | Calls `cb` on value change; automatically unsubscribes on unmount |
|
|
129
|
-
|
|
130
|
-
```ts
|
|
131
|
-
const width = signal(10);
|
|
132
|
-
const height = signal(5);
|
|
133
|
-
const area = useComputed(() => width.value * height.value); // auto-recomputed
|
|
134
|
-
|
|
135
|
-
useWatch(area, (v) => {
|
|
136
|
-
output.textContent = String(v);
|
|
137
|
-
});
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Lifecycle
|
|
141
|
-
|
|
142
|
-
| API | Description |
|
|
143
|
-
| ---------------- | ------------------------------------ |
|
|
144
|
-
| `useMount(fn)` | Runs once after the component mounts |
|
|
145
|
-
| `useUnmount(fn)` | Runs on unmount; use for cleanup |
|
|
146
|
-
|
|
147
|
-
```ts
|
|
148
|
-
import gsap from 'gsap';
|
|
149
|
-
|
|
150
|
-
setup(el) {
|
|
151
|
-
const tween = gsap.from(el, { opacity: 0, duration: 0.4 });
|
|
152
|
-
useUnmount(() => tween.kill());
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### DOM helpers
|
|
157
|
-
|
|
158
|
-
Use **`setup(el)`** for the root element and **`useDomRef()`** for `[data-ref]` descendants.
|
|
159
|
-
|
|
160
|
-
| API | Description |
|
|
161
|
-
| ------------------------------ | -------------------------------------------------------- |
|
|
162
|
-
| `useDomRef<T>()` | Typed access to `[data-ref]` elements |
|
|
163
|
-
| `useEvent(el, event, handler)` | Adds an event listener; automatically removed on unmount |
|
|
164
|
-
| `useSlot()` | Mounts child components; tied to the parent's unmount |
|
|
165
|
-
|
|
166
|
-
### Parent / child
|
|
167
|
-
|
|
168
|
-
You can mount child components with `useSlot()`. You can pass values from parent to child via `props` or `createContext` / `withContext`. From the child context returned by `addChild()`, you can also reference the return value of the child's `setup()`.
|
|
169
|
-
|
|
170
|
-
→ [examples/parent-child](./examples/parent-child/main.ts)
|
|
171
|
-
|
|
172
|
-
### Observers
|
|
173
|
-
|
|
174
|
-
| API | Description |
|
|
175
|
-
| --------------------------------- | ------------------------------------------------------------------- |
|
|
176
|
-
| `useIntersectionWatch(cb, opts?)` | IntersectionObserver wrapper; automatically disconnected on unmount |
|
|
177
|
-
| `useMediaQuery(query)` | Returns `matchMedia` result as a `ReadonlySignal<boolean>` |
|
|
178
|
-
|
|
179
|
-
### Addons
|
|
180
|
-
|
|
181
|
-
```ts
|
|
182
|
-
import { create, defineAddon } from "@usenagi/core";
|
|
183
|
-
import { schedulerAddon } from "@usenagi/core/addons/scheduler";
|
|
184
|
-
|
|
185
|
-
const app = create().install(schedulerAddon(), myAddon());
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
| API | Description |
|
|
189
|
-
| --- | --- |
|
|
190
|
-
| `defineAddon({ name, install(ctx) })` | Defines an addon (`ctx` is `AddonContext`) |
|
|
191
|
-
| `app.install(...addons)` | Registers one or more addons on the app |
|
|
192
|
-
| `ctx.addMountMiddleware` / `addUnmountMiddleware` / `addComponentMiddleware` | Add mount / unmount / ComponentSetup middleware |
|
|
193
|
-
| `ctx.installedAddons` | Addon names already installed on this app |
|
|
194
|
-
|
|
195
|
-
`addMountMiddleware`, `addUnmountMiddleware`, and `addComponentMiddleware` apply **outermost for addons installed later** (`install(a, b)` runs as `b → a → core`).
|
|
196
|
-
|
|
197
|
-
Deferred mounting requires `schedulerAddon()`. The same applies when using `when` or `priority`; these mount options are interpreted by the scheduler addon. Addon state (scheduler / pending) is created **per app `install`**, not per addon instance.
|
|
198
|
-
|
|
199
|
-
#### Scheduler + cue
|
|
200
|
-
|
|
201
|
-
```ts
|
|
202
|
-
import { schedulerAddon } from "@usenagi/core/addons/scheduler";
|
|
203
|
-
import { visible, idle, interaction, media } from "@usenagi/core/addons/cue";
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
| API | Description |
|
|
207
|
-
| --- | --- |
|
|
208
|
-
| `schedulerAddon(opts?)` | Addon for deferred mount (uses `createScheduler` internally) |
|
|
209
|
-
| `createScheduler(opts?)` | Low-level API for custom Scheduler implementations |
|
|
210
|
-
| `visible(opts?)` | A Cue that resolves when the element enters the viewport |
|
|
211
|
-
| `idle(timeout?)` | A Cue that resolves via `requestIdleCallback` |
|
|
212
|
-
| `interaction(events?)` | A Cue that resolves on the first user interaction |
|
|
213
|
-
| `media(query)` | A Cue that resolves when the media query matches |
|
|
214
|
-
|
|
215
|
-
---
|
|
216
|
-
|
|
217
|
-
## Comparison
|
|
218
|
-
|
|
219
|
-
| | **nagi** | Alpine.js | Stimulus | petite-vue |
|
|
220
|
-
| -------------------------- | -------- | --------- | -------- | ---------- |
|
|
221
|
-
| Inline JS in HTML | ✗ | ◯ | ✗ | ◯ |
|
|
222
|
-
| Composition-style setup | ◯ | △ | ✗ | ◯ |
|
|
223
|
-
| BYO mounter | ◯ | △ | △ | △ |
|
|
224
|
-
| Async mount cue | ◯ | ✗ | ✗ | ✗ |
|
|
225
|
-
| Lifecycle cleanup | ◯ | △ | ◯ | △ |
|
|
226
|
-
| computed (derived signals) | ◯ | ◯ | ✗ | ◯ |
|
|
227
|
-
| Core gzip | ~2.5 kB | ~16 kB | ~8 kB | ~6 kB |
|
|
228
|
-
|
|
229
|
-
(◯ = built-in, △ = handled via userland/convention, ✗ = not a primary feature)
|
|
230
|
-
|
|
231
|
-
- **vs Alpine / petite-vue**: Instead of writing logic expressions directly in HTML, you centralize your logic in `.ts` files.
|
|
232
|
-
- **vs Stimulus**: No controller conventions; you are free to implement your own mounting strategy.
|
|
233
|
-
- **vs React / Vue**: It is not a declarative UI framework, but rather a thin layer that adds lifecycle hooks to existing DOM.
|
|
234
|
-
|
|
235
|
-
---
|
|
236
|
-
|
|
237
|
-
## When to use / When not to
|
|
238
|
-
|
|
239
|
-
**Recommended Use Cases:**
|
|
240
|
-
|
|
241
|
-
- Projects where you cannot justify the runtime overhead of React or Vue (e.g., CMS, Webflow, WordPress).
|
|
242
|
-
- Animation-heavy sites that rely heavily on libraries like GSAP or Lenis.
|
|
243
|
-
- Scenarios where you only need to add interactive UI to specific parts of a page.
|
|
244
|
-
- When you want to use a composition-style approach with `setup()`, lifecycle hooks, and reactivity, but do not require a virtual DOM.
|
|
245
|
-
|
|
246
|
-
**Not Recommended For:**
|
|
247
|
-
|
|
248
|
-
- When you want to handle list rendering or conditional logic via HTML templates (it does not support equivalents to `v-for` or `v-if`).
|
|
249
|
-
- When you need deep reactivity for complex objects (it does not provide `reactive({})`).
|
|
250
|
-
- When SSR/hydration is required.
|
|
251
|
-
- When you want a full-featured framework to handle global state management, routing, and declarative view rendering.
|
|
252
|
-
|
|
253
|
-
---
|
|
254
|
-
|
|
255
|
-
## Examples
|
|
256
|
-
|
|
257
|
-
| Example | Description |
|
|
258
|
-
| ----------------------------------------------------- | -------------------------------------------------------- |
|
|
259
|
-
| [basic-counter](./examples/basic-counter/) | Minimal `signal` + `useWatch` example |
|
|
260
|
-
| [computed](./examples/computed/) | Derived value with `useComputed` (width × height = area) |
|
|
261
|
-
| [parent-child](./examples/parent-child/) | `createContext` + `withContext` + `useSlot` |
|
|
262
|
-
| [lenis-scroll-scene](./examples/lenis-scroll-scene/) | Scroll-progress animation with Lenis + `useComputed` |
|
|
263
|
-
| [byo-mounter recipe](./examples/recipes/byo-mounter/) | `[data-component]` scanning + manifest + cue |
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-
## License
|
|
268
|
-
|
|
269
|
-
MIT © [hayakawasho](https://github.com/hayakawasho)
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { Cue, SchedulePriority } from "../../types";
|
|
2
|
-
declare module "../../core/addon" {
|
|
3
|
-
interface MountOptions {
|
|
4
|
-
priority?: SchedulePriority;
|
|
5
|
-
when?: Cue;
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
export declare function schedulerAddon(opts?: {
|
|
9
|
-
priority?: SchedulePriority;
|
|
10
|
-
}): import("../../main").Addon;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { RefElement } from "../../types";
|
|
2
|
-
export type PendingMount = {
|
|
3
|
-
readonly signal: AbortSignal;
|
|
4
|
-
complete(): boolean;
|
|
5
|
-
abort(): void;
|
|
6
|
-
};
|
|
7
|
-
export type PendingMounts = {
|
|
8
|
-
add(el: RefElement): PendingMount;
|
|
9
|
-
abort(el: RefElement): void;
|
|
10
|
-
};
|
|
11
|
-
export declare function createPendingMounts(): PendingMounts;
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { RefElement } from "../../types";
|
|
2
|
-
import type { ComponentContext } from "../component";
|
|
3
|
-
export declare const DOM_COMPONENT_INSTANCE: WeakMap<RefElement, ComponentContext<any>>;
|
|
4
|
-
export declare function bindDOMNodeToComponent(el: RefElement, component: ComponentContext): void;
|
package/types/hooks/domRefs.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function isAbortError(error: unknown): boolean;
|
|
File without changes
|
|
File without changes
|