wuira 0.1.7 → 0.1.9

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
@@ -7,6 +7,53 @@ with the release date and highlight breaking changes explicitly.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.1.9 — 2026-10-15
11
+
12
+ ### Added
13
+
14
+ - Generic `Encoding.Encode` / `Decode` using native CBOR, compression and Base64,
15
+ with decode size checks. Import formats and schema validation stay in applications.
16
+ - Native `ScrollHost` for clipped, mouse-wheel scrolling inside mounted Cells.
17
+ - Native `TextInput` hosts for copy/paste and text entry inside mounted Cells,
18
+ including focus selection, Escape callbacks and cleanup.
19
+
20
+ ### Fixed
21
+
22
+ - `TextInput` passes the required font flags to Forever's native EditBox API.
23
+ - `Window.Open` retains its current content if a replacement fails to render or
24
+ mount. Header cleanup only stops a drag when the window is actually moving.
25
+
26
+ ## 0.1.8 — 2026-10-14
27
+
28
+ ### Added
29
+
30
+ - Standalone `Window` hosts with draggable headers, Escape-to-close, content
31
+ cleanup, and protected-content close deferral.
32
+ - Frameless `ForEach` JSX expansion with typed render children and nested groups.
33
+ Cell's single-child rule is enforced after expansion; lists are snapshots.
34
+ - `Cell.condition` accepts a boolean or visibility callback, combines with existing
35
+ `visible`, and respects protected-tree combat deferral. Secret conditions hide cells.
36
+ - `KnownSpells.Filter(pattern)` matches cached localized names with whole-name
37
+ `*`/`?` globs and ASCII case-insensitive comparisons, returning detached metadata.
38
+ - Per-line `FlowLayout.justify` alignment (`start`, `center`, or `end`), including
39
+ wrapped rows and vertical columns.
40
+ - Cached `KnownSpells.Entries` metadata and rank-aware `Find`, including localized
41
+ names and native passive flags. Metadata changes also notify observers.
42
+ - Named Paladin Blessing, Greater Blessing, and Aura spell identifiers.
43
+
44
+ ### Fixed
45
+
46
+ - Layout children accept mixed JSX siblings and nested arrays with ordered
47
+ expansion and cycle checks.
48
+
49
+ ### Validation and limitations
50
+
51
+ - Lua and package tests cover loop expansion, typed render children, visibility,
52
+ cached name filtering, alignment, window cleanup, and protected close deferral.
53
+ - ForEach expands a snapshot when its view is created. Applications own event
54
+ subscriptions and rebuild protected views outside combat. Serialized expression
55
+ parsing and the visual editor are application features.
56
+
10
57
  ## 0.1.7 — 2026-10-14
11
58
 
12
59
  ### Added
package/README.md CHANGED
@@ -168,6 +168,21 @@ validate required arguments inside your callback.
168
168
 
169
169
  ### Addon settings and editing tools
170
170
 
171
+ Create a standalone window with mounted JSX content. Bind a header as its drag
172
+ handle; Escape or `Close()` releases the content while preserving the host's
173
+ screen position for the next opening:
174
+
175
+ ```tsx
176
+ import { Window, FlowLayout, Label } from "wuira/ui";
177
+
178
+ const window = new Window({ width: 1000, height: 680 });
179
+ window.Open(() => (
180
+ <FlowLayout onMount={frame => window.BindDragHandle(frame)}>
181
+ <Label height={40} text="My addon" />
182
+ </FlowLayout>
183
+ ));
184
+ ```
185
+
171
186
  Register a normal JSX view in Forever's native addon settings list:
172
187
 
173
188
  ```tsx
@@ -206,14 +221,65 @@ import { KnownSpells } from "wuira/datasources";
206
221
  const known = new KnownSpells();
207
222
  const spellIds = known.Value; // readonly number[], read from the cache
208
223
  const canJudge = known.Has(Spells.Judgement);
224
+ const activeSpells = known.Entries.filter(spell => spell.isPassive === false);
225
+ const learnedRank = known.Find(Spells.Judgement); // { id, name, isPassive } or undefined
226
+ const seals = known.Filter("Seal*"); // Detached matching entries, including learned ranks.
209
227
  const stop = known.onChange(ids => console.log("Learned spells:", ids.length));
210
228
  // On disposal: stop(); known.Dispose();
211
229
  ```
212
230
 
231
+ `Entries` returns detached cached metadata. An unknown passive flag stays
232
+ `undefined`; unreadable spellbooks return no entries. Metadata changes also notify
233
+ `onChange` observers, even when the spell IDs have not changed.
234
+
235
+ `Filter` matches the entire localized name. `*` matches any sequence and `?` one
236
+ byte; matching ignores ASCII case. Filtering reads the cache without polling the
237
+ spellbook. Patterns must use names in the client's language.
238
+
213
239
  Protected button trees must be rebuilt outside combat; applications can defer a
