wuira 0.2.3 → 0.2.5

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 (38) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +62 -792
  3. package/data/icons/forever.json +90051 -0
  4. package/docs/icons.md +28 -0
  5. package/docs/source-converters.md +34 -0
  6. package/lib/datasources/DataSource.d.ts +8 -0
  7. package/lib/datasources/DataSource.lua +7 -0
  8. package/lib/datasources/GroupMembers.d.ts +6 -0
  9. package/lib/datasources/GroupMembers.lua +332 -0
  10. package/lib/datasources/GroupRoster.d.ts +28 -0
  11. package/lib/datasources/GroupRoster.lua +611 -0
  12. package/lib/datasources/MathConverter.d.ts +6 -0
  13. package/lib/datasources/MathConverter.lua +61 -0
  14. package/lib/datasources/MathExpression.d.ts +16 -0
  15. package/lib/datasources/MathExpression.lua +281 -0
  16. package/lib/datasources/Resources.d.ts +3 -2
  17. package/lib/datasources/Resources.lua +57 -48
  18. package/lib/datasources/SourceConverter.d.ts +39 -0
  19. package/lib/datasources/SourceConverter.lua +568 -0
  20. package/lib/datasources/UnitInfo.d.ts +6 -0
  21. package/lib/datasources/UnitInfo.lua +347 -15
  22. package/lib/datasources/UnitSource.d.ts +8 -3
  23. package/lib/datasources/UnitSource.lua +318 -5
  24. package/lib/datasources/index.d.ts +7 -1
  25. package/lib/datasources/index.lua +15 -0
  26. package/lib/icons/index.d.ts +45634 -0
  27. package/lib/icons/index.lua +3 -0
  28. package/lib/index.d.ts +1 -0
  29. package/lib/store/index.lua +20 -18
  30. package/lib/ui/Cell.d.ts +2 -0
  31. package/lib/ui/ForEach.d.ts +18 -4
  32. package/lib/ui/ForEach.lua +763 -13
  33. package/lib/ui/index.d.ts +1 -1
  34. package/package.json +20 -4
  35. package/tools/build-library.mjs +1 -1
  36. package/tools/generate-icons.mjs +10 -0
  37. package/tools/icon-data.mjs +59 -0
  38. package/tools/update-icons.mjs +36 -0
package/README.md CHANGED
@@ -1,22 +1,14 @@
1
- # wuira
2
-
3
- Build WoW Forever interfaces with TypeScript and JSX.
4
-
5
- wuira is a UI framework and addon toolchain for developers who want to describe their interface in code, compose reusable components, and keep game data separate from presentation. Write TypeScript, arrange native widgets with JSX, and compile your addon to Lua.
6
1
 
7
- ## Why wuira?
8
2
 
9
- An interface should be easy to understand by reading its source. A health display can be a progress bar inside a layout; a player display can be a composition of bars, labels, and icons rather than a special unit-frame class. wuira provides the foundations for that approach, with TypeScript checking your component props before the addon reaches the game.
10
-
11
- The snippets below use the lowercase JSX primitives. In a generated project, import `{ UI, Wuira }` from `wuira` before mounting a view. Each view example is independent. Wuira is a static accessor; Wuira.App is the current addon's WuiraApp instance and owns its mounted trees and cleanup. Separate addons keep independent application state. The lowercase wuira export remains available for existing addons.
12
-
13
- ### JSX composition
3
+ # wuira
14
4
 
15
- <!-- Screenshot: a reusable ResourceBar component inside a Cell. -->
5
+ Build WoW Forever interfaces with TypeScript and JSX.
16
6
 
17
- Describe UI trees with components and reusable functions. No separate XML view files are required.
7
+ wuira is a UI framework and addon toolchain for developers who want to describe interfaces in code, compose reusable components, and keep game data separate from presentation. Write TypeScript, arrange native widgets with JSX, and compile your addon to Lua.
18
8
 
