kerfjs 4.4.0 → 5.0.0-beta.3

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.
Files changed (73) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/LICENSE +1 -1
  3. package/README.md +34 -35
  4. package/ai/cursorrules +46 -1
  5. package/ai/manifest.json +62 -5
  6. package/ai/skill.md +53 -2
  7. package/dist/actions.d.ts +1 -1
  8. package/dist/actions.js +4 -4
  9. package/dist/actions.js.map +1 -1
  10. package/dist/array-signal.js +5 -5
  11. package/dist/async.js +17 -10
  12. package/dist/async.js.map +1 -1
  13. package/dist/attach.d.ts +11 -8
  14. package/dist/attach.js +52 -4
  15. package/dist/attach.js.map +1 -1
  16. package/dist/chunk-GY4XV2UV.js +1 -1
  17. package/dist/{chunk-VVDJLWMP.js → chunk-HW7KSM2Y.js} +2 -2
  18. package/dist/chunk-HW7KSM2Y.js.map +1 -0
  19. package/dist/{chunk-KEZTD6H4.js → chunk-KPXIOG2C.js} +3 -3
  20. package/dist/{chunk-KEZTD6H4.js.map → chunk-KPXIOG2C.js.map} +1 -1
  21. package/dist/{chunk-SAYPJ6XR.js → chunk-KZJXHFIB.js} +10 -6
  22. package/dist/chunk-KZJXHFIB.js.map +1 -0
  23. package/dist/{chunk-MRYM3O3V.js → chunk-LVH3GC6B.js} +11 -8
  24. package/dist/chunk-LVH3GC6B.js.map +1 -0
  25. package/dist/chunk-QIP723L4.js +1 -1
  26. package/dist/{chunk-SRWQKB33.js → chunk-SVATPF5R.js} +97 -74
  27. package/dist/chunk-SVATPF5R.js.map +1 -0
  28. package/dist/{chunk-3APBEVHF.js → chunk-U6FK33SG.js} +3 -3
  29. package/dist/{chunk-3APBEVHF.js.map → chunk-U6FK33SG.js.map} +1 -1
  30. package/dist/{chunk-U32TFTGZ.js → chunk-UZJ6I4T6.js} +3 -3
  31. package/dist/chunk-UZJ6I4T6.js.map +1 -0
  32. package/dist/{chunk-YHH7OUFA.js → chunk-V46JKE44.js} +3 -3
  33. package/dist/chunk-V46JKE44.js.map +1 -0
  34. package/dist/{chunk-SUPUPSBE.js → chunk-ZDCJZCNO.js} +9 -8
  35. package/dist/chunk-ZDCJZCNO.js.map +1 -0
  36. package/dist/dev.d.ts +9 -6
  37. package/dist/dev.js +5 -5
  38. package/dist/dev.js.map +1 -1
  39. package/dist/html.d.ts +1 -1
  40. package/dist/html.js +5 -5
  41. package/dist/html.js.map +1 -1
  42. package/dist/index.d.ts +1 -1
  43. package/dist/index.js +11 -11
  44. package/dist/jsx-runtime.js +5 -5
  45. package/dist/list.d.ts +1 -1
  46. package/dist/list.js +273 -211
  47. package/dist/list.js.map +1 -1
  48. package/dist/overlay.d.ts +190 -280
  49. package/dist/overlay.js +401 -361
  50. package/dist/overlay.js.map +1 -1
  51. package/dist/remount.d.ts +5 -3
  52. package/dist/remount.js +29 -10
  53. package/dist/remount.js.map +1 -1
  54. package/dist/router.d.ts +1 -1
  55. package/dist/router.js +44 -22
  56. package/dist/router.js.map +1 -1
  57. package/dist/scope.d.ts +4 -3
  58. package/dist/scope.js +10 -10
  59. package/dist/scope.js.map +1 -1
  60. package/dist/testing.js +4 -4
  61. package/dist/timing.d.ts +3 -2
  62. package/dist/timing.js +5 -5
  63. package/dist/timing.js.map +1 -1
  64. package/llms.txt +7 -4
  65. package/package.json +12 -10
  66. package/dist/chunk-MRYM3O3V.js.map +0 -1
  67. package/dist/chunk-SAYPJ6XR.js.map +0 -1
  68. package/dist/chunk-SRWQKB33.js.map +0 -1
  69. package/dist/chunk-SUPUPSBE.js.map +0 -1
  70. package/dist/chunk-U32TFTGZ.js.map +0 -1
  71. package/dist/chunk-VVDJLWMP.js.map +0 -1
  72. package/dist/chunk-YHH7OUFA.js.map +0 -1
  73. /package/dist/{attrSelector-Cmu2ZoGO.d.ts → attr-Cmu2ZoGO.d.ts} +0 -0
