ultimatedarktowerdisplay 0.1.0 → 0.2.0

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
@@ -6,6 +6,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.0] - 2026-04-15
10
+
11
+ ### Fixed
12
+
13
+ - `TowerSideView` now calls `injectStyles()` so side-view-only mode gets CSS
14
+ - JSDoc for `TowerDisplayOptions.renderers` now correctly documents the default as `['readout', 'side-view']`
15
+
16
+ ### Added
17
+
18
+ - Tests for seal overlay injection, double-dispose safety, and multi-button side selection
19
+ - `applySeals(brokenSeals: SealIdentifier[])` method on `TowerDisplay`, `TowerSideView`, and `ITowerDisplay` — hides seal SVG overlays for broken seals on the currently displayed side; re-evaluates when switching sides
20
+ - `SealIdentifier` re-exported from the package public API
21
+ - `clickToToggleSeals` option on `TowerDisplayOptions` (default `true`) — clicking a seal in the side view toggles its visibility independently of game state; user-toggled state and game-broken state are merged so either alone can hide a seal; toggle state is per-side and is cleared on `dispose()`
22
+ - `clickToToggleSeals` public property on `TowerSideView` for consumers using the class directly
23
+ - Console logging on seal click: logs the side, level, and new visibility state (or notes when toggle is disabled)
24
+
9
25
  ## [0.1.0] - 2026-03-22
10
26
 
