Skip to main content

The adapter contract

An adapter supplies three capabilities the engine-free core cannot provide for itself, and app code reaches all of them through a small set of interfaces from @realitycollective/webxr-uiextensions.

PanelHost and PanelHandle​

PanelHost.createPanel(configJson) turns compiled UIKitML into a live panel and returns a PanelHandle: its root element, getElementById, setTargetDimensions and dispose.

WindowHost​

WindowHost extends PanelHost with the portable window surface: supportsStandalonePanels, a boolean saying whether createPanel works on this host at all, and onPanelReady(listener), which fires as each panel becomes wireable and replays the panels already live. IWSDK reports supportsStandalonePanels: false, because the ECS (entity-component-system) owns panel lifecycles there and createPanel throws; the three.js and XR Blocks host reports true. PanelReadyEvent.kind says what became ready: window for one created through the window factory, panel for a bare panel the adapter noticed, whose id is then the adapter's best stable identifier for it, the config path on IWSDK.

WindowHandle​

Every adapter's createWindow is engine-specific, because the config payload differs per engine, but it always returns a WindowHandle: id, panel (undefined until the document is attached), and onReady(listener), which runs once and fires immediately if the panel is already there.

HeadPoseSource and HandPoseSource​

HeadPoseSource.getHeadPose() supplies the viewer pose each frame, for follow mode and body-locked regions: the camera on desktop, the headset pose in XR. HandPoseSource.getHandPose(hand) supplies a hand's pose as a WebXR grip space, for hand menus; an optional hasHands() reports whether hands can be tracked at all right now, so a hand-locked window can fall back to body-follow placement outside a session. Both interfaces use plain tuples, defined in @realitycollective/webxr-input, the contracts package this family shares with Interactions.

Proving a new adapter conforms​

windowHostContractCases() ships the WindowHost conformance suite as data rather than as tests: each case is a name and a run(setup) that throws an Error on failure, so an adapter runs the cases in whatever test runner it already has. A WindowHostContractSetup wraps what differs per adapter: host, the manager the host applies, createWindow(id), and two optional hooks, attach for a host that attaches panels asynchronously and panelConfig for one that supports bare panels.

import { windowHostContractCases } from '@realitycollective/webxr-uiextensions';
import type { WindowHostContractSetup } from '@realitycollective/webxr-uiextensions';

function makeSetup(): WindowHostContractSetup {
const host = createMyHost();
return {
host,
manager: host.manager,
createWindow: (id) => host.createWindow({ id, config: myConfig() }),
};
}

for (const contractCase of windowHostContractCases()) {
it(contractCase.name, () => contractCase.run(makeSetup()));
}

Both shipped adapters run this suite, so a case failing on a new adapter is a real behavioural difference, not a difference in test style.

More information​