kerfjs 5.0.0-beta.90 → 5.0.0-beta.92

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.
@@ -1,6 +1,6 @@
1
1
  import { itemVersion } from './chunk-5WRGJZV6.js';
2
- import { parseRowTemplate, rowContractError, parseSingleRow, collectTemplateChildren } from './chunk-WKIPLNVO.js';
3
- import { captureRowBindings, listSafeHtml, boundTextNodeOf, syncFormProp, newBindingContext, wireBindings, disposeRowBindings, isSafeHtml, _setBindingContext, TEXT_MARKER_PREFIX, ROW_TEXT_PREFIX, wireRowBindings, carryOrRewireRowBindings, granularListSafeHtml } from './chunk-UGNPDLNI.js';
2
+ import { seedRenderedAttrs, RENDERED_ATTRS, isUserAgentOwnedAttr, hasCustomTag, parseRowTemplate, rowContractError, parseSingleRow, collectTemplateChildren } from './chunk-6LXUC4ZY.js';
3
+ import { captureRowBindings, listSafeHtml, boundTextNodeOf, syncFormProp, newBindingContext, wireBindings, disposeRowBindings, isSafeHtml, _setBindingContext, TEXT_MARKER_PREFIX, ROW_TEXT_PREFIX, wireRowBindings, carryOrRewireRowBindings, granularListSafeHtml } from './chunk-VUAZFDLB.js';
4
4
  import { effect } from './chunk-IHMDBBUJ.js';
5
5
  import { LIST_MARKER_PREFIX, flattenWithoutListItems, collectLists, isOverlayHost, flatten } from './chunk-4TX6YJDM.js';
6
6
  import { devHooks } from './chunk-3C6F6NM4.js';
@@ -299,14 +299,6 @@ function restoreFocus(snap) {
299
299
  }
300
300
  }
301
301
 
302
- // src/utils/isUserAgentOwnedAttr.ts
303
- function isUserAgentOwnedAttr(tagNameUpper, name) {
304
- if (tagNameUpper.includes("-")) {
305
- return name === "open" || name === "role" || name.startsWith("aria-");
306
- }
307
- return name === "open" && (tagNameUpper === "DETAILS" || tagNameUpper === "DIALOG");
308
- }
309
-
310
302
  // src/utils/moveNode.ts
