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.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.
|
|
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
|
|
241
|
-
|
|
242
|
-
children.push(childVNode);
|
|
241
|
+
for (let child = (templateContent(element) ?? element).firstChild; child; child = child.nextSibling) {
|
|
242
|
+
children.push(domNodeToVNode(child));
|
|
243
243
|
}
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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 <
|
|
264
|
-
const vnode = domNodeToVNode(
|
|
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
|
-
|
|
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
|
-
|
|
351
|
-
|
|
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
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
380
|
-
|
|
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 (
|
|
384
|
-
|
|
385
|
-
|
|
386
|
+
if (vNode.children && !vNode.attributes?.dangerouslySetInnerHTML) {
|
|
387
|
+
renderer.createChildElements(
|
|
388
|
+
vNode.children,
|
|
389
|
+
templateContent(newEl) ?? newEl
|
|
390
|
+
);
|
|
386
391
|
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
919
|
+
const nodeKeys = [];
|
|
874
920
|
const unkeyedPool = [];
|
|
875
|
-
for (
|
|
876
|
-
const keys = getDomMatchKeys(
|
|
921
|
+
for (let i = 0; i < existing.length; i++) {
|
|
922
|
+
const keys = getDomMatchKeys(existing[i]);
|
|
877
923
|
if (keys.length > 0) {
|
|
878
|
-
nodeKeys
|
|
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,
|
|
928
|
+
keyedPool.set(k, i);
|
|
883
929
|
addedToKeyedPool = true;
|
|
884
930
|
}
|
|
885
931
|
}
|
|
886
932
|
if (!addedToKeyedPool) {
|
|
887
|
-
unkeyedPool.push(
|
|
933
|
+
unkeyedPool.push(i);
|
|
888
934
|
}
|
|
889
935
|
} else {
|
|
890
|
-
unkeyedPool.push(
|
|
936
|
+
unkeyedPool.push(i);
|
|
891
937
|
}
|
|
892
938
|
}
|
|
893
|
-
const consumeKeyedNode = (
|
|
894
|
-
const
|
|
895
|
-
|
|
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
|
|
899
|
-
const
|
|
900
|
-
if (areNodeAndChildMatching(
|
|
901
|
-
unkeyedPool.splice(
|
|
902
|
-
return
|
|
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
|
|
952
|
+
return -1;
|
|
906
953
|
};
|
|
907
|
-
|
|
908
|
-
|
|
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
|
|
961
|
+
let i = -1;
|
|
911
962
|
if (key) {
|
|
912
|
-
|
|
913
|
-
if (
|
|
963
|
+
const hit = keyedPool.get(key);
|
|
964
|
+
if (hit !== void 0) {
|
|
965
|
+
i = hit;
|
|
966
|
+
consumeKeyedNode(hit);
|
|
967
|
+
}
|
|
914
968
|
} else {
|
|
915
|
-
|
|
969
|
+
i = takeUnkeyedMatch(child);
|
|
916
970
|
}
|
|
917
|
-
|
|
918
|
-
if (
|
|
919
|
-
if (
|
|
920
|
-
|
|
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
|
|
937
|
-
for (const
|
|
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.
|
|
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
|
-
|
|
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;
|