@farm.js/react 0.1.0-beta.1 → 0.1.0-beta.3

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/vite.cjs CHANGED
@@ -260,6 +260,31 @@ function isTextExpression(expression, statesByValue, safeGlobals) {
260
260
  function jsxAttributeName(attribute) {
261
261
  return core.types.isJSXIdentifier(attribute.name) ? attribute.name.name : void 0;
262
262
  }
263
+ function jsxAttribute(element, name) {
264
+ return element.openingElement.attributes.find(
265
+ (attribute) => core.types.isJSXAttribute(attribute) && jsxAttributeName(attribute) === name
266
+ );
267
+ }
268
+ function staticJsxAttributeString(attribute) {
269
+ if (!attribute?.value) return attribute ? "" : void 0;
270
+ if (core.types.isStringLiteral(attribute.value)) return attribute.value.value;
271
+ if (core.types.isJSXExpressionContainer(attribute.value) && core.types.isStringLiteral(attribute.value.expression)) {
272
+ return attribute.value.expression.value;
273
+ }
274
+ return void 0;
275
+ }
276
+ function controlledSelectHasDynamicOptions(element) {
277
+ let dynamic = false;
278
+ core.types.traverseFast(element, (node) => {
279
+ if (dynamic || !core.types.isJSXElement(node)) return;
280
+ const tag = node.openingElement.name;
281
+ if (!core.types.isJSXIdentifier(tag) || tag.name !== "option" && tag.name !== "optgroup") return;
282
+ dynamic = node.openingElement.attributes.some(
283
+ (attribute) => core.types.isJSXSpreadAttribute(attribute) || core.types.isJSXAttribute(attribute) && core.types.isJSXExpressionContainer(attribute.value) && !core.types.isJSXEmptyExpression(attribute.value.expression)
284
+ );
285
+ });
286
+ return dynamic;
287
+ }
263
288
  function isEmptyConditionalBranch(expression) {
264
289
  return core.types.isNullLiteral(expression) || core.types.isBooleanLiteral(expression, { value: false });
265
290
  }
@@ -485,7 +510,87 @@ function validateKeyedRowEventHandler(handler) {
485
510
  });
486
511
  return reason;
487
512
  }
