@motion-proto/live-tokens 0.3.9 → 0.6.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/README.md +47 -4
- package/package.json +18 -12
- package/src/component-editor/BadgeEditor.svelte +24 -22
- package/src/component-editor/CalloutEditor.svelte +3 -3
- package/src/component-editor/CardEditor.svelte +25 -21
- package/src/component-editor/CollapsibleSectionEditor.svelte +27 -25
- package/src/component-editor/CornerBadgeEditor.svelte +37 -35
- package/src/component-editor/DialogEditor.svelte +26 -24
- package/src/component-editor/ImageEditor.svelte +11 -9
- package/src/component-editor/InlineEditActionsEditor.svelte +17 -15
- package/src/component-editor/NotificationEditor.svelte +32 -30
- package/src/component-editor/ProgressBarEditor.svelte +3 -3
- package/src/component-editor/RadioButtonEditor.svelte +31 -29
- package/src/component-editor/SectionDividerEditor.svelte +30 -28
- package/src/component-editor/SegmentedControlEditor.svelte +29 -25
- package/src/component-editor/StandardButtonsEditor.svelte +42 -38
- package/src/component-editor/TabBarEditor.svelte +20 -18
- package/src/component-editor/TableEditor.svelte +4 -4
- package/src/component-editor/TooltipEditor.svelte +11 -9
- package/src/component-editor/registry.ts +2 -2
- package/src/component-editor/scaffolding/AngleDial.svelte +20 -19
- package/src/component-editor/scaffolding/ComponentEditorBase.svelte +44 -20
- package/src/component-editor/scaffolding/ComponentFileManager.svelte +262 -38
- package/src/component-editor/scaffolding/ComponentFileMenu.svelte +41 -29
- package/src/component-editor/scaffolding/ComponentsTab.svelte +7 -3
- package/src/component-editor/scaffolding/CopyFromMenu.svelte +21 -12
- package/src/component-editor/scaffolding/DemoHeader.svelte +13 -4
- package/src/component-editor/scaffolding/DividerEditor.svelte +27 -14
- package/src/component-editor/scaffolding/FieldsetWrapper.svelte +10 -4
- package/src/component-editor/scaffolding/GradientCard.svelte +25 -20
- package/src/component-editor/scaffolding/LinkageChart.svelte +43 -34
- package/src/component-editor/scaffolding/LinkedBlock.svelte +24 -21
- package/src/component-editor/scaffolding/NonStylableConfig.svelte +6 -1
- package/src/component-editor/scaffolding/SaveAsDialog.svelte +39 -35
- package/src/component-editor/scaffolding/ShadowBackdrop.svelte +21 -9
- package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +8 -3
- package/src/component-editor/scaffolding/StateBlock.svelte +30 -13
- package/src/component-editor/scaffolding/TokenLayout.svelte +46 -30
- package/src/component-editor/scaffolding/TypeEditor.svelte +52 -26
- package/src/component-editor/scaffolding/VariantGroup.svelte +81 -48
- package/src/component-editor/scaffolding/componentSectionType.ts +2 -2
- package/src/components/Badge.svelte +45 -26
- package/src/components/Button.svelte +44 -21
- package/src/components/Callout.svelte +17 -12
- package/src/components/Card.svelte +23 -11
- package/src/components/CollapsibleSection.svelte +56 -27
- package/src/components/CornerBadge.svelte +32 -18
- package/src/components/Dialog.svelte +55 -31
- package/src/components/Image.svelte +14 -5
- package/src/components/InlineEditActions.svelte +22 -10
- package/src/components/Notification.svelte +39 -19
- package/src/components/ProgressBar.svelte +27 -17
- package/src/components/RadioButton.svelte +27 -10
- package/src/components/SectionDivider.svelte +34 -26
- package/src/components/SegmentedControl.svelte +23 -9
- package/src/components/TabBar.svelte +23 -10
- package/src/components/Table.svelte +8 -3
- package/src/components/Tooltip.svelte +15 -5
- package/src/lib/ColumnsOverlay.svelte +3 -3
- package/src/lib/LiveEditorOverlay.svelte +57 -36
- package/src/pages/ComponentEditorPage.svelte +25 -14
- package/src/pages/Editor.svelte +8 -2
- package/src/pages/EditorShell.svelte +24 -20
- package/src/styles/site.css +138 -0
- package/src/styles/tokens.css +78 -76
- package/src/styles/ui-form-controls.css +186 -0
- package/src/ui/BezierCurveEditor.svelte +59 -43
- package/src/ui/ColorEditPanel.svelte +71 -44
- package/src/ui/EditorViewSwitcher.svelte +9 -5
- package/src/ui/FontStackEditor.svelte +17 -16
- package/src/ui/GradientEditor.svelte +42 -33
- package/src/ui/GradientStopPicker.svelte +18 -29
- package/src/ui/PaletteEditor.svelte +238 -212
- package/src/ui/PresetFileManager.svelte +20 -18
- package/src/ui/ProjectFontsSection.svelte +34 -34
- package/src/ui/SurfacesTab.svelte +3 -3
- package/src/ui/TextTab.svelte +2 -2
- package/src/ui/ThemeFileManager.svelte +38 -35
- package/src/ui/Toggle.svelte +11 -9
- package/src/ui/UICopyPopover.svelte +19 -15
- package/src/ui/UIDialog.svelte +48 -30
- package/src/ui/UIFontFamilySelector.svelte +104 -78
- package/src/ui/UIFontSizeSelector.svelte +38 -20
- package/src/ui/UIFontWeightSelector.svelte +33 -13
- package/src/ui/UILineHeightSelector.svelte +33 -13
- package/src/ui/UILinkToggle.svelte +7 -6
- package/src/ui/UIOptionItem.svelte +21 -7
- package/src/ui/UIOptionList.svelte +9 -3
- package/src/ui/UIPaddingSelector.svelte +108 -82
- package/src/ui/UIPaletteSelector.svelte +186 -161
- package/src/ui/UIRadio.svelte +23 -8
- package/src/ui/UIRadioGroup.svelte +9 -8
- package/src/ui/UIRelinkConfirmPopover.svelte +26 -16
- package/src/ui/UITokenSelector.svelte +112 -68
- package/src/ui/UIVariantSelector.svelte +79 -57
- package/src/ui/VariablesTab.svelte +15 -15
- package/src/ui/palette/GradientStopEditor.svelte +45 -26
- package/src/ui/palette/OverridesPanel.svelte +85 -49
- package/src/ui/palette/PaletteBase.svelte +60 -32
- package/src/ui/palette/ScaleCurveEditor.svelte +25 -10
- package/src/ui/sections/ColumnsSection.svelte +13 -13
- package/src/ui/sections/GradientsSection.svelte +12 -9
- package/src/ui/sections/OverlaysSection.svelte +50 -47
- package/src/ui/sections/ShadowsSection.svelte +110 -104
- package/src/ui/sections/TokenScaleTable.svelte +38 -22
- package/src/ui/sections/tokenScales.ts +2 -2
- package/src/styles/form-controls.css +0 -188
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { stopPropagation } from 'svelte/legacy';
|
|
3
|
+
|
|
2
4
|
import { onMount } from 'svelte';
|
|
3
5
|
import type { PresetMeta } from '../lib/themeTypes';
|
|
4
6
|
import {
|
|
@@ -12,17 +14,17 @@
|
|
|
12
14
|
import { dirty } from '../lib/editorStore';
|
|
13
15
|
import UIDialog from './UIDialog.svelte';
|
|
14
16
|
|
|
15
|
-
let files: PresetMeta[] = [];
|
|
16
|
-
let showFileList = false;
|
|
17
|
-
let saveAsEditing = false;
|
|
18
|
-
let saveAsName = '';
|
|
19
|
-
let saveAsInput: HTMLInputElement;
|
|
17
|
+
let files: PresetMeta[] = $state([]);
|
|
18
|
+
let showFileList = $state(false);
|
|
19
|
+
let saveAsEditing = $state(false);
|
|
20
|
+
let saveAsName = $state('');
|
|
21
|
+
let saveAsInput: HTMLInputElement | undefined = $state();
|
|
20
22
|
|
|
21
|
-
let activeFileName = 'default';
|
|
22
|
-
let currentDisplayName = 'Default';
|
|
23
|
+
let activeFileName = $state('default');
|
|
24
|
+
let currentDisplayName = $state('Default');
|
|
23
25
|
|
|
24
|
-
let saveStatus: 'idle' | 'saving' | 'saved' | 'error' = 'idle';
|
|
25
|
-
let applyStatus: 'idle' | 'applying' = 'idle';
|
|
26
|
+
let saveStatus: 'idle' | 'saving' | 'saved' | 'error' = $state('idle');
|
|
27
|
+
let applyStatus: 'idle' | 'applying' = $state('idle');
|
|
26
28
|
|
|
27
29
|
async function refreshFiles() {
|
|
28
30
|
try {
|
|
@@ -186,7 +188,7 @@
|
|
|
186
188
|
class:saving={saveStatus === 'saving'}
|
|
187
189
|
class:saved={saveStatus === 'saved'}
|
|
188
190
|
class:error={saveStatus === 'error'}
|
|
189
|
-
|
|
191
|
+
onclick={handleSave}
|
|
190
192
|
disabled={saveStatus === 'saving' || applyStatus === 'applying'}
|
|
191
193
|
title="Capture the current theme + component configs into this preset"
|
|
192
194
|
>
|
|
@@ -203,7 +205,7 @@
|
|
|
203
205
|
</button>
|
|
204
206
|
<button
|
|
205
207
|
class="pfm-btn increment-btn"
|
|
206
|
-
|
|
208
|
+
onclick={handleSaveIncrement}
|
|
207
209
|
disabled={saveStatus === 'saving' || applyStatus === 'applying'}
|
|
208
210
|
title="Save as incremented preset"
|
|
209
211
|
>
|
|
@@ -218,25 +220,25 @@
|
|
|
218
220
|
type="text"
|
|
219
221
|
bind:value={saveAsName}
|
|
220
222
|
bind:this={saveAsInput}
|
|
221
|
-
|
|
223
|
+
onkeydown={handleSaveAsKeydown}
|
|
222
224
|
placeholder="Preset name..."
|
|
223
225
|
/>
|
|
224
226
|
<div class="save-as-actions">
|
|
225
227
|
<button
|
|
226
228
|
class="inline-btn confirm-btn"
|
|
227
|
-
|
|
229
|
+
onclick={confirmSaveAs}
|
|
228
230
|
disabled={!saveAsName.trim()}
|
|
229
231
|
title="Save"
|
|
230
232
|
>
|
|
231
233
|
<i class="fas fa-check"></i>
|
|
232
234
|
</button>
|
|
233
|
-
<button class="inline-btn cancel-btn"
|
|
235
|
+
<button class="inline-btn cancel-btn" onclick={cancelSaveAs} title="Cancel">
|
|
234
236
|
<i class="fas fa-times"></i>
|
|
235
237
|
</button>
|
|
236
238
|
</div>
|
|
237
239
|
</div>
|
|
238
240
|
{:else}
|
|
239
|
-
<button class="pfm-btn"
|
|
241
|
+
<button class="pfm-btn" onclick={openSaveAs} title="Save as new preset">
|
|
240
242
|
<i class="fas fa-copy"></i>
|
|
241
243
|
<span>Save As</span>
|
|
242
244
|
</button>
|
|
@@ -245,7 +247,7 @@
|
|
|
245
247
|
<button
|
|
246
248
|
class="pfm-btn"
|
|
247
249
|
class:active={showFileList}
|
|
248
|
-
|
|
250
|
+
onclick={toggleFileList}
|
|
249
251
|
disabled={applyStatus === 'applying'}
|
|
250
252
|
title="Load a preset"
|
|
251
253
|
>
|
|
@@ -263,7 +265,7 @@
|
|
|
263
265
|
<div class="load-list">
|
|
264
266
|
{#each files as file}
|
|
265
267
|
<div class="load-item" class:active={file.fileName === activeFileName}>
|
|
266
|
-
<button class="load-name-btn"
|
|
268
|
+
<button class="load-name-btn" onclick={() => handleApply(file)}>
|
|
267
269
|
{file.name}
|
|
268
270
|
</button>
|
|
269
271
|
{#if file.fileName === activeFileName}
|
|
@@ -272,7 +274,7 @@
|
|
|
272
274
|
{#if file.fileName !== 'default'}
|
|
273
275
|
<button
|
|
274
276
|
class="file-delete-btn"
|
|
275
|
-
|
|
277
|
+
onclick={stopPropagation(() => handleDelete(file))}
|
|
276
278
|
title="Delete {file.name}"
|
|
277
279
|
>
|
|
278
280
|
<i class="fas fa-trash-alt"></i>
|
|
@@ -15,28 +15,28 @@
|
|
|
15
15
|
const GOOGLE_FONTS: GoogleFontEntry[] = (googleFontsData as any).fonts;
|
|
16
16
|
|
|
17
17
|
type AddMode = 'closed' | 'google' | 'url' | 'fontface';
|
|
18
|
-
let addMode: AddMode = 'closed';
|
|
18
|
+
let addMode: AddMode = $state('closed');
|
|
19
19
|
|
|
20
20
|
// Google search
|
|
21
|
-
let googleQuery = '';
|
|
22
|
-
|
|
21
|
+
let googleQuery = $state('');
|
|
22
|
+
let googleMatches = $derived(googleQuery.trim().length >= 1
|
|
23
23
|
? GOOGLE_FONTS
|
|
24
24
|
.filter((f) => f.family.toLowerCase().includes(googleQuery.toLowerCase()))
|
|
25
25
|
.slice(0, 20)
|
|
26
|
-
: GOOGLE_FONTS.slice(0, 10);
|
|
26
|
+
: GOOGLE_FONTS.slice(0, 10));
|
|
27
27
|
|
|
28
28
|
// URL paste
|
|
29
|
-
let urlInput = '';
|
|
30
|
-
let urlError = '';
|
|
31
|
-
let urlDiscovering = false;
|
|
32
|
-
let urlParsed: ParsedFamily[] | null = null;
|
|
33
|
-
let urlPickedNames = new Set<string>();
|
|
34
|
-
let urlNeedsManualFamilies = false;
|
|
35
|
-
let urlManualFamilies = '';
|
|
29
|
+
let urlInput = $state('');
|
|
30
|
+
let urlError = $state('');
|
|
31
|
+
let urlDiscovering = $state(false);
|
|
32
|
+
let urlParsed: ParsedFamily[] | null = $state(null);
|
|
33
|
+
let urlPickedNames = $state(new Set<string>());
|
|
34
|
+
let urlNeedsManualFamilies = $state(false);
|
|
35
|
+
let urlManualFamilies = $state('');
|
|
36
36
|
|
|
37
37
|
// @font-face paste
|
|
38
|
-
let fontFaceText = '';
|
|
39
|
-
let fontFaceParsed: ParsedFamily[] = [];
|
|
38
|
+
let fontFaceText = $state('');
|
|
39
|
+
let fontFaceParsed: ParsedFamily[] = $state([]);
|
|
40
40
|
|
|
41
41
|
function reset() {
|
|
42
42
|
addMode = 'closed';
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
fontFaceParsed = [];
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
let fontSourcesList = $derived($editorState.fonts.sources);
|
|
56
|
+
let fontStacksList = $derived($editorState.fonts.stacks);
|
|
57
57
|
|
|
58
58
|
function commitSources(next: FontSource[]) {
|
|
59
59
|
setFontSources(next);
|
|
@@ -161,7 +161,7 @@
|
|
|
161
161
|
.map((s) => s.variable);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
let expanded = new Set<string>();
|
|
164
|
+
let expanded = $state(new Set<string>());
|
|
165
165
|
function toggleExpanded(familyId: string) {
|
|
166
166
|
const next = new Set(expanded);
|
|
167
167
|
if (next.has(familyId)) next.delete(familyId); else next.add(familyId);
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
type="button"
|
|
201
201
|
class="pf-family-disclosure"
|
|
202
202
|
class:open={isOpen}
|
|
203
|
-
|
|
203
|
+
onclick={() => toggleExpanded(fam.id)}
|
|
204
204
|
aria-label={isOpen ? 'Collapse weights' : 'Expand weights'}
|
|
205
205
|
aria-expanded={isOpen}
|
|
206
206
|
><i class="fas fa-chevron-right" aria-hidden="true"></i></button>
|
|
@@ -212,7 +212,7 @@
|
|
|
212
212
|
<button
|
|
213
213
|
type="button"
|
|
214
214
|
class="pf-family-remove"
|
|
215
|
-
|
|
215
|
+
onclick={() => removeFamily(source.id, fam.id)}
|
|
216
216
|
aria-label={`Remove ${fam.name}`}
|
|
217
217
|
title="Remove family"
|
|
218
218
|
><i class="fas fa-xmark" aria-hidden="true"></i></button>
|
|
@@ -239,20 +239,20 @@
|
|
|
239
239
|
</div>
|
|
240
240
|
|
|
241
241
|
{#if addMode === 'closed'}
|
|
242
|
-
<button type="button" class="pf-add-toggle"
|
|
242
|
+
<button type="button" class="pf-add-toggle" onclick={() => (addMode = 'google')}>+ add font</button>
|
|
243
243
|
{:else}
|
|
244
244
|
<div class="pf-add-panel">
|
|
245
245
|
<div class="pf-add-tabs">
|
|
246
|
-
<button type="button" class:active={addMode === 'google'}
|
|
247
|
-
<button type="button" class:active={addMode === 'url'}
|
|
248
|
-
<button type="button" class:active={addMode === 'fontface'}
|
|
249
|
-
<button type="button" class="pf-add-close"
|
|
246
|
+
<button type="button" class:active={addMode === 'google'} onclick={() => (addMode = 'google')}>Google search</button>
|
|
247
|
+
<button type="button" class:active={addMode === 'url'} onclick={() => (addMode = 'url')}>Paste URL</button>
|
|
248
|
+
<button type="button" class:active={addMode === 'fontface'} onclick={() => (addMode = 'fontface')}>@font-face</button>
|
|
249
|
+
<button type="button" class="pf-add-close" onclick={reset} aria-label="Cancel">×</button>
|
|
250
250
|
</div>
|
|
251
251
|
|
|
252
252
|
{#if addMode === 'google'}
|
|
253
253
|
<input
|
|
254
254
|
type="text"
|
|
255
|
-
class="form-input"
|
|
255
|
+
class="ui-form-input"
|
|
256
256
|
placeholder="Search Google Fonts (e.g. Inter)"
|
|
257
257
|
bind:value={googleQuery}
|
|
258
258
|
/>
|
|
@@ -261,7 +261,7 @@
|
|
|
261
261
|
<li class="pf-result">
|
|
262
262
|
<span class="pf-result-preview" style="font-family: '{m.family}', {m.category};">{m.family}</span>
|
|
263
263
|
<span class="pf-result-meta">{m.category} · {m.variants.length}w</span>
|
|
264
|
-
<button type="button" class="pf-result-add"
|
|
264
|
+
<button type="button" class="pf-result-add" onclick={() => addGoogleFont(m)}>add</button>
|
|
265
265
|
</li>
|
|
266
266
|
{/each}
|
|
267
267
|
{#if googleMatches.length === 0}
|
|
@@ -271,12 +271,12 @@
|
|
|
271
271
|
{:else if addMode === 'url'}
|
|
272
272
|
<input
|
|
273
273
|
type="text"
|
|
274
|
-
class="form-input"
|
|
274
|
+
class="ui-form-input"
|
|
275
275
|
placeholder="https://fonts.googleapis.com/css2?family=... or Typekit URL"
|
|
276
276
|
bind:value={urlInput}
|
|
277
277
|
/>
|
|
278
278
|
<div class="pf-row">
|
|
279
|
-
<button type="button" class="pf-btn"
|
|
279
|
+
<button type="button" class="pf-btn" onclick={discoverUrl} disabled={!urlInput.trim() || urlDiscovering}>
|
|
280
280
|
{urlDiscovering ? 'Checking…' : 'Detect families'}
|
|
281
281
|
</button>
|
|
282
282
|
</div>
|
|
@@ -290,7 +290,7 @@
|
|
|
290
290
|
<input
|
|
291
291
|
type="checkbox"
|
|
292
292
|
checked={urlPickedNames.has(f.name)}
|
|
293
|
-
|
|
293
|
+
onchange={(e) => {
|
|
294
294
|
const target = e.currentTarget;
|
|
295
295
|
const s = new Set(urlPickedNames);
|
|
296
296
|
if (target.checked) s.add(f.name); else s.delete(f.name);
|
|
@@ -305,29 +305,29 @@
|
|
|
305
305
|
</li>
|
|
306
306
|
{/each}
|
|
307
307
|
</ul>
|
|
308
|
-
<button type="button" class="pf-btn primary"
|
|
308
|
+
<button type="button" class="pf-btn primary" onclick={addUrlSource}>Add {urlPickedNames.size} selected</button>
|
|
309
309
|
{:else if urlNeedsManualFamilies}
|
|
310
310
|
<div class="pf-detected">Couldn't auto-detect families (CORS or no metadata). Name them:</div>
|
|
311
311
|
<input
|
|
312
312
|
type="text"
|
|
313
|
-
class="form-input"
|
|
313
|
+
class="ui-form-input"
|
|
314
314
|
placeholder="Comma-separated family names"
|
|
315
315
|
bind:value={urlManualFamilies}
|
|
316
316
|
/>
|
|
317
|
-
<button type="button" class="pf-btn primary"
|
|
317
|
+
<button type="button" class="pf-btn primary" onclick={addUrlSource} disabled={!urlManualFamilies.trim()}>Add</button>
|
|
318
318
|
{/if}
|
|
319
319
|
{:else if addMode === 'fontface'}
|
|
320
320
|
<textarea
|
|
321
|
-
class="form-input pf-textarea"
|
|
321
|
+
class="ui-form-input pf-textarea"
|
|
322
322
|
placeholder={'Paste one or more @font-face { ... } rules'}
|
|
323
323
|
rows="6"
|
|
324
324
|
bind:value={fontFaceText}
|
|
325
|
-
|
|
325
|
+
oninput={parseFontFaceTextInput}
|
|
326
326
|
></textarea>
|
|
327
327
|
{#if fontFaceParsed.length > 0}
|
|
328
328
|
<div class="pf-detected">Detected: {fontFaceParsed.map((f) => f.name).join(', ')}</div>
|
|
329
329
|
{/if}
|
|
330
|
-
<button type="button" class="pf-btn primary"
|
|
330
|
+
<button type="button" class="pf-btn primary" onclick={addFontFaceSource} disabled={fontFaceParsed.length === 0}>Add</button>
|
|
331
331
|
{/if}
|
|
332
332
|
</div>
|
|
333
333
|
{/if}
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
}
|
|
127
127
|
];
|
|
128
128
|
|
|
129
|
-
let copiedVar: string | null = null;
|
|
129
|
+
let copiedVar: string | null = $state(null);
|
|
130
130
|
function copyVariable(v: string) {
|
|
131
131
|
navigator.clipboard.writeText(v);
|
|
132
132
|
copiedVar = v;
|
|
@@ -222,7 +222,7 @@
|
|
|
222
222
|
class="swatch-box"
|
|
223
223
|
style="background: var({swatch.variable});"
|
|
224
224
|
></div>
|
|
225
|
-
<button class="swatch-name copyable" class:copied={copiedVar === swatch.variable}
|
|
225
|
+
<button class="swatch-name copyable" class:copied={copiedVar === swatch.variable} onclick={() => copyVariable(swatch.variable)}>{copiedVar === swatch.variable ? 'copied!' : swatch.name}</button>
|
|
226
226
|
{:else}
|
|
227
227
|
<div class="swatch-box empty"></div>
|
|
228
228
|
<div class="swatch-name"> </div>
|
|
@@ -248,7 +248,7 @@
|
|
|
248
248
|
class="border-box"
|
|
249
249
|
style="border: 2px solid var({border.variable});"
|
|
250
250
|
></div>
|
|
251
|
-
<button class="border-name copyable" class:copied={copiedVar === border.variable}
|
|
251
|
+
<button class="border-name copyable" class:copied={copiedVar === border.variable} onclick={() => copyVariable(border.variable)}>{copiedVar === border.variable ? 'copied!' : border.name}</button>
|
|
252
252
|
</div>
|
|
253
253
|
{/each}
|
|
254
254
|
</div>
|
package/src/ui/TextTab.svelte
CHANGED
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
}
|
|
79
79
|
];
|
|
80
80
|
|
|
81
|
-
let copiedVar: string | null = null;
|
|
81
|
+
let copiedVar: string | null = $state(null);
|
|
82
82
|
function copyVariable(v: string) {
|
|
83
83
|
navigator.clipboard.writeText(v);
|
|
84
84
|
copiedVar = v;
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
Ag
|
|
101
101
|
</div>
|
|
102
102
|
<div class="text-color-info">
|
|
103
|
-
<button class="text-color-name copyable" class:copied={copiedVar === color.variable}
|
|
103
|
+
<button class="text-color-name copyable" class:copied={copiedVar === color.variable} onclick={() => copyVariable(color.variable)}>{copiedVar === color.variable ? 'copied!' : color.name}</button>
|
|
104
104
|
<div class="text-color-variable">{color.variable}</div>
|
|
105
105
|
<div class="text-color-description">{color.description}</div>
|
|
106
106
|
</div>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { stopPropagation } from 'svelte/legacy';
|
|
3
|
+
|
|
4
|
+
import { onMount } from 'svelte';
|
|
3
5
|
import type { ThemeMeta } from '../lib/themeTypes';
|
|
4
6
|
import { listThemes, deleteTheme, setActiveFile, sanitizeFileName, getProductionInfo, setProductionFile } from '../lib/themeService';
|
|
5
7
|
import type { ProductionInfo } from '../lib/themeService';
|
|
@@ -7,23 +9,24 @@
|
|
|
7
9
|
import { dirty } from '../lib/editorStore';
|
|
8
10
|
import UIDialog from './UIDialog.svelte';
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
interface Props {
|
|
13
|
+
saveStatus?: 'idle' | 'saving' | 'saved' | 'error';
|
|
14
|
+
onsave?: (payload: { fileName: string; displayName: string }) => void;
|
|
15
|
+
onload?: (payload: { fileName: string }) => void;
|
|
16
|
+
}
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
let { saveStatus = 'idle', onsave, onload }: Props = $props();
|
|
16
19
|
|
|
17
|
-
let files: ThemeMeta[] = [];
|
|
18
|
-
let showFileList = false;
|
|
19
|
-
let saveAsEditing = false;
|
|
20
|
-
let saveAsName = '';
|
|
21
|
-
let saveAsInput: HTMLInputElement;
|
|
22
|
-
let currentDisplayName = 'Default';
|
|
20
|
+
let files: ThemeMeta[] = $state([]);
|
|
21
|
+
let showFileList = $state(false);
|
|
22
|
+
let saveAsEditing = $state(false);
|
|
23
|
+
let saveAsName = $state('');
|
|
24
|
+
let saveAsInput: HTMLInputElement | undefined = $state();
|
|
25
|
+
let currentDisplayName = $state('Default');
|
|
23
26
|
|
|
24
27
|
// --- Production state ---
|
|
25
|
-
let productionInfo: ProductionInfo | null = null;
|
|
26
|
-
let productionUpdateStatus: 'idle' | 'updating' | 'done' | 'error' = 'idle';
|
|
28
|
+
let productionInfo: ProductionInfo | null = $state(null);
|
|
29
|
+
let productionUpdateStatus: 'idle' | 'updating' | 'done' | 'error' = $state('idle');
|
|
27
30
|
|
|
28
31
|
async function refreshFiles() {
|
|
29
32
|
try {
|
|
@@ -65,7 +68,7 @@
|
|
|
65
68
|
});
|
|
66
69
|
|
|
67
70
|
function handleSave() {
|
|
68
|
-
|
|
71
|
+
onsave?.({ fileName: $activeFileName, displayName: currentDisplayName });
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
function handleSaveIncrement() {
|
|
@@ -83,7 +86,7 @@
|
|
|
83
86
|
const displayName = `${baseName}_${suffix}`;
|
|
84
87
|
const fileName = `${baseFileName}_${suffix}`;
|
|
85
88
|
|
|
86
|
-
|
|
89
|
+
onsave?.({ fileName, displayName });
|
|
87
90
|
$activeFileName = fileName;
|
|
88
91
|
currentDisplayName = displayName;
|
|
89
92
|
setTimeout(() => refreshFiles(), 500);
|
|
@@ -106,7 +109,7 @@
|
|
|
106
109
|
return;
|
|
107
110
|
}
|
|
108
111
|
saveAsEditing = false;
|
|
109
|
-
|
|
112
|
+
onsave?.({ fileName, displayName });
|
|
110
113
|
$activeFileName = fileName;
|
|
111
114
|
currentDisplayName = displayName;
|
|
112
115
|
setTimeout(() => refreshFiles(), 500);
|
|
@@ -122,7 +125,7 @@
|
|
|
122
125
|
await setActiveFile(file.fileName);
|
|
123
126
|
$activeFileName = file.fileName;
|
|
124
127
|
currentDisplayName = file.name;
|
|
125
|
-
|
|
128
|
+
onload?.({ fileName: file.fileName });
|
|
126
129
|
// editorStore.loadFromFile clears history and resets dirty — no snapshot needed here.
|
|
127
130
|
}
|
|
128
131
|
|
|
@@ -135,7 +138,7 @@
|
|
|
135
138
|
if (file.fileName === $activeFileName) {
|
|
136
139
|
$activeFileName = 'default';
|
|
137
140
|
currentDisplayName = 'Default';
|
|
138
|
-
|
|
141
|
+
onload?.({ fileName: 'default' });
|
|
139
142
|
}
|
|
140
143
|
} catch {
|
|
141
144
|
// silent
|
|
@@ -168,8 +171,8 @@
|
|
|
168
171
|
}
|
|
169
172
|
|
|
170
173
|
type SortKey = 'name' | 'updatedAt';
|
|
171
|
-
let sortKey: SortKey = 'updatedAt';
|
|
172
|
-
let sortDir: 'asc' | 'desc' = 'desc';
|
|
174
|
+
let sortKey: SortKey = $state('updatedAt');
|
|
175
|
+
let sortDir: 'asc' | 'desc' = $state('desc');
|
|
173
176
|
|
|
174
177
|
function toggleSort(key: SortKey) {
|
|
175
178
|
if (sortKey === key) {
|
|
@@ -181,7 +184,7 @@
|
|
|
181
184
|
}
|
|
182
185
|
}
|
|
183
186
|
|
|
184
|
-
|
|
187
|
+
let sortedFiles = $derived([...files].sort((a, b) => {
|
|
185
188
|
let cmp = 0;
|
|
186
189
|
if (sortKey === 'name') {
|
|
187
190
|
cmp = a.name.localeCompare(b.name, undefined, { sensitivity: 'base' });
|
|
@@ -189,7 +192,7 @@
|
|
|
189
192
|
cmp = (a.updatedAt || '').localeCompare(b.updatedAt || '');
|
|
190
193
|
}
|
|
191
194
|
return sortDir === 'asc' ? cmp : -cmp;
|
|
192
|
-
});
|
|
195
|
+
}));
|
|
193
196
|
</script>
|
|
194
197
|
|
|
195
198
|
<div class="theme-file-manager">
|
|
@@ -206,7 +209,7 @@
|
|
|
206
209
|
class:saving={saveStatus === 'saving'}
|
|
207
210
|
class:saved={saveStatus === 'saved'}
|
|
208
211
|
class:error={saveStatus === 'error'}
|
|
209
|
-
|
|
212
|
+
onclick={handleSave}
|
|
210
213
|
disabled={saveStatus === 'saving'}
|
|
211
214
|
title="Save to current file"
|
|
212
215
|
>
|
|
@@ -217,7 +220,7 @@
|
|
|
217
220
|
</button>
|
|
218
221
|
<button
|
|
219
222
|
class="tfm-btn increment-btn"
|
|
220
|
-
|
|
223
|
+
onclick={handleSaveIncrement}
|
|
221
224
|
disabled={saveStatus === 'saving'}
|
|
222
225
|
title="Save as incremented copy"
|
|
223
226
|
>
|
|
@@ -232,14 +235,14 @@
|
|
|
232
235
|
type="text"
|
|
233
236
|
bind:value={saveAsName}
|
|
234
237
|
bind:this={saveAsInput}
|
|
235
|
-
|
|
238
|
+
onkeydown={handleSaveAsKeydown}
|
|
236
239
|
placeholder="Theme name..."
|
|
237
240
|
/>
|
|
238
241
|
<div class="save-as-actions">
|
|
239
|
-
<button class="inline-btn confirm-btn"
|
|
242
|
+
<button class="inline-btn confirm-btn" onclick={confirmSaveAs} disabled={!saveAsName.trim()} title="Save">
|
|
240
243
|
<i class="fas fa-check"></i>
|
|
241
244
|
</button>
|
|
242
|
-
<button class="inline-btn cancel-btn"
|
|
245
|
+
<button class="inline-btn cancel-btn" onclick={cancelSaveAs} title="Cancel">
|
|
243
246
|
<i class="fas fa-times"></i>
|
|
244
247
|
</button>
|
|
245
248
|
</div>
|
|
@@ -247,7 +250,7 @@
|
|
|
247
250
|
{:else}
|
|
248
251
|
<button
|
|
249
252
|
class="tfm-btn"
|
|
250
|
-
|
|
253
|
+
onclick={openSaveAs}
|
|
251
254
|
title="Save as new file"
|
|
252
255
|
>
|
|
253
256
|
<i class="fas fa-copy"></i>
|
|
@@ -258,7 +261,7 @@
|
|
|
258
261
|
<button
|
|
259
262
|
class="tfm-btn"
|
|
260
263
|
class:active={showFileList}
|
|
261
|
-
|
|
264
|
+
onclick={toggleFileList}
|
|
262
265
|
title="Load a theme"
|
|
263
266
|
>
|
|
264
267
|
<i class="fas fa-folder-open"></i>
|
|
@@ -287,7 +290,7 @@
|
|
|
287
290
|
class:updating={productionUpdateStatus === 'updating'}
|
|
288
291
|
class:done={productionUpdateStatus === 'done'}
|
|
289
292
|
class:error={productionUpdateStatus === 'error'}
|
|
290
|
-
|
|
293
|
+
onclick={handleUpdateProduction}
|
|
291
294
|
disabled={productionUpdateStatus === 'updating' || (productionInfo?.fileName === $activeFileName)}
|
|
292
295
|
title={productionInfo?.fileName === $activeFileName ? 'Already in production' : `Set "${currentDisplayName}" as production`}
|
|
293
296
|
>
|
|
@@ -317,7 +320,7 @@
|
|
|
317
320
|
<button
|
|
318
321
|
class="sort-btn name-col"
|
|
319
322
|
class:active-sort={sortKey === 'name'}
|
|
320
|
-
|
|
323
|
+
onclick={() => toggleSort('name')}
|
|
321
324
|
>
|
|
322
325
|
<span>Name</span>
|
|
323
326
|
{#if sortKey === 'name'}
|
|
@@ -327,7 +330,7 @@
|
|
|
327
330
|
<button
|
|
328
331
|
class="sort-btn date-col"
|
|
329
332
|
class:active-sort={sortKey === 'updatedAt'}
|
|
330
|
-
|
|
333
|
+
onclick={() => toggleSort('updatedAt')}
|
|
331
334
|
>
|
|
332
335
|
<span>Date</span>
|
|
333
336
|
{#if sortKey === 'updatedAt'}
|
|
@@ -338,7 +341,7 @@
|
|
|
338
341
|
</div>
|
|
339
342
|
{#each sortedFiles as file}
|
|
340
343
|
<div class="load-item" class:active={file.fileName === $activeFileName}>
|
|
341
|
-
<button class="load-name-btn"
|
|
344
|
+
<button class="load-name-btn" onclick={() => handleLoad(file)}>
|
|
342
345
|
{file.name}
|
|
343
346
|
</button>
|
|
344
347
|
<span class="updated-at" title={file.updatedAt}>{formatUpdatedAt(file.updatedAt)}</span>
|
|
@@ -348,7 +351,7 @@
|
|
|
348
351
|
{#if file.fileName !== 'default'}
|
|
349
352
|
<button
|
|
350
353
|
class="file-delete-btn"
|
|
351
|
-
|
|
354
|
+
onclick={stopPropagation(() => handleDelete(file))}
|
|
352
355
|
title="Delete {file.name}"
|
|
353
356
|
>
|
|
354
357
|
<i class="fas fa-trash-alt"></i>
|
package/src/ui/Toggle.svelte
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
interface Props {
|
|
3
|
+
checked?: boolean;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
label?: string;
|
|
6
|
+
onchange?: (checked: boolean) => void;
|
|
7
|
+
}
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
let { checked = $bindable(false), disabled = false, label = '', onchange }: Props = $props();
|
|
9
10
|
|
|
10
11
|
function toggle() {
|
|
11
12
|
if (disabled) return;
|
|
12
13
|
checked = !checked;
|
|
13
|
-
|
|
14
|
+
onchange?.(checked);
|
|
14
15
|
}
|
|
15
16
|
</script>
|
|
16
17
|
|
|
@@ -19,12 +20,13 @@
|
|
|
19
20
|
type="button"
|
|
20
21
|
role="switch"
|
|
21
22
|
aria-checked={checked}
|
|
23
|
+
aria-label={label || 'Toggle'}
|
|
22
24
|
{disabled}
|
|
23
25
|
class="toggle-track"
|
|
24
26
|
class:on={checked}
|
|
25
|
-
|
|
27
|
+
onclick={toggle}
|
|
26
28
|
>
|
|
27
|
-
<span class="toggle-thumb"
|
|
29
|
+
<span class="toggle-thumb"></span>
|
|
28
30
|
</button>
|
|
29
31
|
{#if label}
|
|
30
32
|
<span class="toggle-label">{label}</span>
|
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { run } from 'svelte/legacy';
|
|
3
|
+
|
|
2
4
|
import { copyPopover } from '../lib/copyPopover';
|
|
3
5
|
|
|
4
|
-
let bubbleEl: HTMLDivElement | null = null;
|
|
5
|
-
let bubbleW = 0;
|
|
6
|
-
let bubbleH = 0;
|
|
6
|
+
let bubbleEl: HTMLDivElement | null = $state(null);
|
|
7
|
+
let bubbleW = $state(0);
|
|
8
|
+
let bubbleH = $state(0);
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
run(() => {
|
|
11
|
+
if (bubbleEl && $copyPopover.visible) {
|
|
12
|
+
const r = bubbleEl.getBoundingClientRect();
|
|
13
|
+
bubbleW = r.width;
|
|
14
|
+
bubbleH = r.height;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
13
17
|
|
|
14
18
|
const GAP = 8;
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
let anchor = $derived($copyPopover.anchor);
|
|
21
|
+
let rawLeft = $derived(anchor ? anchor.left + anchor.width / 2 - bubbleW / 2 : 0);
|
|
22
|
+
let rawTop = $derived(anchor ? anchor.top - bubbleH - GAP : 0);
|
|
23
|
+
let maxLeft = $derived(typeof window !== 'undefined' ? window.innerWidth - bubbleW - 4 : rawLeft);
|
|
24
|
+
let clampedLeft = $derived(Math.max(4, Math.min(rawLeft, maxLeft)));
|
|
25
|
+
let clampedTop = $derived(Math.max(4, rawTop));
|
|
26
|
+
let arrowLeft = $derived(anchor ? anchor.left + anchor.width / 2 - clampedLeft : bubbleW / 2);
|
|
23
27
|
</script>
|
|
24
28
|
|
|
25
29
|
{#if $copyPopover.visible && anchor}
|