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.d.ts CHANGED
@@ -39,128 +39,6 @@ declare function positionAnchored(el: HTMLElement, anchor: Element, options?: An
39
39
  */
40
40
  declare function autoReposition(el: HTMLElement, anchor: Element, options?: AnchorPositionOptions): () => void;
41
41
 
42
- /**
43
- * `toast()` for `kerfjs/overlay` — a non-modal, auto-dismissing notification that
44
- * stacks in a shared body-level region. Split out of `overlay.ts` (KF-513) since
45
- * it's a distinct transient-UI concern from the modal dialogs; re-exported from
46
- * `overlay.ts` so the public `kerfjs/overlay` surface is unchanged. Structural
47
- * only — kerf ships no CSS; you style the region / toast / animations.
48
- */
49
-
50
- /** Content for a {@link toast}: text, `SafeHtml`, or a render function. */
51
- type ToastContent = string | SafeHtml | (() => MountResult);
52
- /** Accent variant for a {@link toast} — mapped to a `${className}--${variant}` class. */
53
- type ToastVariant = 'info' | 'success' | 'warning';
54
- /** Options for {@link toast}. */
55
- interface ToastOptions {
56
- /** Where toasts stack. Default: a lazily-created `<div class="kerf-toasts">` on `document.body`. */
57
- container?: Element;
58
- /** Class on the toast element. Default `'kerf-toast'`. */
59
- className?: string;
60
- /** Auto-dismiss after this many ms. `0` keeps it until dismissed by hand. Default `4000`. */
61
- duration?: number;
62
- /** ARIA role. Default `'status'`. */
63
- role?: string;
64
- /**
65
- * `'stack'` (default) shows toasts stacked in the region; `'replace'` dismisses
66
- * the region's current toast(s) first (collapse-to-latest for a rapid sequence).
67
- */
68
- mode?: 'stack' | 'replace';
69
- /**
70
- * How `mode: 'replace'` drops the prior toast(s): `'fade'` (default) runs their
71
- * full exit transition (nice for a STACKING region), or `'instant'` removes them
72
- * synchronously with no exit — what a single, exactly-centered toast slot wants,
73
- * so the outgoing and incoming messages never cross-fade in the same spot.
74
- */
75
- collapse?: 'fade' | 'instant';
76
- /** Accent variant — adds a `${className}--${variant}` class (kerf ships no CSS; you style it). */
77
- variant?: ToastVariant;
78
- /** Class added on the next animation frame after mount, so a CSS **entrance** transition can run. */
79
- enterClass?: string;
80
- /**
81
- * Class added when dismissing, so CSS owns the **exit**. On dismiss the
82
- * `enterClass` (if any) is also REMOVED, so `exitClass` doesn't have to
83
- * out-specify it — and a symmetric single-class fade (entrance = add
84
- * `enterClass`, exit = remove it) works by setting only `enterClass` +
85
- * `exitDuration`. The node is removed `exitDuration` ms later.
86
- */
87
- exitClass?: string;
88
- /** ms to wait before removing the node on dismiss — applies when `exitClass` is set OR when it's > 0 (to let a removed `enterClass` transition out). Default `0`. */
89
- exitDuration?: number;
90
- }
91
- /** Handle returned by {@link toast}. */
92
- interface ToastHandle {
93
- /** The toast element — inspect it, or run your own entrance/exit transitions. */
94
- el: HTMLElement;
95
- /**
96
- * Dismiss it early. Default runs the `exitClass` transition (removed after
97
- * `exitDuration`); pass `{ instant: true }` to remove it **synchronously** with
98
- * no exit — for an action button that immediately shows a replacement toast in a
99
- * single centered slot (no cross-fade). Idempotent.
100
- */
101
- dismiss(options?: {
102
- instant?: boolean;
103
- }): void;
104
- }
105
- /**
106
- * Show a non-modal, auto-dismissing notification. Stacks in a shared body-level
107
- * region (or your `container`). Returns a {@link ToastHandle} (`{ el, dismiss }`)
108
- * so you can run entrance/exit transitions, wire an action button, or inspect the
109
- * node. `mode: 'replace'` collapses a rapid sequence to the latest; `variant`
110
- * adds an accent class; `enterClass`/`exitClass` let CSS own the animation.
111
- */
112
- declare function toast(content: ToastContent, options?: ToastOptions): ToastHandle;
113
-
114
- /** A user-initiated dismissal trigger. */
115
- type DismissTrigger = 'escape' | 'backdrop' | 'outside';
116
- /** Content for an overlay: static `SafeHtml`, or a render function `mount()` drives reactively. */
117
- type OverlayContent = SafeHtml | (() => MountResult);
118
- /** Options for {@link overlay}. */
119
- interface OverlayOptions {
120
- /** Where to append the overlay wrapper. Default `document.body`. */
121
- container?: Element;
122
- /** Class on the wrapper element (you style it — kerf ships no CSS). Default `'kerf-overlay'`. */
123
- className?: string;
124
- /**
125
- * Which user actions dismiss the overlay. Default `['escape', 'backdrop']`.
126
- * `'backdrop'` = a click on the wrapper itself (not its content); `'outside'`
127
- * = a click anywhere outside the wrapper (for anchored popovers). `false`
128
- * disables user dismissal (close it programmatically).
129
- */
130
- dismiss?: DismissTrigger | DismissTrigger[] | false;
131
- /**
132
- * Where focus lands on open: a selector, `true` (first focusable element, or
133
- * the wrapper if none), or `false` (leave focus alone). Default `true`.
134
- */
135
- initialFocus?: string | boolean;
136
- /**
137
- * Trap Tab / Shift+Tab within the overlay while open and mark it
138
- * `role="dialog"` / `aria-modal="true"`. Default `true`. Set `false` for a
139
- * non-modal popover.
140
- */
141
- trap?: boolean;
142
- /** ARIA role for the wrapper when `trap` is on. Default `'dialog'`. */
143
- role?: string;
144
- /** Called on any user-initiated dismissal (before `close()` runs). */
145
- onDismiss?: () => void;
146
- /** For `'outside'` dismissal: clicks on these elements do NOT count as outside (e.g. the trigger button). */
147
- outsideIgnore?: Element | readonly Element[];
148
- }
149
- /** Handle returned by {@link overlay}. Holds no framework state — it's a closure. */
150
- interface OverlayHandle {
151
- /** The wrapper element (mounted into, appended to `container`). */
152
- el: HTMLElement;
153
- /** Tear down: dispose the mount, remove listeners + the node, restore focus, resolve `result`. Idempotent. */
154
- close(result?: unknown): void;
155
- /** Resolves with the value passed to `close()` (or `undefined` on user dismissal). */
156
- result: Promise<unknown>;
157
- }
158
- /**
159
- * Open an overlay: append a wrapper to `container`, `mount()` `content` inside
160
- * it, wire the requested dismissals + (optionally) a focus trap, and return a
161
- * handle. See {@link OverlayOptions}.
162
- */
163
- declare function overlay(content: OverlayContent, options?: OverlayOptions): OverlayHandle;
164
42
  /**
165
43
  * Wiring slots passed to a {@link ConfirmOptions.render} — spread `ok` / `cancel`
166
44
  * onto your own clickable elements so `confirm()` still resolves them (they are
@@ -188,6 +66,8 @@ interface ConfirmOptions {
188
66
  cancelText?: string;
189
67
  /** Add a `kerf-confirm--danger` class to the wrapper for destructive actions. */
190
68
  danger?: boolean;
69
+ /** Host the dialog in the browser top layer (`<dialog>.showModal()`) where supported. See {@link OverlayOptions.native}. */
70
+ native?: boolean;
191
71
  /**
192
72
  * Bring your own markup (design-system dialogs): return the full dialog body,
193
73
  * spreading the provided `ok`/`cancel` wiring onto your buttons. Overrides the
@@ -229,6 +109,8 @@ interface PromptOptions {
229
109
  cancelText?: string;
230
110
  /** Block OK while this returns an error string; the message shows inline. */
231
111
  validate?: FieldValidator;
112
+ /** Host the dialog in the browser top layer (`<dialog>.showModal()`) where supported. See {@link OverlayOptions.native}. */
113
+ native?: boolean;
232
114
  /**
233
115
  * Bring your own markup: return the full dialog body, spreading the provided
234
116
  * `input` (the text field), `ok`/`cancel` (buttons), and optional `error` (the
@@ -303,6 +185,8 @@ interface FormOptions {
303
185
  okText?: string;
304
186
  /** Cancel button label. Default `'Cancel'`. */
305
187
  cancelText?: string;
188
+ /** Host the dialog in the browser top layer (`<dialog>.showModal()`) where supported. See {@link OverlayOptions.native}. */
189
+ native?: boolean;
306
190
  /**
307
191
  * Bring your own markup: return the full form body, laying out `slots.fields`
308
192
  * (each with `input`/`error` wiring to spread) and the `ok`/`cancel` buttons.
@@ -345,6 +229,8 @@ interface ChoiceOptions<R> {
345
229
  title?: string;
346
230
  /** The value resolved when **Enter** is pressed anywhere in the dialog (the default action). */
347
231
  defaultValue?: R;
232
+ /** Host the dialog in the browser top layer (`<dialog>.showModal()`) where supported. See {@link OverlayOptions.native}. */
233
+ native?: boolean;
348
234
  /** Bring your own markup: return the full body, spreading each `slots.actions[i]` onto your buttons. */
349
235
  render?: (slots: ChoiceRenderSlots) => OverlayContent;
350
236
  }
