@getforma/core 1.2.0 → 1.3.0

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.
@@ -86,6 +86,7 @@ var FormaJS = (() => {
86
86
  setStyle: () => setStyle,
87
87
  setText: () => setText,
88
88
  siblings: () => siblings,
89
+ svg: () => svg,
89
90
  template: () => template,
90
91
  templateMany: () => templateMany,
91
92
  toggleClass: () => toggleClass,
@@ -1286,14 +1287,18 @@ var FormaJS = (() => {
1286
1287
  }
1287
1288
  const oldKeyMap = /* @__PURE__ */ new Map();
1288
1289
  for (let i = 0; i < oldLen; i++) {
1289
- oldKeyMap.set(keyFn(oldItems[i]), i);
1290
+ const k = keyFn(oldItems[i]);
1291
+ const bucket = oldKeyMap.get(k);
1292
+ if (bucket) bucket.push(i);
1293
+ else oldKeyMap.set(k, [i]);
1290
1294
  }
1291
1295
  const oldIndices = new Array(newLen);
1292
1296
  const oldUsed = new Uint8Array(oldLen);
1293
1297
  for (let i = 0; i < newLen; i++) {
1294
1298
  const key = keyFn(newItems[i]);
1295
- const oldIdx = oldKeyMap.get(key);
1296
- if (oldIdx !== void 0) {
1299
+ const bucket = oldKeyMap.get(key);
1300
+ if (bucket && bucket.length > 0) {
1301
+ const oldIdx = bucket.shift();
1297
1302
  oldIndices[i] = oldIdx;
1298
1303
  oldUsed[oldIdx] = 1;
1299
1304
  } else {
@@ -2077,6 +2082,17 @@ var FormaJS = (() => {
2077
2082
  var Fragment = /* @__PURE__ */ Symbol.for("forma.fragment");
2078
2083
  var SVG_NS = "http://www.w3.org/2000/svg";
2079
2084
  var XLINK_NS = "http://www.w3.org/1999/xlink";
2085
+ var DUAL_USE_SVG_TAGS = /* @__PURE__ */ new Set(["a", "title", "script", "style", "font"]);
2086
+ var currentNamespace = null;
2087
+ function svg(build) {
2088
+ const prev = currentNamespace;
2089
+ currentNamespace = SVG_NS;
2090
+ try {
2091
+ return build();
2092
+ } finally {
2093
+ currentNamespace = prev;
2094
+ }
2095
+ }
2080
2096
  var SVG_TAGS = /* @__PURE__ */ new Set([
2081
2097
  "svg",
2082
2098
  "path",
@@ -2610,7 +2626,12 @@ var FormaJS = (() => {
2610
2626
  return { type: "element", tag: tagName, props: props ?? null, children: children2 };
2611
2627
  }
2612
2628
  let el;
2613
- if (ELEMENT_PROTOS && ELEMENT_PROTOS[tagName]) {
2629
+ const svgCtx = currentNamespace === SVG_NS;
2630
+ if (svgCtx && (SVG_TAGS.has(tagName) || DUAL_USE_SVG_TAGS.has(tagName))) {
2631
+ el = document.createElementNS(SVG_NS, tagName);
2632
+ } else if (!svgCtx && DUAL_USE_SVG_TAGS.has(tagName)) {
2633
+ el = getProto(tagName).cloneNode(false);
2634
+ } else if (ELEMENT_PROTOS && ELEMENT_PROTOS[tagName]) {
2614
2635
  el = ELEMENT_PROTOS[tagName].cloneNode(false);
2615
2636
  } else if (SVG_TAGS.has(tagName)) {
2616
2637
  el = document.createElementNS(SVG_NS, tagName);
@@ -2876,8 +2897,14 @@ var FormaJS = (() => {
2876
2897
  const isPending = pending() > 0;
2877
2898
  const newNode = isPending ? fallbackNode ??= fallback() : resolvedNode;
2878
2899
  if (newNode === currentNode) return;
2879
- if (currentNode && currentNode.parentNode === parent2) {
2880
- parent2.removeChild(currentNode);
2900
+ if (currentNode) {
2901
+ if (currentNode.parentNode === parent2) {
2902
+ parent2.removeChild(currentNode);
2903
+ } else if (currentNode.nodeType === 11) {
2904
+ while (startMarker.nextSibling && startMarker.nextSibling !== endMarker) {
2905
+ currentNode.appendChild(startMarker.nextSibling);
2906
+ }
2907
+ }
2881
2908
  }
2882
2909
  if (newNode) {
2883
2910
  parent2.insertBefore(newNode, endMarker);
@@ -2933,6 +2960,10 @@ var FormaJS = (() => {
2933
2960
  const sharedProps = scriptBlock ? JSON.parse(scriptBlock.textContent) : null;
2934
2961
  const islands = document.querySelectorAll("[data-forma-island]");
2935
2962
  for (const root of islands) {
2963
+ const status = root.getAttribute("data-forma-status");
2964
+ if (status === "active" || status === "hydrating" || status === "disposed" || status === "error") continue;
2965
+ if (root.__formaScheduled) continue;
2966
+ delete root.__formaDisposed;
2936
2967
  const id = parseInt(root.getAttribute("data-forma-island"), 10);
2937
2968
  const componentName = root.getAttribute("data-forma-component");
2938
2969
  const hydrateFn = registry[componentName];
@@ -2943,30 +2974,39 @@ var FormaJS = (() => {
2943
2974
  }
2944
2975
  const trigger2 = root.getAttribute("data-forma-hydrate") || "load";
2945
2976
  if (trigger2 === "visible") {
2977
+ root.__formaScheduled = true;
2946
2978
  const observer = new IntersectionObserver(
2947
2979
  (entries) => {
2948
2980
  for (const entry of entries) {
2949
2981
  if (!entry.isIntersecting) continue;
2950
2982
  observer.disconnect();
2983
+ delete root.__formaObserver;
2951
2984
  hydrateIslandRoot(root, id, componentName, hydrateFn, sharedProps);
2952
2985
  }
2953
2986
  },
2954
2987
  { rootMargin: "200px" }
2955
2988
  );
2989
+ root.__formaObserver = observer;
2956
2990
  observer.observe(root);
2957
2991
  } else if (trigger2 === "idle") {
2992
+ root.__formaScheduled = true;
2958
2993
  const hydrate = () => hydrateIslandRoot(root, id, componentName, hydrateFn, sharedProps);
2959
2994
  if (typeof requestIdleCallback === "function") {
2960
- requestIdleCallback(hydrate);
2995
+ const handle = requestIdleCallback(hydrate);
2996
+ root.__formaIdleCancel = () => cancelIdleCallback(handle);
2961
2997
  } else {
2962
- setTimeout(hydrate, 200);
2998
+ const handle = setTimeout(hydrate, 200);
2999
+ root.__formaIdleCancel = () => clearTimeout(handle);
2963
3000
  }
2964
3001
  } else if (trigger2 === "interaction") {
3002
+ root.__formaScheduled = true;
2965
3003
  const hydrate = () => {
2966
3004
  root.removeEventListener("pointerdown", hydrate, true);
2967
3005
  root.removeEventListener("focusin", hydrate, true);
3006
+ delete root.__formaInteractionHandler;
2968
3007
  hydrateIslandRoot(root, id, componentName, hydrateFn, sharedProps);
2969
3008
  };
3009
+ root.__formaInteractionHandler = hydrate;
2970
3010
  root.addEventListener("pointerdown", hydrate, { capture: true, once: true });
2971
3011
  root.addEventListener("focusin", hydrate, { capture: true, once: true });
2972
3012
  } else {
@@ -2975,6 +3015,24 @@ var FormaJS = (() => {
2975
3015
  }
2976
3016
  }
2977
3017
  function deactivateIsland(el) {
3018
+ const observer = el.__formaObserver;
3019
+ if (observer) {
3020
+ observer.disconnect();
3021
+ delete el.__formaObserver;
3022
+ }
3023
+ const interactionHandler = el.__formaInteractionHandler;
3024
+ if (interactionHandler) {
3025
+ el.removeEventListener("pointerdown", interactionHandler, true);
3026
+ el.removeEventListener("focusin", interactionHandler, true);
3027
+ delete el.__formaInteractionHandler;
3028
+ }
3029
+ const idleCancel = el.__formaIdleCancel;
3030
+ if (idleCancel) {
3031
+ idleCancel();
3032
+ delete el.__formaIdleCancel;
3033
+ }
3034
+ delete el.__formaScheduled;
3035
+ el.__formaDisposed = true;
2978
3036
  const dispose = el.__formaDispose;
2979
3037
  if (typeof dispose === "function") {
2980
3038
  dispose();
@@ -2983,13 +3041,15 @@ var FormaJS = (() => {
2983
3041
  }
2984
3042
  }
2985
3043
  function deactivateAllIslands(root = document) {
2986
- const islands = root.querySelectorAll('[data-forma-status="active"]');
3044
+ const islands = root.querySelectorAll("[data-forma-island]");
2987
3045
  for (const island of islands) {
2988
3046
  deactivateIsland(island);
2989
3047
  }
2990
3048
  }
2991
3049
  function hydrateIslandRoot(root, id, componentName, hydrateFn, sharedProps) {
3050
+ if (root.__formaDisposed) return;
2992
3051
  try {
3052
+ delete root.__formaScheduled;
2993
3053
  const props = loadIslandProps(root, id, sharedProps);
2994
3054
  root.setAttribute("data-forma-status", "hydrating");
2995
3055
  let activeRoot = root;