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