kerfjs 5.0.0-beta.53 → 5.0.0-beta.55
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 +531 -0
- package/ai/cursorrules +2 -2
- package/ai/manifest.json +9 -7
- package/ai/skill.md +2 -2
- package/dist/{chunk-BDX3R4OM.js → chunk-4TX6YJDM.js} +8 -2
- package/dist/chunk-4TX6YJDM.js.map +1 -0
- package/dist/{chunk-DU35YGKW.js → chunk-UGNPDLNI.js} +5 -3
- package/dist/chunk-UGNPDLNI.js.map +1 -0
- package/dist/{chunk-HTXQR2KL.js → chunk-WMRFXSLG.js} +7 -5
- package/dist/chunk-WMRFXSLG.js.map +1 -0
- package/dist/dev.js +9 -6
- package/dist/dev.js.map +1 -1
- package/dist/html.js +2 -2
- package/dist/index.js +2 -2
- package/dist/jsx-runtime.js +1 -1
- package/dist/list.js +1 -1
- package/dist/overlay.d.ts +12 -3
- package/dist/overlay.js +85 -20
- package/dist/overlay.js.map +1 -1
- package/dist/remount.js +1 -1
- package/dist/router.js +1 -1
- package/dist/scope.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-BDX3R4OM.js.map +0 -1
- package/dist/chunk-DU35YGKW.js.map +0 -1
- package/dist/chunk-HTXQR2KL.js.map +0 -1
package/dist/overlay.d.ts
CHANGED
|
@@ -26,7 +26,11 @@ interface AnchorPositionOptions {
|
|
|
26
26
|
* One-shot: position `el` relative to `anchor` — below by default, flipping above
|
|
27
27
|
* if it would overflow the viewport, aligned to a horizontal edge and clamped into
|
|
28
28
|
* view. Sets `el.style` `position: fixed`, `margin: 0`, `left`, and `top` (fixed so
|
|
29
|
-
* `left`/`top` are viewport coordinates, matching `getBoundingClientRect`).
|
|
29
|
+
* `left`/`top` are viewport coordinates, matching `getBoundingClientRect`). When an
|
|
30
|
+
* ancestor makes itself the containing block for fixed descendants (a
|
|
31
|
+
* `transform`, `filter`, `perspective`, or `contain` on a wrapping `<dialog>`),
|
|
32
|
+
* `left`/`top` are translated (and unscaled) into that box's coordinates so `el`
|
|
33
|
+
* still lands next to the anchor. This is
|
|
30
34
|
* `popover()`'s placement core, usable on any element (an inline hint, a tooltip) —
|
|
31
35
|
* no overlay lifecycle. Pair with {@link autoReposition} to keep it glued while open.
|
|
32
36
|
*/
|
|
@@ -116,7 +120,12 @@ interface OverlayHandle {
|
|
|
116
120
|
declare function overlay(content: OverlayContent, options?: OverlayOptions): OverlayHandle;
|
|
117
121
|
/** Options for {@link popover}. */
|
|
118
122
|
interface PopoverOptions {
|
|
119
|
-
/**
|
|
123
|
+
/**
|
|
124
|
+
* Where to append the popover wrapper. Default: when `anchor` sits inside an
|
|
125
|
+
* open modal `<dialog>`, that dialog's `[data-kerf-overlay-host]` element (a
|
|
126
|
+
* dialog kerf opened gets one automatically), so the popover is part of the
|
|
127
|
+
* modal subtree and stays interactive; otherwise `document.body`.
|
|
128
|
+
*/
|
|
120
129
|
container?: Element;
|
|
121
130
|
/** Class on the wrapper. Default `'kerf-popover'`. */
|
|
122
131
|
className?: string;
|
|
@@ -160,7 +169,7 @@ declare function popover(anchor: Element, content: OverlayContent, options?: Pop
|
|
|
160
169
|
type TooltipContent = string | SafeHtml | (() => MountResult);
|
|
161
170
|
/** Options for {@link tooltip}. */
|
|
162
171
|
interface TooltipOptions extends AnchorPositionOptions {
|
|
163
|
-
/** Where to append the tooltip wrapper. Default `document.body`. */
|
|
172
|
+
/** Where to append the tooltip wrapper. Default: the anchor's modal-dialog host slot (see {@link PopoverOptions.container}), else `document.body`. */
|
|
164
173
|
container?: Element;
|
|
165
174
|
/** Class on the wrapper. Default `'kerf-tooltip'`. */
|
|
166
175
|
className?: string;
|
package/dist/overlay.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { attach } from './chunk-FM6N2B4N.js';
|
|
2
|
-
import { mount } from './chunk-
|
|
2
|
+
import { mount } from './chunk-WMRFXSLG.js';
|
|
3
3
|
import { delegate } from './chunk-X6OQ2HTR.js';
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
import { jsx, escapeHtml } from './chunk-
|
|
7
|
-
|
|
6
|
+
import { jsx, escapeHtml } from './chunk-UGNPDLNI.js';
|
|
8
7
|
|
|
8
|
+
import { OVERLAY_HOST_SELECTOR } from './chunk-4TX6YJDM.js';
|
|
9
9
|
import { devHooks } from './chunk-3C6F6NM4.js';
|
|
10
10
|
|
|
11
11
|
// src/overlay-position.ts
|
|
@@ -26,8 +26,19 @@ function positionAnchored(el, anchor, options = {}) {
|
|
|
26
26
|
below = true;
|
|
27
27
|
let left = align === "end" ? anchorRect.right - elementRect.width : anchorRect.left;
|
|
28
28
|
left = Math.max(0, Math.min(left, viewportWidth - elementRect.width));
|
|
29
|
-
|
|
30
|
-
el.style.
|
|
29
|
+
const [originX, originY, scaleX, scaleY] = fixedOrigin(el);
|
|
30
|
+
el.style.left = `${(left - originX) / scaleX}px`;
|
|
31
|
+
el.style.top = `${((below ? belowTop : aboveTop) - originY) / scaleY}px`;
|
|
32
|
+
}
|
|
33
|
+
function fixedOrigin(el) {
|
|
34
|
+
const parent = el.parentNode;
|
|
35
|
+
if (!parent) return [0, 0, 1, 1];
|
|
36
|
+
const probe = el.ownerDocument.createElement("div");
|
|
37
|
+
probe.style.cssText = "position:fixed;left:0;top:0;width:1px;height:1px;margin:0;padding:0;border:0";
|
|
38
|
+
parent.insertBefore(probe, el);
|
|
39
|
+
const rect = probe.getBoundingClientRect();
|
|
40
|
+
probe.remove();
|
|
41
|
+
return [rect.left, rect.top, rect.width || 1, rect.height || 1];
|
|
31
42
|
}
|
|
32
43
|
function autoReposition(el, anchor, options = {}) {
|
|
33
44
|
const reposition = () => positionAnchored(el, anchor, options);
|
|
@@ -57,22 +68,58 @@ function supportsPopover() {
|
|
|
57
68
|
}
|
|
58
69
|
var OVERLAY_ARBITRATION = /* @__PURE__ */ Symbol("kerf.overlayArbitration");
|
|
59
70
|
function overlayArbitration(doc) {
|
|
60
|
-
|
|
71
|
+
const arbitrated = doc;
|
|
72
|
+
return arbitrated[OVERLAY_ARBITRATION] ??= {
|
|
61
73
|
stack: [],
|
|
62
|
-
handled: /* @__PURE__ */ new WeakSet()
|
|
74
|
+
handled: /* @__PURE__ */ new WeakSet(),
|
|
75
|
+
// KF-AHY6H4: an app-owned modal `<dialog>` opened outside kerf would leave
|
|
76
|
+
// the kerf surfaces already showing inert beneath it. A dialog fires
|
|
77
|
+
// `toggle` after it opens (it does not bubble, so listen in the capture
|
|
78
|
+
// phase); re-host above it exactly as kerf does for its own dialogs, which
|
|
79
|
+
// are skipped here because `overlay()` already re-hosted synchronously.
|
|
80
|
+
onToggle: (event) => {
|
|
81
|
+
const dialog = event.target;
|
|
82
|
+
const arbitration = arbitrated[OVERLAY_ARBITRATION];
|
|
83
|
+
if (dialog.tagName === "DIALOG" && !arbitration.stack.some(({ el }) => el === dialog) && isOpenModal(dialog, arbitration))
|
|
84
|
+
rehostAbove(arbitration, dialog);
|
|
85
|
+
}
|
|
63
86
|
};
|
|
64
87
|
}
|
|
65
|
-
function
|
|
66
|
-
|
|
67
|
-
(
|
|
68
|
-
|
|
88
|
+
function rehostAbove(arbitration, dialog) {
|
|
89
|
+
for (const { el, liftAbove } of arbitration.stack)
|
|
90
|
+
if (el !== dialog && el.isConnected && !dialog.contains(el)) liftAbove?.();
|
|
91
|
+
}
|
|
92
|
+
function isOpenModal(dialog, arbitration) {
|
|
93
|
+
if (!dialog.isConnected || dialog.open !== true)
|
|
94
|
+
return false;
|
|
95
|
+
if (arbitration.stack.some(({ el, modal }) => modal && el === dialog))
|
|
69
96
|
return true;
|
|
70
97
|
try {
|
|
71
|
-
|
|
72
|
-
if (dialog.matches(":modal")) return true;
|
|
98
|
+
return dialog.matches(":modal");
|
|
73
99
|
} catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function modalDialogOpen(doc, arbitration) {
|
|
104
|
+
return arbitration.stack.some(({ el }) => isOpenModal(el, arbitration)) || Array.from(doc.querySelectorAll("dialog[open]")).some(
|
|
105
|
+
(dialog) => isOpenModal(dialog, arbitration)
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
function overlayHost(anchor) {
|
|
109
|
+
const arbitration = overlayArbitration(document);
|
|
110
|
+
const dialog = anchor.closest("dialog");
|
|
111
|
+
if (dialog === null || !isOpenModal(dialog, arbitration)) return void 0;
|
|
112
|
+
let host = Array.from(dialog.querySelectorAll(OVERLAY_HOST_SELECTOR)).find(
|
|
113
|
+
(el) => el.closest("dialog") === dialog
|
|
114
|
+
);
|
|
115
|
+
if (host === void 0 && arbitration.stack.some(({ el }) => el === dialog)) {
|
|
116
|
+
dialog.insertAdjacentHTML(
|
|
117
|
+
"beforeend",
|
|
118
|
+
'<div data-kerf-overlay-host data-morph-skip data-morph-preserve style="display:contents"></div>'
|
|
119
|
+
);
|
|
120
|
+
host = dialog.lastElementChild;
|
|
74
121
|
}
|
|
75
|
-
return
|
|
122
|
+
return host;
|
|
76
123
|
}
|
|
77
124
|
function ownsInput(stack, self, kind) {
|
|
78
125
|
for (let i = stack.indexOf(self) + 1; i < stack.length; i++) {
|
|
@@ -108,7 +155,8 @@ function overlay(content, options = {}) {
|
|
|
108
155
|
assertInitialFocusSelector(initialFocus);
|
|
109
156
|
const triggers = dismiss === false ? [] : Array.isArray(dismiss) ? dismiss : [dismiss];
|
|
110
157
|
const arbitration = overlayArbitration(document);
|
|
111
|
-
const
|
|
158
|
+
const containerDialog = container.closest("dialog");
|
|
159
|
+
const liftOverModal = !native && !(containerDialog !== null && isOpenModal(containerDialog, arbitration)) && modalDialogOpen(document, arbitration);
|
|
112
160
|
const topLayer = native || liftOverModal;
|
|
113
161
|
const useDialog = topLayer && trap && supportsDialog();
|
|
114
162
|
const usePopover = topLayer && !trap && supportsPopover();
|
|
@@ -157,6 +205,8 @@ function overlay(content, options = {}) {
|
|
|
157
205
|
const { stack } = arbitration;
|
|
158
206
|
const stackIndex = stack.indexOf(surface);
|
|
159
207
|
if (stackIndex !== -1) stack.splice(stackIndex, 1);
|
|
208
|
+
if (stack.length === 0)
|
|
209
|
+
document.removeEventListener("toggle", arbitration.onToggle, true);
|
|
160
210
|
for (const other of stack)
|
|
161
211
|
if (other.restoreTo !== null && wrapper.contains(other.restoreTo))
|
|
162
212
|
other.restoreTo = surface.restoreTo;
|
|
@@ -191,18 +241,22 @@ function overlay(content, options = {}) {
|
|
|
191
241
|
wrapper,
|
|
192
242
|
typeof content === "function" ? content : () => content
|
|
193
243
|
);
|
|
194
|
-
arbitration.stack.push(surface)
|
|
244
|
+
if (arbitration.stack.push(surface) === 1)
|
|
245
|
+
document.addEventListener("toggle", arbitration.onToggle, true);
|
|
195
246
|
if (useDialog) {
|
|
196
247
|
wrapper.showModal();
|
|
197
248
|
nativeOpened = true;
|
|
198
|
-
|
|
199
|
-
if (other !== surface && other.el.isConnected) other.liftAbove?.();
|
|
249
|
+
rehostAbove(arbitration, wrapper);
|
|
200
250
|
} else if (usePopover) {
|
|
201
251
|
wrapper.showPopover();
|
|
202
252
|
nativeOpened = true;
|
|
203
253
|
wrapper.style.inset = "auto";
|
|
204
254
|
}
|
|
205
255
|
installDismissal();
|
|
256
|
+
if (container.closest(OVERLAY_HOST_SELECTOR) !== null) {
|
|
257
|
+
makeTabReachable();
|
|
258
|
+
removers.push(attach(wrapper, () => close));
|
|
259
|
+
}
|
|
206
260
|
applyInitialFocus();
|
|
207
261
|
} catch (error) {
|
|
208
262
|
close();
|
|
@@ -283,6 +337,14 @@ function overlay(content, options = {}) {
|
|
|
283
337
|
);
|
|
284
338
|
}
|
|
285
339
|
}
|
|
340
|
+
function makeTabReachable() {
|
|
341
|
+
focusable(wrapper);
|
|
342
|
+
const onTab = (event) => {
|
|
343
|
+
if (event.key === "Tab") focusable(wrapper);
|
|
344
|
+
};
|
|
345
|
+
document.addEventListener("keydown", onTab, true);
|
|
346
|
+
removers.push(() => document.removeEventListener("keydown", onTab, true));
|
|
347
|
+
}
|
|
286
348
|
function applyInitialFocus() {
|
|
287
349
|
if (initialFocus !== false) {
|
|
288
350
|
if (typeof initialFocus === "string") {
|
|
@@ -339,7 +401,7 @@ function popover(anchor, content, options = {}) {
|
|
|
339
401
|
} = options;
|
|
340
402
|
const extraIgnore = outsideIgnore === void 0 ? [] : Array.isArray(outsideIgnore) ? [...outsideIgnore] : [outsideIgnore];
|
|
341
403
|
const handle = overlay(content, {
|
|
342
|
-
container,
|
|
404
|
+
container: container ?? overlayHost(anchor),
|
|
343
405
|
className,
|
|
344
406
|
dismiss,
|
|
345
407
|
trap: false,
|
|
@@ -382,7 +444,7 @@ function tooltip(anchor, content, options = {}) {
|
|
|
382
444
|
function show() {
|
|
383
445
|
if (!anchor.isConnected) return;
|
|
384
446
|
const handle = overlay(body, {
|
|
385
|
-
container,
|
|
447
|
+
container: container ?? overlayHost(anchor),
|
|
386
448
|
className,
|
|
387
449
|
dismiss: false,
|
|
388
450
|
trap: false,
|
|
@@ -403,6 +465,9 @@ function tooltip(anchor, content, options = {}) {
|
|
|
403
465
|
hide();
|
|
404
466
|
});
|
|
405
467
|
current = { handle, stop, unwatch };
|
|
468
|
+
void handle.result.then(() => {
|
|
469
|
+
if (current?.handle === handle) hide();
|
|
470
|
+
});
|
|
406
471
|
}
|
|
407
472
|
function hide() {
|
|
408
473
|
const shown = current;
|