@joewinke/jatui 0.26.0 → 0.27.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.
- package/CLAUDE.md +106 -1
- package/dist/actions/keyboardScope.d.ts +58 -0
- package/dist/actions/keyboardScope.js +110 -0
- package/dist/actions/listNav.d.ts +48 -3
- package/dist/actions/listNav.js +267 -77
- package/dist/actions/longpress.d.ts +37 -0
- package/dist/actions/longpress.js +105 -0
- package/dist/actions/stickToBottom.d.ts +66 -0
- package/dist/actions/stickToBottom.js +155 -0
- package/dist/actions/swipe.d.ts +64 -0
- package/dist/actions/swipe.js +179 -0
- package/dist/components/BottomSheet.svelte +280 -0
- package/dist/components/BottomSheet.svelte.d.ts +27 -0
- package/dist/components/ConfirmDialog.svelte +21 -0
- package/dist/components/ConfirmDialog.svelte.d.ts +11 -1
- package/dist/components/ConfirmModal.svelte +15 -0
- package/dist/components/ConfirmModal.svelte.d.ts +5 -0
- package/dist/components/Drawer.svelte +18 -1
- package/dist/components/Drawer.svelte.d.ts +9 -1
- package/dist/components/InputDialog.svelte +21 -0
- package/dist/components/InputDialog.svelte.d.ts +11 -1
- package/dist/components/Modal.svelte +18 -1
- package/dist/components/Modal.svelte.d.ts +8 -0
- package/dist/components/basic/Empty.svelte +173 -9
- package/dist/components/basic/Empty.svelte.d.ts +50 -1
- package/dist/components/basic/ErrorState.svelte +128 -0
- package/dist/components/basic/ErrorState.svelte.d.ts +46 -0
- package/dist/components/basic/JumpToLatest.svelte +60 -0
- package/dist/components/basic/JumpToLatest.svelte.d.ts +9 -0
- package/dist/components/basic/PageHeader.svelte +216 -0
- package/dist/components/basic/PageHeader.svelte.d.ts +52 -0
- package/dist/components/basic/ProgressButton.svelte +221 -0
- package/dist/components/basic/ProgressButton.svelte.d.ts +63 -0
- package/dist/components/graph/GraphApiBridge.svelte +36 -0
- package/dist/components/graph/GraphApiBridge.svelte.d.ts +9 -0
- package/dist/components/graph/GraphCanvas.svelte +212 -27
- package/dist/components/graph/GraphCanvas.svelte.d.ts +113 -19
- package/dist/components/graph/api.d.ts +52 -0
- package/dist/components/graph/api.js +66 -0
- package/dist/components/graph/index.d.ts +2 -0
- package/dist/components/graph/index.js +1 -0
- package/dist/components/graph/theme.js +13 -0
- package/dist/components/messaging/MessageItem.svelte +5 -0
- package/dist/components/messaging/MessageItem.svelte.d.ts +7 -0
- package/dist/components/table/DataTable.svelte +452 -78
- package/dist/components/table/DataTable.svelte.d.ts +2 -2
- package/dist/components/table/ManageColumnsDropdown.svelte +466 -0
- package/dist/components/table/ManageColumnsDropdown.svelte.d.ts +26 -0
- package/dist/components/table/columnStorage.d.ts +29 -0
- package/dist/components/table/columnStorage.js +61 -0
- package/dist/components/table/groupRows.d.ts +72 -0
- package/dist/components/table/groupRows.js +118 -0
- package/dist/components/table/index.d.ts +7 -1
- package/dist/components/table/index.js +3 -0
- package/dist/components/table/types.d.ts +121 -20
- package/dist/index.d.ts +24 -2
- package/dist/index.js +36 -1
- package/dist/utils/ansiToHtml.d.ts +83 -0
- package/dist/utils/ansiToHtml.js +543 -0
- package/dist/utils/keyboardShortcutRegistry.d.ts +332 -11
- package/dist/utils/keyboardShortcutRegistry.js +1070 -76
- package/dist/utils/pollBackoff.d.ts +83 -0
- package/dist/utils/pollBackoff.js +151 -0
- package/dist/utils/snapFlick.d.ts +59 -0
- package/dist/utils/snapFlick.js +66 -0
- package/dist/video/advisors/qr.d.ts +1 -1
- package/dist/virtual/createRowWindow.d.ts +75 -0
- package/dist/virtual/createRowWindow.js +129 -0
- package/dist/virtual/index.d.ts +4 -0
- package/dist/virtual/index.js +5 -0
- package/dist/virtual/rowWindowCore.d.ts +202 -0
- package/dist/virtual/rowWindowCore.js +510 -0
- package/package.json +12 -2
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
BottomSheet.svelte — Reusable mobile bottom sheet primitive.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
<BottomSheet bind:open title="Pick something" onclose={handleClose}>
|
|
6
|
+
Content here
|
|
7
|
+
</BottomSheet>
|
|
8
|
+
|
|
9
|
+
Props:
|
|
10
|
+
open {boolean} — bindable, controls visibility
|
|
11
|
+
title {string} — optional header title
|
|
12
|
+
zIndex {number} — z-index override (default 70)
|
|
13
|
+
onclose {() => void} — called when the sheet is dismissed
|
|
14
|
+
fullHeight {boolean} — sheet takes full viewport height (slot controls scroll)
|
|
15
|
+
snap {'single'|'dual'} — 'dual' enables the inspector pattern: two snap
|
|
16
|
+
heights (peek → expanded) with flick-momentum release and
|
|
17
|
+
visualViewport keyboard avoidance (wayflow eval §6b).
|
|
18
|
+
Default 'single' preserves the legacy drag-to-dismiss sheet.
|
|
19
|
+
peekFraction / expandedFraction {number} — snap heights as a fraction of the
|
|
20
|
+
keyboard-adjusted visible viewport (dual mode only).
|
|
21
|
+
|
|
22
|
+
Drag physics:
|
|
23
|
+
single — pointer drag downward > 80px dismisses (legacy behavior).
|
|
24
|
+
dual — flick release: a fast flick (≥0.5 px/ms) snaps one step in the
|
|
25
|
+
flick's direction (down from peek dismisses); otherwise the sheet
|
|
26
|
+
settles on the snap nearest the post-drag height.
|
|
27
|
+
-->
|
|
28
|
+
<script lang="ts">
|
|
29
|
+
import { onMount, untrack } from 'svelte';
|
|
30
|
+
import type { ShortcutRegistry } from '../utils/keyboardShortcutRegistry';
|
|
31
|
+
import type { Snippet } from 'svelte';
|
|
32
|
+
import { fly, fade } from 'svelte/transition';
|
|
33
|
+
import { cubicOut } from 'svelte/easing';
|
|
34
|
+
import {
|
|
35
|
+
resolveSnapTarget,
|
|
36
|
+
keyboardInsetPx,
|
|
37
|
+
clampSnapHeight,
|
|
38
|
+
type SnapHeight
|
|
39
|
+
} from '../utils/snapFlick';
|
|
40
|
+
|
|
41
|
+
let {
|
|
42
|
+
open = $bindable(false),
|
|
43
|
+
title = '',
|
|
44
|
+
zIndex = 70,
|
|
45
|
+
fullHeight = false,
|
|
46
|
+
snap = 'single',
|
|
47
|
+
peekFraction = 0.4,
|
|
48
|
+
expandedFraction = 0.9,
|
|
49
|
+
onclose = () => {},
|
|
50
|
+
shortcutRegistry,
|
|
51
|
+
shortcutContext = 'bottom-sheet',
|
|
52
|
+
children
|
|
53
|
+
}: {
|
|
54
|
+
open?: boolean;
|
|
55
|
+
title?: string;
|
|
56
|
+
zIndex?: number;
|
|
57
|
+
/** When true, sheet takes full viewport height and content wrapper is overflow:hidden so the slot controls its own scroll zones. */
|
|
58
|
+
fullHeight?: boolean;
|
|
59
|
+
/** 'single' = legacy drag-to-dismiss only. 'dual' = peek/expanded snaps + flick release + keyboard avoidance. */
|
|
60
|
+
snap?: 'single' | 'dual';
|
|
61
|
+
/** Peek snap height as a fraction of the visible (keyboard-adjusted) viewport. */
|
|
62
|
+
peekFraction?: number;
|
|
63
|
+
/** Expanded snap height as a fraction of the visible (keyboard-adjusted) viewport. */
|
|
64
|
+
expandedFraction?: number;
|
|
65
|
+
onclose?: () => void;
|
|
66
|
+
/**
|
|
67
|
+
* Shortcut registry to push a modal context into while open — page
|
|
68
|
+
* shortcuts registered there stop firing behind it (jatui-voc3i.3).
|
|
69
|
+
*/
|
|
70
|
+
shortcutRegistry?: ShortcutRegistry;
|
|
71
|
+
/** Context name pushed while open. Default `'bottom-sheet'`. */
|
|
72
|
+
shortcutContext?: string;
|
|
73
|
+
children?: Snippet;
|
|
74
|
+
} = $props();
|
|
75
|
+
|
|
76
|
+
// While open, a modal shortcut context keeps the host page's registry
|
|
77
|
+
// shortcuts from firing behind it.
|
|
78
|
+
$effect(() => {
|
|
79
|
+
if (!open || !shortcutRegistry) return;
|
|
80
|
+
return shortcutRegistry.pushContext(shortcutContext, { modal: true, label: untrack(() => title) || undefined });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ── Keyboard avoidance (visualViewport) ───────────────────────────────────
|
|
84
|
+
// The soft keyboard shrinks the visual viewport; track it so the sheet
|
|
85
|
+
// clamps to what is actually visible and content clears the keyboard.
|
|
86
|
+
let visibleHeight = $state(0);
|
|
87
|
+
let keyboardInset = $state(0);
|
|
88
|
+
|
|
89
|
+
function readViewport() {
|
|
90
|
+
const vv = window.visualViewport;
|
|
91
|
+
if (!vv) {
|
|
92
|
+
visibleHeight = window.innerHeight;
|
|
93
|
+
keyboardInset = 0;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
visibleHeight = vv.height;
|
|
97
|
+
keyboardInset = keyboardInsetPx(
|
|
98
|
+
{ height: vv.height, offsetTop: vv.offsetTop },
|
|
99
|
+
window.innerHeight
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
onMount(() => {
|
|
104
|
+
readViewport();
|
|
105
|
+
const vv = window.visualViewport;
|
|
106
|
+
if (!vv) return;
|
|
107
|
+
const onChange = () => readViewport();
|
|
108
|
+
vv.addEventListener('resize', onChange);
|
|
109
|
+
vv.addEventListener('scroll', onChange);
|
|
110
|
+
return () => {
|
|
111
|
+
vv.removeEventListener('resize', onChange);
|
|
112
|
+
vv.removeEventListener('scroll', onChange);
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// ── Snap points (dual mode) ───────────────────────────────────────────────
|
|
117
|
+
// Derived from the keyboard-adjusted visible height, so the expanded snap can
|
|
118
|
+
// never extend behind the soft keyboard.
|
|
119
|
+
const isDual = $derived(snap === 'dual');
|
|
120
|
+
|
|
121
|
+
const snapPoints = $derived.by<SnapHeight[]>(() => {
|
|
122
|
+
const avail = Math.max(0, visibleHeight || (typeof window !== 'undefined' ? window.innerHeight : 0));
|
|
123
|
+
return [
|
|
124
|
+
{ name: 'peek', heightPx: clampSnapHeight(peekFraction * avail, avail) },
|
|
125
|
+
{ name: 'expanded', heightPx: clampSnapHeight(expandedFraction * avail, avail) }
|
|
126
|
+
];
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Resting snap index. Dual mode opens at peek (inspector pattern: peek first,
|
|
130
|
+
// expand on demand); single mode is unaffected.
|
|
131
|
+
// Resting snap index (dual mode). Opens at peek (index 0) — the inspector
|
|
132
|
+
// pattern: content summary first, expand on demand. Single mode never reads it.
|
|
133
|
+
let snapIndex = $state(0);
|
|
134
|
+
|
|
135
|
+
// ── Drag-to-dismiss / snap state ──────────────────────────────────────────
|
|
136
|
+
let dragStartY = 0;
|
|
137
|
+
let dragCurrentY = $state(0);
|
|
138
|
+
let isDragging = $state(false);
|
|
139
|
+
// Last two pointer samples for flick-velocity measurement (px/ms).
|
|
140
|
+
let prevSample: { y: number; t: number } | null = null;
|
|
141
|
+
let lastSample: { y: number; t: number } | null = null;
|
|
142
|
+
|
|
143
|
+
const DISMISS_THRESHOLD = 80;
|
|
144
|
+
const FLICK_VELOCITY_PX_MS = 0.5;
|
|
145
|
+
|
|
146
|
+
function dismiss() {
|
|
147
|
+
open = false;
|
|
148
|
+
onclose();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function onPointerDown(e: PointerEvent) {
|
|
152
|
+
dragStartY = e.clientY;
|
|
153
|
+
dragCurrentY = 0;
|
|
154
|
+
isDragging = true;
|
|
155
|
+
prevSample = null;
|
|
156
|
+
lastSample = { y: e.clientY, t: performance.now() };
|
|
157
|
+
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function onPointerMove(e: PointerEvent) {
|
|
161
|
+
if (!isDragging) return;
|
|
162
|
+
const delta = e.clientY - dragStartY;
|
|
163
|
+
// Dual mode allows upward drag (grow toward expanded); single mode keeps
|
|
164
|
+
// the legacy downward-only gesture.
|
|
165
|
+
dragCurrentY = isDual ? delta : Math.max(0, delta);
|
|
166
|
+
// Velocity sampling for flick release.
|
|
167
|
+
const t = performance.now();
|
|
168
|
+
if (lastSample) prevSample = lastSample;
|
|
169
|
+
lastSample = { y: e.clientY, t: t };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function onPointerUp(_e: PointerEvent) {
|
|
173
|
+
if (!isDragging) return;
|
|
174
|
+
isDragging = false;
|
|
175
|
+
|
|
176
|
+
if (isDual) {
|
|
177
|
+
const velocity =
|
|
178
|
+
prevSample && lastSample && lastSample.t > prevSample.t
|
|
179
|
+
? (lastSample.y - prevSample.y) / (lastSample.t - prevSample.t)
|
|
180
|
+
: 0;
|
|
181
|
+
const result = resolveSnapTarget({
|
|
182
|
+
dragDeltaY: dragCurrentY,
|
|
183
|
+
velocity,
|
|
184
|
+
snapPoints,
|
|
185
|
+
currentIndex: snapIndex
|
|
186
|
+
});
|
|
187
|
+
if (result.dismiss) {
|
|
188
|
+
dismiss();
|
|
189
|
+
} else {
|
|
190
|
+
snapIndex = result.index;
|
|
191
|
+
}
|
|
192
|
+
} else if (dragCurrentY >= DISMISS_THRESHOLD) {
|
|
193
|
+
dismiss();
|
|
194
|
+
}
|
|
195
|
+
dragCurrentY = 0;
|
|
196
|
+
prevSample = null;
|
|
197
|
+
lastSample = null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Subtle resistance feedback during drag
|
|
201
|
+
const dragOpacity = $derived(isDragging ? Math.max(0.4, 1 - Math.abs(dragCurrentY) / 300) : 1);
|
|
202
|
+
|
|
203
|
+
// Height applied to the panel: dual mode snaps between peek/expanded; single
|
|
204
|
+
// mode keeps the legacy max-height/fullHeight styling.
|
|
205
|
+
const panelHeightStyle = $derived.by(() => {
|
|
206
|
+
if (isDual) {
|
|
207
|
+
const i = Math.min(snapIndex, snapPoints.length - 1);
|
|
208
|
+
return `height: ${snapPoints[i].heightPx}px;`;
|
|
209
|
+
}
|
|
210
|
+
return fullHeight ? 'height: 100dvh;' : 'max-height: 80dvh;';
|
|
211
|
+
});
|
|
212
|
+
</script>
|
|
213
|
+
|
|
214
|
+
{#if open}
|
|
215
|
+
<!-- Backdrop -->
|
|
216
|
+
<div
|
|
217
|
+
class="fixed inset-0 bg-black/50"
|
|
218
|
+
style="z-index: {zIndex}"
|
|
219
|
+
role="button"
|
|
220
|
+
tabindex="-1"
|
|
221
|
+
aria-label="Close"
|
|
222
|
+
transition:fade={{ duration: 150 }}
|
|
223
|
+
onclick={dismiss}
|
|
224
|
+
onkeydown={(e) => { if (e.key === 'Escape') dismiss(); }}
|
|
225
|
+
></div>
|
|
226
|
+
|
|
227
|
+
<!-- Sheet Panel -->
|
|
228
|
+
<div
|
|
229
|
+
class="fixed bottom-0 inset-x-0 flex flex-col bg-base-100 rounded-t-2xl border-t border-base-300 shadow-2xl"
|
|
230
|
+
style="z-index: {zIndex + 1}; {panelHeightStyle} transform: translateY({dragCurrentY}px); opacity: {dragOpacity}; transition: {isDragging ? 'none' : 'transform 0.15s ease, opacity 0.15s ease, height 0.2s ease'};"
|
|
231
|
+
role="dialog"
|
|
232
|
+
tabindex="-1"
|
|
233
|
+
aria-modal="true"
|
|
234
|
+
aria-label={title || 'Bottom sheet'}
|
|
235
|
+
transition:fly={{ y: 300, duration: 200, easing: cubicOut }}
|
|
236
|
+
onclick={(e) => e.stopPropagation()}
|
|
237
|
+
onkeydown={(e) => { if (e.key === 'Escape') dismiss(); }}
|
|
238
|
+
>
|
|
239
|
+
<!-- Drag handle (pointer capture target) -->
|
|
240
|
+
<div
|
|
241
|
+
class="flex-shrink-0 flex justify-center pt-3 pb-1 cursor-grab active:cursor-grabbing touch-none select-none"
|
|
242
|
+
role="button"
|
|
243
|
+
tabindex="-1"
|
|
244
|
+
aria-label="Drag to resize"
|
|
245
|
+
onpointerdown={onPointerDown}
|
|
246
|
+
onpointermove={onPointerMove}
|
|
247
|
+
onpointerup={onPointerUp}
|
|
248
|
+
onpointercancel={onPointerUp}
|
|
249
|
+
>
|
|
250
|
+
<div class="w-10 h-1.5 bg-base-300 rounded-full"></div>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<!-- Optional header -->
|
|
254
|
+
{#if title}
|
|
255
|
+
<div class="flex-shrink-0 flex items-center justify-between px-4 py-2 border-b border-base-200">
|
|
256
|
+
<span class="text-sm font-semibold">{title}</span>
|
|
257
|
+
<button
|
|
258
|
+
type="button"
|
|
259
|
+
class="btn btn-sm btn-ghost btn-circle"
|
|
260
|
+
aria-label="Close"
|
|
261
|
+
onclick={dismiss}
|
|
262
|
+
>✕</button>
|
|
263
|
+
</div>
|
|
264
|
+
{/if}
|
|
265
|
+
|
|
266
|
+
<!-- Scrollable content (or overflow-hidden when fullHeight so the slot controls scroll zones).
|
|
267
|
+
Keyboard avoidance: the content's bottom padding clears the occluded strip so
|
|
268
|
+
inputs stay above the soft keyboard (visualViewport-driven). -->
|
|
269
|
+
<div
|
|
270
|
+
class="flex-1 overscroll-contain {fullHeight && !isDual ? 'overflow-hidden' : isDual ? 'overflow-y-auto' : 'overflow-y-auto'}"
|
|
271
|
+
style={!fullHeight
|
|
272
|
+
? 'padding-bottom: max(1rem, env(safe-area-inset-bottom));'
|
|
273
|
+
: keyboardInset > 0
|
|
274
|
+
? `padding-bottom: ${keyboardInset}px;`
|
|
275
|
+
: ''}
|
|
276
|
+
>
|
|
277
|
+
{@render children?.()}
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
{/if}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ShortcutRegistry } from '../utils/keyboardShortcutRegistry';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
open?: boolean;
|
|
5
|
+
title?: string;
|
|
6
|
+
zIndex?: number;
|
|
7
|
+
/** When true, sheet takes full viewport height and content wrapper is overflow:hidden so the slot controls its own scroll zones. */
|
|
8
|
+
fullHeight?: boolean;
|
|
9
|
+
/** 'single' = legacy drag-to-dismiss only. 'dual' = peek/expanded snaps + flick release + keyboard avoidance. */
|
|
10
|
+
snap?: 'single' | 'dual';
|
|
11
|
+
/** Peek snap height as a fraction of the visible (keyboard-adjusted) viewport. */
|
|
12
|
+
peekFraction?: number;
|
|
13
|
+
/** Expanded snap height as a fraction of the visible (keyboard-adjusted) viewport. */
|
|
14
|
+
expandedFraction?: number;
|
|
15
|
+
onclose?: () => void;
|
|
16
|
+
/**
|
|
17
|
+
* Shortcut registry to push a modal context into while open — page
|
|
18
|
+
* shortcuts registered there stop firing behind it (jatui-voc3i.3).
|
|
19
|
+
*/
|
|
20
|
+
shortcutRegistry?: ShortcutRegistry;
|
|
21
|
+
/** Context name pushed while open. Default `'bottom-sheet'`. */
|
|
22
|
+
shortcutContext?: string;
|
|
23
|
+
children?: Snippet;
|
|
24
|
+
};
|
|
25
|
+
declare const BottomSheet: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
26
|
+
type BottomSheet = ReturnType<typeof BottomSheet>;
|
|
27
|
+
export default BottomSheet;
|
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
import { cubicOut } from 'svelte/easing';
|
|
4
4
|
import { tick } from 'svelte';
|
|
5
5
|
import { getPending, confirmDialog, cancelDialog } from '../stores/confirmDialog';
|
|
6
|
+
import type { ShortcutRegistry } from '../utils/keyboardShortcutRegistry';
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
shortcutRegistry,
|
|
10
|
+
shortcutContext = 'confirm-dialog'
|
|
11
|
+
}: {
|
|
12
|
+
/**
|
|
13
|
+
* Shortcut registry to push a modal context into while the dialog is up
|
|
14
|
+
* — page shortcuts registered there stop firing behind it (jatui-voc3i.3).
|
|
15
|
+
*/
|
|
16
|
+
shortcutRegistry?: ShortcutRegistry;
|
|
17
|
+
/** Context name pushed while the dialog is up. Default `'confirm-dialog'`. */
|
|
18
|
+
shortcutContext?: string;
|
|
19
|
+
} = $props();
|
|
6
20
|
|
|
7
21
|
const pending = $derived(getPending());
|
|
8
22
|
const opts = $derived(pending?.options ?? null);
|
|
@@ -58,6 +72,13 @@
|
|
|
58
72
|
if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); confirmDialog(); }
|
|
59
73
|
}
|
|
60
74
|
|
|
75
|
+
// While the dialog is up, a modal shortcut context keeps the host page's
|
|
76
|
+
// registry shortcuts from firing behind it.
|
|
77
|
+
$effect(() => {
|
|
78
|
+
if (!visible || !shortcutRegistry) return;
|
|
79
|
+
return shortcutRegistry.pushContext(shortcutContext, { modal: true });
|
|
80
|
+
});
|
|
81
|
+
|
|
61
82
|
$effect(() => {
|
|
62
83
|
window.addEventListener('keydown', handleKeydown, true);
|
|
63
84
|
return () => window.removeEventListener('keydown', handleKeydown, true);
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ShortcutRegistry } from '../utils/keyboardShortcutRegistry';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
/**
|
|
4
|
+
* Shortcut registry to push a modal context into while the dialog is up
|
|
5
|
+
* — page shortcuts registered there stop firing behind it (jatui-voc3i.3).
|
|
6
|
+
*/
|
|
7
|
+
shortcutRegistry?: ShortcutRegistry;
|
|
8
|
+
/** Context name pushed while the dialog is up. Default `'confirm-dialog'`. */
|
|
9
|
+
shortcutContext?: string;
|
|
10
|
+
};
|
|
11
|
+
declare const ConfirmDialog: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
2
12
|
type ConfirmDialog = ReturnType<typeof ConfirmDialog>;
|
|
3
13
|
export default ConfirmDialog;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { fly, fade } from 'svelte/transition';
|
|
3
3
|
import { cubicOut } from 'svelte/easing';
|
|
4
|
+
import { untrack } from 'svelte';
|
|
5
|
+
import type { ShortcutRegistry } from '../utils/keyboardShortcutRegistry';
|
|
4
6
|
|
|
5
7
|
let {
|
|
6
8
|
open = $bindable(true),
|
|
@@ -13,6 +15,8 @@
|
|
|
13
15
|
iconClass = "text-warning",
|
|
14
16
|
onconfirm,
|
|
15
17
|
oncancel,
|
|
18
|
+
shortcutRegistry,
|
|
19
|
+
shortcutContext = 'confirm-modal',
|
|
16
20
|
} = $props<{
|
|
17
21
|
open?: boolean
|
|
18
22
|
title?: string
|
|
@@ -24,8 +28,19 @@
|
|
|
24
28
|
iconClass?: string
|
|
25
29
|
onconfirm?: () => void
|
|
26
30
|
oncancel?: () => void
|
|
31
|
+
/** Shortcut registry to push a modal context into while open (jatui-voc3i.3). */
|
|
32
|
+
shortcutRegistry?: ShortcutRegistry
|
|
33
|
+
/** Context name pushed while open. Default `'confirm-modal'`. */
|
|
34
|
+
shortcutContext?: string
|
|
27
35
|
}>()
|
|
28
36
|
|
|
37
|
+
// While open, a modal shortcut context keeps the host page's registry
|
|
38
|
+
// shortcuts from firing behind it.
|
|
39
|
+
$effect(() => {
|
|
40
|
+
if (!open || !shortcutRegistry) return
|
|
41
|
+
return shortcutRegistry.pushContext(shortcutContext, { modal: true, label: untrack(() => title) || undefined })
|
|
42
|
+
})
|
|
43
|
+
|
|
29
44
|
// Respect prefers-reduced-motion: skip the entrance/exit transitions.
|
|
30
45
|
const reducedMotion =
|
|
31
46
|
typeof window !== 'undefined' &&
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ShortcutRegistry } from '../utils/keyboardShortcutRegistry';
|
|
1
2
|
type $$ComponentProps = {
|
|
2
3
|
open?: boolean;
|
|
3
4
|
title?: string;
|
|
@@ -9,6 +10,10 @@ type $$ComponentProps = {
|
|
|
9
10
|
iconClass?: string;
|
|
10
11
|
onconfirm?: () => void;
|
|
11
12
|
oncancel?: () => void;
|
|
13
|
+
/** Shortcut registry to push a modal context into while open (jatui-voc3i.3). */
|
|
14
|
+
shortcutRegistry?: ShortcutRegistry;
|
|
15
|
+
/** Context name pushed while open. Default `'confirm-modal'`. */
|
|
16
|
+
shortcutContext?: string;
|
|
12
17
|
};
|
|
13
18
|
declare const ConfirmModal: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
14
19
|
type ConfirmModal = ReturnType<typeof ConfirmModal>;
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* Sliding panel overlay with backdrop, escape key, and header actions.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type
|
|
8
|
+
import { untrack, type Snippet } from 'svelte';
|
|
9
|
+
import type { ShortcutRegistry } from '../utils/keyboardShortcutRegistry';
|
|
9
10
|
|
|
10
11
|
interface Props {
|
|
11
12
|
open?: boolean;
|
|
@@ -26,6 +27,13 @@
|
|
|
26
27
|
* is open.
|
|
27
28
|
*/
|
|
28
29
|
trapKeys?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Shortcut registry to push a modal context into while open — page
|
|
32
|
+
* shortcuts registered there stop firing behind it (jatui-voc3i.3).
|
|
33
|
+
*/
|
|
34
|
+
shortcutRegistry?: ShortcutRegistry;
|
|
35
|
+
/** Context name pushed while open. Default `'drawer'`. */
|
|
36
|
+
shortcutContext?: string;
|
|
29
37
|
onclose?: () => void;
|
|
30
38
|
class?: string;
|
|
31
39
|
headerActions?: Snippet;
|
|
@@ -40,6 +48,8 @@
|
|
|
40
48
|
showCloseButton = true,
|
|
41
49
|
contentClass = '',
|
|
42
50
|
trapKeys = true,
|
|
51
|
+
shortcutRegistry,
|
|
52
|
+
shortcutContext = 'drawer',
|
|
43
53
|
onclose,
|
|
44
54
|
class: className = '',
|
|
45
55
|
headerActions,
|
|
@@ -56,6 +66,13 @@
|
|
|
56
66
|
full: 'w-full'
|
|
57
67
|
};
|
|
58
68
|
|
|
69
|
+
// While open, a modal shortcut context keeps the host page's registry
|
|
70
|
+
// shortcuts from firing behind it.
|
|
71
|
+
$effect(() => {
|
|
72
|
+
if (!open || !shortcutRegistry) return;
|
|
73
|
+
return shortcutRegistry.pushContext(shortcutContext, { modal: true, label: untrack(() => title) || undefined });
|
|
74
|
+
});
|
|
75
|
+
|
|
59
76
|
function handleClose() {
|
|
60
77
|
open = false;
|
|
61
78
|
onclose?.();
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Sliding panel overlay with backdrop, escape key, and header actions.
|
|
5
5
|
*/
|
|
6
|
-
import type
|
|
6
|
+
import { type Snippet } from 'svelte';
|
|
7
|
+
import type { ShortcutRegistry } from '../utils/keyboardShortcutRegistry';
|
|
7
8
|
interface Props {
|
|
8
9
|
open?: boolean;
|
|
9
10
|
title?: string;
|
|
@@ -23,6 +24,13 @@ interface Props {
|
|
|
23
24
|
* is open.
|
|
24
25
|
*/
|
|
25
26
|
trapKeys?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Shortcut registry to push a modal context into while open — page
|
|
29
|
+
* shortcuts registered there stop firing behind it (jatui-voc3i.3).
|
|
30
|
+
*/
|
|
31
|
+
shortcutRegistry?: ShortcutRegistry;
|
|
32
|
+
/** Context name pushed while open. Default `'drawer'`. */
|
|
33
|
+
shortcutContext?: string;
|
|
26
34
|
onclose?: () => void;
|
|
27
35
|
class?: string;
|
|
28
36
|
headerActions?: Snippet;
|
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
import { cubicOut } from 'svelte/easing';
|
|
4
4
|
import { tick } from 'svelte';
|
|
5
5
|
import { getPendingInput, submitInput, cancelInput } from '../stores/inputDialog';
|
|
6
|
+
import type { ShortcutRegistry } from '../utils/keyboardShortcutRegistry';
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
shortcutRegistry,
|
|
10
|
+
shortcutContext = 'input-dialog'
|
|
11
|
+
}: {
|
|
12
|
+
/**
|
|
13
|
+
* Shortcut registry to push a modal context into while the dialog is up
|
|
14
|
+
* — page shortcuts registered there stop firing behind it (jatui-voc3i.3).
|
|
15
|
+
*/
|
|
16
|
+
shortcutRegistry?: ShortcutRegistry;
|
|
17
|
+
/** Context name pushed while the dialog is up. Default `'input-dialog'`. */
|
|
18
|
+
shortcutContext?: string;
|
|
19
|
+
} = $props();
|
|
6
20
|
|
|
7
21
|
const pending = $derived(getPendingInput());
|
|
8
22
|
const opts = $derived(pending?.options ?? null);
|
|
@@ -38,6 +52,13 @@
|
|
|
38
52
|
if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); handleSubmit(); }
|
|
39
53
|
}
|
|
40
54
|
|
|
55
|
+
// While the dialog is up, a modal shortcut context keeps the host page's
|
|
56
|
+
// registry shortcuts from firing behind it.
|
|
57
|
+
$effect(() => {
|
|
58
|
+
if (!visible || !shortcutRegistry) return;
|
|
59
|
+
return shortcutRegistry.pushContext(shortcutContext, { modal: true });
|
|
60
|
+
});
|
|
61
|
+
|
|
41
62
|
$effect(() => {
|
|
42
63
|
window.addEventListener('keydown', handleKeydown, true);
|
|
43
64
|
return () => window.removeEventListener('keydown', handleKeydown, true);
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ShortcutRegistry } from '../utils/keyboardShortcutRegistry';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
/**
|
|
4
|
+
* Shortcut registry to push a modal context into while the dialog is up
|
|
5
|
+
* — page shortcuts registered there stop firing behind it (jatui-voc3i.3).
|
|
6
|
+
*/
|
|
7
|
+
shortcutRegistry?: ShortcutRegistry;
|
|
8
|
+
/** Context name pushed while the dialog is up. Default `'input-dialog'`. */
|
|
9
|
+
shortcutContext?: string;
|
|
10
|
+
};
|
|
11
|
+
declare const InputDialog: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
2
12
|
type InputDialog = ReturnType<typeof InputDialog>;
|
|
3
13
|
export default InputDialog;
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* Converted from Svelte 4 to Svelte 5 runes.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { onMount } from 'svelte';
|
|
9
|
+
import { onMount, untrack } from 'svelte';
|
|
10
|
+
import type { ShortcutRegistry } from '../utils/keyboardShortcutRegistry';
|
|
10
11
|
import { fly, fade } from 'svelte/transition';
|
|
11
12
|
import { cubicOut } from 'svelte/easing';
|
|
12
13
|
|
|
@@ -42,6 +43,13 @@
|
|
|
42
43
|
* is open.
|
|
43
44
|
*/
|
|
44
45
|
trapKeys?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Shortcut registry to push a modal context into while open — page
|
|
48
|
+
* shortcuts registered there stop firing behind it (jatui-voc3i.3).
|
|
49
|
+
*/
|
|
50
|
+
shortcutRegistry?: ShortcutRegistry;
|
|
51
|
+
/** Context name pushed while open. Default `'modal'`. */
|
|
52
|
+
shortcutContext?: string;
|
|
45
53
|
onclose?: () => void;
|
|
46
54
|
onopen?: () => void;
|
|
47
55
|
header?: import('svelte').Snippet;
|
|
@@ -64,6 +72,8 @@
|
|
|
64
72
|
dismissible = true,
|
|
65
73
|
zIndex = 1000,
|
|
66
74
|
trapKeys = true,
|
|
75
|
+
shortcutRegistry,
|
|
76
|
+
shortcutContext = 'modal',
|
|
67
77
|
onclose,
|
|
68
78
|
onopen,
|
|
69
79
|
header,
|
|
@@ -110,6 +120,13 @@
|
|
|
110
120
|
}
|
|
111
121
|
});
|
|
112
122
|
|
|
123
|
+
// While open, a modal shortcut context keeps the host page's registry
|
|
124
|
+
// shortcuts from firing behind it.
|
|
125
|
+
$effect(() => {
|
|
126
|
+
if (!open || !shortcutRegistry) return;
|
|
127
|
+
return shortcutRegistry.pushContext(shortcutContext, { modal: true, label: untrack(() => title) || undefined });
|
|
128
|
+
});
|
|
129
|
+
|
|
113
130
|
function close() {
|
|
114
131
|
if (dismissible) {
|
|
115
132
|
open = false;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ShortcutRegistry } from '../utils/keyboardShortcutRegistry';
|
|
1
2
|
interface Props {
|
|
2
3
|
open?: boolean;
|
|
3
4
|
title?: string;
|
|
@@ -30,6 +31,13 @@ interface Props {
|
|
|
30
31
|
* is open.
|
|
31
32
|
*/
|
|
32
33
|
trapKeys?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Shortcut registry to push a modal context into while open — page
|
|
36
|
+
* shortcuts registered there stop firing behind it (jatui-voc3i.3).
|
|
37
|
+
*/
|
|
38
|
+
shortcutRegistry?: ShortcutRegistry;
|
|
39
|
+
/** Context name pushed while open. Default `'modal'`. */
|
|
40
|
+
shortcutContext?: string;
|
|
33
41
|
onclose?: () => void;
|
|
34
42
|
onopen?: () => void;
|
|
35
43
|
header?: import('svelte').Snippet;
|