214
240
  changed selection until `PLAYER_REGEN_ENABLED`. `Cell.alpha` changes appearance
215
241
  without changing a button's protected action or its interaction state.
216
242
 
243
+ For wrapped action rows, `FlowLayout` supports `grow` and per-line `justify`:
244
+
245
+ ```tsx
246
+ <FlowLayout width={220} height={52} direction="horizontal" gap={4} grow justify="center">
247
+ {buttons /* 24-unit buttons wrap after eight; the last row is centered. */}
248
+ </FlowLayout>
249
+ ```
250
+
251
+ Use `ForEach` to expand a snapshot into layout children without wrapper frames:
252
+
253
+ ```tsx
254
+ import { UI } from "wuira";
255
+ import { FlowLayout, ForEach, SecureButton } from "wuira/ui";
256
+
257
+ function SealButtons() {
258
+ return (
259
+ <FlowLayout width={220} height={24} direction="horizontal" gap={4}>
260
+ <ForEach items={known.Filter("Seal*")}>
261
+ {(spell, index) => (
262
+ <SecureButton width={24} height={24}
263
+ action={{ type: "spell", spell: spell.id }} condition={spell.isPassive === false} />
264
+ )}
265
+ </ForEach>
266
+ </FlowLayout>
267
+ );
268
+ }
269
+ ```
270
+
271
+ The render function receives the item and its zero-based index. Use `ForEach`
272
+ inside a layout for multiple siblings; Cell still accepts at most one expanded
273
+ child. Nested loops are flattened in order. `ForEach` creates no subscriptions:
274
+ recreate the view from `known.onChange` when needed, outside combat for protected
275
+ trees. Each filtered rank remains a separate entry.
276
+
277
+ All visual cells accept `condition={boolean}` or `condition={() => boolean}`.
278
+ This controls visibility and retains the layout slot. It combines with datasource
279
+ activity and the existing `visible` predicate. Callbacks use the shared visibility
280
+ update driver; protected changes wait until combat ends. Secret conditions hide
281
+ the cell without inspecting their value.
282
+
217
283
  ### Empty native action bars
218
284
 
219
285
  This is opt-in and removes real action-slot assignments, not just their visuals.