19
9
  ```tsx
10
+ import { UI, wuira } from "wuira";
11
+
20
12
  function ResourceBar({ current }: { current: number }) {
21
13
  return <progress width="inherit" height={24} min={0} max={100} current={current} />;
22
14
  }
@@ -29,636 +21,26 @@ function App() {
29
21
  );
30
22
  }
31
23
 
32
- Wuira.Mount(App);
33
- ```
34
-
35
- ### Native widgets
36
-
37
- <!-- Screenshot: a native StatusBar with a dark background. -->
38
-
39
- Cells mount to WoW frames, and progress bars use the game's native StatusBar widget.
40
-
41
- ```tsx
42
- const view = (
43
- <progress
44
- width={200} height={24}
45
- min={0} max={100} current={65}
46
- foreground={{ color: { r: 0.1, g: 0.75, b: 0.4 } }}
47
- background={{ color: { r: 0.08, g: 0.08, b: 0.08 } }}
48
- />
49
- );
50
-
51
- Wuira.Mount(view);
52
- ```
53
-
54
- ### Layout primitives
55
-
56
- <!-- Screenshot: two vertically stacked progress bars with a gap. -->
57
-
58
- Flow and grid layouts arrange children, with spacing and sizing options shared across components.
59
-
60
- ```tsx
61
- const view = (
62
- <flow width={200} height={56} direction="vertical" gap={8}>
63
- <progress height={24} min={0} max={100} current={80} />
64
- <progress height={24} min={0} max={100} current={45} />
65
- </flow>
66
- );
67
-
68
- Wuira.Mount(view);
69
- ```
70
-
71
- For rows and columns, use `<grid columns={2} gap={8}>` with an explicit width and height.
72
-
73
- ### Data sources
74
-
75
- <!-- Screenshot: live player health and mana bars. -->
76
-
77
- Connect health and mana values to progress bars without writing a separate resource-specific container class.
78
-
79
- ```tsx
80
- import { HP, Mana, Unit } from "wuira/datasources";
81
-
82
- function App() {
83
- const hp = new HP(Unit.Player);
84
- const mana = new Mana(Unit.Player);
85
-
86
- return (
87
- <flow width={200} height={52} direction="vertical" gap={4}>
88
- <progress height={24} source={hp} />
89
- <progress height={24} source={mana} />
90
- </flow>
91
- );
92
- }
93
-
94
- Wuira.Mount(App);
95
- ```
96
-
97
- ### Conditional sources and health predictions
98
-
99
- ```tsx
100
- import { UI } from "wuira";
101
- import { ProgressBar } from "wuira/ui";
102
- import { HP, Unit } from "wuira/datasources";
103
-
104
- const hp = new HP(Unit.Player)
105
- .UseHealPrediction()
106
- .UseAbsorbPrediction()
107
- .When(value => value >= 20); // Current health points, not a percentage.
108
-
109
- const view = (
110
- <ProgressBar width={220} height={24} source={hp}
111
- healPrediction={{ texture: "blizzard", color: { r: 0.4, g: 1, b: 0.5, a: 0.5 } }}
112
- absorbPrediction={{ texture: "solid", color: { r: 0.8, g: 0.9, b: 1, a: 0.55 } }} />
113
- );
114
- ```
115
-
116
- Configure sources before mounting. `.When()` filters native binding updates using
117
- the source's own value; repeated calls combine with AND. A false condition hides
118
- the bound bar and suppresses writes until a relevant event makes it true again.
119
- The basic health events remain subscribed so the source can reactivate; there is
120
- no health polling. Predicates should depend on this source's event-driven state,
121
- not unrelated mutable values. `.Value` remains a fresh, checked snapshot rather
122
- than a filtered subscription. Secret values never reach condition callbacks:
123
- a conditional binding becomes inactive until readable data returns. Without a
124
- condition, opaque values continue directly to native widgets.
125
-
126
- Each prediction channel is allocated and its event registered only when explicitly
127
- enabled and supported. No prediction snapshots are cached. Access
128
- `hp.HealPrediction?.Value` and `hp.AbsorbPrediction?.Value` when readable, or check
129
- the channel's `IsAvailable` flag. Direct reads retain the usual secret-value guard.
130
- The APIs provide incoming healing and existing damage-absorb shields, not a
131
- forecast of future damage. The adapter uses
132
- [Blizzard's unit prediction APIs](https://github.com/Gethe/wow-ui-source/blob/live/Interface/AddOns/Blizzard_APIDocumentationGenerated/UnitDocumentation.lua).
133
-
134
- Enabled channels draw by default; `healPrediction={false}` or
135
- `absorbPrediction={false}` suppresses an individual overlay. Incoming healing starts
136
- at the native health-fill edge and clips at the bar boundary. Shields overlay
137
- inward from the opposite edge, remaining visible even at full health. Both accept
138
- independent textures/colors and support horizontal, vertical, and reversed bars.
139
- There is no arithmetic on secret health or prediction amounts. Raw incoming heals
140
- are not reduced by heal-absorb debuffs; overheal is visually clipped. Prediction
141
- overlays require the native health scale, so min/max/current overrides are rejected.
142
- Prediction reads pause while the parent HP condition is inactive. The renderer
143
- cleans up the overlays and resource subscriptions on unmount.
144
-
145
- ### Slash commands
146
-
147
- ```ts
148
- import { Wuira } from "wuira";
149
-
150
- const unregister = Wuira.registerCommand("/myaddon", (...args: string[]) => {
151
- console.log("Arguments:", ...args);
152
- });
153
-
154
- // /myaddon arg1 arg2 arg3 passes three string arguments.
155
- // /myaddon alone calls the callback with no arguments.
156
- // Call unregister() when the command is no longer needed.
157
- ```
158
-
159
- Command names start with a slash and a letter, followed by letters, digits,
160
- underscores or hyphens. Names are case-insensitive and cannot replace an existing
161
- command. Register aliases separately with the same callback.
162
-
163
- Arguments are separated by whitespace and retain their case. Single or double
164
- quotes group words into one argument; `""` passes an empty string. Within quotes,
165
- a backslash can escape the matching quote or another backslash. An unclosed quote
166
- reports an error without calling the callback. Argument counts are unrestricted;
167
- validate required arguments inside your callback.
168
-
169
- ### Addon settings and editing tools
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
-
186
- Register a normal JSX view in Forever's native addon settings list:
187
-
188
- ```tsx
189
- import { UI, AddonSettings } from "wuira";
190
- import { FlowLayout, Button, Label } from "wuira/ui";
191
-
192
- const settings = new AddonSettings("MyAddon", () => (
193
- <FlowLayout width={560} height={300} padding={20} gap={12}>
194
- <Label height={28} text="MyAddon settings" />
195
- <Button width={180} height={32} onClick={() => console.log("Settings opened")}>
196
- <Label text="Example control" align="CENTER" />
197
- </Button>
198
- </FlowLayout>
199
- ));
200
- settings.Open();
201
- ```
202
-
203
- `Cell.onMount` receives the configured native frame and may return an unmount
204
- cleanup. `FrameMover` provides a drag proxy with screen-center coordinates
205
- (positive y down). `KeybindCapture` provides hover-to-bind overlays, modifier keys,
206
- Delete/Backspace clearing, and an Escape callback. These tools stop editing in
207
- combat. Addons own mode controls, saved positions, binding conflict policy and
208
- rebuilding protected buttons outside combat.
209
-
210
- ### Learned spells
211
-
212
- `KnownSpells` keeps a cached snapshot of learned player spellbook IDs. It refreshes
213
- on world entry, level, training and talent events without polling. Named entries
214
- in `Spells` are numeric IDs; `Has` also matches a representative ID against learned
215
- ranks by localized name. Future and inactive-spec spellbook entries are excluded.
216
-
217
- ```ts
218
- import { Spells } from "wuira";
219
- import { KnownSpells } from "wuira/datasources";
220
-
221
- const known = new KnownSpells();
222
- const spellIds = known.Value; // readonly number[], read from the cache
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.
227
- const stop = known.onChange(ids => console.log("Learned spells:", ids.length));
228
- // On disposal: stop(); known.Dispose();
229
- ```
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
-
239
- Protected button trees must be rebuilt outside combat; applications can defer a
240
- changed selection until `PLAYER_REGEN_ENABLED`. `Cell.alpha` changes appearance
241
- without changing a button's protected action or its interaction state.
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
-
283
- ### Empty native action bars
284
-
285
- This is opt-in and removes real action-slot assignments, not just their visuals.
286
- Declare a character-specific backup variable in the addon's TOC:
287
-
288
- ```text
289
- ## SavedVariablesPerCharacter: MyAddonActionBarBackup
290
- ```
291
-
292
- ```ts
293
- import { ActionBars } from "wuira";
294
-
295
- ActionBars.InstallEmptyMode({
296
- addon: "MyAddon",
297
- variable: "MyAddonActionBarBackup",
298
- clearCommand: "/myaddonclearbars",
299
- restoreCommand: "/myaddonrestorebars",
300
- });
301
- ```
302
-
303
- The first world entry captures supported spell, item, and macro assignments.
304
- Use `/reload` to persist a new backup, then `/myaddonclearbars` outside combat to
305
- clear the 180 action slots used by the installed Forever implementation. Slot
306
- changes run directly from the command, never from world-entry or cursor events.
307
- Independently, initialization hides stock bars, removes their descendant event/update handlers, disables their
308
- shared action-bar event managers, and detaches native action UI registrations.
309
- It does not create a replacement bar or remove key-binding assignments.
310
- Changes wait until combat ends, and a busy cursor blocks slot changes.
311
-
312
- The restore command puts assignments back into empty or identical slots. Use
313
- `/reload` afterwards to restore native handlers and visibility. The stock bars
314
- remain enabled on future loads. A refused slot operation leaves bar suppression
315
- active and is not automatically retried. Blocked/forbidden native calls report
316
- their API name in the local console; command execution does not bypass client restrictions.
317
- Unsupported action types, secret slot data, uncovered assignments, and conflicting
318
- restores stop the operation. `Capture()`, `Clear(backup)`, and `Restore(backup)` are
319
- also available for applications that manage their own persistence and lifecycle.
320
-
321
- ### Secure action buttons
322
-
323
- `SecureButton` extends Cell's descriptor and props, including size, positioning,
324
- decorations and one child. Mount it like any other component:
325
-
326
- ```tsx
327
- import { UI, Wuira } from "wuira";
328
- import { SecureButton, Label } from "wuira/ui";
329
-
330
- Wuira.Mount(() => (
331
- <SecureButton width={80} height={24}
332
- action={{ type: "spell", spell: 635, unit: "player" }}>
333
- <Label text="Holy Light" align="CENTER" />
334
- </SecureButton>
335
- ));
336
- ```
337
-
338
- Supported actions are `{ type: "spell", spell, unit? }`,
339
- `{ type: "item", item, unit? }`, `{ type: "macro", macro }`,
340
- `{ type: "action", slot, unit? }` and `{ type: "target", unit }`.
341
- Spell and macro references accept IDs/indices or names. Items accept an ID
342
- (converted to `item:ID`) or the native item string. Macros refer to existing
343
- game macros, not arbitrary Lua callbacks or generated macro text.
344
-
345
- The default is left-button release; `button="RightButton"` selects another
346
- button. Secure buttons always receive mouse input, while visual children remain
347
- click-through by default. Omitting `action` creates an inert secure button.
348
-
349
- Actions and units are static mount-time attributes. Changing the descriptor
350
- later does not rewrite them. Mounting a protected tree during combat raises an
351
- error before any frame is created. Visibility changes and unmount requests wait
352
- until combat ends, including changes to ancestors containing secure buttons.
353
- Click-cast presets are not provided by this primitive yet. The native action mapping
354
- follows [Forever's secure templates](https://github.com/Gethe/wow-ui-source/blob/forever/Interface/AddOns/Blizzard_FrameXML/SecureTemplates.lua).
355
-
356
- For an icon action with a radial cooldown and temporary key binding:
357
-
358
- ```tsx
359
- import { Spells } from "wuira";
360
- import { SpellSource } from "wuira/datasources";
361
- import { Icon } from "wuira/ui";
362
-
363
- const judgement = new SpellSource(Spells.Judgement);
364
- const button = (
365
- <SecureButton width={40} height={40} action={judgement.Action}
366
- binding="1" cooldown={judgement.Cooldown} finishGlow>
367
- <Icon textureSource={judgement.Texture} />
368
- </SecureButton>
369
- );
370
- ```
371
-
372
- `binding` temporarily routes the key to this native button and adds its label;
373
- `showHotkey={false}` suppresses the label. Unmount removes the override and leaves
374
- saved bindings intact. The radial widget owns countdown timing and the short
375
- completion flash, including opaque combat durations. `SpellSource.Cooldown`
376
- ignores the GCD so unrelated casts do not trigger a completion flash.
377
-
378
- ### Native skins
379
-
380
- Skin Forever's existing character, professions, spellbook and other micro-menu
381
- buttons while keeping the game's click handlers, tooltips and notifications:
382
-
383
- ```tsx
384
- import { UI } from "wuira";
385
- import { MicroBar } from "wuira/skins";
386
- import { Cell, Background } from "wuira/ui";
387
-
388
- const micro = new MicroBar();
389
- micro.render(bar => {
390
- bar.Style({
391
- background: { r: 0.035, g: 0.045, b: 0.065, a: 0.9 },
392
- accent: { r: 0.4, g: 0.75, b: 1 },
393
- padding: 6,
394
- });
395
- bar.mount(() => (
396
- <Cell width={12} height={2} decorations={{
397
- background: new Background({ color: { r: 0.4, g: 0.75, b: 1 } }),
398
- }} />
399
- ), "TOP");
400
- bar.OnHover(hovered => console.log("Micro menu hover:", hovered));
401
- return bar;
402
- });
403
- ```
404
-
405
- `render` receives the skin adapter once its game-owned frame is ready. The same
406
- object exposes `Frame`, `Style`, `OnHover`, and `mount`; mounts become children of
407
- the native frame and are released by `Dispose()`. Native clicks, events and
408
- tooltips remain intact. Rendering waits until combat ends.
409
-
410
- `HoverTracker` supplies generic secure trigger/popup frames and auto-hide regions;
411
- the addon owns their visuals, contents and positioning. Compact menu launchers
412
- and themes belong in the consuming UI rather than the framework. Hover callbacks
413
- are ordinary addon callbacks; use secure hover visibility for protected panels.
414
-
415
- `BlizzardUI.SkinMicroMenu(options)` is also available for styling alone. Stopping
416
- styling hides its added panel; reload restores original native textures.
417
-
418
- ### Resource observation and mana regen
419
-
420
- Resource changes can also be observed without mounting a widget:
421
-
422
- ```ts
423
- const mana = new Mana(Unit.Player).UseRegenTimer();
424
- const unsubscribe = mana.onChange(value => console.log(value));
425
- // Call unsubscribe() when this consumer is removed.
426
- ```
427
-
428
- `onChange` reports future readable value changes, with no initial callback.
429
- It honors `.When()` and never sends secret values to user code. Native bar
430
- bindings still accept secret values directly. Bars, observers, and the regen
431
- accessory share one lazy native event listener; it unregisters when the last
432
- consumer detaches. `.Value` is still available for a one-time read.
433
-
434
- `mana.RegenTimer` can be bound to a thin `ProgressBar`. Its `Phase` is
435
- `fiveSecondRule`, `estimatedTick`, or `inactive`. Successful mana-cost casts
436
- start/restart a five-second window, followed by estimated two-second cycles.
437
- Mana events keep those cycles alive without resetting a running cycle. No
438
- server tick clock is exposed: this is an estimate, including when the cost is
439
- secret. Zero-cost casts do not restart the window. The native widget animates
440
- the fill; one-shot callbacks handle phase transitions, with no Lua frame polling.
441
- Full readable mana cancels the clock. When mana is secret, a native step curve
442
- hides full mana visually; Lua cannot use that hidden result to dispose the timer,
443
- so the estimate stops when mana events cease. The frame is reused on the next
444
- cast. Configure this player-only accessory before mounting.
445
-
446
- ### Native aura, cooldown, and cast timers
447
-
448
- ```tsx
449
- import { UI, Spells } from "wuira";
450
- import { FlowLayout, ProgressBar, Icon, Label } from "wuira/ui";
451
- import { AuraTimer, GlobalCooldown, CastTimer, Unit } from "wuira/datasources";
452
-
453
- const aura = new AuraTimer(Unit.Player, { spellIds: [Spells.SealOfRighteousness], filter: "HELPFUL" });
454
- const gcd = new GlobalCooldown();
455
- const cast = new CastTimer(Unit.Player);
456
-
457
- function Timers() {
458
- return (
459
- <FlowLayout width={200} height={94} direction="vertical" gap={2}>
460
- <Icon width={24} height={24} textureSource={aura.Texture} />
461
- <ProgressBar height={24} source={aura}><Label text="Aura" /></ProgressBar>
462
- <ProgressBar height={18} source={gcd} />
463
- <ProgressBar height={22} source={cast} />
464
- </FlowLayout>
465
- );
466
- }
467
- ```
468
-
469
- These timers pass opaque native duration objects to `SetTimerDuration`; they do
470
- not calculate restricted durations in Lua. `SpellCooldown(spellId)` tracks a
471
- specific spell, and `GlobalCooldown` supplies the GCD identifier internally.
472
- `CastTimer` follows casts and channels. `AuraTimer` resolves localized spell names
473
- and exposes an associated `Texture` source, with status indicating active,
474
- inactive, restricted, or unavailable data. Aura access still follows the client's
475
- restrictions; missing readable data is not reconstructed from combat events.
476
- `Label` and `Icon` are visual Cells. Application code supplies which spells to
477
- track; native API declarations and reads remain in wuira.
478
-
479
- ### Swing timers
480
-
481
- ```tsx
482
- import { UI, Wuira } from "wuira";
483
- import { ProgressBar } from "wuira/ui";
484
- import { SwingTimer, SwingHand } from "wuira/datasources";
485
-
486
- const mainHand = new SwingTimer(SwingHand.MainHand);
487
- const view = Wuira.Mount(<ProgressBar width={200} height={12} source={mainHand} />);
488
-
489
- // Other choices: SwingHand.OffHand and SwingHand.Ranged.
490
- // mainHand.Value: remaining seconds; Max.Value: duration; Percent.Value: remaining percent.
491
- // When removing the feature:
492
- if (view !== undefined) wuira.Unmount(view);
493
- mainHand.Dispose();
494
- ```
495
-
496
- This source follows the player's `PLAYER_SWING(duration, swingType)` events.
497
- It does not reconstruct swings from the combat log or track enemy attack cycles.
498
- `HasTiming` is false until the first readable event and after a weapon/world reset;
499
- zero before that event is not evidence of a synchronized swing. After a recorded
500
- cycle expires, remaining time reaches zero. Secret payloads block datasource
501
- computation until a readable event or reset arrives.
502
-
503
- Native progress binding uses `C_DurationUtil` and `StatusBar:SetTimerDuration`,
504
- with no Lua per-frame animation. Range/current overrides are not supported for
505
- this binding. Unmount detaches each bar; call `Dispose()` to stop the source's
506
- event listener when its owning feature ends. Support is capability-checked;
507
- actual timing still needs validation in Forever. The adapter follows the
508
- [Forever swing API definition](https://github.com/Gethe/wow-ui-source/blob/bd2470aed543f72697a044e989285b6c83e63f73/Interface/AddOns/Blizzard_APIDocumentationGenerated/SwingTimerDocumentation.lua).
509
-
510
- ### Native Edit Mode layouts
511
-
512
- ```ts
513
- import { EditModeLayouts } from "wuira/datasources";
514
-
515
- const layouts = new EditModeLayouts();
516
- console.log(layouts.Active?.name);
517
- const stop = layouts.onChange(snapshot => console.log(snapshot.active?.key));
518
- // On teardown: stop(); layouts.Dispose();
24
+ wuira.Mount(App);
519
25
  ```
520
26
 
521
- `Layouts` lists native indices, names, types and derived keys. `Value` is a detached
522
- snapshot; native events refresh it without polling. Check `Status` for availability.
523
- Forever exposes an index rather than a persistent UUID: `key` combines type,
524
- interface style and name, survives index shifts, and changes on rename. These keys
525
- describe local game layouts, not portable layout exports.
27
+ ## Highlights
526
28
 
527
- ### Group members
528
-
529
- ```ts
530
- import { GroupMembers, PartyMembers, RaidMembers, Buffs } from "wuira/datasources";
531
-
532
- const group = new GroupMembers();
533
- for (const unit of group.Value) {
534
- const buffs = new Buffs(unit);
535
- // Apply your own buff-tracking rules to buffs.Value when readable.
536
- }
29
+ - **JSX composition** — Describe UI trees with components. No separate XML view files.
30
+ - **Native widgets** — Cells mount to WoW frames; progress bars use the native StatusBar widget.
31
+ - **Layout primitives** — Arrange children with `flow` and `grid`.
32
+ - **Data sources** — HP, Mana, auras, cooldowns, casts, swings, group members, class/spec, spellbook.
33
+ - **Source converters** — Compose checked value transformations, case mappings and arithmetic expressions. See [source converters](docs/source-converters.md).
34
+ - **Slash commands** — Register commands with quoting and escaping support.
35
+ - **Windows and settings** — `Window`, `AddonSettings`, `FrameMover`, `KeybindCapture`.
36
+ - **Secure buttons** — Spell, item, macro, action, and target actions with `SecureButton`.
37
+ - **Native skins** — Style Blizzard UI through adapters such as `MicroBar`.
38
+ - **Stores** — Snapshot-based data storage with SavedVariables support.
39
+ - **Client catalogs** — Generated [spell enums](docs/spells.md) and [icon enums](docs/icons.md) from the pinned Forever client data.
537
40
 
538
- const party = new PartyMembers();
539
- const raid = new RaidMembers();
540
- ```
541
-
542
- `GroupMembers` returns `["player"]` while solo, `player` plus `partyN` tokens
543
- in a party, and `raidN` tokens in a raid. `PartyMembers` is empty outside a
544
- party; `RaidMembers` is empty outside a raid. The player is included once.
545
- Offline members are retained. Each `.Value` access reads the current roster;
546
- there is no automatic subscription, polling, or UI rerender. Read again when
547
- handling `GROUP_ROSTER_UPDATE`. Tokens may refer to different members after
548
- roster changes and should not be used as persistent identities.
549
-
550
- ### Player class and specialization
551
-
552
- ```ts
553
- import { UnitSource, ClassSource, SpecSource, Unit } from "wuira/datasources";
554
-
555
- const target = new UnitSource(Unit.Target);
556
- const playerClass = new ClassSource(target);
557
- const specs = new SpecSource(target);
558
-
559
- console.log(target.Is.PC); // boolean | undefined when restricted
560
- console.log(playerClass.Value?.token);
561
- specs.RequestInspect();
562
- // Later, after INSPECT_READY: specs.Value contains native specialization data.
563
- // Each entry exposes id, name, icon, role, and pointsSpent when provided.
564
- // Call specs.Dispose() when the owning feature is removed or cancels the request.
565
- ```
566
-
567
- These sources accept real players only; NPCs produce no data or inspect request.
568
- The same `UnitSource` can be passed to `Buffs`, `Debuffs`, `HP`, and `Mana`.
569
- Its `Value` contains the current token and GUID, rather than a cached identity.
570
- `Is.PC` and `Is.Self` return `undefined` when their native result is secret.
571
- String unit tokens are still supported as a convenience shorthand.
572
- Own specialization data is read directly. Other players require an explicit,
573
- out-of-combat inspect request and a matching response GUID. An unknown result
574
- remains `undefined`, and a changed unit token cannot expose another player's
575
- cached result. Secret values are guarded by the datasource API. Requests are
576
- not globally queued or automatically retried; callers own cancellation and
577
- request timing. Inspect permission, range, hostile-player support, and the
578
- meaning of talent-point fields must be verified on Forever. Being a player
579
- does not by itself guarantee inspect access.
580
-
581
- ### Aura ownership
582
-
583
- Buff and debuff sources expose fresh snapshots with caster information. Application
584
- code decides which blessings or other effects should be displayed.
585
-
586
- ```ts
587
- import { Buffs, Debuffs, Unit } from "wuira/datasources";
588
-
589
- const buffs = new Buffs(Unit.Target);
590
- const debuffs = new Debuffs(Unit.Target);
591
-
592
- buffs.Compute(auras => {
593
- for (const aura of auras) {
594
- if (aura.isSourceSelf() === true) {
595
- console.log(aura.name, "applied by me");
596
- } else if (aura.isSourceSelf() === false) {
597
- console.log(aura.name, "applied by", aura.sourceUnit);
598
- }
599
- // undefined means ownership is unknown, not that another player cast it.
600
- }
601
- });
602
- ```
603
-
604
- `sourceStatus` is `known`, `unknown`, or `restricted`. Restricted caster values
605
- are not exposed. Secret spell/name/icon data blocks `.Value` and `.Compute` through
606
- the normal datasource guard; `.IsSecret()` reports it. Missing native API support
607
- raises an explicit error instead of returning an apparently empty list. Reads do
608
- not install event listeners or poll automatically. The current adapter requires
609
- `C_UnitAuras.GetAuraDataByIndex`; Forever availability and restrictions still need
610
- in-game verification. Unit tokens refer to snapshot time and are not persistent
611
- caster identities. No Paladin-specific spell catalog or filtering is built in.
612
-
613
- ### Native tooltips
614
-
615
- `Tooltip` attaches to any Cell through decorations. It forwards hover to the
616
- game's native tooltip API and creates no visual regions or update loop.
617
-
618
- ```tsx
619
- import { Cell, Tooltip } from "wuira/ui";
41
+ ## Installation
620
42
 
621
- <Cell width={40} height={40} decorations={{
622
- tooltip: new Tooltip({ text: "My tooltip", lines: [{ text: "Extra details" }] }),
623
- }} />;
624
-
625
- <Cell width={40} height={40} decorations={{
626
- tooltip: new Tooltip({ show: tooltip => tooltip.SetSpellByID(635) }),
627
- }} />;
628
- ```
629
-
630
- The same attachment works on SecureButton. Call SetItemByID, SetUnit, SetHyperlink
631
- or SetAction inside `show` for other native content. `text` may be a function
632
- evaluated on hover; `show` may return false to suppress display. Optional `target`
633
- selects another native tooltip, and `anchor` defaults to ANCHOR_RIGHT.
634
- Hover preserves click-through and existing scripts. Leave, hide and unmount close
635
- only a tooltip still owned by that Cell. Use one decoration instance per Cell.
636
-
637
- ### Visual styling
638
-
639
- <!-- Screenshot: a dark Cell with a thin colored border. -->
640
-
641
- Configure backgrounds, borders, textures, and glow through reusable decoration objects.
642
-
643
- ```tsx
644
- import { Background, Border } from "wuira/ui";
645
-
646
-
647
- const view = (
648
- <cell width={200} height={64} decorations={{
649
- background: new Background({ color: { r: 0.06, g: 0.08, b: 0.1 } }),
650
- border: new Border({ thickness: 1, color: { r: 0.3, g: 0.8, b: 0.7 } }),
651
- }} />
652
- );
653
-
654
- Wuira.Mount(view);
655
- ```
656
-
657
- ### Addon tooling
658
-
659
- <!-- Screenshot: creating and building an addon in the terminal. -->
660
-
661
- Create a project, compile TypeScriptToLua, and generate the TOC and module-loading files through one CLI.
43
+ Requires Node.js 22.13 or newer.
662
44
 
663
45
  ```sh
664
46
  npx wuira@latest create MyAddon
@@ -667,170 +49,78 @@ npm install
667
49
  npm run build
668
50
  ```
669
51
 
670
- ### Readable Lua output
52
+ `create` refuses to overwrite an existing directory. It generates TypeScriptToLua configuration, addon metadata, a module loader, an MIT license, and a minimal `src/index.tsx`.
671
53
 
672
- <!-- Screenshot: source files alongside their emitted Lua modules. -->
54
+ ## Data sources
673
55
 
674
- Emitted modules remain split across files following the source structure.
675
-
676
- ```text
677
- src/index.tsx → dist/index.lua
678
- src/components/Panel.tsx → dist/components/Panel.lua
679
- wuira/ui dependency → dist/lua_modules/wuira/lib/ui/index.lua
680
-
681
- MyAddon.toc loads the module registry, emitted files, and startup entry.
682
- ```
683
-
684
- Use wuira when you want to build and distribute an addon UI in TypeScript, share visual components, and let the toolchain handle Lua module loading. Node.js runs during development; the game loads the generated Lua files without a JavaScript runtime.
685
-
686
- ## Scope and current status
687
-
688
- wuira is an early framework for addon authors. It is not a complete UI replacement, an in-game visual editor, or a WeakAuras replacement. JSX describes widgets; there is no React runtime or automatic reactive rendering layer.
689
-
690
- The current core includes Cells, flow and grid layouts, progress bars, decorations, health and mana sources, a local console polyfill, and project tooling. A complete WoW API surface, secure action and click-cast abstractions, nameplate adapters, and preset management are not implemented yet. Combat restrictions and secret-value rules still apply; framework abstractions cannot bypass them.
691
-
692
- The target client is WoW Forever. Compatibility with Retail or other Classic clients is not promised without verification. Expect API changes while the framework takes shape.
693
-
694
- ## Getting started
695
-
696
- The framework, npm package, CLI command, and public API are named `wuira`. Future extension packages can use the `@wuira/` organization scope once that organization is registered.
56
+ ```tsx
57
+ import { HP, Mana, Unit } from "wuira/datasources";
697
58
 
698
- Available on npm as [`wuira`](https://www.npmjs.com/package/wuira):
59
+ const hp = new HP(Unit.Player).UseHealPrediction().UseAbsorbPrediction();
60
+ const mana = new Mana(Unit.Player).UseRegenTimer();
699
61
 
700
- ```sh
701
- npx wuira create MyAddon
62
+ <progress height={24} source={hp} />
63
+ <progress height={24} source={mana} />
702
64
  ```
703
65
 
704
- A TypeScript and JSX UI framework for WoW Forever. TypeScriptToLua emits separate Lua files following the source directory structure. No Rollup or single-file Lua bundle is required.
66
+ Other sources include `AuraTimer`, `GlobalCooldown`, `CastTimer`, `SpellCooldown`, `SwingTimer`, `GroupMembers`, `PartyMembers`, `RaidMembers`, `Buffs`, `Debuffs`, `KnownSpells`, `Spells`, `UnitSource`, `ClassSource`, `SpecSource`, and `SpellSource`.
705
67
 
706
- ## CLI
707
-
708
- Create an addon with Node.js 22.13 or newer:
68
+ ## Secure buttons
709
69
 
710
- ```sh
711
- npx wuira@latest create MyAddon
712
- cd MyAddon
713
- npm install
714
- npm run build
70
+ ```tsx
71
+ <SecureButton width={80} height={24}
72
+ action={{ type: "spell", spell: 635, unit: "player" }}>
73
+ <Label text="Holy Light" align="CENTER" />
74
+ </SecureButton>
715
75
  ```
716
76
 
717
- Alternatively, install globally with `npm install -g wuira`, then use `wuira create MyAddon`. To test unpublished changes, run `npm install` and `npm link` in this repository, or invoke `node /path/to/wuira/cli/wuira.mjs create MyAddon` directly. Templates reflect the installed CLI version; repository changes become available through npm after the next release.
718
-
719
- `create` refuses to overwrite an existing directory. It generates TypeScriptToLua configuration, addon metadata, a module loader, an MIT license, and this minimal `src/index.tsx`:
720
-
721
- ```tsx
722
- import "wuira/console-polyfill";
723
- import { UI, Wuira } from "wuira";
77
+ Supported action types: `spell`, `item`, `macro`, `action`, and `target`. Actions and units are static mount-time attributes. Protected trees cannot be mounted during combat.
724
78
 
79
+ ## Stores
725
80
 
726
- function App() {
727
- console.log("MyAddon loaded");
728
- return <></>;
81
+ ```ts
82
+ class LumaSettings extends Store {
83
+ static readonly Token = "luma.settings";
84
+ CastSpellId = 1232;
85
+ Width = 220;
729
86
  }
730
87
 
731
- Wuira.Mount(App);
88
+ const store = new StoreRegistry({ addon: "LumaUI", variable: "LumaUIDB" });
89
+ store.onReady(() => console.log(store.get(LumaSettings).Width));
732
90
  ```
733
91
 
734
- An empty fragment creates no native frame. Only empty fragments are currently supported; use a layout for multiple visual elements.
735
-
736
- `console.log/info/warn/error` use a global WoW console polyfill. Output goes to the local chat window, never to other players. The addon uses no Node.js runtime or stdin/stdout. `console.error` does not throw. Arguments are formatted with Lua `tostring`; objects are not deeply inspected. The CLI uses its own Node.js terminal console.
737
-
738
- Importing `wuira` installs the global console polyfill automatically. The shared TypeScript config loads its global declarations and uses Node16 module resolution for package exports; DOM typings are not required. `Wuira.Mount` accepts a `Cell`, no value, or a function returning `Cell | void`, so addons without a visual tree can simply log and return.
92
+ `get()` does not subscribe. `subscribe()` observes future commits. `set()` works on a detached draft, validates it, commits it, and sends one notification per observer. Supported values are finite numbers, strings, booleans, arrays, and nested data objects.
739
93
 
740
94
  ## Build and distribution
741
95
 
742
- `wuira build` transpiles through `tsconfig.json`. It writes a standalone addon directory containing individual Lua modules, a loader, a startup file, and `<addonName>.toc`. The default output directory is `dist/`. The addon name lives in `wuira.config.mjs`; Interface version and other metadata live in `addon.toc.in`. The default Interface version, 16001, targets the Forever playground.
743
-
744
- Copy the contents of `dist/` to `Interface/AddOns/<addonName>/`, or configure the build to output there directly. No Node.js runtime or separate wuira addon is required in game. Module registries are isolated by addon name.
745
-
746
- Framework code and generated `.d.ts` declarations come from the installed npm package. Import UI components from `wuira/ui`, data sources from `wuira/datasources`, and the mounting API from `wuira`. No framework source files or build scripts are copied into addon projects. This repository contains only the framework and CLI; addon entry points and TOC metadata are generated in consumer projects.
747
-
748
- Project templates live in `templates/addon/`. Generated projects depend on `wuira` and extend `wuira/config/tsconfig.json`. Shared settings and the TSTL module plugin stay in the installed package; build scripts are not copied into projects. Project-local `rootDir`, `outDir`, and source includes remain in the generated `tsconfig.json`.
749
-
750
- `npm run build` resolves the locally installed `wuira` command automatically. Use `npx wuira build` when invoking the CLI directly.
751
-
752
- Configure the addon output in `wuira.config.mjs`:
96
+ `wuira build` transpiles through `tsconfig.json`. It writes individual Lua modules, a loader, a startup file, and `<addonName>.toc` into `dist/`. Copy the output to `Interface/AddOns/<addonName>/`, or configure the build to output there directly.
753
97
 
754
98
  ```js
99
+ // wuira.config.mjs
755
100
  export default {
756
101
  name: "MyAddon",
757
102
  outDir: "./dist",
758
- // Or: outDir: "G:/Games/WoW/Interface/AddOns/MyAddon",
759
103
  };
760
104
  ```
761
105
 
762
- Relative output paths resolve from the project directory. Absolute paths can target the game's addon directory, keeping the development project elsewhere. Builds compile into a temporary staging directory before copying successful output to the destination. Existing files with matching names are overwritten; unrelated files are retained. The emitted TOC uses paths relative to the output folder, so the entire folder can be moved independently.
106
+ No Node.js runtime or separate wuira addon is required in game. Framework code comes from the npm package and is not copied into addon projects.
763
107
 
764
- The CLI, configuration, and build plugin use ESM (`.mjs`). Only Lua and TOC files are needed in game. `npm run build` in the framework repository generates distributable Lua modules and TypeScript declarations in `lib/`; addon builds include the framework modules they import.
765
-
766
- ## Stores
767
-
768
- Use data classes to define defaults. Without a static token, records live only
769
- in memory and are keyed by the class itself:
770
-
771
- ```ts
772
- import { Store, StoreRegistry } from "wuira/store";
773
-
774
- class SessionSettings extends Store {
775
- CastSpellId = 1232;
776
- }
777
-
778
- const store = new StoreRegistry();
779
- const snapshot = store.get(SessionSettings); // One-time, deeply readonly copy.
780
-
781
- const unsubscribe = store.subscribe(SessionSettings, settings => {
782
- console.log("New spell:", settings.CastSpellId);
783
- });
784
-
785
- store.set(SessionSettings, settings => {
786
- settings.CastSpellId = 635;
787
- return settings;
788
- });
108
+ ## CLI
789
109
 
790
- unsubscribe();
110
+ ```sh
111
+ npx wuira create MyAddon
112
+ npx wuira build
791
113
  ```
792
114
 
793
- `get()` does not subscribe. `subscribe()` observes future commits and returns an
794
- unsubscribe function; call `get()` for initial data. Each subscriber receives an
795
- independent snapshot. `set()` works on a detached draft, validates the returned
796
- data, commits it, and sends one notification per observer. If the updater throws,
797
- the previous record is preserved. Stores contain data fields, not class methods.
798
-
799
- For persistence, declare a stable token and the owning addon's SavedVariables:
800
-
801
- ```toc
802
- ## SavedVariables: LumaUIDB
803
- ```
115
+ For global installation, use `npm install -g wuira`. To test unpublished changes, run `npm install && npm link` in this repository, or invoke `node /path/to/wuira/cli/wuira.mjs` directly.
804
116
 
805
- ```ts
806
- class LumaSettings extends Store {
807
- static readonly Token = "luma.settings";
808
- CastSpellId = 1232;
809
- Width = 220;
810
- }
117
+ ## Scope and status
811
118
 
812
- const store = new StoreRegistry({ addon: "LumaUI", variable: "LumaUIDB" });
813
- store.onReady(() => {
814
- console.log(store.get(LumaSettings).Width);
815
- store.set(LumaSettings, settings => {
816
- settings.Width = 240;
817
- return settings;
818
- });
819
- });
820
- ```
119
+ wuira is an early framework. It is not a complete UI replacement, an in-game visual editor, or a WeakAuras replacement. JSX describes widgets; there is no React runtime or automatic reactive rendering layer. Combat restrictions and secret-value rules still apply and cannot be bypassed by the framework.
821
120
 
822
- Add the TOC declaration to the addon's `addon.toc.in`. The registry waits for
823
- `ADDON_LOADED` before opening persistent records. Tokenless records can still be
824
- used immediately. WoW writes the saved table on normal logout/reload; `set()`
825
- does not force a disk flush. Keep tokens stable across versions. New default
826
- fields are filled in and incompatible saved field types fall back to defaults;
827
- renaming keys or changing schema meaning still requires an application migration.
121
+ Current core includes Cells, flow and grid layouts, progress bars, decorations, health and mana sources, a local console polyfill, and project tooling. Not yet implemented: a complete WoW API surface, click-cast abstractions, nameplate adapters, and preset management.
828
122
 
829
- Supported values are finite numbers, strings, booleans, arrays, and nested data
830
- objects. Functions, cycles, and secret values cannot be saved. Two classes cannot
831
- claim the same token within a registry. Share one registry instance per database.
832
- `Dispose()` removes its observers and pending initialization listener. Profile
833
- selection, schema migrations, and reactive widget binding are separate features.
123
+ The target client is WoW Forever. Compatibility with Retail or other Classic clients is not promised without verification.
834
124
 
835
125
  ## Development
836
126
 
@@ -841,36 +131,16 @@ npm run check
841
131
  npm test
842
132
  ```
843
133
 
844
- `.github/workflows/ci.yml` runs type checks, builds, tests, and a package dry run on pushes and pull requests, on both Linux and Windows with Node.js 22. CI does not publish packages and requires no npm token.
134
+ CI runs type checks, builds, tests, and a package dry run on Linux and Windows with Node.js 22. It does not publish packages and requires no npm token.
845
135
 
846
- Cell is the basic visual box. FlowLayout and GridLayout arrange children. ProgressBar uses a native StatusBar, while data sources connect values to presentation. Behavior from other WoW versions must be verified against the Forever API before use.
136
+ ## License
847
137
 
848
- Licensed under MIT; see [LICENSE](LICENSE).
138
+ MIT — see [LICENSE](LICENSE).
849
139
 
850
140
  ## AI disclosure
851
141
 
852
- wuira is developed with AI assistance for implementation, documentation, and
853
- tests. AI-assisted code is reviewed line by line. Releases also undergo
854
- TypeScript checks, Lua runtime tests, and package build validation. Project
855
- maintainers remain responsible for the code and release decisions.
142
+ wuira is developed with AI assistance for implementation, documentation, and tests. AI-assisted code is reviewed line by line. Releases also undergo TypeScript checks, Lua runtime tests, and package build validation. Project maintainers remain responsible for the code and release decisions.
856
143
 
857
144
  ## Releases
858
145
 
859
- See [CHANGELOG.md](CHANGELOG.md) for release history. Record new changes under
860
- `Unreleased`, then move them into a dated version section before publishing.
861
-
862
- The first version is published. To publish the next version locally, validate the project, increment the patch version, and publish:
863
-
864
- ```sh
865
- npm run check
866
- npm run build
867
- npm test
868
- npm version patch --no-git-tag-version
869
- npm publish --access public
870
- ```
871
-
872
- Publishing requires npm authentication and 2FA, or a granular access token with write access and Bypass 2FA enabled. Commit both `package.json` and `package-lock.json` after changing the version. npm versions cannot be published twice.
873
-
874
- For future automated releases, every pushed Git tag triggers `.github/workflows/publish.yml`. It checks types, builds, tests, and publishes to npm. Tags must match `package.json` version, optionally prefixed with `v`. Do not push a release tag for a version already published locally; the workflow would attempt to publish it again.
875
-
876
- To enable automated publishing, configure the GitHub repository secret `NPM_TOKEN` with an npm granular access token authorized to publish `wuira` and bypass 2FA. Never commit the token. GitHub Actions must be enabled. Local setup does not publish a package or push a tag.
146
+ See [CHANGELOG.md](CHANGELOG.md) for release history. Record new changes under `Unreleased`, then move them into a dated version section before publishing.