defuss-morph 0.1.1 → 0.2.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/README.md +12 -6
- package/dist/all.js +209 -253
- package/dist/all.js.map +7 -7
- package/dist/all.min.js +1 -1
- package/dist/all.min.js.map +1 -1
- package/dist/index.cjs +199 -249
- package/dist/index.d.ts +32 -4
- package/dist/index.mjs +197 -250
- package/dist/stats.json +9 -9
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -11,8 +11,7 @@ const CAPTURE_ONLY_EVENTS = /* @__PURE__ */ new Set([
|
|
|
11
11
|
// Note: focusin/focusout DO bubble, so they're not included here
|
|
12
12
|
]);
|
|
13
13
|
const elementHandlerMap = /* @__PURE__ */ new WeakMap();
|
|
14
|
-
const
|
|
15
|
-
const captureDispatched = /* @__PURE__ */ new WeakMap();
|
|
14
|
+
const slotListeners = /* @__PURE__ */ new WeakMap();
|
|
16
15
|
const activeDispatches = /* @__PURE__ */ new WeakMap();
|
|
17
16
|
const parseEventPropName = (propName) => {
|
|
18
17
|
if (!propName.startsWith("on")) return null;
|
|
@@ -31,147 +30,64 @@ const getOrCreateElementHandlers = (el) => {
|
|
|
31
30
|
elementHandlerMap.set(el, created);
|
|
32
31
|
return created;
|
|
33
32
|
};
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
37
|
-
const
|
|
38
|
-
let
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
33
|
+
const runSlot = (target, eventType, phase, event) => {
|
|
34
|
+
const handler = elementHandlerMap.get(target)?.get(eventType)?.[phase];
|
|
35
|
+
if (!handler) return;
|
|
36
|
+
const dispatchKey = `${eventType}:${phase}`;
|
|
37
|
+
let active = activeDispatches.get(target);
|
|
38
|
+
if (active?.has(dispatchKey)) return;
|
|
39
|
+
if (!active) {
|
|
40
|
+
active = /* @__PURE__ */ new Set();
|
|
41
|
+
activeDispatches.set(target, active);
|
|
42
|
+
}
|
|
43
|
+
active.add(dispatchKey);
|
|
44
|
+
try {
|
|
45
|
+
handler.call(target, event);
|
|
46
|
+
} finally {
|
|
47
|
+
active.delete(dispatchKey);
|
|
47
48
|
}
|
|
48
|
-
const doc = event.target?.ownerDocument;
|
|
49
|
-
if (doc && path[path.length - 1] !== doc) path.push(doc);
|
|
50
|
-
const win = doc?.defaultView;
|
|
51
|
-
if (win && path[path.length - 1] !== win) path.push(win);
|
|
52
|
-
return path;
|
|
53
49
|
};
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const entry = handlersByEvent.get(eventType);
|
|
65
|
-
if (!entry) continue;
|
|
66
|
-
let targets = dispatched.get(event);
|
|
67
|
-
if (targets?.has(target)) continue;
|
|
68
|
-
if (!targets) {
|
|
69
|
-
targets = /* @__PURE__ */ new WeakSet();
|
|
70
|
-
dispatched.set(event, targets);
|
|
71
|
-
}
|
|
72
|
-
targets.add(target);
|
|
73
|
-
const dispatchKey = `${eventType}:${phase}`;
|
|
74
|
-
let activeSet = activeDispatches.get(target);
|
|
75
|
-
if (activeSet?.has(dispatchKey)) continue;
|
|
76
|
-
if (!activeSet) {
|
|
77
|
-
activeSet = /* @__PURE__ */ new Set();
|
|
78
|
-
activeDispatches.set(target, activeSet);
|
|
79
|
-
}
|
|
80
|
-
activeSet.add(dispatchKey);
|
|
81
|
-
try {
|
|
82
|
-
if (phase === "capture") {
|
|
83
|
-
if (entry.capture) {
|
|
84
|
-
entry.capture.call(target, event);
|
|
85
|
-
if (event.cancelBubble)
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (entry.captureSet) {
|
|
89
|
-
for (const handler of entry.captureSet) {
|
|
90
|
-
handler.call(target, event);
|
|
91
|
-
if (event.cancelBubble)
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
} else {
|
|
96
|
-
if (entry.bubble) {
|
|
97
|
-
entry.bubble.call(target, event);
|
|
98
|
-
if (event.cancelBubble)
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (entry.bubbleSet) {
|
|
102
|
-
for (const handler of entry.bubbleSet) {
|
|
103
|
-
handler.call(target, event);
|
|
104
|
-
if (event.cancelBubble)
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
} finally {
|
|
110
|
-
activeSet.delete(dispatchKey);
|
|
111
|
-
}
|
|
50
|
+
const setSlot = (target, eventType, phase, entry, handler) => {
|
|
51
|
+
entry[phase] = handler;
|
|
52
|
+
const key = `${eventType}:${phase}`;
|
|
53
|
+
const capture = phase === "capture";
|
|
54
|
+
let byKey = slotListeners.get(target);
|
|
55
|
+
const installed = byKey?.get(key);
|
|
56
|
+
if (handler && !installed) {
|
|
57
|
+
if (!byKey) {
|
|
58
|
+
byKey = /* @__PURE__ */ new Map();
|
|
59
|
+
slotListeners.set(target, byKey);
|
|
112
60
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const captureKey = `${eventType}:capture`;
|
|
120
|
-
if (!installed.has(captureKey)) {
|
|
121
|
-
root.addEventListener(
|
|
122
|
-
eventType,
|
|
123
|
-
createPhaseHandler(eventType, "capture"),
|
|
124
|
-
true
|
|
125
|
-
);
|
|
126
|
-
installed.add(captureKey);
|
|
127
|
-
}
|
|
128
|
-
const bubbleKey = `${eventType}:bubble`;
|
|
129
|
-
if (!installed.has(bubbleKey)) {
|
|
130
|
-
root.addEventListener(
|
|
131
|
-
eventType,
|
|
132
|
-
createPhaseHandler(eventType, "bubble"),
|
|
133
|
-
false
|
|
134
|
-
);
|
|
135
|
-
installed.add(bubbleKey);
|
|
61
|
+
const trampoline = (event) => runSlot(target, eventType, phase, event);
|
|
62
|
+
byKey.set(key, trampoline);
|
|
63
|
+
target.addEventListener(eventType, trampoline, capture);
|
|
64
|
+
} else if (!handler && installed) {
|
|
65
|
+
byKey.delete(key);
|
|
66
|
+
target.removeEventListener(eventType, installed, capture);
|
|
136
67
|
}
|
|
137
68
|
};
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return root;
|
|
145
|
-
}
|
|
146
|
-
return null;
|
|
69
|
+
const clearPhase = (target, eventType, entry, phase) => {
|
|
70
|
+
setSlot(target, eventType, phase, entry, void 0);
|
|
71
|
+
const setKey = phase === "capture" ? "captureSet" : "bubbleSet";
|
|
72
|
+
for (const handler of entry[setKey] ?? [])
|
|
73
|
+
target.removeEventListener(eventType, handler, phase === "capture");
|
|
74
|
+
entry[setKey] = void 0;
|
|
147
75
|
};
|
|
148
76
|
const registerDelegatedEvent = (element, eventType, handler, options = {}) => {
|
|
149
|
-
const root = getEventRoot(element);
|
|
150
77
|
const capture = options.capture || CAPTURE_ONLY_EVENTS.has(eventType);
|
|
151
|
-
|
|
152
|
-
ensureRootListener(root, eventType);
|
|
153
|
-
} else if (element.ownerDocument) {
|
|
154
|
-
ensureRootListener(element.ownerDocument, eventType);
|
|
155
|
-
} else {
|
|
156
|
-
element.addEventListener(eventType, handler, capture);
|
|
157
|
-
}
|
|
78
|
+
const phase = capture ? "capture" : "bubble";
|
|
158
79
|
const byEvent = getOrCreateElementHandlers(element);
|
|
159
80
|
const entry = byEvent.get(eventType) ?? {};
|
|
160
81
|
byEvent.set(eventType, entry);
|
|
161
82
|
if (options.multi) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
entry.bubbleSet.add(handler);
|
|
83
|
+
const setKey = capture ? "captureSet" : "bubbleSet";
|
|
84
|
+
const set = entry[setKey] ??= /* @__PURE__ */ new Set();
|
|
85
|
+
if (!set.has(handler)) {
|
|
86
|
+
set.add(handler);
|
|
87
|
+
element.addEventListener(eventType, handler, capture);
|
|
168
88
|
}
|
|
169
89
|
} else {
|
|
170
|
-
|
|
171
|
-
entry.capture = handler;
|
|
172
|
-
} else {
|
|
173
|
-
entry.bubble = handler;
|
|
174
|
-
}
|
|
90
|
+
setSlot(element, eventType, phase, entry, handler);
|
|
175
91
|
}
|
|
176
92
|
};
|
|
177
93
|
const isEntryEmpty = (entry) => !entry.capture && !entry.bubble && (!entry.captureSet || entry.captureSet.size === 0) && (!entry.bubbleSet || entry.bubbleSet.size === 0);
|
|
@@ -181,34 +97,38 @@ const removeDelegatedEvent = (target, eventType, handler, _options = {}) => {
|
|
|
181
97
|
const entry = byEvent.get(eventType);
|
|
182
98
|
if (!entry) return;
|
|
183
99
|
if (handler) {
|
|
184
|
-
|
|
185
|
-
entry.captureSet.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
entry
|
|
189
|
-
|
|
190
|
-
if (entry.capture === handler) {
|
|
191
|
-
entry.capture = void 0;
|
|
192
|
-
}
|
|
193
|
-
if (entry.bubble === handler) {
|
|
194
|
-
entry.bubble = void 0;
|
|
100
|
+
for (const phase of ["capture", "bubble"]) {
|
|
101
|
+
const set = phase === "capture" ? entry.captureSet : entry.bubbleSet;
|
|
102
|
+
if (set?.delete(handler))
|
|
103
|
+
target.removeEventListener(eventType, handler, phase === "capture");
|
|
104
|
+
if (entry[phase] === handler)
|
|
105
|
+
setSlot(target, eventType, phase, entry, void 0);
|
|
195
106
|
}
|
|
196
|
-
target.removeEventListener(eventType, handler, true);
|
|
197
|
-
target.removeEventListener(eventType, handler, false);
|
|
198
107
|
} else {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
entry.captureSet = void 0;
|
|
202
|
-
entry.bubbleSet = void 0;
|
|
108
|
+
clearPhase(target, eventType, entry, "capture");
|
|
109
|
+
clearPhase(target, eventType, entry, "bubble");
|
|
203
110
|
}
|
|
204
111
|
if (isEntryEmpty(entry)) {
|
|
205
112
|
byEvent.delete(eventType);
|
|
206
113
|
}
|
|
207
114
|
};
|
|
115
|
+
const clearHooks = /* @__PURE__ */ new Set();
|
|
116
|
+
const onClearDelegatedEvents = (hook) => {
|
|
117
|
+
clearHooks.add(hook);
|
|
118
|
+
return () => {
|
|
119
|
+
clearHooks.delete(hook);
|
|
120
|
+
};
|
|
121
|
+
};
|
|
208
122
|
const clearDelegatedEvents = (target) => {
|
|
209
123
|
const byEvent = elementHandlerMap.get(target);
|
|
210
|
-
if (
|
|
211
|
-
|
|
124
|
+
if (byEvent) {
|
|
125
|
+
for (const [eventType, entry] of byEvent) {
|
|
126
|
+
clearPhase(target, eventType, entry, "capture");
|
|
127
|
+
clearPhase(target, eventType, entry, "bubble");
|
|
128
|
+
}
|
|
129
|
+
byEvent.clear();
|
|
130
|
+
}
|
|
131
|
+
for (const hook of clearHooks) hook(target);
|
|
212
132
|
};
|
|
213
133
|
const clearDelegatedEventsDeep = (root) => {
|
|
214
134
|
clearDelegatedEvents(root);
|
|
@@ -246,16 +166,109 @@ const removeDelegatedEventByKey = (element, eventType, phase) => {
|
|
|
246
166
|
if (!byEvent) return;
|
|
247
167
|
const entry = byEvent.get(eventType);
|
|
248
168
|
if (!entry) return;
|
|
249
|
-
|
|
250
|
-
entry.capture = void 0;
|
|
251
|
-
entry.captureSet = void 0;
|
|
252
|
-
} else {
|
|
253
|
-
entry.bubble = void 0;
|
|
254
|
-
entry.bubbleSet = void 0;
|
|
255
|
-
}
|
|
169
|
+
clearPhase(element, eventType, entry, phase);
|
|
256
170
|
if (isEntryEmpty(entry)) byEvent.delete(eventType);
|
|
257
171
|
};
|
|
258
172
|
|
|
173
|
+
const FROM_DOM_MARKER = Symbol("defuss-morph.from-dom");
|
|
174
|
+
const COMMENT_TYPE = "#comment";
|
|
175
|
+
const isCommentVNode = (value) => !!value && typeof value === "object" && value.type === COMMENT_TYPE;
|
|
176
|
+
const HTML_BOOLEAN_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
177
|
+
"allowfullscreen",
|
|
178
|
+
"async",
|
|
179
|
+
"autofocus",
|
|
180
|
+
"autoplay",
|
|
181
|
+
"checked",
|
|
182
|
+
"controls",
|
|
183
|
+
"default",
|
|
184
|
+
"defer",
|
|
185
|
+
"disabled",
|
|
186
|
+
"formnovalidate",
|
|
187
|
+
"hidden",
|
|
188
|
+
"inert",
|
|
189
|
+
"ismap",
|
|
190
|
+
"itemscope",
|
|
191
|
+
"loop",
|
|
192
|
+
"multiple",
|
|
193
|
+
"muted",
|
|
194
|
+
"nomodule",
|
|
195
|
+
"novalidate",
|
|
196
|
+
"open",
|
|
197
|
+
"playsinline",
|
|
198
|
+
"readonly",
|
|
199
|
+
"required",
|
|
200
|
+
"reversed",
|
|
201
|
+
"selected"
|
|
202
|
+
]);
|
|
203
|
+
const domAttributeToVNodeValue = (attr) => HTML_BOOLEAN_ATTRIBUTES.has(attr.name.toLowerCase()) ? true : attr.value;
|
|
204
|
+
function parseDOM(input, type, Parser) {
|
|
205
|
+
return new Parser().parseFromString(input, type);
|
|
206
|
+
}
|
|
207
|
+
function isSVG(input, Parser) {
|
|
208
|
+
const doc = parseDOM(input, "image/svg+xml", Parser);
|
|
209
|
+
if (!doc.documentElement) return false;
|
|
210
|
+
return doc.documentElement.nodeName.toLowerCase() === "svg";
|
|
211
|
+
}
|
|
212
|
+
function isHTML(input, Parser) {
|
|
213
|
+
const doc = parseDOM(input, "text/html", Parser);
|
|
214
|
+
return doc.documentElement.querySelectorAll("*").length > 2;
|
|
215
|
+
}
|
|
216
|
+
const isMarkup = (input, Parser) => input.indexOf("<") > -1 && input.indexOf(">") > -1 && (isHTML(input, Parser) || isSVG(input, Parser));
|
|
217
|
+
function renderMarkup(markup, Parser, doc) {
|
|
218
|
+
const parsed = doc ? doc : parseDOM(markup, getMimeType(markup, Parser), Parser);
|
|
219
|
+
if (parsed.body) return Array.from(parsed.body.childNodes);
|
|
220
|
+
return parsed.documentElement ? [parsed.documentElement] : [];
|
|
221
|
+
}
|
|
222
|
+
function getMimeType(input, Parser) {
|
|
223
|
+
if (isSVG(input, Parser)) {
|
|
224
|
+
return "image/svg+xml";
|
|
225
|
+
}
|
|
226
|
+
return "text/html";
|
|
227
|
+
}
|
|
228
|
+
function domNodeToVNode(node) {
|
|
229
|
+
if (node.nodeType === 3) {
|
|
230
|
+
return node.textContent || "";
|
|
231
|
+
}
|
|
232
|
+
if (node.nodeType === 1) {
|
|
233
|
+
const element = node;
|
|
234
|
+
const attributes = {};
|
|
235
|
+
for (let i = 0; i < element.attributes.length; i++) {
|
|
236
|
+
const attr = element.attributes[i];
|
|
237
|
+
attributes[attr.name] = domAttributeToVNodeValue(attr);
|
|
238
|
+
}
|
|
239
|
+
const children = [];
|
|
240
|
+
for (let i = 0; i < element.childNodes.length; i++) {
|
|
241
|
+
const childVNode = domNodeToVNode(element.childNodes[i]);
|
|
242
|
+
children.push(childVNode);
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
type: element.tagName.toLowerCase(),
|
|
246
|
+
attributes: { ...attributes, [FROM_DOM_MARKER]: true },
|
|
247
|
+
children
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
if (node.nodeType === 8) {
|
|
251
|
+
return { type: COMMENT_TYPE, value: node.nodeValue ?? "" };
|
|
252
|
+
}
|
|
253
|
+
return "";
|
|
254
|
+
}
|
|
255
|
+
const DOCUMENT_START = /^\s*(?:<!--[\s\S]*?-->\s*)*<(?:!doctype|html|head|body)[\s>/]/i;
|
|
256
|
+
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
|
+
);
|
|
262
|
+
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
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return vNodes;
|
|
270
|
+
}
|
|
271
|
+
|
|
259
272
|
const CLASS_ATTRIBUTE_NAME = "class";
|
|
260
273
|
const XLINK_ATTRIBUTE_NAME = "xlink";
|
|
261
274
|
const XMLNS_ATTRIBUTE_NAME = "xmlns";
|
|
@@ -329,6 +342,11 @@ const getRenderer = (document) => {
|
|
|
329
342
|
},
|
|
330
343
|
createElement: (virtualNode, parentDomElement) => {
|
|
331
344
|
let newEl;
|
|
345
|
+
if (isCommentVNode(virtualNode)) {
|
|
346
|
+
const comment = document.createComment(virtualNode.value ?? "");
|
|
347
|
+
parentDomElement?.appendChild(comment);
|
|
348
|
+
return comment;
|
|
349
|
+
}
|
|
332
350
|
try {
|
|
333
351
|
if (typeof virtualNode === "function" && virtualNode.constructor.name === "AsyncFunction") {
|
|
334
352
|
newEl = document.createElement("div");
|
|
@@ -519,96 +537,6 @@ const getRenderer = (document) => {
|
|
|
519
537
|
return renderer;
|
|
520
538
|
};
|
|
521
539
|
|
|
522
|
-
const FROM_DOM_MARKER = Symbol("defuss-morph.from-dom");
|
|
523
|
-
const HTML_BOOLEAN_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
524
|
-
"allowfullscreen",
|
|
525
|
-
"async",
|
|
526
|
-
"autofocus",
|
|
527
|
-
"autoplay",
|
|
528
|
-
"checked",
|
|
529
|
-
"controls",
|
|
530
|
-
"default",
|
|
531
|
-
"defer",
|
|
532
|
-
"disabled",
|
|
533
|
-
"formnovalidate",
|
|
534
|
-
"hidden",
|
|
535
|
-
"inert",
|
|
536
|
-
"ismap",
|
|
537
|
-
"itemscope",
|
|
538
|
-
"loop",
|
|
539
|
-
"multiple",
|
|
540
|
-
"muted",
|
|
541
|
-
"nomodule",
|
|
542
|
-
"novalidate",
|
|
543
|
-
"open",
|
|
544
|
-
"playsinline",
|
|
545
|
-
"readonly",
|
|
546
|
-
"required",
|
|
547
|
-
"reversed",
|
|
548
|
-
"selected"
|
|
549
|
-
]);
|
|
550
|
-
const domAttributeToVNodeValue = (attr) => HTML_BOOLEAN_ATTRIBUTES.has(attr.name.toLowerCase()) ? true : attr.value;
|
|
551
|
-
function parseDOM(input, type, Parser) {
|
|
552
|
-
return new Parser().parseFromString(input, type);
|
|
553
|
-
}
|
|
554
|
-
function isSVG(input, Parser) {
|
|
555
|
-
const doc = parseDOM(input, "image/svg+xml", Parser);
|
|
556
|
-
if (!doc.documentElement) return false;
|
|
557
|
-
return doc.documentElement.nodeName.toLowerCase() === "svg";
|
|
558
|
-
}
|
|
559
|
-
function isHTML(input, Parser) {
|
|
560
|
-
const doc = parseDOM(input, "text/html", Parser);
|
|
561
|
-
return doc.documentElement.querySelectorAll("*").length > 2;
|
|
562
|
-
}
|
|
563
|
-
const isMarkup = (input, Parser) => input.indexOf("<") > -1 && input.indexOf(">") > -1 && (isHTML(input, Parser) || isSVG(input, Parser));
|
|
564
|
-
function renderMarkup(markup, Parser, doc) {
|
|
565
|
-
const parsed = doc ? doc : parseDOM(markup, getMimeType(markup, Parser), Parser);
|
|
566
|
-
if (parsed.body) return Array.from(parsed.body.childNodes);
|
|
567
|
-
return parsed.documentElement ? [parsed.documentElement] : [];
|
|
568
|
-
}
|
|
569
|
-
function getMimeType(input, Parser) {
|
|
570
|
-
if (isSVG(input, Parser)) {
|
|
571
|
-
return "image/svg+xml";
|
|
572
|
-
}
|
|
573
|
-
return "text/html";
|
|
574
|
-
}
|
|
575
|
-
function domNodeToVNode(node) {
|
|
576
|
-
if (node.nodeType === 3) {
|
|
577
|
-
return node.textContent || "";
|
|
578
|
-
}
|
|
579
|
-
if (node.nodeType === 1) {
|
|
580
|
-
const element = node;
|
|
581
|
-
const attributes = {};
|
|
582
|
-
for (let i = 0; i < element.attributes.length; i++) {
|
|
583
|
-
const attr = element.attributes[i];
|
|
584
|
-
attributes[attr.name] = domAttributeToVNodeValue(attr);
|
|
585
|
-
}
|
|
586
|
-
const children = [];
|
|
587
|
-
for (let i = 0; i < element.childNodes.length; i++) {
|
|
588
|
-
const childVNode = domNodeToVNode(element.childNodes[i]);
|
|
589
|
-
children.push(childVNode);
|
|
590
|
-
}
|
|
591
|
-
return {
|
|
592
|
-
type: element.tagName.toLowerCase(),
|
|
593
|
-
attributes: { ...attributes, [FROM_DOM_MARKER]: true },
|
|
594
|
-
children
|
|
595
|
-
};
|
|
596
|
-
}
|
|
597
|
-
return "";
|
|
598
|
-
}
|
|
599
|
-
function htmlStringToVNodes(html, Parser) {
|
|
600
|
-
const parser = new Parser();
|
|
601
|
-
const doc = parser.parseFromString(html, "text/html");
|
|
602
|
-
const vNodes = [];
|
|
603
|
-
for (let i = 0; i < doc.body.childNodes.length; i++) {
|
|
604
|
-
const vnode = domNodeToVNode(doc.body.childNodes[i]);
|
|
605
|
-
if (vnode !== "") {
|
|
606
|
-
vNodes.push(vnode);
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
return vNodes;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
540
|
const areDomNodesEqual = (oldNode, newNode) => {
|
|
613
541
|
if (oldNode === newNode) return true;
|
|
614
542
|
if (oldNode.nodeType !== newNode.nodeType) return false;
|
|
@@ -625,8 +553,8 @@ const areDomNodesEqual = (oldNode, newNode) => {
|
|
|
625
553
|
if (oldAttr.value !== newAttrValue) return false;
|
|
626
554
|
}
|
|
627
555
|
}
|
|
628
|
-
if (oldNode.nodeType === 3) {
|
|
629
|
-
if (oldNode.
|
|
556
|
+
if (oldNode.nodeType === 3 || oldNode.nodeType === 8) {
|
|
557
|
+
if (oldNode.nodeValue !== newNode.nodeValue) return false;
|
|
630
558
|
}
|
|
631
559
|
return true;
|
|
632
560
|
};
|
|
@@ -713,6 +641,7 @@ function areNodeAndChildMatching(domNode, child) {
|
|
|
713
641
|
if (typeof child === "string" || typeof child === "number" || typeof child === "boolean") {
|
|
714
642
|
return domNode.nodeType === 3;
|
|
715
643
|
}
|
|
644
|
+
if (isCommentVNode(child)) return domNode.nodeType === 8;
|
|
716
645
|
if (child && typeof child === "object") {
|
|
717
646
|
if (domNode.nodeType !== 1) return false;
|
|
718
647
|
const el = domNode;
|
|
@@ -798,6 +727,11 @@ function patchElementInPlace(el, vnode, globals, mergeAttributes = false) {
|
|
|
798
727
|
return;
|
|
799
728
|
morphDomDirect(el, vnode.children ?? [], globals);
|
|
800
729
|
}
|
|
730
|
+
function replaceNode(old, next) {
|
|
731
|
+
if (old.nodeType === 1)
|
|
732
|
+
clearDelegatedEventsDeep(old);
|
|
733
|
+
old.parentNode?.replaceChild(next, old);
|
|
734
|
+
}
|
|
801
735
|
function morphNode(domNode, child, globals, mergeAttributes = false) {
|
|
802
736
|
if (typeof child === "string" || typeof child === "number" || typeof child === "boolean") {
|
|
803
737
|
const text = String(child);
|
|
@@ -806,7 +740,17 @@ function morphNode(domNode, child, globals, mergeAttributes = false) {
|
|
|
806
740
|
return domNode;
|
|
807
741
|
}
|
|
808
742
|
const next = globals.window.document.createTextNode(text);
|
|
809
|
-
domNode
|
|
743
|
+
replaceNode(domNode, next);
|
|
744
|
+
return next;
|
|
745
|
+
}
|
|
746
|
+
if (isCommentVNode(child)) {
|
|
747
|
+
const data = child.value ?? "";
|
|
748
|
+
if (domNode.nodeType === 8) {
|
|
749
|
+
if (domNode.nodeValue !== data) domNode.nodeValue = data;
|
|
750
|
+
return domNode;
|
|
751
|
+
}
|
|
752
|
+
const next = globals.window.document.createComment(data);
|
|
753
|
+
replaceNode(domNode, next);
|
|
810
754
|
return next;
|
|
811
755
|
}
|
|
812
756
|
if (child && typeof child === "object") {
|
|
@@ -827,13 +771,15 @@ function morphNode(domNode, child, globals, mergeAttributes = false) {
|
|
|
827
771
|
const created = createDomFromChild(child, globals);
|
|
828
772
|
const first = Array.isArray(created) ? created[0] : created;
|
|
829
773
|
if (!first) return null;
|
|
830
|
-
el
|
|
774
|
+
replaceNode(el, first);
|
|
831
775
|
handleLifecycleEventsForOnMount(first);
|
|
832
776
|
return first;
|
|
833
777
|
}
|
|
834
778
|
patchElementInPlace(el, child, globals, mergeAttributes);
|
|
835
779
|
return el;
|
|
836
780
|
}
|
|
781
|
+
if (domNode.nodeType === 1)
|
|
782
|
+
clearDelegatedEventsDeep(domNode);
|
|
837
783
|
domNode.parentNode?.removeChild(domNode);
|
|
838
784
|
return null;
|
|
839
785
|
}
|
|
@@ -888,6 +834,7 @@ function morphDiff(targetRoot, patchItems, globals) {
|
|
|
888
834
|
}
|
|
889
835
|
for (const item of patchItems) {
|
|
890
836
|
if (typeof item === "string" && item.trim() === "") continue;
|
|
837
|
+
if (isCommentVNode(item)) continue;
|
|
891
838
|
const key = getVNodeMatchKey(item);
|
|
892
839
|
if (!key) {
|
|
893
840
|
throw new Error(
|
|
@@ -1205,6 +1152,7 @@ const morph = (el, newContent, options = {}) => {
|
|
|
1205
1152
|
|
|
1206
1153
|
exports.CAPTURE_ONLY_EVENTS = CAPTURE_ONLY_EVENTS;
|
|
1207
1154
|
exports.CLASS_ATTRIBUTE_NAME = CLASS_ATTRIBUTE_NAME;
|
|
1155
|
+
exports.COMMENT_TYPE = COMMENT_TYPE;
|
|
1208
1156
|
exports.DANGEROUSLY_SET_INNER_HTML_ATTRIBUTE = DANGEROUSLY_SET_INNER_HTML_ATTRIBUTE;
|
|
1209
1157
|
exports.DEFAULT_TRANSITION_CONFIG = DEFAULT_TRANSITION_CONFIG;
|
|
1210
1158
|
exports.FROM_DOM_MARKER = FROM_DOM_MARKER;
|
|
@@ -1223,12 +1171,14 @@ exports.getRenderer = getRenderer;
|
|
|
1223
1171
|
exports.getTransitionStyles = getTransitionStyles;
|
|
1224
1172
|
exports.handleLifecycleEventsForOnMount = handleLifecycleEventsForOnMount;
|
|
1225
1173
|
exports.htmlStringToVNodes = htmlStringToVNodes;
|
|
1174
|
+
exports.isCommentVNode = isCommentVNode;
|
|
1226
1175
|
exports.isHTML = isHTML;
|
|
1227
1176
|
exports.isMarkup = isMarkup;
|
|
1228
1177
|
exports.isSVG = isSVG;
|
|
1229
1178
|
exports.morph = morph;
|
|
1230
1179
|
exports.nsMap = nsMap;
|
|
1231
1180
|
exports.observeUnmount = observeUnmount;
|
|
1181
|
+
exports.onClearDelegatedEvents = onClearDelegatedEvents;
|
|
1232
1182
|
exports.parseDOM = parseDOM;
|
|
1233
1183
|
exports.parseEventPropName = parseEventPropName;
|
|
1234
1184
|
exports.performTransition = performTransition;
|
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,11 @@ interface VNode<A = VNodeAttributes> {
|
|
|
28
28
|
type?: VNodeType;
|
|
29
29
|
attributes?: A;
|
|
30
30
|
children?: VNodeChildren;
|
|
31
|
+
/**
|
|
32
|
+
* Comment data of a `{ type: "#comment" }` VNode (see `COMMENT_TYPE`).
|
|
33
|
+
* VERIFIED: optional, so defuss core's VNode (no `value`) stays assignable.
|
|
34
|
+
*/
|
|
35
|
+
value?: string;
|
|
31
36
|
sourceInfo?: unknown;
|
|
32
37
|
/** Original props passed to a function component (set by jsx runtime for SSG hydration). */
|
|
33
38
|
componentProps?: Record<string, any>;
|
|
@@ -82,11 +87,23 @@ interface ParsedEventProp {
|
|
|
82
87
|
eventType: string;
|
|
83
88
|
capture: boolean;
|
|
84
89
|
}
|
|
85
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* Events whose handlers are forced into the capture phase, so an ancestor's
|
|
92
|
+
* handler also sees descendants' events (container onFocus/onBlur). Every
|
|
93
|
+
* other non-bubbling event reaches only its target's handlers, natively.
|
|
94
|
+
*/
|
|
86
95
|
declare const CAPTURE_ONLY_EVENTS: Set<string>;
|
|
87
96
|
declare const parseEventPropName: (propName: string) => ParsedEventProp | null;
|
|
88
97
|
declare const registerDelegatedEvent: (element: HTMLElement, eventType: string, handler: EventListener, options?: DelegatedEventOptions) => void;
|
|
89
98
|
declare const removeDelegatedEvent: (target: EventTarget, eventType: string, handler?: EventListener, _options?: DelegatedEventOptions) => void;
|
|
99
|
+
/**
|
|
100
|
+
* Why: listeners a facade attaches natively (defuss-query's on()) live
|
|
101
|
+
* outside this registry, yet must go whenever morph clears an element
|
|
102
|
+
* (removal, replacement, clearDelegatedEventsDeep). A hook keeps morph
|
|
103
|
+
* ignorant of who owns them, instead of morph also tracking native listeners.
|
|
104
|
+
* Registering the same hook twice is a no-op; returns an unregister function.
|
|
105
|
+
*/
|
|
106
|
+
declare const onClearDelegatedEvents: (hook: (target: EventTarget) => void) => (() => void);
|
|
90
107
|
declare const clearDelegatedEvents: (target: EventTarget) => void;
|
|
91
108
|
/**
|
|
92
109
|
* Clear delegated events for an element and all its descendants.
|
|
@@ -130,7 +147,7 @@ declare const getRenderer: (document: Document) => DomAbstractionImpl;
|
|
|
130
147
|
* 1. Checks for reference equality.
|
|
131
148
|
* 2. Compares node types.
|
|
132
149
|
* 3. For Element nodes, compares tag names and attributes.
|
|
133
|
-
* 4. For Text nodes, compares
|
|
150
|
+
* 4. For Text and Comment nodes, compares their data.
|
|
134
151
|
*/
|
|
135
152
|
declare const areDomNodesEqual: (oldNode: Node, newNode: Node) => boolean;
|
|
136
153
|
/********************************************************
|
|
@@ -182,6 +199,17 @@ declare function replaceDomWithVdom(parentElement: Element, newVDOM: RenderInput
|
|
|
182
199
|
* (same rationale as uncontrolled form-state preservation).
|
|
183
200
|
*/
|
|
184
201
|
declare const FROM_DOM_MARKER: unique symbol;
|
|
202
|
+
/**
|
|
203
|
+
* VNode type of an HTML comment: `{ type: "#comment", value }`.
|
|
204
|
+
* Why "#comment": it is the DOM's own nodeName for comments and can never
|
|
205
|
+
* collide with a tag name ("#" is invalid there). The data lives in `value`,
|
|
206
|
+
* not `children`, so generic tree walkers never mistake it for content.
|
|
207
|
+
*/
|
|
208
|
+
declare const COMMENT_TYPE = "#comment";
|
|
209
|
+
type CommentVNode = VNode & {
|
|
210
|
+
type: typeof COMMENT_TYPE;
|
|
211
|
+
};
|
|
212
|
+
declare const isCommentVNode: (value: unknown) => value is CommentVNode;
|
|
185
213
|
declare function parseDOM(input: string, type: DOMParserSupportedType, Parser: typeof DOMParser): Document;
|
|
186
214
|
declare function isSVG(input: string, Parser: typeof DOMParser): boolean;
|
|
187
215
|
declare function isHTML(input: string, Parser: typeof DOMParser): boolean;
|
|
@@ -229,5 +257,5 @@ interface MorphOptions {
|
|
|
229
257
|
}
|
|
230
258
|
declare const morph: (el: Element, newContent: RenderInput, options?: MorphOptions) => void | Promise<void>;
|
|
231
259
|
|
|
232
|
-
export { CAPTURE_ONLY_EVENTS, CLASS_ATTRIBUTE_NAME, 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, isHTML, isMarkup, isSVG, morph, nsMap, observeUnmount, parseDOM, parseEventPropName, performTransition, queueCallback, registerDelegatedEvent, removeDelegatedEvent, removeDelegatedEventByKey, renderMarkup, replaceDomWithVdom, resolveGlobals, updateDomWithVdom };
|
|
233
|
-
export type { DefussKey, DelegatedEventOptions, DelegatedPhase, DomAbstractionImpl, Globals, MorphMode, MorphOptions, MountHandler, ParsedEventProp, RefLike, RenderInput, TransitionConfig, TransitionStyles, TransitionType, TransitionsEasing, UnmountHandler, VNode, VNodeAttributes, VNodeChild, VNodeChildren, VNodeType, ValidChild };
|
|
260
|
+
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 };
|
|
261
|
+
export type { CommentVNode, DefussKey, DelegatedEventOptions, DelegatedPhase, DomAbstractionImpl, Globals, MorphMode, MorphOptions, MountHandler, ParsedEventProp, RefLike, RenderInput, TransitionConfig, TransitionStyles, TransitionType, TransitionsEasing, UnmountHandler, VNode, VNodeAttributes, VNodeChild, VNodeChildren, VNodeType, ValidChild };
|