@joewinke/jatui 0.23.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.
- package/CLAUDE.md +32 -2
- package/dist/components/AudioRecorder.svelte +84 -304
- package/dist/components/AudioRecorder.svelte.d.ts +1 -7
- package/dist/components/BulkActionBar.svelte +40 -0
- package/dist/components/BulkActionBar.svelte.d.ts +9 -0
- package/dist/components/ConfirmDialog.svelte +39 -35
- package/dist/components/CopyButton.svelte +5 -2
- package/dist/components/FloatingActionBar.svelte +55 -1
- package/dist/components/FloatingActionBar.svelte.d.ts +8 -1
- package/dist/components/InputDialog.svelte +49 -38
- package/dist/components/KeyboardShortcutsOverlay.svelte +54 -0
- package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +30 -0
- package/dist/components/bulkActionBarKeys.d.ts +40 -0
- package/dist/components/bulkActionBarKeys.js +38 -0
- package/dist/components/linked-columns/LinkedColumns.svelte +10 -22
- package/dist/components/linked-columns/LinkedColumns.svelte.d.ts +1 -9
- package/dist/components/linked-columns/types.d.ts +9 -0
- package/dist/components/linked-columns/types.js +1 -0
- package/dist/components/replay/ChapterTimeline.svelte +43 -25
- package/dist/components/replay/ChapterTimeline.svelte.d.ts +26 -1
- package/dist/components/toast/ToastItem.svelte +47 -2
- package/dist/index.d.ts +7 -4
- package/dist/index.js +5 -1
- package/dist/stores/toast.d.ts +8 -0
- package/dist/stores/toast.js +2 -0
- package/dist/utils/audioRecorder.d.ts +28 -0
- package/dist/utils/audioRecorder.js +58 -0
- package/dist/utils/audioRecorderCore.d.ts +132 -0
- package/dist/utils/audioRecorderCore.js +447 -0
- package/dist/utils/clipboard.d.ts +25 -0
- package/dist/utils/clipboard.js +59 -0
- package/dist/utils/dateFormatters.d.ts +11 -2
- package/dist/utils/dateFormatters.js +22 -2
- package/package.json +1 -1
|
@@ -3,6 +3,11 @@ export interface BulkAction {
|
|
|
3
3
|
label: string;
|
|
4
4
|
/** Optional SVG path string for a 24×24 icon. */
|
|
5
5
|
icon?: string;
|
|
6
|
+
/** Optional single-key hotkey (e.g. `'c'`) — fires `onSelect` while the
|
|
7
|
+
* bar is visible and not loading. Ignored while typing (isTypingTarget),
|
|
8
|
+
* for modifier combos and for non-printable keys. Rendered as a <kbd>
|
|
9
|
+
* hint on the button. */
|
|
10
|
+
key?: string;
|
|
6
11
|
/** Called when the action is clicked. */
|
|
7
12
|
onSelect: () => void;
|
|
8
13
|
/** Renders in the danger variant. Default false. */
|
|
@@ -37,6 +42,10 @@ interface Props {
|
|
|
37
42
|
onClear?: () => void;
|
|
38
43
|
/** Loading state — disables all actions and shows a spinner. */
|
|
39
44
|
loading?: boolean;
|
|
45
|
+
/** When set, replaces the action buttons with a spinner + this message
|
|
46
|
+
* (role="status", aria-live="polite") — the "bulk action in flight"
|
|
47
|
+
* presentation from the IDE's fork. */
|
|
48
|
+
loadingMessage?: string | null;
|
|
40
49
|
/** Label for the count. Default "selected". */
|
|
41
50
|
countLabel?: string;
|
|
42
51
|
}
|
|
@@ -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:
|
|
179
|
-
border: 1px solid var(--color-base-
|
|
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
|
|
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:
|
|
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
|
|
192
|
-
0 0 30px
|
|
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)
|
|
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)
|
|
221
|
-
border-color:
|
|
222
|
-
color: var(--color-error
|
|
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:
|
|
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:
|
|
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-
|
|
274
|
-
border-color: var(--color-base-
|
|
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:
|
|
284
|
-
border-color: var(--color-
|
|
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-
|
|
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)
|
|
294
|
-
border-color:
|
|
295
|
-
color: var(--color-error
|
|
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)
|
|
300
|
-
border-color:
|
|
301
|
-
color: var(--color-error
|
|
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-
|
|
315
|
-
border: 1px solid var(--color-base-
|
|
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:
|
|
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:
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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") */
|
|
@@ -23,7 +23,10 @@
|
|
|
23
23
|
function handleSubmit() {
|
|
24
24
|
if (opts?.validate) {
|
|
25
25
|
const msg = opts.validate(value);
|
|
26
|
-
if (msg !== null) {
|
|
26
|
+
if (msg !== null) {
|
|
27
|
+
error = msg;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
27
30
|
}
|
|
28
31
|
error = null;
|
|
29
32
|
submitInput(value);
|
|
@@ -80,7 +83,6 @@
|
|
|
80
83
|
<input
|
|
81
84
|
bind:this={inputRef}
|
|
82
85
|
bind:value
|
|
83
|
-
oninput={() => { if (error) error = null; }}
|
|
84
86
|
type="text"
|
|
85
87
|
class="id-input"
|
|
86
88
|
class:id-input-error={error !== null}
|
|
@@ -88,7 +90,10 @@
|
|
|
88
90
|
maxlength={opts.maxLength}
|
|
89
91
|
autocomplete="off"
|
|
90
92
|
spellcheck="false"
|
|
93
|
+
oninput={() => { if (error) error = null; }}
|
|
91
94
|
/>
|
|
95
|
+
|
|
96
|
+
<!-- Error / char count -->
|
|
92
97
|
{#if error !== null || opts.maxLength}
|
|
93
98
|
<div class="id-field-footer">
|
|
94
99
|
{#if error !== null}
|
|
@@ -127,10 +132,15 @@
|
|
|
127
132
|
{/if}
|
|
128
133
|
|
|
129
134
|
<style>
|
|
135
|
+
/* Theme tokens (jatui-is2ow.3): every colour derives from var(--color-*) —
|
|
136
|
+
relative-color `oklch(from var(--x) l c h / a)` keeps the token's hue/chroma
|
|
137
|
+
and only swaps alpha, so light and dark themes both render correctly.
|
|
138
|
+
--n-00 (pure black) is a jat-IDE neutral; the inline fallback keeps
|
|
139
|
+
standalone consumers (no app.css) rendering the same value. */
|
|
130
140
|
.id-scrim {
|
|
131
141
|
position: fixed;
|
|
132
142
|
inset: 0;
|
|
133
|
-
background: oklch(0 0 0 / 0.45);
|
|
143
|
+
background: oklch(from var(--n-00, oklch(0 0 0)) l c h / 0.45);
|
|
134
144
|
z-index: 9991;
|
|
135
145
|
backdrop-filter: blur(2px);
|
|
136
146
|
}
|
|
@@ -151,12 +161,12 @@
|
|
|
151
161
|
flex-direction: column;
|
|
152
162
|
gap: 0.625rem;
|
|
153
163
|
padding: 1rem 1.125rem 0.625rem;
|
|
154
|
-
background: oklch(
|
|
155
|
-
border: 1px solid
|
|
164
|
+
background: oklch(from var(--color-base-200) l c h / 98%);
|
|
165
|
+
border: 1px solid var(--color-base-100);
|
|
156
166
|
border-radius: 0.875rem;
|
|
157
167
|
box-shadow:
|
|
158
|
-
0 20px 60px oklch(0 0 0 / 0.55),
|
|
159
|
-
0 0 0 1px oklch(
|
|
168
|
+
0 20px 60px oklch(from var(--n-00, oklch(0 0 0)) l c h / 0.55),
|
|
169
|
+
0 0 0 1px oklch(from var(--color-base-100) l c h / 50%);
|
|
160
170
|
backdrop-filter: blur(16px);
|
|
161
171
|
}
|
|
162
172
|
|
|
@@ -174,9 +184,9 @@
|
|
|
174
184
|
display: flex;
|
|
175
185
|
align-items: center;
|
|
176
186
|
justify-content: center;
|
|
177
|
-
background:
|
|
178
|
-
border: 1px solid
|
|
179
|
-
color:
|
|
187
|
+
background: color-mix(in oklch, var(--color-info) 20%, transparent);
|
|
188
|
+
border: 1px solid color-mix(in oklch, var(--color-info) 30%, transparent);
|
|
189
|
+
color: var(--color-info);
|
|
180
190
|
}
|
|
181
191
|
|
|
182
192
|
.id-icon svg {
|
|
@@ -196,14 +206,14 @@
|
|
|
196
206
|
margin: 0;
|
|
197
207
|
font-size: 0.9375rem;
|
|
198
208
|
font-weight: 600;
|
|
199
|
-
color:
|
|
209
|
+
color: var(--color-base-content);
|
|
200
210
|
line-height: 1.3;
|
|
201
211
|
}
|
|
202
212
|
|
|
203
213
|
.id-body {
|
|
204
214
|
margin: 0;
|
|
205
215
|
font-size: 0.8rem;
|
|
206
|
-
color: oklch(
|
|
216
|
+
color: oklch(from var(--color-base-content) l c h / 70%);
|
|
207
217
|
line-height: 1.5;
|
|
208
218
|
}
|
|
209
219
|
|
|
@@ -211,51 +221,52 @@
|
|
|
211
221
|
width: 100%;
|
|
212
222
|
padding: 0.4375rem 0.75rem;
|
|
213
223
|
font-size: 0.875rem;
|
|
214
|
-
background:
|
|
215
|
-
border: 1px solid
|
|
224
|
+
background: var(--color-base-300);
|
|
225
|
+
border: 1px solid var(--color-base-100);
|
|
216
226
|
border-radius: 0.5rem;
|
|
217
|
-
color:
|
|
227
|
+
color: var(--color-base-content);
|
|
218
228
|
outline: none;
|
|
219
229
|
transition: border-color 0.1s ease;
|
|
220
230
|
box-sizing: border-box;
|
|
221
231
|
}
|
|
222
232
|
|
|
223
233
|
.id-input::placeholder {
|
|
224
|
-
color: oklch(
|
|
234
|
+
color: oklch(from var(--color-base-content) l c h / 50%);
|
|
225
235
|
}
|
|
226
236
|
|
|
227
237
|
.id-input:focus {
|
|
228
|
-
border-color:
|
|
229
|
-
box-shadow: 0 0 0 2px oklch(
|
|
238
|
+
border-color: var(--color-primary);
|
|
239
|
+
box-shadow: 0 0 0 2px oklch(from var(--color-primary) l c h / 20%);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.id-input-error {
|
|
243
|
+
border-color: var(--color-error);
|
|
230
244
|
}
|
|
231
245
|
|
|
232
|
-
.id-input-error,
|
|
233
246
|
.id-input-error:focus {
|
|
234
|
-
border-color:
|
|
235
|
-
box-shadow: 0 0 0 2px oklch(
|
|
247
|
+
border-color: var(--color-error);
|
|
248
|
+
box-shadow: 0 0 0 2px oklch(from var(--color-error) l c h / 20%);
|
|
236
249
|
}
|
|
237
250
|
|
|
238
251
|
.id-field-footer {
|
|
239
252
|
display: flex;
|
|
240
|
-
align-items: center;
|
|
241
253
|
justify-content: space-between;
|
|
254
|
+
align-items: flex-start;
|
|
242
255
|
gap: 0.5rem;
|
|
243
256
|
margin-top: -0.125rem;
|
|
244
|
-
min-height: 1rem;
|
|
245
257
|
}
|
|
246
258
|
|
|
247
259
|
.id-error {
|
|
248
260
|
margin: 0;
|
|
249
261
|
font-size: 0.75rem;
|
|
250
|
-
color:
|
|
262
|
+
color: var(--color-error);
|
|
251
263
|
line-height: 1.4;
|
|
252
264
|
}
|
|
253
265
|
|
|
254
266
|
.id-char-count {
|
|
255
267
|
margin-left: auto;
|
|
256
268
|
font-size: 0.75rem;
|
|
257
|
-
color: oklch(
|
|
258
|
-
font-variant-numeric: tabular-nums;
|
|
269
|
+
color: oklch(from var(--color-base-content) l c h / 55%);
|
|
259
270
|
white-space: nowrap;
|
|
260
271
|
flex-shrink: 0;
|
|
261
272
|
}
|
|
@@ -277,34 +288,34 @@
|
|
|
277
288
|
border-radius: 0.5rem;
|
|
278
289
|
border: 1px solid transparent;
|
|
279
290
|
background: transparent;
|
|
280
|
-
color: oklch(
|
|
291
|
+
color: oklch(from var(--color-base-content) l c h / 95%);
|
|
281
292
|
cursor: pointer;
|
|
282
293
|
transition: background 0.1s ease, border-color 0.1s ease, color 0.1s ease;
|
|
283
294
|
white-space: nowrap;
|
|
284
295
|
}
|
|
285
296
|
|
|
286
297
|
.id-btn:hover {
|
|
287
|
-
background:
|
|
288
|
-
border-color:
|
|
298
|
+
background: var(--color-base-100);
|
|
299
|
+
border-color: var(--color-base-100);
|
|
289
300
|
}
|
|
290
301
|
|
|
291
302
|
.id-btn-cancel:focus-visible {
|
|
292
|
-
outline: 2px solid
|
|
303
|
+
outline: 2px solid var(--color-primary);
|
|
293
304
|
outline-offset: 2px;
|
|
294
305
|
}
|
|
295
306
|
|
|
296
307
|
.id-btn-confirm {
|
|
297
|
-
background: oklch(
|
|
298
|
-
border-color: oklch(
|
|
308
|
+
background: oklch(from var(--color-base-100) l c h / 60%);
|
|
309
|
+
border-color: oklch(from var(--color-base-content) l c h / 25%);
|
|
299
310
|
}
|
|
300
311
|
|
|
301
312
|
.id-btn-confirm:hover {
|
|
302
|
-
background: oklch(
|
|
303
|
-
border-color: oklch(
|
|
313
|
+
background: color-mix(in oklch, var(--color-primary) 20%, var(--color-base-300));
|
|
314
|
+
border-color: color-mix(in oklch, var(--color-primary) 40%, transparent);
|
|
304
315
|
}
|
|
305
316
|
|
|
306
317
|
.id-btn-confirm:focus-visible {
|
|
307
|
-
outline: 2px solid
|
|
318
|
+
outline: 2px solid var(--color-primary);
|
|
308
319
|
outline-offset: 2px;
|
|
309
320
|
}
|
|
310
321
|
|
|
@@ -312,11 +323,11 @@
|
|
|
312
323
|
font-family: ui-monospace, monospace;
|
|
313
324
|
font-size: 0.65rem;
|
|
314
325
|
padding: 0.0625rem 0.3125rem;
|
|
315
|
-
background:
|
|
316
|
-
border: 1px solid
|
|
326
|
+
background: var(--color-base-100);
|
|
327
|
+
border: 1px solid var(--color-base-100);
|
|
317
328
|
border-bottom-width: 2px;
|
|
318
329
|
border-radius: 0.25rem;
|
|
319
|
-
color: oklch(
|
|
330
|
+
color: oklch(from var(--color-base-content) l c h / 85%);
|
|
320
331
|
line-height: 1.6;
|
|
321
332
|
}
|
|
322
333
|
|
|
@@ -24,7 +24,23 @@
|
|
|
24
24
|
*
|
|
25
25
|
* Precedence: `sections` > `registry` > `shortcuts`. The overlay owns `?` —
|
|
26
26
|
* don't register it in the registry too.
|
|
27
|
+
*
|
|
28
|
+
* App-specific sections (marks, list motions, cockpit panels…) that the
|
|
29
|
+
* overlay itself doesn't know about come in via `extraSections` — they
|
|
30
|
+
* render after the primary content, before the meta rows, in EVERY mode:
|
|
31
|
+
*
|
|
32
|
+
* <KeyboardShortcutsOverlay {registry} extraSections={[
|
|
33
|
+
* { title: 'Marks & jumps (Vim)', shortcuts: [{ key: "m{a-z}", description: 'Drop a mark' }] },
|
|
34
|
+
* ]} />
|
|
35
|
+
*
|
|
36
|
+
* For content the section renderer can't express, an `extra` snippet is
|
|
37
|
+
* rendered last (just above the meta rows):
|
|
38
|
+
*
|
|
39
|
+
* <KeyboardShortcutsOverlay {registry}>
|
|
40
|
+
* {#snippet extra()}<div>…custom footer…</div>{/snippet}
|
|
41
|
+
* </KeyboardShortcutsOverlay>
|
|
27
42
|
*/
|
|
43
|
+
import type { Snippet } from 'svelte';
|
|
28
44
|
import type { ShortcutRegistry } from '../utils/keyboardShortcutRegistry';
|
|
29
45
|
|
|
30
46
|
export interface KeyboardShortcut {
|
|
@@ -48,6 +64,8 @@
|
|
|
48
64
|
shortcuts = [],
|
|
49
65
|
sections,
|
|
50
66
|
registry,
|
|
67
|
+
extraSections,
|
|
68
|
+
extra,
|
|
51
69
|
title = 'Keyboard Shortcuts',
|
|
52
70
|
open = $bindable(false)
|
|
53
71
|
}: {
|
|
@@ -63,6 +81,20 @@
|
|
|
63
81
|
* the stack first). Ignored when `sections` is given.
|
|
64
82
|
*/
|
|
65
83
|
registry?: ShortcutRegistry;
|
|
84
|
+
/**
|
|
85
|
+
* Extra app-provided sections (e.g. marks, list motions, cockpit
|
|
86
|
+
* panels) appended after the primary content and before the meta
|
|
87
|
+
* rows, regardless of whether the primary content came from
|
|
88
|
+
* `sections`, `registry` or the flat `shortcuts` list. Same shape
|
|
89
|
+
* as `sections`, with per-key `color` tinting supported.
|
|
90
|
+
*/
|
|
91
|
+
extraSections?: ShortcutSection[];
|
|
92
|
+
/**
|
|
93
|
+
* Fully custom footer content, rendered after `extraSections` and
|
|
94
|
+
* just above the built-in meta rows. Use when the section renderer
|
|
95
|
+
* (title + key/description rows) can't express the markup.
|
|
96
|
+
*/
|
|
97
|
+
extra?: Snippet;
|
|
66
98
|
title?: string;
|
|
67
99
|
open?: boolean;
|
|
68
100
|
} = $props();
|
|
@@ -209,6 +241,28 @@
|
|
|
209
241
|
</div>
|
|
210
242
|
{/each}
|
|
211
243
|
{/if}
|
|
244
|
+
{#if extraSections}
|
|
245
|
+
{#each extraSections as section, sIdx (section.title + '::extra::' + sIdx)}
|
|
246
|
+
<div class="kso-section-title">{section.title}</div>
|
|
247
|
+
{#each section.shortcuts as { key, description, color }, i (section.title + '::extra::' + key + '::' + i)}
|
|
248
|
+
<div class="kso-row">
|
|
249
|
+
<kbd
|
|
250
|
+
class="kso-key"
|
|
251
|
+
class:kso-key-pressed={keyMatchesPressed(key, depressedKey)}
|
|
252
|
+
style={color
|
|
253
|
+
? `color:${color};border-color:color-mix(in oklch,${color} 50%,oklch(0.35 0.02 250));background:color-mix(in oklch,${color} 12%,oklch(0.25 0.02 250))`
|
|
254
|
+
: ''}
|
|
255
|
+
>
|
|
256
|
+
{key}
|
|
257
|
+
</kbd>
|
|
258
|
+
<span class="kso-description">{description}</span>
|
|
259
|
+
</div>
|
|
260
|
+
{/each}
|
|
261
|
+
{/each}
|
|
262
|
+
{/if}
|
|
263
|
+
{#if extra}
|
|
264
|
+
{@render extra()}
|
|
265
|
+
{/if}
|
|
212
266
|
<div class="kso-row kso-row-meta">
|
|
213
267
|
<kbd class="kso-key" class:kso-key-pressed={keyMatchesPressed('?', depressedKey)}>?</kbd>
|
|
214
268
|
<span class="kso-description">Toggle this overlay</span>
|