@usenagi/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/dist/addons/cue.cjs.js +1 -0
- package/dist/addons/cue.es.js +77 -0
- package/dist/addons/scheduler.cjs.js +1 -0
- package/dist/addons/scheduler.es.js +52 -0
- package/dist/main.es.js +377 -0
- package/dist/main.umd.js +1 -0
- package/package.json +71 -0
- package/types/addons/cue/index.d.ts +5 -0
- package/types/addons/scheduler/index.d.ts +4 -0
- package/types/addons/scheduler/task.d.ts +2 -0
- package/types/core/app.d.ts +27 -0
- package/types/core/component.d.ts +28 -0
- package/types/core/error.d.ts +20 -0
- package/types/core/internal/pending.d.ts +11 -0
- package/types/core/internal/registry.d.ts +4 -0
- package/types/core/lifecycle.d.ts +3 -0
- package/types/core/reactivity.d.ts +22 -0
- package/types/core/runtime.d.ts +4 -0
- package/types/hooks/createContext.d.ts +7 -0
- package/types/hooks/domRefs.d.ts +2 -0
- package/types/hooks/useDomRef.d.ts +4 -0
- package/types/hooks/useEvent.d.ts +3 -0
- package/types/hooks/useIntersectionWatch.d.ts +3 -0
- package/types/hooks/useMediaQuery.d.ts +4 -0
- package/types/hooks/useRootRef.d.ts +2 -0
- package/types/hooks/useSlot.d.ts +6 -0
- package/types/main.d.ts +17 -0
- package/types/types.d.ts +18 -0
- package/types/utils/isAbortError.d.ts +1 -0
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.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# nagi
|
|
2
|
+
|
|
3
|
+
**Composition-style ergonomics for vanilla DOM. Bring your own mounter.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@usenagi/core)
|
|
6
|
+
[](https://bundlephobia.com/package/@usenagi/core)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Why nagi?
|
|
12
|
+
|
|
13
|
+
**既存 HTML に小さく足せる**
|
|
14
|
+
WordPress、CMS、Webflow、静的サイトなどに、仮想 DOM やテンプレートを持ち込まず `setup()` / lifecycle / reactivity を追加できる。
|
|
15
|
+
|
|
16
|
+
**アニメーションと相性が良い**
|
|
17
|
+
GSAP、Lenis、IntersectionObserver などを `setup()` で初期化し、`useUnmount()` でクリーンアップできる。
|
|
18
|
+
|
|
19
|
+
**マウント戦略を縛らない**
|
|
20
|
+
`[data-component]` スキャン、manifest、lazy import、MutationObserver などは利用側で自由に組める。
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 30-second example
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// counter.ts
|
|
28
|
+
import { create, ref, useWatch, useDomRef } from "@usenagi/core";
|
|
29
|
+
|
|
30
|
+
const { component } = create();
|
|
31
|
+
|
|
32
|
+
component({
|
|
33
|
+
name: "counter",
|
|
34
|
+
setup() {
|
|
35
|
+
const { refs } = useDomRef<{
|
|
36
|
+
count: HTMLSpanElement;
|
|
37
|
+
btn: HTMLButtonElement;
|
|
38
|
+
}>();
|
|
39
|
+
|
|
40
|
+
const n = ref(0);
|
|
41
|
+
useWatch(n, (v) => {
|
|
42
|
+
refs.count.textContent = String(v);
|
|
43
|
+
});
|
|
44
|
+
refs.btn.addEventListener("click", () => {
|
|
45
|
+
n.value++;
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
})(document.querySelector("#counter")!);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```html
|
|
52
|
+
<div id="counter">
|
|
53
|
+
<span data-ref="count">0</span>
|
|
54
|
+
<button data-ref="btn">+</button>
|
|
55
|
+
</div>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Quick start
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm i @usenagi/core
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### First component
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { create, defineComponent, ref, useWatch, useDomRef } from "@usenagi/core";
|
|
70
|
+
|
|
71
|
+
const Greeting = defineComponent({
|
|
72
|
+
name: "greeting",
|
|
73
|
+
setup(el, props) {
|
|
74
|
+
const { refs } = useDomRef<{ message: HTMLParagraphElement }>();
|
|
75
|
+
const text = ref((props.name as string) ?? "world");
|
|
76
|
+
|
|
77
|
+
useWatch(text, (v) => {
|
|
78
|
+
refs.message.textContent = `Hello, ${v}!`;
|
|
79
|
+
});
|
|
80
|
+
refs.message.textContent = `Hello, ${text.value}!`;
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
create().component(Greeting)(document.querySelector("#app")!);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Scheduler + deferred mount
|
|
88
|
+
|
|
89
|
+
遅延マウントが必要なら scheduler / cue addons を追加する。
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { create } from "@usenagi/core";
|
|
93
|
+
import { createScheduler } from "@usenagi/core/addons/scheduler";
|
|
94
|
+
import { visible, idle } from "@usenagi/core/addons/cue";
|
|
95
|
+
|
|
96
|
+
const app = create({ scheduler: createScheduler() });
|
|
97
|
+
|
|
98
|
+
// Intersection で visible になってからマウント
|
|
99
|
+
app.component(HeavyWidget, { when: visible() })(el);
|
|
100
|
+
|
|
101
|
+
// requestIdleCallback でマウント
|
|
102
|
+
app.component(Analytics, { when: idle() })(el);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`when` は `setup()` の前に待機する条件、`priority` は `setup()` を含む mount task の実行タイミングを決める。
|
|
106
|
+
|
|
107
|
+
### BYO mounter recipe
|
|
108
|
+
|
|
109
|
+
`[data-component]` スキャン、manifest、cue を組み合わせて自動マウントする一例。
|
|
110
|
+
→ [examples/recipes/byo-mounter](./examples/recipes/byo-mounter/main.ts)
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## API
|
|
115
|
+
|
|
116
|
+
### Reactivity
|
|
117
|
+
|
|
118
|
+
| API | 説明 |
|
|
119
|
+
| ------------------- | -------------------------------------------- |
|
|
120
|
+
| `ref(value)` | リアクティブな参照を作成 |
|
|
121
|
+
| `readonly(ref)` | 読み取り専用ラッパー |
|
|
122
|
+
| `computed(fn)` | 依存する `ref` を自動追跡する派生値 |
|
|
123
|
+
| `useWatch(ref, cb)` | 値変更時にコールバック。unmount 時に自動解除 |
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const width = ref(10);
|
|
127
|
+
const height = ref(5);
|
|
128
|
+
const area = computed(() => width.value * height.value); // 自動再計算
|
|
129
|
+
|
|
130
|
+
useWatch(area, (v) => {
|
|
131
|
+
output.textContent = String(v);
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Lifecycle
|
|
136
|
+
|
|
137
|
+
| API | 説明 |
|
|
138
|
+
| ---------------- | ------------------------------------------ |
|
|
139
|
+
| `useMount(fn)` | マウント完了後に1回実行 |
|
|
140
|
+
| `useUnmount(fn)` | アンマウント時に実行。クリーンアップに使う |
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import gsap from 'gsap';
|
|
144
|
+
|
|
145
|
+
setup(el) {
|
|
146
|
+
const tween = gsap.from(el, { opacity: 0, duration: 0.4 });
|
|
147
|
+
useUnmount(() => tween.kill());
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### DOM helpers
|
|
152
|
+
|
|
153
|
+
| API | 説明 |
|
|
154
|
+
| ------------------------------ | ----------------------------------------------- |
|
|
155
|
+
| `useDomRef<T>()` | `[data-ref]` 要素を型付きで取得 |
|
|
156
|
+
| `useRootRef()` | ルート要素を取得 |
|
|
157
|
+
| `useEvent(el, event, handler)` | イベントリスナーを登録。unmount 時に自動除去 |
|
|
158
|
+
| `useSlot()` | 子コンポーネントをマウント。親の unmount に連動 |
|
|
159
|
+
|
|
160
|
+
### Parent / child
|
|
161
|
+
|
|
162
|
+
`useSlot()` で子コンポーネントをマウントできる。親から子へは `props` または `createContext` / `withContext` で値を渡せる。`addChild()` が返す child context から、子の `setup()` 返り値も参照できる。
|
|
163
|
+
|
|
164
|
+
→ [examples/parent-child](./examples/parent-child/main.ts)
|
|
165
|
+
|
|
166
|
+
### Observers
|
|
167
|
+
|
|
168
|
+
| API | 説明 |
|
|
169
|
+
| --------------------------------- | --------------------------------------------------- |
|
|
170
|
+
| `useIntersectionWatch(cb, opts?)` | IntersectionObserver。unmount 時に自動解除 |
|
|
171
|
+
| `useMediaQuery(query)` | `matchMedia` の結果を `ReadonlyRef<boolean>` で返す |
|
|
172
|
+
|
|
173
|
+
### Addons
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import { createScheduler } from "@usenagi/core/addons/scheduler";
|
|
177
|
+
import { visible, idle, interaction, media } from "@usenagi/core/addons/cue";
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
| API | 説明 |
|
|
181
|
+
| ------------------------ | ---------------------------------------------------------------------------- |
|
|
182
|
+
| `createScheduler(opts?)` | `scheduler.schedule(task, { priority, signal })` を実装した Scheduler を返す |
|
|
183
|
+
| `visible(opts?)` | 要素が viewport に入ったら解決する Cue |
|
|
184
|
+
| `idle(timeout?)` | `requestIdleCallback` で解決する Cue |
|
|
185
|
+
| `interaction(events?)` | 最初のユーザー操作で解決する Cue |
|
|
186
|
+
| `media(query)` | media query が一致したら解決する Cue |
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Comparison
|
|
191
|
+
|
|
192
|
+
| | **nagi** | Alpine.js | Stimulus | petite-vue |
|
|
193
|
+
| ----------------------- | ----------- | --------- | -------- | ---------- |
|
|
194
|
+
| Inline JS in HTML | ✗ | ◯ | ✗ | ◯ |
|
|
195
|
+
| Composition-style setup | ◯ | △ | ✗ | ◯ |
|
|
196
|
+
| BYO mounter | ◯ | △ | △ | △ |
|
|
197
|
+
| Async mount cue | ◯ | ✗ | ✗ | ✗ |
|
|
198
|
+
| Lifecycle cleanup | ◯ | △ | ◯ | △ |
|
|
199
|
+
| `computed` | ◯ | ◯ | ✗ | ◯ |
|
|
200
|
+
| Core gzip | ~2.6-2.9 kB | ~16 kB | ~8 kB | ~6 kB |
|
|
201
|
+
|
|
202
|
+
◯ = built-in、△ = userland / convention で対応可能、✗ = primary feature ではない。
|
|
203
|
+
|
|
204
|
+
- **vs Alpine / petite-vue**: HTML に式を書かず、ロジックを `.ts` に寄せる。
|
|
205
|
+
- **vs Stimulus**: Controller 規約なし。mounter は利用側で自由に組める。
|
|
206
|
+
- **vs React / Vue**: 宣言的 UI フレームワークではなく、既存 DOM に lifecycle を足す layer。
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## When to use / When not to
|
|
211
|
+
|
|
212
|
+
**向いているケース**:
|
|
213
|
+
|
|
214
|
+
- React/Vue ほどのランタイムを持ち込みにくいプロジェクト(CMS、Webflow、WordPress 等)
|
|
215
|
+
- GSAP/Lenis を多用する、アニメーション主体のサイト
|
|
216
|
+
- ページの一部だけに interactive な UI を追加したい
|
|
217
|
+
- Composition-style な `setup()` / lifecycle / reactivity で書きたいが、仮想 DOM は不要
|
|
218
|
+
|
|
219
|
+
**向いていないケース**:
|
|
220
|
+
|
|
221
|
+
- リスト描画や条件分岐を HTML テンプレートで書きたい(`v-for`, `v-if` 相当は持たない)
|
|
222
|
+
- 深いオブジェクトのリアクティビティが必要(`reactive({})` は提供しない)
|
|
223
|
+
- SSR/hydration が必要
|
|
224
|
+
- アプリ全体の状態管理、ルーティング、宣言的な view rendering をまとめて任せたい
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Examples
|
|
229
|
+
|
|
230
|
+
| Example | 説明 |
|
|
231
|
+
| ----------------------------------------------------- | -------------------------------------------- |
|
|
232
|
+
| [basic-counter](./examples/basic-counter/) | `ref` + `useWatch` の最小例 |
|
|
233
|
+
| [computed](./examples/computed/) | `computed` で派生値 (width × height = area) |
|
|
234
|
+
| [parent-child](./examples/parent-child/) | `createContext` + `withContext` + `useSlot` |
|
|
235
|
+
| [lenis-scroll-scene](./examples/lenis-scroll-scene/) | Lenis + `computed` でスクロール進捗連動 |
|
|
236
|
+
| [byo-mounter recipe](./examples/recipes/byo-mounter/) | `[data-component]` スキャン + manifest + cue |
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
MIT © [hayakawasho](https://github.com/hayakawasho)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(){return new DOMException(`aborted`,`AbortError`)}function t(t,n,r){t.addEventListener(`abort`,()=>{n(),r(e())},{once:!0})}function n(n){return(r,i)=>new Promise((a,o)=>{if(i.aborted){o(e());return}let s=new IntersectionObserver(e=>{for(let t of e)if(t.isIntersecting){s.disconnect(),a();return}},n);s.observe(r),t(i,()=>s.disconnect(),o)})}function r(n){return(r,i)=>new Promise((r,a)=>{if(i.aborted){a(e());return}if(typeof requestIdleCallback!=`function`){let e=setTimeout(()=>r(),n??0);t(i,()=>clearTimeout(e),a);return}let o=requestIdleCallback(()=>r(),n==null?void 0:{timeout:n});t(i,()=>cancelIdleCallback(o),a)})}function i(n){return(r,i)=>new Promise((r,a)=>{if(i.aborted){a(e());return}let o=matchMedia(n);if(o.matches){r();return}let s=()=>{o.matches&&(o.removeEventListener(`change`,s),r())};o.addEventListener(`change`,s),t(i,()=>o.removeEventListener(`change`,s),a)})}function a(n=[`click`,`focus`,`pointerenter`]){return(r,i)=>new Promise((a,o)=>{if(i.aborted){o(e());return}let s=()=>{for(let e of n)r.removeEventListener(e,c)},c=()=>{s(),a()};for(let e of n)r.addEventListener(e,c,{once:!0});t(i,s,o)})}exports.idle=r,exports.interaction=a,exports.media=i,exports.visible=n;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
//#region lib/addons/cue/index.ts
|
|
2
|
+
function e() {
|
|
3
|
+
return new DOMException("aborted", "AbortError");
|
|
4
|
+
}
|
|
5
|
+
function t(t, n, r) {
|
|
6
|
+
t.addEventListener("abort", () => {
|
|
7
|
+
n(), r(e());
|
|
8
|
+
}, { once: !0 });
|
|
9
|
+
}
|
|
10
|
+
function n(n) {
|
|
11
|
+
return (r, i) => new Promise((a, o) => {
|
|
12
|
+
if (i.aborted) {
|
|
13
|
+
o(e());
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
let s = new IntersectionObserver((e) => {
|
|
17
|
+
for (let t of e) if (t.isIntersecting) {
|
|
18
|
+
s.disconnect(), a();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
}, n);
|
|
22
|
+
s.observe(r), t(i, () => s.disconnect(), o);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function r(n) {
|
|
26
|
+
return (r, i) => new Promise((r, a) => {
|
|
27
|
+
if (i.aborted) {
|
|
28
|
+
a(e());
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (typeof requestIdleCallback != "function") {
|
|
32
|
+
let e = setTimeout(() => r(), n ?? 0);
|
|
33
|
+
t(i, () => clearTimeout(e), a);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let o = requestIdleCallback(() => r(), n == null ? void 0 : { timeout: n });
|
|
37
|
+
t(i, () => cancelIdleCallback(o), a);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function i(n) {
|
|
41
|
+
return (r, i) => new Promise((r, a) => {
|
|
42
|
+
if (i.aborted) {
|
|
43
|
+
a(e());
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
let o = matchMedia(n);
|
|
47
|
+
if (o.matches) {
|
|
48
|
+
r();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
let s = () => {
|
|
52
|
+
o.matches && (o.removeEventListener("change", s), r());
|
|
53
|
+
};
|
|
54
|
+
o.addEventListener("change", s), t(i, () => o.removeEventListener("change", s), a);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function a(n = [
|
|
58
|
+
"click",
|
|
59
|
+
"focus",
|
|
60
|
+
"pointerenter"
|
|
61
|
+
]) {
|
|
62
|
+
return (r, i) => new Promise((a, o) => {
|
|
63
|
+
if (i.aborted) {
|
|
64
|
+
o(e());
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
let s = () => {
|
|
68
|
+
for (let e of n) r.removeEventListener(e, c);
|
|
69
|
+
}, c = () => {
|
|
70
|
+
s(), a();
|
|
71
|
+
};
|
|
72
|
+
for (let e of n) r.addEventListener(e, c, { once: !0 });
|
|
73
|
+
t(i, s, o);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { r as idle, a as interaction, i as media, n as visible };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){return(e instanceof DOMException||e instanceof Error)&&e.name===`AbortError`}function t(t,r,i){if(i?.aborted)return;let{scheduler:a}=globalThis;if(typeof a?.postTask==`function`){a.postTask(t,{priority:r,signal:i}).catch(t=>{e(t)||queueMicrotask(()=>{throw t})});return}n(t,r,i)}function n(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 r(e={}){let n=e.priority??`user-visible`;return{schedule(e,r={}){t(e,r.priority??n,r.signal)}}}exports.createScheduler=r;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region lib/utils/isAbortError.ts
|
|
2
|
+
function e(e) {
|
|
3
|
+
return (e instanceof DOMException || e instanceof Error) && e.name === "AbortError";
|
|
4
|
+
}
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region lib/addons/scheduler/task.ts
|
|
7
|
+
function t(t, r, i) {
|
|
8
|
+
if (i?.aborted) return;
|
|
9
|
+
let { scheduler: a } = globalThis;
|
|
10
|
+
if (typeof a?.postTask == "function") {
|
|
11
|
+
a.postTask(t, {
|
|
12
|
+
priority: r,
|
|
13
|
+
signal: i
|
|
14
|
+
}).catch((t) => {
|
|
15
|
+
e(t) || queueMicrotask(() => {
|
|
16
|
+
throw t;
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
n(t, r, i);
|
|
22
|
+
}
|
|
23
|
+
function n(e, t, n) {
|
|
24
|
+
function r() {
|
|
25
|
+
n?.aborted || e();
|
|
26
|
+
}
|
|
27
|
+
function i(e, t) {
|
|
28
|
+
let r = e();
|
|
29
|
+
n?.addEventListener("abort", () => t(r), { once: !0 });
|
|
30
|
+
}
|
|
31
|
+
switch (t) {
|
|
32
|
+
case "user-blocking":
|
|
33
|
+
queueMicrotask(r);
|
|
34
|
+
break;
|
|
35
|
+
case "user-visible":
|
|
36
|
+
i(() => requestAnimationFrame(r), cancelAnimationFrame);
|
|
37
|
+
break;
|
|
38
|
+
case "background":
|
|
39
|
+
typeof requestIdleCallback == "function" ? i(() => requestIdleCallback(r), cancelIdleCallback) : i(() => setTimeout(r, 0), clearTimeout);
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region lib/addons/scheduler/index.ts
|
|
45
|
+
function r(e = {}) {
|
|
46
|
+
let n = e.priority ?? "user-visible";
|
|
47
|
+
return { schedule(e, r = {}) {
|
|
48
|
+
t(e, r.priority ?? n, r.signal);
|
|
49
|
+
} };
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { r as createScheduler };
|
package/dist/main.es.js
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
//#region lib/utils/isAbortError.ts
|
|
2
|
+
function e(e) {
|
|
3
|
+
return (e instanceof DOMException || e instanceof Error) && e.name === "AbortError";
|
|
4
|
+
}
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region lib/core/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
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region lib/core/error.ts
|
|
32
|
+
function n(e) {
|
|
33
|
+
let t = [], n = e;
|
|
34
|
+
for (; n;) t.unshift(n.name), n = n.parent;
|
|
35
|
+
return t.join(" > ");
|
|
36
|
+
}
|
|
37
|
+
var r = class e extends Error {
|
|
38
|
+
details;
|
|
39
|
+
constructor(e) {
|
|
40
|
+
super(`[nagi] Component error in phase "${e.phase}" for "${e.name}"${e.path ? ` (${e.path})` : ""}`, { cause: e.cause }), this.name = "LifecycleError", this.details = e;
|
|
41
|
+
}
|
|
42
|
+
static create(t, r, i, a = r.parent, o) {
|
|
43
|
+
return new e({
|
|
44
|
+
phase: t,
|
|
45
|
+
name: r.name,
|
|
46
|
+
uid: r.uid,
|
|
47
|
+
path: n(r),
|
|
48
|
+
parentName: a?.name,
|
|
49
|
+
parentUid: a?.uid,
|
|
50
|
+
element: r.element,
|
|
51
|
+
cause: i,
|
|
52
|
+
...o
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
function i(e) {
|
|
57
|
+
return e instanceof r;
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region lib/core/internal/registry.ts
|
|
61
|
+
var a = /* @__PURE__ */ new WeakMap();
|
|
62
|
+
function o(e, t) {
|
|
63
|
+
let n = a.get(e);
|
|
64
|
+
if (n) throw r.create("mount", t, /* @__PURE__ */ Error(`Component "${n.name}" (${n.uid}) is already mounted on this element`), n);
|
|
65
|
+
a.set(e, t);
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region lib/core/component.ts
|
|
69
|
+
var s = /* @__PURE__ */ function(e) {
|
|
70
|
+
return e.MOUNTED = "Mounted", e.UNMOUNTED = "Unmounted", e;
|
|
71
|
+
}({}), c = 0, l = class {
|
|
72
|
+
Mounted = [];
|
|
73
|
+
Unmounted = [];
|
|
74
|
+
parent = null;
|
|
75
|
+
#e = [];
|
|
76
|
+
uid;
|
|
77
|
+
name;
|
|
78
|
+
current = {};
|
|
79
|
+
props = {};
|
|
80
|
+
element;
|
|
81
|
+
provides = /* @__PURE__ */ new Map();
|
|
82
|
+
constructor(e, t) {
|
|
83
|
+
this.uid = `${t}.${c++}`, this.name = t, this.element = e;
|
|
84
|
+
}
|
|
85
|
+
onMount = () => {
|
|
86
|
+
let e = [];
|
|
87
|
+
for (let t of this.Mounted) try {
|
|
88
|
+
let n = t();
|
|
89
|
+
typeof n == "function" && e.push(n);
|
|
90
|
+
} catch (e) {
|
|
91
|
+
console.error("[nagi] onMount hook failed", r.create("mount", this, e));
|
|
92
|
+
}
|
|
93
|
+
this.Unmounted.push(...e);
|
|
94
|
+
};
|
|
95
|
+
onUnmount = () => {
|
|
96
|
+
for (let e of this.Unmounted) try {
|
|
97
|
+
e();
|
|
98
|
+
} catch (e) {
|
|
99
|
+
console.error("[nagi] onUnmount cleanup failed", r.create("unmount", this, e));
|
|
100
|
+
}
|
|
101
|
+
for (let e of this.#e) e.onUnmount();
|
|
102
|
+
};
|
|
103
|
+
addChild = (e) => {
|
|
104
|
+
this.#e.push(e), e.parent = this;
|
|
105
|
+
try {
|
|
106
|
+
e.onMount();
|
|
107
|
+
} catch (t) {
|
|
108
|
+
let n = this.#e.indexOf(e);
|
|
109
|
+
throw n !== -1 && this.#e.splice(n, 1), e.parent = null, t;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
removeChild = (e) => {
|
|
113
|
+
let t = this.#e.indexOf(e);
|
|
114
|
+
t !== -1 && (this.#e.splice(t, 1), e.parent = null, e.onUnmount());
|
|
115
|
+
};
|
|
116
|
+
get childElements() {
|
|
117
|
+
return this.#e.map((e) => e.element);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
function u(e) {
|
|
121
|
+
return e === void 0 ? (e) => (t) => ({
|
|
122
|
+
name: e.name,
|
|
123
|
+
setup(n) {
|
|
124
|
+
return e.setup(n, t);
|
|
125
|
+
}
|
|
126
|
+
}) : e;
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region lib/core/runtime.ts
|
|
130
|
+
var d;
|
|
131
|
+
function f(e) {
|
|
132
|
+
if (!d) throw Error(`"${e}" called outside setup() will never be run.`);
|
|
133
|
+
return d;
|
|
134
|
+
}
|
|
135
|
+
function p(e, t, n) {
|
|
136
|
+
let a = new l(t, e.name), o = d;
|
|
137
|
+
d = a;
|
|
138
|
+
try {
|
|
139
|
+
o && (a.parent = o), a.props = n, a.current = e.setup(t, n) || {};
|
|
140
|
+
} catch (e) {
|
|
141
|
+
throw d = o, i(e) ? e : r.create("setup", a, e, o, { props: a.props });
|
|
142
|
+
}
|
|
143
|
+
return d = o, a;
|
|
144
|
+
}
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region lib/core/app.ts
|
|
147
|
+
function m(n = {}) {
|
|
148
|
+
let { scheduler: r } = n, i = t();
|
|
149
|
+
return {
|
|
150
|
+
component(t, { priority: n, when: a } = {}) {
|
|
151
|
+
return (s, c = {}) => {
|
|
152
|
+
function l() {
|
|
153
|
+
let e = p(t, s, c);
|
|
154
|
+
return o(s, e), e.onMount(), e;
|
|
155
|
+
}
|
|
156
|
+
if (!r) return l();
|
|
157
|
+
let u = i.add(s), d = () => {
|
|
158
|
+
r.schedule(() => {
|
|
159
|
+
u.complete() && l();
|
|
160
|
+
}, {
|
|
161
|
+
priority: n,
|
|
162
|
+
signal: u.signal
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
a ? a(s, u.signal).then(() => {
|
|
166
|
+
u.signal.aborted || d();
|
|
167
|
+
}, (t) => {
|
|
168
|
+
e(t) || (u.abort(), queueMicrotask(() => {
|
|
169
|
+
throw t;
|
|
170
|
+
}));
|
|
171
|
+
}) : d();
|
|
172
|
+
};
|
|
173
|
+
},
|
|
174
|
+
unmount(e) {
|
|
175
|
+
for (let t of e) {
|
|
176
|
+
i.abort(t);
|
|
177
|
+
let e = a.get(t);
|
|
178
|
+
e && (e.onUnmount(), a.delete(t));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region lib/core/lifecycle.ts
|
|
185
|
+
function h(e) {
|
|
186
|
+
return (t) => {
|
|
187
|
+
f(e)[e].push(t);
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
var g = h(s.MOUNTED), _ = h(s.UNMOUNTED), v = Symbol("watch"), y = null, b = class {
|
|
191
|
+
#e;
|
|
192
|
+
#t = /* @__PURE__ */ new Set();
|
|
193
|
+
constructor(e) {
|
|
194
|
+
this.#e = e;
|
|
195
|
+
}
|
|
196
|
+
get value() {
|
|
197
|
+
return y !== null && y.add(this), this.#e;
|
|
198
|
+
}
|
|
199
|
+
set value(e) {
|
|
200
|
+
if (Object.is(e, this.#e)) return;
|
|
201
|
+
let t = this.#e;
|
|
202
|
+
this.#e = e;
|
|
203
|
+
for (let n of Array.from(this.#t)) n(e, t);
|
|
204
|
+
}
|
|
205
|
+
[v](e) {
|
|
206
|
+
return this.#t.add(e), () => {
|
|
207
|
+
this.#t.delete(e);
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}, x = (e) => new b(e), S = class {
|
|
211
|
+
#e;
|
|
212
|
+
constructor(e) {
|
|
213
|
+
this.#e = e;
|
|
214
|
+
}
|
|
215
|
+
get value() {
|
|
216
|
+
return this.#e.value;
|
|
217
|
+
}
|
|
218
|
+
[v](e) {
|
|
219
|
+
return this.#e[v](e);
|
|
220
|
+
}
|
|
221
|
+
}, C = (e) => new S(e);
|
|
222
|
+
function w(e, t) {
|
|
223
|
+
return e[v](t);
|
|
224
|
+
}
|
|
225
|
+
function T(e, t) {
|
|
226
|
+
_(w(e, t));
|
|
227
|
+
}
|
|
228
|
+
function E(e) {
|
|
229
|
+
let t = x(void 0), n = [], r = () => {
|
|
230
|
+
n.forEach((e) => {
|
|
231
|
+
e();
|
|
232
|
+
}), n = [];
|
|
233
|
+
}, i = () => {
|
|
234
|
+
r();
|
|
235
|
+
let a = y, o = /* @__PURE__ */ new Set();
|
|
236
|
+
y = o;
|
|
237
|
+
let s;
|
|
238
|
+
try {
|
|
239
|
+
s = e();
|
|
240
|
+
} finally {
|
|
241
|
+
y = a;
|
|
242
|
+
}
|
|
243
|
+
t.value = s;
|
|
244
|
+
for (let e of o) n.push(e[v](() => {
|
|
245
|
+
i();
|
|
246
|
+
}));
|
|
247
|
+
};
|
|
248
|
+
return i(), _(r), C(t);
|
|
249
|
+
}
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region lib/hooks/createContext.ts
|
|
252
|
+
function D() {
|
|
253
|
+
let e = Symbol();
|
|
254
|
+
return [{ _id: e }, () => {
|
|
255
|
+
let t = f("createContext.use");
|
|
256
|
+
for (; t !== null;) {
|
|
257
|
+
if (t.provides.has(e)) return t.provides.get(e);
|
|
258
|
+
t = t.parent;
|
|
259
|
+
}
|
|
260
|
+
throw Error("createContext.use: no provider found");
|
|
261
|
+
}];
|
|
262
|
+
}
|
|
263
|
+
function O(e, t) {
|
|
264
|
+
return (n) => ({
|
|
265
|
+
name: n.name,
|
|
266
|
+
setup(r, i) {
|
|
267
|
+
return f(`withContext.${n.name}`).provides.set(e._id, t), n.setup(r, i);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
//#endregion
|
|
272
|
+
//#region lib/hooks/domRefs.ts
|
|
273
|
+
function k(e, t) {
|
|
274
|
+
return t.some((t) => t !== e && t.contains(e));
|
|
275
|
+
}
|
|
276
|
+
function A(e, t, n) {
|
|
277
|
+
let r = `[data-ref="${CSS.escape(e)}"]`, i = Array.from(t.querySelectorAll(r)).filter((e) => !k(e, n));
|
|
278
|
+
return i.length === 0 ? null : i.length === 1 ? i[0] : i;
|
|
279
|
+
}
|
|
280
|
+
function j(e, t) {
|
|
281
|
+
let n = /* @__PURE__ */ new Map();
|
|
282
|
+
return new Proxy({}, {
|
|
283
|
+
get(r, i) {
|
|
284
|
+
if (typeof i == "symbol" || i === "then") return;
|
|
285
|
+
if (n.has(i)) return n.get(i);
|
|
286
|
+
let a = A(i, e, t());
|
|
287
|
+
return n.set(i, a), a;
|
|
288
|
+
},
|
|
289
|
+
has(e, t) {
|
|
290
|
+
return typeof t == "string";
|
|
291
|
+
},
|
|
292
|
+
ownKeys() {
|
|
293
|
+
return [];
|
|
294
|
+
},
|
|
295
|
+
getOwnPropertyDescriptor() {},
|
|
296
|
+
set() {
|
|
297
|
+
return !1;
|
|
298
|
+
},
|
|
299
|
+
deleteProperty() {
|
|
300
|
+
return !1;
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region lib/hooks/useDomRef.ts
|
|
306
|
+
function M() {
|
|
307
|
+
let e = f("useDomRef");
|
|
308
|
+
return { refs: j(e.element, () => e.childElements) };
|
|
309
|
+
}
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region lib/hooks/useEvent.ts
|
|
312
|
+
function N(e, t, n, r) {
|
|
313
|
+
g(() => (e.addEventListener(t, n, r), () => {
|
|
314
|
+
e.removeEventListener(t, n, r);
|
|
315
|
+
}));
|
|
316
|
+
}
|
|
317
|
+
//#endregion
|
|
318
|
+
//#region lib/hooks/useIntersectionWatch.ts
|
|
319
|
+
function P(e, t, n = {
|
|
320
|
+
rootMargin: "0px",
|
|
321
|
+
threshold: .1
|
|
322
|
+
}) {
|
|
323
|
+
let r = new IntersectionObserver(t, n);
|
|
324
|
+
function i(e) {
|
|
325
|
+
Array.isArray(e) ? e.forEach((e) => {
|
|
326
|
+
r.observe(e);
|
|
327
|
+
}) : r.observe(e);
|
|
328
|
+
}
|
|
329
|
+
i(e), _(() => {
|
|
330
|
+
r.disconnect();
|
|
331
|
+
});
|
|
332
|
+
function a(e) {
|
|
333
|
+
r.unobserve(e);
|
|
334
|
+
}
|
|
335
|
+
return { unwatch: a };
|
|
336
|
+
}
|
|
337
|
+
//#endregion
|
|
338
|
+
//#region lib/hooks/useMediaQuery.ts
|
|
339
|
+
function F(e, t) {
|
|
340
|
+
let n = window.matchMedia(e), r = x(n.matches), i = null;
|
|
341
|
+
function a(e) {
|
|
342
|
+
r.value = e.matches, e.matches ? i = t() : (i?.(), i = null);
|
|
343
|
+
}
|
|
344
|
+
return g(() => (n.addEventListener("change", a), n.matches && (i = t()), () => {
|
|
345
|
+
i?.(), n.removeEventListener("change", a);
|
|
346
|
+
})), { matchesQuery: C(r) };
|
|
347
|
+
}
|
|
348
|
+
//#endregion
|
|
349
|
+
//#region lib/hooks/useRootRef.ts
|
|
350
|
+
function I() {
|
|
351
|
+
return f("useRootRef").element;
|
|
352
|
+
}
|
|
353
|
+
//#endregion
|
|
354
|
+
//#region lib/hooks/useSlot.ts
|
|
355
|
+
function L() {
|
|
356
|
+
let e = f("useSlot");
|
|
357
|
+
return {
|
|
358
|
+
addChild(t, n, r = {}) {
|
|
359
|
+
let i = (t) => {
|
|
360
|
+
let i = p(n, t, r);
|
|
361
|
+
return e.addChild(i), i;
|
|
362
|
+
};
|
|
363
|
+
return Array.isArray(t) ? t.map((e) => i(e)) : [i(t)];
|
|
364
|
+
},
|
|
365
|
+
removeChild(t) {
|
|
366
|
+
t.forEach((t) => {
|
|
367
|
+
try {
|
|
368
|
+
e.removeChild(t);
|
|
369
|
+
} catch (n) {
|
|
370
|
+
console.error("[nagi] removeChild failed", r.create("removeChild", t, n, e));
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
//#endregion
|
|
377
|
+
export { r as LifecycleError, E as computed, m as create, D as createContext, u as defineComponent, i as isLifecycleError, C as readonly, x as ref, M as useDomRef, N as useEvent, P as useIntersectionWatch, F as useMediaQuery, g as useMount, I as useRootRef, L as useSlot, _ as useUnmount, T as useWatch, O as withContext };
|
package/dist/main.umd.js
ADDED
|
@@ -0,0 +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.Lake={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(e){return(e instanceof DOMException||e instanceof Error)&&e.name===`AbortError`}function n(){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 r(e){let t=[],n=e;for(;n;)t.unshift(n.name),n=n.parent;return t.join(` > `)}var i=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,i,a=n.parent,o){return new e({phase:t,name:n.name,uid:n.uid,path:r(n),parentName:a?.name,parentUid:a?.uid,element:n.element,cause:i,...o})}};function a(e){return e instanceof i}var o=new WeakMap;function s(e,t){let n=o.get(e);if(n)throw i.create(`mount`,t,Error(`Component "${n.name}" (${n.uid}) is already mounted on this element`),n);o.set(e,t)}var c=function(e){return e.MOUNTED=`Mounted`,e.UNMOUNTED=`Unmounted`,e}({}),l=0,u=class{Mounted=[];Unmounted=[];parent=null;#e=[];uid;name;current={};props={};element;provides=new Map;constructor(e,t){this.uid=`${t}.${l++}`,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`,i.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`,i.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)}};function d(e){return e===void 0?e=>t=>({name:e.name,setup(n){return e.setup(n,t)}}):e}var 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 u(t,e.name),o=f;f=r;try{o&&(r.parent=o),r.props=n,r.current=e.setup(t,n)||{}}catch(e){throw f=o,a(e)?e:i.create(`setup`,r,e,o,{props:r.props})}return f=o,r}function h(e={}){let{scheduler:r}=e,i=n();return{component(e,{priority:n,when:a}={}){return(o,c={})=>{function l(){let t=m(e,o,c);return s(o,t),t.onMount(),t}if(!r)return l();let u=i.add(o),d=()=>{r.schedule(()=>{u.complete()&&l()},{priority:n,signal:u.signal})};a?a(o,u.signal).then(()=>{u.signal.aborted||d()},e=>{t(e)||(u.abort(),queueMicrotask(()=>{throw e}))}):d()}},unmount(e){for(let t of e){i.abort(t);let e=o.get(t);e&&(e.onUnmount(),o.delete(t))}}}}function g(e){return t=>{p(e)[e].push(t)}}var _=g(c.MOUNTED),v=g(c.UNMOUNTED),y=Symbol(`watch`),b=null,x=class{#e;#t=new Set;constructor(e){this.#e=e}get value(){return b!==null&&b.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)}[y](e){return this.#t.add(e),()=>{this.#t.delete(e)}}},S=e=>new x(e),C=class{#e;constructor(e){this.#e=e}get value(){return this.#e.value}[y](e){return this.#e[y](e)}},w=e=>new C(e);function T(e,t){return e[y](t)}function E(e,t){v(T(e,t))}function D(e){let t=S(void 0),n=[],r=()=>{n.forEach(e=>{e()}),n=[]},i=()=>{r();let a=b,o=new Set;b=o;let s;try{s=e()}finally{b=a}t.value=s;for(let e of o)n.push(e[y](()=>{i()}))};return i(),v(r),w(t)}function O(){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 k(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 A(e,t){return t.some(t=>t!==e&&t.contains(e))}function j(e,t,n){let r=`[data-ref="${CSS.escape(e)}"]`,i=Array.from(t.querySelectorAll(r)).filter(e=>!A(e,n));return i.length===0?null:i.length===1?i[0]:i}function M(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=j(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 N(){let e=p(`useDomRef`);return{refs:M(e.element,()=>e.childElements)}}function P(e,t,n,r){_(()=>(e.addEventListener(t,n,r),()=>{e.removeEventListener(t,n,r)}))}function F(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)}i(e),v(()=>{r.disconnect()});function a(e){r.unobserve(e)}return{unwatch:a}}function I(e,t){let n=window.matchMedia(e),r=S(n.matches),i=null;function a(e){r.value=e.matches,e.matches?i=t():(i?.(),i=null)}return _(()=>(n.addEventListener(`change`,a),n.matches&&(i=t()),()=>{i?.(),n.removeEventListener(`change`,a)})),{matchesQuery:w(r)}}function L(){return p(`useRootRef`).element}function R(){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`,i.create(`removeChild`,t,n,e))}})}}}e.LifecycleError=i,e.computed=D,e.create=h,e.createContext=O,e.defineComponent=d,e.isLifecycleError=a,e.readonly=w,e.ref=S,e.useDomRef=N,e.useEvent=P,e.useIntersectionWatch=F,e.useMediaQuery=I,e.useMount=_,e.useRootRef=L,e.useSlot=R,e.useUnmount=v,e.useWatch=E,e.withContext=k});
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@usenagi/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Composition-API ergonomics for vanilla DOM. Bring your own mounter.",
|
|
5
|
+
"main": "./dist/main.umd.js",
|
|
6
|
+
"module": "./dist/main.es.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./types/main.d.ts",
|
|
10
|
+
"import": "./dist/main.es.js",
|
|
11
|
+
"require": "./dist/main.umd.js"
|
|
12
|
+
},
|
|
13
|
+
"./addons/scheduler": {
|
|
14
|
+
"types": "./types/addons/scheduler/index.d.ts",
|
|
15
|
+
"import": "./dist/addons/scheduler.es.js",
|
|
16
|
+
"require": "./dist/addons/scheduler.cjs.js"
|
|
17
|
+
},
|
|
18
|
+
"./addons/cue": {
|
|
19
|
+
"types": "./types/addons/cue/index.d.ts",
|
|
20
|
+
"import": "./dist/addons/cue.es.js",
|
|
21
|
+
"require": "./dist/addons/cue.cjs.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"types"
|
|
27
|
+
],
|
|
28
|
+
"author": "hayakawasho",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/hayakawasho/nagi.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/hayakawasho/nagi/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/hayakawasho/nagi#readme",
|
|
38
|
+
"keywords": [
|
|
39
|
+
"dom",
|
|
40
|
+
"component",
|
|
41
|
+
"reactivity",
|
|
42
|
+
"composition-api",
|
|
43
|
+
"gsap",
|
|
44
|
+
"animation",
|
|
45
|
+
"vanilla",
|
|
46
|
+
"progressive-enhancement"
|
|
47
|
+
],
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "NODE_ENV=production tsc && vite build && vite build --config vite.addons.config.ts",
|
|
53
|
+
"watch": "vite build -w",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"test:watch": "vitest",
|
|
56
|
+
"lint": "oxlint ./lib",
|
|
57
|
+
"format": "biome format --write ./lib",
|
|
58
|
+
"fix:biome": "biome check --write ./lib",
|
|
59
|
+
"fix": "npm run fix:biome"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@biomejs/biome": "^2.4.13",
|
|
63
|
+
"happy-dom": "^20.9.0",
|
|
64
|
+
"oxlint": "^1.61.0",
|
|
65
|
+
"typescript": "^6.0.3",
|
|
66
|
+
"vite": "^8.0.10",
|
|
67
|
+
"vitest": "^4.1.5"
|
|
68
|
+
},
|
|
69
|
+
"type": "module",
|
|
70
|
+
"sideEffects": false
|
|
71
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Cue } from "../../types";
|
|
2
|
+
export declare function visible(opts?: IntersectionObserverInit): Cue;
|
|
3
|
+
export declare function idle(timeout?: number): Cue;
|
|
4
|
+
export declare function media(query: string): Cue;
|
|
5
|
+
export declare function interaction(events?: string[]): Cue;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ComponentSetup, Cue, RefElement, SchedulePriority, Scheduler } from "../types";
|
|
2
|
+
import type { ComponentContext } from "./component";
|
|
3
|
+
type AppOptions = {
|
|
4
|
+
priority?: SchedulePriority;
|
|
5
|
+
};
|
|
6
|
+
type AsyncAppOptions = AppOptions & {
|
|
7
|
+
when?: Cue;
|
|
8
|
+
};
|
|
9
|
+
type SyncApp = {
|
|
10
|
+
component<S extends ComponentSetup>(wrap: S, opts?: AppOptions): (el: RefElement, props?: Record<string, any>) => ComponentContext<ReturnType<S["setup"]>>;
|
|
11
|
+
unmount(targets: RefElement[]): void;
|
|
12
|
+
};
|
|
13
|
+
type AsyncApp = {
|
|
14
|
+
component<S extends ComponentSetup>(wrap: S, opts?: AsyncAppOptions): (el: RefElement, props?: Record<string, any>) => void;
|
|
15
|
+
unmount(targets: RefElement[]): void;
|
|
16
|
+
};
|
|
17
|
+
export declare function create(): SyncApp;
|
|
18
|
+
export declare function create(config: {
|
|
19
|
+
scheduler?: undefined;
|
|
20
|
+
}): SyncApp;
|
|
21
|
+
export declare function create(config: {
|
|
22
|
+
scheduler: Scheduler;
|
|
23
|
+
}): AsyncApp;
|
|
24
|
+
export declare function create(config: {
|
|
25
|
+
scheduler?: Scheduler | undefined;
|
|
26
|
+
}): SyncApp | AsyncApp;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ComponentSetup, RefElement } from "../types";
|
|
2
|
+
export declare enum LifecycleHooks {
|
|
3
|
+
MOUNTED = "Mounted",
|
|
4
|
+
UNMOUNTED = "Unmounted"
|
|
5
|
+
}
|
|
6
|
+
export declare class ComponentContext<T = any> {
|
|
7
|
+
#private;
|
|
8
|
+
private [LifecycleHooks.MOUNTED];
|
|
9
|
+
private [LifecycleHooks.UNMOUNTED];
|
|
10
|
+
parent: ComponentContext<T> | null;
|
|
11
|
+
readonly uid: string;
|
|
12
|
+
readonly name: string;
|
|
13
|
+
current: ReturnType<ComponentSetup<T>["setup"]>;
|
|
14
|
+
props: Parameters<ComponentSetup<T>["setup"]>[1];
|
|
15
|
+
element: RefElement;
|
|
16
|
+
provides: Map<symbol, unknown>;
|
|
17
|
+
constructor(element: RefElement, name: string);
|
|
18
|
+
onMount: () => void;
|
|
19
|
+
onUnmount: () => void;
|
|
20
|
+
addChild: (child: ComponentContext) => void;
|
|
21
|
+
removeChild: (child: ComponentContext) => void;
|
|
22
|
+
get childElements(): RefElement[];
|
|
23
|
+
}
|
|
24
|
+
export declare function defineComponent<Context extends Record<string, unknown>>(): <SetupResult extends Record<string, unknown> | void>(opts: {
|
|
25
|
+
name: string;
|
|
26
|
+
setup(el: RefElement, context: Context): SetupResult;
|
|
27
|
+
}) => (context: Context) => ComponentSetup<SetupResult>;
|
|
28
|
+
export declare function defineComponent<SetupResult extends Record<string, unknown> | void, Props extends Record<string, unknown>>(opts: ComponentSetup<SetupResult, Props>): ComponentSetup<SetupResult, Props>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { RefElement } from "../types";
|
|
2
|
+
import type { ComponentContext } from "./component";
|
|
3
|
+
export type LifecycleErrorDetails = {
|
|
4
|
+
phase: "setup" | "mount" | "unmount" | "removeChild";
|
|
5
|
+
name: string;
|
|
6
|
+
uid?: string;
|
|
7
|
+
path?: string;
|
|
8
|
+
parentName?: string;
|
|
9
|
+
parentUid?: string;
|
|
10
|
+
element?: RefElement;
|
|
11
|
+
props?: unknown;
|
|
12
|
+
cause: unknown;
|
|
13
|
+
};
|
|
14
|
+
export declare function traceComponentTree(context: ComponentContext): string;
|
|
15
|
+
export declare class LifecycleError extends Error {
|
|
16
|
+
readonly details: LifecycleErrorDetails;
|
|
17
|
+
constructor(details: LifecycleErrorDetails);
|
|
18
|
+
static create(phase: LifecycleErrorDetails["phase"], target: ComponentContext, cause: unknown, parent?: ComponentContext | null | undefined, extra?: Partial<LifecycleErrorDetails>): LifecycleError;
|
|
19
|
+
}
|
|
20
|
+
export declare function isLifecycleError(error: unknown): error is LifecycleError;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RefElement } from "../../types";
|
|
2
|
+
export type PendingMountTask = {
|
|
3
|
+
readonly signal: AbortSignal;
|
|
4
|
+
complete(): boolean;
|
|
5
|
+
abort(): void;
|
|
6
|
+
};
|
|
7
|
+
export type PendingMountTasks = {
|
|
8
|
+
add(el: RefElement): PendingMountTask;
|
|
9
|
+
abort(el: RefElement): void;
|
|
10
|
+
};
|
|
11
|
+
export declare function createPendingMountTasks(): PendingMountTasks;
|
|
@@ -0,0 +1,4 @@
|
|
|
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;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type WatchCallback<T> = (newVal: T, oldVal: T) => void;
|
|
2
|
+
type Unwatch = () => void;
|
|
3
|
+
declare const WATCH: unique symbol;
|
|
4
|
+
declare class Ref<T> {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(value: T);
|
|
7
|
+
get value(): T;
|
|
8
|
+
set value(newVal: T);
|
|
9
|
+
[WATCH](callback: WatchCallback<T>): Unwatch;
|
|
10
|
+
}
|
|
11
|
+
declare const ref: <T = any>(val: T) => Ref<T>;
|
|
12
|
+
declare class ReadonlyRef<T> {
|
|
13
|
+
#private;
|
|
14
|
+
constructor(value: Ref<T>);
|
|
15
|
+
get value(): T;
|
|
16
|
+
[WATCH](callback: WatchCallback<T>): Unwatch;
|
|
17
|
+
}
|
|
18
|
+
declare const readonly: <T = any>(ref: Ref<T>) => ReadonlyRef<T>;
|
|
19
|
+
declare function useWatch<T>(ref: Ref<T> | ReadonlyRef<T>, callback: WatchCallback<T>): void;
|
|
20
|
+
declare function computed<T>(getter: () => T): ReadonlyRef<T>;
|
|
21
|
+
export { computed, readonly, ref, useWatch };
|
|
22
|
+
export type { ReadonlyRef, Ref };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ComponentContext } from "./component";
|
|
2
|
+
import type { ComponentSetup, RefElement } from "../types";
|
|
3
|
+
export declare function getCurrentComponent(hookName: string): ComponentContext;
|
|
4
|
+
export declare function createComponent(wrap: ComponentSetup, root: RefElement, props: Record<string, any>): ComponentContext<any>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ComponentSetup } from "../types";
|
|
2
|
+
export type Provider<T> = {
|
|
3
|
+
readonly _id: symbol;
|
|
4
|
+
readonly _type?: T;
|
|
5
|
+
};
|
|
6
|
+
export declare function createContext<T>(): [Provider<T>, () => Readonly<T>];
|
|
7
|
+
export declare function withContext<T>(key: Provider<T>, value: T): <SetupResult extends Record<string, unknown> | void, Props extends Record<string, unknown>>(component: ComponentSetup<SetupResult, Props>) => ComponentSetup<SetupResult, Props>;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
type ElementEventListener<K extends keyof HTMLElementEventMap = keyof HTMLElementEventMap> = (this: HTMLElement, ev: HTMLElementEventMap[K]) => unknown;
|
|
2
|
+
export declare function useEvent<T extends HTMLElement = HTMLElement, K extends keyof HTMLElementEventMap = keyof HTMLElementEventMap>(target: T, eventType: K, listener: ElementEventListener<K>, optionsOrUseCapture?: boolean | AddEventListenerOptions): void;
|
|
3
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ComponentContext } from "../core/component";
|
|
2
|
+
import type { ComponentSetup, RefElement } from "../types";
|
|
3
|
+
export declare function useSlot(): {
|
|
4
|
+
addChild<Child extends ComponentSetup>(targetOrTargets: RefElement | RefElement[], child: Child, props?: Parameters<Child["setup"]>[1]): ComponentContext<ReturnType<Child["setup"]>>[];
|
|
5
|
+
removeChild(children: ComponentContext[]): void;
|
|
6
|
+
};
|
package/types/main.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { create } from "./core/app";
|
|
2
|
+
export { defineComponent } from "./core/component";
|
|
3
|
+
export { isLifecycleError, LifecycleError } from "./core/error";
|
|
4
|
+
export { useMount, useUnmount } from "./core/lifecycle";
|
|
5
|
+
export { computed, readonly, ref, useWatch } from "./core/reactivity";
|
|
6
|
+
export { createContext, withContext } from "./hooks/createContext";
|
|
7
|
+
export { useDomRef } from "./hooks/useDomRef";
|
|
8
|
+
export { useEvent } from "./hooks/useEvent";
|
|
9
|
+
export { useIntersectionWatch } from "./hooks/useIntersectionWatch";
|
|
10
|
+
export { useMediaQuery } from "./hooks/useMediaQuery";
|
|
11
|
+
export { useRootRef } from "./hooks/useRootRef";
|
|
12
|
+
export { useSlot } from "./hooks/useSlot";
|
|
13
|
+
export type { ComponentContext } from "./core/component";
|
|
14
|
+
export type { LifecycleErrorDetails } from "./core/error";
|
|
15
|
+
export type { ReadonlyRef, Ref } from "./core/reactivity";
|
|
16
|
+
export type { Provider } from "./hooks/createContext";
|
|
17
|
+
export type { ComponentSetup, Cue, IComponent, RefElement, SchedulePriority, Scheduler, } from "./types";
|
package/types/types.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type RefElement = HTMLElement | SVGElement;
|
|
2
|
+
export type ComponentProps<Props> = Readonly<Props>;
|
|
3
|
+
export type ComponentSetup<SetupResult = void | Record<string, unknown>, Props = Record<string, unknown>> = {
|
|
4
|
+
name: string;
|
|
5
|
+
setup(el: RefElement, props: ComponentProps<Props>): SetupResult;
|
|
6
|
+
};
|
|
7
|
+
/** @deprecated Use `ComponentSetup` instead. */
|
|
8
|
+
export type IComponent<SetupResult = void | Record<string, unknown>, Props = Record<string, unknown>> = ComponentSetup<SetupResult, Props>;
|
|
9
|
+
export type Cleanup = () => void;
|
|
10
|
+
export type LifecycleHandler = () => void | Cleanup;
|
|
11
|
+
export type SchedulePriority = "user-blocking" | "user-visible" | "background";
|
|
12
|
+
export type Scheduler = {
|
|
13
|
+
schedule(task: () => void, options?: {
|
|
14
|
+
priority?: SchedulePriority;
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
}): void;
|
|
17
|
+
};
|
|
18
|
+
export type Cue = (el: RefElement, signal: AbortSignal) => Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isAbortError(error: unknown): boolean;
|