@atlaskit/editor-plugin-find-replace 0.2.0 → 0.3.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.
@@ -15,6 +15,7 @@ import debounce from 'lodash/debounce';
15
15
  import rafSchd from 'raf-schd';
16
16
  import { defineMessages, injectIntl } from 'react-intl-next';
17
17
  import { TRIGGER_METHOD } from '@atlaskit/editor-common/analytics';
18
+ import { Label } from '@atlaskit/form';
18
19
  import EditorCloseIcon from '@atlaskit/icon/glyph/editor/close';
19
20
  import MatchCaseIcon from '@atlaskit/icon/glyph/emoji/keyboard';
20
21
  import ChevronDownIcon from '@atlaskit/icon/glyph/hipchat/chevron-down';
@@ -22,7 +23,7 @@ import ChevronUpIcon from '@atlaskit/icon/glyph/hipchat/chevron-up';
22
23
  import { getBooleanFF } from '@atlaskit/platform-feature-flags';
23
24
  import Textfield from '@atlaskit/textfield';
24
25
  import { FindReplaceTooltipButton } from './FindReplaceTooltipButton';
25
- import { countStyles, countWrapperStyles, sectionWrapperStyles } from './styles';
26
+ import { afterInputSection, countStyles, countStylesAlternateStyles, countWrapperStyles, matchCaseSection, sectionWrapperStyles, sectionWrapperStylesAlternate, textFieldWrapper } from './styles';
26
27
  export var FIND_DEBOUNCE_MS = 100;
27
28
  var messages = defineMessages({
28
29
  find: {
@@ -105,6 +106,7 @@ var Find = /*#__PURE__*/function (_React$Component) {
105
106
  onSynced && onSynced();
106
107
  _this.debouncedFind(value);
107
108
  });
109
+ _this.props.setFindTyped(true);
108
110
  });
109
111
  // throtlle between animation frames gives better experience on Enter compared to arbitrary value
110
112
  // it adjusts based on performance (and document size)
