@atlaskit/editor-plugin-insert-block 8.7.2 → 8.7.3

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.
@@ -1,6 +1,14 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import { getDocument } from '@atlaskit/browser-apis';
3
3
  import { EXPERIENCE_FAILURE_REASON, popupWithNestedElement } from '@atlaskit/editor-common/experiences';
4
+
5
+ /**
6
+ * Popup check type determines how popups are observed based on their DOM location:
7
+ * - 'inline': Popups appearing in toolbar button-groups (emoji, media, table selector, image)
8
+ * - 'editorRoot': Popups attached to editor root (e.g., mention popups)
9
+ * - 'editorContent': Content-level popups or modals in portal containers (e.g., block menu)
10
+ */
11
+
4
12
  /**
5
13
  * DOM marker selectors for node types inserted via toolbar actions.
6
14
  * Matches outermost wrapper elements set synchronously by ReactNodeView
@@ -21,42 +29,71 @@ export const isToolbarButtonClick = (target, testId) => {
21
29
  }
22
30
  return !button.disabled && button.getAttribute('aria-disabled') !== 'true';
23
31
  };
24
-
25
- /**
26
- * ExperienceCheck that observes popup mount point and all its
27
- * `[data-editor-popup]` children with `{ childList: true }` (no subtree).
28
- *
29
- * Detects when a popup containing the given nested element is added to the
30
- * DOM — either as a new `[data-editor-popup]` direct child, or as content
31
- * rendered inside an existing `[data-editor-popup]` wrapper.
32
- */
33
- export const TYPEAHEAD_DECORATION_SELECTOR = '[data-type-ahead="typeaheadDecoration"]';
34
- export const handleTypeAheadOpenDomMutation = ({
35
- mutations
36
- }) => {
37
- for (const mutation of mutations) {
38
- if (mutation.type !== 'childList') {
39
- continue;
40
- }
41
- for (const node of mutation.addedNodes) {
42
- if (!(node instanceof HTMLElement)) {
43
- continue;
44
- }
45
- if (node.matches(TYPEAHEAD_DECORATION_SELECTOR) || node.querySelector(TYPEAHEAD_DECORATION_SELECTOR)) {
46
- return {
47
- status: 'success'
48
- };
49
- }
50
- }
51
- }
52
- return undefined;
53
- };
54
32
  export class ExperienceCheckPopupMutation {
55
- constructor(nestedElementQuery, getTarget, getEditorDom) {
33
+ constructor(nestedElementQuery, getTarget, getEditorDom, type = 'editorRoot') {
56
34
  _defineProperty(this, "observers", []);
57
35
  this.nestedElementQuery = nestedElementQuery;
58
36
  this.getTarget = getTarget;
59
37
  this.getEditorDom = getEditorDom;
38
+ this.type = type;
39
+ }
40
+
41
+ /**
42
+ * Returns the list of DOM elements to observe based on popup type.
43
+ */
44
+ getObserveTargets() {
45
+ switch (this.type) {
46
+ case 'inline':
47
+ return this.getInlineTargets();
48
+ case 'editorRoot':
49
+ return this.getEditorRootTargets();
50
+ }
51
+ // Should never reach here - all types handled above
52
+ return [];
53
+ }
54
+
55
+ /**
56
+ * For 'inline' type: observe only the button-group container.
57
+ * The target passed in should be the button-group (or button within it) from getInlinePopupTarget().
58
+ * Inline popups appear as direct children of button-group elements.
59
+ */
60
+ getInlineTargets() {
61
+ const target = this.getTarget();
62
+ if (!target) {
63
+ return [];
64
+ }
65
+
66
+ // Walk up to find the button-group container
67
+ const buttonGroup = target.closest('[data-toolbar-component="button-group"]');
68
+
69
+ // Target is already the button-group or button from getInlinePopupTarget()
70
+ // Just observe this single element
71
+ return buttonGroup ? [buttonGroup, target] : [target];
72
+ }
73
+
74
+ /**
75
+ * For 'editorRoot' type: observe the actual editor root container.
76
+ * The editorDom is the ProseMirror element, but popups appear as direct children
77
+ * of the parent .akEditor container. So we observe the parent of editorDom.
78
+ * No portal observation needed.
79
+ */
80
+ getEditorRootTargets() {
81
+ const targets = [];
82
+ const editorDom = this.getEditorDom();
83
+ if (editorDom) {
84
+ // Find the actual editor root (.akEditor) by walking up the DOM
85
+ const editorRoot = editorDom.closest('.akEditor') || editorDom.parentElement;
86
+ if (editorRoot instanceof HTMLElement) {
87
+ targets.push(editorRoot);
88
+
89
+ // Observe existing [data-editor-popup] wrappers
90
+ const wrappers = editorRoot.querySelectorAll('[data-editor-popup]');
91
+ for (const wrapper of wrappers) {
92
+ targets.push(wrapper);
93
+ }
94
+ }
95
+ }
96
+ return targets;
60
97
  }
61
98
  start(callback) {
62
99
  this.stop();
@@ -86,7 +123,8 @@ export class ExperienceCheckPopupMutation {
86
123
  if (!(node instanceof HTMLElement)) {
87
124
  continue;
88
125
  }
89
- if (popupWithNestedElement(node, query) || node.matches(query) || !!node.querySelector(query)) {
126
+ const found = popupWithNestedElement(node, query) || node.matches(query) || !!node.querySelector(query);
127
+ if (found) {
90
128
  this.stop();
91
129
  callback({
92
130
  status: 'success'
@@ -103,66 +141,12 @@ export class ExperienceCheckPopupMutation {
103
141
  });
104
142
  this.observers.push(observer);
105
143
  };
106
- observe(target);
107
- for (const wrapper of target.querySelectorAll('[data-editor-popup]')) {
108
- observe(wrapper);
109
- }
110
- const portalContainer = doc.querySelector('.atlaskit-portal-container');
111
- if (portalContainer instanceof HTMLElement) {
112
- const observePortal = portal => {
113
- observe(portal);
114
- for (const child of portal.children) {
115
- if (child instanceof HTMLElement) {
116
- observe(child);
117
- }
118
- }
119
- };
120
- const containerObserver = new MutationObserver(mutations => {
121
- for (const mutation of mutations) {
122
- if (mutation.type !== 'childList') {
123
- continue;
124
- }
125
- for (const node of mutation.addedNodes) {
126
- if (node instanceof HTMLElement) {
127
- observePortal(node);
128
- }
129
- }
130
- }
131
- onMutation(mutations);
132
- });
133
- containerObserver.observe(portalContainer, {
134
- childList: true
135
- });
136
- this.observers.push(containerObserver);
137
- for (const portal of portalContainer.querySelectorAll('.atlaskit-portal')) {
138
- observePortal(portal);
139
- }
140
- }
141
- const editorDom = this.getEditorDom();
142
- if (editorDom !== null && editorDom !== void 0 && editorDom.parentElement) {
143
- observe(editorDom.parentElement);
144
- }
145
144
 
146
- // Two-frame DOM check to handle cases where rendering happens before
147
- // observers are attached.
148
- const checkDom = () => {
149
- if (doc.querySelector(query)) {
150
- this.stop();
151
- callback({
152
- status: 'success'
153
- });
154
- return;
155
- }
156
- requestAnimationFrame(() => {
157
- if (doc.querySelector(query)) {
158
- this.stop();
159
- callback({
160
- status: 'success'
161
- });
162
- }
163
- });
164
- };
165
- requestAnimationFrame(checkDom);
145
+ // Get type-specific targets and observe them
146
+ const observeTargets = this.getObserveTargets();
147
+ for (const observeTarget of observeTargets) {
148
+ observe(observeTarget);
149
+ }
166
150
  }
167
151
  stop() {
168
152
  for (const observer of this.observers) {
@@ -4,7 +4,7 @@ import { Experience, EXPERIENCE_ID, ExperienceCheckDomMutation, ExperienceCheckT
4
4
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
5
5
  import { TOOLBAR_BUTTON_TEST_ID } from '@atlaskit/editor-common/toolbar';
6
6
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
7
- import { ExperienceCheckPopupMutation, getParentDOMAtSelection, handleEditorNodeInsertDomMutation, handleTypeAheadOpenDomMutation, isToolbarButtonClick } from './toolbar-experience-utils';
7
+ import { ExperienceCheckPopupMutation, getParentDOMAtSelection, handleEditorNodeInsertDomMutation, isToolbarButtonClick } from './toolbar-experience-utils';
8
8
  var pluginKey = new PluginKey('toolbarActionExperiences');
9
9
  var TIMEOUT_DURATION = 1000;
10
10
  var PRIMARY_TOOLBAR = 'primaryToolbar';
@@ -17,6 +17,7 @@ export var getToolbarActionExperiencesPlugin = function getToolbarActionExperien
17
17
  dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent;
18
18
  var editorView;
19
19
  var popupTargetEl;
20
+ var lastClickedToolbarButton;
20
21
  var getPopupsTarget = function getPopupsTarget() {
21
22
  if (!popupTargetEl) {
22
23
  var _editorView;
@@ -31,6 +32,17 @@ export var getToolbarActionExperiencesPlugin = function getToolbarActionExperien
31
32
  }
32
33
  return null;
33
34
  };
35
+
36
+ /**
37
+ * For inline popups, returns the button-group ancestor of the clicked toolbar button.
38
+ * This allows inline popup checks to observe only the relevant button-group.
39
+ */
40
+ var getInlinePopupTarget = function getInlinePopupTarget() {
41
+ if (!lastClickedToolbarButton) {
42
+ return undefined;
43
+ }
44
+ return lastClickedToolbarButton;
45
+ };
34
46
  var narrowParentObserveConfig = function narrowParentObserveConfig() {
35
47
  var _getParentDOMAtSelect;
36
48
  return {
@@ -65,45 +77,39 @@ export var getToolbarActionExperiencesPlugin = function getToolbarActionExperien
65
77
  });
66
78
  };
67
79
  var createPopupExperience = function createPopupExperience(action, popupSelector) {
80
+ var type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'editorRoot';
68
81
  return new Experience(EXPERIENCE_ID.TOOLBAR_ACTION, {
69
82
  action: action,
70
83
  actionSubjectId: PRIMARY_TOOLBAR,
71
84
  dispatchAnalyticsEvent: dispatchAnalyticsEvent,
72
85
  checks: [new ExperienceCheckTimeout({
73
86
  durationMs: TIMEOUT_DURATION
74
- }), new ExperienceCheckPopupMutation(popupSelector, getPopupsTarget, getEditorDom)]
87
+ }), new ExperienceCheckPopupMutation(popupSelector, type === 'inline' ? getInlinePopupTarget : getPopupsTarget, getEditorDom, type)]
75
88
  });
76
89
  };
77
90
  var experienceButtonMappings = [{
78
- experience: createPopupExperience('emoji', '[data-emoji-picker-container]'),
91
+ experience: createPopupExperience('insert', '[data-testid="popup-wrapper"]', 'inline'),
92
+ buttonTestId: TOOLBAR_BUTTON_TEST_ID.INSERT
93
+ }, {
94
+ experience: createPopupExperience('emoji', '[data-emoji-picker-container], [data-emoji-picker-container="true"], [data-testid="popup-wrapper"]', 'inline'),
79
95
  buttonTestId: TOOLBAR_BUTTON_TEST_ID.EMOJI
80
96
  }, {
81
- experience: createPopupExperience('media', '[id="local-media-upload-button"], [data-testid="media-picker-file-input"]'),
97
+ experience: createPopupExperience('media', '[data-testid="popup-wrapper"]', 'inline'),
82
98
  buttonTestId: TOOLBAR_BUTTON_TEST_ID.MEDIA
83
99
  }, {
84
- experience: new Experience(EXPERIENCE_ID.TOOLBAR_ACTION, {
85
- action: 'mention',
86
- actionSubjectId: PRIMARY_TOOLBAR,
87
- dispatchAnalyticsEvent: dispatchAnalyticsEvent,
88
- checks: [new ExperienceCheckTimeout({
89
- durationMs: TIMEOUT_DURATION
90
- }), new ExperienceCheckDomMutation({
91
- onDomMutation: handleTypeAheadOpenDomMutation,
92
- observeConfig: narrowParentObserveConfig
93
- })]
94
- }),
100
+ experience: createPopupExperience('mention', '[data-testid="popup-wrapper"], [data-type-ahead="typeaheadDecoration"]', 'editorRoot'),
95
101
  buttonTestId: TOOLBAR_BUTTON_TEST_ID.MENTION
96
102
  }, {
97
103
  experience: createNodeInsertExperience('table'),
98
104
  buttonTestId: TOOLBAR_BUTTON_TEST_ID.TABLE
99
105
  }, {
100
- experience: createPopupExperience('tableSelector', '[aria-label*="table size"], [data-testid*="table-selector"]'),
106
+ experience: createPopupExperience('tableSelector', '[aria-label*="table size"], [data-testid*="table-selector"]', 'inline'),
101
107
  buttonTestId: TOOLBAR_BUTTON_TEST_ID.TABLE_SELECTOR
102
108
  }, {
103
109
  experience: createNodeInsertExperience('layout'),
104
110
  buttonTestId: TOOLBAR_BUTTON_TEST_ID.LAYOUT
105
111
  }, {
106
- experience: createPopupExperience('image', '[id="local-media-upload-button"], [data-testid="media-picker-file-input"]'),
112
+ experience: createPopupExperience('image', '[data-testid="popup-wrapper"]', 'inline'),
107
113
  buttonTestId: TOOLBAR_BUTTON_TEST_ID.IMAGE
108
114
  }, {
109
115
  experience: createNodeInsertExperience('action'),
@@ -115,6 +121,8 @@ export var getToolbarActionExperiencesPlugin = function getToolbarActionExperien
115
121
  experience = _experienceButtonMapp2.experience,
116
122
  buttonTestId = _experienceButtonMapp2.buttonTestId;
117
123
  if (isToolbarButtonClick(target, buttonTestId)) {
124
+ // Store the clicked button so inline popup checks can find its button-group
125
+ lastClickedToolbarButton = target;
118
126
  experience.start({
119
127
  forceRestart: true
120
128
  });