@expressive/mvc 0.90.0 → 0.92.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 +8 -4
- package/dist/component.d.ts +5 -4
- package/dist/component.js +25 -9
- package/dist/component.js.map +1 -1
- package/dist/context.d.ts +31 -6
- package/dist/context.js +87 -24
- package/dist/context.js.map +1 -1
- package/dist/element.d.ts +33 -0
- package/dist/element.js +20 -0
- package/dist/element.js.map +1 -0
- package/dist/field/get.d.ts +11 -3
- package/dist/field/get.js +21 -4
- package/dist/field/get.js.map +1 -1
- package/dist/field/has.d.ts +8 -0
- package/dist/field/has.js +21 -2
- package/dist/field/has.js.map +1 -1
- package/dist/field/map.d.ts +4 -0
- package/dist/field/map.js +12 -2
- package/dist/field/map.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime.d.ts +3 -1
- package/dist/jsx-runtime.js +4 -1
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/state.d.ts +25 -1
- package/dist/state.js +19 -6
- package/dist/state.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,7 +30,9 @@ import { Model } from '@expressive/mvc';
|
|
|
30
30
|
|
|
31
31
|
class Counter extends Model {
|
|
32
32
|
count = 0;
|
|
33
|
-
increment
|
|
33
|
+
increment() {
|
|
34
|
+
this.count++;
|
|
35
|
+
}
|
|
34
36
|
|
|
35
37
|
get doubled() {
|
|
36
38
|
return this.count * 2; // recomputes only when count changes
|
|
@@ -87,6 +89,8 @@ const timer = Timer.new(); // construct + activate
|
|
|
87
89
|
timer.set(null); // destroy - runs cleanup, freezes state
|
|
88
90
|
```
|
|
89
91
|
|
|
92
|
+
Under a host that renders on the server, `new()` still runs there but its cleanup never does - so client resources like this interval go in `mount()` instead, which runs only on the client; see the host's docs.
|
|
93
|
+
|
|
90
94
|
## Context & composition
|
|
91
95
|
|
|
92
96
|
Instances find each other through context, and compose by holding one another:
|
|
@@ -104,7 +108,7 @@ class Profile extends Model {
|
|
|
104
108
|
}
|
|
105
109
|
```
|
|
106
110
|
|
|
107
|
-
|
|
111
|
+
`<Component for={Model}>` - part of this package - places a `Model` in a tree; a host such as [`@expressive/react`](https://www.npmjs.com/package/@expressive/react) renders it, and `get()` pulls the instance back out.
|
|
108
112
|
|
|
109
113
|
## Events
|
|
110
114
|
|
|
@@ -130,11 +134,11 @@ off(); // stop listening
|
|
|
130
134
|
}
|
|
131
135
|
```
|
|
132
136
|
|
|
133
|
-
`Component` - a `Model` usable in JSX - lives here
|
|
137
|
+
`Component` - a `Model` usable in JSX - lives here, along with `<Component for>`: props, subcomponents, suspense and context placement are all core. A host only renders them; for how that looks in an app, see **[`@expressive/react`](https://www.npmjs.com/package/@expressive/react)**.
|
|
134
138
|
|
|
135
139
|
---
|
|
136
140
|
|
|
137
|
-
To render state in a UI
|
|
141
|
+
To render state in a UI, add a host: [`@expressive/react`](https://www.npmjs.com/package/@expressive/react) or [`@expressive/dom`](https://www.npmjs.com/package/@expressive/dom).
|
|
138
142
|
|
|
139
143
|
Full guide and API reference → **[github.com/gabeklein/expressive-mvc](https://github.com/gabeklein/expressive-mvc)**
|
|
140
144
|
|
package/dist/component.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ type ForProps<T extends Model> = {
|
|
|
11
11
|
catch?: (error: Error, instance: Component) => Promise<void> | void;
|
|
12
12
|
} & ({
|
|
13
13
|
for: Model.Extends<T>;
|
|
14
|
-
is?: (instance: T) => void;
|
|
14
|
+
is?: (instance: T) => Model.Assign<T> | void;
|
|
15
15
|
} | {
|
|
16
16
|
for: T | undefined;
|
|
17
17
|
is?: never;
|
|
@@ -33,10 +33,11 @@ declare namespace Component {
|
|
|
33
33
|
* Callback for newly created instance. Only called once.
|
|
34
34
|
*
|
|
35
35
|
* Runs after props apply but **before** the `new()` lifecycle hook - so it
|
|
36
|
-
* may configure state that `new()` then observes.
|
|
37
|
-
*
|
|
36
|
+
* may configure state that `new()` then observes. A returned object is
|
|
37
|
+
* assigned, like props. To react to a fully initialized instance instead,
|
|
38
|
+
* use `watch` or an effect.
|
|
38
39
|
*/
|
|
39
|
-
is?(instance: T): void;
|
|
40
|
+
is?(instance: T): Model.Assign<T> | void;
|
|
40
41
|
/**
|
|
41
42
|
* Fallback to show when suspended or in error recovery.
|
|
42
43
|
* Pass `false` to opt out of the component's own suspense boundary,
|
package/dist/component.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { listener } from "./observable.js";
|
|
2
|
+
import { Model, adopt, plain, trailing } from "./state.js";
|
|
3
|
+
import { Context, join } from "./context.js";
|
|
4
|
+
import { element } from "./element.js";
|
|
3
5
|
import { set } from "./field/set.js";
|
|
4
6
|
|
|
5
7
|
//#region src/component.ts
|
|
@@ -37,10 +39,12 @@ var Component = class Component extends Model {
|
|
|
37
39
|
trailing(other).forEach((orphan) => orphan.set(null));
|
|
38
40
|
other.set(null);
|
|
39
41
|
}
|
|
42
|
+
let out;
|
|
40
43
|
if (provider) provide(this);
|
|
41
|
-
else props.is?.(this);
|
|
44
|
+
else out = props.is?.(this);
|
|
42
45
|
Object.defineProperty(this, "props", { enumerable: false });
|
|
43
46
|
PENDING.delete(props);
|
|
47
|
+
if (plain(out)) return out;
|
|
44
48
|
}
|
|
45
49
|
]);
|
|
46
50
|
PENDING.set(props, this);
|
|
@@ -87,6 +91,11 @@ function provide(self) {
|
|
|
87
91
|
let owned;
|
|
88
92
|
let mounted = false;
|
|
89
93
|
let release;
|
|
94
|
+
let provided;
|
|
95
|
+
function remove() {
|
|
96
|
+
if (provided) provided();
|
|
97
|
+
provided = void 0;
|
|
98
|
+
}
|
|
90
99
|
function unmount() {
|
|
91
100
|
if (typeof release == "function") release();
|
|
92
101
|
release = void 0;
|
|
@@ -111,21 +120,28 @@ function provide(self) {
|
|
|
111
120
|
if (next !== input) {
|
|
112
121
|
input = next;
|
|
113
122
|
unmount();
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
remove();
|
|
124
|
+
if (Model.is(next)) owned = target = new next(rest);
|
|
125
|
+
else {
|
|
126
|
+
owned = void 0;
|
|
127
|
+
target = next instanceof Model ? next : void 0;
|
|
128
|
+
}
|
|
129
|
+
if (target) provided = join(self, target, true);
|
|
130
|
+
adopt(self, "for", target);
|
|
131
|
+
if (owned) {
|
|
132
|
+
const out = is?.(owned);
|
|
133
|
+
if (plain(out)) owned.set(out);
|
|
118
134
|
if (mounted) release = owned.mount?.();
|
|
119
135
|
return;
|
|
120
136
|
}
|
|
121
|
-
owned = void 0;
|
|
122
|
-
adopt(self, "for", target = next instanceof Model ? next : void 0);
|
|
123
137
|
}
|
|
124
138
|
target?.set(rest);
|
|
125
139
|
}
|
|
126
140
|
sync();
|
|
127
141
|
self.set("props", sync);
|
|
142
|
+
listener(self, remove, null);
|
|
128
143
|
}
|
|
144
|
+
element(Component);
|
|
129
145
|
|
|
130
146
|
//#endregion
|
|
131
147
|
export { Component };
|
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 { Model, adopt, trailing } from './state';\n\nimport type { Host } from './jsx-runtime';\n\nconst PENDING = new WeakMap<object, Component<any>>();\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 Model> = {\n children?: Component.Node;\n fallback?: Component.Node | false;\n catch?: (error: Error, instance: Component) => Promise<void> | void;\n} & (\n | { for: Model.Extends<T>; is?: (instance: T) => void }\n | { for: T | undefined; is?: never }\n) & { [K in Exclude<keyof T, keyof Model> & 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 ModelProps<T extends Model> = {\n [K in Exclude<keyof T, keyof Component> & Acceptable<T>]?: T[K];\n };\n\n /** @deprecated Renamed to `ModelProps`. */\n type StateProps<T extends Model> = ModelProps<T>;\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 & ModelProps<T>\n & BaseProps<T>\n & RenderProps<T['render']>;\n}\n\nclass Component<P = unknown> extends Model {\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 [Model] ? 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\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 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: Model | 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 (Model.is(next)) {\n adopt(self, 'for', (target = new (next as Model.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 Model ? next : undefined));\n }\n\n target?.set(rest);\n }\n\n sync();\n self.set('props', sync);\n}\n\nexport { Component };\n"],"mappings":";;;;;AAMA,MAAM,0BAAU,IAAI,SAAiC;AA8ErD,IAAM,YAAN,MAAM,kBAA+B,MAAM;;;;;;;;;;CAqBzC,WAA2B,IAAI,KAAK;CAEpC,YAAY,OAAa,GAAG,MAAa;AACvC,MAAI,SAAS,KAAM,SAAQ,EAAE;EAE7B,MAAM,OAAO,EAAE;EACf,MAAM,OAAO,QAAQ,IAAI,MAAM;EAC/B,MAAM,WAAW,IAAI,WAAW,aAAa,SAAS;AAEtD,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;GACJ;GACA,KAAK,QAAQ,MAAM,EAAE,aAAa,SAAS;SACrC;IACJ,MAAM,QAAQ,QAAQ,QAAQ,IAAI,MAAM;AAExC,QAAI,UAAU,MAAM;AAClB,cAAS,MAAM,CAAC,SAAS,WAAW,OAAO,IAAI,KAAK,CAAC;AACrD,WAAM,IAAI,KAAK;;AAGjB,QAAI,SAAU,SAAQ,KAAK;QACtB,OAAM,KAAK,KAAK;AAErB,WAAO,eAAe,MAAM,SAAS,EAAE,YAAY,OAAO,CAAC;AAC3D,YAAQ,OAAO,MAAM;;GAExB,CAAC;AAEF,UAAQ,IAAI,OAAO,KAAK;AAExB,OAAK,QAAQ;AACb,OAAK,IAAI,eAAe;AACtB,QAAK,IAAI,MAAM,KAAK,MAAM,CAAC;IAC3B;;;;;;;;;;;CAYJ,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;AAEF,UAAU,GAAG,EACX,MAAM,MAAM;AAGV,KAFY,OAAO,yBAAyB,MAAM,MAAM,EAE/C,aACP,QAAO,eAAe,MAAM,OAAO;EACjC,YAAY;EACZ,UAAU;EACX,CAAC;GAEP,CAAC;AAEF,SAAS,QAAQ,MAAsB;CACrC,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI,UAAU;CACd,IAAI;CAEJ,SAAS,UAAU;AACjB,MAAI,OAAO,WAAW,WAAY,UAAS;AAC3C,YAAU;;CAGZ,MAAM,YAAa,KAA+B;AAElD,QAAO,eAAe,MAAM,SAAS;EACnC,cAAc;EACd,QAAQ;GACN,MAAM,OAAO,WAAW,KAAK,KAAK;AAElC,aAAU;AACV,aAAU,OAAO,SAAS;AAE1B,gBAAa;AACX,cAAU;AACV,aAAS;AACT,QAAI,OAAO,QAAQ,WAAY,OAAM;;;EAG1C,CAAC;CAEF,SAAS,OAAO;EACd,MAAM,EAAE,KAAK,MAAM,IAAI,UAAU,UAAU,OAAO,QAAQ,GAAG,SAAS,KAAK;AAE3E,MAAI,aAAa,OAAW,MAAK,WAAW;AAE5C,MAAI,SAAS,OAAO;AAClB,WAAQ;AACR,YAAS;AAET,OAAI,MAAM,GAAG,KAAK,EAAE;AAClB,UAAM,MAAM,OAAQ,SAAS,IAAK,KAAoB,KAAK,CAAE;AAC7D,YAAQ;AACR,SAAK,OAAO;AACZ,QAAI,QAAS,WAAU,MAAO,SAAS;AACvC;;AAGF,WAAQ;AACR,SAAM,MAAM,OAAQ,SAAS,gBAAgB,QAAQ,OAAO,OAAW;;AAGzE,UAAQ,IAAI,KAAK;;AAGnB,OAAM;AACN,MAAK,IAAI,SAAS,KAAK"}
|
|
1
|
+
{"version":3,"file":"component.js","names":[],"sources":["../src/component.ts"],"sourcesContent":["import { Context, join } from './context';\nimport { element } from './element';\nimport { listener } from './observable';\nimport { set } from './field/set';\nimport { Model, adopt, plain, trailing } from './state';\n\nimport type { Host } from './jsx-runtime';\n\nconst PENDING = new WeakMap<object, Component<any>>();\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 Model> = {\n children?: Component.Node;\n fallback?: Component.Node | false;\n catch?: (error: Error, instance: Component) => Promise<void> | void;\n} & (\n | { for: Model.Extends<T>; is?: (instance: T) => Model.Assign<T> | void }\n | { for: T | undefined; is?: never }\n) & { [K in Exclude<keyof T, keyof Model> & 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. A returned object is\n * assigned, like props. To react to a fully initialized instance instead,\n * use `watch` or an effect.\n */\n is?(instance: T): Model.Assign<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 ModelProps<T extends Model> = {\n [K in Exclude<keyof T, keyof Component> & Acceptable<T>]?: T[K];\n };\n\n /** @deprecated Renamed to `ModelProps`. */\n type StateProps<T extends Model> = ModelProps<T>;\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 & ModelProps<T>\n & BaseProps<T>\n & RenderProps<T['render']>;\n}\n\nclass Component<P = unknown> extends Model {\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 [Model] ? 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 let out: unknown;\n\n if (provider) provide(this);\n else out = props.is?.(this);\n\n Object.defineProperty(this, 'props', { enumerable: false });\n PENDING.delete(props);\n\n if (plain(out)) return out;\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\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 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: Model | undefined;\n let owned: (Model & { mount?(): unknown }) | undefined;\n let mounted = false;\n let release: unknown;\n let provided: (() => void) | undefined;\n\n function remove() {\n if (provided) provided();\n provided = undefined;\n }\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 remove();\n\n if (Model.is(next)) {\n owned = target = new (next as Model.Type)(rest);\n } else {\n owned = undefined;\n target = next instanceof Model ? next : undefined;\n }\n\n if (target) provided = join(self, target, true);\n adopt(self, 'for', target);\n\n if (owned) {\n const out = is?.(owned);\n\n if (plain(out)) owned.set(out);\n if (mounted) release = owned.mount?.();\n\n return;\n }\n }\n\n target?.set(rest);\n }\n\n sync();\n self.set('props', sync);\n listener(self, remove, null);\n}\n\nelement(Component);\n\nexport { Component };\n"],"mappings":"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"}
|
package/dist/context.d.ts
CHANGED
|
@@ -8,8 +8,20 @@ declare namespace Context {
|
|
|
8
8
|
}
|
|
9
9
|
declare class Context {
|
|
10
10
|
static get root(): Context;
|
|
11
|
-
/**
|
|
12
|
-
static get(
|
|
11
|
+
/** Current context - innermost set by `state.get(Type)` or the `get` instruction, otherwise as the host's {@link resolver} returns it. */
|
|
12
|
+
static get(): Context;
|
|
13
|
+
/** Get the context a Model was added to - root if none. */
|
|
14
|
+
static get(state: Model): Context;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a static `get` of `Type` by running `compute` against the current
|
|
17
|
+
* context. Inside `state.get(Type)` or the `get` instruction, `from` is that
|
|
18
|
+
* Model and a returned Model is tracked through it; otherwise
|
|
19
|
+
* the host's {@link resolver} decides.
|
|
20
|
+
*
|
|
21
|
+
* @param Type - Class `get` was called on.
|
|
22
|
+
* @param compute - Resolves the value from a context.
|
|
23
|
+
*/
|
|
24
|
+
static get<R>(Type: Model.Extends, compute: (context: Context, from?: Model) => R): R;
|
|
13
25
|
id: string;
|
|
14
26
|
parent?: Context;
|
|
15
27
|
scope: Set<Context>;
|
|
@@ -25,7 +37,7 @@ declare class Context {
|
|
|
25
37
|
/** Subscribe to a type becoming available in context. */
|
|
26
38
|
get<T extends Model>(Type: Model.Extends<T>, callback: Expect<T>, downstream?: boolean): () => void;
|
|
27
39
|
/** Lookup excluding a specific instance (used by Model.prototype.get for upstream-of-self semantics). */
|
|
28
|
-
get<T extends Model>(Type: Model.Extends<T>, arg: boolean | Expect<T> | undefined, downstream: boolean | undefined, skip
|
|
40
|
+
get<T extends Model>(Type: Model.Extends<T>, arg: boolean | Expect<T> | undefined, downstream: boolean | undefined, skip?: Model): T | undefined | (() => void);
|
|
29
41
|
add(I: Model, explicit?: boolean): () => void;
|
|
30
42
|
/**
|
|
31
43
|
* Create a child context, optionally registering Models to it.
|
|
@@ -41,13 +53,26 @@ declare class Context {
|
|
|
41
53
|
*/
|
|
42
54
|
pop(): void;
|
|
43
55
|
}
|
|
56
|
+
type Resolve = (Type: Model.Extends | undefined, compute: (context: Context) => unknown) => unknown;
|
|
57
|
+
/**
|
|
58
|
+
* Install how a host runs `compute` for a `Context.get` outside any origin -
|
|
59
|
+
* `Type` is undefined for bare `Context.get()`. Its return value is what the
|
|
60
|
+
* caller receives, so a host may track or wrap it. Returns the previous
|
|
61
|
+
* resolver, for the host to call where it is not rendering.
|
|
62
|
+
*/
|
|
63
|
+
declare function resolver(next: Resolve): Resolve;
|
|
64
|
+
/**
|
|
65
|
+
* Run `fn` with the context of `origin` current, and `origin` as origin of
|
|
66
|
+
* any `get` resolved within. Nests.
|
|
67
|
+
*/
|
|
68
|
+
declare function within<R>(origin: Model, fn: () => R): R;
|
|
44
69
|
/**
|
|
45
|
-
* Add `value` to the
|
|
70
|
+
* Add `value` to the lookup origin of `state`. A context-less `state` holds
|
|
46
71
|
* it instead - every context adding `state` later adds its held children too.
|
|
47
72
|
*/
|
|
48
|
-
declare function join(state: Model, value: Model): () => void;
|
|
73
|
+
declare function join(state: Model, value: Model, explicit?: boolean): () => void;
|
|
49
74
|
/** The Model a host mounted `state` under - the one its nearest enclosing context was set up for. */
|
|
50
75
|
declare function host(state: Model): Model | undefined;
|
|
51
76
|
//#endregion
|
|
52
|
-
export { Context, host, join };
|
|
77
|
+
export { Context, host, join, resolver, within };
|
|
53
78
|
//# sourceMappingURL=context.d.ts.map
|
package/dist/context.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { event, listener } from "./observable.js";
|
|
2
|
-
import { Model, uid } from "./state.js";
|
|
1
|
+
import { Observer, event, listener, touch } from "./observable.js";
|
|
2
|
+
import { Model, observed, parent, uid } from "./state.js";
|
|
3
3
|
|
|
4
4
|
//#region src/context.ts
|
|
5
|
-
const
|
|
5
|
+
const CONTEXT = Symbol("context");
|
|
6
|
+
const STACK = [];
|
|
7
|
+
const HOME = /* @__PURE__ */ new WeakMap();
|
|
6
8
|
const HELD = /* @__PURE__ */ new WeakMap();
|
|
7
9
|
const HOST = /* @__PURE__ */ new WeakMap();
|
|
8
10
|
const CLEANUP = /* @__PURE__ */ new WeakMap();
|
|
@@ -11,9 +13,14 @@ var Context = class Context {
|
|
|
11
13
|
static get root() {
|
|
12
14
|
return ROOT ??= new Context();
|
|
13
15
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
static get(arg, compute = (context) => context) {
|
|
17
|
+
if (!arg || typeof arg == "function") {
|
|
18
|
+
const top = STACK[STACK.length - 1];
|
|
19
|
+
if (!top) return current(arg, compute);
|
|
20
|
+
const [context, from] = top;
|
|
21
|
+
return observed(from, arg, compute(arg ? touch(from, CONTEXT, context) : context, from.is));
|
|
22
|
+
}
|
|
23
|
+
return home(arg.is) || Context.root;
|
|
17
24
|
}
|
|
18
25
|
id = uid();
|
|
19
26
|
parent;
|
|
@@ -45,7 +52,8 @@ var Context = class Context {
|
|
|
45
52
|
get(Type, arg, downstream, skip) {
|
|
46
53
|
let found;
|
|
47
54
|
let priority = false;
|
|
48
|
-
for (let ctx = this; ctx; ctx = ctx
|
|
55
|
+
for (let ctx = this; ctx; ctx = above(ctx)) {
|
|
56
|
+
touch(ctx, Type);
|
|
49
57
|
const entries = ctx.provide.get(Type);
|
|
50
58
|
if (!entries) continue;
|
|
51
59
|
for (const [state, explicit] of entries) {
|
|
@@ -82,16 +90,23 @@ var Context = class Context {
|
|
|
82
90
|
const { provide } = this;
|
|
83
91
|
const root = this === Context.root;
|
|
84
92
|
const TT = [];
|
|
85
|
-
|
|
93
|
+
const conflict = (T) => {
|
|
86
94
|
const entries = provide.get(T);
|
|
87
95
|
if (entries) {
|
|
88
|
-
for (const entry of entries) if (!entry[1] && entry[0] !== I)
|
|
96
|
+
for (const entry of entries) if (!entry[1] && entry[0] !== I) {
|
|
97
|
+
emit(this, T);
|
|
98
|
+
return entries.delete(entry);
|
|
99
|
+
}
|
|
89
100
|
}
|
|
90
|
-
}
|
|
101
|
+
};
|
|
91
102
|
for (let T = I.constructor; T !== Model; T = Object.getPrototypeOf(T)) if (explicit || !root || !conflict(T)) TT.push(T);
|
|
92
103
|
const expects = /* @__PURE__ */ new Map();
|
|
93
104
|
const onDone = /* @__PURE__ */ new Set();
|
|
94
|
-
for (const T of TT)
|
|
105
|
+
for (const T of TT) {
|
|
106
|
+
onDone.add(this.register(T, [I, explicit]));
|
|
107
|
+
onDone.add(() => emit(this, T));
|
|
108
|
+
emit(this, T);
|
|
109
|
+
}
|
|
95
110
|
function queue(ctx, downstream, same) {
|
|
96
111
|
let found = false;
|
|
97
112
|
for (const T of TT) {
|
|
@@ -109,7 +124,9 @@ var Context = class Context {
|
|
|
109
124
|
queue(this, true, true);
|
|
110
125
|
for (let ctx = this.parent; ctx; ctx = ctx.parent) queue(ctx, true);
|
|
111
126
|
this.traverse((ctx) => queue(ctx, false));
|
|
112
|
-
|
|
127
|
+
const owner = parent(I);
|
|
128
|
+
const placed = explicit && !(owner && home(owner));
|
|
129
|
+
if (!HOME.has(I) && (!owner || placed)) HOME.set(I, this);
|
|
113
130
|
listener(I, () => {
|
|
114
131
|
expects.forEach((f) => f());
|
|
115
132
|
expects.clear();
|
|
@@ -120,8 +137,8 @@ var Context = class Context {
|
|
|
120
137
|
onDone.clear();
|
|
121
138
|
}
|
|
122
139
|
const cleanup = CLEANUP.get(this);
|
|
123
|
-
const held = Array.from(HELD.get(I) || [], ([child, added]) => {
|
|
124
|
-
const done = this.add(child);
|
|
140
|
+
const held = Array.from(HELD.get(I) || [], ([child, [added, explicit]]) => {
|
|
141
|
+
const done = this.add(child, explicit);
|
|
125
142
|
added.add(done);
|
|
126
143
|
return () => {
|
|
127
144
|
if (added.delete(done)) done();
|
|
@@ -163,19 +180,58 @@ var Context = class Context {
|
|
|
163
180
|
if (this.parent) this.parent.scope.delete(this);
|
|
164
181
|
}
|
|
165
182
|
};
|
|
166
|
-
Object.
|
|
167
|
-
|
|
168
|
-
|
|
183
|
+
Object.defineProperties(Context.prototype, {
|
|
184
|
+
toString: { value() {
|
|
185
|
+
return `Context-${this.id}`;
|
|
186
|
+
} },
|
|
187
|
+
[Observer]: { get() {
|
|
188
|
+
Object.defineProperty(this, Observer, { writable: true });
|
|
189
|
+
event(this);
|
|
190
|
+
return this[Observer];
|
|
191
|
+
} }
|
|
192
|
+
});
|
|
193
|
+
function emit(ctx, T) {
|
|
194
|
+
if (Object.getOwnPropertyDescriptor(ctx, Observer)) event(ctx, T);
|
|
195
|
+
}
|
|
196
|
+
let current = (_Type, compute) => compute(Context.root);
|
|
197
|
+
/**
|
|
198
|
+
* Install how a host runs `compute` for a `Context.get` outside any origin -
|
|
199
|
+
* `Type` is undefined for bare `Context.get()`. Its return value is what the
|
|
200
|
+
* caller receives, so a host may track or wrap it. Returns the previous
|
|
201
|
+
* resolver, for the host to call where it is not rendering.
|
|
202
|
+
*/
|
|
203
|
+
function resolver(next) {
|
|
204
|
+
const previous = current;
|
|
205
|
+
current = next;
|
|
206
|
+
return previous;
|
|
207
|
+
}
|
|
169
208
|
/**
|
|
170
|
-
*
|
|
209
|
+
* Run `fn` with the context of `origin` current, and `origin` as origin of
|
|
210
|
+
* any `get` resolved within. Nests.
|
|
211
|
+
*/
|
|
212
|
+
function within(origin, fn) {
|
|
213
|
+
STACK.push([Context.get(origin), origin]);
|
|
214
|
+
try {
|
|
215
|
+
return fn();
|
|
216
|
+
} finally {
|
|
217
|
+
STACK.pop();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Add `value` to the lookup origin of `state`. A context-less `state` holds
|
|
171
222
|
* it instead - every context adding `state` later adds its held children too.
|
|
172
223
|
*/
|
|
173
|
-
function join(state, value) {
|
|
174
|
-
const ctx =
|
|
175
|
-
|
|
224
|
+
function join(state, value, explicit) {
|
|
225
|
+
const ctx = home(state.is);
|
|
226
|
+
const none = () => {};
|
|
227
|
+
if (ctx) {
|
|
228
|
+
for (let at = ctx; at; at = at.parent) for (const [x, is] of at.provide.get(value.constructor) || []) if (x === value && is) return none;
|
|
229
|
+
return ctx.add(value, explicit);
|
|
230
|
+
}
|
|
176
231
|
const held = HELD.get(state) || /* @__PURE__ */ new Map();
|
|
232
|
+
if (held.get(value)?.[1]) return none;
|
|
177
233
|
const added = /* @__PURE__ */ new Set();
|
|
178
|
-
HELD.set(state, held.set(value, added));
|
|
234
|
+
HELD.set(state, held.set(value, [added, explicit]));
|
|
179
235
|
return () => {
|
|
180
236
|
held.delete(value);
|
|
181
237
|
added.forEach((done) => {
|
|
@@ -184,9 +240,16 @@ function join(state, value) {
|
|
|
184
240
|
});
|
|
185
241
|
};
|
|
186
242
|
}
|
|
243
|
+
function above(ctx) {
|
|
244
|
+
return ctx.parent && touch(ctx, "parent", ctx.parent);
|
|
245
|
+
}
|
|
246
|
+
function home(state) {
|
|
247
|
+
const owner = parent(state);
|
|
248
|
+
return HOME.get(state) || (owner ? home(owner) : void 0);
|
|
249
|
+
}
|
|
187
250
|
/** The Model a host mounted `state` under - the one its nearest enclosing context was set up for. */
|
|
188
251
|
function host(state) {
|
|
189
|
-
for (let ctx =
|
|
252
|
+
for (let ctx = home(state); ctx; ctx = ctx.parent) {
|
|
190
253
|
const found = HOST.get(ctx);
|
|
191
254
|
if (found && found !== state) return found;
|
|
192
255
|
}
|
|
@@ -214,5 +277,5 @@ function insert(into, input) {
|
|
|
214
277
|
}
|
|
215
278
|
|
|
216
279
|
//#endregion
|
|
217
|
-
export { Context, host, join };
|
|
280
|
+
export { Context, host, join, resolver, within };
|
|
218
281
|
//# 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, Model, uid } from \"./state\";\n\nconst LOOKUP = new WeakMap<Model, Context>();\nconst HELD = new WeakMap<Model, Map<Model, Set<() => void>>>();\nconst HOST = new WeakMap<Context, Model>();\nconst CLEANUP = new WeakMap<Context, Set<() => void>>();\nlet ROOT: Context;\n\ntype Accept<T extends Model = Model> =\n | T\n | Model.Type<T>\n | Record<string | number, T | Model.Type<T>>;\n\ntype Expect<T extends Model = Model> = (\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 Model. Adapters may override to provide framework context. */\n static get(state?: Model): 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 Model.Extends,\n Set<[Expect, boolean | undefined]> | null\n >();\n public provide = new Map<Model.Extends, Set<[Model, boolean]> | null>();\n\n constructor(arg?: Context | Accept) {\n CLEANUP.set(this, new Set());\n\n if (arg instanceof Context) {\n this.parent = arg;\n arg.scope.add(this);\n } else if (arg) {\n insert(this, 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 Model>(\n type: Model.Extends<T>,\n value: [Expect<T>, boolean | undefined],\n asConsumer: true,\n ): () => void;\n\n private register<T extends Model>(\n type: Model.Extends<T>,\n value: [T, boolean],\n asConsumer?: false,\n ): () => void;\n\n private register(type: Model.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 Model>(Type: Model.Extends<T>, required?: true): T;\n\n /** Find specified type upstream. Returns undefined if not found. */\n public get<T extends Model>(\n Type: Model.Extends<T>,\n required?: boolean,\n ): T | undefined;\n\n /** Subscribe to a type becoming available in context. */\n public get<T extends Model>(\n Type: Model.Extends<T>,\n callback: Expect<T>,\n downstream?: boolean,\n ): () => void;\n\n /** Lookup excluding a specific instance (used by Model.prototype.get for upstream-of-self semantics). */\n public get<T extends Model>(\n Type: Model.Extends<T>,\n arg: boolean | Expect<T> | undefined,\n downstream: boolean | undefined,\n skip: Model,\n ): T | undefined | (() => void);\n\n public get<T extends Model>(\n Type: Model.Extends<T>,\n arg?: boolean | Expect<T>,\n downstream?: boolean,\n skip?: Model,\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 add(I: Model, explicit = false) {\n const { provide } = this;\n const root = this === Context.root;\n const TT: Model.Extends[] = [];\n\n function conflict(T: Model.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 Model.Extends;\n T !== Model;\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 cleanup = CLEANUP.get(this)!;\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.add(remove);\n\n return remove;\n }\n\n /**\n * Create a child context, optionally registering Models to it.\n *\n * @param input Model instance to adopt, Model class to create - and destroy when the context pops - or an object of them, all registered before any activates.\n * @param cleanup Called once when the context pops, before its Models are removed.\n */\n public push(input?: Accept, cleanup?: () => void) {\n const next = new Context(this);\n\n if (cleanup) CLEANUP.get(next)!.add(cleanup);\n if (input) insert(next, input);\n\n return next;\n }\n\n /**\n * Remove all Models from this context.\n *\n * Will also run any cleanup callbacks registered when Models were added.\n */\n public pop() {\n this.scope.forEach((x) => x.pop());\n this.scope.clear();\n const cleanup = CLEANUP.get(this)!;\n cleanup.forEach((cb) => cb());\n 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: Model, value: Model): () => 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 Model a host mounted `state` under - the one its nearest enclosing context was set up for. */\nfunction host(state: Model) {\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\nfunction insert(into: Context, input: Accept) {\n const single = typeof input == \"function\" || input instanceof Model;\n const entries = Object.entries(single ? { input } : input);\n const init: Model[] = [];\n const owns: Model[] = [];\n\n for (const [K, V] of entries) {\n const owned = Model.is(V);\n\n if (!(owned || V instanceof Model)) {\n const as = single || K == String(V) ? V : `${V} (as '${K}')`;\n throw new Error(\n `Context can only include an instance or class of Model but got ${as}.`,\n );\n }\n\n const state = owned ? new V() : V.is;\n\n into.add(state, true);\n init.push(state);\n\n if (single) HOST.set(into, state);\n if (owned) owns.push(state);\n }\n\n const cleanup = CLEANUP.get(into)!;\n\n for (const state of owns) cleanup.add(() => event(state, null));\n for (const state of init) event(state);\n}\n\nexport { Context, host, join };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"context.js","names":[],"sources":["../src/context.ts"],"sourcesContent":["import { event as signal, listener, Observer, touch } from \"./observable\";\nimport { event, Model, observed, parent, uid } from \"./state\";\n\nconst CONTEXT = Symbol(\"context\");\nconst STACK: [Context, Model][] = [];\nconst HOME = new WeakMap<Model, Context>();\nconst HELD = new WeakMap<Model, Map<Model, [Set<() => void>, boolean?]>>();\nconst HOST = new WeakMap<Context, Model>();\nconst CLEANUP = new WeakMap<Context, Set<() => void>>();\nlet ROOT: Context;\n\ntype Accept<T extends Model = Model> =\n | T\n | Model.Type<T>\n | Record<string | number, T | Model.Type<T>>;\n\ntype Expect<T extends Model = Model> = (\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 /** Current context - innermost set by `state.get(Type)` or the `get` instruction, otherwise as the host's {@link resolver} returns it. */\n static get(): Context;\n\n /** Get the context a Model was added to - root if none. */\n static get(state: Model): Context;\n\n /**\n * Resolve a static `get` of `Type` by running `compute` against the current\n * context. Inside `state.get(Type)` or the `get` instruction, `from` is that\n * Model and a returned Model is tracked through it; otherwise\n * the host's {@link resolver} decides.\n *\n * @param Type - Class `get` was called on.\n * @param compute - Resolves the value from a context.\n */\n static get<R>(\n Type: Model.Extends,\n compute: (context: Context, from?: Model) => R,\n ): R;\n\n static get(\n arg?: Model | Model.Extends,\n compute: (context: Context, from?: Model) => unknown = (context) => context,\n ): unknown {\n if (!arg || typeof arg == \"function\") {\n const top = STACK[STACK.length - 1];\n\n if (!top) return current(arg, compute);\n\n const [context, from] = top;\n const view = arg ? touch(from, CONTEXT, context) : context;\n\n return observed(from, arg, compute(view, from.is));\n }\n\n return home(arg.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 Model.Extends,\n Set<[Expect, boolean | undefined]> | null\n >();\n public provide = new Map<Model.Extends, Set<[Model, boolean]> | null>();\n\n constructor(arg?: Context | Accept) {\n CLEANUP.set(this, new Set());\n\n if (arg instanceof Context) {\n this.parent = arg;\n arg.scope.add(this);\n } else if (arg) {\n insert(this, 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 Model>(\n type: Model.Extends<T>,\n value: [Expect<T>, boolean | undefined],\n asConsumer: true,\n ): () => void;\n\n private register<T extends Model>(\n type: Model.Extends<T>,\n value: [T, boolean],\n asConsumer?: false,\n ): () => void;\n\n private register(type: Model.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 Model>(Type: Model.Extends<T>, required?: true): T;\n\n /** Find specified type upstream. Returns undefined if not found. */\n public get<T extends Model>(\n Type: Model.Extends<T>,\n required?: boolean,\n ): T | undefined;\n\n /** Subscribe to a type becoming available in context. */\n public get<T extends Model>(\n Type: Model.Extends<T>,\n callback: Expect<T>,\n downstream?: boolean,\n ): () => void;\n\n /** Lookup excluding a specific instance (used by Model.prototype.get for upstream-of-self semantics). */\n public get<T extends Model>(\n Type: Model.Extends<T>,\n arg: boolean | Expect<T> | undefined,\n downstream: boolean | undefined,\n skip?: Model,\n ): T | undefined | (() => void);\n\n public get<T extends Model>(\n Type: Model.Extends<T>,\n arg?: boolean | Expect<T>,\n downstream?: boolean,\n skip?: Model,\n ) {\n let found: T | null | undefined;\n let priority = false;\n\n for (let ctx: Context | undefined = this; ctx; ctx = above(ctx)) {\n touch(ctx, Type);\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 add(I: Model, explicit = false) {\n const { provide } = this;\n const root = this === Context.root;\n const TT: Model.Extends[] = [];\n\n const conflict = (T: Model.Extends) => {\n const entries = provide.get(T);\n if (entries)\n for (const entry of entries)\n if (!entry[1] && entry[0] !== I) {\n emit(this, T);\n return entries.delete(entry);\n }\n };\n\n for (\n let T = I.constructor as Model.Extends;\n T !== Model;\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) {\n onDone.add(this.register(T, [I, explicit]));\n onDone.add(() => emit(this, T));\n emit(this, T);\n }\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 const owner = parent(I);\n const placed = explicit && !(owner && home(owner));\n\n if (!HOME.has(I) && (!owner || placed)) HOME.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 cleanup = CLEANUP.get(this)!;\n const held = Array.from(HELD.get(I) || [], ([child, [added, explicit]]) => {\n const done = this.add(child, explicit);\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.add(remove);\n\n return remove;\n }\n\n /**\n * Create a child context, optionally registering Models to it.\n *\n * @param input Model instance to adopt, Model class to create - and destroy when the context pops - or an object of them, all registered before any activates.\n * @param cleanup Called once when the context pops, before its Models are removed.\n */\n public push(input?: Accept, cleanup?: () => void) {\n const next = new Context(this);\n\n if (cleanup) CLEANUP.get(next)!.add(cleanup);\n if (input) insert(next, input);\n\n return next;\n }\n\n /**\n * Remove all Models from this context.\n *\n * Will also run any cleanup callbacks registered when Models were added.\n */\n public pop() {\n this.scope.forEach((x) => x.pop());\n this.scope.clear();\n const cleanup = CLEANUP.get(this)!;\n cleanup.forEach((cb) => cb());\n 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.defineProperties(Context.prototype, {\n toString: {\n value() {\n return `Context-${this.id}`;\n },\n },\n [Observer]: {\n get(this: Context) {\n Object.defineProperty(this, Observer, { writable: true });\n signal(this);\n return (this as any)[Observer];\n },\n },\n});\n\nfunction emit(ctx: Context, T: Model.Extends) {\n if (Object.getOwnPropertyDescriptor(ctx, Observer)) signal(ctx, T as any);\n}\n\ntype Resolve = (\n Type: Model.Extends | undefined,\n compute: (context: Context) => unknown,\n) => unknown;\n\nlet current: Resolve = (_Type, compute) => compute(Context.root);\n\n/**\n * Install how a host runs `compute` for a `Context.get` outside any origin -\n * `Type` is undefined for bare `Context.get()`. Its return value is what the\n * caller receives, so a host may track or wrap it. Returns the previous\n * resolver, for the host to call where it is not rendering.\n */\nfunction resolver(next: Resolve): Resolve {\n const previous = current;\n current = next;\n return previous;\n}\n\n/**\n * Run `fn` with the context of `origin` current, and `origin` as origin of\n * any `get` resolved within. Nests.\n */\nfunction within<R>(origin: Model, fn: () => R): R {\n STACK.push([Context.get(origin), origin]);\n\n try {\n return fn();\n } finally {\n STACK.pop();\n }\n}\n\n/**\n * Add `value` to the lookup origin of `state`. A context-less `state` holds\n * it instead - every context adding `state` later adds its held children too.\n */\nfunction join(state: Model, value: Model, explicit?: boolean): () => void {\n const ctx = home(state.is);\n const none = () => {};\n\n if (ctx) {\n for (let at: Context | undefined = ctx; at; at = at.parent)\n for (const [x, is] of at.provide.get(value.constructor as Model.Extends) || [])\n if (x === value && is) return none;\n\n return ctx.add(value, explicit);\n }\n\n const held = HELD.get(state) || new Map<Model, [Set<() => void>, boolean?]>();\n\n if (held.get(value)?.[1]) return none;\n\n const added = new Set<() => void>();\n\n HELD.set(state, held.set(value, [added, explicit]));\n\n return () => {\n held.delete(value);\n added.forEach((done) => {\n added.delete(done);\n done();\n });\n };\n}\n\nfunction above(ctx: Context) {\n return ctx.parent && touch(ctx, \"parent\", ctx.parent);\n}\n\nfunction home(state: Model): Context | undefined {\n const owner = parent(state);\n return HOME.get(state) || (owner ? home(owner) : undefined);\n}\n\n/** The Model a host mounted `state` under - the one its nearest enclosing context was set up for. */\nfunction host(state: Model) {\n for (let ctx = home(state); ctx; ctx = ctx.parent) {\n const found = HOST.get(ctx);\n if (found && found !== state) return found;\n }\n}\n\nfunction insert(into: Context, input: Accept) {\n const single = typeof input == \"function\" || input instanceof Model;\n const entries = Object.entries(single ? { input } : input);\n const init: Model[] = [];\n const owns: Model[] = [];\n\n for (const [K, V] of entries) {\n const owned = Model.is(V);\n\n if (!(owned || V instanceof Model)) {\n const as = single || K == String(V) ? V : `${V} (as '${K}')`;\n throw new Error(\n `Context can only include an instance or class of Model but got ${as}.`,\n );\n }\n\n const state = owned ? new V() : V.is;\n\n into.add(state, true);\n init.push(state);\n\n if (single) HOST.set(into, state);\n if (owned) owns.push(state);\n }\n\n const cleanup = CLEANUP.get(into)!;\n\n for (const state of owns) cleanup.add(() => event(state, null));\n for (const state of init) event(state);\n}\n\nexport { Context, host, join, resolver, within };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//#region src/element.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Element classes.
|
|
4
|
+
*
|
|
5
|
+
* A class whose instances a host may render as elements declares itself here -
|
|
6
|
+
* `Component`, `has` lists and pools, `map` collections. Each promises the same
|
|
7
|
+
* small protocol: `is` names the instance behind a view (`Object.create(pool)`
|
|
8
|
+
* renders the pool), and `render()` returns what to show. A host seams every
|
|
9
|
+
* declared class through `elements` (re-exported by `jsx-runtime`), installing
|
|
10
|
+
* its element seam on each prototype; declarations made before the host loaded
|
|
11
|
+
* are replayed, so import order does not matter.
|
|
12
|
+
*
|
|
13
|
+
* This module imports nothing, so a collection used headless never loads the
|
|
14
|
+
* JSX runtime - and a host never imports a collection to patch it, which keeps
|
|
15
|
+
* `has` and `map` out of bundles that do not use them.
|
|
16
|
+
*/
|
|
17
|
+
/** What a declared class promises its host. */
|
|
18
|
+
interface Renderable {
|
|
19
|
+
/** The instance to render - itself, or the source behind a derived view. */
|
|
20
|
+
readonly is: Renderable;
|
|
21
|
+
render(...args: any[]): unknown;
|
|
22
|
+
}
|
|
23
|
+
type Seam = (Type: Function) => void;
|
|
24
|
+
/** Declare instances of `Type` elements, for a host to render. */
|
|
25
|
+
declare function element(Type: Function): void;
|
|
26
|
+
/**
|
|
27
|
+
* Host-side: receive every declared class, past and future, to install the
|
|
28
|
+
* host's element seam on. Pass `undefined` to stop.
|
|
29
|
+
*/
|
|
30
|
+
declare function elements(fn: Seam | undefined): void;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { type Renderable, type Seam, element, elements };
|
|
33
|
+
//# sourceMappingURL=element.d.ts.map
|
package/dist/element.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/element.ts
|
|
2
|
+
const ELEMENTS = /* @__PURE__ */ new Set();
|
|
3
|
+
let seam;
|
|
4
|
+
/** Declare instances of `Type` elements, for a host to render. */
|
|
5
|
+
function element(Type) {
|
|
6
|
+
ELEMENTS.add(Type);
|
|
7
|
+
seam?.(Type);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Host-side: receive every declared class, past and future, to install the
|
|
11
|
+
* host's element seam on. Pass `undefined` to stop.
|
|
12
|
+
*/
|
|
13
|
+
function elements(fn) {
|
|
14
|
+
seam = fn;
|
|
15
|
+
if (fn) for (const Type of ELEMENTS) fn(Type);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { element, elements };
|
|
20
|
+
//# sourceMappingURL=element.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"element.js","names":[],"sources":["../src/element.ts"],"sourcesContent":["/**\n * Element classes.\n *\n * A class whose instances a host may render as elements declares itself here -\n * `Component`, `has` lists and pools, `map` collections. Each promises the same\n * small protocol: `is` names the instance behind a view (`Object.create(pool)`\n * renders the pool), and `render()` returns what to show. A host seams every\n * declared class through `elements` (re-exported by `jsx-runtime`), installing\n * its element seam on each prototype; declarations made before the host loaded\n * are replayed, so import order does not matter.\n *\n * This module imports nothing, so a collection used headless never loads the\n * JSX runtime - and a host never imports a collection to patch it, which keeps\n * `has` and `map` out of bundles that do not use them.\n */\n\n/** What a declared class promises its host. */\ninterface Renderable {\n /** The instance to render - itself, or the source behind a derived view. */\n readonly is: Renderable;\n render(...args: any[]): unknown;\n}\n\ntype Seam = (Type: Function) => void;\n\nconst ELEMENTS = new Set<Function>();\n\nlet seam: Seam | undefined;\n\n/** Declare instances of `Type` elements, for a host to render. */\nfunction element(Type: Function) {\n ELEMENTS.add(Type);\n seam?.(Type);\n}\n\n/**\n * Host-side: receive every declared class, past and future, to install the\n * host's element seam on. Pass `undefined` to stop.\n */\nfunction elements(fn: Seam | undefined) {\n seam = fn;\n\n if (fn) for (const Type of ELEMENTS) fn(Type);\n}\n\nexport { element, elements };\nexport type { Renderable, Seam };\n"],"mappings":";AAyBA,MAAM,2BAAW,IAAI,KAAe;AAEpC,IAAI;;AAGJ,SAAS,QAAQ,MAAgB;AAC/B,UAAS,IAAI,KAAK;AAClB,QAAO,KAAK;;;;;;AAOd,SAAS,SAAS,IAAsB;AACtC,QAAO;AAEP,KAAI,GAAI,MAAK,MAAM,QAAQ,SAAU,IAAG,KAAK"}
|
package/dist/field/get.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Model } from "../state.js";
|
|
1
|
+
import { Got, Keyed, Model } from "../state.js";
|
|
2
2
|
|
|
3
3
|
//#region src/field/get.d.ts
|
|
4
4
|
declare namespace get {
|
|
@@ -14,8 +14,6 @@ declare namespace get {
|
|
|
14
14
|
declare function get<T extends Model>(Type: Model.Extends<T>, callback?: get.Callback<T>): T;
|
|
15
15
|
/**
|
|
16
16
|
* Fetches and assigns the controller which spawned this host.
|
|
17
|
-
* When parent assigns an instance via `use()` directive, it
|
|
18
|
-
* will be made available to child upon this request.
|
|
19
17
|
*
|
|
20
18
|
* @param Type - Type of controller compatible with this class.
|
|
21
19
|
* @param required - If false, property may be undefined. Otherwise will throw.
|
|
@@ -52,6 +50,16 @@ declare function get<T extends Model>(Type: Model.Extends<T>, downstream: true,
|
|
|
52
50
|
* @param required - If false, returns undefined if no match found.
|
|
53
51
|
*/
|
|
54
52
|
declare function get<T extends Model>(Type: Model.Extends<T>, downstream: true, required: false): T | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Fetches through `Type.get(...args)` as this field is defined, with this
|
|
55
|
+
* Model as origin. Resolves once; a thrown Promise suspends the field and
|
|
56
|
+
* retries once settled. Applies where the first argument is neither boolean
|
|
57
|
+
* nor function - a key for a class overriding static `get`.
|
|
58
|
+
*
|
|
59
|
+
* @param Type - Class to fetch from.
|
|
60
|
+
* @param args - Arguments for `Type.get`.
|
|
61
|
+
*/
|
|
62
|
+
declare function get<C extends Model.Extends>(Type: C, ...args: Keyed<C>): Got<C>;
|
|
55
63
|
//#endregion
|
|
56
64
|
export { get };
|
|
57
65
|
//# sourceMappingURL=get.d.ts.map
|
package/dist/field/get.js
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
import { capture, listener, observer } from "../observable.js";
|
|
1
|
+
import { capture, listener, observer, watch } from "../observable.js";
|
|
2
2
|
import { Model, STORE, children, owned, parent, update } from "../state.js";
|
|
3
|
-
import { Context } from "../context.js";
|
|
3
|
+
import { Context, within } from "../context.js";
|
|
4
4
|
import { def } from "./def.js";
|
|
5
5
|
|
|
6
6
|
//#region src/field/get.ts
|
|
7
|
-
function get(Type, arg1,
|
|
8
|
-
|
|
7
|
+
function get(Type, arg1, ...args) {
|
|
8
|
+
if (arg1 === true) return below(Type, args[0]);
|
|
9
|
+
if (arg1 === void 0 || typeof arg1 == "boolean" || typeof arg1 == "function") return above(Type, arg1);
|
|
10
|
+
return def((key, subject) => {
|
|
11
|
+
const resolve = () => update(subject, key, within(subject, () => Type.get(arg1, ...args)));
|
|
12
|
+
try {
|
|
13
|
+
resolve();
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (!(error instanceof Promise)) throw error;
|
|
16
|
+
watch(subject, () => {
|
|
17
|
+
resolve();
|
|
18
|
+
return null;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
get: true,
|
|
23
|
+
enumerable: false
|
|
24
|
+
};
|
|
25
|
+
});
|
|
9
26
|
}
|
|
10
27
|
function above(Type, argument) {
|
|
11
28
|
return def((key, subject) => {
|