@@ -177,7 +179,8 @@ var Find = /*#__PURE__*/function (_React$Component) {
177
179
  _this.findPrevious = formatMessage(messages.findPrevious);
178
180
  _this.matchCase = formatMessage(messages.matchCase);
179
181
  _this.matchCaseIcon = jsx(MatchCaseIcon, {
180
- label: _this.matchCase
182
+ label: _this.matchCase,
183
+ size: getBooleanFF('platform.editor.a11y-find-replace') ? 'small' : 'medium'
181
184
  });
182
185
  _this.findNextIcon = jsx(ChevronDownIcon, {
183
186
  label: _this.findNext
@@ -217,21 +220,19 @@ var Find = /*#__PURE__*/function (_React$Component) {
217
220
  }, {
218
221
  key: "componentDidUpdate",
219
222
  value: function componentDidUpdate(prevProps) {
220
- var _this3 = this;
223
+ var _this$state2,
224
+ _this3 = this;
221
225
  // focus on update if find text did not change
222
- if (!getBooleanFF('platform.editor.a11y-find-replace')) {
223
- var _this$state2;
224
- if (this.props.findText === ((_this$state2 = this.state) === null || _this$state2 === void 0 ? void 0 : _this$state2.localFindText)) {
225
- this.focusFindTextfield();
226
- }
227
- if (this.props.findText !== prevProps.findText) {
228
- this.syncFindText(function () {
229
- // focus after input is synced if find text provided
230
- if (_this3.props.findText) {
231
- _this3.focusFindTextfield();
232
- }
233
- });
234
- }
226
+ if (this.props.findText === ((_this$state2 = this.state) === null || _this$state2 === void 0 ? void 0 : _this$state2.localFindText)) {
227
+ this.focusFindTextfield();
228
+ }
229
+ if (this.props.findText !== prevProps.findText) {
230
+ this.syncFindText(function () {
231
+ // focus after input is synced if find text provided
232
+ if (_this3.props.findText) {
233
+ _this3.focusFindTextfield();
234
+ }
235
+ });
235
236
  }
236
237
  }
237
238
  }, {
@@ -253,50 +254,90 @@ var Find = /*#__PURE__*/function (_React$Component) {
253
254
  selectedMatchPosition: count.index + 1,
254
255
  totalResultsCount: count.total
255
256
  });
256
- return jsx("div", {
257
- css: sectionWrapperStyles
258
- }, jsx(Textfield, {
259
- name: "find",
260
- appearance: "none",
261
- placeholder: this.find,
262
- value: this.state.localFindText,
263
- ref: this.findTextfieldRef,
264
- autoComplete: "off",
265
- onChange: this.handleFindChange,
266
- onKeyDown: this.handleFindKeyDown,
267
- onKeyUp: this.handleFindKeyUp,
268
- onBlur: this.props.onFindBlur,
269
- onCompositionStart: this.handleCompositionStart,
270
- onCompositionEnd: this.handleCompositionEnd
271
- }), jsx("div", {
272
- css: countWrapperStyles,
273
- "aria-live": "polite"
274
- }, findText && jsx("span", {
275
- "data-testid": "textfield-count",
276
- css: countStyles
277
- }, count.total === 0 ? this.noResultsFound : resultsCount)), allowMatchCase && jsx(FindReplaceTooltipButton, {
278
- title: this.matchCase,
279
- icon: this.matchCaseIcon,
280
- onClick: this.handleMatchCaseClick,
281
- isPressed: shouldMatchCase
282
- }), jsx(FindReplaceTooltipButton, {
283
- title: this.findNext,
284
- icon: this.findNextIcon,
285
- keymapDescription: 'Enter',
286
- onClick: this.handleFindNextClick,
287
- disabled: count.total <= 1
288
- }), jsx(FindReplaceTooltipButton, {
289
- title: this.findPrevious,
290
- icon: this.findPrevIcon,
291
- keymapDescription: 'Shift Enter',
292
- onClick: this.handleFindPrevClick,
293
- disabled: count.total <= 1
294
- }), jsx(FindReplaceTooltipButton, {
295
- title: this.closeFindReplaceDialog,
296
- icon: this.closeIcon,
297
- keymapDescription: 'Escape',
298
- onClick: this.clearSearch
299
- }));
257
+ if (getBooleanFF('platform.editor.a11y-find-replace')) {
258
+ var elemAfterInput = jsx("div", {
259
+ css: afterInputSection
260
+ }, jsx("div", {
261
+ "aria-live": "polite"
262
+ }, findText && jsx("span", {
263
+ "data-testid": "textfield-count",
264
+ css: [countStyles, countStylesAlternateStyles]
265
+ }, count.total === 0 ? this.noResultsFound : resultsCount)), allowMatchCase && jsx("div", {
266
+ css: matchCaseSection
267
+ }, jsx(FindReplaceTooltipButton, {
268
+ title: this.matchCase,
269
+ appearance: "default",
270
+ icon: this.matchCaseIcon,
271
+ onClick: this.handleMatchCaseClick,
272
+ isPressed: shouldMatchCase
273
+ })));
274
+ return jsx("div", {
275
+ css: [sectionWrapperStyles, sectionWrapperStylesAlternate]
276
+ }, jsx("div", {
277
+ css: textFieldWrapper
278
+ }, jsx(Label, {
279
+ htmlFor: "find-text-field"
280
+ }, this.find), jsx(Textfield, {
281
+ name: "find",
282
+ id: "find-text-field",
283
+ appearance: "standard",
284
+ value: this.state.localFindText,
285
+ ref: this.findTextfieldRef,
286
+ autoComplete: "off",
287
+ onChange: this.handleFindChange,
288
+ onKeyDown: this.handleFindKeyDown,
289
+ onKeyUp: this.handleFindKeyUp,
290
+ onBlur: this.props.onFindBlur,
291
+ onCompositionStart: this.handleCompositionStart,
292
+ onCompositionEnd: this.handleCompositionEnd,
293
+ elemAfterInput: elemAfterInput
294
+ })));
295
+ } else {
296
+ return jsx("div", {
297
+ css: sectionWrapperStyles
298
+ }, jsx(Textfield, {
299
+ name: "find",
300
+ appearance: "none",
301
+ placeholder: this.find,
302
+ value: this.state.localFindText,
303
+ ref: this.findTextfieldRef,
304
+ autoComplete: "off",
305
+ onChange: this.handleFindChange,
306
+ onKeyDown: this.handleFindKeyDown,
307
+ onKeyUp: this.handleFindKeyUp,
308
+ onBlur: this.props.onFindBlur,
309
+ onCompositionStart: this.handleCompositionStart,
310
+ onCompositionEnd: this.handleCompositionEnd
311
+ }), jsx("div", {
312
+ css: countWrapperStyles,
313
+ "aria-live": "polite"
314
+ }, findText && jsx("span", {
315
+ "data-testid": "textfield-count",
316
+ css: countStyles
317
+ }, count.total === 0 ? this.noResultsFound : resultsCount)), allowMatchCase && jsx(FindReplaceTooltipButton, {
318
+ title: this.matchCase,
319
+ icon: this.matchCaseIcon,
320
+ onClick: this.handleMatchCaseClick,
321
+ isPressed: shouldMatchCase
322
+ }), jsx(FindReplaceTooltipButton, {
323
+ title: this.findNext,
324
+ icon: this.findNextIcon,
325
+ keymapDescription: 'Enter',
326
+ onClick: this.handleFindNextClick,
327
+ disabled: count.total <= 1
328
+ }), jsx(FindReplaceTooltipButton, {
329
+ title: this.findPrevious,
330
+ icon: this.findPrevIcon,
331
+ keymapDescription: 'Shift Enter',
332
+ onClick: this.handleFindPrevClick,
333
+ disabled: count.total <= 1
334
+ }), jsx(FindReplaceTooltipButton, {
335
+ title: this.closeFindReplaceDialog,
336
+ icon: this.closeIcon,
337
+ keymapDescription: 'Escape',
338
+ onClick: this.clearSearch
339
+ }));
340
+ }
300
341
  }
301
342
  }]);
