From ab338c432b8c548d043b8e5880d00edae83fc310 Mon Sep 17 00:00:00 2001 From: rahim Date: Fri, 10 Apr 2026 18:18:32 -0700 Subject: [PATCH] fix(html): replace bare side-effect imports with explicit safeDefine() in define modules (#1307) --- CLAUDE.md | 8 ++- build/scripts/check-workspace.mjs | 60 +++++++++++++++++ packages/html/src/define/audio/minimal-ui.ts | 50 +++++++++----- packages/html/src/define/audio/ui.ts | 53 ++++++++++----- packages/html/src/define/ui/compounds.ts | 67 +++++++++++++++++++ packages/html/src/define/ui/controls.ts | 6 +- packages/html/src/define/ui/error-dialog.ts | 11 ++- packages/html/src/define/ui/slider.ts | 15 ++--- packages/html/src/define/ui/time-slider.ts | 19 +++--- packages/html/src/define/ui/time.ts | 8 +-- packages/html/src/define/ui/volume-slider.ts | 15 ++--- packages/html/src/define/video/minimal-ui.ts | 67 +++++++++++++------ packages/html/src/define/video/ui.ts | 70 ++++++++++++++------ 13 files changed, 334 insertions(+), 115 deletions(-) create mode 100644 packages/html/src/define/ui/compounds.ts diff --git a/CLAUDE.md b/CLAUDE.md index 4632b53c..fd331772 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -103,6 +103,9 @@ pnpm lint:fix:file # Remove all dist and types outputs pnpm clean +# Validate workspace consistency (CI coverage, scopes, define imports, etc.) +pnpm check:workspace + # Measure bundle size (SPF only) pnpm -F @videojs/spf size # Public API (minified + gzipped) pnpm -F @videojs/spf size:all # All exports (minified + gzipped) @@ -118,8 +121,9 @@ pnpm -F @videojs/spf size:all # All exports (minified + gzipped) 4. Run test/s, fix all issues. If there are no tests add them. 5. Lint file/s, fix all issues. 6. Run build/s, fix all errors. -7. Before creating a PR `pnpm test`. -8. If your changes introduced new patterns or conventions, ask the user to run `/claude-update`. +7. Run `pnpm check:workspace` — fix any consistency warnings. +8. Before creating a PR `pnpm test`. +9. If your changes introduced new patterns or conventions, ask the user to run `/claude-update`. Be efficient when running operations, see "Common Root Commands". diff --git a/build/scripts/check-workspace.mjs b/build/scripts/check-workspace.mjs index 660e23e5..1b551952 100644 --- a/build/scripts/check-workspace.mjs +++ b/build/scripts/check-workspace.mjs @@ -10,6 +10,7 @@ * 3. Root tsconfig references — every composite project is referenced * 4. Package metadata — non-private packages have required fields * 5. Release-please config — every versioned package is registered + * 6. Define imports — no bare side-effect imports from relative paths */ import { existsSync, readdirSync, readFileSync } from 'node:fs'; import { dirname, join, resolve } from 'node:path'; @@ -230,6 +231,64 @@ function checkReleasePleaseConfig() { return { ok: warnings.length === 0, warnings }; } +// ── Check 6: Define imports ────────────────────────────────────────────────── + +/** + * Bare side-effect imports from relative paths in the define directory cause + * non-deterministic registration order when loaded as native ESM in the + * browser. All registration must go through explicit safeDefine() calls. + */ +function checkDefineImports() { + const warnings = []; + const defineDir = join(PACKAGES_DIR, 'html/src/define'); + + if (!existsSync(defineDir)) { + return { ok: true, warnings: [] }; + } + + // Matches: import './foo'; import "../bar"; import './foo/bar'; + // Ignores value imports: import { X } from './foo'; import X from './foo'; + // Ignores CSS imports: import './foo.css'; import './foo.css?inline'; + const sideEffectImportRe = /^import\s+['"](\.[^'"]+)['"]\s*;/gm; + const cssSpecifierRe = /\.css(?:\?|$)/; + + function findTsFiles(dir, results = []) { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + if (entry.name === 'node_modules' || entry.name === 'tests') continue; + const full = join(dir, entry.name); + if (entry.isDirectory()) { + findTsFiles(full, results); + } else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.test.ts')) { + results.push(full); + } + } + return results; + } + + for (const filePath of findTsFiles(defineDir)) { + const content = readText(filePath); + const relative = filePath.slice(ROOT.length + 1); + + const sideEffects = []; + for (const match of content.matchAll(sideEffectImportRe)) { + const specifier = match[1]; + if (cssSpecifierRe.test(specifier)) continue; + sideEffects.push(specifier); + } + + // A single side-effect import (e.g. skin importing its ui module) is safe + // because ESM evaluates it synchronously before the importing module's body. + // Multiple side-effect imports are the problem — they race in the browser. + if (sideEffects.length > 1) { + for (const specifier of sideEffects) { + warnings.push(`${relative}: bare side-effect import "${specifier}" — use safeDefine() instead`); + } + } + } + + return { ok: warnings.length === 0, warnings }; +} + // ── Main ──────────────────────────────────────────────────────────────────── const checks = [ @@ -238,6 +297,7 @@ const checks = [ { name: 'Root tsconfig references', fn: checkTsconfigReferences }, { name: 'Package metadata', fn: checkPackageMetadata }, { name: 'Release-please config', fn: checkReleasePleaseConfig }, + { name: 'Define imports', fn: checkDefineImports }, ]; let failed = 0; diff --git a/packages/html/src/define/audio/minimal-ui.ts b/packages/html/src/define/audio/minimal-ui.ts index d328a9c3..b4f045a7 100644 --- a/packages/html/src/define/audio/minimal-ui.ts +++ b/packages/html/src/define/audio/minimal-ui.ts @@ -1,16 +1,36 @@ -// Side-effect-only module: registers the audio player, container, and all -// audio UI custom elements used by the minimal skin without creating a skin -// element. Use this entry when building an ejected (light DOM) player layout. -import './player'; +// Registers the audio player, container, and all audio UI custom elements +// used by the minimal skin without creating a skin element. Use this entry +// when building an ejected (light DOM) player layout. +import { MediaContainerElement } from '../../media/container-element'; +import { MuteButtonElement } from '../../ui/mute-button/mute-button-element'; +import { PlayButtonElement } from '../../ui/play-button/play-button-element'; +import { PlaybackRateButtonElement } from '../../ui/playback-rate-button/playback-rate-button-element'; +import { PopoverElement } from '../../ui/popover/popover-element'; +import { SeekButtonElement } from '../../ui/seek-button/seek-button-element'; +import { TooltipElement } from '../../ui/tooltip/tooltip-element'; +import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element'; +import { safeDefine } from '../safe-define'; +import { defineErrorDialog, defineTime, defineTimeSlider, defineVolumeSlider } from '../ui/compounds'; -import '../ui/error-dialog'; -import '../ui/mute-button'; -import '../ui/play-button'; -import '../ui/playback-rate-button'; -import '../ui/popover'; -import '../ui/seek-button'; -import '../ui/time'; -import '../ui/time-slider'; -import '../ui/tooltip'; -import '../ui/tooltip-group'; -import '../ui/volume-slider'; +// Value import — player.ts body runs before this module's body. +import { AudioPlayerElement } from './player'; + +// ── Registration (providers / parents first) ──────────────────────────── + +safeDefine(AudioPlayerElement); +safeDefine(MediaContainerElement); + +// Compound groups. +defineErrorDialog(); +defineTimeSlider(); +defineVolumeSlider(); +defineTime(); + +// Standalone elements. +safeDefine(MuteButtonElement); +safeDefine(PlayButtonElement); +safeDefine(PlaybackRateButtonElement); +safeDefine(PopoverElement); +safeDefine(SeekButtonElement); +safeDefine(TooltipElement); +safeDefine(TooltipGroupElement); diff --git a/packages/html/src/define/audio/ui.ts b/packages/html/src/define/audio/ui.ts index 4178e381..8747d81e 100644 --- a/packages/html/src/define/audio/ui.ts +++ b/packages/html/src/define/audio/ui.ts @@ -1,17 +1,38 @@ -// Side-effect-only module: registers the audio player, container, and all -// audio UI custom elements without creating a skin element. Use this entry -// when building an ejected (light DOM) player layout. -import './player'; +// Registers the audio player, container, and all audio UI custom elements +// without creating a skin element. Use this entry when building an ejected +// (light DOM) player layout. +import { MediaContainerElement } from '../../media/container-element'; +import { HotkeyElement } from '../../ui/hotkey/hotkey-element'; +import { MuteButtonElement } from '../../ui/mute-button/mute-button-element'; +import { PlayButtonElement } from '../../ui/play-button/play-button-element'; +import { PlaybackRateButtonElement } from '../../ui/playback-rate-button/playback-rate-button-element'; +import { PopoverElement } from '../../ui/popover/popover-element'; +import { SeekButtonElement } from '../../ui/seek-button/seek-button-element'; +import { TooltipElement } from '../../ui/tooltip/tooltip-element'; +import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element'; +import { safeDefine } from '../safe-define'; +import { defineErrorDialog, defineTime, defineTimeSlider, defineVolumeSlider } from '../ui/compounds'; -import '../ui/error-dialog'; -import '../ui/hotkey'; -import '../ui/mute-button'; -import '../ui/play-button'; -import '../ui/playback-rate-button'; -import '../ui/popover'; -import '../ui/seek-button'; -import '../ui/time'; -import '../ui/time-slider'; -import '../ui/tooltip'; -import '../ui/tooltip-group'; -import '../ui/volume-slider'; +// Value import — player.ts body runs before this module's body. +import { AudioPlayerElement } from './player'; + +// ── Registration (providers / parents first) ──────────────────────────── + +safeDefine(AudioPlayerElement); +safeDefine(MediaContainerElement); + +// Compound groups. +defineErrorDialog(); +defineTimeSlider(); +defineVolumeSlider(); +defineTime(); + +// Standalone elements. +safeDefine(HotkeyElement); +safeDefine(MuteButtonElement); +safeDefine(PlayButtonElement); +safeDefine(PlaybackRateButtonElement); +safeDefine(PopoverElement); +safeDefine(SeekButtonElement); +safeDefine(TooltipElement); +safeDefine(TooltipGroupElement); diff --git a/packages/html/src/define/ui/compounds.ts b/packages/html/src/define/ui/compounds.ts new file mode 100644 index 00000000..90953e7b --- /dev/null +++ b/packages/html/src/define/ui/compounds.ts @@ -0,0 +1,67 @@ +import { AlertDialogCloseElement } from '../../ui/alert-dialog/alert-dialog-close-element'; +import { AlertDialogDescriptionElement } from '../../ui/alert-dialog/alert-dialog-description-element'; +import { AlertDialogTitleElement } from '../../ui/alert-dialog/alert-dialog-title-element'; +import { ControlsElement } from '../../ui/controls/controls-element'; +import { ControlsGroupElement } from '../../ui/controls/controls-group-element'; +import { ErrorDialogElement } from '../../ui/error-dialog/error-dialog-element'; +import { SliderBufferElement } from '../../ui/slider/slider-buffer-element'; +import { SliderElement } from '../../ui/slider/slider-element'; +import { SliderFillElement } from '../../ui/slider/slider-fill-element'; +import { SliderPreviewElement } from '../../ui/slider/slider-preview-element'; +import { SliderThumbElement } from '../../ui/slider/slider-thumb-element'; +import { SliderThumbnailElement } from '../../ui/slider/slider-thumbnail-element'; +import { SliderTrackElement } from '../../ui/slider/slider-track-element'; +import { SliderValueElement } from '../../ui/slider/slider-value-element'; +import { TimeElement } from '../../ui/time/time-element'; +import { TimeGroupElement } from '../../ui/time/time-group-element'; +import { TimeSeparatorElement } from '../../ui/time/time-separator-element'; +import { TimeSliderElement } from '../../ui/time-slider/time-slider-element'; +import { VolumeSliderElement } from '../../ui/volume-slider/volume-slider-element'; +import { safeDefine } from '../safe-define'; + +// ── Define functions ──────────────────────────────────────────────────── + +export function defineControls(): void { + safeDefine(ControlsElement); + safeDefine(ControlsGroupElement); +} + +export function defineErrorDialog(): void { + // Parent first — child elements consume its context. + safeDefine(ErrorDialogElement); + safeDefine(AlertDialogCloseElement); + safeDefine(AlertDialogDescriptionElement); + safeDefine(AlertDialogTitleElement); +} + +/** Shared slider sub-elements used by all slider types. */ +export function defineSliderParts(): void { + safeDefine(SliderFillElement); + safeDefine(SliderPreviewElement); + safeDefine(SliderThumbElement); + safeDefine(SliderTrackElement); + safeDefine(SliderValueElement); +} + +export function defineSlider(): void { + safeDefine(SliderElement); + defineSliderParts(); +} + +export function defineTime(): void { + safeDefine(TimeElement); + safeDefine(TimeGroupElement); + safeDefine(TimeSeparatorElement); +} + +export function defineTimeSlider(): void { + safeDefine(TimeSliderElement); + defineSliderParts(); + safeDefine(SliderBufferElement); + safeDefine(SliderThumbnailElement); +} + +export function defineVolumeSlider(): void { + safeDefine(VolumeSliderElement); + defineSliderParts(); +} diff --git a/packages/html/src/define/ui/controls.ts b/packages/html/src/define/ui/controls.ts index d5044495..a4071d5c 100644 --- a/packages/html/src/define/ui/controls.ts +++ b/packages/html/src/define/ui/controls.ts @@ -1,12 +1,12 @@ import { ControlsElement } from '../../ui/controls/controls-element'; import { ControlsGroupElement } from '../../ui/controls/controls-group-element'; -import { safeDefine } from '../safe-define'; +import { defineControls } from './compounds'; -safeDefine(ControlsElement); -safeDefine(ControlsGroupElement); +defineControls(); declare global { interface HTMLElementTagNameMap { [ControlsElement.tagName]: ControlsElement; + [ControlsGroupElement.tagName]: ControlsGroupElement; } } diff --git a/packages/html/src/define/ui/error-dialog.ts b/packages/html/src/define/ui/error-dialog.ts index 01da6f7c..a824e282 100644 --- a/packages/html/src/define/ui/error-dialog.ts +++ b/packages/html/src/define/ui/error-dialog.ts @@ -2,16 +2,15 @@ import { AlertDialogCloseElement } from '../../ui/alert-dialog/alert-dialog-clos import { AlertDialogDescriptionElement } from '../../ui/alert-dialog/alert-dialog-description-element'; import { AlertDialogTitleElement } from '../../ui/alert-dialog/alert-dialog-title-element'; import { ErrorDialogElement } from '../../ui/error-dialog/error-dialog-element'; -import { safeDefine } from '../safe-define'; +import { defineErrorDialog } from './compounds'; -// Parent first — child elements consume its context. -safeDefine(ErrorDialogElement); -safeDefine(AlertDialogCloseElement); -safeDefine(AlertDialogDescriptionElement); -safeDefine(AlertDialogTitleElement); +defineErrorDialog(); declare global { interface HTMLElementTagNameMap { [ErrorDialogElement.tagName]: ErrorDialogElement; + [AlertDialogCloseElement.tagName]: AlertDialogCloseElement; + [AlertDialogDescriptionElement.tagName]: AlertDialogDescriptionElement; + [AlertDialogTitleElement.tagName]: AlertDialogTitleElement; } } diff --git a/packages/html/src/define/ui/slider.ts b/packages/html/src/define/ui/slider.ts index bdad7b75..de33bf45 100644 --- a/packages/html/src/define/ui/slider.ts +++ b/packages/html/src/define/ui/slider.ts @@ -4,18 +4,17 @@ import { SliderPreviewElement } from '../../ui/slider/slider-preview-element'; import { SliderThumbElement } from '../../ui/slider/slider-thumb-element'; import { SliderTrackElement } from '../../ui/slider/slider-track-element'; import { SliderValueElement } from '../../ui/slider/slider-value-element'; -import { safeDefine } from '../safe-define'; +import { defineSlider } from './compounds'; -// Parent slider first — sub-elements consume its context. -safeDefine(SliderElement); -safeDefine(SliderFillElement); -safeDefine(SliderPreviewElement); -safeDefine(SliderThumbElement); -safeDefine(SliderTrackElement); -safeDefine(SliderValueElement); +defineSlider(); declare global { interface HTMLElementTagNameMap { [SliderElement.tagName]: SliderElement; + [SliderFillElement.tagName]: SliderFillElement; + [SliderPreviewElement.tagName]: SliderPreviewElement; + [SliderThumbElement.tagName]: SliderThumbElement; + [SliderTrackElement.tagName]: SliderTrackElement; + [SliderValueElement.tagName]: SliderValueElement; } } diff --git a/packages/html/src/define/ui/time-slider.ts b/packages/html/src/define/ui/time-slider.ts index 7136c12d..32844f82 100644 --- a/packages/html/src/define/ui/time-slider.ts +++ b/packages/html/src/define/ui/time-slider.ts @@ -6,20 +6,19 @@ import { SliderThumbnailElement } from '../../ui/slider/slider-thumbnail-element import { SliderTrackElement } from '../../ui/slider/slider-track-element'; import { SliderValueElement } from '../../ui/slider/slider-value-element'; import { TimeSliderElement } from '../../ui/time-slider/time-slider-element'; -import { safeDefine } from '../safe-define'; +import { defineTimeSlider } from './compounds'; -// Parent slider first — sub-elements consume its context. -safeDefine(TimeSliderElement); -safeDefine(SliderBufferElement); -safeDefine(SliderFillElement); -safeDefine(SliderPreviewElement); -safeDefine(SliderThumbElement); -safeDefine(SliderThumbnailElement); -safeDefine(SliderTrackElement); -safeDefine(SliderValueElement); +defineTimeSlider(); declare global { interface HTMLElementTagNameMap { [TimeSliderElement.tagName]: TimeSliderElement; + [SliderBufferElement.tagName]: SliderBufferElement; + [SliderFillElement.tagName]: SliderFillElement; + [SliderPreviewElement.tagName]: SliderPreviewElement; + [SliderThumbElement.tagName]: SliderThumbElement; + [SliderThumbnailElement.tagName]: SliderThumbnailElement; + [SliderTrackElement.tagName]: SliderTrackElement; + [SliderValueElement.tagName]: SliderValueElement; } } diff --git a/packages/html/src/define/ui/time.ts b/packages/html/src/define/ui/time.ts index 96aa7c9c..1742cdcf 100644 --- a/packages/html/src/define/ui/time.ts +++ b/packages/html/src/define/ui/time.ts @@ -1,14 +1,14 @@ import { TimeElement } from '../../ui/time/time-element'; import { TimeGroupElement } from '../../ui/time/time-group-element'; import { TimeSeparatorElement } from '../../ui/time/time-separator-element'; -import { safeDefine } from '../safe-define'; +import { defineTime } from './compounds'; -safeDefine(TimeElement); -safeDefine(TimeGroupElement); -safeDefine(TimeSeparatorElement); +defineTime(); declare global { interface HTMLElementTagNameMap { [TimeElement.tagName]: TimeElement; + [TimeGroupElement.tagName]: TimeGroupElement; + [TimeSeparatorElement.tagName]: TimeSeparatorElement; } } diff --git a/packages/html/src/define/ui/volume-slider.ts b/packages/html/src/define/ui/volume-slider.ts index f87cbfc7..11adf426 100644 --- a/packages/html/src/define/ui/volume-slider.ts +++ b/packages/html/src/define/ui/volume-slider.ts @@ -4,18 +4,17 @@ import { SliderThumbElement } from '../../ui/slider/slider-thumb-element'; import { SliderTrackElement } from '../../ui/slider/slider-track-element'; import { SliderValueElement } from '../../ui/slider/slider-value-element'; import { VolumeSliderElement } from '../../ui/volume-slider/volume-slider-element'; -import { safeDefine } from '../safe-define'; +import { defineVolumeSlider } from './compounds'; -// Parent slider first — sub-elements consume its context. -safeDefine(VolumeSliderElement); -safeDefine(SliderFillElement); -safeDefine(SliderPreviewElement); -safeDefine(SliderThumbElement); -safeDefine(SliderTrackElement); -safeDefine(SliderValueElement); +defineVolumeSlider(); declare global { interface HTMLElementTagNameMap { [VolumeSliderElement.tagName]: VolumeSliderElement; + [SliderFillElement.tagName]: SliderFillElement; + [SliderPreviewElement.tagName]: SliderPreviewElement; + [SliderThumbElement.tagName]: SliderThumbElement; + [SliderTrackElement.tagName]: SliderTrackElement; + [SliderValueElement.tagName]: SliderValueElement; } } diff --git a/packages/html/src/define/video/minimal-ui.ts b/packages/html/src/define/video/minimal-ui.ts index 394fbade..83d0c618 100644 --- a/packages/html/src/define/video/minimal-ui.ts +++ b/packages/html/src/define/video/minimal-ui.ts @@ -1,22 +1,47 @@ -// Side-effect-only module: registers the video player, container, and all -// video UI custom elements used by the minimal skin without creating a skin -// element. Use this entry when building an ejected (light DOM) player layout. -import './player'; +// Registers the video player, container, and all video UI custom elements +// used by the minimal skin without creating a skin element. Use this entry +// when building an ejected (light DOM) player layout. +import { MediaContainerElement } from '../../media/container-element'; +import { BufferingIndicatorElement } from '../../ui/buffering-indicator/buffering-indicator-element'; +import { CaptionsButtonElement } from '../../ui/captions-button/captions-button-element'; +import { FullscreenButtonElement } from '../../ui/fullscreen-button/fullscreen-button-element'; +import { MuteButtonElement } from '../../ui/mute-button/mute-button-element'; +import { PiPButtonElement } from '../../ui/pip-button/pip-button-element'; +import { PlayButtonElement } from '../../ui/play-button/play-button-element'; +import { PlaybackRateButtonElement } from '../../ui/playback-rate-button/playback-rate-button-element'; +import { PopoverElement } from '../../ui/popover/popover-element'; +import { PosterElement } from '../../ui/poster/poster-element'; +import { SeekButtonElement } from '../../ui/seek-button/seek-button-element'; +import { TooltipElement } from '../../ui/tooltip/tooltip-element'; +import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element'; +import { safeDefine } from '../safe-define'; +import { defineControls, defineErrorDialog, defineTime, defineTimeSlider, defineVolumeSlider } from '../ui/compounds'; -import '../ui/buffering-indicator'; -import '../ui/captions-button'; -import '../ui/controls'; -import '../ui/error-dialog'; -import '../ui/fullscreen-button'; -import '../ui/mute-button'; -import '../ui/pip-button'; -import '../ui/play-button'; -import '../ui/playback-rate-button'; -import '../ui/popover'; -import '../ui/poster'; -import '../ui/seek-button'; -import '../ui/time'; -import '../ui/time-slider'; -import '../ui/tooltip'; -import '../ui/tooltip-group'; -import '../ui/volume-slider'; +// Value import — player.ts body runs before this module's body. +import { VideoPlayerElement } from './player'; + +// ── Registration (providers / parents first) ──────────────────────────── + +safeDefine(VideoPlayerElement); +safeDefine(MediaContainerElement); + +// Compound groups. +defineControls(); +defineErrorDialog(); +defineTimeSlider(); +defineVolumeSlider(); +defineTime(); + +// Standalone elements. +safeDefine(BufferingIndicatorElement); +safeDefine(CaptionsButtonElement); +safeDefine(FullscreenButtonElement); +safeDefine(MuteButtonElement); +safeDefine(PiPButtonElement); +safeDefine(PlayButtonElement); +safeDefine(PlaybackRateButtonElement); +safeDefine(PopoverElement); +safeDefine(PosterElement); +safeDefine(SeekButtonElement); +safeDefine(TooltipElement); +safeDefine(TooltipGroupElement); diff --git a/packages/html/src/define/video/ui.ts b/packages/html/src/define/video/ui.ts index cd2fa1f1..2ac60bdf 100644 --- a/packages/html/src/define/video/ui.ts +++ b/packages/html/src/define/video/ui.ts @@ -1,23 +1,49 @@ -// Side-effect-only module: registers the video player, container, and all -// video UI custom elements without creating a skin element. Use this entry -// when building an ejected (light DOM) player layout. -import './player'; +// Registers the video player, container, and all video UI custom elements +// without creating a skin element. Use this entry when building an ejected +// (light DOM) player layout. +import { MediaContainerElement } from '../../media/container-element'; +import { BufferingIndicatorElement } from '../../ui/buffering-indicator/buffering-indicator-element'; +import { CaptionsButtonElement } from '../../ui/captions-button/captions-button-element'; +import { FullscreenButtonElement } from '../../ui/fullscreen-button/fullscreen-button-element'; +import { HotkeyElement } from '../../ui/hotkey/hotkey-element'; +import { MuteButtonElement } from '../../ui/mute-button/mute-button-element'; +import { PiPButtonElement } from '../../ui/pip-button/pip-button-element'; +import { PlayButtonElement } from '../../ui/play-button/play-button-element'; +import { PlaybackRateButtonElement } from '../../ui/playback-rate-button/playback-rate-button-element'; +import { PopoverElement } from '../../ui/popover/popover-element'; +import { PosterElement } from '../../ui/poster/poster-element'; +import { SeekButtonElement } from '../../ui/seek-button/seek-button-element'; +import { TooltipElement } from '../../ui/tooltip/tooltip-element'; +import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element'; +import { safeDefine } from '../safe-define'; +import { defineControls, defineErrorDialog, defineTime, defineTimeSlider, defineVolumeSlider } from '../ui/compounds'; -import '../ui/buffering-indicator'; -import '../ui/captions-button'; -import '../ui/controls'; -import '../ui/error-dialog'; -import '../ui/fullscreen-button'; -import '../ui/hotkey'; -import '../ui/mute-button'; -import '../ui/pip-button'; -import '../ui/play-button'; -import '../ui/playback-rate-button'; -import '../ui/popover'; -import '../ui/poster'; -import '../ui/seek-button'; -import '../ui/time'; -import '../ui/time-slider'; -import '../ui/tooltip'; -import '../ui/tooltip-group'; -import '../ui/volume-slider'; +// Value import — player.ts body runs before this module's body. +import { VideoPlayerElement } from './player'; + +// ── Registration (providers / parents first) ──────────────────────────── + +safeDefine(VideoPlayerElement); +safeDefine(MediaContainerElement); + +// Compound groups. +defineControls(); +defineErrorDialog(); +defineTimeSlider(); +defineVolumeSlider(); +defineTime(); + +// Standalone elements. +safeDefine(BufferingIndicatorElement); +safeDefine(CaptionsButtonElement); +safeDefine(FullscreenButtonElement); +safeDefine(HotkeyElement); +safeDefine(MuteButtonElement); +safeDefine(PiPButtonElement); +safeDefine(PlayButtonElement); +safeDefine(PlaybackRateButtonElement); +safeDefine(PopoverElement); +safeDefine(PosterElement); +safeDefine(SeekButtonElement); +safeDefine(TooltipElement); +safeDefine(TooltipGroupElement);