@usenagi/core 0.4.1 → 0.4.3
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/LICENSE +21 -0
- package/README.ja.md +276 -0
- package/README.md +276 -0
- package/dist/addons/scheduler.cjs.js +1 -1
- package/dist/addons/scheduler.es.js +47 -46
- package/dist/main.es.js +115 -114
- package/dist/main.umd.js +1 -1
- package/package.json +8 -4
- package/types/addons/scheduler/_internal/deferredMounts.d.ts +13 -0
- package/types/addons/scheduler/_internal/isAbortError.d.ts +2 -0
- package/types/core/_internal/addonRegistry.d.ts +16 -6
- package/types/core/addon.d.ts +5 -11
- package/types/addons/scheduler/_internal/pending.d.ts +0 -11
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 hayakawasho
|
|
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.
|
package/README.ja.md
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
[English](./README.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
|
+
**既存 HTML に小さく足せる**
|
|
16
|
+
|
|
17
|
+
WordPress、CMS、Webflow、静的サイトなどに、仮想 DOM やテンプレートを持ち込まず、`setup()` / lifecycle / reactivity を追加できる。
|
|
18
|
+
|
|
19
|
+
**アニメーションと相性が良い**
|
|
20
|
+
|
|
21
|
+
GSAP、Lenis、IntersectionObserver などを `setup()` で初期化し、`useUnmount()` でクリーンアップできる。
|
|
22
|
+
|
|
23
|
+
**マウント戦略を縛らない**
|
|
24
|
+
|
|
25
|
+
`[data-component]` スキャン、manifest、lazy import、MutationObserver などは、利用側で自由に組み立てられる。
|
|
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 app = create();
|
|
36
|
+
|
|
37
|
+
app.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, propTypes, signal, useWatch, useDomRef } from "@usenagi/core";
|
|
75
|
+
|
|
76
|
+
const Greeting = defineComponent({
|
|
77
|
+
name: "greeting",
|
|
78
|
+
props: propTypes<{ name: string }>(),
|
|
79
|
+
setup(el, props) {
|
|
80
|
+
const { refs } = useDomRef<{ message: HTMLParagraphElement }>();
|
|
81
|
+
const text = signal(props.name ?? "world");
|
|
82
|
+
|
|
83
|
+
useWatch(text, (v) => {
|
|
84
|
+
refs.message.textContent = `Hello, ${v}!`;
|
|
85
|
+
});
|
|
86
|
+
refs.message.textContent = `Hello, ${text.value}!`;
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
create().component(Greeting)(document.querySelector("#app")!);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Scheduler + deferred mount
|
|
94
|
+
|
|
95
|
+
遅延マウントが必要な場合は、scheduler / cue addons を追加する。
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { create } from "@usenagi/core";
|
|
99
|
+
import { schedulerAddon } from "@usenagi/core/addons/scheduler";
|
|
100
|
+
import { visible, idle } from "@usenagi/core/addons/cue";
|
|
101
|
+
|
|
102
|
+
const app = create().install(schedulerAddon());
|
|
103
|
+
|
|
104
|
+
// mount when the element enters the viewport
|
|
105
|
+
app.component(HeavyWidget, { when: visible() })(el);
|
|
106
|
+
|
|
107
|
+
// mount during browser idle time
|
|
108
|
+
app.component(Analytics, { when: idle() })(el);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`schedulerAddon()` を使うと、`when` は `setup()` の前に待機する条件、`priority` は `setup()` を含む mount task の実行タイミングを決める。
|
|
112
|
+
|
|
113
|
+
### BYO mounter recipe
|
|
114
|
+
|
|
115
|
+
`[data-component]` スキャン、manifest、cue を組み合わせた自動マウントの例。
|
|
116
|
+
→ [examples/recipes/byo-mounter](../../examples/recipes/byo-mounter/main.ts)
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## API
|
|
121
|
+
|
|
122
|
+
### Component Definition
|
|
123
|
+
|
|
124
|
+
| API | 説明 |
|
|
125
|
+
| ---------------------- | ---------------------------------------------------------------- |
|
|
126
|
+
| `defineComponent(opts)` | 型安全な `ComponentSetup` 定義ヘルパー |
|
|
127
|
+
| `propTypes<T>()` | コンポーネント props の型マーカー(ランタイムコストゼロ) |
|
|
128
|
+
|
|
129
|
+
### Reactivity
|
|
130
|
+
|
|
131
|
+
| API | 説明 |
|
|
132
|
+
| ---------------------- | ------------------------------------------------------ |
|
|
133
|
+
| `signal(value)` | `.value` を持つリアクティブな値コンテナを作成する |
|
|
134
|
+
| `readonly(signal)` | 書き込み可能な `signal` の読み取り専用ラッパー |
|
|
135
|
+
| `useComputed(fn)` | `signal` の依存を自動追跡する派生値 |
|
|
136
|
+
| `useWatch(target, cb)` | 値変更時に `cb` を呼ぶ。unmount 時に自動で購読解除する |
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const width = signal(10);
|
|
140
|
+
const height = signal(5);
|
|
141
|
+
const area = useComputed(() => width.value * height.value); // auto-recomputed
|
|
142
|
+
|
|
143
|
+
useWatch(area, (v) => {
|
|
144
|
+
output.textContent = String(v);
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Lifecycle
|
|
149
|
+
|
|
150
|
+
| API | 説明 |
|
|
151
|
+
| ---------------- | ------------------------------------------- |
|
|
152
|
+
| `useMount(fn)` | コンポーネントのマウント完了後に1回実行する |
|
|
153
|
+
| `useUnmount(fn)` | unmount 時に実行する。クリーンアップに使う |
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import gsap from 'gsap';
|
|
157
|
+
|
|
158
|
+
setup(el) {
|
|
159
|
+
const tween = gsap.from(el, { opacity: 0, duration: 0.4 });
|
|
160
|
+
useUnmount(() => tween.kill());
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### DOM helpers
|
|
165
|
+
|
|
166
|
+
ルート要素には **`setup(el)`** を、**`[data-ref]`** の子要素には **`useDomRef()`** を使う。
|
|
167
|
+
|
|
168
|
+
| API | 説明 |
|
|
169
|
+
| ------------------------------ | ------------------------------------------------------- |
|
|
170
|
+
| `useDomRef<T>()` | `[data-ref]` 要素への型付きアクセス |
|
|
171
|
+
| `useEvent(el, event, handler)` | イベントリスナーを追加する。unmount 時に自動で除去する |
|
|
172
|
+
| `useSlot()` | 子コンポーネントをマウントする。親の unmount に連動する |
|
|
173
|
+
|
|
174
|
+
### Parent / child
|
|
175
|
+
|
|
176
|
+
`useSlot()` で子コンポーネントをマウントできる。親から子へは `props` または `createContext` / `withContext` で値を渡せる。`addChild()` が返す child context から、子の `setup()` の返り値も参照できる。
|
|
177
|
+
|
|
178
|
+
→ [examples/parent-child](../../examples/parent-child/main.ts)
|
|
179
|
+
|
|
180
|
+
### Observers
|
|
181
|
+
|
|
182
|
+
| API | 説明 |
|
|
183
|
+
| --------------------------------- | ----------------------------------------------------------- |
|
|
184
|
+
| `useIntersectionWatch(cb, opts?)` | IntersectionObserver のラッパー。unmount 時に自動で切断する |
|
|
185
|
+
| `useMediaQuery(query, cb)` | query 一致時に callback を実行し、`matchesQuery` を `ReadonlySignal<boolean>` で返す |
|
|
186
|
+
|
|
187
|
+
### Addons
|
|
188
|
+
|
|
189
|
+
```ts
|
|
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";
|
|
211
|
+
import { visible, idle, interaction, media } from "@usenagi/core/addons/cue";
|
|
212
|
+
```
|
|
213
|
+
|
|
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 |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Comparison
|
|
225
|
+
|
|
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 |
|
|
235
|
+
|
|
236
|
+
(◯ = 組み込み、△ = 利用側の実装・規約で対応可能、✗ = 主な機能ではない)
|
|
237
|
+
|
|
238
|
+
- **vs Alpine / petite-vue**: HTML に式を直接書かず、ロジックを `.ts` に集約する。
|
|
239
|
+
- **vs Stimulus**: Controller 規約はない。マウント戦略は利用側で自由に組み立てられる。
|
|
240
|
+
- **vs React / Vue**: 宣言的 UI フレームワークではなく、既存 DOM に lifecycle を足す薄いレイヤー。
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## When to use / When not to
|
|
245
|
+
|
|
246
|
+
**向いているケース:**
|
|
247
|
+
|
|
248
|
+
- React や Vue のランタイムを持ち込みにくいプロジェクト(CMS、Webflow、WordPress など)
|
|
249
|
+
- GSAP や Lenis を多用する、アニメーション主体のサイト
|
|
250
|
+
- ページの一部だけにインタラクティブな UI を追加したい場合
|
|
251
|
+
- `setup()`、lifecycle、reactivity による composition-style で書きたいが、仮想 DOM は不要な場合
|
|
252
|
+
|
|
253
|
+
**向いていないケース:**
|
|
254
|
+
|
|
255
|
+
- リスト描画や条件分岐を HTML テンプレートで書きたい場合(`v-for` や `v-if` 相当はない)
|
|
256
|
+
- 複雑なオブジェクトの深いリアクティビティが必要な場合(`reactive({})` は提供しない)
|
|
257
|
+
- SSR / hydration が必要な場合
|
|
258
|
+
- 状態管理、ルーティング、宣言的な view rendering をフレームワークにまとめて任せたい場合
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Examples
|
|
263
|
+
|
|
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 |
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
MIT © [hayakawasho](https://github.com/hayakawasho)
|
package/README.md
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
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 app = create();
|
|
36
|
+
|
|
37
|
+
app.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, propTypes, signal, useWatch, useDomRef } from "@usenagi/core";
|
|
75
|
+
|
|
76
|
+
const Greeting = defineComponent({
|
|
77
|
+
name: "greeting",
|
|
78
|
+
props: propTypes<{ name: string }>(),
|
|
79
|
+
setup(el, props) {
|
|
80
|
+
const { refs } = useDomRef<{ message: HTMLParagraphElement }>();
|
|
81
|
+
const text = signal(props.name ?? "world");
|
|
82
|
+
|
|
83
|
+
useWatch(text, (v) => {
|
|
84
|
+
refs.message.textContent = `Hello, ${v}!`;
|
|
85
|
+
});
|
|
86
|
+
refs.message.textContent = `Hello, ${text.value}!`;
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
create().component(Greeting)(document.querySelector("#app")!);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Scheduler + deferred mount
|
|
94
|
+
|
|
95
|
+
If delayed mounting is required, add the scheduler / cue addons.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { create } from "@usenagi/core";
|
|
99
|
+
import { schedulerAddon } from "@usenagi/core/addons/scheduler";
|
|
100
|
+
import { visible, idle } from "@usenagi/core/addons/cue";
|
|
101
|
+
|
|
102
|
+
const app = create().install(schedulerAddon());
|
|
103
|
+
|
|
104
|
+
// mount when the element enters the viewport
|
|
105
|
+
app.component(HeavyWidget, { when: visible() })(el);
|
|
106
|
+
|
|
107
|
+
// mount during browser idle time
|
|
108
|
+
app.component(Analytics, { when: idle() })(el);
|
|
109
|
+
```
|
|
110
|
+
|
|
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()`.
|
|
112
|
+
|
|
113
|
+
### BYO mounter recipe
|
|
114
|
+
|
|
115
|
+
An example of automatic mounting by combining `[data-component]` scanning, manifests, and cues.
|
|
116
|
+
→ [examples/recipes/byo-mounter](../../examples/recipes/byo-mounter/main.ts)
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## API
|
|
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
|
+
|
|
129
|
+
### Reactivity
|
|
130
|
+
|
|
131
|
+
| API | Description |
|
|
132
|
+
| ---------------------- | ----------------------------------------------------------------- |
|
|
133
|
+
| `signal(value)` | Creates a reactive value container (`.value`) |
|
|
134
|
+
| `readonly(signal)` | Read-only wrapper around a writable `signal` |
|
|
135
|
+
| `useComputed(fn)` | Derived value that auto-tracks `signal` dependencies |
|
|
136
|
+
| `useWatch(target, cb)` | Calls `cb` on value change; automatically unsubscribes on unmount |
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const width = signal(10);
|
|
140
|
+
const height = signal(5);
|
|
141
|
+
const area = useComputed(() => width.value * height.value); // auto-recomputed
|
|
142
|
+
|
|
143
|
+
useWatch(area, (v) => {
|
|
144
|
+
output.textContent = String(v);
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Lifecycle
|
|
149
|
+
|
|
150
|
+
| API | Description |
|
|
151
|
+
| ---------------- | ------------------------------------ |
|
|
152
|
+
| `useMount(fn)` | Runs once after the component mounts |
|
|
153
|
+
| `useUnmount(fn)` | Runs on unmount; use for cleanup |
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import gsap from 'gsap';
|
|
157
|
+
|
|
158
|
+
setup(el) {
|
|
159
|
+
const tween = gsap.from(el, { opacity: 0, duration: 0.4 });
|
|
160
|
+
useUnmount(() => tween.kill());
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### DOM helpers
|
|
165
|
+
|
|
166
|
+
Use **`setup(el)`** for the root element and **`useDomRef()`** for `[data-ref]` descendants.
|
|
167
|
+
|
|
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 |
|
|
173
|
+
|
|
174
|
+
### Parent / child
|
|
175
|
+
|
|
176
|
+
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()`.
|
|
177
|
+
|
|
178
|
+
→ [examples/parent-child](../../examples/parent-child/main.ts)
|
|
179
|
+
|
|
180
|
+
### Observers
|
|
181
|
+
|
|
182
|
+
| API | Description |
|
|
183
|
+
| --------------------------------- | ------------------------------------------------------------------- |
|
|
184
|
+
| `useIntersectionWatch(cb, opts?)` | IntersectionObserver wrapper; automatically disconnected on unmount |
|
|
185
|
+
| `useMediaQuery(query, cb)` | Runs `callback` when the query matches; returns `matchesQuery` as `ReadonlySignal<boolean>` |
|
|
186
|
+
|
|
187
|
+
### Addons
|
|
188
|
+
|
|
189
|
+
```ts
|
|
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";
|
|
211
|
+
import { visible, idle, interaction, media } from "@usenagi/core/addons/cue";
|
|
212
|
+
```
|
|
213
|
+
|
|
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 |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Comparison
|
|
225
|
+
|
|
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 |
|
|
235
|
+
|
|
236
|
+
(◯ = built-in, △ = handled via userland/convention, ✗ = not a primary feature)
|
|
237
|
+
|
|
238
|
+
- **vs Alpine / petite-vue**: Instead of writing logic expressions directly in HTML, you centralize your logic in `.ts` files.
|
|
239
|
+
- **vs Stimulus**: No controller conventions; you are free to implement your own mounting strategy.
|
|
240
|
+
- **vs React / Vue**: It is not a declarative UI framework, but rather a thin layer that adds lifecycle hooks to existing DOM.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## When to use / When not to
|
|
245
|
+
|
|
246
|
+
**Recommended Use Cases:**
|
|
247
|
+
|
|
248
|
+
- Projects where you cannot justify the runtime overhead of React or Vue (e.g., CMS, Webflow, WordPress).
|
|
249
|
+
- Animation-heavy sites that rely heavily on libraries like GSAP or Lenis.
|
|
250
|
+
- Scenarios where you only need to add interactive UI to specific parts of a page.
|
|
251
|
+
- When you want to use a composition-style approach with `setup()`, lifecycle hooks, and reactivity, but do not require a virtual DOM.
|
|
252
|
+
|
|
253
|
+
**Not Recommended For:**
|
|
254
|
+
|
|
255
|
+
- When you want to handle list rendering or conditional logic via HTML templates (it does not support equivalents to `v-for` or `v-if`).
|
|
256
|
+
- When you need deep reactivity for complex objects (it does not provide `reactive({})`).
|
|
257
|
+
- When SSR/hydration is required.
|
|
258
|
+
- When you want a full-featured framework to handle global state management, routing, and declarative view rendering.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Examples
|
|
263
|
+
|
|
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 |
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
MIT © [hayakawasho](https://github.com/hayakawasho)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){return e}
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){return e}var t=class{#e=new Map;add(e){this.#n(e,this.#e.get(e));let t=new AbortController;return this.#e.set(e,t),{signal:t.signal,complete:()=>this.#t(e,t),abort:()=>this.#n(e,t)}}abort=e=>{this.#n(e,this.#e.get(e))};#t(e,t){let n=this.#e.get(e)!==t,r=t.signal.aborted;return n||r?!1:(this.#e.delete(e),!0)}#n(e,t){!t||this.#e.get(e)!==t||(t.abort(),this.#e.delete(e))}};function n(){return new t}function r(e){return(e instanceof DOMException||e instanceof Error)&&e.name===`AbortError`}function i(e,t,n){if(n?.aborted)return;let{scheduler:i}=globalThis;if(typeof i?.postTask==`function`){i.postTask(e,{priority:t,signal:n}).catch(e=>{r(e)||queueMicrotask(()=>{throw e})});return}a(e,t,n)}function a(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 o(e={}){let t=e.priority??`user-visible`;return{schedule(e,n={}){i(e,n.priority??t,n.signal)}}}function s(t){return e({name:`@usenagi/scheduler`,install(e){let i=o(t),a=n();e.addMountMiddleware((e,t,n)=>(t,o)=>{let s=a.add(t),c=()=>{i.schedule(()=>{s.complete()&&e(t,o)},{priority:n.priority,signal:s.signal})},{when:l}=n;l?l(t,s.signal).then(()=>{s.signal.aborted||c()},e=>{r(e)||(s.abort(),queueMicrotask(()=>{throw e}))}):c()}),e.addUnmountMiddleware(e=>t=>{t.forEach(a.abort),e(t)})}})}exports.schedulerAddon=s;
|
|
@@ -3,52 +3,56 @@ function e(e) {
|
|
|
3
3
|
return e;
|
|
4
4
|
}
|
|
5
5
|
//#endregion
|
|
6
|
-
//#region ../addons/scheduler/_internal/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
}
|
|
6
|
+
//#region ../addons/scheduler/_internal/deferredMounts.ts
|
|
7
|
+
var t = class {
|
|
8
|
+
#e = /* @__PURE__ */ new Map();
|
|
9
|
+
add(e) {
|
|
10
|
+
this.#n(e, this.#e.get(e));
|
|
11
|
+
let t = new AbortController();
|
|
12
|
+
return this.#e.set(e, t), {
|
|
13
|
+
signal: t.signal,
|
|
14
|
+
complete: () => this.#t(e, t),
|
|
15
|
+
abort: () => this.#n(e, t)
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
abort = (e) => {
|
|
19
|
+
this.#n(e, this.#e.get(e));
|
|
28
20
|
};
|
|
21
|
+
#t(e, t) {
|
|
22
|
+
let n = this.#e.get(e) !== t, r = t.signal.aborted;
|
|
23
|
+
return n || r ? !1 : (this.#e.delete(e), !0);
|
|
24
|
+
}
|
|
25
|
+
#n(e, t) {
|
|
26
|
+
!t || this.#e.get(e) !== t || (t.abort(), this.#e.delete(e));
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
function n() {
|
|
30
|
+
return new t();
|
|
29
31
|
}
|
|
30
32
|
//#endregion
|
|
31
|
-
//#region ../addons/scheduler/_internal/
|
|
32
|
-
function
|
|
33
|
+
//#region ../addons/scheduler/_internal/isAbortError.ts
|
|
34
|
+
function r(e) {
|
|
33
35
|
return (e instanceof DOMException || e instanceof Error) && e.name === "AbortError";
|
|
34
36
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region ../addons/scheduler/_internal/schedule.ts
|
|
39
|
+
function i(e, t, n) {
|
|
40
|
+
if (n?.aborted) return;
|
|
41
|
+
let { scheduler: i } = globalThis;
|
|
42
|
+
if (typeof i?.postTask == "function") {
|
|
43
|
+
i.postTask(e, {
|
|
40
44
|
priority: t,
|
|
41
|
-
signal:
|
|
45
|
+
signal: n
|
|
42
46
|
}).catch((e) => {
|
|
43
|
-
|
|
47
|
+
r(e) || queueMicrotask(() => {
|
|
44
48
|
throw e;
|
|
45
49
|
});
|
|
46
50
|
});
|
|
47
51
|
return;
|
|
48
52
|
}
|
|
49
|
-
|
|
53
|
+
a(e, t, n);
|
|
50
54
|
}
|
|
51
|
-
function
|
|
55
|
+
function a(e, t, n) {
|
|
52
56
|
function r() {
|
|
53
57
|
n?.aborted || e();
|
|
54
58
|
}
|
|
@@ -68,26 +72,23 @@ function i(e, t, n) {
|
|
|
68
72
|
break;
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
|
-
function
|
|
75
|
+
function o(e = {}) {
|
|
72
76
|
let t = e.priority ?? "user-visible";
|
|
73
77
|
return { schedule(e, n = {}) {
|
|
74
|
-
|
|
78
|
+
i(e, n.priority ?? t, n.signal);
|
|
75
79
|
} };
|
|
76
80
|
}
|
|
77
81
|
//#endregion
|
|
78
82
|
//#region ../addons/scheduler/index.ts
|
|
79
|
-
function
|
|
80
|
-
return (e instanceof DOMException || e instanceof Error) && e.name === "AbortError";
|
|
81
|
-
}
|
|
82
|
-
function s(n) {
|
|
83
|
+
function s(t) {
|
|
83
84
|
return e({
|
|
84
85
|
name: "@usenagi/scheduler",
|
|
85
86
|
install(e) {
|
|
86
|
-
let
|
|
87
|
-
e.addMountMiddleware((e, t, n) => (t,
|
|
88
|
-
let s =
|
|
89
|
-
|
|
90
|
-
s.complete() && e(t,
|
|
87
|
+
let i = o(t), a = n();
|
|
88
|
+
e.addMountMiddleware((e, t, n) => (t, o) => {
|
|
89
|
+
let s = a.add(t), c = () => {
|
|
90
|
+
i.schedule(() => {
|
|
91
|
+
s.complete() && e(t, o);
|
|
91
92
|
}, {
|
|
92
93
|
priority: n.priority,
|
|
93
94
|
signal: s.signal
|
|
@@ -96,12 +97,12 @@ function s(n) {
|
|
|
96
97
|
l ? l(t, s.signal).then(() => {
|
|
97
98
|
s.signal.aborted || c();
|
|
98
99
|
}, (e) => {
|
|
99
|
-
|
|
100
|
+
r(e) || (s.abort(), queueMicrotask(() => {
|
|
100
101
|
throw e;
|
|
101
102
|
}));
|
|
102
103
|
}) : c();
|
|
103
104
|
}), e.addUnmountMiddleware((e) => (t) => {
|
|
104
|
-
t.forEach(
|
|
105
|
+
t.forEach(a.abort), e(t);
|
|
105
106
|
});
|
|
106
107
|
}
|
|
107
108
|
});
|
package/dist/main.es.js
CHANGED
|
@@ -4,78 +4,79 @@ function e(e) {
|
|
|
4
4
|
}
|
|
5
5
|
//#endregion
|
|
6
6
|
//#region lib/core/_internal/addonRegistry.ts
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
7
|
+
var t = class {
|
|
8
|
+
#e = /* @__PURE__ */ new Set();
|
|
9
|
+
#t = [];
|
|
10
|
+
#n = [];
|
|
11
|
+
#r = [];
|
|
12
|
+
get installedAddons() {
|
|
13
|
+
return this.#e;
|
|
14
|
+
}
|
|
15
|
+
addComponentMiddleware(e) {
|
|
16
|
+
this.#t.push(e);
|
|
17
|
+
}
|
|
18
|
+
addMountMiddleware(e) {
|
|
19
|
+
this.#n.push(e);
|
|
20
|
+
}
|
|
21
|
+
addUnmountMiddleware(e) {
|
|
22
|
+
this.#r.push(e);
|
|
23
|
+
}
|
|
24
|
+
composeComponent(e) {
|
|
25
|
+
return this.#t.reduce((e, t) => t(e), e);
|
|
26
|
+
}
|
|
27
|
+
composeMount(e, t, n) {
|
|
28
|
+
return this.#n.reduce((e, r) => r(e, t, n), e);
|
|
29
|
+
}
|
|
30
|
+
composeUnmount(e) {
|
|
31
|
+
return this.#r.reduce((e, t) => t(e), e);
|
|
32
|
+
}
|
|
33
|
+
install = (e) => {
|
|
34
|
+
if (this.#e.has(e.name)) throw Error(`[nagi] addon "${e.name}" is already installed`);
|
|
35
|
+
e.install(this), this.#e.add(e.name);
|
|
34
36
|
};
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
+
}, n = () => new t();
|
|
37
38
|
//#endregion
|
|
38
39
|
//#region lib/core/error.ts
|
|
39
|
-
function
|
|
40
|
+
function r(e) {
|
|
40
41
|
let t = [], n = e;
|
|
41
42
|
for (; n;) t.unshift(n.name), n = n.parent;
|
|
42
43
|
return t.join(" > ");
|
|
43
44
|
}
|
|
44
|
-
var
|
|
45
|
+
var i = class e extends Error {
|
|
45
46
|
details;
|
|
46
47
|
constructor(e) {
|
|
47
48
|
super(`[nagi] Component error in phase "${e.phase}" for "${e.name}"${e.path ? ` (${e.path})` : ""}`, { cause: e.cause }), this.name = "LifecycleError", this.details = e;
|
|
48
49
|
}
|
|
49
|
-
static create(t,
|
|
50
|
+
static create(t, n, i, a = n.parent, o) {
|
|
50
51
|
return new e({
|
|
51
52
|
phase: t,
|
|
52
|
-
name:
|
|
53
|
-
uid:
|
|
54
|
-
path: n
|
|
53
|
+
name: n.name,
|
|
54
|
+
uid: n.uid,
|
|
55
|
+
path: r(n),
|
|
55
56
|
parentName: a?.name,
|
|
56
57
|
parentUid: a?.uid,
|
|
57
|
-
element:
|
|
58
|
+
element: n.element,
|
|
58
59
|
cause: i,
|
|
59
60
|
...o
|
|
60
61
|
});
|
|
61
62
|
}
|
|
62
63
|
};
|
|
63
|
-
function
|
|
64
|
-
return e instanceof
|
|
64
|
+
function a(e) {
|
|
65
|
+
return e instanceof i;
|
|
65
66
|
}
|
|
66
67
|
//#endregion
|
|
67
68
|
//#region lib/core/_internal/registry.ts
|
|
68
|
-
var
|
|
69
|
-
function
|
|
70
|
-
let n =
|
|
71
|
-
if (n) throw
|
|
72
|
-
|
|
69
|
+
var o = /* @__PURE__ */ new WeakMap();
|
|
70
|
+
function s(e, t) {
|
|
71
|
+
let n = o.get(e);
|
|
72
|
+
if (n) throw i.create("mount", t, /* @__PURE__ */ Error(`Component "${n.name}" (${n.uid}) is already mounted on this element`), n);
|
|
73
|
+
o.set(e, t);
|
|
73
74
|
}
|
|
74
75
|
//#endregion
|
|
75
76
|
//#region lib/core/_internal/component.ts
|
|
76
|
-
var
|
|
77
|
+
var c = /* @__PURE__ */ function(e) {
|
|
77
78
|
return e.MOUNTED = "Mounted", e.UNMOUNTED = "Unmounted", e;
|
|
78
|
-
}(
|
|
79
|
+
}(c || {}), l = 0, u = class {
|
|
79
80
|
Mounted = [];
|
|
80
81
|
Unmounted = [];
|
|
81
82
|
parent = null;
|
|
@@ -87,7 +88,7 @@ var s = /* @__PURE__ */ function(e) {
|
|
|
87
88
|
element;
|
|
88
89
|
provides = /* @__PURE__ */ new Map();
|
|
89
90
|
constructor(e, t) {
|
|
90
|
-
this.uid = `${t}.${
|
|
91
|
+
this.uid = `${t}.${l++}`, this.name = t, this.element = e;
|
|
91
92
|
}
|
|
92
93
|
onMount = () => {
|
|
93
94
|
let e = [];
|
|
@@ -95,7 +96,7 @@ var s = /* @__PURE__ */ function(e) {
|
|
|
95
96
|
let n = t();
|
|
96
97
|
typeof n == "function" && e.push(n);
|
|
97
98
|
} catch (e) {
|
|
98
|
-
console.error("[nagi] onMount hook failed",
|
|
99
|
+
console.error("[nagi] onMount hook failed", i.create("mount", this, e));
|
|
99
100
|
}
|
|
100
101
|
this.Unmounted.push(...e);
|
|
101
102
|
};
|
|
@@ -103,7 +104,7 @@ var s = /* @__PURE__ */ function(e) {
|
|
|
103
104
|
for (let e of this.Unmounted) try {
|
|
104
105
|
e();
|
|
105
106
|
} catch (e) {
|
|
106
|
-
console.error("[nagi] onUnmount cleanup failed",
|
|
107
|
+
console.error("[nagi] onUnmount cleanup failed", i.create("unmount", this, e));
|
|
107
108
|
}
|
|
108
109
|
for (let e of this.#e) e.onUnmount();
|
|
109
110
|
};
|
|
@@ -123,28 +124,28 @@ var s = /* @__PURE__ */ function(e) {
|
|
|
123
124
|
get childElements() {
|
|
124
125
|
return this.#e.map((e) => e.element);
|
|
125
126
|
}
|
|
126
|
-
},
|
|
127
|
-
function
|
|
128
|
-
if (!
|
|
129
|
-
return
|
|
127
|
+
}, d;
|
|
128
|
+
function f(e) {
|
|
129
|
+
if (!d) throw Error(`"${e}" called outside setup() will never be run.`);
|
|
130
|
+
return d;
|
|
130
131
|
}
|
|
131
|
-
function
|
|
132
|
-
let
|
|
133
|
-
|
|
132
|
+
function p(e, t, n = {}) {
|
|
133
|
+
let r = new u(t, e.name), o = d;
|
|
134
|
+
d = r;
|
|
134
135
|
try {
|
|
135
|
-
o && (
|
|
136
|
+
o && (r.parent = o), r.props = n, r.current = e.setup(t, n) || {};
|
|
136
137
|
} catch (e) {
|
|
137
|
-
throw
|
|
138
|
+
throw d = o, a(e) ? e : i.create("setup", r, e, o, { props: r.props });
|
|
138
139
|
}
|
|
139
|
-
return
|
|
140
|
+
return d = o, r;
|
|
140
141
|
}
|
|
141
142
|
//#endregion
|
|
142
143
|
//#region lib/core/app.ts
|
|
143
|
-
function
|
|
144
|
-
let e =
|
|
144
|
+
function m() {
|
|
145
|
+
let e = n(), t = (e) => {
|
|
145
146
|
for (let t of e) {
|
|
146
|
-
let e =
|
|
147
|
-
e && (e.onUnmount(),
|
|
147
|
+
let e = o.get(t);
|
|
148
|
+
e && (e.onUnmount(), o.delete(t));
|
|
148
149
|
}
|
|
149
150
|
}, r = {
|
|
150
151
|
install(...t) {
|
|
@@ -152,28 +153,28 @@ function p() {
|
|
|
152
153
|
},
|
|
153
154
|
component(t, n = {}) {
|
|
154
155
|
let r = e.composeComponent(t), i = e.composeMount((e, t) => {
|
|
155
|
-
let n =
|
|
156
|
-
return
|
|
156
|
+
let n = p(r, e, t);
|
|
157
|
+
return s(e, n), n.onMount(), n;
|
|
157
158
|
}, r, n);
|
|
158
159
|
return (e, t = {}) => i(e, t);
|
|
159
160
|
},
|
|
160
|
-
unmount(
|
|
161
|
-
e.composeUnmount(
|
|
161
|
+
unmount(n) {
|
|
162
|
+
e.composeUnmount(t)(n);
|
|
162
163
|
}
|
|
163
164
|
};
|
|
164
165
|
return r;
|
|
165
166
|
}
|
|
166
167
|
//#endregion
|
|
167
168
|
//#region lib/core/component.ts
|
|
168
|
-
function
|
|
169
|
+
function h(e) {
|
|
169
170
|
return e;
|
|
170
171
|
}
|
|
171
172
|
//#endregion
|
|
172
173
|
//#region lib/core/context.ts
|
|
173
|
-
function
|
|
174
|
+
function g() {
|
|
174
175
|
let e = Symbol();
|
|
175
176
|
return [{ _id: e }, () => {
|
|
176
|
-
let t =
|
|
177
|
+
let t = f("createContext.use");
|
|
177
178
|
for (; t !== null;) {
|
|
178
179
|
if (t.provides.has(e)) return t.provides.get(e);
|
|
179
180
|
t = t.parent;
|
|
@@ -181,35 +182,35 @@ function h() {
|
|
|
181
182
|
throw Error("createContext.use: no provider found");
|
|
182
183
|
}];
|
|
183
184
|
}
|
|
184
|
-
function
|
|
185
|
+
function _(e, t) {
|
|
185
186
|
return (n) => ({
|
|
186
187
|
name: n.name,
|
|
187
188
|
setup(r, i) {
|
|
188
|
-
return
|
|
189
|
+
return f(`withContext.${n.name}`).provides.set(e._id, t), n.setup(r, i);
|
|
189
190
|
}
|
|
190
191
|
});
|
|
191
192
|
}
|
|
192
193
|
//#endregion
|
|
193
194
|
//#region lib/core/lifecycle.ts
|
|
194
|
-
function
|
|
195
|
+
function v(e) {
|
|
195
196
|
return (t) => {
|
|
196
|
-
|
|
197
|
+
f(e)[e].push(t);
|
|
197
198
|
};
|
|
198
199
|
}
|
|
199
|
-
var
|
|
200
|
+
var y = v(c.MOUNTED), b = v(c.UNMOUNTED);
|
|
200
201
|
//#endregion
|
|
201
202
|
//#region lib/core/props.ts
|
|
202
|
-
function
|
|
203
|
+
function x() {}
|
|
203
204
|
//#endregion
|
|
204
205
|
//#region lib/core/reactivity.ts
|
|
205
|
-
var
|
|
206
|
+
var S = Symbol("watch"), C = null, w = class {
|
|
206
207
|
#e;
|
|
207
208
|
#t = /* @__PURE__ */ new Set();
|
|
208
209
|
constructor(e) {
|
|
209
210
|
this.#e = e;
|
|
210
211
|
}
|
|
211
212
|
get value() {
|
|
212
|
-
return
|
|
213
|
+
return C !== null && C.add(this), this.#e;
|
|
213
214
|
}
|
|
214
215
|
set value(e) {
|
|
215
216
|
if (Object.is(e, this.#e)) return;
|
|
@@ -217,12 +218,12 @@ var x = Symbol("watch"), S = null, C = class {
|
|
|
217
218
|
this.#e = e;
|
|
218
219
|
for (let n of Array.from(this.#t)) n(e, t);
|
|
219
220
|
}
|
|
220
|
-
[
|
|
221
|
+
[S](e) {
|
|
221
222
|
return this.#t.add(e), () => {
|
|
222
223
|
this.#t.delete(e);
|
|
223
224
|
};
|
|
224
225
|
}
|
|
225
|
-
},
|
|
226
|
+
}, T = (e) => new w(e), E = class {
|
|
226
227
|
#e;
|
|
227
228
|
constructor(e) {
|
|
228
229
|
this.#e = e;
|
|
@@ -230,54 +231,54 @@ var x = Symbol("watch"), S = null, C = class {
|
|
|
230
231
|
get value() {
|
|
231
232
|
return this.#e.value;
|
|
232
233
|
}
|
|
233
|
-
[
|
|
234
|
-
return this.#e[
|
|
234
|
+
[S](e) {
|
|
235
|
+
return this.#e[S](e);
|
|
235
236
|
}
|
|
236
|
-
},
|
|
237
|
-
function D(e, t) {
|
|
238
|
-
return e[x](t);
|
|
239
|
-
}
|
|
237
|
+
}, D = (e) => new E(e);
|
|
240
238
|
function O(e, t) {
|
|
241
|
-
|
|
239
|
+
return e[S](t);
|
|
240
|
+
}
|
|
241
|
+
function k(e, t) {
|
|
242
|
+
b(O(e, t));
|
|
242
243
|
}
|
|
243
|
-
function
|
|
244
|
-
let t =
|
|
244
|
+
function A(e) {
|
|
245
|
+
let t = T(void 0), n = [], r = () => {
|
|
245
246
|
n.forEach((e) => {
|
|
246
247
|
e();
|
|
247
248
|
}), n = [];
|
|
248
249
|
}, i = () => {
|
|
249
250
|
r();
|
|
250
|
-
let a =
|
|
251
|
-
|
|
251
|
+
let a = C, o = /* @__PURE__ */ new Set();
|
|
252
|
+
C = o;
|
|
252
253
|
let s;
|
|
253
254
|
try {
|
|
254
255
|
s = e();
|
|
255
256
|
} finally {
|
|
256
|
-
|
|
257
|
+
C = a;
|
|
257
258
|
}
|
|
258
259
|
t.value = s;
|
|
259
|
-
for (let e of o) n.push(e[
|
|
260
|
+
for (let e of o) n.push(e[S](() => {
|
|
260
261
|
i();
|
|
261
262
|
}));
|
|
262
263
|
};
|
|
263
|
-
return i(),
|
|
264
|
+
return i(), b(r), D(t);
|
|
264
265
|
}
|
|
265
266
|
//#endregion
|
|
266
267
|
//#region lib/hooks/core/useDomRef.ts
|
|
267
|
-
function
|
|
268
|
+
function j(e, t) {
|
|
268
269
|
return t.some((t) => t !== e && t.contains(e));
|
|
269
270
|
}
|
|
270
|
-
function
|
|
271
|
-
let r = `[data-ref="${CSS.escape(e)}"]`, i = Array.from(t.querySelectorAll(r)).filter((e) => !
|
|
271
|
+
function M(e, t, n) {
|
|
272
|
+
let r = `[data-ref="${CSS.escape(e)}"]`, i = Array.from(t.querySelectorAll(r)).filter((e) => !j(e, n));
|
|
272
273
|
return i.length === 0 ? null : i.length === 1 ? i[0] : i;
|
|
273
274
|
}
|
|
274
|
-
function
|
|
275
|
+
function N(e, t) {
|
|
275
276
|
let n = /* @__PURE__ */ new Map();
|
|
276
277
|
return new Proxy({}, {
|
|
277
278
|
get(r, i) {
|
|
278
279
|
if (typeof i == "symbol" || i === "then") return;
|
|
279
280
|
if (n.has(i)) return n.get(i);
|
|
280
|
-
let a =
|
|
281
|
+
let a = M(i, e, t());
|
|
281
282
|
return n.set(i, a), a;
|
|
282
283
|
},
|
|
283
284
|
has(e, t) {
|
|
@@ -295,18 +296,18 @@ function M(e, t) {
|
|
|
295
296
|
}
|
|
296
297
|
});
|
|
297
298
|
}
|
|
298
|
-
function
|
|
299
|
-
let e =
|
|
300
|
-
return { refs:
|
|
299
|
+
function P() {
|
|
300
|
+
let e = f("useDomRef");
|
|
301
|
+
return { refs: N(e.element, () => e.childElements) };
|
|
301
302
|
}
|
|
302
303
|
//#endregion
|
|
303
304
|
//#region lib/hooks/core/useSlot.ts
|
|
304
|
-
function
|
|
305
|
-
let e =
|
|
305
|
+
function F() {
|
|
306
|
+
let e = f("useSlot");
|
|
306
307
|
return {
|
|
307
308
|
addChild(t, n, r) {
|
|
308
309
|
let i = (t) => {
|
|
309
|
-
let i =
|
|
310
|
+
let i = p(n, t, r);
|
|
310
311
|
return e.addChild(i), i;
|
|
311
312
|
};
|
|
312
313
|
return Array.isArray(t) ? t.map((e) => i(e)) : [i(t)];
|
|
@@ -316,7 +317,7 @@ function P() {
|
|
|
316
317
|
try {
|
|
317
318
|
e.removeChild(t);
|
|
318
319
|
} catch (n) {
|
|
319
|
-
console.error("[nagi] removeChild failed",
|
|
320
|
+
console.error("[nagi] removeChild failed", i.create("removeChild", t, n, e));
|
|
320
321
|
}
|
|
321
322
|
});
|
|
322
323
|
}
|
|
@@ -324,14 +325,14 @@ function P() {
|
|
|
324
325
|
}
|
|
325
326
|
//#endregion
|
|
326
327
|
//#region lib/hooks/useEvent.ts
|
|
327
|
-
function
|
|
328
|
-
|
|
328
|
+
function I(e, t, n, r) {
|
|
329
|
+
y(() => (e.addEventListener(t, n, r), () => {
|
|
329
330
|
e.removeEventListener(t, n, r);
|
|
330
331
|
}));
|
|
331
332
|
}
|
|
332
333
|
//#endregion
|
|
333
334
|
//#region lib/hooks/useIntersectionWatch.ts
|
|
334
|
-
function
|
|
335
|
+
function L(e, t, n = {
|
|
335
336
|
rootMargin: "0px",
|
|
336
337
|
threshold: .1
|
|
337
338
|
}) {
|
|
@@ -341,7 +342,7 @@ function I(e, t, n = {
|
|
|
341
342
|
r.observe(e);
|
|
342
343
|
}) : r.observe(e);
|
|
343
344
|
}
|
|
344
|
-
|
|
345
|
+
y(() => (i(e), () => {
|
|
345
346
|
r.disconnect();
|
|
346
347
|
}));
|
|
347
348
|
function a(e) {
|
|
@@ -351,14 +352,14 @@ function I(e, t, n = {
|
|
|
351
352
|
}
|
|
352
353
|
//#endregion
|
|
353
354
|
//#region lib/hooks/useMediaQuery.ts
|
|
354
|
-
function
|
|
355
|
-
let n = window.matchMedia(e), r =
|
|
355
|
+
function R(e, t) {
|
|
356
|
+
let n = window.matchMedia(e), r = T(n.matches), i = null;
|
|
356
357
|
function a(e) {
|
|
357
358
|
r.value = e.matches, e.matches ? i = t() : (i?.(), i = null);
|
|
358
359
|
}
|
|
359
|
-
return
|
|
360
|
+
return y(() => (n.addEventListener("change", a), n.matches && (i = t()), () => {
|
|
360
361
|
i?.(), n.removeEventListener("change", a);
|
|
361
|
-
})), { matchesQuery:
|
|
362
|
+
})), { matchesQuery: D(r) };
|
|
362
363
|
}
|
|
363
364
|
//#endregion
|
|
364
|
-
export {
|
|
365
|
+
export { i as LifecycleError, m as create, g as createContext, e as defineAddon, h as defineComponent, a as isLifecycleError, x as propTypes, D as readonly, T as signal, A as useComputed, P as useDomRef, I as useEvent, L as useIntersectionWatch, R as useMediaQuery, y as useMount, F as useSlot, b as useUnmount, k as useWatch, _ as withContext };
|
package/dist/main.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.Nagi={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(e){return e}
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.Nagi={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(e){return e}var n=class{#e=new Set;#t=[];#n=[];#r=[];get installedAddons(){return this.#e}addComponentMiddleware(e){this.#t.push(e)}addMountMiddleware(e){this.#n.push(e)}addUnmountMiddleware(e){this.#r.push(e)}composeComponent(e){return this.#t.reduce((e,t)=>t(e),e)}composeMount(e,t,n){return this.#n.reduce((e,r)=>r(e,t,n),e)}composeUnmount(e){return this.#r.reduce((e,t)=>t(e),e)}install=e=>{if(this.#e.has(e.name))throw Error(`[nagi] addon "${e.name}" is already installed`);e.install(this),this.#e.add(e.name)}},r=()=>new n;function i(e){let t=[],n=e;for(;n;)t.unshift(n.name),n=n.parent;return t.join(` > `)}var a=class e extends Error{details;constructor(e){super(`[nagi] Component error in phase "${e.phase}" for "${e.name}"${e.path?` (${e.path})`:``}`,{cause:e.cause}),this.name=`LifecycleError`,this.details=e}static create(t,n,r,a=n.parent,o){return new e({phase:t,name:n.name,uid:n.uid,path:i(n),parentName:a?.name,parentUid:a?.uid,element:n.element,cause:r,...o})}};function o(e){return e instanceof a}var s=new WeakMap;function c(e,t){let n=s.get(e);if(n)throw a.create(`mount`,t,Error(`Component "${n.name}" (${n.uid}) is already mounted on this element`),n);s.set(e,t)}var l=function(e){return e.MOUNTED=`Mounted`,e.UNMOUNTED=`Unmounted`,e}(l||{}),u=0,d=class{Mounted=[];Unmounted=[];parent=null;#e=[];uid;name;current={};props={};element;provides=new Map;constructor(e,t){this.uid=`${t}.${u++}`,this.name=t,this.element=e}onMount=()=>{let e=[];for(let t of this.Mounted)try{let n=t();typeof n==`function`&&e.push(n)}catch(e){console.error(`[nagi] onMount hook failed`,a.create(`mount`,this,e))}this.Unmounted.push(...e)};onUnmount=()=>{for(let e of this.Unmounted)try{e()}catch(e){console.error(`[nagi] onUnmount cleanup failed`,a.create(`unmount`,this,e))}for(let e of this.#e)e.onUnmount()};addChild=e=>{this.#e.push(e),e.parent=this;try{e.onMount()}catch(t){let n=this.#e.indexOf(e);throw n!==-1&&this.#e.splice(n,1),e.parent=null,t}};removeChild=e=>{let t=this.#e.indexOf(e);t!==-1&&(this.#e.splice(t,1),e.parent=null,e.onUnmount())};get childElements(){return this.#e.map(e=>e.element)}},f;function p(e){if(!f)throw Error(`"${e}" called outside setup() will never be run.`);return f}function m(e,t,n={}){let r=new d(t,e.name),i=f;f=r;try{i&&(r.parent=i),r.props=n,r.current=e.setup(t,n)||{}}catch(e){throw f=i,o(e)?e:a.create(`setup`,r,e,i,{props:r.props})}return f=i,r}function h(){let e=r(),t=e=>{for(let t of e){let e=s.get(t);e&&(e.onUnmount(),s.delete(t))}},n={install(...t){return t.forEach(e.install),n},component(t,n={}){let r=e.composeComponent(t),i=e.composeMount((e,t)=>{let n=m(r,e,t);return c(e,n),n.onMount(),n},r,n);return(e,t={})=>i(e,t)},unmount(n){e.composeUnmount(t)(n)}};return n}function g(e){return e}function _(){let e=Symbol();return[{_id:e},()=>{let t=p(`createContext.use`);for(;t!==null;){if(t.provides.has(e))return t.provides.get(e);t=t.parent}throw Error(`createContext.use: no provider found`)}]}function v(e,t){return n=>({name:n.name,setup(r,i){return p(`withContext.${n.name}`).provides.set(e._id,t),n.setup(r,i)}})}function y(e){return t=>{p(e)[e].push(t)}}var b=y(l.MOUNTED),x=y(l.UNMOUNTED);function S(){}var C=Symbol(`watch`),w=null,T=class{#e;#t=new Set;constructor(e){this.#e=e}get value(){return w!==null&&w.add(this),this.#e}set value(e){if(Object.is(e,this.#e))return;let t=this.#e;this.#e=e;for(let n of Array.from(this.#t))n(e,t)}[C](e){return this.#t.add(e),()=>{this.#t.delete(e)}}},E=e=>new T(e),D=class{#e;constructor(e){this.#e=e}get value(){return this.#e.value}[C](e){return this.#e[C](e)}},O=e=>new D(e);function k(e,t){return e[C](t)}function A(e,t){x(k(e,t))}function j(e){let t=E(void 0),n=[],r=()=>{n.forEach(e=>{e()}),n=[]},i=()=>{r();let a=w,o=new Set;w=o;let s;try{s=e()}finally{w=a}t.value=s;for(let e of o)n.push(e[C](()=>{i()}))};return i(),x(r),O(t)}function M(e,t){return t.some(t=>t!==e&&t.contains(e))}function N(e,t,n){let r=`[data-ref="${CSS.escape(e)}"]`,i=Array.from(t.querySelectorAll(r)).filter(e=>!M(e,n));return i.length===0?null:i.length===1?i[0]:i}function P(e,t){let n=new Map;return new Proxy({},{get(r,i){if(typeof i==`symbol`||i===`then`)return;if(n.has(i))return n.get(i);let a=N(i,e,t());return n.set(i,a),a},has(e,t){return typeof t==`string`},ownKeys(){return[]},getOwnPropertyDescriptor(){},set(){return!1},deleteProperty(){return!1}})}function F(){let e=p(`useDomRef`);return{refs:P(e.element,()=>e.childElements)}}function I(){let e=p(`useSlot`);return{addChild(t,n,r){let i=t=>{let i=m(n,t,r);return e.addChild(i),i};return Array.isArray(t)?t.map(e=>i(e)):[i(t)]},removeChild(t){t.forEach(t=>{try{e.removeChild(t)}catch(n){console.error(`[nagi] removeChild failed`,a.create(`removeChild`,t,n,e))}})}}}function L(e,t,n,r){b(()=>(e.addEventListener(t,n,r),()=>{e.removeEventListener(t,n,r)}))}function R(e,t,n={rootMargin:`0px`,threshold:.1}){let r=new IntersectionObserver(t,n);function i(e){Array.isArray(e)?e.forEach(e=>{r.observe(e)}):r.observe(e)}b(()=>(i(e),()=>{r.disconnect()}));function a(e){r.unobserve(e)}return{unwatch:a}}function z(e,t){let n=window.matchMedia(e),r=E(n.matches),i=null;function a(e){r.value=e.matches,e.matches?i=t():(i?.(),i=null)}return b(()=>(n.addEventListener(`change`,a),n.matches&&(i=t()),()=>{i?.(),n.removeEventListener(`change`,a)})),{matchesQuery:O(r)}}e.LifecycleError=a,e.create=h,e.createContext=_,e.defineAddon=t,e.defineComponent=g,e.isLifecycleError=o,e.propTypes=S,e.readonly=O,e.signal=E,e.useComputed=j,e.useDomRef=F,e.useEvent=L,e.useIntersectionWatch=R,e.useMediaQuery=z,e.useMount=b,e.useSlot=I,e.useUnmount=x,e.useWatch=A,e.withContext=v});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usenagi/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Composition-API ergonomics for vanilla DOM. Bring your own mounter.",
|
|
5
5
|
"main": "./dist/main.umd.js",
|
|
6
6
|
"module": "./dist/main.es.js",
|
|
@@ -23,18 +23,22 @@
|
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"dist",
|
|
26
|
-
"types"
|
|
26
|
+
"types",
|
|
27
|
+
"README.md",
|
|
28
|
+
"README.ja.md",
|
|
29
|
+
"LICENSE"
|
|
27
30
|
],
|
|
28
31
|
"author": "hayakawasho",
|
|
29
32
|
"license": "MIT",
|
|
30
33
|
"repository": {
|
|
31
34
|
"type": "git",
|
|
32
|
-
"url": "git+https://github.com/hayakawasho/nagi.git"
|
|
35
|
+
"url": "git+https://github.com/hayakawasho/nagi.git",
|
|
36
|
+
"directory": "packages/core"
|
|
33
37
|
},
|
|
34
38
|
"bugs": {
|
|
35
39
|
"url": "https://github.com/hayakawasho/nagi/issues"
|
|
36
40
|
},
|
|
37
|
-
"homepage": "https://github.com/hayakawasho/nagi
|
|
41
|
+
"homepage": "https://github.com/hayakawasho/nagi/blob/main/packages/core/README.md",
|
|
38
42
|
"keywords": [
|
|
39
43
|
"dom",
|
|
40
44
|
"component",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RefElement } from "@usenagi/core";
|
|
2
|
+
type DeferredMount = {
|
|
3
|
+
readonly signal: AbortSignal;
|
|
4
|
+
complete(): boolean;
|
|
5
|
+
abort(): void;
|
|
6
|
+
};
|
|
7
|
+
declare class DeferredMounts {
|
|
8
|
+
#private;
|
|
9
|
+
add(el: RefElement): DeferredMount;
|
|
10
|
+
abort: (el: RefElement) => void;
|
|
11
|
+
}
|
|
12
|
+
declare function createDeferredMounts(): DeferredMounts;
|
|
13
|
+
export {};
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import type { ComponentSetup } from "../../types";
|
|
2
|
-
import type { Addon, AddonContext,
|
|
3
|
-
type
|
|
1
|
+
import type { ComponentSetup, RefElement } from "../../types";
|
|
2
|
+
import type { Addon, AddonContext, MountOptions } from "../addon";
|
|
3
|
+
type MountFn = (el: RefElement, props: Record<string, any>) => any;
|
|
4
|
+
type UnmountFn = (targets: RefElement[]) => void;
|
|
5
|
+
export type ComponentMiddleware = <S extends ComponentSetup>(comp: S) => S;
|
|
6
|
+
export type MountMiddleware = (next: MountFn, setup: ComponentSetup, opts: MountOptions) => MountFn;
|
|
7
|
+
export type UnmountMiddleware = (next: UnmountFn) => UnmountFn;
|
|
8
|
+
declare class AddonRegistry implements AddonContext {
|
|
9
|
+
#private;
|
|
10
|
+
get installedAddons(): ReadonlySet<string>;
|
|
11
|
+
addComponentMiddleware(middleware: ComponentMiddleware): void;
|
|
12
|
+
addMountMiddleware(middleware: MountMiddleware): void;
|
|
13
|
+
addUnmountMiddleware(middleware: UnmountMiddleware): void;
|
|
4
14
|
composeComponent<S extends ComponentSetup>(setup: S): S;
|
|
5
15
|
composeMount(mountFn: MountFn, setup: ComponentSetup, opts: MountOptions): MountFn;
|
|
6
16
|
composeUnmount(unmountFn: UnmountFn): UnmountFn;
|
|
7
|
-
install(addon: Addon)
|
|
8
|
-
}
|
|
9
|
-
declare
|
|
17
|
+
install: (addon: Addon) => void;
|
|
18
|
+
}
|
|
19
|
+
declare const createAddonRegistry: () => AddonRegistry;
|
|
10
20
|
export {};
|
package/types/core/addon.d.ts
CHANGED
|
@@ -1,25 +1,19 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ComponentMiddleware, MountMiddleware, UnmountMiddleware } from "./_internal/addonRegistry";
|
|
2
2
|
declare const mountOptionsBrand: unique symbol;
|
|
3
3
|
/** Options for `app.component(setup, opts)` — extended by mount addons. */
|
|
4
4
|
export interface MountOptions {
|
|
5
5
|
readonly [mountOptionsBrand]?: never;
|
|
6
6
|
}
|
|
7
|
-
/** Mount after addon middleware runs — may return void when mount is deferred (e.g. scheduler). */
|
|
8
|
-
export type MountFn = (el: RefElement, props: Record<string, any>) => any;
|
|
9
|
-
export type UnmountFn = (targets: RefElement[]) => void;
|
|
10
|
-
export type ComponentMiddleware = <S extends ComponentSetup>(comp: S) => S;
|
|
11
|
-
export type MountMiddleware = (next: MountFn, setup: ComponentSetup, opts: MountOptions) => MountFn;
|
|
12
|
-
export type UnmountMiddleware = (next: UnmountFn) => UnmountFn;
|
|
13
|
-
export type Addon = {
|
|
14
|
-
readonly name: string;
|
|
15
|
-
install(ctx: AddonContext): void;
|
|
16
|
-
};
|
|
17
7
|
export type AddonContext = {
|
|
18
8
|
readonly installedAddons: ReadonlySet<string>;
|
|
19
9
|
addComponentMiddleware(middleware: ComponentMiddleware): void;
|
|
20
10
|
addMountMiddleware(middleware: MountMiddleware): void;
|
|
21
11
|
addUnmountMiddleware(middleware: UnmountMiddleware): void;
|
|
22
12
|
};
|
|
13
|
+
export type Addon = {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
install(ctx: AddonContext): void;
|
|
16
|
+
};
|
|
23
17
|
/**
|
|
24
18
|
* Identity helper for type inference only — no runtime effect.
|
|
25
19
|
*/
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { RefElement } from "@usenagi/core";
|
|
2
|
-
type PendingMounts = {
|
|
3
|
-
add(el: RefElement): {
|
|
4
|
-
readonly signal: AbortSignal;
|
|
5
|
-
complete(): boolean;
|
|
6
|
-
abort(): void;
|
|
7
|
-
};
|
|
8
|
-
abort(el: RefElement): void;
|
|
9
|
-
};
|
|
10
|
-
declare function createPendingMounts(): PendingMounts;
|
|
11
|
-
export {};
|