Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

README.md

@a3s-lab/gui

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.

JSX Runtime

/** @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.

Test

npm test