@motion-proto/live-tokens 0.67.1 → 0.68.1

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 (63) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/bin/generate-theme.mjs +5 -4
  3. package/dist-plugin/adjust/index.d.cts +1 -1
  4. package/dist-plugin/adjust/index.d.ts +1 -1
  5. package/dist-plugin/{chunk-J2JT4UEA.js → chunk-2UX6EVVA.js} +25 -25
  6. package/dist-plugin/{dataPaths-DZUzVv8H.d.cts → dataPaths-bJTCEO4H.d.cts} +1 -1
  7. package/dist-plugin/{dataPaths-DZUzVv8H.d.ts → dataPaths-bJTCEO4H.d.ts} +1 -1
  8. package/dist-plugin/fontPairing/index.d.cts +1 -1
  9. package/dist-plugin/fontPairing/index.d.ts +1 -1
  10. package/dist-plugin/generateColorsAndType/index.d.cts +1 -1
  11. package/dist-plugin/generateColorsAndType/index.d.ts +1 -1
  12. package/dist-plugin/index.cjs +27 -27
  13. package/dist-plugin/index.d.cts +1 -1
  14. package/dist-plugin/index.d.ts +1 -1
  15. package/dist-plugin/index.js +5 -5
  16. package/dist-plugin/migrateData/index.cjs +25 -25
  17. package/dist-plugin/migrateData/index.d.cts +1 -1
  18. package/dist-plugin/migrateData/index.d.ts +1 -1
  19. package/dist-plugin/migrateData/index.js +1 -1
  20. package/dist-plugin/tokensCssMigrations/index.d.cts +1 -1
  21. package/dist-plugin/tokensCssMigrations/index.d.ts +1 -1
  22. package/package.json +4 -4
  23. package/src/editor/bootstrap.ts +6 -6
  24. package/src/editor/component-editor/ImageLightboxEditor.svelte +1 -1
  25. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +4 -4
  26. package/src/editor/core/fonts/fontLoader.ts +2 -2
  27. package/src/editor/core/preview/{lookPreview.ts → themePreview.ts} +27 -27
  28. package/src/editor/core/productionPulse.ts +3 -3
  29. package/src/editor/core/sketch/index.ts +52 -44
  30. package/src/editor/core/sketch/maskField.ts +9 -9
  31. package/src/editor/core/sketch/sketchLayer.ts +9 -9
  32. package/src/editor/core/sketch/sketchRegistry.ts +34 -34
  33. package/src/editor/core/sketch/sketchStore.ts +98 -83
  34. package/src/editor/core/sketch/sketchStyleService.ts +4 -4
  35. package/src/editor/core/sketch/sketchStyles.ts +28 -28
  36. package/src/editor/core/themes/colorsAndTypeService.ts +1 -1
  37. package/src/editor/core/themes/loadRows.ts +8 -8
  38. package/src/editor/core/themes/themeDocumentSync.ts +2 -2
  39. package/src/editor/core/themes/themeInit.ts +2 -2
  40. package/src/editor/core/themes/themeService.ts +17 -17
  41. package/src/editor/core/themes/{lookSummary.ts → themeSummary.ts} +9 -9
  42. package/src/editor/core/themes/themeTypes.ts +10 -6
  43. package/src/editor/docs/content/getting-started.md +1 -1
  44. package/src/editor/docs/content/sketch-mode.md +22 -17
  45. package/src/editor/docs/content/themes-workflow.md +11 -11
  46. package/src/editor/docs/content/where-themes-live.md +6 -6
  47. package/src/editor/docs/content.generated.ts +4 -4
  48. package/src/editor/index.ts +2 -2
  49. package/src/editor/ui/ThemePanel.svelte +52 -52
  50. package/src/editor/ui/sketch/SketchPreview.svelte +2 -2
  51. package/src/editor/ui/sketch/SketchTab.svelte +64 -38
  52. package/src/live-tokens/data/sketch-styles/dry.json +5 -5
  53. package/src/live-tokens/data/sketch-styles/hatched.json +9 -9
  54. package/src/live-tokens/data/themes/autumn.json +1 -1
  55. package/src/live-tokens/data/themes/halloween.json +1 -1
  56. package/src/live-tokens/data/themes/midnight-study.json +1 -1
  57. package/src/live-tokens/data/themes/ocean.json +1 -1
  58. package/src/live-tokens/data/themes/royal-velvet.json +1 -1
  59. package/src/live-tokens/data/themes/sketchy.json +1 -1
  60. package/src/live-tokens/data/themes/spring-meadow.json +1 -1
  61. package/src/live-tokens/data/themes/sunset.json +1 -1
  62. package/src/system/backdrop/backdrop.ts +1 -1
  63. package/src/system/components/SectionDivider.svelte +1 -1
