@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
139 lines (132 loc) • 4.48 kB
JavaScript
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 = [];
}
}