marko 6.3.28 → 6.3.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.
@@ -23,6 +23,7 @@ export declare const PlaceholderBranch = "P";
23
23
  export declare const PlaceholderContent = "Q";
24
24
  export declare const Renderer = "R";
25
25
  export declare const StartNode = "S";
26
+ export declare const Subscriptions = "Z";
26
27
  export declare const TagVariable = "T";
27
28
  export declare const TagVariableChange = "U";
28
29
  type Self = typeof import("./accessor-prop");
@@ -23,6 +23,7 @@ export declare const PlaceholderBranch = "#PlaceholderBranch";
23
23
  export declare const PlaceholderContent = "#PlaceholderContent";
24
24
  export declare const Renderer = "#Renderer";
25
25
  export declare const StartNode = "#StartNode";
26
+ export declare const Subscriptions = "#Subscriptions";
26
27
  export declare const TagVariable = "#TagVariable";
27
28
  export declare const TagVariableChange = "#TagVariableChange";
28
29
  type Self = typeof import("./accessor-prop.debug");
@@ -29,6 +29,7 @@ export interface Scope {
29
29
  [AccessorProp.AbortControllers]: Record<string | number, AbortController | void> | undefined;
30
30
  [AccessorProp.ClosestBranch]: BranchScope | undefined;
31
31
  [AccessorProp.ClosestBranchId]: number | undefined;
32
+ [AccessorProp.Subscriptions]: Set<Scope>[] | undefined;
32
33
  [x: `___${string}`]: never;
33
34
  [x: string | number]: any;
34
35
  }
package/dist/debug/dom.js CHANGED
@@ -60,6 +60,7 @@ const PlaceholderBranch = "#PlaceholderBranch";
60
60
  const PlaceholderContent = "#PlaceholderContent";
61
61
  const Renderer = "#Renderer";
62
62
  const StartNode = "#StartNode";
63
+ const Subscriptions = "#Subscriptions";
63
64
  const TagVariable = "#TagVariable";
64
65
  const TagVariableChange = "#TagVariableChange";
65
66
  //#endregion
