@trops/dash-core 0.1.574 → 0.1.576

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.
@@ -50817,7 +50817,7 @@ function parseColor(str) {
50817
50817
  /^hsla?\(\s*([\d.]+)\s*[,/\s]\s*([\d.]+)%\s*[,/\s]\s*([\d.]+)%/,
50818
50818
  );
50819
50819
  if (hslMatch) {
50820
- return hslToRgb(
50820
+ return hslToRgb$1(
50821
50821
  parseFloat(hslMatch[1]),
50822
50822
  parseFloat(hslMatch[2]),
50823
50823
  parseFloat(hslMatch[3]),
@@ -50831,7 +50831,7 @@ function parseColor(str) {
50831
50831
  }
50832
50832
 
50833
50833
  /** HSL (h: 0-360, s: 0-100, l: 0-100) → { r, g, b } (0-255) */
50834
- function hslToRgb(h, s, l) {
50834
+ function hslToRgb$1(h, s, l) {
50835
50835
  s /= 100;
50836
50836
  l /= 100;
50837
50837
  const k = (n) => (n + h / 30) % 12;
@@ -50846,7 +50846,7 @@ function hslToRgb(h, s, l) {
50846
50846
  }
50847
50847
 
50848
50848
  /** { r, g, b } → hex string "#rrggbb" */
50849
- function rgbToHex({ r, g, b }) {
50849
+ function rgbToHex$1({ r, g, b }) {
50850
50850
  return (
50851
50851
  "#" +
50852
50852
  [r, g, b]
@@ -50856,7 +50856,7 @@ function rgbToHex({ r, g, b }) {
50856
50856
  }
50857
50857
 
50858
50858
  /** { r, g, b } (0-255) → { h, s, l } (h: 0-360, s/l: 0-100) */
50859
- function rgbToHsl$1({ r, g, b }) {
50859
+ function rgbToHsl$2({ r, g, b }) {
50860
50860
  r /= 255;
50861
50861
  g /= 255;
50862
50862
  b /= 255;
@@ -50929,7 +50929,7 @@ function extractMetaColors(htmlContent) {
50929
50929
  const rgb = parseColor(themeColorMatch[1]);
50930
50930
  if (rgb)
50931
50931
  results.push({
50932
- hex: rgbToHex(rgb),
50932
+ hex: rgbToHex$1(rgb),
50933
50933
  source: "meta",
50934
50934
  confidence: 1.0,
50935
50935
  });
@@ -50943,7 +50943,7 @@ function extractMetaColors(htmlContent) {
50943
50943
  const rgb = parseColor(themeColorMatch2[1]);
50944
50944
  if (rgb)
50945
50945
  results.push({
50946
- hex: rgbToHex(rgb),
50946
+ hex: rgbToHex$1(rgb),
50947
50947
  source: "meta",
50948
50948
  confidence: 1.0,
50949
50949
  });
@@ -50957,7 +50957,7 @@ function extractMetaColors(htmlContent) {
50957
50957
  const rgb = parseColor(tileColorMatch[1]);
50958
50958
  if (rgb)
50959
50959
  results.push({
50960
- hex: rgbToHex(rgb),
50960
+ hex: rgbToHex$1(rgb),
50961
50961
  source: "meta",
50962
50962
  confidence: 0.9,
50963
50963
  });
@@ -50971,7 +50971,7 @@ function extractMetaColors(htmlContent) {
50971
50971
  const rgb = parseColor(tileColorMatch2[1]);
50972
50972
  if (rgb)
50973
50973
  results.push({
50974
- hex: rgbToHex(rgb),
50974
+ hex: rgbToHex$1(rgb),
50975
50975
  source: "meta",
50976
50976
  confidence: 0.9,
50977
50977
  });
@@ -50998,7 +50998,7 @@ function extractCssVarColors(cssContent) {
50998
50998
  const rgb = parseColor(value);
50999
50999
  if (rgb) {
51000
51000
  results.push({
51001
- hex: rgbToHex(rgb),
51001
+ hex: rgbToHex$1(rgb),
51002
51002
  source: "cssvar",
51003
51003
  confidence: 0.8,
51004
51004
  });
@@ -51021,7 +51021,7 @@ function extractCssVarColors(cssContent) {
51021
51021
  const rgb = parseColor(decl.value);
51022
51022
  if (rgb) {
51023
51023
  results.push({
51024
- hex: rgbToHex(rgb),
51024
+ hex: rgbToHex$1(rgb),
51025
51025
  source: "cssvar",
51026
51026
  confidence: 0.8,
51027
51027
  });
@@ -51055,7 +51055,7 @@ function extractComputedColors(computedStyles) {
51055
51055
  const rgb = parseColor(styles[prop]);
51056
51056
  if (rgb) {
51057
51057
  results.push({
51058
- hex: rgbToHex(rgb),
51058
+ hex: rgbToHex$1(rgb),
51059
51059
  source: "computed",
51060
51060
  confidence: 0.6,
51061
51061
  });
@@ -51079,7 +51079,7 @@ function extractDomColors(domColors) {
51079
51079
  const rgb = parseColor(entry.color);
51080
51080
  if (!rgb) continue;
51081
51081
  results.push({
51082
- hex: rgbToHex(rgb),
51082
+ hex: rgbToHex$1(rgb),
51083
51083
  source: "dom",
51084
51084
  confidence: 0.7,
51085
51085
  count: entry.count,
@@ -51243,7 +51243,7 @@ async function extractFaviconColors(htmlContent, baseUrl) {
51243
51243
  const swatch = palette[name];
51244
51244
  if (!swatch) continue;
51245
51245
  const [r, g, b] = swatch.rgb;
51246
- const hex = rgbToHex({
51246
+ const hex = rgbToHex$1({
51247
51247
  r: Math.round(r),
51248
51248
  g: Math.round(g),
51249
51249
  b: Math.round(b),
@@ -51281,7 +51281,7 @@ async function extractFaviconColors(htmlContent, baseUrl) {
51281
51281
  function isBoringColor(hex) {
51282
51282
  const rgb = parseColor(hex);
51283
51283
  if (!rgb) return true;
51284
- const hsl = rgbToHsl$1(rgb);
51284
+ const hsl = rgbToHsl$2(rgb);
51285
51285
 
51286
51286
  // Near-black or near-white
51287
51287
  if (hsl.l < 5 || hsl.l > 95) return true;
@@ -51352,7 +51352,7 @@ function mergeAndRank(allColors, maxColors = 6) {
51352
51352
 
51353
51353
  // Score: confidence * frequency weight * saturation bonus
51354
51354
  const scored = interesting.map((c) => {
51355
- const hsl = rgbToHsl$1(c.rgb);
51355
+ const hsl = rgbToHsl$2(c.rgb);
51356
51356
  const freqWeight = Math.min(c.count / 3, 1.5); // cap frequency bonus
51357
51357
  const satBonus = 1 + hsl.s / 200; // slight boost for saturated colors
51358
51358
  return {
@@ -51382,7 +51382,7 @@ function mergeAndRank(allColors, maxColors = 6) {
51382
51382
  // append the top boring color as a candidate for the neutral role
51383
51383
  if (boring.length > 0 && palette.length < maxColors) {
51384
51384
  const bestBoring = boring.sort((a, b) => b.confidence - a.confidence)[0];
51385
- const hsl = rgbToHsl$1(bestBoring.rgb);
51385
+ const hsl = rgbToHsl$2(bestBoring.rgb);
51386
51386
  palette.push({
51387
51387
  hex: bestBoring.hex,
51388
51388
  rgb: {
@@ -51572,6 +51572,138 @@ var require$$8 = /*@__PURE__*/getAugmentedNamespace(colorUtils);
51572
51572
  * Utility for generating themes via presets, random generation,
51573
51573
  * and color-harmony-based generation from a user-selected base color.
51574
51574
  */
51575
+ // All color math is inlined here (no @trops/dash-react import). This
51576
+ // file is reachable from Electron's main process via
51577
+ // `electron/controller/paletteToThemeMapper.js`. Importing dash-react
51578
+ // from a main-process reachable file pulls dash-react's renderer
51579
+ // bundle into the main bundle — that bundle has top-level browser
51580
+ // API access (`window.ResizeObserver` in CodeEditorVS) which throws
51581
+ // when required under Node. Keeping this module pure-JS / no-deps
51582
+ // breaks the chain.
51583
+
51584
+ const HEX_RE = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
51585
+ function isHexColor(value) {
51586
+ return typeof value === "string" && HEX_RE.test(value.trim());
51587
+ }
51588
+ function normalizeHex(input) {
51589
+ if (typeof input !== "string") return null;
51590
+ let s = input.trim().toLowerCase();
51591
+ if (!s.startsWith("#")) s = `#${s}`;
51592
+ if (!HEX_RE.test(s)) return null;
51593
+ if (s.length === 4) s = `#${s[1]}${s[1]}${s[2]}${s[2]}${s[3]}${s[3]}`;
51594
+ else if (s.length === 5) s = `#${s[1]}${s[1]}${s[2]}${s[2]}${s[3]}${s[3]}`;
51595
+ else if (s.length === 9) s = s.slice(0, 7);
51596
+ return s;
51597
+ }
51598
+ function hexToRgb(hex) {
51599
+ const n = normalizeHex(hex);
51600
+ if (!n) return null;
51601
+ const v = parseInt(n.slice(1), 16);
51602
+ return { r: (v >> 16) & 0xff, g: (v >> 8) & 0xff, b: v & 0xff };
51603
+ }
51604
+ function rgbToHex({ r, g, b }) {
51605
+ const c = (x) => Math.max(0, Math.min(255, Math.round(x)));
51606
+ const h = (x) => c(x).toString(16).padStart(2, "0");
51607
+ return `#${h(r)}${h(g)}${h(b)}`;
51608
+ }
51609
+ function rgbToHsl$1({ r, g, b }) {
51610
+ const R = r / 255,
51611
+ G = g / 255,
51612
+ B = b / 255;
51613
+ const max = Math.max(R, G, B),
51614
+ min = Math.min(R, G, B);
51615
+ const l = (max + min) / 2;
51616
+ let h = 0,
51617
+ s = 0;
51618
+ if (max !== min) {
51619
+ const d = max - min;
51620
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
51621
+ switch (max) {
51622
+ case R:
51623
+ h = (G - B) / d + (G < B ? 6 : 0);
51624
+ break;
51625
+ case G:
51626
+ h = (B - R) / d + 2;
51627
+ break;
51628
+ default:
51629
+ h = (R - G) / d + 4;
51630
+ }
51631
+ h *= 60;
51632
+ }
51633
+ return { h, s, l };
51634
+ }
51635
+ function hslToRgb({ h, s, l }) {
51636
+ if (s === 0) {
51637
+ const v = l * 255;
51638
+ return { r: v, g: v, b: v };
51639
+ }
51640
+ const c = (1 - Math.abs(2 * l - 1)) * s;
51641
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
51642
+ const m = l - c / 2;
51643
+ let rp, gp, bp;
51644
+ if (h < 60) [rp, gp, bp] = [c, x, 0];
51645
+ else if (h < 120) [rp, gp, bp] = [x, c, 0];
51646
+ else if (h < 180) [rp, gp, bp] = [0, c, x];
51647
+ else if (h < 240) [rp, gp, bp] = [0, x, c];
51648
+ else if (h < 300) [rp, gp, bp] = [x, 0, c];
51649
+ else [rp, gp, bp] = [c, 0, x];
51650
+ return { r: (rp + m) * 255, g: (gp + m) * 255, b: (bp + m) * 255 };
51651
+ }
51652
+
51653
+ // Color harmony helpers — pure JS, no DOM/window access.
51654
+ function rotateHex(hex, deltaH) {
51655
+ const rgb = hexToRgb(hex);
51656
+ if (!rgb) return null;
51657
+ const { h, s, l } = rgbToHsl$1(rgb);
51658
+ const newH = (((h + deltaH) % 360) + 360) % 360;
51659
+ return rgbToHex(hslToRgb({ h: newH, s, l }));
51660
+ }
51661
+ function complementHex(hex) {
51662
+ return rotateHex(hex, 180);
51663
+ }
51664
+ function analogousHexes(hex) {
51665
+ const a = rotateHex(hex, -30);
51666
+ const b = rotateHex(hex, 30);
51667
+ return a && b ? [a, b] : null;
51668
+ }
51669
+ function triadicHexes(hex) {
51670
+ const a = rotateHex(hex, 120);
51671
+ const b = rotateHex(hex, 240);
51672
+ return a && b ? [a, b] : null;
51673
+ }
51674
+ function splitComplementaryHexes(hex) {
51675
+ const a = rotateHex(hex, 150);
51676
+ const b = rotateHex(hex, 210);
51677
+ return a && b ? [a, b] : null;
51678
+ }
51679
+ function tetradicHexes(hex) {
51680
+ const a = rotateHex(hex, 90);
51681
+ const b = rotateHex(hex, 180);
51682
+ const c = rotateHex(hex, 270);
51683
+ return a && b && c ? [a, b, c] : null;
51684
+ }
51685
+ function monochromaticHexes(hex, count = 3) {
51686
+ const rgb = hexToRgb(hex);
51687
+ if (!rgb) return null;
51688
+ const { h, s } = rgbToHsl$1(rgb);
51689
+ const out = [];
51690
+ for (let i = 0; i < count; i++) {
51691
+ const t = count === 1 ? 0.5 : i / (count - 1);
51692
+ const l = 0.15 + t * 0.7;
51693
+ const sAdj = s * (1 - 0.3 * Math.abs(t - 0.5) * 2);
51694
+ out.push(rgbToHex(hslToRgb({ h, s: sAdj, l })));
51695
+ }
51696
+ return out;
51697
+ }
51698
+ function adjustHsl(hex, dH = 0, dS = 0, dL = 0) {
51699
+ const rgb = hexToRgb(hex);
51700
+ if (!rgb) return null;
51701
+ const hsl = rgbToHsl$1(rgb);
51702
+ const h = (((hsl.h + dH) % 360) + 360) % 360;
51703
+ const s = Math.max(0, Math.min(1, hsl.s + dS));
51704
+ const l = Math.max(0, Math.min(1, hsl.l + dL));
51705
+ return rgbToHex(hslToRgb({ h, s, l }));
51706
+ }
51575
51707
 
51576
51708
  // Color wheel mapping for Tailwind palettes (approximate hue angles)
51577
51709
  const COLOR_WHEEL = {
@@ -51772,10 +51904,25 @@ function generateRandomTheme() {
51772
51904
 
51773
51905
  /**
51774
51906
  * Generate a theme using color harmony rules from a base color.
51775
- * @param {string} baseColor - A Tailwind color name (e.g. "blue")
51776
- * @param {"complementary"|"analogous"|"triadic"|"split-complementary"} strategy
51777
- */
51778
- function generateHarmonyTheme(baseColor, strategy = "complementary") {
51907
+ * Accepts either a Tailwind color name OR a hex string. Hex bases
51908
+ * run through dash-react's harmony math and emit hex channels for
51909
+ * every output; named bases use the legacy COLOR_WHEEL mapping.
51910
+ *
51911
+ * @param {string} baseColor - Tailwind color name (e.g. "blue") OR hex
51912
+ * @param {"complementary"|"analogous"|"triadic"|"split-complementary"|"tetradic"|"monochromatic"} strategy
51913
+ * @param {{ dH?: number, dS?: number, dL?: number }} [hslNudge] - optional
51914
+ * HSL offsets applied to every generated channel after the harmony
51915
+ * math. dH in degrees; dS/dL as fractions in [-1..1].
51916
+ */
51917
+ function generateHarmonyTheme(
51918
+ baseColor,
51919
+ strategy = "complementary",
51920
+ hslNudge = null,
51921
+ ) {
51922
+ if (typeof baseColor === "string" && isHexColor(baseColor)) {
51923
+ return generateHexHarmonyTheme(baseColor, strategy, hslNudge);
51924
+ }
51925
+
51779
51926
  const baseHue = COLOR_WHEEL[baseColor];
51780
51927
  if (baseHue === undefined) {
51781
51928
  return generateRandomTheme();
@@ -51815,17 +51962,109 @@ function generateHarmonyTheme(baseColor, strategy = "complementary") {
51815
51962
  }
51816
51963
 
51817
51964
  /**
51818
- * Generate a theme from three independently chosen colors.
51819
- * @param {string} primary - A Tailwind color name
51820
- * @param {string} secondary - A Tailwind color name
51821
- * @param {string} tertiary - A Tailwind color name
51965
+ * Hex-mode harmony generation. Always emits hex channels; supports
51966
+ * all the named harmony strategies plus Monochromatic and Tetradic.
51967
+ * The neutral channel is derived from the base by desaturating
51968
+ * almost fully, keeping the same hue family for a cohesive feel.
51822
51969
  */
51823
- function generateCustomTheme(primary, secondary, tertiary) {
51824
- const neutral = pick(NEUTRAL_COLORS);
51970
+ function generateHexHarmonyTheme(baseHex, strategy, hslNudge) {
51971
+ const base = normalizeHex(baseHex);
51972
+ if (!base) return generateRandomTheme();
51973
+
51974
+ let primary = base;
51975
+ let secondary;
51976
+ let tertiary;
51977
+ let neutral;
51978
+
51979
+ switch (strategy) {
51980
+ case "complementary": {
51981
+ secondary = complementHex(base);
51982
+ const ana = analogousHexes(base);
51983
+ tertiary = ana ? ana[1] : base;
51984
+ break;
51985
+ }
51986
+ case "analogous": {
51987
+ const ana = analogousHexes(base);
51988
+ if (ana) {
51989
+ secondary = ana[0];
51990
+ tertiary = ana[1];
51991
+ }
51992
+ break;
51993
+ }
51994
+ case "triadic": {
51995
+ const tri = triadicHexes(base);
51996
+ if (tri) {
51997
+ secondary = tri[0];
51998
+ tertiary = tri[1];
51999
+ }
52000
+ break;
52001
+ }
52002
+ case "split-complementary": {
52003
+ const split = splitComplementaryHexes(base);
52004
+ if (split) {
52005
+ secondary = split[0];
52006
+ tertiary = split[1];
52007
+ }
52008
+ break;
52009
+ }
52010
+ case "tetradic": {
52011
+ const tet = tetradicHexes(base);
52012
+ if (tet) {
52013
+ secondary = tet[0];
52014
+ tertiary = tet[1];
52015
+ neutral = tet[2];
52016
+ }
52017
+ break;
52018
+ }
52019
+ case "monochromatic": {
52020
+ const mono = monochromaticHexes(base, 3);
52021
+ if (mono) {
52022
+ primary = mono[1];
52023
+ secondary = mono[0];
52024
+ tertiary = mono[2];
52025
+ }
52026
+ break;
52027
+ }
52028
+ default: {
52029
+ secondary = complementHex(base);
52030
+ const ana = analogousHexes(base);
52031
+ tertiary = ana ? ana[1] : base;
52032
+ }
52033
+ }
52034
+
52035
+ // Neutral defaults to a near-grey of the same hue (desaturated).
52036
+ if (!neutral) {
52037
+ neutral = adjustHsl(base, 0, -0.85, 0);
52038
+ }
52039
+
52040
+ // Apply optional HSL nudge uniformly to all channels.
52041
+ if (hslNudge) {
52042
+ const { dH = 0, dS = 0, dL = 0 } = hslNudge;
52043
+ primary = adjustHsl(primary, dH, dS, dL) || primary;
52044
+ secondary = adjustHsl(secondary, dH, dS, dL) || secondary;
52045
+ tertiary = adjustHsl(tertiary, dH, dS, dL) || tertiary;
52046
+ neutral = adjustHsl(neutral, dH, dS, dL) || neutral;
52047
+ }
52048
+
51825
52049
  const name = generateThemeName$1(primary, secondary);
51826
52050
  return buildRawTheme(name, primary, secondary, tertiary, neutral);
51827
52051
  }
51828
52052
 
52053
+ /**
52054
+ * Generate a theme from four independently chosen channel values.
52055
+ * Each channel may be a Tailwind name or a hex string.
52056
+ */
52057
+ function generateCustomTheme(
52058
+ primary,
52059
+ secondary,
52060
+ tertiary,
52061
+ neutral = null,
52062
+ ) {
52063
+ const resolvedNeutral = neutral || pick(NEUTRAL_COLORS);
52064
+ const name = generateThemeName$1(primary, secondary);
52065
+ return buildRawTheme(name, primary, secondary, tertiary, resolvedNeutral);
52066
+ }
52067
+
51829
52068
  /**
51830
52069
  * All available chromatic colors for the "From Color" picker.
51831
52070
  */
@@ -51839,6 +52078,8 @@ const HARMONY_STRATEGIES = [
51839
52078
  { value: "analogous", label: "Analogous" },
51840
52079
  { value: "triadic", label: "Triadic" },
51841
52080
  { value: "split-complementary", label: "Split Complementary" },
52081
+ { value: "tetradic", label: "Tetradic" },
52082
+ { value: "monochromatic", label: "Monochromatic" },
51842
52083
  { value: "custom", label: "Custom" },
51843
52084
  ];
51844
52085