Skip to content

refactor(packages)!: dry up core, html, and react UI architecture#699

Merged
mihar-22 merged 14 commits intomainfrom
refactor/dry-core-html-react
Mar 4, 2026
Merged

refactor(packages)!: dry up core, html, and react UI architecture#699
mihar-22 merged 14 commits intomainfrom
refactor/dry-core-html-react

Conversation

@mihar-22
Copy link
Copy Markdown
Member

@mihar-22 mihar-22 commented Mar 4, 2026

Summary

DRY up repetitive UI code across store, core, html, and react. Introduces shared type contracts, base classes for HTML elements, factories for React components, and consistent naming across the board.

Changes

Store

  • Selector converted from intersection type to callable interface with displayName?: string | undefined
  • createSelector simplified — no separate meta object, inlines { displayName: slice.name }
  • Slices gain optional name property for debug labeling

Core — type system

  • UIComponent<Props, State> and MediaUIComponent<Props, State> interfaces replace old UICore
  • InferMediaState<Core> and InferComponentState<Core> use indexed access types (Parameters/ReturnType) instead of conditional types — eliminates deferred type resolution and removes all as never casts in consumers
  • StateAttrMap<State> for typed data-attribute maps

Core — setMedia/setInput pattern

  • All 12 core classes now have setMedia()/setInput() + parameterless getState()
  • Nullable #media fields with Type | null = null
  • SliderCore gains getStepPercent(), getLargeStepPercent(), adjustPercentForAlignment()

Core — naming

  • SliderHandleSliderApi, PopoverHandlePopoverApi, ThumbnailHandleThumbnailApi
  • TransitionHandlerTransitionApi, createTransitionHandler()createTransition()
  • SliderInteractionSliderInput, .interaction.input
  • dataAttrsstateAttrMap everywhere
  • featureNamedisplayName, NamedSelector removed
  • commitSeek removed (media.currentTime set synchronously before first await)
  • SliderBaseProps removed — domain sliders extend SliderProps with @internal on value/min/max
  • All 10 features gain name property
  • applyElementProps options changed to { signal?: AbortSignal }

HTML — base classes

  • MediaButtonElement<Core> abstract base — all 6 button elements extend it
  • MediaUIElement<Core> abstract base — poster extends it
  • ContextPartElement<State> abstract base with Lit context protocol — slider Track/Fill/Buffer and controls Group extend it
  • Controls gains full compound pattern: ControlsElement provides context, ControlsGroupElement consumes it
  • Context files renamed from slider-context.tscontext.ts (directory already scopes)
  • Slider thumb/value use ctx.stateAttrMap from context instead of hardcoded imports
  • #handle#api in ThumbnailElement

React — factories

  • createMediaButton<Core, Props>(config) — flat generic signature (no curry), typed stateAttrMap via InferComponentState<Core>, automatic prop splitting via CoreClass.defaultProps keys. All 6 buttons use it
  • createContextPart<Props, State>(config) — typed useContext return, staticProps as Partial<Props>. Slider Track/Fill/Buffer and popover Arrow use it
  • Context files renamed, hooks (useSliderContext, useControlsContext) throw when used outside provider
  • Popover popup/trigger use context.stateAttrMap instead of hardcoded PopoverDataAttrs
Implementation details
  • Custom elements need parameterless constructors, so config uses abstract properties (class fields in subclasses) instead of super() args
  • InferMediaState uses Parameters<Core['setMedia']>[0] — indexed access resolves via the constraint when Core is generic (unlike conditional types which defer). This is why the as never casts are gone
  • HTML ContextPartElement.update() keeps if (ctx) guard — Lit context protocol is async and the first update can fire before the provider's setValue. React context is synchronous so hooks throw immediately
  • ForwardRefExoticComponent return type on createContextPart/createMediaButton is required for React 18 ref support (peer dep is >=16.8.0)
  • createMediaButton retains two casts: core.getState() as InferComponentState<Core> (deferred utility type) and as unknown as ForwardRefExoticComponent (forwardRef + dynamic prop splitting)

Testing

All existing tests updated + new tests for selector displayName and slider features.

pnpm typecheck  # clean
pnpm test       # 24/24 tasks, all pass
pnpm build:packages  # 8/8 packages

@netlify
Copy link
Copy Markdown

netlify bot commented Mar 4, 2026

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 295f617
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/69a7dd491c75430008ec7f21
😎 Deploy Preview https://deploy-preview-699--vjs10-site.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Introduce UICore interface and shared base classes to eliminate
duplicated patterns across HTML and React UI components. Unify
slider interaction model, pending seek lifecycle, and type
inference into core so both platforms share a single source of
truth.
@mihar-22 mihar-22 force-pushed the refactor/dry-core-html-react branch from b0e2780 to 553f805 Compare March 4, 2026 01:08
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 4, 2026

📦 Bundle Size Report