11
27
  ### Added
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  DOM-based visual readout for decoded [Ultimate Dark Tower](https://github.com/ChessMess/ultimatedarktower) tower state.
4
4
 
5
+ **[Live Demo](https://chessmess.github.io/UltimateDarkTowerDisplay/)**
6
+
5
7
  This package renders a live tower dashboard into a DOM element. It is intended for tools that already know how to obtain or decode a `TowerState` and want a compact on-screen display of:
6
8
 
7
9
  - LED layers and per-light effects
@@ -30,13 +32,13 @@ npm install ultimatedarktowerdisplay ultimatedarktower
30
32
  ## Quick Start
31
33
 
32
34
  ```ts
33
- import { TowerDisplay } from "ultimatedarktowerdisplay";
34
- import { createDefaultTowerState } from "ultimatedarktower";
35
+ import { TowerDisplay } from 'ultimatedarktowerdisplay';
36
+ import { createDefaultTowerState } from 'ultimatedarktower';
35
37
 
36
- const container = document.getElementById("tower");
38
+ const container = document.getElementById('tower');
37
39
 
38
40
  if (!container) {
39
- throw new Error("Missing #tower container");
41
+ throw new Error('Missing #tower container');
40
42
  }
41
43
 
42
44
  const display = new TowerDisplay({ container });
@@ -72,17 +74,13 @@ In most applications the flow looks like this:
72
74
  Example with a manually adjusted state:
73
75
 
74
76
  ```ts
75
- import { TowerDisplay } from "ultimatedarktowerdisplay";
76
- import {
77
- createDefaultTowerState,
78
- LIGHT_EFFECTS,
79
- TOWER_AUDIO_LIBRARY,
80
- } from "ultimatedarktower";
77
+ import { TowerDisplay } from 'ultimatedarktowerdisplay';
78
+ import { createDefaultTowerState, LIGHT_EFFECTS, TOWER_AUDIO_LIBRARY } from 'ultimatedarktower';
81
79
 
82
- const container = document.getElementById("tower");
80
+ const container = document.getElementById('tower');
83
81
 
84
82
  if (!container) {
85
- throw new Error("Missing #tower container");
83
+ throw new Error('Missing #tower container');
86
84
  }
87
85
 
88
86
  const display = new TowerDisplay({ container });
@@ -133,7 +131,7 @@ type TowerState = {
133
131
  For a valid starting point, prefer:
134
132
 
135
133
  ```ts
136
- import { createDefaultTowerState } from "ultimatedarktower";
134
+ import { createDefaultTowerState } from 'ultimatedarktower';
137
135
 
138
136
  const state = createDefaultTowerState();
139
137
  ```
@@ -151,36 +149,58 @@ The example page in [example/index.html](./example/index.html) follows this patt
151
149
 
152
150
  ### `TowerDisplay`
153
151
 
154
- Primary entry point. Wraps `TowerStateReadout` with an options object.
152
+ Primary entry point. Composes one or both renderers into a container.
155
153
 
156
154
  ```ts
157
155
  new TowerDisplay(options: TowerDisplayOptions)
158
156
  ```
159
157
 
160
- | Option | Type | Description |
161
- | ----------- | ------------- | -------------------------------------------------- |
162
- | `container` | `HTMLElement` | DOM element that will receive the rendered readout |
158
+ | Option | Type | Default | Description |
159
+ | -------------------- | -------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------- |
160
+ | `container` | `HTMLElement` | | DOM element that will receive the rendered output |
161
+ | `renderers` | `RendererType \| RendererType[]` | `['readout', 'side-view']` | Which renderer(s) to show: `'readout'`, `'side-view'`, or both |
162
+ | `onSealClick` | `(seal: SealIdentifier) => void` | — | Called whenever the user clicks a seal overlay in the side view |
163
+ | `clickToToggleSeals` | `boolean` | `true` | When true, clicking a seal toggles its visibility independent of game state. Set to `false` to disable. |
163
164
 
164
165
  Methods:
165
166
 
166
- - `applyState(state: TowerState): void` updates the readout with a decoded tower state
167
- - `showIdle(): void` replaces the readout with the idle placeholder
168
- - `dispose(): void` clears the container and resets internal state
167
+ - `applyState(state: TowerState): void` update all renderers with a new decoded tower state
168
+ - `applySeals(brokenSeals: SealIdentifier[]): void` hide seal overlays for broken seals; pass the current list of broken seals each time it changes
169
+ - `showIdle(): void` reset all renderers to their idle placeholder
170
+ - `dispose(): void` — remove all rendered DOM and reset internal state
171
+
172
+ ### `TowerSideView`
173
+
174
+ SVG side-view renderer. Shows a rotatable view of one tower face with seal overlays and LED markers. Can be used standalone or composed via `TowerDisplay`.
175
+
176
+ ```ts
177
+ const view = new TowerSideView(container);
178
+ view.onSealClick = (seal) => console.log(seal);
179
+ view.clickToToggleSeals = true; // default
180
+ ```
181
+
182
+ | Property | Type | Default | Description |
183
+ | -------------------- | -------------------------------- | ------- | ------------------------------------------------------------ |
184
+ | `onSealClick` | `(seal: SealIdentifier) => void` | — | Callback fired on every seal click |
185
+ | `clickToToggleSeals` | `boolean` | `true` | Enables built-in click-to-toggle visibility on seal overlays |
169
186
 
170
187
  ### `TowerStateReadout`
171
188
 
172
- Lower-level renderer for callers that want to pass the container directly.
189
+ Text-based readout renderer. Lower-level; takes a container directly.
173
190
 
174
191
  ```ts
175
192
  new TowerStateReadout(container: HTMLElement)
176
193
  ```
177
194
 
178
- It exposes the same methods as `TowerDisplay`.
195
+ Exposes the same methods as `TowerDisplay` (`applyState`, `applySeals`, `showIdle`, `dispose`).
179
196
 
180
197
  ### Exported Types
181
198
 
182
- - `ITowerDisplay` common interface implemented by both classes
183
- - `TowerDisplayOptions` configuration object for `TowerDisplay`
199
+ - `ITowerDisplay` common interface implemented by all renderers
200
+ - `TowerDisplayOptions` configuration object for `TowerDisplay`
201
+ - `RendererType` — `'readout' | 'side-view'`
202
+ - `TowerSide` — `'north' | 'east' | 'south' | 'west'`
203
+ - `SealIdentifier` — `{ side: TowerSide, level: TowerLevels }`
184
204
 
185
205
  `ultimatedarktowerdisplay` does not re-export tower protocol constants or helpers from `ultimatedarktower`.
186
206
 
@@ -1,4 +1,4 @@
1
- import type { TowerState } from 'ultimatedarktower';
1
+ import type { TowerState, SealIdentifier } from 'ultimatedarktower';
2
2
  import type { TowerDisplayOptions, ITowerDisplay } from './types';
3
3
  /**
4
4
  * TowerDisplay renders decoded tower state into a DOM container.
@@ -7,15 +7,19 @@ import type { TowerDisplayOptions, ITowerDisplay } from './types';
7
7
  * ```ts
8
8
  * const display = new TowerDisplay({
9
9
  * container: document.getElementById('tower')!,
10
+ * renderers: ['readout', 'side-view'],
10
11
  * });
11
12
  * display.applyState(state);
12
13
  * ```
13
14
  */
14
15
  export declare class TowerDisplay implements ITowerDisplay {
15
- private readonly impl;
16
+ private readonly renderers;
17
+ private readonly root;
16
18
  constructor(options: TowerDisplayOptions);
17
19
  /** Update the display with a new decoded tower state. */
18
20
  applyState(state: TowerState): void;
21
+ /** Update seal visibility — pass the current list of broken seals. */
22
+ applySeals(brokenSeals: SealIdentifier[]): void;
19
23
  /** Reset the display to its idle/waiting state. */
20
24
  showIdle(): void;
21
25
  /** Remove all rendered DOM content and reset internal state. */
@@ -0,0 +1,32 @@
1
+ import { type TowerState, type SealIdentifier } from 'ultimatedarktower';
2
+ import type { ITowerDisplay } from './types';
3
+ export declare class TowerSideView implements ITowerDisplay {
4
+ private readonly container;
5
+ private wrapper;
6
+ private currentSide;
7
+ private latestState;
8
+ private buttons;
9
+ private ledNodes;
10
+ private sealNodes;
11
+ private latestBrokenSeals;
12
+ /** Optional callback fired when a user clicks a seal overlay. */
13
+ onSealClick?: (seal: SealIdentifier) => void;
14
+ /**
15
+ * When true (the default), clicking a seal toggles its visibility independently
16
+ * of game state. Set to false to disable the built-in toggle.
17
+ */
18
+ clickToToggleSeals: boolean;
19
+ private userToggledSeals;
20
+ constructor(container: HTMLElement);
21
+ applyState(state: TowerState): void;
22
+ applySeals(brokenSeals: SealIdentifier[]): void;
23
+ showIdle(): void;
24
+ dispose(): void;
25
+ private detectSealSide;
26
+ private updateSealVisibility;
27
+ private build;
28
+ private injectSeals;
29
+ private cacheLedNodes;
30
+ private applyLedState;
31
+ private selectSide;
32
+ }
@@ -1,5 +1,5 @@
1
1
  import { type TowerState } from 'ultimatedarktower';
2
- import type { ITowerDisplay } from './types';
2
+ import type { ITowerDisplay, SealIdentifier } from './types';
3
3
  /**
4
4
  * Core DOM renderer for tower state.
5
5
  *
@@ -18,6 +18,7 @@ export declare class TowerStateReadout implements ITowerDisplay {
18
18
  constructor(container: HTMLElement);
19
19
  /** Update the display with a new decoded tower state. */
20
20
  applyState(state: TowerState): void;
21
+ applySeals(_brokenSeals: SealIdentifier[]): void;
21
22
  /** Reset the display to its idle/waiting state. */
22
23
  showIdle(): void;
23
24
  /** Remove all rendered DOM content and reset internal state. */
@@ -0,0 +1 @@
1
+ export declare const EFFECT_LABELS: Record<number, string>;