kerfjs 5.0.0-beta.52 → 5.0.0-beta.53

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 (56) hide show
  1. package/CHANGELOG.md +329 -0
  2. package/README.md +4 -3
  3. package/ai/cursorrules +6 -5
  4. package/ai/manifest.json +9 -7
  5. package/ai/skill.md +6 -5
  6. package/dist/actions.js +3 -3
  7. package/dist/actions.js.map +1 -1
  8. package/dist/array-signal.js +1 -1
  9. package/dist/async.js +1 -1
  10. package/dist/attach.js +1 -68
  11. package/dist/attach.js.map +1 -1
  12. package/dist/{chunk-CEQMZYLR.js → chunk-3C6F6NM4.js} +1 -1
  13. package/dist/{chunk-CEQMZYLR.js.map → chunk-3C6F6NM4.js.map} +1 -1
  14. package/dist/{chunk-ZOIERTUW.js → chunk-BI4HGICV.js} +3 -3
  15. package/dist/{chunk-ZOIERTUW.js.map → chunk-BI4HGICV.js.map} +1 -1
  16. package/dist/{chunk-3GSWKMBZ.js → chunk-DU35YGKW.js} +3 -3
  17. package/dist/{chunk-3GSWKMBZ.js.map → chunk-DU35YGKW.js.map} +1 -1
  18. package/dist/chunk-FM6N2B4N.js +70 -0
  19. package/dist/chunk-FM6N2B4N.js.map +1 -0
  20. package/dist/{chunk-ELXVRKY2.js → chunk-H7WTRM6F.js} +2 -2
  21. package/dist/{chunk-ELXVRKY2.js.map → chunk-H7WTRM6F.js.map} +1 -1
  22. package/dist/{chunk-H3TTM7PQ.js → chunk-HTXQR2KL.js} +108 -106
  23. package/dist/chunk-HTXQR2KL.js.map +1 -0
  24. package/dist/{chunk-Y2FOYPBV.js → chunk-IHMDBBUJ.js} +2 -2
  25. package/dist/chunk-IHMDBBUJ.js.map +1 -0
  26. package/dist/{chunk-E5R5GNKE.js → chunk-X6OQ2HTR.js} +2 -2
  27. package/dist/{chunk-E5R5GNKE.js.map → chunk-X6OQ2HTR.js.map} +1 -1
  28. package/dist/dev.d.ts +31 -13
  29. package/dist/dev.js +34 -18
  30. package/dist/dev.js.map +1 -1
  31. package/dist/html.js +2 -2
  32. package/dist/index.d.ts +12 -7
  33. package/dist/index.js +11 -11
  34. package/dist/index.js.map +1 -1
  35. package/dist/jsx-runtime.js +1 -1
  36. package/dist/list.d.ts +3 -2
  37. package/dist/list.js +27 -13
  38. package/dist/list.js.map +1 -1
  39. package/dist/overlay.d.ts +0 -22
  40. package/dist/overlay.js +107 -30
  41. package/dist/overlay.js.map +1 -1
  42. package/dist/remount.js +2 -2
  43. package/dist/router.d.ts +13 -4
  44. package/dist/router.js +8 -4
  45. package/dist/router.js.map +1 -1
  46. package/dist/scope.d.ts +10 -2
  47. package/dist/scope.js +22 -28
  48. package/dist/scope.js.map +1 -1
  49. package/dist/testing.js +1 -1
  50. package/dist/timing.d.ts +3 -2
  51. package/dist/timing.js +1 -1
  52. package/dist/timing.js.map +1 -1
  53. package/llms.txt +1 -1
  54. package/package.json +1 -1
  55. package/dist/chunk-H3TTM7PQ.js.map +0 -1
  56. package/dist/chunk-Y2FOYPBV.js.map +0 -1
package/dist/overlay.js CHANGED
@@ -1,11 +1,12 @@
1
- import { mount } from './chunk-H3TTM7PQ.js';
2
- import { delegate } from './chunk-E5R5GNKE.js';
1
+ import { attach } from './chunk-FM6N2B4N.js';
2
+ import { mount } from './chunk-HTXQR2KL.js';
3
+ import { delegate } from './chunk-X6OQ2HTR.js';
3
4
 