@@ -1,97 +1,97 @@
1
1
  import { derived, get, writable, type Readable } from 'svelte/store';
2
- import { SKETCH_STYLES, hydrateSketchStyle, type SketchStyle } from './sketchStyles';
2
+ import { SHIPPED_SKETCH_SETTINGS, hydrateSketchSettings, type SketchStyleSettings } from './sketchStyles';
3
3
 
4
- /** Where a look came from, which is what decides the affordances on offer.
4
+ /** Where a style came from, which is what decides the affordances on offer.
5
5
  `shipped` is served by the package: it has a file, but not one in this
6
6
  project, so there is nothing here to save over or delete until the editor
7
7
  writes a copy. `file` lives in this project's data tree, so the editor can
8
8
  save over it and delete it. `registered` was handed to `bootLiveTokens`, so
9
9
  it is real on a built site but owns no file the editor could touch. */
10
- export type SketchLookSource = 'shipped' | 'file' | 'registered';
10
+ export type SketchStyleSource = 'shipped' | 'file' | 'registered';
11
11
 
12
- export interface SketchLook {
12
+ export interface SketchStyle {
13
13
  /** What `setSketch` takes. A saved sketchstyle's file slug is its id, so a
14
- look picked in dev keeps working once the site is built. */
14
+ style picked in dev keeps working once the site is built. */
15
15
  id: string;
16
16
  label: string;
17
17
  blurb: string;
18
- settings: SketchStyle;
19
- source: SketchLookSource;
18
+ settings: SketchStyleSettings;
19
+ source: SketchStyleSource;
20
20
  }
21
21
 
22
- export interface RegisterSketchLookInput {
22
+ export interface RegisterSketchStyleInput {
23
23
  id: string;
24
24
  label: string;
25
25
  blurb?: string;
26
26
  settings: unknown;
27
- /** Only `replaceRegisteredLooks` sets this. A listing carries the package's
27
+ /** Only `replaceRegisteredSketchStyles` sets this. A listing carries the package's
28
28
  own sketchstyles as well as the project's, and the two differ in what the
29
29
  editor may do to them, so the row's origin has to survive the trip. */
30
- source?: SketchLookSource;
30
+ source?: SketchStyleSource;
31
31
  }
32
32
 
33
- const SHIPPED: ReadonlyMap<string, SketchLook> = new Map(
34
- Object.entries(SKETCH_STYLES).map(([id, style]) => [
33
+ const SHIPPED: ReadonlyMap<string, SketchStyle> = new Map(
34
+ Object.entries(SHIPPED_SKETCH_SETTINGS).map(([id, settings]) => [
35
35
  id,
36
- { id, label: style.label, blurb: style.blurb, settings: style, source: 'shipped' as const },
36
+ { id, label: settings.label, blurb: settings.blurb, settings, source: 'shipped' as const },
37
37
  ]),
38
38
  );
39
39
 
40
- /** Seeded with the shipped looks, then written by `registerSketchLook`. A Map
40
+ /** Seeded with the shipped styles, then written by `registerSketchStyle`. A Map
41
41
  because re-setting an existing key keeps its position, so a project's own
42
42
  `pencil` lands where the shipped Pencil sat rather than at the end of the
43
43
  grid. */
44
- const looks = writable(new Map(SHIPPED));
44
+ const styles = writable(new Map(SHIPPED));
45
45
 
46
46
  /**
47
- * Every sketchstyle on offer: the shipped looks, plus whatever this project
47
+ * Every sketchstyle on offer: the shipped styles, plus whatever this project
48
48
  * registered over and beside them. A store rather than a constant, because
49
49
  * registration happens after this module is imported.
50
50
  *
51
51
  * A picker adds its own "None" row, since off is a state of the effect rather
52
- * than one of the looks. A theme's own look is not here either; `themeSketchLook`
52
+ * than one of the styles. A theme's own style is not here either; `unsavedSketchStyle`
53
53
  * carries that one row.
54
54
  */
55
- export const sketchLooks: Readable<SketchLook[]> = derived(looks, (m) => [...m.values()]);
55
+ export const sketchStyles: Readable<SketchStyle[]> = derived(styles, (m) => [...m.values()]);
56
56
 
57
- export function lookById(id: string): SketchLook | undefined {
58
- return get(looks).get(id);
57
+ export function sketchStyleById(id: string): SketchStyle | undefined {
58
+ return get(styles).get(id);
59
59
  }
60
60
 
61
- function put(m: Map<string, SketchLook>, look: RegisterSketchLookInput, source: SketchLookSource) {
61
+ function put(m: Map<string, SketchStyle>, style: RegisterSketchStyleInput, source: SketchStyleSource) {
62
62
  // Hydrated on the way in: a consumer hands over raw JSON it imported, so this
63
- // is the only place a look stored under a retired dial name gets carried
63
+ // is the only place a style stored under a retired dial name gets carried
64
64
  // forward, the same job `seedSketchFromTheme` does for a theme.
65
- const settings = hydrateSketchStyle(look.settings);
66
- return m.set(look.id, {
67
- id: look.id,
68
- label: look.label,
69
- blurb: look.blurb ?? settings.blurb ?? '',
65
+ const settings = hydrateSketchSettings(style.settings);
66
+ return m.set(style.id, {
67
+ id: style.id,
68
+ label: style.label,
69
+ blurb: style.blurb ?? settings.blurb ?? '',
70
70
  settings,
71
71
  source,
72
72
  });
73
73
  }
74
74
 
75
- export function registerSketchLook(look: RegisterSketchLookInput): void {
76
- looks.update((m) => put(new Map(m), look, 'registered'));
75
+ export function registerSketchStyle(style: RegisterSketchStyleInput): void {
76
+ styles.update((m) => put(new Map(m), style, 'registered'));
77
77
  }
78
78
 
79
79
  /** Ids the last file listing claimed. The sweep below removes these and nothing
80
- else, so a look handed to `bootLiveTokens` survives the editor opening. */
80
+ else, so a style handed to `bootLiveTokens` survives the editor opening. */
81
81
  let fromFiles: string[] = [];
82
82
 
83
83
  /** The editor re-lists its files after every save and delete, so a file removed
84
- on disk has to leave the pool too. A file that had shadowed a shipped look
84
+ on disk has to leave the pool too. A file that had shadowed a shipped style
85
85
  hands the id back on its way out. */
86
- export function replaceRegisteredLooks(next: RegisterSketchLookInput[]): void {
87
- looks.update((m) => {
86
+ export function replaceRegisteredSketchStyles(next: RegisterSketchStyleInput[]): void {
87
+ styles.update((m) => {
88
88
  const out = new Map(m);
89
89
  for (const id of fromFiles) {
90
90
  const shipped = SHIPPED.get(id);
91
91
  if (shipped) out.set(id, shipped);
92
92
  else out.delete(id);
93
93
  }
94
- for (const look of next) put(out, look, look.source ?? 'file');
94
+ for (const style of next) put(out, style, style.source ?? 'file');
95
95
  return out;
96
96
  });
97
97
  fromFiles = next.map((l) => l.id);
@@ -1,12 +1,12 @@
1
1
  import { derived, get, writable } from 'svelte/store';
2
2
  import {
3
- SKETCH_STYLES,
3
+ SHIPPED_SKETCH_SETTINGS,
4
4
  DEFAULT_SKETCH_STYLE,
5
5
  THEME_SKETCH_ID,
6
- hydrateSketchStyle,
7
- type SketchStyle,
6
+ hydrateSketchSettings,
7
+ type SketchStyleSettings,
8
8
  } from './sketchStyles';
9
- import { lookById, replaceRegisteredLooks, sketchLooks } from './sketchRegistry';
9
+ import { sketchStyleById, replaceRegisteredSketchStyles, sketchStyles } from './sketchRegistry';
10
10
  import {
11
11
  applySketchLayer,
12
12
  hostRoot,
@@ -26,7 +26,9 @@ import {
26
26
 
27
27
  const ENABLED_KEY = 'lt.sketchEnabled';
28
28
  const SETTINGS_KEY = 'lt.sketchSettings';
29
- const STYLE_NAME_KEY = 'lt.sketchStyleName';
29
+ /** The stored key keeps its old spelling. It is a browser's saved selection,
30
+ so renaming the string would drop every visitor's pick on upgrade. */
31
+ const SELECTED_ID_KEY = 'lt.sketchStyleName';
30
32
  const BASELINE_KEY = 'lt.sketchBaseline';
31
33
  /** Set only by a genuine user decision (`markSketchTouched`), never by the
32
34
  `subscribe` write-back below. That write-back plants the four keys above
@@ -66,7 +68,7 @@ export function hasPersistedSketchState(): boolean {
66
68
  }
67
69
 
68
70
  /** Called from every control a user can act on: a dial, a sketchstyle pick, the
69
- on/off switch. Never from `openThemeSketchStyle` or the storage-sync `adopt`
71
+ on/off switch. Never from `openThemeSketchSettings` or the storage-sync `adopt`
70
72
  path, both of which write the same four keys as a side effect of state
71
73
  this browser did not decide on its own. */
72
74
  function markSketchTouched(): void {
@@ -83,20 +85,20 @@ function readEnabled(): boolean {
83
85
  }
84
86
  }
85
87
 
86
- function readSettings(): SketchStyle {
88
+ function readSettings(): SketchStyleSettings {
87
89
  try {
88
90
  const raw = localStorage.getItem(SETTINGS_KEY);
89
- if (raw) return hydrateSketchStyle(JSON.parse(raw));
91
+ if (raw) return hydrateSketchSettings(JSON.parse(raw));
90
92
  } catch {
91
93
  // fall through to the default sketchstyle
92
94
  }
93
- return { ...SKETCH_STYLES[DEFAULT_SKETCH_STYLE] };
95
+ return { ...SHIPPED_SKETCH_SETTINGS[DEFAULT_SKETCH_STYLE] };
94
96
  }
95
97
 
96
- function readBaseline(): SketchStyle | null {
98
+ function readBaseline(): SketchStyleSettings | null {
97
99
  try {
98
100
  const raw = localStorage.getItem(BASELINE_KEY);
99
- if (raw) return hydrateSketchStyle(JSON.parse(raw));
101
+ if (raw) return hydrateSketchSettings(JSON.parse(raw));
100
102
  } catch {
101
103
  // fall through
102
104
  }
@@ -108,14 +110,14 @@ function readBaseline(): SketchStyle | null {
108
110
  it. Stripped on read below; delete a release after that ships. */
109
111
  const RETIRED_USER_PREFIX = 'user:';
110
112
 
111
- /** Deliberately unvalidated. Looks are registered after this module is
113
+ /** Deliberately unvalidated. Sketchstyles are registered after this module is
112
114
  imported, so an id it has never heard of is the normal case rather than a
113
115
  fault: `selectSketchStyle` no-ops on one, and `sketchPick` already reports
114
- a look nothing names as `adjusted`. Only a browser that has stored nothing
116
+ a style nothing names as `adjusted`. Only a browser that has stored nothing
115
117
  falls back. */
116
- function readStyleName(): string {
118
+ function readSelectedId(): string {
117
119
  try {
118
- const name = localStorage.getItem(STYLE_NAME_KEY);
120
+ const name = localStorage.getItem(SELECTED_ID_KEY);
119
121
  if (name !== null) {
120
122
  return name.startsWith(RETIRED_USER_PREFIX) ? name.slice(RETIRED_USER_PREFIX.length) : name;
121
123
  }
@@ -128,39 +130,39 @@ function readStyleName(): string {
128
130
  /** Off by default: the effect paints nothing until the open theme's own
129
131
  sketchstyle, or a decision this browser already recorded, says otherwise. */
130
132
  export const sketchEnabled = writable<boolean>(readEnabled());
131
- export const sketchSettings = writable<SketchStyle>(readSettings());
132
- /** The sketchstyle the dials started from. It survives dial moves, so the grid
133
- keeps showing what the current look is closest to. Any id in the pool, or
134
- `THEME_SKETCH_ID` for the look the open theme carries; empty only when
135
- nothing was picked, or the picked file was deleted. */
136
- export const sketchStyleName = writable<string>(readStyleName());
133
+ export const sketchSettings = writable<SketchStyleSettings>(readSettings());
134
+ /** Id of the sketchstyle the dials started from. It survives dial moves, so the
135
+ grid keeps showing what the current settings are closest to. Any id in the
136
+ pool, or `THEME_SKETCH_ID` for settings the open theme carries that no file
137
+ holds; empty only when nothing was picked, or the picked file was deleted. */
138
+ export const selectedSketchStyleId = writable<string>(readSelectedId());
137
139
 
138
140
  /** The settings as the selected sketchstyle defined them. Kept beside the live
139
141
  settings so "modified" is derived by comparison rather than tracked as a
140
142
  flag: dial a value back to where it started and the tab stops claiming a
141
143
  change, with no bookkeeping to get wrong. */
142
- export const sketchBaseline = writable<SketchStyle | null>(readBaseline());
144
+ export const sketchBaseline = writable<SketchStyleSettings | null>(readBaseline());
143
145
 
144
146
  /** Dial-set fields only. `label` and `blurb` name the sketchstyle rather than
145
- describe the look, and no dial writes them. */
146
- export function sameLook(a: SketchStyle, b: SketchStyle): boolean {
147
- return (Object.keys(a) as (keyof SketchStyle)[])
147
+ describe the style, and no dial writes them. */
148
+ export function sameSketchStyle(a: SketchStyleSettings, b: SketchStyleSettings): boolean {
149
+ return (Object.keys(a) as (keyof SketchStyleSettings)[])
148
150
  .filter((k) => k !== 'label' && k !== 'blurb')
149
151
  .every((k) => a[k] === b[k]);
150
152
  }
151
153
 
152
154
  export const sketchDirty = derived(
153
155
  [sketchSettings, sketchBaseline],
154
- ([settings, baseline]) => baseline !== null && !sameLook(settings, baseline),
156
+ ([settings, baseline]) => baseline !== null && !sameSketchStyle(settings, baseline),
155
157
  );
156
158
 
157
159
  export const sketchBlurb = derived(sketchSettings, (s) => s.blurb);
158
160
 
159
- /** The live look as a theme would carry it: the dials when the effect is
161
+ /** The live style as a theme would carry it: the dials when the effect is
160
162
  on, nothing when it is off (RJC 1). A copy, not the store's own object,
161
163
  so a caller holding onto the result can never observe a later dial move
162
164
  through it. */
163
- export function liveSketchStyle(): SketchStyle | undefined {
165
+ export function liveSketchSettings(): SketchStyleSettings | undefined {
164
166
  return get(sketchEnabled) ? { ...get(sketchSettings) } : undefined;
165
167
  }
166
168
 
@@ -175,25 +177,25 @@ export function setSketchEnabled(enabled: boolean): void {
175
177
 
176
178
  /** What the open theme holds, so "unsaved" is a comparison rather than a
177
179
  flag (RJC 5). Set by every path that opens or saves a theme. */
178
- export const themeSketchStyle = writable<SketchStyle | undefined>(undefined);
180
+ export const themeSketchSettings = writable<SketchStyleSettings | undefined>(undefined);
179
181
 
180
182
  /** Open a theme's sketchstyle: the dials, the on/off state, and the name
181
183
  recovered by comparison (RJC 3). Overwrites the live buffer, which
182
184
  is what opening a theme means everywhere else (RJC 6). The name is recovered
183
- over the whole pool, so a theme carrying a look a saved file also holds is
185
+ over the whole pool, so a theme carrying a style a saved file also holds is
184
186
  named by that file rather than falling back to `THEME_SKETCH_ID`. */
185
- export function openThemeSketchStyle(sketchStyle: SketchStyle | undefined): void {
186
- themeSketchStyle.set(sketchStyle);
187
- if (!sketchStyle) {
187
+ export function openThemeSketchSettings(settings: SketchStyleSettings | undefined): void {
188
+ themeSketchSettings.set(settings);
189
+ if (!settings) {
188
190
  sketchEnabled.set(false);
189
191
  sketchBaseline.set(null);
190
- sketchStyleName.set('');
192
+ selectedSketchStyleId.set('');
191
193
  return;
192
194
  }
193
- const matched = get(sketchLooks).find((look) => sameLook(look.settings, sketchStyle))?.id;
194
- sketchSettings.set({ ...sketchStyle });
195
- sketchBaseline.set({ ...sketchStyle });
196
- sketchStyleName.set(matched ?? THEME_SKETCH_ID);
195
+ const matched = get(sketchStyles).find((style) => sameSketchStyle(style.settings, settings))?.id;
196
+ sketchSettings.set({ ...settings });
197
+ sketchBaseline.set({ ...settings });
198
+ selectedSketchStyleId.set(matched ?? THEME_SKETCH_ID);
197
199
  sketchEnabled.set(true);
198
200
  }
199
201
 
@@ -202,40 +204,40 @@ export function openThemeSketchStyle(sketchStyle: SketchStyle | undefined): void
202
204
  * browser has already decided for itself.
203
205
  *
204
206
  * The rule boot has always followed in dev, and the only one a built site has:
205
- * a visitor who picked a look, or picked None, keeps it, and `themeSketchStyle`
207
+ * a visitor who picked a style, or picked None, keeps it, and `themeSketchSettings`
206
208
  * still learns what the theme holds so the panel can call the difference
207
- * unsaved. Both branches set it, so a picker can offer the theme's look as a
209
+ * unsaved. Both branches set it, so a picker can offer the theme's style as a
208
210
  * row either way.
209
211
  *
210
- * Takes the raw field rather than a `SketchStyle`, and hydrates it here: a
212
+ * Takes the raw field rather than a `SketchStyleSettings`, and hydrates it here: a
211
213
  * built site reads its theme JSON straight off disk with no dev server to run
212
- * `normalizeTheme` over it first, so this is the only place a look stored under
214
+ * `normalizeTheme` over it first, so this is the only place a style stored under
213
215
  * a retired dial name gets carried forward. Anything that is not an object is
214
216
  * the absent case, which is off (invariant 3).
215
217
  */
216
- export function seedSketchFromTheme(sketchStyle: unknown): void {
218
+ export function seedSketchFromTheme(raw: unknown): void {
217
219
  const style =
218
- typeof sketchStyle === 'object' && sketchStyle !== null && !Array.isArray(sketchStyle)
219
- ? hydrateSketchStyle(sketchStyle)
220
+ typeof raw === 'object' && raw !== null && !Array.isArray(raw)
221
+ ? hydrateSketchSettings(raw)
220
222
  : undefined;
221
223
  if (hasPersistedSketchState()) {
222
- themeSketchStyle.set(style);
224
+ themeSketchSettings.set(style);
223
225
  return;
224
226
  }
225
- openThemeSketchStyle(style);
227
+ openThemeSketchSettings(style);
226
228
  }
227
229
 
228
- /** Go back to the look the theme carries, after picking something else. The
229
- theme's is the one look a picker can offer that this module did not ship, so
230
+ /** Go back to the style the theme carries, after picking something else. The
231
+ theme's is the one style a picker can offer that this module did not ship, so
230
232
  it needs a door of its own beside `selectSketchStyle`; `setSketch` gives the
231
233
  two the same face. Silent when the theme carries none, the way
232
234
  `selectSketchStyle` is for a name it does not know. */
233
- export function selectThemeSketchStyle(): void {
234
- const style = get(themeSketchStyle);
235
+ export function selectUnsavedSketchStyle(): void {
236
+ const style = get(themeSketchSettings);
235
237
  if (!style) return;
236
238
  markSketchTouched();
237
239
  if (get(sketchEnabled)) liveMovedSinceBake.set(true);
238
- sketchStyleName.set(THEME_SKETCH_ID);
240
+ selectedSketchStyleId.set(THEME_SKETCH_ID);
239
241
  sketchBaseline.set({ ...style });
240
242
  sketchSettings.set({ ...style });
241
243
  }
@@ -243,13 +245,13 @@ export function selectThemeSketchStyle(): void {
243
245
  /** The live sketch differs from what the open theme carries. Presence is
244
246
  half the comparison: on with dials the theme does not hold, or off while
245
247
  the theme holds a layer, are both off the theme. */
246
- export const sketchOffLook = derived(
247
- [sketchEnabled, sketchSettings, themeSketchStyle],
248
+ export const sketchOffTheme = derived(
249
+ [sketchEnabled, sketchSettings, themeSketchSettings],
248
250
  ([enabled, settings, saved]) => {
249
251
  const live = enabled ? settings : undefined;
250
252
  if (!live && !saved) return false;
251
253
  if (!live || !saved) return true;
252
- return !sameLook(live, saved);
254
+ return !sameSketchStyle(live, saved);
253
255
  },
254
256
  );
255
257
 
@@ -257,13 +259,13 @@ export const sketchOffLook = derived(
257
259
  Silent for an id nothing knows, the way it has always been for an unknown
258
260
  shipped name. */
259
261
  export function selectSketchStyle(id: string): void {
260
- const look = lookById(id);
261
- if (!look) return;
262
+ const style = sketchStyleById(id);
263
+ if (!style) return;
262
264
  markSketchTouched();
263
265
  if (get(sketchEnabled)) liveMovedSinceBake.set(true);
264
- sketchStyleName.set(id);
265
- sketchBaseline.set({ ...look.settings });
266
- sketchSettings.set({ ...look.settings });
266
+ selectedSketchStyleId.set(id);
267
+ sketchBaseline.set({ ...style.settings });
268
+ sketchSettings.set({ ...style.settings });
267
269
  }
268
270
 
269
271
  /** Saved sketchstyles, listed from the data tree. Empty until
@@ -279,7 +281,7 @@ export async function refreshSavedSketchStyles(): Promise<void> {
279
281
  const files = await listSketchStyles();
280
282
  const loaded = await Promise.all(files.map((f) => loadSketchStyle(f.fileName)));
281
283
  savedSketchStyles.set(files);
282
- replaceRegisteredLooks(
284
+ replaceRegisteredSketchStyles(
283
285
  files.map((file, i) => ({
284
286
  id: file.fileName,
285
287
  label: file.name || file.fileName,
@@ -303,23 +305,36 @@ export async function saveCurrentSketchStyle(name: string): Promise<string> {
303
305
  await refreshSavedSketchStyles();
304
306
  sketchSettings.set(settings);
305
307
  sketchBaseline.set({ ...settings });
306
- sketchStyleName.set(fileName);
308
+ selectedSketchStyleId.set(fileName);
307
309
  return fileName;
308
310
  }
309
311
 
310
- /** Overwrite the selected saved sketchstyle. The file name comes from the
311
- selection rather than from re-slugifying the label, because the two can
312
- disagree: a file hand-edited to a new display name would otherwise be saved
313
- beside itself under a fresh slug instead of over itself. The label comes
314
- from the look for the same reason, so the name the grid shows survives.
312
+ /** Overwrite the selected sketchstyle, so a style can be put down and picked up
313
+ again across sessions.
314
+
315
+ The file name comes from the selection rather than from re-slugifying the
316
+ label, because the two can disagree: `dry` is labelled "Dry marker", which
317
+ re-slugified writes `dry-marker.json` and leaves the selected file untouched.
318
+ The label comes from the style for the same reason, so the name the grid shows
319
+ survives.
320
+
321
+ A shipped style saves the same way. Its file lives in the package, so the
322
+ write lands in this project under the same id and shadows it, which is how a
323
+ project comes to own its Pencil; deleting that file uncovers the packaged one
324
+ again. A registered style owns no file at all, so it has none to write over.
315
325
 
316
326
  No `markSketchTouched`: the button only lights once a dial has moved, and
317
327
  every dial goes through `updateSketchSettings`, which marks it. */
318
328
  export async function saveSelectedSketchStyle(): Promise<void> {
319
- const look = lookById(get(sketchStyleName));
320
- if (look?.source !== 'file') throw new Error('No saved sketchstyle is selected');
329
+ const style = sketchStyleById(get(selectedSketchStyleId));
330
+ if (!style) throw new Error('No sketchstyle is selected');
331
+ if (style.source === 'registered') {
332
+ throw new Error(
333
+ `${style.label} was registered in code rather than saved as a file. Use Save As to keep these dials.`,
334
+ );
335
+ }
321
336
  const settings = { ...get(sketchSettings) };
322
- await saveSketchStyle(look.id, look.label, settings);
337
+ await saveSketchStyle(style.id, style.label, settings);
323
338
  await refreshSavedSketchStyles();
324
339
  // Re-baselining is what disables the button again and returns the readout
325
340
  // from "Modified from X" to the saved blurb.
@@ -330,16 +345,16 @@ export async function deleteSavedSketchStyle(fileName: string): Promise<void> {
330
345
  await deleteSketchStyle(fileName);
331
346
  await refreshSavedSketchStyles();
332
347
  // The dials keep their values; only the name stops naming a file that exists.
333
- if (get(sketchStyleName) === fileName) {
334
- sketchStyleName.set('');
348
+ if (get(selectedSketchStyleId) === fileName) {
349
+ selectedSketchStyleId.set('');
335
350
  sketchBaseline.set(null);
336
351
  }
337
352
  }
338
353
 
339
354
  /** Every dial goes through here. The selection deliberately survives: the grid
340
- keeps naming the sketchstyle this look came from, and `sketchDirty` reports
355
+ keeps naming the sketchstyle this style came from, and `sketchDirty` reports
341
356
  the drift. Save writes a new sketchstyle rather than overwriting the base. */
342
- export function updateSketchSettings(patch: Partial<SketchStyle>): void {
357
+ export function updateSketchSettings(patch: Partial<SketchStyleSettings>): void {
343
358
  markSketchTouched();
344
359
  if (get(sketchEnabled)) liveMovedSinceBake.set(true);
345
360
  sketchSettings.update((s) => ({ ...s, ...patch }));
@@ -361,7 +376,7 @@ function persist(key: string, value: string): void {
361
376
  itself between import and first render. */
362
377
  let pageRoot: HTMLElement | null = null;
363
378
 
364
- function render(enabled: boolean, settings: SketchStyle): void {
379
+ function render(enabled: boolean, settings: SketchStyleSettings): void {
365
380
  if (typeof document === 'undefined') return;
366
381
  if (!enabled) {
367
382
  if (sketchLayerInstalled()) removeSketchLayer();
@@ -375,14 +390,14 @@ function render(enabled: boolean, settings: SketchStyle): void {
375
390
  setSketchScope(pageRoot, settings);
376
391
  }
377
392
 
378
- /** Paint one look on the two roots `render` owns, and on nothing else.
393
+ /** Paint one style on the two roots `render` owns, and on nothing else.
379
394
 
380
395
  `render(false, ...)` reaches further: it clears every `[data-sketch]` in
381
396
  the document. The scopes it takes with it belong to `$effect`s that paint
382
397
  from the live stores, which a preview deliberately never ticks, so those
383
398
  never come back. Previewing a theme carrying no sketchstyle left the
384
399
  Sketchstyle view's own stage crisp for good, Cancel included. */
385
- function paintPreviewRoots(style: SketchStyle | undefined): void {
400
+ function paintPreviewRoots(style: SketchStyleSettings | undefined): void {
386
401
  if (typeof document === 'undefined') return;
387
402
  if (style) applySketchLayer(style);
388
403
  setSketchScope(hostRoot(), style ?? null);
@@ -397,7 +412,7 @@ function paintPreviewRoots(style: SketchStyle | undefined): void {
397
412
  host page across the iframe boundary the same way Apply's does; only the
398
413
  store write and the persistence are skipped. Pass `undefined` for a theme
399
414
  that carries no sketchstyle. */
400
- export function previewSketchStyle(style: SketchStyle | undefined): void {
415
+ export function previewSketchStyle(style: SketchStyleSettings | undefined): void {
401
416
  paintPreviewRoots(style);
402
417
  }
403
418
 
@@ -457,7 +472,7 @@ if (typeof document !== 'undefined') {
457
472
  render(get(sketchEnabled), settings);
458
473
  });
459
474
 
460
- sketchStyleName.subscribe((name) => share(STYLE_NAME_KEY, name));
475
+ selectedSketchStyleId.subscribe((name) => share(SELECTED_ID_KEY, name));
461
476
  sketchBaseline.subscribe((b) => share(BASELINE_KEY, b ? JSON.stringify(b) : ''));
462
477
 
463
478
  /* The overlay editor runs in an iframe, so the Sketchstyle view and a
@@ -477,10 +492,10 @@ if (typeof document !== 'undefined') {
477
492
  adopt(SETTINGS_KEY, JSON.stringify(next));
478
493
  sketchSettings.set(next);
479
494
  }
480
- } else if (event.key === STYLE_NAME_KEY) {
481
- const next = readStyleName();
482
- adopt(STYLE_NAME_KEY, next);
483
- if (next !== get(sketchStyleName)) sketchStyleName.set(next);
495
+ } else if (event.key === SELECTED_ID_KEY) {
496
+ const next = readSelectedId();
497
+ adopt(SELECTED_ID_KEY, next);
498
+ if (next !== get(selectedSketchStyleId)) selectedSketchStyleId.set(next);
484
499
  } else if (event.key === BASELINE_KEY) {
485
500
  const current = get(sketchBaseline);
486
501
  if ((event.newValue || '') !== (current ? JSON.stringify(current) : '')) {
@@ -1,11 +1,11 @@
1
1
  import { API_BASE } from '../storage/apiBase';
2
- import { hydrateSketchStyle, type SketchStyle } from './sketchStyles';
2
+ import { hydrateSketchSettings, type SketchStyleSettings } from './sketchStyles';
3
3
 
4
4
  export interface SketchStyleFile {
5
5
  name: string;
6
6
  createdAt?: string;
7
7
  updatedAt?: string;
8
- settings: SketchStyle;
8
+ settings: SketchStyleSettings;
9
9
  }
10
10
 
11
11
  export interface SketchStyleMeta {
@@ -40,13 +40,13 @@ export async function loadSketchStyle(fileName: string): Promise<SketchStyleFile
40
40
  const res = await fetch(`${BASE}/${encodeURIComponent(fileName)}`);
41
41
  if (!res.ok) throw new Error(`Failed to load sketchstyle: ${fileName}`);
42
42
  const body = await res.json();
43
- return { ...body, settings: hydrateSketchStyle(body.settings) };
43
+ return { ...body, settings: hydrateSketchSettings(body.settings) };
44
44
  }
45
45
 
46
46
  export async function saveSketchStyle(
47
47
  fileName: string,
48
48
  name: string,
49
- settings: SketchStyle,
49
+ settings: SketchStyleSettings,
50
50
  ): Promise<void> {
51
51
  const res = await fetch(`${BASE}/${encodeURIComponent(fileName)}`, {
52
52
  method: 'PUT',