mirror of
https://github.com/zoriya/v10.git
synced 2026-08-05 05:37:21 +00:00
fix(core): update trigger aria-expanded on close (#1644)
This commit is contained in:
@@ -21,7 +21,7 @@ const menuContentPlacementClass = [
|
||||
|
||||
const menuContentClass = [
|
||||
menuContentSurfaceClass,
|
||||
'transition-[opacity,scale,translate,filter] duration-150',
|
||||
'transition-[opacity,scale,translate,filter] duration-150 ease-in-out',
|
||||
menuContentPlacementClass,
|
||||
].join(' ');
|
||||
|
||||
@@ -68,7 +68,7 @@ const menuNavPopupClass = [
|
||||
'group relative',
|
||||
menuNavSurfaceClass,
|
||||
'w-(--media-menu-width) h-(--media-menu-height)',
|
||||
'transition-[opacity,scale,translate,filter,width,height] duration-300 ease-in-out',
|
||||
'transition-[opacity,scale,translate,filter,width,height] duration-150 ease-in-out',
|
||||
menuContentPlacementClass,
|
||||
].join(' ');
|
||||
|
||||
@@ -87,7 +87,7 @@ root.innerHTML = `
|
||||
<div class="flex gap-4 flex-wrap justify-center">
|
||||
|
||||
<!-- Radio group -->
|
||||
<div class="bg-white border border-slate-200 rounded-xl p-6 flex flex-col items-start gap-3.5 min-w-[200px] shadow-[0_1px_3px_0_rgb(0_0_0/.05)]">
|
||||
<div class="bg-white ring-1 ring-slate-700/10 rounded-xl p-6 flex flex-col items-start gap-3.5 min-w-[200px] shadow-sm">
|
||||
<span class="text-xs font-medium text-slate-500 uppercase tracking-widest">Radio group</span>
|
||||
<button
|
||||
commandfor="quality-menu"
|
||||
@@ -97,12 +97,12 @@ root.innerHTML = `
|
||||
<svg class="w-3.5 h-3.5 text-slate-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
<media-menu id="quality-menu" class="${menuContentClass}">
|
||||
<media-menu-label class="${menuLabelClass}">Resolution</media-menu-label>
|
||||
<media-menu-radio-group id="quality-group" value="auto">
|
||||
<media-menu-radio-item value="auto" class="${radioItemClass}">Auto</media-menu-radio-item>
|
||||
<media-menu-radio-item value="1080p" class="${radioItemClass}">1080p</media-menu-radio-item>
|
||||
<media-menu-radio-item value="720p" class="${radioItemClass}">720p</media-menu-radio-item>
|
||||
<media-menu-radio-item value="480p" class="${radioItemClass}">480p</media-menu-radio-item>
|
||||
<media-menu-label class="${menuLabelClass}">Resolution</media-menu-label>
|
||||
<media-menu-radio-item value="auto" class="${radioItemClass}">Auto</media-menu-radio-item>
|
||||
<media-menu-radio-item value="1080p" class="${radioItemClass}">1080p</media-menu-radio-item>
|
||||
<media-menu-radio-item value="720p" class="${radioItemClass}">720p</media-menu-radio-item>
|
||||
<media-menu-radio-item value="480p" class="${radioItemClass}">480p</media-menu-radio-item>
|
||||
<media-menu-radio-item value="360p" disabled class="${radioItemClass}">360p (unavailable)</media-menu-radio-item>
|
||||
</media-menu-radio-group>
|
||||
</media-menu>
|
||||
@@ -110,7 +110,7 @@ root.innerHTML = `
|
||||
</div>
|
||||
|
||||
<!-- Mixed items -->
|
||||
<div class="bg-white border border-slate-200 rounded-xl p-6 flex flex-col items-start gap-3.5 min-w-[200px] shadow-[0_1px_3px_0_rgb(0_0_0/.05)]">
|
||||
<div class="bg-white ring-1 ring-slate-700/10 rounded-xl p-6 flex flex-col items-start gap-3.5 min-w-[200px] shadow-sm">
|
||||
<span class="text-xs font-medium text-slate-500 uppercase tracking-widest">Mixed items</span>
|
||||
<button
|
||||
commandfor="settings-menu"
|
||||
@@ -120,18 +120,20 @@ root.innerHTML = `
|
||||
<svg class="w-3.5 h-3.5 text-slate-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
<media-menu id="settings-menu" class="${menuContentClass}">
|
||||
<media-menu-label class="${menuLabelClass}">Playback</media-menu-label>
|
||||
<media-menu-checkbox-item id="loop-item" class="${checkboxItemClass}">Loop</media-menu-checkbox-item>
|
||||
<media-menu-checkbox-item id="autoplay-item" class="${checkboxItemClass}">Autoplay</media-menu-checkbox-item>
|
||||
<media-menu-group>
|
||||
<media-menu-label class="${menuLabelClass}">Playback</media-menu-label>
|
||||
<media-menu-checkbox-item id="loop-item" class="${checkboxItemClass}">Loop</media-menu-checkbox-item>
|
||||
<media-menu-checkbox-item id="autoplay-item" class="${checkboxItemClass}">Autoplay</media-menu-checkbox-item>
|
||||
</media-menu-group>
|
||||
<media-menu-separator class="${menuSeparatorClass}"></media-menu-separator>
|
||||
<media-menu-item id="copy-item" class="${menuItemClass}">Copy link</media-menu-item>
|
||||
<media-menu-item id="copy-item" class="${menuItemClass}">Copy link</media-menu-item>
|
||||
<media-menu-item id="report-item" class="${menuItemClass}">Report issue</media-menu-item>
|
||||
</media-menu>
|
||||
<p class="text-[0.8125rem] text-slate-500">Loop: <strong id="loop-output" class="text-slate-900 font-medium">off</strong></p>
|
||||
</div>
|
||||
|
||||
<!-- Submenu navigation -->
|
||||
<div class="bg-white border border-slate-200 rounded-xl p-6 flex flex-col items-start gap-3.5 min-w-[200px] shadow-[0_1px_3px_0_rgb(0_0_0/.05)]">
|
||||
<div class="bg-white ring-1 ring-slate-700/10 rounded-xl p-6 flex flex-col items-start gap-3.5 min-w-[200px] shadow-sm">
|
||||
<span class="text-xs font-medium text-slate-500 uppercase tracking-widest">Submenu</span>
|
||||
<button
|
||||
commandfor="nav-menu"
|
||||
@@ -173,11 +175,11 @@ root.innerHTML = `
|
||||
<svg class="w-3.5 h-3.5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
|
||||
Quality
|
||||
</media-menu-back>
|
||||
<media-menu-radio-group id="nav-quality-group" value="auto">
|
||||
<media-menu-radio-item value="auto" class="${radioItemClass}">Auto</media-menu-radio-item>
|
||||
<media-menu-radio-group id="nav-quality-group" aria-label="Resolution" value="auto">
|
||||
<media-menu-radio-item value="auto" class="${radioItemClass}">Auto</media-menu-radio-item>
|
||||
<media-menu-radio-item value="1080p" class="${radioItemClass}">1080p</media-menu-radio-item>
|
||||
<media-menu-radio-item value="720p" class="${radioItemClass}">720p</media-menu-radio-item>
|
||||
<media-menu-radio-item value="480p" class="${radioItemClass}">480p</media-menu-radio-item>
|
||||
<media-menu-radio-item value="720p" class="${radioItemClass}">720p</media-menu-radio-item>
|
||||
<media-menu-radio-item value="480p" class="${radioItemClass}">480p</media-menu-radio-item>
|
||||
</media-menu-radio-group>
|
||||
</media-menu>
|
||||
|
||||
@@ -186,13 +188,13 @@ root.innerHTML = `
|
||||
<svg class="w-3.5 h-3.5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
|
||||
Speed
|
||||
</media-menu-back>
|
||||
<media-menu-radio-group id="nav-speed-group" value="1">
|
||||
<media-menu-radio-item value="0.5" class="${radioItemClass}">0.5x</media-menu-radio-item>
|
||||
<media-menu-radio-group id="nav-speed-group" aria-label="Speed" value="1">
|
||||
<media-menu-radio-item value="0.5" class="${radioItemClass}">0.5x</media-menu-radio-item>
|
||||
<media-menu-radio-item value="0.75" class="${radioItemClass}">0.75x</media-menu-radio-item>
|
||||
<media-menu-radio-item value="1" class="${radioItemClass}">Normal</media-menu-radio-item>
|
||||
<media-menu-radio-item value="1" class="${radioItemClass}">Normal</media-menu-radio-item>
|
||||
<media-menu-radio-item value="1.25" class="${radioItemClass}">1.25x</media-menu-radio-item>
|
||||
<media-menu-radio-item value="1.5" class="${radioItemClass}">1.5x</media-menu-radio-item>
|
||||
<media-menu-radio-item value="2" class="${radioItemClass}">2x</media-menu-radio-item>
|
||||
<media-menu-radio-item value="1.5" class="${radioItemClass}">1.5x</media-menu-radio-item>
|
||||
<media-menu-radio-item value="2" class="${radioItemClass}">2x</media-menu-radio-item>
|
||||
</media-menu-radio-group>
|
||||
</media-menu>
|
||||
</media-menu>
|
||||
|
||||
@@ -15,15 +15,15 @@ const menuNavSurfaceClass =
|
||||
'bg-white border-none ring-1 ring-black/10 shadow-sm rounded-md min-w-[10rem] overflow-hidden outline-none';
|
||||
|
||||
const menuContentPlacementClass = [
|
||||
'data-side=bottom:origin-top data-side=top:origin-bottom',
|
||||
'data-side=left:origin-right data-side=right:origin-left',
|
||||
'data-[side=bottom]:origin-top data-[side=top]:origin-bottom',
|
||||
'data-[side=left]:origin-right data-[side=right]:origin-left',
|
||||
'data-starting-style:opacity-0 data-starting-style:scale-95 data-starting-style:-translate-y-1 data-starting-style:blur-sm',
|
||||
'data-ending-style:opacity-0 data-ending-style:scale-95 data-ending-style:-translate-y-1 data-ending-style:blur-sm',
|
||||
].join(' ');
|
||||
|
||||
const menuContentClass = [
|
||||
menuContentSurfaceClass,
|
||||
'transition-[opacity,scale,translate,filter] duration-150',
|
||||
'transition-[opacity,scale,translate,filter] duration-150 ease-in-out',
|
||||
menuContentPlacementClass,
|
||||
].join(' ');
|
||||
|
||||
@@ -70,19 +70,19 @@ const menuNavPopupClass = [
|
||||
'group relative',
|
||||
menuNavSurfaceClass,
|
||||
'w-(--media-menu-width) h-(--media-menu-height)',
|
||||
'transition-[opacity,scale,translate,filter,width,height] duration-300 ease-in-out',
|
||||
'transition-[opacity,scale,translate,filter,width,height] duration-150 ease-in-out',
|
||||
menuContentPlacementClass,
|
||||
].join(' ');
|
||||
|
||||
// ── Indicators ────────────────────────────────────────────────────────────────
|
||||
|
||||
function RadioDot() {
|
||||
return <span className="absolute left-[0.5625rem] top-1/2 -translate-y-1/2 w-2 h-2 rounded-full bg-current" />;
|
||||
return <span className="absolute left-2.25 top-1/2 -translate-y-1/2 w-2 h-2 rounded-full bg-current" />;
|
||||
}
|
||||
|
||||
function Checkmark() {
|
||||
return (
|
||||
<span className="absolute left-[0.4375rem] top-1/2 -translate-y-1/2 w-3 h-3 flex items-center justify-center">
|
||||
<span className="absolute left-1.75 top-1/2 -translate-y-1/2 w-3 h-3 flex items-center justify-center">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -174,13 +174,13 @@ function App() {
|
||||
|
||||
<div className="flex gap-4 flex-wrap justify-center">
|
||||
{/* Radio group */}
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-6 flex flex-col items-start gap-3.5 min-w-[200px] shadow-[0_1px_3px_0_rgb(0_0_0/.05)]">
|
||||
<div className="bg-white ring-1 ring-slate-700/10 rounded-xl p-6 flex flex-col items-start gap-3.5 min-w-[200px] shadow-sm">
|
||||
<span className="text-xs font-medium text-slate-500 uppercase tracking-widest">Radio group</span>
|
||||
<Menu.Root>
|
||||
<TriggerButton>Quality</TriggerButton>
|
||||
<Menu.Content className={menuContentClass}>
|
||||
<Menu.Label className={menuLabelClass}>Resolution</Menu.Label>
|
||||
<Menu.RadioGroup value={quality} onValueChange={setQuality}>
|
||||
<Menu.Label className={menuLabelClass}>Resolution</Menu.Label>
|
||||
{['auto', '1080p', '720p', '480p'].map((value) => (
|
||||
<Menu.RadioItem key={value} value={value} className={radioItemClass}>
|
||||
{quality === value && <RadioDot />}
|
||||
@@ -199,20 +199,22 @@ function App() {
|
||||
</div>
|
||||
|
||||
{/* Mixed items */}
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-6 flex flex-col items-start gap-3.5 min-w-[200px] shadow-[0_1px_3px_0_rgb(0_0_0/.05)]">
|
||||
<div className="bg-white ring-1 ring-slate-700/10 rounded-xl p-6 flex flex-col items-start gap-3.5 min-w-[200px] shadow-sm">
|
||||
<span className="text-xs font-medium text-slate-500 uppercase tracking-widest">Mixed items</span>
|
||||
<Menu.Root>
|
||||
<TriggerButton>Settings</TriggerButton>
|
||||
<Menu.Content className={menuContentClass}>
|
||||
<Menu.Label className={menuLabelClass}>Playback</Menu.Label>
|
||||
<Menu.CheckboxItem checked={loop} onCheckedChange={setLoop} className={checkboxItemClass}>
|
||||
{loop && <Checkmark />}
|
||||
Loop
|
||||
</Menu.CheckboxItem>
|
||||
<Menu.CheckboxItem checked={autoplay} onCheckedChange={setAutoplay} className={checkboxItemClass}>
|
||||
{autoplay && <Checkmark />}
|
||||
Autoplay
|
||||
</Menu.CheckboxItem>
|
||||
<Menu.Group>
|
||||
<Menu.Label className={menuLabelClass}>Playback</Menu.Label>
|
||||
<Menu.CheckboxItem checked={loop} onCheckedChange={setLoop} className={checkboxItemClass}>
|
||||
{loop && <Checkmark />}
|
||||
Loop
|
||||
</Menu.CheckboxItem>
|
||||
<Menu.CheckboxItem checked={autoplay} onCheckedChange={setAutoplay} className={checkboxItemClass}>
|
||||
{autoplay && <Checkmark />}
|
||||
Autoplay
|
||||
</Menu.CheckboxItem>
|
||||
</Menu.Group>
|
||||
<Menu.Separator className={menuSeparatorClass} />
|
||||
<Menu.Item onSelect={() => console.log('copy link')} className={menuItemClass}>
|
||||
Copy link
|
||||
@@ -228,7 +230,7 @@ function App() {
|
||||
</div>
|
||||
|
||||
{/* Submenu navigation */}
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-6 flex flex-col items-start gap-3.5 min-w-[200px] shadow-[0_1px_3px_0_rgb(0_0_0/.05)]">
|
||||
<div className="bg-white ring-1 ring-slate-700/10 rounded-xl p-6 flex flex-col items-start gap-3.5 min-w-[200px] shadow-sm">
|
||||
<span className="text-xs font-medium text-slate-500 uppercase tracking-widest">Submenu</span>
|
||||
<Menu.Root>
|
||||
<TriggerButton>Settings</TriggerButton>
|
||||
@@ -248,7 +250,7 @@ function App() {
|
||||
<ChevronLeft />
|
||||
Quality
|
||||
</Menu.Back>
|
||||
<Menu.RadioGroup value={quality} onValueChange={setQuality}>
|
||||
<Menu.RadioGroup aria-label="Resolution" value={quality} onValueChange={setQuality}>
|
||||
{['auto', '1080p', '720p', '480p'].map((v) => (
|
||||
<Menu.RadioItem key={v} value={v} className={radioItemClass}>
|
||||
{quality === v && <RadioDot />}
|
||||
@@ -273,7 +275,7 @@ function App() {
|
||||
<ChevronLeft />
|
||||
Speed
|
||||
</Menu.Back>
|
||||
<Menu.RadioGroup value={speed} onValueChange={setSpeed}>
|
||||
<Menu.RadioGroup aria-label="Speed" value={speed} onValueChange={setSpeed}>
|
||||
{[
|
||||
{ value: '0.5', label: '0.5x' },
|
||||
{ value: '0.75', label: '0.75x' },
|
||||
|
||||
@@ -83,7 +83,7 @@ export class MenuCore {
|
||||
getTriggerAttrs(state: MenuState, contentId?: string) {
|
||||
return {
|
||||
'aria-haspopup': 'menu' as const,
|
||||
'aria-expanded': state.open ? 'true' : 'false',
|
||||
'aria-expanded': state.open && state.status !== 'ending' ? 'true' : 'false',
|
||||
'aria-controls': contentId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -114,6 +114,16 @@ describe('MenuCore', () => {
|
||||
expect(attrs['aria-expanded']).toBe('true');
|
||||
});
|
||||
|
||||
it('returns aria-expanded false when closing', () => {
|
||||
const core = new MenuCore();
|
||||
core.setInput(createInput({ active: true, status: 'ending' }));
|
||||
const state = core.getState();
|
||||
const attrs = core.getTriggerAttrs(state);
|
||||
|
||||
expect(state.open).toBe(true);
|
||||
expect(attrs['aria-expanded']).toBe('false');
|
||||
});
|
||||
|
||||
it('sets aria-controls when contentId is provided', () => {
|
||||
const core = new MenuCore();
|
||||
core.setInput(createInput());
|
||||
|
||||
@@ -94,7 +94,7 @@ export class PopoverCore {
|
||||
|
||||
getTriggerAttrs(state: PopoverState, popupId?: string) {
|
||||
return {
|
||||
'aria-expanded': state.open ? 'true' : 'false',
|
||||
'aria-expanded': state.open && state.status !== 'ending' ? 'true' : 'false',
|
||||
'aria-haspopup': 'dialog',
|
||||
'aria-controls': popupId,
|
||||
};
|
||||
|
||||
@@ -69,6 +69,16 @@ describe('PopoverCore', () => {
|
||||
expect(attrs['aria-expanded']).toBe('true');
|
||||
});
|
||||
|
||||
it('returns aria-expanded false when closing', () => {
|
||||
const core = new PopoverCore();
|
||||
core.setInput({ active: true, status: 'ending' });
|
||||
const state = core.getState();
|
||||
const attrs = core.getTriggerAttrs(state);
|
||||
|
||||
expect(state.open).toBe(true);
|
||||
expect(attrs['aria-expanded']).toBe('false');
|
||||
});
|
||||
|
||||
it('includes aria-controls when popupId is provided', () => {
|
||||
const core = new PopoverCore();
|
||||
core.setInput(OPEN);
|
||||
|
||||
Reference in New Issue
Block a user