@joewinke/jatui 0.38.5 → 0.39.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.
Files changed (67) hide show
  1. package/CLAUDE.md +32 -1
  2. package/THIRD_PARTY_NOTICES.md +51 -0
  3. package/dist/components/BulkActionBar.svelte +32 -2
  4. package/dist/components/BulkActionBar.svelte.d.ts +5 -1
  5. package/dist/components/FloatingActionBar.svelte +102 -34
  6. package/dist/components/FloatingActionBar.svelte.d.ts +9 -0
  7. package/dist/components/MediaExplorer.svelte +249 -0
  8. package/dist/components/MediaExplorer.svelte.d.ts +37 -0
  9. package/dist/components/NotificationCenter.svelte +481 -0
  10. package/dist/components/NotificationCenter.svelte.d.ts +29 -0
  11. package/dist/components/SearchDropdown.svelte +54 -8
  12. package/dist/components/SearchDropdown.svelte.d.ts +12 -0
  13. package/dist/components/VideoPlayer.svelte +269 -3
  14. package/dist/components/agent/AgentLog.svelte +217 -0
  15. package/dist/components/agent/AgentLog.svelte.d.ts +72 -0
  16. package/dist/components/agent/AgentLogRow.svelte +281 -0
  17. package/dist/components/agent/AgentLogRow.svelte.d.ts +21 -0
  18. package/dist/components/agent/AgentTaskList.svelte +607 -0
  19. package/dist/components/agent/AgentTaskList.svelte.d.ts +40 -0
  20. package/dist/components/agent/AgentThinking.svelte +280 -0
  21. package/dist/components/agent/AgentThinking.svelte.d.ts +22 -0
  22. package/dist/components/agent/ComposerGlow.svelte +265 -0
  23. package/dist/components/agent/ComposerGlow.svelte.d.ts +18 -0
  24. package/dist/components/agent/ContextMeter.svelte +277 -0
  25. package/dist/components/agent/ContextMeter.svelte.d.ts +21 -0
  26. package/dist/components/agent/Questionnaire.svelte +767 -0
  27. package/dist/components/agent/Questionnaire.svelte.d.ts +29 -0
  28. package/dist/components/agent/ShimmerText.svelte +100 -0
  29. package/dist/components/agent/ShimmerText.svelte.d.ts +19 -0
  30. package/dist/components/agent/StreamedText.svelte +135 -0
  31. package/dist/components/agent/StreamedText.svelte.d.ts +13 -0
  32. package/dist/components/agent/agentLog.d.ts +22 -0
  33. package/dist/components/agent/agentLog.js +26 -0
  34. package/dist/components/agent/agentTaskList.d.ts +90 -0
  35. package/dist/components/agent/agentTaskList.js +93 -0
  36. package/dist/components/agent/agentThinking.d.ts +46 -0
  37. package/dist/components/agent/agentThinking.js +78 -0
  38. package/dist/components/agent/contextMeter.d.ts +97 -0
  39. package/dist/components/agent/contextMeter.js +156 -0
  40. package/dist/components/agent/questionnaire.d.ts +104 -0
  41. package/dist/components/agent/questionnaire.js +225 -0
  42. package/dist/components/agent/revealCore.d.ts +74 -0
  43. package/dist/components/agent/revealCore.js +160 -0
  44. package/dist/components/agent/revealTicker.d.ts +34 -0
  45. package/dist/components/agent/revealTicker.js +73 -0
  46. package/dist/components/agent/streamedText.d.ts +28 -0
  47. package/dist/components/agent/streamedText.js +59 -0
  48. package/dist/components/messaging/MessageInput.svelte +34 -4
  49. package/dist/components/messaging/MessageInput.svelte.d.ts +7 -0
  50. package/dist/components/messaging/MessageItem.svelte +2 -2
  51. package/dist/components/notificationCenter.d.ts +65 -0
  52. package/dist/components/notificationCenter.js +110 -0
  53. package/dist/components/progress/Steps.svelte +269 -5
  54. package/dist/components/progress/Steps.svelte.d.ts +16 -1
  55. package/dist/components/progress/stepsSummary.d.ts +14 -0
  56. package/dist/components/progress/stepsSummary.js +32 -0
  57. package/dist/index.d.ts +34 -0
  58. package/dist/index.js +29 -0
  59. package/dist/selection/clickSelection.d.ts +89 -0
  60. package/dist/selection/clickSelection.js +152 -0
  61. package/dist/selection/createClickSelection.d.ts +79 -0
  62. package/dist/selection/createClickSelection.js +134 -0
  63. package/dist/video/keys.d.ts +5 -5
  64. package/dist/video/keys.js +9 -5
  65. package/dist/video/transcriptSearch.d.ts +80 -0
  66. package/dist/video/transcriptSearch.js +153 -0
  67. package/package.json +3 -2
