@gyral/core 0.1.0 → 0.3.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/CHANGELOG.md +112 -0
- package/LICENSE +1 -1
- package/NOTICE +1 -1
- package/README.md +9 -7
- package/dist/children.d.ts +5 -2
- package/dist/children.js +8 -3
- package/dist/children.js.map +1 -1
- package/dist/command.js +6 -0
- package/dist/command.js.map +1 -1
- package/dist/compiler/ast.d.ts +21 -0
- package/dist/compiler/ast.js +145 -0
- package/dist/compiler/ast.js.map +1 -0
- package/dist/compiler/compile.d.ts +38 -0
- package/dist/compiler/compile.js +129 -0
- package/dist/compiler/compile.js.map +1 -0
- package/dist/compiler/hooks.d.ts +18 -0
- package/dist/compiler/hooks.js +186 -0
- package/dist/compiler/hooks.js.map +1 -0
- package/dist/compiler/parse5-check.d.ts +21 -0
- package/dist/compiler/parse5-check.js +55 -0
- package/dist/compiler/parse5-check.js.map +1 -0
- package/dist/compiler/scan.d.ts +31 -0
- package/dist/compiler/scan.js +152 -0
- package/dist/compiler/scan.js.map +1 -0
- package/dist/compiler/source.d.ts +34 -0
- package/dist/compiler/source.js +114 -0
- package/dist/compiler/source.js.map +1 -0
- package/dist/define.d.ts +2 -2
- package/dist/define.js +35 -334
- package/dist/define.js.map +1 -1
- package/dist/devtools-events.d.ts +5 -0
- package/dist/devtools-events.js.map +1 -1
- package/dist/devtools-off.d.ts +1 -0
- package/dist/devtools-off.js +1 -0
- package/dist/devtools-off.js.map +1 -1
- package/dist/devtools.d.ts +1 -0
- package/dist/devtools.js +3 -0
- package/dist/devtools.js.map +1 -1
- package/dist/drivers-scope.d.ts +1 -1
- package/dist/drivers-scope.js +1 -8
- package/dist/drivers-scope.js.map +1 -1
- package/dist/element-types.d.ts +5 -3
- package/dist/element-types.js.map +1 -1
- package/dist/element.d.ts +3 -0
- package/dist/element.js +269 -0
- package/dist/element.js.map +1 -0
- package/dist/eslint/imports.d.ts +24 -0
- package/dist/eslint/imports.js +78 -0
- package/dist/eslint/imports.js.map +1 -0
- package/dist/eslint/index.d.ts +15 -0
- package/dist/eslint/index.js +15 -0
- package/dist/eslint/index.js.map +1 -0
- package/dist/eslint/locate.d.ts +33 -0
- package/dist/eslint/locate.js +132 -0
- package/dist/eslint/locate.js.map +1 -0
- package/dist/eslint/row-purity-rule.d.ts +2 -0
- package/dist/eslint/row-purity-rule.js +103 -0
- package/dist/eslint/row-purity-rule.js.map +1 -0
- package/dist/eslint/template-rule.d.ts +5 -0
- package/dist/eslint/template-rule.js +66 -0
- package/dist/eslint/template-rule.js.map +1 -0
- package/dist/features.d.ts +39 -0
- package/dist/features.js +2 -0
- package/dist/features.js.map +1 -0
- package/dist/focus.d.ts +9 -2
- package/dist/focus.js +17 -2
- package/dist/focus.js.map +1 -1
- package/dist/hooks/invalid.d.ts +14 -0
- package/dist/hooks/invalid.js +72 -0
- package/dist/hooks/invalid.js.map +1 -0
- package/dist/hooks/labelled-by.d.ts +13 -0
- package/dist/hooks/labelled-by.js +51 -0
- package/dist/hooks/labelled-by.js.map +1 -0
- package/dist/host-model.d.ts +48 -0
- package/dist/host-model.js +155 -0
- package/dist/host-model.js.map +1 -0
- package/dist/hydration-client.d.ts +9 -0
- package/dist/hydration-client.js +35 -0
- package/dist/hydration-client.js.map +1 -0
- package/dist/hydration.d.ts +13 -19
- package/dist/hydration.js +45 -45
- package/dist/hydration.js.map +1 -1
- package/dist/index.d.ts +9 -16
- package/dist/index.js +9 -16
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +31 -8
- package/dist/intent.js +82 -27
- package/dist/intent.js.map +1 -1
- package/dist/internal/interpreter.d.ts +7 -1
- package/dist/internal/interpreter.js +118 -73
- package/dist/internal/interpreter.js.map +1 -1
- package/dist/invokers-shim.d.ts +6 -0
- package/dist/invokers-shim.js +32 -0
- package/dist/invokers-shim.js.map +1 -0
- package/dist/invokers.d.ts +3 -5
- package/dist/invokers.js +11 -51
- package/dist/invokers.js.map +1 -1
- package/dist/islands.d.ts +0 -2
- package/dist/islands.js +4 -11
- package/dist/islands.js.map +1 -1
- package/dist/light-dom.d.ts +3 -52
- package/dist/light-dom.js +5 -100
- package/dist/light-dom.js.map +1 -1
- package/dist/prop.d.ts +66 -0
- package/dist/prop.js +22 -0
- package/dist/prop.js.map +1 -0
- package/dist/props.d.ts +52 -21
- package/dist/props.js +150 -34
- package/dist/props.js.map +1 -1
- package/dist/scheduler.d.ts +39 -0
- package/dist/scheduler.js +230 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/server-component.d.ts +10 -0
- package/dist/server-component.js +102 -0
- package/dist/server-component.js.map +1 -0
- package/dist/server-specs.d.ts +6 -0
- package/dist/server-specs.js +13 -0
- package/dist/server-specs.js.map +1 -0
- package/dist/server.d.ts +28 -0
- package/dist/server.js +50 -0
- package/dist/server.js.map +1 -0
- package/dist/settled.d.ts +10 -0
- package/dist/settled.js +40 -0
- package/dist/settled.js.map +1 -0
- package/dist/states.d.ts +3 -3
- package/dist/states.js +4 -7
- package/dist/states.js.map +1 -1
- package/dist/store-binding.d.ts +6 -3
- package/dist/store-binding.js +5 -6
- package/dist/store-binding.js.map +1 -1
- package/dist/store-scope.d.ts +6 -7
- package/dist/store-scope.js +11 -26
- package/dist/store-scope.js.map +1 -1
- package/dist/store.d.ts +3 -0
- package/dist/store.js +9 -2
- package/dist/store.js.map +1 -1
- package/dist/stores-provider.d.ts +2 -6
- package/dist/stores-provider.js +21 -55
- package/dist/stores-provider.js.map +1 -1
- package/dist/transitions.d.ts +5 -6
- package/dist/transitions.js +11 -20
- package/dist/transitions.js.map +1 -1
- package/dist/types.d.ts +19 -23
- package/dist/types.js.map +1 -1
- package/dist/view/attributes.d.ts +6 -0
- package/dist/view/attributes.js +10 -0
- package/dist/view/attributes.js.map +1 -0
- package/dist/view/compiled.d.ts +1 -0
- package/dist/view/compiled.js +6 -0
- package/dist/view/compiled.js.map +1 -0
- package/dist/view/dev-off.d.ts +1 -0
- package/dist/view/dev-off.js +4 -0
- package/dist/view/dev-off.js.map +1 -0
- package/dist/view/dev.d.ts +1 -0
- package/dist/view/dev.js +5 -0
- package/dist/view/dev.js.map +1 -0
- package/dist/view/flags.d.ts +1 -0
- package/dist/view/flags.js +4 -0
- package/dist/view/flags.js.map +1 -0
- package/dist/view/index.d.ts +21 -0
- package/dist/view/index.js +21 -0
- package/dist/view/index.js.map +1 -0
- package/dist/view/normalize/check.d.ts +20 -0
- package/dist/view/normalize/check.js +78 -0
- package/dist/view/normalize/check.js.map +1 -0
- package/dist/view/normalize/emit.d.ts +13 -0
- package/dist/view/normalize/emit.js +136 -0
- package/dist/view/normalize/emit.js.map +1 -0
- package/dist/view/normalize/entities.d.ts +4 -0
- package/dist/view/normalize/entities.js +38 -0
- package/dist/view/normalize/entities.js.map +1 -0
- package/dist/view/normalize/errors.d.ts +9 -0
- package/dist/view/normalize/errors.js +19 -0
- package/dist/view/normalize/errors.js.map +1 -0
- package/dist/view/normalize/id.d.ts +7 -0
- package/dist/view/normalize/id.js +48 -0
- package/dist/view/normalize/id.js.map +1 -0
- package/dist/view/normalize/normalize.d.ts +15 -0
- package/dist/view/normalize/normalize.js +36 -0
- package/dist/view/normalize/normalize.js.map +1 -0
- package/dist/view/normalize/repairs.d.ts +24 -0
- package/dist/view/normalize/repairs.js +166 -0
- package/dist/view/normalize/repairs.js.map +1 -0
- package/dist/view/normalize/rules.d.ts +30 -0
- package/dist/view/normalize/rules.js +90 -0
- package/dist/view/normalize/rules.js.map +1 -0
- package/dist/view/normalize/shape.d.ts +5 -0
- package/dist/view/normalize/shape.js +38 -0
- package/dist/view/normalize/shape.js.map +1 -0
- package/dist/view/normalize/source.d.ts +13 -0
- package/dist/view/normalize/source.js +34 -0
- package/dist/view/normalize/source.js.map +1 -0
- package/dist/view/normalize/tag-states.d.ts +23 -0
- package/dist/view/normalize/tag-states.js +192 -0
- package/dist/view/normalize/tag-states.js.map +1 -0
- package/dist/view/normalize/tokenizer.d.ts +5 -0
- package/dist/view/normalize/tokenizer.js +233 -0
- package/dist/view/normalize/tokenizer.js.map +1 -0
- package/dist/view/normalize/tokens.d.ts +51 -0
- package/dist/view/normalize/tokens.js +24 -0
- package/dist/view/normalize/tokens.js.map +1 -0
- package/dist/view/normalize/tree.d.ts +59 -0
- package/dist/view/normalize/tree.js +190 -0
- package/dist/view/normalize/tree.js.map +1 -0
- package/dist/view/normalize/types.d.ts +126 -0
- package/dist/view/normalize/types.js +26 -0
- package/dist/view/normalize/types.js.map +1 -0
- package/dist/view/normalize/whitespace.d.ts +2 -0
- package/dist/view/normalize/whitespace.js +182 -0
- package/dist/view/normalize/whitespace.js.map +1 -0
- package/dist/view/prepare-stub.d.ts +3 -0
- package/dist/view/prepare-stub.js +7 -0
- package/dist/view/prepare-stub.js.map +1 -0
- package/dist/view/prepare.d.ts +8 -0
- package/dist/view/prepare.js +57 -0
- package/dist/view/prepare.js.map +1 -0
- package/dist/view/registry.d.ts +63 -0
- package/dist/view/registry.js +19 -0
- package/dist/view/registry.js.map +1 -0
- package/dist/view/render/adopt-attr.d.ts +14 -0
- package/dist/view/render/adopt-attr.js +77 -0
- package/dist/view/render/adopt-attr.js.map +1 -0
- package/dist/view/render/adopt-plan.d.ts +10 -0
- package/dist/view/render/adopt-plan.js +26 -0
- package/dist/view/render/adopt-plan.js.map +1 -0
- package/dist/view/render/adopt.d.ts +3 -0
- package/dist/view/render/adopt.js +269 -0
- package/dist/view/render/adopt.js.map +1 -0
- package/dist/view/render/attr-parts.d.ts +61 -0
- package/dist/view/render/attr-parts.js +229 -0
- package/dist/view/render/attr-parts.js.map +1 -0
- package/dist/view/render/child-part.d.ts +51 -0
- package/dist/view/render/child-part.js +170 -0
- package/dist/view/render/child-part.js.map +1 -0
- package/dist/view/render/dev-check.d.ts +12 -0
- package/dist/view/render/dev-check.js +122 -0
- package/dist/view/render/dev-check.js.map +1 -0
- package/dist/view/render/hook-part.d.ts +3 -0
- package/dist/view/render/hook-part.js +20 -0
- package/dist/view/render/hook-part.js.map +1 -0
- package/dist/view/render/hooks.d.ts +40 -0
- package/dist/view/render/hooks.js +46 -0
- package/dist/view/render/hooks.js.map +1 -0
- package/dist/view/render/hydrate.d.ts +10 -0
- package/dist/view/render/hydrate.js +33 -0
- package/dist/view/render/hydrate.js.map +1 -0
- package/dist/view/render/instance.d.ts +33 -0
- package/dist/view/render/instance.js +107 -0
- package/dist/view/render/instance.js.map +1 -0
- package/dist/view/render/list.d.ts +20 -0
- package/dist/view/render/list.js +156 -0
- package/dist/view/render/list.js.map +1 -0
- package/dist/view/render/mismatch.d.ts +10 -0
- package/dist/view/render/mismatch.js +53 -0
- package/dist/view/render/mismatch.js.map +1 -0
- package/dist/view/render/nodes.d.ts +10 -0
- package/dist/view/render/nodes.js +37 -0
- package/dist/view/render/nodes.js.map +1 -0
- package/dist/view/render/place.d.ts +18 -0
- package/dist/view/render/place.js +69 -0
- package/dist/view/render/place.js.map +1 -0
- package/dist/view/render/plan.d.ts +26 -0
- package/dist/view/render/plan.js +124 -0
- package/dist/view/render/plan.js.map +1 -0
- package/dist/view/render/raw.d.ts +18 -0
- package/dist/view/render/raw.js +68 -0
- package/dist/view/render/raw.js.map +1 -0
- package/dist/view/render/render.d.ts +12 -0
- package/dist/view/render/render.js +60 -0
- package/dist/view/render/render.js.map +1 -0
- package/dist/view/render/reorder.d.ts +4 -0
- package/dist/view/render/reorder.js +101 -0
- package/dist/view/render/reorder.js.map +1 -0
- package/dist/view/render/rows.d.ts +23 -0
- package/dist/view/render/rows.js +95 -0
- package/dist/view/render/rows.js.map +1 -0
- package/dist/view/render/seen.d.ts +11 -0
- package/dist/view/render/seen.js +17 -0
- package/dist/view/render/seen.js.map +1 -0
- package/dist/view/render/values.d.ts +29 -0
- package/dist/view/render/values.js +13 -0
- package/dist/view/render/values.js.map +1 -0
- package/dist/view/render/warn.d.ts +14 -0
- package/dist/view/render/warn.js +69 -0
- package/dist/view/render/warn.js.map +1 -0
- package/dist/view/server/component.d.ts +9 -0
- package/dist/view/server/component.js +42 -0
- package/dist/view/server/component.js.map +1 -0
- package/dist/view/server/escape.d.ts +10 -0
- package/dist/view/server/escape.js +34 -0
- package/dist/view/server/escape.js.map +1 -0
- package/dist/view/server/index.d.ts +28 -0
- package/dist/view/server/index.js +72 -0
- package/dist/view/server/index.js.map +1 -0
- package/dist/view/server/sha256.d.ts +2 -0
- package/dist/view/server/sha256.js +75 -0
- package/dist/view/server/sha256.js.map +1 -0
- package/dist/view/server/tags.d.ts +57 -0
- package/dist/view/server/tags.js +121 -0
- package/dist/view/server/tags.js.map +1 -0
- package/dist/view/server/values.d.ts +16 -0
- package/dist/view/server/values.js +86 -0
- package/dist/view/server/values.js.map +1 -0
- package/dist/view/server/writer.d.ts +16 -0
- package/dist/view/server/writer.js +155 -0
- package/dist/view/server/writer.js.map +1 -0
- package/dist/view/styles.d.ts +25 -0
- package/dist/view/styles.js +58 -0
- package/dist/view/styles.js.map +1 -0
- package/dist/view/template-element.d.ts +3 -0
- package/dist/view/template-element.js +25 -0
- package/dist/view/template-element.js.map +1 -0
- package/dist/view/template.d.ts +21 -0
- package/dist/view/template.js +30 -0
- package/dist/view/template.js.map +1 -0
- package/dist/vite.d.ts +45 -6
- package/dist/vite.js +150 -25
- package/dist/vite.js.map +1 -1
- package/package.json +41 -7
- package/dist/accessible-name.d.ts +0 -19
- package/dist/accessible-name.js +0 -56
- package/dist/accessible-name.js.map +0 -1
- package/dist/element-directive.d.ts +0 -20
- package/dist/element-directive.js +0 -31
- package/dist/element-directive.js.map +0 -1
- package/dist/internal/runtime.d.ts +0 -7
- package/dist/internal/runtime.js +0 -8
- package/dist/internal/runtime.js.map +0 -1
- package/dist/invalid.d.ts +0 -21
- package/dist/invalid.js +0 -70
- package/dist/invalid.js.map +0 -1
- package/dist/live-boolean.d.ts +0 -22
- package/dist/live-boolean.js +0 -34
- package/dist/live-boolean.js.map +0 -1
- package/dist/styles.d.ts +0 -12
- package/dist/styles.js +0 -11
- package/dist/styles.js.map +0 -1
- package/dist/textarea.d.ts +0 -26
- package/dist/textarea.js +0 -85
- package/dist/textarea.js.map +0 -1
package/dist/props.d.ts
CHANGED
|
@@ -1,26 +1,57 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Prop } from './prop.js';
|
|
2
2
|
type Bag = Readonly<Record<string, unknown>>;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export type
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
export declare function
|
|
17
|
-
/**
|
|
18
|
-
export declare function
|
|
3
|
+
export type PropTable = Readonly<Record<string, Prop<unknown>>>;
|
|
4
|
+
/** `minPrice` → `min-price`. */
|
|
5
|
+
export declare const kebab: (name: string) => string;
|
|
6
|
+
/** The prop's attribute, or undefined for a property-only prop. */
|
|
7
|
+
export declare function attributeOf(name: string, def: Prop<unknown>): string | undefined;
|
|
8
|
+
export type Checked = {
|
|
9
|
+
readonly ok: true;
|
|
10
|
+
readonly value: unknown;
|
|
11
|
+
} | {
|
|
12
|
+
readonly ok: false;
|
|
13
|
+
readonly issues: readonly string[];
|
|
14
|
+
};
|
|
15
|
+
/** Runs the built-in type check and the prop's schema. Schemas must be synchronous. */
|
|
16
|
+
export declare function checkValue(tag: string, name: string, def: Prop<unknown>, value: unknown): Checked;
|
|
17
|
+
/** Parses an attribute value (`null`: absent) per the builder table; undefined means missing. */
|
|
18
|
+
export declare function parseAttribute(def: Prop<unknown>, raw: string | null): Checked | undefined;
|
|
19
|
+
/** Logs an invalid value; the caller then treats the prop as missing. */
|
|
20
|
+
export declare function reportInvalid(tag: string, name: string, from: string, issues: readonly string[]): void;
|
|
19
21
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
* An attribute's value for prop `name` (view/05-element.md "When props are validated"):
|
|
23
|
+
* parsed per the builder and always validated; invalid or absent values are missing.
|
|
24
|
+
*/
|
|
25
|
+
export declare function attributeValue(tag: string, name: string, def: Prop<unknown> | undefined, attr: string, raw: string | null): unknown;
|
|
26
|
+
/**
|
|
27
|
+
* A property set's (or seed's) value for prop `name`: validated in development only, where an
|
|
28
|
+
* invalid value is missing and a schema that transforms warns (production keeps the input).
|
|
29
|
+
*/
|
|
30
|
+
export declare function propertyValue(tag: string, name: string, def: Prop<unknown> | undefined, value: unknown, from?: string): unknown;
|
|
31
|
+
/** Structural equality for plain data (arrays, plain objects), `Object.is` for the rest. */
|
|
32
|
+
export declare function sameData(a: unknown, b: unknown): boolean;
|
|
33
|
+
/** The declared props as components see them: `default` fills missing values. */
|
|
34
|
+
export declare function readProps(values: Bag, table: PropTable): Bag;
|
|
35
|
+
/** Required props that are still missing (ADR 0007 addendum). */
|
|
36
|
+
export declare function missingRequired(values: Bag, table: PropTable): string[];
|
|
37
|
+
/** Warns about required props that are still missing, at first render. */
|
|
38
|
+
export declare function warnMissing(tag: string, values: Bag, table: PropTable): void;
|
|
39
|
+
/**
|
|
40
|
+
* Declared props that collide with a built-in element property (`hidden`, `title`, `id`, …):
|
|
41
|
+
* the accessor replaces the built-in, so setting the prop silently changes what the platform
|
|
42
|
+
* does with it (gyral-czi.33).
|
|
24
43
|
*/
|
|
25
44
|
export declare function shadowedBuiltins(names: readonly string[]): string[];
|
|
45
|
+
/** define(): a prop named like a built-in property is an error in development, else a warning. */
|
|
46
|
+
export declare function checkShadowed(tag: string, names: readonly string[]): void;
|
|
47
|
+
/** What the element uses, registered by the prop builders (features.ts). */
|
|
48
|
+
export declare const propFeature: {
|
|
49
|
+
attributeOf: typeof attributeOf;
|
|
50
|
+
attributeValue: typeof attributeValue;
|
|
51
|
+
propertyValue: typeof propertyValue;
|
|
52
|
+
warnMissing: typeof warnMissing;
|
|
53
|
+
readProps: typeof readProps;
|
|
54
|
+
checkShadowed: typeof checkShadowed;
|
|
55
|
+
};
|
|
56
|
+
export type PropFeature = typeof propFeature;
|
|
26
57
|
export {};
|
package/dist/props.js
CHANGED
|
@@ -1,44 +1,160 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
1
|
+
import { DEV } from './view/index.js';
|
|
2
|
+
/** `minPrice` → `min-price`. */
|
|
3
|
+
export const kebab = (name) => name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
4
|
+
/** The prop's attribute, or undefined for a property-only prop. */
|
|
5
|
+
export function attributeOf(name, def) {
|
|
6
|
+
if (def.attribute === false)
|
|
7
|
+
return undefined;
|
|
8
|
+
return def.attribute ?? kebab(name);
|
|
9
|
+
}
|
|
10
|
+
const builtIn = {
|
|
11
|
+
string: (v) => typeof v === 'string',
|
|
12
|
+
number: (v) => typeof v === 'number' && !Number.isNaN(v),
|
|
13
|
+
boolean: (v) => typeof v === 'boolean',
|
|
14
|
+
};
|
|
15
|
+
/** Runs the built-in type check and the prop's schema. Schemas must be synchronous. */
|
|
16
|
+
export function checkValue(tag, name, def, value) {
|
|
17
|
+
const typeOk = builtIn[def.kind];
|
|
18
|
+
if (typeOk !== undefined && !typeOk(value)) {
|
|
19
|
+
return {
|
|
20
|
+
ok: false,
|
|
21
|
+
issues: [`expected a ${def.kind}, got ${typeof value}`],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (def.schema === undefined)
|
|
25
|
+
return { ok: true, value };
|
|
26
|
+
const result = def.schema['~standard'].validate(value);
|
|
27
|
+
if (result instanceof Promise) {
|
|
28
|
+
throw new TypeError(`<${tag}> prop "${name}" has an asynchronous schema; it must be synchronous.`);
|
|
29
|
+
}
|
|
30
|
+
if (result.issues !== undefined)
|
|
31
|
+
return { ok: false, issues: result.issues.map((i) => i.message) };
|
|
32
|
+
return { ok: true, value: result.value };
|
|
33
|
+
}
|
|
34
|
+
/** Parses an attribute value (`null`: absent) per the builder table; undefined means missing. */
|
|
35
|
+
export function parseAttribute(def, raw) {
|
|
36
|
+
if (def.kind === 'boolean')
|
|
37
|
+
return { ok: true, value: raw !== null };
|
|
38
|
+
if (raw === null)
|
|
39
|
+
return undefined;
|
|
40
|
+
if (def.kind === 'number') {
|
|
41
|
+
const n = raw.trim() === '' ? Number.NaN : Number(raw);
|
|
42
|
+
return Number.isNaN(n)
|
|
43
|
+
? { ok: false, issues: [`"${raw}" is not a number`] }
|
|
44
|
+
: { ok: true, value: n };
|
|
45
|
+
}
|
|
46
|
+
if (def.kind === 'json') {
|
|
47
|
+
try {
|
|
48
|
+
return { ok: true, value: JSON.parse(raw) };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return { ok: false, issues: ['the attribute is not valid JSON'] };
|
|
52
|
+
}
|
|
31
53
|
}
|
|
54
|
+
return { ok: true, value: raw };
|
|
55
|
+
}
|
|
56
|
+
/** Logs an invalid value; the caller then treats the prop as missing. */
|
|
57
|
+
export function reportInvalid(tag, name, from, issues) {
|
|
58
|
+
console.error(`<${tag}> prop "${name}" got an invalid value from ${from}, so it is treated as missing: ` +
|
|
59
|
+
issues.join('; '));
|
|
32
60
|
}
|
|
33
61
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
* An attribute's value for prop `name` (view/05-element.md "When props are validated"):
|
|
63
|
+
* parsed per the builder and always validated; invalid or absent values are missing.
|
|
64
|
+
*/
|
|
65
|
+
export function attributeValue(tag, name, def, attr, raw) {
|
|
66
|
+
const parsed = def === undefined ? undefined : parseAttribute(def, raw);
|
|
67
|
+
if (def === undefined || parsed === undefined)
|
|
68
|
+
return undefined;
|
|
69
|
+
const checked = parsed.ok ? checkValue(tag, name, def, parsed.value) : parsed;
|
|
70
|
+
if (checked.ok)
|
|
71
|
+
return checked.value;
|
|
72
|
+
reportInvalid(tag, name, `the attribute ${attr}`, checked.issues);
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* A property set's (or seed's) value for prop `name`: validated in development only, where an
|
|
77
|
+
* invalid value is missing and a schema that transforms warns (production keeps the input).
|
|
78
|
+
*/
|
|
79
|
+
export function propertyValue(tag, name, def, value, from = 'a property') {
|
|
80
|
+
if (!DEV || value === undefined || def === undefined)
|
|
81
|
+
return value;
|
|
82
|
+
const checked = checkValue(tag, name, def, value);
|
|
83
|
+
if (!checked.ok) {
|
|
84
|
+
reportInvalid(tag, name, from, checked.issues);
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
if (!sameData(checked.value, value)) {
|
|
88
|
+
console.warn(`<${tag}> prop "${name}": its schema transformed the value from ${from}. Production ` +
|
|
89
|
+
"doesn't run schemas on property sets; validate, don't transform.");
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
/** Structural equality for plain data (arrays, plain objects), `Object.is` for the rest. */
|
|
94
|
+
export function sameData(a, b) {
|
|
95
|
+
if (Object.is(a, b))
|
|
96
|
+
return true;
|
|
97
|
+
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null)
|
|
98
|
+
return false;
|
|
99
|
+
if (Array.isArray(a)) {
|
|
100
|
+
return Array.isArray(b) && a.length === b.length && a.every((x, i) => sameData(x, b[i]));
|
|
101
|
+
}
|
|
102
|
+
if (Object.getPrototypeOf(a) !== Object.prototype ||
|
|
103
|
+
Object.getPrototypeOf(b) !== Object.prototype) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
const ka = Object.keys(a);
|
|
107
|
+
return (ka.length === Object.keys(b).length && ka.every((k) => sameData(a[k], b[k])));
|
|
108
|
+
}
|
|
109
|
+
/** The declared props as components see them: `default` fills missing values. */
|
|
110
|
+
export function readProps(values, table) {
|
|
111
|
+
const out = {};
|
|
112
|
+
for (const [name, def] of Object.entries(table)) {
|
|
113
|
+
const value = values[name];
|
|
114
|
+
out[name] = value === undefined ? def.default : value;
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
/** Required props that are still missing (ADR 0007 addendum). */
|
|
119
|
+
export function missingRequired(values, table) {
|
|
120
|
+
return Object.keys(table).filter((name) => table[name]?.required === true && values[name] === undefined);
|
|
121
|
+
}
|
|
122
|
+
/** Warns about required props that are still missing, at first render. */
|
|
123
|
+
export function warnMissing(tag, values, table) {
|
|
124
|
+
const missing = missingRequired(values, table);
|
|
125
|
+
if (missing.length > 0) {
|
|
126
|
+
console.warn(`<${tag}> is missing required prop(s): ${missing.join(', ')}.`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Declared props that collide with a built-in element property (`hidden`, `title`, `id`, …):
|
|
131
|
+
* the accessor replaces the built-in, so setting the prop silently changes what the platform
|
|
132
|
+
* does with it (gyral-czi.33).
|
|
38
133
|
*/
|
|
39
134
|
export function shadowedBuiltins(names) {
|
|
40
135
|
if (typeof HTMLElement === 'undefined')
|
|
41
136
|
return [];
|
|
42
137
|
return names.filter((name) => name in HTMLElement.prototype);
|
|
43
138
|
}
|
|
139
|
+
/** define(): a prop named like a built-in property is an error in development, else a warning. */
|
|
140
|
+
export function checkShadowed(tag, names) {
|
|
141
|
+
const shadowed = shadowedBuiltins(names);
|
|
142
|
+
if (shadowed.length === 0)
|
|
143
|
+
return;
|
|
144
|
+
const message = `<${tag}> declares prop(s) that shadow built-in element properties: ` +
|
|
145
|
+
`${shadowed.join(', ')}. Setting them changes platform behaviour ` +
|
|
146
|
+
`(a prop named "hidden" hides the element). Rename them (view/05-element.md).`;
|
|
147
|
+
if (DEV)
|
|
148
|
+
throw new TypeError(message);
|
|
149
|
+
console.warn(message);
|
|
150
|
+
}
|
|
151
|
+
/** What the element uses, registered by the prop builders (features.ts). */
|
|
152
|
+
export const propFeature = {
|
|
153
|
+
attributeOf,
|
|
154
|
+
attributeValue,
|
|
155
|
+
propertyValue,
|
|
156
|
+
warnMissing,
|
|
157
|
+
readProps,
|
|
158
|
+
checkShadowed,
|
|
159
|
+
};
|
|
44
160
|
//# sourceMappingURL=props.js.map
|
package/dist/props.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"props.js","sourceRoot":"","sources":["../src/props.ts"],"names":[],"mappings":"AAaA,uFAAuF;AACvF,MAAM,UAAU,YAAY,CAAC,EAAU,EAAE,KAAgB;IACvD,MAAM,IAAI,GAAG,EAAS,CAAC;IACvB,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAClF,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,SAAS,CAAC,EAAU,EAAE,KAAgB;IACpD,MAAM,IAAI,GAAG,EAAS,CAAC;IACvB,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QACzB,OAAO,CAAC,IAAI,EAAE,KAAK,KAAK,SAAS,IAAI,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACjF,CAAC,CAAC,CACH,CAAC;AACJ,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,eAAe,CAAC,EAAU,EAAE,KAAgB;IAC1D,MAAM,IAAI,GAAG,EAAS,CAAC;IACvB,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;SACzB,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC;SAC5E,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,SAAS,CAAI,KAAwB,EAAE,CAAI,EAAE,CAAI;IAC/D,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAE,CAAS,CAAC,IAAI,CAAC,EAAG,CAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,YAAY,CAAC,EAAU,EAAE,KAAwB,EAAE,MAAW;IAC5E,MAAM,IAAI,GAAG,EAA6B,CAAC;IAC3C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IAC3E,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAwB;IACvD,IAAI,OAAO,WAAW,KAAK,WAAW;QAAE,OAAO,EAAE,CAAC;IAClD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,WAAW,CAAC,SAAS,CAAC,CAAC;AAC/D,CAAC","sourcesContent":["// Prop bookkeeping for define() (ADR 0007): snapshots, defaults, change detection, seed restore.\nimport type { PropertyDeclaration } from 'lit';\n\ntype Bag = Readonly<Record<string, unknown>>;\n\n/** Gyral's view of a declaration: Lit's options plus `required` and `default`. */\nexport interface PropInfo extends PropertyDeclaration {\n readonly required?: boolean;\n readonly default?: unknown;\n}\n\nexport type PropTable = Readonly<Record<string, PropInfo>>;\n\n/** The element's declared props as set on it (no defaults): what a seed must carry. */\nexport function readRawProps(el: object, table: PropTable): Bag {\n const self = el as Bag;\n return Object.fromEntries(Object.keys(table).map((name) => [name, self[name]]));\n}\n\n/** The element's declared props as components see them: `default` fills `undefined`. */\nexport function readProps(el: object, table: PropTable): Bag {\n const self = el as Bag;\n return Object.fromEntries(\n Object.entries(table).map(([name, info]) => {\n const value = self[name];\n return [name, value === undefined && 'default' in info ? info.default : value];\n }),\n );\n}\n\n/** Required props that are still `undefined` (ADR 0007 addendum). */\nexport function missingRequired(el: object, table: PropTable): string[] {\n const self = el as Bag;\n return Object.entries(table)\n .filter(([name, info]) => info.required === true && self[name] === undefined)\n .map(([name]) => name);\n}\n\n/** True when every declared prop is identical (`Object.is`) in both snapshots. */\nexport function sameProps<P>(names: readonly string[], a: P, b: P): boolean {\n return names.every((name) => Object.is((a as Bag)[name], (b as Bag)[name]));\n}\n\n/** Hydration: sets props that only travelled in the seed, unless the page already set them. */\nexport function restoreProps(el: object, names: readonly string[], seeded: Bag): void {\n const self = el as Record<string, unknown>;\n for (const [name, value] of Object.entries(seeded)) {\n if (names.includes(name) && self[name] === undefined) self[name] = value;\n }\n}\n\n/**\n * Declared props that collide with a built-in element property (`hidden`, `title`, `id`, …).\n * Lit's reactive accessor replaces the built-in, so setting the prop silently changes what the\n * platform does with it: a prop named `hidden` hides the element (gyral-czi.33). Browser only:\n * the server's DOM shim doesn't model the full prototype chain.\n */\nexport function shadowedBuiltins(names: readonly string[]): string[] {\n if (typeof HTMLElement === 'undefined') return [];\n return names.filter((name) => name in HTMLElement.prototype);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"props.js","sourceRoot":"","sources":["../src/props.ts"],"names":[],"mappings":"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","sourcesContent":["// Prop bookkeeping for define() (view/05-element.md \"Props\", ADR 0007): attribute names and\n// parsing, validation through Standard Schema, defaults, change detection, seed restore. The\n// prop builders register `propFeature` (features.ts): components without props don't bundle it.\nimport type { Prop } from './prop.js';\nimport { DEV } from './view/index.js';\n\ntype Bag = Readonly<Record<string, unknown>>;\n\nexport type PropTable = Readonly<Record<string, Prop<unknown>>>;\n\n/** `minPrice` → `min-price`. */\nexport const kebab = (name: string): string => name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n\n/** The prop's attribute, or undefined for a property-only prop. */\nexport function attributeOf(name: string, def: Prop<unknown>): string | undefined {\n if (def.attribute === false) return undefined;\n return def.attribute ?? kebab(name);\n}\n\nexport type Checked =\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly issues: readonly string[] };\n\nconst builtIn: Readonly<Record<string, (v: unknown) => boolean>> = {\n string: (v) => typeof v === 'string',\n number: (v) => typeof v === 'number' && !Number.isNaN(v),\n boolean: (v) => typeof v === 'boolean',\n};\n\n/** Runs the built-in type check and the prop's schema. Schemas must be synchronous. */\nexport function checkValue(tag: string, name: string, def: Prop<unknown>, value: unknown): Checked {\n const typeOk = builtIn[def.kind];\n if (typeOk !== undefined && !typeOk(value)) {\n return {\n ok: false,\n issues: [`expected a ${def.kind}, got ${typeof value}`],\n };\n }\n if (def.schema === undefined) return { ok: true, value };\n const result = def.schema['~standard'].validate(value);\n if (result instanceof Promise) {\n throw new TypeError(\n `<${tag}> prop \"${name}\" has an asynchronous schema; it must be synchronous.`,\n );\n }\n if (result.issues !== undefined)\n return { ok: false, issues: result.issues.map((i) => i.message) };\n return { ok: true, value: result.value };\n}\n\n/** Parses an attribute value (`null`: absent) per the builder table; undefined means missing. */\nexport function parseAttribute(def: Prop<unknown>, raw: string | null): Checked | undefined {\n if (def.kind === 'boolean') return { ok: true, value: raw !== null };\n if (raw === null) return undefined;\n if (def.kind === 'number') {\n const n = raw.trim() === '' ? Number.NaN : Number(raw);\n return Number.isNaN(n)\n ? { ok: false, issues: [`\"${raw}\" is not a number`] }\n : { ok: true, value: n };\n }\n if (def.kind === 'json') {\n try {\n return { ok: true, value: JSON.parse(raw) as unknown };\n } catch {\n return { ok: false, issues: ['the attribute is not valid JSON'] };\n }\n }\n return { ok: true, value: raw };\n}\n\n/** Logs an invalid value; the caller then treats the prop as missing. */\nexport function reportInvalid(tag: string, name: string, from: string, issues: readonly string[]) {\n console.error(\n `<${tag}> prop \"${name}\" got an invalid value from ${from}, so it is treated as missing: ` +\n issues.join('; '),\n );\n}\n\n/**\n * An attribute's value for prop `name` (view/05-element.md \"When props are validated\"):\n * parsed per the builder and always validated; invalid or absent values are missing.\n */\nexport function attributeValue(\n tag: string,\n name: string,\n def: Prop<unknown> | undefined,\n attr: string,\n raw: string | null,\n): unknown {\n const parsed = def === undefined ? undefined : parseAttribute(def, raw);\n if (def === undefined || parsed === undefined) return undefined;\n const checked = parsed.ok ? checkValue(tag, name, def, parsed.value) : parsed;\n if (checked.ok) return checked.value;\n reportInvalid(tag, name, `the attribute ${attr}`, checked.issues);\n return undefined;\n}\n\n/**\n * A property set's (or seed's) value for prop `name`: validated in development only, where an\n * invalid value is missing and a schema that transforms warns (production keeps the input).\n */\nexport function propertyValue(\n tag: string,\n name: string,\n def: Prop<unknown> | undefined,\n value: unknown,\n from = 'a property',\n): unknown {\n if (!DEV || value === undefined || def === undefined) return value;\n const checked = checkValue(tag, name, def, value);\n if (!checked.ok) {\n reportInvalid(tag, name, from, checked.issues);\n return undefined;\n }\n if (!sameData(checked.value, value)) {\n console.warn(\n `<${tag}> prop \"${name}\": its schema transformed the value from ${from}. Production ` +\n \"doesn't run schemas on property sets; validate, don't transform.\",\n );\n }\n return value;\n}\n\n/** Structural equality for plain data (arrays, plain objects), `Object.is` for the rest. */\nexport function sameData(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;\n if (Array.isArray(a)) {\n return Array.isArray(b) && a.length === b.length && a.every((x, i) => sameData(x, b[i]));\n }\n if (\n Object.getPrototypeOf(a) !== Object.prototype ||\n Object.getPrototypeOf(b) !== Object.prototype\n ) {\n return false;\n }\n const ka = Object.keys(a);\n return (\n ka.length === Object.keys(b).length && ka.every((k) => sameData((a as Bag)[k], (b as Bag)[k]))\n );\n}\n\n/** The declared props as components see them: `default` fills missing values. */\nexport function readProps(values: Bag, table: PropTable): Bag {\n const out: Record<string, unknown> = {};\n for (const [name, def] of Object.entries(table)) {\n const value = values[name];\n out[name] = value === undefined ? def.default : value;\n }\n return out;\n}\n\n/** Required props that are still missing (ADR 0007 addendum). */\nexport function missingRequired(values: Bag, table: PropTable): string[] {\n return Object.keys(table).filter(\n (name) => table[name]?.required === true && values[name] === undefined,\n );\n}\n\n/** Warns about required props that are still missing, at first render. */\nexport function warnMissing(tag: string, values: Bag, table: PropTable): void {\n const missing = missingRequired(values, table);\n if (missing.length > 0) {\n console.warn(`<${tag}> is missing required prop(s): ${missing.join(', ')}.`);\n }\n}\n\n/**\n * Declared props that collide with a built-in element property (`hidden`, `title`, `id`, …):\n * the accessor replaces the built-in, so setting the prop silently changes what the platform\n * does with it (gyral-czi.33).\n */\nexport function shadowedBuiltins(names: readonly string[]): string[] {\n if (typeof HTMLElement === 'undefined') return [];\n return names.filter((name) => name in HTMLElement.prototype);\n}\n\n/** define(): a prop named like a built-in property is an error in development, else a warning. */\nexport function checkShadowed(tag: string, names: readonly string[]): void {\n const shadowed = shadowedBuiltins(names);\n if (shadowed.length === 0) return;\n const message =\n `<${tag}> declares prop(s) that shadow built-in element properties: ` +\n `${shadowed.join(', ')}. Setting them changes platform behaviour ` +\n `(a prop named \"hidden\" hides the element). Rename them (view/05-element.md).`;\n if (DEV) throw new TypeError(message);\n console.warn(message);\n}\n\n/** What the element uses, registered by the prop builders (features.ts). */\nexport const propFeature = {\n attributeOf,\n attributeValue,\n propertyValue,\n warnMissing,\n readProps,\n checkShadowed,\n};\n\nexport type PropFeature = typeof propFeature;\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** One host as the scheduler sees it. define() creates one per element. */
|
|
2
|
+
export interface HostTask {
|
|
3
|
+
/** The component's tag, for errors and the loop guard. */
|
|
4
|
+
readonly tag: string;
|
|
5
|
+
/** Gyral host ancestors in the composed tree, recorded when the host connects. */
|
|
6
|
+
depth: number;
|
|
7
|
+
/** `PropsChanged` (when props changed), then the view and its commit. May throw. */
|
|
8
|
+
readonly render: () => void;
|
|
9
|
+
}
|
|
10
|
+
/** Post-render phases, run in this order once per pass (04 "Post-render queue"). */
|
|
11
|
+
export declare const POST_FOCUS = 0;
|
|
12
|
+
export declare const POST_STATES = 1;
|
|
13
|
+
export declare const POST_HYDRATED = 2;
|
|
14
|
+
export declare const POST_INIT = 3;
|
|
15
|
+
/**
|
|
16
|
+
* True when no host is dirty (in either lane), no flush or transition update is pending and
|
|
17
|
+
* nothing is deferred.
|
|
18
|
+
*/
|
|
19
|
+
export declare const isQuiet: () => boolean;
|
|
20
|
+
/** Resolves at the end of the flush that leaves the scheduler quiet. */
|
|
21
|
+
export declare function whenQuiet(): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Marks `task` for the next flush: the microtask one, or with `onFrame` the next animation
|
|
24
|
+
* frame's (04 "Frame lane"). Marking an already-dirty host does nothing; a microtask mark moves a
|
|
25
|
+
* host waiting for the frame into the microtask flush. `requestAnimationFrame` doesn't run in
|
|
26
|
+
* hidden pages, so a timer runs the frame's flush if no frame came first.
|
|
27
|
+
*/
|
|
28
|
+
export declare function markDirty(task: HostTask, onFrame?: boolean): void;
|
|
29
|
+
/** Queues post-render work for the current (or next) flush. */
|
|
30
|
+
export declare function afterRender(phaseOf: number, run: () => void): void;
|
|
31
|
+
/** A message asked for a view transition: the pending flush runs inside one (04). */
|
|
32
|
+
export declare function requestTransition(): void;
|
|
33
|
+
/** Runs `fn` in a microtask that settled() waits for (outputs sent up to a parent). */
|
|
34
|
+
export declare function defer(fn: () => void): void;
|
|
35
|
+
/**
|
|
36
|
+
* Counts `work` (code loading with import(), whose continuation connects hosts) as pending
|
|
37
|
+
* until it settles, so settled() waits for it. `work` must not reject.
|
|
38
|
+
*/
|
|
39
|
+
export declare function hold(work: Promise<void>): void;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// The global scheduler (docs/design-docs/view/04-scheduler.md). Every host's render goes
|
|
2
|
+
// through it: reducers run at once, the render waits for one microtask flush shared by the
|
|
3
|
+
// whole page. A flush renders dirty hosts parents first (smallest depth, then marking order),
|
|
4
|
+
// then runs the post-render queue (focus, custom states, `Hydrated`, deferred init commands),
|
|
5
|
+
// and loops until nothing is dirty. Messages a spec lists in `renderOnFrame` mark their host in
|
|
6
|
+
// the frame lane instead: it renders in the next animation frame (04 "Frame lane"). It lives
|
|
7
|
+
// outside view/ (view/ never imports it) and drives the renderer through view/index.ts.
|
|
8
|
+
import { canTransition, startTransition } from './transitions.js';
|
|
9
|
+
import { DEV, renderBatch } from './view/index.js';
|
|
10
|
+
/** Post-render phases, run in this order once per pass (04 "Post-render queue"). */
|
|
11
|
+
export const POST_FOCUS = 0;
|
|
12
|
+
export const POST_STATES = 1;
|
|
13
|
+
export const POST_HYDRATED = 2;
|
|
14
|
+
export const POST_INIT = 3;
|
|
15
|
+
/** Loop guard (04 "Loop guard"): renders of one host, and passes, in one flush. */
|
|
16
|
+
const MAX_RENDERS = 10;
|
|
17
|
+
const MAX_PASSES = 100;
|
|
18
|
+
const IDLE = 0;
|
|
19
|
+
const SCHEDULED = 1;
|
|
20
|
+
const TRANSITION = 2;
|
|
21
|
+
const FLUSHING = 3;
|
|
22
|
+
/** A frame-lane flush that no animation frame has run by then runs from a timer (hidden pages). */
|
|
23
|
+
const FRAME_FALLBACK_MS = 100;
|
|
24
|
+
const ignore = () => undefined;
|
|
25
|
+
const dirty = new Set();
|
|
26
|
+
/** Hosts marked only in the frame lane: they join `dirty` when the frame comes. */
|
|
27
|
+
const framed = new Set();
|
|
28
|
+
let frameRaf = 0;
|
|
29
|
+
/** Set while a frame-lane flush is requested. */
|
|
30
|
+
let frameTimer;
|
|
31
|
+
let post = [];
|
|
32
|
+
let phase = IDLE;
|
|
33
|
+
let transitionWanted = false;
|
|
34
|
+
/** Deferred callbacks (outputs to parents) and held loads still pending; settled() waits. */
|
|
35
|
+
let deferred = 0;
|
|
36
|
+
let quiet = null;
|
|
37
|
+
/**
|
|
38
|
+
* True when no host is dirty (in either lane), no flush or transition update is pending and
|
|
39
|
+
* nothing is deferred.
|
|
40
|
+
*/
|
|
41
|
+
export const isQuiet = () => phase === IDLE && dirty.size === 0 && framed.size === 0 && deferred === 0;
|
|
42
|
+
/** Resolves at the end of the flush that leaves the scheduler quiet. */
|
|
43
|
+
export function whenQuiet() {
|
|
44
|
+
if (isQuiet())
|
|
45
|
+
return Promise.resolve();
|
|
46
|
+
if (quiet === null) {
|
|
47
|
+
let resolve = ignore;
|
|
48
|
+
let reject = ignore;
|
|
49
|
+
const promise = new Promise((res, rej) => {
|
|
50
|
+
resolve = res;
|
|
51
|
+
reject = rej;
|
|
52
|
+
});
|
|
53
|
+
quiet = { promise, resolve, reject };
|
|
54
|
+
}
|
|
55
|
+
return quiet.promise;
|
|
56
|
+
}
|
|
57
|
+
function schedule() {
|
|
58
|
+
if (phase !== IDLE)
|
|
59
|
+
return;
|
|
60
|
+
phase = SCHEDULED;
|
|
61
|
+
queueMicrotask(start);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Marks `task` for the next flush: the microtask one, or with `onFrame` the next animation
|
|
65
|
+
* frame's (04 "Frame lane"). Marking an already-dirty host does nothing; a microtask mark moves a
|
|
66
|
+
* host waiting for the frame into the microtask flush. `requestAnimationFrame` doesn't run in
|
|
67
|
+
* hidden pages, so a timer runs the frame's flush if no frame came first.
|
|
68
|
+
*/
|
|
69
|
+
export function markDirty(task, onFrame = false) {
|
|
70
|
+
if (dirty.has(task))
|
|
71
|
+
return;
|
|
72
|
+
if (onFrame) {
|
|
73
|
+
framed.add(task);
|
|
74
|
+
if (frameTimer === undefined) {
|
|
75
|
+
frameRaf = requestAnimationFrame(runFrame);
|
|
76
|
+
frameTimer = setTimeout(runFrame, FRAME_FALLBACK_MS);
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
framed.delete(task);
|
|
81
|
+
dirty.add(task);
|
|
82
|
+
schedule();
|
|
83
|
+
}
|
|
84
|
+
/** The frame lane's hosts join a microtask flush, which runs right after this callback. */
|
|
85
|
+
function runFrame() {
|
|
86
|
+
cancelAnimationFrame(frameRaf);
|
|
87
|
+
clearTimeout(frameTimer);
|
|
88
|
+
frameTimer = undefined;
|
|
89
|
+
for (const task of framed)
|
|
90
|
+
markDirty(task);
|
|
91
|
+
}
|
|
92
|
+
/** Queues post-render work for the current (or next) flush. */
|
|
93
|
+
export function afterRender(phaseOf, run) {
|
|
94
|
+
post.push({ phase: phaseOf, run });
|
|
95
|
+
schedule();
|
|
96
|
+
}
|
|
97
|
+
/** A message asked for a view transition: the pending flush runs inside one (04). */
|
|
98
|
+
export function requestTransition() {
|
|
99
|
+
if (phase === IDLE || phase === SCHEDULED)
|
|
100
|
+
transitionWanted = true;
|
|
101
|
+
}
|
|
102
|
+
/** Runs `fn` in a microtask that settled() waits for (outputs sent up to a parent). */
|
|
103
|
+
export function defer(fn) {
|
|
104
|
+
deferred += 1;
|
|
105
|
+
queueMicrotask(() => {
|
|
106
|
+
try {
|
|
107
|
+
fn();
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
console.error('gyral: a deferred callback failed', error);
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
deferred -= 1;
|
|
114
|
+
settle();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Counts `work` (code loading with import(), whose continuation connects hosts) as pending
|
|
120
|
+
* until it settles, so settled() waits for it. `work` must not reject.
|
|
121
|
+
*/
|
|
122
|
+
export function hold(work) {
|
|
123
|
+
deferred += 1;
|
|
124
|
+
void work.finally(() => {
|
|
125
|
+
deferred -= 1;
|
|
126
|
+
settle();
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function start() {
|
|
130
|
+
if (transitionWanted && canTransition()) {
|
|
131
|
+
phase = TRANSITION;
|
|
132
|
+
startTransition(flush);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
flush();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** The dirty host with the smallest depth; ties go to the one marked first. */
|
|
139
|
+
function next() {
|
|
140
|
+
let best;
|
|
141
|
+
for (const task of dirty)
|
|
142
|
+
if (best === undefined || task.depth < best.depth)
|
|
143
|
+
best = task;
|
|
144
|
+
return best;
|
|
145
|
+
}
|
|
146
|
+
function renderAll(counts) {
|
|
147
|
+
while (dirty.size > 0) {
|
|
148
|
+
const task = next();
|
|
149
|
+
dirty.delete(task);
|
|
150
|
+
const renders = (counts.get(task) ?? 0) + 1;
|
|
151
|
+
counts.set(task, renders);
|
|
152
|
+
if (renders > MAX_RENDERS)
|
|
153
|
+
throw loopError(counts);
|
|
154
|
+
try {
|
|
155
|
+
task.render();
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
console.error(`<${task.tag}> failed to render; its previous DOM stays.`, error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function runPost() {
|
|
163
|
+
const tasks = post.sort((a, b) => a.phase - b.phase); // stable: queue order within a phase
|
|
164
|
+
post = [];
|
|
165
|
+
for (const task of tasks) {
|
|
166
|
+
try {
|
|
167
|
+
task.run();
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
console.error('gyral: post-render work failed', error);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function loopError(counts) {
|
|
175
|
+
const tags = [...new Set([...counts.keys()].map((t) => `<${t.tag}>`))].join(', ');
|
|
176
|
+
return new Error(`gyral: rendering did not settle in one flush (a cycle between ${tags}).` +
|
|
177
|
+
(DEV
|
|
178
|
+
? ' Components are probably feeding each other props or messages; break the cycle ' +
|
|
179
|
+
'with a condition in update (docs/design-docs/view/04-scheduler.md "Loop guard").'
|
|
180
|
+
: ''));
|
|
181
|
+
}
|
|
182
|
+
function flush() {
|
|
183
|
+
phase = FLUSHING;
|
|
184
|
+
const counts = new Map();
|
|
185
|
+
let failure;
|
|
186
|
+
try {
|
|
187
|
+
renderBatch(() => {
|
|
188
|
+
for (let pass = 1;; pass++) {
|
|
189
|
+
renderAll(counts);
|
|
190
|
+
if (post.length === 0)
|
|
191
|
+
return;
|
|
192
|
+
runPost();
|
|
193
|
+
if (dirty.size === 0 && post.length === 0)
|
|
194
|
+
return;
|
|
195
|
+
if (pass >= MAX_PASSES)
|
|
196
|
+
throw loopError(counts);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
dirty.clear();
|
|
202
|
+
post = [];
|
|
203
|
+
failure = error instanceof Error ? error : new Error(String(error));
|
|
204
|
+
}
|
|
205
|
+
phase = IDLE;
|
|
206
|
+
transitionWanted = false;
|
|
207
|
+
if (failure === undefined) {
|
|
208
|
+
settle();
|
|
209
|
+
}
|
|
210
|
+
else if (!DEV) {
|
|
211
|
+
console.error(failure); // production: log and drop the rest, so the page doesn't freeze
|
|
212
|
+
settle();
|
|
213
|
+
}
|
|
214
|
+
else if (quiet !== null) {
|
|
215
|
+
const waiting = quiet;
|
|
216
|
+
quiet = null;
|
|
217
|
+
waiting.reject(failure);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
throw failure;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function settle() {
|
|
224
|
+
if (quiet === null || !isQuiet())
|
|
225
|
+
return;
|
|
226
|
+
const waiting = quiet;
|
|
227
|
+
quiet = null;
|
|
228
|
+
waiting.resolve();
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"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","sourcesContent":["// The global scheduler (docs/design-docs/view/04-scheduler.md). Every host's render goes\n// through it: reducers run at once, the render waits for one microtask flush shared by the\n// whole page. A flush renders dirty hosts parents first (smallest depth, then marking order),\n// then runs the post-render queue (focus, custom states, `Hydrated`, deferred init commands),\n// and loops until nothing is dirty. Messages a spec lists in `renderOnFrame` mark their host in\n// the frame lane instead: it renders in the next animation frame (04 \"Frame lane\"). It lives\n// outside view/ (view/ never imports it) and drives the renderer through view/index.ts.\nimport { canTransition, startTransition } from './transitions.js';\nimport { DEV, renderBatch } from './view/index.js';\n\n/** One host as the scheduler sees it. define() creates one per element. */\nexport interface HostTask {\n /** The component's tag, for errors and the loop guard. */\n readonly tag: string;\n /** Gyral host ancestors in the composed tree, recorded when the host connects. */\n depth: number;\n /** `PropsChanged` (when props changed), then the view and its commit. May throw. */\n readonly render: () => void;\n}\n\n/** Post-render phases, run in this order once per pass (04 \"Post-render queue\"). */\nexport const POST_FOCUS = 0;\nexport const POST_STATES = 1;\nexport const POST_HYDRATED = 2;\nexport const POST_INIT = 3;\n\ninterface PostTask {\n readonly phase: number;\n readonly run: () => void;\n}\n\n/** Loop guard (04 \"Loop guard\"): renders of one host, and passes, in one flush. */\nconst MAX_RENDERS = 10;\nconst MAX_PASSES = 100;\n\nconst IDLE = 0;\nconst SCHEDULED = 1;\nconst TRANSITION = 2;\nconst FLUSHING = 3;\n\n/** A frame-lane flush that no animation frame has run by then runs from a timer (hidden pages). */\nconst FRAME_FALLBACK_MS = 100;\n\nconst ignore = (): void => undefined;\nconst dirty = new Set<HostTask>();\n/** Hosts marked only in the frame lane: they join `dirty` when the frame comes. */\nconst framed = new Set<HostTask>();\nlet frameRaf = 0;\n/** Set while a frame-lane flush is requested. */\nlet frameTimer: ReturnType<typeof setTimeout> | undefined;\nlet post: PostTask[] = [];\nlet phase = IDLE;\nlet transitionWanted = false;\n/** Deferred callbacks (outputs to parents) and held loads still pending; settled() waits. */\nlet deferred = 0;\nlet quiet: { promise: Promise<void>; resolve: () => void; reject: (e: unknown) => void } | null =\n null;\n\n/**\n * True when no host is dirty (in either lane), no flush or transition update is pending and\n * nothing is deferred.\n */\nexport const isQuiet = (): boolean =>\n phase === IDLE && dirty.size === 0 && framed.size === 0 && deferred === 0;\n\n/** Resolves at the end of the flush that leaves the scheduler quiet. */\nexport function whenQuiet(): Promise<void> {\n if (isQuiet()) return Promise.resolve();\n if (quiet === null) {\n let resolve: () => void = ignore;\n let reject: (error: unknown) => void = ignore;\n const promise = new Promise<void>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n quiet = { promise, resolve, reject };\n }\n return quiet.promise;\n}\n\nfunction schedule(): void {\n if (phase !== IDLE) return;\n phase = SCHEDULED;\n queueMicrotask(start);\n}\n\n/**\n * Marks `task` for the next flush: the microtask one, or with `onFrame` the next animation\n * frame's (04 \"Frame lane\"). Marking an already-dirty host does nothing; a microtask mark moves a\n * host waiting for the frame into the microtask flush. `requestAnimationFrame` doesn't run in\n * hidden pages, so a timer runs the frame's flush if no frame came first.\n */\nexport function markDirty(task: HostTask, onFrame = false): void {\n if (dirty.has(task)) return;\n if (onFrame) {\n framed.add(task);\n if (frameTimer === undefined) {\n frameRaf = requestAnimationFrame(runFrame);\n frameTimer = setTimeout(runFrame, FRAME_FALLBACK_MS);\n }\n return;\n }\n framed.delete(task);\n dirty.add(task);\n schedule();\n}\n\n/** The frame lane's hosts join a microtask flush, which runs right after this callback. */\nfunction runFrame(): void {\n cancelAnimationFrame(frameRaf);\n clearTimeout(frameTimer);\n frameTimer = undefined;\n for (const task of framed) markDirty(task);\n}\n\n/** Queues post-render work for the current (or next) flush. */\nexport function afterRender(phaseOf: number, run: () => void): void {\n post.push({ phase: phaseOf, run });\n schedule();\n}\n\n/** A message asked for a view transition: the pending flush runs inside one (04). */\nexport function requestTransition(): void {\n if (phase === IDLE || phase === SCHEDULED) transitionWanted = true;\n}\n\n/** Runs `fn` in a microtask that settled() waits for (outputs sent up to a parent). */\nexport function defer(fn: () => void): void {\n deferred += 1;\n queueMicrotask(() => {\n try {\n fn();\n } catch (error) {\n console.error('gyral: a deferred callback failed', error);\n } finally {\n deferred -= 1;\n settle();\n }\n });\n}\n\n/**\n * Counts `work` (code loading with import(), whose continuation connects hosts) as pending\n * until it settles, so settled() waits for it. `work` must not reject.\n */\nexport function hold(work: Promise<void>): void {\n deferred += 1;\n void work.finally(() => {\n deferred -= 1;\n settle();\n });\n}\n\nfunction start(): void {\n if (transitionWanted && canTransition()) {\n phase = TRANSITION;\n startTransition(flush);\n } else {\n flush();\n }\n}\n\n/** The dirty host with the smallest depth; ties go to the one marked first. */\nfunction next(): HostTask {\n let best: HostTask | undefined;\n for (const task of dirty) if (best === undefined || task.depth < best.depth) best = task;\n return best as HostTask;\n}\n\nfunction renderAll(counts: Map<HostTask, number>): void {\n while (dirty.size > 0) {\n const task = next();\n dirty.delete(task);\n const renders = (counts.get(task) ?? 0) + 1;\n counts.set(task, renders);\n if (renders > MAX_RENDERS) throw loopError(counts);\n try {\n task.render();\n } catch (error) {\n console.error(`<${task.tag}> failed to render; its previous DOM stays.`, error);\n }\n }\n}\n\nfunction runPost(): void {\n const tasks = post.sort((a, b) => a.phase - b.phase); // stable: queue order within a phase\n post = [];\n for (const task of tasks) {\n try {\n task.run();\n } catch (error) {\n console.error('gyral: post-render work failed', error);\n }\n }\n}\n\nfunction loopError(counts: Map<HostTask, number>): Error {\n const tags = [...new Set([...counts.keys()].map((t) => `<${t.tag}>`))].join(', ');\n return new Error(\n `gyral: rendering did not settle in one flush (a cycle between ${tags}).` +\n (DEV\n ? ' Components are probably feeding each other props or messages; break the cycle ' +\n 'with a condition in update (docs/design-docs/view/04-scheduler.md \"Loop guard\").'\n : ''),\n );\n}\n\nfunction flush(): void {\n phase = FLUSHING;\n const counts = new Map<HostTask, number>();\n let failure: Error | undefined;\n try {\n renderBatch(() => {\n for (let pass = 1; ; pass++) {\n renderAll(counts);\n if (post.length === 0) return;\n runPost();\n if (dirty.size === 0 && post.length === 0) return;\n if (pass >= MAX_PASSES) throw loopError(counts);\n }\n });\n } catch (error) {\n dirty.clear();\n post = [];\n failure = error instanceof Error ? error : new Error(String(error));\n }\n phase = IDLE;\n transitionWanted = false;\n if (failure === undefined) {\n settle();\n } else if (!DEV) {\n console.error(failure); // production: log and drop the rest, so the page doesn't freeze\n settle();\n } else if (quiet !== null) {\n const waiting = quiet;\n quiet = null;\n waiting.reject(failure);\n } else {\n throw failure;\n }\n}\n\nfunction settle(): void {\n if (quiet === null || !isQuiet()) return;\n const waiting = quiet;\n quiet = null;\n waiting.resolve();\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ComponentSpec, Tagged } from './types.js';
|
|
2
|
+
import { type ServerComponent } from './view/index.js';
|
|
3
|
+
/** The server registry entry for `spec`. */
|
|
4
|
+
export declare function serverComponent<S, M extends Tagged, P>(tag: string, spec: ComponentSpec<S, M, P>): ServerComponent;
|
|
5
|
+
/**
|
|
6
|
+
* Registers every spec define() recorded outside the browser (and not registered yet) in
|
|
7
|
+
* view/'s server registry. `@gyral/core/server` calls it before each render; client code never
|
|
8
|
+
* imports this module.
|
|
9
|
+
*/
|
|
10
|
+
export declare function registerRecordedSpecs(): void;
|