@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.
package/dist/index.esm.js CHANGED
@@ -2,9 +2,9 @@ import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
2
2
  import _typeof from '@babel/runtime/helpers/typeof';
3
3
  import _defineProperty from '@babel/runtime/helpers/defineProperty';
4
4
  import * as DashReact from '@trops/dash-react';
5
- import { isObject, ThemeContext, Modal, FontAwesomeIcon, deepCopy, MainSection, getUUID, getStylesForItem, themeObjects, Heading, SearchInput, ButtonIcon, SubHeading3, InputText, Button, Tag, Button3, Button2, Paragraph, Sidebar, Panel, Stepper, Tag3, Card2, Card3, Heading3, MenuItem3, FormLabel, SelectMenu, Switch, SelectInput, CodeEditorVS, SettingsModal, SubHeading2, tailwindHeightFractions, Menu3, Panel3, ButtonIcon2, DropdownPanel, MenuItem2, DragComponent, ConfirmationModal, DropComponent, getStyleName, capitalizeFirstLetter, isHexColor, deriveShades, colorTypes, TAILWIND_PALETTE, Toggle, normalizeHex, contrastRatio, Caption2, Caption, getColorFamilies, getCuratedColorGrid, DashPanel, themeVariants, SubHeading, Heading2, Paragraph2, Paragraph3, ButtonIcon3, Tag2, Alert, Alert2, Alert3, Toast, Toast2, Toast3, ProgressBar, ProgressBar2, ProgressBar3, MenuItem, Breadcrumbs, Breadcrumbs2, Breadcrumbs3, Caption3, Code, Code2, Code3, Divider, Divider2, Divider3, DashPanel2, DashPanel3, Tabs, Tabs2, Tabs3, Accordion, Accordion2, Accordion3, Toggle2, Toggle3, TextArea, Checkbox, Slider, RadioGroup, AlertBanner, StatusBadge, Skeleton, StatCard, DataList, EmptyState, Card, Panel2, ThemeFromUrlPane, CodeEditorInline, Icon2, AlgoliaSearchBox, CommandPalette, useSidebar, Navbar, withRouter, Menu as Menu$1 } from '@trops/dash-react';
5
+ import { isObject, ThemeContext, Modal, FontAwesomeIcon, deepCopy, MainSection, getUUID, getStylesForItem, themeObjects, Heading, SearchInput, ButtonIcon, SubHeading3, InputText, Button, Tag, Button3, Button2, Paragraph, Sidebar, Panel, Stepper, Tag3, Card2, Card3, Heading3, MenuItem3, FormLabel, SelectMenu, Switch, SelectInput, CodeEditorVS, SettingsModal, SubHeading2, tailwindHeightFractions, Menu3, Panel3, ButtonIcon2, DropdownPanel, MenuItem2, DragComponent, ConfirmationModal, DropComponent, getStyleName, capitalizeFirstLetter, isHexColor as isHexColor$1, deriveShades, colorTypes, TAILWIND_PALETTE, Toggle, normalizeHex as normalizeHex$1, contrastRatio, Caption2, Caption, getColorFamilies, getCuratedColorGrid, DashPanel, themeVariants, SubHeading, Heading2, Paragraph2, Paragraph3, ButtonIcon3, Tag2, Alert, Alert2, Alert3, Toast, Toast2, Toast3, ProgressBar, ProgressBar2, ProgressBar3, MenuItem, Breadcrumbs, Breadcrumbs2, Breadcrumbs3, Caption3, Code, Code2, Code3, Divider, Divider2, Divider3, DashPanel2, DashPanel3, Tabs, Tabs2, Tabs3, Accordion, Accordion2, Accordion3, Toggle2, Toggle3, TextArea, Checkbox, Slider, RadioGroup, AlertBanner, StatusBadge, Skeleton, StatCard, DataList, EmptyState, Card, Panel2, ThemeFromUrlPane, CodeEditorInline, Icon2, AlgoliaSearchBox, CommandPalette, useSidebar, Navbar, withRouter, Menu as Menu$1 } from '@trops/dash-react';
6
6
  export * from '@trops/dash-react';
7
- export { ThemeContext } from '@trops/dash-react';
7
+ export { FontAwesomeIcon, ThemeContext } from '@trops/dash-react';
8
8
  import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray';
9
9
  import * as React from 'react';
10
10
  import React__default, { createContext, useContext, useState, useEffect, useMemo, useCallback, useRef, Fragment as Fragment$1, useReducer, Component, memo, Profiler } from 'react';