Package Size Diff %
@videojs/core 10.41 kB +105 B ████████ +1.0% 🔺
@videojs/element 1.60 kB 0 B ░░░░░░░░ 0%
@videojs/html 18.51 kB +116 B █████░░░ +0.6% 🔺
@videojs/icons 3.79 kB 0 B ░░░░░░░░ 0%
@videojs/react 15.08 kB +143 B ████████ +0.9% 🔺
@videojs/store 1.96 kB +17 B ███████░ +0.9% 🔺
@videojs/utils 2.81 kB 0 B ░░░░░░░░ 0%

Total: 54.16 kB · +381 B · +0.7%


Entry Breakdown

Subpath sizes are the additional bytes on top of the root entry point, measured by bundling root + subpath together and subtracting the root-only size.

@videojs/core
Entry Base PR Diff %
. 4.28 kB 4.39 kB +104 B +2.4% 🔺
./dom 6.03 kB 6.03 kB +1 B +0.0% 🔺
total 10.31 kB 10.41 kB +105 B +1.0%
@videojs/element
Entry Base PR Diff %
. 817 B 817 B 0 B 0%
./context 823 B 823 B 0 B 0%
total 1.60 kB 1.60 kB 0 B 0%
@videojs/html
Entry Base PR Diff %
. 15.18 kB 15.41 kB +234 B +1.5% 🔺
./video 1.08 kB 1.05 kB -26 B -2.4% 🔽
./audio 1.06 kB 1.03 kB -36 B -3.3% 🔽
./background 1.07 kB 1.02 kB -56 B -5.1% 🔽
total 18.39 kB 18.51 kB +116 B +0.6%
@videojs/icons
Entry Base PR Diff %
./react 2.27 kB 2.27 kB 0 B 0%
./html 1.52 kB 1.52 kB 0 B 0%
total 3.79 kB 3.79 kB 0 B 0%
@videojs/store
Entry Base PR Diff %
. 1.29 kB 1.31 kB +18 B +1.4% 🔺
./html 468 B 472 B +4 B +0.9% 🔺
./react 204 B 199 B -5 B -2.5% 🔽
total 1.95 kB 1.96 kB +17 B +0.9%
@videojs/utils
Entry Base PR Diff %
./array 104 B 104 B 0 B 0%
./dom 928 B 928 B 0 B 0%
./events 227 B 227 B 0 B 0%
./function 261 B 261 B 0 B 0%
./object 119 B 119 B 0 B 0%
./predicate 265 B 265 B 0 B 0%
./string 148 B 148 B 0 B 0%
./style 185 B 185 B 0 B 0%
./time 478 B 478 B 0 B 0%
./number 158 B 158 B 0 B 0%
total 2.81 kB 2.81 kB 0 B 0%

ℹ️ How to interpret

Sizes are minified + brotli, measured with esbuild.
Package totals are computed as root size + marginal subpath costs.
Subpath marginal cost = (root + subpath bundled together) − root alone.

Icon Meaning
No change
🔺 Increased ≤ 10%
🔴 Increased > 10%
🔽 Decreased
🆕 New (no baseline)

Run pnpm size locally to check current sizes.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 4, 2026

CI Failure Diagnosis

File Type What failed
packages/react/src/ui/time-slider/time-slider-root.tsx:29 typecheck Property commitThrottle does not exist on type NonNullableObject<SliderProps> (TS2339).

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 4, 2026

CI Failure Diagnosis

File Type What failed
packages/react/src/ui/time-slider/time-slider-root.tsx:29 typecheck Property commitThrottle does not exist on type NonNullableObject<SliderProps> (TS2339).

@mihar-22 mihar-22 force-pushed the refactor/dry-core-html-react branch from 7f9194c to 9ccb00e Compare March 4, 2026 02:57
@mihar-22 mihar-22 force-pushed the refactor/dry-core-html-react branch from 9ccb00e to 3918cfc Compare March 4, 2026 02:57
mihar-22 added 10 commits March 3, 2026 20:01
…attern

Replace UICore with UIComponent/MediaUIComponent interfaces. All 12 core
classes now use setMedia()/setInput() + parameterless getState() instead of
getState(media). PopoverInteraction renamed to PopoverInput, .interaction
to .input.

Add ContextPartElement base class (HTML) and createContextPart factory
(React) for DRY compound-component parts. Slider Track/Fill/Buffer and
PopoverArrow now use the shared pattern. stateAttrMap added to slider and
popover context values.

Controls gains full compound pattern: ControlsElement provides context,
ControlsGroupElement consumes it for data-attribute propagation.
…enerics

PopoverHandle → PopoverApi, ThumbnailHandle → ThumbnailApi,
TransitionHandler → TransitionApi, createTransitionHandler → createTransition.

PartContextValue and ContextPartConfig now use a generic State parameter
instead of hardcoded object. Remove unnecessary StateAttrMap<object> casts
from slider and controls root elements.
@mihar-22 mihar-22 force-pushed the refactor/dry-core-html-react branch from 7e4fc62 to 295f617 Compare March 4, 2026 07:20
@mihar-22 mihar-22 marked this pull request as ready for review March 4, 2026 07:27
@mihar-22 mihar-22 merged commit 1edeade into main Mar 4, 2026
19 checks passed
@mihar-22 mihar-22 deleted the refactor/dry-core-html-react branch March 4, 2026 07:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant