@atlaskit/editor-plugin-mentions 13.1.1 → 13.3.0

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.
@@ -0,0 +1,168 @@
1
+ /* disabledTooltipRenderer.tsx generated by @compiled/babel-plugin v0.39.1 */
2
+ import "./disabledTooltipRenderer.compiled.css";
3
+ import { ax, ix } from "@compiled/react/runtime";
4
+ import React, { useEffect, useState } from 'react';
5
+ import { bindAll } from 'bind-event-listener';
6
+ // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- mirror existing renderer pattern
7
+ import uuid from 'uuid/v4';
8
+ import Tooltip from '@atlaskit/tooltip';
9
+ const styles = {
10
+ hiddenTrigger: "_1e0cglyw"
11
+ };
12
+
13
+ /**
14
+ * ADS `<Tooltip>` hands us a ref via its render-prop child. That ref may be
15
+ * either a callback ref or a mutable ref object — React supports both shapes
16
+ * and ADS's typing keeps both possible. This helper assigns the supplied
17
+ * element to whichever ref shape was provided so the tooltip ends up using
18
+ * the chip's bounding box for positioning and its DOM for trigger events.
19
+ */
20
+ const assignRef = (ref, element) => {
21
+ if (typeof ref === 'function') {
22
+ ref(element);
23
+ } else if (ref) {
24
+ ref.current = element;
25
+ }
26
+ };
27
+
28
+ /**
29
+ * Names of the React-style trigger-prop handlers we forward from ADS
30
+ * `<Tooltip>` onto the chip element.
31
+ */
32
+
33
+ /**
34
+ * Maps each React-style trigger-prop handler ADS `<Tooltip>` hands us in its
35
+ * render-prop callback to the DOM event name we attach to the chip via
36
+ * `bind-event-listener`. The chip lives in the ProseMirror document and is
37
+ * not a React child of the tooltip, so we have to bridge React handlers to
38
+ * native event listeners ourselves.
39
+ *
40
+ * Names are listed in the same order ADS internally registers them.
41
+ */
42
+ const TRIGGER_EVENT_MAP = [['onMouseOver', 'mouseover'], ['onMouseOut', 'mouseout'], ['onMouseMove', 'mousemove'], ['onMouseDown', 'mousedown'], ['onFocus', 'focusin'], ['onBlur', 'focusout']];
43
+
44
+ /**
45
+ * Anchors an ADS `<Tooltip>` to a ProseMirror chip element that is **not** a
46
+ * React child of the tooltip. The chip itself stays in the PM document; this
47
+ * renderer mounts an always-present React subtree via `portalProviderAPI` whose
48
+ * sole job is to host the tooltip and forward the ADS-provided trigger ref +
49
+ * event handlers onto the chip.
50
+ *
51
+ * The forwarding is deliberate: ADS `<Tooltip>` expects to attach its hover /
52
+ * focus / blur listeners to its render-prop child, but our render-prop child
53
+ * is a `display: none` placeholder span that no real event can reach. We bridge
54
+ * those listeners to the chip via `addEventListener` so the tooltip opens and
55
+ * closes in response to the user actually interacting with the chip — keyboard
56
+ * focus, mouse hover, screen-reader focus, the lot.
57
+ */
58
+
59
+ /**
60
+ * Hook that mirrors ADS Tooltip's React trigger props onto a DOM element
61
+ * which is not a React child of the tooltip. Re-attaches whenever ADS hands
62
+ * us a fresh set of props (which happens on every render of the render-prop
63
+ * child) and cleans up on unmount.
64
+ *
65
+ * We use `bind-event-listener`'s `bindAll` rather than raw
66
+ * `addEventListener` / `removeEventListener` calls so the lifetime of every
67
+ * subscription is paired with a single `UnbindFn` — the AFM-mandated pattern
68
+ * for keeping listener bookkeeping leak-free.
69
+ */
70
+ const useBridgedTriggerListeners = (chip, triggerProps) => {
71
+ useEffect(() => {
72
+ // Build the bindAll bindings array out of only the handlers ADS actually
73
+ // supplied. Each React handler is wrapped in a thin adapter so the
74
+ // listener signature matches the native `EventListener` shape; ADS
75
+ // handlers ignore the synthetic-event-only fields they don't use
76
+ // (target, currentTarget), so passing the raw DOM event through
77
+ // `unknown` is safe in practice.
78
+ const bindings = TRIGGER_EVENT_MAP.flatMap(([propName, eventName]) => {
79
+ const handler = triggerProps[propName];
80
+ if (!handler) {
81
+ return [];
82
+ }
83
+ return [{
84
+ type: eventName,
85
+ listener: event => {
86
+ handler(event);
87
+ }
88
+ }];
89
+ });
90
+ if (bindings.length === 0) {
91
+ return;
92
+ }
93
+ const unbind = bindAll(chip, bindings);
94
+ return unbind;
95
+ }, [chip, triggerProps]);
96
+ };
97
+ const AnchoredTooltip = ({
98
+ subscribe,
99
+ getInitialTooltip,
100
+ referenceElement
101
+ }) => {
102
+ const [tooltip, setTooltipState] = useState(getInitialTooltip);
103
+ useEffect(() => subscribe(setTooltipState), [subscribe]);
104
+ if (!tooltip) {
105
+ return null;
106
+ }
107
+ return /*#__PURE__*/React.createElement(Tooltip, {
108
+ content: tooltip,
109
+ position: "top"
110
+ }, tooltipProps => /*#__PURE__*/React.createElement(TriggerBridge, {
111
+ referenceElement: referenceElement,
112
+ tooltipProps: tooltipProps
113
+ }));
114
+ };
115
+
116
+ /**
117
+ * Render-prop child for ADS `<Tooltip>`. The element it returns is a
118
+ * `display: none` placeholder that satisfies the render-prop API; the
119
+ * real trigger surface is the chip in the PM document, which receives
120
+ * both the ADS ref and the bridged event listeners via the side-effects
121
+ * declared on this component.
122
+ */
123
+ const TriggerBridge = ({
124
+ referenceElement,
125
+ tooltipProps
126
+ }) => {
127
+ useBridgedTriggerListeners(referenceElement, tooltipProps);
128
+ return /*#__PURE__*/React.createElement("span", {
129
+ ref: () => assignRef(tooltipProps.ref, referenceElement),
130
+ "aria-hidden": "true",
131
+ className: ax([styles.hiddenTrigger])
132
+ });
133
+ };
134
+ export const disabledTooltipRenderer = ({
135
+ chipElement,
136
+ portalProviderAPI
137
+ }) => {
138
+ // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- mirror existing renderer pattern
139
+ const key = uuid();
140
+ let currentTooltip;
141
+ const listeners = new Set();
142
+ const broadcast = () => {
143
+ listeners.forEach(listener => listener(currentTooltip));
144
+ };
145
+ const getInitialTooltip = () => currentTooltip;
146
+ const subscribe = listener => {
147
+ listeners.add(listener);
148
+ return () => {
149
+ listeners.delete(listener);
150
+ };
151
+ };
152
+ const renderElement = () => /*#__PURE__*/React.createElement(AnchoredTooltip, {
153
+ referenceElement: chipElement,
154
+ getInitialTooltip: getInitialTooltip,
155
+ subscribe: subscribe
156
+ });
157
+ portalProviderAPI.render(renderElement, chipElement, key);
158
+ return {
159
+ setTooltip(text) {
160
+ currentTooltip = text;
161
+ broadcast();
162
+ },
163
+ destroy() {
164
+ portalProviderAPI.remove(key);
165
+ listeners.clear();
166
+ }
167
+ };
168
+ };
@@ -1,4 +1,3 @@
1
- import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
1
  import { getBrowserInfo } from '@atlaskit/editor-common/browser';
