@atlaskit/editor-plugin-text-color 12.3.18 → 12.3.19

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @atlaskit/editor-plugin-text-color
2
2
 
3
+ ## 12.3.19
4
+
5
+ ### Patch Changes
6
+
7
+ - [`4821d5874a821`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/4821d5874a821) -
8
+ EDITOR-7869: Show worst accessibility status when selection spans multiple text or highlight
9
+ colors
10
+ - Updated dependencies
11
+
3
12
  ## 12.3.18
4
13
 
5
14
  ### Patch Changes
@@ -11,7 +11,6 @@ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/de
11
11
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
12
12
  var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
13
13
  var _uiColor = require("@atlaskit/editor-common/ui-color");
14
- var _text = require("@atlaskit/editor-palette/text");
15
14
  var _state = require("@atlaskit/editor-prosemirror/state");
16
15
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
17
16
  var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
@@ -20,13 +19,6 @@ var _constants = require("./utils/constants");
20
19
  var _disabled = require("./utils/disabled");
21
20
  function ownKeys(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; }
22
21
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
23
- var getColorInNonActiveTheme = function getColorInNonActiveTheme(color, defaultColor) {
24
- if (!color || color === defaultColor) {
25
- return (0, _uiColor.getTokenCSSVariableValueForNonActiveTheme)("var(--ds-text, #292A2E)", defaultColor);
26
- }
27
- var colorValue = (0, _text.hexToEditorTextPaletteColor)(color) || color;
28
- return (0, _uiColor.getTokenCSSVariableValueForNonActiveTheme)(colorValue, color);
29
- };
30
22
  function createInitialPluginState(editorState, pluginConfig) {
31
23
  var defaultColor = (pluginConfig === null || pluginConfig === void 0 ? void 0 : pluginConfig.defaultColor) || _constants.DEFAULT_COLOR;
32
24
  var paletteColors = (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_lovability_text_bg_color', 'isEnabled', true) ? _uiColor.textColorPaletteNew : _uiColor.textColorPalette;
@@ -43,7 +35,8 @@ function createInitialPluginState(editorState, pluginConfig) {
43
35
  defaultColor: defaultColor.color,
44
36
  isPaletteOpen: false
45
37
  }, (0, _platformFeatureFlags.fg)('platform_editor_lovability_text_bg_color_patch_1') && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_lovability_text_bg_color', 'isEnabled', true) && {
46
- colorInNonActiveTheme: getColorInNonActiveTheme(color, defaultColor.color)
38
+ colorInNonActiveTheme: (0, _uiColor.getTextColorInNonActiveTheme)(color, defaultColor.color),
39
+ isMultiTextColor: (0, _color.isMultiTextColorSelection)(editorState)
47
40
  });
48
41
  return state;
49
42
  }
@@ -70,7 +63,8 @@ function createPlugin(dispatch, pluginConfig) {
70
63
  nextState = _objectSpread(_objectSpread({}, pluginState), {}, {
71
64
  color: pluginState.defaultColor
72
65
  }, (0, _platformFeatureFlags.fg)('platform_editor_lovability_text_bg_color_patch_1') && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_lovability_text_bg_color', 'isEnabled', true) && {
73
- colorInNonActiveTheme: getColorInNonActiveTheme(pluginState.defaultColor, pluginState.defaultColor)
66
+ colorInNonActiveTheme: (0, _uiColor.getTextColorInNonActiveTheme)(pluginState.defaultColor, pluginState.defaultColor),
67
+ isMultiTextColor: false
74
68
  });
75
69
  break;
76
70
  case ACTIONS.SET_COLOR:
@@ -79,7 +73,8 @@ function createPlugin(dispatch, pluginConfig) {
79
73
  disabled: false,
80
74
  isPaletteOpen: false
81
75
  }, (0, _platformFeatureFlags.fg)('platform_editor_lovability_text_bg_color_patch_1') && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_lovability_text_bg_color', 'isEnabled', true) && {
82
- colorInNonActiveTheme: getColorInNonActiveTheme(meta.color, pluginState.defaultColor)
76
+ colorInNonActiveTheme: (0, _uiColor.getTextColorInNonActiveTheme)(meta.color, pluginState.defaultColor),
77
+ isMultiTextColor: false
83
78
  });
84
79
  break;
85
80
  case ACTIONS.DISABLE:
@@ -98,10 +93,11 @@ function createPlugin(dispatch, pluginConfig) {
98
93
  color: color,
99
94
  disabled: (0, _disabled.getDisabledState)(newState)
100
95
  }, color !== pluginState.color && (0, _platformFeatureFlags.fg)('platform_editor_lovability_text_bg_color_patch_1') && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_lovability_text_bg_color', 'isEnabled', true) && {
101
- colorInNonActiveTheme: getColorInNonActiveTheme(color, pluginState.defaultColor)
96
+ colorInNonActiveTheme: (0, _uiColor.getTextColorInNonActiveTheme)(color, pluginState.defaultColor),
97
+ isMultiTextColor: (0, _color.isMultiTextColorSelection)(newState)
102
98
  });
103
99
  }
