feat(core): add popover component (#615)

This commit is contained in:
rahim
2026-02-28 00:33:30 -08:00
committed by GitHub
parent c92a9f914f
commit 44188d4823
20 changed files with 1663 additions and 2 deletions
+7 -1
View File
@@ -7,7 +7,13 @@ export { listen } from './listen';
export { isHTMLAudioElement, isHTMLMediaElement, isHTMLVideoElement } from './predicates';
export { type RafThrottled, rafThrottle } from './raf-throttle';
export { getSlottedElement, querySlot } from './slotted';
export { supportsAnimationFrame, supportsIdleCallback } from './supports';
export { applyStyles } from './style';
export {
supportsAnchorPositioning,
supportsAnimationFrame,
supportsIdleCallback,
supportsPopoverAPI,
} from './supports';
export { findTrackElement } from './text-track';
export { serializeTimeRanges } from './time-ranges';
export type { CustomElement, CustomElementCallbacks } from './types';
+11
View File
@@ -0,0 +1,11 @@
import { kebabCase } from '../string/casing';
export function applyStyles(element: HTMLElement, styles: Record<string, string | undefined>): void {
for (const [prop, value] of Object.entries(styles)) {
if (typeof value === 'string') {
// CSS custom properties (--*) are already in the correct format.
const key = prop.startsWith('--') ? prop : kebabCase(prop);
element.style.setProperty(key, value);
}
}
}
+8
View File
@@ -5,3 +5,11 @@ export function supportsIdleCallback(): boolean {
export function supportsAnimationFrame(): boolean {
return typeof requestAnimationFrame === 'function';
}
export function supportsPopoverAPI(): boolean {
return typeof HTMLElement !== 'undefined' && 'popover' in HTMLElement.prototype;
}
export function supportsAnchorPositioning(): boolean {
return typeof CSS !== 'undefined' && CSS.supports('anchor-name: --a');
}
+4
View File
@@ -5,3 +5,7 @@ export function pascalCase(str: string): string {
export function camelCase(str: string): string {
return pascalCase(str).replace(/^(.)/, (_, c) => c.toLowerCase());
}
export function kebabCase(str: string): string {
return str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
}
+19 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { camelCase, pascalCase } from '../casing';
import { camelCase, kebabCase, pascalCase } from '../casing';
describe('casing', () => {
describe('pascalCase', () => {
@@ -41,4 +41,22 @@ describe('casing', () => {
expect(camelCase('hello_world')).toBe('helloWorld');
});
});
describe('kebabCase', () => {
it('converts camelCase', () => {
expect(kebabCase('positionAnchor')).toBe('position-anchor');
});
it('converts PascalCase', () => {
expect(kebabCase('PositionAnchor')).toBe('-position-anchor');
});
it('preserves lowercase', () => {
expect(kebabCase('margin')).toBe('margin');
});
it('does not special-case CSS custom properties', () => {
expect(kebabCase('--media-popover-offset')).toBe('--media-popover-offset');
});
});
});