Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
c113112
chore(post-popovercontainer): remove animate-pop-in class references
hugomslv May 30, 2025
0c9b513
chore(post-popovercontainer): remove animate-pop-in class references
hugomslv Jun 4, 2025
cb0e11f
Merge branch 'main' into 5463-refactor-post-popovercontainer-animatio…
hugomslv Jun 11, 2025
89fc041
Merge branch 'main' into 5463-refactor-post-popovercontainer-animatio…
hugomslv Jun 13, 2025
dd84738
Merge branch 'main' into 5463-refactor-post-popovercontainer-animatio…
hugomslv Jul 7, 2025
58ea300
fix PR comments
hugomslv Jul 7, 2025
4ee5b71
Merge branch '5463-refactor-post-popovercontainer-animation-to-use-we…
hugomslv Jul 7, 2025
d881d5e
Update post-popovercontainer.tsx
hugomslv Jul 7, 2025
764dbf6
Merge branch 'main' into 5463-refactor-post-popovercontainer-animatio…
hugomslv Aug 8, 2025
b0d1335
assign `role=menuitem` to focusable children
myrta2302 Aug 15, 2025
2839a4f
move the role="menu" back to host
myrta2302 Aug 15, 2025
b2209a2
trials
myrta2302 Aug 17, 2025
d40e5a7
Merge branch 'main' into 5463-refactor-post-popovercontainer-animatio…
hugomslv Aug 18, 2025
69db18c
remove old pop-in animation
hugomslv Aug 18, 2025
8cf3b89
Merge branch 'main' into 5463-refactor-post-popovercontainer-animatio…
hugomslv Aug 18, 2025
75f13b9
add roles on popovercontainer show
myrta2302 Aug 19, 2025
1ac1ba9
remove test component
myrta2302 Aug 19, 2025
ce4bd25
revert files
myrta2302 Aug 19, 2025
cd0ebfb
lint issue
myrta2302 Aug 19, 2025
6e62b74
Merge branch 'main' into 5837-bug-accessibility-issues-with-post-menu…
myrta2302 Aug 19, 2025
3d2f5d7
add changeset
myrta2302 Aug 19, 2025
0ae6785
update roles setup on language switcher and options to imrpove acce…
myrta2302 Aug 19, 2025
a079fba
Merge branch '5837-bug-accessibility-issues-with-post-menu-trigger-an…
myrta2302 Aug 19, 2025
eace006
improve breadcrumb accessibility
myrta2302 Aug 19, 2025
9926d50
Update whole-crews-dream.md
myrta2302 Aug 19, 2025
8489cb9
Update whole-crews-dream.md
myrta2302 Aug 19, 2025
8319dec
replace the language-option test with a language-switch test
myrta2302 Aug 19, 2025
55d3c32
Update whole-crews-dream.md
myrta2302 Aug 19, 2025
77e7d72
Merge branch 'main' into 5837-bug-accessibility-issues-with-post-menu…
myrta2302 Aug 20, 2025
2c6605c
language switch remove obsolete roles and aria-label
myrta2302 Aug 21, 2025
0e8db36
corrected accessibility error
myrta2302 Aug 21, 2025
2348d20
language-switch test update
myrta2302 Aug 21, 2025
82ab83b
Merge branch 'main' into 5837-bug-accessibility-issues-with-post-menu…
myrta2302 Aug 21, 2025
b8bd682
udpate language switch test
myrta2302 Aug 21, 2025
7af9341
Merge branch '5837-bug-accessibility-issues-with-post-menu-trigger-an…
myrta2302 Aug 21, 2025
e3be8d9
Merge branch 'main' into 5837-bug-accessibility-issues-with-post-menu…
myrta2302 Aug 25, 2025
58b680b
fix e2e test
myrta2302 Aug 25, 2025
62b7649
Merge branch 'main' into 5837-bug-accessibility-issues-with-post-menu…
myrta2302 Aug 25, 2025
fd02a6d
Merge branch 'main' into 5837-bug-accessibility-issues-with-post-menu…
myrta2302 Aug 25, 2025
bfed3d7
Merge branch 'main' into 5463-refactor-post-popovercontainer-animatio…
hugomslv Aug 26, 2025
1f6bf00
Update packages/components/src/animations/pop-in.ts
hugomslv Aug 26, 2025
0fea9db
Update packages/components/src/animations/pop-in.ts
hugomslv Aug 26, 2025
5682c4e
Merge branch 'main' into 5837-bug-accessibility-issues-with-post-menu…
myrta2302 Aug 26, 2025
abc902e
add caption prop to menu
myrta2302 Aug 28, 2025
eb8db3e
removed aria-label again as obsolete
myrta2302 Aug 28, 2025
61db02f
review comments update
myrta2302 Aug 28, 2025
2e0189c
Merge branch 'main' into 5837-bug-accessibility-issues-with-post-menu…
myrta2302 Aug 29, 2025
36376d6
Merge branch '5463-refactor-post-popovercontainer-animation-to-use-we…
hugomslv Sep 2, 2025
5edd2e7
Merge branch 'main' into 5463-refactor-post-popovercontainer-animatio…
hugomslv Sep 16, 2025
6c340c1
Merge branch 'main' into 5837-bug-accessibility-issues-with-post-menu…
myrta2302 Sep 26, 2025
bd604c2
add label prop for post-menu
myrta2302 Sep 26, 2025
f88d679
update first open check
myrta2302 Sep 26, 2025
756185e
update postToggle event payload
myrta2302 Sep 29, 2025
5c19472
Merge branch 'main' into 5837-bug-accessibility-issues-with-post-menu…
myrta2302 Sep 29, 2025
8b419ca
update popovercontainer events
myrta2302 Sep 30, 2025
4358936
Merge remote-tracking branch 'origin/5463-refactor-post-popovercontai…
myrta2302 Sep 30, 2025
dacf4a7
update events
myrta2302 Oct 1, 2025
40f76b9
restore label and aria set
myrta2302 Oct 1, 2025
a62c847
updates first
myrta2302 Oct 1, 2025
1405f7c
remove console.logs
myrta2302 Oct 1, 2025
4af08a5
remove obsolete role
myrta2302 Oct 2, 2025
058f6bf
add changeset vast-onions-return.md
myrta2302 Oct 2, 2025
18eed87
update <post-popovercontainer> event names
myrta2302 Oct 6, 2025
b35cd7b
Merge branch 'main' into 6341-update-post-menu-and-post-popovecontain…
myrta2302 Oct 6, 2025
ea7a6df
remove postHide payload
myrta2302 Oct 6, 2025
697390d
update components.d.ts
myrta2302 Oct 6, 2025
fe2f062
minor
myrta2302 Oct 6, 2025
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
5 changes: 5 additions & 0 deletions .changeset/vast-onions-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@swisspost/design-system-components': patch
---

