synstate-dom 0.5.0 → 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.
Files changed (59) hide show
  1. package/README.md +1 -0
  2. package/dist/control/for.d.mts +4 -0
  3. package/dist/control/for.d.mts.map +1 -1
  4. package/dist/control/for.mjs +25 -15
  5. package/dist/control/for.mjs.map +1 -1
  6. package/dist/control/mount-switch.d.mts +4 -0
  7. package/dist/control/mount-switch.d.mts.map +1 -1
  8. package/dist/control/mount-switch.mjs +25 -12
  9. package/dist/control/mount-switch.mjs.map +1 -1
  10. package/dist/control/portal.d.mts.map +1 -1
  11. package/dist/control/portal.mjs +2 -2
  12. package/dist/control/portal.mjs.map +1 -1
  13. package/dist/index.d.mts +1 -0
  14. package/dist/index.d.mts.map +1 -1
  15. package/dist/index.mjs +1 -0
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/internal/after-attach.d.mts +35 -0
  18. package/dist/internal/after-attach.d.mts.map +1 -0
  19. package/dist/internal/after-attach.mjs +122 -0
  20. package/dist/internal/after-attach.mjs.map +1 -0
  21. package/dist/internal/as-constant.d.mts +4 -4
  22. package/dist/internal/dom-tables.d.mts.map +1 -1
  23. package/dist/internal/dom-tables.mjs +3 -0
  24. package/dist/internal/dom-tables.mjs.map +1 -1
  25. package/dist/internal/index.d.mts +1 -0
  26. package/dist/internal/index.d.mts.map +1 -1
  27. package/dist/internal/index.mjs +1 -0
  28. package/dist/internal/index.mjs.map +1 -1
  29. package/dist/jsx/element-props.d.mts +15 -2
  30. package/dist/jsx/element-props.d.mts.map +1 -1
  31. package/dist/jsx/jsx.mjs +2 -2
  32. package/dist/jsx/jsx.mjs.map +1 -1
  33. package/dist/keyboard/index.d.mts +2 -0
  34. package/dist/keyboard/index.d.mts.map +1 -0
  35. package/dist/keyboard/index.mjs +2 -0
  36. package/dist/keyboard/index.mjs.map +1 -0
  37. package/dist/keyboard/is-ime-key-down.d.mts +11 -0
  38. package/dist/keyboard/is-ime-key-down.d.mts.map +1 -0
  39. package/dist/keyboard/is-ime-key-down.mjs +15 -0
  40. package/dist/keyboard/is-ime-key-down.mjs.map +1 -0
  41. package/dist/render.d.mts +2 -1
  42. package/dist/render.d.mts.map +1 -1
  43. package/dist/render.mjs +19 -4
  44. package/dist/render.mjs.map +1 -1
  45. package/package.json +10 -10
  46. package/src/control/for.mts +41 -19
  47. package/src/control/mount-switch.mts +36 -15
  48. package/src/control/portal.mts +7 -3
  49. package/src/index.mts +1 -0
  50. package/src/internal/after-attach.mts +173 -0
  51. package/src/internal/as-constant.mts +4 -4
  52. package/src/internal/dom-tables.mts +3 -0
  53. package/src/internal/index.mts +1 -0
  54. package/src/jsx/element-props.mts +39 -2
  55. package/src/jsx/jsx.mts +2 -2
  56. package/src/keyboard/index.mts +1 -0
  57. package/src/keyboard/is-ime-key-down.mts +20 -0
  58. package/src/keyboard/is-ime-key-down.test.mts +42 -0
  59. package/src/render.mts +32 -6
@@ -0,0 +1,42 @@
1
+ import { isImeKeyDown } from './is-ime-key-down.mjs';
2
+
3
+ // Plain objects stand in for `KeyboardEvent`s: it reads only these fields, and
4
+ // Node.js has no `KeyboardEvent`.
5
+ describe(isImeKeyDown, () => {
6
+ test('is a key down sent while an input method is composing', () => {
7
+ assert.isTrue(isImeKeyDown(keyDown('a', 229, true)));
8
+
9
+ assert.isTrue(isImeKeyDown(keyDown('ArrowUp', 229, true)));
10
+ });
11
+
12
+ test.each(['Enter', 'Escape'])(
13
+ 'is %s with key code 229, which Safari sends after compositionend',
14
+ (key) => {
15
+ assert.isTrue(isImeKeyDown(keyDown(key, 229)));
16
+ },
17
+ );
18
+
19
+ test('is not another key with key code 229, as an Android virtual keyboard sends every key', () => {
20
+ assert.isFalse(isImeKeyDown(keyDown('ArrowUp', 229)));
21
+ });
22
+
23
+ test.each([
24
+ ['Enter', 13],
25
+ ['Escape', 27],
26
+ ['a', 65],
27
+ ] as const)('is not a plain %s', (key, code) => {
28
+ assert.isFalse(isImeKeyDown(keyDown(key, code)));
29
+ });
30
+ });
31
+
32
+ /** The fields of a key down that `isImeKeyDown` reads. */
33
+ const keyDown = (
34
+ key: string,
35
+ code: number,
36
+ isComposing: boolean = false,
37
+ ): Readonly<{ key: string; keyCode: number; isComposing: boolean }> =>
38
+ ({
39
+ key,
40
+ keyCode: code,
41
+ isComposing,
42
+ }) as const;
package/src/render.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createScope } from 'synstate';
2
+ import { appendTo, mountThenInsert } from './internal/index.mjs';
2
3
  import { type JSX, toNodes } from './jsx/index.mjs';
3
4
 
4
5
  /**
@@ -7,7 +8,8 @@ import { type JSX, toNodes } from './jsx/index.mjs';
7
8
  *
8
9
  * `app` is called once, inside a new root scope; everything created while it
9
10
  * and the description it returns are mounted belongs to that scope, and
10
- * unmounting disposes it and removes the nodes.
11
+ * unmounting disposes it and removes the nodes. If mounting throws, what was
12
+ * mounted is disposed and removed before the error is rethrown.
11
13
  *
12
14
  * `render(App, el)` rather than `render(<App />, el)`: taking the component
13
15
  * makes it plain that it is evaluated here.
@@ -18,15 +20,39 @@ export const render = (
18
20
  ): (() => void) => {
19
21
  const root = createScope(undefined);
20
22
 
21
- const nodes = root.run(() => toNodes(app().mount()));
23
+ const mut_inserted: { nodes: readonly ChildNode[] } = { nodes: [] };
22
24
 
23
- container.append(...nodes);
24
-
25
- return () => {
25
+ const unmount = (): void => {
26
26
  root.dispose();
27
27
 
28
- for (const node of nodes) {
28
+ for (const node of mut_inserted.nodes) {
29
29
  node.remove();
30
30
  }
31
31
  };
32
+
33
+ try {
34
+ root.run(() =>
35
+ mountThenInsert(
36
+ mountApp,
37
+ app,
38
+ (into: Element, nodes: readonly ChildNode[]) => {
39
+ mut_inserted.nodes = nodes;
40
+
41
+ appendTo(into, nodes);
42
+ },
43
+ container,
44
+ ),
45
+ );
46
+ } catch (error) {
47
+ // An update applied after the app was appended can throw; take back what
48
+ // was mounted rather than leave it in `container` with no way to unmount.
49
+ unmount();
50
+
51
+ throw error;
52
+ }
53
+
54
+ return unmount;
32
55
  };
56
+
57
+ const mountApp = (app: () => JSX.Element): readonly ChildNode[] =>
58
+ toNodes(app().mount());