@@ -1,9 +1,9 @@
1
1
  import { itemVersion } from './chunk-QIP723L4.js';
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-SUPUPSBE.js';
4
- import { effect } from './chunk-3APBEVHF.js';
2
+ import { parseRowTemplate, rowContractError, parseSingleRow, collectTemplateChildren } from './chunk-V46JKE44.js';
3
+ import { captureRowBindings, listSafeHtml, boundTextNodeOf, syncFormProp, newBindingContext, disposeRowBindings, wireBindings, isSafeHtml, _setBindingContext, TEXT_MARKER_PREFIX, ROW_TEXT_PREFIX, wireRowBindings, carryOrRewireRowBindings, granularListSafeHtml } from './chunk-ZDCJZCNO.js';
4
+ import { effect } from './chunk-U6FK33SG.js';
5
5
  import { LIST_MARKER_PREFIX, flattenWithoutListItems, collectLists, flatten } from './chunk-GY4XV2UV.js';
6
- import { devHooks } from './chunk-VVDJLWMP.js';
6
+ import { devHooks } from './chunk-HW7KSM2Y.js';
7
7
 
8
8
  // src/list-render-state.ts
9
9
  function deriveListRenderState(bindingCount) {
@@ -106,6 +106,28 @@ function assertObjectItem(item, index) {
106
106
  );
107
107
  }
108
108
  }
