@expressive/dom 0.0.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 ADDED
@@ -0,0 +1,79 @@
1
+ <h1 align="center">@expressive/dom</h1>
2
+
3
+ <p align="center">
4
+ MVC-native JSX rendering for the browser DOM.
5
+ </p>
6
+
7
+ ---
8
+
9
+ `@expressive/dom` renders Expressive MVC without React or Preact. Function components are renderer-stateless: they project props and MVC snapshots into DOM, while `State.get()` and `State.use()` supply dependency tracking and owned state.
10
+
11
+ ```bash
12
+ npm install @expressive/mvc @expressive/dom
13
+ ```
14
+
15
+ ```jsonc
16
+ {
17
+ "compilerOptions": {
18
+ "jsx": "react-jsx",
19
+ "jsxImportSource": "@expressive/dom"
20
+ }
21
+ }
22
+ ```
23
+
24
+ ```tsx
25
+ import { State, Component } from '@expressive/mvc';
26
+ import { render } from '@expressive/dom';
27
+
28
+ class Counter extends State {
29
+ count = 0;
30
+ increment() {
31
+ this.count++;
32
+ }
33
+ }
34
+
35
+ function Count() {
36
+ const { count, increment } = Counter.use();
37
+ return <button onClick={increment}>{count}</button>;
38
+ }
39
+
40
+ const unmount = render(<Count />, document.getElementById('app')!);
41
+ ```
42
+
43
+ `style` recursively composes conditional class tokens and inline declarations. Strings become classes; objects merge left-to-right and render inline. Use `className` for a browser-specific class outside the composition. The `class` attribute spelling and CSS declaration strings are not supported.
44
+
45
+ ```tsx
46
+ <button
47
+ className="external-widget"
48
+ style={[styles.button, active && 'active', { width }]}
49
+ />
50
+ ```
51
+
52
+ `style` on a component automatically reaches its rendered host root, including through component, fragment, provider, portal, and collection boundaries. A fragment applies it to each host root. Forwarded style overrides the root's own, with the outermost caller winning. A component which reads its `style` prop while rendering owns placement, and nothing is forwarded. It receives a frozen object of the caller's inline declarations - spread, pluck or merge it freely; the caller's classes travel hidden with it. `className` applies to elements only.
53
+
54
+ A component may register immutable maps with `style(Component, map)`. A `_name` entry is a rule, applied by a truthy `_name` attribute or a matching host tag and emitted as one class named after its source; nested inside a rule it opens a descendant scope. Component names never match - they do not survive minification. Bare keys are declarations, or macro calls where a macro owns the name, and those at the top of a map form a base rule applied to each of the component's host roots. Macros are defined only by `macro()` - within a map, call a plain function instead. `$name` is reserved. `_` attributes never reach the DOM, and `0` or `''` leaves a rule off. A map never reaches inside a child component; only the class chain inherits.
55
+
56
+ ```tsx
57
+ function Button({ active }: { active: boolean }) {
58
+ return <button _active={active}>Save</button>;
59
+ }
60
+
61
+ style(Button, {
62
+ padding: '8px',
63
+ _active: { fontWeight: 700 }
64
+ });
65
+ ```
66
+
67
+ Global maps use `macro(map)` and can live in side-effect imports. Register macros and styles before the first relevant render. A key resolves at its topmost definition; a macro returning its own name falls to the next definition down, then through any `'*'` handler. Unhandled keys reach the host, which joins arrays and rejects functions and objects - units belong to a macro, never a guess.
68
+
69
+ Inline style always beats classes; among classes, a caller's rule beats the callee's, and a rule carried through more component `style` props wins - independent of render order. `false`, `null`, and `undefined` omit an entry; `0` remains a macro argument.
70
+
71
+ `Component` retains the React adapter's model: fields read by `render()` are dependencies, owned instances mount and clean up with the DOM range, and an externally activated instance can be placed directly without transferring ownership.
72
+
73
+ The first release includes keyed reconciliation, native events, refs, SVG, `Provider` / `Consumer`, direct `has` / `map` collection rendering, portals, lazy components, error and suspense fallbacks, generated component appearance rules, and retention of committed content while `pending()` work suspends. It deliberately excludes standalone hooks, synthetic events, memo wrappers, SSR, hydration, and build-time style extraction.
74
+
75
+ See [the DOM renderer guide](../../skills/dom/dom.md) for the API and constraints. This package currently targets browser DOM; native rendering and build-time expressive-jsx extraction are not included.
76
+
77
+ ## License
78
+
79
+ MIT
@@ -0,0 +1,33 @@
1
+ //#region src/vnode.ts
2
+ const VNODE = Symbol("@expressive/dom.vnode");
3
+ const PORTAL = Symbol("@expressive/dom.portal");
4
+ function vnode(type, props, key) {
5
+ return {
6
+ [VNODE]: true,
7
+ type,
8
+ props: props || {},
9
+ key
10
+ };
11
+ }
12
+ function isVNode(value) {
13
+ return !!value && typeof value == "object" && value[VNODE] === true;
14
+ }
15
+ function childrenOf(value) {
16
+ const output = [];
17
+ function add(child) {
18
+ if (Array.isArray(child)) child.forEach(add);
19
+ else if (child !== null && child !== void 0 && typeof child != "boolean") output.push(child);
20
+ }
21
+ add(value);
22
+ return output;
23
+ }
24
+ function createPortal(children, container, key) {
25
+ return vnode(PORTAL, {
26
+ children,
27
+ container
28
+ }, key);
29
+ }
30
+
31
+ //#endregion
32
+ export { vnode as a, isVNode as i, childrenOf as n, createPortal as r, PORTAL as t };
33
+ //# sourceMappingURL=chunk-vnode-DA7cLTKP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-vnode-DA7cLTKP.js","names":[],"sources":["../src/vnode.ts"],"sourcesContent":["import type { has, map } from '@expressive/mvc';\n\nconst VNODE = Symbol('@expressive/dom.vnode');\nconst PORTAL = Symbol('@expressive/dom.portal');\n\ntype Key = string | number | null | undefined;\ntype FunctionComponent<P = any> = (props: P) => Node;\ninterface ComponentInstance {\n readonly props: any;\n render(props?: any): Node;\n}\ntype ComponentType<P = any> = FunctionComponent<P> | (new (props: P) => ComponentInstance);\n\ntype Node =\n | VNode\n | ComponentInstance\n | string\n | number\n | bigint\n | boolean\n | null\n | undefined\n | readonly Node[]\n | has.List<Node>\n | has.Pool<Node, any, any>\n | map.Managed<unknown, Node>;\n\ninterface VNode<P = any> {\n readonly [VNODE]: true;\n readonly type: string | symbol | ComponentType<P>;\n readonly props: P & { children?: Node };\n readonly key?: Key;\n}\n\nfunction vnode<P>(type: VNode<P>['type'], props: P, key?: unknown): VNode<P> {\n return {\n [VNODE]: true,\n type,\n props: (props || {}) as VNode<P>['props'],\n key: key as Key\n };\n}\n\nfunction isVNode(value: unknown): value is VNode {\n return !!value && typeof value == 'object' && (value as VNode)[VNODE] === true;\n}\n\nfunction childrenOf(value: unknown): Node[] {\n const output: Node[] = [];\n\n function add(child: unknown) {\n if (Array.isArray(child)) child.forEach(add);\n else if (child !== null && child !== undefined && typeof child != 'boolean')\n output.push(child as Node);\n }\n\n add(value);\n return output;\n}\n\nfunction createPortal(children: Node, container: Element | DocumentFragment, key?: Key): VNode {\n return vnode(PORTAL, { children, container }, key);\n}\n\nexport {\n VNODE,\n PORTAL,\n childrenOf,\n createPortal,\n isVNode,\n vnode\n};\n\nexport type { ComponentInstance, ComponentType, FunctionComponent, Key, Node, VNode };\n"],"mappings":";AAEA,MAAM,QAAQ,OAAO,wBAAwB;AAC7C,MAAM,SAAS,OAAO,yBAAyB;AA+B/C,SAAS,MAAS,MAAwB,OAAU,KAAyB;AAC3E,QAAO;GACJ,QAAQ;EACT;EACA,OAAQ,SAAS,EAAE;EACd;EACN;;AAGH,SAAS,QAAQ,OAAgC;AAC/C,QAAO,CAAC,CAAC,SAAS,OAAO,SAAS,YAAa,MAAgB,WAAW;;AAG5E,SAAS,WAAW,OAAwB;CAC1C,MAAM,SAAiB,EAAE;CAEzB,SAAS,IAAI,OAAgB;AAC3B,MAAI,MAAM,QAAQ,MAAM,CAAE,OAAM,QAAQ,IAAI;WACnC,UAAU,QAAQ,UAAU,UAAa,OAAO,SAAS,UAChE,QAAO,KAAK,MAAc;;AAG9B,KAAI,MAAM;AACV,QAAO;;AAGT,SAAS,aAAa,UAAgB,WAAuC,KAAkB;AAC7F,QAAO,MAAM,QAAQ;EAAE;EAAU;EAAW,EAAE,IAAI"}
@@ -0,0 +1,25 @@
1
+ import { has, map } from "@expressive/mvc";
2
+
3
+ //#region src/vnode.d.ts
4
+ declare const VNODE: unique symbol;
5
+ type Key = string | number | null | undefined;
6
+ type FunctionComponent<P = any> = (props: P) => Node;
7
+ interface ComponentInstance {
8
+ readonly props: any;
9
+ render(props?: any): Node;
10
+ }
11
+ type ComponentType<P = any> = FunctionComponent<P> | (new (props: P) => ComponentInstance);
12
+ type Node = VNode | ComponentInstance | string | number | bigint | boolean | null | undefined | readonly Node[] | has.List<Node> | has.Pool<Node, any, any> | map.Managed<unknown, Node>;
13
+ interface VNode<P = any> {
14
+ readonly [VNODE]: true;
15
+ readonly type: string | symbol | ComponentType<P>;
16
+ readonly props: P & {
17
+ children?: Node;
18
+ };
19
+ readonly key?: Key;
20
+ }
21
+ declare function vnode<P>(type: VNode<P>['type'], props: P, key?: unknown): VNode<P>;
22
+ declare function createPortal(children: Node, container: Element | DocumentFragment, key?: Key): VNode;
23
+ //#endregion
24
+ export { createPortal as a, VNode as i, FunctionComponent as n, vnode as o, Node as r, ComponentType as t };
25
+ //# sourceMappingURL=chunk-vnode-DHwzzX6t.d.ts.map
@@ -0,0 +1,148 @@
1
+ import { a as createPortal, n as FunctionComponent, r as Node, t as ComponentType } from "./chunk-vnode-DHwzzX6t.js";
2
+ import { Component, Context, State } from "@expressive/mvc";
3
+
4
+ //#region src/adapter.d.ts
5
+ interface UseState extends State {
6
+ use?(...props: any[]): Promise<void> | void;
7
+ mount?(): (() => void) | void;
8
+ }
9
+ type NoVoid<T> = T extends undefined | void ? null : T;
10
+ declare module '@expressive/mvc' {
11
+ interface UseState extends State {
12
+ use?(...props: any[]): Promise<void> | void;
13
+ mount?(): (() => void) | void;
14
+ }
15
+ namespace State {
16
+ type ForceRefresh = {
17
+ (): void;
18
+ <T = void>(waitFor: Promise<T>): Promise<T>;
19
+ <T = void>(invoke: () => Promise<T>): Promise<T>;
20
+ };
21
+ type GetFactory<T extends State, R> = (this: T, current: T, refresh: ForceRefresh) => R;
22
+ type UseArgs<T extends State> = T extends {
23
+ use(...props: infer P): any;
24
+ } ? P : State.Args<T>;
25
+ function get<T extends State>(this: State.Extends<T>): T;
26
+ function get<T extends State>(this: State.Extends<T>, required: false): T | undefined;
27
+ function get<T extends State>(this: State.Extends<T>, required: true): Required<T>;
28
+ function get<T extends State, R>(this: State.Extends<T>, factory: GetFactory<T, R>): NoVoid<R>;
29
+ function use<T extends UseState>(this: State.Type<T>, ...args: UseArgs<T>): T;
30
+ }
31
+ namespace Component {
32
+ const use: never;
33
+ }
34
+ interface Component {
35
+ mount?(): (() => void) | void;
36
+ }
37
+ }
38
+ //#endregion
39
+ //#region src/context.d.ts
40
+ declare namespace Consumer {
41
+ interface Props<T extends State> {
42
+ for: State.Extends<T>;
43
+ children: (value: T) => Node;
44
+ }
45
+ }
46
+ declare function Consumer<T extends State>(props: Consumer.Props<T>): Node;
47
+ declare namespace Provider {
48
+ type ForEach<T extends State> = (state: T) => void;
49
+ interface SharedProps {
50
+ children?: Node;
51
+ fallback?: Node;
52
+ name?: string;
53
+ }
54
+ type Props<T extends State = State> = SharedProps & {
55
+ for?: Context.Accept<T>;
56
+ is?: ForEach<T>;
57
+ } & Record<string, unknown>;
58
+ }
59
+ declare function Provider<T extends State>(_props: Provider.Props<T>): Component.Node;
60
+ //#endregion
61
+ //#region src/lazy.d.ts
62
+ type Module<P> = {
63
+ default: ComponentType<P>;
64
+ } | ComponentType<P>;
65
+ declare function lazy<P>(load: () => Promise<Module<P>>): FunctionComponent<P>;
66
+ //#endregion
67
+ //#region src/stylesheet.d.ts
68
+ type Member = Extract<keyof CSSStyleDeclaration, string>;
69
+ type Settable = { [K in Member]-?: CSSStyleDeclaration[K] extends Function ? never : K }[Member];
70
+ type Key = style.Property | keyof macro.Registry | `--${string}` | `_${string}`;
71
+ type Argument<K extends keyof macro.Registry> = macro.Registry[K] extends ((value: infer A, ...rest: any[]) => any) ? A : never;
72
+ declare namespace macro {
73
+ /**
74
+ * Macros visible to a style map. A pack augments this interface to declare
75
+ * what it registers with `macro()`.
76
+ */
77
+ interface Registry {}
78
+ /** Maps one argument to style, or to keys which resolve further. */
79
+ type Fn = (value: any, key: string) => unknown;
80
+ /**
81
+ * Body of a `macro()` map. A name declared in {@link Registry} must match its
82
+ * declaration; an undeclared one is accepted, and becomes checkable at call
83
+ * sites once a module augments `Registry` with it.
84
+ */
85
+ type Map = Partial<Registry> & { [K in `_${string}`]?: style.Map } & Record<string, Fn | style.Map | undefined>;
86
+ }
87
+ declare namespace style {
88
+ /** CSS property names, minus the CSSOM's own members. */
89
+ type Property = Exclude<Settable, 'length' | 'parentRule' | 'cssText'>;
90
+ /** What a declaration or macro argument may be; never a plain object. */
91
+ type Value = string | number | boolean | null | undefined | readonly Value[];
92
+ /**
93
+ * Body of a `style()` map. Bare keys are declarations or macro calls,
94
+ * `_name` opens a rule or descendant scope, and `$name` is reserved.
95
+ */
96
+ type Map = { [K in Key]?: K extends `_${string}` ? Map : K extends keyof macro.Registry ? Argument<K> : Value };
97
+ }
98
+ declare function style<T extends object>(type: T, rules: style.Map): T;
99
+ declare function macro(rules: macro.Map): macro.Map;
100
+ //#endregion
101
+ //#region src/css.d.ts
102
+ /**
103
+ * Web macro pack. Register with `macro(css)` to opt into pixel units for
104
+ * numbers and the axis shorthands CSS itself does not provide.
105
+ */
106
+ declare const css: {
107
+ '*': (value: unknown, key: string) => {
108
+ [x: string]: unknown;
109
+ };
110
+ mx: (value: style.Value) => {
111
+ marginLeft: style.Value;
112
+ marginRight: style.Value;
113
+ };
114
+ my: (value: style.Value) => {
115
+ marginTop: style.Value;
116
+ marginBottom: style.Value;
117
+ };
118
+ px: (value: style.Value) => {
119
+ paddingLeft: style.Value;
120
+ paddingRight: style.Value;
121
+ };
122
+ py: (value: style.Value) => {
123
+ paddingTop: style.Value;
124
+ paddingBottom: style.Value;
125
+ };
126
+ size: (value: style.Value) => {
127
+ width: any;
128
+ height: any;
129
+ };
130
+ };
131
+ declare module './stylesheet' {
132
+ namespace macro {
133
+ interface Registry {
134
+ mx(value: style.Value): style.Map;
135
+ my(value: style.Value): style.Map;
136
+ px(value: style.Value): style.Map;
137
+ py(value: style.Value): style.Map;
138
+ size(value: style.Value): style.Map;
139
+ }
140
+ }
141
+ }
142
+ //#endregion
143
+ //#region src/render.d.ts
144
+ type Container = Element | DocumentFragment;
145
+ declare function render(node: Node, container: Container): () => void;
146
+ //#endregion
147
+ export { Consumer, Provider, createPortal, css, lazy, macro, render, style };
148
+ //# sourceMappingURL=index.d.ts.map