@velarscript/web 0.12.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/analyzer.d.ts +164 -23
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +1254 -133
- package/dist/analyzer.js.map +1 -1
- package/dist/ast.d.ts +11 -0
- package/dist/ast.d.ts.map +1 -1
- package/dist/ast.js +22 -0
- package/dist/ast.js.map +1 -1
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +69 -44
- package/dist/compiler.js.map +1 -1
- package/dist/css-string.d.ts +32 -0
- package/dist/css-string.d.ts.map +1 -0
- package/dist/css-string.js +66 -0
- package/dist/css-string.js.map +1 -0
- package/dist/css-tokens.d.ts +16 -0
- package/dist/css-tokens.d.ts.map +1 -1
- package/dist/css-tokens.js +84 -0
- package/dist/css-tokens.js.map +1 -1
- package/dist/elements.d.ts +55 -0
- package/dist/elements.d.ts.map +1 -1
- package/dist/elements.js +152 -0
- package/dist/elements.js.map +1 -1
- package/dist/emitter.d.ts +10 -0
- package/dist/emitter.d.ts.map +1 -1
- package/dist/emitter.js +588 -159
- package/dist/emitter.js.map +1 -1
- package/dist/keyframes.d.ts +17 -0
- package/dist/keyframes.d.ts.map +1 -1
- package/dist/keyframes.js +28 -9
- package/dist/keyframes.js.map +1 -1
- package/dist/lexer.d.ts +21 -7
- package/dist/lexer.d.ts.map +1 -1
- package/dist/lexer.js +32 -12
- package/dist/lexer.js.map +1 -1
- package/dist/look-static.d.ts.map +1 -1
- package/dist/look-static.js +120 -20
- package/dist/look-static.js.map +1 -1
- package/dist/look.d.ts +15 -0
- package/dist/look.d.ts.map +1 -1
- package/dist/look.js +7 -2
- package/dist/look.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +15 -20
- package/dist/parser.js.map +1 -1
- package/dist/runtime-foundation.d.ts.map +1 -1
- package/dist/runtime-foundation.js +321 -70
- package/dist/runtime-foundation.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +320 -179
- package/dist/runtime.js.map +1 -1
- package/dist/stable-order.d.ts +16 -0
- package/dist/stable-order.d.ts.map +1 -0
- package/dist/stable-order.js +18 -0
- package/dist/stable-order.js.map +1 -0
- package/dist/types.d.ts +0 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -16
- package/dist/types.js.map +1 -1
- package/dist/websocket-runtime.d.ts.map +1 -1
- package/dist/websocket-runtime.js +9 -5
- package/dist/websocket-runtime.js.map +1 -1
- package/dist/worker-runtime.d.ts.map +1 -1
- package/dist/worker-runtime.js +9 -7
- package/dist/worker-runtime.js.map +1 -1
- package/package.json +2 -2
package/dist/emitter.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { cssPropertyName, LOOK_ARITHMETIC_HINT, LOOK_MEDIA_LENGTH_UNITS, LOOK_PROPERTIES } from "./look.js";
|
|
1
|
+
import { cssPropertyName, LOOK_ARITHMETIC_HINT, LOOK_MEDIA_LENGTH_UNITS, LOOK_PROPERTIES, LOOK_PROPERTY_KEYWORDS, LOOK_PROPERTY_VALUE_KINDS } from "./look.js";
|
|
2
|
+
import { isCssDeclarationValue } from "./css-tokens.js";
|
|
3
|
+
import { CSS_STRING_RUNTIME } from "./css-string.js";
|
|
2
4
|
import { collectLookStaticValues, evaluateLookStaticExpression, isLookStaticValue, lookStaticCss } from "./look-static.js";
|
|
3
5
|
import { keyframeCssValue, keyframesCanonical, keyframesName } from "./keyframes.js";
|
|
4
6
|
import { JavaScriptEmitter, spanIdentity, VELAR_ERROR_NORMALIZATION_MODULE, VELAR_RUNTIME_REGISTRY_KEY } from "@velarscript/compiler/extension";
|
|
5
7
|
import { WEB_RUNTIME_FOUNDATION, WEB_RUNTIME_FOUNDATION_SHARED_ERROR } from "./runtime-foundation.js";
|
|
6
|
-
import { isWebExpression, isWebJsx, isWebKeyframes, isWebLook, isWebStatement, isWebUnit, webExpressionContainsDirectAwait, webStatementContainsDirectAwait, } from "./ast.js";
|
|
8
|
+
import { isWebExpression, isWebJsx, isWebKeyframes, isWebLook, isWebStatement, isWebUnit, webExpressionContainsDirectAwait, webStatementContainsDirectAwait, webWatchSubjectLabel, } from "./ast.js";
|
|
7
9
|
const FILE_TYPE_RUNTIME = String.raw `
|
|
8
10
|
function __velarFileTypeIs(value) {
|
|
9
11
|
const descriptor = Object.getOwnPropertyDescriptor(globalThis, Symbol.for("velar.file.registry.v1"));
|
|
@@ -41,6 +43,8 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
|
|
|
41
43
|
needsLookArithmeticRuntime = false;
|
|
42
44
|
importedLookStaticValues;
|
|
43
45
|
lookStaticValues = new Map();
|
|
46
|
+
/** CSS names of the closed-keyword properties this module styles, for the runtime guard. */
|
|
47
|
+
lookKeywordProperties = new Set();
|
|
44
48
|
jsxId = 0;
|
|
45
49
|
keyframeNames = new Map();
|
|
46
50
|
constructor(hints, forcedFunctionExports = new Set(), resourceContents = new Map(), extensionImports = new Map(), options = {}) {
|
|
@@ -117,13 +121,26 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
|
|
|
117
121
|
}
|
|
118
122
|
webRuntimeHelpers() {
|
|
119
123
|
if (!this.usesSharedRuntimeModules())
|
|
120
|
-
return [webRuntime(WEB_RUNTIME_FOUNDATION)];
|
|
124
|
+
return [webRuntime(WEB_RUNTIME_FOUNDATION, this.lookKeywordTable())];
|
|
121
125
|
this.requireRuntimeModule(VELAR_ERROR_NORMALIZATION_MODULE);
|
|
122
126
|
return [
|
|
123
127
|
`import { errorApply as __velarErrorApply, errorCode as __velarErrorCode, isError as __velarIsError, normalizeError as __velarNormalizeError } from ${JSON.stringify(VELAR_ERROR_NORMALIZATION_MODULE)};`,
|
|
124
|
-
webRuntime(WEB_RUNTIME_FOUNDATION_SHARED_ERROR),
|
|
128
|
+
webRuntime(WEB_RUNTIME_FOUNDATION_SHARED_ERROR, this.lookKeywordTable()),
|
|
125
129
|
];
|
|
126
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* The closed keyword sets of the properties this module styles, so a value
|
|
133
|
+
* the compiler could not read is still checked before it reaches the DOM.
|
|
134
|
+
* Only the properties written here ship: the whole table is 17 KiB, and a
|
|
135
|
+
* module pays for the properties it uses.
|
|
136
|
+
*/
|
|
137
|
+
lookKeywordTable() {
|
|
138
|
+
const entries = [...this.lookKeywordProperties].sort()
|
|
139
|
+
.map((name) => ` ${JSON.stringify(cssPropertyName(name))}: ${JSON.stringify([...LOOK_PROPERTY_KEYWORDS.get(name) ?? []])},`);
|
|
140
|
+
return entries.length === 0
|
|
141
|
+
? "const __velarLookKeywords = { __proto__: null };"
|
|
142
|
+
: `const __velarLookKeywords = {\n __proto__: null,\n${entries.join("\n")}\n};`;
|
|
143
|
+
}
|
|
127
144
|
reactiveBridgeHelpers(needsJavaScriptCallBoundary, needsCollections) {
|
|
128
145
|
if (this.usesSharedRuntimeModules())
|
|
129
146
|
return super.reactiveBridgeHelpers(needsJavaScriptCallBoundary, needsCollections);
|
|
@@ -264,7 +281,7 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
|
|
|
264
281
|
return this.emitComponent(statement, depth);
|
|
265
282
|
if (statement.kind === "ExtensionStatement:web:state") {
|
|
266
283
|
const indentation = " ".repeat(depth);
|
|
267
|
-
return `${indentation}${statement.exported ? "export " : ""}const ${statement.name} = __velarState(${this.emitMappedExpression(statement.initializer)});`;
|
|
284
|
+
return `${indentation}${statement.exported ? "export " : ""}const ${statement.name} = __velarState(${this.emitMappedExpression(statement.initializer)}, ${JSON.stringify(statement.name)});`;
|
|
268
285
|
}
|
|
269
286
|
if (statement.kind === "ExtensionStatement:web:computed") {
|
|
270
287
|
const indentation = " ".repeat(depth);
|
|
@@ -288,7 +305,7 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
|
|
|
288
305
|
const indentation = " ".repeat(depth);
|
|
289
306
|
const parameters = [statement.currentName, statement.previousName].filter((name) => name !== null).join(", ");
|
|
290
307
|
const body = this.emitStatementLines(statement.body, depth + 1).join("\n");
|
|
291
|
-
return `${indentation}__velarWatch(() => ${this.emitMappedExpression(statement.expression)}, (${parameters}) => {${body ? `\n${body}\n${indentation}` : ""}}, __velarGlobalScope);`;
|
|
308
|
+
return `${indentation}__velarWatch(() => ${this.emitMappedExpression(statement.expression)}, (${parameters}) => {${body ? `\n${body}\n${indentation}` : ""}}, __velarGlobalScope, ${JSON.stringify(webWatchSubjectLabel(statement.expression))});`;
|
|
292
309
|
}
|
|
293
310
|
}
|
|
294
311
|
if (statement.kind === "AssignmentStatement") {
|
|
@@ -331,8 +348,6 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
|
|
|
331
348
|
return "__velarMount";
|
|
332
349
|
if (expression.name === "tick")
|
|
333
350
|
return "__velarTick";
|
|
334
|
-
if (expression.name === "cached")
|
|
335
|
-
return "__velarRuntime.computed";
|
|
336
351
|
const controlled = this.hints.extensionLiterals.get(spanIdentity(expression.span));
|
|
337
352
|
if (controlled !== undefined)
|
|
338
353
|
return JSON.stringify(controlled);
|
|
@@ -432,7 +447,7 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
|
|
|
432
447
|
let cleanupBody = [];
|
|
433
448
|
for (const item of statement.body) {
|
|
434
449
|
if (item.kind === "ExtensionStatement:web:state") {
|
|
435
|
-
lines.push(`${bodyIndent}const ${item.name} = __velarState(${this.emitMappedExpression(item.initializer)});`);
|
|
450
|
+
lines.push(`${bodyIndent}const ${item.name} = __velarState(${this.emitMappedExpression(item.initializer)}, ${JSON.stringify(item.name)});`);
|
|
436
451
|
}
|
|
437
452
|
else if (item.kind === "ExtensionStatement:web:computed") {
|
|
438
453
|
lines.push(`${bodyIndent}const ${item.name} = __velarComputed(() => (${this.emitMappedExpression(item.initializer)}));`);
|
|
@@ -451,7 +466,7 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
|
|
|
451
466
|
else if (item.kind === "ExtensionStatement:web:watch") {
|
|
452
467
|
const parameters = [item.currentName, item.previousName].filter((name) => name !== null).join(", ");
|
|
453
468
|
const watchLines = this.emitStatementLines(item.body, depth + 3).join("\n");
|
|
454
|
-
lines.push(`${bodyIndent}__velarWatch(() => ${this.emitMappedExpression(item.expression)}, (${parameters}) => {${watchLines ? `\n${watchLines}\n${bodyIndent}` : ""}}, __velarComponentScope);`);
|
|
469
|
+
lines.push(`${bodyIndent}__velarWatch(() => ${this.emitMappedExpression(item.expression)}, (${parameters}) => {${watchLines ? `\n${watchLines}\n${bodyIndent}` : ""}}, __velarComponentScope, ${JSON.stringify(webWatchSubjectLabel(item.expression))});`);
|
|
455
470
|
}
|
|
456
471
|
else if (item.kind === "ExtensionStatement:web:expose") {
|
|
457
472
|
expose ??= item.value;
|
|
@@ -492,7 +507,10 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
|
|
|
492
507
|
lines.push(`${bodyIndent}const __velarHandle = ${expose ? `__velarComponentHandle(${this.emitMappedExpression(expose)}, ${JSON.stringify(statement.name)})` : "null"};`);
|
|
493
508
|
lines.push(`${bodyIndent}if (__velarProps.class !== undefined) __velarClassBindRoot(__velarRoot, () => __velarProps.class, __velarComponentScope);`);
|
|
494
509
|
lines.push(`${bodyIndent}if (__velarProps.look !== undefined) __velarLookBindRoot(__velarRoot, () => __velarProps.look, __velarComponentScope);`);
|
|
495
|
-
|
|
510
|
+
// 'class' and 'look' are fields a component may declare and read, so they
|
|
511
|
+
// arrive as props. The 'style:' slot is not: it is bound to the instance
|
|
512
|
+
// root by whoever wrote it, at the instantiation site, for every component
|
|
513
|
+
// alike -- see __velarInstantiate.
|
|
496
514
|
const mounted = this.emitStatementLines(mountedBody, depth + 3).join("\n");
|
|
497
515
|
const cleanup = cleanupBody.map((child) => {
|
|
498
516
|
if (["VariableDeclaration", "FunctionDeclaration", "ClassDeclaration", "TypeDeclaration", "EnumDeclaration"].includes(child.kind)) {
|
|
@@ -567,9 +585,11 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
|
|
|
567
585
|
}
|
|
568
586
|
// Children stay a thunk so the charter's evaluation order holds at
|
|
569
587
|
// the runtime boundary: props left to right, then children, then the
|
|
570
|
-
// component function.
|
|
588
|
+
// component function. The thunk takes the scope to build into, so the
|
|
589
|
+
// position that shows the slot owns what it built and destroys it when
|
|
590
|
+
// it stops showing it.
|
|
571
591
|
const children = hasMeaningfulChildren(expression.children)
|
|
572
|
-
? `() => (${this.
|
|
592
|
+
? `(__velarChildrenScope = ${componentScope}) => (${this.emitJsxChildrenCode(expression.children, namespace)})`
|
|
573
593
|
: "undefined";
|
|
574
594
|
const ref = expression.attributes.find((attribute) => attribute.name === "ref")?.value;
|
|
575
595
|
const refSetter = ref && typeof ref !== "string" && ref.kind === "IdentifierExpression"
|
|
@@ -679,6 +699,19 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
|
|
|
679
699
|
const fragment = { kind: "ExtensionExpression:web:jsx", tag: "", tagSpan: { start: fragmentSpan.start, end: fragmentSpan.start }, attributes: [], children, span: fragmentSpan };
|
|
680
700
|
return this.emitJsx(fragment, scope, true, namespace);
|
|
681
701
|
}
|
|
702
|
+
// The slot body builds into whichever scope the consuming position hands it,
|
|
703
|
+
// so every observer, ref and cleanup it registers dies with that position
|
|
704
|
+
// rather than accumulating on the caller for the component's whole lifetime.
|
|
705
|
+
emitJsxChildrenCode(children, namespace) {
|
|
706
|
+
const previousScope = this.currentScope;
|
|
707
|
+
this.currentScope = "__velarChildrenScope";
|
|
708
|
+
try {
|
|
709
|
+
return this.emitJsxChildren(children, "__velarChildrenScope", namespace);
|
|
710
|
+
}
|
|
711
|
+
finally {
|
|
712
|
+
this.currentScope = previousScope;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
682
715
|
emitDynamicChild(parent, expression, scope, namespace) {
|
|
683
716
|
const leaves = dynamicChildLeaves(expression);
|
|
684
717
|
const previousScope = this.currentScope;
|
|
@@ -778,7 +811,24 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
|
|
|
778
811
|
prepareLooks(program) {
|
|
779
812
|
const rules = new Map();
|
|
780
813
|
const keyframeRules = new Map();
|
|
814
|
+
const keyframeCanonicals = new Map();
|
|
781
815
|
this.keyframeNames.clear();
|
|
816
|
+
this.lookKeywordProperties.clear();
|
|
817
|
+
// A token's stylesheet position used to be wherever it first appeared
|
|
818
|
+
// anywhere in the module, because a Map keeps first-insertion order. The
|
|
819
|
+
// sequence records that first appearance explicitly so emission can sort by
|
|
820
|
+
// condition rank first and fall back to declaration order, rather than
|
|
821
|
+
// letting an unrelated earlier look decide a later one's winner (LOK-U8).
|
|
822
|
+
let sequence = 0;
|
|
823
|
+
const addRule = (token, property, target, staticAtoms) => {
|
|
824
|
+
if (rules.has(token))
|
|
825
|
+
return;
|
|
826
|
+
rules.set(token, { token, property, target, staticAtoms, sequence: sequence += 1 });
|
|
827
|
+
};
|
|
828
|
+
const noteKeywordProperty = (name) => {
|
|
829
|
+
if (LOOK_PROPERTY_VALUE_KINDS.get(name) === "keyword")
|
|
830
|
+
this.lookKeywordProperties.add(name);
|
|
831
|
+
};
|
|
782
832
|
const visit = (value) => {
|
|
783
833
|
if (!value || typeof value !== "object")
|
|
784
834
|
return;
|
|
@@ -786,14 +836,16 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
|
|
|
786
836
|
if (record.kind === "ExtensionExpression:web:jsx") {
|
|
787
837
|
const element = record;
|
|
788
838
|
for (const attribute of element.attributes) {
|
|
789
|
-
if (!attribute.name.startsWith("look:"))
|
|
839
|
+
if (!attribute.name.startsWith("look:") && !attribute.name.startsWith("style:"))
|
|
790
840
|
continue;
|
|
791
|
-
const name = attribute.name.slice("
|
|
841
|
+
const name = attribute.name.slice(attribute.name.indexOf(":") + 1);
|
|
792
842
|
if (!LOOK_PROPERTIES.has(name))
|
|
793
843
|
continue;
|
|
844
|
+
noteKeywordProperty(name);
|
|
845
|
+
if (!attribute.name.startsWith("look:"))
|
|
846
|
+
continue;
|
|
794
847
|
const property = cssPropertyName(name);
|
|
795
|
-
|
|
796
|
-
rules.set(token, { token, property, target: "", staticAtoms: [] });
|
|
848
|
+
addRule(lookToken([], "", property), property, "", []);
|
|
797
849
|
}
|
|
798
850
|
}
|
|
799
851
|
if (record.kind === "ExtensionExpression:web:look") {
|
|
@@ -801,9 +853,9 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
|
|
|
801
853
|
for (const entry of entries) {
|
|
802
854
|
if (entry.kind === "LookProperty") {
|
|
803
855
|
const property = cssPropertyName(entry.name);
|
|
856
|
+
noteKeywordProperty(entry.name);
|
|
804
857
|
for (const context of contexts) {
|
|
805
|
-
|
|
806
|
-
rules.set(token, { token, property, target, staticAtoms: context.staticAtoms });
|
|
858
|
+
addRule(lookToken(context.staticAtoms, target, property), property, target, context.staticAtoms);
|
|
807
859
|
}
|
|
808
860
|
}
|
|
809
861
|
else if (entry.kind === "LookIf") {
|
|
@@ -822,7 +874,16 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
|
|
|
822
874
|
const canonical = keyframesCanonical(expression, this.lookStaticValues);
|
|
823
875
|
const name = keyframesName(canonical);
|
|
824
876
|
this.keyframeNames.set(spanIdentity(expression.span), name);
|
|
825
|
-
|
|
877
|
+
const reused = keyframeCanonicals.get(name);
|
|
878
|
+
// The name is a promise that equal structures share one rule. Reusing
|
|
879
|
+
// it for a structure that is not equal would make one animation play
|
|
880
|
+
// another's motion, so the reuse path proves the identity rather than
|
|
881
|
+
// trusting the digest (LOK-U11).
|
|
882
|
+
if (reused !== undefined && reused !== canonical) {
|
|
883
|
+
throw new Error(`Generated keyframes name ${name} collides between two different keyframe structures`);
|
|
884
|
+
}
|
|
885
|
+
if (reused === undefined) {
|
|
886
|
+
keyframeCanonicals.set(name, canonical);
|
|
826
887
|
const stops = [...expression.stops]
|
|
827
888
|
.sort((left, right) => Math.min(...left.offsets) - Math.min(...right.offsets))
|
|
828
889
|
.map((stop) => {
|
|
@@ -831,7 +892,13 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
|
|
|
831
892
|
.join(",");
|
|
832
893
|
const declarations = stop.entries.map((entry) => {
|
|
833
894
|
const css = keyframeCssValue(entry.value, this.lookStaticValues);
|
|
834
|
-
|
|
895
|
+
// A value the lowering could not prove is one balanced
|
|
896
|
+
// declaration never reaches the concatenation: `}` in a stop
|
|
897
|
+
// closed the at-rule and turned the rest into author-owned CSS
|
|
898
|
+
// in the compiler-owned segment (LOK-U9). The analyzer already
|
|
899
|
+
// reports the same null as a diagnostic, so emission only has
|
|
900
|
+
// to stay structurally incapable of writing the escape.
|
|
901
|
+
return css === null || !isCssDeclarationValue(css) ? "" : `${cssPropertyName(entry.name)}:${css}`;
|
|
835
902
|
}).filter(Boolean).join(";");
|
|
836
903
|
return `${selectors}{${declarations}}`;
|
|
837
904
|
}).join("");
|
|
@@ -846,16 +913,22 @@ export class WebJavaScriptEmitter extends JavaScriptEmitter {
|
|
|
846
913
|
}
|
|
847
914
|
};
|
|
848
915
|
visit(program);
|
|
849
|
-
const lookCss = []
|
|
850
|
-
|
|
916
|
+
const lookCss = [...rules.values()]
|
|
917
|
+
.map((rule) => ({ rule, depth: lookConditionDepth(rule.staticAtoms) }))
|
|
918
|
+
// Rank decides, and declaration order only separates rules that share a
|
|
919
|
+
// rank. Emission used to follow the Map, so the sheet's byte order — and
|
|
920
|
+
// through it the per-module concatenation order the CLI sorts by
|
|
921
|
+
// filename — could pick the winner of a tie (LOK-U8, LOK-U10).
|
|
922
|
+
.sort((left, right) => left.depth - right.depth || left.rule.sequence - right.rule.sequence)
|
|
923
|
+
.map(({ rule, depth }) => {
|
|
851
924
|
const hookAtoms = rule.staticAtoms.filter((atom) => atom.kind === "hook");
|
|
852
925
|
const mediaAtoms = rule.staticAtoms.filter((atom) => atom.kind === "media" || atom.kind === "scheme" || atom.kind === "motion");
|
|
853
|
-
const base = `[data-velar-look~=${JSON.stringify(rule.token)}]${
|
|
926
|
+
const base = `[data-velar-look~=${JSON.stringify(rule.token)}]${"[data-velar-look]".repeat(depth)}`;
|
|
854
927
|
const selectors = lookSelectors(base, hookAtoms, rule.target);
|
|
855
928
|
const css = `${selectors.join(",")}{${lookDeclaration(rule.token, rule.property)}}`;
|
|
856
929
|
const query = mediaAtoms.map(lookMediaQuery).join(" and ");
|
|
857
|
-
|
|
858
|
-
}
|
|
930
|
+
return query ? `@media ${query}{${css}}` : css;
|
|
931
|
+
});
|
|
859
932
|
const before = [];
|
|
860
933
|
const after = [];
|
|
861
934
|
for (const statement of program.body) {
|
|
@@ -935,6 +1008,14 @@ function lookConditionTerms(expression, negated = false, staticValues = new Map(
|
|
|
935
1008
|
return [{ staticAtoms: [media], runtimeAtoms: [] }];
|
|
936
1009
|
return [{ staticAtoms: [], runtimeAtoms: [{ expression, negated }] }];
|
|
937
1010
|
}
|
|
1011
|
+
// A breakpoint is complementary the way the schemes and the motion preference
|
|
1012
|
+
// are: `not (width <= X)` is `width > X`, one condition with one media query.
|
|
1013
|
+
// The atom therefore folds the negation into its operator at construction, so
|
|
1014
|
+
// the two spellings reach `lookToken` as the same token instead of as two
|
|
1015
|
+
// rules that tie on specificity and are separated by source order.
|
|
1016
|
+
const LOOK_NEGATED_MEDIA_OPERATORS = new Map([
|
|
1017
|
+
["<", ">="], ["<=", ">"], [">", "<="], [">=", "<"],
|
|
1018
|
+
]);
|
|
938
1019
|
function viewportAtom(expression, negated, staticValues) {
|
|
939
1020
|
if (expression.kind !== "BinaryExpression" || !["<", "<=", ">", ">="].includes(expression.operator))
|
|
940
1021
|
return null;
|
|
@@ -945,12 +1026,13 @@ function viewportAtom(expression, negated, staticValues) {
|
|
|
945
1026
|
const threshold = evaluateLookStaticExpression(expression.right, staticValues);
|
|
946
1027
|
if (threshold?.kind !== "unit" || !LOOK_MEDIA_LENGTH_UNITS.has(threshold.unit))
|
|
947
1028
|
return null;
|
|
1029
|
+
const written = expression.operator;
|
|
948
1030
|
return {
|
|
949
1031
|
kind: "media",
|
|
950
1032
|
name: expression.left.property,
|
|
951
|
-
operator:
|
|
1033
|
+
operator: negated ? LOOK_NEGATED_MEDIA_OPERATORS.get(written) : written,
|
|
952
1034
|
value: lookStaticCss(threshold),
|
|
953
|
-
negated,
|
|
1035
|
+
negated: false,
|
|
954
1036
|
};
|
|
955
1037
|
}
|
|
956
1038
|
// 'scheme.dark' / 'scheme.light' lower to prefers-color-scheme media atoms.
|
|
@@ -994,11 +1076,38 @@ function lookToken(atoms, target, property) {
|
|
|
994
1076
|
return `scheme-${atom.name}`;
|
|
995
1077
|
if (atom.kind === "motion")
|
|
996
1078
|
return `motion-${atom.name}`;
|
|
997
|
-
return `viewport-${atom.name}-${
|
|
1079
|
+
return `viewport-${atom.name}-${lookOperatorName(atom.operator)}-${atom.value}`;
|
|
998
1080
|
}).sort();
|
|
999
1081
|
const prefix = [target ? kebab(target) : "", conditions.length > 0 ? conditions.join("+") : "base"].filter(Boolean).join(":");
|
|
1000
1082
|
return `${prefix}:${property}`;
|
|
1001
1083
|
}
|
|
1084
|
+
/**
|
|
1085
|
+
* How many extra `[data-velar-look]` selectors a rule carries, so that the
|
|
1086
|
+
* winner between two Look rules is decided by the conditions they name rather
|
|
1087
|
+
* than by their position in the sheet.
|
|
1088
|
+
*
|
|
1089
|
+
* The bump used to be a single flat `+1` for any non-empty condition set, which
|
|
1090
|
+
* made every conditional rule specificity `(0,2,0)`: a state rule tied with a
|
|
1091
|
+
* media rule, and a two-condition refinement tied with the one-condition
|
|
1092
|
+
* fallback it refines. Ties then fell through to source order, and source order
|
|
1093
|
+
* is per-module concatenation order, which the CLI sorts by filename — so the
|
|
1094
|
+
* rendered colour could change when a file was renamed (LOK-U8, LOK-U10,
|
|
1095
|
+
* LOK-U12).
|
|
1096
|
+
*
|
|
1097
|
+
* The rank is base < media < state < media+state, and within a rank a rule that
|
|
1098
|
+
* names more conditions outranks one that names fewer. The per-rank span is
|
|
1099
|
+
* bounded so a pathological condition count cannot cross a rank boundary; rules
|
|
1100
|
+
* that saturate it fall back to declaration order, which is stable.
|
|
1101
|
+
*/
|
|
1102
|
+
const LOOK_RANK_SPAN = 3;
|
|
1103
|
+
function lookConditionDepth(atoms) {
|
|
1104
|
+
if (atoms.length === 0)
|
|
1105
|
+
return 0;
|
|
1106
|
+
const hooks = atoms.filter((atom) => atom.kind === "hook").length;
|
|
1107
|
+
const media = atoms.length - hooks;
|
|
1108
|
+
const rank = hooks > 0 ? (media > 0 ? 2 : 1) : 0;
|
|
1109
|
+
return rank * LOOK_RANK_SPAN + Math.min(atoms.length, LOOK_RANK_SPAN);
|
|
1110
|
+
}
|
|
1002
1111
|
function lookVariable(token) {
|
|
1003
1112
|
return `--velar-look-${token.replace(/[^A-Za-z0-9_-]+/gu, "-")}`;
|
|
1004
1113
|
}
|
|
@@ -1014,10 +1123,7 @@ function lookMediaQuery(atom) {
|
|
|
1014
1123
|
return `(prefers-color-scheme: ${atom.name})`;
|
|
1015
1124
|
if (atom.kind === "motion")
|
|
1016
1125
|
return `(prefers-reduced-motion: ${atom.name})`;
|
|
1017
|
-
|
|
1018
|
-
? atom.operator === "<" ? ">=" : atom.operator === "<=" ? ">" : atom.operator === ">" ? "<=" : "<"
|
|
1019
|
-
: atom.operator;
|
|
1020
|
-
return `(${atom.name} ${operator} ${atom.value})`;
|
|
1126
|
+
return `(${atom.name} ${atom.operator} ${atom.value})`;
|
|
1021
1127
|
}
|
|
1022
1128
|
function lookSelectors(base, atoms, target) {
|
|
1023
1129
|
let selectors = [base];
|
|
@@ -1092,7 +1198,7 @@ function containsWebSyntax(value) {
|
|
|
1092
1198
|
if (record.kind === "ExtensionStatement:web:component" || record.kind === "ExtensionStatement:web:expose" || record.kind === "ExtensionStatement:web:unsafe-css" || record.kind === "ExtensionExpression:web:look" || record.kind === "ExtensionExpression:web:keyframes" || record.kind === "ExtensionExpression:web:jsx"
|
|
1093
1199
|
|| record.kind === "ExtensionStatement:web:state" || record.kind === "ExtensionStatement:web:computed" || record.kind === "ExtensionStatement:web:resource" || record.kind === "ExtensionStatement:web:action" || record.kind === "ExtensionStatement:web:watch")
|
|
1094
1200
|
return true;
|
|
1095
|
-
if (record.kind === "IdentifierExpression" && (record.name === "mount" || record.name === "tick"
|
|
1201
|
+
if (record.kind === "IdentifierExpression" && (record.name === "mount" || record.name === "tick"))
|
|
1096
1202
|
return true;
|
|
1097
1203
|
return Object.values(record).some((child) => Array.isArray(child) ? child.some(containsWebSyntax) : containsWebSyntax(child));
|
|
1098
1204
|
}
|
|
@@ -1213,61 +1319,6 @@ function __velarEventCall(value, name, nativeMethod) {
|
|
|
1213
1319
|
return __velarEventReflectApply(method, value, []);
|
|
1214
1320
|
}
|
|
1215
1321
|
|
|
1216
|
-
// This scheduler has a twin inside the runtime registry (runtime.schedule in
|
|
1217
|
-
// packages/web/src/runtime-foundation.ts) so registry-owned computed
|
|
1218
|
-
// observers schedule correctly no matter which module stamped the registry.
|
|
1219
|
-
// Both sides drain the same shared queues under the shared flushPending flag;
|
|
1220
|
-
// their budgets and overflow behavior must stay identical.
|
|
1221
|
-
function __velarSchedule(observer) {
|
|
1222
|
-
const queue = observer.mode === "watch" ? __velarRuntime.watchQueue : __velarRuntime.domQueue;
|
|
1223
|
-
if (!__velarGraphSetContains(queue, observer) && __velarGraphSetCount(queue) >= 100000) throw new RangeError("VelarScript reactive queues cannot exceed 100000 observers");
|
|
1224
|
-
__velarGraphSetInsert(queue, observer);
|
|
1225
|
-
if (!__velarRuntime.flushPending) {
|
|
1226
|
-
__velarRuntime.flushPending = true;
|
|
1227
|
-
__velarEnqueue(__velarFlush);
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
// Both queues are drained live: an observer that re-schedules itself or another
|
|
1232
|
-
// observer is picked up by the same walk. Two observers that invalidate each
|
|
1233
|
-
// other therefore never leave this function, which froze the page with nothing
|
|
1234
|
-
// on the error channel. The per-flush budget gives that case the same owned
|
|
1235
|
-
// ending as the single-observer cap: stop the observers still queued, report
|
|
1236
|
-
// once through velar/app, and let the turn finish.
|
|
1237
|
-
function __velarFlushOverflow() {
|
|
1238
|
-
const stalled = [];
|
|
1239
|
-
for (const observer of __velarGraphSetItems(__velarRuntime.domQueue)) stalled[stalled.length] = observer;
|
|
1240
|
-
for (const observer of __velarGraphSetItems(__velarRuntime.watchQueue)) stalled[stalled.length] = observer;
|
|
1241
|
-
__velarGraphSetEmpty(__velarRuntime.domQueue);
|
|
1242
|
-
__velarGraphSetEmpty(__velarRuntime.watchQueue);
|
|
1243
|
-
for (let index = 0; index < stalled.length; index += 1) {
|
|
1244
|
-
const observer = stalled[index];
|
|
1245
|
-
if (typeof observer.stop === "function") observer.stop();
|
|
1246
|
-
else observer.stopped = true;
|
|
1247
|
-
}
|
|
1248
|
-
__velarReport(new RangeError("Reactive updates cannot run more than 100000 observers in one flush"), "update", null);
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
function __velarFlush() {
|
|
1252
|
-
__velarRuntime.flushPending = false;
|
|
1253
|
-
let budget = 100000;
|
|
1254
|
-
for (const observer of __velarGraphSetItems(__velarRuntime.domQueue)) {
|
|
1255
|
-
__velarGraphSetRemove(__velarRuntime.domQueue, observer);
|
|
1256
|
-
if ((budget -= 1) < 0) { __velarFlushOverflow(); return; }
|
|
1257
|
-
observer.run();
|
|
1258
|
-
}
|
|
1259
|
-
for (const observer of __velarGraphSetItems(__velarRuntime.watchQueue)) {
|
|
1260
|
-
__velarGraphSetRemove(__velarRuntime.watchQueue, observer);
|
|
1261
|
-
if ((budget -= 1) < 0) { __velarFlushOverflow(); return; }
|
|
1262
|
-
observer.run();
|
|
1263
|
-
}
|
|
1264
|
-
if (__velarGraphSetCount(__velarRuntime.domQueue) || __velarGraphSetCount(__velarRuntime.watchQueue)) __velarScheduleFlush();
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
function __velarScheduleFlush() {
|
|
1268
|
-
if (!__velarRuntime.flushPending) { __velarRuntime.flushPending = true; __velarEnqueue(__velarFlush); }
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
1322
|
function __velarTrack(subscribers) {
|
|
1272
1323
|
__velarRuntime.trackSubscribers(subscribers);
|
|
1273
1324
|
}
|
|
@@ -1289,7 +1340,12 @@ function __velarCleanupObserver(observer) {
|
|
|
1289
1340
|
__velarRuntime.cleanupObserver(observer);
|
|
1290
1341
|
}
|
|
1291
1342
|
|
|
1292
|
-
|
|
1343
|
+
// D90 R21: "label" names the observer in a report -- a watch carries its
|
|
1344
|
+
// subject as the author spelled it -- and "sequence" is its registration
|
|
1345
|
+
// number, which is the order the flush runs the watch tier in. The counter is
|
|
1346
|
+
// application-wide rather than per module, so two modules' watches order by the
|
|
1347
|
+
// order the two modules initialized.
|
|
1348
|
+
function __velarObserver(read, mode, scope, label = "") {
|
|
1293
1349
|
// The first run of a DOM observer executes while its JSX position is being
|
|
1294
1350
|
// constructed, and construction is transactional: the failure must reach
|
|
1295
1351
|
// the surrounding owner (the mount transaction at the root, the containing
|
|
@@ -1299,6 +1355,9 @@ function __velarObserver(read, mode, scope) {
|
|
|
1299
1355
|
let initial = mode === "dom";
|
|
1300
1356
|
const observer = {
|
|
1301
1357
|
mode,
|
|
1358
|
+
label,
|
|
1359
|
+
sequence: __velarNextObserverSequence(),
|
|
1360
|
+
component: scope !== null && scope !== undefined && typeof scope.component === "string" ? scope.component : "",
|
|
1302
1361
|
stopped: false,
|
|
1303
1362
|
running: false,
|
|
1304
1363
|
selfInvalidations: 0,
|
|
@@ -1328,7 +1387,12 @@ function __velarObserver(read, mode, scope) {
|
|
|
1328
1387
|
} else {
|
|
1329
1388
|
observer.selfInvalidations = 0;
|
|
1330
1389
|
}
|
|
1331
|
-
|
|
1390
|
+
// The registry owns the one scheduler: the emitted prelude used to carry
|
|
1391
|
+
// its own copy of the queue insert and the flush drain, and the two
|
|
1392
|
+
// definitions of one concept is what let a watch be classified three
|
|
1393
|
+
// different ways. The capability observers in packages/web/src/runtime.ts
|
|
1394
|
+
// have always scheduled this way.
|
|
1395
|
+
__velarRuntime.schedule(observer);
|
|
1332
1396
|
},
|
|
1333
1397
|
stop() { observer.stopped = true; __velarCleanupObserver(observer); },
|
|
1334
1398
|
};
|
|
@@ -1374,8 +1438,8 @@ function __velarSetupEnd(value) {
|
|
|
1374
1438
|
}
|
|
1375
1439
|
|
|
1376
1440
|
// The framework's own reads on the author's behalf -- checking a prop is
|
|
1377
|
-
// present,
|
|
1378
|
-
//
|
|
1441
|
+
// present, building the content a children slot holds, reading a handler
|
|
1442
|
+
// thunk. None of them freezes anything the author wrote: the
|
|
1379
1443
|
// prop handle behind them stays live. Reporting them would be the false-positive
|
|
1380
1444
|
// flood D70 rule 180 rejected, in its most literal form.
|
|
1381
1445
|
function __velarInternalRead(read) {
|
|
@@ -1459,10 +1523,21 @@ function __velarFrozenText(value) {
|
|
|
1459
1523
|
return typeof value === "string" ? __velarQuotedText(value) : __velarDomString(value);
|
|
1460
1524
|
}
|
|
1461
1525
|
|
|
1462
|
-
|
|
1526
|
+
// "name" is the declared name an author "state" wrote, carried on the cell and
|
|
1527
|
+
// visible in the emitted source. The cells __velarResource and __velarAction
|
|
1528
|
+
// build for their own pending/error fields are created without one, because
|
|
1529
|
+
// they are the runtime's bookkeeping rather than state anyone declared.
|
|
1530
|
+
//
|
|
1531
|
+
// D90 R21: nothing reads it any more. It existed for the two watch referees --
|
|
1532
|
+
// only a cell with a declared name could be a declared write target -- and they
|
|
1533
|
+
// are gone with the clause. It is left in place rather than removed with them:
|
|
1534
|
+
// it is a fact about the cell, not a mechanism, and it is what a report naming
|
|
1535
|
+
// the state a runaway wrote would have to read.
|
|
1536
|
+
function __velarState(initial, name) {
|
|
1463
1537
|
let value = __velarToRaw(initial);
|
|
1464
1538
|
const subscribers = __velarGraphCreateSet();
|
|
1465
1539
|
const cell = {
|
|
1540
|
+
velarStateName: name,
|
|
1466
1541
|
// Named rather than shorthand so a captured stack shows a compiler frame
|
|
1467
1542
|
// here: D70's report walks past its own frames to find the reading line,
|
|
1468
1543
|
// and every JavaScript engine spells an anonymous getter differently.
|
|
@@ -1492,8 +1567,8 @@ function __velarState(initial) {
|
|
|
1492
1567
|
}
|
|
1493
1568
|
|
|
1494
1569
|
// D71 rule 182: a declared derived value reads bare, so it presents the same
|
|
1495
|
-
// .get() face a state cell does. The cache underneath is the
|
|
1496
|
-
//
|
|
1570
|
+
// .get() face a state cell does. The cache underneath is the runtime's own
|
|
1571
|
+
// memo; only the face it is read through differs.
|
|
1497
1572
|
function __velarComputed(read) {
|
|
1498
1573
|
const access = __velarRuntime.computed(read);
|
|
1499
1574
|
if (!__velarFrozenHooks) return __velarGraphFreeze({ get: access });
|
|
@@ -1658,19 +1733,31 @@ function __velarCleanupStep(run, scope) {
|
|
|
1658
1733
|
} catch (error) { __velarReport(error, "cleanup", scope); }
|
|
1659
1734
|
}
|
|
1660
1735
|
|
|
1661
|
-
|
|
1736
|
+
// D90 R21: nothing here asks whether this watch writes. Execution order is
|
|
1737
|
+
// source order, so the only thing the flush needs of a watch is when it was
|
|
1738
|
+
// registered, and __velarObserver stamps that on it. The label is the subject
|
|
1739
|
+
// as the author spelled it, carried so a runaway flush can name the watches
|
|
1740
|
+
// that ran away.
|
|
1741
|
+
function __velarWatch(read, callback, scope, label = "") {
|
|
1662
1742
|
let current;
|
|
1663
1743
|
let currentVersion = 0;
|
|
1664
1744
|
let initialized = false;
|
|
1665
|
-
|
|
1745
|
+
let observer = null;
|
|
1746
|
+
observer = __velarObserver(() => {
|
|
1666
1747
|
const next = read();
|
|
1667
1748
|
__velarRuntime.trackDeep(next);
|
|
1668
1749
|
const nextVersion = __velarRuntime.versionOf(next);
|
|
1669
|
-
if (initialized && (!__velarGraphSame(next, current) || nextVersion !== currentVersion))
|
|
1750
|
+
if (initialized && (!__velarGraphSame(next, current) || nextVersion !== currentVersion)) {
|
|
1751
|
+
// The body is the author's effect, not part of the watched expression:
|
|
1752
|
+
// its own reactive reads must never become dependencies of what is being
|
|
1753
|
+
// watched, or one write re-evaluates the expression twice and an
|
|
1754
|
+
// unwatched value re-runs the watch.
|
|
1755
|
+
__velarUntracked(() => callback(next, current));
|
|
1756
|
+
}
|
|
1670
1757
|
current = next;
|
|
1671
1758
|
currentVersion = nextVersion;
|
|
1672
1759
|
initialized = true;
|
|
1673
|
-
}, "watch", scope);
|
|
1760
|
+
}, "watch", scope, label);
|
|
1674
1761
|
}
|
|
1675
1762
|
|
|
1676
1763
|
function __velarComponentHandle(value, componentName) {
|
|
@@ -1702,6 +1789,15 @@ function __velarComponent(node, scope, mounted, cleanup, handleState) {
|
|
|
1702
1789
|
let destroyed = false;
|
|
1703
1790
|
const refCleanups = [];
|
|
1704
1791
|
const ownedNodes = node && __velarDomNodeType(node) === 11 ? __velarDomChildNodes(node) : [node];
|
|
1792
|
+
// An enclosing component forwards to a nested component's host only when that
|
|
1793
|
+
// component's root sits at its own root level. Marking the nodes here is what
|
|
1794
|
+
// lets the enclosing scan walk past a nested host buried inside one of its
|
|
1795
|
+
// own elements instead of counting it as a second host of its own.
|
|
1796
|
+
for (let index = 0; index < ownedNodes.length; index += 1) {
|
|
1797
|
+
if (ownedNodes[index] && __velarDomNodeType(ownedNodes[index]) === 1) {
|
|
1798
|
+
__velarGraphDefine(ownedNodes[index], "__velarComponentNode", { value: true, enumerable: true, configurable: true });
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1705
1801
|
if (mounted) __velarAppendOwned(scope.mounts, mounted);
|
|
1706
1802
|
return {
|
|
1707
1803
|
__velarComponent: true,
|
|
@@ -1871,6 +1967,17 @@ function __velarAppend(parent, value, state = null) {
|
|
|
1871
1967
|
throw new TypeError("JSX can render only text, finite numbers, bool, enums, WebNode values, and Lists of those values");
|
|
1872
1968
|
}
|
|
1873
1969
|
|
|
1970
|
+
// The rendering region currently being constructed. A 'children' slot is built
|
|
1971
|
+
// by the position that shows it and must be destroyed with it, so the slot is
|
|
1972
|
+
// owned by this scope rather than by the caller's component scope, which would
|
|
1973
|
+
// keep every hidden build's observers alive for the component's whole lifetime.
|
|
1974
|
+
let __velarBuildScope = null;
|
|
1975
|
+
|
|
1976
|
+
function __velarChildrenNode(build, scope) {
|
|
1977
|
+
const owner = __velarBuildScope === null ? scope : __velarBuildScope;
|
|
1978
|
+
return __velarInternalRead(() => build(owner));
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1874
1981
|
function __velarDynamic(parent, read, scope, rootState = null) {
|
|
1875
1982
|
const start = __velarDomCreateComment("velar:start");
|
|
1876
1983
|
const end = __velarDomCreateComment("velar:end");
|
|
@@ -1882,11 +1989,14 @@ function __velarDynamic(parent, read, scope, rootState = null) {
|
|
|
1882
1989
|
const nextScope = __velarScope(scope.component);
|
|
1883
1990
|
const fragment = __velarDomCreateFragment();
|
|
1884
1991
|
let nextHost = null;
|
|
1992
|
+
const previousBuildScope = __velarBuildScope;
|
|
1993
|
+
__velarBuildScope = nextScope;
|
|
1885
1994
|
try {
|
|
1886
1995
|
__velarAppend(fragment, read(nextScope));
|
|
1887
1996
|
if (rootState) nextHost = __velarRootHost(fragment, "dynamic component");
|
|
1888
1997
|
}
|
|
1889
1998
|
catch (error) { __velarDestroyScope(nextScope); throw error; }
|
|
1999
|
+
finally { __velarBuildScope = previousBuildScope; }
|
|
1890
2000
|
const nextNodes = __velarDomChildNodes(fragment);
|
|
1891
2001
|
if (childScope) __velarDestroyScope(childScope);
|
|
1892
2002
|
for (let index = 0; index < nodes.length; index += 1) __velarDomRemove(nodes[index]);
|
|
@@ -1943,9 +2053,25 @@ function __velarKeyed(parent, read, keyOf, render, scope) {
|
|
|
1943
2053
|
if (!entry) {
|
|
1944
2054
|
const childScope = __velarScope(scope.component);
|
|
1945
2055
|
const fragment = __velarDomCreateFragment();
|
|
2056
|
+
const previousBuildScope = __velarBuildScope;
|
|
2057
|
+
__velarBuildScope = childScope;
|
|
1946
2058
|
try { __velarAppend(fragment, render(trackedValue, childScope)); }
|
|
1947
2059
|
catch (error) { __velarDestroyScope(childScope); throw error; }
|
|
1948
|
-
|
|
2060
|
+
finally { __velarBuildScope = previousBuildScope; }
|
|
2061
|
+
// A row is held by its first and last top-level nodes, never by a
|
|
2062
|
+
// snapshot of the list between them: every dynamic construct brackets
|
|
2063
|
+
// itself with comments created once, so those two nodes are stable
|
|
2064
|
+
// while everything between them is replaced over time. Caching the
|
|
2065
|
+
// whole list put destroyed nodes back into the document on a later
|
|
2066
|
+
// reorder and stranded the live ones outside their own markers.
|
|
2067
|
+
const rowNodes = __velarDomChildNodes(fragment);
|
|
2068
|
+
entry = {
|
|
2069
|
+
value: rawValue,
|
|
2070
|
+
scope: childScope,
|
|
2071
|
+
first: rowNodes.length > 0 ? rowNodes[0] : null,
|
|
2072
|
+
last: rowNodes.length > 0 ? rowNodes[rowNodes.length - 1] : null,
|
|
2073
|
+
fragment,
|
|
2074
|
+
};
|
|
1949
2075
|
created[created.length] = entry;
|
|
1950
2076
|
}
|
|
1951
2077
|
__velarGraphMapWrite(next, key, entry);
|
|
@@ -1958,7 +2084,13 @@ function __velarKeyed(parent, read, keyOf, render, scope) {
|
|
|
1958
2084
|
const entry = __velarGraphMapRead(entries, key);
|
|
1959
2085
|
if (__velarGraphMapRead(next, key) === entry) continue;
|
|
1960
2086
|
__velarDestroyScope(entry.scope);
|
|
1961
|
-
|
|
2087
|
+
let node = entry.first;
|
|
2088
|
+
while (node !== null && node !== end) {
|
|
2089
|
+
const following = __velarDomNextSibling(node);
|
|
2090
|
+
__velarDomRemove(node);
|
|
2091
|
+
if (node === entry.last) break;
|
|
2092
|
+
node = following;
|
|
2093
|
+
}
|
|
1962
2094
|
}
|
|
1963
2095
|
// A row already standing in its final position must not be detached and
|
|
1964
2096
|
// reattached: that moves focus off a live <input>, ends IME composition,
|
|
@@ -1972,10 +2104,13 @@ function __velarKeyed(parent, read, keyOf, render, scope) {
|
|
|
1972
2104
|
if (scope.mounted) __velarMountScope(entry.scope);
|
|
1973
2105
|
continue;
|
|
1974
2106
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
2107
|
+
let node = entry.first;
|
|
2108
|
+
while (node !== null && node !== end) {
|
|
2109
|
+
const following = __velarDomNextSibling(node);
|
|
1977
2110
|
if (node === cursor) cursor = __velarDomNextSibling(cursor);
|
|
1978
2111
|
else __velarDomBefore(cursor === null ? end : cursor, node);
|
|
2112
|
+
if (node === entry.last) break;
|
|
2113
|
+
node = following;
|
|
1979
2114
|
}
|
|
1980
2115
|
}
|
|
1981
2116
|
entries = next;
|
|
@@ -2011,6 +2146,55 @@ function __velarAttr(element, name, read, scope) {
|
|
|
2011
2146
|
}, "dom", scope);
|
|
2012
2147
|
}
|
|
2013
2148
|
|
|
2149
|
+
// The attributes whose value the user agent navigates or fetches. For these the
|
|
2150
|
+
// scheme is part of what the value means, so it is checked; every other
|
|
2151
|
+
// attribute takes its text unchanged.
|
|
2152
|
+
const __velarUrlAttributes = ["href", "src", "action", "formaction", "poster", "data", "xlink:href", "ping", "cite"];
|
|
2153
|
+
const __velarUrlSchemes = ["http", "https", "mailto", "tel", "blob"];
|
|
2154
|
+
// 'data:' carries its own payload, so it is admitted only for media types the
|
|
2155
|
+
// user agent cannot execute. 'image/svg+xml' is deliberately absent: an SVG
|
|
2156
|
+
// document can carry script.
|
|
2157
|
+
const __velarInertDataTypes = [
|
|
2158
|
+
"image/png", "image/jpeg", "image/gif", "image/webp", "image/avif", "image/bmp", "image/x-icon",
|
|
2159
|
+
"video/mp4", "video/webm", "video/ogg", "audio/mpeg", "audio/ogg", "audio/wav", "audio/webm",
|
|
2160
|
+
"font/woff", "font/woff2", "text/plain",
|
|
2161
|
+
];
|
|
2162
|
+
|
|
2163
|
+
// 'javascript:' and 'vbscript:' are code, not locations. A value that arrived as
|
|
2164
|
+
// data must never become code because it reached an href, so an unknown scheme
|
|
2165
|
+
// is refused rather than passed through.
|
|
2166
|
+
function __velarUrlAttributeValue(value, name) {
|
|
2167
|
+
let scheme = "";
|
|
2168
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
2169
|
+
const code = value.charCodeAt(index);
|
|
2170
|
+
// The user agent strips ASCII whitespace and control characters before it
|
|
2171
|
+
// parses the scheme, so "java\tscript:" reads as "javascript:" to it and
|
|
2172
|
+
// has to read that way here too.
|
|
2173
|
+
if (code <= 0x20 || code === 0x7f) continue;
|
|
2174
|
+
if (code === 58 && scheme.length > 0) {
|
|
2175
|
+
const lowered = scheme.toLowerCase();
|
|
2176
|
+
if (lowered === "data") {
|
|
2177
|
+
const payload = value.slice(index + 1).toLowerCase();
|
|
2178
|
+
for (let type = 0; type < __velarInertDataTypes.length; type += 1) {
|
|
2179
|
+
if (payload.startsWith(__velarInertDataTypes[type])) return value;
|
|
2180
|
+
}
|
|
2181
|
+
throw new TypeError("JSX attribute '" + name + "' rejected a 'data:' URL whose media type is not a known inert one");
|
|
2182
|
+
}
|
|
2183
|
+
if (__velarHasName(__velarUrlSchemes, lowered)) return value;
|
|
2184
|
+
throw new TypeError("JSX attribute '" + name + "' rejected the '" + lowered + ":' URL scheme");
|
|
2185
|
+
}
|
|
2186
|
+
const letter = (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
|
|
2187
|
+
if (letter || (scheme.length > 0 && ((code >= 48 && code <= 57) || code === 43 || code === 45 || code === 46))) {
|
|
2188
|
+
scheme += value[index];
|
|
2189
|
+
continue;
|
|
2190
|
+
}
|
|
2191
|
+
// Anything else this early means the value names no scheme: it is a
|
|
2192
|
+
// relative URL, which is always the application's own origin.
|
|
2193
|
+
return value;
|
|
2194
|
+
}
|
|
2195
|
+
return value;
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2014
2198
|
function __velarAttributeValue(value, name) {
|
|
2015
2199
|
if (value === true) return __velarAriaState(name) ? "true" : "";
|
|
2016
2200
|
if (typeof value === "number") {
|
|
@@ -2019,6 +2203,7 @@ function __velarAttributeValue(value, name) {
|
|
|
2019
2203
|
}
|
|
2020
2204
|
if (typeof value !== "string") throw new TypeError("JSX attribute '" + name + "' requires text, a finite number, bool, an enum, or null");
|
|
2021
2205
|
if (value.length > 1024 * 1024) throw new RangeError("JSX attribute '" + name + "' cannot exceed 1 MiB");
|
|
2206
|
+
if (__velarHasName(__velarUrlAttributes, name)) return __velarUrlAttributeValue(value, name);
|
|
2022
2207
|
return value;
|
|
2023
2208
|
}
|
|
2024
2209
|
|
|
@@ -2048,23 +2233,86 @@ function __velarClass(element, name, read, scope) {
|
|
|
2048
2233
|
__velarClassBind(element, () => read() ? name : null, scope);
|
|
2049
2234
|
}
|
|
2050
2235
|
|
|
2051
|
-
|
|
2236
|
+
// The property a token carries. A token is '[target:]conditions:property' and
|
|
2237
|
+
// a CSS property name never contains ':', so the last segment is the property.
|
|
2238
|
+
function __velarLookProperty(token) {
|
|
2239
|
+
return token.slice(token.lastIndexOf(":") + 1);
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
// The surface a token writes on: its pseudo-element target, if it has one, plus
|
|
2243
|
+
// its property. Composition overrides a property on the surface that owns it —
|
|
2244
|
+
// '@before: content' and a bare 'content' are two surfaces, not one property
|
|
2245
|
+
// written under two conditions.
|
|
2246
|
+
function __velarLookSurface(token) {
|
|
2247
|
+
const first = token.indexOf(":");
|
|
2248
|
+
const last = token.lastIndexOf(":");
|
|
2249
|
+
return (first === last ? "" : token.slice(0, first + 1)) + token.slice(last + 1);
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
// Whether a token declares its property unconditionally: an unconditional
|
|
2253
|
+
// declaration spells its condition segment 'base', so the segment in front of
|
|
2254
|
+
// the property is what says a token stands on its own.
|
|
2255
|
+
function __velarLookUnconditional(token) {
|
|
2256
|
+
const last = token.lastIndexOf(":");
|
|
2257
|
+
return token.slice(token.lastIndexOf(":", last - 1) + 1, last) === "base";
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
// Composition is property-level across sources and token-level inside one look
|
|
2261
|
+
// block. A later unconditional declaration wins its property outright, so it
|
|
2262
|
+
// drops every condition the earlier sources wrote it under: the caller of a
|
|
2263
|
+
// component could otherwise not reach a padding the component set behind its
|
|
2264
|
+
// own private breakpoint, and 'the caller wins every property both of them set'
|
|
2265
|
+
// was true only under the identical condition (LOK-U10). A later conditional
|
|
2266
|
+
// declaration refines rather than replaces — it overwrites the identical token
|
|
2267
|
+
// and leaves the earlier unconditional value standing, because a caller that
|
|
2268
|
+
// writes only 'if @hover: color' never mentioned the resting colour and must
|
|
2269
|
+
// not delete it. Declarations written in one block are one source, so a block's
|
|
2270
|
+
// own 'if scheme.dark:' and 'if @hover:' still coexist and the cascade decides
|
|
2271
|
+
// between them.
|
|
2272
|
+
function __velarMergeRules(rules, groups, source, group) {
|
|
2052
2273
|
const names = __velarGraphOwnNames(source);
|
|
2274
|
+
const owned = __velarGraphOwnNames(rules);
|
|
2053
2275
|
for (let index = 0; index < names.length; index += 1) {
|
|
2054
2276
|
const descriptor = __velarGraphOwnDescriptor(source, names[index]);
|
|
2055
2277
|
if (!descriptor || !("value" in descriptor)) continue;
|
|
2056
|
-
if (
|
|
2057
|
-
|
|
2278
|
+
if (!__velarLookUnconditional(names[index])) continue;
|
|
2279
|
+
const surface = __velarLookSurface(names[index]);
|
|
2280
|
+
for (let other = 0; other < owned.length; other += 1) {
|
|
2281
|
+
const token = owned[other];
|
|
2282
|
+
if (groups[token] === group || __velarLookSurface(token) !== surface) continue;
|
|
2283
|
+
delete rules[token];
|
|
2284
|
+
delete groups[token];
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
for (let index = 0; index < names.length; index += 1) {
|
|
2288
|
+
const descriptor = __velarGraphOwnDescriptor(source, names[index]);
|
|
2289
|
+
if (!descriptor || !("value" in descriptor)) continue;
|
|
2290
|
+
if (descriptor.value == null) { delete rules[names[index]]; delete groups[names[index]]; continue; }
|
|
2291
|
+
rules[names[index]] = descriptor.value;
|
|
2292
|
+
groups[names[index]] = group;
|
|
2058
2293
|
}
|
|
2059
2294
|
}
|
|
2060
2295
|
|
|
2061
2296
|
function __velarLook(parts) {
|
|
2062
2297
|
const rules = __velarGraphCreateRecord();
|
|
2298
|
+
const groups = __velarGraphCreateRecord();
|
|
2299
|
+
// A built Look is one source. A bare rules record is a run of declarations
|
|
2300
|
+
// written in one look block, so consecutive ones share a source and a spread
|
|
2301
|
+
// between them opens the next.
|
|
2302
|
+
let group = 0;
|
|
2303
|
+
let run = 0;
|
|
2063
2304
|
const add = (part) => {
|
|
2064
2305
|
if (part == null || part === false) return;
|
|
2065
2306
|
if (__velarGraphIsList(part)) { for (let index = 0; index < part.length; index += 1) add(part[index]); return; }
|
|
2066
|
-
if (part.__velarLook === true
|
|
2067
|
-
|
|
2307
|
+
if (part.__velarLook === true) {
|
|
2308
|
+
group += 1;
|
|
2309
|
+
run = 0;
|
|
2310
|
+
__velarMergeRules(rules, groups, part.rules, group);
|
|
2311
|
+
return;
|
|
2312
|
+
}
|
|
2313
|
+
if (part.rules && typeof part.rules === "object") {
|
|
2314
|
+
if (run === 0) { group += 1; run = group; }
|
|
2315
|
+
__velarMergeRules(rules, groups, part.rules, run);
|
|
2068
2316
|
return;
|
|
2069
2317
|
}
|
|
2070
2318
|
throw new TypeError("look composition accepts only Look, Look?, or lists of Look values");
|
|
@@ -2074,7 +2322,7 @@ function __velarLook(parts) {
|
|
|
2074
2322
|
}
|
|
2075
2323
|
|
|
2076
2324
|
function __velarKeyframesValue(name) {
|
|
2077
|
-
if (typeof name !== "string" || !/^velar-kf-[0-9a-f]{8}$/.test(name)) throw new TypeError("Generated keyframes name is invalid");
|
|
2325
|
+
if (typeof name !== "string" || !/^velar-kf-[0-9a-f]{8,32}$/.test(name)) throw new TypeError("Generated keyframes name is invalid");
|
|
2078
2326
|
return __velarGraphFreeze({ __velarKeyframes: true, name });
|
|
2079
2327
|
}
|
|
2080
2328
|
|
|
@@ -2090,7 +2338,17 @@ function __velarLookValue(token, value) {
|
|
|
2090
2338
|
}
|
|
2091
2339
|
if (typeof value !== "string") throw new TypeError("Look properties require text, finite numbers, typed visual values, or null");
|
|
2092
2340
|
if (value.length > 1024 * 1024) throw new RangeError("A Look property value cannot exceed 1 MiB");
|
|
2093
|
-
if (token.endsWith(":content") && typeof value === "string" && value !== "none" && value !== "normal") return
|
|
2341
|
+
if (token.endsWith(":content") && typeof value === "string" && value !== "none" && value !== "normal") return __velarCssString(value);
|
|
2342
|
+
// A closed keyword set is the property's whole string vocabulary, so a value
|
|
2343
|
+
// the compiler could not read — a prop, a record field, a call result — is
|
|
2344
|
+
// checked here instead. Without this a 'display' bound to "grdi" reached the
|
|
2345
|
+
// browser as a declaration it silently discards (LOK-U14).
|
|
2346
|
+
const keywords = __velarLookKeywords[__velarLookProperty(token)];
|
|
2347
|
+
if (keywords !== undefined && !__velarHasName(keywords, value.trim())) {
|
|
2348
|
+
let expected = "";
|
|
2349
|
+
for (let index = 0; index < keywords.length; index += 1) expected += (expected === "" ? "" : ", ") + keywords[index];
|
|
2350
|
+
throw new TypeError("Look property '" + __velarLookProperty(token) + "' does not accept '" + value + "'; use one of " + expected);
|
|
2351
|
+
}
|
|
2094
2352
|
return value;
|
|
2095
2353
|
}
|
|
2096
2354
|
|
|
@@ -2279,33 +2537,80 @@ function __velarElementState(element, name, create) {
|
|
|
2279
2537
|
function __velarApplyLooks(element) {
|
|
2280
2538
|
const sources = __velarGraphWeakMapRead(__velarRuntime.lookSources, element);
|
|
2281
2539
|
const merged = __velarGraphCreateRecord();
|
|
2540
|
+
const groups = __velarGraphCreateRecord();
|
|
2282
2541
|
if (sources) {
|
|
2542
|
+
// Each attached source is one Look, so a source that sets a property
|
|
2543
|
+
// unconditionally drops the property's other conditions from the sources
|
|
2544
|
+
// before it: the caller of a component composes after the component's own
|
|
2545
|
+
// host look and wins every property both of them set, whatever condition
|
|
2546
|
+
// either wrote it under. A source that writes the property only under a
|
|
2547
|
+
// condition refines that condition alone — the identical token is
|
|
2548
|
+
// overwritten and the earlier unconditional value stays, so the caller does
|
|
2549
|
+
// not delete a resting value it never mentioned.
|
|
2550
|
+
//
|
|
2283
2551
|
// A null rule keeps its token: the token drives the generated selector and
|
|
2284
2552
|
// only the custom property behind it disappears.
|
|
2553
|
+
let group = 0;
|
|
2285
2554
|
for (const source of __velarGraphSetItems(sources)) {
|
|
2555
|
+
group += 1;
|
|
2286
2556
|
const names = __velarGraphOwnNames(source.rules);
|
|
2557
|
+
const owned = __velarGraphOwnNames(merged);
|
|
2558
|
+
for (let index = 0; index < names.length; index += 1) {
|
|
2559
|
+
// A rule the assignment pass below refuses to read must not clear the
|
|
2560
|
+
// earlier sources either, or the property disappears with nothing put
|
|
2561
|
+
// in its place — the same 'a source deletes a value it never replaces'
|
|
2562
|
+
// shape the conditional guard above closes. '__velarMergeRules' spells
|
|
2563
|
+
// the test the same way.
|
|
2564
|
+
const descriptor = __velarGraphOwnDescriptor(source.rules, names[index]);
|
|
2565
|
+
if (!descriptor || !("value" in descriptor)) continue;
|
|
2566
|
+
if (!__velarLookUnconditional(names[index])) continue;
|
|
2567
|
+
const surface = __velarLookSurface(names[index]);
|
|
2568
|
+
for (let other = 0; other < owned.length; other += 1) {
|
|
2569
|
+
if (groups[owned[other]] === group || __velarLookSurface(owned[other]) !== surface) continue;
|
|
2570
|
+
delete merged[owned[other]];
|
|
2571
|
+
delete groups[owned[other]];
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2287
2574
|
for (let index = 0; index < names.length; index += 1) {
|
|
2288
2575
|
const descriptor = __velarGraphOwnDescriptor(source.rules, names[index]);
|
|
2289
|
-
if (descriptor
|
|
2576
|
+
if (!descriptor || !("value" in descriptor)) continue;
|
|
2577
|
+
merged[names[index]] = descriptor.value;
|
|
2578
|
+
groups[names[index]] = group;
|
|
2290
2579
|
}
|
|
2291
2580
|
}
|
|
2292
2581
|
}
|
|
2293
|
-
|
|
2582
|
+
// The last value written per token, so a reactive change to one dynamic
|
|
2583
|
+
// property costs one DOM write instead of one per token plus a full attribute
|
|
2584
|
+
// rewrite (LOK-U15).
|
|
2585
|
+
const state = __velarElementState(element, "__velarLookTokens", () => __velarGraphFreeze({
|
|
2586
|
+
tokens: __velarGraphCreateSet(), written: { record: __velarGraphCreateRecord(), attribute: undefined },
|
|
2587
|
+
}));
|
|
2588
|
+
const written = state.written.record;
|
|
2294
2589
|
const tokens = __velarGraphOwnNames(merged);
|
|
2295
2590
|
const next = __velarGraphCreateSet(tokens);
|
|
2296
2591
|
for (const token of __velarGraphSetItems(state.tokens)) {
|
|
2297
|
-
if (
|
|
2592
|
+
if (__velarGraphSetContains(next, token)) continue;
|
|
2593
|
+
__velarDomStyleClear(element, __velarLookVariable(token));
|
|
2594
|
+
delete written[token];
|
|
2298
2595
|
}
|
|
2299
2596
|
let attribute = "";
|
|
2300
2597
|
for (let index = 0; index < tokens.length; index += 1) {
|
|
2301
2598
|
const token = tokens[index];
|
|
2302
2599
|
const value = __velarGraphOwnDescriptor(merged, token)?.value;
|
|
2303
|
-
|
|
2304
|
-
|
|
2600
|
+
const text = value == null ? null : __velarLookValue(token, value);
|
|
2601
|
+
if (!(token in written) || written[token] !== text) {
|
|
2602
|
+
if (text === null) __velarDomStyleClear(element, __velarLookVariable(token));
|
|
2603
|
+
else __velarDomStyleWrite(element, __velarLookVariable(token), text);
|
|
2604
|
+
written[token] = text;
|
|
2605
|
+
}
|
|
2305
2606
|
attribute = attribute === "" ? token : attribute + " " + token;
|
|
2306
2607
|
}
|
|
2307
|
-
|
|
2308
|
-
|
|
2608
|
+
const desired = tokens.length === 0 ? null : attribute;
|
|
2609
|
+
if (state.written.attribute !== desired) {
|
|
2610
|
+
if (desired === null) __velarDomRemoveAttribute(element, "data-velar-look");
|
|
2611
|
+
else __velarDomSetAttribute(element, "data-velar-look", desired);
|
|
2612
|
+
state.written.attribute = desired;
|
|
2613
|
+
}
|
|
2309
2614
|
__velarGraphSetEmpty(state.tokens);
|
|
2310
2615
|
for (let index = 0; index < tokens.length; index += 1) __velarGraphSetInsert(state.tokens, tokens[index]);
|
|
2311
2616
|
}
|
|
@@ -2346,27 +2651,72 @@ function __velarApplyExternalLook(element, value) {
|
|
|
2346
2651
|
|
|
2347
2652
|
__velarRuntime.installLook(__velarApplyExternalLook);
|
|
2348
2653
|
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2654
|
+
// One resolution per root, shared by every capability bound to it: look, class
|
|
2655
|
+
// and style used to pay a fresh O(subtree) walk each.
|
|
2656
|
+
const __velarRootHosts = __velarGraphCreateWeakMap();
|
|
2657
|
+
|
|
2658
|
+
function __velarRootHostResolve(root) {
|
|
2352
2659
|
const elements = [];
|
|
2353
2660
|
const children = __velarDomChildNodes(root);
|
|
2354
2661
|
for (let index = 0; index < children.length; index += 1) {
|
|
2355
2662
|
if (__velarDomNodeType(children[index]) === 1) __velarAppendOwned(elements, children[index]);
|
|
2356
2663
|
}
|
|
2357
2664
|
const explicit = [];
|
|
2665
|
+
const forwarded = [];
|
|
2358
2666
|
for (let index = 0; index < elements.length; index += 1) {
|
|
2359
2667
|
const element = elements[index];
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2668
|
+
// A nested component's own, perfectly legal host is that component's, not
|
|
2669
|
+
// this one's, at the root level exactly as below it. Every root-level node
|
|
2670
|
+
// of a nested component carries the marker and this component's own nodes
|
|
2671
|
+
// do not yet — construction marks them after a caller's look binds — so
|
|
2672
|
+
// the marker is what separates the two. Reading 'host' first counted a
|
|
2673
|
+
// nested component's marked root as a second host of the enclosing one and
|
|
2674
|
+
// collapsed the whole region, which is the buried-host defect standing at
|
|
2675
|
+
// the root level instead of below it. Such a node is not this component's
|
|
2676
|
+
// host but the host this component forwards to when it declares none of
|
|
2677
|
+
// its own, so it is collected apart rather than dropped.
|
|
2678
|
+
if (__velarDomOwnData(element, "__velarComponentNode") === true) {
|
|
2679
|
+
if (__velarDomOwnData(element, "__velarHost") === true) __velarAppendOwned(forwarded, element);
|
|
2680
|
+
continue;
|
|
2681
|
+
}
|
|
2682
|
+
if (__velarDomOwnData(element, "__velarHost") === true) { __velarAppendOwned(explicit, element); continue; }
|
|
2683
|
+
// A walk that refuses to enter a marked node stops at a nested component's
|
|
2684
|
+
// boundary whatever depth its host sits at; a flat 'querySelectorAll' scan
|
|
2685
|
+
// could only skip the marked node itself and still counted a host one level
|
|
2686
|
+
// below it.
|
|
2687
|
+
const pending = [element];
|
|
2688
|
+
for (let cursor = 0; cursor < pending.length; cursor += 1) {
|
|
2689
|
+
const nodes = __velarDomChildNodes(pending[cursor]);
|
|
2690
|
+
for (let child = 0; child < nodes.length; child += 1) {
|
|
2691
|
+
const node = nodes[child];
|
|
2692
|
+
if (__velarDomNodeType(node) !== 1) continue;
|
|
2693
|
+
if (__velarDomOwnData(node, "__velarComponentNode") === true) continue;
|
|
2694
|
+
if (__velarDomOwnData(node, "__velarHost") === true) __velarAppendOwned(explicit, node);
|
|
2695
|
+
__velarAppendOwned(pending, node);
|
|
2696
|
+
}
|
|
2364
2697
|
}
|
|
2365
2698
|
}
|
|
2366
2699
|
if (explicit.length === 1) return explicit[0];
|
|
2367
|
-
if (explicit.length > 1)
|
|
2700
|
+
if (explicit.length > 1) return "A component can declare only one host element";
|
|
2701
|
+
// Nothing of this component's own is marked, so a single nested component
|
|
2702
|
+
// root hands its host on. Two of them leave nothing to forward to, and the
|
|
2703
|
+
// component has to mark a native element of its own to settle it.
|
|
2704
|
+
if (forwarded.length === 1) return forwarded[0];
|
|
2705
|
+
if (forwarded.length > 1) return "A component with multiple roots must mark exactly one native element with 'host'";
|
|
2368
2706
|
if (elements.length === 1) return elements[0];
|
|
2369
|
-
|
|
2707
|
+
return "A component with multiple roots must mark exactly one native element with 'host'";
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
function __velarRootHost(root, capability) {
|
|
2711
|
+
if (root == null) throw new TypeError("A component with multiple roots must mark exactly one native element with 'host'");
|
|
2712
|
+
if (__velarDomNodeType(root) === 1) return root;
|
|
2713
|
+
let resolved = __velarGraphWeakMapRead(__velarRootHosts, root);
|
|
2714
|
+
if (resolved === undefined) {
|
|
2715
|
+
resolved = __velarRootHostResolve(root);
|
|
2716
|
+
__velarGraphWeakMapWrite(__velarRootHosts, root, resolved);
|
|
2717
|
+
}
|
|
2718
|
+
if (typeof resolved === "string") throw new TypeError(resolved);
|
|
2719
|
+
return resolved;
|
|
2370
2720
|
}
|
|
2371
2721
|
|
|
2372
2722
|
function __velarLookBindRoot(root, read, scope) {
|
|
@@ -2494,7 +2844,13 @@ function __velarHtml(element, read, scope) {
|
|
|
2494
2844
|
function __velarOn(element, event, read, scope, modifiers = []) {
|
|
2495
2845
|
if (typeof __velarInternalRead(read) !== "function") throw new TypeError("Event '" + event + "' requires a function");
|
|
2496
2846
|
const capture = __velarHasName(modifiers, "capture");
|
|
2497
|
-
const
|
|
2847
|
+
const once = __velarHasName(modifiers, "once");
|
|
2848
|
+
let removed = false;
|
|
2849
|
+
const remove = () => {
|
|
2850
|
+
if (removed) return;
|
|
2851
|
+
removed = true;
|
|
2852
|
+
__velarDomRemoveListener(element, event, listener, capture);
|
|
2853
|
+
};
|
|
2498
2854
|
const listener = (value) => {
|
|
2499
2855
|
try {
|
|
2500
2856
|
if (__velarHasName(modifiers, "self")) {
|
|
@@ -2502,6 +2858,13 @@ function __velarOn(element, event, read, scope, modifiers = []) {
|
|
|
2502
2858
|
if (target === __velarEventMissingField) throw new TypeError("DOM event does not expose a native target");
|
|
2503
2859
|
if (target !== element) return;
|
|
2504
2860
|
}
|
|
2861
|
+
// 'once' is spent here rather than through the native option, so the
|
|
2862
|
+
// documented order -- self, then prevent, then stop, then the handler --
|
|
2863
|
+
// holds for it too. The native option removes the registration on the
|
|
2864
|
+
// first dispatch that merely reaches the element, which for 'self' is
|
|
2865
|
+
// exactly the dispatch the handler must not see: 'on:click.self.once'
|
|
2866
|
+
// could then never run at all.
|
|
2867
|
+
if (once) remove();
|
|
2505
2868
|
if (__velarHasName(modifiers, "prevent")) __velarEventCall(value, "preventDefault", __velarEventPreventDefault);
|
|
2506
2869
|
if (__velarHasName(modifiers, "stop")) __velarEventCall(value, "stopPropagation", __velarEventStopPropagation);
|
|
2507
2870
|
// The handler expression is re-read per dispatch so handlers routed
|
|
@@ -2512,8 +2875,8 @@ function __velarOn(element, event, read, scope, modifiers = []) {
|
|
|
2512
2875
|
__velarObservePromise(result, (error) => __velarReportEvent(error, scope, event));
|
|
2513
2876
|
} catch (error) { __velarReportEvent(error, scope, event); }
|
|
2514
2877
|
};
|
|
2515
|
-
__velarDomAddListener(element, event, listener,
|
|
2516
|
-
__velarAppendOwned(scope.cleanups,
|
|
2878
|
+
__velarDomAddListener(element, event, listener, { capture });
|
|
2879
|
+
__velarAppendOwned(scope.cleanups, remove);
|
|
2517
2880
|
}
|
|
2518
2881
|
|
|
2519
2882
|
function __velarBindValue(element, state, scope, numeric = false, parse = null) {
|
|
@@ -2589,8 +2952,17 @@ function __velarBindChecked(element, state, scope) {
|
|
|
2589
2952
|
// Prop handles give a component body live reads over its props store. The
|
|
2590
2953
|
// component function still runs exactly once per instance; only reads race
|
|
2591
2954
|
// ahead, so state initializers can never re-run on a prop update.
|
|
2955
|
+
function __velarPropProvided(props, name) {
|
|
2956
|
+
// A children slot is rendered content rather than a value thunk. Asking for
|
|
2957
|
+
// its value to decide whether the required slot exists would build the slot
|
|
2958
|
+
// once here and again at the JSX position that owns it. Its own property is
|
|
2959
|
+
// the presence proof; the first value read remains the one rendering read.
|
|
2960
|
+
if (name === "children") return __velarGraphOwnDescriptor(props, name) !== undefined;
|
|
2961
|
+
return __velarInternalRead(() => props[name]) !== undefined;
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2592
2964
|
function __velarRequiredProp(props, name, component) {
|
|
2593
|
-
if (
|
|
2965
|
+
if (!__velarPropProvided(props, name)) throw new TypeError("Component " + component + " requires prop " + name);
|
|
2594
2966
|
return __velarGraphFreeze({
|
|
2595
2967
|
get() {
|
|
2596
2968
|
const value = props[name];
|
|
@@ -2601,7 +2973,7 @@ function __velarRequiredProp(props, name, component) {
|
|
|
2601
2973
|
}
|
|
2602
2974
|
|
|
2603
2975
|
function __velarProp(props, name, fallback) {
|
|
2604
|
-
const fallbackValue =
|
|
2976
|
+
const fallbackValue = __velarPropProvided(props, name) ? undefined : fallback();
|
|
2605
2977
|
return __velarGraphFreeze({
|
|
2606
2978
|
get() {
|
|
2607
2979
|
const value = props[name];
|
|
@@ -2610,10 +2982,6 @@ function __velarProp(props, name, fallback) {
|
|
|
2610
2982
|
});
|
|
2611
2983
|
}
|
|
2612
2984
|
|
|
2613
|
-
// Instantiates a component with a live props store: each prop thunk runs in
|
|
2614
|
-
// its own observer that writes a reactive cell, and the props object exposes
|
|
2615
|
-
// tracked getters over those cells. The component call itself is untracked so
|
|
2616
|
-
// construction can never subscribe an enclosing dynamic region to prop reads.
|
|
2617
2985
|
function __velarBindComponentRef(instance, setRef) {
|
|
2618
2986
|
if (setRef === undefined) return instance;
|
|
2619
2987
|
if (!instance || typeof instance.__bindRef !== "function") throw new TypeError("This component does not expose a Handle");
|
|
@@ -2621,32 +2989,74 @@ function __velarBindComponentRef(instance, setRef) {
|
|
|
2621
2989
|
return instance;
|
|
2622
2990
|
}
|
|
2623
2991
|
|
|
2992
|
+
// Holds every prop's cache open for the length of one construction. The graph
|
|
2993
|
+
// records a dependency for whichever observer is running, so forcing the props
|
|
2994
|
+
// under this one keeps them clean while the component function runs: a read
|
|
2995
|
+
// during construction is served from the value the force produced, and a
|
|
2996
|
+
// tracked read still subscribes the observer that made it. Releasing the hold
|
|
2997
|
+
// hands each prop over to whatever actually observes it, which is the second
|
|
2998
|
+
// half of the rule -- recomputed on demand, cached while observed.
|
|
2999
|
+
function __velarConstructionHold() {
|
|
3000
|
+
return {
|
|
3001
|
+
mode: "computed",
|
|
3002
|
+
stopped: false,
|
|
3003
|
+
running: false,
|
|
3004
|
+
selfInvalidations: 0,
|
|
3005
|
+
dependencies: __velarGraphCreateSet(),
|
|
3006
|
+
spareDependencies: null,
|
|
3007
|
+
notify() {},
|
|
3008
|
+
run() {},
|
|
3009
|
+
};
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
// Instantiates a component with a live props store: each prop is a cached
|
|
3013
|
+
// derived value the props object exposes as a tracked getter, so a prop
|
|
3014
|
+
// expression that nothing reads costs nothing after construction. The component
|
|
3015
|
+
// call itself is untracked so construction can never subscribe an enclosing
|
|
3016
|
+
// dynamic region to prop reads.
|
|
2624
3017
|
function __velarInstantiate(component, thunks, children, scope, namespace, setRef) {
|
|
2625
|
-
if (component != null && component.__velarSnapshotProps === true) {
|
|
2626
|
-
// Runtime-implemented components (Head, Router, Link, NavLink) consume a
|
|
2627
|
-
// one-time plain snapshot so their strict record validation still holds.
|
|
2628
|
-
const snapshot = {};
|
|
2629
|
-
const styleRead = thunks.__velarStyle;
|
|
2630
|
-
const snapshotNames = __velarGraphOwnNames(thunks);
|
|
2631
|
-
for (let index = 0; index < snapshotNames.length; index += 1) {
|
|
2632
|
-
if (snapshotNames[index] !== "__velarStyle") snapshot[snapshotNames[index]] = __velarInternalRead(thunks[snapshotNames[index]]);
|
|
2633
|
-
}
|
|
2634
|
-
if (children !== undefined) snapshot.children = __velarInternalRead(children);
|
|
2635
|
-
const instance = __velarUntracked(() => component(snapshot, namespace));
|
|
2636
|
-
if (styleRead !== undefined) __velarStyleBindRoot(instance.node, styleRead, scope);
|
|
2637
|
-
return __velarBindComponentRef(instance, setRef);
|
|
2638
|
-
}
|
|
2639
3018
|
const props = {};
|
|
3019
|
+
// 'style:' on a component host is the caller decorating the instance's root,
|
|
3020
|
+
// the way it decorates a native element. The slot the compiler inserts for it
|
|
3021
|
+
// is not a field the component declares, so it is bound here rather than
|
|
3022
|
+
// handed inward -- a component the runtime implements validates its props
|
|
3023
|
+
// against the fields it does declare and would refuse an unknown one.
|
|
3024
|
+
const styleRead = thunks.__velarStyle;
|
|
2640
3025
|
const propNames = __velarGraphOwnNames(thunks);
|
|
3026
|
+
const accesses = [];
|
|
2641
3027
|
for (let index = 0; index < propNames.length; index += 1) {
|
|
2642
3028
|
const name = propNames[index];
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
3029
|
+
if (name === "__velarStyle") continue;
|
|
3030
|
+
// A prop is a derived value, not an eagerly pushed one: an unconditional
|
|
3031
|
+
// observer per prop re-ran every prop expression on every dependency
|
|
3032
|
+
// change, including the expensive ones behind props the component never
|
|
3033
|
+
// reads. The cache is the same one a declared derived value uses.
|
|
3034
|
+
const access = __velarComputed(thunks[name]);
|
|
3035
|
+
accesses[accesses.length] = access;
|
|
3036
|
+
__velarGraphDefine(props, name, { enumerable: true, get: () => access.get() });
|
|
2647
3037
|
}
|
|
2648
|
-
|
|
2649
|
-
|
|
3038
|
+
// 'children' follows Vel's ordinary rules: it is rendered content owned by
|
|
3039
|
+
// the position that shows it, rebuilt whenever that position renders again.
|
|
3040
|
+
// As a one-shot fragment value it could be shown exactly once, and the first
|
|
3041
|
+
// time a conditional position hid it the content was destroyed for good.
|
|
3042
|
+
if (children !== undefined) __velarGraphDefine(props, "children", { enumerable: true, get: () => __velarChildrenNode(children, scope) });
|
|
3043
|
+
const instance = __velarUntracked(() => {
|
|
3044
|
+
if (accesses.length === 0) return component(props, namespace);
|
|
3045
|
+
// Component JSX evaluates the way the charter says a JavaScript object
|
|
3046
|
+
// literal does: every prop expression runs once, in the order the caller
|
|
3047
|
+
// wrote it, and only then does the component function run. 'children' is
|
|
3048
|
+
// not one of them -- it is rendered content owned by the position that
|
|
3049
|
+
// shows it, so a position that never shows it never builds it.
|
|
3050
|
+
const hold = __velarConstructionHold();
|
|
3051
|
+
try {
|
|
3052
|
+
__velarRuntime.runTracked(hold, () => {
|
|
3053
|
+
for (let index = 0; index < accesses.length; index += 1) accesses[index].get();
|
|
3054
|
+
});
|
|
3055
|
+
return component(props, namespace);
|
|
3056
|
+
} finally { __velarCleanupObserver(hold); }
|
|
3057
|
+
});
|
|
3058
|
+
if (styleRead !== undefined) __velarStyleBindRoot(instance.node, styleRead, scope);
|
|
3059
|
+
return __velarBindComponentRef(instance, setRef);
|
|
2650
3060
|
}
|
|
2651
3061
|
|
|
2652
3062
|
// A component element in child position: one stable instance whose prop
|
|
@@ -2668,10 +3078,26 @@ function __velarChild(component, thunks, children, scope, namespace, setRef) {
|
|
|
2668
3078
|
}
|
|
2669
3079
|
}
|
|
2670
3080
|
|
|
2671
|
-
|
|
3081
|
+
// tick() promises the settled flush, so it drains the reactive queues instead
|
|
3082
|
+
// of hopping one microtask and hoping. Each round runs the pending flush and
|
|
3083
|
+
// yields, so work an observer queued asynchronously is picked up too; the round
|
|
3084
|
+
// count is bounded for the same reason the flush budget is, and the flush's own
|
|
3085
|
+
// budget is what ends a runaway cycle.
|
|
3086
|
+
function __velarSettledStep() {
|
|
2672
3087
|
return __velarManagedAsyncCreate((resolve) => __velarEnqueue(resolve));
|
|
2673
3088
|
}
|
|
2674
3089
|
|
|
3090
|
+
function __velarSettled(rounds = 10000) {
|
|
3091
|
+
return __velarManagedAsyncThen(__velarSettledStep(), () => {
|
|
3092
|
+
if (__velarRuntime.flushPending) __velarFlush();
|
|
3093
|
+
if (rounds > 1 && (__velarGraphSetCount(__velarRuntime.domQueue)
|
|
3094
|
+
|| __velarGraphSetCount(__velarRuntime.watchQueue) || __velarRuntime.flushPending)) {
|
|
3095
|
+
return __velarSettled(rounds - 1);
|
|
3096
|
+
}
|
|
3097
|
+
return null;
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3100
|
+
|
|
2675
3101
|
function __velarTakeUnhandledFailure() {
|
|
2676
3102
|
for (const failure of __velarGraphSetItems(__velarRuntime.unhandledFailures)) {
|
|
2677
3103
|
__velarGraphSetRemove(__velarRuntime.unhandledFailures, failure);
|
|
@@ -2693,7 +3119,10 @@ function __velarTick() {
|
|
|
2693
3119
|
});
|
|
2694
3120
|
}
|
|
2695
3121
|
`.trim();
|
|
2696
|
-
function webRuntime(foundation) {
|
|
2697
|
-
|
|
3122
|
+
function webRuntime(foundation, lookKeywords) {
|
|
3123
|
+
// `content` is written as a CSS string, which is not a JSON string, so the
|
|
3124
|
+
// runtime carries the serializer css-string.ts publishes rather than a second
|
|
3125
|
+
// spelling of it (LOK-U13).
|
|
3126
|
+
return `${foundation}\n${CSS_STRING_RUNTIME.trim()}\n${lookKeywords}\n${WEB_RUNTIME_BODY}`;
|
|
2698
3127
|
}
|
|
2699
3128
|
//# sourceMappingURL=emitter.js.map
|