@trops/dash-core 0.1.604 → 0.1.605

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
@@ -2549,6 +2549,27 @@ var ThemeWrapper = function ThemeWrapper(_ref) {
2549
2549
  // eslint-disable-next-line react-hooks/exhaustive-deps
2550
2550
  }, [chosenTheme, themeVariant, themeName, themesForApplication, rawThemes]);
2551
2551
 
2552
+ // Broadcast the app-level theme to a SEPARATE global so
2553
+ // components rendered OUTSIDE the React theme tree
2554
+ // (WidgetBuilderModal, AppUpdatesModal — they live as siblings
2555
+ // of DashboardStage in dash-electron's Dash.js render) can fall
2556
+ // back to it when no dashboard-specific override is in play.
2557
+ //
2558
+ // We can't share the same global with DashboardThemeProvider's
2559
+ // per-workspace broadcast because React commits child effects
2560
+ // BEFORE parent effects — ThemeWrapper would always run last
2561
+ // and silently overwrite the workspace theme. Two separate
2562
+ // globals avoid the race: consumers read
2563
+ // `__dashThemeContext` (workspace) first, fall back to
2564
+ // `__dashAppThemeContext` (app-level) when no workspace is open.
2565
+ useEffect(function () {
2566
+ if (typeof window === "undefined") return undefined;
2567
+ if (contextValue !== null && contextValue !== void 0 && contextValue.currentTheme) {
2568
+ window.__dashAppThemeContext = contextValue;
2569
+ window.dispatchEvent(new Event("dash:theme-changed"));
2570
+ }
2571
+ }, [contextValue]);
2572
+
2552
2573
  // Write the active theme's CSS custom properties to :root so any
2553
2574
  // hex-channel tokens (`bg-[var(--primary-700)]` etc. emitted by
2554
2575
  // ThemeModel) resolve app-wide. Without this, saving a hex theme
@@ -5725,6 +5746,45 @@ function ThemeBroadcast(_ref) {
5725
5746
  return null;
5726
5747
  }
5727
5748
 
5749
+ /**
5750
+ * Writes the dashboard theme's cssVars to `:root` while mounted, and
5751
+ * restores the previous values on unmount / theme switch.
5752
+ *
5753
+ * ThemeWrapper writes the APP theme's cssVars at the top of the tree.
5754
+ * Without this helper, a dashboard that overrides the app theme with a
5755
+ * hex-channel theme (like "Slack Generic") would carry the right class
5756
+ * names (`bg-[var(--primary-900)]`) but the `--primary-900` variable
5757
+ * on :root would still be the APP theme's value — or undefined if the
5758
+ * app theme is named-family. Hex-themed surfaces then render with no
5759
+ * background. This effect closes that gap by promoting the dashboard
5760
+ * theme's cssVars to :root for the duration of its mount.
5761
+ */
5762
+ function DashboardCssVarsBridge(_ref2) {
5763
+ var cssVars = _ref2.cssVars;
5764
+ useEffect(function () {
5765
+ if (!cssVars || typeof document === "undefined") return undefined;
5766
+ var root = document.documentElement;
5767
+ var previous = {};
5768
+ var keys = Object.keys(cssVars);
5769
+ for (var _i = 0, _keys = keys; _i < _keys.length; _i++) {
5770
+ var key = _keys[_i];
5771
+ previous[key] = root.style.getPropertyValue(key);
5772
+ root.style.setProperty(key, cssVars[key]);
5773
+ }
5774
+ return function () {
5775
+ for (var _i2 = 0, _keys2 = keys; _i2 < _keys2.length; _i2++) {
5776
+ var _key = _keys2[_i2];
5777
+ if (previous[_key]) {
5778
+ root.style.setProperty(_key, previous[_key]);
5779
+ } else {
5780
+ root.style.removeProperty(_key);
5781
+ }
5782
+ }
5783
+ };
5784
+ }, [cssVars]);
5785
+ return null;
5786
+ }
5787
+
5728
5788
  /**
5729
5789
  * DashboardThemeProvider
5730
5790
  *
@@ -5735,9 +5795,10 @@ function ThemeBroadcast(_ref) {
5735
5795
  * App chrome (navbar, tab bar, sidebar) stays OUTSIDE this wrapper
5736
5796
  * and keeps the app theme.
5737
5797
  */
