@rydr/game-sdk 3.0.1 → 3.1.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.
Files changed (48) hide show
  1. package/README.md +34 -0
  2. package/dist/protocol/version.d.ts +1 -1
  3. package/dist/protocol/version.js +1 -1
  4. package/dist/ui/action-card.d.ts +39 -0
  5. package/dist/ui/action-card.d.ts.map +1 -0
  6. package/dist/ui/action-card.js +38 -0
  7. package/dist/ui/action-card.js.map +1 -0
  8. package/dist/ui/action-diamond.d.ts +41 -0
  9. package/dist/ui/action-diamond.d.ts.map +1 -0
  10. package/dist/ui/action-diamond.js +35 -0
  11. package/dist/ui/action-diamond.js.map +1 -0
  12. package/dist/ui/card.d.ts +45 -0
  13. package/dist/ui/card.d.ts.map +1 -0
  14. package/dist/ui/card.js +73 -0
  15. package/dist/ui/card.js.map +1 -0
  16. package/dist/ui/choice-card.d.ts +73 -0
  17. package/dist/ui/choice-card.d.ts.map +1 -0
  18. package/dist/ui/choice-card.js +141 -0
  19. package/dist/ui/choice-card.js.map +1 -0
  20. package/dist/ui/dialogue-card.d.ts +54 -0
  21. package/dist/ui/dialogue-card.d.ts.map +1 -0
  22. package/dist/ui/dialogue-card.js +107 -0
  23. package/dist/ui/dialogue-card.js.map +1 -0
  24. package/dist/ui/index.d.ts +31 -0
  25. package/dist/ui/index.d.ts.map +1 -0
  26. package/dist/ui/index.js +31 -0
  27. package/dist/ui/index.js.map +1 -0
  28. package/dist/ui/keycap.d.ts +77 -0
  29. package/dist/ui/keycap.d.ts.map +1 -0
  30. package/dist/ui/keycap.js +160 -0
  31. package/dist/ui/keycap.js.map +1 -0
  32. package/dist/ui/labeled-diamond.d.ts +32 -0
  33. package/dist/ui/labeled-diamond.d.ts.map +1 -0
  34. package/dist/ui/labeled-diamond.js +86 -0
  35. package/dist/ui/labeled-diamond.js.map +1 -0
  36. package/dist/ui/showcase/gallery.d.ts +57 -0
  37. package/dist/ui/showcase/gallery.d.ts.map +1 -0
  38. package/dist/ui/showcase/gallery.js +90 -0
  39. package/dist/ui/showcase/gallery.js.map +1 -0
  40. package/dist/ui/showcase/index.d.ts +35 -0
  41. package/dist/ui/showcase/index.d.ts.map +1 -0
  42. package/dist/ui/showcase/index.js +256 -0
  43. package/dist/ui/showcase/index.js.map +1 -0
  44. package/dist/ui/styles.d.ts +12 -0
  45. package/dist/ui/styles.d.ts.map +1 -0
  46. package/dist/ui/styles.js +183 -0
  47. package/dist/ui/styles.js.map +1 -0
  48. package/package.json +13 -2
package/README.md CHANGED
@@ -363,6 +363,40 @@ room.setState({ phase: "racing" }); // merge into shared opaque state (last-writ
363
363
 
364
364
  > **Status: when `room` lands.** The client + protocol are shipped, but the backend `room` party is **not yet deployed** — `joinRoom` works in standalone-dev loopback today, and goes live against the shared backend with the realtime/multiplayer follow-up. Build against it; just don't expect cross-client presence in production until then.
365
365
 
