@atlaskit/editor-plugin-status 13.0.25 → 13.1.1
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 +17 -0
- package/dist/cjs/ui/ContentComponent.js +56 -2
- package/dist/cjs/ui/getSuggestedStatuses.js +72 -0
- package/dist/cjs/ui/statusPicker.js +152 -8
- package/dist/es2019/ui/ContentComponent.js +55 -2
- package/dist/es2019/ui/getSuggestedStatuses.js +56 -0
- package/dist/es2019/ui/statusPicker.js +143 -8
- package/dist/esm/ui/ContentComponent.js +56 -2
- package/dist/esm/ui/getSuggestedStatuses.js +65 -0
- package/dist/esm/ui/statusPicker.js +152 -8
- package/dist/types/ui/ContentComponent.d.ts +1 -0
- package/dist/types/ui/getSuggestedStatuses.d.ts +15 -0
- package/dist/types/ui/statusPicker.d.ts +3 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-status
|
|
2
2
|
|
|
3
|
+
## 13.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
|
|
9
|
+
## 13.1.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [`f4f1a7be76c2c`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/f4f1a7be76c2c) -
|
|
14
|
+
Add reusable status suggestions to the editor status picker
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Updated dependencies
|
|
19
|
+
|
|
3
20
|
## 13.0.25
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
|
@@ -6,13 +6,19 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
value: true
|
|
7
7
|
});
|
|
8
8
|
exports.ContentComponent = ContentComponent;
|
|
9
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
10
|
var _react = _interopRequireWildcard(require("react"));
|
|
10
11
|
var _hooks = require("@atlaskit/editor-common/hooks");
|
|
11
12
|
var _utils = require("@atlaskit/editor-prosemirror/utils");
|
|
12
13
|
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
14
|
+
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
|
|
13
15
|
var _actions = require("../pm-plugins/actions");
|
|
16
|
+
var _getSuggestedStatuses = require("./getSuggestedStatuses");
|
|
14
17
|
var _statusPicker = _interopRequireDefault(require("./statusPicker"));
|
|
15
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); }
|
|
19
|
+
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; }
|
|
20
|
+
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; }
|
|
21
|
+
/** Renders the status picker popup for the currently selected status node. */
|
|
16
22
|
function ContentComponent(_ref) {
|
|
17
23
|
var api = _ref.api,
|
|
18
24
|
popupsMountPoint = _ref.popupsMountPoint,
|
|
@@ -33,7 +39,9 @@ function ContentComponent(_ref) {
|
|
|
33
39
|
}, [showStatusPickerAt, domAtPos]);
|
|
34
40
|
var statusNode = (0, _react.useMemo)(function () {
|
|
35
41
|
return showStatusPickerAt ? editorView.state.doc.nodeAt(showStatusPickerAt) : undefined;
|
|
36
|
-
},
|
|
42
|
+
},
|
|
43
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- preserve original recompute cadence for the ungated path
|
|
44
|
+
[showStatusPickerAt, editorView]);
|
|
37
45
|
var onSelect = (0, _react.useCallback)(function (status) {
|
|
38
46
|
(0, _actions.updateStatus)(status)(editorView.state, editorView.dispatch);
|
|
39
47
|
}, [editorView]);
|
|
@@ -46,6 +54,50 @@ function ContentComponent(_ref) {
|
|
|
46
54
|
var onEnter = (0, _react.useCallback)(function () {
|
|
47
55
|
(0, _actions.commitStatusPicker)()(editorView);
|
|
48
56
|
}, [editorView]);
|
|
57
|
+
var onSuggestedStatusClick = (0, _react.useCallback)(function (status) {
|
|
58
|
+
var _editorView$state$doc;
|
|
59
|
+
var currentLocalId = typeof showStatusPickerAt === 'number' ? (_editorView$state$doc = editorView.state.doc.nodeAt(showStatusPickerAt)) === null || _editorView$state$doc === void 0 ? void 0 : _editorView$state$doc.attrs.localId : undefined;
|
|
60
|
+
var suggestedStatus = _objectSpread(_objectSpread(_objectSpread({
|
|
61
|
+
color: status.color
|
|
62
|
+
}, currentLocalId ? {
|
|
63
|
+
localId: currentLocalId
|
|
64
|
+
} : {}), status.style ? {
|
|
65
|
+
style: status.style
|
|
66
|
+
} : {}), {}, {
|
|
67
|
+
text: status.text
|
|
68
|
+
});
|
|
69
|
+
var didUpdateStatus = (0, _actions.updateStatus)(suggestedStatus)(editorView.state, editorView.dispatch);
|
|
70
|
+
if (didUpdateStatus) {
|
|
71
|
+
(0, _actions.commitStatusPicker)()(editorView);
|
|
72
|
+
}
|
|
73
|
+
}, [editorView, showStatusPickerAt]);
|
|
74
|
+
var suggestedStatuses = (0, _react.useMemo)(function () {
|
|
75
|
+
if (typeof showStatusPickerAt !== 'number' || !(0, _expValEquals.expValEquals)('platform_editor_status_popup_suggestions', 'isEnabled', true)) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Read the node directly from the current doc so suggestions reflect the latest
|
|
80
|
+
// document state (the memoized `statusNode` intentionally preserves the original
|
|
81
|
+
// recompute cadence for the ungated path).
|
|
82
|
+
var currentNode = editorView.state.doc.nodeAt(showStatusPickerAt);
|
|
83
|
+
if (!currentNode || currentNode.type.name !== 'status') {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
var _currentNode$attrs = currentNode.attrs,
|
|
87
|
+
color = _currentNode$attrs.color,
|
|
88
|
+
localId = _currentNode$attrs.localId,
|
|
89
|
+
text = _currentNode$attrs.text;
|
|
90
|
+
return (0, _getSuggestedStatuses.getSuggestedStatuses)({
|
|
91
|
+
currentPos: showStatusPickerAt,
|
|
92
|
+
currentStatus: {
|
|
93
|
+
color: color,
|
|
94
|
+
localId: localId,
|
|
95
|
+
text: text
|
|
96
|
+
},
|
|
97
|
+
doc: editorView.state.doc,
|
|
98
|
+
shouldUppercaseText: (0, _platformFeatureFlags.fg)('platform-dst-lozenge-tag-badge-visual-uplifts')
|
|
99
|
+
});
|
|
100
|
+
}, [showStatusPickerAt, editorView.state.doc]);
|
|
49
101
|
if (typeof showStatusPickerAt !== 'number') {
|
|
50
102
|
return null;
|
|
51
103
|
}
|
|
@@ -72,7 +124,9 @@ function ContentComponent(_ref) {
|
|
|
72
124
|
onTextChanged: onTextChanged,
|
|
73
125
|
closeStatusPicker: closeStatusPicker,
|
|
74
126
|
onEnter: onEnter,
|
|
127
|
+
onSuggestedStatusClick: onSuggestedStatusClick,
|
|
75
128
|
editorView: editorView,
|
|
76
|
-
api: api
|
|
129
|
+
api: api,
|
|
130
|
+
suggestedStatuses: suggestedStatuses
|
|
77
131
|
});
|
|
78
132
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.getSuggestedStatuses = exports.MAX_SUGGESTED_STATUSES = void 0;
|
|
8
|
+
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
|
|
9
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
10
|
+
var _excluded = ["distance"];
|
|
11
|
+
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; }
|
|
12
|
+
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; }
|
|
13
|
+
var MAX_SUGGESTED_STATUSES = exports.MAX_SUGGESTED_STATUSES = 7;
|
|
14
|
+
var getStatusKey = function getStatusKey(_ref) {
|
|
15
|
+
var color = _ref.color,
|
|
16
|
+
text = _ref.text;
|
|
17
|
+
return "".concat(color, ":").concat(text.trim().toLowerCase());
|
|
18
|
+
};
|
|
19
|
+
var getDisplayText = function getDisplayText(status, shouldUppercaseText) {
|
|
20
|
+
return shouldUppercaseText && status.style !== 'mixedCase' ? status.text.toUpperCase() : status.text;
|
|
21
|
+
};
|
|
22
|
+
var getSuggestedStatuses = exports.getSuggestedStatuses = function getSuggestedStatuses(_ref2) {
|
|
23
|
+
var currentPos = _ref2.currentPos,
|
|
24
|
+
currentStatus = _ref2.currentStatus,
|
|
25
|
+
doc = _ref2.doc,
|
|
26
|
+
_ref2$limit = _ref2.limit,
|
|
27
|
+
limit = _ref2$limit === void 0 ? MAX_SUGGESTED_STATUSES : _ref2$limit,
|
|
28
|
+
_ref2$shouldUppercase = _ref2.shouldUppercaseText,
|
|
29
|
+
shouldUppercaseText = _ref2$shouldUppercase === void 0 ? false : _ref2$shouldUppercase;
|
|
30
|
+
var closestStatusesByKey = new Map();
|
|
31
|
+
var currentStatusKey = getStatusKey(currentStatus);
|
|
32
|
+
doc.descendants(function (node, pos) {
|
|
33
|
+
if (node.type.name !== 'status' || pos === currentPos) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
var text = typeof node.attrs.text === 'string' ? node.attrs.text.trim() : '';
|
|
37
|
+
if (!text) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
var status = {
|
|
41
|
+
color: node.attrs.color,
|
|
42
|
+
localId: node.attrs.localId,
|
|
43
|
+
style: node.attrs.style,
|
|
44
|
+
text: text
|
|
45
|
+
};
|
|
46
|
+
var statusKey = getStatusKey(status);
|
|
47
|
+
if (status.localId === currentStatus.localId || statusKey === currentStatusKey) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
var existingStatus = closestStatusesByKey.get(statusKey);
|
|
51
|
+
var nextStatus = _objectSpread(_objectSpread({}, status), {}, {
|
|
52
|
+
distance: Math.abs(pos - currentPos),
|
|
53
|
+
displayText: getDisplayText(status, shouldUppercaseText)
|
|
54
|
+
});
|
|
55
|
+
if (!existingStatus || nextStatus.distance < existingStatus.distance) {
|
|
56
|
+
closestStatusesByKey.set(statusKey, nextStatus);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return Array.from(closestStatusesByKey.values()).sort(function (statusA, statusB) {
|
|
60
|
+
if (statusA.distance !== statusB.distance) {
|
|
61
|
+
return statusA.distance - statusB.distance;
|
|
62
|
+
}
|
|
63
|
+
if (statusA.displayText !== statusB.displayText) {
|
|
64
|
+
return statusA.displayText.localeCompare(statusB.displayText);
|
|
65
|
+
}
|
|
66
|
+
return statusA.color.localeCompare(statusB.color);
|
|
67
|
+
}).slice(0, limit).map(function (_ref3) {
|
|
68
|
+
var _distance = _ref3.distance,
|
|
69
|
+
status = (0, _objectWithoutProperties2.default)(_ref3, _excluded);
|
|
70
|
+
return status;
|
|
71
|
+
});
|
|
72
|
+
};
|
|
@@ -22,7 +22,10 @@ var _uiReact = require("@atlaskit/editor-common/ui-react");
|
|
|
22
22
|
var _userIntent = require("@atlaskit/editor-common/user-intent");
|
|
23
23
|
var _editorSharedStyles = require("@atlaskit/editor-shared-styles");
|
|
24
24
|
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
25
|
+
var _element = require("@atlaskit/status/element");
|
|
25
26
|
var _picker = require("@atlaskit/status/picker");
|
|
27
|
+
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
|
|
28
|
+
var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
|
|
26
29
|
var _visuallyHidden = _interopRequireDefault(require("@atlaskit/visually-hidden"));
|
|
27
30
|
var _actions = require("../pm-plugins/actions");
|
|
28
31
|
var _analytics = require("./analytics");
|
|
@@ -68,6 +71,27 @@ var pickerContainerStylesTeam26 = (0, _react2.css)({
|
|
|
68
71
|
outline: 'none'
|
|
69
72
|
}
|
|
70
73
|
});
|
|
74
|
+
var suggestedStatusesContainerStyles = (0, _react2.css)({
|
|
75
|
+
display: 'flex',
|
|
76
|
+
flexDirection: 'column',
|
|
77
|
+
gap: "var(--ds-space-100, 8px)",
|
|
78
|
+
margin: 0,
|
|
79
|
+
padding: "var(--ds-space-100, 8px)".concat(" ", "var(--ds-space-150, 12px)", " ", "var(--ds-space-050, 4px)")
|
|
80
|
+
});
|
|
81
|
+
var suggestedStatusButtonStyles = (0, _react2.css)({
|
|
82
|
+
background: 'transparent',
|
|
83
|
+
border: 0,
|
|
84
|
+
borderRadius: "var(--ds-radius-small, 3px)",
|
|
85
|
+
cursor: 'pointer',
|
|
86
|
+
display: 'flex',
|
|
87
|
+
justifyContent: 'flex-start',
|
|
88
|
+
padding: 0,
|
|
89
|
+
width: '100%',
|
|
90
|
+
'&:focus-visible': {
|
|
91
|
+
outline: "2px solid ".concat("var(--ds-border-focused, #0C66E4)"),
|
|
92
|
+
outlineOffset: "var(--ds-space-025, 2px)"
|
|
93
|
+
}
|
|
94
|
+
});
|
|
71
95
|
|
|
72
96
|
// eslint-disable-next-line @repo/internal/react/no-class-components
|
|
73
97
|
var StatusPickerWithIntl = /*#__PURE__*/function (_React$Component) {
|
|
@@ -89,18 +113,59 @@ var StatusPickerWithIntl = /*#__PURE__*/function (_React$Component) {
|
|
|
89
113
|
_this.props.onEnter(_this.state);
|
|
90
114
|
});
|
|
91
115
|
(0, _defineProperty2.default)(_this, "handleTabPress", function (event) {
|
|
92
|
-
var
|
|
93
|
-
|
|
116
|
+
var _getDocument2;
|
|
117
|
+
if (!(0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_status_popup_suggestions', 'isEnabled', true)) {
|
|
118
|
+
var _getDocument;
|
|
119
|
+
/* original tab-navigation behaviour: cycle between color buttons and the input field */
|
|
120
|
+
var _colorButtons = event.currentTarget.querySelectorAll('button');
|
|
121
|
+
var _inputField = event.currentTarget.querySelector('input');
|
|
122
|
+
var _activeElement = (_getDocument = (0, _browserApis.getDocument)()) === null || _getDocument === void 0 ? void 0 : _getDocument.activeElement;
|
|
123
|
+
var _isInputFocussed = _activeElement === _inputField;
|
|
124
|
+
var _isButtonFocussed = Array.from(_colorButtons).some(function (buttonElement) {
|
|
125
|
+
return _activeElement === buttonElement;
|
|
126
|
+
});
|
|
127
|
+
if (event !== null && event !== void 0 && event.shiftKey) {
|
|
128
|
+
/* shift + tab */
|
|
129
|
+
if (_isInputFocussed) {
|
|
130
|
+
_colorButtons[0].focus();
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
}
|
|
133
|
+
/* After the user presses shift + tab the color-palette component updates tab index for the first color to be 0.
|
|
134
|
+
To correctly set focus to the input field instead of the first color button we need to set focus manually
|
|
135
|
+
*/
|
|
136
|
+
if (_isButtonFocussed) {
|
|
137
|
+
_inputField === null || _inputField === void 0 || _inputField.focus();
|
|
138
|
+
event.preventDefault();
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
/* tab */
|
|
142
|
+
if (_isButtonFocussed) {
|
|
143
|
+
_inputField === null || _inputField === void 0 || _inputField.focus();
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
var colorButtons = event.currentTarget.querySelectorAll('button:not([data-suggested-status-button])');
|
|
150
|
+
var suggestedStatusButtons = event.currentTarget.querySelectorAll('button[data-suggested-status-button]');
|
|
94
151
|
var inputField = event.currentTarget.querySelector('input');
|
|
95
|
-
var activeElement = (
|
|
152
|
+
var activeElement = (_getDocument2 = (0, _browserApis.getDocument)()) === null || _getDocument2 === void 0 ? void 0 : _getDocument2.activeElement;
|
|
96
153
|
var isInputFocussed = activeElement === inputField;
|
|
97
154
|
var isButtonFocussed = Array.from(colorButtons).some(function (buttonElement) {
|
|
98
155
|
return activeElement === buttonElement;
|
|
99
156
|
});
|
|
157
|
+
var isSuggestedStatusButtonFocussed = Array.from(suggestedStatusButtons).some(function (buttonElement) {
|
|
158
|
+
return activeElement === buttonElement;
|
|
159
|
+
});
|
|
100
160
|
if (event !== null && event !== void 0 && event.shiftKey) {
|
|
101
161
|
/* shift + tab */
|
|
102
162
|
if (isInputFocussed) {
|
|
103
|
-
|
|
163
|
+
if (suggestedStatusButtons.length > 0) {
|
|
164
|
+
suggestedStatusButtons[suggestedStatusButtons.length - 1].focus();
|
|
165
|
+
} else {
|
|
166
|
+
var _colorButtons$;
|
|
167
|
+
(_colorButtons$ = colorButtons[0]) === null || _colorButtons$ === void 0 || _colorButtons$.focus();
|
|
168
|
+
}
|
|
104
169
|
event.preventDefault();
|
|
105
170
|
}
|
|
106
171
|
/* After the user presses shift + tab the color-palette component updates tab index for the first color to be 0.
|
|
@@ -110,17 +175,30 @@ var StatusPickerWithIntl = /*#__PURE__*/function (_React$Component) {
|
|
|
110
175
|
inputField === null || inputField === void 0 || inputField.focus();
|
|
111
176
|
event.preventDefault();
|
|
112
177
|
}
|
|
178
|
+
if (isSuggestedStatusButtonFocussed) {
|
|
179
|
+
var _colorButtons2;
|
|
180
|
+
(_colorButtons2 = colorButtons[colorButtons.length - 1]) === null || _colorButtons2 === void 0 || _colorButtons2.focus();
|
|
181
|
+
event.preventDefault();
|
|
182
|
+
}
|
|
113
183
|
} else {
|
|
114
184
|
/* tab */
|
|
115
185
|
if (isButtonFocussed) {
|
|
186
|
+
if (suggestedStatusButtons.length > 0) {
|
|
187
|
+
suggestedStatusButtons[0].focus();
|
|
188
|
+
} else {
|
|
189
|
+
inputField === null || inputField === void 0 || inputField.focus();
|
|
190
|
+
}
|
|
191
|
+
event.preventDefault();
|
|
192
|
+
}
|
|
193
|
+
if (isSuggestedStatusButtonFocussed) {
|
|
116
194
|
inputField === null || inputField === void 0 || inputField.focus();
|
|
117
195
|
event.preventDefault();
|
|
118
196
|
}
|
|
119
197
|
}
|
|
120
198
|
});
|
|
121
199
|
(0, _defineProperty2.default)(_this, "handleArrow", function (event, closingMethod) {
|
|
122
|
-
var
|
|
123
|
-
var activeElement = (
|
|
200
|
+
var _getDocument3;
|
|
201
|
+
var activeElement = (_getDocument3 = (0, _browserApis.getDocument)()) === null || _getDocument3 === void 0 ? void 0 : _getDocument3.activeElement;
|
|
124
202
|
if (activeElement === _this.popupBodyWrapper.current) {
|
|
125
203
|
var _this$popupBodyWrappe;
|
|
126
204
|
event.preventDefault();
|
|
@@ -194,6 +272,19 @@ var StatusPickerWithIntl = /*#__PURE__*/function (_React$Component) {
|
|
|
194
272
|
_this.inputMethod = InputMethod.enterKey;
|
|
195
273
|
_this.props.onEnter(_this.state);
|
|
196
274
|
});
|
|
275
|
+
(0, _defineProperty2.default)(_this, "onSuggestedStatusClick", function (status, position) {
|
|
276
|
+
_this.fireSuggestedStatusClickedAnalytics(status, position);
|
|
277
|
+
_this.props.onSuggestedStatusClick(status);
|
|
278
|
+
});
|
|
279
|
+
(0, _defineProperty2.default)(_this, "handleSuggestedStatusKeyDown", function (event) {
|
|
280
|
+
// Selection (Enter/Space) is handled natively via the button's onClick.
|
|
281
|
+
// Keydown only drives ArrowUp/ArrowDown navigation between suggestions.
|
|
282
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
283
|
+
event.preventDefault();
|
|
284
|
+
event.stopPropagation();
|
|
285
|
+
_this.focusAdjacentSuggestedStatus(event.currentTarget, event.key === 'ArrowDown' ? 1 : -1);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
197
288
|
// cancel bubbling to fix clickOutside logic:
|
|
198
289
|
// popup re-renders its content before the click event bubbles up to the document
|
|
199
290
|
// therefore click target element would be different from the popup content
|
|
@@ -302,10 +393,12 @@ var StatusPickerWithIntl = /*#__PURE__*/function (_React$Component) {
|
|
|
302
393
|
}, {
|
|
303
394
|
key: "renderWithSetOutsideClickTargetRef",
|
|
304
395
|
value: function renderWithSetOutsideClickTargetRef(setOutsideClickTargetRef) {
|
|
396
|
+
var _this3 = this;
|
|
305
397
|
var _this$props = this.props,
|
|
306
398
|
isNew = _this$props.isNew,
|
|
307
399
|
focusStatusInput = _this$props.focusStatusInput,
|
|
308
|
-
api = _this$props.api
|
|
400
|
+
api = _this$props.api,
|
|
401
|
+
suggestedStatuses = _this$props.suggestedStatuses;
|
|
309
402
|
var _this$state3 = this.state,
|
|
310
403
|
color = _this$state3.color,
|
|
311
404
|
text = _this$state3.text;
|
|
@@ -326,7 +419,26 @@ var StatusPickerWithIntl = /*#__PURE__*/function (_React$Component) {
|
|
|
326
419
|
onColorHover: this.onColorHover,
|
|
327
420
|
onTextChanged: this.onTextChanged,
|
|
328
421
|
onEnter: this.onEnter
|
|
329
|
-
})))
|
|
422
|
+
}), suggestedStatuses !== null && suggestedStatuses !== void 0 && suggestedStatuses.length && (0, _expValEquals.expValEquals)('platform_editor_status_popup_suggestions', 'isEnabled', true) ? (0, _react2.jsx)("div", {
|
|
423
|
+
css: suggestedStatusesContainerStyles,
|
|
424
|
+
"data-suggested-status-list": true
|
|
425
|
+
}, suggestedStatuses.map(function (suggestedStatus, index) {
|
|
426
|
+
return (0, _react2.jsx)("div", {
|
|
427
|
+
key: "".concat(suggestedStatus.color, ":").concat(suggestedStatus.text)
|
|
428
|
+
}, (0, _react2.jsx)("button", {
|
|
429
|
+
type: "button",
|
|
430
|
+
css: suggestedStatusButtonStyles,
|
|
431
|
+
onClick: function onClick() {
|
|
432
|
+
return _this3.onSuggestedStatusClick(suggestedStatus, index + 1);
|
|
433
|
+
},
|
|
434
|
+
onKeyDown: _this3.handleSuggestedStatusKeyDown,
|
|
435
|
+
"aria-label": suggestedStatus.displayText,
|
|
436
|
+
"data-suggested-status-button": true
|
|
437
|
+
}, (0, _react2.jsx)(_element.Status, {
|
|
438
|
+
color: suggestedStatus.color,
|
|
439
|
+
text: suggestedStatus.displayText
|
|
440
|
+
})));
|
|
441
|
+
})) : null));
|
|
330
442
|
}
|
|
331
443
|
}, {
|
|
332
444
|
key: "render",
|
|
@@ -360,6 +472,38 @@ var StatusPickerWithIntl = /*#__PURE__*/function (_React$Component) {
|
|
|
360
472
|
role: "alert"
|
|
361
473
|
}, intl.formatMessage(_messages.statusMessages.statusPickerOpenedAlert)), (0, _react2.jsx)(_uiReact.OutsideClickTargetRefContext.Consumer, null, this.renderWithSetOutsideClickTargetRef.bind(this)));
|
|
362
474
|
}
|
|
475
|
+
}, {
|
|
476
|
+
key: "fireSuggestedStatusClickedAnalytics",
|
|
477
|
+
value: function fireSuggestedStatusClickedAnalytics(status, position) {
|
|
478
|
+
var _this$props$suggested, _this$props$suggested2;
|
|
479
|
+
this.createStatusAnalyticsAndFireFunc({
|
|
480
|
+
action: 'clicked',
|
|
481
|
+
actionSubject: 'statusSuggestion',
|
|
482
|
+
attributes: {
|
|
483
|
+
color: status.color,
|
|
484
|
+
localId: this.state.localId,
|
|
485
|
+
position: position,
|
|
486
|
+
state: (0, _analytics.analyticsState)(this.props.isNew),
|
|
487
|
+
textLength: status.text ? status.text.length : 0,
|
|
488
|
+
totalSuggestions: (_this$props$suggested = (_this$props$suggested2 = this.props.suggestedStatuses) === null || _this$props$suggested2 === void 0 ? void 0 : _this$props$suggested2.length) !== null && _this$props$suggested !== void 0 ? _this$props$suggested : 0
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}, {
|
|
493
|
+
key: "focusAdjacentSuggestedStatus",
|
|
494
|
+
value: function focusAdjacentSuggestedStatus(currentButton, direction) {
|
|
495
|
+
var _this$popupBodyWrappe2, _suggestedStatusButto;
|
|
496
|
+
var suggestedStatusButtons = (_this$popupBodyWrappe2 = this.popupBodyWrapper.current) === null || _this$popupBodyWrappe2 === void 0 ? void 0 : _this$popupBodyWrappe2.querySelectorAll('button[data-suggested-status-button]');
|
|
497
|
+
if (!(suggestedStatusButtons !== null && suggestedStatusButtons !== void 0 && suggestedStatusButtons.length)) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
var currentIndex = Array.from(suggestedStatusButtons).indexOf(currentButton);
|
|
501
|
+
if (currentIndex < 0) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
var nextIndex = (currentIndex + direction + suggestedStatusButtons.length) % suggestedStatusButtons.length;
|
|
505
|
+
(_suggestedStatusButto = suggestedStatusButtons[nextIndex]) === null || _suggestedStatusButto === void 0 || _suggestedStatusButto.focus();
|
|
506
|
+
}
|
|
363
507
|
}]);
|
|
364
508
|
}(_react.default.Component); // eslint-disable-next-line @typescript-eslint/ban-types
|
|
365
509
|
var StatusPickerWithoutAnalytcs = exports.StatusPickerWithoutAnalytcs = (0, _reactIntl.injectIntl)(StatusPickerWithIntl);
|
|
@@ -2,8 +2,11 @@ import React, { useCallback, useMemo } from 'react';
|
|
|
2
2
|
import { useSharedPluginState } from '@atlaskit/editor-common/hooks';
|
|
3
3
|
import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils';
|
|
4
4
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
5
|
+
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
5
6
|
import { commitStatusPicker, updateStatus } from '../pm-plugins/actions';
|
|
7
|
+
import { getSuggestedStatuses } from './getSuggestedStatuses';
|
|
6
8
|
import StatusPicker from './statusPicker';
|
|
9
|
+
/** Renders the status picker popup for the currently selected status node. */
|
|
7
10
|
export function ContentComponent({
|
|
8
11
|
api,
|
|
9
12
|
popupsMountPoint,
|
|
@@ -22,7 +25,9 @@ export function ContentComponent({
|
|
|
22
25
|
// Ignored via go/ees005
|
|
23
26
|
// eslint-disable-next-line @atlaskit/editor/no-as-casting
|
|
24
27
|
showStatusPickerAt ? findDomRefAtPos(showStatusPickerAt, domAtPos) : null, [showStatusPickerAt, domAtPos]);
|
|
25
|
-
const statusNode = useMemo(() => showStatusPickerAt ? editorView.state.doc.nodeAt(showStatusPickerAt) : undefined,
|
|
28
|
+
const statusNode = useMemo(() => showStatusPickerAt ? editorView.state.doc.nodeAt(showStatusPickerAt) : undefined,
|
|
29
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- preserve original recompute cadence for the ungated path
|
|
30
|
+
[showStatusPickerAt, editorView]);
|
|
26
31
|
const onSelect = useCallback(status => {
|
|
27
32
|
updateStatus(status)(editorView.state, editorView.dispatch);
|
|
28
33
|
}, [editorView]);
|
|
@@ -35,6 +40,52 @@ export function ContentComponent({
|
|
|
35
40
|
const onEnter = useCallback(() => {
|
|
36
41
|
commitStatusPicker()(editorView);
|
|
37
42
|
}, [editorView]);
|
|
43
|
+
const onSuggestedStatusClick = useCallback(status => {
|
|
44
|
+
var _editorView$state$doc;
|
|
45
|
+
const currentLocalId = typeof showStatusPickerAt === 'number' ? (_editorView$state$doc = editorView.state.doc.nodeAt(showStatusPickerAt)) === null || _editorView$state$doc === void 0 ? void 0 : _editorView$state$doc.attrs.localId : undefined;
|
|
46
|
+
const suggestedStatus = {
|
|
47
|
+
color: status.color,
|
|
48
|
+
...(currentLocalId ? {
|
|
49
|
+
localId: currentLocalId
|
|
50
|
+
} : {}),
|
|
51
|
+
...(status.style ? {
|
|
52
|
+
style: status.style
|
|
53
|
+
} : {}),
|
|
54
|
+
text: status.text
|
|
55
|
+
};
|
|
56
|
+
const didUpdateStatus = updateStatus(suggestedStatus)(editorView.state, editorView.dispatch);
|
|
57
|
+
if (didUpdateStatus) {
|
|
58
|
+
commitStatusPicker()(editorView);
|
|
59
|
+
}
|
|
60
|
+
}, [editorView, showStatusPickerAt]);
|
|
61
|
+
const suggestedStatuses = useMemo(() => {
|
|
62
|
+
if (typeof showStatusPickerAt !== 'number' || !expValEquals('platform_editor_status_popup_suggestions', 'isEnabled', true)) {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Read the node directly from the current doc so suggestions reflect the latest
|
|
67
|
+
// document state (the memoized `statusNode` intentionally preserves the original
|
|
68
|
+
// recompute cadence for the ungated path).
|
|
69
|
+
const currentNode = editorView.state.doc.nodeAt(showStatusPickerAt);
|
|
70
|
+
if (!currentNode || currentNode.type.name !== 'status') {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
const {
|
|
74
|
+
color,
|
|
75
|
+
localId,
|
|
76
|
+
text
|
|
77
|
+
} = currentNode.attrs;
|
|
78
|
+
return getSuggestedStatuses({
|
|
79
|
+
currentPos: showStatusPickerAt,
|
|
80
|
+
currentStatus: {
|
|
81
|
+
color,
|
|
82
|
+
localId,
|
|
83
|
+
text
|
|
84
|
+
},
|
|
85
|
+
doc: editorView.state.doc,
|
|
86
|
+
shouldUppercaseText: fg('platform-dst-lozenge-tag-badge-visual-uplifts')
|
|
87
|
+
});
|
|
88
|
+
}, [showStatusPickerAt, editorView.state.doc]);
|
|
38
89
|
if (typeof showStatusPickerAt !== 'number') {
|
|
39
90
|
return null;
|
|
40
91
|
}
|
|
@@ -62,7 +113,9 @@ export function ContentComponent({
|
|
|
62
113
|
onTextChanged: onTextChanged,
|
|
63
114
|
closeStatusPicker: closeStatusPicker,
|
|
64
115
|
onEnter: onEnter,
|
|
116
|
+
onSuggestedStatusClick: onSuggestedStatusClick,
|
|
65
117
|
editorView: editorView,
|
|
66
|
-
api: api
|
|
118
|
+
api: api,
|
|
119
|
+
suggestedStatuses: suggestedStatuses
|
|
67
120
|
});
|
|
68
121
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const MAX_SUGGESTED_STATUSES = 7;
|
|
2
|
+
const getStatusKey = ({
|
|
3
|
+
color,
|
|
4
|
+
text
|
|
5
|
+
}) => `${color}:${text.trim().toLowerCase()}`;
|
|
6
|
+
const getDisplayText = (status, shouldUppercaseText) => shouldUppercaseText && status.style !== 'mixedCase' ? status.text.toUpperCase() : status.text;
|
|
7
|
+
export const getSuggestedStatuses = ({
|
|
8
|
+
currentPos,
|
|
9
|
+
currentStatus,
|
|
10
|
+
doc,
|
|
11
|
+
limit = MAX_SUGGESTED_STATUSES,
|
|
12
|
+
shouldUppercaseText = false
|
|
13
|
+
}) => {
|
|
14
|
+
const closestStatusesByKey = new Map();
|
|
15
|
+
const currentStatusKey = getStatusKey(currentStatus);
|
|
16
|
+
doc.descendants((node, pos) => {
|
|
17
|
+
if (node.type.name !== 'status' || pos === currentPos) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const text = typeof node.attrs.text === 'string' ? node.attrs.text.trim() : '';
|
|
21
|
+
if (!text) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const status = {
|
|
25
|
+
color: node.attrs.color,
|
|
26
|
+
localId: node.attrs.localId,
|
|
27
|
+
style: node.attrs.style,
|
|
28
|
+
text
|
|
29
|
+
};
|
|
30
|
+
const statusKey = getStatusKey(status);
|
|
31
|
+
if (status.localId === currentStatus.localId || statusKey === currentStatusKey) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const existingStatus = closestStatusesByKey.get(statusKey);
|
|
35
|
+
const nextStatus = {
|
|
36
|
+
...status,
|
|
37
|
+
distance: Math.abs(pos - currentPos),
|
|
38
|
+
displayText: getDisplayText(status, shouldUppercaseText)
|
|
39
|
+
};
|
|
40
|
+
if (!existingStatus || nextStatus.distance < existingStatus.distance) {
|
|
41
|
+
closestStatusesByKey.set(statusKey, nextStatus);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return Array.from(closestStatusesByKey.values()).sort((statusA, statusB) => {
|
|
45
|
+
if (statusA.distance !== statusB.distance) {
|
|
46
|
+
return statusA.distance - statusB.distance;
|
|
47
|
+
}
|
|
48
|
+
if (statusA.displayText !== statusB.displayText) {
|
|
49
|
+
return statusA.displayText.localeCompare(statusB.displayText);
|
|
50
|
+
}
|
|
51
|
+
return statusA.color.localeCompare(statusB.color);
|
|
52
|
+
}).slice(0, limit).map(({
|
|
53
|
+
distance: _distance,
|
|
54
|
+
...status
|
|
55
|
+
}) => status);
|
|
56
|
+
};
|
|
@@ -17,7 +17,10 @@ import { OutsideClickTargetRefContext, withReactEditorViewOuterListeners as with
|
|
|
17
17
|
import { UserIntentPopupWrapper } from '@atlaskit/editor-common/user-intent';
|
|
18
18
|
import { akEditorFloatingDialogZIndex } from '@atlaskit/editor-shared-styles';
|
|
19
19
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
20
|
+
import { Status } from '@atlaskit/status/element';
|
|
20
21
|
import { StatusPicker as AkStatusPicker } from '@atlaskit/status/picker';
|
|
22
|
+
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
23
|
+
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
21
24
|
import VisuallyHidden from '@atlaskit/visually-hidden';
|
|
22
25
|
import { DEFAULT_STATUS } from '../pm-plugins/actions';
|
|
23
26
|
import { analyticsState, createStatusAnalyticsAndFire } from './analytics';
|
|
@@ -58,6 +61,27 @@ const pickerContainerStylesTeam26 = css({
|
|
|
58
61
|
outline: 'none'
|
|
59
62
|
}
|
|
60
63
|
});
|
|
64
|
+
const suggestedStatusesContainerStyles = css({
|
|
65
|
+
display: 'flex',
|
|
66
|
+
flexDirection: 'column',
|
|
67
|
+
gap: "var(--ds-space-100, 8px)",
|
|
68
|
+
margin: 0,
|
|
69
|
+
padding: `${"var(--ds-space-100, 8px)"} ${"var(--ds-space-150, 12px)"} ${"var(--ds-space-050, 4px)"}`
|
|
70
|
+
});
|
|
71
|
+
const suggestedStatusButtonStyles = css({
|
|
72
|
+
background: 'transparent',
|
|
73
|
+
border: 0,
|
|
74
|
+
borderRadius: "var(--ds-radius-small, 3px)",
|
|
75
|
+
cursor: 'pointer',
|
|
76
|
+
display: 'flex',
|
|
77
|
+
justifyContent: 'flex-start',
|
|
78
|
+
padding: 0,
|
|
79
|
+
width: '100%',
|
|
80
|
+
'&:focus-visible': {
|
|
81
|
+
outline: `2px solid ${"var(--ds-border-focused, #0C66E4)"}`,
|
|
82
|
+
outlineOffset: "var(--ds-space-025, 2px)"
|
|
83
|
+
}
|
|
84
|
+
});
|
|
61
85
|
|
|
62
86
|
// eslint-disable-next-line @repo/internal/react/no-class-components
|
|
63
87
|
class StatusPickerWithIntl extends React.Component {
|
|
@@ -77,18 +101,59 @@ class StatusPickerWithIntl extends React.Component {
|
|
|
77
101
|
this.props.onEnter(this.state);
|
|
78
102
|
});
|
|
79
103
|
_defineProperty(this, "handleTabPress", event => {
|
|
80
|
-
var
|
|
81
|
-
|
|
104
|
+
var _getDocument2;
|
|
105
|
+
if (!expValEqualsNoExposure('platform_editor_status_popup_suggestions', 'isEnabled', true)) {
|
|
106
|
+
var _getDocument;
|
|
107
|
+
/* original tab-navigation behaviour: cycle between color buttons and the input field */
|
|
108
|
+
const colorButtons = event.currentTarget.querySelectorAll('button');
|
|
109
|
+
const inputField = event.currentTarget.querySelector('input');
|
|
110
|
+
const activeElement = (_getDocument = getDocument()) === null || _getDocument === void 0 ? void 0 : _getDocument.activeElement;
|
|
111
|
+
const isInputFocussed = activeElement === inputField;
|
|
112
|
+
const isButtonFocussed = Array.from(colorButtons).some(buttonElement => {
|
|
113
|
+
return activeElement === buttonElement;
|
|
114
|
+
});
|
|
115
|
+
if (event !== null && event !== void 0 && event.shiftKey) {
|
|
116
|
+
/* shift + tab */
|
|
117
|
+
if (isInputFocussed) {
|
|
118
|
+
colorButtons[0].focus();
|
|
119
|
+
event.preventDefault();
|
|
120
|
+
}
|
|
121
|
+
/* After the user presses shift + tab the color-palette component updates tab index for the first color to be 0.
|
|
122
|
+
To correctly set focus to the input field instead of the first color button we need to set focus manually
|
|
123
|
+
*/
|
|
124
|
+
if (isButtonFocussed) {
|
|
125
|
+
inputField === null || inputField === void 0 ? void 0 : inputField.focus();
|
|
126
|
+
event.preventDefault();
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
/* tab */
|
|
130
|
+
if (isButtonFocussed) {
|
|
131
|
+
inputField === null || inputField === void 0 ? void 0 : inputField.focus();
|
|
132
|
+
event.preventDefault();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const colorButtons = event.currentTarget.querySelectorAll('button:not([data-suggested-status-button])');
|
|
138
|
+
const suggestedStatusButtons = event.currentTarget.querySelectorAll('button[data-suggested-status-button]');
|
|
82
139
|
const inputField = event.currentTarget.querySelector('input');
|
|
83
|
-
const activeElement = (
|
|
140
|
+
const activeElement = (_getDocument2 = getDocument()) === null || _getDocument2 === void 0 ? void 0 : _getDocument2.activeElement;
|
|
84
141
|
const isInputFocussed = activeElement === inputField;
|
|
85
142
|
const isButtonFocussed = Array.from(colorButtons).some(buttonElement => {
|
|
86
143
|
return activeElement === buttonElement;
|
|
87
144
|
});
|
|
145
|
+
const isSuggestedStatusButtonFocussed = Array.from(suggestedStatusButtons).some(buttonElement => {
|
|
146
|
+
return activeElement === buttonElement;
|
|
147
|
+
});
|
|
88
148
|
if (event !== null && event !== void 0 && event.shiftKey) {
|
|
89
149
|
/* shift + tab */
|
|
90
150
|
if (isInputFocussed) {
|
|
91
|
-
|
|
151
|
+
if (suggestedStatusButtons.length > 0) {
|
|
152
|
+
suggestedStatusButtons[suggestedStatusButtons.length - 1].focus();
|
|
153
|
+
} else {
|
|
154
|
+
var _colorButtons$;
|
|
155
|
+
(_colorButtons$ = colorButtons[0]) === null || _colorButtons$ === void 0 ? void 0 : _colorButtons$.focus();
|
|
156
|
+
}
|
|
92
157
|
event.preventDefault();
|
|
93
158
|
}
|
|
94
159
|
/* After the user presses shift + tab the color-palette component updates tab index for the first color to be 0.
|
|
@@ -98,17 +163,30 @@ class StatusPickerWithIntl extends React.Component {
|
|
|
98
163
|
inputField === null || inputField === void 0 ? void 0 : inputField.focus();
|
|
99
164
|
event.preventDefault();
|
|
100
165
|
}
|
|
166
|
+
if (isSuggestedStatusButtonFocussed) {
|
|
167
|
+
var _colorButtons;
|
|
168
|
+
(_colorButtons = colorButtons[colorButtons.length - 1]) === null || _colorButtons === void 0 ? void 0 : _colorButtons.focus();
|
|
169
|
+
event.preventDefault();
|
|
170
|
+
}
|
|
101
171
|
} else {
|
|
102
172
|
/* tab */
|
|
103
173
|
if (isButtonFocussed) {
|
|
174
|
+
if (suggestedStatusButtons.length > 0) {
|
|
175
|
+
suggestedStatusButtons[0].focus();
|
|
176
|
+
} else {
|
|
177
|
+
inputField === null || inputField === void 0 ? void 0 : inputField.focus();
|
|
178
|
+
}
|
|
179
|
+
event.preventDefault();
|
|
180
|
+
}
|
|
181
|
+
if (isSuggestedStatusButtonFocussed) {
|
|
104
182
|
inputField === null || inputField === void 0 ? void 0 : inputField.focus();
|
|
105
183
|
event.preventDefault();
|
|
106
184
|
}
|
|
107
185
|
}
|
|
108
186
|
});
|
|
109
187
|
_defineProperty(this, "handleArrow", (event, closingMethod) => {
|
|
110
|
-
var
|
|
111
|
-
const activeElement = (
|
|
188
|
+
var _getDocument3;
|
|
189
|
+
const activeElement = (_getDocument3 = getDocument()) === null || _getDocument3 === void 0 ? void 0 : _getDocument3.activeElement;
|
|
112
190
|
if (activeElement === this.popupBodyWrapper.current) {
|
|
113
191
|
var _this$popupBodyWrappe, _this$popupBodyWrappe2;
|
|
114
192
|
event.preventDefault();
|
|
@@ -184,6 +262,19 @@ class StatusPickerWithIntl extends React.Component {
|
|
|
184
262
|
this.inputMethod = InputMethod.enterKey;
|
|
185
263
|
this.props.onEnter(this.state);
|
|
186
264
|
});
|
|
265
|
+
_defineProperty(this, "onSuggestedStatusClick", (status, position) => {
|
|
266
|
+
this.fireSuggestedStatusClickedAnalytics(status, position);
|
|
267
|
+
this.props.onSuggestedStatusClick(status);
|
|
268
|
+
});
|
|
269
|
+
_defineProperty(this, "handleSuggestedStatusKeyDown", event => {
|
|
270
|
+
// Selection (Enter/Space) is handled natively via the button's onClick.
|
|
271
|
+
// Keydown only drives ArrowUp/ArrowDown navigation between suggestions.
|
|
272
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
event.stopPropagation();
|
|
275
|
+
this.focusAdjacentSuggestedStatus(event.currentTarget, event.key === 'ArrowDown' ? 1 : -1);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
187
278
|
// cancel bubbling to fix clickOutside logic:
|
|
188
279
|
// popup re-renders its content before the click event bubbles up to the document
|
|
189
280
|
// therefore click target element would be different from the popup content
|
|
@@ -278,7 +369,8 @@ class StatusPickerWithIntl extends React.Component {
|
|
|
278
369
|
const {
|
|
279
370
|
isNew,
|
|
280
371
|
focusStatusInput,
|
|
281
|
-
api
|
|
372
|
+
api,
|
|
373
|
+
suggestedStatuses
|
|
282
374
|
} = this.props;
|
|
283
375
|
const {
|
|
284
376
|
color,
|
|
@@ -301,7 +393,22 @@ class StatusPickerWithIntl extends React.Component {
|
|
|
301
393
|
onColorHover: this.onColorHover,
|
|
302
394
|
onTextChanged: this.onTextChanged,
|
|
303
395
|
onEnter: this.onEnter
|
|
304
|
-
}))
|
|
396
|
+
}), suggestedStatuses !== null && suggestedStatuses !== void 0 && suggestedStatuses.length && expValEquals('platform_editor_status_popup_suggestions', 'isEnabled', true) ? jsx("div", {
|
|
397
|
+
css: suggestedStatusesContainerStyles,
|
|
398
|
+
"data-suggested-status-list": true
|
|
399
|
+
}, suggestedStatuses.map((suggestedStatus, index) => jsx("div", {
|
|
400
|
+
key: `${suggestedStatus.color}:${suggestedStatus.text}`
|
|
401
|
+
}, jsx("button", {
|
|
402
|
+
type: "button",
|
|
403
|
+
css: suggestedStatusButtonStyles,
|
|
404
|
+
onClick: () => this.onSuggestedStatusClick(suggestedStatus, index + 1),
|
|
405
|
+
onKeyDown: this.handleSuggestedStatusKeyDown,
|
|
406
|
+
"aria-label": suggestedStatus.displayText,
|
|
407
|
+
"data-suggested-status-button": true
|
|
408
|
+
}, jsx(Status, {
|
|
409
|
+
color: suggestedStatus.color,
|
|
410
|
+
text: suggestedStatus.displayText
|
|
411
|
+
}))))) : null));
|
|
305
412
|
}
|
|
306
413
|
render() {
|
|
307
414
|
const {
|
|
@@ -334,6 +441,34 @@ class StatusPickerWithIntl extends React.Component {
|
|
|
334
441
|
role: "alert"
|
|
335
442
|
}, intl.formatMessage(messages.statusPickerOpenedAlert)), jsx(OutsideClickTargetRefContext.Consumer, null, this.renderWithSetOutsideClickTargetRef.bind(this)));
|
|
336
443
|
}
|
|
444
|
+
fireSuggestedStatusClickedAnalytics(status, position) {
|
|
445
|
+
var _this$props$suggested, _this$props$suggested2;
|
|
446
|
+
this.createStatusAnalyticsAndFireFunc({
|
|
447
|
+
action: 'clicked',
|
|
448
|
+
actionSubject: 'statusSuggestion',
|
|
449
|
+
attributes: {
|
|
450
|
+
color: status.color,
|
|
451
|
+
localId: this.state.localId,
|
|
452
|
+
position,
|
|
453
|
+
state: analyticsState(this.props.isNew),
|
|
454
|
+
textLength: status.text ? status.text.length : 0,
|
|
455
|
+
totalSuggestions: (_this$props$suggested = (_this$props$suggested2 = this.props.suggestedStatuses) === null || _this$props$suggested2 === void 0 ? void 0 : _this$props$suggested2.length) !== null && _this$props$suggested !== void 0 ? _this$props$suggested : 0
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
focusAdjacentSuggestedStatus(currentButton, direction) {
|
|
460
|
+
var _this$popupBodyWrappe3, _suggestedStatusButto;
|
|
461
|
+
const suggestedStatusButtons = (_this$popupBodyWrappe3 = this.popupBodyWrapper.current) === null || _this$popupBodyWrappe3 === void 0 ? void 0 : _this$popupBodyWrappe3.querySelectorAll('button[data-suggested-status-button]');
|
|
462
|
+
if (!(suggestedStatusButtons !== null && suggestedStatusButtons !== void 0 && suggestedStatusButtons.length)) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const currentIndex = Array.from(suggestedStatusButtons).indexOf(currentButton);
|
|
466
|
+
if (currentIndex < 0) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const nextIndex = (currentIndex + direction + suggestedStatusButtons.length) % suggestedStatusButtons.length;
|
|
470
|
+
(_suggestedStatusButto = suggestedStatusButtons[nextIndex]) === null || _suggestedStatusButto === void 0 ? void 0 : _suggestedStatusButto.focus();
|
|
471
|
+
}
|
|
337
472
|
}
|
|
338
473
|
|
|
339
474
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
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; }
|
|
3
|
+
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; }
|
|
1
4
|
import React, { useCallback, useMemo } from 'react';
|
|
2
5
|
import { useSharedPluginState } from '@atlaskit/editor-common/hooks';
|
|
3
6
|
import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils';
|
|
4
7
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
8
|
+
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
5
9
|
import { commitStatusPicker, updateStatus } from '../pm-plugins/actions';
|
|
10
|
+
import { getSuggestedStatuses } from './getSuggestedStatuses';
|
|
6
11
|
import StatusPicker from './statusPicker';
|
|
12
|
+
/** Renders the status picker popup for the currently selected status node. */
|
|
7
13
|
export function ContentComponent(_ref) {
|
|
8
14
|
var api = _ref.api,
|
|
9
15
|
popupsMountPoint = _ref.popupsMountPoint,
|
|
@@ -24,7 +30,9 @@ export function ContentComponent(_ref) {
|
|
|
24
30
|
}, [showStatusPickerAt, domAtPos]);
|
|
25
31
|
var statusNode = useMemo(function () {
|
|
26
32
|
return showStatusPickerAt ? editorView.state.doc.nodeAt(showStatusPickerAt) : undefined;
|
|
27
|
-
},
|
|
33
|
+
},
|
|
34
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- preserve original recompute cadence for the ungated path
|
|
35
|
+
[showStatusPickerAt, editorView]);
|
|
28
36
|
var onSelect = useCallback(function (status) {
|
|
29
37
|
updateStatus(status)(editorView.state, editorView.dispatch);
|
|
30
38
|
}, [editorView]);
|
|
@@ -37,6 +45,50 @@ export function ContentComponent(_ref) {
|
|
|
37
45
|
var onEnter = useCallback(function () {
|
|
38
46
|
commitStatusPicker()(editorView);
|
|
39
47
|
}, [editorView]);
|
|
48
|
+
var onSuggestedStatusClick = useCallback(function (status) {
|
|
49
|
+
var _editorView$state$doc;
|
|
50
|
+
var currentLocalId = typeof showStatusPickerAt === 'number' ? (_editorView$state$doc = editorView.state.doc.nodeAt(showStatusPickerAt)) === null || _editorView$state$doc === void 0 ? void 0 : _editorView$state$doc.attrs.localId : undefined;
|
|
51
|
+
var suggestedStatus = _objectSpread(_objectSpread(_objectSpread({
|
|
52
|
+
color: status.color
|
|
53
|
+
}, currentLocalId ? {
|
|
54
|
+
localId: currentLocalId
|
|
55
|
+
} : {}), status.style ? {
|
|
56
|
+
style: status.style
|
|
57
|
+
} : {}), {}, {
|
|
58
|
+
text: status.text
|
|
59
|
+
});
|
|
60
|
+
var didUpdateStatus = updateStatus(suggestedStatus)(editorView.state, editorView.dispatch);
|
|
61
|
+
if (didUpdateStatus) {
|
|
62
|
+
commitStatusPicker()(editorView);
|
|
63
|
+
}
|
|
64
|
+
}, [editorView, showStatusPickerAt]);
|
|
65
|
+
var suggestedStatuses = useMemo(function () {
|
|
66
|
+
if (typeof showStatusPickerAt !== 'number' || !expValEquals('platform_editor_status_popup_suggestions', 'isEnabled', true)) {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Read the node directly from the current doc so suggestions reflect the latest
|
|
71
|
+
// document state (the memoized `statusNode` intentionally preserves the original
|
|
72
|
+
// recompute cadence for the ungated path).
|
|
73
|
+
var currentNode = editorView.state.doc.nodeAt(showStatusPickerAt);
|
|
74
|
+
if (!currentNode || currentNode.type.name !== 'status') {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
var _currentNode$attrs = currentNode.attrs,
|
|
78
|
+
color = _currentNode$attrs.color,
|
|
79
|
+
localId = _currentNode$attrs.localId,
|
|
80
|
+
text = _currentNode$attrs.text;
|
|
81
|
+
return getSuggestedStatuses({
|
|
82
|
+
currentPos: showStatusPickerAt,
|
|
83
|
+
currentStatus: {
|
|
84
|
+
color: color,
|
|
85
|
+
localId: localId,
|
|
86
|
+
text: text
|
|
87
|
+
},
|
|
88
|
+
doc: editorView.state.doc,
|
|
89
|
+
shouldUppercaseText: fg('platform-dst-lozenge-tag-badge-visual-uplifts')
|
|
90
|
+
});
|
|
91
|
+
}, [showStatusPickerAt, editorView.state.doc]);
|
|
40
92
|
if (typeof showStatusPickerAt !== 'number') {
|
|
41
93
|
return null;
|
|
42
94
|
}
|
|
@@ -63,7 +115,9 @@ export function ContentComponent(_ref) {
|
|
|
63
115
|
onTextChanged: onTextChanged,
|
|
64
116
|
closeStatusPicker: closeStatusPicker,
|
|
65
117
|
onEnter: onEnter,
|
|
118
|
+
onSuggestedStatusClick: onSuggestedStatusClick,
|
|
66
119
|
editorView: editorView,
|
|
67
|
-
api: api
|
|
120
|
+
api: api,
|
|
121
|
+
suggestedStatuses: suggestedStatuses
|
|
68
122
|
});
|
|
69
123
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
|
|
2
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
3
|
+
var _excluded = ["distance"];
|
|
4
|
+
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; }
|
|
5
|
+
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; }
|
|
6
|
+
export var MAX_SUGGESTED_STATUSES = 7;
|
|
7
|
+
var getStatusKey = function getStatusKey(_ref) {
|
|
8
|
+
var color = _ref.color,
|
|
9
|
+
text = _ref.text;
|
|
10
|
+
return "".concat(color, ":").concat(text.trim().toLowerCase());
|
|
11
|
+
};
|
|
12
|
+
var getDisplayText = function getDisplayText(status, shouldUppercaseText) {
|
|
13
|
+
return shouldUppercaseText && status.style !== 'mixedCase' ? status.text.toUpperCase() : status.text;
|
|
14
|
+
};
|
|
15
|
+
export var getSuggestedStatuses = function getSuggestedStatuses(_ref2) {
|
|
16
|
+
var currentPos = _ref2.currentPos,
|
|
17
|
+
currentStatus = _ref2.currentStatus,
|
|
18
|
+
doc = _ref2.doc,
|
|
19
|
+
_ref2$limit = _ref2.limit,
|
|
20
|
+
limit = _ref2$limit === void 0 ? MAX_SUGGESTED_STATUSES : _ref2$limit,
|
|
21
|
+
_ref2$shouldUppercase = _ref2.shouldUppercaseText,
|
|
22
|
+
shouldUppercaseText = _ref2$shouldUppercase === void 0 ? false : _ref2$shouldUppercase;
|
|
23
|
+
var closestStatusesByKey = new Map();
|
|
24
|
+
var currentStatusKey = getStatusKey(currentStatus);
|
|
25
|
+
doc.descendants(function (node, pos) {
|
|
26
|
+
if (node.type.name !== 'status' || pos === currentPos) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
var text = typeof node.attrs.text === 'string' ? node.attrs.text.trim() : '';
|
|
30
|
+
if (!text) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
var status = {
|
|
34
|
+
color: node.attrs.color,
|
|
35
|
+
localId: node.attrs.localId,
|
|
36
|
+
style: node.attrs.style,
|
|
37
|
+
text: text
|
|
38
|
+
};
|
|
39
|
+
var statusKey = getStatusKey(status);
|
|
40
|
+
if (status.localId === currentStatus.localId || statusKey === currentStatusKey) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
var existingStatus = closestStatusesByKey.get(statusKey);
|
|
44
|
+
var nextStatus = _objectSpread(_objectSpread({}, status), {}, {
|
|
45
|
+
distance: Math.abs(pos - currentPos),
|
|
46
|
+
displayText: getDisplayText(status, shouldUppercaseText)
|
|
47
|
+
});
|
|
48
|
+
if (!existingStatus || nextStatus.distance < existingStatus.distance) {
|
|
49
|
+
closestStatusesByKey.set(statusKey, nextStatus);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return Array.from(closestStatusesByKey.values()).sort(function (statusA, statusB) {
|
|
53
|
+
if (statusA.distance !== statusB.distance) {
|
|
54
|
+
return statusA.distance - statusB.distance;
|
|
55
|
+
}
|
|
56
|
+
if (statusA.displayText !== statusB.displayText) {
|
|
57
|
+
return statusA.displayText.localeCompare(statusB.displayText);
|
|
58
|
+
}
|
|
59
|
+
return statusA.color.localeCompare(statusB.color);
|
|
60
|
+
}).slice(0, limit).map(function (_ref3) {
|
|
61
|
+
var _distance = _ref3.distance,
|
|
62
|
+
status = _objectWithoutProperties(_ref3, _excluded);
|
|
63
|
+
return status;
|
|
64
|
+
});
|
|
65
|
+
};
|
|
@@ -24,7 +24,10 @@ import { OutsideClickTargetRefContext, withReactEditorViewOuterListeners as with
|
|
|
24
24
|
import { UserIntentPopupWrapper } from '@atlaskit/editor-common/user-intent';
|
|
25
25
|
import { akEditorFloatingDialogZIndex } from '@atlaskit/editor-shared-styles';
|
|
26
26
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
27
|
+
import { Status } from '@atlaskit/status/element';
|
|
27
28
|
import { StatusPicker as AkStatusPicker } from '@atlaskit/status/picker';
|
|
29
|
+
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
30
|
+
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
28
31
|
import VisuallyHidden from '@atlaskit/visually-hidden';
|
|
29
32
|
import { DEFAULT_STATUS } from '../pm-plugins/actions';
|
|
30
33
|
import { analyticsState, createStatusAnalyticsAndFire } from './analytics';
|
|
@@ -65,6 +68,27 @@ var pickerContainerStylesTeam26 = css({
|
|
|
65
68
|
outline: 'none'
|
|
66
69
|
}
|
|
67
70
|
});
|
|
71
|
+
var suggestedStatusesContainerStyles = css({
|
|
72
|
+
display: 'flex',
|
|
73
|
+
flexDirection: 'column',
|
|
74
|
+
gap: "var(--ds-space-100, 8px)",
|
|
75
|
+
margin: 0,
|
|
76
|
+
padding: "var(--ds-space-100, 8px)".concat(" ", "var(--ds-space-150, 12px)", " ", "var(--ds-space-050, 4px)")
|
|
77
|
+
});
|
|
78
|
+
var suggestedStatusButtonStyles = css({
|
|
79
|
+
background: 'transparent',
|
|
80
|
+
border: 0,
|
|
81
|
+
borderRadius: "var(--ds-radius-small, 3px)",
|
|
82
|
+
cursor: 'pointer',
|
|
83
|
+
display: 'flex',
|
|
84
|
+
justifyContent: 'flex-start',
|
|
85
|
+
padding: 0,
|
|
86
|
+
width: '100%',
|
|
87
|
+
'&:focus-visible': {
|
|
88
|
+
outline: "2px solid ".concat("var(--ds-border-focused, #0C66E4)"),
|
|
89
|
+
outlineOffset: "var(--ds-space-025, 2px)"
|
|
90
|
+
}
|
|
91
|
+
});
|
|
68
92
|
|
|
69
93
|
// eslint-disable-next-line @repo/internal/react/no-class-components
|
|
70
94
|
var StatusPickerWithIntl = /*#__PURE__*/function (_React$Component) {
|
|
@@ -86,18 +110,59 @@ var StatusPickerWithIntl = /*#__PURE__*/function (_React$Component) {
|
|
|
86
110
|
_this.props.onEnter(_this.state);
|
|
87
111
|
});
|
|
88
112
|
_defineProperty(_this, "handleTabPress", function (event) {
|
|
89
|
-
var
|
|
90
|
-
|
|
113
|
+
var _getDocument2;
|
|
114
|
+
if (!expValEqualsNoExposure('platform_editor_status_popup_suggestions', 'isEnabled', true)) {
|
|
115
|
+
var _getDocument;
|
|
116
|
+
/* original tab-navigation behaviour: cycle between color buttons and the input field */
|
|
117
|
+
var _colorButtons = event.currentTarget.querySelectorAll('button');
|
|
118
|
+
var _inputField = event.currentTarget.querySelector('input');
|
|
119
|
+
var _activeElement = (_getDocument = getDocument()) === null || _getDocument === void 0 ? void 0 : _getDocument.activeElement;
|
|
120
|
+
var _isInputFocussed = _activeElement === _inputField;
|
|
121
|
+
var _isButtonFocussed = Array.from(_colorButtons).some(function (buttonElement) {
|
|
122
|
+
return _activeElement === buttonElement;
|
|
123
|
+
});
|
|
124
|
+
if (event !== null && event !== void 0 && event.shiftKey) {
|
|
125
|
+
/* shift + tab */
|
|
126
|
+
if (_isInputFocussed) {
|
|
127
|
+
_colorButtons[0].focus();
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
}
|
|
130
|
+
/* After the user presses shift + tab the color-palette component updates tab index for the first color to be 0.
|
|
131
|
+
To correctly set focus to the input field instead of the first color button we need to set focus manually
|
|
132
|
+
*/
|
|
133
|
+
if (_isButtonFocussed) {
|
|
134
|
+
_inputField === null || _inputField === void 0 || _inputField.focus();
|
|
135
|
+
event.preventDefault();
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
/* tab */
|
|
139
|
+
if (_isButtonFocussed) {
|
|
140
|
+
_inputField === null || _inputField === void 0 || _inputField.focus();
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
var colorButtons = event.currentTarget.querySelectorAll('button:not([data-suggested-status-button])');
|
|
147
|
+
var suggestedStatusButtons = event.currentTarget.querySelectorAll('button[data-suggested-status-button]');
|
|
91
148
|
var inputField = event.currentTarget.querySelector('input');
|
|
92
|
-
var activeElement = (
|
|
149
|
+
var activeElement = (_getDocument2 = getDocument()) === null || _getDocument2 === void 0 ? void 0 : _getDocument2.activeElement;
|
|
93
150
|
var isInputFocussed = activeElement === inputField;
|
|
94
151
|
var isButtonFocussed = Array.from(colorButtons).some(function (buttonElement) {
|
|
95
152
|
return activeElement === buttonElement;
|
|
96
153
|
});
|
|
154
|
+
var isSuggestedStatusButtonFocussed = Array.from(suggestedStatusButtons).some(function (buttonElement) {
|
|
155
|
+
return activeElement === buttonElement;
|
|
156
|
+
});
|
|
97
157
|
if (event !== null && event !== void 0 && event.shiftKey) {
|
|
98
158
|
/* shift + tab */
|
|
99
159
|
if (isInputFocussed) {
|
|
100
|
-
|
|
160
|
+
if (suggestedStatusButtons.length > 0) {
|
|
161
|
+
suggestedStatusButtons[suggestedStatusButtons.length - 1].focus();
|
|
162
|
+
} else {
|
|
163
|
+
var _colorButtons$;
|
|
164
|
+
(_colorButtons$ = colorButtons[0]) === null || _colorButtons$ === void 0 || _colorButtons$.focus();
|
|
165
|
+
}
|
|
101
166
|
event.preventDefault();
|
|
102
167
|
}
|
|
103
168
|
/* After the user presses shift + tab the color-palette component updates tab index for the first color to be 0.
|
|
@@ -107,17 +172,30 @@ var StatusPickerWithIntl = /*#__PURE__*/function (_React$Component) {
|
|
|
107
172
|
inputField === null || inputField === void 0 || inputField.focus();
|
|
108
173
|
event.preventDefault();
|
|
109
174
|
}
|
|
175
|
+
if (isSuggestedStatusButtonFocussed) {
|
|
176
|
+
var _colorButtons2;
|
|
177
|
+
(_colorButtons2 = colorButtons[colorButtons.length - 1]) === null || _colorButtons2 === void 0 || _colorButtons2.focus();
|
|
178
|
+
event.preventDefault();
|
|
179
|
+
}
|
|
110
180
|
} else {
|
|
111
181
|
/* tab */
|
|
112
182
|
if (isButtonFocussed) {
|
|
183
|
+
if (suggestedStatusButtons.length > 0) {
|
|
184
|
+
suggestedStatusButtons[0].focus();
|
|
185
|
+
} else {
|
|
186
|
+
inputField === null || inputField === void 0 || inputField.focus();
|
|
187
|
+
}
|
|
188
|
+
event.preventDefault();
|
|
189
|
+
}
|
|
190
|
+
if (isSuggestedStatusButtonFocussed) {
|
|
113
191
|
inputField === null || inputField === void 0 || inputField.focus();
|
|
114
192
|
event.preventDefault();
|
|
115
193
|
}
|
|
116
194
|
}
|
|
117
195
|
});
|
|
118
196
|
_defineProperty(_this, "handleArrow", function (event, closingMethod) {
|
|
119
|
-
var
|
|
120
|
-
var activeElement = (
|
|
197
|
+
var _getDocument3;
|
|
198
|
+
var activeElement = (_getDocument3 = getDocument()) === null || _getDocument3 === void 0 ? void 0 : _getDocument3.activeElement;
|
|
121
199
|
if (activeElement === _this.popupBodyWrapper.current) {
|
|
122
200
|
var _this$popupBodyWrappe;
|
|
123
201
|
event.preventDefault();
|
|
@@ -191,6 +269,19 @@ var StatusPickerWithIntl = /*#__PURE__*/function (_React$Component) {
|
|
|
191
269
|
_this.inputMethod = InputMethod.enterKey;
|
|
192
270
|
_this.props.onEnter(_this.state);
|
|
193
271
|
});
|
|
272
|
+
_defineProperty(_this, "onSuggestedStatusClick", function (status, position) {
|
|
273
|
+
_this.fireSuggestedStatusClickedAnalytics(status, position);
|
|
274
|
+
_this.props.onSuggestedStatusClick(status);
|
|
275
|
+
});
|
|
276
|
+
_defineProperty(_this, "handleSuggestedStatusKeyDown", function (event) {
|
|
277
|
+
// Selection (Enter/Space) is handled natively via the button's onClick.
|
|
278
|
+
// Keydown only drives ArrowUp/ArrowDown navigation between suggestions.
|
|
279
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
280
|
+
event.preventDefault();
|
|
281
|
+
event.stopPropagation();
|
|
282
|
+
_this.focusAdjacentSuggestedStatus(event.currentTarget, event.key === 'ArrowDown' ? 1 : -1);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
194
285
|
// cancel bubbling to fix clickOutside logic:
|
|
195
286
|
// popup re-renders its content before the click event bubbles up to the document
|
|
196
287
|
// therefore click target element would be different from the popup content
|
|
@@ -299,10 +390,12 @@ var StatusPickerWithIntl = /*#__PURE__*/function (_React$Component) {
|
|
|
299
390
|
}, {
|
|
300
391
|
key: "renderWithSetOutsideClickTargetRef",
|
|
301
392
|
value: function renderWithSetOutsideClickTargetRef(setOutsideClickTargetRef) {
|
|
393
|
+
var _this3 = this;
|
|
302
394
|
var _this$props = this.props,
|
|
303
395
|
isNew = _this$props.isNew,
|
|
304
396
|
focusStatusInput = _this$props.focusStatusInput,
|
|
305
|
-
api = _this$props.api
|
|
397
|
+
api = _this$props.api,
|
|
398
|
+
suggestedStatuses = _this$props.suggestedStatuses;
|
|
306
399
|
var _this$state3 = this.state,
|
|
307
400
|
color = _this$state3.color,
|
|
308
401
|
text = _this$state3.text;
|
|
@@ -323,7 +416,26 @@ var StatusPickerWithIntl = /*#__PURE__*/function (_React$Component) {
|
|
|
323
416
|
onColorHover: this.onColorHover,
|
|
324
417
|
onTextChanged: this.onTextChanged,
|
|
325
418
|
onEnter: this.onEnter
|
|
326
|
-
}))
|
|
419
|
+
}), suggestedStatuses !== null && suggestedStatuses !== void 0 && suggestedStatuses.length && expValEquals('platform_editor_status_popup_suggestions', 'isEnabled', true) ? jsx("div", {
|
|
420
|
+
css: suggestedStatusesContainerStyles,
|
|
421
|
+
"data-suggested-status-list": true
|
|
422
|
+
}, suggestedStatuses.map(function (suggestedStatus, index) {
|
|
423
|
+
return jsx("div", {
|
|
424
|
+
key: "".concat(suggestedStatus.color, ":").concat(suggestedStatus.text)
|
|
425
|
+
}, jsx("button", {
|
|
426
|
+
type: "button",
|
|
427
|
+
css: suggestedStatusButtonStyles,
|
|
428
|
+
onClick: function onClick() {
|
|
429
|
+
return _this3.onSuggestedStatusClick(suggestedStatus, index + 1);
|
|
430
|
+
},
|
|
431
|
+
onKeyDown: _this3.handleSuggestedStatusKeyDown,
|
|
432
|
+
"aria-label": suggestedStatus.displayText,
|
|
433
|
+
"data-suggested-status-button": true
|
|
434
|
+
}, jsx(Status, {
|
|
435
|
+
color: suggestedStatus.color,
|
|
436
|
+
text: suggestedStatus.displayText
|
|
437
|
+
})));
|
|
438
|
+
})) : null));
|
|
327
439
|
}
|
|
328
440
|
}, {
|
|
329
441
|
key: "render",
|
|
@@ -357,6 +469,38 @@ var StatusPickerWithIntl = /*#__PURE__*/function (_React$Component) {
|
|
|
357
469
|
role: "alert"
|
|
358
470
|
}, intl.formatMessage(messages.statusPickerOpenedAlert)), jsx(OutsideClickTargetRefContext.Consumer, null, this.renderWithSetOutsideClickTargetRef.bind(this)));
|
|
359
471
|
}
|
|
472
|
+
}, {
|
|
473
|
+
key: "fireSuggestedStatusClickedAnalytics",
|
|
474
|
+
value: function fireSuggestedStatusClickedAnalytics(status, position) {
|
|
475
|
+
var _this$props$suggested, _this$props$suggested2;
|
|
476
|
+
this.createStatusAnalyticsAndFireFunc({
|
|
477
|
+
action: 'clicked',
|
|
478
|
+
actionSubject: 'statusSuggestion',
|
|
479
|
+
attributes: {
|
|
480
|
+
color: status.color,
|
|
481
|
+
localId: this.state.localId,
|
|
482
|
+
position: position,
|
|
483
|
+
state: analyticsState(this.props.isNew),
|
|
484
|
+
textLength: status.text ? status.text.length : 0,
|
|
485
|
+
totalSuggestions: (_this$props$suggested = (_this$props$suggested2 = this.props.suggestedStatuses) === null || _this$props$suggested2 === void 0 ? void 0 : _this$props$suggested2.length) !== null && _this$props$suggested !== void 0 ? _this$props$suggested : 0
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}, {
|
|
490
|
+
key: "focusAdjacentSuggestedStatus",
|
|
491
|
+
value: function focusAdjacentSuggestedStatus(currentButton, direction) {
|
|
492
|
+
var _this$popupBodyWrappe2, _suggestedStatusButto;
|
|
493
|
+
var suggestedStatusButtons = (_this$popupBodyWrappe2 = this.popupBodyWrapper.current) === null || _this$popupBodyWrappe2 === void 0 ? void 0 : _this$popupBodyWrappe2.querySelectorAll('button[data-suggested-status-button]');
|
|
494
|
+
if (!(suggestedStatusButtons !== null && suggestedStatusButtons !== void 0 && suggestedStatusButtons.length)) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
var currentIndex = Array.from(suggestedStatusButtons).indexOf(currentButton);
|
|
498
|
+
if (currentIndex < 0) {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
var nextIndex = (currentIndex + direction + suggestedStatusButtons.length) % suggestedStatusButtons.length;
|
|
502
|
+
(_suggestedStatusButto = suggestedStatusButtons[nextIndex]) === null || _suggestedStatusButto === void 0 || _suggestedStatusButto.focus();
|
|
503
|
+
}
|
|
360
504
|
}]);
|
|
361
505
|
}(React.Component); // eslint-disable-next-line @typescript-eslint/ban-types
|
|
362
506
|
export var StatusPickerWithoutAnalytcs = injectIntl(StatusPickerWithIntl);
|
|
@@ -11,5 +11,6 @@ interface ContentComponentProps {
|
|
|
11
11
|
popupsMountPoint?: HTMLElement;
|
|
12
12
|
popupsScrollableElement?: HTMLElement;
|
|
13
13
|
}
|
|
14
|
+
/** Renders the status picker popup for the currently selected status node. */
|
|
14
15
|
export declare function ContentComponent({ api, popupsMountPoint, popupsBoundariesElement, popupsScrollableElement, editorView, domAtPos, }: ContentComponentProps): React.JSX.Element | null;
|
|
15
16
|
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
|
|
2
|
+
import type { StatusType } from '../types';
|
|
3
|
+
export type SuggestedStatus = StatusType & {
|
|
4
|
+
displayText: string;
|
|
5
|
+
};
|
|
6
|
+
type GetSuggestedStatusesArgs = {
|
|
7
|
+
currentPos: number;
|
|
8
|
+
currentStatus: Pick<StatusType, 'color' | 'localId' | 'text'>;
|
|
9
|
+
doc: PMNode;
|
|
10
|
+
limit?: number;
|
|
11
|
+
shouldUppercaseText?: boolean;
|
|
12
|
+
};
|
|
13
|
+
export declare const MAX_SUGGESTED_STATUSES = 7;
|
|
14
|
+
export declare const getSuggestedStatuses: ({ currentPos, currentStatus, doc, limit, shouldUppercaseText, }: GetSuggestedStatusesArgs) => SuggestedStatus[];
|
|
15
|
+
export {};
|
|
@@ -6,6 +6,7 @@ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
|
6
6
|
import type { ColorType as Color } from '@atlaskit/status/picker';
|
|
7
7
|
import type { StatusPlugin } from '../statusPluginType';
|
|
8
8
|
import type { ClosingPayload, StatusType } from '../types';
|
|
9
|
+
import type { SuggestedStatus } from './getSuggestedStatuses';
|
|
9
10
|
export declare enum InputMethod {
|
|
10
11
|
blur = "blur",
|
|
11
12
|
escKey = "escKey",
|
|
@@ -30,8 +31,10 @@ export interface Props {
|
|
|
30
31
|
mountTo?: HTMLElement;
|
|
31
32
|
onEnter: (status: StatusType) => void;
|
|
32
33
|
onSelect: (status: StatusType) => void;
|
|
34
|
+
onSuggestedStatusClick: (status: StatusType) => void;
|
|
33
35
|
onTextChanged: (status: StatusType, isNew: boolean) => void;
|
|
34
36
|
scrollableElement?: HTMLElement;
|
|
37
|
+
suggestedStatuses?: SuggestedStatus[];
|
|
35
38
|
target: HTMLElement | null;
|
|
36
39
|
}
|
|
37
40
|
export interface State {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-status",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.1.1",
|
|
4
4
|
"description": "Status plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
"@atlaskit/icon": "^37.0.0",
|
|
31
31
|
"@atlaskit/platform-feature-flags": "^2.0.0",
|
|
32
32
|
"@atlaskit/status": "^5.4.0",
|
|
33
|
-
"@atlaskit/tmp-editor-statsig": "^
|
|
33
|
+
"@atlaskit/tmp-editor-statsig": "^127.0.0",
|
|
34
34
|
"@atlaskit/tokens": "^16.0.0",
|
|
35
|
-
"@atlaskit/visually-hidden": "^4.
|
|
35
|
+
"@atlaskit/visually-hidden": "^4.2.0",
|
|
36
36
|
"@babel/runtime": "^7.0.0",
|
|
37
37
|
"@emotion/react": "^11.7.1"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@atlaskit/editor-common": "^116.
|
|
40
|
+
"@atlaskit/editor-common": "^116.33.0",
|
|
41
41
|
"react": "^18.2.0",
|
|
42
42
|
"react-dom": "^18.2.0",
|
|
43
43
|
"react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
|