kerfjs 4.4.0 → 4.5.0-beta.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.
Files changed (84) hide show
  1. package/CHANGELOG.md +284 -104
  2. package/LICENSE +1 -1
  3. package/README.md +73 -54
  4. package/ai/cursorrules +130 -48
  5. package/ai/manifest.json +63 -5
  6. package/ai/skill.md +148 -65
  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 +34 -24
  12. package/dist/async.js.map +1 -1
  13. package/dist/attach.d.ts +11 -8
  14. package/dist/attach.js +54 -4
  15. package/dist/attach.js.map +1 -1
  16. package/dist/{chunk-QIP723L4.js → chunk-5WRGJZV6.js} +4 -3
  17. package/dist/chunk-5WRGJZV6.js.map +1 -0
  18. package/dist/{chunk-GY4XV2UV.js → chunk-BDX3R4OM.js} +4 -3
  19. package/dist/chunk-BDX3R4OM.js.map +1 -0
  20. package/dist/{chunk-VVDJLWMP.js → chunk-CEQMZYLR.js} +2 -2
  21. package/dist/chunk-CEQMZYLR.js.map +1 -0
  22. package/dist/{chunk-KEZTD6H4.js → chunk-E5R5GNKE.js} +15 -5
  23. package/dist/chunk-E5R5GNKE.js.map +1 -0
  24. package/dist/{chunk-MRYM3O3V.js → chunk-ELXVRKY2.js} +12 -9
  25. package/dist/chunk-ELXVRKY2.js.map +1 -0
  26. package/dist/{chunk-U32TFTGZ.js → chunk-MK42GLPV.js} +3 -3
  27. package/dist/chunk-MK42GLPV.js.map +1 -0
  28. package/dist/{chunk-SUPUPSBE.js → chunk-QFUNWHKH.js} +42 -20
  29. package/dist/chunk-QFUNWHKH.js.map +1 -0
  30. package/dist/{chunk-SRWQKB33.js → chunk-V757BT6U.js} +206 -111
  31. package/dist/chunk-V757BT6U.js.map +1 -0
  32. package/dist/{chunk-YHH7OUFA.js → chunk-WKIPLNVO.js} +3 -3
  33. package/dist/chunk-WKIPLNVO.js.map +1 -0
  34. package/dist/{chunk-3APBEVHF.js → chunk-Y2FOYPBV.js} +3 -3
  35. package/dist/{chunk-3APBEVHF.js.map → chunk-Y2FOYPBV.js.map} +1 -1
  36. package/dist/{chunk-SAYPJ6XR.js → chunk-ZOIERTUW.js} +10 -6
  37. package/dist/chunk-ZOIERTUW.js.map +1 -0
  38. package/dist/dev.d.ts +9 -6
  39. package/dist/dev.js +66 -19
  40. package/dist/dev.js.map +1 -1
  41. package/dist/html.d.ts +1 -1
  42. package/dist/html.js +8 -7
  43. package/dist/html.js.map +1 -1
  44. package/dist/index.d.ts +1 -1
  45. package/dist/index.js +28 -19
  46. package/dist/index.js.map +1 -1
  47. package/dist/jsx-runtime.js +5 -5
  48. package/dist/list.d.ts +1 -1
  49. package/dist/list.js +304 -213
  50. package/dist/list.js.map +1 -1
  51. package/dist/overlay.d.ts +190 -280
  52. package/dist/overlay.js +471 -364
  53. package/dist/overlay.js.map +1 -1
  54. package/dist/remount.d.ts +5 -3
  55. package/dist/remount.js +29 -10
  56. package/dist/remount.js.map +1 -1
  57. package/dist/router.d.ts +1 -1
  58. package/dist/router.js +73 -28
  59. package/dist/router.js.map +1 -1
  60. package/dist/scope.d.ts +4 -3
  61. package/dist/scope.js +11 -10
  62. package/dist/scope.js.map +1 -1
  63. package/dist/testing.js +4 -4
  64. package/dist/timing.d.ts +3 -2
  65. package/dist/timing.js +5 -5
  66. package/dist/timing.js.map +1 -1
  67. package/llms.txt +13 -6
  68. package/package.json +35 -13
  69. package/setup/cli.mjs +90 -0
  70. package/setup/index.d.mts +37 -0
  71. package/setup/index.mjs +1347 -0
  72. package/setup/jsonc.mjs +201 -0
  73. package/setup/state.schema.json +56 -0
  74. package/dist/chunk-GY4XV2UV.js.map +0 -1
  75. package/dist/chunk-KEZTD6H4.js.map +0 -1
  76. package/dist/chunk-MRYM3O3V.js.map +0 -1
  77. package/dist/chunk-QIP723L4.js.map +0 -1
  78. package/dist/chunk-SAYPJ6XR.js.map +0 -1
  79. package/dist/chunk-SRWQKB33.js.map +0 -1
  80. package/dist/chunk-SUPUPSBE.js.map +0 -1
  81. package/dist/chunk-U32TFTGZ.js.map +0 -1
  82. package/dist/chunk-VVDJLWMP.js.map +0 -1
  83. package/dist/chunk-YHH7OUFA.js.map +0 -1
  84. /package/dist/{attrSelector-Cmu2ZoGO.d.ts → attr-Cmu2ZoGO.d.ts} +0 -0
