@motion-proto/live-tokens 0.1.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 +41 -0
- package/dist-plugin/index.cjs +444 -0
- package/dist-plugin/index.d.cts +12 -0
- package/dist-plugin/index.d.ts +12 -0
- package/dist-plugin/index.js +407 -0
- package/package.json +86 -0
- package/src/components/Badge.svelte +82 -0
- package/src/components/Button.svelte +333 -0
- package/src/components/Card.svelte +83 -0
- package/src/components/CollapsibleSection.svelte +82 -0
- package/src/components/DetailNav.svelte +78 -0
- package/src/components/Dialog.svelte +269 -0
- package/src/components/InlineEditActions.svelte +73 -0
- package/src/components/Notification.svelte +308 -0
- package/src/components/ProgressBar.svelte +99 -0
- package/src/components/RadioButton.svelte +87 -0
- package/src/components/SectionDivider.svelte +121 -0
- package/src/components/TabBar.svelte +92 -0
- package/src/components/Toggle.svelte +86 -0
- package/src/components/Tooltip.svelte +64 -0
- package/src/lib/ColumnsOverlay.svelte +120 -0
- package/src/lib/LiveEditorOverlay.svelte +467 -0
- package/src/lib/columnsOverlay.ts +26 -0
- package/src/lib/cssVarSync.ts +72 -0
- package/src/lib/editorConfig.ts +9 -0
- package/src/lib/editorConfigStore.ts +14 -0
- package/src/lib/index.ts +51 -0
- package/src/lib/oklch.ts +129 -0
- package/src/lib/pageSource.ts +6 -0
- package/src/lib/tokenInit.ts +29 -0
- package/src/lib/tokenService.ts +144 -0
- package/src/lib/tokenTypes.ts +45 -0
- package/src/pages/Admin.svelte +100 -0
- package/src/pages/ShowcasePage.svelte +146 -0
- package/src/showcase/BackupBrowser.svelte +617 -0
- package/src/showcase/BezierCurveEditor.svelte +648 -0
- package/src/showcase/ColorEditPanel.svelte +498 -0
- package/src/showcase/ComponentsTab.svelte +107 -0
- package/src/showcase/EditorDialog.svelte +137 -0
- package/src/showcase/PaletteEditor.svelte +2579 -0
- package/src/showcase/PaletteSelector.svelte +627 -0
- package/src/showcase/SurfacesTab.svelte +409 -0
- package/src/showcase/TextTab.svelte +205 -0
- package/src/showcase/TokenFileManager.svelte +683 -0
- package/src/showcase/TokenMap.svelte +54 -0
- package/src/showcase/VariablesTab.svelte +2657 -0
- package/src/showcase/VisualsTab.svelte +233 -0
- package/src/showcase/curveEngine.ts +190 -0
- package/src/showcase/demos/BadgeDemo.svelte +58 -0
- package/src/showcase/demos/CardDemo.svelte +52 -0
- package/src/showcase/demos/ChoiceButtonsDemo.svelte +194 -0
- package/src/showcase/demos/CollapsibleSectionDemo.svelte +56 -0
- package/src/showcase/demos/DialogDemo.svelte +42 -0
- package/src/showcase/demos/InlineEditActionsDemo.svelte +27 -0
- package/src/showcase/demos/NotificationDemo.svelte +149 -0
- package/src/showcase/demos/ProgressBarDemo.svelte +56 -0
- package/src/showcase/demos/RadioButtonDemo.svelte +58 -0
- package/src/showcase/demos/SectionDividerDemo.svelte +79 -0
- package/src/showcase/demos/StandardButtonsDemo.svelte +457 -0
- package/src/showcase/demos/TabBarDemo.svelte +60 -0
- package/src/showcase/demos/TooltipDemo.svelte +54 -0
- package/src/showcase/editor.css +93 -0
- package/src/showcase/index.ts +17 -0
- package/src/styles/fonts/Domine/Domine-VariableFont_wght.ttf +0 -0
- package/src/styles/fonts/Domine/OFL.txt +97 -0
- package/src/styles/fonts/Domine/README.txt +66 -0
- package/src/styles/fonts.css +18 -0
- package/src/styles/form-controls.css +190 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, onDestroy } from 'svelte';
|
|
3
|
+
import { route, navigate } from '../router';
|
|
4
|
+
import { resolvePageSource } from './pageSource';
|
|
5
|
+
import { columnsVisible, toggleColumns } from './columnsOverlay';
|
|
6
|
+
import { storageKey } from './editorConfig';
|
|
7
|
+
|
|
8
|
+
const projectRoot = __PROJECT_ROOT__;
|
|
9
|
+
|
|
10
|
+
$: sourceFile = resolvePageSource($route);
|
|
11
|
+
|
|
12
|
+
// `open` controls the editor's visible state. The overlay chrome itself
|
|
13
|
+
// is always rendered (in dev, on non-admin pages):
|
|
14
|
+
// open === true → full panel (docked or floating) with the iframe
|
|
15
|
+
// open === false → just the header bar pinned to top-right
|
|
16
|
+
export let open: boolean = false;
|
|
17
|
+
|
|
18
|
+
// Mount the iframe the first time the editor is shown, then keep it mounted
|
|
19
|
+
// across hide/show cycles so editor state (unsaved slider values, scroll
|
|
20
|
+
// position, expanded sections) survives.
|
|
21
|
+
let hasBeenOpen: boolean = open;
|
|
22
|
+
$: if (open) hasBeenOpen = true;
|
|
23
|
+
|
|
24
|
+
type Mode = 'docked' | 'floating';
|
|
25
|
+
|
|
26
|
+
const STORAGE_KEY = storageKey('overlay-state');
|
|
27
|
+
const MIN_WIDTH = 360;
|
|
28
|
+
const MIN_HEIGHT = 480;
|
|
29
|
+
const DEFAULT_DOCKED_WIDTH = Math.min(960, Math.floor(window.innerWidth * 0.55));
|
|
30
|
+
const DEFAULT_FLOATING = {
|
|
31
|
+
x: Math.max(16, window.innerWidth - 960 - 32),
|
|
32
|
+
y: 64,
|
|
33
|
+
width: 960,
|
|
34
|
+
height: Math.min(880, window.innerHeight - 96),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
interface OverlayState {
|
|
38
|
+
mode: Mode;
|
|
39
|
+
dockedWidth: number;
|
|
40
|
+
floating: { x: number; y: number; width: number; height: number };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function loadState(): OverlayState {
|
|
44
|
+
try {
|
|
45
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
46
|
+
if (raw) {
|
|
47
|
+
const parsed = JSON.parse(raw) as Partial<OverlayState>;
|
|
48
|
+
return {
|
|
49
|
+
mode: parsed.mode === 'floating' ? 'floating' : 'docked',
|
|
50
|
+
dockedWidth: typeof parsed.dockedWidth === 'number' ? parsed.dockedWidth : DEFAULT_DOCKED_WIDTH,
|
|
51
|
+
floating: {
|
|
52
|
+
x: parsed.floating?.x ?? DEFAULT_FLOATING.x,
|
|
53
|
+
y: parsed.floating?.y ?? DEFAULT_FLOATING.y,
|
|
54
|
+
width: parsed.floating?.width ?? DEFAULT_FLOATING.width,
|
|
55
|
+
height: parsed.floating?.height ?? DEFAULT_FLOATING.height,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
// ignore
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
mode: 'docked',
|
|
64
|
+
dockedWidth: DEFAULT_DOCKED_WIDTH,
|
|
65
|
+
floating: { ...DEFAULT_FLOATING },
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function persist() {
|
|
70
|
+
try {
|
|
71
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify({ mode, dockedWidth, floating }));
|
|
72
|
+
} catch {
|
|
73
|
+
// ignore quota errors
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const initial = loadState();
|
|
78
|
+
let mode: Mode = initial.mode;
|
|
79
|
+
let dockedWidth: number = Math.max(MIN_WIDTH, initial.dockedWidth);
|
|
80
|
+
let floating = { ...initial.floating };
|
|
81
|
+
|
|
82
|
+
// Gesture state — a transparent scrim covers the iframe while any gesture is active
|
|
83
|
+
// so pointer events land on the panel, not on content inside the iframe.
|
|
84
|
+
let gesturing: 'drag' | 'resize-left' | 'resize-se' | null = null;
|
|
85
|
+
|
|
86
|
+
function startDrag(e: PointerEvent) {
|
|
87
|
+
if (!open || mode !== 'floating') return;
|
|
88
|
+
if ((e.target as HTMLElement).closest('button')) return;
|
|
89
|
+
gesturing = 'drag';
|
|
90
|
+
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
|
91
|
+
const startX = e.clientX;
|
|
92
|
+
const startY = e.clientY;
|
|
93
|
+
const origX = floating.x;
|
|
94
|
+
const origY = floating.y;
|
|
95
|
+
function move(ev: PointerEvent) {
|
|
96
|
+
floating = {
|
|
97
|
+
...floating,
|
|
98
|
+
x: clamp(origX + (ev.clientX - startX), 0, window.innerWidth - 120),
|
|
99
|
+
y: clamp(origY + (ev.clientY - startY), 0, window.innerHeight - 40),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function up() {
|
|
103
|
+
gesturing = null;
|
|
104
|
+
window.removeEventListener('pointermove', move);
|
|
105
|
+
window.removeEventListener('pointerup', up);
|
|
106
|
+
persist();
|
|
107
|
+
}
|
|
108
|
+
window.addEventListener('pointermove', move);
|
|
109
|
+
window.addEventListener('pointerup', up);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function startDockedResize(e: PointerEvent) {
|
|
113
|
+
if (!open || mode !== 'docked') return;
|
|
114
|
+
gesturing = 'resize-left';
|
|
115
|
+
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
|
116
|
+
const startX = e.clientX;
|
|
117
|
+
const origWidth = dockedWidth;
|
|
118
|
+
function move(ev: PointerEvent) {
|
|
119
|
+
// Dragging left increases width (panel is anchored to the right edge)
|
|
120
|
+
dockedWidth = clamp(origWidth + (startX - ev.clientX), MIN_WIDTH, window.innerWidth - 120);
|
|
121
|
+
}
|
|
122
|
+
function up() {
|
|
123
|
+
gesturing = null;
|
|
124
|
+
window.removeEventListener('pointermove', move);
|
|
125
|
+
window.removeEventListener('pointerup', up);
|
|
126
|
+
persist();
|
|
127
|
+
}
|
|
128
|
+
window.addEventListener('pointermove', move);
|
|
129
|
+
window.addEventListener('pointerup', up);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function startFloatingResize(e: PointerEvent) {
|
|
133
|
+
if (!open || mode !== 'floating') return;
|
|
134
|
+
gesturing = 'resize-se';
|
|
135
|
+
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
|
136
|
+
const startX = e.clientX;
|
|
137
|
+
const startY = e.clientY;
|
|
138
|
+
const origW = floating.width;
|
|
139
|
+
const origH = floating.height;
|
|
140
|
+
function move(ev: PointerEvent) {
|
|
141
|
+
floating = {
|
|
142
|
+
...floating,
|
|
143
|
+
width: clamp(origW + (ev.clientX - startX), MIN_WIDTH, window.innerWidth - floating.x - 8),
|
|
144
|
+
height: clamp(origH + (ev.clientY - startY), MIN_HEIGHT, window.innerHeight - floating.y - 8),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function up() {
|
|
148
|
+
gesturing = null;
|
|
149
|
+
window.removeEventListener('pointermove', move);
|
|
150
|
+
window.removeEventListener('pointerup', up);
|
|
151
|
+
persist();
|
|
152
|
+
}
|
|
153
|
+
window.addEventListener('pointermove', move);
|
|
154
|
+
window.addEventListener('pointerup', up);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function clamp(v: number, lo: number, hi: number) {
|
|
158
|
+
return Math.max(lo, Math.min(hi, v));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function toggleMode() {
|
|
162
|
+
mode = mode === 'docked' ? 'floating' : 'docked';
|
|
163
|
+
// Snap the floating rect back inside the viewport if it drifted off-screen since last use
|
|
164
|
+
if (mode === 'floating') {
|
|
165
|
+
floating = {
|
|
166
|
+
x: clamp(floating.x, 0, window.innerWidth - MIN_WIDTH),
|
|
167
|
+
y: clamp(floating.y, 0, window.innerHeight - 40),
|
|
168
|
+
width: clamp(floating.width, MIN_WIDTH, window.innerWidth),
|
|
169
|
+
height: clamp(floating.height, MIN_HEIGHT, window.innerHeight),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
persist();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function toggleOpen() {
|
|
176
|
+
open = !open;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function handleHeaderDblClick(e: MouseEvent) {
|
|
180
|
+
// Ignore double-clicks on buttons so toggling mode/fullscreen/show-hide
|
|
181
|
+
// doesn't also fire the dblclick handler.
|
|
182
|
+
if ((e.target as HTMLElement).closest('button')) return;
|
|
183
|
+
toggleOpen();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Messages from the editor iframe:
|
|
187
|
+
// lt-overlay-close — iframe's Close button (hides the editor)
|
|
188
|
+
function handleMessage(ev: MessageEvent) {
|
|
189
|
+
if (ev.origin !== window.location.origin) return;
|
|
190
|
+
const data = ev.data;
|
|
191
|
+
if (!data || typeof data !== 'object') return;
|
|
192
|
+
if (data.type === 'lt-overlay-close') {
|
|
193
|
+
open = false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
onMount(() => {
|
|
198
|
+
window.addEventListener('message', handleMessage);
|
|
199
|
+
});
|
|
200
|
+
onDestroy(() => {
|
|
201
|
+
window.removeEventListener('message', handleMessage);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
$: panelStyle = !open
|
|
205
|
+
? 'position: fixed; top: 12px; right: 12px;'
|
|
206
|
+
: mode === 'docked'
|
|
207
|
+
? `position: fixed; top: 0; right: 0; bottom: 0; width: ${dockedWidth}px;`
|
|
208
|
+
: `position: fixed; top: ${floating.y}px; left: ${floating.x}px; width: ${floating.width}px; height: ${floating.height}px;`;
|
|
209
|
+
</script>
|
|
210
|
+
|
|
211
|
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
212
|
+
<div
|
|
213
|
+
class="lt-overlay"
|
|
214
|
+
style={panelStyle}
|
|
215
|
+
class:shown={open}
|
|
216
|
+
class:hidden={!open}
|
|
217
|
+
class:docked={open && mode === 'docked'}
|
|
218
|
+
class:floating={open && mode === 'floating'}
|
|
219
|
+
>
|
|
220
|
+
<div
|
|
221
|
+
class="header"
|
|
222
|
+
on:pointerdown={startDrag}
|
|
223
|
+
on:dblclick={handleHeaderDblClick}
|
|
224
|
+
title={open ? 'Double-click to hide' : 'Double-click to show'}
|
|
225
|
+
>
|
|
226
|
+
{#if open}
|
|
227
|
+
<span class="title">Design Editor</span>
|
|
228
|
+
<div class="spacer"></div>
|
|
229
|
+
{/if}
|
|
230
|
+
|
|
231
|
+
{#if open}
|
|
232
|
+
<div class="preview-nav">
|
|
233
|
+
<button class="hdr-btn nav" class:active={$route === '/'} on:click={() => navigate('/')}>
|
|
234
|
+
<i class="fas fa-home"></i>
|
|
235
|
+
<span>Site</span>
|
|
236
|
+
</button>
|
|
237
|
+
<button class="hdr-btn nav" class:active={$route === '/components'} on:click={() => navigate('/components')}>
|
|
238
|
+
<i class="fas fa-puzzle-piece"></i>
|
|
239
|
+
<span>Components</span>
|
|
240
|
+
</button>
|
|
241
|
+
</div>
|
|
242
|
+
{/if}
|
|
243
|
+
|
|
244
|
+
<button
|
|
245
|
+
class="hdr-btn icon"
|
|
246
|
+
class:active={$columnsVisible}
|
|
247
|
+
on:click={toggleColumns}
|
|
248
|
+
title="{$columnsVisible ? 'Hide' : 'Show'} columns"
|
|
249
|
+
>
|
|
250
|
+
<i class="fas fa-grip-lines-vertical"></i>
|
|
251
|
+
</button>
|
|
252
|
+
|
|
253
|
+
<button class="hdr-btn text" on:click={toggleOpen}>
|
|
254
|
+
{open ? 'Hide Editor' : 'Editor'}
|
|
255
|
+
</button>
|
|
256
|
+
|
|
257
|
+
{#if open}
|
|
258
|
+
<button class="hdr-btn icon" title={mode === 'docked' ? 'Float' : 'Dock to right'} on:click={toggleMode}>
|
|
259
|
+
<i class={mode === 'docked' ? 'fas fa-up-right-from-square' : 'fas fa-thumbtack'}></i>
|
|
260
|
+
</button>
|
|
261
|
+
{/if}
|
|
262
|
+
|
|
263
|
+
{#if sourceFile}
|
|
264
|
+
<a
|
|
265
|
+
class="hdr-btn text source"
|
|
266
|
+
href="vscode://file/{projectRoot}/{sourceFile}"
|
|
267
|
+
title="Open {sourceFile} in VS Code"
|
|
268
|
+
>
|
|
269
|
+
<i class="fas fa-code"></i>
|
|
270
|
+
Page Source
|
|
271
|
+
</a>
|
|
272
|
+
{/if}
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
{#if hasBeenOpen}
|
|
276
|
+
<div class="frame-wrap">
|
|
277
|
+
<iframe
|
|
278
|
+
src="/admin"
|
|
279
|
+
title="Design editor"
|
|
280
|
+
class="editor-frame"
|
|
281
|
+
></iframe>
|
|
282
|
+
{#if gesturing}
|
|
283
|
+
<div class="gesture-scrim"></div>
|
|
284
|
+
{/if}
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
{#if mode === 'docked'}
|
|
288
|
+
<div class="resize-left" on:pointerdown={startDockedResize}></div>
|
|
289
|
+
{:else}
|
|
290
|
+
<div class="resize-se" on:pointerdown={startFloatingResize}></div>
|
|
291
|
+
{/if}
|
|
292
|
+
{/if}
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
<style>
|
|
296
|
+
.lt-overlay {
|
|
297
|
+
display: flex;
|
|
298
|
+
flex-direction: column;
|
|
299
|
+
background: #0a0a0a;
|
|
300
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
301
|
+
box-shadow: 0 18px 60px rgba(0, 0, 0, 0.6);
|
|
302
|
+
z-index: 2000;
|
|
303
|
+
overflow: hidden;
|
|
304
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
305
|
+
color: #fff;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.lt-overlay.docked {
|
|
309
|
+
border-right: none;
|
|
310
|
+
border-radius: 0;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.lt-overlay.floating {
|
|
314
|
+
border-radius: 8px;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/* Hidden state: the editor panel is collapsed to just the header bar,
|
|
318
|
+
pinned to the top-right. The iframe stays mounted (for instant show)
|
|
319
|
+
but its container is display:none. */
|
|
320
|
+
.lt-overlay.hidden {
|
|
321
|
+
border-radius: 6px;
|
|
322
|
+
width: auto;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.lt-overlay.hidden .frame-wrap,
|
|
326
|
+
.lt-overlay.hidden .resize-left,
|
|
327
|
+
.lt-overlay.hidden .resize-se {
|
|
328
|
+
display: none;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.header {
|
|
332
|
+
display: flex;
|
|
333
|
+
align-items: center;
|
|
334
|
+
gap: 6px;
|
|
335
|
+
padding: 6px 10px;
|
|
336
|
+
background: #111;
|
|
337
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
338
|
+
cursor: default;
|
|
339
|
+
flex-shrink: 0;
|
|
340
|
+
user-select: none;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.lt-overlay.hidden .header {
|
|
344
|
+
border-bottom: none;
|
|
345
|
+
padding: 5px 8px;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.lt-overlay.floating .header {
|
|
349
|
+
cursor: move;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.title {
|
|
353
|
+
font-size: 13px;
|
|
354
|
+
font-weight: 600;
|
|
355
|
+
color: rgba(255, 255, 255, 0.85);
|
|
356
|
+
letter-spacing: 0.02em;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.spacer { flex: 1; }
|
|
360
|
+
|
|
361
|
+
.hdr-btn {
|
|
362
|
+
display: inline-flex;
|
|
363
|
+
align-items: center;
|
|
364
|
+
justify-content: center;
|
|
365
|
+
background: transparent;
|
|
366
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
367
|
+
border-radius: 4px;
|
|
368
|
+
color: rgba(255, 255, 255, 0.75);
|
|
369
|
+
cursor: pointer;
|
|
370
|
+
transition: background 0.1s, color 0.1s;
|
|
371
|
+
font-family: inherit;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.hdr-btn.icon {
|
|
375
|
+
width: 26px;
|
|
376
|
+
height: 26px;
|
|
377
|
+
font-size: 11px;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.hdr-btn.text {
|
|
381
|
+
height: 26px;
|
|
382
|
+
padding: 0 10px;
|
|
383
|
+
font-size: 12px;
|
|
384
|
+
font-weight: 500;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
a.hdr-btn.source {
|
|
388
|
+
gap: 6px;
|
|
389
|
+
text-decoration: none;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.hdr-btn.nav {
|
|
393
|
+
height: 26px;
|
|
394
|
+
padding: 0 9px;
|
|
395
|
+
gap: 5px;
|
|
396
|
+
font-size: 11px;
|
|
397
|
+
font-weight: 500;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.hdr-btn:hover {
|
|
401
|
+
background: rgba(255, 255, 255, 0.08);
|
|
402
|
+
color: #fff;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.hdr-btn.active {
|
|
406
|
+
background: rgba(255, 255, 255, 0.12);
|
|
407
|
+
color: #fff;
|
|
408
|
+
border-color: rgba(255, 255, 255, 0.18);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.preview-nav {
|
|
412
|
+
display: flex;
|
|
413
|
+
gap: 3px;
|
|
414
|
+
margin-right: 4px;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.frame-wrap {
|
|
418
|
+
position: relative;
|
|
419
|
+
flex: 1;
|
|
420
|
+
min-height: 0;
|
|
421
|
+
background: #000;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.editor-frame {
|
|
425
|
+
width: 100%;
|
|
426
|
+
height: 100%;
|
|
427
|
+
border: 0;
|
|
428
|
+
display: block;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.gesture-scrim {
|
|
432
|
+
position: absolute;
|
|
433
|
+
inset: 0;
|
|
434
|
+
background: transparent;
|
|
435
|
+
cursor: inherit;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.resize-left {
|
|
439
|
+
position: absolute;
|
|
440
|
+
top: 0;
|
|
441
|
+
left: 0;
|
|
442
|
+
bottom: 0;
|
|
443
|
+
width: 6px;
|
|
444
|
+
cursor: ew-resize;
|
|
445
|
+
background: transparent;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.resize-left:hover {
|
|
449
|
+
background: rgba(255, 255, 255, 0.08);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.resize-se {
|
|
453
|
+
position: absolute;
|
|
454
|
+
right: 0;
|
|
455
|
+
bottom: 0;
|
|
456
|
+
width: 16px;
|
|
457
|
+
height: 16px;
|
|
458
|
+
cursor: nwse-resize;
|
|
459
|
+
background: linear-gradient(
|
|
460
|
+
135deg,
|
|
461
|
+
transparent 45%,
|
|
462
|
+
rgba(255, 255, 255, 0.35) 45%,
|
|
463
|
+
rgba(255, 255, 255, 0.35) 55%,
|
|
464
|
+
transparent 55%
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { writable } from 'svelte/store';
|
|
2
|
+
import { storageKey } from './editorConfig';
|
|
3
|
+
|
|
4
|
+
const STORAGE_KEY = storageKey('columns-visible');
|
|
5
|
+
|
|
6
|
+
function load(): boolean {
|
|
7
|
+
try {
|
|
8
|
+
return localStorage.getItem(STORAGE_KEY) === '1';
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const columnsVisible = writable<boolean>(load());
|
|
15
|
+
|
|
16
|
+
columnsVisible.subscribe((v) => {
|
|
17
|
+
try {
|
|
18
|
+
localStorage.setItem(STORAGE_KEY, v ? '1' : '0');
|
|
19
|
+
} catch {
|
|
20
|
+
// ignore quota errors
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export function toggleColumns() {
|
|
25
|
+
columnsVisible.update((v) => !v);
|
|
26
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Central CSS custom-property writer.
|
|
3
|
+
*
|
|
4
|
+
* Writes to document.documentElement and — when running inside a same-origin
|
|
5
|
+
* iframe (the live-preview overlay) — also writes to
|
|
6
|
+
* window.parent.document.documentElement. This lets the overlay editor at
|
|
7
|
+
* /admin drive the host site's :root in real time without any message-passing
|
|
8
|
+
* infrastructure.
|
|
9
|
+
*
|
|
10
|
+
* When the editor runs standalone at /admin (not inside the overlay iframe),
|
|
11
|
+
* parentRoot is null and every call is a plain single-root write.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
function resolveParentRoot(): HTMLElement | null {
|
|
15
|
+
try {
|
|
16
|
+
if (window.parent !== window && window.parent?.document) {
|
|
17
|
+
return window.parent.document.documentElement;
|
|
18
|
+
}
|
|
19
|
+
} catch {
|
|
20
|
+
// Cross-origin parent — not expected in dev, but be defensive.
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const selfRoot: HTMLElement = document.documentElement;
|
|
26
|
+
const parentRoot: HTMLElement | null = resolveParentRoot();
|
|
27
|
+
|
|
28
|
+
export function setCssVar(name: string, value: string): void {
|
|
29
|
+
selfRoot.style.setProperty(name, value);
|
|
30
|
+
parentRoot?.style.setProperty(name, value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function removeCssVar(name: string): void {
|
|
34
|
+
selfRoot.style.removeProperty(name);
|
|
35
|
+
parentRoot?.style.removeProperty(name);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Apply a map of CSS variables to :root (and the parent :root when in an iframe). */
|
|
39
|
+
export function applyCssVariables(variables: Record<string, string>): void {
|
|
40
|
+
for (const [name, value] of Object.entries(variables)) {
|
|
41
|
+
setCssVar(name, value);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Remove all inline CSS custom properties from :root on both self and parent. */
|
|
46
|
+
export function clearAllCssVarOverrides(): void {
|
|
47
|
+
clearRoot(selfRoot);
|
|
48
|
+
if (parentRoot) clearRoot(parentRoot);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function clearRoot(el: HTMLElement): void {
|
|
52
|
+
const style = el.style;
|
|
53
|
+
const names: string[] = [];
|
|
54
|
+
for (let i = 0; i < style.length; i++) {
|
|
55
|
+
const name = style[i];
|
|
56
|
+
if (name.startsWith('--')) names.push(name);
|
|
57
|
+
}
|
|
58
|
+
for (const name of names) style.removeProperty(name);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Scrape all inline CSS custom properties currently on self :root. */
|
|
62
|
+
export function scrapeCssVariables(): Record<string, string> {
|
|
63
|
+
const style = selfRoot.style;
|
|
64
|
+
const variables: Record<string, string> = {};
|
|
65
|
+
for (let i = 0; i < style.length; i++) {
|
|
66
|
+
const name = style[i];
|
|
67
|
+
if (name.startsWith('--')) {
|
|
68
|
+
variables[name] = style.getPropertyValue(name).trim();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return variables;
|
|
72
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { writable } from 'svelte/store';
|
|
2
|
+
import type { PaletteConfig } from './tokenTypes';
|
|
3
|
+
|
|
4
|
+
/** Each PaletteEditor pushes its config here keyed by label. Used when saving. */
|
|
5
|
+
export const editorConfigs = writable<Record<string, PaletteConfig>>({});
|
|
6
|
+
|
|
7
|
+
/** Set by the load flow. Each PaletteEditor watches for its label and applies the config. */
|
|
8
|
+
export const loadedConfigs = writable<Record<string, PaletteConfig> | null>(null);
|
|
9
|
+
|
|
10
|
+
/** True when editorConfigs were populated from a token file load or deliberate user edit, not just component defaults. */
|
|
11
|
+
export const configsLoadedFromFile = writable<boolean>(false);
|
|
12
|
+
|
|
13
|
+
/** The file name of the currently active token file. */
|
|
14
|
+
export const activeFileName = writable<string>('default');
|
package/src/lib/index.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export { default as LiveEditorOverlay } from './LiveEditorOverlay.svelte';
|
|
2
|
+
export { default as ColumnsOverlay } from './ColumnsOverlay.svelte';
|
|
3
|
+
|
|
4
|
+
export { columnsVisible, toggleColumns } from './columnsOverlay';
|
|
5
|
+
export { configureEditor, storageKey } from './editorConfig';
|
|
6
|
+
export {
|
|
7
|
+
editorConfigs,
|
|
8
|
+
loadedConfigs,
|
|
9
|
+
configsLoadedFromFile,
|
|
10
|
+
activeFileName,
|
|
11
|
+
} from './editorConfigStore';
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
setCssVar,
|
|
15
|
+
removeCssVar,
|
|
16
|
+
applyCssVariables,
|
|
17
|
+
clearAllCssVarOverrides,
|
|
18
|
+
scrapeCssVariables,
|
|
19
|
+
} from './cssVarSync';
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
listTokenFiles,
|
|
23
|
+
loadTokenFile,
|
|
24
|
+
saveTokenFile,
|
|
25
|
+
deleteTokenFile,
|
|
26
|
+
getActiveTokens,
|
|
27
|
+
setActiveFile,
|
|
28
|
+
getProductionInfo,
|
|
29
|
+
setProductionFile,
|
|
30
|
+
listBackups,
|
|
31
|
+
getBackupContent,
|
|
32
|
+
restoreBackup,
|
|
33
|
+
getCurrentCss,
|
|
34
|
+
sanitizeFileName,
|
|
35
|
+
} from './tokenService';
|
|
36
|
+
export type { ProductionInfo, BackupEntry } from './tokenService';
|
|
37
|
+
|
|
38
|
+
export type {
|
|
39
|
+
PaletteConfig,
|
|
40
|
+
TokenFile,
|
|
41
|
+
TokenFileMeta,
|
|
42
|
+
GradientStyle,
|
|
43
|
+
GradientStop,
|
|
44
|
+
} from './tokenTypes';
|
|
45
|
+
|
|
46
|
+
export { hexToOklch, oklchToHex, gamutClamp } from './oklch';
|
|
47
|
+
export type { Oklch } from './oklch';
|
|
48
|
+
|
|
49
|
+
export { initializeTokens } from './tokenInit';
|
|
50
|
+
|
|
51
|
+
export { resolvePageSource } from './pageSource';
|