@joewinke/jatui 0.22.0 → 0.24.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 (71) hide show
  1. package/CLAUDE.md +72 -2
  2. package/dist/actions/reveal.d.ts +37 -0
  3. package/dist/actions/reveal.js +42 -0
  4. package/dist/components/AudioRecorder.svelte +529 -0
  5. package/dist/components/AudioRecorder.svelte.d.ts +30 -0
  6. package/dist/components/BulkActionBar.svelte +40 -0
  7. package/dist/components/BulkActionBar.svelte.d.ts +9 -0
  8. package/dist/components/ConfirmDialog.svelte +39 -35
  9. package/dist/components/CopyButton.svelte +5 -2
  10. package/dist/components/FloatingActionBar.svelte +55 -1
  11. package/dist/components/FloatingActionBar.svelte.d.ts +8 -1
  12. package/dist/components/IconSelector.svelte +417 -0
  13. package/dist/components/IconSelector.svelte.d.ts +55 -0
  14. package/dist/components/InfiniteGrid.svelte +305 -0
  15. package/dist/components/InfiniteGrid.svelte.d.ts +71 -0
  16. package/dist/components/InfiniteSearchGrid.svelte +131 -0
  17. package/dist/components/InfiniteSearchGrid.svelte.d.ts +67 -0
  18. package/dist/components/InputDialog.svelte +49 -38
  19. package/dist/components/KeyboardShortcutsOverlay.svelte +94 -3
  20. package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +61 -16
  21. package/dist/components/MediaCaptureIcon.svelte +23 -0
  22. package/dist/components/MediaCaptureIcon.svelte.d.ts +1 -1
  23. package/dist/components/ThumbnailGrid.svelte +137 -0
  24. package/dist/components/ThumbnailGrid.svelte.d.ts +42 -0
  25. package/dist/components/batch/BatchJobTable.svelte +549 -0
  26. package/dist/components/batch/BatchJobTable.svelte.d.ts +114 -0
  27. package/dist/components/batch/QuotaMeter.svelte +78 -0
  28. package/dist/components/batch/QuotaMeter.svelte.d.ts +16 -0
  29. package/dist/components/batch/batchQueue.d.ts +56 -0
  30. package/dist/components/batch/batchQueue.js +81 -0
  31. package/dist/components/batch/elapsedEstimate.d.ts +24 -0
  32. package/dist/components/batch/elapsedEstimate.js +25 -0
  33. package/dist/components/batch/pollOperation.d.ts +45 -0
  34. package/dist/components/batch/pollOperation.js +80 -0
  35. package/dist/components/batch/useElapsedEstimate.d.ts +10 -0
  36. package/dist/components/batch/useElapsedEstimate.js +38 -0
  37. package/dist/components/bulkActionBarKeys.d.ts +40 -0
  38. package/dist/components/bulkActionBarKeys.js +38 -0
  39. package/dist/components/infiniteGridLoader.d.ts +70 -0
  40. package/dist/components/infiniteGridLoader.js +141 -0
  41. package/dist/components/linked-columns/LinkedColumns.svelte +10 -22
  42. package/dist/components/linked-columns/LinkedColumns.svelte.d.ts +1 -9
  43. package/dist/components/linked-columns/types.d.ts +9 -0
  44. package/dist/components/linked-columns/types.js +1 -0
  45. package/dist/components/replay/ChapterTimeline.svelte +43 -25
  46. package/dist/components/replay/ChapterTimeline.svelte.d.ts +26 -1
  47. package/dist/components/table/columnResize.d.ts +38 -0
  48. package/dist/components/table/columnResize.js +117 -0
  49. package/dist/components/table/index.d.ts +2 -0
  50. package/dist/components/table/index.js +1 -0
  51. package/dist/components/toast/ToastItem.svelte +47 -2
  52. package/dist/components/variant/VariantMatrixPicker.svelte +181 -0
  53. package/dist/components/variant/VariantMatrixPicker.svelte.d.ts +37 -0
  54. package/dist/components/variant/variantCombinations.d.ts +75 -0
  55. package/dist/components/variant/variantCombinations.js +100 -0
  56. package/dist/index.d.ts +34 -3
  57. package/dist/index.js +30 -1
  58. package/dist/stores/toast.d.ts +8 -0
  59. package/dist/stores/toast.js +2 -0
  60. package/dist/styles/motion.css +100 -0
  61. package/dist/utils/audioRecorder.d.ts +28 -0
  62. package/dist/utils/audioRecorder.js +58 -0
  63. package/dist/utils/audioRecorderCore.d.ts +132 -0
  64. package/dist/utils/audioRecorderCore.js +447 -0
  65. package/dist/utils/clipboard.d.ts +25 -0
  66. package/dist/utils/clipboard.js +59 -0
  67. package/dist/utils/dateFormatters.d.ts +11 -2
  68. package/dist/utils/dateFormatters.js +22 -2
  69. package/dist/utils/keyboardShortcutRegistry.d.ts +152 -0
  70. package/dist/utils/keyboardShortcutRegistry.js +365 -0
  71. package/package.json +1 -1
