kerfjs 4.2.0 → 4.3.0

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/dist/overlay.js CHANGED
@@ -1,4 +1,4 @@
1
- import { mount } from './chunk-LKWAKC2X.js';
1
+ import { mount } from './chunk-SRWQKB33.js';
2
2
  import { delegate } from './chunk-KEZTD6H4.js';
3
3
  import './chunk-QIP723L4.js';
4
4
  import './chunk-YHH7OUFA.js';
@@ -37,197 +37,7 @@ function autoReposition(el, anchor, options = {}) {
37
37
  };
38
38
  }
39
39
 
40
- // src/overlay-toast.ts
41
- var TOAST_SET = /* @__PURE__ */ Symbol("kerf.toasts");
42
- function toastRegion(container) {
43
- if (container !== void 0) return container;
44
- const existing = document.querySelector(".kerf-toasts");
45
- if (existing !== null) return existing;
46
- const region = document.createElement("div");
47
- region.className = "kerf-toasts";
48
- region.setAttribute("aria-live", "polite");
49
- document.body.appendChild(region);
50
- return region;
51
- }
52
- function toast(content, options = {}) {
53
- const {
54
- container,
55
- className = "kerf-toast",
56
- duration = 4e3,
57
- role = "status",
58
- mode = "stack",
59
- collapse = "fade",
60
- variant,
61
- enterClass,
62
- exitClass,
63
- exitDuration = 0
64
- } = options;
65
- const region = toastRegion(container);
66
- const active = region[TOAST_SET] ??= /* @__PURE__ */ new Set();
67
- if (mode === "replace") {
68
- for (const d of [...active]) {
69
- if (collapse === "instant") d.removeNow();
70
- else d();
71
- }
72
- }
73
- const el = document.createElement("div");
74
- el.className = className;
75
- if (variant !== void 0) el.classList.add(`${className}--${variant}`);
76
- el.setAttribute("role", role);
77
- region.appendChild(el);
78
- const disposeMount = mount(el, typeof content === "function" ? content : () => content);
79
- const state = { dismissed: false, removed: false, timer: void 0, exitTimer: void 0 };
80
- if (enterClass !== void 0) {
81
- globalThis.requestAnimationFrame(() => {
82
- if (!state.dismissed) el.classList.add(enterClass);
83
- });
84
- }
85
- const remove = () => {
86
- if (state.removed) return;
87
- state.removed = true;
88
- state.dismissed = true;
89
- if (state.exitTimer !== void 0) clearTimeout(state.exitTimer);
90
- disposeMount();
91
- el.remove();
92
- active.delete(dismiss);
93
- };
94
- const removeNow = () => {
95
- if (state.timer !== void 0) clearTimeout(state.timer);
96
- remove();
97
- };
98
- const fadeOut = () => {
99
- if (state.dismissed) return;
100
- state.dismissed = true;
101
- if (state.timer !== void 0) clearTimeout(state.timer);
102
- if (enterClass !== void 0) el.classList.remove(enterClass);
103
- if (exitClass !== void 0) el.classList.add(exitClass);
104
- if (exitClass !== void 0 || exitDuration > 0) {
105
- state.exitTimer = setTimeout(remove, exitDuration);
106
- } else {
107
- remove();
108
- }
109
- };
110
- function dismiss(options2) {
111
- if (options2?.instant === true) removeNow();
112
- else fadeOut();
113
- }
114
- dismiss.removeNow = removeNow;
115
- active.add(dismiss);
116
- if (duration > 0) state.timer = setTimeout(fadeOut, duration);
117
- return { el, dismiss };
118
- }
119
-
120
- // src/overlay.ts
121
- 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"]';
122
- function focusable(root) {
123
- return Array.from(root.querySelectorAll(FOCUSABLE)).filter(
124
- (el) => !el.hasAttribute("hidden")
125
- );
126
- }
127
- function overlay(content, options = {}) {
128
- const {
129
- container = document.body,
130
- className = "kerf-overlay",
131
- dismiss = ["escape", "backdrop"],
132
- initialFocus = true,
133
- trap = true,
134
- role = "dialog",
135
- onDismiss,
136
- outsideIgnore
137
- } = options;
138
- const triggers = dismiss === false ? [] : Array.isArray(dismiss) ? dismiss : [dismiss];
139
- const restoreTo = document.activeElement;
140
- const wrapper = document.createElement("div");
141
- wrapper.className = className;
142
- if (trap) {
143
- wrapper.setAttribute("role", role);
144
- wrapper.setAttribute("aria-modal", "true");
145
- }
146
- container.appendChild(wrapper);
147
- const disposeMount = mount(wrapper, typeof content === "function" ? content : () => content);
148
- const removers = [];
149
- const resultBox = {};
150
- const result = new Promise((resolve) => {
151
- resultBox.resolve = resolve;
152
- });
153
- const state = { closed: false };
154
- function close(value) {
155
- if (state.closed) return;
156
- state.closed = true;
157
- for (const remove of removers) remove();
158
- disposeMount();
159
- wrapper.remove();
160
- if (restoreTo instanceof HTMLElement && restoreTo.isConnected) restoreTo.focus();
161
- resultBox.resolve?.(value);
162
- }
163
- function userDismiss() {
164
- onDismiss?.();
165
- close();
166
- }
167
- const wantEscape = triggers.includes("escape");
168
- if (wantEscape || trap) {
169
- const onKeydown = (event) => {
170
- if (wantEscape && event.key === "Escape") {
171
- event.stopPropagation();
172
- userDismiss();
173
- return;
174
- }
175
- if (trap && event.key === "Tab") {
176
- const items = focusable(wrapper);
177
- if (items.length === 0) {
178
- event.preventDefault();
179
- return;
180
- }
181
- const first = items[0];
182
- const last = items[items.length - 1];
183
- const active = document.activeElement;
184
- const outside = !wrapper.contains(active);
185
- if (event.shiftKey && (active === first || outside)) {
186
- event.preventDefault();
187
- last.focus();
188
- } else if (!event.shiftKey && (active === last || outside)) {
189
- event.preventDefault();
190
- first.focus();
191
- }
192
- }
193
- };
194
- document.addEventListener("keydown", onKeydown, true);
195
- removers.push(() => document.removeEventListener("keydown", onKeydown, true));
196
- }
197
- if (triggers.includes("backdrop")) {
198
- const onClick = (event) => {
199
- if (event.target === wrapper) userDismiss();
200
- };
201
- wrapper.addEventListener("click", onClick);
202
- removers.push(() => wrapper.removeEventListener("click", onClick));
203
- }
204
- if (triggers.includes("outside")) {
205
- const ignore = outsideIgnore === void 0 ? [] : Array.isArray(outsideIgnore) ? outsideIgnore : [outsideIgnore];
206
- const onDocClick = (event) => {
207
- const target = event.target;
208
- if (target === null) return;
209
- if (wrapper.contains(target)) return;
210
- if (ignore.some((el) => el === target || el.contains(target))) return;
211
- userDismiss();
212
- };
213
- document.addEventListener("click", onDocClick, true);
214
- removers.push(() => document.removeEventListener("click", onDocClick, true));
215
- }
216
- if (initialFocus !== false) {
217
- if (typeof initialFocus === "string") {
218
- wrapper.querySelector(initialFocus)?.focus();
219
- } else {
220
- const first = focusable(wrapper)[0];
221
- if (first !== void 0) {
222
- first.focus();
223
- } else {
224
- wrapper.tabIndex = -1;
225
- wrapper.focus();
226
- }
227
- }
228
- }
229
- return { el: wrapper, close, result };
230
- }
40
+ // src/overlay-dialogs.ts
231
41
  function confirm(message, options = {}) {
232
42
  const {
233
43
  container,
@@ -236,6 +46,7 @@ function confirm(message, options = {}) {
236
46
  okText = "OK",
237
47
  cancelText = "Cancel",
238
48
  danger = false,
49
+ native = false,
239
50
  render
240
51
  } = options;
241
52
  const body = render !== void 0 ? render({ message, ok: { "data-confirm": "ok" }, cancel: { "data-confirm": "cancel" } }) : jsx("div", {
@@ -262,7 +73,8 @@ function confirm(message, options = {}) {
262
73
  className: danger ? `${className} kerf-confirm--danger` : className,
263
74
  dismiss: ["escape", "backdrop"],
264
75
  initialFocus: '[data-confirm="ok"]',
265
- trap: true
76
+ trap: true,
77
+ native
266
78
  });
267
79
  delegate(handle.el, "click", "[data-confirm]", (_event, el) => {
268
80
  handle.close(el.getAttribute("data-confirm") === "ok");
@@ -280,6 +92,7 @@ function prompt(message, options = {}) {
280
92
  okText = "OK",
281
93
  cancelText = "Cancel",
282
94
  validate,
95
+ native = false,
283
96
  render
284
97
  } = options;
285
98
  const inputAttrs = {
@@ -320,7 +133,8 @@ function prompt(message, options = {}) {
320
133
  className,
321
134
  dismiss: ["escape", "backdrop"],
322
135
  initialFocus: "[data-prompt-input]",
323
- trap: true
136
+ trap: true,
137
+ native
324
138
  });
325
139
  const input = handle.el.querySelector("[data-prompt-input]");
326
140
  const errorEl = handle.el.querySelector("[data-prompt-error]");
@@ -351,7 +165,15 @@ function prompt(message, options = {}) {
351
165
  return handle.result.then((value) => typeof value === "string" ? value : null);
352
166
  }
353
167
  function form(fields, options = {}) {
354
- const { container, className = "kerf-overlay", title, okText = "OK", cancelText = "Cancel", render } = options;
168
+ const {
169
+ container,
170
+ className = "kerf-overlay",
171
+ title,
172
+ okText = "OK",
173
+ cancelText = "Cancel",
174
+ native = false,
175
+ render
176
+ } = options;
355
177
  const fieldAttrs = (field) => ({
356
178
  "data-field": field.name,
357
179
  name: field.name,
@@ -401,7 +223,8 @@ function form(fields, options = {}) {
401
223
  className,
402
224
  dismiss: ["escape", "backdrop"],
403
225
  initialFocus: "[data-field]",
404
- trap: true
226
+ trap: true,
227
+ native
405
228
  });
406
229
  const byAttr = (attr, name) => Array.from(handle.el.querySelectorAll(`[${attr}]`)).find(
407
230
  (el) => el.getAttribute(attr) === name
@@ -453,7 +276,7 @@ function form(fields, options = {}) {
453
276
  );
454
277
  }
455
278
  function choice(message, actions, options = {}) {
456
- const { container, className = "kerf-overlay", title, defaultValue, render } = options;
279
+ const { container, className = "kerf-overlay", title, defaultValue, native = false, render } = options;
457
280
  const hasDefault = "defaultValue" in options;
458
281
  const actionAttrs = actions.map((_, i) => ({ "data-choice": String(i) }));
459
282
  const body = render !== void 0 ? render({ message, actions: actionAttrs }) : jsx("div", {
@@ -483,7 +306,8 @@ function choice(message, actions, options = {}) {
483
306
  className,
484
307
  dismiss: ["escape", "backdrop"],
485
308
  initialFocus: "[data-choice]",
486
- trap: true
309
+ trap: true,
310
+ native
487
311
  });
488
312
  delegate(handle.el, "click", "[data-choice]", (_event, el) => {
489
313
  resolveChoice(actions[Number(el.getAttribute("data-choice"))].value);
@@ -501,6 +325,229 @@ function choice(message, actions, options = {}) {
501
325
  void handle.result.then(() => resolveChoice(null));
502
326
  return result;
503
327
  }
328
+
329
+ // src/overlay-toast.ts
330
+ var TOAST_SET = /* @__PURE__ */ Symbol("kerf.toasts");
331
+ function toastRegion(container) {
332
+ if (container !== void 0) return container;
333
+ const existing = document.querySelector(".kerf-toasts");
334
+ if (existing !== null) return existing;
335
+ const region = document.createElement("div");
336
+ region.className = "kerf-toasts";
337
+ region.setAttribute("aria-live", "polite");
338
+ document.body.appendChild(region);
339
+ return region;
340
+ }
341
+ function toast(content, options = {}) {
342
+ const {
343
+ container,
344
+ className = "kerf-toast",
345
+ duration = 4e3,
346
+ role = "status",
347
+ mode = "stack",
348
+ collapse = "fade",
349
+ variant,
350
+ enterClass,
351
+ exitClass,
352
+ exitDuration = 0
353
+ } = options;
354
+ const region = toastRegion(container);
355
+ const active = region[TOAST_SET] ??= /* @__PURE__ */ new Set();
356
+ if (mode === "replace") {
357
+ for (const d of [...active]) {
358
+ if (collapse === "instant") d.removeNow();
359
+ else d();
360
+ }
361
+ }
362
+ const el = document.createElement("div");
363
+ el.className = className;
364
+ if (variant !== void 0) el.classList.add(`${className}--${variant}`);
365
+ el.setAttribute("role", role);
366
+ 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 };
369
+ if (enterClass !== void 0) {
370
+ globalThis.requestAnimationFrame(() => {
371
+ if (!state.dismissed) el.classList.add(enterClass);
372
+ });
373
+ }
374
+ const remove = () => {
375
+ if (state.removed) return;
376
+ state.removed = true;
377
+ state.dismissed = true;
378
+ if (state.exitTimer !== void 0) clearTimeout(state.exitTimer);
379
+ disposeMount();
380
+ el.remove();
381
+ active.delete(dismiss);
382
+ };
383
+ const removeNow = () => {
384
+ if (state.timer !== void 0) clearTimeout(state.timer);
385
+ remove();
386
+ };
387
+ const fadeOut = () => {
388
+ if (state.dismissed) return;
389
+ state.dismissed = true;
390
+ if (state.timer !== void 0) clearTimeout(state.timer);
391
+ if (enterClass !== void 0) el.classList.remove(enterClass);
392
+ if (exitClass !== void 0) el.classList.add(exitClass);
393
+ if (exitClass !== void 0 || exitDuration > 0) {
394
+ state.exitTimer = setTimeout(remove, exitDuration);
395
+ } else {
396
+ remove();
397
+ }
398
+ };
399
+ function dismiss(options2) {
400
+ if (options2?.instant === true) removeNow();
401
+ else fadeOut();
402
+ }
403
+ dismiss.removeNow = removeNow;
404
+ active.add(dismiss);
405
+ if (duration > 0) state.timer = setTimeout(fadeOut, duration);
406
+ return { el, dismiss };
407
+ }
408
+
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
+ }
504
551
  function popover(anchor, content, options = {}) {
505
552
  const {
506
553
  container,
@@ -511,7 +558,8 @@ function popover(anchor, content, options = {}) {
511
558
  dismiss = ["outside"],
512
559
  initialFocus = false,
513
560
  outsideIgnore,
514
- onDismiss
561
+ onDismiss,
562
+ native = false
515
563
  } = options;
516
564
  const extraIgnore = outsideIgnore === void 0 ? [] : Array.isArray(outsideIgnore) ? [...outsideIgnore] : [outsideIgnore];
517
565
  const handle = overlay(content, {
@@ -521,7 +569,8 @@ function popover(anchor, content, options = {}) {
521
569
  trap: false,
522
570
  initialFocus,
523
571
  onDismiss,
524
- outsideIgnore: [anchor, ...extraIgnore]
572
+ outsideIgnore: [anchor, ...extraIgnore],
573
+ native
525
574
  });
526
575
  const stopReposition = autoReposition(handle.el, anchor, { placement, align, gap });
527
576
  void handle.result.then(stopReposition);
@@ -536,13 +585,14 @@ function tooltip(anchor, content, options = {}) {
536
585
  role = "tooltip",
537
586
  placement = "top",
538
587
  align = "start",
539
- gap = 4
588
+ gap = 4,
589
+ native = false
540
590
  } = options;
541
591
  const body = typeof content === "function" ? content : typeof content === "string" ? jsx("span", { class: `${className}__text`, children: content }) : content;
542
592
  const timers = {};
543
593
  let current;
544
594
  function show() {
545
- const handle = overlay(body, { container, className, dismiss: false, trap: false, initialFocus: false });
595
+ const handle = overlay(body, { container, className, dismiss: false, trap: false, initialFocus: false, native });
546
596
  handle.el.setAttribute("role", role);
547
597
  const stop = autoReposition(handle.el, anchor, { placement, align, gap });
548
598
  current = { handle, stop };