@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.
Files changed (34) hide show
  1. package/dist/addons/cue.es.js +1 -1
  2. package/dist/addons/scheduler.cjs.js +1 -1
  3. package/dist/addons/scheduler.es.js +26 -27
  4. package/dist/main.es.js +120 -118
  5. package/dist/main.umd.js +1 -1
  6. package/package.json +3 -17
  7. package/types/addons/cue/index.d.ts +1 -1
  8. package/types/addons/scheduler/_internal/pending.d.ts +11 -0
  9. package/types/addons/scheduler/_internal/schedule.d.ts +11 -0
  10. package/types/addons/scheduler/index.d.ts +10 -2
  11. package/types/core/_internal/addonRegistry.d.ts +10 -0
  12. package/types/core/_internal/component.d.ts +24 -0
  13. package/types/core/_internal/registry.d.ts +5 -0
  14. package/types/core/addon.d.ts +0 -7
  15. package/types/core/app.d.ts +2 -3
  16. package/types/core/component.d.ts +0 -22
  17. package/types/core/error.d.ts +2 -3
  18. package/types/core/runtime.d.ts +4 -3
  19. package/types/hooks/{useDomRef.d.ts → core/useDomRef.d.ts} +1 -1
  20. package/types/hooks/{useSlot.d.ts → core/useSlot.d.ts} +2 -3
  21. package/types/main.d.ts +7 -8
  22. package/types/types.d.ts +10 -8
  23. package/LICENSE +0 -21
  24. package/README.ja.md +0 -269
  25. package/README.md +0 -269
  26. package/types/addons/scheduler/addon.d.ts +0 -10
  27. package/types/addons/scheduler/pending.d.ts +0 -11
  28. package/types/addons/scheduler/scheduler.d.ts +0 -4
  29. package/types/addons/scheduler/task.d.ts +0 -2
  30. package/types/core/internal/registry.d.ts +0 -4
  31. package/types/hooks/domRefs.d.ts +0 -2
  32. package/types/utils/isAbortError.d.ts +0 -1
  33. /package/types/{hooks/createContext.d.ts → core/context.d.ts} +0 -0
  34. /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
- [![npm](https://img.shields.io/npm/v/@usenagi/core)](https://www.npmjs.com/package/@usenagi/core)
8
- [![bundle size](https://img.shields.io/bundlephobia/minzip/@usenagi/core)](https://bundlephobia.com/package/@usenagi/core)
9
- [![license](https://img.shields.io/npm/l/@usenagi/core)](./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 { SchedulePriority, Scheduler } from "../../types";
2
- export declare function createScheduler(opts?: {
3
- priority?: SchedulePriority;
4
- }): Scheduler;
@@ -1,2 +0,0 @@
1
- import type { SchedulePriority } from "../../types";
2
- export declare function scheduleTask(task: () => void, priority: SchedulePriority, signal?: AbortSignal): void;
@@ -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;
@@ -1,2 +0,0 @@
1
- import type { RefElement } from "../types";
2
- export declare function domRefs<T extends Record<string, RefElement | RefElement[] | null>>(scope: RefElement, getBoundaries: () => RefElement[]): T;
@@ -1 +0,0 @@
1
- export declare function isAbortError(error: unknown): boolean;
File without changes