foldkit 0.146.0 → 0.148.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/README.md +5 -2
- package/dist/buildToken.d.ts +3 -0
- package/dist/buildToken.d.ts.map +1 -0
- package/dist/buildToken.js +21 -0
- package/dist/controlledDomState.d.ts +25 -0
- package/dist/controlledDomState.d.ts.map +1 -0
- package/dist/controlledDomState.js +240 -0
- package/dist/cssStyleProperties.d.ts +6 -0
- package/dist/cssStyleProperties.d.ts.map +1 -0
- package/dist/cssStyleProperties.js +91 -0
- package/dist/customElement/index.d.ts.map +1 -1
- package/dist/customElement/index.js +23 -0
- package/dist/domReflection.d.ts +73 -0
- package/dist/domReflection.d.ts.map +1 -0
- package/dist/domReflection.js +557 -0
- package/dist/experimental/index.d.ts +1 -0
- package/dist/experimental/index.d.ts.map +1 -1
- package/dist/experimental/index.js +1 -0
- package/dist/experimental/machine/machine.d.ts +22 -3
- package/dist/experimental/machine/machine.d.ts.map +1 -1
- package/dist/experimental/machine/machine.js +8 -0
- package/dist/experimental/server/entry.d.ts +73 -0
- package/dist/experimental/server/entry.d.ts.map +1 -0
- package/dist/experimental/server/entry.js +41 -0
- package/dist/experimental/server/host.d.ts +136 -0
- package/dist/experimental/server/host.d.ts.map +1 -0
- package/dist/experimental/server/host.js +359 -0
- package/dist/experimental/server/index.d.ts +5 -0
- package/dist/experimental/server/index.d.ts.map +1 -0
- package/dist/experimental/server/index.js +4 -0
- package/dist/experimental/server/public.d.ts +3 -0
- package/dist/experimental/server/public.d.ts.map +1 -0
- package/dist/experimental/server/public.js +1 -0
- package/dist/experimental/server/serialize.d.ts +43 -0
- package/dist/experimental/server/serialize.d.ts.map +1 -0
- package/dist/experimental/server/serialize.js +786 -0
- package/dist/experimental/server/server.d.ts +242 -0
- package/dist/experimental/server/server.d.ts.map +1 -0
- package/dist/experimental/server/server.js +946 -0
- package/dist/experimental/server/template.d.ts +53 -0
- package/dist/experimental/server/template.d.ts.map +1 -0
- package/dist/experimental/server/template.js +617 -0
- package/dist/html/index.d.ts +5 -0
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +474 -33
- package/dist/hydrate.d.ts +4 -0
- package/dist/hydrate.d.ts.map +1 -0
- package/dist/hydrate.js +850 -0
- package/dist/hydrationMarker.d.ts +10 -0
- package/dist/hydrationMarker.d.ts.map +1 -0
- package/dist/hydrationMarker.js +9 -0
- package/dist/hydrationMarkers.d.ts +15 -0
- package/dist/hydrationMarkers.d.ts.map +1 -0
- package/dist/hydrationMarkers.js +70 -0
- package/dist/nativeInnerHtml.d.ts +13 -0
- package/dist/nativeInnerHtml.d.ts.map +1 -0
- package/dist/nativeInnerHtml.js +30 -0
- package/dist/propertyProvenance.d.ts +33 -0
- package/dist/propertyProvenance.d.ts.map +1 -0
- package/dist/propertyProvenance.js +78 -0
- package/dist/propsModule.d.ts.map +1 -1
- package/dist/propsModule.js +149 -15
- package/dist/runtime/public.d.ts +2 -2
- package/dist/runtime/public.d.ts.map +1 -1
- package/dist/runtime/public.js +1 -1
- package/dist/runtime/runtime.d.ts +99 -36
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +412 -71
- package/dist/snabbdom/attributes.d.ts.map +1 -1
- package/dist/snabbdom/attributes.js +65 -37
- package/dist/snabbdom/h.d.ts +1 -0
- package/dist/snabbdom/h.d.ts.map +1 -1
- package/dist/snabbdom/h.js +85 -4
- package/dist/snabbdom/style.d.ts.map +1 -1
- package/dist/snabbdom/style.js +53 -34
- package/dist/snabbdom/tovnode.d.ts.map +1 -1
- package/dist/snabbdom/tovnode.js +5 -1
- package/dist/tagName.d.ts +6 -0
- package/dist/tagName.d.ts.map +1 -0
- package/dist/tagName.js +11 -0
- package/dist/test/apps/attributes.d.ts +1 -0
- package/dist/test/apps/attributes.d.ts.map +1 -1
- package/dist/test/apps/attributes.js +8 -1
- package/dist/test/apps/login.js +1 -1
- package/dist/test/matchers.d.ts.map +1 -1
- package/dist/test/matchers.js +2 -1
- package/dist/test/scene.d.ts.map +1 -1
- package/dist/test/scene.js +2 -1
- package/dist/vdom.d.ts.map +1 -1
- package/dist/vdom.js +25 -1
- package/package.json +9 -2
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Attribute stamped on the server-rendered application root. Its presence
|
|
2
|
+
* tells a booting runtime to hydrate instead of rendering fresh, and its
|
|
3
|
+
* value is the runtime id used for HMR model preservation. */
|
|
4
|
+
export declare const FOLDKIT_APP_ATTRIBUTE = "data-foldkit-app";
|
|
5
|
+
/** Attribute on the JSON script tag carrying the Schema-encoded flags the
|
|
6
|
+
* server rendered with. A hydrating runtime decodes this payload instead of
|
|
7
|
+
* running the client `flags` Effect, so both sides call `init` with the
|
|
8
|
+
* same value. */
|
|
9
|
+
export declare const FOLDKIT_FLAGS_ATTRIBUTE = "data-foldkit-flags";
|
|
10
|
+
//# sourceMappingURL=hydrationMarker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hydrationMarker.d.ts","sourceRoot":"","sources":["../src/hydrationMarker.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAC/D,eAAO,MAAM,qBAAqB,qBAAqB,CAAA;AAEvD;;;kBAGkB;AAClB,eAAO,MAAM,uBAAuB,uBAAuB,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Attribute stamped on the server-rendered application root. Its presence
|
|
2
|
+
* tells a booting runtime to hydrate instead of rendering fresh, and its
|
|
3
|
+
* value is the runtime id used for HMR model preservation. */
|
|
4
|
+
export const FOLDKIT_APP_ATTRIBUTE = 'data-foldkit-app';
|
|
5
|
+
/** Attribute on the JSON script tag carrying the Schema-encoded flags the
|
|
6
|
+
* server rendered with. A hydrating runtime decodes this payload instead of
|
|
7
|
+
* running the client `flags` Effect, so both sides call `init` with the
|
|
8
|
+
* same value. */
|
|
9
|
+
export const FOLDKIT_FLAGS_ATTRIBUTE = 'data-foldkit-flags';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const HYDRATION_KEY_ATTRIBUTE = "data-foldkit-key";
|
|
2
|
+
export declare const HYDRATION_IDENTITY_ATTRIBUTE = "data-foldkit-identity";
|
|
3
|
+
/** The marker value for a vnode key, or `undefined` for a key type the
|
|
4
|
+
* hydration handoff does not support (a symbol, which the server refuses to
|
|
5
|
+
* render and hydration therefore never adopts).
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export declare const hydrationKeyMarker: (key: PropertyKey) => string | undefined;
|
|
10
|
+
/** The marker value for a compiler-assigned view identity.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export declare const hydrationIdentityMarker: (identity: string) => string;
|
|
15
|
+
//# sourceMappingURL=hydrationMarkers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hydrationMarkers.d.ts","sourceRoot":"","sources":["../src/hydrationMarkers.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,uBAAuB,qBAAqB,CAAA;AACzD,eAAO,MAAM,4BAA4B,0BAA0B,CAAA;AAkCnE;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,KAAK,WAAW,KAAG,MAAM,GAAG,SAQ9D,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GAAI,UAAU,MAAM,KAAG,MAClC,CAAA"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Hydration markers the serializer stamps onto a keyed or identity-bearing
|
|
2
|
+
// element, and hydration reads then strips. The server HTML does not otherwise
|
|
3
|
+
// encode a vnode's `key` or `identity`, so a reordered or stale keyed list would
|
|
4
|
+
// be adopted positionally: the client's first row would take over the DOM node
|
|
5
|
+
// (and the user state on it) of a different logical row. Serializing the markers
|
|
6
|
+
// lets hydration verify it is adopting the same logical entity by key and
|
|
7
|
+
// identity, not just by tag and position, and rebuild when it is not.
|
|
8
|
+
//
|
|
9
|
+
// A marker carries a digest of the key, never the key itself. Application keys
|
|
10
|
+
// are database ids, account identifiers, and email addresses the view otherwise
|
|
11
|
+
// never renders, and a compiler identity spells out a source path and function
|
|
12
|
+
// name; neither belongs in public HTML. A digest is a one-way comparison token:
|
|
13
|
+
// it is enough for hydration, which only ever asks whether two markers are
|
|
14
|
+
// equal, and it does not read back to the value it was computed from. It is not
|
|
15
|
+
// a secret, since a reader who guesses a candidate key can hash it and compare,
|
|
16
|
+
// so it is sized to make an accidental collision between two distinct keys
|
|
17
|
+
// vanishingly unlikely rather than to withstand a guessing attack.
|
|
18
|
+
//
|
|
19
|
+
// Markers are part of the hydration handoff, so they are emitted only for a
|
|
20
|
+
// hydratable render. Non-hydratable output carries no key or identity channel
|
|
21
|
+
// at all.
|
|
22
|
+
export const HYDRATION_KEY_ATTRIBUTE = 'data-foldkit-key';
|
|
23
|
+
export const HYDRATION_IDENTITY_ATTRIBUTE = 'data-foldkit-identity';
|
|
24
|
+
// FNV-1a, run as two 32-bit lanes from different offset bases and concatenated
|
|
25
|
+
// to 64 bits. The lanes are plain integer arithmetic over the string's code
|
|
26
|
+
// units, so the server and the browser compute the same digest for the same
|
|
27
|
+
// input with no platform dependency and no async crypto API.
|
|
28
|
+
const FNV_PRIME = 16777619;
|
|
29
|
+
const FNV_OFFSET_BASIS = 2166136261;
|
|
30
|
+
const SECOND_LANE_OFFSET_BASIS = 1099511628;
|
|
31
|
+
const digest = (value) => {
|
|
32
|
+
let lowLane = FNV_OFFSET_BASIS;
|
|
33
|
+
let highLane = SECOND_LANE_OFFSET_BASIS;
|
|
34
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
35
|
+
const codeUnit = value.charCodeAt(index);
|
|
36
|
+
const lowByte = codeUnit & 0xff;
|
|
37
|
+
const highByte = codeUnit >>> 8;
|
|
38
|
+
lowLane = Math.imul(lowLane ^ lowByte, FNV_PRIME);
|
|
39
|
+
lowLane = Math.imul(lowLane ^ highByte, FNV_PRIME);
|
|
40
|
+
highLane = Math.imul(highLane ^ highByte, FNV_PRIME);
|
|
41
|
+
highLane = Math.imul(highLane ^ lowByte, FNV_PRIME);
|
|
42
|
+
}
|
|
43
|
+
return ((lowLane >>> 0).toString(16).padStart(8, '0') +
|
|
44
|
+
(highLane >>> 0).toString(16).padStart(8, '0'));
|
|
45
|
+
};
|
|
46
|
+
// Each digest is taken over a type-tagged string, which keeps the supported key
|
|
47
|
+
// types apart. The runtime compares keys with `===`, so the number 1 and the
|
|
48
|
+
// string '1' are different keys and must digest differently; untagged, both
|
|
49
|
+
// would reach the digest as "1" and a server-rendered numeric key would adopt a
|
|
50
|
+
// client string key's node, and the state on it.
|
|
51
|
+
/** The marker value for a vnode key, or `undefined` for a key type the
|
|
52
|
+
* hydration handoff does not support (a symbol, which the server refuses to
|
|
53
|
+
* render and hydration therefore never adopts).
|
|
54
|
+
*
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
export const hydrationKeyMarker = (key) => {
|
|
58
|
+
if (typeof key === 'string') {
|
|
59
|
+
return digest(`s:${key}`);
|
|
60
|
+
}
|
|
61
|
+
if (typeof key === 'number') {
|
|
62
|
+
return digest(`n:${key}`);
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
};
|
|
66
|
+
/** The marker value for a compiler-assigned view identity.
|
|
67
|
+
*
|
|
68
|
+
* @internal
|
|
69
|
+
*/
|
|
70
|
+
export const hydrationIdentityMarker = (identity) => digest(`i:${identity}`);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Reads the platform's `Element.innerHTML`, bypassing a same-named property
|
|
2
|
+
* an autonomous custom element defines.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export declare const readNativeInnerHtml: (element: Element) => string;
|
|
7
|
+
/** Writes the platform's `Element.innerHTML`, bypassing a same-named property
|
|
8
|
+
* an autonomous custom element defines.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare const writeNativeInnerHtml: (element: Element, value: string) => void;
|
|
13
|
+
//# sourceMappingURL=nativeInnerHtml.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nativeInnerHtml.d.ts","sourceRoot":"","sources":["../src/nativeInnerHtml.ts"],"names":[],"mappings":"AAeA;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,OAAO,KAAG,MAGtD,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,OAAO,EAAE,OAAO,MAAM,KAAG,IAEtE,CAAA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const innerHtmlDescriptor = (element) => {
|
|
2
|
+
const elementConstructor = element.ownerDocument.defaultView?.Element ?? globalThis.Element;
|
|
3
|
+
const prototype = elementConstructor.prototype;
|
|
4
|
+
let current = prototype;
|
|
5
|
+
while (current !== null) {
|
|
6
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, 'innerHTML');
|
|
7
|
+
if (descriptor?.get !== undefined && descriptor.set !== undefined) {
|
|
8
|
+
return descriptor;
|
|
9
|
+
}
|
|
10
|
+
current = Object.getPrototypeOf(current);
|
|
11
|
+
}
|
|
12
|
+
throw new Error('[foldkit] The browser does not expose Element.innerHTML.');
|
|
13
|
+
};
|
|
14
|
+
/** Reads the platform's `Element.innerHTML`, bypassing a same-named property
|
|
15
|
+
* an autonomous custom element defines.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export const readNativeInnerHtml = (element) => {
|
|
20
|
+
const value = innerHtmlDescriptor(element).get?.call(element);
|
|
21
|
+
return typeof value === 'string' ? value : '';
|
|
22
|
+
};
|
|
23
|
+
/** Writes the platform's `Element.innerHTML`, bypassing a same-named property
|
|
24
|
+
* an autonomous custom element defines.
|
|
25
|
+
*
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export const writeNativeInnerHtml = (element, value) => {
|
|
29
|
+
innerHtmlDescriptor(element).set?.call(element, value);
|
|
30
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Records `value` as markup `h.InnerHTML` wrote. The value itself is the mark,
|
|
2
|
+
* so a later generic write to `innerHTML` no longer matches and the markup it
|
|
3
|
+
* wrote is never treated as trusted.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export declare const markTrustedInnerHtml: (props: Record<string, unknown>, value: string) => void;
|
|
8
|
+
/** Records `name` as a property a builder wrote without claiming it is markup:
|
|
9
|
+
* an internal `Prop`, which is also what a `CustomElement.define` property
|
|
10
|
+
* factory produces.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export declare const markClientOnlyProperty: (props: Record<string, unknown>, name: string) => void;
|
|
15
|
+
/** Drops the client-only mark from `name`, for a typed attribute builder
|
|
16
|
+
* overwriting a property a generic write left behind.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export declare const unmarkClientOnlyProperty: (props: Record<string, unknown>, name: string) => void;
|
|
21
|
+
/** Whether `props.innerHTML` is the markup `h.InnerHTML` wrote, rather than an
|
|
22
|
+
* arbitrary property that happens to carry that name.
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export declare const hasTrustedInnerHtml: (props: Readonly<Record<string, unknown>> | undefined) => boolean;
|
|
27
|
+
/** Whether `name` is a client-only DOM property rather than the HTML attribute
|
|
28
|
+
* it is named after.
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export declare const isClientOnlyProperty: (props: Readonly<Record<string, unknown>> | undefined, name: string) => boolean;
|
|
33
|
+
//# sourceMappingURL=propertyProvenance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"propertyProvenance.d.ts","sourceRoot":"","sources":["../src/propertyProvenance.ts"],"names":[],"mappings":"AAgCA;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAC/B,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,MAAM,KACZ,IAIF,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,GACjC,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,MAAM,MAAM,KACX,IAOF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GACnC,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,MAAM,MAAM,KACX,IAEF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC9B,OAAO,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,KACnD,OASF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC/B,OAAO,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,EACpD,MAAM,MAAM,KACX,OAKF,CAAA"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// What the server serializer may assume about an entry in a vnode's
|
|
2
|
+
// `data.props`. A DOM property name alone does not say what the property means:
|
|
3
|
+
// `innerHTML` is markup the view author intends to be parsed when `h.InnerHTML`
|
|
4
|
+
// wrote it and an arbitrary client-only value when anything else did, and `id`
|
|
5
|
+
// on a custom element is a reflected HTML attribute when `h.Id` wrote it and a
|
|
6
|
+
// component-private value when `CustomElement.define` did.
|
|
7
|
+
//
|
|
8
|
+
// Both marks belong to the last write, not to the props bag, which is what stops
|
|
9
|
+
// a value the serializer trusts from standing in for one written after it. The
|
|
10
|
+
// serializer always emits the value that is in `props`, and the marks always
|
|
11
|
+
// describe the builder that put it there.
|
|
12
|
+
//
|
|
13
|
+
// The marks live under symbol keys, so `Object.keys` never surfaces them, the
|
|
14
|
+
// props module never applies them to the DOM, the attribute serializer never
|
|
15
|
+
// emits them, and no string-keyed property can forge them. Object spread copies
|
|
16
|
+
// own enumerable symbol keys, so a copied props bag keeps its marks.
|
|
17
|
+
const TRUSTED_INNER_HTML = Symbol('foldkit/trusted-inner-html');
|
|
18
|
+
const CLIENT_ONLY_PROPERTIES = Symbol('foldkit/client-only-properties');
|
|
19
|
+
const markedProps = (props) =>
|
|
20
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
21
|
+
props;
|
|
22
|
+
/** Records `value` as markup `h.InnerHTML` wrote. The value itself is the mark,
|
|
23
|
+
* so a later generic write to `innerHTML` no longer matches and the markup it
|
|
24
|
+
* wrote is never treated as trusted.
|
|
25
|
+
*
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export const markTrustedInnerHtml = (props, value) => {
|
|
29
|
+
const marked = markedProps(props);
|
|
30
|
+
marked[TRUSTED_INNER_HTML] = value;
|
|
31
|
+
marked[CLIENT_ONLY_PROPERTIES]?.delete('innerHTML');
|
|
32
|
+
};
|
|
33
|
+
/** Records `name` as a property a builder wrote without claiming it is markup:
|
|
34
|
+
* an internal `Prop`, which is also what a `CustomElement.define` property
|
|
35
|
+
* factory produces.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export const markClientOnlyProperty = (props, name) => {
|
|
40
|
+
const marked = markedProps(props);
|
|
41
|
+
const names = (marked[CLIENT_ONLY_PROPERTIES] ??= new Set());
|
|
42
|
+
names.add(name);
|
|
43
|
+
if (name === 'innerHTML') {
|
|
44
|
+
Reflect.deleteProperty(marked, TRUSTED_INNER_HTML);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
/** Drops the client-only mark from `name`, for a typed attribute builder
|
|
48
|
+
* overwriting a property a generic write left behind.
|
|
49
|
+
*
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
export const unmarkClientOnlyProperty = (props, name) => {
|
|
53
|
+
markedProps(props)[CLIENT_ONLY_PROPERTIES]?.delete(name);
|
|
54
|
+
};
|
|
55
|
+
/** Whether `props.innerHTML` is the markup `h.InnerHTML` wrote, rather than an
|
|
56
|
+
* arbitrary property that happens to carry that name.
|
|
57
|
+
*
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
export const hasTrustedInnerHtml = (props) => {
|
|
61
|
+
if (props === undefined) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
const marked = markedProps(props);
|
|
65
|
+
const innerHtml = marked['innerHTML'];
|
|
66
|
+
return (typeof innerHtml === 'string' && marked[TRUSTED_INNER_HTML] === innerHtml);
|
|
67
|
+
};
|
|
68
|
+
/** Whether `name` is a client-only DOM property rather than the HTML attribute
|
|
69
|
+
* it is named after.
|
|
70
|
+
*
|
|
71
|
+
* @internal
|
|
72
|
+
*/
|
|
73
|
+
export const isClientOnlyProperty = (props, name) => {
|
|
74
|
+
if (props === undefined) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return markedProps(props)[CLIENT_ONLY_PROPERTIES]?.has(name) === true;
|
|
78
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"propsModule.d.ts","sourceRoot":"","sources":["../src/propsModule.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"propsModule.d.ts","sourceRoot":"","sources":["../src/propsModule.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AA0NjD,eAAO,MAAM,WAAW,EAAE,MAIzB,CAAA"}
|
package/dist/propsModule.js
CHANGED
|
@@ -1,8 +1,64 @@
|
|
|
1
|
+
import { clearControlledDefault, restoreControlledStateFromRawAttribute, } from './controlledDomState.js';
|
|
2
|
+
import { GLOBAL_ATTRIBUTE_PROPERTIES, HTML_NAMESPACE, htmlAttributeValue, reflectedAttributeName, } from './domReflection.js';
|
|
3
|
+
import { writeNativeInnerHtml } from './nativeInnerHtml.js';
|
|
4
|
+
import { hasTrustedInnerHtml, isClientOnlyProperty, } from './propertyProvenance.js';
|
|
1
5
|
import { VNodeDataMask } from './snabbdom/vnode.js';
|
|
2
|
-
|
|
3
|
-
|
|
6
|
+
const writeProperty = (element, name, value) => {
|
|
7
|
+
if (name === '__proto__') {
|
|
8
|
+
Object.defineProperty(element, name, {
|
|
9
|
+
configurable: true,
|
|
10
|
+
enumerable: true,
|
|
11
|
+
value,
|
|
12
|
+
writable: true,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
if (!Reflect.set(element, name, value)) {
|
|
17
|
+
throw new TypeError(`[foldkit] Cannot assign DOM property ${name}.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const isAutonomousCustomElement = (element) => (element.namespaceURI === null || element.namespaceURI === HTML_NAMESPACE) &&
|
|
22
|
+
element.localName.includes('-');
|
|
23
|
+
const isNativeCustomElementAttribute = (element, properties, name) => isAutonomousCustomElement(element) &&
|
|
24
|
+
GLOBAL_ATTRIBUTE_PROPERTIES.has(name) &&
|
|
25
|
+
!isClientOnlyProperty(properties, name);
|
|
26
|
+
const writeAttribute = (element, name, value) => {
|
|
27
|
+
const attributeName = reflectedAttributeName(name);
|
|
28
|
+
if (attributeName === undefined) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (name === 'draggable') {
|
|
32
|
+
element.setAttribute(attributeName, String(value));
|
|
33
|
+
}
|
|
34
|
+
else if (value === true) {
|
|
35
|
+
element.setAttribute(attributeName, '');
|
|
36
|
+
}
|
|
37
|
+
else if (value === false) {
|
|
38
|
+
element.removeAttribute(attributeName);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
element.setAttribute(attributeName, String(value));
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const resetProperty = (element, name, value) => {
|
|
45
|
+
if (name === '__proto__') {
|
|
46
|
+
Reflect.deleteProperty(element, name);
|
|
47
|
+
}
|
|
48
|
+
else if (typeof value === 'boolean') {
|
|
49
|
+
writeProperty(element, name, false);
|
|
50
|
+
}
|
|
51
|
+
else if (typeof value === 'string') {
|
|
52
|
+
writeProperty(element, name, '');
|
|
53
|
+
}
|
|
54
|
+
else if (typeof value === 'number') {
|
|
55
|
+
writeProperty(element, name, 0);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
/** A props module that extends Snabbdom's built-in module with cleanup of
|
|
59
|
+
* removed properties and explicit controlled-state ownership transitions.
|
|
4
60
|
*
|
|
5
|
-
* Snabbdom's propsModule only iterates over
|
|
61
|
+
* Snabbdom's propsModule only iterates over new props. It never resets
|
|
6
62
|
* old props that disappeared between renders. This means `elm.disabled = true`
|
|
7
63
|
* persists even after `Disabled(true)` is removed from the attribute array.
|
|
8
64
|
* Since a disabled button swallows click events at the browser level, an
|
|
@@ -10,7 +66,10 @@ import { VNodeDataMask } from './snabbdom/vnode.js';
|
|
|
10
66
|
*
|
|
11
67
|
* This module adds a second loop (mirroring what snabbdom's attributesModule
|
|
12
68
|
* already does) that resets removed props to type-appropriate defaults:
|
|
13
|
-
* booleans
|
|
69
|
+
* booleans become false, strings become empty, and numbers become zero. For
|
|
70
|
+
* reflected form and media state it also keeps the parsed default state in
|
|
71
|
+
* step with the controlled value, then restores raw-attribute ownership when
|
|
72
|
+
* the typed property leaves. */
|
|
14
73
|
function updateProps(oldVnode, vnode) {
|
|
15
74
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
16
75
|
const elm = vnode.elm;
|
|
@@ -26,25 +85,100 @@ function updateProps(oldVnode, vnode) {
|
|
|
26
85
|
}
|
|
27
86
|
oldProps = oldProps ?? {};
|
|
28
87
|
props = props ?? {};
|
|
29
|
-
|
|
88
|
+
if (elm instanceof Element) {
|
|
89
|
+
for (const key of Object.keys(oldProps)) {
|
|
90
|
+
if (key === 'innerHTML' &&
|
|
91
|
+
Object.hasOwn(props, key) &&
|
|
92
|
+
hasTrustedInnerHtml(oldProps) !== hasTrustedInnerHtml(props)) {
|
|
93
|
+
if (hasTrustedInnerHtml(oldProps)) {
|
|
94
|
+
writeNativeInnerHtml(elm, '');
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
resetProperty(elm, key, oldProps[key]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (Object.hasOwn(props, key) &&
|
|
101
|
+
!isClientOnlyProperty(oldProps, key) &&
|
|
102
|
+
isClientOnlyProperty(props, key)) {
|
|
103
|
+
clearControlledDefault(elm, key);
|
|
104
|
+
if (isNativeCustomElementAttribute(elm, oldProps, key)) {
|
|
105
|
+
const attributeName = reflectedAttributeName(key);
|
|
106
|
+
if (attributeName !== undefined) {
|
|
107
|
+
elm.removeAttribute(attributeName);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (Object.hasOwn(props, key) &&
|
|
112
|
+
isClientOnlyProperty(oldProps, key) &&
|
|
113
|
+
!isClientOnlyProperty(props, key) &&
|
|
114
|
+
isNativeCustomElementAttribute(elm, props, key)) {
|
|
115
|
+
resetProperty(elm, key, oldProps[key]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for (const key of Object.keys(props)) {
|
|
30
120
|
const cur = props[key];
|
|
31
121
|
const old = oldProps[key];
|
|
32
|
-
|
|
33
|
-
|
|
122
|
+
const isOwnershipChanged = isClientOnlyProperty(oldProps, key) !== isClientOnlyProperty(props, key);
|
|
123
|
+
if ((old !== cur || isOwnershipChanged) &&
|
|
124
|
+
(key !== 'value' || elm[key] !== cur)) {
|
|
125
|
+
if (key === 'innerHTML' &&
|
|
126
|
+
elm instanceof Element &&
|
|
127
|
+
hasTrustedInnerHtml(props)) {
|
|
128
|
+
writeNativeInnerHtml(elm, String(cur));
|
|
129
|
+
}
|
|
130
|
+
else if (elm instanceof Element &&
|
|
131
|
+
isNativeCustomElementAttribute(elm, props, key)) {
|
|
132
|
+
writeAttribute(elm, key, cur);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
writeProperty(elm, key, cur);
|
|
136
|
+
}
|
|
34
137
|
}
|
|
35
138
|
}
|
|
36
|
-
for (const key
|
|
37
|
-
if (!(key
|
|
139
|
+
for (const key of Object.keys(oldProps)) {
|
|
140
|
+
if (!Object.hasOwn(props, key)) {
|
|
38
141
|
const old = oldProps[key];
|
|
39
|
-
if (
|
|
40
|
-
elm
|
|
142
|
+
if (key === 'innerHTML' &&
|
|
143
|
+
elm instanceof Element &&
|
|
144
|
+
hasTrustedInnerHtml(oldProps)) {
|
|
145
|
+
writeNativeInnerHtml(elm, '');
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (elm instanceof Element &&
|
|
149
|
+
isNativeCustomElementAttribute(elm, oldProps, key)) {
|
|
150
|
+
const attributeName = reflectedAttributeName(key);
|
|
151
|
+
if (attributeName !== undefined) {
|
|
152
|
+
elm.removeAttribute(attributeName);
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (elm instanceof Element &&
|
|
157
|
+
key === 'value' &&
|
|
158
|
+
(elm.localName === 'select' ||
|
|
159
|
+
elm.localName === 'textarea' ||
|
|
160
|
+
elm.localName === 'output')) {
|
|
161
|
+
if (!Object.hasOwn(props, 'innerHTML')) {
|
|
162
|
+
clearControlledDefault(elm, key);
|
|
163
|
+
}
|
|
164
|
+
continue;
|
|
41
165
|
}
|
|
42
|
-
|
|
43
|
-
|
|
166
|
+
const attributeName = reflectedAttributeName(key);
|
|
167
|
+
if (attributeName !== undefined &&
|
|
168
|
+
!isClientOnlyProperty(oldProps, key) &&
|
|
169
|
+
elm instanceof Element) {
|
|
170
|
+
if (htmlAttributeValue(vnode.data?.attrs, attributeName) !== undefined) {
|
|
171
|
+
restoreControlledStateFromRawAttribute(elm, key);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
clearControlledDefault(elm, key);
|
|
175
|
+
elm.removeAttribute(attributeName);
|
|
176
|
+
continue;
|
|
44
177
|
}
|
|
45
|
-
|
|
46
|
-
elm
|
|
178
|
+
if (elm instanceof Element) {
|
|
179
|
+
clearControlledDefault(elm, key);
|
|
47
180
|
}
|
|
181
|
+
resetProperty(elm, key, old);
|
|
48
182
|
}
|
|
49
183
|
}
|
|
50
184
|
}
|
package/dist/runtime/public.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { SlowPhase, defaultSlowCallback, embed, makeApplication, makeElement, run, } from './runtime.js';
|
|
2
|
-
export type { RoutingConfig, CrashConfig, CrashContext, ElementCrashConfig, RoutingApplicationConfigWithFlags, RoutingApplicationConfig, ApplicationConfigWithFlags, ApplicationConfig, ElementConfigWithFlags, ElementConfig, ApplicationInit, RoutingApplicationInit, ElementInit, EmbedHandle, InboundPortHandle, InboundPortHandles, OutboundPortHandle, OutboundPortHandles, PortHandles, MakeRuntimeReturn, Visibility, SlowConfig, SlowContext, SlowPatchContext, SlowSubscriptionDependenciesContext, SlowThresholdOverrides, SlowUpdateContext, SlowViewContext, DevToolsConfig, DevToolsMode, DevToolsModeConfig, DevToolsPosition, } from './runtime.js';
|
|
1
|
+
export { SlowPhase, defaultSlowCallback, embed, hydrate, makeApplication, makeElement, run, } from './runtime.js';
|
|
2
|
+
export type { RoutingConfig, CrashConfig, CrashContext, ElementCrashConfig, RoutingApplicationConfigWithFlags, RoutingApplicationConfig, ApplicationConfigWithFlags, ApplicationConfig, ElementConfigWithFlags, ElementConfig, ApplicationInit, RoutingApplicationInit, ElementInit, EmbedHandle, HydrateOptions, InboundPortHandle, InboundPortHandles, OutboundPortHandle, OutboundPortHandles, PortHandles, MakeRuntimeReturn, RunOptions, Visibility, SlowConfig, SlowContext, SlowPatchContext, SlowSubscriptionDependenciesContext, SlowThresholdOverrides, SlowUpdateContext, SlowViewContext, DevToolsConfig, DevToolsMode, DevToolsModeConfig, DevToolsPosition, } from './runtime.js';
|
|
3
3
|
export type { ViewTransitionConfig, ViewTransitionContext, ViewTransitionDecision, } from './viewTransition.js';
|
|
4
4
|
//# sourceMappingURL=public.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/runtime/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,KAAK,EACL,eAAe,EACf,WAAW,EACX,GAAG,GACJ,MAAM,cAAc,CAAA;AAErB,YAAY,EACV,aAAa,EACb,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,iCAAiC,EACjC,wBAAwB,EACxB,0BAA0B,EAC1B,iBAAiB,EACjB,sBAAsB,EACtB,aAAa,EACb,eAAe,EACf,sBAAsB,EACtB,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,WAAW,EACX,iBAAiB,EACjB,UAAU,EACV,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,mCAAmC,EACnC,sBAAsB,EACtB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,cAAc,CAAA;AAErB,YAAY,EACV,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,qBAAqB,CAAA"}
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/runtime/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,KAAK,EACL,OAAO,EACP,eAAe,EACf,WAAW,EACX,GAAG,GACJ,MAAM,cAAc,CAAA;AAErB,YAAY,EACV,aAAa,EACb,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,iCAAiC,EACjC,wBAAwB,EACxB,0BAA0B,EAC1B,iBAAiB,EACjB,sBAAsB,EACtB,aAAa,EACb,eAAe,EACf,sBAAsB,EACtB,WAAW,EACX,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,WAAW,EACX,iBAAiB,EACjB,UAAU,EACV,UAAU,EACV,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,mCAAmC,EACnC,sBAAsB,EACtB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,cAAc,CAAA;AAErB,YAAY,EACV,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,qBAAqB,CAAA"}
|
package/dist/runtime/public.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { SlowPhase, defaultSlowCallback, embed, makeApplication, makeElement, run, } from './runtime.js';
|
|
1
|
+
export { SlowPhase, defaultSlowCallback, embed, hydrate, makeApplication, makeElement, run, } from './runtime.js';
|