fix(popover): gate visibility until positioned, tighten types
This commit is contained in:
@@ -47,7 +47,7 @@ interface Props {
|
||||
* ARIA role for the content
|
||||
* @default 'dialog'
|
||||
*/
|
||||
role?: string;
|
||||
role?: 'dialog' | 'menu' | 'listbox';
|
||||
/**
|
||||
* Trigger snippet — spread the provided props onto your trigger element
|
||||
*/
|
||||
@@ -74,8 +74,20 @@ const contentId = `popover-${uid}`;
|
||||
|
||||
let triggerEl: HTMLElement | undefined = $state();
|
||||
let contentEl: HTMLElement | undefined = $state();
|
||||
/**
|
||||
* Side actually used after flip. Seeded from the `side` prop; the authoritative
|
||||
* value is written by updatePosition() on every open, so the seed only matters
|
||||
* for the closed state (hence the intentional state_referenced_locally warning).
|
||||
*/
|
||||
let resolvedSide = $state(side);
|
||||
|
||||
/**
|
||||
* True once updatePosition has applied coordinates for the current open.
|
||||
* Gates visibility so the content never paints at its pre-positioned (0,0)
|
||||
* top-layer default before the first measurement.
|
||||
*/
|
||||
let positioned = $state(false);
|
||||
|
||||
/**
|
||||
* Actual DOM open state, driven by the `toggle` event. Source of truth for
|
||||
* whether the browser currently shows the popover; `open` is the public binding.
|
||||
@@ -122,14 +134,18 @@ function updatePosition(): void {
|
||||
resolvedSide = result.side;
|
||||
contentEl.style.left = `${result.x}px`;
|
||||
contentEl.style.top = `${result.y}px`;
|
||||
positioned = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirror the `toggle` event into our state.
|
||||
*/
|
||||
function onToggle(event: Event & { newState?: string }): void {
|
||||
function onToggle(event: ToggleEvent): void {
|
||||
shown = event.newState === 'open';
|
||||
open = shown;
|
||||
if (!shown) {
|
||||
positioned = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,6 +191,11 @@ $effect(() => {
|
||||
|
||||
{@render trigger(triggerProps)}
|
||||
|
||||
<!--
|
||||
inset:auto + margin:0 neutralize the UA popover stylesheet (which sets
|
||||
inset:0; margin:auto to center it) so the JS-applied left/top win.
|
||||
visibility is hidden until updatePosition runs (see `positioned`).
|
||||
-->
|
||||
<div
|
||||
bind:this={contentEl}
|
||||
id={contentId}
|
||||
@@ -183,7 +204,7 @@ $effect(() => {
|
||||
data-side={resolvedSide}
|
||||
data-state={shown ? 'open' : 'closed'}
|
||||
ontoggle={onToggle}
|
||||
style="position: fixed; inset: auto; margin: 0;"
|
||||
style={`position: fixed; inset: auto; margin: 0;${positioned ? '' : ' visibility: hidden;'}`}
|
||||
class={cn(
|
||||
'opacity-0 scale-95 transition-discrete transition-all duration-fast',
|
||||
'starting:opacity-0 starting:scale-95',
|
||||
|
||||
Reference in New Issue
Block a user