@archie/devtools 0.0.40 → 0.0.42
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/{chunk-6QB35SLQ.mjs → chunk-O3UMTM4N.mjs} +89 -3
- package/dist/collection-binding.mjs +1 -1
- package/dist/index.d.mts +44 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +89 -3
- package/dist/index.mjs +1 -1
- package/dist/inspector.standalone.js +125 -1
- package/package.json +1 -1
|
@@ -78,6 +78,9 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
78
78
|
}
|
|
79
79
|
return "";
|
|
80
80
|
}
|
|
81
|
+
function isIntrinsicTagName(name) {
|
|
82
|
+
return t.isJSXIdentifier(name) && /^[a-z]/.test(name.name);
|
|
83
|
+
}
|
|
81
84
|
function isHostLikeMemberExpressionName(name) {
|
|
82
85
|
if (!t.isJSXMemberExpression(name)) return false;
|
|
83
86
|
const propertyName = name.property.name;
|
|
@@ -168,6 +171,18 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
168
171
|
}
|
|
169
172
|
return hasAny ? t.objectExpression(props) : null;
|
|
170
173
|
}
|
|
174
|
+
function describeChildrenText(opening) {
|
|
175
|
+
const elementPath = opening.parentPath;
|
|
176
|
+
if (!elementPath || !elementPath.isJSXElement()) return null;
|
|
177
|
+
const element = elementPath.node;
|
|
178
|
+
if (!element.closingElement) return null;
|
|
179
|
+
const children = element.children;
|
|
180
|
+
if (children.length === 0) return "";
|
|
181
|
+
if (children.length !== 1) return "__dynamic__";
|
|
182
|
+
const [only] = children;
|
|
183
|
+
if (!t.isJSXText(only)) return "__dynamic__";
|
|
184
|
+
return only.value.trim();
|
|
185
|
+
}
|
|
171
186
|
function isMetaStripSpreadExpression(arg) {
|
|
172
187
|
if (!t.isCallExpression(arg)) return false;
|
|
173
188
|
if (!t.isArrowFunctionExpression(arg.callee)) return false;
|
|
@@ -275,6 +290,14 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
275
290
|
}
|
|
276
291
|
return map;
|
|
277
292
|
}
|
|
293
|
+
function restParamName(pattern) {
|
|
294
|
+
for (const prop of pattern.properties) {
|
|
295
|
+
if (!t.isRestElement(prop)) continue;
|
|
296
|
+
if (t.isIdentifier(prop.argument)) return prop.argument.name;
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
278
301
|
function resolvePropContext(path2) {
|
|
279
302
|
let fn = path2.getFunctionParent();
|
|
280
303
|
while (fn) {
|
|
@@ -283,7 +306,9 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
283
306
|
return {
|
|
284
307
|
componentScope: fn.scope,
|
|
285
308
|
propMap: buildPropMap(first),
|
|
286
|
-
componentName: getFunctionName(fn)
|
|
309
|
+
componentName: getFunctionName(fn),
|
|
310
|
+
restParam: restParamName(first),
|
|
311
|
+
componentPath: fn
|
|
287
312
|
};
|
|
288
313
|
}
|
|
289
314
|
fn = fn.getFunctionParent();
|
|
@@ -403,12 +428,62 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
403
428
|
const props = collectPropIdentifiers(expr, scope, ctx);
|
|
404
429
|
return props.length > 0 ? { kind: "formatted", prop: props[0] } : null;
|
|
405
430
|
}
|
|
431
|
+
function unwrapSanitizedSpread(arg) {
|
|
432
|
+
if (!t.isCallExpression(arg)) return arg;
|
|
433
|
+
if (!isMetaStripSpreadExpression(arg)) return arg;
|
|
434
|
+
if (arg.arguments.length !== 1) return arg;
|
|
435
|
+
const [payload] = arg.arguments;
|
|
436
|
+
if (!t.isObjectExpression(payload) || payload.properties.length !== 1) return arg;
|
|
437
|
+
const [only] = payload.properties;
|
|
438
|
+
if (!t.isSpreadElement(only)) return arg;
|
|
439
|
+
return only.argument;
|
|
440
|
+
}
|
|
441
|
+
function forwardsOwnerRestChildren(opening, ctx) {
|
|
442
|
+
if (ctx.restParam === null) return false;
|
|
443
|
+
const elementPath = opening.parentPath;
|
|
444
|
+
if (!elementPath || !elementPath.isJSXElement()) return false;
|
|
445
|
+
if (meaningfulChildren(t, elementPath.node.children).length > 0) return false;
|
|
446
|
+
let spreadsRest = 0;
|
|
447
|
+
for (const attr of opening.node.attributes) {
|
|
448
|
+
if (t.isJSXAttribute(attr)) {
|
|
449
|
+
if (t.isJSXIdentifier(attr.name, { name: "children" })) return false;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (!t.isJSXSpreadAttribute(attr)) continue;
|
|
453
|
+
const payload = unwrapSanitizedSpread(attr.argument);
|
|
454
|
+
if (!t.isIdentifier(payload, { name: ctx.restParam })) return false;
|
|
455
|
+
spreadsRest += 1;
|
|
456
|
+
}
|
|
457
|
+
if (spreadsRest !== 1) return false;
|
|
458
|
+
const binding = opening.scope.getBinding(ctx.restParam);
|
|
459
|
+
if (!binding) return false;
|
|
460
|
+
if (binding.scope !== ctx.componentScope) return false;
|
|
461
|
+
return binding.constant;
|
|
462
|
+
}
|
|
463
|
+
const restForwarderCounts = /* @__PURE__ */ new WeakMap();
|
|
464
|
+
function countRestForwarders(ctx) {
|
|
465
|
+
const cached = restForwarderCounts.get(ctx.componentPath.node);
|
|
466
|
+
if (cached !== void 0) return cached;
|
|
467
|
+
let count = 0;
|
|
468
|
+
ctx.componentPath.traverse({
|
|
469
|
+
JSXOpeningElement(inner) {
|
|
470
|
+
if (forwardsOwnerRestChildren(inner, ctx)) count += 1;
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
restForwarderCounts.set(ctx.componentPath.node, count);
|
|
474
|
+
return count;
|
|
475
|
+
}
|
|
406
476
|
function collectPropBindings(path2, attrs) {
|
|
407
477
|
const ctx = resolvePropContext(path2);
|
|
408
|
-
if (!ctx || ctx.propMap.size === 0
|
|
478
|
+
if (!ctx || ctx.propMap.size === 0 && ctx.restParam === null) {
|
|
479
|
+
return { bindings: [], owner: null };
|
|
480
|
+
}
|
|
409
481
|
if (ctx.componentName === null) return { bindings: [], owner: null };
|
|
410
482
|
const scope = path2.scope;
|
|
411
483
|
const bindings = [];
|
|
484
|
+
if (forwardsOwnerRestChildren(path2, ctx) && countRestForwarders(ctx) === 1) {
|
|
485
|
+
bindings.push({ target: "children", prop: "children", kind: "direct" });
|
|
486
|
+
}
|
|
412
487
|
const elementPath = path2.parentPath;
|
|
413
488
|
if (elementPath && elementPath.isJSXElement()) {
|
|
414
489
|
const children = meaningfulChildren(t, elementPath.node.children);
|
|
@@ -510,7 +585,9 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
510
585
|
return;
|
|
511
586
|
}
|
|
512
587
|
let rootName = null;
|
|
513
|
-
if (
|
|
588
|
+
if (isIntrinsicTagName(path2.node.name)) {
|
|
589
|
+
rootName = null;
|
|
590
|
+
} else if (t.isJSXIdentifier(path2.node.name)) {
|
|
514
591
|
rootName = path2.node.name.name;
|
|
515
592
|
} else if (t.isJSXMemberExpression(path2.node.name)) {
|
|
516
593
|
let cur = path2.node.name;
|
|
@@ -572,6 +649,15 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
572
649
|
t.objectProperty(t.identifier("slot"), t.stringLiteral(slot))
|
|
573
650
|
);
|
|
574
651
|
}
|
|
652
|
+
if (!isHostElement) {
|
|
653
|
+
const childrenText = describeChildrenText(path2);
|
|
654
|
+
metaObj.properties.push(
|
|
655
|
+
t.objectProperty(
|
|
656
|
+
t.identifier("childrenText"),
|
|
657
|
+
childrenText === null ? t.nullLiteral() : t.stringLiteral(childrenText)
|
|
658
|
+
)
|
|
659
|
+
);
|
|
660
|
+
}
|
|
575
661
|
if (bindings.length > 0) {
|
|
576
662
|
metaObj.properties.push(
|
|
577
663
|
t.objectProperty(t.identifier("propBindings"), propBindingsToAst(bindings))
|
package/dist/index.d.mts
CHANGED
|
@@ -12,6 +12,7 @@ import { types, PluginObj } from '@babel/core';
|
|
|
12
12
|
* nodeId, file, line, col, componentName, staticProps, movable, parentNodeId
|
|
13
13
|
* + (ARCH-883 F1) slot, propBindings, propOwner
|
|
14
14
|
* + (ARCH-883-KOI) origin
|
|
15
|
+
* + (ARCH-1444) childrenText — non-host elements only
|
|
15
16
|
*
|
|
16
17
|
* At runtime the Fiber bridge reads `memoizedProps.__editorMeta` to resolve
|
|
17
18
|
* element identity without any DOM attributes.
|
|
@@ -169,6 +170,17 @@ declare function babelPluginEditorMeta({ types: t }: {
|
|
|
169
170
|
* `id`/`htmlFor`/`name`/`type`/`role`/`slot`) are downgraded to `formatted` by the
|
|
170
171
|
* PRODUCER, because no usage value could make them a text edit. The consumer does
|
|
171
172
|
* not need to re-derive that list; it only needs the allowlist above.
|
|
173
|
+
*
|
|
174
|
+
* ARCH-1443: `{ target: 'children', prop: 'children', kind: 'direct' }` is emitted
|
|
175
|
+
* for an element that forwards its component's props REST and renders no children
|
|
176
|
+
* of its own — the shape EVERY ejected shadcn-style primitive has
|
|
177
|
+
* (`({ className, ...p }) => <h3 className={…} {...p} />`). Nothing about the
|
|
178
|
+
* allowlist changes: the usage value of `children` is the JSX children written at
|
|
179
|
+
* the usage site (`<CardTitle>Cómo funciona</CardTitle>`), and the same
|
|
180
|
+
* usage-value law applies to it — plain text is writable, a nested element or a
|
|
181
|
+
* computed expression is a gap. The producer refuses whenever more than one
|
|
182
|
+
* element in the component forwards the rest, since one `children` prop cannot
|
|
183
|
+
* name which of them an edit is about.
|
|
172
184
|
*/
|
|
173
185
|
interface PropBinding {
|
|
174
186
|
/**
|
|
@@ -216,6 +228,38 @@ interface EditorMeta {
|
|
|
216
228
|
componentName: string;
|
|
217
229
|
/** Safely serializable literal prop values. Dynamic expressions are "__dynamic__". */
|
|
218
230
|
staticProps: Record<string, unknown> | null;
|
|
231
|
+
/**
|
|
232
|
+
* ARCH-1444 — the SHAPE of this element's JSX children, and their text when
|
|
233
|
+
* that text is plain. `staticProps` is built from ATTRIBUTES only, so without
|
|
234
|
+
* this field the `children` prop is the one binding target whose value at the
|
|
235
|
+
* usage a consumer cannot see — and the usage-value law above ("plain text is
|
|
236
|
+
* writable, a nested element or a computed expression is a gap") is stated in
|
|
237
|
+
* terms of exactly that value.
|
|
238
|
+
*
|
|
239
|
+
* `null` — the element is SELF-CLOSING: no children container at
|
|
240
|
+
* all. NOT the same as `''` (`<X></X>`), which can take a
|
|
241
|
+
* text write; `<X />` cannot without a structural rewrite.
|
|
242
|
+
* `''` — an empty children container.
|
|
243
|
+
* `'__dynamic__'` — anything that is not exactly one JSXText child: an
|
|
244
|
+
* expression container, a nested element, a comment, or
|
|
245
|
+
* several children. Same sentinel as `staticProps`.
|
|
246
|
+
* any other string— the sole JSXText child, TRIMMED (JSX itself trims
|
|
247
|
+
* leading/trailing whitespace-with-newlines, so the
|
|
248
|
+
* trimmed form is what renders). A newline that SURVIVES
|
|
249
|
+
* the trim is real multi-line content: a consumer that
|
|
250
|
+
* writes by splicing must refuse it, because replacing it
|
|
251
|
+
* with one line deletes source lines and invalidates every
|
|
252
|
+
* anchor below.
|
|
253
|
+
*
|
|
254
|
+
* Emitted for NON-HOST elements only — a usage is always a component tag, and
|
|
255
|
+
* stamping every `<div>` would be weight for an answer nothing asks.
|
|
256
|
+
*
|
|
257
|
+
* ABSENT (`undefined`) on a host element and on every bundle built before this
|
|
258
|
+
* change. Absence is NOT "no children": it means the page cannot answer the
|
|
259
|
+
* question, and a consumer must gap the edit rather than assume (the same law
|
|
260
|
+
* `origin`'s docstring states at length).
|
|
261
|
+
*/
|
|
262
|
+
childrenText?: string | null;
|
|
219
263
|
/**
|
|
220
264
|
* Whether the JSX node is in a reorderable position (2+ siblings).
|
|
221
265
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { types, PluginObj } from '@babel/core';
|
|
|
12
12
|
* nodeId, file, line, col, componentName, staticProps, movable, parentNodeId
|
|
13
13
|
* + (ARCH-883 F1) slot, propBindings, propOwner
|
|
14
14
|
* + (ARCH-883-KOI) origin
|
|
15
|
+
* + (ARCH-1444) childrenText — non-host elements only
|
|
15
16
|
*
|
|
16
17
|
* At runtime the Fiber bridge reads `memoizedProps.__editorMeta` to resolve
|
|
17
18
|
* element identity without any DOM attributes.
|
|
@@ -169,6 +170,17 @@ declare function babelPluginEditorMeta({ types: t }: {
|
|
|
169
170
|
* `id`/`htmlFor`/`name`/`type`/`role`/`slot`) are downgraded to `formatted` by the
|
|
170
171
|
* PRODUCER, because no usage value could make them a text edit. The consumer does
|
|
171
172
|
* not need to re-derive that list; it only needs the allowlist above.
|
|
173
|
+
*
|
|
174
|
+
* ARCH-1443: `{ target: 'children', prop: 'children', kind: 'direct' }` is emitted
|
|
175
|
+
* for an element that forwards its component's props REST and renders no children
|
|
176
|
+
* of its own — the shape EVERY ejected shadcn-style primitive has
|
|
177
|
+
* (`({ className, ...p }) => <h3 className={…} {...p} />`). Nothing about the
|
|
178
|
+
* allowlist changes: the usage value of `children` is the JSX children written at
|
|
179
|
+
* the usage site (`<CardTitle>Cómo funciona</CardTitle>`), and the same
|
|
180
|
+
* usage-value law applies to it — plain text is writable, a nested element or a
|
|
181
|
+
* computed expression is a gap. The producer refuses whenever more than one
|
|
182
|
+
* element in the component forwards the rest, since one `children` prop cannot
|
|
183
|
+
* name which of them an edit is about.
|
|
172
184
|
*/
|
|
173
185
|
interface PropBinding {
|
|
174
186
|
/**
|
|
@@ -216,6 +228,38 @@ interface EditorMeta {
|
|
|
216
228
|
componentName: string;
|
|
217
229
|
/** Safely serializable literal prop values. Dynamic expressions are "__dynamic__". */
|
|
218
230
|
staticProps: Record<string, unknown> | null;
|
|
231
|
+
/**
|
|
232
|
+
* ARCH-1444 — the SHAPE of this element's JSX children, and their text when
|
|
233
|
+
* that text is plain. `staticProps` is built from ATTRIBUTES only, so without
|
|
234
|
+
* this field the `children` prop is the one binding target whose value at the
|
|
235
|
+
* usage a consumer cannot see — and the usage-value law above ("plain text is
|
|
236
|
+
* writable, a nested element or a computed expression is a gap") is stated in
|
|
237
|
+
* terms of exactly that value.
|
|
238
|
+
*
|
|
239
|
+
* `null` — the element is SELF-CLOSING: no children container at
|
|
240
|
+
* all. NOT the same as `''` (`<X></X>`), which can take a
|
|
241
|
+
* text write; `<X />` cannot without a structural rewrite.
|
|
242
|
+
* `''` — an empty children container.
|
|
243
|
+
* `'__dynamic__'` — anything that is not exactly one JSXText child: an
|
|
244
|
+
* expression container, a nested element, a comment, or
|
|
245
|
+
* several children. Same sentinel as `staticProps`.
|
|
246
|
+
* any other string— the sole JSXText child, TRIMMED (JSX itself trims
|
|
247
|
+
* leading/trailing whitespace-with-newlines, so the
|
|
248
|
+
* trimmed form is what renders). A newline that SURVIVES
|
|
249
|
+
* the trim is real multi-line content: a consumer that
|
|
250
|
+
* writes by splicing must refuse it, because replacing it
|
|
251
|
+
* with one line deletes source lines and invalidates every
|
|
252
|
+
* anchor below.
|
|
253
|
+
*
|
|
254
|
+
* Emitted for NON-HOST elements only — a usage is always a component tag, and
|
|
255
|
+
* stamping every `<div>` would be weight for an answer nothing asks.
|
|
256
|
+
*
|
|
257
|
+
* ABSENT (`undefined`) on a host element and on every bundle built before this
|
|
258
|
+
* change. Absence is NOT "no children": it means the page cannot answer the
|
|
259
|
+
* question, and a consumer must gap the edit rather than assume (the same law
|
|
260
|
+
* `origin`'s docstring states at length).
|
|
261
|
+
*/
|
|
262
|
+
childrenText?: string | null;
|
|
219
263
|
/**
|
|
220
264
|
* Whether the JSX node is in a reorderable position (2+ siblings).
|
|
221
265
|
*
|
package/dist/index.js
CHANGED
|
@@ -277,6 +277,9 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
277
277
|
}
|
|
278
278
|
return "";
|
|
279
279
|
}
|
|
280
|
+
function isIntrinsicTagName(name) {
|
|
281
|
+
return t.isJSXIdentifier(name) && /^[a-z]/.test(name.name);
|
|
282
|
+
}
|
|
280
283
|
function isHostLikeMemberExpressionName(name) {
|
|
281
284
|
if (!t.isJSXMemberExpression(name)) return false;
|
|
282
285
|
const propertyName = name.property.name;
|
|
@@ -367,6 +370,18 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
367
370
|
}
|
|
368
371
|
return hasAny ? t.objectExpression(props) : null;
|
|
369
372
|
}
|
|
373
|
+
function describeChildrenText(opening) {
|
|
374
|
+
const elementPath = opening.parentPath;
|
|
375
|
+
if (!elementPath || !elementPath.isJSXElement()) return null;
|
|
376
|
+
const element = elementPath.node;
|
|
377
|
+
if (!element.closingElement) return null;
|
|
378
|
+
const children = element.children;
|
|
379
|
+
if (children.length === 0) return "";
|
|
380
|
+
if (children.length !== 1) return "__dynamic__";
|
|
381
|
+
const [only] = children;
|
|
382
|
+
if (!t.isJSXText(only)) return "__dynamic__";
|
|
383
|
+
return only.value.trim();
|
|
384
|
+
}
|
|
370
385
|
function isMetaStripSpreadExpression(arg) {
|
|
371
386
|
if (!t.isCallExpression(arg)) return false;
|
|
372
387
|
if (!t.isArrowFunctionExpression(arg.callee)) return false;
|
|
@@ -474,6 +489,14 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
474
489
|
}
|
|
475
490
|
return map;
|
|
476
491
|
}
|
|
492
|
+
function restParamName(pattern) {
|
|
493
|
+
for (const prop of pattern.properties) {
|
|
494
|
+
if (!t.isRestElement(prop)) continue;
|
|
495
|
+
if (t.isIdentifier(prop.argument)) return prop.argument.name;
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
477
500
|
function resolvePropContext(path) {
|
|
478
501
|
let fn = path.getFunctionParent();
|
|
479
502
|
while (fn) {
|
|
@@ -482,7 +505,9 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
482
505
|
return {
|
|
483
506
|
componentScope: fn.scope,
|
|
484
507
|
propMap: buildPropMap(first),
|
|
485
|
-
componentName: getFunctionName(fn)
|
|
508
|
+
componentName: getFunctionName(fn),
|
|
509
|
+
restParam: restParamName(first),
|
|
510
|
+
componentPath: fn
|
|
486
511
|
};
|
|
487
512
|
}
|
|
488
513
|
fn = fn.getFunctionParent();
|
|
@@ -602,12 +627,62 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
602
627
|
const props = collectPropIdentifiers(expr, scope, ctx);
|
|
603
628
|
return props.length > 0 ? { kind: "formatted", prop: props[0] } : null;
|
|
604
629
|
}
|
|
630
|
+
function unwrapSanitizedSpread(arg) {
|
|
631
|
+
if (!t.isCallExpression(arg)) return arg;
|
|
632
|
+
if (!isMetaStripSpreadExpression(arg)) return arg;
|
|
633
|
+
if (arg.arguments.length !== 1) return arg;
|
|
634
|
+
const [payload] = arg.arguments;
|
|
635
|
+
if (!t.isObjectExpression(payload) || payload.properties.length !== 1) return arg;
|
|
636
|
+
const [only] = payload.properties;
|
|
637
|
+
if (!t.isSpreadElement(only)) return arg;
|
|
638
|
+
return only.argument;
|
|
639
|
+
}
|
|
640
|
+
function forwardsOwnerRestChildren(opening, ctx) {
|
|
641
|
+
if (ctx.restParam === null) return false;
|
|
642
|
+
const elementPath = opening.parentPath;
|
|
643
|
+
if (!elementPath || !elementPath.isJSXElement()) return false;
|
|
644
|
+
if (meaningfulChildren(t, elementPath.node.children).length > 0) return false;
|
|
645
|
+
let spreadsRest = 0;
|
|
646
|
+
for (const attr of opening.node.attributes) {
|
|
647
|
+
if (t.isJSXAttribute(attr)) {
|
|
648
|
+
if (t.isJSXIdentifier(attr.name, { name: "children" })) return false;
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (!t.isJSXSpreadAttribute(attr)) continue;
|
|
652
|
+
const payload = unwrapSanitizedSpread(attr.argument);
|
|
653
|
+
if (!t.isIdentifier(payload, { name: ctx.restParam })) return false;
|
|
654
|
+
spreadsRest += 1;
|
|
655
|
+
}
|
|
656
|
+
if (spreadsRest !== 1) return false;
|
|
657
|
+
const binding = opening.scope.getBinding(ctx.restParam);
|
|
658
|
+
if (!binding) return false;
|
|
659
|
+
if (binding.scope !== ctx.componentScope) return false;
|
|
660
|
+
return binding.constant;
|
|
661
|
+
}
|
|
662
|
+
const restForwarderCounts = /* @__PURE__ */ new WeakMap();
|
|
663
|
+
function countRestForwarders(ctx) {
|
|
664
|
+
const cached = restForwarderCounts.get(ctx.componentPath.node);
|
|
665
|
+
if (cached !== void 0) return cached;
|
|
666
|
+
let count = 0;
|
|
667
|
+
ctx.componentPath.traverse({
|
|
668
|
+
JSXOpeningElement(inner) {
|
|
669
|
+
if (forwardsOwnerRestChildren(inner, ctx)) count += 1;
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
restForwarderCounts.set(ctx.componentPath.node, count);
|
|
673
|
+
return count;
|
|
674
|
+
}
|
|
605
675
|
function collectPropBindings(path, attrs) {
|
|
606
676
|
const ctx = resolvePropContext(path);
|
|
607
|
-
if (!ctx || ctx.propMap.size === 0
|
|
677
|
+
if (!ctx || ctx.propMap.size === 0 && ctx.restParam === null) {
|
|
678
|
+
return { bindings: [], owner: null };
|
|
679
|
+
}
|
|
608
680
|
if (ctx.componentName === null) return { bindings: [], owner: null };
|
|
609
681
|
const scope = path.scope;
|
|
610
682
|
const bindings = [];
|
|
683
|
+
if (forwardsOwnerRestChildren(path, ctx) && countRestForwarders(ctx) === 1) {
|
|
684
|
+
bindings.push({ target: "children", prop: "children", kind: "direct" });
|
|
685
|
+
}
|
|
611
686
|
const elementPath = path.parentPath;
|
|
612
687
|
if (elementPath && elementPath.isJSXElement()) {
|
|
613
688
|
const children = meaningfulChildren(t, elementPath.node.children);
|
|
@@ -709,7 +784,9 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
709
784
|
return;
|
|
710
785
|
}
|
|
711
786
|
let rootName = null;
|
|
712
|
-
if (
|
|
787
|
+
if (isIntrinsicTagName(path.node.name)) {
|
|
788
|
+
rootName = null;
|
|
789
|
+
} else if (t.isJSXIdentifier(path.node.name)) {
|
|
713
790
|
rootName = path.node.name.name;
|
|
714
791
|
} else if (t.isJSXMemberExpression(path.node.name)) {
|
|
715
792
|
let cur = path.node.name;
|
|
@@ -771,6 +848,15 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
771
848
|
t.objectProperty(t.identifier("slot"), t.stringLiteral(slot))
|
|
772
849
|
);
|
|
773
850
|
}
|
|
851
|
+
if (!isHostElement) {
|
|
852
|
+
const childrenText = describeChildrenText(path);
|
|
853
|
+
metaObj.properties.push(
|
|
854
|
+
t.objectProperty(
|
|
855
|
+
t.identifier("childrenText"),
|
|
856
|
+
childrenText === null ? t.nullLiteral() : t.stringLiteral(childrenText)
|
|
857
|
+
)
|
|
858
|
+
);
|
|
859
|
+
}
|
|
774
860
|
if (bindings.length > 0) {
|
|
775
861
|
metaObj.properties.push(
|
|
776
862
|
t.objectProperty(t.identifier("propBindings"), propBindingsToAst(bindings))
|
package/dist/index.mjs
CHANGED
|
@@ -275,7 +275,31 @@
|
|
|
275
275
|
while (fiber && chain.length < MAX_META_CHAIN) {
|
|
276
276
|
if (fiber.memoizedProps && fiber.memoizedProps.__editorMeta) {
|
|
277
277
|
var meta = resolveEditorMetaValue(fiber.memoizedProps.__editorMeta)
|
|
278
|
-
if (meta)
|
|
278
|
+
if (meta) {
|
|
279
|
+
// ARCH-1367 F6 (host-measured incident, 2026-09-06) — `hostIsSelf`:
|
|
280
|
+
// TRUE when this chain entry's fiber renders EXACTLY `el` as its
|
|
281
|
+
// single host element (getSingleHostElementFromFiber), i.e. the
|
|
282
|
+
// usage and the node are the same DOM element — a `<Button>`
|
|
283
|
+
// usage whose whole render is this `<button>`. FALSE for a real
|
|
284
|
+
// ancestor (a `<header>` around it) AND for a usage whose render
|
|
285
|
+
// is a Fragment/array with siblings (`ThemeGlyphs` = two spans):
|
|
286
|
+
// that usage does NOT stand for this node alone. The host walks
|
|
287
|
+
// up from a template root ONLY through `hostIsSelf` entries to
|
|
288
|
+
// find "the element the user sees"; without this flag it climbed
|
|
289
|
+
// by name and could resolve one glyph to the whole `ThemeGlyphs`
|
|
290
|
+
// usage (measured: `const themeIcon = null`). A COPY, so the
|
|
291
|
+
// shared meta object is never mutated.
|
|
292
|
+
var host = null
|
|
293
|
+
try {
|
|
294
|
+
host = getSingleHostElementFromFiber(fiber)
|
|
295
|
+
} catch (hostError) {
|
|
296
|
+
host = null
|
|
297
|
+
}
|
|
298
|
+
var entry = {}
|
|
299
|
+
for (var k in meta) if (Object.prototype.hasOwnProperty.call(meta, k)) entry[k] = meta[k]
|
|
300
|
+
entry.hostIsSelf = host === el
|
|
301
|
+
chain.push(entry)
|
|
302
|
+
}
|
|
279
303
|
}
|
|
280
304
|
fiber = fiber["return"]
|
|
281
305
|
}
|
|
@@ -1626,6 +1650,13 @@
|
|
|
1626
1650
|
// (`CAPTURE_VIEWPORT` → `VIEWPORT_CAPTURED`). Hosts MUST
|
|
1627
1651
|
// feature-detect: a frozen earlier inspector has no handler for
|
|
1628
1652
|
// either message and would never reply.
|
|
1653
|
+
// ARCH-1367 F6 (CR round 3, MAJOR-2) — this build annotates every
|
|
1654
|
+
// `editorMetaChain` entry with `hostIsSelf` and attaches the drop
|
|
1655
|
+
// container's chain to `element-inserted` / arrange-session
|
|
1656
|
+
// inserts. The CDN object is unversioned, so this bit is the ONLY
|
|
1657
|
+
// honest floor a host can check; without it the host refuses a
|
|
1658
|
+
// delete/drop on a kit template root instead of guessing.
|
|
1659
|
+
metaChainHostIsSelf: true,
|
|
1629
1660
|
rectElementQuery: true,
|
|
1630
1661
|
// Best-effort by construction — the foreignObject round-trip drops
|
|
1631
1662
|
// web fonts, cross-origin images and <canvas> pixels, and answers
|
|
@@ -3786,7 +3817,68 @@
|
|
|
3786
3817
|
return
|
|
3787
3818
|
}
|
|
3788
3819
|
|
|
3820
|
+
// ARCH-1367 F6 — attach the drop container's editor meta chain to a
|
|
3821
|
+
// container descriptor ({selector, nodeId, …}). Shared by the
|
|
3822
|
+
// `element-inserted` relay AND the `arrange-session-changed` relay:
|
|
3823
|
+
// the host builds its insert change from the SESSION snapshot, not
|
|
3824
|
+
// from the one-shot inserted event, so both must carry it. Additive
|
|
3825
|
+
// and fail-open.
|
|
3826
|
+
function attachContainerMetaChain(container) {
|
|
3827
|
+
if (!container || typeof container !== "object") return
|
|
3828
|
+
var containerEl = null
|
|
3829
|
+
if (typeof container.selector === "string" && container.selector) {
|
|
3830
|
+
try {
|
|
3831
|
+
containerEl = _rawDocumentQuerySelector.call(document, container.selector)
|
|
3832
|
+
} catch (selectorError) {
|
|
3833
|
+
containerEl = null
|
|
3834
|
+
}
|
|
3835
|
+
}
|
|
3836
|
+
if (!containerEl && typeof container.nodeId === "string" && container.nodeId) {
|
|
3837
|
+
containerEl = _rawDocumentQuerySelector.call(
|
|
3838
|
+
document,
|
|
3839
|
+
'[data-dnd-node-id="' + container.nodeId.replace(/"/g, '\\"') + '"]'
|
|
3840
|
+
)
|
|
3841
|
+
}
|
|
3842
|
+
if (containerEl) {
|
|
3843
|
+
container.editorMetaChain = collectEditorMetaChain(containerEl)
|
|
3844
|
+
} else {
|
|
3845
|
+
// CR round 4 (archie-app #1820), MAJOR-D — a silent miss here is
|
|
3846
|
+
// indistinguishable, on the host, from an inspector that predates
|
|
3847
|
+
// the chain. Say it, and attach an EMPTY chain so the host can
|
|
3848
|
+
// tell "this inspector tried and could not find the container"
|
|
3849
|
+
// from "no chain at all"; the host fails closed either way.
|
|
3850
|
+
container.editorMetaChain = []
|
|
3851
|
+
inspectorWarn(
|
|
3852
|
+
"[Inspector] drop container not found for meta chain",
|
|
3853
|
+
container.selector || container.nodeId || null
|
|
3854
|
+
)
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
|
|
3789
3858
|
if (detail.type === "element-inserted") {
|
|
3859
|
+
// ARCH-1367 F6 (host-measured incident, 2026-09-06) — the drop
|
|
3860
|
+
// container the DnD runtime reports is the DEEPEST droppable under
|
|
3861
|
+
// the pointer, which for a kit primitive is that primitive's own
|
|
3862
|
+
// TEMPLATE root (`components/ui/button.jsx:8:78:button` when the
|
|
3863
|
+
// drop lands on the theme toggle). The host has no way to see the
|
|
3864
|
+
// USAGE that root stands for (`<IconButton>` in shell-layouts.jsx)
|
|
3865
|
+
// — so it wrote the insert INTO the template, and every button in
|
|
3866
|
+
// the project grew a nested button. The runtime's `element-inserted`
|
|
3867
|
+
// detail carries the container's selector/nodeId only; this is the
|
|
3868
|
+
// one place that can still reach the live element and its fiber
|
|
3869
|
+
// chain, so the chain is attached here — ADDITIVE, the host reads
|
|
3870
|
+
// `container.editorMetaChain` when present and ignores its absence.
|
|
3871
|
+
// Same collector `ELEMENT_SELECTED` uses, so both sides agree.
|
|
3872
|
+
try {
|
|
3873
|
+
attachContainerMetaChain(detail && detail.container)
|
|
3874
|
+
} catch (chainError) {
|
|
3875
|
+
// CR round 5, NIT-5 — a throw must not collapse the "tried and
|
|
3876
|
+
// failed" signal into "no chain at all".
|
|
3877
|
+
if (detail && detail.container && typeof detail.container === "object") {
|
|
3878
|
+
detail.container.editorMetaChain = []
|
|
3879
|
+
}
|
|
3880
|
+
inspectorWarn("[Inspector] element-inserted: container meta chain unavailable", chainError)
|
|
3881
|
+
}
|
|
3790
3882
|
postMessageToParent({
|
|
3791
3883
|
type: "ELEMENT_INSERTED",
|
|
3792
3884
|
payload: detail,
|
|
@@ -3795,6 +3887,38 @@
|
|
|
3795
3887
|
}
|
|
3796
3888
|
|
|
3797
3889
|
if (detail.type === "arrange-session-changed") {
|
|
3890
|
+
// ARCH-1367 F6 — same chain on every INSERT change of the session
|
|
3891
|
+
// snapshot (this is what the host actually builds its change from).
|
|
3892
|
+
try {
|
|
3893
|
+
var sessionChanges = detail.session && detail.session.changes
|
|
3894
|
+
if (Array.isArray(sessionChanges)) {
|
|
3895
|
+
for (var sc = 0; sc < sessionChanges.length; sc++) {
|
|
3896
|
+
var sessionChange = sessionChanges[sc]
|
|
3897
|
+
if (sessionChange && sessionChange.kind === "insert") {
|
|
3898
|
+
if (!sessionChange.container || typeof sessionChange.container !== "object") {
|
|
3899
|
+
sessionChange.container = { nodeId: sessionChange.containerId || null, selector: null }
|
|
3900
|
+
}
|
|
3901
|
+
if (!sessionChange.container.nodeId && sessionChange.containerId) {
|
|
3902
|
+
sessionChange.container.nodeId = sessionChange.containerId
|
|
3903
|
+
}
|
|
3904
|
+
attachContainerMetaChain(sessionChange.container)
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
} catch (sessionChainError) {
|
|
3909
|
+
try {
|
|
3910
|
+
var scs = detail.session && detail.session.changes
|
|
3911
|
+
if (Array.isArray(scs)) {
|
|
3912
|
+
for (var sci = 0; sci < scs.length; sci++) {
|
|
3913
|
+
var scc = scs[sci]
|
|
3914
|
+
if (scc && scc.kind === "insert" && scc.container && typeof scc.container === "object" && !Array.isArray(scc.container.editorMetaChain)) {
|
|
3915
|
+
scc.container.editorMetaChain = []
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
} catch (ignored) {}
|
|
3920
|
+
inspectorWarn("[Inspector] arrange-session-changed: container meta chain unavailable", sessionChainError)
|
|
3921
|
+
}
|
|
3798
3922
|
postMessageToParent({
|
|
3799
3923
|
type: "ARRANGE_SESSION_CHANGED",
|
|
3800
3924
|
session: detail.session,
|