@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.js CHANGED
@@ -2567,6 +2567,27 @@ var ThemeWrapper = function ThemeWrapper(_ref) {
2567
2567
  // eslint-disable-next-line react-hooks/exhaustive-deps
2568
2568
  }, [chosenTheme, themeVariant, themeName, themesForApplication, rawThemes]);
2569
2569
 
2570
+ // Broadcast the app-level theme to a SEPARATE global so
2571
+ // components rendered OUTSIDE the React theme tree
2572
+ // (WidgetBuilderModal, AppUpdatesModal — they live as siblings
2573
+ // of DashboardStage in dash-electron's Dash.js render) can fall
2574
+ // back to it when no dashboard-specific override is in play.
2575
+ //
2576
+ // We can't share the same global with DashboardThemeProvider's
2577
+ // per-workspace broadcast because React commits child effects
2578
+ // BEFORE parent effects — ThemeWrapper would always run last
2579
+ // and silently overwrite the workspace theme. Two separate
2580
+ // globals avoid the race: consumers read
2581
+ // `__dashThemeContext` (workspace) first, fall back to
2582
+ // `__dashAppThemeContext` (app-level) when no workspace is open.
2583
+ React.useEffect(function () {
2584
+ if (typeof window === "undefined") return undefined;
2585
+ if (contextValue !== null && contextValue !== void 0 && contextValue.currentTheme) {
2586
+ window.__dashAppThemeContext = contextValue;
2587
+ window.dispatchEvent(new Event("dash:theme-changed"));
2588
+ }
2589
+ }, [contextValue]);
2590
+
2570
2591
  // Write the active theme's CSS custom properties to :root so any
2571
2592
  // hex-channel tokens (`bg-[var(--primary-700)]` etc. emitted by
2572
2593
  // ThemeModel) resolve app-wide. Without this, saving a hex theme
@@ -5743,6 +5764,45 @@ function ThemeBroadcast(_ref) {
5743
5764
  return null;
5744
5765
  }
5745
5766
 
5767
+ /**
5768
+ * Writes the dashboard theme's cssVars to `:root` while mounted, and
5769
+ * restores the previous values on unmount / theme switch.
5770
+ *
5771
+ * ThemeWrapper writes the APP theme's cssVars at the top of the tree.
5772
+ * Without this helper, a dashboard that overrides the app theme with a
5773
+ * hex-channel theme (like "Slack Generic") would carry the right class
5774
+ * names (`bg-[var(--primary-900)]`) but the `--primary-900` variable
5775
+ * on :root would still be the APP theme's value — or undefined if the
5776
+ * app theme is named-family. Hex-themed surfaces then render with no
5777
+ * background. This effect closes that gap by promoting the dashboard
5778
+ * theme's cssVars to :root for the duration of its mount.
5779
+ */
5780
+ function DashboardCssVarsBridge(_ref2) {
5781
+ var cssVars = _ref2.cssVars;
5782
+ React.useEffect(function () {
5783
+ if (!cssVars || typeof document === "undefined") return undefined;
5784
+ var root = document.documentElement;
5785
+ var previous = {};
5786
+ var keys = Object.keys(cssVars);
5787
+ for (var _i = 0, _keys = keys; _i < _keys.length; _i++) {
5788
+ var key = _keys[_i];
5789
+ previous[key] = root.style.getPropertyValue(key);
5790
+ root.style.setProperty(key, cssVars[key]);
5791
+ }
5792
+ return function () {
5793
+ for (var _i2 = 0, _keys2 = keys; _i2 < _keys2.length; _i2++) {
5794
+ var _key = _keys2[_i2];
5795
+ if (previous[_key]) {
5796
+ root.style.setProperty(_key, previous[_key]);
5797
+ } else {
5798
+ root.style.removeProperty(_key);
5799
+ }
5800
+ }
5801
+ };
5802
+ }, [cssVars]);
5803
+ return null;
5804
+ }
5805
+
5746
5806
  /**
5747
5807
  * DashboardThemeProvider
5748
5808
  *
@@ -5753,9 +5813,10 @@ function ThemeBroadcast(_ref) {
5753
5813
  * App chrome (navbar, tab bar, sidebar) stays OUTSIDE this wrapper
5754
5814
  * and keeps the app theme.
5755
5815
  */