package/dist/overlay.js CHANGED
@@ -1,28 +1,30 @@
1
- import { mount } from './chunk-SRWQKB33.js';
2
- import { delegate } from './chunk-KEZTD6H4.js';
3
- import './chunk-QIP723L4.js';
4
- import './chunk-YHH7OUFA.js';
5
- import { jsx } from './chunk-SUPUPSBE.js';
6
- import './chunk-3APBEVHF.js';
7
- import './chunk-GY4XV2UV.js';
8
- import './chunk-VVDJLWMP.js';
1
+ import { mount } from './chunk-V757BT6U.js';
2
+ import { delegate } from './chunk-E5R5GNKE.js';
3
+
4
+
5
+ import { jsx, escapeHtml } from './chunk-QFUNWHKH.js';
6
+
7
+
8
+
9
9
 
10
10
  // src/overlay-position.ts
11
11
  function positionAnchored(el, anchor, options = {}) {
12
12
  const { placement = "bottom", align = "start", gap = 4 } = options;
13
13
  el.style.position = "fixed";
14
14
  el.style.margin = "0";
15
- const a = anchor.getBoundingClientRect();
16
- const p = el.getBoundingClientRect();
17
- const vw = window.innerWidth;
18
- const vh = window.innerHeight;
19
- const belowTop = a.bottom + gap;
20
- const aboveTop = a.top - gap - p.height;
15
+ const anchorRect = anchor.getBoundingClientRect();
16
+ const elementRect = el.getBoundingClientRect();
17
+ const viewportWidth = window.innerWidth;
18
+ const viewportHeight = window.innerHeight;
19
+ const belowTop = anchorRect.bottom + gap;
20
+ const aboveTop = anchorRect.top - gap - elementRect.height;
21
21
  let below = placement !== "top";
22
- if (below && belowTop + p.height > vh && aboveTop >= 0) below = false;
23
- else if (!below && aboveTop < 0 && belowTop + p.height <= vh) below = true;
24
- let left = align === "end" ? a.right - p.width : a.left;
25
- left = Math.max(0, Math.min(left, vw - p.width));
22
+ if (below && belowTop + elementRect.height > viewportHeight && aboveTop >= 0)
23
+ below = false;
24
+ else if (!below && aboveTop < 0 && belowTop + elementRect.height <= viewportHeight)
25
+ below = true;
26
+ let left = align === "end" ? anchorRect.right - elementRect.width : anchorRect.left;
27
+ left = Math.max(0, Math.min(left, viewportWidth - elementRect.width));
26
28
  el.style.left = `${left}px`;
27
29
  el.style.top = `${below ? belowTop : aboveTop}px`;
28
30
  }
@@ -37,91 +39,362 @@ function autoReposition(el, anchor, options = {}) {
37
39
  };
38
40
  }
39
41
 
