UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

883 lines (874 loc) 31.4 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; /* eslint-disable @atlaskit/ui-styling-standard/use-compiled -- Pre-existing lint debt surfaced by this mechanical type-import-only PR. */ /** * @jsxRuntime classic * @jsx jsx */ import React, { PureComponent } from 'react'; // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled, @typescript-eslint/consistent-type-imports -- Ignored via go/DSP-18766; jsx required at runtime for @jsxRuntime classic import { css, jsx } from '@emotion/react'; import debounce from 'lodash/debounce'; import { flushSync } from 'react-dom'; import FocusLock from 'react-focus-lock'; import { defineMessages, injectIntl } from 'react-intl'; import { isSafeUrl } from '@atlaskit/adf-schema'; import withAnalyticsEvents from '@atlaskit/analytics-next/withAnalyticsEvents'; import CrossCircleIcon from '@atlaskit/icon/core/cross-circle'; import PageObject from '@atlaskit/object/page'; // eslint-disable-next-line @atlaskit/design-system/no-emotion-primitives -- to be migrated to @atlaskit/primitives/compiled – go/akcss import { Pressable, xcss } from '@atlaskit/primitives'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import Tooltip from '@atlaskit/tooltip'; import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, fireAnalyticsEvent, INPUT_METHOD } from '../../../analytics'; import { Announcer, PanelTextInput } from '../../../ui'; import { normalizeUrl } from '../../../utils'; import { getBrowserInfo } from '../../../utils/browser'; import LinkSearchList from '../../LinkSearch/LinkSearchList'; import { container, narrowContainerWidth, inputWrapper } from '../../LinkSearch/ToolbarComponents'; import { transformTimeStamp } from '../../LinkSearch/transformTimeStamp'; import { filterUniqueItems, mapContentTypeToIcon, sha1, wordCount } from './utils'; /** * Visible only to screenreaders. Use when there is a need * to provide more context to a non-sighted user. */ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-exported-styles -- Ignored via go/DSP-18766 export const visuallyHiddenStyles = css({ clip: 'rect(1px, 1px, 1px, 1px)', clipPath: 'inset(50%)', height: '1px', width: '1px', margin: "var(--ds-space-negative-025, -2px)", overflow: 'hidden', padding: 0, position: 'absolute' }); export const RECENT_SEARCH_LIST_SIZE = 5; const clearTextButtonStyles = xcss({ padding: 'space.0', marginRight: 'space.100', backgroundColor: 'color.background.neutral.subtle', border: 'none' }); const clearTextWrapper = css({ position: 'absolute', right: 0 }); const containerPadding = css({ padding: `${"var(--ds-space-150, 12px)"} ${"var(--ds-space-100, 8px)"}` }); const textLabelMargin = css({ marginTop: "var(--ds-space-150, 12px)" }); const inputLabel = css({ color: "var(--ds-text-subtlest, #6B6E76)", paddingBottom: "var(--ds-space-050, 4px)", font: "var(--ds-font-body-small, normal 400 12px/16px \"Atlassian Sans\", ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)", fontWeight: "var(--ds-font-weight-medium, 500)" }); const inputWrapperPosition = css({ position: 'relative' }); export const messages = defineMessages({ displayText: { id: 'fabric.editor.displayText', defaultMessage: 'Text to display', description: 'Label for the text input field in the hyperlink toolbar where users enter the display text for a link.' }, clearText: { id: 'fabric.editor.clearLinkText', defaultMessage: 'Clear text', description: 'The text is shown on a button in the hyperlink toolbar that clears the display text field, removing any custom link text the user has entered.' }, clearLink: { id: 'fabric.editor.clearLink', defaultMessage: 'Clear link', description: 'The text is shown on a button in the hyperlink toolbar that clears the URL field, removing any link address the user has entered.' }, hyperlinkAriaLabel: { id: 'fabric.editor.hyperlink.ariaLabel', defaultMessage: 'Hyperlink Edit', description: 'Aria label for the Hyperlink Add Toolbar' }, searchLinkAriaDescription: { id: 'fabric.editor.hyperlink.searchLinkAriaDescription', defaultMessage: 'Suggestions will appear below as you type into the field', description: 'Describes what the search field does for screen reader users.' }, searchLinkResults: { id: 'fabric.editor.hyperlink.searchLinkResults', defaultMessage: '{count, plural, =0 {no results} one {# result} other {# results}} found', description: 'Announce search results for screen-reader users.' }, linkVisibleLabel: { id: 'fabric.editor.hyperlink.linkVisibleLabel', defaultMessage: 'Paste or search for link', description: 'Visible label for link input in hyperlink floating control' }, textVisibleLabel: { id: 'fabric.editor.hyperlink.textVisibleLabel', defaultMessage: 'Display text (optional)', description: 'Visible label for text input in hyperlink floating control' } }); const defaultIcon = jsx(PageObject, { label: "page" }); const mapActivityProviderResultToLinkSearchItemData = ({ name, container, iconUrl, objectId, url, viewedTimestamp }) => ({ objectId, name, container, iconUrl, url, lastViewedDate: viewedTimestamp ? new Date(viewedTimestamp) : undefined, prefetch: true }); const mapSearchProviderResultToLinkSearchItemData = ({ objectId, container, title, contentType, url, updatedTimestamp }) => ({ objectId, container, name: title, url, lastUpdatedDate: updatedTimestamp ? new Date(updatedTimestamp) : undefined, icon: contentType && mapContentTypeToIcon[contentType] || defaultIcon, prefetch: false }); // Ignored via go/ees005 // eslint-disable-next-line @repo/internal/react/no-class-components export class HyperlinkLinkAddToolbar extends PureComponent { constructor(props) { super(props); /* To prevent double submit */ _defineProperty(this, "submitted", false); _defineProperty(this, "urlInputContainer", null); _defineProperty(this, "displayTextInputContainer", null); _defineProperty(this, "wrapperRef", /*#__PURE__*/React.createRef()); _defineProperty(this, "listItemRefs", { current: {} }); _defineProperty(this, "quickSearchQueryVersion", 0); _defineProperty(this, "analyticSource", 'createLinkInlineDialog'); _defineProperty(this, "quickSearch", async (input, items, quickSearchLimit) => { const { searchProvider, displayUrl } = this.state; const { searchSessionId } = this.props; if (!searchProvider) { return; } const queryVersion = ++this.quickSearchQueryVersion; this.fireAnalytics({ action: ACTION.ENTERED, actionSubject: ACTION_SUBJECT.TEXT, actionSubjectId: ACTION_SUBJECT_ID.LINK_SEARCH_INPUT, attributes: { queryLength: input.length, queryVersion, queryHash: sha1(input), searchSessionId: searchSessionId !== null && searchSessionId !== void 0 ? searchSessionId : '', wordCount: wordCount(input), source: this.analyticSource }, nonPrivacySafeAttributes: { query: input }, eventType: EVENT_TYPE.UI }); const perfStart = performance.now(); try { const searchProviderResultItems = await searchProvider.quickSearch(input, quickSearchLimit); const searchItems = limit(filterUniqueItems([...items, ...searchProviderResultItems.map(mapSearchProviderResultToLinkSearchItemData)], (firstItem, secondItem) => firstItem.objectId === secondItem.objectId)); if (displayUrl === input && queryVersion === this.quickSearchQueryVersion) { this.setState({ items: searchItems, isLoading: false }); } const perfStop = performance.now(); const duration = perfStop - perfStart; this.fireAnalytics({ action: ACTION.INVOKED, actionSubject: ACTION_SUBJECT.SEARCH_RESULT, actionSubjectId: ACTION_SUBJECT_ID.QUICK_SEARCH, attributes: { duration, count: searchProviderResultItems.length }, eventType: EVENT_TYPE.OPERATIONAL }); this.fireAnalytics({ action: ACTION.SHOWN, actionSubject: ACTION_SUBJECT.SEARCH_RESULT, actionSubjectId: ACTION_SUBJECT_ID.POST_QUERY_SEARCH_RESULTS, attributes: { source: this.analyticSource, postQueryRequestDurationMs: duration, searchSessionId: searchSessionId !== null && searchSessionId !== void 0 ? searchSessionId : '', resultCount: searchProviderResultItems.length, results: searchProviderResultItems.map(item => ({ resultContentId: item.objectId, resultType: item.contentType })) }, eventType: EVENT_TYPE.UI }); // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err) { const perfStop = performance.now(); const duration = perfStop - perfStart; this.fireAnalytics({ action: ACTION.INVOKED, actionSubject: ACTION_SUBJECT.SEARCH_RESULT, actionSubjectId: ACTION_SUBJECT_ID.QUICK_SEARCH, attributes: { duration, count: -1, errorCode: err.status }, eventType: EVENT_TYPE.OPERATIONAL }); } }); _defineProperty(this, "updateInput", async input => { const { activityProvider, searchProvider } = this.state; this.setState({ displayUrl: input }); if (activityProvider) { if (input.length === 0) { this.setState({ items: await this.getRecentItems(activityProvider), selectedIndex: -1 }); } else if (isSafeUrl(input)) { this.setState({ items: [], selectedIndex: -1, isLoading: false }); } else { const items = await this.getRecentItems(activityProvider, input); const shouldQuerySearchProvider = items.length < RECENT_SEARCH_LIST_SIZE && !!searchProvider; this.setState({ items, isLoading: shouldQuerySearchProvider }); if (shouldQuerySearchProvider) { this.debouncedQuickSearch(input, items, RECENT_SEARCH_LIST_SIZE); } } } }); _defineProperty(this, "createClearHandler", field => { return async () => { const { activityProvider } = this.state; switch (field) { case 'displayUrl': { this.setState({ [field]: '', items: !!activityProvider ? limit(await activityProvider.getRecentItems()) : [] }); if (this.urlInputContainer) { this.urlInputContainer.focus(); } break; } case 'displayText': { this.setState({ [field]: '' }); if (this.displayTextInputContainer) { this.displayTextInputContainer.focus(); } } } }; }); _defineProperty(this, "handleClickOutside", event => { if (event.target instanceof Element && this.wrapperRef.current && !this.wrapperRef.current.contains(event.target)) { const { view: { state, dispatch }, onClickAwayCallback } = this.props; onClickAwayCallback === null || onClickAwayCallback === void 0 ? void 0 : onClickAwayCallback(state, dispatch); } }); _defineProperty(this, "getScreenReaderText", () => { const { intl } = this.props; const { items, selectedIndex } = this.state; if (items.length && selectedIndex > -1) { const { name, container, lastUpdatedDate, lastViewedDate } = items[selectedIndex]; const date = transformTimeStamp(intl, lastViewedDate, lastUpdatedDate); return `${name}, ${container}, ${date === null || date === void 0 ? void 0 : date.pageAction} ${date === null || date === void 0 ? void 0 : date.dateString} ${(date === null || date === void 0 ? void 0 : date.timeSince) || ''}`; } }); _defineProperty(this, "listItemRefCallback", (el, id) => { if (!this.listItemRefs.current) { return; } if (el === null) { delete this.listItemRefs.current[id]; } else { this.listItemRefs.current[id] = el; } }); _defineProperty(this, "isUrlPopulatedWithSelectedItem", () => { /** * When we use ArrowKey to navigate through result items, * the URL field will be populated with the content of * selected item. * This function will check if the URL field is populated * with selected item. * It can be useful to detect whether we want to insert a * smartlink or a hyperlink with customized title */ const { items, selectedIndex, displayUrl } = this.state; const selectedItem = items[selectedIndex]; if (selectedItem && selectedItem.url === displayUrl) { return true; } return false; }); _defineProperty(this, "handleSelected", (href, text) => { this.handleInsert(href, text, INPUT_METHOD.TYPEAHEAD, 'click'); }); _defineProperty(this, "handleInsert", (href, title, inputType, interaction) => { const { searchSessionId, onSubmit } = this.props; const { items, selectedIndex, displayText } = this.state; if (onSubmit) { this.submitted = true; onSubmit(href, title, displayText, inputType); } if (interaction === 'click' || this.isUrlPopulatedWithSelectedItem()) { var _selectedItem$prefetc; /** * When it's a mouse click even or the selectedItem.url matches displayUrl, we think * it's selected from the result list and fire the * analytic */ const selectedItem = items[selectedIndex]; this.fireAnalytics({ action: ACTION.SELECTED, actionSubject: ACTION_SUBJECT.SEARCH_RESULT, attributes: { source: this.analyticSource, searchSessionId: searchSessionId !== null && searchSessionId !== void 0 ? searchSessionId : '', trigger: interaction, resultCount: items.length, selectedResultId: selectedItem.objectId, selectedRelativePosition: selectedIndex, prefetch: (_selectedItem$prefetc = selectedItem.prefetch) !== null && _selectedItem$prefetc !== void 0 ? _selectedItem$prefetc : false }, eventType: EVENT_TYPE.UI }); } }); _defineProperty(this, "handleMouseEnterResultItem", objectId => { const { items } = this.state; const index = findIndex(items, item => item.objectId === objectId); this.setState({ selectedIndex: index }); }); _defineProperty(this, "handleMouseLeaveResultItem", objectId => { const { items, selectedIndex } = this.state; const index = findIndex(items, item => item.objectId === objectId); // This is to avoid updating index that was set by other mouseenter event if (selectedIndex === index) { this.setState({ selectedIndex: -1 }); } }); _defineProperty(this, "handleSubmit", () => { const { displayUrl, selectedIndex, items } = this.state; const selectedItem = items[selectedIndex]; if (this.isUrlPopulatedWithSelectedItem()) { this.handleInsert(normalizeUrl(selectedItem.url), selectedItem.name, INPUT_METHOD.TYPEAHEAD, 'keyboard'); } else if (displayUrl && displayUrl.length > 0) { const url = normalizeUrl(displayUrl); if (url) { this.handleInsert(url, displayUrl, INPUT_METHOD.MANUAL, 'notselected'); } } }); _defineProperty(this, "handleClearTextKeyDown", event => { const KEY_CODE_TAB = 9; const { keyCode } = event; if (keyCode === KEY_CODE_TAB) { /** If there are items in the list, allow normal tabbing so focus moves to the next interactive element (in this case, a search result item). */ if (this.state.items.length > 0) { return; } if (!this.submitted) { const { displayUrl, displayText } = this.state; const url = normalizeUrl(displayUrl); this.handleInsert(url, displayText || displayUrl, INPUT_METHOD.MANUAL, 'notselected'); } event.preventDefault(); return; } }); _defineProperty(this, "handleListItemFocus", index => { this.setState({ selectedIndex: index }); }); _defineProperty(this, "handleListItemBlur", () => { this.setState({ selectedIndex: -1 }); }); // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any _defineProperty(this, "handleKeyDown", event => { const { items, selectedIndex } = this.state; const { view, onEscapeCallback, searchSessionId } = this.props; const { keyCode } = event; const KEY_CODE_ESCAPE = 27; const KEY_CODE_ARROW_DOWN = 40; const KEY_CODE_ARROW_UP = 38; if (keyCode === KEY_CODE_ESCAPE) { // escape event.preventDefault(); const { state, dispatch } = view; onEscapeCallback === null || onEscapeCallback === void 0 ? void 0 : onEscapeCallback(state, dispatch); return; } if (!items || !items.length) { return; } let updatedIndex = selectedIndex; if (keyCode === KEY_CODE_ARROW_DOWN) { // down event.preventDefault(); updatedIndex = (selectedIndex + 1) % items.length; } else if (keyCode === KEY_CODE_ARROW_UP) { // up event.preventDefault(); updatedIndex = selectedIndex > 0 ? selectedIndex - 1 : items.length - 1; } if ([KEY_CODE_ARROW_DOWN, KEY_CODE_ARROW_UP].includes(keyCode) && items[updatedIndex]) { if (this.listItemRefs.current) { var _this$listItemRefs$cu; (_this$listItemRefs$cu = this.listItemRefs.current[items[updatedIndex].objectId]) === null || _this$listItemRefs$cu === void 0 ? void 0 : _this$listItemRefs$cu.focus(); } this.setState({ selectedIndex: updatedIndex, displayUrl: items[updatedIndex].url }); this.fireAnalytics({ action: ACTION.HIGHLIGHTED, actionSubject: ACTION_SUBJECT.SEARCH_RESULT, attributes: { source: this.analyticSource, searchSessionId: searchSessionId !== null && searchSessionId !== void 0 ? searchSessionId : '', selectedResultId: items[updatedIndex].objectId, selectedRelativePosition: updatedIndex }, eventType: EVENT_TYPE.UI }); } }); _defineProperty(this, "updateTextInput", displayText => { this.setState({ displayText }); }); // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any _defineProperty(this, "handleCancel", e => { const { view: { state, dispatch }, onClickAwayCallback } = this.props; e.preventDefault(); onClickAwayCallback === null || onClickAwayCallback === void 0 ? void 0 : onClickAwayCallback(state, dispatch); }); this.state = { selectedIndex: -1, isLoading: false, displayUrl: normalizeUrl(props.displayUrl), displayText: props.displayText, items: [] }; /* Cache functions */ this.handleClearText = this.createClearHandler('displayUrl'); this.handleClearDisplayText = this.createClearHandler('displayText'); this.debouncedQuickSearch = debounce(this.quickSearch, 400); this.fireCustomAnalytics = fireAnalyticsEvent(props.createAnalyticsEvent); } async componentDidMount() { const { timesViewed, inputMethod, searchSessionId } = this.props; // Ignored via go/ees005 // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners, @atlaskit/platform/no-direct-document-usage document.addEventListener('mousedown', this.handleClickOutside); this.fireAnalytics({ action: ACTION.VIEWED, actionSubject: ACTION_SUBJECT.CREATE_LINK_INLINE_DIALOG, attributes: { timesViewed: timesViewed !== null && timesViewed !== void 0 ? timesViewed : 0, searchSessionId: searchSessionId !== null && searchSessionId !== void 0 ? searchSessionId : '', trigger: inputMethod !== null && inputMethod !== void 0 ? inputMethod : '' }, eventType: EVENT_TYPE.SCREEN }); const [activityProvider, searchProvider] = await Promise.all([this.props.activityProvider, this.props.searchProvider]); // loadInitialLinkSearchResult and updateInput rely on activityProvider being set in state flushSync(() => this.setState({ activityProvider, searchProvider })); await this.loadInitialLinkSearchResult(); } componentWillUnmount() { const { searchSessionId } = this.props; // Ignored via go/ees005 // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners, @atlaskit/platform/no-direct-document-usage document.removeEventListener('mousedown', this.handleClickOutside); if (!this.submitted) { this.fireAnalytics({ action: ACTION.DISMISSED, actionSubject: ACTION_SUBJECT.CREATE_LINK_INLINE_DIALOG, attributes: { source: this.analyticSource, searchSessionId: searchSessionId !== null && searchSessionId !== void 0 ? searchSessionId : '', trigger: 'blur' }, eventType: EVENT_TYPE.UI }); } } async getRecentItems(activityProvider, query) { const { searchSessionId } = this.props; const perfStart = performance.now(); try { const activityRecentItems = limit(query ? await activityProvider.searchRecent(query) : await activityProvider.getRecentItems()); const items = activityRecentItems.map(mapActivityProviderResultToLinkSearchItemData); const perfStop = performance.now(); const duration = perfStop - perfStart; this.fireAnalytics({ action: ACTION.INVOKED, actionSubject: ACTION_SUBJECT.SEARCH_RESULT, actionSubjectId: ACTION_SUBJECT_ID.RECENT_ACTIVITIES, attributes: { duration, count: items.length }, eventType: EVENT_TYPE.OPERATIONAL }); this.fireAnalytics({ action: ACTION.SHOWN, actionSubject: ACTION_SUBJECT.SEARCH_RESULT, actionSubjectId: ACTION_SUBJECT_ID.PRE_QUERY_SEARCH_RESULTS, attributes: { source: this.analyticSource, preQueryRequestDurationMs: duration, searchSessionId: searchSessionId !== null && searchSessionId !== void 0 ? searchSessionId : '', resultCount: items.length, results: activityRecentItems.map(item => { var _item$type; return { resultContentId: item.objectId, resultType: (_item$type = item.type) !== null && _item$type !== void 0 ? _item$type : '' }; }) }, eventType: EVENT_TYPE.UI }); return items; // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err) { const perfStop = performance.now(); const duration = perfStop - perfStart; this.fireAnalytics({ action: ACTION.INVOKED, actionSubject: ACTION_SUBJECT.SEARCH_RESULT, actionSubjectId: ACTION_SUBJECT_ID.RECENT_ACTIVITIES, attributes: { duration, count: -1, errorCode: err.status }, eventType: EVENT_TYPE.OPERATIONAL }); return []; } } fireAnalytics(payload) { if (this.props.createAnalyticsEvent && this.fireCustomAnalytics) { this.fireCustomAnalytics({ payload }); } } async loadInitialLinkSearchResult() { const { displayUrl, activityProvider } = this.state; try { if (!displayUrl && activityProvider) { this.setState({ isLoading: true }); const items = await this.getRecentItems(activityProvider); this.setState({ items }); } } finally { this.setState({ isLoading: false }); } } render() { const { items, isLoading, selectedIndex, displayUrl, displayText } = this.state; const { intl: { formatMessage } } = this.props; const formatClearLinkText = formatMessage(messages.clearLink); const screenReaderDescriptionId = 'search-recent-links-field-description'; const linkSearchListId = 'hyperlink-search-list'; const ariaActiveDescendant = selectedIndex > -1 ? `link-search-list-item-${selectedIndex}` : ''; const linkSearchInputId = 'search-recent-links-field-id'; const displayTextInputId = 'display-text-filed-id'; const browser = getBrowserInfo(); // Added workaround with a screen reader Announcer specifically for VoiceOver + Safari // as the Aria design pattern for combobox does not work in this case // for details: https://a11y-internal.atlassian.net/browse/AK-740 const screenReaderText = browser.safari && this.getScreenReaderText(); const hyperlinkElement = jsx("div", { "aria-label": formatMessage(messages.hyperlinkAriaLabel) // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 , className: "recent-list", "data-testid": "hyperlink-add-toolbar" }, jsx("div", { // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 css: [container, narrowContainerWidth, containerPadding], ref: this.wrapperRef }, jsx("label", { htmlFor: linkSearchInputId, css: inputLabel }, formatMessage(messages.linkVisibleLabel)), jsx("div", { css: [inputWrapper, inputWrapperPosition] }, screenReaderText && jsx(Announcer, { ariaLive: "assertive", text: screenReaderText, ariaRelevant: "additions", delay: 250 }), jsx("div", { css: visuallyHiddenStyles, "aria-hidden": "true", id: screenReaderDescriptionId }, formatMessage(messages.searchLinkAriaDescription)), jsx(PanelTextInput, { role: "combobox", ariaExpanded: items && items.length > 0 && !isLoading, ariaActiveDescendant: ariaActiveDescendant, ariaControls: linkSearchListId, ariaAutoComplete: true, describedById: screenReaderDescriptionId // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , ref: ele => this.urlInputContainer = ele, testId: 'link-url', onSubmit: this.handleSubmit, onChange: this.updateInput // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , autoFocus: { preventScroll: true }, onCancel: this.handleCancel, defaultValue: displayUrl, onKeyDown: this.handleKeyDown, inputId: linkSearchInputId }), displayUrl && jsx("div", { css: clearTextWrapper }, jsx(Tooltip, { content: formatClearLinkText }, jsx(Pressable, { xcss: clearTextButtonStyles, onClick: this.handleClearText }, jsx(CrossCircleIcon, { label: formatClearLinkText, color: "var(--ds-icon-subtle, #505258)" }))))), jsx("label", { htmlFor: displayTextInputId, css: [inputLabel, textLabelMargin] }, formatMessage(messages.textVisibleLabel)), jsx("div", { css: [inputWrapper, inputWrapperPosition] }, jsx(PanelTextInput // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , { ref: ele => this.displayTextInputContainer = ele, testId: 'link-text', onChange: this.updateTextInput, onCancel: this.handleCancel, defaultValue: displayText, onSubmit: this.handleSubmit, onKeyDown: this.handleKeyDown, inputId: displayTextInputId }), displayText && jsx("div", { css: clearTextWrapper }, jsx(Tooltip, { content: formatMessage(messages.clearText) }, jsx(Pressable, { xcss: clearTextButtonStyles, onClick: this.handleClearDisplayText, onKeyDown: this.handleClearTextKeyDown }, jsx(CrossCircleIcon, { label: formatMessage(messages.clearText), color: "var(--ds-icon-subtle, #505258)" }))))), jsx("div", { css: visuallyHiddenStyles, "aria-live": "polite", "aria-atomic": "true", id: "fabric.editor.hyperlink.suggested.results" }, displayUrl && formatMessage(messages.searchLinkResults, { count: items.length })), jsx(LinkSearchList, { ariaControls: "fabric.editor.hyperlink.suggested.results", id: linkSearchListId, role: "listbox", items: items, isLoading: isLoading, selectedIndex: selectedIndex, listItemRefCallback: this.listItemRefCallback, onFocus: this.handleListItemFocus, onBlur: this.handleListItemBlur, onKeyDown: this.handleKeyDown, onSelect: this.handleSelected, onMouseEnter: this.handleMouseEnterResultItem, onMouseLeave: this.handleMouseLeaveResultItem }))); if (expValEquals('platform_editor_a11y_escape_link_dialog', 'isEnabled', true)) { return ( // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) jsx(FocusLock, { returnFocus: { preventScroll: true }, focusOptions: { preventScroll: true } }, hyperlinkElement) ); } return hyperlinkElement; } } function findIndex(array, predicate) { let index = -1; array.some((item, i) => { if (predicate(item)) { index = i; return true; } return false; }); return index; } function limit(items) { return items.slice(0, RECENT_SEARCH_LIST_SIZE); } // eslint-disable-next-line @typescript-eslint/ban-types export const HyperlinkLinkAddToolbarWithIntl = injectIntl(HyperlinkLinkAddToolbar); const _default_1 = withAnalyticsEvents()(HyperlinkLinkAddToolbarWithIntl); export default _default_1;