5756
- var DashboardThemeProvider = function DashboardThemeProvider(_ref2) {
5757
- var themeKey = _ref2.themeKey,
5758
- children = _ref2.children;
5816
+ var DashboardThemeProvider = function DashboardThemeProvider(_ref3) {
5817
+ var _contextValue$current;
5818
+ var themeKey = _ref3.themeKey,
5819
+ children = _ref3.children;
5759
5820
  var parentContext = React.useContext(DashReact.ThemeContext);
5760
5821
  var themes = parentContext.themes,
5761
5822
  themeVariant = parentContext.themeVariant;
@@ -5787,6 +5848,8 @@ var DashboardThemeProvider = function DashboardThemeProvider(_ref2) {
5787
5848
  value: contextValue,
5788
5849
  children: [/*#__PURE__*/jsxRuntime.jsx(ThemeBroadcast, {
5789
5850
  ctx: contextValue
5851
+ }), /*#__PURE__*/jsxRuntime.jsx(DashboardCssVarsBridge, {
5852
+ cssVars: contextValue === null || contextValue === void 0 || (_contextValue$current = contextValue.currentTheme) === null || _contextValue$current === void 0 ? void 0 : _contextValue$current.cssVars
5790
5853
  }), children]
5791
5854
  });
5792
5855
  };
@@ -28976,6 +29039,36 @@ var WorkspaceModel = function WorkspaceModel(workspaceItem) {
28976
29039
  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; }
28977
29040
  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; }
28978
29041
 
29042
+ /**
29043
+ * Last-resort family for ANY channel whose value can't be resolved
29044
+ * (unknown name, missing palette entry, missing shade, …). Picked
29045
+ * because it's universally safelist-covered, has every shade from
29046
+ * 50..950, and is visually neutral — so a misconfigured theme
29047
+ * renders "muted" rather than blank/transparent.
29048
+ *
29049
+ * Slack Generic etc. exposed this: when a channel value was missing
29050
+ * or unrecognized, ThemeModel silently emitted `bg-undefined-700`
29051
+ * (not in any safelist) → Tailwind dropped the class → the
29052
+ * dropdown popover rendered transparent. Falling back here means
29053
+ * no token can be "missing" by the time `getStylesForItem` reads it.
29054
+ */
29055
+ var FALLBACK_FAMILY = "gray";
29056
+
29057
+ /**
29058
+ * Resolve a channel value to a safelist-covered Tailwind family
29059
+ * name. Returns the original value when it's a hex (the caller
29060
+ * handles those via CSS variables) or a recognized palette family;
29061
+ * otherwise returns FALLBACK_FAMILY so the resulting class always
29062
+ * renders.
29063
+ */
29064
+ function resolveChannelFamily(channelValue) {
29065
+ if (DashReact.isHexColor(channelValue)) return channelValue;
29066
+ if (typeof channelValue === "string" && channelValue.length > 0 && DashReact.TAILWIND_PALETTE && DashReact.TAILWIND_PALETTE[channelValue]) {
29067
+ return channelValue;
29068
+ }
29069
+ return FALLBACK_FAMILY;
29070
+ }
29071
+
28979
29072
  /**
28980
29073
  * getNextLevel
28981
29074
  * Need to generate the levels for tailwind
@@ -28994,6 +29087,11 @@ function invert(shade) {
28994
29087
  * shade. Routes to the arbitrary-value syntax (`bg-[var(--type-shade)]`)
28995
29088
  * when the channel's value is a hex.
28996
29089
  *
29090
+ * Layer 1 fallback (audit follow-up to the Slack Generic bug): if
29091
+ * `channelValue` isn't a hex AND isn't a recognized Tailwind palette
29092
+ * family, fall back to FALLBACK_FAMILY so the emitted class is
29093
+ * always safelist-covered and never resolves to `bg-undefined-700`.
29094
+ *
28997
29095
  * @param {"bg"|"text"|"border"} prefix
28998
29096
  * @param {string} type channel name (primary | secondary | …)
28999
29097
  * @param {number} shade tailwind shade (100..950)
@@ -29003,10 +29101,11 @@ function invert(shade) {
29003
29101
  function classFor(prefix, type, shade, channelValue) {
29004
29102
  var hover = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
29005
29103
  var h = hover ? "hover:" : "";
29006
- if (DashReact.isHexColor(channelValue)) {
29104
+ var resolved = resolveChannelFamily(channelValue);
29105
+ if (DashReact.isHexColor(resolved)) {
29007
29106
  return "".concat(h).concat(prefix, "-[var(--").concat(type, "-").concat(shade, ")]");
29008
29107
  }
29009
- return "".concat(h).concat(prefix, "-").concat(channelValue, "-").concat(shade);
29108
+ return "".concat(h).concat(prefix, "-").concat(resolved, "-").concat(shade);
29010
29109
  }
29011
29110
 
29012
29111
  /**
@@ -29019,8 +29118,10 @@ function classFor(prefix, type, shade, channelValue) {
29019
29118
  * produce a `var(--{type}-{shade})` reference that resolves against
29020
29119
  * the CSS custom properties ThemePreviewProvider writes to :root.
29021
29120
  *
29022
- * Returns null for unknown named-color families or shades caller
29023
- * falls back to className-only reactivity for that token.
29121
+ * Layer 1 fallback: when the channel value or shade can't be
29122
+ * resolved, fall back to FALLBACK_FAMILY's shade so cssValue is
29123
+ * never null. Pairs with classFor's same fallback to guarantee
29124
+ * every (type, shade) tuple yields a renderable token.
29024
29125
  */
