@realitycollective/iwsdk-uiextensions 0.1.0 → 0.1.1-preview.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,9 +1,60 @@
1
1
  # Changelog
2
2
 
3
- Change log for the Reality Collective WebXR UI Extensions packages. All four packages are versioned and released together; the version below is the one carried by the `v<version>` release tag.
3
+ Change log for the Reality Collective WebXR UI Extensions packages. All five packages are versioned and released together; the version below is the one carried by the `v<version>` release tag.
4
4
 
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Preview builds are not listed separately. The entry for a version accumulates while its previews are published, and is dated when that version is released.
6
6
 
7
+ ## [0.1.1]
8
+
9
+ ### Added
10
+
11
+ - `@realitycollective/threejs-uiextensions` - a plain three.js / WebXR binding. `UixWindowHost`, `UixPanelDocument`, `configureRendererForUikit` and `fitScale` moved here from `@realitycollective/xrblocks-uiextensions` unchanged (the XR Blocks scene graph is plain three.js already, so its host had no XR-Blocks-specific code in it), plus a new `pointerBridgeFactory` option on `UixWindowHost` so a platform's own interaction source can drive the same host - `@realitycollective/xrblocks-uiextensions`'s `UixWindowHost` is now a thin subclass that supplies `XrBlocksPointerBridge` in place of this package's own bridge, and every other export keeps its old import path through a re-export. New in this package: `ScenePointerBridge` (`pointer-bridge.ts`), which resolves a hit-tested ray, a proximity-tested grab or touch, into the same `pointerdown`/`pointerup`/`click`/`pointerenter`/`pointerleave` events and `TouchPress` samples `XrBlocksPointerBridge` produces from XR Blocks' own callbacks; `connectWebXrPointerInput` (`webxr-input.ts`), which wires that bridge straight to a live WebXR session through `renderer.xr` - `selectstart`/`selectend`/`squeezestart`/`squeezeend` on three's own controller objects for ray and near/grab, and a per-frame `index-finger-tip` joint poll for touch - with no engine SDK beyond three.js itself; and `connectUIExtensions` (`setup.ts`), the package's one setup entry point, mirroring `xrblocks-uiextensions`'s own `connectUIExtensions` for a scene with no XR Blocks Script. `webxrRayInputAccess` (`ray-input.ts`) reads a title-bar ray drag's live ray from three's own `getController(i)` objects, so a drag rides the ray at a fixed grab distance (`beginDrag`/`dragPosition`), the same laser-distance math every other platform uses. Runs the shared `windowHostContractCases`, `sceneTargetContractCases` and `uixElementContractCases`, a core-parity suite (follow equals `stepFollow`, region and hand-menu defaults, title default, control upgrades), and a rule-by-rule suite mirroring `xrblocks-uiextensions/test/pointer-bridge.test.ts` for touch press, controller poke, ray click on release, hover, hold-to-drag with a per-window `dragDelay`, near drag, laser-distance ray drag, billboard while dragging, drop capture and focus bias including a docked window. `CursorVisual` (`cursor-visual.ts`) closes `ui/pointer-cursor` for this binding: a disc drawn at each ray's current hit on a panel, oriented to the hit surface (or the ray's own orientation with no surface normal to read), shown and hidden only by whether the ray currently hits something - never by hover or focus state. XR Blocks is excused from this rule (its SDK draws its own reticle with no seam to observe) but plain three.js owns rendering, so nothing drew one here until now. `ScenePointerBridge.rayCast` reports the world-space hit point and surface normal a cursor needs, alongside the existing `rayHit` (now a thin projection of it); `connectWebXrPointerInput` draws one `CursorVisual` per controller slot from the same per-frame ray cast hover already runs, given a `scene` (`connectUIExtensions` passes its own automatically).
12
+ - `@realitycollective/webxr-uiextensions` - `core/cursor.ts`'s `cursorPlacement` and `DEFAULT_CURSOR_OFFSET` (0.004 m): the one placement rule `ui/pointer-cursor` states as pure logic - a cursor disc's hit point nudged a fixed distance off the surface along its own facing, so it never z-fights that surface, restated from IWSDK's `@iwsdk/xr-input` `CursorVisual.updateFromIntersection`'s `zOffset`. The orientation itself reuses the core's existing `faceViewer`/`rotate` primitives rather than adding a new one. `@realitycollective/threejs-uiextensions`'s `CursorVisual` is the first binding to run it.
13
+ - `stepFollow()`, the follow rule every following window and region uses, as pure logic in the core: IWSDK's `FollowSystem` in its `PivotY` behaviour restated step for step, with `enterFollow()`, `followOffsetFromPose()`, `resolveFollow()`, the yaw helpers (`yawOf`, `yawQuaternion`, `rotateByYaw`, `yawToward`, `strictFollowTarget`) and the defaults `DEFAULT_WINDOW_FOLLOW` (offset `[0, -0.15, -1.2]`, speed 3, tolerance 0.35 m, 30 degrees) and `DEFAULT_REGION_FOLLOW` (IWSDK's `Follower` defaults: offset `[0, -0.2, -1.4]`, speed 1, tolerance 0.4 m). A new IWSDK test drives IWSDK's own `FollowSystem` headlessly beside `stepFollow` over the same head motion and requires the same position and facing every frame, so the core rule cannot drift from the reference.
14
+ - Placement rules as pure logic: `focusBiasAmount()` and `applyFocusBias()` (the focused window is drawn 0.02 m nearer per window behind it), `regionSlotPose()`, and the window defaults `DEFAULT_FOCUS_BIAS` (0.02 m), `DEFAULT_DRAG_DELAY` (0.3 s) and `DEFAULT_BILLBOARD_WHILE_DRAGGING` (true). `RayTuple` is re-exported with the other geometry names.
15
+ - `@realitycollective/native-uiextensions` - `nativeUiHostConformanceCases()`, the host conformance kit: ten runner-free host cases, named `ui/<master row>`, that a native app runs on its device against its real `ui` slice. They check that the host draws a window at the pose it is handed, follows the core rule in `body-follow` and `head-locked`, hides a hand menu with no tracked hand, applies the title, chrome and labels, hides and restores, applies focus bias, and measures a fingertip's signed distance positive in front. The host provides the test readbacks of the new `NativeUiTestHost` type (`windowPose`, `windowHidden`, `elementProperties`, `measureTouch`) in a test build. Against the reference fake every case passes, and the kit is shown to fail a host that misplaces a window or drops the sign of a distance.
16
+ - `uixElementContractCases()`, a shared conformance suite for `UixElement`, the interface the core's controls drive panel elements through, with `CONTRACT_PANEL_MARKUP` and the `UixElementContractSubject`, `UixElementContractDriver` and `UixElementContractCase` types. Every platform builds the same small panel from the markup its own way, and six cases check it: children follow the markup in order, `tagOf` reads a `uix-*` tag and nothing for a built-in one, `data-uix-*` attributes arrive camelCased in `userData`, `setProperties` reaches what the platform shows, one event reaches every listener on its element and no other, and a core stepper upgraded from the markup follows clicks to its maximum. It runs against real uikit elements on IWSDK and on XR Blocks and three.js, built headlessly the way each platform builds them, and against `NativeWindowHost` over the fake native app. It found the native defect under Fixed.
17
+ - `dataAttributeKey()`, the one rule for where a `data-*` attribute lands in `userData`: `data-uix-min` becomes `uixMin`, and an already camelCased `dataUixMin` gives the same key. The IWSDK component set and the native proxy elements both use it; the IWSDK copy was private.
18
+ - `sceneTargetContractCases()`, a shared conformance suite for `SceneTarget`, shipped as data beside `windowHostContractCases()`, with `CONTRACT_SCENE` and the `SceneTargetContractSetup` and `SceneTargetContractCase` types. A client builds its UI with the core's `applyScene()` and never asks which platform draws it, so every host must turn the same descriptor into the same windows. The suite checks that through the host's `WindowManager`: every descriptor window is opened under its id and title, a window named into a region is in that region, a window keeps its dock mode and chrome, and a window given both a region and a dock mode is world-locked in that region. The IWSDK, XR Blocks and native hosts all pass it.
19
+ - `MemoryWindowHost`, `MemoryWindowHandle`, `MemoryPanel` and `createMemoryWindowHostSetup()` in `@realitycollective/webxr-uiextensions`, an in-memory `WindowHost` and the family's mock. The package shipped `windowHostContractCases()` but nothing to run them against without an engine, so every headless consumer and every new adapter had to write a host first. The mock opens windows on a real `WindowManager`. A window's panel attaches when the test calls `attach(id)`, as on IWSDK, and closing a window through the manager disposes its panel and drops it from `onPanelReady` replay. `dispose()` closes every window it opened and forgets its listeners, as every platform host does. `createMemoryWindowHostSetup()` returns a fresh `WindowHostContractSetup`, so the suite runs in one loop. It passes every contract case, which `test/memory-window-host.test.ts` checks, and it is inside the 100% coverage gate. It came from a team building a native host, whose version passed the suite under Node and Hermes.
20
+ - `dispose()` on `WindowHost`, implemented by every platform host: `IwsdkSceneHost`, `UixWindowHost` (XR Blocks and three.js) and `NativeWindowHost`. It means the same thing everywhere: close every window the host opened through its `WindowManager`, so each goes down the normal close path, then release the host's own subscriptions and listeners, so `onPanelReady` replays nothing afterwards. It is safe to call twice. The XR Blocks host also removes the region groups it added to the scene. The IWSDK host silences its readiness system, which stays registered on the world, and forgets itself, so a later `createSceneHost(world)` builds a fresh host. No host could be torn down before, so an app that rebuilt its UI leaked listeners and scene objects on every platform. `windowHostContractCases()` gains a seventh case that holds every host to it. This adds a required member to the `WindowHost` interface, so a host written outside this repository must implement it.
21
+ - `@realitycollective/native-uiextensions` - the native app adapter, for an OpenXR or visionOS app that embeds a JavaScript engine such as Hermes, renders panels itself and installs `globalThis.__rcHost`, per the Reality Collective native host contract shared across the WebXR family repositories. `NativeWindowHost` implements the core `WindowHost` and `SceneTarget`, as the IWSDK and XR Blocks hosts do, so `applyScene` builds the same scene on native; regions go to the host as `createRegion` and are removed on `dispose`. Bare panels and windows build a tree of internal proxy `UixElement`s over the host's `NativeElementNode` tree, so the existing `data-uix` control upgraders work unchanged; `setProperties` forwards to `host.setProperties`, and `addEventListener` is routed from the host's single `onElementEvent` callback by `(panelId, elementHandle)`. Every `WindowManager` state change (open, focus, minimize, hide, dock, chrome, hand menu) mirrors to the host as `applyWindow(record)`; closing a window disposes its panel (once attached) and calls `closeWindow`. No engine dependency - the package reads and writes only plain data across the host boundary. Ships with an in-memory fake of the `ui` slice under `test/helpers` and runs the shared `windowHostContractCases()` against it.
22
+ - `demos/webxr-multiplatform` - `?uix-engine=<engine>&uix-autostart=1` boots that pipeline at once instead of showing the launch screen. It exists for the post-deploy smoke test, which now loads the desktop and IWSDK pipelines of the deployed lab as well as its launch screen; before this a pipeline that failed to start was invisible to CI, because nothing pressed START. The XR Blocks pipeline is not smoked: xrblocks renders into `<body>` rather than the mount point and logs a `console.error` about the three.js revision it wants, both of which the smoke test counts as failures.
23
+ - `dragDelay` on `WindowOptionsBase`: seconds a title-bar ray press is held before it becomes a drag, per window, default `DEFAULT_DRAG_DELAY` (0.3 s). Every platform already had the constant; only IWSDK's `UIWindow` component read it per window, and even there `createUIWindow` never passed the option through, so it was unreachable everywhere. Native and XR Blocks now take it too.
24
+ - `@realitycollective/webxr-uiextensions` - two core modules extracted from `NativeWindowHost`, the only binding that had this orchestration before: `pointer-events.ts` (`EdgePress`, the press/release/click state machine for a boolean-active ray or grab pointer, and `dispatchTouchUpdate`, which turns a `TouchPress` update into the same three events) and `titlebar-drag.ts` (`TitlebarDragController`, hold-to-drag timing, drag math, billboard-while-dragging and drop capture as one class a binding drives per window). `NativeWindowHost` now runs both with no behaviour change; `@realitycollective/xrblocks-uiextensions` runs `TitlebarDragController` too (see below). `EdgePress` is not needed by XR Blocks: its own select/grab callbacks are already edge-triggered, one start/end pair per gesture, so there is no boolean to edge-detect there.
25
+ - `@realitycollective/xrblocks-uiextensions` - `pointer-bridge.ts`'s `XrBlocksPointerBridge`, attached automatically to every panel `UixWindowHost` creates. It runs the IWSDK pointer rules: a poke (touch) and a controller tip both press and release through the core `TouchPress`, from `onObjectTouching`/`onObjectTouchEnd`; a ray clicks on `onSelectEnd` (release), never on `onSelectStart` (intersection); hover forwards `onHoverEnter`/`onHoverExit` to `pointerenter`/`pointerleave`; a title-bar press runs the core `TitlebarDragController` - a ray waits out `dragDelay`, a grab (near drag, gated to the title bar) starts at once, both billboard while dragging by default (`billboardWhileDragging`), and a drop within a region's snap radius docks the window into it. The demo pipeline's `onSelectStart` override that raycast and clicked on intersection is gone; `pointer-forward.ts` stays for a caller driving its own raycaster outside the host.
26
+ - `@realitycollective/xrblocks-uiextensions` - `ray-input.ts`'s `rayOf`/`XrBlocksRayInputAccess`, read from a new `rayInput` option (`input` on `connectUIExtensions`, mirroring `input: xb.input` on `@realitycollective/xrblocks-interactions`' own provider). A title-bar RAY drag now rides the controller's live ray at a fixed grab distance - the core `beginDrag`/`dragPosition` laser math IWSDK and native use, via a new core helper, `intersectRayPlane` (`drag-math.ts`), which seeds the drag session from where the ray meets the title bar's own plane, since XR Blocks' `SelectEvent` carries no hit point of its own. With no `rayInput` wired (or no ray source for that controller yet), a ray drag falls back to the controller's own position delta - correct, but not laser-distance - the same fallback this host used before `rayInput` existed. A grab (near drag) is unaffected: it already rode the hand's own position, which is exactly how native's grab pointer works too.
27
+ - `@realitycollective/xrblocks-uiextensions` - focus bias (`applyFocusBias`): the focused window is now drawn 0.02 m nearer the viewer per window behind it, every frame, as IWSDK and native already do - including a DOCKED window. `layoutRegion` now uses the core `regionSlotPose` (rotating the slot offset by the region's own orientation, which the previous hand-written version did not) and runs every frame, not only on a dock change, so a docked window's pre-bias position is always fresh before bias nudges it - `update()`'s three passes (drag, then regions, then placement and bias) now match `NativeWindowHost`'s order.
28
+
29
+ ### Changed
30
+
31
+ - The reference is now IWSDK 1.0. `@realitycollective/iwsdk-uiextensions` peers on `@iwsdk/core >=1.0.0 <2.0.0` (was `>=0.5.0 <0.6.0`) and is built and tested against 1.0.0. IWSDK 1.0's FollowSystem honours the height of the follow offset, where 0.5 set a following window at head height, so the core `stepFollow` now does the same on every platform: a body-follow or head-locked window sits 0.15 m below eye level by default, and a following region 0.2 m below. `follow-reference.test.ts` proves the core equal to IWSDK 1.0's own FollowSystem frame by frame.
32
+ - `@realitycollective/xrblocks-uiextensions` - follows by the core rule. `body-follow` and `head-locked` windows now move by `stepFollow`, the same lazy yaw follow IWSDK applies: a snap on entering the mode, the 0.35 m dead zone with a 30 degree angle limit, a lerp of `dt * speed`, and following from where the window was left when it is unpinned. Before this XR Blocks eased with its own exponential approach, had no angle limit or snap, and did not move a `head-locked` window at all. A following region now uses IWSDK's region defaults (speed 1, tolerance 0.4 m) rather than speed 3 and 0.2 m. A window with no title is titled with its id, as on every platform, and every panel's `data-uix` controls are upgraded automatically, as IWSDK's `UIControlsSystem` does (`controls: false` opts out). `follow-math.ts` stays exported and unchanged for existing callers.
33
+ - **Breaking for native hosts:** `@realitycollective/native-uiextensions` now applies every UI rule itself, and a native host is handed results. Before this, the binding mirrored `WindowManager` records to the host and left the host to place windows, write chrome, run the touch press and decide clicks, and a native host that re-derived those rules drifted from the web on chrome, dock modes, touch press, click timing and more. Now the binding runs, from the core, what IWSDK's `UIWindowSystem`, `UIDockSystem`, `FollowSystem`, `UIDockRegionSystem`, `UIDragSystem`, `UITouchGuardSystem` and `UIControlsSystem` do. What a native host must absorb:
34
+ - Implement `setWindowPose(windowId, pose, depthOrder)` and draw and hit-test each window exactly there. Never place a window yourself; the pose already carries the follow rule, hand-menu anchor, region slot, drag and focus bias.
35
+ - Implement `onPointerSample(cb)` and report what each pointer measures: `ray` (every frame, with `ray` and `active` for select), `touch` (while near a panel, with `signedDistance` along the panel normal, positive in front) and `grab` (every frame, with `point` and `active` for squeeze or pinch). Stop raising `pointerdown`, `pointerup` and `click` yourself; the binding raises them, and a click now fires on RELEASE for rays too, as on IWSDK. `onElementEvent` stays for hover and value changes.
36
+ - `applyWindow(record)` carries only two fields a host applies: `hidden`, which now means "not shown" (hidden, or a hand menu whose palm gate is closed), and `dockMode`. Title, chrome button `display`, the PIN and MIN labels and the minimized content arrive through `setProperties`.
37
+ - `createRegion` and `removeRegion` are gone from the `ui` slice: regions are the binding's.
38
+ - Give the binding the `input` slice (`getHeadPose()`, and `sample()` for grip poses) and frames (`update(dt)`, or `attachToHost: true` over `__rcHost.onFrame`), as `createNativeInteractions` takes them.
39
+ - `@realitycollective/native-uiextensions` - a window's `WindowManager` record opens when its panel attaches, as IWSDK's `UIWindowSystem` opens it on adoption, not synchronously in `createWindow`. `manager.has(id)` is false until `onReady` fires. `NativeWindowHostOptions` gains the `registerUIExtensions` switches (`drag`, `nearDrag`, `regions`, `controls`, all on) and `touchPress` thresholds, and `CreateWindowOptions` gains `billboardWhileDragging`.
40
+ - `head-locked` is defined: inside an immersive session it follows exactly as `body-follow` does, as IWSDK does because its `ScreenSpaceUISystem` hands the panel back to world space while presenting. Only outside a session is it pinned to the screen. The core's dock-state comments said "rigidly attached to the view", which no platform does in a session.
41
+ - `@realitycollective/iwsdk-uiextensions` - the `UIWindow` and `UIDockRegion` component defaults and the `createUIWindow` and `createDockRegion` factory defaults now read the core constants (`DEFAULT_REGION`, `DEFAULT_WINDOW_FOLLOW`, `DEFAULT_REGION_FOLLOW`, `DEFAULT_FOCUS_BIAS`, `DEFAULT_DRAG_DELAY`, `DEFAULT_BILLBOARD_WHILE_DRAGGING`), so there is one set of defaults, in the core, for every platform. No value changed: the region defaults stay `column`, pitch 0.35 m, 2 columns, capacity 0, snap radius 0.5 m, as published in 0.1.1-preview.0.
42
+ - `tagOf()` reads nothing for a built-in element on every platform, as its doc always said. The XR Blocks and three.js parser records a built-in tag such as `span` the same way as a custom one, so `tagOf` read `"span"` there and nothing on IWSDK. A custom element name always holds a hyphen, so `tagOf` now returns a name only when it has one.
43
+ - `WindowManager` holds one rule for regions: a window in a region is always world-locked, because the region places it. Opening a window into a region, or `dockTo`, makes it world-locked whatever dock mode it asked for; `dockTo` emits `regionChanged` and then, if the mode changed, `dockChanged`. Setting a follow mode with `setDockMode` or `togglePin` takes a docked window out of its region first. The hosts disagreed before this: the XR Blocks host forced a docked window to world-locked on its own, while the IWSDK and native hosts left the manager's record in the requested follow mode. The same scene descriptor therefore laid out differently by platform. The XR Blocks host's own copy of the rule is gone. The native host now sends the native app the manager's dock mode for a new window, not the one the options asked for.
44
+ - `@realitycollective/iwsdk-uiextensions` - `TouchPointerLike` (the parameter type of `sampleOf`) is exported, and `UITouchGuardSystem` uses the core's `Hand` type rather than a private duplicate. `@realitycollective/xrblocks-uiextensions` exports `UikitComponentLike` (`UixPanelDocument.rootComponent`) and `InteractiveLike` (the return type of `pickInteractive`); `@realitycollective/uix-devtools` exports `CompileOptions` (the options of `compilePanelSource`). Each was a module-private type that appeared in an exported signature, so a consumer had nothing to name and TypeDoc reported it as referenced but undocumented.
45
+ - **Breaking:** `HandPoseSource.hasHands()` is gone. It existed only to let `@realitycollective/xrblocks-uiextensions` keep a hand-locked window shown, by body-follow, when no hand was tracked - a behaviour IWSDK and native never had. A hand-locked window with no tracked hand is now hidden on every platform, as `evaluateHandMenu` already decides: a platform never decides a behaviour the reference has not. `webxrHandPoseSource` no longer implements the method.
46
+
47
+ ### Fixed
48
+
49
+ - `@realitycollective/native-uiextensions` - a window spawned without a title showed a blank title bar until a later record change. The plain options the host received defaulted the title to `''` while the record defaulted it to the window id. Both now use the id, and the binding writes it to `uix-title` itself.
50
+ - `@realitycollective/native-uiextensions` - `data-uix` controls were never upgraded on native: nothing called `upgradePanel`, so steppers, toggles, expandable labels and log views rendered but did nothing. Every panel, window or bare, is now upgraded when it is built.
51
+ - `@realitycollective/native-uiextensions` - the `data-uix-*` attributes of a native panel never reached the controls. `NativeElementNode` had no field for them, so on native every control ran on its defaults: a stepper lost its range, step and starting value, a toggle its labels and colours, and every control its `data-uix-id`, so `controls.stepper("count")` found nothing. `NativeElementNode` gains `attributes`, the element's `data-*` attributes as the markup wrote them, and the proxy lifts them into `userData` with `dataAttributeKey()`. The native app must send them.
52
+
53
+ - `demos/webxr-multiplatform` - the IWSDK pipeline failed to start on the deployed lab, for every visitor, with `Failed to resolve module specifier "three-mesh-bvh"`. The Vite config externalised `three-mesh-bvh` alongside the optional integrations xrblocks imports lazily, but `@iwsdk/core` imports it for real, so the IWSDK chunk shipped a bare import the browser cannot resolve. It resolves in the workspace through `@iwsdk/core`'s own dependency and is now bundled like `three-pathfinding`. Found while photographing the live lab for the Reality Collective site.
54
+ - `@realitycollective/iwsdk-uiextensions` - the README and `Examples/controls` still showed the `data-uix="stepper"` / `data-uix-role` attribute form that the core stopped upgrading when controls became custom elements, so the shipped example silently produced no controls. Both now use `<uix-stepper>`, `<uix-toggle>`, `<uix-expandable-label>` and `<uix-log-view>` with their part elements, and both register `uixComponentSet` when creating the world, which IWSDK 0.5 needs before it will parse a panel that uses a control. The README says why.
55
+ - `docs/developer-cycle.md` - the publishing section described GitHub Packages ("no npmjs.com for now"); the packages publish to npmjs.com under the `@realitycollective` scope through `publish-npm.yml`, and the section now describes that workflow, its two dist-tags and the consumer install commands.
56
+ - `@realitycollective/iwsdk-uiextensions` - `createUIWindow` never read `options.dragDelay`: the `UIWindow` component always opened with `DEFAULT_DRAG_DELAY` regardless of what a caller asked for, even though the component field and `UIDragSystem` already honoured a per-window value. It is now read through, alongside the new `WindowOptionsBase.dragDelay`.
57
+
7
58
  ## [0.1.0] - 2026-09-17
8
59
 
9
60
  ### Added
@@ -52,6 +103,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
52
103
  - `@realitycollective/webxr-uiextensions` takes its geometry vocabulary from `@realitycollective/webxr-input` at `^0.1.1` rather than redeclaring it. `Vec3Tuple`, `QuatTuple`, `HeadPose`, `HeadPoseSource` and `PointerSample` are now that package's types, re-exported under the same names, so no import changes for a consumer. `PointerSample` is its `RayTuple`, which is what lets an input provider written against the shared contracts feed this contract unchanged. It is the core's only runtime dependency: the contracts package is engine-free and carries none of its own, and `test/architecture.test.ts` now allows exactly that one name and fails on any other.
53
104
  - `PointerSample` states its ownership rule: a delivered sample belongs to the listener and the source never writes to it again, so the core's hold-to-drag and drag maths may keep a press-time sample without copying. It mirrors the rule `@realitycollective/webxr-input` 0.1.3 writes on `InputSourceSnapshot`, so a provider feeding both contracts has one promise to keep.
54
105
 
106
+ [0.1.1]: https://github.com/realitycollective/WebXR-UIExtensions/compare/v0.1.0...development
55
107
  [0.1.0]: https://github.com/realitycollective/WebXR-UIExtensions/releases/tag/v0.1.0
56
108
 
57
109
  ### Fixed
@@ -23,27 +23,27 @@
23
23
  <div class="panel">
24
24
  <div class="row">
25
25
  <span class="label">Volume</span>
26
- <div data-uix="stepper" data-uix-id="volume" data-uix-min="0" data-uix-max="10" class="stepper">
27
- <div data-uix-role="decrement" class="stepper-button">-</div>
28
- <span data-uix-role="value" class="stepper-value">.</span>
29
- <div data-uix-role="increment" class="stepper-button">+</div>
30
- </div>
26
+ <uix-stepper data-uix-id="volume" data-uix-min="0" data-uix-max="10" class="stepper">
27
+ <uix-decrement class="stepper-button">-</uix-decrement>
28
+ <uix-value class="stepper-value">.</uix-value>
29
+ <uix-increment class="stepper-button">+</uix-increment>
30
+ </uix-stepper>
31
31
  </div>
32
32
  <div class="row">
33
33
  <span class="label">Music</span>
34
- <div data-uix="toggle" data-uix-id="music" data-uix-on-color="#1f6d3d" data-uix-off-color="#5a3037" class="toggle">
35
- <span data-uix-role="label" class="toggle-label">.</span>
36
- </div>
34
+ <uix-toggle data-uix-id="music" data-uix-on-color="#1f6d3d" data-uix-off-color="#5a3037" class="toggle">
35
+ <uix-label class="toggle-label">.</uix-label>
36
+ </uix-toggle>
37
37
  </div>
38
- <div data-uix="expandable-label" data-uix-id="details" data-uix-lines="2" data-uix-chars-per-line="40"
38
+ <uix-expandable-label data-uix-id="details" data-uix-lines="2" data-uix-chars-per-line="40"
39
39
  data-uix-text="Long descriptive text collapses to two lines with an ellipsis, and the more/less affordance expands it in place - no scrolling, no clipping."
40
40
  class="long">
41
- <span data-uix-role="text" class="long-text">.</span>
42
- <div data-uix-role="toggle" class="long-toggle">more</div>
43
- </div>
44
- <div data-uix="log-view" data-uix-id="log" class="log">
45
- <span data-uix-role="line" class="log-line">.</span>
46
- <span data-uix-role="line" class="log-line">.</span>
47
- <span data-uix-role="line" class="log-line">.</span>
48
- </div>
41
+ <uix-text class="long-text">.</uix-text>
42
+ <uix-more class="long-toggle">more</uix-more>
43
+ </uix-expandable-label>
44
+ <uix-log-view data-uix-id="log" class="log">
45
+ <uix-line class="log-line">.</uix-line>
46
+ <uix-line class="log-line">.</uix-line>
47
+ <uix-line class="log-line">.</uix-line>
48
+ </uix-log-view>
49
49
  </div>
@@ -1,19 +1,25 @@
1
1
  /**
2
- * Controls - data-uix markup upgrades on any IWSDK panel.
2
+ * Controls - `<uix-*>` custom elements upgraded on any IWSDK panel.
3
3
  *
4
4
  * The controls work in plain panels too (no window required): the
5
5
  * UIControlsSystem upgrades every loaded PanelDocument.
6
+ *
7
+ * IWSDK 0.5 parses panels against a component schema and rejects any tag it
8
+ * does not know, so a panel that uses a control must register
9
+ * `uixComponentSet` when the world is created. Without it the panel fails to
10
+ * parse and never attaches, with no error in the scene.
6
11
  */
7
12
  import { PanelDocument, PanelUI, UIKitDocument, World, createSystem, eq } from '@iwsdk/core';
8
13
  import {
9
14
  registerUIExtensions,
15
+ uixComponentSet,
10
16
  upgradePanel,
11
17
  type UixElement,
12
18
  } from '@realitycollective/iwsdk-uiextensions';
13
19
 
14
20
  export async function start(container: HTMLDivElement) {
15
21
  const world = await World.create(container, {
16
- features: { spatialUI: true },
22
+ features: { spatialUI: { kit: 'horizon', componentSets: [uixComponentSet] } },
17
23
  });
18
24
  registerUIExtensions(world);
19
25
 
package/README.md CHANGED
@@ -13,7 +13,7 @@ Windowing, docking, layout regions and extra controls for [Meta's Immersive Web
13
13
  | **Dock states** | `world-locked` (place in space) ⇄ `body-follow` (lazy follow) ⇄ `head-locked`, realised with the IWSDK's own `Follower`/`ScreenSpace` |
14
14
  | **Manipulation** | Drag windows by the title bar with the far ray or a near grab (controller squeeze, hand pinch), powered by `@pmndrs/handle`, the same library behind IWSDK grabbing; billboard-while-dragging, drop-to-dock |
15
15
  | **Layout regions** | Named regions (row / column / grid slots) windows snap into; regions can themselves follow the player |
16
- | **Controls** | `data-uix` markup upgrades: **stepper**, **toggle**, **expandable multi-line label**, **log/list view** - plus everything UIKitML already has (buttons, inputs, textareas, images, and the horizon kit's Slider/Checkbox/…) |
16
+ | **Controls** | `<uix-*>` custom-element upgrades: **stepper**, **toggle**, **expandable multi-line label**, **log/list view** - plus everything UIKitML already has (buttons, inputs, textareas, images, and the horizon kit's Slider/Checkbox/…) |
17
17
 
18
18
  Everything is authored in plain UIKitML (HTML/CSS-like) - no new markup language, no custom renderer, no wrapper widgets around things the IWSDK already does.
19
19
 
@@ -21,7 +21,7 @@ Everything is authored in plain UIKitML (HTML/CSS-like) - no new markup language
21
21
 
22
22
  ```bash
23
23
  npm install @realitycollective/iwsdk-uiextensions
24
- # peers: @iwsdk/core >=0.5.0 <0.6.0 and three >=0.170.0 (every IWSDK app already has both)
24
+ # peers: @iwsdk/core >=1.0.0 <2.0.0 and three >=0.170.0 (every IWSDK app already has both)
25
25
  ```
26
26
 
27
27
  ## Quick start
@@ -85,14 +85,24 @@ Windows are ordinary UIKitML panels; the chrome is discovered by well-known elem
85
85
 
86
86
  ### Controls markup
87
87
 
88
- Annotate any element with `data-uix` and the `UIControlsSystem` upgrades it - in any panel, not just windows:
88
+ A control is a custom element, `<uix-stepper>`, `<uix-toggle>`, `<uix-expandable-label>` or `<uix-log-view>`, with its parts as elements too (`<uix-decrement>`, `<uix-value>`, `<uix-increment>`, `<uix-label>`, `<uix-text>`, `<uix-more>`, `<uix-line>`). Parameters stay `data-uix-*` attributes. The `UIControlsSystem` upgrades every control it finds, in any panel, not just windows:
89
89
 
90
90
  ```html
91
- <div data-uix="stepper" data-uix-id="health" data-uix-min="0" data-uix-max="100" data-uix-step="10">
92
- <button data-uix-role="decrement">-</button>
93
- <span data-uix-role="value">.</span>
94
- <button data-uix-role="increment">+</button>
95
- </div>
91
+ <uix-stepper data-uix-id="health" data-uix-min="0" data-uix-max="100" data-uix-step="10">
92
+ <uix-decrement>-</uix-decrement>
93
+ <uix-value>.</uix-value>
94
+ <uix-increment>+</uix-increment>
95
+ </uix-stepper>
96
+ ```
97
+
98
+ IWSDK 0.5 validates every tag against a component schema, so a world whose panels use controls must register the set, or the panel fails to parse and never attaches:
99
+
100
+ ```ts
101
+ import { uixComponentSet } from '@realitycollective/iwsdk-uiextensions';
102
+
103
+ const world = await World.create(container, {
104
+ features: { spatialUI: { kit: 'horizon', componentSets: [uixComponentSet] } },
105
+ });
96
106
  ```
97
107
 
98
108
  ```ts
@@ -102,7 +112,7 @@ const controls = panelControlsFor(document); // the panel's UIKitDocument
102
112
  controls.stepper('health').events.on('change', (hp) => setHealth(hp));
103
113
  ```
104
114
 
105
- > UIKitML note: every dynamic-text element needs a literal placeholder child (`<span data-uix-role="value">.</span>`) or no Text node is created.
115
+ > UIKitML note: every dynamic-text element needs a literal placeholder child (`<uix-value>.</uix-value>`) or no Text node is created.
106
116
 
107
117
  **Every title-bar button is off by default.** Keep all four in the markup, then enable the ones a window should have with `closable`, `minimizable`, `pinnable` and `dockable` at spawn, or later with `windows.setChrome(id, { pin: true })`. A disabled button is hidden and its click ignored; enabling one needs no rewiring.
108
118
 
@@ -22,6 +22,7 @@
22
22
  */
23
23
  import { htmlComponentSet } from '@drawcall/uikitml';
24
24
  import { z } from 'zod';
25
+ import { dataAttributeKey } from '@realitycollective/webxr-uiextensions';
25
26
  /**
26
27
  * Base container, taken from the parser's own `<div>` definition rather than
27
28
  * imported from `@pmndrs/uikit` directly - that keeps a single copy of uikit
@@ -49,21 +50,6 @@ const ROLE_TAGS = [
49
50
  'uix-text',
50
51
  'uix-more',
51
52
  ];
52
- /**
53
- * `data-uix-min` → `uixMin`, matching how the other parser fills userData.
54
- *
55
- * The parser hands properties over already camelCased, so `data-uix-min`
56
- * arrives as `dataUixMin`; both spellings are normalised here.
57
- */
58
- function toUserDataKey(attribute) {
59
- if (/^data[A-Z]/.test(attribute)) {
60
- const rest = attribute.slice(4);
61
- return rest.charAt(0).toLowerCase() + rest.slice(1);
62
- }
63
- return attribute
64
- .replace(/^data-/, '')
65
- .replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
66
- }
67
53
  /**
68
54
  * A plain uikit Container that records what it was declared as.
69
55
  *
@@ -78,7 +64,7 @@ function controlComponent(tag) {
78
64
  const userData = {};
79
65
  for (const [key, value] of Object.entries(properties)) {
80
66
  if (key.startsWith('data-') || /^data[A-Z]/.test(key)) {
81
- userData[toUserDataKey(key)] = value;
67
+ userData[dataAttributeKey(key)] = value;
82
68
  }
83
69
  else {
84
70
  uikitProps[key] = value;
@@ -1 +1 @@
1
- {"version":3,"file":"component-set.js","sourceRoot":"","sources":["../src/component-set.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,gBAAgB,EAA+C,MAAM,mBAAmB,CAAC;AAClG,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AACH,MAAM,aAAa,GAAG,gBAAgB,CAAC,KAAK,CAAC;IAC3C,EAAE,SAEH,CAAC;AAEF,8EAA8E;AAC9E,MAAM,YAAY,GAAG;IACnB,aAAa;IACb,YAAY;IACZ,sBAAsB;IACtB,cAAc;CACN,CAAC;AAEX,MAAM,SAAS,GAAG;IAChB,eAAe;IACf,WAAW;IACX,eAAe;IACf,WAAW;IACX,UAAU;IACV,QAAQ;IACR,UAAU;IACV,WAAW;IACX,YAAY;IACZ,UAAU;IACV,UAAU;CACF,CAAC;AAEX;;;;;GAKG;AACH,SAAS,aAAa,CAAC,SAAiB;IACtC,IAAI,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,SAAS;SACb,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;SACrB,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAChE,CAAC;AAED;;;;;;GAMG;AACH,SAAS,gBAAgB,CAAC,GAAW;IACnC,OAAO,MAAM,mBAAoB,SAAQ,aAAa;QACpD,YAAY,aAAsC,EAAE;YAClD,MAAM,UAAU,GAA4B,EAAE,CAAC;YAC/C,MAAM,QAAQ,GAA4B,EAAE,CAAC;YAC7C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBACtD,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBACtD,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;gBACvC,CAAC;qBAAM,CAAC;oBACN,UAAU,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;gBAC1B,CAAC;YACH,CAAC;YACD,KAAK,CAAC,UAAU,CAAC,CAAC;YAClB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE;gBACrC,aAAa,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE;aACtD,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AAE1C,SAAS,MAAM,CAAC,GAAW;IACzB,OAAO;QACL,SAAS,EAAE,gBAAgB,CAAC,GAAG,CAAgD;QAC/E,MAAM;QACN,eAAe,EAAE,IAAI;KACtB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAiB,MAAM,CAAC,WAAW,CAC7D,CAAC,GAAG,YAAY,EAAE,GAAG,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CACjE,CAAC","sourcesContent":["/**\n * The UIKitML component set for `<uix-*>` control elements.\n *\n * IWSDK 0.5 parses panels with `@drawcall/uikitml`, which validates every tag\n * against a component schema and rejects anything it does not know - including\n * every `data-*` attribute on a built-in tag. Declaring the control elements\n * here is the supported way to extend that vocabulary.\n *\n * The three.js and XR Blocks adapters need no equivalent: their parser accepts\n * custom tags unregistered, exposing the tag on\n * `userData.customElement.componentName`. This set reproduces that convention\n * on the IWSDK side - the constructor below stamps the same key and lifts\n * `data-uix-*` attributes into `userData` - so ONE markup file drives every\n * adapter and the engine-free upgraders read it identically everywhere.\n *\n * Register it when creating the world:\n * ```ts\n * const world = await World.create(container, {\n * features: { spatialUI: { kit: 'horizon', componentSets: [uixComponentSet] } },\n * });\n * ```\n */\nimport { htmlComponentSet, type ComponentSet, type ComponentDefinition } from '@drawcall/uikitml';\nimport { z } from 'zod';\n\n/**\n * Base container, taken from the parser's own `<div>` definition rather than\n * imported from `@pmndrs/uikit` directly - that keeps a single copy of uikit\n * in the tree, whichever version `@iwsdk/core` resolves.\n */\nconst BaseContainer = htmlComponentSet['div']\n ?.component as unknown as new (properties?: Record<string, unknown>) => {\n userData: Record<string, unknown>;\n};\n\n/** Control elements an author writes, and the parts that live inside them. */\nconst CONTROL_TAGS = [\n 'uix-stepper',\n 'uix-toggle',\n 'uix-expandable-label',\n 'uix-log-view',\n] as const;\n\nconst ROLE_TAGS = [\n 'uix-decrement',\n 'uix-value',\n 'uix-increment',\n 'uix-label',\n 'uix-line',\n 'uix-up',\n 'uix-down',\n 'uix-clear',\n 'uix-status',\n 'uix-text',\n 'uix-more',\n] as const;\n\n/**\n * `data-uix-min` → `uixMin`, matching how the other parser fills userData.\n *\n * The parser hands properties over already camelCased, so `data-uix-min`\n * arrives as `dataUixMin`; both spellings are normalised here.\n */\nfunction toUserDataKey(attribute: string): string {\n if (/^data[A-Z]/.test(attribute)) {\n const rest = attribute.slice(4);\n return rest.charAt(0).toLowerCase() + rest.slice(1);\n }\n return attribute\n .replace(/^data-/, '')\n .replace(/-([a-z0-9])/g, (_, c: string) => c.toUpperCase());\n}\n\n/**\n * A plain uikit Container that records what it was declared as.\n *\n * Anything the schema let through that is not a uikit property - the\n * `data-uix-*` parameters - is moved into `userData` rather than handed to\n * uikit, which would warn about properties it does not recognise.\n */\nfunction controlComponent(tag: string): typeof BaseContainer {\n return class UixControlContainer extends BaseContainer {\n constructor(properties: Record<string, unknown> = {}) {\n const uikitProps: Record<string, unknown> = {};\n const userData: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(properties)) {\n if (key.startsWith('data-') || /^data[A-Z]/.test(key)) {\n userData[toUserDataKey(key)] = value;\n } else {\n uikitProps[key] = value;\n }\n }\n super(uikitProps);\n Object.assign(this.userData, userData, {\n customElement: { componentName: tag, sourceTag: tag },\n });\n }\n };\n}\n\n/** Permissive: the parameters are ours, and the upgraders validate them. */\nconst schema = z.object({}).passthrough();\n\nfunction define(tag: string): ComponentDefinition {\n return {\n component: controlComponent(tag) as unknown as ComponentDefinition['component'],\n schema,\n canHaveChildren: true,\n };\n}\n\n/**\n * Pass to `spatialUI.componentSets` so `<uix-*>` elements parse on IWSDK 0.5.\n * Without it, a panel using any control fails to parse and never attaches.\n */\nexport const uixComponentSet: ComponentSet = Object.fromEntries(\n [...CONTROL_TAGS, ...ROLE_TAGS].map((tag) => [tag, define(tag)]),\n);\n"]}
1
+ {"version":3,"file":"component-set.js","sourceRoot":"","sources":["../src/component-set.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,gBAAgB,EAA+C,MAAM,mBAAmB,CAAC;AAClG,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAEzE;;;;GAIG;AACH,MAAM,aAAa,GAAG,gBAAgB,CAAC,KAAK,CAAC;IAC3C,EAAE,SAEH,CAAC;AAEF,8EAA8E;AAC9E,MAAM,YAAY,GAAG;IACnB,aAAa;IACb,YAAY;IACZ,sBAAsB;IACtB,cAAc;CACN,CAAC;AAEX,MAAM,SAAS,GAAG;IAChB,eAAe;IACf,WAAW;IACX,eAAe;IACf,WAAW;IACX,UAAU;IACV,QAAQ;IACR,UAAU;IACV,WAAW;IACX,YAAY;IACZ,UAAU;IACV,UAAU;CACF,CAAC;AAEX;;;;;;GAMG;AACH,SAAS,gBAAgB,CAAC,GAAW;IACnC,OAAO,MAAM,mBAAoB,SAAQ,aAAa;QACpD,YAAY,aAAsC,EAAE;YAClD,MAAM,UAAU,GAA4B,EAAE,CAAC;YAC/C,MAAM,QAAQ,GAA4B,EAAE,CAAC;YAC7C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBACtD,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBACtD,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;gBAC1C,CAAC;qBAAM,CAAC;oBACN,UAAU,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;gBAC1B,CAAC;YACH,CAAC;YACD,KAAK,CAAC,UAAU,CAAC,CAAC;YAClB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE;gBACrC,aAAa,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE;aACtD,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AAE1C,SAAS,MAAM,CAAC,GAAW;IACzB,OAAO;QACL,SAAS,EAAE,gBAAgB,CAAC,GAAG,CAAgD;QAC/E,MAAM;QACN,eAAe,EAAE,IAAI;KACtB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAiB,MAAM,CAAC,WAAW,CAC7D,CAAC,GAAG,YAAY,EAAE,GAAG,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CACjE,CAAC","sourcesContent":["/**\n * The UIKitML component set for `<uix-*>` control elements.\n *\n * IWSDK 0.5 parses panels with `@drawcall/uikitml`, which validates every tag\n * against a component schema and rejects anything it does not know - including\n * every `data-*` attribute on a built-in tag. Declaring the control elements\n * here is the supported way to extend that vocabulary.\n *\n * The three.js and XR Blocks adapters need no equivalent: their parser accepts\n * custom tags unregistered, exposing the tag on\n * `userData.customElement.componentName`. This set reproduces that convention\n * on the IWSDK side - the constructor below stamps the same key and lifts\n * `data-uix-*` attributes into `userData` - so ONE markup file drives every\n * adapter and the engine-free upgraders read it identically everywhere.\n *\n * Register it when creating the world:\n * ```ts\n * const world = await World.create(container, {\n * features: { spatialUI: { kit: 'horizon', componentSets: [uixComponentSet] } },\n * });\n * ```\n */\nimport { htmlComponentSet, type ComponentSet, type ComponentDefinition } from '@drawcall/uikitml';\nimport { z } from 'zod';\nimport { dataAttributeKey } from '@realitycollective/webxr-uiextensions';\n\n/**\n * Base container, taken from the parser's own `<div>` definition rather than\n * imported from `@pmndrs/uikit` directly - that keeps a single copy of uikit\n * in the tree, whichever version `@iwsdk/core` resolves.\n */\nconst BaseContainer = htmlComponentSet['div']\n ?.component as unknown as new (properties?: Record<string, unknown>) => {\n userData: Record<string, unknown>;\n};\n\n/** Control elements an author writes, and the parts that live inside them. */\nconst CONTROL_TAGS = [\n 'uix-stepper',\n 'uix-toggle',\n 'uix-expandable-label',\n 'uix-log-view',\n] as const;\n\nconst ROLE_TAGS = [\n 'uix-decrement',\n 'uix-value',\n 'uix-increment',\n 'uix-label',\n 'uix-line',\n 'uix-up',\n 'uix-down',\n 'uix-clear',\n 'uix-status',\n 'uix-text',\n 'uix-more',\n] as const;\n\n/**\n * A plain uikit Container that records what it was declared as.\n *\n * Anything the schema let through that is not a uikit property - the\n * `data-uix-*` parameters - is moved into `userData` rather than handed to\n * uikit, which would warn about properties it does not recognise.\n */\nfunction controlComponent(tag: string): typeof BaseContainer {\n return class UixControlContainer extends BaseContainer {\n constructor(properties: Record<string, unknown> = {}) {\n const uikitProps: Record<string, unknown> = {};\n const userData: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(properties)) {\n if (key.startsWith('data-') || /^data[A-Z]/.test(key)) {\n userData[dataAttributeKey(key)] = value;\n } else {\n uikitProps[key] = value;\n }\n }\n super(uikitProps);\n Object.assign(this.userData, userData, {\n customElement: { componentName: tag, sourceTag: tag },\n });\n }\n };\n}\n\n/** Permissive: the parameters are ours, and the upgraders validate them. */\nconst schema = z.object({}).passthrough();\n\nfunction define(tag: string): ComponentDefinition {\n return {\n component: controlComponent(tag) as unknown as ComponentDefinition['component'],\n schema,\n canHaveChildren: true,\n };\n}\n\n/**\n * Pass to `spatialUI.componentSets` so `<uix-*>` elements parse on IWSDK 0.5.\n * Without it, a panel using any control fails to parse and never attaches.\n */\nexport const uixComponentSet: ComponentSet = Object.fromEntries(\n [...CONTROL_TAGS, ...ROLE_TAGS].map((tag) => [tag, define(tag)]),\n);\n"]}
@@ -235,7 +235,7 @@ export declare const UIDockRegion: import("elics").Component<{
235
235
  readonly Column: "column";
236
236
  readonly Grid: "grid";
237
237
  };
238
- default: "column";
238
+ default: import("@realitycollective/webxr-uiextensions").RegionFlow;
239
239
  };
240
240
  /** Distance between slot origins (meters). */
241
241
  pitch: {
@@ -8,7 +8,7 @@
8
8
  * components to realise dock modes rather than re-implementing them.
9
9
  */
10
10
  import { Types, createComponent } from '@iwsdk/core';
11
- import { DEFAULT_HAND_MENU, DockMode } from '@realitycollective/webxr-uiextensions';
11
+ import { DEFAULT_BILLBOARD_WHILE_DRAGGING, DEFAULT_DRAG_DELAY, DEFAULT_FOCUS_BIAS, DEFAULT_HAND_MENU, DEFAULT_REGION, DEFAULT_WINDOW_FOLLOW, DockMode, } from '@realitycollective/webxr-uiextensions';
12
12
  /** `HandMenuOptions.hand` as an enum object for the component schema. */
13
13
  export const HandChoice = { Left: 'left', Right: 'right', Either: 'either' };
14
14
  /** `HandMenuOptions.anchor` as an enum object for the component schema. */
@@ -50,20 +50,20 @@ export const UIWindow = createComponent('UIWindow', {
50
50
  /** The DOCK affordance, which returns the window to where it spawned. */
51
51
  dockable: { type: Types.Boolean, default: false },
52
52
  /** Keep the window yawed toward the viewer while it is being dragged. */
53
- billboardWhileDragging: { type: Types.Boolean, default: true },
53
+ billboardWhileDragging: { type: Types.Boolean, default: DEFAULT_BILLBOARD_WHILE_DRAGGING },
54
54
  /**
55
55
  * Seconds a title-bar press must be held before it becomes a drag.
56
56
  * Shorter presses stay clicks, so title-bar buttons don't fight the drag.
57
57
  */
58
- dragDelay: { type: Types.Float32, default: 0.3 },
58
+ dragDelay: { type: Types.Float32, default: DEFAULT_DRAG_DELAY },
59
59
  /** Head-relative offset used in `body-follow` mode (meters). */
60
- followOffset: { type: Types.Vec3, default: [0, -0.15, -1.2] },
60
+ followOffset: { type: Types.Vec3, default: [...DEFAULT_WINDOW_FOLLOW.offset] },
61
61
  /** `Follower` lerp speed for body-follow. */
62
- followSpeed: { type: Types.Float32, default: 3 },
62
+ followSpeed: { type: Types.Float32, default: DEFAULT_WINDOW_FOLLOW.speed },
63
63
  /** `Follower` positional deadzone for body-follow (meters). */
64
- followTolerance: { type: Types.Float32, default: 0.35 },
64
+ followTolerance: { type: Types.Float32, default: DEFAULT_WINDOW_FOLLOW.tolerance },
65
65
  /** Meters the focused window is nudged toward the viewer per focus depth. */
66
- focusBias: { type: Types.Float32, default: 0.02 },
66
+ focusBias: { type: Types.Float32, default: DEFAULT_FOCUS_BIAS },
67
67
  /**
68
68
  * Hand-menu placement, used while `dockMode` is `hand-locked`. These seed
69
69
  * the record's `handMenu` on adoption; after that
@@ -115,15 +115,15 @@ export const UIWindowState = createComponent('UIWindowState', {
115
115
  */
116
116
  export const UIDockRegion = createComponent('UIDockRegion', {
117
117
  regionId: { type: Types.String, default: '' },
118
- flow: { type: Types.Enum, enum: RegionFlowType, default: RegionFlowType.Column },
118
+ flow: { type: Types.Enum, enum: RegionFlowType, default: DEFAULT_REGION.flow },
119
119
  /** Distance between slot origins (meters). */
120
- pitch: { type: Types.Float32, default: 0.35 },
120
+ pitch: { type: Types.Float32, default: DEFAULT_REGION.pitch },
121
121
  /** Columns per row (grid flow only). */
122
- columns: { type: Types.Float32, default: 2 },
122
+ columns: { type: Types.Float32, default: DEFAULT_REGION.columns },
123
123
  /** Max docked windows (0 = unlimited). */
124
- capacity: { type: Types.Float32, default: 0 },
124
+ capacity: { type: Types.Float32, default: DEFAULT_REGION.capacity },
125
125
  /** Drop-capture radius (meters). */
126
- snapRadius: { type: Types.Float32, default: 0.5 },
126
+ snapRadius: { type: Types.Float32, default: DEFAULT_REGION.snapRadius },
127
127
  }, 'UI Extensions layout region for docking windows');
128
128
  /** Present on a window entity while it is docked into a region. */
129
129
  export const UIDockedTo = createComponent('UIDockedTo', {
@@ -1 +1 @@
1
- {"version":3,"file":"components.js","sourceRoot":"","sources":["../src/components.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ECS components for UI Extensions.\n *\n * These compose with the IWSDK's own components - a window entity typically\n * carries `PanelUI` (the IWSDK loads/renders the panel), `UIWindow` (ours),\n * and the IWSDK interaction tags (`RayInteractable`, `PokeInteractable`).\n * The systems in this package add/remove the IWSDK `Follower` / `ScreenSpace`\n * components to realise dock modes rather than re-implementing them.\n */\nimport { Types, createComponent } from '@iwsdk/core';\nimport { DEFAULT_HAND_MENU, DockMode } from '@realitycollective/webxr-uiextensions';\n\n/** `HandMenuOptions.hand` as an enum object for the component schema. */\nexport const HandChoice = { Left: 'left', Right: 'right', Either: 'either' } as const;\n/** `HandMenuOptions.anchor` as an enum object for the component schema. */\nexport const HandAnchor = {\n Above: 'above',\n Inside: 'inside',\n Outside: 'outside',\n Wrist: 'wrist',\n} as const;\n\n/** Region flow options mirrored as an enum object for the component schema. */\nexport const RegionFlowType = {\n Row: 'row',\n Column: 'column',\n Grid: 'grid',\n} as const;\n\n/**\n * Marks a panel entity as a managed window with chrome, focus, docking and\n * drag behaviour. Pair with `PanelUI` whose markup contains the window chrome\n * elements (see `WINDOW_CHROME_IDS` / the Examples folder).\n */\nexport const UIWindow = createComponent(\n 'UIWindow',\n {\n /** Stable id; auto-generated when empty. */\n windowId: { type: Types.String, default: '' },\n /** Title text written into the `uix-title` element. */\n title: { type: Types.String, default: '' },\n /** Current dock state. */\n dockMode: { type: Types.Enum, enum: DockMode, default: DockMode.WorldLocked },\n /** Whether the title bar drags the window. */\n movable: { type: Types.Boolean, default: true },\n /**\n * Title-bar buttons, all OFF by default. These seed the window's chrome\n * state on adoption; after that `WindowManager.setChrome` is the way to\n * change them, and the values here are kept in step with it.\n */\n closable: { type: Types.Boolean, default: false },\n minimizable: { type: Types.Boolean, default: false },\n /** The pin (body-follow ⇄ world-locked) affordance. */\n pinnable: { type: Types.Boolean, default: false },\n /** The DOCK affordance, which returns the window to where it spawned. */\n dockable: { type: Types.Boolean, default: false },\n /** Keep the window yawed toward the viewer while it is being dragged. */\n billboardWhileDragging: { type: Types.Boolean, default: true },\n /**\n * Seconds a title-bar press must be held before it becomes a drag.\n * Shorter presses stay clicks, so title-bar buttons don't fight the drag.\n */\n dragDelay: { type: Types.Float32, default: 0.3 },\n /** Head-relative offset used in `body-follow` mode (meters). */\n followOffset: { type: Types.Vec3, default: [0, -0.15, -1.2] },\n /** `Follower` lerp speed for body-follow. */\n followSpeed: { type: Types.Float32, default: 3 },\n /** `Follower` positional deadzone for body-follow (meters). */\n followTolerance: { type: Types.Float32, default: 0.35 },\n /** Meters the focused window is nudged toward the viewer per focus depth. */\n focusBias: { type: Types.Float32, default: 0.02 },\n /**\n * Hand-menu placement, used while `dockMode` is `hand-locked`. These seed\n * the record's `handMenu` on adoption; after that\n * `WindowManager.setHandMenu` is the way to change them, and the values\n * here are kept in step with it. See `hand-menu.ts` in the core for the\n * hand frame and what each anchor means.\n */\n hand: { type: Types.Enum, enum: HandChoice, default: DEFAULT_HAND_MENU.hand },\n handAnchor: { type: Types.Enum, enum: HandAnchor, default: DEFAULT_HAND_MENU.anchor },\n handAnchorDistance: { type: Types.Float32, default: DEFAULT_HAND_MENU.anchorDistance },\n handOffset: { type: Types.Vec3, default: [0, 0, 0] },\n palmGate: { type: Types.Boolean, default: DEFAULT_HAND_MENU.palmGate },\n palmAngle: { type: Types.Float32, default: DEFAULT_HAND_MENU.palmAngle },\n /**\n * World size box in meters, applied via `UIKitDocument.setTargetDimensions`\n * once the document loads. IWSDK 0.5 removed `PanelUI.maxWidth/maxHeight`;\n * this is the supported replacement. `0` leaves the document at its\n * intrinsic markup size, scaled only by the entity transform.\n */\n targetWidth: { type: Types.Float32, default: 0 },\n targetHeight: { type: Types.Float32, default: 0 },\n },\n 'UI Extensions managed window (chrome, focus, docking, drag)',\n);\n\n/**\n * Internal bookkeeping the systems keep on window entities - which dock mode\n * has actually been applied to engine components, and whether chrome wiring\n * has run for the current PanelDocument.\n */\nexport const UIWindowState = createComponent(\n 'UIWindowState',\n {\n appliedDockMode: { type: Types.String, default: '' },\n chromeWired: { type: Types.Boolean, default: false },\n /**\n * Whether the hand-menu palm gate is open this frame (always true in the\n * other dock modes). `UIDockSystem` writes it; `UIWindowSystem` combines\n * it with the record's `hidden` to decide what is drawn and hittable.\n */\n gateOpen: { type: Types.Boolean, default: true },\n /** What `UIWindowSystem` last applied: drawn and hittable, or not. */\n presented: { type: Types.Boolean, default: true },\n /** \"Home\" snapshot captured on adoption - the DOCK button returns here. */\n homeDockMode: { type: Types.String, default: '' },\n homeRegion: { type: Types.String, default: '' },\n homePosition: { type: Types.Vec3, default: [0, 0, 0] },\n homeYaw: { type: Types.Float32, default: 0 },\n },\n 'Internal UI Extensions window bookkeeping',\n);\n\n/**\n * A named layout region that docked windows snap to and are laid out within.\n * Place the entity where the region should live (it can itself carry a\n * `Follower` to make a body-locked region).\n */\nexport const UIDockRegion = createComponent(\n 'UIDockRegion',\n {\n regionId: { type: Types.String, default: '' },\n flow: { type: Types.Enum, enum: RegionFlowType, default: RegionFlowType.Column },\n /** Distance between slot origins (meters). */\n pitch: { type: Types.Float32, default: 0.35 },\n /** Columns per row (grid flow only). */\n columns: { type: Types.Float32, default: 2 },\n /** Max docked windows (0 = unlimited). */\n capacity: { type: Types.Float32, default: 0 },\n /** Drop-capture radius (meters). */\n snapRadius: { type: Types.Float32, default: 0.5 },\n },\n 'UI Extensions layout region for docking windows',\n);\n\n/** Present on a window entity while it is docked into a region. */\nexport const UIDockedTo = createComponent(\n 'UIDockedTo',\n {\n regionId: { type: Types.String, default: '' },\n },\n 'Marks a UI Extensions window as docked into a region',\n);\n"]}
1
+ {"version":3,"file":"components.js","sourceRoot":"","sources":["../src/components.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ECS components for UI Extensions.\n *\n * These compose with the IWSDK's own components - a window entity typically\n * carries `PanelUI` (the IWSDK loads/renders the panel), `UIWindow` (ours),\n * and the IWSDK interaction tags (`RayInteractable`, `PokeInteractable`).\n * The systems in this package add/remove the IWSDK `Follower` / `ScreenSpace`\n * components to realise dock modes rather than re-implementing them.\n */\nimport { Types, createComponent } from '@iwsdk/core';\nimport {\n DEFAULT_BILLBOARD_WHILE_DRAGGING,\n DEFAULT_DRAG_DELAY,\n DEFAULT_FOCUS_BIAS,\n DEFAULT_HAND_MENU,\n DEFAULT_REGION,\n DEFAULT_WINDOW_FOLLOW,\n DockMode,\n} from '@realitycollective/webxr-uiextensions';\n\n/** `HandMenuOptions.hand` as an enum object for the component schema. */\nexport const HandChoice = { Left: 'left', Right: 'right', Either: 'either' } as const;\n/** `HandMenuOptions.anchor` as an enum object for the component schema. */\nexport const HandAnchor = {\n Above: 'above',\n Inside: 'inside',\n Outside: 'outside',\n Wrist: 'wrist',\n} as const;\n\n/** Region flow options mirrored as an enum object for the component schema. */\nexport const RegionFlowType = {\n Row: 'row',\n Column: 'column',\n Grid: 'grid',\n} as const;\n\n/**\n * Marks a panel entity as a managed window with chrome, focus, docking and\n * drag behaviour. Pair with `PanelUI` whose markup contains the window chrome\n * elements (see `WINDOW_CHROME_IDS` / the Examples folder).\n */\nexport const UIWindow = createComponent(\n 'UIWindow',\n {\n /** Stable id; auto-generated when empty. */\n windowId: { type: Types.String, default: '' },\n /** Title text written into the `uix-title` element. */\n title: { type: Types.String, default: '' },\n /** Current dock state. */\n dockMode: { type: Types.Enum, enum: DockMode, default: DockMode.WorldLocked },\n /** Whether the title bar drags the window. */\n movable: { type: Types.Boolean, default: true },\n /**\n * Title-bar buttons, all OFF by default. These seed the window's chrome\n * state on adoption; after that `WindowManager.setChrome` is the way to\n * change them, and the values here are kept in step with it.\n */\n closable: { type: Types.Boolean, default: false },\n minimizable: { type: Types.Boolean, default: false },\n /** The pin (body-follow ⇄ world-locked) affordance. */\n pinnable: { type: Types.Boolean, default: false },\n /** The DOCK affordance, which returns the window to where it spawned. */\n dockable: { type: Types.Boolean, default: false },\n /** Keep the window yawed toward the viewer while it is being dragged. */\n billboardWhileDragging: { type: Types.Boolean, default: DEFAULT_BILLBOARD_WHILE_DRAGGING },\n /**\n * Seconds a title-bar press must be held before it becomes a drag.\n * Shorter presses stay clicks, so title-bar buttons don't fight the drag.\n */\n dragDelay: { type: Types.Float32, default: DEFAULT_DRAG_DELAY },\n /** Head-relative offset used in `body-follow` mode (meters). */\n followOffset: { type: Types.Vec3, default: [...DEFAULT_WINDOW_FOLLOW.offset] },\n /** `Follower` lerp speed for body-follow. */\n followSpeed: { type: Types.Float32, default: DEFAULT_WINDOW_FOLLOW.speed },\n /** `Follower` positional deadzone for body-follow (meters). */\n followTolerance: { type: Types.Float32, default: DEFAULT_WINDOW_FOLLOW.tolerance },\n /** Meters the focused window is nudged toward the viewer per focus depth. */\n focusBias: { type: Types.Float32, default: DEFAULT_FOCUS_BIAS },\n /**\n * Hand-menu placement, used while `dockMode` is `hand-locked`. These seed\n * the record's `handMenu` on adoption; after that\n * `WindowManager.setHandMenu` is the way to change them, and the values\n * here are kept in step with it. See `hand-menu.ts` in the core for the\n * hand frame and what each anchor means.\n */\n hand: { type: Types.Enum, enum: HandChoice, default: DEFAULT_HAND_MENU.hand },\n handAnchor: { type: Types.Enum, enum: HandAnchor, default: DEFAULT_HAND_MENU.anchor },\n handAnchorDistance: { type: Types.Float32, default: DEFAULT_HAND_MENU.anchorDistance },\n handOffset: { type: Types.Vec3, default: [0, 0, 0] },\n palmGate: { type: Types.Boolean, default: DEFAULT_HAND_MENU.palmGate },\n palmAngle: { type: Types.Float32, default: DEFAULT_HAND_MENU.palmAngle },\n /**\n * World size box in meters, applied via `UIKitDocument.setTargetDimensions`\n * once the document loads. IWSDK 0.5 removed `PanelUI.maxWidth/maxHeight`;\n * this is the supported replacement. `0` leaves the document at its\n * intrinsic markup size, scaled only by the entity transform.\n */\n targetWidth: { type: Types.Float32, default: 0 },\n targetHeight: { type: Types.Float32, default: 0 },\n },\n 'UI Extensions managed window (chrome, focus, docking, drag)',\n);\n\n/**\n * Internal bookkeeping the systems keep on window entities - which dock mode\n * has actually been applied to engine components, and whether chrome wiring\n * has run for the current PanelDocument.\n */\nexport const UIWindowState = createComponent(\n 'UIWindowState',\n {\n appliedDockMode: { type: Types.String, default: '' },\n chromeWired: { type: Types.Boolean, default: false },\n /**\n * Whether the hand-menu palm gate is open this frame (always true in the\n * other dock modes). `UIDockSystem` writes it; `UIWindowSystem` combines\n * it with the record's `hidden` to decide what is drawn and hittable.\n */\n gateOpen: { type: Types.Boolean, default: true },\n /** What `UIWindowSystem` last applied: drawn and hittable, or not. */\n presented: { type: Types.Boolean, default: true },\n /** \"Home\" snapshot captured on adoption - the DOCK button returns here. */\n homeDockMode: { type: Types.String, default: '' },\n homeRegion: { type: Types.String, default: '' },\n homePosition: { type: Types.Vec3, default: [0, 0, 0] },\n homeYaw: { type: Types.Float32, default: 0 },\n },\n 'Internal UI Extensions window bookkeeping',\n);\n\n/**\n * A named layout region that docked windows snap to and are laid out within.\n * Place the entity where the region should live (it can itself carry a\n * `Follower` to make a body-locked region).\n */\nexport const UIDockRegion = createComponent(\n 'UIDockRegion',\n {\n regionId: { type: Types.String, default: '' },\n flow: { type: Types.Enum, enum: RegionFlowType, default: DEFAULT_REGION.flow },\n /** Distance between slot origins (meters). */\n pitch: { type: Types.Float32, default: DEFAULT_REGION.pitch },\n /** Columns per row (grid flow only). */\n columns: { type: Types.Float32, default: DEFAULT_REGION.columns },\n /** Max docked windows (0 = unlimited). */\n capacity: { type: Types.Float32, default: DEFAULT_REGION.capacity },\n /** Drop-capture radius (meters). */\n snapRadius: { type: Types.Float32, default: DEFAULT_REGION.snapRadius },\n },\n 'UI Extensions layout region for docking windows',\n);\n\n/** Present on a window entity while it is docked into a region. */\nexport const UIDockedTo = createComponent(\n 'UIDockedTo',\n {\n regionId: { type: Types.String, default: '' },\n },\n 'Marks a UI Extensions window as docked into a region',\n);\n"]}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `createSystem` from `@iwsdk/core`, with its return type written out.
3
3
  *
4
- * Why this file exists. IWSDK 0.5.3 declares `createSystem` in
4
+ * Why this file exists. IWSDK (0.5.3, and still 1.0.0) declares `createSystem` in
5
5
  * `dist/ecs/system.d.ts` as returning `SystemConstructor<S, Q, World, System<S, Q>>`,
6
6
  * and that file imports `World` from './world' with no extension, the only
7
7
  * extensionless relative import in the package. This repository builds with
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `createSystem` from `@iwsdk/core`, with its return type written out.
3
3
  *
4
- * Why this file exists. IWSDK 0.5.3 declares `createSystem` in
4
+ * Why this file exists. IWSDK (0.5.3, and still 1.0.0) declares `createSystem` in
5
5
  * `dist/ecs/system.d.ts` as returning `SystemConstructor<S, Q, World, System<S, Q>>`,
6
6
  * and that file imports `World` from './world' with no extension, the only
7
7
  * extensionless relative import in the package. This repository builds with
@@ -1 +1 @@
1
- {"version":3,"file":"create-system.js","sourceRoot":"","sources":["../src/create-system.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAE,YAAY,IAAI,iBAAiB,EAA2B,MAAM,aAAa,CAAC;AAGzF,MAAM,UAAU,YAAY,CAC1B,OAAW,EACX,MAAU;IAEV,OAAO,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AAC5C,CAAC","sourcesContent":["/**\n * `createSystem` from `@iwsdk/core`, with its return type written out.\n *\n * Why this file exists. IWSDK 0.5.3 declares `createSystem` in\n * `dist/ecs/system.d.ts` as returning `SystemConstructor<S, Q, World, System<S, Q>>`,\n * and that file imports `World` from './world' with no extension, the only\n * extensionless relative import in the package. This repository builds with\n * `moduleResolution: NodeNext`, under which an extensionless relative import\n * in an ES module does not resolve (TS2835), so inside the host's own\n * declaration `World` is an unresolved name. `skipLibCheck` hides the error,\n * and TypeScript's declaration emitter preserves an unresolved name verbatim,\n * so every `export class X extends createSystem(...)` in this package was\n * emitted with a bare `World` in its base type. A consumer that checks\n * library files (`skipLibCheck: false`, or a package emitting declarations on\n * top of ours) fails with TS2304 on its first import of this package. A file\n * that happened to import `World` for another signature resolved the bare\n * name by accident, which is how the sibling adapters looked clean.\n *\n * The annotation below names `World` through `@iwsdk/core`'s barrel, whose\n * imports all carry extensions, so the emitter writes\n * `import(\"@iwsdk/core\").World` wherever the result is extended. Every system\n * in this package takes `createSystem` from here, never from `@iwsdk/core`.\n * `elics` is imported for its types only: it is `@iwsdk/core`'s own\n * dependency, the import is erased at build time, and the emitted\n * declarations already name `import(\"elics\").SystemConstructor`.\n *\n * Delete this file, and import `createSystem` from `@iwsdk/core` again, once\n * IWSDK ships `./world.js` there. `scripts/declaration-check.mjs`, run by\n * `verify:pack`, is the check that fails while the bare name is emitted.\n */\nimport { createSystem as createIWSDKSystem, type System, type World } from '@iwsdk/core';\nimport type { SystemConstructor, SystemQueries, SystemSchema } from 'elics';\n\nexport function createSystem<S extends SystemSchema, Q extends SystemQueries>(\n queries?: Q,\n schema?: S,\n): SystemConstructor<S, Q, World, System<S, Q>> {\n return createIWSDKSystem(queries, schema);\n}\n"]}
1
+ {"version":3,"file":"create-system.js","sourceRoot":"","sources":["../src/create-system.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAE,YAAY,IAAI,iBAAiB,EAA2B,MAAM,aAAa,CAAC;AAGzF,MAAM,UAAU,YAAY,CAC1B,OAAW,EACX,MAAU;IAEV,OAAO,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AAC5C,CAAC","sourcesContent":["/**\n * `createSystem` from `@iwsdk/core`, with its return type written out.\n *\n * Why this file exists. IWSDK (0.5.3, and still 1.0.0) declares `createSystem` in\n * `dist/ecs/system.d.ts` as returning `SystemConstructor<S, Q, World, System<S, Q>>`,\n * and that file imports `World` from './world' with no extension, the only\n * extensionless relative import in the package. This repository builds with\n * `moduleResolution: NodeNext`, under which an extensionless relative import\n * in an ES module does not resolve (TS2835), so inside the host's own\n * declaration `World` is an unresolved name. `skipLibCheck` hides the error,\n * and TypeScript's declaration emitter preserves an unresolved name verbatim,\n * so every `export class X extends createSystem(...)` in this package was\n * emitted with a bare `World` in its base type. A consumer that checks\n * library files (`skipLibCheck: false`, or a package emitting declarations on\n * top of ours) fails with TS2304 on its first import of this package. A file\n * that happened to import `World` for another signature resolved the bare\n * name by accident, which is how the sibling adapters looked clean.\n *\n * The annotation below names `World` through `@iwsdk/core`'s barrel, whose\n * imports all carry extensions, so the emitter writes\n * `import(\"@iwsdk/core\").World` wherever the result is extended. Every system\n * in this package takes `createSystem` from here, never from `@iwsdk/core`.\n * `elics` is imported for its types only: it is `@iwsdk/core`'s own\n * dependency, the import is erased at build time, and the emitted\n * declarations already name `import(\"elics\").SystemConstructor`.\n *\n * Delete this file, and import `createSystem` from `@iwsdk/core` again, once\n * IWSDK ships `./world.js` there. `scripts/declaration-check.mjs`, run by\n * `verify:pack`, is the check that fails while the bare name is emitted.\n */\nimport { createSystem as createIWSDKSystem, type System, type World } from '@iwsdk/core';\nimport type { SystemConstructor, SystemQueries, SystemSchema } from 'elics';\n\nexport function createSystem<S extends SystemSchema, Q extends SystemQueries>(\n queries?: Q,\n schema?: S,\n): SystemConstructor<S, Q, World, System<S, Q>> {\n return createIWSDKSystem(queries, schema);\n}\n"]}
package/dist/factory.js CHANGED
@@ -4,8 +4,8 @@
4
4
  * they do can be done manually with the components.
5
5
  */
6
6
  import { Follower, FollowBehavior, PanelUI, PokeInteractable, RayInteractable, } from '@iwsdk/core';
7
- import { UIDockRegion, UIDockedTo, UIWindow, RegionFlowType } from './components.js';
8
- import { DockMode, resolveHandMenu, } from '@realitycollective/webxr-uiextensions';
7
+ import { UIDockRegion, UIDockedTo, UIWindow } from './components.js';
8
+ import { DEFAULT_BILLBOARD_WHILE_DRAGGING, DEFAULT_DRAG_DELAY, DEFAULT_REGION, DEFAULT_REGION_FOLLOW, DEFAULT_WINDOW_FOLLOW, DockMode, resolveHandMenu, } from '@realitycollective/webxr-uiextensions';
9
9
  export function createUIWindow(world, options) {
10
10
  const handMenu = resolveHandMenu(options.handMenu);
11
11
  const entity = world
@@ -26,10 +26,11 @@ export function createUIWindow(world, options) {
26
26
  minimizable: options.minimizable ?? false,
27
27
  pinnable: options.pinnable ?? false,
28
28
  dockable: options.dockable ?? false,
29
- billboardWhileDragging: options.billboardWhileDragging ?? true,
30
- followOffset: options.followOffset ?? [0, -0.15, -1.2],
31
- followSpeed: options.followSpeed ?? 3,
32
- followTolerance: options.followTolerance ?? 0.35,
29
+ billboardWhileDragging: options.billboardWhileDragging ?? DEFAULT_BILLBOARD_WHILE_DRAGGING,
30
+ dragDelay: options.dragDelay ?? DEFAULT_DRAG_DELAY,
31
+ followOffset: options.followOffset ?? [...DEFAULT_WINDOW_FOLLOW.offset],
32
+ followSpeed: options.followSpeed ?? DEFAULT_WINDOW_FOLLOW.speed,
33
+ followTolerance: options.followTolerance ?? DEFAULT_WINDOW_FOLLOW.tolerance,
33
34
  hand: handMenu.hand,
34
35
  handAnchor: handMenu.anchor,
35
36
  handAnchorDistance: handMenu.anchorDistance,
@@ -50,11 +51,12 @@ export function createUIWindow(world, options) {
50
51
  export function createDockRegion(world, options) {
51
52
  const entity = world.createTransformEntity().addComponent(UIDockRegion, {
52
53
  regionId: options.id,
53
- flow: options.flow ?? RegionFlowType.Column,
54
- pitch: options.pitch ?? 0.35,
55
- columns: options.columns ?? 2,
56
- capacity: options.capacity ?? 0,
57
- snapRadius: options.snapRadius ?? 0.5,
54
+ // One set of region defaults: the core's DEFAULT_REGION.
55
+ flow: options.flow ?? DEFAULT_REGION.flow,
56
+ pitch: options.pitch ?? DEFAULT_REGION.pitch,
57
+ columns: options.columns ?? DEFAULT_REGION.columns,
58
+ capacity: options.capacity ?? DEFAULT_REGION.capacity,
59
+ snapRadius: options.snapRadius ?? DEFAULT_REGION.snapRadius,
58
60
  });
59
61
  if (options.position) {
60
62
  entity.object3D?.position.set(...options.position);
@@ -63,7 +65,7 @@ export function createDockRegion(world, options) {
63
65
  entity.addComponent(Follower, {
64
66
  // Camera, not player.head - the head rig is origin-locked outside XR.
65
67
  target: world.camera,
66
- offsetPosition: options.followOffset ?? [0, -0.2, -1.4],
68
+ offsetPosition: options.followOffset ?? [...DEFAULT_REGION_FOLLOW.offset],
67
69
  behavior: FollowBehavior.PivotY,
68
70
  });
69
71
  }
@@ -1 +1 @@
1
- {"version":3,"file":"factory.js","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Convenience factories - one call to spawn a managed window or dock region.\n * Thin sugar over `createTransformEntity().addComponent(...)`; everything\n * they do can be done manually with the components.\n */\nimport {\n Follower,\n FollowBehavior,\n PanelUI,\n PokeInteractable,\n RayInteractable,\n type Entity,\n type World,\n} from '@iwsdk/core';\nimport { UIDockRegion, UIDockedTo, UIWindow, RegionFlowType } from './components.js';\nimport {\n DockMode,\n resolveHandMenu,\n type WindowOptionsBase,\n} from '@realitycollective/webxr-uiextensions';\n\n/**\n * Options for {@link createUIWindow}.\n *\n * Everything but `config` comes from the portable {@link WindowOptionsBase},\n * so the same option names mean the same thing on every adapter. `maxWidth`\n * and `maxHeight` route to `UIWindow.targetWidth/Height` here, because IWSDK\n * 0.5 removed those fields from `PanelUI`; omit them to keep the markup's\n * intrinsic size and scale through the entity transform. The title-bar\n * buttons (`closable`, `minimizable`, `pinnable`, `dockable`) are all off\n * unless asked for.\n */\nexport interface CreateWindowOptions extends WindowOptionsBase {\n /**\n * URL of the UIKitML source (e.g. \"/ui/my-window.uikitml\"). IWSDK 0.5 parses\n * UIKitML at runtime - the build-time plugin and its generated JSON are gone.\n */\n config: string;\n /** Keep the window yawed toward the viewer while it is being dragged. */\n billboardWhileDragging?: boolean;\n}\n\nexport function createUIWindow(world: World, options: CreateWindowOptions): Entity {\n const handMenu = resolveHandMenu(options.handMenu);\n const entity = world\n .createTransformEntity()\n .addComponent(PanelUI, {\n config: options.config,\n })\n .addComponent(UIWindow, {\n // Applied by UIWindowSystem once the document loads - see components.ts.\n targetWidth: options.maxWidth ?? 0,\n targetHeight: options.maxHeight ?? 0,\n windowId: options.id ?? '',\n title: options.title ?? '',\n dockMode: options.dockMode ?? DockMode.WorldLocked,\n movable: options.movable ?? true,\n // Chrome buttons are opt-in - see WindowOptionsBase.\n closable: options.closable ?? false,\n minimizable: options.minimizable ?? false,\n pinnable: options.pinnable ?? false,\n dockable: options.dockable ?? false,\n billboardWhileDragging: options.billboardWhileDragging ?? true,\n followOffset: options.followOffset ?? [0, -0.15, -1.2],\n followSpeed: options.followSpeed ?? 3,\n followTolerance: options.followTolerance ?? 0.35,\n hand: handMenu.hand,\n handAnchor: handMenu.anchor,\n handAnchorDistance: handMenu.anchorDistance,\n handOffset: handMenu.offset,\n palmGate: handMenu.palmGate,\n palmAngle: handMenu.palmAngle,\n })\n .addComponent(RayInteractable)\n .addComponent(PokeInteractable);\n\n if (options.position) {\n entity.object3D?.position.set(...options.position);\n }\n if (options.region) {\n entity.addComponent(UIDockedTo, { regionId: options.region });\n }\n return entity;\n}\n\nexport interface CreateDockRegionOptions {\n id: string;\n flow?: (typeof RegionFlowType)[keyof typeof RegionFlowType];\n pitch?: number;\n columns?: number;\n capacity?: number;\n snapRadius?: number;\n /** World position of the region origin. */\n position?: [number, number, number];\n /**\n * Body-lock the region itself: it follows the player's head at\n * `followOffset`, and every window docked into it comes along.\n */\n follow?: boolean;\n followOffset?: [number, number, number];\n}\n\nexport function createDockRegion(world: World, options: CreateDockRegionOptions): Entity {\n const entity = world.createTransformEntity().addComponent(UIDockRegion, {\n regionId: options.id,\n flow: options.flow ?? RegionFlowType.Column,\n pitch: options.pitch ?? 0.35,\n columns: options.columns ?? 2,\n capacity: options.capacity ?? 0,\n snapRadius: options.snapRadius ?? 0.5,\n });\n if (options.position) {\n entity.object3D?.position.set(...options.position);\n }\n if (options.follow) {\n entity.addComponent(Follower, {\n // Camera, not player.head - the head rig is origin-locked outside XR.\n target: world.camera,\n offsetPosition: options.followOffset ?? [0, -0.2, -1.4],\n behavior: FollowBehavior.PivotY,\n });\n }\n return entity;\n}\n"]}
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Convenience factories - one call to spawn a managed window or dock region.\n * Thin sugar over `createTransformEntity().addComponent(...)`; everything\n * they do can be done manually with the components.\n */\nimport {\n Follower,\n FollowBehavior,\n PanelUI,\n PokeInteractable,\n RayInteractable,\n type Entity,\n type World,\n} from '@iwsdk/core';\nimport { UIDockRegion, UIDockedTo, UIWindow, RegionFlowType } from './components.js';\nimport {\n DEFAULT_BILLBOARD_WHILE_DRAGGING,\n DEFAULT_DRAG_DELAY,\n DEFAULT_REGION,\n DEFAULT_REGION_FOLLOW,\n DEFAULT_WINDOW_FOLLOW,\n DockMode,\n resolveHandMenu,\n type WindowOptionsBase,\n} from '@realitycollective/webxr-uiextensions';\n\n/**\n * Options for {@link createUIWindow}.\n *\n * Everything but `config` comes from the portable {@link WindowOptionsBase},\n * so the same option names mean the same thing on every adapter. `maxWidth`\n * and `maxHeight` route to `UIWindow.targetWidth/Height` here, because IWSDK\n * 0.5 removed those fields from `PanelUI`; omit them to keep the markup's\n * intrinsic size and scale through the entity transform. The title-bar\n * buttons (`closable`, `minimizable`, `pinnable`, `dockable`) are all off\n * unless asked for.\n */\nexport interface CreateWindowOptions extends WindowOptionsBase {\n /**\n * URL of the UIKitML source (e.g. \"/ui/my-window.uikitml\"). IWSDK 0.5 parses\n * UIKitML at runtime - the build-time plugin and its generated JSON are gone.\n */\n config: string;\n /** Keep the window yawed toward the viewer while it is being dragged. */\n billboardWhileDragging?: boolean;\n}\n\nexport function createUIWindow(world: World, options: CreateWindowOptions): Entity {\n const handMenu = resolveHandMenu(options.handMenu);\n const entity = world\n .createTransformEntity()\n .addComponent(PanelUI, {\n config: options.config,\n })\n .addComponent(UIWindow, {\n // Applied by UIWindowSystem once the document loads - see components.ts.\n targetWidth: options.maxWidth ?? 0,\n targetHeight: options.maxHeight ?? 0,\n windowId: options.id ?? '',\n title: options.title ?? '',\n dockMode: options.dockMode ?? DockMode.WorldLocked,\n movable: options.movable ?? true,\n // Chrome buttons are opt-in - see WindowOptionsBase.\n closable: options.closable ?? false,\n minimizable: options.minimizable ?? false,\n pinnable: options.pinnable ?? false,\n dockable: options.dockable ?? false,\n billboardWhileDragging: options.billboardWhileDragging ?? DEFAULT_BILLBOARD_WHILE_DRAGGING,\n dragDelay: options.dragDelay ?? DEFAULT_DRAG_DELAY,\n followOffset: options.followOffset ?? [...DEFAULT_WINDOW_FOLLOW.offset],\n followSpeed: options.followSpeed ?? DEFAULT_WINDOW_FOLLOW.speed,\n followTolerance: options.followTolerance ?? DEFAULT_WINDOW_FOLLOW.tolerance,\n hand: handMenu.hand,\n handAnchor: handMenu.anchor,\n handAnchorDistance: handMenu.anchorDistance,\n handOffset: handMenu.offset,\n palmGate: handMenu.palmGate,\n palmAngle: handMenu.palmAngle,\n })\n .addComponent(RayInteractable)\n .addComponent(PokeInteractable);\n\n if (options.position) {\n entity.object3D?.position.set(...options.position);\n }\n if (options.region) {\n entity.addComponent(UIDockedTo, { regionId: options.region });\n }\n return entity;\n}\n\nexport interface CreateDockRegionOptions {\n id: string;\n flow?: (typeof RegionFlowType)[keyof typeof RegionFlowType];\n pitch?: number;\n columns?: number;\n capacity?: number;\n snapRadius?: number;\n /** World position of the region origin. */\n position?: [number, number, number];\n /**\n * Body-lock the region itself: it follows the player's head at\n * `followOffset`, and every window docked into it comes along.\n */\n follow?: boolean;\n followOffset?: [number, number, number];\n}\n\nexport function createDockRegion(world: World, options: CreateDockRegionOptions): Entity {\n const entity = world.createTransformEntity().addComponent(UIDockRegion, {\n regionId: options.id,\n // One set of region defaults: the core's DEFAULT_REGION.\n flow: options.flow ?? DEFAULT_REGION.flow,\n pitch: options.pitch ?? DEFAULT_REGION.pitch,\n columns: options.columns ?? DEFAULT_REGION.columns,\n capacity: options.capacity ?? DEFAULT_REGION.capacity,\n snapRadius: options.snapRadius ?? DEFAULT_REGION.snapRadius,\n });\n if (options.position) {\n entity.object3D?.position.set(...options.position);\n }\n if (options.follow) {\n entity.addComponent(Follower, {\n // Camera, not player.head - the head rig is origin-locked outside XR.\n target: world.camera,\n offsetPosition: options.followOffset ?? [...DEFAULT_REGION_FOLLOW.offset],\n behavior: FollowBehavior.PivotY,\n });\n }\n return entity;\n}\n"]}
@@ -81,7 +81,15 @@ function buildSceneHost(world) {
81
81
  const readyListeners = new Set();
82
82
  const ready = new Map();
83
83
  const seen = new WeakSet();
84
+ /** Ids of the windows this host spawned, closed by `dispose`. */
85
+ const spawned = new Set();
86
+ let disposed = false;
84
87
  const announce = (entity) => {
88
+ // The readiness system stays registered on the world after `dispose`
89
+ // (IWSDK has no per-system removal here), so it goes quiet instead.
90
+ if (disposed) {
91
+ return;
92
+ }
85
93
  const document = PanelDocument.data.document[entity.index];
86
94
  if (!document || seen.has(document)) {
87
95
  return;
@@ -113,8 +121,10 @@ function buildSceneHost(world) {
113
121
  world.registerSystem(createReadySystem(announce));
114
122
  // A window closed through the manager is gone: stop replaying it to late
115
123
  // subscribers. (The window system destroys the entity on the same event.)
116
- windowManagerFor(world).events.on('closed', ({ id }) => {
124
+ const manager = windowManagerFor(world);
125
+ const stopClosed = manager.events.on('closed', ({ id }) => {
117
126
  ready.delete(id);
127
+ spawned.delete(id);
118
128
  });
119
129
  let windowSequence = 0;
120
130
  const host = {
@@ -126,6 +136,29 @@ function buildSceneHost(world) {
126
136
  throw new Error('[uix] createPanel() is not supported on the IWSDK host - spawn a window ' +
127
137
  '(spawnWindow/createUIWindow) so IWSDK owns the panel lifecycle.');
128
138
  },
139
+ // Leave nothing behind: close every window this host spawned through the
140
+ // world's manager (the window system destroys each entity on that event),
141
+ // silence the readiness system, release the manager subscription, and
142
+ // forget this host so `createSceneHost` builds a fresh one. Safe to call
143
+ // twice.
144
+ dispose() {
145
+ if (disposed) {
146
+ return;
147
+ }
148
+ // The window system opens a window's record only once its panel
149
+ // attaches, so a window still loading has nothing to close yet.
150
+ for (const id of [...spawned]) {
151
+ if (manager.has(id)) {
152
+ manager.close(id);
153
+ }
154
+ }
155
+ disposed = true;
156
+ spawned.clear();
157
+ ready.clear();
158
+ readyListeners.clear();
159
+ stopClosed();
160
+ hosts.delete(world);
161
+ },
129
162
  onPanelReady(listener) {
130
163
  readyListeners.add(listener);
131
164
  for (const event of ready.values()) {
@@ -136,6 +169,7 @@ function buildSceneHost(world) {
136
169
  createWindow(options) {
137
170
  const id = options.id ?? `uix-window-${(windowSequence += 1)}`;
138
171
  const entity = createUIWindow(world, { ...options, id });
172
+ spawned.add(id);
139
173
  return {
140
174
  entity,
141
175
  id,
@@ -1 +1 @@
1
- {"version":3,"file":"scene-host.js","sourceRoot":"","sources":["../src/scene-host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * IWSDK implementation of the core's `WindowHost` + `SceneTarget`.\n *\n * This is what makes a portable `SceneDescriptor` build the SAME playground\n * on IWSDK as on plain three.js, and lets app behaviour be wired through\n * `onPanelReady` instead of hand-written ECS queries:\n *\n * const host = createSceneHost(world);\n * applyScene(host, PLAYGROUND);\n * host.onPanelReady(({ id, panel }) => wireMyPanel(id, panel));\n *\n * Readiness is detected with a single ECS query on `PanelUI + PanelDocument`\n * - the engine-specific mechanism stays here, behind the portable interface.\n * Bare panels (a `PanelUI` entity with no `UIWindow`) are announced too, with\n * `kind: 'panel'` and their config path as the id, so devtools and hand-built\n * entities show up in the same stream as managed windows.\n */\nimport {\n PanelDocument,\n PanelUI,\n UIKitDocument,\n type Entity,\n type World,\n} from '@iwsdk/core';\nimport { createSystem } from './create-system.js';\nimport {\n upgradePanel,\n type PanelHandle,\n type PanelReadyEvent,\n type SceneRegion,\n type SceneTarget,\n type SceneWindow,\n type UixElement,\n type WindowHandle,\n type WindowHost,\n} from '@realitycollective/webxr-uiextensions';\nimport {\n createDockRegion,\n createUIWindow,\n type CreateWindowOptions,\n} from './factory.js';\nimport { UIWindow } from './components.js';\nimport { windowManagerFor } from './manager-registry.js';\n\n/** Panel handle over an IWSDK `UIKitDocument`. */\nfunction panelHandleFor(document: UIKitDocument): PanelHandle {\n return {\n get root(): UixElement {\n return document.rootElement as unknown as UixElement;\n },\n getElementById: (id) =>\n (document.getElementById(id) as UixElement | null) ?? undefined,\n setTargetDimensions: (width, height) =>\n document.setTargetDimensions(width, height),\n dispose: () => {\n /* IWSDK owns the document lifecycle via the entity. */\n },\n };\n}\n\n/**\n * The live panel on an entity, or `undefined` while IWSDK is still loading\n * its document. Use it to reach a panel from an entity you already hold;\n * `onPanelReady` stays the way to be TOLD when one appears.\n */\nexport function getPanelHandle(entity: Entity): PanelHandle | undefined {\n const document = PanelDocument.data.document[entity.index] as\n | UIKitDocument\n | undefined;\n return document ? panelHandleFor(document) : undefined;\n}\n\n/**\n * Bridges panel loading into `onPanelReady`. Registered once per host; the\n * query fires as each panel's document is attached by IWSDK's UI system.\n */\nfunction createReadySystem(onReady: (entity: Entity) => void) {\n return class UixPanelReadySystem extends createSystem({\n panels: { required: [PanelUI, PanelDocument] },\n }) {\n override init(): void {\n this.cleanupFuncs.push(\n this.queries.panels.subscribe('qualify', (entity) => onReady(entity)),\n );\n this.queries.panels.entities.forEach((entity) => onReady(entity));\n }\n };\n}\n\n/** A window spawned by {@link IwsdkSceneHost.createWindow}. */\nexport interface IwsdkWindowHandle extends WindowHandle {\n /** The ECS entity carrying `PanelUI`, `UIWindow` and the interaction tags. */\n readonly entity: Entity;\n}\n\nexport interface IwsdkSceneHost extends WindowHost, SceneTarget {\n readonly world: World;\n /**\n * Spawn a managed window and get a handle back straight away. IWSDK loads\n * the markup over later frames, so `handle.panel` is `undefined` at first\n * and `handle.onReady` is how you wait for it without an ECS query.\n */\n createWindow(options: CreateWindowOptions): IwsdkWindowHandle;\n}\n\nconst hosts = new WeakMap<World, IwsdkSceneHost>();\n\n/**\n * The host for a world: built on the first call, and the same instance every\n * call after that. Call AFTER `registerUIExtensions(world)`.\n *\n * One host per world is deliberate. A host owns an ECS system, the record of\n * which documents it has already announced, and the panel upgrade pass, so a\n * second independent host on the same world would run a second copy of all\n * three. Two modules can each ask for the host without coordinating, exactly\n * as they do for the window manager registry.\n */\nexport function createSceneHost(world: World): IwsdkSceneHost {\n const existing = hosts.get(world);\n if (existing) {\n return existing;\n }\n const host = buildSceneHost(world);\n hosts.set(world, host);\n return host;\n}\n\nfunction buildSceneHost(world: World): IwsdkSceneHost {\n const readyListeners = new Set<(event: PanelReadyEvent) => void>();\n const ready = new Map<string, PanelReadyEvent>();\n const seen = new WeakSet<object>();\n\n const announce = (entity: Entity): void => {\n const document = PanelDocument.data.document[entity.index] as\n | UIKitDocument\n | undefined;\n if (!document || seen.has(document)) {\n return;\n }\n seen.add(document);\n // Window id comes from the UIWindow component the factory attached. A\n // panel without one is a bare panel - still worth announcing, keyed by\n // its config path, which is the only stable id IWSDK gives it.\n const windowId = entity.hasComponent(UIWindow)\n ? (UIWindow.data.windowId[entity.index] as string)\n : '';\n const kind: 'window' | 'panel' = windowId ? 'window' : 'panel';\n const id = windowId || (PanelUI.data.config[entity.index] as string);\n const panel = panelHandleFor(document);\n // Upgrade `data-uix` controls exactly as the ECS controls system does,\n // so wiring code sees the same handles on every adapter (idempotent).\n upgradePanel(document, document.rootElement as unknown as UixElement);\n const event: PanelReadyEvent = { id, panel, kind };\n ready.set(id, event);\n for (const listener of readyListeners) {\n try {\n listener(event);\n } catch (error) {\n console.error(`[uix] panel-ready listener failed for \"${id}\":`, error);\n }\n }\n };\n\n world.registerSystem(createReadySystem(announce));\n\n // A window closed through the manager is gone: stop replaying it to late\n // subscribers. (The window system destroys the entity on the same event.)\n windowManagerFor(world).events.on('closed', ({ id }) => {\n ready.delete(id);\n });\n\n let windowSequence = 0;\n\n const host: IwsdkSceneHost = {\n world,\n\n // IWSDK owns panel lifecycles through the ECS, so there is no bare-panel\n // path here - `createPanel` throws rather than half-working.\n supportsStandalonePanels: false,\n\n createPanel(): PanelHandle {\n throw new Error(\n '[uix] createPanel() is not supported on the IWSDK host - spawn a window ' +\n '(spawnWindow/createUIWindow) so IWSDK owns the panel lifecycle.',\n );\n },\n\n onPanelReady(listener) {\n readyListeners.add(listener);\n for (const event of ready.values()) {\n listener(event);\n }\n return () => void readyListeners.delete(listener);\n },\n\n createWindow(options: CreateWindowOptions): IwsdkWindowHandle {\n const id = options.id ?? `uix-window-${(windowSequence += 1)}`;\n const entity = createUIWindow(world, { ...options, id });\n return {\n entity,\n id,\n get panel(): PanelHandle | undefined {\n return ready.get(id)?.panel;\n },\n onReady(listener: (panel: PanelHandle) => void): () => void {\n const existing = ready.get(id)?.panel;\n if (existing) {\n listener(existing);\n return () => {};\n }\n // `onPanelReady` replays panels that are already live, but this id\n // is not one of them, so the callback only ever runs later.\n const unsubscribe = host.onPanelReady((event) => {\n if (event.id === id) {\n unsubscribe();\n listener(event.panel);\n }\n });\n return unsubscribe;\n },\n };\n },\n\n spawnRegion(region: SceneRegion): void {\n createDockRegion(world, {\n id: region.id,\n ...(region.flow !== undefined ? { flow: region.flow } : {}),\n ...(region.pitch !== undefined ? { pitch: region.pitch } : {}),\n ...(region.columns !== undefined ? { columns: region.columns } : {}),\n ...(region.capacity !== undefined ? { capacity: region.capacity } : {}),\n ...(region.snapRadius !== undefined\n ? { snapRadius: region.snapRadius }\n : {}),\n ...(region.position !== undefined\n ? { position: [...region.position] as [number, number, number] }\n : {}),\n ...(region.follow !== undefined ? { follow: region.follow } : {}),\n ...(region.followOffset !== undefined\n ? { followOffset: [...region.followOffset] as [number, number, number] }\n : {}),\n });\n },\n\n spawnWindow(window: SceneWindow): void {\n host.createWindow({\n id: window.id,\n title: window.title,\n config: window.config,\n ...(window.position !== undefined\n ? { position: [...window.position] as [number, number, number] }\n : {}),\n ...(window.maxWidth !== undefined ? { maxWidth: window.maxWidth } : {}),\n ...(window.maxHeight !== undefined ? { maxHeight: window.maxHeight } : {}),\n ...(window.dockMode !== undefined ? { dockMode: window.dockMode } : {}),\n ...(window.region !== undefined ? { region: window.region } : {}),\n ...(window.followOffset !== undefined\n ? { followOffset: [...window.followOffset] as [number, number, number] }\n : {}),\n ...(window.followSpeed !== undefined\n ? { followSpeed: window.followSpeed }\n : {}),\n ...(window.followTolerance !== undefined\n ? { followTolerance: window.followTolerance }\n : {}),\n ...(window.movable !== undefined ? { movable: window.movable } : {}),\n ...(window.closable !== undefined ? { closable: window.closable } : {}),\n ...(window.minimizable !== undefined\n ? { minimizable: window.minimizable }\n : {}),\n ...(window.pinnable !== undefined ? { pinnable: window.pinnable } : {}),\n ...(window.dockable !== undefined ? { dockable: window.dockable } : {}),\n ...(window.handMenu !== undefined ? { handMenu: window.handMenu } : {}),\n });\n },\n };\n\n return host;\n}\n"]}
1
+ {"version":3,"file":"scene-host.js","sourceRoot":"","sources":["../src/scene-host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * IWSDK implementation of the core's `WindowHost` + `SceneTarget`.\n *\n * This is what makes a portable `SceneDescriptor` build the SAME playground\n * on IWSDK as on plain three.js, and lets app behaviour be wired through\n * `onPanelReady` instead of hand-written ECS queries:\n *\n * const host = createSceneHost(world);\n * applyScene(host, PLAYGROUND);\n * host.onPanelReady(({ id, panel }) => wireMyPanel(id, panel));\n *\n * Readiness is detected with a single ECS query on `PanelUI + PanelDocument`\n * - the engine-specific mechanism stays here, behind the portable interface.\n * Bare panels (a `PanelUI` entity with no `UIWindow`) are announced too, with\n * `kind: 'panel'` and their config path as the id, so devtools and hand-built\n * entities show up in the same stream as managed windows.\n */\nimport {\n PanelDocument,\n PanelUI,\n UIKitDocument,\n type Entity,\n type World,\n} from '@iwsdk/core';\nimport { createSystem } from './create-system.js';\nimport {\n upgradePanel,\n type PanelHandle,\n type PanelReadyEvent,\n type SceneRegion,\n type SceneTarget,\n type SceneWindow,\n type UixElement,\n type WindowHandle,\n type WindowHost,\n} from '@realitycollective/webxr-uiextensions';\nimport {\n createDockRegion,\n createUIWindow,\n type CreateWindowOptions,\n} from './factory.js';\nimport { UIWindow } from './components.js';\nimport { windowManagerFor } from './manager-registry.js';\n\n/** Panel handle over an IWSDK `UIKitDocument`. */\nfunction panelHandleFor(document: UIKitDocument): PanelHandle {\n return {\n get root(): UixElement {\n return document.rootElement as unknown as UixElement;\n },\n getElementById: (id) =>\n (document.getElementById(id) as UixElement | null) ?? undefined,\n setTargetDimensions: (width, height) =>\n document.setTargetDimensions(width, height),\n dispose: () => {\n /* IWSDK owns the document lifecycle via the entity. */\n },\n };\n}\n\n/**\n * The live panel on an entity, or `undefined` while IWSDK is still loading\n * its document. Use it to reach a panel from an entity you already hold;\n * `onPanelReady` stays the way to be TOLD when one appears.\n */\nexport function getPanelHandle(entity: Entity): PanelHandle | undefined {\n const document = PanelDocument.data.document[entity.index] as\n | UIKitDocument\n | undefined;\n return document ? panelHandleFor(document) : undefined;\n}\n\n/**\n * Bridges panel loading into `onPanelReady`. Registered once per host; the\n * query fires as each panel's document is attached by IWSDK's UI system.\n */\nfunction createReadySystem(onReady: (entity: Entity) => void) {\n return class UixPanelReadySystem extends createSystem({\n panels: { required: [PanelUI, PanelDocument] },\n }) {\n override init(): void {\n this.cleanupFuncs.push(\n this.queries.panels.subscribe('qualify', (entity) => onReady(entity)),\n );\n this.queries.panels.entities.forEach((entity) => onReady(entity));\n }\n };\n}\n\n/** A window spawned by {@link IwsdkSceneHost.createWindow}. */\nexport interface IwsdkWindowHandle extends WindowHandle {\n /** The ECS entity carrying `PanelUI`, `UIWindow` and the interaction tags. */\n readonly entity: Entity;\n}\n\nexport interface IwsdkSceneHost extends WindowHost, SceneTarget {\n readonly world: World;\n /**\n * Spawn a managed window and get a handle back straight away. IWSDK loads\n * the markup over later frames, so `handle.panel` is `undefined` at first\n * and `handle.onReady` is how you wait for it without an ECS query.\n */\n createWindow(options: CreateWindowOptions): IwsdkWindowHandle;\n}\n\nconst hosts = new WeakMap<World, IwsdkSceneHost>();\n\n/**\n * The host for a world: built on the first call, and the same instance every\n * call after that. Call AFTER `registerUIExtensions(world)`.\n *\n * One host per world is deliberate. A host owns an ECS system, the record of\n * which documents it has already announced, and the panel upgrade pass, so a\n * second independent host on the same world would run a second copy of all\n * three. Two modules can each ask for the host without coordinating, exactly\n * as they do for the window manager registry.\n */\nexport function createSceneHost(world: World): IwsdkSceneHost {\n const existing = hosts.get(world);\n if (existing) {\n return existing;\n }\n const host = buildSceneHost(world);\n hosts.set(world, host);\n return host;\n}\n\nfunction buildSceneHost(world: World): IwsdkSceneHost {\n const readyListeners = new Set<(event: PanelReadyEvent) => void>();\n const ready = new Map<string, PanelReadyEvent>();\n const seen = new WeakSet<object>();\n /** Ids of the windows this host spawned, closed by `dispose`. */\n const spawned = new Set<string>();\n let disposed = false;\n\n const announce = (entity: Entity): void => {\n // The readiness system stays registered on the world after `dispose`\n // (IWSDK has no per-system removal here), so it goes quiet instead.\n if (disposed) {\n return;\n }\n const document = PanelDocument.data.document[entity.index] as\n | UIKitDocument\n | undefined;\n if (!document || seen.has(document)) {\n return;\n }\n seen.add(document);\n // Window id comes from the UIWindow component the factory attached. A\n // panel without one is a bare panel - still worth announcing, keyed by\n // its config path, which is the only stable id IWSDK gives it.\n const windowId = entity.hasComponent(UIWindow)\n ? (UIWindow.data.windowId[entity.index] as string)\n : '';\n const kind: 'window' | 'panel' = windowId ? 'window' : 'panel';\n const id = windowId || (PanelUI.data.config[entity.index] as string);\n const panel = panelHandleFor(document);\n // Upgrade `data-uix` controls exactly as the ECS controls system does,\n // so wiring code sees the same handles on every adapter (idempotent).\n upgradePanel(document, document.rootElement as unknown as UixElement);\n const event: PanelReadyEvent = { id, panel, kind };\n ready.set(id, event);\n for (const listener of readyListeners) {\n try {\n listener(event);\n } catch (error) {\n console.error(`[uix] panel-ready listener failed for \"${id}\":`, error);\n }\n }\n };\n\n world.registerSystem(createReadySystem(announce));\n\n // A window closed through the manager is gone: stop replaying it to late\n // subscribers. (The window system destroys the entity on the same event.)\n const manager = windowManagerFor(world);\n const stopClosed = manager.events.on('closed', ({ id }) => {\n ready.delete(id);\n spawned.delete(id);\n });\n\n let windowSequence = 0;\n\n const host: IwsdkSceneHost = {\n world,\n\n // IWSDK owns panel lifecycles through the ECS, so there is no bare-panel\n // path here - `createPanel` throws rather than half-working.\n supportsStandalonePanels: false,\n\n createPanel(): PanelHandle {\n throw new Error(\n '[uix] createPanel() is not supported on the IWSDK host - spawn a window ' +\n '(spawnWindow/createUIWindow) so IWSDK owns the panel lifecycle.',\n );\n },\n\n // Leave nothing behind: close every window this host spawned through the\n // world's manager (the window system destroys each entity on that event),\n // silence the readiness system, release the manager subscription, and\n // forget this host so `createSceneHost` builds a fresh one. Safe to call\n // twice.\n dispose(): void {\n if (disposed) {\n return;\n }\n // The window system opens a window's record only once its panel\n // attaches, so a window still loading has nothing to close yet.\n for (const id of [...spawned]) {\n if (manager.has(id)) {\n manager.close(id);\n }\n }\n disposed = true;\n spawned.clear();\n ready.clear();\n readyListeners.clear();\n stopClosed();\n hosts.delete(world);\n },\n\n onPanelReady(listener) {\n readyListeners.add(listener);\n for (const event of ready.values()) {\n listener(event);\n }\n return () => void readyListeners.delete(listener);\n },\n\n createWindow(options: CreateWindowOptions): IwsdkWindowHandle {\n const id = options.id ?? `uix-window-${(windowSequence += 1)}`;\n const entity = createUIWindow(world, { ...options, id });\n spawned.add(id);\n return {\n entity,\n id,\n get panel(): PanelHandle | undefined {\n return ready.get(id)?.panel;\n },\n onReady(listener: (panel: PanelHandle) => void): () => void {\n const existing = ready.get(id)?.panel;\n if (existing) {\n listener(existing);\n return () => {};\n }\n // `onPanelReady` replays panels that are already live, but this id\n // is not one of them, so the callback only ever runs later.\n const unsubscribe = host.onPanelReady((event) => {\n if (event.id === id) {\n unsubscribe();\n listener(event.panel);\n }\n });\n return unsubscribe;\n },\n };\n },\n\n spawnRegion(region: SceneRegion): void {\n createDockRegion(world, {\n id: region.id,\n ...(region.flow !== undefined ? { flow: region.flow } : {}),\n ...(region.pitch !== undefined ? { pitch: region.pitch } : {}),\n ...(region.columns !== undefined ? { columns: region.columns } : {}),\n ...(region.capacity !== undefined ? { capacity: region.capacity } : {}),\n ...(region.snapRadius !== undefined\n ? { snapRadius: region.snapRadius }\n : {}),\n ...(region.position !== undefined\n ? { position: [...region.position] as [number, number, number] }\n : {}),\n ...(region.follow !== undefined ? { follow: region.follow } : {}),\n ...(region.followOffset !== undefined\n ? { followOffset: [...region.followOffset] as [number, number, number] }\n : {}),\n });\n },\n\n spawnWindow(window: SceneWindow): void {\n host.createWindow({\n id: window.id,\n title: window.title,\n config: window.config,\n ...(window.position !== undefined\n ? { position: [...window.position] as [number, number, number] }\n : {}),\n ...(window.maxWidth !== undefined ? { maxWidth: window.maxWidth } : {}),\n ...(window.maxHeight !== undefined ? { maxHeight: window.maxHeight } : {}),\n ...(window.dockMode !== undefined ? { dockMode: window.dockMode } : {}),\n ...(window.region !== undefined ? { region: window.region } : {}),\n ...(window.followOffset !== undefined\n ? { followOffset: [...window.followOffset] as [number, number, number] }\n : {}),\n ...(window.followSpeed !== undefined\n ? { followSpeed: window.followSpeed }\n : {}),\n ...(window.followTolerance !== undefined\n ? { followTolerance: window.followTolerance }\n : {}),\n ...(window.movable !== undefined ? { movable: window.movable } : {}),\n ...(window.closable !== undefined ? { closable: window.closable } : {}),\n ...(window.minimizable !== undefined\n ? { minimizable: window.minimizable }\n : {}),\n ...(window.pinnable !== undefined ? { pinnable: window.pinnable } : {}),\n ...(window.dockable !== undefined ? { dockable: window.dockable } : {}),\n ...(window.handMenu !== undefined ? { handMenu: window.handMenu } : {}),\n });\n },\n };\n\n return host;\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import type { RegionFlow } from '@realitycollective/webxr-uiextensions';
1
2
  declare const UIDockRegionSystem_base: import("elics").SystemConstructor<import("elics").SystemSchema, {
2
3
  regions: {
3
4
  required: import("elics").Component<{
@@ -12,7 +13,7 @@ declare const UIDockRegionSystem_base: import("elics").SystemConstructor<import(
12
13
  readonly Column: "column";
13
14
  readonly Grid: "grid";
14
15
  };
15
- default: "column";
16
+ default: RegionFlow;
16
17
  };
17
18
  pitch: {
18
19
  type: import("@iwsdk/core").Types.Float32;
@@ -160,7 +161,7 @@ declare const UIDockRegionSystem_base: import("elics").SystemConstructor<import(
160
161
  readonly Column: "column";
161
162
  readonly Grid: "grid";
162
163
  };
163
- default: "column";
164
+ default: RegionFlow;
164
165
  };
165
166
  pitch: {
166
167
  type: import("@iwsdk/core").Types.Float32;
@@ -81,7 +81,8 @@ export class UIDockRegionSystem extends createSystem({
81
81
  entity.removeComponent(UIDockedTo);
82
82
  return;
83
83
  }
84
- // Docked windows are world-locked by definition (the region may follow).
84
+ // Docked windows are world-locked, the core manager's rule (the region
85
+ // may follow). Mirror it on the component before the record catches up.
85
86
  if (entity.getValue(UIWindow, 'dockMode') !== DockMode.WorldLocked) {
86
87
  entity.setValue(UIWindow, 'dockMode', DockMode.WorldLocked);
87
88
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dock-region-system.js","sourceRoot":"","sources":["../../src/systems/dock-region-system.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * UIDockRegionSystem - keeps the region registry in sync with `UIDockRegion`\n * entities and lays docked windows out in their region's slots each frame.\n *\n * Regions are ordinary entities: give one a `Follower` and its docked\n * windows become a body-locked toolbar; leave it static for a world-locked\n * console wall.\n */\nimport type { Entity } from '@iwsdk/core';\nimport { createSystem } from '../create-system.js';\nimport { Quaternion, Vector3 } from 'three';\nimport { UIDockRegion, UIDockedTo, UIWindow } from '../components.js';\nimport { DockMode } from '@realitycollective/webxr-uiextensions';\nimport type { RegionDefinition, RegionFlow } from '@realitycollective/webxr-uiextensions';\nimport { regionRegistryFor } from '../region-registry-store.js';\nimport { windowManagerFor } from '../manager-registry.js';\nimport type { RegionRegistry } from '@realitycollective/webxr-uiextensions';\n\nlet autoRegionId = 0;\n\nexport class UIDockRegionSystem extends createSystem({\n regions: { required: [UIDockRegion] },\n docked: { required: [UIWindow, UIDockedTo] },\n}) {\n private registry!: RegionRegistry;\n private regionEntities = new Map<string, Entity>();\n private slotPosition = new Vector3();\n private regionQuaternion = new Quaternion();\n\n override init(): void {\n this.registry = regionRegistryFor(this.world);\n\n this.cleanupFuncs.push(\n this.queries.regions.subscribe('qualify', (entity) => this.addRegion(entity)),\n this.queries.regions.subscribe('disqualify', (entity) => this.removeRegion(entity)),\n this.queries.docked.subscribe('qualify', (entity) => this.dockWindow(entity)),\n this.queries.docked.subscribe('disqualify', (entity) => this.undockWindow(entity)),\n );\n this.queries.regions.entities.forEach((entity) => this.addRegion(entity));\n this.queries.docked.entities.forEach((entity) => this.dockWindow(entity));\n }\n\n override update(): void {\n for (const entity of this.queries.docked.entities) {\n const windowId = entity.getValue(UIWindow, 'windowId') as string;\n const offset = this.registry.offsetOf(windowId);\n const regionId = this.registry.regionOf(windowId);\n const regionEntity = regionId === undefined ? undefined : this.regionEntities.get(regionId);\n const regionObject = regionEntity?.object3D;\n const object = entity.object3D;\n if (!offset || !regionObject || !object || !object.parent) {\n continue;\n }\n // Slot offset is region-local; express it in the window's parent space.\n this.slotPosition.set(offset[0], offset[1], offset[2]);\n regionObject.localToWorld(this.slotPosition);\n object.parent.worldToLocal(this.slotPosition);\n object.position.copy(this.slotPosition);\n regionObject.getWorldQuaternion(this.regionQuaternion);\n object.quaternion.copy(this.regionQuaternion);\n }\n }\n\n private addRegion(entity: Entity): void {\n let id = entity.getValue(UIDockRegion, 'regionId') as string;\n if (!id) {\n id = `uix-region-${autoRegionId++}`;\n entity.setValue(UIDockRegion, 'regionId', id);\n }\n if (this.registry.get(id)) {\n return;\n }\n const definition: Partial<RegionDefinition> = {\n flow: entity.getValue(UIDockRegion, 'flow') as RegionFlow,\n pitch: entity.getValue(UIDockRegion, 'pitch') as number,\n columns: Math.max(1, Math.round(entity.getValue(UIDockRegion, 'columns') as number)),\n capacity: Math.max(0, Math.round(entity.getValue(UIDockRegion, 'capacity') as number)),\n snapRadius: entity.getValue(UIDockRegion, 'snapRadius') as number,\n };\n this.registry.register(id, definition);\n this.regionEntities.set(id, entity);\n }\n\n private removeRegion(entity: Entity): void {\n const id = entity.getValue(UIDockRegion, 'regionId') as string;\n if (id && this.regionEntities.get(id) === entity) {\n this.registry.unregister(id);\n this.regionEntities.delete(id);\n }\n }\n\n private dockWindow(entity: Entity): void {\n const windowId = entity.getValue(UIWindow, 'windowId') as string;\n const regionId = entity.getValue(UIDockedTo, 'regionId') as string;\n if (!windowId || !regionId || !this.registry.get(regionId)) {\n // Unknown region (or the window has no id yet) - docking is meaningless.\n entity.removeComponent(UIDockedTo);\n return;\n }\n try {\n this.registry.dock(windowId, regionId);\n } catch {\n // Region is full: reject the dock.\n entity.removeComponent(UIDockedTo);\n return;\n }\n // Docked windows are world-locked by definition (the region may follow).\n if (entity.getValue(UIWindow, 'dockMode') !== DockMode.WorldLocked) {\n entity.setValue(UIWindow, 'dockMode', DockMode.WorldLocked);\n }\n // Write the placement back so the record shows it (a drag-drop dock, or\n // a spawn-docked window). A no-op when the manager asked for it.\n const manager = windowManagerFor(this.world);\n if (manager.has(windowId)) {\n manager.dockTo(windowId, regionId);\n }\n }\n\n private undockWindow(entity: Entity): void {\n const windowId = entity.getValue(UIWindow, 'windowId') as string;\n if (!windowId) {\n return;\n }\n this.registry.undock(windowId);\n // Same write-back on the way out: a drag lifted it, or the dock was\n // rejected (unknown or full region) and the record must not claim it.\n const manager = windowManagerFor(this.world);\n if (manager.has(windowId)) {\n manager.undock(windowId);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"dock-region-system.js","sourceRoot":"","sources":["../../src/systems/dock-region-system.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * UIDockRegionSystem - keeps the region registry in sync with `UIDockRegion`\n * entities and lays docked windows out in their region's slots each frame.\n *\n * Regions are ordinary entities: give one a `Follower` and its docked\n * windows become a body-locked toolbar; leave it static for a world-locked\n * console wall.\n */\nimport type { Entity } from '@iwsdk/core';\nimport { createSystem } from '../create-system.js';\nimport { Quaternion, Vector3 } from 'three';\nimport { UIDockRegion, UIDockedTo, UIWindow } from '../components.js';\nimport { DockMode } from '@realitycollective/webxr-uiextensions';\nimport type { RegionDefinition, RegionFlow } from '@realitycollective/webxr-uiextensions';\nimport { regionRegistryFor } from '../region-registry-store.js';\nimport { windowManagerFor } from '../manager-registry.js';\nimport type { RegionRegistry } from '@realitycollective/webxr-uiextensions';\n\nlet autoRegionId = 0;\n\nexport class UIDockRegionSystem extends createSystem({\n regions: { required: [UIDockRegion] },\n docked: { required: [UIWindow, UIDockedTo] },\n}) {\n private registry!: RegionRegistry;\n private regionEntities = new Map<string, Entity>();\n private slotPosition = new Vector3();\n private regionQuaternion = new Quaternion();\n\n override init(): void {\n this.registry = regionRegistryFor(this.world);\n\n this.cleanupFuncs.push(\n this.queries.regions.subscribe('qualify', (entity) => this.addRegion(entity)),\n this.queries.regions.subscribe('disqualify', (entity) => this.removeRegion(entity)),\n this.queries.docked.subscribe('qualify', (entity) => this.dockWindow(entity)),\n this.queries.docked.subscribe('disqualify', (entity) => this.undockWindow(entity)),\n );\n this.queries.regions.entities.forEach((entity) => this.addRegion(entity));\n this.queries.docked.entities.forEach((entity) => this.dockWindow(entity));\n }\n\n override update(): void {\n for (const entity of this.queries.docked.entities) {\n const windowId = entity.getValue(UIWindow, 'windowId') as string;\n const offset = this.registry.offsetOf(windowId);\n const regionId = this.registry.regionOf(windowId);\n const regionEntity = regionId === undefined ? undefined : this.regionEntities.get(regionId);\n const regionObject = regionEntity?.object3D;\n const object = entity.object3D;\n if (!offset || !regionObject || !object || !object.parent) {\n continue;\n }\n // Slot offset is region-local; express it in the window's parent space.\n this.slotPosition.set(offset[0], offset[1], offset[2]);\n regionObject.localToWorld(this.slotPosition);\n object.parent.worldToLocal(this.slotPosition);\n object.position.copy(this.slotPosition);\n regionObject.getWorldQuaternion(this.regionQuaternion);\n object.quaternion.copy(this.regionQuaternion);\n }\n }\n\n private addRegion(entity: Entity): void {\n let id = entity.getValue(UIDockRegion, 'regionId') as string;\n if (!id) {\n id = `uix-region-${autoRegionId++}`;\n entity.setValue(UIDockRegion, 'regionId', id);\n }\n if (this.registry.get(id)) {\n return;\n }\n const definition: Partial<RegionDefinition> = {\n flow: entity.getValue(UIDockRegion, 'flow') as RegionFlow,\n pitch: entity.getValue(UIDockRegion, 'pitch') as number,\n columns: Math.max(1, Math.round(entity.getValue(UIDockRegion, 'columns') as number)),\n capacity: Math.max(0, Math.round(entity.getValue(UIDockRegion, 'capacity') as number)),\n snapRadius: entity.getValue(UIDockRegion, 'snapRadius') as number,\n };\n this.registry.register(id, definition);\n this.regionEntities.set(id, entity);\n }\n\n private removeRegion(entity: Entity): void {\n const id = entity.getValue(UIDockRegion, 'regionId') as string;\n if (id && this.regionEntities.get(id) === entity) {\n this.registry.unregister(id);\n this.regionEntities.delete(id);\n }\n }\n\n private dockWindow(entity: Entity): void {\n const windowId = entity.getValue(UIWindow, 'windowId') as string;\n const regionId = entity.getValue(UIDockedTo, 'regionId') as string;\n if (!windowId || !regionId || !this.registry.get(regionId)) {\n // Unknown region (or the window has no id yet) - docking is meaningless.\n entity.removeComponent(UIDockedTo);\n return;\n }\n try {\n this.registry.dock(windowId, regionId);\n } catch {\n // Region is full: reject the dock.\n entity.removeComponent(UIDockedTo);\n return;\n }\n // Docked windows are world-locked, the core manager's rule (the region\n // may follow). Mirror it on the component before the record catches up.\n if (entity.getValue(UIWindow, 'dockMode') !== DockMode.WorldLocked) {\n entity.setValue(UIWindow, 'dockMode', DockMode.WorldLocked);\n }\n // Write the placement back so the record shows it (a drag-drop dock, or\n // a spawn-docked window). A no-op when the manager asked for it.\n const manager = windowManagerFor(this.world);\n if (manager.has(windowId)) {\n manager.dockTo(windowId, regionId);\n }\n }\n\n private undockWindow(entity: Entity): void {\n const windowId = entity.getValue(UIWindow, 'windowId') as string;\n if (!windowId) {\n return;\n }\n this.registry.undock(windowId);\n // Same write-back on the way out: a drag lifted it, or the dock was\n // rejected (unknown or full region) and the record must not claim it.\n const manager = windowManagerFor(this.world);\n if (manager.has(windowId)) {\n manager.undock(windowId);\n }\n }\n}\n"]}
@@ -169,7 +169,7 @@ declare const UIDragSystem_base: import("elics").SystemConstructor<{
169
169
  readonly Column: "column";
170
170
  readonly Grid: "grid";
171
171
  };
172
- default: "column";
172
+ default: import("@realitycollective/webxr-uiextensions").RegionFlow;
173
173
  };
174
174
  pitch: {
175
175
  type: Types.Float32;
@@ -359,7 +359,7 @@ declare const UIDragSystem_base: import("elics").SystemConstructor<{
359
359
  readonly Column: "column";
360
360
  readonly Grid: "grid";
361
361
  };
362
- default: "column";
362
+ default: import("@realitycollective/webxr-uiextensions").RegionFlow;
363
363
  };
364
364
  pitch: {
365
365
  type: Types.Float32;
@@ -26,10 +26,13 @@
26
26
  */
27
27
  import { Types } from '@iwsdk/core';
28
28
  import { Vector3, type Object3D } from 'three';
29
- import { TouchPress, type TouchSample } from '@realitycollective/webxr-uiextensions';
30
- type Handedness = 'left' | 'right';
31
- /** The slice of a pointer-events `Pointer` this system drives. */
32
- interface TouchPointerLike {
29
+ import { type Hand, TouchPress, type TouchSample } from '@realitycollective/webxr-uiextensions';
30
+ /**
31
+ * The slice of a pointer-events `Pointer` this system drives. Exported
32
+ * because it is the parameter type of `sampleOf`, so a caller feeding the
33
+ * sampler from a fake pointer in a test has a name for the shape.
34
+ */
35
+ export interface TouchPointerLike {
33
36
  down(event: {
34
37
  timeStamp: number;
35
38
  button: number;
@@ -86,7 +89,7 @@ export declare class UITouchGuardSystem extends UITouchGuardSystem_base {
86
89
  init(): void;
87
90
  update(_delta: number, time: number): void;
88
91
  /** The state machine for a hand, for inspection and tests. */
89
- pressFor(hand: Handedness): TouchPress<Object3D> | undefined;
92
+ pressFor(hand: Hand): TouchPress<Object3D> | undefined;
90
93
  }
91
94
  /**
92
95
  * This frame's signed distance from the fingertip to the surface it is over,
@@ -1 +1 @@
1
- {"version":3,"file":"touch-guard-system.js","sourceRoot":"","sources":["../../src/systems/touch-guard-system.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * UITouchGuardSystem - press / hold / release for IWSDK's near (poke)\n * pointers, driven by the core's `TouchPress` state machine.\n *\n * What IWSDK does on its own. Its touch pointer (`@pmndrs/pointer-events`)\n * presses when the fingertip's distance to a panel drops under 2 cm and\n * releases when it rises above, and that distance has no sign: uikit's panel\n * spherecast projects the fingertip onto the panel plane and measures how\n * far it is, either side. A finger pushed through a panel and pulled back\n * therefore fires press, release, press, release - two clicks for one\n * gesture - and a finger arriving from behind presses too.\n *\n * What this system does. It takes over the two touch pointers' `down` and\n * `up`: IWSDK's own calls become no-ops, and each frame (right after\n * `InputSystem` has moved the pointers) the fingertip's SIGNED distance to\n * what it is over is fed to a `TouchPress`, whose transitions call the\n * original `down` / `up`. The pointer still delivers `pointerdown`,\n * `pointerup` and `click` to the element exactly as before, so nothing\n * downstream changes; only when they fire does. The guarantees are the\n * machine's: a press only from the front, one press per touch until a\n * release, release on coming back out or on losing contact.\n *\n * Every poke target in the app benefits, IWSDK's own panels included, since\n * they share the same two pointers. Off with\n * `registerUIExtensions(world, { touchGuard: false })`.\n */\nimport { Types } from '@iwsdk/core';\nimport { createSystem } from '../create-system.js';\nimport { Quaternion, Vector3, type Object3D } from 'three';\nimport {\n DEFAULT_TOUCH_PRESS,\n TouchPress,\n type TouchSample,\n} from '@realitycollective/webxr-uiextensions';\n\ntype Handedness = 'left' | 'right';\n\n/** The slice of a pointer-events `Pointer` this system drives. */\ninterface TouchPointerLike {\n down(event: { timeStamp: number; button: number }): void;\n up(event: { timeStamp: number; button: number }): void;\n getIntersection():\n | {\n object: Object3D & { isVoidObject?: boolean };\n point: Vector3;\n pointerPosition?: Vector3;\n normal?: Vector3;\n distance: number;\n }\n | undefined;\n}\n\ninterface Guard {\n pointer: TouchPointerLike;\n press: TouchPress<Object3D>;\n down: TouchPointerLike['down'];\n up: TouchPointerLike['up'];\n}\n\nconst HANDS: readonly Handedness[] = ['left', 'right'];\nconst HELPER_NORMAL = new Vector3();\nconst HELPER_OFFSET = new Vector3();\nconst HELPER_QUATERNION = new Quaternion();\n\nexport class UITouchGuardSystem extends createSystem(\n {},\n {\n /** Signed distance in front (meters) at or under which a touch presses. */\n pressDistance: { type: Types.Float32, default: DEFAULT_TOUCH_PRESS.pressDistance },\n /** Signed distance in front the finger must come back past to release. */\n releaseDistance: { type: Types.Float32, default: DEFAULT_TOUCH_PRESS.releaseDistance },\n /** Let a fingertip arriving from behind a panel press. */\n allowFromBehind: { type: Types.Boolean, default: DEFAULT_TOUCH_PRESS.allowFromBehind },\n },\n) {\n private guards = new Map<Handedness, Guard>();\n\n override init(): void {\n const xr = (this.input as typeof this.input | undefined)?.xr;\n if (!xr) {\n return; // headless, or no XR input: nothing to guard\n }\n for (const hand of HANDS) {\n const pointer = xr.multiPointers[hand].getPointer('touch') as unknown as TouchPointerLike;\n const guard: Guard = {\n pointer,\n press: new TouchPress<Object3D>({\n pressDistance: this.config.pressDistance.value,\n releaseDistance: this.config.releaseDistance.value,\n allowFromBehind: this.config.allowFromBehind.value,\n }),\n down: pointer.down.bind(pointer),\n up: pointer.up.bind(pointer),\n };\n // IWSDK's unsigned-distance press and its forced release on lost hover\n // both land here and are dropped; the machine below decides instead.\n pointer.down = () => {};\n pointer.up = () => {};\n this.guards.set(hand, guard);\n }\n this.cleanupFuncs.push(() => {\n for (const guard of this.guards.values()) {\n guard.pointer.down = guard.down;\n guard.pointer.up = guard.up;\n guard.press.reset();\n }\n this.guards.clear();\n });\n }\n\n override update(_delta: number, time: number): void {\n const event = { timeStamp: time * 1000, button: 0 };\n for (const guard of this.guards.values()) {\n const result = guard.press.update(sampleOf(guard.pointer));\n if (result.pressed) {\n guard.down(event);\n }\n if (result.released) {\n guard.up(event);\n }\n }\n }\n\n /** The state machine for a hand, for inspection and tests. */\n pressFor(hand: Handedness): TouchPress<Object3D> | undefined {\n return this.guards.get(hand)?.press;\n }\n}\n\n/**\n * This frame's signed distance from the fingertip to the surface it is over,\n * positive in front. The surface normal is the intersection's local normal\n * (uikit panels report +Z) taken to world space; a target with no normal is\n * treated as faced from the front, so a plain mesh keeps IWSDK's behaviour.\n */\nexport function sampleOf(pointer: TouchPointerLike): TouchSample<Object3D> | undefined {\n const intersection = pointer.getIntersection();\n if (!intersection || intersection.object.isVoidObject) {\n return undefined;\n }\n const { object, point, pointerPosition, normal } = intersection;\n if (!normal || !pointerPosition) {\n return { signedDistance: intersection.distance, target: object };\n }\n object.getWorldQuaternion(HELPER_QUATERNION);\n HELPER_NORMAL.copy(normal).applyQuaternion(HELPER_QUATERNION).normalize();\n HELPER_OFFSET.copy(pointerPosition).sub(point);\n return { signedDistance: HELPER_OFFSET.dot(HELPER_NORMAL), target: object };\n}\n"]}
1
+ {"version":3,"file":"touch-guard-system.js","sourceRoot":"","sources":["../../src/systems/touch-guard-system.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * UITouchGuardSystem - press / hold / release for IWSDK's near (poke)\n * pointers, driven by the core's `TouchPress` state machine.\n *\n * What IWSDK does on its own. Its touch pointer (`@pmndrs/pointer-events`)\n * presses when the fingertip's distance to a panel drops under 2 cm and\n * releases when it rises above, and that distance has no sign: uikit's panel\n * spherecast projects the fingertip onto the panel plane and measures how\n * far it is, either side. A finger pushed through a panel and pulled back\n * therefore fires press, release, press, release - two clicks for one\n * gesture - and a finger arriving from behind presses too.\n *\n * What this system does. It takes over the two touch pointers' `down` and\n * `up`: IWSDK's own calls become no-ops, and each frame (right after\n * `InputSystem` has moved the pointers) the fingertip's SIGNED distance to\n * what it is over is fed to a `TouchPress`, whose transitions call the\n * original `down` / `up`. The pointer still delivers `pointerdown`,\n * `pointerup` and `click` to the element exactly as before, so nothing\n * downstream changes; only when they fire does. The guarantees are the\n * machine's: a press only from the front, one press per touch until a\n * release, release on coming back out or on losing contact.\n *\n * Every poke target in the app benefits, IWSDK's own panels included, since\n * they share the same two pointers. Off with\n * `registerUIExtensions(world, { touchGuard: false })`.\n */\nimport { Types } from '@iwsdk/core';\nimport { createSystem } from '../create-system.js';\nimport { Quaternion, Vector3, type Object3D } from 'three';\nimport {\n DEFAULT_TOUCH_PRESS,\n type Hand,\n TouchPress,\n type TouchSample,\n} from '@realitycollective/webxr-uiextensions';\n\n/**\n * The slice of a pointer-events `Pointer` this system drives. Exported\n * because it is the parameter type of `sampleOf`, so a caller feeding the\n * sampler from a fake pointer in a test has a name for the shape.\n */\nexport interface TouchPointerLike {\n down(event: { timeStamp: number; button: number }): void;\n up(event: { timeStamp: number; button: number }): void;\n getIntersection():\n | {\n object: Object3D & { isVoidObject?: boolean };\n point: Vector3;\n pointerPosition?: Vector3;\n normal?: Vector3;\n distance: number;\n }\n | undefined;\n}\n\ninterface Guard {\n pointer: TouchPointerLike;\n press: TouchPress<Object3D>;\n down: TouchPointerLike['down'];\n up: TouchPointerLike['up'];\n}\n\nconst HANDS: readonly Hand[] = ['left', 'right'];\nconst HELPER_NORMAL = new Vector3();\nconst HELPER_OFFSET = new Vector3();\nconst HELPER_QUATERNION = new Quaternion();\n\nexport class UITouchGuardSystem extends createSystem(\n {},\n {\n /** Signed distance in front (meters) at or under which a touch presses. */\n pressDistance: { type: Types.Float32, default: DEFAULT_TOUCH_PRESS.pressDistance },\n /** Signed distance in front the finger must come back past to release. */\n releaseDistance: { type: Types.Float32, default: DEFAULT_TOUCH_PRESS.releaseDistance },\n /** Let a fingertip arriving from behind a panel press. */\n allowFromBehind: { type: Types.Boolean, default: DEFAULT_TOUCH_PRESS.allowFromBehind },\n },\n) {\n private guards = new Map<Hand, Guard>();\n\n override init(): void {\n const xr = (this.input as typeof this.input | undefined)?.xr;\n if (!xr) {\n return; // headless, or no XR input: nothing to guard\n }\n for (const hand of HANDS) {\n const pointer = xr.multiPointers[hand].getPointer('touch') as unknown as TouchPointerLike;\n const guard: Guard = {\n pointer,\n press: new TouchPress<Object3D>({\n pressDistance: this.config.pressDistance.value,\n releaseDistance: this.config.releaseDistance.value,\n allowFromBehind: this.config.allowFromBehind.value,\n }),\n down: pointer.down.bind(pointer),\n up: pointer.up.bind(pointer),\n };\n // IWSDK's unsigned-distance press and its forced release on lost hover\n // both land here and are dropped; the machine below decides instead.\n pointer.down = () => {};\n pointer.up = () => {};\n this.guards.set(hand, guard);\n }\n this.cleanupFuncs.push(() => {\n for (const guard of this.guards.values()) {\n guard.pointer.down = guard.down;\n guard.pointer.up = guard.up;\n guard.press.reset();\n }\n this.guards.clear();\n });\n }\n\n override update(_delta: number, time: number): void {\n const event = { timeStamp: time * 1000, button: 0 };\n for (const guard of this.guards.values()) {\n const result = guard.press.update(sampleOf(guard.pointer));\n if (result.pressed) {\n guard.down(event);\n }\n if (result.released) {\n guard.up(event);\n }\n }\n }\n\n /** The state machine for a hand, for inspection and tests. */\n pressFor(hand: Hand): TouchPress<Object3D> | undefined {\n return this.guards.get(hand)?.press;\n }\n}\n\n/**\n * This frame's signed distance from the fingertip to the surface it is over,\n * positive in front. The surface normal is the intersection's local normal\n * (uikit panels report +Z) taken to world space; a target with no normal is\n * treated as faced from the front, so a plain mesh keeps IWSDK's behaviour.\n */\nexport function sampleOf(pointer: TouchPointerLike): TouchSample<Object3D> | undefined {\n const intersection = pointer.getIntersection();\n if (!intersection || intersection.object.isVoidObject) {\n return undefined;\n }\n const { object, point, pointerPosition, normal } = intersection;\n if (!normal || !pointerPosition) {\n return { signedDistance: intersection.distance, target: object };\n }\n object.getWorldQuaternion(HELPER_QUATERNION);\n HELPER_NORMAL.copy(normal).applyQuaternion(HELPER_QUATERNION).normalize();\n HELPER_OFFSET.copy(pointerPosition).sub(point);\n return { signedDistance: HELPER_OFFSET.dot(HELPER_NORMAL), target: object };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@realitycollective/iwsdk-uiextensions",
3
- "version": "0.1.0",
3
+ "version": "0.1.1-preview.1",
4
4
  "description": "Meta IWSDK adapter for the Reality Collective UI Extensions - windowing, docking, layout regions and extra controls, binding the engine-free @realitycollective/webxr-uiextensions core onto IWSDK's ECS, UIKitML and interaction systems.",
5
5
  "keywords": [
6
6
  "realitycollective",
@@ -39,14 +39,14 @@
39
39
  "@drawcall/uikitml": "0.1.8",
40
40
  "zod": "^4.4.3",
41
41
  "@pmndrs/handle": "^6.6.16",
42
- "@realitycollective/webxr-uiextensions": "^0.1.0"
42
+ "@realitycollective/webxr-uiextensions": "^0.1.1-preview.1"
43
43
  },
44
44
  "peerDependencies": {
45
- "@iwsdk/core": ">=0.5.0 <0.6.0",
45
+ "@iwsdk/core": ">=1.0.0 <2.0.0",
46
46
  "three": ">=0.170.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@iwsdk/core": "0.5.3",
49
+ "@iwsdk/core": "1.0.0",
50
50
  "three": "npm:super-three@0.181.0",
51
51
  "@types/three": "0.184.0"
52
52
  },