@hozo/engine
v0.4.0
Published
Runtime fallback for genuinely dynamic styles, interactive behavior, and accessibility behavior.
Maintainers
Readme
@hozo/engine
The small amount that genuinely has to happen at runtime.
Hozo compiles away everything it can. What is left is here: styles the compiler could not resolve statically, the interaction states CSS pseudo-classes give you for free on Web and nothing gives you on Native, and the accessibility behaviour that is behaviour rather than markup.
Generated components import what they need. The exception is explicit setup for an optional platform adapter, because Hozo does not add a native module to every application automatically.
What lives here
Dynamic class resolution. A className the compiler could not read — <View className={getVariant()} /> — is resolved on device against a project-wide map that @hozo/metro generates at config time. React Native has no CSS engine, so this is the one place a class name becomes a style object at runtime.
Contextual variants. dark:, sm:/md:/lg:, and viewport-relative sizes are media queries on Web and subscriptions on Native. useHozoDark, useHozoBreakpoint and useHozoViewport are separate stores on purpose: a component using only md: must not re-render on every resize that does not cross a breakpoint.
Native transform controls. rotate-none, scale-none, translate-none, and
transform-none reset their own CSS transform property, not every transform.
For example, rotate-45 scale-95 md:rotate-none keeps its scale when the rotation
is reset. transform/transform-cpu restore the function-slot chain; they do not
switch Native rendering to a CPU backend. Unconditional controls compile into
plain StyleSheet values. Conditional controls and dynamic class strings use one
small slot composer, without extra state or subscriptions; dynamic results stay
cached by class string. transform-gpu still reports a diagnostic because a
browser compositing hint cannot force a Native hardware layer. Transform controls
combined with starting:/exit animation frames, or used as independent
@keyframes controls, remain explicitly unwired. Use explicit transform values
for those animation endpoints.
Native negated variants. not-disabled:, not-enabled:, not-aria-*:,
negated known sibling predicates such as not-first:/not-last:, and negated
supported environment queries such as not-motion-reduce: reuse the positive
predicate, including when stacked with other supported conditions. Prop predicates
need the same readable driver as their positive form; not-disabled: without a
readable disabled fact or an unreadable ARIA state is still a diagnostic, not a false value
to invert. not-enabled: follows the existing enabled: contract: without a
disabled prop, enabled is known true and its negation does not apply. Unknown
sibling positions and unsupported environment queries remain diagnostics too.
Boolean ARIA predicates match the literal true state; an optional state object
can be absent at runtime, and checked: 'mixed' is not treated as checked=true.
Known sibling predicates cost no runtime state; environment queries share their
existing hook. not-focus: and not-focus-visible: on Pressable/Button reuse
their existing interaction callback, including stacked conditions and transitions;
negated focus-visible alone enables the same pointer/keyboard modality inference
as its positive form. Text in that interaction context uses the owner's state,
not an independent text focus target, just as positive focus variants do. No new
store or subscription is added. Other elements without a readable focus driver
remain diagnostics. Dynamic class maps also carry negated ARIA predicates as
compiler-owned !aria-… keys, prepared once by the resolver from the underlying
owner facts. Missing facts remain unassessed, including under negation; no new
owner-state fields or subscriptions are needed. Both ordinary and ordered maps
use this same matcher. Non-ARIA dynamic selector negations such as not-hover:
remain unwired; their static lowering is a separate capability.
Interaction state infrastructure. The canonical HozoPressable in @hozo/primitives tracks
pressed, hovered, focused and focus-visible state using the shared helpers owned here. Focus-visible
follows the input modality, so a tap does not draw a focus ring and a Tab key does.
Web responder negotiation. Canonical View, Pressable, and ScrollView, plus the optional
touchables in @hozo/rn-compat, use one responder state machine. Pointer capture and
bubble negotiation map to React Native's grant, start, move, end, release, rejection, and
termination callbacks; one winning surface retains all active pointers until the final release.
ScrollView installs these handlers only when the author supplied a responder contract, so an
ordinary scroll container keeps the browser's unmediated scrolling path.
This is a lifecycle compatibility layer, not an attempt to make browser input delivery identical to React Native. Pointer/touch coalescing, native dispatch timing, gesture recognition, and the platform's choice of synthesized mouse events remain platform behavior. Hozo normalizes ownership and callback order where an application explicitly asks for the responder API; it does not replace Pointer Events or the native input system.
Transitions. transition-* utilities compile to Animated timings, including colour interpolation, with the blend point preserved when an interrupted transition restarts.
Generated-code infrastructure. Project facts, style normalization, navigation protocol,
responder state and optional adapter configuration live here, and what compiled output calls is
published leaf by leaf under ./generated/*. React components emitted only to reproduce browser
layout on Native live behind @hozo/primitives/generated/* instead.
Optional backdrop blur. Static StyleX backdropFilter: 'blur(Npx)' on an ordinary View
uses a configured Native component. Expo users can keep expo-blur in the application rather
than making it a Hozo dependency:
import { BlurView } from 'expo-blur'
import {
configureHozoBackdropFilter,
createExpoBlurAdapter,
} from '@hozo/engine'
configureHozoBackdropFilter(
createExpoBlurAdapter(BlurView, {
props: { tint: 'default' },
// Optional: Expo intensity and a CSS pixel radius are different scales.
intensityForRadius: (radius) => Math.min(100, radius * 4),
}),
)On Android, Expo's real background blur additionally needs a BlurTargetView around the content
and its ref passed as blurTarget; pass that and the selected blurMethod through props. The
adapter cannot infer the target across the React tree. Hozo supports one static non-negative
blur(px) (plus the no-op none); filter chains, conditional blur, and non-View contexts remain
explicit diagnostics or official StyleX residuals.
Platform split
index.ts is the Web build and index.native.ts the React Native one, selected by the
react-native export condition. Platform-free infrastructure stays independently testable;
rendering boundaries and their layout solvers are owned by @hozo/primitives.
Part of Hozo, a universal UI framework for React Native, compiled by Rust, with native semantics on every platform, first-class accessibility, and diagnostics for humans and AI agents alike. Most applications install @hozo/core and one build integration; see Getting started. Source and issues: github.com/iray-tno/hozo.