366
+ ## UI components (optional `@rydr/game-sdk/ui`)
367
+
368
+ A self-contained DOM/CSS kit for the **controller-button UI** — showing "which button to press" in one consistent visual language across the library. Like the `three` helper it's a separate subpath export; unlike it, it needs **no peer dep** (no three.js). It injects its own scoped `.rydr-ui-*` CSS once into `<head>`, depends on no global styles or CSS variables, and renders pure DOM, so a game gets the look just by constructing a component.
369
+
370
+ ```ts
371
+ import { createKeycap, mountActionDiamond, mountDialogueCard } from "@rydr/game-sdk/ui";
372
+
373
+ // a single A keycap that reflects real presses — pass the session (it satisfies `ButtonSource`)
374
+ host.appendChild(createKeycap("A", { press: session }).el);
375
+
376
+ // the full A/B/Y/Z move diamond, wired to input
377
+ const dia = mountActionDiamond(host, { press: session, cards: { A: { label: "Séisme" } } });
378
+ dia.cards.A.setActive(true);
379
+ dia.cards.Z.keycap.setCooldown(0.4, 3); // depleting ring + countdown
380
+ ```
381
+
382
+ - **`createKeycap(button, opts)`** / **`createDpadKeycap(dir, opts)`** — a keycap of the A/B/Y/Z face buttons or the UP/DOWN/LEFT/RIGHT d-pad. A small state machine: `solo` vs `full` diamond, pulse, pressed, cooldown ring + countdown, colored vs mono, disabled, hidden.
383
+ - **`mountCard`** — the dark glass container with a content slot. **`mountActionCard`** — that card + a keycap straddling its bottom edge. **`mountActionDiamond`** — the full A/B/Y/Z move diamond.
384
+ - **`mountDialogueCard`** — an NPC dialogue box (name + typewriter line + continue keycap). **`mountChoiceCard`** — a player menu (prompt + selectable list, ▲▼ + confirm). **`mountLabeledDiamond`** — a pip cluster with a card-seated label radiating from each active button.
385
+
386
+ Wire any keycap to real input by passing the `session` — it structurally satisfies `ButtonSource`, and the keycap then shows its pressed sink while its button is held. The kit speaks the canonical `ButtonName`/`ButtonEdge` vocabulary.
387
+
388
+ > **Don't restyle the components — the sizing is deliberate.** RYDR games run on a **trainer screen viewed from a few feet away while pedaling**, so the type sizes, paddings, and dimensions are tuned to stay legible at that distance. Fill the **content slot** (a card/action card takes any DOM; the diamond takes a label or custom node) and drive the state methods — but don't override the `.rydr-ui-*` font sizes/paddings/widths or wrap a component in `transform: scale()`. If a label doesn't fit, **shorten the text, don't shrink the type.**
389
+
390
+ **Live catalog.** Every component, in every state, is rendered by `mountUiShowcase(host, { session? })` from the separate `@rydr/game-sdk/ui/showcase` entry — the SDK owns *what* the catalog shows (so it versions with the components and never drifts), a host owns only *where*. It's a separate subpath so a game importing the components never pulls the catalog into its bundle. Two hosts, one function:
391
+
392
+ ```ts
393
+ import { mountUiShowcase } from "@rydr/game-sdk/ui/showcase";
394
+ mountUiShowcase(document.body, { session }); // pass the session → live watts + real button reflection
395
+ ```
396
+
397
+ - **Locally:** `npm run showcase` (in this repo) serves the standalone dev page in `examples/ui` — no shell needed; keycaps are driven by the keyboard.
398
+ - **In the platform:** the shell mounts the same function at a permanent admin route (e.g. `/game-ui`), so the catalog is always live and reviewed in the real trainer-screen chrome.
399
+
366
400
  ## Versioning
367
401
 
368
402
  `RYDR_PROTOCOL_VERSION` is the wire version. The shell supports a range and adapts older messages. Breaking shape changes are forbidden; evolve additively.
@@ -9,5 +9,5 @@
9
9
  export declare const RYDR_PROTOCOL_VERSION: 11;
10
10
  /** Semver of this SDK build. Sent in the handshake for telemetry/debugging.
11
11
  * (Bumped to 2.0.0 by `npm version major` on release — see CHANGELOG [Unreleased].) */
12
- export declare const RYDR_SDK_VERSION = "3.0.1";
12
+ export declare const RYDR_SDK_VERSION = "3.1.1";
13
13
  //# sourceMappingURL=version.d.ts.map
@@ -29,5 +29,5 @@
29
29
  export const RYDR_PROTOCOL_VERSION = 11;
30
30
  /** Semver of this SDK build. Sent in the handshake for telemetry/debugging.
31
31
  * (Bumped to 2.0.0 by `npm version major` on release — see CHANGELOG [Unreleased].) */
32
- export const RYDR_SDK_VERSION = "3.0.1";
32
+ export const RYDR_SDK_VERSION = "3.1.1";
33
33
  //# sourceMappingURL=version.js.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * ACTION CARD — a {@link Card} (the dark glass card with a content slot) plus a controller-button