@@ -151,10 +151,15 @@
151
151
  {/if}
152
152
 
153
153
  <style>
154
+ /* Theme tokens (jatui-is2ow.3): every colour derives from var(--color-*) —
155
+ relative-color `oklch(from var(--x) l c h / a)` keeps the token's hue/chroma
156
+ and only swaps alpha, so light and dark themes both render correctly.
157
+ --n-00 (pure black) is a jat-IDE neutral; the inline fallback keeps
158
+ standalone consumers (no app.css) rendering the same value. */
154
159
  .cd-scrim {
155
160
  position: fixed;
156
161
  inset: 0;
157
- background: oklch(0 0 0 / 0.45);
162
+ background: oklch(from var(--n-00, oklch(0 0 0)) l c h / 0.45);
158
163
  z-index: 9990;
159
164
  backdrop-filter: blur(2px);
160
165
  }
@@ -175,21 +180,21 @@
175
180
  flex-direction: column;
176
181
  gap: 0.625rem;
177
182
  padding: 1rem 1.125rem 0.625rem;
178
- background: color-mix(in oklch, var(--color-base-100) 98%, transparent);
179
- border: 1px solid var(--color-base-300);
183
+ background: oklch(from var(--color-base-200) l c h / 98%);
184
+ border: 1px solid var(--color-base-100);
180
185
  border-radius: 0.875rem;
181
186
  box-shadow:
182
- 0 20px 60px oklch(0 0 0 / 0.55),
183
- 0 0 0 1px color-mix(in oklch, var(--color-base-300) 50%, transparent);
187
+ 0 20px 60px oklch(from var(--n-00, oklch(0 0 0)) l c h / 0.55),
188
+ 0 0 0 1px oklch(from var(--color-base-100) l c h / 50%);
184
189
  backdrop-filter: blur(16px);
185
190
  }
186
191
 
187
192
  .cd-box.cd-danger {
188
- border-color: color-mix(in oklch, var(--color-error) 55%, transparent);
193
+ border-color: oklch(from var(--color-error) l c h / 55%);
189
194
  box-shadow:
190
- 0 20px 60px oklch(0 0 0 / 0.6),
191
- 0 0 0 1px color-mix(in oklch, var(--color-error) 25%, transparent),
192
- 0 0 30px color-mix(in oklch, var(--color-error) 12%, transparent);
195
+ 0 20px 60px oklch(from var(--n-00, oklch(0 0 0)) l c h / 0.6),
196
+ 0 0 0 1px oklch(from var(--color-error) l c h / 25%),
197
+ 0 0 30px oklch(from var(--color-error) l c h / 12%);
193
198
  }
194
199
 
195
200
  .cd-header {
@@ -206,7 +211,7 @@
206
211
  display: flex;
207
212
  align-items: center;
208
213
  justify-content: center;
209
- background: color-mix(in oklch, var(--color-info) 35%, transparent);
214
+ background: color-mix(in oklch, var(--color-info) 20%, transparent);
210
215
  border: 1px solid color-mix(in oklch, var(--color-info) 40%, transparent);
211
216
  color: var(--color-info);
212
217
  }
@@ -217,9 +222,9 @@
217
222
  }
218
223
 
