defuss-morph 0.2.0 → 0.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.
package/dist/index.cjs CHANGED
@@ -170,7 +170,7 @@ const removeDelegatedEventByKey = (element, eventType, phase) => {
170
170
  if (isEntryEmpty(entry)) byEvent.delete(eventType);
171
171
  };
172
172
 
173
- const FROM_DOM_MARKER = Symbol("defuss-morph.from-dom");
173
+ const FROM_DOM_MARKER = /* @__PURE__ */ Symbol("defuss-morph.from-dom");
174
174
  const COMMENT_TYPE = "#comment";
175
175
  const isCommentVNode = (value) => !!value && typeof value === "object" && value.type === COMMENT_TYPE;
176
176
  const HTML_BOOLEAN_ATTRIBUTES = /* @__PURE__ */ new Set([
@@ -225,9 +225,10 @@ function getMimeType(input, Parser) {
225
225
  }
226
226
  return "text/html";
227
227
  }
228
+ const templateContent = (element) => element.localName === "template" ? element.content : void 0;
228
229
  function domNodeToVNode(node) {
229
230
  if (node.nodeType === 3) {
230
- return node.textContent || "";
231
+ return node.nodeValue || "";
231
232
  }
232
233
  if (node.nodeType === 1) {
233
234
  const element = node;
@@ -237,34 +238,37 @@ function domNodeToVNode(node) {
237
238
  attributes[attr.name] = domAttributeToVNodeValue(attr);
238
239
  }
239
240
  const children = [];
240
- for (let i = 0; i < element.childNodes.length; i++) {
241
- const childVNode = domNodeToVNode(element.childNodes[i]);
242
- children.push(childVNode);
241
+ for (let child = (templateContent(element) ?? element).firstChild; child; child = child.nextSibling) {
242
+ children.push(domNodeToVNode(child));
243
243
  }
244
- return {
245
- type: element.tagName.toLowerCase(),
246
- attributes: { ...attributes, [FROM_DOM_MARKER]: true },
247
- children
248
- };
244
+ attributes[FROM_DOM_MARKER] = true;
245
+ return { type: element.localName, attributes, children };
249
246
  }
250
247
  if (node.nodeType === 8) {
251
248
  return { type: COMMENT_TYPE, value: node.nodeValue ?? "" };
252
249
  }
253
250
  return "";
254
251
  }
252
+ const inertDocuments = /* @__PURE__ */ new WeakMap();
255
253
  const DOCUMENT_START = /^\s*(?:<!--[\s\S]*?-->\s*)*<(?:!doctype|html|head|body)[\s>/]/i;
256
254
  function htmlStringToVNodes(html, Parser) {
257
- const parser = new Parser();
258
- const doc = parser.parseFromString(
259
- DOCUMENT_START.test(html) ? html : `<body>${html}`,
260
- "text/html"
261
- );
255
+ if (DOCUMENT_START.test(html)) {
256
+ return toVNodes(new Parser().parseFromString(html, "text/html").body.childNodes);
257
+ }
258
+ let inert = inertDocuments.get(Parser);
259
+ if (!inert) {
260
+ inert = new Parser().parseFromString("", "text/html");
261
+ inertDocuments.set(Parser, inert);
262
+ }
263
+ const template = inert.createElement("template");
264
+ template.innerHTML = html;
265
+ return toVNodes(template.content.childNodes);
266
+ }
267
+ function toVNodes(nodes) {
262
268
  const vNodes = [];
263
- for (let i = 0; i < doc.body.childNodes.length; i++) {
264
- const vnode = domNodeToVNode(doc.body.childNodes[i]);
265
- if (vnode !== "") {
266
- vNodes.push(vnode);
267
- }
269
+ for (let i = 0; i < nodes.length; i++) {
270
+ const vnode = domNodeToVNode(nodes[i]);
271
+ if (vnode !== "") vNodes.push(vnode);
268
272
  }
269
273
  return vNodes;
270
274
  }
@@ -318,13 +322,18 @@ const observeUnmount = (domNode, onUnmount) => {
318
322
  });
319
323
  observer.observe(parentNode, { childList: true });
320
324
  };
325
+ const unmountObserved = /* @__PURE__ */ new WeakSet();
321
326
  const handleLifecycleEventsForOnMount = (newEl) => {
322
327
  if (typeof newEl?.$onMount === "function") {
323
328
  newEl.$onMount(newEl);
324
329
  newEl.$onMount = null;
325
330
  }
326
- if (typeof newEl?.$onUnmount === "function") {
327
- observeUnmount(newEl, newEl.$onUnmount);
331
+ if (typeof newEl?.$onUnmount === "function" && !unmountObserved.has(newEl)) {
332
+ unmountObserved.add(newEl);
333
+ observeUnmount(newEl, () => {
334
+ unmountObserved.delete(newEl);
335
+ newEl.$onUnmount?.(newEl);
336
+ });
328
337
  }
329
338
  };
330
339
  const getRenderer = (document) => {
@@ -335,6 +344,9 @@ const getRenderer = (document) => {
335
344
  if (Array.isArray(virtualNode)) {
336
345
  return renderer.createChildElements(virtualNode, parentDomElement);
337
346
  }
347
+ if (typeof virtualNode === "string" || typeof virtualNode === "number") {
348
+ return renderer.createTextNode(String(virtualNode), parentDomElement);
349
+ }
338
350
  if (typeof virtualNode !== "undefined") {
339
351
  return renderer.createElement(virtualNode, parentDomElement);
340
352
  }
@@ -347,50 +359,40 @@ const getRenderer = (document) => {
347
359
  parentDomElement?.appendChild(comment);
348
360
  return comment;
349
361
  }
350
- try {
351
- if (typeof virtualNode === "function" && virtualNode.constructor.name === "AsyncFunction") {
362
+ if (typeof virtualNode === "function" && virtualNode.constructor.name === "AsyncFunction") {
363
+ newEl = document.createElement("div");
364
+ } else if (typeof virtualNode === "object" && virtualNode !== null && "type" in virtualNode) {
365
+ const vNode = virtualNode;
366
+ if (typeof vNode.type === "function") {
352
367
  newEl = document.createElement("div");
353
- } else if (typeof virtualNode === "object" && virtualNode !== null && "type" in virtualNode) {
354
- const vNode = virtualNode;
355
- if (typeof vNode.type === "function") {
356
- newEl = document.createElement("div");
357
- newEl.innerText = `FATAL ERROR: ${vNode.type._error}`;
358
- } else if (
359
- // SVG support
360
- typeof vNode.type === "string" && vNode.type.toUpperCase() === "SVG" || parentDomElement && renderer.hasSvgNamespace(
361
- parentDomElement,
362
- typeof vNode.type === "string" ? vNode.type.toUpperCase() : ""
363
- )
364
- ) {
365
- newEl = document.createElementNS(nsMap.svg, vNode.type);
366
- } else {
367
- newEl = document.createElement(vNode.type);
368
- }
369
- if (vNode.attributes) {
370
- renderer.setAttributes(vNode, newEl);
371
- if (vNode.attributes.dangerouslySetInnerHTML) {
372
- newEl.innerHTML = vNode.attributes.dangerouslySetInnerHTML.__html;
373
- }
374
- }
375
- if (vNode.children && !vNode.attributes?.dangerouslySetInnerHTML) {
376
- renderer.createChildElements(vNode.children, newEl);
377
- }
368
+ newEl.innerText = `FATAL ERROR: ${vNode.type._error}`;
369
+ } else if (
370
+ // SVG support
371
+ typeof vNode.type === "string" && vNode.type.toUpperCase() === "SVG" || parentDomElement && renderer.hasSvgNamespace(
372
+ parentDomElement,
373
+ typeof vNode.type === "string" ? vNode.type.toUpperCase() : ""
374
+ )
375
+ ) {
376
+ newEl = document.createElementNS(nsMap.svg, vNode.type);
378
377
  } else {
379
- if (typeof virtualNode === "string" || typeof virtualNode === "number") {
380
- newEl = document.createElement(String(virtualNode));
378
+ newEl = document.createElement(vNode.type);
379
+ }
380
+ if (vNode.attributes) {
381
+ renderer.setAttributes(vNode, newEl);
382
+ if (vNode.attributes.dangerouslySetInnerHTML) {
383
+ newEl.innerHTML = vNode.attributes.dangerouslySetInnerHTML.__html;
381
384
  }
382
385
  }
383
- if (newEl && parentDomElement) {
384
- parentDomElement.appendChild(newEl);
385
- handleLifecycleEventsForOnMount(newEl);
386
+ if (vNode.children && !vNode.attributes?.dangerouslySetInnerHTML) {
387
+ renderer.createChildElements(
388
+ vNode.children,
389
+ templateContent(newEl) ?? newEl
390
+ );
386
391
  }
387
- } catch (e) {
388
- console.error(
389
- "Fatal error! Error happend while rendering the VDOM!",
390
- e,
391
- virtualNode
392
- );
393
- throw e;
392
+ }
393
+ if (newEl && parentDomElement) {
394
+ parentDomElement.appendChild(newEl);
395
+ handleLifecycleEventsForOnMount(newEl);
394
396
  }
395
397
  return newEl;
396
398
  },
@@ -431,20 +433,8 @@ const getRenderer = (document) => {
431
433
  return;
432
434
  }
433
435
  if (name === REF_ATTRIBUTE_NAME && typeof value !== "function") {
434
- const ref = value;
435
- ref.current = domElement;
436
+ value.current = domElement;
436
437
  domElement._defussRef = value;
437
- domElement.$onUnmount = queueCallback(() => {
438
- });
439
- if (domElement.parentNode) {
440
- observeUnmount(domElement, domElement.$onUnmount);
441
- } else {
442
- queueMicrotask(() => {
443
- if (domElement.parentNode) {
444
- observeUnmount(domElement, domElement.$onUnmount);
445
- }
446
- });
447
- }
448
438
  return;
449
439
  }
450
440
  const parsed = parseEventPropName(name);
@@ -455,15 +445,7 @@ const getRenderer = (document) => {
455
445
  return;
456
446
  }
457
447
  if (eventType === "unmount") {
458
- if (domElement.$onUnmount) {
459
- const existingUnmount = domElement.$onUnmount;
460
- domElement.$onUnmount = () => {
461
- existingUnmount();
462
- value();
463
- };
464
- } else {
465
- domElement.$onUnmount = queueCallback(value);
466
- }
448
+ domElement.$onUnmount = queueCallback(value);
467
449
  return;
468
450
  }
469
451
  registerDelegatedEvent(
@@ -537,6 +519,30 @@ const getRenderer = (document) => {
537
519
  return renderer;
538
520
  };
539
521
 
522
+ const longestIncreasingRun = (values) => {
523
+ const n = values.length;
524
+ const marked = new Uint8Array(n);
525
+ const tails = new Int32Array(n);
526
+ const previous = new Int32Array(n);
527
+ let length = 0;
528
+ for (let i = 0; i < n; i++) {
529
+ const value = values[i];
530
+ if (value < 0) continue;
531
+ let low = 0;
532
+ let high = length;
533
+ while (low < high) {
534
+ const mid = low + high >> 1;
535
+ if (values[tails[mid]] < value) low = mid + 1;
536
+ else high = mid;
537
+ }
538
+ previous[i] = low > 0 ? tails[low - 1] : -1;
539
+ tails[low] = i;
540
+ if (low === length) length++;
541
+ }
542
+ for (let i = length > 0 ? tails[length - 1] : -1; i >= 0; i = previous[i]) marked[i] = 1;
543
+ return marked;
544
+ };
545
+
540
546
  const areDomNodesEqual = (oldNode, newNode) => {
541
547
  if (oldNode === newNode) return true;
542
548
  if (oldNode.nodeType !== newNode.nodeType) return false;
@@ -561,7 +567,7 @@ const areDomNodesEqual = (oldNode, newNode) => {
561
567
  function isTextLike(value) {
562
568
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
563
569
  }
564
- function isVNode(value) {
570
+ function isVNode$1(value) {
565
571
  return Boolean(
566
572
  value && typeof value === "object" && "type" in value
567
573
  );
@@ -569,7 +575,7 @@ function isVNode(value) {
569
575
  function toValidChild(child, lenient = false) {
570
576
  if (child == null) return child;
571
577
  if (isTextLike(child)) return child;
572
- if (isVNode(child)) return child;
578
+ if (isVNode$1(child)) return child;
573
579
  if (lenient && child && typeof child === "object" && "attributes" in child)
574
580
  return child;
575
581
  return void 0;
@@ -583,7 +589,7 @@ function normalizeChildren(input, lenient = false) {
583
589
  }
584
590
  const valid = toValidChild(child, lenient);
585
591
  if (typeof valid === "undefined") return;
586
- if (isVNode(valid) && (valid.type === "fragment" || valid.type === "Fragment")) {
592
+ if (isVNode$1(valid) && (valid.type === "fragment" || valid.type === "Fragment")) {
587
593
  const nested = Array.isArray(valid.children) ? valid.children : [];
588
594
  nested.forEach(pushChild);
589
595
  return;
@@ -859,68 +865,130 @@ function morphDiff(targetRoot, patchItems, globals) {
859
865
  }
860
866
  }
861
867
  }
868
+ function moveNode(parent, node, anchor) {
869
+ if (typeof parent.moveBefore === "function") {
870
+ try {
871
+ parent.moveBefore(node, anchor);
872
+ return;
873
+ } catch {
874
+ }
875
+ }
876
+ const doc = node.ownerDocument;
877
+ const active = doc?.activeElement;
878
+ const holdsFocus = !!active && active !== doc.body && node.contains(active);
879
+ let selection;
880
+ if (holdsFocus) {
881
+ try {
882
+ if (active.selectionStart !== null && active.selectionStart !== void 0) {
883
+ selection = [active.selectionStart, active.selectionEnd ?? active.selectionStart, active.selectionDirection ?? "none"];
884
+ }
885
+ } catch {
886
+ selection = void 0;
887
+ }
888
+ }
889
+ parent.insertBefore(node, anchor);
890
+ if (holdsFocus && active.isConnected && doc.activeElement !== active) {
891
+ active.focus({ preventScroll: true });
892
+ if (selection) active.setSelectionRange(...selection);
893
+ }
894
+ }
895
+ function patchByPosition(targetRoot, nextChildren, globals) {
896
+ const live = targetRoot.childNodes;
897
+ if (live.length !== nextChildren.length) return false;
898
+ for (let c = 0; c < nextChildren.length; c++) {
899
+ const node = live[c];
900
+ const child = nextChildren[c];
901
+ if (getVNodeMatchKey(child) !== null || !areNodeAndChildMatching(node, child)) return false;
902
+ if (node.nodeType === 1 && getDomMatchKeys(node).length > 0) return false;
903
+ }
904
+ for (let c = 0; c < nextChildren.length; c++) morphNode(live[c], nextChildren[c], globals);
905
+ return true;
906
+ }
862
907
  function morphDomDirect(parentElement, newVDOM, globals, mode = "replace") {
863
908
  const el = parentElement;
864
909
  const isCustomElement = el.tagName.includes("-");
865
- const targetRoot = el.shadowRoot && !isCustomElement ? el.shadowRoot : parentElement;
910
+ const targetRoot = templateContent(parentElement) ?? (el.shadowRoot && !isCustomElement ? el.shadowRoot : parentElement);
866
911
  const nextChildren = normalizeChildren(newVDOM, mode === "diff");
867
912
  if (mode === "diff") {
868
913
  morphDiff(targetRoot, nextChildren, globals);
869
914
  return;
870
915
  }
916
+ if (patchByPosition(targetRoot, nextChildren, globals)) return;
871
917
  const existing = Array.from(targetRoot.childNodes);
872
918
  const keyedPool = /* @__PURE__ */ new Map();
873
- const nodeKeys = /* @__PURE__ */ new WeakMap();
919
+ const nodeKeys = [];
874
920
  const unkeyedPool = [];
875
- for (const node of existing) {
876
- const keys = getDomMatchKeys(node);
921
+ for (let i = 0; i < existing.length; i++) {
922
+ const keys = getDomMatchKeys(existing[i]);
877
923
  if (keys.length > 0) {
878
- nodeKeys.set(node, keys);
924
+ nodeKeys[i] = keys;
879
925
  let addedToKeyedPool = false;
880
926
  for (const k of keys) {
881
927
  if (!keyedPool.has(k)) {
882
- keyedPool.set(k, node);
928
+ keyedPool.set(k, i);
883
929
  addedToKeyedPool = true;
884
930
  }
885
931
  }
886
932
  if (!addedToKeyedPool) {
887
- unkeyedPool.push(node);
933
+ unkeyedPool.push(i);
888
934
  }
889
935
  } else {
890
- unkeyedPool.push(node);
936
+ unkeyedPool.push(i);
891
937
  }
892
938
  }
893
- const consumeKeyedNode = (node) => {
894
- const keys = nodeKeys.get(node) ?? [];
895
- for (const k of keys) keyedPool.delete(k);
939
+ const consumeKeyedNode = (i) => {
940
+ for (const k of nodeKeys[i] ?? []) {
941
+ if (keyedPool.get(k) === i) keyedPool.delete(k);
942
+ }
896
943
  };
897
944
  const takeUnkeyedMatch = (child) => {
898
- for (let i = 0; i < unkeyedPool.length; i++) {
899
- const candidate = unkeyedPool[i];
900
- if (areNodeAndChildMatching(candidate, child)) {
901
- unkeyedPool.splice(i, 1);
902
- return candidate;
945
+ for (let j = 0; j < unkeyedPool.length; j++) {
946
+ const i = unkeyedPool[j];
947
+ if (areNodeAndChildMatching(existing[i], child)) {
948
+ unkeyedPool.splice(j, 1);
949
+ return i;
903
950
  }
904
951
  }
905
- return void 0;
952
+ return -1;
906
953
  };
907
- let domIndex = 0;
908
- for (const child of nextChildren) {
954
+ const count = nextChildren.length;
955
+ const matched = new Int32Array(count);
956
+ let inOrder = true;
957
+ let previous = -1;
958
+ for (let c = 0; c < count; c++) {
959
+ const child = nextChildren[c];
909
960
  const key = getVNodeMatchKey(child);
910
- let match;
961
+ let i = -1;
911
962
  if (key) {
912
- match = keyedPool.get(key);
913
- if (match) consumeKeyedNode(match);
963
+ const hit = keyedPool.get(key);
964
+ if (hit !== void 0) {
965
+ i = hit;
966
+ consumeKeyedNode(hit);
967
+ }
914
968
  } else {
915
- match = takeUnkeyedMatch(child);
969
+ i = takeUnkeyedMatch(child);
916
970
  }
917
- const anchor = targetRoot.childNodes[domIndex] ?? null;
918
- if (match) {
919
- if (match !== anchor) {
920
- targetRoot.insertBefore(match, anchor);
921
- }
971
+ matched[c] = i;
972
+ if (i >= 0) {
973
+ if (i < previous) inOrder = false;
974
+ previous = i;
975
+ }
976
+ }
977
+ const stays = inOrder ? null : longestIncreasingRun(matched);
978
+ const staying = (c) => matched[c] >= 0 && (stays === null || stays[c] === 1);
979
+ let anchorAt = 0;
980
+ while (anchorAt < count && !staying(anchorAt)) anchorAt++;
981
+ for (let c = 0; c < count; c++) {
982
+ if (anchorAt <= c) {
983
+ anchorAt = c + 1;
984
+ while (anchorAt < count && !staying(anchorAt)) anchorAt++;
985
+ }
986
+ const anchor = anchorAt < count ? existing[matched[anchorAt]] : null;
987
+ const child = nextChildren[c];
988
+ if (matched[c] >= 0) {
989
+ const match = existing[matched[c]];
990
+ if (!staying(c)) moveNode(targetRoot, match, anchor);
922
991
  morphNode(match, child, globals);
923
- domIndex++;
924
992
  continue;
925
993
  }
926
994
  const created = createDomFromChild(child, globals);
@@ -929,12 +997,11 @@ function morphDomDirect(parentElement, newVDOM, globals, mode = "replace") {
929
997
  for (const node of nodes) {
930
998
  targetRoot.insertBefore(node, anchor);
931
999
  handleLifecycleEventsForOnMount(node);
932
- domIndex++;
933
1000
  }
934
1001
  }
935
1002
  const remaining = /* @__PURE__ */ new Set();
936
- for (const node of unkeyedPool) remaining.add(node);
937
- for (const node of keyedPool.values()) remaining.add(node);
1003
+ for (const i of unkeyedPool) remaining.add(existing[i]);
1004
+ for (const i of keyedPool.values()) remaining.add(existing[i]);
938
1005
  for (const node of remaining) {
939
1006
  if (node.parentNode === targetRoot) {
940
1007
  if (node.nodeType === 1) {
@@ -947,7 +1014,10 @@ function morphDomDirect(parentElement, newVDOM, globals, mode = "replace") {
947
1014
  function replaceDomWithVdom(parentElement, newVDOM, globals) {
948
1015
  const resolvedGlobals = resolveGlobals(parentElement, globals);
949
1016
  while (parentElement.firstChild) {
950
- parentElement.removeChild(parentElement.firstChild);
1017
+ const child = parentElement.firstChild;
1018
+ if (child.nodeType === 1)
1019
+ clearDelegatedEventsDeep(child);
1020
+ parentElement.removeChild(child);
951
1021
  }
952
1022
  const renderer = getRenderer(resolvedGlobals.window.document);
953
1023
  const newDom = renderer.createElementOrElements(
@@ -1051,7 +1121,9 @@ const performCrossfade = async (element, updateCallback, duration, easing) => {
1051
1121
  const doc = element.ownerDocument;
1052
1122
  try {
1053
1123
  const rect = element.getBoundingClientRect();
1054
- snapshot.style.cssText = `position:absolute;top:${rect.top}px;left:${rect.left}px;width:${rect.width}px;height:${rect.height}px;opacity:1;transition:opacity ${duration}ms ${easing};z-index:1000;`;
1124
+ const top = rect.top + (doc.defaultView?.scrollY ?? 0);
1125
+ const left = rect.left + (doc.defaultView?.scrollX ?? 0);
1126
+ snapshot.style.cssText = `position:absolute;top:${top}px;left:${left}px;width:${rect.width}px;height:${rect.height}px;opacity:1;transition:opacity ${duration}ms ${easing};z-index:1000;`;
1055
1127
  element.style.opacity = "0";
1056
1128
  element.style.transition = `opacity ${duration}ms ${easing}`;
1057
1129
  doc.body.appendChild(snapshot);
@@ -1127,7 +1199,7 @@ const morph = (el, newContent, options = {}) => {
1127
1199
  const transitionTarget = config.target === "self" ? el : el.parentElement;
1128
1200
  if (!transitionTarget) {
1129
1201
  apply(newContent);
1130
- return;
1202
+ return Promise.resolve();
1131
1203
  }
1132
1204
  const slot2 = { content: newContent };
1133
1205
  inflightTransitions.set(el, slot2);
@@ -1150,31 +1222,180 @@ const morph = (el, newContent, options = {}) => {
1150
1222
  apply(newContent);
1151
1223
  };
1152
1224
 
1225
+ const toChildren$1 = (children) => children === void 0 ? [] : Array.isArray(children) ? children.flat(Infinity) : [children];
1226
+ const isVNode = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && "type" in value;
1227
+ const Fragment = (props) => toChildren$1(props?.children);
1228
+ function jsx(type, props, key) {
1229
+ const { children, ...attributes } = props ?? {};
1230
+ const resolvedKey = key ?? attributes.key;
1231
+ if (resolvedKey !== void 0) attributes.key = resolvedKey;
1232
+ if (typeof type === "function") {
1233
+ const result = type(children === void 0 ? attributes : { ...attributes, children });
1234
+ return resolvedKey !== void 0 && isVNode(result) && result.attributes?.key === void 0 ? { ...result, attributes: { ...result.attributes, key: resolvedKey } } : result;
1235
+ }
1236
+ return { type, attributes, children: toChildren$1(children) };
1237
+ }
1238
+ const jsxs = jsx;
1239
+ const jsxDEV = (type, props, key) => jsx(type, props, key);
1240
+ function h(type, props, ...children) {
1241
+ return jsx(type, children.length === 0 ? props : { ...props, children: children.length === 1 ? children[0] : children });
1242
+ }
1243
+
1244
+ const createRef = (state) => ({
1245
+ current: null,
1246
+ state
1247
+ });
1248
+ const isRef = (value) => typeof value === "object" && value !== null && "current" in value;
1249
+
1250
+ const isElement = (value) => typeof value === "object" && value !== null && value.nodeType === 1;
1251
+ const toStores = (store, caller) => {
1252
+ const stores = Array.isArray(store) ? store : [store];
1253
+ stores.forEach((candidate, index) => {
1254
+ if (typeof candidate?.subscribe !== "function") {
1255
+ throw new TypeError(`${caller}: store${Array.isArray(store) ? ` ${index}` : ""} has no subscribe() method`);
1256
+ }
1257
+ });
1258
+ return stores;
1259
+ };
1260
+ const listen = (stores, update) => {
1261
+ const unsubscribes = stores.map((store) => {
1262
+ const result = store.subscribe(() => update());
1263
+ if (typeof result === "function") return result;
1264
+ return () => result?.unsubscribe?.();
1265
+ });
1266
+ return () => {
1267
+ for (const unsubscribe of unsubscribes) unsubscribe();
1268
+ };
1269
+ };
1270
+ const resolveTarget = (target) => {
1271
+ if (isElement(target)) return () => [target];
1272
+ if (isRef(target)) {
1273
+ if (!isElement(target.current)) {
1274
+ throw new TypeError(
1275
+ "reactive: the ref has no element yet; call reactive() after the element is rendered (in onMount or later)"
1276
+ );
1277
+ }
1278
+ return () => isElement(target.current) ? [target.current] : [];
1279
+ }
1280
+ if (target && typeof target.length === "number") {
1281
+ const elements = Array.from(target).filter(isElement);
1282
+ return () => elements;
1283
+ }
1284
+ throw new TypeError("reactive: the target must be an element, a ref or a list of elements");
1285
+ };
1286
+ function reactive(config, target) {
1287
+ const stores = toStores(config?.store, "reactive");
1288
+ if (typeof config.render !== "function") throw new TypeError("reactive: config.render must be a function");
1289
+ const elements = resolveTarget(target);
1290
+ if (config.signal?.aborted || elements().length === 0) return () => {
1291
+ };
1292
+ const update = () => {
1293
+ for (const el of elements()) morph(el, config.render());
1294
+ };
1295
+ update();
1296
+ const unsubscribe = listen(stores, update);
1297
+ let disposed = false;
1298
+ const dispose = () => {
1299
+ if (disposed) return;
1300
+ disposed = true;
1301
+ unsubscribe();
1302
+ config.signal?.removeEventListener("abort", dispose);
1303
+ config.cleanup?.();
1304
+ };
1305
+ config.signal?.addEventListener("abort", dispose, { once: true });
1306
+ return dispose;
1307
+ }
1308
+ const mounted = /* @__PURE__ */ new WeakMap();
1309
+ const toChildren = (content) => {
1310
+ if (typeof content === "string") {
1311
+ if (!/[<&]/.test(content)) return content === "" ? [] : [content];
1312
+ const Parser = globalThis.DOMParser;
1313
+ if (!Parser) {
1314
+ throw new TypeError("Reactive: rendering an HTML string needs a global DOMParser; return JSX/VNodes, or call reactive()");
1315
+ }
1316
+ return htmlStringToVNodes(content, Parser);
1317
+ }
1318
+ return Array.isArray(content) ? content.flat(Infinity) : [content];
1319
+ };
1320
+ function Reactive(props) {
1321
+ const { store, render, cleanup, tag = "div", onMount, onUnmount, children: _children, ...attributes } = props;
1322
+ const stores = toStores(store, "Reactive");
1323
+ if (typeof render !== "function") throw new TypeError("Reactive: the render prop must be a function");
1324
+ return {
1325
+ type: tag,
1326
+ attributes: {
1327
+ ...attributes,
1328
+ // morph runs onMount in a microtask after inserting the wrapper, and again after every patch of it by a
1329
+ // parent re-render: the subscription moves to this render's stores and render function, and one render here
1330
+ // picks up a store change made between the render that built the wrapper and this callback. VERIFIED:
1331
+ // reactive.test.ts ("picks up a store change made before its mount callback ran", "keeps one subscription")
1332
+ onMount: (el) => {
1333
+ mounted.get(el)?.unsubscribe();
1334
+ mounted.set(el, { unsubscribe: listen(stores, () => morph(el, render())), cleanup });
1335
+ morph(el, render());
1336
+ if (typeof onMount === "function") onMount(el);
1337
+ },
1338
+ onUnmount: (el) => {
1339
+ const entry = mounted.get(el);
1340
+ mounted.delete(el);
1341
+ entry?.unsubscribe();
1342
+ entry?.cleanup?.();
1343
+ if (typeof onUnmount === "function") onUnmount(el);
1344
+ }
1345
+ },
1346
+ children: toChildren(render())
1347
+ };
1348
+ }
1349
+ const installReactive = (df$) => {
1350
+ const fn = df$?.fn;
1351
+ if (typeof fn !== "object" || fn === null) {
1352
+ throw new TypeError("installReactive: expected defuss-query's df$ (a df$ with a df$.fn prototype)");
1353
+ }
1354
+ Object.defineProperty(fn, "reactive", {
1355
+ configurable: true,
1356
+ writable: true,
1357
+ value: function(config) {
1358
+ reactive(config, this);
1359
+ return this;
1360
+ }
1361
+ });
1362
+ return df$;
1363
+ };
1364
+
1153
1365
  exports.CAPTURE_ONLY_EVENTS = CAPTURE_ONLY_EVENTS;
1154
1366
  exports.CLASS_ATTRIBUTE_NAME = CLASS_ATTRIBUTE_NAME;
1155
1367
  exports.COMMENT_TYPE = COMMENT_TYPE;
1156
1368
  exports.DANGEROUSLY_SET_INNER_HTML_ATTRIBUTE = DANGEROUSLY_SET_INNER_HTML_ATTRIBUTE;
1157
1369
  exports.DEFAULT_TRANSITION_CONFIG = DEFAULT_TRANSITION_CONFIG;
1158
1370
  exports.FROM_DOM_MARKER = FROM_DOM_MARKER;
1371
+ exports.Fragment = Fragment;
1159
1372
  exports.REF_ATTRIBUTE_NAME = REF_ATTRIBUTE_NAME;
1373
+ exports.Reactive = Reactive;
1160
1374
  exports.XLINK_ATTRIBUTE_NAME = XLINK_ATTRIBUTE_NAME;
1161
1375
  exports.XMLNS_ATTRIBUTE_NAME = XMLNS_ATTRIBUTE_NAME;
1162
1376
  exports.applyStyles = applyStyles;
1163
1377
  exports.areDomNodesEqual = areDomNodesEqual;
1164
1378
  exports.clearDelegatedEvents = clearDelegatedEvents;
1165
1379
  exports.clearDelegatedEventsDeep = clearDelegatedEventsDeep;
1380
+ exports.createRef = createRef;
1166
1381
  exports.domNodeToVNode = domNodeToVNode;
1167
1382
  exports.getMimeType = getMimeType;
1168
1383
  exports.getRegisteredEventKeys = getRegisteredEventKeys;
1169
1384
  exports.getRegisteredEventTypes = getRegisteredEventTypes;
1170
1385
  exports.getRenderer = getRenderer;
1171
1386
  exports.getTransitionStyles = getTransitionStyles;
1387
+ exports.h = h;
1172
1388
  exports.handleLifecycleEventsForOnMount = handleLifecycleEventsForOnMount;
1173
1389
  exports.htmlStringToVNodes = htmlStringToVNodes;
1390
+ exports.installReactive = installReactive;
1174
1391
  exports.isCommentVNode = isCommentVNode;
1175
1392
  exports.isHTML = isHTML;
1176
1393
  exports.isMarkup = isMarkup;
1394
+ exports.isRef = isRef;
1177
1395
  exports.isSVG = isSVG;
1396
+ exports.jsx = jsx;
1397
+ exports.jsxDEV = jsxDEV;
1398
+ exports.jsxs = jsxs;
1178
1399
  exports.morph = morph;
1179
1400
  exports.nsMap = nsMap;
1180
1401
  exports.observeUnmount = observeUnmount;
@@ -1183,10 +1404,12 @@ exports.parseDOM = parseDOM;
1183
1404
  exports.parseEventPropName = parseEventPropName;
1184
1405
  exports.performTransition = performTransition;
1185
1406
  exports.queueCallback = queueCallback;
1407
+ exports.reactive = reactive;
1186
1408
  exports.registerDelegatedEvent = registerDelegatedEvent;
1187
1409
  exports.removeDelegatedEvent = removeDelegatedEvent;
1188
1410
  exports.removeDelegatedEventByKey = removeDelegatedEventByKey;
1189
1411
  exports.renderMarkup = renderMarkup;
1190
1412
  exports.replaceDomWithVdom = replaceDomWithVdom;
1191
1413
  exports.resolveGlobals = resolveGlobals;
1414
+ exports.templateContent = templateContent;
1192
1415
  exports.updateDomWithVdom = updateDomWithVdom;