@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 +79 -0
- package/dist/chunk-vnode-DA7cLTKP.js +33 -0
- package/dist/chunk-vnode-DA7cLTKP.js.map +1 -0
- package/dist/chunk-vnode-DHwzzX6t.d.ts +25 -0
- package/dist/index.d.ts +148 -0
- package/dist/index.js +1664 -0
- package/dist/index.js.map +1 -0
- package/dist/jsx-dev-runtime.d.ts +9 -0
- package/dist/jsx-dev-runtime.js +11 -0
- package/dist/jsx-dev-runtime.js.map +1 -0
- package/dist/jsx-runtime.d.ts +57 -0
- package/dist/jsx-runtime.js +23 -0
- package/dist/jsx-runtime.js.map +1 -0
- package/package.json +59 -0
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
|
package/dist/index.d.ts
ADDED
|
@@ -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
|