kerfjs 1.0.2 → 2.0.1
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 +36 -0
- package/LICENSE +29 -1
- package/README.md +53 -17
- package/ai/cursorrules +21 -6
- package/ai/manifest.json +5 -5
- package/ai/skill.md +23 -6
- package/dist/array-signal.js +1 -1
- package/dist/{chunk-QNYOMGI4.js → chunk-GYRZQCSY.js} +78 -17
- package/dist/chunk-GYRZQCSY.js.map +1 -0
- package/dist/chunk-KFUDM3VP.js +131 -0
- package/dist/chunk-KFUDM3VP.js.map +1 -0
- package/dist/{chunk-4E26PO2C.js → chunk-NU7YHYEV.js} +13 -5
- package/dist/chunk-NU7YHYEV.js.map +1 -0
- package/dist/html.d.ts +50 -0
- package/dist/html.js +156 -0
- package/dist/html.js.map +1 -0
- package/dist/index.d.ts +70 -34
- package/dist/index.js +239 -126
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime.d.ts +96 -55
- package/dist/jsx-runtime.js +2 -2
- package/dist/testing.js +2 -2
- package/llms.txt +6 -5
- package/package.json +7 -2
- package/dist/chunk-4E26PO2C.js.map +0 -1
- package/dist/chunk-7SKIIA5P.js +0 -70
- package/dist/chunk-7SKIIA5P.js.map +0 -1
- package/dist/chunk-QNYOMGI4.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { captureRowBindings, isSafeHtml, listSafeHtml, newBindingContext, _setBindingContext, flattenWithoutListItems, wireBindings, collectLists, disposeRowBindings, flatten, wireRowBindings, granularListSafeHtml } from './chunk-
|
|
2
|
-
export { Fragment, SafeHtml, isSafeHtml, raw } from './chunk-
|
|
3
|
-
export { defineStore, resetAllStores } from './chunk-
|
|
4
|
-
import { warnIfInsideEffect, effect } from './chunk-
|
|
5
|
-
export { batch, computed, effect, signal } from './chunk-
|
|
1
|
+
import { captureRowBindings, isSafeHtml, listSafeHtml, boundTextNodeOf, syncFormProp, newBindingContext, _setBindingContext, flattenWithoutListItems, wireBindings, collectLists, disposeRowBindings, flatten, LIST_MARKER_PREFIX, wireRowBindings, granularListSafeHtml, carryOrRewireRowBindings } from './chunk-GYRZQCSY.js';
|
|
2
|
+
export { Fragment, SafeHtml, isSafeHtml, raw } from './chunk-GYRZQCSY.js';
|
|
3
|
+
export { defineStore, resetAllStores } from './chunk-KFUDM3VP.js';
|
|
4
|
+
import { warnIfInsideEffect, effect, isDevMode } from './chunk-NU7YHYEV.js';
|
|
5
|
+
export { batch, computed, effect, signal } from './chunk-NU7YHYEV.js';
|
|
6
6
|
|
|
7
7
|
// src/attrSelector.ts
|
|
8
8
|
function cssEscapeIdent(value) {
|
|
@@ -94,33 +94,30 @@ function assertValidSelector(selector, fn) {
|
|
|
94
94
|
);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
function
|
|
98
|
-
|
|
99
|
-
warnIfInsideEffect("delegate");
|
|
100
|
-
const listener = (event) => {
|
|
97
|
+
function makeListener(rootEl, selector, handler, match) {
|
|
98
|
+
return (event) => {
|
|
101
99
|
const target = event.target;
|
|
102
100
|
if (!(target instanceof Element)) return;
|
|
103
|
-
const matched = target.closest(selector);
|
|
101
|
+
const matched = match === "direct" ? target.matches(selector) ? target : null : target.closest(selector);
|
|
104
102
|
if (matched !== null && rootEl.contains(matched)) {
|
|
105
103
|
handler(event, matched);
|
|
106
104
|
}
|
|
107
105
|
};
|
|
106
|
+
}
|
|
107
|
+
function delegate(rootEl, type, selector, handler, options) {
|
|
108
|
+
assertValidSelector(selector, "delegate");
|
|
109
|
+
warnIfInsideEffect("delegate");
|
|
110
|
+
const listener = makeListener(rootEl, selector, handler, options?.match ?? "closest");
|
|
108
111
|
const capture = NON_BUBBLING.has(type);
|
|
109
112
|
rootEl.addEventListener(type, listener, capture);
|
|
110
113
|
return () => {
|
|
111
114
|
rootEl.removeEventListener(type, listener, capture);
|
|
112
115
|
};
|
|
113
116
|
}
|
|
114
|
-
function delegateCapture(rootEl, type, selector, handler) {
|
|
117
|
+
function delegateCapture(rootEl, type, selector, handler, options) {
|
|
115
118
|
assertValidSelector(selector, "delegateCapture");
|
|
116
119
|
warnIfInsideEffect("delegateCapture");
|
|
117
|
-
const listener = (
|
|
118
|
-
const target = event.target;
|
|
119
|
-
if (!(target instanceof Element)) return;
|
|
120
|
-
if (target.matches(selector) && rootEl.contains(target)) {
|
|
121
|
-
handler(event, target);
|
|
122
|
-
}
|
|
123
|
-
};
|
|
120
|
+
const listener = makeListener(rootEl, selector, handler, options?.match ?? "closest");
|
|
124
121
|
rootEl.addEventListener(type, listener, true);
|
|
125
122
|
return () => {
|
|
126
123
|
rootEl.removeEventListener(type, listener, true);
|
|
@@ -130,8 +127,8 @@ function delegateCapture(rootEl, type, selector, handler) {
|
|
|
130
127
|
// src/dev-each-warn.ts
|
|
131
128
|
var warnedIds = /* @__PURE__ */ new Set();
|
|
132
129
|
function isOptedIn() {
|
|
130
|
+
if (!isDevMode()) return false;
|
|
133
131
|
const proc = globalThis.process;
|
|
134
|
-
if (proc?.env?.NODE_ENV === "production") return false;
|
|
135
132
|
return proc?.env?.KERF_DEV_WARN_EACH_IN_MORPH_SKIP === "1";
|
|
136
133
|
}
|
|
137
134
|
function hasMorphSkipAncestor(el, root) {
|
|
@@ -153,8 +150,8 @@ function maybeWarnEachInMorphSkip(id, liveParent, rootEl) {
|
|
|
153
150
|
}
|
|
154
151
|
var warnedDupIds = /* @__PURE__ */ new Set();
|
|
155
152
|
function isOptedInDupKeys() {
|
|
153
|
+
if (!isDevMode()) return false;
|
|
156
154
|
const proc = globalThis.process;
|
|
157
|
-
if (proc?.env?.NODE_ENV === "production") return false;
|
|
158
155
|
return proc?.env?.KERF_DEV_WARN_DUPLICATE_EACH_KEYS === "1";
|
|
159
156
|
}
|
|
160
157
|
function maybeWarnDuplicateCacheKeys(id, segItems) {
|
|
@@ -173,6 +170,28 @@ function maybeWarnDuplicateCacheKeys(id, segItems) {
|
|
|
173
170
|
}
|
|
174
171
|
}
|
|
175
172
|
|
|
173
|
+
// src/list-render-state.ts
|
|
174
|
+
function deriveListRenderState(bindingCount) {
|
|
175
|
+
if (bindingCount === void 0) return "unbound";
|
|
176
|
+
return bindingCount === 0 ? "empty" : "bound";
|
|
177
|
+
}
|
|
178
|
+
function decideListPath(state, patches, snapshotLength, previousBindingCount) {
|
|
179
|
+
if (state === "unbound") return { path: "snapshot", reason: "first-render" };
|
|
180
|
+
if (state === "empty") return { path: "snapshot", reason: "empty-binding" };
|
|
181
|
+
if (patches.length === 0) return { path: "snapshot", reason: "no-patches" };
|
|
182
|
+
let netDelta = 0;
|
|
183
|
+
for (const p of patches) {
|
|
184
|
+
if (p.type === "insert") netDelta += 1;
|
|
185
|
+
else if (p.type === "remove") netDelta -= 1;
|
|
186
|
+
else if (p.type === "replace") return { path: "snapshot", reason: "replace" };
|
|
187
|
+
}
|
|
188
|
+
const count = previousBindingCount ?? 0;
|
|
189
|
+
if (count + netDelta !== snapshotLength) {
|
|
190
|
+
return { path: "snapshot", reason: "count-drift" };
|
|
191
|
+
}
|
|
192
|
+
return { path: "granular" };
|
|
193
|
+
}
|
|
194
|
+
|
|
176
195
|
// src/each.ts
|
|
177
196
|
var ARRAY_SIGNAL_BRAND = /* @__PURE__ */ Symbol.for("kerfjs.ArraySignal");
|
|
178
197
|
function isArraySignal(value) {
|
|
@@ -204,18 +223,13 @@ function eachGranular(sig, render, cacheKey) {
|
|
|
204
223
|
const previousBindingCount = ctx.bindingCounts.get(id);
|
|
205
224
|
const patches = sig._consumePatches();
|
|
206
225
|
const snapshot = sig.value;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
else if (p.type === "replace") {
|
|
215
|
-
return eachSnapshotById(snapshot, render, cacheKey, id);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (previousBindingCount + netDelta !== snapshot.length) {
|
|
226
|
+
const decision = decideListPath(
|
|
227
|
+
deriveListRenderState(previousBindingCount),
|
|
228
|
+
patches,
|
|
229
|
+
snapshot.length,
|
|
230
|
+
previousBindingCount
|
|
231
|
+
);
|
|
232
|
+
if (decision.path === "snapshot") {
|
|
219
233
|
return eachSnapshotById(snapshot, render, cacheKey, id);
|
|
220
234
|
}
|
|
221
235
|
if (cacheKey !== void 0) {
|
|
@@ -322,6 +336,11 @@ function getNodeKey(node) {
|
|
|
322
336
|
}
|
|
323
337
|
var EMPTY_OWNED = /* @__PURE__ */ new Set();
|
|
324
338
|
function morph(liveRoot, template, ownedItems = EMPTY_OWNED) {
|
|
339
|
+
if (liveRoot == null) {
|
|
340
|
+
throw new Error(
|
|
341
|
+
'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.'
|
|
342
|
+
);
|
|
343
|
+
}
|
|
325
344
|
const templateEl = isElementNode(template) ? template : parseTemplate(liveRoot, template);
|
|
326
345
|
morphChildren(liveRoot, templateEl, ownedItems);
|
|
327
346
|
}
|
|
@@ -367,6 +386,12 @@ function morphChildren(fromParent, toParent, ownedItems) {
|
|
|
367
386
|
if (matched === null && fromChild !== null && fromChild.nodeType === toChild.nodeType && (toChild.nodeType !== ELEMENT_NODE || fromChild.tagName === toChild.tagName && getNodeKey(fromChild) === void 0)) {
|
|
368
387
|
matched = fromChild;
|
|
369
388
|
fromChild = skipOwned(fromChild.nextSibling, ownedItems);
|
|
389
|
+
if (matched.nodeType === COMMENT_NODE && fromChild !== null) {
|
|
390
|
+
const owned = boundTextNodeOf(matched);
|
|
391
|
+
if (owned !== null && fromChild === owned) {
|
|
392
|
+
fromChild = skipOwned(owned.nextSibling, ownedItems);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
370
395
|
}
|
|
371
396
|
if (matched !== null) {
|
|
372
397
|
morphNode(matched, toChild, ownedItems);
|
|
@@ -415,7 +440,11 @@ function morphElement(fromEl, toEl, ownedItems) {
|
|
|
415
440
|
}
|
|
416
441
|
morphAttributes(fromEl, toEl);
|
|
417
442
|
if (fromEl.dataset.morphSkipChildren !== void 0) return;
|
|
443
|
+
const syncTextareaValue = fromEl.tagName === "TEXTAREA" && fromEl !== document.activeElement && fromEl.textContent !== toEl.textContent;
|
|
418
444
|
morphChildren(fromEl, toEl, ownedItems);
|
|
445
|
+
if (syncTextareaValue) {
|
|
446
|
+
fromEl.value = toEl.textContent;
|
|
447
|
+
}
|
|
419
448
|
}
|
|
420
449
|
function isUserAgentOwnedAttr(tagName, name) {
|
|
421
450
|
return name === "open" && (tagName === "DETAILS" || tagName === "DIALOG");
|
|
@@ -433,6 +462,7 @@ function morphAttributes(fromEl, toEl) {
|
|
|
433
462
|
}
|
|
434
463
|
} else if (fromEl.getAttribute(name) !== value) {
|
|
435
464
|
fromEl.setAttribute(name, value);
|
|
465
|
+
syncFormProp(fromEl, name, value, true);
|
|
436
466
|
}
|
|
437
467
|
}
|
|
438
468
|
const fromAttrs = fromEl.attributes;
|
|
@@ -445,6 +475,7 @@ function morphAttributes(fromEl, toEl) {
|
|
|
445
475
|
if (!toEl.hasAttributeNS(ns, name)) fromEl.removeAttributeNS(ns, name);
|
|
446
476
|
} else if (!toEl.hasAttribute(name) && !isUserAgentOwnedAttr(fromTag, name)) {
|
|
447
477
|
fromEl.removeAttribute(name);
|
|
478
|
+
syncFormProp(fromEl, name, "", false);
|
|
448
479
|
}
|
|
449
480
|
}
|
|
450
481
|
}
|
|
@@ -468,13 +499,37 @@ function preserveTextEntryState(fromEl, toEl) {
|
|
|
468
499
|
}
|
|
469
500
|
}
|
|
470
501
|
|
|
502
|
+
// src/dev-binding-warn.ts
|
|
503
|
+
var warnedHoles = /* @__PURE__ */ new Set();
|
|
504
|
+
function isOptedIn2() {
|
|
505
|
+
if (!isDevMode()) return false;
|
|
506
|
+
const proc = globalThis.process;
|
|
507
|
+
return proc?.env?.KERF_DEV_WARN_STALE_BINDING === "1";
|
|
508
|
+
}
|
|
509
|
+
function describeHole(b) {
|
|
510
|
+
return b.kind === "attr" ? `attr '${b.attr}' (id '${b.id}')` : `text (id '${b.id}')`;
|
|
511
|
+
}
|
|
512
|
+
function maybeWarnStaleBinding(prevWired, current) {
|
|
513
|
+
if (!isOptedIn2()) return;
|
|
514
|
+
const n = Math.min(prevWired.length, current.length);
|
|
515
|
+
for (let i = 0; i < n; i++) {
|
|
516
|
+
const cur = current[i];
|
|
517
|
+
if (prevWired[i].signal === cur.signal) continue;
|
|
518
|
+
if (warnedHoles.has(cur.id)) continue;
|
|
519
|
+
warnedHoles.add(cur.id);
|
|
520
|
+
console.warn(
|
|
521
|
+
`kerf: fine-grained binding ${describeHole(cur)} switched to a different signal instance on a render whose static-surrounds HTML was byte-for-byte unchanged. On that fast path kerf keeps the original binding effect and does NOT re-bind, so this hole is now stale \u2014 it still tracks the FIRST signal instance and will not reflect the new one. Bind one computed that switches internally (e.g. class={computed(() => cond.value ? sigA.value : sigB.value)}) instead of switching which signal instance you bind (see docs/2-reactivity \xA72.9). Set KERF_DEV_WARN_STALE_BINDING=0 (or unset it) to silence this warning.`
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
471
526
|
// src/dev-listener-warn.ts
|
|
472
527
|
var LISTENER_MARKER = /* @__PURE__ */ Symbol.for("kerfjs.devListener");
|
|
473
528
|
var patched = false;
|
|
474
529
|
var warned = false;
|
|
475
|
-
function
|
|
530
|
+
function isOptedIn3() {
|
|
531
|
+
if (!isDevMode()) return false;
|
|
476
532
|
const proc = globalThis.process;
|
|
477
|
-
if (proc?.env?.NODE_ENV === "production") return false;
|
|
478
533
|
return proc?.env?.KERF_DEV_WARN_REBUILT_LISTENERS === "1";
|
|
479
534
|
}
|
|
480
535
|
function findAddEventListenerProto() {
|
|
@@ -516,7 +571,7 @@ function emitWarning() {
|
|
|
516
571
|
);
|
|
517
572
|
}
|
|
518
573
|
function installListenerRebuildWarn(rootEl) {
|
|
519
|
-
if (!
|
|
574
|
+
if (!isOptedIn3()) return null;
|
|
520
575
|
patchAddEventListenerOnce();
|
|
521
576
|
const observer = new MutationObserver((mutations) => {
|
|
522
577
|
if (warned) return;
|
|
@@ -535,6 +590,47 @@ function installListenerRebuildWarn(rootEl) {
|
|
|
535
590
|
return observer;
|
|
536
591
|
}
|
|
537
592
|
|
|
593
|
+
// src/dev-rerender-warn.ts
|
|
594
|
+
function isOptedIn4() {
|
|
595
|
+
const proc = globalThis.process;
|
|
596
|
+
if (proc?.env?.KERF_DEV_WARN_VALUE_ONLY_RERENDER !== "1") return false;
|
|
597
|
+
return isDevMode();
|
|
598
|
+
}
|
|
599
|
+
var ELEMENT_NODE2 = 1;
|
|
600
|
+
var TEXT_NODE2 = 3;
|
|
601
|
+
var COMMENT_NODE2 = 8;
|
|
602
|
+
function _isValueOnlyDiff(a, b) {
|
|
603
|
+
const an = a.childNodes;
|
|
604
|
+
const bn = b.childNodes;
|
|
605
|
+
if (an.length !== bn.length) return false;
|
|
606
|
+
for (let i = 0; i < an.length; i++) {
|
|
607
|
+
const x = an[i];
|
|
608
|
+
const y = bn[i];
|
|
609
|
+
if (x.nodeType !== y.nodeType) return false;
|
|
610
|
+
if (x.nodeType === ELEMENT_NODE2) {
|
|
611
|
+
if (x.tagName !== y.tagName) return false;
|
|
612
|
+
if (!_isValueOnlyDiff(x, y)) return false;
|
|
613
|
+
} else if (x.nodeType === COMMENT_NODE2) {
|
|
614
|
+
if (x.data !== y.data) return false;
|
|
615
|
+
} else if (x.nodeType !== TEXT_NODE2) {
|
|
616
|
+
return false;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return true;
|
|
620
|
+
}
|
|
621
|
+
function maybeWarnValueOnlyRerender(prevHtml, nextHtml, ctx) {
|
|
622
|
+
if (ctx.warned || !isOptedIn4()) return;
|
|
623
|
+
const a = document.createElement("template");
|
|
624
|
+
const b = document.createElement("template");
|
|
625
|
+
a.innerHTML = prevHtml;
|
|
626
|
+
b.innerHTML = nextHtml;
|
|
627
|
+
if (!_isValueOnlyDiff(a.content, b.content)) return;
|
|
628
|
+
ctx.warned = true;
|
|
629
|
+
console.warn(
|
|
630
|
+
"kerf: this re-render changed only text content and attribute values \u2014 no structural change \u2014 so every changed hole could be a fine-grained binding instead. Values bind, structure re-renders: pass the signal/computed itself ({count}, class={sig}) rather than reading .value in the hole, and each change updates just that node with no render re-run (a mount whose render reads no .value never re-renders at all). See docs/2-reactivity \xA72.9. Set KERF_DEV_WARN_VALUE_ONLY_RERENDER=0 (or unset it) to silence this warning."
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
|
|
538
634
|
// src/list-binding.ts
|
|
539
635
|
function endAnchor(binding) {
|
|
540
636
|
if (binding.items.length > 0) {
|
|
@@ -551,8 +647,8 @@ var SQUOTE = 39;
|
|
|
551
647
|
var AMP = 38;
|
|
552
648
|
var EQ = 61;
|
|
553
649
|
var SLASH = 47;
|
|
554
|
-
var
|
|
555
|
-
var
|
|
650
|
+
var TEXT_NODE3 = 3;
|
|
651
|
+
var ELEMENT_NODE3 = 1;
|
|
556
652
|
function isWhitespace(cc) {
|
|
557
653
|
return cc === 32 || cc === 9 || cc === 10 || cc === 13;
|
|
558
654
|
}
|
|
@@ -611,6 +707,7 @@ function tryTextContentFastPath(liveNode, oldHtml, newHtml) {
|
|
|
611
707
|
const newTextEnd = textEnd + (newHtml.length - oldHtml.length);
|
|
612
708
|
const oldText = oldHtml.slice(textStart + 1, textEnd);
|
|
613
709
|
const newText = newHtml.slice(textStart + 1, newTextEnd);
|
|
710
|
+
if (oldHtml.lastIndexOf("<!--kfb", textStart) !== -1) return false;
|
|
614
711
|
const textIdx = countTextNodesBefore(oldHtml, textStart + 1);
|
|
615
712
|
const targetNode = nthTextNodeDescendant(liveNode, textIdx);
|
|
616
713
|
if (targetNode === null) return false;
|
|
@@ -655,13 +752,13 @@ function nthTextNodeDescendant(root, n) {
|
|
|
655
752
|
function walk(node) {
|
|
656
753
|
for (let c = node.firstChild; c !== null; c = c.nextSibling) {
|
|
657
754
|
if (result !== null) return;
|
|
658
|
-
if (c.nodeType ===
|
|
755
|
+
if (c.nodeType === TEXT_NODE3) {
|
|
659
756
|
if (count === n) {
|
|
660
757
|
result = c;
|
|
661
758
|
return;
|
|
662
759
|
}
|
|
663
760
|
count++;
|
|
664
|
-
} else if (c.nodeType ===
|
|
761
|
+
} else if (c.nodeType === ELEMENT_NODE3) {
|
|
665
762
|
walk(c);
|
|
666
763
|
}
|
|
667
764
|
}
|
|
@@ -760,6 +857,20 @@ function parseRowTemplate(html) {
|
|
|
760
857
|
tpl.innerHTML = html;
|
|
761
858
|
return { tpl, count: tpl.content.children.length };
|
|
762
859
|
}
|
|
860
|
+
function parseSingleRow(html, index) {
|
|
861
|
+
const { tpl, count } = parseRowTemplate(html);
|
|
862
|
+
if (count !== 1) throw rowContractError(index, html);
|
|
863
|
+
return tpl.content.firstElementChild;
|
|
864
|
+
}
|
|
865
|
+
function collectTemplateChildren(tpl, n) {
|
|
866
|
+
const nodes = new Array(n);
|
|
867
|
+
let child = tpl.content.firstElementChild;
|
|
868
|
+
for (let k = 0; k < n; k++) {
|
|
869
|
+
nodes[k] = child;
|
|
870
|
+
child = child.nextElementSibling;
|
|
871
|
+
}
|
|
872
|
+
return nodes;
|
|
873
|
+
}
|
|
763
874
|
function rowContractError(index, html) {
|
|
764
875
|
const { count } = parseRowTemplate(html);
|
|
765
876
|
const reason = count === 0 ? "produced no top-level element" : `produced ${count} top-level elements; exactly one is required`;
|
|
@@ -767,17 +878,13 @@ function rowContractError(index, html) {
|
|
|
767
878
|
`each(): row render at index ${index} ${reason}. Each item's render must return exactly one element \u2014 wrap multiple roots in a single parent (e.g. <li>...</li>). Got HTML: ${JSON.stringify(truncateRowHtml(html))}`
|
|
768
879
|
);
|
|
769
880
|
}
|
|
770
|
-
function
|
|
771
|
-
const proc = globalThis.process;
|
|
772
|
-
return proc?.env?.NODE_ENV !== "production";
|
|
773
|
-
}
|
|
774
|
-
function maybeWarnMissingRowKey(rowEl, rowIndex, rowHtml, binding) {
|
|
881
|
+
function maybeWarnMissingRowKey(rowEl, rowHtml, binding) {
|
|
775
882
|
if (!isDevMode()) return;
|
|
776
883
|
if (binding.warnedMissingKey === true) return;
|
|
777
884
|
binding.warnedMissingKey = true;
|
|
778
885
|
if (rowEl.id !== "" || rowEl.hasAttribute("data-key")) return;
|
|
779
886
|
console.warn(
|
|
780
|
-
`kerf each():
|
|
887
|
+
`kerf each(): the first row has no \`id\` or \`data-key\` attribute. Without one, rows match positionally \u2014 an insert/remove at the head shifts every row's identity, so focused inputs jump to the wrong row, mid-edit textareas swap content with their neighbor, and any per-row state silently follows the wrong item. Add \`data-key={item.id}\` (or set \`id\`) to the top-level element returned by the row render. Row HTML: ${JSON.stringify(truncateRowHtml(rowHtml))}`
|
|
781
888
|
);
|
|
782
889
|
}
|
|
783
890
|
|
|
@@ -843,12 +950,12 @@ function reconcileGranular(binding, patches) {
|
|
|
843
950
|
}
|
|
844
951
|
if (focusSnap !== null) restoreFocus(focusSnap);
|
|
845
952
|
if (items.length > 0) {
|
|
846
|
-
maybeWarnMissingRowKey(items[0].node,
|
|
953
|
+
maybeWarnMissingRowKey(items[0].node, items[0].html, binding);
|
|
847
954
|
}
|
|
848
955
|
}
|
|
849
956
|
function applySingleInsert(liveParent, items, patch, tailAnchor) {
|
|
850
957
|
const { html } = patch;
|
|
851
|
-
const newNode = parseSingleRow(html);
|
|
958
|
+
const newNode = parseSingleRow(html, patch.index);
|
|
852
959
|
const anchor = patch.index < items.length ? items[patch.index].node : tailAnchor;
|
|
853
960
|
liveParent.insertBefore(newNode, anchor);
|
|
854
961
|
items.splice(patch.index, 0, {
|
|
@@ -867,12 +974,19 @@ function wireRowIfBound(node, bindings) {
|
|
|
867
974
|
function applySingleUpdate(liveParent, items, patch) {
|
|
868
975
|
const { html } = patch;
|
|
869
976
|
const oldEntry = items[patch.index];
|
|
870
|
-
if (html === oldEntry.html)
|
|
977
|
+
if (html === oldEntry.html) {
|
|
978
|
+
items[patch.index] = reuseBound(patch, html, oldEntry);
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
871
981
|
if (tryAttributeOnlyFastPath(oldEntry.node, oldEntry.html, html) || tryTextContentFastPath(oldEntry.node, oldEntry.html, html)) {
|
|
872
982
|
items[patch.index] = reuseBound(patch, html, oldEntry);
|
|
873
983
|
return;
|
|
874
984
|
}
|
|
875
|
-
const newNode = parseSingleRow(html);
|
|
985
|
+
const newNode = parseSingleRow(html, patch.index);
|
|
986
|
+
applyParsedRowUpdate(liveParent, items, patch, html, newNode);
|
|
987
|
+
}
|
|
988
|
+
function applyParsedRowUpdate(liveParent, items, patch, html, newNode) {
|
|
989
|
+
const oldEntry = items[patch.index];
|
|
876
990
|
if (oldEntry.node.tagName === newNode.tagName) {
|
|
877
991
|
_morphElement(oldEntry.node, newNode);
|
|
878
992
|
items[patch.index] = reuseBound(patch, html, oldEntry);
|
|
@@ -890,13 +1004,19 @@ function applySingleUpdate(liveParent, items, patch) {
|
|
|
890
1004
|
}
|
|
891
1005
|
}
|
|
892
1006
|
function reuseBound(patch, html, oldEntry) {
|
|
1007
|
+
const kept = carryOrRewireRowBindings(
|
|
1008
|
+
oldEntry.node,
|
|
1009
|
+
oldEntry.bindings,
|
|
1010
|
+
oldEntry.bindingDisposers,
|
|
1011
|
+
patch.bindings
|
|
1012
|
+
);
|
|
893
1013
|
return {
|
|
894
1014
|
ref: patch.item,
|
|
895
1015
|
cacheKey: void 0,
|
|
896
1016
|
html,
|
|
897
1017
|
node: oldEntry.node,
|
|
898
|
-
bindings:
|
|
899
|
-
bindingDisposers:
|
|
1018
|
+
bindings: kept.bindings,
|
|
1019
|
+
bindingDisposers: kept.bindingDisposers
|
|
900
1020
|
};
|
|
901
1021
|
}
|
|
902
1022
|
function applyBulkUpdate(liveParent, items, patches, start, end) {
|
|
@@ -904,7 +1024,10 @@ function applyBulkUpdate(liveParent, items, patches, start, end) {
|
|
|
904
1024
|
for (let k = start; k < end; k++) {
|
|
905
1025
|
const p = patches[k];
|
|
906
1026
|
const oldEntry = items[p.index];
|
|
907
|
-
if (p.html === oldEntry.html)
|
|
1027
|
+
if (p.html === oldEntry.html) {
|
|
1028
|
+
items[p.index] = reuseBound(p, p.html, oldEntry);
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
908
1031
|
if (tryAttributeOnlyFastPath(oldEntry.node, oldEntry.html, p.html) || tryTextContentFastPath(oldEntry.node, oldEntry.html, p.html)) {
|
|
909
1032
|
items[p.index] = reuseBound(p, p.html, oldEntry);
|
|
910
1033
|
continue;
|
|
@@ -916,50 +1039,24 @@ function applyBulkUpdate(liveParent, items, patches, start, end) {
|
|
|
916
1039
|
if (count !== morphChanges.length) {
|
|
917
1040
|
throw findOffendingChange(patches, morphChanges);
|
|
918
1041
|
}
|
|
919
|
-
const newNodes =
|
|
920
|
-
let child = tpl.content.firstElementChild;
|
|
921
|
-
for (let k = 0; k < newNodes.length; k++) {
|
|
922
|
-
newNodes[k] = child;
|
|
923
|
-
child = child.nextElementSibling;
|
|
924
|
-
}
|
|
1042
|
+
const newNodes = collectTemplateChildren(tpl, morphChanges.length);
|
|
925
1043
|
for (let k = 0; k < morphChanges.length; k++) {
|
|
926
1044
|
const c = morphChanges[k];
|
|
927
1045
|
const p = patches[c.patchIdx];
|
|
928
|
-
|
|
929
|
-
if (oldEntry.node.tagName === newNodes[k].tagName) {
|
|
930
|
-
_morphElement(oldEntry.node, newNodes[k]);
|
|
931
|
-
items[p.index] = reuseBound(p, c.html, oldEntry);
|
|
932
|
-
} else {
|
|
933
|
-
disposeRowBindings(oldEntry.bindingDisposers);
|
|
934
|
-
liveParent.replaceChild(newNodes[k], oldEntry.node);
|
|
935
|
-
items[p.index] = {
|
|
936
|
-
ref: p.item,
|
|
937
|
-
cacheKey: void 0,
|
|
938
|
-
html: c.html,
|
|
939
|
-
node: newNodes[k],
|
|
940
|
-
bindings: p.bindings,
|
|
941
|
-
bindingDisposers: wireRowIfBound(newNodes[k], p.bindings)
|
|
942
|
-
};
|
|
943
|
-
}
|
|
1046
|
+
applyParsedRowUpdate(liveParent, items, p, c.html, newNodes[k]);
|
|
944
1047
|
}
|
|
945
1048
|
}
|
|
946
1049
|
function applyBulkInsert(liveParent, items, patches, start, end, tailAnchor) {
|
|
947
1050
|
const startIdx = patches[start].index;
|
|
948
1051
|
const htmls = new Array(end - start);
|
|
949
1052
|
for (let k = start; k < end; k++) {
|
|
950
|
-
|
|
951
|
-
htmls[k - start] = p.html;
|
|
1053
|
+
htmls[k - start] = patches[k].html;
|
|
952
1054
|
}
|
|
953
1055
|
const { tpl, count } = parseRowTemplate(htmls.join(""));
|
|
954
1056
|
if (count !== htmls.length) {
|
|
955
1057
|
throw findOffendingInsert(patches, start, htmls);
|
|
956
1058
|
}
|
|
957
|
-
const newNodes =
|
|
958
|
-
let child = tpl.content.firstElementChild;
|
|
959
|
-
for (let k = 0; k < newNodes.length; k++) {
|
|
960
|
-
newNodes[k] = child;
|
|
961
|
-
child = child.nextElementSibling;
|
|
962
|
-
}
|
|
1059
|
+
const newNodes = collectTemplateChildren(tpl, end - start);
|
|
963
1060
|
const anchor = startIdx < items.length ? items[startIdx].node : tailAnchor;
|
|
964
1061
|
liveParent.insertBefore(tpl.content, anchor);
|
|
965
1062
|
const newEntries = new Array(end - start);
|
|
@@ -977,21 +1074,10 @@ function applyBulkInsert(liveParent, items, patches, start, end, tailAnchor) {
|
|
|
977
1074
|
}
|
|
978
1075
|
items.splice(startIdx, 0, ...newEntries);
|
|
979
1076
|
}
|
|
980
|
-
function parseSingleRow(html) {
|
|
981
|
-
const { tpl, count } = parseRowTemplate(html);
|
|
982
|
-
if (count !== 1) {
|
|
983
|
-
const reason = count === 0 ? "produced no top-level element" : `produced ${count} top-level elements; exactly one is required`;
|
|
984
|
-
throw new Error(
|
|
985
|
-
`each() granular reconcile: row render ${reason}. Each item's render must return exactly one element. Got HTML: ${JSON.stringify(truncateRowHtml(html))}`
|
|
986
|
-
);
|
|
987
|
-
}
|
|
988
|
-
return tpl.content.firstElementChild;
|
|
989
|
-
}
|
|
990
1077
|
function findOffendingInsert(patches, start, htmls) {
|
|
991
1078
|
for (let i = 0; i < htmls.length; i++) {
|
|
992
1079
|
if (parseRowTemplate(htmls[i]).count !== 1) {
|
|
993
|
-
|
|
994
|
-
return rowContractError(p.index, htmls[i]);
|
|
1080
|
+
return rowContractError(patches[start + i].index, htmls[i]);
|
|
995
1081
|
}
|
|
996
1082
|
}
|
|
997
1083
|
return new Error("each(): bulk-insert mismatch with no per-row offender (kerf bug).");
|
|
@@ -999,8 +1085,7 @@ function findOffendingInsert(patches, start, htmls) {
|
|
|
999
1085
|
function findOffendingChange(patches, changes) {
|
|
1000
1086
|
for (const c of changes) {
|
|
1001
1087
|
if (parseRowTemplate(c.html).count !== 1) {
|
|
1002
|
-
|
|
1003
|
-
return rowContractError(p.index, c.html);
|
|
1088
|
+
return rowContractError(patches[c.patchIdx].index, c.html);
|
|
1004
1089
|
}
|
|
1005
1090
|
}
|
|
1006
1091
|
return new Error("each(): bulk-update mismatch with no per-row offender (kerf bug).");
|
|
@@ -1023,40 +1108,45 @@ function tryInPlaceContentUpdate(binding, listSeg) {
|
|
|
1023
1108
|
}
|
|
1024
1109
|
if (focusSnap !== null) restoreFocus(focusSnap);
|
|
1025
1110
|
binding.items = newRecord;
|
|
1026
|
-
maybeWarnMissingRowKey(newRecord[0].node,
|
|
1111
|
+
maybeWarnMissingRowKey(newRecord[0].node, newRecord[0].html, binding);
|
|
1027
1112
|
return true;
|
|
1028
1113
|
}
|
|
1029
1114
|
function updateRowInPlace(liveParent, old, ni, index) {
|
|
1030
1115
|
if (old.html === ni.html || tryAttributeOnlyFastPath(old.node, old.html, ni.html) || tryTextContentFastPath(old.node, old.html, ni.html)) {
|
|
1116
|
+
const kept = carryOrRewireRowBindings(old.node, old.bindings, old.bindingDisposers, ni.bindings);
|
|
1031
1117
|
return {
|
|
1032
1118
|
ref: ni.ref,
|
|
1033
1119
|
cacheKey: ni.cacheKey,
|
|
1034
1120
|
html: ni.html,
|
|
1035
1121
|
node: old.node,
|
|
1036
|
-
bindings:
|
|
1037
|
-
bindingDisposers:
|
|
1122
|
+
bindings: kept.bindings,
|
|
1123
|
+
bindingDisposers: kept.bindingDisposers
|
|
1038
1124
|
};
|
|
1039
1125
|
}
|
|
1040
|
-
const newNode =
|
|
1126
|
+
const newNode = parseSingleRow(ni.html, index);
|
|
1041
1127
|
if (old.node.tagName === newNode.tagName) {
|
|
1042
1128
|
_morphElement(old.node, newNode);
|
|
1129
|
+
const kept = carryOrRewireRowBindings(old.node, old.bindings, old.bindingDisposers, ni.bindings);
|
|
1043
1130
|
return {
|
|
1044
1131
|
ref: ni.ref,
|
|
1045
1132
|
cacheKey: ni.cacheKey,
|
|
1046
1133
|
html: ni.html,
|
|
1047
1134
|
node: old.node,
|
|
1048
|
-
bindings:
|
|
1049
|
-
bindingDisposers:
|
|
1135
|
+
bindings: kept.bindings,
|
|
1136
|
+
bindingDisposers: kept.bindingDisposers
|
|
1050
1137
|
};
|
|
1051
1138
|
}
|
|
1052
1139
|
disposeRowBindings(old.bindingDisposers);
|
|
1053
1140
|
liveParent.replaceChild(newNode, old.node);
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1141
|
+
const fresh = carryOrRewireRowBindings(newNode, void 0, void 0, ni.bindings);
|
|
1142
|
+
return {
|
|
1143
|
+
ref: ni.ref,
|
|
1144
|
+
cacheKey: ni.cacheKey,
|
|
1145
|
+
html: ni.html,
|
|
1146
|
+
node: newNode,
|
|
1147
|
+
bindings: fresh.bindings,
|
|
1148
|
+
bindingDisposers: fresh.bindingDisposers
|
|
1149
|
+
};
|
|
1060
1150
|
}
|
|
1061
1151
|
|
|
1062
1152
|
// src/list-reconcile-snapshot.ts
|
|
@@ -1072,7 +1162,7 @@ function reconcileSnapshot(binding, listSeg) {
|
|
|
1072
1162
|
if (focusSnap !== null) restoreFocus(focusSnap);
|
|
1073
1163
|
binding.items = newRecord;
|
|
1074
1164
|
if (newRecord.length > 0) {
|
|
1075
|
-
maybeWarnMissingRowKey(newRecord[0].node,
|
|
1165
|
+
maybeWarnMissingRowKey(newRecord[0].node, newRecord[0].html, binding);
|
|
1076
1166
|
}
|
|
1077
1167
|
}
|
|
1078
1168
|
function classifyItems(oldItems, listSeg) {
|
|
@@ -1098,6 +1188,8 @@ function classifyItems(oldItems, listSeg) {
|
|
|
1098
1188
|
removedItems.push(oi[0]);
|
|
1099
1189
|
}
|
|
1100
1190
|
newRecord[i] = {
|
|
1191
|
+
// `node` placeholder is filled by `buildFreshNodes`; its parse-count
|
|
1192
|
+
// check guarantees every fresh index gets a real element before use.
|
|
1101
1193
|
ref: ni.ref,
|
|
1102
1194
|
cacheKey: ni.cacheKey,
|
|
1103
1195
|
html: ni.html,
|
|
@@ -1192,37 +1284,39 @@ function reconcileList(binding, listSeg) {
|
|
|
1192
1284
|
}
|
|
1193
1285
|
|
|
1194
1286
|
// src/mount.ts
|
|
1195
|
-
var LIST_MARKER_PREFIX = "kf-list:";
|
|
1196
1287
|
var MOUNTED_MARKER = /* @__PURE__ */ Symbol.for("kerfjs.mounted");
|
|
1288
|
+
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.";
|
|
1289
|
+
function isMounted(el) {
|
|
1290
|
+
return el[MOUNTED_MARKER] === true;
|
|
1291
|
+
}
|
|
1292
|
+
function setMounted(el, on) {
|
|
1293
|
+
if (on) {
|
|
1294
|
+
el[MOUNTED_MARKER] = true;
|
|
1295
|
+
} else {
|
|
1296
|
+
delete el[MOUNTED_MARKER];
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1197
1299
|
function describeEl(el) {
|
|
1198
1300
|
const tag = el.tagName.toLowerCase();
|
|
1199
1301
|
const id = el.id ? `#${el.id}` : "";
|
|
1200
1302
|
return `<${tag}${id}>`;
|
|
1201
1303
|
}
|
|
1202
1304
|
function assertNotInsideMountedTree(rootEl) {
|
|
1203
|
-
if (rootEl
|
|
1305
|
+
if (isMounted(rootEl)) {
|
|
1204
1306
|
throw new Error(
|
|
1205
1307
|
`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.`
|
|
1206
1308
|
);
|
|
1207
1309
|
}
|
|
1208
1310
|
let ancestor = rootEl.parentElement;
|
|
1209
1311
|
while (ancestor !== null) {
|
|
1210
|
-
if (ancestor
|
|
1211
|
-
throw new Error(
|
|
1212
|
-
"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."
|
|
1213
|
-
);
|
|
1214
|
-
}
|
|
1312
|
+
if (isMounted(ancestor)) throw new Error(NESTED_MOUNT_MSG);
|
|
1215
1313
|
ancestor = ancestor.parentElement;
|
|
1216
1314
|
}
|
|
1217
1315
|
const stack = [];
|
|
1218
1316
|
for (let i = 0; i < rootEl.children.length; i++) stack.push(rootEl.children[i]);
|
|
1219
1317
|
while (stack.length > 0) {
|
|
1220
1318
|
const cur = stack.pop();
|
|
1221
|
-
if (cur
|
|
1222
|
-
throw new Error(
|
|
1223
|
-
"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."
|
|
1224
|
-
);
|
|
1225
|
-
}
|
|
1319
|
+
if (isMounted(cur)) throw new Error(NESTED_MOUNT_MSG);
|
|
1226
1320
|
for (let i = 0; i < cur.children.length; i++) stack.push(cur.children[i]);
|
|
1227
1321
|
}
|
|
1228
1322
|
}
|
|
@@ -1237,7 +1331,7 @@ function mount(rootEl, render) {
|
|
|
1237
1331
|
if (owner.defaultView === null) document.adoptNode(rootEl);
|
|
1238
1332
|
}
|
|
1239
1333
|
assertNotInsideMountedTree(rootEl);
|
|
1240
|
-
rootEl
|
|
1334
|
+
setMounted(rootEl, true);
|
|
1241
1335
|
const listenerWarnObserver = installListenerRebuildWarn(rootEl);
|
|
1242
1336
|
const bindings = /* @__PURE__ */ new Map();
|
|
1243
1337
|
const renderCtx = {
|
|
@@ -1247,8 +1341,10 @@ function mount(rootEl, render) {
|
|
|
1247
1341
|
};
|
|
1248
1342
|
const bindingCtx = newBindingContext();
|
|
1249
1343
|
let bindingDisposers = [];
|
|
1344
|
+
let prevWiredBindings = [];
|
|
1250
1345
|
let isFirst = true;
|
|
1251
1346
|
let prevStaticHtml = "";
|
|
1347
|
+
const valueOnlyWarnCtx = { warned: false };
|
|
1252
1348
|
const disposeEffect = effect(() => {
|
|
1253
1349
|
renderCtx.counter = 0;
|
|
1254
1350
|
bindingCtx.counter = 0;
|
|
@@ -1267,16 +1363,32 @@ function mount(rootEl, render) {
|
|
|
1267
1363
|
runFirstRender(rootEl, segment, bindings);
|
|
1268
1364
|
prevStaticHtml = flattenWithoutListItems(segment);
|
|
1269
1365
|
bindingDisposers = wireBindings(rootEl, bindingCtx, bindingDisposers);
|
|
1366
|
+
if (isOptedIn2()) prevWiredBindings = bindingCtx.list;
|
|
1270
1367
|
isFirst = false;
|
|
1271
1368
|
} else {
|
|
1272
|
-
const nextStaticHtml = runSubsequentRender(
|
|
1369
|
+
const nextStaticHtml = runSubsequentRender(
|
|
1370
|
+
rootEl,
|
|
1371
|
+
segment,
|
|
1372
|
+
bindings,
|
|
1373
|
+
renderCtx,
|
|
1374
|
+
prevStaticHtml,
|
|
1375
|
+
valueOnlyWarnCtx
|
|
1376
|
+
);
|
|
1273
1377
|
if (nextStaticHtml !== prevStaticHtml) {
|
|
1274
1378
|
bindingDisposers = wireBindings(rootEl, bindingCtx, bindingDisposers);
|
|
1379
|
+
if (isOptedIn2()) prevWiredBindings = bindingCtx.list;
|
|
1380
|
+
} else {
|
|
1381
|
+
maybeWarnStaleBinding(prevWiredBindings, bindingCtx.list);
|
|
1275
1382
|
}
|
|
1276
1383
|
prevStaticHtml = nextStaticHtml;
|
|
1277
1384
|
}
|
|
1278
1385
|
for (const listSeg of collectLists(segment).values()) {
|
|
1279
1386
|
const binding = bindings.get(listSeg.id);
|
|
1387
|
+
if (binding === void 0) {
|
|
1388
|
+
throw new Error(
|
|
1389
|
+
"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."
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1280
1392
|
reconcileList(binding, listSeg);
|
|
1281
1393
|
renderCtx.bindingCounts.set(listSeg.id, binding.items.length);
|
|
1282
1394
|
}
|
|
@@ -1289,18 +1401,19 @@ function mount(rootEl, render) {
|
|
|
1289
1401
|
for (const item of b.items) disposeRowBindings(item.bindingDisposers);
|
|
1290
1402
|
}
|
|
1291
1403
|
listenerWarnObserver?.disconnect();
|
|
1292
|
-
|
|
1404
|
+
setMounted(rootEl, false);
|
|
1293
1405
|
};
|
|
1294
1406
|
}
|
|
1295
1407
|
function runFirstRender(rootEl, segment, bindings) {
|
|
1296
1408
|
rootEl.innerHTML = flatten(segment, true);
|
|
1297
1409
|
bindListsFromMarkers(rootEl, segment, bindings, true);
|
|
1298
1410
|
}
|
|
1299
|
-
function runSubsequentRender(rootEl, segment, bindings, renderCtx, prevStaticHtml) {
|
|
1411
|
+
function runSubsequentRender(rootEl, segment, bindings, renderCtx, prevStaticHtml, valueOnlyWarnCtx) {
|
|
1300
1412
|
const currentStaticHtml = flattenWithoutListItems(segment);
|
|
1301
1413
|
if (currentStaticHtml === prevStaticHtml) {
|
|
1302
1414
|
return prevStaticHtml;
|
|
1303
1415
|
}
|
|
1416
|
+
maybeWarnValueOnlyRerender(prevStaticHtml, currentStaticHtml, valueOnlyWarnCtx);
|
|
1304
1417
|
cleanupOrphanBindings(segment, bindings, renderCtx);
|
|
1305
1418
|
const template = rootEl.cloneNode(false);
|
|
1306
1419
|
template.innerHTML = currentStaticHtml;
|
|
@@ -1345,7 +1458,7 @@ function bindListsFromMarkers(rootEl, segment, bindings, inlinedItems) {
|
|
|
1345
1458
|
}
|
|
1346
1459
|
const binding = { liveParent, items, marker };
|
|
1347
1460
|
if (items.length > 0) {
|
|
1348
|
-
maybeWarnMissingRowKey(items[0].node,
|
|
1461
|
+
maybeWarnMissingRowKey(items[0].node, items[0].html, binding);
|
|
1349
1462
|
}
|
|
1350
1463
|
maybeWarnEachInMorphSkip(id, liveParent, rootEl);
|
|
1351
1464
|
bindings.set(id, binding);
|