@archie/devtools 0.0.28 → 0.0.30
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/collection-binding.d.mts +38 -8
- package/dist/collection-binding.d.ts +38 -8
- package/dist/collection-binding.js +123 -30
- package/dist/collection-binding.mjs +123 -30
- package/dist/inspector.standalone.js +178 -1
- package/package.json +1 -1
|
@@ -21,8 +21,12 @@ import { types, PluginObj } from '@babel/core';
|
|
|
21
21
|
* data-agent-row {String(param?.id)}, on the row root
|
|
22
22
|
* data-agent-field static string OR a runtime expression
|
|
23
23
|
* (??/ternary field selection, D5.5)
|
|
24
|
-
* data-agent-field-state "mixed" | "unresolved"
|
|
25
|
-
*
|
|
24
|
+
* data-agent-field-state "mixed" | "unresolved" | "rendered"
|
|
25
|
+
* ("rendered": a formatter-CALL-wrapped
|
|
26
|
+
* value — the DOM only ever holds the
|
|
27
|
+
* FORMATTED string, ARCH-1065 M-COV FIX)
|
|
28
|
+
* data-agent-field-parts comma-separated field list (mixed leaves,
|
|
29
|
+
* or the single field of a "rendered" leaf)
|
|
26
30
|
* data-agent-field-slot text-node index of the dynamic part
|
|
27
31
|
* (label+value leaves, D6)
|
|
28
32
|
* data-agent-collection-order "stable" | "derived" (D18), on the row root
|
|
@@ -35,8 +39,12 @@ import { types, PluginObj } from '@babel/core';
|
|
|
35
39
|
* `.filter()/.slice()/.sort()/.map()` chain calls, through single-assignment
|
|
36
40
|
* (`const`, never reassigned) local-variable hops (bounded to
|
|
37
41
|
* `MAX_SOURCE_CHAIN_HOPS`), and through a `useMemo(() => …, deps)` /
|
|
38
|
-
* `React.useMemo(() => …, deps)` unwrap
|
|
39
|
-
*
|
|
42
|
+
* `React.useMemo(() => …, deps)` unwrap — EITHER the arrow-EXPRESSION-body
|
|
43
|
+
* shape (`() => expr`) OR a block body with exactly ONE return statement
|
|
44
|
+
* (`() => { …; return expr; }`, ARCH-1065 M-COV FIX, devtools 0.0.29); a
|
|
45
|
+
* block body with zero or 2+ returns (ambiguous control flow) is left
|
|
46
|
+
* unresolved, never guessed at (see `resolveSliceSource` /
|
|
47
|
+
* `findSingleReturnArgument`). A `.sort(` ANYWHERE in the chain marks the
|
|
40
48
|
* collection `data-agent-collection-order="derived"`; otherwise `"stable"`
|
|
41
49
|
* (D18). The identifier root must be exactly `slice` — this mirrors the
|
|
42
50
|
* runtime's `window.__slice` (M3); the codemod does not trace how a `slice`
|
|
@@ -139,10 +147,25 @@ import { types, PluginObj } from '@babel/core';
|
|
|
139
147
|
* the duplicated test would double-evaluate it across SSR and hydration,
|
|
140
148
|
* so the whole leaf is left unstamped instead;
|
|
141
149
|
* - `cond ? 'Literal' : 'Other'` where `cond` is itself a bare field
|
|
142
|
-
* member (a boolean-typed field driving two literal branches)
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
150
|
+
* member (a boolean-typed field driving two literal branches), OR a
|
|
151
|
+
* CALL-FREE comparison (`===`/`!==`/`==`/`!=`/`</<=`/`>`/`>=`) naming a
|
|
152
|
+
* field on either side (`r.status === 'Active' ? 'Pause' : 'Enable'`,
|
|
153
|
+
* D5.5 widening, ARCH-1065 M-COV FIX) → static field named by the
|
|
154
|
+
* resolved test field — the FE's typed controls (D7, a different
|
|
155
|
+
* mechanism, M3) read the boolean's/status's true/false or vocabulary
|
|
156
|
+
* rendering from `window.__slice`, not from this attribute. Nothing
|
|
157
|
+
* about the test is ever duplicated into the DOM here (only the field
|
|
158
|
+
* NAME is emitted), so there is no C3 double-evaluation concern for this
|
|
159
|
+
* shape — `containsEvaluation(test)` is still checked defensively;
|
|
160
|
+
* - a formatter CALL with exactly one argument that is itself a bare
|
|
161
|
+
* member/optional-member field (`Kit.date(r.updated_at)`,
|
|
162
|
+
* `money(auction.currentPrice)`, ARCH-1065 M-COV FIX) → the resolved
|
|
163
|
+
* field name, PLUS `data-agent-field-state="rendered"` and
|
|
164
|
+
* `data-agent-field-parts` (the same field name) — the DOM only ever
|
|
165
|
+
* holds the FORMATTED string, never the raw value, so this is marked
|
|
166
|
+
* the same "honest, non-invertible" way a `??`/boolean fallback is (D8);
|
|
167
|
+
* a multi-arg call, or a call whose sole argument isn't a bare field
|
|
168
|
+
* member, is left unstamped rather than guessed at;
|
|
146
169
|
* - a COMPUTED member (`a[expr]`, `expr` not a string literal) → no field
|
|
147
170
|
* name, `data-agent-field-state="unresolved"` (a positive "this is
|
|
148
171
|
* row-scoped data, but which field is unknown" signal — never silent).
|
|
@@ -171,6 +194,13 @@ import { types, PluginObj } from '@babel/core';
|
|
|
171
194
|
* that is itself slice-rooted gets discovered and stamped independently by
|
|
172
195
|
* the same Program-wide `.map()` scan, with its own entity/row identity.
|
|
173
196
|
*
|
|
197
|
+
* A JSXExpressionContainer wrapping `cond && <JSX/>` / `cond || <JSX/>` / a
|
|
198
|
+
* ternary IS a structural child, though (ARCH-1065 M-COV FIX, devtools
|
|
199
|
+
* 0.0.29, see `collectConditionalJsxBranchPaths`): the walker descends into
|
|
200
|
+
* whichever branch(es) are themselves a JSXElement, with the SAME scope —
|
|
201
|
+
* purely structural, the guard/test is never duplicated or stamped, only the
|
|
202
|
+
* JSX it conditionally renders is walked for further fields.
|
|
203
|
+
*
|
|
174
204
|
* ── Failure posture ─────────────────────────────────────────────────────
|
|
175
205
|
*
|
|
176
206
|
* Every resolution step is an explicit allowlist; anything the resolver is
|
|
@@ -21,8 +21,12 @@ import { types, PluginObj } from '@babel/core';
|
|
|
21
21
|
* data-agent-row {String(param?.id)}, on the row root
|
|
22
22
|
* data-agent-field static string OR a runtime expression
|
|
23
23
|
* (??/ternary field selection, D5.5)
|
|
24
|
-
* data-agent-field-state "mixed" | "unresolved"
|
|
25
|
-
*
|
|
24
|
+
* data-agent-field-state "mixed" | "unresolved" | "rendered"
|
|
25
|
+
* ("rendered": a formatter-CALL-wrapped
|
|
26
|
+
* value — the DOM only ever holds the
|
|
27
|
+
* FORMATTED string, ARCH-1065 M-COV FIX)
|
|
28
|
+
* data-agent-field-parts comma-separated field list (mixed leaves,
|
|
29
|
+
* or the single field of a "rendered" leaf)
|
|
26
30
|
* data-agent-field-slot text-node index of the dynamic part
|
|
27
31
|
* (label+value leaves, D6)
|
|
28
32
|
* data-agent-collection-order "stable" | "derived" (D18), on the row root
|
|
@@ -35,8 +39,12 @@ import { types, PluginObj } from '@babel/core';
|
|
|
35
39
|
* `.filter()/.slice()/.sort()/.map()` chain calls, through single-assignment
|
|
36
40
|
* (`const`, never reassigned) local-variable hops (bounded to
|
|
37
41
|
* `MAX_SOURCE_CHAIN_HOPS`), and through a `useMemo(() => …, deps)` /
|
|
38
|
-
* `React.useMemo(() => …, deps)` unwrap
|
|
39
|
-
*
|
|
42
|
+
* `React.useMemo(() => …, deps)` unwrap — EITHER the arrow-EXPRESSION-body
|
|
43
|
+
* shape (`() => expr`) OR a block body with exactly ONE return statement
|
|
44
|
+
* (`() => { …; return expr; }`, ARCH-1065 M-COV FIX, devtools 0.0.29); a
|
|
45
|
+
* block body with zero or 2+ returns (ambiguous control flow) is left
|
|
46
|
+
* unresolved, never guessed at (see `resolveSliceSource` /
|
|
47
|
+
* `findSingleReturnArgument`). A `.sort(` ANYWHERE in the chain marks the
|
|
40
48
|
* collection `data-agent-collection-order="derived"`; otherwise `"stable"`
|
|
41
49
|
* (D18). The identifier root must be exactly `slice` — this mirrors the
|
|
42
50
|
* runtime's `window.__slice` (M3); the codemod does not trace how a `slice`
|
|
@@ -139,10 +147,25 @@ import { types, PluginObj } from '@babel/core';
|
|
|
139
147
|
* the duplicated test would double-evaluate it across SSR and hydration,
|
|
140
148
|
* so the whole leaf is left unstamped instead;
|
|
141
149
|
* - `cond ? 'Literal' : 'Other'` where `cond` is itself a bare field
|
|
142
|
-
* member (a boolean-typed field driving two literal branches)
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
150
|
+
* member (a boolean-typed field driving two literal branches), OR a
|
|
151
|
+
* CALL-FREE comparison (`===`/`!==`/`==`/`!=`/`</<=`/`>`/`>=`) naming a
|
|
152
|
+
* field on either side (`r.status === 'Active' ? 'Pause' : 'Enable'`,
|
|
153
|
+
* D5.5 widening, ARCH-1065 M-COV FIX) → static field named by the
|
|
154
|
+
* resolved test field — the FE's typed controls (D7, a different
|
|
155
|
+
* mechanism, M3) read the boolean's/status's true/false or vocabulary
|
|
156
|
+
* rendering from `window.__slice`, not from this attribute. Nothing
|
|
157
|
+
* about the test is ever duplicated into the DOM here (only the field
|
|
158
|
+
* NAME is emitted), so there is no C3 double-evaluation concern for this
|
|
159
|
+
* shape — `containsEvaluation(test)` is still checked defensively;
|
|
160
|
+
* - a formatter CALL with exactly one argument that is itself a bare
|
|
161
|
+
* member/optional-member field (`Kit.date(r.updated_at)`,
|
|
162
|
+
* `money(auction.currentPrice)`, ARCH-1065 M-COV FIX) → the resolved
|
|
163
|
+
* field name, PLUS `data-agent-field-state="rendered"` and
|
|
164
|
+
* `data-agent-field-parts` (the same field name) — the DOM only ever
|
|
165
|
+
* holds the FORMATTED string, never the raw value, so this is marked
|
|
166
|
+
* the same "honest, non-invertible" way a `??`/boolean fallback is (D8);
|
|
167
|
+
* a multi-arg call, or a call whose sole argument isn't a bare field
|
|
168
|
+
* member, is left unstamped rather than guessed at;
|
|
146
169
|
* - a COMPUTED member (`a[expr]`, `expr` not a string literal) → no field
|
|
147
170
|
* name, `data-agent-field-state="unresolved"` (a positive "this is
|
|
148
171
|
* row-scoped data, but which field is unknown" signal — never silent).
|
|
@@ -171,6 +194,13 @@ import { types, PluginObj } from '@babel/core';
|
|
|
171
194
|
* that is itself slice-rooted gets discovered and stamped independently by
|
|
172
195
|
* the same Program-wide `.map()` scan, with its own entity/row identity.
|
|
173
196
|
*
|
|
197
|
+
* A JSXExpressionContainer wrapping `cond && <JSX/>` / `cond || <JSX/>` / a
|
|
198
|
+
* ternary IS a structural child, though (ARCH-1065 M-COV FIX, devtools
|
|
199
|
+
* 0.0.29, see `collectConditionalJsxBranchPaths`): the walker descends into
|
|
200
|
+
* whichever branch(es) are themselves a JSXElement, with the SAME scope —
|
|
201
|
+
* purely structural, the guard/test is never duplicated or stamped, only the
|
|
202
|
+
* JSX it conditionally renders is walked for further fields.
|
|
203
|
+
*
|
|
174
204
|
* ── Failure posture ─────────────────────────────────────────────────────
|
|
175
205
|
*
|
|
176
206
|
* Every resolution step is an explicit allowlist; anything the resolver is
|
|
@@ -45,6 +45,7 @@ var ATTR_COLLECTION_ORDER = "data-agent-collection-order";
|
|
|
45
45
|
var SLICE_ROOT_NAME = "slice";
|
|
46
46
|
var CHAIN_METHODS = /* @__PURE__ */ new Set(["filter", "slice", "sort", "map"]);
|
|
47
47
|
var MAX_SOURCE_CHAIN_HOPS = 4;
|
|
48
|
+
var COMPARISON_OPERATORS = /* @__PURE__ */ new Set(["==", "!=", "===", "!==", "<", "<=", ">", ">="]);
|
|
48
49
|
var MAX_PROJECTION_DEPTH = 3;
|
|
49
50
|
function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
50
51
|
const { fileExclude = /node_modules/, forwardingComponents = [] } = opts;
|
|
@@ -92,44 +93,72 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
92
93
|
if (t.isIdentifier(callee, { name: "useMemo" })) return true;
|
|
93
94
|
return t.isMemberExpression(callee) && !callee.computed && t.isIdentifier(callee.property, { name: "useMemo" });
|
|
94
95
|
}
|
|
95
|
-
function
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return resolveSliceSource(factory.body, scope, hops, hasSort);
|
|
96
|
+
function findSingleReturnArgument(blockPath) {
|
|
97
|
+
const returnPaths = [];
|
|
98
|
+
blockPath.traverse({
|
|
99
|
+
Function(inner) {
|
|
100
|
+
inner.skip();
|
|
101
|
+
},
|
|
102
|
+
ReturnStatement(rp) {
|
|
103
|
+
returnPaths.push(rp);
|
|
104
104
|
}
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
});
|
|
106
|
+
if (returnPaths.length !== 1) return null;
|
|
107
|
+
const argPath = returnPaths[0].get("argument");
|
|
108
|
+
if (Array.isArray(argPath) || !argPath.node || !argPath.isExpression()) return null;
|
|
109
|
+
return argPath;
|
|
110
|
+
}
|
|
111
|
+
function resolveSliceSource(path, hops, hasSort) {
|
|
112
|
+
if (path.isCallExpression()) {
|
|
113
|
+
const calleePath = path.get("callee");
|
|
114
|
+
if (Array.isArray(calleePath) || !calleePath.isExpression()) return null;
|
|
115
|
+
if (isUseMemoCallee(calleePath.node)) {
|
|
116
|
+
const argPaths = path.get("arguments");
|
|
117
|
+
const factoryPath = argPaths[0];
|
|
118
|
+
if (!factoryPath || !factoryPath.isArrowFunctionExpression()) return null;
|
|
119
|
+
const bodyPath = factoryPath.get("body");
|
|
120
|
+
if (Array.isArray(bodyPath)) return null;
|
|
121
|
+
if (bodyPath.isExpression()) {
|
|
122
|
+
return resolveSliceSource(bodyPath, hops, hasSort);
|
|
123
|
+
}
|
|
124
|
+
if (bodyPath.isBlockStatement()) {
|
|
125
|
+
const returnArgPath = findSingleReturnArgument(bodyPath);
|
|
126
|
+
if (!returnArgPath) return null;
|
|
127
|
+
return resolveSliceSource(returnArgPath, hops, hasSort);
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const calleeNode = calleePath.node;
|
|
132
|
+
if (t.isMemberExpression(calleeNode) && !calleeNode.computed && t.isIdentifier(calleeNode.property)) {
|
|
133
|
+
const method = calleeNode.property.name;
|
|
107
134
|
if (!CHAIN_METHODS.has(method)) return null;
|
|
108
|
-
|
|
109
|
-
|
|
135
|
+
const objectPath = calleePath.get("object");
|
|
136
|
+
if (Array.isArray(objectPath) || !objectPath.isExpression()) return null;
|
|
137
|
+
return resolveSliceSource(objectPath, hops, hasSort || method === "sort");
|
|
110
138
|
}
|
|
111
139
|
return null;
|
|
112
140
|
}
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
if (!
|
|
116
|
-
if (!
|
|
117
|
-
|
|
141
|
+
if (path.isMemberExpression()) {
|
|
142
|
+
const { object, property, computed } = path.node;
|
|
143
|
+
if (!t.isIdentifier(object, { name: SLICE_ROOT_NAME })) return null;
|
|
144
|
+
if (!isPageSliceBinding(path.scope)) return null;
|
|
145
|
+
if (!computed && t.isIdentifier(property)) {
|
|
146
|
+
return { entity: property.name, hasSort };
|
|
118
147
|
}
|
|
119
|
-
if (
|
|
120
|
-
return { entity:
|
|
148
|
+
if (computed && t.isStringLiteral(property)) {
|
|
149
|
+
return { entity: property.value, hasSort };
|
|
121
150
|
}
|
|
122
151
|
return null;
|
|
123
152
|
}
|
|
124
|
-
if (
|
|
153
|
+
if (path.isIdentifier()) {
|
|
125
154
|
if (hops >= MAX_SOURCE_CHAIN_HOPS) return null;
|
|
126
|
-
const binding = scope.getBinding(
|
|
155
|
+
const binding = path.scope.getBinding(path.node.name);
|
|
127
156
|
if (!binding || !binding.constant) return null;
|
|
128
157
|
const declarator = binding.path;
|
|
129
158
|
if (!declarator.isVariableDeclarator()) return null;
|
|
130
|
-
const
|
|
131
|
-
if (!
|
|
132
|
-
return resolveSliceSource(
|
|
159
|
+
const initPath = declarator.get("init");
|
|
160
|
+
if (Array.isArray(initPath) || !initPath.node || !initPath.isExpression()) return null;
|
|
161
|
+
return resolveSliceSource(initPath, hops + 1, hasSort);
|
|
133
162
|
}
|
|
134
163
|
return null;
|
|
135
164
|
}
|
|
@@ -267,11 +296,27 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
267
296
|
const field = fieldNameForBranch(expr.left, scope);
|
|
268
297
|
return field ? { kind: "static", field } : null;
|
|
269
298
|
}
|
|
299
|
+
function fieldNameForLiteralBranchTest(expr, scope) {
|
|
300
|
+
const direct = fieldNameForBranch(expr, scope);
|
|
301
|
+
if (direct) return direct;
|
|
302
|
+
if (t.isBinaryExpression(expr) && COMPARISON_OPERATORS.has(expr.operator)) {
|
|
303
|
+
if (t.isExpression(expr.left)) {
|
|
304
|
+
const leftField = fieldNameForBranch(expr.left, scope);
|
|
305
|
+
if (leftField) return leftField;
|
|
306
|
+
}
|
|
307
|
+
if (t.isExpression(expr.right)) {
|
|
308
|
+
const rightField = fieldNameForBranch(expr.right, scope);
|
|
309
|
+
if (rightField) return rightField;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
270
314
|
function classifyConditional(expr, scope) {
|
|
271
315
|
const { test, consequent, alternate } = expr;
|
|
272
316
|
if (!t.isExpression(test) || !t.isExpression(consequent) || !t.isExpression(alternate)) return null;
|
|
273
317
|
if (isStaticLiteralExpr(consequent) && isStaticLiteralExpr(alternate)) {
|
|
274
|
-
|
|
318
|
+
if (containsEvaluation(test)) return null;
|
|
319
|
+
const testField = fieldNameForLiteralBranchTest(test, scope);
|
|
275
320
|
return testField ? { kind: "static", field: testField } : null;
|
|
276
321
|
}
|
|
277
322
|
const consField = fieldNameForBranch(consequent, scope);
|
|
@@ -284,6 +329,14 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
284
329
|
expr: t.conditionalExpression(t.cloneNode(test), t.stringLiteral(consField), t.stringLiteral(altField))
|
|
285
330
|
};
|
|
286
331
|
}
|
|
332
|
+
function classifyFormatterCall(expr, scope) {
|
|
333
|
+
if (expr.arguments.length !== 1) return null;
|
|
334
|
+
const [arg] = expr.arguments;
|
|
335
|
+
if (!t.isMemberExpression(arg) && !t.isOptionalMemberExpression(arg)) return null;
|
|
336
|
+
const verdict = classifyMemberField(arg, scope);
|
|
337
|
+
if (!verdict || verdict.kind !== "static") return null;
|
|
338
|
+
return { kind: "formatted", field: verdict.field };
|
|
339
|
+
}
|
|
287
340
|
function classifyFieldExpr(expr, scope) {
|
|
288
341
|
if (t.isIdentifier(expr)) {
|
|
289
342
|
const binding = scope.identifiers.get(expr.name);
|
|
@@ -295,6 +348,7 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
295
348
|
if (t.isLogicalExpression(expr) && expr.operator === "??") return classifyNullishChain(expr, scope);
|
|
296
349
|
if (t.isLogicalExpression(expr) && expr.operator === "||") return classifySimpleLogicalOr(expr, scope);
|
|
297
350
|
if (t.isConditionalExpression(expr)) return classifyConditional(expr, scope);
|
|
351
|
+
if (t.isCallExpression(expr)) return classifyFormatterCall(expr, scope);
|
|
298
352
|
return null;
|
|
299
353
|
}
|
|
300
354
|
function applyFieldVerdict(elementPath, verdict) {
|
|
@@ -307,6 +361,12 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
307
361
|
addAttr(elementPath, ATTR_FIELD, t.stringLiteral(verdict.field));
|
|
308
362
|
return;
|
|
309
363
|
}
|
|
364
|
+
if (verdict.kind === "formatted") {
|
|
365
|
+
addAttr(elementPath, ATTR_FIELD, t.stringLiteral(verdict.field));
|
|
366
|
+
addAttr(elementPath, ATTR_FIELD_STATE, t.stringLiteral("rendered"));
|
|
367
|
+
addAttr(elementPath, ATTR_FIELD_PARTS, t.stringLiteral(verdict.field));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
310
370
|
addAttr(elementPath, ATTR_FIELD, t.jsxExpressionContainer(verdict.expr));
|
|
311
371
|
}
|
|
312
372
|
function meaningfulChildPaths(elementPath) {
|
|
@@ -485,13 +545,42 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
485
545
|
if (!innerReturn) return null;
|
|
486
546
|
return resolveFieldProjection(innerReturn, { identifiers }, depth + 1);
|
|
487
547
|
}
|
|
548
|
+
function collectConditionalJsxBranchPaths(containerPath) {
|
|
549
|
+
const exprPath = containerPath.get("expression");
|
|
550
|
+
if (Array.isArray(exprPath)) return [];
|
|
551
|
+
if (exprPath.isLogicalExpression() && (exprPath.node.operator === "&&" || exprPath.node.operator === "||")) {
|
|
552
|
+
const rightPath = exprPath.get("right");
|
|
553
|
+
return !Array.isArray(rightPath) && rightPath.isJSXElement() ? [rightPath] : [];
|
|
554
|
+
}
|
|
555
|
+
if (exprPath.isConditionalExpression()) {
|
|
556
|
+
const branches = [];
|
|
557
|
+
const consPath = exprPath.get("consequent");
|
|
558
|
+
const altPath = exprPath.get("alternate");
|
|
559
|
+
if (!Array.isArray(consPath) && consPath.isJSXElement()) branches.push(consPath);
|
|
560
|
+
if (!Array.isArray(altPath) && altPath.isJSXElement()) branches.push(altPath);
|
|
561
|
+
return branches;
|
|
562
|
+
}
|
|
563
|
+
return [];
|
|
564
|
+
}
|
|
488
565
|
function walkFieldsWithinRow(elementPath, scope) {
|
|
489
566
|
const childPaths = meaningfulChildPaths(elementPath);
|
|
490
|
-
const
|
|
567
|
+
const conditionalBranchesByChild = /* @__PURE__ */ new Map();
|
|
568
|
+
for (const p of childPaths) {
|
|
569
|
+
if (!p.isJSXExpressionContainer()) continue;
|
|
570
|
+
const branches = collectConditionalJsxBranchPaths(p);
|
|
571
|
+
if (branches.length > 0) conditionalBranchesByChild.set(p, branches);
|
|
572
|
+
}
|
|
573
|
+
const hasElementChild = childPaths.some((p) => p.isJSXElement() || conditionalBranchesByChild.has(p));
|
|
491
574
|
if (hasElementChild) {
|
|
492
575
|
for (const childPath of childPaths) {
|
|
493
|
-
if (
|
|
494
|
-
|
|
576
|
+
if (childPath.isJSXElement()) {
|
|
577
|
+
handleStructuralChild(childPath, scope);
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
const branches = conditionalBranchesByChild.get(childPath);
|
|
581
|
+
if (branches) {
|
|
582
|
+
for (const branchPath of branches) handleStructuralChild(branchPath, scope);
|
|
583
|
+
}
|
|
495
584
|
}
|
|
496
585
|
return;
|
|
497
586
|
}
|
|
@@ -525,7 +614,11 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
525
614
|
if (!t.isMemberExpression(callee) || callee.computed) return;
|
|
526
615
|
if (!t.isIdentifier(callee.property, { name: "map" })) return;
|
|
527
616
|
if (!t.isExpression(callee.object)) return;
|
|
528
|
-
const
|
|
617
|
+
const calleePath = callPath.get("callee");
|
|
618
|
+
if (Array.isArray(calleePath) || !calleePath.isMemberExpression()) return;
|
|
619
|
+
const objectPath = calleePath.get("object");
|
|
620
|
+
if (Array.isArray(objectPath) || !objectPath.isExpression()) return;
|
|
621
|
+
const source = resolveSliceSource(objectPath, 0, false);
|
|
529
622
|
if (!source) return;
|
|
530
623
|
const [callback] = node.arguments;
|
|
531
624
|
if (!callback || !t.isArrowFunctionExpression(callback) && !t.isFunctionExpression(callback)) return;
|
|
@@ -14,6 +14,7 @@ var ATTR_COLLECTION_ORDER = "data-agent-collection-order";
|
|
|
14
14
|
var SLICE_ROOT_NAME = "slice";
|
|
15
15
|
var CHAIN_METHODS = /* @__PURE__ */ new Set(["filter", "slice", "sort", "map"]);
|
|
16
16
|
var MAX_SOURCE_CHAIN_HOPS = 4;
|
|
17
|
+
var COMPARISON_OPERATORS = /* @__PURE__ */ new Set(["==", "!=", "===", "!==", "<", "<=", ">", ">="]);
|
|
17
18
|
var MAX_PROJECTION_DEPTH = 3;
|
|
18
19
|
function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
19
20
|
const { fileExclude = /node_modules/, forwardingComponents = [] } = opts;
|
|
@@ -61,44 +62,72 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
61
62
|
if (t.isIdentifier(callee, { name: "useMemo" })) return true;
|
|
62
63
|
return t.isMemberExpression(callee) && !callee.computed && t.isIdentifier(callee.property, { name: "useMemo" });
|
|
63
64
|
}
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return resolveSliceSource(factory.body, scope, hops, hasSort);
|
|
65
|
+
function findSingleReturnArgument(blockPath) {
|
|
66
|
+
const returnPaths = [];
|
|
67
|
+
blockPath.traverse({
|
|
68
|
+
Function(inner) {
|
|
69
|
+
inner.skip();
|
|
70
|
+
},
|
|
71
|
+
ReturnStatement(rp) {
|
|
72
|
+
returnPaths.push(rp);
|
|
73
73
|
}
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
});
|
|
75
|
+
if (returnPaths.length !== 1) return null;
|
|
76
|
+
const argPath = returnPaths[0].get("argument");
|
|
77
|
+
if (Array.isArray(argPath) || !argPath.node || !argPath.isExpression()) return null;
|
|
78
|
+
return argPath;
|
|
79
|
+
}
|
|
80
|
+
function resolveSliceSource(path, hops, hasSort) {
|
|
81
|
+
if (path.isCallExpression()) {
|
|
82
|
+
const calleePath = path.get("callee");
|
|
83
|
+
if (Array.isArray(calleePath) || !calleePath.isExpression()) return null;
|
|
84
|
+
if (isUseMemoCallee(calleePath.node)) {
|
|
85
|
+
const argPaths = path.get("arguments");
|
|
86
|
+
const factoryPath = argPaths[0];
|
|
87
|
+
if (!factoryPath || !factoryPath.isArrowFunctionExpression()) return null;
|
|
88
|
+
const bodyPath = factoryPath.get("body");
|
|
89
|
+
if (Array.isArray(bodyPath)) return null;
|
|
90
|
+
if (bodyPath.isExpression()) {
|
|
91
|
+
return resolveSliceSource(bodyPath, hops, hasSort);
|
|
92
|
+
}
|
|
93
|
+
if (bodyPath.isBlockStatement()) {
|
|
94
|
+
const returnArgPath = findSingleReturnArgument(bodyPath);
|
|
95
|
+
if (!returnArgPath) return null;
|
|
96
|
+
return resolveSliceSource(returnArgPath, hops, hasSort);
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const calleeNode = calleePath.node;
|
|
101
|
+
if (t.isMemberExpression(calleeNode) && !calleeNode.computed && t.isIdentifier(calleeNode.property)) {
|
|
102
|
+
const method = calleeNode.property.name;
|
|
76
103
|
if (!CHAIN_METHODS.has(method)) return null;
|
|
77
|
-
|
|
78
|
-
|
|
104
|
+
const objectPath = calleePath.get("object");
|
|
105
|
+
if (Array.isArray(objectPath) || !objectPath.isExpression()) return null;
|
|
106
|
+
return resolveSliceSource(objectPath, hops, hasSort || method === "sort");
|
|
79
107
|
}
|
|
80
108
|
return null;
|
|
81
109
|
}
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
if (!
|
|
85
|
-
if (!
|
|
86
|
-
|
|
110
|
+
if (path.isMemberExpression()) {
|
|
111
|
+
const { object, property, computed } = path.node;
|
|
112
|
+
if (!t.isIdentifier(object, { name: SLICE_ROOT_NAME })) return null;
|
|
113
|
+
if (!isPageSliceBinding(path.scope)) return null;
|
|
114
|
+
if (!computed && t.isIdentifier(property)) {
|
|
115
|
+
return { entity: property.name, hasSort };
|
|
87
116
|
}
|
|
88
|
-
if (
|
|
89
|
-
return { entity:
|
|
117
|
+
if (computed && t.isStringLiteral(property)) {
|
|
118
|
+
return { entity: property.value, hasSort };
|
|
90
119
|
}
|
|
91
120
|
return null;
|
|
92
121
|
}
|
|
93
|
-
if (
|
|
122
|
+
if (path.isIdentifier()) {
|
|
94
123
|
if (hops >= MAX_SOURCE_CHAIN_HOPS) return null;
|
|
95
|
-
const binding = scope.getBinding(
|
|
124
|
+
const binding = path.scope.getBinding(path.node.name);
|
|
96
125
|
if (!binding || !binding.constant) return null;
|
|
97
126
|
const declarator = binding.path;
|
|
98
127
|
if (!declarator.isVariableDeclarator()) return null;
|
|
99
|
-
const
|
|
100
|
-
if (!
|
|
101
|
-
return resolveSliceSource(
|
|
128
|
+
const initPath = declarator.get("init");
|
|
129
|
+
if (Array.isArray(initPath) || !initPath.node || !initPath.isExpression()) return null;
|
|
130
|
+
return resolveSliceSource(initPath, hops + 1, hasSort);
|
|
102
131
|
}
|
|
103
132
|
return null;
|
|
104
133
|
}
|
|
@@ -236,11 +265,27 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
236
265
|
const field = fieldNameForBranch(expr.left, scope);
|
|
237
266
|
return field ? { kind: "static", field } : null;
|
|
238
267
|
}
|
|
268
|
+
function fieldNameForLiteralBranchTest(expr, scope) {
|
|
269
|
+
const direct = fieldNameForBranch(expr, scope);
|
|
270
|
+
if (direct) return direct;
|
|
271
|
+
if (t.isBinaryExpression(expr) && COMPARISON_OPERATORS.has(expr.operator)) {
|
|
272
|
+
if (t.isExpression(expr.left)) {
|
|
273
|
+
const leftField = fieldNameForBranch(expr.left, scope);
|
|
274
|
+
if (leftField) return leftField;
|
|
275
|
+
}
|
|
276
|
+
if (t.isExpression(expr.right)) {
|
|
277
|
+
const rightField = fieldNameForBranch(expr.right, scope);
|
|
278
|
+
if (rightField) return rightField;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
239
283
|
function classifyConditional(expr, scope) {
|
|
240
284
|
const { test, consequent, alternate } = expr;
|
|
241
285
|
if (!t.isExpression(test) || !t.isExpression(consequent) || !t.isExpression(alternate)) return null;
|
|
242
286
|
if (isStaticLiteralExpr(consequent) && isStaticLiteralExpr(alternate)) {
|
|
243
|
-
|
|
287
|
+
if (containsEvaluation(test)) return null;
|
|
288
|
+
const testField = fieldNameForLiteralBranchTest(test, scope);
|
|
244
289
|
return testField ? { kind: "static", field: testField } : null;
|
|
245
290
|
}
|
|
246
291
|
const consField = fieldNameForBranch(consequent, scope);
|
|
@@ -253,6 +298,14 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
253
298
|
expr: t.conditionalExpression(t.cloneNode(test), t.stringLiteral(consField), t.stringLiteral(altField))
|
|
254
299
|
};
|
|
255
300
|
}
|
|
301
|
+
function classifyFormatterCall(expr, scope) {
|
|
302
|
+
if (expr.arguments.length !== 1) return null;
|
|
303
|
+
const [arg] = expr.arguments;
|
|
304
|
+
if (!t.isMemberExpression(arg) && !t.isOptionalMemberExpression(arg)) return null;
|
|
305
|
+
const verdict = classifyMemberField(arg, scope);
|
|
306
|
+
if (!verdict || verdict.kind !== "static") return null;
|
|
307
|
+
return { kind: "formatted", field: verdict.field };
|
|
308
|
+
}
|
|
256
309
|
function classifyFieldExpr(expr, scope) {
|
|
257
310
|
if (t.isIdentifier(expr)) {
|
|
258
311
|
const binding = scope.identifiers.get(expr.name);
|
|
@@ -264,6 +317,7 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
264
317
|
if (t.isLogicalExpression(expr) && expr.operator === "??") return classifyNullishChain(expr, scope);
|
|
265
318
|
if (t.isLogicalExpression(expr) && expr.operator === "||") return classifySimpleLogicalOr(expr, scope);
|
|
266
319
|
if (t.isConditionalExpression(expr)) return classifyConditional(expr, scope);
|
|
320
|
+
if (t.isCallExpression(expr)) return classifyFormatterCall(expr, scope);
|
|
267
321
|
return null;
|
|
268
322
|
}
|
|
269
323
|
function applyFieldVerdict(elementPath, verdict) {
|
|
@@ -276,6 +330,12 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
276
330
|
addAttr(elementPath, ATTR_FIELD, t.stringLiteral(verdict.field));
|
|
277
331
|
return;
|
|
278
332
|
}
|
|
333
|
+
if (verdict.kind === "formatted") {
|
|
334
|
+
addAttr(elementPath, ATTR_FIELD, t.stringLiteral(verdict.field));
|
|
335
|
+
addAttr(elementPath, ATTR_FIELD_STATE, t.stringLiteral("rendered"));
|
|
336
|
+
addAttr(elementPath, ATTR_FIELD_PARTS, t.stringLiteral(verdict.field));
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
279
339
|
addAttr(elementPath, ATTR_FIELD, t.jsxExpressionContainer(verdict.expr));
|
|
280
340
|
}
|
|
281
341
|
function meaningfulChildPaths(elementPath) {
|
|
@@ -454,13 +514,42 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
454
514
|
if (!innerReturn) return null;
|
|
455
515
|
return resolveFieldProjection(innerReturn, { identifiers }, depth + 1);
|
|
456
516
|
}
|
|
517
|
+
function collectConditionalJsxBranchPaths(containerPath) {
|
|
518
|
+
const exprPath = containerPath.get("expression");
|
|
519
|
+
if (Array.isArray(exprPath)) return [];
|
|
520
|
+
if (exprPath.isLogicalExpression() && (exprPath.node.operator === "&&" || exprPath.node.operator === "||")) {
|
|
521
|
+
const rightPath = exprPath.get("right");
|
|
522
|
+
return !Array.isArray(rightPath) && rightPath.isJSXElement() ? [rightPath] : [];
|
|
523
|
+
}
|
|
524
|
+
if (exprPath.isConditionalExpression()) {
|
|
525
|
+
const branches = [];
|
|
526
|
+
const consPath = exprPath.get("consequent");
|
|
527
|
+
const altPath = exprPath.get("alternate");
|
|
528
|
+
if (!Array.isArray(consPath) && consPath.isJSXElement()) branches.push(consPath);
|
|
529
|
+
if (!Array.isArray(altPath) && altPath.isJSXElement()) branches.push(altPath);
|
|
530
|
+
return branches;
|
|
531
|
+
}
|
|
532
|
+
return [];
|
|
533
|
+
}
|
|
457
534
|
function walkFieldsWithinRow(elementPath, scope) {
|
|
458
535
|
const childPaths = meaningfulChildPaths(elementPath);
|
|
459
|
-
const
|
|
536
|
+
const conditionalBranchesByChild = /* @__PURE__ */ new Map();
|
|
537
|
+
for (const p of childPaths) {
|
|
538
|
+
if (!p.isJSXExpressionContainer()) continue;
|
|
539
|
+
const branches = collectConditionalJsxBranchPaths(p);
|
|
540
|
+
if (branches.length > 0) conditionalBranchesByChild.set(p, branches);
|
|
541
|
+
}
|
|
542
|
+
const hasElementChild = childPaths.some((p) => p.isJSXElement() || conditionalBranchesByChild.has(p));
|
|
460
543
|
if (hasElementChild) {
|
|
461
544
|
for (const childPath of childPaths) {
|
|
462
|
-
if (
|
|
463
|
-
|
|
545
|
+
if (childPath.isJSXElement()) {
|
|
546
|
+
handleStructuralChild(childPath, scope);
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
const branches = conditionalBranchesByChild.get(childPath);
|
|
550
|
+
if (branches) {
|
|
551
|
+
for (const branchPath of branches) handleStructuralChild(branchPath, scope);
|
|
552
|
+
}
|
|
464
553
|
}
|
|
465
554
|
return;
|
|
466
555
|
}
|
|
@@ -494,7 +583,11 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
494
583
|
if (!t.isMemberExpression(callee) || callee.computed) return;
|
|
495
584
|
if (!t.isIdentifier(callee.property, { name: "map" })) return;
|
|
496
585
|
if (!t.isExpression(callee.object)) return;
|
|
497
|
-
const
|
|
586
|
+
const calleePath = callPath.get("callee");
|
|
587
|
+
if (Array.isArray(calleePath) || !calleePath.isMemberExpression()) return;
|
|
588
|
+
const objectPath = calleePath.get("object");
|
|
589
|
+
if (Array.isArray(objectPath) || !objectPath.isExpression()) return;
|
|
590
|
+
const source = resolveSliceSource(objectPath, 0, false);
|
|
498
591
|
if (!source) return;
|
|
499
592
|
const [callback] = node.arguments;
|
|
500
593
|
if (!callback || !t.isArrowFunctionExpression(callback) && !t.isFunctionExpression(callback)) return;
|
|
@@ -615,6 +615,122 @@
|
|
|
615
615
|
}
|
|
616
616
|
}
|
|
617
617
|
|
|
618
|
+
// ARCH-1065 M3 (v2.3.0) — the nearest data-agent-row-bearing element
|
|
619
|
+
// SELF-outward. This is `nearestRowId` above but returning the ELEMENT
|
|
620
|
+
// (not just its id), because QUERY_COLLECTION_ORDER needs the row
|
|
621
|
+
// root's sibling `data-agent-entity`/`data-agent-collection-order`
|
|
622
|
+
// frame too, not only the row id. Nested collections resolve correctly
|
|
623
|
+
// "for free": walking self-outward finds the INNERMOST row root first,
|
|
624
|
+
// i.e. the nearest enclosing collection of the target node.
|
|
625
|
+
function nearestRowRootElement(el) {
|
|
626
|
+
var node = el
|
|
627
|
+
for (var depth = 0; node && depth < MAX_ANCHOR_CHAIN; depth++) {
|
|
628
|
+
if (node.nodeType === 1 && typeof node.getAttribute === "function") {
|
|
629
|
+
var rowId = node.getAttribute("data-agent-row")
|
|
630
|
+
if (typeof rowId === "string" && rowId !== "") return node
|
|
631
|
+
}
|
|
632
|
+
node = node.parentElement
|
|
633
|
+
}
|
|
634
|
+
return null
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// The first row-root element stamped with the given entity name,
|
|
638
|
+
// anywhere in the document. Used when QUERY_COLLECTION_ORDER is
|
|
639
|
+
// addressed by `entity` alone (no `selector`) — the entity-name
|
|
640
|
+
// targeting mode.
|
|
641
|
+
function findFirstRowRootForEntity(entity) {
|
|
642
|
+
if (typeof entity !== "string" || entity === "") return null
|
|
643
|
+
try {
|
|
644
|
+
return safeQuerySelector(
|
|
645
|
+
'[data-agent-entity="' +
|
|
646
|
+
escapeAttributeSelectorValue(entity) +
|
|
647
|
+
'"][data-agent-row]'
|
|
648
|
+
)
|
|
649
|
+
} catch (e) {
|
|
650
|
+
return null
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Every row root that belongs to the SAME collection instantiation as
|
|
655
|
+
// `rowRootEl`: the babel codemod (M2) stamps each `.map()` row's root
|
|
656
|
+
// element as a DIRECT child of whatever wraps the `.map()` call, so
|
|
657
|
+
// siblings sharing `data-agent-entity` under `rowRootEl`'s parent ARE
|
|
658
|
+
// the collection, in DOM order. Falls back to `[rowRootEl]` alone when
|
|
659
|
+
// no parent is reachable (defensive — should not happen for an
|
|
660
|
+
// attached element).
|
|
661
|
+
function collectionRowElements(rowRootEl, entity) {
|
|
662
|
+
var parent = rowRootEl.parentElement
|
|
663
|
+
if (!parent) return [rowRootEl]
|
|
664
|
+
var siblings = []
|
|
665
|
+
var kids = parent.children
|
|
666
|
+
for (var i = 0; i < kids.length; i++) {
|
|
667
|
+
var kid = kids[i]
|
|
668
|
+
if (
|
|
669
|
+
kid.nodeType === 1 &&
|
|
670
|
+
typeof kid.getAttribute === "function" &&
|
|
671
|
+
kid.getAttribute("data-agent-entity") === entity &&
|
|
672
|
+
kid.hasAttribute("data-agent-row")
|
|
673
|
+
) {
|
|
674
|
+
siblings.push(kid)
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return siblings.length > 0 ? siblings : [rowRootEl]
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// ARCH-1065 M3 (v2.3.0) — {entity, order, rowIds, rowCount} for the
|
|
681
|
+
// collection enclosing a target node, read live off the DOM
|
|
682
|
+
// (data-agent-entity/-row/-collection-order, D18) and cross-checked
|
|
683
|
+
// against window.__slice[entity].length when the slice is available.
|
|
684
|
+
//
|
|
685
|
+
// `order` fails CLOSED ("derived") on anything but an explicit
|
|
686
|
+
// "stable" attribute value. babel-plugin-collection-binding stamps
|
|
687
|
+
// entity/row/collection-order ATOMICALLY — three addAttr calls back to
|
|
688
|
+
// back with no branch between them — and no other emitter of
|
|
689
|
+
// data-agent-row exists. So data-agent-row present WITHOUT
|
|
690
|
+
// data-agent-collection-order can only mean the stamping contract
|
|
691
|
+
// never ran for this element: the order is UNKNOWN, not "stable".
|
|
692
|
+
// Defaulting unknown to "stable" would let the FE permit a drag whose
|
|
693
|
+
// Save can't be honored (silent, user-visible corruption); defaulting
|
|
694
|
+
// to "derived" only disables reorder on markup we can't vouch for.
|
|
695
|
+
function queryCollectionOrder(rowRootEl) {
|
|
696
|
+
if (!rowRootEl) return null
|
|
697
|
+
var entity = rowRootEl.getAttribute("data-agent-entity")
|
|
698
|
+
if (typeof entity !== "string" || entity === "") return null
|
|
699
|
+
|
|
700
|
+
var orderAttr = rowRootEl.getAttribute("data-agent-collection-order")
|
|
701
|
+
var order = orderAttr === "stable" ? "stable" : "derived"
|
|
702
|
+
|
|
703
|
+
var rowElements = collectionRowElements(rowRootEl, entity)
|
|
704
|
+
var rowIds = []
|
|
705
|
+
for (var i = 0; i < rowElements.length; i++) {
|
|
706
|
+
var rowId = rowElements[i].getAttribute("data-agent-row")
|
|
707
|
+
if (typeof rowId === "string" && rowId !== "") rowIds.push(rowId)
|
|
708
|
+
}
|
|
709
|
+
var domRowCount = rowIds.length
|
|
710
|
+
|
|
711
|
+
var sliceRows = resolveSliceEntityRows(entity)
|
|
712
|
+
var sliceRowCount = sliceRows ? sliceRows.length : null
|
|
713
|
+
|
|
714
|
+
// `rowCount` is the A17 primary field — DOM population, the same
|
|
715
|
+
// rows the FE samples values from. `domRowCount`/`sliceRowCount` are
|
|
716
|
+
// the divergence-diagnostic pair (identical to rowCount when the
|
|
717
|
+
// slice agrees, or absent from parity entirely when the slice is
|
|
718
|
+
// unavailable). Known limit: two same-entity `.map()`s that happen
|
|
719
|
+
// to share one DOM parent merge into a single reported collection —
|
|
720
|
+
// rare by construction (siblings of a `.map()` output share a
|
|
721
|
+
// parent by definition), and there is no collection-INSTANCE stamp
|
|
722
|
+
// (only per-row entity/row/order) to disambiguate two side-by-side
|
|
723
|
+
// instantiations of the same entity.
|
|
724
|
+
return {
|
|
725
|
+
entity: entity,
|
|
726
|
+
order: order,
|
|
727
|
+
rowIds: rowIds,
|
|
728
|
+
rowCount: domRowCount,
|
|
729
|
+
domRowCount: domRowCount,
|
|
730
|
+
sliceRowCount: sliceRowCount,
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
618
734
|
if (window._inspectorSharedTargetObserver) {
|
|
619
735
|
try {
|
|
620
736
|
window._inspectorSharedTargetObserver.disconnect()
|
|
@@ -1231,7 +1347,10 @@
|
|
|
1231
1347
|
// `QUERY_SLICE_FIELD` in the message protocol, and
|
|
1232
1348
|
// `capabilities.bindingContract` below. No existing field changed
|
|
1233
1349
|
// shape, so `protocolVersion` stays 1.
|
|
1234
|
-
|
|
1350
|
+
// 2.3.0 (ARCH-1065 A17-support): additive only — `QUERY_COLLECTION_ORDER`
|
|
1351
|
+
// in the message protocol and `capabilities.collectionOrderQuery`
|
|
1352
|
+
// below. No existing field changed shape, so `protocolVersion` stays 1.
|
|
1353
|
+
version: "2.3.0",
|
|
1235
1354
|
protocolVersion: 1,
|
|
1236
1355
|
capabilities: {
|
|
1237
1356
|
// 2.1.1: function-valued __editorMeta props are invoked before any
|
|
@@ -1257,6 +1376,12 @@
|
|
|
1257
1376
|
// before relying on either — a frozen pre-1065 inspector has
|
|
1258
1377
|
// neither.
|
|
1259
1378
|
collectionBinding: true,
|
|
1379
|
+
// ARCH-1065 A17-support (v2.3.0): `QUERY_COLLECTION_ORDER` resolves
|
|
1380
|
+
// the enclosing collection's order ("stable"/"derived") and row
|
|
1381
|
+
// ids/count for a target node, off the same data-agent-* grammar
|
|
1382
|
+
// as `collectionBinding`. Hosts MUST feature-detect on this flag —
|
|
1383
|
+
// a frozen pre-2.3.0 inspector has no handler for the message.
|
|
1384
|
+
collectionOrderQuery: true,
|
|
1260
1385
|
// The BUILD-TIME contract version this specific page was stamped
|
|
1261
1386
|
// with, forwarded verbatim from `<meta name="archie-binding-contract">`
|
|
1262
1387
|
// when the kit build emitted one — null on any page built before
|
|
@@ -3881,6 +4006,58 @@
|
|
|
3881
4006
|
field: queryField,
|
|
3882
4007
|
result: queryResult,
|
|
3883
4008
|
})
|
|
4009
|
+
} else if (event.data && event.data.type === "QUERY_COLLECTION_ORDER") {
|
|
4010
|
+
// ARCH-1065 M3 (v2.3.0) — {order, rowIds, rowCount} for the
|
|
4011
|
+
// collection enclosing a target node, so the FE can refuse
|
|
4012
|
+
// reorder-at-drag on "derived" collections and drive the
|
|
4013
|
+
// repetition-aware enum threshold off rowCount. Mirrors
|
|
4014
|
+
// QUERY_SLICE_FIELD's request/response envelope and graceful
|
|
4015
|
+
// (result: null) failure shape.
|
|
4016
|
+
//
|
|
4017
|
+
// Targeting, in priority order:
|
|
4018
|
+
// 1. `selector` — a CSS selector for ANY element inside the
|
|
4019
|
+
// collection (a row, or a leaf nested inside one); resolved
|
|
4020
|
+
// the same way other selector-addressed messages resolve one
|
|
4021
|
+
// (`findElementBySelector`), then walked up to the nearest
|
|
4022
|
+
// enclosing row root.
|
|
4023
|
+
// 2. `entity` alone (no selector) — the first row root in the
|
|
4024
|
+
// document stamped with that entity name.
|
|
4025
|
+
// 3. neither — mirrors QUERY_SLICE_FIELD's no-rowId fallback:
|
|
4026
|
+
// the currently selected (else hovered) element.
|
|
4027
|
+
var collectionQuerySelector =
|
|
4028
|
+
typeof event.data.selector === "string" && event.data.selector !== ""
|
|
4029
|
+
? event.data.selector
|
|
4030
|
+
: null
|
|
4031
|
+
var collectionQueryEntity =
|
|
4032
|
+
typeof event.data.entity === "string" && event.data.entity !== ""
|
|
4033
|
+
? event.data.entity
|
|
4034
|
+
: null
|
|
4035
|
+
|
|
4036
|
+
var collectionRowRoot = null
|
|
4037
|
+
if (collectionQuerySelector) {
|
|
4038
|
+
var collectionTargetEl = findElementBySelector(collectionQuerySelector)
|
|
4039
|
+
collectionRowRoot = collectionTargetEl
|
|
4040
|
+
? nearestRowRootElement(collectionTargetEl)
|
|
4041
|
+
: null
|
|
4042
|
+
} else if (collectionQueryEntity) {
|
|
4043
|
+
collectionRowRoot = findFirstRowRootForEntity(collectionQueryEntity)
|
|
4044
|
+
} else {
|
|
4045
|
+
var collectionContextEl = getSelectedElement() || highlightedElement
|
|
4046
|
+
collectionRowRoot = collectionContextEl
|
|
4047
|
+
? nearestRowRootElement(collectionContextEl)
|
|
4048
|
+
: null
|
|
4049
|
+
}
|
|
4050
|
+
|
|
4051
|
+
var collectionQueryResult = queryCollectionOrder(collectionRowRoot)
|
|
4052
|
+
|
|
4053
|
+
postMessageToParent({
|
|
4054
|
+
type: "COLLECTION_ORDER_RESULT",
|
|
4055
|
+
requestId: event.data.requestId,
|
|
4056
|
+
entity: collectionQueryResult
|
|
4057
|
+
? collectionQueryResult.entity
|
|
4058
|
+
: collectionQueryEntity,
|
|
4059
|
+
result: collectionQueryResult,
|
|
4060
|
+
})
|
|
3884
4061
|
}
|
|
3885
4062
|
}
|
|
3886
4063
|
|