Skip to content

Commit 849d8c6

Browse files
authored
feat(popover): use new popover attribute (#40)
closes #27
1 parent 42c123f commit 849d8c6

File tree

6 files changed

+207
-147
lines changed

6 files changed

+207
-147
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

77
## [Unreleased]
88

9+
### Changed
10+
11+
- Use `popover` attribute in the popover element
12+
913
### Fixed
1014

1115
- Do not click menuitem again

packages/core/src/elements/dialog/index.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,12 @@ describe('Dialog', async () => {
193193
const dialog = twcDialog.querySelector('dialog')!;
194194

195195
trigger.click();
196-
expect(el).to.have.attribute('open');
196+
expect(el.open).to.eq(true);
197197

198198
twcDialog.open = true;
199199
assertDialogShown(twcDialog, dialog);
200200

201201
await sendKeys({ press: 'Escape' });
202-
expect(el).to.have.attribute('open'); // Should not close the popover.
202+
expect(el.open).to.eq(true); // Should not close the popover.
203203
});
204204
});

packages/core/src/elements/popover/README.md

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,21 @@ import 'tailwindcss-elements/elements/popover';
1111
## Usage
1212

1313
```html
14-
<twc-popover class="relative">
14+
<twc-popover>
1515
<button data-target="twc-popover.trigger" type="button">Toggle popover</button>
16-
<div data-target="twc-popover.panel" class="absolute hidden data-[headlessui-state='open']:block">
16+
<div data-target="twc-popover.panel">
1717
Popover contents!
1818
</div>
1919
</twc-popover>
2020
```
2121

