@atlaskit/spotlight 0.5.0 → 0.6.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.
Files changed (32) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/cjs/controllers/context.js +36 -6
  3. package/dist/cjs/ui/card/caret/index.js +2 -2
  4. package/dist/cjs/ui/card/index.js +2 -2
  5. package/dist/cjs/ui/popover-content/index.js +21 -5
  6. package/dist/cjs/utils/use-focus-within/index.js +51 -0
  7. package/dist/cjs/utils/use-on-escape/index.js +40 -0
  8. package/dist/es2019/controllers/context.js +20 -6
  9. package/dist/es2019/ui/UNSAFE_update-on-change/index.js +3 -3
  10. package/dist/es2019/ui/card/caret/index.js +2 -2
  11. package/dist/es2019/ui/card/index.js +2 -2
  12. package/dist/es2019/ui/popover-content/index.js +20 -5
  13. package/dist/es2019/utils/use-focus-within/index.js +40 -0
  14. package/dist/es2019/utils/use-on-escape/index.js +31 -0
  15. package/dist/esm/controllers/context.js +36 -6
  16. package/dist/esm/ui/UNSAFE_update-on-change/index.js +3 -3
  17. package/dist/esm/ui/card/caret/index.js +2 -2
  18. package/dist/esm/ui/card/index.js +2 -2
  19. package/dist/esm/ui/popover-content/index.js +20 -5
  20. package/dist/esm/utils/use-focus-within/index.js +44 -0
  21. package/dist/esm/utils/use-on-escape/index.js +34 -0
  22. package/dist/types/controllers/context.d.ts +9 -3
  23. package/dist/types/ui/UNSAFE_update-on-change/index.d.ts +1 -1
  24. package/dist/types/ui/popover-content/index.d.ts +2 -1
  25. package/dist/types/utils/use-focus-within/index.d.ts +2 -0
  26. package/dist/types/utils/use-on-escape/index.d.ts +1 -0
  27. package/dist/types-ts4.5/controllers/context.d.ts +9 -3
  28. package/dist/types-ts4.5/ui/UNSAFE_update-on-change/index.d.ts +1 -1
  29. package/dist/types-ts4.5/ui/popover-content/index.d.ts +2 -1
  30. package/dist/types-ts4.5/utils/use-focus-within/index.d.ts +2 -0
  31. package/dist/types-ts4.5/utils/use-on-escape/index.d.ts +1 -0
  32. package/package.json +5 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @atlaskit/spotlight
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`c43cbdde6f08c`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/c43cbdde6f08c) -
8
+ `PopoverContent` `dismiss` prop is now required.
9
+
10
+ ## 0.5.1
11
+
12
+ ### Patch Changes
13
+
14
+ - [`4f9f525caaa13`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/4f9f525caaa13) -
15
+ Implement 'dismiss on escape key press' functionality. Escape will cause the current spotlight to
16
+ call the `dismiss` function passed to `PopoverContent`. Implementation of the `dismiss` function
17
+ is up to the consumer of the package.
18
+ - Updated dependencies
19
+
3
20
  ## 0.5.0
4
21
 
5
22
  ### Minor Changes
@@ -18,9 +18,11 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
18
18
 
19
19
  // eslint-disable-next-line @repo/internal/react/require-jsdoc
