@expressive/mvc 0.88.0 → 0.90.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 CHANGED
@@ -11,9 +11,9 @@
11
11
 
12
12
  ---
13
13
 
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.
14
+ The core of [Expressive MVC](https://github.com/gabeklein/expressive-mvc): reactive primitives built around plain classes, with no framework dependency. Provides the `Model` class, 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) 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.
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 `Model`, `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
 
@@ -21,14 +21,14 @@ Installed alone, it serves host-agnostic code - a shared domain package, a Node
21
21
  npm install @expressive/mvc
22
22
  ```
23
23
 
24
- ## State
24
+ ## Model
25
25
 
26
- A `State` is a class whose fields are reactive - read to subscribe, assign to update. Getters are cached computed values.
26
+ A `Model` is a class whose fields are reactive - read to subscribe, assign to update. Getters are cached computed values.
27
27
 
28
28
  ```ts
29
- import { State } from '@expressive/mvc';
29
+ import { Model } from '@expressive/mvc';
30
30
 
31
- class Counter extends State {
31
+ class Counter extends Model {
32
32
  count = 0;
33
33
  increment = () => this.count++;
34
34
 
@@ -47,7 +47,7 @@ counter.get(({ count }) => {
47
47
  counter.increment(); // -> "count is 1"
48
48
  ```
49
49
 
50
- Models run and test anywhere - no renderer required.
50
+ Instances run and test anywhere - no renderer required.
51
51
 
52
52
  ## Instructions
53
53
 
@@ -57,14 +57,14 @@ Field initializers that change how a property behaves:
57
57
  |---|---|
58
58
  | `ref()` | a mutable reference (e.g. a DOM node) that's still reactive |
59
59
  | `set()` | computed values, smart setters, side-effects on assignment |
60
- | `get()` | dependency injection - pull another `State` from context |
60
+ | `get()` | dependency injection - pull another `Model` from context |
61
61
  | `map()` | a shallow-reactive Map |
62
62
  | `has()` | an owned reactive collection - an ordered list or a spawned pool |
63
63
 
64
64
  ```ts
65
- import { State, set, has, map } from '@expressive/mvc';
65
+ import { Model, set, has, map } from '@expressive/mvc';
66
66
 
67
- class Cart extends State {
67
+ class Cart extends Model {
68
68
  items = has<Item>(); // reactive collection
69
69
  products = map<string, Product>(); // reactive keyed collection
70
70
  coupon = set('', code => apply(code)); // side-effect on assignment
@@ -74,7 +74,7 @@ class Cart extends State {
74
74
  ## Lifecycle
75
75
 
76
76
  ```ts
77
- class Timer extends State {
77
+ class Timer extends Model {
78
78
  seconds = 0;
79
79
 
80
80
  new() { // runs once on activation
@@ -89,22 +89,22 @@ timer.set(null); // destroy - runs cleanup, freezes state
89
89
 
90
90
  ## Context & composition
91
91
 
92
- States find each other through context, and compose by holding one another:
92
+ Instances find each other through context, and compose by holding one another:
93
93
 
94
94
  ```ts
95
- import { State, get } from '@expressive/mvc';
95
+ import { Model, get } from '@expressive/mvc';
96
96
 
97
- class Session extends State {
97
+ class Session extends Model {
98
98
  user = 'guest';
99
99
  }
100
100
 
101
- class Profile extends State {
101
+ class Profile extends Model {
102
102
  session = get(Session); // injected from the nearest provider
103
- address = new Address(); // nested state - its changes bubble up
103
+ address = new Address(); // nested instance - its changes bubble up
104
104
  }
105
105
  ```
106
106
 
107
- An adapter (e.g. [`@expressive/react`](https://www.npmjs.com/package/@expressive/react)) supplies the provider that places a `State` in a tree; `get()` pulls it back out.
107
+ An adapter (e.g. [`@expressive/react`](https://www.npmjs.com/package/@expressive/react)) supplies the provider that places a `Model` in a tree; `get()` pulls it back out.
108
108
 
109
109
  ## Events
110
110
 
@@ -130,7 +130,7 @@ off(); // stop listening
130
130
  }
131
131
  ```
132
132
 
133
- `Component` - a `State` that renders - lives here as the agnostic base. For using it in an app (rendering, props, subcomponents, suspense), see **[`@expressive/react`](https://www.npmjs.com/package/@expressive/react)**.
133
+ `Component` - a `Model` usable in JSX - lives here as the agnostic base. For using it in an app (rendering, props, subcomponents, suspense), see **[`@expressive/react`](https://www.npmjs.com/package/@expressive/react)**.
134
134
 
135
135
  ---
136
136
 
@@ -1,21 +1,21 @@
1
- import { State } from "./state.js";
1
+ import { Model } from "./state.js";
2
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> = {
8
+ type ForProps<T extends Model> = {
9
9
  children?: Component.Node;
10
10
  fallback?: Component.Node | false;
11
11
  catch?: (error: Error, instance: Component) => Promise<void> | void;
12
12
  } & ({
13
- for: State.Extends<T>;
13
+ for: Model.Extends<T>;
14
14
  is?: (instance: T) => void;
15
15
  } | {
16
16
  for: T | undefined;
17
17
  is?: never;
18
- }) & { [K in Exclude<keyof T, keyof State> & Acceptable<T>]?: T[K] };
18
+ }) & { [K in Exclude<keyof T, keyof Model> & Acceptable<T>]?: T[K] };
19
19
  declare namespace Component {
20
20
  /**
21
21
  * Host element type produced by `Component.render`. Delegates to the
@@ -49,22 +49,24 @@ declare namespace Component {
49
49
  */
50
50
  catch?(error: Error, instance: T): Promise<void> | void;
51
51
  }
52
- type StateProps<T extends State> = { [K in Exclude<keyof T, keyof Component> & Acceptable<T>]?: T[K] };
52
+ type ModelProps<T extends Model> = { [K in Exclude<keyof T, keyof Component> & Acceptable<T>]?: T[K] };
53
+ /** @deprecated Renamed to `ModelProps`. */
54
+ type StateProps<T extends Model> = ModelProps<T>;
53
55
  type RenderProps<T> = [T] extends [(props: infer P) => any] ? [keyof NonNullable<P>] extends [never] ? {
54
56
  children?: Component.Node;
55
57
  } : NonNullable<P> : {
56
58
  children?: Component.Node;
57
59
  };
58
- type Props<T extends Component<any>> = StateProps<T> & BaseProps<T> & RenderProps<T['render']>;
60
+ type Props<T extends Component<any>> = ModelProps<T> & BaseProps<T> & RenderProps<T['render']>;
59
61
  }
60
- declare class Component<P = unknown> extends State {
62
+ declare class Component<P = unknown> extends Model {
61
63
  /**
62
64
  * All JSX attributes passed to this component.
63
65
  * Includes state-derived props, render props, and built-in props like `is` and `fallback`.
64
66
  *
65
67
  * Will incorperate extra props you declare as props parameter in `render` method.
66
68
  */
67
- readonly props: [P] extends [State] ? ForProps<P> : Component.Props<this>;
69
+ readonly props: [P] extends [Model] ? ForProps<P> : Component.Props<this>;
68
70
  /** Stable identity used when this instance is rendered in a collection. */
69
71
  readonly key: string;
70
72
  /**
@@ -86,8 +88,6 @@ declare class Component<P = unknown> extends State {
86
88
  * props from JSX, beyond those merged to state properties; declare its shape
87
89
  * via `props = {} as { ... }`. Without a parameter (the default below),
88
90
  * children pass through.
89
- *
90
- * The constructor installs a composed `render` per instance.
91
91
  */
92
92
  render(props?: {}): Component.Node;
93
93
  /**
@@ -101,14 +101,6 @@ declare class Component<P = unknown> extends State {
101
101
  */
102
102
  catch?(error: Error, instance: this): Promise<void> | void;
103
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;
112
104
  //#endregion
113
- export { Component, compose, rechain };
105
+ export { Component };
114
106
  //# sourceMappingURL=component.d.ts.map
package/dist/component.js CHANGED
@@ -1,12 +1,10 @@
1
- import { State, adopt, trailing, unbind } from "./state.js";
1
+ import { Model, adopt, trailing } 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
- /** Per-class composed content render. */
8
- let CHAIN = /* @__PURE__ */ new WeakMap();
9
- var Component = class Component extends State {
7
+ var Component = class Component extends Model {
10
8
  /**
11
9
  * Content to display while this component or its children are suspended.
12
10
  * Will cover suspense by pending properties in `this.render` as well.
@@ -50,11 +48,6 @@ var Component = class Component extends State {
50
48
  this.set("props", () => {
51
49
  this.set(merge(this.props));
52
50
  });
53
- Object.defineProperty(this, "render", {
54
- writable: true,
55
- configurable: true,
56
- value: compose
57
- });
58
51
  }
59
52
  /**
60
53
  * Output for this component. Override to define custom JSX.
@@ -64,8 +57,6 @@ var Component = class Component extends State {
64
57
  * props from JSX, beyond those merged to state properties; declare its shape
65
58
  * via `props = {} as { ... }`. Without a parameter (the default below),
66
59
  * children pass through.
67
- *
68
- * The constructor installs a composed `render` per instance.
69
60
  */
70
61
  render(props) {
71
62
  const { children } = props || this.props;
@@ -120,7 +111,7 @@ function provide(self) {
120
111
  if (next !== input) {
121
112
  input = next;
122
113
  unmount();
123
- if (State.is(next)) {
114
+ if (Model.is(next)) {
124
115
  adopt(self, "for", target = new next(rest));
125
116
  owned = target;
126
117
  is?.(target);
@@ -128,51 +119,14 @@ function provide(self) {
128
119
  return;
129
120
  }
130
121
  owned = void 0;
131
- adopt(self, "for", target = next instanceof State ? next : void 0);
122
+ adopt(self, "for", target = next instanceof Model ? next : void 0);
132
123
  }
133
124
  target?.set(rest);
134
125
  }
135
126
  sync();
136
127
  self.set("props", sync);
137
128
  }
138
- /**
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.
142
- */
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;
150
- }
151
- if (!render) render = method(Component);
152
- CHAIN.set(type, render);
153
- }
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);
159
- }
160
- function wrap(outer, inner) {
161
- return function(props) {
162
- const self = this;
163
- return outer.call(self, {
164
- ...props,
165
- get children() {
166
- return inner.call(self, props);
167
- }
168
- });
169
- };
170
- }
171
- /** Drop composed renders, so each is rebuilt from current prototypes. */
172
- function rechain() {
173
- CHAIN = /* @__PURE__ */ new WeakMap();
174
- }
175
129
 
176
130
  //#endregion
177
- export { Component, compose, rechain };
131
+ export { Component };
178
132
  //# sourceMappingURL=component.js.map
@@ -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, 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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"}
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"}
package/dist/context.d.ts CHANGED
@@ -1,56 +1,43 @@
1
- import { State } from "./state.js";
1
+ import { Model } from "./state.js";
2
2
 
3
3
  //#region src/context.d.ts
4
- type Accept<T extends State = State> = T | State.Type<T> | Record<string | number, T | State.Type<T>>;
5
- type Expect<T extends State = State> = (state: T, downstream: boolean) => (() => void) | void;
4
+ type Accept<T extends Model = Model> = T | Model.Type<T> | Record<string | number, T | Model.Type<T>>;
5
+ type Expect<T extends Model = Model> = (state: T, downstream: boolean) => (() => void) | void;
6
6
  declare namespace Context {
7
7
  export { Accept, Expect };
8
8
  }
9
9
  declare class Context {
10
10
  static get root(): Context;
11
- /** Get the context for a State. Adapters may override to provide framework context. */
12
- static get(state?: State): Context;
11
+ /** Get the context for a Model. Adapters may override to provide framework context. */
12
+ static get(state?: Model): Context;
13
13
  id: string;
14
14
  parent?: Context;
15
15
  scope: Set<Context>;
16
- consume: Map<State.Extends<State>, Set<[Expect<State>, boolean | undefined]> | null>;
17
- provide: Map<State.Extends<State>, Set<[State, boolean]> | null>;
18
- protected inputs: Record<string | number, State | State.Extends>;
19
- private cleanup;
16
+ consume: Map<Model.Extends<Model>, Set<[Expect<Model>, boolean | undefined]> | null>;
17
+ provide: Map<Model.Extends<Model>, Set<[Model, boolean]> | null>;
20
18
  constructor(arg?: Context | Accept);
21
19
  private traverse;
22
20
  private register;
23
21
  /** Find specified type upstream. Throws if not found. */
24
- get<T extends State>(Type: State.Extends<T>, required?: true): T;
22
+ get<T extends Model>(Type: Model.Extends<T>, required?: true): T;
25
23
  /** Find specified type upstream. Returns undefined if not found. */
26
- get<T extends State>(Type: State.Extends<T>, required?: boolean): T | undefined;
24
+ get<T extends Model>(Type: Model.Extends<T>, required?: boolean): T | undefined;
27
25
  /** Subscribe to a type becoming available in context. */
28
- get<T extends State>(Type: State.Extends<T>, callback: Expect<T>, downstream?: boolean): () => void;
29
- /** Lookup excluding a specific instance (used by State.prototype.get for upstream-of-self semantics). */
30
- get<T extends State>(Type: State.Extends<T>, arg: boolean | Expect<T> | undefined, downstream: boolean | undefined, skip: State): T | undefined | (() => void);
26
+ get<T extends Model>(Type: Model.Extends<T>, callback: Expect<T>, downstream?: boolean): () => void;
27
+ /** 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: Model): T | undefined | (() => void);
29
+ add(I: Model, explicit?: boolean): () => void;
31
30
  /**
32
- * Register one or more States to this context.
31
+ * Create a child context, optionally registering Models to it.
33
32
  *
34
- * Context will add or remove States as needed to keep with provided input.
35
- *
36
- * @param inputs State, State class, or map of States / State classes to register.
37
- * @param forEach Optional callback to run for each State registered. Receives
38
- * whether this context created the State - and so will destroy it - as
39
- * opposed to it having been provided already active. May return a callback
40
- * to run before that entry is dropped.
41
- */
42
- set<T extends State>(inputs: Accept<T>, forEach?: (state: T, owned: boolean) => (() => void) | void): this;
43
- add(I: State, explicit?: boolean): () => void;
44
- /**
45
- * Create a child context, optionally registering one or more States to it.
46
- *
47
- * @param inputs State, State class, or map of States / State classes to register.
33
+ * @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.
34
+ * @param cleanup Called once when the context pops, before its Models are removed.
48
35
  */
49
- push(inputs?: Accept): Context;
36
+ push(input?: Accept, cleanup?: () => void): Context;
50
37
  /**
51
- * Remove all States from this context.
38
+ * Remove all Models from this context.
52
39
  *
53
- * Will also run any cleanup callbacks registered when States were added.
40
+ * Will also run any cleanup callbacks registered when Models were added.
54
41
  */
55
42
  pop(): void;
56
43
  }
@@ -58,9 +45,9 @@ declare class Context {
58
45
  * Add `value` to the context `state` was added to. A context-less `state` holds
59
46
  * it instead - every context adding `state` later adds its held children too.
60
47
  */
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;
48
+ declare function join(state: Model, value: Model): () => void;
49
+ /** The Model a host mounted `state` under - the one its nearest enclosing context was set up for. */
50
+ declare function host(state: Model): Model | undefined;
64
51
  //#endregion
65
52
  export { Context, host, join };
66
53
  //# sourceMappingURL=context.d.ts.map
package/dist/context.js CHANGED
@@ -1,16 +1,17 @@
1
1
  import { event, listener } from "./observable.js";
2
- import { State, uid } from "./state.js";
2
+ import { Model, 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
7
  const HOST = /* @__PURE__ */ new WeakMap();
8
+ const CLEANUP = /* @__PURE__ */ new WeakMap();
8
9
  let ROOT;
9
10
  var Context = class Context {
10
11
  static get root() {
11
12
  return ROOT ??= new Context();
12
13
  }
13
- /** Get the context for a State. Adapters may override to provide framework context. */
14
+ /** Get the context for a Model. Adapters may override to provide framework context. */
14
15
  static get(state) {
15
16
  return state && LOOKUP.get(state.is) || Context.root;
16
17
  }
@@ -19,13 +20,12 @@ var Context = class Context {
19
20
  scope = /* @__PURE__ */ new Set();
20
21
  consume = /* @__PURE__ */ new Map();
21
22
  provide = /* @__PURE__ */ new Map();
22
- inputs = {};
23
- cleanup = /* @__PURE__ */ new Map();
24
23
  constructor(arg) {
24
+ CLEANUP.set(this, /* @__PURE__ */ new Set());
25
25
  if (arg instanceof Context) {
26
26
  this.parent = arg;
27
27
  arg.scope.add(this);
28
- } else if (arg) this.set(arg);
28
+ } else if (arg) insert(this, arg);
29
29
  }
30
30
  traverse(accept) {
31
31
  const queue = [...this.scope];
@@ -78,58 +78,8 @@ var Context = class Context {
78
78
  if (found === null) return null;
79
79
  if (arg !== false) throw new Error(`Could not find ${Type} in context.`);
80
80
  }
81
- /**
82
- * Register one or more States to this context.
83
- *
84
- * Context will add or remove States as needed to keep with provided input.
85
- *
86
- * @param inputs State, State class, or map of States / State classes to register.
87
- * @param forEach Optional callback to run for each State registered. Receives
88
- * whether this context created the State - and so will destroy it - as
89
- * opposed to it having been provided already active. May return a callback
90
- * to run before that entry is dropped.
91
- */
92
- set(inputs, forEach) {
93
- const init = /* @__PURE__ */ new Set();
94
- const { cleanup } = this;
95
- const single = typeof inputs == "function" || inputs instanceof State;
96
- if (typeof inputs == "function" || inputs instanceof State) inputs = { [0]: inputs };
97
- for (const K of Object.keys({
98
- ...this.inputs,
99
- ...inputs
100
- })) {
101
- const V = inputs[K];
102
- const E = this.inputs[K];
103
- if (E === V) continue;
104
- if (E) {
105
- cleanup.get(K)?.();
106
- cleanup.delete(K);
107
- }
108
- if (!V) continue;
109
- const owned = State.is(V);
110
- if (!(owned || V instanceof State)) {
111
- const as = K == "0" || K == String(V) ? V : `${V} (as '${K}')`;
112
- throw new Error(`Context can only include an instance or class of State but got ${as}.`);
113
- }
114
- const state = owned ? new V() : V.is;
115
- const remove = this.add(state, true);
116
- if (single && this !== ROOT) HOST.set(this, state);
117
- init.add(() => {
118
- event(state);
119
- const dispose = forEach?.(state, owned);
120
- cleanup.set(K, () => {
121
- if (typeof dispose == "function") dispose();
122
- remove();
123
- if (owned) event(state, null);
124
- });
125
- });
126
- }
127
- for (const i of init) i();
128
- this.inputs = inputs;
129
- return this;
130
- }
131
81
  add(I, explicit = false) {
132
- const { cleanup, provide } = this;
82
+ const { provide } = this;
133
83
  const root = this === Context.root;
134
84
  const TT = [];
135
85
  function conflict(T) {
@@ -138,7 +88,7 @@ var Context = class Context {
138
88
  for (const entry of entries) if (!entry[1] && entry[0] !== I) return entries.delete(entry);
139
89
  }
140
90
  }
141
- for (let T = I.constructor; T !== State; T = Object.getPrototypeOf(T)) if (explicit || !root || !conflict(T)) TT.push(T);
91
+ for (let T = I.constructor; T !== Model; T = Object.getPrototypeOf(T)) if (explicit || !root || !conflict(T)) TT.push(T);
142
92
  const expects = /* @__PURE__ */ new Map();
143
93
  const onDone = /* @__PURE__ */ new Set();
144
94
  for (const T of TT) onDone.add(this.register(T, [I, explicit]));
@@ -169,6 +119,7 @@ var Context = class Context {
169
119
  onDone.forEach((r) => r());
170
120
  onDone.clear();
171
121
  }
122
+ const cleanup = CLEANUP.get(this);
172
123
  const held = Array.from(HELD.get(I) || [], ([child, added]) => {
173
124
  const done = this.add(child);
174
125
  added.add(done);
@@ -181,30 +132,32 @@ var Context = class Context {
181
132
  held.forEach((done) => done());
182
133
  flush();
183
134
  }
184
- cleanup.set(remove, remove);
135
+ cleanup.add(remove);
185
136
  return remove;
186
137
  }
187
138
  /**
188
- * Create a child context, optionally registering one or more States to it.
139
+ * Create a child context, optionally registering Models to it.
189
140
  *
190
- * @param inputs State, State class, or map of States / State classes to register.
141
+ * @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.
142
+ * @param cleanup Called once when the context pops, before its Models are removed.
191
143
  */
192
- push(inputs) {
144
+ push(input, cleanup) {
193
145
  const next = new Context(this);
194
- if (inputs) next.set(inputs);
146
+ if (cleanup) CLEANUP.get(next).add(cleanup);
147
+ if (input) insert(next, input);
195
148
  return next;
196
149
  }
197
150
  /**
198
- * Remove all States from this context.
151
+ * Remove all Models from this context.
199
152
  *
200
- * Will also run any cleanup callbacks registered when States were added.
153
+ * Will also run any cleanup callbacks registered when Models were added.
201
154
  */
202
155
  pop() {
203
- this.inputs = {};
204
156
  this.scope.forEach((x) => x.pop());
205
157
  this.scope.clear();
206
- this.cleanup.forEach((cb) => cb());
207
- this.cleanup.clear();
158
+ const cleanup = CLEANUP.get(this);
159
+ cleanup.forEach((cb) => cb());
160
+ cleanup.clear();
208
161
  this.consume.clear();
209
162
  this.provide.clear();
210
163
  if (this.parent) this.parent.scope.delete(this);
@@ -231,13 +184,34 @@ function join(state, value) {
231
184
  });
232
185
  };
233
186
  }
234
- /** The State a host mounted `state` under - the one its nearest enclosing context was set up for. */
187
+ /** The Model a host mounted `state` under - the one its nearest enclosing context was set up for. */
235
188
  function host(state) {
236
189
  for (let ctx = LOOKUP.get(state); ctx; ctx = ctx.parent) {
237
190
  const found = HOST.get(ctx);
238
191
  if (found && found !== state) return found;
239
192
  }
240
193
  }
194
+ function insert(into, input) {
195
+ const single = typeof input == "function" || input instanceof Model;
196
+ const entries = Object.entries(single ? { input } : input);
197
+ const init = [];
198
+ const owns = [];
199
+ for (const [K, V] of entries) {
200
+ const owned = Model.is(V);
201
+ if (!(owned || V instanceof Model)) {
202
+ const as = single || K == String(V) ? V : `${V} (as '${K}')`;
203
+ throw new Error(`Context can only include an instance or class of Model but got ${as}.`);
204
+ }
205
+ const state = owned ? new V() : V.is;
206
+ into.add(state, true);
207
+ init.push(state);
208
+ if (single) HOST.set(into, state);
209
+ if (owned) owns.push(state);
210
+ }
211
+ const cleanup = CLEANUP.get(into);
212
+ for (const state of owns) cleanup.add(() => event(state, null));
213
+ for (const state of init) event(state);
214
+ }
241
215
 
242
216
  //#endregion
243
217
  export { Context, host, join };