synstate-dom 0.6.0 → 0.7.1
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 +1 -1
- package/dist/bind/bind-props.mjs +14 -1
- package/dist/bind/bind-props.mjs.map +1 -1
- package/dist/control/for.d.mts +6 -0
- package/dist/control/for.d.mts.map +1 -1
- package/dist/control/for.mjs +27 -15
- package/dist/control/for.mjs.map +1 -1
- package/dist/control/mount-switch.d.mts +6 -0
- package/dist/control/mount-switch.d.mts.map +1 -1
- package/dist/control/mount-switch.mjs +27 -12
- package/dist/control/mount-switch.mjs.map +1 -1
- package/dist/control/portal.d.mts.map +1 -1
- package/dist/control/portal.mjs +2 -2
- package/dist/control/portal.mjs.map +1 -1
- package/dist/internal/after-attach.d.mts +46 -0
- package/dist/internal/after-attach.d.mts.map +1 -0
- package/dist/internal/after-attach.mjs +170 -0
- package/dist/internal/after-attach.mjs.map +1 -0
- package/dist/internal/bind-value.mjs +11 -0
- package/dist/internal/bind-value.mjs.map +1 -1
- package/dist/internal/index.d.mts +1 -0
- package/dist/internal/index.d.mts.map +1 -1
- package/dist/internal/index.mjs +1 -0
- package/dist/internal/index.mjs.map +1 -1
- package/dist/jsx/element-props.d.mts +3 -1
- package/dist/jsx/element-props.d.mts.map +1 -1
- package/dist/jsx/jsx.mjs +2 -2
- package/dist/jsx/jsx.mjs.map +1 -1
- package/dist/render.d.mts +2 -1
- package/dist/render.d.mts.map +1 -1
- package/dist/render.mjs +19 -4
- package/dist/render.mjs.map +1 -1
- package/package.json +10 -10
- package/src/bind/bind-props.mts +22 -1
- package/src/control/for.mts +43 -19
- package/src/control/mount-switch.mts +38 -15
- package/src/control/portal.mts +7 -3
- package/src/internal/after-attach.mts +238 -0
- package/src/internal/bind-value.mts +14 -1
- package/src/internal/index.mts +1 -0
- package/src/jsx/element-props.mts +6 -3
- package/src/jsx/element-props.test.mts +18 -0
- package/src/jsx/jsx.mts +2 -2
- package/src/render.mts +32 -6
|
@@ -13,13 +13,15 @@ import type { EventPropNames } from './event-prop-names.mjs';
|
|
|
13
13
|
* - `style` is either a `cssText` string or an object of CSS properties, each
|
|
14
14
|
* bound with `style.setProperty`, so the keys are the hyphenated names
|
|
15
15
|
* (`'background-color'`, `'--accent'`).
|
|
16
|
+
* - An `aria-*` attribute also takes a number (`aria-level`, `aria-valuenow`)
|
|
17
|
+
* and a boolean, written as `"true"` or `"false"`.
|
|
16
18
|
*/
|
|
17
19
|
export type CommonElementProps<E extends Element> = Readonly<{
|
|
18
20
|
ref?: (element: E) => void;
|
|
19
21
|
children?: Child;
|
|
20
22
|
class?: Bindable<string> | ClassObject;
|
|
21
23
|
style?: Bindable<string> | StyleObject;
|
|
22
|
-
}> & Partial<ReadonlyRecord<`aria-${string}
|
|
24
|
+
}> & Partial<ReadonlyRecord<`aria-${string}`, Bindable<boolean | number | string | undefined>>> & Partial<ReadonlyRecord<`data-${string}`, Bindable<string | undefined>>>;
|
|
23
25
|
/** `class` as one `Bindable<boolean>` per class name. */
|
|
24
26
|
export type ClassObject = ReadonlyRecord<string, Bindable<boolean>>;
|
|
25
27
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"element-props.d.mts","sourceRoot":"","sources":["../../src/jsx/element-props.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,UAAU,EACX,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D
|
|
1
|
+
{"version":3,"file":"element-props.d.mts","sourceRoot":"","sources":["../../src/jsx/element-props.mts"],"names":[],"mappings":"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"}
|
package/dist/jsx/jsx.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { hasKey, isRecord } from 'ts-data-forge';
|
|
2
2
|
import { bindProps } from '../bind/index.mjs';
|
|
3
|
-
import { svgTagNames } from '../internal/index.mjs';
|
|
3
|
+
import { appendTo, mountThenInsert, svgTagNames } from '../internal/index.mjs';
|
|
4
4
|
import { isChild, mountChild } from './child.mjs';
|
|
5
5
|
import { mountable } from './mountable.mjs';
|
|
6
6
|
|
|
@@ -36,7 +36,7 @@ const mountElement = (tag, props) => {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
if (hasKey(props, 'children') && isChild(props.children)) {
|
|
39
|
-
|
|
39
|
+
mountThenInsert(mountChild, props.children, appendTo, element);
|
|
40
40
|
}
|
|
41
41
|
bindProps(element, props);
|
|
42
42
|
return element;
|
package/dist/jsx/jsx.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsx.mjs","sourceRoot":"","sources":["../../src/jsx/jsx.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"jsx.mjs","sourceRoot":"","sources":["../../src/jsx/jsx.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAE,UAAU,EAAc,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAkB,MAAM,iBAAiB,CAAC;AAE5D;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,IAAiD,EACjD,KAAQ,EACR,GAAa,EACF,EAAE;IACb,6EAA6E;IAC7E,sEAAsE;IACtE,MAAM,YAAY,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,GAAG,KAAK,EAAE,GAAG,EAAY,CAAC;IAE9E,OAAO,OAAO,IAAI,KAAK,QAAQ;QAC7B,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC5C,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;AAClD,CAAC,CAAC;AAEF,iEAAiE;AACjE,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAqC,EAAa,EAAE,CAC3E,SAAS,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC;AAEpD,MAAM,aAAa,GAAG,CAAC,KAAc,EAAuC,EAAE,CAC5E,OAAO,KAAK,KAAK,UAAU,CAAC;AAE9B,MAAM,YAAY,GAAG,CAAC,GAAW,EAAE,KAAc,EAAW,EAAE;IAC5D,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC;QACpC,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC,YAAY,EAAE,GAAG,CAAC;QAC7C,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IAEhC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACrD,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACrB,CAAC;IAED,wEAAwE;IACxE,IAAI,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzD,eAAe,CAAC,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IACjE,CAAC;IAED,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAE1B,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,4BAA4B,CAAC;AAElD,MAAM,aAAa,GAAwB,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC"}
|
package/dist/render.d.mts
CHANGED
|
@@ -5,7 +5,8 @@ import { type JSX } from './jsx/index.mjs';
|
|
|
5
5
|
*
|
|
6
6
|
* `app` is called once, inside a new root scope; everything created while it
|
|
7
7
|
* and the description it returns are mounted belongs to that scope, and
|
|
8
|
-
* unmounting disposes it and removes the nodes.
|
|
8
|
+
* unmounting disposes it and removes the nodes. If mounting throws, what was
|
|
9
|
+
* mounted is disposed and removed before the error is rethrown.
|
|
9
10
|
*
|
|
10
11
|
* `render(App, el)` rather than `render(<App />, el)`: taking the component
|
|
11
12
|
* makes it plain that it is evaluated here.
|
package/dist/render.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.mts","sourceRoot":"","sources":["../src/render.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"render.d.mts","sourceRoot":"","sources":["../src/render.mts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,GAAG,EAAW,MAAM,iBAAiB,CAAC;AAEpD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,MAAM,QACZ,MAAM,GAAG,CAAC,OAAO,aACX,OAAO,KACjB,CAAC,MAAM,IAAI,CAmCb,CAAC"}
|
package/dist/render.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createScope } from 'synstate';
|
|
2
|
+
import { appendTo, mountThenInsert } from './internal/index.mjs';
|
|
2
3
|
import { toNodes } from './jsx/index.mjs';
|
|
3
4
|
|
|
4
5
|
|
|
@@ -11,15 +12,29 @@ import { toNodes } from './jsx/index.mjs';
|
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
|
|
15
|
+
|
|
14
16
|
export const render = (app, container) => {
|
|
15
17
|
const root = createScope(undefined);
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
return () => {
|
|
18
|
+
const mut_inserted = { nodes: [] };
|
|
19
|
+
const unmount = () => {
|
|
19
20
|
root.dispose();
|
|
20
|
-
for (const node of nodes) {
|
|
21
|
+
for (const node of mut_inserted.nodes) {
|
|
21
22
|
node.remove();
|
|
22
23
|
}
|
|
23
24
|
};
|
|
25
|
+
try {
|
|
26
|
+
root.run(() => mountThenInsert(mountApp, app, (into, nodes) => {
|
|
27
|
+
mut_inserted.nodes = nodes;
|
|
28
|
+
appendTo(into, nodes);
|
|
29
|
+
}, container));
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
unmount();
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
return unmount;
|
|
24
38
|
};
|
|
39
|
+
const mountApp = (app) => toNodes(app().mount());
|
|
25
40
|
//# sourceMappingURL=render.mjs.map
|
package/dist/render.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.mjs","sourceRoot":"","sources":["../src/render.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAY,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAEpD
|
|
1
|
+
{"version":3,"file":"render.mjs","sourceRoot":"","sources":["../src/render.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAY,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CACpB,GAAsB,EACtB,SAAkB,EACJ,EAAE;IAChB,MAAM,IAAI,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAEpC,MAAM,YAAY,GAAoC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAEpE,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,IAAI,CAAC,OAAO,EAAE,CAAC;QAEf,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,KAAK,EAAE,CAAC;YACtC,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,CAAC;IACH,CAAC,CAAC;IAEF,IAAI,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CACZ,eAAe,CACb,QAAQ,EACR,GAAG,EACH,CAAC,IAAa,EAAE,KAA2B,EAAE,EAAE;YAC7C,YAAY,CAAC,KAAK,GAAG,KAAK,CAAC;YAE3B,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACxB,CAAC,EACD,SAAS,CACV,CACF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,yEAAyE;QACzE,0EAA0E;QAC1E,OAAO,EAAE,CAAC;QAEV,MAAM,KAAK,CAAC;IACd,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,GAAsB,EAAwB,EAAE,CAChE,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "synstate-dom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Compiler-free fine-grained UI library that binds synstate observables directly to the DOM",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -44,21 +44,21 @@
|
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@vitest/browser-playwright": "5.0.5",
|
|
47
|
-
"eslint-config-typed": "7.
|
|
48
|
-
"eslint-plugin-ts-data-forge": "0.10.
|
|
49
|
-
"eslint-plugin-ts-fortress": "1.3.
|
|
50
|
-
"eslint-plugin-ts-type-forge": "0.8.
|
|
47
|
+
"eslint-config-typed": "7.5.0",
|
|
48
|
+
"eslint-plugin-ts-data-forge": "0.10.10",
|
|
49
|
+
"eslint-plugin-ts-fortress": "1.3.14",
|
|
50
|
+
"eslint-plugin-ts-type-forge": "0.8.19",
|
|
51
51
|
"playwright": "1.64.1",
|
|
52
52
|
"prettier": "3.9.10",
|
|
53
53
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
54
54
|
"prettier-plugin-packagejson": "3.0.2",
|
|
55
|
-
"synstate": "
|
|
56
|
-
"ts-data-forge": "14.
|
|
57
|
-
"ts-repo-utils": "10.7.
|
|
55
|
+
"synstate": "7.0.1",
|
|
56
|
+
"ts-data-forge": "14.11.0",
|
|
57
|
+
"ts-repo-utils": "10.7.3"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"synstate": ">=
|
|
61
|
-
"ts-data-forge": ">=14.
|
|
60
|
+
"synstate": ">=7.0.1",
|
|
61
|
+
"ts-data-forge": ">=14.11.0"
|
|
62
62
|
},
|
|
63
63
|
"engines": {
|
|
64
64
|
"node": ">=22.23.4",
|
package/src/bind/bind-props.mts
CHANGED
|
@@ -49,6 +49,25 @@ const writeAttribute = (
|
|
|
49
49
|
element.setAttribute(name, unknownToString(value));
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* An `aria-*` attribute reads `""` as its default, so a boolean is written as
|
|
54
|
+
* `"true"` or `"false"`, as React does (#3165); anything else as
|
|
55
|
+
* {@link writeAttribute} writes it.
|
|
56
|
+
*/
|
|
57
|
+
const writeAriaAttribute = (
|
|
58
|
+
element: Element,
|
|
59
|
+
name: string,
|
|
60
|
+
value: unknown,
|
|
61
|
+
): void => {
|
|
62
|
+
if (isBoolean(value)) {
|
|
63
|
+
element.setAttribute(name, value ? 'true' : 'false');
|
|
64
|
+
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
writeAttribute(element, name, value);
|
|
69
|
+
};
|
|
70
|
+
|
|
52
71
|
/**
|
|
53
72
|
* Every value is a `Bindable` at run time: what is neither an observable nor a
|
|
54
73
|
* view is a constant. An omitted prop is `undefined`, which is not.
|
|
@@ -269,8 +288,10 @@ const bindProp = (element: Element, key: string, value: unknown): void => {
|
|
|
269
288
|
|
|
270
289
|
const name = attributeNameOf[key] ?? key;
|
|
271
290
|
|
|
291
|
+
const write = name.startsWith('aria-') ? writeAriaAttribute : writeAttribute;
|
|
292
|
+
|
|
272
293
|
bindValue(value, (v) => {
|
|
273
|
-
|
|
294
|
+
write(element, name, v);
|
|
274
295
|
});
|
|
275
296
|
};
|
|
276
297
|
|
package/src/control/for.mts
CHANGED
|
@@ -11,7 +11,12 @@ import {
|
|
|
11
11
|
import { Arr, Optional } from 'ts-data-forge';
|
|
12
12
|
import type { BivariantHack } from 'ts-type-forge';
|
|
13
13
|
import { toObservable, type Bindable } from '../constant/index.mjs';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
applyWhenAttached,
|
|
16
|
+
bindValue,
|
|
17
|
+
insertBefore,
|
|
18
|
+
mountThenInsert,
|
|
19
|
+
} from '../internal/index.mjs';
|
|
15
20
|
import { mountable, toNodes, type JSX } from '../jsx/index.mjs';
|
|
16
21
|
import { longestIncreasingSubsequence } from './longest-increasing-subsequence.mjs';
|
|
17
22
|
|
|
@@ -31,6 +36,12 @@ import { longestIncreasingSubsequence } from './longest-increasing-subsequence.m
|
|
|
31
36
|
* nodes; the rows that stay are moved as little as the longest increasing
|
|
32
37
|
* subsequence allows.
|
|
33
38
|
*
|
|
39
|
+
* An update that arrives before the anchor is in a parent (a later sibling
|
|
40
|
+
* wrote `items` while the enclosing element was still mounting its children) is
|
|
41
|
+
* applied, with the latest `items`, once the nodes `For` returned are inserted.
|
|
42
|
+
* One that a row being mounted writes is applied, with the latest `items`, once
|
|
43
|
+
* the rows of the update under way are all mounted and inserted (#3141).
|
|
44
|
+
*
|
|
34
45
|
* `key` is required, as in React; `(_, i) => i` matches rows by position. A key
|
|
35
46
|
* that appears twice is reported with `console.error`, and its later
|
|
36
47
|
* occurrences are rebuilt on every update.
|
|
@@ -72,7 +83,10 @@ export const For = <T,>({
|
|
|
72
83
|
|
|
73
84
|
const rowStarts = new WeakSet<Node>();
|
|
74
85
|
|
|
75
|
-
const mut_initial: { nodes: readonly ChildNode[] } = {
|
|
86
|
+
const mut_initial: { nodes: readonly ChildNode[]; built: boolean } = {
|
|
87
|
+
nodes: [],
|
|
88
|
+
built: false,
|
|
89
|
+
};
|
|
76
90
|
|
|
77
91
|
/** Creates a row in a scope of its own and returns its first nodes. */
|
|
78
92
|
const createRow = (
|
|
@@ -161,12 +175,11 @@ export const For = <T,>({
|
|
|
161
175
|
range.deleteContents();
|
|
162
176
|
};
|
|
163
177
|
|
|
164
|
-
|
|
178
|
+
/** Brings the attached rows in line with `entries`. */
|
|
179
|
+
const update = (entries: Entries<T>): void => {
|
|
165
180
|
const previousOrder = Array.from(mut_rows.keys());
|
|
166
181
|
|
|
167
|
-
|
|
168
|
-
removeAllAtOnceIfEveryKeyGoes(previousOrder, entries);
|
|
169
|
-
}
|
|
182
|
+
removeAllAtOnceIfEveryKeyGoes(previousOrder, entries);
|
|
170
183
|
|
|
171
184
|
for (const [rowKey, row] of mut_rows) {
|
|
172
185
|
if (entries.byKey.has(rowKey)) {
|
|
@@ -178,16 +191,6 @@ export const For = <T,>({
|
|
|
178
191
|
mut_rows.delete(rowKey);
|
|
179
192
|
}
|
|
180
193
|
|
|
181
|
-
if (anchor.parentNode === null) {
|
|
182
|
-
mut_initial.nodes = entries.order.flatMap((rowKey) => {
|
|
183
|
-
const entry = entries.byKey.get(rowKey);
|
|
184
|
-
|
|
185
|
-
return entry === undefined ? [] : createRow(rowKey, entry);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
194
|
if (Arr.eq(previousOrder, entries.order)) {
|
|
192
195
|
return;
|
|
193
196
|
}
|
|
@@ -224,11 +227,14 @@ export const For = <T,>({
|
|
|
224
227
|
continue;
|
|
225
228
|
}
|
|
226
229
|
|
|
227
|
-
const [start
|
|
230
|
+
const [start] = mountThenInsert(
|
|
231
|
+
(e: Entry<T>) => createRow(rowKey, e),
|
|
232
|
+
entry,
|
|
233
|
+
insertBefore,
|
|
234
|
+
mut_next,
|
|
235
|
+
);
|
|
228
236
|
|
|
229
237
|
if (start !== undefined) {
|
|
230
|
-
mut_next.before(start, ...rest);
|
|
231
|
-
|
|
232
238
|
mut_next = start;
|
|
233
239
|
}
|
|
234
240
|
|
|
@@ -243,6 +249,24 @@ export const For = <T,>({
|
|
|
243
249
|
}
|
|
244
250
|
|
|
245
251
|
reorderMap(mut_rows, entries.order);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const updateWhenAttached = applyWhenAttached(anchor, update);
|
|
255
|
+
|
|
256
|
+
bindValue(entries$, (entries) => {
|
|
257
|
+
if (!mut_initial.built) {
|
|
258
|
+
mut_initial.built = true;
|
|
259
|
+
|
|
260
|
+
mut_initial.nodes = entries.order.flatMap((rowKey) => {
|
|
261
|
+
const entry = entries.byKey.get(rowKey);
|
|
262
|
+
|
|
263
|
+
return entry === undefined ? [] : createRow(rowKey, entry);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
updateWhenAttached(entries);
|
|
246
270
|
});
|
|
247
271
|
|
|
248
272
|
return Arr.toPushed(mut_initial.nodes, anchor);
|
|
@@ -6,7 +6,12 @@ import {
|
|
|
6
6
|
} from 'synstate';
|
|
7
7
|
import { Arr } from 'ts-data-forge';
|
|
8
8
|
import type { Bindable } from '../constant/index.mjs';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
applyWhenAttached,
|
|
11
|
+
bindValue,
|
|
12
|
+
insertBefore,
|
|
13
|
+
mountThenInsert,
|
|
14
|
+
} from '../internal/index.mjs';
|
|
10
15
|
import { toNodes, type Mountable, type MountedNodes } from '../jsx/index.mjs';
|
|
11
16
|
|
|
12
17
|
/**
|
|
@@ -22,6 +27,12 @@ import { toNodes, type Mountable, type MountedNodes } from '../jsx/index.mjs';
|
|
|
22
27
|
*
|
|
23
28
|
* `branch` runs inside the new scope, so whatever it creates belongs to the
|
|
24
29
|
* branch.
|
|
30
|
+
*
|
|
31
|
+
* A value that arrives before the anchor is in a parent (a later sibling wrote
|
|
32
|
+
* it while the enclosing element was still mounting its children) is kept, and
|
|
33
|
+
* the switch follows the latest one once the nodes it returned are inserted.
|
|
34
|
+
* A value the branch being mounted writes itself is applied once that branch is
|
|
35
|
+
* inserted (#3141).
|
|
25
36
|
*/
|
|
26
37
|
export const mountSwitch = <V, K>(
|
|
27
38
|
value: Bindable<V>,
|
|
@@ -37,17 +48,8 @@ export const mountSwitch = <V, K>(
|
|
|
37
48
|
initialNodes: readonly ChildNode[];
|
|
38
49
|
} = { current: undefined, initialNodes: [] };
|
|
39
50
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (mut_state.current !== undefined) {
|
|
44
|
-
if (Object.is(mut_state.current.key, key)) {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
mut_state.current.scope.dispose();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
+
/** Mounts the branch for `v` in a scope of its own and returns its nodes. */
|
|
52
|
+
const mountBranch = (key: K, v: V): readonly ChildNode[] => {
|
|
51
53
|
const scope = createScope(parent);
|
|
52
54
|
|
|
53
55
|
const nodes = scope.run(() => {
|
|
@@ -64,10 +66,31 @@ export const mountSwitch = <V, K>(
|
|
|
64
66
|
|
|
65
67
|
mut_state.current = { key, scope };
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
return nodes;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** Switches the attached switch to the branch for `v`. */
|
|
73
|
+
const switchTo = (v: V): void => {
|
|
74
|
+
const key = keyOf(v);
|
|
75
|
+
|
|
76
|
+
if (mut_state.current !== undefined) {
|
|
77
|
+
if (Object.is(mut_state.current.key, key)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
mut_state.current.scope.dispose();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
mountThenInsert((k: K) => mountBranch(k, v), key, insertBefore, anchor);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const switchWhenAttached = applyWhenAttached(anchor, switchTo);
|
|
88
|
+
|
|
89
|
+
bindValue(value, (v) => {
|
|
90
|
+
if (mut_state.current === undefined) {
|
|
91
|
+
mut_state.initialNodes = mountBranch(keyOf(v), v);
|
|
69
92
|
} else {
|
|
70
|
-
|
|
93
|
+
switchWhenAttached(v);
|
|
71
94
|
}
|
|
72
95
|
});
|
|
73
96
|
|
package/src/control/portal.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { onScopeDispose } from 'synstate';
|
|
2
|
+
import { appendTo, mountThenInsert } from '../internal/index.mjs';
|
|
2
3
|
import { mountChild, mountable, type Child, type JSX } from '../jsx/index.mjs';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -18,9 +19,12 @@ export const Portal = ({
|
|
|
18
19
|
children: Child;
|
|
19
20
|
}>): JSX.Element =>
|
|
20
21
|
mountable(() => {
|
|
21
|
-
const nodes =
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const nodes = mountThenInsert(
|
|
23
|
+
mountChild,
|
|
24
|
+
children,
|
|
25
|
+
appendTo,
|
|
26
|
+
target ?? document.body,
|
|
27
|
+
);
|
|
24
28
|
|
|
25
29
|
onScopeDispose(() => {
|
|
26
30
|
for (const node of nodes) {
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { getCurrentScope, onScopeDispose } from 'synstate';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Nodes are mounted before they are inserted: an element mounts all its
|
|
5
|
+
* children and then appends them, `render` mounts the app and then appends it,
|
|
6
|
+
* and `For` and `If` mount a row or a branch and then insert it. A child
|
|
7
|
+
* mounted later that writes to what an earlier `For` or `If` reads updates them
|
|
8
|
+
* while their anchor is in no parent yet (#2935).
|
|
9
|
+
*
|
|
10
|
+
* Every mount-and-insert goes through {@link mountThenInsert}. A `For` or a
|
|
11
|
+
* switch takes {@link applyWhenAttached} when it is mounted, which notes the
|
|
12
|
+
* mount open then; a value that arrives while its anchor is detached is kept,
|
|
13
|
+
* and the latest one is applied once that mount has inserted its nodes.
|
|
14
|
+
*
|
|
15
|
+
* `mount` takes `arg` rather than closing over it, so that the element and
|
|
16
|
+
* `render` paths allocate nothing; a mount that nothing defers to allocates no
|
|
17
|
+
* frame.
|
|
18
|
+
*/
|
|
19
|
+
export const mountThenInsert = <A, N, T>(
|
|
20
|
+
mount: (input: A) => N,
|
|
21
|
+
arg: A,
|
|
22
|
+
insert: (into: T, mounted: N) => void,
|
|
23
|
+
target: T,
|
|
24
|
+
): N => {
|
|
25
|
+
mut_state.depth += 1;
|
|
26
|
+
|
|
27
|
+
mut_state.lastId += 1;
|
|
28
|
+
|
|
29
|
+
const { depth } = mut_state;
|
|
30
|
+
|
|
31
|
+
mut_state.ids[depth] = mut_state.lastId;
|
|
32
|
+
|
|
33
|
+
// Left behind by a mount at this depth that threw.
|
|
34
|
+
takeFrame(depth);
|
|
35
|
+
|
|
36
|
+
const nodes = runMount(mount, arg, depth);
|
|
37
|
+
|
|
38
|
+
const frame = takeFrame(depth);
|
|
39
|
+
|
|
40
|
+
insert(target, nodes);
|
|
41
|
+
|
|
42
|
+
if (frame !== undefined) {
|
|
43
|
+
runAll(frame);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return nodes;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Appends `nodes` to `parent`: an `insert` for {@link mountThenInsert}. */
|
|
50
|
+
export const appendTo = (
|
|
51
|
+
parent: Element,
|
|
52
|
+
nodes: readonly ChildNode[],
|
|
53
|
+
): void => {
|
|
54
|
+
parent.append(...nodes);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Inserts `nodes` in front of `next`: an `insert` for {@link mountThenInsert}. */
|
|
58
|
+
export const insertBefore = (
|
|
59
|
+
next: ChildNode,
|
|
60
|
+
nodes: readonly ChildNode[],
|
|
61
|
+
): void => {
|
|
62
|
+
next.before(...nodes);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Returns the function a `For` or a switch calls with each value after its
|
|
67
|
+
* first. While `anchor` is attached the value is applied at once, and replaces
|
|
68
|
+
* any value still waiting. While it is detached the value is kept, and the
|
|
69
|
+
* latest one is applied once the mount open now has inserted its nodes, and
|
|
70
|
+
* only if `anchor` is then attached: it is not when the `For` or switch was
|
|
71
|
+
* disposed in between.
|
|
72
|
+
*
|
|
73
|
+
* Mounted outside every {@link mountThenInsert} (a description mounted by hand),
|
|
74
|
+
* there is nothing to wait for: a value that arrives while `anchor` is detached
|
|
75
|
+
* is dropped.
|
|
76
|
+
*
|
|
77
|
+
* A value that arrives while `apply` runs (a row or a branch it mounts writes
|
|
78
|
+
* what chose it, #3141) is kept too, and the latest one is applied once `apply`
|
|
79
|
+
* returns, if `anchor` is still attached: applying it at once would act on rows
|
|
80
|
+
* or a branch the running call has not finished recording. A write that keeps
|
|
81
|
+
* coming back this way is an error after {@link maxReapply} passes, as synstate
|
|
82
|
+
* stops a nested update loop.
|
|
83
|
+
*
|
|
84
|
+
* Once the scope current when it was taken is disposed, nothing more is
|
|
85
|
+
* applied: `anchor` can still have a parent then, a disposed element that held
|
|
86
|
+
* the `For` or switch.
|
|
87
|
+
*/
|
|
88
|
+
export const applyWhenAttached = <V,>(
|
|
89
|
+
anchor: Node,
|
|
90
|
+
apply: (value: V) => void,
|
|
91
|
+
): ((value: V) => void) => {
|
|
92
|
+
const { depth } = mut_state;
|
|
93
|
+
|
|
94
|
+
const id = mut_state.ids[depth];
|
|
95
|
+
|
|
96
|
+
const mut_pending: { latest: Readonly<{ value: V }> | undefined } = {
|
|
97
|
+
latest: undefined,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const mut_running = { current: false };
|
|
101
|
+
|
|
102
|
+
const mut_disposed = { current: false };
|
|
103
|
+
|
|
104
|
+
if (getCurrentScope() !== undefined) {
|
|
105
|
+
onScopeDispose(() => {
|
|
106
|
+
mut_disposed.current = true;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const attached = (): boolean =>
|
|
111
|
+
!mut_disposed.current && anchor.parentNode !== null;
|
|
112
|
+
|
|
113
|
+
/** Applies `value`, then each value that arrived meanwhile, while attached. */
|
|
114
|
+
const applyNow = (value: V): void => {
|
|
115
|
+
mut_running.current = true;
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
apply(value);
|
|
119
|
+
|
|
120
|
+
for (let mut_pass = 1; ; mut_pass += 1) {
|
|
121
|
+
const { latest } = mut_pending;
|
|
122
|
+
|
|
123
|
+
mut_pending.latest = undefined;
|
|
124
|
+
|
|
125
|
+
if (latest === undefined || !attached()) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (mut_pass >= maxReapply) {
|
|
130
|
+
throw new Error(reapplyLoopMessage);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
apply(latest.value);
|
|
134
|
+
}
|
|
135
|
+
} finally {
|
|
136
|
+
mut_running.current = false;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const flush = (): void => {
|
|
141
|
+
const { latest } = mut_pending;
|
|
142
|
+
|
|
143
|
+
mut_pending.latest = undefined;
|
|
144
|
+
|
|
145
|
+
if (latest !== undefined && attached()) {
|
|
146
|
+
applyNow(latest.value);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return (value) => {
|
|
151
|
+
if (mut_running.current) {
|
|
152
|
+
mut_pending.latest = { value };
|
|
153
|
+
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (attached()) {
|
|
158
|
+
mut_pending.latest = undefined;
|
|
159
|
+
|
|
160
|
+
applyNow(value);
|
|
161
|
+
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const scheduled = mut_pending.latest !== undefined;
|
|
166
|
+
|
|
167
|
+
mut_pending.latest = { value };
|
|
168
|
+
|
|
169
|
+
if (scheduled) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (depth === 0 || mut_state.depth < depth || mut_state.ids[depth] !== id) {
|
|
174
|
+
// That mount has ended: its nodes are inserted.
|
|
175
|
+
flush();
|
|
176
|
+
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const mut_frame = mut_state.frames[depth];
|
|
181
|
+
|
|
182
|
+
if (mut_frame === undefined) {
|
|
183
|
+
mut_state.frames[depth] = [flush];
|
|
184
|
+
} else {
|
|
185
|
+
mut_frame.push(flush);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/** How many times a value written from inside `apply` is applied in a row. */
|
|
191
|
+
const maxReapply = 100;
|
|
192
|
+
|
|
193
|
+
const reapplyLoopMessage =
|
|
194
|
+
`synstate-dom: a For or a switch was updated more than ${maxReapply.toString()} times in a row by the rows or branch it was mounting: a row or branch keeps writing the value that chose it. Make the write stop (a condition, or a value that leads to rows or a branch that do not write).` as const;
|
|
195
|
+
|
|
196
|
+
const mut_state: {
|
|
197
|
+
depth: number;
|
|
198
|
+
lastId: number;
|
|
199
|
+
/** The id of the mount open at each depth. */
|
|
200
|
+
ids: number[];
|
|
201
|
+
/** What was deferred to the mount open at each depth, in order. */
|
|
202
|
+
frames: ((() => void)[] | undefined)[];
|
|
203
|
+
} = { depth: 0, lastId: 0, ids: [0], frames: [] };
|
|
204
|
+
|
|
205
|
+
/** Takes what was deferred to the mount at `depth` out of the state. */
|
|
206
|
+
const takeFrame = (depth: number): readonly (() => void)[] | undefined => {
|
|
207
|
+
const frame = mut_state.frames[depth];
|
|
208
|
+
|
|
209
|
+
mut_state.frames[depth] = undefined;
|
|
210
|
+
|
|
211
|
+
return frame;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Runs what was deferred to a mount. One that throws does not keep the rest
|
|
216
|
+
* from running, or they would keep their value waiting for good; the first
|
|
217
|
+
* error is rethrown once they have run.
|
|
218
|
+
*/
|
|
219
|
+
const runAll = (frame: readonly (() => void)[]): void => {
|
|
220
|
+
for (const [index, apply] of frame.entries()) {
|
|
221
|
+
try {
|
|
222
|
+
apply();
|
|
223
|
+
} catch (error) {
|
|
224
|
+
runAll(frame.slice(index + 1));
|
|
225
|
+
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/** A mount that throws must not leave the depth raised. */
|
|
232
|
+
const runMount = <A, N>(mount: (input: A) => N, arg: A, depth: number): N => {
|
|
233
|
+
try {
|
|
234
|
+
return mount(arg);
|
|
235
|
+
} finally {
|
|
236
|
+
mut_state.depth = depth - 1;
|
|
237
|
+
}
|
|
238
|
+
};
|