29025
29126
  function cssValueFor(type, shade, channelValue) {
29026
29127
  if (DashReact.isHexColor(channelValue)) {
@@ -29033,12 +29134,17 @@ function cssValueFor(type, shade, channelValue) {
29033
29134
  var _hex = shades[shade] || shades[String(shade)];
29034
29135
  if (_hex) return _hex;
29035
29136
  }
29036
- return null;
29137
+ return fallbackCssValue(shade);
29037
29138
  }
29038
29139
  var family = DashReact.TAILWIND_PALETTE && DashReact.TAILWIND_PALETTE[channelValue];
29039
- if (!family) return null;
29140
+ if (!family) return fallbackCssValue(shade);
29040
29141
  var hex = family[shade] || family[String(shade)];
29041
- return hex || null;
29142
+ return hex || fallbackCssValue(shade);
29143
+ }
29144
+ function fallbackCssValue(shade) {
29145
+ var fallback = DashReact.TAILWIND_PALETTE && DashReact.TAILWIND_PALETTE[FALLBACK_FAMILY];
29146
+ if (!fallback) return null;
29147
+ return fallback[shade] || fallback[String(shade)] || null;
29042
29148
  }
29043
29149
  function gradientFor(direction, type, fromShade, viaShade, toShade, channelValue) {
29044
29150
  if (DashReact.isHexColor(channelValue)) {
@@ -35223,11 +35329,25 @@ var ChannelEditorModal = function ChannelEditorModal(_ref) {
35223
35329
  var nearest = React.useMemo(function () {
35224
35330
  return nearestSwatch(selectedHex, families);
35225
35331
  }, [selectedHex, families]);
35332
+
35333
+ // Auto-switch the family tab when the underlying selected color
35334
+ // changes (user picked a different channel/shade or the theme
35335
+ // mutated). Track the last hex we synced for so we don't fight
35336
+ // the user's manual tab clicks — without this guard, every
35337
+ // re-render rebuilds `nearest` as a new object reference and the
35338
+ // effect snaps activeFamily back to the family containing the
35339
+ // current color, making it impossible to browse other families.
35340
+ var lastSyncedHexRef = React.useRef(null);
35226
35341
  React.useEffect(function () {
35227
- if (!isOpen) return;
35342
+ if (!isOpen) {
35343
+ lastSyncedHexRef.current = null;
35344
+ return;
35345
+ }
35346
+ if (lastSyncedHexRef.current === selectedHex) return;
35347
+ lastSyncedHexRef.current = selectedHex;
35228
35348
  if (nearest) setActiveFamily(nearest.family);
35229
35349
  // eslint-disable-next-line react-hooks/exhaustive-deps
35230
- }, [isOpen, nearest]);
35350
+ }, [isOpen, selectedHex, nearest]);
35231
35351
  function expandChannel(channelKey) {
35232
35352
  setActiveChannel(channelKey);
35233
35353
  setActiveSlot("base");