219
224
  .cd-icon.cd-icon-danger {
220
- background: color-mix(in oklch, var(--color-error) 35%, transparent);
221
- border-color: color-mix(in oklch, var(--color-error) 40%, transparent);
222
- color: var(--color-error-content);
225
+ background: color-mix(in oklch, var(--color-error) 20%, transparent);
226
+ border-color: oklch(from var(--color-error) l c h / 40%);
227
+ color: var(--color-error);
223
228
  }
224
229
 
225
230
  .cd-title-group {
@@ -241,13 +246,12 @@
241
246
  .cd-body {
242
247
  margin: 0;
243
248
  font-size: 0.8rem;
244
- color: color-mix(in oklch, var(--color-base-content) 65%, transparent);
249
+ color: oklch(from var(--color-base-content) l c h / 70%);
245
250
  line-height: 1.5;
246
251
  }
247
252
 
248
253
  .cd-actions {
249
254
  display: flex;
250
- justify-content: flex-end;
251
255
  align-items: center;
252
256
  gap: 0.375rem;
253
257
  padding-top: 0.125rem;
@@ -263,15 +267,15 @@
263
267
  border-radius: 0.5rem;
264
268
  border: 1px solid transparent;
265
269
  background: transparent;
266
- color: color-mix(in oklch, var(--color-base-content) 85%, transparent);
270
+ color: oklch(from var(--color-base-content) l c h / 95%);
267
271
  cursor: pointer;
268
272
  transition: background 0.1s ease, border-color 0.1s ease, color 0.1s ease;
269
273
  white-space: nowrap;
270
274
  }
271
275
 
272
276
  .cd-btn:hover {
273
- background: var(--color-base-200);
274
- border-color: var(--color-base-300);
277
+ background: var(--color-base-100);
278
+ border-color: var(--color-base-100);
275
279
  }
276
280
 
277
281
  .cd-btn-cancel:focus-visible {
@@ -280,25 +284,25 @@
280
284
  }
281
285
 
282
286
  .cd-btn-confirm {
283
- background: color-mix(in oklch, var(--color-base-300) 60%, transparent);
284
- border-color: var(--color-base-300);
287
+ background: oklch(from var(--color-base-100) l c h / 60%);
288
+ border-color: color-mix(in oklch, var(--color-primary) 40%, transparent);
285
289
  }
286
290
 
287
291
  .cd-btn-confirm:hover {
288
- background: var(--color-base-200);
289
- border-color: color-mix(in oklch, var(--color-base-content) 30%, transparent);
292
+ background: color-mix(in oklch, var(--color-primary) 20%, var(--color-base-200));
293
+ border-color: color-mix(in oklch, var(--color-primary) 50%, transparent);
290
294
  }
291
295
 
292
296
  .cd-btn-danger {
293
- background: color-mix(in oklch, var(--color-error) 45%, transparent);
294
- border-color: color-mix(in oklch, var(--color-error) 55%, transparent);
295
- color: var(--color-error-content);
297
+ background: color-mix(in oklch, var(--color-error) 30%, transparent);
298
+ border-color: oklch(from var(--color-error) l c h / 55%);
299
+ color: oklch(from var(--color-error) calc(l + 0.2) c h);
296
300
  }
297
301
 
298
302
  .cd-btn-danger:hover {
299
- background: color-mix(in oklch, var(--color-error) 65%, transparent);
300
- border-color: color-mix(in oklch, var(--color-error) 70%, transparent);
301
- color: var(--color-error-content);
303
+ background: color-mix(in oklch, var(--color-error) 45%, transparent);
304
+ border-color: oklch(from var(--color-error) l c h / 70%);
305
+ color: oklch(from var(--color-error) calc(l + 0.25) c h);
302
306
  }
303
307
 
304
308
  .cd-btn-confirm:focus-visible,
@@ -311,11 +315,11 @@
311
315
  font-family: ui-monospace, monospace;
312
316
  font-size: 0.65rem;
313
317
  padding: 0.0625rem 0.3125rem;
314
- background: var(--color-base-200);
315
- border: 1px solid var(--color-base-300);
318
+ background: var(--color-base-100);
319
+ border: 1px solid var(--color-base-100);
316
320
  border-bottom-width: 2px;
317
321
  border-radius: 0.25rem;
318
- color: color-mix(in oklch, var(--color-base-content) 70%, transparent);
322
+ color: oklch(from var(--color-base-content) l c h / 85%);
319
323
  line-height: 1.6;
320
324
  }
