Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dialog, modal, popover, input-date-picker, input-time-picker, sheet): support stacked component sequential closing with escape #9231

Merged
merged 78 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
b9b684d
set up demo for testing component sequential closing behavior
Elijbet Apr 30, 2024
281619e
WIP
driskull Apr 30, 2024
2ad2c8e
WIP
Elijbet May 1, 2024
c95e3aa
WIP
Elijbet May 1, 2024
c54cab1
WIP
Elijbet May 1, 2024
e3823ea
add docs
Elijbet May 1, 2024
1187a3a
simplified HTML for Demo and e2e test to reflect the demo workflow
Elijbet May 1, 2024
fadbb0d
cleanup
Elijbet May 2, 2024
0622da1
stacked focus-trap components test
Elijbet May 2, 2024
00e576b
relocate test for closing of stacked focus-trap components to globalS…
Elijbet May 8, 2024
55fbb07
fix failing tests
Elijbet May 8, 2024
0af108e
add wait for events open/close to input-date-picker for additional check
Elijbet Jun 20, 2024
47cf21d
add wait for events open/close to input-time-picker for additional check
Elijbet Jun 20, 2024
6a1ad45
merge dev
Elijbet Jul 30, 2024
8ca1dc5
docs
Elijbet Jul 30, 2024
d2324b2
merge dev
Elijbet Jul 30, 2024
5861f82
fix timing issues with modal and sheet
Elijbet Aug 2, 2024
95af390
remove focus-trapping logic and let popover handle it
Elijbet Aug 2, 2024
69dbd5a
WIP
Elijbet Aug 2, 2024
ba39a7f
WIP
Elijbet Aug 2, 2024
3e4b38e
enable focus trap in input-time-picker and fix failing tests
Elijbet Aug 4, 2024
85f4ef2
Helper to manage focusTrap by determining if a click event occurred o…
Elijbet Aug 8, 2024
7418a25
WIP
Elijbet Aug 8, 2024
a9dd5c0
WIP
Elijbet Aug 8, 2024
58622a4
WIP test
Elijbet Aug 9, 2024
0376e14
add focus trap to dialog
Elijbet Aug 9, 2024
f11fb0f
WIP
Elijbet Aug 9, 2024
31764b2
WIP
Elijbet Aug 9, 2024
a7cebb6
WIP should not toggle popovers with triggerDisabled
Elijbet Aug 9, 2024
c993e02
merge dev
jcfranco Aug 19, 2024
327aa38
WIP popover
Elijbet Aug 20, 2024
785fed5
WIP popover
Elijbet Aug 20, 2024
84f881e
WIP popover
Elijbet Aug 21, 2024
84c20d8
WIP input-date-picker
Elijbet Aug 21, 2024
9db785e
merge dev
Elijbet Aug 21, 2024
06570cd
merge dev
Elijbet Aug 21, 2024
c3e5da4
test cleanup
Elijbet Aug 21, 2024
ef5ad2a
cleanup
Elijbet Aug 21, 2024
16564f1
merge dev
jcfranco Aug 22, 2024
f7dd170
WIP
Elijbet Aug 23, 2024
a493275
WIP
Elijbet Aug 23, 2024
6d3ed51
WIP
Elijbet Aug 23, 2024
d8d0412
WIP
Elijbet Aug 23, 2024
9f18ccc
WIP
Elijbet Aug 24, 2024
1e67bf8
Utilities for implementing FocusTrapComponent are no longer necessary…
Elijbet Aug 25, 2024
3e94a4d
WIP
Elijbet Aug 25, 2024
16c7f1a
WIP
Elijbet Aug 25, 2024
5d5b90b
WIP
Elijbet Aug 26, 2024
20cde10
make clickOutsideDeactivates depend on outsideCloseDisabled value
Elijbet Sep 1, 2024
249081a
merge dev
Elijbet Sep 4, 2024
829d93b
remove clickOutsideDeactivates option from focus-trap since scrim han…
Elijbet Sep 6, 2024
d8acece
merge dev
Elijbet Sep 24, 2024
76a4cf7
cleanup
Elijbet Sep 24, 2024
766ec1e
merge dev
Elijbet Sep 26, 2024
f720d10
change logic to handle ignoring an Escape key press in focus-trap
Elijbet Sep 26, 2024
6bf5a5f
change logic to handle ignoring an Escape key press in focus-trap on …
Elijbet Sep 26, 2024
7f110b6
adjust test timing to accomodate changes
Elijbet Sep 26, 2024
0b582b8
adjust timing on modal tests
Elijbet Sep 26, 2024
7afdb64
remove closable from panel so it doesn't prevent shell from closing
Elijbet Sep 26, 2024
d8f34c5
this.effectiveReferenceElement
Elijbet Sep 26, 2024
87f4f56
cleanup
Elijbet Sep 27, 2024
b645dea
remove redundant connectFocusTrap(this);
Elijbet Sep 30, 2024
353ec39
sub waiting for timeout to waiting for event
Elijbet Oct 1, 2024
79ddd20
seperate stackedFocusTrap.e2e.ts
Elijbet Oct 1, 2024
337342b
cleanup stackedFocusTrap test
Elijbet Oct 1, 2024
212f6bc
clean up test
Elijbet Oct 1, 2024
0f8c4e2
assert on focus component
Elijbet Oct 2, 2024
fd53ff4
remove redundant components from modal, add dialog, account for focus…
Elijbet Oct 2, 2024
ee5d656
add camelCase util and target sheet-button id instead of sheet as it …
Elijbet Oct 3, 2024
7e3dda8
tab out of focus-retaining input components to assert on focus return…
Elijbet Oct 3, 2024
06e68ff
add comments to focusTrap options and cleanu up tests
Elijbet Oct 3, 2024
cfe719d
remove focusTrap component properties and pass options to connectFocu…
Elijbet Oct 3, 2024
b7f7411
cleanup
Elijbet Oct 3, 2024
b75baee
move anonymous focusTrap option functions to private methods on the c…
Elijbet Oct 3, 2024
fc85f74
remove stylistic changes and group helpers
Elijbet Oct 4, 2024
5097b0b
cleanup
Elijbet Oct 4, 2024
2c40889
clickOutsideDeactivates option on sheet and scrim comment, remove ext…
Elijbet Oct 4, 2024
5117688
explainer to sheet-button assertion
Elijbet Oct 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 48 additions & 48 deletions packages/calcite-components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,10 +393,6 @@ export namespace Components {
* When `true`, the component is expanded.
*/
"expanded": boolean;
/**
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
*/
"flipPlacements": FlipPlacement[];
/**
* Accessible name for the component.
*/
Expand All @@ -406,10 +402,18 @@ export namespace Components {
* @deprecated Use the `layout` property on the component's parent instead.
*/
"layout": Extract<"horizontal" | "vertical" | "grid", Layout>;
/**
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
*/
"menuFlipPlacements": FlipPlacement[];
/**
* When `true`, the `calcite-action-menu` is open.
*/
"menuOpen": boolean;
/**
* Determines where the action menu will be positioned.
*/
"menuPlacement": LogicalPlacement;
/**
* Use this property to override individual strings used by the component.
*/
Expand All @@ -422,10 +426,6 @@ export namespace Components {
* Determines the type of positioning to use for the overlaid content. Using `"absolute"` will work for most cases. The component will be positioned inside of overflowing parent containers and will affect the container's layout. `"fixed"` should be used to escape an overflowing parent container, or when the reference element's `position` CSS property is `"fixed"`.
*/
"overlayPositioning": OverlayPositioning;
/**
* Determines where the action menu will be positioned.
*/
"placement": LogicalPlacement;
/**
* Specifies the size of the `calcite-action-menu`.
*/
Expand Down Expand Up @@ -632,10 +632,6 @@ export namespace Components {
* When `true`, displays a drag handle in the header.
*/
"dragHandle": boolean;
/**
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
*/
"flipPlacements": FlipPlacement[];
/**
* The component header text.
*/
Expand All @@ -660,6 +656,14 @@ export namespace Components {
* When `true`, a busy indicator is displayed.
*/
"loading": boolean;
/**
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
*/
"menuFlipPlacements": FlipPlacement[];
/**
* Determines where the action menu will be positioned.
*/
"menuPlacement": LogicalPlacement;
/**
* Use this property to override individual strings used by the component.
*/
Expand All @@ -676,10 +680,6 @@ export namespace Components {
* Determines the type of positioning to use for the overlaid content. Using `"absolute"` will work for most cases. The component will be positioned inside of overflowing parent containers and will affect the container's layout. `"fixed"` should be used to escape an overflowing parent container, or when the reference element's `position` CSS property is `"fixed"`.
*/
"overlayPositioning": OverlayPositioning;
/**
* Determines where the action menu will be positioned.
*/
"placement": LogicalPlacement;
/**
* Sets focus on the component's first tabbable element.
*/
Expand Down Expand Up @@ -3880,10 +3880,6 @@ export namespace Components {
* When `true`, interaction is prevented and the component is displayed with lower opacity.
*/
"disabled": boolean;
/**
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
*/
"flipPlacements": FlipPlacement[];
/**
* The component header text.
*/
Expand All @@ -3896,10 +3892,18 @@ export namespace Components {
* When `true`, a busy indicator is displayed.
*/
"loading": boolean;
/**
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
*/
"menuFlipPlacements": FlipPlacement[];
/**
* When `true`, the action menu items in the `header-menu-actions` slot are open.
*/
"menuOpen": boolean;
/**
* Determines where the action menu will be positioned.
*/
"menuPlacement": LogicalPlacement;
/**
* Use this property to override individual strings used by the component.
*/
Expand All @@ -3912,10 +3916,6 @@ export namespace Components {
* Determines the type of positioning to use for the overlaid content. Using `"absolute"` will work for most cases. The component will be positioned inside of overflowing parent containers and will affect the container's layout. `"fixed"` should be used to escape an overflowing parent container, or when the reference element's `position` CSS property is `"fixed"`.
*/
"overlayPositioning": OverlayPositioning;
/**
* Determines where the action menu will be positioned.
*/
"placement": LogicalPlacement;
/**
* Specifies the size of the component.
*/
Expand Down Expand Up @@ -8349,10 +8349,6 @@ declare namespace LocalJSX {
* When `true`, the component is expanded.
*/
"expanded"?: boolean;
/**
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
*/
"flipPlacements"?: FlipPlacement[];
/**
* Accessible name for the component.
*/
Expand All @@ -8362,10 +8358,18 @@ declare namespace LocalJSX {
* @deprecated Use the `layout` property on the component's parent instead.
*/
"layout"?: Extract<"horizontal" | "vertical" | "grid", Layout>;
/**
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
*/
"menuFlipPlacements"?: FlipPlacement[];
/**
* When `true`, the `calcite-action-menu` is open.
*/
"menuOpen"?: boolean;
/**
* Determines where the action menu will be positioned.
*/
"menuPlacement"?: LogicalPlacement;
/**
* Use this property to override individual strings used by the component.
*/
Expand All @@ -8378,10 +8382,6 @@ declare namespace LocalJSX {
* Determines the type of positioning to use for the overlaid content. Using `"absolute"` will work for most cases. The component will be positioned inside of overflowing parent containers and will affect the container's layout. `"fixed"` should be used to escape an overflowing parent container, or when the reference element's `position` CSS property is `"fixed"`.
*/
"overlayPositioning"?: OverlayPositioning;
/**
* Determines where the action menu will be positioned.
*/
"placement"?: LogicalPlacement;
/**
* Specifies the size of the `calcite-action-menu`.
*/
Expand Down Expand Up @@ -8595,10 +8595,6 @@ declare namespace LocalJSX {
* When `true`, displays a drag handle in the header.
*/
"dragHandle"?: boolean;
/**
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
*/
"flipPlacements"?: FlipPlacement[];
/**
* The component header text.
*/
Expand All @@ -8623,6 +8619,14 @@ declare namespace LocalJSX {
* When `true`, a busy indicator is displayed.
*/
"loading"?: boolean;
/**
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
*/
"menuFlipPlacements"?: FlipPlacement[];
/**
* Determines where the action menu will be positioned.
*/
"menuPlacement"?: LogicalPlacement;
/**
* Use this property to override individual strings used by the component.
*/
Expand Down Expand Up @@ -8660,10 +8664,6 @@ declare namespace LocalJSX {
* Determines the type of positioning to use for the overlaid content. Using `"absolute"` will work for most cases. The component will be positioned inside of overflowing parent containers and will affect the container's layout. `"fixed"` should be used to escape an overflowing parent container, or when the reference element's `position` CSS property is `"fixed"`.
*/
"overlayPositioning"?: OverlayPositioning;
/**
* Determines where the action menu will be positioned.
*/
"placement"?: LogicalPlacement;
/**
* Displays a status-related indicator icon.
* @deprecated Use `icon-start` instead.
Expand Down Expand Up @@ -12032,10 +12032,6 @@ declare namespace LocalJSX {
* When `true`, interaction is prevented and the component is displayed with lower opacity.
*/
"disabled"?: boolean;
/**
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
*/
"flipPlacements"?: FlipPlacement[];
/**
* The component header text.
*/
Expand All @@ -12048,10 +12044,18 @@ declare namespace LocalJSX {
* When `true`, a busy indicator is displayed.
*/
"loading"?: boolean;
/**
* Specifies the component's fallback menu `placement` when it's initial or specified `placement` has insufficient space available.
*/
"menuFlipPlacements"?: FlipPlacement[];
/**
* When `true`, the action menu items in the `header-menu-actions` slot are open.
*/
"menuOpen"?: boolean;
/**
* Determines where the action menu will be positioned.
*/
"menuPlacement"?: LogicalPlacement;
/**
* Use this property to override individual strings used by the component.
*/
Expand All @@ -12076,10 +12080,6 @@ declare namespace LocalJSX {
* Determines the type of positioning to use for the overlaid content. Using `"absolute"` will work for most cases. The component will be positioned inside of overflowing parent containers and will affect the container's layout. `"fixed"` should be used to escape an overflowing parent container, or when the reference element's `position` CSS property is `"fixed"`.
*/
"overlayPositioning"?: OverlayPositioning;
/**
* Determines where the action menu will be positioned.
*/
"placement"?: LogicalPlacement;
/**
* Specifies the size of the component.
*/
Expand Down
14 changes: 9 additions & 5 deletions packages/calcite-components/src/components/dialog/dialog.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ describe("calcite-dialog", () => {
accessible(`<calcite-dialog heading="My Dialog" description="My Description" open>Hello world!</calcite-dialog>`);
});

const delayInMilliseconds = 300;

it("should set internal panel properties", async () => {
const page = await newE2EPage();
await page.exposeFunction("beforeClose", () => Promise.reject());
Expand Down Expand Up @@ -430,6 +432,7 @@ describe("calcite-dialog", () => {
await page.setContent(`
<calcite-dialog></calcite-dialog>
`);
await skipAnimations(page);

const dialog = await page.find("calcite-dialog");
await page.$eval(
Expand All @@ -444,7 +447,7 @@ describe("calcite-dialog", () => {

await page.keyboard.press("Escape");
await page.waitForChanges();
await page.waitForChanges();

expect(mockCallBack).toHaveBeenCalledTimes(2);
expect(await page.find(`calcite-dialog >>> .${CSS.containerOpen}`)).toBeNull();
});
Expand Down Expand Up @@ -697,6 +700,7 @@ describe("calcite-dialog", () => {

dialog.setProperty("open", true);
await page.waitForChanges();
await page.waitForTimeout(delayInMilliseconds);
Elijbet marked this conversation as resolved.
Show resolved Hide resolved
expect(await dialog.isVisible()).toBe(true);

await page.keyboard.press("Escape");
Expand All @@ -711,22 +715,20 @@ describe("calcite-dialog", () => {

it("closes when Escape key is pressed and dialog is open on page load", async () => {
const page = await newE2EPage();
await page.setContent(`<calcite-dialog open></calcite-dialog>`);
await page.setContent(`<calcite-dialog open></calcite-dialog>`);

const dialog = await page.find("calcite-dialog");
await page.waitForChanges();
expect(dialog).toHaveAttribute("open");
expect(dialog).toHaveAttribute("open");
await page.waitForTimeout(delayInMilliseconds);
Elijbet marked this conversation as resolved.
Show resolved Hide resolved

await page.keyboard.press("Escape");
await page.waitForChanges();
expect(dialog).not.toHaveAttribute("open");
expect(dialog).not.toHaveAttribute("open");

dialog.setProperty("open", true);
await page.waitForChanges();
expect(dialog).toHaveAttribute("open");
expect(dialog).toHaveAttribute("open");
});

it("closes and allows re-opening when Close button is clicked", async () => {
Expand All @@ -735,9 +737,11 @@ describe("calcite-dialog", () => {
await skipAnimations(page);
const dialog = await page.find("calcite-dialog");
const container = await page.find(`calcite-dialog >>> .${CSS.container}`);
await page.waitForChanges();

dialog.setProperty("open", true);
await page.waitForChanges();
await page.waitForTimeout(delayInMilliseconds);
Elijbet marked this conversation as resolved.
Show resolved Hide resolved
expect(await container.isVisible()).toBe(true);

const closeButton = await page.find(`calcite-dialog >>> calcite-panel >>> #${PanelIDS.close}`);
Expand Down
31 changes: 16 additions & 15 deletions packages/calcite-components/src/components/dialog/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
EventEmitter,
h,
Host,
Listen,
Method,
Prop,
State,
Expand Down Expand Up @@ -231,6 +230,18 @@ export class Dialog
this.mutationObserver?.observe(this.el, { childList: true, subtree: true });
connectLocalized(this);
connectMessages(this);
connectFocusTrap(this, {
focusTrapOptions: {
clickOutsideDeactivates: false,
Elijbet marked this conversation as resolved.
Show resolved Hide resolved
escapeDeactivates: (event) => {
if (event.defaultPrevented || this.escapeDisabled) {
return false;
}
event.preventDefault();
return true;
},
},
});
connectFocusTrap(this);
this.setupInteractions();
}
Expand Down Expand Up @@ -379,20 +390,6 @@ export class Dialog
this.handleMutationObserver(),
);

//--------------------------------------------------------------------------
//
// Event Listeners
//
//--------------------------------------------------------------------------

@Listen("keydown", { target: "window" })
handleEscape(event: KeyboardEvent): void {
if (this.open && !this.escapeDisabled && event.key === "Escape" && !event.defaultPrevented) {
this.open = false;
event.preventDefault();
}
}

//--------------------------------------------------------------------------
//
// Events
Expand Down Expand Up @@ -479,6 +476,10 @@ export class Dialog
deactivateFocusTrap(this);
}

onFocusTrapDeactivate(): void {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed components close on blur or when clicking outside or on another element (e.g., scrim). Can you confirm all component-closing onFocusTrapDeactivate calls are needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in dialog/modal we also have to consider the value of outsideCloseDisabled.

this.open = false;
}

@Watch("open")
toggleDialog(value: boolean): void {
if (this.ignoreOpenChange) {
Expand Down
Loading
Loading