488
- function analyzeKeyedRowTree(row, renderCallback, safeGlobals) {
513
+ function stabilizeDeferredEventCurrentTarget(handler, compilerSetters) {
514
+ const cloned = core.types.cloneNode(handler, true);
515
+ const eventParameter = cloned.params[0];
516
+ if (!core.types.isIdentifier(eventParameter) || compilerSetters.size === 0) return cloned;
517
+ const file = expressionFile(cloned);
518
+ const expression = file.program.body[0].expression;
519
+ if (!core.types.isArrowFunctionExpression(expression) && !core.types.isFunctionExpression(expression)) {
520
+ return cloned;
521
+ }
522
+ const rootBindingIdentifier = expression.params[0];
523
+ const snapshots = [];
524
+ const snapshotsByProperty = /* @__PURE__ */ new Map();
525
+ const replaceCurrentTarget = (path) => {
526
+ const property = path.node.property;
527
+ const isCurrentTarget = path.node.computed ? core.types.isStringLiteral(property, { value: "currentTarget" }) : core.types.isIdentifier(property, { name: "currentTarget" });
528
+ if (!isCurrentTarget || !core.types.isIdentifier(path.node.object, { name: eventParameter.name }))
529
+ return;
530
+ const binding = path.scope.getBinding(eventParameter.name);
531
+ if (!binding || binding.identifier !== rootBindingIdentifier) return;
532
+ const setterCall = path.findParent(
533
+ (parent2) => parent2.isCallExpression() && core.types.isIdentifier(parent2.node.callee) && compilerSetters.has(parent2.node.callee.name)
534
+ );
535
+ if (!setterCall) return;
536
+ const deferredFunction = path.findParent(
537
+ (parent2) => parent2.isFunction() && parent2.node !== expression
538
+ );
539
+ if (!deferredFunction || !deferredFunction.findParent((parent2) => parent2 === setterCall))
540
+ return;
541
+ const parent = path.parentPath;
542
+ const propertyRead = (parent?.isMemberExpression() || parent?.isOptionalMemberExpression()) && parent.node.object === path.node ? parent : path;
543
+ const snapshotProperty = propertyRead === path ? "element" : propertyRead.node.computed ? core.types.isStringLiteral(propertyRead.node.property) ? propertyRead.node.property.value : `computed${snapshots.length}` : core.types.isIdentifier(propertyRead.node.property) ? propertyRead.node.property.name : `property${snapshots.length}`;
544
+ let snapshot = snapshotsByProperty.get(snapshotProperty);
545
+ if (!snapshot) {
546
+ const suffix = snapshotProperty.replace(/[^A-Za-z0-9_$]/g, " ").replace(
547
+ /(?:^|\s+)([A-Za-z0-9_$])/g,
548
+ (_match, character) => character.toUpperCase()
549
+ ).replace(/\s/g, "");
550
+ snapshot = uniqueLocalIdentifier(`_farmCurrentTarget${suffix}`, [
551
+ expression,
552
+ ...snapshots.map((entry) => entry.identifier)
553
+ ]);
554
+ snapshotsByProperty.set(snapshotProperty, snapshot);
555
+ snapshots.push({
556
+ identifier: snapshot,
557
+ value: core.types.cloneNode(propertyRead.node, true)
558
+ });
559
+ }
560
+ propertyRead.replaceWith(core.types.cloneNode(snapshot));
561
+ propertyRead.skip();
562
+ };
563
+ core.traverse(file, {
564
+ MemberExpression: replaceCurrentTarget,
565
+ OptionalMemberExpression: replaceCurrentTarget
566
+ });
567
+ if (snapshots.length === 0) return expression;
568
+ const declarations = snapshots.map(
569
+ ({ identifier, value }) => core.types.variableDeclaration("const", [core.types.variableDeclarator(core.types.cloneNode(identifier), value)])
570
+ );
571
+ if (core.types.isBlockStatement(expression.body)) {
572
+ expression.body.body.unshift(...declarations);
573
+ } else {
574
+ expression.body = core.types.blockStatement([...declarations, core.types.returnStatement(expression.body)]);
575
+ }
576
+ return expression;
577
+ }
578
+ function stabilizeDeferredEventCurrentTargets(root, compilerSetters) {
579
+ const file = expressionFile(core.types.cloneNode(root, true));
580
+ core.traverse(file, {
581
+ JSXAttribute(path) {
582
+ if (!core.types.isJSXIdentifier(path.node.name) || !/^on[A-Z]/.test(path.node.name.name)) return;
583
+ const value = path.node.value;
584
+ if (!core.types.isJSXExpressionContainer(value) || !core.types.isArrowFunctionExpression(value.expression) && !core.types.isFunctionExpression(value.expression)) {
585
+ return;
586
+ }
587
+ value.expression = stabilizeDeferredEventCurrentTarget(value.expression, compilerSetters);
588
+ }
589
+ });
590
+ const expression = file.program.body[0].expression;
591
+ return core.types.isJSXElement(expression) ? expression : core.types.cloneNode(root, true);
592
+ }
593
+ function analyzeKeyedRowTree(row, renderCallback, safeGlobals, compilerSetters) {
489
594
  if (renderCallback.async || renderCallback.generator || renderCallback.params.length < 1 || renderCallback.params.length > 2 || !core.types.isIdentifier(renderCallback.params[0]) || renderCallback.params[1] !== void 0 && !core.types.isIdentifier(renderCallback.params[1])) {
490
595
  return { reason: "compiled keyed rows require item and optional index identifiers" };
491
596
  }
@@ -501,8 +606,25 @@ function analyzeKeyedRowTree(row, renderCallback, safeGlobals) {
501
606
  const attributeNames = new Set(
502
607
  element.openingElement.attributes.filter((attribute) => core.types.isJSXAttribute(attribute)).map(jsxAttributeName).filter((name) => name !== void 0)
503
608
  );
504
- if ((tag.name === "input" || tag.name === "textarea" || tag.name === "select") && (attributeNames.has("value") || attributeNames.has("checked")) && [...attributeNames].some((name) => /^on[A-Z]/.test(name))) {
505
- return "controlled form elements in interactive compiled keyed rows require React ownership";
609
+ const controlled = attributeNames.has("value") || attributeNames.has("checked");
610
+ if (attributeNames.has("contentEditable")) {
611
+ return "content-editable keyed rows require React ownership";
612
+ }
613
+ if (tag.name === "input" && controlled) {
614
+ const type = jsxAttribute(element, "type");
615
+ const staticType = staticJsxAttributeString(type);
616
+ if (type && staticType === void 0) {
617
+ return "controlled keyed row inputs require a static type";
618
+ }
619
+ if (attributeNames.has("value") && staticType?.toLowerCase() === "file") {
620
+ return "file inputs require React ownership";
621
+ }
622
+ }
623
+ if (tag.name === "textarea" && controlled && meaningfulJsxChildren(element).length > 0) {
624
+ return "controlled keyed row textareas cannot also use children";
625
+ }
626
+ if (tag.name === "select" && controlled && controlledSelectHasDynamicOptions(element)) {
627
+ return "controlled keyed row selects require static options";
506
628
  }
507
629
  for (const attribute of element.openingElement.attributes) {
508
630
  if (core.types.isJSXSpreadAttribute(attribute)) {
@@ -533,7 +655,7 @@ function analyzeKeyedRowTree(row, renderCallback, safeGlobals) {
533
655
  id: events.length,
534
656
  path: [...path],
535
657
  name,
536
- value: core.types.cloneNode(handler, true)
658
+ value: stabilizeDeferredEventCurrentTarget(handler, compilerSetters)
537
659
  });
538
660
  continue;
539
661
  }
@@ -647,26 +769,14 @@ function analyzeKeyedRowTree(row, renderCallback, safeGlobals) {
647
769
  const reason = visit(row, []);
648
770
  return reason ? { reason } : { bindings, events, conditionals };
649
771
  }
650
- function analyzeKeyedRowsContainer(container, statesByValue, safeGlobals, listNames) {
651
- if (container.openingElement.attributes.some(
652
- (attribute) => core.types.isJSXSpreadAttribute(attribute) || core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name) && (attribute.name.name === "ref" || attribute.name.name === "dangerouslySetInnerHTML")
653
- )) {
654
- return void 0;
655
- }
656
- for (const attribute of container.openingElement.attributes) {
657
- if (core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name) && core.types.isJSXExpressionContainer(attribute.value) && !core.types.isJSXEmptyExpression(attribute.value.expression) && !/^on[A-Z]/.test(attribute.name.name) && collectStateDependencies(attribute.value.expression, statesByValue).length > 0) {
658
- return void 0;
659
- }
660
- }
661
- const children = meaningfulJsxChildren(container);
662
- if (children.length !== 1) return void 0;
772
+ function analyzeKeyedRowChild(child, statesByValue, safeGlobals, listNames) {
663
773
  let collection;
664
774
  let keyCallback;
665
775
  let renderCallback;
666
776
  let row;
667
777
  let dependencies;
668
778
  let syntax;
669
- const child = children[0];
779
+ let source;
670
780
  if (core.types.isJSXExpressionContainer(child) && !core.types.isJSXEmptyExpression(child.expression) && isMapCall(child.expression)) {
671
781
  const result = analyzeMapList(child.expression, statesByValue, safeGlobals);
672
782
  if (result.reason) return void 0;
@@ -686,6 +796,7 @@ function analyzeKeyedRowsContainer(container, statesByValue, safeGlobals, listNa
686
796
  row = returned;
687
797
  dependencies = result.dependencies || [];
688
798
  syntax = "map";
799
+ source = child.expression;
689
800
  } else if (core.types.isJSXElement(child) && isPublicListElement(child, listNames)) {
690
801
  const result = analyzePublicList(child, statesByValue, safeGlobals);
691
802
  if (result.reason) return void 0;
@@ -703,12 +814,19 @@ function analyzeKeyedRowsContainer(container, statesByValue, safeGlobals, listNa
703
814
  row = returned;
704
815
  dependencies = result.dependencies || [];
705
816
  syntax = "list";
817
+ source = child;
706
818
  } else {
707
819
  return void 0;
708
820
  }
709
- const rowAnalysis = analyzeKeyedRowTree(row, renderCallback, safeGlobals);
821
+ const rowAnalysis = analyzeKeyedRowTree(
822
+ row,
823
+ renderCallback,
824
+ safeGlobals,
825
+ new Set([...statesByValue.values()].map((state) => state.setterName))
826
+ );
710
827
  if (rowAnalysis.reason) return void 0;
711
828
  return {
829
+ source,
712
830
  collection,
713
831
  keyCallback,
714
832
  renderCallback,
@@ -720,6 +838,76 @@ function analyzeKeyedRowsContainer(container, statesByValue, safeGlobals, listNa
720
838
  syntax
721
839
  };
722
840
  }
841
+ function analyzeKeyedRowsContainer(container, statesByValue, safeGlobals, listNames) {
842
+ if (container.openingElement.attributes.some(
843
+ (attribute) => core.types.isJSXSpreadAttribute(attribute) || core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name) && (attribute.name.name === "ref" || attribute.name.name === "dangerouslySetInnerHTML")
844
+ )) {
845
+ return void 0;
846
+ }
847
+ for (const attribute of container.openingElement.attributes) {
848
+ if (core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name) && core.types.isJSXExpressionContainer(attribute.value) && !core.types.isJSXEmptyExpression(attribute.value.expression) && !/^on[A-Z]/.test(attribute.name.name) && collectStateDependencies(attribute.value.expression, statesByValue).length > 0) {
849
+ return void 0;
850
+ }
851
+ }
852
+ const children = meaningfulJsxChildren(container);
853
+ if (children.length !== 1) return void 0;
854
+ return analyzeKeyedRowChild(children[0], statesByValue, safeGlobals, listNames);
855
+ }
856
+ function isStaticRangeSibling(element, statesByValue, safeGlobals) {
857
+ for (const child of meaningfulJsxChildren(element)) {
858
+ if (core.types.isJSXFragment(child)) return false;
859
+ if (core.types.isJSXElement(child)) {
860
+ if (!isHostElement(child) || !isStaticRangeSibling(child, statesByValue, safeGlobals)) {
861
+ return false;
862
+ }
863
+ continue;
864
+ }
865
+ if (core.types.isJSXExpressionContainer(child) && !core.types.isJSXEmptyExpression(child.expression) && !isTextExpression(child.expression, statesByValue, safeGlobals)) {
866
+ return false;
867
+ }
868
+ }
869
+ return true;
870
+ }
871
+ function analyzeKeyedRangesContainer(container, statesByValue, safeGlobals, listNames, allowSingleRange = false) {
872
+ if (container.openingElement.attributes.some(
873
+ (attribute) => core.types.isJSXSpreadAttribute(attribute) || core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name) && (attribute.name.name === "ref" || attribute.name.name === "dangerouslySetInnerHTML")
874
+ )) {
875
+ return void 0;
876
+ }
877
+ const children = meaningfulJsxChildren(container);
878
+ if (children.length < (allowSingleRange ? 1 : 2)) return void 0;
879
+ const ranges = [];
880
+ const dependencies = /* @__PURE__ */ new Set();
881
+ let staticBefore = 0;
882
+ for (const child of children) {
883
+ const range = analyzeKeyedRowChild(child, statesByValue, safeGlobals, listNames);
884
+ if (range) {
885
+ if (range.events.length > 0 || range.conditionals.length > 0) return void 0;
886
+ ranges.push({
887
+ before: staticBefore,
888
+ source: range.source,
889
+ collection: range.collection,
890
+ keyCallback: range.keyCallback,
891
+ renderCallback: range.renderCallback,
892
+ row: range.row,
893
+ bindings: range.bindings,
894
+ syntax: range.syntax
895
+ });
896
+ for (const dependency of range.dependencies) dependencies.add(dependency);
897
+ staticBefore = 0;
898
+ continue;
899
+ }
900
+ if (!core.types.isJSXElement(child) || !isHostElement(child)) return void 0;
901
+ if (!isStaticRangeSibling(child, statesByValue, safeGlobals)) return void 0;
902
+ staticBefore += 1;
903
+ }
904
+ if (ranges.length === 0) return void 0;
905
+ return {
906
+ ranges,
907
+ trailing: staticBefore,
908
+ dependencies: [...dependencies].sort((left, right) => left - right)
909
+ };
910
+ }
723
911
  function analyzeHostConditionalTree(branch, safeGlobals) {
724
912
  const bindings = [];
725
913
  const visit = (element, path) => {
@@ -862,6 +1050,60 @@ function analyzeHostConditionalContainer(container, statesByValue, safeGlobals)
862
1050
  falsyBindings: falsyAnalysis.bindings || []
863
1051
  };
864
1052
  }
1053
+ function analyzeConditionalRangesContainer(container, statesByValue, safeGlobals, allowSingleRange = false) {
1054
+ const containerName = container.openingElement.name;
1055
+ if (!core.types.isJSXIdentifier(containerName) || !/^[a-z]/.test(containerName.name) || containerName.name === "svg" || container.openingElement.attributes.some(
1056
+ (attribute) => core.types.isJSXSpreadAttribute(attribute) || core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name) && (attribute.name.name === "ref" || attribute.name.name === "dangerouslySetInnerHTML")
1057
+ )) {
1058
+ return void 0;
1059
+ }
1060
+ const children = meaningfulJsxChildren(container);
1061
+ if (children.length < (allowSingleRange ? 1 : 2)) return void 0;
1062
+ const ranges = [];
1063
+ const dependencies = /* @__PURE__ */ new Set();
1064
+ let staticBefore = 0;
1065
+ for (const child of children) {
1066
+ if (core.types.isJSXExpressionContainer(child) && !core.types.isJSXEmptyExpression(child.expression)) {
1067
+ const shape = conditionalBlockShape(child.expression);
1068
+ if (shape && !validateDerivedExpression(shape.test, safeGlobals)) {
1069
+ const truthyAnalysis = shape.truthy ? analyzeHostConditionalTree(shape.truthy, safeGlobals) : { bindings: [] };
1070
+ const falsyAnalysis = shape.falsy ? analyzeHostConditionalTree(shape.falsy, safeGlobals) : { bindings: [] };
1071
+ if (truthyAnalysis.reason || falsyAnalysis.reason) return void 0;
1072
+ const rangeDependencies = new Set(collectStateDependencies(shape.test, statesByValue));
1073
+ for (const binding of [
1074
+ ...truthyAnalysis.bindings || [],
1075
+ ...falsyAnalysis.bindings || []
1076
+ ]) {
1077
+ for (const dependency of collectStateDependencies(binding.value, statesByValue)) {
1078
+ rangeDependencies.add(dependency);
1079
+ }
1080
+ }
1081
+ for (const dependency of rangeDependencies) dependencies.add(dependency);
1082
+ ranges.push({
1083
+ before: staticBefore,
1084
+ source: child.expression,
1085
+ test: cloneExpression(shape.test),
1086
+ logical: shape.logical,
1087
+ truthy: shape.truthy,
1088
+ falsy: shape.falsy,
1089
+ truthyBindings: truthyAnalysis.bindings || [],
1090
+ falsyBindings: falsyAnalysis.bindings || []
1091
+ });
1092
+ staticBefore = 0;
1093
+ continue;
1094
+ }
1095
+ }
1096
+ if (!core.types.isJSXElement(child) || !isHostElement(child)) return void 0;
1097
+ if (!isStaticRangeSibling(child, statesByValue, safeGlobals)) return void 0;
1098
+ staticBefore += 1;
1099
+ }
1100
+ if (ranges.length === 0) return void 0;
1101
+ return {
1102
+ ranges,
1103
+ trailing: staticBefore,
1104
+ dependencies: [...dependencies].sort((left, right) => left - right)
1105
+ };
1106
+ }
865
1107
  function keyedListBoundary(blockRuntime, plan, source) {
866
1108
  const name = core.types.jsxMemberExpression(
867
1109
  core.types.jsxIdentifier(blockRuntime.name),
@@ -1209,6 +1451,74 @@ function keyedRowsBoundary(blockRuntime, plan) {
1209
1451
  true
1210
1452
  );
1211
1453
  }
1454
+ function keyedRangeObject(range) {
1455
+ const parameters = range.renderCallback.params.map(
1456
+ (parameter) => core.types.cloneNode(parameter, true)
1457
+ );
1458
+ return core.types.objectExpression([
1459
+ core.types.objectProperty(core.types.identifier("before"), core.types.numericLiteral(range.before)),
1460
+ core.types.objectProperty(
1461
+ core.types.identifier("items"),
1462
+ core.types.arrowFunctionExpression([], cloneExpression(range.collection))
1463
+ ),
1464
+ core.types.objectProperty(core.types.identifier("rowKey"), core.types.cloneNode(range.keyCallback, true)),
1465
+ core.types.objectProperty(
1466
+ core.types.identifier("create"),
1467
+ core.types.arrowFunctionExpression(
1468
+ parameters.map((parameter) => core.types.cloneNode(parameter, true)),
1469
+ hostElementDescriptor(range.row)
1470
+ )
1471
+ ),
1472
+ core.types.objectProperty(
1473
+ core.types.identifier("bindings"),
1474
+ core.types.arrayExpression(
1475
+ range.bindings.map((binding) => keyedRowBindingObject(binding, parameters))
1476
+ )
1477
+ )
1478
+ ]);
1479
+ }
1480
+ function keyedRangesBoundary(blockRuntime, plan) {
1481
+ const name = core.types.jsxMemberExpression(
1482
+ core.types.jsxIdentifier(blockRuntime.name),
1483
+ core.types.jsxIdentifier("KeyedRanges")
1484
+ );
1485
+ const source = core.types.cloneNode(plan.source, true);
1486
+ let rootRef;
1487
+ source.openingElement.attributes = source.openingElement.attributes.filter((attribute) => {
1488
+ if (!core.types.isJSXAttribute(attribute) || jsxAttributeName(attribute) !== "ref" || !core.types.isJSXExpressionContainer(attribute.value) || core.types.isJSXEmptyExpression(attribute.value.expression)) {
1489
+ return true;
1490
+ }
1491
+ rootRef = cloneExpression(attribute.value.expression);
1492
+ return false;
1493
+ });
1494
+ return core.types.jsxElement(
1495
+ core.types.jsxOpeningElement(
1496
+ name,
1497
+ [
1498
+ core.types.jsxAttribute(core.types.jsxIdentifier("id"), core.types.jsxExpressionContainer(core.types.numericLiteral(plan.id))),
1499
+ core.types.jsxAttribute(
1500
+ core.types.jsxIdentifier("render"),
1501
+ core.types.jsxExpressionContainer(core.types.arrowFunctionExpression([], source))
1502
+ ),
1503
+ ...rootRef ? [core.types.jsxAttribute(core.types.jsxIdentifier("rootRef"), core.types.jsxExpressionContainer(rootRef))] : [],
1504
+ core.types.jsxAttribute(
1505
+ core.types.jsxIdentifier("ranges"),
1506
+ core.types.jsxExpressionContainer(
1507
+ core.types.arrayExpression(plan.ranges.map((range) => keyedRangeObject(range)))
1508
+ )
1509
+ ),
1510
+ core.types.jsxAttribute(
1511
+ core.types.jsxIdentifier("trailing"),
1512
+ core.types.jsxExpressionContainer(core.types.numericLiteral(plan.trailing))
1513
+ )
1514
+ ],
1515
+ true
1516
+ ),
1517
+ null,
1518
+ [],
1519
+ true
1520
+ );
1521
+ }
1212
1522
  function hostConditionalBranchObject(branch, bindings) {
1213
1523
  return core.types.objectExpression([
1214
1524
  core.types.objectProperty(
@@ -1256,6 +1566,67 @@ function hostConditionalBoundary(blockRuntime, plan) {
1256
1566
  }
1257
1567
  return core.types.jsxElement(core.types.jsxOpeningElement(name, attributes, true), null, [], true);
1258
1568
  }
1569
+ function conditionalRangeObject(range) {
1570
+ const properties = [
1571
+ core.types.objectProperty(core.types.identifier("before"), core.types.numericLiteral(range.before)),
1572
+ core.types.objectProperty(
1573
+ core.types.identifier("test"),
1574
+ core.types.arrowFunctionExpression([], cloneExpression(range.test))
1575
+ )
1576
+ ];
1577
+ if (range.logical) {
1578
+ properties.push(core.types.objectProperty(core.types.identifier("logical"), core.types.booleanLiteral(true)));
1579
+ }
1580
+ if (range.truthy) {
1581
+ properties.push(
1582
+ core.types.objectProperty(
1583
+ core.types.identifier("truthy"),
1584
+ hostConditionalBranchObject(range.truthy, range.truthyBindings)
1585
+ )
1586
+ );
1587
+ }
1588
+ if (range.falsy) {
1589
+ properties.push(
1590
+ core.types.objectProperty(
1591
+ core.types.identifier("falsy"),
1592
+ hostConditionalBranchObject(range.falsy, range.falsyBindings)
1593
+ )
1594
+ );
1595
+ }
1596
+ return core.types.objectExpression(properties);
1597
+ }
1598
+ function conditionalRangesBoundary(blockRuntime, plan) {
1599
+ const name = core.types.jsxMemberExpression(
1600
+ core.types.jsxIdentifier(blockRuntime.name),
1601
+ core.types.jsxIdentifier("ConditionalRanges")
1602
+ );
1603
+ const source = core.types.cloneNode(plan.source, true);
1604
+ const rootRefIndex = source.openingElement.attributes.findIndex(
1605
+ (attribute) => core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name) && attribute.name.name === "ref"
1606
+ );
1607
+ const rootRef = rootRefIndex >= 0 ? source.openingElement.attributes.splice(rootRefIndex, 1)[0] : void 0;
1608
+ const attributes = [
1609
+ core.types.jsxAttribute(core.types.jsxIdentifier("id"), core.types.jsxExpressionContainer(core.types.numericLiteral(plan.id))),
1610
+ core.types.jsxAttribute(
1611
+ core.types.jsxIdentifier("render"),
1612
+ core.types.jsxExpressionContainer(core.types.arrowFunctionExpression([], source))
1613
+ ),
1614
+ core.types.jsxAttribute(
1615
+ core.types.jsxIdentifier("ranges"),
1616
+ core.types.jsxExpressionContainer(
1617
+ core.types.arrayExpression(plan.ranges.map((range) => conditionalRangeObject(range)))
1618
+ )
1619
+ ),
1620
+ core.types.jsxAttribute(
1621
+ core.types.jsxIdentifier("trailing"),
1622
+ core.types.jsxExpressionContainer(core.types.numericLiteral(plan.trailing))
1623
+ )
1624
+ ];
1625
+ if (rootRef?.value) {
1626
+ attributes.push(core.types.jsxAttribute(core.types.jsxIdentifier("rootRef"), core.types.cloneNode(rootRef.value, true)));
1627
+ }
1628
+ return core.types.jsxElement(core.types.jsxOpeningElement(name, attributes, true), null, [], true);
1629
+ }
1259
1630
  function analyzeComponentIslandElement(element, statesByValue, safeGlobals, allowedComponentNames) {
1260
1631
  const name = element.openingElement.name;
1261
1632
  if (!core.types.isJSXIdentifier(name) || !isComponentName(name.name)) {
@@ -1421,6 +1792,39 @@ function analyzeComposableBlocks(root, statesByValue, safeGlobals, listNames, al
1421
1792
  }
1422
1793
  if (core.types.isJSXElement(child)) {
1423
1794
  if (isHostElement(child)) {
1795
+ const keyedRanges = insideConditional ? void 0 : analyzeKeyedRangesContainer(child, statesByValue, safeGlobals, listNames);
1796
+ if (keyedRanges) {
1797
+ plans.push({
1798
+ kind: "keyed-ranges",
1799
+ id: nextId++,
1800
+ parent,
1801
+ dependencies: keyedRanges.dependencies,
1802
+ source: child,
1803
+ ranges: keyedRanges.ranges,
1804
+ trailing: keyedRanges.trailing
1805
+ });
1806
+ for (const range of keyedRanges.ranges) {
1807
+ if (core.types.isJSXElement(range.source)) ownedElements.add(range.source);
1808
+ else keyedExpressions.add(range.source);
1809
+ }
1810
+ continue;
1811
+ }
1812
+ const conditionalRanges = insideConditional ? void 0 : analyzeConditionalRangesContainer(child, statesByValue, safeGlobals);
1813
+ if (conditionalRanges) {
1814
+ plans.push({
1815
+ kind: "conditional-ranges",
1816
+ id: nextId++,
1817
+ parent,
1818
+ dependencies: conditionalRanges.dependencies,
1819
+ source: child,
1820
+ ranges: conditionalRanges.ranges,
1821
+ trailing: conditionalRanges.trailing
1822
+ });
1823
+ for (const range of conditionalRanges.ranges) {
1824
+ conditionalExpressions.add(range.source);
1825
+ }
1826
+ continue;
1827
+ }
1424
1828
  const hostConditional = analyzeHostConditionalContainer(
1425
1829
  child,
1426
1830
  statesByValue,
@@ -1553,6 +1957,54 @@ function analyzeComposableBlocks(root, statesByValue, safeGlobals, listNames, al
1553
1957
  }
1554
1958
  return { dependencies };
1555
1959
  };
1960
+ const rootRanges = analyzeKeyedRangesContainer(root, statesByValue, safeGlobals, listNames, true);
1961
+ if (rootRanges) {
1962
+ plans.push({
1963
+ kind: "keyed-ranges",
1964
+ id: nextId++,
1965
+ dependencies: rootRanges.dependencies,
1966
+ source: root,
1967
+ ranges: rootRanges.ranges,
1968
+ trailing: rootRanges.trailing
1969
+ });
1970
+ for (const range of rootRanges.ranges) {
1971
+ if (core.types.isJSXElement(range.source)) ownedElements.add(range.source);
1972
+ else keyedExpressions.add(range.source);
1973
+ }
1974
+ return {
1975
+ plans,
1976
+ componentElements,
1977
+ conditionalExpressions,
1978
+ ownedElements,
1979
+ keyedExpressions
1980
+ };
1981
+ }
1982
+ const rootConditionalRanges = analyzeConditionalRangesContainer(
1983
+ root,
1984
+ statesByValue,
1985
+ safeGlobals,
1986
+ true
1987
+ );
1988
+ if (rootConditionalRanges) {
1989
+ plans.push({
1990
+ kind: "conditional-ranges",
1991
+ id: nextId++,
1992
+ dependencies: rootConditionalRanges.dependencies,
1993
+ source: root,
1994
+ ranges: rootConditionalRanges.ranges,
1995
+ trailing: rootConditionalRanges.trailing
1996
+ });
1997
+ for (const range of rootConditionalRanges.ranges) {
1998
+ conditionalExpressions.add(range.source);
1999
+ }
2000
+ return {
2001
+ plans,
2002
+ componentElements,
2003
+ conditionalExpressions,
2004
+ ownedElements,
2005
+ keyedExpressions
2006
+ };
2007
+ }
1556
2008
  const result = visitHost(root, void 0, false);
1557
2009
  if (result.reason) return { reason: result.reason };
1558
2010
  return {
@@ -1589,6 +2041,13 @@ function lowerComposableBlocks(root, plans, blockRuntime, listNames) {
1589
2041
  const planBySource = new Map(
1590
2042
  plans.map((plan) => [plan.source, plan])
1591
2043
  );
2044
+ const rootPlan = planBySource.get(root);
2045
+ if (rootPlan?.kind === "keyed-ranges") {
2046
+ return keyedRangesBoundary(blockRuntime, rootPlan);
2047
+ }
2048
+ if (rootPlan?.kind === "conditional-ranges") {
2049
+ return conditionalRangesBoundary(blockRuntime, rootPlan);
2050
+ }
1592
2051
  const visit = (element) => {
1593
2052
  element.children = element.children.map((child) => {
1594
2053
  if (core.types.isJSXElement(child)) {
@@ -1599,6 +2058,12 @@ function lowerComposableBlocks(root, plans, blockRuntime, listNames) {
1599
2058
  if (plan2?.kind === "keyed-rows") {
1600
2059
  return keyedRowsBoundary(blockRuntime, plan2);
1601
2060
  }
2061
+ if (plan2?.kind === "keyed-ranges") {
2062
+ return keyedRangesBoundary(blockRuntime, plan2);
2063
+ }
2064
+ if (plan2?.kind === "conditional-ranges") {
2065
+ return conditionalRangesBoundary(blockRuntime, plan2);
2066
+ }
1602
2067
  if (plan2?.kind === "component") {
1603
2068
  return componentIslandBoundary(blockRuntime, plan2, child);
1604
2069
  }
@@ -2032,11 +2497,15 @@ function compileCandidate(candidate, createComponentIdentifier, useStateNames, r
2032
2497
  const handlerRewrite = rewriteHandlerAccess(rootWithDerived, handlersByName);
2033
2498
  if (handlerRewrite.reason) return handlerRewrite.reason;
2034
2499
  if (!handlerRewrite.root) return "event handlers could not be prepared safely";
2035
- const expandedRoot = rewriteDestructuredPropAccess(
2500
+ const expandedRootWithDeferredEvents = rewriteDestructuredPropAccess(
2036
2501
  handlerRewrite.root,
2037
2502
  propsPlan.destructuredNames,
2038
2503
  propsPlan.definitionParameter
2039
2504
  );
2505
+ const expandedRoot = stabilizeDeferredEventCurrentTargets(
2506
+ expandedRootWithDeferredEvents,
2507
+ new Set(states.map((state) => state.setterName))
2508
+ );
2040
2509
  const setterReason = validateSetterUsage(expandedRoot, statesBySetter);
2041
2510
  if (setterReason) return setterReason;
2042
2511
  const referencedComponentNames = /* @__PURE__ */ new Set();