@khanacademy/wonder-blocks-popover 3.2.15 → 3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @khanacademy/wonder-blocks-popover
2
2
 
3
+ ## 3.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - badad6ee: Adds a `viewportPadding` prop to provide spacing between the popper and the viewport edges. If this prop is not provided, default spacing is applied.
8
+
9
+ ### Patch Changes
10
+
11
+ - fcab789b: Only show the `TooltipPopper` contents once the popper has positioned itself. This fixes the issue where Tooltips are initially rendered in the top left corner for a brief moment before moving to the correct position (which was causing a flickering effect).
12
+ - Updated dependencies [badad6ee]
13
+ - Updated dependencies [fcab789b]
14
+ - @khanacademy/wonder-blocks-tooltip@2.5.0
15
+
16
+ ## 3.2.16
17
+
18
+ ### Patch Changes
19
+
20
+ - 02a1b298: Make sure we don't package tsconfig and tsbuildinfo files
21
+ - Updated dependencies [02a1b298]
22
+ - @khanacademy/wonder-blocks-core@7.0.1
23
+ - @khanacademy/wonder-blocks-icon-button@5.4.1
24
+ - @khanacademy/wonder-blocks-modal@5.1.12
25
+ - @khanacademy/wonder-blocks-tokens@2.0.1
26
+ - @khanacademy/wonder-blocks-tooltip@2.4.3
27
+ - @khanacademy/wonder-blocks-typography@2.1.16
28
+
3
29
  ## 3.2.15
4
30
 
5
31
  ### Patch Changes
@@ -103,6 +103,12 @@ type Props = AriaProps & Readonly<{
103
103
  * on where there is available room within the document body.
104
104
  */
105
105
  rootBoundary?: RootBoundary;
106
+ /**
107
+ * If `rootBoundary` is `viewport`, this padding value is used to provide
108
+ * spacing between the popper and the viewport. If not provided, default
109
+ * spacing of 12px is applied.
110
+ */
111
+ viewportPadding?: number;
106
112
  }>;
