@usenagi/core 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/addons/cue.es.js +1 -1
  2. package/dist/addons/scheduler.cjs.js +1 -1
  3. package/dist/addons/scheduler.es.js +26 -27
  4. package/dist/main.es.js +120 -118
  5. package/dist/main.umd.js +1 -1
  6. package/package.json +3 -17
  7. package/types/addons/cue/index.d.ts +1 -1
  8. package/types/addons/scheduler/_internal/pending.d.ts +11 -0
  9. package/types/addons/scheduler/_internal/schedule.d.ts +11 -0
  10. package/types/addons/scheduler/index.d.ts +10 -2
  11. package/types/core/_internal/addonRegistry.d.ts +10 -0
  12. package/types/core/_internal/component.d.ts +24 -0
  13. package/types/core/_internal/registry.d.ts +5 -0
  14. package/types/core/addon.d.ts +0 -7
  15. package/types/core/app.d.ts +2 -3
  16. package/types/core/component.d.ts +0 -22
  17. package/types/core/error.d.ts +2 -3
  18. package/types/core/runtime.d.ts +4 -3
  19. package/types/hooks/{useDomRef.d.ts → core/useDomRef.d.ts} +1 -1
  20. package/types/hooks/{useSlot.d.ts → core/useSlot.d.ts} +2 -3
  21. package/types/main.d.ts +7 -8
  22. package/types/types.d.ts +10 -8
  23. package/LICENSE +0 -21
  24. package/README.ja.md +0 -269
  25. package/README.md +0 -269
  26. package/types/addons/scheduler/addon.d.ts +0 -10
  27. package/types/addons/scheduler/pending.d.ts +0 -11
  28. package/types/addons/scheduler/scheduler.d.ts +0 -4
  29. package/types/addons/scheduler/task.d.ts +0 -2
  30. package/types/core/internal/registry.d.ts +0 -4
  31. package/types/hooks/domRefs.d.ts +0 -2
  32. package/types/utils/isAbortError.d.ts +0 -1
  33. /package/types/{hooks/createContext.d.ts → core/context.d.ts} +0 -0
  34. /package/types/{props.d.ts → core/props.d.ts} +0 -0