20
20
  var SpotlightContext = exports.SpotlightContext = /*#__PURE__*/(0, _react.createContext)({
21
- placement: 'bottom-end',
22
- setPlacement: function setPlacement() {
23
- return undefined;
21
+ card: {
22
+ placement: 'bottom-end',
23
+ setPlacement: function setPlacement() {
24
+ return undefined;
25
+ }
24
26
  },
25
27
  heading: {
26
28
  id: '',
@@ -29,6 +31,10 @@ var SpotlightContext = exports.SpotlightContext = /*#__PURE__*/(0, _react.create
29
31
  }
30
32
  },
31
33
  popoverContent: {
34
+ ref: undefined,
35
+ setRef: function setRef() {
36
+ return undefined;
37
+ },
32
38
  update: function update() {
33
39
  return function () {
34
40
  return new Promise(function () {
@@ -42,6 +48,14 @@ var SpotlightContext = exports.SpotlightContext = /*#__PURE__*/(0, _react.create
42
48
  return null;
43
49
  });
44
50
  };
51
+ },
52
+ dismiss: function dismiss() {
53
+ return undefined;
54
+ },
55
+ setDismiss: function setDismiss() {
56
+ return function () {
57
+ return undefined;
58
+ };
45
59
  }
46
60
  }
47
61
  });
@@ -74,17 +88,33 @@ var SpotlightContextProvider = exports.SpotlightContextProvider = function Spotl
74
88
  _useState6 = (0, _slicedToArray2.default)(_useState5, 2),
75
89
  update = _useState6[0],
76
90
  setUpdate = _useState6[1];
91
+ var _useState7 = (0, _react.useState)(),
92
+ _useState8 = (0, _slicedToArray2.default)(_useState7, 2),
93
+ ref = _useState8[0],
94
+ setRef = _useState8[1];
95
+ var _useState9 = (0, _react.useState)(function () {
96
+ return undefined;
97
+ }),
98
+ _useState0 = (0, _slicedToArray2.default)(_useState9, 2),
99
+ dismiss = _useState0[0],
100
+ setDismiss = _useState0[1];
77
101
  return /*#__PURE__*/React.createElement(SpotlightContext.Provider, {
78
102
  value: {
79
- placement: placement,
80
- setPlacement: setPlacement,
103
+ card: {
104
+ placement: placement,
105
+ setPlacement: setPlacement
106
+ },
81
107
  heading: {
82
108
  id: headingId,
83
109
  setId: setHeadingId
84
110
  },
85
111
  popoverContent: {
112
+ ref: ref,
113
+ setRef: setRef,
86
114
  update: update,
87
- setUpdate: setUpdate
115
+ setUpdate: setUpdate,
116
+ dismiss: dismiss,
117
+ setDismiss: setDismiss
88
118
  }
89
119
  }
90
120
  }, children);
@@ -34,10 +34,10 @@ var styles = {
34
34
  var Caret = exports.Caret = /*#__PURE__*/(0, _react.forwardRef)(function (_ref, ref) {
35
35
  var testId = _ref.testId;
36
36
  var _useContext = (0, _react.useContext)(_context.SpotlightContext),
37
- placement = _useContext.placement;
37
+ card = _useContext.card;
38
38
  return /*#__PURE__*/React.createElement("div", {
39
39
  "data-testid": testId,
40
40
  ref: ref,
41
- className: (0, _runtime.ax)([styles.root, styles[placement]])
41
+ className: (0, _runtime.ax)([styles.root, styles[card.placement]])
42
42
  });
43
43
  });
@@ -41,13 +41,13 @@ var SpotlightCard = exports.SpotlightCard = /*#__PURE__*/(0, _react.forwardRef)(
41
41
  var children = _ref.children,
42
42
  testId = _ref.testId;
43
43
  var _useContext = (0, _react.useContext)(_context.SpotlightContext),
44
- placement = _useContext.placement;
44
+ card = _useContext.card;
45
45
  return /*#__PURE__*/React.createElement("div", {
46
46
  "data-testid": testId,
47
47
  ref: ref,
48
48
  className: (0, _runtime.ax)([styles.root])
49
49
  }, /*#__PURE__*/React.createElement(_caret.Caret, null), /*#__PURE__*/React.createElement(_compiled.Box, {
50
50
  backgroundColor: "color.background.neutral.bold",
51
- xcss: (0, _css.cx)(styles.card, placementStyles[placement])
51
+ xcss: (0, _css.cx)(styles.card, placementStyles[card.placement])
52
52
  }, children));
53
53
  });
@@ -1,6 +1,7 @@
1
1
  /* index.tsx generated by @compiled/babel-plugin v0.38.1 */
2
2
  "use strict";
3
3
 
4
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5
  var _typeof = require("@babel/runtime/helpers/typeof");
5
6
  Object.defineProperty(exports, "__esModule", {
6
7
  value: true
@@ -10,8 +11,11 @@ require("./index.compiled.css");
10
11
  var _react = _interopRequireWildcard(require("react"));
11
12
  var React = _react;
12
13
  var _runtime = require("@compiled/react/runtime");
14
+ var _mergeRefs = _interopRequireDefault(require("@atlaskit/ds-lib/merge-refs"));
13
15
  var _popper = require("@atlaskit/popper");
14
16
  var _context = require("../../controllers/context");
17
+ var _useFocusWithin = require("../../utils/use-focus-within");
18
+ var _useOnEscape = require("../../utils/use-on-escape");
15
19
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
16
20
  var styles = {
17
21
  root: "_1pby1fw0"
@@ -55,19 +59,25 @@ var PopoverContent = exports.PopoverContent = function PopoverContent(_ref) {
55
59
  placement = _ref.placement,
56
60
  _ref$isVisible = _ref.isVisible,
57
61
  isVisible = _ref$isVisible === void 0 ? true : _ref$isVisible,
62
+ dismiss = _ref.dismiss,
58
63
  testId = _ref.testId;
59
64
  var updateRef = (0, _react.useRef)(function () {
60
65
  return new Promise(function () {
61
66
  return undefined;
62
67
  });
63
68
  });
69
+ var ref = (0, _react.useRef)();
64
70
  var _useContext = (0, _react.useContext)(_context.SpotlightContext),
65
71
  heading = _useContext.heading,
66
72
  popoverContent = _useContext.popoverContent,
67
- setPlacement = _useContext.setPlacement;
73
+ card = _useContext.card;
74
+ var focusWithin = (0, _useFocusWithin.useFocusWithin)(popoverContent.ref);
68
75
  (0, _react.useEffect)(function () {
69
- setPlacement(placement);
70
- }, [placement, setPlacement]);
76
+ popoverContent.setRef(ref);
77
+ }, [ref, popoverContent]);
78
+ (0, _react.useEffect)(function () {
79
+ card.setPlacement(placement);
80
+ }, [placement, card]);
71
81
  (0, _react.useEffect)(function () {
72
82
  if (updateRef.current) {
73
83
  popoverContent.setUpdate(function () {
@@ -75,11 +85,17 @@ var PopoverContent = exports.PopoverContent = function PopoverContent(_ref) {
75
85
  });
76
86
  }
77
87
  }, [popoverContent]);
88
+ (0, _useOnEscape.useOnEscape)(function (event) {
89
+ if (!focusWithin) {
90
+ return;
91
+ }
92
+ dismiss(event);
93
+ });
78
94
  return /*#__PURE__*/React.createElement(_popper.Popper, {
79
95
  offset: offset,
80
96
  placement: popperPlacementMap[placement]
81
97
  }, function (_ref2) {
82
- var ref = _ref2.ref,
98
+ var localRef = _ref2.ref,
83
99
  style = _ref2.style,
84
100
  update = _ref2.update;
85
101
  if (!isVisible) {
@@ -90,7 +106,7 @@ var PopoverContent = exports.PopoverContent = function PopoverContent(_ref) {
90
106
  role: "dialog",
91
107
  "data-testid": testId,
92
108
  "aria-labelledby": heading.id,
93
- ref: ref
109
+ ref: (0, _mergeRefs.default)([ref, localRef])
94
110
  // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
95
111
  ,
96
112
  style: style,
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.useFocusWithin = void 0;
8
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
9
+ var _react = require("react");
10
+ var _bindEventListener = require("bind-event-listener");
11
+ var _browserApis = require("@atlaskit/browser-apis");
12
+ var useFocusWithin = exports.useFocusWithin = function useFocusWithin(ref) {
13
+ var _useState = (0, _react.useState)(null),
14
+ _useState2 = (0, _slicedToArray2.default)(_useState, 2),
15
+ active = _useState2[0],
16
+ setActive = _useState2[1];
17
+ var updateFocusedElement = (0, _react.useCallback)(function () {
18
+ var doc = (0, _browserApis.getDocument)();
19
+ var activeElement = (doc === null || doc === void 0 ? void 0 : doc.activeElement) || null;
20
+ if (!ref || !ref.current) {
21
+ setActive(null);
22
+ return;
23
+ }
24
+ if (!activeElement) {
25
+ setActive(null);
26
+ return;
27
+ }
28
+
29
+ // Check if the focused element is within the ref element
30
+ if (!ref.current.contains(activeElement)) {
31
+ setActive(null);
32
+ return;
33
+ }
34
+ setActive(activeElement);
35
+ }, [ref]);
36
+ (0, _react.useEffect)(function () {
37
+ var doc = (0, _browserApis.getDocument)();
38
+ if (!doc) {
39
+ return;
40
+ }
41
+ updateFocusedElement();
42
+ var unbindFocusIn = (0, _bindEventListener.bind)(doc, {
43
+ type: 'focusin',
44
+ listener: updateFocusedElement
45
+ });
46
+ return function () {
47
+ unbindFocusIn();
48
+ };
49
+ }, [updateFocusedElement]);
50
+ return active;
51
+ };
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useOnEscape = void 0;
7
+ var _react = require("react");
8
+ var _bindEventListener = require("bind-event-listener");
9
+ var _browserApis = require("@atlaskit/browser-apis");
10
+ var onKeyDown = function onKeyDown(event, _ref) {
11
+ var onEscape = _ref.onEscape;
12
+ switch (event.key) {
13
+ case 'Escape':
14
+ onEscape();
15
+ break;
16
+ default:
17
+ break;
18
+ }
19
+ };
20
+ var useOnEscape = exports.useOnEscape = function useOnEscape(_onEscape) {
21
+ (0, _react.useEffect)(function () {
22
+ var doc = (0, _browserApis.getDocument)();
23
+ if (!doc) {
24
+ return;
25
+ }
26
+ var unbindFocusIn = (0, _bindEventListener.bind)(doc, {
27
+ type: 'keydown',
28
+ listener: function listener(event) {
29
+ return onKeyDown(event, {
30
+ onEscape: function onEscape() {
31
+ return _onEscape(event);
32
+ }
33
+ });
34
+ }
35
+ });
36
+ return function () {
37
+ unbindFocusIn();
38
+ };
39
+ }, [_onEscape]);
40
+ };
@@ -7,15 +7,21 @@ import { createContext, useId, useState } from 'react';
7
7
 
8
8
  // eslint-disable-next-line @repo/internal/react/require-jsdoc
9
9
  export const SpotlightContext = /*#__PURE__*/createContext({
10
- placement: 'bottom-end',
11
- setPlacement: () => undefined,
10
+ card: {
11
+ placement: 'bottom-end',
12
+ setPlacement: () => undefined
13
+ },
12
14
  heading: {
13
15
  id: '',
14
16
  setId: () => undefined
15
17
  },
16
18
  popoverContent: {
19
+ ref: undefined,
20
+ setRef: () => undefined,
17
21
  update: () => () => new Promise(() => null),
18
- setUpdate: () => () => new Promise(() => null)
22
+ setUpdate: () => () => new Promise(() => null),
23
+ dismiss: () => undefined,
24
+ setDismiss: () => () => undefined
19
25
  }
20
26
  });
21
27
 
@@ -27,17 +33,25 @@ export const SpotlightContextProvider = ({
27
33
  const [placement, setPlacement] = useState('bottom-end');
28
34
  const [headingId, setHeadingId] = useState(`${id}-heading`);
29
35
  const [update, setUpdate] = useState(() => async () => undefined);
36
+ const [ref, setRef] = useState();
37
+ const [dismiss, setDismiss] = useState(() => undefined);
30
38
  return /*#__PURE__*/React.createElement(SpotlightContext.Provider, {
31
39
  value: {
32
- placement,
33
- setPlacement,
40
+ card: {
41
+ placement,
42
+ setPlacement
43
+ },
34
44
  heading: {
35
45
  id: headingId,
36
46
  setId: setHeadingId
37
47
  },
38
48
  popoverContent: {
49
+ ref,
50
+ setRef,
39
51
  update,
40
- setUpdate
52
+ setUpdate,
53
+ dismiss,
54
+ setDismiss
41
55
  }
42
56
  }
43
57
  }, children);
@@ -1,6 +1,6 @@
1
- import { useContext, useEffect } from "react";
2
- import { getDocument } from "@atlaskit/browser-apis";
3
- import { SpotlightContext } from "../../controllers/context";
1
+ import { useContext, useEffect } from 'react';
2
+ import { getDocument } from '@atlaskit/browser-apis';
3
+ import { SpotlightContext } from '../../controllers/context';
4
4
  const defaultOptions = {
5
5
  childList: true,
6
6
  subtree: true
@@ -27,11 +27,11 @@ export const Caret = /*#__PURE__*/forwardRef(({
27
27
  testId
28
28
  }, ref) => {
29
29
  const {
30
- placement
30
+ card
31
31
  } = useContext(SpotlightContext);
32
32
  return /*#__PURE__*/React.createElement("div", {
33
33
  "data-testid": testId,
34
34
  ref: ref,
35
- className: ax([styles.root, styles[placement]])
35
+ className: ax([styles.root, styles[card.placement]])
36
36
  });
37
37
  });
@@ -34,7 +34,7 @@ export const SpotlightCard = /*#__PURE__*/forwardRef(({
34
34
  testId
35
35
  }, ref) => {
36
36
  const {
37
- placement
37
+ card
38
38
  } = useContext(SpotlightContext);
39
39
  return /*#__PURE__*/React.createElement("div", {
40
40
  "data-testid": testId,
@@ -42,6 +42,6 @@ export const SpotlightCard = /*#__PURE__*/forwardRef(({
42
42
  className: ax([styles.root])
43
43
  }, /*#__PURE__*/React.createElement(Caret, null), /*#__PURE__*/React.createElement(Box, {
44
44
  backgroundColor: "color.background.neutral.bold",
45
- xcss: cx(styles.card, placementStyles[placement])
45
+ xcss: cx(styles.card, placementStyles[card.placement])
46
46
  }, children));
47
47
  });
@@ -3,8 +3,11 @@ import "./index.compiled.css";
3
3
  import * as React from 'react';
4
4
  import { ax, ix } from "@compiled/react/runtime";
5
5
  import { useContext, useEffect, useRef } from 'react';
6
+ import mergeRefs from '@atlaskit/ds-lib/merge-refs';
6
7
  import { Popper } from '@atlaskit/popper';
7
8
  import { SpotlightContext } from '../../controllers/context';
9
+ import { useFocusWithin } from '../../utils/use-focus-within';
10
+ import { useOnEscape } from '../../utils/use-on-escape';
8
11
  const styles = {
9
12
  root: "_1pby1fw0"
10
13
  };
@@ -46,27 +49,39 @@ export const PopoverContent = ({
46
49
  children,
47
50
  placement,
48
51
  isVisible = true,
52
+ dismiss,
49
53
  testId
50
54
  }) => {
51
55
  const updateRef = useRef(() => new Promise(() => undefined));
56
+ const ref = useRef();
52
57
  const {
53
58
  heading,
54
59
  popoverContent,
55
- setPlacement
60
+ card
56
61
  } = useContext(SpotlightContext);
62
+ const focusWithin = useFocusWithin(popoverContent.ref);
57
63
  useEffect(() => {
58
- setPlacement(placement);
59
- }, [placement, setPlacement]);
64
+ popoverContent.setRef(ref);
65
+ }, [ref, popoverContent]);
66
+ useEffect(() => {
67
+ card.setPlacement(placement);
68
+ }, [placement, card]);
60
69
  useEffect(() => {
61
70
  if (updateRef.current) {
62
71
  popoverContent.setUpdate(() => updateRef.current);
63
72
  }
64
73
  }, [popoverContent]);
74
+ useOnEscape(event => {
75
+ if (!focusWithin) {
76
+ return;
77
+ }
78
+ dismiss(event);
79
+ });
65
80
  return /*#__PURE__*/React.createElement(Popper, {
66
81
  offset: offset,
67
82
  placement: popperPlacementMap[placement]
68
83
  }, ({
69
- ref,
84
+ ref: localRef,
70
85
  style,
71
86
  update
72
87
  }) => {
@@ -78,7 +93,7 @@ export const PopoverContent = ({
78
93
  role: "dialog",
79
94
  "data-testid": testId,
80
95
  "aria-labelledby": heading.id,
81
- ref: ref
96
+ ref: mergeRefs([ref, localRef])
82
97
  // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
83
98
  ,
84
99
  style: style,
@@ -0,0 +1,40 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { bind } from 'bind-event-listener';
3
+ import { getDocument } from '@atlaskit/browser-apis';
4
+ export const useFocusWithin = ref => {
5
+ const [active, setActive] = useState(null);
6
+ const updateFocusedElement = useCallback(() => {
7
+ const doc = getDocument();
8
+ const activeElement = (doc === null || doc === void 0 ? void 0 : doc.activeElement) || null;
9
+ if (!ref || !ref.current) {
10
+ setActive(null);
11
+ return;
12
+ }
13
+ if (!activeElement) {
14
+ setActive(null);
15
+ return;
16
+ }
17
+
18
+ // Check if the focused element is within the ref element
19
+ if (!ref.current.contains(activeElement)) {
20
+ setActive(null);
21
+ return;
22
+ }
23
+ setActive(activeElement);
24
+ }, [ref]);
25
+ useEffect(() => {
26
+ const doc = getDocument();
27
+ if (!doc) {
28
+ return;
29
+ }
30
+ updateFocusedElement();
31
+ const unbindFocusIn = bind(doc, {
32
+ type: 'focusin',
33
+ listener: updateFocusedElement
34
+ });
35
+ return () => {
36
+ unbindFocusIn();
37
+ };
38
+ }, [updateFocusedElement]);
39
+ return active;
40
+ };
@@ -0,0 +1,31 @@
1
+ import { useEffect } from 'react';
2
+ import { bind } from 'bind-event-listener';
3
+ import { getDocument } from '@atlaskit/browser-apis';
4
+ const onKeyDown = (event, {
5
+ onEscape
6
+ }) => {
7
+ switch (event.key) {
8
+ case 'Escape':
9
+ onEscape();
10
+ break;
11
+ default:
12
+ break;
13
+ }
14
+ };
15
+ export const useOnEscape = onEscape => {
16
+ useEffect(() => {
17
+ const doc = getDocument();
18
+ if (!doc) {
19
+ return;
20
+ }
21
+ const unbindFocusIn = bind(doc, {
22
+ type: 'keydown',
23
+ listener: event => onKeyDown(event, {
24
+ onEscape: () => onEscape(event)
25
+ })
26
+ });
27
+ return () => {
28
+ unbindFocusIn();
29
+ };
30
+ }, [onEscape]);
31
+ };
@@ -10,9 +10,11 @@ import { createContext, useId, useState } from 'react';
10
10
 
11
11
  // eslint-disable-next-line @repo/internal/react/require-jsdoc
12
12
  export var SpotlightContext = /*#__PURE__*/createContext({
13
- placement: 'bottom-end',
14
- setPlacement: function setPlacement() {
15
- return undefined;
13
+ card: {
14
+ placement: 'bottom-end',
15
+ setPlacement: function setPlacement() {
16
+ return undefined;
17
+ }
16
18
  },
17
19
  heading: {
18
20
  id: '',
@@ -21,6 +23,10 @@ export var SpotlightContext = /*#__PURE__*/createContext({
21
23
  }
22
24
  },
23
25
  popoverContent: {
26
+ ref: undefined,
27
+ setRef: function setRef() {
28
+ return undefined;
29
+ },
24
30
  update: function update() {
25
31
  return function () {
26
32
  return new Promise(function () {
@@ -34,6 +40,14 @@ export var SpotlightContext = /*#__PURE__*/createContext({
34
40
  return null;
35
41
  });
36
42
  };
43
+ },
44
+ dismiss: function dismiss() {
45
+ return undefined;
46
+ },
47
+ setDismiss: function setDismiss() {
48
+ return function () {
49
+ return undefined;
50
+ };
37
51
  }
38
52
  }
39
53
  });
@@ -66,17 +80,33 @@ export var SpotlightContextProvider = function SpotlightContextProvider(_ref) {
66
80
  _useState6 = _slicedToArray(_useState5, 2),
67
81
  update = _useState6[0],
68
82
  setUpdate = _useState6[1];
83
+ var _useState7 = useState(),
84
+ _useState8 = _slicedToArray(_useState7, 2),
85
+ ref = _useState8[0],
86
+ setRef = _useState8[1];
87
+ var _useState9 = useState(function () {
88
+ return undefined;
89
+ }),
90
+ _useState0 = _slicedToArray(_useState9, 2),
91
+ dismiss = _useState0[0],
92
+ setDismiss = _useState0[1];
69
93
  return /*#__PURE__*/React.createElement(SpotlightContext.Provider, {
70
94
  value: {
71
- placement: placement,
72
- setPlacement: setPlacement,
95
+ card: {
96
+ placement: placement,
97
+ setPlacement: setPlacement
98
+ },
73
99
  heading: {
74
100
  id: headingId,
75
101
  setId: setHeadingId
76
102
  },
77
103
  popoverContent: {
104
+ ref: ref,
105
+ setRef: setRef,
78
106
  update: update,
79
- setUpdate: setUpdate
107
+ setUpdate: setUpdate,
108
+ dismiss: dismiss,
109
+ setDismiss: setDismiss
80
110
  }
81
111
  }
82
112
  }, children);
@@ -1,6 +1,6 @@
1
- import { useContext, useEffect } from "react";
2
- import { getDocument } from "@atlaskit/browser-apis";
3
- import { SpotlightContext } from "../../controllers/context";
1
+ import { useContext, useEffect } from 'react';
2
+ import { getDocument } from '@atlaskit/browser-apis';
3
+ import { SpotlightContext } from '../../controllers/context';
4
4
  var defaultOptions = {
5
5
  childList: true,
6
6
  subtree: true
@@ -26,10 +26,10 @@ var styles = {
26
26
  export var Caret = /*#__PURE__*/forwardRef(function (_ref, ref) {
27
27
  var testId = _ref.testId;
28
28
  var _useContext = useContext(SpotlightContext),
29
- placement = _useContext.placement;
29
+ card = _useContext.card;
30
30
  return /*#__PURE__*/React.createElement("div", {
31
31
  "data-testid": testId,
32
32
  ref: ref,
33
- className: ax([styles.root, styles[placement]])
33
+ className: ax([styles.root, styles[card.placement]])
34
34
  });
35
35
  });
@@ -33,13 +33,13 @@ export var SpotlightCard = /*#__PURE__*/forwardRef(function (_ref, ref) {
33
33
  var children = _ref.children,
34
34
  testId = _ref.testId;
35
35
  var _useContext = useContext(SpotlightContext),
36
- placement = _useContext.placement;
36
+ card = _useContext.card;
37
37
  return /*#__PURE__*/React.createElement("div", {
38
38
  "data-testid": testId,
39
39
  ref: ref,
40
40
  className: ax([styles.root])
41
41
  }, /*#__PURE__*/React.createElement(Caret, null), /*#__PURE__*/React.createElement(Box, {
42
42
  backgroundColor: "color.background.neutral.bold",
43
- xcss: cx(styles.card, placementStyles[placement])
43
+ xcss: cx(styles.card, placementStyles[card.placement])
44
44
  }, children));
45
45
  });
@@ -3,8 +3,11 @@ import "./index.compiled.css";
3
3
  import * as React from 'react';
4
4
  import { ax, ix } from "@compiled/react/runtime";
5
5
  import { useContext, useEffect, useRef } from 'react';
6
+ import mergeRefs from '@atlaskit/ds-lib/merge-refs';
6
7
  import { Popper } from '@atlaskit/popper';
7
8
  import { SpotlightContext } from '../../controllers/context';
9
+ import { useFocusWithin } from '../../utils/use-focus-within';
10
+ import { useOnEscape } from '../../utils/use-on-escape';
8
11
  var styles = {
9
12
  root: "_1pby1fw0"
10
13
  };
@@ -47,19 +50,25 @@ export var PopoverContent = function PopoverContent(_ref) {
47
50
  placement = _ref.placement,
48
51
  _ref$isVisible = _ref.isVisible,
49
52
  isVisible = _ref$isVisible === void 0 ? true : _ref$isVisible,
53
+ dismiss = _ref.dismiss,
50
54
  testId = _ref.testId;
51
55
  var updateRef = useRef(function () {
52
56
  return new Promise(function () {
53
57
  return undefined;
54
58
  });
55
59
  });
60
+ var ref = useRef();
56
61
  var _useContext = useContext(SpotlightContext),
57
62
  heading = _useContext.heading,
58
63
  popoverContent = _useContext.popoverContent,
59
- setPlacement = _useContext.setPlacement;
64
+ card = _useContext.card;
65
+ var focusWithin = useFocusWithin(popoverContent.ref);
60
66
  useEffect(function () {
61
- setPlacement(placement);
62
- }, [placement, setPlacement]);
67
+ popoverContent.setRef(ref);
68
+ }, [ref, popoverContent]);
69
+ useEffect(function () {
70
+ card.setPlacement(placement);
71
+ }, [placement, card]);
63
72
  useEffect(function () {
64
73
  if (updateRef.current) {
65
74
  popoverContent.setUpdate(function () {
@@ -67,11 +76,17 @@ export var PopoverContent = function PopoverContent(_ref) {
67
76
  });
68
77
  }
69
78
  }, [popoverContent]);
79
+ useOnEscape(function (event) {
80
+ if (!focusWithin) {
81
+ return;
82
+ }
83
+ dismiss(event);
84
+ });
70
85
  return /*#__PURE__*/React.createElement(Popper, {
71
86
  offset: offset,
72
87
  placement: popperPlacementMap[placement]
73
88
  }, function (_ref2) {
74
- var ref = _ref2.ref,
89
+ var localRef = _ref2.ref,
75
90
  style = _ref2.style,
76
91
  update = _ref2.update;
77
92
  if (!isVisible) {
@@ -82,7 +97,7 @@ export var PopoverContent = function PopoverContent(_ref) {
82
97
  role: "dialog",
83
98
  "data-testid": testId,
84
99
  "aria-labelledby": heading.id,
85
- ref: ref
100
+ ref: mergeRefs([ref, localRef])
86
101
  // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
87
102
  ,
88
103
  style: style,
@@ -0,0 +1,44 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
+ import { useCallback, useEffect, useState } from 'react';
3
+ import { bind } from 'bind-event-listener';
4
+ import { getDocument } from '@atlaskit/browser-apis';
5
+ export var useFocusWithin = function useFocusWithin(ref) {
6
+ var _useState = useState(null),
7
+ _useState2 = _slicedToArray(_useState, 2),
8
+ active = _useState2[0],
9
+ setActive = _useState2[1];
10
+ var updateFocusedElement = useCallback(function () {
11
+ var doc = getDocument();
12
+ var activeElement = (doc === null || doc === void 0 ? void 0 : doc.activeElement) || null;
13
+ if (!ref || !ref.current) {
14
+ setActive(null);
15
+ return;
16
+ }
17
+ if (!activeElement) {
18
+ setActive(null);
19
+ return;
20
+ }
21
+
22
+ // Check if the focused element is within the ref element
23
+ if (!ref.current.contains(activeElement)) {
24
+ setActive(null);
25
+ return;
26
+ }
27
+ setActive(activeElement);
28
+ }, [ref]);
29
+ useEffect(function () {
30
+ var doc = getDocument();
31
+ if (!doc) {
32
+ return;
33
+ }
34
+ updateFocusedElement();
35
+ var unbindFocusIn = bind(doc, {
36
+ type: 'focusin',
37
+ listener: updateFocusedElement
38
+ });
39
+ return function () {
40
+ unbindFocusIn();
41
+ };
42
+ }, [updateFocusedElement]);
43
+ return active;
44
+ };
@@ -0,0 +1,34 @@
1
+ import { useEffect } from 'react';
2
+ import { bind } from 'bind-event-listener';
3
+ import { getDocument } from '@atlaskit/browser-apis';
4
+ var onKeyDown = function onKeyDown(event, _ref) {
5
+ var onEscape = _ref.onEscape;
6
+ switch (event.key) {
7
+ case 'Escape':
8
+ onEscape();
9
+ break;
10
+ default:
11
+ break;
12
+ }
13
+ };
14
+ export var useOnEscape = function useOnEscape(_onEscape) {
15
+ useEffect(function () {
16
+ var doc = getDocument();
17
+ if (!doc) {
18
+ return;
19
+ }
20
+ var unbindFocusIn = bind(doc, {
21
+ type: 'keydown',
22
+ listener: function listener(event) {
23
+ return onKeyDown(event, {
24
+ onEscape: function onEscape() {
25
+ return _onEscape(event);
26
+ }
27
+ });
28
+ }
29
+ });
30
+ return function () {
31
+ unbindFocusIn();
32
+ };
33
+ }, [_onEscape]);
34
+ };
@@ -2,18 +2,24 @@
2
2
  * @jsxRuntime classic
3
3
  * @jsx jsx
4
4
  */
5
- import { type Dispatch, type ReactNode, type SetStateAction } from 'react';
5
+ import { type Dispatch, type MutableRefObject, type ReactNode, type SetStateAction } from 'react';
6
6
  import type { Placement } from '../types';
7
7
  export interface SpotlightContextType {
8
- placement: Placement;
9
- setPlacement: Dispatch<SetStateAction<Placement>>;
8
+ card: {
9
+ placement: Placement;
10
+ setPlacement: Dispatch<SetStateAction<Placement>>;
11
+ };
10
12
  heading: {
11
13
  id: string;
12
14
  setId: Dispatch<SetStateAction<string>>;
13
15
  };
14
16
  popoverContent: {
17
+ ref: MutableRefObject<HTMLDivElement | undefined> | undefined;
18
+ setRef: Dispatch<SetStateAction<MutableRefObject<HTMLDivElement | undefined> | undefined>>;
15
19
  update: () => () => Promise<any>;
16
20
  setUpdate: Dispatch<SetStateAction<() => () => Promise<any>>>;
21
+ dismiss: () => void;
22
+ setDismiss: Dispatch<SetStateAction<() => void>>;
17
23
  };
18
24
  }
19
25
  export declare const SpotlightContext: import("react").Context<SpotlightContextType>;
@@ -2,5 +2,5 @@ interface UpdateOnChangeProps {
2
2
  selectors?: string[];
3
3
  options?: MutationObserverInit;
4
4
  }
5
- export declare const UNSAFE_UpdateOnChange: ({ selectors, options }: UpdateOnChangeProps) => null;
5
+ export declare const UNSAFE_UpdateOnChange: ({ selectors, options, }: UpdateOnChangeProps) => null;
6
6
  export {};
@@ -13,6 +13,7 @@ export interface PopoverContentProps {
13
13
  testId?: string;
14
14
  placement: Placement;
15
15
  isVisible?: boolean;
16
+ dismiss: (event: KeyboardEvent) => void;
16
17
  children: ReactNode;
17
18
  }
18
19
  /**
@@ -20,4 +21,4 @@ export interface PopoverContentProps {
20
21
  *
21
22
  * A `PopoverContent` is the element that is shown as a popover.
22
23
  */
23
- export declare const PopoverContent: ({ children, placement, isVisible, testId, }: PopoverContentProps) => JSX.Element;
24
+ export declare const PopoverContent: ({ children, placement, isVisible, dismiss, testId, }: PopoverContentProps) => JSX.Element;
@@ -0,0 +1,2 @@
1
+ import { type MutableRefObject } from 'react';
2
+ export declare const useFocusWithin: (ref: MutableRefObject<HTMLDivElement | undefined> | undefined) => Element | null;
@@ -0,0 +1 @@
1
+ export declare const useOnEscape: (onEscape: (event: KeyboardEvent) => void) => void;
@@ -2,18 +2,24 @@
2
2
  * @jsxRuntime classic
3
3
  * @jsx jsx
4
4
  */
5
- import { type Dispatch, type ReactNode, type SetStateAction } from 'react';
5
+ import { type Dispatch, type MutableRefObject, type ReactNode, type SetStateAction } from 'react';
6
6
  import type { Placement } from '../types';
7
7
  export interface SpotlightContextType {
8
- placement: Placement;
9
- setPlacement: Dispatch<SetStateAction<Placement>>;
8
+ card: {
9
+ placement: Placement;
10
+ setPlacement: Dispatch<SetStateAction<Placement>>;
11
+ };
10
12
  heading: {
11
13
  id: string;
12
14
  setId: Dispatch<SetStateAction<string>>;
13
15
  };
14
16
  popoverContent: {
17
+ ref: MutableRefObject<HTMLDivElement | undefined> | undefined;
18
+ setRef: Dispatch<SetStateAction<MutableRefObject<HTMLDivElement | undefined> | undefined>>;
15
19
  update: () => () => Promise<any>;
16
20
  setUpdate: Dispatch<SetStateAction<() => () => Promise<any>>>;
21
+ dismiss: () => void;
22
+ setDismiss: Dispatch<SetStateAction<() => void>>;
17
23
  };
18
24
  }
19
25
  export declare const SpotlightContext: import("react").Context<SpotlightContextType>;
@@ -2,5 +2,5 @@ interface UpdateOnChangeProps {
2
2
  selectors?: string[];
3
3
  options?: MutationObserverInit;
4
4
  }
5
- export declare const UNSAFE_UpdateOnChange: ({ selectors, options }: UpdateOnChangeProps) => null;
5
+ export declare const UNSAFE_UpdateOnChange: ({ selectors, options, }: UpdateOnChangeProps) => null;
6
6
  export {};
@@ -13,6 +13,7 @@ export interface PopoverContentProps {
13
13
  testId?: string;
14
14
  placement: Placement;
15
15
  isVisible?: boolean;
16
+ dismiss: (event: KeyboardEvent) => void;
16
17
  children: ReactNode;
17
18
  }
18
19
  /**
@@ -20,4 +21,4 @@ export interface PopoverContentProps {
20
21
  *
21
22
  * A `PopoverContent` is the element that is shown as a popover.
22
23
  */
23
- export declare const PopoverContent: ({ children, placement, isVisible, testId, }: PopoverContentProps) => JSX.Element;
24
+ export declare const PopoverContent: ({ children, placement, isVisible, dismiss, testId, }: PopoverContentProps) => JSX.Element;
@@ -0,0 +1,2 @@
1
+ import { type MutableRefObject } from 'react';
2
+ export declare const useFocusWithin: (ref: MutableRefObject<HTMLDivElement | undefined> | undefined) => Element | null;
@@ -0,0 +1 @@
1
+ export declare const useOnEscape: (onEscape: (event: KeyboardEvent) => void) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/spotlight",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "A spotlight introduces users to various points of interest across Atlassian through focused messages or multi-step tours.",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -29,15 +29,17 @@
29
29
  "@atlaskit/browser-apis": "^0.0.1",
30
30
  "@atlaskit/button": "^23.5.0",
31
31
  "@atlaskit/css": "^0.14.0",
32
+ "@atlaskit/ds-lib": "^5.1.0",
32
33
  "@atlaskit/heading": "^5.2.0",
33
- "@atlaskit/icon": "^28.3.0",
34
+ "@atlaskit/icon": "^28.5.0",
34
35
  "@atlaskit/image": "^3.0.0",
35
36
  "@atlaskit/popper": "^7.1.0",
36
37
  "@atlaskit/primitives": "^14.15.0",
37
38
  "@atlaskit/tokens": "^6.4.0",
38
39
  "@atlaskit/visually-hidden": "^3.0.0",
39
40
  "@babel/runtime": "^7.0.0",
40
- "@compiled/react": "^0.18.3"
41
+ "@compiled/react": "^0.18.3",
42
+ "bind-event-listener": "^3.0.0"
41
43
  },
42
44
  "peerDependencies": {
43
45
  "react": "^18.2.0"