@gyral/core 0.3.0 → 0.3.2
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 +57 -0
- package/NOTICE +19 -0
- package/README.md +36 -8
- package/dist/call.d.ts +32 -0
- package/dist/call.js +63 -0
- package/dist/call.js.map +1 -0
- package/dist/children.d.ts +25 -3
- package/dist/children.js +18 -1
- package/dist/children.js.map +1 -1
- package/dist/cli/gyral-types.d.ts +2 -0
- package/dist/cli/gyral-types.js +23 -0
- package/dist/cli/gyral-types.js.map +1 -0
- package/dist/command.d.ts +54 -12
- package/dist/command.js +25 -11
- package/dist/command.js.map +1 -1
- package/dist/compiler/ast.d.ts +2 -0
- package/dist/compiler/ast.js +5 -1
- package/dist/compiler/ast.js.map +1 -1
- package/dist/compiler/compile.d.ts +5 -2
- package/dist/compiler/compile.js +36 -17
- package/dist/compiler/compile.js.map +1 -1
- package/dist/compiler/component-entries.d.ts +7 -0
- package/dist/compiler/component-entries.js +42 -0
- package/dist/compiler/component-entries.js.map +1 -0
- package/dist/compiler/components.d.ts +4 -0
- package/dist/compiler/components.js +190 -0
- package/dist/compiler/components.js.map +1 -0
- package/dist/compiler/facts.d.ts +31 -0
- package/dist/compiler/facts.js +285 -0
- package/dist/compiler/facts.js.map +1 -0
- package/dist/compiler/features.d.ts +34 -0
- package/dist/compiler/features.js +158 -0
- package/dist/compiler/features.js.map +1 -0
- package/dist/compiler/hooks.d.ts +4 -0
- package/dist/compiler/hooks.js +144 -89
- package/dist/compiler/hooks.js.map +1 -1
- package/dist/compiler/loader-source.d.ts +14 -0
- package/dist/compiler/loader-source.js +86 -0
- package/dist/compiler/loader-source.js.map +1 -0
- package/dist/compiler/locate.d.ts +14 -0
- package/dist/compiler/locate.js +90 -0
- package/dist/compiler/locate.js.map +1 -0
- package/dist/compiler/parse5-check.d.ts +1 -1
- package/dist/compiler/parse5-check.js +8 -3
- package/dist/compiler/parse5-check.js.map +1 -1
- package/dist/compiler/prop-schemas.d.ts +4 -0
- package/dist/compiler/prop-schemas.js +108 -0
- package/dist/compiler/prop-schemas.js.map +1 -0
- package/dist/compiler/scan.d.ts +13 -3
- package/dist/compiler/scan.js +21 -13
- package/dist/compiler/scan.js.map +1 -1
- package/dist/compiler/scope.d.ts +8 -0
- package/dist/compiler/scope.js +116 -0
- package/dist/compiler/scope.js.map +1 -0
- package/dist/compiler/tag-map.d.ts +41 -0
- package/dist/compiler/tag-map.js +126 -0
- package/dist/compiler/tag-map.js.map +1 -0
- package/dist/components-ready.d.ts +7 -0
- package/dist/components-ready.js +19 -0
- package/dist/components-ready.js.map +1 -0
- package/dist/define.d.ts +12 -4
- package/dist/define.js +8 -12
- package/dist/define.js.map +1 -1
- package/dist/devtools-events.d.ts +8 -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 +3 -0
- package/dist/devtools.js +14 -0
- package/dist/devtools.js.map +1 -1
- package/dist/drivers-scope.js.map +1 -1
- package/dist/element-internals.d.ts +1 -0
- package/dist/element-internals.js +11 -0
- package/dist/element-internals.js.map +1 -0
- package/dist/element-types.d.ts +7 -4
- package/dist/element-types.js.map +1 -1
- package/dist/element.js +80 -27
- package/dist/element.js.map +1 -1
- package/dist/error-view.d.ts +10 -0
- package/dist/error-view.js +30 -0
- package/dist/error-view.js.map +1 -0
- package/dist/errors.d.ts +21 -0
- package/dist/errors.js +47 -0
- package/dist/errors.js.map +1 -0
- package/dist/errors.json +289 -0
- package/dist/eslint/imports.d.ts +2 -0
- package/dist/eslint/imports.js +2 -2
- package/dist/eslint/imports.js.map +1 -1
- package/dist/eslint/index.d.ts +2 -1
- package/dist/eslint/index.js +11 -3
- package/dist/eslint/index.js.map +1 -1
- package/dist/eslint/template-rule.d.ts +1 -0
- package/dist/eslint/template-rule.js +15 -5
- package/dist/eslint/template-rule.js.map +1 -1
- package/dist/eslint/unused-intent-rule.d.ts +2 -0
- package/dist/eslint/unused-intent-rule.js +186 -0
- package/dist/eslint/unused-intent-rule.js.map +1 -0
- package/dist/features.d.ts +2 -2
- package/dist/features.js.map +1 -1
- package/dist/focus.d.ts +6 -0
- package/dist/focus.js +32 -2
- package/dist/focus.js.map +1 -1
- package/dist/form-associated.d.ts +46 -0
- package/dist/form-associated.js +77 -0
- package/dist/form-associated.js.map +1 -0
- package/dist/forms.d.ts +42 -30
- package/dist/forms.js +85 -41
- package/dist/forms.js.map +1 -1
- package/dist/frame-lane.d.ts +11 -0
- package/dist/frame-lane.js +28 -0
- package/dist/frame-lane.js.map +1 -0
- package/dist/head.d.ts +61 -0
- package/dist/head.js +85 -0
- package/dist/head.js.map +1 -0
- package/dist/host-model.d.ts +34 -5
- package/dist/host-model.js +146 -23
- package/dist/host-model.js.map +1 -1
- package/dist/hydration-client.js +2 -2
- package/dist/hydration-client.js.map +1 -1
- package/dist/hydration-loader.d.ts +13 -0
- package/dist/hydration-loader.js +58 -0
- package/dist/hydration-loader.js.map +1 -0
- package/dist/hydration-off.d.ts +8 -0
- package/dist/hydration-off.js +25 -0
- package/dist/hydration-off.js.map +1 -0
- package/dist/hydration.d.ts +0 -12
- package/dist/hydration.js +3 -52
- package/dist/hydration.js.map +1 -1
- package/dist/index.d.ts +21 -24
- package/dist/index.js +10 -19
- package/dist/index.js.map +1 -1
- package/dist/indexeddb-types.d.ts +97 -0
- package/dist/indexeddb-types.js +2 -0
- package/dist/indexeddb-types.js.map +1 -0
- package/dist/indexeddb.d.ts +5 -0
- package/dist/indexeddb.js +118 -0
- package/dist/indexeddb.js.map +1 -0
- package/dist/intent.d.ts +42 -20
- package/dist/intent.js +145 -107
- package/dist/intent.js.map +1 -1
- package/dist/internal/indexeddb-open.d.ts +8 -0
- package/dist/internal/indexeddb-open.js +96 -0
- package/dist/internal/indexeddb-open.js.map +1 -0
- package/dist/internal/indexeddb-transaction.d.ts +4 -0
- package/dist/internal/indexeddb-transaction.js +134 -0
- package/dist/internal/indexeddb-transaction.js.map +1 -0
- package/dist/internal/interpreter.d.ts +12 -4
- package/dist/internal/interpreter.js +36 -48
- package/dist/internal/interpreter.js.map +1 -1
- package/dist/internal/token.d.ts +1 -0
- package/dist/internal/token.js +7 -0
- package/dist/internal/token.js.map +1 -0
- package/dist/internal.d.ts +20 -0
- package/dist/internal.js +22 -0
- package/dist/internal.js.map +1 -0
- package/dist/invoker-fallback-used.d.ts +4 -0
- package/dist/invoker-fallback-used.js +10 -0
- package/dist/invoker-fallback-used.js.map +1 -0
- package/dist/invoker-fallback.d.ts +4 -0
- package/dist/invoker-fallback.js +15 -0
- package/dist/invoker-fallback.js.map +1 -0
- package/dist/light-dom.d.ts +1 -1
- package/dist/light-dom.js.map +1 -1
- package/dist/prop.d.ts +30 -7
- package/dist/prop.js +19 -0
- package/dist/prop.js.map +1 -1
- package/dist/props.d.ts +8 -3
- package/dist/props.js +22 -15
- package/dist/props.js.map +1 -1
- package/dist/retry.d.ts +15 -0
- package/dist/retry.js +48 -0
- package/dist/retry.js.map +1 -0
- package/dist/scheduler.d.ts +7 -3
- package/dist/scheduler.js +38 -56
- package/dist/scheduler.js.map +1 -1
- package/dist/script-json.d.ts +2 -0
- package/dist/script-json.js +13 -0
- package/dist/script-json.js.map +1 -0
- package/dist/server-component.d.ts +1 -1
- package/dist/server-component.js +45 -14
- package/dist/server-component.js.map +1 -1
- package/dist/server.d.ts +9 -3
- package/dist/server.js +12 -3
- package/dist/server.js.map +1 -1
- package/dist/settled.d.ts +4 -3
- package/dist/settled.js +48 -26
- package/dist/settled.js.map +1 -1
- package/dist/spec-features-used.d.ts +21 -0
- package/dist/spec-features-used.js +50 -0
- package/dist/spec-features-used.js.map +1 -0
- package/dist/spec-features.d.ts +28 -0
- package/dist/spec-features.js +42 -0
- package/dist/spec-features.js.map +1 -0
- package/dist/states.d.ts +3 -5
- package/dist/states.js +22 -8
- package/dist/states.js.map +1 -1
- package/dist/storage.d.ts +69 -0
- package/dist/storage.js +158 -0
- package/dist/storage.js.map +1 -0
- package/dist/store-binding.js +2 -2
- package/dist/store-binding.js.map +1 -1
- package/dist/store-scope.d.ts +2 -4
- package/dist/store-scope.js +6 -16
- package/dist/store-scope.js.map +1 -1
- package/dist/store.d.ts +2 -6
- package/dist/store.js +36 -8
- package/dist/store.js.map +1 -1
- package/dist/subscription.d.ts +35 -0
- package/dist/subscription.js +74 -0
- package/dist/subscription.js.map +1 -0
- package/dist/subscriptions.d.ts +26 -0
- package/dist/subscriptions.js +98 -0
- package/dist/subscriptions.js.map +1 -0
- package/dist/transitions.d.ts +4 -5
- package/dist/transitions.js +10 -4
- package/dist/transitions.js.map +1 -1
- package/dist/types.d.ts +141 -32
- package/dist/types.js.map +1 -1
- package/dist/use-dialogs.d.ts +2 -0
- package/dist/use-dialogs.js +9 -0
- package/dist/use-dialogs.js.map +1 -0
- package/dist/use-disposal.d.ts +2 -0
- package/dist/use-disposal.js +9 -0
- package/dist/use-disposal.js.map +1 -0
- package/dist/use-error-view.d.ts +2 -0
- package/dist/use-error-view.js +9 -0
- package/dist/use-error-view.js.map +1 -0
- package/dist/use-errored.d.ts +2 -0
- package/dist/use-errored.js +9 -0
- package/dist/use-errored.js.map +1 -0
- package/dist/use-form-associated.d.ts +2 -0
- package/dist/use-form-associated.js +7 -0
- package/dist/use-form-associated.js.map +1 -0
- package/dist/use-frame-lane.d.ts +2 -0
- package/dist/use-frame-lane.js +10 -0
- package/dist/use-frame-lane.js.map +1 -0
- package/dist/use-invokers.d.ts +2 -0
- package/dist/use-invokers.js +10 -0
- package/dist/use-invokers.js.map +1 -0
- package/dist/use-states.d.ts +2 -0
- package/dist/use-states.js +10 -0
- package/dist/use-states.js.map +1 -0
- package/dist/use-subscriptions.d.ts +2 -0
- package/dist/use-subscriptions.js +10 -0
- package/dist/use-subscriptions.js.map +1 -0
- package/dist/use-transitions.d.ts +2 -0
- package/dist/use-transitions.js +10 -0
- package/dist/use-transitions.js.map +1 -0
- package/dist/view/attributes.d.ts +5 -0
- package/dist/view/attributes.js +5 -0
- package/dist/view/attributes.js.map +1 -1
- package/dist/view/compiled.d.ts +1 -1
- package/dist/view/compiled.js +1 -1
- package/dist/view/compiled.js.map +1 -1
- package/dist/view/index.d.ts +9 -4
- package/dist/view/index.js +9 -3
- package/dist/view/index.js.map +1 -1
- package/dist/view/loc.d.ts +9 -0
- package/dist/view/loc.js +50 -0
- package/dist/view/loc.js.map +1 -0
- package/dist/view/message.d.ts +17 -0
- package/dist/view/message.js +40 -0
- package/dist/view/message.js.map +1 -0
- package/dist/view/messages.d.ts +43 -0
- package/dist/view/messages.js +84 -0
- package/dist/view/messages.js.map +1 -0
- package/dist/view/normalize/check.d.ts +5 -2
- package/dist/view/normalize/check.js +6 -3
- package/dist/view/normalize/check.js.map +1 -1
- package/dist/view/normalize/emit.d.ts +2 -0
- package/dist/view/normalize/emit.js +40 -11
- package/dist/view/normalize/emit.js.map +1 -1
- package/dist/view/normalize/errors.js +1 -1
- package/dist/view/normalize/errors.js.map +1 -1
- package/dist/view/normalize/id.d.ts +5 -2
- package/dist/view/normalize/id.js +9 -2
- package/dist/view/normalize/id.js.map +1 -1
- package/dist/view/normalize/normalize.d.ts +7 -4
- package/dist/view/normalize/normalize.js +15 -9
- package/dist/view/normalize/normalize.js.map +1 -1
- package/dist/view/normalize/rules.d.ts +9 -0
- package/dist/view/normalize/rules.js +35 -5
- package/dist/view/normalize/rules.js.map +1 -1
- package/dist/view/normalize/svg.d.ts +14 -0
- package/dist/view/normalize/svg.js +40 -0
- package/dist/view/normalize/svg.js.map +1 -0
- package/dist/view/normalize/tokens.d.ts +5 -1
- package/dist/view/normalize/tokens.js.map +1 -1
- package/dist/view/normalize/tree.d.ts +8 -2
- package/dist/view/normalize/tree.js +58 -13
- package/dist/view/normalize/tree.js.map +1 -1
- package/dist/view/normalize/types.d.ts +24 -4
- package/dist/view/normalize/types.js +4 -1
- package/dist/view/normalize/types.js.map +1 -1
- package/dist/view/normalize/whitespace.js +4 -3
- package/dist/view/normalize/whitespace.js.map +1 -1
- package/dist/view/prepare-stub.js +2 -2
- package/dist/view/prepare-stub.js.map +1 -1
- package/dist/view/prepare.d.ts +5 -2
- package/dist/view/prepare.js +9 -3
- package/dist/view/prepare.js.map +1 -1
- package/dist/view/registry.d.ts +15 -0
- package/dist/view/registry.js +10 -2
- package/dist/view/registry.js.map +1 -1
- package/dist/view/render/adopt-attr.js +7 -1
- package/dist/view/render/adopt-attr.js.map +1 -1
- package/dist/view/render/adopt.js +21 -4
- package/dist/view/render/adopt.js.map +1 -1
- package/dist/view/render/attr-parts.d.ts +12 -1
- package/dist/view/render/attr-parts.js +38 -5
- package/dist/view/render/attr-parts.js.map +1 -1
- package/dist/view/render/child-part.js +5 -2
- package/dist/view/render/child-part.js.map +1 -1
- package/dist/view/render/dispose.d.ts +3 -0
- package/dist/view/render/dispose.js +76 -0
- package/dist/view/render/dispose.js.map +1 -0
- package/dist/view/render/hook-part.d.ts +3 -1
- package/dist/view/render/hook-part.js +21 -5
- package/dist/view/render/hook-part.js.map +1 -1
- package/dist/view/render/hook-run.d.ts +2 -0
- package/dist/view/render/hook-run.js +72 -0
- package/dist/view/render/hook-run.js.map +1 -0
- package/dist/view/render/hooks.d.ts +74 -6
- package/dist/view/render/hooks.js +79 -17
- package/dist/view/render/hooks.js.map +1 -1
- package/dist/view/render/hydrate.js +2 -2
- package/dist/view/render/hydrate.js.map +1 -1
- package/dist/view/render/keyed.d.ts +6 -0
- package/dist/view/render/keyed.js +15 -0
- package/dist/view/render/keyed.js.map +1 -0
- package/dist/view/render/list.d.ts +1 -1
- package/dist/view/render/list.js.map +1 -1
- package/dist/view/render/mismatch.js +5 -3
- package/dist/view/render/mismatch.js.map +1 -1
- package/dist/view/render/modal.d.ts +3 -0
- package/dist/view/render/modal.js +39 -0
- package/dist/view/render/modal.js.map +1 -0
- package/dist/view/render/raw.d.ts +4 -1
- package/dist/view/render/raw.js +6 -2
- package/dist/view/render/raw.js.map +1 -1
- package/dist/view/render/render.d.ts +4 -1
- package/dist/view/render/render.js +17 -12
- package/dist/view/render/render.js.map +1 -1
- package/dist/view/render/toggles.d.ts +6 -0
- package/dist/view/render/toggles.js +6 -0
- package/dist/view/render/toggles.js.map +1 -0
- package/dist/view/render/values.d.ts +1 -1
- package/dist/view/render/values.js.map +1 -1
- package/dist/view/render/warn.d.ts +14 -0
- package/dist/view/render/warn.js +45 -0
- package/dist/view/render/warn.js.map +1 -1
- package/dist/view/server/component.d.ts +6 -3
- package/dist/view/server/component.js +29 -7
- package/dist/view/server/component.js.map +1 -1
- package/dist/view/server/index.d.ts +20 -0
- package/dist/view/server/index.js +12 -4
- package/dist/view/server/index.js.map +1 -1
- package/dist/view/server/tags.d.ts +13 -2
- package/dist/view/server/tags.js +13 -2
- package/dist/view/server/tags.js.map +1 -1
- package/dist/view/server/values.d.ts +2 -0
- package/dist/view/server/values.js +8 -0
- package/dist/view/server/values.js.map +1 -1
- package/dist/view/server/writer.d.ts +9 -4
- package/dist/view/server/writer.js +33 -18
- package/dist/view/server/writer.js.map +1 -1
- package/dist/view/template-element.d.ts +7 -0
- package/dist/view/template-element.js +25 -5
- package/dist/view/template-element.js.map +1 -1
- package/dist/view/template.d.ts +9 -2
- package/dist/view/template.js +61 -8
- package/dist/view/template.js.map +1 -1
- package/dist/view/trusted-html.d.ts +6 -0
- package/dist/view/trusted-html.js +34 -0
- package/dist/view/trusted-html.js.map +1 -0
- package/dist/vite.d.ts +38 -31
- package/dist/vite.js +206 -31
- package/dist/vite.js.map +1 -1
- package/package.json +34 -3
- package/dist/hooks/labelled-by.d.ts +0 -13
- package/dist/hooks/labelled-by.js +0 -51
- package/dist/hooks/labelled-by.js.map +0 -1
- package/dist/random.d.ts +0 -13
- package/dist/random.js +0 -19
- package/dist/random.js.map +0 -1
package/dist/prop.js
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import { features } from './features.js';
|
|
2
2
|
import { propFeature } from './props.js';
|
|
3
|
+
import { DEV } from '#view-dev';
|
|
4
|
+
/** `prop.json` values are JSON: the same JSON text is the same value. */
|
|
5
|
+
// `prop.json`'s default `equals`: the same JSON text. A value JSON can't encode (a cycle, a
|
|
6
|
+
// BigInt) is never equal, so the write always lands instead of throwing from the setter.
|
|
7
|
+
let warned = false;
|
|
8
|
+
function sameJson(a, b) {
|
|
9
|
+
try {
|
|
10
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (DEV && !warned) {
|
|
14
|
+
warned = true;
|
|
15
|
+
console.warn(`prop.json got a value JSON can't encode (${String(error)}); every write of it counts ` +
|
|
16
|
+
'as a change. Use prop.value for non-JSON values, or pass equals.');
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
3
21
|
function make(kind, opts = {}, schema) {
|
|
4
22
|
features.props = propFeature; // components can declare props now (features.ts)
|
|
5
23
|
const value = opts.default !== undefined ? opts.default : kind === 'boolean' ? false : undefined;
|
|
@@ -8,6 +26,7 @@ function make(kind, opts = {}, schema) {
|
|
|
8
26
|
attribute: kind === 'value' ? false : opts.attribute,
|
|
9
27
|
schema: schema ?? opts.schema,
|
|
10
28
|
required: opts.required === true,
|
|
29
|
+
equals: opts.equals ?? (kind === 'json' ? sameJson : Object.is),
|
|
11
30
|
};
|
|
12
31
|
return value === undefined ? base : { ...base, default: value };
|
|
13
32
|
}
|
package/dist/prop.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prop.js","sourceRoot":"","sources":["../src/prop.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prop.js","sourceRoot":"","sources":["../src/prop.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAgGhC,yEAAyE;AACzE,4FAA4F;AAC5F,yFAAyF;AACzF,IAAI,MAAM,GAAG,KAAK,CAAC;AACnB,SAAS,QAAQ,CAAC,CAAU,EAAE,CAAU;IACtC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,GAAG,IAAI,CAAC;YACd,OAAO,CAAC,IAAI,CACV,4CAA4C,MAAM,CAAC,KAAK,CAAC,8BAA8B;gBACrF,kEAAkE,CACrE,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,IAAI,CAAC,IAAc,EAAE,OAAgB,EAAE,EAAE,MAAc;IAC9D,QAAQ,CAAC,KAAK,GAAG,WAAW,CAAC,CAAC,iDAAiD;IAC/E,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACjG,MAAM,IAAI,GAAG;QACX,IAAI;QACJ,SAAS,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS;QACpD,MAAM,EAAE,MAAM,IAAI,IAAI,CAAC,MAAM;QAC7B,QAAQ,EAAE,IAAI,CAAC,QAAQ,KAAK,IAAI;QAChC,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;KAChE,CAAC;IACF,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAClE,CAAC;AAED,yBAAyB;AACzB,MAAM,CAAC,MAAM,IAAI,GAoBb;IACF,MAAM,EAAE,CAAC,IAAc,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC;IAChD,MAAM,EAAE,CAAC,IAAc,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC;IAChD,OAAO,EAAE,CAAC,CAAC,IAAc,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAqC;IACxF,IAAI,EAAE,CAAC,MAAa,EAAE,IAAc,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC;IACnE,KAAK,EAAE,CAAC,MAAa,EAAE,IAAc,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC;CACtE,CAAC","sourcesContent":["// Prop builders (docs/design-docs/view/05-element.md \"Props\"): every component input is\n// declared with a builder over Standard Schema, so \"parse at boundaries\" covers attributes.\n//\n// props: {\n// label: prop.string({ required: true }), // attribute \"label\"\n// minPrice: prop.number({ default: 0 }), // attribute \"min-price\"\n// items: prop.value(v.array(Item), { default: [] }), // property only\n// }\n//\n// A prop's type is inferred from its schema's output. The honesty rule (ADR 0007) holds by\n// construction: a prop without `required` or `default` includes `undefined`. `prop.value` and\n// `prop.json` also take a plain type guard (`(u: unknown) => u is T`) instead of a schema.\n// Schemas and guards check; they don't decode: a property set keeps the object it was given.\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { features } from './features.js';\nimport { propFeature } from './props.js';\nimport { DEV } from '#view-dev';\n\ndeclare const OUTPUT: unique symbol;\n\nexport type PropKind = 'string' | 'number' | 'boolean' | 'json' | 'value';\n\n/** One declared prop: what a builder returns. */\nexport interface Prop<T> {\n readonly kind: PropKind;\n /** The attribute name, `false` for property only, `undefined` for the kebab-case default. */\n readonly attribute: string | false | undefined;\n /**\n * Refines (string/number/boolean) or defines (json/value) the accepted values: a Standard\n * Schema, or for json/value a type guard (props.ts `checkValue` tells them apart).\n */\n readonly schema: StandardSchemaV1 | PropGuard<unknown> | undefined;\n /** A missing value is a bug: warned once per instance at first render. */\n readonly required: boolean;\n /** Used whenever the element's value is missing (`boolean` props default to `false`). */\n readonly default?: unknown;\n /**\n * When a new value counts as unchanged (no re-render, no `PropsChanged`): `Object.is`, the\n * same JSON for `prop.json`, or the `equals` option of `prop.json`/`prop.value`.\n */\n readonly equals: (a: unknown, b: unknown) => boolean;\n /** Type-only: the value components see. Never set. */\n readonly [OUTPUT]?: () => T;\n}\n\n/** The props type of a table of builders: `PropsOf<typeof props>`. */\nexport type PropsOf<T extends Readonly<Record<string, Prop<unknown>>>> = {\n readonly [K in keyof T]: T[K] extends Prop<infer V> ? V : never;\n};\n\ninterface Common {\n /** Attribute name, or `false` for property only. Default: kebab-case of the prop name. */\n readonly attribute?: string | false;\n}\n\ntype Schema<O> = StandardSchemaV1<unknown, O>;\n\n/** A plain type guard, accepted by `prop.value` and `prop.json` in place of a schema. */\nexport type PropGuard<T> = (value: unknown) => value is T;\n\n/** What `prop.value` and `prop.json` accept: a Standard Schema or a type guard. */\ntype Check = StandardSchemaV1 | PropGuard<unknown>;\ntype Out<S extends Check> = S extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<S>\n : S extends (value: unknown) => value is infer T\n ? T\n : never;\n\ninterface Required {\n readonly required: true;\n readonly default?: undefined;\n}\ninterface Defaulted<T> {\n readonly required?: false;\n readonly default: T;\n}\ninterface Optional {\n readonly required?: false;\n readonly default?: undefined;\n}\n\ntype Refined<O> = Common & { readonly schema?: Schema<O> };\n\ninterface Scalar<B> {\n <O extends B = B>(opts: Refined<O> & (Required | Defaulted<NoInfer<O>>)): Prop<O>;\n <O extends B = B>(opts?: Refined<O> & Optional): Prop<O | undefined>;\n}\n\n/** `equals` for `prop.json`/`prop.value`: the old and new value (`undefined` when unset). */\ninterface Equals<T> {\n readonly equals?: (a: T | undefined, b: T | undefined) => boolean;\n}\n\ninterface WithSchema {\n <S extends Check>(\n schema: S,\n opts: Common & Equals<NoInfer<Out<S>>> & (Required | Defaulted<NoInfer<Out<S>>>),\n ): Prop<Out<S>>;\n <S extends Check>(\n schema: S,\n opts?: Common & Equals<NoInfer<Out<S>>> & Optional,\n ): Prop<Out<S> | undefined>;\n}\n\ninterface Options {\n readonly attribute?: string | false;\n readonly schema?: StandardSchemaV1;\n readonly required?: boolean;\n readonly default?: unknown;\n readonly equals?: (a: unknown, b: unknown) => boolean;\n}\n\n/** `prop.json` values are JSON: the same JSON text is the same value. */\n// `prop.json`'s default `equals`: the same JSON text. A value JSON can't encode (a cycle, a\n// BigInt) is never equal, so the write always lands instead of throwing from the setter.\nlet warned = false;\nfunction sameJson(a: unknown, b: unknown): boolean {\n try {\n return JSON.stringify(a) === JSON.stringify(b);\n } catch (error) {\n if (DEV && !warned) {\n warned = true;\n console.warn(\n `prop.json got a value JSON can't encode (${String(error)}); every write of it counts ` +\n 'as a change. Use prop.value for non-JSON values, or pass equals.',\n );\n }\n return false;\n }\n}\n\nfunction make(kind: PropKind, opts: Options = {}, schema?: Check): Prop<unknown> {\n features.props = propFeature; // components can declare props now (features.ts)\n const value = opts.default !== undefined ? opts.default : kind === 'boolean' ? false : undefined;\n const base = {\n kind,\n attribute: kind === 'value' ? false : opts.attribute,\n schema: schema ?? opts.schema,\n required: opts.required === true,\n equals: opts.equals ?? (kind === 'json' ? sameJson : Object.is),\n };\n return value === undefined ? base : { ...base, default: value };\n}\n\n/** The prop builders. */\nexport const prop: {\n /** Attribute parsed as is. */\n readonly string: Scalar<string>;\n /** Attribute parsed with `Number(v)`; empty or `NaN` is invalid. */\n readonly number: Scalar<number>;\n /** Attribute present → `true`, absent → `false` (the default). */\n readonly boolean: <O extends boolean = boolean>(\n opts?: Refined<O> & { readonly default?: NoInfer<O> },\n ) => Prop<O>;\n /**\n * Attribute parsed with `JSON.parse`, then the schema (or type guard). Compared by value: a\n * new but equal object (a fresh `.prop=${{…}}` each render) changes nothing.\n */\n readonly json: WithSchema;\n /**\n * Property only: no attribute. Validated in development by the schema (or type guard), and\n * the object set is kept as is (production skips the check). Compared with `Object.is`\n * unless `equals` is given.\n */\n readonly value: WithSchema;\n} = {\n string: (opts?: Options) => make('string', opts),\n number: (opts?: Options) => make('number', opts),\n boolean: ((opts?: Options) => make('boolean', opts)) as <O extends boolean>() => Prop<O>,\n json: (schema: Check, opts?: Options) => make('json', opts, schema),\n value: (schema: Check, opts?: Options) => make('value', opts, schema),\n};\n"]}
|
package/dist/props.d.ts
CHANGED
|
@@ -25,7 +25,8 @@ export declare function reportInvalid(tag: string, name: string, from: string, i
|
|
|
25
25
|
export declare function attributeValue(tag: string, name: string, def: Prop<unknown> | undefined, attr: string, raw: string | null): unknown;
|
|
26
26
|
/**
|
|
27
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
|
|
28
|
+
* invalid value is missing and a schema that transforms warns. Production keeps the input
|
|
29
|
+
* without calling this (propFeature, element.ts: gyral-c5d.14).
|
|
29
30
|
*/
|
|
30
31
|
export declare function propertyValue(tag: string, name: string, def: Prop<unknown> | undefined, value: unknown, from?: string): unknown;
|
|
31
32
|
/** Structural equality for plain data (arrays, plain objects), `Object.is` for the rest. */
|
|
@@ -44,11 +45,15 @@ export declare function warnMissing(tag: string, values: Bag, table: PropTable):
|
|
|
44
45
|
export declare function shadowedBuiltins(names: readonly string[]): string[];
|
|
45
46
|
/** define(): a prop named like a built-in property is an error in development, else a warning. */
|
|
46
47
|
export declare function checkShadowed(tag: string, names: readonly string[]): void;
|
|
47
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* What the element uses, registered by the prop builders (features.ts). Property sets and seeds
|
|
50
|
+
* go through `propertyValue` in development only: production keeps the value as given, so the
|
|
51
|
+
* property path's schema and guard handling stay out of production bundles (gyral-c5d.14).
|
|
52
|
+
*/
|
|
48
53
|
export declare const propFeature: {
|
|
49
54
|
attributeOf: typeof attributeOf;
|
|
50
55
|
attributeValue: typeof attributeValue;
|
|
51
|
-
propertyValue: typeof propertyValue;
|
|
56
|
+
propertyValue: typeof propertyValue | undefined;
|
|
52
57
|
warnMissing: typeof warnMissing;
|
|
53
58
|
readProps: typeof readProps;
|
|
54
59
|
checkShadowed: typeof checkShadowed;
|
package/dist/props.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEV } from './view/index.js';
|
|
1
|
+
import { DEV, message } from './view/index.js';
|
|
2
2
|
/** `minPrice` → `min-price`. */
|
|
3
3
|
export const kebab = (name) => name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
4
4
|
/** The prop's attribute, or undefined for a property-only prop. */
|
|
@@ -21,11 +21,16 @@ export function checkValue(tag, name, def, value) {
|
|
|
21
21
|
issues: [`expected a ${def.kind}, got ${typeof value}`],
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
const schema = def.schema;
|
|
25
|
+
if (schema === undefined)
|
|
25
26
|
return { ok: true, value };
|
|
26
|
-
|
|
27
|
+
// A type guard (prop.value/prop.json, gyral-c5d.7); ArkType-style callable schemas have ~standard.
|
|
28
|
+
if (!('~standard' in schema)) {
|
|
29
|
+
return schema(value) ? { ok: true, value } : { ok: false, issues: [`failed ${schema.name}`] };
|
|
30
|
+
}
|
|
31
|
+
const result = schema['~standard'].validate(value);
|
|
27
32
|
if (result instanceof Promise) {
|
|
28
|
-
throw new TypeError(
|
|
33
|
+
throw new TypeError(message(20, tag, name));
|
|
29
34
|
}
|
|
30
35
|
if (result.issues !== undefined)
|
|
31
36
|
return { ok: false, issues: result.issues.map((i) => i.message) };
|
|
@@ -55,8 +60,7 @@ export function parseAttribute(def, raw) {
|
|
|
55
60
|
}
|
|
56
61
|
/** Logs an invalid value; the caller then treats the prop as missing. */
|
|
57
62
|
export function reportInvalid(tag, name, from, issues) {
|
|
58
|
-
console.error(
|
|
59
|
-
issues.join('; '));
|
|
63
|
+
console.error(message(21, tag, name, from, issues.join('; ')));
|
|
60
64
|
}
|
|
61
65
|
/**
|
|
62
66
|
* An attribute's value for prop `name` (view/05-element.md "When props are validated"):
|
|
@@ -74,7 +78,8 @@ export function attributeValue(tag, name, def, attr, raw) {
|
|
|
74
78
|
}
|
|
75
79
|
/**
|
|
76
80
|
* 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
|
|
81
|
+
* invalid value is missing and a schema that transforms warns. Production keeps the input
|
|
82
|
+
* without calling this (propFeature, element.ts: gyral-c5d.14).
|
|
78
83
|
*/
|
|
79
84
|
export function propertyValue(tag, name, def, value, from = 'a property') {
|
|
80
85
|
if (!DEV || value === undefined || def === undefined)
|
|
@@ -123,7 +128,7 @@ export function missingRequired(values, table) {
|
|
|
123
128
|
export function warnMissing(tag, values, table) {
|
|
124
129
|
const missing = missingRequired(values, table);
|
|
125
130
|
if (missing.length > 0) {
|
|
126
|
-
console.warn(
|
|
131
|
+
console.warn(message(22, tag, missing.join(', ')));
|
|
127
132
|
}
|
|
128
133
|
}
|
|
129
134
|
/**
|
|
@@ -141,18 +146,20 @@ export function checkShadowed(tag, names) {
|
|
|
141
146
|
const shadowed = shadowedBuiltins(names);
|
|
142
147
|
if (shadowed.length === 0)
|
|
143
148
|
return;
|
|
144
|
-
const
|
|
145
|
-
`${shadowed.join(', ')}. Setting them changes platform behaviour ` +
|
|
146
|
-
`(a prop named "hidden" hides the element). Rename them (view/05-element.md).`;
|
|
149
|
+
const text = message(23, tag, shadowed.join(', '));
|
|
147
150
|
if (DEV)
|
|
148
|
-
throw new TypeError(
|
|
149
|
-
console.warn(
|
|
151
|
+
throw new TypeError(text);
|
|
152
|
+
console.warn(text);
|
|
150
153
|
}
|
|
151
|
-
/**
|
|
154
|
+
/**
|
|
155
|
+
* What the element uses, registered by the prop builders (features.ts). Property sets and seeds
|
|
156
|
+
* go through `propertyValue` in development only: production keeps the value as given, so the
|
|
157
|
+
* property path's schema and guard handling stay out of production bundles (gyral-c5d.14).
|
|
158
|
+
*/
|
|
152
159
|
export const propFeature = {
|
|
153
160
|
attributeOf,
|
|
154
161
|
attributeValue,
|
|
155
|
-
propertyValue,
|
|
162
|
+
propertyValue: DEV ? propertyValue : undefined,
|
|
156
163
|
warnMissing,
|
|
157
164
|
readProps,
|
|
158
165
|
checkShadowed,
|
package/dist/props.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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"]}
|
|
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, message } 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 const schema = def.schema;\n if (schema === undefined) return { ok: true, value };\n // A type guard (prop.value/prop.json, gyral-c5d.7); ArkType-style callable schemas have ~standard.\n if (!('~standard' in schema)) {\n return schema(value) ? { ok: true, value } : { ok: false, issues: [`failed ${schema.name}`] };\n }\n const result = schema['~standard'].validate(value);\n if (result instanceof Promise) {\n throw new TypeError(message(20, tag, name));\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(message(21, tag, name, from, issues.join('; ')));\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 * without calling this (propFeature, element.ts: gyral-c5d.14).\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(message(22, tag, 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 text = message(23, tag, shadowed.join(', '));\n if (DEV) throw new TypeError(text);\n console.warn(text);\n}\n\n/**\n * What the element uses, registered by the prop builders (features.ts). Property sets and seeds\n * go through `propertyValue` in development only: production keeps the value as given, so the\n * property path's schema and guard handling stay out of production bundles (gyral-c5d.14).\n */\nexport const propFeature = {\n attributeOf,\n attributeValue,\n propertyValue: DEV ? propertyValue : undefined,\n warnMissing,\n readProps,\n checkShadowed,\n};\n\nexport type PropFeature = typeof propFeature;\n"]}
|
package/dist/retry.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Driver, RetryPolicy } from './command.js';
|
|
2
|
+
/**
|
|
3
|
+
* The same driver (same name, so substitution by name still works), with `run` retried when it
|
|
4
|
+
* rejects: up to `policy.times` more attempts, after `delayMs` (doubling from it with
|
|
5
|
+
* `backoff: 'exponential'`; a random part of it with `jitter`), and only for failures
|
|
6
|
+
* `retryIf` accepts. An abort (the command switched away, the component disconnected) ends it
|
|
7
|
+
* at once and is never retried. Wrap where the driver is chosen: app setup, a component's
|
|
8
|
+
* `drivers`, or a test fake.
|
|
9
|
+
*
|
|
10
|
+
* const api = retry(makeHttpDriver({ baseUrl: '/api', timeoutMs: 8000 }), {
|
|
11
|
+
* times: 3, delayMs: 300, backoff: 'exponential', jitter: true, retryIf: retryableHttpError,
|
|
12
|
+
* });
|
|
13
|
+
* const feed = retry(subscription('feed', connect), { times: Infinity, delayMs: 1000 });
|
|
14
|
+
*/
|
|
15
|
+
export declare function retry<I, O, E>(driver: Driver<I, O, E>, policy: RetryPolicy<E>): Driver<I, O, E>;
|
package/dist/retry.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Waits `ms`, or rejects with the abort reason as soon as `signal` aborts. */
|
|
2
|
+
const sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
3
|
+
const stop = () => {
|
|
4
|
+
clearTimeout(timer);
|
|
5
|
+
reject(signal.reason);
|
|
6
|
+
};
|
|
7
|
+
const timer = setTimeout(() => {
|
|
8
|
+
signal.removeEventListener('abort', stop);
|
|
9
|
+
resolve();
|
|
10
|
+
}, ms);
|
|
11
|
+
signal.addEventListener('abort', stop, { once: true });
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* The same driver (same name, so substitution by name still works), with `run` retried when it
|
|
15
|
+
* rejects: up to `policy.times` more attempts, after `delayMs` (doubling from it with
|
|
16
|
+
* `backoff: 'exponential'`; a random part of it with `jitter`), and only for failures
|
|
17
|
+
* `retryIf` accepts. An abort (the command switched away, the component disconnected) ends it
|
|
18
|
+
* at once and is never retried. Wrap where the driver is chosen: app setup, a component's
|
|
19
|
+
* `drivers`, or a test fake.
|
|
20
|
+
*
|
|
21
|
+
* const api = retry(makeHttpDriver({ baseUrl: '/api', timeoutMs: 8000 }), {
|
|
22
|
+
* times: 3, delayMs: 300, backoff: 'exponential', jitter: true, retryIf: retryableHttpError,
|
|
23
|
+
* });
|
|
24
|
+
* const feed = retry(subscription('feed', connect), { times: Infinity, delayMs: 1000 });
|
|
25
|
+
*/
|
|
26
|
+
export function retry(driver, policy) {
|
|
27
|
+
const { times, delayMs: base = 0, backoff, jitter, retryIf } = policy;
|
|
28
|
+
return {
|
|
29
|
+
...driver,
|
|
30
|
+
run: async (input, ctx) => {
|
|
31
|
+
for (let attempt = 0;; attempt += 1) {
|
|
32
|
+
try {
|
|
33
|
+
return await driver.run(input, ctx);
|
|
34
|
+
}
|
|
35
|
+
catch (cause) {
|
|
36
|
+
if (ctx.signal.aborted || attempt >= times)
|
|
37
|
+
throw cause;
|
|
38
|
+
// Sound: without `toError` the driver's error type is what it throws.
|
|
39
|
+
if (retryIf?.(driver.toError?.(cause) ?? cause) === false)
|
|
40
|
+
throw cause;
|
|
41
|
+
const delay = backoff === 'exponential' ? base * 2 ** attempt : base;
|
|
42
|
+
await sleep(jitter === true ? Math.random() * delay : delay, ctx.signal);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAIA,+EAA+E;AAC/E,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,MAAmB,EAAiB,EAAE,CAC/D,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;IAC9B,MAAM,IAAI,GAAG,GAAS,EAAE;QACtB,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,MAAM,CAAC,MAAM,CAAC,MAAe,CAAC,CAAC;IACjC,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;QAC5B,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC1C,OAAO,EAAE,CAAC;IACZ,CAAC,EAAE,EAAE,CAAC,CAAC;IACP,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AACzD,CAAC,CAAC,CAAC;AAEL;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,KAAK,CAAU,MAAuB,EAAE,MAAsB;IAC5E,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IACtE,OAAO;QACL,GAAG,MAAM;QACT,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACxB,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,IAAI,CAAC,EAAE,CAAC;gBACrC,IAAI,CAAC;oBACH,OAAO,MAAM,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;gBACtC,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,OAAO,IAAI,KAAK;wBAAE,MAAM,KAAK,CAAC;oBACxD,sEAAsE;oBACtE,IAAI,OAAO,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,IAAK,KAAW,CAAC,KAAK,KAAK;wBAAE,MAAM,KAAK,CAAC;oBAC9E,MAAM,KAAK,GAAG,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;oBACrE,MAAM,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC3E,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["// Retries as a driver wrapper (ADR 0022): the interpreter runs a driver once, and apps that\n// never call `retry` don't bundle its delays. A standalone module, like subscription.ts.\nimport type { Driver, RetryPolicy } from './command.js';\n\n/** Waits `ms`, or rejects with the abort reason as soon as `signal` aborts. */\nconst sleep = (ms: number, signal: AbortSignal): Promise<void> =>\n new Promise((resolve, reject) => {\n const stop = (): void => {\n clearTimeout(timer);\n reject(signal.reason as Error);\n };\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', stop);\n resolve();\n }, ms);\n signal.addEventListener('abort', stop, { once: true });\n });\n\n/**\n * The same driver (same name, so substitution by name still works), with `run` retried when it\n * rejects: up to `policy.times` more attempts, after `delayMs` (doubling from it with\n * `backoff: 'exponential'`; a random part of it with `jitter`), and only for failures\n * `retryIf` accepts. An abort (the command switched away, the component disconnected) ends it\n * at once and is never retried. Wrap where the driver is chosen: app setup, a component's\n * `drivers`, or a test fake.\n *\n * const api = retry(makeHttpDriver({ baseUrl: '/api', timeoutMs: 8000 }), {\n * times: 3, delayMs: 300, backoff: 'exponential', jitter: true, retryIf: retryableHttpError,\n * });\n * const feed = retry(subscription('feed', connect), { times: Infinity, delayMs: 1000 });\n */\nexport function retry<I, O, E>(driver: Driver<I, O, E>, policy: RetryPolicy<E>): Driver<I, O, E> {\n const { times, delayMs: base = 0, backoff, jitter, retryIf } = policy;\n return {\n ...driver,\n run: async (input, ctx) => {\n for (let attempt = 0; ; attempt += 1) {\n try {\n return await driver.run(input, ctx);\n } catch (cause) {\n if (ctx.signal.aborted || attempt >= times) throw cause;\n // Sound: without `toError` the driver's error type is what it throws.\n if (retryIf?.(driver.toError?.(cause) ?? (cause as E)) === false) throw cause;\n const delay = backoff === 'exponential' ? base * 2 ** attempt : base;\n await sleep(jitter === true ? Math.random() * delay : delay, ctx.signal);\n }\n }\n },\n };\n}\n"]}
|
package/dist/scheduler.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ export declare const POST_FOCUS = 0;
|
|
|
12
12
|
export declare const POST_STATES = 1;
|
|
13
13
|
export declare const POST_HYDRATED = 2;
|
|
14
14
|
export declare const POST_INIT = 3;
|
|
15
|
+
/** A message reached a host or a store (04 "`settled()`": messages restart the quiet window). */
|
|
16
|
+
export declare function noteActivity(): void;
|
|
17
|
+
/** How much activity (messages, marks) the page has seen; only differences matter. */
|
|
18
|
+
export declare const activityCount: () => number;
|
|
15
19
|
/**
|
|
16
20
|
* True when no host is dirty (in either lane), no flush or transition update is pending and
|
|
17
21
|
* nothing is deferred.
|
|
@@ -21,9 +25,9 @@ export declare const isQuiet: () => boolean;
|
|
|
21
25
|
export declare function whenQuiet(): Promise<void>;
|
|
22
26
|
/**
|
|
23
27
|
* Marks `task` for the next flush: the microtask one, or with `onFrame` the next animation
|
|
24
|
-
* frame's (04 "Frame lane"
|
|
25
|
-
* host
|
|
26
|
-
*
|
|
28
|
+
* frame's (04 "Frame lane"; the microtask one when the build left the frame lane out). Marking
|
|
29
|
+
* an already-dirty host does nothing; a microtask mark moves a host waiting for the frame into
|
|
30
|
+
* the microtask flush.
|
|
27
31
|
*/
|
|
28
32
|
export declare function markDirty(task: HostTask, onFrame?: boolean): void;
|
|
29
33
|
/** Queues post-render work for the current (or next) flush. */
|
package/dist/scheduler.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
// The global scheduler (docs/design-docs/view/04-scheduler.md). Every host's render goes
|
|
2
2
|
// through it: reducers run at once, the render waits for one microtask flush shared by the
|
|
3
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),
|
|
4
|
+
// then their element hooks (children first, view/02 "Commit order"), then runs the post-render queue (focus, custom states, `Hydrated`, deferred init commands),
|
|
5
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"
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
// the frame lane instead: it renders in the next animation frame (04 "Frame lane",
|
|
7
|
+
// frame-lane.ts). The frame lane and view transitions come through `#spec-features`, present
|
|
8
|
+
// unless a compiled build found no module naming them (gyral-c5d.12). It lives outside view/
|
|
9
|
+
// (view/ never imports it) and drives the renderer through view/index.ts.
|
|
10
|
+
import { fail } from './errors.js';
|
|
11
|
+
import { frameLane, viewTransitions } from '#spec-features';
|
|
12
|
+
import { DEV, flushHooks, message, renderBatch } from './view/index.js';
|
|
10
13
|
/** Post-render phases, run in this order once per pass (04 "Post-render queue"). */
|
|
11
14
|
export const POST_FOCUS = 0;
|
|
12
15
|
export const POST_STATES = 1;
|
|
@@ -19,38 +22,32 @@ const IDLE = 0;
|
|
|
19
22
|
const SCHEDULED = 1;
|
|
20
23
|
const TRANSITION = 2;
|
|
21
24
|
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
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
26
|
let post = [];
|
|
32
27
|
let phase = IDLE;
|
|
33
28
|
let transitionWanted = false;
|
|
34
29
|
/** Deferred callbacks (outputs to parents) and held loads still pending; settled() waits. */
|
|
35
30
|
let deferred = 0;
|
|
36
31
|
let quiet = null;
|
|
32
|
+
/** Messages delivered and hosts marked so far; settled() restarts its quiet window on a change. */
|
|
33
|
+
let activity = 0;
|
|
34
|
+
/** A message reached a host or a store (04 "`settled()`": messages restart the quiet window). */
|
|
35
|
+
export function noteActivity() {
|
|
36
|
+
activity += 1;
|
|
37
|
+
}
|
|
38
|
+
/** How much activity (messages, marks) the page has seen; only differences matter. */
|
|
39
|
+
export const activityCount = () => activity;
|
|
37
40
|
/**
|
|
38
41
|
* True when no host is dirty (in either lane), no flush or transition update is pending and
|
|
39
42
|
* nothing is deferred.
|
|
40
43
|
*/
|
|
41
|
-
export const isQuiet = () => phase === IDLE && dirty.size === 0 &&
|
|
44
|
+
export const isQuiet = () => phase === IDLE && dirty.size === 0 && frameLane?.pending() !== true && deferred === 0;
|
|
42
45
|
/** Resolves at the end of the flush that leaves the scheduler quiet. */
|
|
43
46
|
export function whenQuiet() {
|
|
44
47
|
if (isQuiet())
|
|
45
48
|
return Promise.resolve();
|
|
46
49
|
if (quiet === null) {
|
|
47
|
-
|
|
48
|
-
let reject = ignore;
|
|
49
|
-
const promise = new Promise((res, rej) => {
|
|
50
|
-
resolve = res;
|
|
51
|
-
reject = rej;
|
|
52
|
-
});
|
|
53
|
-
quiet = { promise, resolve, reject };
|
|
50
|
+
quiet = Promise.withResolvers();
|
|
54
51
|
}
|
|
55
52
|
return quiet.promise;
|
|
56
53
|
}
|
|
@@ -62,33 +59,22 @@ function schedule() {
|
|
|
62
59
|
}
|
|
63
60
|
/**
|
|
64
61
|
* Marks `task` for the next flush: the microtask one, or with `onFrame` the next animation
|
|
65
|
-
* frame's (04 "Frame lane"
|
|
66
|
-
* host
|
|
67
|
-
*
|
|
62
|
+
* frame's (04 "Frame lane"; the microtask one when the build left the frame lane out). Marking
|
|
63
|
+
* an already-dirty host does nothing; a microtask mark moves a host waiting for the frame into
|
|
64
|
+
* the microtask flush.
|
|
68
65
|
*/
|
|
69
66
|
export function markDirty(task, onFrame = false) {
|
|
67
|
+
activity += 1;
|
|
70
68
|
if (dirty.has(task))
|
|
71
69
|
return;
|
|
72
|
-
if (onFrame) {
|
|
73
|
-
|
|
74
|
-
if (frameTimer === undefined) {
|
|
75
|
-
frameRaf = requestAnimationFrame(runFrame);
|
|
76
|
-
frameTimer = setTimeout(runFrame, FRAME_FALLBACK_MS);
|
|
77
|
-
}
|
|
70
|
+
if (onFrame && frameLane !== undefined) {
|
|
71
|
+
frameLane.add(task, markDirty);
|
|
78
72
|
return;
|
|
79
73
|
}
|
|
80
|
-
|
|
74
|
+
frameLane?.drop(task);
|
|
81
75
|
dirty.add(task);
|
|
82
76
|
schedule();
|
|
83
77
|
}
|
|
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
78
|
/** Queues post-render work for the current (or next) flush. */
|
|
93
79
|
export function afterRender(phaseOf, run) {
|
|
94
80
|
post.push({ phase: phaseOf, run });
|
|
@@ -107,7 +93,7 @@ export function defer(fn) {
|
|
|
107
93
|
fn();
|
|
108
94
|
}
|
|
109
95
|
catch (error) {
|
|
110
|
-
|
|
96
|
+
fail(error, 'command', message(30));
|
|
111
97
|
}
|
|
112
98
|
finally {
|
|
113
99
|
deferred -= 1;
|
|
@@ -127,13 +113,12 @@ export function hold(work) {
|
|
|
127
113
|
});
|
|
128
114
|
}
|
|
129
115
|
function start() {
|
|
130
|
-
if (transitionWanted
|
|
116
|
+
if (transitionWanted) {
|
|
131
117
|
phase = TRANSITION;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
else {
|
|
135
|
-
flush();
|
|
118
|
+
if (viewTransitions?.(flush) === true)
|
|
119
|
+
return;
|
|
136
120
|
}
|
|
121
|
+
flush();
|
|
137
122
|
}
|
|
138
123
|
/** The dirty host with the smallest depth; ties go to the one marked first. */
|
|
139
124
|
function next() {
|
|
@@ -155,7 +140,7 @@ function renderAll(counts) {
|
|
|
155
140
|
task.render();
|
|
156
141
|
}
|
|
157
142
|
catch (error) {
|
|
158
|
-
|
|
143
|
+
fail(error, 'view', message(31, task.tag), { tag: task.tag });
|
|
159
144
|
}
|
|
160
145
|
}
|
|
161
146
|
}
|
|
@@ -167,17 +152,13 @@ function runPost() {
|
|
|
167
152
|
task.run();
|
|
168
153
|
}
|
|
169
154
|
catch (error) {
|
|
170
|
-
|
|
155
|
+
fail(error, 'view', message(32));
|
|
171
156
|
}
|
|
172
157
|
}
|
|
173
158
|
}
|
|
174
159
|
function loopError(counts) {
|
|
175
160
|
const tags = [...new Set([...counts.keys()].map((t) => `<${t.tag}>`))].join(', ');
|
|
176
|
-
return new Error(
|
|
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
|
-
: ''));
|
|
161
|
+
return new Error(message(33, tags));
|
|
181
162
|
}
|
|
182
163
|
function flush() {
|
|
183
164
|
phase = FLUSHING;
|
|
@@ -187,9 +168,10 @@ function flush() {
|
|
|
187
168
|
renderBatch(() => {
|
|
188
169
|
for (let pass = 1;; pass++) {
|
|
189
170
|
renderAll(counts);
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
171
|
+
flushHooks(); // every dirty host rendered: element hooks run, children first
|
|
172
|
+
// Client calls can deliver intents: commit their new state before focus/other work.
|
|
173
|
+
if (dirty.size === 0 && post.length > 0)
|
|
174
|
+
runPost();
|
|
193
175
|
if (dirty.size === 0 && post.length === 0)
|
|
194
176
|
return;
|
|
195
177
|
if (pass >= MAX_PASSES)
|
|
@@ -208,7 +190,7 @@ function flush() {
|
|
|
208
190
|
settle();
|
|
209
191
|
}
|
|
210
192
|
else if (!DEV) {
|
|
211
|
-
|
|
193
|
+
fail(failure, 'view', failure.message); // production: report and drop the rest, so the page doesn't freeze
|
|
212
194
|
settle();
|
|
213
195
|
}
|
|
214
196
|
else if (quiet !== null) {
|
package/dist/scheduler.js.map
CHANGED
|
@@ -1 +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"]}
|
|
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 their element hooks (children first, view/02 \"Commit order\"), 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\",\n// frame-lane.ts). The frame lane and view transitions come through `#spec-features`, present\n// unless a compiled build found no module naming them (gyral-c5d.12). It lives outside view/\n// (view/ never imports it) and drives the renderer through view/index.ts.\nimport { fail } from './errors.js';\nimport { frameLane, viewTransitions } from '#spec-features';\nimport { DEV, flushHooks, message, 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\nconst dirty = new Set<HostTask>();\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/** Messages delivered and hosts marked so far; settled() restarts its quiet window on a change. */\nlet activity = 0;\n\n/** A message reached a host or a store (04 \"`settled()`\": messages restart the quiet window). */\nexport function noteActivity(): void {\n activity += 1;\n}\n\n/** How much activity (messages, marks) the page has seen; only differences matter. */\nexport const activityCount = (): number => activity;\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 && frameLane?.pending() !== true && 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 quiet = Promise.withResolvers();\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\"; the microtask one when the build left the frame lane out). Marking\n * an already-dirty host does nothing; a microtask mark moves a host waiting for the frame into\n * the microtask flush.\n */\nexport function markDirty(task: HostTask, onFrame = false): void {\n activity += 1;\n if (dirty.has(task)) return;\n if (onFrame && frameLane !== undefined) {\n frameLane.add(task, markDirty);\n return;\n }\n frameLane?.drop(task);\n dirty.add(task);\n schedule();\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 fail(error, 'command', message(30));\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) {\n phase = TRANSITION;\n if (viewTransitions?.(flush) === true) return;\n }\n flush();\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 fail(error, 'view', message(31, task.tag), { tag: task.tag });\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 fail(error, 'view', message(32));\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(message(33, tags));\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 flushHooks(); // every dirty host rendered: element hooks run, children first\n // Client calls can deliver intents: commit their new state before focus/other work.\n if (dirty.size === 0 && post.length > 0) 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 fail(failure, 'view', failure.message); // production: report 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"]}
|