mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat: use anchor API for html elements (#174)
This commit is contained in:
@@ -35,9 +35,9 @@
|
||||
>
|
||||
<video
|
||||
slot="media"
|
||||
src="https://stream.mux.com/A3VXy02VoUinw01pwyomEO3bHnG4P32xzV7u1j1FSzjNg/high.mp4"
|
||||
src="https://stream.mux.com/fXNzVtmtWuyz00xnSrJg4OJH6PyNo6D02UzmgeKGkP5YQ/high.mp4"
|
||||
playsinline
|
||||
poster="https://image.mux.com/A3VXy02VoUinw01pwyomEO3bHnG4P32xzV7u1j1FSzjNg/thumbnail.webp"
|
||||
poster="https://image.mux.com/fXNzVtmtWuyz00xnSrJg4OJH6PyNo6D02UzmgeKGkP5YQ/thumbnail.webp"
|
||||
></video>
|
||||
|
||||
<div class="overlay"></div>
|
||||
|
||||
@@ -35,9 +35,9 @@
|
||||
>
|
||||
<video
|
||||
slot="media"
|
||||
src="https://stream.mux.com/A3VXy02VoUinw01pwyomEO3bHnG4P32xzV7u1j1FSzjNg/high.mp4"
|
||||
src="https://stream.mux.com/fXNzVtmtWuyz00xnSrJg4OJH6PyNo6D02UzmgeKGkP5YQ/high.mp4"
|
||||
playsinline
|
||||
poster="https://image.mux.com/A3VXy02VoUinw01pwyomEO3bHnG4P32xzV7u1j1FSzjNg/thumbnail.webp"
|
||||
poster="https://image.mux.com/fXNzVtmtWuyz00xnSrJg4OJH6PyNo6D02UzmgeKGkP5YQ/thumbnail.webp"
|
||||
></video>
|
||||
|
||||
<div class="overlay"></div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import './frosted-eject.css';
|
||||
import '@videojs/html/icons';
|
||||
// be sure to import video-provider first
|
||||
// be sure to import video-provider first for proper context initialization
|
||||
import '@videojs/html/define/video-provider';
|
||||
import '@videojs/html/define/media-container';
|
||||
import '@videojs/html/define/media-play-button';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import './minimal-eject.css';
|
||||
import '@videojs/html/icons';
|
||||
// be sure to import video-provider first
|
||||
// be sure to import video-provider first for proper context initialization
|
||||
import '@videojs/html/define/video-provider';
|
||||
import '@videojs/html/define/media-container';
|
||||
import '@videojs/html/define/media-play-button';
|
||||
|
||||
@@ -52,8 +52,6 @@
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.6.13",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@open-wc/context-protocol": "^0.0.9",
|
||||
"@videojs/core": "workspace:*",
|
||||
"@videojs/icons": "workspace:*",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import './media-container';
|
||||
// be sure to import video-provider first for proper context initialization
|
||||
import './video-provider';
|
||||
import './media-container';
|
||||
import './media-play-button';
|
||||
import './media-mute-button';
|
||||
import './media-volume-slider';
|
||||
|
||||
@@ -27,6 +27,8 @@ export class ButtonElement extends HTMLElement {
|
||||
|
||||
static getTemplateHTML: typeof getTemplateHTML = getTemplateHTML;
|
||||
|
||||
static observedAttributes: string[] = ['commandfor'];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@@ -46,6 +48,12 @@ export class ButtonElement extends HTMLElement {
|
||||
this.addEventListener('keydown', this);
|
||||
}
|
||||
|
||||
attributeChangedCallback(name: string, _oldValue: string, newValue: string): void {
|
||||
if (name === 'commandfor') {
|
||||
this.style.setProperty('anchor-name', `--${newValue}`);
|
||||
}
|
||||
}
|
||||
|
||||
handleEvent(event: Event): void {
|
||||
const { type } = event;
|
||||
if (type === 'keydown') {
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
import type { ComputePositionReturn } from '@floating-ui/core';
|
||||
import type { Placement } from '@floating-ui/dom';
|
||||
import { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';
|
||||
import { contains, getDocument, getDocumentOrShadowRoot, safePolygon } from '@videojs/utils/dom';
|
||||
|
||||
type Prettify<T> = {
|
||||
[K in keyof T]: T[K];
|
||||
};
|
||||
|
||||
type FloatingContext = Prettify<ComputePositionReturn> & {
|
||||
elements: {
|
||||
domReference: HTMLElement;
|
||||
floating: HTMLElement;
|
||||
};
|
||||
};
|
||||
type Placement = 'top' | 'bottom' | 'left' | 'right';
|
||||
|
||||
export class PopoverElement extends HTMLElement {
|
||||
static get observedAttributes(): string[] {
|
||||
return ['id', 'open-on-hover', 'delay', 'close-delay', 'side', 'side-offset'];
|
||||
}
|
||||
|
||||
#open = false;
|
||||
#transitionStatus: 'initial' | 'open' | 'close' | 'unmounted' = 'initial';
|
||||
#hoverTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
#cleanup: (() => void) | null = null;
|
||||
#abortController: AbortController | null = null;
|
||||
#floatingContext: FloatingContext | null = null;
|
||||
|
||||
attributeChangedCallback(name: string, _oldValue: string, newValue: string): void {
|
||||
if (name === 'id') {
|
||||
this.style.setProperty('position-anchor', `--${newValue}`);
|
||||
}
|
||||
|
||||
this.style.setProperty('top', `calc(anchor(${this.side}) - ${this.sideOffset}px)`);
|
||||
this.style.setProperty('translate', `0 -100%`);
|
||||
this.style.setProperty('justify-self', 'anchor-center');
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
this.#abortController ??= new AbortController();
|
||||
@@ -71,10 +72,6 @@ export class PopoverElement extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
static get observedAttributes(): string[] {
|
||||
return ['open-on-hover', 'delay', 'close-delay', 'side', 'side-offset'];
|
||||
}
|
||||
|
||||
get openOnHover(): boolean {
|
||||
return this.hasAttribute('open-on-hover');
|
||||
}
|
||||
@@ -105,8 +102,6 @@ export class PopoverElement extends HTMLElement {
|
||||
this.#open = open;
|
||||
|
||||
if (open) {
|
||||
this.#setupFloating();
|
||||
|
||||
this.#transitionStatus = 'initial';
|
||||
this.#updateVisibility();
|
||||
|
||||
@@ -141,38 +136,6 @@ export class PopoverElement extends HTMLElement {
|
||||
this.toggleAttribute('data-closed', this.#transitionStatus === 'close' || this.#transitionStatus === 'unmounted');
|
||||
}
|
||||
|
||||
#setupFloating(): void {
|
||||
const trigger = this.#triggerElement as HTMLElement;
|
||||
if (!trigger) return;
|
||||
|
||||
const placement = this.side ?? 'top';
|
||||
const sideOffset = this.sideOffset;
|
||||
|
||||
const updatePosition = () => {
|
||||
computePosition(trigger, this, {
|
||||
placement,
|
||||
middleware: [offset(sideOffset), flip(), shift()],
|
||||
strategy: 'fixed',
|
||||
}).then((data: ComputePositionReturn) => {
|
||||
this.#floatingContext = {
|
||||
...data,
|
||||
elements: {
|
||||
domReference: trigger,
|
||||
floating: this,
|
||||
},
|
||||
};
|
||||
|
||||
Object.assign(this.style, {
|
||||
left: `${data.x}px`,
|
||||
top: `${data.y}px`,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
updatePosition();
|
||||
this.#cleanup = autoUpdate(trigger, this, updatePosition);
|
||||
}
|
||||
|
||||
#clearHoverTimeout(): void {
|
||||
if (this.#hoverTimeout) {
|
||||
globalThis.clearTimeout(this.#hoverTimeout);
|
||||
@@ -210,10 +173,14 @@ export class PopoverElement extends HTMLElement {
|
||||
}
|
||||
|
||||
#handlePointerMove(event: PointerEvent): void {
|
||||
if (!this.openOnHover || !this.#floatingContext) return;
|
||||
if (!this.openOnHover || !this.#triggerElement) return;
|
||||
|
||||
const close = safePolygon({ blockPointerEvents: true })({
|
||||
...this.#floatingContext,
|
||||
placement: this.side,
|
||||
elements: {
|
||||
domReference: this.#triggerElement,
|
||||
floating: this,
|
||||
},
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
onClose: () => {
|
||||
|
||||
@@ -50,7 +50,7 @@ export const getTimeSliderRootProps: PropsHook<{
|
||||
};
|
||||
|
||||
export class TimeSliderRoot extends HTMLElement {
|
||||
static readonly observedAttributes: readonly string[] = ['orientation'];
|
||||
static readonly observedAttributes: readonly string[] = ['commandfor', 'orientation'];
|
||||
|
||||
_state: TimeSliderRootState | undefined;
|
||||
_core: CoreTimeSlider | null = null;
|
||||
@@ -67,9 +67,11 @@ export class TimeSliderRoot extends HTMLElement {
|
||||
return (this.getAttribute('orientation') as 'horizontal' | 'vertical') || 'horizontal';
|
||||
}
|
||||
|
||||
attributeChangedCallback(name: string, _oldValue: string | null, _newValue: string | null): void {
|
||||
attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void {
|
||||
if (name === 'orientation' && this._state) {
|
||||
this._render(getTimeSliderRootProps(this._state, this), this._state);
|
||||
} else if (name === 'commandfor') {
|
||||
this.style.setProperty('anchor-name', `--${newValue}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,46 @@
|
||||
import type { Placement } from '@floating-ui/dom';
|
||||
import type { MediaContainerElement } from '@/media/media-container';
|
||||
|
||||
import { arrow, autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';
|
||||
import { getDocumentOrShadowRoot } from '@videojs/utils/dom';
|
||||
import {
|
||||
getBoundingClientRectWithoutTransform,
|
||||
getDocumentOrShadowRoot,
|
||||
getInBoundsAdjustments,
|
||||
} from '@videojs/utils/dom';
|
||||
|
||||
type Placement = 'top' | 'bottom' | 'left' | 'right';
|
||||
|
||||
export class TooltipElement extends HTMLElement {
|
||||
static get observedAttributes(): string[] {
|
||||
return ['id', 'delay', 'close-delay', 'track-cursor-axis', 'side', 'side-offset', 'collision-padding'];
|
||||
}
|
||||
|
||||
#open = false;
|
||||
#hoverTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
#cleanup: (() => void) | null = null;
|
||||
#arrowElement: HTMLElement | null = null;
|
||||
#pointerPosition = { x: 0, y: 0 };
|
||||
#transitionStatus: 'initial' | 'open' | 'close' | 'unmounted' = 'initial';
|
||||
#abortController: AbortController | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => this.#checkCollision());
|
||||
resizeObserver.observe(this);
|
||||
}
|
||||
|
||||
attributeChangedCallback(name: string, _oldValue: string, newValue: string): void {
|
||||
if (name === 'id') {
|
||||
this.style.setProperty('position-anchor', `--${newValue}`);
|
||||
}
|
||||
|
||||
this.style.setProperty('top', `calc(anchor(${this.side}) - ${this.sideOffset}px)`);
|
||||
|
||||
if (this.trackCursorAxis) {
|
||||
this.style.setProperty('translate', `-50% -100%`);
|
||||
} else {
|
||||
this.style.setProperty('translate', `0 -100%`);
|
||||
this.style.setProperty('justify-self', 'anchor-center');
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
this.setAttribute('role', 'tooltip');
|
||||
|
||||
@@ -33,7 +61,6 @@ export class TooltipElement extends HTMLElement {
|
||||
|
||||
disconnectedCallback(): void {
|
||||
this.#clearHoverTimeout();
|
||||
this.#cleanup?.();
|
||||
this.#abortController?.abort();
|
||||
this.#abortController = null;
|
||||
|
||||
@@ -57,10 +84,6 @@ export class TooltipElement extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
static get observedAttributes(): string[] {
|
||||
return ['delay', 'close-delay', 'track-cursor-axis', 'side', 'side-offset', 'collision-padding'];
|
||||
}
|
||||
|
||||
get delay(): number {
|
||||
return Number.parseInt(this.getAttribute('delay') ?? '0', 10);
|
||||
}
|
||||
@@ -96,8 +119,6 @@ export class TooltipElement extends HTMLElement {
|
||||
this.#open = open;
|
||||
|
||||
if (open) {
|
||||
this.#setupFloating();
|
||||
|
||||
this.#transitionStatus = 'initial';
|
||||
this.#updateVisibility();
|
||||
|
||||
@@ -106,6 +127,7 @@ export class TooltipElement extends HTMLElement {
|
||||
requestAnimationFrame(() => {
|
||||
this.#transitionStatus = 'open';
|
||||
this.#updateVisibility();
|
||||
this.#checkCollision();
|
||||
});
|
||||
} else {
|
||||
this.#transitionStatus = 'close';
|
||||
@@ -119,16 +141,16 @@ export class TooltipElement extends HTMLElement {
|
||||
} else {
|
||||
this.hidePopover();
|
||||
}
|
||||
|
||||
this.#cleanup?.();
|
||||
this.#cleanup = null;
|
||||
}
|
||||
}
|
||||
|
||||
#updateVisibility(): void {
|
||||
this.toggleAttribute('data-starting-style', this.#transitionStatus === 'initial');
|
||||
this.toggleAttribute('data-open', this.#transitionStatus === 'initial' || this.#transitionStatus === 'open');
|
||||
this.toggleAttribute('data-ending-style', this.#transitionStatus === 'close' || this.#transitionStatus === 'unmounted');
|
||||
this.toggleAttribute(
|
||||
'data-ending-style',
|
||||
this.#transitionStatus === 'close' || this.#transitionStatus === 'unmounted',
|
||||
);
|
||||
this.toggleAttribute('data-closed', this.#transitionStatus === 'close' || this.#transitionStatus === 'unmounted');
|
||||
|
||||
const triggerElement = this.#triggerElement as HTMLElement;
|
||||
@@ -137,105 +159,34 @@ export class TooltipElement extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
#setupFloating(): void {
|
||||
const trigger = this.#triggerElement as HTMLElement;
|
||||
if (!trigger) return;
|
||||
|
||||
const placement = this.side;
|
||||
const sideOffset = this.sideOffset;
|
||||
const collisionPadding = this.collisionPadding;
|
||||
const mediaContainer = this.closest('media-container') as MediaContainerElement;
|
||||
|
||||
this.#arrowElement = this.querySelector('media-tooltip-arrow') as HTMLElement;
|
||||
|
||||
const updatePosition = () => {
|
||||
const middleware = [
|
||||
offset(sideOffset),
|
||||
flip(),
|
||||
shift({
|
||||
boundary: mediaContainer,
|
||||
padding: collisionPadding,
|
||||
}),
|
||||
];
|
||||
|
||||
if (this.#arrowElement) {
|
||||
middleware.push(arrow({ element: this.#arrowElement }));
|
||||
}
|
||||
|
||||
const referenceElement = this.trackCursorAxis
|
||||
? {
|
||||
getBoundingClientRect: () => {
|
||||
const triggerRect = trigger.getBoundingClientRect();
|
||||
|
||||
if (this.trackCursorAxis === 'x') {
|
||||
return {
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: triggerRect.top,
|
||||
right: this.#pointerPosition.x,
|
||||
bottom: triggerRect.bottom,
|
||||
left: this.#pointerPosition.x,
|
||||
x: this.#pointerPosition.x,
|
||||
y: triggerRect.top,
|
||||
};
|
||||
} else if (this.trackCursorAxis === 'y') {
|
||||
return {
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: this.#pointerPosition.y,
|
||||
right: triggerRect.right,
|
||||
bottom: this.#pointerPosition.y,
|
||||
left: triggerRect.left,
|
||||
x: triggerRect.left,
|
||||
y: this.#pointerPosition.y,
|
||||
};
|
||||
} else {
|
||||
// Track both axes (trackCursorAxis === 'both')
|
||||
return {
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: this.#pointerPosition.y,
|
||||
right: this.#pointerPosition.x,
|
||||
bottom: this.#pointerPosition.y,
|
||||
left: this.#pointerPosition.x,
|
||||
x: this.#pointerPosition.x,
|
||||
y: this.#pointerPosition.y,
|
||||
};
|
||||
}
|
||||
},
|
||||
}
|
||||
: trigger;
|
||||
|
||||
computePosition(referenceElement, this, {
|
||||
placement,
|
||||
middleware,
|
||||
strategy: 'fixed',
|
||||
}).then(({ x, y, middlewareData }: { x: number; y: number; middlewareData: any }) => {
|
||||
Object.assign(this.style, {
|
||||
left: `${x}px`,
|
||||
top: `${y}px`,
|
||||
});
|
||||
|
||||
if (this.#arrowElement && middlewareData.arrow) {
|
||||
const { x: arrowX, y: arrowY } = middlewareData.arrow;
|
||||
Object.assign(this.#arrowElement.style, {
|
||||
left: arrowX != null ? `${arrowX}px` : undefined,
|
||||
top: arrowY != null ? `${arrowY}px` : undefined,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
updatePosition();
|
||||
|
||||
if (!this.trackCursorAxis) {
|
||||
this.#cleanup = autoUpdate(trigger, this, updatePosition);
|
||||
#updatePosition(): void {
|
||||
if (this.#open && this.trackCursorAxis) {
|
||||
this.style.setProperty('left', `${this.#pointerPosition.x}px`);
|
||||
this.#checkCollision();
|
||||
}
|
||||
}
|
||||
|
||||
#updatePosition(): void {
|
||||
if (this.#open && this.trackCursorAxis) {
|
||||
this.#setupFloating();
|
||||
#checkCollision(): void {
|
||||
const mediaContainer = this.closest('media-container') as MediaContainerElement;
|
||||
if (!mediaContainer || !this.#open) return;
|
||||
|
||||
const popupRect = getBoundingClientRectWithoutTransform(this);
|
||||
const boundsRect = getBoundingClientRectWithoutTransform(mediaContainer);
|
||||
const { x } = getInBoundsAdjustments(popupRect, boundsRect, this.collisionPadding);
|
||||
|
||||
if (x !== 0) {
|
||||
if (this.trackCursorAxis) {
|
||||
const currentLeft = Number.parseFloat(this.style.left || '0');
|
||||
this.style.setProperty('left', `${currentLeft + x}px`);
|
||||
} else {
|
||||
this.style.setProperty('translate', `${x}px -100%`);
|
||||
}
|
||||
} else {
|
||||
if (this.trackCursorAxis) {
|
||||
this.style.setProperty('translate', '-50% -100%');
|
||||
} else {
|
||||
this.style.setProperty('translate', '0 -100%');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ export class VolumeSliderTrack extends HTMLElement {
|
||||
/**
|
||||
* VolumeSlider Progress component - Shows current progress
|
||||
*/
|
||||
export class VolumeSliderIndicatorElement extends HTMLElement {
|
||||
export class VolumeSliderIndicator extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.style.position = 'absolute';
|
||||
@@ -255,8 +255,8 @@ export const VolumeSliderTrackElement: ConnectedComponentConstructor<any> = toCo
|
||||
/**
|
||||
* Connected VolumeSlider Progress component
|
||||
*/
|
||||
export const VolumeSliderProgressElement: ConnectedComponentConstructor<any> = toConnectedHTMLComponent(
|
||||
VolumeSliderIndicatorElement,
|
||||
export const VolumeSliderIndicatorElement: ConnectedComponentConstructor<any> = toConnectedHTMLComponent(
|
||||
VolumeSliderIndicator,
|
||||
{ keys: [], transform: () => ({}) },
|
||||
getVolumeSliderProgressProps,
|
||||
'VolumeSliderProgress',
|
||||
@@ -280,12 +280,12 @@ export const VolumeSliderElement = Object.assign(
|
||||
{
|
||||
Root: VolumeSliderRootElement,
|
||||
Track: VolumeSliderTrackElement,
|
||||
Progress: VolumeSliderProgressElement,
|
||||
Indicator: VolumeSliderIndicatorElement,
|
||||
Thumb: VolumeSliderThumbElement,
|
||||
},
|
||||
) as {
|
||||
Root: typeof VolumeSliderRootElement;
|
||||
Track: typeof VolumeSliderTrackElement;
|
||||
Progress: typeof VolumeSliderProgressElement;
|
||||
Indicator: typeof VolumeSliderIndicatorElement;
|
||||
Thumb: typeof VolumeSliderThumbElement;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { MediaSkinElement } from '@/media/media-skin';
|
||||
import { defineCustomElement } from '@/utils/custom-element';
|
||||
import styles from './styles.css';
|
||||
import '@/define/media-container';
|
||||
// be sure to import video-provider first for proper context initialization
|
||||
import '@/define/video-provider';
|
||||
import '@/define/media-container';
|
||||
import '@/define/media-play-button';
|
||||
import '@/define/media-mute-button';
|
||||
import '@/define/media-volume-slider';
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { MediaSkinElement } from '@/media/media-skin';
|
||||
import { defineCustomElement } from '@/utils/custom-element';
|
||||
import styles from './styles.css';
|
||||
import '@/define/media-container';
|
||||
// be sure to import video-provider first for proper context initialization
|
||||
import '@/define/video-provider';
|
||||
import '@/define/media-container';
|
||||
import '@/define/media-play-button';
|
||||
import '@/define/media-mute-button';
|
||||
import '@/define/media-volume-slider';
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
*
|
||||
* @param root - The root node to search for the active element.
|
||||
*/
|
||||
export function activeElement(
|
||||
root: Document = document,
|
||||
): Element | null {
|
||||
export function activeElement(root: Document = document): Element | null {
|
||||
let element = root.activeElement;
|
||||
|
||||
while (element?.shadowRoot?.activeElement != null) {
|
||||
@@ -20,9 +18,7 @@ export function activeElement(
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/Node/getRootNode#return_value
|
||||
* @param node - The node to get the root node from.
|
||||
*/
|
||||
export function getDocumentOrShadowRoot(
|
||||
node: Node,
|
||||
): Document | ShadowRoot | null {
|
||||
export function getDocumentOrShadowRoot(node: Node): Document | ShadowRoot | null {
|
||||
const rootNode = node?.getRootNode?.();
|
||||
if (rootNode instanceof ShadowRoot || rootNode instanceof Document) {
|
||||
return rootNode;
|
||||
@@ -85,9 +81,7 @@ export function isShadowRoot(value: unknown): value is ShadowRoot {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
value instanceof ShadowRoot || value instanceof getWindow(value).ShadowRoot
|
||||
);
|
||||
return value instanceof ShadowRoot || value instanceof getWindow(value).ShadowRoot;
|
||||
}
|
||||
|
||||
function hasWindow() {
|
||||
@@ -102,7 +96,7 @@ export interface FloatingNodeType {
|
||||
id: string;
|
||||
parentId: string | null;
|
||||
context: FloatingContext;
|
||||
};
|
||||
}
|
||||
|
||||
interface FloatingContext {
|
||||
open: boolean;
|
||||
@@ -113,11 +107,59 @@ export function getNodeChildren(
|
||||
id: string | undefined,
|
||||
onlyOpenChildren = true,
|
||||
): Array<FloatingNodeType> {
|
||||
const directChildren = nodes.filter(
|
||||
node => node.parentId === id && (!onlyOpenChildren || node.context?.open),
|
||||
);
|
||||
return directChildren.flatMap(child => [
|
||||
child,
|
||||
...getNodeChildren(nodes, child.id, onlyOpenChildren),
|
||||
]);
|
||||
const directChildren = nodes.filter(node => node.parentId === id && (!onlyOpenChildren || node.context?.open));
|
||||
return directChildren.flatMap(child => [child, ...getNodeChildren(nodes, child.id, onlyOpenChildren)]);
|
||||
}
|
||||
|
||||
export function getBoundingClientRectWithoutTransform(element: HTMLElement): DOMRect {
|
||||
let el = element;
|
||||
let left = 0;
|
||||
let top = 0;
|
||||
|
||||
do {
|
||||
left += el.offsetLeft;
|
||||
top += el.offsetTop;
|
||||
el = el.offsetParent as HTMLElement;
|
||||
} while (el);
|
||||
|
||||
return {
|
||||
x: left,
|
||||
y: top,
|
||||
left,
|
||||
top,
|
||||
bottom: top + element.offsetHeight,
|
||||
right: left + element.offsetWidth,
|
||||
width: element.offsetWidth,
|
||||
height: element.offsetHeight,
|
||||
} as DOMRect;
|
||||
}
|
||||
|
||||
export function getInBoundsAdjustments(
|
||||
popupRect: DOMRect,
|
||||
containerRect: DOMRect,
|
||||
collisionPadding: number,
|
||||
): { x: number; y: number } {
|
||||
const bounds = {
|
||||
top: containerRect.top + collisionPadding,
|
||||
right: containerRect.right - collisionPadding,
|
||||
bottom: containerRect.bottom - collisionPadding,
|
||||
left: containerRect.left + collisionPadding,
|
||||
};
|
||||
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
|
||||
if (popupRect.left < bounds.left) {
|
||||
x = bounds.left - popupRect.left;
|
||||
} else if (popupRect.right > bounds.right) {
|
||||
x = bounds.right - popupRect.right;
|
||||
}
|
||||
|
||||
if (popupRect.top < bounds.top) {
|
||||
y = bounds.top - popupRect.top;
|
||||
} else if (popupRect.bottom > bounds.bottom) {
|
||||
y = bounds.bottom - popupRect.bottom;
|
||||
}
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
Generated
+18
-13
@@ -198,12 +198,6 @@ importers:
|
||||
|
||||
packages/html:
|
||||
dependencies:
|
||||
'@floating-ui/core':
|
||||
specifier: ^1.6.13
|
||||
version: 1.7.3
|
||||
'@floating-ui/dom':
|
||||
specifier: ^1.6.13
|
||||
version: 1.7.4
|
||||
'@open-wc/context-protocol':
|
||||
specifier: ^0.0.9
|
||||
version: 0.0.9
|
||||
@@ -237,7 +231,7 @@ importers:
|
||||
dependencies:
|
||||
'@floating-ui/react':
|
||||
specifier: ^0.27.16
|
||||
version: 0.27.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 0.27.16(react-dom@19.2.0(react@18.3.1))(react@18.3.1)
|
||||
'@videojs/core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
@@ -7411,12 +7405,18 @@ snapshots:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
'@floating-ui/react@0.27.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@floating-ui/react-dom@2.1.6(react-dom@19.2.0(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@floating-ui/react-dom': 2.1.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@floating-ui/dom': 1.7.4
|
||||
react: 18.3.1
|
||||
react-dom: 19.2.0(react@18.3.1)
|
||||
|
||||
'@floating-ui/react@0.27.16(react-dom@19.2.0(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@floating-ui/react-dom': 2.1.6(react-dom@19.2.0(react@18.3.1))(react@18.3.1)
|
||||
'@floating-ui/utils': 0.2.10
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-dom: 19.2.0(react@18.3.1)
|
||||
tabbable: 6.2.0
|
||||
|
||||
'@floating-ui/utils@0.2.10': {}
|
||||
@@ -9775,7 +9775,7 @@ snapshots:
|
||||
eslint: 9.37.0(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.37.0(jiti@2.6.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1))
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.37.0(jiti@2.6.1))
|
||||
eslint-plugin-react: 7.37.5(eslint@9.37.0(jiti@2.6.1))
|
||||
eslint-plugin-react-hooks: 7.0.1(eslint@9.37.0(jiti@2.6.1))
|
||||
@@ -9817,7 +9817,7 @@ snapshots:
|
||||
tinyglobby: 0.2.15
|
||||
unrs-resolver: 1.11.1
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.37.0(jiti@2.6.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -9893,7 +9893,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.9.2
|
||||
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.37.0(jiti@2.6.1)):
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
@@ -12392,6 +12392,11 @@ snapshots:
|
||||
react: 18.3.1
|
||||
scheduler: 0.23.2
|
||||
|
||||
react-dom@19.2.0(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
scheduler: 0.27.0
|
||||
|
||||
react-dom@19.2.0(react@19.2.0):
|
||||
dependencies:
|
||||
react: 19.2.0
|
||||
|
||||
@@ -1916,7 +1916,7 @@ export function generateHTMLJS(skin: Skin): string {
|
||||
|
||||
// npm install @videojs/html@next
|
||||
import '@videojs/html/icons';
|
||||
// be sure to import video-provider first
|
||||
// be sure to import video-provider first for proper context initialization
|
||||
import '@videojs/html/define/video-provider';
|
||||
import '@videojs/html/define/media-container';
|
||||
import '@videojs/html/define/media-play-button';
|
||||
|
||||
Reference in New Issue
Block a user