@@ -359,6 +245,168 @@ interface ChoiceOptions<R> {
359
245
  */
360
246
  declare function choice<R>(message: string, actions: ReadonlyArray<ChoiceAction<R>>, options?: ChoiceOptions<R>): Promise<R | null>;
361
247
 
248
+ /**
249
+ * `toast()` for `kerfjs/overlay` — a non-modal, auto-dismissing notification that
250
+ * stacks in a shared body-level region. Split out of `overlay.ts` (KF-513) since
251
+ * it's a distinct transient-UI concern from the modal dialogs; re-exported from
252
+ * `overlay.ts` so the public `kerfjs/overlay` surface is unchanged. Structural
253
+ * only — kerf ships no CSS; you style the region / toast / animations.
254
+ */
255
+
256
+ /** Content for a {@link toast}: text, `SafeHtml`, or a render function. */
257
+ type ToastContent = string | SafeHtml | (() => MountResult);
258
+ /** Accent variant for a {@link toast} — mapped to a `${className}--${variant}` class. */
259
+ type ToastVariant = 'info' | 'success' | 'warning';
260
+ /** Options for {@link toast}. */
261
+ interface ToastOptions {
262
+ /** Where toasts stack. Default: a lazily-created `<div class="kerf-toasts">` on `document.body`. */
263
+ container?: Element;
264
+ /** Class on the toast element. Default `'kerf-toast'`. */
265
+ className?: string;
266
+ /** Auto-dismiss after this many ms. `0` keeps it until dismissed by hand. Default `4000`. */
267
+ duration?: number;
268
+ /** ARIA role. Default `'status'`. */
269
+ role?: string;
270
+ /**
271
+ * `'stack'` (default) shows toasts stacked in the region; `'replace'` dismisses
272
+ * the region's current toast(s) first (collapse-to-latest for a rapid sequence).
273
+ */
274
+ mode?: 'stack' | 'replace';
275
+ /**
276
+ * How `mode: 'replace'` drops the prior toast(s): `'fade'` (default) runs their
277
+ * full exit transition (nice for a STACKING region), or `'instant'` removes them
278
+ * synchronously with no exit — what a single, exactly-centered toast slot wants,
279
+ * so the outgoing and incoming messages never cross-fade in the same spot.
280
+ */
281
+ collapse?: 'fade' | 'instant';
282
+ /** Accent variant — adds a `${className}--${variant}` class (kerf ships no CSS; you style it). */
283
+ variant?: ToastVariant;
284
+ /** Class added on the next animation frame after mount, so a CSS **entrance** transition can run. */
285
+ enterClass?: string;
286
+ /**
287
+ * Class added when dismissing, so CSS owns the **exit**. On dismiss the
288
+ * `enterClass` (if any) is also REMOVED, so `exitClass` doesn't have to
289
+ * out-specify it — and a symmetric single-class fade (entrance = add
290
+ * `enterClass`, exit = remove it) works by setting only `enterClass` +
291
+ * `exitDuration`. The node is removed `exitDuration` ms later.
292
+ */
293
+ exitClass?: string;
294
+ /** ms to wait before removing the node on dismiss — applies when `exitClass` is set OR when it's > 0 (to let a removed `enterClass` transition out). Default `0`. */
295
+ exitDuration?: number;
296
+ }
297
+ /** Handle returned by {@link toast}. */
298
+ interface ToastHandle {
299
+ /** The toast element — inspect it, or run your own entrance/exit transitions. */
300
+ el: HTMLElement;
301
+ /**
302
+ * Dismiss it early. Default runs the `exitClass` transition (removed after
303
+ * `exitDuration`); pass `{ instant: true }` to remove it **synchronously** with
304
+ * no exit — for an action button that immediately shows a replacement toast in a
305
+ * single centered slot (no cross-fade). Idempotent.
306
+ */
307
+ dismiss(options?: {
308
+ instant?: boolean;
309
+ }): void;
310
+ }
311
+ /**
312
+ * Show a non-modal, auto-dismissing notification. Stacks in a shared body-level
313
+ * region (or your `container`). Returns a {@link ToastHandle} (`{ el, dismiss }`)
314
+ * so you can run entrance/exit transitions, wire an action button, or inspect the
315
+ * node. `mode: 'replace'` collapses a rapid sequence to the latest; `variant`
316
+ * adds an accent class; `enterClass`/`exitClass` let CSS own the animation.
317
+ */
318
+ declare function toast(content: ToastContent, options?: ToastOptions): ToastHandle;
319
+
320
+ /**
321
+ * `kerfjs/overlay` — the modal / overlay + dismiss manager.
322
+ *
323
+ * Every real kerf app hand-rolls this: `toElement → body.appendChild → mount →
324
+ * wire dismissal → remove`, plus the fiddly parts (Escape, backdrop / outside
325
+ * click, focus trap, restoring focus on close). `window.confirm` is a no-op in
326
+ * Tauri WKWebViews, so a hand-built overlay is mandatory there. This subpath
327
+ * blesses the pattern as three functions over `mount()` — `overlay()`, and the
328
+ * `confirm()` / `toast()` conveniences built on it. No per-instance framework
329
+ * state: each call owns its DOM + listeners in a closure and returns a handle.
330
+ *
331
+ * import { overlay, confirm, toast } from 'kerfjs/overlay';
332
+ *
333
+ * const ok = await confirm('Delete this file?', { danger: true });
334
+ * toast('Saved');
335
+ * const dialog = overlay(<Settings />, { dismiss: ['escape', 'backdrop'] });
336
+ * // …later: dialog.close(); or await dialog.result;
337
+ *
338
+ * Structural only — kerf ships no CSS. The wrapper gets your `className`; style
339
+ * the backdrop / centering / animation yourself.
340
+ */
341
+
342
+ /** A user-initiated dismissal trigger. */
343
+ type DismissTrigger = 'escape' | 'backdrop' | 'outside';
344
+ /** Content for an overlay: static `SafeHtml`, or a render function `mount()` drives reactively. */
345
+ type OverlayContent = SafeHtml | (() => MountResult);
346
+ /** Options for {@link overlay}. */
347
+ interface OverlayOptions {
348
+ /** Where to append the overlay wrapper. Default `document.body`. */
349
+ container?: Element;
350
+ /** Class on the wrapper element (you style it — kerf ships no CSS). Default `'kerf-overlay'`. */
351
+ className?: string;
352
+ /**
353
+ * Which user actions dismiss the overlay. Default `['escape', 'backdrop']`.
354
+ * `'backdrop'` = a click on the wrapper itself (not its content); `'outside'`
355
+ * = a click anywhere outside the wrapper (for anchored popovers). `false`
356
+ * disables user dismissal (close it programmatically).
357
+ */
358
+ dismiss?: DismissTrigger | DismissTrigger[] | false;
359
+ /**
360
+ * Where focus lands on open: a selector, `true` (first focusable element, or
361
+ * the wrapper if none), or `false` (leave focus alone). Default `true`.
362
+ */
363
+ initialFocus?: string | boolean;
364
+ /**
365
+ * Trap Tab / Shift+Tab within the overlay while open and mark it
366
+ * `role="dialog"` / `aria-modal="true"`. Default `true`. Set `false` for a
367
+ * non-modal popover.
368
+ */
369
+ trap?: boolean;
370
+ /** ARIA role for the wrapper when `trap` is on. Default `'dialog'`. */
371
+ role?: string;
372
+ /** Called on any user-initiated dismissal (before `close()` runs). */
373
+ onDismiss?: () => void;
374
+ /** For `'outside'` dismissal: clicks on these elements do NOT count as outside (e.g. the trigger button). */
375
+ outsideIgnore?: Element | readonly Element[];
376
+ /**
377
+ * Opt into the browser **top layer** (`docs/19-native-overlay-backing.md`).
378
+ * When `true` and the engine supports it, a modal overlay (`trap: true`) is
379
+ * hosted in a `<dialog>` opened with `.showModal()` — real inerting of the rest
380
+ * of the document + guaranteed stacking above any `z-index` — and a non-modal
381
+ * one (`trap: false`) uses the Popover API (`[popover]` + `showPopover()`).
382
+ * Feature-detected; falls back to today's plain `<div>` where unsupported.
383
+ *
384
+ * The `render` slot + promise API are unchanged — kerf just hosts your markup
385
+ * in a `<dialog>` / `[popover]` instead of a `<div>`. Two caveats: native
386
+ * `<dialog>` / `[popover]` carry **UA default styles** (a `::backdrop`,
387
+ * centering, border, padding) that kerf does not reset — style the element (and
388
+ * its `::backdrop`) via `className`; and `container` is effectively a **no-op**
389
+ * for visual position, since the top layer ignores where the element lives in
390
+ * the DOM. Default `false`.
391
+ */
392
+ native?: boolean;
393
+ }
394
+ /** Handle returned by {@link overlay}. Holds no framework state — it's a closure. */
395
+ interface OverlayHandle {
396
+ /** The wrapper element (mounted into, appended to `container`). */
397
+ el: HTMLElement;
398
+ /** Tear down: dispose the mount, remove listeners + the node, restore focus, resolve `result`. Idempotent. */
399
+ close(result?: unknown): void;
400
+ /** Resolves with the value passed to `close()` (or `undefined` on user dismissal). */
401
+ result: Promise<unknown>;
402
+ }
403
+ /**
404
+ * Open an overlay: append a wrapper to `container`, `mount()` `content` inside
405
+ * it, wire the requested dismissals + (optionally) a focus trap, and return a
406
+ * handle. See {@link OverlayOptions}.
407
+ */
408
+ declare function overlay(content: OverlayContent, options?: OverlayOptions): OverlayHandle;
409
+
362
410
  /** Options for {@link popover}. */
363
411
  interface PopoverOptions {
364
412
  /** Where to append the popover wrapper. Default `document.body`. */
@@ -382,6 +430,14 @@ interface PopoverOptions {
382
430
  outsideIgnore?: Element | readonly Element[];
383
431
  /** Called on any user-initiated dismissal. */
384
432
  onDismiss?: () => void;
433
+ /**
434
+ * Host the popover in the browser top layer (the Popover API — `[popover]` +
435
+ * `showPopover()`) where supported, so it stacks above any `z-index` without a
436
+ * z-index war. Falls back to today's plain `<div>` where unsupported. kerf keeps
437
+ * owning positioning + its own dismiss wiring; the popover is `popover="manual"`.
438
+ * See {@link OverlayOptions.native}. Default `false`.
439
+ */
440
+ native?: boolean;
385
441
  }
386
442
  /**
387
443
  * Anchored, non-modal overlay: positions `content` relative to `anchor` (below by
@@ -407,6 +463,8 @@ interface TooltipOptions extends AnchorPositionOptions {
407
463
  hideDelay?: number;
408
464
  /** ARIA role on the wrapper. Default `'tooltip'`. */
409
465
  role?: string;
466
+ /** Host the tooltip in the browser top layer (the Popover API) where supported. See {@link OverlayOptions.native}. Default `false`. */
467
+ native?: boolean;
410
468
  }
411
469
  /**
412
470
  * A hover/focus-triggered, non-modal, auto-hiding tooltip anchored to `anchor`.