107
113
  type State = Readonly<{
108
114
  /**
package/dist/es/index.js CHANGED
@@ -449,7 +449,8 @@ class Popover extends React.Component {
449
449
  portal,
450
450
  "aria-label": ariaLabel,
451
451
  "aria-describedby": ariaDescribedBy,
452
- rootBoundary
452
+ rootBoundary,
453
+ viewportPadding
453
454
  } = this.props;
454
455
  const {
455
456
  anchorElement
@@ -459,7 +460,8 @@ class Popover extends React.Component {
459
460
  const popperContent = React.createElement(TooltipPopper, {
460
461
  anchorElement: anchorElement,
461
462
  placement: placement,
462
- rootBoundary: rootBoundary
463
+ rootBoundary: rootBoundary,
464
+ viewportPadding: viewportPadding
463
465
  }, props => React.createElement(PopoverDialog, _extends({}, props, {
464
466
  "aria-label": ariaLabel,
465
467
  "aria-describedby": describedBy,
package/dist/index.js CHANGED
@@ -479,7 +479,8 @@ class Popover extends React__namespace.Component {
479
479
  portal,
480
480
  "aria-label": ariaLabel,
481
481
  "aria-describedby": ariaDescribedBy,
482
- rootBoundary
482
+ rootBoundary,
483
+ viewportPadding
483
484
  } = this.props;
484
485
  const {
485
486
  anchorElement
@@ -489,7 +490,8 @@ class Popover extends React__namespace.Component {
489
490
  const popperContent = React__namespace.createElement(wonderBlocksTooltip.TooltipPopper, {
490
491
  anchorElement: anchorElement,
491
492
  placement: placement,
492
- rootBoundary: rootBoundary
493
+ rootBoundary: rootBoundary,
494
+ viewportPadding: viewportPadding
493
495
  }, props => React__namespace.createElement(PopoverDialog, _extends__default["default"]({}, props, {
494
496
  "aria-label": ariaLabel,
495
497
  "aria-describedby": describedBy,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-popover",
3
- "version": "3.2.15",
3
+ "version": "3.3.0",
4
4
  "design": "v1",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -16,12 +16,12 @@
16
16
  "license": "MIT",
17
17
  "dependencies": {
18
18
  "@babel/runtime": "^7.18.6",
19
- "@khanacademy/wonder-blocks-core": "^7.0.0",
20
- "@khanacademy/wonder-blocks-icon-button": "^5.4.0",
21
- "@khanacademy/wonder-blocks-modal": "^5.1.11",
22
- "@khanacademy/wonder-blocks-tokens": "^2.0.0",
23
- "@khanacademy/wonder-blocks-tooltip": "^2.4.2",
24
- "@khanacademy/wonder-blocks-typography": "^2.1.15"
19
+ "@khanacademy/wonder-blocks-core": "^7.0.1",
20
+ "@khanacademy/wonder-blocks-icon-button": "^5.4.1",
21
+ "@khanacademy/wonder-blocks-modal": "^5.1.12",
22
+ "@khanacademy/wonder-blocks-tokens": "^2.0.1",
23
+ "@khanacademy/wonder-blocks-tooltip": "^2.5.0",
24
+ "@khanacademy/wonder-blocks-typography": "^2.1.16"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "@phosphor-icons/core": "^2.0.2",
@@ -1,180 +0,0 @@
1
- import * as React from "react";
2
- import {render, screen} from "@testing-library/react";
3
- import {userEvent} from "@testing-library/user-event";
4
-
5
- import FocusManager from "../focus-manager";
6
-
7
- describe("FocusManager", () => {
8
- it("should focus on the first focusable element inside the popover", async () => {
9
- // Arrange
10
- const externalNodes = (
11
- <div>
12
- <button>Open popover</button>
13
- <button>Next focusable element outside</button>
14
- </div>
15
- );
16
- render(externalNodes);
17
-
18
- // get the anchor reference to be able pass it to the FocusManager
19
- const anchorElementNode = await screen.findByRole("button", {
20
- name: "Open popover",
21
- });
22
-
23
- render(
24
- <FocusManager anchorElement={anchorElementNode}>
25
- <div>
26
- <button>first focusable element inside</button>
27
- <button>second focusable element inside</button>
28
- <button>third focusable element inside</button>
29
- </div>
30
- </FocusManager>,
31
- );
32
-
33
- // Act
34
- // focus on the previous element before the popover (anchor element)
35
- anchorElementNode.focus();
36
- // focus gets automatically moved to be on the first focusable element
37
-
38
- const firstFocusableElementInside = await screen.findByText(
39
- "first focusable element inside",
40
- );
41
-
42
- // Assert
43
- expect(firstFocusableElementInside).toHaveFocus();
44
- });
45
-
46
- it("should focus on the last focusable element inside the popover", async () => {
47
- // Arrange
48
- const externalNodes = (
49
- <div>
50
- <button>Open popover</button>
51
- <button>Next focusable element outside</button>
52
- </div>
53
- );
54
- render(externalNodes);
55
-
56
- // get the anchor reference to be able pass it to the FocusManager
57
- const anchorElementNode = await screen.findByRole("button", {
58
- name: "Open popover",
59
- });
60
-
61
- render(
62
- <FocusManager anchorElement={anchorElementNode}>
63
- <div>
64
- <button>first focusable element inside</button>
65
- <button>second focusable element inside</button>
66
- <button>third focusable element inside</button>
67
- </div>
68
- </FocusManager>,
69
- );
70
-
71
- // Act
72
-
73
- // find previous focusable element outside the popover
74
- const nextFocusableElementOutside = await screen.findByRole("button", {
75
- name: "Next focusable element outside",
76
- });
77
-
78
- // focus on the next element after the popover
79
- nextFocusableElementOutside.focus();
80
- await userEvent.tab({shift: true});
81
-
82
- const lastFocusableElementInside = await screen.findByText(
83
- "third focusable element inside",
84
- );
85
-
86
- // Assert
87
- expect(lastFocusableElementInside).toHaveFocus();
88
- });
89
-
90
- it("should allow flowing the focus correctly", async () => {
91
- // Arrange
92
- const externalNodes = (
93
- <div>
94
- <button>Prev focusable element outside</button>
95
- <button>Open popover</button>
96
- <button>Next focusable element outside</button>
97
- </div>
98
- );
99
- render(externalNodes);
100
-
101
- // get the anchor reference to be able pass it to the FocusManager
102
- const anchorElementNode = await screen.findByRole("button", {
103
- name: "Open popover",
104
- });
105
-
106
- render(
107
- <FocusManager anchorElement={anchorElementNode}>
108
- <div>
109
- <button>first focusable element inside</button>
110
- </div>
111
- </FocusManager>,
112
- );
113
-
114
- // Act
115
- // 1. focus on the Open popover button, this opens the focus manager
116
- // and focuses the button inside the popover
117
- await userEvent.tab();
118
-
119
- // 2. we advance to the next focusable element outside the popover
120
- await userEvent.tab();
121
-
122
- // 3. we loop back around to the first focusable element outside the popover
123
- await userEvent.tab();
124
-
125
- // find previous focusable element outside the popover
126
- const prevFocusableElementOutside = await screen.findByRole("button", {
127
- name: "Prev focusable element outside",
128
- });
129
-
130
- // Assert
131
- expect(prevFocusableElementOutside).toHaveFocus();
132
- });
133
-
134
- it("should disallow focusability on internal elements if the user focus out of the focus manager", async () => {
135
- // Arrange
136
- const externalNodes = (
137
- <div>
138
- <button>Prev focusable element outside</button>
139
- <button>Open popover</button>
140
- <button>Next focusable element outside</button>
141
- </div>
142
- );
143
- render(externalNodes);
144
-
145
- // get the anchor reference to be able pass it to the FocusManager
146
- const anchorElementNode = await screen.findByRole("button", {
147
- name: "Open popover",
148
- });
149
-
150
- render(
151
- <FocusManager anchorElement={anchorElementNode}>
152
- <div>
153
- <button>first focusable element inside</button>
154
- </div>
155
- </FocusManager>,
156
- );
157
-
158
- // Act
159
- // 1. focus on the previous element before the popover
160
- await userEvent.tab();
161
-
162
- // 2. focus on the anchor element
163
- await userEvent.tab();
164
-
165
- // 3. focus on focusable element inside the popover
166
- await userEvent.tab();
167
-
168
- // 4. focus on the next focusable element outside the popover (this will
169
- // be the first focusable element outside the popover)
170
- await userEvent.tab();
171
-
172
- // The elements inside the focus manager should not be focusable anymore.
173
- const focusableElementInside = await screen.findByRole("button", {
174
- name: "first focusable element inside",
175
- });
176
-
177
- // Assert
178
- expect(focusableElementInside).toHaveAttribute("tabIndex", "-1");
179
- });
180
- });
@@ -1,73 +0,0 @@
1
- import * as React from "react";
2
- import {render, screen} from "@testing-library/react";
3
-
4
- import InitialFocus from "../initial-focus";
5
-
6
- describe("InitialFocus", () => {
7
- beforeEach(() => {
8
- jest.useFakeTimers();
9
- });
10
-
11
- it("should try to focus on a given element by id", () => {
12
- // Arrange
13
- render(
14
- <InitialFocus initialFocusId="initial-focus-id">
15
- <div data-testid="container">
16
- <button data-testid="item-0" />
17
- <button data-testid="item-1" id="initial-focus-id" />
18
- <button data-testid="item-2" />
19
- </div>
20
- </InitialFocus>,
21
- );
22
-
23
- // Act
24
- const firstFocusableElement = screen.getByTestId("item-1");
25
- // Fast-forward until all timers have been executed
26
- jest.runAllTimers();
27
-
28
- // Assert
29
- expect(firstFocusableElement).toHaveFocus();
30
- });
31
-
32
- it("should try to focus on the first focusable element", () => {
33
- // Arrange
34
- render(
35
- <InitialFocus>
36
- <div data-testid="container">
37
- <button data-testid="item-0" />
38
- <button data-testid="item-1" id="initial-focus-id" />
39
- <button data-testid="item-2" />
40
- </div>
41
- </InitialFocus>,
42
- );
43
-
44
- // Act
45
- const firstFocusableElement = screen.getByTestId("item-0");
46
-
47
- // Fast-forward until all timers have been executed
48
- jest.runAllTimers();
49
-
50
- // Assert
51
- expect(firstFocusableElement).toHaveFocus();
52
- });
53
-
54
- it("should try to focus on the container if no focusable elements are found", () => {
55
- // Arrange
56
- render(
57
- <InitialFocus>
58
- <div data-testid="container">
59
- <p>no focusable elements here</p>
60
- </div>
61
- </InitialFocus>,
62
- );
63
-
64
- // Act
65
- const firstFocusableElement = screen.getByTestId("container");
66
-
67
- // Fast-forward until all timers have been executed
68
- jest.runAllTimers();
69
-
70
- // Assert
71
- expect(firstFocusableElement).toHaveFocus();
72
- });
73
- });
@@ -1,61 +0,0 @@
1
- import * as React from "react";
2
- import {render, screen} from "@testing-library/react";
3
- import {userEvent} from "@testing-library/user-event";
4
-
5
- import PopoverAnchor from "../popover-anchor";
6
-
7
- describe("PopoverAnchor", () => {
8
- it("should set child node as ref", async () => {
9
- // Arrange
10
- const updateRef = jest.fn();
11
-
12
- render(
13
- <PopoverAnchor anchorRef={updateRef} onClick={jest.fn()}>
14
- <button>test</button>
15
- </PopoverAnchor>,
16
- );
17
-
18
- // Act
19
- const triggerElement = await screen.findByRole("button");
20
-
21
- // Assert
22
- expect(updateRef).toBeCalledWith(triggerElement);
23
- });
24
-
25
- it("should add onClick handler if child is a function", async () => {
26
- // Arrange
27
- const onClickMock = jest.fn();
28
-
29
- render(
30
- <PopoverAnchor anchorRef={jest.fn()} onClick={onClickMock}>
31
- {({open}: any) => <button onClick={open}>open</button>}
32
- </PopoverAnchor>,
33
- );
34
-
35
- // Act
36
- await userEvent.click(await screen.findByRole("button"));
37
-
38
- // Assert
39
- expect(onClickMock).toBeCalled();
40
- });
41
-
42
- it("should add onClick handler if child is a Node", async () => {
43
- // Arrange
44
- const onClickMock = jest.fn();
45
- const onClickInnerMock = jest.fn();
46
-
47
- render(
48
- <PopoverAnchor anchorRef={jest.fn()} onClick={onClickMock}>
49
- <button onClick={onClickInnerMock}>test</button>
50
- </PopoverAnchor>,
51
- );
52
-
53
- // Act
54
- await userEvent.click(await screen.findByRole("button"));
55
-
56
- // Assert
57
- // both custom and internal click should be called
58
- expect(onClickInnerMock).toBeCalled();
59
- expect(onClickMock).toBeCalled();
60
- });
61
- });
@@ -1,76 +0,0 @@
1
- import * as React from "react";
2
- import {render, screen} from "@testing-library/react";
3
- import {userEvent} from "@testing-library/user-event";
4
-
5
- import PopoverContent from "../popover-content";
6
- import PopoverContext from "../popover-context";
7
-
8
- describe("PopoverContent", () => {
9
- it("should close the popover from the actions", async () => {
10
- // Arrange
11
- const onCloseMock = jest.fn();
12
-
13
- render(
14
- <PopoverContext.Provider
15
- value={{close: onCloseMock, placement: "left"}}
16
- >
17
- <PopoverContent
18
- title="Title"
19
- content="content"
20
- actions={({close}: any) => (
21
- <button onClick={close}>close popover</button>
22
- )}
23
- />
24
- </PopoverContext.Provider>,
25
- );
26
-
27
- // Act
28
- await userEvent.click(await screen.findByRole("button"));
29
-
30
- // Assert
31
- expect(onCloseMock).toBeCalled();
32
- });
33
-
34
- it("should warn when setting a image and icon at the same time", async () => {
35
- // Arrange
36
- const nodes = (
37
- <PopoverContent
38
- title="illustration"
39
- content="content"
40
- image={<img src="/dummy-image.png" alt="popover image" />}
41
- icon={<img src="/dummy-icon.png" alt="popover icon" />}
42
- />
43
- );
44
-
45
- // Act
46
- const underTest = () => render(nodes);
47
-
48
- // Assert
49
- expect(underTest).toThrowErrorMatchingInlineSnapshot(
50
- `"'image' and 'icon' cannot be used at the same time. You can fix this by either removing 'image' or 'icon' from your instance."`,
51
- );
52
- });
53
-
54
- it("should warn when setting a horizontal placement with an Illustration popover", async () => {
55
- // Arrange
56
- const nodes = (
57
- <PopoverContext.Provider
58
- value={{close: () => {}, placement: "left"}}
59
- >
60
- <PopoverContent
61
- title="illustration"
62
- content="content"
63
- image={<img src="/dummy-image.png" alt="dummy" />}
64
- />
65
- </PopoverContext.Provider>
66
- );
67
-
68
- // Act
69
- const underTest = () => render(nodes);
70
-
71
- // Assert
72
- expect(underTest).toThrowErrorMatchingInlineSnapshot(
73
- `"'image' can only be vertically placed. You can fix this by either changing \`placement\` to \`top\` or \`bottom\` or removing the \`image\` prop inside \`content\`."`,
74
- );
75
- });
76
- });
@@ -1,38 +0,0 @@
1
- import * as React from "react";
2
-
3
- import PopoverContent from "../popover-content";
4
-
5
- <PopoverContent title="Title" content="Content" />;
6
-
7
- <PopoverContent title="Title" content="Content" icon="close" />;
8
-
9
- <PopoverContent
10
- title="Title"
11
- content="Content"
12
- image={<img src="domokun.jpg" alt="domokun" />}
13
- />;
14
-
15
- <PopoverContent
16
- title="Title"
17
- content="Content"
18
- icon="close"
19
- image={<img src="domokun.jpg" alt="domokun" />}
20
- />;
21
-
22
- <PopoverContent title="Title" content="Content" emphasized={true} />;
23
-
24
- // @ts-expect-error `emphasized` cannot be used with `icon`
25
- <PopoverContent
26
- title="Title"
27
- content="Content"
28
- icon="close"
29
- emphasized={true}
30
- />;
31
-
32
- // @ts-expect-error `emphasized` cannot be used with `img`
33
- <PopoverContent
34
- title="Title"
35
- content="Content"
36
- image={<img src="domokun.jpg" alt="domokun" />}
37
- emphasized={true}
38
- />;
@@ -1,98 +0,0 @@
1
- import * as React from "react";
2
- import {render} from "@testing-library/react";
3
- import * as Tooltip from "@khanacademy/wonder-blocks-tooltip";
4
-
5
- import type {Placement} from "@khanacademy/wonder-blocks-tooltip";
6
- import PopoverDialog from "../popover-dialog";
7
- import PopoverContentCore from "../popover-content-core";
8
-
9
- jest.mock("@khanacademy/wonder-blocks-tooltip");
10
-
11
- describe("PopoverDialog", () => {
12
- it("should update the tail color to match the content's color", () => {
13
- // Arrange
14
- const tooltipTailSpy = jest.spyOn(Tooltip, "TooltipTail");
15
-
16
- // Act
17
- render(
18
- <PopoverDialog showTail={true} placement="top" onUpdate={jest.fn()}>
19
- <PopoverContentCore color="darkBlue">
20
- popover content
21
- </PopoverContentCore>
22
- </PopoverDialog>,
23
- );
24
-
25
- // Assert
26
- expect(tooltipTailSpy).toHaveBeenCalledWith(
27
- expect.objectContaining({color: "darkBlue"}),
28
- {},
29
- );
30
- });
31
-
32
- it("should call onUpdate if placement is changed", () => {
33
- // Arrange
34
- const onUpdateMock = jest.fn();
35
- const UnderTest = ({placement}: {placement: Placement}) => (
36
- <PopoverDialog
37
- showTail={true}
38
- placement={placement}
39
- onUpdate={onUpdateMock}
40
- >
41
- <PopoverContentCore>popover content</PopoverContentCore>
42
- </PopoverDialog>
43
- );
44
-
45
- const {rerender} = render(<UnderTest placement="top" />);
46
-
47
- // Act
48
- rerender(<UnderTest placement="bottom" />);
49
-
50
- // Assert
51
- expect(onUpdateMock).toBeCalledWith("bottom");
52
- });
53
-
54
- it("should not call onUpdate if placement remains the same", () => {
55
- // Arrange
56
- const onUpdateMock = jest.fn();
57
-
58
- const UnderTest = ({placement}: {placement: Placement}) => (
59
- <PopoverDialog
60
- showTail={true}
61
- placement={placement}
62
- onUpdate={onUpdateMock}
63
- >
64
- <PopoverContentCore>popover content</PopoverContentCore>
65
- </PopoverDialog>
66
- );
67
-
68
- const {rerender} = render(<UnderTest placement="top" />);
69
-
70
- // Act
71
- rerender(<UnderTest placement="top" />);
72
-
73
- // Assert
74
- expect(onUpdateMock).not.toBeCalled();
75
- });
76
-
77
- it("should not render a tail if showTail is false", () => {
78
- // Arrange
79
- const tooltipTailSpy = jest.spyOn(Tooltip, "TooltipTail");
80
-
81
- // Act
82
- render(
83
- <PopoverDialog
84
- showTail={false}
85
- placement="top"
86
- onUpdate={jest.fn()}
87
- >
88
- <PopoverContentCore>popover content</PopoverContentCore>
89
- </PopoverDialog>,
90
- );
91
-
92
- // Assert
93
- expect(tooltipTailSpy).toHaveBeenCalledWith(
94
- expect.objectContaining({show: false}),
95
- {},
96
- );
97
- });
98
- });