From 993b76c230563d05e61c5a33c9a4302620f31213 Mon Sep 17 00:00:00 2001 From: Chris Holt <13071055+chrisdholt@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:34:49 -0700 Subject: [PATCH 1/5] add support for popovertarget and popovertargetaction in button web component --- .../src/button/button.stories.ts | 7 ++ packages/web-components/src/button/button.ts | 76 ++++++++++++++++++- 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/packages/web-components/src/button/button.stories.ts b/packages/web-components/src/button/button.stories.ts index b22cff2b03093..6294abf3e34dd 100644 --- a/packages/web-components/src/button/button.stories.ts +++ b/packages/web-components/src/button/button.stories.ts @@ -220,3 +220,10 @@ export const ResetAndSubmitButtonsInForm: Story = renderComponent(
Div Label
`); + +export const Popover: Story = renderComponent(html>` + Show Popover + Hide Popover + Toggle Popover +
This is a popover
+`); diff --git a/packages/web-components/src/button/button.ts b/packages/web-components/src/button/button.ts index bff33807e42ad..1c42a9c610936 100644 --- a/packages/web-components/src/button/button.ts +++ b/packages/web-components/src/button/button.ts @@ -18,6 +18,13 @@ import { ButtonType } from './button.options.js'; * @public */ export class BaseButton extends FASTElement { + private popoverOpen?: boolean = false; + + /** + * Holds an element reference for the popovertarget + */ + private popoverTargetElement: HTMLElement | null | undefined; + /** * Indicates the button should be focused when the page is loaded. * @see The {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#autofocus | `autofocus`} attribute @@ -29,6 +36,28 @@ export class BaseButton extends FASTElement { @attr({ mode: 'boolean' }) public autofocus!: boolean; + /** + * The ID for the popover which is controlled by the element + * @see The {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#popovertarget | `popovertarget`} attribute + * + * @public + * @remarks + * HTML Attribute: `popovertarget` + */ + @attr + public popovertarget?: string; + + /** + * Specifies the action to be performed on a popover element being controlled by the element + * @see The {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#popovertargetaction | `popovertargetaction`} attribute + * + * @public + * @remarks + * HTML Attribute: `popovertargetaction` + */ + @attr + public popovertargetaction?: 'hide' | 'show' | 'toggle'; + /** * Default slotted content. * @@ -68,6 +97,17 @@ export class BaseButton extends FASTElement { @attr({ attribute: 'tabindex', mode: 'fromView', converter: nullableNumberConverter }) public override tabIndex: number = 0; + /** + * Handles changes to the popovertarget attribute + * + * @param previous - the previous popovertarget value + * @param next - the current popovertarget value + * @internal + */ + public popoverTargetChanged(previous: string | undefined, next: string | undefined): void { + this.setPopoverTargetElement(next); + } + /** * Sets the element's internal disabled state when the element is focusable while disabled. * @@ -254,13 +294,32 @@ export class BaseButton extends FASTElement { return; } + if (this.popoverTargetElement) { + switch (this.popovertargetaction) { + case 'hide': + this.popoverTargetElement?.hidePopover(); + break; + case 'show': + this.popoverTargetElement?.showPopover(); + break; + case 'toggle': + default: + this.popoverTargetElement?.togglePopover(!this.popoverOpen); + break; + } + } + this.press(); return true; } - connectedCallback(): void { + public connectedCallback(): void { super.connectedCallback(); this.elementInternals.ariaDisabled = `${!!this.disabledFocusable}`; + + if (this.popovertarget) { + this.setPopoverTargetElement(this.popovertarget); + } } constructor() { @@ -268,6 +327,21 @@ export class BaseButton extends FASTElement { this.elementInternals.role = 'button'; } + /** + * Sets up handling for the popover target element + * @param element - The element to reference + */ + private setPopoverTargetElement(element: string | undefined) { + this.popoverTargetElement = element ? document.getElementById(element) : undefined; + + this.popoverTargetElement?.addEventListener( + 'toggle', + e => + (this.popoverOpen = + (e as Event & { newState: 'open' | 'closed'; oldState: 'open' | 'closed' }).newState === 'open'), + ); + } + /** * This fallback creates a new slot, then creates a submit button to mirror the custom element's * properties. The submit button is then appended to the slot and the form is submitted. From be3a20d6c67f4afad42e12460c3d7100f9c35fc9 Mon Sep 17 00:00:00 2001 From: Chris Holt <13071055+chrisdholt@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:35:12 -0700 Subject: [PATCH 2/5] change files --- ...eb-components-c80213f4-0da2-4267-bcf7-614b2aa6bc5f.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-web-components-c80213f4-0da2-4267-bcf7-614b2aa6bc5f.json diff --git a/change/@fluentui-web-components-c80213f4-0da2-4267-bcf7-614b2aa6bc5f.json b/change/@fluentui-web-components-c80213f4-0da2-4267-bcf7-614b2aa6bc5f.json new file mode 100644 index 0000000000000..e6ce189205638 --- /dev/null +++ b/change/@fluentui-web-components-c80213f4-0da2-4267-bcf7-614b2aa6bc5f.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "add support for popovertarget and popovertargetaction in button web component", + "packageName": "@fluentui/web-components", + "email": "13071055+chrisdholt@users.noreply.github.com", + "dependentChangeType": "patch" +} From 4e43b659cc93dbf97cb1cc94bc859b5c6000725e Mon Sep 17 00:00:00 2001 From: Chris Holt <13071055+chrisdholt@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:49:48 -0700 Subject: [PATCH 3/5] verify popover attribute before invocation --- packages/web-components/src/button/button.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web-components/src/button/button.ts b/packages/web-components/src/button/button.ts index 1c42a9c610936..a20c85b22b515 100644 --- a/packages/web-components/src/button/button.ts +++ b/packages/web-components/src/button/button.ts @@ -294,7 +294,7 @@ export class BaseButton extends FASTElement { return; } - if (this.popoverTargetElement) { + if (this.popoverTargetElement && !!this.popoverTargetElement.hasAttribute('popover')) { switch (this.popovertargetaction) { case 'hide': this.popoverTargetElement?.hidePopover(); From 9bcb26c1b8beaf5f3c9d1c555de8aeae01b63d74 Mon Sep 17 00:00:00 2001 From: Chris Holt <13071055+chrisdholt@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:58:40 -0700 Subject: [PATCH 4/5] add ts-expect-error blocks --- packages/web-components/src/button/button.stories.ts | 2 +- packages/web-components/src/button/button.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/web-components/src/button/button.stories.ts b/packages/web-components/src/button/button.stories.ts index 6294abf3e34dd..64836ed9efd04 100644 --- a/packages/web-components/src/button/button.stories.ts +++ b/packages/web-components/src/button/button.stories.ts @@ -225,5 +225,5 @@ export const Popover: Story = renderComponent(htmlShow Popover Hide Popover Toggle Popover -
This is a popover
+
This is a popover
`); diff --git a/packages/web-components/src/button/button.ts b/packages/web-components/src/button/button.ts index a20c85b22b515..962dcbdce483e 100644 --- a/packages/web-components/src/button/button.ts +++ b/packages/web-components/src/button/button.ts @@ -297,13 +297,16 @@ export class BaseButton extends FASTElement { if (this.popoverTargetElement && !!this.popoverTargetElement.hasAttribute('popover')) { switch (this.popovertargetaction) { case 'hide': + // @ts-expect-error - Baseline 2024 this.popoverTargetElement?.hidePopover(); break; case 'show': + // @ts-expect-error - Baseline 2024 this.popoverTargetElement?.showPopover(); break; case 'toggle': default: + // @ts-expect-error - Baseline 2024 this.popoverTargetElement?.togglePopover(!this.popoverOpen); break; } From 96f3ac7c94961beb88d18add5f509ac3affc59bf Mon Sep 17 00:00:00 2001 From: Chris Holt <13071055+chrisdholt@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:36:59 -0700 Subject: [PATCH 5/5] remove event listeners --- packages/web-components/src/button/button.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/web-components/src/button/button.ts b/packages/web-components/src/button/button.ts index 962dcbdce483e..c11411fdcd61f 100644 --- a/packages/web-components/src/button/button.ts +++ b/packages/web-components/src/button/button.ts @@ -325,6 +325,14 @@ export class BaseButton extends FASTElement { } } + public disconnectedCallback(): void { + super.disconnectedCallback(); + + if (this.popoverTargetElement) { + this.popoverTargetElement.removeEventListener('toggle', this.handlePopover); + } + } + constructor() { super(); this.elementInternals.role = 'button'; @@ -337,14 +345,13 @@ export class BaseButton extends FASTElement { private setPopoverTargetElement(element: string | undefined) { this.popoverTargetElement = element ? document.getElementById(element) : undefined; - this.popoverTargetElement?.addEventListener( - 'toggle', - e => - (this.popoverOpen = - (e as Event & { newState: 'open' | 'closed'; oldState: 'open' | 'closed' }).newState === 'open'), - ); + this.popoverTargetElement?.addEventListener('toggle', this.handlePopover); } + private handlePopover = (e: Event) => { + this.popoverOpen = (e as Event & { newState: 'open' | 'closed'; oldState: 'open' | 'closed' }).newState === 'open'; + }; + /** * This fallback creates a new slot, then creates a submit button to mirror the custom element's * properties. The submit button is then appended to the slot and the form is submitted.