@joewinke/jatui 0.35.0 → 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 +8 -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/ThumbnailGrid.svelte +34 -7
- package/dist/components/ThumbnailGrid.svelte.d.ts +17 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -2
- 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
|
@@ -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>;
|
|
@@ -21,12 +21,28 @@
|
|
|
21
21
|
max?: number;
|
|
22
22
|
/** Tile size. Default 'md'. */
|
|
23
23
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
24
|
-
/** Show remove (×) buttons
|
|
24
|
+
/** Show remove (×) buttons; requires `onRemove`. Visibility follows `removeMode`. */
|
|
25
25
|
removable?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* When the remove × is visible. 'hover' (default) reveals it on
|
|
28
|
+
* hover/focus and keeps it visible on coarse (touch) pointers — a
|
|
29
|
+
* hover-only control is unusable on touch (the ContextMenuTrigger
|
|
30
|
+
* pattern). 'always' pins it visible for touch-first flows.
|
|
31
|
+
*/
|
|
32
|
+
removeMode?: 'hover' | 'always';
|
|
26
33
|
/** Show the dashed add tile at the end; requires `onAdd`. */
|
|
27
34
|
addable?: boolean;
|
|
28
35
|
/** Tooltip / aria-label for the add tile. Default 'Add items'. */
|
|
29
36
|
addLabel?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Tailwind grid-cols classes for a responsive tile grid, e.g.
|
|
39
|
+
* 'grid-cols-2 sm:grid-cols-4'. When set, the root renders as a CSS grid
|
|
40
|
+
* (`grid {gridCols} gap-2`) instead of the fixed flex-wrap strip, and
|
|
41
|
+
* tiles stretch to fill their column (height still follows `size`).
|
|
42
|
+
*/
|
|
43
|
+
gridCols?: string;
|
|
44
|
+
/** Extra classes for the root container, composed after the layout classes. */
|
|
45
|
+
class?: string;
|
|
30
46
|
/** Fired when a thumbnail is clicked. */
|
|
31
47
|
onSelect?: (item: ThumbnailItem) => void;
|
|
32
48
|
/** Fired when a remove (×) button is clicked. */
|
|
@@ -46,12 +62,15 @@
|
|
|
46
62
|
max = 8,
|
|
47
63
|
size = 'md',
|
|
48
64
|
removable = false,
|
|
65
|
+
removeMode = 'hover',
|
|
49
66
|
addable = false,
|
|
50
67
|
addLabel = 'Add items',
|
|
68
|
+
gridCols,
|
|
51
69
|
onSelect,
|
|
52
70
|
onRemove,
|
|
53
71
|
onAdd,
|
|
54
|
-
cell
|
|
72
|
+
cell,
|
|
73
|
+
class: className = ''
|
|
55
74
|
}: Props = $props();
|
|
56
75
|
|
|
57
76
|
const sizeClasses: Record<NonNullable<Props['size']>, string> = {
|
|
@@ -63,18 +82,26 @@
|
|
|
63
82
|
|
|
64
83
|
const visible = $derived(items.slice(0, max));
|
|
65
84
|
const overflow = $derived(Math.max(0, items.length - max));
|
|
85
|
+
/** Grid mode when the caller supplies grid-cols classes; tiles fill their column. */
|
|
86
|
+
const isGrid = $derived(Boolean(gridCols));
|
|
87
|
+
const fillClass = $derived(isGrid ? ' w-full' : '');
|
|
88
|
+
const removeVisibility = $derived(
|
|
89
|
+
removeMode === 'always'
|
|
90
|
+
? 'opacity-100'
|
|
91
|
+
: 'opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100 pointer-coarse:opacity-100'
|
|
92
|
+
);
|
|
66
93
|
|
|
67
94
|
function shortLabel(alt: string): string {
|
|
68
95
|
return alt.length > 14 ? `${alt.slice(0, 14)}…` : alt;
|
|
69
96
|
}
|
|
70
97
|
</script>
|
|
71
98
|
|
|
72
|
-
<div class="flex flex-wrap items-center gap-2">
|
|
99
|
+
<div class="{isGrid ? 'grid gap-2' : 'flex flex-wrap items-center gap-2'} {gridCols ?? ''} {className}">
|
|
73
100
|
{#each visible as item (item.id)}
|
|
74
101
|
<div class="group relative">
|
|
75
102
|
<button
|
|
76
103
|
type="button"
|
|
77
|
-
class="{sizeClasses[size]} cursor-pointer overflow-hidden rounded border transition-all duration-200
|
|
104
|
+
class="{sizeClasses[size]}{fillClass} cursor-pointer overflow-hidden rounded border transition-all duration-200
|
|
78
105
|
{removable ? 'border-2 border-accent' : 'border-base-300 hover:border-primary'}"
|
|
79
106
|
onclick={() => onSelect?.(item)}
|
|
80
107
|
title={item.alt}
|
|
@@ -96,7 +123,7 @@
|
|
|
96
123
|
{#if removable && onRemove}
|
|
97
124
|
<button
|
|
98
125
|
type="button"
|
|
99
|
-
class="absolute -top-1 -right-1 z-10 flex h-4 w-4 items-center justify-center rounded-full bg-error text-error-content
|
|
126
|
+
class="absolute -top-1 -right-1 z-10 flex h-4 w-4 items-center justify-center rounded-full bg-error text-error-content {removeVisibility}"
|
|
100
127
|
onclick={(e) => {
|
|
101
128
|
e.stopPropagation();
|
|
102
129
|
onRemove(item);
|
|
@@ -114,7 +141,7 @@
|
|
|
114
141
|
|
|
115
142
|
{#if overflow > 0}
|
|
116
143
|
<div
|
|
117
|
-
class="{sizeClasses[size]} flex items-center justify-center rounded border border-base-300 bg-base-200"
|
|
144
|
+
class="{sizeClasses[size]}{fillClass} flex items-center justify-center rounded border border-base-300 bg-base-200"
|
|
118
145
|
title="{overflow} more item{overflow === 1 ? '' : 's'}"
|
|
119
146
|
>
|
|
120
147
|
<span class="text-xs opacity-70">+{overflow}</span>
|
|
@@ -124,7 +151,7 @@
|
|
|
124
151
|
{#if addable && onAdd}
|
|
125
152
|
<button
|
|
126
153
|
type="button"
|
|
127
|
-
class="{sizeClasses[size]} flex scale-90 cursor-pointer items-center justify-center rounded border-2 border-dashed border-primary/30 bg-primary/5 transition-all duration-200 hover:border-primary/50 hover:bg-primary/10"
|
|
154
|
+
class="{sizeClasses[size]}{fillClass} flex scale-90 cursor-pointer items-center justify-center rounded border-2 border-dashed border-primary/30 bg-primary/5 transition-all duration-200 hover:border-primary/50 hover:bg-primary/10"
|
|
128
155
|
onclick={onAdd}
|
|
129
156
|
title={addLabel}
|
|
130
157
|
aria-label={addLabel}
|
|
@@ -18,12 +18,28 @@ interface Props {
|
|
|
18
18
|
max?: number;
|
|
19
19
|
/** Tile size. Default 'md'. */
|
|
20
20
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
21
|
-
/** Show remove (×) buttons
|
|
21
|
+
/** Show remove (×) buttons; requires `onRemove`. Visibility follows `removeMode`. */
|
|
22
22
|
removable?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* When the remove × is visible. 'hover' (default) reveals it on
|
|
25
|
+
* hover/focus and keeps it visible on coarse (touch) pointers — a
|
|
26
|
+
* hover-only control is unusable on touch (the ContextMenuTrigger
|
|
27
|
+
* pattern). 'always' pins it visible for touch-first flows.
|
|
28
|
+
*/
|
|
29
|
+
removeMode?: 'hover' | 'always';
|
|
23
30
|
/** Show the dashed add tile at the end; requires `onAdd`. */
|
|
24
31
|
addable?: boolean;
|
|
25
32
|
/** Tooltip / aria-label for the add tile. Default 'Add items'. */
|
|
26
33
|
addLabel?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Tailwind grid-cols classes for a responsive tile grid, e.g.
|
|
36
|
+
* 'grid-cols-2 sm:grid-cols-4'. When set, the root renders as a CSS grid
|
|
37
|
+
* (`grid {gridCols} gap-2`) instead of the fixed flex-wrap strip, and
|
|
38
|
+
* tiles stretch to fill their column (height still follows `size`).
|
|
39
|
+
*/
|
|
40
|
+
gridCols?: string;
|
|
41
|
+
/** Extra classes for the root container, composed after the layout classes. */
|
|
42
|
+
class?: string;
|
|
27
43
|
/** Fired when a thumbnail is clicked. */
|
|
28
44
|
onSelect?: (item: ThumbnailItem) => void;
|
|
29
45
|
/** Fired when a remove (×) button is clicked. */
|
package/dist/index.d.ts
CHANGED
|
@@ -93,7 +93,7 @@ export { default as ResizablePanel } from './components/ResizablePanel.svelte';
|
|
|
93
93
|
export { default as VoicePlayer } from './components/VoicePlayer.svelte';
|
|
94
94
|
export { default as SpeechForm } from './components/SpeechForm.svelte';
|
|
95
95
|
export { default as AudioRecorder } from './components/AudioRecorder.svelte';
|
|
96
|
-
export type { AudioRecordingResult } from './utils/audioRecorderCore';
|
|
96
|
+
export type { AudioRecordingResult, AudioRecorderChunk } from './utils/audioRecorderCore';
|
|
97
97
|
export { createAudioRecorder } from './utils/audioRecorder';
|
|
98
98
|
export type { AudioRecorder, AudioRecorderCore, AudioRecorderError, AudioRecorderErrorKind, AudioRecorderFrame, AudioRecorderMode, AudioRecorderOptions, AudioRecorderPhase, AudioRecorderSnapshot } from './utils/audioRecorderCore';
|
|
99
99
|
export { default as ThemeSelector } from './components/ThemeSelector.svelte';
|
|
@@ -172,7 +172,8 @@ export { default as PipelineColumn } from './components/pipeline/PipelineColumn.
|
|
|
172
172
|
export { default as PipelineCard } from './components/pipeline/PipelineCard.svelte';
|
|
173
173
|
export type { PipelineStage, StageColors, PipelineItem, PipelineMetric, StageMetrics, PipelineViewMode, PipelineTableColumn, PipelineDragConfig, PipelineConfig, PipelineStageChangeEvent, PipelineItemClickEvent } from './types/pipeline';
|
|
174
174
|
export { DEFAULT_DRAG_CONFIG, DEFAULT_PIPELINE_CONFIG } from './types/pipeline';
|
|
175
|
-
export { parseMentions, renderMentionsAsHTML, createMentionString, extractPlainText, findMentionAtCursor, findPartialMention, replaceMentionInContent, escapeHTML, countMentions, setMentionUrlGenerator } from './utils/mentionParser';
|
|
175
|
+
export { parseMentions, renderMentionsAsHTML, createMentionString, extractPlainText, findMentionAtCursor, findPartialMention, replaceMentionInContent, escapeHTML, countMentions, hasMentions, setMentionUrlGenerator, registerMentionType, extractAgentMentions, getUniqueMentionsByType, getMentionedUserIds, AGENT_MENTION_TYPE } from './utils/mentionParser';
|
|
176
|
+
export type { MentionMatch, MentionUrlGenerator, MentionTypeConfig } from './utils/mentionParser';
|
|
176
177
|
export { default as CommentThread } from './components/CommentThread.svelte';
|
|
177
178
|
export type { Comment, CommentEntityType, CommentVisibility, CommentCreateInput, CommentCallbacks } from './types/comments';
|
|
178
179
|
export { COMMENT_ENTITY_TYPES, COMMENT_VISIBILITIES } from './types/comments';
|
|
@@ -187,6 +188,7 @@ export type { MarkupTool, Point, MarkupShape, ArrowShape, RectangleShape, Ellips
|
|
|
187
188
|
export type { Annotation, AnnotationProfile, AnnotationThread, AnnotationCallbacks, } from './types/annotation';
|
|
188
189
|
export { default as KeyboardShortcutsOverlay } from './components/KeyboardShortcutsOverlay.svelte';
|
|
189
190
|
export type { KeyboardShortcut, SectionEntry as KeyboardShortcutSectionEntry, ShortcutSection as KeyboardShortcutSection, } from './components/KeyboardShortcutsOverlay.svelte';
|
|
191
|
+
export { keyboardShortcutHub, KEYBOARD_SHORTCUT_HUB_ID, type KeyboardShortcutHost, type ShortcutContribution, type ShortcutSectionLike, } from './utils/keyboardShortcutHub';
|
|
190
192
|
export { createShortcutRegistry, getShortcutRegistry, formatShortcut, parseCombo as parseShortcutCombo, isTypingTarget, detectPlatform as detectShortcutPlatform, SIDEBAR_TOGGLE_KEY, normalizeCombo, comboFromEvent, validateCombo, localStorageBindingStore, BROWSER_RESERVED_COMBOS, } from './utils/keyboardShortcutRegistry';
|
|
191
193
|
export type { ShortcutRegistry, ShortcutDefinition, ShortcutModifier, ShortcutPlatform, PushContextOptions as ShortcutContextOptions, RegistrySection as ShortcutRegistrySection, ToSectionsOptions as ShortcutSectionsOptions, ShortcutRegistryOptions, ShortcutEventTarget, ShortcutDeclaration, BindingOverrides, BindingStore, BindingInfo, ShortcutConflict, ShortcutConflictSeverity, FindConflictsTarget, AllConflictsOptions as ShortcutAllConflictsOptions, ReservedCombo, RebindResult, RebindOptions as ShortcutRebindOptions, RejectedBinding, RawShortcutHandler, ModifierState as ShortcutModifierState, } from './utils/keyboardShortcutRegistry';
|
|
192
194
|
export { default as MapView } from './components/MapView.svelte';
|
|
@@ -200,7 +202,7 @@ export { staggerEntrance } from './actions/staggerEntrance';
|
|
|
200
202
|
export type { StaggerEntranceOptions } from './actions/staggerEntrance';
|
|
201
203
|
export { attention } from './actions/attention';
|
|
202
204
|
export type { AttentionEffect, AttentionOptions } from './actions/attention';
|
|
203
|
-
export { reveal, REVEAL_HIDDEN_CLASS, REVEAL_BASE_CLASS } from './actions/reveal';
|
|
205
|
+
export { reveal, REVEAL_HIDDEN_CLASS, REVEAL_BASE_CLASS, REVEAL_OFF_SELECTOR, REVEAL_MAX_DELAY_SECONDS } from './actions/reveal';
|
|
204
206
|
export type { RevealOptions, RevealVariant } from './actions/reveal';
|
|
205
207
|
export { default as ThumbnailGrid } from './components/ThumbnailGrid.svelte';
|
|
206
208
|
export type { ThumbnailItem } from './components/ThumbnailGrid.svelte';
|
package/dist/index.js
CHANGED
|
@@ -204,7 +204,7 @@ export { default as PipelineColumn } from './components/pipeline/PipelineColumn.
|
|
|
204
204
|
export { default as PipelineCard } from './components/pipeline/PipelineCard.svelte';
|
|
205
205
|
export { DEFAULT_DRAG_CONFIG, DEFAULT_PIPELINE_CONFIG } from './types/pipeline';
|
|
206
206
|
// Utilities — mention parsing
|
|
207
|
-
export { parseMentions, renderMentionsAsHTML, createMentionString, extractPlainText, findMentionAtCursor, findPartialMention, replaceMentionInContent, escapeHTML, countMentions, setMentionUrlGenerator } from './utils/mentionParser';
|
|
207
|
+
export { parseMentions, renderMentionsAsHTML, createMentionString, extractPlainText, findMentionAtCursor, findPartialMention, replaceMentionInContent, escapeHTML, countMentions, hasMentions, setMentionUrlGenerator, registerMentionType, extractAgentMentions, getUniqueMentionsByType, getMentionedUserIds, AGENT_MENTION_TYPE } from './utils/mentionParser';
|
|
208
208
|
// Components — Comments (universal threaded comments)
|
|
209
209
|
export { default as CommentThread } from './components/CommentThread.svelte';
|
|
210
210
|
export { COMMENT_ENTITY_TYPES, COMMENT_VISIBILITIES } from './types/comments';
|
|
@@ -220,6 +220,7 @@ export { default as AnnotationPanel } from './components/AnnotationPanel.svelte'
|
|
|
220
220
|
export { MARKUP_COLORS, DEFAULT_STROKE_WIDTH, renderShape, renderAllShapes, nextShapeId, } from './components/markup';
|
|
221
221
|
// Components — Keyboard Navigation
|
|
222
222
|
export { default as KeyboardShortcutsOverlay } from './components/KeyboardShortcutsOverlay.svelte';
|
|
223
|
+
export { keyboardShortcutHub, KEYBOARD_SHORTCUT_HUB_ID, } from './utils/keyboardShortcutHub';
|
|
223
224
|
export { createShortcutRegistry, getShortcutRegistry, formatShortcut, parseCombo as parseShortcutCombo, isTypingTarget, detectPlatform as detectShortcutPlatform, SIDEBAR_TOGGLE_KEY, normalizeCombo, comboFromEvent, validateCombo, localStorageBindingStore, BROWSER_RESERVED_COMBOS, } from './utils/keyboardShortcutRegistry';
|
|
224
225
|
// Components — Google Maps (DispatchMap fork + location primitives)
|
|
225
226
|
export { default as MapView } from './components/MapView.svelte';
|
|
@@ -243,7 +244,7 @@ export { attention } from './actions/attention';
|
|
|
243
244
|
// Scroll-reveal (jatui-m4uz3.3, chimaro port): IntersectionObserver
|
|
244
245
|
// scroll-reveal whose variants live in styles/motion.css as jatui-reveal-*
|
|
245
246
|
// classes on the --motion-* tokens — no shipped animista.css.
|
|
246
|
-
export { reveal, REVEAL_HIDDEN_CLASS, REVEAL_BASE_CLASS } from './actions/reveal';
|
|
247
|
+
export { reveal, REVEAL_HIDDEN_CLASS, REVEAL_BASE_CLASS, REVEAL_OFF_SELECTOR, REVEAL_MAX_DELAY_SECONDS } from './actions/reveal';
|
|
247
248
|
// Thumbnail grid (jatui-m4uz3.3, chimaro port): generic {id,url,alt} strip
|
|
248
249
|
// with overflow counter, remove/add affordances and a cell snippet override.
|
|
249
250
|
export { default as ThumbnailGrid } from './components/ThumbnailGrid.svelte';
|
package/dist/motion/index.d.ts
CHANGED
|
@@ -13,5 +13,5 @@ export { createMotionLevelStore } from './store';
|
|
|
13
13
|
export type { MotionState, MotionLevelStore, MotionLevelStoreOptions } from './store';
|
|
14
14
|
export { glide } from '../transitions/glide';
|
|
15
15
|
export type { GlideOrigin, GlideParams } from '../transitions/glide';
|
|
16
|
-
export { reveal, REVEAL_HIDDEN_CLASS, REVEAL_BASE_CLASS } from '../actions/reveal';
|
|
16
|
+
export { reveal, REVEAL_HIDDEN_CLASS, REVEAL_BASE_CLASS, REVEAL_OFF_SELECTOR, REVEAL_MAX_DELAY_SECONDS } from '../actions/reveal';
|
|
17
17
|
export type { RevealOptions, RevealVariant } from '../actions/reveal';
|
package/dist/motion/index.js
CHANGED
|
@@ -8,4 +8,4 @@ export { cubicBezier, motionEasing } from './easing';
|
|
|
8
8
|
export { motionFade, motionScale, motionSlide, resolveMotionTiming } from './transitions';
|
|
9
9
|
export { createMotionLevelStore } from './store';
|
|
10
10
|
export { glide } from '../transitions/glide';
|
|
11
|
-
export { reveal, REVEAL_HIDDEN_CLASS, REVEAL_BASE_CLASS } from '../actions/reveal';
|
|
11
|
+
export { reveal, REVEAL_HIDDEN_CLASS, REVEAL_BASE_CLASS, REVEAL_OFF_SELECTOR, REVEAL_MAX_DELAY_SECONDS } from '../actions/reveal';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AudioRecorderMode, AudioRecorderOptions, AudioRecorderPhase, AudioRecorderSnapshot } from './audioRecorderCore';
|
|
2
|
-
export type { AudioRecorderError, AudioRecorderErrorKind, AudioRecorderFrame, AudioRecorderMode, AudioRecorderOptions, AudioRecorderPhase, AudioRecordingResult } from './audioRecorderCore';
|
|
2
|
+
export type { AudioRecorderChunk, AudioRecorderError, AudioRecorderErrorKind, AudioRecorderFrame, AudioRecorderMode, AudioRecorderOptions, AudioRecorderPhase, AudioRecordingResult } from './audioRecorderCore';
|
|
3
3
|
export interface AudioRecorder {
|
|
4
4
|
/** False when the platform has no getUserMedia/MediaRecorder (SSR, Node). */
|
|
5
5
|
readonly supported: boolean;
|
|
@@ -15,6 +15,11 @@ export interface AudioRecorder {
|
|
|
15
15
|
readonly elapsedMs: number;
|
|
16
16
|
/** Last error, or null. */
|
|
17
17
|
readonly error: AudioRecorderSnapshot['error'];
|
|
18
|
+
/** Live mic MediaStream while the recorder holds it (starting/recording),
|
|
19
|
+
* else null (jatui-ft2ot). Reactive — read it in templates/effects. For
|
|
20
|
+
* hosts that attach the raw stream (WebRTC, a custom MediaRecorder with
|
|
21
|
+
* requestData()-driven streaming). */
|
|
22
|
+
readonly stream: MediaStream | null;
|
|
18
23
|
start(): Promise<void>;
|
|
19
24
|
stop(): void;
|
|
20
25
|
pause(): void;
|
|
@@ -13,6 +13,7 @@ export function createAudioRecorder(options = {}) {
|
|
|
13
13
|
snap.sizeBytes = s.sizeBytes;
|
|
14
14
|
snap.elapsedMs = s.elapsedMs;
|
|
15
15
|
snap.error = s.error;
|
|
16
|
+
snap.stream = s.stream;
|
|
16
17
|
});
|
|
17
18
|
|
|
18
19
|
return {
|
|
@@ -47,6 +48,10 @@ export function createAudioRecorder(options = {}) {
|
|
|
47
48
|
get error() {
|
|
48
49
|
return snap.error;
|
|
49
50
|
},
|
|
51
|
+
|
|
52
|
+
get stream() {
|
|
53
|
+
return snap.stream;
|
|
54
|
+
},
|
|
50
55
|
start: () => core.start(),
|
|
51
56
|
stop: () => core.stop(),
|
|
52
57
|
pause: () => core.pause(),
|
|
@@ -57,11 +57,32 @@ export interface AudioRecorderFrame {
|
|
|
57
57
|
/** Time-domain bytes (from getByteTimeDomainData) — raw PCM-ish shape. */
|
|
58
58
|
time: Uint8Array;
|
|
59
59
|
}
|
|
60
|
+
/** One MediaRecorder timeslice of audio, handed to `onChunk` as it arrives
|
|
61
|
+
* (jatui-ft2ot). The take keeps accumulating for `onStop` regardless — hosts
|
|
62
|
+
* that buffer chunks (restartable dictation, live transcription) consume
|
|
63
|
+
* both streams independently. */
|
|
64
|
+
export interface AudioRecorderChunk {
|
|
65
|
+
/** The chunk blob — one MediaRecorder `dataavailable` payload. */
|
|
66
|
+
blob: Blob;
|
|
67
|
+
/** MIME type of the recording (same value `onStop` reports). */
|
|
68
|
+
mimeType: string;
|
|
69
|
+
/** Cumulative bytes captured so far in this take (all chunks, not just this one). */
|
|
70
|
+
sizeBytes: number;
|
|
71
|
+
/** Pause-adjusted elapsed ms at the moment this chunk arrived. */
|
|
72
|
+
elapsedMs: number;
|
|
73
|
+
/** 0-based index of this chunk within the take. Resets on a new take. */
|
|
74
|
+
index: number;
|
|
75
|
+
}
|
|
60
76
|
export interface AudioRecorderOptions {
|
|
61
77
|
/** `'toggle'` (default) or `'push'` (hold-to-talk — see module header). */
|
|
62
78
|
mode?: AudioRecorderMode;
|
|
63
79
|
/** Auto-stop ceiling in ms. 0 (default) records until stopped manually. */
|
|
64
80
|
maxDurationMs?: number;
|
|
81
|
+
/** Timeslice in ms passed to `MediaRecorder.start()` — chunks are emitted
|
|
82
|
+
* at least this often and delivered to `onChunk` (jatui-ft2ot). Default
|
|
83
|
+
* 1000 (the historical behavior). 0 records as a single blob until stop
|
|
84
|
+
* (no intermediate chunking). */
|
|
85
|
+
timesliceMs?: number;
|
|
65
86
|
/** Fired once the mic stream is live and recording has started. */
|
|
66
87
|
onStart?: () => void;
|
|
67
88
|
onPause?: () => void;
|
|
@@ -77,6 +98,12 @@ export interface AudioRecorderOptions {
|
|
|
77
98
|
/** Fired every animation frame while recording — analyser data for hosts
|
|
78
99
|
* that draw their own waveform/level UI. Not fired while paused. */
|
|
79
100
|
onFrame?: (frame: AudioRecorderFrame) => void;
|
|
101
|
+
/** Fired per MediaRecorder chunk as it arrives (every `timesliceMs`, plus
|
|
102
|
+
* a final shorter chunk at stop). The take still accumulates for `onStop` —
|
|
103
|
+
* hosts get the full take AND a live chunk stream (restartable dictation,
|
|
104
|
+
* streaming transcription). A throwing handler reports `handler-failed`
|
|
105
|
+
* but does NOT stop the recording. */
|
|
106
|
+
onChunk?: (chunk: AudioRecorderChunk) => void;
|
|
80
107
|
}
|
|
81
108
|
/** Injectable platform bindings — defaults resolve from globalThis lazily at factory time, so Node (no MediaRecorder) degrades to `supported: false` instead of throwing at import. */
|
|
82
109
|
export interface AudioRecorderDeps {
|
|
@@ -104,11 +131,18 @@ export interface AudioRecorderSnapshot {
|
|
|
104
131
|
elapsedMs: number;
|
|
105
132
|
/** Last error, or null. */
|
|
106
133
|
error: AudioRecorderError | null;
|
|
134
|
+
/** Live mic MediaStream while the recorder holds it (starting/recording),
|
|
135
|
+
* else null (jatui-ft2ot). For hosts that attach the raw stream — WebRTC,
|
|
136
|
+
* a custom MediaRecorder with requestData()-driven streaming. */
|
|
137
|
+
stream: MediaStream | null;
|
|
107
138
|
}
|
|
108
139
|
export interface AudioRecorderCore {
|
|
109
140
|
/** False when the platform has no getUserMedia/MediaRecorder (SSR, Node). */
|
|
110
141
|
readonly supported: boolean;
|
|
111
142
|
readonly mode: AudioRecorderMode;
|
|
143
|
+
/** Live mic MediaStream while the recorder holds it, else null — same
|
|
144
|
+
* value as `snapshot().stream`, for hosts that read it imperatively. */
|
|
145
|
+
readonly stream: MediaStream | null;
|
|
112
146
|
/** Current reactive-independent snapshot (the rune wrapper copies this into $state). */
|
|
113
147
|
snapshot(): AudioRecorderSnapshot;
|
|
114
148
|
/** Subscribe to snapshot changes. Returns an unsubscribe fn. */
|