Skip to content

Commit 426f0a7

Browse files
refactor: popover moved into its own hook file
1 parent 93a0d28 commit 426f0a7

File tree

7 files changed

+117
-142
lines changed

7 files changed

+117
-142
lines changed

apps/website/src/routes/docs/headless/popover/index.mdx

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ To read more about the popover API you can check it out on:
414414
- [Open UI Proposal](https://open-ui.org/components/popover.research.explainer/)
415415
- [What is the top layer?](https://developer.chrome.com/blog/what-is-the-top-layer/)
416416

417-
### Popover
417+
### Popover Root
418418

419419
<APITable
420420
propDescriptors={[
@@ -423,12 +423,6 @@ To read more about the popover API you can check it out on:
423423
type: 'string',
424424
description: `Popover's id. Should match the popover target.`,
425425
},
426-
{
427-
name: 'popover',
428-
type: 'union',
429-
description:
430-
'Defines the popover behavior, can be auto or manual. Default is auto.',
431-
},
432426
{
433427
name: 'manual',
434428
type: 'boolean',
@@ -480,9 +474,9 @@ To read more about the popover API you can check it out on:
480474
description: 'Selects the popover on all browsers.',
481475
},
482476
{
483-
name: ':popover-open',
477+
name: 'data-open',
484478
type: 'selector',
485-
description: 'Native supported pseudo element when the popover is open.',
479+
description: 'Style the element when the popover is open.',
486480
},
487481
{
488482
name: '.popover-open',
@@ -502,18 +496,6 @@ To read more about the popover API you can check it out on:
502496
]}
503497
/>
504498

505-
### Popover Trigger
506-
507-
<APITable
508-
propDescriptors={[
509-
{
510-
name: 'popovertarget',
511-
type: 'union',
512-
description: 'Accepts a string that matches the id of the popover.',
513-
},
514-
]}
515-
/>
516-
517499
### usePopover hook
518500

519501
<APITable

packages/kit-headless/src/components/combobox/combobox-popover.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { component$, useContext, Slot, useTask$, PropsOf } from '@builder.io/qwik';
22
import { PopoverPanel } from '../popover/popover-panel';
3-
import { usePopover } from '../popover/popover-trigger';
3+
import { usePopover } from '../popover/use-popover';
44
import { PopoverRoot } from '../popover/popover-root';
55

66
import ComboboxContextId from './combobox-context-id';

packages/kit-headless/src/components/popover/popover-panel-impl.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import {
1313

1414
import { isServer } from '@builder.io/qwik/build';
1515
import popoverStyles from './popover.css?inline';
16-
import { supportShowAnimation, supportClosingAnimation } from './utils';
1716
import { popoverContextId } from './popover-context';
17+
import { supportShowAnimation, supportClosingAnimation } from './utils';
1818

1919
// We don't need a provider, that way we connect all context to the root
2020
const ensureContextId = createContextId('qui-popover-null-context');
@@ -112,7 +112,7 @@ export const PopoverPanelImpl = component$((props: PropsOf<'div'>) => {
112112
: 'auto' || 'auto'
113113
}
114114
onBeforeToggle$={[
115-
$((e) => {
115+
$(async (e) => {
116116
if (!context.panelRef?.value) return;
117117

118118
if (e.newState === 'open' && context.panelRef.value) {

packages/kit-headless/src/components/popover/popover-trigger.tsx

Lines changed: 2 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,12 @@
1-
import {
2-
useOnDocument,
3-
useTask$,
4-
Slot,
5-
component$,
6-
useSignal,
7-
$,
8-
PropsOf,
9-
useContext,
10-
} from '@builder.io/qwik';
11-
import { isBrowser } from '@builder.io/qwik/build';
1+
import { Slot, component$, $, PropsOf, useContext } from '@builder.io/qwik';
122
import { popoverContextId } from './popover-context';
3+
import { usePopover } from './use-popover';
134

145
type PopoverTriggerProps = {
156
popovertarget?: string;
167
disableClickInitPopover?: boolean;
178
} & PropsOf<'button'>;
189

19-
export function usePopover(customId?: string) {
20-
const hasPolyfillLoadedSig = useSignal<boolean>(false);
21-
const isSupportedSig = useSignal<boolean>(false);
22-
23-
const didInteractSig = useSignal<boolean>(false);
24-
const programmaticRef = useSignal<HTMLElement | null>(null);
25-
const isCSRSig = useSignal<boolean>(false);
26-
27-
const loadPolyfill$ = $(async () => {
28-
document.dispatchEvent(new CustomEvent('poppolyload'));
29-
});
30-
31-
useTask$(() => {
32-
if (isBrowser) {
33-
isCSRSig.value = true;
34-
}
35-
});
36-
37-
const initPopover$ = $(async () => {
38-
/* needs to run before poly load */
39-
const isSupported =
40-
typeof HTMLElement !== 'undefined' &&
41-
typeof HTMLElement.prototype === 'object' &&
42-
'popover' in HTMLElement.prototype;
43-
44-
isSupportedSig.value = isSupported;
45-
46-
if (!hasPolyfillLoadedSig.value && !isSupported) {
47-
await loadPolyfill$();
48-
}
49-
50-
if (!didInteractSig.value) {
51-
if (programmaticRef.value === null) {
52-
programmaticRef.value = document.getElementById(`${customId}-panel`);
53-
}
54-
55-
// only opens the popover that is interacted with
56-
didInteractSig.value = true;
57-
}
58-
59-
return programmaticRef.value;
60-
});
61-
62-
// event is created after teleported properly
63-
useOnDocument(
64-
'showpopoverpoly',
65-
$(async () => {
66-
if (!didInteractSig.value) return;
67-
68-
// make sure to load the polyfill after the client re-render
69-
await import('@oddbird/popover-polyfill');
70-
71-
hasPolyfillLoadedSig.value = true;
72-
}),
73-
);
74-
75-
const showPopover = $(async () => {
76-
await initPopover$();
77-
78-
if (!isSupportedSig.value) {
79-
// Wait until the polyfill has been loaded if necessary
80-
while (!hasPolyfillLoadedSig.value) {
81-
await new Promise((resolve) => setTimeout(resolve, 10)); // Poll every 10ms
82-
}
83-
}
84-
85-
programmaticRef.value?.showPopover();
86-
});
87-
88-
const togglePopover = $(async () => {
89-
await initPopover$();
90-
91-
if (!isSupportedSig.value) {
92-
// Wait until the polyfill has been loaded if necessary
93-
while (!hasPolyfillLoadedSig.value) {
94-
await new Promise((resolve) => setTimeout(resolve, 10)); // Poll every 10ms
95-
}
96-
}
97-
98-
programmaticRef.value?.togglePopover();
99-
});
100-
101-
const hidePopover = $(async () => {
102-
await initPopover$();
103-
104-
if (!isSupportedSig.value) {
105-
// Wait until the polyfill has been loaded if necessary
106-
while (!hasPolyfillLoadedSig.value) {
107-
await new Promise((resolve) => setTimeout(resolve, 10)); // Poll every 10ms
108-
}
109-
}
110-
111-
programmaticRef.value?.hidePopover();
112-
});
113-
114-
return {
115-
showPopover,
116-
togglePopover,
117-
hidePopover,
118-
initPopover$,
119-
hasPolyfillLoadedSig,
120-
isSupportedSig,
121-
};
122-
}
123-
12410
export const PopoverTrigger = component$<PopoverTriggerProps>(
12511
(props: PopoverTriggerProps) => {
12612
const context = useContext(popoverContextId);
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { useSignal, useTask$, useOnDocument, $ } from '@builder.io/qwik';
2+
import { isBrowser } from '@builder.io/qwik/build';
3+
4+
export function usePopover(customId?: string) {
5+
const hasPolyfillLoadedSig = useSignal<boolean>(false);
6+
const isSupportedSig = useSignal<boolean>(false);
7+
8+
const didInteractSig = useSignal<boolean>(false);
9+
const programmaticRef = useSignal<HTMLElement | null>(null);
10+
const isCSRSig = useSignal<boolean>(false);
11+
12+
const loadPolyfill$ = $(async () => {
13+
document.dispatchEvent(new CustomEvent('poppolyload'));
14+
});
15+
16+
useTask$(() => {
17+
if (isBrowser) {
18+
isCSRSig.value = true;
19+
}
20+
});
21+
22+
const initPopover$ = $(async () => {
23+
/* needs to run before poly load */
24+
const isSupported =
25+
typeof HTMLElement !== 'undefined' &&
26+
typeof HTMLElement.prototype === 'object' &&
27+
'popover' in HTMLElement.prototype;
28+
29+
isSupportedSig.value = isSupported;
30+
31+
if (!hasPolyfillLoadedSig.value && !isSupported) {
32+
await loadPolyfill$();
33+
}
34+
35+
if (!didInteractSig.value) {
36+
if (programmaticRef.value === null) {
37+
programmaticRef.value = document.getElementById(`${customId}-panel`);
38+
}
39+
40+
// only opens the popover that is interacted with
41+
didInteractSig.value = true;
42+
}
43+
44+
return programmaticRef.value;
45+
});
46+
47+
// event is created after teleported properly
48+
useOnDocument(
49+
'showpopoverpoly',
50+
$(async () => {
51+
if (!didInteractSig.value) return;
52+
53+
// make sure to load the polyfill after the client re-render
54+
await import('@oddbird/popover-polyfill');
55+
56+
hasPolyfillLoadedSig.value = true;
57+
}),
58+
);
59+
60+
const showPopover = $(async () => {
61+
await initPopover$();
62+
63+
if (!isSupportedSig.value) {
64+
// Wait until the polyfill has been loaded if necessary
65+
while (!hasPolyfillLoadedSig.value) {
66+
await new Promise((resolve) => setTimeout(resolve, 10)); // Poll every 10ms
67+
}
68+
}
69+
70+
programmaticRef.value?.showPopover();
71+
});
72+
73+
const togglePopover = $(async () => {
74+
await initPopover$();
75+
76+
if (!isSupportedSig.value) {
77+
// Wait until the polyfill has been loaded if necessary
78+
while (!hasPolyfillLoadedSig.value) {
79+
await new Promise((resolve) => setTimeout(resolve, 10)); // Poll every 10ms
80+
}
81+
}
82+
83+
programmaticRef.value?.togglePopover();
84+
});
85+
86+
const hidePopover = $(async () => {
87+
await initPopover$();
88+
89+
if (!isSupportedSig.value) {
90+
// Wait until the polyfill has been loaded if necessary
91+
while (!hasPolyfillLoadedSig.value) {
92+
await new Promise((resolve) => setTimeout(resolve, 10)); // Poll every 10ms
93+
}
94+
}
95+
96+
programmaticRef.value?.hidePopover();
97+
});
98+
99+
return {
100+
showPopover,
101+
togglePopover,
102+
hidePopover,
103+
initPopover$,
104+
hasPolyfillLoadedSig,
105+
isSupportedSig,
106+
};
107+
}

packages/kit-headless/src/components/select/select-popover.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
PropsOf,
77
useSignal,
88
} from '@builder.io/qwik';
9-
import { usePopover } from '../popover/popover-trigger';
9+
import { usePopover } from '../popover/use-popover';
1010
import { PopoverPanel } from '../popover/popover-panel';
1111

1212
import SelectContextId from './select-context';

packages/kit-headless/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export * from './components/label';
77
export * as Modal from './components/modal';
88
export { Pagination } from './components/pagination/pagination';
99
export * as Popover from './components/popover';
10-
export { usePopover } from './components/popover/popover-trigger';
10+
export { usePopover } from './components/popover/use-popover';
1111
export * as Select from './components/select';
1212
export * as Progress from './components/progress';
1313
export * from './components/separator/separator';

0 commit comments

Comments
 (0)