@atlaskit/editor-plugin-find-replace
Version:
find replace plugin for @atlaskit/editor-core
269 lines (266 loc) • 12.1 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.FIND_DEBOUNCE_MS = void 0;
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _react = _interopRequireDefault(require("react"));
var _react2 = require("@emotion/react");
var _debounce = _interopRequireDefault(require("lodash/debounce"));
var _rafSchd = _interopRequireDefault(require("raf-schd"));
var _reactIntl = require("react-intl");
var _analytics = require("@atlaskit/editor-common/analytics");
var _messages = require("@atlaskit/editor-common/messages");
var _form = require("@atlaskit/form");
var _textLetterCase = _interopRequireDefault(require("@atlaskit/icon-lab/core/text-letter-case"));
var _textStyle = _interopRequireDefault(require("@atlaskit/icon/core/text-style"));
var _textfield = _interopRequireDefault(require("@atlaskit/textfield"));
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
var _FindReplaceTooltipButton = require("./FindReplaceTooltipButton");
var _uiStyles = require("./ui-styles");
function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } /* eslint-disable @atlaskit/design-system/consistent-css-prop-usage */ /**
* @jsxRuntime classic
* @jsx jsx
*/ // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
var FIND_DEBOUNCE_MS = exports.FIND_DEBOUNCE_MS = 100;
// eslint-disable-next-line @repo/internal/react/no-class-components
var Find = /*#__PURE__*/function (_React$Component) {
function Find(props) {
var _this;
(0, _classCallCheck2.default)(this, Find);
_this = _callSuper(this, Find, [props]);
(0, _defineProperty2.default)(_this, "findTextfieldRef", /*#__PURE__*/_react.default.createRef());
(0, _defineProperty2.default)(_this, "isComposing", false);
(0, _defineProperty2.default)(_this, "syncFindText", function (onSynced) {
var _this$state;
// If the external prop findText changes and we aren't in a composition we should update to
// use the external prop value.
//
// An example of where this may happen is when a find occurs through the user selecting some text
// and pressing Mod-f.
if (!_this.isComposing && _this.props.findText !== ((_this$state = _this.state) === null || _this$state === void 0 ? void 0 : _this$state.localFindText)) {
_this.updateFindValue(_this.props.findText || '', onSynced);
}
});
(0, _defineProperty2.default)(_this, "focusFindTextfield", function () {
var input = _this.findTextfieldRef.current;
if (_this.props.shouldFocus && input) {
input.select();
}
});
(0, _defineProperty2.default)(_this, "handleFindChange", function (event) {
_this.updateFindValue(event.target.value);
});
// debounce (vs throttle) to not block typing inside find input while onFind runs
(0, _defineProperty2.default)(_this, "debouncedFind", (0, _debounce.default)(function (value) {
_this.props.onFind(value);
}, FIND_DEBOUNCE_MS));
(0, _defineProperty2.default)(_this, "updateFindValue", function (value, onSynced) {
_this.setState({
localFindText: value
}, function () {
if (_this.isComposing) {
return;
}
onSynced && onSynced();
_this.debouncedFind(value);
});
_this.props.setFindTyped(true);
});
// throtlle between animation frames gives better experience on Enter compared to arbitrary value
// it adjusts based on performance (and document size)
(0, _defineProperty2.default)(_this, "handleFindKeyDownThrottled", (0, _rafSchd.default)(function (event) {
if (event.key === 'Enter') {
if (event.shiftKey) {
_this.props.onFindPrev({
triggerMethod: _analytics.TRIGGER_METHOD.KEYBOARD
});
} else {
_this.props.onFindNext({
triggerMethod: _analytics.TRIGGER_METHOD.KEYBOARD
});
}
} else if (event.key === 'ArrowDown') {
// we want to move focus between find & replace texfields when user hits up/down arrows
_this.props.onArrowDown();
}
}));
(0, _defineProperty2.default)(_this, "handleFindKeyDown", function (event) {
if (_this.isComposing) {
return;
}
event.persist();
_this.handleFindKeyDownThrottled(event);
});
(0, _defineProperty2.default)(_this, "handleFindKeyUp", function () {
_this.handleFindKeyDownThrottled.cancel();
});
(0, _defineProperty2.default)(_this, "handleFindNextClick", function () {
if (_this.isComposing) {
return;
}
_this.props.onFindNext({
triggerMethod: _analytics.TRIGGER_METHOD.BUTTON
});
});
(0, _defineProperty2.default)(_this, "handleFindPrevClick", function () {
if (_this.isComposing) {
return;
}
_this.props.onFindPrev({
triggerMethod: _analytics.TRIGGER_METHOD.BUTTON
});
});
(0, _defineProperty2.default)(_this, "handleCompositionStart", function () {
_this.isComposing = true;
});
(0, _defineProperty2.default)(_this, "handleCompositionEnd", function (event) {
_this.isComposing = false;
// type for React.CompositionEvent doesn't set type for target correctly
_this.updateFindValue(event.target.value);
});
(0, _defineProperty2.default)(_this, "clearSearch", function () {
_this.props.onCancel({
triggerMethod: _analytics.TRIGGER_METHOD.BUTTON
});
});
(0, _defineProperty2.default)(_this, "handleMatchCaseClick", function () {
if (_this.props.onToggleMatchCase) {
_this.props.onToggleMatchCase();
_this.props.onFind(_this.props.findText);
}
});
(0, _defineProperty2.default)(_this, "matchCaseIconEle", function (iconProps) {
return (0, _expValEquals.expValEquals)('platform_editor_find_and_replace_improvements', 'isEnabled', true) ? (0, _react2.jsx)(_textLetterCase.default, {
label: iconProps.label,
size: "small"
}) : (0, _react2.jsx)(_textStyle.default, {
label: _this.matchCase
});
});
var formatMessage = props.intl.formatMessage;
_this.find = formatMessage(_messages.findReplaceMessages.find);
_this.noResultsFound = formatMessage(_messages.findReplaceMessages.noResultsFound);
_this.matchCase = formatMessage(_messages.findReplaceMessages.matchCase);
// We locally manage the value of the input inside this component in order to support compositions.
// This requires some additional work inside componentDidUpdate to ensure we support changes that
// occur to this value which do not originate from this component.
_this.state = {
localFindText: ''
};
return _this;
}
(0, _inherits2.default)(Find, _React$Component);
return (0, _createClass2.default)(Find, [{
key: "componentDidMount",
value: function componentDidMount() {
var _this2 = this;
this.props.onFindTextfieldRefSet(this.findTextfieldRef);
// focus initially on dialog mount if there is no find text provided
if (!this.props.findText) {
// Wait for findTextfieldRef to become available then focus
setTimeout(function () {
_this2.focusFindTextfield();
}, 100);
}
this.syncFindText(function () {
// focus after input is synced if find text provided
if (_this2.props.findText) {
_this2.focusFindTextfield();
}
});
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate(prevProps) {
var _this$state2,
_this3 = this;
// focus on update if find text did not change
if (this.props.findText === ((_this$state2 = this.state) === null || _this$state2 === void 0 ? void 0 : _this$state2.localFindText)) {
this.focusFindTextfield();
}
if (this.props.findText !== prevProps.findText && this.props.shouldFocus) {
this.syncFindText(function () {
// focus after input is synced if find text provided
if (_this3.props.findText) {
_this3.focusFindTextfield();
}
});
}
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
this.debouncedFind.cancel();
this.handleFindKeyDownThrottled.cancel();
}
}, {
key: "render",
value: function render() {
var _this$props = this.props,
findText = _this$props.findText,
count = _this$props.count,
shouldMatchCase = _this$props.shouldMatchCase,
formatMessage = _this$props.intl.formatMessage;
var resultsCount = formatMessage(_messages.findReplaceMessages.resultsCount, {
selectedMatchPosition: count.index + 1,
totalResultsCount: count.total
});
var elemAfterInput =
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
(0, _react2.jsx)("div", {
css: _uiStyles.afterInputSection
}, (0, _react2.jsx)("div", {
"aria-live": "polite"
}, findText &&
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
(0, _react2.jsx)("span", {
"data-testid": "textfield-count",
css: [_uiStyles.countStyles, _uiStyles.countStylesAlternateStyles]
}, count.total === 0 ? this.noResultsFound : resultsCount)), (0, _react2.jsx)("div", {
css: _uiStyles.matchCaseSection
}, (0, _react2.jsx)(_FindReplaceTooltipButton.FindReplaceTooltipButton, {
title: this.matchCase,
appearance: "default",
icon: this.matchCaseIconEle,
iconLabel: this.matchCase,
onClick: this.handleMatchCaseClick,
isPressed: shouldMatchCase
})));
return (
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
(0, _react2.jsx)("div", {
css: [_uiStyles.sectionWrapperStyles, _uiStyles.sectionWrapperStylesAlternate]
}, (0, _react2.jsx)("div", {
css: _uiStyles.textFieldWrapper
}, (0, _react2.jsx)(_form.Label, {
htmlFor: "find-text-field"
}, this.find), (0, _react2.jsx)(_textfield.default, {
name: "find",
id: "find-text-field",
testId: "find-field",
appearance: "standard",
value: this.state.localFindText,
ref: this.findTextfieldRef,
autoComplete: "off",
onChange: this.handleFindChange,
onKeyDown: this.handleFindKeyDown,
onKeyUp: this.handleFindKeyUp,
onBlur: this.props.onFindBlur,
onCompositionStart: this.handleCompositionStart,
onCompositionEnd: this.handleCompositionEnd,
elemAfterInput: elemAfterInput
})))
);
}
}]);
}(_react.default.Component); // eslint-disable-next-line @typescript-eslint/ban-types
var _default_1 = (0, _reactIntl.injectIntl)(Find);
var _default = exports.default = _default_1;