-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(components): add a post-collapsible-trigger (#3209)
Co-authored-by: Philipp Gfeller <[email protected]>
- Loading branch information
1 parent
dd67dfe
commit b43887d
Showing
18 changed files
with
355 additions
and
109 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
'@swisspost/design-system-documentation': minor | ||
'@swisspost/design-system-components': minor | ||
'@swisspost/design-system-components-angular': minor | ||
'@swisspost/design-system-components-react': minor | ||
--- | ||
|
||
Added a `post-collapsible-trigger` component to properly handle the role, ARIA attributes, and event listeners for elements that toggle a `post-collapsible`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
packages/components/src/components/post-collapsible-trigger/post-collapsible-trigger.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import { Component, Element, Listen, Method, Prop, Watch } from '@stencil/core'; | ||
import { version } from 'typescript'; | ||
import { checkNonEmpty, checkType, debounce } from '@/utils'; | ||
import { PostCollapsibleCustomEvent } from '@/components'; | ||
|
||
@Component({ | ||
tag: 'post-collapsible-trigger', | ||
}) | ||
export class PostCollapsibleTrigger { | ||
private trigger?: HTMLButtonElement; | ||
private observer = new MutationObserver(() => this.setTrigger()); | ||
|
||
@Element() host: HTMLPostCollapsibleTriggerElement; | ||
|
||
/** | ||
* Link the trigger to a post-collapsible with this id | ||
*/ | ||
@Prop() for: string; | ||
|
||
/** | ||
* Set the "aria-controls" and "aria-expanded" attributes on the trigger to match the state of the controlled post-collapsible | ||
*/ | ||
@Watch('for') | ||
setAriaAttributes() { | ||
checkNonEmpty(this.for, 'The post-collapsible-trigger "for" prop is required.'); | ||
checkType(this.for, 'string', 'The post-collapsible-trigger "for" prop should be a id.'); | ||
|
||
void this.update(); | ||
} | ||
|
||
/** | ||
* Initiate a mutation observer that updates the trigger whenever necessary | ||
*/ | ||
connectedCallback() { | ||
this.observer.observe(this.host, { childList: true, subtree: true }); | ||
} | ||
|
||
/** | ||
* Add the "data-version" to the host element and set the trigger | ||
*/ | ||
componentDidLoad() { | ||
this.host.setAttribute('data-version', version); | ||
this.setTrigger(); | ||
|
||
if (!this.trigger) console.warn('The post-collapsible-trigger must contain a button.'); | ||
} | ||
|
||
/** | ||
* Disconnect the mutation observer | ||
*/ | ||
disconnectedCallback() { | ||
this.observer.disconnect(); | ||
} | ||
|
||
/** | ||
* Update the "aria-expanded" attribute on the trigger anytime the controlled post-collapsible is toggled | ||
*/ | ||
@Listen('postToggle', { target: 'document' }) | ||
setAriaExpanded(e: PostCollapsibleCustomEvent<boolean>) { | ||
if (!this.trigger || !e.target.isEqualNode(this.collapsible)) return; | ||
this.trigger.setAttribute('aria-expanded', `${e.detail}`); | ||
} | ||
|
||
/** | ||
* Update the "aria-controls" and "aria-expanded" attributes on the trigger button | ||
*/ | ||
@Method() | ||
async update() { | ||
this.debouncedUpdate(); | ||
} | ||
|
||
private debouncedUpdate = debounce(() => { | ||
if (!this.trigger) return; | ||
|
||
// add the provided id to the aria-controls list | ||
const ariaControls = this.trigger.getAttribute('aria-controls'); | ||
if (!ariaControls?.includes(this.for)) { | ||
const newAriaControls = ariaControls ? `${ariaControls} ${this.for}` : this.for; | ||
this.trigger.setAttribute('aria-controls', newAriaControls); | ||
} | ||
|
||
// set the aria-expanded to `false` if the controlled collapsible is collapsed or undefined, set it to `true` otherwise | ||
const isCollapsed = this.collapsible?.collapsed; | ||
const newAriaExpanded = isCollapsed !== undefined ? !isCollapsed : undefined; | ||
this.trigger.setAttribute('aria-expanded', `${newAriaExpanded}`); | ||
}); | ||
|
||
/** | ||
* Toggle the post-collapsible controlled by the trigger | ||
*/ | ||
private async toggleCollapsible() { | ||
await this.collapsible?.toggle(); | ||
} | ||
|
||
/** | ||
* Retrieve the post-collapsible controlled by the trigger | ||
*/ | ||
private get collapsible(): HTMLPostCollapsibleElement | null { | ||
const ref = document.getElementById(this.for); | ||
if (ref && ref.localName === 'post-collapsible') { | ||
return ref as HTMLPostCollapsibleElement; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* Find the button and add the proper event listener and ARIA attributes to it | ||
*/ | ||
private setTrigger() { | ||
const trigger = this.host.querySelector('button'); | ||
if (!trigger || (this.trigger && trigger.isEqualNode(this.trigger))) return; | ||
|
||
this.trigger = trigger; | ||
|
||
this.trigger.addEventListener('click', () => this.toggleCollapsible()); | ||
this.setAriaAttributes(); | ||
} | ||
} |
Oops, something went wrong.