321
325
 
@@ -323,7 +327,7 @@
323
327
  margin-left: auto;
324
328
  font-family: ui-monospace, monospace;
325
329
  font-size: 0.7rem;
326
- color: color-mix(in oklch, var(--color-base-content) 45%, transparent);
330
+ color: oklch(from var(--color-base-content) l c h / 55%);
327
331
  font-variant-numeric: tabular-nums;
328
332
  min-width: 2ch;
329
333
  text-align: right;
@@ -331,20 +335,20 @@
331
335
 
332
336
  .cd-progress {
333
337
  height: 2px;
334
- background: var(--color-base-200);
338
+ background: var(--color-base-100);
335
339
  border-radius: 999px;
336
340
  overflow: hidden;
337
341
  }
338
342
 
339
343
  .cd-progress-fill {
340
344
  height: 100%;
341
- background: linear-gradient(90deg, var(--color-info), var(--color-info));
345
+ background: linear-gradient(90deg, var(--color-primary), var(--color-info));
342
346
  border-radius: 999px;
343
347
  transition: width 60ms linear;
344
348
  }
345
349
 
346
350
  .cd-progress-fill.cd-progress-danger {
347
- background: linear-gradient(90deg, var(--color-error), var(--color-error));
351
+ background: linear-gradient(90deg, var(--color-error), var(--color-warning));
348
352
  }
349
353
 
350
354
  @media (prefers-reduced-motion: reduce) {
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import { copyText } from '../utils/clipboard';
3
+
2
4
  interface Props {
3
5
  text?: string;
4
6
  getText?: () => string | Promise<string>;
@@ -42,14 +44,15 @@
42
44
  if (copied) return;
43
45
  try {
44
46
  const value = getText ? await getText() : (text ?? '');
45
- await navigator.clipboard.writeText(value);
47
+ const ok = await copyText(value);
48
+ if (!ok) return;
46
49
  copied = true;
47
50
  clearTimeout(timer);
48
51
  timer = setTimeout(() => {
49
52
  copied = false;
50
53
  }, 1400);
51
54
  } catch {
52
- // clipboard not available — no-op
55
+ // clipboard unavailable — no-op
53
56
  }
54
57
  }
55
58
  </script>
@@ -11,6 +11,11 @@
11
11
  * loading={bulkActionLoading}
12
12
  * onClear={clearSelection}
13
13
  * />
14
+ *
15
+ * Per-action `key` renders a <kbd> hotkey hint on the button (advisory —
16
+ * the matching keydown handling lives in the host/BulkActionBar). While
17
+ * `loading` with a `loadingMessage` set, the buttons are replaced by a
18
+ * spinner + message row (matching the IDE's fork, audit jat-1alfp #15).
14
19
  */
15
20
 
16
21
  import { fade } from 'svelte/transition';
@@ -24,6 +29,8 @@
24
29
  icon?: string;
25
30
  /** Color variant */
26
31
  variant?: 'default' | 'primary' | 'danger' | 'warning' | 'success';
32
+ /** Single-key hotkey hint rendered as a <kbd> chip (advisory only). */
33
+ key?: string;
27
34
  /** Called when clicked */
28
35
  onclick: () => void;
29
36
  }
@@ -59,8 +66,13 @@
59
66
  actions?: ActionButton[];
60
67
  /** Dropdown menus (open upward from the bar) */
61
68
  dropdowns?: ActionDropdown[];
62
- /** Disables all buttons while true and shows a spinner */
69
+ /** While loading, disables all buttons. With `loadingMessage` also set,
70
+ * the buttons are replaced by a spinner + message row. */
63
71
  loading?: boolean;
72
+ /** When set, replaces the action buttons with a spinner + this message
73
+ * (role="status", aria-live="polite") — matching the IDE's fork, where
74
+ * this alone signals the in-flight state. */
75
+ loadingMessage?: string | null;
64
76
  /** Called when the "Clear" button is clicked */
65
77
  onClear?: () => void;
66
78
  /** Label for the selection count (default: "selected") */
@@ -72,6 +84,7 @@
72
84
  actions = [],
73
85
  dropdowns = [],
74
86
  loading = false,
87
+ loadingMessage = null,
75
88
  onClear,
76
89
  countLabel = 'selected',
77
90
  }: Props = $props();