311
303
  function moveNode(parent, node, ref) {
312
304
  const move = parent.moveBefore;
@@ -459,6 +451,9 @@ function morphChildren(fromParent, toParent, ownedItems) {
459
451
  morphNode(matched, toChild, ownedItems);
460
452
  } else {
461
453
  const cloned = toChild.cloneNode(true);
454
+ if (toChild.nodeType === ELEMENT_NODE) {
455
+ seedRenderedAttrs(toChild, cloned);
456
+ }
462
457
  fromParent.insertBefore(cloned, fromChild);
463
458
  }
464
459
  toChild = toNext;
@@ -490,6 +485,7 @@ function morphNode(fromNode, toNode, ownedItems) {
490
485
  function morphElement(fromEl, toEl, ownedItems) {
491
486
  if (fromEl.tagName !== toEl.tagName) {
492
487
  const replacement = toEl.cloneNode(true);
488
+ seedRenderedAttrs(toEl, replacement);
493
489
  fromEl.parentNode?.replaceChild(replacement, fromEl);
494
490
  return;
495
491
  }
@@ -509,6 +505,9 @@ function morphElement(fromEl, toEl, ownedItems) {
509
505
  }
510
506
  }
511
507
  function morphAttributes(fromEl, toEl) {
508
+ const fromTag = fromEl.tagName;
509
+ const host = fromEl;
510
+ const rendered = host[RENDERED_ATTRS];
512
511
  const toAttrs = toEl.attributes;
513
512
  for (let i = 0; i < toAttrs.length; i++) {
514
513
  const attr = toAttrs[i];
@@ -525,18 +524,18 @@ function morphAttributes(fromEl, toEl) {
525
524
  }
526
525
  }
527
526
  const fromAttrs = fromEl.attributes;
528
- const fromTag = fromEl.tagName;
529
527
  for (let i = fromAttrs.length - 1; i >= 0; i--) {
530
528
  const attr = fromAttrs[i];
531
529
  const ns = attr.namespaceURI;
532
530
  const name = attr.localName;
533
531
  if (ns !== null) {
534
532
  if (!toEl.hasAttributeNS(ns, name)) fromEl.removeAttributeNS(ns, name);
535
- } else if (!toEl.hasAttribute(name) && !isUserAgentOwnedAttr(fromTag, name)) {
533
+ } else if (!toEl.hasAttribute(name) && !isUserAgentOwnedAttr(fromTag, name) && (!rendered || rendered.includes(name))) {
536
534
  fromEl.removeAttribute(name);
537
535
  syncFormProp(fromEl, name, "", false);
538
536
  }
539
537
  }
538
+ if (fromTag.includes("-")) host[RENDERED_ATTRS] = toEl.getAttributeNames();
540
539
  }
541
540
  function isTextInputOrTextarea(el) {
542
541
  if (el.tagName === "TEXTAREA") return true;
@@ -614,6 +613,7 @@ function tryAttributeOnlyFastPath(liveNode, oldHtml, newHtml) {
614
613
  liveNode.removeAttribute(name);
615
614
  syncFormProp(liveNode, name, "", false);
616
615
  }
616
+ delete liveNode[RENDERED_ATTRS];
617
617
  return true;
618
618
  }
619
619
  function tryTextContentFastPath(liveNode, oldHtml, newHtml) {
@@ -1207,6 +1207,7 @@ function anyRebuiltListIsGranular(segment, rebuilt) {
1207
1207
  }
1208
1208
  function bindListsFromMarkers(rootEl, segment, bindings, inlinedItems, rebuiltLists) {
1209
1209
  const lists = collectLists(segment);
1210
+ if (lists.size === 0 && bindings.size === 0) return;
1210
1211
  const found = [];
1211
1212
  collectComments(rootEl, found);
1212
1213
  for (const marker of found) {
@@ -1301,6 +1302,259 @@ function collectComments(node, out) {
1301
1302
  }
1302
1303
  }
1303
1304
 
1305
+ // src/static-subtree-morph.ts
1306
+ var START = 0;
1307
+ var OPEN_END = 1;
1308
+ var CLOSE = 2;
1309
+ var END = 3;
1310
+ var PARENT = 4;
1311
+ var INDEX = 5;
1312
+ var COUNT = 6;
1313
+ var FIRST = 7;
1314
+ var NEXT = 8;
1315
+ var FLAG = 9;
1316
+ var LAST = 10;
1317
+ var W = 11;
1318
+ var VOID = 1;
1319
+ var RAW = 2;
1320
+ var REFUSE = 4;
1321
+ var BOX = 8;
1322
+ var SENSITIVE = 16;
1323
+ var FOREIGN = 32;
1324
+ var FLAGS = /* @__PURE__ */ new Map();
1325
+ for (const [tags, bit] of [
1326
+ [
1327
+ "area base basefont bgsound br col embed frame hr img input keygen link meta param source track wbr",
1328
+ VOID
1329
+ ],
1330
+ ["script style textarea title xmp iframe noembed noframes", RAW],
1331
+ ["html head body frameset image noscript plaintext", REFUSE],
1332
+ ["table tbody thead tfoot tr colgroup", BOX],
1333
+ [
1334
+ "p li dd dt a button nobr select option optgroup datalist ruby rb rt rtc rp svg math template h1 h2 h3 h4 h5 h6 b big code em font i s small strike strong tt u",
1335
+ SENSITIVE
1336
+ ],
1337
+ ["svg math", FOREIGN]
1338
+ ]) {
1339
+ for (const t of tags.split(" ")) FLAGS.set(t, (FLAGS.get(t) ?? 0) | bit);
1340
+ }
1341
+ var TABLE_PARENT = {
1342
+ tr: " tbody thead tfoot ",
1343
+ td: " tr ",
1344
+ th: " tr ",
1345
+ tbody: " table ",
1346
+ thead: " table ",
1347
+ tfoot: " table ",
1348
+ caption: " table ",
1349
+ colgroup: " table ",
1350
+ col: " colgroup "
1351
+ };
1352
+ var isSpace = (c) => c === 32 || c === 9 || c === 10 || c === 12 || c === 13;
1353
+ function scanStatic(html, rootTag) {
1354
+ const len = html.length;
1355
+ const tag = [rootTag];
1356
+ const r = [-1, 0, len, len, -1, 0, 0, -1, -1, FLAGS.get(rootTag) ?? 0, -1];
1357
+ const stack = [0];
1358
+ let textFrom = 0;
1359
+ let dq = -1;
1360
+ let sq = -1;
1361
+ const blankUntil = (to) => {
1362
+ for (let k = textFrom; k < to; k++)
1363
+ if (!isSpace(html.charCodeAt(k))) return false;
1364
+ return true;
1365
+ };
1366
+ for (let i = 0; (i = html.indexOf("<", i)) !== -1; ) {
1367
+ const top = stack[stack.length - 1];
1368
+ const topFlag = r[top * W + FLAG];
1369
+ const c = html.charCodeAt(i + 1);
1370
+ if (c !== 33 && c !== 47 && !((c | 32) >= 97 && (c | 32) <= 122)) {
1371
+ i++;
1372
+ continue;
1373
+ }
1374
+ if (topFlag & BOX && !blankUntil(i)) return null;
1375
+ if (c === 33) {
1376
+ const e2 = html.indexOf("-->", i + 4);
1377
+ if (!html.startsWith("!--", i + 1) || e2 < 0) return null;
1378
+ const body = html.slice(i + 4, e2);
1379
+ if (body[0] === ">" || body.startsWith("->") || body.includes("--!>"))
1380
+ return null;
1381
+ i = textFrom = e2 + 3;
1382
+ continue;
1383
+ }
1384
+ if (c === 47) {
1385
+ const gt = html.indexOf(">", i);
1386
+ if (top === 0 || gt < 0) return null;
1387
+ const t = tag[top];
1388
+ let j2 = i + 2 + t.length;
1389
+ if (!html.startsWith(t, i + 2)) {
1390
+ j2 = i + 2;
1391
+ while (j2 < gt && !isSpace(html.charCodeAt(j2))) j2++;
1392
+ if (html.slice(i + 2, j2).toLowerCase() !== t) return null;
1393
+ } else if (j2 !== gt && !isSpace(html.charCodeAt(j2))) return null;
1394
+ stack.pop();
1395
+ r[top * W + CLOSE] = i;
1396
+ i = textFrom = r[top * W + END] = gt + 1;
1397
+ continue;
1398
+ }
1399
+ let j = i + 1;
1400
+ let ch;
1401
+ while (j < len && !isSpace(ch = html.charCodeAt(j)) && ch !== 47 && ch !== 62)
1402
+ j++;
1403
+ const name = html.slice(i + 1, j).toLowerCase();
1404
+ const flag = FLAGS.get(name) ?? 0;
1405
+ for (; ; ) {
1406
+ const gt = html.indexOf(">", j);
1407
+ if (gt < 0) return null;
1408
+ if (dq < j) dq = html.indexOf('"', j) >>> 0;
1409
+ if (sq < j) sq = html.indexOf("'", j) >>> 0;
1410
+ const qpos = dq < sq ? dq : sq;
1411
+ if (qpos > gt) {
1412
+ j = gt;
1413
+ break;
1414
+ }
1415
+ let k = qpos - 1;
1416
+ while (isSpace(html.charCodeAt(k))) k--;
1417
+ const close = html.indexOf(html[qpos], qpos + 1);
1418
+ if (html.charCodeAt(k) !== 61 || close < 0) return null;
1419
+ j = close + 1;
1420
+ }
1421
+ const want = TABLE_PARENT[name];
1422
+ if (flag & REFUSE || (want !== void 0 ? !want.includes(` ${tag[top]} `) : topFlag & BOX))
1423
+ return null;
1424
+ const e = tag.length;
1425
+ const p = top * W;
1426
+ const last = r[p + LAST];
1427
+ if (last < 0) r[p + FIRST] = e;
1428
+ else r[last * W + NEXT] = e;
1429
+ r[p + LAST] = e;
1430
+ tag.push(name);
1431
+ r.push(i, j + 1, j + 1, j + 1, top, r[p + COUNT]++, 0, -1, -1, flag, -1);
1432
+ i = textFrom = j + 1;
1433
+ if (flag & (FOREIGN | RAW)) {
1434
+ const re = new RegExp(`<(/?)${name}[\\s/>]`, "gi");
1435
+ re.lastIndex = i;
1436
+ let depth = flag & FOREIGN && html[j - 1] === "/" ? 0 : 1;
1437
+ while (depth > 0) {
1438
+ const m = re.exec(html);
1439
+ if (m === null) return null;
1440
+ const gt = html.indexOf(">", m.index);
1441
+ if (m[1] === "/") depth--;
1442
+ else if (flag & FOREIGN && html[gt - 1] !== "/") depth++;
1443
+ r[e * W + CLOSE] = m.index;
1444
+ i = textFrom = r[e * W + END] = gt + 1;
1445
+ }
1446
+ } else if (!(flag & VOID)) {
1447
+ stack.push(e);
1448
+ }
1449
+ }
1450
+ return stack.length > 1 || r[FLAG] & BOX && !blankUntil(len) ? null : { tag, r };
1451
+ }
1452
+ function commonRun(a, b, max, fromEnd) {
1453
+ const at = (s, from, to) => fromEnd ? s.slice(s.length - to, s.length - from) : s.slice(from, to);
1454
+ let n = 0;
1455
+ for (let step = 1024; n < max; ) {
1456
+ const to = Math.min(n + step, max);
1457
+ if (at(a, n, to) === at(b, n, to)) {
1458
+ n = to;
1459
+ step *= 2;
1460
+ } else if (step > 16) step >>= 2;
1461
+ else break;
1462
+ }
1463
+ while (n < max && at(a, n, n + 1) === at(b, n, n + 1)) n++;
1464
+ return n;
1465
+ }
1466
+ function overlapping(s, host, p, to) {
1467
+ const out = [];
1468
+ for (let c = s.r[host * W + FIRST]; c >= 0; c = s.r[c * W + NEXT])
1469
+ if (s.r[c * W + END] > p && s.r[c * W + START] < to) out.push(c);
1470
+ return out;
1471
+ }
1472
+ function morphStaticChange(rootEl, prev, next, cache, ownedItems) {
1473
+ const rootTag = rootEl.localName;
1474
+ const sa = cache.html === prev ? cache.scan : scanStatic(prev, rootTag);
1475
+ const sb = scanStatic(next, rootTag);
1476
+ cache.html = next;
1477
+ cache.scan = sb;
1478
+ if (sa === null || sb === null) return false;
1479
+ const a = sa.r;
1480
+ const b = sb.r;
1481
+ const delta = next.length - prev.length;
1482
+ const p = commonRun(prev, next, Math.min(prev.length, next.length), false);
1483
+ const q = commonRun(prev, next, Math.min(prev.length, next.length) - p, true);
1484
+ const aEnd = prev.length - q;
1485
+ let host = 0;
1486
+ for (let lo = 1, hi = sa.tag.length - 1; lo <= hi; ) {
1487
+ const mid = lo + hi >> 1;
1488
+ if (a[mid * W + START] < p) {
1489
+ host = mid;
1490
+ lo = mid + 1;
1491
+ } else hi = mid - 1;
1492
+ }
1493
+ while (!(a[host * W + OPEN_END] <= p && a[host * W + CLOSE] >= aEnd))
1494
+ host = a[host * W + PARENT];
1495
+ for (let x = host; x > 0; x = a[x * W + PARENT])
1496
+ if (a[x * W + FLAG] & SENSITIVE) host = a[x * W + PARENT];
1497
+ let olds;
1498
+ let news;
1499
+ for (; ; host = a[host * W + PARENT]) {
1500
+ if (host < 0) return false;
1501
+ if (b[host * W + CLOSE] - delta !== a[host * W + CLOSE]) continue;
1502
+ olds = overlapping(sa, host, p, aEnd);
1503
+ news = overlapping(sb, host, p, next.length - q);
1504
+ const k = olds.length;
1505
+ let ok = k > 0 && k === news.length && a[olds[0] * W + START] <= p && b[news[0] * W + START] <= p && a[olds[k - 1] * W + END] >= aEnd && b[news[k - 1] * W + END] - delta === a[olds[k - 1] * W + END];
1506
+ for (let m = 0; ok && m < k; m++) {
1507
+ ok = sa.tag[olds[m]] === sb.tag[news[m]] && (m === 0 || prev.slice(a[olds[m - 1] * W + END], a[olds[m] * W + START]) === next.slice(b[news[m - 1] * W + END], b[news[m] * W + START]));
1508
+ }
1509
+ if (ok) break;
1510
+ }
1511
+ if (rootEl.childElementCount !== a[COUNT]) return false;
1512
+ const path = [];
1513
+ for (let x = host; x > 0; x = a[x * W + PARENT]) path.push(x);
1514
+ let live = rootEl;
1515
+ for (let m = path.length - 1; m >= 0; m--) {
1516
+ const x = path[m];
1517
+ const el = live.children[a[x * W + INDEX]];
1518
+ if (el.localName !== sa.tag[x] || el.childElementCount !== a[x * W + COUNT] || el.dataset.morphSkip !== void 0 || el.dataset.morphSkipChildren !== void 0 || el === document.activeElement)
1519
+ return false;
1520
+ live = el;
1521
+ }
1522
+ const lives = [];
1523
+ let html = "";
1524
+ for (let m = 0; m < olds.length; m++) {
1525
+ const o = olds[m] * W;
1526
+ const n = news[m] * W;
1527
+ const nextChild = next.slice(b[n + START], b[n + END]);
1528
+ if (prev.slice(a[o + START], a[o + END]) === nextChild) continue;
1529
+ const el = live.children[a[o + INDEX]];
1530
+ if (el.localName !== sa.tag[olds[m]]) return false;
1531
+ lives.push(el);
1532
+ html += nextChild;
1533
+ }
1534
+ const tpl = live.cloneNode(false);
1535
+ tpl.innerHTML = html;
1536
+ const tos = tpl.childNodes;
1537
+ if (tos.length !== lives.length) return false;
1538
+ for (let m = 0; m < lives.length; m++) {
1539
+ const from = lives[m];
1540
+ const to = tos[m];
1541
+ if (to.localName !== from.localName || keyOf(from) !== keyOf(to) || keyOf(from) === "" && protectionOf(from) !== protectionOf(to))
1542
+ return false;
1543
+ }
1544
+ const focusSnap = captureFocus(live);
1545
+ for (let m = 0; m < lives.length; m++)
1546
+ _morphElement(lives[m], tos[m], ownedItems);
1547
+ if (focusSnap !== null) restoreFocus(focusSnap);
1548
+ return true;
1549
+ }
1550
+ function keyOf(el) {
1551
+ return el.id !== "" ? `#${el.id}` : el.dataset.key !== void 0 ? `k${el.dataset.key}` : "";
1552
+ }
1553
+ function protectionOf(el) {
1554
+ const d = el.dataset;
1555
+ return `${d.morphSkip !== void 0}${d.morphSkipChildren !== void 0}${d.morphPreserve !== void 0}`;
1556
+ }
1557
+
1304
1558
  // src/mount.ts
1305
1559
  var MOUNTED_MARKER = /* @__PURE__ */ Symbol.for("kerfjs.mounted");
1306
1560
  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.";
@@ -1369,6 +1623,7 @@ function mount(rootEl, render) {
1369
1623
  let prevWiredBindings = [];
1370
1624
  let isFirst = true;
1371
1625
  let prevStaticHtml = "";
1626
+ const scanCache = { html: "", scan: null };
1372
1627
  const valueOnlyWarnCtx = { warned: false };
1373
1628
  const runRenderPass = () => {
1374
1629
  renderCtx.counter = 0;
@@ -1411,7 +1666,8 @@ function mount(rootEl, render) {
1411
1666
  bindings,
1412
1667
  renderCtx,
1413
1668
  prevStaticHtml,
1414
- valueOnlyWarnCtx
1669
+ valueOnlyWarnCtx,
1670
+ scanCache
1415
1671
  );
1416
1672
  if (anyRebuiltListIsGranular(segment, renderCtx.rebuiltLists)) {
1417
1673
  for (const id of renderCtx.rebuiltLists)
@@ -1424,7 +1680,8 @@ function mount(rootEl, render) {
1424
1680
  bindings,
1425
1681
  renderCtx,
1426
1682
  prevStaticHtml,
1427
- valueOnlyWarnCtx
1683
+ valueOnlyWarnCtx,
1684
+ scanCache
1428
1685
  );
1429
1686
  }
1430
1687
  if (nextStaticHtml !== prevStaticHtml) {
@@ -1484,10 +1741,20 @@ function mount(rootEl, render) {
1484
1741
  };
1485
1742
  }
1486
1743
  function runFirstRender(rootEl, segment, bindings) {
1487
- rootEl.innerHTML = flatten(segment, true);
1744
+ const html = flatten(segment, true);
1745
+ if (hasCustomTag(html)) {
1746
+ const tpl = rootEl.cloneNode(false);
1747
+ tpl.innerHTML = html;
1748
+ const content = document.createDocumentFragment();
1749
+ while (tpl.firstChild !== null) content.appendChild(tpl.firstChild);
1750
+ seedRenderedAttrs(content);
1751
+ rootEl.replaceChildren(content);
1752
+ } else {
1753
+ rootEl.innerHTML = html;
1754
+ }
1488
1755
  bindListsFromMarkers(rootEl, segment, bindings, true);
1489
1756
  }
1490
- function runSubsequentRender(rootEl, segment, bindings, renderCtx, prevStaticHtml, valueOnlyWarnCtx) {
1757
+ function runSubsequentRender(rootEl, segment, bindings, renderCtx, prevStaticHtml, valueOnlyWarnCtx, scanCache) {
1491
1758
  renderCtx.rebuiltLists.clear();
1492
1759
  const currentStaticHtml = flattenWithoutListItems(segment);
1493
1760
  if (currentStaticHtml === prevStaticHtml) {
@@ -1499,9 +1766,18 @@ function runSubsequentRender(rootEl, segment, bindings, renderCtx, prevStaticHtm
1499
1766
  valueOnlyWarnCtx
1500
1767
  );
1501
1768
  cleanupOrphanBindings(segment, bindings, renderCtx);
1502
- const template = rootEl.cloneNode(false);
1503
- template.innerHTML = currentStaticHtml;
1504
- morph(rootEl, template, collectOwnedItems(bindings));
1769
+ const ownedItems = collectOwnedItems(bindings);
1770
+ if (!(segment.kind === "static" && bindings.size === 0 && morphStaticChange(
1771
+ rootEl,
1772
+ prevStaticHtml,
1773
+ currentStaticHtml,
1774
+ scanCache,
1775
+ ownedItems
1776
+ ))) {
1777
+ const template = rootEl.cloneNode(false);
1778
+ template.innerHTML = currentStaticHtml;
1779
+ morph(rootEl, template, ownedItems);
1780
+ }
1505
1781
  bindListsFromMarkers(
1506
1782
  rootEl,
1507
1783
  segment,
@@ -1521,4 +1797,4 @@ function resultToSegment(result) {
1521
1797
 
1522
1798
  export { captureFocus, each, morph, mount, moveNode, restoreFocus };
1523
1799
 
1524
- //# sourceMappingURL=chunk-T33U7SRR.js.map
1800
+ //# sourceMappingURL=chunk-E53LC47B.js.map