40
- // src/overlay-dialogs.ts
41
- function confirm(message, options = {}) {
42
+ // src/overlay-core.ts
43
+ var FOCUSABLE = 'a[href],area[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),iframe,[tabindex]:not([tabindex="-1"]),[contenteditable="true"]';
44
+ function focusable(root) {
45
+ const items = Array.from(
46
+ root.querySelectorAll(FOCUSABLE)
47
+ ).filter((el) => !el.hasAttribute("hidden") && el.tabIndex >= 0);
48
+ for (const el of items) if (!el.hasAttribute("tabindex")) el.tabIndex = 0;
49
+ return items;
50
+ }
51
+ function supportsDialog() {
52
+ return typeof HTMLDialogElement !== "undefined" && typeof HTMLDialogElement.prototype.showModal === "function";
53
+ }
54
+ function supportsPopover() {
55
+ return typeof HTMLElement !== "undefined" && typeof HTMLElement.prototype.showPopover === "function";
56
+ }
57
+ var FALLBACK_OVERLAY_STACK = /* @__PURE__ */ Symbol("kerf.fallbackOverlayStack");
58
+ function fallbackOverlayStack(wrapper) {
59
+ const document2 = wrapper.ownerDocument;
60
+ return document2[FALLBACK_OVERLAY_STACK] ??= [];
61
+ }
62
+ function overlay(content, options = {}) {
63
+ const {
64
+ container = document.body,
65
+ className = "kerf-overlay",
66
+ dismiss = ["escape", "backdrop"],
67
+ initialFocus = true,
68
+ trap = true,
69
+ role = "dialog",
70
+ onDismiss,
71
+ outsideIgnore,
72
+ native = false
73
+ } = options;
74
+ const triggers = dismiss === false ? [] : Array.isArray(dismiss) ? dismiss : [dismiss];
75
+ const restoreTo = document.activeElement;
76
+ const useDialog = native && trap && supportsDialog();
77
+ const usePopover = native && !trap && supportsPopover();
78
+ const wrapper = useDialog ? document.createElement("dialog") : document.createElement("div");
79
+ wrapper.className = className;
80
+ if (usePopover) wrapper.setAttribute("popover", "manual");
81
+ if (trap && !useDialog) {
82
+ wrapper.setAttribute("role", role);
83
+ wrapper.setAttribute("aria-modal", "true");
84
+ }
85
+ container.appendChild(wrapper);
86
+ const fallback = !useDialog && !usePopover;
87
+ const fallbackStack = fallback ? fallbackOverlayStack(wrapper) : void 0;
88
+ const isTopmostFallback = () => fallbackStack === void 0 || fallbackStack[fallbackStack.length - 1] === wrapper;
89
+ const disposeMount = mount(
90
+ wrapper,
91
+ typeof content === "function" ? content : () => content
92
+ );
93
+ fallbackStack?.push(wrapper);
94
+ let nativeOpened = false;
95
+ if (useDialog) {
96
+ wrapper.showModal();
97
+ nativeOpened = true;
98
+ } else if (usePopover) {
99
+ wrapper.showPopover();
100
+ wrapper.style.inset = "auto";
101
+ nativeOpened = true;
102
+ }
103
+ const removers = [];
104
+ const resultBox = {};
105
+ const result = new Promise((resolve) => {
106
+ resultBox.resolve = resolve;
107
+ });
108
+ const state = { closed: false };
109
+ function close(value) {
110
+ if (state.closed) return;
111
+ state.closed = true;
112
+ if (fallbackStack !== void 0) {
113
+ const stackIndex = fallbackStack.indexOf(wrapper);
114
+ if (stackIndex !== -1) fallbackStack.splice(stackIndex, 1);
115
+ }
116
+ for (const remove of removers) remove();
117
+ disposeMount();
118
+ if (nativeOpened) {
119
+ nativeOpened = false;
120
+ if (useDialog) wrapper.close();
121
+ else wrapper.hidePopover();
122
+ }
123
+ wrapper.remove();
124
+ if (restoreTo instanceof HTMLElement && restoreTo.isConnected)
125
+ restoreTo.focus();
126
+ resultBox.resolve?.(value);
127
+ }
128
+ function userDismiss() {
129
+ onDismiss?.();
130
+ close();
131
+ }
132
+ const wantEscape = triggers.includes("escape");
133
+ if (useDialog) {
134
+ const onCancel = (event) => {
135
+ event.preventDefault();
136
+ if (wantEscape) userDismiss();
137
+ };
138
+ wrapper.addEventListener("cancel", onCancel);
139
+ removers.push(() => wrapper.removeEventListener("cancel", onCancel));
140
+ } else if (wantEscape || trap) {
141
+ const onKeydown = (event) => {
142
+ if (!isTopmostFallback()) return;
143
+ if (wantEscape && event.key === "Escape") {
144
+ event.stopPropagation();
145
+ userDismiss();
146
+ return;
147
+ }
148
+ if (trap && event.key === "Tab") {
149
+ const items = focusable(wrapper);
150
+ if (items.length === 0) {
151
+ event.preventDefault();
152
+ return;
153
+ }
154
+ const first = items[0];
155
+ const last = items[items.length - 1];
156
+ const active = document.activeElement;
157
+ const outside = !wrapper.contains(active);
158
+ if (event.shiftKey && (active === first || outside)) {
159
+ event.preventDefault();
160
+ last.focus();
161
+ } else if (!event.shiftKey && (active === last || outside)) {
162
+ event.preventDefault();
163
+ first.focus();
164
+ }
165
+ }
166
+ };
167
+ document.addEventListener("keydown", onKeydown, true);
168
+ removers.push(
169
+ () => document.removeEventListener("keydown", onKeydown, true)
170
+ );
171
+ }
172
+ if (triggers.includes("backdrop")) {
173
+ const onClick = (event) => {
174
+ if (!isTopmostFallback()) return;
175
+ if (event.target === wrapper) userDismiss();
176
+ };
177
+ wrapper.addEventListener("click", onClick);
178
+ removers.push(() => wrapper.removeEventListener("click", onClick));
179
+ }
180
+ if (triggers.includes("outside")) {
181
+ const ignore = outsideIgnore === void 0 ? [] : Array.isArray(outsideIgnore) ? outsideIgnore : [outsideIgnore];
182
+ const onDocClick = (event) => {
183
+ if (!isTopmostFallback()) return;
184
+ const target = event.target;
185
+ if (target === null) return;
186
+ if (wrapper.contains(target)) return;
187
+ if (ignore.some((el) => el === target || el.contains(target))) return;
188
+ userDismiss();
189
+ };
190
+ document.addEventListener("click", onDocClick, true);
191
+ removers.push(
192
+ () => document.removeEventListener("click", onDocClick, true)
193
+ );
194
+ }
195
+ if (initialFocus !== false) {
196
+ if (typeof initialFocus === "string") {
197
+ wrapper.querySelector(initialFocus)?.focus();
198
+ } else {
199
+ const first = focusable(wrapper)[0];
200
+ if (first !== void 0) {
201
+ first.focus();
202
+ } else {
203
+ wrapper.tabIndex = -1;
204
+ wrapper.focus();
205
+ }
206
+ }
207
+ }
208
+ return { el: wrapper, close, result };
209
+ }
210
+ function popover(anchor, content, options = {}) {
211
+ const {
212
+ container,
213
+ className = "kerf-popover",
214
+ placement = "bottom",
215
+ align = "start",
216
+ gap = 4,
217
+ dismiss = ["outside"],
218
+ initialFocus = false,
219
+ outsideIgnore,
220
+ onDismiss,
221
+ native = false
222
+ } = options;
223
+ const extraIgnore = outsideIgnore === void 0 ? [] : Array.isArray(outsideIgnore) ? [...outsideIgnore] : [outsideIgnore];
224
+ const handle = overlay(content, {
225
+ container,
226
+ className,
227
+ dismiss,
228
+ trap: false,
229
+ initialFocus,
230
+ onDismiss,
231
+ outsideIgnore: [anchor, ...extraIgnore],
232
+ native
233
+ });
234
+ const stopReposition = autoReposition(handle.el, anchor, {
235
+ placement,
236
+ align,
237
+ gap
238
+ });
239
+ void handle.result.then(stopReposition);
240
+ return handle;
241
+ }
242
+ function tooltip(anchor, content, options = {}) {
243
+ const {
244
+ container,
245
+ className = "kerf-tooltip",
246
+ delay = 400,
247
+ hideDelay = 100,
248
+ role = "tooltip",
249
+ placement = "top",
250
+ align = "start",
251
+ gap = 4,
252
+ native = false
253
+ } = options;
254
+ const body = typeof content === "function" ? content : typeof content === "string" ? jsx("span", { class: `${className}__text`, children: content }) : content;
255
+ const timers = {};
256
+ let current;
257
+ let presence = 0;
258
+ function show() {
259
+ const handle = overlay(body, {
260
+ container,
261
+ className,
262
+ dismiss: false,
263
+ trap: false,
264
+ initialFocus: false,
265
+ native
266
+ });
267
+ handle.el.setAttribute("role", role);
268
+ const stop = autoReposition(handle.el, anchor, { placement, align, gap });
269
+ current = { handle, stop };
270
+ }
271
+ function hide() {
272
+ if (current === void 0) return;
273
+ current.stop();
274
+ current.handle.close();
275
+ current = void 0;
276
+ }
277
+ const onEnter = (event) => {
278
+ presence |= event.type === "focus" ? 2 : 1;
279
+ if (timers.hide !== void 0) clearTimeout(timers.hide);
280
+ if (current !== void 0) return;
281
+ if (timers.show !== void 0) clearTimeout(timers.show);
282
+ timers.show = setTimeout(show, delay);
283
+ };
284
+ const onLeave = (event) => {
285
+ presence &= event.type === "blur" ? -3 : -2;
286
+ if (presence) return;
287
+ if (timers.show !== void 0) clearTimeout(timers.show);
288
+ if (current === void 0) return;
289
+ timers.hide = setTimeout(hide, hideDelay);
290
+ };
291
+ anchor.addEventListener("pointerenter", onEnter);
292
+ anchor.addEventListener("pointerleave", onLeave);
293
+ anchor.addEventListener("focus", onEnter);
294
+ anchor.addEventListener("blur", onLeave);
295
+ return () => {
296
+ anchor.removeEventListener("pointerenter", onEnter);
297
+ anchor.removeEventListener("pointerleave", onLeave);
298
+ anchor.removeEventListener("focus", onEnter);
299
+ anchor.removeEventListener("blur", onLeave);
300
+ if (timers.show !== void 0) clearTimeout(timers.show);
301
+ if (timers.hide !== void 0) clearTimeout(timers.hide);
302
+ hide();
303
+ };
304
+ }
305
+
306
+ // src/overlay-choice.ts
307
+ function choice(message, actions, options = {}) {
42
308
  const {
43
309
  container,
44
310
  className = "kerf-overlay",
45
311
  title,
46
- okText = "OK",
47
- cancelText = "Cancel",
48
- danger = false,
312
+ defaultValue,
49
313
  native = false,
50
314
  render
51
315
  } = options;
52
- const body = render !== void 0 ? render({ message, ok: { "data-confirm": "ok" }, cancel: { "data-confirm": "cancel" } }) : jsx("div", {
53
- class: "kerf-confirm",
316
+ const hasDefault = "defaultValue" in options;
317
+ const actionAttrs = actions.map((_, i) => ({ "data-choice": String(i) }));
318
+ const body = render !== void 0 ? render({ message, actions: actionAttrs }) : jsx("div", {
319
+ class: "kerf-choice",
54
320
  children: [
55
- title !== void 0 ? jsx("h2", { class: "kerf-confirm__title", children: title }) : "",
56
- jsx("p", { class: "kerf-confirm__message", children: message }),
321
+ title !== void 0 ? jsx("h2", { class: "kerf-choice__title", children: title }) : "",
322
+ jsx("p", { class: "kerf-choice__message", children: message }),
57
323
  jsx("div", {
58
- class: "kerf-confirm__actions",
59
- children: [
60
- jsx("button", { type: "button", "data-confirm": "cancel", children: cancelText }),
61
- jsx("button", {
324
+ class: "kerf-choice__actions",
325
+ children: actions.map(
326
+ (action, i) => jsx("button", {
62
327
  type: "button",
63
- "data-confirm": "ok",
64
- class: "kerf-confirm__ok",
65
- children: okText
328
+ class: action.className !== void 0 ? `kerf-choice__action ${action.className}` : "kerf-choice__action",
329
+ ...actionAttrs[i],
330
+ children: action.label
66
331
  })
67
- ]
332
+ )
68
333
  })
69
334
  ]
70
335
  });
336
+ let resolveChoice;
337
+ const result = new Promise((resolve) => {
338
+ resolveChoice = resolve;
339
+ });
71
340
  const handle = overlay(body, {
72
341
  container,
73
- className: danger ? `${className} kerf-confirm--danger` : className,
342
+ className,
74
343
  dismiss: ["escape", "backdrop"],
75
- initialFocus: '[data-confirm="ok"]',
344
+ initialFocus: "[data-choice]",
76
345
  trap: true,
77
346
  native
78
347
  });
79
- delegate(handle.el, "click", "[data-confirm]", (_event, el) => {
80
- handle.close(el.getAttribute("data-confirm") === "ok");
348
+ delegate(handle.el, "click", "[data-choice]", (_event, el) => {
349
+ resolveChoice(actions[Number(el.getAttribute("data-choice"))].value);
350
+ handle.close();
81
351
  });
82
- return handle.result.then((value) => value === true);
352
+ if (hasDefault) {
353
+ handle.el.addEventListener("keydown", (event) => {
354
+ if (event.key === "Enter") {
355
+ event.preventDefault();
356
+ resolveChoice(defaultValue);
357
+ handle.close();
358
+ }
359
+ });
360
+ }
361
+ void handle.result.then(() => resolveChoice(null));
362
+ return result;
83
363
  }
84
- function prompt(message, options = {}) {
364
+
365
+ // src/overlay-confirm.ts
366
+ function confirm(message, options = {}) {
85
367
  const {
86
368
  container,
87
369
  className = "kerf-overlay",
88
370
  title,
89
- defaultValue = "",
90
- placeholder,
91
- inputType = "text",
92
371
  okText = "OK",
93
372
  cancelText = "Cancel",
94
- validate,
373
+ danger = false,
95
374
  native = false,
96
375
  render
97
376
  } = options;
98
- const inputAttrs = {
99
- "data-prompt-input": "",
100
- type: inputType,
101
- value: defaultValue,
102
- ...placeholder !== void 0 ? { placeholder } : {}
103
- };
104
377
  const body = render !== void 0 ? render({
105
378
  message,
106
- input: inputAttrs,
107
- error: { "data-prompt-error": "" },
108
- ok: { "data-prompt": "ok" },
109
- cancel: { "data-prompt": "cancel" }
379
+ ok: { "data-confirm": "ok" },
380
+ cancel: { "data-confirm": "cancel" }
110
381
  }) : jsx("div", {
111
- class: "kerf-prompt",
382
+ class: "kerf-confirm",
112
383
  children: [
113
- title !== void 0 ? jsx("h2", { class: "kerf-prompt__title", children: title }) : "",
114
- jsx("label", { class: "kerf-prompt__message", children: message }),
115
- jsx("input", { class: "kerf-prompt__input", ...inputAttrs }),
116
- jsx("p", { class: "kerf-prompt__error", "data-prompt-error": "", children: "" }),
384
+ title !== void 0 ? jsx("h2", { class: "kerf-confirm__title", children: title }) : "",
385
+ jsx("p", { class: "kerf-confirm__message", children: message }),
117
386
  jsx("div", {
118
- class: "kerf-prompt__actions",
387
+ class: "kerf-confirm__actions",
119
388
  children: [
120
- jsx("button", { type: "button", "data-prompt": "cancel", children: cancelText }),
121
389
  jsx("button", {
122
390
  type: "button",
123
- "data-prompt": "ok",
124
- class: "kerf-prompt__ok",
391
+ "data-confirm": "cancel",
392
+ children: cancelText
393
+ }),
394
+ jsx("button", {
395
+ type: "button",
396
+ "data-confirm": "ok",
397
+ class: "kerf-confirm__ok",
125
398
  children: okText
126
399
  })
127
400
  ]
@@ -130,40 +403,19 @@ function prompt(message, options = {}) {
130
403
  });
131
404
  const handle = overlay(body, {
132
405
  container,
133
- className,
406
+ className: danger ? `${className} kerf-confirm--danger` : className,
134
407
  dismiss: ["escape", "backdrop"],
135
- initialFocus: "[data-prompt-input]",
408
+ initialFocus: '[data-confirm="ok"]',
136
409
  trap: true,
137
410
  native
138
411
  });
139
- const input = handle.el.querySelector("[data-prompt-input]");
140
- const errorEl = handle.el.querySelector("[data-prompt-error]");
141
- if (errorEl !== null) errorEl.hidden = true;
142
- function attemptOk() {
143
- const value = input.value;
144
- const error = validate?.(value);
145
- if (typeof error === "string" && error.length > 0) {
146
- if (errorEl !== null) {
147
- errorEl.textContent = error;
148
- errorEl.hidden = false;
149
- }
150
- input.focus();
151
- return;
152
- }
153
- handle.close(value);
154
- }
155
- delegate(handle.el, "click", "[data-prompt]", (_event, el) => {
156
- if (el.getAttribute("data-prompt") === "ok") attemptOk();
157
- else handle.close(null);
158
- });
159
- handle.el.addEventListener("keydown", (event) => {
160
- if (event.key === "Enter" && event.target === input) {
161
- event.preventDefault();
162
- attemptOk();
163
- }
412
+ delegate(handle.el, "click", "[data-confirm]", (_event, el) => {
413
+ handle.close(el.getAttribute("data-confirm") === "ok");
164
414
  });
165
- return handle.result.then((value) => typeof value === "string" ? value : null);
415
+ return handle.result.then((value) => value === true);
166
416
  }
417
+
418
+ // src/overlay-form.ts
167
419
  function form(fields, options = {}) {
168
420
  const {
169
421
  container,
@@ -198,16 +450,30 @@ function form(fields, options = {}) {
198
450
  (field) => jsx("div", {
199
451
  class: "kerf-form__field",
200
452
  children: [
201
- jsx("label", { class: "kerf-form__label", children: field.label ?? field.name }),
202
- jsx("input", { class: "kerf-form__input", ...fieldAttrs(field) }),
203
- jsx("p", { class: "kerf-form__error", "data-field-error": field.name, children: "" })
453
+ jsx("label", {
454
+ class: "kerf-form__label",
455
+ children: field.label ?? field.name
456
+ }),
457
+ jsx("input", {
458
+ class: "kerf-form__input",
459
+ ...fieldAttrs(field)
460
+ }),
461
+ jsx("p", {
462
+ class: "kerf-form__error",
463
+ "data-field-error": field.name,
464
+ children: ""
465
+ })
204
466
  ]
205
467
  })
206
468
  ),
207
469
  jsx("div", {
208
470
  class: "kerf-form__actions",
209
471
  children: [
210
- jsx("button", { type: "button", "data-form": "cancel", children: cancelText }),
472
+ jsx("button", {
473
+ type: "button",
474
+ "data-form": "cancel",
475
+ children: cancelText
476
+ }),
211
477
  jsx("button", {
212
478
  type: "button",
213
479
  "data-form": "ok",
@@ -226,13 +492,19 @@ function form(fields, options = {}) {
226
492
  trap: true,
227
493
  native
228
494
  });
229
- const byAttr = (attr, name) => Array.from(handle.el.querySelectorAll(`[${attr}]`)).find(
230
- (el) => el.getAttribute(attr) === name
231
- );
232
- const errorFor = (name) => Array.from(handle.el.querySelectorAll("[data-field-error]")).find(
233
- (el) => el.getAttribute("data-field-error") === name
234
- ) ?? null;
495
+ const errorFor = (name) => Array.from(
496
+ handle.el.querySelectorAll("[data-field-error]")
497
+ ).find((el) => el.getAttribute("data-field-error") === name) ?? null;
498
+ const inputFor = (name) => {
499
+ const input = Array.from(
500
+ handle.el.querySelectorAll("input[data-field]")
501
+ ).find((el) => el.getAttribute("data-field") === name);
502
+ if (input !== void 0) return input;
503
+ handle.close(null);
504
+ throw new Error(`form(): render missing <input data-field="${name}">.`);
505
+ };
235
506
  for (const field of fields) {
507
+ inputFor(field.name);
236
508
  const errorEl = errorFor(field.name);
237
509
  if (errorEl !== null) errorEl.hidden = true;
238
510
  }
@@ -240,7 +512,7 @@ function form(fields, options = {}) {
240
512
  const record = {};
241
513
  let firstInvalid = null;
242
514
  for (const field of fields) {
243
- const el = byAttr("data-field", field.name);
515
+ const el = inputFor(field.name);
244
516
  const value = el.value;
245
517
  record[field.name] = value;
246
518
  const error = field.validate?.(value);
@@ -275,55 +547,107 @@ function form(fields, options = {}) {
275
547
  (value) => value !== null && typeof value === "object" ? value : null
276
548
  );
277
549
  }
278
- function choice(message, actions, options = {}) {
279
- const { container, className = "kerf-overlay", title, defaultValue, native = false, render } = options;
280
- const hasDefault = "defaultValue" in options;
281
- const actionAttrs = actions.map((_, i) => ({ "data-choice": String(i) }));
282
- const body = render !== void 0 ? render({ message, actions: actionAttrs }) : jsx("div", {
283
- class: "kerf-choice",
550
+
551
+ // src/overlay-prompt.ts
552
+ function prompt(message, options = {}) {
553
+ const {
554
+ container,
555
+ className = "kerf-overlay",
556
+ title,
557
+ defaultValue = "",
558
+ placeholder,
559
+ inputType = "text",
560
+ okText = "OK",
561
+ cancelText = "Cancel",
562
+ validate,
563
+ native = false,
564
+ render
565
+ } = options;
566
+ const inputAttrs = {
567
+ "data-prompt-input": "",
568
+ type: inputType,
569
+ value: defaultValue,
570
+ ...placeholder !== void 0 ? { placeholder } : {}
571
+ };
572
+ const body = render !== void 0 ? render({
573
+ message,
574
+ input: inputAttrs,
575
+ error: { "data-prompt-error": "" },
576
+ ok: { "data-prompt": "ok" },
577
+ cancel: { "data-prompt": "cancel" }
578
+ }) : jsx("div", {
579
+ class: "kerf-prompt",
284
580
  children: [
285
- title !== void 0 ? jsx("h2", { class: "kerf-choice__title", children: title }) : "",
286
- jsx("p", { class: "kerf-choice__message", children: message }),
581
+ title !== void 0 ? jsx("h2", { class: "kerf-prompt__title", children: title }) : "",
582
+ jsx("label", { class: "kerf-prompt__message", children: message }),
583
+ jsx("input", { class: "kerf-prompt__input", ...inputAttrs }),
584
+ jsx("p", {
585
+ class: "kerf-prompt__error",
586
+ "data-prompt-error": "",
587
+ children: ""
588
+ }),
287
589
  jsx("div", {
288
- class: "kerf-choice__actions",
289
- children: actions.map(
290
- (action, i) => jsx("button", {
590
+ class: "kerf-prompt__actions",
591
+ children: [
592
+ jsx("button", {
291
593
  type: "button",
292
- class: action.className !== void 0 ? `kerf-choice__action ${action.className}` : "kerf-choice__action",
293
- ...actionAttrs[i],
294
- children: action.label
594
+ "data-prompt": "cancel",
595
+ children: cancelText
596
+ }),
597
+ jsx("button", {
598
+ type: "button",
599
+ "data-prompt": "ok",
600
+ class: "kerf-prompt__ok",
601
+ children: okText
295
602
  })
296
- )
297
- })
298
- ]
299
- });
300
- let resolveChoice;
301
- const result = new Promise((resolve) => {
302
- resolveChoice = resolve;
603
+ ]
604
+ })
605
+ ]
303
606
  });
304
607
  const handle = overlay(body, {
305
608
  container,
306
609
  className,
307
610
  dismiss: ["escape", "backdrop"],
308
- initialFocus: "[data-choice]",
611
+ initialFocus: "[data-prompt-input]",
309
612
  trap: true,
310
613
  native
311
614
  });
312
- delegate(handle.el, "click", "[data-choice]", (_event, el) => {
313
- resolveChoice(actions[Number(el.getAttribute("data-choice"))].value);
314
- handle.close();
315
- });
316
- if (hasDefault) {
317
- handle.el.addEventListener("keydown", (event) => {
318
- if (event.key === "Enter") {
319
- event.preventDefault();
320
- resolveChoice(defaultValue);
321
- handle.close();
615
+ const input = handle.el.querySelector(
616
+ "input[data-prompt-input]"
617
+ );
618
+ if (input === null) {
619
+ handle.close(null);
620
+ throw new Error("prompt(): render missing <input data-prompt-input>.");
621
+ }
622
+ const promptInput = input;
623
+ const errorEl = handle.el.querySelector("[data-prompt-error]");
624
+ if (errorEl !== null) errorEl.hidden = true;
625
+ function attemptOk() {
626
+ const value = promptInput.value;
627
+ const error = validate?.(value);
628
+ if (typeof error === "string" && error.length > 0) {
629
+ if (errorEl !== null) {
630
+ errorEl.textContent = error;
631
+ errorEl.hidden = false;
322
632
  }
323
- });
633
+ promptInput.focus();
634
+ return;
635
+ }
636
+ handle.close(value);
324
637
  }
325
- void handle.result.then(() => resolveChoice(null));
326
- return result;
638
+ delegate(handle.el, "click", "[data-prompt]", (_event, el) => {
639
+ if (el.getAttribute("data-prompt") === "ok") attemptOk();
640
+ else handle.close(null);
641
+ });
642
+ handle.el.addEventListener("keydown", (event) => {
643
+ if (event.key === "Enter" && event.target === promptInput) {
644
+ event.preventDefault();
645
+ attemptOk();
646
+ }
647
+ });
648
+ return handle.result.then(
649
+ (value) => typeof value === "string" ? value : null
650
+ );
327
651
  }
328
652
 
329
653
  // src/overlay-toast.ts
@@ -364,8 +688,14 @@ function toast(content, options = {}) {
364
688
  if (variant !== void 0) el.classList.add(`${className}--${variant}`);
365
689
  el.setAttribute("role", role);
366
690
  region.appendChild(el);
367
- const disposeMount = mount(el, typeof content === "function" ? content : () => content);
368
- const state = { dismissed: false, removed: false, timer: void 0, exitTimer: void 0 };
691
+ const render = typeof content === "function" ? content : () => typeof content === "string" ? escapeHtml(content) : content;
692
+ const disposeMount = mount(el, render);
693
+ const state = {
694
+ dismissed: false,
695
+ removed: false,
696
+ timer: void 0,
697
+ exitTimer: void 0
698
+ };
369
699
  if (enterClass !== void 0) {
370
700
  globalThis.requestAnimationFrame(() => {
371
701
  if (!state.dismissed) el.classList.add(enterClass);
@@ -406,229 +736,6 @@ function toast(content, options = {}) {
406
736
  return { el, dismiss };
407
737
  }
408
738
 
409
- // src/overlay.ts
410
- var FOCUSABLE = 'a[href],area[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),iframe,[tabindex]:not([tabindex="-1"]),[contenteditable="true"]';
411
- function focusable(root) {
412
- return Array.from(root.querySelectorAll(FOCUSABLE)).filter(
413
- (el) => !el.hasAttribute("hidden")
414
- );
415
- }
416
- function supportsDialog() {
417
- return typeof HTMLDialogElement !== "undefined" && typeof HTMLDialogElement.prototype.showModal === "function";
418
- }
419
- function supportsPopover() {
420
- return typeof HTMLElement !== "undefined" && typeof HTMLElement.prototype.showPopover === "function";
421
- }
422
- function overlay(content, options = {}) {
423
- const {
424
- container = document.body,
425
- className = "kerf-overlay",
426
- dismiss = ["escape", "backdrop"],
427
- initialFocus = true,
428
- trap = true,
429
- role = "dialog",
430
- onDismiss,
431
- outsideIgnore,
432
- native = false
433
- } = options;
434
- const triggers = dismiss === false ? [] : Array.isArray(dismiss) ? dismiss : [dismiss];
435
- const restoreTo = document.activeElement;
436
- const useDialog = native && trap && supportsDialog();
437
- const usePopover = native && !trap && supportsPopover();
438
- const wrapper = useDialog ? document.createElement("dialog") : document.createElement("div");
439
- wrapper.className = className;
440
- if (usePopover) wrapper.setAttribute("popover", "manual");
441
- if (trap && !useDialog) {
442
- wrapper.setAttribute("role", role);
443
- wrapper.setAttribute("aria-modal", "true");
444
- }
445
- container.appendChild(wrapper);
446
- const disposeMount = mount(wrapper, typeof content === "function" ? content : () => content);
447
- let nativeOpened = false;
448
- if (useDialog) {
449
- wrapper.showModal();
450
- nativeOpened = true;
451
- } else if (usePopover) {
452
- wrapper.showPopover();
453
- wrapper.style.inset = "auto";
454
- nativeOpened = true;
455
- }
456
- const removers = [];
457
- const resultBox = {};
458
- const result = new Promise((resolve) => {
459
- resultBox.resolve = resolve;
460
- });
461
- const state = { closed: false };
462
- function close(value) {
463
- if (state.closed) return;
464
- state.closed = true;
465
- for (const remove of removers) remove();
466
- disposeMount();
467
- if (nativeOpened) {
468
- nativeOpened = false;
469
- if (useDialog) wrapper.close();
470
- else wrapper.hidePopover();
471
- }
472
- wrapper.remove();
473
- if (restoreTo instanceof HTMLElement && restoreTo.isConnected) restoreTo.focus();
474
- resultBox.resolve?.(value);
475
- }
476
- function userDismiss() {
477
- onDismiss?.();
478
- close();
479
- }
480
- const wantEscape = triggers.includes("escape");
481
- if (useDialog) {
482
- const onCancel = (event) => {
483
- event.preventDefault();
484
- if (wantEscape) userDismiss();
485
- };
486
- wrapper.addEventListener("cancel", onCancel);
487
- removers.push(() => wrapper.removeEventListener("cancel", onCancel));
488
- } else if (wantEscape || trap) {
489
- const onKeydown = (event) => {
490
- if (wantEscape && event.key === "Escape") {
491
- event.stopPropagation();
492
- userDismiss();
493
- return;
494
- }
495
- if (trap && event.key === "Tab") {
496
- const items = focusable(wrapper);
497
- if (items.length === 0) {
498
- event.preventDefault();
499
- return;
500
- }
501
- const first = items[0];
502
- const last = items[items.length - 1];
503
- const active = document.activeElement;
504
- const outside = !wrapper.contains(active);
505
- if (event.shiftKey && (active === first || outside)) {
506
- event.preventDefault();
507
- last.focus();
508
- } else if (!event.shiftKey && (active === last || outside)) {
509
- event.preventDefault();
510
- first.focus();
511
- }
512
- }
513
- };
514
- document.addEventListener("keydown", onKeydown, true);
515
- removers.push(() => document.removeEventListener("keydown", onKeydown, true));
516
- }
517
- if (triggers.includes("backdrop")) {
518
- const onClick = (event) => {
519
- if (event.target === wrapper) userDismiss();
520
- };
521
- wrapper.addEventListener("click", onClick);
522
- removers.push(() => wrapper.removeEventListener("click", onClick));
523
- }
524
- if (triggers.includes("outside")) {
525
- const ignore = outsideIgnore === void 0 ? [] : Array.isArray(outsideIgnore) ? outsideIgnore : [outsideIgnore];
526
- const onDocClick = (event) => {
527
- const target = event.target;
528
- if (target === null) return;
529
- if (wrapper.contains(target)) return;
530
- if (ignore.some((el) => el === target || el.contains(target))) return;
531
- userDismiss();
532
- };
533
- document.addEventListener("click", onDocClick, true);
534
- removers.push(() => document.removeEventListener("click", onDocClick, true));
535
- }
536
- if (initialFocus !== false) {
537
- if (typeof initialFocus === "string") {
538
- wrapper.querySelector(initialFocus)?.focus();
539
- } else {
540
- const first = focusable(wrapper)[0];
541
- if (first !== void 0) {
542
- first.focus();
543
- } else {
544
- wrapper.tabIndex = -1;
545
- wrapper.focus();
546
- }
547
- }
548
- }
549
- return { el: wrapper, close, result };
550
- }
551
- function popover(anchor, content, options = {}) {
552
- const {
553
- container,
554
- className = "kerf-popover",
555
- placement = "bottom",
556
- align = "start",
557
- gap = 4,
558
- dismiss = ["outside"],
559
- initialFocus = false,
560
- outsideIgnore,
561
- onDismiss,
562
- native = false
563
- } = options;
564
- const extraIgnore = outsideIgnore === void 0 ? [] : Array.isArray(outsideIgnore) ? [...outsideIgnore] : [outsideIgnore];
565
- const handle = overlay(content, {
566
- container,
567
- className,
568
- dismiss,
569
- trap: false,
570
- initialFocus,
571
- onDismiss,
572
- outsideIgnore: [anchor, ...extraIgnore],
573
- native
574
- });
575
- const stopReposition = autoReposition(handle.el, anchor, { placement, align, gap });
576
- void handle.result.then(stopReposition);
577
- return handle;
578
- }
579
- function tooltip(anchor, content, options = {}) {
580
- const {
581
- container,
582
- className = "kerf-tooltip",
583
- delay = 400,
584
- hideDelay = 100,
585
- role = "tooltip",
586
- placement = "top",
587
- align = "start",
588
- gap = 4,
589
- native = false
590
- } = options;
591
- const body = typeof content === "function" ? content : typeof content === "string" ? jsx("span", { class: `${className}__text`, children: content }) : content;
592
- const timers = {};
593
- let current;
594
- function show() {
595
- const handle = overlay(body, { container, className, dismiss: false, trap: false, initialFocus: false, native });
596
- handle.el.setAttribute("role", role);
597
- const stop = autoReposition(handle.el, anchor, { placement, align, gap });
598
- current = { handle, stop };
599
- }
600
- function hide() {
601
- if (current === void 0) return;
602
- current.stop();
603
- current.handle.close();
604
- current = void 0;
605
- }
606
- const onEnter = () => {
607
- if (timers.hide !== void 0) clearTimeout(timers.hide);
608
- if (current !== void 0) return;
609
- if (timers.show !== void 0) clearTimeout(timers.show);
610
- timers.show = setTimeout(show, delay);
611
- };
612
- const onLeave = () => {
613
- if (timers.show !== void 0) clearTimeout(timers.show);
614
- if (current === void 0) return;
615
- timers.hide = setTimeout(hide, hideDelay);
616
- };
617
- anchor.addEventListener("pointerenter", onEnter);
618
- anchor.addEventListener("pointerleave", onLeave);
619
- anchor.addEventListener("focus", onEnter);
620
- anchor.addEventListener("blur", onLeave);
621
- return () => {
622
- anchor.removeEventListener("pointerenter", onEnter);
623
- anchor.removeEventListener("pointerleave", onLeave);
624
- anchor.removeEventListener("focus", onEnter);
625
- anchor.removeEventListener("blur", onLeave);
626
- if (timers.show !== void 0) clearTimeout(timers.show);
627
- if (timers.hide !== void 0) clearTimeout(timers.hide);
628
- hide();
629
- };
630
- }
631
-
632
739
  export { autoReposition, choice, confirm, form, overlay, popover, positionAnchored, prompt, toast, tooltip };
633
- //# sourceMappingURL=overlay.js.map
740
+
634
741
  //# sourceMappingURL=overlay.js.map