4
5
 
5
- import { jsx, escapeHtml } from './chunk-3GSWKMBZ.js';
6
-
6
+ import { jsx, escapeHtml } from './chunk-DU35YGKW.js';
7
7
 
8
8
 
9
+ import { devHooks } from './chunk-3C6F6NM4.js';
9
10
 
10
11
  // src/overlay-position.ts
11
12
  function positionAnchored(el, anchor, options = {}) {
@@ -54,10 +55,32 @@ function supportsDialog() {
54
55
  function supportsPopover() {
55
56
  return typeof HTMLElement !== "undefined" && typeof HTMLElement.prototype.showPopover === "function";
56
57
  }
57
- var FALLBACK_OVERLAY_STACK = /* @__PURE__ */ Symbol("kerf.fallbackOverlayStack");
58
- function fallbackOverlayStack(wrapper) {
59
- const document2 = wrapper.ownerDocument;
60
- return document2[FALLBACK_OVERLAY_STACK] ??= [];
58
+ var OVERLAY_ARBITRATION = /* @__PURE__ */ Symbol("kerf.overlayArbitration");
59
+ function overlayArbitration(doc) {
60
+ return doc[OVERLAY_ARBITRATION] ??= {
61
+ stack: [],
62
+ handled: /* @__PURE__ */ new WeakSet()
63
+ };
64
+ }
65
+ function modalDialogOpen(doc, arbitration) {
66
+ if (arbitration.stack.some(
67
+ ({ el, modal }) => modal && el.isConnected && el.open === true
68
+ ))
69
+ return true;
70
+ try {
71
+ for (const dialog of doc.querySelectorAll("dialog[open]"))
72
+ if (dialog.matches(":modal")) return true;
73
+ } catch {
74
+ }
75
+ return false;
76
+ }
77
+ function ownsInput(stack, self, kind) {
78
+ for (let i = stack.indexOf(self) + 1; i < stack.length; i++) {
79
+ const above = stack[i];
80
+ if (above.modal || kind !== "trap" && above.triggers.includes(kind))
81
+ return false;
82
+ }
83
+ return true;
61
84
  }
62
85
  function assertInitialFocusSelector(selector) {
63
86
  try {
@@ -84,9 +107,11 @@ function overlay(content, options = {}) {
84
107
  if (typeof initialFocus === "string")
85
108
  assertInitialFocusSelector(initialFocus);
86
109
  const triggers = dismiss === false ? [] : Array.isArray(dismiss) ? dismiss : [dismiss];
87
- const restoreTo = document.activeElement;
88
- const useDialog = native && trap && supportsDialog();
89
- const usePopover = native && !trap && supportsPopover();
110
+ const arbitration = overlayArbitration(document);
111
+ const liftOverModal = !native && modalDialogOpen(document, arbitration);
112
+ const topLayer = native || liftOverModal;
113
+ const useDialog = topLayer && trap && supportsDialog();
114
+ const usePopover = topLayer && !trap && supportsPopover();
90
115
  const wrapper = useDialog ? document.createElement("dialog") : document.createElement("div");
91
116
  wrapper.className = className;
92
117
  if (usePopover) wrapper.setAttribute("popover", "manual");
@@ -94,9 +119,13 @@ function overlay(content, options = {}) {
94
119
  wrapper.setAttribute("role", role);
95
120
  wrapper.setAttribute("aria-modal", "true");
96
121
  }
97
- const fallback = !useDialog && !usePopover;
98
- const fallbackStack = fallback ? fallbackOverlayStack(wrapper) : void 0;
99
- const isTopmostFallback = () => fallbackStack === void 0 || fallbackStack[fallbackStack.length - 1] === wrapper;
122
+ const surface = {
123
+ el: wrapper,
124
+ triggers,
125
+ modal: trap,
126
+ restoreTo: document.activeElement
127
+ };
128
+ const owns = (kind, event) => !(event !== void 0 && arbitration.handled.has(event)) && ownsInput(arbitration.stack, surface, kind);
100
129
  const removers = [];
101
130
  const resultBox = {};
102
131
  const result = new Promise((resolve) => {
@@ -105,13 +134,33 @@ function overlay(content, options = {}) {
105
134
  const state = { closed: false };
106
135
  let disposeMount;
107
136
  let nativeOpened = false;
137
+ if (!trap)
138
+ surface.liftAbove = () => {
139
+ if (!supportsPopover()) {
140
+ devHooks.overlayBlockedByModal?.("hidden");
141
+ return;
142
+ }
143
+ const el = wrapper;
144
+ if (nativeOpened) el.hidePopover();
145
+ else {
146
+ wrapper.setAttribute("popover", "manual");
147
+ wrapper.style.inset = "auto";
148
+ }
149
+ el.showPopover();
150
+ nativeOpened = true;
151
+ if (wrapper.querySelector(FOCUSABLE) !== null)
152
+ devHooks.overlayBlockedByModal?.("inert");
153
+ };
108
154
  function close(value) {
109
155
  if (state.closed) return;
110
156
  state.closed = true;
111
- if (fallbackStack !== void 0) {
112
- const stackIndex = fallbackStack.indexOf(wrapper);
113
- if (stackIndex !== -1) fallbackStack.splice(stackIndex, 1);
114
- }
157
+ const { stack } = arbitration;
158
+ const stackIndex = stack.indexOf(surface);
159
+ if (stackIndex !== -1) stack.splice(stackIndex, 1);
160
+ for (const other of stack)
161
+ if (other.restoreTo !== null && wrapper.contains(other.restoreTo))
162
+ other.restoreTo = surface.restoreTo;
163
+ const active = document.activeElement;
115
164
  for (const remove of removers) remove();
116
165
  disposeMount?.();
117
166
  if (nativeOpened) {
@@ -120,9 +169,17 @@ function overlay(content, options = {}) {
120
169
  else wrapper.hidePopover();
121
170
  }
122
171
  wrapper.remove();
172
+ restoreFocus(active);
173
+ resultBox.resolve?.(value);
174
+ }
175
+ function restoreFocus(active) {
176
+ if (active instanceof HTMLElement && active.isConnected && arbitration.stack.some((other) => other.el.contains(active))) {
177
+ if (document.activeElement !== active) active.focus();
178
+ return;
179
+ }
180
+ const { restoreTo } = surface;
123
181
  if (restoreTo instanceof HTMLElement && restoreTo.isConnected)
124
182
  restoreTo.focus();
125
- resultBox.resolve?.(value);
126
183
  }
127
184
  function userDismiss() {
128
185
  onDismiss?.();
@@ -134,10 +191,12 @@ function overlay(content, options = {}) {
134
191
  wrapper,
135
192
  typeof content === "function" ? content : () => content
136
193
  );
137
- fallbackStack?.push(wrapper);
194
+ arbitration.stack.push(surface);
138
195
  if (useDialog) {
139
196
  wrapper.showModal();
140
197
  nativeOpened = true;
198
+ for (const other of arbitration.stack)
199
+ if (other !== surface && other.el.isConnected) other.liftAbove?.();
141
200
  } else if (usePopover) {
142
201
  wrapper.showPopover();
143
202
  nativeOpened = true;
@@ -149,25 +208,32 @@ function overlay(content, options = {}) {
149
208
  close();
150
209
  throw error;
151
210
  }
211
+ if (liftOverModal) {
212
+ if (!useDialog && !usePopover) devHooks.overlayBlockedByModal?.("hidden");
213
+ else if (usePopover && wrapper.querySelector(FOCUSABLE) !== null)
214
+ devHooks.overlayBlockedByModal?.("inert");
215
+ }
152
216
  return { el: wrapper, close, result };
153
217
  function installDismissal() {
154
218
  const wantEscape = triggers.includes("escape");
155
219
  if (useDialog) {
156
220
  const onCancel = (event) => {
157
221
  event.preventDefault();
158
- if (wantEscape) userDismiss();
222
+ if (wantEscape && owns("escape")) userDismiss();
159
223
  };
160
224
  wrapper.addEventListener("cancel", onCancel);
161
225
  removers.push(() => wrapper.removeEventListener("cancel", onCancel));
162
226
  } else if (wantEscape || trap) {
163
227
  const onKeydown = (event) => {
164
- if (!isTopmostFallback()) return;
165
228
  if (wantEscape && event.key === "Escape") {
229
+ if (!owns("escape", event)) return;
166
230
  event.stopPropagation();
231
+ event.preventDefault();
232
+ arbitration.handled.add(event);
167
233
  userDismiss();
168
234
  return;
169
235
  }
170
- if (trap && event.key === "Tab") {
236
+ if (trap && event.key === "Tab" && owns("trap")) {
171
237
  const items = focusable(wrapper);
172
238
  if (items.length === 0) {
173
239
  event.preventDefault();
@@ -193,8 +259,9 @@ function overlay(content, options = {}) {
193
259
  }
194
260
  if (triggers.includes("backdrop")) {
195
261
  const onClick = (event) => {
196
- if (!isTopmostFallback()) return;
197
- if (event.target === wrapper) userDismiss();
262
+ if (event.target !== wrapper || !owns("backdrop", event)) return;
263
+ arbitration.handled.add(event);
264
+ userDismiss();
198
265
  };
199
266
  wrapper.addEventListener("click", onClick);
200
267
  removers.push(() => wrapper.removeEventListener("click", onClick));
@@ -202,11 +269,12 @@ function overlay(content, options = {}) {
202
269
  if (triggers.includes("outside")) {
203
270
  const ignore = outsideIgnore === void 0 ? [] : Array.isArray(outsideIgnore) ? outsideIgnore : [outsideIgnore];
204
271
  const onDocClick = (event) => {
205
- if (!isTopmostFallback()) return;
206
272
  const target = event.target;
207
273
  if (target === null) return;
208
274
  if (wrapper.contains(target)) return;
209
275
  if (ignore.some((el) => el === target || el.contains(target))) return;
276
+ if (!owns("outside", event)) return;
277
+ arbitration.handled.add(event);
210
278
  userDismiss();
211
279
  };
212
280
  document.addEventListener("click", onDocClick, true);
@@ -292,6 +360,7 @@ function popover(anchor, content, options = {}) {
292
360
  throw error;
293
361
  }
294
362
  void handle.result.then(stopReposition);
363
+ void handle.result.then(attach(anchor, () => handle.close));
295
364
  return handle;
296
365
  }
297
366
  function tooltip(anchor, content, options = {}) {
@@ -311,6 +380,7 @@ function tooltip(anchor, content, options = {}) {
311
380
  let current;
312
381
  let presence = 0;
313
382
  function show() {
383
+ if (!anchor.isConnected) return;
314
384
  const handle = overlay(body, {
315
385
  container,
316
386
  className,
@@ -327,13 +397,20 @@ function tooltip(anchor, content, options = {}) {
327
397
  handle.close();
328
398
  throw error;
329
399
  }
330
- current = { handle, stop };
400
+ const unwatch = attach(anchor, () => () => {
401
+ presence = 0;
402
+ if (timers.hide !== void 0) clearTimeout(timers.hide);
403
+ hide();
404
+ });
405
+ current = { handle, stop, unwatch };
331
406
  }
332
407
  function hide() {
333
- if (current === void 0) return;
334
- current.stop();
335
- current.handle.close();
408
+ const shown = current;
409
+ if (shown === void 0) return;
336
410
  current = void 0;
411
+ shown.unwatch();
412
+ shown.stop();
413
+ shown.handle.close();
337
414
  }
338
415
  const onEnter = (event) => {
339
416
  presence |= event.type === "focus" ? 2 : 1;