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/CHANGELOG.md +34 -0
- package/README.md +281 -218
- package/dist/all.js +454 -200
- package/dist/all.js.map +15 -11
- package/dist/all.min.js +1 -1
- package/dist/all.min.js.map +1 -1
- package/dist/index.cjs +344 -121
- package/dist/index.d.ts +308 -26
- package/dist/index.mjs +334 -122
- package/dist/stats.json +9 -9
- package/package.json +46 -13
- package/skills/defuss-morph/SKILL.md +101 -0
- package/skills/defuss-morph/references/api.md +86 -0
- package/skills/defuss-morph/references/integrations.md +116 -0
- package/skills/defuss-morph/references/patterns.md +190 -0
- package/skills/defuss-morph/references/pitfalls.md +29 -0
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.
|
|
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
|
|
239
|
-
|
|
240
|
-
children.push(childVNode);
|
|
239
|
+
for (let child = (templateContent(element) ?? element).firstChild; child; child = child.nextSibling) {
|
|
240
|
+
children.push(domNodeToVNode(child));
|
|
241
241
|
}
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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 <
|
|
262
|
-
const vnode = domNodeToVNode(
|
|
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
|
-
|
|
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
|
-
|
|
349
|
-
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
|
|
378
|
-
|
|
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 (
|
|
382
|
-
|
|
383
|
-
|
|
384
|
+
if (vNode.children && !vNode.attributes?.dangerouslySetInnerHTML) {
|
|
385
|
+
renderer.createChildElements(
|
|
386
|
+
vNode.children,
|
|
387
|
+
templateContent(newEl) ?? newEl
|
|
388
|
+
);
|
|
384
389
|
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
917
|
+
const nodeKeys = [];
|
|
872
918
|
const unkeyedPool = [];
|
|
873
|
-
for (
|
|
874
|
-
const keys = getDomMatchKeys(
|
|
919
|
+
for (let i = 0; i < existing.length; i++) {
|
|
920
|
+
const keys = getDomMatchKeys(existing[i]);
|
|
875
921
|
if (keys.length > 0) {
|
|
876
|
-
nodeKeys
|
|
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,
|
|
926
|
+
keyedPool.set(k, i);
|
|
881
927
|
addedToKeyedPool = true;
|
|
882
928
|
}
|
|
883
929
|
}
|
|
884
930
|
if (!addedToKeyedPool) {
|
|
885
|
-
unkeyedPool.push(
|
|
931
|
+
unkeyedPool.push(i);
|
|
886
932
|
}
|
|
887
933
|
} else {
|
|
888
|
-
unkeyedPool.push(
|
|
934
|
+
unkeyedPool.push(i);
|
|
889
935
|
}
|
|
890
936
|
}
|
|
891
|
-
const consumeKeyedNode = (
|
|
892
|
-
const
|
|
893
|
-
|
|
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
|
|
897
|
-
const
|
|
898
|
-
if (areNodeAndChildMatching(
|
|
899
|
-
unkeyedPool.splice(
|
|
900
|
-
return
|
|
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
|
|
950
|
+
return -1;
|
|
904
951
|
};
|
|
905
|
-
|
|
906
|
-
|
|
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
|
|
959
|
+
let i = -1;
|
|
909
960
|
if (key) {
|
|
910
|
-
|
|
911
|
-
if (
|
|
961
|
+
const hit = keyedPool.get(key);
|
|
962
|
+
if (hit !== void 0) {
|
|
963
|
+
i = hit;
|
|
964
|
+
consumeKeyedNode(hit);
|
|
965
|
+
}
|
|
912
966
|
} else {
|
|
913
|
-
|
|
967
|
+
i = takeUnkeyedMatch(child);
|
|
914
968
|
}
|
|
915
|
-
|
|
916
|
-
if (
|
|
917
|
-
if (
|
|
918
|
-
|
|
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
|
|
935
|
-
for (const
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"files": {
|
|
5
5
|
"dist/index.mjs": {
|
|
6
|
-
"bytes":
|
|
7
|
-
"gzip":
|
|
6
|
+
"bytes": 48836,
|
|
7
|
+
"gzip": 12403
|
|
8
8
|
},
|
|
9
9
|
"dist/index.cjs": {
|
|
10
|
-
"bytes":
|
|
11
|
-
"gzip":
|
|
10
|
+
"bytes": 50151,
|
|
11
|
+
"gzip": 12591
|
|
12
12
|
},
|
|
13
13
|
"dist/all.js": {
|
|
14
|
-
"bytes":
|
|
15
|
-
"gzip":
|
|
14
|
+
"bytes": 50501,
|
|
15
|
+
"gzip": 12573
|
|
16
16
|
},
|
|
17
17
|
"dist/all.min.js": {
|
|
18
|
-
"bytes":
|
|
19
|
-
"gzip":
|
|
18
|
+
"bytes": 23740,
|
|
19
|
+
"gzip": 8699
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
}
|