@realitycollective/webxr-uiextensions 0.1.0-preview.0 → 0.1.0-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
@@ -13,5 +13,17 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
13
13
  - `@realitycollective/xrblocks-uiextensions` - EXPERIMENTAL Google XR Blocks / plain three.js adapter: panel document, window host, follow and scale maths, desktop controls and locomotion, pointer forwarding.
14
14
  - `@realitycollective/uix-devtools` - dev-only tooling: edit-session launch gate, runtime UIKitML compilation, and the `uix-dev` CLI (Cloudflare quick tunnel, QR onboarding, environment doctor).
15
15
  - Demo clients: the IWSDK showcase, the devtools playground, and the multiplatform lab that picks its pipeline from the hardware.
16
+ - `uixComponentSet`, exported from `@realitycollective/iwsdk-uiextensions`. Pass it to `spatialUI.componentSets` so the IWSDK 0.5 parser accepts the `<uix-*>` control elements. Without it a panel using any control fails to parse and never attaches. No equivalent is needed on the three.js or XR Blocks adapters.
17
+
18
+ ### Changed
19
+
20
+ - **Controls are declared as custom elements rather than attributes.** `<uix-stepper>` replaces `<div data-uix="stepper">`, and each part is its own element: `<uix-value>`, `<uix-line>`, `<uix-label>` and so on, replacing `data-uix-role`. Parameters are unchanged and stay `data-uix-*`. This is what makes one markup file portable: IWSDK 0.5 parses with `@drawcall/uikitml`, which rejects every `data-*` attribute on a built-in tag but accepts custom tags declared in a component set, while the three.js and XR Blocks parser accepts custom tags with no registration at all. Both expose the declared tag on `userData.customElement.componentName`, so the engine-free upgraders read one contract on every adapter.
21
+ - The expandable label's `toggle` part is now `<uix-more>`. As an element the old name would have collided with the `<uix-toggle>` control.
22
+ - `@realitycollective/iwsdk-uiextensions` targets **IWSDK 0.5.x**: peer range `>=0.5.0 <0.6.0`, developed against `@iwsdk/core` 0.5.3. `PanelUI` in 0.5 declares a single field, `config`, holding the URL of the UIKitML **source**; the compiled JSON it took on 0.4.x, and the `@iwsdk/vite-plugin-uikitml` step that produced it, are both gone. `maxWidth` and `maxHeight` were removed from the component schema, and ECS ignores unknown fields silently, so a panel that still writes them is mis-sized with no error. Size through `createUIWindow`, which routes to `UIWindow.targetWidth/Height`.
23
+ - `UixWindowHost` resolves a window's `config` by fetching the `.uikitml` source and parsing it, where it previously fetched compiled JSON. Supplying `loadConfig` is no longer necessary for an ordinary panel; override it only for an unusual transport.
24
+ - `compilePanelSource` in `@realitycollective/uix-devtools` serves the **source** on its `blob:` URL rather than compiled JSON, matching what 0.5 fetches. `CompiledPanel.json` is now `CompiledPanel.source`, and the `resolveFile` option is gone because the 0.5 parser has no `<link ref>` stylesheet resolution; put shared rules in a `<style>` block, of which several are allowed and merge. Validation now runs on `@drawcall/uikitml`, pinned to the version `@iwsdk/core` depends on, so a source that passes here is one `PanelUISystem` will accept and no second parser enters the tree.
25
+ - Example and demo markup uses `rgba()` or eight-digit hex rather than `background-opacity`, which the 0.5 parser removed. Both parsers accept the replacement, so one stylesheet serves every adapter.
26
+ - The IWSDK component kit is selected by name, `spatialUI: { kit: 'horizon' }`, rather than by passing an imported kit module. The kits now live inside the parser.
27
+ - `@types/three` is pinned to `0.184.0` across the workspace. IWSDK 0.5.3 and xrblocks resolved different copies, and two copies of the three typings make structurally identical `Object3D` types mutually unassignable, which surfaces as spurious errors far from their cause.
16
28
 
17
29
  [0.1.0]: https://github.com/realitycollective/WebXR-UIExtensions/commits/main
@@ -1,10 +1,17 @@
1
1
  /**
2
2
  * Structural view of a uikit element as the controls layer needs it.
3
3
  *
4
- * UIKitML preserves `data-*` attributes into `element.userData` (camelCased,
5
- * `data-` stripped): `data-uix="stepper"` → `userData.uix`. Controls are
6
- * discovered by traversing a panel's object tree for those markers - no
7
- * custom uikit component classes, so any markup (or kit) works.
4
+ * Controls and their parts are declared as CUSTOM ELEMENTS - `<uix-stepper>`,
5
+ * `<uix-value>` - rather than as attributes on a `<div>`. That is what makes
6
+ * one markup file portable across every adapter: the IWSDK 0.5 parser rejects
7
+ * unknown attributes on built-in tags, but accepts a custom tag declared in a
8
+ * component set, and the three.js / XR Blocks parser accepts custom tags with
9
+ * no registration at all.
10
+ *
11
+ * Both paths expose the declared tag the same way, on
12
+ * `userData.customElement.componentName`. Parameters stay `data-uix-*`
13
+ * attributes, which land in `userData` camelCased with `data-` stripped:
14
+ * `data-uix-min="0"` → `userData.uixMin`.
8
15
  */
9
16
  export interface UixElement {
10
17
  userData: Record<string, unknown>;
@@ -14,7 +21,12 @@ export interface UixElement {
14
21
  }
15
22
  /** Depth-first traversal over uikit element children. */
16
23
  export declare function walk(element: UixElement, visit: (element: UixElement) => void): void;
17
- /** All descendants (including self) with `data-uix-role="<role>"`. */
24
+ /**
25
+ * The custom-element tag this element was declared with, lowercased, or
26
+ * undefined for a plain built-in element.
27
+ */
28
+ export declare function tagOf(element: UixElement): string | undefined;
29
+ /** All descendants (including self) declared as `<uix-{role}>`. */
18
30
  export declare function findRoles(root: UixElement, role: string): UixElement[];
19
31
  export declare function findRole(root: UixElement, role: string): UixElement | undefined;
20
32
  export declare function attrString(element: UixElement, key: string): string | undefined;
@@ -5,11 +5,21 @@ export function walk(element, visit) {
5
5
  walk(child, visit);
6
6
  }
7
7
  }
