@atlaskit/editor-toolbar 0.4.0 → 0.5.0

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/index.js +7 -0
  3. package/dist/cjs/ui/ColorPalette/Color.compiled.css +1 -1
  4. package/dist/cjs/ui/ColorPalette/Color.js +2 -2
  5. package/dist/cjs/ui/ColorPalette/index.js +239 -5
  6. package/dist/cjs/ui/ToolbarDropdownItem.js +9 -2
  7. package/dist/cjs/ui/ToolbarDropdownMenu.compiled.css +2 -1
  8. package/dist/cjs/ui/ToolbarDropdownMenu.js +4 -2
  9. package/dist/cjs/ui/icons/LoomIcon.js +13 -0
  10. package/dist/es2019/index.js +1 -0
  11. package/dist/es2019/ui/ColorPalette/Color.compiled.css +1 -1
  12. package/dist/es2019/ui/ColorPalette/Color.js +2 -2
  13. package/dist/es2019/ui/ColorPalette/index.js +240 -5
  14. package/dist/es2019/ui/ToolbarDropdownItem.js +23 -14
  15. package/dist/es2019/ui/ToolbarDropdownMenu.compiled.css +2 -1
  16. package/dist/es2019/ui/ToolbarDropdownMenu.js +4 -2
  17. package/dist/es2019/ui/icons/LoomIcon.js +2 -0
  18. package/dist/esm/index.js +1 -0
  19. package/dist/esm/ui/ColorPalette/Color.compiled.css +1 -1
  20. package/dist/esm/ui/ColorPalette/Color.js +2 -2
  21. package/dist/esm/ui/ColorPalette/index.js +242 -8
  22. package/dist/esm/ui/ToolbarDropdownItem.js +9 -2
  23. package/dist/esm/ui/ToolbarDropdownMenu.compiled.css +2 -1
  24. package/dist/esm/ui/ToolbarDropdownMenu.js +4 -2
  25. package/dist/esm/ui/icons/LoomIcon.js +2 -0
  26. package/dist/types/index.d.ts +1 -0
  27. package/dist/types/ui/ToolbarDropdownItem.d.ts +4 -1
  28. package/dist/types/ui/icons/LoomIcon.d.ts +1 -0
  29. package/dist/types-ts4.5/index.d.ts +1 -0
  30. package/dist/types-ts4.5/ui/ToolbarDropdownItem.d.ts +4 -1
  31. package/dist/types-ts4.5/ui/icons/LoomIcon.d.ts +1 -0
  32. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @atlaskit/editor-toolbar
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`bf3ab0c552ba7`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/bf3ab0c552ba7) -
8
+ [ux] ED-29000 Add keyboard navigation to colour palette and minor UI fixes
9
+
10
+ ### Patch Changes
11
+
12
+ - [`4ef462fecb522`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/4ef462fecb522) -
13
+ [ux] [ED-29003] Register loom component as a dropdown menu item in overflow menu
14
+ - Updated dependencies
15
+
3
16
  ## 0.4.0
4
17
 
5
18
  ### Minor Changes
package/dist/cjs/index.js CHANGED
@@ -226,6 +226,12 @@ Object.defineProperty(exports, "ListNumberedIcon", {
226
226
  return _ListNumberedIcon.ListNumberedIcon;
227
227
  }
228
228
  });
