@usenagi/core 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ja.md CHANGED
@@ -95,10 +95,10 @@ create().component(Greeting)(document.querySelector("#app")!);
95
95
 
96
96
  ```ts
97
97
  import { create } from "@usenagi/core";
98
- import { createScheduler } from "@usenagi/core/addons/scheduler";
98
+ import { schedulerAddon } from "@usenagi/core/addons/scheduler";
99
99
  import { visible, idle } from "@usenagi/core/addons/cue";
100
100
 
101
- const app = create({ scheduler: createScheduler() });
101
+ const app = create().install(schedulerAddon());
102
102
 
103
103
  // mount when the element enters the viewport
104
104
  app.component(HeavyWidget, { when: visible() })(el);
@@ -107,7 +107,7 @@ app.component(HeavyWidget, { when: visible() })(el);
107
107
  app.component(Analytics, { when: idle() })(el);
108
108
  ```
109
109
 
110
- `when` は `setup()` の前に待機する条件、`priority` は `setup()` を含む mount task の実行タイミングを決める。
110
+ `schedulerAddon()` を使うと、`when` は `setup()` の前に待機する条件、`priority` は `setup()` を含む mount task の実行タイミングを決める。
111
111
 
112
112
  ### BYO mounter recipe
113
113
 
@@ -120,12 +120,12 @@ app.component(Analytics, { when: idle() })(el);
120
120
 
121
121
  ### Reactivity
122
122
 
123
- | API | 説明 |
124
- | ---------------------- | -------------------------------------------------------------------- |
125
- | `signal(value)` | `.value` を持つリアクティブな値コンテナを作成する |
126
- | `readonly(signal)` | 書き込み可能な `signal` の読み取り専用ラッパー |
127
- | `useComputed(fn)` | `signal` の依存を自動追跡する派生値 |
128
- | `useWatch(target, cb)` | 値変更時に `cb` を呼ぶ。unmount 時に自動で購読解除する |
123
+ | API | 説明 |
124
+ | ---------------------- | ------------------------------------------------------ |
125
+ | `signal(value)` | `.value` を持つリアクティブな値コンテナを作成する |
126
+ | `readonly(signal)` | 書き込み可能な `signal` の読み取り専用ラッパー |
127
+ | `useComputed(fn)` | `signal` の依存を自動追跡する派生値 |
128
+ | `useWatch(target, cb)` | 値変更時に `cb` を呼ぶ。unmount 時に自動で購読解除する |
129
129
 
130
130
  ```ts
131
131
  const width = signal(10);
@@ -139,10 +139,10 @@ useWatch(area, (v) => {
139
139
 
140
140
  ### Lifecycle
141
141
 
142
- | API | 説明 |
143
- | ---------------- | ------------------------------------------------------ |
144
- | `useMount(fn)` | コンポーネントのマウント完了後に1回実行する |
145
- | `useUnmount(fn)` | unmount 時に実行する。クリーンアップに使う |
142
+ | API | 説明 |
143
+ | ---------------- | ------------------------------------------- |
144
+ | `useMount(fn)` | コンポーネントのマウント完了後に1回実行する |
145
+ | `useUnmount(fn)` | unmount 時に実行する。クリーンアップに使う |
146
146
 
147
147
  ```ts
148
148
  import gsap from 'gsap';
