@opencode/theme 2.0.8 → 2.0.10

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.
@@ -1,11 +1,36 @@
1
1
  import { RGBA } from "@opentui/core";
2
2
  import { oklchToHex, rgbToOklch } from "./color.js";
3
- import { DEFAULT_CATEGORICAL, DEFAULT_THEME } from "./defaults.js";
3
+ import { DEFAULT_CATEGORICAL } from "./categorical.js";
4
4
  import { HueStep } from "./schema.js";
5
5
  const chromaticHues = ["red", "orange", "yellow", "green", "cyan", "blue", "purple"];
6
6
  const categoricalTokens = ["secondary", "accent", "success", "warning", "primary", "error"];
7
7
  const minimumChroma = 0.03;
8
8
  const lightThreshold = 0.6;
9
+ // Canonical swatches copied from the original default-theme classifier keep V1 migration self-contained.
10
+ const hueReferences = {
11
+ light: {
12
+ red: "#fca5a5",
13
+ orange: "#fdba74",
14
+ yellow: "#fde047",
15
+ green: "#86efac",
16
+ cyan: "#67e8f9",
17
+ blue: "#93c5fd",
18
+ purple: "#d8b4fe",
19
+ },
20
+ dark: {
21
+ red: "#b91c1c",
22
+ orange: "#c2410c",
23
+ yellow: "#a16207",
24
+ green: "#15803d",
25
+ cyan: "#0e7490",
26
+ blue: "#1d4ed8",
27
+ purple: "#7e22ce",
28
+ },
29
+ };
30
+ const hueAngles = Object.fromEntries(Object.entries(hueReferences).map(([level, colors]) => [
31
+ level,
32
+ Object.fromEntries(Object.entries(colors).map(([name, color]) => [name, toOklch(RGBA.fromHex(color)).h])),
33
+ ]));
9
34
  export function migrateV1(theme) {
10
35
  const light = resolveV1(theme, "light");
11
36
  const dark = resolveV1(theme, "dark");
@@ -13,18 +38,24 @@ export function migrateV1(theme) {
13
38
  const lightMode = detectMode(light);
14
39
  const darkMode = detectMode(dark);
15
40
  if (lightMode === darkMode) {
41
+ const definition = migrateMode(lightMode === "light" ? light : dark, lightMode);
16
42
  if (lightMode === "light")
17
- return { version: 2, standalone: true, light: migrateMode(light, "light") };
18
- return { version: 2, standalone: true, dark: migrateMode(dark, "dark") };
43
+ return { base: base(definition), light: { hue: definition.hue } };
44
+ return { base: base(definition), dark: { hue: definition.hue } };
19
45
  }
20
46
  }
47
+ const lightDefinition = migrateMode(light, "light");
48
+ const darkDefinition = migrateMode(dark, "dark");
21
49
  return {
22
- version: 2,
23
- standalone: true,
24
- light: migrateMode(light, "light"),
25
- dark: migrateMode(dark, "dark"),
50
+ base: base(lightDefinition),
51
+ light: { hue: lightDefinition.hue },
52
+ dark: darkDefinition,
26
53
  };
27
54
  }
55
+ function base(definition) {
56
+ const { hue: _, ...base } = definition;
57
+ return base;
58
+ }
28
59
  function detectMode(theme) {
29
60
  return luminance(theme.text) > luminance(theme.background) ? "dark" : "light";
30
61
  }