@@ -0,0 +1,249 @@
1
+ <script lang="ts">
2
+ /**
3
+ * MediaExplorer — a two-pane source browser shell for files, images, or any
4
+ * per-source collection. The LEFT RAIL lists the sources (grouped, keyboard
5
+ * navigable); the RIGHT PANE renders whatever the host shows for the active
6
+ * source via the `pane` snippet. MediaExplorer owns the shell only — it is
7
+ * deliberately decoupled from any backend, media type, or selection model:
8
+ * the host keeps its own selection state in the pane and the footer.
9
+ *
10
+ * <MediaExplorer {sources} bind:activeId title="Add references" onClose={...}>
11
+ * {#snippet pane(source)}
12
+ * <MyBrowser source={source.id} />
13
+ * {/snippet}
14
+ * {#snippet footer()}…{/snippet}
15
+ * </MediaExplorer>
16
+ *
17
+ * Rail keyboard contract: the rail is one tab stop (roving tabindex).
18
+ * ArrowUp/ArrowDown/Home/End move between enabled sources and ACTIVATE them
19
+ * (the pane follows focus — arrowing through sources is the browsing gesture,
20
+ * no Enter needed). Click activates. Disabled sources are skipped; their
21
+ * `hint` explains why (rendered as the row's title).
22
+ *
23
+ * Dialog conventions follow Modal.svelte: native <dialog>, focus moved in on
24
+ * open and restored on close, Escape + backdrop close, body scroll locked,
25
+ * reduced-motion skips the entrance transitions.
26
+ */
27
+ import { fade, fly } from "svelte/transition"
28
+ import { cubicOut } from "svelte/easing"
29
+ import type { Snippet } from "svelte"
30
+
31
+ export interface ExplorerSource {
32
+ /** Stable source id, e.g. "refs", "out", "catalogs/costume". */
33
+ id: string
34
+ /** Rail label. */
35
+ label: string
36
+ /** Small mono suffix after the label, e.g. "refs/". */
37
+ hint?: string
38
+ /** Right-aligned count badge. */
39
+ badge?: string | number
40
+ /** Rail section heading; ungrouped sources render above every group. */
41
+ group?: string
42
+ /** Not clickable and skipped by the arrows; `hint` should say why. */
43
+ disabled?: boolean
44
+ }
45
+
46
+ interface Props {
47
+ sources: ExplorerSource[]
48
+ /** The active source id. Falls back to the first enabled source when unset or stale. */
49
+ activeId?: string
50
+ title: string
51
+ onClose: () => void
52
+ /** Content of the active source. */
53
+ pane: Snippet<[ExplorerSource]>
54
+ /** Rendered beside the title (filters, view toggles). */
55
+ toolbar?: Snippet
56
+ /** Below the pane (selection counts, actions). */
57
+ footer?: Snippet
58
+ /** Accessible label for the rail. Default "Sources". */
59
+ railLabel?: string
60
+ /** Accessible label for the pane column. Default "Contents". */
61
+ paneLabel?: string
62
+ /** Max width of the dialog as a CSS length. Default "72rem". */
63
+ maxWidth?: string
64
+ }
65
+
66
+ let {
67
+ sources,
68
+ activeId = $bindable(),
69
+ title,
70
+ onClose,
71
+ pane,
72
+ toolbar,
73
+ footer,
74
+ railLabel = "Sources",
75
+ paneLabel = "Contents",
76
+ maxWidth = "72rem",
77
+ }: Props = $props()
78
+
79
+ const enabled = $derived(sources.filter((s) => !s.disabled))
80
+
81
+ // An unset or stale activeId falls to the first enabled source, so a caller
82
+ // can bind a plain variable and read it back.
83
+ const active = $derived(sources.find((s) => s.id === activeId) ?? enabled[0])
84
+ $effect(() => {
85
+ if (active && active.id !== activeId) activeId = active.id
86
+ })
87
+
88
+ // Rail sections: ungrouped first, then groups in first-seen order.
89
+ const sections = $derived.by(() => {
90
+ const ungrouped = sources.filter((s) => !s.group)
91
+ const groups: { name: string; sources: ExplorerSource[] }[] = []
92
+ for (const s of sources) {
93
+ if (!s.group) continue
94
+ let g = groups.find((x) => x.name === s.group)
95
+ if (!g) groups.push((g = { name: s.group, sources: [] }))
96
+ g.sources.push(s)
97
+ }
98
+ return [...(ungrouped.length ? [{ name: "", sources: ungrouped }] : []), ...groups]
99
+ })
100
+
101
+ function activate(s: ExplorerSource) {
102
+ if (s.disabled) return
103
+ activeId = s.id
104
+ }
105
+
106
+ // Roving tabindex: one tab stop, arrows both move and activate.
107
+ let railEl = $state<HTMLElement | null>(null)
108
+ let dialogEl = $state<HTMLDialogElement | null>(null)
109
+ let previousFocus: HTMLElement | null = null
110
+
111
+ const reducedMotion =
112
+ typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
113
+
114
+ const FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
115
+
116
+ $effect(() => {
117
+ if (typeof document === "undefined") return
118
+ previousFocus = document.activeElement as HTMLElement
119
+ document.body.style.overflow = "hidden"
120
+ requestAnimationFrame(() => dialogEl?.focus())
121
+ return () => {
122
+ document.body.style.overflow = ""
123
+ previousFocus?.focus?.()
124
+ previousFocus = null
125
+ }
126
+ })
127
+
128
+ function focusRow(id: string) {
129
+ railEl?.querySelector<HTMLElement>(`[data-source-id="${CSS.escape(id)}"]`)?.focus()
130
+ }
131
+ function onRailKey(e: KeyboardEvent) {
132
+ if (e.key !== "ArrowDown" && e.key !== "ArrowUp" && e.key !== "Home" && e.key !== "End") return
133
+ e.preventDefault()
134
+ if (!enabled.length) return
135
+ const i = enabled.findIndex((s) => s.id === active?.id)
136
+ const next =
137
+ e.key === "Home" ? 0 : e.key === "End" ? enabled.length - 1 : (i + (e.key === "ArrowDown" ? 1 : enabled.length - 1)) % enabled.length
138
+ const target = enabled[next]!
139
+ activate(target)
140
+ focusRow(target.id)
141
+ }
142
+ function onDialogKey(e: KeyboardEvent) {
143
+ if (e.key !== "Escape") return
144
+ e.preventDefault()
145
+ e.stopPropagation()
146
+ onClose()
147
+ }
148
+ function trapFocus(e: KeyboardEvent) {
149
+ if (e.key !== "Tab" || !dialogEl) return
150
+ const focusable = dialogEl.querySelectorAll<HTMLElement>(FOCUSABLE)
151
+ if (!focusable.length) return
152
+ const first = focusable[0]!
153
+ const last = focusable[focusable.length - 1]!
154
+ if (e.shiftKey && document.activeElement === first) {
155
+ e.preventDefault()
156
+ last.focus()
157
+ } else if (!e.shiftKey && document.activeElement === last) {
158
+ e.preventDefault()
159
+ first.focus()
160
+ }
161
+ }
162
+ </script>
163
+
164
+ <svelte:window onkeydown={onDialogKey} />
165
+
166
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
167
+ <dialog
168
+ bind:this={dialogEl}
169
+ class="modal"
170
+ aria-modal="true"
171
+ aria-label={title}
172
+ open
173
+ tabindex="-1"
174
+ onkeydown={(e) => {
175
+ trapFocus(e)
176
+ }}
177
+ >
178
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
179
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
180
+ <div
181
+ class="modal-backdrop"
182
+ onclick={(e) => {
183
+ // Only a direct click on the backdrop closes; clicks inside the sheet bubble
184
+ // here and must not (same guard as Modal.svelte).
185
+ if (e.target === e.currentTarget) onClose()
186
+ }}
187
+ transition:fade={{ duration: reducedMotion ? 0 : 150 }}
188
+ >
189
+ <div
190
+ class="bg-base-100 text-base-content relative mx-auto my-auto flex max-h-[92vh] flex-col rounded-lg shadow-2xl"
191
+ style="width: min(calc(100% - 2rem), {maxWidth})"
192
+ in:fly={{ y: reducedMotion ? 0 : 12, duration: reducedMotion ? 0 : 200, easing: cubicOut }}
193
+ out:fade={{ duration: reducedMotion ? 0 : 120 }}
194
+ >
195
+ <header class="flex items-center gap-3 px-4 pt-4 pb-2">
196
+ <h2 class="text-lg font-semibold">{title}</h2>
197
+ <span class="min-w-0 flex-1"></span>
198
+ {@render toolbar?.()}
199
+ <button type="button" class="btn btn-sm btn-circle btn-ghost" onclick={onClose} aria-label="Close">✕</button>
200
+ </header>
201
+ <div class="grid min-h-0 flex-1 sm:grid-cols-[13rem_1fr]" style="height: min(70vh, 620px)">
202
+ <nav class="border-base-content/10 overflow-y-auto border-r pr-1" aria-label={railLabel}>
203
+ <div
204
+ class="flex flex-col gap-0.5 py-1"
205
+ role="tablist"
206
+ aria-orientation="vertical"
207
+ aria-label={railLabel}
208
+ bind:this={railEl}
209
+ onkeydown={onRailKey}
210
+ >
211
+ {#each sections as section (section.name)}
212
+ {#if section.name}
213
+ <p class="text-base-content/50 mt-2 px-2 text-[10px] font-semibold tracking-widest uppercase">{section.name}</p>
214
+ {/if}
215
+ {#each section.sources as s (s.id)}
216
+ <button
217
+ data-source-id={s.id}
218
+ role="tab"
219
+ aria-selected={active?.id === s.id}
220
+ tabindex={active?.id === s.id ? 0 : -1}
221
+ disabled={s.disabled}
222
+ title={s.disabled ? s.hint : undefined}
223
+ class="flex items-center gap-2 rounded-lg px-2 py-1.5 text-left text-sm
224
+ {active?.id === s.id ? 'bg-base-content/10 font-semibold' : 'hover:bg-base-content/5'}
225
+ {s.disabled ? 'opacity-50' : ''}"
226
+ onclick={() => activate(s)}
227
+ >
228
+ <span class="min-w-0 flex-1 truncate">{s.label}</span>
229
+ {#if s.hint}<span class="text-base-content/40 shrink-0 font-mono text-[10px]">{s.hint}</span>{/if}
230
+ {#if s.badge !== undefined}<span class="text-base-content/50 shrink-0 font-mono text-[10px] tnum">{s.badge}</span>{/if}
231
+ </button>
232
+ {/each}
233
+ {/each}
234
+ </div>
235
+ </nav>
236
+ <section class="min-h-0 overflow-y-auto p-4" aria-label={paneLabel}>
237
+ {#if active}
238
+ {@render pane(active)}
239
+ {/if}
240
+ </section>
241
+ </div>
242
+ {#if footer}
243
+ <div class="border-base-content/10 flex flex-wrap items-center gap-2 border-t px-4 py-3">
244
+ {@render footer()}
245
+ </div>
246
+ {/if}
247
+ </div>
248
+ </div>
249
+ </dialog>
@@ -0,0 +1,37 @@
1
+ import type { Snippet } from "svelte";
2
+ export interface ExplorerSource {
3
+ /** Stable source id, e.g. "refs", "out", "catalogs/costume". */
4
+ id: string;
5
+ /** Rail label. */
6
+ label: string;
7
+ /** Small mono suffix after the label, e.g. "refs/". */
8
+ hint?: string;
9
+ /** Right-aligned count badge. */
10
+ badge?: string | number;
11
+ /** Rail section heading; ungrouped sources render above every group. */
12
+ group?: string;
13
+ /** Not clickable and skipped by the arrows; `hint` should say why. */
14
+ disabled?: boolean;
15
+ }
16
+ interface Props {
17
+ sources: ExplorerSource[];
18
+ /** The active source id. Falls back to the first enabled source when unset or stale. */
19
+ activeId?: string;
20
+ title: string;
21
+ onClose: () => void;
22
+ /** Content of the active source. */
23
+ pane: Snippet<[ExplorerSource]>;
24
+ /** Rendered beside the title (filters, view toggles). */
25
+ toolbar?: Snippet;
26
+ /** Below the pane (selection counts, actions). */
27
+ footer?: Snippet;
28
+ /** Accessible label for the rail. Default "Sources". */
29
+ railLabel?: string;
30
+ /** Accessible label for the pane column. Default "Contents". */
31
+ paneLabel?: string;
32
+ /** Max width of the dialog as a CSS length. Default "72rem". */
33
+ maxWidth?: string;
34
+ }
35
+ declare const MediaExplorer: import("svelte").Component<Props, {}, "activeId">;
36
+ type MediaExplorer = ReturnType<typeof MediaExplorer>;
37
+ export default MediaExplorer;