3
2
  import { ZERO_WIDTH_SPACE } from '@atlaskit/editor-common/whitespace';
4
3
  import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
@@ -6,6 +5,7 @@ import { isResolvingMentionProvider, MentionNameStatus } from '@atlaskit/mention
6
5
  import { isRestricted } from '@atlaskit/mention/types';
7
6
  import { fg } from '@atlaskit/platform-feature-flags';
8
7
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
8
+ import { disabledTooltipRenderer } from './disabledTooltipRenderer';
9
9
  import { profileCardRenderer } from './profileCardRenderer';
10
10
  const primitiveClassName = 'editor-mention-primitive';
11
11
  // @ts-ignore - TS1501 TypeScript 5.9.2 upgrade
@@ -55,7 +55,10 @@ const handleProviderName = async (mentionProvider, node) => {
55
55
  return processName(resolvedNameDetail);
56
56
  }
57
57
  };
58
- const getNewState = (isHighlighted, isRestricted) => {
58
+ const getNewState = (isHighlighted, isRestricted, isDisabled) => {
59
+ if (isDisabled) {
60
+ return 'disabled';
61
+ }
59
62
  if (isHighlighted) {
60
63
  return 'self';
61
64
  }
@@ -67,7 +70,6 @@ const getNewState = (isHighlighted, isRestricted) => {
67
70
  export class MentionNodeView {
68
71
  constructor(node, config) {
69
72
  var _this$domElement$quer, _api$mention$sharedSt;
70
- _defineProperty(this, "state", 'default');
71
73
  const {
72
74
  options,
73
75
  api,
@@ -87,10 +89,12 @@ export class MentionNodeView {
87
89
  mentionProvider
88
90
  } = (_api$mention$sharedSt = api === null || api === void 0 ? void 0 : api.mention.sharedState.currentState()) !== null && _api$mention$sharedSt !== void 0 ? _api$mention$sharedSt : {};
89
91
  this.updateState(mentionProvider);
92
+ this.subscribeToProviderDisabledStateChanges(mentionProvider);
90
93
  this.cleanup = api === null || api === void 0 ? void 0 : api.mention.sharedState.onChange(({
91
94
  nextSharedState
92
95
  }) => {
93
96
  this.updateState(nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.mentionProvider);
97
+ this.subscribeToProviderDisabledStateChanges(nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.mentionProvider);
94
98
  });
95
99
  const {
96
100
  destroyProfileCard,
@@ -115,10 +119,88 @@ export class MentionNodeView {
115
119
  this.destroyProfileCard = destroyProfileCard;
116
120
  this.removeProfileCard = removeProfileCard;
117
121
  }
118
- setClassList(state) {
119
- var _this$mentionPrimitiv, _this$mentionPrimitiv2;
122
+ setClassList(state, disabledTooltip) {
123
+ var _this$mentionPrimitiv, _this$mentionPrimitiv2, _this$mentionPrimitiv3;
120
124
  (_this$mentionPrimitiv = this.mentionPrimitiveElement) === null || _this$mentionPrimitiv === void 0 ? void 0 : _this$mentionPrimitiv.classList.toggle('mention-self', state === 'self');
121
125
  (_this$mentionPrimitiv2 = this.mentionPrimitiveElement) === null || _this$mentionPrimitiv2 === void 0 ? void 0 : _this$mentionPrimitiv2.classList.toggle('mention-restricted', state === 'restricted');
126
+ (_this$mentionPrimitiv3 = this.mentionPrimitiveElement) === null || _this$mentionPrimitiv3 === void 0 ? void 0 : _this$mentionPrimitiv3.classList.toggle('mention-disabled', state === 'disabled');
127
+ // Mirror the React `<Mention>` a11y behaviour: when the chip is
128
+ // disabled, expose `aria-disabled` so assistive tech announces it as
129
+ // such. Also surface the tooltip text via `aria-label` so screen-reader
130
+ // users hear *why* the chip is disabled, matching the React `<Mention>`
131
+ // behaviour at `Mention/index.tsx` line 152.
132
+ if (this.domElement) {
133
+ if (state === 'disabled') {
134
+ this.domElement.setAttribute('aria-disabled', 'true');
135
+ if (disabledTooltip) {
136
+ const text = this.node.attrs.text || '@...';
137
+ this.domElement.setAttribute('aria-label', `${text} — ${disabledTooltip}`);
138
+ }
139
+ } else {
140
+ this.domElement.removeAttribute('aria-disabled');
141
+ this.domElement.removeAttribute('aria-label');
142
+ }
143
+ }
144
+ }
145
+ getDisabledState(mentionProvider) {
146
+ var _mentionProvider$getM;
147
+ const input = {
148
+ id: this.node.attrs.id
149
+ };
150
+ return mentionProvider === null || mentionProvider === void 0 ? void 0 : (_mentionProvider$getM = mentionProvider.getMentionDisabledState) === null || _mentionProvider$getM === void 0 ? void 0 : _mentionProvider$getM.call(mentionProvider, input);
151
+ }
152
+
153
+ /**
154
+ * Subscribes this NodeView to disabled-state-change notifications on the
155
+ * supplied provider so already-rendered chips can re-evaluate themselves
156
+ * when the consumer's predicate inputs change (e.g. the active agent
157
+ * selection toggling in Rovo Chat). No-op for providers that don't
158
+ * implement `subscribeToDisabledStateChanges`.
159
+ *
160
+ * Idempotent: re-calling with the same provider keeps the existing
161
+ * subscription; passing a different provider tears the old subscription
162
+ * down before attaching the new one. Safe to call from the sharedState
163
+ * `onChange` handler when the editor swaps providers.
164
+ */
165
+ subscribeToProviderDisabledStateChanges(mentionProvider) {
166
+ var _this$unsubscribeFrom;
167
+ if (this.subscribedProvider === mentionProvider) {
168
+ return;
169
+ }
170
+ (_this$unsubscribeFrom = this.unsubscribeFromDisabledStateChanges) === null || _this$unsubscribeFrom === void 0 ? void 0 : _this$unsubscribeFrom.call(this);
171
+ this.unsubscribeFromDisabledStateChanges = undefined;
172
+ this.subscribedProvider = mentionProvider;
173
+ if (!(mentionProvider !== null && mentionProvider !== void 0 && mentionProvider.subscribeToDisabledStateChanges)) {
174
+ return;
175
+ }
176
+ this.unsubscribeFromDisabledStateChanges = mentionProvider.subscribeToDisabledStateChanges(() => {
177
+ this.updateState(this.subscribedProvider);
178
+ });
179
+ }
180
+ syncDisabledTooltip(disabledState) {
181
+ // Capture the tooltip text into a local so the rest of the method can
182
+ // branch on a truthy string instead of re-asserting non-null fields
183
+ // off of `disabledState`.
184
+ const tooltipText = disabledState !== null && disabledState !== void 0 && disabledState.disabled ? disabledState.tooltip : undefined;
185
+ const chip = this.mentionPrimitiveElement;
186
+ const {
187
+ portalProviderAPI
188
+ } = this.config;
189
+ if (!chip || !portalProviderAPI) {
190
+ return;
191
+ }
192
+ if (tooltipText) {
193
+ if (!this.disabledTooltip) {
194
+ this.disabledTooltip = disabledTooltipRenderer({
195
+ chipElement: chip,
196
+ portalProviderAPI
197
+ });
198
+ }
199
+ this.disabledTooltip.setTooltip(tooltipText);
200
+ } else if (this.disabledTooltip) {
201
+ this.disabledTooltip.destroy();
202
+ this.disabledTooltip = undefined;
203
+ }
122
204
  }
123
205
  setTextContent(name) {
124
206
  if (name && !this.node.attrs.text && this.mentionPrimitiveElement) {
@@ -145,14 +227,25 @@ export class MentionNodeView {
145
227
  const isHighlighted = expValEquals('platform_editor_vc90_transition_mentions', 'isEnabled', true) ? this.shouldHighlightMention(mentionProvider) : (_mentionProvider$shou2 = mentionProvider === null || mentionProvider === void 0 ? void 0 : mentionProvider.shouldHighlightMention({
146
228
  id: this.node.attrs.id
147
229
  })) !== null && _mentionProvider$shou2 !== void 0 ? _mentionProvider$shou2 : false;
148
- const newState = getNewState(isHighlighted, isRestricted(this.node.attrs.accessLevel));
149
- if (newState !== this.state) {
150
- this.setClassList(newState);
151
- this.state = newState;
152
- }
230
+ const disabledState = this.getDisabledState(mentionProvider);
231
+ const isDisabled = !!(disabledState !== null && disabledState !== void 0 && disabledState.disabled);
232
+ const newState = getNewState(isHighlighted, isRestricted(this.node.attrs.accessLevel), isDisabled);
233
+ const disabledTooltip = disabledState !== null && disabledState !== void 0 && disabledState.disabled ? disabledState.tooltip : undefined;
234
+ // `setClassList` always runs so the aria-label (which depends on the
235
+ // tooltip text) stays in sync when the tooltip reason changes while
236
+ // the chip remains disabled. State-change-only writes would leave a
237
+ // stale aria-label after a tooltip-text-only update.
238
+ this.setClassList(newState, disabledTooltip);
239
+ // Tooltip wiring runs every update (not just on state change) so that
240
+ // the tooltip text stays in sync if the disabled reason changes while
241
+ // the chip is already disabled.
242
+ this.syncDisabledTooltip(disabledState);
153
243
  const name = await handleProviderName(mentionProvider, this.node);
154
244
  this.setTextContent(name);
155
- if (name && this.domElement && (_this$config$options2 = this.config.options) !== null && _this$config$options2 !== void 0 && _this$config$options2.profilecardProvider) {
245
+ // Only overwrite the disabled-state aria-label with the name-based one
246
+ // when the chip is NOT disabled; otherwise the disabled reason set in
247
+ // `setClassList` would be silently clobbered, regressing a11y.
248
+ if (name && this.domElement && (_this$config$options2 = this.config.options) !== null && _this$config$options2 !== void 0 && _this$config$options2.profilecardProvider && newState !== 'disabled') {
156
249
  this.domElement.setAttribute('aria-label', getAccessibilityLabelFromName(name));
157
250
  }
158
251
  }
@@ -176,9 +269,27 @@ export class MentionNodeView {
176
269
  return true;
177
270
  }
178
271
  destroy() {
179
- var _this$cleanup, _this$destroyProfileC;
272
+ var _this$cleanup, _this$destroyProfileC, _this$disabledTooltip, _this$unsubscribeFrom2;
273
+ // Surface the destruction to the provider before tearing down so the
274
+ // chat layer can react (e.g. drop the agent id from `selectedAgentIds`).
275
+ // This is the lowest-level deletion signal — fires for backspace,
276
+ // select-and-delete, programmatic doc replaces, and editor unmount.
277
+ try {
278
+ var _this$subscribedProvi, _this$subscribedProvi2;
279
+ (_this$subscribedProvi = this.subscribedProvider) === null || _this$subscribedProvi === void 0 ? void 0 : (_this$subscribedProvi2 = _this$subscribedProvi.notifyMentionDestroyed) === null || _this$subscribedProvi2 === void 0 ? void 0 : _this$subscribedProvi2.call(_this$subscribedProvi, {
280
+ id: this.node.attrs.id
281
+ });
282
+ } catch (_error) {
283
+ // Defensive: never let consumer-side notification errors prevent
284
+ // the NodeView from cleaning up its own resources below.
285
+ }
180
286
  (_this$cleanup = this.cleanup) === null || _this$cleanup === void 0 ? void 0 : _this$cleanup.call(this);
181
287
  (_this$destroyProfileC = this.destroyProfileCard) === null || _this$destroyProfileC === void 0 ? void 0 : _this$destroyProfileC.call(this);
288
+ (_this$disabledTooltip = this.disabledTooltip) === null || _this$disabledTooltip === void 0 ? void 0 : _this$disabledTooltip.destroy();
289
+ this.disabledTooltip = undefined;
290
+ (_this$unsubscribeFrom2 = this.unsubscribeFromDisabledStateChanges) === null || _this$unsubscribeFrom2 === void 0 ? void 0 : _this$unsubscribeFrom2.call(this);
291
+ this.unsubscribeFromDisabledStateChanges = undefined;
292
+ this.subscribedProvider = undefined;
182
293
  }
183
294
  deselectNode() {
184
295
  var _this$removeProfileCa;
@@ -17,7 +17,7 @@ export const ACTIONS = {
17
17
  const AGENT_USER_TYPES = new Set(['APP', 'AGENT']);
18
18
  const AI_STREAMING_TRANSFORMATION_META_KEY = 'isAIStreamingTransformation';
19
19
  const PACKAGE_NAME = "@atlaskit/editor-plugin-mentions";
20
- const PACKAGE_VERSION = "13.1.0";
20
+ const PACKAGE_VERSION = "13.2.0";
21
21
  const setProvider = provider => (state, dispatch) => {
22
22
  if (dispatch) {
23
23
  dispatch(state.tr.setMeta(mentionPluginKey, {
@@ -14,6 +14,7 @@ import { IconMention } from '@atlaskit/editor-common/quick-insert';
14
14
  import { isResolvingMentionProvider } from '@atlaskit/mention/resource';
15
15
  import { MentionNameStatus, isPromise } from '@atlaskit/mention/types';
16
16
  import { fg } from '@atlaskit/platform-feature-flags';
17
+ import { expVal } from '@atlaskit/tmp-editor-statsig/expVal';
17
18
  import { insertMention } from './editor-commands';
18
19
  import { mentionNodeSpec } from './nodeviews/mentionNodeSpec';
19
20
  import { mentionPluginKey } from './pm-plugins/key';
@@ -187,6 +188,13 @@ var mentionsPlugin = function mentionsPlugin(_ref3) {
187
188
  options.handleMentionsChanged(mentionChanges);
188
189
  }
189
190
  },
191
+ updateSectionTitle: function updateSectionTitle(props) {
192
+ var _api$typeAhead$action, _api$typeAhead2, _api$typeAhead2$updat;
193
+ if (!expVal('platform_editor_agent_mentions', 'isEnabled', false)) {
194
+ return false;
195
+ }
196
+ return (_api$typeAhead$action = api === null || api === void 0 || (_api$typeAhead2 = api.typeAhead) === null || _api$typeAhead2 === void 0 || (_api$typeAhead2 = _api$typeAhead2.actions) === null || _api$typeAhead2 === void 0 || (_api$typeAhead2$updat = _api$typeAhead2.updateSectionTitle) === null || _api$typeAhead2$updat === void 0 ? void 0 : _api$typeAhead2$updat.call(_api$typeAhead2, props)) !== null && _api$typeAhead$action !== void 0 ? _api$typeAhead$action : false;
197
+ },
190
198
  setProvider: function () {
191
199
  var _setProvider = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(providerPromise) {
192
200
  var _api$core$actions$exe;
@@ -255,13 +263,13 @@ var mentionsPlugin = function mentionsPlugin(_ref3) {
255
263
  return /*#__PURE__*/React.createElement(IconMention, null);
256
264
  },
257
265
  action: function action(insert, state) {
258
- var _api$typeAhead2;
266
+ var _api$typeAhead3;
259
267
  var tr = insert(undefined);
260
268
  var pluginState = mentionPluginKey.getState(state);
261
269
  if (pluginState && pluginState.canInsertMention === false) {
262
270
  return false;
263
271
  }
264
- api === null || api === void 0 || (_api$typeAhead2 = api.typeAhead) === null || _api$typeAhead2 === void 0 || _api$typeAhead2.actions.openAtTransaction({
272
+ api === null || api === void 0 || (_api$typeAhead3 = api.typeAhead) === null || _api$typeAhead3 === void 0 || _api$typeAhead3.actions.openAtTransaction({
265
273
  triggerHandler: typeAhead,
266
274
  inputMethod: INPUT_METHOD.QUICK_INSERT
267
275
  })(tr);
@@ -0,0 +1 @@
1
+ ._1e0cglyw{display:none}
@@ -0,0 +1,184 @@
1
+ /* disabledTooltipRenderer.tsx generated by @compiled/babel-plugin v0.39.1 */
2
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
+ import "./disabledTooltipRenderer.compiled.css";
4
+ import { ax, ix } from "@compiled/react/runtime";
5
+ import React, { useEffect, useState } from 'react';
6
+ import { bindAll } from 'bind-event-listener';
7
+ // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- mirror existing renderer pattern
8
+ import uuid from 'uuid/v4';
9
+ import Tooltip from '@atlaskit/tooltip';
10
+ var styles = {
11
+ hiddenTrigger: "_1e0cglyw"
12
+ };
13
+
14
+ /**
15
+ * ADS `<Tooltip>` hands us a ref via its render-prop child. That ref may be
16
+ * either a callback ref or a mutable ref object — React supports both shapes
17
+ * and ADS's typing keeps both possible. This helper assigns the supplied
18
+ * element to whichever ref shape was provided so the tooltip ends up using
19
+ * the chip's bounding box for positioning and its DOM for trigger events.
20
+ */
21
+ var assignRef = function assignRef(ref, element) {
22
+ if (typeof ref === 'function') {
23
+ ref(element);
24
+ } else if (ref) {
25
+ ref.current = element;
26
+ }
27
+ };
28
+
29
+ /**
30
+ * Names of the React-style trigger-prop handlers we forward from ADS
31
+ * `<Tooltip>` onto the chip element.
32
+ */
33
+
34
+ /**
35
+ * Maps each React-style trigger-prop handler ADS `<Tooltip>` hands us in its
36
+ * render-prop callback to the DOM event name we attach to the chip via
37
+ * `bind-event-listener`. The chip lives in the ProseMirror document and is
38
+ * not a React child of the tooltip, so we have to bridge React handlers to
39
+ * native event listeners ourselves.
40
+ *
41
+ * Names are listed in the same order ADS internally registers them.
42
+ */
43
+ var TRIGGER_EVENT_MAP = [['onMouseOver', 'mouseover'], ['onMouseOut', 'mouseout'], ['onMouseMove', 'mousemove'], ['onMouseDown', 'mousedown'], ['onFocus', 'focusin'], ['onBlur', 'focusout']];
44
+
45
+ /**
46
+ * Anchors an ADS `<Tooltip>` to a ProseMirror chip element that is **not** a
47
+ * React child of the tooltip. The chip itself stays in the PM document; this
48
+ * renderer mounts an always-present React subtree via `portalProviderAPI` whose
49
+ * sole job is to host the tooltip and forward the ADS-provided trigger ref +
50
+ * event handlers onto the chip.
51
+ *
52
+ * The forwarding is deliberate: ADS `<Tooltip>` expects to attach its hover /
53
+ * focus / blur listeners to its render-prop child, but our render-prop child
54
+ * is a `display: none` placeholder span that no real event can reach. We bridge
55
+ * those listeners to the chip via `addEventListener` so the tooltip opens and
56
+ * closes in response to the user actually interacting with the chip — keyboard
57
+ * focus, mouse hover, screen-reader focus, the lot.
58
+ */
59
+
60
+ /**
61
+ * Hook that mirrors ADS Tooltip's React trigger props onto a DOM element
62
+ * which is not a React child of the tooltip. Re-attaches whenever ADS hands
63
+ * us a fresh set of props (which happens on every render of the render-prop
64
+ * child) and cleans up on unmount.
65
+ *
66
+ * We use `bind-event-listener`'s `bindAll` rather than raw
67
+ * `addEventListener` / `removeEventListener` calls so the lifetime of every
68
+ * subscription is paired with a single `UnbindFn` — the AFM-mandated pattern
69
+ * for keeping listener bookkeeping leak-free.
70
+ */
71
+ var useBridgedTriggerListeners = function useBridgedTriggerListeners(chip, triggerProps) {
72
+ useEffect(function () {
73
+ // Build the bindAll bindings array out of only the handlers ADS actually
74
+ // supplied. Each React handler is wrapped in a thin adapter so the
75
+ // listener signature matches the native `EventListener` shape; ADS
76
+ // handlers ignore the synthetic-event-only fields they don't use
77
+ // (target, currentTarget), so passing the raw DOM event through
78
+ // `unknown` is safe in practice.
79
+ var bindings = TRIGGER_EVENT_MAP.flatMap(function (_ref) {
80
+ var _ref2 = _slicedToArray(_ref, 2),
81
+ propName = _ref2[0],
82
+ eventName = _ref2[1];
83
+ var handler = triggerProps[propName];
84
+ if (!handler) {
85
+ return [];
86
+ }
87
+ return [{
88
+ type: eventName,
89
+ listener: function listener(event) {
90
+ handler(event);
91
+ }
92
+ }];
93
+ });
94
+ if (bindings.length === 0) {
95
+ return;
96
+ }
97
+ var unbind = bindAll(chip, bindings);
98
+ return unbind;
99
+ }, [chip, triggerProps]);
100
+ };
101
+ var AnchoredTooltip = function AnchoredTooltip(_ref3) {
102
+ var subscribe = _ref3.subscribe,
103
+ getInitialTooltip = _ref3.getInitialTooltip,
104
+ referenceElement = _ref3.referenceElement;
105
+ var _useState = useState(getInitialTooltip),
106
+ _useState2 = _slicedToArray(_useState, 2),
107
+ tooltip = _useState2[0],
108
+ setTooltipState = _useState2[1];
109
+ useEffect(function () {
110
+ return subscribe(setTooltipState);
111
+ }, [subscribe]);
112
+ if (!tooltip) {
113
+ return null;
114
+ }
115
+ return /*#__PURE__*/React.createElement(Tooltip, {
116
+ content: tooltip,
117
+ position: "top"
118
+ }, function (tooltipProps) {
119
+ return /*#__PURE__*/React.createElement(TriggerBridge, {
120
+ referenceElement: referenceElement,
121
+ tooltipProps: tooltipProps
122
+ });
123
+ });
124
+ };
125
+
126
+ /**
127
+ * Render-prop child for ADS `<Tooltip>`. The element it returns is a
128
+ * `display: none` placeholder that satisfies the render-prop API; the
129
+ * real trigger surface is the chip in the PM document, which receives
130
+ * both the ADS ref and the bridged event listeners via the side-effects
131
+ * declared on this component.
132
+ */
133
+ var TriggerBridge = function TriggerBridge(_ref4) {
134
+ var referenceElement = _ref4.referenceElement,
135
+ tooltipProps = _ref4.tooltipProps;
136
+ useBridgedTriggerListeners(referenceElement, tooltipProps);
137
+ return /*#__PURE__*/React.createElement("span", {
138
+ ref: function ref() {
139
+ return assignRef(tooltipProps.ref, referenceElement);
140
+ },
141
+ "aria-hidden": "true",
142
+ className: ax([styles.hiddenTrigger])
143
+ });
144
+ };
145
+ export var disabledTooltipRenderer = function disabledTooltipRenderer(_ref5) {
146
+ var chipElement = _ref5.chipElement,
147
+ portalProviderAPI = _ref5.portalProviderAPI;
148
+ // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- mirror existing renderer pattern
149
+ var key = uuid();
150
+ var currentTooltip;
151
+ var listeners = new Set();
152
+ var broadcast = function broadcast() {
153
+ listeners.forEach(function (listener) {
154
+ return listener(currentTooltip);
155
+ });
156
+ };
157
+ var getInitialTooltip = function getInitialTooltip() {
158
+ return currentTooltip;
159
+ };
160
+ var subscribe = function subscribe(listener) {
161
+ listeners.add(listener);
162
+ return function () {
163
+ listeners.delete(listener);
164
+ };
165
+ };
166
+ var renderElement = function renderElement() {
167
+ return /*#__PURE__*/React.createElement(AnchoredTooltip, {
168
+ referenceElement: chipElement,
169
+ getInitialTooltip: getInitialTooltip,
170
+ subscribe: subscribe
171
+ });
172
+ };
173
+ portalProviderAPI.render(renderElement, chipElement, key);
174
+ return {
175
+ setTooltip: function setTooltip(text) {
176
+ currentTooltip = text;
177
+ broadcast();
178
+ },
179
+ destroy: function destroy() {
180
+ portalProviderAPI.remove(key);
181
+ listeners.clear();
182
+ }
183
+ };
184
+ };