Skip to content

Commit 1b6faa7

Browse files
authored
PopoverPrimitive - Add onFocusOut argument (#3571)
1 parent baea215 commit 1b6faa7

File tree

16 files changed

+433
-2
lines changed

16 files changed

+433
-2
lines changed

.changeset/twenty-rice-retire.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
"@hashicorp/design-system-components": minor
3+
---
4+
5+
<!-- START utilities/popover-primitive -->
6+
`PopoverPrimitive` - Added `@onFocusOut` argument to manage focus for dynamic popover content and prevent the popover from closing when content changes
7+
<!-- END -->
8+
9+
<!-- START components/dropdown -->
10+
`Dropdown` - Added `@onFocusOut` argument to manage focus for dynamic dropdown content and prevent the dropdown from closing when content changes
11+
<!-- END -->
12+
13+
<!-- START components/filter-bar -->
14+
`FilterBar` - Added `@onFocusOut` argument to the `FiltersDropdown` to help manage focus for dynamic dropdown content and prevent the dropdown from closing when content changes
15+
<!-- END -->
16+
17+
<!-- START components/rich-tooltip -->
18+
`RichTooltip` - Added `@onFocusOut` argument to manage focus for dynamic tooltip content and prevent the tooltip from closing when content changes
19+
<!-- END -->

packages/components/src/components/hds/dropdown/index.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<Hds::PopoverPrimitive
66
@isOpen={{@isOpen}}
77
@onClose={{@onClose}}
8+
@onFocusOut={{@onFocusOut}}
89
@boundary={{@boundary}}
910
@enableClickEvents={{true}}
1011
as |PP|

packages/components/src/components/hds/dropdown/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export interface HdsDropdownSignature {
4949
preserveContentInDom?: boolean;
5050
matchToggleWidth?: boolean;
5151
onClose?: HdsPopoverPrimitiveSignature['Args']['onClose'];
52+
onFocusOut?: HdsPopoverPrimitiveSignature['Args']['onFocusOut'];
5253
boundary?: HdsAnchoredPositionOptions['boundary'];
5354
};
5455
Blocks: {

packages/components/src/components/hds/filter-bar/actions-dropdown.gts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface HdsFilterBarActionsDropdownSignature {
2020
| 'height'
2121
| 'preserveContentInDom'
2222
| 'onClose'
23+
| 'onFocusOut'
2324
> & {
2425
toggleButtonText?: HdsDropdownToggleButtonSignature['Args']['text'];
2526
toggleButtonIcon?: HdsDropdownToggleButtonSignature['Args']['icon'];
@@ -53,6 +54,7 @@ export default class HdsFilterBarActionsDropdown extends Component<HdsFilterBarA
5354
@height={{@height}}
5455
@preserveContentInDom={{@preserveContentInDom}}
5556
@onClose={{@onClose}}
57+
@onFocusOut={{@onFocusOut}}
5658
class="hds-filter-bar__actions-dropdown"
5759
...attributes
5860
as |D|

packages/components/src/components/hds/filter-bar/filters-dropdown.gts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export interface HdsFilterBarFiltersDropdownSignature {
3030
isLiveFilter?: boolean;
3131
height?: string;
3232
onFilter?: (filters: HdsFilterBarFilters) => void;
33+
onFocusOut?: HdsDropdownSignature['Args']['onFocusOut'];
3334
};
3435
Blocks: {
3536
default: [
@@ -138,6 +139,7 @@ export default class HdsFilterBarFiltersDropdown extends Component<HdsFilterBarF
138139
<HdsDropdown
139140
@listPosition="bottom-left"
140141
@width="600px"
142+
@onFocusOut={{@onFocusOut}}
141143
...attributes
142144
class="hds-filter-bar__filters-dropdown"
143145
{{style this.dropdownHeightStyle}}

packages/components/src/components/hds/popover-primitive/index.gts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface HdsPopoverPrimitiveSignature {
2727
boundary?: HdsAnchoredPositionOptions['boundary'];
2828
onOpen?: () => void;
2929
onClose?: () => void;
30+
onFocusOut?: () => void;
3031
};
3132
Blocks: {
3233
default: [
@@ -348,7 +349,12 @@ export default class HdsPopoverPrimitive extends Component<HdsPopoverPrimitiveSi
348349
}
349350
// if the target receiving the focus is _not_ part of the disclosed content we close the disclosed container
350351
if (!isFocusStillInside) {
351-
this.hidePopover();
352+
const { onFocusOut } = this.args;
353+
if (!event.relatedTarget && typeof onFocusOut === 'function') {
354+
onFocusOut();
355+
} else {
356+
this.hidePopover();
357+
}
352358
}
353359
}
354360
};

packages/components/src/components/hds/rich-tooltip/index.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
@isOpen={{@isOpen}}
88
@onOpen={{@onOpen}}
99
@onClose={{@onClose}}
10+
@onFocusOut={{@onFocusOut}}
1011
@enableSoftEvents={{this.enableSoftEvents}}
1112
@enableClickEvents={{this.enableClickEvents}}
1213
as |PP|

showcase/app/components/page-components/filter-bar/code-fragments/with-generic-filters.gts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,18 @@ export default class CodeFragmentWithGenericContent extends Component<CodeFragme
208208
}
209209
}
210210

211+
onFocusOut = () => {
212+
const demoNameInput = document.getElementById(
213+
'demo-name-2',
214+
) as HTMLInputElement | null;
215+
if (demoNameInput) {
216+
demoNameInput.focus();
217+
}
218+
};
219+
211220
<template>
212221
<HdsFilterBar @filters={{this.filters}} @onFilter={{this.onFilter}} as |F|>
213-
<F.FiltersDropdown as |D|>
222+
<F.FiltersDropdown @onFocusOut={{this.onFocusOut}} as |D|>
214223
<D.FilterGroup
215224
@key="single"
216225
@text="Single filter"
@@ -245,6 +254,7 @@ export default class CodeFragmentWithGenericContent extends Component<CodeFragme
245254
@value={{this.multiFilterName}}
246255
{{on "input" (fn this.onMultiNameChange G.updateFilter)}}
247256
name="name-2"
257+
id="demo-name-2"
248258
as |F|
249259
>
250260
<F.Label>Name</F.Label>
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* Copyright IBM Corp. 2021, 2025
3+
* SPDX-License-Identifier: MPL-2.0
4+
*/
5+
import Component from '@glimmer/component';
6+
import { on } from '@ember/modifier';
7+
import { tracked } from '@glimmer/tracking';
8+
import style from 'ember-style-modifier';
9+
10+
import ShwPlaceholder from 'showcase/components/shw/placeholder';
11+
12+
import { HdsPopoverPrimitive } from '@hashicorp/design-system-components/components';
13+
import type { HdsAnchoredPositionOptions } from '@hashicorp/design-system-components/modifiers/hds-anchored-position';
14+
15+
export interface CodeFragmentWithFocusOutSignature {
16+
Element: HTMLDivElement;
17+
}
18+
19+
export default class CodeFragmentWithFocusOut extends Component<CodeFragmentWithFocusOutSignature> {
20+
@tracked showExtraContent = false;
21+
22+
private _anchoredPositionOptions: HdsAnchoredPositionOptions = {
23+
enableCollisionDetection: false,
24+
placement: 'bottom',
25+
};
26+
27+
demoShowContent = () => {
28+
this.showExtraContent = true;
29+
};
30+
31+
demoHideContent = () => {
32+
this.showExtraContent = false;
33+
};
34+
35+
demoOnFocusOut = () => {
36+
console.log('Popover focus out detected');
37+
const demoBtn = document.getElementById('demo-btn');
38+
demoBtn?.focus();
39+
};
40+
41+
<template>
42+
<HdsPopoverPrimitive
43+
@enableClickEvents={{true}}
44+
@onFocusOut={{this.demoOnFocusOut}}
45+
as |PP|
46+
>
47+
<div
48+
class="shw-utilities-popover-primitive-fake-container"
49+
{{PP.setupPrimitiveContainer}}
50+
>
51+
<button
52+
type="button"
53+
class="shw-utilities-popover-primitive-fake-toggle"
54+
{{style marginBottom="150px"}}
55+
{{PP.setupPrimitiveToggle}}
56+
>Toggle</button>
57+
<div
58+
class="shw-utilities-popover-primitive-fake-popover"
59+
{{PP.setupPrimitivePopover
60+
anchoredPositionOptions=this._anchoredPositionOptions
61+
}}
62+
>
63+
<ShwPlaceholder @text="Content" @width="200" @height="40" />
64+
<button
65+
type="button"
66+
id="demo-btn"
67+
{{on "click" this.demoShowContent}}
68+
>Show extra content</button>
69+
{{#if this.showExtraContent}}
70+
<div class="shw-utilities-popover-primitive-fake-extra-content">
71+
<button
72+
type="button"
73+
id="demo-btn-2"
74+
{{on "click" this.demoHideContent}}
75+
>Hide extra content</button>
76+
<ShwPlaceholder
77+
@text="On click of the button, this content will be removed, and focus will be set back on the previous button. The popover should not close."
78+
@height="100"
79+
@width="200"
80+
/>
81+
</div>
82+
{{/if}}
83+
</div>
84+
</div>
85+
</HdsPopoverPrimitive>
86+
</template>
87+
}

showcase/app/components/page-utilities/popover-primitive/index.gts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import ShwTextH1 from 'showcase/components/shw/text/h1';
1010
import SubSectionBase from 'showcase/components/page-utilities/popover-primitive/sub-sections/base';
1111
import SubSectionInteraction from 'showcase/components/page-utilities/popover-primitive/sub-sections/interaction';
1212
import SubSectionOptions from 'showcase/components/page-utilities/popover-primitive/sub-sections/options';
13+
import SubSectionDemos from 'showcase/components/page-utilities/popover-primitive/sub-sections/demos';
1314

1415
const PopoverPrimitiveIndex: TemplateOnlyComponent = <template>
1516
{{pageTitle "PopoverPrimitive Component"}}
@@ -20,6 +21,7 @@ const PopoverPrimitiveIndex: TemplateOnlyComponent = <template>
2021
<SubSectionBase />
2122
<SubSectionOptions />
2223
<SubSectionInteraction />
24+
<SubSectionDemos />
2325
</section>
2426
</template>;
2527

0 commit comments

Comments
 (0)