8
- /** All descendants (including self) with `data-uix-role="<role>"`. */
8
+ /**
9
+ * The custom-element tag this element was declared with, lowercased, or
10
+ * undefined for a plain built-in element.
11
+ */
12
+ export function tagOf(element) {
13
+ const custom = element.userData?.['customElement'];
14
+ const name = custom?.componentName;
15
+ return typeof name === 'string' && name.length > 0 ? name.toLowerCase() : undefined;
16
+ }
17
+ /** All descendants (including self) declared as `<uix-{role}>`. */
9
18
  export function findRoles(root, role) {
19
+ const tag = `uix-${role}`;
10
20
  const found = [];
11
21
  walk(root, (element) => {
12
- if (element.userData?.['uixRole'] === role) {
22
+ if (tagOf(element) === tag) {
13
23
  found.push(element);
14
24
  }
15
25
  });
@@ -1 +1 @@
1
- {"version":3,"file":"element.js","sourceRoot":"","sources":["../../src/controls/element.ts"],"names":[],"mappings":"AAeA,yDAAyD;AACzD,MAAM,UAAU,IAAI,CAAC,OAAmB,EAAE,KAAoC;IAC5E,KAAK,CAAC,OAAO,CAAC,CAAC;IACf,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;QAC3C,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACrB,CAAC;AACH,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,SAAS,CAAC,IAAgB,EAAE,IAAY;IACtD,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,IAAI,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE;QACrB,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE,CAAC;YAC3C,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAgB,EAAE,IAAY;IACrD,OAAO,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAmB,EAAE,GAAW;IACzD,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;IACtC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAmB,EAAE,GAAW;IACzD,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACrC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1B,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAmB,EAAE,GAAW;IAC1D,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACrC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,IAAI,CAAC;AACvD,CAAC","sourcesContent":["/**\n * Structural view of a uikit element as the controls layer needs it.\n *\n * UIKitML preserves `data-*` attributes into `element.userData` (camelCased,\n * `data-` stripped): `data-uix=\"stepper\"` → `userData.uix`. Controls are\n * discovered by traversing a panel's object tree for those markers - no\n * custom uikit component classes, so any markup (or kit) works.\n */\nexport interface UixElement {\n userData: Record<string, unknown>;\n children: UixElement[];\n addEventListener(type: string, listener: (event?: unknown) => void): void;\n setProperties(props: Record<string, unknown>): void;\n}\n\n/** Depth-first traversal over uikit element children. */\nexport function walk(element: UixElement, visit: (element: UixElement) => void): void {\n visit(element);\n for (const child of element.children ?? []) {\n walk(child, visit);\n }\n}\n\n/** All descendants (including self) with `data-uix-role=\"<role>\"`. */\nexport function findRoles(root: UixElement, role: string): UixElement[] {\n const found: UixElement[] = [];\n walk(root, (element) => {\n if (element.userData?.['uixRole'] === role) {\n found.push(element);\n }\n });\n return found;\n}\n\nexport function findRole(root: UixElement, role: string): UixElement | undefined {\n return findRoles(root, role)[0];\n}\n\nexport function attrString(element: UixElement, key: string): string | undefined {\n const value = element.userData?.[key];\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nexport function attrNumber(element: UixElement, key: string): number | undefined {\n const raw = attrString(element, key);\n if (raw === undefined) {\n return undefined;\n }\n const value = Number(raw);\n return Number.isFinite(value) ? value : undefined;\n}\n\nexport function attrBoolean(element: UixElement, key: string): boolean | undefined {\n const raw = attrString(element, key);\n if (raw === undefined) {\n return undefined;\n }\n return raw === 'true' || raw === '1' || raw === 'on';\n}\n"]}
1
+ {"version":3,"file":"element.js","sourceRoot":"","sources":["../../src/controls/element.ts"],"names":[],"mappings":"AAsBA,yDAAyD;AACzD,MAAM,UAAU,IAAI,CAAC,OAAmB,EAAE,KAAoC;IAC5E,KAAK,CAAC,OAAO,CAAC,CAAC;IACf,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;QAC3C,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACrB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,KAAK,CAAC,OAAmB;IACvC,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,eAAe,CAEpC,CAAC;IACd,MAAM,IAAI,GAAG,MAAM,EAAE,aAAa,CAAC;IACnC,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AACtF,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,SAAS,CAAC,IAAgB,EAAE,IAAY;IACtD,MAAM,GAAG,GAAG,OAAO,IAAI,EAAE,CAAC;IAC1B,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,IAAI,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE;QACrB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAgB,EAAE,IAAY;IACrD,OAAO,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAmB,EAAE,GAAW;IACzD,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;IACtC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAmB,EAAE,GAAW;IACzD,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACrC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1B,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAmB,EAAE,GAAW;IAC1D,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACrC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,IAAI,CAAC;AACvD,CAAC","sourcesContent":["/**\n * Structural view of a uikit element as the controls layer needs it.\n *\n * Controls and their parts are declared as CUSTOM ELEMENTS - `<uix-stepper>`,\n * `<uix-value>` - rather than as attributes on a `<div>`. That is what makes\n * one markup file portable across every adapter: the IWSDK 0.5 parser rejects\n * unknown attributes on built-in tags, but accepts a custom tag declared in a\n * component set, and the three.js / XR Blocks parser accepts custom tags with\n * no registration at all.\n *\n * Both paths expose the declared tag the same way, on\n * `userData.customElement.componentName`. Parameters stay `data-uix-*`\n * attributes, which land in `userData` camelCased with `data-` stripped:\n * `data-uix-min=\"0\"` → `userData.uixMin`.\n */\nexport interface UixElement {\n userData: Record<string, unknown>;\n children: UixElement[];\n addEventListener(type: string, listener: (event?: unknown) => void): void;\n setProperties(props: Record<string, unknown>): void;\n}\n\n/** Depth-first traversal over uikit element children. */\nexport function walk(element: UixElement, visit: (element: UixElement) => void): void {\n visit(element);\n for (const child of element.children ?? []) {\n walk(child, visit);\n }\n}\n\n/**\n * The custom-element tag this element was declared with, lowercased, or\n * undefined for a plain built-in element.\n */\nexport function tagOf(element: UixElement): string | undefined {\n const custom = element.userData?.['customElement'] as\n | { componentName?: unknown }\n | undefined;\n const name = custom?.componentName;\n return typeof name === 'string' && name.length > 0 ? name.toLowerCase() : undefined;\n}\n\n/** All descendants (including self) declared as `<uix-{role}>`. */\nexport function findRoles(root: UixElement, role: string): UixElement[] {\n const tag = `uix-${role}`;\n const found: UixElement[] = [];\n walk(root, (element) => {\n if (tagOf(element) === tag) {\n found.push(element);\n }\n });\n return found;\n}\n\nexport function findRole(root: UixElement, role: string): UixElement | undefined {\n return findRoles(root, role)[0];\n}\n\nexport function attrString(element: UixElement, key: string): string | undefined {\n const value = element.userData?.[key];\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nexport function attrNumber(element: UixElement, key: string): number | undefined {\n const raw = attrString(element, key);\n if (raw === undefined) {\n return undefined;\n }\n const value = Number(raw);\n return Number.isFinite(value) ? value : undefined;\n}\n\nexport function attrBoolean(element: UixElement, key: string): boolean | undefined {\n const raw = attrString(element, key);\n if (raw === undefined) {\n return undefined;\n }\n return raw === 'true' || raw === '1' || raw === 'on';\n}\n"]}
@@ -1,16 +1,16 @@
1
1
  /**
2
- * Expandable label - `data-uix="expandable-label"`.
2
+ * Expandable label - `<uix-expandable-label>`.
3
3
  *
4
4
  * A multi-line label that collapses to a character budget with an ellipsis
5
5
  * and an inline more/less affordance.
6
6
  *
7
7
  * ```html
8
- * <div data-uix="expandable-label" data-uix-id="lore"
9
- * data-uix-lines="2" data-uix-chars-per-line="40"
10
- * data-uix-text="Long text..." class="my-label">
11
- * <span data-uix-role="text">.</span>
12
- * <button data-uix-role="toggle">more</button>
13
- * </div>
8
+ * <uix-expandable-label data-uix-id="lore"
9
+ * data-uix-lines="2" data-uix-chars-per-line="40"
10
+ * data-uix-text="Long text..." class="my-label">
11
+ * <uix-text>.</uix-text>
12
+ * <uix-more>more</uix-more>
13
+ * </uix-expandable-label>
14
14
  * ```
15
15
  */
16
16
  import { Emitter } from '../core/events.js';
@@ -1,16 +1,16 @@
1
1
  /**
2
- * Expandable label - `data-uix="expandable-label"`.
2
+ * Expandable label - `<uix-expandable-label>`.
3
3
  *
4
4
  * A multi-line label that collapses to a character budget with an ellipsis
5
5
  * and an inline more/less affordance.
6
6
  *
7
7
  * ```html
8
- * <div data-uix="expandable-label" data-uix-id="lore"
9
- * data-uix-lines="2" data-uix-chars-per-line="40"
10
- * data-uix-text="Long text..." class="my-label">
11
- * <span data-uix-role="text">.</span>
12
- * <button data-uix-role="toggle">more</button>
13
- * </div>
8
+ * <uix-expandable-label data-uix-id="lore"
9
+ * data-uix-lines="2" data-uix-chars-per-line="40"
10
+ * data-uix-text="Long text..." class="my-label">
11
+ * <uix-text>.</uix-text>
12
+ * <uix-more>more</uix-more>
13
+ * </uix-expandable-label>
14
14
  * ```
15
15
  */
16
16
  import { Emitter } from '../core/events.js';
@@ -55,7 +55,7 @@ export function upgradeExpandableLabel(root) {
55
55
  ...(lines !== undefined && { collapsedLines: lines }),
56
56
  ...(charsPerLine !== undefined && { charsPerLine }),
57
57
  });
58
- const toggle = findRole(root, 'toggle');
58
+ const toggle = findRole(root, 'more');
59
59
  const handle = new ExpandableLabelHandle(model, findRole(root, 'text'), toggle);
60
60
  toggle?.addEventListener('click', () => handle.toggle());
61
61
  return handle;
@@ -1 +1 @@
1
- {"version":3,"file":"expandable-label.js","sourceRoot":"","sources":["../../src/controls/expandable-label.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAc,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE5E,MAAM,OAAO,qBAAqB;IAIrB;IACD;IACA;IALD,MAAM,GAAG,IAAI,OAAO,EAAuB,CAAC;IAErD,YACW,KAAsB,EACvB,WAAmC,EACnC,aAAqC;QAFpC,UAAK,GAAL,KAAK,CAAiB;QACvB,gBAAW,GAAX,WAAW,CAAwB;QACnC,kBAAa,GAAb,aAAa,CAAwB;QAE7C,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;IAC7B,CAAC;IAED,OAAO,CAAC,IAAY;QAClB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAED,MAAM;QACJ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QAChD,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAEO,MAAM;QACZ,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;QAClE,wEAAwE;QACxE,IAAI,CAAC,aAAa,EAAE,aAAa,CAAC;YAChC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW;YAC5B,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;SAChD,CAAC,CAAC;IACL,CAAC;CACF;AAED,MAAM,UAAU,sBAAsB,CAAC,IAAgB;IACrD,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,UAAU,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;IACzD,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC;QAChC,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE;QACvC,GAAG,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;QACrD,GAAG,CAAC,YAAY,KAAK,SAAS,IAAI,EAAE,YAAY,EAAE,CAAC;KACpD,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,IAAI,qBAAqB,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAChF,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACzD,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["/**\n * Expandable label - `data-uix=\"expandable-label\"`.\n *\n * A multi-line label that collapses to a character budget with an ellipsis\n * and an inline more/less affordance.\n *\n * ```html\n * <div data-uix=\"expandable-label\" data-uix-id=\"lore\"\n * data-uix-lines=\"2\" data-uix-chars-per-line=\"40\"\n * data-uix-text=\"Long text...\" class=\"my-label\">\n * <span data-uix-role=\"text\">.</span>\n * <button data-uix-role=\"toggle\">more</button>\n * </div>\n * ```\n */\nimport { Emitter } from '../core/events.js';\nimport { ExpandableModel } from '../core/expandable-model.js';\nimport { UixElement, attrNumber, attrString, findRole } from './element.js';\n\nexport class ExpandableLabelHandle {\n readonly events = new Emitter<{ change: boolean }>();\n\n constructor(\n readonly model: ExpandableModel,\n private textElement: UixElement | undefined,\n private toggleElement: UixElement | undefined,\n ) {\n this.render();\n }\n\n get expanded(): boolean {\n return this.model.expanded;\n }\n\n setText(text: string): void {\n this.model.setText(text);\n this.render();\n }\n\n toggle(): void {\n this.events.emit('change', this.model.toggle());\n this.render();\n }\n\n private render(): void {\n this.textElement?.setProperties({ text: this.model.displayText });\n // Hide the affordance entirely when the text fits the collapsed budget.\n this.toggleElement?.setProperties({\n text: this.model.toggleLabel,\n display: this.model.overflows ? 'flex' : 'none',\n });\n }\n}\n\nexport function upgradeExpandableLabel(root: UixElement): ExpandableLabelHandle {\n const lines = attrNumber(root, 'uixLines');\n const charsPerLine = attrNumber(root, 'uixCharsPerLine');\n const model = new ExpandableModel({\n text: attrString(root, 'uixText') ?? '',\n ...(lines !== undefined && { collapsedLines: lines }),\n ...(charsPerLine !== undefined && { charsPerLine }),\n });\n const toggle = findRole(root, 'toggle');\n const handle = new ExpandableLabelHandle(model, findRole(root, 'text'), toggle);\n toggle?.addEventListener('click', () => handle.toggle());\n return handle;\n}\n"]}
1
+ {"version":3,"file":"expandable-label.js","sourceRoot":"","sources":["../../src/controls/expandable-label.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAc,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE5E,MAAM,OAAO,qBAAqB;IAIrB;IACD;IACA;IALD,MAAM,GAAG,IAAI,OAAO,EAAuB,CAAC;IAErD,YACW,KAAsB,EACvB,WAAmC,EACnC,aAAqC;QAFpC,UAAK,GAAL,KAAK,CAAiB;QACvB,gBAAW,GAAX,WAAW,CAAwB;QACnC,kBAAa,GAAb,aAAa,CAAwB;QAE7C,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;IAC7B,CAAC;IAED,OAAO,CAAC,IAAY;QAClB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAED,MAAM;QACJ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QAChD,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAEO,MAAM;QACZ,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;QAClE,wEAAwE;QACxE,IAAI,CAAC,aAAa,EAAE,aAAa,CAAC;YAChC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW;YAC5B,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;SAChD,CAAC,CAAC;IACL,CAAC;CACF;AAED,MAAM,UAAU,sBAAsB,CAAC,IAAgB;IACrD,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,UAAU,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;IACzD,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC;QAChC,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE;QACvC,GAAG,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;QACrD,GAAG,CAAC,YAAY,KAAK,SAAS,IAAI,EAAE,YAAY,EAAE,CAAC;KACpD,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,IAAI,qBAAqB,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAChF,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACzD,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["/**\n * Expandable label - `<uix-expandable-label>`.\n *\n * A multi-line label that collapses to a character budget with an ellipsis\n * and an inline more/less affordance.\n *\n * ```html\n * <uix-expandable-label data-uix-id=\"lore\"\n * data-uix-lines=\"2\" data-uix-chars-per-line=\"40\"\n * data-uix-text=\"Long text...\" class=\"my-label\">\n * <uix-text>.</uix-text>\n * <uix-more>more</uix-more>\n * </uix-expandable-label>\n * ```\n */\nimport { Emitter } from '../core/events.js';\nimport { ExpandableModel } from '../core/expandable-model.js';\nimport { UixElement, attrNumber, attrString, findRole } from './element.js';\n\nexport class ExpandableLabelHandle {\n readonly events = new Emitter<{ change: boolean }>();\n\n constructor(\n readonly model: ExpandableModel,\n private textElement: UixElement | undefined,\n private toggleElement: UixElement | undefined,\n ) {\n this.render();\n }\n\n get expanded(): boolean {\n return this.model.expanded;\n }\n\n setText(text: string): void {\n this.model.setText(text);\n this.render();\n }\n\n toggle(): void {\n this.events.emit('change', this.model.toggle());\n this.render();\n }\n\n private render(): void {\n this.textElement?.setProperties({ text: this.model.displayText });\n // Hide the affordance entirely when the text fits the collapsed budget.\n this.toggleElement?.setProperties({\n text: this.model.toggleLabel,\n display: this.model.overflows ? 'flex' : 'none',\n });\n }\n}\n\nexport function upgradeExpandableLabel(root: UixElement): ExpandableLabelHandle {\n const lines = attrNumber(root, 'uixLines');\n const charsPerLine = attrNumber(root, 'uixCharsPerLine');\n const model = new ExpandableModel({\n text: attrString(root, 'uixText') ?? '',\n ...(lines !== undefined && { collapsedLines: lines }),\n ...(charsPerLine !== undefined && { charsPerLine }),\n });\n const toggle = findRole(root, 'more');\n const handle = new ExpandableLabelHandle(model, findRole(root, 'text'), toggle);\n toggle?.addEventListener('click', () => handle.toggle());\n return handle;\n}\n"]}
@@ -1,25 +1,25 @@
1
1
  /**
2
- * Log view - `data-uix="log-view"`.
2
+ * Log view - `<uix-log-view>`.
3
3
  *
4
4
  * A console-style scrolling list rendered into a fixed pool of row elements
5
5
  * (UIKitML's text rule: each row needs a literal placeholder child). Rows
6
- * are the descendants with role `line`, in document order; their count is
7
- * the viewport height.
6
+ * are the `<uix-line>` descendants, in document order; their count is the
7
+ * viewport height.
8
8
  *
9
9
  * ```html
10
- * <div data-uix="log-view" data-uix-id="combat" data-uix-capacity="200">
10
+ * <uix-log-view data-uix-id="combat" data-uix-capacity="200">
11
11
  * <div class="rows">
12
- * <span data-uix-role="line">.</span>
13
- * <span data-uix-role="line">.</span>
14
- * <span data-uix-role="line">.</span>
12
+ * <uix-line>.</uix-line>
13
+ * <uix-line>.</uix-line>
14
+ * <uix-line>.</uix-line>
15
15
  * </div>
16
16
  * <div class="log-buttons">
17
- * <button data-uix-role="up">▲</button>
18
- * <button data-uix-role="down">▼</button>
19
- * <button data-uix-role="clear">clear</button>
17
+ * <uix-up>▲</uix-up>
18
+ * <uix-down>▼</uix-down>
19
+ * <uix-clear>clear</uix-clear>
20
20
  * </div>
21
- * <span data-uix-role="status">.</span>
22
- * </div>
21
+ * <uix-status>.</uix-status>
22
+ * </uix-log-view>
23
23
  * ```
24
24
  */
25
25
  import { LogModel } from '../core/log-model.js';
@@ -1,25 +1,25 @@
1
1
  /**
2
- * Log view - `data-uix="log-view"`.
2
+ * Log view - `<uix-log-view>`.
3
3
  *
4
4
  * A console-style scrolling list rendered into a fixed pool of row elements
5
5
  * (UIKitML's text rule: each row needs a literal placeholder child). Rows
6
- * are the descendants with role `line`, in document order; their count is
7
- * the viewport height.
6
+ * are the `<uix-line>` descendants, in document order; their count is the
7
+ * viewport height.
8
8
  *
9
9
  * ```html
10
- * <div data-uix="log-view" data-uix-id="combat" data-uix-capacity="200">
10
+ * <uix-log-view data-uix-id="combat" data-uix-capacity="200">
11
11
  * <div class="rows">
12
- * <span data-uix-role="line">.</span>
13
- * <span data-uix-role="line">.</span>
14
- * <span data-uix-role="line">.</span>
12
+ * <uix-line>.</uix-line>
13
+ * <uix-line>.</uix-line>
14
+ * <uix-line>.</uix-line>
15
15
  * </div>
16
16
  * <div class="log-buttons">
17
- * <button data-uix-role="up">▲</button>
18
- * <button data-uix-role="down">▼</button>
19
- * <button data-uix-role="clear">clear</button>
17
+ * <uix-up>▲</uix-up>
18
+ * <uix-down>▼</uix-down>
19
+ * <uix-clear>clear</uix-clear>
20
20
  * </div>
21
- * <span data-uix-role="status">.</span>
22
- * </div>
21
+ * <uix-status>.</uix-status>
22
+ * </uix-log-view>
23
23
  * ```
24
24
  */
25
25
  import { LogModel } from '../core/log-model.js';
@@ -1 +1 @@
1
- {"version":3,"file":"log-view.js","sourceRoot":"","sources":["../../src/controls/log-view.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Log view - `data-uix=\"log-view\"`.\n *\n * A console-style scrolling list rendered into a fixed pool of row elements\n * (UIKitML's text rule: each row needs a literal placeholder child). Rows\n * are the descendants with role `line`, in document order; their count is\n * the viewport height.\n *\n * ```html\n * <div data-uix=\"log-view\" data-uix-id=\"combat\" data-uix-capacity=\"200\">\n * <div class=\"rows\">\n * <span data-uix-role=\"line\">.</span>\n * <span data-uix-role=\"line\">.</span>\n * <span data-uix-role=\"line\">.</span>\n * </div>\n * <div class=\"log-buttons\">\n * <button data-uix-role=\"up\">▲</button>\n * <button data-uix-role=\"down\">▼</button>\n * <button data-uix-role=\"clear\">clear</button>\n * </div>\n * <span data-uix-role=\"status\">.</span>\n * </div>\n * ```\n */\nimport { LogModel } from '../core/log-model.js';\nimport { UixElement, attrNumber, findRole, findRoles } from './element.js';\n\nexport class LogViewHandle {\n constructor(\n readonly model: LogModel,\n private rows: UixElement[],\n private statusElement: UixElement | undefined,\n ) {\n this.render();\n }\n\n push(message: string, level = 'info'): void {\n this.model.push(message, level);\n this.render();\n }\n\n clear(): void {\n this.model.clear();\n this.render();\n }\n\n scrollUp(rows = 1): void {\n this.model.scrollUp(rows);\n this.render();\n }\n\n scrollDown(rows = 1): void {\n this.model.scrollDown(rows);\n this.render();\n }\n\n private render(): void {\n const visible = this.model.visible();\n this.rows.forEach((row, index) => {\n const entry = visible[index];\n row.setProperties({\n // UIKitML renders \"\" as empty text - exactly what vacant rows need.\n text: entry ? entry.message : '',\n });\n });\n if (this.statusElement) {\n const suffix = this.model.isFollowing ? '' : ' (scrolled)';\n this.statusElement.setProperties({\n text: `${this.model.count} entries${suffix}`,\n });\n }\n }\n}\n\nexport function upgradeLogView(root: UixElement): LogViewHandle {\n const rows = findRoles(root, 'line');\n const capacity = attrNumber(root, 'uixCapacity');\n const model = new LogModel({\n visibleRows: Math.max(1, rows.length),\n ...(capacity !== undefined && { capacity }),\n });\n const handle = new LogViewHandle(model, rows, findRole(root, 'status'));\n findRole(root, 'up')?.addEventListener('click', () => handle.scrollUp());\n findRole(root, 'down')?.addEventListener('click', () => handle.scrollDown());\n findRole(root, 'clear')?.addEventListener('click', () => handle.clear());\n return handle;\n}\n"]}
1
+ {"version":3,"file":"log-view.js","sourceRoot":"","sources":["../../src/controls/log-view.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Log view - `<uix-log-view>`.\n *\n * A console-style scrolling list rendered into a fixed pool of row elements\n * (UIKitML's text rule: each row needs a literal placeholder child). Rows\n * are the `<uix-line>` descendants, in document order; their count is the\n * viewport height.\n *\n * ```html\n * <uix-log-view data-uix-id=\"combat\" data-uix-capacity=\"200\">\n * <div class=\"rows\">\n * <uix-line>.</uix-line>\n * <uix-line>.</uix-line>\n * <uix-line>.</uix-line>\n * </div>\n * <div class=\"log-buttons\">\n * <uix-up>▲</uix-up>\n * <uix-down>▼</uix-down>\n * <uix-clear>clear</uix-clear>\n * </div>\n * <uix-status>.</uix-status>\n * </uix-log-view>\n * ```\n */\nimport { LogModel } from '../core/log-model.js';\nimport { UixElement, attrNumber, findRole, findRoles } from './element.js';\n\nexport class LogViewHandle {\n constructor(\n readonly model: LogModel,\n private rows: UixElement[],\n private statusElement: UixElement | undefined,\n ) {\n this.render();\n }\n\n push(message: string, level = 'info'): void {\n this.model.push(message, level);\n this.render();\n }\n\n clear(): void {\n this.model.clear();\n this.render();\n }\n\n scrollUp(rows = 1): void {\n this.model.scrollUp(rows);\n this.render();\n }\n\n scrollDown(rows = 1): void {\n this.model.scrollDown(rows);\n this.render();\n }\n\n private render(): void {\n const visible = this.model.visible();\n this.rows.forEach((row, index) => {\n const entry = visible[index];\n row.setProperties({\n // UIKitML renders \"\" as empty text - exactly what vacant rows need.\n text: entry ? entry.message : '',\n });\n });\n if (this.statusElement) {\n const suffix = this.model.isFollowing ? '' : ' (scrolled)';\n this.statusElement.setProperties({\n text: `${this.model.count} entries${suffix}`,\n });\n }\n }\n}\n\nexport function upgradeLogView(root: UixElement): LogViewHandle {\n const rows = findRoles(root, 'line');\n const capacity = attrNumber(root, 'uixCapacity');\n const model = new LogModel({\n visibleRows: Math.max(1, rows.length),\n ...(capacity !== undefined && { capacity }),\n });\n const handle = new LogViewHandle(model, rows, findRole(root, 'status'));\n findRole(root, 'up')?.addEventListener('click', () => handle.scrollUp());\n findRole(root, 'down')?.addEventListener('click', () => handle.scrollDown());\n findRole(root, 'clear')?.addEventListener('click', () => handle.clear());\n return handle;\n}\n"]}
@@ -1,14 +1,14 @@
1
1
  /**
2
- * Stepper control - `data-uix="stepper"`.
2
+ * Stepper control - `<uix-stepper>`.
3
3
  *
4
- * Markup contract (roles are `data-uix-role` attributes on descendants):
4
+ * Markup contract (parts are `<uix-*>` elements; params are `data-uix-*`):
5
5
  * ```html
6
- * <div data-uix="stepper" data-uix-id="count" data-uix-min="0" data-uix-max="10"
7
- * data-uix-step="1" data-uix-value="5" class="my-stepper">
8
- * <button data-uix-role="decrement">-</button>
9
- * <span data-uix-role="value">.</span>
10
- * <button data-uix-role="increment">+</button>
11
- * </div>
6
+ * <uix-stepper data-uix-id="count" data-uix-min="0" data-uix-max="10"
7
+ * data-uix-step="1" data-uix-value="5" class="my-stepper">
8
+ * <uix-decrement>-</uix-decrement>
9
+ * <uix-value>.</uix-value>
10
+ * <uix-increment>+</uix-increment>
11
+ * </uix-stepper>
12
12
  * ```
13
13
  */
14
14
  import { Emitter } from '../core/events.js';
@@ -1,14 +1,14 @@
1
1
  /**
2
- * Stepper control - `data-uix="stepper"`.
2
+ * Stepper control - `<uix-stepper>`.
3
3
  *
4
- * Markup contract (roles are `data-uix-role` attributes on descendants):
4
+ * Markup contract (parts are `<uix-*>` elements; params are `data-uix-*`):
5
5
  * ```html
6
- * <div data-uix="stepper" data-uix-id="count" data-uix-min="0" data-uix-max="10"
7
- * data-uix-step="1" data-uix-value="5" class="my-stepper">
8
- * <button data-uix-role="decrement">-</button>
9
- * <span data-uix-role="value">.</span>
10
- * <button data-uix-role="increment">+</button>
11
- * </div>
6
+ * <uix-stepper data-uix-id="count" data-uix-min="0" data-uix-max="10"
7
+ * data-uix-step="1" data-uix-value="5" class="my-stepper">
8
+ * <uix-decrement>-</uix-decrement>
9
+ * <uix-value>.</uix-value>
10
+ * <uix-increment>+</uix-increment>
11
+ * </uix-stepper>
12
12
  * ```
13
13
  */
14
14
  import { Emitter } from '../core/events.js';
@@ -1 +1 @@
1
- {"version":3,"file":"stepper.js","sourceRoot":"","sources":["../../src/controls/stepper.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Stepper control - `data-uix=\"stepper\"`.\n *\n * Markup contract (roles are `data-uix-role` attributes on descendants):\n * ```html\n * <div data-uix=\"stepper\" data-uix-id=\"count\" data-uix-min=\"0\" data-uix-max=\"10\"\n * data-uix-step=\"1\" data-uix-value=\"5\" class=\"my-stepper\">\n * <button data-uix-role=\"decrement\">-</button>\n * <span data-uix-role=\"value\">.</span>\n * <button data-uix-role=\"increment\">+</button>\n * </div>\n * ```\n */\nimport { Emitter } from '../core/events.js';\nimport { StepperModel, type StepperOptions } from '../core/stepper-model.js';\nimport { UixElement, attrNumber, findRole } from './element.js';\n\nconst DISABLED_OPACITY = 0.35;\n\nexport class StepperHandle {\n readonly events = new Emitter<{ change: number }>();\n\n constructor(\n readonly model: StepperModel,\n private valueElement: UixElement | undefined,\n private decrementElement: UixElement | undefined,\n private incrementElement: UixElement | undefined,\n ) {\n this.render();\n }\n\n get value(): number {\n return this.model.value;\n }\n\n set(value: number): void {\n if (this.model.set(value)) {\n this.render();\n this.events.emit('change', this.model.value);\n }\n }\n\n increment(): void {\n this.set(this.model.value + this.model.step);\n }\n\n decrement(): void {\n this.set(this.model.value - this.model.step);\n }\n\n private render(): void {\n this.valueElement?.setProperties({ text: String(this.model.value) });\n this.decrementElement?.setProperties({\n opacity: this.model.canDecrement ? 1 : DISABLED_OPACITY,\n });\n this.incrementElement?.setProperties({\n opacity: this.model.canIncrement ? 1 : DISABLED_OPACITY,\n });\n }\n}\n\nexport function upgradeStepper(root: UixElement): StepperHandle {\n const options: StepperOptions = {};\n const min = attrNumber(root, 'uixMin');\n const max = attrNumber(root, 'uixMax');\n const step = attrNumber(root, 'uixStep');\n const value = attrNumber(root, 'uixValue');\n if (min !== undefined) options.min = min;\n if (max !== undefined) options.max = max;\n if (step !== undefined) options.step = step;\n if (value !== undefined) options.value = value;\n const model = new StepperModel(options);\n const decrement = findRole(root, 'decrement');\n const increment = findRole(root, 'increment');\n const handle = new StepperHandle(model, findRole(root, 'value'), decrement, increment);\n decrement?.addEventListener('click', () => handle.decrement());\n increment?.addEventListener('click', () => handle.increment());\n return handle;\n}\n"]}
1
+ {"version":3,"file":"stepper.js","sourceRoot":"","sources":["../../src/controls/stepper.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Stepper control - `<uix-stepper>`.\n *\n * Markup contract (parts are `<uix-*>` elements; params are `data-uix-*`):\n * ```html\n * <uix-stepper data-uix-id=\"count\" data-uix-min=\"0\" data-uix-max=\"10\"\n * data-uix-step=\"1\" data-uix-value=\"5\" class=\"my-stepper\">\n * <uix-decrement>-</uix-decrement>\n * <uix-value>.</uix-value>\n * <uix-increment>+</uix-increment>\n * </uix-stepper>\n * ```\n */\nimport { Emitter } from '../core/events.js';\nimport { StepperModel, type StepperOptions } from '../core/stepper-model.js';\nimport { UixElement, attrNumber, findRole } from './element.js';\n\nconst DISABLED_OPACITY = 0.35;\n\nexport class StepperHandle {\n readonly events = new Emitter<{ change: number }>();\n\n constructor(\n readonly model: StepperModel,\n private valueElement: UixElement | undefined,\n private decrementElement: UixElement | undefined,\n private incrementElement: UixElement | undefined,\n ) {\n this.render();\n }\n\n get value(): number {\n return this.model.value;\n }\n\n set(value: number): void {\n if (this.model.set(value)) {\n this.render();\n this.events.emit('change', this.model.value);\n }\n }\n\n increment(): void {\n this.set(this.model.value + this.model.step);\n }\n\n decrement(): void {\n this.set(this.model.value - this.model.step);\n }\n\n private render(): void {\n this.valueElement?.setProperties({ text: String(this.model.value) });\n this.decrementElement?.setProperties({\n opacity: this.model.canDecrement ? 1 : DISABLED_OPACITY,\n });\n this.incrementElement?.setProperties({\n opacity: this.model.canIncrement ? 1 : DISABLED_OPACITY,\n });\n }\n}\n\nexport function upgradeStepper(root: UixElement): StepperHandle {\n const options: StepperOptions = {};\n const min = attrNumber(root, 'uixMin');\n const max = attrNumber(root, 'uixMax');\n const step = attrNumber(root, 'uixStep');\n const value = attrNumber(root, 'uixValue');\n if (min !== undefined) options.min = min;\n if (max !== undefined) options.max = max;\n if (step !== undefined) options.step = step;\n if (value !== undefined) options.value = value;\n const model = new StepperModel(options);\n const decrement = findRole(root, 'decrement');\n const increment = findRole(root, 'increment');\n const handle = new StepperHandle(model, findRole(root, 'value'), decrement, increment);\n decrement?.addEventListener('click', () => handle.decrement());\n increment?.addEventListener('click', () => handle.increment());\n return handle;\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Toggle control - `data-uix="toggle"`.
2
+ * Toggle control - `<uix-toggle>`.
3
3
  *
4
4
  * The whole control is the click target. Optional attributes:
5
5
  * `data-uix-on` (initial state), `data-uix-on-text` / `data-uix-off-text`
@@ -7,11 +7,11 @@
7
7
  * `data-uix-off-color` (background color applied to the control root).
8
8
  *
9
9
  * ```html
10
- * <div data-uix="toggle" data-uix-id="shield" data-uix-on="true"
11
- * data-uix-on-text="SHIELD ON" data-uix-off-text="SHIELD OFF"
12
- * data-uix-on-color="#2e7d32" data-uix-off-color="#5d4037" class="my-toggle">
13
- * <span data-uix-role="label">.</span>
14
- * </div>
10
+ * <uix-toggle data-uix-id="shield" data-uix-on="true"
11
+ * data-uix-on-text="SHIELD ON" data-uix-off-text="SHIELD OFF"
12
+ * data-uix-on-color="#2e7d32" data-uix-off-color="#5d4037" class="my-toggle">
13
+ * <uix-label>.</uix-label>
14
+ * </uix-toggle>
15
15
  * ```
16
16
  */
17
17
  import { Emitter } from '../core/events.js';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Toggle control - `data-uix="toggle"`.
2
+ * Toggle control - `<uix-toggle>`.
3
3
  *
4
4
  * The whole control is the click target. Optional attributes:
5
5
  * `data-uix-on` (initial state), `data-uix-on-text` / `data-uix-off-text`
@@ -7,11 +7,11 @@
7
7
  * `data-uix-off-color` (background color applied to the control root).
8
8
  *
9
9
  * ```html
10
- * <div data-uix="toggle" data-uix-id="shield" data-uix-on="true"
11
- * data-uix-on-text="SHIELD ON" data-uix-off-text="SHIELD OFF"
12
- * data-uix-on-color="#2e7d32" data-uix-off-color="#5d4037" class="my-toggle">
13
- * <span data-uix-role="label">.</span>
14
- * </div>
10
+ * <uix-toggle data-uix-id="shield" data-uix-on="true"
11
+ * data-uix-on-text="SHIELD ON" data-uix-off-text="SHIELD OFF"
12
+ * data-uix-on-color="#2e7d32" data-uix-off-color="#5d4037" class="my-toggle">
13
+ * <uix-label>.</uix-label>
14
+ * </uix-toggle>
15
15
  * ```
16
16
  */
17
17
  import { Emitter } from '../core/events.js';
@@ -1 +1 @@
1
- {"version":3,"file":"toggle.js","sourceRoot":"","sources":["../../src/controls/toggle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAc,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7E,MAAM,OAAO,YAAY;IAIZ;IACD;IACA;IALD,MAAM,GAAG,IAAI,OAAO,EAAuB,CAAC;IAErD,YACW,KAAkB,EACnB,IAAgB,EAChB,YAAoC;QAFnC,UAAK,GAAL,KAAK,CAAa;QACnB,SAAI,GAAJ,IAAI,CAAY;QAChB,iBAAY,GAAZ,YAAY,CAAwB;QAE5C,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAED,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;IAC1B,CAAC;IAED,GAAG,CAAC,KAAc;QAChB,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,MAAM;QACJ,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAEO,MAAM;QACZ,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;QAC5B,MAAM,IAAI,GAAG,EAAE;YACb,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,IAAI,CAAC;YAC9C,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,IAAI,KAAK,CAAC,CAAC;QACnD,IAAI,CAAC,YAAY,EAAE,aAAa,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,EAAE;YACd,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC;YACrC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;QACzC,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;CACF;AAED,MAAM,UAAU,aAAa,CAAC,IAAgB;IAC5C,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC;IACnE,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACtE,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACtD,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["/**\n * Toggle control - `data-uix=\"toggle\"`.\n *\n * The whole control is the click target. Optional attributes:\n * `data-uix-on` (initial state), `data-uix-on-text` / `data-uix-off-text`\n * (label written into the `label` role), `data-uix-on-color` /\n * `data-uix-off-color` (background color applied to the control root).\n *\n * ```html\n * <div data-uix=\"toggle\" data-uix-id=\"shield\" data-uix-on=\"true\"\n * data-uix-on-text=\"SHIELD ON\" data-uix-off-text=\"SHIELD OFF\"\n * data-uix-on-color=\"#2e7d32\" data-uix-off-color=\"#5d4037\" class=\"my-toggle\">\n * <span data-uix-role=\"label\">.</span>\n * </div>\n * ```\n */\nimport { Emitter } from '../core/events.js';\nimport { ToggleModel } from '../core/toggle-model.js';\nimport { UixElement, attrBoolean, attrString, findRole } from './element.js';\n\nexport class ToggleHandle {\n readonly events = new Emitter<{ change: boolean }>();\n\n constructor(\n readonly model: ToggleModel,\n private root: UixElement,\n private labelElement: UixElement | undefined,\n ) {\n this.render();\n }\n\n get value(): boolean {\n return this.model.value;\n }\n\n set(value: boolean): void {\n if (this.model.set(value)) {\n this.render();\n this.events.emit('change', this.model.value);\n }\n }\n\n toggle(): void {\n this.set(!this.model.value);\n }\n\n private render(): void {\n const on = this.model.value;\n const text = on\n ? (attrString(this.root, 'uixOnText') ?? 'ON')\n : (attrString(this.root, 'uixOffText') ?? 'OFF');\n this.labelElement?.setProperties({ text });\n const color = on\n ? attrString(this.root, 'uixOnColor')\n : attrString(this.root, 'uixOffColor');\n if (color) {\n this.root.setProperties({ backgroundColor: color });\n }\n }\n}\n\nexport function upgradeToggle(root: UixElement): ToggleHandle {\n const model = new ToggleModel(attrBoolean(root, 'uixOn') ?? false);\n const handle = new ToggleHandle(model, root, findRole(root, 'label'));\n root.addEventListener('click', () => handle.toggle());\n return handle;\n}\n"]}
1
+ {"version":3,"file":"toggle.js","sourceRoot":"","sources":["../../src/controls/toggle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAc,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7E,MAAM,OAAO,YAAY;IAIZ;IACD;IACA;IALD,MAAM,GAAG,IAAI,OAAO,EAAuB,CAAC;IAErD,YACW,KAAkB,EACnB,IAAgB,EAChB,YAAoC;QAFnC,UAAK,GAAL,KAAK,CAAa;QACnB,SAAI,GAAJ,IAAI,CAAY;QAChB,iBAAY,GAAZ,YAAY,CAAwB;QAE5C,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAED,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;IAC1B,CAAC;IAED,GAAG,CAAC,KAAc;QAChB,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,MAAM;QACJ,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAEO,MAAM;QACZ,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;QAC5B,MAAM,IAAI,GAAG,EAAE;YACb,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,IAAI,CAAC;YAC9C,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,IAAI,KAAK,CAAC,CAAC;QACnD,IAAI,CAAC,YAAY,EAAE,aAAa,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,EAAE;YACd,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC;YACrC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;QACzC,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;CACF;AAED,MAAM,UAAU,aAAa,CAAC,IAAgB;IAC5C,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC;IACnE,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACtE,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACtD,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["/**\n * Toggle control - `<uix-toggle>`.\n *\n * The whole control is the click target. Optional attributes:\n * `data-uix-on` (initial state), `data-uix-on-text` / `data-uix-off-text`\n * (label written into the `label` role), `data-uix-on-color` /\n * `data-uix-off-color` (background color applied to the control root).\n *\n * ```html\n * <uix-toggle data-uix-id=\"shield\" data-uix-on=\"true\"\n * data-uix-on-text=\"SHIELD ON\" data-uix-off-text=\"SHIELD OFF\"\n * data-uix-on-color=\"#2e7d32\" data-uix-off-color=\"#5d4037\" class=\"my-toggle\">\n * <uix-label>.</uix-label>\n * </uix-toggle>\n * ```\n */\nimport { Emitter } from '../core/events.js';\nimport { ToggleModel } from '../core/toggle-model.js';\nimport { UixElement, attrBoolean, attrString, findRole } from './element.js';\n\nexport class ToggleHandle {\n readonly events = new Emitter<{ change: boolean }>();\n\n constructor(\n readonly model: ToggleModel,\n private root: UixElement,\n private labelElement: UixElement | undefined,\n ) {\n this.render();\n }\n\n get value(): boolean {\n return this.model.value;\n }\n\n set(value: boolean): void {\n if (this.model.set(value)) {\n this.render();\n this.events.emit('change', this.model.value);\n }\n }\n\n toggle(): void {\n this.set(!this.model.value);\n }\n\n private render(): void {\n const on = this.model.value;\n const text = on\n ? (attrString(this.root, 'uixOnText') ?? 'ON')\n : (attrString(this.root, 'uixOffText') ?? 'OFF');\n this.labelElement?.setProperties({ text });\n const color = on\n ? attrString(this.root, 'uixOnColor')\n : attrString(this.root, 'uixOffColor');\n if (color) {\n this.root.setProperties({ backgroundColor: color });\n }\n }\n}\n\nexport function upgradeToggle(root: UixElement): ToggleHandle {\n const model = new ToggleModel(attrBoolean(root, 'uixOn') ?? false);\n const handle = new ToggleHandle(model, root, findRole(root, 'label'));\n root.addEventListener('click', () => handle.toggle());\n return handle;\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Panel upgrade - scans a loaded panel for `data-uix` markers and wires the
3
- * matching control behaviour. App code retrieves the resulting handles via
2
+ * Panel upgrade - scans a loaded panel for `<uix-*>` control elements and
3
+ * wires the matching behaviour. App code retrieves the resulting handles via
4
4
  * `panelControlsFor(document)` and the control's `data-uix-id`.
5
5
  */
6
6
  import { UixElement } from './element.js';
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Panel upgrade - scans a loaded panel for `data-uix` markers and wires the
3
- * matching control behaviour. App code retrieves the resulting handles via
2
+ * Panel upgrade - scans a loaded panel for `<uix-*>` control elements and
3
+ * wires the matching behaviour. App code retrieves the resulting handles via
4
4
  * `panelControlsFor(document)` and the control's `data-uix-id`.
5
5
  */
6
- import { attrString, walk } from './element.js';
6
+ import { attrString, tagOf, walk } from './element.js';
7
7
  import { StepperHandle, upgradeStepper } from './stepper.js';
8
8
  import { ToggleHandle, upgradeToggle } from './toggle.js';
9
9
  import { ExpandableLabelHandle, upgradeExpandableLabel } from './expandable-label.js';
@@ -48,12 +48,35 @@ export class PanelControls {
48
48
  return handle;
49
49
  }
50
50
  }
51
+ /**
52
+ * Control tags. These are the custom elements an author writes; every part
53
+ * inside them (`<uix-value>`, `<uix-line>`, …) is a role, resolved by the
54
+ * individual upgraders through `findRole`.
55
+ */
51
56
  const UPGRADERS = {
52
- stepper: upgradeStepper,
53
- toggle: upgradeToggle,
54
- 'expandable-label': upgradeExpandableLabel,
55
- 'log-view': upgradeLogView,
57
+ 'uix-stepper': upgradeStepper,
58
+ 'uix-toggle': upgradeToggle,
59
+ 'uix-expandable-label': upgradeExpandableLabel,
60
+ 'uix-log-view': upgradeLogView,
56
61
  };
62
+ /**
63
+ * Parts claimed by the control that owns them, resolved through `findRole`.
64
+ * Listed here only so a genuine typo in a control tag still gets a warning
65
+ * instead of being silently mistaken for a role.
66
+ */
67
+ const ROLE_TAGS = new Set([
68
+ 'uix-decrement',
69
+ 'uix-value',
70
+ 'uix-increment',
71
+ 'uix-label',
72
+ 'uix-line',
73
+ 'uix-up',
74
+ 'uix-down',
75
+ 'uix-clear',
76
+ 'uix-status',
77
+ 'uix-text',
78
+ 'uix-more',
79
+ ]);
57
80
  const registry = new WeakMap();
58
81
  /**
59
82
  * Upgrade every `data-uix` control under `root` and remember the result
@@ -66,13 +89,15 @@ export function upgradePanel(documentKey, root) {
66
89
  }
67
90
  const controls = new PanelControls();
68
91
  walk(root, (element) => {
69
- const marker = attrString(element, 'uix');
70
- if (marker === undefined) {
92
+ const tag = tagOf(element);
93
+ if (tag === undefined || !tag.startsWith('uix-')) {
71
94
  return;
72
95
  }
73
- const upgrader = UPGRADERS[marker];
96
+ const upgrader = UPGRADERS[tag];
74
97
  if (!upgrader) {
75
- console.warn(`[uix] unknown control type data-uix="${marker}" - skipped`);
98
+ if (!ROLE_TAGS.has(tag)) {
99
+ console.warn(`[uix] unknown control element <${tag}> - skipped`);
100
+ }
76
101
  return;
77
102
  }
78
103
  controls.add(attrString(element, 'uixId'), upgrader(element));
@@ -1 +1 @@
1
- {"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../src/controls/upgrade.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Panel upgrade - scans a loaded panel for `data-uix` markers and wires the\n * matching control behaviour. App code retrieves the resulting handles via\n * `panelControlsFor(document)` and the control's `data-uix-id`.\n */\nimport { UixElement, attrString, walk } from './element.js';\nimport { StepperHandle, upgradeStepper } from './stepper.js';\nimport { ToggleHandle, upgradeToggle } from './toggle.js';\nimport { ExpandableLabelHandle, upgradeExpandableLabel } from './expandable-label.js';\nimport { LogViewHandle, upgradeLogView } from './log-view.js';\n\nexport type ControlHandle =\n | StepperHandle\n | ToggleHandle\n | ExpandableLabelHandle\n | LogViewHandle;\n\nexport class PanelControls {\n private handles = new Map<string, ControlHandle>();\n private anonymous = 0;\n\n add(id: string | undefined, handle: ControlHandle): void {\n const key = id ?? `uix-control-${this.anonymous++}`;\n if (this.handles.has(key)) {\n throw new Error(`[uix] duplicate control id \"${key}\" in panel`);\n }\n this.handles.set(key, handle);\n }\n\n /** Typed accessors - throw on wrong id so mistakes surface immediately. */\n stepper(id: string): StepperHandle {\n return this.expect(id, StepperHandle);\n }\n\n toggle(id: string): ToggleHandle {\n return this.expect(id, ToggleHandle);\n }\n\n expandableLabel(id: string): ExpandableLabelHandle {\n return this.expect(id, ExpandableLabelHandle);\n }\n\n logView(id: string): LogViewHandle {\n return this.expect(id, LogViewHandle);\n }\n\n get(id: string): ControlHandle | undefined {\n return this.handles.get(id);\n }\n\n ids(): string[] {\n return [...this.handles.keys()];\n }\n\n private expect<T extends ControlHandle>(\n id: string,\n kind: abstract new (...args: never[]) => T,\n ): T {\n const handle = this.handles.get(id);\n if (!handle) {\n throw new Error(`[uix] no control \"${id}\" in panel (have: ${this.ids().join(', ') || 'none'})`);\n }\n if (!(handle instanceof kind)) {\n throw new Error(`[uix] control \"${id}\" is not a ${kind.name}`);\n }\n return handle;\n }\n}\n\nconst UPGRADERS: Record<string, (root: UixElement) => ControlHandle> = {\n stepper: upgradeStepper,\n toggle: upgradeToggle,\n 'expandable-label': upgradeExpandableLabel,\n 'log-view': upgradeLogView,\n};\n\nconst registry = new WeakMap<object, PanelControls>();\n\n/**\n * Upgrade every `data-uix` control under `root` and remember the result\n * against `documentKey` (the panel's `UIKitDocument`). Idempotent per key.\n */\nexport function upgradePanel(documentKey: object, root: UixElement): PanelControls {\n const existing = registry.get(documentKey);\n if (existing) {\n return existing;\n }\n const controls = new PanelControls();\n walk(root, (element) => {\n const marker = attrString(element, 'uix');\n if (marker === undefined) {\n return;\n }\n const upgrader = UPGRADERS[marker];\n if (!upgrader) {\n console.warn(`[uix] unknown control type data-uix=\"${marker}\" - skipped`);\n return;\n }\n controls.add(attrString(element, 'uixId'), upgrader(element));\n });\n registry.set(documentKey, controls);\n return controls;\n}\n\n/** Controls of an upgraded panel; undefined before `UIControlsSystem` ran. */\nexport function panelControlsFor(documentKey: object): PanelControls | undefined {\n return registry.get(documentKey);\n}\n"]}
1
+ {"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../src/controls/upgrade.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Panel upgrade - scans a loaded panel for `<uix-*>` control elements and\n * wires the matching behaviour. App code retrieves the resulting handles via\n * `panelControlsFor(document)` and the control's `data-uix-id`.\n */\nimport { UixElement, attrString, tagOf, walk } from './element.js';\nimport { StepperHandle, upgradeStepper } from './stepper.js';\nimport { ToggleHandle, upgradeToggle } from './toggle.js';\nimport { ExpandableLabelHandle, upgradeExpandableLabel } from './expandable-label.js';\nimport { LogViewHandle, upgradeLogView } from './log-view.js';\n\nexport type ControlHandle =\n | StepperHandle\n | ToggleHandle\n | ExpandableLabelHandle\n | LogViewHandle;\n\nexport class PanelControls {\n private handles = new Map<string, ControlHandle>();\n private anonymous = 0;\n\n add(id: string | undefined, handle: ControlHandle): void {\n const key = id ?? `uix-control-${this.anonymous++}`;\n if (this.handles.has(key)) {\n throw new Error(`[uix] duplicate control id \"${key}\" in panel`);\n }\n this.handles.set(key, handle);\n }\n\n /** Typed accessors - throw on wrong id so mistakes surface immediately. */\n stepper(id: string): StepperHandle {\n return this.expect(id, StepperHandle);\n }\n\n toggle(id: string): ToggleHandle {\n return this.expect(id, ToggleHandle);\n }\n\n expandableLabel(id: string): ExpandableLabelHandle {\n return this.expect(id, ExpandableLabelHandle);\n }\n\n logView(id: string): LogViewHandle {\n return this.expect(id, LogViewHandle);\n }\n\n get(id: string): ControlHandle | undefined {\n return this.handles.get(id);\n }\n\n ids(): string[] {\n return [...this.handles.keys()];\n }\n\n private expect<T extends ControlHandle>(\n id: string,\n kind: abstract new (...args: never[]) => T,\n ): T {\n const handle = this.handles.get(id);\n if (!handle) {\n throw new Error(`[uix] no control \"${id}\" in panel (have: ${this.ids().join(', ') || 'none'})`);\n }\n if (!(handle instanceof kind)) {\n throw new Error(`[uix] control \"${id}\" is not a ${kind.name}`);\n }\n return handle;\n }\n}\n\n/**\n * Control tags. These are the custom elements an author writes; every part\n * inside them (`<uix-value>`, `<uix-line>`, …) is a role, resolved by the\n * individual upgraders through `findRole`.\n */\nconst UPGRADERS: Record<string, (root: UixElement) => ControlHandle> = {\n 'uix-stepper': upgradeStepper,\n 'uix-toggle': upgradeToggle,\n 'uix-expandable-label': upgradeExpandableLabel,\n 'uix-log-view': upgradeLogView,\n};\n\n/**\n * Parts claimed by the control that owns them, resolved through `findRole`.\n * Listed here only so a genuine typo in a control tag still gets a warning\n * instead of being silently mistaken for a role.\n */\nconst ROLE_TAGS = new Set([\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]);\n\nconst registry = new WeakMap<object, PanelControls>();\n\n/**\n * Upgrade every `data-uix` control under `root` and remember the result\n * against `documentKey` (the panel's `UIKitDocument`). Idempotent per key.\n */\nexport function upgradePanel(documentKey: object, root: UixElement): PanelControls {\n const existing = registry.get(documentKey);\n if (existing) {\n return existing;\n }\n const controls = new PanelControls();\n walk(root, (element) => {\n const tag = tagOf(element);\n if (tag === undefined || !tag.startsWith('uix-')) {\n return;\n }\n const upgrader = UPGRADERS[tag];\n if (!upgrader) {\n if (!ROLE_TAGS.has(tag)) {\n console.warn(`[uix] unknown control element <${tag}> - skipped`);\n }\n return;\n }\n controls.add(attrString(element, 'uixId'), upgrader(element));\n });\n registry.set(documentKey, controls);\n return controls;\n}\n\n/** Controls of an upgraded panel; undefined before `UIControlsSystem` ran. */\nexport function panelControlsFor(documentKey: object): PanelControls | undefined {\n return registry.get(documentKey);\n}\n"]}
package/dist/scene.d.ts CHANGED
@@ -9,9 +9,11 @@
9
9
  * applyScene(iwsdkTarget, PLAYGROUND); // ECS entities
10
10
  * applyScene(vanillaHost, PLAYGROUND); // three.js groups
11
11
  *
12
- * Panel config paths stay strings (`./ui/foo.json`) because every adapter
13
- * resolves them the same way - IWSDK fetches them itself, the vanilla host
14
- * is handed the parsed JSON by `applyScene`'s loader.
12
+ * Panel config paths stay opaque strings (`./ui/foo.uikitml`) because each
13
+ * adapter resolves them its own way. IWSDK 0.5 fetches the source and parses
14
+ * it inside PanelUI; the vanilla and XR Blocks hosts parse it through the
15
+ * `loadConfig` resolver and are handed the resulting AST. The descriptor makes
16
+ * no claim about the artefact behind the path.
15
17
  */
16
18
  import type { DockModeValue } from './core/dock-state.js';
17
19
  import type { RegionFlow, Vec3 } from './core/region-layout.js';
@@ -19,7 +21,7 @@ import type { RegionFlow, Vec3 } from './core/region-layout.js';
19
21
  export interface SceneWindow {
20
22
  id: string;
21
23
  title: string;
22
- /** Path to the compiled UIKitML JSON, e.g. `./ui/clicker.json`. */
24
+ /** Path to the UIKitML source, e.g. `./ui/clicker.uikitml`. */
23
25
  config: string;
24
26
  /** World position for world-locked windows. */
25
27
  position?: Vec3;
package/dist/scene.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"scene.js","sourceRoot":"","sources":["../src/scene.ts"],"names":[],"mappings":"AAuEA;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,MAAmB,EAAE,KAAsB;IACpE,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QACzC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;IACD,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACnC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,gBAAgB,CAAC,KAAsB;IACrD,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AACpE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAsB;IAClD,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QACzC,IAAI,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC,wBAAwB,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;QACtD,CAAC;QACD,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACnC,IAAI,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC,wBAAwB,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;QACtD,CAAC;QACD,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACzB,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YACjE,QAAQ,CAAC,IAAI,CACX,WAAW,MAAM,CAAC,EAAE,gCAAgC,MAAM,CAAC,MAAM,GAAG,CACrE,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["/**\n * Portable scene descriptors.\n *\n * A scene is plain DATA - which windows exist, where they sit, which dock\n * regions they can drop into - with no engine types anywhere. Each adapter\n * knows how to apply the same descriptor through its own factories, so one\n * playground definition drives IWSDK, XR Blocks and plain three.js alike:\n *\n * applyScene(iwsdkTarget, PLAYGROUND); // ECS entities\n * applyScene(vanillaHost, PLAYGROUND); // three.js groups\n *\n * Panel config paths stay strings (`./ui/foo.json`) because every adapter\n * resolves them the same way - IWSDK fetches them itself, the vanilla host\n * is handed the parsed JSON by `applyScene`'s loader.\n */\nimport type { DockModeValue } from './core/dock-state.js';\nimport type { RegionFlow, Vec3 } from './core/region-layout.js';\n\n/** One window in a scene. */\nexport interface SceneWindow {\n id: string;\n title: string;\n /** Path to the compiled UIKitML JSON, e.g. `./ui/clicker.json`. */\n config: string;\n /** World position for world-locked windows. */\n position?: Vec3;\n maxWidth?: number;\n maxHeight?: number;\n dockMode?: DockModeValue;\n /** Dock straight into this region on spawn. */\n region?: string;\n followOffset?: Vec3;\n followSpeed?: number;\n followTolerance?: number;\n movable?: boolean;\n closable?: boolean;\n minimizable?: boolean;\n pinnable?: boolean;\n}\n\n/** One dock region in a scene. */\nexport interface SceneRegion {\n id: string;\n flow?: RegionFlow;\n pitch?: number;\n columns?: number;\n capacity?: number;\n snapRadius?: number;\n position?: Vec3;\n /** Body-lock the region so it follows the viewer. */\n follow?: boolean;\n followOffset?: Vec3;\n}\n\n/** A complete, engine-free scene definition. */\nexport interface SceneDescriptor {\n name?: string;\n regions?: readonly SceneRegion[];\n windows: readonly SceneWindow[];\n}\n\n/**\n * What an adapter must provide for {@link applyScene} to build a scene.\n * Both shipped adapters implement this; a new adapter only needs these two\n * methods to gain full scene portability.\n */\nexport interface SceneTarget {\n spawnRegion(region: SceneRegion): void;\n spawnWindow(window: SceneWindow): void;\n}\n\n/**\n * Apply a descriptor to an adapter. Regions are created before windows so a\n * window that spawns docked (`region: 'x'`) always finds its region.\n */\nexport function applyScene(target: SceneTarget, scene: SceneDescriptor): void {\n for (const region of scene.regions ?? []) {\n target.spawnRegion(region);\n }\n for (const window of scene.windows) {\n target.spawnWindow(window);\n }\n}\n\n/** Every distinct panel config path in a scene (for preloading). */\nexport function sceneConfigPaths(scene: SceneDescriptor): string[] {\n return [...new Set(scene.windows.map((window) => window.config))];\n}\n\n/**\n * Validate a descriptor: unique window/region ids, and every `region`\n * reference resolvable. Returns the problems found (empty = valid) rather\n * than throwing, so callers can report them all at once.\n */\nexport function validateScene(scene: SceneDescriptor): string[] {\n const problems: string[] = [];\n const regionIds = new Set<string>();\n for (const region of scene.regions ?? []) {\n if (regionIds.has(region.id)) {\n problems.push(`duplicate region id \"${region.id}\"`);\n }\n regionIds.add(region.id);\n }\n const windowIds = new Set<string>();\n for (const window of scene.windows) {\n if (windowIds.has(window.id)) {\n problems.push(`duplicate window id \"${window.id}\"`);\n }\n windowIds.add(window.id);\n if (window.region !== undefined && !regionIds.has(window.region)) {\n problems.push(\n `window \"${window.id}\" docks into unknown region \"${window.region}\"`,\n );\n }\n }\n return problems;\n}\n"]}
1
+ {"version":3,"file":"scene.js","sourceRoot":"","sources":["../src/scene.ts"],"names":[],"mappings":"AAyEA;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,MAAmB,EAAE,KAAsB;IACpE,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QACzC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;IACD,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACnC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,gBAAgB,CAAC,KAAsB;IACrD,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AACpE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAsB;IAClD,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QACzC,IAAI,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC,wBAAwB,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;QACtD,CAAC;QACD,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACnC,IAAI,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC,wBAAwB,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;QACtD,CAAC;QACD,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACzB,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YACjE,QAAQ,CAAC,IAAI,CACX,WAAW,MAAM,CAAC,EAAE,gCAAgC,MAAM,CAAC,MAAM,GAAG,CACrE,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["/**\n * Portable scene descriptors.\n *\n * A scene is plain DATA - which windows exist, where they sit, which dock\n * regions they can drop into - with no engine types anywhere. Each adapter\n * knows how to apply the same descriptor through its own factories, so one\n * playground definition drives IWSDK, XR Blocks and plain three.js alike:\n *\n * applyScene(iwsdkTarget, PLAYGROUND); // ECS entities\n * applyScene(vanillaHost, PLAYGROUND); // three.js groups\n *\n * Panel config paths stay opaque strings (`./ui/foo.uikitml`) because each\n * adapter resolves them its own way. IWSDK 0.5 fetches the source and parses\n * it inside PanelUI; the vanilla and XR Blocks hosts parse it through the\n * `loadConfig` resolver and are handed the resulting AST. The descriptor makes\n * no claim about the artefact behind the path.\n */\nimport type { DockModeValue } from './core/dock-state.js';\nimport type { RegionFlow, Vec3 } from './core/region-layout.js';\n\n/** One window in a scene. */\nexport interface SceneWindow {\n id: string;\n title: string;\n /** Path to the UIKitML source, e.g. `./ui/clicker.uikitml`. */\n config: string;\n /** World position for world-locked windows. */\n position?: Vec3;\n maxWidth?: number;\n maxHeight?: number;\n dockMode?: DockModeValue;\n /** Dock straight into this region on spawn. */\n region?: string;\n followOffset?: Vec3;\n followSpeed?: number;\n followTolerance?: number;\n movable?: boolean;\n closable?: boolean;\n minimizable?: boolean;\n pinnable?: boolean;\n}\n\n/** One dock region in a scene. */\nexport interface SceneRegion {\n id: string;\n flow?: RegionFlow;\n pitch?: number;\n columns?: number;\n capacity?: number;\n snapRadius?: number;\n position?: Vec3;\n /** Body-lock the region so it follows the viewer. */\n follow?: boolean;\n followOffset?: Vec3;\n}\n\n/** A complete, engine-free scene definition. */\nexport interface SceneDescriptor {\n name?: string;\n regions?: readonly SceneRegion[];\n windows: readonly SceneWindow[];\n}\n\n/**\n * What an adapter must provide for {@link applyScene} to build a scene.\n * Both shipped adapters implement this; a new adapter only needs these two\n * methods to gain full scene portability.\n */\nexport interface SceneTarget {\n spawnRegion(region: SceneRegion): void;\n spawnWindow(window: SceneWindow): void;\n}\n\n/**\n * Apply a descriptor to an adapter. Regions are created before windows so a\n * window that spawns docked (`region: 'x'`) always finds its region.\n */\nexport function applyScene(target: SceneTarget, scene: SceneDescriptor): void {\n for (const region of scene.regions ?? []) {\n target.spawnRegion(region);\n }\n for (const window of scene.windows) {\n target.spawnWindow(window);\n }\n}\n\n/** Every distinct panel config path in a scene (for preloading). */\nexport function sceneConfigPaths(scene: SceneDescriptor): string[] {\n return [...new Set(scene.windows.map((window) => window.config))];\n}\n\n/**\n * Validate a descriptor: unique window/region ids, and every `region`\n * reference resolvable. Returns the problems found (empty = valid) rather\n * than throwing, so callers can report them all at once.\n */\nexport function validateScene(scene: SceneDescriptor): string[] {\n const problems: string[] = [];\n const regionIds = new Set<string>();\n for (const region of scene.regions ?? []) {\n if (regionIds.has(region.id)) {\n problems.push(`duplicate region id \"${region.id}\"`);\n }\n regionIds.add(region.id);\n }\n const windowIds = new Set<string>();\n for (const window of scene.windows) {\n if (windowIds.has(window.id)) {\n problems.push(`duplicate window id \"${window.id}\"`);\n }\n windowIds.add(window.id);\n if (window.region !== undefined && !regionIds.has(window.region)) {\n problems.push(\n `window \"${window.id}\" docks into unknown region \"${window.region}\"`,\n );\n }\n }\n return problems;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@realitycollective/webxr-uiextensions",
3
- "version": "0.1.0-preview.0",
3
+ "version": "0.1.0-preview.1",
4
4
  "description": "Engine-free core of the Reality Collective UI Extensions: windowing, docking, layout regions and control models for WebXR spatial UI, driven through platform-adapter interfaces. Pair with an engine adapter - @realitycollective/iwsdk-uiextensions (Meta IWSDK) or @realitycollective/xrblocks-uiextensions (Google XR Blocks).",
5
5
  "keywords": [
6
6
  "realitycollective",