2222
[![Edit Popover](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/p/sandbox/popover-yyvw2d)
2323

24-
## Examples
25-
26-
### Default to the open state
24+
**Note:** The popover element uses the [`popover`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/popover)
25+
attribute and comes with a [polyfill](https://github.com/oddbird/popover-polyfill). Therefore, you should not add any
26+
hidden classes to the popover' panel as it is managed by the browser.
2727

28-
You can set the `open` attribute on the element and it will be open by default.
29-
30-
```html
31-
<twc-popover open>
32-
...
33-
</twc-popover>
34-
```
28+
## Examples
3529

3630
### Programmatically toggling the visibility state
3731

@@ -45,10 +39,10 @@ You can set the `open` attribute on the element and it will be open by default.
4539
const element = document.querySelector('twc-popover');
4640

4741
// Show
48-
element.open = true;
42+
element.show();
4943

5044
// Hide
51-
element.open = false;
45+
element.hide();
5246
```
5347

5448
### Adding a close button inside the popover
@@ -59,7 +53,7 @@ will close the parent popover.
5953
```html
6054
<twc-popover>
6155
<button data-target="twc-popover.trigger" type="button">Toggle popover</button>
62-
<div data-target="twc-popover.panel" class="hidden data-[headlessui-state='open']:block">
56+
<div data-target="twc-popover.panel">
6357
Popover contents!
6458
<button type="button" data-action="click->twc-popover#hide">x</button>
6559
</div>
@@ -71,12 +65,12 @@ will close the parent popover.
7165
```html
7266
<twc-popover>
7367
<button data-target="twc-popover.trigger" type="button">Toggle popover</button>
74-
<div data-target="twc-popover.panel" class="hidden data-[headlessui-state='open']:block">
68+
<div data-target="twc-popover.panel">
7569
Look another popover!
7670

7771
<twc-popover>
7872
<button data-target="twc-popover.trigger" type="button">Toggle nested popover</button>
79-
<div data-target="twc-popover.panel" class="hidden data-[headlessui-state='open']:block">
73+
<div data-target="twc-popover.panel">
8074
Nested popover contents!
8175
</div>
8276
</twc-popover>
@@ -90,10 +84,10 @@ By default, the positioning logic is not taken care of when you use the `twc-pop
9084
wrapping the trigger and panel with the [`twc-floating-panel`](../floating_panel/README.md) element.
9185

9286
```html
93-
<twc-popover class="relative">
87+
<twc-popover>
9488
<twc-floating-panel>
9589
<button data-target="twc-popover.trigger twc-floating-panel.trigger" type="button">Toggle popover</button>
96-
<div data-target="twc-popover.panel twc-floating-panel.panel" class="absolute hidden data-[headlessui-state='open']:block">
90+
<div data-target="twc-popover.panel twc-floating-panel.panel">
9791
Popover contents!
9892
</div>
9993
</twc-floating-panel>

packages/core/src/elements/popover/index.test.ts

Lines changed: 83 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@ import Sinon from 'sinon';
44
import FloatingPanelElement from '../floating_panel';
55
import PopoverElement from './index';
66

7-
function assertPopoverShown(el: PopoverElement, trigger: HTMLButtonElement, panel: Element) {
8-
expect(el).to.have.attribute('open');
7+
function assertPopoverShown(trigger: HTMLButtonElement, panel: Element) {
98
expect(trigger).to.have.attribute('data-headlessui-state', 'open');
109
expect(trigger).to.have.attribute('aria-expanded', 'true');
1110
expect(panel).to.have.attribute('data-headlessui-state', 'open');
1211
}
1312

14-
function assertPopoverHidden(el: PopoverElement, trigger: HTMLButtonElement, panel: Element) {
15-
expect(el).not.to.have.attribute('open');
13+
function assertPopoverHidden(trigger: HTMLButtonElement, panel: Element) {
1614
expect(trigger).to.have.attribute('data-headlessui-state', '');
1715
expect(trigger).to.have.attribute('aria-expanded', 'false');
1816
expect(panel).to.have.attribute('data-headlessui-state', '');
@@ -27,8 +25,6 @@ describe('Popover', () => {
2725
</twc-popover>
2826
`);
2927

30-
expect(el).not.to.have.attribute('open');
31-
3228
const trigger = el.querySelector('button')!;
3329
const panel = el.querySelector('div')!;
3430

@@ -61,12 +57,12 @@ describe('Popover', () => {
6157
const panel = el.querySelector('div')!;
6258

6359
trigger.click();
64-
assertPopoverShown(el, trigger, panel);
60+
assertPopoverShown(trigger, panel);
6561
expect(document.activeElement).to.eq(panel);
6662
expect(shownHandler.calledOnce).to.be.true;
6763

6864
trigger.click();
69-
assertPopoverHidden(el, trigger, panel);
65+
assertPopoverHidden(trigger, panel);
7066
expect(document.activeElement).to.eq(trigger);
7167
expect(hiddenHandler.calledOnce).to.be.true;
7268
});
@@ -86,11 +82,11 @@ describe('Popover', () => {
8682
const panel = el.querySelector('div')!;
8783

8884
trigger.click();
89-
assertPopoverShown(el, trigger, panel);
85+
assertPopoverShown(trigger, panel);
9086
expect(document.activeElement).to.eq(panel);
9187

9288
await sendKeys({ press: 'Escape' });
93-
assertPopoverHidden(el, trigger, panel);
89+
assertPopoverHidden(trigger, panel);
9490
expect(document.activeElement).to.eq(trigger);
9591
expect(hiddenHandler.calledOnce).to.be.true;
9692
});
@@ -112,11 +108,11 @@ describe('Popover', () => {
112108
const closeButton = el.querySelector<HTMLButtonElement>('[data-test-id="close-btn"]')!;
113109

114110
trigger.click();
115-
assertPopoverShown(el, trigger, panel);
111+
assertPopoverShown(trigger, panel);
116112
expect(document.activeElement).to.eq(closeButton);
117113

118114
closeButton.click();
119-
assertPopoverHidden(el, trigger, panel);
115+
assertPopoverHidden(trigger, panel);
120116
expect(document.activeElement).to.eq(trigger);
121117
expect(hiddenHandler.calledOnce).to.be.true;
122118
});
@@ -136,36 +132,10 @@ describe('Popover', () => {
136132
const panel = el.querySelector('div')!;
137133

138134
trigger.click();
139-
assertPopoverShown(el, trigger, panel);
135+
assertPopoverShown(trigger, panel);
140136

141137
el.hide();
142-
assertPopoverHidden(el, trigger, panel);
143-
expect(hiddenHandler.called).to.be.false;
144-
});
145-
146-
it('toggles the popover when setting the open attribute', async () => {
147-
const el = await fixture<PopoverElement>(html`
148-
<twc-popover>
149-
<button type="button" data-target="twc-popover.trigger">Toggle</button>
150-
<div data-target="twc-popover.panel"></div>
151-
</twc-popover>
152-
`);
153-
154-
const shownHandler = Sinon.spy();
155-
el.addEventListener(`${el.identifier}:shown`, shownHandler);
156-
157-
const hiddenHandler = Sinon.spy();
158-
el.addEventListener(`${el.identifier}:hidden`, hiddenHandler);
159-
160-
const trigger = el.querySelector('button')!;
161-
const panel = el.querySelector('div')!;
162-
163-
el.open = true;
164-
assertPopoverShown(el, trigger, panel);
165-
expect(shownHandler.called).to.be.false;
166-
167-
el.open = false;
168-
assertPopoverHidden(el, trigger, panel);
138+
assertPopoverHidden(trigger, panel);
169139
expect(hiddenHandler.called).to.be.false;
170140
});
171141

@@ -184,33 +154,48 @@ describe('Popover', () => {
184154
const panel = el.querySelector('div')!;
185155

186156
trigger.click();
187-
assertPopoverShown(el, trigger, panel);
157+
assertPopoverShown(trigger, panel);
188158

189159
await sendMouse({ type: 'click', position: [0, 0] });
190-
assertPopoverHidden(el, trigger, panel);
160+
assertPopoverHidden(trigger, panel);
191161
expect(hiddenHandler.calledOnce).to.be.true;
192162
});
193163

194-
it('shows the popover initially if the open attribute is set to true', async () => {
164+
it('closes all the nested popovers and the parent popover when parent trigger button is clicked', async () => {
195165
const el = await fixture<PopoverElement>(html`
196-
<twc-popover open>
197-
<button type="button" data-target="twc-popover.trigger">Toggle</button>
198-
<div data-target="twc-popover.panel"></div>
166+
<twc-popover>
167+
<button type="button" data-target="twc-popover.trigger" data-test-id="parent-trigger">Toggle</button>
168+
<div data-target="twc-popover.panel" data-test-id="parent-panel">
169+
<twc-popover>
170+
<button type="button" data-target="twc-popover.trigger">Toggle</button>
171+
<div data-target="twc-popover.panel"></div>
172+
</twc-popover>
173+
</div>
199174
</twc-popover>
200175
`);
201176

202-
const shownHandler = Sinon.spy();
203-
el.addEventListener(`${el.identifier}:shown`, shownHandler);
177+
const trigger = el.querySelector<HTMLButtonElement>('[data-test-id="parent-trigger"]')!;
178+
const panel = el.querySelector('[data-test-id="parent-panel"]')!;
179+
const nestedEl = el.querySelector('twc-popover')!;
180+
const nestedTrigger = nestedEl.querySelector('button')!;
181+
const nestedPanel = nestedEl.querySelector('div')!;
204182

205-
const trigger = el.querySelector('button')!;
206-
const panel = el.querySelector('div')!;
183+
trigger.click();
184+
assertPopoverShown(trigger, panel);
185+
186+
nestedTrigger.click();
187+
assertPopoverShown(nestedTrigger, nestedPanel);
188+
assertPopoverShown(trigger, panel);
207189

208-
assertPopoverShown(el, trigger, panel);
209-
expect(shownHandler.called).to.be.false;
210-
expect(document.activeElement).to.eq(document.body);
190+
await sendMouse({
191+
type: 'click',
192+
position: [trigger.getBoundingClientRect().x, trigger.getBoundingClientRect().y],
193+
});
194+
assertPopoverHidden(trigger, panel);
195+
assertPopoverHidden(nestedTrigger, nestedPanel);
211196
});
212197

213-
it('closes all the nested popovers and the parent popover when parent trigger button is clicked', async () => {
198+
it('closes popover in order when clicked outside', async () => {
214199
const el = await fixture<PopoverElement>(html`
215200
<twc-popover>
216201
<button type="button" data-target="twc-popover.trigger" data-test-id="parent-trigger">Toggle</button>
@@ -230,18 +215,51 @@ describe('Popover', () => {
230215
const nestedPanel = nestedEl.querySelector('div')!;
231216

232217
trigger.click();
233-
assertPopoverShown(el, trigger, panel);
218+
nestedTrigger.click();
219+
220+
await sendMouse({ type: 'click', position: [0, 0] });
221+
assertPopoverHidden(nestedTrigger, nestedPanel);
222+
assertPopoverShown(trigger, panel);
234223

224+
await sendMouse({ type: 'click', position: [0, 0] });
225+
assertPopoverHidden(trigger, panel);
226+
});
227+
228+
it('closes all open popovers when opening a non-related popover', async () => {
229+
const el = await fixture<PopoverElement>(html`
230+
<div>
231+
<twc-popover>
232+
<button type="button" data-target="twc-popover.trigger" data-test-id="diff-trigger">Toggle</button>
233+
<div data-target="twc-popover.panel" data-test-id="diff-panel"></div>
234+
</twc-popover>
235+
<twc-popover>
236+
<button type="button" data-target="twc-popover.trigger" data-test-id="parent-trigger">Toggle</button>
237+
<div data-target="twc-popover.panel" data-test-id="parent-panel">
238+
<twc-popover data-test-id="nested-popover">
239+
<button type="button" data-target="twc-popover.trigger">Toggle</button>
240+
<div data-target="twc-popover.panel"></div>
241+
</twc-popover>
242+
</div>
243+
</twc-popover>
244+
</div>
245+
`);
246+
247+
const diffTrigger = el.querySelector<HTMLButtonElement>('[data-test-id="diff-trigger"]')!;
248+
const diffPanel = el.querySelector('[data-test-id="diff-panel"]')!;
249+
250+
const trigger = el.querySelector<HTMLButtonElement>('[data-test-id="parent-trigger"]')!;
251+
const panel = el.querySelector('[data-test-id="parent-panel"]')!;
252+
const nestedEl = el.querySelector<PopoverElement>('[data-test-id="nested-popover"]')!;
253+
const nestedTrigger = nestedEl.querySelector('button')!;
254+
const nestedPanel = nestedEl.querySelector('div')!;
255+
256+
trigger.click();
235257
nestedTrigger.click();
236-
assertPopoverShown(nestedEl, nestedTrigger, nestedPanel);
237-
assertPopoverShown(el, trigger, panel);
238258

239-
await sendMouse({
240-
type: 'click',
241-
position: [trigger.getBoundingClientRect().x, trigger.getBoundingClientRect().y],
242-
});
243-
assertPopoverHidden(el, trigger, panel);
244-
assertPopoverHidden(nestedEl, nestedTrigger, nestedPanel);
259+
diffTrigger.click();
260+
assertPopoverHidden(trigger, panel);
261+
assertPopoverHidden(nestedTrigger, nestedPanel);
262+
assertPopoverShown(diffTrigger, diffPanel);
245263
});
246264

247265
it('starts and stops the positioning logic', async () => {
@@ -256,10 +274,10 @@ describe('Popover', () => {
256274

257275
const floatingPanel = el.querySelector('twc-floating-panel')! as FloatingPanelElement;
258276

259-
el.open = true;
277+
el.show();
260278
expect(floatingPanel).to.have.attribute('active');
261279

262-
el.open = false;
280+
el.hide();
263281
expect(floatingPanel).not.to.have.attribute('active');
264282
});
265283
});

0 commit comments

Comments
 (0)