@zoijs/core 1.2.0 → 1.3.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.
- package/CHANGELOG.md +93 -70
- package/README.md +154 -154
- package/package.json +63 -63
- package/src/core/boundary.js +34 -0
- package/src/core/each.js +24 -24
- package/src/core/renderer.js +442 -400
- package/src/index.d.ts +185 -164
- package/src/index.js +1 -0
package/src/index.d.ts
CHANGED
|
@@ -1,164 +1,185 @@
|
|
|
1
|
-
// Type definitions for Zoijs.
|
|
2
|
-
//
|
|
3
|
-
// Zoijs is authored in plain JavaScript; these declarations add editor
|
|
4
|
-
// autocomplete and optional type-checking WITHOUT converting the project to
|
|
5
|
-
// TypeScript. Using them is entirely opt-in — JS users are unaffected.
|
|
6
|
-
|
|
7
|
-
/** A reactive value created by {@link createState}. */
|
|
8
|
-
export interface State<T> {
|
|
9
|
-
/** Read the current value. Inside a binding/effect this subscribes to it. */
|
|
10
|
-
get(): T;
|
|
11
|
-
/** Write a new value. Dependents update only if the value actually changed. */
|
|
12
|
-
set(next: T): void;
|
|
13
|
-
/** Read the current value WITHOUT subscribing. */
|
|
14
|
-
peek(): T;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** A lazy, cached, value-gated derived value created by {@link computed}. */
|
|
18
|
-
export interface Computed<T> {
|
|
19
|
-
/** Read the current value. Recomputes only if a dependency changed. */
|
|
20
|
-
get(): T;
|
|
21
|
-
/** Read without subscribing. */
|
|
22
|
-
peek(): T;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
declare const templateBrand: unique symbol;
|
|
26
|
-
/**
|
|
27
|
-
* The result of an `html` template. Pass it to {@link mount}, return it from a
|
|
28
|
-
* component, or place it in another template. Treat it as opaque.
|
|
29
|
-
*/
|
|
30
|
-
export interface TemplateResult {
|
|
31
|
-
readonly [templateBrand]: true;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
declare const eachBrand: unique symbol;
|
|
35
|
-
/** The result of {@link each}. Place it in a template's child position. */
|
|
36
|
-
export interface EachResult {
|
|
37
|
-
readonly [eachBrand]: true;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** A component is a function that returns an `html` template. */
|
|
41
|
-
export type Component = () => TemplateResult;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* A callback ref. Place it on an element as `ref=${fn}`; it receives the real DOM
|
|
45
|
-
* element once the surrounding render has been inserted (deferred one microtask,
|
|
46
|
-
* so `focus()` / `scrollIntoView()` / `getBoundingClientRect()` work). It may
|
|
47
|
-
* return a cleanup function, which runs on unmount or list-item removal. It runs
|
|
48
|
-
* once and is not reactive.
|
|
49
|
-
*
|
|
50
|
-
* ```ts
|
|
51
|
-
* html`<input ref=${(el: HTMLInputElement) => el.focus()} />`
|
|
52
|
-
* html`<div ref=${(el) => { const c = chart(el); return () => c.destroy(); }}></div>`
|
|
53
|
-
* ```
|
|
54
|
-
*/
|
|
55
|
-
export type Ref<E extends Element = Element> = (element: E) => void | (() => void);
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Tagged-template function — write your markup as HTML.
|
|
59
|
-
*
|
|
60
|
-
* ```js
|
|
61
|
-
* html`<button onclick=${() => count.set(count.get() + 1)}>${() => count.get()}</button>`
|
|
62
|
-
* ```
|
|
63
|
-
*
|
|
64
|
-
* To reach the rendered DOM element, add a callback `ref` (see {@link Ref}):
|
|
65
|
-
* `html\`<input ref=${(el) => el.focus()} />\``.
|
|
66
|
-
*/
|
|
67
|
-
export function html(strings: TemplateStringsArray, ...values: unknown[]): TemplateResult;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Render a component (or a template) into a DOM element or CSS selector.
|
|
71
|
-
* Returns an `unmount()` that detaches the DOM and disposes all reactivity.
|
|
72
|
-
*/
|
|
73
|
-
export function mount(component: Component | TemplateResult, target: Element | string): () => void;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Create a reactive value.
|
|
77
|
-
*
|
|
78
|
-
* ```ts
|
|
79
|
-
* const count = createState(0); // State<number>
|
|
80
|
-
* const name = createState<string>(""); // explicit
|
|
81
|
-
* ```
|
|
82
|
-
*/
|
|
83
|
-
export function createState<T>(initial: T, equals?: (a: T, b: T) => boolean): State<T>;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Create a lazy, cached derived value.
|
|
87
|
-
*
|
|
88
|
-
* ```ts
|
|
89
|
-
* const fullName = computed(() => `${first.get()} ${last.get()}`); // Computed<string>
|
|
90
|
-
* ```
|
|
91
|
-
*/
|
|
92
|
-
export function computed<T>(fn: () => T, equals?: (a: T, b: T) => boolean): Computed<T>;
|
|
93
|
-
|
|
94
|
-
/** A disposable handle returned by {@link effect}. */
|
|
95
|
-
export interface EffectHandle {
|
|
96
|
-
/** Dispose the effect now. It also auto-disposes with its owner (component/list item). */
|
|
97
|
-
dispose(): void;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Run a side effect that re-runs whenever a reactive value it reads changes.
|
|
102
|
-
* Runs once immediately, then again (batched on a microtask) on any change.
|
|
103
|
-
* Dependencies are tracked automatically — there is no dependency array.
|
|
104
|
-
*
|
|
105
|
-
* The function may return a cleanup function (same convention as a `ref`); it
|
|
106
|
-
* runs **before the next run** and **on dispose**. Use the return value for
|
|
107
|
-
* per-run teardown — `onCleanup` is component-scoped (fires on unmount), not
|
|
108
|
-
* per-run.
|
|
109
|
-
*
|
|
110
|
-
* Created inside a component or list item, it auto-disposes with that scope;
|
|
111
|
-
* created at module top level, it lives until you call `dispose()`.
|
|
112
|
-
*
|
|
113
|
-
* ```ts
|
|
114
|
-
* // persist on change
|
|
115
|
-
* effect(() => localStorage.setItem("theme", theme.get()));
|
|
116
|
-
*
|
|
117
|
-
* // per-run cleanup
|
|
118
|
-
* effect(() => {
|
|
119
|
-
* const id = setInterval(() => poll(query.get()), 1000);
|
|
120
|
-
* return () => clearInterval(id);
|
|
121
|
-
* });
|
|
122
|
-
* ```
|
|
123
|
-
*
|
|
124
|
-
* For reactive *content on screen*, use a binding (`${() => …}`), not an effect.
|
|
125
|
-
*/
|
|
126
|
-
export function effect(fn: () => void | (() => void)): EffectHandle;
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Keyed list rendering. `items` may be a reactive function or a plain array;
|
|
130
|
-
* `keyFn` returns a stable unique key; `renderFn` returns the template for one item.
|
|
131
|
-
*
|
|
132
|
-
* ```ts
|
|
133
|
-
* each(() => todos.get(), (t) => t.id, (t) => html`<li>${() => t.text}</li>`)
|
|
134
|
-
* ```
|
|
135
|
-
*/
|
|
136
|
-
export function each<T>(
|
|
137
|
-
items: () => readonly T[],
|
|
138
|
-
keyFn: (item: T) => unknown,
|
|
139
|
-
renderFn: (item: T) => TemplateResult
|
|
140
|
-
): EachResult;
|
|
141
|
-
export function each<T>(
|
|
142
|
-
items: readonly T[],
|
|
143
|
-
keyFn: (item: T) => unknown,
|
|
144
|
-
renderFn: (item: T) => TemplateResult
|
|
145
|
-
): EachResult;
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
1
|
+
// Type definitions for Zoijs.
|
|
2
|
+
//
|
|
3
|
+
// Zoijs is authored in plain JavaScript; these declarations add editor
|
|
4
|
+
// autocomplete and optional type-checking WITHOUT converting the project to
|
|
5
|
+
// TypeScript. Using them is entirely opt-in — JS users are unaffected.
|
|
6
|
+
|
|
7
|
+
/** A reactive value created by {@link createState}. */
|
|
8
|
+
export interface State<T> {
|
|
9
|
+
/** Read the current value. Inside a binding/effect this subscribes to it. */
|
|
10
|
+
get(): T;
|
|
11
|
+
/** Write a new value. Dependents update only if the value actually changed. */
|
|
12
|
+
set(next: T): void;
|
|
13
|
+
/** Read the current value WITHOUT subscribing. */
|
|
14
|
+
peek(): T;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A lazy, cached, value-gated derived value created by {@link computed}. */
|
|
18
|
+
export interface Computed<T> {
|
|
19
|
+
/** Read the current value. Recomputes only if a dependency changed. */
|
|
20
|
+
get(): T;
|
|
21
|
+
/** Read without subscribing. */
|
|
22
|
+
peek(): T;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
declare const templateBrand: unique symbol;
|
|
26
|
+
/**
|
|
27
|
+
* The result of an `html` template. Pass it to {@link mount}, return it from a
|
|
28
|
+
* component, or place it in another template. Treat it as opaque.
|
|
29
|
+
*/
|
|
30
|
+
export interface TemplateResult {
|
|
31
|
+
readonly [templateBrand]: true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
declare const eachBrand: unique symbol;
|
|
35
|
+
/** The result of {@link each}. Place it in a template's child position. */
|
|
36
|
+
export interface EachResult {
|
|
37
|
+
readonly [eachBrand]: true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** A component is a function that returns an `html` template. */
|
|
41
|
+
export type Component = () => TemplateResult;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A callback ref. Place it on an element as `ref=${fn}`; it receives the real DOM
|
|
45
|
+
* element once the surrounding render has been inserted (deferred one microtask,
|
|
46
|
+
* so `focus()` / `scrollIntoView()` / `getBoundingClientRect()` work). It may
|
|
47
|
+
* return a cleanup function, which runs on unmount or list-item removal. It runs
|
|
48
|
+
* once and is not reactive.
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* html`<input ref=${(el: HTMLInputElement) => el.focus()} />`
|
|
52
|
+
* html`<div ref=${(el) => { const c = chart(el); return () => c.destroy(); }}></div>`
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export type Ref<E extends Element = Element> = (element: E) => void | (() => void);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Tagged-template function — write your markup as HTML.
|
|
59
|
+
*
|
|
60
|
+
* ```js
|
|
61
|
+
* html`<button onclick=${() => count.set(count.get() + 1)}>${() => count.get()}</button>`
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* To reach the rendered DOM element, add a callback `ref` (see {@link Ref}):
|
|
65
|
+
* `html\`<input ref=${(el) => el.focus()} />\``.
|
|
66
|
+
*/
|
|
67
|
+
export function html(strings: TemplateStringsArray, ...values: unknown[]): TemplateResult;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Render a component (or a template) into a DOM element or CSS selector.
|
|
71
|
+
* Returns an `unmount()` that detaches the DOM and disposes all reactivity.
|
|
72
|
+
*/
|
|
73
|
+
export function mount(component: Component | TemplateResult, target: Element | string): () => void;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Create a reactive value.
|
|
77
|
+
*
|
|
78
|
+
* ```ts
|
|
79
|
+
* const count = createState(0); // State<number>
|
|
80
|
+
* const name = createState<string>(""); // explicit
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export function createState<T>(initial: T, equals?: (a: T, b: T) => boolean): State<T>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create a lazy, cached derived value.
|
|
87
|
+
*
|
|
88
|
+
* ```ts
|
|
89
|
+
* const fullName = computed(() => `${first.get()} ${last.get()}`); // Computed<string>
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export function computed<T>(fn: () => T, equals?: (a: T, b: T) => boolean): Computed<T>;
|
|
93
|
+
|
|
94
|
+
/** A disposable handle returned by {@link effect}. */
|
|
95
|
+
export interface EffectHandle {
|
|
96
|
+
/** Dispose the effect now. It also auto-disposes with its owner (component/list item). */
|
|
97
|
+
dispose(): void;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Run a side effect that re-runs whenever a reactive value it reads changes.
|
|
102
|
+
* Runs once immediately, then again (batched on a microtask) on any change.
|
|
103
|
+
* Dependencies are tracked automatically — there is no dependency array.
|
|
104
|
+
*
|
|
105
|
+
* The function may return a cleanup function (same convention as a `ref`); it
|
|
106
|
+
* runs **before the next run** and **on dispose**. Use the return value for
|
|
107
|
+
* per-run teardown — `onCleanup` is component-scoped (fires on unmount), not
|
|
108
|
+
* per-run.
|
|
109
|
+
*
|
|
110
|
+
* Created inside a component or list item, it auto-disposes with that scope;
|
|
111
|
+
* created at module top level, it lives until you call `dispose()`.
|
|
112
|
+
*
|
|
113
|
+
* ```ts
|
|
114
|
+
* // persist on change
|
|
115
|
+
* effect(() => localStorage.setItem("theme", theme.get()));
|
|
116
|
+
*
|
|
117
|
+
* // per-run cleanup
|
|
118
|
+
* effect(() => {
|
|
119
|
+
* const id = setInterval(() => poll(query.get()), 1000);
|
|
120
|
+
* return () => clearInterval(id);
|
|
121
|
+
* });
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* For reactive *content on screen*, use a binding (`${() => …}`), not an effect.
|
|
125
|
+
*/
|
|
126
|
+
export function effect(fn: () => void | (() => void)): EffectHandle;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Keyed list rendering. `items` may be a reactive function or a plain array;
|
|
130
|
+
* `keyFn` returns a stable unique key; `renderFn` returns the template for one item.
|
|
131
|
+
*
|
|
132
|
+
* ```ts
|
|
133
|
+
* each(() => todos.get(), (t) => t.id, (t) => html`<li>${() => t.text}</li>`)
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export function each<T>(
|
|
137
|
+
items: () => readonly T[],
|
|
138
|
+
keyFn: (item: T) => unknown,
|
|
139
|
+
renderFn: (item: T) => TemplateResult
|
|
140
|
+
): EachResult;
|
|
141
|
+
export function each<T>(
|
|
142
|
+
items: readonly T[],
|
|
143
|
+
keyFn: (item: T) => unknown,
|
|
144
|
+
renderFn: (item: T) => TemplateResult
|
|
145
|
+
): EachResult;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Render `child`; if it **throws synchronously while building its markup** (a
|
|
149
|
+
* setup/render error that would otherwise break the whole `mount`), tear down the
|
|
150
|
+
* partial work and render `fallback` instead. Place the call in a template slot.
|
|
151
|
+
*
|
|
152
|
+
* Catches synchronous setup/render throws only. Errors in reactive *updates* are
|
|
153
|
+
* already contained per binding by the core; *async* errors belong to
|
|
154
|
+
* `@zoijs/resource` / `@zoijs/action`'s `error()` state. It renders once (no reset
|
|
155
|
+
* — re-mount the subtree to retry), logs in dev, and is silent in production.
|
|
156
|
+
*
|
|
157
|
+
* ```ts
|
|
158
|
+
* html`<section>
|
|
159
|
+
* ${boundary(() => RiskyWidget(), (err) => html`<p>Couldn't load this.</p>`)}
|
|
160
|
+
* </section>`
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export function boundary<C, F>(
|
|
164
|
+
child: (() => C) | C,
|
|
165
|
+
fallback: ((error: unknown) => F) | F
|
|
166
|
+
): C | F;
|
|
167
|
+
|
|
168
|
+
/** Toggle development warnings (default: `dev` is `true`). */
|
|
169
|
+
export function configure(options: { dev?: boolean }): void;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Register a teardown function for the current component or list item. It runs
|
|
173
|
+
* when that component is unmounted or that list item is removed. Use it for
|
|
174
|
+
* timers, subscriptions, or third-party widgets created during setup.
|
|
175
|
+
*
|
|
176
|
+
* ```ts
|
|
177
|
+
* function Clock() {
|
|
178
|
+
* const now = createState(Date.now());
|
|
179
|
+
* const id = setInterval(() => now.set(Date.now()), 1000);
|
|
180
|
+
* onCleanup(() => clearInterval(id));
|
|
181
|
+
* return html`<time>${() => now.get()}</time>`;
|
|
182
|
+
* }
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
export function onCleanup(fn: () => void): void;
|
package/src/index.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
export { html } from "./core/html.js";
|
|
9
9
|
export { mount } from "./core/mount.js";
|
|
10
10
|
export { each } from "./core/each.js";
|
|
11
|
+
export { boundary } from "./core/boundary.js";
|
|
11
12
|
export { createState } from "./reactivity/state.js";
|
|
12
13
|
export { computed } from "./reactivity/computed.js";
|
|
13
14
|
export { effect } from "./reactivity/effect.js";
|