UNPKG

@atlaskit/editor-common

Version:

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

139 lines (132 loc) 4.48 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import { getDocument } from '@atlaskit/browser-apis'; import { EXPERIENCE_FAILURE_REASON } from './consts'; import { popupWithNestedElement } from './experience-utils'; const PORTAL_CONTAINER_SELECTOR = 'body > .atlaskit-portal-container'; /** * Popup check type determines how popups are observed based on their DOM location: * - 'inline': Popups appearing in toolbar button-groups (emoji, media, table selector, image) * - 'editorRoot': Popups attached to editor root (e.g., mention popups) * - 'editorContent': Content-level popups or modals in portal containers (e.g., block menu) * - 'portalRoot': Popups in body > .atlaskit-portal-container (e.g., flags, modals) */ export class ExperienceCheckPopupMutation { constructor(config) { _defineProperty(this, "observers", []); this.config = config; } /** * Returns the list of DOM elements to observe based on popup type. */ getObserveTargets() { const { config } = this; switch (config.type) { case 'inline': return this.getInlineTargets(config); case 'editorRoot': return this.getEditorRootTargets(config); case 'editorContent': return this.getEditorContentTargets(config); case 'portalRoot': return this.getPortalRootTargets(); } } /** * For 'portalRoot' type: observe .atlaskit-portal-container. * Popups like flags and modals render in body > .atlaskit-portal-container. */ getPortalRootTargets() { var _getDocument; const portalContainer = (_getDocument = getDocument()) === null || _getDocument === void 0 ? void 0 : _getDocument.querySelector(PORTAL_CONTAINER_SELECTOR); return portalContainer ? [portalContainer] : []; } /** * For 'editorContent' type: observe the target (mount point) and any existing * [data-editor-popup] wrappers within it. Content-level popups and modals * appear in portal containers. */ getEditorContentTargets(config) { const target = config.getTarget(); if (!target) { return []; } const targets = [target]; const wrappers = target.querySelectorAll('[data-editor-popup]'); for (const wrapper of wrappers) { targets.push(wrapper); } return targets; } /** * For 'inline' type: observe the target element directly. * The caller is responsible for resolving the correct container * (e.g. the toolbar button-group) via the getTarget function. */ getInlineTargets(config) { const target = config.getTarget(); if (!target) { return []; } return [target]; } /** * For 'editorRoot' type: observe the actual editor root container. * The editorDom is the ProseMirror element, but popups appear as direct children * of the parent .akEditor container. So we observe the parent of editorDom. */ getEditorRootTargets(config) { const targets = []; const editorDom = config.getEditorDom(); if (editorDom) { const editorRoot = editorDom.closest('.akEditor') || editorDom.parentElement; if (editorRoot instanceof HTMLElement) { targets.push(editorRoot); } } return targets; } start(callback) { const doc = getDocument(); const observeTargets = this.getObserveTargets(); if (!doc || !observeTargets.length) { callback({ status: 'failure', reason: EXPERIENCE_FAILURE_REASON.DOM_MUTATION_TARGET_NOT_FOUND }); return; } const query = this.config.nestedElementQuery; const subtree = this.config.type === 'inline' && this.config.subtree === true; const observe = el => { const observer = new MutationObserver(onMutation); observer.observe(el, { childList: true, subtree }); this.observers.push(observer); }; const onMutation = mutations => { const found = mutations.some(({ type, addedNodes }) => type === 'childList' && [...addedNodes].some(node => node instanceof HTMLElement && (popupWithNestedElement(node, query) || node.matches(query) || node.querySelector(query) !== null))); if (found) { callback({ status: 'success' }); return; } }; for (const observeTarget of observeTargets) { observe(observeTarget); } } stop() { for (const observer of this.observers) { observer.disconnect(); } this.observers = []; } }