Component Behavior Definitions & Hooks #198
cjpillsbury
started this conversation in
General
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Component Behavior Pattern
Executive Summary
Video.js 10 uses behavior hooks (
useXAttrs) to transform component state into rendered DOM props and attributes. This transformation layer defines a component's semantic identity:paused: truebecomesdata-paused=""(for CSS styling) andaria-label="play"(for accessibility).Key outputs: data-attributes (styling selectors), ARIA properties (accessibility), CSS variables (dynamic styling), and other DOM props (tabIndex, className, etc.). Event handlers and tooltip/label derivation are still being explored.
Open architectural questions: Should behavior hooks share actual code across frameworks or just patterns? How should event handlers be named and exposed? Who owns tooltip label derivation? How does i18n integrate?
Note on maturity: This architecture is in early exploration. The document presents transformation patterns, code sharing strategies, and tradeoffs rather than prescribing specific implementations.
Overview: The Component Identity Layer
Behavior hooks define what a component is and does, independent of how it's rendered or which framework presents it. While component state definitions act as a "lens" into the media store (selecting relevant media state), behavior hooks describe the component's own semantics and identity.
This is the first architectural abstraction focused on the component itself rather than its relationship to media state. A play button is a toggle-like button with play/pause semantics—this is true whether rendered in React, HTML, or React Native.
The Abstraction
Component state (from state definitions + hooks):
paused,volume,currentTime)requestPlay,requestSeek)Component behavior (from behavior hooks):
button,slider)The Transformation Flow
Key insight: Behavior hooks bridge between "what the component needs from media" (state definitions) and "how the component is presented" (render functions). They describe the component's essential nature—its semantic identity—in a framework-agnostic way.
Why this layer exists:
Behavior Hook Architecture: Code Sharing Strategy
A fundamental architectural question: Should behavior hooks share actual code across frameworks, or just share patterns and conventions?
Three Approaches Under Consideration
1. Shared Definitions
Framework-agnostic behavior hook definitions used by all platforms.
Conceptual example:
When this works: Simple components (buttons) where React and HTML behavior is nearly identical.
When this breaks down: Complex components with DOM-specific requirements (refs, callbacks), platform-specific APIs (React Native a11y props vs. DOM ARIA), framework idioms (hooks vs. mixins).
2. Shared Abstractions, Framework-Specific Definitions
Common patterns and conventions, separate implementations per framework.
Conceptual example:
Benefits: Each framework uses idiomatic patterns, no impedance mismatch.
Cost: Duplication—same logic written multiple times.
3. Hybrid: Sometimes Shared, Sometimes Not
Flexible approach based on component complexity and framework compatibility.
When to share:
When framework-specific:
Sharing strategies:
Evaluating Against Goals
Code Size
Shared definitions: Potentially smaller if truly reused, but abstraction overhead can negate savings.
Framework-specific duplication: Unlikely developers use both React and HTML together, so duplication doesn't increase real-world bundle size. Small utility functions (case conversion like
tabindex→tabIndex,class→className) add negligible overhead and may simplify architecture.Consideration: Don't over-optimize for "statistical noise" code size. Prioritize clarity and maintainability unless bloat is significant.
Maintainability
Shared definitions: Single source of truth—fix once, fixed everywhere. But: harder to reason about (where/how does this run?), harder to predict what changes affect which platforms.
Framework-specific: Clear ownership, easier to understand what code does. But: must update in multiple places, risk of divergence.
AI/LLM context: Tests (unit, integration, regression) catch divergence. AI tools can help propagate changes across implementations. With small teams and AI assistance, duplication may be acceptable if it improves code clarity.
Consideration: Small teams need code that's easy to reason about. Complex abstractions can be harder to maintain than clear duplication with good tests.
Extensibility/Customization
Shared definitions: Users must understand the abstraction layer to customize. Override mechanisms must work across frameworks.
Framework-specific: Users customize using framework-native patterns. Want custom ARIA in React? Override the hook. Want custom behavior in Web Components? Extend the class. No need to understand cross-framework abstractions.
Customization scenario: User wants VJS's A11Y and i18n but with different UI. Framework-specific definitions let them extend using familiar patterns (React hooks, Web Component inheritance). Shared definitions require understanding the abstraction boundary and how to plug in across frameworks.
Consideration: Framework-native patterns may be more approachable for developers extending/customizing components.
Current State & Open Questions
Prototype approach: Framework-specific definitions with shared patterns. Simple components (buttons) have nearly identical code across React and HTML. Complex components may diverge based on framework-specific requirements.
As architecture evolves, the sharing strategy may shift based on:
This decision affects: Codebase organization, maintenance burden, customization patterns, and the learning curve for contributors and consumers extending Video.js.
Core Transformations
The following sections describe common transformation patterns found in behavior hooks. These are illustrative examples of how component state maps to DOM semantics, not prescriptive requirements.
1. State → data-attributes
Purpose: Provide styling selectors for CSS attribute selectors.
Typical patterns: Boolean state as presence/absence (
data-paused), enum state as attribute values (data-volume-level="low"), numeric state as stringified values.Example:
paused: true→data-paused="",volumeLevel: 'low'→data-volume-level="low"Animation states: Some explorations include exposing animation/transition state attributes (e.g.,
data-state="open|closed",data-starting-style,data-ending-style,data-direction="ltr|rtl") to enable CSS transitions, keyframes, and JS animation libraries like Framer Motion or React Native Reanimated.Framework differences: React uses empty string
''for boolean presence; HTML uses truthy/falsy withsetAttribute.2. State → ARIA Properties
Purpose: Enable screen readers and assistive technologies to understand component purpose and state.
Typical patterns:
role(component type),aria-label(state-dependent accessible name),aria-valuemin/max/now/text(for sliders),aria-orientation(directional components).Button example:
paused: true→role="button",aria-label="play"Slider example:
volume: 0.5→role="slider",aria-valuenow="50",aria-valuetext="50%"Framework differences: None—ARIA properties are identical across platforms.
3. State → Tooltips/Labels (Open Question)
Purpose: Provide human-readable feedback about component state and available actions.
Open architectural questions:
Current prototype: Components may set
data-tooltipattributes (e.g.,data-tooltip="Play"when paused), but ownership and patterns are still being explored.4. State → CSS Variables
Purpose: Enable dynamic styling without JavaScript. Used primarily for sliders and components with dynamic visual properties.
Typical pattern: Computed percentages for fill/pointer positions.
Example:
fillWidth: 50→--slider-fill: "50%",pointerWidth: 0.5→--slider-pointer: "50%"Framework differences: React uses style objects; HTML uses
element.style.setProperty().5. Other DOM Props
Purpose: Standard DOM properties for interaction and presentation.
Typical patterns:
tabIndex(keyboard navigation),className/class(styling),aria-disabled(disabled state), user-provided prop spreading (overrides).Example: Interactive component →
tabIndex="0", disabled →aria-disabled="true"+tabIndex="-1"Framework differences: React uses
className, HTML usesclass; otherwise conceptually identical.6. State → Event Handlers (Open Question)
Purpose: Define how components respond to user interaction (clicks, pointer events, keyboard).
Architectural question: Should behavior hooks define event handlers, and if so, how?
Three Approaches Under Consideration
A. Semantic callbacks (framework-agnostic):
Pros: Framework-agnostic contract, clear semantics
Cons: Burden on render layer to map
onButtonPress→onClick/onPointerUp/etc. Harder to share across frameworks.B. Framework-specific callbacks:
Pros: Directly spreadable in React and similar frameworks
Cons: Less intuitive for customization—developers may add their own
onMouseDownand be confused whenonPointerDownalso fires. Debugging which handlers are active becomes harder.C. Defer event handlers (current):
Pros: Avoids premature decisions, treats handlers as one-offs for now (mainly needed for compound components), no assumptions about naming or sharing
Cons: Eventually may need a strategy as patterns emerge
Current state: Prototype primarily handles events in render functions. Event handler patterns in behavior hooks are being deferred until clear use cases and conventions emerge.
How This Achieves Architectural Goals
The behavior hook layer contributes to Video.js's architectural goals, though the specific implementation strategy (shared code vs. shared patterns) affects how these goals are realized.
Use Cases - Simple to Complex
The transformation pattern scales from simple (buttons with basic ARIA and data-attributes) to complex (interactive components with CSS variables, value formatting, orientation handling). Whether this scaling is achieved through shared code or consistent patterns across frameworks is still being explored.
Platform Permutations
Pattern consistency: React and HTML behavior hooks follow identical transformation patterns—same component state produces the same semantic meaning (ARIA roles, data-attributes, etc.), even if the mechanism differs (prop objects vs. setAttribute).
Implementation strategy open question: Whether transformation logic is actually shared (utilities, common code) or just conceptually aligned (same patterns, separate implementations) depends on the code sharing strategy discussion above.
Resilience
Clear layer separation: Behavior transformation is independent of state selection (component state definitions) and independent of rendering (render functions). Can refactor any layer without affecting others.
Override-friendly: Behavior hooks receive user-provided props for customization. Props spread pattern allows consumers to override derived props (e.g., custom
aria-label). Framework adapters can extend or replace behavior hooks for custom behavior.Customization
Framework-native customization: Developers override or extend behavior hooks using their framework's patterns:
Or provide props at component instance level:
Framework Integration
Behavior hooks return prop/attribute objects that must be applied by framework adapters. The integration patterns and challenges are similar to those for component state definitions.
React/React Native: Hooks provide reactivity—behavior hooks return props, React's rendering system automatically applies them when state changes.
HTML/Web Components: Require explicit subscription management and manual DOM updates—behavior hooks return attributes, adapter must subscribe to state changes and call update methods.
For detailed discussion of framework integration patterns, subscription strategies, and HTML/Web Component challenges, see the Component State Pattern discussion.
Open Questions & Future Directions
i18n integration: How should tooltip/label text be localized? Centralized registry? Per-component overrides? Context-based?
Attribute override patterns: Should user props override derived props or vice versa? Currently inconsistent (spread before vs. after).
Formatting logic location: Should time/volume formatting live in behavior hooks, core utilities, or separate formatting layer?
Shared utilities vs. duplication: How much transformation logic should be extracted to shared utilities vs. duplicated between React/HTML for clarity?
CSS variable naming conventions: Should variable names be standardized across components or component-specific?
ARIA value text formatting: Should this use the same formatters as display text, or separate?
Default event handlers: Should behavior hooks provide default onClick/onKeyDown handlers, or leave entirely to render functions?
Connection to Component State Pattern
Behavior hooks are the next layer after component state definitions:
Separation of concerns:
This layering enables independent evolution: can change how props are derived without touching state definitions, can change rendering without touching props derivation.
Influences & References
The behavior hook architecture and many of the concepts discussed in this document are influenced by discussions, documentation, and writeups from:
Framework References:
useButtonThese influences have shaped the thinking around framework-agnostic behavior definitions, accessibility ownership, and the separation between component identity and presentation.
Beta Was this translation helpful? Give feedback.
All reactions