@expressive/mvc 0.86.0 → 0.88.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.md +2 -2
- package/dist/component.d.ts +33 -11
- package/dist/component.js +88 -53
- package/dist/component.js.map +1 -1
- package/dist/context.d.ts +3 -1
- package/dist/context.js +30 -12
- package/dist/context.js.map +1 -1
- package/dist/dispatch.d.ts +6 -1
- package/dist/dispatch.js +14 -6
- package/dist/dispatch.js.map +1 -1
- package/dist/field/def.js +4 -3
- package/dist/field/def.js.map +1 -1
- package/dist/field/get.js +21 -5
- package/dist/field/get.js.map +1 -1
- package/dist/field/has.js +1 -1
- package/dist/field/map.js +1 -1
- package/dist/field/ref.js +1 -1
- package/dist/field/set.js +1 -1
- package/dist/hot.d.ts +29 -0
- package/dist/hot.js +225 -0
- package/dist/hot.js.map +1 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/jsx-dev-runtime.d.ts +1 -2
- package/dist/jsx-dev-runtime.js +1 -1
- package/dist/jsx-runtime.d.ts +78 -5
- package/dist/jsx-runtime.js +69 -2
- package/dist/jsx-runtime.js.map +1 -0
- package/dist/observable.js +3 -1
- package/dist/observable.js.map +1 -1
- package/dist/state.d.ts +78 -32
- package/dist/state.js +143 -89
- package/dist/state.js.map +1 -1
- package/package.json +7 -5
- package/dist/caught.d.ts +0 -5237
- package/dist/caught.js +0 -64
- package/dist/caught.js.map +0 -1
- package/dist/runtime.d.ts +0 -71
- package/dist/runtime.js +0 -68
- package/dist/runtime.js.map +0 -1
package/README.md
CHANGED
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
The core of [Expressive MVC](https://github.com/gabeklein/expressive-mvc): reactive primitives built around plain classes, with no framework dependency. Provides the `State` model, instructions, context, and the renderer-agnostic `Component` that adapters like [`@expressive/react`](https://www.npmjs.com/package/@expressive/react) build on.
|
|
15
15
|
|
|
16
|
-
> **Building a React app?** Install [`@expressive/react`](https://www.npmjs.com/package/@expressive/react)
|
|
16
|
+
> **Building a React app?** Install [`@expressive/react`](https://www.npmjs.com/package/@expressive/react) alongside this package - the adapter declares `@expressive/mvc` as a peer dependency, so both belong in your `package.json`. Import `State`, `Component`, and every instruction from this package, and import the adapter once from your entry so the host registers. The same holds for [`@expressive/dom`](https://www.npmjs.com/package/@expressive/dom), the renderer without React.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Installed alone, it serves host-agnostic code - a shared domain package, a Node service, or a new adapter:
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
21
|
npm install @expressive/mvc
|
package/dist/component.d.ts
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import { State } from "./state.js";
|
|
2
|
-
import { Host } from "./runtime.js";
|
|
2
|
+
import { Host } from "./jsx-runtime.js";
|
|
3
3
|
|
|
4
4
|
//#region src/component.d.ts
|
|
5
5
|
type IfEquals<X, Y, A, B> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? A : B;
|
|
6
6
|
/** Keys of T which are settable (excludes get-only accessors and `readonly`). */
|
|
7
7
|
type Acceptable<T> = { [P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P, never> }[keyof T];
|
|
8
|
+
type ForProps<T extends State> = {
|
|
9
|
+
children?: Component.Node;
|
|
10
|
+
fallback?: Component.Node | false;
|
|
11
|
+
catch?: (error: Error, instance: Component) => Promise<void> | void;
|
|
12
|
+
} & ({
|
|
13
|
+
for: State.Extends<T>;
|
|
14
|
+
is?: (instance: T) => void;
|
|
15
|
+
} | {
|
|
16
|
+
for: T | undefined;
|
|
17
|
+
is?: never;
|
|
18
|
+
}) & { [K in Exclude<keyof T, keyof State> & Acceptable<T>]?: T[K] };
|
|
8
19
|
declare namespace Component {
|
|
9
20
|
/**
|
|
10
21
|
* Host element type produced by `Component.render`. Delegates to the
|
|
@@ -17,7 +28,7 @@ declare namespace Component {
|
|
|
17
28
|
type Node = Host extends {
|
|
18
29
|
node: infer T;
|
|
19
30
|
} ? T : any;
|
|
20
|
-
interface BaseProps<T extends Component
|
|
31
|
+
interface BaseProps<T extends Component<any>> {
|
|
21
32
|
/**
|
|
22
33
|
* Callback for newly created instance. Only called once.
|
|
23
34
|
*
|
|
@@ -25,13 +36,18 @@ declare namespace Component {
|
|
|
25
36
|
* may configure state that `new()` then observes. To react to a fully
|
|
26
37
|
* initialized instance instead, use `watch` or an effect.
|
|
27
38
|
*/
|
|
28
|
-
is
|
|
39
|
+
is?(instance: T): void;
|
|
29
40
|
/**
|
|
30
41
|
* Fallback to show when suspended or in error recovery.
|
|
31
42
|
* Pass `false` to opt out of the component's own suspense boundary,
|
|
32
43
|
* letting suspension bubble to an ancestor.
|
|
33
44
|
*/
|
|
34
45
|
fallback?: Component.Node;
|
|
46
|
+
/**
|
|
47
|
+
* Called when this element's content throws, in place of the class's own
|
|
48
|
+
* `catch`. A returned promise retries once settled.
|
|
49
|
+
*/
|
|
50
|
+
catch?(error: Error, instance: T): Promise<void> | void;
|
|
35
51
|
}
|
|
36
52
|
type StateProps<T extends State> = { [K in Exclude<keyof T, keyof Component> & Acceptable<T>]?: T[K] };
|
|
37
53
|
type RenderProps<T> = [T] extends [(props: infer P) => any] ? [keyof NonNullable<P>] extends [never] ? {
|
|
@@ -39,16 +55,16 @@ declare namespace Component {
|
|
|
39
55
|
} : NonNullable<P> : {
|
|
40
56
|
children?: Component.Node;
|
|
41
57
|
};
|
|
42
|
-
type Props<T extends Component
|
|
58
|
+
type Props<T extends Component<any>> = StateProps<T> & BaseProps<T> & RenderProps<T['render']>;
|
|
43
59
|
}
|
|
44
|
-
declare class Component extends State {
|
|
60
|
+
declare class Component<P = unknown> extends State {
|
|
45
61
|
/**
|
|
46
62
|
* All JSX attributes passed to this component.
|
|
47
63
|
* Includes state-derived props, render props, and built-in props like `is` and `fallback`.
|
|
48
64
|
*
|
|
49
65
|
* Will incorperate extra props you declare as props parameter in `render` method.
|
|
50
66
|
*/
|
|
51
|
-
readonly props: Component.Props<this>;
|
|
67
|
+
readonly props: [P] extends [State] ? ForProps<P> : Component.Props<this>;
|
|
52
68
|
/** Stable identity used when this instance is rendered in a collection. */
|
|
53
69
|
readonly key: string;
|
|
54
70
|
/**
|
|
@@ -71,9 +87,7 @@ declare class Component extends State {
|
|
|
71
87
|
* via `props = {} as { ... }`. Without a parameter (the default below),
|
|
72
88
|
* children pass through.
|
|
73
89
|
*
|
|
74
|
-
* The constructor installs a composed `render` per instance
|
|
75
|
-
* `type` pass seals this and any override non-configurable, keeping it the
|
|
76
|
-
* content-render seam the chain reads.
|
|
90
|
+
* The constructor installs a composed `render` per instance.
|
|
77
91
|
*/
|
|
78
92
|
render(props?: {}): Component.Node;
|
|
79
93
|
/**
|
|
@@ -85,8 +99,16 @@ declare class Component extends State {
|
|
|
85
99
|
* await async recovery, or await user interaction before retrying. If you
|
|
86
100
|
* assign a fallback within catch, it will be reverted after resolved.
|
|
87
101
|
*/
|
|
88
|
-
catch?(error: Error): Promise<void> | void;
|
|
102
|
+
catch?(error: Error, instance: this): Promise<void> | void;
|
|
89
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Render `this` through its class's composed content render: content renders
|
|
106
|
+
* up the prototype chain, each base layer receiving the subclass's as a lazy
|
|
107
|
+
* `children` getter. Falls back to passing `children` through.
|
|
108
|
+
*/
|
|
109
|
+
declare function compose(this: State, props?: {}): any;
|
|
110
|
+
/** Drop composed renders, so each is rebuilt from current prototypes. */
|
|
111
|
+
declare function rechain(): void;
|
|
90
112
|
//#endregion
|
|
91
|
-
export { Component };
|
|
113
|
+
export { Component, compose, rechain };
|
|
92
114
|
//# sourceMappingURL=component.d.ts.map
|
package/dist/component.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { State, unbind } from "./state.js";
|
|
1
|
+
import { State, adopt, trailing, unbind } from "./state.js";
|
|
2
2
|
import { Context } from "./context.js";
|
|
3
3
|
import { set } from "./field/set.js";
|
|
4
4
|
|
|
5
5
|
//#region src/component.ts
|
|
6
6
|
const PENDING = /* @__PURE__ */ new WeakMap();
|
|
7
7
|
/** Per-class composed content render. */
|
|
8
|
-
|
|
9
|
-
var Component = class extends State {
|
|
8
|
+
let CHAIN = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
var Component = class Component extends State {
|
|
10
10
|
/**
|
|
11
11
|
* Content to display while this component or its children are suspended.
|
|
12
12
|
* Will cover suspense by pending properties in `this.render` as well.
|
|
@@ -20,7 +20,8 @@ var Component = class extends State {
|
|
|
20
20
|
constructor(props, ...rest) {
|
|
21
21
|
if (props == null) props = {};
|
|
22
22
|
const seen = {};
|
|
23
|
-
const
|
|
23
|
+
const twin = PENDING.get(props);
|
|
24
|
+
const provider = new.target === Component && "for" in props;
|
|
24
25
|
if (typeof props == "object") merge(props);
|
|
25
26
|
function merge(props) {
|
|
26
27
|
for (const k in props) seen[k] = void 0;
|
|
@@ -29,19 +30,21 @@ var Component = class extends State {
|
|
|
29
30
|
...props
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
|
-
super(
|
|
33
|
+
super([
|
|
33
34
|
props,
|
|
34
35
|
rest.filter((x) => !(x instanceof Context)),
|
|
35
36
|
() => {
|
|
36
|
-
|
|
37
|
+
const other = twin || PENDING.get(props);
|
|
38
|
+
if (other !== this) {
|
|
39
|
+
trailing(other).forEach((orphan) => orphan.set(null));
|
|
40
|
+
other.set(null);
|
|
41
|
+
}
|
|
42
|
+
if (provider) provide(this);
|
|
43
|
+
else props.is?.(this);
|
|
37
44
|
Object.defineProperty(this, "props", { enumerable: false });
|
|
38
45
|
PENDING.delete(props);
|
|
39
46
|
}
|
|
40
47
|
]);
|
|
41
|
-
if (copy) {
|
|
42
|
-
this.set(null);
|
|
43
|
-
return copy;
|
|
44
|
-
}
|
|
45
48
|
PENDING.set(props, this);
|
|
46
49
|
this.props = props;
|
|
47
50
|
this.set("props", () => {
|
|
@@ -50,7 +53,7 @@ var Component = class extends State {
|
|
|
50
53
|
Object.defineProperty(this, "render", {
|
|
51
54
|
writable: true,
|
|
52
55
|
configurable: true,
|
|
53
|
-
value:
|
|
56
|
+
value: compose
|
|
54
57
|
});
|
|
55
58
|
}
|
|
56
59
|
/**
|
|
@@ -62,9 +65,7 @@ var Component = class extends State {
|
|
|
62
65
|
* via `props = {} as { ... }`. Without a parameter (the default below),
|
|
63
66
|
* children pass through.
|
|
64
67
|
*
|
|
65
|
-
* The constructor installs a composed `render` per instance
|
|
66
|
-
* `type` pass seals this and any override non-configurable, keeping it the
|
|
67
|
-
* content-render seam the chain reads.
|
|
68
|
+
* The constructor installs a composed `render` per instance.
|
|
68
69
|
*/
|
|
69
70
|
render(props) {
|
|
70
71
|
const { children } = props || this.props;
|
|
@@ -83,50 +84,80 @@ Object.defineProperty(Component.prototype, "key", {
|
|
|
83
84
|
Object.defineProperty(this, "key", { value });
|
|
84
85
|
}
|
|
85
86
|
});
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
...desc,
|
|
102
|
-
configurable: false
|
|
103
|
-
});
|
|
87
|
+
Component.on({ setup(self) {
|
|
88
|
+
if (Object.getOwnPropertyDescriptor(self, "key")?.configurable) Object.defineProperty(self, "key", {
|
|
89
|
+
enumerable: false,
|
|
90
|
+
writable: false
|
|
91
|
+
});
|
|
92
|
+
} });
|
|
93
|
+
function provide(self) {
|
|
94
|
+
let input;
|
|
95
|
+
let target;
|
|
96
|
+
let owned;
|
|
97
|
+
let mounted = false;
|
|
98
|
+
let release;
|
|
99
|
+
function unmount() {
|
|
100
|
+
if (typeof release == "function") release();
|
|
101
|
+
release = void 0;
|
|
104
102
|
}
|
|
105
|
-
|
|
103
|
+
const inherited = self.mount;
|
|
104
|
+
Object.defineProperty(self, "mount", {
|
|
105
|
+
configurable: true,
|
|
106
|
+
value() {
|
|
107
|
+
const done = inherited?.call(self);
|
|
108
|
+
mounted = true;
|
|
109
|
+
release = owned?.mount?.();
|
|
110
|
+
return () => {
|
|
111
|
+
mounted = false;
|
|
112
|
+
unmount();
|
|
113
|
+
if (typeof done == "function") done();
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
function sync() {
|
|
118
|
+
const { for: next, is, children, fallback, catch: _catch, ...rest } = self.props;
|
|
119
|
+
if (fallback === void 0) self.fallback = false;
|
|
120
|
+
if (next !== input) {
|
|
121
|
+
input = next;
|
|
122
|
+
unmount();
|
|
123
|
+
if (State.is(next)) {
|
|
124
|
+
adopt(self, "for", target = new next(rest));
|
|
125
|
+
owned = target;
|
|
126
|
+
is?.(target);
|
|
127
|
+
if (mounted) release = owned.mount?.();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
owned = void 0;
|
|
131
|
+
adopt(self, "for", target = next instanceof State ? next : void 0);
|
|
132
|
+
}
|
|
133
|
+
target?.set(rest);
|
|
134
|
+
}
|
|
135
|
+
sync();
|
|
136
|
+
self.set("props", sync);
|
|
137
|
+
}
|
|
106
138
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* when a class authors none.
|
|
139
|
+
* Render `this` through its class's composed content render: content renders
|
|
140
|
+
* up the prototype chain, each base layer receiving the subclass's as a lazy
|
|
141
|
+
* `children` getter. Falls back to passing `children` through.
|
|
111
142
|
*/
|
|
112
|
-
function
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (desc) {
|
|
120
|
-
const layer = unbind(desc.get || desc.value);
|
|
121
|
-
render = render ? compose(layer, render) : layer;
|
|
143
|
+
function compose(props) {
|
|
144
|
+
const type = this.constructor;
|
|
145
|
+
let render = CHAIN.get(type);
|
|
146
|
+
if (!render) {
|
|
147
|
+
for (let T = type; T !== Component && T !== State; T = Object.getPrototypeOf(T)) {
|
|
148
|
+
const next = method(T);
|
|
149
|
+
if (next) render = render ? wrap(next, render) : next;
|
|
122
150
|
}
|
|
151
|
+
if (!render) render = method(Component);
|
|
152
|
+
CHAIN.set(type, render);
|
|
123
153
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
154
|
+
return render.call(this, props);
|
|
155
|
+
}
|
|
156
|
+
function method(from) {
|
|
157
|
+
const desc = Object.getOwnPropertyDescriptor(from.prototype, "render");
|
|
158
|
+
if (desc) return unbind(desc.get || desc.value);
|
|
127
159
|
}
|
|
128
|
-
|
|
129
|
-
function compose(outer, inner) {
|
|
160
|
+
function wrap(outer, inner) {
|
|
130
161
|
return function(props) {
|
|
131
162
|
const self = this;
|
|
132
163
|
return outer.call(self, {
|
|
@@ -137,7 +168,11 @@ function compose(outer, inner) {
|
|
|
137
168
|
});
|
|
138
169
|
};
|
|
139
170
|
}
|
|
171
|
+
/** Drop composed renders, so each is rebuilt from current prototypes. */
|
|
172
|
+
function rechain() {
|
|
173
|
+
CHAIN = /* @__PURE__ */ new WeakMap();
|
|
174
|
+
}
|
|
140
175
|
|
|
141
176
|
//#endregion
|
|
142
|
-
export { Component };
|
|
177
|
+
export { Component, compose, rechain };
|
|
143
178
|
//# sourceMappingURL=component.js.map
|
package/dist/component.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.js","names":[],"sources":["../src/component.ts"],"sourcesContent":["import { Context } from './context';\nimport { set } from './field/set';\nimport { State, unbind } from './state';\n\nimport type { Host } from './runtime';\n\nconst PENDING = new WeakMap<object, Component>();\n\n/** Per-class composed content render. */\nconst CHAIN = new WeakMap<Function, Function>();\n\ntype IfEquals<X, Y, A, B> =\n (<T>() => T extends X ? 1 : 2) extends\n (<T>() => T extends Y ? 1 : 2) ? A : B;\n\n/** Keys of T which are settable (excludes get-only accessors and `readonly`). */\ntype Acceptable<T> = {\n [P in keyof T]-?: IfEquals<\n { [Q in P]: T[P] },\n { -readonly [Q in P]: T[P] },\n P, never\n >;\n}[keyof T];\n\ndeclare namespace Component {\n /**\n * Host element type produced by `Component.render`. Delegates to the\n * {@link Host} manifest on the jsx-runtime entry; falls back to `any`\n * until an adapter augments it with `node`. `any` (not `unknown`) so an\n * un-annotated `render` override in a host-agnostic package still emits a\n * JSX-valid return - `any` is assignable to every host's node type, where\n * `unknown` is assignable to none.\n */\n type Node = Host extends { node: infer T } ? T : any;\n\n interface BaseProps<T extends Component> {\n /**\n * Callback for newly created instance. Only called once.\n *\n * Runs after props apply but **before** the `new()` lifecycle hook - so it\n * may configure state that `new()` then observes. To react to a fully\n * initialized instance instead, use `watch` or an effect.\n */\n is?: (instance: T) => void;\n\n /**\n * Fallback to show when suspended or in error recovery.\n * Pass `false` to opt out of the component's own suspense boundary,\n * letting suspension bubble to an ancestor.\n */\n fallback?: Component.Node;\n }\n\n type StateProps<T extends State> = {\n [K in Exclude<keyof T, keyof Component> & Acceptable<T>]?: T[K];\n };\n\n type RenderProps<T> = [T] extends [(props: infer P) => any]\n ? [keyof NonNullable<P>] extends [never]\n ? { children?: Component.Node }\n : NonNullable<P>\n : { children?: Component.Node };\n\n type Props<T extends Component> =\n & StateProps<T>\n & BaseProps<T>\n & RenderProps<T['render']>;\n}\n\nclass Component extends State {\n /**\n * All JSX attributes passed to this component.\n * Includes state-derived props, render props, and built-in props like `is` and `fallback`.\n *\n * Will incorperate extra props you declare as props parameter in `render` method.\n */\n declare readonly props: Component.Props<this>;\n\n /** Stable identity used when this instance is rendered in a collection. */\n declare readonly key: string;\n\n /**\n * Content to display while this component or its children are suspended.\n * Will cover suspense by pending properties in `this.render` as well.\n * Can be set as a class property or overridden via the `fallback` JSX prop.\n *\n * Defaults to null - nothing will be rendered.\n * Set `false` to opt out of having a boundary at all - suspension then\n * bubbles to the nearest ancestor boundary.\n */\n fallback: Component.Node = set(null);\n\n constructor(props?: any, ...rest: any[]) {\n if (props == null) props = {};\n\n const seen = {} as Record<string, undefined>;\n const copy = PENDING.get(props);\n\n if (typeof props == 'object') merge(props);\n\n function merge(props: {}) {\n for (const k in props) seen[k] = undefined;\n return { ...seen, ...props };\n }\n\n super(copy ? [] : [\n props,\n rest.filter((x) => !(x instanceof Context)),\n () => {\n props.is?.(this);\n Object.defineProperty(this, 'props', { enumerable: false });\n PENDING.delete(props);\n }\n ]);\n\n if (copy) {\n this.set(null);\n return copy;\n }\n\n PENDING.set(props, this);\n\n this.props = props;\n this.set('props', () => {\n this.set(merge(this.props));\n });\n\n Object.defineProperty(this, 'render', {\n writable: true,\n configurable: true,\n value: render(this)\n });\n }\n\n /**\n * Output for this component. Override to define custom JSX.\n *\n * Properties accessed via `this` are reactive and trigger a render when they\n * change, in addition to props. Accepts an optional parameter to receive extra\n * props from JSX, beyond those merged to state properties; declare its shape\n * via `props = {} as { ... }`. Without a parameter (the default below),\n * children pass through.\n *\n * The constructor installs a composed `render` per instance; the bootstrap\n * `type` pass seals this and any override non-configurable, keeping it the\n * content-render seam the chain reads.\n */\n render(props?: {}): Component.Node {\n const { children } = (props || this.props) as { children?: Component.Node };\n return children || null;\n }\n\n /**\n * Called when a child component throws during render.\n * While this is pending, `fallback` is displayed.\n * When resolved, the error boundary resets and `render` is called again.\n *\n * Override to handle errors - set `this.fallback` for error-specific UI,\n * await async recovery, or await user interaction before retrying. If you\n * assign a fallback within catch, it will be reverted after resolved.\n */\n catch?(error: Error): Promise<void> | void;\n}\n\n/**\n * Default `key` accessor. The get/set pair keeps it off observed state.\n */\nObject.defineProperty(Component.prototype, 'key', {\n configurable: true,\n get() {\n return String(this);\n },\n set(value) {\n Object.defineProperty(this, 'key', { value });\n }\n});\n\n/**\n * Seal each class's own `render` non-configurable at bootstrap so member\n * classification leaves it unbound - it stays the content-render seam the chain\n * reads (and which adapters like preact detect as the class-component marker).\n */\nComponent.on({\n before(self) {\n const key = Object.getOwnPropertyDescriptor(self, 'key');\n\n if (key?.configurable)\n Object.defineProperty(self, 'key', {\n enumerable: false,\n writable: false\n });\n },\n type(type) {\n const desc = Object.getOwnPropertyDescriptor(type.prototype, 'render');\n\n if (desc && typeof desc.value == 'function')\n Object.defineProperty(type.prototype, 'render', {\n ...desc,\n configurable: false\n });\n }\n});\n\n/**\n * Build (or fetch the cached) composed content render for an instance's class.\n * Walks the prototype chain for content renders strictly below Component - the\n * default render on Component.prototype is excluded and serves as the fallback\n * when a class authors none.\n */\nfunction render(target: Component) {\n const cached = CHAIN.get(target.constructor);\n\n if (cached) return cached;\n\n let render: Function | undefined;\n let proto = target;\n\n while ((proto = Object.getPrototypeOf(proto)) !== Component.prototype) {\n const desc = Object.getOwnPropertyDescriptor(proto, 'render');\n\n if (desc) {\n const layer = unbind(desc.get || desc.value);\n render = render ? compose(layer, render) : layer;\n }\n }\n\n if (!render)\n render = Component.prototype.render;\n\n CHAIN.set(target.constructor, render);\n\n return render;\n}\n\n/** Wrap an outer render so it receives `inner` as a lazy `children` getter. */\nfunction compose(outer: Function, inner: Function): Function {\n return function (this: Component, props?: {}) {\n const self = this;\n return outer.call(self, {\n ...props,\n get children() {\n return inner.call(self, props);\n }\n });\n };\n}\n\nexport { Component };\n"],"mappings":";;;;;AAMA,MAAM,0BAAU,IAAI,SAA4B;;AAGhD,MAAM,wBAAQ,IAAI,SAA6B;AA4D/C,IAAM,YAAN,cAAwB,MAAM;;;;;;;;;;CAqB5B,WAA2B,IAAI,KAAK;CAEpC,YAAY,OAAa,GAAG,MAAa;AACvC,MAAI,SAAS,KAAM,SAAQ,EAAE;EAE7B,MAAM,OAAO,EAAE;EACf,MAAM,OAAO,QAAQ,IAAI,MAAM;AAE/B,MAAI,OAAO,SAAS,SAAU,OAAM,MAAM;EAE1C,SAAS,MAAM,OAAW;AACxB,QAAK,MAAM,KAAK,MAAO,MAAK,KAAK;AACjC,UAAO;IAAE,GAAG;IAAM,GAAG;IAAO;;AAG9B,QAAM,OAAO,EAAE,GAAG;GAChB;GACA,KAAK,QAAQ,MAAM,EAAE,aAAa,SAAS;SACrC;AACJ,UAAM,KAAK,KAAK;AAChB,WAAO,eAAe,MAAM,SAAS,EAAE,YAAY,OAAO,CAAC;AAC3D,YAAQ,OAAO,MAAM;;GAExB,CAAC;AAEF,MAAI,MAAM;AACR,QAAK,IAAI,KAAK;AACd,UAAO;;AAGT,UAAQ,IAAI,OAAO,KAAK;AAExB,OAAK,QAAQ;AACb,OAAK,IAAI,eAAe;AACtB,QAAK,IAAI,MAAM,KAAK,MAAM,CAAC;IAC3B;AAEF,SAAO,eAAe,MAAM,UAAU;GACpC,UAAU;GACV,cAAc;GACd,OAAO,OAAO,KAAK;GACpB,CAAC;;;;;;;;;;;;;;;CAgBJ,OAAO,OAA4B;EACjC,MAAM,EAAE,aAAc,SAAS,KAAK;AACpC,SAAO,YAAY;;;;;;AAkBvB,OAAO,eAAe,UAAU,WAAW,OAAO;CAChD,cAAc;CACd,MAAM;AACJ,SAAO,OAAO,KAAK;;CAErB,IAAI,OAAO;AACT,SAAO,eAAe,MAAM,OAAO,EAAE,OAAO,CAAC;;CAEhD,CAAC;;;;;;AAOF,UAAU,GAAG;CACX,OAAO,MAAM;AAGX,MAFY,OAAO,yBAAyB,MAAM,MAAM,EAE/C,aACP,QAAO,eAAe,MAAM,OAAO;GACjC,YAAY;GACZ,UAAU;GACX,CAAC;;CAEN,KAAK,MAAM;EACT,MAAM,OAAO,OAAO,yBAAyB,KAAK,WAAW,SAAS;AAEtE,MAAI,QAAQ,OAAO,KAAK,SAAS,WAC/B,QAAO,eAAe,KAAK,WAAW,UAAU;GAC9C,GAAG;GACH,cAAc;GACf,CAAC;;CAEP,CAAC;;;;;;;AAQF,SAAS,OAAO,QAAmB;CACjC,MAAM,SAAS,MAAM,IAAI,OAAO,YAAY;AAE5C,KAAI,OAAQ,QAAO;CAEnB,IAAI;CACJ,IAAI,QAAQ;AAEZ,SAAQ,QAAQ,OAAO,eAAe,MAAM,MAAM,UAAU,WAAW;EACrE,MAAM,OAAO,OAAO,yBAAyB,OAAO,SAAS;AAE7D,MAAI,MAAM;GACR,MAAM,QAAQ,OAAO,KAAK,OAAO,KAAK,MAAM;AAC5C,YAAS,SAAS,QAAQ,OAAO,OAAO,GAAG;;;AAI/C,KAAI,CAAC,OACH,UAAS,UAAU,UAAU;AAE/B,OAAM,IAAI,OAAO,aAAa,OAAO;AAErC,QAAO;;;AAIT,SAAS,QAAQ,OAAiB,OAA2B;AAC3D,QAAO,SAA2B,OAAY;EAC5C,MAAM,OAAO;AACb,SAAO,MAAM,KAAK,MAAM;GACtB,GAAG;GACH,IAAI,WAAW;AACb,WAAO,MAAM,KAAK,MAAM,MAAM;;GAEjC,CAAC"}
|
|
1
|
+
{"version":3,"file":"component.js","names":[],"sources":["../src/component.ts"],"sourcesContent":["import { Context } from './context';\nimport { set } from './field/set';\nimport { State, adopt, trailing, unbind } from './state';\n\nimport type { Host } from './jsx-runtime';\n\nconst PENDING = new WeakMap<object, Component<any>>();\n\n/** Per-class composed content render. */\nlet CHAIN = new WeakMap<Function, Function>();\n\ntype IfEquals<X, Y, A, B> =\n (<T>() => T extends X ? 1 : 2) extends\n (<T>() => T extends Y ? 1 : 2) ? A : B;\n\n/** Keys of T which are settable (excludes get-only accessors and `readonly`). */\ntype Acceptable<T> = {\n [P in keyof T]-?: IfEquals<\n { [Q in P]: T[P] },\n { -readonly [Q in P]: T[P] },\n P, never\n >;\n}[keyof T];\n\ntype ForProps<T extends State> = {\n children?: Component.Node;\n fallback?: Component.Node | false;\n catch?: (error: Error, instance: Component) => Promise<void> | void;\n} & (\n | { for: State.Extends<T>; is?: (instance: T) => void }\n | { for: T | undefined; is?: never }\n) & { [K in Exclude<keyof T, keyof State> & Acceptable<T>]?: T[K] };\n\ndeclare namespace Component {\n /**\n * Host element type produced by `Component.render`. Delegates to the\n * {@link Host} manifest on the jsx-runtime entry; falls back to `any`\n * until an adapter augments it with `node`. `any` (not `unknown`) so an\n * un-annotated `render` override in a host-agnostic package still emits a\n * JSX-valid return - `any` is assignable to every host's node type, where\n * `unknown` is assignable to none.\n */\n type Node = Host extends { node: infer T } ? T : any;\n\n interface BaseProps<T extends Component<any>> {\n /**\n * Callback for newly created instance. Only called once.\n *\n * Runs after props apply but **before** the `new()` lifecycle hook - so it\n * may configure state that `new()` then observes. To react to a fully\n * initialized instance instead, use `watch` or an effect.\n */\n is?(instance: T): void;\n\n /**\n * Fallback to show when suspended or in error recovery.\n * Pass `false` to opt out of the component's own suspense boundary,\n * letting suspension bubble to an ancestor.\n */\n fallback?: Component.Node;\n\n /**\n * Called when this element's content throws, in place of the class's own\n * `catch`. A returned promise retries once settled.\n */\n catch?(error: Error, instance: T): Promise<void> | void;\n }\n\n type StateProps<T extends State> = {\n [K in Exclude<keyof T, keyof Component> & Acceptable<T>]?: T[K];\n };\n\n type RenderProps<T> = [T] extends [(props: infer P) => any]\n ? [keyof NonNullable<P>] extends [never]\n ? { children?: Component.Node }\n : NonNullable<P>\n : { children?: Component.Node };\n\n type Props<T extends Component<any>> =\n & StateProps<T>\n & BaseProps<T>\n & RenderProps<T['render']>;\n}\n\nclass Component<P = unknown> extends State {\n /**\n * All JSX attributes passed to this component.\n * Includes state-derived props, render props, and built-in props like `is` and `fallback`.\n *\n * Will incorperate extra props you declare as props parameter in `render` method.\n */\n declare readonly props: [P] extends [State] ? ForProps<P> : Component.Props<this>;\n\n /** Stable identity used when this instance is rendered in a collection. */\n declare readonly key: string;\n\n /**\n * Content to display while this component or its children are suspended.\n * Will cover suspense by pending properties in `this.render` as well.\n * Can be set as a class property or overridden via the `fallback` JSX prop.\n *\n * Defaults to null - nothing will be rendered.\n * Set `false` to opt out of having a boundary at all - suspension then\n * bubbles to the nearest ancestor boundary.\n */\n fallback: Component.Node = set(null);\n\n constructor(props?: any, ...rest: any[]) {\n if (props == null) props = {};\n\n const seen = {} as Record<string, undefined>;\n const twin = PENDING.get(props);\n const provider = new.target === Component && 'for' in props;\n\n if (typeof props == 'object') merge(props);\n\n function merge(props: {}) {\n for (const k in props) seen[k] = undefined;\n return { ...seen, ...props };\n }\n\n super([\n props,\n rest.filter((x) => !(x instanceof Context)),\n () => {\n const other = twin || PENDING.get(props)!;\n\n if (other !== this) {\n trailing(other).forEach((orphan) => orphan.set(null));\n other.set(null);\n }\n\n if (provider) provide(this);\n else props.is?.(this);\n\n Object.defineProperty(this, 'props', { enumerable: false });\n PENDING.delete(props);\n }\n ]);\n\n PENDING.set(props, this);\n\n this.props = props;\n this.set('props', () => {\n this.set(merge(this.props));\n });\n\n Object.defineProperty(this, 'render', {\n writable: true,\n configurable: true,\n value: compose\n });\n }\n\n /**\n * Output for this component. Override to define custom JSX.\n *\n * Properties accessed via `this` are reactive and trigger a render when they\n * change, in addition to props. Accepts an optional parameter to receive extra\n * props from JSX, beyond those merged to state properties; declare its shape\n * via `props = {} as { ... }`. Without a parameter (the default below),\n * children pass through.\n *\n * The constructor installs a composed `render` per instance.\n */\n render(props?: {}): Component.Node {\n const { children } = (props || this.props) as { children?: Component.Node };\n return children || null;\n }\n\n /**\n * Called when a child component throws during render.\n * While this is pending, `fallback` is displayed.\n * When resolved, the error boundary resets and `render` is called again.\n *\n * Override to handle errors - set `this.fallback` for error-specific UI,\n * await async recovery, or await user interaction before retrying. If you\n * assign a fallback within catch, it will be reverted after resolved.\n */\n catch?(error: Error, instance: this): Promise<void> | void;\n}\n\n/**\n * Default `key` accessor. The get/set pair keeps it off observed state.\n */\nObject.defineProperty(Component.prototype, 'key', {\n configurable: true,\n get() {\n return String(this);\n },\n set(value) {\n Object.defineProperty(this, 'key', { value });\n }\n});\n\nComponent.on({\n setup(self) {\n const key = Object.getOwnPropertyDescriptor(self, 'key');\n\n if (key?.configurable)\n Object.defineProperty(self, 'key', {\n enumerable: false,\n writable: false\n });\n }\n});\n\nfunction provide(self: Component<any>) {\n let input: unknown;\n let target: State | undefined;\n let owned: { mount?(): unknown } | undefined;\n let mounted = false;\n let release: unknown;\n\n function unmount() {\n if (typeof release == 'function') release();\n release = undefined;\n }\n\n const inherited = (self as { mount?(): unknown }).mount;\n\n Object.defineProperty(self, 'mount', {\n configurable: true,\n value() {\n const done = inherited?.call(self);\n\n mounted = true;\n release = owned?.mount?.();\n\n return () => {\n mounted = false;\n unmount();\n if (typeof done == 'function') done();\n };\n }\n });\n\n function sync() {\n const { for: next, is, children, fallback, catch: _catch, ...rest } = self.props as Record<string, any>;\n\n if (fallback === undefined) self.fallback = false;\n\n if (next !== input) {\n input = next;\n unmount();\n\n if (State.is(next)) {\n adopt(self, 'for', (target = new (next as State.Type)(rest)));\n owned = target as typeof owned;\n is?.(target);\n if (mounted) release = owned!.mount?.();\n return;\n }\n\n owned = undefined;\n adopt(self, 'for', (target = next instanceof State ? next : undefined));\n }\n\n target?.set(rest);\n }\n\n sync();\n self.set('props', sync);\n}\n\n/**\n * Render `this` through its class's composed content render: content renders\n * up the prototype chain, each base layer receiving the subclass's as a lazy\n * `children` getter. Falls back to passing `children` through.\n */\nfunction compose(this: State, props?: {}) {\n const type = this.constructor;\n let render = CHAIN.get(type);\n\n if (!render) {\n for (let T = type; T !== Component && T !== State; T = Object.getPrototypeOf(T)) {\n const next = method(T);\n\n if (next) render = render ? wrap(next, render) : next;\n }\n\n if (!render) render = method(Component)!;\n\n CHAIN.set(type, render);\n }\n\n return render.call(this, props);\n}\n\nfunction method(from: Function) {\n const desc = Object.getOwnPropertyDescriptor(from.prototype, 'render');\n if (desc) return unbind(desc.get || desc.value) as Function;\n}\n\nfunction wrap(outer: Function, inner: Function): Function {\n return function (this: State, props?: {}) {\n const self = this;\n return outer.call(self, {\n ...props,\n get children() {\n return inner.call(self, props);\n }\n });\n };\n}\n\n/** Drop composed renders, so each is rebuilt from current prototypes. */\nfunction rechain() {\n CHAIN = new WeakMap();\n}\n\nexport { Component, compose, rechain };\n"],"mappings":"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"}
|
package/dist/context.d.ts
CHANGED
|
@@ -59,6 +59,8 @@ declare class Context {
|
|
|
59
59
|
* it instead - every context adding `state` later adds its held children too.
|
|
60
60
|
*/
|
|
61
61
|
declare function join(state: State, value: State): () => void;
|
|
62
|
+
/** The State a host mounted `state` under - the one its nearest enclosing context was set up for. */
|
|
63
|
+
declare function host(state: State): State | undefined;
|
|
62
64
|
//#endregion
|
|
63
|
-
export { Context, join };
|
|
65
|
+
export { Context, host, join };
|
|
64
66
|
//# sourceMappingURL=context.d.ts.map
|
package/dist/context.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { State, uid } from "./state.js";
|
|
2
1
|
import { event, listener } from "./observable.js";
|
|
2
|
+
import { State, uid } from "./state.js";
|
|
3
3
|
|
|
4
4
|
//#region src/context.ts
|
|
5
5
|
const LOOKUP = /* @__PURE__ */ new WeakMap();
|
|
6
6
|
const HELD = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
const HOST = /* @__PURE__ */ new WeakMap();
|
|
7
8
|
let ROOT;
|
|
8
9
|
var Context = class Context {
|
|
9
10
|
static get root() {
|
|
@@ -91,6 +92,7 @@ var Context = class Context {
|
|
|
91
92
|
set(inputs, forEach) {
|
|
92
93
|
const init = /* @__PURE__ */ new Set();
|
|
93
94
|
const { cleanup } = this;
|
|
95
|
+
const single = typeof inputs == "function" || inputs instanceof State;
|
|
94
96
|
if (typeof inputs == "function" || inputs instanceof State) inputs = { [0]: inputs };
|
|
95
97
|
for (const K of Object.keys({
|
|
96
98
|
...this.inputs,
|
|
@@ -111,6 +113,7 @@ var Context = class Context {
|
|
|
111
113
|
}
|
|
112
114
|
const state = owned ? new V() : V.is;
|
|
113
115
|
const remove = this.add(state, true);
|
|
116
|
+
if (single && this !== ROOT) HOST.set(this, state);
|
|
114
117
|
init.add(() => {
|
|
115
118
|
event(state);
|
|
116
119
|
const dispose = forEach?.(state, owned);
|
|
@@ -132,12 +135,7 @@ var Context = class Context {
|
|
|
132
135
|
function conflict(T) {
|
|
133
136
|
const entries = provide.get(T);
|
|
134
137
|
if (entries) {
|
|
135
|
-
for (const entry of entries) if (!entry[1] && entry[0] !== I)
|
|
136
|
-
const type = I.constructor;
|
|
137
|
-
const g = type.global;
|
|
138
|
-
if (entry[0].constructor === type && (typeof g == "function" ? g(I) : g)) throw new Error(`Cannot register ${I} as a global - ${entry[0]} already exists in root. Destroy the existing instance first, or provide additional ones via explicit context.`);
|
|
139
|
-
return entries.delete(entry);
|
|
140
|
-
}
|
|
138
|
+
for (const entry of entries) if (!entry[1] && entry[0] !== I) return entries.delete(entry);
|
|
141
139
|
}
|
|
142
140
|
}
|
|
143
141
|
for (let T = I.constructor; T !== State; T = Object.getPrototypeOf(T)) if (explicit || !root || !conflict(T)) TT.push(T);
|
|
@@ -171,7 +169,13 @@ var Context = class Context {
|
|
|
171
169
|
onDone.forEach((r) => r());
|
|
172
170
|
onDone.clear();
|
|
173
171
|
}
|
|
174
|
-
const held = Array.from(HELD.get(I) || [], (child) =>
|
|
172
|
+
const held = Array.from(HELD.get(I) || [], ([child, added]) => {
|
|
173
|
+
const done = this.add(child);
|
|
174
|
+
added.add(done);
|
|
175
|
+
return () => {
|
|
176
|
+
if (added.delete(done)) done();
|
|
177
|
+
};
|
|
178
|
+
});
|
|
175
179
|
function remove() {
|
|
176
180
|
cleanup.delete(remove);
|
|
177
181
|
held.forEach((done) => done());
|
|
@@ -216,11 +220,25 @@ Object.defineProperty(Context.prototype, "toString", { value() {
|
|
|
216
220
|
function join(state, value) {
|
|
217
221
|
const ctx = LOOKUP.get(state.is);
|
|
218
222
|
if (ctx) return ctx.add(value);
|
|
219
|
-
const held = HELD.get(state) || /* @__PURE__ */ new
|
|
220
|
-
|
|
221
|
-
|
|
223
|
+
const held = HELD.get(state) || /* @__PURE__ */ new Map();
|
|
224
|
+
const added = /* @__PURE__ */ new Set();
|
|
225
|
+
HELD.set(state, held.set(value, added));
|
|
226
|
+
return () => {
|
|
227
|
+
held.delete(value);
|
|
228
|
+
added.forEach((done) => {
|
|
229
|
+
added.delete(done);
|
|
230
|
+
done();
|
|
231
|
+
});
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/** The State a host mounted `state` under - the one its nearest enclosing context was set up for. */
|
|
235
|
+
function host(state) {
|
|
236
|
+
for (let ctx = LOOKUP.get(state); ctx; ctx = ctx.parent) {
|
|
237
|
+
const found = HOST.get(ctx);
|
|
238
|
+
if (found && found !== state) return found;
|
|
239
|
+
}
|
|
222
240
|
}
|
|
223
241
|
|
|
224
242
|
//#endregion
|
|
225
|
-
export { Context, join };
|
|
243
|
+
export { Context, host, join };
|
|
226
244
|
//# sourceMappingURL=context.js.map
|
package/dist/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":[],"sources":["../src/context.ts"],"sourcesContent":["import { listener } from \"./observable\";\nimport { event, State, uid } from \"./state\";\n\nconst LOOKUP = new WeakMap<State, Context>();\nconst HELD = new WeakMap<State, Set<State>>();\nlet ROOT: Context;\n\ntype Accept<T extends State = State> =\n | T\n | State.Type<T>\n | Record<string | number, T | State.Type<T>>;\n\ntype Expect<T extends State = State> = (\n state: T,\n downstream: boolean,\n) => (() => void) | void;\n\ndeclare namespace Context {\n export { Accept, Expect };\n}\n\nclass Context {\n static get root(): Context {\n return ROOT ??= new Context();\n }\n\n /** Get the context for a State. Adapters may override to provide framework context. */\n static get(state?: State): Context {\n return state && LOOKUP.get(state.is) || Context.root;\n }\n\n public id = uid();\n public parent?: Context;\n public scope = new Set<Context>();\n public consume = new Map<\n State.Extends,\n Set<[Expect, boolean | undefined]> | null\n >();\n public provide = new Map<State.Extends, Set<[State, boolean]> | null>();\n\n protected inputs: Record<string | number, State | State.Extends> = {};\n\n private cleanup = new Map<string | number | Function, () => void>();\n\n constructor(arg?: Context | Accept) {\n if (arg instanceof Context) {\n this.parent = arg;\n arg.scope.add(this);\n } else if (arg) {\n this.set(arg);\n }\n }\n\n private traverse(accept: (ctx: Context) => boolean | void) {\n const queue = [...this.scope];\n for (const ctx of queue)\n if (accept(ctx) !== false) for (const c of ctx.scope) queue.push(c);\n }\n\n private register<T extends State>(\n type: State.Extends<T>,\n value: [Expect<T>, boolean | undefined],\n asConsumer: true,\n ): () => void;\n\n private register<T extends State>(\n type: State.Extends<T>,\n value: [T, boolean],\n asConsumer?: false,\n ): () => void;\n\n private register(type: State.Extends, value: any, asConsumer?: boolean) {\n const mode = asConsumer ? \"consume\" : \"provide\";\n let set = this[mode].get(type) as Set<any>;\n if (!set) this[mode].set(type, (set = new Set()));\n set.add(value);\n\n for (let ctx = this.parent; ctx; ctx = ctx.parent) {\n if (ctx[mode].has(type)) break;\n ctx[mode].set(type, null);\n }\n\n return () => set.delete(value);\n }\n\n /** Find specified type upstream. Throws if not found. */\n public get<T extends State>(Type: State.Extends<T>, required?: true): T;\n\n /** Find specified type upstream. Returns undefined if not found. */\n public get<T extends State>(\n Type: State.Extends<T>,\n required?: boolean,\n ): T | undefined;\n\n /** Subscribe to a type becoming available in context. */\n public get<T extends State>(\n Type: State.Extends<T>,\n callback: Expect<T>,\n downstream?: boolean,\n ): () => void;\n\n /** Lookup excluding a specific instance (used by State.prototype.get for upstream-of-self semantics). */\n public get<T extends State>(\n Type: State.Extends<T>,\n arg: boolean | Expect<T> | undefined,\n downstream: boolean | undefined,\n skip: State,\n ): T | undefined | (() => void);\n\n public get<T extends State>(\n Type: State.Extends<T>,\n arg?: boolean | Expect<T>,\n downstream?: boolean,\n skip?: State,\n ) {\n let found: T | null | undefined;\n let priority = false;\n\n for (let ctx: Context | undefined = this; ctx; ctx = ctx.parent) {\n const entries = ctx.provide.get(Type);\n if (!entries) continue;\n\n for (const [state, explicit] of entries) {\n if (state === skip || found === state) continue;\n if (!found || (explicit && !priority)) {\n found = state as T;\n priority = explicit;\n continue;\n }\n if (!priority) {\n found = null;\n break;\n }\n if (explicit)\n throw new Error(\n `Did find ${Type} in context, but multiple were defined.`,\n );\n }\n if (found !== undefined) break;\n }\n\n if (typeof arg === \"function\") {\n if (found && !downstream) arg(found, false);\n\n if (downstream !== false) {\n this.traverse((ctx) => {\n const has = ctx.provide.get(Type);\n if (has) for (const x of has) if (x[0] !== skip) arg(x[0] as T, true);\n return has !== undefined;\n });\n }\n\n return this.register(Type, [arg as Expect, downstream], true);\n }\n\n if (found) return found;\n if (found === null) return null;\n if (arg !== false) throw new Error(`Could not find ${Type} in context.`);\n }\n\n /**\n * Register one or more States to this context.\n *\n * Context will add or remove States as needed to keep with provided input.\n *\n * @param inputs State, State class, or map of States / State classes to register.\n * @param forEach Optional callback to run for each State registered. Receives\n * whether this context created the State - and so will destroy it - as\n * opposed to it having been provided already active. May return a callback\n * to run before that entry is dropped.\n */\n public set<T extends State>(\n inputs: Accept<T>,\n forEach?: (state: T, owned: boolean) => (() => void) | void,\n ) {\n const init = new Set<() => void>();\n const { cleanup } = this;\n\n if (typeof inputs == \"function\" || inputs instanceof State)\n inputs = { [0]: inputs };\n\n for (const K of Object.keys({ ...this.inputs, ...inputs })) {\n const V = inputs[K];\n const E = this.inputs[K];\n\n if (E === V) continue;\n\n if (E) {\n cleanup.get(K)?.();\n cleanup.delete(K);\n }\n\n if (!V) continue;\n\n // a class is constructed here - and so destroyed here; an instance is\n // adopted as-is and outlives this context\n const owned = State.is(V);\n\n if (!(owned || V instanceof State)) {\n const as = K == \"0\" || K == String(V) ? V : `${V} (as '${K}')`;\n throw new Error(\n `Context can only include an instance or class of State but got ${as}.`,\n );\n }\n\n const state = owned ? new (V as State.Type)() : V.is;\n const remove = this.add(state, true);\n\n init.add(() => {\n event(state);\n const dispose = forEach?.(state as T, owned);\n cleanup.set(K, () => {\n if (typeof dispose == \"function\") dispose();\n remove();\n if (owned) event(state, null);\n });\n });\n }\n\n for (const i of init) i();\n\n this.inputs = inputs;\n\n return this;\n }\n\n add(I: State, explicit = false) {\n const { cleanup, provide } = this;\n const root = this === Context.root;\n const TT: State.Extends[] = [];\n\n function conflict(T: State.Extends) {\n const entries = provide.get(T);\n if (entries)\n for (const entry of entries)\n if (!entry[1] && entry[0] !== I) {\n const type = I.constructor as State.Extends;\n const g = type.global;\n\n if (entry[0].constructor === type && (typeof g == 'function' ? g(I) : g))\n throw new Error(\n `Cannot register ${I} as a global - ${entry[0]} already exists in root. Destroy the existing instance first, or provide additional ones via explicit context.`\n );\n\n return entries.delete(entry);\n }\n }\n\n for (\n let T = I.constructor as State.Extends;\n T !== State;\n T = Object.getPrototypeOf(T)\n )\n if (explicit || !root || !conflict(T)) TT.push(T);\n\n const expects = new Map<Expect, () => void>();\n const onDone = new Set<() => void>();\n\n for (const T of TT) onDone.add(this.register(T, [I, explicit]));\n\n function queue(ctx: Context, downstream: boolean, same?: boolean) {\n let found = false;\n for (const T of TT) {\n const list = ctx.consume.get(T);\n if (list !== undefined) found = true;\n if (list)\n for (const [cb, filter] of list)\n if (same || filter == null || filter === downstream)\n expects.set(cb, () => {\n const r = cb(I, filter ?? downstream);\n if (r) onDone.add(r);\n });\n }\n return found;\n }\n\n queue(this, true, true);\n for (let ctx = this.parent; ctx; ctx = ctx.parent) queue(ctx, true);\n this.traverse((ctx) => queue(ctx, false));\n\n if (!LOOKUP.has(I)) LOOKUP.set(I, this);\n\n listener(I, () => {\n expects.forEach((f) => f());\n expects.clear();\n return null;\n });\n\n function flush() {\n onDone.forEach((r) => r());\n onDone.clear();\n }\n\n const held = Array.from(HELD.get(I) || [], (child) => this.add(child));\n\n function remove() {\n cleanup.delete(remove);\n held.forEach((done) => done());\n flush();\n }\n\n cleanup.set(remove, remove);\n\n return remove;\n }\n\n /**\n * Create a child context, optionally registering one or more States to it.\n *\n * @param inputs State, State class, or map of States / State classes to register.\n */\n public push(inputs?: Accept) {\n const next = new Context(this);\n if (inputs) next.set(inputs);\n return next;\n }\n\n /**\n * Remove all States from this context.\n *\n * Will also run any cleanup callbacks registered when States were added.\n */\n public pop() {\n this.inputs = {};\n this.scope.forEach((x) => x.pop());\n this.scope.clear();\n this.cleanup.forEach((cb) => cb());\n this.cleanup.clear();\n this.consume.clear();\n this.provide.clear();\n if (this.parent) {\n this.parent.scope.delete(this);\n }\n }\n}\n\nObject.defineProperty(Context.prototype, \"toString\", {\n value() {\n return `Context-${this.id}`;\n },\n});\n\n/**\n * Add `value` to the context `state` was added to. A context-less `state` holds\n * it instead - every context adding `state` later adds its held children too.\n */\nfunction join(state: State, value: State): () => void {\n const ctx = LOOKUP.get(state.is);\n\n if (ctx) return ctx.add(value);\n\n const held = HELD.get(state) || new Set();\n HELD.set(state, held.add(value));\n\n return () => held.delete(value);\n}\n\nexport { Context, join };\n"],"mappings":";;;;AAGA,MAAM,yBAAS,IAAI,SAAyB;AAC5C,MAAM,uBAAO,IAAI,SAA4B;AAC7C,IAAI;AAgBJ,IAAM,UAAN,MAAM,QAAQ;CACZ,WAAW,OAAgB;AACzB,SAAO,SAAS,IAAI,SAAS;;;CAI/B,OAAO,IAAI,OAAwB;AACjC,SAAO,SAAS,OAAO,IAAI,MAAM,GAAG,IAAI,QAAQ;;CAGlD,AAAO,KAAK,KAAK;CACjB,AAAO;CACP,AAAO,wBAAQ,IAAI,KAAc;CACjC,AAAO,0BAAU,IAAI,KAGlB;CACH,AAAO,0BAAU,IAAI,KAAkD;CAEvE,AAAU,SAAyD,EAAE;CAErE,AAAQ,0BAAU,IAAI,KAA6C;CAEnE,YAAY,KAAwB;AAClC,MAAI,eAAe,SAAS;AAC1B,QAAK,SAAS;AACd,OAAI,MAAM,IAAI,KAAK;aACV,IACT,MAAK,IAAI,IAAI;;CAIjB,AAAQ,SAAS,QAA0C;EACzD,MAAM,QAAQ,CAAC,GAAG,KAAK,MAAM;AAC7B,OAAK,MAAM,OAAO,MAChB,KAAI,OAAO,IAAI,KAAK,MAAO,MAAK,MAAM,KAAK,IAAI,MAAO,OAAM,KAAK,EAAE;;CAevE,AAAQ,SAAS,MAAqB,OAAY,YAAsB;EACtE,MAAM,OAAO,aAAa,YAAY;EACtC,IAAI,MAAM,KAAK,MAAM,IAAI,KAAK;AAC9B,MAAI,CAAC,IAAK,MAAK,MAAM,IAAI,MAAO,sBAAM,IAAI,KAAK,CAAE;AACjD,MAAI,IAAI,MAAM;AAEd,OAAK,IAAI,MAAM,KAAK,QAAQ,KAAK,MAAM,IAAI,QAAQ;AACjD,OAAI,IAAI,MAAM,IAAI,KAAK,CAAE;AACzB,OAAI,MAAM,IAAI,MAAM,KAAK;;AAG3B,eAAa,IAAI,OAAO,MAAM;;CA2BhC,AAAO,IACL,MACA,KACA,YACA,MACA;EACA,IAAI;EACJ,IAAI,WAAW;AAEf,OAAK,IAAI,MAA2B,MAAM,KAAK,MAAM,IAAI,QAAQ;GAC/D,MAAM,UAAU,IAAI,QAAQ,IAAI,KAAK;AACrC,OAAI,CAAC,QAAS;AAEd,QAAK,MAAM,CAAC,OAAO,aAAa,SAAS;AACvC,QAAI,UAAU,QAAQ,UAAU,MAAO;AACvC,QAAI,CAAC,SAAU,YAAY,CAAC,UAAW;AACrC,aAAQ;AACR,gBAAW;AACX;;AAEF,QAAI,CAAC,UAAU;AACb,aAAQ;AACR;;AAEF,QAAI,SACF,OAAM,IAAI,MACR,YAAY,KAAK,yCAClB;;AAEL,OAAI,UAAU,OAAW;;AAG3B,MAAI,OAAO,QAAQ,YAAY;AAC7B,OAAI,SAAS,CAAC,WAAY,KAAI,OAAO,MAAM;AAE3C,OAAI,eAAe,MACjB,MAAK,UAAU,QAAQ;IACrB,MAAM,MAAM,IAAI,QAAQ,IAAI,KAAK;AACjC,QAAI,KAAK;UAAK,MAAM,KAAK,IAAK,KAAI,EAAE,OAAO,KAAM,KAAI,EAAE,IAAS,KAAK;;AACrE,WAAO,QAAQ;KACf;AAGJ,UAAO,KAAK,SAAS,MAAM,CAAC,KAAe,WAAW,EAAE,KAAK;;AAG/D,MAAI,MAAO,QAAO;AAClB,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,QAAQ,MAAO,OAAM,IAAI,MAAM,kBAAkB,KAAK,cAAc;;;;;;;;;;;;;CAc1E,AAAO,IACL,QACA,SACA;EACA,MAAM,uBAAO,IAAI,KAAiB;EAClC,MAAM,EAAE,YAAY;AAEpB,MAAI,OAAO,UAAU,cAAc,kBAAkB,MACnD,UAAS,GAAG,IAAI,QAAQ;AAE1B,OAAK,MAAM,KAAK,OAAO,KAAK;GAAE,GAAG,KAAK;GAAQ,GAAG;GAAQ,CAAC,EAAE;GAC1D,MAAM,IAAI,OAAO;GACjB,MAAM,IAAI,KAAK,OAAO;AAEtB,OAAI,MAAM,EAAG;AAEb,OAAI,GAAG;AACL,YAAQ,IAAI,EAAE,IAAI;AAClB,YAAQ,OAAO,EAAE;;AAGnB,OAAI,CAAC,EAAG;GAIR,MAAM,QAAQ,MAAM,GAAG,EAAE;AAEzB,OAAI,EAAE,SAAS,aAAa,QAAQ;IAClC,MAAM,KAAK,KAAK,OAAO,KAAK,OAAO,EAAE,GAAG,IAAI,GAAG,EAAE,QAAQ,EAAE;AAC3D,UAAM,IAAI,MACR,kEAAkE,GAAG,GACtE;;GAGH,MAAM,QAAQ,QAAQ,IAAK,GAAkB,GAAG,EAAE;GAClD,MAAM,SAAS,KAAK,IAAI,OAAO,KAAK;AAEpC,QAAK,UAAU;AACb,UAAM,MAAM;IACZ,MAAM,UAAU,UAAU,OAAY,MAAM;AAC5C,YAAQ,IAAI,SAAS;AACnB,SAAI,OAAO,WAAW,WAAY,UAAS;AAC3C,aAAQ;AACR,SAAI,MAAO,OAAM,OAAO,KAAK;MAC7B;KACF;;AAGJ,OAAK,MAAM,KAAK,KAAM,IAAG;AAEzB,OAAK,SAAS;AAEd,SAAO;;CAGT,IAAI,GAAU,WAAW,OAAO;EAC9B,MAAM,EAAE,SAAS,YAAY;EAC7B,MAAM,OAAO,SAAS,QAAQ;EAC9B,MAAM,KAAsB,EAAE;EAE9B,SAAS,SAAS,GAAkB;GAClC,MAAM,UAAU,QAAQ,IAAI,EAAE;AAC9B,OAAI,SACF;SAAK,MAAM,SAAS,QAClB,KAAI,CAAC,MAAM,MAAM,MAAM,OAAO,GAAG;KAC/B,MAAM,OAAO,EAAE;KACf,MAAM,IAAI,KAAK;AAEf,SAAI,MAAM,GAAG,gBAAgB,SAAS,OAAO,KAAK,aAAa,EAAE,EAAE,GAAG,GACpE,OAAM,IAAI,MACR,mBAAmB,EAAE,iBAAiB,MAAM,GAAG,gHAChD;AAEH,YAAO,QAAQ,OAAO,MAAM;;;;AAIpC,OACE,IAAI,IAAI,EAAE,aACV,MAAM,OACN,IAAI,OAAO,eAAe,EAAE,CAE5B,KAAI,YAAY,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAE,IAAG,KAAK,EAAE;EAEnD,MAAM,0BAAU,IAAI,KAAyB;EAC7C,MAAM,yBAAS,IAAI,KAAiB;AAEpC,OAAK,MAAM,KAAK,GAAI,QAAO,IAAI,KAAK,SAAS,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC;EAE/D,SAAS,MAAM,KAAc,YAAqB,MAAgB;GAChE,IAAI,QAAQ;AACZ,QAAK,MAAM,KAAK,IAAI;IAClB,MAAM,OAAO,IAAI,QAAQ,IAAI,EAAE;AAC/B,QAAI,SAAS,OAAW,SAAQ;AAChC,QAAI,MACF;UAAK,MAAM,CAAC,IAAI,WAAW,KACzB,KAAI,QAAQ,UAAU,QAAQ,WAAW,WACvC,SAAQ,IAAI,UAAU;MACpB,MAAM,IAAI,GAAG,GAAG,UAAU,WAAW;AACrC,UAAI,EAAG,QAAO,IAAI,EAAE;OACpB;;;AAEV,UAAO;;AAGT,QAAM,MAAM,MAAM,KAAK;AACvB,OAAK,IAAI,MAAM,KAAK,QAAQ,KAAK,MAAM,IAAI,OAAQ,OAAM,KAAK,KAAK;AACnE,OAAK,UAAU,QAAQ,MAAM,KAAK,MAAM,CAAC;AAEzC,MAAI,CAAC,OAAO,IAAI,EAAE,CAAE,QAAO,IAAI,GAAG,KAAK;AAEvC,WAAS,SAAS;AAChB,WAAQ,SAAS,MAAM,GAAG,CAAC;AAC3B,WAAQ,OAAO;AACf,UAAO;IACP;EAEF,SAAS,QAAQ;AACf,UAAO,SAAS,MAAM,GAAG,CAAC;AAC1B,UAAO,OAAO;;EAGhB,MAAM,OAAO,MAAM,KAAK,KAAK,IAAI,EAAE,IAAI,EAAE,GAAG,UAAU,KAAK,IAAI,MAAM,CAAC;EAEtE,SAAS,SAAS;AAChB,WAAQ,OAAO,OAAO;AACtB,QAAK,SAAS,SAAS,MAAM,CAAC;AAC9B,UAAO;;AAGT,UAAQ,IAAI,QAAQ,OAAO;AAE3B,SAAO;;;;;;;CAQT,AAAO,KAAK,QAAiB;EAC3B,MAAM,OAAO,IAAI,QAAQ,KAAK;AAC9B,MAAI,OAAQ,MAAK,IAAI,OAAO;AAC5B,SAAO;;;;;;;CAQT,AAAO,MAAM;AACX,OAAK,SAAS,EAAE;AAChB,OAAK,MAAM,SAAS,MAAM,EAAE,KAAK,CAAC;AAClC,OAAK,MAAM,OAAO;AAClB,OAAK,QAAQ,SAAS,OAAO,IAAI,CAAC;AAClC,OAAK,QAAQ,OAAO;AACpB,OAAK,QAAQ,OAAO;AACpB,OAAK,QAAQ,OAAO;AACpB,MAAI,KAAK,OACP,MAAK,OAAO,MAAM,OAAO,KAAK;;;AAKpC,OAAO,eAAe,QAAQ,WAAW,YAAY,EACnD,QAAQ;AACN,QAAO,WAAW,KAAK;GAE1B,CAAC;;;;;AAMF,SAAS,KAAK,OAAc,OAA0B;CACpD,MAAM,MAAM,OAAO,IAAI,MAAM,GAAG;AAEhC,KAAI,IAAK,QAAO,IAAI,IAAI,MAAM;CAE9B,MAAM,OAAO,KAAK,IAAI,MAAM,oBAAI,IAAI,KAAK;AACzC,MAAK,IAAI,OAAO,KAAK,IAAI,MAAM,CAAC;AAEhC,cAAa,KAAK,OAAO,MAAM"}
|
|
1
|
+
{"version":3,"file":"context.js","names":[],"sources":["../src/context.ts"],"sourcesContent":["import { listener } from \"./observable\";\nimport { event, State, uid } from \"./state\";\n\nconst LOOKUP = new WeakMap<State, Context>();\nconst HELD = new WeakMap<State, Map<State, Set<() => void>>>();\nconst HOST = new WeakMap<Context, State>();\nlet ROOT: Context;\n\ntype Accept<T extends State = State> =\n | T\n | State.Type<T>\n | Record<string | number, T | State.Type<T>>;\n\ntype Expect<T extends State = State> = (\n state: T,\n downstream: boolean,\n) => (() => void) | void;\n\ndeclare namespace Context {\n export { Accept, Expect };\n}\n\nclass Context {\n static get root(): Context {\n return ROOT ??= new Context();\n }\n\n /** Get the context for a State. Adapters may override to provide framework context. */\n static get(state?: State): Context {\n return state && LOOKUP.get(state.is) || Context.root;\n }\n\n public id = uid();\n public parent?: Context;\n public scope = new Set<Context>();\n public consume = new Map<\n State.Extends,\n Set<[Expect, boolean | undefined]> | null\n >();\n public provide = new Map<State.Extends, Set<[State, boolean]> | null>();\n\n protected inputs: Record<string | number, State | State.Extends> = {};\n\n private cleanup = new Map<string | number | Function, () => void>();\n\n constructor(arg?: Context | Accept) {\n if (arg instanceof Context) {\n this.parent = arg;\n arg.scope.add(this);\n } else if (arg) {\n this.set(arg);\n }\n }\n\n private traverse(accept: (ctx: Context) => boolean | void) {\n const queue = [...this.scope];\n for (const ctx of queue)\n if (accept(ctx) !== false) for (const c of ctx.scope) queue.push(c);\n }\n\n private register<T extends State>(\n type: State.Extends<T>,\n value: [Expect<T>, boolean | undefined],\n asConsumer: true,\n ): () => void;\n\n private register<T extends State>(\n type: State.Extends<T>,\n value: [T, boolean],\n asConsumer?: false,\n ): () => void;\n\n private register(type: State.Extends, value: any, asConsumer?: boolean) {\n const mode = asConsumer ? \"consume\" : \"provide\";\n let set = this[mode].get(type) as Set<any>;\n if (!set) this[mode].set(type, (set = new Set()));\n set.add(value);\n\n for (let ctx = this.parent; ctx; ctx = ctx.parent) {\n if (ctx[mode].has(type)) break;\n ctx[mode].set(type, null);\n }\n\n return () => set.delete(value);\n }\n\n /** Find specified type upstream. Throws if not found. */\n public get<T extends State>(Type: State.Extends<T>, required?: true): T;\n\n /** Find specified type upstream. Returns undefined if not found. */\n public get<T extends State>(\n Type: State.Extends<T>,\n required?: boolean,\n ): T | undefined;\n\n /** Subscribe to a type becoming available in context. */\n public get<T extends State>(\n Type: State.Extends<T>,\n callback: Expect<T>,\n downstream?: boolean,\n ): () => void;\n\n /** Lookup excluding a specific instance (used by State.prototype.get for upstream-of-self semantics). */\n public get<T extends State>(\n Type: State.Extends<T>,\n arg: boolean | Expect<T> | undefined,\n downstream: boolean | undefined,\n skip: State,\n ): T | undefined | (() => void);\n\n public get<T extends State>(\n Type: State.Extends<T>,\n arg?: boolean | Expect<T>,\n downstream?: boolean,\n skip?: State,\n ) {\n let found: T | null | undefined;\n let priority = false;\n\n for (let ctx: Context | undefined = this; ctx; ctx = ctx.parent) {\n const entries = ctx.provide.get(Type);\n if (!entries) continue;\n\n for (const [state, explicit] of entries) {\n if (state === skip || found === state) continue;\n if (!found || (explicit && !priority)) {\n found = state as T;\n priority = explicit;\n continue;\n }\n if (!priority) {\n found = null;\n break;\n }\n if (explicit)\n throw new Error(\n `Did find ${Type} in context, but multiple were defined.`,\n );\n }\n if (found !== undefined) break;\n }\n\n if (typeof arg === \"function\") {\n if (found && !downstream) arg(found, false);\n\n if (downstream !== false) {\n this.traverse((ctx) => {\n const has = ctx.provide.get(Type);\n if (has) for (const x of has) if (x[0] !== skip) arg(x[0] as T, true);\n return has !== undefined;\n });\n }\n\n return this.register(Type, [arg as Expect, downstream], true);\n }\n\n if (found) return found;\n if (found === null) return null;\n if (arg !== false) throw new Error(`Could not find ${Type} in context.`);\n }\n\n /**\n * Register one or more States to this context.\n *\n * Context will add or remove States as needed to keep with provided input.\n *\n * @param inputs State, State class, or map of States / State classes to register.\n * @param forEach Optional callback to run for each State registered. Receives\n * whether this context created the State - and so will destroy it - as\n * opposed to it having been provided already active. May return a callback\n * to run before that entry is dropped.\n */\n public set<T extends State>(\n inputs: Accept<T>,\n forEach?: (state: T, owned: boolean) => (() => void) | void,\n ) {\n const init = new Set<() => void>();\n const { cleanup } = this;\n\n const single = typeof inputs == \"function\" || inputs instanceof State;\n\n if (typeof inputs == \"function\" || inputs instanceof State)\n inputs = { [0]: inputs };\n\n for (const K of Object.keys({ ...this.inputs, ...inputs })) {\n const V = inputs[K];\n const E = this.inputs[K];\n\n if (E === V) continue;\n\n if (E) {\n cleanup.get(K)?.();\n cleanup.delete(K);\n }\n\n if (!V) continue;\n\n // a class is constructed here - and so destroyed here; an instance is\n // adopted as-is and outlives this context\n const owned = State.is(V);\n\n if (!(owned || V instanceof State)) {\n const as = K == \"0\" || K == String(V) ? V : `${V} (as '${K}')`;\n throw new Error(\n `Context can only include an instance or class of State but got ${as}.`,\n );\n }\n\n const state = owned ? new (V as State.Type)() : V.is;\n const remove = this.add(state, true);\n\n if (single && this !== ROOT) HOST.set(this, state);\n\n init.add(() => {\n event(state);\n const dispose = forEach?.(state as T, owned);\n cleanup.set(K, () => {\n if (typeof dispose == \"function\") dispose();\n remove();\n if (owned) event(state, null);\n });\n });\n }\n\n for (const i of init) i();\n\n this.inputs = inputs;\n\n return this;\n }\n\n add(I: State, explicit = false) {\n const { cleanup, provide } = this;\n const root = this === Context.root;\n const TT: State.Extends[] = [];\n\n function conflict(T: State.Extends) {\n const entries = provide.get(T);\n if (entries)\n for (const entry of entries)\n if (!entry[1] && entry[0] !== I) return entries.delete(entry);\n }\n\n for (\n let T = I.constructor as State.Extends;\n T !== State;\n T = Object.getPrototypeOf(T)\n )\n if (explicit || !root || !conflict(T)) TT.push(T);\n\n const expects = new Map<Expect, () => void>();\n const onDone = new Set<() => void>();\n\n for (const T of TT) onDone.add(this.register(T, [I, explicit]));\n\n function queue(ctx: Context, downstream: boolean, same?: boolean) {\n let found = false;\n for (const T of TT) {\n const list = ctx.consume.get(T);\n if (list !== undefined) found = true;\n if (list)\n for (const [cb, filter] of list)\n if (same || filter == null || filter === downstream)\n expects.set(cb, () => {\n const r = cb(I, filter ?? downstream);\n if (r) onDone.add(r);\n });\n }\n return found;\n }\n\n queue(this, true, true);\n for (let ctx = this.parent; ctx; ctx = ctx.parent) queue(ctx, true);\n this.traverse((ctx) => queue(ctx, false));\n\n if (!LOOKUP.has(I)) LOOKUP.set(I, this);\n\n listener(I, () => {\n expects.forEach((f) => f());\n expects.clear();\n return null;\n });\n\n function flush() {\n onDone.forEach((r) => r());\n onDone.clear();\n }\n\n const held = Array.from(HELD.get(I) || [], ([child, added]) => {\n const done = this.add(child);\n added.add(done);\n return () => {\n if (added.delete(done)) done();\n };\n });\n\n function remove() {\n cleanup.delete(remove);\n held.forEach((done) => done());\n flush();\n }\n\n cleanup.set(remove, remove);\n\n return remove;\n }\n\n /**\n * Create a child context, optionally registering one or more States to it.\n *\n * @param inputs State, State class, or map of States / State classes to register.\n */\n public push(inputs?: Accept) {\n const next = new Context(this);\n if (inputs) next.set(inputs);\n return next;\n }\n\n /**\n * Remove all States from this context.\n *\n * Will also run any cleanup callbacks registered when States were added.\n */\n public pop() {\n this.inputs = {};\n this.scope.forEach((x) => x.pop());\n this.scope.clear();\n this.cleanup.forEach((cb) => cb());\n this.cleanup.clear();\n this.consume.clear();\n this.provide.clear();\n if (this.parent) {\n this.parent.scope.delete(this);\n }\n }\n}\n\nObject.defineProperty(Context.prototype, \"toString\", {\n value() {\n return `Context-${this.id}`;\n },\n});\n\n/**\n * Add `value` to the context `state` was added to. A context-less `state` holds\n * it instead - every context adding `state` later adds its held children too.\n */\nfunction join(state: State, value: State): () => void {\n const ctx = LOOKUP.get(state.is);\n\n if (ctx) return ctx.add(value);\n\n const held = HELD.get(state) || new Map();\n const added = new Set<() => void>();\n\n HELD.set(state, held.set(value, added));\n\n return () => {\n held.delete(value);\n added.forEach((done) => {\n added.delete(done);\n done();\n });\n };\n}\n\n/** The State a host mounted `state` under - the one its nearest enclosing context was set up for. */\nfunction host(state: State) {\n for (let ctx = LOOKUP.get(state); ctx; ctx = ctx.parent) {\n const found = HOST.get(ctx);\n if (found && found !== state) return found;\n }\n}\n\nexport { Context, host, join };\n"],"mappings":"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"}
|
package/dist/dispatch.d.ts
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
type Handler = () => void;
|
|
3
3
|
type Transition = (work: Handler) => void;
|
|
4
4
|
type Hold = (retry?: Handler | false) => void;
|
|
5
|
+
/** Where errors caught in a flush go - `as` takes a report, `error` throws what is left uncaught. */
|
|
6
|
+
declare const REPORT: {
|
|
7
|
+
as(kind: string, owner?: object, cause?: unknown, key?: string): void;
|
|
8
|
+
error(err: unknown): void;
|
|
9
|
+
};
|
|
5
10
|
/**
|
|
6
11
|
* Hold the work being replayed until the returned callback runs - a subscriber
|
|
7
12
|
* which has not yet absorbed the update takes one, and settlement waits on it
|
|
@@ -33,5 +38,5 @@ declare function pending(work: Handler): Promise<void>;
|
|
|
33
38
|
*/
|
|
34
39
|
declare function pending(): (() => void) | undefined;
|
|
35
40
|
//#endregion
|
|
36
|
-
export { enqueue, hold, pending };
|
|
41
|
+
export { REPORT, enqueue, hold, pending };
|
|
37
42
|
//# sourceMappingURL=dispatch.d.ts.map
|
package/dist/dispatch.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import { fault } from "./state.js";
|
|
2
|
-
|
|
3
1
|
//#region src/dispatch.ts
|
|
4
2
|
const DISPATCH = /* @__PURE__ */ new Map();
|
|
3
|
+
/** Where errors caught in a flush go - `as` takes a report, `error` throws what is left uncaught. */
|
|
4
|
+
const REPORT = {
|
|
5
|
+
as(kind, owner, cause, key) {
|
|
6
|
+
REPORT.error(cause);
|
|
7
|
+
},
|
|
8
|
+
error(err) {
|
|
9
|
+
queueMicrotask(() => {
|
|
10
|
+
throw err;
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
};
|
|
5
14
|
let current;
|
|
6
15
|
let replaying;
|
|
7
16
|
/**
|
|
@@ -59,10 +68,9 @@ function flush() {
|
|
|
59
68
|
if (transition) transition(handler);
|
|
60
69
|
else handler();
|
|
61
70
|
} catch (err) {
|
|
62
|
-
|
|
63
|
-
} finally {
|
|
64
|
-
current = replaying = void 0;
|
|
71
|
+
REPORT.as("effect", scheduled.owner, err);
|
|
65
72
|
}
|
|
73
|
+
current = replaying = void 0;
|
|
66
74
|
if (!scheduled.holds) drop(scheduled);
|
|
67
75
|
}
|
|
68
76
|
}
|
|
@@ -102,5 +110,5 @@ function pending(work) {
|
|
|
102
110
|
}
|
|
103
111
|
|
|
104
112
|
//#endregion
|
|
105
|
-
export { enqueue, hold, pending };
|
|
113
|
+
export { REPORT, enqueue, hold, pending };
|
|
106
114
|
//# sourceMappingURL=dispatch.js.map
|
package/dist/dispatch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.js","names":[],"sources":["../src/dispatch.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"dispatch.js","names":[],"sources":["../src/dispatch.ts"],"sourcesContent":["type Handler = () => void;\ntype Transition = (work: Handler) => void;\n\ninterface Pending {\n count: number;\n done(): void;\n}\n\ntype Hold = (retry?: Handler | false) => void;\n\ninterface Scheduled {\n /** How this subscriber defers, if it can - supplied where it subscribed. */\n transition?: Transition;\n awaiting?: Set<Pending>;\n holds?: number;\n owner?: object;\n}\n\nconst DISPATCH = new Map<Handler, Scheduled>();\n\n/** Where errors caught in a flush go - `as` takes a report, `error` throws what is left uncaught. */\nconst REPORT = {\n as(kind: string, owner?: object, cause?: unknown, key?: string): void {\n REPORT.error(cause);\n },\n error(err: unknown): void {\n queueMicrotask(() => {\n throw err;\n });\n }\n};\nlet current: Iterable<Pending> | undefined;\nlet replaying: Scheduled | undefined;\n\n/**\n * Await this handler's replay for the work being scheduled. Overlapping calls\n * may each be waiting on the same handler, which replays once.\n */\nfunction claim(scheduled: Scheduled) {\n if (!current) return;\n\n const awaiting = scheduled.awaiting || (scheduled.awaiting = new Set());\n\n for (const record of current)\n if (!awaiting.has(record)) {\n awaiting.add(record);\n record.count++;\n }\n}\n\nfunction drop(scheduled: Scheduled) {\n const { awaiting } = scheduled;\n\n if (!awaiting) return;\n\n scheduled.awaiting = undefined;\n\n for (const record of awaiting)\n if (!--record.count) record.done();\n}\n\n/**\n * Hold the work being replayed until the returned callback runs - a subscriber\n * which has not yet absorbed the update takes one, and settlement waits on it\n * rather than on the replay. Returns nothing unless the replay carries pending\n * work, so an unrelated subscriber pays for none of it.\n */\nfunction hold(detached?: true, transition?: Transition) {\n const scheduled = replaying || {};\n\n if (detached) {\n scheduled.transition = transition;\n claim(scheduled);\n }\n else if (!scheduled.awaiting) return;\n\n let released = false;\n\n scheduled.holds = (scheduled.holds || 0) + 1;\n\n const release: Hold = (retry) => {\n if (released) return;\n if (retry === false) return claim(scheduled);\n\n if (retry) {\n current = scheduled.awaiting;\n enqueue(retry, scheduled.transition);\n current = undefined;\n }\n\n released = true;\n if (!--scheduled.holds!) drop(scheduled);\n };\n\n return release;\n}\n\nfunction flush() {\n for (const [handler, scheduled] of DISPATCH) {\n DISPATCH.delete(handler);\n\n const { awaiting, transition } = scheduled;\n\n current = awaiting;\n replaying = scheduled;\n\n try {\n if (transition) transition(handler);\n else handler();\n } catch (err) {\n REPORT.as('effect', scheduled.owner, err);\n }\n\n current = replaying = undefined;\n\n if (!scheduled.holds) drop(scheduled);\n }\n}\n\n/**\n * Queue `handler` to replay after this tick. `transition` is how this\n * subscriber defers - it brackets the replay, but only where the scheduled\n * work asked for one.\n */\nfunction enqueue(handler: Handler, transition?: Transition, owner?: object) {\n if (!DISPATCH.size) queueMicrotask(flush);\n\n const scheduled = DISPATCH.get(handler);\n\n if (!scheduled) {\n const next: Scheduled = {\n transition: current ? transition : undefined,\n owner\n };\n\n DISPATCH.set(handler, next);\n claim(next);\n } else if (current) {\n claim(scheduled);\n } else {\n scheduled.transition = undefined;\n }\n}\n\n/**\n * Run `work` now, marking the subscriber updates it queues non-urgent - each\n * replays through whatever scheduler it subscribed with. The callback itself is\n * synchronous; only what it notifies is pending.\n *\n * Resolves once every one of those updates has replayed and been absorbed. A\n * subscriber which cannot report absorption resolves on replay; one which can\n * defer brackets its own replay.\n */\nfunction pending(work: Handler): Promise<void>;\n\n/**\n * Declare the subscriber currently replaying has not absorbed its update yet -\n * settlement waits on the returned callback rather than on the replay. Call\n * only from inside a replay carrying pending work; elsewhere this returns\n * nothing and the subscriber pays for none of it.\n */\nfunction pending(): (() => void) | undefined;\n\nfunction pending(work?: Handler): Promise<void> | (() => void) | undefined {\n if (!work) return hold();\n\n const parent = current;\n const record = { count: 1 } as Pending;\n const promise = new Promise<void>((resolve) => {\n record.done = resolve;\n });\n\n current = parent ? [...parent, record] : [record];\n\n try {\n work();\n } finally {\n current = parent;\n if (!--record.count) record.done();\n }\n\n return promise;\n}\n\nexport { enqueue, hold, pending, REPORT };\n"],"mappings":";AAkBA,MAAM,2BAAW,IAAI,KAAyB;;AAG9C,MAAM,SAAS;CACb,GAAG,MAAc,OAAgB,OAAiB,KAAoB;AACpE,SAAO,MAAM,MAAM;;CAErB,MAAM,KAAoB;AACxB,uBAAqB;AACnB,SAAM;IACN;;CAEL;AACD,IAAI;AACJ,IAAI;;;;;AAMJ,SAAS,MAAM,WAAsB;AACnC,KAAI,CAAC,QAAS;CAEd,MAAM,WAAW,UAAU,aAAa,UAAU,2BAAW,IAAI,KAAK;AAEtE,MAAK,MAAM,UAAU,QACnB,KAAI,CAAC,SAAS,IAAI,OAAO,EAAE;AACzB,WAAS,IAAI,OAAO;AACpB,SAAO;;;AAIb,SAAS,KAAK,WAAsB;CAClC,MAAM,EAAE,aAAa;AAErB,KAAI,CAAC,SAAU;AAEf,WAAU,WAAW;AAErB,MAAK,MAAM,UAAU,SACnB,KAAI,CAAC,EAAE,OAAO,MAAO,QAAO,MAAM;;;;;;;;AAStC,SAAS,KAAK,UAAiB,YAAyB;CACtD,MAAM,YAAY,aAAa,EAAE;AAEjC,KAAI,UAAU;AACZ,YAAU,aAAa;AACvB,QAAM,UAAU;YAET,CAAC,UAAU,SAAU;CAE9B,IAAI,WAAW;AAEf,WAAU,SAAS,UAAU,SAAS,KAAK;CAE3C,MAAM,WAAiB,UAAU;AAC/B,MAAI,SAAU;AACd,MAAI,UAAU,MAAO,QAAO,MAAM,UAAU;AAE5C,MAAI,OAAO;AACT,aAAU,UAAU;AACpB,WAAQ,OAAO,UAAU,WAAW;AACpC,aAAU;;AAGZ,aAAW;AACX,MAAI,CAAC,EAAE,UAAU,MAAQ,MAAK,UAAU;;AAG1C,QAAO;;AAGT,SAAS,QAAQ;AACf,MAAK,MAAM,CAAC,SAAS,cAAc,UAAU;AAC3C,WAAS,OAAO,QAAQ;EAExB,MAAM,EAAE,UAAU,eAAe;AAEjC,YAAU;AACV,cAAY;AAEZ,MAAI;AACF,OAAI,WAAY,YAAW,QAAQ;OAC9B,UAAS;WACP,KAAK;AACZ,UAAO,GAAG,UAAU,UAAU,OAAO,IAAI;;AAG3C,YAAU,YAAY;AAEtB,MAAI,CAAC,UAAU,MAAO,MAAK,UAAU;;;;;;;;AASzC,SAAS,QAAQ,SAAkB,YAAyB,OAAgB;AAC1E,KAAI,CAAC,SAAS,KAAM,gBAAe,MAAM;CAEzC,MAAM,YAAY,SAAS,IAAI,QAAQ;AAEvC,KAAI,CAAC,WAAW;EACd,MAAM,OAAkB;GACtB,YAAY,UAAU,aAAa;GACnC;GACD;AAED,WAAS,IAAI,SAAS,KAAK;AAC3B,QAAM,KAAK;YACF,QACT,OAAM,UAAU;KAEhB,WAAU,aAAa;;AAuB3B,SAAS,QAAQ,MAA0D;AACzE,KAAI,CAAC,KAAM,QAAO,MAAM;CAExB,MAAM,SAAS;CACf,MAAM,SAAS,EAAE,OAAO,GAAG;CAC3B,MAAM,UAAU,IAAI,SAAe,YAAY;AAC7C,SAAO,OAAO;GACd;AAEF,WAAU,SAAS,CAAC,GAAG,QAAQ,OAAO,GAAG,CAAC,OAAO;AAEjD,KAAI;AACF,QAAM;WACE;AACR,YAAU;AACV,MAAI,CAAC,EAAE,OAAO,MAAO,QAAO,MAAM;;AAGpC,QAAO"}
|