@joewinke/jatui 0.34.2 → 0.36.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 +9 -3
- package/dist/actions/reveal.d.ts +72 -11
- package/dist/actions/reveal.js +103 -14
- package/dist/components/AudioRecorder.svelte +23 -1
- package/dist/components/AudioRecorder.svelte.d.ts +13 -1
- package/dist/components/BulkActionBar.svelte +17 -2
- package/dist/components/BulkActionBar.svelte.d.ts +5 -0
- package/dist/components/Drawer.svelte +20 -1
- package/dist/components/Drawer.svelte.d.ts +11 -0
- package/dist/components/FloatingActionBar.svelte +26 -11
- package/dist/components/FloatingActionBar.svelte.d.ts +5 -0
- package/dist/components/KeyboardShortcutsOverlay.svelte +180 -7
- package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +21 -0
- package/dist/components/ListenControl.svelte +1064 -0
- package/dist/components/ListenControl.svelte.d.ts +87 -0
- package/dist/components/ThumbnailGrid.svelte +34 -7
- package/dist/components/ThumbnailGrid.svelte.d.ts +17 -1
- package/dist/components/table/DataTable.svelte.d.ts +2 -2
- package/dist/index.d.ts +9 -3
- package/dist/index.js +9 -2
- package/dist/listen/keys.d.ts +36 -0
- package/dist/listen/keys.js +68 -0
- package/dist/listen/mediaSession.d.ts +90 -0
- package/dist/listen/mediaSession.js +108 -0
- package/dist/listen/prefs.d.ts +49 -0
- package/dist/listen/prefs.js +97 -0
- package/dist/listen/segments.d.ts +101 -0
- package/dist/listen/segments.js +224 -0
- package/dist/listen/types.d.ts +81 -0
- package/dist/listen/types.js +7 -0
- package/dist/motion/index.d.ts +1 -1
- package/dist/motion/index.js +1 -1
- package/dist/utils/audioRecorder.d.ts +6 -1
- package/dist/utils/audioRecorder.js +5 -0
- package/dist/utils/audioRecorderCore.d.ts +34 -0
- package/dist/utils/audioRecorderCore.js +43 -6
- package/dist/utils/keyboardShortcutHub.d.ts +73 -0
- package/dist/utils/keyboardShortcutHub.js +89 -0
- package/dist/utils/mentionParser.d.ts +44 -2
- package/dist/utils/mentionParser.js +84 -4
- package/package.json +2 -1
|
@@ -11,6 +11,11 @@ export interface ActionButton {
|
|
|
11
11
|
key?: string;
|
|
12
12
|
/** Called when clicked */
|
|
13
13
|
onclick: () => void;
|
|
14
|
+
/** Dimmed, non-interactive button (mardukos). */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
/** When set, the button is disabled and shows a spinner + this text
|
|
17
|
+
* instead of the label (mardukos per-action loading). */
|
|
18
|
+
loadingText?: string | null;
|
|
14
19
|
}
|
|
15
20
|
export interface DropdownItem {
|
|
16
21
|
/** Display label */
|
|
@@ -39,9 +39,32 @@
|
|
|
39
39
|
* <KeyboardShortcutsOverlay {registry}>
|
|
40
40
|
* {#snippet extra()}<div>…custom footer…</div>{/snippet}
|
|
41
41
|
* </KeyboardShortcutsOverlay>
|
|
42
|
+
*
|
|
43
|
+
* ── Host / contributor mode ───────────────────────────────────────────
|
|
44
|
+
*
|
|
45
|
+
* An app shell can mount ONE global host overlay; every other instance
|
|
46
|
+
* on the page then contributes its sections to the host instead of
|
|
47
|
+
* self-rendering, so exactly one `?` overlay exists no matter how many
|
|
48
|
+
* surfaces mount the component:
|
|
49
|
+
*
|
|
50
|
+
* // App shell (layout.svelte):
|
|
51
|
+
* <KeyboardShortcutsOverlay mode="host" />
|
|
52
|
+
*
|
|
53
|
+
* // A page with its own shortcut set:
|
|
54
|
+
* <KeyboardShortcutsOverlay {sections} sectionTitle="Inbox" />
|
|
55
|
+
*
|
|
56
|
+
* `mode` defaults to `'auto'`: the instance contributes to the host when
|
|
57
|
+
* one is mounted, and self-renders when there isn't (the portal /
|
|
58
|
+
* contract-page fallback). Force self-rendering with
|
|
59
|
+
* `mode="standalone"`.
|
|
42
60
|
*/
|
|
43
61
|
import type { Snippet } from 'svelte';
|
|
44
62
|
import type { ShortcutRegistry } from '../utils/keyboardShortcutRegistry';
|
|
63
|
+
import {
|
|
64
|
+
keyboardShortcutHub,
|
|
65
|
+
type ShortcutContribution,
|
|
66
|
+
type ShortcutSectionLike
|
|
67
|
+
} from '../utils/keyboardShortcutHub';
|
|
45
68
|
|
|
46
69
|
export interface KeyboardShortcut {
|
|
47
70
|
key: string;
|
|
@@ -67,7 +90,27 @@
|
|
|
67
90
|
extraSections,
|
|
68
91
|
extra,
|
|
69
92
|
title = 'Keyboard Shortcuts',
|
|
70
|
-
open = $bindable(false)
|
|
93
|
+
open = $bindable(false),
|
|
94
|
+
/**
|
|
95
|
+
* Host / contributor mode.
|
|
96
|
+
* - `'host'`: this instance IS the global overlay — it registers
|
|
97
|
+
* with the hub and aggregates contributions from other instances.
|
|
98
|
+
* - `'auto'` (default): contribute to the host when one exists,
|
|
99
|
+
* otherwise self-render (the portal / contract-page fallback).
|
|
100
|
+
* - `'standalone'`: never defer, always self-render.
|
|
101
|
+
*/
|
|
102
|
+
mode = 'auto',
|
|
103
|
+
/**
|
|
104
|
+
* Label under which this instance's sections appear in the host
|
|
105
|
+
* panel when contributing. Ignored when self-rendering.
|
|
106
|
+
*/
|
|
107
|
+
sectionTitle = 'Page shortcuts',
|
|
108
|
+
/**
|
|
109
|
+
* Show the closed-state "press ?" hint pill (bottom-right).
|
|
110
|
+
* `showHint={false}` opts out — e.g. on contributor instances that
|
|
111
|
+
* defer to the host's own hint.
|
|
112
|
+
*/
|
|
113
|
+
showHint = true
|
|
71
114
|
}: {
|
|
72
115
|
shortcuts?: KeyboardShortcut[];
|
|
73
116
|
/**
|
|
@@ -97,6 +140,9 @@
|
|
|
97
140
|
extra?: Snippet;
|
|
98
141
|
title?: string;
|
|
99
142
|
open?: boolean;
|
|
143
|
+
mode?: 'auto' | 'host' | 'standalone';
|
|
144
|
+
sectionTitle?: string;
|
|
145
|
+
showHint?: boolean;
|
|
100
146
|
} = $props();
|
|
101
147
|
|
|
102
148
|
let isOpen = $state(open);
|
|
@@ -108,19 +154,103 @@
|
|
|
108
154
|
open = isOpen;
|
|
109
155
|
});
|
|
110
156
|
|
|
111
|
-
// Registry sections
|
|
112
|
-
//
|
|
157
|
+
// ── Registry → sections ─────────────────────────────────────────────
|
|
158
|
+
// Registry sections are kept live via `registry.subscribe()` so the host
|
|
159
|
+
// panel always reflects the current context stack. The overlay pushes its
|
|
160
|
+
// own modal context while open, which hides the page contexts underneath —
|
|
161
|
+
// that push is what suppresses page shortcuts behind the modal.
|
|
113
162
|
let registrySections = $state<ShortcutSection[] | null>(null);
|
|
114
163
|
$effect(() => {
|
|
115
|
-
if (!
|
|
164
|
+
if (!registry) return;
|
|
116
165
|
registrySections = registry.toSections();
|
|
166
|
+
return registry.subscribe(() => {
|
|
167
|
+
registrySections = registry.toSections();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
$effect(() => {
|
|
171
|
+
if (!isOpen || !registry) return;
|
|
117
172
|
return registry.pushContext('keyboard-shortcuts-overlay', { modal: true });
|
|
118
173
|
});
|
|
119
174
|
|
|
120
|
-
|
|
175
|
+
// ── Hub wiring ──────────────────────────────────────────────────────
|
|
176
|
+
let hubHasHost = $state(keyboardShortcutHub.hasHost);
|
|
177
|
+
let hubContributions = $state<ShortcutContribution[]>(keyboardShortcutHub.getContributions());
|
|
178
|
+
$effect(() => {
|
|
179
|
+
return keyboardShortcutHub.subscribe(() => {
|
|
180
|
+
hubHasHost = keyboardShortcutHub.hasHost;
|
|
181
|
+
hubContributions = keyboardShortcutHub.getContributions();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// A 'host' instance registers itself with the hub while mounted.
|
|
186
|
+
$effect(() => {
|
|
187
|
+
if (mode !== 'host') return;
|
|
188
|
+
return keyboardShortcutHub.registerHost({
|
|
189
|
+
open: () => {
|
|
190
|
+
isOpen = true;
|
|
191
|
+
},
|
|
192
|
+
toggle: () => {
|
|
193
|
+
isOpen = !isOpen;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Whether this instance self-renders or defers to the host.
|
|
199
|
+
const isHost = $derived(mode === 'host');
|
|
200
|
+
const isContributor = $derived(mode !== 'host' && mode !== 'standalone' && hubHasHost);
|
|
201
|
+
|
|
202
|
+
// This instance's own sections (precedence: sections > registry > null).
|
|
203
|
+
const ownSections = $derived(
|
|
121
204
|
sections && sections.length > 0 ? sections : registry ? registrySections : null
|
|
122
205
|
);
|
|
123
206
|
|
|
207
|
+
// Contributing instances hand their sections to the hub. `extraSections`
|
|
208
|
+
// ride along too — they belong to the page, not the overlay itself.
|
|
209
|
+
const contributionId = `kso-${Math.random().toString(36).slice(2, 10)}`;
|
|
210
|
+
$effect(() => {
|
|
211
|
+
if (!isContributor) return;
|
|
212
|
+
const primary = ownSections ?? [];
|
|
213
|
+
if (primary.length === 0 && (!extraSections || extraSections.length === 0)) return;
|
|
214
|
+
const contribution: ShortcutContribution = {
|
|
215
|
+
id: contributionId,
|
|
216
|
+
sectionTitle,
|
|
217
|
+
sections: primary,
|
|
218
|
+
registrySections: extraSections ?? []
|
|
219
|
+
};
|
|
220
|
+
return keyboardShortcutHub.contribute(contribution);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Contributing instances forward external `open` requests to the host —
|
|
224
|
+
// they can't open themselves.
|
|
225
|
+
$effect(() => {
|
|
226
|
+
if (!isContributor) return;
|
|
227
|
+
if (!open) return;
|
|
228
|
+
keyboardShortcutHub.requestOpen();
|
|
229
|
+
isOpen = false;
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
function flattenSections(secs: ShortcutSectionLike[]): SectionEntry[] {
|
|
233
|
+
return secs.flatMap((s) => s.shortcuts);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Host panel content: own sections plus every contributor's sections,
|
|
237
|
+
// each grouped under its sectionTitle.
|
|
238
|
+
const hostSections = $derived.by(() => {
|
|
239
|
+
if (!isHost) return null;
|
|
240
|
+
const primary = ownSections ?? (shortcuts.length > 0 ? [{ title, shortcuts }] : []);
|
|
241
|
+
const contributed: ShortcutSection[] = hubContributions.flatMap((c) => [
|
|
242
|
+
{ title: c.sectionTitle, shortcuts: flattenSections(c.sections) },
|
|
243
|
+
...(c.registrySections ?? []).map((s) => ({ title: s.title, shortcuts: s.shortcuts }))
|
|
244
|
+
]);
|
|
245
|
+
return [...primary, ...contributed];
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// What actually renders in the panel.
|
|
249
|
+
const shownSections = $derived(isHost ? hostSections : ownSections);
|
|
250
|
+
const shownFlatShortcuts = $derived(
|
|
251
|
+
isHost || (shownSections && shownSections.length > 0) ? [] : shortcuts
|
|
252
|
+
);
|
|
253
|
+
|
|
124
254
|
// Track which key label is currently depressed
|
|
125
255
|
let depressedKey = $state<string | null>(null);
|
|
126
256
|
let depressTimer: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -143,6 +273,8 @@
|
|
|
143
273
|
if (e.key === '?') {
|
|
144
274
|
// `?` is always Shift+/. Don't trigger while typing — would swallow the char.
|
|
145
275
|
if (isTypingTarget(e.target)) return;
|
|
276
|
+
// A contributor doesn't own `?` — the host's own listener handles it.
|
|
277
|
+
if (isContributor) return;
|
|
146
278
|
e.preventDefault();
|
|
147
279
|
isOpen = !isOpen;
|
|
148
280
|
return;
|
|
@@ -236,7 +368,7 @@
|
|
|
236
368
|
{/each}
|
|
237
369
|
{/each}
|
|
238
370
|
{:else}
|
|
239
|
-
{#each
|
|
371
|
+
{#each shownFlatShortcuts as { key, description }, i (key + '::' + i)}
|
|
240
372
|
<div class="kso-row">
|
|
241
373
|
<kbd
|
|
242
374
|
class="kso-key"
|
|
@@ -279,6 +411,13 @@
|
|
|
279
411
|
</div>
|
|
280
412
|
</div>
|
|
281
413
|
</div>
|
|
414
|
+
{:else if showHint && !isContributor}
|
|
415
|
+
<!-- Closed-state hint — press ? to open. Contributors that defer to a
|
|
416
|
+
host stay silent: the host renders its own hint. -->
|
|
417
|
+
<div class="kso-hint" aria-hidden="true">
|
|
418
|
+
<kbd class="kso-hint-key">?</kbd>
|
|
419
|
+
<span class="kso-hint-text">for shortcuts</span>
|
|
420
|
+
</div>
|
|
282
421
|
{/if}
|
|
283
422
|
|
|
284
423
|
<style>
|
|
@@ -419,6 +558,39 @@
|
|
|
419
558
|
border-color: oklch(0.45 0.06 240);
|
|
420
559
|
}
|
|
421
560
|
|
|
561
|
+
.kso-hint {
|
|
562
|
+
position: fixed;
|
|
563
|
+
right: 1rem;
|
|
564
|
+
bottom: 1rem;
|
|
565
|
+
z-index: 50;
|
|
566
|
+
display: flex;
|
|
567
|
+
align-items: center;
|
|
568
|
+
gap: 0.45rem;
|
|
569
|
+
padding: 0.3rem 0.65rem;
|
|
570
|
+
background: oklch(0.18 0.02 250 / 0.85);
|
|
571
|
+
border: 1px solid oklch(0.30 0.03 250);
|
|
572
|
+
border-radius: 0.5rem;
|
|
573
|
+
color: oklch(0.85 0.02 250);
|
|
574
|
+
font-size: 0.78rem;
|
|
575
|
+
pointer-events: none;
|
|
576
|
+
animation: kso-fade 0.15s ease-out;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.kso-hint-key {
|
|
580
|
+
font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
|
|
581
|
+
font-size: 0.72rem;
|
|
582
|
+
background: oklch(0.25 0.02 250);
|
|
583
|
+
border: 1px solid oklch(0.35 0.02 250);
|
|
584
|
+
border-bottom-width: 2px;
|
|
585
|
+
border-radius: 0.3rem;
|
|
586
|
+
padding: 0.05rem 0.45rem;
|
|
587
|
+
color: oklch(0.93 0.02 250);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.kso-hint-text {
|
|
591
|
+
color: oklch(0.72 0.02 250);
|
|
592
|
+
}
|
|
593
|
+
|
|
422
594
|
@keyframes kso-fade {
|
|
423
595
|
from {
|
|
424
596
|
opacity: 0;
|
|
@@ -430,7 +602,8 @@
|
|
|
430
602
|
|
|
431
603
|
@media (prefers-reduced-motion: reduce) {
|
|
432
604
|
.kso-backdrop,
|
|
433
|
-
.kso-panel
|
|
605
|
+
.kso-panel,
|
|
606
|
+
.kso-hint {
|
|
434
607
|
animation: none !important;
|
|
435
608
|
}
|
|
436
609
|
.kso-key {
|
|
@@ -38,6 +38,24 @@
|
|
|
38
38
|
* <KeyboardShortcutsOverlay {registry}>
|
|
39
39
|
* {#snippet extra()}<div>…custom footer…</div>{/snippet}
|
|
40
40
|
* </KeyboardShortcutsOverlay>
|
|
41
|
+
*
|
|
42
|
+
* ── Host / contributor mode ───────────────────────────────────────────
|
|
43
|
+
*
|
|
44
|
+
* An app shell can mount ONE global host overlay; every other instance
|
|
45
|
+
* on the page then contributes its sections to the host instead of
|
|
46
|
+
* self-rendering, so exactly one `?` overlay exists no matter how many
|
|
47
|
+
* surfaces mount the component:
|
|
48
|
+
*
|
|
49
|
+
* // App shell (layout.svelte):
|
|
50
|
+
* <KeyboardShortcutsOverlay mode="host" />
|
|
51
|
+
*
|
|
52
|
+
* // A page with its own shortcut set:
|
|
53
|
+
* <KeyboardShortcutsOverlay {sections} sectionTitle="Inbox" />
|
|
54
|
+
*
|
|
55
|
+
* `mode` defaults to `'auto'`: the instance contributes to the host when
|
|
56
|
+
* one is mounted, and self-renders when there isn't (the portal /
|
|
57
|
+
* contract-page fallback). Force self-rendering with
|
|
58
|
+
* `mode="standalone"`.
|
|
41
59
|
*/
|
|
42
60
|
import type { Snippet } from 'svelte';
|
|
43
61
|
import type { ShortcutRegistry } from '../utils/keyboardShortcutRegistry';
|
|
@@ -84,6 +102,9 @@ type $$ComponentProps = {
|
|
|
84
102
|
extra?: Snippet;
|
|
85
103
|
title?: string;
|
|
86
104
|
open?: boolean;
|
|
105
|
+
mode?: 'auto' | 'host' | 'standalone';
|
|
106
|
+
sectionTitle?: string;
|
|
107
|
+
showHint?: boolean;
|
|
87
108
|
};
|
|
88
109
|
declare const KeyboardShortcutsOverlay: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
89
110
|
type KeyboardShortcutsOverlay = ReturnType<typeof KeyboardShortcutsOverlay>;
|