302
343
  return Find;
@@ -12,22 +12,53 @@ function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Re
12
12
  /** @jsx jsx */
13
13
  import React from 'react';
14
14
  import { jsx } from '@emotion/react';
15
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
15
16
  import Find from './Find';
16
17
  import Replace from './Replace';
17
- import { ruleStyles, wrapperStyles } from './styles';
18
+ import { ruleStyles, wrapperPaddingStyles, wrapperStyles } from './styles';
18
19
  // eslint-disable-next-line @repo/internal/react/no-class-components
19
20
  var FindReplace = /*#__PURE__*/function (_React$PureComponent) {
20
21
  _inherits(FindReplace, _React$PureComponent);
21
22
  var _super = _createSuper(FindReplace);
22
- function FindReplace() {
23
+ function FindReplace(props) {
23
24
  var _this;
24
25
  _classCallCheck(this, FindReplace);
25
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
26
- args[_key] = arguments[_key];
27
- }
28
- _this = _super.call.apply(_super, [this].concat(args));
26
+ _this = _super.call(this, props);
29
27
  _defineProperty(_assertThisInitialized(_this), "findTextfield", null);
30
28
  _defineProperty(_assertThisInitialized(_this), "replaceTextfield", null);
29
+ _defineProperty(_assertThisInitialized(_this), "handleTabNavigation", function (event) {
30
+ if (event.key === 'Tab') {
31
+ event.preventDefault();
32
+ var modalFindReplace = _this.modalRef.current;
33
+ if (!modalFindReplace || !modalFindReplace.contains(document.activeElement)) {
34
+ return;
35
+ }
36
+ var focusableElements = Array.from(modalFindReplace.querySelectorAll('[tabindex]:not([tabindex="-1"]), input, button')).filter(function (el) {
37
+ return el.getAttribute('tabindex') !== '-1';
38
+ });
39
+ var currentIndex = focusableElements.findIndex(function (el) {
40
+ return el === document.activeElement;
41
+ });
42
+ var isShiftPressed = event.shiftKey;
43
+ if (isShiftPressed) {
44
+ var prevIndex = (currentIndex - 1 + focusableElements.length) % focusableElements.length;
45
+ focusableElements[prevIndex].focus();
46
+ } else {
47
+ var nextIndex = (currentIndex + 1) % focusableElements.length;
48
+ focusableElements[nextIndex].focus();
49
+ }
50
+ }
51
+ });
52
+ _defineProperty(_assertThisInitialized(_this), "state", {
53
+ findTyped: false
54
+ });
55
+ _defineProperty(_assertThisInitialized(_this), "setFindTyped", function (value) {
56
+ if (getBooleanFF('platform.editor.a11y-find-replace')) {
57
+ _this.setState({
58
+ findTyped: value
59
+ });
60
+ }
61
+ });
31
62
  _defineProperty(_assertThisInitialized(_this), "setFindTextfieldRef", function (findTextfieldRef) {
32
63
  _this.findTextfield = findTextfieldRef.current;
33
64
  });
@@ -44,9 +75,26 @@ var FindReplace = /*#__PURE__*/function (_React$PureComponent) {
44
75
  _this.replaceTextfield.focus();
45
76
  }
46
77
  });