@@ -35,22 +66,22 @@ function migrateMode(theme, mode) {
35
66
  const color = (key) => hex(theme[key]);
36
67
  const selected = hex(selectedForeground(theme, theme.primary));
37
68
  const destructive = hex(selectedForeground(theme, theme.error));
38
- const hues = inferHues(theme, mode);
69
+ const hues = inferHues(theme);
39
70
  const categorical = categoricalTokens.flatMap((token) => {
40
71
  const hue = hues.byToken[token];
41
72
  return hue ? [hue] : [];
42
73
  });
43
74
  const uniqueCategorical = categorical.filter((hue, index) => categorical.indexOf(hue) === index);
44
- const text = mode === "light" ? "$hue.neutral.800" : "$hue.neutral.200";
45
- const textMuted = mode === "light" ? "$hue.neutral.600" : "$hue.neutral.400";
46
- const primary = mode === "light" ? "$hue.interactive.800" : "$hue.interactive.200";
47
- const background = mode === "light" ? "$hue.neutral.200" : "$hue.neutral.800";
48
- const backgroundPanel = mode === "light" ? "$hue.neutral.300" : "$hue.neutral.700";
49
- const backgroundMenu = mode === "light" ? "$hue.neutral.400" : "$hue.neutral.600";
75
+ const text = "$hue.neutral.200";
76
+ const textMuted = "$hue.neutral.400";
77
+ const primary = "$hue.interactive.200";
78
+ const background = "$hue.neutral.800";
79
+ const backgroundPanel = "$hue.neutral.700";
80
+ const backgroundMenu = "$hue.neutral.600";
50
81
  const backgroundRaisedMax = "$hue.neutral.500";
51
82
  return referenceHues({
52
83
  hue: {
53
- gray: neutralScale(theme, mode),
84
+ gray: neutralScale(theme),
54
85
  ...Object.fromEntries(chromaticHues.map((name) => {
55
86
  const match = hues.byHue[name];
56
87
  return [name, match ? hueScale(match.color, mode) : "$hue.gray"];
@@ -61,20 +92,20 @@ function migrateMode(theme, mode) {
61
92
  },
62
93
  categorical: uniqueCategorical.length ? uniqueCategorical : DEFAULT_CATEGORICAL,
63
94
  text: {
64
- default: text,
65
- subdued: textMuted,
95
+ base: text,
96
+ muted: textMuted,
66
97
  action: {
67
98
  primary: {
68
- default: "$text.default",
99
+ base: "$text.base",
69
100
  $disabled: textMuted,
70
101
  $focused: selected,
71
102
  $selected: primary,
72
103
  },
73
- secondary: { default: "$text.subdued", $hovered: "$text.default" },
74
- destructive: { default: destructive, $disabled: textMuted },
104
+ secondary: { base: "$text.muted", $hovered: "$text.base" },
105
+ destructive: { base: destructive, $disabled: textMuted },
75
106
  },
76
107
  formfield: {
77
- default: text,
108
+ base: text,
78
109
  $hovered: primary,
79
110
  $focused: primary,
80
111
  $pressed: primary,
@@ -82,36 +113,36 @@ function migrateMode(theme, mode) {
82
113
  $selected: primary,
83
114
  },
84
115
  feedback: {
85
- error: { default: color("error") },
86
- warning: { default: color("warning") },
87
- success: { default: color("success") },
88
- info: { default: color("info") },
116
+ error: { base: color("error") },
117
+ warning: { base: color("warning") },
118
+ success: { base: color("success") },
119
+ info: { base: color("info") },
89
120
  },
90
121
  },
91
122
  background: {
92
- default: background,
123
+ base: background,
93
124
  raised: {
94
125
  base: backgroundPanel,
95
126
  high: backgroundMenu,
96
127
  max: backgroundRaisedMax,
97
128
  },
98
129
  action: {
99
- primary: { default: "transparent", $hovered: backgroundPanel, $focused: primary, $selected: "transparent" },
100
- secondary: { default: "transparent" },
101
- destructive: { default: color("error") },
130
+ primary: { base: "transparent", $hovered: backgroundPanel, $focused: primary, $selected: "transparent" },
131
+ secondary: { base: "transparent" },
132
+ destructive: { base: color("error") },
102
133
  },
103
134
  formfield: {
104
- default: "$background.default",
135
+ base: "$background.base",
105
136
  },
106
137
  feedback: {
107
- error: { default: "$background.default" },
108
- warning: { default: "$background.default" },
109
- success: { default: "$background.default" },
110
- info: { default: "$background.default" },
138
+ error: { base: "$background.base" },
139
+ warning: { base: "$background.base" },
140
+ success: { base: "$background.base" },
141
+ info: { base: "$background.base" },
111
142
  },
112
143
  },
113
- border: { default: color("border") },
114
- scrollbar: { default: color("borderActive") },
144
+ border: { base: color("border") },
145
+ scrollbar: { base: color("borderActive") },
115
146
  diff: {
116
147
  text: {
117
148
  added: color("diffAdded"),
@@ -160,13 +191,12 @@ function migrateMode(theme, mode) {
160
191
  imageText: color("markdownImageText"),
161
192
  codeBlock: color("markdownCodeBlock"),
162
193
  },
163
- "@context:elevated": {
194
+ "@dialog": {
164
195
  background: {
165
- default: "$background.raised.base",
196
+ base: "$background.raised.base",
166
197
  action: { primary: { $hovered: "$background.raised.high" } },
167
198
  },
168
199
  },
169
- "@context:overlay": { background: { default: "$background.raised.high" } },
170
200
  });
171
201
  }
172
202
  function referenceHues(theme) {
@@ -221,7 +251,7 @@ function referenceHues(theme) {
221
251
  }
222
252
  return Object.fromEntries(Object.entries(theme).map(([key, value]) => [key, key === "hue" || key === "categorical" ? value : replace(value)]));
223
253
  }
224
- function inferHues(theme, mode) {
254
+ function inferHues(theme) {
225
255
  const colors = [
226
256
  ["accent", theme.accent],
227
257
  ["success", theme.success],
@@ -232,7 +262,7 @@ function inferHues(theme, mode) {
232
262
  ["secondary", theme.secondary],
233
263
  ];
234
264
  const inferred = colors.reduce((result, [token, color]) => {
235
- const nearest = inferHue(color, mode);
265
+ const nearest = inferHue(color);
236
266
  if (!nearest)
237
267
  return result;
238
268
  const current = result.byHue[nearest.name];
@@ -247,7 +277,7 @@ function inferHues(theme, mode) {
247
277
  ["accent", theme.accent],
248
278
  ["primary", theme.primary],
249
279
  ].reduce((result, [token, color]) => {
250
- const nearest = inferHue(color, mode);
280
+ const nearest = inferHue(color);
251
281
  if (!nearest)
252
282
  return result;
253
283
  return {
@@ -256,21 +286,18 @@ function inferHues(theme, mode) {
256
286
  };
257
287
  }, inferred);
258
288
  }
259
- function inferHue(color, mode) {
289
+ function inferHue(color) {
260
290
  const value = toOklch(color);
261
291
  if (ambiguous(color, value.c))
262
292
  return;
263
- const anchor = inferenceAnchor(value.l);
293
+ const reference = value.l >= lightThreshold ? hueAngles.light : hueAngles.dark;
264
294
  return chromaticHues
265
295
  .map((name) => ({
266
296
  name,
267
- distance: hueDistance(value.h, toOklch(RGBA.fromHex(DEFAULT_THEME[mode].hue[name][anchor])).h),
297
+ distance: hueDistance(value.h, reference[name]),
268
298
  }))
269
299
  .sort((first, second) => first.distance - second.distance)[0];
270
300
  }
271
- function inferenceAnchor(lightness) {
272
- return lightness >= lightThreshold ? 300 : 700;
273
- }
274
301
  function hueDistance(first, second) {
275
302
  const difference = Math.abs(first - second);
276
303
  return Math.min(difference, 360 - difference);
@@ -328,13 +355,13 @@ function selectedForeground(theme, background) {
328
355
  }
329
356
  function hueScale(color, mode) {
330
357
  const value = toOklch(color);
331
- const anchor = mode === "light" ? 800 : 200;
358
+ const anchor = 200;
332
359
  const endpoint = mode === "light" ? Math.max(0.97, value.l) : Math.min(0.18, value.l);
333
360
  const alpha = color.toInts()[3];
334
361
  return Object.fromEntries(HueStep.literals.map((step) => {
335
362
  if (step === anchor)
336
363
  return [step, hex(color)];
337
- const progress = mode === "light" ? (anchor - step) / (anchor - 100) : (step - anchor) / (900 - anchor);
364
+ const progress = (step - anchor) / (900 - anchor);
338
365
  const generated = oklchToHex({
339
366
  l: value.l + (endpoint - value.l) * progress,
340
367
  c: value.c * (1 - progress * 0.5),
@@ -343,8 +370,8 @@ function hueScale(color, mode) {
343
370
  return [step, alpha === 255 ? generated : `${generated}${byte(alpha)}`];
344
371
  }));
345
372
  }
346
- function neutralScale(theme, mode) {
347
- const anchors = neutralAnchors(theme, mode);
373
+ function neutralScale(theme) {
374
+ const anchors = neutralAnchors(theme);
348
375
  return Object.fromEntries(HueStep.literals.map((step) => {
349
376
  const exact = anchors.find((anchor) => anchor.step === step);
350
377
  if (exact)
@@ -359,7 +386,7 @@ function neutralScale(theme, mode) {
359
386
  return [step, interpolate(lower.color, upper.color, (step - lower.step) / (upper.step - lower.step))];
360
387
  }));
361
388
  }
362
- function neutralAnchors(theme, mode) {
389
+ function neutralAnchors(theme) {
363
390
  const light = [
364
391
  { step: 200, color: theme.background },
365
392
  { step: 300, color: theme.backgroundPanel },
@@ -367,8 +394,6 @@ function neutralAnchors(theme, mode) {
367
394
  { step: 600, color: theme.textMuted },
368
395
  { step: 800, color: theme.text },
369
396
  ];
370
- if (mode === "light")
371
- return light;
372
397
  return light.toReversed().map((source) => ({ ...source, step: (1000 - source.step) }));
373
398
  }
374
399
  function interpolate(first, second, amount) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@opencode/theme",
4
- "version": "2.0.8",
4
+ "version": "2.0.10",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {