@atlaskit/tooltip 18.8.5 → 18.9.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @atlaskit/tooltip
2
2
 
3
+ ## 18.9.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#164322](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/164322)
8
+ [`e9317ed13ba40`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/e9317ed13ba40) -
9
+ Internal refactors to state to reduce the number of JavaScript event listeners.
10
+
11
+ ## 18.9.0
12
+
13
+ ### Minor Changes
14
+
15
+ - [`be6f923511512`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/be6f923511512) - -
16
+ Added new prop: `canAppear`, which can be used to _conditionally_ show tooltips.
17
+ - Added new prop: `isScreenReaderAnnouncementDisabled` which can be used to disable hidden text
18
+ for tooltips. This is useful when the Tooltip `content` matches the Tooltip trigger content as
19
+ hidden text is not required.
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies
24
+
3
25
  ## 18.8.5
4
26
 
5
27
  ### Patch Changes
@@ -14,6 +14,7 @@ var _bindEventListener = require("bind-event-listener");
14
14
  var _analyticsNext = require("@atlaskit/analytics-next");
15
15
  var _noop = _interopRequireDefault(require("@atlaskit/ds-lib/noop"));
16
16
  var _useCloseOnEscapePress = _interopRequireDefault(require("@atlaskit/ds-lib/use-close-on-escape-press"));
17
+ var _useStableRef = _interopRequireDefault(require("@atlaskit/ds-lib/use-stable-ref"));
17
18
  var _openLayerObserver = require("@atlaskit/layering/experimental/open-layer-observer");
18
19
  var _motion = require("@atlaskit/motion");
19
20
  var _durations = require("@atlaskit/motion/durations");
@@ -33,7 +34,7 @@ var tooltipZIndex = _constants.layers.tooltip();
33
34
  var analyticsAttributes = {
34
35
  componentName: 'tooltip',
35
36
  packageName: "@atlaskit/tooltip",
36
- packageVersion: "18.8.5"
37
+ packageVersion: "18.9.1"
37
38
  };
38
39
 
39
40
  // Inverts motion direction
@@ -78,6 +79,7 @@ function Tooltip(_ref) {
78
79
  onShow = _ref$onShow === void 0 ? _noop.default : _ref$onShow,
79
80
  _ref$onHide = _ref.onHide,
80
81
  onHide = _ref$onHide === void 0 ? _noop.default : _ref$onHide,
82
+ canAppear = _ref.canAppear,
81
83
  _ref$hideTooltipOnCli = _ref.hideTooltipOnClick,
82
84
  hideTooltipOnClick = _ref$hideTooltipOnCli === void 0 ? false : _ref$hideTooltipOnCli,
83
85
  _ref$hideTooltipOnMou = _ref.hideTooltipOnMouseDown,
@@ -86,7 +88,9 @@ function Tooltip(_ref) {
86
88
  _ref$strategy = _ref.strategy,
87
89
  strategy = _ref$strategy === void 0 ? 'fixed' : _ref$strategy,
88
90
  _ref$ignoreTooltipPoi = _ref.ignoreTooltipPointerEvents,
89
- ignoreTooltipPointerEvents = _ref$ignoreTooltipPoi === void 0 ? false : _ref$ignoreTooltipPoi;
91
+ ignoreTooltipPointerEvents = _ref$ignoreTooltipPoi === void 0 ? false : _ref$ignoreTooltipPoi,
92
+ _ref$isScreenReaderAn = _ref.isScreenReaderAnnouncementDisabled,
93
+ isScreenReaderAnnouncementDisabled = _ref$isScreenReaderAn === void 0 ? false : _ref$isScreenReaderAn;
90
94
  var tooltipPosition = position === 'mouse' ? mousePosition : position;
91
95
  var onShowHandler = (0, _analyticsNext.usePlatformLeafSyntheticEventHandler)(_objectSpread({
92
96
  fn: onShow,
@@ -119,21 +123,15 @@ function Tooltip(_ref) {
119
123
  }, []);
120
124
 
121
125
  // Putting a few things into refs so that we don't have to break memoization
122
- var lastState = (0, _react.useRef)(state);
123
- var lastDelay = (0, _react.useRef)(delay);
124
- var lastHandlers = (0, _react.useRef)({
125
- onShowHandler: onShowHandler,
126
- onHideHandler: onHideHandler
127
- });
126
+ var stableState = (0, _useStableRef.default)(state);
127
+ // These props are placed in separate refs instead of a single object to reduce memory usage.
128
+ // Placing them in the same object previously caused an increase in the number of JavaScript event listeners
129
+ // before garbage collection.
130
+ var onShowHandlerStable = (0, _useStableRef.default)(onShowHandler);
131
+ var onHideHandlerStable = (0, _useStableRef.default)(onHideHandler);
132
+ var delayStable = (0, _useStableRef.default)(delay);
133
+ var canAppearStable = (0, _useStableRef.default)(canAppear);
128
134
  var hasCalledShowHandler = (0, _react.useRef)(false);
129
- (0, _react.useEffect)(function () {
130
- lastState.current = state;
131
- lastDelay.current = delay;
132
- lastHandlers.current = {
133
- onShowHandler: onShowHandler,
134
- onHideHandler: onHideHandler
135
- };
136
- }, [delay, onHideHandler, onShowHandler, state]);
137
135
  var start = (0, _react.useCallback)(function (api) {
138
136
  // @ts-ignore
139
137
  apiRef.current = api;
@@ -145,7 +143,7 @@ function Tooltip(_ref) {
145
143
  }
146
144
  // Only call onHideHandler if we have called onShowHandler
147
145
  if (hasCalledShowHandler.current) {
148
- lastHandlers.current.onHideHandler();
146
+ onHideHandlerStable.current();
149
147
  }
150
148
  // @ts-ignore
151
149
  apiRef.current = null;
@@ -153,7 +151,7 @@ function Tooltip(_ref) {
153
151
  hasCalledShowHandler.current = false;
154
152
  // just in case
155
153
  setState('hide');
156
- }, []);
154
+ }, [onHideHandlerStable]);
157
155
  var abort = (0, _react.useCallback)(function () {
158
156
  if (!apiRef.current) {
159
157
  return;
@@ -161,11 +159,11 @@ function Tooltip(_ref) {
161
159
  apiRef.current.abort();
162
160
  // Only call onHideHandler if we have called onShowHandler
163
161
  if (hasCalledShowHandler.current) {
164
- lastHandlers.current.onHideHandler();
162
+ onHideHandlerStable.current();
165
163
  }
166
164
  // @ts-ignore
167
165
  apiRef.current = null;
168
- }, []);
166
+ }, [onHideHandlerStable]);
169
167
  (0, _react.useEffect)(function mount() {
170
168
  return function unmount() {
171
169
  if (apiRef.current) {
@@ -196,7 +194,8 @@ function Tooltip(_ref) {
196
194
  }
197
195
  });
198
196
  }, []);
199
- var showTooltip = (0, _react.useCallback)(function (source) {
197
+ var tryShowTooltip = (0, _react.useCallback)(function (source) {
198
+ var _canAppearStable$curr;
200
199
  /**
201
200
  * Prevent tooltips from being shown during a drag. This can occur with
202
201
  * the native drag and drop API, where some pointer events can fire
@@ -205,24 +204,45 @@ function Tooltip(_ref) {
205
204
  if (isDraggingRef.current) {
206
205
  return;
207
206
  }
207
+
208
+ // Another tooltip is has been active but we still have the old `api`
209
+ // around. We need to finish up the last usage.
210
+ // Note: just being safe - this should not happen
208
211
  if (apiRef.current && !apiRef.current.isActive()) {
209
212
  abort();
210
213
  }
211
214
 
212
- // Tell the tooltip to keep showing
215
+ // This tooltip is already active, we can exit
213
216
  if (apiRef.current && apiRef.current.isActive()) {
214
217
  apiRef.current.keep();
215
218
  return;
216
219
  }
220
+
221
+ /**
222
+ * Check if tooltip is allowed to show.
223
+ *
224
+ * Once a tooltip has started, or has scheduled to start
225
+ * we won't be checking `canAppear` again.
226
+ *
227
+ * - We don't want tooltips to disappear once they are shown
228
+ * - For consistency, we start after a single positive `canAppear`.
229
+ * Otherwise the amount of times we ask consumers would depend on
230
+ * how many times we get a "mousemove", which _could_ lead to situations
231
+ * where moving the mouse could result in a different outcome to if
232
+ * the mouse was not moved.
233
+ */
234
+ if (canAppearStable.current && !((_canAppearStable$curr = canAppearStable.current) !== null && _canAppearStable$curr !== void 0 && _canAppearStable$curr.call(canAppearStable))) {
235
+ return;
236
+ }
217
237
  var entry = {
218
238
  source: source,
219
- delay: lastDelay.current,
239
+ delay: delayStable.current,
220
240
  show: function show(_ref3) {
221
241
  var isImmediate = _ref3.isImmediate;
222
242
  // Call the onShow handler if it hasn't been called yet
223
243
  if (!hasCalledShowHandler.current) {
224
244
  hasCalledShowHandler.current = true;
225
- lastHandlers.current.onShowHandler();
245
+ onShowHandlerStable.current();
226
246
  }
227
247
  setState(isImmediate ? 'show-immediate' : 'fade-in');
228
248
  },
@@ -238,7 +258,7 @@ function Tooltip(_ref) {
238
258
  };
239
259
  var api = (0, _tooltipManager.show)(entry);
240
260
  start(api);
241
- }, [abort, done, start]);
261
+ }, [canAppearStable, delayStable, done, start, abort, onShowHandlerStable]);
242
262
  var hideTooltipOnEsc = (0, _react.useCallback)(function () {
243
263
  var _apiRef$current2;
244
264
  (_apiRef$current2 = apiRef.current) === null || _apiRef$current2 === void 0 || _apiRef$current2.requestHide({
@@ -314,8 +334,8 @@ function Tooltip(_ref) {
314
334
  } : {
315
335
  type: 'keyboard'
316
336
  };
317
- showTooltip(source);
318
- }, [position, showTooltip]);
337
+ tryShowTooltip(source);
338
+ }, [position, tryShowTooltip]);
319
339
 
320
340
  // Ideally we would be using onMouseEnter here, but
321
341
  // because we are binding the event to the target parent
@@ -355,10 +375,12 @@ function Tooltip(_ref) {
355
375
  }
356
376
  }, []);
357
377
  var onFocus = (0, _react.useCallback)(function () {
358
- showTooltip({
378
+ // TODO: this does not play well with `hideTooltipOnMouseDown`
379
+ // as "focus" will occur after the "mousedown".
380
+ tryShowTooltip({
359
381
  type: 'keyboard'
360
382
  });
361
- }, [showTooltip]);
383
+ }, [tryShowTooltip]);
362
384
  var onBlur = (0, _react.useCallback)(function () {
363
385
  if (apiRef.current) {
364
386
  apiRef.current.requestHide({
@@ -368,18 +390,19 @@ function Tooltip(_ref) {
368
390
  }, []);
369
391
  var onAnimationFinished = (0, _react.useCallback)(function (transition) {
370
392
  // Using lastState here because motion is not picking up the latest value
371
- if (transition === 'exiting' && lastState.current === 'fade-out' && apiRef.current) {
393
+ if (transition === 'exiting' && stableState.current === 'fade-out' && apiRef.current) {
372
394
  // @ts-ignore: refs are writeable
373
395
  apiRef.current.finishHideAnimation();
374
396
  }
375
- }, []);
397
+ }, [stableState]);
376
398
 
377
399
  // Doing a cast because typescript is struggling to narrow the type
378
400
  var CastTargetContainer = TargetContainer;
379
- var shouldRenderTooltipContainer = state !== 'hide' && Boolean(content);
401
+ var shouldRenderTooltipPopup = state !== 'hide' && Boolean(content);
402
+ var shouldRenderHiddenContent = !isScreenReaderAnnouncementDisabled && shouldRenderTooltipPopup;
380
403
  var shouldRenderTooltipChildren = state !== 'hide' && state !== 'fade-out';
381
404
  (0, _openLayerObserver.useNotifyOpenLayerObserver)({
382
- isOpen: shouldRenderTooltipContainer
405
+ isOpen: shouldRenderTooltipPopup
383
406
  });
384
407
  var getReferenceElement = function getReferenceElement() {
385
408
  var _apiRef$current4;
@@ -389,7 +412,7 @@ function Tooltip(_ref) {
389
412
  }
390
413
  return targetRef.current || undefined;
391
414
  };
392
- var tooltipId = (0, _useUniqueId.default)('tooltip', shouldRenderTooltipContainer);
415
+ var tooltipIdForHiddenContent = (0, _useUniqueId.default)('tooltip', shouldRenderHiddenContent);
393
416
  var tooltipTriggerProps = {
394
417
  onMouseOver: onMouseOver,
395
418
  onMouseOut: onMouseOut,
@@ -408,38 +431,47 @@ function Tooltip(_ref) {
408
431
 
409
432
  // This useEffect is purely for managing the aria attribute when using the
410
433
  // wrapped children approach.
434
+ var isChildrenAFunction = typeof children === 'function';
411
435
  (0, _react.useEffect)(function () {
412
- // If there is no container element, we should exit early, because that
413
- // means they are using the render prop API, and that is implemented in a
414
- // different way. If there is no target element yet or tooltipId, we also
415
- // shouldn't do anything because there is nothing to operate on or with.
416
- if (!containerRef.current || !targetRef.current || !tooltipId) {
436
+ if (isChildrenAFunction) {
417
437
  return;
418
438
  }
439
+
440
+ // If `children` is _not_ a function, we are stepping outside of the public
441
+ // API to add a `aria-describedby` attribute.
442
+
419
443
  var target = targetRef.current;
420
- if (shouldRenderTooltipContainer) {
421
- target.setAttribute('aria-describedby', tooltipId);
422
- } else {
423
- target.removeAttribute('aria-describedby');
444
+ if (!target || !tooltipIdForHiddenContent) {
445
+ return;
424
446
  }
425
- }, [shouldRenderTooltipContainer, tooltipId]);
426
- var hiddenContent = /*#__PURE__*/_react.default.createElement("span", {
447
+ target.setAttribute('aria-describedby', tooltipIdForHiddenContent);
448
+ return function () {
449
+ return target.removeAttribute('aria-describedby');
450
+ };
451
+ }, [isChildrenAFunction, tooltipIdForHiddenContent]);
452
+ var hiddenContent = shouldRenderHiddenContent ? /*#__PURE__*/_react.default.createElement("span", {
427
453
  "data-testid": testId ? "".concat(testId, "-hidden") : undefined,
428
454
  hidden: true,
429
- id: tooltipId
430
- }, typeof content === 'function' ? content({}) : content);
455
+ id: tooltipIdForHiddenContent
456
+ }, typeof content === 'function' ? content({}) : content) : null;
431
457
  return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, typeof children === 'function' ?
432
458
  /*#__PURE__*/
433
459
  // once we deprecate the wrapped approach, we can put the aria
434
460
  // attribute back into the tooltipTriggerProps and make it required
435
461
  // instead of optional in `types`
436
462
  _react.default.createElement(_react.default.Fragment, null, children(_objectSpread(_objectSpread({}, tooltipTriggerProps), {}, {
437
- 'aria-describedby': tooltipId,
463
+ 'aria-describedby': tooltipIdForHiddenContent,
438
464
  ref: setDirectRef
439
- })), shouldRenderTooltipContainer && hiddenContent) : /*#__PURE__*/_react.default.createElement(CastTargetContainer, (0, _extends2.default)({}, tooltipTriggerProps, {
440
- ref: setImplicitRefFromChildren,
465
+ })), hiddenContent) : /*#__PURE__*/_react.default.createElement(CastTargetContainer, (0, _extends2.default)({}, tooltipTriggerProps, {
466
+ ref: setImplicitRefFromChildren
467
+ /**
468
+ * TODO: Why is role="presentation" added?
469
+ * - Is it only to "remove" the `Container` from screen readers?
470
+ * - Why is it added only to the `Container` but not to `tooltipTriggerProps`?
471
+ * - Should `role="presentation"` only be used if `shouldRenderHiddenContent == false`?
472
+ */,
441
473
  role: "presentation"
442
- }), children, shouldRenderTooltipContainer && hiddenContent), shouldRenderTooltipContainer ? /*#__PURE__*/_react.default.createElement(_portal.default, {
474
+ }), children, hiddenContent), shouldRenderTooltipPopup ? /*#__PURE__*/_react.default.createElement(_portal.default, {
443
475
  zIndex: tooltipZIndex
444
476
  }, /*#__PURE__*/_react.default.createElement(_popper.Popper, {
445
477
  placement: tooltipPosition,
@@ -4,6 +4,7 @@ import { bind } from 'bind-event-listener';
4
4
  import { usePlatformLeafSyntheticEventHandler } from '@atlaskit/analytics-next';
5
5
  import noop from '@atlaskit/ds-lib/noop';
6
6
  import useCloseOnEscapePress from '@atlaskit/ds-lib/use-close-on-escape-press';
7
+ import useStableRef from '@atlaskit/ds-lib/use-stable-ref';
7
8
  import { useNotifyOpenLayerObserver } from '@atlaskit/layering/experimental/open-layer-observer';
8
9
  import { ExitingPersistence, FadeIn } from '@atlaskit/motion';
9
10
  import { mediumDurationMs } from '@atlaskit/motion/durations';
@@ -19,7 +20,7 @@ const tooltipZIndex = layers.tooltip();
19
20
  const analyticsAttributes = {
20
21
  componentName: 'tooltip',
21
22
  packageName: "@atlaskit/tooltip",
22
- packageVersion: "18.8.5"
23
+ packageVersion: "18.9.1"
23
24
  };
24
25
 
25
26
  // Inverts motion direction
@@ -55,11 +56,13 @@ function Tooltip({
55
56
  delay = 300,
56
57
  onShow = noop,
57
58
  onHide = noop,
59
+ canAppear,
58
60
  hideTooltipOnClick = false,
59
61
  hideTooltipOnMouseDown = false,
60
62
  analyticsContext,
61
63
  strategy = 'fixed',
62
- ignoreTooltipPointerEvents = false
64
+ ignoreTooltipPointerEvents = false,
65
+ isScreenReaderAnnouncementDisabled = false
63
66
  }) {
64
67
  const tooltipPosition = position === 'mouse' ? mousePosition : position;
65
68
  const onShowHandler = usePlatformLeafSyntheticEventHandler({
@@ -92,21 +95,15 @@ function Tooltip({
92
95
  }, []);
93
96
 
94
97
  // Putting a few things into refs so that we don't have to break memoization
95
- const lastState = useRef(state);
96
- const lastDelay = useRef(delay);
97
- const lastHandlers = useRef({
98
- onShowHandler,
99
- onHideHandler
100
- });
98
+ const stableState = useStableRef(state);
99
+ // These props are placed in separate refs instead of a single object to reduce memory usage.
100
+ // Placing them in the same object previously caused an increase in the number of JavaScript event listeners
101
+ // before garbage collection.
102
+ const onShowHandlerStable = useStableRef(onShowHandler);
103
+ const onHideHandlerStable = useStableRef(onHideHandler);
104
+ const delayStable = useStableRef(delay);
105
+ const canAppearStable = useStableRef(canAppear);
101
106
  const hasCalledShowHandler = useRef(false);
102
- useEffect(() => {
103
- lastState.current = state;
104
- lastDelay.current = delay;
105
- lastHandlers.current = {
106
- onShowHandler,
107
- onHideHandler
108
- };
109
- }, [delay, onHideHandler, onShowHandler, state]);
110
107
  const start = useCallback(api => {
111
108
  // @ts-ignore
112
109
  apiRef.current = api;
@@ -118,7 +115,7 @@ function Tooltip({
118
115
  }
119
116
  // Only call onHideHandler if we have called onShowHandler
120
117
  if (hasCalledShowHandler.current) {
121
- lastHandlers.current.onHideHandler();
118
+ onHideHandlerStable.current();
122
119
  }
123
120
  // @ts-ignore
124
121
  apiRef.current = null;
@@ -126,7 +123,7 @@ function Tooltip({
126
123
  hasCalledShowHandler.current = false;
127
124
  // just in case
128
125
  setState('hide');
129
- }, []);
126
+ }, [onHideHandlerStable]);
130
127
  const abort = useCallback(() => {
131
128
  if (!apiRef.current) {
132
129
  return;
@@ -134,11 +131,11 @@ function Tooltip({
134
131
  apiRef.current.abort();
135
132
  // Only call onHideHandler if we have called onShowHandler
136
133
  if (hasCalledShowHandler.current) {
137
- lastHandlers.current.onHideHandler();
134
+ onHideHandlerStable.current();
138
135
  }
139
136
  // @ts-ignore
140
137
  apiRef.current = null;
141
- }, []);
138
+ }, [onHideHandlerStable]);
142
139
  useEffect(function mount() {
143
140
  return function unmount() {
144
141
  if (apiRef.current) {
@@ -170,7 +167,8 @@ function Tooltip({
170
167
  }
171
168
  });
172
169
  }, []);
173
- const showTooltip = useCallback(source => {
170
+ const tryShowTooltip = useCallback(source => {
171
+ var _canAppearStable$curr;
174
172
  /**
175
173
  * Prevent tooltips from being shown during a drag. This can occur with
176
174
  * the native drag and drop API, where some pointer events can fire
@@ -179,25 +177,46 @@ function Tooltip({
179
177
  if (isDraggingRef.current) {
180
178
  return;
181
179
  }
180
+
181
+ // Another tooltip is has been active but we still have the old `api`
182
+ // around. We need to finish up the last usage.
183
+ // Note: just being safe - this should not happen
182
184
  if (apiRef.current && !apiRef.current.isActive()) {
183
185
  abort();
184
186
  }
185
187
 
186
- // Tell the tooltip to keep showing
188
+ // This tooltip is already active, we can exit
187
189
  if (apiRef.current && apiRef.current.isActive()) {
188
190
  apiRef.current.keep();
189
191
  return;
190
192
  }
193
+
194
+ /**
195
+ * Check if tooltip is allowed to show.
196
+ *
197
+ * Once a tooltip has started, or has scheduled to start
198
+ * we won't be checking `canAppear` again.
199
+ *
200
+ * - We don't want tooltips to disappear once they are shown
201
+ * - For consistency, we start after a single positive `canAppear`.
202
+ * Otherwise the amount of times we ask consumers would depend on
203
+ * how many times we get a "mousemove", which _could_ lead to situations
204
+ * where moving the mouse could result in a different outcome to if
205
+ * the mouse was not moved.
206
+ */
207
+ if (canAppearStable.current && !((_canAppearStable$curr = canAppearStable.current) !== null && _canAppearStable$curr !== void 0 && _canAppearStable$curr.call(canAppearStable))) {
208
+ return;
209
+ }
191
210
  const entry = {
192
211
  source,
193
- delay: lastDelay.current,
212
+ delay: delayStable.current,
194
213
  show: ({
195
214
  isImmediate
196
215
  }) => {
197
216
  // Call the onShow handler if it hasn't been called yet
198
217
  if (!hasCalledShowHandler.current) {
199
218
  hasCalledShowHandler.current = true;
200
- lastHandlers.current.onShowHandler();
219
+ onShowHandlerStable.current();
201
220
  }
202
221
  setState(isImmediate ? 'show-immediate' : 'fade-in');
203
222
  },
@@ -210,11 +229,11 @@ function Tooltip({
210
229
  setState('before-fade-out');
211
230
  }
212
231
  },
213
- done: done
232
+ done
214
233
  };
215
234
  const api = show(entry);
216
235
  start(api);
217
- }, [abort, done, start]);
236
+ }, [canAppearStable, delayStable, done, start, abort, onShowHandlerStable]);
218
237
  const hideTooltipOnEsc = useCallback(() => {
219
238
  var _apiRef$current2;
220
239
  (_apiRef$current2 = apiRef.current) === null || _apiRef$current2 === void 0 ? void 0 : _apiRef$current2.requestHide({
@@ -290,8 +309,8 @@ function Tooltip({
290
309
  } : {
291
310
  type: 'keyboard'
292
311
  };
293
- showTooltip(source);
294
- }, [position, showTooltip]);
312
+ tryShowTooltip(source);
313
+ }, [position, tryShowTooltip]);
295
314
 
296
315
  // Ideally we would be using onMouseEnter here, but
297
316
  // because we are binding the event to the target parent
@@ -331,10 +350,12 @@ function Tooltip({
331
350
  }
332
351
  }, []);
333
352
  const onFocus = useCallback(() => {
334
- showTooltip({
353
+ // TODO: this does not play well with `hideTooltipOnMouseDown`
354
+ // as "focus" will occur after the "mousedown".
355
+ tryShowTooltip({
335
356
  type: 'keyboard'
336
357
  });
337
- }, [showTooltip]);
358
+ }, [tryShowTooltip]);
338
359
  const onBlur = useCallback(() => {
339
360
  if (apiRef.current) {
340
361
  apiRef.current.requestHide({
@@ -344,18 +365,19 @@ function Tooltip({
344
365
  }, []);
345
366
  const onAnimationFinished = useCallback(transition => {
346
367
  // Using lastState here because motion is not picking up the latest value
347
- if (transition === 'exiting' && lastState.current === 'fade-out' && apiRef.current) {
368
+ if (transition === 'exiting' && stableState.current === 'fade-out' && apiRef.current) {
348
369
  // @ts-ignore: refs are writeable
349
370
  apiRef.current.finishHideAnimation();
350
371
  }
351
- }, []);
372
+ }, [stableState]);
352
373
 
353
374
  // Doing a cast because typescript is struggling to narrow the type
354
375
  const CastTargetContainer = TargetContainer;
355
- const shouldRenderTooltipContainer = state !== 'hide' && Boolean(content);
376
+ const shouldRenderTooltipPopup = state !== 'hide' && Boolean(content);
377
+ const shouldRenderHiddenContent = !isScreenReaderAnnouncementDisabled && shouldRenderTooltipPopup;
356
378
  const shouldRenderTooltipChildren = state !== 'hide' && state !== 'fade-out';
357
379
  useNotifyOpenLayerObserver({
358
- isOpen: shouldRenderTooltipContainer
380
+ isOpen: shouldRenderTooltipPopup
359
381
  });
360
382
  const getReferenceElement = () => {
361
383
  var _apiRef$current4;
@@ -365,7 +387,7 @@ function Tooltip({
365
387
  }
366
388
  return targetRef.current || undefined;
367
389
  };
368
- const tooltipId = useUniqueId('tooltip', shouldRenderTooltipContainer);
390
+ const tooltipIdForHiddenContent = useUniqueId('tooltip', shouldRenderHiddenContent);
369
391
  const tooltipTriggerProps = {
370
392
  onMouseOver,
371
393
  onMouseOut,
@@ -384,26 +406,27 @@ function Tooltip({
384
406
 
385
407
  // This useEffect is purely for managing the aria attribute when using the
386
408
  // wrapped children approach.
409
+ const isChildrenAFunction = typeof children === 'function';
387
410
  useEffect(() => {
388
- // If there is no container element, we should exit early, because that
389
- // means they are using the render prop API, and that is implemented in a
390
- // different way. If there is no target element yet or tooltipId, we also
391
- // shouldn't do anything because there is nothing to operate on or with.
392
- if (!containerRef.current || !targetRef.current || !tooltipId) {
411
+ if (isChildrenAFunction) {
393
412
  return;
394
413
  }
414
+
415
+ // If `children` is _not_ a function, we are stepping outside of the public
416
+ // API to add a `aria-describedby` attribute.
417
+
395
418
  const target = targetRef.current;
396
- if (shouldRenderTooltipContainer) {
397
- target.setAttribute('aria-describedby', tooltipId);
398
- } else {
399
- target.removeAttribute('aria-describedby');
419
+ if (!target || !tooltipIdForHiddenContent) {
420
+ return;
400
421
  }
401
- }, [shouldRenderTooltipContainer, tooltipId]);
402
- const hiddenContent = /*#__PURE__*/React.createElement("span", {
422
+ target.setAttribute('aria-describedby', tooltipIdForHiddenContent);
423
+ return () => target.removeAttribute('aria-describedby');
424
+ }, [isChildrenAFunction, tooltipIdForHiddenContent]);
425
+ const hiddenContent = shouldRenderHiddenContent ? /*#__PURE__*/React.createElement("span", {
403
426
  "data-testid": testId ? `${testId}-hidden` : undefined,
404
427
  hidden: true,
405
- id: tooltipId
406
- }, typeof content === 'function' ? content({}) : content);
428
+ id: tooltipIdForHiddenContent
429
+ }, typeof content === 'function' ? content({}) : content) : null;
407
430
  return /*#__PURE__*/React.createElement(React.Fragment, null, typeof children === 'function' ?
408
431
  /*#__PURE__*/
409
432
  // once we deprecate the wrapped approach, we can put the aria
@@ -411,12 +434,18 @@ function Tooltip({
411
434
  // instead of optional in `types`
412
435
  React.createElement(React.Fragment, null, children({
413
436
  ...tooltipTriggerProps,
414
- 'aria-describedby': tooltipId,
437
+ 'aria-describedby': tooltipIdForHiddenContent,
415
438
  ref: setDirectRef
416
- }), shouldRenderTooltipContainer && hiddenContent) : /*#__PURE__*/React.createElement(CastTargetContainer, _extends({}, tooltipTriggerProps, {
417
- ref: setImplicitRefFromChildren,
439
+ }), hiddenContent) : /*#__PURE__*/React.createElement(CastTargetContainer, _extends({}, tooltipTriggerProps, {
440
+ ref: setImplicitRefFromChildren
441
+ /**
442
+ * TODO: Why is role="presentation" added?
443
+ * - Is it only to "remove" the `Container` from screen readers?
444
+ * - Why is it added only to the `Container` but not to `tooltipTriggerProps`?
445
+ * - Should `role="presentation"` only be used if `shouldRenderHiddenContent == false`?
446
+ */,
418
447
  role: "presentation"
419
- }), children, shouldRenderTooltipContainer && hiddenContent), shouldRenderTooltipContainer ? /*#__PURE__*/React.createElement(Portal, {
448
+ }), children, hiddenContent), shouldRenderTooltipPopup ? /*#__PURE__*/React.createElement(Portal, {
420
449
  zIndex: tooltipZIndex
421
450
  }, /*#__PURE__*/React.createElement(Popper, {
422
451
  placement: tooltipPosition,
@@ -8,6 +8,7 @@ import { bind } from 'bind-event-listener';
8
8
  import { usePlatformLeafSyntheticEventHandler } from '@atlaskit/analytics-next';
9
9
  import noop from '@atlaskit/ds-lib/noop';
10
10
  import useCloseOnEscapePress from '@atlaskit/ds-lib/use-close-on-escape-press';
11
+ import useStableRef from '@atlaskit/ds-lib/use-stable-ref';
11
12
  import { useNotifyOpenLayerObserver } from '@atlaskit/layering/experimental/open-layer-observer';
12
13
  import { ExitingPersistence, FadeIn } from '@atlaskit/motion';
13
14
  import { mediumDurationMs } from '@atlaskit/motion/durations';
@@ -23,7 +24,7 @@ var tooltipZIndex = layers.tooltip();
23
24
  var analyticsAttributes = {
24
25
  componentName: 'tooltip',
25
26
  packageName: "@atlaskit/tooltip",
26
- packageVersion: "18.8.5"
27
+ packageVersion: "18.9.1"
27
28
  };
28
29
 
29
30
  // Inverts motion direction
@@ -68,6 +69,7 @@ function Tooltip(_ref) {
68
69
  onShow = _ref$onShow === void 0 ? noop : _ref$onShow,
69
70
  _ref$onHide = _ref.onHide,
70
71
  onHide = _ref$onHide === void 0 ? noop : _ref$onHide,
72
+ canAppear = _ref.canAppear,
71
73
  _ref$hideTooltipOnCli = _ref.hideTooltipOnClick,
72
74
  hideTooltipOnClick = _ref$hideTooltipOnCli === void 0 ? false : _ref$hideTooltipOnCli,
73
75
  _ref$hideTooltipOnMou = _ref.hideTooltipOnMouseDown,
@@ -76,7 +78,9 @@ function Tooltip(_ref) {
76
78
  _ref$strategy = _ref.strategy,
77
79
  strategy = _ref$strategy === void 0 ? 'fixed' : _ref$strategy,
78
80
  _ref$ignoreTooltipPoi = _ref.ignoreTooltipPointerEvents,
79
- ignoreTooltipPointerEvents = _ref$ignoreTooltipPoi === void 0 ? false : _ref$ignoreTooltipPoi;
81
+ ignoreTooltipPointerEvents = _ref$ignoreTooltipPoi === void 0 ? false : _ref$ignoreTooltipPoi,
82
+ _ref$isScreenReaderAn = _ref.isScreenReaderAnnouncementDisabled,
83
+ isScreenReaderAnnouncementDisabled = _ref$isScreenReaderAn === void 0 ? false : _ref$isScreenReaderAn;
80
84
  var tooltipPosition = position === 'mouse' ? mousePosition : position;
81
85
  var onShowHandler = usePlatformLeafSyntheticEventHandler(_objectSpread({
82
86
  fn: onShow,
@@ -109,21 +113,15 @@ function Tooltip(_ref) {
109
113
  }, []);
110
114
 
111
115
  // Putting a few things into refs so that we don't have to break memoization
112
- var lastState = useRef(state);
113
- var lastDelay = useRef(delay);
114
- var lastHandlers = useRef({
115
- onShowHandler: onShowHandler,
116
- onHideHandler: onHideHandler
117
- });
116
+ var stableState = useStableRef(state);
117
+ // These props are placed in separate refs instead of a single object to reduce memory usage.
118
+ // Placing them in the same object previously caused an increase in the number of JavaScript event listeners
119
+ // before garbage collection.
120
+ var onShowHandlerStable = useStableRef(onShowHandler);
121
+ var onHideHandlerStable = useStableRef(onHideHandler);
122
+ var delayStable = useStableRef(delay);
123
+ var canAppearStable = useStableRef(canAppear);
118
124
  var hasCalledShowHandler = useRef(false);
119
- useEffect(function () {
120
- lastState.current = state;
121
- lastDelay.current = delay;
122
- lastHandlers.current = {
123
- onShowHandler: onShowHandler,
124
- onHideHandler: onHideHandler
125
- };
126
- }, [delay, onHideHandler, onShowHandler, state]);
127
125
  var start = useCallback(function (api) {
128
126
  // @ts-ignore
129
127
  apiRef.current = api;
@@ -135,7 +133,7 @@ function Tooltip(_ref) {
135
133
  }
136
134
  // Only call onHideHandler if we have called onShowHandler
137
135
  if (hasCalledShowHandler.current) {
138
- lastHandlers.current.onHideHandler();
136
+ onHideHandlerStable.current();
139
137
  }
140
138
  // @ts-ignore
141
139
  apiRef.current = null;
@@ -143,7 +141,7 @@ function Tooltip(_ref) {
143
141
  hasCalledShowHandler.current = false;
144
142
  // just in case
145
143
  setState('hide');
146
- }, []);
144
+ }, [onHideHandlerStable]);
147
145
  var abort = useCallback(function () {
148
146
  if (!apiRef.current) {
149
147
  return;
@@ -151,11 +149,11 @@ function Tooltip(_ref) {
151
149
  apiRef.current.abort();
152
150
  // Only call onHideHandler if we have called onShowHandler
153
151
  if (hasCalledShowHandler.current) {
154
- lastHandlers.current.onHideHandler();
152
+ onHideHandlerStable.current();
155
153
  }
156
154
  // @ts-ignore
157
155
  apiRef.current = null;
158
- }, []);
156
+ }, [onHideHandlerStable]);
159
157
  useEffect(function mount() {
160
158
  return function unmount() {
161
159
  if (apiRef.current) {
@@ -186,7 +184,8 @@ function Tooltip(_ref) {
186
184
  }
187
185
  });
188
186
  }, []);
189
- var showTooltip = useCallback(function (source) {
187
+ var tryShowTooltip = useCallback(function (source) {
188
+ var _canAppearStable$curr;
190
189
  /**
191
190
  * Prevent tooltips from being shown during a drag. This can occur with
192
191
  * the native drag and drop API, where some pointer events can fire
@@ -195,24 +194,45 @@ function Tooltip(_ref) {
195
194
  if (isDraggingRef.current) {
196
195
  return;
197
196
  }
197
+
198
+ // Another tooltip is has been active but we still have the old `api`
199
+ // around. We need to finish up the last usage.
200
+ // Note: just being safe - this should not happen
198
201
  if (apiRef.current && !apiRef.current.isActive()) {
199
202
  abort();
200
203
  }
201
204
 
202
- // Tell the tooltip to keep showing
205
+ // This tooltip is already active, we can exit
203
206
  if (apiRef.current && apiRef.current.isActive()) {
204
207
  apiRef.current.keep();
205
208
  return;
206
209
  }
210
+
211
+ /**
212
+ * Check if tooltip is allowed to show.
213
+ *
214
+ * Once a tooltip has started, or has scheduled to start
215
+ * we won't be checking `canAppear` again.
216
+ *
217
+ * - We don't want tooltips to disappear once they are shown
218
+ * - For consistency, we start after a single positive `canAppear`.
219
+ * Otherwise the amount of times we ask consumers would depend on
220
+ * how many times we get a "mousemove", which _could_ lead to situations
221
+ * where moving the mouse could result in a different outcome to if
222
+ * the mouse was not moved.
223
+ */
224
+ if (canAppearStable.current && !((_canAppearStable$curr = canAppearStable.current) !== null && _canAppearStable$curr !== void 0 && _canAppearStable$curr.call(canAppearStable))) {
225
+ return;
226
+ }
207
227
  var entry = {
208
228
  source: source,
209
- delay: lastDelay.current,
229
+ delay: delayStable.current,
210
230
  show: function show(_ref3) {
211
231
  var isImmediate = _ref3.isImmediate;
212
232
  // Call the onShow handler if it hasn't been called yet
213
233
  if (!hasCalledShowHandler.current) {
214
234
  hasCalledShowHandler.current = true;
215
- lastHandlers.current.onShowHandler();
235
+ onShowHandlerStable.current();
216
236
  }
217
237
  setState(isImmediate ? 'show-immediate' : 'fade-in');
218
238
  },
@@ -228,7 +248,7 @@ function Tooltip(_ref) {
228
248
  };
229
249
  var api = show(entry);
230
250
  start(api);
231
- }, [abort, done, start]);
251
+ }, [canAppearStable, delayStable, done, start, abort, onShowHandlerStable]);
232
252
  var hideTooltipOnEsc = useCallback(function () {
233
253
  var _apiRef$current2;
234
254
  (_apiRef$current2 = apiRef.current) === null || _apiRef$current2 === void 0 || _apiRef$current2.requestHide({
@@ -304,8 +324,8 @@ function Tooltip(_ref) {
304
324
  } : {
305
325
  type: 'keyboard'
306
326
  };
307
- showTooltip(source);
308
- }, [position, showTooltip]);
327
+ tryShowTooltip(source);
328
+ }, [position, tryShowTooltip]);
309
329
 
310
330
  // Ideally we would be using onMouseEnter here, but
311
331
  // because we are binding the event to the target parent
@@ -345,10 +365,12 @@ function Tooltip(_ref) {
345
365
  }
346
366
  }, []);
347
367
  var onFocus = useCallback(function () {
348
- showTooltip({
368
+ // TODO: this does not play well with `hideTooltipOnMouseDown`
369
+ // as "focus" will occur after the "mousedown".
370
+ tryShowTooltip({
349
371
  type: 'keyboard'
350
372
  });
351
- }, [showTooltip]);
373
+ }, [tryShowTooltip]);
352
374
  var onBlur = useCallback(function () {
353
375
  if (apiRef.current) {
354
376
  apiRef.current.requestHide({
@@ -358,18 +380,19 @@ function Tooltip(_ref) {
358
380
  }, []);
359
381
  var onAnimationFinished = useCallback(function (transition) {
360
382
  // Using lastState here because motion is not picking up the latest value
361
- if (transition === 'exiting' && lastState.current === 'fade-out' && apiRef.current) {
383
+ if (transition === 'exiting' && stableState.current === 'fade-out' && apiRef.current) {
362
384
  // @ts-ignore: refs are writeable
363
385
  apiRef.current.finishHideAnimation();
364
386
  }
365
- }, []);
387
+ }, [stableState]);
366
388
 
367
389
  // Doing a cast because typescript is struggling to narrow the type
368
390
  var CastTargetContainer = TargetContainer;
369
- var shouldRenderTooltipContainer = state !== 'hide' && Boolean(content);
391
+ var shouldRenderTooltipPopup = state !== 'hide' && Boolean(content);
392
+ var shouldRenderHiddenContent = !isScreenReaderAnnouncementDisabled && shouldRenderTooltipPopup;
370
393
  var shouldRenderTooltipChildren = state !== 'hide' && state !== 'fade-out';
371
394
  useNotifyOpenLayerObserver({
372
- isOpen: shouldRenderTooltipContainer
395
+ isOpen: shouldRenderTooltipPopup
373
396
  });
374
397
  var getReferenceElement = function getReferenceElement() {
375
398
  var _apiRef$current4;
@@ -379,7 +402,7 @@ function Tooltip(_ref) {
379
402
  }
380
403
  return targetRef.current || undefined;
381
404
  };
382
- var tooltipId = useUniqueId('tooltip', shouldRenderTooltipContainer);
405
+ var tooltipIdForHiddenContent = useUniqueId('tooltip', shouldRenderHiddenContent);
383
406
  var tooltipTriggerProps = {
384
407
  onMouseOver: onMouseOver,
385
408
  onMouseOut: onMouseOut,
@@ -398,38 +421,47 @@ function Tooltip(_ref) {
398
421
 
399
422
  // This useEffect is purely for managing the aria attribute when using the
400
423
  // wrapped children approach.
424
+ var isChildrenAFunction = typeof children === 'function';
401
425
  useEffect(function () {
402
- // If there is no container element, we should exit early, because that
403
- // means they are using the render prop API, and that is implemented in a
404
- // different way. If there is no target element yet or tooltipId, we also
405
- // shouldn't do anything because there is nothing to operate on or with.
406
- if (!containerRef.current || !targetRef.current || !tooltipId) {
426
+ if (isChildrenAFunction) {
407
427
  return;
408
428
  }
429
+
430
+ // If `children` is _not_ a function, we are stepping outside of the public
431
+ // API to add a `aria-describedby` attribute.
432
+
409
433
  var target = targetRef.current;
410
- if (shouldRenderTooltipContainer) {
411
- target.setAttribute('aria-describedby', tooltipId);
412
- } else {
413
- target.removeAttribute('aria-describedby');
434
+ if (!target || !tooltipIdForHiddenContent) {
435
+ return;
414
436
  }
415
- }, [shouldRenderTooltipContainer, tooltipId]);
416
- var hiddenContent = /*#__PURE__*/React.createElement("span", {
437
+ target.setAttribute('aria-describedby', tooltipIdForHiddenContent);
438
+ return function () {
439
+ return target.removeAttribute('aria-describedby');
440
+ };
441
+ }, [isChildrenAFunction, tooltipIdForHiddenContent]);
442
+ var hiddenContent = shouldRenderHiddenContent ? /*#__PURE__*/React.createElement("span", {
417
443
  "data-testid": testId ? "".concat(testId, "-hidden") : undefined,
418
444
  hidden: true,
419
- id: tooltipId
420
- }, typeof content === 'function' ? content({}) : content);
445
+ id: tooltipIdForHiddenContent
446
+ }, typeof content === 'function' ? content({}) : content) : null;
421
447
  return /*#__PURE__*/React.createElement(React.Fragment, null, typeof children === 'function' ?
422
448
  /*#__PURE__*/
423
449
  // once we deprecate the wrapped approach, we can put the aria
424
450
  // attribute back into the tooltipTriggerProps and make it required
425
451
  // instead of optional in `types`
426
452
  React.createElement(React.Fragment, null, children(_objectSpread(_objectSpread({}, tooltipTriggerProps), {}, {
427
- 'aria-describedby': tooltipId,
453
+ 'aria-describedby': tooltipIdForHiddenContent,
428
454
  ref: setDirectRef
429
- })), shouldRenderTooltipContainer && hiddenContent) : /*#__PURE__*/React.createElement(CastTargetContainer, _extends({}, tooltipTriggerProps, {
430
- ref: setImplicitRefFromChildren,
455
+ })), hiddenContent) : /*#__PURE__*/React.createElement(CastTargetContainer, _extends({}, tooltipTriggerProps, {
456
+ ref: setImplicitRefFromChildren
457
+ /**
458
+ * TODO: Why is role="presentation" added?
459
+ * - Is it only to "remove" the `Container` from screen readers?
460
+ * - Why is it added only to the `Container` but not to `tooltipTriggerProps`?
461
+ * - Should `role="presentation"` only be used if `shouldRenderHiddenContent == false`?
462
+ */,
431
463
  role: "presentation"
432
- }), children, shouldRenderTooltipContainer && hiddenContent), shouldRenderTooltipContainer ? /*#__PURE__*/React.createElement(Portal, {
464
+ }), children, hiddenContent), shouldRenderTooltipPopup ? /*#__PURE__*/React.createElement(Portal, {
433
465
  zIndex: tooltipZIndex
434
466
  }, /*#__PURE__*/React.createElement(Popper, {
435
467
  placement: tooltipPosition,
@@ -5,7 +5,7 @@ import { type TooltipProps } from './types';
5
5
  *
6
6
  * A tooltip is a floating, non-actionable label used to explain a user interface element or feature.
7
7
  */
8
- declare function Tooltip({ children, position, mousePosition, content, truncate, component: Container, tag: TargetContainer, testId, delay, onShow, onHide, hideTooltipOnClick, hideTooltipOnMouseDown, analyticsContext, strategy, ignoreTooltipPointerEvents, }: TooltipProps): JSX.Element;
8
+ declare function Tooltip({ children, position, mousePosition, content, truncate, component: Container, tag: TargetContainer, testId, delay, onShow, onHide, canAppear, hideTooltipOnClick, hideTooltipOnMouseDown, analyticsContext, strategy, ignoreTooltipPointerEvents, isScreenReaderAnnouncementDisabled, }: TooltipProps): JSX.Element;
9
9
  declare namespace Tooltip {
10
10
  var displayName: string;
11
11
  }
@@ -22,6 +22,11 @@ export interface TooltipProps {
22
22
  * 2. Function which returns a `ReactNode`
23
23
  * The benefit of the second approach is that it allows you to consume the `update` render prop.
24
24
  * This `update` function can be called to manually recalculate the position of the tooltip.
25
+ *
26
+ * This content will be rendered into two places:
27
+ * 1. Into the tooltip
28
+ * 2. Into a hidden element for screen readers (unless `isScreenReaderAnnouncementDisabled` is set to `true`)
29
+ *
25
30
  */
26
31
  content: ReactNode | (({ update }: {
27
32
  update?: () => void;
@@ -55,6 +60,25 @@ export interface TooltipProps {
55
60
  * When interacting with the target element using a keyboard, it will use this position against the target element instead.
56
61
  */
57
62
  mousePosition?: PositionTypeBase;
63
+ /**
64
+ * Whether or not the tooltip can be displayed. Once a tooltip
65
+ * is scheduled to be displayed, or is already displayed, it will
66
+ * continue to be shown.
67
+ *
68
+ * @description
69
+ *
70
+ * `canAppear()` is called in response to user events, and
71
+ * not during the rendering of components.
72
+ *
73
+ */
74
+ canAppear?: () => boolean;
75
+ /**
76
+ * By default tooltip content will be duplicated into a hidden element so
77
+ * it can be read out by a screen reader. Sometimes this is not ideal as
78
+ * it can result in the same content be announced twice. For those situations,
79
+ * you can leverage this prop to disable the duplicate hidden text.
80
+ */
81
+ isScreenReaderAnnouncementDisabled?: boolean;
58
82
  /**
59
83
  * Function to be called when the tooltip will be shown. It's called when the
60
84
  * tooltip begins to animate in.
@@ -5,7 +5,7 @@ import { type TooltipProps } from './types';
5
5
  *
6
6
  * A tooltip is a floating, non-actionable label used to explain a user interface element or feature.
7
7
  */
8
- declare function Tooltip({ children, position, mousePosition, content, truncate, component: Container, tag: TargetContainer, testId, delay, onShow, onHide, hideTooltipOnClick, hideTooltipOnMouseDown, analyticsContext, strategy, ignoreTooltipPointerEvents, }: TooltipProps): JSX.Element;
8
+ declare function Tooltip({ children, position, mousePosition, content, truncate, component: Container, tag: TargetContainer, testId, delay, onShow, onHide, canAppear, hideTooltipOnClick, hideTooltipOnMouseDown, analyticsContext, strategy, ignoreTooltipPointerEvents, isScreenReaderAnnouncementDisabled, }: TooltipProps): JSX.Element;
9
9
  declare namespace Tooltip {
10
10
  var displayName: string;
11
11
  }
@@ -22,6 +22,11 @@ export interface TooltipProps {
22
22
  * 2. Function which returns a `ReactNode`
23
23
  * The benefit of the second approach is that it allows you to consume the `update` render prop.
24
24
  * This `update` function can be called to manually recalculate the position of the tooltip.
25
+ *
26
+ * This content will be rendered into two places:
27
+ * 1. Into the tooltip
28
+ * 2. Into a hidden element for screen readers (unless `isScreenReaderAnnouncementDisabled` is set to `true`)
29
+ *
25
30
  */
26
31
  content: ReactNode | (({ update }: {
27
32
  update?: () => void;
@@ -55,6 +60,25 @@ export interface TooltipProps {
55
60
  * When interacting with the target element using a keyboard, it will use this position against the target element instead.
56
61
  */
57
62
  mousePosition?: PositionTypeBase;
63
+ /**
64
+ * Whether or not the tooltip can be displayed. Once a tooltip
65
+ * is scheduled to be displayed, or is already displayed, it will
66
+ * continue to be shown.
67
+ *
68
+ * @description
69
+ *
70
+ * `canAppear()` is called in response to user events, and
71
+ * not during the rendering of components.
72
+ *
73
+ */
74
+ canAppear?: () => boolean;
75
+ /**
76
+ * By default tooltip content will be duplicated into a hidden element so
77
+ * it can be read out by a screen reader. Sometimes this is not ideal as
78
+ * it can result in the same content be announced twice. For those situations,
79
+ * you can leverage this prop to disable the duplicate hidden text.
80
+ */
81
+ isScreenReaderAnnouncementDisabled?: boolean;
58
82
  /**
59
83
  * Function to be called when the tooltip will be shown. It's called when the
60
84
  * tooltip begins to animate in.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/tooltip",
3
- "version": "18.8.5",
3
+ "version": "18.9.1",
4
4
  "description": "A tooltip is a floating, non-actionable label used to explain a user interface element or feature.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -40,13 +40,13 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@atlaskit/analytics-next": "^10.1.0",
43
- "@atlaskit/ds-lib": "^3.1.0",
44
- "@atlaskit/layering": "^0.7.0",
43
+ "@atlaskit/ds-lib": "^3.2.0",
44
+ "@atlaskit/layering": "^0.7.3",
45
45
  "@atlaskit/motion": "^1.9.0",
46
46
  "@atlaskit/popper": "^6.3.0",
47
47
  "@atlaskit/portal": "^4.9.0",
48
48
  "@atlaskit/theme": "^14.0.0",
49
- "@atlaskit/tokens": "^2.0.0",
49
+ "@atlaskit/tokens": "^2.2.0",
50
50
  "@babel/runtime": "^7.0.0",
51
51
  "@emotion/react": "^11.7.1",
52
52
  "bind-event-listener": "^3.0.0"
@@ -57,7 +57,8 @@
57
57
  },
58
58
  "devDependencies": {
59
59
  "@af/accessibility-testing": "*",
60
- "@atlaskit/button": "^20.2.0",
60
+ "@af/integration-testing": "*",
61
+ "@atlaskit/button": "^20.3.0",
61
62
  "@atlaskit/ssr": "*",
62
63
  "@atlaskit/visual-regression": "*",
63
64
  "@emotion/styled": "^11.0.0",
@@ -67,7 +68,7 @@
67
68
  "jest-in-case": "^1.0.2",
68
69
  "react-dom": "^16.8.0",
69
70
  "react-redux": "^5.1.2",
70
- "storybook-addon-performance": "^0.16.0",
71
+ "storybook-addon-performance": "^0.17.3",
71
72
  "typescript": "~5.4.2",
72
73
  "wait-for-expect": "^1.2.0"
73
74
  },