defuss-morph 0.1.0 โ 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 +14 -6
- package/dist/all.js +211 -254
- package/dist/all.js.map +8 -8
- 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/README.md
CHANGED
|
@@ -15,11 +15,14 @@ Features:
|
|
|
15
15
|
- ๐๏ธ Fast! Uses native DOM APIs
|
|
16
16
|
- ๐ฏ Stable key/id-aware node matching
|
|
17
17
|
- ๐ฆ CDN-served and packaged with ESM + CJS
|
|
18
|
-
- ๐งน Event handler preservation
|
|
18
|
+
- ๐งน Event handler preservation; JSX handlers listen on the element itself, so propagation is exactly native (order, `stopPropagation()`, `currentTarget`, non-bubbling events like `toggle`, `load`, `error`)
|
|
19
19
|
- ๐ Form-state preservation (see one limitation below w/ solution)
|
|
20
|
+
- ๐ฌ HTML comments are kept and morphed like text โ `morph(el, el.innerHTML)` changes no node
|
|
20
21
|
- โก No compile/transpile step required (but optionally available)
|
|
21
22
|
- ๐ชถ Extremely lightweight โ just ~6 KiB minified and gzipped
|
|
22
23
|
- ๐
Includes support for beautiful transitions (fade, slide, shake, custom styles)
|
|
24
|
+
- ๐งฉ Supports partial updates (diff mode) for streaming/AI-driven UIs
|
|
25
|
+
- โ
~97%+ test coverage with unit tests and real browser E2E tests
|
|
23
26
|
- ๐ฆ Written in TypeScript
|
|
24
27
|
|
|
25
28
|
## Quick, traditional CDN-based setup
|
|
@@ -163,7 +166,8 @@ Morphs the children of `el` to match the given HTML string or JSX/VNode content.
|
|
|
163
166
|
`morph()` accepts **either an HTML string or JSX / VNodes** โ same API, same
|
|
164
167
|
algorithm. Strings are parsed with the native `DOMParser`; VNodes
|
|
165
168
|
(`{ type, attributes, children }` objects, as produced by any JSX factory)
|
|
166
|
-
are morphed directly:
|
|
169
|
+
are morphed directly. An HTML comment is `{ type: "#comment", value }`
|
|
170
|
+
(`COMMENT_TYPE`):
|
|
167
171
|
|
|
168
172
|
```tsx
|
|
169
173
|
import { morph } from "defuss-morph";
|
|
@@ -243,6 +247,8 @@ anything, so addressing stays unambiguous:
|
|
|
243
247
|
- A declared `children` replaces the node's children; if children are not
|
|
244
248
|
declared, they stay untouched (via VNodes, `children: []` clears explicitly).
|
|
245
249
|
- A tag change on an addressed node applies as an in-place replacement.
|
|
250
|
+
- Top-level comments in a change-set are skipped (they have no address, and
|
|
251
|
+
appending would duplicate them per patch); comments *inside* items morph.
|
|
246
252
|
|
|
247
253
|
Great for streaming/AI-driven UIs: patch one table row or status badge without
|
|
248
254
|
re-sending (or risking) the rest of the subtree. Runnable demo:
|
|
@@ -276,6 +282,8 @@ import {
|
|
|
276
282
|
domNodeToVNode, // DOM Node -> VNode
|
|
277
283
|
getRenderer, // VNode -> DOM renderer factory
|
|
278
284
|
registerDelegatedEvent, removeDelegatedEvent, clearDelegatedEventsDeep, // ...
|
|
285
|
+
onClearDelegatedEvents, // hook: tear down your own native listeners whenever
|
|
286
|
+
// morph removes or replaces an element
|
|
279
287
|
} from "defuss-morph";
|
|
280
288
|
|
|
281
289
|
// the third `globals` argument is optional everywhere โ it is derived
|
|
@@ -353,10 +361,10 @@ The same rule applies to an `<input>`'s live `value`. See the runnable demos [`e
|
|
|
353
361
|
<!-- bundle-size:start -->
|
|
354
362
|
| File | Size | Gzipped | Purpose |
|
|
355
363
|
| --- | ---: | ---: | --- |
|
|
356
|
-
| `index.mjs` |
|
|
357
|
-
| `index.cjs` |
|
|
358
|
-
| `all.js` |
|
|
359
|
-
| `all.min.js` | 18.
|
|
364
|
+
| `index.mjs` | 39.3 kB | 9.6 kB | ESM/library build; used when installing via npm/bun |
|
|
365
|
+
| `index.cjs` | 40.4 kB | 9.7 kB | CommonJS build |
|
|
366
|
+
| `all.js` | 40.9 kB | 9.9 kB | UMD build; for CDN-based usage with debugging |
|
|
367
|
+
| `all.min.js` | 18.7 kB | **6.8 kB** | Minified UMD build; for CDN-based usage without debugging (Pareto-optimal when no bundler is used) |
|
|
360
368
|
<!-- bundle-size:end -->
|
|
361
369
|
|
|
362
370
|
`index.cjs` (CommonJS) is kept for `require()` compatibility on older tool
|
package/dist/all.js
CHANGED
|
@@ -27,12 +27,14 @@ __export(exports_src, {
|
|
|
27
27
|
performTransition: () => performTransition,
|
|
28
28
|
parseEventPropName: () => parseEventPropName,
|
|
29
29
|
parseDOM: () => parseDOM,
|
|
30
|
+
onClearDelegatedEvents: () => onClearDelegatedEvents,
|
|
30
31
|
observeUnmount: () => observeUnmount,
|
|
31
32
|
nsMap: () => nsMap,
|
|
32
33
|
morph: () => morph2,
|
|
33
34
|
isSVG: () => isSVG,
|
|
34
35
|
isMarkup: () => isMarkup,
|
|
35
36
|
isHTML: () => isHTML,
|
|
37
|
+
isCommentVNode: () => isCommentVNode,
|
|
36
38
|
htmlStringToVNodes: () => htmlStringToVNodes,
|
|
37
39
|
handleLifecycleEventsForOnMount: () => handleLifecycleEventsForOnMount,
|
|
38
40
|
getTransitionStyles: () => getTransitionStyles,
|
|
@@ -51,6 +53,7 @@ __export(exports_src, {
|
|
|
51
53
|
FROM_DOM_MARKER: () => FROM_DOM_MARKER,
|
|
52
54
|
DEFAULT_TRANSITION_CONFIG: () => DEFAULT_TRANSITION_CONFIG,
|
|
53
55
|
DANGEROUSLY_SET_INNER_HTML_ATTRIBUTE: () => DANGEROUSLY_SET_INNER_HTML_ATTRIBUTE,
|
|
56
|
+
COMMENT_TYPE: () => COMMENT_TYPE,
|
|
54
57
|
CLASS_ATTRIBUTE_NAME: () => CLASS_ATTRIBUTE_NAME,
|
|
55
58
|
CAPTURE_ONLY_EVENTS: () => CAPTURE_ONLY_EVENTS
|
|
56
59
|
});
|
|
@@ -67,8 +70,7 @@ var CAPTURE_ONLY_EVENTS = new Set([
|
|
|
67
70
|
"mouseleave"
|
|
68
71
|
]);
|
|
69
72
|
var elementHandlerMap = new WeakMap;
|
|
70
|
-
var
|
|
71
|
-
var captureDispatched = new WeakMap;
|
|
73
|
+
var slotListeners = new WeakMap;
|
|
72
74
|
var activeDispatches = new WeakMap;
|
|
73
75
|
var parseEventPropName = (propName) => {
|
|
74
76
|
if (!propName.startsWith("on"))
|
|
@@ -91,146 +93,66 @@ var getOrCreateElementHandlers = (el) => {
|
|
|
91
93
|
elementHandlerMap.set(el, created);
|
|
92
94
|
return created;
|
|
93
95
|
};
|
|
94
|
-
var
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
97
|
-
return
|
|
98
|
-
const
|
|
99
|
-
let
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
96
|
+
var runSlot = (target, eventType, phase, event) => {
|
|
97
|
+
const handler = elementHandlerMap.get(target)?.get(eventType)?.[phase];
|
|
98
|
+
if (!handler)
|
|
99
|
+
return;
|
|
100
|
+
const dispatchKey = `${eventType}:${phase}`;
|
|
101
|
+
let active = activeDispatches.get(target);
|
|
102
|
+
if (active?.has(dispatchKey))
|
|
103
|
+
return;
|
|
104
|
+
if (!active) {
|
|
105
|
+
active = new Set;
|
|
106
|
+
activeDispatches.set(target, active);
|
|
107
|
+
}
|
|
108
|
+
active.add(dispatchKey);
|
|
109
|
+
try {
|
|
110
|
+
handler.call(target, event);
|
|
111
|
+
} finally {
|
|
112
|
+
active.delete(dispatchKey);
|
|
108
113
|
}
|
|
109
|
-
const doc = event.target?.ownerDocument;
|
|
110
|
-
if (doc && path[path.length - 1] !== doc)
|
|
111
|
-
path.push(doc);
|
|
112
|
-
const win = doc?.defaultView;
|
|
113
|
-
if (win && path[path.length - 1] !== win)
|
|
114
|
-
path.push(win);
|
|
115
|
-
return path;
|
|
116
114
|
};
|
|
117
|
-
var
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (!entry)
|
|
128
|
-
continue;
|
|
129
|
-
let targets = dispatched.get(event);
|
|
130
|
-
if (targets?.has(target))
|
|
131
|
-
continue;
|
|
132
|
-
if (!targets) {
|
|
133
|
-
targets = new WeakSet;
|
|
134
|
-
dispatched.set(event, targets);
|
|
135
|
-
}
|
|
136
|
-
targets.add(target);
|
|
137
|
-
const dispatchKey = `${eventType}:${phase}`;
|
|
138
|
-
let activeSet = activeDispatches.get(target);
|
|
139
|
-
if (activeSet?.has(dispatchKey))
|
|
140
|
-
continue;
|
|
141
|
-
if (!activeSet) {
|
|
142
|
-
activeSet = new Set;
|
|
143
|
-
activeDispatches.set(target, activeSet);
|
|
144
|
-
}
|
|
145
|
-
activeSet.add(dispatchKey);
|
|
146
|
-
try {
|
|
147
|
-
if (phase === "capture") {
|
|
148
|
-
if (entry.capture) {
|
|
149
|
-
entry.capture.call(target, event);
|
|
150
|
-
if (event.cancelBubble)
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
if (entry.captureSet) {
|
|
154
|
-
for (const handler of entry.captureSet) {
|
|
155
|
-
handler.call(target, event);
|
|
156
|
-
if (event.cancelBubble)
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
} else {
|
|
161
|
-
if (entry.bubble) {
|
|
162
|
-
entry.bubble.call(target, event);
|
|
163
|
-
if (event.cancelBubble)
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
if (entry.bubbleSet) {
|
|
167
|
-
for (const handler of entry.bubbleSet) {
|
|
168
|
-
handler.call(target, event);
|
|
169
|
-
if (event.cancelBubble)
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
} finally {
|
|
175
|
-
activeSet.delete(dispatchKey);
|
|
176
|
-
}
|
|
115
|
+
var setSlot = (target, eventType, phase, entry, handler) => {
|
|
116
|
+
entry[phase] = handler;
|
|
117
|
+
const key = `${eventType}:${phase}`;
|
|
118
|
+
const capture = phase === "capture";
|
|
119
|
+
let byKey = slotListeners.get(target);
|
|
120
|
+
const installed = byKey?.get(key);
|
|
121
|
+
if (handler && !installed) {
|
|
122
|
+
if (!byKey) {
|
|
123
|
+
byKey = new Map;
|
|
124
|
+
slotListeners.set(target, byKey);
|
|
177
125
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const captureKey = `${eventType}:capture`;
|
|
185
|
-
if (!installed.has(captureKey)) {
|
|
186
|
-
root.addEventListener(eventType, createPhaseHandler(eventType, "capture"), true);
|
|
187
|
-
installed.add(captureKey);
|
|
188
|
-
}
|
|
189
|
-
const bubbleKey = `${eventType}:bubble`;
|
|
190
|
-
if (!installed.has(bubbleKey)) {
|
|
191
|
-
root.addEventListener(eventType, createPhaseHandler(eventType, "bubble"), false);
|
|
192
|
-
installed.add(bubbleKey);
|
|
126
|
+
const trampoline = (event) => runSlot(target, eventType, phase, event);
|
|
127
|
+
byKey.set(key, trampoline);
|
|
128
|
+
target.addEventListener(eventType, trampoline, capture);
|
|
129
|
+
} else if (!handler && installed) {
|
|
130
|
+
byKey.delete(key);
|
|
131
|
+
target.removeEventListener(eventType, installed, capture);
|
|
193
132
|
}
|
|
194
133
|
};
|
|
195
|
-
var
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return root;
|
|
202
|
-
}
|
|
203
|
-
return null;
|
|
134
|
+
var clearPhase = (target, eventType, entry, phase) => {
|
|
135
|
+
setSlot(target, eventType, phase, entry, undefined);
|
|
136
|
+
const setKey = phase === "capture" ? "captureSet" : "bubbleSet";
|
|
137
|
+
for (const handler of entry[setKey] ?? [])
|
|
138
|
+
target.removeEventListener(eventType, handler, phase === "capture");
|
|
139
|
+
entry[setKey] = undefined;
|
|
204
140
|
};
|
|
205
141
|
var registerDelegatedEvent = (element, eventType, handler, options = {}) => {
|
|
206
|
-
const root = getEventRoot(element);
|
|
207
142
|
const capture = options.capture || CAPTURE_ONLY_EVENTS.has(eventType);
|
|
208
|
-
|
|
209
|
-
ensureRootListener(root, eventType);
|
|
210
|
-
} else if (element.ownerDocument) {
|
|
211
|
-
ensureRootListener(element.ownerDocument, eventType);
|
|
212
|
-
} else {
|
|
213
|
-
element.addEventListener(eventType, handler, capture);
|
|
214
|
-
}
|
|
143
|
+
const phase = capture ? "capture" : "bubble";
|
|
215
144
|
const byEvent = getOrCreateElementHandlers(element);
|
|
216
145
|
const entry = byEvent.get(eventType) ?? {};
|
|
217
146
|
byEvent.set(eventType, entry);
|
|
218
147
|
if (options.multi) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (!entry.bubbleSet)
|
|
225
|
-
entry.bubbleSet = new Set;
|
|
226
|
-
entry.bubbleSet.add(handler);
|
|
148
|
+
const setKey = capture ? "captureSet" : "bubbleSet";
|
|
149
|
+
const set = entry[setKey] ??= new Set;
|
|
150
|
+
if (!set.has(handler)) {
|
|
151
|
+
set.add(handler);
|
|
152
|
+
element.addEventListener(eventType, handler, capture);
|
|
227
153
|
}
|
|
228
154
|
} else {
|
|
229
|
-
|
|
230
|
-
entry.capture = handler;
|
|
231
|
-
} else {
|
|
232
|
-
entry.bubble = handler;
|
|
233
|
-
}
|
|
155
|
+
setSlot(element, eventType, phase, entry, handler);
|
|
234
156
|
}
|
|
235
157
|
};
|
|
236
158
|
var isEntryEmpty = (entry) => !entry.capture && !entry.bubble && (!entry.captureSet || entry.captureSet.size === 0) && (!entry.bubbleSet || entry.bubbleSet.size === 0);
|
|
@@ -242,35 +164,39 @@ var removeDelegatedEvent = (target, eventType, handler, _options = {}) => {
|
|
|
242
164
|
if (!entry)
|
|
243
165
|
return;
|
|
244
166
|
if (handler) {
|
|
245
|
-
|
|
246
|
-
entry.captureSet.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
entry
|
|
167
|
+
for (const phase of ["capture", "bubble"]) {
|
|
168
|
+
const set = phase === "capture" ? entry.captureSet : entry.bubbleSet;
|
|
169
|
+
if (set?.delete(handler))
|
|
170
|
+
target.removeEventListener(eventType, handler, phase === "capture");
|
|
171
|
+
if (entry[phase] === handler)
|
|
172
|
+
setSlot(target, eventType, phase, entry, undefined);
|
|
250
173
|
}
|
|
251
|
-
if (entry.capture === handler) {
|
|
252
|
-
entry.capture = undefined;
|
|
253
|
-
}
|
|
254
|
-
if (entry.bubble === handler) {
|
|
255
|
-
entry.bubble = undefined;
|
|
256
|
-
}
|
|
257
|
-
target.removeEventListener(eventType, handler, true);
|
|
258
|
-
target.removeEventListener(eventType, handler, false);
|
|
259
174
|
} else {
|
|
260
|
-
entry
|
|
261
|
-
entry
|
|
262
|
-
entry.captureSet = undefined;
|
|
263
|
-
entry.bubbleSet = undefined;
|
|
175
|
+
clearPhase(target, eventType, entry, "capture");
|
|
176
|
+
clearPhase(target, eventType, entry, "bubble");
|
|
264
177
|
}
|
|
265
178
|
if (isEntryEmpty(entry)) {
|
|
266
179
|
byEvent.delete(eventType);
|
|
267
180
|
}
|
|
268
181
|
};
|
|
182
|
+
var clearHooks = new Set;
|
|
183
|
+
var onClearDelegatedEvents = (hook) => {
|
|
184
|
+
clearHooks.add(hook);
|
|
185
|
+
return () => {
|
|
186
|
+
clearHooks.delete(hook);
|
|
187
|
+
};
|
|
188
|
+
};
|
|
269
189
|
var clearDelegatedEvents = (target) => {
|
|
270
190
|
const byEvent = elementHandlerMap.get(target);
|
|
271
|
-
if (
|
|
272
|
-
|
|
273
|
-
|
|
191
|
+
if (byEvent) {
|
|
192
|
+
for (const [eventType, entry] of byEvent) {
|
|
193
|
+
clearPhase(target, eventType, entry, "capture");
|
|
194
|
+
clearPhase(target, eventType, entry, "bubble");
|
|
195
|
+
}
|
|
196
|
+
byEvent.clear();
|
|
197
|
+
}
|
|
198
|
+
for (const hook of clearHooks)
|
|
199
|
+
hook(target);
|
|
274
200
|
};
|
|
275
201
|
var clearDelegatedEventsDeep = (root) => {
|
|
276
202
|
clearDelegatedEvents(root);
|
|
@@ -310,17 +236,110 @@ var removeDelegatedEventByKey = (element, eventType, phase) => {
|
|
|
310
236
|
const entry = byEvent.get(eventType);
|
|
311
237
|
if (!entry)
|
|
312
238
|
return;
|
|
313
|
-
|
|
314
|
-
entry.capture = undefined;
|
|
315
|
-
entry.captureSet = undefined;
|
|
316
|
-
} else {
|
|
317
|
-
entry.bubble = undefined;
|
|
318
|
-
entry.bubbleSet = undefined;
|
|
319
|
-
}
|
|
239
|
+
clearPhase(element, eventType, entry, phase);
|
|
320
240
|
if (isEntryEmpty(entry))
|
|
321
241
|
byEvent.delete(eventType);
|
|
322
242
|
};
|
|
323
243
|
|
|
244
|
+
// src/html.ts
|
|
245
|
+
var FROM_DOM_MARKER = Symbol("defuss-morph.from-dom");
|
|
246
|
+
var COMMENT_TYPE = "#comment";
|
|
247
|
+
var isCommentVNode = (value) => !!value && typeof value === "object" && value.type === COMMENT_TYPE;
|
|
248
|
+
var HTML_BOOLEAN_ATTRIBUTES = new Set([
|
|
249
|
+
"allowfullscreen",
|
|
250
|
+
"async",
|
|
251
|
+
"autofocus",
|
|
252
|
+
"autoplay",
|
|
253
|
+
"checked",
|
|
254
|
+
"controls",
|
|
255
|
+
"default",
|
|
256
|
+
"defer",
|
|
257
|
+
"disabled",
|
|
258
|
+
"formnovalidate",
|
|
259
|
+
"hidden",
|
|
260
|
+
"inert",
|
|
261
|
+
"ismap",
|
|
262
|
+
"itemscope",
|
|
263
|
+
"loop",
|
|
264
|
+
"multiple",
|
|
265
|
+
"muted",
|
|
266
|
+
"nomodule",
|
|
267
|
+
"novalidate",
|
|
268
|
+
"open",
|
|
269
|
+
"playsinline",
|
|
270
|
+
"readonly",
|
|
271
|
+
"required",
|
|
272
|
+
"reversed",
|
|
273
|
+
"selected"
|
|
274
|
+
]);
|
|
275
|
+
var domAttributeToVNodeValue = (attr) => HTML_BOOLEAN_ATTRIBUTES.has(attr.name.toLowerCase()) ? true : attr.value;
|
|
276
|
+
function parseDOM(input, type, Parser) {
|
|
277
|
+
return new Parser().parseFromString(input, type);
|
|
278
|
+
}
|
|
279
|
+
function isSVG(input, Parser) {
|
|
280
|
+
const doc = parseDOM(input, "image/svg+xml", Parser);
|
|
281
|
+
if (!doc.documentElement)
|
|
282
|
+
return false;
|
|
283
|
+
return doc.documentElement.nodeName.toLowerCase() === "svg";
|
|
284
|
+
}
|
|
285
|
+
function isHTML(input, Parser) {
|
|
286
|
+
const doc = parseDOM(input, "text/html", Parser);
|
|
287
|
+
return doc.documentElement.querySelectorAll("*").length > 2;
|
|
288
|
+
}
|
|
289
|
+
var isMarkup = (input, Parser) => input.indexOf("<") > -1 && input.indexOf(">") > -1 && (isHTML(input, Parser) || isSVG(input, Parser));
|
|
290
|
+
function renderMarkup(markup, Parser, doc) {
|
|
291
|
+
const parsed = doc ? doc : parseDOM(markup, getMimeType(markup, Parser), Parser);
|
|
292
|
+
if (parsed.body)
|
|
293
|
+
return Array.from(parsed.body.childNodes);
|
|
294
|
+
return parsed.documentElement ? [parsed.documentElement] : [];
|
|
295
|
+
}
|
|
296
|
+
function getMimeType(input, Parser) {
|
|
297
|
+
if (isSVG(input, Parser)) {
|
|
298
|
+
return "image/svg+xml";
|
|
299
|
+
}
|
|
300
|
+
return "text/html";
|
|
301
|
+
}
|
|
302
|
+
function domNodeToVNode(node) {
|
|
303
|
+
if (node.nodeType === 3) {
|
|
304
|
+
return node.textContent || "";
|
|
305
|
+
}
|
|
306
|
+
if (node.nodeType === 1) {
|
|
307
|
+
const element = node;
|
|
308
|
+
const attributes = {};
|
|
309
|
+
for (let i = 0;i < element.attributes.length; i++) {
|
|
310
|
+
const attr = element.attributes[i];
|
|
311
|
+
attributes[attr.name] = domAttributeToVNodeValue(attr);
|
|
312
|
+
}
|
|
313
|
+
const children = [];
|
|
314
|
+
for (let i = 0;i < element.childNodes.length; i++) {
|
|
315
|
+
const childVNode = domNodeToVNode(element.childNodes[i]);
|
|
316
|
+
children.push(childVNode);
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
type: element.tagName.toLowerCase(),
|
|
320
|
+
attributes: { ...attributes, [FROM_DOM_MARKER]: true },
|
|
321
|
+
children
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
if (node.nodeType === 8) {
|
|
325
|
+
return { type: COMMENT_TYPE, value: node.nodeValue ?? "" };
|
|
326
|
+
}
|
|
327
|
+
return "";
|
|
328
|
+
}
|
|
329
|
+
var DOCUMENT_START = /^\s*(?:<!--[\s\S]*?-->\s*)*<(?:!doctype|html|head|body)[\s>/]/i;
|
|
330
|
+
function htmlStringToVNodes(html, Parser) {
|
|
331
|
+
const parser = new Parser;
|
|
332
|
+
const doc = parser.parseFromString(DOCUMENT_START.test(html) ? html : `<body>${html}`, "text/html");
|
|
333
|
+
const vNodes = [];
|
|
334
|
+
for (let i = 0;i < doc.body.childNodes.length; i++) {
|
|
335
|
+
const vnode = domNodeToVNode(doc.body.childNodes[i]);
|
|
336
|
+
if (vnode !== "") {
|
|
337
|
+
vNodes.push(vnode);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return vNodes;
|
|
341
|
+
}
|
|
342
|
+
|
|
324
343
|
// src/renderer.ts
|
|
325
344
|
var CLASS_ATTRIBUTE_NAME = "class";
|
|
326
345
|
var XLINK_ATTRIBUTE_NAME = "xlink";
|
|
@@ -393,6 +412,11 @@ var getRenderer = (document2) => {
|
|
|
393
412
|
},
|
|
394
413
|
createElement: (virtualNode, parentDomElement) => {
|
|
395
414
|
let newEl;
|
|
415
|
+
if (isCommentVNode(virtualNode)) {
|
|
416
|
+
const comment = document2.createComment(virtualNode.value ?? "");
|
|
417
|
+
parentDomElement?.appendChild(comment);
|
|
418
|
+
return comment;
|
|
419
|
+
}
|
|
396
420
|
try {
|
|
397
421
|
if (typeof virtualNode === "function" && virtualNode.constructor.name === "AsyncFunction") {
|
|
398
422
|
newEl = document2.createElement("div");
|
|
@@ -548,99 +572,6 @@ var getRenderer = (document2) => {
|
|
|
548
572
|
return renderer;
|
|
549
573
|
};
|
|
550
574
|
|
|
551
|
-
// src/html.ts
|
|
552
|
-
var FROM_DOM_MARKER = Symbol("defuss-morph.from-dom");
|
|
553
|
-
var HTML_BOOLEAN_ATTRIBUTES = new Set([
|
|
554
|
-
"allowfullscreen",
|
|
555
|
-
"async",
|
|
556
|
-
"autofocus",
|
|
557
|
-
"autoplay",
|
|
558
|
-
"checked",
|
|
559
|
-
"controls",
|
|
560
|
-
"default",
|
|
561
|
-
"defer",
|
|
562
|
-
"disabled",
|
|
563
|
-
"formnovalidate",
|
|
564
|
-
"hidden",
|
|
565
|
-
"inert",
|
|
566
|
-
"ismap",
|
|
567
|
-
"itemscope",
|
|
568
|
-
"loop",
|
|
569
|
-
"multiple",
|
|
570
|
-
"muted",
|
|
571
|
-
"nomodule",
|
|
572
|
-
"novalidate",
|
|
573
|
-
"open",
|
|
574
|
-
"playsinline",
|
|
575
|
-
"readonly",
|
|
576
|
-
"required",
|
|
577
|
-
"reversed",
|
|
578
|
-
"selected"
|
|
579
|
-
]);
|
|
580
|
-
var domAttributeToVNodeValue = (attr) => HTML_BOOLEAN_ATTRIBUTES.has(attr.name.toLowerCase()) ? true : attr.value;
|
|
581
|
-
function parseDOM(input, type, Parser) {
|
|
582
|
-
return new Parser().parseFromString(input, type);
|
|
583
|
-
}
|
|
584
|
-
function isSVG(input, Parser) {
|
|
585
|
-
const doc = parseDOM(input, "image/svg+xml", Parser);
|
|
586
|
-
if (!doc.documentElement)
|
|
587
|
-
return false;
|
|
588
|
-
return doc.documentElement.nodeName.toLowerCase() === "svg";
|
|
589
|
-
}
|
|
590
|
-
function isHTML(input, Parser) {
|
|
591
|
-
const doc = parseDOM(input, "text/html", Parser);
|
|
592
|
-
return doc.documentElement.querySelectorAll("*").length > 2;
|
|
593
|
-
}
|
|
594
|
-
var isMarkup = (input, Parser) => input.indexOf("<") > -1 && input.indexOf(">") > -1 && (isHTML(input, Parser) || isSVG(input, Parser));
|
|
595
|
-
function renderMarkup(markup, Parser, doc) {
|
|
596
|
-
const parsed = doc ? doc : parseDOM(markup, getMimeType(markup, Parser), Parser);
|
|
597
|
-
if (parsed.body)
|
|
598
|
-
return Array.from(parsed.body.childNodes);
|
|
599
|
-
return parsed.documentElement ? [parsed.documentElement] : [];
|
|
600
|
-
}
|
|
601
|
-
function getMimeType(input, Parser) {
|
|
602
|
-
if (isSVG(input, Parser)) {
|
|
603
|
-
return "image/svg+xml";
|
|
604
|
-
}
|
|
605
|
-
return "text/html";
|
|
606
|
-
}
|
|
607
|
-
function domNodeToVNode(node) {
|
|
608
|
-
if (node.nodeType === 3) {
|
|
609
|
-
return node.textContent || "";
|
|
610
|
-
}
|
|
611
|
-
if (node.nodeType === 1) {
|
|
612
|
-
const element = node;
|
|
613
|
-
const attributes = {};
|
|
614
|
-
for (let i = 0;i < element.attributes.length; i++) {
|
|
615
|
-
const attr = element.attributes[i];
|
|
616
|
-
attributes[attr.name] = domAttributeToVNodeValue(attr);
|
|
617
|
-
}
|
|
618
|
-
const children = [];
|
|
619
|
-
for (let i = 0;i < element.childNodes.length; i++) {
|
|
620
|
-
const childVNode = domNodeToVNode(element.childNodes[i]);
|
|
621
|
-
children.push(childVNode);
|
|
622
|
-
}
|
|
623
|
-
return {
|
|
624
|
-
type: element.tagName.toLowerCase(),
|
|
625
|
-
attributes: { ...attributes, [FROM_DOM_MARKER]: true },
|
|
626
|
-
children
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
return "";
|
|
630
|
-
}
|
|
631
|
-
function htmlStringToVNodes(html, Parser) {
|
|
632
|
-
const parser = new Parser;
|
|
633
|
-
const doc = parser.parseFromString(html, "text/html");
|
|
634
|
-
const vNodes = [];
|
|
635
|
-
for (let i = 0;i < doc.body.childNodes.length; i++) {
|
|
636
|
-
const vnode = domNodeToVNode(doc.body.childNodes[i]);
|
|
637
|
-
if (vnode !== "") {
|
|
638
|
-
vNodes.push(vnode);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
return vNodes;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
575
|
// src/morph.ts
|
|
645
576
|
var areDomNodesEqual = (oldNode, newNode) => {
|
|
646
577
|
if (oldNode === newNode)
|
|
@@ -663,8 +594,8 @@ var areDomNodesEqual = (oldNode, newNode) => {
|
|
|
663
594
|
return false;
|
|
664
595
|
}
|
|
665
596
|
}
|
|
666
|
-
if (oldNode.nodeType === 3) {
|
|
667
|
-
if (oldNode.
|
|
597
|
+
if (oldNode.nodeType === 3 || oldNode.nodeType === 8) {
|
|
598
|
+
if (oldNode.nodeValue !== newNode.nodeValue)
|
|
668
599
|
return false;
|
|
669
600
|
}
|
|
670
601
|
return true;
|
|
@@ -761,6 +692,8 @@ function areNodeAndChildMatching(domNode, child) {
|
|
|
761
692
|
if (typeof child === "string" || typeof child === "number" || typeof child === "boolean") {
|
|
762
693
|
return domNode.nodeType === 3;
|
|
763
694
|
}
|
|
695
|
+
if (isCommentVNode(child))
|
|
696
|
+
return domNode.nodeType === 8;
|
|
764
697
|
if (child && typeof child === "object") {
|
|
765
698
|
if (domNode.nodeType !== 1)
|
|
766
699
|
return false;
|
|
@@ -854,6 +787,11 @@ function patchElementInPlace(el, vnode, globals, mergeAttributes = false) {
|
|
|
854
787
|
return;
|
|
855
788
|
morphDomDirect(el, vnode.children ?? [], globals);
|
|
856
789
|
}
|
|
790
|
+
function replaceNode(old, next) {
|
|
791
|
+
if (old.nodeType === 1)
|
|
792
|
+
clearDelegatedEventsDeep(old);
|
|
793
|
+
old.parentNode?.replaceChild(next, old);
|
|
794
|
+
}
|
|
857
795
|
function morphNode(domNode, child, globals, mergeAttributes = false) {
|
|
858
796
|
if (typeof child === "string" || typeof child === "number" || typeof child === "boolean") {
|
|
859
797
|
const text = String(child);
|
|
@@ -863,7 +801,18 @@ function morphNode(domNode, child, globals, mergeAttributes = false) {
|
|
|
863
801
|
return domNode;
|
|
864
802
|
}
|
|
865
803
|
const next = globals.window.document.createTextNode(text);
|
|
866
|
-
domNode
|
|
804
|
+
replaceNode(domNode, next);
|
|
805
|
+
return next;
|
|
806
|
+
}
|
|
807
|
+
if (isCommentVNode(child)) {
|
|
808
|
+
const data = child.value ?? "";
|
|
809
|
+
if (domNode.nodeType === 8) {
|
|
810
|
+
if (domNode.nodeValue !== data)
|
|
811
|
+
domNode.nodeValue = data;
|
|
812
|
+
return domNode;
|
|
813
|
+
}
|
|
814
|
+
const next = globals.window.document.createComment(data);
|
|
815
|
+
replaceNode(domNode, next);
|
|
867
816
|
return next;
|
|
868
817
|
}
|
|
869
818
|
if (child && typeof child === "object") {
|
|
@@ -887,13 +836,15 @@ function morphNode(domNode, child, globals, mergeAttributes = false) {
|
|
|
887
836
|
const first = Array.isArray(created) ? created[0] : created;
|
|
888
837
|
if (!first)
|
|
889
838
|
return null;
|
|
890
|
-
el
|
|
839
|
+
replaceNode(el, first);
|
|
891
840
|
handleLifecycleEventsForOnMount(first);
|
|
892
841
|
return first;
|
|
893
842
|
}
|
|
894
843
|
patchElementInPlace(el, child, globals, mergeAttributes);
|
|
895
844
|
return el;
|
|
896
845
|
}
|
|
846
|
+
if (domNode.nodeType === 1)
|
|
847
|
+
clearDelegatedEventsDeep(domNode);
|
|
897
848
|
domNode.parentNode?.removeChild(domNode);
|
|
898
849
|
return null;
|
|
899
850
|
}
|
|
@@ -954,6 +905,8 @@ function morphDiff(targetRoot, patchItems, globals) {
|
|
|
954
905
|
for (const item of patchItems) {
|
|
955
906
|
if (typeof item === "string" && item.trim() === "")
|
|
956
907
|
continue;
|
|
908
|
+
if (isCommentVNode(item))
|
|
909
|
+
continue;
|
|
957
910
|
const key = getVNodeMatchKey(item);
|
|
958
911
|
if (!key) {
|
|
959
912
|
throw new Error(`morph diff: patch items must be elements with a key or id attribute (got ${describePatchItem(item)})`);
|
|
@@ -1263,7 +1216,8 @@ var morph2 = (el, newContent, options = {}) => {
|
|
|
1263
1216
|
// src/global.ts
|
|
1264
1217
|
if (typeof globalThis !== "undefined") {
|
|
1265
1218
|
const g = globalThis;
|
|
1266
|
-
|
|
1219
|
+
const current = g.df$;
|
|
1220
|
+
if (current === null || typeof current !== "object" && typeof current !== "function") {
|
|
1267
1221
|
g.df$ = {};
|
|
1268
1222
|
}
|
|
1269
1223
|
Object.assign(g.df$, exports_src);
|
|
@@ -1280,12 +1234,14 @@ export {
|
|
|
1280
1234
|
performTransition,
|
|
1281
1235
|
parseEventPropName,
|
|
1282
1236
|
parseDOM,
|
|
1237
|
+
onClearDelegatedEvents,
|
|
1283
1238
|
observeUnmount,
|
|
1284
1239
|
nsMap,
|
|
1285
1240
|
morph2 as morph,
|
|
1286
1241
|
isSVG,
|
|
1287
1242
|
isMarkup,
|
|
1288
1243
|
isHTML,
|
|
1244
|
+
isCommentVNode,
|
|
1289
1245
|
htmlStringToVNodes,
|
|
1290
1246
|
handleLifecycleEventsForOnMount,
|
|
1291
1247
|
getTransitionStyles,
|
|
@@ -1304,9 +1260,10 @@ export {
|
|
|
1304
1260
|
FROM_DOM_MARKER,
|
|
1305
1261
|
DEFAULT_TRANSITION_CONFIG,
|
|
1306
1262
|
DANGEROUSLY_SET_INNER_HTML_ATTRIBUTE,
|
|
1263
|
+
COMMENT_TYPE,
|
|
1307
1264
|
CLASS_ATTRIBUTE_NAME,
|
|
1308
1265
|
CAPTURE_ONLY_EVENTS
|
|
1309
1266
|
};
|
|
1310
1267
|
|
|
1311
|
-
//# debugId=
|
|
1268
|
+
//# debugId=2B212A8922F1193364756E2164756E21
|
|
1312
1269
|
//# sourceMappingURL=all.js.map
|