Updated `<post-popovercontainer>` to emit events: postBeforeShow, postShow, postHide, postBeforeToggle, and postToggle. Updated `<post-menu>` and `<post-tooltip-trigger>` to consume the `onPostToggle` event emitted by `<post-popovercontainer>`.
20 changes: 20 additions & 0 deletions packages/components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,10 @@ declare global {
new (): HTMLPostPopoverElement;
};
interface HTMLPostPopovercontainerElementEventMap {
"postBeforeShow": { first?: boolean };
"postShow": { first?: boolean };
"postHide": any;
"postBeforeToggle": { willOpen: boolean; first?: boolean };
"postToggle": { isOpen: boolean; first?: boolean };
}
interface HTMLPostPopovercontainerElement extends Components.PostPopovercontainer, HTMLStencilElement {
Expand Down Expand Up @@ -1259,6 +1263,22 @@ declare namespace LocalJSX {
* @default false
*/
"manualClose"?: boolean;
/**
* Fires whenever the popovercontainer is about to be shown, passing in event.detail a `first` boolean, which is true if it is to be shown for the first time.
*/
"onPostBeforeShow"?: (event: PostPopovercontainerCustomEvent<{ first?: boolean }>) => void;
/**
* Fires whenever the popovercontainer is about to be shown or hidden, passing in event.detail an object containing two booleans: `willOpen`, which is true if the popovercontainer is about to be opened and false if it is about to be closed, and `first`, which is true if it is to be opened for the first time.
*/
"onPostBeforeToggle"?: (event: PostPopovercontainerCustomEvent<{ willOpen: boolean; first?: boolean }>) => void;
/**
* Fires whenever the popovercontainer is hidden
*/
"onPostHide"?: (event: PostPopovercontainerCustomEvent<any>) => void;
/**
* Fires whenever the popovercontainer is shown, passing in event.detail a `first` boolean, which is true if it is shown for the first time.
*/
"onPostShow"?: (event: PostPopovercontainerCustomEvent<{ first?: boolean }>) => void;
/**
* Fires whenever the popovercontainer gets shown or hidden, passing in event.detail an object containing two booleans: `isOpen`, which is true if the popovercontainer was opened and false if it was closed, and `first`, which is true if it was opened for the first time.
*/
Expand Down
77 changes: 35 additions & 42 deletions packages/components/src/components/post-menu/post-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { Placement } from '@floating-ui/dom';
import { PLACEMENT_TYPES } from '@/types';
import { version } from '@root/package.json';
import { getFocusableChildren } from '@/utils/get-focusable-children';
import { getRoot, checkEmptyOrOneOf, EventFrom } from '@/utils';
import { getRoot, checkEmptyOrOneOf } from '@/utils';

/**
* @part menu - The container element that holds the list of menu items.
Expand Down Expand Up @@ -83,14 +83,10 @@ export class PostMenu {
disconnectedCallback() {
this.host.removeEventListener('keydown', this.handleKeyDown);
this.host.removeEventListener('click', this.handleClick);
this.popoverRef?.removeEventListener('postToggle', this.handlePostToggle);
}

componentDidLoad() {
this.validatePlacement();
if (this.popoverRef) {
this.popoverRef.addEventListener('postToggle', this.handlePostToggle);
}
}

/**
Expand Down Expand Up @@ -131,7 +127,7 @@ export class PostMenu {
}
}

private handleKeyDown = (e: KeyboardEvent) => {
private readonly handleKeyDown = (e: KeyboardEvent) => {
e.stopPropagation();

if (e.key === this.KEYCODES.ESCAPE) {
Expand All @@ -144,43 +140,36 @@ export class PostMenu {
}
};

@EventFrom('post-popovercontainer')
private handlePostToggle = (event: CustomEvent<{ isOpen: boolean; first?: boolean }>) => {
this.isVisible = event.detail.isOpen;
this.toggleMenu.emit(this.isVisible);

requestAnimationFrame(() => {
if (this.isVisible) {
this.lastFocusedElement = this.root?.activeElement as HTMLElement;

const menuItems = this.getSlottedItems();

if (event.detail.first) {
if (menuItems.length > 0) {
// Add role="menu" to the popovercontainer
this.host.setAttribute('role', 'menu');

// Add role="menuitem" to the focusable elements
menuItems.forEach(item => {
item.setAttribute('role', 'menuitem');
});

// Add aria-label to the menu
if (this.label) this.host.setAttribute('aria-label', this.label);
}
}

(menuItems[0] as HTMLElement).focus();
} else if (this.lastFocusedElement) {
setTimeout(() => {
// This timeout is added for NVDA to announce the menu as collapsed
this.lastFocusedElement.focus();
}, 0);
private readonly handlePostToggled = (
event: CustomEvent<{ isOpen: boolean; first?: boolean }>,
) => {
this.isVisible = event.detail.isOpen;
this.toggleMenu.emit(this.isVisible);

if (this.isVisible) {
this.lastFocusedElement = this.root?.activeElement as HTMLElement;

const menuItems = this.getSlottedItems();
if (menuItems.length > 0) {
(menuItems[0] as HTMLElement).focus();

// Only for the first open
if (event.detail.first) {
// Add "menu" and "menuitem" aria roles and aria-label
this.host.setAttribute('role', 'menu');
menuItems.forEach(item => {
item.setAttribute('role', 'menuitem');
});

if (this.label) this.host.setAttribute('aria-label', this.label);
}
});
};
}
} else if (this.lastFocusedElement) {
this.lastFocusedElement.focus();
}
};

private handleClick = (e: MouseEvent) => {
private readonly handleClick = (e: MouseEvent) => {
const target = e.target as HTMLElement;
if (['BUTTON', 'A', 'INPUT', 'SELECT', 'TEXTAREA'].includes(target.tagName)) {
this.toggle(this.host);
Expand Down Expand Up @@ -245,7 +234,11 @@ export class PostMenu {
render() {
return (
<Host data-version={version}>
<post-popovercontainer placement={this.placement} ref={e => (this.popoverRef = e)}>
<post-popovercontainer
onPostToggle={this.handlePostToggled}
placement={this.placement}
ref={e => (this.popoverRef = e)}
>
<div part="menu">
<slot></slot>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,27 @@ export class PostPopovercontainer {
private eventTarget: Element;
private clearAutoUpdate: () => void;
private toggleTimeoutId: number;
private firstOpen: boolean = true;
private first: boolean = true;

/**
* Fires whenever the popovercontainer is about to be shown, passing in event.detail a `first` boolean, which is true if it is to be shown for the first time.
*/
@Event() postBeforeShow: EventEmitter<{ first?: boolean }>;

/**
* Fires whenever the popovercontainer is shown, passing in event.detail a `first` boolean, which is true if it is shown for the first time.
*/
@Event() postShow: EventEmitter<{ first?: boolean }>;

/**
* Fires whenever the popovercontainer is hidden.
*/
@Event() postHide: EventEmitter;

/**
* Fires whenever the popovercontainer is about to be shown or hidden, passing in event.detail an object containing two booleans: `willOpen`, which is true if the popovercontainer is about to be opened and false if it is about to be closed, and `first`, which is true if it is to be opened for the first time.
*/
@Event() postBeforeToggle: EventEmitter<{ willOpen: boolean; first?: boolean }>;

/**
* Fires whenever the popovercontainer gets shown or hidden, passing in event.detail an object containing two booleans: `isOpen`, which is true if the popovercontainer was opened and false if it was closed, and `first`, which is true if it was opened for the first time.
Expand Down Expand Up @@ -161,7 +181,6 @@ export class PostPopovercontainer {
@Method()
async show(target: HTMLElement) {
if (this.toggleTimeoutId) return;

this.eventTarget = target;
this.calculatePosition();
this.host.showPopover();
Expand All @@ -175,6 +194,7 @@ export class PostPopovercontainer {
if (!this.toggleTimeoutId) {
this.eventTarget = null;
this.host.hidePopover();
this.postHide.emit();
}
}

Expand Down Expand Up @@ -206,27 +226,48 @@ export class PostPopovercontainer {

const isOpen = e.newState === 'open';
if (isOpen) {
const content = this.host.querySelector('.popover-content');
this.startAutoupdates();
if (content && this.animation === 'pop-in') {
popIn(content);
}
this.handleOpen();
} else {
this.handleClose();
}
}

private handleOpen() {
const content = this.host.querySelector('.popover-content');
this.startAutoupdates();

if (this.safeSpace)
window.addEventListener('mousemove', this.mouseTrackingHandler.bind(this));
if (content) {
const animation = popIn(content);

// Emit event with `first` flag only true on the first open
if (this.firstOpen) {
this.postToggle.emit({ isOpen, first: this.firstOpen });
this.firstOpen = false;
return;
if (animation?.playState === 'running') {
this.postBeforeToggle.emit({ willOpen: true, first: this.first });
this.postBeforeShow.emit({ first: this.first });
}
} else {
if (typeof this.clearAutoUpdate === 'function') this.clearAutoUpdate();
if (this.safeSpace)
window.removeEventListener('mousemove', this.mouseTrackingHandler.bind(this));

animation?.finished.then(() => {
this.postToggle.emit({ isOpen: true, first: this.first });
this.postShow.emit({ first: this.first });

if (this.first) this.first = false;
});
}

if (this.safeSpace) {
window.addEventListener('mousemove', this.mouseTrackingHandler.bind(this));
}
this.postToggle.emit({ isOpen: isOpen, first: false });
}

private handleClose() {
if (typeof this.clearAutoUpdate === 'function') {
this.clearAutoUpdate();
}

if (this.safeSpace) {
window.removeEventListener('mousemove', this.mouseTrackingHandler.bind(this));
}

this.postToggle.emit({ isOpen: false, first: this.first });
this.postHide.emit({ first: this.first });
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@

## Events

| Event | Description | Type |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `postToggle` | Fires whenever the popovercontainer gets shown or hidden, passing in event.detail an object containing two booleans: `isOpen`, which is true if the popovercontainer was opened and false if it was closed, and `first`, which is true if it was opened for the first time. | `CustomEvent<{ isOpen: boolean; first?: boolean; }>` |
| Event | Description | Type |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ |
| `postBeforeShow` | Fires whenever the popovercontainer is about to be shown, passing in event.detail a `first` boolean, which is true if it is to be shown for the first time. | `CustomEvent<{ first?: boolean; }>` |
| `postBeforeToggle` | Fires whenever the popovercontainer is about to be shown or hidden, passing in event.detail an object containing two booleans: `willOpen`, which is true if the popovercontainer is about to be opened and false if it is about to be closed, and `first`, which is true if it is to be opened for the first time. | `CustomEvent<{ willOpen: boolean; first?: boolean; }>` |
| `postHide` | Fires whenever the popovercontainer is hidden | `CustomEvent<any>` |
| `postShow` | Fires whenever the popovercontainer is shown, passing in event.detail a `first` boolean, which is true if it is shown for the first time. | `CustomEvent<{ first?: boolean; }>` |
| `postToggle` | Fires whenever the popovercontainer gets shown or hidden, passing in event.detail an object containing two booleans: `isOpen`, which is true if the popovercontainer was opened and false if it was closed, and `first`, which is true if it was opened for the first time. | `CustomEvent<{ isOpen: boolean; first?: boolean; }>` |


## Methods
Expand Down
Loading