78
+ _this.modalRef = /*#__PURE__*/React.createRef();
47
79
  return _this;
48
80
  }
49
81
  _createClass(FindReplace, [{
82
+ key: "componentDidMount",
83
+ value: function componentDidMount() {
84
+ if (getBooleanFF('platform.editor.a11y-find-replace')) {
85
+ // eslint-disable-next-line
86
+ window.addEventListener('keydown', this.handleTabNavigation);
87
+ }
88
+ }
89
+ }, {
90
+ key: "componentWillUnmount",
91
+ value: function componentWillUnmount() {
92
+ if (getBooleanFF('platform.editor.a11y-find-replace')) {
93
+ // eslint-disable-next-line
94
+ window.removeEventListener('keydown', this.handleTabNavigation);
95
+ }
96
+ }
97
+ }, {
50
98
  key: "render",
51
99
  value: function render() {
52
100
  var _this$props = this.props,
@@ -65,8 +113,10 @@ var FindReplace = /*#__PURE__*/function (_React$PureComponent) {
65
113
  allowMatchCase = _this$props.allowMatchCase,
66
114
  shouldMatchCase = _this$props.shouldMatchCase,
67
115
  onToggleMatchCase = _this$props.onToggleMatchCase;
116
+ var focusToolbarButton = getBooleanFF('platform.editor.a11y-find-replace') ? this.props.focusToolbarButton || function () {} : function () {};
68
117
  return jsx("div", {
69
- css: wrapperStyles
118
+ ref: this.modalRef,
119
+ css: getBooleanFF('platform.editor.a11y-find-replace') ? [wrapperStyles, wrapperPaddingStyles] : wrapperStyles
70
120
  }, jsx(Find, {
71
121
  allowMatchCase: allowMatchCase,
72
122
  shouldMatchCase: shouldMatchCase,
@@ -80,8 +130,10 @@ var FindReplace = /*#__PURE__*/function (_React$PureComponent) {
80
130
  onFindNext: onFindNext,
81
131
  onFindTextfieldRefSet: this.setFindTextfieldRef,
82
132
  onCancel: onCancel,
83
- onArrowDown: this.setFocusToReplace
84
- }), jsx("hr", {
133
+ onArrowDown: this.setFocusToReplace,
134
+ findTyped: this.state.findTyped,
135
+ setFindTyped: this.setFindTyped
136
+ }), !getBooleanFF('platform.editor.a11y-find-replace') && jsx("hr", {
85
137
  css: ruleStyles,
86
138
  id: "replace-hr-element"
87
139
  }), jsx(Replace, {
@@ -91,7 +143,14 @@ var FindReplace = /*#__PURE__*/function (_React$PureComponent) {
91
143
  onReplaceAll: onReplaceAll,
92
144
  onReplaceTextfieldRefSet: this.setReplaceTextfieldRef,
93
145
  onArrowUp: this.setFocusToFind,
94
- dispatchAnalyticsEvent: dispatchAnalyticsEvent
146
+ onCancel: onCancel,
147
+ count: count,
148
+ onFindPrev: onFindPrev,
149
+ onFindNext: onFindNext,
150
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
151
+ findTyped: this.state.findTyped,
152
+ setFindTyped: this.setFindTyped,
153
+ focusToolbarButton: focusToolbarButton
95
154
  }));
96
155
  }
97
156
  }]);
@@ -20,6 +20,7 @@ import { findKeymapByDescription, getAriaKeyshortcuts, tooltip, ToolTipContent }
20
20
  import { ArrowKeyNavigationType, Dropdown, TOOLBAR_BUTTON, ToolbarButton } from '@atlaskit/editor-common/ui-menu';
21
21
  import { akEditorFloatingPanelZIndex, akEditorMobileMaxWidth } from '@atlaskit/editor-shared-styles';
22
22
  import EditorSearchIcon from '@atlaskit/icon/glyph/editor/search';
23
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
23
24
  import FindReplace from './FindReplace';
24
25
  var toolbarButtonWrapper = css(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n display: flex;\n flex: 1 1 auto;\n flex-grow: 0;\n justify-content: flex-end;\n align-items: center;\n padding: 0 ", ";\n @media (max-width: ", "px) {\n justify-content: center;\n padding: 0;\n }\n"])), "var(--ds-space-100, 8px)", akEditorMobileMaxWidth);
25
26
  var toolbarButtonWrapperFullWith = css(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["\n flex-grow: 1;\n"])));
@@ -31,6 +32,8 @@ var messages = defineMessages({
31
32
  description: '"Find" highlights all instances of a word or phrase on the document, and "Replace" changes one or all of those instances to something else'
32
33
  }
33
34
  });
35
+ var dropdownWidthNewDesign = 382;
36
+ var dropdownWidthOldDesign = 352;
34
37
  // eslint-disable-next-line @repo/internal/react/no-class-components
35
38
  var FindReplaceToolbarButton = /*#__PURE__*/function (_React$PureComponent) {
36
39
  _inherits(FindReplaceToolbarButton, _React$PureComponent);
@@ -42,15 +45,30 @@ var FindReplaceToolbarButton = /*#__PURE__*/function (_React$PureComponent) {
42
45
  args[_key] = arguments[_key];
43
46
  }
44
47
  _this = _super.call.apply(_super, [this].concat(args));
48
+ _defineProperty(_assertThisInitialized(_this), "state", {
49
+ openedByClick: false
50
+ });
45
51
  _defineProperty(_assertThisInitialized(_this), "toggleOpen", function () {
46
52
  if (_this.props.isActive) {
47
53
  _this.props.onCancel({
48
54
  triggerMethod: TRIGGER_METHOD.TOOLBAR
49
55
  });
50
56
  } else {
57
+ _this.setState({
58
+ openedByClick: true
59
+ });
51
60
  _this.props.onActivate();
52
61
  }
53
62
  });
63
+ _defineProperty(_assertThisInitialized(_this), "focusToolbarButton", function () {
64
+ if (_this.state.openedByClick && _this.toolbarButtonRef.current) {
65
+ _this.toolbarButtonRef.current.focus();
66
+ }
67
+ _this.setState({
68
+ openedByClick: false
69
+ });
70
+ });
71
+ _defineProperty(_assertThisInitialized(_this), "toolbarButtonRef", /*#__PURE__*/React.createRef());
54
72
  return _this;
55
73
  }
56
74
  _createClass(FindReplaceToolbarButton, [{
@@ -84,9 +102,17 @@ var FindReplaceToolbarButton = /*#__PURE__*/function (_React$PureComponent) {
84
102
  _this2.props.onCancel({
85
103
  triggerMethod: TRIGGER_METHOD.KEYBOARD
86
104
  });
105
+ if (getBooleanFF('platform.editor.a11y-find-replace')) {
106
+ if (_this2.state.openedByClick && _this2.toolbarButtonRef.current) {
107
+ _this2.toolbarButtonRef.current.focus();
108
+ }
109
+ _this2.setState({
110
+ openedByClick: false
111
+ });
112
+ }
87
113
  }
88
114
  },
89
- fitWidth: 352,
115
+ fitWidth: getBooleanFF('platform.editor.a11y-find-replace') ? dropdownWidthNewDesign : dropdownWidthOldDesign,
90
116
  zIndex: stackBelowOtherEditorFloatingPanels,
91
117
  arrowKeyNavigationProviderOptions: {
92
118
  type: ArrowKeyNavigationType.MENU,
@@ -107,7 +133,8 @@ var FindReplaceToolbarButton = /*#__PURE__*/function (_React$PureComponent) {
107
133
  "aria-expanded": isActive,
108
134
  "aria-haspopup": true,
109
135
  "aria-label": keymap ? tooltip(keymap, title) : title,
110
- "aria-keyshortcuts": getAriaKeyshortcuts(keymap)
136
+ "aria-keyshortcuts": getAriaKeyshortcuts(keymap),
137
+ ref: getBooleanFF('platform.editor.a11y-find-replace') ? this.toolbarButtonRef : null
111
138
  })
112
139
  }, jsx("div", {
113
140
  css: wrapper
@@ -117,7 +144,8 @@ var FindReplaceToolbarButton = /*#__PURE__*/function (_React$PureComponent) {
117
144
  count: {
118
145
  index: index,
119
146
  total: numMatches
120
- }
147
+ },
148
+ focusToolbarButton: this.focusToolbarButton
121
149
  }, this.props)))));
