solarite 0.5.2 → 0.7.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/LICENSE +21 -0
- package/dist/Solarite-debug.js +2823 -1856
- package/dist/Solarite.js +2779 -1824
- package/dist/Solarite.min.js +2 -4
- package/package.json +16 -3
- package/readme.md +58 -11
- package/src/Globals.js +54 -72
- package/src/HtmlParser.js +90 -90
- package/src/MultiValueMap.js +57 -105
- package/src/NodeGroup.js +624 -470
- package/src/Path.js +224 -211
- package/src/PathToAttribValue.js +401 -261
- package/src/PathToAttribs.js +113 -80
- package/src/PathToComment.js +7 -7
- package/src/PathToComponent.js +183 -188
- package/src/PathToEvent.js +76 -64
- package/src/PathToKey.js +19 -0
- package/src/PathToNodes.js +1053 -566
- package/src/RootNodeGroup.js +120 -8
- package/src/Shell.js +570 -348
- package/src/Solarite.d.ts +134 -113
- package/src/Solarite.js +243 -286
- package/src/Template.js +195 -274
- package/src/Util.js +353 -351
- package/src/assert.js +10 -10
- package/src/assignAttributes.js +63 -0
- package/src/delve.js +55 -43
- package/src/h.js +220 -138
- package/src/jsx-dev-runtime.d.ts +1 -0
- package/src/jsx-dev-runtime.js +5 -0
- package/src/jsx-runtime.d.ts +21 -0
- package/src/jsx-runtime.js +84 -0
- package/src/jsx.js +194 -0
- package/src/toEl.js +77 -82
- package/dist/udomdiff-license.txt +0 -18
- package/src/getArg.js +0 -137
- package/src/hash.js +0 -89
- package/src/udomdiff.js +0 -176
- package/src/unused/FastLookupArray.js +0 -54
- package/src/unused/Hashes.js +0 -339
- package/src/unused/InUse.test.js +0 -92
- package/src/unused/InUseMap.js +0 -98
- package/src/unused/LinkedList.js +0 -117
- package/src/unused/LinkedList.test.js +0 -115
- package/src/unused/Misc.js +0 -13
- package/src/unused/Perf.js +0 -47
- package/src/unused/TrackedArray.js +0 -54
- package/src/unused/WeakArray.js +0 -33
- package/src/watch.js +0 -543
package/src/assert.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
/*@__NO_SIDE_EFFECTS__*/
|
|
3
|
-
export default function assert(val) {
|
|
4
|
-
//#IFDEV
|
|
5
|
-
if (!val) {
|
|
6
|
-
//debugger;
|
|
7
|
-
throw new Error('Assertion failed: ' + val);
|
|
8
|
-
}
|
|
9
|
-
//#ENDIF
|
|
10
|
-
}
|
|
1
|
+
|
|
2
|
+
/*@__NO_SIDE_EFFECTS__*/
|
|
3
|
+
export default function assert(val) {
|
|
4
|
+
//#IFDEV
|
|
5
|
+
if (!val) {
|
|
6
|
+
//debugger;
|
|
7
|
+
throw new Error('Assertion failed: ' + val);
|
|
8
|
+
}
|
|
9
|
+
//#ENDIF
|
|
10
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
┏┓ ┓ •
|
|
3
|
+
┗┓┏┓┃┏┓┏┓┓╋▗▖
|
|
4
|
+
┗┛┗┛┗┗┻╹ ╹╹┗
|
|
5
|
+
JavaScript UI library
|
|
6
|
+
@license MIT
|
|
7
|
+
@copyright Vorticode LLC
|
|
8
|
+
https://vorticode.github.io/solarite/ */
|
|
9
|
+
|
|
10
|
+
import Util from "./Util.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Read an element's html attributes onto fields that already exist on the element.
|
|
14
|
+
* Typically called from a web component constructor to support plain-html instantiation
|
|
15
|
+
* like `<my-timer duration="7" auto-start>`. Tagged-template values are already typed and
|
|
16
|
+
* arrive in the constructor's argument instead, so assign those directly.
|
|
17
|
+
*
|
|
18
|
+
* Attribute names convert from kebab-case to camelCase, so `auto-start` becomes `autoStart`.
|
|
19
|
+
* An attribute written like `${...}` is JSON-parsed back to its original type and assigned
|
|
20
|
+
* as-is. Every other attribute value is a string: if its field is named in `types`, the
|
|
21
|
+
* string is cast with that converter, otherwise it's assigned as a string.
|
|
22
|
+
*
|
|
23
|
+
* `types` maps a field name to a converter: Number, Boolean, String, Date, or any function
|
|
24
|
+
* taking the string and returning a value. Boolean is true for any string except 'false'
|
|
25
|
+
* and '0', so a bare attribute like `<my-timer auto-start>` reads as true. Date uses
|
|
26
|
+
* new Date(value). No type is inferred from the field's existing value.
|
|
27
|
+
*
|
|
28
|
+
* Field names listed in `ignore` are skipped.
|
|
29
|
+
* @param dest {HTMLElement}
|
|
30
|
+
* @param types {Object<string, Function>}
|
|
31
|
+
* @param ignore {string[]} */
|
|
32
|
+
export function assignAttributes(dest, types={}, ignore=[]) {
|
|
33
|
+
for (let attrib of dest.attributes) {
|
|
34
|
+
let name = Util.dashesToCamel(attrib.name);
|
|
35
|
+
if (!(name in dest) || ignore.includes(name))
|
|
36
|
+
continue;
|
|
37
|
+
|
|
38
|
+
let value = attrib.value;
|
|
39
|
+
let type = types[name];
|
|
40
|
+
|
|
41
|
+
// 1. A `${...}` attribute holds an already-typed JSON value.
|
|
42
|
+
if (value.startsWith('${') && value.endsWith('}'))
|
|
43
|
+
dest[name] = JSON.parse(value.slice(2, -1));
|
|
44
|
+
|
|
45
|
+
// 2. Cast the string with the converter named in `types`, if any.
|
|
46
|
+
else if (type === Date)
|
|
47
|
+
dest[name] = new Date(value);
|
|
48
|
+
else if (type === Boolean)
|
|
49
|
+
dest[name] = !['false', '0'].includes(value);
|
|
50
|
+
else if (type === Number)
|
|
51
|
+
dest[name] = Number(value);
|
|
52
|
+
else if (type === String)
|
|
53
|
+
dest[name] = String(value);
|
|
54
|
+
else if (type) // custom string=>value function
|
|
55
|
+
dest[name] = type(value);
|
|
56
|
+
|
|
57
|
+
// 3. No converter named: assign the raw string. But an empty value over a function/object
|
|
58
|
+
// field is just the serialization residue of a template expression (functions render as
|
|
59
|
+
// attribute="") — skip it, or it clobbers the real value the expression already assigned.
|
|
60
|
+
else if (value !== '' || !(typeof dest[name] === 'function' || (typeof dest[name] === 'object' && dest[name] !== null)))
|
|
61
|
+
dest[name] = value;
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/delve.js
CHANGED
|
@@ -1,44 +1,56 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Follow a path into an object.
|
|
3
|
-
* @param obj {object}
|
|
4
|
-
* @param path {string[]}
|
|
5
|
-
* @param createVal {*} If set, non-existent paths will be created and value at path will be set to createVal.
|
|
6
|
-
* @return {*} The value, or undefined if it can't be reached. */
|
|
7
|
-
export default function delve(obj, path, createVal = d) {
|
|
8
|
-
let isCreate = createVal !== d;
|
|
9
|
-
|
|
10
|
-
let len = path.length;
|
|
11
|
-
if (!obj && !isCreate && len)
|
|
12
|
-
return undefined;
|
|
13
|
-
|
|
14
|
-
let i = 0;
|
|
15
|
-
for (let srcProp of path) {
|
|
16
|
-
|
|
17
|
-
// If the path is undefined and we're not to the end yet:
|
|
18
|
-
if (obj[srcProp] === undefined) {
|
|
19
|
-
|
|
20
|
-
// If the next index is an integer or integer string.
|
|
21
|
-
if (isCreate) {
|
|
22
|
-
if (i < len - 1) {
|
|
23
|
-
// If next level path is a number, create as an array
|
|
24
|
-
let isArray = (path[i + 1] + '').match(/^\d+$/);
|
|
25
|
-
obj[srcProp] = isArray ? [] : {};
|
|
26
|
-
}
|
|
27
|
-
} else
|
|
28
|
-
return undefined; // can't traverse
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// If last item in path
|
|
32
|
-
if (isCreate && i === len - 1)
|
|
33
|
-
obj[srcProp] = createVal;
|
|
34
|
-
|
|
35
|
-
// Traverse deeper along destination object.
|
|
36
|
-
obj = obj[srcProp];
|
|
37
|
-
i++;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return obj;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Follow a path into an object.
|
|
3
|
+
* @param obj {object}
|
|
4
|
+
* @param path {string[]}
|
|
5
|
+
* @param createVal {*} If set, non-existent paths will be created and value at path will be set to createVal.
|
|
6
|
+
* @return {*} The value, or undefined if it can't be reached. */
|
|
7
|
+
export default function delve(obj, path, createVal = d) {
|
|
8
|
+
let isCreate = createVal !== d;
|
|
9
|
+
|
|
10
|
+
let len = path.length;
|
|
11
|
+
if (!obj && !isCreate && len)
|
|
12
|
+
return undefined;
|
|
13
|
+
|
|
14
|
+
let i = 0;
|
|
15
|
+
for (let srcProp of path) {
|
|
16
|
+
|
|
17
|
+
// If the path is undefined and we're not to the end yet:
|
|
18
|
+
if (obj[srcProp] === undefined) {
|
|
19
|
+
|
|
20
|
+
// If the next index is an integer or integer string.
|
|
21
|
+
if (isCreate) {
|
|
22
|
+
if (i < len - 1) {
|
|
23
|
+
// If next level path is a number, create as an array
|
|
24
|
+
let isArray = (path[i + 1] + '').match(/^\d+$/);
|
|
25
|
+
obj[srcProp] = isArray ? [] : {};
|
|
26
|
+
}
|
|
27
|
+
} else
|
|
28
|
+
return undefined; // can't traverse
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// If last item in path
|
|
32
|
+
if (isCreate && i === len - 1)
|
|
33
|
+
obj[srcProp] = createVal;
|
|
34
|
+
|
|
35
|
+
// Traverse deeper along destination object.
|
|
36
|
+
obj = obj[srcProp];
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return obj;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Is it an array and a path that can be evaluated by delve() ?
|
|
46
|
+
* We allow the first element to be null/undefined so binding can report errors.
|
|
47
|
+
* @param arr {Array|*}
|
|
48
|
+
* @returns {boolean} */
|
|
49
|
+
export function isDelvePath(arr) {
|
|
50
|
+
return Array.isArray(arr) && arr.length >=2 // An array of at least two elements.
|
|
51
|
+
&& (typeof arr[0] === 'object' || arr[0] === undefined) // Where the first element is an object, null, or undefined.
|
|
52
|
+
&& !arr.slice(1).find(p => typeof p !== 'string' && typeof p !== 'number'); // Path 1..x is only numbers and strings.
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// d means "don't create"
|
|
44
56
|
let d = {};
|
package/src/h.js
CHANGED
|
@@ -1,138 +1,220 @@
|
|
|
1
|
-
import Template from "./Template.js";
|
|
2
|
-
import Globals from "./Globals.js";
|
|
3
|
-
import toEl from "./toEl.js";
|
|
4
|
-
import Util from "./Util.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*
|
|
9
|
-
* Using h
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* @param
|
|
38
|
-
* @
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
let
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
let
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
else
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
1
|
+
import Template from "./Template.js";
|
|
2
|
+
import Globals from "./Globals.js";
|
|
3
|
+
import toEl from "./toEl.js";
|
|
4
|
+
import Util from "./Util.js";
|
|
5
|
+
import {jsxToTemplate, Fragment} from "./jsx.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Convert strings to HTMLNodes.
|
|
9
|
+
* Using h`...` as a tag will always create a Template.
|
|
10
|
+
* Using h() as a function() will always create a DOM element.
|
|
11
|
+
*
|
|
12
|
+
* Features beyond what standard js tagged template strings do:
|
|
13
|
+
* 1. h`` sub-expressions
|
|
14
|
+
* 2. functions, nodes, and arrays of nodes as sub-expressions.
|
|
15
|
+
* 3. html-escape all expressions by default, unless wrapped in h()
|
|
16
|
+
* 4. event binding
|
|
17
|
+
* 5. TODO: list more
|
|
18
|
+
*
|
|
19
|
+
* General rule:
|
|
20
|
+
* If h() is a function with null or an HTMLElement as its first argument create a Node.
|
|
21
|
+
* Otherwise create a template
|
|
22
|
+
*
|
|
23
|
+
* Currently supported:
|
|
24
|
+
*
|
|
25
|
+
* Create Tempataes
|
|
26
|
+
* 1. h`<b>Hello</b> ${'World'}!` // Create Template that can later be used to create nodes.
|
|
27
|
+
* 2. h('<b>Hello</b><u>Goodbye</u>'); // Create Template from string, that can later be used to create nodes.
|
|
28
|
+
*
|
|
29
|
+
* Add children to an element.
|
|
30
|
+
* 3. h(el, h`<b>${'Hi'}</b>`, ?options)
|
|
31
|
+
* 4. h(el, ?options)`<b>${'Hi'}</b>` // typical path used in render(). Create template and render its nodes to el.
|
|
32
|
+
*
|
|
33
|
+
* Create top-level element
|
|
34
|
+
* 5. h()`Hello<b>${'World'}!</b>`
|
|
35
|
+
*
|
|
36
|
+
* 6. h(string, object, ...) // Used for JSX
|
|
37
|
+
* @param htmlStrings {?HTMLElement|string|string[]|function():Template|{render:function()}}
|
|
38
|
+
* @param exprs {*[]|string|Template|Object}
|
|
39
|
+
* @return {Node|HTMLElement|Template|Function} */
|
|
40
|
+
/**
|
|
41
|
+
* Like h`...` but the fragment is parsed in the SVG namespace.
|
|
42
|
+
* Required for nested SVG fragments, since they're parsed standalone without an <svg> ancestor:
|
|
43
|
+
* h`<svg>${svg`<circle r="1"/>`}</svg>`
|
|
44
|
+
* @param htmlStrings {string[]}
|
|
45
|
+
* @param exprs {*[]}
|
|
46
|
+
* @return {Template} */
|
|
47
|
+
export function svg(htmlStrings, ...exprs) {
|
|
48
|
+
let template = new Template(htmlStrings, exprs);
|
|
49
|
+
template.svgMode = true;
|
|
50
|
+
return template;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const renderTemplateKey = Symbol('solariteRender');
|
|
54
|
+
|
|
55
|
+
// Unique default that detects h() called with no arguments.
|
|
56
|
+
// Using `arguments` alongside rest params would force the engine to materialize both per call.
|
|
57
|
+
const noArg = Symbol();
|
|
58
|
+
|
|
59
|
+
export default function h(htmlStrings=noArg, ...exprs) {
|
|
60
|
+
|
|
61
|
+
// 1. Tagged template: h`<div>...</div>`
|
|
62
|
+
if (Array.isArray(htmlStrings)) {
|
|
63
|
+
return new Template(htmlStrings, exprs);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 2. String to template, or JSX factory form h(tag, props, ...children)
|
|
67
|
+
else if (typeof htmlStrings === 'string' || htmlStrings instanceof String) {
|
|
68
|
+
let tagOrHtml = htmlStrings;
|
|
69
|
+
|
|
70
|
+
// 2a. JSX classic factory: h("tag", {props}, ...children)
|
|
71
|
+
if (exprs.length && (typeof exprs[0] === 'object' || exprs[0] === null)) {
|
|
72
|
+
let tag = tagOrHtml + '';
|
|
73
|
+
let props = exprs[0] || {};
|
|
74
|
+
let children = exprs.slice(1);
|
|
75
|
+
|
|
76
|
+
return jsxToTemplate(tag, props, children);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 2b. Plain html string => template: h('<div>...</div>')
|
|
80
|
+
else {
|
|
81
|
+
let html = tagOrHtml;
|
|
82
|
+
// If it starts with whitespace and then a tag, trim it.
|
|
83
|
+
if (/^\s+</.test(html))
|
|
84
|
+
html = html.trim();
|
|
85
|
+
return new Template([html], []);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 2c. JSX classic factory for a component or Fragment: h(Component, {props}, ...children)
|
|
90
|
+
// The transform passes the class/function (or the Fragment symbol) as the first argument.
|
|
91
|
+
else if (typeof htmlStrings === 'function' || htmlStrings === Fragment) {
|
|
92
|
+
return jsxToTemplate(htmlStrings, exprs[0] || {}, exprs.slice(1));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
else if (htmlStrings instanceof HTMLElement || htmlStrings instanceof DocumentFragment) {
|
|
96
|
+
|
|
97
|
+
// 3. Render template to element: h(el, template)
|
|
98
|
+
if (exprs[0] instanceof Template) {
|
|
99
|
+
|
|
100
|
+
/** @type Template */
|
|
101
|
+
let template = exprs[0];
|
|
102
|
+
let parent = htmlStrings;
|
|
103
|
+
let options = exprs[1];
|
|
104
|
+
template.render(parent, options);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 4. Render tagged template to element: h(el)`<div>...</div>`
|
|
108
|
+
else {
|
|
109
|
+
let parent = htmlStrings, options = exprs[0];
|
|
110
|
+
|
|
111
|
+
// The closure is cached on the element so repeated renders don't recreate it.
|
|
112
|
+
if (options === undefined) {
|
|
113
|
+
let cached = parent[renderTemplateKey];
|
|
114
|
+
if (cached)
|
|
115
|
+
return cached;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Return a tagged template function that applies the tagged template to parent.
|
|
119
|
+
let renderTemplate = (htmlStrings, ...exprs) => {
|
|
120
|
+
// Remove shadowroot if present. TODO: This could mess up paths?
|
|
121
|
+
if (parent.shadowRoot)
|
|
122
|
+
parent.innerHTML = '';
|
|
123
|
+
|
|
124
|
+
Globals.rendered.add(parent)
|
|
125
|
+
let template = new Template(htmlStrings, exprs);
|
|
126
|
+
return template.render(parent, options);
|
|
127
|
+
}
|
|
128
|
+
if (options === undefined)
|
|
129
|
+
parent[renderTemplateKey] = renderTemplate;
|
|
130
|
+
return renderTemplate;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 5. Create a static element: h()`<div></div>`
|
|
135
|
+
else if (htmlStrings === noArg) {
|
|
136
|
+
return (htmlStrings, ...exprs) => {
|
|
137
|
+
let template = h(htmlStrings, ...exprs);
|
|
138
|
+
return toEl(template);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 6. Help toEl() with objects: h(this)`<div>...</div>` inside an object's render()
|
|
143
|
+
// Intercepts the main h(this)`...` function call inside render().
|
|
144
|
+
// TODO: This path doesn't handle embeds like data-id="..."
|
|
145
|
+
else if (typeof htmlStrings === 'object' && Globals.objToEl.has(htmlStrings)) {
|
|
146
|
+
let obj = htmlStrings;
|
|
147
|
+
|
|
148
|
+
if (obj.constructor.name !== 'Object')
|
|
149
|
+
throw new Error(`Solarate Web Component class ${obj.constructor?.name} must extend HTMLElement.`);
|
|
150
|
+
|
|
151
|
+
// Jsx with h(this, <jsx>)
|
|
152
|
+
if (exprs[0] instanceof Template) {
|
|
153
|
+
let template = exprs[0];
|
|
154
|
+
let el = template.render();
|
|
155
|
+
Globals.objToEl.set(obj, el);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// h(this)`<div>...</div>`
|
|
159
|
+
else
|
|
160
|
+
return function(...args) {
|
|
161
|
+
let template = h(...args);
|
|
162
|
+
let el = template.render();
|
|
163
|
+
Globals.objToEl.set(obj, el);
|
|
164
|
+
}.bind(obj);
|
|
165
|
+
}
|
|
166
|
+
// TODO: Handle other primitive types?
|
|
167
|
+
else if (Util.isFalsy(htmlStrings))
|
|
168
|
+
return new Template();
|
|
169
|
+
|
|
170
|
+
else
|
|
171
|
+
throw new Error('h() does not support argument of type: ' + (htmlStrings ? typeof htmlStrings : htmlStrings))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// h.map caches each item's Template keyed by the item's identity, so a re-render returns
|
|
175
|
+
// the SAME Template instance for any item whose reference is unchanged. The reconciler's
|
|
176
|
+
// `ng.template === item` fast path (PathToNodes.applyKeyed/applyDiff) then skips rebuilding
|
|
177
|
+
// and comparing that row. A WeakMap is used instead of a symbol property so the idiomatic
|
|
178
|
+
// immutable update `{...item, x}` yields a fresh object that ISN'T in the cache and re-renders;
|
|
179
|
+
// a symbol property would be copied by spread and silently reuse the stale Template.
|
|
180
|
+
const mapCache = new WeakMap();
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Render a list, reusing each item's DOM for as long as the item is the SAME object.
|
|
184
|
+
*
|
|
185
|
+
* Treat items as immutable: to change a row, replace it with a new object rather than
|
|
186
|
+
* mutating it in place, so its identity changes and it re-renders. This is the contract
|
|
187
|
+
* Solid's <For> and React's keyed lists use. It takes no deps — the object identity IS
|
|
188
|
+
* the dependency — so the call site stays a plain list with no caching code.
|
|
189
|
+
*
|
|
190
|
+
* Each item must be a distinct object, and an object must appear in only one h.map.
|
|
191
|
+
* Non-object items (strings, numbers) are never cached and rebuild every render.
|
|
192
|
+
*
|
|
193
|
+
* h.immutableMap is the same function under a longer, self-documenting name; use whichever
|
|
194
|
+
* reads better: h.map for brevity, h.immutableMap to flag the immutability contract.
|
|
195
|
+
*
|
|
196
|
+
* ${h.map(this.rows, row => h`<tr key=${row.id}>${row.label}</tr>`)}
|
|
197
|
+
*
|
|
198
|
+
* @param items {Array} The list to render.
|
|
199
|
+
* @param fn {function(item:*):Template} Builds an item's Template; called only for new items.
|
|
200
|
+
* @return {Template[]} */
|
|
201
|
+
h.map = (items, fn) => {
|
|
202
|
+
let result = new Array(items.length);
|
|
203
|
+
for (let i=0; i<items.length; i++) {
|
|
204
|
+
let item = items[i];
|
|
205
|
+
if (item !== null && typeof item === 'object') {
|
|
206
|
+
let template = mapCache.get(item);
|
|
207
|
+
if (template === undefined) {
|
|
208
|
+
template = fn(item);
|
|
209
|
+
mapCache.set(item, template);
|
|
210
|
+
}
|
|
211
|
+
result[i] = template;
|
|
212
|
+
}
|
|
213
|
+
else
|
|
214
|
+
result[i] = fn(item);
|
|
215
|
+
}
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
h.immutableMap = h.map;
|
|
220
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { jsx, jsxs, jsxDEV, Fragment, jsxTemplate, jsxAttr, jsxEscape, JSX } from './jsx-runtime';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Dev entry point used by toolchains configured with the automatic runtime in development mode
|
|
2
|
+
* (e.g. esbuild/tsc `jsxDEV`). It re-exports the same runtime; the extra dev source args are
|
|
3
|
+
* ignored. */
|
|
4
|
+
export { jsx, jsxs, jsxDEV, Fragment, jsxTemplate, jsxAttr, jsxEscape } from './jsx-runtime.js';
|
|
5
|
+
export { jsxDEV as jsxDEVRuntime } from './jsx-runtime.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Template } from './Solarite';
|
|
2
|
+
|
|
3
|
+
export function jsxTemplate(strings: string[], ...exprs: any[]): Template;
|
|
4
|
+
export function jsxAttr(name: string, value: any): any;
|
|
5
|
+
export function jsxEscape(value: any): any;
|
|
6
|
+
export function jsx(tag: any, props: any, key?: any): Template;
|
|
7
|
+
export function jsxs(tag: any, props: any, key?: any): Template;
|
|
8
|
+
export function jsxDEV(tag: any, props: any, key?: any): Template;
|
|
9
|
+
export const Fragment: unique symbol;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Types for `tsconfig` / `deno.json` with `jsx: "react-jsx" | "precompile"` and
|
|
13
|
+
* `jsxImportSource: "solarite"`. IntrinsicElements is intentionally permissive for now: any
|
|
14
|
+
* lowercase tag with any attributes is allowed. A JSX expression evaluates to a Solarite Template. */
|
|
15
|
+
export namespace JSX {
|
|
16
|
+
type Element = Template;
|
|
17
|
+
interface ElementChildrenAttribute { children: {}; }
|
|
18
|
+
interface IntrinsicElements {
|
|
19
|
+
[tagName: string]: any;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solarite JSX runtime. Configure your toolchain with `jsxImportSource: "solarite"` (automatic
|
|
3
|
+
* runtime) or have Deno's `jsx:"precompile"` / a Solarite build plugin target this module.
|
|
4
|
+
*
|
|
5
|
+
* Tier 1 (precompile): jsxTemplate/jsxAttr/jsxEscape map hoisted statics straight onto Template.
|
|
6
|
+
* Tier 2 (automatic): jsx/jsxs/jsxDEV build a Template per render via the shape-interning factory.
|
|
7
|
+
*
|
|
8
|
+
* See src/jsx.js for the shared core (jsxToTemplate, JsxAttr, Fragment). */
|
|
9
|
+
import Template from "./Template.js";
|
|
10
|
+
import {JsxAttr, jsxToTemplate, Fragment} from "./jsx.js";
|
|
11
|
+
|
|
12
|
+
export {Fragment};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Tier 1: build a Template from hoisted static strings and expression holes, with the statics'
|
|
16
|
+
* stable array identity feeding Shell cache / NodeGroup reuse just like a tagged template.
|
|
17
|
+
* @param strings {string[]} Module-hoisted static html (same array every render).
|
|
18
|
+
* @param exprs {...*} jsxAttr() pairs, jsxEscape() children, and bare boolean-attr strings.
|
|
19
|
+
* @return {Template} */
|
|
20
|
+
export function jsxTemplate(strings, ...exprs) {
|
|
21
|
+
let key;
|
|
22
|
+
for (let i = 0; i < exprs.length; i++) {
|
|
23
|
+
let e = exprs[i];
|
|
24
|
+
// A `key` prop arrives as jsxAttr("key", value) at an attribute hole. Lift it onto the
|
|
25
|
+
// Template for keyed diffing and blank the hole so it never renders as an attribute.
|
|
26
|
+
if (e instanceof JsxAttr && e.name === 'key') {
|
|
27
|
+
key = e.value;
|
|
28
|
+
exprs[i] = '';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
let t = new Template(strings, exprs);
|
|
32
|
+
if (key !== undefined)
|
|
33
|
+
t.key = key;
|
|
34
|
+
return t;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tier 1: a whole-attribute hole, e.g. `<a ` + jsxAttr("href", v) + `>`. PathToAttribs detects
|
|
39
|
+
* this and routes the value through normal attribute/event/property application.
|
|
40
|
+
* @param name {string}
|
|
41
|
+
* @param value {*}
|
|
42
|
+
* @return {JsxAttr} */
|
|
43
|
+
export function jsxAttr(name, value) {
|
|
44
|
+
return new JsxAttr(name, value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Tier 1: a child hole. Solarite already html-escapes child expressions (strings render as text,
|
|
49
|
+
* Templates/arrays as nodes), so this is the identity function.
|
|
50
|
+
* @param value {*}
|
|
51
|
+
* @return {*} */
|
|
52
|
+
export function jsxEscape(value) {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Tier 2 automatic runtime: a single-child element. props.children is one child hole.
|
|
58
|
+
* @param tag {string|Function|symbol}
|
|
59
|
+
* @param props {Object}
|
|
60
|
+
* @param key {*}
|
|
61
|
+
* @return {Template} */
|
|
62
|
+
export function jsx(tag, props, key) {
|
|
63
|
+
props = props || {};
|
|
64
|
+
let children = ('children' in props) ? [props.children] : [];
|
|
65
|
+
return jsxToTemplate(tag, props, children, key);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Tier 2 automatic runtime: multiple static children. props.children is the children array.
|
|
70
|
+
* @param tag {string|Function|symbol}
|
|
71
|
+
* @param props {Object}
|
|
72
|
+
* @param key {*}
|
|
73
|
+
* @return {Template} */
|
|
74
|
+
export function jsxs(tag, props, key) {
|
|
75
|
+
props = props || {};
|
|
76
|
+
let children = props.children;
|
|
77
|
+
children = Array.isArray(children) ? children : (children === undefined ? [] : [children]);
|
|
78
|
+
return jsxToTemplate(tag, props, children, key);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Dev variant (extra source/self args are ignored). */
|
|
82
|
+
export function jsxDEV(tag, props, key) {
|
|
83
|
+
return jsx(tag, props, key);
|
|
84
|
+
}
|