@usenagi/core 0.2.0 → 0.4.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/README.ja.md +78 -50
- package/README.md +73 -45
- package/dist/addons/scheduler.cjs.js +1 -1
- package/dist/addons/scheduler.es.js +77 -19
- package/dist/main.es.js +152 -160
- package/dist/main.umd.js +1 -1
- package/package.json +4 -4
- 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 +9 -3
- 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 +28 -0
- package/types/core/app.d.ts +6 -24
- package/types/core/component.d.ts +14 -27
- package/types/core/error.d.ts +2 -3
- package/types/core/props.d.ts +2 -0
- package/types/core/runtime.d.ts +4 -3
- package/types/hooks/{useDomRef.d.ts → core/useDomRef.d.ts} +1 -1
- package/types/hooks/core/useSlot.d.ts +5 -0
- package/types/main.d.ts +8 -6
- package/types/types.d.ts +11 -9
- package/types/addons/scheduler/task.d.ts +0 -2
- package/types/core/internal/pending.d.ts +0 -11
- package/types/core/internal/registry.d.ts +0 -4
- package/types/hooks/domRefs.d.ts +0 -2
- package/types/hooks/useSlot.d.ts +0 -6
- package/types/utils/isAbortError.d.ts +0 -1
- /package/types/{hooks/createContext.d.ts → core/context.d.ts} +0 -0
package/README.ja.md
CHANGED
|
@@ -32,9 +32,9 @@ GSAP、Lenis、IntersectionObserver などを `setup()` で初期化し、`useUn
|
|
|
32
32
|
// counter.ts
|
|
33
33
|
import { create, signal, useWatch, useDomRef } from "@usenagi/core";
|
|
34
34
|
|
|
35
|
-
const
|
|
35
|
+
const app = create();
|
|
36
36
|
|
|
37
|
-
component({
|
|
37
|
+
app.component({
|
|
38
38
|
name: "counter",
|
|
39
39
|
setup() {
|
|
40
40
|
const { refs } = useDomRef<{
|
|
@@ -71,13 +71,14 @@ npm i @usenagi/core
|
|
|
71
71
|
### First component
|
|
72
72
|
|
|
73
73
|
```ts
|
|
74
|
-
import { create, defineComponent, signal, useWatch, useDomRef } from "@usenagi/core";
|
|
74
|
+
import { create, defineComponent, propTypes, signal, useWatch, useDomRef } from "@usenagi/core";
|
|
75
75
|
|
|
76
76
|
const Greeting = defineComponent({
|
|
77
77
|
name: "greeting",
|
|
78
|
+
props: propTypes<{ name: string }>(),
|
|
78
79
|
setup(el, props) {
|
|
79
80
|
const { refs } = useDomRef<{ message: HTMLParagraphElement }>();
|
|
80
|
-
const text = signal(
|
|
81
|
+
const text = signal(props.name ?? "world");
|
|
81
82
|
|
|
82
83
|
useWatch(text, (v) => {
|
|
83
84
|
refs.message.textContent = `Hello, ${v}!`;
|
|
@@ -95,10 +96,10 @@ create().component(Greeting)(document.querySelector("#app")!);
|
|
|
95
96
|
|
|
96
97
|
```ts
|
|
97
98
|
import { create } from "@usenagi/core";
|
|
98
|
-
import {
|
|
99
|
+
import { schedulerAddon } from "@usenagi/core/addons/scheduler";
|
|
99
100
|
import { visible, idle } from "@usenagi/core/addons/cue";
|
|
100
101
|
|
|
101
|
-
const app = create(
|
|
102
|
+
const app = create().install(schedulerAddon());
|
|
102
103
|
|
|
103
104
|
// mount when the element enters the viewport
|
|
104
105
|
app.component(HeavyWidget, { when: visible() })(el);
|
|
@@ -107,7 +108,7 @@ app.component(HeavyWidget, { when: visible() })(el);
|
|
|
107
108
|
app.component(Analytics, { when: idle() })(el);
|
|
108
109
|
```
|
|
109
110
|
|
|
110
|
-
`when` は `setup()` の前に待機する条件、`priority` は `setup()` を含む mount task の実行タイミングを決める。
|
|
111
|
+
`schedulerAddon()` を使うと、`when` は `setup()` の前に待機する条件、`priority` は `setup()` を含む mount task の実行タイミングを決める。
|
|
111
112
|
|
|
112
113
|
### BYO mounter recipe
|
|
113
114
|
|
|
@@ -118,14 +119,21 @@ app.component(Analytics, { when: idle() })(el);
|
|
|
118
119
|
|
|
119
120
|
## API
|
|
120
121
|
|
|
122
|
+
### Component Definition
|
|
123
|
+
|
|
124
|
+
| API | 説明 |
|
|
125
|
+
| ---------------------- | ---------------------------------------------------------------- |
|
|
126
|
+
| `defineComponent(opts)` | 型安全な `ComponentSetup` 定義ヘルパー |
|
|
127
|
+
| `propTypes<T>()` | コンポーネント props の型マーカー(ランタイムコストゼロ) |
|
|
128
|
+
|
|
121
129
|
### Reactivity
|
|
122
130
|
|
|
123
|
-
| API | 説明
|
|
124
|
-
| ---------------------- |
|
|
125
|
-
| `signal(value)` | `.value` を持つリアクティブな値コンテナを作成する
|
|
126
|
-
| `readonly(signal)` | 書き込み可能な `signal` の読み取り専用ラッパー
|
|
127
|
-
| `useComputed(fn)` | `signal` の依存を自動追跡する派生値
|
|
128
|
-
| `useWatch(target, cb)` | 値変更時に `cb` を呼ぶ。unmount 時に自動で購読解除する
|
|
131
|
+
| API | 説明 |
|
|
132
|
+
| ---------------------- | ------------------------------------------------------ |
|
|
133
|
+
| `signal(value)` | `.value` を持つリアクティブな値コンテナを作成する |
|
|
134
|
+
| `readonly(signal)` | 書き込み可能な `signal` の読み取り専用ラッパー |
|
|
135
|
+
| `useComputed(fn)` | `signal` の依存を自動追跡する派生値 |
|
|
136
|
+
| `useWatch(target, cb)` | 値変更時に `cb` を呼ぶ。unmount 時に自動で購読解除する |
|
|
129
137
|
|
|
130
138
|
```ts
|
|
131
139
|
const width = signal(10);
|
|
@@ -139,10 +147,10 @@ useWatch(area, (v) => {
|
|
|
139
147
|
|
|
140
148
|
### Lifecycle
|
|
141
149
|
|
|
142
|
-
| API | 説明
|
|
143
|
-
| ---------------- |
|
|
144
|
-
| `useMount(fn)` | コンポーネントのマウント完了後に1回実行する
|
|
145
|
-
| `useUnmount(fn)` | unmount 時に実行する。クリーンアップに使う
|
|
150
|
+
| API | 説明 |
|
|
151
|
+
| ---------------- | ------------------------------------------- |
|
|
152
|
+
| `useMount(fn)` | コンポーネントのマウント完了後に1回実行する |
|
|
153
|
+
| `useUnmount(fn)` | unmount 時に実行する。クリーンアップに使う |
|
|
146
154
|
|
|
147
155
|
```ts
|
|
148
156
|
import gsap from 'gsap';
|
|
@@ -157,11 +165,11 @@ setup(el) {
|
|
|
157
165
|
|
|
158
166
|
ルート要素には **`setup(el)`** を、**`[data-ref]`** の子要素には **`useDomRef()`** を使う。
|
|
159
167
|
|
|
160
|
-
| API | 説明
|
|
161
|
-
| ------------------------------ |
|
|
162
|
-
| `useDomRef<T>()` | `[data-ref]` 要素への型付きアクセス
|
|
163
|
-
| `useEvent(el, event, handler)` | イベントリスナーを追加する。unmount 時に自動で除去する
|
|
164
|
-
| `useSlot()` | 子コンポーネントをマウントする。親の unmount に連動する
|
|
168
|
+
| API | 説明 |
|
|
169
|
+
| ------------------------------ | ------------------------------------------------------- |
|
|
170
|
+
| `useDomRef<T>()` | `[data-ref]` 要素への型付きアクセス |
|
|
171
|
+
| `useEvent(el, event, handler)` | イベントリスナーを追加する。unmount 時に自動で除去する |
|
|
172
|
+
| `useSlot()` | 子コンポーネントをマウントする。親の unmount に連動する |
|
|
165
173
|
|
|
166
174
|
### Parent / child
|
|
167
175
|
|
|
@@ -171,39 +179,59 @@ setup(el) {
|
|
|
171
179
|
|
|
172
180
|
### Observers
|
|
173
181
|
|
|
174
|
-
| API | 説明
|
|
175
|
-
| --------------------------------- |
|
|
176
|
-
| `useIntersectionWatch(cb, opts?)` | IntersectionObserver のラッパー。unmount 時に自動で切断する
|
|
177
|
-
| `useMediaQuery(query)`
|
|
182
|
+
| API | 説明 |
|
|
183
|
+
| --------------------------------- | ----------------------------------------------------------- |
|
|
184
|
+
| `useIntersectionWatch(cb, opts?)` | IntersectionObserver のラッパー。unmount 時に自動で切断する |
|
|
185
|
+
| `useMediaQuery(query, cb)` | query 一致時に callback を実行し、`matchesQuery` を `ReadonlySignal<boolean>` で返す |
|
|
178
186
|
|
|
179
187
|
### Addons
|
|
180
188
|
|
|
181
189
|
```ts
|
|
182
|
-
import {
|
|
190
|
+
import { create, defineAddon } from "@usenagi/core";
|
|
191
|
+
import { schedulerAddon } from "@usenagi/core/addons/scheduler";
|
|
192
|
+
|
|
193
|
+
const app = create().install(schedulerAddon(), myAddon());
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
| API | 説明 |
|
|
197
|
+
| --- | --- |
|
|
198
|
+
| `defineAddon({ name, install(ctx) })` | addon を定義する(`ctx` は `AddonContext`) |
|
|
199
|
+
| `app.install(...addons)` | app に addon を登録する(複数可) |
|
|
200
|
+
| `ctx.addMountMiddleware` / `addUnmountMiddleware` / `addComponentMiddleware` | mount / unmount / ComponentSetup の middleware を追加する |
|
|
201
|
+
| `ctx.installedAddons` | この app に install 済みの addon 名 |
|
|
202
|
+
|
|
203
|
+
`addMountMiddleware` / `addUnmountMiddleware` / `addComponentMiddleware` は **後から install した addon ほど外側**に適用される(`install(a, b)` なら実行順は `b → a → コア`)。
|
|
204
|
+
|
|
205
|
+
遅延 mount には `schedulerAddon()` が必要。`when` や `priority` を使う場合も同様で、これらの mount option は scheduler addon が解釈する。addon の状態(scheduler / pending)は **各 app の `install` ごと**に作られる。
|
|
206
|
+
|
|
207
|
+
#### Scheduler + cue
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import { schedulerAddon } from "@usenagi/core/addons/scheduler";
|
|
183
211
|
import { visible, idle, interaction, media } from "@usenagi/core/addons/cue";
|
|
184
212
|
```
|
|
185
213
|
|
|
186
|
-
| API
|
|
187
|
-
|
|
|
188
|
-
| `
|
|
189
|
-
| `visible(opts?)`
|
|
190
|
-
| `idle(timeout?)`
|
|
191
|
-
| `interaction(events?)`
|
|
192
|
-
| `media(query)`
|
|
214
|
+
| API | 説明 |
|
|
215
|
+
| --- | --- |
|
|
216
|
+
| `schedulerAddon(opts?)` | 遅延 mount 用 addon |
|
|
217
|
+
| `visible(opts?)` | 要素が viewport に入ったときに解決する Cue |
|
|
218
|
+
| `idle(timeout?)` | `requestIdleCallback` で解決する Cue |
|
|
219
|
+
| `interaction(events?)` | 最初のユーザー操作で解決する Cue |
|
|
220
|
+
| `media(query)` | media query が一致したときに解決する Cue |
|
|
193
221
|
|
|
194
222
|
---
|
|
195
223
|
|
|
196
224
|
## Comparison
|
|
197
225
|
|
|
198
|
-
|
|
|
199
|
-
|
|
|
200
|
-
| Inline JS in HTML
|
|
201
|
-
| Composition-style setup
|
|
202
|
-
| BYO mounter
|
|
203
|
-
| Async mount cue
|
|
204
|
-
| Lifecycle cleanup
|
|
205
|
-
|
|
|
206
|
-
| Core gzip
|
|
226
|
+
| | **nagi** | Alpine.js | Stimulus | petite-vue |
|
|
227
|
+
| -------------------------- | -------- | --------- | -------- | ---------- |
|
|
228
|
+
| Inline JS in HTML | ✗ | ◯ | ✗ | ◯ |
|
|
229
|
+
| Composition-style setup | ◯ | △ | ✗ | ◯ |
|
|
230
|
+
| BYO mounter | ◯ | △ | △ | △ |
|
|
231
|
+
| Async mount cue | ◯ | ✗ | ✗ | ✗ |
|
|
232
|
+
| Lifecycle cleanup | ◯ | △ | ◯ | △ |
|
|
233
|
+
| computed (derived signals) | ◯ | ◯ | ✗ | ◯ |
|
|
234
|
+
| Core gzip | ~2.5 kB | ~16 kB | ~8 kB | ~6 kB |
|
|
207
235
|
|
|
208
236
|
(◯ = 組み込み、△ = 利用側の実装・規約で対応可能、✗ = 主な機能ではない)
|
|
209
237
|
|
|
@@ -233,13 +261,13 @@ import { visible, idle, interaction, media } from "@usenagi/core/addons/cue";
|
|
|
233
261
|
|
|
234
262
|
## Examples
|
|
235
263
|
|
|
236
|
-
| Example | 説明
|
|
237
|
-
| ----------------------------------------------------- |
|
|
238
|
-
| [basic-counter](./examples/basic-counter/) | 最小の `signal` + `useWatch` 例
|
|
239
|
-
| [computed](./examples/computed/) | `useComputed` による派生値(width × height = area)
|
|
240
|
-
| [parent-child](./examples/parent-child/) | `createContext` + `withContext` + `useSlot`
|
|
241
|
-
| [lenis-scroll-scene](./examples/lenis-scroll-scene/) | Lenis + `useComputed` によるスクロール進捗連動
|
|
242
|
-
| [byo-mounter recipe](./examples/recipes/byo-mounter/) | `[data-component]` スキャン + manifest + cue
|
|
264
|
+
| Example | 説明 |
|
|
265
|
+
| ----------------------------------------------------- | --------------------------------------------------- |
|
|
266
|
+
| [basic-counter](./examples/basic-counter/) | 最小の `signal` + `useWatch` 例 |
|
|
267
|
+
| [computed](./examples/computed/) | `useComputed` による派生値(width × height = area) |
|
|
268
|
+
| [parent-child](./examples/parent-child/) | `createContext` + `withContext` + `useSlot` |
|
|
269
|
+
| [lenis-scroll-scene](./examples/lenis-scroll-scene/) | Lenis + `useComputed` によるスクロール進捗連動 |
|
|
270
|
+
| [byo-mounter recipe](./examples/recipes/byo-mounter/) | `[data-component]` スキャン + manifest + cue |
|
|
243
271
|
|
|
244
272
|
---
|
|
245
273
|
|
package/README.md
CHANGED
|
@@ -32,9 +32,9 @@ You are free to implement `[data-component]` scanning, manifests, lazy imports,
|
|
|
32
32
|
// counter.ts
|
|
33
33
|
import { create, signal, useWatch, useDomRef } from "@usenagi/core";
|
|
34
34
|
|
|
35
|
-
const
|
|
35
|
+
const app = create();
|
|
36
36
|
|
|
37
|
-
component({
|
|
37
|
+
app.component({
|
|
38
38
|
name: "counter",
|
|
39
39
|
setup() {
|
|
40
40
|
const { refs } = useDomRef<{
|
|
@@ -71,13 +71,14 @@ npm i @usenagi/core
|
|
|
71
71
|
### First component
|
|
72
72
|
|
|
73
73
|
```ts
|
|
74
|
-
import { create, defineComponent, signal, useWatch, useDomRef } from "@usenagi/core";
|
|
74
|
+
import { create, defineComponent, propTypes, signal, useWatch, useDomRef } from "@usenagi/core";
|
|
75
75
|
|
|
76
76
|
const Greeting = defineComponent({
|
|
77
77
|
name: "greeting",
|
|
78
|
+
props: propTypes<{ name: string }>(),
|
|
78
79
|
setup(el, props) {
|
|
79
80
|
const { refs } = useDomRef<{ message: HTMLParagraphElement }>();
|
|
80
|
-
const text = signal(
|
|
81
|
+
const text = signal(props.name ?? "world");
|
|
81
82
|
|
|
82
83
|
useWatch(text, (v) => {
|
|
83
84
|
refs.message.textContent = `Hello, ${v}!`;
|
|
@@ -95,10 +96,10 @@ If delayed mounting is required, add the scheduler / cue addons.
|
|
|
95
96
|
|
|
96
97
|
```ts
|
|
97
98
|
import { create } from "@usenagi/core";
|
|
98
|
-
import {
|
|
99
|
+
import { schedulerAddon } from "@usenagi/core/addons/scheduler";
|
|
99
100
|
import { visible, idle } from "@usenagi/core/addons/cue";
|
|
100
101
|
|
|
101
|
-
const app = create(
|
|
102
|
+
const app = create().install(schedulerAddon());
|
|
102
103
|
|
|
103
104
|
// mount when the element enters the viewport
|
|
104
105
|
app.component(HeavyWidget, { when: visible() })(el);
|
|
@@ -107,7 +108,7 @@ app.component(HeavyWidget, { when: visible() })(el);
|
|
|
107
108
|
app.component(Analytics, { when: idle() })(el);
|
|
108
109
|
```
|
|
109
110
|
|
|
110
|
-
`when` is a condition to wait for before `setup()`, and `priority` determines the execution timing of the mount task that includes `setup()`.
|
|
111
|
+
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
|
|
|
112
113
|
### BYO mounter recipe
|
|
113
114
|
|
|
@@ -118,12 +119,19 @@ An example of automatic mounting by combining `[data-component]` scanning, manif
|
|
|
118
119
|
|
|
119
120
|
## API
|
|
120
121
|
|
|
122
|
+
### Component Definition
|
|
123
|
+
|
|
124
|
+
| API | Description |
|
|
125
|
+
| ---------------------- | --------------------------------------------------------------------------- |
|
|
126
|
+
| `defineComponent(opts)` | Type-safe helper to define a `ComponentSetup` object |
|
|
127
|
+
| `propTypes<T>()` | Type-only marker for declaring component props shape (zero runtime cost) |
|
|
128
|
+
|
|
121
129
|
### Reactivity
|
|
122
130
|
|
|
123
|
-
| API | Description
|
|
124
|
-
| ---------------------- |
|
|
125
|
-
| `signal(value)` | Creates a reactive value container (`.value`)
|
|
126
|
-
| `readonly(signal)` | Read-only wrapper around a writable `signal`
|
|
131
|
+
| API | Description |
|
|
132
|
+
| ---------------------- | ----------------------------------------------------------------- |
|
|
133
|
+
| `signal(value)` | Creates a reactive value container (`.value`) |
|
|
134
|
+
| `readonly(signal)` | Read-only wrapper around a writable `signal` |
|
|
127
135
|
| `useComputed(fn)` | Derived value that auto-tracks `signal` dependencies |
|
|
128
136
|
| `useWatch(target, cb)` | Calls `cb` on value change; automatically unsubscribes on unmount |
|
|
129
137
|
|
|
@@ -139,10 +147,10 @@ useWatch(area, (v) => {
|
|
|
139
147
|
|
|
140
148
|
### Lifecycle
|
|
141
149
|
|
|
142
|
-
| API | Description
|
|
143
|
-
| ---------------- |
|
|
144
|
-
| `useMount(fn)` | Runs once after the component mounts
|
|
145
|
-
| `useUnmount(fn)` | Runs on unmount; use for cleanup
|
|
150
|
+
| API | Description |
|
|
151
|
+
| ---------------- | ------------------------------------ |
|
|
152
|
+
| `useMount(fn)` | Runs once after the component mounts |
|
|
153
|
+
| `useUnmount(fn)` | Runs on unmount; use for cleanup |
|
|
146
154
|
|
|
147
155
|
```ts
|
|
148
156
|
import gsap from 'gsap';
|
|
@@ -157,11 +165,11 @@ setup(el) {
|
|
|
157
165
|
|
|
158
166
|
Use **`setup(el)`** for the root element and **`useDomRef()`** for `[data-ref]` descendants.
|
|
159
167
|
|
|
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
|
|
168
|
+
| API | Description |
|
|
169
|
+
| ------------------------------ | -------------------------------------------------------- |
|
|
170
|
+
| `useDomRef<T>()` | Typed access to `[data-ref]` elements |
|
|
171
|
+
| `useEvent(el, event, handler)` | Adds an event listener; automatically removed on unmount |
|
|
172
|
+
| `useSlot()` | Mounts child components; tied to the parent's unmount |
|
|
165
173
|
|
|
166
174
|
### Parent / child
|
|
167
175
|
|
|
@@ -174,36 +182,56 @@ You can mount child components with `useSlot()`. You can pass values from parent
|
|
|
174
182
|
| API | Description |
|
|
175
183
|
| --------------------------------- | ------------------------------------------------------------------- |
|
|
176
184
|
| `useIntersectionWatch(cb, opts?)` | IntersectionObserver wrapper; automatically disconnected on unmount |
|
|
177
|
-
| `useMediaQuery(query)`
|
|
185
|
+
| `useMediaQuery(query, cb)` | Runs `callback` when the query matches; returns `matchesQuery` as `ReadonlySignal<boolean>` |
|
|
178
186
|
|
|
179
187
|
### Addons
|
|
180
188
|
|
|
181
189
|
```ts
|
|
182
|
-
import {
|
|
190
|
+
import { create, defineAddon } from "@usenagi/core";
|
|
191
|
+
import { schedulerAddon } from "@usenagi/core/addons/scheduler";
|
|
192
|
+
|
|
193
|
+
const app = create().install(schedulerAddon(), myAddon());
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
| API | Description |
|
|
197
|
+
| --- | --- |
|
|
198
|
+
| `defineAddon({ name, install(ctx) })` | Defines an addon (`ctx` is `AddonContext`) |
|
|
199
|
+
| `app.install(...addons)` | Registers one or more addons on the app |
|
|
200
|
+
| `ctx.addMountMiddleware` / `addUnmountMiddleware` / `addComponentMiddleware` | Add mount / unmount / ComponentSetup middleware |
|
|
201
|
+
| `ctx.installedAddons` | Addon names already installed on this app |
|
|
202
|
+
|
|
203
|
+
`addMountMiddleware`, `addUnmountMiddleware`, and `addComponentMiddleware` apply **outermost for addons installed later** (`install(a, b)` runs as `b → a → core`).
|
|
204
|
+
|
|
205
|
+
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.
|
|
206
|
+
|
|
207
|
+
#### Scheduler + cue
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import { schedulerAddon } from "@usenagi/core/addons/scheduler";
|
|
183
211
|
import { visible, idle, interaction, media } from "@usenagi/core/addons/cue";
|
|
184
212
|
```
|
|
185
213
|
|
|
186
|
-
| API
|
|
187
|
-
|
|
|
188
|
-
| `
|
|
189
|
-
| `visible(opts?)`
|
|
190
|
-
| `idle(timeout?)`
|
|
191
|
-
| `interaction(events?)`
|
|
192
|
-
| `media(query)`
|
|
214
|
+
| API | Description |
|
|
215
|
+
| --- | --- |
|
|
216
|
+
| `schedulerAddon(opts?)` | Addon for deferred mount |
|
|
217
|
+
| `visible(opts?)` | A Cue that resolves when the element enters the viewport |
|
|
218
|
+
| `idle(timeout?)` | A Cue that resolves via `requestIdleCallback` |
|
|
219
|
+
| `interaction(events?)` | A Cue that resolves on the first user interaction |
|
|
220
|
+
| `media(query)` | A Cue that resolves when the media query matches |
|
|
193
221
|
|
|
194
222
|
---
|
|
195
223
|
|
|
196
224
|
## Comparison
|
|
197
225
|
|
|
198
|
-
|
|
|
199
|
-
|
|
|
200
|
-
| Inline JS in HTML
|
|
201
|
-
| Composition-style setup
|
|
202
|
-
| BYO mounter
|
|
203
|
-
| Async mount cue
|
|
204
|
-
| Lifecycle cleanup
|
|
205
|
-
|
|
|
206
|
-
| Core gzip
|
|
226
|
+
| | **nagi** | Alpine.js | Stimulus | petite-vue |
|
|
227
|
+
| -------------------------- | -------- | --------- | -------- | ---------- |
|
|
228
|
+
| Inline JS in HTML | ✗ | ◯ | ✗ | ◯ |
|
|
229
|
+
| Composition-style setup | ◯ | △ | ✗ | ◯ |
|
|
230
|
+
| BYO mounter | ◯ | △ | △ | △ |
|
|
231
|
+
| Async mount cue | ◯ | ✗ | ✗ | ✗ |
|
|
232
|
+
| Lifecycle cleanup | ◯ | △ | ◯ | △ |
|
|
233
|
+
| computed (derived signals) | ◯ | ◯ | ✗ | ◯ |
|
|
234
|
+
| Core gzip | ~2.5 kB | ~16 kB | ~8 kB | ~6 kB |
|
|
207
235
|
|
|
208
236
|
(◯ = built-in, △ = handled via userland/convention, ✗ = not a primary feature)
|
|
209
237
|
|
|
@@ -233,13 +261,13 @@ import { visible, idle, interaction, media } from "@usenagi/core/addons/cue";
|
|
|
233
261
|
|
|
234
262
|
## Examples
|
|
235
263
|
|
|
236
|
-
| Example | Description
|
|
237
|
-
| ----------------------------------------------------- |
|
|
238
|
-
| [basic-counter](./examples/basic-counter/) | Minimal `signal` + `useWatch` example
|
|
239
|
-
| [computed](./examples/computed/)
|
|
240
|
-
| [parent-child](./examples/parent-child/) | `createContext` + `withContext` + `useSlot`
|
|
241
|
-
| [lenis-scroll-scene](./examples/lenis-scroll-scene/) | Scroll-progress animation with Lenis + `useComputed`
|
|
242
|
-
| [byo-mounter recipe](./examples/recipes/byo-mounter/) | `[data-component]` scanning + manifest + cue
|
|
264
|
+
| Example | Description |
|
|
265
|
+
| ----------------------------------------------------- | -------------------------------------------------------- |
|
|
266
|
+
| [basic-counter](./examples/basic-counter/) | Minimal `signal` + `useWatch` example |
|
|
267
|
+
| [computed](./examples/computed/) | Derived value with `useComputed` (width × height = area) |
|
|
268
|
+
| [parent-child](./examples/parent-child/) | `createContext` + `withContext` + `useSlot` |
|
|
269
|
+
| [lenis-scroll-scene](./examples/lenis-scroll-scene/) | Scroll-progress animation with Lenis + `useComputed` |
|
|
270
|
+
| [byo-mounter recipe](./examples/recipes/byo-mounter/) | `[data-component]` scanning + manifest + cue |
|
|
243
271
|
|
|
244
272
|
---
|
|
245
273
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){return(e instanceof DOMException||e instanceof Error)&&e.name===`AbortError`}function
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){return e}function t(){let e=new Map;return{add(t){let n=e.get(t);n&&n.abort();let r=new AbortController;return e.set(t,r),{signal:r.signal,complete(){return e.get(t)!==r||r.signal.aborted?!1:(e.delete(t),!0)},abort(){e.get(t)===r&&(r.abort(),e.delete(t))}}},abort(t){let n=e.get(t);n&&(n.abort(),e.delete(t))}}}function n(e){return(e instanceof DOMException||e instanceof Error)&&e.name===`AbortError`}function r(e,t,r){if(r?.aborted)return;let{scheduler:a}=globalThis;if(typeof a?.postTask==`function`){a.postTask(e,{priority:t,signal:r}).catch(e=>{n(e)||queueMicrotask(()=>{throw e})});return}i(e,t,r)}function i(e,t,n){function r(){n?.aborted||e()}function i(e,t){let r=e();n?.addEventListener(`abort`,()=>t(r),{once:!0})}switch(t){case`user-blocking`:queueMicrotask(r);break;case`user-visible`:i(()=>requestAnimationFrame(r),cancelAnimationFrame);break;case`background`:typeof requestIdleCallback==`function`?i(()=>requestIdleCallback(r),cancelIdleCallback):i(()=>setTimeout(r,0),clearTimeout);break}}function a(e={}){let t=e.priority??`user-visible`;return{schedule(e,n={}){r(e,n.priority??t,n.signal)}}}function o(e){return(e instanceof DOMException||e instanceof Error)&&e.name===`AbortError`}function s(n){return e({name:`@usenagi/scheduler`,install(e){let r=a(n),i=t();e.addMountMiddleware((e,t,n)=>(t,a)=>{let s=i.add(t),c=()=>{r.schedule(()=>{s.complete()&&e(t,a)},{priority:n.priority,signal:s.signal})},{when:l}=n;l?l(t,s.signal).then(()=>{s.signal.aborted||c()},e=>{o(e)||(s.abort(),queueMicrotask(()=>{throw e}))}):c()}),e.addUnmountMiddleware(e=>t=>{t.forEach(i.abort),e(t)})}})}exports.schedulerAddon=s;
|
|
@@ -1,26 +1,54 @@
|
|
|
1
|
-
//#region lib/
|
|
1
|
+
//#region lib/core/addon.ts
|
|
2
2
|
function e(e) {
|
|
3
|
-
return
|
|
3
|
+
return e;
|
|
4
|
+
}
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region lib/addons/scheduler/_internal/pending.ts
|
|
7
|
+
function t() {
|
|
8
|
+
let e = /* @__PURE__ */ new Map();
|
|
9
|
+
return {
|
|
10
|
+
add(t) {
|
|
11
|
+
let n = e.get(t);
|
|
12
|
+
n && n.abort();
|
|
13
|
+
let r = new AbortController();
|
|
14
|
+
return e.set(t, r), {
|
|
15
|
+
signal: r.signal,
|
|
16
|
+
complete() {
|
|
17
|
+
return e.get(t) !== r || r.signal.aborted ? !1 : (e.delete(t), !0);
|
|
18
|
+
},
|
|
19
|
+
abort() {
|
|
20
|
+
e.get(t) === r && (r.abort(), e.delete(t));
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
abort(t) {
|
|
25
|
+
let n = e.get(t);
|
|
26
|
+
n && (n.abort(), e.delete(t));
|
|
27
|
+
}
|
|
28
|
+
};
|
|
4
29
|
}
|
|
5
30
|
//#endregion
|
|
6
|
-
//#region lib/addons/scheduler/
|
|
7
|
-
function
|
|
8
|
-
|
|
31
|
+
//#region lib/addons/scheduler/_internal/schedule.ts
|
|
32
|
+
function n(e) {
|
|
33
|
+
return (e instanceof DOMException || e instanceof Error) && e.name === "AbortError";
|
|
34
|
+
}
|
|
35
|
+
function r(e, t, r) {
|
|
36
|
+
if (r?.aborted) return;
|
|
9
37
|
let { scheduler: a } = globalThis;
|
|
10
38
|
if (typeof a?.postTask == "function") {
|
|
11
|
-
a.postTask(
|
|
12
|
-
priority:
|
|
13
|
-
signal:
|
|
14
|
-
}).catch((
|
|
15
|
-
e
|
|
16
|
-
throw
|
|
39
|
+
a.postTask(e, {
|
|
40
|
+
priority: t,
|
|
41
|
+
signal: r
|
|
42
|
+
}).catch((e) => {
|
|
43
|
+
n(e) || queueMicrotask(() => {
|
|
44
|
+
throw e;
|
|
17
45
|
});
|
|
18
46
|
});
|
|
19
47
|
return;
|
|
20
48
|
}
|
|
21
|
-
|
|
49
|
+
i(e, t, r);
|
|
22
50
|
}
|
|
23
|
-
function
|
|
51
|
+
function i(e, t, n) {
|
|
24
52
|
function r() {
|
|
25
53
|
n?.aborted || e();
|
|
26
54
|
}
|
|
@@ -40,13 +68,43 @@ function n(e, t, n) {
|
|
|
40
68
|
break;
|
|
41
69
|
}
|
|
42
70
|
}
|
|
71
|
+
function a(e = {}) {
|
|
72
|
+
let t = e.priority ?? "user-visible";
|
|
73
|
+
return { schedule(e, n = {}) {
|
|
74
|
+
r(e, n.priority ?? t, n.signal);
|
|
75
|
+
} };
|
|
76
|
+
}
|
|
43
77
|
//#endregion
|
|
44
78
|
//#region lib/addons/scheduler/index.ts
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
79
|
+
function o(e) {
|
|
80
|
+
return (e instanceof DOMException || e instanceof Error) && e.name === "AbortError";
|
|
81
|
+
}
|
|
82
|
+
function s(n) {
|
|
83
|
+
return e({
|
|
84
|
+
name: "@usenagi/scheduler",
|
|
85
|
+
install(e) {
|
|
86
|
+
let r = a(n), i = t();
|
|
87
|
+
e.addMountMiddleware((e, t, n) => (t, a) => {
|
|
88
|
+
let s = i.add(t), c = () => {
|
|
89
|
+
r.schedule(() => {
|
|
90
|
+
s.complete() && e(t, a);
|
|
91
|
+
}, {
|
|
92
|
+
priority: n.priority,
|
|
93
|
+
signal: s.signal
|
|
94
|
+
});
|
|
95
|
+
}, { when: l } = n;
|
|
96
|
+
l ? l(t, s.signal).then(() => {
|
|
97
|
+
s.signal.aborted || c();
|
|
98
|
+
}, (e) => {
|
|
99
|
+
o(e) || (s.abort(), queueMicrotask(() => {
|
|
100
|
+
throw e;
|
|
101
|
+
}));
|
|
102
|
+
}) : c();
|
|
103
|
+
}), e.addUnmountMiddleware((e) => (t) => {
|
|
104
|
+
t.forEach(i.abort), e(t);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
50
108
|
}
|
|
51
109
|
//#endregion
|
|
52
|
-
export {
|
|
110
|
+
export { s as schedulerAddon };
|