JSX protocol bridge for a3s-gui.
This package provides the protocol-side JSX runtime used to emit the
serializable UiFrame format consumed by the Rust runtime. The package has no
runtime dependencies while the compiler integration stabilizes.
React Aria-compatible component names, semantic UI component names, intrinsic
HTML element names, and intrinsic SVG element names are accepted as JSX tags.
style can be an object or CSS text string, and className is preserved for
the Rust-side Tailwind utility resolver, including variant-prefixed utilities.
CSS text parsing preserves delimiters inside strings, functions, and URLs.
/** @jsxImportSource @a3s-lab/gui */
import {Button, createAction, createUiFrame, defineAction} from '@a3s-lab/gui';
const saveProfile = createAction('saveProfile', 'Save profile');
const closeProfile = createAction('closeProfile', 'Close profile');
const root = (
<Button className="primary" onPress={saveProfile}>
Save
</Button>
);
export const frame = createUiFrame('profile', root, {
window: {
title: 'Profile',
onClose: closeProfile,
width: 640,
height: 480,
minWidth: 480,
},
actions: [defineAction(saveProfile), defineAction(closeProfile)],
});When labels are not needed, createUiFrame can infer actions from JSX event
props. Frame ids must be non-empty strings, and frame roots must be a single
compiled element; wrap fragment children in Group or another container.
Compiled node keys and element tags must be non-empty, and sibling node keys
must be unique.
Explicit frame actions must also use non-empty, unique string ids. Use
defineAction when the host needs action metadata beyond the stable id.
Inferred actions preserve labels from createAction(id, label) handlers. Empty
event action ids are ignored by Rust routing instead of being dispatched.
Window dimensions accept positive finite numbers or non-empty numeric strings,
and explicit width/height values must stay within any declared min/max
constraints.
window.resizable accepts booleans or boolean strings and is emitted as a
protocol boolean.
window.onClose accepts the same action-like values as JSX events and is routed
from native close events.
Focus, value, and toggle aliases such as onFocusChange, onInput,
onToggle, and onExpandedChange are preserved in the emitted protocol
alongside press, change, selection, and keyboard events.
Rust hosts route explicit onKeyDown handlers on the target or its ancestors
first; otherwise Enter and Space key-down events can activate press actions on
buttons, links, and menu items.
They also normalize keyboard activation for stateful controls into toggle or
selection events so action payloads carry checked, expanded, or selected values.
The runtime accepts React Aria-style state props such as isDisabled and HTML
or ARIA aliases such as disabled, required, aria-expanded,
aria-selected, min, max, step, and aria-valuenow; these normalize to
the same native control-state fields consumed by the Rust renderer. HTML state
aliases are also retained under their original Web JSX attribute names. ARIA
relationship props such as aria-labelledby, aria-describedby, and
aria-controls are preserved and projected into native accessibility
relationship hints. ARIA description and value props such as aria-description,
aria-roledescription, aria-keyshortcuts, and aria-valuetext are projected
into native accessibility description hints. ARIA structure props such as
aria-level, aria-posinset, aria-setsize, aria-rowindex, and
aria-colindex, plus aria-sort, are projected into native accessibility
structure hints. ARIA state and live-region props such as aria-hidden,
aria-autocomplete, aria-multiline, aria-current, aria-pressed,
aria-haspopup, aria-live, and aria-busy are preserved and projected into
native accessibility state hints.
Intrinsic global and form-control props such as title, hidden, lang, dir,
tabIndex, role, accessKey, contentEditable, draggable, spellCheck,
translate, inert, popover, anchor, is, nonce, readOnly, multiple, autoFocus,
slot, part, exportParts, itemScope, itemProp, itemType, itemID,
itemRef, autoComplete, inputMode, enterKeyHint, autoCapitalize,
autoCorrect, virtualKeyboardPolicy, pattern, minLength, maxLength,
rows, cols, size, dialog open, formAction, formEncType,
formMethod, formTarget,
formNoValidate, accept, capture, alt, href, src, srcSet, sizes,
loading, decoding, fetchPriority, crossOrigin, referrerPolicy,
poster, controls, autoPlay, playsInline, preload, srcLang, list,
dirname, colSpan, rowSpan, headers, scope, abbr, span, start,
reversed, list type, li value, download, ping, rel, hrefLang,
link as, integrity, blocking, nonce, imageSrcSet, imageSizes,
script async, defer, noModule, iframe allow, allowFullScreen,
sandbox, srcDoc, button command, commandFor, popoverTarget,
popoverTargetAction, quote/change cite, change dateTime, time dateTime,
label htmlFor, output for, and meter low, high, and optimum are
preserved with their Web JSX names and projected by the Rust bridge into native
control, activation, text annotation, form association, and resource policy
hints.
For protocol-native aliases such as media resource fields, form submission
overrides, track labels, intrinsic dimensions, and boolean playback flags, the
SDK also fills the corresponding compiled UiFrame fields while retaining the
original Web JSX attribute names.
Marker exports cover the same semantic component names accepted by the Rust
compiler bridge, including document, text, landmark, disclosure, figure,
description-list, media, form, selection, overlay, tab, menu, toolbar, and
table structures. The emitted frame contains semantic JSX element names.
For intrinsic input tags, type="range" normalizes numeric value and
defaultValue props to valueNumber. type="number" also fills
valueNumber, while preserving the text value needed by native text-field
backends.
Numeric control props accept finite numbers and non-empty numeric strings.
Empty strings, whitespace, booleans, and other non-numeric values are ignored
for native numeric fields so transient form state does not become 0 or 1.
For intrinsic textarea tags, direct text children remain in the compiled
children list and are projected by the Rust bridge as the native text-field
value when no explicit value is supplied.
The emitted frame is plain JSON:
{
"frameId": "profile",
"window": {
"title": "Profile",
"onClose": "closeProfile",
"width": 640,
"height": 480,
"minWidth": 480
},
"actions": [
{"id": "saveProfile", "label": "Save profile"},
{"id": "closeProfile", "label": "Close profile"}
],
"root": {
"kind": "element",
"key": "Button",
"tag": "Button",
"props": {"className": "primary", "events": {"onPress": "saveProfile"}},
"children": [{"kind": "text", "key": "text-0", "value": "Save"}]
}
}The package also exports protocol types and helper constructors for native render responses, host event responses, embedded runtime event batches, handled native event results, and rendered accessibility trees with host node ids. These helpers mirror the Rust serde envelopes for mock hosts and process-boundary tests. Response helpers validate accessibility tree shape, action invocations, interaction changes, native runtime batch diagnostics, and native command envelopes, including command-specific fields for create, update, insert-child, remove, and set-root commands, before returning serializable objects.
npm test