octane 0.2.13 → 0.2.14
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/dist/cjs/constants.cjs +7 -7
- package/dist/cjs/dom-binding-handoff.cjs +2 -2
- package/dist/cjs/dom-bindings.cjs +70 -7
- package/dist/cjs/runtime.cjs +294 -53
- package/dist/cjs/server/streamed-signals.cjs +3 -2
- package/dist/cjs/signals/control-binding.cjs +33 -9
- package/dist/cjs/signals/control-handoff.cjs +39 -0
- package/dist/cjs/stream-protocol.cjs +6 -0
- package/dist/cjs/version.cjs +1 -1
- package/dist/compiler/compile.js +86 -24
- package/dist/compiler/dom-binding-program.js +18 -2
- package/dist/compiler/dom-bindings.js +87 -30
- package/dist/compiler/volar.js +58 -17
- package/dist/constants.d.ts +2 -2
- package/dist/constants.js +2 -2
- package/dist/dom-binding-handoff.d.ts +6 -4
- package/dist/dom-binding-handoff.js +1 -1
- package/dist/dom-binding-program.d.ts +2 -1
- package/dist/dom-binding-program.js +16 -1
- package/dist/dom-bindings.d.ts +2 -0
- package/dist/dom-bindings.js +70 -7
- package/dist/runtime.d.ts +34 -3
- package/dist/runtime.js +297 -53
- package/dist/server/streamed-signals.js +3 -2
- package/dist/signals/control-binding.d.ts +9 -5
- package/dist/signals/control-binding.js +38 -6
- package/dist/signals/control-handoff.d.ts +26 -0
- package/dist/signals/control-handoff.js +12 -0
- package/dist/signals/index.d.ts +1 -0
- package/dist/stream-protocol.d.ts +4 -0
- package/dist/stream-protocol.js +4 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -248,7 +248,7 @@ function importedProjectionCall(node, imports, lexical, parameterScope) {
|
|
|
248
248
|
const callee = unwrap(node?.callee);
|
|
249
249
|
let importedRoot = callee;
|
|
250
250
|
while (importedRoot?.type === 'MemberExpression' && !importedRoot.computed)
|
|
251
|
-
importedRoot = importedRoot.object;
|
|
251
|
+
importedRoot = unwrap(importedRoot.object);
|
|
252
252
|
const namespace =
|
|
253
253
|
importedRoot !== callee &&
|
|
254
254
|
importedRoot?.type === 'Identifier' &&
|
|
@@ -695,6 +695,8 @@ function planView(fn, filename, source, imports, lexical, native = null) {
|
|
|
695
695
|
const signalIndices = [];
|
|
696
696
|
const styleIndices = [];
|
|
697
697
|
const projectionGroups = [];
|
|
698
|
+
const providerBindings = new Set();
|
|
699
|
+
const unboundAttributes = new Set();
|
|
698
700
|
const classAttributes = new Map();
|
|
699
701
|
const id = `d:${strongHash(`octane:dom-bindings:2\0${filename}\0${fn.id.name}\0${source}`)}`;
|
|
700
702
|
const parameterScope = lexical.nodeScopes.get(fn.body) ?? lexical.rootScope;
|
|
@@ -702,14 +704,15 @@ function planView(fn, filename, source, imports, lexical, native = null) {
|
|
|
702
704
|
let addressed = false;
|
|
703
705
|
const markUnbound = (expression, opaque = false) => {
|
|
704
706
|
const value = unwrap(expression);
|
|
707
|
+
const callee = unwrap(value?.callee);
|
|
705
708
|
const external =
|
|
706
|
-
value?.type === 'CallExpression' &&
|
|
707
|
-
? imports.get(
|
|
709
|
+
value?.type === 'CallExpression' && callee?.type === 'Identifier'
|
|
710
|
+
? imports.get(callee.name)
|
|
708
711
|
: null;
|
|
709
712
|
if (
|
|
710
713
|
external?.source !== 'octane/behavior' ||
|
|
711
714
|
external.imported !== 'unbound' ||
|
|
712
|
-
lexical.resolveBinding(lexical.nodeScopes.get(
|
|
715
|
+
lexical.resolveBinding(lexical.nodeScopes.get(callee), callee.name)?.scope !==
|
|
713
716
|
lexical.rootScope
|
|
714
717
|
)
|
|
715
718
|
return false;
|
|
@@ -918,7 +921,9 @@ function planView(fn, filename, source, imports, lexical, native = null) {
|
|
|
918
921
|
const externalNames = new Set();
|
|
919
922
|
const knownFields = new Set();
|
|
920
923
|
for (const attr of element.openingElement?.attributes ?? element.attributes ?? []) {
|
|
921
|
-
|
|
924
|
+
// A provider proves normal-renderer fields, not early ownership. External
|
|
925
|
+
// spreads retain the ordinary unbound checks and are stripped below.
|
|
926
|
+
if (attr._octaneKnownAttributeSpread && !attr._octaneKnownAttributeSpread.unbound) {
|
|
922
927
|
const argument = attr.argument ?? attr.value.expression;
|
|
923
928
|
// The compiler verified the exact imported factory against its
|
|
924
929
|
// provider contract. Its arguments still need the ordinary proof.
|
|
@@ -956,6 +961,7 @@ function planView(fn, filename, source, imports, lexical, native = null) {
|
|
|
956
961
|
if (!group) signalIndices.push(bindings.length);
|
|
957
962
|
bindings.push([index, bindingKind(tag, name), name]);
|
|
958
963
|
}
|
|
964
|
+
providerBindings.add(bindings.length - 1);
|
|
959
965
|
values.push(
|
|
960
966
|
group
|
|
961
967
|
? inheritHookMemoOrigin(
|
|
@@ -979,26 +985,31 @@ function planView(fn, filename, source, imports, lexical, native = null) {
|
|
|
979
985
|
error(filename, attr, 'binding attribute spreads must be explicitly unbound');
|
|
980
986
|
if (bindings.some((binding) => binding[0] === index))
|
|
981
987
|
error(filename, attr, 'unbound attribute spreads must precede owned binding attributes');
|
|
988
|
+
const addExternalName = (raw, node) => {
|
|
989
|
+
const name = (
|
|
990
|
+
raw === 'className' ? 'class' : (ATTRIBUTE_ALIASES.get(raw) ?? raw)
|
|
991
|
+
).toLowerCase();
|
|
992
|
+
if (
|
|
993
|
+
FORBIDDEN_ATTRS.has(name) ||
|
|
994
|
+
name.startsWith('on') ||
|
|
995
|
+
name.startsWith('data-octane-class-')
|
|
996
|
+
)
|
|
997
|
+
error(
|
|
998
|
+
filename,
|
|
999
|
+
node,
|
|
1000
|
+
`unbound spreads cannot supply reserved or structural attribute ${JSON.stringify(raw)}`,
|
|
1001
|
+
);
|
|
1002
|
+
externalNames.add(name);
|
|
1003
|
+
};
|
|
982
1004
|
const external = unwrap(unwrap(attr.argument).arguments[0]);
|
|
983
|
-
if (
|
|
1005
|
+
if (attr._octaneKnownAttributeSpread) {
|
|
1006
|
+
for (const raw of attr._octaneKnownAttributeSpread.fields) addExternalName(raw, attr);
|
|
1007
|
+
} else if (external?.type === 'ObjectExpression') {
|
|
984
1008
|
for (const property of external.properties) {
|
|
985
1009
|
if (property.type !== 'Property' || property.computed) continue;
|
|
986
1010
|
const raw = property.key.name ?? property.key.value;
|
|
987
1011
|
if (typeof raw !== 'string') continue;
|
|
988
|
-
|
|
989
|
-
raw === 'className' ? 'class' : (ATTRIBUTE_ALIASES.get(raw) ?? raw)
|
|
990
|
-
).toLowerCase();
|
|
991
|
-
if (
|
|
992
|
-
FORBIDDEN_ATTRS.has(name) ||
|
|
993
|
-
name.startsWith('on') ||
|
|
994
|
-
name.startsWith('data-octane-class-')
|
|
995
|
-
)
|
|
996
|
-
error(
|
|
997
|
-
filename,
|
|
998
|
-
property,
|
|
999
|
-
`unbound spreads cannot supply reserved or structural attribute ${JSON.stringify(raw)}`,
|
|
1000
|
-
);
|
|
1001
|
-
externalNames.add(name);
|
|
1012
|
+
addExternalName(raw, property);
|
|
1002
1013
|
}
|
|
1003
1014
|
}
|
|
1004
1015
|
continue;
|
|
@@ -1022,6 +1033,16 @@ function planView(fn, filename, source, imports, lexical, native = null) {
|
|
|
1022
1033
|
nodes[index][3] = null;
|
|
1023
1034
|
continue;
|
|
1024
1035
|
}
|
|
1036
|
+
const value =
|
|
1037
|
+
attr.value === null
|
|
1038
|
+
? b.literal(true)
|
|
1039
|
+
: unwrap(
|
|
1040
|
+
attr.value?.type === 'JSXExpressionContainer' ? attr.value.expression : attr.value,
|
|
1041
|
+
);
|
|
1042
|
+
if ((lower === 'ref' || /^on[A-Z]/.test(raw)) && markUnbound(value)) {
|
|
1043
|
+
unboundAttributes.add(lower);
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1025
1046
|
if (
|
|
1026
1047
|
!/^[A-Za-z_][A-Za-z0-9_.:-]*$/.test(name) ||
|
|
1027
1048
|
lower.startsWith('on') ||
|
|
@@ -1030,13 +1051,10 @@ function planView(fn, filename, source, imports, lexical, native = null) {
|
|
|
1030
1051
|
) {
|
|
1031
1052
|
error(filename, attr, `attribute ${JSON.stringify(raw)} is not supported in binding views`);
|
|
1032
1053
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
attr.value?.type === 'JSXExpressionContainer' ? attr.value.expression : attr.value,
|
|
1038
|
-
);
|
|
1039
|
-
if (markUnbound(value)) continue;
|
|
1054
|
+
if (markUnbound(value)) {
|
|
1055
|
+
unboundAttributes.add(lower);
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1040
1058
|
if (value?.type !== 'Literal' && externalNames.has(lower))
|
|
1041
1059
|
error(
|
|
1042
1060
|
filename,
|
|
@@ -1144,6 +1162,28 @@ function planView(fn, filename, source, imports, lexical, native = null) {
|
|
|
1144
1162
|
}
|
|
1145
1163
|
};
|
|
1146
1164
|
visit(render, -1, native?.namespace ?? 0, native?.ancestors ?? []);
|
|
1165
|
+
// Opaque descendants are outside this receipt. Only a scalar native host's
|
|
1166
|
+
// declared presentation channels can transfer independently of those children.
|
|
1167
|
+
const hostHandoff =
|
|
1168
|
+
nodes.length === 1 &&
|
|
1169
|
+
nodes[0][3] === null &&
|
|
1170
|
+
!addressed &&
|
|
1171
|
+
!['textarea', 'input', 'select'].includes(nodes[0][1]) &&
|
|
1172
|
+
bindings.length > 0 &&
|
|
1173
|
+
bindings.every(
|
|
1174
|
+
(binding, index) =>
|
|
1175
|
+
binding[0] === 0 &&
|
|
1176
|
+
(['class', 'styleProperty', 'styleAttribute', 'styleObject'].includes(binding[1]) ||
|
|
1177
|
+
providerBindings.has(index)),
|
|
1178
|
+
) &&
|
|
1179
|
+
[...(render.openingElement?.attributes ?? render.attributes ?? [])].every(
|
|
1180
|
+
(attr) =>
|
|
1181
|
+
!['JSXSpreadAttribute', 'SpreadAttribute'].includes(attr.type) ||
|
|
1182
|
+
(attr._octaneKnownAttributeSpread && !attr._octaneKnownAttributeSpread.unbound),
|
|
1183
|
+
) &&
|
|
1184
|
+
!(render.openingElement?.attributes ?? render.attributes ?? []).some(
|
|
1185
|
+
(attr) => attrName(attr)?.toLowerCase() === 'dangerouslysetinnerhtml',
|
|
1186
|
+
);
|
|
1147
1187
|
return {
|
|
1148
1188
|
fn,
|
|
1149
1189
|
render,
|
|
@@ -1159,6 +1199,8 @@ function planView(fn, filename, source, imports, lexical, native = null) {
|
|
|
1159
1199
|
unbound,
|
|
1160
1200
|
classAttributes,
|
|
1161
1201
|
id,
|
|
1202
|
+
hostHandoff,
|
|
1203
|
+
unboundAttributes,
|
|
1162
1204
|
};
|
|
1163
1205
|
}
|
|
1164
1206
|
|
|
@@ -1181,6 +1223,7 @@ function scalarProperties(
|
|
|
1181
1223
|
b.prop('init', b.id('nodes'), literalData(plan.nodes)),
|
|
1182
1224
|
b.prop('init', b.id('bindings'), literalData(plan.bindings)),
|
|
1183
1225
|
b.prop('init', b.id('project'), project),
|
|
1226
|
+
...(plan.hostHandoff ? [b.prop('init', b.id('handoff'), b.literal('host'))] : []),
|
|
1184
1227
|
...(plan.signalIndices.length
|
|
1185
1228
|
? [
|
|
1186
1229
|
b.prop('init', b.id('signalIndices'), literalData(plan.signalIndices)),
|
|
@@ -1876,12 +1919,13 @@ export function prepareDomBindings(ast, source, filename, selectedExport, helper
|
|
|
1876
1919
|
: new Map();
|
|
1877
1920
|
const isUnbound = (expression) => {
|
|
1878
1921
|
const value = unwrap(expression);
|
|
1879
|
-
|
|
1880
|
-
|
|
1922
|
+
const callee = unwrap(value?.callee);
|
|
1923
|
+
if (value?.type !== 'CallExpression' || callee?.type !== 'Identifier') return false;
|
|
1924
|
+
const imported = imports.get(callee.name);
|
|
1881
1925
|
return (
|
|
1882
1926
|
imported?.source === 'octane/behavior' &&
|
|
1883
1927
|
imported.imported === 'unbound' &&
|
|
1884
|
-
lexical.resolveBinding(lexical.nodeScopes.get(
|
|
1928
|
+
lexical.resolveBinding(lexical.nodeScopes.get(callee), callee.name)?.scope ===
|
|
1885
1929
|
lexical.rootScope
|
|
1886
1930
|
);
|
|
1887
1931
|
};
|
|
@@ -2132,6 +2176,9 @@ export function prepareDomBindings(ast, source, filename, selectedExport, helper
|
|
|
2132
2176
|
_octanePresentationHydration: {
|
|
2133
2177
|
id: plan.id,
|
|
2134
2178
|
supported: fixedPresentation || structuralPresentation,
|
|
2179
|
+
...(field('nodes').elements.some((node) => node.elements[5]?.value === 'value')
|
|
2180
|
+
? { nativeControl: true }
|
|
2181
|
+
: {}),
|
|
2135
2182
|
...(structuralPresentation || conditionalRest ? { structural: true } : {}),
|
|
2136
2183
|
...(conditionalRest ? { conditionalRest: true } : {}),
|
|
2137
2184
|
},
|
|
@@ -2207,6 +2254,16 @@ export function prepareDomBindings(ast, source, filename, selectedExport, helper
|
|
|
2207
2254
|
);
|
|
2208
2255
|
}
|
|
2209
2256
|
for (const [node, replacement] of rewritten) replacements.set(node, replacement);
|
|
2257
|
+
if (plan.hostHandoff)
|
|
2258
|
+
replacements.set(node, {
|
|
2259
|
+
...mapCow(node, rewritten),
|
|
2260
|
+
_octanePresentationHydration: {
|
|
2261
|
+
id: plan.id,
|
|
2262
|
+
supported: true,
|
|
2263
|
+
host: true,
|
|
2264
|
+
unboundAttributes: plan.unboundAttributes,
|
|
2265
|
+
},
|
|
2266
|
+
});
|
|
2210
2267
|
});
|
|
2211
2268
|
if (selectedExport !== null) {
|
|
2212
2269
|
const plan = plans.get(selectedExport);
|
package/dist/compiler/volar.js
CHANGED
|
@@ -31829,11 +31829,10 @@ var OCTANE_PLATFORM = {
|
|
|
31829
31829
|
}
|
|
31830
31830
|
};
|
|
31831
31831
|
var octaneTransform = createJsxTransform(OCTANE_PLATFORM);
|
|
31832
|
-
function
|
|
31832
|
+
function projectNativeDomReads(ast, contracts) {
|
|
31833
31833
|
const attributes = new Set(
|
|
31834
31834
|
contracts?.flatMap((contract) => contract.jsxAttribute ? [contract.jsxAttribute] : [])
|
|
31835
31835
|
);
|
|
31836
|
-
if (attributes.size === 0) return ast;
|
|
31837
31836
|
const names = /* @__PURE__ */ new Set();
|
|
31838
31837
|
const walk2 = (node, replace) => {
|
|
31839
31838
|
if (!node || typeof node !== "object") return node;
|
|
@@ -31858,20 +31857,54 @@ function projectNativeAttributeReads(ast, contracts) {
|
|
|
31858
31857
|
let helper = "__octane_nativeAttributeValue";
|
|
31859
31858
|
while (names.has(helper)) helper += "_";
|
|
31860
31859
|
let used = false;
|
|
31860
|
+
const read = (value) => {
|
|
31861
|
+
used = true;
|
|
31862
|
+
return inheritHookMemoOrigin(builders_exports.call(builders_exports.id(helper), value), value);
|
|
31863
|
+
};
|
|
31864
|
+
const projectText = (node) => {
|
|
31865
|
+
if (!node) return node;
|
|
31866
|
+
if (["TSAsExpression", "TSTypeAssertion", "TSSatisfiesExpression"].includes(node.type) && node.typeAnnotation?.type === "TSStringKeyword") {
|
|
31867
|
+
used = true;
|
|
31868
|
+
const expression = projectText(node.expression);
|
|
31869
|
+
return {
|
|
31870
|
+
...node,
|
|
31871
|
+
expression: expression === node.expression ? builders_exports.call(builders_exports.id(helper), expression) : expression
|
|
31872
|
+
};
|
|
31873
|
+
}
|
|
31874
|
+
if ([
|
|
31875
|
+
"TSAsExpression",
|
|
31876
|
+
"TSTypeAssertion",
|
|
31877
|
+
"TSNonNullExpression",
|
|
31878
|
+
"TSSatisfiesExpression",
|
|
31879
|
+
"TSInstantiationExpression",
|
|
31880
|
+
"ParenthesizedExpression"
|
|
31881
|
+
].includes(node.type)) {
|
|
31882
|
+
const expression = projectText(node.expression);
|
|
31883
|
+
return expression === node.expression ? node : { ...node, expression };
|
|
31884
|
+
}
|
|
31885
|
+
return node;
|
|
31886
|
+
};
|
|
31861
31887
|
const projected = walk2(ast, (node) => {
|
|
31862
|
-
if (
|
|
31888
|
+
if (["JSXElement", "JSXFragment", "Element", "Fragment"].includes(node.type)) {
|
|
31889
|
+
node = {
|
|
31890
|
+
...node,
|
|
31891
|
+
children: node.children.map((child) => {
|
|
31892
|
+
if (child.type !== "JSXExpressionContainer") return child;
|
|
31893
|
+
const expression = projectText(child.expression);
|
|
31894
|
+
return expression === child.expression ? child : { ...child, expression };
|
|
31895
|
+
})
|
|
31896
|
+
};
|
|
31897
|
+
}
|
|
31898
|
+
if (node.type !== "JSXOpeningElement" && node.type !== "Element") return node;
|
|
31863
31899
|
const name = node.type === "Element" ? node.id : node.name;
|
|
31864
31900
|
if (name?.type !== "JSXIdentifier" && name?.type !== "Identifier" || typeof name.name !== "string" || name.name[0] !== name.name[0].toLowerCase())
|
|
31865
|
-
return
|
|
31901
|
+
return node;
|
|
31866
31902
|
return {
|
|
31867
31903
|
...node,
|
|
31868
31904
|
attributes: node.attributes.map((attribute) => {
|
|
31869
31905
|
if (!attributes.has(attribute.name?.name ?? attribute.name) || attribute.value?.type !== "JSXExpressionContainer")
|
|
31870
31906
|
return attribute;
|
|
31871
|
-
const result = lowerNativeAttributeReads(attribute.value.expression,
|
|
31872
|
-
used = true;
|
|
31873
|
-
return inheritHookMemoOrigin(builders_exports.call(builders_exports.id(helper), value), value);
|
|
31874
|
-
});
|
|
31907
|
+
const result = lowerNativeAttributeReads(attribute.value.expression, read);
|
|
31875
31908
|
return { ...attribute, value: { ...attribute.value, expression: result.expression } };
|
|
31876
31909
|
})
|
|
31877
31910
|
};
|
|
@@ -32099,7 +32132,7 @@ function compileToVolarMappings(source, filename, options) {
|
|
|
32099
32132
|
const transform = selectOctaneTransform(ast);
|
|
32100
32133
|
const transformed = transform(
|
|
32101
32134
|
projectServerContextCalls(
|
|
32102
|
-
renderer.target === "dom" ?
|
|
32135
|
+
renderer.target === "dom" ? projectNativeDomReads(transformAst, options?.knownAttributeSpreads) : transformAst,
|
|
32103
32136
|
filename
|
|
32104
32137
|
),
|
|
32105
32138
|
source,
|
|
@@ -32251,14 +32284,22 @@ function compileTypesInspection(source, filename, options) {
|
|
|
32251
32284
|
const renderer = resolveRendererForFile(rendererConfig, filename ?? "untitled.tsrx");
|
|
32252
32285
|
const rendererPragma = authoredLeadingPragma(ast, comments) ? null : createRendererTypePragma(renderer, ast);
|
|
32253
32286
|
const transform = selectOctaneTransform(ast);
|
|
32254
|
-
const transformed = transform(
|
|
32255
|
-
|
|
32256
|
-
|
|
32257
|
-
|
|
32258
|
-
|
|
32259
|
-
|
|
32260
|
-
|
|
32261
|
-
|
|
32287
|
+
const transformed = transform(
|
|
32288
|
+
projectServerContextCalls(
|
|
32289
|
+
renderer.target === "dom" ? projectNativeDomReads(ast) : ast,
|
|
32290
|
+
filename
|
|
32291
|
+
),
|
|
32292
|
+
source,
|
|
32293
|
+
filename,
|
|
32294
|
+
{
|
|
32295
|
+
collect: true,
|
|
32296
|
+
loose: true,
|
|
32297
|
+
typeOnly: true,
|
|
32298
|
+
inspect: true,
|
|
32299
|
+
errors,
|
|
32300
|
+
comments: rendererPragma === null ? comments : [rendererPragma, ...comments]
|
|
32301
|
+
}
|
|
32302
|
+
);
|
|
32262
32303
|
markNativeTemplateBodies(ast);
|
|
32263
32304
|
return {
|
|
32264
32305
|
code: transformed.code,
|
package/dist/constants.d.ts
CHANGED
|
@@ -50,7 +50,7 @@ export declare const HYDRATION_TEXT_SEP = " ";
|
|
|
50
50
|
* render order) so a hydrating boundary returns synchronously instead of
|
|
51
51
|
* re-suspending. Shared so server emit and client read stay byte-identical.
|
|
52
52
|
*/
|
|
53
|
-
export
|
|
53
|
+
export { SUSPENSE_SCRIPT_ATTR } from './stream-protocol.js';
|
|
54
54
|
export declare const SUSPENSE_RESOLVED_COMMENT = "oct-suspense:";
|
|
55
55
|
export declare const SUSPENSE_RESOLVED_SEED_ATTR = "data-octane-suspense-seeds";
|
|
56
56
|
export declare const SUSPENSE_RESOLVED_NATIVE_ATTR = "data-octane-suspense-signals";
|
|
@@ -105,7 +105,7 @@ export declare const STREAM_SEGMENT_ATTR = "data-oct-s";
|
|
|
105
105
|
/** Per-boundary seed-JSON script attribute (inside the segment). */
|
|
106
106
|
export declare const STREAM_SEED_ATTR = "data-oct-seed";
|
|
107
107
|
/** Renderer-owned executable/data scripts emitted by the streaming protocol. */
|
|
108
|
-
export
|
|
108
|
+
export { STREAM_SCRIPT_ATTR } from './stream-protocol.js';
|
|
109
109
|
/** Hidden carrier for Float sheet resources discovered after the shell; the
|
|
110
110
|
* inline `$OCTRH` call hoists its tags into document.head. */
|
|
111
111
|
export declare const STREAM_RESOURCE_ATTR = "data-oct-fr";
|
package/dist/constants.js
CHANGED
|
@@ -8,7 +8,7 @@ const FOR_BLOCK_OPEN_EMPTY = `<!--${HYDRATION_FOR_EMPTY}-->`;
|
|
|
8
8
|
const FOR_BLOCK_OPEN_ITEMS = `<!--${HYDRATION_FOR_ITEMS}-->`;
|
|
9
9
|
const EMPTY_COMMENT = "<!---->";
|
|
10
10
|
const HYDRATION_TEXT_SEP = " ";
|
|
11
|
-
|
|
11
|
+
import { SUSPENSE_SCRIPT_ATTR } from "./stream-protocol.js";
|
|
12
12
|
const SUSPENSE_RESOLVED_COMMENT = "oct-suspense:";
|
|
13
13
|
const SUSPENSE_RESOLVED_SEED_ATTR = "data-octane-suspense-seeds";
|
|
14
14
|
const SUSPENSE_RESOLVED_NATIVE_ATTR = "data-octane-suspense-signals";
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
import { STREAM_BOUNDARY_ATTR } from "./stream-protocol.js";
|
|
38
38
|
const STREAM_SEGMENT_ATTR = "data-oct-s";
|
|
39
39
|
const STREAM_SEED_ATTR = "data-oct-seed";
|
|
40
|
-
|
|
40
|
+
import { STREAM_SCRIPT_ATTR } from "./stream-protocol.js";
|
|
41
41
|
const STREAM_RESOURCE_ATTR = "data-oct-fr";
|
|
42
42
|
const STREAM_SEED_COMMENT = "oct-seed:";
|
|
43
43
|
import {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
import { BINDING_HANDOFF } from './signals/control-handoff.js';
|
|
2
|
+
export { BINDING_HANDOFF };
|
|
3
3
|
/** Current early-owned range, not a license to render a different shape. */
|
|
4
4
|
export interface BindingHandoffRange {
|
|
5
5
|
readonly kind: 'if' | 'view' | 'slot' | 'text';
|
|
@@ -24,13 +24,15 @@ export interface BindingHandoff {
|
|
|
24
24
|
readonly root: Node;
|
|
25
25
|
readonly anchor: Node;
|
|
26
26
|
readonly end?: Node;
|
|
27
|
-
/**
|
|
27
|
+
/** A scalar host's declared native channels; descendants belong to other owners. */
|
|
28
|
+
readonly host?: ReadonlySet<string>;
|
|
29
|
+
/** Revisioned presentations return -1 while publication is in progress. */
|
|
28
30
|
revision?(): number;
|
|
29
31
|
ranges?(): ReadonlyMap<Node, BindingHandoffRange>;
|
|
30
32
|
view?(root: Node): BindingHandoffView | undefined;
|
|
31
33
|
rest?(element: Element, site: number): BindingHandoffRest | undefined;
|
|
32
34
|
valid?(): boolean;
|
|
33
|
-
/** One deferred retry;
|
|
35
|
+
/** One deferred retry; listener storage is allocated only when requested. */
|
|
34
36
|
afterPublication?(callback: () => void): () => void;
|
|
35
37
|
active(): boolean;
|
|
36
38
|
retire(publish?: () => void): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { BINDING_HANDOFF } from "./signals/control-handoff.js";
|
|
2
2
|
const events = /* @__PURE__ */ new WeakMap();
|
|
3
3
|
const delivered = /* @__PURE__ */ new WeakMap();
|
|
4
4
|
function registerBindingEvent(node, type) {
|
|
@@ -15,7 +15,8 @@ export type BindingProgramNode = readonly [
|
|
|
15
15
|
kind: 'element',
|
|
16
16
|
tag: string,
|
|
17
17
|
namespace: 0 | 1,
|
|
18
|
-
children: number | null
|
|
18
|
+
children: number | null,
|
|
19
|
+
nativeControlContent?: 'value'
|
|
19
20
|
] | readonly [parent: number, kind: 'text', text: string] | readonly [parent: number, kind: 'region', site: string];
|
|
20
21
|
export interface BindingRange {
|
|
21
22
|
readonly start: Comment;
|
|
@@ -1250,15 +1250,30 @@ function bindProgram(root, descriptor, source, options, mount, list = descriptor
|
|
|
1250
1250
|
dispose,
|
|
1251
1251
|
() => !busy
|
|
1252
1252
|
);
|
|
1253
|
-
if (mount || descriptor.root.handoff !== true || !instance || descriptor.root.regions.length !== 0 || descriptor.root.bindings.some((binding) => binding[1] === "control") || descriptor.root.nodes.some(
|
|
1253
|
+
if (mount || descriptor.root.handoff !== true || !instance || descriptor.root.regions.length !== 0 || descriptor.root.bindings.some((binding) => binding[1] === "control") || descriptor.root.nodes.some(
|
|
1254
|
+
(node, index) => node[1] === "element" && node[4] === null && !(node[2] === "textarea" && node[3] === 0 && node[5] === "value" && Array.from(instance.nodes[index].childNodes).every((child) => child.nodeType === 3))
|
|
1255
|
+
))
|
|
1254
1256
|
throw new Error(
|
|
1255
1257
|
"Hydration binding leases require an adopted fixed native view without structural regions or controls."
|
|
1256
1258
|
);
|
|
1259
|
+
const topology = handoff === void 0 && descriptor.root.nodes.some((node) => node[1] === "element" && node[5] === "value") ? [instance.range.start, ...instance.nodes, instance.range.end].map(
|
|
1260
|
+
(node) => [node, node.parentNode, node.previousSibling, node.nextSibling]
|
|
1261
|
+
) : void 0;
|
|
1257
1262
|
return handoff ??= {
|
|
1258
1263
|
id: descriptor.id,
|
|
1259
1264
|
root: instance.range.start,
|
|
1260
1265
|
anchor: instance.nodes.find((node) => node.nodeType === 1) ?? instance.range.start,
|
|
1261
1266
|
active: () => !transaction.disposed,
|
|
1267
|
+
...topology === void 0 ? {} : {
|
|
1268
|
+
end: instance.range.end,
|
|
1269
|
+
valid: () => topology.every(
|
|
1270
|
+
([node, parent, before, after]) => node.parentNode === parent && (node === instance.range.start || node.previousSibling === before) && (node === instance.range.end || node.nextSibling === after)
|
|
1271
|
+
) && descriptor.root.nodes.every(
|
|
1272
|
+
(proof, index) => proof[1] !== "element" || (proof[4] === null ? Array.from(instance.nodes[index].childNodes).every(
|
|
1273
|
+
(child) => child.nodeType === 3
|
|
1274
|
+
) : instance.nodes[index].childNodes.length === proof[4])
|
|
1275
|
+
)
|
|
1276
|
+
},
|
|
1262
1277
|
retire: (publish) => {
|
|
1263
1278
|
transaction.preservePresentation = true;
|
|
1264
1279
|
dispose(void 0, publish);
|
package/dist/dom-bindings.d.ts
CHANGED
|
@@ -59,6 +59,8 @@ export type BindingValue = string | null | BindingStyleSnapshot;
|
|
|
59
59
|
/** @internal No application function is invoked to discover the DOM topology. */
|
|
60
60
|
export interface CompiledBindings<Props> {
|
|
61
61
|
readonly id: string;
|
|
62
|
+
/** Compiler-proven single native host, with no descendant ownership. */
|
|
63
|
+
readonly handoff?: 'host';
|
|
62
64
|
/** Targets carry compiler-issued addresses when unbound siblings may change. */
|
|
63
65
|
readonly addressed?: true;
|
|
64
66
|
readonly nodes: readonly BindingNode[];
|
package/dist/dom-bindings.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { normalizeClass } from "./class-names.js";
|
|
2
2
|
import { sanitizeURL } from "./sanitize-url.js";
|
|
3
|
+
import { STREAM_SCRIPT_ATTR, SUSPENSE_SCRIPT_ATTR } from "./stream-protocol.js";
|
|
4
|
+
import { NATIVE_SIGNAL_SEED_ATTR } from "./signals/native-read-seeds.js";
|
|
5
|
+
import { BINDING_HANDOFF } from "./signals/control-handoff.js";
|
|
3
6
|
import { captureSignalOwner, currentSignalOwner } from "./signals/owner-context.js";
|
|
4
7
|
import {
|
|
5
8
|
NATIVE_TRANSITION_CONSUMER,
|
|
@@ -254,16 +257,28 @@ function __adoptBindings(root, descriptor, source, options) {
|
|
|
254
257
|
let dirty = false;
|
|
255
258
|
let revision = 0;
|
|
256
259
|
let unsubscribe;
|
|
260
|
+
let publicationRetries;
|
|
261
|
+
let published;
|
|
257
262
|
const signal = options?.signal;
|
|
258
|
-
const dispose = () => {
|
|
259
|
-
if (disposed)
|
|
263
|
+
const dispose = (_disposal, publish, preservePresentation = false) => {
|
|
264
|
+
if (disposed) {
|
|
265
|
+
publish?.();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
260
268
|
disposed = true;
|
|
269
|
+
publicationRetries?.clear();
|
|
261
270
|
signal?.removeEventListener("abort", dispose);
|
|
262
271
|
let failed = false;
|
|
263
272
|
let failure;
|
|
273
|
+
try {
|
|
274
|
+
publish?.();
|
|
275
|
+
} catch (error) {
|
|
276
|
+
failed = true;
|
|
277
|
+
failure = error;
|
|
278
|
+
}
|
|
264
279
|
for (const connection of signalConnections?.values() ?? []) {
|
|
265
280
|
try {
|
|
266
|
-
connection.dispose();
|
|
281
|
+
connection.dispose(preservePresentation);
|
|
267
282
|
} catch (error) {
|
|
268
283
|
if (!failed) {
|
|
269
284
|
failed = true;
|
|
@@ -273,7 +288,7 @@ function __adoptBindings(root, descriptor, source, options) {
|
|
|
273
288
|
}
|
|
274
289
|
for (const connection of projections ? new Set(projections.values()) : []) {
|
|
275
290
|
try {
|
|
276
|
-
connection.dispose();
|
|
291
|
+
connection.dispose(preservePresentation);
|
|
277
292
|
} catch (error) {
|
|
278
293
|
if (!failed) {
|
|
279
294
|
failed = true;
|
|
@@ -298,9 +313,9 @@ function __adoptBindings(root, descriptor, source, options) {
|
|
|
298
313
|
for (let i = 0; i < owned.length; i++) {
|
|
299
314
|
const [node, name] = owned[i];
|
|
300
315
|
try {
|
|
301
|
-
groups?.get(i)?.dispose();
|
|
302
|
-
styles?.get(i)?.dispose();
|
|
303
|
-
if (bindings[i][1] === "classToken" && previous[i] === "")
|
|
316
|
+
groups?.get(i)?.dispose(preservePresentation);
|
|
317
|
+
styles?.get(i)?.dispose(preservePresentation);
|
|
318
|
+
if (!preservePresentation && bindings[i][1] === "classToken" && previous[i] === "")
|
|
304
319
|
node.classList.remove(bindings[i][2]);
|
|
305
320
|
} catch (error) {
|
|
306
321
|
if (!failed) {
|
|
@@ -571,6 +586,7 @@ function __adoptBindings(root, descriptor, source, options) {
|
|
|
571
586
|
writePrepared(next, indices, preparedControls, preparedGroups);
|
|
572
587
|
} finally {
|
|
573
588
|
busy = false;
|
|
589
|
+
published?.();
|
|
574
590
|
}
|
|
575
591
|
}
|
|
576
592
|
};
|
|
@@ -593,6 +609,7 @@ function __adoptBindings(root, descriptor, source, options) {
|
|
|
593
609
|
for (const index of previousUpdates ?? []) signalUpdates.add(index);
|
|
594
610
|
}
|
|
595
611
|
busy = false;
|
|
612
|
+
if (!preview) published?.();
|
|
596
613
|
}
|
|
597
614
|
};
|
|
598
615
|
const refresh = () => {
|
|
@@ -608,6 +625,52 @@ function __adoptBindings(root, descriptor, source, options) {
|
|
|
608
625
|
prepare: () => run(() => drain(true))
|
|
609
626
|
};
|
|
610
627
|
const handle = { refresh, dispose };
|
|
628
|
+
if (descriptor.handoff === "host") {
|
|
629
|
+
published = () => {
|
|
630
|
+
if (!publicationRetries?.size) return;
|
|
631
|
+
const pending = [...publicationRetries];
|
|
632
|
+
publicationRetries.clear();
|
|
633
|
+
for (const retry of pending)
|
|
634
|
+
queueMicrotask(() => {
|
|
635
|
+
if (!disposed) retry();
|
|
636
|
+
});
|
|
637
|
+
};
|
|
638
|
+
const host = root;
|
|
639
|
+
const document = host.ownerDocument;
|
|
640
|
+
const ancestry = [];
|
|
641
|
+
for (let node = host; node !== null; node = node.parentNode)
|
|
642
|
+
ancestry.push([node, node.parentNode]);
|
|
643
|
+
const sibling = (node, previous2) => {
|
|
644
|
+
let next = previous2 ? node.previousSibling : node.nextSibling;
|
|
645
|
+
while (next?.nodeType === 1 && next.localName === "script" && (next.hasAttribute(NATIVE_SIGNAL_SEED_ATTR) || next.hasAttribute(SUSPENSE_SCRIPT_ATTR) || next.hasAttribute(STREAM_SCRIPT_ATTR)))
|
|
646
|
+
next = previous2 ? next.previousSibling : next.nextSibling;
|
|
647
|
+
return next;
|
|
648
|
+
};
|
|
649
|
+
const previousSibling = sibling(host, true);
|
|
650
|
+
const nextSibling = sibling(host, false);
|
|
651
|
+
let handoff;
|
|
652
|
+
handle[BINDING_HANDOFF] = () => {
|
|
653
|
+
if (handoff !== void 0) return handoff;
|
|
654
|
+
return handoff = {
|
|
655
|
+
id: descriptor.id,
|
|
656
|
+
root: host,
|
|
657
|
+
anchor: host,
|
|
658
|
+
host: new Set(
|
|
659
|
+
bindings.map(
|
|
660
|
+
(binding) => binding[1] === "styleProperty" ? "style:" + binding[2] : binding[1].startsWith("style") ? "style" : binding[2]
|
|
661
|
+
)
|
|
662
|
+
),
|
|
663
|
+
revision: () => busy ? -1 : revision,
|
|
664
|
+
afterPublication: (callback) => {
|
|
665
|
+
(publicationRetries ??= /* @__PURE__ */ new Set()).add(callback);
|
|
666
|
+
return () => publicationRetries.delete(callback);
|
|
667
|
+
},
|
|
668
|
+
valid: () => host.parentNode !== null && host.ownerDocument === document && ancestry.every(([node, parent]) => node.parentNode === parent) && sibling(host, true) === previousSibling && sibling(host, false) === nextSibling && host.getAttribute("data-octane-bindings") === descriptor.id,
|
|
669
|
+
active: () => !disposed,
|
|
670
|
+
retire: (publish) => dispose(void 0, publish, true)
|
|
671
|
+
};
|
|
672
|
+
};
|
|
673
|
+
}
|
|
611
674
|
if (signal?.aborted) {
|
|
612
675
|
dispose();
|
|
613
676
|
return handle;
|