package/lib/Spells.d.ts CHANGED
@@ -9,6 +9,26 @@ export declare enum Spells {
9
9
  LayOnHands = 633,
10
10
  BlessingOfProtection = 1022,
11
11
  BlessingOfFreedom = 1044,
12
+ BlessingOfMight = 19740,
13
+ BlessingOfWisdom = 19742,
14
+ BlessingOfKings = 20217,
15
+ BlessingOfLight = 19977,
16
+ BlessingOfSalvation = 1038,
17
+ BlessingOfSanctuary = 20911,
18
+ BlessingOfSacrifice = 6940,
19
+ GreaterBlessingOfMight = 25782,
20
+ GreaterBlessingOfWisdom = 25894,
21
+ GreaterBlessingOfKings = 25898,
22
+ GreaterBlessingOfLight = 25890,
23
+ GreaterBlessingOfSalvation = 25895,
24
+ GreaterBlessingOfSanctuary = 25899,
25
+ DevotionAura = 465,
26
+ RetributionAura = 7294,
27
+ ConcentrationAura = 19746,
28
+ ShadowResistanceAura = 19876,
29
+ FrostResistanceAura = 19888,
30
+ FireResistanceAura = 19891,
31
+ SanctityAura = 20218,
12
32
  VoiceOfTruth = 1310897,
13
33
  HammerOfJustice = 853,
14
34
  SealOfRighteousness = 21084,
package/lib/Spells.lua CHANGED
@@ -20,6 +20,46 @@ ____exports.Spells.BlessingOfProtection = 1022
20
20
  ____exports.Spells[____exports.Spells.BlessingOfProtection] = "BlessingOfProtection"
21
21
  ____exports.Spells.BlessingOfFreedom = 1044
22
22
  ____exports.Spells[____exports.Spells.BlessingOfFreedom] = "BlessingOfFreedom"
23
+ ____exports.Spells.BlessingOfMight = 19740
24
+ ____exports.Spells[____exports.Spells.BlessingOfMight] = "BlessingOfMight"
25
+ ____exports.Spells.BlessingOfWisdom = 19742
26
+ ____exports.Spells[____exports.Spells.BlessingOfWisdom] = "BlessingOfWisdom"
27
+ ____exports.Spells.BlessingOfKings = 20217
28
+ ____exports.Spells[____exports.Spells.BlessingOfKings] = "BlessingOfKings"
29
+ ____exports.Spells.BlessingOfLight = 19977
30
+ ____exports.Spells[____exports.Spells.BlessingOfLight] = "BlessingOfLight"
31
+ ____exports.Spells.BlessingOfSalvation = 1038
32
+ ____exports.Spells[____exports.Spells.BlessingOfSalvation] = "BlessingOfSalvation"
33
+ ____exports.Spells.BlessingOfSanctuary = 20911
34
+ ____exports.Spells[____exports.Spells.BlessingOfSanctuary] = "BlessingOfSanctuary"
35
+ ____exports.Spells.BlessingOfSacrifice = 6940
36
+ ____exports.Spells[____exports.Spells.BlessingOfSacrifice] = "BlessingOfSacrifice"
37
+ ____exports.Spells.GreaterBlessingOfMight = 25782
38
+ ____exports.Spells[____exports.Spells.GreaterBlessingOfMight] = "GreaterBlessingOfMight"
39
+ ____exports.Spells.GreaterBlessingOfWisdom = 25894
40
+ ____exports.Spells[____exports.Spells.GreaterBlessingOfWisdom] = "GreaterBlessingOfWisdom"
41
+ ____exports.Spells.GreaterBlessingOfKings = 25898
42
+ ____exports.Spells[____exports.Spells.GreaterBlessingOfKings] = "GreaterBlessingOfKings"
43
+ ____exports.Spells.GreaterBlessingOfLight = 25890
44
+ ____exports.Spells[____exports.Spells.GreaterBlessingOfLight] = "GreaterBlessingOfLight"
45
+ ____exports.Spells.GreaterBlessingOfSalvation = 25895
46
+ ____exports.Spells[____exports.Spells.GreaterBlessingOfSalvation] = "GreaterBlessingOfSalvation"
47
+ ____exports.Spells.GreaterBlessingOfSanctuary = 25899
48
+ ____exports.Spells[____exports.Spells.GreaterBlessingOfSanctuary] = "GreaterBlessingOfSanctuary"
49
+ ____exports.Spells.DevotionAura = 465
50
+ ____exports.Spells[____exports.Spells.DevotionAura] = "DevotionAura"
51
+ ____exports.Spells.RetributionAura = 7294
52
+ ____exports.Spells[____exports.Spells.RetributionAura] = "RetributionAura"
53
+ ____exports.Spells.ConcentrationAura = 19746
54
+ ____exports.Spells[____exports.Spells.ConcentrationAura] = "ConcentrationAura"
55
+ ____exports.Spells.ShadowResistanceAura = 19876
56
+ ____exports.Spells[____exports.Spells.ShadowResistanceAura] = "ShadowResistanceAura"
57
+ ____exports.Spells.FrostResistanceAura = 19888
58
+ ____exports.Spells[____exports.Spells.FrostResistanceAura] = "FrostResistanceAura"
59
+ ____exports.Spells.FireResistanceAura = 19891
60
+ ____exports.Spells[____exports.Spells.FireResistanceAura] = "FireResistanceAura"
61
+ ____exports.Spells.SanctityAura = 20218
62
+ ____exports.Spells[____exports.Spells.SanctityAura] = "SanctityAura"
23
63
  ____exports.Spells.VoiceOfTruth = 1310897
24
64
  ____exports.Spells[____exports.Spells.VoiceOfTruth] = "VoiceOfTruth"
25
65
  ____exports.Spells.HammerOfJustice = 853
@@ -1,19 +1,31 @@
1
1
  import { DataSource } from "./DataSource";
2
2
  export type KnownSpellsStatus = "ready" | "unavailable" | "restricted";
3
+ export interface KnownSpell {
4
+ readonly id: number;
5
+ readonly name?: string;
6
+ /** Undefined when the client cannot determine whether this spell is passive. */
7
+ readonly isPassive?: boolean;
8
+ }
3
9
  /** Cached learned player spellbook IDs. Native events refresh the snapshot; no polling. */
4
10
  export declare class KnownSpells extends DataSource<readonly number[]> {
5
11
  private ids;
6
- private names;
12
+ private entries;
7
13
  private status;
8
14
  private readonly listeners;
9
15
  private readonly frame;
10
16
  private disposed;
11
17
  constructor();
12
18
  get Status(): KnownSpellsStatus;
19
+ /** Detached cached metadata, with no native reads. Unreadable books return no entries. */
20
+ get Entries(): readonly KnownSpell[];
21
+ /** Whole-name glob over cached localized names. Supports * and ?, ignoring ASCII case. */
22
+ Filter(pattern: string): readonly KnownSpell[];
13
23
  protected GetValue(): readonly number[];
14
24
  protected HasSecretFields(): boolean;
15
25
  /** Representative enum IDs match any learned rank through its localized name. */
16
26
  Has(spell: number | string): boolean;
27
+ /** Resolve a representative spell ID or localized name to a learned rank. */
28
+ Find(spell: number | string): KnownSpell | undefined;
17
29
  /** No initial emission; only changed, readable snapshots reach observers. */
18
30
  onChange(listener: (spells: readonly number[]) => void): () => void;
19
31
  private Refresh;