@@ -20,13 +20,6 @@ export type AddonContext = {
20
20
  addMountMiddleware(middleware: MountMiddleware): void;
21
21
  addUnmountMiddleware(middleware: UnmountMiddleware): void;
22
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
23
  /**
31
24
  * Identity helper for type inference only — no runtime effect.
32
25
  */
@@ -1,9 +1,8 @@
1
- import type { ComponentSetup, RefElement } from "../types";
1
+ import type { ComponentContext, ComponentSetup, ExposedSetup, RefElement } from "../types";
2
2
  import type { Addon, MountOptions } from "./addon";
3
- import type { ComponentContext } from "./component";
4
3
  type App = {
5
4
  install(...addons: Addon[]): App;
6
- component<S extends ComponentSetup>(component: S, opts?: MountOptions): (el: RefElement, props?: Record<string, any>) => ComponentContext<ReturnType<S["setup"]>> | void;
5
+ component<S extends ComponentSetup>(component: S, opts?: MountOptions): (el: RefElement, props?: Record<string, any>) => ComponentContext<ExposedSetup<ReturnType<S["setup"]>>> | void;
7
6
  unmount(targets: RefElement[]): void;
8
7
  };
9
8
  export declare function create(): App;
@@ -1,26 +1,4 @@
1
1
  import type { ComponentProps, 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
2
  export declare function defineComponent<SetupResult extends Record<string, unknown> | void, Props extends Record<string, unknown>>(opts: {
25
3
  name: string;
26
4
  props: Props;
@@ -1,5 +1,5 @@
1
1
  import type { RefElement } from "../types";
2
- import type { ComponentContext } from "./component";
2
+ import type { ComponentContextImpl } from "./_internal/component";
3
3
  export type LifecycleErrorDetails = {
4
4
  phase: "setup" | "mount" | "unmount" | "removeChild";
5
5
  name: string;
@@ -11,10 +11,9 @@ export type LifecycleErrorDetails = {
11
11
  props?: unknown;
12
12
  cause: unknown;
13
13
  };
14
- export declare function traceComponentTree(context: ComponentContext): string;
15
14
  export declare class LifecycleError extends Error {
16
15
  readonly details: LifecycleErrorDetails;
17
16
  constructor(details: LifecycleErrorDetails);
18
- static create(phase: LifecycleErrorDetails["phase"], target: ComponentContext, cause: unknown, parent?: ComponentContext | null | undefined, extra?: Partial<LifecycleErrorDetails>): LifecycleError;
17
+ static create(phase: LifecycleErrorDetails["phase"], target: ComponentContextImpl, cause: unknown, parent?: ComponentContextImpl | null | undefined, extra?: Partial<LifecycleErrorDetails>): LifecycleError;
19
18
  }
20
19
  export declare function isLifecycleError(error: unknown): error is LifecycleError;
@@ -1,4 +1,5 @@
1
- import { ComponentContext } from "./component";
1
+ import { ComponentContextImpl } from "./_internal/component";
2
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>;
3
+ declare function getCurrentComponent(hookName: string): ComponentContextImpl;
4
+ declare function createComponent<S extends ComponentSetup>(wrap: S, root: RefElement, props?: Record<string, any>): ComponentContextImpl<ReturnType<S["setup"]>>;
5
+ export {};
@@ -1,4 +1,4 @@
1
- import type { RefElement } from "../types";
1
+ import type { RefElement } from "../../types";
2
2
  export declare function useDomRef<T extends Record<string, RefElement | RefElement[] | null>>(): {
3
3
  refs: T;
4
4
  };
@@ -1,6 +1,5 @@
1
- import type { ComponentContext } from "../core/component";
2
- import type { ComponentSetup, RefElement } from "../types";
1
+ import type { ComponentContext, ComponentSetup, ExposedSetup, RefElement } from "../../types";
3
2
  export declare function useSlot(): {
4
- addChild<Child extends ComponentSetup>(targetOrTargets: RefElement | RefElement[], child: Child, props?: Partial<Parameters<Child["setup"]>[1]>): ComponentContext<ReturnType<Child["setup"]>>[];
3
+ addChild<Child extends ComponentSetup>(targetOrTargets: RefElement | RefElement[], child: Child, props?: Partial<Parameters<Child["setup"]>[1]>): ComponentContext<ExposedSetup<ReturnType<Child["setup"]>>>[];
5
4
  removeChild(children: ComponentContext[]): void;
6
5
  };
package/types/main.d.ts CHANGED
@@ -1,19 +1,18 @@
1
1
  export { defineAddon } from "./core/addon";
2
2
  export { create } from "./core/app";
3
3
  export { defineComponent } from "./core/component";
4
+ export { createContext, withContext } from "./core/context";
4
5
  export { isLifecycleError, LifecycleError } from "./core/error";
5
6
  export { useMount, useUnmount } from "./core/lifecycle";
7
+ export { propTypes } from "./core/props";
6
8
  export { readonly, signal, useComputed, useWatch } from "./core/reactivity";
7
- export { createContext, withContext } from "./hooks/createContext";
8
- export { useDomRef } from "./hooks/useDomRef";
9
+ export { useDomRef } from "./hooks/core/useDomRef";
10
+ export { useSlot } from "./hooks/core/useSlot";
9
11
  export { useEvent } from "./hooks/useEvent";
10
12
  export { useIntersectionWatch } from "./hooks/useIntersectionWatch";
11
13
  export { useMediaQuery } from "./hooks/useMediaQuery";
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";
15
- export type { ComponentContext } from "./core/component";
14
+ export type { Addon, AddonContext, MountOptions } from "./core/addon";
15
+ export type { Provider } from "./core/context";
16
16
  export type { LifecycleErrorDetails } from "./core/error";
17
17
  export type { ReadonlySignal, Signal } from "./core/reactivity";
18
- export type { Provider } from "./hooks/createContext";
19
- export type { ComponentSetup, Cue, IComponent, RefElement, SchedulePriority, Scheduler, } from "./types";
18
+ export type { Cleanup, ComponentContext, ComponentSetup, Cue, RefElement, SchedulePriority, } from "./types";
package/types/types.d.ts CHANGED
@@ -1,18 +1,20 @@
1
1
  export type RefElement = HTMLElement | SVGElement;
2
2
  export type ComponentProps<Props> = Readonly<Props>;
3
+ type IsAny<T> = 0 extends 1 & T ? true : false;
4
+ /** Normalize the return value of setup to the type stored in `current` (void / undefined → empty object). */
5
+ export type ExposedSetup<T> = IsAny<T> extends true ? Record<string, unknown> : [T] extends [void | undefined] ? Record<string, never> : T extends Record<string, unknown> ? T : Record<string, never>;
3
6
  export type ComponentSetup<SetupResult = void | Record<string, unknown>, Props extends Record<string, unknown> = Record<string, unknown>> = {
4
7
  name: string;
5
8
  setup(el: RefElement, props: ComponentProps<Props>): SetupResult;
6
9
  };
7
- /** @deprecated Use `ComponentSetup` instead. */
8
- export type IComponent<SetupResult = void | Record<string, unknown>, Props extends Record<string, unknown> = Record<string, unknown>> = ComponentSetup<SetupResult, Props>;
10
+ /** Mounted component instance exposed to app / hook callers. */
11
+ export type ComponentContext<Exposed extends Record<string, unknown> = Record<string, never>> = {
12
+ readonly current: Exposed;
13
+ readonly element: RefElement;
14
+ readonly name: string;
15
+ };
9
16
  export type Cleanup = () => void;
10
17
  export type LifecycleHandler = () => void | Cleanup;
11
18
  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
19
  export type Cue = (el: RefElement, signal: AbortSignal) => Promise<void>;
20
+ export {};
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2022 hayakawasho
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
package/README.ja.md DELETED
@@ -1,269 +0,0 @@
1
- [English](./README.md) | **日本語**
2
-
3
- # nagi
4
-
5
- **Composition-style ergonomics for vanilla DOM. Bring your own mounter.**
6
-
7
- [![npm](https://img.shields.io/npm/v/@usenagi/core)](https://www.npmjs.com/package/@usenagi/core)
8
- [![bundle size](https://img.shields.io/bundlephobia/minzip/@usenagi/core)](https://bundlephobia.com/package/@usenagi/core)
9
- [![license](https://img.shields.io/npm/l/@usenagi/core)](./LICENSE)
10
-
11
- ---
12
-
13
- ## Why nagi?
14
-
15
- **既存 HTML に小さく足せる**
16
-
17
- WordPress、CMS、Webflow、静的サイトなどに、仮想 DOM やテンプレートを持ち込まず、`setup()` / lifecycle / reactivity を追加できる。
18
-
19
- **アニメーションと相性が良い**
20
-
21
- GSAP、Lenis、IntersectionObserver などを `setup()` で初期化し、`useUnmount()` でクリーンアップできる。
22
-
23
- **マウント戦略を縛らない**
24
-
25
- `[data-component]` スキャン、manifest、lazy import、MutationObserver などは、利用側で自由に組み立てられる。
26
-
27
- ---
28
-
29
- ## 30-second example
30
-
31
- ```ts
32
- // counter.ts
33
- import { create, signal, useWatch, useDomRef } from "@usenagi/core";
34
-
35
- const { component } = create();
36
-
37
- component({
38
- name: "counter",
39
- setup() {
40
- const { refs } = useDomRef<{
41
- count: HTMLSpanElement;
42
- btn: HTMLButtonElement;
43
- }>();
44
-
45
- const n = signal(0);
46
- useWatch(n, (v) => {
47
- refs.count.textContent = String(v);
48
- });
49
- refs.btn.addEventListener("click", () => {
50
- n.value++;
51
- });
52
- },
53
- })(document.querySelector("#counter")!);
54
- ```
55
-
56
- ```html
57
- <div id="counter">
58
- <span data-ref="count">0</span>
59
- <button data-ref="btn">+</button>
60
- </div>
61
- ```
62
-
63
- ---
64
-
65
- ## Quick start
66
-
67
- ```bash
68
- npm i @usenagi/core
69
- ```
70
-
71
- ### First component
72
-
73
- ```ts
74
- import { create, defineComponent, signal, useWatch, useDomRef } from "@usenagi/core";
75
-
76
- const Greeting = defineComponent({
77
- name: "greeting",
78
- setup(el, props) {
79
- const { refs } = useDomRef<{ message: HTMLParagraphElement }>();
80
- const text = signal((props.name as string) ?? "world");
81
-
82
- useWatch(text, (v) => {
83
- refs.message.textContent = `Hello, ${v}!`;
84
- });
85
- refs.message.textContent = `Hello, ${text.value}!`;
86
- },
87
- });
88
-
89
- create().component(Greeting)(document.querySelector("#app")!);
90
- ```
91
-
92
- ### Scheduler + deferred mount
93
-
94
- 遅延マウントが必要な場合は、scheduler / cue addons を追加する。
95
-
96
- ```ts
97
- import { create } from "@usenagi/core";
98
- import { schedulerAddon } from "@usenagi/core/addons/scheduler";
99
- import { visible, idle } from "@usenagi/core/addons/cue";
100
-
101
- const app = create().install(schedulerAddon());
102
-
103
- // mount when the element enters the viewport
104
- app.component(HeavyWidget, { when: visible() })(el);
105
-
106
- // mount during browser idle time
107
- app.component(Analytics, { when: idle() })(el);
108
- ```
109
-
110
- `schedulerAddon()` を使うと、`when` は `setup()` の前に待機する条件、`priority` は `setup()` を含む mount task の実行タイミングを決める。
111
-
112
- ### BYO mounter recipe
113
-
114
- `[data-component]` スキャン、manifest、cue を組み合わせた自動マウントの例。
115
- → [examples/recipes/byo-mounter](./examples/recipes/byo-mounter/main.ts)
116
-
117
- ---
118
-
119
- ## API
120
-
121
- ### Reactivity
122
-
123
- | API | 説明 |
124
- | ---------------------- | ------------------------------------------------------ |
125
- | `signal(value)` | `.value` を持つリアクティブな値コンテナを作成する |
126
- | `readonly(signal)` | 書き込み可能な `signal` の読み取り専用ラッパー |
127
- | `useComputed(fn)` | `signal` の依存を自動追跡する派生値 |
128
- | `useWatch(target, cb)` | 値変更時に `cb` を呼ぶ。unmount 時に自動で購読解除する |
129
-
130
- ```ts
131
- const width = signal(10);
132
- const height = signal(5);
133
- const area = useComputed(() => width.value * height.value); // auto-recomputed
134
-
135
- useWatch(area, (v) => {
136
- output.textContent = String(v);
137
- });
138
- ```
139
-
140
- ### Lifecycle
141
-
142
- | API | 説明 |
143
- | ---------------- | ------------------------------------------- |
144
- | `useMount(fn)` | コンポーネントのマウント完了後に1回実行する |
145
- | `useUnmount(fn)` | unmount 時に実行する。クリーンアップに使う |
146
-
147
- ```ts
148
- import gsap from 'gsap';
149
-
150
- setup(el) {
151
- const tween = gsap.from(el, { opacity: 0, duration: 0.4 });
152
- useUnmount(() => tween.kill());
153
- }
154
- ```
155
-
156
- ### DOM helpers
157
-
158
- ルート要素には **`setup(el)`** を、**`[data-ref]`** の子要素には **`useDomRef()`** を使う。
159
-
160
- | API | 説明 |
161
- | ------------------------------ | ------------------------------------------------------- |
162
- | `useDomRef<T>()` | `[data-ref]` 要素への型付きアクセス |
163
- | `useEvent(el, event, handler)` | イベントリスナーを追加する。unmount 時に自動で除去する |
164
- | `useSlot()` | 子コンポーネントをマウントする。親の unmount に連動する |
165
-
166
- ### Parent / child
167
-
168
- `useSlot()` で子コンポーネントをマウントできる。親から子へは `props` または `createContext` / `withContext` で値を渡せる。`addChild()` が返す child context から、子の `setup()` の返り値も参照できる。
169
-
170
- → [examples/parent-child](./examples/parent-child/main.ts)
171
-
172
- ### Observers
173
-
174
- | API | 説明 |
175
- | --------------------------------- | ----------------------------------------------------------- |
176
- | `useIntersectionWatch(cb, opts?)` | IntersectionObserver のラッパー。unmount 時に自動で切断する |
177
- | `useMediaQuery(query)` | `matchMedia` の結果を `ReadonlySignal<boolean>` で返す |
178
-
179
- ### Addons
180
-
181
- ```ts
182
- import { create, defineAddon } from "@usenagi/core";
183
- import { schedulerAddon } from "@usenagi/core/addons/scheduler";
184
-
185
- const app = create().install(schedulerAddon(), myAddon());
186
- ```
187
-
188
- | API | 説明 |
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";
203
- import { visible, idle, interaction, media } from "@usenagi/core/addons/cue";
204
- ```
205
-
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 |
214
-
215
- ---
216
-
217
- ## Comparison
218
-
219
- | | **nagi** | Alpine.js | Stimulus | petite-vue |
220
- | -------------------------- | -------- | --------- | -------- | ---------- |
221
- | Inline JS in HTML | ✗ | ◯ | ✗ | ◯ |
222
- | Composition-style setup | ◯ | △ | ✗ | ◯ |
223
- | BYO mounter | ◯ | △ | △ | △ |
224
- | Async mount cue | ◯ | ✗ | ✗ | ✗ |
225
- | Lifecycle cleanup | ◯ | △ | ◯ | △ |
226
- | computed (derived signals) | ◯ | ◯ | ✗ | ◯ |
227
- | Core gzip | ~2.5 kB | ~16 kB | ~8 kB | ~6 kB |
228
-
229
- (◯ = 組み込み、△ = 利用側の実装・規約で対応可能、✗ = 主な機能ではない)
230
-
231
- - **vs Alpine / petite-vue**: HTML に式を直接書かず、ロジックを `.ts` に集約する。
232
- - **vs Stimulus**: Controller 規約はない。マウント戦略は利用側で自由に組み立てられる。
233
- - **vs React / Vue**: 宣言的 UI フレームワークではなく、既存 DOM に lifecycle を足す薄いレイヤー。
234
-
235
- ---
236
-
237
- ## When to use / When not to
238
-
239
- **向いているケース:**
240
-
241
- - React や Vue のランタイムを持ち込みにくいプロジェクト(CMS、Webflow、WordPress など)
242
- - GSAP や Lenis を多用する、アニメーション主体のサイト
243
- - ページの一部だけにインタラクティブな UI を追加したい場合
244
- - `setup()`、lifecycle、reactivity による composition-style で書きたいが、仮想 DOM は不要な場合
245
-
246
- **向いていないケース:**
247
-
248
- - リスト描画や条件分岐を HTML テンプレートで書きたい場合(`v-for` や `v-if` 相当はない)
249
- - 複雑なオブジェクトの深いリアクティビティが必要な場合(`reactive({})` は提供しない)
250
- - SSR / hydration が必要な場合
251
- - 状態管理、ルーティング、宣言的な view rendering をフレームワークにまとめて任せたい場合
252
-
253
- ---
254
-
255
- ## Examples
256
-
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 |
264
-
265
- ---
266
-
267
- ## License
268
-
269
- MIT © [hayakawasho](https://github.com/hayakawasho)