mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
fix(packages): scope menu data attributes (#1628)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { applyElementProps, applyStateDataAttrs } from '@videojs/core/dom';
|
||||
import { applyElementProps } from '@videojs/core/dom';
|
||||
import type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';
|
||||
import { ContextConsumer } from '@videojs/element/context';
|
||||
|
||||
@@ -57,7 +57,5 @@ export class MenuBackElement extends MediaElement {
|
||||
role: 'button',
|
||||
'aria-label': this.label,
|
||||
});
|
||||
|
||||
if (ctx) applyStateDataAttrs(this, ctx.state, ctx.stateAttrMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { applyElementProps, applyStateDataAttrs } from '@videojs/core/dom';
|
||||
import { applyElementProps } from '@videojs/core/dom';
|
||||
import type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';
|
||||
import { ContextConsumer } from '@videojs/element/context';
|
||||
|
||||
@@ -72,7 +72,5 @@ export class MenuCheckboxItemElement extends MediaElement {
|
||||
'aria-checked': String(this.checked),
|
||||
'aria-disabled': this.disabled ? 'true' : undefined,
|
||||
});
|
||||
|
||||
applyStateDataAttrs(this, ctx.state, ctx.stateAttrMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,6 +282,9 @@ export class MenuElement extends MediaElement {
|
||||
// Apply base submenu attributes regardless of phase.
|
||||
const transitionState = this.#menuViewTransition.input.current;
|
||||
|
||||
this.removeAttribute(MenuDataAttrs.side);
|
||||
this.removeAttribute(MenuDataAttrs.align);
|
||||
|
||||
applyElementProps(this, {
|
||||
...getMenuViewTransitionAttrs(transitionState),
|
||||
role: 'menu',
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { applyElementProps, applyStateDataAttrs } from '@videojs/core/dom';
|
||||
import { applyElementProps } from '@videojs/core/dom';
|
||||
import type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';
|
||||
import { ContextConsumer } from '@videojs/element/context';
|
||||
|
||||
import { MediaElement } from '../media-element';
|
||||
import { menuContext } from './context';
|
||||
|
||||
export class MenuGroupElement extends MediaElement {
|
||||
static readonly tagName = 'media-menu-group';
|
||||
@@ -14,8 +12,6 @@ export class MenuGroupElement extends MediaElement {
|
||||
|
||||
label: string | undefined = undefined;
|
||||
|
||||
readonly #ctx = new ContextConsumer(this, { context: menuContext, subscribe: true });
|
||||
|
||||
protected override update(_changed: PropertyValues): void {
|
||||
super.update(_changed);
|
||||
|
||||
@@ -23,8 +19,5 @@ export class MenuGroupElement extends MediaElement {
|
||||
role: 'group',
|
||||
'aria-label': this.label,
|
||||
});
|
||||
|
||||
const ctx = this.#ctx.value;
|
||||
if (ctx) applyStateDataAttrs(this, ctx.state, ctx.stateAttrMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { applyElementProps, applyStateDataAttrs, completeMenuItemSelection } from '@videojs/core/dom';
|
||||
import { applyElementProps, completeMenuItemSelection } from '@videojs/core/dom';
|
||||
import type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';
|
||||
import { ContextConsumer } from '@videojs/element/context';
|
||||
|
||||
@@ -100,7 +100,5 @@ export class MenuItemElement extends MediaElement {
|
||||
'data-has-submenu': '',
|
||||
}),
|
||||
});
|
||||
|
||||
applyStateDataAttrs(this, ctx.state, ctx.stateAttrMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import type { MenuState } from '@videojs/core';
|
||||
import { ContextConsumer } from '@videojs/element/context';
|
||||
import { MediaElement } from '../media-element';
|
||||
|
||||
import { ContextPartElement } from '../context-part-element';
|
||||
import { menuContext } from './context';
|
||||
|
||||
export class MenuLabelElement extends ContextPartElement<MenuState> {
|
||||
export class MenuLabelElement extends MediaElement {
|
||||
static readonly tagName = 'media-menu-label';
|
||||
|
||||
protected readonly consumer = new ContextConsumer(this, { context: menuContext, subscribe: true });
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { applyElementProps, applyStateDataAttrs } from '@videojs/core/dom';
|
||||
import { applyElementProps } from '@videojs/core/dom';
|
||||
import type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';
|
||||
import { ContextConsumer, ContextProvider } from '@videojs/element/context';
|
||||
import { ContextProvider } from '@videojs/element/context';
|
||||
|
||||
import { MediaElement } from '../media-element';
|
||||
import { menuContext, menuRadioGroupContext } from './context';
|
||||
import { menuRadioGroupContext } from './context';
|
||||
|
||||
export class MenuRadioGroupElement extends MediaElement {
|
||||
static readonly tagName: string = 'media-menu-radio-group';
|
||||
@@ -16,7 +16,6 @@ export class MenuRadioGroupElement extends MediaElement {
|
||||
value = '';
|
||||
label: string | undefined = undefined;
|
||||
|
||||
readonly #menuCtx = new ContextConsumer(this, { context: menuContext, subscribe: true });
|
||||
readonly #provider = new ContextProvider(this, { context: menuRadioGroupContext });
|
||||
|
||||
protected override update(_changed: PropertyValues): void {
|
||||
@@ -26,10 +25,6 @@ export class MenuRadioGroupElement extends MediaElement {
|
||||
role: 'group',
|
||||
'aria-label': this.label,
|
||||
});
|
||||
|
||||
const ctx = this.#menuCtx.value;
|
||||
if (ctx) applyStateDataAttrs(this, ctx.state, ctx.stateAttrMap);
|
||||
|
||||
this.#provider.setValue({
|
||||
value: this.value,
|
||||
onValueChange: (next: string) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { applyElementProps, applyStateDataAttrs, completeMenuItemSelection } from '@videojs/core/dom';
|
||||
import { applyElementProps, completeMenuItemSelection } from '@videojs/core/dom';
|
||||
import type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';
|
||||
import { ContextConsumer } from '@videojs/element/context';
|
||||
|
||||
@@ -77,7 +77,5 @@ export class MenuRadioItemElement extends MediaElement {
|
||||
'aria-checked': String(checked),
|
||||
'aria-disabled': this.disabled ? 'true' : undefined,
|
||||
});
|
||||
|
||||
applyStateDataAttrs(this, menuCtx.state, menuCtx.stateAttrMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
import { applyElementProps, applyStateDataAttrs } from '@videojs/core/dom';
|
||||
import { applyElementProps } from '@videojs/core/dom';
|
||||
import type { PropertyValues } from '@videojs/element';
|
||||
import { ContextConsumer } from '@videojs/element/context';
|
||||
|
||||
import { MediaElement } from '../media-element';
|
||||
import { menuContext } from './context';
|
||||
|
||||
export class MenuSeparatorElement extends MediaElement {
|
||||
static readonly tagName = 'media-menu-separator';
|
||||
|
||||
readonly #ctx = new ContextConsumer(this, { context: menuContext, subscribe: true });
|
||||
|
||||
protected override update(_changed: PropertyValues): void {
|
||||
super.update(_changed);
|
||||
|
||||
applyElementProps(this, { role: 'separator' });
|
||||
|
||||
const ctx = this.#ctx.value;
|
||||
if (ctx) applyStateDataAttrs(this, ctx.state, ctx.stateAttrMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,16 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { playerContext } from '../../../player/context';
|
||||
import { ControlsElement } from '../../controls/controls-element';
|
||||
import { MediaElement } from '../../media-element';
|
||||
import { MenuBackElement } from '../menu-back-element';
|
||||
import { MenuCheckboxItemElement } from '../menu-checkbox-item-element';
|
||||
import { MenuElement } from '../menu-element';
|
||||
import { MenuGroupElement } from '../menu-group-element';
|
||||
import { MenuItemElement } from '../menu-item-element';
|
||||
import { MenuItemIndicatorElement } from '../menu-item-indicator-element';
|
||||
import { MenuLabelElement } from '../menu-label-element';
|
||||
import { MenuRadioGroupElement } from '../menu-radio-group-element';
|
||||
import { MenuRadioItemElement } from '../menu-radio-item-element';
|
||||
import { MenuSeparatorElement } from '../menu-separator-element';
|
||||
import { MenuViewElement } from '../menu-view-element';
|
||||
|
||||
let tagCounter = 0;
|
||||
@@ -91,11 +98,108 @@ async function waitForAssertion(assertion: () => void): Promise<void> {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const menuStateAttrs = ['data-open', 'data-side', 'data-align', 'data-starting-style', 'data-ending-style'] as const;
|
||||
|
||||
function expectNoMenuStateAttrs(element: HTMLElement): void {
|
||||
for (const attr of menuStateAttrs) {
|
||||
expect(element.hasAttribute(attr), `${element.localName} should not have ${attr}`).toBe(false);
|
||||
}
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
describe('MenuElement', () => {
|
||||
it('scopes menu state data attributes to menu elements', async () => {
|
||||
const root = createElement(MenuElement);
|
||||
const label = createElement(MenuLabelElement);
|
||||
const group = createElement(MenuGroupElement);
|
||||
const item = createElement(MenuItemElement);
|
||||
const checkboxItem = createElement(MenuCheckboxItemElement);
|
||||
const radioGroup = createElement(MenuRadioGroupElement);
|
||||
const radioItem = createElement(MenuRadioItemElement);
|
||||
const indicator = createElement(MenuItemIndicatorElement);
|
||||
const separator = createElement(MenuSeparatorElement);
|
||||
const rootView = createElement(MenuViewElement);
|
||||
const trigger = createElement(MenuItemElement);
|
||||
const child = createElement(MenuElement);
|
||||
const back = createElement(MenuBackElement);
|
||||
const childItem = createElement(MenuItemElement);
|
||||
|
||||
root.open = true;
|
||||
root.side = 'top';
|
||||
root.align = 'end';
|
||||
label.textContent = 'Playback';
|
||||
group.label = 'Playback';
|
||||
item.textContent = 'Copy link';
|
||||
checkboxItem.textContent = 'Autoplay';
|
||||
radioGroup.label = 'Quality';
|
||||
radioGroup.value = 'auto';
|
||||
radioItem.value = 'auto';
|
||||
radioItem.textContent = 'Auto';
|
||||
indicator.checked = true;
|
||||
trigger.id = 'child-trigger';
|
||||
trigger.commandfor = 'child-menu';
|
||||
trigger.textContent = 'Quality';
|
||||
child.id = 'child-menu';
|
||||
back.textContent = 'Back';
|
||||
childItem.textContent = 'Auto';
|
||||
|
||||
radioItem.append(indicator);
|
||||
radioGroup.append(radioItem);
|
||||
group.append(item, checkboxItem, radioGroup);
|
||||
rootView.append(trigger);
|
||||
child.append(back, childItem);
|
||||
root.append(label, group, separator, rootView, child);
|
||||
document.body.append(root);
|
||||
|
||||
await root.updateComplete;
|
||||
await label.updateComplete;
|
||||
await group.updateComplete;
|
||||
await item.updateComplete;
|
||||
await checkboxItem.updateComplete;
|
||||
await radioGroup.updateComplete;
|
||||
await radioItem.updateComplete;
|
||||
await indicator.updateComplete;
|
||||
await separator.updateComplete;
|
||||
await rootView.updateComplete;
|
||||
await trigger.updateComplete;
|
||||
await child.updateComplete;
|
||||
await back.updateComplete;
|
||||
await childItem.updateComplete;
|
||||
|
||||
expect(root.hasAttribute('data-open')).toBe(true);
|
||||
expect(root.getAttribute('data-side')).toBe('top');
|
||||
expect(root.getAttribute('data-align')).toBe('end');
|
||||
|
||||
for (const element of [label, group, separator, item, checkboxItem, radioGroup, radioItem, indicator, trigger]) {
|
||||
expectNoMenuStateAttrs(element);
|
||||
}
|
||||
|
||||
expect(item.hasAttribute('data-item')).toBe(true);
|
||||
expect(checkboxItem.hasAttribute('data-item')).toBe(true);
|
||||
expect(radioItem.hasAttribute('data-item')).toBe(true);
|
||||
expect(trigger.hasAttribute('data-item')).toBe(true);
|
||||
|
||||
trigger.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
||||
|
||||
await root.updateComplete;
|
||||
await child.updateComplete;
|
||||
await back.updateComplete;
|
||||
await waitForAssertion(() => {
|
||||
expect(child.getAttribute('data-menu-view-state')).toBe('active');
|
||||
});
|
||||
|
||||
expect(child.hasAttribute('data-submenu')).toBe(true);
|
||||
expect(child.hasAttribute('data-menu-view')).toBe(true);
|
||||
expect(child.hasAttribute('data-open')).toBe(true);
|
||||
expect(child.hasAttribute('data-side')).toBe(false);
|
||||
expect(child.hasAttribute('data-align')).toBe(false);
|
||||
expectNoMenuStateAttrs(back);
|
||||
expectNoMenuStateAttrs(childItem);
|
||||
});
|
||||
|
||||
it('marks root and nested menu views with generic view attributes', async () => {
|
||||
const root = createElement(MenuElement);
|
||||
const rootView = createElement(MenuViewElement);
|
||||
|
||||
Reference in New Issue
Block a user