@@ -28902,7 +28902,7 @@ function invert(shade) {
28902
28902
  function classFor(prefix, type, shade, channelValue) {
28903
28903
  var hover = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
28904
28904
  var h = hover ? "hover:" : "";
28905
- if (isHexColor(channelValue)) {
28905
+ if (isHexColor$1(channelValue)) {
28906
28906
  return "".concat(h).concat(prefix, "-[var(--").concat(type, "-").concat(shade, ")]");
28907
28907
  }
28908
28908
  return "".concat(h).concat(prefix, "-").concat(channelValue, "-").concat(shade);
@@ -28922,7 +28922,7 @@ function classFor(prefix, type, shade, channelValue) {
28922
28922
  * falls back to className-only reactivity for that token.
28923
28923
  */
28924
28924
  function cssValueFor(type, shade, channelValue) {
28925
- if (isHexColor(channelValue)) {
28925
+ if (isHexColor$1(channelValue)) {
28926
28926
  // Resolve to a real hex so non-active themes (e.g. tiles in the
28927
28927
  // theme picker) can render inline without depending on :root
28928
28928
  // cssVars, which are only written for the currently-active
@@ -28940,7 +28940,7 @@ function cssValueFor(type, shade, channelValue) {
28940
28940
  return hex || null;
28941
28941
  }
28942
28942
  function gradientFor(direction, type, fromShade, viaShade, toShade, channelValue) {
28943
- if (isHexColor(channelValue)) {
28943
+ if (isHexColor$1(channelValue)) {
28944
28944
  return "bg-gradient-to-".concat(direction, " from-[var(--").concat(type, "-").concat(fromShade, ")] via-[var(--").concat(type, "-").concat(viaShade, ")] to-[var(--").concat(type, "-").concat(toShade, ")]");
28945
28945
  }
28946
28946
  return "bg-gradient-to-".concat(direction, " from-").concat(channelValue, "-").concat(fromShade, " via-").concat(channelValue, "-").concat(viaShade, " to-").concat(channelValue, "-").concat(toShade);
@@ -28950,7 +28950,7 @@ var ThemeModel = function ThemeModel() {
28950
28950
  try {
28951
28951
  var applyOverride = function applyOverride(variantKey, key, value) {
28952
28952
  var m = key.match(OVR_TOKEN_RE);
28953
- if (m && typeof value === "string" && isHexColor(value)) {
28953
+ if (m && typeof value === "string" && isHexColor$1(value)) {
28954
28954
  var prefix = m[1];
28955
28955
  var overrideChannel = m[2];
28956
28956
  var level = m[3];
@@ -29087,7 +29087,7 @@ var ThemeModel = function ThemeModel() {
29087
29087
  // PRD: arbitrary-color-themes.md.
29088
29088
  var hexChannels = new Set();
29089
29089
  colorTypes.forEach(function (type) {
29090
- if (isHexColor(theme[type])) hexChannels.add(type);
29090
+ if (isHexColor$1(theme[type])) hexChannels.add(type);
29091
29091
  });
29092
29092
  var overrideTargetsHexChannel = function overrideTargetsHexChannel(key) {
29093
29093
  var m = key.match(/^(?:hover-)?(?:bg|text|border|from|via|to)-([a-z]+)-/);
@@ -29149,7 +29149,7 @@ var ThemeModel = function ThemeModel() {
29149
29149
  var cssVarsAll = {};
29150
29150
  colorTypes.forEach(function (type) {
29151
29151
  var channelValue = theme[type];
29152
- if (isHexColor(channelValue)) {
29152
+ if (isHexColor$1(channelValue)) {
29153
29153
  var shades = deriveShades(channelValue);
29154
29154
  if (shades) {
29155
29155
  for (var _i = 0, _Object$entries = Object.entries(shades); _i < _Object$entries.length; _i++) {
@@ -34658,11 +34658,8 @@ var ThemePickerGridPane = function ThemePickerGridPane(_ref) {
34658
34658
  }
34659
34659
  return /*#__PURE__*/jsx(ThemePane, {
34660
34660
  children: /*#__PURE__*/jsx("div", {
34661
- className: "flex flex-row rounded overflow-clip justify-center items-center align-center w-full",
34662
- children: /*#__PURE__*/jsx("div", {
34663
- className: "grid grid-cols-3 gap-4 w-full h-full overflow-y-scroll scrollbar scrollbar-thumb-gray-700 scrollbar-thin scrollbar-track-transparent",
34664
- children: renderCurrentThemes()
34665
- })
34661
+ className: "grid grid-cols-3 gap-4 w-full flex-1 min-h-0 overflow-y-auto pr-2 scrollbar scrollbar-thumb-gray-700 scrollbar-thin scrollbar-track-transparent",
34662
+ children: renderCurrentThemes()
34666
34663
  })
34667
34664
  });
34668
34665
  };
@@ -34783,7 +34780,7 @@ var CHANNELS$1 = [{
34783
34780
  label: "Neutral"
34784
34781
  }];
34785
34782
  function swatchDisplay(channelValue) {
34786
- if (isHexColor(channelValue)) {
34783
+ if (isHexColor$1(channelValue)) {
34787
34784
  return {
34788
34785
  style: {
34789
34786
  backgroundColor: channelValue
@@ -34853,7 +34850,7 @@ var CustomHexColorPane = function CustomHexColorPane(_ref) {
34853
34850
  error = _useState4[0],
34854
34851
  setError = _useState4[1];
34855
34852
  var normalized = useMemo(function () {
34856
- return normalizeHex(input);
34853
+ return normalizeHex$1(input);
34857
34854
  }, [input]);
34858
34855
  var isValid = !!normalized;
34859
34856
 
@@ -34952,7 +34949,7 @@ var SHADE_LEVELS = [{
34952
34949
  function channelSwatchProps(theme, themeVariant, channelKey) {
34953
34950
  var _theme$themeVariant;
34954
34951
  var baseValue = theme === null || theme === void 0 || (_theme$themeVariant = theme[themeVariant]) === null || _theme$themeVariant === void 0 ? void 0 : _theme$themeVariant[channelKey];
34955
- if (isHexColor(baseValue)) {
34952
+ if (isHexColor$1(baseValue)) {
34956
34953
  return {
34957
34954
  style: {
34958
34955
  backgroundColor: baseValue
@@ -35000,7 +34997,7 @@ var ChannelEditorModal = function ChannelEditorModal(_ref) {
35000
34997
  setActiveSlot = _useState6[1];
35001
34998
  var themeData = theme === null || theme === void 0 ? void 0 : theme[themeVariant];
35002
34999
  var baseValue = themeData === null || themeData === void 0 ? void 0 : themeData[activeChannel];
35003
- var baseHex = isHexColor(baseValue) ? baseValue : null;
35000
+ var baseHex = isHexColor$1(baseValue) ? baseValue : null;
35004
35001
  function expandChannel(channelKey) {
35005
35002
  setActiveChannel(channelKey);
35006
35003
  setActiveSlot("base");
@@ -36912,6 +36909,205 @@ var PanelThemeStudio = function PanelThemeStudio(_ref) {
36912
36909
  * Utility for generating themes via presets, random generation,
36913
36910
  * and color-harmony-based generation from a user-selected base color.
36914
36911
  */
36912
+ // All color math is inlined here (no @trops/dash-react import). This
36913
+ // file is reachable from Electron's main process via
36914
+ // `electron/controller/paletteToThemeMapper.js`. Importing dash-react
36915
+ // from a main-process reachable file pulls dash-react's renderer
36916
+ // bundle into the main bundle — that bundle has top-level browser
36917
+ // API access (`window.ResizeObserver` in CodeEditorVS) which throws
36918
+ // when required under Node. Keeping this module pure-JS / no-deps
36919
+ // breaks the chain.
36920
+
36921
+ var HEX_RE = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
36922
+ function isHexColor(value) {
36923
+ return typeof value === "string" && HEX_RE.test(value.trim());
36924
+ }
36925
+ function normalizeHex(input) {
36926
+ if (typeof input !== "string") return null;
36927
+ var s = input.trim().toLowerCase();
36928
+ if (!s.startsWith("#")) s = "#".concat(s);
36929
+ if (!HEX_RE.test(s)) return null;
36930
+ if (s.length === 4) s = "#".concat(s[1]).concat(s[1]).concat(s[2]).concat(s[2]).concat(s[3]).concat(s[3]);else if (s.length === 5) s = "#".concat(s[1]).concat(s[1]).concat(s[2]).concat(s[2]).concat(s[3]).concat(s[3]);else if (s.length === 9) s = s.slice(0, 7);
36931
+ return s;
36932
+ }
36933
+ function hexToRgb(hex) {
36934
+ var n = normalizeHex(hex);
36935
+ if (!n) return null;
36936
+ var v = parseInt(n.slice(1), 16);
36937
+ return {
36938
+ r: v >> 16 & 0xff,
36939
+ g: v >> 8 & 0xff,
36940
+ b: v & 0xff
36941
+ };
36942
+ }
36943
+ function rgbToHex(_ref) {
36944
+ var r = _ref.r,
36945
+ g = _ref.g,
36946
+ b = _ref.b;
36947
+ var c = function c(x) {
36948
+ return Math.max(0, Math.min(255, Math.round(x)));
36949
+ };
36950
+ var h = function h(x) {
36951
+ return c(x).toString(16).padStart(2, "0");
36952
+ };
36953
+ return "#".concat(h(r)).concat(h(g)).concat(h(b));
36954
+ }
36955
+ function rgbToHsl(_ref2) {
36956
+ var r = _ref2.r,
36957
+ g = _ref2.g,
36958
+ b = _ref2.b;
36959
+ var R = r / 255,
36960
+ G = g / 255,
36961
+ B = b / 255;
36962
+ var max = Math.max(R, G, B),
36963
+ min = Math.min(R, G, B);
36964
+ var l = (max + min) / 2;
36965
+ var h = 0,
36966
+ s = 0;
36967
+ if (max !== min) {
36968
+ var d = max - min;
36969
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
36970
+ switch (max) {
36971
+ case R:
36972
+ h = (G - B) / d + (G < B ? 6 : 0);
36973
+ break;
36974
+ case G:
36975
+ h = (B - R) / d + 2;
36976
+ break;
36977
+ default:
36978
+ h = (R - G) / d + 4;
36979
+ }
36980
+ h *= 60;
36981
+ }
36982
+ return {
36983
+ h: h,
36984
+ s: s,
36985
+ l: l
36986
+ };
36987
+ }
36988
+ function hslToRgb(_ref3) {
36989
+ var h = _ref3.h,
36990
+ s = _ref3.s,
36991
+ l = _ref3.l;
36992
+ if (s === 0) {
36993
+ var v = l * 255;
36994
+ return {
36995
+ r: v,
36996
+ g: v,
36997
+ b: v
36998
+ };
36999
+ }
37000
+ var c = (1 - Math.abs(2 * l - 1)) * s;
37001
+ var x = c * (1 - Math.abs(h / 60 % 2 - 1));
37002
+ var m = l - c / 2;
37003
+ var rp, gp, bp;
37004
+ if (h < 60) {
37005
+ rp = c;
37006
+ gp = x;
37007
+ bp = 0;
37008
+ } else if (h < 120) {
37009
+ rp = x;
37010
+ gp = c;
37011
+ bp = 0;
37012
+ } else if (h < 180) {
37013
+ rp = 0;
37014
+ gp = c;
37015
+ bp = x;
37016
+ } else if (h < 240) {
37017
+ rp = 0;
37018
+ gp = x;
37019
+ bp = c;
37020
+ } else if (h < 300) {
37021
+ rp = x;
37022
+ gp = 0;
37023
+ bp = c;
37024
+ } else {
37025
+ rp = c;
37026
+ gp = 0;
37027
+ bp = x;
37028
+ }
37029
+ return {
37030
+ r: (rp + m) * 255,
37031
+ g: (gp + m) * 255,
37032
+ b: (bp + m) * 255
37033
+ };
37034
+ }
37035
+
37036
+ // Color harmony helpers — pure JS, no DOM/window access.
37037
+ function rotateHex(hex, deltaH) {
37038
+ var rgb = hexToRgb(hex);
37039
+ if (!rgb) return null;
37040
+ var _rgbToHsl = rgbToHsl(rgb),
37041
+ h = _rgbToHsl.h,
37042
+ s = _rgbToHsl.s,
37043
+ l = _rgbToHsl.l;
37044
+ var newH = ((h + deltaH) % 360 + 360) % 360;
37045
+ return rgbToHex(hslToRgb({
37046
+ h: newH,
37047
+ s: s,
37048
+ l: l
37049
+ }));
37050
+ }
37051
+ function complementHex(hex) {
37052
+ return rotateHex(hex, 180);
37053
+ }
37054
+ function analogousHexes(hex) {
37055
+ var a = rotateHex(hex, -30);
37056
+ var b = rotateHex(hex, 30);
37057
+ return a && b ? [a, b] : null;
37058
+ }
37059
+ function triadicHexes(hex) {
37060
+ var a = rotateHex(hex, 120);
37061
+ var b = rotateHex(hex, 240);
37062
+ return a && b ? [a, b] : null;
37063
+ }
37064
+ function splitComplementaryHexes(hex) {
37065
+ var a = rotateHex(hex, 150);
37066
+ var b = rotateHex(hex, 210);
37067
+ return a && b ? [a, b] : null;
37068
+ }
37069
+ function tetradicHexes(hex) {
37070
+ var a = rotateHex(hex, 90);
37071
+ var b = rotateHex(hex, 180);
37072
+ var c = rotateHex(hex, 270);
37073
+ return a && b && c ? [a, b, c] : null;
37074
+ }
37075
+ function monochromaticHexes(hex) {
37076
+ var count = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3;
37077
+ var rgb = hexToRgb(hex);
37078
+ if (!rgb) return null;
37079
+ var _rgbToHsl2 = rgbToHsl(rgb),
37080
+ h = _rgbToHsl2.h,
37081
+ s = _rgbToHsl2.s;
37082
+ var out = [];
37083
+ for (var i = 0; i < count; i++) {
37084
+ var t = count === 1 ? 0.5 : i / (count - 1);
37085
+ var l = 0.15 + t * 0.7;
37086
+ var sAdj = s * (1 - 0.3 * Math.abs(t - 0.5) * 2);
37087
+ out.push(rgbToHex(hslToRgb({
37088
+ h: h,
37089
+ s: sAdj,
37090
+ l: l
37091
+ })));
37092
+ }
37093
+ return out;
37094
+ }
37095
+ function adjustHsl(hex) {
37096
+ var dH = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
37097
+ var dS = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
37098
+ var dL = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
37099
+ var rgb = hexToRgb(hex);
37100
+ if (!rgb) return null;
37101
+ var hsl = rgbToHsl(rgb);
37102
+ var h = ((hsl.h + dH) % 360 + 360) % 360;
37103
+ var s = Math.max(0, Math.min(1, hsl.s + dS));
37104
+ var l = Math.max(0, Math.min(1, hsl.l + dL));
37105
+ return rgbToHex(hslToRgb({
37106
+ h: h,
37107
+ s: s,
37108
+ l: l
37109
+ }));
37110
+ }
36915
37111
 
36916
37112
  // Color wheel mapping for Tailwind palettes (approximate hue angles)
36917
37113
  var COLOR_WHEEL = {
@@ -37079,11 +37275,22 @@ function generateRandomTheme() {
37079
37275
 
37080
37276
  /**
37081
37277
  * Generate a theme using color harmony rules from a base color.
37082
- * @param {string} baseColor - A Tailwind color name (e.g. "blue")
37083
- * @param {"complementary"|"analogous"|"triadic"|"split-complementary"} strategy
37278
+ * Accepts either a Tailwind color name OR a hex string. Hex bases
37279
+ * run through dash-react's harmony math and emit hex channels for
37280
+ * every output; named bases use the legacy COLOR_WHEEL mapping.
37281
+ *
37282
+ * @param {string} baseColor - Tailwind color name (e.g. "blue") OR hex
37283
+ * @param {"complementary"|"analogous"|"triadic"|"split-complementary"|"tetradic"|"monochromatic"} strategy
37284
+ * @param {{ dH?: number, dS?: number, dL?: number }} [hslNudge] - optional
37285
+ * HSL offsets applied to every generated channel after the harmony
37286
+ * math. dH in degrees; dS/dL as fractions in [-1..1].
37084
37287
  */
37085
37288
  function generateHarmonyTheme(baseColor) {
37086
37289
  var strategy = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "complementary";
37290
+ var hslNudge = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
37291
+ if (typeof baseColor === "string" && isHexColor(baseColor)) {
37292
+ return generateHexHarmonyTheme(baseColor, strategy, hslNudge);
37293
+ }
37087
37294
  var baseHue = COLOR_WHEEL[baseColor];
37088
37295
  if (baseHue === undefined) {
37089
37296
  return generateRandomTheme();
@@ -37119,17 +37326,114 @@ function generateHarmonyTheme(baseColor) {
37119
37326
  }
37120
37327
 
37121
37328
  /**
37122
- * Generate a theme from three independently chosen colors.
37123
- * @param {string} primary - A Tailwind color name
37124
- * @param {string} secondary - A Tailwind color name
37125
- * @param {string} tertiary - A Tailwind color name
37329
+ * Hex-mode harmony generation. Always emits hex channels; supports
37330
+ * all the named harmony strategies plus Monochromatic and Tetradic.
37331
+ * The neutral channel is derived from the base by desaturating
37332
+ * almost fully, keeping the same hue family for a cohesive feel.
37126
37333
  */
37127
- function generateCustomTheme(primary, secondary, tertiary) {
37128
- var neutral = pick(NEUTRAL_COLORS);
37334
+ function generateHexHarmonyTheme(baseHex, strategy, hslNudge) {
37335
+ var base = normalizeHex(baseHex);
37336
+ if (!base) return generateRandomTheme();
37337
+ var primary = base;
37338
+ var secondary;
37339
+ var tertiary;
37340
+ var neutral;
37341
+ switch (strategy) {
37342
+ case "complementary":
37343
+ {
37344
+ secondary = complementHex(base);
37345
+ var ana = analogousHexes(base);
37346
+ tertiary = ana ? ana[1] : base;
37347
+ break;
37348
+ }
37349
+ case "analogous":
37350
+ {
37351
+ var _ana = analogousHexes(base);
37352
+ if (_ana) {
37353
+ secondary = _ana[0];
37354
+ tertiary = _ana[1];
37355
+ }
37356
+ break;
37357
+ }
37358
+ case "triadic":
37359
+ {
37360
+ var tri = triadicHexes(base);
37361
+ if (tri) {
37362
+ secondary = tri[0];
37363
+ tertiary = tri[1];
37364
+ }
37365
+ break;
37366
+ }
37367
+ case "split-complementary":
37368
+ {
37369
+ var split = splitComplementaryHexes(base);
37370
+ if (split) {
37371
+ secondary = split[0];
37372
+ tertiary = split[1];
37373
+ }
37374
+ break;
37375
+ }
37376
+ case "tetradic":
37377
+ {
37378
+ var tet = tetradicHexes(base);
37379
+ if (tet) {
37380
+ secondary = tet[0];
37381
+ tertiary = tet[1];
37382
+ neutral = tet[2];
37383
+ }
37384
+ break;
37385
+ }
37386
+ case "monochromatic":
37387
+ {
37388
+ var mono = monochromaticHexes(base, 3);
37389
+ if (mono) {
37390
+ primary = mono[1];
37391
+ secondary = mono[0];
37392
+ tertiary = mono[2];
37393
+ }
37394
+ break;
37395
+ }
37396
+ default:
37397
+ {
37398
+ secondary = complementHex(base);
37399
+ var _ana2 = analogousHexes(base);
37400
+ tertiary = _ana2 ? _ana2[1] : base;
37401
+ }
37402
+ }
37403
+
37404
+ // Neutral defaults to a near-grey of the same hue (desaturated).
37405
+ if (!neutral) {
37406
+ neutral = adjustHsl(base, 0, -0.85, 0);
37407
+ }
37408
+
37409
+ // Apply optional HSL nudge uniformly to all channels.
37410
+ if (hslNudge) {
37411
+ var _hslNudge$dH = hslNudge.dH,
37412
+ dH = _hslNudge$dH === void 0 ? 0 : _hslNudge$dH,
37413
+ _hslNudge$dS = hslNudge.dS,
37414
+ dS = _hslNudge$dS === void 0 ? 0 : _hslNudge$dS,
37415
+ _hslNudge$dL = hslNudge.dL,
37416
+ dL = _hslNudge$dL === void 0 ? 0 : _hslNudge$dL;
37417
+ primary = adjustHsl(primary, dH, dS, dL) || primary;
37418
+ secondary = adjustHsl(secondary, dH, dS, dL) || secondary;
37419
+ tertiary = adjustHsl(tertiary, dH, dS, dL) || tertiary;
37420
+ neutral = adjustHsl(neutral, dH, dS, dL) || neutral;
37421
+ }
37129
37422
  var name = generateThemeName(primary, secondary);
37130
37423
  return buildRawTheme(name, primary, secondary, tertiary, neutral);
37131
37424
  }
37132
37425
 
37426
+ /**
37427
+ * Generate a theme from four independently chosen channel values.
37428
+ * Each channel may be a Tailwind name or a hex string.
37429
+ */
37430
+ function generateCustomTheme(primary, secondary, tertiary) {
37431
+ var neutral = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
37432
+ var resolvedNeutral = neutral || pick(NEUTRAL_COLORS);
37433
+ var name = generateThemeName(primary, secondary);
37434
+ return buildRawTheme(name, primary, secondary, tertiary, resolvedNeutral);
37435
+ }
37436
+
37133
37437
  /**
37134
37438
  * All available chromatic colors for the "From Color" picker.
37135
37439
  */
@@ -37150,6 +37454,12 @@ var HARMONY_STRATEGIES = [{
37150
37454
  }, {
37151
37455
  value: "split-complementary",
37152
37456
  label: "Split Complementary"
37457
+ }, {
37458
+ value: "tetradic",
37459
+ label: "Tetradic"
37460
+ }, {
37461
+ value: "monochromatic",
37462
+ label: "Monochromatic"
37153
37463
  }, {
37154
37464
  value: "custom",
37155
37465
  label: "Custom"
@@ -37172,11 +37482,11 @@ var GENERATE_MODES = {
37172
37482
  // carry hex palettes.
37173
37483
  var PresetSwatch = function PresetSwatch(_ref) {
37174
37484
  var color = _ref.color;
37175
- if (isHexColor(color)) {
37485
+ if (isHexColor$1(color)) {
37176
37486
  return /*#__PURE__*/jsx("div", {
37177
37487
  className: "h-6 flex-1 rounded",
37178
37488
  style: {
37179
- backgroundColor: normalizeHex(color) || color
37489
+ backgroundColor: normalizeHex$1(color) || color
37180
37490
  }
37181
37491
  });
37182
37492
  }
@@ -37248,87 +37558,165 @@ var PresetGallery = function PresetGallery(_ref3) {
37248
37558
 
37249
37559
  // ─── Color Harmony Picker ────────────────────────────────────────────────
37250
37560
 
37251
- var ColorSwatchGrid$1 = function ColorSwatchGrid(_ref4) {
37561
+ /**
37562
+ * Render a swatch for a generated palette channel. Hex values render
37563
+ * inline; named Tailwind families fall back to `bg-{name}-500` which
37564
+ * is in the safelist.
37565
+ */
37566
+ var PaletteSwatch = function PaletteSwatch(_ref4) {
37252
37567
  var value = _ref4.value,
37253
- onChange = _ref4.onChange,
37254
37568
  label = _ref4.label;
37569
+ var isHex = isHexColor$1(value);
37255
37570
  return /*#__PURE__*/jsxs("div", {
37256
- className: "flex flex-col gap-2",
37257
- children: [/*#__PURE__*/jsx("span", {
37258
- className: "text-xs opacity-50",
37571
+ className: "flex flex-col items-center gap-1 flex-1",
37572
+ children: [/*#__PURE__*/jsx("div", {
37573
+ className: "h-10 w-full rounded ".concat(isHex ? "" : "bg-".concat(value, "-500")),
37574
+ style: isHex ? {
37575
+ backgroundColor: value
37576
+ } : undefined
37577
+ }), /*#__PURE__*/jsx("span", {
37578
+ className: "text-xs opacity-60 truncate w-full text-center",
37259
37579
  children: label
37580
+ })]
37581
+ });
37582
+ };
37583
+
37584
+ /**
37585
+ * Compact hex picker reused inline by the wizard. Category chips +
37586
+ * a curated grid of swatches + a custom-hex input below. Same
37587
+ * primitives as ChannelEditorModal but laid out for a narrower
37588
+ * column.
37589
+ */
37590
+ var InlineHexPicker = function InlineHexPicker(_ref5) {
37591
+ var selectedHex = _ref5.selectedHex,
37592
+ onPick = _ref5.onPick;
37593
+ var families = getColorFamilies();
37594
+ var _useState = useState(families[0]),
37595
+ _useState2 = _slicedToArray(_useState, 2),
37596
+ activeFamily = _useState2[0],
37597
+ setActiveFamily = _useState2[1];
37598
+ var swatches = getCuratedColorGrid(activeFamily);
37599
+ return /*#__PURE__*/jsxs("div", {
37600
+ className: "flex flex-col gap-2",
37601
+ children: [/*#__PURE__*/jsx("div", {
37602
+ className: "flex flex-row gap-2 flex-wrap",
37603
+ children: families.map(function (family) {
37604
+ var isActive = activeFamily === family;
37605
+ return /*#__PURE__*/jsx("button", {
37606
+ type: "button",
37607
+ onClick: function onClick() {
37608
+ return setActiveFamily(family);
37609
+ },
37610
+ className: "px-2 py-0.5 rounded-full text-xs font-medium ".concat(isActive ? "bg-blue-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"),
37611
+ children: family
37612
+ }, family);
37613
+ })
37260
37614
  }), /*#__PURE__*/jsx("div", {
37261
- className: "grid grid-cols-6 gap-2",
37262
- children: AVAILABLE_COLORS.map(function (color) {
37263
- return /*#__PURE__*/jsx("div", {
37264
- className: "h-8 rounded cursor-pointer transition-all bg-".concat(color, "-500 ").concat(value === color ? "ring-2 ring-white scale-110" : "opacity-70 hover:opacity-100"),
37265
- title: color,
37615
+ className: "grid grid-cols-12 gap-1",
37616
+ children: swatches.map(function (hex) {
37617
+ var isSelected = selectedHex && hex.toLowerCase() === selectedHex.toLowerCase();
37618
+ return /*#__PURE__*/jsx("button", {
37619
+ type: "button",
37266
37620
  onClick: function onClick() {
37267
- return onChange(color);
37268
- }
37269
- }, color);
37621
+ return onPick(hex);
37622
+ },
37623
+ style: {
37624
+ backgroundColor: hex
37625
+ },
37626
+ className: "h-6 rounded transition-transform hover:scale-110 ".concat(isSelected ? "border-2 border-yellow-400 scale-110" : ""),
37627
+ "aria-label": "Pick ".concat(hex),
37628
+ title: hex
37629
+ }, hex);
37270
37630
  })
37631
+ }), /*#__PURE__*/jsx(CustomHexColorPane, {
37632
+ onApply: onPick,
37633
+ label: "Or paste a hex value"
37271
37634
  })]
37272
37635
  });
37273
37636
  };
37274
- var PreviewSwatch = function PreviewSwatch(_ref5) {
37275
- var color = _ref5.color,
37276
- strategy = _ref5.strategy,
37277
- customColors = _ref5.customColors;
37278
- var colors;
37279
- if (strategy === "custom" && customColors) {
37280
- colors = [customColors.primary, customColors.secondary, customColors.tertiary];
37281
- } else {
37282
- var theme = generateHarmonyTheme(color, strategy);
37283
- colors = [theme.primary, theme.secondary, theme.tertiary];
37284
- }
37285
- return /*#__PURE__*/jsx("div", {
37286
- className: "flex flex-row gap-2",
37287
- children: colors.map(function (c, i) {
37288
- return /*#__PURE__*/jsxs("div", {
37289
- className: "flex flex-col items-center gap-1 flex-1",
37290
- children: [/*#__PURE__*/jsx("div", {
37291
- className: "h-10 w-full rounded bg-".concat(c, "-500")
37292
- }), /*#__PURE__*/jsx("span", {
37293
- className: "text-[10px] opacity-40",
37294
- children: c
37295
- })]
37296
- }, i);
37297
- })
37637
+
37638
+ /**
37639
+ * HSL nudge slider — single-axis. Display label + numeric value.
37640
+ */
37641
+ var HslSlider = function HslSlider(_ref6) {
37642
+ var label = _ref6.label,
37643
+ value = _ref6.value,
37644
+ min = _ref6.min,
37645
+ max = _ref6.max,
37646
+ step = _ref6.step,
37647
+ _onChange = _ref6.onChange,
37648
+ _ref6$unit = _ref6.unit,
37649
+ unit = _ref6$unit === void 0 ? "" : _ref6$unit;
37650
+ return /*#__PURE__*/jsxs("div", {
37651
+ className: "flex flex-col gap-1",
37652
+ children: [/*#__PURE__*/jsxs("div", {
37653
+ className: "flex flex-row items-center justify-between",
37654
+ children: [/*#__PURE__*/jsx("span", {
37655
+ className: "text-xs opacity-60",
37656
+ children: label
37657
+ }), /*#__PURE__*/jsxs("span", {
37658
+ className: "text-xs opacity-50 font-mono",
37659
+ children: [value > 0 ? "+".concat(value) : value, unit]
37660
+ })]
37661
+ }), /*#__PURE__*/jsx("input", {
37662
+ type: "range",
37663
+ min: min,
37664
+ max: max,
37665
+ step: step,
37666
+ value: value,
37667
+ onChange: function onChange(e) {
37668
+ return _onChange(parseFloat(e.target.value));
37669
+ },
37670
+ className: "w-full accent-blue-500"
37671
+ })]
37298
37672
  });
37299
37673
  };
37300
- var ColorHarmonyPicker = function ColorHarmonyPicker(_ref6) {
37301
- var onGenerate = _ref6.onGenerate,
37302
- _ref6$inline = _ref6.inline,
37303
- inline = _ref6$inline === void 0 ? false : _ref6$inline;
37304
- var _useState = useState("blue"),
37305
- _useState2 = _slicedToArray(_useState, 2),
37306
- selectedColor = _useState2[0],
37307
- setSelectedColor = _useState2[1];
37308
- var _useState3 = useState("rose"),
37674
+ var ColorHarmonyPicker = function ColorHarmonyPicker(_ref7) {
37675
+ var onGenerate = _ref7.onGenerate,
37676
+ _ref7$inline = _ref7.inline,
37677
+ inline = _ref7$inline === void 0 ? false : _ref7$inline;
37678
+ // Default to a brand-blue hex so the wizard opens in hex mode.
37679
+ var _useState3 = useState("#3b82f6"),
37309
37680
  _useState4 = _slicedToArray(_useState3, 2),
37310
- secondaryColor = _useState4[0],
37311
- setSecondaryColor = _useState4[1];
37312
- var _useState5 = useState("amber"),
37681
+ selectedColor = _useState4[0],
37682
+ setSelectedColor = _useState4[1];
37683
+ var _useState5 = useState("complementary"),
37313
37684
  _useState6 = _slicedToArray(_useState5, 2),
37314
- tertiaryColor = _useState6[0],
37315
- setTertiaryColor = _useState6[1];
37316
- var _useState7 = useState("complementary"),
37685
+ strategy = _useState6[0],
37686
+ setStrategy = _useState6[1];
37687
+ // HSL nudge — applied to every generated channel after harmony math.
37688
+ var _useState7 = useState(0),
37317
37689
  _useState8 = _slicedToArray(_useState7, 2),
37318
- strategy = _useState8[0],
37319
- setStrategy = _useState8[1];
37320
- var isCustom = strategy === "custom";
37690
+ dH = _useState8[0],
37691
+ setDH = _useState8[1];
37692
+ var _useState9 = useState(0),
37693
+ _useState0 = _slicedToArray(_useState9, 2),
37694
+ dS = _useState0[0],
37695
+ setDS = _useState0[1];
37696
+ var _useState1 = useState(0),
37697
+ _useState10 = _slicedToArray(_useState1, 2),
37698
+ dL = _useState10[0],
37699
+ setDL = _useState10[1];
37700
+
37701
+ // Generate the current theme from inputs. Memoized through useEffect
37702
+ // for the inline-preview path; explicit handler for the button path.
37703
+ function buildTheme() {
37704
+ return generateHarmonyTheme(selectedColor, strategy, {
37705
+ dH: dH,
37706
+ dS: dS,
37707
+ dL: dL
37708
+ });
37709
+ }
37321
37710
  useEffect(function () {
37322
37711
  if (inline) {
37323
- var theme = isCustom ? generateCustomTheme(selectedColor, secondaryColor, tertiaryColor) : generateHarmonyTheme(selectedColor, strategy);
37324
- onGenerate(theme);
37712
+ onGenerate(buildTheme());
37325
37713
  }
37326
37714
  // eslint-disable-next-line react-hooks/exhaustive-deps
37327
- }, [selectedColor, secondaryColor, tertiaryColor, strategy, inline]);
37715
+ }, [selectedColor, strategy, dH, dS, dL, inline]);
37328
37716
  function handleGenerate() {
37329
- var theme = isCustom ? generateCustomTheme(selectedColor, secondaryColor, tertiaryColor) : generateHarmonyTheme(selectedColor, strategy);
37330
- onGenerate(theme);
37717
+ onGenerate(buildTheme());
37331
37718
  }
37719
+ var previewTheme = buildTheme();
37332
37720
  return /*#__PURE__*/jsxs("div", {
37333
37721
  className: inline ? "flex flex-col gap-4" : "flex flex-col gap-4 p-6 overflow-y-auto flex-1 min-h-0",
37334
37722
  children: [/*#__PURE__*/jsx("span", {
@@ -37341,7 +37729,9 @@ var ColorHarmonyPicker = function ColorHarmonyPicker(_ref6) {
37341
37729
  children: "Harmony"
37342
37730
  }), /*#__PURE__*/jsx("div", {
37343
37731
  className: "flex flex-row gap-2 flex-wrap",
37344
- children: HARMONY_STRATEGIES.map(function (s) {
37732
+ children: HARMONY_STRATEGIES.filter(function (s) {
37733
+ return s.value !== "custom";
37734
+ }).map(function (s) {
37345
37735
  return /*#__PURE__*/jsx("button", {
37346
37736
  type: "button",
37347
37737
  className: "px-3 py-1.5 rounded text-xs transition-all ".concat(strategy === s.value ? "bg-blue-600 text-white" : "bg-white/10 opacity-70 hover:opacity-100"),
@@ -37352,37 +37742,71 @@ var ColorHarmonyPicker = function ColorHarmonyPicker(_ref6) {
37352
37742
  }, s.value);
37353
37743
  })
37354
37744
  })]
37355
- }), isCustom ? /*#__PURE__*/jsxs(Fragment, {
37356
- children: [/*#__PURE__*/jsx(ColorSwatchGrid$1, {
37357
- value: selectedColor,
37358
- onChange: setSelectedColor,
37359
- label: "Primary"
37360
- }), /*#__PURE__*/jsx(ColorSwatchGrid$1, {
37361
- value: secondaryColor,
37362
- onChange: setSecondaryColor,
37363
- label: "Secondary"
37364
- }), /*#__PURE__*/jsx(ColorSwatchGrid$1, {
37365
- value: tertiaryColor,
37366
- onChange: setTertiaryColor,
37367
- label: "Tertiary"
37745
+ }), /*#__PURE__*/jsxs("div", {
37746
+ className: "flex flex-col gap-2",
37747
+ children: [/*#__PURE__*/jsx("span", {
37748
+ className: "text-xs opacity-50",
37749
+ children: "Base Color"
37750
+ }), /*#__PURE__*/jsx(InlineHexPicker, {
37751
+ selectedHex: isHexColor$1(selectedColor) ? selectedColor : null,
37752
+ onPick: function onPick(hex) {
37753
+ return setSelectedColor(hex);
37754
+ }
37755
+ })]
37756
+ }), /*#__PURE__*/jsxs("div", {
37757
+ className: "flex flex-col gap-3",
37758
+ children: [/*#__PURE__*/jsx("span", {
37759
+ className: "text-xs opacity-50",
37760
+ children: "Fine-tune (HSL nudge)"
37761
+ }), /*#__PURE__*/jsx(HslSlider, {
37762
+ label: "Hue",
37763
+ value: dH,
37764
+ min: -180,
37765
+ max: 180,
37766
+ step: 1,
37767
+ unit: "\xB0",
37768
+ onChange: setDH
37769
+ }), /*#__PURE__*/jsx(HslSlider, {
37770
+ label: "Saturation",
37771
+ value: Math.round(dS * 100),
37772
+ min: -100,
37773
+ max: 100,
37774
+ step: 1,
37775
+ unit: "%",
37776
+ onChange: function onChange(v) {
37777
+ return setDS(v / 100);
37778
+ }
37779
+ }), /*#__PURE__*/jsx(HslSlider, {
37780
+ label: "Lightness",
37781
+ value: Math.round(dL * 100),
37782
+ min: -100,
37783
+ max: 100,
37784
+ step: 1,
37785
+ unit: "%",
37786
+ onChange: function onChange(v) {
37787
+ return setDL(v / 100);
37788
+ }
37368
37789
  })]
37369
- }) : /*#__PURE__*/jsx(ColorSwatchGrid$1, {
37370
- value: selectedColor,
37371
- onChange: setSelectedColor,
37372
- label: "Base Color"
37373
37790
  }), /*#__PURE__*/jsxs("div", {
37374
37791
  className: "flex flex-col gap-2",
37375
37792
  children: [/*#__PURE__*/jsx("span", {
37376
37793
  className: "text-xs opacity-50",
37377
- children: "Preview"
37378
- }), /*#__PURE__*/jsx(PreviewSwatch, {
37379
- color: selectedColor,
37380
- strategy: strategy,
37381
- customColors: isCustom ? {
37382
- primary: selectedColor,
37383
- secondary: secondaryColor,
37384
- tertiary: tertiaryColor
37385
- } : null
37794
+ children: "Generated Palette"
37795
+ }), /*#__PURE__*/jsxs("div", {
37796
+ className: "flex flex-row gap-2",
37797
+ children: [/*#__PURE__*/jsx(PaletteSwatch, {
37798
+ value: previewTheme.primary,
37799
+ label: "Primary"
37800
+ }), /*#__PURE__*/jsx(PaletteSwatch, {
37801
+ value: previewTheme.secondary,
37802
+ label: "Secondary"
37803
+ }), /*#__PURE__*/jsx(PaletteSwatch, {
37804
+ value: previewTheme.tertiary,
37805
+ label: "Tertiary"
37806
+ }), /*#__PURE__*/jsx(PaletteSwatch, {
37807
+ value: previewTheme.neutral,
37808
+ label: "Neutral"
37809
+ })]
37386
37810
  })]
37387
37811
  }), !inline && /*#__PURE__*/jsx(Button, {
37388
37812
  title: "Generate Theme",
@@ -37394,12 +37818,12 @@ var ColorHarmonyPicker = function ColorHarmonyPicker(_ref6) {
37394
37818
 
37395
37819
  // ─── Wizard Components ──────────────────────────────────────────────────
37396
37820
 
37397
- var MethodCard = function MethodCard(_ref7) {
37398
- var icon = _ref7.icon,
37399
- title = _ref7.title,
37400
- subtitle = _ref7.subtitle,
37401
- selected = _ref7.selected,
37402
- onClick = _ref7.onClick;
37821
+ var MethodCard = function MethodCard(_ref8) {
37822
+ var icon = _ref8.icon,
37823
+ title = _ref8.title,
37824
+ subtitle = _ref8.subtitle,
37825
+ selected = _ref8.selected,
37826
+ onClick = _ref8.onClick;
37403
37827
  return /*#__PURE__*/jsxs("div", {
37404
37828
  className: "flex flex-col gap-1.5 p-3 rounded-lg cursor-pointer transition-all bg-white/5 hover:bg-white/10 ".concat(selected ? "ring-2 ring-blue-500 bg-white/10" : "hover:ring-1 hover:ring-white/20"),
37405
37829
  onClick: onClick,
@@ -37418,9 +37842,9 @@ var MethodCard = function MethodCard(_ref7) {
37418
37842
  })]
37419
37843
  });
37420
37844
  };
37421
- var RandomPreview = function RandomPreview(_ref8) {
37422
- var theme = _ref8.theme,
37423
- onRegenerate = _ref8.onRegenerate;
37845
+ var RandomPreview = function RandomPreview(_ref9) {
37846
+ var theme = _ref9.theme,
37847
+ onRegenerate = _ref9.onRegenerate;
37424
37848
  var colors = theme ? [theme.primary, theme.secondary, theme.tertiary] : [];
37425
37849
  return /*#__PURE__*/jsxs("div", {
37426
37850
  className: "flex flex-col gap-4",
@@ -37447,20 +37871,20 @@ var RandomPreview = function RandomPreview(_ref8) {
37447
37871
  })]
37448
37872
  });
37449
37873
  };
37450
- var ThemeQuickCreate = function ThemeQuickCreate(_ref9) {
37451
- var wizardName = _ref9.wizardName,
37452
- setWizardName = _ref9.setWizardName,
37453
- wizardMethod = _ref9.wizardMethod,
37454
- setWizardMethod = _ref9.setWizardMethod,
37455
- wizardTheme = _ref9.wizardTheme,
37456
- setWizardTheme = _ref9.setWizardTheme,
37457
- onComplete = _ref9.onComplete,
37458
- _ref9$onExtract = _ref9.onExtract,
37459
- onExtract = _ref9$onExtract === void 0 ? null : _ref9$onExtract,
37460
- _ref9$onMapToTheme = _ref9.onMapToTheme,
37461
- onMapToTheme = _ref9$onMapToTheme === void 0 ? null : _ref9$onMapToTheme,
37462
- _ref9$initialMethod = _ref9.initialMethod,
37463
- initialMethod = _ref9$initialMethod === void 0 ? null : _ref9$initialMethod;
37874
+ var ThemeQuickCreate = function ThemeQuickCreate(_ref0) {
37875
+ var wizardName = _ref0.wizardName,
37876
+ setWizardName = _ref0.setWizardName,
37877
+ wizardMethod = _ref0.wizardMethod,
37878
+ setWizardMethod = _ref0.setWizardMethod,
37879
+ wizardTheme = _ref0.wizardTheme,
37880
+ setWizardTheme = _ref0.setWizardTheme,
37881
+ onComplete = _ref0.onComplete,
37882
+ _ref0$onExtract = _ref0.onExtract,
37883
+ onExtract = _ref0$onExtract === void 0 ? null : _ref0$onExtract,
37884
+ _ref0$onMapToTheme = _ref0.onMapToTheme,
37885
+ onMapToTheme = _ref0$onMapToTheme === void 0 ? null : _ref0$onMapToTheme,
37886
+ _ref0$initialMethod = _ref0.initialMethod,
37887
+ initialMethod = _ref0$initialMethod === void 0 ? null : _ref0$initialMethod;
37464
37888
  var canCreate = wizardName.trim().length > 0 && wizardTheme !== null;
37465
37889
  var showMethodPicker = initialMethod === null;
37466
37890
  useEffect(function () {