@mindees/core 0.2.0 → 0.3.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.
@@ -25,7 +25,7 @@ type Component<P = Record<string, unknown>> = (props: P) => MindeesNode;
25
25
  * and patches exactly that region when its signals change (the fine-grained
26
26
  * update model, à la SolidJS). Static trees never need it; reactive UIs do.
27
27
  */
28
- type MindeesNode = MindeesElement | string | number | boolean | null | undefined | (() => MindeesNode) | MindeesNode[];
28
+ type MindeesNode = MindeesElement | KeyedRegion<any> | string | number | boolean | null | undefined | (() => MindeesNode) | MindeesNode[];
29
29
  /** The tag of an element: a host string (e.g. `"view"`) or a component function. */
30
30
  type ElementType = string | Component<never>;
31
31
  /** A virtual element: a tag, its props, and its children. */
@@ -40,6 +40,40 @@ interface MindeesElement {
40
40
  declare const ELEMENT_TYPE: unique symbol;
41
41
  /** Marker tag for a fragment (children with no wrapper host node). */
42
42
  declare const Fragment: unique symbol;
43
+ /** Brand identifying a keyed list region (reconciled by key, not full-rebuilt). */
44
+ declare const KEYED_REGION: unique symbol;
45
+ /**
46
+ * A keyed list region: a reactive item list + a per-item render function, reconciled by key
47
+ * so rows keep their identity (host node, focus, caret, scroll) across reorders instead of
48
+ * being torn down and rebuilt. This is a serializable *description* (no rendering logic); the
49
+ * renderer materializes it (`bindKeyedChild`). Build one with {@link keyedRegion}.
50
+ */
51
+ interface KeyedRegion<T = unknown> {
52
+ readonly $$keyed: typeof KEYED_REGION;
53
+ /** The items, as a reactive accessor. */
54
+ readonly each: () => readonly T[];
55
+ /** Render one row from reactive `item`/`index` accessors (consume them lazily to patch in place). */
56
+ readonly mapFn: (item: () => T, index: () => number) => MindeesNode;
57
+ /** Stable key per item (when omitted, the row is keyed by item identity). */
58
+ readonly key: ((item: T, index: number) => unknown) | undefined;
59
+ /** Rendered when the list is empty. */
60
+ readonly fallback: (() => MindeesNode) | undefined;
61
+ }
62
+ /** Options for {@link keyedRegion}. */
63
+ interface KeyedRegionOptions<T> {
64
+ /** The items, static or reactive. */
65
+ readonly each: readonly T[] | (() => readonly T[]);
66
+ /** Render one row from reactive `item`/`index` accessors. */
67
+ readonly children: (item: () => T, index: () => number) => MindeesNode;
68
+ /** Stable key per item (defaults to item identity). */
69
+ readonly key?: (item: T, index: number) => unknown;
70
+ /** Rendered when the list is empty. */
71
+ readonly fallback?: () => MindeesNode;
72
+ }
73
+ /** Build a {@link KeyedRegion} — a keyed, identity-preserving list node. */
74
+ declare function keyedRegion<T>(options: KeyedRegionOptions<T>): KeyedRegion<T>;
75
+ /** Type guard: is `value` a {@link KeyedRegion}? */
76
+ declare function isKeyedRegion(value: unknown): value is KeyedRegion;
43
77
  interface PropsWithKey {
44
78
  key?: string | number | null;
45
79
  children?: MindeesNode;
@@ -108,5 +142,5 @@ declare function renderComponent<P>(component: Component<P>, props: P): {
108
142
  /** Whether code is currently running inside a reactive ownership scope. */
109
143
  declare function hasOwner(): boolean;
110
144
  //#endregion
111
- export { Component, Context, ContextProvider, ELEMENT_TYPE, ElementType, Fragment, MindeesElement, MindeesNode, SelectorEquals, createContext, createElement, createProvider, hasOwner, isElement, renderComponent };
145
+ export { Component, Context, ContextProvider, ELEMENT_TYPE, ElementType, Fragment, KEYED_REGION, KeyedRegion, KeyedRegionOptions, MindeesElement, MindeesNode, SelectorEquals, createContext, createElement, createProvider, hasOwner, isElement, isKeyedRegion, keyedRegion, renderComponent };
112
146
  //# sourceMappingURL=component.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"component.d.ts","names":[],"sources":["../../src/component/component.ts"],"mappings":";;AAwBA;;;;;;;;;;;;;;AAA8E;AAU9E;AAAA,KAVY,SAAA,KAAc,MAAA,sBAA4B,KAAA,EAAO,CAAA,KAAM,WAAA;;;;;;;;;KAUvD,WAAA,GACR,cAAA,yDAMO,WAAA,IACP,WAAA;;KAGQ,WAAA,YAAuB,SAAS;AAA5C;AAAA,UAGiB,cAAA;EAAA,SACN,QAAA,SAAiB,YAAA;EAAA,SACjB,IAAA,EAAM,WAAA;EAAA,SACN,KAAA,EAAO,QAAA,CAAS,MAAA;EAAA,SAChB,QAAA,WAAmB,WAAA;EAAA,SACnB,GAAA;AAAA;;cAIE,YAAA;;cAGA,QAAA;AAAA,UAEH,YAAA;EACR,GAAA;EACA,QAAA,GAAW,WAAW;AAAA;;;;;;;;iBAUR,aAAA,CACd,IAAA,EAAM,WAAA,UAAqB,QAAA,EAC3B,KAAA,IAAS,MAAA,oBAA0B,YAAA,aAChC,QAAA,EAAU,WAAA,KACZ,cAAA;;iBAea,SAAA,CAAU,KAAA,YAAiB,KAAA,IAAS,cAAc;AAxCpD;AAAA,KAqDF,cAAA,OAAqB,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAC;;UAG1B,OAAA;EApDuD;EAAA,SAsD7D,EAAA;EAnDE;EAAA,SAqDF,YAAA,EAAc,CAAC;AAAA;;AArD2C;AAAA;;UA4DpD,eAAA;EAxDO;EA0DtB,GAAA,CAAI,KAAA,EAAO,CAAA;EA1DX;EA4DA,IAAA,IAAQ,CAAA;EA5Dc;AAAA;AAUxB;;;EAwDE,MAAA,IAAU,QAAA,GAAW,KAAA,EAAO,CAAA,KAAM,CAAA,EAAG,MAAA,GAAS,cAAA,CAAe,CAAA,UAAW,CAAA;AAAA;;iBAI1D,aAAA,IAAiB,YAAA,EAAc,CAAA,GAAI,OAAA,CAAQ,CAAA;;;;;;;;;;;iBAc3C,cAAA,IAAkB,OAAA,EAAS,OAAA,CAAQ,CAAA,GAAI,OAAA,GAAU,CAAA,GAAI,eAAA,CAAgB,CAAA;;;;AAtEpE;AAejB;;;;;iBAmFgB,eAAA,IACd,SAAA,EAAW,SAAA,CAAU,CAAA,GACrB,KAAA,EAAO,CAAA;EACJ,IAAA,EAAM,WAAA;EAAa,OAAA;AAAA;AAzExB;AAAA,iBA8FgB,QAAA"}
1
+ {"version":3,"file":"component.d.ts","names":[],"sources":["../../src/component/component.ts"],"mappings":";;AAwBA;;;;;;;;;;;;;;AAA8E;AAU9E;AAAA,KAVY,SAAA,KAAc,MAAA,sBAA4B,KAAA,EAAO,CAAA,KAAM,WAAA;;;;;;;;;KAUvD,WAAA,GACR,cAAA,GAKA,WAAA,8DAMO,WAAA,IACP,WAAA;;KAGQ,WAAA,YAAuB,SAAS;;UAG3B,cAAA;EAAA,SACN,QAAA,SAAiB,YAAA;EAAA,SACjB,IAAA,EAAM,WAAA;EAAA,SACN,KAAA,EAAO,QAAA,CAAS,MAAA;EAAA,SAChB,QAAA,WAAmB,WAAA;EAAA,SACnB,GAAA;AAAA;;cAIE,YAAA;;cAGA,QAAA;;cAGA,YAAA;;;;;;;UAQI,WAAA;EAAA,SACN,OAAA,SAAgB,YAAA;EArBT;EAAA,SAuBP,IAAA,iBAAqB,CAAA;EAtBrB;EAAA,SAwBA,KAAA,GAAQ,IAAA,QAAY,CAAA,EAAG,KAAA,mBAAwB,WAAA;EAvB/C;EAAA,SAyBA,GAAA,IAAO,IAAA,EAAM,CAAA,EAAG,KAAA;EAzBb;EAAA,SA2BH,QAAA,SAAiB,WAAA;AAAA;;UAIX,kBAAA;EA3BuD;EAAA,SA6B7D,IAAA,WAAe,CAAA,qBAAsB,CAAA;EA1BqB;EAAA,SA4B1D,QAAA,GAAW,IAAA,QAAY,CAAA,EAAG,KAAA,mBAAwB,WAAA;EA5BQ;EAAA,SA8B1D,GAAA,IAAO,IAAA,EAAM,CAAA,EAAG,KAAA;EA3Bd;EAAA,SA6BF,QAAA,SAAiB,WAAA;AAAA;;iBAIZ,WAAA,IAAe,OAAA,EAAS,kBAAA,CAAmB,CAAA,IAAK,WAAA,CAAY,CAAA;AAzB5E;AAAA,iBAsCgB,aAAA,CAAc,KAAA,YAAiB,KAAA,IAAS,WAAW;AAAA,UAQzD,YAAA;EACR,GAAA;EACA,QAAA,GAAW,WAAW;AAAA;;;;;;;;iBAUR,aAAA,CACd,IAAA,EAAM,WAAA,UAAqB,QAAA,EAC3B,KAAA,IAAS,MAAA,oBAA0B,YAAA,aAChC,QAAA,EAAU,WAAA,KACZ,cAAA;;iBAea,SAAA,CAAU,KAAA,YAAiB,KAAA,IAAS,cAAc;;KAatD,cAAA,OAAqB,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAC;;UAG1B,OAAA;EAxFyC;EAAA,SA0F/C,EAAA;EAxFa;EAAA,SA0Fb,YAAA,EAAc,CAAC;AAAA;;;;AAxFa;UA+FtB,eAAA;EA3FkB;EA6FjC,GAAA,CAAI,KAAA,EAAO,CAAA;EA3Fa;EA6FxB,IAAA,IAAQ,CAAA;EA3FwB;;;;;EAiGhC,MAAA,IAAU,QAAA,GAAW,KAAA,EAAO,CAAA,KAAM,CAAA,EAAG,MAAA,GAAS,cAAA,CAAe,CAAA,UAAW,CAAA;AAAA;;iBAI1D,aAAA,IAAiB,YAAA,EAAc,CAAA,GAAI,OAAA,CAAQ,CAAA;;;;;;;;;;;iBAc3C,cAAA,IAAkB,OAAA,EAAS,OAAA,CAAQ,CAAA,GAAI,OAAA,GAAU,CAAA,GAAI,eAAA,CAAgB,CAAA;;;AA/G9C;AAIvC;;;;;;iBAuIgB,eAAA,IACd,SAAA,EAAW,SAAA,CAAU,CAAA,GACrB,KAAA,EAAO,CAAA;EACJ,IAAA,EAAM,WAAA;EAAa,OAAA;AAAA;;iBAqBR,QAAA"}
@@ -20,6 +20,22 @@ import { computed, createRoot, getOwner, signal } from "../reactive/reactive.js"
20
20
  const ELEMENT_TYPE = Symbol.for("mindees.element");
21
21
  /** Marker tag for a fragment (children with no wrapper host node). */
22
22
  const Fragment = Symbol.for("mindees.fragment");
23
+ /** Brand identifying a keyed list region (reconciled by key, not full-rebuilt). */
24
+ const KEYED_REGION = Symbol.for("mindees.keyed-region");
25
+ /** Build a {@link KeyedRegion} — a keyed, identity-preserving list node. */
26
+ function keyedRegion(options) {
27
+ return {
28
+ $$keyed: KEYED_REGION,
29
+ each: typeof options.each === "function" ? options.each : () => options.each,
30
+ mapFn: options.children,
31
+ key: options.key,
32
+ fallback: options.fallback
33
+ };
34
+ }
35
+ /** Type guard: is `value` a {@link KeyedRegion}? */
36
+ function isKeyedRegion(value) {
37
+ return typeof value === "object" && value !== null && value.$$keyed === KEYED_REGION;
38
+ }
23
39
  /**
24
40
  * Create a virtual element. `type` is a host-component string or a component
25
41
  * function; `Fragment` groups children without a wrapper.
@@ -102,6 +118,6 @@ function hasOwner() {
102
118
  return getOwner() !== null;
103
119
  }
104
120
  //#endregion
105
- export { ELEMENT_TYPE, Fragment, createContext, createElement, createProvider, hasOwner, isElement, renderComponent };
121
+ export { ELEMENT_TYPE, Fragment, KEYED_REGION, createContext, createElement, createProvider, hasOwner, isElement, isKeyedRegion, keyedRegion, renderComponent };
106
122
 
107
123
  //# sourceMappingURL=component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"component.js","names":[],"sources":["../../src/component/component.ts"],"sourcesContent":["/**\n * MindeesNative component model — a minimal, renderer-agnostic element tree plus\n * **selector-based, re-render-isolated context**.\n *\n * The element tree (`createElement` / `Fragment`) is a plain data structure: a\n * component is a function of `props` returning elements. It carries no rendering\n * logic itself — the Helix renderer (Phase 3) turns this tree into host nodes.\n *\n * The context system is the important primitive: a `createContext` value is read\n * through a **selector**, and a consumer only re-runs when its *selected slice*\n * actually changes — not on every context update. This is the re-render\n * isolation the Quantum Router (Phase 6) builds on. It is implemented on the\n * Phase 1 signals, so selection participates in normal reactivity.\n *\n * @module\n */\n\nimport { computed, createRoot, getOwner, signal } from '../reactive'\n\n// ---------------------------------------------------------------------------\n// Element tree\n// ---------------------------------------------------------------------------\n\n/** A component: a function from props to a renderable node. */\nexport type Component<P = Record<string, unknown>> = (props: P) => MindeesNode\n\n/**\n * Anything that can appear in the tree.\n *\n * Includes an **accessor** form `() => MindeesNode`: a function child (or\n * function prop value) is a *reactive region* — the renderer subscribes to it\n * and patches exactly that region when its signals change (the fine-grained\n * update model, à la SolidJS). Static trees never need it; reactive UIs do.\n */\nexport type MindeesNode =\n | MindeesElement\n | string\n | number\n | boolean\n | null\n | undefined\n | (() => MindeesNode)\n | MindeesNode[]\n\n/** The tag of an element: a host string (e.g. `\"view\"`) or a component function. */\nexport type ElementType = string | Component<never>\n\n/** A virtual element: a tag, its props, and its children. */\nexport interface MindeesElement {\n readonly $$typeof: typeof ELEMENT_TYPE\n readonly type: ElementType\n readonly props: Readonly<Record<string, unknown>>\n readonly children: readonly MindeesNode[]\n readonly key: string | number | null\n}\n\n/** Brand so a renderer can reliably distinguish elements from plain objects. */\nexport const ELEMENT_TYPE: unique symbol = Symbol.for('mindees.element')\n\n/** Marker tag for a fragment (children with no wrapper host node). */\nexport const Fragment: unique symbol = Symbol.for('mindees.fragment')\n\ninterface PropsWithKey {\n key?: string | number | null\n children?: MindeesNode\n}\n\n/**\n * Create a virtual element. `type` is a host-component string or a component\n * function; `Fragment` groups children without a wrapper.\n *\n * @example\n * createElement('view', { id: 'root' }, createElement('text', null, 'hi'))\n */\nexport function createElement(\n type: ElementType | typeof Fragment,\n props?: (Record<string, unknown> & PropsWithKey) | null,\n ...children: MindeesNode[]\n): MindeesElement {\n const { key = null, children: propsChildren, ...rest } = props ?? {}\n // Children passed as args win over a `children` prop; otherwise fall back to it.\n const resolved: MindeesNode[] =\n children.length > 0 ? children : propsChildren !== undefined ? [propsChildren] : []\n return {\n $$typeof: ELEMENT_TYPE,\n type: type as ElementType,\n props: Object.freeze({ ...rest }),\n children: Object.freeze(resolved),\n key,\n }\n}\n\n/** Type guard: is `value` a MindeesElement? */\nexport function isElement(value: unknown): value is MindeesElement {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { $$typeof?: unknown }).$$typeof === ELEMENT_TYPE\n )\n}\n\n// ---------------------------------------------------------------------------\n// Selector-based, re-render-isolated context\n// ---------------------------------------------------------------------------\n\n/** Equality used by context selectors. Defaults to `Object.is`. */\nexport type SelectorEquals<S> = (a: S, b: S) => boolean\n\n/** A context handle created by {@link createContext}. */\nexport interface Context<T> {\n /** Provide a value to a subtree (conceptually); returns a scoped reader set. */\n readonly id: symbol\n /** The default value used when no provider is present. */\n readonly defaultValue: T\n}\n\n/**\n * A live provider instance: holds the current value and lets consumers subscribe\n * to a derived slice with re-render isolation.\n */\nexport interface ContextProvider<T> {\n /** Replace the provided value (notifies only consumers whose slice changed). */\n set(value: T): void\n /** Read the current value without subscribing. */\n peek(): T\n /**\n * Subscribe to a selected slice. The returned accessor is a memo that only\n * changes — and thus only re-runs its observers — when `selector(value)`\n * changes under `equals` (default `Object.is`).\n */\n select<S>(selector: (value: T) => S, equals?: SelectorEquals<S>): () => S\n}\n\n/** Create a context with a default value. */\nexport function createContext<T>(defaultValue: T): Context<T> {\n return { id: Symbol('mindees.context'), defaultValue }\n}\n\n/**\n * Create a provider instance for `context`. Built on a signal, so selected\n * slices are memos: a consumer that selects `c => c.user.name` does not re-run\n * when an unrelated field changes.\n *\n * @example\n * const Theme = createContext({ mode: 'light', accent: 'blue' })\n * const p = createProvider(Theme, { mode: 'light', accent: 'blue' })\n * const mode = p.select((t) => t.mode) // only re-runs when `mode` changes\n */\nexport function createProvider<T>(context: Context<T>, initial?: T): ContextProvider<T> {\n // equals:false at the root — every set() re-evaluates selectors, but each\n // selector memo applies its own equality to isolate re-renders.\n const source = signal<T>(initial ?? context.defaultValue, { equals: false })\n return {\n set: (value: T) => {\n source.set(value)\n },\n peek: () => source.peek(),\n select<S>(selector: (value: T) => S, equals: SelectorEquals<S> = Object.is): () => S {\n return computed(() => selector(source()), { equals })\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Rendering a component subtree under an owner (so it can be disposed)\n// ---------------------------------------------------------------------------\n\n/**\n * Invoke a component within a reactive ownership scope, so every effect, memo,\n * and `onCleanup` it registers is torn down together when `dispose` is called.\n * Returns the produced node and that disposer.\n *\n * Built on the Phase 1 {@link createRoot}, so disposal reuses the same\n * leak-free teardown the reactivity tests cover. This is a renderer building\n * block; it does not touch any host.\n */\nexport function renderComponent<P>(\n component: Component<P>,\n props: P,\n): { node: MindeesNode; dispose: () => void } {\n let node!: MindeesNode\n // Capture the disposer eagerly: createRoot hands it to the callback synchronously\n // *before* the component runs, so if the component throws mid-render we can still\n // tear down anything it registered before the throw (effects, subscriptions,\n // timers). Without this, createRoot re-throws without disposing and the caller\n // never receives a disposer — the partial scope would leak forever.\n let dispose!: () => void\n try {\n createRoot((d) => {\n dispose = d\n node = component(props)\n })\n } catch (error) {\n dispose?.()\n throw error\n }\n return { node, dispose }\n}\n\n/** Whether code is currently running inside a reactive ownership scope. */\nexport function hasOwner(): boolean {\n return getOwner() !== null\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAyDA,MAAa,eAA8B,OAAO,IAAI,iBAAiB;;AAGvE,MAAa,WAA0B,OAAO,IAAI,kBAAkB;;;;;;;;AAcpE,SAAgB,cACd,MACA,OACA,GAAG,UACa;CAChB,MAAM,EAAE,MAAM,MAAM,UAAU,eAAe,GAAG,SAAS,SAAS,CAAC;CAEnE,MAAM,WACJ,SAAS,SAAS,IAAI,WAAW,kBAAkB,KAAA,IAAY,CAAC,aAAa,IAAI,CAAC;CACpF,OAAO;EACL,UAAU;EACJ;EACN,OAAO,OAAO,OAAO,EAAE,GAAG,KAAK,CAAC;EAChC,UAAU,OAAO,OAAO,QAAQ;EAChC;CACF;AACF;;AAGA,SAAgB,UAAU,OAAyC;CACjE,OACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAiC,aAAa;AAEnD;;AAmCA,SAAgB,cAAiB,cAA6B;CAC5D,OAAO;EAAE,IAAI,OAAO,iBAAiB;EAAG;CAAa;AACvD;;;;;;;;;;;AAYA,SAAgB,eAAkB,SAAqB,SAAiC;CAGtF,MAAM,SAAS,OAAU,WAAW,QAAQ,cAAc,EAAE,QAAQ,MAAM,CAAC;CAC3E,OAAO;EACL,MAAM,UAAa;GACjB,OAAO,IAAI,KAAK;EAClB;EACA,YAAY,OAAO,KAAK;EACxB,OAAU,UAA2B,SAA4B,OAAO,IAAa;GACnF,OAAO,eAAe,SAAS,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC;EACtD;CACF;AACF;;;;;;;;;;AAeA,SAAgB,gBACd,WACA,OAC4C;CAC5C,IAAI;CAMJ,IAAI;CACJ,IAAI;EACF,YAAY,MAAM;GAChB,UAAU;GACV,OAAO,UAAU,KAAK;EACxB,CAAC;CACH,SAAS,OAAO;EACd,UAAU;EACV,MAAM;CACR;CACA,OAAO;EAAE;EAAM;CAAQ;AACzB;;AAGA,SAAgB,WAAoB;CAClC,OAAO,SAAS,MAAM;AACxB"}
1
+ {"version":3,"file":"component.js","names":[],"sources":["../../src/component/component.ts"],"sourcesContent":["/**\n * MindeesNative component model — a minimal, renderer-agnostic element tree plus\n * **selector-based, re-render-isolated context**.\n *\n * The element tree (`createElement` / `Fragment`) is a plain data structure: a\n * component is a function of `props` returning elements. It carries no rendering\n * logic itself — the Helix renderer (Phase 3) turns this tree into host nodes.\n *\n * The context system is the important primitive: a `createContext` value is read\n * through a **selector**, and a consumer only re-runs when its *selected slice*\n * actually changes — not on every context update. This is the re-render\n * isolation the Quantum Router (Phase 6) builds on. It is implemented on the\n * Phase 1 signals, so selection participates in normal reactivity.\n *\n * @module\n */\n\nimport { computed, createRoot, getOwner, signal } from '../reactive'\n\n// ---------------------------------------------------------------------------\n// Element tree\n// ---------------------------------------------------------------------------\n\n/** A component: a function from props to a renderable node. */\nexport type Component<P = Record<string, unknown>> = (props: P) => MindeesNode\n\n/**\n * Anything that can appear in the tree.\n *\n * Includes an **accessor** form `() => MindeesNode`: a function child (or\n * function prop value) is a *reactive region* — the renderer subscribes to it\n * and patches exactly that region when its signals change (the fine-grained\n * update model, à la SolidJS). Static trees never need it; reactive UIs do.\n */\nexport type MindeesNode =\n | MindeesElement\n // A node may hold a keyed region of ANY item type. KeyedRegion is invariant in T (its\n // mapFn/key take T), so `KeyedRegion<unknown>` would reject `KeyedRegion<Row>`; `any` is\n // the correct \"some item type\" here.\n // biome-ignore lint/suspicious/noExplicitAny: see above — invariant T at a node boundary.\n | KeyedRegion<any>\n | string\n | number\n | boolean\n | null\n | undefined\n | (() => MindeesNode)\n | MindeesNode[]\n\n/** The tag of an element: a host string (e.g. `\"view\"`) or a component function. */\nexport type ElementType = string | Component<never>\n\n/** A virtual element: a tag, its props, and its children. */\nexport interface MindeesElement {\n readonly $$typeof: typeof ELEMENT_TYPE\n readonly type: ElementType\n readonly props: Readonly<Record<string, unknown>>\n readonly children: readonly MindeesNode[]\n readonly key: string | number | null\n}\n\n/** Brand so a renderer can reliably distinguish elements from plain objects. */\nexport const ELEMENT_TYPE: unique symbol = Symbol.for('mindees.element')\n\n/** Marker tag for a fragment (children with no wrapper host node). */\nexport const Fragment: unique symbol = Symbol.for('mindees.fragment')\n\n/** Brand identifying a keyed list region (reconciled by key, not full-rebuilt). */\nexport const KEYED_REGION: unique symbol = Symbol.for('mindees.keyed-region')\n\n/**\n * A keyed list region: a reactive item list + a per-item render function, reconciled by key\n * so rows keep their identity (host node, focus, caret, scroll) across reorders instead of\n * being torn down and rebuilt. This is a serializable *description* (no rendering logic); the\n * renderer materializes it (`bindKeyedChild`). Build one with {@link keyedRegion}.\n */\nexport interface KeyedRegion<T = unknown> {\n readonly $$keyed: typeof KEYED_REGION\n /** The items, as a reactive accessor. */\n readonly each: () => readonly T[]\n /** Render one row from reactive `item`/`index` accessors (consume them lazily to patch in place). */\n readonly mapFn: (item: () => T, index: () => number) => MindeesNode\n /** Stable key per item (when omitted, the row is keyed by item identity). */\n readonly key: ((item: T, index: number) => unknown) | undefined\n /** Rendered when the list is empty. */\n readonly fallback: (() => MindeesNode) | undefined\n}\n\n/** Options for {@link keyedRegion}. */\nexport interface KeyedRegionOptions<T> {\n /** The items, static or reactive. */\n readonly each: readonly T[] | (() => readonly T[])\n /** Render one row from reactive `item`/`index` accessors. */\n readonly children: (item: () => T, index: () => number) => MindeesNode\n /** Stable key per item (defaults to item identity). */\n readonly key?: (item: T, index: number) => unknown\n /** Rendered when the list is empty. */\n readonly fallback?: () => MindeesNode\n}\n\n/** Build a {@link KeyedRegion} — a keyed, identity-preserving list node. */\nexport function keyedRegion<T>(options: KeyedRegionOptions<T>): KeyedRegion<T> {\n const each: () => readonly T[] =\n typeof options.each === 'function' ? options.each : () => options.each as readonly T[]\n return {\n $$keyed: KEYED_REGION,\n each,\n mapFn: options.children,\n key: options.key,\n fallback: options.fallback,\n }\n}\n\n/** Type guard: is `value` a {@link KeyedRegion}? */\nexport function isKeyedRegion(value: unknown): value is KeyedRegion {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { $$keyed?: unknown }).$$keyed === KEYED_REGION\n )\n}\n\ninterface PropsWithKey {\n key?: string | number | null\n children?: MindeesNode\n}\n\n/**\n * Create a virtual element. `type` is a host-component string or a component\n * function; `Fragment` groups children without a wrapper.\n *\n * @example\n * createElement('view', { id: 'root' }, createElement('text', null, 'hi'))\n */\nexport function createElement(\n type: ElementType | typeof Fragment,\n props?: (Record<string, unknown> & PropsWithKey) | null,\n ...children: MindeesNode[]\n): MindeesElement {\n const { key = null, children: propsChildren, ...rest } = props ?? {}\n // Children passed as args win over a `children` prop; otherwise fall back to it.\n const resolved: MindeesNode[] =\n children.length > 0 ? children : propsChildren !== undefined ? [propsChildren] : []\n return {\n $$typeof: ELEMENT_TYPE,\n type: type as ElementType,\n props: Object.freeze({ ...rest }),\n children: Object.freeze(resolved),\n key,\n }\n}\n\n/** Type guard: is `value` a MindeesElement? */\nexport function isElement(value: unknown): value is MindeesElement {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { $$typeof?: unknown }).$$typeof === ELEMENT_TYPE\n )\n}\n\n// ---------------------------------------------------------------------------\n// Selector-based, re-render-isolated context\n// ---------------------------------------------------------------------------\n\n/** Equality used by context selectors. Defaults to `Object.is`. */\nexport type SelectorEquals<S> = (a: S, b: S) => boolean\n\n/** A context handle created by {@link createContext}. */\nexport interface Context<T> {\n /** Provide a value to a subtree (conceptually); returns a scoped reader set. */\n readonly id: symbol\n /** The default value used when no provider is present. */\n readonly defaultValue: T\n}\n\n/**\n * A live provider instance: holds the current value and lets consumers subscribe\n * to a derived slice with re-render isolation.\n */\nexport interface ContextProvider<T> {\n /** Replace the provided value (notifies only consumers whose slice changed). */\n set(value: T): void\n /** Read the current value without subscribing. */\n peek(): T\n /**\n * Subscribe to a selected slice. The returned accessor is a memo that only\n * changes — and thus only re-runs its observers — when `selector(value)`\n * changes under `equals` (default `Object.is`).\n */\n select<S>(selector: (value: T) => S, equals?: SelectorEquals<S>): () => S\n}\n\n/** Create a context with a default value. */\nexport function createContext<T>(defaultValue: T): Context<T> {\n return { id: Symbol('mindees.context'), defaultValue }\n}\n\n/**\n * Create a provider instance for `context`. Built on a signal, so selected\n * slices are memos: a consumer that selects `c => c.user.name` does not re-run\n * when an unrelated field changes.\n *\n * @example\n * const Theme = createContext({ mode: 'light', accent: 'blue' })\n * const p = createProvider(Theme, { mode: 'light', accent: 'blue' })\n * const mode = p.select((t) => t.mode) // only re-runs when `mode` changes\n */\nexport function createProvider<T>(context: Context<T>, initial?: T): ContextProvider<T> {\n // equals:false at the root — every set() re-evaluates selectors, but each\n // selector memo applies its own equality to isolate re-renders.\n const source = signal<T>(initial ?? context.defaultValue, { equals: false })\n return {\n set: (value: T) => {\n source.set(value)\n },\n peek: () => source.peek(),\n select<S>(selector: (value: T) => S, equals: SelectorEquals<S> = Object.is): () => S {\n return computed(() => selector(source()), { equals })\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Rendering a component subtree under an owner (so it can be disposed)\n// ---------------------------------------------------------------------------\n\n/**\n * Invoke a component within a reactive ownership scope, so every effect, memo,\n * and `onCleanup` it registers is torn down together when `dispose` is called.\n * Returns the produced node and that disposer.\n *\n * Built on the Phase 1 {@link createRoot}, so disposal reuses the same\n * leak-free teardown the reactivity tests cover. This is a renderer building\n * block; it does not touch any host.\n */\nexport function renderComponent<P>(\n component: Component<P>,\n props: P,\n): { node: MindeesNode; dispose: () => void } {\n let node!: MindeesNode\n // Capture the disposer eagerly: createRoot hands it to the callback synchronously\n // *before* the component runs, so if the component throws mid-render we can still\n // tear down anything it registered before the throw (effects, subscriptions,\n // timers). Without this, createRoot re-throws without disposing and the caller\n // never receives a disposer — the partial scope would leak forever.\n let dispose!: () => void\n try {\n createRoot((d) => {\n dispose = d\n node = component(props)\n })\n } catch (error) {\n dispose?.()\n throw error\n }\n return { node, dispose }\n}\n\n/** Whether code is currently running inside a reactive ownership scope. */\nexport function hasOwner(): boolean {\n return getOwner() !== null\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA8DA,MAAa,eAA8B,OAAO,IAAI,iBAAiB;;AAGvE,MAAa,WAA0B,OAAO,IAAI,kBAAkB;;AAGpE,MAAa,eAA8B,OAAO,IAAI,sBAAsB;;AAiC5E,SAAgB,YAAe,SAAgD;CAG7E,OAAO;EACL,SAAS;EACT,MAHA,OAAO,QAAQ,SAAS,aAAa,QAAQ,aAAa,QAAQ;EAIlE,OAAO,QAAQ;EACf,KAAK,QAAQ;EACb,UAAU,QAAQ;CACpB;AACF;;AAGA,SAAgB,cAAc,OAAsC;CAClE,OACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAgC,YAAY;AAEjD;;;;;;;;AAcA,SAAgB,cACd,MACA,OACA,GAAG,UACa;CAChB,MAAM,EAAE,MAAM,MAAM,UAAU,eAAe,GAAG,SAAS,SAAS,CAAC;CAEnE,MAAM,WACJ,SAAS,SAAS,IAAI,WAAW,kBAAkB,KAAA,IAAY,CAAC,aAAa,IAAI,CAAC;CACpF,OAAO;EACL,UAAU;EACJ;EACN,OAAO,OAAO,OAAO,EAAE,GAAG,KAAK,CAAC;EAChC,UAAU,OAAO,OAAO,QAAQ;EAChC;CACF;AACF;;AAGA,SAAgB,UAAU,OAAyC;CACjE,OACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAiC,aAAa;AAEnD;;AAmCA,SAAgB,cAAiB,cAA6B;CAC5D,OAAO;EAAE,IAAI,OAAO,iBAAiB;EAAG;CAAa;AACvD;;;;;;;;;;;AAYA,SAAgB,eAAkB,SAAqB,SAAiC;CAGtF,MAAM,SAAS,OAAU,WAAW,QAAQ,cAAc,EAAE,QAAQ,MAAM,CAAC;CAC3E,OAAO;EACL,MAAM,UAAa;GACjB,OAAO,IAAI,KAAK;EAClB;EACA,YAAY,OAAO,KAAK;EACxB,OAAU,UAA2B,SAA4B,OAAO,IAAa;GACnF,OAAO,eAAe,SAAS,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC;EACtD;CACF;AACF;;;;;;;;;;AAeA,SAAgB,gBACd,WACA,OAC4C;CAC5C,IAAI;CAMJ,IAAI;CACJ,IAAI;EACF,YAAY,MAAM;GAChB,UAAU;GACV,OAAO,UAAU,KAAK;EACxB,CAAC;CACH,SAAS,OAAO;EACd,UAAU;EACV,MAAM;CACR;CACA,OAAO;EAAE;EAAM;CAAQ;AACzB;;AAGA,SAAgB,WAAoB;CAClC,OAAO,SAAS,MAAM;AACxB"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Maturity, PackageInfo } from "./types.js";
2
- import { Component, Context, ContextProvider, ELEMENT_TYPE, ElementType, Fragment, MindeesElement, MindeesNode, SelectorEquals, createContext, createElement, createProvider, hasOwner, isElement, renderComponent } from "./component/component.js";
2
+ import { Component, Context, ContextProvider, ELEMENT_TYPE, ElementType, Fragment, KEYED_REGION, KeyedRegion, KeyedRegionOptions, MindeesElement, MindeesNode, SelectorEquals, createContext, createElement, createProvider, hasOwner, isElement, isKeyedRegion, keyedRegion, renderComponent } from "./component/component.js";
3
3
  import { NotImplementedError } from "./errors.js";
4
4
  import { notImplemented } from "./not-implemented.js";
5
5
  import { Accessor, ComputedOptions, EqualsFn, Memo, Owner, Signal, SignalOptions, batch, computed, createRoot, effect, getOwner, memo, onCleanup, runWithOwner, signal, untrack } from "./reactive/reactive.js";
@@ -10,7 +10,7 @@ import { ThreadPool, WorkerLike, WorkerPoolOptions, createInlineThreadPool, crea
10
10
  /** The npm package name. */
11
11
  declare const name = "@mindees/core";
12
12
  /** The package version. All `@mindees/*` packages share one locked version line. */
13
- declare const VERSION = "0.2.0";
13
+ declare const VERSION = "0.3.0";
14
14
  /**
15
15
  * Current maturity of this package. See the repository `STATUS.md`.
16
16
  *
@@ -27,5 +27,5 @@ declare const maturity: Maturity;
27
27
  */
28
28
  declare const info: PackageInfo;
29
29
  //#endregion
30
- export { type Accessor, type Component, type ComputedOptions, type Context, type ContextProvider, ELEMENT_TYPE, type ElementType, type EqualsFn, Fragment, type Maturity, type Memo, type MindeesElement, type MindeesNode, NotImplementedError, type Owner, type PackageInfo, type Priority, type ScheduleOptions, type ScheduledTask, Scheduler, type SchedulerOptions, type SelectorEquals, type Signal, type SignalOptions, type Task, type ThreadPool, VERSION, type WorkerLike, type WorkerPoolOptions, batch, computed, createContext, createElement, createInlineThreadPool, createNativeThreadPool, createProvider, createRoot, createScheduler, createWorkerPool, effect, getOwner, hasOwner, info, isElement, maturity, memo, name, notImplemented, onCleanup, renderComponent, runWithOwner, signal, untrack };
30
+ export { type Accessor, type Component, type ComputedOptions, type Context, type ContextProvider, ELEMENT_TYPE, type ElementType, type EqualsFn, Fragment, KEYED_REGION, type KeyedRegion, type KeyedRegionOptions, type Maturity, type Memo, type MindeesElement, type MindeesNode, NotImplementedError, type Owner, type PackageInfo, type Priority, type ScheduleOptions, type ScheduledTask, Scheduler, type SchedulerOptions, type SelectorEquals, type Signal, type SignalOptions, type Task, type ThreadPool, VERSION, type WorkerLike, type WorkerPoolOptions, batch, computed, createContext, createElement, createInlineThreadPool, createNativeThreadPool, createProvider, createRoot, createScheduler, createWorkerPool, effect, getOwner, hasOwner, info, isElement, isKeyedRegion, keyedRegion, maturity, memo, name, notImplemented, onCleanup, renderComponent, runWithOwner, signal, untrack };
31
31
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;AAgGA;AAAA,cApBa,IAAA;;cAGA,OAAA;AAiBuE;;;;;;;;AAAA,cAPvE,QAAA,EAAU,QAAyB;;;;;;cAOnC,IAAA,EAAM,WAAiE"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;AAqGA;AAAA,cApBa,IAAA;;cAGA,OAAA;AAiBuE;;;;;;;;AAAA,cAPvE,QAAA,EAAU,QAAyB;;;;;;cAOnC,IAAA,EAAM,WAAiE"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { batch, computed, createRoot, effect, getOwner, memo, onCleanup, runWithOwner, signal, untrack } from "./reactive/reactive.js";
2
- import { ELEMENT_TYPE, Fragment, createContext, createElement, createProvider, hasOwner, isElement, renderComponent } from "./component/component.js";
2
+ import { ELEMENT_TYPE, Fragment, KEYED_REGION, createContext, createElement, createProvider, hasOwner, isElement, isKeyedRegion, keyedRegion, renderComponent } from "./component/component.js";
3
3
  import { NotImplementedError } from "./errors.js";
4
4
  import { notImplemented } from "./not-implemented.js";
5
5
  import { Scheduler, createScheduler } from "./scheduler/scheduler.js";
@@ -8,7 +8,7 @@ import { createInlineThreadPool, createNativeThreadPool, createWorkerPool } from
8
8
  /** The npm package name. */
9
9
  const name = "@mindees/core";
10
10
  /** The package version. All `@mindees/*` packages share one locked version line. */
11
- const VERSION = "0.2.0";
11
+ const VERSION = "0.3.0";
12
12
  /**
13
13
  * Current maturity of this package. See the repository `STATUS.md`.
14
14
  *
@@ -29,6 +29,6 @@ const info = Object.freeze({
29
29
  maturity
30
30
  });
31
31
  //#endregion
32
- export { ELEMENT_TYPE, Fragment, NotImplementedError, Scheduler, VERSION, batch, computed, createContext, createElement, createInlineThreadPool, createNativeThreadPool, createProvider, createRoot, createScheduler, createWorkerPool, effect, getOwner, hasOwner, info, isElement, maturity, memo, name, notImplemented, onCleanup, renderComponent, runWithOwner, signal, untrack };
32
+ export { ELEMENT_TYPE, Fragment, KEYED_REGION, NotImplementedError, Scheduler, VERSION, batch, computed, createContext, createElement, createInlineThreadPool, createNativeThreadPool, createProvider, createRoot, createScheduler, createWorkerPool, effect, getOwner, hasOwner, info, isElement, isKeyedRegion, keyedRegion, maturity, memo, name, notImplemented, onCleanup, renderComponent, runWithOwner, signal, untrack };
33
33
 
34
34
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Maturity, PackageInfo } from './types'\n\n/**\n * Component model: a renderer-agnostic element tree plus selector-based,\n * re-render-isolated context. (Phase 2)\n */\nexport {\n type Component,\n type Context,\n type ContextProvider,\n createContext,\n createElement,\n createProvider,\n ELEMENT_TYPE,\n type ElementType,\n Fragment,\n hasOwner,\n isElement,\n type MindeesElement,\n type MindeesNode,\n renderComponent,\n type SelectorEquals,\n} from './component'\nexport { NotImplementedError } from './errors'\nexport { notImplemented } from './not-implemented'\n/**\n * Fine-grained reactivity: signals, computed values, effects, batching, and\n * disposal scopes. This is the reactive core of MindeesNative.\n */\nexport {\n type Accessor,\n batch,\n type ComputedOptions,\n computed,\n createRoot,\n type EqualsFn,\n effect,\n getOwner,\n type Memo,\n memo,\n type Owner,\n onCleanup,\n runWithOwner,\n type Signal,\n type SignalOptions,\n signal,\n untrack,\n} from './reactive'\n/**\n * Priority scheduler: two-lane (sync/normal), microtask-batched, with\n * cancellable and dedupable tasks. (Phase 2)\n */\nexport {\n createScheduler,\n type Priority,\n type ScheduledTask,\n type ScheduleOptions,\n Scheduler,\n type SchedulerOptions,\n type Task,\n} from './scheduler'\n/**\n * Threading abstraction: a {@link ThreadPool} contract with a working Web Worker\n * backend and an inline fallback. Native multi-threading is a research track. (Phase 2)\n */\nexport {\n createInlineThreadPool,\n createNativeThreadPool,\n createWorkerPool,\n type ThreadPool,\n type WorkerLike,\n type WorkerPoolOptions,\n} from './threading'\nexport type { Maturity, PackageInfo } from './types'\n\n/** The npm package name. */\nexport const name = '@mindees/core'\n\n/** The package version. All `@mindees/*` packages share one locked version line. */\nexport const VERSION = '0.2.0'\n\n/**\n * Current maturity of this package. See the repository `STATUS.md`.\n *\n * The reactivity layer (signals/computed/effect/batch), the component model with\n * selector-isolated context, the priority scheduler, and the thread-pool\n * abstraction (Web Worker + inline) are all implemented and tested. Native\n * multi-threading remains a research track (throws `NotImplementedError`).\n */\nexport const maturity: Maturity = 'experimental'\n\n/**\n * Static identity + maturity metadata for this package. Frozen so the\n * self-reported identity tooling introspects cannot be mutated at runtime,\n * matching the `readonly` fields of {@link PackageInfo}.\n */\nexport const info: PackageInfo = Object.freeze({ name, version: VERSION, maturity })\n"],"mappings":";;;;;;;;AA4EA,MAAa,OAAO;;AAGpB,MAAa,UAAU;;;;;;;;;AAUvB,MAAa,WAAqB;;;;;;AAOlC,MAAa,OAAoB,OAAO,OAAO;CAAE;CAAM,SAAS;CAAS;AAAS,CAAC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Maturity, PackageInfo } from './types'\n\n/**\n * Component model: a renderer-agnostic element tree plus selector-based,\n * re-render-isolated context. (Phase 2)\n */\nexport {\n type Component,\n type Context,\n type ContextProvider,\n createContext,\n createElement,\n createProvider,\n ELEMENT_TYPE,\n type ElementType,\n Fragment,\n hasOwner,\n isElement,\n isKeyedRegion,\n KEYED_REGION,\n type KeyedRegion,\n type KeyedRegionOptions,\n keyedRegion,\n type MindeesElement,\n type MindeesNode,\n renderComponent,\n type SelectorEquals,\n} from './component'\nexport { NotImplementedError } from './errors'\nexport { notImplemented } from './not-implemented'\n/**\n * Fine-grained reactivity: signals, computed values, effects, batching, and\n * disposal scopes. This is the reactive core of MindeesNative.\n */\nexport {\n type Accessor,\n batch,\n type ComputedOptions,\n computed,\n createRoot,\n type EqualsFn,\n effect,\n getOwner,\n type Memo,\n memo,\n type Owner,\n onCleanup,\n runWithOwner,\n type Signal,\n type SignalOptions,\n signal,\n untrack,\n} from './reactive'\n/**\n * Priority scheduler: two-lane (sync/normal), microtask-batched, with\n * cancellable and dedupable tasks. (Phase 2)\n */\nexport {\n createScheduler,\n type Priority,\n type ScheduledTask,\n type ScheduleOptions,\n Scheduler,\n type SchedulerOptions,\n type Task,\n} from './scheduler'\n/**\n * Threading abstraction: a {@link ThreadPool} contract with a working Web Worker\n * backend and an inline fallback. Native multi-threading is a research track. (Phase 2)\n */\nexport {\n createInlineThreadPool,\n createNativeThreadPool,\n createWorkerPool,\n type ThreadPool,\n type WorkerLike,\n type WorkerPoolOptions,\n} from './threading'\nexport type { Maturity, PackageInfo } from './types'\n\n/** The npm package name. */\nexport const name = '@mindees/core'\n\n/** The package version. All `@mindees/*` packages share one locked version line. */\nexport const VERSION = '0.3.0'\n\n/**\n * Current maturity of this package. See the repository `STATUS.md`.\n *\n * The reactivity layer (signals/computed/effect/batch), the component model with\n * selector-isolated context, the priority scheduler, and the thread-pool\n * abstraction (Web Worker + inline) are all implemented and tested. Native\n * multi-threading remains a research track (throws `NotImplementedError`).\n */\nexport const maturity: Maturity = 'experimental'\n\n/**\n * Static identity + maturity metadata for this package. Frozen so the\n * self-reported identity tooling introspects cannot be mutated at runtime,\n * matching the `readonly` fields of {@link PackageInfo}.\n */\nexport const info: PackageInfo = Object.freeze({ name, version: VERSION, maturity })\n"],"mappings":";;;;;;;;AAiFA,MAAa,OAAO;;AAGpB,MAAa,UAAU;;;;;;;;;AAUvB,MAAa,WAAqB;;;;;;AAOlC,MAAa,OAAoB,OAAO,OAAO;CAAE;CAAM,SAAS;CAAS;AAAS,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindees/core",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "MindeesNative core — fine-grained reactivity (signals/computed/effect/batch), component model with selector-isolated context, priority scheduler, and a thread-pool abstraction (Web Worker + inline; native is a research track).",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "type": "module",