kerfjs 4.1.1 → 4.2.0-beta.2
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 +11 -0
- package/dist/actions.d.ts +72 -0
- package/dist/actions.js +26 -0
- package/dist/actions.js.map +1 -0
- package/dist/array-signal.js +3 -104
- package/dist/array-signal.js.map +1 -1
- package/dist/async.d.ts +59 -0
- package/dist/async.js +55 -0
- package/dist/async.js.map +1 -0
- package/dist/attrSelector-Cmu2ZoGO.d.ts +79 -0
- package/dist/chunk-4MY2656S.js +1395 -0
- package/dist/chunk-4MY2656S.js.map +1 -0
- package/dist/{chunk-JXAR5J54.js → chunk-FSAQR6IU.js} +7 -4
- package/dist/chunk-FSAQR6IU.js.map +1 -0
- package/dist/chunk-KEZTD6H4.js +54 -0
- package/dist/chunk-KEZTD6H4.js.map +1 -0
- package/dist/chunk-MRYM3O3V.js +106 -0
- package/dist/chunk-MRYM3O3V.js.map +1 -0
- package/dist/chunk-U32TFTGZ.js +74 -0
- package/dist/chunk-U32TFTGZ.js.map +1 -0
- package/dist/delegate-CL9VTZFb.d.ts +93 -0
- package/dist/html.js +2 -2
- package/dist/imperative.d.ts +34 -0
- package/dist/imperative.js +20 -0
- package/dist/imperative.js.map +1 -0
- package/dist/index.d.ts +21 -219
- package/dist/index.js +13 -1511
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime.d.ts +13 -1
- package/dist/jsx-runtime.js +2 -2
- package/dist/list.d.ts +39 -0
- package/dist/list.js +146 -0
- package/dist/list.js.map +1 -0
- package/dist/mount-Bo2qOx25.d.ts +57 -0
- package/dist/overlay.d.ts +204 -0
- package/dist/overlay.js +406 -0
- package/dist/overlay.js.map +1 -0
- package/dist/remount.d.ts +52 -0
- package/dist/remount.js +45 -0
- package/dist/remount.js.map +1 -0
- package/dist/scope.d.ts +67 -0
- package/dist/scope.js +71 -0
- package/dist/scope.js.map +1 -0
- package/dist/timing.d.ts +65 -0
- package/dist/timing.js +80 -0
- package/dist/timing.js.map +1 -0
- package/package.json +34 -1
- package/dist/chunk-JXAR5J54.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,1516 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
import { parseRowTemplate, rowContractError, parseSingleRow, collectTemplateChildren } from './chunk-YHH7OUFA.js';
|
|
3
|
-
import { captureRowBindings, listSafeHtml, boundTextNodeOf, syncFormProp, newBindingContext, wireBindings, disposeRowBindings, isSafeHtml, wireRowBindings, _setBindingContext, TEXT_MARKER_PREFIX, ROW_TEXT_PREFIX, carryOrRewireRowBindings, granularListSafeHtml } from './chunk-JXAR5J54.js';
|
|
4
|
-
export { Fragment, SafeHtml, isSafeHtml, raw } from './chunk-JXAR5J54.js';
|
|
5
|
-
import { LIST_MARKER_PREFIX, flattenWithoutListItems, collectLists, flatten } from './chunk-GY4XV2UV.js';
|
|
1
|
+
export { each, morph, mount } from './chunk-4MY2656S.js';
|
|
6
2
|
export { defineStore, resetAllStores } from './chunk-SAYPJ6XR.js';
|
|
7
|
-
|
|
3
|
+
export { attr } from './chunk-U32TFTGZ.js';
|
|
4
|
+
export { delegate, delegateCapture } from './chunk-KEZTD6H4.js';
|
|
5
|
+
import './chunk-QIP723L4.js';
|
|
6
|
+
import './chunk-YHH7OUFA.js';
|
|
7
|
+
export { Fragment, SafeHtml, isSafeHtml, raw, trustedRaw } from './chunk-FSAQR6IU.js';
|
|
8
8
|
export { batch, computed, effect, signal } from './chunk-3APBEVHF.js';
|
|
9
|
-
import
|
|
9
|
+
import './chunk-GY4XV2UV.js';
|
|
10
|
+
import './chunk-VVDJLWMP.js';
|
|
10
11
|
|
|
11
|
-
// src/
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
const str = String(value);
|
|
17
|
-
let result = "";
|
|
18
|
-
for (let i = 0; i < str.length; i++) {
|
|
19
|
-
const cp = str.charCodeAt(i);
|
|
20
|
-
const ch = str.charAt(i);
|
|
21
|
-
if (cp === 0) {
|
|
22
|
-
result += "\uFFFD";
|
|
23
|
-
continue;
|
|
24
|
-
}
|
|
25
|
-
if (cp >= 1 && cp <= 31 || cp === 127) {
|
|
26
|
-
result += "\\" + cp.toString(16) + " ";
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
if (i === 0 && cp >= 48 && cp <= 57) {
|
|
30
|
-
result += "\\" + cp.toString(16) + " ";
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
if (i === 1 && cp >= 48 && cp <= 57 && str.charCodeAt(0) === 45) {
|
|
34
|
-
result += "\\" + cp.toString(16) + " ";
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
if (cp >= 128 || cp === 45 || // `-`
|
|
38
|
-
cp === 95 || // `_`
|
|
39
|
-
cp >= 48 && cp <= 57 || // 0-9
|
|
40
|
-
cp >= 65 && cp <= 90 || // A-Z
|
|
41
|
-
cp >= 97 && cp <= 122) {
|
|
42
|
-
result += ch;
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
result += "\\" + ch;
|
|
46
|
-
}
|
|
47
|
-
return result;
|
|
48
|
-
}
|
|
49
|
-
function escapeCSSString(value) {
|
|
50
|
-
let result = "";
|
|
51
|
-
for (let i = 0; i < value.length; i++) {
|
|
52
|
-
const cp = value.charCodeAt(i);
|
|
53
|
-
const ch = value.charAt(i);
|
|
54
|
-
if (cp === 0) {
|
|
55
|
-
result += "\uFFFD";
|
|
56
|
-
} else if (cp >= 1 && cp <= 31 || cp === 127) {
|
|
57
|
-
result += "\\" + cp.toString(16) + " ";
|
|
58
|
-
} else if (cp === 92) {
|
|
59
|
-
result += "\\\\";
|
|
60
|
-
} else if (cp === 34) {
|
|
61
|
-
result += '\\"';
|
|
62
|
-
} else {
|
|
63
|
-
result += ch;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return result;
|
|
67
|
-
}
|
|
68
|
-
function attr(name, value) {
|
|
69
|
-
const escapedName = cssEscapeIdent(name);
|
|
70
|
-
if (value !== void 0) {
|
|
71
|
-
const selector = `[${escapedName}="${escapeCSSString(value)}"]`;
|
|
72
|
-
return Object.freeze({
|
|
73
|
-
name,
|
|
74
|
-
value,
|
|
75
|
-
selector,
|
|
76
|
-
attrs: Object.freeze({ [name]: value })
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
return (v) => Object.freeze({ [name]: v });
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// src/delegate.ts
|
|
83
|
-
var NON_BUBBLING = /* @__PURE__ */ new Set([
|
|
84
|
-
"focus",
|
|
85
|
-
"blur",
|
|
86
|
-
"scroll",
|
|
87
|
-
"load",
|
|
88
|
-
"error",
|
|
89
|
-
"mouseenter",
|
|
90
|
-
"mouseleave"
|
|
91
|
-
]);
|
|
92
|
-
function assertValidSelector(selector, fn) {
|
|
93
|
-
try {
|
|
94
|
-
document.createElement("div").matches(selector);
|
|
95
|
-
} catch {
|
|
96
|
-
throw new Error(
|
|
97
|
-
`${fn}: invalid selector "${selector}". Pass a valid CSS selector (e.g. '[data-action="add"]', '.btn', 'input').`
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
function makeListener(rootEl, selector, handler, match) {
|
|
102
|
-
return (event) => {
|
|
103
|
-
const target = event.target;
|
|
104
|
-
if (!(target instanceof Element)) return;
|
|
105
|
-
const matched = match === "direct" ? target.matches(selector) ? target : null : target.closest(selector);
|
|
106
|
-
if (matched !== null && rootEl.contains(matched)) {
|
|
107
|
-
handler(event, matched);
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
function delegate(rootEl, type, selector, handler, options) {
|
|
112
|
-
assertValidSelector(selector, "delegate");
|
|
113
|
-
devHooks.delegateInEffect?.("delegate");
|
|
114
|
-
const listener = makeListener(rootEl, selector, handler, options?.match ?? "closest");
|
|
115
|
-
const capture = NON_BUBBLING.has(type);
|
|
116
|
-
rootEl.addEventListener(type, listener, capture);
|
|
117
|
-
return () => {
|
|
118
|
-
rootEl.removeEventListener(type, listener, capture);
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
function delegateCapture(rootEl, type, selector, handler, options) {
|
|
122
|
-
assertValidSelector(selector, "delegateCapture");
|
|
123
|
-
devHooks.delegateInEffect?.("delegateCapture");
|
|
124
|
-
const listener = makeListener(rootEl, selector, handler, options?.match ?? "closest");
|
|
125
|
-
rootEl.addEventListener(type, listener, true);
|
|
126
|
-
return () => {
|
|
127
|
-
rootEl.removeEventListener(type, listener, true);
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// src/list-render-state.ts
|
|
132
|
-
function deriveListRenderState(bindingCount) {
|
|
133
|
-
if (bindingCount === void 0) return "unbound";
|
|
134
|
-
return bindingCount === 0 ? "empty" : "bound";
|
|
135
|
-
}
|
|
136
|
-
function decideListPath(state, patches, snapshotLength, previousBindingCount) {
|
|
137
|
-
if (state === "unbound") return { path: "snapshot", reason: "first-render" };
|
|
138
|
-
if (state === "empty") return { path: "snapshot", reason: "empty-binding" };
|
|
139
|
-
if (patches.length === 0) return { path: "snapshot", reason: "no-patches" };
|
|
140
|
-
let netDelta = 0;
|
|
141
|
-
for (const p of patches) {
|
|
142
|
-
if (p.type === "insert") netDelta += 1;
|
|
143
|
-
else if (p.type === "remove") netDelta -= 1;
|
|
144
|
-
else if (p.type === "replace") return { path: "snapshot", reason: "replace" };
|
|
145
|
-
}
|
|
146
|
-
const count = previousBindingCount ?? 0;
|
|
147
|
-
if (count + netDelta !== snapshotLength) {
|
|
148
|
-
return { path: "snapshot", reason: "count-drift" };
|
|
149
|
-
}
|
|
150
|
-
return { path: "granular" };
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// src/each.ts
|
|
154
|
-
var ARRAY_SIGNAL_BRAND = /* @__PURE__ */ Symbol.for("kerfjs.ArraySignal");
|
|
155
|
-
function isArraySignal(value) {
|
|
156
|
-
return typeof value === "object" && value !== null && value[ARRAY_SIGNAL_BRAND] === true;
|
|
157
|
-
}
|
|
158
|
-
var context = null;
|
|
159
|
-
var renderingRow = false;
|
|
160
|
-
function inRowScope(fn) {
|
|
161
|
-
const prev = renderingRow;
|
|
162
|
-
renderingRow = true;
|
|
163
|
-
try {
|
|
164
|
-
return fn();
|
|
165
|
-
} finally {
|
|
166
|
-
renderingRow = prev;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
function _setRenderContext(c) {
|
|
170
|
-
context = c;
|
|
171
|
-
}
|
|
172
|
-
function _resetCallOrderListState(ctx) {
|
|
173
|
-
const isCallOrderId = (id) => !id.startsWith("k:");
|
|
174
|
-
for (const map of [ctx.caches, ctx.bindingCounts, ctx.bindingSources]) {
|
|
175
|
-
for (const id of Array.from(map.keys())) {
|
|
176
|
-
if (isCallOrderId(id)) map.delete(id);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
function isEachOptions(v) {
|
|
181
|
-
return typeof v === "object" && v !== null;
|
|
182
|
-
}
|
|
183
|
-
var VALID_KEY = /^[A-Za-z0-9_.:/-]+$/;
|
|
184
|
-
function assertValidKey(key) {
|
|
185
|
-
if (typeof key !== "string" || !VALID_KEY.test(key) || key.includes("--")) {
|
|
186
|
-
throw new Error(
|
|
187
|
-
`each(): invalid list key ${JSON.stringify(key)}. A key must be a non-empty string of letters, digits, or _ . : / - (and may not contain "--"), because kerf writes it into the list's marker comment in the DOM. Use a short stable identifier, e.g. { key: 'results' }.`
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
function claimKey(ctx, key) {
|
|
192
|
-
assertValidKey(key);
|
|
193
|
-
if (renderingRow) {
|
|
194
|
-
throw new Error(
|
|
195
|
-
`each(): list key ${JSON.stringify(key)} was used by an each() inside a row render. A nested each() is not reconciled \u2014 the row is flattened to HTML, so the inner list never binds and would render as static markup. Render the inner collection with plain .map() (it re-renders with its row), or restructure to a flat list.`
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
if (ctx.keysThisRender.has(key)) {
|
|
199
|
-
throw new Error(
|
|
200
|
-
`each(): duplicate list key ${JSON.stringify(key)}. Every keyed each() in a mount must have its own key \u2014 two lists sharing one would share the same cache, binding and DOM anchor. Give each list a distinct key.`
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
ctx.keysThisRender.add(key);
|
|
204
|
-
return `k:${key}`;
|
|
205
|
-
}
|
|
206
|
-
function each(items, render, cacheKeyOrOptions) {
|
|
207
|
-
const useOptions = isEachOptions(cacheKeyOrOptions);
|
|
208
|
-
const cacheKey = useOptions ? cacheKeyOrOptions.cacheKey : cacheKeyOrOptions;
|
|
209
|
-
const listKey = useOptions ? cacheKeyOrOptions.key : void 0;
|
|
210
|
-
if (isArraySignal(items) && context !== null) {
|
|
211
|
-
return eachGranular(items, render, cacheKey, listKey);
|
|
212
|
-
}
|
|
213
|
-
const snapshotItems = isArraySignal(items) ? items.value : items;
|
|
214
|
-
return eachSnapshot(snapshotItems, render, cacheKey, listKey);
|
|
215
|
-
}
|
|
216
|
-
function eachSnapshot(items, render, cacheKey, listKey) {
|
|
217
|
-
let id;
|
|
218
|
-
if (context !== null) {
|
|
219
|
-
id = listKey !== void 0 ? claimKey(context, listKey) : String(context.counter++);
|
|
220
|
-
} else {
|
|
221
|
-
id = "orphan";
|
|
222
|
-
}
|
|
223
|
-
return eachSnapshotById(items, render, cacheKey, id);
|
|
224
|
-
}
|
|
225
|
-
function assertObjectItem(item, index) {
|
|
226
|
-
if (typeof item !== "object" || item === null) {
|
|
227
|
-
throw new Error(
|
|
228
|
-
`each(): items must be objects (the per-item HTML cache is a WeakMap), got ${item === null ? "null" : typeof item} at index ${index}. Wrap primitives if you need to iterate them, e.g. items.map(v => ({ v })).`
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
function eachGranular(sig, render, cacheKey, listKey) {
|
|
233
|
-
const ctx = context;
|
|
234
|
-
const id = listKey !== void 0 ? claimKey(ctx, listKey) : String(ctx.counter++);
|
|
235
|
-
const previousBindingCount = ctx.bindingCounts.get(id);
|
|
236
|
-
const patches = sig._consumePatches();
|
|
237
|
-
const snapshot = sig.value;
|
|
238
|
-
const previousSource = ctx.bindingSources.get(id);
|
|
239
|
-
const sourceReused = ctx.bindingSources.has(id) && previousSource !== sig;
|
|
240
|
-
if (sourceReused && listKey === void 0) ctx.shiftCandidates.push(id);
|
|
241
|
-
const decision = sourceReused ? { path: "snapshot" } : decideListPath(
|
|
242
|
-
deriveListRenderState(previousBindingCount),
|
|
243
|
-
patches,
|
|
244
|
-
snapshot.length,
|
|
245
|
-
previousBindingCount
|
|
246
|
-
);
|
|
247
|
-
if (decision.path === "snapshot") {
|
|
248
|
-
return eachSnapshotById(snapshot, render, cacheKey, id, sig);
|
|
249
|
-
}
|
|
250
|
-
let staleIndexShift = false;
|
|
251
|
-
if (render.length >= 2 && devHooks.staleIndexEnabled?.() === true) {
|
|
252
|
-
const rendered = [];
|
|
253
|
-
for (let i = 0; i < previousBindingCount; i++) rendered.push(i);
|
|
254
|
-
for (const p of patches) {
|
|
255
|
-
if (p.type === "insert") rendered.splice(p.index, 0, p.index);
|
|
256
|
-
else if (p.type === "remove") rendered.splice(p.index, 1);
|
|
257
|
-
else if (p.type === "move") {
|
|
258
|
-
const [moved] = rendered.splice(p.from, 1);
|
|
259
|
-
rendered.splice(p.to, 0, moved);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
for (let i = 0; i < rendered.length; i++) {
|
|
263
|
-
if (rendered[i] !== i) {
|
|
264
|
-
staleIndexShift = true;
|
|
265
|
-
break;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
if (cacheKey !== void 0) {
|
|
270
|
-
const cache2 = ctx.caches.get(id);
|
|
271
|
-
for (let i = 0; i < snapshot.length; i++) {
|
|
272
|
-
const item = snapshot[i];
|
|
273
|
-
const k = cacheKey(item, i);
|
|
274
|
-
const cached = cache2.get(item);
|
|
275
|
-
if (cached !== void 0 && cached.cacheKey !== k) {
|
|
276
|
-
return eachSnapshotById(snapshot, render, cacheKey, id, sig);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
const renderRow = (item, index) => captureRowBindings(() => inRowScope(() => {
|
|
281
|
-
const out = render(item, index);
|
|
282
|
-
return isSafeHtml(out) ? out.toString() : out;
|
|
283
|
-
}));
|
|
284
|
-
const internalPatches = new Array(patches.length);
|
|
285
|
-
const cache = ctx.caches.get(id);
|
|
286
|
-
try {
|
|
287
|
-
for (let i = 0; i < patches.length; i++) {
|
|
288
|
-
const p = patches[i];
|
|
289
|
-
if (p.type === "insert" || p.type === "update") {
|
|
290
|
-
assertObjectItem(p.item, p.index);
|
|
291
|
-
const { html, bindings } = renderRow(p.item, p.index);
|
|
292
|
-
internalPatches[i] = {
|
|
293
|
-
type: p.type,
|
|
294
|
-
index: p.index,
|
|
295
|
-
item: p.item,
|
|
296
|
-
html,
|
|
297
|
-
bindings
|
|
298
|
-
};
|
|
299
|
-
cache?.set(p.item, {
|
|
300
|
-
cacheKey: cacheKey ? cacheKey(p.item, p.index) : void 0,
|
|
301
|
-
html,
|
|
302
|
-
bindings,
|
|
303
|
-
version: itemVersion(p.item),
|
|
304
|
-
index: p.index
|
|
305
|
-
});
|
|
306
|
-
} else {
|
|
307
|
-
internalPatches[i] = p;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
} catch {
|
|
311
|
-
ctx.bindingCounts.delete(id);
|
|
312
|
-
return eachSnapshotById(snapshot, render, cacheKey, id, sig);
|
|
313
|
-
}
|
|
314
|
-
if (staleIndexShift) devHooks.staleIndex?.(id);
|
|
315
|
-
return granularListSafeHtml(id, [], internalPatches, sig);
|
|
316
|
-
}
|
|
317
|
-
function eachSnapshotById(items, render, cacheKey, id, source) {
|
|
318
|
-
let cache = null;
|
|
319
|
-
if (context !== null) {
|
|
320
|
-
let c = context.caches.get(id);
|
|
321
|
-
if (c === void 0) {
|
|
322
|
-
c = /* @__PURE__ */ new WeakMap();
|
|
323
|
-
context.caches.set(id, c);
|
|
324
|
-
}
|
|
325
|
-
cache = c;
|
|
326
|
-
}
|
|
327
|
-
const segItems = new Array(items.length);
|
|
328
|
-
const seen = /* @__PURE__ */ new Set();
|
|
329
|
-
for (let i = 0; i < items.length; i++) {
|
|
330
|
-
const item = items[i];
|
|
331
|
-
assertObjectItem(item, i);
|
|
332
|
-
if (seen.has(item)) {
|
|
333
|
-
throw new Error(
|
|
334
|
-
`each(): the same object reference appears at multiple indices in items (first seen earlier, again at index ${i}). The per-item HTML cache is keyed on object identity, so duplicate references break the keyed reconciler and can leak DOM nodes on re-render. Use a fresh object per row (e.g. items.map(o => ({ ...o })) before passing to each()).`
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
seen.add(item);
|
|
338
|
-
const k = cacheKey ? cacheKey(item, i) : void 0;
|
|
339
|
-
const version = itemVersion(item);
|
|
340
|
-
let html;
|
|
341
|
-
let bindings;
|
|
342
|
-
const cached = cache !== null ? cache.get(item) : void 0;
|
|
343
|
-
if (cached !== void 0 && cached.cacheKey === k && cached.version === version) {
|
|
344
|
-
html = cached.html;
|
|
345
|
-
bindings = cached.bindings;
|
|
346
|
-
if (cached.index !== i && render.length >= 2 && devHooks.staleIndexEnabled?.() === true) {
|
|
347
|
-
devHooks.staleIndex?.(id);
|
|
348
|
-
}
|
|
349
|
-
} else {
|
|
350
|
-
const captured = captureRowBindings(() => inRowScope(() => {
|
|
351
|
-
const out = render(item, i);
|
|
352
|
-
return isSafeHtml(out) ? out.toString() : out;
|
|
353
|
-
}));
|
|
354
|
-
html = captured.html;
|
|
355
|
-
bindings = captured.bindings;
|
|
356
|
-
if (cache !== null) cache.set(item, { cacheKey: k, html, bindings, version, index: i });
|
|
357
|
-
}
|
|
358
|
-
segItems[i] = { ref: item, cacheKey: k, html, bindings };
|
|
359
|
-
}
|
|
360
|
-
if (cacheKey !== void 0) {
|
|
361
|
-
devHooks.duplicateCacheKeys?.(id, segItems);
|
|
362
|
-
}
|
|
363
|
-
return listSafeHtml(id, segItems, source);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// src/list-reconcile-focus.ts
|
|
367
|
-
function captureFocus(liveParent) {
|
|
368
|
-
const active = document.activeElement;
|
|
369
|
-
if (active === null || active === document.body) return null;
|
|
370
|
-
if (!liveParent.contains(active)) return null;
|
|
371
|
-
const el = active;
|
|
372
|
-
let selStart = null;
|
|
373
|
-
let selEnd = null;
|
|
374
|
-
if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
|
|
375
|
-
try {
|
|
376
|
-
selStart = el.selectionStart;
|
|
377
|
-
selEnd = el.selectionEnd;
|
|
378
|
-
} catch {
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
return { el, selStart, selEnd };
|
|
382
|
-
}
|
|
383
|
-
function restoreFocus(snap) {
|
|
384
|
-
if (document.activeElement === snap.el) return;
|
|
385
|
-
if (!snap.el.isConnected) return;
|
|
386
|
-
snap.el.focus();
|
|
387
|
-
if (snap.selStart !== null && snap.selEnd !== null) {
|
|
388
|
-
try {
|
|
389
|
-
snap.el.setSelectionRange(snap.selStart, snap.selEnd);
|
|
390
|
-
} catch {
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// src/morph.ts
|
|
396
|
-
var ID_KEY_PREFIX = "id:";
|
|
397
|
-
var DATA_KEY_PREFIX = "data-key:";
|
|
398
|
-
var ELEMENT_NODE = 1;
|
|
399
|
-
var TEXT_NODE = 3;
|
|
400
|
-
var COMMENT_NODE = 8;
|
|
401
|
-
function getNodeKey(node) {
|
|
402
|
-
if (node.nodeType !== ELEMENT_NODE) return void 0;
|
|
403
|
-
const el = node;
|
|
404
|
-
if (el.id !== "") return `${ID_KEY_PREFIX}${el.id}`;
|
|
405
|
-
if (el.dataset !== void 0 && el.dataset.key !== void 0) {
|
|
406
|
-
return `${DATA_KEY_PREFIX}${el.dataset.key}`;
|
|
407
|
-
}
|
|
408
|
-
return void 0;
|
|
409
|
-
}
|
|
410
|
-
var EMPTY_OWNED = /* @__PURE__ */ new Set();
|
|
411
|
-
function morph(liveRoot, template, ownedItems = EMPTY_OWNED) {
|
|
412
|
-
if (liveRoot == null) {
|
|
413
|
-
throw new Error(
|
|
414
|
-
'morph: liveRoot is null/undefined \u2014 pass the live element, e.g. morph(document.getElementById("app")!, template). A common cause is a typo in the id or selector that returns null at runtime even though the TypeScript types say Element.'
|
|
415
|
-
);
|
|
416
|
-
}
|
|
417
|
-
const templateEl = isElementNode(template) ? template : parseTemplate(liveRoot, template);
|
|
418
|
-
const focusSnap = captureFocus(liveRoot);
|
|
419
|
-
morphChildren(liveRoot, templateEl, ownedItems);
|
|
420
|
-
if (focusSnap !== null) restoreFocus(focusSnap);
|
|
421
|
-
}
|
|
422
|
-
function _morphElement(fromEl, toEl, ownedItems = EMPTY_OWNED) {
|
|
423
|
-
morphElement(fromEl, toEl, ownedItems);
|
|
424
|
-
}
|
|
425
|
-
function isElementNode(t) {
|
|
426
|
-
return typeof t === "object" && t !== null && t.nodeType === ELEMENT_NODE;
|
|
427
|
-
}
|
|
428
|
-
function parseTemplate(liveRoot, template) {
|
|
429
|
-
const el = liveRoot.cloneNode(false);
|
|
430
|
-
el.innerHTML = String(template);
|
|
431
|
-
return el;
|
|
432
|
-
}
|
|
433
|
-
function protectionTag(node) {
|
|
434
|
-
const { dataset } = node;
|
|
435
|
-
return (dataset.morphSkip !== void 0 ? "s" : "") + (dataset.morphSkipChildren !== void 0 ? "c" : "") + (dataset.morphPreserve !== void 0 ? "p" : "");
|
|
436
|
-
}
|
|
437
|
-
var MARKER_PREFIXES = [LIST_MARKER_PREFIX, TEXT_MARKER_PREFIX, ROW_TEXT_PREFIX];
|
|
438
|
-
function isMarker(node) {
|
|
439
|
-
if (node.nodeType !== COMMENT_NODE) return false;
|
|
440
|
-
const { data } = node;
|
|
441
|
-
return MARKER_PREFIXES.some((prefix) => data.startsWith(prefix));
|
|
442
|
-
}
|
|
443
|
-
function markersPairable(a, b) {
|
|
444
|
-
if (!isMarker(a) && !isMarker(b)) return true;
|
|
445
|
-
return a.data === b.data;
|
|
446
|
-
}
|
|
447
|
-
function skipOwned(node, ownedItems) {
|
|
448
|
-
while (node !== null && node.nodeType === ELEMENT_NODE && ownedItems.has(node)) {
|
|
449
|
-
node = node.nextSibling;
|
|
450
|
-
}
|
|
451
|
-
return node;
|
|
452
|
-
}
|
|
453
|
-
function isListMarker(node) {
|
|
454
|
-
return node.nodeType === COMMENT_NODE && node.data.startsWith(LIST_MARKER_PREFIX);
|
|
455
|
-
}
|
|
456
|
-
function afterListRegion(marker, ownedItems) {
|
|
457
|
-
let last = marker;
|
|
458
|
-
for (let r = marker.nextSibling; r !== null; r = r.nextSibling) {
|
|
459
|
-
if (isListMarker(r)) break;
|
|
460
|
-
if (r.nodeType === ELEMENT_NODE && ownedItems.has(r)) last = r;
|
|
461
|
-
}
|
|
462
|
-
return last.nextSibling;
|
|
463
|
-
}
|
|
464
|
-
function morphChildren(fromParent, toParent, ownedItems) {
|
|
465
|
-
const keyed = /* @__PURE__ */ new Map();
|
|
466
|
-
for (let c = fromParent.firstChild; c !== null; c = c.nextSibling) {
|
|
467
|
-
if (c.nodeType === ELEMENT_NODE && ownedItems.has(c)) continue;
|
|
468
|
-
const k = getNodeKey(c);
|
|
469
|
-
if (k !== void 0) keyed.set(k, c);
|
|
470
|
-
}
|
|
471
|
-
let fromChild = skipOwned(fromParent.firstChild, ownedItems);
|
|
472
|
-
let toChild = toParent.firstChild;
|
|
473
|
-
while (toChild !== null) {
|
|
474
|
-
const toNext = toChild.nextSibling;
|
|
475
|
-
let matched = null;
|
|
476
|
-
const toKey = getNodeKey(toChild);
|
|
477
|
-
if (toKey !== void 0 && keyed.has(toKey)) {
|
|
478
|
-
matched = keyed.get(toKey);
|
|
479
|
-
keyed.delete(toKey);
|
|
480
|
-
if (matched !== fromChild) {
|
|
481
|
-
fromParent.insertBefore(matched, fromChild);
|
|
482
|
-
} else {
|
|
483
|
-
fromChild = skipOwned(fromChild.nextSibling, ownedItems);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
if (matched === null && fromChild !== null && fromChild.nodeType === toChild.nodeType && markersPairable(fromChild, toChild) && (toChild.nodeType !== ELEMENT_NODE || fromChild.tagName === toChild.tagName && getNodeKey(fromChild) === void 0 && toKey === void 0 && protectionTag(fromChild) === protectionTag(toChild))) {
|
|
487
|
-
matched = fromChild;
|
|
488
|
-
fromChild = skipOwned(
|
|
489
|
-
isListMarker(matched) ? afterListRegion(matched, ownedItems) : fromChild.nextSibling,
|
|
490
|
-
ownedItems
|
|
491
|
-
);
|
|
492
|
-
if (matched.nodeType === COMMENT_NODE && fromChild !== null) {
|
|
493
|
-
const owned = boundTextNodeOf(matched);
|
|
494
|
-
if (owned !== null && fromChild === owned) {
|
|
495
|
-
fromChild = skipOwned(owned.nextSibling, ownedItems);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
if (matched === null && toChild.nodeType === ELEMENT_NODE && fromChild !== null && toKey === void 0) {
|
|
500
|
-
const toTag = toChild.tagName;
|
|
501
|
-
for (let scan = fromChild.nextSibling; scan !== null; scan = scan.nextSibling) {
|
|
502
|
-
if (scan.nodeType !== ELEMENT_NODE) continue;
|
|
503
|
-
const el = scan;
|
|
504
|
-
if (ownedItems.has(el)) continue;
|
|
505
|
-
if (el.tagName !== toTag || getNodeKey(el) !== void 0) continue;
|
|
506
|
-
if (protectionTag(el) !== protectionTag(toChild)) continue;
|
|
507
|
-
matched = el;
|
|
508
|
-
fromParent.insertBefore(el, fromChild);
|
|
509
|
-
break;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
if (matched === null && fromChild !== null && toChild.nodeType === COMMENT_NODE && toChild.data.startsWith(LIST_MARKER_PREFIX)) {
|
|
513
|
-
const wantData = toChild.data;
|
|
514
|
-
for (let scan = fromChild.nextSibling; scan !== null; scan = scan.nextSibling) {
|
|
515
|
-
if (scan.nodeType !== COMMENT_NODE || scan.data !== wantData) continue;
|
|
516
|
-
const regionEnd = afterListRegion(scan, ownedItems);
|
|
517
|
-
const run = [];
|
|
518
|
-
for (let r = scan; r !== null && r !== regionEnd; r = r.nextSibling) {
|
|
519
|
-
run.push(r);
|
|
520
|
-
}
|
|
521
|
-
const focusSnap = captureFocus(fromParent);
|
|
522
|
-
for (const node of run) fromParent.insertBefore(node, fromChild);
|
|
523
|
-
if (focusSnap !== null) restoreFocus(focusSnap);
|
|
524
|
-
matched = scan;
|
|
525
|
-
break;
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
if (matched !== null) {
|
|
529
|
-
morphNode(matched, toChild, ownedItems);
|
|
530
|
-
} else {
|
|
531
|
-
const cloned = toChild.cloneNode(true);
|
|
532
|
-
fromParent.insertBefore(cloned, fromChild);
|
|
533
|
-
}
|
|
534
|
-
toChild = toNext;
|
|
535
|
-
}
|
|
536
|
-
while (fromChild !== null) {
|
|
537
|
-
const next = fromChild.nextSibling;
|
|
538
|
-
if (fromChild.nodeType === ELEMENT_NODE) {
|
|
539
|
-
const el = fromChild;
|
|
540
|
-
if (!ownedItems.has(el) && el.dataset.morphPreserve === void 0) {
|
|
541
|
-
fromParent.removeChild(fromChild);
|
|
542
|
-
}
|
|
543
|
-
} else {
|
|
544
|
-
fromParent.removeChild(fromChild);
|
|
545
|
-
}
|
|
546
|
-
fromChild = next;
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
function morphNode(fromNode, toNode, ownedItems) {
|
|
550
|
-
if (fromNode.nodeType === ELEMENT_NODE) {
|
|
551
|
-
morphElement(fromNode, toNode, ownedItems);
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
if (fromNode.nodeType === TEXT_NODE || fromNode.nodeType === COMMENT_NODE) {
|
|
555
|
-
const fromText = fromNode;
|
|
556
|
-
const toText = toNode;
|
|
557
|
-
if (fromText.data !== toText.data) fromText.data = toText.data;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
function morphElement(fromEl, toEl, ownedItems) {
|
|
561
|
-
if (fromEl.tagName !== toEl.tagName) {
|
|
562
|
-
const replacement = toEl.cloneNode(true);
|
|
563
|
-
fromEl.parentNode?.replaceChild(replacement, fromEl);
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
if (fromEl.dataset.morphSkip !== void 0) return;
|
|
567
|
-
if (fromEl.isEqualNode(toEl)) return;
|
|
568
|
-
if (fromEl === document.activeElement) {
|
|
569
|
-
const ce = fromEl.getAttribute("contenteditable");
|
|
570
|
-
if (ce !== null && ce.toLowerCase() !== "false") return;
|
|
571
|
-
if (isTextInputOrTextarea(fromEl)) preserveTextEntryState(fromEl, toEl);
|
|
572
|
-
}
|
|
573
|
-
morphAttributes(fromEl, toEl);
|
|
574
|
-
if (fromEl.dataset.morphSkipChildren !== void 0) return;
|
|
575
|
-
const syncTextareaValue = fromEl.tagName === "TEXTAREA" && fromEl !== document.activeElement && fromEl.textContent !== toEl.textContent;
|
|
576
|
-
morphChildren(fromEl, toEl, ownedItems);
|
|
577
|
-
if (syncTextareaValue) {
|
|
578
|
-
fromEl.value = toEl.textContent;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
function isUserAgentOwnedAttr(tagName, name) {
|
|
582
|
-
return name === "open" && (tagName === "DETAILS" || tagName === "DIALOG");
|
|
583
|
-
}
|
|
584
|
-
function morphAttributes(fromEl, toEl) {
|
|
585
|
-
const toAttrs = toEl.attributes;
|
|
586
|
-
for (let i = 0; i < toAttrs.length; i++) {
|
|
587
|
-
const attr2 = toAttrs[i];
|
|
588
|
-
const ns = attr2.namespaceURI;
|
|
589
|
-
const name = attr2.localName;
|
|
590
|
-
const value = attr2.value;
|
|
591
|
-
if (ns !== null) {
|
|
592
|
-
if (fromEl.getAttributeNS(ns, name) !== value) {
|
|
593
|
-
fromEl.setAttributeNS(ns, attr2.name, value);
|
|
594
|
-
}
|
|
595
|
-
} else if (fromEl.getAttribute(name) !== value) {
|
|
596
|
-
fromEl.setAttribute(name, value);
|
|
597
|
-
syncFormProp(fromEl, name, value, true);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
const fromAttrs = fromEl.attributes;
|
|
601
|
-
const fromTag = fromEl.tagName;
|
|
602
|
-
for (let i = fromAttrs.length - 1; i >= 0; i--) {
|
|
603
|
-
const attr2 = fromAttrs[i];
|
|
604
|
-
const ns = attr2.namespaceURI;
|
|
605
|
-
const name = attr2.localName;
|
|
606
|
-
if (ns !== null) {
|
|
607
|
-
if (!toEl.hasAttributeNS(ns, name)) fromEl.removeAttributeNS(ns, name);
|
|
608
|
-
} else if (!toEl.hasAttribute(name) && !isUserAgentOwnedAttr(fromTag, name)) {
|
|
609
|
-
fromEl.removeAttribute(name);
|
|
610
|
-
syncFormProp(fromEl, name, "", false);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
function isTextInputOrTextarea(el) {
|
|
615
|
-
if (el.tagName === "TEXTAREA") return true;
|
|
616
|
-
if (el.tagName === "INPUT") {
|
|
617
|
-
const type = el.type;
|
|
618
|
-
return type === "text" || type === "search" || type === "url" || type === "email" || type === "tel" || type === "password" || type === "";
|
|
619
|
-
}
|
|
620
|
-
return false;
|
|
621
|
-
}
|
|
622
|
-
function preserveTextEntryState(fromEl, toEl) {
|
|
623
|
-
if (fromEl.tagName === "TEXTAREA" || fromEl.tagName === "INPUT") {
|
|
624
|
-
const fromInput = fromEl;
|
|
625
|
-
const toInput = toEl;
|
|
626
|
-
toInput.value = fromInput.value;
|
|
627
|
-
try {
|
|
628
|
-
toInput.setSelectionRange(fromInput.selectionStart, fromInput.selectionEnd);
|
|
629
|
-
} catch {
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
// src/list-binding.ts
|
|
635
|
-
function endAnchor(binding) {
|
|
636
|
-
if (binding.items.length > 0) {
|
|
637
|
-
return binding.items[binding.items.length - 1].node.nextSibling;
|
|
638
|
-
}
|
|
639
|
-
return binding.marker.nextSibling;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// src/list-reconcile-fast-paths.ts
|
|
643
|
-
var LT = 60;
|
|
644
|
-
var GT = 62;
|
|
645
|
-
var DQUOTE = 34;
|
|
646
|
-
var SQUOTE = 39;
|
|
647
|
-
var AMP = 38;
|
|
648
|
-
var EQ = 61;
|
|
649
|
-
var SLASH = 47;
|
|
650
|
-
var TEXT_NODE2 = 3;
|
|
651
|
-
var ELEMENT_NODE2 = 1;
|
|
652
|
-
function isWhitespace(cc) {
|
|
653
|
-
return cc === 32 || cc === 9 || cc === 10 || cc === 13;
|
|
654
|
-
}
|
|
655
|
-
function tryAttributeOnlyFastPath(liveNode, oldHtml, newHtml) {
|
|
656
|
-
const oldGt = oldHtml.indexOf(">");
|
|
657
|
-
const newGt = newHtml.indexOf(">");
|
|
658
|
-
if (oldGt === -1 || newGt === -1) return false;
|
|
659
|
-
if (oldHtml.length - oldGt !== newHtml.length - newGt) return false;
|
|
660
|
-
if (oldHtml.slice(oldGt) !== newHtml.slice(newGt)) return false;
|
|
661
|
-
if (containsDataMorphSkip(oldHtml) || containsDataMorphSkip(newHtml)) return false;
|
|
662
|
-
const oldTag = parseOpeningTag(oldHtml, oldGt);
|
|
663
|
-
const newTag = parseOpeningTag(newHtml, newGt);
|
|
664
|
-
if (oldTag === null || newTag === null) return false;
|
|
665
|
-
if (oldTag.tagName !== newTag.tagName) return false;
|
|
666
|
-
for (const name of oldTag.attrs.keys()) {
|
|
667
|
-
if (name.indexOf(":") !== -1) return false;
|
|
668
|
-
}
|
|
669
|
-
for (const name of newTag.attrs.keys()) {
|
|
670
|
-
if (name.indexOf(":") !== -1) return false;
|
|
671
|
-
}
|
|
672
|
-
const liveTagUpper = liveNode.tagName;
|
|
673
|
-
for (const [name, rawValue] of newTag.attrs) {
|
|
674
|
-
const oldValue = oldTag.attrs.get(name);
|
|
675
|
-
if (oldValue === rawValue) continue;
|
|
676
|
-
const value = unescapeAttrValue(rawValue);
|
|
677
|
-
liveNode.setAttribute(name, value);
|
|
678
|
-
syncFormProp(liveNode, name, value, true);
|
|
679
|
-
}
|
|
680
|
-
for (const name of oldTag.attrs.keys()) {
|
|
681
|
-
if (newTag.attrs.has(name)) continue;
|
|
682
|
-
if (isUserAgentOwnedAttr2(liveTagUpper, name)) continue;
|
|
683
|
-
liveNode.removeAttribute(name);
|
|
684
|
-
syncFormProp(liveNode, name, "", false);
|
|
685
|
-
}
|
|
686
|
-
return true;
|
|
687
|
-
}
|
|
688
|
-
function tryTextContentFastPath(liveNode, oldHtml, newHtml) {
|
|
689
|
-
if (containsDataMorphSkip(oldHtml) || containsDataMorphSkip(newHtml)) return false;
|
|
690
|
-
let p = 0;
|
|
691
|
-
const minLen = Math.min(oldHtml.length, newHtml.length);
|
|
692
|
-
while (p < minLen && oldHtml.charCodeAt(p) === newHtml.charCodeAt(p)) p++;
|
|
693
|
-
let s = 0;
|
|
694
|
-
const maxS = minLen - p;
|
|
695
|
-
while (s < maxS && oldHtml.charCodeAt(oldHtml.length - 1 - s) === newHtml.charCodeAt(newHtml.length - 1 - s)) {
|
|
696
|
-
s++;
|
|
697
|
-
}
|
|
698
|
-
const oldWinEnd = oldHtml.length - s;
|
|
699
|
-
const newWinEnd = newHtml.length - s;
|
|
700
|
-
if (!isPureTextWindow(oldHtml, p, oldWinEnd)) return false;
|
|
701
|
-
if (!isPureTextWindow(newHtml, p, newWinEnd)) return false;
|
|
702
|
-
if (p === 0) return false;
|
|
703
|
-
const boundaryCc = oldHtml.charCodeAt(p - 1);
|
|
704
|
-
if (boundaryCc === LT || boundaryCc === DQUOTE || boundaryCc === SQUOTE || boundaryCc === EQ || boundaryCc === AMP) return false;
|
|
705
|
-
const textStart = lastIndexOfChar(oldHtml, GT, p - 1);
|
|
706
|
-
if (textStart === -1) return false;
|
|
707
|
-
const textEnd = oldHtml.indexOf("<", p);
|
|
708
|
-
if (textEnd === -1) return false;
|
|
709
|
-
if (textEnd < oldWinEnd) return false;
|
|
710
|
-
const newTextEnd = textEnd + (newHtml.length - oldHtml.length);
|
|
711
|
-
const oldText = oldHtml.slice(textStart + 1, textEnd);
|
|
712
|
-
const newText = newHtml.slice(textStart + 1, newTextEnd);
|
|
713
|
-
if (oldHtml.lastIndexOf("<!--kfb", textStart) !== -1) return false;
|
|
714
|
-
const textIdx = countTextNodesBefore(oldHtml, textStart + 1);
|
|
715
|
-
const targetNode = nthTextNodeDescendant(liveNode, textIdx);
|
|
716
|
-
if (targetNode === null) return false;
|
|
717
|
-
if (targetNode.nodeValue !== oldText) return false;
|
|
718
|
-
targetNode.nodeValue = newText;
|
|
719
|
-
const host = targetNode.parentNode;
|
|
720
|
-
if (host !== null && host.tagName === "TEXTAREA" && host !== document.activeElement) {
|
|
721
|
-
host.value = newText;
|
|
722
|
-
}
|
|
723
|
-
return true;
|
|
724
|
-
}
|
|
725
|
-
function containsDataMorphSkip(html) {
|
|
726
|
-
return html.indexOf("data-morph-skip") !== -1;
|
|
727
|
-
}
|
|
728
|
-
function isPureTextWindow(html, start, end) {
|
|
729
|
-
for (let i = start; i < end; i++) {
|
|
730
|
-
const cc = html.charCodeAt(i);
|
|
731
|
-
if (cc === LT || cc === GT || cc === DQUOTE || cc === SQUOTE || cc === AMP || cc === EQ) return false;
|
|
732
|
-
}
|
|
733
|
-
return true;
|
|
734
|
-
}
|
|
735
|
-
function lastIndexOfChar(html, target, beforeInclusive) {
|
|
736
|
-
for (let i = beforeInclusive; i >= 0; i--) {
|
|
737
|
-
if (html.charCodeAt(i) === target) return i;
|
|
738
|
-
}
|
|
739
|
-
return -1;
|
|
740
|
-
}
|
|
741
|
-
function countTextNodesBefore(html, beforePos) {
|
|
742
|
-
let count = 0;
|
|
743
|
-
let i = 0;
|
|
744
|
-
while (i < beforePos) {
|
|
745
|
-
if (html.charCodeAt(i) === LT) {
|
|
746
|
-
while (i < beforePos && html.charCodeAt(i) !== GT) i++;
|
|
747
|
-
i++;
|
|
748
|
-
} else {
|
|
749
|
-
const start = i;
|
|
750
|
-
while (i < beforePos && html.charCodeAt(i) !== LT) i++;
|
|
751
|
-
if (i > start) count++;
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
return count;
|
|
755
|
-
}
|
|
756
|
-
function nthTextNodeDescendant(root, n) {
|
|
757
|
-
let count = 0;
|
|
758
|
-
let result = null;
|
|
759
|
-
function walk(node) {
|
|
760
|
-
for (let c = node.firstChild; c !== null; c = c.nextSibling) {
|
|
761
|
-
if (result !== null) return;
|
|
762
|
-
if (c.nodeType === TEXT_NODE2) {
|
|
763
|
-
if (count === n) {
|
|
764
|
-
result = c;
|
|
765
|
-
return;
|
|
766
|
-
}
|
|
767
|
-
count++;
|
|
768
|
-
} else if (c.nodeType === ELEMENT_NODE2) {
|
|
769
|
-
walk(c);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
walk(root);
|
|
774
|
-
return result;
|
|
775
|
-
}
|
|
776
|
-
function parseOpeningTag(html, gtPos) {
|
|
777
|
-
if (html.charCodeAt(0) !== LT) return null;
|
|
778
|
-
let i = 1;
|
|
779
|
-
let end = gtPos;
|
|
780
|
-
if (i < end && html.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
781
|
-
const nameStart = i;
|
|
782
|
-
while (i < end) {
|
|
783
|
-
const cc = html.charCodeAt(i);
|
|
784
|
-
if (isWhitespace(cc)) break;
|
|
785
|
-
i++;
|
|
786
|
-
}
|
|
787
|
-
const tagName = html.slice(nameStart, i);
|
|
788
|
-
if (tagName.length === 0) return null;
|
|
789
|
-
const attrs = /* @__PURE__ */ new Map();
|
|
790
|
-
while (i < end) {
|
|
791
|
-
while (i < end && isWhitespace(html.charCodeAt(i))) i++;
|
|
792
|
-
if (i >= end) break;
|
|
793
|
-
const aNameStart = i;
|
|
794
|
-
while (i < end) {
|
|
795
|
-
const cc = html.charCodeAt(i);
|
|
796
|
-
if (cc === EQ || isWhitespace(cc)) break;
|
|
797
|
-
i++;
|
|
798
|
-
}
|
|
799
|
-
const aName = html.slice(aNameStart, i);
|
|
800
|
-
if (aName.length === 0) return null;
|
|
801
|
-
while (i < end && isWhitespace(html.charCodeAt(i))) i++;
|
|
802
|
-
if (i < end && html.charCodeAt(i) === EQ) {
|
|
803
|
-
i++;
|
|
804
|
-
while (i < end && isWhitespace(html.charCodeAt(i))) i++;
|
|
805
|
-
if (i >= end) return null;
|
|
806
|
-
const q = html.charCodeAt(i);
|
|
807
|
-
if (q !== DQUOTE && q !== SQUOTE) return null;
|
|
808
|
-
i++;
|
|
809
|
-
const vStart = i;
|
|
810
|
-
while (i < end && html.charCodeAt(i) !== q) i++;
|
|
811
|
-
if (i >= end) return null;
|
|
812
|
-
attrs.set(aName, html.slice(vStart, i));
|
|
813
|
-
i++;
|
|
814
|
-
} else {
|
|
815
|
-
attrs.set(aName, "");
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
return { tagName, attrs };
|
|
819
|
-
}
|
|
820
|
-
function unescapeAttrValue(s) {
|
|
821
|
-
if (s.indexOf("&") === -1) return s;
|
|
822
|
-
return s.replace(/"/g, '"').replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
823
|
-
}
|
|
824
|
-
function isUserAgentOwnedAttr2(tagNameUpper, name) {
|
|
825
|
-
return name === "open" && (tagNameUpper === "DETAILS" || tagNameUpper === "DIALOG");
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
// src/list-reconcile-granular.ts
|
|
829
|
-
function reconcileGranular(binding, patches) {
|
|
830
|
-
const { liveParent } = binding;
|
|
831
|
-
const items = binding.items;
|
|
832
|
-
const focusSnap = captureFocus(liveParent);
|
|
833
|
-
let i = 0;
|
|
834
|
-
while (i < patches.length) {
|
|
835
|
-
const patch = patches[i];
|
|
836
|
-
if (patch.type === "replace") {
|
|
837
|
-
i += 1;
|
|
838
|
-
continue;
|
|
839
|
-
}
|
|
840
|
-
if (patch.type === "update") {
|
|
841
|
-
let runEnd = i + 1;
|
|
842
|
-
while (runEnd < patches.length && patches[runEnd].type === "update") {
|
|
843
|
-
runEnd += 1;
|
|
844
|
-
}
|
|
845
|
-
const runLen = runEnd - i;
|
|
846
|
-
if (runLen === 1) {
|
|
847
|
-
applySingleUpdate(liveParent, items, patch);
|
|
848
|
-
} else {
|
|
849
|
-
applyBulkUpdate(liveParent, items, patches, i, runEnd);
|
|
850
|
-
}
|
|
851
|
-
i = runEnd;
|
|
852
|
-
continue;
|
|
853
|
-
}
|
|
854
|
-
if (patch.type === "insert") {
|
|
855
|
-
let runEnd = i + 1;
|
|
856
|
-
while (runEnd < patches.length && patches[runEnd].type === "insert" && patches[runEnd].index === patches[runEnd - 1].index + 1) {
|
|
857
|
-
runEnd += 1;
|
|
858
|
-
}
|
|
859
|
-
const runLen = runEnd - i;
|
|
860
|
-
if (runLen === 1) {
|
|
861
|
-
applySingleInsert(liveParent, items, patch, endAnchor(binding));
|
|
862
|
-
} else {
|
|
863
|
-
applyBulkInsert(liveParent, items, patches, i, runEnd, endAnchor(binding));
|
|
864
|
-
}
|
|
865
|
-
i = runEnd;
|
|
866
|
-
continue;
|
|
867
|
-
}
|
|
868
|
-
if (patch.type === "remove") {
|
|
869
|
-
const entry = items[patch.index];
|
|
870
|
-
disposeRowBindings(entry.bindingDisposers);
|
|
871
|
-
liveParent.removeChild(entry.node);
|
|
872
|
-
items.splice(patch.index, 1);
|
|
873
|
-
i += 1;
|
|
874
|
-
continue;
|
|
875
|
-
}
|
|
876
|
-
if (patch.type === "move") {
|
|
877
|
-
const moved = items[patch.from];
|
|
878
|
-
let anchorIdx = patch.to;
|
|
879
|
-
if (patch.from < patch.to) anchorIdx += 1;
|
|
880
|
-
const anchor = anchorIdx < items.length ? items[anchorIdx].node : endAnchor(binding);
|
|
881
|
-
liveParent.insertBefore(moved.node, anchor);
|
|
882
|
-
items.splice(patch.from, 1);
|
|
883
|
-
items.splice(patch.to, 0, moved);
|
|
884
|
-
i += 1;
|
|
885
|
-
continue;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
if (focusSnap !== null) restoreFocus(focusSnap);
|
|
889
|
-
if (items.length > 0) {
|
|
890
|
-
devHooks.missingRowKey?.(items[0].node, items[0].html, binding);
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
function applySingleInsert(liveParent, items, patch, tailAnchor) {
|
|
894
|
-
const { html } = patch;
|
|
895
|
-
const newNode = parseSingleRow(html, patch.index, liveParent);
|
|
896
|
-
const anchor = patch.index < items.length ? items[patch.index].node : tailAnchor;
|
|
897
|
-
liveParent.insertBefore(newNode, anchor);
|
|
898
|
-
items.splice(patch.index, 0, {
|
|
899
|
-
ref: patch.item,
|
|
900
|
-
cacheKey: void 0,
|
|
901
|
-
html,
|
|
902
|
-
node: newNode,
|
|
903
|
-
bindings: patch.bindings,
|
|
904
|
-
// KF-294: wire the inserted row's fine-grained bindings to its new node.
|
|
905
|
-
bindingDisposers: wireRowIfBound(newNode, patch.bindings)
|
|
906
|
-
});
|
|
907
|
-
}
|
|
908
|
-
function wireRowIfBound(node, bindings) {
|
|
909
|
-
return bindings !== void 0 && bindings.length > 0 ? wireRowBindings(node, bindings) : void 0;
|
|
910
|
-
}
|
|
911
|
-
function applySingleUpdate(liveParent, items, patch) {
|
|
912
|
-
const { html } = patch;
|
|
913
|
-
const oldEntry = items[patch.index];
|
|
914
|
-
if (html === oldEntry.html) {
|
|
915
|
-
items[patch.index] = reuseBound(patch, html, oldEntry);
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
if (tryAttributeOnlyFastPath(oldEntry.node, oldEntry.html, html) || tryTextContentFastPath(oldEntry.node, oldEntry.html, html)) {
|
|
919
|
-
items[patch.index] = reuseBound(patch, html, oldEntry);
|
|
920
|
-
return;
|
|
921
|
-
}
|
|
922
|
-
const newNode = parseSingleRow(html, patch.index, liveParent);
|
|
923
|
-
applyParsedRowUpdate(liveParent, items, patch, html, newNode);
|
|
924
|
-
}
|
|
925
|
-
function applyParsedRowUpdate(liveParent, items, patch, html, newNode) {
|
|
926
|
-
const oldEntry = items[patch.index];
|
|
927
|
-
if (oldEntry.node.tagName === newNode.tagName) {
|
|
928
|
-
_morphElement(oldEntry.node, newNode);
|
|
929
|
-
items[patch.index] = reuseBound(patch, html, oldEntry);
|
|
930
|
-
} else {
|
|
931
|
-
disposeRowBindings(oldEntry.bindingDisposers);
|
|
932
|
-
liveParent.replaceChild(newNode, oldEntry.node);
|
|
933
|
-
items[patch.index] = {
|
|
934
|
-
ref: patch.item,
|
|
935
|
-
cacheKey: void 0,
|
|
936
|
-
html,
|
|
937
|
-
node: newNode,
|
|
938
|
-
bindings: patch.bindings,
|
|
939
|
-
bindingDisposers: wireRowIfBound(newNode, patch.bindings)
|
|
940
|
-
};
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
function reuseBound(patch, html, oldEntry) {
|
|
944
|
-
const kept = carryOrRewireRowBindings(
|
|
945
|
-
oldEntry.node,
|
|
946
|
-
oldEntry.bindings,
|
|
947
|
-
oldEntry.bindingDisposers,
|
|
948
|
-
patch.bindings
|
|
949
|
-
);
|
|
950
|
-
return {
|
|
951
|
-
ref: patch.item,
|
|
952
|
-
cacheKey: void 0,
|
|
953
|
-
html,
|
|
954
|
-
node: oldEntry.node,
|
|
955
|
-
bindings: kept.bindings,
|
|
956
|
-
bindingDisposers: kept.bindingDisposers
|
|
957
|
-
};
|
|
958
|
-
}
|
|
959
|
-
function applyBulkUpdate(liveParent, items, patches, start, end) {
|
|
960
|
-
const morphChanges = [];
|
|
961
|
-
for (let k = start; k < end; k++) {
|
|
962
|
-
const p = patches[k];
|
|
963
|
-
const oldEntry = items[p.index];
|
|
964
|
-
if (p.html === oldEntry.html) {
|
|
965
|
-
items[p.index] = reuseBound(p, p.html, oldEntry);
|
|
966
|
-
continue;
|
|
967
|
-
}
|
|
968
|
-
if (tryAttributeOnlyFastPath(oldEntry.node, oldEntry.html, p.html) || tryTextContentFastPath(oldEntry.node, oldEntry.html, p.html)) {
|
|
969
|
-
items[p.index] = reuseBound(p, p.html, oldEntry);
|
|
970
|
-
continue;
|
|
971
|
-
}
|
|
972
|
-
morphChanges.push({ patchIdx: k, html: p.html });
|
|
973
|
-
}
|
|
974
|
-
if (morphChanges.length === 0) return;
|
|
975
|
-
const { content, count } = parseRowTemplate(morphChanges.map((c) => c.html).join(""), liveParent);
|
|
976
|
-
if (count !== morphChanges.length) {
|
|
977
|
-
throw findOffendingChange(patches, morphChanges, liveParent);
|
|
978
|
-
}
|
|
979
|
-
const newNodes = collectTemplateChildren(content, morphChanges.length);
|
|
980
|
-
for (let k = 0; k < morphChanges.length; k++) {
|
|
981
|
-
const c = morphChanges[k];
|
|
982
|
-
const p = patches[c.patchIdx];
|
|
983
|
-
applyParsedRowUpdate(liveParent, items, p, c.html, newNodes[k]);
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
function applyBulkInsert(liveParent, items, patches, start, end, tailAnchor) {
|
|
987
|
-
const startIdx = patches[start].index;
|
|
988
|
-
const htmls = new Array(end - start);
|
|
989
|
-
for (let k = start; k < end; k++) {
|
|
990
|
-
htmls[k - start] = patches[k].html;
|
|
991
|
-
}
|
|
992
|
-
const { content, count } = parseRowTemplate(htmls.join(""), liveParent);
|
|
993
|
-
if (count !== htmls.length) {
|
|
994
|
-
throw findOffendingInsert(patches, start, htmls, liveParent);
|
|
995
|
-
}
|
|
996
|
-
const newNodes = collectTemplateChildren(content, end - start);
|
|
997
|
-
const anchor = startIdx < items.length ? items[startIdx].node : tailAnchor;
|
|
998
|
-
liveParent.insertBefore(content, anchor);
|
|
999
|
-
const newEntries = new Array(end - start);
|
|
1000
|
-
for (let k = 0; k < newEntries.length; k++) {
|
|
1001
|
-
const p = patches[start + k];
|
|
1002
|
-
newEntries[k] = {
|
|
1003
|
-
ref: p.item,
|
|
1004
|
-
cacheKey: void 0,
|
|
1005
|
-
html: htmls[k],
|
|
1006
|
-
node: newNodes[k],
|
|
1007
|
-
bindings: p.bindings,
|
|
1008
|
-
bindingDisposers: wireRowIfBound(newNodes[k], p.bindings)
|
|
1009
|
-
// KF-294
|
|
1010
|
-
};
|
|
1011
|
-
}
|
|
1012
|
-
items.splice(startIdx, 0, ...newEntries);
|
|
1013
|
-
}
|
|
1014
|
-
function findOffendingInsert(patches, start, htmls, liveParent) {
|
|
1015
|
-
for (let i = 0; i < htmls.length; i++) {
|
|
1016
|
-
if (parseRowTemplate(htmls[i], liveParent).count !== 1) {
|
|
1017
|
-
return rowContractError(patches[start + i].index, htmls[i], liveParent);
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
return new Error("each(): bulk-insert mismatch with no per-row offender (kerf bug).");
|
|
1021
|
-
}
|
|
1022
|
-
function findOffendingChange(patches, changes, liveParent) {
|
|
1023
|
-
for (const c of changes) {
|
|
1024
|
-
if (parseRowTemplate(c.html, liveParent).count !== 1) {
|
|
1025
|
-
return rowContractError(patches[c.patchIdx].index, c.html, liveParent);
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
return new Error("each(): bulk-update mismatch with no per-row offender (kerf bug).");
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
// src/list-reconcile-inplace.ts
|
|
1032
|
-
function tryInPlaceContentUpdate(binding, listSeg) {
|
|
1033
|
-
const oldItems = binding.items;
|
|
1034
|
-
const items = listSeg.items;
|
|
1035
|
-
const n = items.length;
|
|
1036
|
-
if (n === 0 || n !== oldItems.length) return false;
|
|
1037
|
-
for (let i = 0; i < n; i++) {
|
|
1038
|
-
if (items[i].ref !== oldItems[i].ref) return false;
|
|
1039
|
-
}
|
|
1040
|
-
const { liveParent } = binding;
|
|
1041
|
-
const newRecord = new Array(n);
|
|
1042
|
-
const focusSnap = captureFocus(liveParent);
|
|
1043
|
-
for (let i = 0; i < n; i++) {
|
|
1044
|
-
newRecord[i] = updateRowInPlace(liveParent, oldItems[i], items[i], i);
|
|
1045
|
-
}
|
|
1046
|
-
if (focusSnap !== null) restoreFocus(focusSnap);
|
|
1047
|
-
binding.items = newRecord;
|
|
1048
|
-
devHooks.missingRowKey?.(newRecord[0].node, newRecord[0].html, binding);
|
|
1049
|
-
return true;
|
|
1050
|
-
}
|
|
1051
|
-
function updateRowInPlace(liveParent, old, ni, index) {
|
|
1052
|
-
if (old.html === ni.html || tryAttributeOnlyFastPath(old.node, old.html, ni.html) || tryTextContentFastPath(old.node, old.html, ni.html)) {
|
|
1053
|
-
const kept = carryOrRewireRowBindings(old.node, old.bindings, old.bindingDisposers, ni.bindings);
|
|
1054
|
-
return {
|
|
1055
|
-
ref: ni.ref,
|
|
1056
|
-
cacheKey: ni.cacheKey,
|
|
1057
|
-
html: ni.html,
|
|
1058
|
-
node: old.node,
|
|
1059
|
-
bindings: kept.bindings,
|
|
1060
|
-
bindingDisposers: kept.bindingDisposers
|
|
1061
|
-
};
|
|
1062
|
-
}
|
|
1063
|
-
const newNode = parseSingleRow(ni.html, index, liveParent);
|
|
1064
|
-
if (old.node.tagName === newNode.tagName) {
|
|
1065
|
-
_morphElement(old.node, newNode);
|
|
1066
|
-
const kept = carryOrRewireRowBindings(old.node, old.bindings, old.bindingDisposers, ni.bindings);
|
|
1067
|
-
return {
|
|
1068
|
-
ref: ni.ref,
|
|
1069
|
-
cacheKey: ni.cacheKey,
|
|
1070
|
-
html: ni.html,
|
|
1071
|
-
node: old.node,
|
|
1072
|
-
bindings: kept.bindings,
|
|
1073
|
-
bindingDisposers: kept.bindingDisposers
|
|
1074
|
-
};
|
|
1075
|
-
}
|
|
1076
|
-
disposeRowBindings(old.bindingDisposers);
|
|
1077
|
-
liveParent.replaceChild(newNode, old.node);
|
|
1078
|
-
const fresh = carryOrRewireRowBindings(newNode, void 0, void 0, ni.bindings);
|
|
1079
|
-
return {
|
|
1080
|
-
ref: ni.ref,
|
|
1081
|
-
cacheKey: ni.cacheKey,
|
|
1082
|
-
html: ni.html,
|
|
1083
|
-
node: newNode,
|
|
1084
|
-
bindings: fresh.bindings,
|
|
1085
|
-
bindingDisposers: fresh.bindingDisposers
|
|
1086
|
-
};
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
// src/list-reconcile-snapshot.ts
|
|
1090
|
-
function reconcileSnapshot(binding, listSeg) {
|
|
1091
|
-
if (tryInPlaceContentUpdate(binding, listSeg)) return;
|
|
1092
|
-
const { liveParent } = binding;
|
|
1093
|
-
const { newRecord, prevIdx, removedItems, freshIndices, freshHtmls } = classifyItems(binding.items, listSeg);
|
|
1094
|
-
const tailAnchor = endAnchor(binding);
|
|
1095
|
-
buildFreshNodes(newRecord, freshIndices, freshHtmls, liveParent);
|
|
1096
|
-
const focusSnap = captureFocus(liveParent);
|
|
1097
|
-
removeOldNodes(liveParent, removedItems);
|
|
1098
|
-
applyMoves(liveParent, newRecord, prevIdx, lis(prevIdx), tailAnchor);
|
|
1099
|
-
if (focusSnap !== null) restoreFocus(focusSnap);
|
|
1100
|
-
binding.items = newRecord;
|
|
1101
|
-
if (newRecord.length > 0) {
|
|
1102
|
-
devHooks.missingRowKey?.(newRecord[0].node, newRecord[0].html, binding);
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
function classifyItems(oldItems, listSeg) {
|
|
1106
|
-
const oldByRef = /* @__PURE__ */ new Map();
|
|
1107
|
-
for (let i = 0; i < oldItems.length; i++) {
|
|
1108
|
-
oldByRef.set(oldItems[i].ref, [oldItems[i], i]);
|
|
1109
|
-
}
|
|
1110
|
-
const newRecord = new Array(listSeg.items.length);
|
|
1111
|
-
const prevIdx = new Array(listSeg.items.length);
|
|
1112
|
-
const removedItems = [];
|
|
1113
|
-
const freshIndices = [];
|
|
1114
|
-
const freshHtmls = [];
|
|
1115
|
-
for (let i = 0; i < listSeg.items.length; i++) {
|
|
1116
|
-
const ni = listSeg.items[i];
|
|
1117
|
-
const oi = oldByRef.get(ni.ref);
|
|
1118
|
-
if (oi !== void 0) {
|
|
1119
|
-
oldByRef.delete(ni.ref);
|
|
1120
|
-
if (oi[0].html === ni.html) {
|
|
1121
|
-
newRecord[i] = oi[0];
|
|
1122
|
-
prevIdx[i] = oi[1];
|
|
1123
|
-
continue;
|
|
1124
|
-
}
|
|
1125
|
-
removedItems.push(oi[0]);
|
|
1126
|
-
}
|
|
1127
|
-
newRecord[i] = {
|
|
1128
|
-
// `node` placeholder is filled by `buildFreshNodes`; its parse-count
|
|
1129
|
-
// check guarantees every fresh index gets a real element before use.
|
|
1130
|
-
ref: ni.ref,
|
|
1131
|
-
cacheKey: ni.cacheKey,
|
|
1132
|
-
html: ni.html,
|
|
1133
|
-
node: null,
|
|
1134
|
-
bindings: ni.bindings
|
|
1135
|
-
};
|
|
1136
|
-
prevIdx[i] = -1;
|
|
1137
|
-
freshIndices.push(i);
|
|
1138
|
-
freshHtmls.push(ni.html);
|
|
1139
|
-
}
|
|
1140
|
-
for (const [, orphan] of oldByRef) removedItems.push(orphan[0]);
|
|
1141
|
-
return { newRecord, prevIdx, removedItems, freshIndices, freshHtmls };
|
|
1142
|
-
}
|
|
1143
|
-
function buildFreshNodes(newRecord, freshIndices, freshHtmls, liveParent) {
|
|
1144
|
-
if (freshHtmls.length === 0) return;
|
|
1145
|
-
const { content, count } = parseRowTemplate(freshHtmls.join(""), liveParent);
|
|
1146
|
-
if (count !== freshHtmls.length) {
|
|
1147
|
-
throw findOffendingRow(newRecord, freshIndices, freshHtmls, liveParent);
|
|
1148
|
-
}
|
|
1149
|
-
let node = content.firstElementChild;
|
|
1150
|
-
for (const idx of freshIndices) {
|
|
1151
|
-
const next = node.nextElementSibling;
|
|
1152
|
-
const item = newRecord[idx];
|
|
1153
|
-
item.node = node;
|
|
1154
|
-
if (item.bindings !== void 0 && item.bindings.length > 0) {
|
|
1155
|
-
item.bindingDisposers = wireRowBindings(item.node, item.bindings);
|
|
1156
|
-
}
|
|
1157
|
-
node = next;
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
function findOffendingRow(newRecord, freshIndices, freshHtmls, liveParent) {
|
|
1161
|
-
for (let i = 0; i < freshHtmls.length; i++) {
|
|
1162
|
-
if (parseRowTemplate(freshHtmls[i], liveParent).count !== 1) {
|
|
1163
|
-
return rowContractError(freshIndices[i], newRecord[freshIndices[i]].html, liveParent);
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
return new Error("each(): bulk-parse mismatch with no per-row offender (kerf bug).");
|
|
1167
|
-
}
|
|
1168
|
-
function removeOldNodes(liveParent, removedItems) {
|
|
1169
|
-
for (const item of removedItems) {
|
|
1170
|
-
disposeRowBindings(item.bindingDisposers);
|
|
1171
|
-
if (item.node.parentElement === liveParent) liveParent.removeChild(item.node);
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
function applyMoves(liveParent, newRecord, prevIdx, stable, tailAnchor) {
|
|
1175
|
-
let nextSibling = tailAnchor;
|
|
1176
|
-
for (let i = newRecord.length - 1; i >= 0; i--) {
|
|
1177
|
-
const node = newRecord[i].node;
|
|
1178
|
-
if (prevIdx[i] === -1 || !stable.has(i)) {
|
|
1179
|
-
liveParent.insertBefore(node, nextSibling);
|
|
1180
|
-
}
|
|
1181
|
-
nextSibling = node;
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
function lis(arr) {
|
|
1185
|
-
const tails = [];
|
|
1186
|
-
const tailIdx = [];
|
|
1187
|
-
const prev = new Array(arr.length);
|
|
1188
|
-
for (let i = 0; i < arr.length; i++) {
|
|
1189
|
-
const v = arr[i];
|
|
1190
|
-
if (v === -1) {
|
|
1191
|
-
prev[i] = -1;
|
|
1192
|
-
continue;
|
|
1193
|
-
}
|
|
1194
|
-
let lo = 0;
|
|
1195
|
-
let hi = tails.length;
|
|
1196
|
-
while (lo < hi) {
|
|
1197
|
-
const mid = lo + hi >> 1;
|
|
1198
|
-
if (tails[mid] < v) lo = mid + 1;
|
|
1199
|
-
else hi = mid;
|
|
1200
|
-
}
|
|
1201
|
-
prev[i] = lo > 0 ? tailIdx[lo - 1] : -1;
|
|
1202
|
-
tails[lo] = v;
|
|
1203
|
-
tailIdx[lo] = i;
|
|
1204
|
-
}
|
|
1205
|
-
const out = /* @__PURE__ */ new Set();
|
|
1206
|
-
let k = tailIdx.length > 0 ? tailIdx[tailIdx.length - 1] : -1;
|
|
1207
|
-
while (k !== -1) {
|
|
1208
|
-
out.add(k);
|
|
1209
|
-
k = prev[k];
|
|
1210
|
-
}
|
|
1211
|
-
return out;
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
// src/list-reconcile.ts
|
|
1215
|
-
function reconcileList(binding, listSeg) {
|
|
1216
|
-
if (listSeg.patches !== void 0 && binding.items.length > 0) {
|
|
1217
|
-
reconcileGranular(binding, listSeg.patches);
|
|
1218
|
-
return;
|
|
1219
|
-
}
|
|
1220
|
-
reconcileSnapshot(binding, listSeg);
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
// src/mount.ts
|
|
1224
|
-
var MOUNTED_MARKER = /* @__PURE__ */ Symbol.for("kerfjs.mounted");
|
|
1225
|
-
var NESTED_MOUNT_MSG = "mount: rootEl is already inside (or contains) a mounted tree. kerf supports one mount per tree \u2014 compose with plain functions that return JSX instead of nesting mounts.";
|
|
1226
|
-
function isMounted(el) {
|
|
1227
|
-
return el[MOUNTED_MARKER] === true;
|
|
1228
|
-
}
|
|
1229
|
-
function setMounted(el, on) {
|
|
1230
|
-
if (on) {
|
|
1231
|
-
el[MOUNTED_MARKER] = true;
|
|
1232
|
-
} else {
|
|
1233
|
-
delete el[MOUNTED_MARKER];
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
function describeEl(el) {
|
|
1237
|
-
const tag = el.tagName.toLowerCase();
|
|
1238
|
-
const id = el.id ? `#${el.id}` : "";
|
|
1239
|
-
return `<${tag}${id}>`;
|
|
1240
|
-
}
|
|
1241
|
-
function assertNotInsideMountedTree(rootEl) {
|
|
1242
|
-
if (isMounted(rootEl)) {
|
|
1243
|
-
throw new Error(
|
|
1244
|
-
`mount: ${describeEl(rootEl)} is already mounted. Call the disposer returned by the first mount() before mounting again. kerf supports one mount per element \u2014 compose with plain functions that return JSX instead of nesting mounts.`
|
|
1245
|
-
);
|
|
1246
|
-
}
|
|
1247
|
-
let ancestor = rootEl.parentElement;
|
|
1248
|
-
while (ancestor !== null) {
|
|
1249
|
-
if (isMounted(ancestor)) throw new Error(NESTED_MOUNT_MSG);
|
|
1250
|
-
ancestor = ancestor.parentElement;
|
|
1251
|
-
}
|
|
1252
|
-
const stack = [];
|
|
1253
|
-
for (let i = 0; i < rootEl.children.length; i++) stack.push(rootEl.children[i]);
|
|
1254
|
-
while (stack.length > 0) {
|
|
1255
|
-
const cur = stack.pop();
|
|
1256
|
-
if (isMounted(cur)) throw new Error(NESTED_MOUNT_MSG);
|
|
1257
|
-
for (let i = 0; i < cur.children.length; i++) stack.push(cur.children[i]);
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
function mount(rootEl, render) {
|
|
1261
|
-
if (rootEl == null) {
|
|
1262
|
-
throw new Error(
|
|
1263
|
-
'mount: rootEl is null/undefined \u2014 pass the live element, e.g. mount(document.getElementById("app")!, render). A common cause is a typo in the id or selector that returns null at runtime even though the TypeScript types say HTMLElement.'
|
|
1264
|
-
);
|
|
1265
|
-
}
|
|
1266
|
-
const owner = rootEl.ownerDocument;
|
|
1267
|
-
if (owner !== document) {
|
|
1268
|
-
if (owner.defaultView === null) document.adoptNode(rootEl);
|
|
1269
|
-
}
|
|
1270
|
-
assertNotInsideMountedTree(rootEl);
|
|
1271
|
-
setMounted(rootEl, true);
|
|
1272
|
-
const listenerWarnObserver = devHooks.listenerRebuild?.(rootEl) ?? null;
|
|
1273
|
-
const bindings = /* @__PURE__ */ new Map();
|
|
1274
|
-
const renderCtx = {
|
|
1275
|
-
counter: 0,
|
|
1276
|
-
caches: /* @__PURE__ */ new Map(),
|
|
1277
|
-
bindingCounts: /* @__PURE__ */ new Map(),
|
|
1278
|
-
bindingSources: /* @__PURE__ */ new Map(),
|
|
1279
|
-
keysThisRender: /* @__PURE__ */ new Set(),
|
|
1280
|
-
shiftCandidates: [],
|
|
1281
|
-
warnedShiftIds: /* @__PURE__ */ new Set(),
|
|
1282
|
-
rebuiltLists: /* @__PURE__ */ new Set()
|
|
1283
|
-
};
|
|
1284
|
-
const bindingCtx = newBindingContext();
|
|
1285
|
-
let bindingDisposers = [];
|
|
1286
|
-
let prevWiredBindings = [];
|
|
1287
|
-
let isFirst = true;
|
|
1288
|
-
let prevStaticHtml = "";
|
|
1289
|
-
const valueOnlyWarnCtx = { warned: false };
|
|
1290
|
-
const runRenderPass = () => {
|
|
1291
|
-
renderCtx.counter = 0;
|
|
1292
|
-
renderCtx.keysThisRender.clear();
|
|
1293
|
-
renderCtx.shiftCandidates.length = 0;
|
|
1294
|
-
bindingCtx.counter = 0;
|
|
1295
|
-
bindingCtx.list = [];
|
|
1296
|
-
_setRenderContext(renderCtx);
|
|
1297
|
-
_setBindingContext(bindingCtx);
|
|
1298
|
-
try {
|
|
1299
|
-
return render();
|
|
1300
|
-
} finally {
|
|
1301
|
-
_setRenderContext(null);
|
|
1302
|
-
_setBindingContext(null);
|
|
1303
|
-
}
|
|
1304
|
-
};
|
|
1305
|
-
const disposeEffect = effect(() => {
|
|
1306
|
-
let result = runRenderPass();
|
|
1307
|
-
const countChanged = renderCtx.previousCallCount !== void 0 && renderCtx.previousCallCount !== renderCtx.counter;
|
|
1308
|
-
if (countChanged) {
|
|
1309
|
-
for (const id of renderCtx.shiftCandidates) {
|
|
1310
|
-
if (renderCtx.warnedShiftIds.has(id)) continue;
|
|
1311
|
-
renderCtx.warnedShiftIds.add(id);
|
|
1312
|
-
devHooks.listIdShift?.(id);
|
|
1313
|
-
}
|
|
1314
|
-
_resetCallOrderListState(renderCtx);
|
|
1315
|
-
result = runRenderPass();
|
|
1316
|
-
}
|
|
1317
|
-
let segment = resultToSegment(result);
|
|
1318
|
-
if (isFirst) {
|
|
1319
|
-
runFirstRender(rootEl, segment, bindings);
|
|
1320
|
-
prevStaticHtml = flattenWithoutListItems(segment);
|
|
1321
|
-
devHooks.parserRepair?.(prevStaticHtml);
|
|
1322
|
-
bindingDisposers = wireBindings(rootEl, bindingCtx, bindingDisposers);
|
|
1323
|
-
if (devHooks.staleBindingEnabled?.() === true) prevWiredBindings = bindingCtx.list;
|
|
1324
|
-
isFirst = false;
|
|
1325
|
-
} else {
|
|
1326
|
-
let nextStaticHtml = runSubsequentRender(
|
|
1327
|
-
rootEl,
|
|
1328
|
-
segment,
|
|
1329
|
-
bindings,
|
|
1330
|
-
renderCtx,
|
|
1331
|
-
prevStaticHtml,
|
|
1332
|
-
valueOnlyWarnCtx
|
|
1333
|
-
);
|
|
1334
|
-
if (anyRebuiltListIsGranular(segment, renderCtx.rebuiltLists)) {
|
|
1335
|
-
for (const id of renderCtx.rebuiltLists) renderCtx.bindingCounts.delete(id);
|
|
1336
|
-
result = runRenderPass();
|
|
1337
|
-
segment = resultToSegment(result);
|
|
1338
|
-
nextStaticHtml = runSubsequentRender(
|
|
1339
|
-
rootEl,
|
|
1340
|
-
segment,
|
|
1341
|
-
bindings,
|
|
1342
|
-
renderCtx,
|
|
1343
|
-
prevStaticHtml,
|
|
1344
|
-
valueOnlyWarnCtx
|
|
1345
|
-
);
|
|
1346
|
-
}
|
|
1347
|
-
if (nextStaticHtml !== prevStaticHtml) {
|
|
1348
|
-
bindingDisposers = wireBindings(rootEl, bindingCtx, bindingDisposers);
|
|
1349
|
-
if (devHooks.staleBindingEnabled?.() === true) prevWiredBindings = bindingCtx.list;
|
|
1350
|
-
} else {
|
|
1351
|
-
devHooks.staleBinding?.(prevWiredBindings, bindingCtx.list);
|
|
1352
|
-
}
|
|
1353
|
-
prevStaticHtml = nextStaticHtml;
|
|
1354
|
-
}
|
|
1355
|
-
const expectedCounts = devHooks.listInvariantsEnabled?.() === true ? /* @__PURE__ */ new Map() : null;
|
|
1356
|
-
for (const listSeg of collectLists(segment).values()) {
|
|
1357
|
-
const binding = bindings.get(listSeg.id);
|
|
1358
|
-
if (binding === void 0) {
|
|
1359
|
-
throw new Error(
|
|
1360
|
-
"mount: an each() list appeared in the render output but its marker never reached the live DOM. The most common cause is an each() introduced inside a data-morph-skip subtree on a re-render \u2014 the morph leaves that subtree untouched, so the list can never bind. Move the each() outside the skipped subtree, or remove data-morph-skip from its ancestor."
|
|
1361
|
-
);
|
|
1362
|
-
}
|
|
1363
|
-
reconcileList(binding, listSeg);
|
|
1364
|
-
renderCtx.bindingCounts.set(listSeg.id, binding.items.length);
|
|
1365
|
-
renderCtx.bindingSources.set(listSeg.id, listSeg.source);
|
|
1366
|
-
expectedCounts?.set(
|
|
1367
|
-
listSeg.id,
|
|
1368
|
-
listSeg.patches !== void 0 && listSeg.source !== void 0 ? listSeg.source.value.length : listSeg.items.length
|
|
1369
|
-
);
|
|
1370
|
-
}
|
|
1371
|
-
renderCtx.previousCallCount = renderCtx.counter;
|
|
1372
|
-
devHooks.listInvariants?.(rootEl, bindings, expectedCounts ?? void 0);
|
|
1373
|
-
});
|
|
1374
|
-
return () => {
|
|
1375
|
-
disposeEffect();
|
|
1376
|
-
for (const d of bindingDisposers) d();
|
|
1377
|
-
bindingDisposers = [];
|
|
1378
|
-
for (const b of bindings.values()) {
|
|
1379
|
-
for (const item of b.items) disposeRowBindings(item.bindingDisposers);
|
|
1380
|
-
}
|
|
1381
|
-
listenerWarnObserver?.disconnect();
|
|
1382
|
-
setMounted(rootEl, false);
|
|
1383
|
-
};
|
|
1384
|
-
}
|
|
1385
|
-
function runFirstRender(rootEl, segment, bindings) {
|
|
1386
|
-
rootEl.innerHTML = flatten(segment, true);
|
|
1387
|
-
bindListsFromMarkers(rootEl, segment, bindings, true);
|
|
1388
|
-
}
|
|
1389
|
-
function runSubsequentRender(rootEl, segment, bindings, renderCtx, prevStaticHtml, valueOnlyWarnCtx) {
|
|
1390
|
-
renderCtx.rebuiltLists.clear();
|
|
1391
|
-
const currentStaticHtml = flattenWithoutListItems(segment);
|
|
1392
|
-
if (currentStaticHtml === prevStaticHtml) {
|
|
1393
|
-
return prevStaticHtml;
|
|
1394
|
-
}
|
|
1395
|
-
devHooks.valueOnlyRerender?.(prevStaticHtml, currentStaticHtml, valueOnlyWarnCtx);
|
|
1396
|
-
cleanupOrphanBindings(segment, bindings, renderCtx);
|
|
1397
|
-
const template = rootEl.cloneNode(false);
|
|
1398
|
-
template.innerHTML = currentStaticHtml;
|
|
1399
|
-
morph(rootEl, template, collectOwnedItems(bindings));
|
|
1400
|
-
bindListsFromMarkers(rootEl, segment, bindings, false, renderCtx.rebuiltLists);
|
|
1401
|
-
return currentStaticHtml;
|
|
1402
|
-
}
|
|
1403
|
-
function coerceRenderResult(result) {
|
|
1404
|
-
if (result === null || result === void 0) return "";
|
|
1405
|
-
if (result === false || result === true) return "";
|
|
1406
|
-
return String(result);
|
|
1407
|
-
}
|
|
1408
|
-
function resultToSegment(result) {
|
|
1409
|
-
return isSafeHtml(result) ? result.__segment ?? { kind: "static", html: result.__html } : { kind: "static", html: coerceRenderResult(result) };
|
|
1410
|
-
}
|
|
1411
|
-
function anyRebuiltListIsGranular(segment, rebuilt) {
|
|
1412
|
-
if (rebuilt.size === 0) return false;
|
|
1413
|
-
const lists = collectLists(segment);
|
|
1414
|
-
for (const id of rebuilt) {
|
|
1415
|
-
if (lists.get(id)?.patches !== void 0) return true;
|
|
1416
|
-
}
|
|
1417
|
-
return false;
|
|
1418
|
-
}
|
|
1419
|
-
function bindListsFromMarkers(rootEl, segment, bindings, inlinedItems, rebuiltLists) {
|
|
1420
|
-
const lists = collectLists(segment);
|
|
1421
|
-
const found = [];
|
|
1422
|
-
collectComments(rootEl, found);
|
|
1423
|
-
for (const marker of found) {
|
|
1424
|
-
if (!marker.data.startsWith(LIST_MARKER_PREFIX)) continue;
|
|
1425
|
-
const id = marker.data.slice(LIST_MARKER_PREFIX.length);
|
|
1426
|
-
const existing = bindings.get(id);
|
|
1427
|
-
if (existing !== void 0) {
|
|
1428
|
-
if (existing.marker === marker && rootEl.contains(existing.marker)) continue;
|
|
1429
|
-
for (const item of existing.items) {
|
|
1430
|
-
disposeRowBindings(item.bindingDisposers);
|
|
1431
|
-
if (rootEl.contains(item.node)) {
|
|
1432
|
-
item.node.parentElement?.removeChild(item.node);
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1435
|
-
bindings.delete(id);
|
|
1436
|
-
rebuiltLists?.add(id);
|
|
1437
|
-
devHooks.listRebind?.(id, marker.parentElement);
|
|
1438
|
-
}
|
|
1439
|
-
const listSeg = lists.get(id);
|
|
1440
|
-
const liveParent = marker.parentElement;
|
|
1441
|
-
const items = [];
|
|
1442
|
-
if (inlinedItems) {
|
|
1443
|
-
let next = marker.nextElementSibling;
|
|
1444
|
-
for (let i = 0; i < listSeg.items.length && next !== null; i++) {
|
|
1445
|
-
validateInlinedRowMatch(listSeg.items[i].html, i, next, liveParent);
|
|
1446
|
-
const rowBindings = listSeg.items[i].bindings;
|
|
1447
|
-
const bound = {
|
|
1448
|
-
ref: listSeg.items[i].ref,
|
|
1449
|
-
cacheKey: listSeg.items[i].cacheKey,
|
|
1450
|
-
html: listSeg.items[i].html,
|
|
1451
|
-
node: next,
|
|
1452
|
-
bindings: rowBindings
|
|
1453
|
-
};
|
|
1454
|
-
if (rowBindings !== void 0 && rowBindings.length > 0) {
|
|
1455
|
-
bound.bindingDisposers = wireRowBindings(next, rowBindings);
|
|
1456
|
-
}
|
|
1457
|
-
items.push(bound);
|
|
1458
|
-
next = next.nextElementSibling;
|
|
1459
|
-
}
|
|
1460
|
-
}
|
|
1461
|
-
const binding = { liveParent, items, marker };
|
|
1462
|
-
if (items.length > 0) {
|
|
1463
|
-
devHooks.missingRowKey?.(items[0].node, items[0].html, binding);
|
|
1464
|
-
}
|
|
1465
|
-
devHooks.eachInMorphSkip?.(id, liveParent, rootEl);
|
|
1466
|
-
bindings.set(id, binding);
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
function validateInlinedRowMatch(expectedHtml, index, boundEl, liveParent) {
|
|
1470
|
-
if (boundEl.outerHTML === expectedHtml) return;
|
|
1471
|
-
const { content, count } = parseRowTemplate(expectedHtml, liveParent);
|
|
1472
|
-
if (count !== 1) throw rowContractError(index, expectedHtml, liveParent);
|
|
1473
|
-
const expectedTag = content.firstElementChild.tagName;
|
|
1474
|
-
if (boundEl.tagName !== expectedTag) throw rowStructureError(index, boundEl.tagName, expectedTag);
|
|
1475
|
-
}
|
|
1476
|
-
function rowStructureError(index, gotTag, wantTag) {
|
|
1477
|
-
const got = gotTag.toLowerCase();
|
|
1478
|
-
const want = wantTag.toLowerCase();
|
|
1479
|
-
return new Error(
|
|
1480
|
-
`each(): row ${index} renders <${want}>, but the HTML parser wrapped the rows in <${got}> \u2014 so kerf cannot bind one row per element. This happens when an each() of <${want}> sits directly inside a table: the parser inserts <${got}> around the whole run. Put the each() inside an explicit <${got}> (e.g. <table><${got}>{each(...)}</${got}></table>) so the rows are the direct children kerf binds.`
|
|
1481
|
-
);
|
|
1482
|
-
}
|
|
1483
|
-
function collectOwnedItems(bindings) {
|
|
1484
|
-
const owned = /* @__PURE__ */ new Set();
|
|
1485
|
-
for (const b of bindings.values()) {
|
|
1486
|
-
for (const item of b.items) owned.add(item.node);
|
|
1487
|
-
}
|
|
1488
|
-
return owned;
|
|
1489
|
-
}
|
|
1490
|
-
function cleanupOrphanBindings(segment, bindings, renderCtx) {
|
|
1491
|
-
const liveIds = collectLists(segment);
|
|
1492
|
-
for (const [id, binding] of bindings) {
|
|
1493
|
-
if (liveIds.has(id)) continue;
|
|
1494
|
-
for (const item of binding.items) {
|
|
1495
|
-
disposeRowBindings(item.bindingDisposers);
|
|
1496
|
-
if (item.node.parentElement !== null) {
|
|
1497
|
-
item.node.parentElement.removeChild(item.node);
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
if (binding.marker.parentElement !== null) {
|
|
1501
|
-
binding.marker.parentElement.removeChild(binding.marker);
|
|
1502
|
-
}
|
|
1503
|
-
bindings.delete(id);
|
|
1504
|
-
renderCtx.bindingCounts.delete(id);
|
|
1505
|
-
renderCtx.bindingSources.delete(id);
|
|
1506
|
-
renderCtx.caches.delete(id);
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
function collectComments(node, out) {
|
|
1510
|
-
for (let c = node.firstChild; c !== null; c = c.nextSibling) {
|
|
1511
|
-
if (c.nodeType === Node.COMMENT_NODE) out.push(c);
|
|
1512
|
-
else if (c.nodeType === Node.ELEMENT_NODE) collectComments(c, out);
|
|
1513
|
-
}
|
|
12
|
+
// src/renderDocument.ts
|
|
13
|
+
function renderDocument(node, options = {}) {
|
|
14
|
+
const { doctype = "html" } = options;
|
|
15
|
+
return `<!DOCTYPE ${doctype}>${node.toString()}`;
|
|
1514
16
|
}
|
|
1515
17
|
|
|
1516
18
|
// src/toElement.ts
|
|
@@ -1601,6 +103,6 @@ function toElement(jsx) {
|
|
|
1601
103
|
return adopt(content);
|
|
1602
104
|
}
|
|
1603
105
|
|
|
1604
|
-
export {
|
|
106
|
+
export { renderDocument, toElement };
|
|
1605
107
|
//# sourceMappingURL=index.js.map
|
|
1606
108
|
//# sourceMappingURL=index.js.map
|