@@ -400,17 +401,21 @@ function destroyBranch(branch) {
400
401
  function destroyScope(scope) {
401
402
  if (scope["#Gen"]) {
402
403
  destroyNestedScopes(scope);
403
- resetControllers(scope);
404
+ cleanupScope(scope);
404
405
  }
405
406
  }
406
407
  const destroyNestedScopes = function destroyNestedScopes(scope) {
407
408
  scope[Gen$1] = 0;
408
409
  scope[BranchScopes]?.forEach(destroyNestedScopes);
409
- scope[AbortScopes]?.forEach(resetControllers);
410
+ scope[AbortScopes]?.forEach(cleanupScope);
410
411
  };
411
- function resetControllers(scope) {
412
+ function cleanupScope(scope) {
413
+ scope[Subscriptions]?.forEach(unsubscribe, scope);
412
414
  for (const id in scope[AbortControllers]) $signalReset(scope, id);
413
415
  }
416
+ function unsubscribe(subscribers) {
417
+ subscribers.delete(this);
418
+ }
414
419
  function removeAndDestroyBranch(branch) {
415
420
  destroyBranch(branch);
416
421
  removeChildNodes(branch[StartNode], branch[EndNode]);
@@ -565,10 +570,8 @@ function _if_closure(ownerConditionalNodeAccessor, branch, fn) {
565
570
  }
566
571
  function subscribeToScopeSet(ownerScope, accessor, scope) {
567
572
  const subscribers = ownerScope[accessor] ||= /* @__PURE__ */ new Set();
568
- if (!subscribers.has(scope)) {
569
- subscribers.add(scope);
570
- $signal(scope, -1).addEventListener("abort", () => ownerScope[accessor].delete(scope));
571
- }
573
+ const { size } = subscribers;
574
+ if (subscribers.add(scope).size !== size) trackCleanup(scope, subscribers);
572
575
  }
573
576
  function _closure(...closureSignals) {
574
577
  const [firstSignal] = closureSignals;
@@ -1160,18 +1163,19 @@ function removeChildNodes(startNode, endNode) {
1160
1163
  }
1161
1164
  }
1162
1165
  function insertChildNodes(parentNode, referenceNode, startNode, endNode) {
1163
- parentNode.insertBefore(toInsertNode(startNode, endNode), referenceNode);
1166
+ if (parentNode.isConnected) parentNode.insertBefore(toInsertNode(startNode, endNode), referenceNode);
1167
+ else {
1168
+ const stop = endNode.nextSibling;
1169
+ while (startNode !== stop) {
1170
+ const next = startNode.nextSibling;
1171
+ parentNode.insertBefore(startNode, referenceNode);
1172
+ startNode = next;
1173
+ }
1174
+ }
1175
+ return parentNode;
1164
1176
  }
1165
1177
  function toInsertNode(startNode, endNode) {
1166
- if (startNode === endNode) return startNode;
1167
- const parent = new DocumentFragment();
1168
- const stop = endNode.nextSibling;
1169
- while (startNode !== stop) {
1170
- const next = startNode.nextSibling;
1171
- parent.appendChild(startNode);
1172
- startNode = next;
1173
- }
1174
- return parent;
1178
+ return startNode === endNode ? startNode : insertChildNodes(new DocumentFragment(), null, startNode, endNode);
1175
1179
  }
1176
1180
  //#endregion
1177
1181
  //#region src/dom/resolve-cursor-position.ts
@@ -1694,7 +1698,7 @@ function _if(nodeAccessor, ...branchesArgs) {
1694
1698
  if (newBranch !== (scope[branchAccessor] ?? (scope["BranchScopes:" + nodeAccessor] && 0))) setConditionalRenderer(scope, nodeAccessor, branches[scope[branchAccessor] = newBranch], createAndSetupBranch);
1695
1699
  };
1696
1700
  }
1697
- function _show(nodeAccessor, startNodeAccessor) {
1701
+ function _show(nodeAccessor, startNodeAccessor, endNodeAccessor) {
1698
1702
  const rangeAccessor = BranchScopes$1 + nodeAccessor;
1699
1703
  enableBranches();
1700
1704
  return (scope, display) => {
@@ -1705,7 +1709,7 @@ function _show(nodeAccessor, startNodeAccessor) {
1705
1709
  if (!range) {
1706
1710
  range = scope[rangeAccessor] = {};
1707
1711
  range[StartNode] = onlyChild ? parentNode.firstChild : scope[startNodeAccessor];
1708
- range[EndNode] = onlyChild ? parentNode.lastChild : referenceNode.previousSibling;
1712
+ range[EndNode] = onlyChild ? parentNode.lastChild : endNodeAccessor === void 0 ? referenceNode.previousSibling : scope[endNodeAccessor];
1709
1713
  }
1710
1714
  let startNode = range[StartNode];
1711
1715
  if (range["#Id"] && startNode === range["#EndNode"] && startNode.tagName === "T") {
@@ -1716,10 +1720,16 @@ function _show(nodeAccessor, startNodeAccessor) {
1716
1720
  range[EndNode] = wrapper.lastChild;
1717
1721
  wrapper.replaceWith(...wrapper.childNodes);
1718
1722
  }
1719
- const inDom = startNode.parentNode === parentNode;
1723
+ const inDom = onlyChild ? !!parentNode.firstChild : startNode.parentNode === parentNode;
1720
1724
  if (display) {
1721
1725
  if (!inDom) insertBranchBefore(range, parentNode, onlyChild ? null : referenceNode);
1722
- } else if (inDom) tempDetachBranch(range);
1726
+ } else if (inDom) {
1727
+ if (onlyChild) {
1728
+ range[StartNode] = parentNode.firstChild;
1729
+ range[EndNode] = parentNode.lastChild;
1730
+ }
1731
+ tempDetachBranch(range);
1732
+ }
1723
1733
  };
1724
1734
  }
1725
1735
  function patchDynamicTag(fn) {
@@ -2084,9 +2094,15 @@ function $signalReset(scope, id) {
2084
2094
  }
2085
2095
  }
2086
2096
  function $signal(scope, id) {
2087
- if (scope["#ClosestBranch"]) (scope[ClosestBranch][AbortScopes] ||= /* @__PURE__ */ new Set()).add(scope);
2097
+ trackCleanup(scope);
2088
2098
  return ((scope[AbortControllers] ||= {})[id] ||= new AbortController()).signal;
2089
2099
  }
2100
+ /** Enrols `scope` with its branch so destroying the branch cleans it up. */
2101
+ function trackCleanup(scope, subscribers) {
2102
+ const branch = scope[ClosestBranch];
2103
+ if (branch) (branch[AbortScopes] ||= /* @__PURE__ */ new Set()).add(scope);
2104
+ if (subscribers) (scope[Subscriptions] ||= []).push(subscribers);
2105
+ }
2090
2106
  function abort(ctrl) {
2091
2107
  ctrl.abort();
2092
2108
  }
@@ -58,6 +58,7 @@ const PlaceholderBranch = "#PlaceholderBranch";
58
58
  const PlaceholderContent = "#PlaceholderContent";
59
59
  const Renderer = "#Renderer";
60
60
  const StartNode = "#StartNode";
61
+ const Subscriptions = "#Subscriptions";
61
62
  const TagVariable = "#TagVariable";
62
63
  const TagVariableChange = "#TagVariableChange";
63
64
  //#endregion
@@ -398,17 +399,21 @@ function destroyBranch(branch) {
398
399
  function destroyScope(scope) {
399
400
  if (scope["#Gen"]) {
400
401
  destroyNestedScopes(scope);
401
- resetControllers(scope);
402
+ cleanupScope(scope);
402
403
  }
403
404
  }
404
405
  const destroyNestedScopes = function destroyNestedScopes(scope) {
405
406
  scope[Gen$1] = 0;
406
407
  scope[BranchScopes]?.forEach(destroyNestedScopes);
407
- scope[AbortScopes]?.forEach(resetControllers);
408
+ scope[AbortScopes]?.forEach(cleanupScope);
408
409
  };
409
- function resetControllers(scope) {
410
+ function cleanupScope(scope) {
411
+ scope[Subscriptions]?.forEach(unsubscribe, scope);
410
412
  for (const id in scope[AbortControllers]) $signalReset(scope, id);
411
413
  }
414
+ function unsubscribe(subscribers) {
415
+ subscribers.delete(this);
416
+ }
412
417
  function removeAndDestroyBranch(branch) {
413
418
  destroyBranch(branch);
414
419
  removeChildNodes(branch[StartNode], branch[EndNode]);
@@ -563,10 +568,8 @@ function _if_closure(ownerConditionalNodeAccessor, branch, fn) {
563
568
  }
564
569
  function subscribeToScopeSet(ownerScope, accessor, scope) {
565
570
  const subscribers = ownerScope[accessor] ||= /* @__PURE__ */ new Set();
566
- if (!subscribers.has(scope)) {
567
- subscribers.add(scope);
568
- $signal(scope, -1).addEventListener("abort", () => ownerScope[accessor].delete(scope));
569
- }
571
+ const { size } = subscribers;
572
+ if (subscribers.add(scope).size !== size) trackCleanup(scope, subscribers);
570
573
  }
571
574
  function _closure(...closureSignals) {
572
575
  const [firstSignal] = closureSignals;
@@ -1158,18 +1161,19 @@ function removeChildNodes(startNode, endNode) {
1158
1161
  }
1159
1162
  }
1160
1163
  function insertChildNodes(parentNode, referenceNode, startNode, endNode) {
1161
- parentNode.insertBefore(toInsertNode(startNode, endNode), referenceNode);
1164
+ if (parentNode.isConnected) parentNode.insertBefore(toInsertNode(startNode, endNode), referenceNode);
1165
+ else {
1166
+ const stop = endNode.nextSibling;
1167
+ while (startNode !== stop) {
1168
+ const next = startNode.nextSibling;
1169
+ parentNode.insertBefore(startNode, referenceNode);
1170
+ startNode = next;
1171
+ }
1172
+ }
1173
+ return parentNode;
1162
1174
  }
1163
1175
  function toInsertNode(startNode, endNode) {
1164
- if (startNode === endNode) return startNode;
1165
- const parent = new DocumentFragment();
1166
- const stop = endNode.nextSibling;
1167
- while (startNode !== stop) {
1168
- const next = startNode.nextSibling;
1169
- parent.appendChild(startNode);
1170
- startNode = next;
1171
- }
1172
- return parent;
1176
+ return startNode === endNode ? startNode : insertChildNodes(new DocumentFragment(), null, startNode, endNode);
1173
1177
  }
1174
1178
  //#endregion
1175
1179
  //#region src/dom/resolve-cursor-position.ts
@@ -1692,7 +1696,7 @@ function _if(nodeAccessor, ...branchesArgs) {
1692
1696
  if (newBranch !== (scope[branchAccessor] ?? (scope["BranchScopes:" + nodeAccessor] && 0))) setConditionalRenderer(scope, nodeAccessor, branches[scope[branchAccessor] = newBranch], createAndSetupBranch);
1693
1697
  };
1694
1698
  }
1695
- function _show(nodeAccessor, startNodeAccessor) {
1699
+ function _show(nodeAccessor, startNodeAccessor, endNodeAccessor) {
1696
1700
  const rangeAccessor = BranchScopes$1 + nodeAccessor;
1697
1701
  enableBranches();
1698
1702
  return (scope, display) => {
@@ -1703,7 +1707,7 @@ function _show(nodeAccessor, startNodeAccessor) {
1703
1707
  if (!range) {
1704
1708
  range = scope[rangeAccessor] = {};
1705
1709
  range[StartNode] = onlyChild ? parentNode.firstChild : scope[startNodeAccessor];
1706
- range[EndNode] = onlyChild ? parentNode.lastChild : referenceNode.previousSibling;
1710
+ range[EndNode] = onlyChild ? parentNode.lastChild : endNodeAccessor === void 0 ? referenceNode.previousSibling : scope[endNodeAccessor];
1707
1711
  }
1708
1712
  let startNode = range[StartNode];
1709
1713
  if (range["#Id"] && startNode === range["#EndNode"] && startNode.tagName === "T") {
@@ -1714,10 +1718,16 @@ function _show(nodeAccessor, startNodeAccessor) {
1714
1718
  range[EndNode] = wrapper.lastChild;
1715
1719
  wrapper.replaceWith(...wrapper.childNodes);
1716
1720
  }
1717
- const inDom = startNode.parentNode === parentNode;
1721
+ const inDom = onlyChild ? !!parentNode.firstChild : startNode.parentNode === parentNode;
1718
1722
  if (display) {
1719
1723
  if (!inDom) insertBranchBefore(range, parentNode, onlyChild ? null : referenceNode);
1720
- } else if (inDom) tempDetachBranch(range);
1724
+ } else if (inDom) {
1725
+ if (onlyChild) {
1726
+ range[StartNode] = parentNode.firstChild;
1727
+ range[EndNode] = parentNode.lastChild;
1728
+ }
1729
+ tempDetachBranch(range);
1730
+ }
1721
1731
  };
1722
1732
  }
1723
1733
  function patchDynamicTag(fn) {
@@ -2082,9 +2092,15 @@ function $signalReset(scope, id) {
2082
2092
  }
2083
2093
  }
2084
2094
  function $signal(scope, id) {
2085
- if (scope["#ClosestBranch"]) (scope[ClosestBranch][AbortScopes] ||= /* @__PURE__ */ new Set()).add(scope);
2095
+ trackCleanup(scope);
2086
2096
  return ((scope[AbortControllers] ||= {})[id] ||= new AbortController()).signal;
2087
2097
  }
2098
+ /** Enrols `scope` with its branch so destroying the branch cleans it up. */
2099
+ function trackCleanup(scope, subscribers) {
2100
+ const branch = scope[ClosestBranch];
2101
+ if (branch) (branch[AbortScopes] ||= /* @__PURE__ */ new Set()).add(scope);
2102
+ if (subscribers) (scope[Subscriptions] ||= []).push(subscribers);
2103
+ }
2088
2104
  function abort(ctrl) {
2089
2105
  ctrl.abort();
2090
2106
  }
@@ -1,3 +1,5 @@
1
1
  import { type Scope } from "../common/types";
2
2
  export declare function $signalReset(scope: Scope, id: string | number): void;
3
3
  export declare function $signal(scope: Scope, id: string | number): AbortSignal;
4
+ /** Enrols `scope` with its branch so destroying the branch cleans it up. */
5
+ export declare function trackCleanup(scope: Scope, subscribers?: Set<Scope>): void;
@@ -10,7 +10,7 @@ export declare function _try(nodeAccessor: EncodedAccessor, template?: string |
10
10
  }) => void;
11
11
  export declare function renderCatch(scope: Scope, error: unknown): void;
12
12
  export declare function _if(nodeAccessor: EncodedAccessor, ...branchesArgs: (string | SetupFn | 0)[]): (scope: Scope, newBranch: number) => void;
13
- export declare function _show(nodeAccessor: EncodedAccessor, startNodeAccessor?: EncodedAccessor): (scope: Scope, display: unknown) => void;
13
+ export declare function _show(nodeAccessor: EncodedAccessor, startNodeAccessor?: EncodedAccessor, endNodeAccessor?: EncodedAccessor): (scope: Scope, display: unknown) => void;
14
14
  export declare function patchDynamicTag(fn: <T extends typeof _dynamic_tag>(cond: T) => T): void;
15
15
  export declare let _dynamic_tag: (nodeAccessor: EncodedAccessor, getContent?: ((scope: Scope) => Renderer) | 0, getTagVar?: (() => Signal<unknown>) | 0, inputIsArgs?: 1) => Signal<Renderer | string | undefined>;
16
16
  export declare function _dynamic_tag_content(nodeAccessor: EncodedAccessor): Signal<Renderer | undefined>;
package/dist/dom/dom.d.ts CHANGED
@@ -27,5 +27,5 @@ export declare function _lifecycle(scope: Scope, thisObj: Record<string, unknown
27
27
  onDestroy?: (this: unknown) => void;
28
28
  }, index?: number): void;
29
29
  export declare function removeChildNodes(startNode: ChildNode, endNode: ChildNode): void;
30
- export declare function insertChildNodes(parentNode: ParentNode, referenceNode: Node | null, startNode: Node, endNode: Node): void;
30
+ export declare function insertChildNodes(parentNode: ParentNode, referenceNode: Node | null, startNode: Node, endNode: Node): ParentNode;
31
31
  export declare function toInsertNode(startNode: Node, endNode: Node): Node;
package/dist/dom.js CHANGED
@@ -5,7 +5,7 @@ let empty = [], rest = Symbol(), unsafeStyleAttrReg = /[\\;]/g, replaceUnsafeSty
5
5
  else for (let name in val) part = stringify(name, val[name]), part && (str += sep + part, sep = delimiter);
6
6
  return str;
7
7
  }, decodeAccessor = (num) => (num + (num < 26 ? 10 : num < 962 ? 334 : 11998)).toString(36), delegate = (type, handler) => handler[type] ||= (document.addEventListener(type, handler, !0), 1), parsers = {}, nextScopeId = 1e6, collectingScopes, destroyNestedScopes = function destroyNestedScopes(scope) {
8
- scope.H = 0, scope.D?.forEach(destroyNestedScopes), scope.B?.forEach(resetControllers);
8
+ scope.H = 0, scope.D?.forEach(destroyNestedScopes), scope.B?.forEach(cleanupScope);
9
9
  }, isScheduled, channel, _return = (scope, value) => scope.T?.(value), _var_change = (scope, value) => scope.U?.(value), tagIdsByGlobal = /* @__PURE__ */ new WeakMap(), walker = /* @__PURE__ */ document.createTreeWalker(document), walkInternal = function walkInternal(currentWalkIndex, walkCodes, scope) {
10
10
  let value, currentMultiplier, storedMultiplier = 0, currentScopeIndex = 0;
11
11
  for (; currentWalkIndex < walkCodes.length;) if (value = walkCodes.charCodeAt(currentWalkIndex++), currentMultiplier = storedMultiplier, storedMultiplier = 0, value === 32) {
@@ -250,11 +250,15 @@ function destroyBranch(branch) {
250
250
  branch.N?.D?.delete(branch), destroyNestedScopes(branch);
251
251
  }
252
252
  function destroyScope(scope) {
253
- scope.H && (destroyNestedScopes(scope), resetControllers(scope));
253
+ scope.H && (destroyNestedScopes(scope), cleanupScope(scope));
254
254
  }
255
- function resetControllers(scope) {
255
+ function cleanupScope(scope) {
256
+ scope.Z?.forEach(unsubscribe, scope);
256
257
  for (let id in scope.A) $signalReset(scope, id);
257
258
  }
259
+ function unsubscribe(subscribers) {
260
+ subscribers.delete(this);
261
+ }
258
262
  function removeAndDestroyBranch(branch) {
259
263
  destroyBranch(branch), removeChildNodes(branch.S, branch.K);
260
264
  }
@@ -347,8 +351,8 @@ function _if_closure(ownerConditionalNodeAccessor, branch, fn) {
347
351
  return ownerSignal._ = fn, ownerSignal;
348
352
  }
349
353
  function subscribeToScopeSet(ownerScope, accessor, scope) {
350
- let subscribers = ownerScope[accessor] ||= /* @__PURE__ */ new Set();
351
- subscribers.has(scope) || (subscribers.add(scope), $signal(scope, -1).addEventListener("abort", () => ownerScope[accessor].delete(scope)));
354
+ let subscribers = ownerScope[accessor] ||= /* @__PURE__ */ new Set(), { size } = subscribers;
355
+ subscribers.add(scope).size !== size && trackCleanup(scope, subscribers);
352
356
  }
353
357
  function _closure(...closureSignals) {
354
358
  let [firstSignal] = closureSignals, scopeInstances = firstSignal.a, signalIndex = firstSignal.b;
@@ -702,16 +706,18 @@ function removeChildNodes(startNode, endNode) {
702
706
  }
703
707
  }
704
708
  function insertChildNodes(parentNode, referenceNode, startNode, endNode) {
705
- parentNode.insertBefore(toInsertNode(startNode, endNode), referenceNode);
709
+ if (parentNode.isConnected) parentNode.insertBefore(toInsertNode(startNode, endNode), referenceNode);
710
+ else {
711
+ let stop = endNode.nextSibling;
712
+ for (; startNode !== stop;) {
713
+ let next = startNode.nextSibling;
714
+ parentNode.insertBefore(startNode, referenceNode), startNode = next;
715
+ }
716
+ }
717
+ return parentNode;
706
718
  }
707
719
  function toInsertNode(startNode, endNode) {
708
- if (startNode === endNode) return startNode;
709
- let parent = new DocumentFragment(), stop = endNode.nextSibling;
710
- for (; startNode !== stop;) {
711
- let next = startNode.nextSibling;
712
- parent.appendChild(startNode), startNode = next;
713
- }
714
- return parent;
720
+ return startNode === endNode ? startNode : insertChildNodes(new DocumentFragment(), null, startNode, endNode);
715
721
  }
716
722
  //#endregion
717
723
  //#region src/dom/resolve-cursor-position.ts
@@ -1054,19 +1060,19 @@ function _if(nodeAccessor, ...branchesArgs) {
1054
1060
  newBranch !== (scope[branchAccessor] ?? (scope["A" + nodeAccessor] && 0)) && setConditionalRenderer(scope, nodeAccessor, branches[scope[branchAccessor] = newBranch], createAndSetupBranch);
1055
1061
  };
1056
1062
  }
1057
- function _show(nodeAccessor, startNodeAccessor) {
1058
- nodeAccessor = decodeAccessor(nodeAccessor), startNodeAccessor !== void 0 && (startNodeAccessor = decodeAccessor(startNodeAccessor));
1063
+ function _show(nodeAccessor, startNodeAccessor, endNodeAccessor) {
1064
+ nodeAccessor = decodeAccessor(nodeAccessor), startNodeAccessor !== void 0 && (startNodeAccessor = decodeAccessor(startNodeAccessor)), endNodeAccessor !== void 0 && (endNodeAccessor = decodeAccessor(endNodeAccessor));
1059
1065
  let rangeAccessor = "A" + nodeAccessor;
1060
1066
  return enableBranches(), (scope, display) => {
1061
1067
  let referenceNode = scope[nodeAccessor], onlyChild = referenceNode.nodeType === 1, parentNode = onlyChild ? referenceNode : referenceNode.parentNode, range = scope[rangeAccessor];
1062
- range || (range = scope[rangeAccessor] = {}, range.S = onlyChild ? parentNode.firstChild : scope[startNodeAccessor], range.K = onlyChild ? parentNode.lastChild : referenceNode.previousSibling);
1068
+ range || (range = scope[rangeAccessor] = {}, range.S = onlyChild ? parentNode.firstChild : scope[startNodeAccessor], range.K = onlyChild ? parentNode.lastChild : endNodeAccessor === void 0 ? referenceNode.previousSibling : scope[endNodeAccessor]);
1063
1069
  let startNode = range.S;
1064
1070
  if (range.L && startNode === range.K && startNode.tagName === "T") {
1065
1071
  let wrapper = startNode;
1066
1072
  wrapper.firstChild || wrapper.appendChild(new Text()), range = scope[rangeAccessor] = {}, range.S = startNode = wrapper.firstChild, range.K = wrapper.lastChild, wrapper.replaceWith(...wrapper.childNodes);
1067
1073
  }
1068
- let inDom = startNode.parentNode === parentNode;
1069
- display ? inDom || insertBranchBefore(range, parentNode, onlyChild ? null : referenceNode) : inDom && tempDetachBranch(range);
1074
+ let inDom = onlyChild ? !!parentNode.firstChild : startNode.parentNode === parentNode;
1075
+ display ? inDom || insertBranchBefore(range, parentNode, onlyChild ? null : referenceNode) : inDom && (onlyChild && (range.S = parentNode.firstChild, range.K = parentNode.lastChild), tempDetachBranch(range));
1070
1076
  };
1071
1077
  }
1072
1078
  function patchDynamicTag(fn) {
@@ -1242,7 +1248,12 @@ function $signalReset(scope, id) {
1242
1248
  ctrl && (scope.A[id] = void 0, rendering ? queueEffect(ctrl, abort) : abort(ctrl));
1243
1249
  }
1244
1250
  function $signal(scope, id) {
1245
- return scope.F && (scope.F.B ||= /* @__PURE__ */ new Set()).add(scope), ((scope.A ||= {})[id] ||= new AbortController()).signal;
1251
+ return trackCleanup(scope), ((scope.A ||= {})[id] ||= new AbortController()).signal;
1252
+ }
1253
+ /** Enrols `scope` with its branch so destroying the branch cleans it up. */
1254
+ function trackCleanup(scope, subscribers) {
1255
+ let branch = scope.F;
1256
+ branch && (branch.B ||= /* @__PURE__ */ new Set()).add(scope), subscribers && (scope.Z ||= []).push(subscribers);
1246
1257
  }
1247
1258
  function abort(ctrl) {
1248
1259
  ctrl.abort();
package/dist/dom.mjs CHANGED
@@ -5,7 +5,7 @@ let empty = [], rest = Symbol(), unsafeStyleAttrReg = /[\\;]/g, replaceUnsafeSty
5
5
  else for (let name in val) part = stringify(name, val[name]), part && (str += sep + part, sep = delimiter);
6
6
  return str;
7
7
  }, decodeAccessor = (num) => (num + (num < 26 ? 10 : num < 962 ? 334 : 11998)).toString(36), delegate = (type, handler) => handler[type] ||= (document.addEventListener(type, handler, !0), 1), parsers = {}, nextScopeId = 1e6, collectingScopes, destroyNestedScopes = function destroyNestedScopes(scope) {
8
- scope.H = 0, scope.D?.forEach(destroyNestedScopes), scope.B?.forEach(resetControllers);
8
+ scope.H = 0, scope.D?.forEach(destroyNestedScopes), scope.B?.forEach(cleanupScope);
9
9
  }, isScheduled, channel, _return = (scope, value) => scope.T?.(value), _var_change = (scope, value) => scope.U?.(value), tagIdsByGlobal = /* @__PURE__ */ new WeakMap(), walker = /* @__PURE__ */ document.createTreeWalker(document), walkInternal = function walkInternal(currentWalkIndex, walkCodes, scope) {
10
10
  let value, currentMultiplier, storedMultiplier = 0, currentScopeIndex = 0;
11
11
  for (; currentWalkIndex < walkCodes.length;) if (value = walkCodes.charCodeAt(currentWalkIndex++), currentMultiplier = storedMultiplier, storedMultiplier = 0, value === 32) {
@@ -249,11 +249,15 @@ function destroyBranch(branch) {
249
249
  branch.N?.D?.delete(branch), destroyNestedScopes(branch);
250
250
  }
251
251
  function destroyScope(scope) {
252
- scope.H && (destroyNestedScopes(scope), resetControllers(scope));
252
+ scope.H && (destroyNestedScopes(scope), cleanupScope(scope));
253
253
  }
254
- function resetControllers(scope) {
254
+ function cleanupScope(scope) {
255
+ scope.Z?.forEach(unsubscribe, scope);
255
256
  for (let id in scope.A) $signalReset(scope, id);
256
257
  }
258
+ function unsubscribe(subscribers) {
259
+ subscribers.delete(this);
260
+ }
257
261
  function removeAndDestroyBranch(branch) {
258
262
  destroyBranch(branch), removeChildNodes(branch.S, branch.K);
259
263
  }
@@ -346,8 +350,8 @@ function _if_closure(ownerConditionalNodeAccessor, branch, fn) {
346
350
  return ownerSignal._ = fn, ownerSignal;
347
351
  }
348
352
  function subscribeToScopeSet(ownerScope, accessor, scope) {
349
- let subscribers = ownerScope[accessor] ||= /* @__PURE__ */ new Set();
350
- subscribers.has(scope) || (subscribers.add(scope), $signal(scope, -1).addEventListener("abort", () => ownerScope[accessor].delete(scope)));
353
+ let subscribers = ownerScope[accessor] ||= /* @__PURE__ */ new Set(), { size } = subscribers;
354
+ subscribers.add(scope).size !== size && trackCleanup(scope, subscribers);
351
355
  }
352
356
  function _closure(...closureSignals) {
353
357
  let [firstSignal] = closureSignals, scopeInstances = firstSignal.a, signalIndex = firstSignal.b;
@@ -701,16 +705,18 @@ function removeChildNodes(startNode, endNode) {
701
705
  }
702
706
  }
703
707
  function insertChildNodes(parentNode, referenceNode, startNode, endNode) {
704
- parentNode.insertBefore(toInsertNode(startNode, endNode), referenceNode);
708
+ if (parentNode.isConnected) parentNode.insertBefore(toInsertNode(startNode, endNode), referenceNode);
709
+ else {
710
+ let stop = endNode.nextSibling;
711
+ for (; startNode !== stop;) {
712
+ let next = startNode.nextSibling;
713
+ parentNode.insertBefore(startNode, referenceNode), startNode = next;
714
+ }
715
+ }
716
+ return parentNode;
705
717
  }
706
718
  function toInsertNode(startNode, endNode) {
707
- if (startNode === endNode) return startNode;
708
- let parent = new DocumentFragment(), stop = endNode.nextSibling;
709
- for (; startNode !== stop;) {
710
- let next = startNode.nextSibling;
711
- parent.appendChild(startNode), startNode = next;
712
- }
713
- return parent;
719
+ return startNode === endNode ? startNode : insertChildNodes(new DocumentFragment(), null, startNode, endNode);
714
720
  }
715
721
  //#endregion
716
722
  //#region src/dom/resolve-cursor-position.ts
@@ -1053,19 +1059,19 @@ function _if(nodeAccessor, ...branchesArgs) {
1053
1059
  newBranch !== (scope[branchAccessor] ?? (scope["A" + nodeAccessor] && 0)) && setConditionalRenderer(scope, nodeAccessor, branches[scope[branchAccessor] = newBranch], createAndSetupBranch);
1054
1060
  };
1055
1061
  }
1056
- function _show(nodeAccessor, startNodeAccessor) {
1057
- nodeAccessor = decodeAccessor(nodeAccessor), startNodeAccessor !== void 0 && (startNodeAccessor = decodeAccessor(startNodeAccessor));
1062
+ function _show(nodeAccessor, startNodeAccessor, endNodeAccessor) {
1063
+ nodeAccessor = decodeAccessor(nodeAccessor), startNodeAccessor !== void 0 && (startNodeAccessor = decodeAccessor(startNodeAccessor)), endNodeAccessor !== void 0 && (endNodeAccessor = decodeAccessor(endNodeAccessor));
1058
1064
  let rangeAccessor = "A" + nodeAccessor;
1059
1065
  return enableBranches(), (scope, display) => {
1060
1066
  let referenceNode = scope[nodeAccessor], onlyChild = referenceNode.nodeType === 1, parentNode = onlyChild ? referenceNode : referenceNode.parentNode, range = scope[rangeAccessor];
1061
- range || (range = scope[rangeAccessor] = {}, range.S = onlyChild ? parentNode.firstChild : scope[startNodeAccessor], range.K = onlyChild ? parentNode.lastChild : referenceNode.previousSibling);
1067
+ range || (range = scope[rangeAccessor] = {}, range.S = onlyChild ? parentNode.firstChild : scope[startNodeAccessor], range.K = onlyChild ? parentNode.lastChild : endNodeAccessor === void 0 ? referenceNode.previousSibling : scope[endNodeAccessor]);
1062
1068
  let startNode = range.S;
1063
1069
  if (range.L && startNode === range.K && startNode.tagName === "T") {
1064
1070
  let wrapper = startNode;
1065
1071
  wrapper.firstChild || wrapper.appendChild(new Text()), range = scope[rangeAccessor] = {}, range.S = startNode = wrapper.firstChild, range.K = wrapper.lastChild, wrapper.replaceWith(...wrapper.childNodes);
1066
1072
  }
1067
- let inDom = startNode.parentNode === parentNode;
1068
- display ? inDom || insertBranchBefore(range, parentNode, onlyChild ? null : referenceNode) : inDom && tempDetachBranch(range);
1073
+ let inDom = onlyChild ? !!parentNode.firstChild : startNode.parentNode === parentNode;
1074
+ display ? inDom || insertBranchBefore(range, parentNode, onlyChild ? null : referenceNode) : inDom && (onlyChild && (range.S = parentNode.firstChild, range.K = parentNode.lastChild), tempDetachBranch(range));
1069
1075
  };
1070
1076
  }
1071
1077
  function patchDynamicTag(fn) {
@@ -1241,7 +1247,12 @@ function $signalReset(scope, id) {
1241
1247
  ctrl && (scope.A[id] = void 0, rendering ? queueEffect(ctrl, abort) : abort(ctrl));
1242
1248
  }
1243
1249
  function $signal(scope, id) {
1244
- return scope.F && (scope.F.B ||= /* @__PURE__ */ new Set()).add(scope), ((scope.A ||= {})[id] ||= new AbortController()).signal;
1250
+ return trackCleanup(scope), ((scope.A ||= {})[id] ||= new AbortController()).signal;
1251
+ }
1252
+ /** Enrols `scope` with its branch so destroying the branch cleans it up. */
1253
+ function trackCleanup(scope, subscribers) {
1254
+ let branch = scope.F;
1255
+ branch && (branch.B ||= /* @__PURE__ */ new Set()).add(scope), subscribers && (scope.Z ||= []).push(subscribers);
1245
1256
  }
1246
1257
  function abort(ctrl) {
1247
1258
  ctrl.abort();
@@ -2,12 +2,14 @@ import { types as t } from "@marko/compiler";
2
2
  import { type Tag } from "@marko/compiler/babel-utils";
3
3
  import { type Binding } from "../util/references";
4
4
  declare const kStartBinding: unique symbol;
5
+ declare const kEndBinding: unique symbol;
5
6
  declare const kStaticDisplay: unique symbol;
6
7
  declare const kSingleNodeBody: unique symbol;
7
8
  declare const kDisplayRef: unique symbol;
8
9
  declare module "@marko/compiler/dist/types" {
9
10
  interface NodeExtra {
10
11
  [kStartBinding]?: Binding;
12
+ [kEndBinding]?: Binding;
11
13
  [kStaticDisplay]?: boolean;
12
14
  [kSingleNodeBody]?: boolean;
13
15
  [kDisplayRef]?: t.Expression;
@@ -162,6 +162,7 @@ var accessor_prop_debug_exports = /* @__PURE__ */ __exportAll({
162
162
  PlaceholderContent: () => PlaceholderContent$1,
163
163
  Renderer: () => Renderer$1,
164
164
  StartNode: () => StartNode$1,
165
+ Subscriptions: () => Subscriptions$1,
165
166
  TagVariable: () => TagVariable$1,
166
167
  TagVariableChange: () => TagVariableChange$2
167
168
  });
@@ -188,6 +189,7 @@ const PlaceholderBranch$1 = "#PlaceholderBranch";
188
189
  const PlaceholderContent$1 = "#PlaceholderContent";
189
190
  const Renderer$1 = "#Renderer";
190
191
  const StartNode$1 = "#StartNode";
192
+ const Subscriptions$1 = "#Subscriptions";
191
193
  const TagVariable$1 = "#TagVariable";
192
194
  const TagVariableChange$2 = "#TagVariableChange";
193
195
  //#endregion
@@ -948,6 +950,7 @@ var accessor_prop_exports = /* @__PURE__ */ __exportAll({
948
950
  PlaceholderContent: () => "Q",
949
951
  Renderer: () => "R",
950
952
  StartNode: () => "S",
953
+ Subscriptions: () => "Z",
951
954
  TagVariable: () => "T",
952
955
  TagVariableChange: () => "U"
953
956
  });
@@ -1514,6 +1517,7 @@ function isNativeNode(tag) {
1514
1517
  //#endregion
1515
1518
  //#region src/translator/visitors/function.ts
1516
1519
  const [getReferencesByFn] = createProgramState(() => /* @__PURE__ */ new Map());
1520
+ const [getReferencesByImportedFn] = createProgramState(() => /* @__PURE__ */ new Map());
1517
1521
  var function_default = { analyze(fn) {
1518
1522
  if (fn !== getFnRoot(fn)) return;
1519
1523
  const exprRoot = getExprRoot(fn);
@@ -1524,8 +1528,9 @@ var function_default = { analyze(fn) {
1524
1528
  const fnExtra = node.extra ??= {};
1525
1529
  fnExtra.section = section;
1526
1530
  fnExtra.name = node.id?.name || (isMarkoAttribute(markoRoot) ? markoRoot.node.default ? _marko_compiler.types.toIdentifier((markoRoot.parentPath.has("var") ? markoRoot.parentPath.get("var") : markoRoot.parentPath.get("name")).toString()) : markoRoot.node.name : _marko_compiler.types.isVariableDeclarator(fn.parent) && _marko_compiler.types.isIdentifier(fn.parent.id) ? fn.parent.id.name : _marko_compiler.types.isObjectMethod(node) && _marko_compiler.types.isIdentifier(node.key) ? node.key.name : "anonymous");
1531
+ reserveExportRegisterId(fnExtra, fn, markoRoot);
1527
1532
  if (isStaticRoot(markoRoot)) {
1528
- const refs = getStaticDeclRefs(fnExtra, fn);
1533
+ const refs = getStaticDeclRefs(fn);
1529
1534
  if (refs === true) registerFunction(fnExtra, true);
1530
1535
  else if (refs.size) getReferencesByFn().set(fnExtra, refs);
1531
1536
  } else if (shouldAlwaysRegister(markoRoot)) registerFunction(fnExtra, true);
@@ -1533,15 +1538,113 @@ var function_default = { analyze(fn) {
1533
1538
  } };
1534
1539
  function finalizeFunctionRegistry() {
1535
1540
  for (const [fnExtra, exprExtras] of getReferencesByFn()) {
1536
- let reason;
1537
- for (const exprExtra of exprExtras) reason = mergeSerializeReasons(reason, getAllSerializeReasonsForExtra(getCanonicalExtra(exprExtra)));
1541
+ const reason = resolveSerializeReason(exprExtras);
1538
1542
  if (reason) registerFunction(fnExtra, reason);
1539
1543
  }
1544
+ for (const [importedFn, exprExtras] of getReferencesByImportedFn()) if (resolveSerializeReason(exprExtras)) registerImportedFn(importedFn);
1545
+ }
1546
+ /**
1547
+ * Tracks a function imported from another template that reserved a register id.
1548
+ * The importing template registers it (under the reserved id) when its own
1549
+ * references reach something serialized.
1550
+ */
1551
+ function trackImportedFn(importDecl, local, resolved) {
1552
+ const binding = importDecl.scope.getBinding(local);
1553
+ if (!binding) return;
1554
+ const importedFn = {
1555
+ ...resolved,
1556
+ node: importDecl.node,
1557
+ local
1558
+ };
1559
+ const refs = /* @__PURE__ */ new Set();
1560
+ if (addBindingRefs(binding, refs, /* @__PURE__ */ new Set()) === true) registerImportedFn(importedFn);
1561
+ else if (refs.size) getReferencesByImportedFn().set(importedFn, refs);
1562
+ }
1563
+ /**
1564
+ * Finds the id reserved for an export, following `export ... from` hops. Each
1565
+ * template only records the exports it declares, so this resolves on demand:
1566
+ * a template in an import cycle is read while it is still analyzing, and would
1567
+ * hand out an incomplete map if the ids were pushed ahead of time.
1568
+ */
1569
+ function resolveRegisteredExport(file, exportName, seen = /* @__PURE__ */ new Set()) {
1570
+ const filename = file.opts.filename;
1571
+ const key = `${filename}\0${exportName}`;
1572
+ if (seen.has(key)) return;
1573
+ seen.add(key);
1574
+ const reserved = file.ast.program.extra?.registeredExports?.get(exportName);
1575
+ if (reserved) return {
1576
+ ...reserved,
1577
+ filename
1578
+ };
1579
+ for (const child of file.ast.program.body) {
1580
+ if (child.type !== "ExportNamedDeclaration" && child.type !== "ExportAllDeclaration" || !child.source || (child.exportKind || "value") !== "value") continue;
1581
+ const sourceFile = (0, _marko_compiler_babel_utils.loadFileForImport)(file, child.source.value);
1582
+ if (!sourceFile) continue;
1583
+ if (child.type === "ExportAllDeclaration") {
1584
+ const resolved = resolveRegisteredExport(sourceFile, exportName, seen);
1585
+ if (resolved) return resolved;
1586
+ continue;
1587
+ }
1588
+ for (const specifier of child.specifiers) {
1589
+ if (specifier.type !== "ExportSpecifier" || (specifier.exportKind || "value") !== "value" || getExportedName(specifier) !== exportName) continue;
1590
+ const resolved = resolveRegisteredExport(sourceFile, specifier.local.name, seen);
1591
+ if (resolved) return resolved;
1592
+ }
1593
+ }
1594
+ }
1595
+ function resolveSerializeReason(exprExtras) {
1596
+ let reason;
1597
+ for (const exprExtra of exprExtras) reason = mergeSerializeReasons(reason, getAllSerializeReasonsForExtra(getCanonicalExtra(exprExtra)));
1598
+ return reason;
1599
+ }
1600
+ function registerImportedFn({ node, ...importedFn }) {
1601
+ const extra = node.extra ??= {};
1602
+ extra.registeredImportedFns ??= [];
1603
+ extra.registeredImportedFns.push(importedFn);
1604
+ (0, _marko_compiler_babel_utils.getProgram)().node.extra.isInteractive = true;
1605
+ }
1606
+ function reserveExportRegisterId(fnExtra, fn, markoRoot) {
1607
+ if (!isStaticRoot(markoRoot)) return;
1608
+ const exportNames = getExportNames(fn, markoRoot);
1609
+ const exportName = exportNames?.find((name) => _marko_compiler.types.isValidIdentifier(name));
1610
+ if (!exportName) return;
1611
+ const { markoOpts, opts: { filename } } = (0, _marko_compiler_babel_utils.getFile)();
1612
+ const programExtra = (0, _marko_compiler_babel_utils.getProgram)().node.extra;
1613
+ const registerId = (0, _marko_compiler_babel_utils.getTemplateId)(markoOpts, filename, `${fnExtra.section.id}/export/${exportName}`);
1614
+ fnExtra.exportRegisterId = registerId;
1615
+ programExtra.registeredExports ??= /* @__PURE__ */ new Map();
1616
+ for (const name of exportNames) programExtra.registeredExports.set(name, {
1617
+ registerId,
1618
+ exportName
1619
+ });
1620
+ }
1621
+ function getExportNames(fn, markoRoot) {
1622
+ const localName = getExportableName(fn);
1623
+ if (localName === void 0) return;
1624
+ const exportNames = markoRoot.isExportNamedDeclaration() && !markoRoot.node.source ? [localName] : [];
1625
+ const binding = fn.scope.getBinding(localName);
1626
+ if (binding) for (const ref of binding.referencePaths) {
1627
+ const specifier = ref.parent;
1628
+ if (specifier?.type === "ExportSpecifier" && (specifier.exportKind || "value") === "value" && !ref.parentPath.parent.source) {
1629
+ const exportName = getExportedName(specifier);
1630
+ if (exportName !== "default") exportNames.push(exportName);
1631
+ }
1632
+ }
1633
+ return exportNames.length ? exportNames : void 0;
1634
+ }
1635
+ function getExportableName(fn) {
1636
+ const { node, parent } = fn;
1637
+ if (node.type === "FunctionDeclaration") return node.id?.name;
1638
+ if (_marko_compiler.types.isVariableDeclarator(parent) && parent.init === node && _marko_compiler.types.isIdentifier(parent.id) && _marko_compiler.types.isVariableDeclaration(fn.parentPath.parent, { kind: "const" })) return parent.id.name;
1639
+ }
1640
+ function getExportedName(specifier) {
1641
+ const { exported } = specifier;
1642
+ return exported.type === "Identifier" ? exported.name : exported.value;
1540
1643
  }
1541
1644
  function canIgnoreRegister(markoRoot, exprRoot) {
1542
1645
  return markoRoot.isMarkoPlaceholder() || markoRoot.isMarkoScriptlet() && (!markoRoot.node.static || markoRoot.node.target === "server") || markoRoot.isMarkoTag() && markoRoot.node.name == exprRoot.node || isMarkoAttribute(markoRoot) && (analyzeTagNameType(markoRoot.parentPath) === 0 && /^on[A-Z-]/.test(markoRoot.node.name) && !hasSpreadAttributeAfter(markoRoot) || isCoreTagName(markoRoot.parentPath, "script") || isCoreTagName(markoRoot.parentPath, "lifecycle") || isCoreTagName(markoRoot.parentPath, "for"));
1543
1646
  }
1544
- function getStaticDeclRefs(fnExtra, path, refs = /* @__PURE__ */ new Set(), seen = /* @__PURE__ */ new Set()) {
1647
+ function getStaticDeclRefs(path, refs = /* @__PURE__ */ new Set(), seen = /* @__PURE__ */ new Set()) {
1545
1648
  const decl = getDeclarationRoot(path);
1546
1649
  if (decl && !seen.has(decl.node)) {
1547
1650
  seen.add(decl.node);
@@ -1549,20 +1652,23 @@ function getStaticDeclRefs(fnExtra, path, refs = /* @__PURE__ */ new Set(), seen
1549
1652
  if (ids) for (const name in ids) {
1550
1653
  const binding = decl.scope.getBinding(name);
1551
1654
  if (!binding) continue;
1552
- for (const ref of binding.referencePaths) {
1553
- if (isInvokedFunction(ref) || ref.parentPath.type === "Program") continue;
1554
- const exprRoot = getExprRoot(ref);
1555
- const markoRoot = getMarkoRoot(exprRoot);
1556
- if (!markoRoot || canIgnoreRegister(markoRoot, exprRoot)) continue;
1557
- if (isStaticRoot(markoRoot)) {
1558
- if (getStaticDeclRefs(fnExtra, ref, refs, seen) === true) return true;
1559
- } else if (shouldAlwaysRegister(markoRoot)) return true;
1560
- else refs.add(exprRoot.node.extra ??= {});
1561
- }
1655
+ if (addBindingRefs(binding, refs, seen) === true) return true;
1562
1656
  }
1563
1657
  }
1564
1658
  return refs;
1565
1659
  }
1660
+ function addBindingRefs(binding, refs, seen) {
1661
+ for (const ref of binding.referencePaths) {
1662
+ if (isInvokedFunction(ref) || ref.parentPath.type === "Program") continue;
1663
+ const exprRoot = getExprRoot(ref);
1664
+ const markoRoot = getMarkoRoot(exprRoot);
1665
+ if (!markoRoot || canIgnoreRegister(markoRoot, exprRoot)) continue;
1666
+ if (isStaticRoot(markoRoot)) {
1667
+ if (getStaticDeclRefs(ref, refs, seen) === true) return true;
1668
+ } else if (shouldAlwaysRegister(markoRoot)) return true;
1669
+ else refs.add(exprRoot.node.extra ??= {});
1670
+ }
1671
+ }
1566
1672
  function shouldAlwaysRegister(markoRoot) {
1567
1673
  const tag = getTagFromMarkoRoot(markoRoot);
1568
1674
  if (!tag) return false;
@@ -1594,7 +1700,7 @@ function registerFunction(fnExtra, reason) {
1594
1700
  program.node.extra.isInteractive = true;
1595
1701
  fnExtra.registerReason = reason;
1596
1702
  fnExtra.name = generateUid(fnExtra.name);
1597
- fnExtra.registerId = (0, _marko_compiler_babel_utils.getTemplateId)(markoOpts, filename, `${fnExtra.section.id}/${fnExtra.name.slice(1)}`);
1703
+ fnExtra.registerId = fnExtra.exportRegisterId ?? (0, _marko_compiler_babel_utils.getTemplateId)(markoOpts, filename, `${fnExtra.section.id}/${fnExtra.name.slice(1)}`);
1598
1704
  }
1599
1705
  function isMarkoAttribute(path) {
1600
1706
  return path ? path.isMarkoAttribute() : false;
@@ -2231,6 +2337,61 @@ function bindingHasProperty(binding, properties) {
2231
2337
  return false;
2232
2338
  }
2233
2339
  //#endregion
2340
+ //#region src/translator/util/module-registrations.ts
2341
+ /**
2342
+ * Writes the registrations for module scoped functions: the ones this template
2343
+ * exports and registers itself, and the ones it imports from a template that
2344
+ * reserved a register id without registering it.
2345
+ */
2346
+ function writeModuleRegistrations(program) {
2347
+ const { file } = program.hub;
2348
+ const statements = [];
2349
+ const seen = /* @__PURE__ */ new Set();
2350
+ for (const child of program.node.body) {
2351
+ const registeredImportedFns = child.extra?.registeredImportedFns;
2352
+ if (registeredImportedFns) for (const importedFn of registeredImportedFns) {
2353
+ if (seen.has(importedFn.registerId)) continue;
2354
+ seen.add(importedFn.registerId);
2355
+ if (isOutputHTML()) statements.push(buildRegistration(importedFn.local, importedFn.registerId));
2356
+ else (0, _marko_compiler_babel_utils.importDefault)(file, resolveRegisterModule(file, importedFn));
2357
+ }
2358
+ else if (child.type === "ExportNamedDeclaration") addExportRegistrations(child, seen, statements);
2359
+ }
2360
+ program.node.body.push(...statements);
2361
+ }
2362
+ function addExportRegistrations(node, seen, statements) {
2363
+ const { declaration } = node;
2364
+ if (!declaration) return;
2365
+ const add = (local, registerId) => {
2366
+ if (seen.has(registerId)) return;
2367
+ seen.add(registerId);
2368
+ statements.push(buildRegistration(local, registerId));
2369
+ };
2370
+ if (declaration.type === "FunctionDeclaration") {
2371
+ if (isRegisteredFnExtra(declaration.extra)) add(declaration.id.name, declaration.extra.registerId);
2372
+ return;
2373
+ }
2374
+ if (isOutputHTML() || declaration.type !== "VariableDeclaration") return;
2375
+ for (const declarator of declaration.declarations) {
2376
+ const extra = declarator.init?.extra;
2377
+ if (isRegisteredFnExtra(extra) && _marko_compiler.types.isIdentifier(declarator.id)) add(declarator.id.name, extra.registerId);
2378
+ }
2379
+ }
2380
+ function buildRegistration(local, registerId) {
2381
+ return _marko_compiler.types.expressionStatement(isOutputHTML() ? callRuntime("_resume", _marko_compiler.types.identifier(local), _marko_compiler.types.stringLiteral(registerId)) : callRuntime("_resume", _marko_compiler.types.stringLiteral(registerId), _marko_compiler.types.identifier(local)));
2382
+ }
2383
+ function resolveRegisterModule(file, { filename, exportName, registerId }) {
2384
+ const importer = file.opts.filename;
2385
+ return getMarkoOpts().resolveVirtualDependency(importer, {
2386
+ virtualPath: `${relativePath(importer, filename)}.register-${exportName}.js`,
2387
+ code: `import { ${exportName} } from "./${path.default.basename(filename)}";\nimport { _resume } from "${getRuntimePath("dom")}";\n_resume(${JSON.stringify(registerId)}, ${exportName});\n`
2388
+ });
2389
+ }
2390
+ function relativePath(from, to) {
2391
+ const relative = path.default.relative(path.default.dirname(from), to).split(path.default.sep).join("/");
2392
+ return relative.startsWith(".") ? relative : `./${relative}`;
2393
+ }
2394
+ //#endregion
2234
2395
  //#region src/translator/util/get-known-attr-values.ts
2235
2396
  function getKnownAttrValues(tag) {
2236
2397
  const attrs = {};
@@ -3677,6 +3838,7 @@ var dom_default = { translate: {
3677
3838
  if (!setup) program.node.body.unshift(_marko_compiler.types.exportNamedDeclaration(_marko_compiler.types.variableDeclaration("const", [_marko_compiler.types.variableDeclarator(setupIdentifier, _marko_compiler.types.arrowFunctionExpression([], _marko_compiler.types.blockStatement([])))])));
3678
3839
  program.node.body.unshift(_marko_compiler.types.exportNamedDeclaration(_marko_compiler.types.variableDeclaration("const", [_marko_compiler.types.variableDeclarator(templateIdentifier, writes || _marko_compiler.types.stringLiteral(""))])), _marko_compiler.types.exportNamedDeclaration(_marko_compiler.types.variableDeclaration("const", [_marko_compiler.types.variableDeclarator(walksIdentifier, walks || _marko_compiler.types.stringLiteral(""))])));
3679
3840
  if (extraDecls) program.node.body.unshift(_marko_compiler.types.variableDeclaration("const", extraDecls));
3841
+ writeModuleRegistrations(program);
3680
3842
  program.node.body.push(_marko_compiler.types.exportDefaultDeclaration(callRuntime("_template", _marko_compiler.types.stringLiteral(program.hub.file.metadata.marko.id), templateIdentifier, walksIdentifier, setupIdentifier, programInputSignal?.identifier)));
3681
3843
  }
3682
3844
  } };
@@ -3737,6 +3899,7 @@ var html_default = { translate: {
3737
3899
  child.remove();
3738
3900
  } else if (child.isMarkoScriptlet()) if (child.node.target && child.node.target !== "server") child.remove();
3739
3901
  else child.replaceWithMultiple(child.node.body);
3902
+ writeModuleRegistrations(program);
3740
3903
  const contentId = usedSharedUid("content") && getTemplateContentName();
3741
3904
  const contentFn = _marko_compiler.types.arrowFunctionExpression([_marko_compiler.types.identifier("input")], _marko_compiler.types.blockStatement(renderContent));
3742
3905
  const exportDefault = _marko_compiler.types.exportDefaultDeclaration(callRuntime("_template", _marko_compiler.types.stringLiteral(program.hub.file.metadata.marko.id), contentId ? _marko_compiler.types.identifier(contentId) : contentFn, program.node.extra.page || !getMarkoOpts().linkAssets ? _marko_compiler.types.numericLiteral(1) : void 0));
@@ -8096,6 +8259,8 @@ var export_default = {
8096
8259
  const { node } = tag;
8097
8260
  const statements = (0, _marko_compiler_babel_utils.parseStatements)(tag.hub.file, node.rawValue, node.start, node.end);
8098
8261
  if (statements.length > 1) throw tag.hub.buildError(statements[1], "The `<export>` tag takes a single export statement.");
8262
+ const defaultExport = getDefaultExport(statements[0]);
8263
+ if (defaultExport) throw tag.hub.buildError(defaultExport, "A template is already its own default export. Use a named export instead.");
8099
8264
  tag.replaceWith(statements[0]);
8100
8265
  },
8101
8266
  parseOptions: {
@@ -8104,6 +8269,14 @@ var export_default = {
8104
8269
  },
8105
8270
  autocomplete: [{ displayText: "export <value>" }]
8106
8271
  };
8272
+ function getDefaultExport(statement) {
8273
+ if (statement.type === "ExportDefaultDeclaration") return statement;
8274
+ if (statement.type !== "ExportNamedDeclaration") return;
8275
+ for (const specifier of statement.specifiers) {
8276
+ const { exported } = specifier;
8277
+ if (exported && (exported.type === "Identifier" ? exported.name : exported.value) === "default") return specifier;
8278
+ }
8279
+ }
8107
8280
  //#endregion
8108
8281
  //#region src/translator/core/html-comment.ts
8109
8282
  const kNodeBinding$1 = Symbol("comment tag binding");
@@ -8540,9 +8713,25 @@ var server_default = {
8540
8713
  }]
8541
8714
  };
8542
8715
  //#endregion
8716
+ //#region src/translator/util/insertion-context.ts
8717
+ const discardsUnknownChildren = /* @__PURE__ */ new Set([
8718
+ "table",
8719
+ "thead",
8720
+ "tbody",
8721
+ "tfoot",
8722
+ "tr",
8723
+ "colgroup",
8724
+ "select",
8725
+ "optgroup"
8726
+ ]);
8727
+ function discardsWrapperChildren(tagName) {
8728
+ return discardsUnknownChildren.has(tagName);
8729
+ }
8730
+ //#endregion
8543
8731
  //#region src/translator/core/show.ts
8544
8732
  const kStatefulReason = Symbol("<show> stateful reason");
8545
8733
  const kStartBinding = Symbol("<show> range start binding");
8734
+ const kEndBinding = Symbol("<show> range end binding");
8546
8735
  const kStaticDisplay = Symbol("<show> static display");
8547
8736
  const kSingleNodeBody = Symbol("<show> single node body");
8548
8737
  const kDisplayRef = Symbol("<show> hoisted display reference");
@@ -8556,6 +8745,7 @@ var show_default = {
8556
8745
  tagExtra[kSingleNodeBody] = isSingleNodeBody(tag);
8557
8746
  if (displayEval.confident) tagExtra[kStaticDisplay] = !!displayEval.computed;
8558
8747
  if (tagExtra[kStaticDisplay] === true) return;
8748
+ assertLegalHiddenContext(tag);
8559
8749
  const tagSection = getOrCreateSection(tag);
8560
8750
  if (getOnlyChildParentTagName(tag)) getOptimizedOnlyChildNodeBinding(tag, tagSection);
8561
8751
  else tagExtra[kStartBinding] = createBinding("#text", 0, tagSection);
@@ -8568,6 +8758,7 @@ var show_default = {
8568
8758
  const tagExtra = tag.node.extra;
8569
8759
  if (tagExtra[kStaticDisplay] === true) return;
8570
8760
  const tagSection = getSection(tag);
8761
+ if (tagExtra[kStartBinding] && !tagExtra[kSingleNodeBody]) tagExtra[kEndBinding] = createBinding("#text", 0, tagSection);
8571
8762
  const nodeBinding = getOptimizedOnlyChildNodeBinding(tag, tagSection);
8572
8763
  if (tagExtra[kStaticDisplay] === void 0) addSerializeExpr(tagSection, tagExtra, nodeBinding);
8573
8764
  }
@@ -8637,16 +8828,21 @@ var show_default = {
8637
8828
  return;
8638
8829
  }
8639
8830
  const tagSection = getSection(tag);
8831
+ const endBinding = tagExtra[kEndBinding];
8640
8832
  const nodeBinding = getOptimizedOnlyChildNodeBinding(tag, tagSection);
8641
8833
  const startBinding = tagExtra[kStartBinding];
8642
8834
  const display = tagExtra[kStaticDisplay] === false ? _marko_compiler.types.booleanLiteral(false) : tag.node.attributes[0].value;
8835
+ if (endBinding) {
8836
+ visit(tag, 37);
8837
+ enterShallow(tag);
8838
+ }
8643
8839
  if (startBinding) {
8644
8840
  visit(tag, 37);
8645
8841
  enterShallow(tag);
8646
8842
  }
8647
8843
  const signal = getSignal(tagSection, nodeBinding, "show");
8648
8844
  signal.build = () => {
8649
- return callRuntime("_show", getScopeAccessorLiteral(nodeBinding, true), startBinding ? getScopeAccessorLiteral(startBinding, true) : void 0);
8845
+ return callRuntime("_show", getScopeAccessorLiteral(nodeBinding, true), startBinding ? getScopeAccessorLiteral(startBinding, true) : void 0, endBinding ? getScopeAccessorLiteral(endBinding, true) : void 0);
8650
8846
  };
8651
8847
  addValue(tagSection, tagExtra.referencedBindings, signal, display);
8652
8848
  tag.remove();
@@ -8677,6 +8873,10 @@ function assertValidShow(tag) {
8677
8873
  assertHasBody(tag);
8678
8874
  assertHasValueAttribute(tag);
8679
8875
  }
8876
+ function assertLegalHiddenContext(tag) {
8877
+ const parentName = getParentTag(tag)?.node.name;
8878
+ if (_marko_compiler.types.isStringLiteral(parentName) && discardsWrapperChildren(parentName.value)) throw tag.get("name").buildCodeFrameError(`A [\`<${getTagName(tag)}>\` tag](https://markojs.com/docs/reference/core-tag#show) cannot be a direct child of \`<${parentName.value}>\`: hidden content is wrapped in an element that \`<${parentName.value}>\` discards, which would render the content instead of hiding it. Move the \`<${getTagName(tag)}>\` inside the row or option, or use [\`<if>\`](https://markojs.com/docs/reference/core-tag#if).`);
8879
+ }
8680
8880
  function assertHasBody(tag) {
8681
8881
  if (!tag.node.body.body.length) throw tag.get("name").buildCodeFrameError(`The [\`<${getTagName(tag)}>\` tag](https://markojs.com/docs/reference/core-tag#show) requires [body content](https://markojs.com/docs/reference/language#tag-content).`);
8682
8882
  if (tag.node.body.attributeTags) throw tag.get("name").buildCodeFrameError(`The [\`<${getTagName(tag)}>\` tag](https://markojs.com/docs/reference/core-tag#show) does not support [attribute tags](https://markojs.com/docs/reference/language#attribute-tags).`);
@@ -9133,6 +9333,7 @@ var import_declaration_default = {
9133
9333
  (node.extra ??= {}).tagImport = tagImport;
9134
9334
  const tags = importDecl.hub.file.metadata.marko.tags;
9135
9335
  if (!tags.includes(tagImport)) tags.push(tagImport);
9336
+ trackImportedRegisteredFns(importDecl);
9136
9337
  }
9137
9338
  const loadAttrPath = node.attributes?.length ? importDecl.get("attributes").find((p) => (p.node.key.type === "Identifier" ? p.node.key.name : p.node.key.value) === "load") : void 0;
9138
9339
  if (loadAttrPath) {
@@ -9188,6 +9389,19 @@ function getOrCreateHtmlLoadWrapped(readyId, originalIdentifier, filename, trigg
9188
9389
  (0, _marko_compiler_babel_utils.getProgram)().node.body.push(_marko_compiler.types.markoScriptlet([_marko_compiler.types.variableDeclaration("const", [_marko_compiler.types.variableDeclarator(_marko_compiler.types.identifier(wrappedName), callRuntime("withLoadAssets", originalIdentifier, _marko_compiler.types.stringLiteral(readyId), triggers ? _marko_compiler.types.valueToNode(triggers) : void 0))])], true));
9189
9390
  return wrappedName;
9190
9391
  }
9392
+ function trackImportedRegisteredFns(importDecl) {
9393
+ const { node } = importDecl;
9394
+ const childFile = (0, _marko_compiler_babel_utils.loadFileForImport)(importDecl.hub.file, node.source.value);
9395
+ if (!childFile) return;
9396
+ for (const specifier of importDecl.get("specifiers")) {
9397
+ if (!specifier.isImportSpecifier()) continue;
9398
+ const { imported } = specifier.node;
9399
+ const importedName = imported.type === "Identifier" ? imported.name : imported.value;
9400
+ if (importedName === "default") continue;
9401
+ const resolved = resolveRegisteredExport(childFile, importedName);
9402
+ if (resolved) trackImportedFn(importDecl, specifier.node.local.name, resolved);
9403
+ }
9404
+ }
9191
9405
  function getLoadImportConfig(attrValue) {
9192
9406
  const raw = attrValue.node.value;
9193
9407
  if (raw === "render") return { render: true };
@@ -10006,7 +10220,7 @@ var text_default = {
10006
10220
  function buildAggregateError(file, rootMsg, ...paths) {
10007
10221
  const err = /* @__PURE__ */ new SyntaxError();
10008
10222
  const fileName = path.default.relative(_marko_compiler_modules.cwd, file.opts.filename);
10009
- const finalMsg = `${rootMsg}:\n\n${paths.map(([msg, path$12]) => `\x1b[90m${msg} at ${getFileNameWithLoc(fileName, path$12)}:\x1b[0m\n${getFrame(file, path$12)}`).join("\n\n")}`;
10223
+ const finalMsg = `${rootMsg}:\n\n${paths.map(([msg, path$13]) => `\x1b[90m${msg} at ${getFileNameWithLoc(fileName, path$13)}:\x1b[0m\n${getFrame(file, path$13)}`).join("\n\n")}`;
10010
10224
  if (!("MARKO_DEBUG" in process.env)) err.stack = finalMsg;
10011
10225
  Object.defineProperty(err, "message", {
10012
10226
  get() {
@@ -10236,13 +10450,13 @@ function mergeVisitors(visitor5, visitor6) {
10236
10450
  function mergeVisit(visit5, visit6) {
10237
10451
  const enter5 = getVisitorEnter(visit5);
10238
10452
  const enter6 = getVisitorEnter(visit6);
10239
- const enter = (enter5 || enter6) && function enter(path$8, state) {
10240
- return (isTagsAPI() ? enter6 : enter5)?.call(this, path$8, state);
10453
+ const enter = (enter5 || enter6) && function enter(path$9, state) {
10454
+ return (isTagsAPI() ? enter6 : enter5)?.call(this, path$9, state);
10241
10455
  };
10242
10456
  const exit5 = getVisitorExit(visit5);
10243
10457
  const exit6 = getVisitorExit(visit6);
10244
- const exit = (exit5 || exit6) && function exit(path$9, state) {
10245
- return (isTagsAPI() ? exit6 : exit5)?.call(this, path$9, state);
10458
+ const exit = (exit5 || exit6) && function exit(path$10, state) {
10459
+ return (isTagsAPI() ? exit6 : exit5)?.call(this, path$10, state);
10246
10460
  };
10247
10461
  return exit ? enter ? {
10248
10462
  enter,
@@ -10325,17 +10539,17 @@ function sequenceVisit(first, second) {
10325
10539
  if (!first || !second) return first || second;
10326
10540
  const enterFirst = getVisitorEnter(first);
10327
10541
  const enterSecond = getVisitorEnter(second);
10328
- const enter = (enterFirst || enterSecond) && function enter(path$10, state) {
10329
- const { node } = path$10;
10330
- enterFirst?.call(this, path$10, state);
10331
- if (path$10.node === node) enterSecond?.call(this, path$10, state);
10542
+ const enter = (enterFirst || enterSecond) && function enter(path$11, state) {
10543
+ const { node } = path$11;
10544
+ enterFirst?.call(this, path$11, state);
10545
+ if (path$11.node === node) enterSecond?.call(this, path$11, state);
10332
10546
  };
10333
10547
  const exitFirst = getVisitorExit(first);
10334
10548
  const exitSecond = getVisitorExit(second);
10335
- const exit = (exitFirst || exitSecond) && function exit(path$11, state) {
10336
- const { node } = path$11;
10337
- exitFirst?.call(this, path$11, state);
10338
- if (path$11.node === node) exitSecond?.call(this, path$11, state);
10549
+ const exit = (exitFirst || exitSecond) && function exit(path$12, state) {
10550
+ const { node } = path$12;
10551
+ exitFirst?.call(this, path$12, state);
10552
+ if (path$12.node === node) exitSecond?.call(this, path$12, state);
10339
10553
  };
10340
10554
  return exit ? enter ? {
10341
10555
  enter,
@@ -0,0 +1 @@
1
+ export declare function discardsWrapperChildren(tagName: string): boolean;
@@ -0,0 +1,7 @@
1
+ import { types as t } from "@marko/compiler";
2
+ /**
3
+ * Writes the registrations for module scoped functions: the ones this template
4
+ * exports and registers itself, and the ones it imports from a template that
5
+ * reserved a register id without registering it.
6
+ */
7
+ export declare function writeModuleRegistrations(program: t.NodePath<t.Program>): void;
@@ -103,6 +103,7 @@ declare module "@marko/compiler/dist/types" {
103
103
  name?: string;
104
104
  registerId?: string;
105
105
  registerReason?: SerializeReason;
106
+ exportRegisterId?: string;
106
107
  }
107
108
  interface ArrowFunctionExpressionExtra extends FunctionExtra {
108
109
  }
@@ -1,6 +1,33 @@
1
1
  import { types as t } from "@marko/compiler";
2
+ declare module "@marko/compiler/dist/types" {
3
+ interface ProgramExtra {
4
+ registeredExports?: Map<string, ReservedExport>;
5
+ }
6
+ }
7
+ /** The canonical name a reserved register id is exported under, and its id. */
8
+ export interface ReservedExport {
9
+ registerId: string;
10
+ exportName: string;
11
+ }
12
+ /** A reserved export, plus the template that declares it. */
13
+ export interface ResolvedExport extends ReservedExport {
14
+ filename: string;
15
+ }
2
16
  declare const _default: {
3
17
  analyze(this: unknown, fn: t.NodePath<t.Function>): void;
4
18
  };
5
19
  export default _default;
6
20
  export declare function finalizeFunctionRegistry(): void;
21
+ /**
22
+ * Tracks a function imported from another template that reserved a register id.
23
+ * The importing template registers it (under the reserved id) when its own
24
+ * references reach something serialized.
25
+ */
26
+ export declare function trackImportedFn(importDecl: t.NodePath<t.ImportDeclaration>, local: string, resolved: ResolvedExport): void;
27
+ /**
28
+ * Finds the id reserved for an export, following `export ... from` hops. Each
29
+ * template only records the exports it declares, so this resolves on demand:
30
+ * a template in an import cycle is read while it is still analyzing, and would
31
+ * hand out an incomplete map if the ids were pushed ahead of time.
32
+ */
33
+ export declare function resolveRegisteredExport(file: t.BabelFile, exportName: string, seen?: Set<string>): ResolvedExport | undefined;
@@ -1,9 +1,13 @@
1
1
  import { types as t } from "@marko/compiler";
2
2
  import type { LoadTrigger } from "../../html/assets";
3
+ import { type ResolvedExport } from "./function";
3
4
  declare module "@marko/compiler/dist/types" {
4
5
  interface NodeExtra {
5
6
  tagImport?: string;
6
7
  loadImport?: LoadImportConfig;
8
+ registeredImportedFns?: (ResolvedExport & {
9
+ local: string;
10
+ })[];
7
11
  }
8
12
  }
9
13
  export type LoadImportConfig = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "marko",
3
- "version": "6.3.28",
3
+ "version": "6.3.30",
4
4
  "description": "Optimized runtime for Marko templates.",
5
5
  "keywords": [
6
6
  "api",
@@ -48,13 +48,13 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@marko/compiler": "^5.41.11",
51
+ "@marko/compiler": "^5.41.12",
52
52
  "csstype": "^3.2.3",
53
53
  "fastest-levenshtein": "^1.0.16",
54
54
  "magic-string": "^0.30.21"
55
55
  },
56
56
  "devDependencies": {
57
- "@marko/runtime-tags": "npm:marko@6.3.28",
57
+ "@marko/runtime-tags": "npm:marko@6.3.30",
58
58
  "marko": "5.39.30"
59
59
  },
60
60
  "engines": {