122
150
  }
123
151
  }]);
@@ -39,7 +39,8 @@ export var FindReplaceTooltipButton = /*#__PURE__*/function (_React$PureComponen
39
39
  icon = _this$props.icon,
40
40
  keymapDescription = _this$props.keymapDescription,
41
41
  disabled = _this$props.disabled,
42
- isPressed = _this$props.isPressed;
42
+ isPressed = _this$props.isPressed,
43
+ appearance = _this$props.appearance;
43
44
  var pressedProps = _objectSpread({}, typeof isPressed === 'boolean' && {
44
45
  'aria-pressed': isPressed
45
46
  });
@@ -51,8 +52,9 @@ export var FindReplaceTooltipButton = /*#__PURE__*/function (_React$PureComponen
51
52
  hideTooltipOnClick: true,
52
53
  position: 'top'
53
54
  }, /*#__PURE__*/React.createElement(Button, _extends({
55
+ id: "afterInputSection",
54
56
  label: title,
55
- appearance: "subtle",
57
+ appearance: appearance,
56
58
  testId: title,
57
59
  ref: this.buttonRef,
58
60
  iconBefore: icon,
@@ -66,5 +68,6 @@ export var FindReplaceTooltipButton = /*#__PURE__*/function (_React$PureComponen
66
68
  return FindReplaceTooltipButton;
67
69
  }(React.PureComponent);
68
70
  _defineProperty(FindReplaceTooltipButton, "defaultProps", {
69
- keymapDescription: 'no-keymap'
71
+ keymapDescription: 'no-keymap',
72
+ appearance: 'subtle'
70
73
  });