3
+ * keycap straddling its bottom edge. Inside the diamond it becomes a uniform fixed-size frame.
4
+ *
5
+ * `createButtonDiamond` / `mountButtonPrompt` are kept as thin back-compat wrappers.
6
+ */
7
+ import { type Card, type CardOptions } from "./card.js";
8
+ import { type ButtonLetter, type KeycapOptions, type KeycapVariant, type Keycap } from "./keycap.js";
9
+ export interface ActionCardOptions extends KeycapOptions, CardOptions {
10
+ }
11
+ /** A mounted action card: a {@link Card} + a keycap (state setters delegated onto the card). */
12
+ export interface ActionCard extends Card {
13
+ /** The underlying keycap (state setters live here; the card delegates the common ones). */
14
+ keycap: Keycap;
15
+ setPressed(on: boolean): void;
16
+ setCooldown(frac: number | null, label?: string | number): void;
17
+ setAnimated(on: boolean): void;
18
+ setColored(on: boolean): void;
19
+ /** Disable the WHOLE action at once: the card fades to 50% and the keycap goes inert. */
20
+ setDisabled(on: boolean): void;
21
+ }
22
+ /**
23
+ * Mount an action card: a dark glass card with the keycap straddling its bottom edge and a content
24
+ * SLOT (`card.body`). Fixed bottom-centre by default (call `setScreenPos` to track a projected point),
25
+ * or `inline:true` to lay it in normal flow.
26
+ */
27
+ export declare function mountActionCard(host: HTMLElement, button: ButtonLetter, opts?: ActionCardOptions): ActionCard;
28
+ export interface ButtonPrompt {
29
+ root: HTMLElement;
30
+ setLabel(text: string): void;
31
+ setVisible(v: boolean): void;
32
+ setScreenPos(x: number, y: number): void;
33
+ dispose(): void;
34
+ }
35
+ /** The bare keycap (no card) for embedding — back-compat shim over {@link createKeycap}. */
36
+ export declare function createButtonDiamond(button: ButtonLetter, variant?: KeycapVariant): HTMLElement;
37
+ /** A label-only action prompt — back-compat shim over {@link mountActionCard}. */
38
+ export declare function mountButtonPrompt(host: HTMLElement, button: ButtonLetter, label: string): ButtonPrompt;
39
+ //# sourceMappingURL=action-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-card.d.ts","sourceRoot":"","sources":["../../src/ui/action-card.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAa,KAAK,IAAI,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AACnE,OAAO,EAAgB,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAEnH,MAAM,WAAW,iBAAkB,SAAQ,aAAa,EAAE,WAAW;CAAG;AAExE,gGAAgG;AAChG,MAAM,WAAW,UAAW,SAAQ,IAAI;IACtC,2FAA2F;IAC3F,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9B,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAChE,WAAW,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9B,yFAAyF;IACzF,WAAW,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,GAAE,iBAAsB,GAAG,UAAU,CAgBjH;AAID,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,4FAA4F;AAC5F,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,GAAE,aAAsB,GAAG,WAAW,CAEtG;AAED,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAEtG"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * ACTION CARD — a {@link Card} (the dark glass card with a content slot) plus a controller-button
3
+ * keycap straddling its bottom edge. Inside the diamond it becomes a uniform fixed-size frame.
4
+ *
5
+ * `createButtonDiamond` / `mountButtonPrompt` are kept as thin back-compat wrappers.
6
+ */
7
+ import { buildCard } from "./card.js";
8
+ import { createKeycap } from "./keycap.js";
9
+ /**
10
+ * Mount an action card: a dark glass card with the keycap straddling its bottom edge and a content
11
+ * SLOT (`card.body`). Fixed bottom-centre by default (call `setScreenPos` to track a projected point),
12
+ * or `inline:true` to lay it in normal flow.
13
+ */
14
+ export function mountActionCard(host, button, opts = {}) {
15
+ const card = buildCard(opts);
16
+ const keycap = createKeycap(button, opts);
17
+ card.card.appendChild(keycap.el); // the button floats over the card's bottom edge
18
+ host.appendChild(card.root);
19
+ return {
20
+ ...card,
21
+ keycap,
22
+ setPressed: keycap.setPressed,
23
+ setCooldown: keycap.setCooldown,
24
+ setAnimated: keycap.setAnimated,
25
+ setColored: keycap.setColored,
26
+ // the whole action goes unavailable: fade the card AND make the keycap inert in one call.
27
+ setDisabled: (on) => { card.setDisabled(on); keycap.setDisabled(on); },
28
+ };
29
+ }
30
+ /** The bare keycap (no card) for embedding — back-compat shim over {@link createKeycap}. */
31
+ export function createButtonDiamond(button, variant = "full") {
32
+ return createKeycap(button, { variant }).el;
33
+ }
34
+ /** A label-only action prompt — back-compat shim over {@link mountActionCard}. */
35
+ export function mountButtonPrompt(host, button, label) {
36
+ return mountActionCard(host, button, { label });
37
+ }
38
+ //# sourceMappingURL=action-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-card.js","sourceRoot":"","sources":["../../src/ui/action-card.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAA+B,MAAM,WAAW,CAAC;AACnE,OAAO,EAAE,YAAY,EAA0E,MAAM,aAAa,CAAC;AAgBnH;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAiB,EAAE,MAAoB,EAAE,OAA0B,EAAE;IACnG,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,gDAAgD;IAClF,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAE5B,OAAO;QACL,GAAG,IAAI;QACP,MAAM;QACN,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,0FAA0F;QAC1F,WAAW,EAAE,CAAC,EAAW,EAAE,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;KAChF,CAAC;AACJ,CAAC;AAYD,4FAA4F;AAC5F,MAAM,UAAU,mBAAmB,CAAC,MAAoB,EAAE,UAAyB,MAAM;IACvF,OAAO,YAAY,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC;AAC9C,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,iBAAiB,CAAC,IAAiB,EAAE,MAAoB,EAAE,KAAa;IACtF,OAAO,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * ACTION DIAMOND — the full A/B/Y/Z move diamond: four action cards in a losange (Y top, A right, B
3
+ * bottom, Z left). Each card is exactly the {@link mountActionCard} card (same `.rydr-ui-card` style +
4
+ * keycap), with a freeform content SLOT the consumer fills. The diamond is a centred grid that sizes
5
+ * to its content; `compact` / `rowGap` / `columnGap` tune the spread.
6
+ */
7
+ import { type ButtonLetter, type ButtonSource } from "./keycap.js";
8
+ import { type ActionCard } from "./action-card.js";
9
+ /** One diamond item: a full action card (same `.rydr-ui-card` style) with a `setActive` highlight added. */
10
+ export interface DiamondCard extends ActionCard {
11
+ /** Highlight this card (e.g. the currently-selected move). */
12
+ setActive(on: boolean): void;
13
+ }
14
+ export interface ActionDiamond {
15
+ root: HTMLElement;
16
+ /** The four cards by face button (Y top · A right · B bottom · Z left). */
17
+ cards: Record<ButtonLetter, DiamondCard>;
18
+ dispose(): void;
19
+ }
20
+ export interface ActionDiamondOptions {
21
+ colored?: boolean;
22
+ /** Initial content per button — a string label (e.g. the attack name) or custom DOM in `content`. */
23
+ cards?: Partial<Record<ButtonLetter, {
24
+ content?: string | HTMLElement;
25
+ label?: string;
26
+ }>>;
27
+ /** Vertical gap (px) between the stacked Y/B centre cards. */
28
+ rowGap?: number;
29
+ /** Horizontal gap (px) between the centre column and the Z/A side cards. */
30
+ columnGap?: number;
31
+ /** Wire every card's keycap to real controller input (pressed while its button is held). */
32
+ press?: ButtonSource;
33
+ }
34
+ /**
35
+ * Mount the A/B/Y/Z move diamond: Z left · Y+B stacked in the centre column · A right. Each card is
36
+ * the {@link mountActionCard} card with a freeform content SLOT — an attack-name string, or any custom
37
+ * DOM. The cluster sizes to its content and stays centred; per-card `setActive`/`setCooldown` drive
38
+ * the move state.
39
+ */
40
+ export declare function mountActionDiamond(host: HTMLElement, opts?: ActionDiamondOptions): ActionDiamond;
41
+ //# sourceMappingURL=action-diamond.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-diamond.d.ts","sourceRoot":"","sources":["../../src/ui/action-diamond.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAIpE,4GAA4G;AAC5G,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C,8DAA8D;IAC9D,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;IACzC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qGAAqG;IACrG,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IAC1F,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4FAA4F;IAC5F,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,GAAE,oBAAyB,GAAG,aAAa,CAkBpG"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * ACTION DIAMOND — the full A/B/Y/Z move diamond: four action cards in a losange (Y top, A right, B
3
+ * bottom, Z left). Each card is exactly the {@link mountActionCard} card (same `.rydr-ui-card` style +
4
+ * keycap), with a freeform content SLOT the consumer fills. The diamond is a centred grid that sizes
5
+ * to its content; `compact` / `rowGap` / `columnGap` tune the spread.
6
+ */
7
+ import {} from "./keycap.js";
8
+ import { mountActionCard } from "./action-card.js";
9
+ const POS_CLASS = { Y: "pos-top", A: "pos-rgt", B: "pos-bot", Z: "pos-lft" };
10
+ /**
11
+ * Mount the A/B/Y/Z move diamond: Z left · Y+B stacked in the centre column · A right. Each card is
12
+ * the {@link mountActionCard} card with a freeform content SLOT — an attack-name string, or any custom
13
+ * DOM. The cluster sizes to its content and stays centred; per-card `setActive`/`setCooldown` drive
14
+ * the move state.
15
+ */
16
+ export function mountActionDiamond(host, opts = {}) {
17
+ const colored = opts.colored ?? true;
18
+ const root = document.createElement("div");
19
+ root.className = "rydr-ui-dia";
20
+ if (opts.rowGap != null)
21
+ root.style.rowGap = `${opts.rowGap}px`;
22
+ if (opts.columnGap != null)
23
+ root.style.columnGap = `${opts.columnGap}px`;
24
+ const cards = {};
25
+ for (const button of ["Y", "A", "B", "Z"]) {
26
+ const init = opts.cards?.[button];
27
+ const card = mountActionCard(root, button, { colored, animate: false, inline: true, press: opts.press, content: init?.content, label: init?.label });
28
+ card.root.classList.add("rydr-ui-dia-card", POS_CLASS[button]);
29
+ card.setActive = (on) => card.root.classList.toggle("active", on);
30
+ cards[button] = card;
31
+ }
32
+ host.appendChild(root);
33
+ return { root, cards, dispose: () => root.remove() };
34
+ }
35
+ //# sourceMappingURL=action-diamond.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-diamond.js","sourceRoot":"","sources":["../../src/ui/action-diamond.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAwC,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,eAAe,EAAmB,MAAM,kBAAkB,CAAC;AAEpE,MAAM,SAAS,GAAiC,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC;AA2B3G;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAiB,EAAE,OAA6B,EAAE;IACnF,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC;IACrC,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAC3C,IAAI,CAAC,SAAS,GAAG,aAAa,CAAC;IAC/B,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI;QAAE,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC;IAChE,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI;QAAE,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC;IAEzE,MAAM,KAAK,GAAG,EAAuC,CAAC;IACtD,KAAK,MAAM,MAAM,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAmB,EAAE,CAAC;QAC5D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC;QAClC,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAgB,CAAC;QACpK,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,kBAAkB,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/D,IAAI,CAAC,SAAS,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAClE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IACvB,CAAC;IAED,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IACvB,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AACvD,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * CARD — the dark glass card (the "ATTAQUER" look): a styled frame with a CONTENT SLOT (`card.body`)
3
+ * the game fills with whatever it wants (a label, a creature plate, a dialogue line, …). No keycap —
4
+ * that's {@link mountActionCard}, which composes this card with a controller button on top.
5
+ *
6
+ * A standalone card WRAPS its content; `inline:false` (default) places it fixed bottom-centre (use
7
+ * `setScreenPos` to track a projected point), `inline:true` lays it in normal flow.
8
+ */
9
+ export interface CardOptions {
10
+ /** Initial slot content — a string (set as text) or an element appended into `body`. */
11
+ content?: string | HTMLElement;
12
+ /** Convenience: a single line of label text (rendered as the card's body). */
13
+ label?: string;
14
+ /** Lay the card inline (in normal flow) instead of fixed bottom-centre. */
15
+ inline?: boolean;
16
+ /** Fixed card width (number → px, or any CSS length). Omit → the card hugs its content. */
17
+ width?: number | string;
18
+ /** Fixed card height (number → px, or any CSS length). Omit → the card hugs its content. */
19
+ height?: number | string;
20
+ }
21
+ /** A mounted card: a styled frame + a content slot the game owns. */
22
+ export interface Card {
23
+ root: HTMLElement;
24
+ /** The content SLOT — append your own DOM here, or use setContent/setLabel. */
25
+ body: HTMLElement;
26
+ setLabel(text: string): void;
27
+ setContent(content: string | HTMLElement): void;
28
+ /** Disabled = the action is unavailable: the card (and any floating keycap) fades to 50% + inert. */
29
+ setDisabled(on: boolean): void;
30
+ setVisible(v: boolean): void;
31
+ /** Centre the card at a viewport pixel (e.g. a creature projected to screen). */
32
+ setScreenPos(x: number, y: number): void;
33
+ dispose(): void;
34
+ }
35
+ /**
36
+ * Build the card DOM + handle WITHOUT attaching it to a host. Also returns the inner `.rydr-ui-card`
37
+ * element so {@link mountActionCard} can hang a keycap on it. Internal building block — most callers
38
+ * want `mountCard` or `mountActionCard`.
39
+ */
40
+ export declare function buildCard(opts?: CardOptions): Card & {
41
+ card: HTMLElement;
42
+ };
43
+ /** Mount a standalone dark card (no keycap) with a content slot. */
44
+ export declare function mountCard(host: HTMLElement, opts?: CardOptions): Card;
45
+ //# sourceMappingURL=card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card.d.ts","sourceRoot":"","sources":["../../src/ui/card.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,WAAW,WAAW;IAC1B,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IAC/B,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2FAA2F;IAC3F,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,4FAA4F;IAC5F,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED,qEAAqE;AACrE,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,WAAW,CAAC;IAClB,+EAA+E;IAC/E,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAAC;IAChD,qGAAqG;IACrG,WAAW,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,iFAAiF;IACjF,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,GAAE,WAAgB,GAAG,IAAI,GAAG;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAgD9E;AAED,oEAAoE;AACpE,wBAAgB,SAAS,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,GAAE,WAAgB,GAAG,IAAI,CAIzE"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * CARD — the dark glass card (the "ATTAQUER" look): a styled frame with a CONTENT SLOT (`card.body`)
3
+ * the game fills with whatever it wants (a label, a creature plate, a dialogue line, …). No keycap —
4
+ * that's {@link mountActionCard}, which composes this card with a controller button on top.
5
+ *
6
+ * A standalone card WRAPS its content; `inline:false` (default) places it fixed bottom-centre (use
7
+ * `setScreenPos` to track a projected point), `inline:true` lays it in normal flow.
8
+ */
9
+ import { injectCss } from "./styles.js";
10
+ /**
11
+ * Build the card DOM + handle WITHOUT attaching it to a host. Also returns the inner `.rydr-ui-card`
12
+ * element so {@link mountActionCard} can hang a keycap on it. Internal building block — most callers
13
+ * want `mountCard` or `mountActionCard`.
14
+ */
15
+ export function buildCard(opts = {}) {
16
+ injectCss();
17
+ const root = document.createElement("div");
18
+ root.className = "rydr-ui-root" + (opts.inline ? " inline" : "");
19
+ const card = document.createElement("div");
20
+ card.className = "rydr-ui-card";
21
+ // size is the consumer's choice: hug content (default), or a fixed frame when width/height are given.
22
+ if (opts.width != null || opts.height != null) {
23
+ card.classList.add("fixed");
24
+ const len = (v) => (typeof v === "number" ? `${v}px` : v);
25
+ if (opts.width != null)
26
+ card.style.width = len(opts.width);
27
+ if (opts.height != null)
28
+ card.style.height = len(opts.height);
29
+ }
30
+ const body = document.createElement("div");
31
+ body.className = "rydr-ui-card-body";
32
+ card.appendChild(body);
33
+ root.appendChild(card);
34
+ const setContent = (content) => {
35
+ body.textContent = "";
36
+ if (typeof content === "string") {
37
+ const span = document.createElement("span");
38
+ span.className = "rydr-ui-card-lbl";
39
+ span.textContent = content;
40
+ body.appendChild(span);
41
+ }
42
+ else {
43
+ body.appendChild(content);
44
+ }
45
+ };
46
+ if (opts.content != null)
47
+ setContent(opts.content);
48
+ else if (opts.label != null)
49
+ setContent(opts.label);
50
+ return {
51
+ root,
52
+ card,
53
+ body,
54
+ setLabel: (t) => setContent(t),
55
+ setContent,
56
+ setDisabled: (on) => card.classList.toggle("disabled", on),
57
+ setVisible: (v) => { root.style.opacity = v ? "1" : "0"; },
58
+ setScreenPos(x, y) {
59
+ root.style.left = `${x}px`;
60
+ root.style.top = `${y}px`;
61
+ root.style.bottom = "auto";
62
+ root.style.transform = "translate(-50%, -50%)";
63
+ },
64
+ dispose: () => root.remove(),
65
+ };
66
+ }
67
+ /** Mount a standalone dark card (no keycap) with a content slot. */
68
+ export function mountCard(host, opts = {}) {
69
+ const card = buildCard(opts);
70
+ host.appendChild(card.root);
71
+ return card;
72
+ }
73
+ //# sourceMappingURL=card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card.js","sourceRoot":"","sources":["../../src/ui/card.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * CHOICE CARD — a PLAYER-facing menu built from the kit: a {@link Card} (dark container) holding an
3
+ * optional SPEAKER name, an optional PROMPT line, and a vertical list of selectable OPTIONS. One option
4
+ * is highlighted at a time; ▲▼ move the highlight (clamped at the ends) and the confirm KEYCAP (default
5
+ * A) rides the right of the highlighted option. Where the dialogue card is the NPC talking, this is how
6
+ * the PLAYER answers — e.g. "Voici le Vivax que j'aimerais aller voir…" + a list of Vivax.
7
+ *
8
+ * `open({...})` shows the menu; `moveBy(±1)` / `setIndex(i)` move the highlight; `confirm()` returns the
9
+ * chosen `{index,label}` (and fires `onConfirm`). The consumer wires these to controller input
10
+ * (UP/DOWN → moveBy, A → confirm), mirroring how the dialogue card's `advance()` is driven.
11
+ *
12
+ * Each option + the prompt share the dialogue card's soft `maxChars` budget (a long string logs a dev
13
+ * warning), and the rows clamp + wrap so an over-long label can't break the layout. Fixed bottom-centre
14
+ * by default (a classic RPG bar), or `inline:true` to lay it in normal flow.
15
+ */
16
+ import { type Card } from "./card.js";
17
+ import { type ButtonLetter, type ButtonSource, type Keycap } from "./keycap.js";
18
+ export interface ChoiceCardOptions {
19
+ /** The confirm button parked on the highlighted option (default "A"). */
20
+ button?: ButtonLetter;
21
+ /** Lay the card inline (normal flow) instead of fixed bottom-centre. */
22
+ inline?: boolean;
23
+ /** Wire the confirm keycap to real controller input (pressed while its button is held). */
24
+ press?: ButtonSource;
25
+ /** Soft per-string character budget for the prompt and each option (default 140). */
26
+ maxChars?: number;
27
+ /**
28
+ * Show a directional-keycap guide on the left of the highlighted option (default true): both UP and
29
+ * DOWN always appear, with the unavailable one disabled (UP at the top, DOWN at the bottom).
30
+ */
31
+ arrows?: boolean;
32
+ }
33
+ export interface ChoiceConfig {
34
+ /** Speaker name above the prompt (e.g. the player's name); omit/empty to hide. */
35
+ name?: string;
36
+ /** The question shown above the options (e.g. "Voici le Vivax que j'aimerais aller voir…"). Optional
37
+ * but strongly recommended — it frames the choice; omit/empty to hide. */
38
+ prompt?: string;
39
+ /** The selectable options, top to bottom. */
40
+ options: string[];
41
+ /** Initially highlighted index (default 0). */
42
+ selected?: number;
43
+ /** Called when `confirm()` runs, with the chosen option. */
44
+ onConfirm?: (index: number, label: string) => void;
45
+ }
46
+ export interface ChoiceCard {
47
+ root: HTMLElement;
48
+ /** The underlying card (container) — for styling / positioning. */
49
+ card: Card;
50
+ /** The confirm keycap (drive its state if you want). */
51
+ keycap: Keycap;
52
+ /** Show the menu: speaker + prompt + options, highlighting `selected` (default 0). */
53
+ open(cfg: ChoiceConfig): void;
54
+ /** Move the highlight by `delta` (clamped at the ends, matching the arrow guide); returns the new index. */
55
+ moveBy(delta: number): number;
56
+ /** Set the highlight to `i` (clamped into range); returns the new index. */
57
+ setIndex(i: number): number;
58
+ /** The currently highlighted index. */
59
+ index(): number;
60
+ /** Confirm the current option: fires `onConfirm` and returns `{index,label}` (null if no options). */
61
+ confirm(): {
62
+ index: number;
63
+ label: string;
64
+ } | null;
65
+ /** Hide the card and reset. */
66
+ close(): void;
67
+ setVisible(v: boolean): void;
68
+ setScreenPos(x: number, y: number): void;
69
+ dispose(): void;
70
+ }
71
+ /** Mount a choice card: name + prompt + a highlighted option list + a confirm cue & keycap. */
72
+ export declare function mountChoiceCard(host: HTMLElement, opts?: ChoiceCardOptions): ChoiceCard;
73
+ //# sourceMappingURL=choice-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"choice-card.d.ts","sourceRoot":"","sources":["../../src/ui/choice-card.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAa,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAkC,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAEhH,MAAM,WAAW,iBAAiB;IAChC,yEAAyE;IACzE,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2FAA2F;IAC3F,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,qFAAqF;IACrF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;+EAC2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpD;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,mEAAmE;IACnE,IAAI,EAAE,IAAI,CAAC;IACX,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,IAAI,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9B,4GAA4G;IAC5G,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,4EAA4E;IAC5E,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,uCAAuC;IACvC,KAAK,IAAI,MAAM,CAAC;IAChB,sGAAsG;IACtG,OAAO,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACnD,+BAA+B;IAC/B,KAAK,IAAI,IAAI,CAAC;IACd,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,+FAA+F;AAC/F,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,GAAE,iBAAsB,GAAG,UAAU,CAyH3F"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * CHOICE CARD — a PLAYER-facing menu built from the kit: a {@link Card} (dark container) holding an
3
+ * optional SPEAKER name, an optional PROMPT line, and a vertical list of selectable OPTIONS. One option
4
+ * is highlighted at a time; ▲▼ move the highlight (clamped at the ends) and the confirm KEYCAP (default
5
+ * A) rides the right of the highlighted option. Where the dialogue card is the NPC talking, this is how
6
+ * the PLAYER answers — e.g. "Voici le Vivax que j'aimerais aller voir…" + a list of Vivax.
7
+ *
8
+ * `open({...})` shows the menu; `moveBy(±1)` / `setIndex(i)` move the highlight; `confirm()` returns the
9
+ * chosen `{index,label}` (and fires `onConfirm`). The consumer wires these to controller input
10
+ * (UP/DOWN → moveBy, A → confirm), mirroring how the dialogue card's `advance()` is driven.
11
+ *
12
+ * Each option + the prompt share the dialogue card's soft `maxChars` budget (a long string logs a dev
13
+ * warning), and the rows clamp + wrap so an over-long label can't break the layout. Fixed bottom-centre
14
+ * by default (a classic RPG bar), or `inline:true` to lay it in normal flow.
15
+ */
16
+ import { mountCard } from "./card.js";
17
+ import { createKeycap, createDpadKeycap } from "./keycap.js";
18
+ /** Mount a choice card: name + prompt + a highlighted option list + a confirm cue & keycap. */
19
+ export function mountChoiceCard(host, opts = {}) {
20
+ const maxChars = Math.max(1, opts.maxChars ?? 140);
21
+ const showArrows = opts.arrows ?? true;
22
+ const wrap = document.createElement("div");
23
+ wrap.className = "rydr-ui-choice";
24
+ const nameEl = document.createElement("div");
25
+ nameEl.className = "rydr-ui-choice-name";
26
+ const promptEl = document.createElement("div");
27
+ promptEl.className = "rydr-ui-choice-prompt";
28
+ const listEl = document.createElement("div");
29
+ listEl.className = "rydr-ui-choice-list";
30
+ const keycap = createKeycap(opts.button ?? "A", { variant: "solo", animate: true, press: opts.press });
31
+ // the keycap is parked on the highlighted row by render(); it has no fixed home in the card.
32
+ wrap.append(nameEl, promptEl, listEl);
33
+ const card = mountCard(host, { content: wrap, inline: opts.inline });
34
+ card.setVisible(false);
35
+ let options = [];
36
+ let rows = [];
37
+ let gutters = [];
38
+ let arrowKeycaps = [];
39
+ let idx = 0;
40
+ let onConfirm;
41
+ const clearArrows = () => {
42
+ arrowKeycaps.forEach((k) => k.dispose());
43
+ arrowKeycaps = [];
44
+ gutters.forEach((gu) => { gu.textContent = ""; });
45
+ };
46
+ // Char count is a rough proxy for "will this fit" — enough to flag a likely overflow to the author.
47
+ const checkLen = (s, what) => {
48
+ if (s.length > maxChars)
49
+ console.warn(`[choice-card] ${what} is ${s.length} chars (budget ${maxChars}); it may be clipped: "${s.slice(0, 48)}…"`);
50
+ };
51
+ const render = () => {
52
+ rows.forEach((r, i) => r.classList.toggle("selected", i === idx));
53
+ // park the (single) confirm keycap on the right of the highlighted row so it rides selection.
54
+ const selectedRow = rows[idx];
55
+ if (selectedRow)
56
+ selectedRow.appendChild(keycap.el);
57
+ if (!showArrows)
58
+ return;
59
+ // arrows live ONLY on the highlighted row: BOTH UP and DOWN always show, but the one with nowhere
60
+ // to go is disabled (UP at the top, DOWN at the bottom) — a moving guide of where ▲▼ can take you.
61
+ clearArrows();
62
+ const guide = gutters[idx];
63
+ if (!guide)
64
+ return;
65
+ const up = createDpadKeycap("UP", { animate: false });
66
+ const dn = createDpadKeycap("DOWN", { animate: false });
67
+ up.setDisabled(idx <= 0);
68
+ dn.setDisabled(idx >= options.length - 1);
69
+ arrowKeycaps.push(up, dn);
70
+ guide.append(up.el, dn.el);
71
+ };
72
+ return {
73
+ root: card.root,
74
+ card,
75
+ keycap,
76
+ open(cfg) {
77
+ const name = cfg.name ?? "";
78
+ const prompt = cfg.prompt ?? "";
79
+ nameEl.textContent = name;
80
+ nameEl.style.display = name ? "" : "none";
81
+ promptEl.textContent = prompt;
82
+ promptEl.style.display = prompt ? "" : "none";
83
+ if (prompt)
84
+ checkLen(prompt, "prompt");
85
+ options = cfg.options.slice();
86
+ onConfirm = cfg.onConfirm;
87
+ clearArrows();
88
+ listEl.textContent = "";
89
+ gutters = [];
90
+ rows = options.map((label, i) => {
91
+ checkLen(label, `option ${i}`);
92
+ const row = document.createElement("div");
93
+ row.className = "rydr-ui-choice-opt" + (showArrows ? " has-arrows" : "");
94
+ if (showArrows) {
95
+ // an empty, absolutely-positioned left gutter; render() fills only the selected row's gutter
96
+ // with the right arrows (UP unless first, DOWN unless last) — so the guide rides the highlight.
97
+ const guide = document.createElement("div");
98
+ guide.className = "rydr-ui-choice-arrows";
99
+ row.appendChild(guide);
100
+ gutters[i] = guide;
101
+ }
102
+ const lbl = document.createElement("span");
103
+ lbl.className = "rydr-ui-choice-label";
104
+ lbl.textContent = label;
105
+ row.appendChild(lbl);
106
+ listEl.appendChild(row);
107
+ return row;
108
+ });
109
+ idx = Math.min(Math.max(0, cfg.selected ?? 0), Math.max(0, options.length - 1));
110
+ render();
111
+ card.setVisible(true);
112
+ },
113
+ moveBy(delta) {
114
+ if (options.length === 0)
115
+ return idx;
116
+ idx = Math.min(Math.max(0, idx + delta), options.length - 1); // clamp at the ends
117
+ render();
118
+ return idx;
119
+ },
120
+ setIndex(i) {
121
+ if (options.length === 0)
122
+ return idx;
123
+ idx = Math.min(Math.max(0, i), options.length - 1);
124
+ render();
125
+ return idx;
126
+ },
127
+ index: () => idx,
128
+ confirm() {
129
+ const label = options[idx];
130
+ if (label == null)
131
+ return null;
132
+ onConfirm?.(idx, label);
133
+ return { index: idx, label };
134
+ },
135
+ close: () => { card.setVisible(false); clearArrows(); options = []; rows = []; idx = 0; },
136
+ setVisible: card.setVisible,
137
+ setScreenPos: card.setScreenPos,
138
+ dispose: () => { clearArrows(); card.dispose(); },
139
+ };
140
+ }
141
+ //# sourceMappingURL=choice-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"choice-card.js","sourceRoot":"","sources":["../../src/ui/choice-card.ts"],"names":[],"mappings":"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"}