feat(core): add mute button component (#455)

This commit is contained in:
rahim
2026-02-04 23:10:10 +11:00
committed by GitHub
parent d8f15195e3
commit aa189eec84
18 changed files with 541 additions and 24 deletions
+1
View File
@@ -1,3 +1,4 @@
export { applyElementProps } from './element-props';
export { logMissingFeature } from './log';
export type { StateAttrMap } from './state-data-attrs';
export { applyStateDataAttrs, getStateDataAttrs } from './state-data-attrs';
+28 -10
View File
@@ -1,3 +1,7 @@
export type StateAttrMap<State> = {
[Key in keyof State]?: string;
};
/**
* Convert state object to data attributes.
*
@@ -11,17 +15,23 @@
* getStateDataAttrs(state);
* // { 'data-paused': '', 'data-volume': '0.5' }
* ```
*
* When a mapping is provided, only mapped keys are converted.
*/
export function getStateDataAttrs<State extends object>(state: State): Record<string, string> {
export function getStateDataAttrs<State extends object>(
state: State,
map?: StateAttrMap<State>
): Record<string, string> {
const attrs: Record<string, string> = {};
for (const key in state) {
const value = state[key];
const name = map?.[key] ?? toDataAttrName(key),
value = state[key];
if (value === true) {
attrs[`data-${key.toLowerCase()}`] = '';
attrs[name] = '';
} else if (value) {
attrs[`data-${key.toLowerCase()}`] = String(value);
attrs[name] = String(value);
}
}
@@ -42,17 +52,25 @@ export function getStateDataAttrs<State extends object>(state: State): Record<st
* // element has data-paused="", data-ended is removed
* ```
*/
export function applyStateDataAttrs<State extends object>(element: HTMLElement, state: State): void {
export function applyStateDataAttrs<State extends object>(
element: HTMLElement,
state: State,
map?: StateAttrMap<State>
): void {
for (const key in state) {
const value = state[key];
const attrName = `data-${key.toLowerCase()}`;
const name = map?.[key] ?? toDataAttrName(key),
value = state[key];
if (value === true) {
element.setAttribute(attrName, '');
element.setAttribute(name, '');
} else if (value) {
element.setAttribute(attrName, String(value));
element.setAttribute(name, String(value));
} else {
element.removeAttribute(attrName);
element.removeAttribute(name);
}
}
}
function toDataAttrName(key: string): string {
return `data-${key.toLowerCase()}`;
}
@@ -64,6 +64,29 @@ describe('getStateDataAttrs', () => {
const state = { label: '' };
expect(getStateDataAttrs(state)).toEqual({});
});
it('supports explicit attribute mapping', () => {
const state = { muted: true, volumeLevel: 'low' };
const mapping = {
muted: 'data-muted',
volumeLevel: 'data-volume-level',
};
expect(getStateDataAttrs(state, mapping)).toEqual({
'data-muted': '',
'data-volume-level': 'low',
});
});
it('allows unmapped keys when mapping is provided', () => {
const state = { muted: true, volumeLevel: 'low' };
const mapping = { muted: 'data-muted' };
expect(getStateDataAttrs(state, mapping)).toEqual({
'data-muted': '',
'data-volumelevel': 'low',
});
});
});
describe('applyStateDataAttrs', () => {
@@ -134,4 +157,17 @@ describe('applyStateDataAttrs', () => {
expect(element.hasAttribute('data-ended')).toBe(true);
expect(element.hasAttribute('data-volume')).toBe(false);
});
it('removes mapped attributes when values become falsy', () => {
const element = document.createElement('div');
const mapping = { muted: 'data-muted', volumeLevel: 'data-volume-level' };
applyStateDataAttrs(element, { muted: true, volumeLevel: 'high' }, mapping);
expect(element.getAttribute('data-muted')).toBe('');
expect(element.getAttribute('data-volume-level')).toBe('high');
applyStateDataAttrs(element, { muted: false, volumeLevel: '' }, mapping);
expect(element.hasAttribute('data-muted')).toBe(false);
expect(element.hasAttribute('data-volume-level')).toBe(false);
});
});