5738
- var DashboardThemeProvider = function DashboardThemeProvider(_ref2) {
5739
- var themeKey = _ref2.themeKey,
5740
- children = _ref2.children;
5798
+ var DashboardThemeProvider = function DashboardThemeProvider(_ref3) {
5799
+ var _contextValue$current;
5800
+ var themeKey = _ref3.themeKey,
5801
+ children = _ref3.children;
5741
5802
  var parentContext = useContext(ThemeContext);
5742
5803
  var themes = parentContext.themes,
5743
5804
  themeVariant = parentContext.themeVariant;
@@ -5769,6 +5830,8 @@ var DashboardThemeProvider = function DashboardThemeProvider(_ref2) {
5769
5830
  value: contextValue,
5770
5831
  children: [/*#__PURE__*/jsx(ThemeBroadcast, {
5771
5832
  ctx: contextValue
5833
+ }), /*#__PURE__*/jsx(DashboardCssVarsBridge, {
5834
+ cssVars: contextValue === null || contextValue === void 0 || (_contextValue$current = contextValue.currentTheme) === null || _contextValue$current === void 0 ? void 0 : _contextValue$current.cssVars
5772
5835
  }), children]
5773
5836
  });
5774
5837
  };
@@ -28958,6 +29021,36 @@ var WorkspaceModel = function WorkspaceModel(workspaceItem) {
28958
29021
  function ownKeys$H(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
28959
29022
  function _objectSpread$H(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$H(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$H(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
28960
29023
 
29024
+ /**
29025
+ * Last-resort family for ANY channel whose value can't be resolved
29026
+ * (unknown name, missing palette entry, missing shade, …). Picked
29027
+ * because it's universally safelist-covered, has every shade from
29028
+ * 50..950, and is visually neutral — so a misconfigured theme
29029
+ * renders "muted" rather than blank/transparent.
29030
+ *
29031
+ * Slack Generic etc. exposed this: when a channel value was missing
29032
+ * or unrecognized, ThemeModel silently emitted `bg-undefined-700`
29033
+ * (not in any safelist) → Tailwind dropped the class → the
29034
+ * dropdown popover rendered transparent. Falling back here means
29035
+ * no token can be "missing" by the time `getStylesForItem` reads it.
29036
+ */
29037
+ var FALLBACK_FAMILY = "gray";
29038
+
29039
+ /**
29040
+ * Resolve a channel value to a safelist-covered Tailwind family
29041
+ * name. Returns the original value when it's a hex (the caller
29042
+ * handles those via CSS variables) or a recognized palette family;
29043
+ * otherwise returns FALLBACK_FAMILY so the resulting class always
29044
+ * renders.
29045
+ */
29046
+ function resolveChannelFamily(channelValue) {
29047
+ if (isHexColor$1(channelValue)) return channelValue;
29048
+ if (typeof channelValue === "string" && channelValue.length > 0 && TAILWIND_PALETTE && TAILWIND_PALETTE[channelValue]) {
29049
+ return channelValue;
29050
+ }
29051
+ return FALLBACK_FAMILY;
29052
+ }
29053
+
28961
29054
  /**
28962
29055
  * getNextLevel
28963
29056
  * Need to generate the levels for tailwind
@@ -28976,6 +29069,11 @@ function invert(shade) {
28976
29069
  * shade. Routes to the arbitrary-value syntax (`bg-[var(--type-shade)]`)
28977
29070
  * when the channel's value is a hex.
28978
29071
  *
29072
+ * Layer 1 fallback (audit follow-up to the Slack Generic bug): if
29073
+ * `channelValue` isn't a hex AND isn't a recognized Tailwind palette
29074
+ * family, fall back to FALLBACK_FAMILY so the emitted class is
29075
+ * always safelist-covered and never resolves to `bg-undefined-700`.
29076
+ *
28979
29077
  * @param {"bg"|"text"|"border"} prefix
28980
29078
  * @param {string} type channel name (primary | secondary | …)
28981
29079
  * @param {number} shade tailwind shade (100..950)
@@ -28985,10 +29083,11 @@ function invert(shade) {
28985
29083
  function classFor(prefix, type, shade, channelValue) {
28986
29084
  var hover = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
28987
29085
  var h = hover ? "hover:" : "";
28988
- if (isHexColor$1(channelValue)) {
29086
+ var resolved = resolveChannelFamily(channelValue);
29087
+ if (isHexColor$1(resolved)) {
28989
29088
  return "".concat(h).concat(prefix, "-[var(--").concat(type, "-").concat(shade, ")]");
28990
29089
  }
28991
- return "".concat(h).concat(prefix, "-").concat(channelValue, "-").concat(shade);
29090
+ return "".concat(h).concat(prefix, "-").concat(resolved, "-").concat(shade);
28992
29091
  }
28993
29092
 
28994
29093
  /**
@@ -29001,8 +29100,10 @@ function classFor(prefix, type, shade, channelValue) {
29001
29100
  * produce a `var(--{type}-{shade})` reference that resolves against
29002
29101
  * the CSS custom properties ThemePreviewProvider writes to :root.
29003
29102
  *
29004
- * Returns null for unknown named-color families or shades caller
29005
- * falls back to className-only reactivity for that token.
29103
+ * Layer 1 fallback: when the channel value or shade can't be
29104
+ * resolved, fall back to FALLBACK_FAMILY's shade so cssValue is
29105
+ * never null. Pairs with classFor's same fallback to guarantee
29106
+ * every (type, shade) tuple yields a renderable token.
29006
29107
  */
29007
29108
  function cssValueFor(type, shade, channelValue) {
29008
29109
  if (isHexColor$1(channelValue)) {
@@ -29015,12 +29116,17 @@ function cssValueFor(type, shade, channelValue) {
29015
29116
  var _hex = shades[shade] || shades[String(shade)];
29016
29117
  if (_hex) return _hex;
29017
29118
  }
29018
- return null;
29119
+ return fallbackCssValue(shade);
29019
29120
  }
29020
29121
  var family = TAILWIND_PALETTE && TAILWIND_PALETTE[channelValue];
29021
- if (!family) return null;
29122
+ if (!family) return fallbackCssValue(shade);
29022
29123
  var hex = family[shade] || family[String(shade)];
29023
- return hex || null;
29124
+ return hex || fallbackCssValue(shade);
29125
+ }
29126
+ function fallbackCssValue(shade) {
29127
+ var fallback = TAILWIND_PALETTE && TAILWIND_PALETTE[FALLBACK_FAMILY];
29128
+ if (!fallback) return null;
29129
+ return fallback[shade] || fallback[String(shade)] || null;
29024
29130
  }
29025
29131
  function gradientFor(direction, type, fromShade, viaShade, toShade, channelValue) {
29026
29132
  if (isHexColor$1(channelValue)) {
@@ -35205,11 +35311,25 @@ var ChannelEditorModal = function ChannelEditorModal(_ref) {
35205
35311
  var nearest = useMemo(function () {
35206
35312
  return nearestSwatch(selectedHex, families);
35207
35313
  }, [selectedHex, families]);
35314
+
35315
+ // Auto-switch the family tab when the underlying selected color
35316
+ // changes (user picked a different channel/shade or the theme
35317
+ // mutated). Track the last hex we synced for so we don't fight
35318
+ // the user's manual tab clicks — without this guard, every
35319
+ // re-render rebuilds `nearest` as a new object reference and the
35320
+ // effect snaps activeFamily back to the family containing the
35321
+ // current color, making it impossible to browse other families.
35322
+ var lastSyncedHexRef = useRef(null);
35208
35323
  useEffect(function () {
35209
- if (!isOpen) return;
35324
+ if (!isOpen) {
35325
+ lastSyncedHexRef.current = null;
35326
+ return;
35327
+ }
35328
+ if (lastSyncedHexRef.current === selectedHex) return;
35329
+ lastSyncedHexRef.current = selectedHex;
35210
35330
  if (nearest) setActiveFamily(nearest.family);
35211
35331
  // eslint-disable-next-line react-hooks/exhaustive-deps
35212
- }, [isOpen, nearest]);
35332
+ }, [isOpen, selectedHex, nearest]);
35213
35333
  function expandChannel(channelKey) {
35214
35334
  setActiveChannel(channelKey);
35215
35335
  setActiveSlot("base");