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.
Files changed (44) hide show
  1. package/README.md +1 -1
  2. package/dist/bind/bind-props.mjs +14 -1
  3. package/dist/bind/bind-props.mjs.map +1 -1
  4. package/dist/control/for.d.mts +6 -0
  5. package/dist/control/for.d.mts.map +1 -1
  6. package/dist/control/for.mjs +27 -15
  7. package/dist/control/for.mjs.map +1 -1
  8. package/dist/control/mount-switch.d.mts +6 -0
  9. package/dist/control/mount-switch.d.mts.map +1 -1
  10. package/dist/control/mount-switch.mjs +27 -12
  11. package/dist/control/mount-switch.mjs.map +1 -1
  12. package/dist/control/portal.d.mts.map +1 -1
  13. package/dist/control/portal.mjs +2 -2
  14. package/dist/control/portal.mjs.map +1 -1
  15. package/dist/internal/after-attach.d.mts +46 -0
  16. package/dist/internal/after-attach.d.mts.map +1 -0
  17. package/dist/internal/after-attach.mjs +170 -0
  18. package/dist/internal/after-attach.mjs.map +1 -0
  19. package/dist/internal/bind-value.mjs +11 -0
  20. package/dist/internal/bind-value.mjs.map +1 -1
  21. package/dist/internal/index.d.mts +1 -0
  22. package/dist/internal/index.d.mts.map +1 -1
  23. package/dist/internal/index.mjs +1 -0
  24. package/dist/internal/index.mjs.map +1 -1
  25. package/dist/jsx/element-props.d.mts +3 -1
  26. package/dist/jsx/element-props.d.mts.map +1 -1
  27. package/dist/jsx/jsx.mjs +2 -2
  28. package/dist/jsx/jsx.mjs.map +1 -1
  29. package/dist/render.d.mts +2 -1
  30. package/dist/render.d.mts.map +1 -1
  31. package/dist/render.mjs +19 -4
  32. package/dist/render.mjs.map +1 -1
  33. package/package.json +10 -10
  34. package/src/bind/bind-props.mts +22 -1
  35. package/src/control/for.mts +43 -19
  36. package/src/control/mount-switch.mts +38 -15
  37. package/src/control/portal.mts +7 -3
  38. package/src/internal/after-attach.mts +238 -0
  39. package/src/internal/bind-value.mts +14 -1
  40. package/src/internal/index.mts +1 -0
  41. package/src/jsx/element-props.mts +6 -3
  42. package/src/jsx/element-props.test.mts +18 -0
  43. package/src/jsx/jsx.mts +2 -2
  44. 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}` | `data-${string}`, Bindable<string | undefined>>>;
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":"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"}
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
- element.append(...mountChild(props.children));
39
+ mountThenInsert(mountChild, props.children, appendTo, element);
40
40
  }
41
41
  bindProps(element, props);
42
42
  return element;
@@ -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;AACpD,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,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChD,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"}
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.
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.mts","sourceRoot":"","sources":["../src/render.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,GAAG,EAAW,MAAM,iBAAiB,CAAC;AAEpD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,MAAM,QACZ,MAAM,GAAG,CAAC,OAAO,aACX,OAAO,KACjB,CAAC,MAAM,IAAI,CAcb,CAAC"}
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 nodes = root.run(() => toNodes(app().mount()));
17
- container.append(...nodes);
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
@@ -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;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CACpB,GAAsB,EACtB,SAAkB,EACJ,EAAE;IAChB,MAAM,IAAI,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAEpC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAErD,SAAS,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC;IAE3B,OAAO,GAAG,EAAE;QACV,IAAI,CAAC,OAAO,EAAE,CAAC;QAEf,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,CAAC;IACH,CAAC,CAAC;AACJ,CAAC,CAAC"}
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.6.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.3.1",
48
- "eslint-plugin-ts-data-forge": "0.10.8",
49
- "eslint-plugin-ts-fortress": "1.3.12",
50
- "eslint-plugin-ts-type-forge": "0.8.17",
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": "6.2.1",
56
- "ts-data-forge": "14.10.2",
57
- "ts-repo-utils": "10.7.1"
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": ">=6.2.1",
61
- "ts-data-forge": ">=14.10.2"
60
+ "synstate": ">=7.0.1",
61
+ "ts-data-forge": ">=14.11.0"
62
62
  },
63
63
  "engines": {
64
64
  "node": ">=22.23.4",
@@ -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
- writeAttribute(element, name, v);
294
+ write(element, name, v);
274
295
  });
275
296
  };
276
297
 
@@ -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 { bindValue } from '../internal/index.mjs';
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[] } = { nodes: [] };
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
- bindValue(entries$, (entries) => {
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
- if (anchor.parentNode !== null) {
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, ...rest] = createRow(rowKey, entry);
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 { bindValue } from '../internal/index.mjs';
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
- bindValue(value, (v) => {
41
- const key = keyOf(v);
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
- if (anchor.parentNode === null) {
68
- mut_state.initialNodes = nodes;
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
- anchor.before(...nodes);
93
+ switchWhenAttached(v);
71
94
  }
72
95
  });
73
96
 
@@ -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 = mountChild(children);
22
-
23
- (target ?? document.body).append(...nodes);
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
+ };