@@ -157,11 +157,11 @@ setup(el) {
157
157
 
158
158
  ルート要素には **`setup(el)`** を、**`[data-ref]`** の子要素には **`useDomRef()`** を使う。
159
159
 
160
- | API | 説明 |
161
- | ------------------------------ | ---------------------------------------------------------- |
162
- | `useDomRef<T>()` | `[data-ref]` 要素への型付きアクセス |
163
- | `useEvent(el, event, handler)` | イベントリスナーを追加する。unmount 時に自動で除去する |
164
- | `useSlot()` | 子コンポーネントをマウントする。親の unmount に連動する |
160
+ | API | 説明 |
161
+ | ------------------------------ | ------------------------------------------------------- |
162
+ | `useDomRef<T>()` | `[data-ref]` 要素への型付きアクセス |
163
+ | `useEvent(el, event, handler)` | イベントリスナーを追加する。unmount 時に自動で除去する |
164
+ | `useSlot()` | 子コンポーネントをマウントする。親の unmount に連動する |
165
165
 
166
166
  ### Parent / child
167
167
 
@@ -171,39 +171,60 @@ setup(el) {
171
171
 
172
172
  ### Observers
173
173
 
174
- | API | 説明 |
175
- | --------------------------------- | ----------------------------------------------------------------- |
176
- | `useIntersectionWatch(cb, opts?)` | IntersectionObserver のラッパー。unmount 時に自動で切断する |
177
- | `useMediaQuery(query)` | `matchMedia` の結果を `ReadonlySignal<boolean>` で返す |
174
+ | API | 説明 |
175
+ | --------------------------------- | ----------------------------------------------------------- |
176
+ | `useIntersectionWatch(cb, opts?)` | IntersectionObserver のラッパー。unmount 時に自動で切断する |
177
+ | `useMediaQuery(query)` | `matchMedia` の結果を `ReadonlySignal<boolean>` で返す |
178
178
 
179
179
  ### Addons
180
180
 
181
181
  ```ts
182
- import { createScheduler } from "@usenagi/core/addons/scheduler";
182
+ import { create, defineAddon } from "@usenagi/core";
183
+ import { schedulerAddon } from "@usenagi/core/addons/scheduler";
184
+
185
+ const app = create().install(schedulerAddon(), myAddon());
186
+ ```
187
+
188
+ | API | 説明 |
189
+ | --- | --- |
190
+ | `defineAddon({ name, install(ctx) })` | addon を定義する(`ctx` は `AddonContext`) |
191
+ | `app.install(...addons)` | app に addon を登録する(複数可) |
192
+ | `ctx.addMountMiddleware` / `addUnmountMiddleware` / `addComponentMiddleware` | mount / unmount / ComponentSetup の middleware を追加する |
193
+ | `ctx.installedAddons` | この app に install 済みの addon 名 |
194
+
195
+ `addMountMiddleware` / `addUnmountMiddleware` / `addComponentMiddleware` は **後から install した addon ほど外側**に適用される(`install(a, b)` なら実行順は `b → a → コア`)。
196
+
197
+ 遅延 mount には `schedulerAddon()` が必要。`when` や `priority` を使う場合も同様で、これらの mount option は scheduler addon が解釈する。addon の状態(scheduler / pending)は **各 app の `install` ごと**に作られる。
198
+
199
+ #### Scheduler + cue
200
+
201
+ ```ts
202
+ import { schedulerAddon } from "@usenagi/core/addons/scheduler";
183
203
  import { visible, idle, interaction, media } from "@usenagi/core/addons/cue";
184
204
  ```
185
205
 
186
- | API | 説明 |
187
- | ------------------------ | ------------------------------------------------------------------------------------- |
188
- | `createScheduler(opts?)` | `schedule(task, { priority, signal })` を実装した Scheduler を返す |
189
- | `visible(opts?)` | 要素が viewport に入ったときに解決する Cue |
190
- | `idle(timeout?)` | `requestIdleCallback` で解決する Cue |
191
- | `interaction(events?)` | 最初のユーザー操作で解決する Cue |
192
- | `media(query)` | media query が一致したときに解決する Cue |
206
+ | API | 説明 |
207
+ | --- | --- |
208
+ | `schedulerAddon(opts?)` | 遅延 mount addon(内部で `createScheduler` を使用) |
209
+ | `createScheduler(opts?)` | カスタム Scheduler 実装用の low-level API |
210
+ | `visible(opts?)` | 要素が viewport に入ったときに解決する Cue |
211
+ | `idle(timeout?)` | `requestIdleCallback` で解決する Cue |
212
+ | `interaction(events?)` | 最初のユーザー操作で解決する Cue |
213
+ | `media(query)` | media query が一致したときに解決する Cue |
193
214
 
194
215
  ---
195
216
 
196
217
  ## Comparison
197
218
 
198
- | | **nagi** | Alpine.js | Stimulus | petite-vue |
199
- | ----------------------- | ----------- | --------- | -------- | ---------- |
200
- | Inline JS in HTML | ✗ | ◯ | ✗ | ◯ |
201
- | Composition-style setup | ◯ | △ | ✗ | ◯ |
202
- | BYO mounter | ◯ | △ | △ | △ |
203
- | Async mount cue | ◯ | ✗ | ✗ | ✗ |
204
- | Lifecycle cleanup | ◯ | △ | ◯ | △ |
205
- | `useComputed` (derived signals) | ◯ | ◯ | ✗ | ◯ |
206
- | Core gzip | ~2.5 kB | ~16 kB | ~8 kB | ~6 kB |
219
+ | | **nagi** | Alpine.js | Stimulus | petite-vue |
220
+ | -------------------------- | -------- | --------- | -------- | ---------- |
221
+ | Inline JS in HTML | ✗ | ◯ | ✗ | ◯ |
222
+ | Composition-style setup | ◯ | △ | ✗ | ◯ |
223
+ | BYO mounter | ◯ | △ | △ | △ |
224
+ | Async mount cue | ◯ | ✗ | ✗ | ✗ |
225
+ | Lifecycle cleanup | ◯ | △ | ◯ | △ |
226
+ | computed (derived signals) | ◯ | ◯ | ✗ | ◯ |
227
+ | Core gzip | ~2.5 kB | ~16 kB | ~8 kB | ~6 kB |
207
228
 
208
229
  (◯ = 組み込み、△ = 利用側の実装・規約で対応可能、✗ = 主な機能ではない)
209
230
 
@@ -233,13 +254,13 @@ import { visible, idle, interaction, media } from "@usenagi/core/addons/cue";
233
254
 
234
255
  ## Examples
235
256
 
236
- | Example | 説明 |
237
- | ----------------------------------------------------- | ----------------------------------------------------------------- |
238
- | [basic-counter](./examples/basic-counter/) | 最小の `signal` + `useWatch` 例 |
239
- | [computed](./examples/computed/) | `useComputed` による派生値(width × height = area) |
240
- | [parent-child](./examples/parent-child/) | `createContext` + `withContext` + `useSlot` |
241
- | [lenis-scroll-scene](./examples/lenis-scroll-scene/) | Lenis + `useComputed` によるスクロール進捗連動 |
242
- | [byo-mounter recipe](./examples/recipes/byo-mounter/) | `[data-component]` スキャン + manifest + cue |
257
+ | Example | 説明 |
258
+ | ----------------------------------------------------- | --------------------------------------------------- |
259
+ | [basic-counter](./examples/basic-counter/) | 最小の `signal` + `useWatch` 例 |
260
+ | [computed](./examples/computed/) | `useComputed` による派生値(width × height = area) |
261
+ | [parent-child](./examples/parent-child/) | `createContext` + `withContext` + `useSlot` |
262
+ | [lenis-scroll-scene](./examples/lenis-scroll-scene/) | Lenis + `useComputed` によるスクロール進捗連動 |
263
+ | [byo-mounter recipe](./examples/recipes/byo-mounter/) | `[data-component]` スキャン + manifest + cue |
243
264
 
244
265
  ---
245
266
 
package/README.md CHANGED
@@ -95,10 +95,10 @@ If delayed mounting is required, add the scheduler / cue addons.
95
95
 
96
96
  ```ts
97
97
  import { create } from "@usenagi/core";
98
- import { createScheduler } from "@usenagi/core/addons/scheduler";
98
+ import { schedulerAddon } from "@usenagi/core/addons/scheduler";
99
99
  import { visible, idle } from "@usenagi/core/addons/cue";
100
100
 
101
- const app = create({ scheduler: createScheduler() });
101
+ const app = create().install(schedulerAddon());
102
102
 
103
103
  // mount when the element enters the viewport
104
104
  app.component(HeavyWidget, { when: visible() })(el);
@@ -107,7 +107,7 @@ app.component(HeavyWidget, { when: visible() })(el);
107
107
  app.component(Analytics, { when: idle() })(el);
108
108
  ```
109
109
 
110
- `when` is a condition to wait for before `setup()`, and `priority` determines the execution timing of the mount task that includes `setup()`.
110
+ With `schedulerAddon()`, `when` is a condition to wait for before `setup()`, and `priority` determines the execution timing of the mount task that includes `setup()`.
111
111
 
112
112
  ### BYO mounter recipe
113
113
 
@@ -120,10 +120,10 @@ An example of automatic mounting by combining `[data-component]` scanning, manif
120
120
 
121
121
  ### Reactivity
122
122
 
123
- | API | Description |
124
- | ---------------------- | -------------------------------------------------------------- |
125
- | `signal(value)` | Creates a reactive value container (`.value`) |
126
- | `readonly(signal)` | Read-only wrapper around a writable `signal` |
123
+ | API | Description |
124
+ | ---------------------- | ----------------------------------------------------------------- |
125
+ | `signal(value)` | Creates a reactive value container (`.value`) |
126
+ | `readonly(signal)` | Read-only wrapper around a writable `signal` |
127
127
  | `useComputed(fn)` | Derived value that auto-tracks `signal` dependencies |
128
128
  | `useWatch(target, cb)` | Calls `cb` on value change; automatically unsubscribes on unmount |
129
129
 
@@ -139,10 +139,10 @@ useWatch(area, (v) => {
139
139
 
140
140
  ### Lifecycle
141
141
 
142
- | API | Description |
143
- | ---------------- | ----------------------------------------- |
144
- | `useMount(fn)` | Runs once after the component mounts |
145
- | `useUnmount(fn)` | Runs on unmount; use for cleanup |
142
+ | API | Description |
143
+ | ---------------- | ------------------------------------ |
144
+ | `useMount(fn)` | Runs once after the component mounts |
145
+ | `useUnmount(fn)` | Runs on unmount; use for cleanup |
146
146
 
147
147
  ```ts
148
148
  import gsap from 'gsap';
@@ -157,11 +157,11 @@ setup(el) {
157
157
 
158
158
  Use **`setup(el)`** for the root element and **`useDomRef()`** for `[data-ref]` descendants.
159
159
 
160
- | API | Description |
161
- | ------------------------------ | ------------------------------------------------------------ |
162
- | `useDomRef<T>()` | Typed access to `[data-ref]` elements |
163
- | `useEvent(el, event, handler)` | Adds an event listener; automatically removed on unmount |
164
- | `useSlot()` | Mounts child components; tied to the parent's unmount |
160
+ | API | Description |
161
+ | ------------------------------ | -------------------------------------------------------- |
162
+ | `useDomRef<T>()` | Typed access to `[data-ref]` elements |
163
+ | `useEvent(el, event, handler)` | Adds an event listener; automatically removed on unmount |
164
+ | `useSlot()` | Mounts child components; tied to the parent's unmount |
165
165
 
166
166
  ### Parent / child
167
167
 
@@ -174,36 +174,57 @@ You can mount child components with `useSlot()`. You can pass values from parent
174
174
  | API | Description |
175
175
  | --------------------------------- | ------------------------------------------------------------------- |
176
176
  | `useIntersectionWatch(cb, opts?)` | IntersectionObserver wrapper; automatically disconnected on unmount |
177
- | `useMediaQuery(query)` | Returns `matchMedia` result as a `ReadonlySignal<boolean>` |
177
+ | `useMediaQuery(query)` | Returns `matchMedia` result as a `ReadonlySignal<boolean>` |
178
178
 
179
179
  ### Addons
180
180
 
181
181
  ```ts
182
- import { createScheduler } from "@usenagi/core/addons/scheduler";
182
+ import { create, defineAddon } from "@usenagi/core";
183
+ import { schedulerAddon } from "@usenagi/core/addons/scheduler";
184
+
185
+ const app = create().install(schedulerAddon(), myAddon());
186
+ ```
187
+
188
+ | API | Description |
189
+ | --- | --- |
190
+ | `defineAddon({ name, install(ctx) })` | Defines an addon (`ctx` is `AddonContext`) |
191
+ | `app.install(...addons)` | Registers one or more addons on the app |
192
+ | `ctx.addMountMiddleware` / `addUnmountMiddleware` / `addComponentMiddleware` | Add mount / unmount / ComponentSetup middleware |
193
+ | `ctx.installedAddons` | Addon names already installed on this app |
194
+
195
+ `addMountMiddleware`, `addUnmountMiddleware`, and `addComponentMiddleware` apply **outermost for addons installed later** (`install(a, b)` runs as `b → a → core`).
196
+
197
+ Deferred mounting requires `schedulerAddon()`. The same applies when using `when` or `priority`; these mount options are interpreted by the scheduler addon. Addon state (scheduler / pending) is created **per app `install`**, not per addon instance.
198
+
199
+ #### Scheduler + cue
200
+
201
+ ```ts
202
+ import { schedulerAddon } from "@usenagi/core/addons/scheduler";
183
203
  import { visible, idle, interaction, media } from "@usenagi/core/addons/cue";
184
204
  ```
185
205
 
186
- | API | Description |
187
- | ------------------------ | --------------------------------------------------------------------- |
188
- | `createScheduler(opts?)` | Returns a Scheduler implementing `schedule(task, { priority, signal })` |
189
- | `visible(opts?)` | A Cue that resolves when the element enters the viewport |
190
- | `idle(timeout?)` | A Cue that resolves via `requestIdleCallback` |
191
- | `interaction(events?)` | A Cue that resolves on the first user interaction |
192
- | `media(query)` | A Cue that resolves when the media query matches |
206
+ | API | Description |
207
+ | --- | --- |
208
+ | `schedulerAddon(opts?)` | Addon for deferred mount (uses `createScheduler` internally) |
209
+ | `createScheduler(opts?)` | Low-level API for custom Scheduler implementations |
210
+ | `visible(opts?)` | A Cue that resolves when the element enters the viewport |
211
+ | `idle(timeout?)` | A Cue that resolves via `requestIdleCallback` |
212
+ | `interaction(events?)` | A Cue that resolves on the first user interaction |
213
+ | `media(query)` | A Cue that resolves when the media query matches |
193
214
 
194
215
  ---
195
216
 
196
217
  ## Comparison
197
218
 
198
- | | **nagi** | Alpine.js | Stimulus | petite-vue |
199
- | ----------------------- | ----------- | --------- | -------- | ---------- |
200
- | Inline JS in HTML | ✗ | ◯ | ✗ | ◯ |
201
- | Composition-style setup | ◯ | △ | ✗ | ◯ |
202
- | BYO mounter | ◯ | △ | △ | △ |
203
- | Async mount cue | ◯ | ✗ | ✗ | ✗ |
204
- | Lifecycle cleanup | ◯ | △ | ◯ | △ |
205
- | `useComputed` (derived signals) | ◯ | ◯ | ✗ | ◯ |
206
- | Core gzip | ~2.5 kB | ~16 kB | ~8 kB | ~6 kB |
219
+ | | **nagi** | Alpine.js | Stimulus | petite-vue |
220
+ | -------------------------- | -------- | --------- | -------- | ---------- |
221
+ | Inline JS in HTML | ✗ | ◯ | ✗ | ◯ |
222
+ | Composition-style setup | ◯ | △ | ✗ | ◯ |
223
+ | BYO mounter | ◯ | △ | △ | △ |
224
+ | Async mount cue | ◯ | ✗ | ✗ | ✗ |
225
+ | Lifecycle cleanup | ◯ | △ | ◯ | △ |
226
+ | computed (derived signals) | ◯ | ◯ | ✗ | ◯ |
227
+ | Core gzip | ~2.5 kB | ~16 kB | ~8 kB | ~6 kB |
207
228
 
208
229
  (◯ = built-in, △ = handled via userland/convention, ✗ = not a primary feature)
209
230
 
@@ -233,13 +254,13 @@ import { visible, idle, interaction, media } from "@usenagi/core/addons/cue";
233
254
 
234
255
  ## Examples
235
256
 
236
- | Example | Description |
237
- | ----------------------------------------------------- | ------------------------------------------------------ |
238
- | [basic-counter](./examples/basic-counter/) | Minimal `signal` + `useWatch` example |
239
- | [computed](./examples/computed/) | Derived value with `useComputed` (width × height = area) |
240
- | [parent-child](./examples/parent-child/) | `createContext` + `withContext` + `useSlot` |
241
- | [lenis-scroll-scene](./examples/lenis-scroll-scene/) | Scroll-progress animation with Lenis + `useComputed` |
242
- | [byo-mounter recipe](./examples/recipes/byo-mounter/) | `[data-component]` scanning + manifest + cue |
257
+ | Example | Description |
258
+ | ----------------------------------------------------- | -------------------------------------------------------- |
259
+ | [basic-counter](./examples/basic-counter/) | Minimal `signal` + `useWatch` example |
260
+ | [computed](./examples/computed/) | Derived value with `useComputed` (width × height = area) |
261
+ | [parent-child](./examples/parent-child/) | `createContext` + `withContext` + `useSlot` |
262
+ | [lenis-scroll-scene](./examples/lenis-scroll-scene/) | Scroll-progress animation with Lenis + `useComputed` |
263
+ | [byo-mounter recipe](./examples/recipes/byo-mounter/) | `[data-component]` scanning + manifest + cue |
243
264
 
244
265
  ---
245
266
 
@@ -1 +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;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){return e}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,n,r){if(r?.aborted)return;let{scheduler:a}=globalThis;if(typeof a?.postTask==`function`){a.postTask(e,{priority:n,signal:r}).catch(e=>{t(e)||queueMicrotask(()=>{throw e})});return}i(e,n,r)}function i(e,t,n){function r(){n?.aborted||e()}function i(e,t){let r=e();n?.addEventListener(`abort`,()=>t(r),{once:!0})}switch(t){case`user-blocking`:queueMicrotask(r);break;case`user-visible`:i(()=>requestAnimationFrame(r),cancelAnimationFrame);break;case`background`:typeof requestIdleCallback==`function`?i(()=>requestIdleCallback(r),cancelIdleCallback):i(()=>setTimeout(r,0),clearTimeout);break}}function a(e={}){let t=e.priority??`user-visible`;return{schedule(e,n={}){r(e,n.priority??t,n.signal)}}}function o(r){return e({name:`@usenagi/scheduler`,install(e){let i=a(r),o=n();e.addMountMiddleware((e,n,r)=>(n,a)=>{let s=o.add(n),c=()=>{i.schedule(()=>{s.complete()&&e(n,a)},{priority:r.priority,signal:s.signal})},{when:l}=r;l?l(n,s.signal).then(()=>{s.signal.aborted||c()},e=>{t(e)||(s.abort(),queueMicrotask(()=>{throw e}))}):c()}),e.addUnmountMiddleware(e=>t=>{t.forEach(o.abort),e(t)})}})}exports.createScheduler=a,exports.schedulerAddon=o;
@@ -1,26 +1,56 @@
1
- //#region lib/utils/isAbortError.ts
1
+ //#region lib/core/addon.ts
2
2
  function e(e) {
3
+ return e;
4
+ }
5
+ //#endregion
6
+ //#region lib/utils/isAbortError.ts
7
+ function t(e) {
3
8
  return (e instanceof DOMException || e instanceof Error) && e.name === "AbortError";
4
9
  }
5
10
  //#endregion
11
+ //#region lib/addons/scheduler/pending.ts
12
+ function n() {
13
+ let e = /* @__PURE__ */ new Map();
14
+ return {
15
+ add(t) {
16
+ let n = e.get(t);
17
+ n && n.abort();
18
+ let r = new AbortController();
19
+ return e.set(t, r), {
20
+ signal: r.signal,
21
+ complete() {
22
+ return e.get(t) !== r || r.signal.aborted ? !1 : (e.delete(t), !0);
23
+ },
24
+ abort() {
25
+ e.get(t) === r && (r.abort(), e.delete(t));
26
+ }
27
+ };
28
+ },
29
+ abort(t) {
30
+ let n = e.get(t);
31
+ n && (n.abort(), e.delete(t));
32
+ }
33
+ };
34
+ }
35
+ //#endregion
6
36
  //#region lib/addons/scheduler/task.ts
7
- function t(t, r, i) {
8
- if (i?.aborted) return;
37
+ function r(e, n, r) {
38
+ if (r?.aborted) return;
9
39
  let { scheduler: a } = globalThis;
10
40
  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;
41
+ a.postTask(e, {
42
+ priority: n,
43
+ signal: r
44
+ }).catch((e) => {
45
+ t(e) || queueMicrotask(() => {
46
+ throw e;
17
47
  });
18
48
  });
19
49
  return;
20
50
  }
21
- n(t, r, i);
51
+ i(e, n, r);
22
52
  }
23
- function n(e, t, n) {
53
+ function i(e, t, n) {
24
54
  function r() {
25
55
  n?.aborted || e();
26
56
  }
@@ -41,12 +71,41 @@ function n(e, t, n) {
41
71
  }
42
72
  }
43
73
  //#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);
74
+ //#region lib/addons/scheduler/scheduler.ts
75
+ function a(e = {}) {
76
+ let t = e.priority ?? "user-visible";
77
+ return { schedule(e, n = {}) {
78
+ r(e, n.priority ?? t, n.signal);
49
79
  } };
50
80
  }
51
81
  //#endregion
52
- export { r as createScheduler };
82
+ //#region lib/addons/scheduler/addon.ts
83
+ function o(r) {
84
+ return e({
85
+ name: "@usenagi/scheduler",
86
+ install(e) {
87
+ let i = a(r), o = n();
88
+ e.addMountMiddleware((e, n, r) => (n, a) => {
89
+ let s = o.add(n), c = () => {
90
+ i.schedule(() => {
91
+ s.complete() && e(n, a);
92
+ }, {
93
+ priority: r.priority,
94
+ signal: s.signal
95
+ });
96
+ }, { when: l } = r;
97
+ l ? l(n, s.signal).then(() => {
98
+ s.signal.aborted || c();
99
+ }, (e) => {
100
+ t(e) || (s.abort(), queueMicrotask(() => {
101
+ throw e;
102
+ }));
103
+ }) : c();
104
+ }), e.addUnmountMiddleware((e) => (t) => {
105
+ t.forEach(o.abort), e(t);
106
+ });
107
+ }
108
+ });
109
+ }
110
+ //#endregion
111
+ export { a as createScheduler, o as schedulerAddon };
package/dist/main.es.js CHANGED
@@ -1,31 +1,36 @@
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
- };
1
+ //#region lib/core/addon.ts
2
+ function e() {
3
+ let e = /* @__PURE__ */ new Set(), t = [], n = [], r = [], i = {
4
+ get installedAddons() {
5
+ return e;
6
+ },
7
+ addComponentMiddleware(e) {
8
+ t.push(e);
9
+ },
10
+ addMountMiddleware(e) {
11
+ n.push(e);
12
+ },
13
+ addUnmountMiddleware(e) {
14
+ r.push(e);
15
+ },
16
+ composeComponent(e) {
17
+ return t.reduce((e, t) => t(e), e);
18
+ },
19
+ composeMount(e, t, r) {
20
+ return n.reduce((e, n) => n(e, t, r), e);
23
21
  },
24
- abort(t) {
25
- let n = e.get(t);
26
- n && (n.abort(), e.delete(t));
22
+ composeUnmount(e) {
23
+ return r.reduce((e, t) => t(e), e);
24
+ },
25
+ install(t) {
26
+ if (e.has(t.name)) throw Error(`[nagi] addon "${t.name}" is already installed`);
27
+ t.install(i), e.add(t.name);
27
28
  }
28
29
  };
30
+ return i;
31
+ }
32
+ function t(e) {
33
+ return e;
29
34
  }
30
35
  //#endregion
31
36
  //#region lib/core/error.ts
@@ -118,12 +123,7 @@ var s = /* @__PURE__ */ function(e) {
118
123
  }
119
124
  };
120
125
  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;
126
+ return e;
127
127
  }
128
128
  //#endregion
129
129
  //#region lib/core/runtime.ts
@@ -132,7 +132,7 @@ function f(e) {
132
132
  if (!d) throw Error(`"${e}" called outside setup() will never be run.`);
133
133
  return d;
134
134
  }
135
- function p(e, t, n) {
135
+ function p(e, t, n = {}) {
136
136
  let a = new l(t, e.name), o = d;
137
137
  d = a;
138
138
  try {
@@ -144,41 +144,28 @@ function p(e, t, n) {
144
144
  }
145
145
  //#endregion
146
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
- };
147
+ function m() {
148
+ let t = e(), n = (e) => {
149
+ for (let t of e) {
150
+ let e = a.get(t);
151
+ e && (e.onUnmount(), a.delete(t));
152
+ }
153
+ }, r = {
154
+ install(...e) {
155
+ return e.forEach(t.install), r;
156
+ },
157
+ component(e, n = {}) {
158
+ let r = t.composeComponent(e), i = t.composeMount((e, t) => {
159
+ let n = p(r, e, t);
160
+ return o(e, n), n.onMount(), n;
161
+ }, r, n);
162
+ return (e, t = {}) => i(e, t);
173
163
  },
174
164
  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
- }
165
+ t.composeUnmount(n)(e);
180
166
  }
181
167
  };
168
+ return r;
182
169
  }
183
170
  //#endregion
184
171
  //#region lib/core/lifecycle.ts
@@ -350,7 +337,7 @@ function F(e, t) {
350
337
  function I() {
351
338
  let e = f("useSlot");
352
339
  return {
353
- addChild(t, n, r = {}) {
340
+ addChild(t, n, r) {
354
341
  let i = (t) => {
355
342
  let i = p(n, t, r);
356
343
  return e.addChild(i), i;
@@ -369,4 +356,7 @@ function I() {
369
356
  };
370
357
  }
371
358
  //#endregion
372
- export { r as LifecycleError, m as create, D as createContext, u as defineComponent, i as isLifecycleError, C as readonly, x as signal, E as useComputed, M as useDomRef, N as useEvent, P as useIntersectionWatch, F as useMediaQuery, g as useMount, I as useSlot, _ as useUnmount, T as useWatch, O as withContext };
359
+ //#region lib/props.ts
360
+ function L() {}
361
+ //#endregion
362
+ export { r as LifecycleError, m as create, D as createContext, t as defineAddon, u as defineComponent, i as isLifecycleError, L as propTypes, C as readonly, x as signal, E as useComputed, M as useDomRef, N as useEvent, P as useIntersectionWatch, F as useMediaQuery, g as useMount, I as useSlot, _ as useUnmount, T as useWatch, O 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.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(){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.create=h,e.createContext=O,e.defineComponent=d,e.isLifecycleError=a,e.readonly=w,e.signal=S,e.useComputed=D,e.useDomRef=N,e.useEvent=P,e.useIntersectionWatch=F,e.useMediaQuery=I,e.useMount=_,e.useSlot=L,e.useUnmount=v,e.useWatch=E,e.withContext=k});
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(){let e=new Set,t=[],n=[],r=[],i={get installedAddons(){return e},addComponentMiddleware(e){t.push(e)},addMountMiddleware(e){n.push(e)},addUnmountMiddleware(e){r.push(e)},composeComponent(e){return t.reduce((e,t)=>t(e),e)},composeMount(e,t,r){return n.reduce((e,n)=>n(e,t,r),e)},composeUnmount(e){return r.reduce((e,t)=>t(e),e)},install(t){if(e.has(t.name))throw Error(`[nagi] addon "${t.name}" is already installed`);t.install(i),e.add(t.name)}};return i}function n(e){return e}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}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(){let e=t(),n=e=>{for(let t of e){let e=o.get(t);e&&(e.onUnmount(),o.delete(t))}},r={install(...t){return t.forEach(e.install),r},component(t,n={}){let r=e.composeComponent(t),i=e.composeMount((e,t)=>{let n=m(r,e,t);return s(e,n),n.onMount(),n},r,n);return(e,t={})=>i(e,t)},unmount(t){e.composeUnmount(n)(t)}};return r}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(){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))}})}}}function R(){}e.LifecycleError=i,e.create=h,e.createContext=O,e.defineAddon=n,e.defineComponent=d,e.isLifecycleError=a,e.propTypes=R,e.readonly=w,e.signal=S,e.useComputed=D,e.useDomRef=N,e.useEvent=P,e.useIntersectionWatch=F,e.useMediaQuery=I,e.useMount=_,e.useSlot=L,e.useUnmount=v,e.useWatch=E,e.withContext=k});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usenagi/core",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
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",
@@ -0,0 +1,10 @@
1
+ import type { Cue, SchedulePriority } from "../../types";
2
+ declare module "../../core/addon" {
3
+ interface MountOptions {
4
+ priority?: SchedulePriority;
5
+ when?: Cue;
6
+ }
7
+ }
8
+ export declare function schedulerAddon(opts?: {
9
+ priority?: SchedulePriority;
10
+ }): import("../../main").Addon;
@@ -1,4 +1,2 @@
1
- import type { SchedulePriority, Scheduler } from "../../types";
2
- export declare function createScheduler(opts?: {
3
- priority?: SchedulePriority;
4
- }): Scheduler;
1
+ export { schedulerAddon } from "./addon";
2
+ export { createScheduler } from "./scheduler";
@@ -0,0 +1,11 @@
1
+ import type { RefElement } from "../../types";
2
+ export type PendingMount = {
3
+ readonly signal: AbortSignal;
4
+ complete(): boolean;
5
+ abort(): void;
6
+ };
7
+ export type PendingMounts = {
8
+ add(el: RefElement): PendingMount;
9
+ abort(el: RefElement): void;
10
+ };
11
+ export declare function createPendingMounts(): PendingMounts;
@@ -0,0 +1,4 @@
1
+ import type { SchedulePriority, Scheduler } from "../../types";
2
+ export declare function createScheduler(opts?: {
3
+ priority?: SchedulePriority;
4
+ }): Scheduler;
@@ -0,0 +1,35 @@
1
+ import type { ComponentSetup, RefElement } from "../types";
2
+ declare const mountOptionsBrand: unique symbol;
3
+ /** Options for `app.component(setup, opts)` — extended by mount addons. */
4
+ export interface MountOptions {
5
+ readonly [mountOptionsBrand]?: never;
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
+ export type AddonContext = {
18
+ readonly installedAddons: ReadonlySet<string>;
19
+ addComponentMiddleware(middleware: ComponentMiddleware): void;
20
+ addMountMiddleware(middleware: MountMiddleware): void;
21
+ addUnmountMiddleware(middleware: UnmountMiddleware): void;
22
+ };
23
+ type AddonRegistry = AddonContext & {
24
+ composeComponent<S extends ComponentSetup>(setup: S): S;
25
+ composeMount(mountFn: MountFn, setup: ComponentSetup, opts: MountOptions): MountFn;
26
+ composeUnmount(unmountFn: UnmountFn): UnmountFn;
27
+ install(addon: Addon): void;
28
+ };
29
+ export declare function createAddonRegistry(): AddonRegistry;
30
+ /**
31
+ * Identity helper for type inference only — no runtime effect.
32
+ */
33
+ export declare function defineAddon(addon: Addon): Addon;
34
+ export declare function defineAddon<TOptions>(factory: (options?: TOptions) => Addon): (options?: TOptions) => Addon;
35
+ export {};
@@ -1,27 +1,10 @@
1
- import type { ComponentSetup, Cue, RefElement, SchedulePriority, Scheduler } from "../types";
1
+ import type { ComponentSetup, RefElement } from "../types";
2
+ import type { Addon, MountOptions } from "./addon";
2
3
  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;
4
+ type App = {
5
+ install(...addons: Addon[]): App;
6
+ component<S extends ComponentSetup>(component: S, opts?: MountOptions): (el: RefElement, props?: Record<string, any>) => ComponentContext<ReturnType<S["setup"]>> | void;
15
7
  unmount(targets: RefElement[]): void;
16
8
  };
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;
9
+ export declare function create(): App;
27
10
  export {};
@@ -1,4 +1,4 @@
1
- import type { ComponentSetup, RefElement } from "../types";
1
+ import type { ComponentProps, ComponentSetup, RefElement } from "../types";
2
2
  export declare enum LifecycleHooks {
3
3
  MOUNTED = "Mounted",
4
4
  UNMOUNTED = "Unmounted"
@@ -21,8 +21,17 @@ export declare class ComponentContext<T = any> {
21
21
  removeChild: (child: ComponentContext) => void;
22
22
  get childElements(): RefElement[];
23
23
  }
24
- export declare function defineComponent<Context extends Record<string, unknown>>(): <SetupResult extends Record<string, unknown> | void>(opts: {
24
+ export declare function defineComponent<SetupResult extends Record<string, unknown> | void, Props extends Record<string, unknown>>(opts: {
25
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>;
26
+ props: Props;
27
+ setup(el: RefElement, props: ComponentProps<Props>): SetupResult;
28
+ }): ComponentSetup<SetupResult, Props>;
29
+ export declare function defineComponent<SetupResult extends Record<string, unknown> | void, Props extends Record<string, unknown>>(opts: {
30
+ name: string;
31
+ setup(el: RefElement, props: ComponentProps<Props>): SetupResult;
32
+ }): ComponentSetup<SetupResult, Props>;
33
+ export declare function defineComponent<SetupResult extends Record<string, unknown> | void>(opts: {
34
+ name: string;
35
+ setup(el: RefElement): SetupResult;
36
+ }): ComponentSetup<SetupResult, Record<string, never>>;
37
+ export declare function defineComponent(opts: ComponentSetup): ComponentSetup;
@@ -1,4 +1,4 @@
1
1
  import { ComponentContext } from "./component";
2
2
  import type { ComponentSetup, RefElement } from "../types";
3
3
  export declare function getCurrentComponent(hookName: string): ComponentContext;
4
- export declare function createComponent(wrap: ComponentSetup, root: RefElement, props: Record<string, any>): ComponentContext<any>;
4
+ export declare function createComponent(wrap: ComponentSetup, root: RefElement, props?: Record<string, any>): ComponentContext<any>;
@@ -1,6 +1,6 @@
1
1
  import type { ComponentContext } from "../core/component";
2
2
  import type { ComponentSetup, RefElement } from "../types";
3
3
  export declare function useSlot(): {
4
- addChild<Child extends ComponentSetup>(targetOrTargets: RefElement | RefElement[], child: Child, props?: Parameters<Child["setup"]>[1]): ComponentContext<ReturnType<Child["setup"]>>[];
4
+ addChild<Child extends ComponentSetup>(targetOrTargets: RefElement | RefElement[], child: Child, props?: Partial<Parameters<Child["setup"]>[1]>): ComponentContext<ReturnType<Child["setup"]>>[];
5
5
  removeChild(children: ComponentContext[]): void;
6
6
  };
package/types/main.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { defineAddon } from "./core/addon";
1
2
  export { create } from "./core/app";
2
3
  export { defineComponent } from "./core/component";
3
4
  export { isLifecycleError, LifecycleError } from "./core/error";
@@ -9,6 +10,8 @@ export { useEvent } from "./hooks/useEvent";
9
10
  export { useIntersectionWatch } from "./hooks/useIntersectionWatch";
10
11
  export { useMediaQuery } from "./hooks/useMediaQuery";
11
12
  export { useSlot } from "./hooks/useSlot";
13
+ export { propTypes } from "./props";
14
+ export type { Addon, AddonContext, ComponentMiddleware, MountFn, MountMiddleware, MountOptions, UnmountFn, UnmountMiddleware, } from "./core/addon";
12
15
  export type { ComponentContext } from "./core/component";
13
16
  export type { LifecycleErrorDetails } from "./core/error";
14
17
  export type { ReadonlySignal, Signal } from "./core/reactivity";
@@ -0,0 +1,2 @@
1
+ /** Type-only marker for component props. Runtime value is unused. */
2
+ export declare function propTypes<T extends Record<string, unknown>>(): T;
package/types/types.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  export type RefElement = HTMLElement | SVGElement;
2
2
  export type ComponentProps<Props> = Readonly<Props>;
3
- export type ComponentSetup<SetupResult = void | Record<string, unknown>, Props = Record<string, unknown>> = {
3
+ export type ComponentSetup<SetupResult = void | Record<string, unknown>, Props extends Record<string, unknown> = Record<string, unknown>> = {
4
4
  name: string;
5
5
  setup(el: RefElement, props: ComponentProps<Props>): SetupResult;
6
6
  };
7
7
  /** @deprecated Use `ComponentSetup` instead. */
8
- export type IComponent<SetupResult = void | Record<string, unknown>, Props = Record<string, unknown>> = ComponentSetup<SetupResult, Props>;
8
+ export type IComponent<SetupResult = void | Record<string, unknown>, Props extends Record<string, unknown> = Record<string, unknown>> = ComponentSetup<SetupResult, Props>;
9
9
  export type Cleanup = () => void;
10
10
  export type LifecycleHandler = () => void | Cleanup;
11
11
  export type SchedulePriority = "user-blocking" | "user-visible" | "background";
@@ -1,11 +0,0 @@
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;