109
+ function _hasGranularIndexShift(previousBindingCount, patches) {
110
+ const rendered = [];
111
+ for (let i = 0; i < previousBindingCount; i++) rendered.push(i);
112
+ for (const patch of patches) {
113
+ if (patch.type === "insert") rendered.splice(patch.index, 0, patch.index);
114
+ else if (patch.type === "remove") rendered.splice(patch.index, 1);
115
+ else if (patch.type === "move") {
116
+ const [moved] = rendered.splice(patch.from, 1);
117
+ rendered.splice(patch.to, 0, moved);
118
+ }
119
+ }
120
+ return rendered.some((renderedAt, index) => renderedAt !== index);
121
+ }
122
+ function _hasGranularCacheKeyDrift(snapshot, cacheKey, cache) {
123
+ for (let i = 0; i < snapshot.length; i++) {
124
+ const item = snapshot[i];
125
+ const nextKey = cacheKey(item, i);
126
+ const cached = cache.get(item);
127
+ if (cached !== void 0 && cached.cacheKey !== nextKey) return true;
128
+ }
129
+ return false;
130
+ }
109
131
  function eachGranular(sig, render, cacheKey, listKey) {
110
132
  const ctx = context;
111
133
  const id = listKey !== void 0 ? claimKey(ctx, listKey) : String(ctx.counter++);
@@ -124,34 +146,11 @@ function eachGranular(sig, render, cacheKey, listKey) {
124
146
  if (decision.path === "snapshot") {
125
147
  return eachSnapshotById(snapshot, render, cacheKey, id, sig);
126
148
  }
127
- let staleIndexShift = false;
128
- if (render.length >= 2 && devHooks.staleIndexEnabled?.() === true) {
129
- const rendered = [];
130
- for (let i = 0; i < previousBindingCount; i++) rendered.push(i);
131
- for (const p of patches) {
132
- if (p.type === "insert") rendered.splice(p.index, 0, p.index);
133
- else if (p.type === "remove") rendered.splice(p.index, 1);
134
- else if (p.type === "move") {
135
- const [moved] = rendered.splice(p.from, 1);
136
- rendered.splice(p.to, 0, moved);
137
- }
138
- }
139
- for (let i = 0; i < rendered.length; i++) {
140
- if (rendered[i] !== i) {
141
- staleIndexShift = true;
142
- break;
143
- }
144
- }
145
- }
149
+ const staleIndexShift = render.length >= 2 && devHooks.staleIndexEnabled?.() === true && _hasGranularIndexShift(previousBindingCount, patches);
146
150
  if (cacheKey !== void 0) {
147
151
  const cache2 = ctx.caches.get(id);
148
- for (let i = 0; i < snapshot.length; i++) {
149
- const item = snapshot[i];
150
- const k = cacheKey(item, i);
151
- const cached = cache2.get(item);
152
- if (cached !== void 0 && cached.cacheKey !== k) {
153
- return eachSnapshotById(snapshot, render, cacheKey, id, sig);
154
- }
152
+ if (_hasGranularCacheKeyDrift(snapshot, cacheKey, cache2)) {
153
+ return eachSnapshotById(snapshot, render, cacheKey, id, sig);
155
154
  }
156
155
  }
157
156
  const renderRow = (item, index) => captureRowBindings(() => inRowScope(() => {
@@ -240,16 +239,6 @@ function eachSnapshotById(items, render, cacheKey, id, source) {
240
239
  return listSafeHtml(id, segItems, source);
241
240
  }
242
241
 
243
- // src/utils/moveNode.ts
244
- function moveNode(parent, node, ref) {
245
- const move = parent.moveBefore;
246
- if (move !== void 0 && node.isConnected) {
247
- move.call(parent, node, ref);
248
- } else {
249
- parent.insertBefore(node, ref);
250
- }
251
- }
252
-
253
242
  // src/list-reconcile-focus.ts
254
243
  function captureFocus(liveParent) {
255
244
  const active = document.activeElement;
@@ -258,6 +247,7 @@ function captureFocus(liveParent) {
258
247
  const el = active;
259
248
  let selStart = null;
260
249
  let selEnd = null;
250
+ let domSelection = null;
261
251
  if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
262
252
  try {
263
253
  selStart = el.selectionStart;
@@ -265,18 +255,50 @@ function captureFocus(liveParent) {
265
255
  } catch {
266
256
  }
267
257
  }
268
- return { el, selStart, selEnd };
258
+ if (el.isContentEditable) {
259
+ const selection = document.getSelection();
260
+ if (selection !== null && selection.anchorNode !== null && selection.focusNode !== null) {
261
+ domSelection = [
262
+ selection.anchorNode,
263
+ selection.anchorOffset,
264
+ selection.focusNode,
265
+ selection.focusOffset
266
+ ];
267
+ }
268
+ }
269
+ return [el, selStart, selEnd, domSelection];
269
270
  }
270
271
  function restoreFocus(snap) {
271
- if (document.activeElement === snap.el) return;
272
- if (!snap.el.isConnected) return;
273
- snap.el.focus();
274
- if (snap.selStart !== null && snap.selEnd !== null) {
272
+ const [el, selStart, selEnd, domSelection] = snap;
273
+ if (!el.isConnected) return;
274
+ if (document.activeElement !== el) el.focus();
275
+ if (selStart !== null && selEnd !== null) {
275
276
  try {
276
- snap.el.setSelectionRange(snap.selStart, snap.selEnd);
277
+ el.setSelectionRange(selStart, selEnd);
277
278
  } catch {
278
279
  }
279
280
  }
281
+ if (domSelection === null) return;
282
+ const [anchorNode, anchorOffset, focusNode, focusOffset] = domSelection;
283
+ try {
284
+ document.getSelection()?.setBaseAndExtent(
285
+ anchorNode,
286
+ anchorOffset,
287
+ focusNode,
288
+ focusOffset
289
+ );
290
+ } catch {
291
+ }
292
+ }
293
+
294
+ // src/utils/moveNode.ts
295
+ function moveNode(parent, node, ref) {
296
+ const move = parent.moveBefore;
297
+ if (move !== void 0 && node.isConnected) {
298
+ move.call(parent, node, ref);
299
+ } else {
300
+ parent.insertBefore(node, ref);
301
+ }
280
302
  }
281
303
 
282
304
  // src/morph.ts
@@ -892,8 +914,9 @@ function applyBulkInsert(liveParent, items, patches, start, end, tailAnchor) {
892
914
  html: htmls[k],
893
915
  node: newNodes[k],
894
916
  bindings: p.bindings,
917
+ // Every fresh row needs its fine-grained holes wired to the newly parsed
918
+ // node; otherwise later signal writes would target no live DOM binding.
895
919
  bindingDisposers: wireRowIfBound(newNodes[k], p.bindings)
896
- // KF-294
897
920
  };
898
921
  }
899
922
  items.splice(startIdx, 0, ...newEntries);
@@ -1132,7 +1155,7 @@ function assertNotInsideMountedTree(rootEl) {
1132
1155
  );
1133
1156
  }
1134
1157
  let ancestor = rootEl.parentElement;
1135
- while (ancestor !== null) {
1158
+ while (ancestor) {
1136
1159
  if (isMounted(ancestor)) throw new Error(NESTED_MOUNT_MSG);
1137
1160
  ancestor = ancestor.parentElement;
1138
1161
  }
@@ -1152,7 +1175,7 @@ function mount(rootEl, render) {
1152
1175
  }
1153
1176
  const owner = rootEl.ownerDocument;
1154
1177
  if (owner !== document) {
1155
- if (owner.defaultView === null) document.adoptNode(rootEl);
1178
+ if (!owner.defaultView) document.adoptNode(rootEl);
1156
1179
  }
1157
1180
  assertNotInsideMountedTree(rootEl);
1158
1181
  setMounted(rootEl, true);
@@ -1189,10 +1212,9 @@ function mount(rootEl, render) {
1189
1212
  _setBindingContext(null);
1190
1213
  }
1191
1214
  };
1192
- const disposeEffect = effect(() => {
1215
+ function renderStaticPhase() {
1193
1216
  let result = runRenderPass();
1194
- const countChanged = renderCtx.previousCallCount !== void 0 && renderCtx.previousCallCount !== renderCtx.counter;
1195
- if (countChanged) {
1217
+ if (renderCtx.previousCallCount !== void 0 && renderCtx.previousCallCount !== renderCtx.counter) {
1196
1218
  for (const id of renderCtx.shiftCandidates) {
1197
1219
  if (renderCtx.warnedShiftIds.has(id)) continue;
1198
1220
  renderCtx.warnedShiftIds.add(id);
@@ -1207,7 +1229,7 @@ function mount(rootEl, render) {
1207
1229
  prevStaticHtml = flattenWithoutListItems(segment);
1208
1230
  devHooks.parserRepair?.(prevStaticHtml);
1209
1231
  bindingDisposers = wireBindings(rootEl, bindingCtx, bindingDisposers);
1210
- if (devHooks.staleBindingEnabled?.() === true) prevWiredBindings = bindingCtx.list;
1232
+ if (devHooks.staleBindingEnabled?.()) prevWiredBindings = bindingCtx.list;
1211
1233
  isFirst = false;
1212
1234
  } else {
1213
1235
  let nextStaticHtml = runSubsequentRender(
@@ -1233,16 +1255,19 @@ function mount(rootEl, render) {
1233
1255
  }
1234
1256
  if (nextStaticHtml !== prevStaticHtml) {
1235
1257
  bindingDisposers = wireBindings(rootEl, bindingCtx, bindingDisposers);
1236
- if (devHooks.staleBindingEnabled?.() === true) prevWiredBindings = bindingCtx.list;
1258
+ if (devHooks.staleBindingEnabled?.()) prevWiredBindings = bindingCtx.list;
1237
1259
  } else {
1238
1260
  devHooks.staleBinding?.(prevWiredBindings, bindingCtx.list);
1239
1261
  }
1240
1262
  prevStaticHtml = nextStaticHtml;
1241
1263
  }
1242
- const expectedCounts = devHooks.listInvariantsEnabled?.() === true ? /* @__PURE__ */ new Map() : null;
1264
+ return segment;
1265
+ }
1266
+ function reconcileAndCommitListPhase(segment) {
1267
+ const expectedCounts = devHooks.listInvariantsEnabled?.() ? /* @__PURE__ */ new Map() : null;
1243
1268
  for (const listSeg of collectLists(segment).values()) {
1244
1269
  const binding = bindings.get(listSeg.id);
1245
- if (binding === void 0) {
1270
+ if (!binding) {
1246
1271
  throw new Error(
1247
1272
  "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."
1248
1273
  );
@@ -1252,11 +1277,14 @@ function mount(rootEl, render) {
1252
1277
  renderCtx.bindingSources.set(listSeg.id, listSeg.source);
1253
1278
  expectedCounts?.set(
1254
1279
  listSeg.id,
1255
- listSeg.patches !== void 0 && listSeg.source !== void 0 ? listSeg.source.value.length : listSeg.items.length
1280
+ listSeg.patches && listSeg.source ? listSeg.source.value.length : listSeg.items.length
1256
1281
  );
1257
1282
  }
1258
1283
  renderCtx.previousCallCount = renderCtx.counter;
1259
- devHooks.listInvariants?.(rootEl, bindings, expectedCounts ?? void 0);
1284
+ devHooks.listInvariants?.(rootEl, bindings, expectedCounts || void 0);
1285
+ }
1286
+ const disposeEffect = effect(() => {
1287
+ reconcileAndCommitListPhase(renderStaticPhase());
1260
1288
  });
1261
1289
  return () => {
1262
1290
  disposeEffect();
@@ -1288,18 +1316,17 @@ function runSubsequentRender(rootEl, segment, bindings, renderCtx, prevStaticHtm
1288
1316
  return currentStaticHtml;
1289
1317
  }
1290
1318
  function coerceRenderResult(result) {
1291
- if (result === null || result === void 0) return "";
1292
- if (result === false || result === true) return "";
1319
+ if (result == null || typeof result === "boolean") return "";
1293
1320
  return String(result);
1294
1321
  }
1295
1322
  function resultToSegment(result) {
1296
1323
  return isSafeHtml(result) ? result.__segment ?? { kind: "static", html: result.__html } : { kind: "static", html: coerceRenderResult(result) };
1297
1324
  }
1298
1325
  function anyRebuiltListIsGranular(segment, rebuilt) {
1299
- if (rebuilt.size === 0) return false;
1326
+ if (!rebuilt.size) return false;
1300
1327
  const lists = collectLists(segment);
1301
1328
  for (const id of rebuilt) {
1302
- if (lists.get(id)?.patches !== void 0) return true;
1329
+ if (lists.get(id)?.patches) return true;
1303
1330
  }
1304
1331
  return false;
1305
1332
  }
@@ -1311,7 +1338,7 @@ function bindListsFromMarkers(rootEl, segment, bindings, inlinedItems, rebuiltLi
1311
1338
  if (!marker.data.startsWith(LIST_MARKER_PREFIX)) continue;
1312
1339
  const id = marker.data.slice(LIST_MARKER_PREFIX.length);
1313
1340
  const existing = bindings.get(id);
1314
- if (existing !== void 0) {
1341
+ if (existing) {
1315
1342
  if (existing.marker === marker && rootEl.contains(existing.marker)) continue;
1316
1343
  for (const item of existing.items) {
1317
1344
  disposeRowBindings(item.bindingDisposers);
@@ -1338,7 +1365,7 @@ function bindListsFromMarkers(rootEl, segment, bindings, inlinedItems, rebuiltLi
1338
1365
  node: next,
1339
1366
  bindings: rowBindings
1340
1367
  };
1341
- if (rowBindings !== void 0 && rowBindings.length > 0) {
1368
+ if (rowBindings?.length) {
1342
1369
  bound.bindingDisposers = wireRowBindings(next, rowBindings);
1343
1370
  }
1344
1371
  items.push(bound);
@@ -1380,13 +1407,9 @@ function cleanupOrphanBindings(segment, bindings, renderCtx) {
1380
1407
  if (liveIds.has(id)) continue;
1381
1408
  for (const item of binding.items) {
1382
1409
  disposeRowBindings(item.bindingDisposers);
1383
- if (item.node.parentElement !== null) {
1384
- item.node.parentElement.removeChild(item.node);
1385
- }
1386
- }
1387
- if (binding.marker.parentElement !== null) {
1388
- binding.marker.parentElement.removeChild(binding.marker);
1410
+ item.node.remove();
1389
1411
  }
1412
+ binding.marker.remove();
1390
1413
  bindings.delete(id);
1391
1414
  renderCtx.bindingCounts.delete(id);
1392
1415
  renderCtx.bindingSources.delete(id);
@@ -1394,12 +1417,12 @@ function cleanupOrphanBindings(segment, bindings, renderCtx) {
1394
1417
  }
1395
1418
  }
1396
1419
  function collectComments(node, out) {
1397
- for (let c = node.firstChild; c !== null; c = c.nextSibling) {
1420
+ for (let c = node.firstChild; c; c = c.nextSibling) {
1398
1421
  if (c.nodeType === Node.COMMENT_NODE) out.push(c);
1399
1422
  else if (c.nodeType === Node.ELEMENT_NODE) collectComments(c, out);
1400
1423
  }
1401
1424
  }
1402
1425
 
1403
- export { each, morph, mount, moveNode };
1404
- //# sourceMappingURL=chunk-SRWQKB33.js.map
1405
- //# sourceMappingURL=chunk-SRWQKB33.js.map
1426
+ export { captureFocus, each, morph, mount, moveNode, restoreFocus };
1427
+
1428
+ //# sourceMappingURL=chunk-SVATPF5R.js.map