104
- if (pluginState && pluginState.color !== nextState.color || pluginState && pluginState.colorInNonActiveTheme !== nextState.colorInNonActiveTheme || pluginState && pluginState.disabled !== nextState.disabled || pluginState && pluginState.isPaletteOpen !== nextState.isPaletteOpen) {
100
+ if (pluginState && pluginState.color !== nextState.color || pluginState && pluginState.colorInNonActiveTheme !== nextState.colorInNonActiveTheme || pluginState && pluginState.disabled !== nextState.disabled || pluginState && pluginState.isMultiTextColor !== nextState.isMultiTextColor || pluginState && pluginState.isPaletteOpen !== nextState.isPaletteOpen) {
105
101
  dispatch(pluginKey, nextState);
106
102
  return nextState;
107
103
  }
@@ -3,7 +3,8 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.getActiveColorNew = exports.getActiveColor = void 0;
6
+ exports.isMultiTextColorSelection = exports.getActiveColorNew = exports.getActiveColor = void 0;
7
+ var _state = require("@atlaskit/editor-prosemirror/state");
7
8
  var _constants = require("./constants");
8
9
  /**
9
10
  * Use getDisabledStateNew instead and pass in `tr`
@@ -49,6 +50,36 @@ var getActiveColor = exports.getActiveColor = function getActiveColor(state) {
49
50
  }
50
51
  return marksWithColor.length ? marksWithColor[0].attrs.color : _constants.DEFAULT_COLOR.color;
51
52
  };
53
+
54
+ /**
55
+ * Returns true only when the selection contains two or more *distinct* text
56
+ * colors.
57
+ *
58
+ * This is deliberately narrower than `getActiveColor() === null`: `getActiveColor`
59
+ * also returns `null` for a mixed selection where a single colored region sits
60
+ * alongside uncolored text (`marksWithColor.length === 1 && marks.length > 1`).
61
+ * That mixed case is not a multi-color selection and must not trigger the
62
+ * expensive worst-status selection walk, so we key the `isMultiTextColor` flag
63
+ * off this predicate instead.
64
+ */
65
+ var isMultiTextColorSelection = exports.isMultiTextColorSelection = function isMultiTextColorSelection(state) {
66
+ var selection = state.selection,
67
+ doc = state.doc;
68
+ if (!(selection instanceof _state.TextSelection)) {
69
+ return false;
70
+ }
71
+ var textColor = doc.type.schema.marks.textColor;
72
+ var colors = new Set();
73
+ doc.nodesBetween(selection.from, selection.to, function (node) {
74
+ if (node.isLeaf) {
75
+ var mark = textColor.isInSet(node.marks);
76
+ if (mark) {
77
+ colors.add(mark.attrs.color);
78
+ }
79
+ }
80
+ });
81
+ return colors.size > 1;
82
+ };
52
83
  var getActiveColorNew = exports.getActiveColorNew = function getActiveColorNew(tr) {
53
84
  var _ref3 = tr.selection,
54
85
  $from = _ref3.$from,
@@ -15,6 +15,7 @@ var _hooks = require("@atlaskit/editor-common/hooks");
15
15
  var _messages = require("@atlaskit/editor-common/messages");
16
16
  var _uiColor = require("@atlaskit/editor-common/ui-color");
17
17
  var _editorPalette = require("@atlaskit/editor-palette");
18
+ var _state = require("@atlaskit/editor-prosemirror/state");
18
19
  var _accessibility = _interopRequireDefault(require("@atlaskit/icon/core/accessibility"));
19
20
  var _questionCircle = _interopRequireDefault(require("@atlaskit/icon/core/question-circle"));
20
21
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
@@ -22,6 +23,9 @@ var _compiled = require("@atlaskit/primitives/compiled");
22
23
  var _tokens = require("@atlaskit/tokens");
23
24
  var _colorContrast = require("../pm-plugins/utils/color-contrast");
24
25
  var _constants = require("../pm-plugins/utils/constants");
26
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
27
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
28
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
25
29
  var resolveColorValue = function resolveColorValue(color, fallback) {
26
30
  return (0, _uiColor.getTokenCSSVariableValue)(color) || (color.startsWith('var(') ? fallback : color);
27
31
  };
@@ -41,13 +45,15 @@ var getBackgroundColor = function getBackgroundColor(highlightColor) {
41
45
  };
42
46
  var useColorAccessibilityState = function useColorAccessibilityState(api) {
43
47
  return (0, _hooks.useSharedPluginStateWithSelector)(api, ['textColor', 'highlight'], function (states) {
44
- var _states$textColorStat, _states$highlightStat, _states$highlightStat2, _states$textColorStat2, _states$textColorStat3;
48
+ var _states$textColorStat, _states$highlightStat, _states$highlightStat2, _states$highlightStat3, _states$textColorStat2, _states$textColorStat3, _states$textColorStat4;
45
49
  return {
46
50
  defaultColor: (_states$textColorStat = states.textColorState) === null || _states$textColorStat === void 0 ? void 0 : _states$textColorStat.defaultColor,
47
51
  highlightColor: (_states$highlightStat = states.highlightState) === null || _states$highlightStat === void 0 ? void 0 : _states$highlightStat.activeColor,
48
52
  highlightColorInNonActiveTheme: (_states$highlightStat2 = states.highlightState) === null || _states$highlightStat2 === void 0 ? void 0 : _states$highlightStat2.activeColorInNonActiveTheme,
49
- textColor: (_states$textColorStat2 = states.textColorState) === null || _states$textColorStat2 === void 0 ? void 0 : _states$textColorStat2.color,
50
- textColorInNonActiveTheme: (_states$textColorStat3 = states.textColorState) === null || _states$textColorStat3 === void 0 ? void 0 : _states$textColorStat3.colorInNonActiveTheme
53
+ isMultiHighlightColor: (_states$highlightStat3 = states.highlightState) === null || _states$highlightStat3 === void 0 ? void 0 : _states$highlightStat3.isMultiHighlightColor,
54
+ isMultiTextColor: (_states$textColorStat2 = states.textColorState) === null || _states$textColorStat2 === void 0 ? void 0 : _states$textColorStat2.isMultiTextColor,
55
+ textColor: (_states$textColorStat3 = states.textColorState) === null || _states$textColorStat3 === void 0 ? void 0 : _states$textColorStat3.color,
56
+ textColorInNonActiveTheme: (_states$textColorStat4 = states.textColorState) === null || _states$textColorStat4 === void 0 ? void 0 : _states$textColorStat4.colorInNonActiveTheme
51
57
  };
52
58
  });
53
59
  };
@@ -60,6 +66,22 @@ var getContrastRatio = function getContrastRatio(defaultColor, highlightColor, t
60
66
  return null;
61
67
  }
62
68
  };
69
+
70
+ // Computes the contrast ratio for a (textColor, highlightColor) pair as it would
71
+ // appear in the non-active theme, using the shared resolvers so the walker
72
+ // evaluates the same colors the single-pair plugin-state path does. Returns null
73
+ // if it cannot be calculated.
74
+ var getNonActiveThemeContrastRatio = function getNonActiveThemeContrastRatio(defaultColor, highlightColor, textColor) {
75
+ try {
76
+ return (0, _colorContrast.getContrastRatio)((0, _uiColor.getTextColorInNonActiveTheme)(textColor !== null && textColor !== void 0 ? textColor : null, defaultColor || _constants.DEFAULT_COLOR.color), (0, _uiColor.getHighlightColorInNonActiveTheme)(highlightColor !== null && highlightColor !== void 0 ? highlightColor : null, {
77
+ defaultBackgroundColor: _constants.DEFAULT_BACKGROUND_COLOR,
78
+ transparentColor: _constants.TRANSPARENT_HIGHLIGHT_COLOR
79
+ }));
80
+ } catch (_unused2) {
81
+ // if we failed to calculate the contrast ratio, return null
82
+ return null;
83
+ }
84
+ };
63
85
  var getAccessibilityStatus = function getAccessibilityStatus(contrastRatio) {
64
86
  if (contrastRatio >= _constants.ACCESSIBLE_CONTRAST_RATIO) {
65
87
  return 'accessible';
@@ -69,9 +91,98 @@ var getAccessibilityStatus = function getAccessibilityStatus(contrastRatio) {
69
91
  return 'inaccessible';
70
92
  }
71
93
  };
72
- var AccessibilityStatus = function AccessibilityStatus(_ref) {
73
- var accessibilityStatus = _ref.accessibilityStatus,
74
- formatMessage = _ref.formatMessage;
94
+
95
+ /**
96
+ * Collects the worst accessibility status across all unique (textColor, highlightColor)
97
+ * combinations in the current selection.
98
+ *
99
+ * When the selection spans multiple text colors and/or highlight colors,
100
+ * this walks the document between selection boundaries and checks every
101
+ * text node's color marks, computing the contrast ratio for each unique pair
102
+ * (across both the active and non-active theme) and returning the worst
103
+ * status found.
104
+ */
105
+ var getWorstAccessibilityStatusFromSelection = function getWorstAccessibilityStatusFromSelection(api, defaultColor) {
106
+ if (!(api !== null && api !== void 0 && api.core)) {
107
+ return null;
108
+ }
109
+
110
+ // Track the lowest (worst) contrast ratio across all pairs; a lower ratio
111
+ // means a worse accessibility status, so a single numeric comparison is
112
+ // enough to find the worst pair.
113
+ var worstContrastRatio = null;
114
+ api.core.actions.execute(function (_ref) {
115
+ var tr = _ref.tr;
116
+ // Text/highlight colors only apply to a text range. If the selection is
117
+ // not a text selection (e.g. a node, cell or all selection) there is no
118
+ // meaningful range of colored text to evaluate.
119
+ if (!(tr.selection instanceof _state.TextSelection)) {
120
+ return null;
121
+ }
122
+ var _tr$selection = tr.selection,
123
+ from = _tr$selection.from,
124
+ to = _tr$selection.to;
125
+ var seen = new Set();
126
+ tr.doc.nodesBetween(from, to, function (node) {
127
+ // Text and highlight color marks only apply to text nodes, so skip
128
+ // any non-text leaves (e.g. emoji, mentions, inline cards). Continue
129
+ // descending into container nodes to reach their text children.
130
+ if (!node.isText) {
131
+ return !node.isLeaf;
132
+ }
133
+
134
+ // Extract both color marks in a single pass over the node's marks
135
+ // rather than two `Array.find` scans.
136
+ var textColor = null;
137
+ var highlightColor = null;
138
+ var _iterator = _createForOfIteratorHelper(node.marks),
139
+ _step;
140
+ try {
141
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
142
+ var mark = _step.value;
143
+ if (mark.type.name === 'textColor') {
144
+ var _mark$attrs$color;
145
+ textColor = (_mark$attrs$color = mark.attrs.color) !== null && _mark$attrs$color !== void 0 ? _mark$attrs$color : null;
146
+ } else if (mark.type.name === 'backgroundColor') {
147
+ var _mark$attrs$color2;
148
+ highlightColor = (_mark$attrs$color2 = mark.attrs.color) !== null && _mark$attrs$color2 !== void 0 ? _mark$attrs$color2 : null;
149
+ }
150
+ }
151
+ } catch (err) {
152
+ _iterator.e(err);
153
+ } finally {
154
+ _iterator.f();
155
+ }
156
+ var pairKey = "".concat(textColor !== null && textColor !== void 0 ? textColor : 'default', "|").concat(highlightColor !== null && highlightColor !== void 0 ? highlightColor : 'default');
157
+ // Skip pairs we have already evaluated so the expensive contrast
158
+ // calculations run at most once per unique (text, highlight) pair.
159
+ if (seen.has(pairKey)) {
160
+ return;
161
+ }
162
+ seen.add(pairKey);
163
+ var contrastRatio = getContrastRatio(defaultColor, highlightColor, textColor);
164
+ if (contrastRatio === null) {
165
+ return;
166
+ }
167
+
168
+ // Account for the non-active theme so the worst-case (least
169
+ // accessible) contrast across both themes is used for each pair,
170
+ // keeping multi-color selections consistent with the single-pair path.
171
+ var nonActiveThemeContrastRatio = getNonActiveThemeContrastRatio(defaultColor, highlightColor, textColor);
172
+ var mostCriticalContrastRatio = nonActiveThemeContrastRatio !== null ? Math.min(contrastRatio, nonActiveThemeContrastRatio) : contrastRatio;
173
+ if (worstContrastRatio === null || mostCriticalContrastRatio < worstContrastRatio) {
174
+ worstContrastRatio = mostCriticalContrastRatio;
175
+ }
176
+ });
177
+
178
+ // Read-only operation — do not dispatch
179
+ return null;
180
+ });
181
+ return worstContrastRatio === null ? null : getAccessibilityStatus(worstContrastRatio);
182
+ };
183
+ var AccessibilityStatus = function AccessibilityStatus(_ref2) {
184
+ var accessibilityStatus = _ref2.accessibilityStatus,
185
+ formatMessage = _ref2.formatMessage;
75
186
  if (accessibilityStatus === 'accessible') {
76
187
  return /*#__PURE__*/_react.default.createElement(_compiled.Text, {
77
188
  as: "span",
@@ -95,30 +206,65 @@ var AccessibilityStatus = function AccessibilityStatus(_ref) {
95
206
  var styles = {
96
207
  container: "_19itglyw _19pku2gc _ca0q1b66 _n3td1b66 _19bvu2gc _u5f31b66 _1e0c1txw _4cvr1h6o _1bah1yb4"
97
208
  };
98
- var ColorAccessibilityMenuItem = exports.ColorAccessibilityMenuItem = function ColorAccessibilityMenuItem(_ref2) {
99
- var api = _ref2.api;
209
+ var ColorAccessibilityMenuItem = exports.ColorAccessibilityMenuItem = function ColorAccessibilityMenuItem(_ref3) {
210
+ var api = _ref3.api;
100
211
  var _useIntl = (0, _reactIntl.useIntl)(),
101
212
  formatMessage = _useIntl.formatMessage;
102
213
  var _useColorAccessibilit = useColorAccessibilityState(api),
103
214
  defaultColor = _useColorAccessibilit.defaultColor,
104
215
  highlightColor = _useColorAccessibilit.highlightColor,
105
216
  highlightColorInNonActiveTheme = _useColorAccessibilit.highlightColorInNonActiveTheme,
217
+ isMultiHighlightColor = _useColorAccessibilit.isMultiHighlightColor,
218
+ isMultiTextColor = _useColorAccessibilit.isMultiTextColor,
106
219
  textColor = _useColorAccessibilit.textColor,
107
220
  textColorInNonActiveTheme = _useColorAccessibilit.textColorInNonActiveTheme;
108
- var contrastRatio = getContrastRatio(defaultColor, highlightColor, textColor);
109
- var nonActiveThemeContrastRatio = null;
110
- try {
221
+ // The multi-color selection handling (walking every unique text/highlight
222
+ // pair across the active and non-active theme) is only performed when the
223
+ // patch gate is on. When the gate is off we fall back to the legacy behavior
224
+ // of only evaluating the single active text/highlight color in the current
225
+ // active theme
226
+ var isMultiColorSelection = Boolean(isMultiTextColor || isMultiHighlightColor) && (0, _platformFeatureFlags.fg)('platform_editor_lovability_text_bg_color_patch_1');
227
+
228
+ // The complement of `isMultiColorSelection`: the single active text/highlight
229
+ // pair is evaluated whenever we are not walking a multi-color selection. This
230
+ // covers both a genuinely single-color selection (gate on) and every
231
+ // selection when the gate is off (legacy behavior).
232
+ var isSingleColorSelection = !isMultiColorSelection;
233
+
234
+ // Memoize the selection walk so we don't re-traverse the entire ProseMirror
235
+ // document on every render. It only needs to recompute when the multi-color
236
+ // selection state or the inputs to the walk change (the selection flags,
237
+ // default color, or the plugin api).
238
+ var multiColorStatus = _react.default.useMemo(function () {
239
+ return isMultiColorSelection ? getWorstAccessibilityStatusFromSelection(api, defaultColor) : null;
240
+ },
241
+ // eslint-disable-next-line react-hooks/exhaustive-deps
242
+ [isMultiColorSelection, isMultiTextColor, isMultiHighlightColor, defaultColor, api]);
243
+
244
+ // The single active text/highlight pair path. Everything below only applies
245
+ // when we are not walking a multi-color selection
246
+ var singleColorStatus = null;
247
+ if (isSingleColorSelection) {
248
+ var singleColorContrastRatio = getContrastRatio(defaultColor, highlightColor, textColor);
249
+
250
+ // Also account for the non-active theme (gate on only) so the worst-case
251
+ // (least accessible) status across both themes is shown.
252
+ var nonActiveThemeSingleColorRatio = null;
111
253
  if (textColorInNonActiveTheme && highlightColorInNonActiveTheme && (0, _platformFeatureFlags.fg)('platform_editor_lovability_text_bg_color_patch_1')) {
112
- nonActiveThemeContrastRatio = (0, _colorContrast.getContrastRatio)(textColorInNonActiveTheme, highlightColorInNonActiveTheme);
254
+ try {
255
+ nonActiveThemeSingleColorRatio = (0, _colorContrast.getContrastRatio)(textColorInNonActiveTheme, highlightColorInNonActiveTheme);
256
+ } catch (_unused3) {
257
+ // if we failed to calculate the contrast ratio, leave as null
258
+ nonActiveThemeSingleColorRatio = null;
259
+ }
113
260
  }
114
- } catch (_unused2) {
115
- // if we failed to calculate the contrast ratio, leave null
261
+ var mostCriticalContrastRatio = singleColorContrastRatio !== null && nonActiveThemeSingleColorRatio !== null ? Math.min(singleColorContrastRatio, nonActiveThemeSingleColorRatio) : singleColorContrastRatio;
262
+ singleColorStatus = mostCriticalContrastRatio !== null ? getAccessibilityStatus(mostCriticalContrastRatio) : null;
116
263
  }
117
- var mostCriticalContrastRatio = contrastRatio !== null && nonActiveThemeContrastRatio !== null && (0, _platformFeatureFlags.fg)('platform_editor_lovability_text_bg_color_patch_1') ? Math.min(contrastRatio, nonActiveThemeContrastRatio) : contrastRatio;
118
- if (mostCriticalContrastRatio === null) {
264
+ var accessibilityStatus = isSingleColorSelection ? singleColorStatus : multiColorStatus;
265
+ if (accessibilityStatus === null) {
119
266
  return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null);
120
267
  }
121
- var accessibilityStatus = getAccessibilityStatus(mostCriticalContrastRatio);
122
268
  var tooltipContent = function tooltipContent(accessibilityStatus) {
123
269
  if (accessibilityStatus === 'accessible') {
124
270
  return formatMessage(_messages.colorAccessibilityMessages.accessibleTooltip);
@@ -1,19 +1,11 @@
1
1
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
- import { getTokenCSSVariableValueForNonActiveTheme, textColorPalette, textColorPaletteNew } from '@atlaskit/editor-common/ui-color';
3
- import { hexToEditorTextPaletteColor } from '@atlaskit/editor-palette/text';
2
+ import { getTextColorInNonActiveTheme, textColorPalette, textColorPaletteNew } from '@atlaskit/editor-common/ui-color';
4
3
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
5
4
  import { fg } from '@atlaskit/platform-feature-flags';
6
5
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
7
- import { getActiveColor } from './utils/color';
6
+ import { getActiveColor, isMultiTextColorSelection } from './utils/color';
8
7
  import { DEFAULT_COLOR } from './utils/constants';
9
8
  import { getDisabledState } from './utils/disabled';
10
- const getColorInNonActiveTheme = (color, defaultColor) => {
11
- if (!color || color === defaultColor) {
12
- return getTokenCSSVariableValueForNonActiveTheme("var(--ds-text, #292A2E)", defaultColor);
13
- }
14
- const colorValue = hexToEditorTextPaletteColor(color) || color;
15
- return getTokenCSSVariableValueForNonActiveTheme(colorValue, color);
16
- };
17
9
  function createInitialPluginState(editorState, pluginConfig) {
18
10
  const defaultColor = (pluginConfig === null || pluginConfig === void 0 ? void 0 : pluginConfig.defaultColor) || DEFAULT_COLOR;
19
11
  const paletteColors = expValEqualsNoExposure('platform_editor_lovability_text_bg_color', 'isEnabled', true) ? textColorPaletteNew : textColorPalette;
@@ -31,7 +23,8 @@ function createInitialPluginState(editorState, pluginConfig) {
31
23
  isPaletteOpen: false,
32
24
  // eslint-disable-next-line @atlaskit/platform/no-preconditioning
33
25
  ...(fg('platform_editor_lovability_text_bg_color_patch_1') && expValEqualsNoExposure('platform_editor_lovability_text_bg_color', 'isEnabled', true) && {
34
- colorInNonActiveTheme: getColorInNonActiveTheme(color, defaultColor.color)
26
+ colorInNonActiveTheme: getTextColorInNonActiveTheme(color, defaultColor.color),
27
+ isMultiTextColor: isMultiTextColorSelection(editorState)
35
28
  })
36
29
  };
37
30
  return state;
@@ -61,7 +54,8 @@ export function createPlugin(dispatch, pluginConfig) {
61
54
  color: pluginState.defaultColor,
62
55
  // eslint-disable-next-line @atlaskit/platform/no-preconditioning
63
56
  ...(fg('platform_editor_lovability_text_bg_color_patch_1') && expValEqualsNoExposure('platform_editor_lovability_text_bg_color', 'isEnabled', true) && {
64
- colorInNonActiveTheme: getColorInNonActiveTheme(pluginState.defaultColor, pluginState.defaultColor)
57
+ colorInNonActiveTheme: getTextColorInNonActiveTheme(pluginState.defaultColor, pluginState.defaultColor),
58
+ isMultiTextColor: false
65
59
  })
66
60
  };
67
61
  break;
@@ -73,7 +67,8 @@ export function createPlugin(dispatch, pluginConfig) {
73
67
  isPaletteOpen: false,
74
68
  // eslint-disable-next-line @atlaskit/platform/no-preconditioning
75
69
  ...(fg('platform_editor_lovability_text_bg_color_patch_1') && expValEqualsNoExposure('platform_editor_lovability_text_bg_color', 'isEnabled', true) && {
76
- colorInNonActiveTheme: getColorInNonActiveTheme(meta.color, pluginState.defaultColor)
70
+ colorInNonActiveTheme: getTextColorInNonActiveTheme(meta.color, pluginState.defaultColor),
71
+ isMultiTextColor: false
77
72
  })
78
73
  };
79
74
  break;
@@ -97,11 +92,12 @@ export function createPlugin(dispatch, pluginConfig) {
97
92
  disabled: getDisabledState(newState),
98
93
  // eslint-disable-next-line @atlaskit/platform/no-preconditioning
99
94
  ...(color !== pluginState.color && fg('platform_editor_lovability_text_bg_color_patch_1') && expValEqualsNoExposure('platform_editor_lovability_text_bg_color', 'isEnabled', true) && {
100
- colorInNonActiveTheme: getColorInNonActiveTheme(color, pluginState.defaultColor)
95
+ colorInNonActiveTheme: getTextColorInNonActiveTheme(color, pluginState.defaultColor),
96
+ isMultiTextColor: isMultiTextColorSelection(newState)
101
97
  })
102
98
  };
103
99
  }
104
- if (pluginState && pluginState.color !== nextState.color || pluginState && pluginState.colorInNonActiveTheme !== nextState.colorInNonActiveTheme || pluginState && pluginState.disabled !== nextState.disabled || pluginState && pluginState.isPaletteOpen !== nextState.isPaletteOpen) {
100
+ if (pluginState && pluginState.color !== nextState.color || pluginState && pluginState.colorInNonActiveTheme !== nextState.colorInNonActiveTheme || pluginState && pluginState.disabled !== nextState.disabled || pluginState && pluginState.isMultiTextColor !== nextState.isMultiTextColor || pluginState && pluginState.isPaletteOpen !== nextState.isPaletteOpen) {
105
101
  dispatch(pluginKey, nextState);
106
102
  return nextState;
107
103
  }
@@ -1,3 +1,4 @@
1
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
1
2
  import { DEFAULT_COLOR } from './constants';
2
3
 
3
4
  /**
@@ -44,6 +45,40 @@ export const getActiveColor = state => {
44
45
  }
45
46
  return marksWithColor.length ? marksWithColor[0].attrs.color : DEFAULT_COLOR.color;
46
47
  };
48
+
49
+ /**
50
+ * Returns true only when the selection contains two or more *distinct* text
51
+ * colors.
52
+ *
53
+ * This is deliberately narrower than `getActiveColor() === null`: `getActiveColor`
54
+ * also returns `null` for a mixed selection where a single colored region sits
55
+ * alongside uncolored text (`marksWithColor.length === 1 && marks.length > 1`).
56
+ * That mixed case is not a multi-color selection and must not trigger the
57
+ * expensive worst-status selection walk, so we key the `isMultiTextColor` flag
58
+ * off this predicate instead.
59
+ */
60
+ export const isMultiTextColorSelection = state => {
61
+ const {
62
+ selection,
63
+ doc
64
+ } = state;
65
+ if (!(selection instanceof TextSelection)) {
66
+ return false;
67
+ }
68
+ const {
69
+ textColor
70
+ } = doc.type.schema.marks;
71
+ const colors = new Set();
72
+ doc.nodesBetween(selection.from, selection.to, node => {
73
+ if (node.isLeaf) {
74
+ const mark = textColor.isInSet(node.marks);
75
+ if (mark) {
76
+ colors.add(mark.attrs.color);
77
+ }
78
+ }
79
+ });
80
+ return colors.size > 1;
81
+ };
47
82
  export const getActiveColorNew = tr => {
48
83
  const {
49
84
  $from,
@@ -6,8 +6,9 @@ import { useIntl } from 'react-intl';
6
6
  import { IconButton } from '@atlaskit/button/new';
7
7
  import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
8
8
  import { colorAccessibilityMessages as messages } from '@atlaskit/editor-common/messages';
9
- import { getTokenCSSVariableValue } from '@atlaskit/editor-common/ui-color';
9
+ import { getHighlightColorInNonActiveTheme, getTextColorInNonActiveTheme, getTokenCSSVariableValue } from '@atlaskit/editor-common/ui-color';
10
10
  import { hexToEditorTextBackgroundPaletteColor, hexToEditorTextPaletteColor } from '@atlaskit/editor-palette';
11
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
11
12
  import AccessibilityIcon from '@atlaskit/icon/core/accessibility';
12
13
  import QuestionCircleIcon from '@atlaskit/icon/core/question-circle';
13
14
  import { fg } from '@atlaskit/platform-feature-flags';
@@ -34,13 +35,15 @@ const getBackgroundColor = highlightColor => {
34
35
  };
35
36
  const useColorAccessibilityState = api => {
36
37
  return useSharedPluginStateWithSelector(api, ['textColor', 'highlight'], states => {
37
- var _states$textColorStat, _states$highlightStat, _states$highlightStat2, _states$textColorStat2, _states$textColorStat3;
38
+ var _states$textColorStat, _states$highlightStat, _states$highlightStat2, _states$highlightStat3, _states$textColorStat2, _states$textColorStat3, _states$textColorStat4;
38
39
  return {
39
40
  defaultColor: (_states$textColorStat = states.textColorState) === null || _states$textColorStat === void 0 ? void 0 : _states$textColorStat.defaultColor,
40
41
  highlightColor: (_states$highlightStat = states.highlightState) === null || _states$highlightStat === void 0 ? void 0 : _states$highlightStat.activeColor,
41
42
  highlightColorInNonActiveTheme: (_states$highlightStat2 = states.highlightState) === null || _states$highlightStat2 === void 0 ? void 0 : _states$highlightStat2.activeColorInNonActiveTheme,
42
- textColor: (_states$textColorStat2 = states.textColorState) === null || _states$textColorStat2 === void 0 ? void 0 : _states$textColorStat2.color,
43
- textColorInNonActiveTheme: (_states$textColorStat3 = states.textColorState) === null || _states$textColorStat3 === void 0 ? void 0 : _states$textColorStat3.colorInNonActiveTheme
43
+ isMultiHighlightColor: (_states$highlightStat3 = states.highlightState) === null || _states$highlightStat3 === void 0 ? void 0 : _states$highlightStat3.isMultiHighlightColor,
44
+ isMultiTextColor: (_states$textColorStat2 = states.textColorState) === null || _states$textColorStat2 === void 0 ? void 0 : _states$textColorStat2.isMultiTextColor,
45
+ textColor: (_states$textColorStat3 = states.textColorState) === null || _states$textColorStat3 === void 0 ? void 0 : _states$textColorStat3.color,
46
+ textColorInNonActiveTheme: (_states$textColorStat4 = states.textColorState) === null || _states$textColorStat4 === void 0 ? void 0 : _states$textColorStat4.colorInNonActiveTheme
44
47
  };
45
48
  });
46
49
  };
@@ -53,6 +56,22 @@ const getContrastRatio = (defaultColor, highlightColor, textColor) => {
53
56
  return null;
54
57
  }
55
58
  };
59
+
60
+ // Computes the contrast ratio for a (textColor, highlightColor) pair as it would
61
+ // appear in the non-active theme, using the shared resolvers so the walker
62
+ // evaluates the same colors the single-pair plugin-state path does. Returns null
63
+ // if it cannot be calculated.
64
+ const getNonActiveThemeContrastRatio = (defaultColor, highlightColor, textColor) => {
65
+ try {
66
+ return calcContrastRatio(getTextColorInNonActiveTheme(textColor !== null && textColor !== void 0 ? textColor : null, defaultColor || DEFAULT_COLOR.color), getHighlightColorInNonActiveTheme(highlightColor !== null && highlightColor !== void 0 ? highlightColor : null, {
67
+ defaultBackgroundColor: DEFAULT_BACKGROUND_COLOR,
68
+ transparentColor: TRANSPARENT_HIGHLIGHT_COLOR
69
+ }));
70
+ } catch {
71
+ // if we failed to calculate the contrast ratio, return null
72
+ return null;
73
+ }
74
+ };
56
75
  const getAccessibilityStatus = contrastRatio => {
57
76
  if (contrastRatio >= ACCESSIBLE_CONTRAST_RATIO) {
58
77
  return 'accessible';
@@ -62,6 +81,89 @@ const getAccessibilityStatus = contrastRatio => {
62
81
  return 'inaccessible';
63
82
  }
64
83
  };
84
+
85
+ /**
86
+ * Collects the worst accessibility status across all unique (textColor, highlightColor)
87
+ * combinations in the current selection.
88
+ *
89
+ * When the selection spans multiple text colors and/or highlight colors,
90
+ * this walks the document between selection boundaries and checks every
91
+ * text node's color marks, computing the contrast ratio for each unique pair
92
+ * (across both the active and non-active theme) and returning the worst
93
+ * status found.
94
+ */
95
+ const getWorstAccessibilityStatusFromSelection = (api, defaultColor) => {
96
+ if (!(api !== null && api !== void 0 && api.core)) {
97
+ return null;
98
+ }
99
+
100
+ // Track the lowest (worst) contrast ratio across all pairs; a lower ratio
101
+ // means a worse accessibility status, so a single numeric comparison is
102
+ // enough to find the worst pair.
103
+ let worstContrastRatio = null;
104
+ api.core.actions.execute(({
105
+ tr
106
+ }) => {
107
+ // Text/highlight colors only apply to a text range. If the selection is
108
+ // not a text selection (e.g. a node, cell or all selection) there is no
109
+ // meaningful range of colored text to evaluate.
110
+ if (!(tr.selection instanceof TextSelection)) {
111
+ return null;
112
+ }
113
+ const {
114
+ from,
115
+ to
116
+ } = tr.selection;
117
+ const seen = new Set();
118
+ tr.doc.nodesBetween(from, to, node => {
119
+ var _textColor, _highlightColor;
120
+ // Text and highlight color marks only apply to text nodes, so skip
121
+ // any non-text leaves (e.g. emoji, mentions, inline cards). Continue
122
+ // descending into container nodes to reach their text children.
123
+ if (!node.isText) {
124
+ return !node.isLeaf;
125
+ }
126
+
127
+ // Extract both color marks in a single pass over the node's marks
128
+ // rather than two `Array.find` scans.
129
+ let textColor = null;
130
+ let highlightColor = null;
131
+ for (const mark of node.marks) {
132
+ if (mark.type.name === 'textColor') {
133
+ var _mark$attrs$color;
134
+ textColor = (_mark$attrs$color = mark.attrs.color) !== null && _mark$attrs$color !== void 0 ? _mark$attrs$color : null;
135
+ } else if (mark.type.name === 'backgroundColor') {
136
+ var _mark$attrs$color2;
137
+ highlightColor = (_mark$attrs$color2 = mark.attrs.color) !== null && _mark$attrs$color2 !== void 0 ? _mark$attrs$color2 : null;
138
+ }
139
+ }
140
+ const pairKey = `${(_textColor = textColor) !== null && _textColor !== void 0 ? _textColor : 'default'}|${(_highlightColor = highlightColor) !== null && _highlightColor !== void 0 ? _highlightColor : 'default'}`;
141
+ // Skip pairs we have already evaluated so the expensive contrast
142
+ // calculations run at most once per unique (text, highlight) pair.
143
+ if (seen.has(pairKey)) {
144
+ return;
145
+ }
146
+ seen.add(pairKey);
147
+ const contrastRatio = getContrastRatio(defaultColor, highlightColor, textColor);
148
+ if (contrastRatio === null) {
149
+ return;
150
+ }
151
+
152
+ // Account for the non-active theme so the worst-case (least
153
+ // accessible) contrast across both themes is used for each pair,
154
+ // keeping multi-color selections consistent with the single-pair path.
155
+ const nonActiveThemeContrastRatio = getNonActiveThemeContrastRatio(defaultColor, highlightColor, textColor);
156
+ const mostCriticalContrastRatio = nonActiveThemeContrastRatio !== null ? Math.min(contrastRatio, nonActiveThemeContrastRatio) : contrastRatio;
157
+ if (worstContrastRatio === null || mostCriticalContrastRatio < worstContrastRatio) {
158
+ worstContrastRatio = mostCriticalContrastRatio;
159
+ }
160
+ });
161
+
162
+ // Read-only operation — do not dispatch
163
+ return null;
164
+ });
165
+ return worstContrastRatio === null ? null : getAccessibilityStatus(worstContrastRatio);
166
+ };
65
167
  const AccessibilityStatus = ({
66
168
  accessibilityStatus,
67
169
  formatMessage
@@ -99,23 +201,56 @@ export const ColorAccessibilityMenuItem = ({
99
201
  defaultColor,
100
202
  highlightColor,
101
203
  highlightColorInNonActiveTheme,
204
+ isMultiHighlightColor,
205
+ isMultiTextColor,
102
206
  textColor,
103
207
  textColorInNonActiveTheme
104
208
  } = useColorAccessibilityState(api);
105
- const contrastRatio = getContrastRatio(defaultColor, highlightColor, textColor);
106
- let nonActiveThemeContrastRatio = null;
107
- try {
209
+ // The multi-color selection handling (walking every unique text/highlight
210
+ // pair across the active and non-active theme) is only performed when the
211
+ // patch gate is on. When the gate is off we fall back to the legacy behavior
212
+ // of only evaluating the single active text/highlight color in the current
213
+ // active theme
214
+ const isMultiColorSelection = Boolean(isMultiTextColor || isMultiHighlightColor) && fg('platform_editor_lovability_text_bg_color_patch_1');
215
+
216
+ // The complement of `isMultiColorSelection`: the single active text/highlight
217
+ // pair is evaluated whenever we are not walking a multi-color selection. This
218
+ // covers both a genuinely single-color selection (gate on) and every
219
+ // selection when the gate is off (legacy behavior).
220
+ const isSingleColorSelection = !isMultiColorSelection;
221
+
222
+ // Memoize the selection walk so we don't re-traverse the entire ProseMirror
223
+ // document on every render. It only needs to recompute when the multi-color
224
+ // selection state or the inputs to the walk change (the selection flags,
225
+ // default color, or the plugin api).
226
+ const multiColorStatus = React.useMemo(() => isMultiColorSelection ? getWorstAccessibilityStatusFromSelection(api, defaultColor) : null,
227
+ // eslint-disable-next-line react-hooks/exhaustive-deps
228
+ [isMultiColorSelection, isMultiTextColor, isMultiHighlightColor, defaultColor, api]);
229
+
230
+ // The single active text/highlight pair path. Everything below only applies
231
+ // when we are not walking a multi-color selection
232
+ let singleColorStatus = null;
233
+ if (isSingleColorSelection) {
234
+ const singleColorContrastRatio = getContrastRatio(defaultColor, highlightColor, textColor);
235
+
236
+ // Also account for the non-active theme (gate on only) so the worst-case
237
+ // (least accessible) status across both themes is shown.
238
+ let nonActiveThemeSingleColorRatio = null;
108
239
  if (textColorInNonActiveTheme && highlightColorInNonActiveTheme && fg('platform_editor_lovability_text_bg_color_patch_1')) {
109
- nonActiveThemeContrastRatio = calcContrastRatio(textColorInNonActiveTheme, highlightColorInNonActiveTheme);
240
+ try {
241
+ nonActiveThemeSingleColorRatio = calcContrastRatio(textColorInNonActiveTheme, highlightColorInNonActiveTheme);
242
+ } catch {
243
+ // if we failed to calculate the contrast ratio, leave as null
244
+ nonActiveThemeSingleColorRatio = null;
245
+ }
110
246
  }
111
- } catch {
112
- // if we failed to calculate the contrast ratio, leave null
247
+ const mostCriticalContrastRatio = singleColorContrastRatio !== null && nonActiveThemeSingleColorRatio !== null ? Math.min(singleColorContrastRatio, nonActiveThemeSingleColorRatio) : singleColorContrastRatio;
248
+ singleColorStatus = mostCriticalContrastRatio !== null ? getAccessibilityStatus(mostCriticalContrastRatio) : null;
113
249
  }
114
- const mostCriticalContrastRatio = contrastRatio !== null && nonActiveThemeContrastRatio !== null && fg('platform_editor_lovability_text_bg_color_patch_1') ? Math.min(contrastRatio, nonActiveThemeContrastRatio) : contrastRatio;
115
- if (mostCriticalContrastRatio === null) {
250
+ const accessibilityStatus = isSingleColorSelection ? singleColorStatus : multiColorStatus;
251
+ if (accessibilityStatus === null) {
116
252
  return /*#__PURE__*/React.createElement(React.Fragment, null);
117
253
  }
118
- const accessibilityStatus = getAccessibilityStatus(mostCriticalContrastRatio);
119
254
  const tooltipContent = accessibilityStatus => {
120
255
  if (accessibilityStatus === 'accessible') {
121
256
  return formatMessage(messages.accessibleTooltip);
@@ -3,21 +3,13 @@ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
3
3
  function ownKeys(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; }
4
4
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5
5
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
6
- import { getTokenCSSVariableValueForNonActiveTheme, textColorPalette, textColorPaletteNew } from '@atlaskit/editor-common/ui-color';
7
- import { hexToEditorTextPaletteColor } from '@atlaskit/editor-palette/text';
6
+ import { getTextColorInNonActiveTheme, textColorPalette, textColorPaletteNew } from '@atlaskit/editor-common/ui-color';
8
7
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
9
8
  import { fg } from '@atlaskit/platform-feature-flags';
10
9
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
11
- import { getActiveColor } from './utils/color';
10
+ import { getActiveColor, isMultiTextColorSelection } from './utils/color';
12
11
  import { DEFAULT_COLOR } from './utils/constants';
13
12
  import { getDisabledState } from './utils/disabled';
14
- var getColorInNonActiveTheme = function getColorInNonActiveTheme(color, defaultColor) {
15
- if (!color || color === defaultColor) {
16
- return getTokenCSSVariableValueForNonActiveTheme("var(--ds-text, #292A2E)", defaultColor);
17
- }
18
- var colorValue = hexToEditorTextPaletteColor(color) || color;
19
- return getTokenCSSVariableValueForNonActiveTheme(colorValue, color);
20
- };
21
13
  function createInitialPluginState(editorState, pluginConfig) {
22
14
  var defaultColor = (pluginConfig === null || pluginConfig === void 0 ? void 0 : pluginConfig.defaultColor) || DEFAULT_COLOR;
23
15
  var paletteColors = expValEqualsNoExposure('platform_editor_lovability_text_bg_color', 'isEnabled', true) ? textColorPaletteNew : textColorPalette;
@@ -34,7 +26,8 @@ function createInitialPluginState(editorState, pluginConfig) {
34
26
  defaultColor: defaultColor.color,
35
27
  isPaletteOpen: false
36
28
  }, fg('platform_editor_lovability_text_bg_color_patch_1') && expValEqualsNoExposure('platform_editor_lovability_text_bg_color', 'isEnabled', true) && {
37
- colorInNonActiveTheme: getColorInNonActiveTheme(color, defaultColor.color)
29
+ colorInNonActiveTheme: getTextColorInNonActiveTheme(color, defaultColor.color),
30
+ isMultiTextColor: isMultiTextColorSelection(editorState)
38
31
  });
39
32
  return state;
40
33
  }
@@ -61,7 +54,8 @@ export function createPlugin(dispatch, pluginConfig) {
61
54
  nextState = _objectSpread(_objectSpread({}, pluginState), {}, {
62
55
  color: pluginState.defaultColor
63
56
  }, fg('platform_editor_lovability_text_bg_color_patch_1') && expValEqualsNoExposure('platform_editor_lovability_text_bg_color', 'isEnabled', true) && {
64
- colorInNonActiveTheme: getColorInNonActiveTheme(pluginState.defaultColor, pluginState.defaultColor)
57
+ colorInNonActiveTheme: getTextColorInNonActiveTheme(pluginState.defaultColor, pluginState.defaultColor),
58
+ isMultiTextColor: false
65
59
  });
66
60
  break;
67
61
  case ACTIONS.SET_COLOR:
@@ -70,7 +64,8 @@ export function createPlugin(dispatch, pluginConfig) {
70
64
  disabled: false,
71
65
  isPaletteOpen: false
72
66
  }, fg('platform_editor_lovability_text_bg_color_patch_1') && expValEqualsNoExposure('platform_editor_lovability_text_bg_color', 'isEnabled', true) && {
73
- colorInNonActiveTheme: getColorInNonActiveTheme(meta.color, pluginState.defaultColor)
67
+ colorInNonActiveTheme: getTextColorInNonActiveTheme(meta.color, pluginState.defaultColor),
68
+ isMultiTextColor: false
74
69
  });
75
70
  break;
76
71
  case ACTIONS.DISABLE:
@@ -89,10 +84,11 @@ export function createPlugin(dispatch, pluginConfig) {
89
84
  color: color,
90
85
  disabled: getDisabledState(newState)
91
86
  }, color !== pluginState.color && fg('platform_editor_lovability_text_bg_color_patch_1') && expValEqualsNoExposure('platform_editor_lovability_text_bg_color', 'isEnabled', true) && {
92
- colorInNonActiveTheme: getColorInNonActiveTheme(color, pluginState.defaultColor)
87
+ colorInNonActiveTheme: getTextColorInNonActiveTheme(color, pluginState.defaultColor),
88
+ isMultiTextColor: isMultiTextColorSelection(newState)
93
89
  });
94
90
  }
95
- if (pluginState && pluginState.color !== nextState.color || pluginState && pluginState.colorInNonActiveTheme !== nextState.colorInNonActiveTheme || pluginState && pluginState.disabled !== nextState.disabled || pluginState && pluginState.isPaletteOpen !== nextState.isPaletteOpen) {
91
+ if (pluginState && pluginState.color !== nextState.color || pluginState && pluginState.colorInNonActiveTheme !== nextState.colorInNonActiveTheme || pluginState && pluginState.disabled !== nextState.disabled || pluginState && pluginState.isMultiTextColor !== nextState.isMultiTextColor || pluginState && pluginState.isPaletteOpen !== nextState.isPaletteOpen) {
96
92
  dispatch(pluginKey, nextState);
97
93
  return nextState;
98
94
  }
@@ -1,3 +1,4 @@
1
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
1
2
  import { DEFAULT_COLOR } from './constants';
2
3
 
3
4
  /**
@@ -44,6 +45,36 @@ export var getActiveColor = function getActiveColor(state) {
44
45
  }
45
46
  return marksWithColor.length ? marksWithColor[0].attrs.color : DEFAULT_COLOR.color;
46
47
  };
48
+
49
+ /**
50
+ * Returns true only when the selection contains two or more *distinct* text
51
+ * colors.
52
+ *
53
+ * This is deliberately narrower than `getActiveColor() === null`: `getActiveColor`
54
+ * also returns `null` for a mixed selection where a single colored region sits
55
+ * alongside uncolored text (`marksWithColor.length === 1 && marks.length > 1`).
56
+ * That mixed case is not a multi-color selection and must not trigger the
57
+ * expensive worst-status selection walk, so we key the `isMultiTextColor` flag
58
+ * off this predicate instead.
59
+ */
60
+ export var isMultiTextColorSelection = function isMultiTextColorSelection(state) {
61
+ var selection = state.selection,
62
+ doc = state.doc;
63
+ if (!(selection instanceof TextSelection)) {
64
+ return false;
65
+ }
66
+ var textColor = doc.type.schema.marks.textColor;
67
+ var colors = new Set();
68
+ doc.nodesBetween(selection.from, selection.to, function (node) {
69
+ if (node.isLeaf) {
70
+ var mark = textColor.isInSet(node.marks);
71
+ if (mark) {
72
+ colors.add(mark.attrs.color);
73
+ }
74
+ }
75
+ });
76
+ return colors.size > 1;
77
+ };
47
78
  export var getActiveColorNew = function getActiveColorNew(tr) {
48
79
  var _ref3 = tr.selection,
49
80
  $from = _ref3.$from,
@@ -1,13 +1,17 @@
1
1
  /* ColorAccessibilityMenuItem.tsx generated by @compiled/babel-plugin v0.39.1 */
2
2
  import "./ColorAccessibilityMenuItem.compiled.css";
3
3
  import { ax, ix } from "@compiled/react/runtime";
4
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
5
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
6
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
4
7
  import React from 'react';
5
8
  import { useIntl } from 'react-intl';
6
9
  import { IconButton } from '@atlaskit/button/new';
7
10
  import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
8
11
  import { colorAccessibilityMessages as messages } from '@atlaskit/editor-common/messages';
9
- import { getTokenCSSVariableValue } from '@atlaskit/editor-common/ui-color';
12
+ import { getHighlightColorInNonActiveTheme, getTextColorInNonActiveTheme, getTokenCSSVariableValue } from '@atlaskit/editor-common/ui-color';
10
13
  import { hexToEditorTextBackgroundPaletteColor, hexToEditorTextPaletteColor } from '@atlaskit/editor-palette';
14
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
11
15
  import AccessibilityIcon from '@atlaskit/icon/core/accessibility';
12
16
  import QuestionCircleIcon from '@atlaskit/icon/core/question-circle';
13
17
  import { fg } from '@atlaskit/platform-feature-flags';
@@ -34,13 +38,15 @@ var getBackgroundColor = function getBackgroundColor(highlightColor) {
34
38
  };
35
39
  var useColorAccessibilityState = function useColorAccessibilityState(api) {
36
40
  return useSharedPluginStateWithSelector(api, ['textColor', 'highlight'], function (states) {
37
- var _states$textColorStat, _states$highlightStat, _states$highlightStat2, _states$textColorStat2, _states$textColorStat3;
41
+ var _states$textColorStat, _states$highlightStat, _states$highlightStat2, _states$highlightStat3, _states$textColorStat2, _states$textColorStat3, _states$textColorStat4;
38
42
  return {
39
43
  defaultColor: (_states$textColorStat = states.textColorState) === null || _states$textColorStat === void 0 ? void 0 : _states$textColorStat.defaultColor,
40
44
  highlightColor: (_states$highlightStat = states.highlightState) === null || _states$highlightStat === void 0 ? void 0 : _states$highlightStat.activeColor,
41
45
  highlightColorInNonActiveTheme: (_states$highlightStat2 = states.highlightState) === null || _states$highlightStat2 === void 0 ? void 0 : _states$highlightStat2.activeColorInNonActiveTheme,
42
- textColor: (_states$textColorStat2 = states.textColorState) === null || _states$textColorStat2 === void 0 ? void 0 : _states$textColorStat2.color,
43
- textColorInNonActiveTheme: (_states$textColorStat3 = states.textColorState) === null || _states$textColorStat3 === void 0 ? void 0 : _states$textColorStat3.colorInNonActiveTheme
46
+ isMultiHighlightColor: (_states$highlightStat3 = states.highlightState) === null || _states$highlightStat3 === void 0 ? void 0 : _states$highlightStat3.isMultiHighlightColor,
47
+ isMultiTextColor: (_states$textColorStat2 = states.textColorState) === null || _states$textColorStat2 === void 0 ? void 0 : _states$textColorStat2.isMultiTextColor,
48
+ textColor: (_states$textColorStat3 = states.textColorState) === null || _states$textColorStat3 === void 0 ? void 0 : _states$textColorStat3.color,
49
+ textColorInNonActiveTheme: (_states$textColorStat4 = states.textColorState) === null || _states$textColorStat4 === void 0 ? void 0 : _states$textColorStat4.colorInNonActiveTheme
44
50
  };
45
51
  });
46
52
  };
@@ -53,6 +59,22 @@ var getContrastRatio = function getContrastRatio(defaultColor, highlightColor, t
53
59
  return null;
54
60
  }
55
61
  };
62
+
63
+ // Computes the contrast ratio for a (textColor, highlightColor) pair as it would
64
+ // appear in the non-active theme, using the shared resolvers so the walker
65
+ // evaluates the same colors the single-pair plugin-state path does. Returns null
66
+ // if it cannot be calculated.
67
+ var getNonActiveThemeContrastRatio = function getNonActiveThemeContrastRatio(defaultColor, highlightColor, textColor) {
68
+ try {
69
+ return calcContrastRatio(getTextColorInNonActiveTheme(textColor !== null && textColor !== void 0 ? textColor : null, defaultColor || DEFAULT_COLOR.color), getHighlightColorInNonActiveTheme(highlightColor !== null && highlightColor !== void 0 ? highlightColor : null, {
70
+ defaultBackgroundColor: DEFAULT_BACKGROUND_COLOR,
71
+ transparentColor: TRANSPARENT_HIGHLIGHT_COLOR
72
+ }));
73
+ } catch (_unused2) {
74
+ // if we failed to calculate the contrast ratio, return null
75
+ return null;
76
+ }
77
+ };
56
78
  var getAccessibilityStatus = function getAccessibilityStatus(contrastRatio) {
57
79
  if (contrastRatio >= ACCESSIBLE_CONTRAST_RATIO) {
58
80
  return 'accessible';
@@ -62,9 +84,98 @@ var getAccessibilityStatus = function getAccessibilityStatus(contrastRatio) {
62
84
  return 'inaccessible';
63
85
  }
64
86
  };
65
- var AccessibilityStatus = function AccessibilityStatus(_ref) {
66
- var accessibilityStatus = _ref.accessibilityStatus,
67
- formatMessage = _ref.formatMessage;
87
+
88
+ /**
89
+ * Collects the worst accessibility status across all unique (textColor, highlightColor)
90
+ * combinations in the current selection.
91
+ *
92
+ * When the selection spans multiple text colors and/or highlight colors,
93
+ * this walks the document between selection boundaries and checks every
94
+ * text node's color marks, computing the contrast ratio for each unique pair
95
+ * (across both the active and non-active theme) and returning the worst
96
+ * status found.
97
+ */
98
+ var getWorstAccessibilityStatusFromSelection = function getWorstAccessibilityStatusFromSelection(api, defaultColor) {
99
+ if (!(api !== null && api !== void 0 && api.core)) {
100
+ return null;
101
+ }
102
+
103
+ // Track the lowest (worst) contrast ratio across all pairs; a lower ratio
104
+ // means a worse accessibility status, so a single numeric comparison is
105
+ // enough to find the worst pair.
106
+ var worstContrastRatio = null;
107
+ api.core.actions.execute(function (_ref) {
108
+ var tr = _ref.tr;
109
+ // Text/highlight colors only apply to a text range. If the selection is
110
+ // not a text selection (e.g. a node, cell or all selection) there is no
111
+ // meaningful range of colored text to evaluate.
112
+ if (!(tr.selection instanceof TextSelection)) {
113
+ return null;
114
+ }
115
+ var _tr$selection = tr.selection,
116
+ from = _tr$selection.from,
117
+ to = _tr$selection.to;
118
+ var seen = new Set();
119
+ tr.doc.nodesBetween(from, to, function (node) {
120
+ // Text and highlight color marks only apply to text nodes, so skip
121
+ // any non-text leaves (e.g. emoji, mentions, inline cards). Continue
122
+ // descending into container nodes to reach their text children.
123
+ if (!node.isText) {
124
+ return !node.isLeaf;
125
+ }
126
+
127
+ // Extract both color marks in a single pass over the node's marks
128
+ // rather than two `Array.find` scans.
129
+ var textColor = null;
130
+ var highlightColor = null;
131
+ var _iterator = _createForOfIteratorHelper(node.marks),
132
+ _step;
133
+ try {
134
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
135
+ var mark = _step.value;
136
+ if (mark.type.name === 'textColor') {
137
+ var _mark$attrs$color;
138
+ textColor = (_mark$attrs$color = mark.attrs.color) !== null && _mark$attrs$color !== void 0 ? _mark$attrs$color : null;
139
+ } else if (mark.type.name === 'backgroundColor') {
140
+ var _mark$attrs$color2;
141
+ highlightColor = (_mark$attrs$color2 = mark.attrs.color) !== null && _mark$attrs$color2 !== void 0 ? _mark$attrs$color2 : null;
142
+ }
143
+ }
144
+ } catch (err) {
145
+ _iterator.e(err);
146
+ } finally {
147
+ _iterator.f();
148
+ }
149
+ var pairKey = "".concat(textColor !== null && textColor !== void 0 ? textColor : 'default', "|").concat(highlightColor !== null && highlightColor !== void 0 ? highlightColor : 'default');
150
+ // Skip pairs we have already evaluated so the expensive contrast
151
+ // calculations run at most once per unique (text, highlight) pair.
152
+ if (seen.has(pairKey)) {
153
+ return;
154
+ }
155
+ seen.add(pairKey);
156
+ var contrastRatio = getContrastRatio(defaultColor, highlightColor, textColor);
157
+ if (contrastRatio === null) {
158
+ return;
159
+ }
160
+
161
+ // Account for the non-active theme so the worst-case (least
162
+ // accessible) contrast across both themes is used for each pair,
163
+ // keeping multi-color selections consistent with the single-pair path.
164
+ var nonActiveThemeContrastRatio = getNonActiveThemeContrastRatio(defaultColor, highlightColor, textColor);
165
+ var mostCriticalContrastRatio = nonActiveThemeContrastRatio !== null ? Math.min(contrastRatio, nonActiveThemeContrastRatio) : contrastRatio;
166
+ if (worstContrastRatio === null || mostCriticalContrastRatio < worstContrastRatio) {
167
+ worstContrastRatio = mostCriticalContrastRatio;
168
+ }
169
+ });
170
+
171
+ // Read-only operation — do not dispatch
172
+ return null;
173
+ });
174
+ return worstContrastRatio === null ? null : getAccessibilityStatus(worstContrastRatio);
175
+ };
176
+ var AccessibilityStatus = function AccessibilityStatus(_ref2) {
177
+ var accessibilityStatus = _ref2.accessibilityStatus,
178
+ formatMessage = _ref2.formatMessage;
68
179
  if (accessibilityStatus === 'accessible') {
69
180
  return /*#__PURE__*/React.createElement(Text, {
70
181
  as: "span",
@@ -88,30 +199,65 @@ var AccessibilityStatus = function AccessibilityStatus(_ref) {
88
199
  var styles = {
89
200
  container: "_19itglyw _19pku2gc _ca0q1b66 _n3td1b66 _19bvu2gc _u5f31b66 _1e0c1txw _4cvr1h6o _1bah1yb4"
90
201
  };
91
- export var ColorAccessibilityMenuItem = function ColorAccessibilityMenuItem(_ref2) {
92
- var api = _ref2.api;
202
+ export var ColorAccessibilityMenuItem = function ColorAccessibilityMenuItem(_ref3) {
203
+ var api = _ref3.api;
93
204
  var _useIntl = useIntl(),
94
205
  formatMessage = _useIntl.formatMessage;
95
206
  var _useColorAccessibilit = useColorAccessibilityState(api),
96
207
  defaultColor = _useColorAccessibilit.defaultColor,
97
208
  highlightColor = _useColorAccessibilit.highlightColor,
98
209
  highlightColorInNonActiveTheme = _useColorAccessibilit.highlightColorInNonActiveTheme,
210
+ isMultiHighlightColor = _useColorAccessibilit.isMultiHighlightColor,
211
+ isMultiTextColor = _useColorAccessibilit.isMultiTextColor,
99
212
  textColor = _useColorAccessibilit.textColor,
100
213
  textColorInNonActiveTheme = _useColorAccessibilit.textColorInNonActiveTheme;
101
- var contrastRatio = getContrastRatio(defaultColor, highlightColor, textColor);
102
- var nonActiveThemeContrastRatio = null;
103
- try {
214
+ // The multi-color selection handling (walking every unique text/highlight
215
+ // pair across the active and non-active theme) is only performed when the
216
+ // patch gate is on. When the gate is off we fall back to the legacy behavior
217
+ // of only evaluating the single active text/highlight color in the current
218
+ // active theme
219
+ var isMultiColorSelection = Boolean(isMultiTextColor || isMultiHighlightColor) && fg('platform_editor_lovability_text_bg_color_patch_1');
220
+
221
+ // The complement of `isMultiColorSelection`: the single active text/highlight
222
+ // pair is evaluated whenever we are not walking a multi-color selection. This
223
+ // covers both a genuinely single-color selection (gate on) and every
224
+ // selection when the gate is off (legacy behavior).
225
+ var isSingleColorSelection = !isMultiColorSelection;
226
+
227
+ // Memoize the selection walk so we don't re-traverse the entire ProseMirror
228
+ // document on every render. It only needs to recompute when the multi-color
229
+ // selection state or the inputs to the walk change (the selection flags,
230
+ // default color, or the plugin api).
231
+ var multiColorStatus = React.useMemo(function () {
232
+ return isMultiColorSelection ? getWorstAccessibilityStatusFromSelection(api, defaultColor) : null;
233
+ },
234
+ // eslint-disable-next-line react-hooks/exhaustive-deps
235
+ [isMultiColorSelection, isMultiTextColor, isMultiHighlightColor, defaultColor, api]);
236
+
237
+ // The single active text/highlight pair path. Everything below only applies
238
+ // when we are not walking a multi-color selection
239
+ var singleColorStatus = null;
240
+ if (isSingleColorSelection) {
241
+ var singleColorContrastRatio = getContrastRatio(defaultColor, highlightColor, textColor);
242
+
243
+ // Also account for the non-active theme (gate on only) so the worst-case
244
+ // (least accessible) status across both themes is shown.
245
+ var nonActiveThemeSingleColorRatio = null;
104
246
  if (textColorInNonActiveTheme && highlightColorInNonActiveTheme && fg('platform_editor_lovability_text_bg_color_patch_1')) {
105
- nonActiveThemeContrastRatio = calcContrastRatio(textColorInNonActiveTheme, highlightColorInNonActiveTheme);
247
+ try {
248
+ nonActiveThemeSingleColorRatio = calcContrastRatio(textColorInNonActiveTheme, highlightColorInNonActiveTheme);
249
+ } catch (_unused3) {
250
+ // if we failed to calculate the contrast ratio, leave as null
251
+ nonActiveThemeSingleColorRatio = null;
252
+ }
106
253
  }
107
- } catch (_unused2) {
108
- // if we failed to calculate the contrast ratio, leave null
254
+ var mostCriticalContrastRatio = singleColorContrastRatio !== null && nonActiveThemeSingleColorRatio !== null ? Math.min(singleColorContrastRatio, nonActiveThemeSingleColorRatio) : singleColorContrastRatio;
255
+ singleColorStatus = mostCriticalContrastRatio !== null ? getAccessibilityStatus(mostCriticalContrastRatio) : null;
109
256
  }
110
- var mostCriticalContrastRatio = contrastRatio !== null && nonActiveThemeContrastRatio !== null && fg('platform_editor_lovability_text_bg_color_patch_1') ? Math.min(contrastRatio, nonActiveThemeContrastRatio) : contrastRatio;
111
- if (mostCriticalContrastRatio === null) {
257
+ var accessibilityStatus = isSingleColorSelection ? singleColorStatus : multiColorStatus;
258
+ if (accessibilityStatus === null) {
112
259
  return /*#__PURE__*/React.createElement(React.Fragment, null);
113
260
  }
114
- var accessibilityStatus = getAccessibilityStatus(mostCriticalContrastRatio);
115
261
  var tooltipContent = function tooltipContent(accessibilityStatus) {
116
262
  if (accessibilityStatus === 'accessible') {
117
263
  return formatMessage(messages.accessibleTooltip);
@@ -8,6 +8,11 @@ export type TextColorPluginState = {
8
8
  colorInNonActiveTheme?: string;
9
9
  defaultColor: string;
10
10
  disabled?: boolean;
11
+ /**
12
+ * True when the current selection spans more than one text color. Only
13
+ * populated behind the lovability text/bg color patch gate + experiment.
14
+ */
15
+ isMultiTextColor?: boolean;
11
16
  isPaletteOpen?: boolean;
12
17
  palette: Array<PaletteColor>;
13
18
  };
@@ -3,4 +3,16 @@ import type { EditorState, Transaction } from '@atlaskit/editor-prosemirror/stat
3
3
  * Use getDisabledStateNew instead and pass in `tr`
4
4
  */
5
5
  export declare const getActiveColor: (state: EditorState) => string | null;
6
+ /**
7
+ * Returns true only when the selection contains two or more *distinct* text
8
+ * colors.
9
+ *
10
+ * This is deliberately narrower than `getActiveColor() === null`: `getActiveColor`
11
+ * also returns `null` for a mixed selection where a single colored region sits
12
+ * alongside uncolored text (`marksWithColor.length === 1 && marks.length > 1`).
13
+ * That mixed case is not a multi-color selection and must not trigger the
14
+ * expensive worst-status selection walk, so we key the `isMultiTextColor` flag
15
+ * off this predicate instead.
16
+ */
17
+ export declare const isMultiTextColorSelection: (state: EditorState) => boolean;
6
18
  export declare const getActiveColorNew: (tr: Transaction) => string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-text-color",
3
- "version": "12.3.18",
3
+ "version": "12.3.19",
4
4
  "description": "Text color plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -33,21 +33,21 @@
33
33
  "@atlaskit/editor-plugin-toolbar": "^9.0.0",
34
34
  "@atlaskit/editor-prosemirror": "^8.0.0",
35
35
  "@atlaskit/editor-shared-styles": "^4.0.0",
36
- "@atlaskit/editor-toolbar": "^2.1.0",
36
+ "@atlaskit/editor-toolbar": "^2.2.0",
37
37
  "@atlaskit/editor-toolbar-model": "^1.1.0",
38
38
  "@atlaskit/heading": "^6.2.0",
39
39
  "@atlaskit/icon": "^37.0.0",
40
40
  "@atlaskit/icon-lab": "^7.3.0",
41
41
  "@atlaskit/platform-feature-flags": "^2.0.0",
42
42
  "@atlaskit/platform-feature-flags-react": "^1.1.0",
43
- "@atlaskit/primitives": "^20.5.0",
43
+ "@atlaskit/primitives": "^20.6.0",
44
44
  "@atlaskit/tmp-editor-statsig": "^124.1.0",
45
- "@atlaskit/tokens": "^15.5.0",
45
+ "@atlaskit/tokens": "^15.6.0",
46
46
  "@babel/runtime": "^7.0.0",
47
47
  "@emotion/react": "^11.7.1"
48
48
  },
49
49
  "peerDependencies": {
50
- "@atlaskit/editor-common": "^116.25.0",
50
+ "@atlaskit/editor-common": "^116.26.0",
51
51
  "react": "^18.2.0",
52
52
  "react-dom": "^18.2.0",
53
53
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"