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
|
@@ -37,7 +37,14 @@ export const bindValue = <T,>(
|
|
|
37
37
|
write(bindable);
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* A completed observable is written once and not subscribed to.
|
|
42
|
+
*
|
|
43
|
+
* `subscribe` delivers the current value before it adds the subscriber, so a
|
|
44
|
+
* value written by the first `write` itself (a `For` row or a switch branch
|
|
45
|
+
* mounted then writes what chose it, #3141) reaches no subscriber; it is
|
|
46
|
+
* written once the subscription is in place.
|
|
47
|
+
*/
|
|
41
48
|
const bindObservable = <A,>(
|
|
42
49
|
source: InitializedObservable<A>,
|
|
43
50
|
write: (value: A) => void,
|
|
@@ -48,5 +55,11 @@ const bindObservable = <A,>(
|
|
|
48
55
|
return;
|
|
49
56
|
}
|
|
50
57
|
|
|
58
|
+
const { updateToken } = source;
|
|
59
|
+
|
|
51
60
|
subscribeInScope(source, write);
|
|
61
|
+
|
|
62
|
+
if (source.updateToken !== updateToken) {
|
|
63
|
+
write(source.getSnapshot().value);
|
|
64
|
+
}
|
|
52
65
|
};
|
package/src/internal/index.mts
CHANGED
|
@@ -18,6 +18,8 @@ import type { EventPropNames } from './event-prop-names.mjs';
|
|
|
18
18
|
* - `style` is either a `cssText` string or an object of CSS properties, each
|
|
19
19
|
* bound with `style.setProperty`, so the keys are the hyphenated names
|
|
20
20
|
* (`'background-color'`, `'--accent'`).
|
|
21
|
+
* - An `aria-*` attribute also takes a number (`aria-level`, `aria-valuenow`)
|
|
22
|
+
* and a boolean, written as `"true"` or `"false"`.
|
|
21
23
|
*/
|
|
22
24
|
export type CommonElementProps<E extends Element> = Readonly<{
|
|
23
25
|
ref?: (element: E) => void;
|
|
@@ -27,10 +29,11 @@ export type CommonElementProps<E extends Element> = Readonly<{
|
|
|
27
29
|
}> &
|
|
28
30
|
Partial<
|
|
29
31
|
ReadonlyRecord<
|
|
30
|
-
`aria-${string}
|
|
31
|
-
Bindable<string | undefined>
|
|
32
|
+
`aria-${string}`,
|
|
33
|
+
Bindable<boolean | number | string | undefined>
|
|
32
34
|
>
|
|
33
|
-
|
|
35
|
+
> &
|
|
36
|
+
Partial<ReadonlyRecord<`data-${string}`, Bindable<string | undefined>>>;
|
|
34
37
|
|
|
35
38
|
/** `class` as one `Bindable<boolean>` per class name. */
|
|
36
39
|
export type ClassObject = ReadonlyRecord<string, Bindable<boolean>>;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { expectType } from 'ts-data-forge';
|
|
2
|
+
import type { Bindable } from '../constant/index.mjs';
|
|
3
|
+
import type { CommonElementProps } from './element-props.mjs';
|
|
2
4
|
import type { EventPropNames } from './event-prop-names.mjs';
|
|
3
5
|
|
|
4
6
|
describe('EventPropNames', () => {
|
|
@@ -26,3 +28,19 @@ describe('EventPropNames', () => {
|
|
|
26
28
|
assert.isTrue(true);
|
|
27
29
|
});
|
|
28
30
|
});
|
|
31
|
+
|
|
32
|
+
describe('CommonElementProps', () => {
|
|
33
|
+
test('an aria-* attribute takes a boolean and a number, a data-* one neither (#3165)', () => {
|
|
34
|
+
expectType<
|
|
35
|
+
CommonElementProps<HTMLElement>['aria-busy'],
|
|
36
|
+
Bindable<boolean | number | string | undefined> | undefined
|
|
37
|
+
>('=');
|
|
38
|
+
|
|
39
|
+
expectType<
|
|
40
|
+
CommonElementProps<HTMLElement>['data-row'],
|
|
41
|
+
Bindable<string | undefined> | undefined
|
|
42
|
+
>('=');
|
|
43
|
+
|
|
44
|
+
assert.isTrue(true);
|
|
45
|
+
});
|
|
46
|
+
});
|
package/src/jsx/jsx.mts
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, type Child } from './child.mjs';
|
|
5
5
|
import { mountable, type Mountable } from './mountable.mjs';
|
|
6
6
|
|
|
@@ -50,7 +50,7 @@ const mountElement = (tag: string, props: unknown): Element => {
|
|
|
50
50
|
|
|
51
51
|
// Children first, so that a `<select>`'s `value` finds its `<option>`s.
|
|
52
52
|
if (hasKey(props, 'children') && isChild(props.children)) {
|
|
53
|
-
|
|
53
|
+
mountThenInsert(mountChild, props.children, appendTo, element);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
bindProps(element, props);
|
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 =
|
|
23
|
+
const mut_inserted: { nodes: readonly ChildNode[] } = { nodes: [] };
|
|
22
24
|
|
|
23
|
-
|
|
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());
|