@@ -110,6 +123,12 @@
110
123
  <div class="fab" transition:fade={{ duration: 150 }}>
111
124
  <span class="fab-count">{count} {countLabel}</span>
112
125
 
126
+ {#if loadingMessage}
127
+ <div class="fab-loading" role="status" aria-live="polite">
128
+ <span class="fab-spinner"></span>
129
+ <span class="fab-loading-msg">{loadingMessage}</span>
130
+ </div>
131
+ {:else}
113
132
  {#if actions.length > 0}
114
133
  <div class="fab-divider"></div>
115
134
  {#each actions as action}
@@ -118,6 +137,7 @@
118
137
  class="fab-btn {variantClass(action.variant)}"
119
138
  onclick={action.onclick}
120
139
  disabled={loading}
140
+ title={action.key ? `${action.label} (${action.key})` : undefined}
121
141
  >
122
142
  {#if action.icon}
123
143
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14">
@@ -126,6 +146,9 @@
126
146
  </svg>
127
147
  {/if}
128
148
  {action.label}
149
+ {#if action.key}
150
+ <kbd class="fab-key">{action.key}</kbd>
151
+ {/if}
129
152
  </button>
130
153
  {/each}
131
154
  {/if}
@@ -175,6 +198,7 @@
175
198
  </div>
176
199
  {/each}
177
200
  {/if}
201
+ {/if}
178
202
 
179
203
  {#if onClear}
180
204
  <div class="fab-divider"></div>
@@ -310,6 +334,36 @@
310
334
  to { transform: rotate(360deg); }
311
335
  }
312
336
 
337
+ .fab-key {
338
+ display: inline-flex;
339
+ align-items: center;
340
+ justify-content: center;
341
+ min-width: 1.125rem;
342
+ height: 1.125rem;
343
+ padding: 0 0.25rem;
344
+ background: var(--color-base-100);
345
+ border: 1px solid oklch(from var(--color-base-content) l c h / 35%);
346
+ border-radius: 0.25rem;
347
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
348
+ font-size: 0.6875rem;
349
+ font-weight: 600;
350
+ line-height: 1;
351
+ color: oklch(from var(--color-base-content) l c h / 80%);
352
+ }
353
+
354
+ .fab-loading {
355
+ display: flex;
356
+ align-items: center;
357
+ gap: 0.5rem;
358
+ padding: 0.125rem 0.375rem;
359
+ }
360
+
361
+ .fab-loading-msg {
362
+ font-size: 0.8125rem;
363
+ color: var(--color-primary);
364
+ white-space: nowrap;
365
+ }
366
+
313
367
  /* Dropdown */
314
368
  .fab-dropdown-wrapper {
315
369
  position: relative;
@@ -7,6 +7,8 @@ export interface ActionButton {
7
7
  icon?: string;
8
8
  /** Color variant */
9
9
  variant?: 'default' | 'primary' | 'danger' | 'warning' | 'success';
10
+ /** Single-key hotkey hint rendered as a <kbd> chip (advisory only). */
11
+ key?: string;
10
12
  /** Called when clicked */
11
13
  onclick: () => void;
12
14
  }
@@ -39,8 +41,13 @@ interface Props {
39
41
  actions?: ActionButton[];
40
42
  /** Dropdown menus (open upward from the bar) */
41
43
  dropdowns?: ActionDropdown[];
42
- /** Disables all buttons while true and shows a spinner */
44
+ /** While loading, disables all buttons. With `loadingMessage` also set,
45
+ * the buttons are replaced by a spinner + message row. */
43
46
  loading?: boolean;
47
+ /** When set, replaces the action buttons with a spinner + this message
48
+ * (role="status", aria-live="polite") — matching the IDE's fork, where
49
+ * this alone signals the in-flight state. */
50
+ loadingMessage?: string | null;
44
51
  /** Called when the "Clear" button is clicked */
45
52
  onClear?: () => void;
46
53
  /** Label for the selection count (default: "selected") */