229
+ Object.defineProperty(exports, "LoomIcon", {
230
+ enumerable: true,
231
+ get: function get() {
232
+ return _LoomIcon.LoomIcon;
233
+ }
234
+ });
229
235
  Object.defineProperty(exports, "MentionIcon", {
230
236
  enumerable: true,
231
237
  get: function get() {
@@ -496,6 +502,7 @@ var _TaskIcon = require("./ui/icons/TaskIcon");
496
502
  var _UndoIcon = require("./ui/icons/UndoIcon");
497
503
  var _RedoIcon = require("./ui/icons/RedoIcon");
498
504
  var _HistoryIcon = require("./ui/icons/HistoryIcon");
505
+ var _LoomIcon = require("./ui/icons/LoomIcon");
499
506
  var _ColorPalette = _interopRequireDefault(require("./ui/ColorPalette"));
500
507
  var _utils = require("./ui/ColorPalette/utils");
501
508
  var _uiContext = require("./hooks/ui-context");
@@ -1,6 +1,6 @@
1
1
 
2
2
  ._19itcf40{border:var(--_1wr0y6w)}
3
- ._2rkoiti9{border-radius:var(--ds-border-radius-100,4px)}._189et94y{border-width:1px}
3
+ ._2rko12b0{border-radius:var(--ds-radius-small,4px)}._189et94y{border-width:1px}
4
4
  ._1dqonqa1{border-style:solid}
5
5
  ._1h6d1j28{border-color:transparent}
6
6
  ._19bvidpf{padding-left:0}
@@ -51,7 +51,7 @@ var Color = exports.Color = /*#__PURE__*/(0, _react.memo)(function (_ref) {
51
51
  return /*#__PURE__*/_react.default.createElement(_tooltip.default, {
52
52
  content: label
53
53
  }, /*#__PURE__*/_react.default.createElement("span", {
54
- className: (0, _runtime.ax)(["_2rkoiti9 _1h6d1j28 _1dqonqa1 _189et94y _1e0c1txw _4cvr1h6o _ca0qv77o _u5f3v77o _n3tdv77o _19bvv77o _858umuej _jyzfmuej _4cvxmuej"])
54
+ className: (0, _runtime.ax)(["_2rko12b0 _1h6d1j28 _1dqonqa1 _189et94y _1e0c1txw _4cvr1h6o _ca0qv77o _u5f3v77o _n3tdv77o _19bvv77o _858umuej _jyzfmuej _4cvxmuej"])
55
55
  }, /*#__PURE__*/_react.default.createElement("button", {
56
56
  type: "button",
57
57
  "aria-label": label,
@@ -62,7 +62,7 @@ var Color = exports.Color = /*#__PURE__*/(0, _react.memo)(function (_ref) {
62
62
  onMouseDown: handleMouseDown,
63
63
  tabIndex: tabIndex,
64
64
  autoFocus: autoFocus,
65
- className: (0, _runtime.ax)(["_ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _2rkoiti9 _19itcf40 _4t3icr4y _1bsbcr4y _bfhkm7j4 _80omtlke _1e0c1ule _kqswh2mm _y2mv12j9 _1bg4v77o"]),
65
+ className: (0, _runtime.ax)(["_ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _2rko12b0 _19itcf40 _4t3icr4y _1bsbcr4y _bfhkm7j4 _80omtlke _1e0c1ule _kqswh2mm _y2mv12j9 _1bg4v77o"]),
66
66
  style: {
67
67
  backgroundColor: colorStyle || "var(--ds-background-input, #FFFFFF)",
68
68
  border: "1px solid ".concat(borderColor),
@@ -13,6 +13,7 @@ var _react = _interopRequireWildcard(require("react"));
13
13
  var _chromatism = _interopRequireDefault(require("chromatism"));
14
14
  var _reactIntlNext = require("react-intl-next");
15
15
  var _compiled = require("@atlaskit/primitives/compiled");
16
+ var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
16
17
  var _tokens = require("@atlaskit/tokens");
17
18
  var _Color = require("./Color");
18
19
  var _getColorMessage = _interopRequireDefault(require("./getColorMessage"));
@@ -28,7 +29,7 @@ var styles = {
28
29
  *
29
30
  * @param color color string, supports HEX, RGB, RGBA etc.
30
31
  * @param useIconToken boolean, describes if a token should be used for the icon color
31
- * @return Highest contrast color in pool
32
+ * @returns Highest contrast color in pool
32
33
  */
33
34
  function getCheckMarkColor(color, useIconToken) {
34
35
  var tokenVal = (0, _utils.getTokenCSSVariableValue)(color);
@@ -75,15 +76,203 @@ var ColorPalette = function ColorPalette(_ref) {
75
76
  var _useThemeObserver = (0, _tokens.useThemeObserver)(),
76
77
  tokenTheme = _useThemeObserver.colorMode;
77
78
  var useIconToken = !!hexToPaletteColor;
79
+
80
+ // Refs for keyboard navigation
81
+ var paletteRef = (0, _react.useRef)(null);
82
+ var currentFocusRef = (0, _react.useRef)({
83
+ row: 0,
84
+ col: 0
85
+ });
78
86
  var colorsPerRow = (0, _react.useMemo)(function () {
79
87
  return (0, _utils.getColorsPerRowFromPalette)(palette, cols);
80
88
  }, [palette, cols]);
81
- return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, colorsPerRow.map(function (row) {
82
- return /*#__PURE__*/_react.default.createElement(_compiled.Box, {
83
- xcss: styles.paletteWrapper,
89
+
90
+ // Get initial focus position based on selected color
91
+ var _useMemo = (0, _react.useMemo)(function () {
92
+ return (0, _utils.getSelectedRowAndColumnFromPalette)(palette, selectedColor, cols);
93
+ }, [palette, selectedColor, cols]),
94
+ selectedRowIndex = _useMemo.selectedRowIndex,
95
+ selectedColumnIndex = _useMemo.selectedColumnIndex;
96
+
97
+ // Focus management utility
98
+ var focusColorAt = (0, _react.useCallback)(function (row, col) {
99
+ var _rowElement$children$, _rowElement$children$2;
100
+ if (!paletteRef.current) {
101
+ return;
102
+ }
103
+ var rowElement = paletteRef.current.children[row];
104
+ if (!(rowElement instanceof HTMLElement)) {
105
+ return;
106
+ }
107
+ var colorButtonCandidate = (_rowElement$children$ = rowElement.children[col]) === null || _rowElement$children$ === void 0 || (_rowElement$children$2 = _rowElement$children$.querySelector) === null || _rowElement$children$2 === void 0 ? void 0 : _rowElement$children$2.call(_rowElement$children$, 'button');
108
+ var colorButton = colorButtonCandidate instanceof HTMLButtonElement ? colorButtonCandidate : null;
109
+ if (colorButton) {
110
+ colorButton.focus();
111
+ currentFocusRef.current = {
112
+ row: row,
113
+ col: col
114
+ };
115
+ }
116
+ }, []);
117
+
118
+ // Initialize focus position and handle autofocus
119
+ (0, _react.useEffect)(function () {
120
+ if (!(0, _expValEquals.expValEquals)('platform_editor_toolbar_aifc_patch_1', 'isEnabled', true)) {
121
+ return;
122
+ }
123
+ if (selectedRowIndex >= 0 && selectedColumnIndex >= 0) {
124
+ currentFocusRef.current = {
125
+ row: selectedRowIndex,
126
+ col: selectedColumnIndex
127
+ };
128
+ } else {
129
+ currentFocusRef.current = {
130
+ row: 0,
131
+ col: 0
132
+ };
133
+ }
134
+ }, [selectedRowIndex, selectedColumnIndex]);
135
+
136
+ // Keyboard navigation handler
137
+ var handleKeyDown = (0, _react.useCallback)(function (value, label, event) {
138
+ var _colorsPerRow$row;
139
+ var _currentFocusRef$curr = currentFocusRef.current,
140
+ row = _currentFocusRef$curr.row,
141
+ col = _currentFocusRef$curr.col;
142
+ var maxRow = colorsPerRow.length - 1;
143
+ var maxCol = ((_colorsPerRow$row = colorsPerRow[row]) === null || _colorsPerRow$row === void 0 ? void 0 : _colorsPerRow$row.length) - 1 || 0;
144
+ switch (event.key) {
145
+ case 'ArrowRight':
146
+ {
147
+ event.preventDefault();
148
+ if (col < maxCol) {
149
+ focusColorAt(row, col + 1);
150
+ } else if (row < maxRow) {
151
+ // Move to first color of next row
152
+ focusColorAt(row + 1, 0);
153
+ } else {
154
+ // Wrap to first color of first row
155
+ focusColorAt(0, 0);
156
+ }
157
+ break;
158
+ }
159
+ case 'ArrowLeft':
160
+ {
161
+ event.preventDefault();
162
+ if (col > 0) {
163
+ focusColorAt(row, col - 1);
164
+ } else if (row > 0) {
165
+ var _colorsPerRow;
166
+ // Move to last color of previous row
167
+ var prevRowMaxCol = ((_colorsPerRow = colorsPerRow[row - 1]) === null || _colorsPerRow === void 0 ? void 0 : _colorsPerRow.length) - 1 || 0;
168
+ focusColorAt(row - 1, prevRowMaxCol);
169
+ } else {
170
+ var _colorsPerRow$maxRow;
171
+ // Wrap to last color of last row
172
+ var lastRowMaxCol = ((_colorsPerRow$maxRow = colorsPerRow[maxRow]) === null || _colorsPerRow$maxRow === void 0 ? void 0 : _colorsPerRow$maxRow.length) - 1 || 0;
173
+ focusColorAt(maxRow, lastRowMaxCol);
174
+ }
175
+ break;
176
+ }
177
+ case 'ArrowDown':
178
+ {
179
+ event.preventDefault();
180
+ if (row < maxRow) {
181
+ var _colorsPerRow2;
182
+ // Move to same column in next row, or last available column
183
+ var nextRowMaxCol = ((_colorsPerRow2 = colorsPerRow[row + 1]) === null || _colorsPerRow2 === void 0 ? void 0 : _colorsPerRow2.length) - 1 || 0;
184
+ var targetCol = Math.min(col, nextRowMaxCol);
185
+ focusColorAt(row + 1, targetCol);
186
+ } else {
187
+ var _colorsPerRow$;
188
+ // Wrap to same column in first row
189
+ var firstRowMaxCol = ((_colorsPerRow$ = colorsPerRow[0]) === null || _colorsPerRow$ === void 0 ? void 0 : _colorsPerRow$.length) - 1 || 0;
190
+ var _targetCol = Math.min(col, firstRowMaxCol);
191
+ focusColorAt(0, _targetCol);
192
+ }
193
+ break;
194
+ }
195
+ case 'ArrowUp':
196
+ {
197
+ event.preventDefault();
198
+ if (row > 0) {
199
+ var _colorsPerRow3;
200
+ // Move to same column in previous row, or last available column
201
+ var _prevRowMaxCol = ((_colorsPerRow3 = colorsPerRow[row - 1]) === null || _colorsPerRow3 === void 0 ? void 0 : _colorsPerRow3.length) - 1 || 0;
202
+ var _targetCol2 = Math.min(col, _prevRowMaxCol);
203
+ focusColorAt(row - 1, _targetCol2);
204
+ } else {
205
+ var _colorsPerRow$maxRow2;
206
+ // Wrap to same column in last row
207
+ var _lastRowMaxCol = ((_colorsPerRow$maxRow2 = colorsPerRow[maxRow]) === null || _colorsPerRow$maxRow2 === void 0 ? void 0 : _colorsPerRow$maxRow2.length) - 1 || 0;
208
+ var _targetCol3 = Math.min(col, _lastRowMaxCol);
209
+ focusColorAt(maxRow, _targetCol3);
210
+ }
211
+ break;
212
+ }
213
+ case 'Home':
214
+ {
215
+ event.preventDefault();
216
+ focusColorAt(row, 0);
217
+ break;
218
+ }
219
+ case 'End':
220
+ {
221
+ event.preventDefault();
222
+ focusColorAt(row, maxCol);
223
+ break;
224
+ }
225
+ case 'PageUp':
226
+ {
227
+ event.preventDefault();
228
+ focusColorAt(0, col);
229
+ break;
230
+ }
231
+ case 'PageDown':
232
+ {
233
+ var _colorsPerRow$maxRow3;
234
+ event.preventDefault();
235
+ var _lastRowMaxCol2 = ((_colorsPerRow$maxRow3 = colorsPerRow[maxRow]) === null || _colorsPerRow$maxRow3 === void 0 ? void 0 : _colorsPerRow$maxRow3.length) - 1 || 0;
236
+ var _targetCol4 = Math.min(col, _lastRowMaxCol2);
237
+ focusColorAt(maxRow, _targetCol4);
238
+ break;
239
+ }
240
+ case 'Tab':
241
+ {
242
+ // Allow Tab to move to next focusable element (don't prevent default)
243
+ // This will allow Tab to move between color palettes
244
+ if (onKeyDown) {
245
+ onKeyDown(value, label, event);
246
+ }
247
+ break;
248
+ }
249
+ case 'Enter':
250
+ case ' ':
251
+ {
252
+ event.preventDefault();
253
+ onClick(value, label);
254
+ break;
255
+ }
256
+ default:
257
+ {
258
+ // Pass through to custom onKeyDown handler if provided
259
+ if (onKeyDown) {
260
+ onKeyDown(value, label, event);
261
+ }
262
+ break;
263
+ }
264
+ }
265
+ }, [colorsPerRow, focusColorAt, onClick, onKeyDown]);
266
+ return (0, _expValEquals.expValEquals)('platform_editor_toolbar_aifc_patch_1', 'isEnabled', true) ? /*#__PURE__*/_react.default.createElement(_compiled.Grid, {
267
+ gap: "space.050",
268
+ ref: paletteRef,
269
+ role: "group"
270
+ }, colorsPerRow.map(function (row, rowIndex) {
271
+ return /*#__PURE__*/_react.default.createElement(_compiled.Inline, {
272
+ rowSpace: "space.050",
84
273
  key: "row-first-color-".concat(row[0].value),
85
274
  role: "radiogroup"
86
- }, row.map(function (_ref2) {
275
+ }, row.map(function (_ref2, colIndex) {
87
276
  var value = _ref2.value,
88
277
  label = _ref2.label,
89
278
  border = _ref2.border,
@@ -91,6 +280,51 @@ var ColorPalette = function ColorPalette(_ref) {
91
280
  decorator = _ref2.decorator;
92
281
  var tooltipMessage = message;
93
282
 
283
+ // Override with theme-specific tooltip messages if provided
284
+ if (paletteColorTooltipMessages) {
285
+ if (tokenTheme === 'dark') {
286
+ tooltipMessage = (0, _getColorMessage.default)(paletteColorTooltipMessages.dark, value.toUpperCase());
287
+ }
288
+ if (tokenTheme === 'light') {
289
+ tooltipMessage = (0, _getColorMessage.default)(paletteColorTooltipMessages.light, value.toUpperCase());
290
+ }
291
+ }
292
+
293
+ // Determine if this color should be focusable
294
+ var isSelectedColor = value === selectedColor;
295
+ var isFirstColor = rowIndex === 0 && colIndex === 0;
296
+
297
+ // Only the selected color or first color should be focusable via Tab
298
+ // This allows Tab to move between color palettes
299
+ var shouldBeFocusable = isSelectedColor || !selectedColor && isFirstColor;
300
+ return /*#__PURE__*/_react.default.createElement(_Color.Color, {
301
+ key: value,
302
+ value: value,
303
+ borderColor: border,
304
+ label: tooltipMessage ? formatMessage(tooltipMessage) : label,
305
+ onClick: onClick,
306
+ onKeyDown: handleKeyDown,
307
+ isSelected: isSelectedColor,
308
+ checkMarkColor: getCheckMarkColor(value, useIconToken),
309
+ hexToPaletteColor: hexToPaletteColor,
310
+ decorator: decorator,
311
+ tabIndex: shouldBeFocusable ? 0 : -1,
312
+ autoFocus: isSelectedColor && rowIndex === selectedRowIndex && colIndex === selectedColumnIndex
313
+ });
314
+ }));
315
+ })) : /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, colorsPerRow.map(function (row) {
316
+ return /*#__PURE__*/_react.default.createElement(_compiled.Box, {
317
+ xcss: styles.paletteWrapper,
318
+ key: "row-first-color-".concat(row[0].value),
319
+ role: "radiogroup"
320
+ }, row.map(function (_ref3) {
321
+ var value = _ref3.value,
322
+ label = _ref3.label,
323
+ border = _ref3.border,
324
+ message = _ref3.message,
325
+ decorator = _ref3.decorator;
326
+ var tooltipMessage = message;
327
+
94
328
  // Override with theme-specific tooltip messages if provided
95
329
  if (paletteColorTooltipMessages) {
96
330
  if (tokenTheme === 'dark') {
@@ -12,6 +12,7 @@ var _react = _interopRequireWildcard(require("react"));
12
12
  var _css = require("@atlaskit/css");
13
13
  var _dropdownMenu = require("@atlaskit/dropdown-menu");
14
14
  var _compiled = require("@atlaskit/primitives/compiled");
15
+ var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
15
16
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
16
17
  var styles = {
17
18
  toolbarDropdownItem: "_18zrpxbi _1rjcu2gc _1e0c1txw _kqswh2mm _bfhksm61 _1bsb1osq _1tke14no _1ul9163w _1basglpi _1ah31i6y",
@@ -51,7 +52,10 @@ var ToolbarDropdownItem = exports.ToolbarDropdownItem = function ToolbarDropdown
51
52
  hasNestedDropdownMenu = _ref2.hasNestedDropdownMenu,
52
53
  triggerRef = _ref2.triggerRef,
53
54
  testId = _ref2.testId,
54
- ariaKeyshortcuts = _ref2.ariaKeyshortcuts;
55
+ ariaKeyshortcuts = _ref2.ariaKeyshortcuts,
56
+ href = _ref2.href,
57
+ target = _ref2.target,
58
+ rel = _ref2.rel;
55
59
  return /*#__PURE__*/_react.default.createElement(_dropdownMenu.DropdownItem, {
56
60
  onClick: onClick,
57
61
  elemBefore: elemBefore,
@@ -62,7 +66,10 @@ var ToolbarDropdownItem = exports.ToolbarDropdownItem = function ToolbarDropdown
62
66
  "aria-pressed": isSelected,
63
67
  "aria-keyshortcuts": ariaKeyshortcuts,
64
68
  ref: triggerRef,
65
- component: CustomDropdownMenuItemButton,
69
+ href: href,
70
+ target: target,
71
+ rel: rel,
72
+ component: href && (0, _expValEquals.expValEquals)('platform_editor_toolbar_migrate_loom', 'isEnabled', true) ? undefined : CustomDropdownMenuItemButton,
66
73
  testId: testId
67
74
  }, children);
68
75
  };
@@ -1 +1,2 @@
1
- ._1mou1b66{margin-block:var(--ds-space-050,4px)}
1
+
2
+ ._1mou1b66{margin-block:var(--ds-space-050,4px)}._18l8n7od [data-section]:first-of-type{border-block-start:unset}
@@ -11,12 +11,14 @@ require("./ToolbarDropdownMenu.compiled.css");
11
11
  var _runtime = require("@compiled/react/runtime");
12
12
  var _react = _interopRequireWildcard(require("react"));
13
13
  var _dropdownMenu = _interopRequireDefault(require("@atlaskit/dropdown-menu"));
14
+ var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
14
15
  var _uiContext = require("../hooks/ui-context");
15
16
  var _ToolbarButton = require("./ToolbarButton");
16
17
  var _ToolbarDropdownMenuContext = require("./ToolbarDropdownMenuContext");
17
18
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
18
19
  var styles = {
19
- sectionMargin: "_1mou1b66"
20
+ sectionMargin: "_1mou1b66",
21
+ firstSectionSeparator: "_18l8n7od"
20
22
  };
21
23
  var ToolbarDropdownMenuContent = function ToolbarDropdownMenuContent(_ref) {
22
24
  var iconBefore = _ref.iconBefore,
@@ -81,6 +83,6 @@ var ToolbarDropdownMenu = exports.ToolbarDropdownMenu = function ToolbarDropdown
81
83
  testId: testId,
82
84
  label: label
83
85
  }, /*#__PURE__*/_react.default.createElement("div", {
84
- className: (0, _runtime.ax)([hasSectionMargin && styles.sectionMargin])
86
+ className: (0, _runtime.ax)([hasSectionMargin && styles.sectionMargin, (0, _expValEquals.expValEquals)('platform_editor_toolbar_migrate_loom', 'isEnabled', true) && styles.firstSectionSeparator])
85
87
  }, children)));
86
88
  };
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ Object.defineProperty(exports, "LoomIcon", {
8
+ enumerable: true,
9
+ get: function get() {
10
+ return _video.default;
11
+ }
12
+ });
13
+ var _video = _interopRequireDefault(require("@atlaskit/icon/core/video"));
@@ -65,6 +65,7 @@ export { TaskIcon } from './ui/icons/TaskIcon';
65
65
  export { UndoIcon } from './ui/icons/UndoIcon';
66
66
  export { RedoIcon } from './ui/icons/RedoIcon';
67
67
  export { HistoryIcon } from './ui/icons/HistoryIcon';
68
+ export { LoomIcon } from './ui/icons/LoomIcon';
68
69
  export { default as ColorPalette } from './ui/ColorPalette';
69
70
  export { getContrastingBackgroundColor } from './ui/ColorPalette/utils';
70
71
  export { useToolbarUI, ToolbarUIProvider } from './hooks/ui-context';
@@ -1,6 +1,6 @@
1
1
 
2
2
  ._19itv4vr{border:1px solid var(--ds-border-inverse,#fff)}
3
- ._2rkoiti9{border-radius:var(--ds-border-radius-100,4px)}._189et94y{border-width:1px}
3
+ ._2rko12b0{border-radius:var(--ds-radius-small,4px)}._189et94y{border-width:1px}
4
4
  ._1dqonqa1{border-style:solid}
5
5
  ._1h6d1j28{border-color:transparent}
6
6
  ._19bvidpf{padding-left:0}
@@ -42,7 +42,7 @@ export const Color = /*#__PURE__*/memo(({
42
42
  return /*#__PURE__*/React.createElement(Tooltip, {
43
43
  content: label
44
44
  }, /*#__PURE__*/React.createElement("span", {
45
- className: ax(["_2rkoiti9 _1h6d1j28 _1dqonqa1 _189et94y _1e0c1txw _4cvr1h6o _ca0qv77o _u5f3v77o _n3tdv77o _19bvv77o _858umuej _jyzfmuej _4cvxmuej"])
45
+ className: ax(["_2rko12b0 _1h6d1j28 _1dqonqa1 _189et94y _1e0c1txw _4cvr1h6o _ca0qv77o _u5f3v77o _n3tdv77o _19bvv77o _858umuej _jyzfmuej _4cvxmuej"])
46
46
  }, /*#__PURE__*/React.createElement("button", {
47
47
  type: "button",
48
48
  "aria-label": label,
@@ -57,7 +57,7 @@ export const Color = /*#__PURE__*/memo(({
57
57
  border: `1px solid ${borderColor}`
58
58
  },
59
59
  autoFocus: autoFocus,
60
- className: ax(["_ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _2rkoiti9 _19itv4vr _4t3icr4y _1bsbcr4y _bfhkm7j4 _80omtlke _1e0c1ule _kqswh2mm _y2mvugt7 _1bg4v77o"])
60
+ className: ax(["_ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _2rko12b0 _19itv4vr _4t3icr4y _1bsbcr4y _bfhkm7j4 _80omtlke _1e0c1ule _kqswh2mm _y2mvugt7 _1bg4v77o"])
61
61
  }, !decorator && isSelected && /*#__PURE__*/React.createElement(EditorDoneIcon, {
62
62
  color: checkMarkColor,
63
63
  LEGACY_primaryColor: checkMarkColor,
@@ -1,14 +1,15 @@
1
1
  /* index.tsx generated by @compiled/babel-plugin v0.36.1 */
2
2
  import "./index.compiled.css";
3
3
  import { ax, ix } from "@compiled/react/runtime";
4
- import React, { useMemo } from 'react';
4
+ import React, { useMemo, useCallback, useRef, useEffect } from 'react';
5
5
  import chromatism from 'chromatism';
6
6
  import { useIntl } from 'react-intl-next';
7
- import { Box } from '@atlaskit/primitives/compiled';
7
+ import { Box, Grid, Inline } from '@atlaskit/primitives/compiled';
8
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
8
9
  import { useThemeObserver } from '@atlaskit/tokens';
9
10
  import { Color } from './Color';
10
11
  import getColorMessage from './getColorMessage';
11
- import { DEFAULT_COLOR_PICKER_COLUMNS, getColorsPerRowFromPalette, getTokenCSSVariableValue } from './utils';
12
+ import { DEFAULT_COLOR_PICKER_COLUMNS, getColorsPerRowFromPalette, getSelectedRowAndColumnFromPalette, getTokenCSSVariableValue } from './utils';
12
13
  const styles = {
13
14
  paletteWrapper: "_1e0c1txw"
14
15
  };
@@ -19,7 +20,7 @@ const styles = {
19
20
  *
20
21
  * @param color color string, supports HEX, RGB, RGBA etc.
21
22
  * @param useIconToken boolean, describes if a token should be used for the icon color
22
- * @return Highest contrast color in pool
23
+ * @returns Highest contrast color in pool
23
24
  */
24
25
  function getCheckMarkColor(color, useIconToken) {
25
26
  const tokenVal = getTokenCSSVariableValue(color);
@@ -68,10 +69,244 @@ const ColorPalette = ({
68
69
  colorMode: tokenTheme
69
70
  } = useThemeObserver();
70
71
  const useIconToken = !!hexToPaletteColor;
72
+
73
+ // Refs for keyboard navigation
74
+ const paletteRef = useRef(null);
75
+ const currentFocusRef = useRef({
76
+ row: 0,
77
+ col: 0
78
+ });
71
79
  const colorsPerRow = useMemo(() => {
72
80
  return getColorsPerRowFromPalette(palette, cols);
73
81
  }, [palette, cols]);
74
- return /*#__PURE__*/React.createElement(React.Fragment, null, colorsPerRow.map(row => /*#__PURE__*/React.createElement(Box, {
82
+
83
+ // Get initial focus position based on selected color
84
+ const {
85
+ selectedRowIndex,
86
+ selectedColumnIndex
87
+ } = useMemo(() => {
88
+ return getSelectedRowAndColumnFromPalette(palette, selectedColor, cols);
89
+ }, [palette, selectedColor, cols]);
90
+
91
+ // Focus management utility
92
+ const focusColorAt = useCallback((row, col) => {
93
+ var _rowElement$children$, _rowElement$children$2;
94
+ if (!paletteRef.current) {
95
+ return;
96
+ }
97
+ const rowElement = paletteRef.current.children[row];
98
+ if (!(rowElement instanceof HTMLElement)) {
99
+ return;
100
+ }
101
+ const colorButtonCandidate = (_rowElement$children$ = rowElement.children[col]) === null || _rowElement$children$ === void 0 ? void 0 : (_rowElement$children$2 = _rowElement$children$.querySelector) === null || _rowElement$children$2 === void 0 ? void 0 : _rowElement$children$2.call(_rowElement$children$, 'button');
102
+ const colorButton = colorButtonCandidate instanceof HTMLButtonElement ? colorButtonCandidate : null;
103
+ if (colorButton) {
104
+ colorButton.focus();
105
+ currentFocusRef.current = {
106
+ row,
107
+ col
108
+ };
109
+ }
110
+ }, []);
111
+
112
+ // Initialize focus position and handle autofocus
113
+ useEffect(() => {
114
+ if (!expValEquals('platform_editor_toolbar_aifc_patch_1', 'isEnabled', true)) {
115
+ return;
116
+ }
117
+ if (selectedRowIndex >= 0 && selectedColumnIndex >= 0) {
118
+ currentFocusRef.current = {
119
+ row: selectedRowIndex,
120
+ col: selectedColumnIndex
121
+ };
122
+ } else {
123
+ currentFocusRef.current = {
124
+ row: 0,
125
+ col: 0
126
+ };
127
+ }
128
+ }, [selectedRowIndex, selectedColumnIndex]);
129
+
130
+ // Keyboard navigation handler
131
+ const handleKeyDown = useCallback((value, label, event) => {
132
+ var _colorsPerRow$row;
133
+ const {
134
+ row,
135
+ col
136
+ } = currentFocusRef.current;
137
+ const maxRow = colorsPerRow.length - 1;
138
+ const maxCol = ((_colorsPerRow$row = colorsPerRow[row]) === null || _colorsPerRow$row === void 0 ? void 0 : _colorsPerRow$row.length) - 1 || 0;
139
+ switch (event.key) {
140
+ case 'ArrowRight':
141
+ {
142
+ event.preventDefault();
143
+ if (col < maxCol) {
144
+ focusColorAt(row, col + 1);
145
+ } else if (row < maxRow) {
146
+ // Move to first color of next row
147
+ focusColorAt(row + 1, 0);
148
+ } else {
149
+ // Wrap to first color of first row
150
+ focusColorAt(0, 0);
151
+ }
152
+ break;
153
+ }
154
+ case 'ArrowLeft':
155
+ {
156
+ event.preventDefault();
157
+ if (col > 0) {
158
+ focusColorAt(row, col - 1);
159
+ } else if (row > 0) {
160
+ var _colorsPerRow;
161
+ // Move to last color of previous row
162
+ const prevRowMaxCol = ((_colorsPerRow = colorsPerRow[row - 1]) === null || _colorsPerRow === void 0 ? void 0 : _colorsPerRow.length) - 1 || 0;
163
+ focusColorAt(row - 1, prevRowMaxCol);
164
+ } else {
165
+ var _colorsPerRow$maxRow;
166
+ // Wrap to last color of last row
167
+ const lastRowMaxCol = ((_colorsPerRow$maxRow = colorsPerRow[maxRow]) === null || _colorsPerRow$maxRow === void 0 ? void 0 : _colorsPerRow$maxRow.length) - 1 || 0;
168
+ focusColorAt(maxRow, lastRowMaxCol);
169
+ }
170
+ break;
171
+ }
172
+ case 'ArrowDown':
173
+ {
174
+ event.preventDefault();
175
+ if (row < maxRow) {
176
+ var _colorsPerRow2;
177
+ // Move to same column in next row, or last available column
178
+ const nextRowMaxCol = ((_colorsPerRow2 = colorsPerRow[row + 1]) === null || _colorsPerRow2 === void 0 ? void 0 : _colorsPerRow2.length) - 1 || 0;
179
+ const targetCol = Math.min(col, nextRowMaxCol);
180
+ focusColorAt(row + 1, targetCol);
181
+ } else {
182
+ var _colorsPerRow$;
183
+ // Wrap to same column in first row
184
+ const firstRowMaxCol = ((_colorsPerRow$ = colorsPerRow[0]) === null || _colorsPerRow$ === void 0 ? void 0 : _colorsPerRow$.length) - 1 || 0;
185
+ const targetCol = Math.min(col, firstRowMaxCol);
186
+ focusColorAt(0, targetCol);
187
+ }
188
+ break;
189
+ }
190
+ case 'ArrowUp':
191
+ {
192
+ event.preventDefault();
193
+ if (row > 0) {
194
+ var _colorsPerRow3;
195
+ // Move to same column in previous row, or last available column
196
+ const prevRowMaxCol = ((_colorsPerRow3 = colorsPerRow[row - 1]) === null || _colorsPerRow3 === void 0 ? void 0 : _colorsPerRow3.length) - 1 || 0;
197
+ const targetCol = Math.min(col, prevRowMaxCol);
198
+ focusColorAt(row - 1, targetCol);
199
+ } else {
200
+ var _colorsPerRow$maxRow2;
201
+ // Wrap to same column in last row
202
+ const lastRowMaxCol = ((_colorsPerRow$maxRow2 = colorsPerRow[maxRow]) === null || _colorsPerRow$maxRow2 === void 0 ? void 0 : _colorsPerRow$maxRow2.length) - 1 || 0;
203
+ const targetCol = Math.min(col, lastRowMaxCol);
204
+ focusColorAt(maxRow, targetCol);
205
+ }
206
+ break;
207
+ }
208
+ case 'Home':
209
+ {
210
+ event.preventDefault();
211
+ focusColorAt(row, 0);
212
+ break;
213
+ }
214
+ case 'End':
215
+ {
216
+ event.preventDefault();
217
+ focusColorAt(row, maxCol);
218
+ break;
219
+ }
220
+ case 'PageUp':
221
+ {
222
+ event.preventDefault();
223
+ focusColorAt(0, col);
224
+ break;
225
+ }
226
+ case 'PageDown':
227
+ {
228
+ var _colorsPerRow$maxRow3;
229
+ event.preventDefault();
230
+ const lastRowMaxCol = ((_colorsPerRow$maxRow3 = colorsPerRow[maxRow]) === null || _colorsPerRow$maxRow3 === void 0 ? void 0 : _colorsPerRow$maxRow3.length) - 1 || 0;
231
+ const targetCol = Math.min(col, lastRowMaxCol);
232
+ focusColorAt(maxRow, targetCol);
233
+ break;
234
+ }
235
+ case 'Tab':
236
+ {
237
+ // Allow Tab to move to next focusable element (don't prevent default)
238
+ // This will allow Tab to move between color palettes
239
+ if (onKeyDown) {
240
+ onKeyDown(value, label, event);
241
+ }
242
+ break;
243
+ }
244
+ case 'Enter':
245
+ case ' ':
246
+ {
247
+ event.preventDefault();
248
+ onClick(value, label);
249
+ break;
250
+ }
251
+ default:
252
+ {
253
+ // Pass through to custom onKeyDown handler if provided
254
+ if (onKeyDown) {
255
+ onKeyDown(value, label, event);
256
+ }
257
+ break;
258
+ }
259
+ }
260
+ }, [colorsPerRow, focusColorAt, onClick, onKeyDown]);
261
+ return expValEquals('platform_editor_toolbar_aifc_patch_1', 'isEnabled', true) ? /*#__PURE__*/React.createElement(Grid, {
262
+ gap: "space.050",
263
+ ref: paletteRef,
264
+ role: "group"
265
+ }, colorsPerRow.map((row, rowIndex) => /*#__PURE__*/React.createElement(Inline, {
266
+ rowSpace: "space.050",
267
+ key: `row-first-color-${row[0].value}`,
268
+ role: "radiogroup"
269
+ }, row.map(({
270
+ value,
271
+ label,
272
+ border,
273
+ message,
274
+ decorator
275
+ }, colIndex) => {
276
+ let tooltipMessage = message;
277
+
278
+ // Override with theme-specific tooltip messages if provided
279
+ if (paletteColorTooltipMessages) {
280
+ if (tokenTheme === 'dark') {
281
+ tooltipMessage = getColorMessage(paletteColorTooltipMessages.dark, value.toUpperCase());
282
+ }
283
+ if (tokenTheme === 'light') {
284
+ tooltipMessage = getColorMessage(paletteColorTooltipMessages.light, value.toUpperCase());
285
+ }
286
+ }
287
+
288
+ // Determine if this color should be focusable
289
+ const isSelectedColor = value === selectedColor;
290
+ const isFirstColor = rowIndex === 0 && colIndex === 0;
291
+
292
+ // Only the selected color or first color should be focusable via Tab
293
+ // This allows Tab to move between color palettes
294
+ const shouldBeFocusable = isSelectedColor || !selectedColor && isFirstColor;
295
+ return /*#__PURE__*/React.createElement(Color, {
296
+ key: value,
297
+ value: value,
298
+ borderColor: border,
299
+ label: tooltipMessage ? formatMessage(tooltipMessage) : label,
300
+ onClick: onClick,
301
+ onKeyDown: handleKeyDown,
302
+ isSelected: isSelectedColor,
303
+ checkMarkColor: getCheckMarkColor(value, useIconToken),
304
+ hexToPaletteColor: hexToPaletteColor,
305
+ decorator: decorator,
306
+ tabIndex: shouldBeFocusable ? 0 : -1,
307
+ autoFocus: isSelectedColor && rowIndex === selectedRowIndex && colIndex === selectedColumnIndex
308
+ });
309
+ })))) : /*#__PURE__*/React.createElement(React.Fragment, null, colorsPerRow.map(row => /*#__PURE__*/React.createElement(Box, {
75
310
  xcss: styles.paletteWrapper,
76
311
  key: `row-first-color-${row[0].value}`,
77
312
  role: "radiogroup"
@@ -5,6 +5,7 @@ import React, { forwardRef } from 'react';
5
5
  import { cx } from '@atlaskit/css';
6
6
  import { DropdownItem } from '@atlaskit/dropdown-menu';
7
7
  import { Pressable } from '@atlaskit/primitives/compiled';
8
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
8
9
  const styles = {
9
10
  toolbarDropdownItem: "_18zrpxbi _1rjcu2gc _1e0c1txw _kqswh2mm _bfhksm61 _1bsb1osq _1tke14no _1ul9163w _1basglpi _1ah31i6y",
10
11
  enabled: "_irr3166n _1di61dty",
@@ -42,17 +43,25 @@ export const ToolbarDropdownItem = ({
42
43
  hasNestedDropdownMenu,
43
44
  triggerRef,
44
45
  testId,
45
- ariaKeyshortcuts
46
- }) => /*#__PURE__*/React.createElement(DropdownItem, {
47
- onClick: onClick,
48
- elemBefore: elemBefore,
49
- elemAfter: elemAfter,
50
- isSelected: isSelected,
51
- isDisabled: isDisabled,
52
- "aria-haspopup": hasNestedDropdownMenu,
53
- "aria-pressed": isSelected,
54
- "aria-keyshortcuts": ariaKeyshortcuts,
55
- ref: triggerRef,
56
- component: CustomDropdownMenuItemButton,
57
- testId: testId
58
- }, children);
46
+ ariaKeyshortcuts,
47
+ href,
48
+ target,
49
+ rel
50
+ }) => {
51
+ return /*#__PURE__*/React.createElement(DropdownItem, {
52
+ onClick: onClick,
53
+ elemBefore: elemBefore,
54
+ elemAfter: elemAfter,
55
+ isSelected: isSelected,
56
+ isDisabled: isDisabled,
57
+ "aria-haspopup": hasNestedDropdownMenu,
58
+ "aria-pressed": isSelected,
59
+ "aria-keyshortcuts": ariaKeyshortcuts,
60
+ ref: triggerRef,
61
+ href: href,
62
+ target: target,
63
+ rel: rel,
64
+ component: href && expValEquals('platform_editor_toolbar_migrate_loom', 'isEnabled', true) ? undefined : CustomDropdownMenuItemButton,
65
+ testId: testId
66
+ }, children);
67
+ };
@@ -1 +1,2 @@
1
- ._1mou1b66{margin-block:var(--ds-space-050,4px)}
1
+
2
+ ._1mou1b66{margin-block:var(--ds-space-050,4px)}._18l8n7od [data-section]:first-of-type{border-block-start:unset}
@@ -3,11 +3,13 @@ import "./ToolbarDropdownMenu.compiled.css";
3
3
  import { ax, ix } from "@compiled/react/runtime";
4
4
  import React, { useCallback } from 'react';
5
5
  import DropdownMenu from '@atlaskit/dropdown-menu';
6
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
6
7
  import { useToolbarUI } from '../hooks/ui-context';
7
8
  import { ToolbarButton } from './ToolbarButton';
8
9
  import { ToolbarDropdownMenuProvider, useToolbarDropdownMenu } from './ToolbarDropdownMenuContext';
9
10
  const styles = {
10
- sectionMargin: "_1mou1b66"
11
+ sectionMargin: "_1mou1b66",
12
+ firstSectionSeparator: "_18l8n7od"
11
13
  };
12
14
  const ToolbarDropdownMenuContent = ({
13
15
  iconBefore,
@@ -73,6 +75,6 @@ export const ToolbarDropdownMenu = ({
73
75
  testId: testId,
74
76
  label: label
75
77
  }, /*#__PURE__*/React.createElement("div", {
76
- className: ax([hasSectionMargin && styles.sectionMargin])
78
+ className: ax([hasSectionMargin && styles.sectionMargin, expValEquals('platform_editor_toolbar_migrate_loom', 'isEnabled', true) && styles.firstSectionSeparator])
77
79
  }, children)));
78
80
  };
@@ -0,0 +1,2 @@
1
+ /* eslint-disable @atlaskit/editor/no-re-export */
2
+ export { default as LoomIcon } from '@atlaskit/icon/core/video';
package/dist/esm/index.js CHANGED
@@ -65,6 +65,7 @@ export { TaskIcon } from './ui/icons/TaskIcon';
65
65
  export { UndoIcon } from './ui/icons/UndoIcon';
66
66
  export { RedoIcon } from './ui/icons/RedoIcon';
67
67
  export { HistoryIcon } from './ui/icons/HistoryIcon';
68
+ export { LoomIcon } from './ui/icons/LoomIcon';
68
69
  export { default as ColorPalette } from './ui/ColorPalette';
69
70
  export { getContrastingBackgroundColor } from './ui/ColorPalette/utils';
70
71
  export { useToolbarUI, ToolbarUIProvider } from './hooks/ui-context';
@@ -1,6 +1,6 @@
1
1
 
2
2
  ._19itcf40{border:var(--_1wr0y6w)}
3
- ._2rkoiti9{border-radius:var(--ds-border-radius-100,4px)}._189et94y{border-width:1px}
3
+ ._2rko12b0{border-radius:var(--ds-radius-small,4px)}._189et94y{border-width:1px}
4
4
  ._1dqonqa1{border-style:solid}
5
5
  ._1h6d1j28{border-color:transparent}
6
6
  ._19bvidpf{padding-left:0}
@@ -42,7 +42,7 @@ export var Color = /*#__PURE__*/memo(function (_ref) {
42
42
  return /*#__PURE__*/React.createElement(Tooltip, {
43
43
  content: label
44
44
  }, /*#__PURE__*/React.createElement("span", {
45
- className: ax(["_2rkoiti9 _1h6d1j28 _1dqonqa1 _189et94y _1e0c1txw _4cvr1h6o _ca0qv77o _u5f3v77o _n3tdv77o _19bvv77o _858umuej _jyzfmuej _4cvxmuej"])
45
+ className: ax(["_2rko12b0 _1h6d1j28 _1dqonqa1 _189et94y _1e0c1txw _4cvr1h6o _ca0qv77o _u5f3v77o _n3tdv77o _19bvv77o _858umuej _jyzfmuej _4cvxmuej"])
46
46
  }, /*#__PURE__*/React.createElement("button", {
47
47
  type: "button",
48
48
  "aria-label": label,
@@ -53,7 +53,7 @@ export var Color = /*#__PURE__*/memo(function (_ref) {
53
53
  onMouseDown: handleMouseDown,
54
54
  tabIndex: tabIndex,
55
55
  autoFocus: autoFocus,
56
- className: ax(["_ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _2rkoiti9 _19itcf40 _4t3icr4y _1bsbcr4y _bfhkm7j4 _80omtlke _1e0c1ule _kqswh2mm _y2mv12j9 _1bg4v77o"]),
56
+ className: ax(["_ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _2rko12b0 _19itcf40 _4t3icr4y _1bsbcr4y _bfhkm7j4 _80omtlke _1e0c1ule _kqswh2mm _y2mv12j9 _1bg4v77o"]),
57
57
  style: {
58
58
  backgroundColor: colorStyle || "var(--ds-background-input, #FFFFFF)",
59
59
  border: "1px solid ".concat(borderColor),
@@ -1,14 +1,15 @@
1
1
  /* index.tsx generated by @compiled/babel-plugin v0.36.1 */
2
2
  import "./index.compiled.css";
3
3
  import { ax, ix } from "@compiled/react/runtime";
4
- import React, { useMemo } from 'react';
4
+ import React, { useMemo, useCallback, useRef, useEffect } from 'react';
5
5
  import chromatism from 'chromatism';
6
6
  import { useIntl } from 'react-intl-next';
7
- import { Box } from '@atlaskit/primitives/compiled';
7
+ import { Box, Grid, Inline } from '@atlaskit/primitives/compiled';
8
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
8
9
  import { useThemeObserver } from '@atlaskit/tokens';
9
10
  import { Color } from './Color';
10
11
  import getColorMessage from './getColorMessage';
11
- import { DEFAULT_COLOR_PICKER_COLUMNS, getColorsPerRowFromPalette, getTokenCSSVariableValue } from './utils';
12
+ import { DEFAULT_COLOR_PICKER_COLUMNS, getColorsPerRowFromPalette, getSelectedRowAndColumnFromPalette, getTokenCSSVariableValue } from './utils';
12
13
  var styles = {
13
14
  paletteWrapper: "_1e0c1txw"
14
15
  };
@@ -19,7 +20,7 @@ var styles = {
19
20
  *
20
21
  * @param color color string, supports HEX, RGB, RGBA etc.
21
22
  * @param useIconToken boolean, describes if a token should be used for the icon color
22
- * @return Highest contrast color in pool
23
+ * @returns Highest contrast color in pool
23
24
  */
24
25
  function getCheckMarkColor(color, useIconToken) {
25
26
  var tokenVal = getTokenCSSVariableValue(color);
@@ -66,15 +67,203 @@ var ColorPalette = function ColorPalette(_ref) {
66
67
  var _useThemeObserver = useThemeObserver(),
67
68
  tokenTheme = _useThemeObserver.colorMode;
68
69
  var useIconToken = !!hexToPaletteColor;
70
+
71
+ // Refs for keyboard navigation
72
+ var paletteRef = useRef(null);
73
+ var currentFocusRef = useRef({
74
+ row: 0,
75
+ col: 0
76
+ });
69
77
  var colorsPerRow = useMemo(function () {
70
78
  return getColorsPerRowFromPalette(palette, cols);
71
79
  }, [palette, cols]);
72
- return /*#__PURE__*/React.createElement(React.Fragment, null, colorsPerRow.map(function (row) {
73
- return /*#__PURE__*/React.createElement(Box, {
74
- xcss: styles.paletteWrapper,
80
+
81
+ // Get initial focus position based on selected color
82
+ var _useMemo = useMemo(function () {
83
+ return getSelectedRowAndColumnFromPalette(palette, selectedColor, cols);
84
+ }, [palette, selectedColor, cols]),
85
+ selectedRowIndex = _useMemo.selectedRowIndex,
86
+ selectedColumnIndex = _useMemo.selectedColumnIndex;
87
+
88
+ // Focus management utility
89
+ var focusColorAt = useCallback(function (row, col) {
90
+ var _rowElement$children$, _rowElement$children$2;
91
+ if (!paletteRef.current) {
92
+ return;
93
+ }
94
+ var rowElement = paletteRef.current.children[row];
95
+ if (!(rowElement instanceof HTMLElement)) {
96
+ return;
97
+ }
98
+ var colorButtonCandidate = (_rowElement$children$ = rowElement.children[col]) === null || _rowElement$children$ === void 0 || (_rowElement$children$2 = _rowElement$children$.querySelector) === null || _rowElement$children$2 === void 0 ? void 0 : _rowElement$children$2.call(_rowElement$children$, 'button');
99
+ var colorButton = colorButtonCandidate instanceof HTMLButtonElement ? colorButtonCandidate : null;
100
+ if (colorButton) {
101
+ colorButton.focus();
102
+ currentFocusRef.current = {
103
+ row: row,
104
+ col: col
105
+ };
106
+ }
107
+ }, []);
108
+
109
+ // Initialize focus position and handle autofocus
110
+ useEffect(function () {
111
+ if (!expValEquals('platform_editor_toolbar_aifc_patch_1', 'isEnabled', true)) {
112
+ return;
113
+ }
114
+ if (selectedRowIndex >= 0 && selectedColumnIndex >= 0) {
115
+ currentFocusRef.current = {
116
+ row: selectedRowIndex,
117
+ col: selectedColumnIndex
118
+ };
119
+ } else {
120
+ currentFocusRef.current = {
121
+ row: 0,
122
+ col: 0
123
+ };
124
+ }
125
+ }, [selectedRowIndex, selectedColumnIndex]);
126
+
127
+ // Keyboard navigation handler
128
+ var handleKeyDown = useCallback(function (value, label, event) {
129
+ var _colorsPerRow$row;
130
+ var _currentFocusRef$curr = currentFocusRef.current,
131
+ row = _currentFocusRef$curr.row,
132
+ col = _currentFocusRef$curr.col;
133
+ var maxRow = colorsPerRow.length - 1;
134
+ var maxCol = ((_colorsPerRow$row = colorsPerRow[row]) === null || _colorsPerRow$row === void 0 ? void 0 : _colorsPerRow$row.length) - 1 || 0;
135
+ switch (event.key) {
136
+ case 'ArrowRight':
137
+ {
138
+ event.preventDefault();
139
+ if (col < maxCol) {
140
+ focusColorAt(row, col + 1);
141
+ } else if (row < maxRow) {
142
+ // Move to first color of next row
143
+ focusColorAt(row + 1, 0);
144
+ } else {
145
+ // Wrap to first color of first row
146
+ focusColorAt(0, 0);
147
+ }
148
+ break;
149
+ }
150
+ case 'ArrowLeft':
151
+ {
152
+ event.preventDefault();
153
+ if (col > 0) {
154
+ focusColorAt(row, col - 1);
155
+ } else if (row > 0) {
156
+ var _colorsPerRow;
157
+ // Move to last color of previous row
158
+ var prevRowMaxCol = ((_colorsPerRow = colorsPerRow[row - 1]) === null || _colorsPerRow === void 0 ? void 0 : _colorsPerRow.length) - 1 || 0;
159
+ focusColorAt(row - 1, prevRowMaxCol);
160
+ } else {
161
+ var _colorsPerRow$maxRow;
162
+ // Wrap to last color of last row
163
+ var lastRowMaxCol = ((_colorsPerRow$maxRow = colorsPerRow[maxRow]) === null || _colorsPerRow$maxRow === void 0 ? void 0 : _colorsPerRow$maxRow.length) - 1 || 0;
164
+ focusColorAt(maxRow, lastRowMaxCol);
165
+ }
166
+ break;
167
+ }
168
+ case 'ArrowDown':
169
+ {
170
+ event.preventDefault();
171
+ if (row < maxRow) {
172
+ var _colorsPerRow2;
173
+ // Move to same column in next row, or last available column
174
+ var nextRowMaxCol = ((_colorsPerRow2 = colorsPerRow[row + 1]) === null || _colorsPerRow2 === void 0 ? void 0 : _colorsPerRow2.length) - 1 || 0;
175
+ var targetCol = Math.min(col, nextRowMaxCol);
176
+ focusColorAt(row + 1, targetCol);
177
+ } else {
178
+ var _colorsPerRow$;
179
+ // Wrap to same column in first row
180
+ var firstRowMaxCol = ((_colorsPerRow$ = colorsPerRow[0]) === null || _colorsPerRow$ === void 0 ? void 0 : _colorsPerRow$.length) - 1 || 0;
181
+ var _targetCol = Math.min(col, firstRowMaxCol);
182
+ focusColorAt(0, _targetCol);
183
+ }
184
+ break;
185
+ }
186
+ case 'ArrowUp':
187
+ {
188
+ event.preventDefault();
189
+ if (row > 0) {
190
+ var _colorsPerRow3;
191
+ // Move to same column in previous row, or last available column
192
+ var _prevRowMaxCol = ((_colorsPerRow3 = colorsPerRow[row - 1]) === null || _colorsPerRow3 === void 0 ? void 0 : _colorsPerRow3.length) - 1 || 0;
193
+ var _targetCol2 = Math.min(col, _prevRowMaxCol);
194
+ focusColorAt(row - 1, _targetCol2);
195
+ } else {
196
+ var _colorsPerRow$maxRow2;
197
+ // Wrap to same column in last row
198
+ var _lastRowMaxCol = ((_colorsPerRow$maxRow2 = colorsPerRow[maxRow]) === null || _colorsPerRow$maxRow2 === void 0 ? void 0 : _colorsPerRow$maxRow2.length) - 1 || 0;
199
+ var _targetCol3 = Math.min(col, _lastRowMaxCol);
200
+ focusColorAt(maxRow, _targetCol3);
201
+ }
202
+ break;
203
+ }
204
+ case 'Home':
205
+ {
206
+ event.preventDefault();
207
+ focusColorAt(row, 0);
208
+ break;
209
+ }
210
+ case 'End':
211
+ {
212
+ event.preventDefault();
213
+ focusColorAt(row, maxCol);
214
+ break;
215
+ }
216
+ case 'PageUp':
217
+ {
218
+ event.preventDefault();
219
+ focusColorAt(0, col);
220
+ break;
221
+ }
222
+ case 'PageDown':
223
+ {
224
+ var _colorsPerRow$maxRow3;
225
+ event.preventDefault();
226
+ var _lastRowMaxCol2 = ((_colorsPerRow$maxRow3 = colorsPerRow[maxRow]) === null || _colorsPerRow$maxRow3 === void 0 ? void 0 : _colorsPerRow$maxRow3.length) - 1 || 0;
227
+ var _targetCol4 = Math.min(col, _lastRowMaxCol2);
228
+ focusColorAt(maxRow, _targetCol4);
229
+ break;
230
+ }
231
+ case 'Tab':
232
+ {
233
+ // Allow Tab to move to next focusable element (don't prevent default)
234
+ // This will allow Tab to move between color palettes
235
+ if (onKeyDown) {
236
+ onKeyDown(value, label, event);
237
+ }
238
+ break;
239
+ }
240
+ case 'Enter':
241
+ case ' ':
242
+ {
243
+ event.preventDefault();
244
+ onClick(value, label);
245
+ break;
246
+ }
247
+ default:
248
+ {
249
+ // Pass through to custom onKeyDown handler if provided
250
+ if (onKeyDown) {
251
+ onKeyDown(value, label, event);
252
+ }
253
+ break;
254
+ }
255
+ }
256
+ }, [colorsPerRow, focusColorAt, onClick, onKeyDown]);
257
+ return expValEquals('platform_editor_toolbar_aifc_patch_1', 'isEnabled', true) ? /*#__PURE__*/React.createElement(Grid, {
258
+ gap: "space.050",
259
+ ref: paletteRef,
260
+ role: "group"
261
+ }, colorsPerRow.map(function (row, rowIndex) {
262
+ return /*#__PURE__*/React.createElement(Inline, {
263
+ rowSpace: "space.050",
75
264
  key: "row-first-color-".concat(row[0].value),
76
265
  role: "radiogroup"
77
- }, row.map(function (_ref2) {
266
+ }, row.map(function (_ref2, colIndex) {
78
267
  var value = _ref2.value,
79
268
  label = _ref2.label,
80
269
  border = _ref2.border,
@@ -82,6 +271,51 @@ var ColorPalette = function ColorPalette(_ref) {
82
271
  decorator = _ref2.decorator;
83
272
  var tooltipMessage = message;
84
273
 
274
+ // Override with theme-specific tooltip messages if provided
275
+ if (paletteColorTooltipMessages) {
276
+ if (tokenTheme === 'dark') {
277
+ tooltipMessage = getColorMessage(paletteColorTooltipMessages.dark, value.toUpperCase());
278
+ }
279
+ if (tokenTheme === 'light') {
280
+ tooltipMessage = getColorMessage(paletteColorTooltipMessages.light, value.toUpperCase());
281
+ }
282
+ }
283
+
284
+ // Determine if this color should be focusable
285
+ var isSelectedColor = value === selectedColor;
286
+ var isFirstColor = rowIndex === 0 && colIndex === 0;
287
+
288
+ // Only the selected color or first color should be focusable via Tab
289
+ // This allows Tab to move between color palettes
290
+ var shouldBeFocusable = isSelectedColor || !selectedColor && isFirstColor;
291
+ return /*#__PURE__*/React.createElement(Color, {
292
+ key: value,
293
+ value: value,
294
+ borderColor: border,
295
+ label: tooltipMessage ? formatMessage(tooltipMessage) : label,
296
+ onClick: onClick,
297
+ onKeyDown: handleKeyDown,
298
+ isSelected: isSelectedColor,
299
+ checkMarkColor: getCheckMarkColor(value, useIconToken),
300
+ hexToPaletteColor: hexToPaletteColor,
301
+ decorator: decorator,
302
+ tabIndex: shouldBeFocusable ? 0 : -1,
303
+ autoFocus: isSelectedColor && rowIndex === selectedRowIndex && colIndex === selectedColumnIndex
304
+ });
305
+ }));
306
+ })) : /*#__PURE__*/React.createElement(React.Fragment, null, colorsPerRow.map(function (row) {
307
+ return /*#__PURE__*/React.createElement(Box, {
308
+ xcss: styles.paletteWrapper,
309
+ key: "row-first-color-".concat(row[0].value),
310
+ role: "radiogroup"
311
+ }, row.map(function (_ref3) {
312
+ var value = _ref3.value,
313
+ label = _ref3.label,
314
+ border = _ref3.border,
315
+ message = _ref3.message,
316
+ decorator = _ref3.decorator;
317
+ var tooltipMessage = message;
318
+
85
319
  // Override with theme-specific tooltip messages if provided
86
320
  if (paletteColorTooltipMessages) {
87
321
  if (tokenTheme === 'dark') {
@@ -5,6 +5,7 @@ import React, { forwardRef } from 'react';
5
5
  import { cx } from '@atlaskit/css';
6
6
  import { DropdownItem } from '@atlaskit/dropdown-menu';
7
7
  import { Pressable } from '@atlaskit/primitives/compiled';
8
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
8
9
  var styles = {
9
10
  toolbarDropdownItem: "_18zrpxbi _1rjcu2gc _1e0c1txw _kqswh2mm _bfhksm61 _1bsb1osq _1tke14no _1ul9163w _1basglpi _1ah31i6y",
10
11
  enabled: "_irr3166n _1di61dty",
@@ -43,7 +44,10 @@ export var ToolbarDropdownItem = function ToolbarDropdownItem(_ref2) {
43
44
  hasNestedDropdownMenu = _ref2.hasNestedDropdownMenu,
44
45
  triggerRef = _ref2.triggerRef,
45
46
  testId = _ref2.testId,
46
- ariaKeyshortcuts = _ref2.ariaKeyshortcuts;
47
+ ariaKeyshortcuts = _ref2.ariaKeyshortcuts,
48
+ href = _ref2.href,
49
+ target = _ref2.target,
50
+ rel = _ref2.rel;
47
51
  return /*#__PURE__*/React.createElement(DropdownItem, {
48
52
  onClick: onClick,
49
53
  elemBefore: elemBefore,
@@ -54,7 +58,10 @@ export var ToolbarDropdownItem = function ToolbarDropdownItem(_ref2) {
54
58
  "aria-pressed": isSelected,
55
59
  "aria-keyshortcuts": ariaKeyshortcuts,
56
60
  ref: triggerRef,
57
- component: CustomDropdownMenuItemButton,
61
+ href: href,
62
+ target: target,
63
+ rel: rel,
64
+ component: href && expValEquals('platform_editor_toolbar_migrate_loom', 'isEnabled', true) ? undefined : CustomDropdownMenuItemButton,
58
65
  testId: testId
59
66
  }, children);
60
67
  };
@@ -1 +1,2 @@
1
- ._1mou1b66{margin-block:var(--ds-space-050,4px)}
1
+
2
+ ._1mou1b66{margin-block:var(--ds-space-050,4px)}._18l8n7od [data-section]:first-of-type{border-block-start:unset}
@@ -3,11 +3,13 @@ import "./ToolbarDropdownMenu.compiled.css";
3
3
  import { ax, ix } from "@compiled/react/runtime";
4
4
  import React, { useCallback } from 'react';
5
5
  import DropdownMenu from '@atlaskit/dropdown-menu';
6
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
6
7
  import { useToolbarUI } from '../hooks/ui-context';
7
8
  import { ToolbarButton } from './ToolbarButton';
8
9
  import { ToolbarDropdownMenuProvider, useToolbarDropdownMenu } from './ToolbarDropdownMenuContext';
9
10
  var styles = {
10
- sectionMargin: "_1mou1b66"
11
+ sectionMargin: "_1mou1b66",
12
+ firstSectionSeparator: "_18l8n7od"
11
13
  };
12
14
  var ToolbarDropdownMenuContent = function ToolbarDropdownMenuContent(_ref) {
13
15
  var iconBefore = _ref.iconBefore,
@@ -72,6 +74,6 @@ export var ToolbarDropdownMenu = function ToolbarDropdownMenu(_ref2) {
72
74
  testId: testId,
73
75
  label: label
74
76
  }, /*#__PURE__*/React.createElement("div", {
75
- className: ax([hasSectionMargin && styles.sectionMargin])
77
+ className: ax([hasSectionMargin && styles.sectionMargin, expValEquals('platform_editor_toolbar_migrate_loom', 'isEnabled', true) && styles.firstSectionSeparator])
76
78
  }, children)));
77
79
  };
@@ -0,0 +1,2 @@
1
+ /* eslint-disable @atlaskit/editor/no-re-export */
2
+ export { default as LoomIcon } from '@atlaskit/icon/core/video';
@@ -64,6 +64,7 @@ export { TaskIcon } from './ui/icons/TaskIcon';
64
64
  export { UndoIcon } from './ui/icons/UndoIcon';
65
65
  export { RedoIcon } from './ui/icons/RedoIcon';
66
66
  export { HistoryIcon } from './ui/icons/HistoryIcon';
67
+ export { LoomIcon } from './ui/icons/LoomIcon';
67
68
  export { default as ColorPalette } from './ui/ColorPalette';
68
69
  export { getContrastingBackgroundColor } from './ui/ColorPalette/utils';
69
70
  export type { IconComponent } from './types';
@@ -13,12 +13,15 @@ type ToolbarDropdownItemProps = {
13
13
  elemAfter?: ReactNode;
14
14
  elemBefore?: ReactNode;
15
15
  hasNestedDropdownMenu?: boolean;
16
+ href?: string;
16
17
  isDisabled?: boolean;
17
18
  isSelected?: boolean;
18
19
  onClick?: (e: React.MouseEvent | React.KeyboardEvent) => void;
20
+ rel?: string;
21
+ target?: string;
19
22
  testId?: string;
20
23
  textStyle?: TextStyle;
21
24
  triggerRef?: Ref<HTMLButtonElement>;
22
25
  };
23
- export declare const ToolbarDropdownItem: ({ onClick, elemBefore, elemAfter, isSelected, children, isDisabled, hasNestedDropdownMenu, triggerRef, testId, ariaKeyshortcuts, }: ToolbarDropdownItemProps) => React.JSX.Element;
26
+ export declare const ToolbarDropdownItem: ({ onClick, elemBefore, elemAfter, isSelected, children, isDisabled, hasNestedDropdownMenu, triggerRef, testId, ariaKeyshortcuts, href, target, rel, }: ToolbarDropdownItemProps) => React.JSX.Element;
24
27
  export {};
@@ -0,0 +1 @@
1
+ export { default as LoomIcon } from '@atlaskit/icon/core/video';
@@ -64,6 +64,7 @@ export { TaskIcon } from './ui/icons/TaskIcon';
64
64
  export { UndoIcon } from './ui/icons/UndoIcon';
65
65
  export { RedoIcon } from './ui/icons/RedoIcon';
66
66
  export { HistoryIcon } from './ui/icons/HistoryIcon';
67
+ export { LoomIcon } from './ui/icons/LoomIcon';
67
68
  export { default as ColorPalette } from './ui/ColorPalette';
68
69
  export { getContrastingBackgroundColor } from './ui/ColorPalette/utils';
69
70
  export type { IconComponent } from './types';
@@ -13,12 +13,15 @@ type ToolbarDropdownItemProps = {
13
13
  elemAfter?: ReactNode;
14
14
  elemBefore?: ReactNode;
15
15
  hasNestedDropdownMenu?: boolean;
16
+ href?: string;
16
17
  isDisabled?: boolean;
17
18
  isSelected?: boolean;
18
19
  onClick?: (e: React.MouseEvent | React.KeyboardEvent) => void;
20
+ rel?: string;
21
+ target?: string;
19
22
  testId?: string;
20
23
  textStyle?: TextStyle;
21
24
  triggerRef?: Ref<HTMLButtonElement>;
22
25
  };
23
- export declare const ToolbarDropdownItem: ({ onClick, elemBefore, elemAfter, isSelected, children, isDisabled, hasNestedDropdownMenu, triggerRef, testId, ariaKeyshortcuts, }: ToolbarDropdownItemProps) => React.JSX.Element;
26
+ export declare const ToolbarDropdownItem: ({ onClick, elemBefore, elemAfter, isSelected, children, isDisabled, hasNestedDropdownMenu, triggerRef, testId, ariaKeyshortcuts, href, target, rel, }: ToolbarDropdownItemProps) => React.JSX.Element;
24
27
  export {};
@@ -0,0 +1 @@
1
+ export { default as LoomIcon } from '@atlaskit/icon/core/video';
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "registry": "https://registry.npmjs.org/"
5
5
  },
6
- "version": "0.4.0",
6
+ "version": "0.5.0",
7
7
  "description": "Common UI for Toolbars across the platform",
8
8
  "atlassian": {
9
9
  "team": "Editor: Jenga",
@@ -30,6 +30,7 @@
30
30
  "@atlaskit/logo": "^19.7.0",
31
31
  "@atlaskit/popup": "^4.3.0",
32
32
  "@atlaskit/primitives": "^14.12.0",
33
+ "@atlaskit/tmp-editor-statsig": "^11.9.0",
33
34
  "@atlaskit/tokens": "^6.1.0",
34
35
  "@atlaskit/tooltip": "^20.4.0",
35
36
  "@babel/runtime": "^7.0.0",