wuira 0.2.4 → 0.2.6
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 +33 -0
- package/README.md +62 -831
- package/data/icons/forever.json +90051 -0
- package/docs/icons.md +28 -0
- package/docs/source-converters.md +27 -0
- package/lib/datasources/DataSource.d.ts +8 -0
- package/lib/datasources/DataSource.lua +7 -0
- package/lib/datasources/SourceConverter.d.ts +30 -0
- package/lib/datasources/SourceConverter.lua +475 -0
- package/lib/datasources/UnitInfo.d.ts +6 -0
- package/lib/datasources/UnitInfo.lua +347 -15
- package/lib/datasources/index.d.ts +3 -0
- package/lib/datasources/index.lua +4 -0
- package/lib/icons/index.d.ts +45634 -0
- package/lib/icons/index.lua +3 -0
- package/lib/index.d.ts +1 -0
- package/lib/store/index.lua +20 -18
- package/package.json +20 -4
- package/tools/build-library.mjs +1 -1
- package/tools/generate-icons.mjs +10 -0
- package/tools/icon-data.mjs +59 -0
- 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
|
-
|
|
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
|
-
|
|
5
|
+
Build WoW Forever interfaces with TypeScript and JSX.
|
|
16
6
|
|
|
17
|
-
|
|
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,675 +21,26 @@ function App() {
|
|
|
29
21
|
);
|
|
30
22
|
}
|
|
31
23
|
|
|
32
|
-
|
|
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();
|
|
24
|
+
wuira.Mount(App);
|
|
229
25
|
```
|
|
230
26
|
|
|
231
|
-
|
|
232
|
-
`undefined`; unreadable spellbooks return no entries. Metadata changes also notify
|
|
233
|
-
`onChange` observers, even when the spell IDs have not changed.
|
|
27
|
+
## Highlights
|
|
234
28
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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. Array inputs create no subscriptions:
|
|
274
|
-
recreate the view from `known.onChange` when needed, outside combat for protected
|
|
275
|
-
trees. Each filtered rank remains a separate entry. Observable list sources also
|
|
276
|
-
support keyed updates, as shown below under Group members.
|
|
277
|
-
|
|
278
|
-
All visual cells accept `condition={boolean}` or `condition={() => boolean}`.
|
|
279
|
-
This controls visibility and retains the layout slot. It combines with datasource
|
|
280
|
-
activity and the existing `visible` predicate. Callbacks use the shared visibility
|
|
281
|
-
update driver; protected changes wait until combat ends. Secret conditions hide
|
|
282
|
-
the cell without inspecting their value.
|
|
283
|
-
|
|
284
|
-
### Empty native action bars
|
|
285
|
-
|
|
286
|
-
This is opt-in and removes real action-slot assignments, not just their visuals.
|
|
287
|
-
Declare a character-specific backup variable in the addon's TOC:
|
|
288
|
-
|
|
289
|
-
```text
|
|
290
|
-
## SavedVariablesPerCharacter: MyAddonActionBarBackup
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
```ts
|
|
294
|
-
import { ActionBars } from "wuira";
|
|
295
|
-
|
|
296
|
-
ActionBars.InstallEmptyMode({
|
|
297
|
-
addon: "MyAddon",
|
|
298
|
-
variable: "MyAddonActionBarBackup",
|
|
299
|
-
clearCommand: "/myaddonclearbars",
|
|
300
|
-
restoreCommand: "/myaddonrestorebars",
|
|
301
|
-
});
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
The first world entry captures supported spell, item, and macro assignments.
|
|
305
|
-
Use `/reload` to persist a new backup, then `/myaddonclearbars` outside combat to
|
|
306
|
-
clear the 180 action slots used by the installed Forever implementation. Slot
|
|
307
|
-
changes run directly from the command, never from world-entry or cursor events.
|
|
308
|
-
Independently, initialization hides stock bars, removes their descendant event/update handlers, disables their
|
|
309
|
-
shared action-bar event managers, and detaches native action UI registrations.
|
|
310
|
-
It does not create a replacement bar or remove key-binding assignments.
|
|
311
|
-
Changes wait until combat ends, and a busy cursor blocks slot changes.
|
|
312
|
-
|
|
313
|
-
The restore command puts assignments back into empty or identical slots. Use
|
|
314
|
-
`/reload` afterwards to restore native handlers and visibility. The stock bars
|
|
315
|
-
remain enabled on future loads. A refused slot operation leaves bar suppression
|
|
316
|
-
active and is not automatically retried. Blocked/forbidden native calls report
|
|
317
|
-
their API name in the local console; command execution does not bypass client restrictions.
|
|
318
|
-
Unsupported action types, secret slot data, uncovered assignments, and conflicting
|
|
319
|
-
restores stop the operation. `Capture()`, `Clear(backup)`, and `Restore(backup)` are
|
|
320
|
-
also available for applications that manage their own persistence and lifecycle.
|
|
321
|
-
|
|
322
|
-
### Secure action buttons
|
|
323
|
-
|
|
324
|
-
`SecureButton` extends Cell's descriptor and props, including size, positioning,
|
|
325
|
-
decorations and one child. Mount it like any other component:
|
|
326
|
-
|
|
327
|
-
```tsx
|
|
328
|
-
import { UI, Wuira } from "wuira";
|
|
329
|
-
import { SecureButton, Label } from "wuira/ui";
|
|
330
|
-
|
|
331
|
-
Wuira.Mount(() => (
|
|
332
|
-
<SecureButton width={80} height={24}
|
|
333
|
-
action={{ type: "spell", spell: 635, unit: "player" }}>
|
|
334
|
-
<Label text="Holy Light" align="CENTER" />
|
|
335
|
-
</SecureButton>
|
|
336
|
-
));
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
Supported actions are `{ type: "spell", spell, unit? }`,
|
|
340
|
-
`{ type: "item", item, unit? }`, `{ type: "macro", macro }`,
|
|
341
|
-
`{ type: "action", slot, unit? }` and `{ type: "target", unit }`.
|
|
342
|
-
Spell and macro references accept IDs/indices or names. Items accept an ID
|
|
343
|
-
(converted to `item:ID`) or the native item string. Macros refer to existing
|
|
344
|
-
game macros, not arbitrary Lua callbacks or generated macro text.
|
|
345
|
-
|
|
346
|
-
The default is left-button release; `button="RightButton"` selects another
|
|
347
|
-
button. Secure buttons always receive mouse input, while visual children remain
|
|
348
|
-
click-through by default. Omitting `action` creates an inert secure button.
|
|
349
|
-
|
|
350
|
-
Actions and units are static mount-time attributes. Changing the descriptor
|
|
351
|
-
later does not rewrite them. Mounting a protected tree during combat raises an
|
|
352
|
-
error before any frame is created. Visibility changes and unmount requests wait
|
|
353
|
-
until combat ends, including changes to ancestors containing secure buttons.
|
|
354
|
-
Click-cast presets are not provided by this primitive yet. The native action mapping
|
|
355
|
-
follows [Forever's secure templates](https://github.com/Gethe/wow-ui-source/blob/forever/Interface/AddOns/Blizzard_FrameXML/SecureTemplates.lua).
|
|
356
|
-
|
|
357
|
-
For an icon action with a radial cooldown and temporary key binding:
|
|
358
|
-
|
|
359
|
-
```tsx
|
|
360
|
-
import { Spells } from "wuira";
|
|
361
|
-
import { SpellSource } from "wuira/datasources";
|
|
362
|
-
import { Icon } from "wuira/ui";
|
|
363
|
-
|
|
364
|
-
const judgement = new SpellSource(Spells.Judgement);
|
|
365
|
-
const button = (
|
|
366
|
-
<SecureButton width={40} height={40} action={judgement.Action}
|
|
367
|
-
binding="1" cooldown={judgement.Cooldown} finishGlow>
|
|
368
|
-
<Icon textureSource={judgement.Texture} />
|
|
369
|
-
</SecureButton>
|
|
370
|
-
);
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
`binding` temporarily routes the key to this native button and adds its label;
|
|
374
|
-
`showHotkey={false}` suppresses the label. Unmount removes the override and leaves
|
|
375
|
-
saved bindings intact. The radial widget owns countdown timing and the short
|
|
376
|
-
completion flash, including opaque combat durations. `SpellSource.Cooldown`
|
|
377
|
-
ignores the GCD so unrelated casts do not trigger a completion flash.
|
|
378
|
-
|
|
379
|
-
### Native skins
|
|
380
|
-
|
|
381
|
-
Skin Forever's existing character, professions, spellbook and other micro-menu
|
|
382
|
-
buttons while keeping the game's click handlers, tooltips and notifications:
|
|
383
|
-
|
|
384
|
-
```tsx
|
|
385
|
-
import { UI } from "wuira";
|
|
386
|
-
import { MicroBar } from "wuira/skins";
|
|
387
|
-
import { Cell, Background } from "wuira/ui";
|
|
388
|
-
|
|
389
|
-
const micro = new MicroBar();
|
|
390
|
-
micro.render(bar => {
|
|
391
|
-
bar.Style({
|
|
392
|
-
background: { r: 0.035, g: 0.045, b: 0.065, a: 0.9 },
|
|
393
|
-
accent: { r: 0.4, g: 0.75, b: 1 },
|
|
394
|
-
padding: 6,
|
|
395
|
-
});
|
|
396
|
-
bar.mount(() => (
|
|
397
|
-
<Cell width={12} height={2} decorations={{
|
|
398
|
-
background: new Background({ color: { r: 0.4, g: 0.75, b: 1 } }),
|
|
399
|
-
}} />
|
|
400
|
-
), "TOP");
|
|
401
|
-
bar.OnHover(hovered => console.log("Micro menu hover:", hovered));
|
|
402
|
-
return bar;
|
|
403
|
-
});
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
`render` receives the skin adapter once its game-owned frame is ready. The same
|
|
407
|
-
object exposes `Frame`, `Style`, `OnHover`, and `mount`; mounts become children of
|
|
408
|
-
the native frame and are released by `Dispose()`. Native clicks, events and
|
|
409
|
-
tooltips remain intact. Rendering waits until combat ends.
|
|
410
|
-
|
|
411
|
-
`HoverTracker` supplies generic secure trigger/popup frames and auto-hide regions;
|
|
412
|
-
the addon owns their visuals, contents and positioning. Compact menu launchers
|
|
413
|
-
and themes belong in the consuming UI rather than the framework. Hover callbacks
|
|
414
|
-
are ordinary addon callbacks; use secure hover visibility for protected panels.
|
|
415
|
-
|
|
416
|
-
`BlizzardUI.SkinMicroMenu(options)` is also available for styling alone. Stopping
|
|
417
|
-
styling hides its added panel; reload restores original native textures.
|
|
418
|
-
|
|
419
|
-
### Resource observation and mana regen
|
|
420
|
-
|
|
421
|
-
Resource changes can also be observed without mounting a widget:
|
|
422
|
-
|
|
423
|
-
```ts
|
|
424
|
-
const mana = new Mana(Unit.Player).UseRegenTimer();
|
|
425
|
-
const unsubscribe = mana.onChange(value => console.log(value));
|
|
426
|
-
// Call unsubscribe() when this consumer is removed.
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
`onChange` reports future readable value changes, with no initial callback.
|
|
430
|
-
It honors `.When()` and never sends secret values to user code. Native bar
|
|
431
|
-
bindings still accept secret values directly. Bars, observers, and the regen
|
|
432
|
-
accessory share one lazy native event listener; it unregisters when the last
|
|
433
|
-
consumer detaches. `.Value` is still available for a one-time read.
|
|
434
|
-
|
|
435
|
-
`mana.RegenTimer` can be bound to a thin `ProgressBar`. Its `Phase` is
|
|
436
|
-
`fiveSecondRule`, `estimatedTick`, or `inactive`. Successful mana-cost casts
|
|
437
|
-
start/restart a five-second window, followed by estimated two-second cycles.
|
|
438
|
-
Mana events keep those cycles alive without resetting a running cycle. No
|
|
439
|
-
server tick clock is exposed: this is an estimate, including when the cost is
|
|
440
|
-
secret. Zero-cost casts do not restart the window. The native widget animates
|
|
441
|
-
the fill; one-shot callbacks handle phase transitions, with no Lua frame polling.
|
|
442
|
-
Full readable mana cancels the clock. When mana is secret, a native step curve
|
|
443
|
-
hides full mana visually; Lua cannot use that hidden result to dispose the timer,
|
|
444
|
-
so the estimate stops when mana events cease. The frame is reused on the next
|
|
445
|
-
cast. Configure this player-only accessory before mounting.
|
|
446
|
-
|
|
447
|
-
### Native aura, cooldown, and cast timers
|
|
448
|
-
|
|
449
|
-
```tsx
|
|
450
|
-
import { UI, Spells } from "wuira";
|
|
451
|
-
import { FlowLayout, ProgressBar, Icon, Label } from "wuira/ui";
|
|
452
|
-
import { AuraTimer, GlobalCooldown, CastTimer, Unit } from "wuira/datasources";
|
|
453
|
-
|
|
454
|
-
const aura = new AuraTimer(Unit.Player, { spellIds: [Spells.SealOfRighteousness], filter: "HELPFUL" });
|
|
455
|
-
const gcd = new GlobalCooldown();
|
|
456
|
-
const cast = new CastTimer(Unit.Player);
|
|
457
|
-
|
|
458
|
-
function Timers() {
|
|
459
|
-
return (
|
|
460
|
-
<FlowLayout width={200} height={94} direction="vertical" gap={2}>
|
|
461
|
-
<Icon width={24} height={24} textureSource={aura.Texture} />
|
|
462
|
-
<ProgressBar height={24} source={aura}><Label text="Aura" /></ProgressBar>
|
|
463
|
-
<ProgressBar height={18} source={gcd} />
|
|
464
|
-
<ProgressBar height={22} source={cast} />
|
|
465
|
-
</FlowLayout>
|
|
466
|
-
);
|
|
467
|
-
}
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
These timers pass opaque native duration objects to `SetTimerDuration`; they do
|
|
471
|
-
not calculate restricted durations in Lua. `SpellCooldown(spellId)` tracks a
|
|
472
|
-
specific spell, and `GlobalCooldown` supplies the GCD identifier internally.
|
|
473
|
-
`CastTimer` follows casts and channels. `AuraTimer` resolves localized spell names
|
|
474
|
-
and exposes an associated `Texture` source, with status indicating active,
|
|
475
|
-
inactive, restricted, or unavailable data. Aura access still follows the client's
|
|
476
|
-
restrictions; missing readable data is not reconstructed from combat events.
|
|
477
|
-
`Label` and `Icon` are visual Cells. Application code supplies which spells to
|
|
478
|
-
track; native API declarations and reads remain in wuira.
|
|
479
|
-
|
|
480
|
-
### Swing timers
|
|
481
|
-
|
|
482
|
-
```tsx
|
|
483
|
-
import { UI, Wuira } from "wuira";
|
|
484
|
-
import { ProgressBar } from "wuira/ui";
|
|
485
|
-
import { SwingTimer, SwingHand } from "wuira/datasources";
|
|
486
|
-
|
|
487
|
-
const mainHand = new SwingTimer(SwingHand.MainHand);
|
|
488
|
-
const view = Wuira.Mount(<ProgressBar width={200} height={12} source={mainHand} />);
|
|
489
|
-
|
|
490
|
-
// Other choices: SwingHand.OffHand and SwingHand.Ranged.
|
|
491
|
-
// mainHand.Value: remaining seconds; Max.Value: duration; Percent.Value: remaining percent.
|
|
492
|
-
// When removing the feature:
|
|
493
|
-
if (view !== undefined) wuira.Unmount(view);
|
|
494
|
-
mainHand.Dispose();
|
|
495
|
-
```
|
|
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** — Transform checked values with typed callbacks. 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.
|
|
496
40
|
|
|
497
|
-
|
|
498
|
-
It does not reconstruct swings from the combat log or track enemy attack cycles.
|
|
499
|
-
`HasTiming` is false until the first readable event and after a weapon/world reset;
|
|
500
|
-
zero before that event is not evidence of a synchronized swing. After a recorded
|
|
501
|
-
cycle expires, remaining time reaches zero. Secret payloads block datasource
|
|
502
|
-
computation until a readable event or reset arrives.
|
|
41
|
+
## Installation
|
|
503
42
|
|
|
504
|
-
|
|
505
|
-
with no Lua per-frame animation. Range/current overrides are not supported for
|
|
506
|
-
this binding. Unmount detaches each bar; call `Dispose()` to stop the source's
|
|
507
|
-
event listener when its owning feature ends. Support is capability-checked;
|
|
508
|
-
actual timing still needs validation in Forever. The adapter follows the
|
|
509
|
-
[Forever swing API definition](https://github.com/Gethe/wow-ui-source/blob/bd2470aed543f72697a044e989285b6c83e63f73/Interface/AddOns/Blizzard_APIDocumentationGenerated/SwingTimerDocumentation.lua).
|
|
510
|
-
|
|
511
|
-
### Native Edit Mode layouts
|
|
512
|
-
|
|
513
|
-
```ts
|
|
514
|
-
import { EditModeLayouts } from "wuira/datasources";
|
|
515
|
-
|
|
516
|
-
const layouts = new EditModeLayouts();
|
|
517
|
-
console.log(layouts.Active?.name);
|
|
518
|
-
const stop = layouts.onChange(snapshot => console.log(snapshot.active?.key));
|
|
519
|
-
// On teardown: stop(); layouts.Dispose();
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
`Layouts` lists native indices, names, types and derived keys. `Value` is a detached
|
|
523
|
-
snapshot; native events refresh it without polling. Check `Status` for availability.
|
|
524
|
-
Forever exposes an index rather than a persistent UUID: `key` combines type,
|
|
525
|
-
interface style and name, survives index shifts, and changes on rename. These keys
|
|
526
|
-
describe local game layouts, not portable layout exports.
|
|
527
|
-
|
|
528
|
-
### Group members
|
|
529
|
-
|
|
530
|
-
```ts
|
|
531
|
-
import { GroupMembers, PartyMembers, RaidMembers, Buffs } from "wuira/datasources";
|
|
532
|
-
|
|
533
|
-
const group = new GroupMembers();
|
|
534
|
-
for (const unit of group.Value) {
|
|
535
|
-
const buffs = new Buffs(unit);
|
|
536
|
-
// Apply your own buff-tracking rules to buffs.Value when readable.
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
const party = new PartyMembers();
|
|
540
|
-
const raid = new RaidMembers();
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
`GroupMembers` returns `["player"]` while solo, `player` plus `partyN` tokens
|
|
544
|
-
in a party, and `raidN` tokens in a raid. `PartyMembers` is empty outside a
|
|
545
|
-
party; `RaidMembers` is empty outside a raid. The player is included once.
|
|
546
|
-
Offline members are retained. Each `.Value` access reads the current roster.
|
|
547
|
-
`onChange` subscribes to roster, pet, connection and name events without polling;
|
|
548
|
-
unsubscribe or call `Dispose()` when done. Tokens may refer to different members
|
|
549
|
-
after roster changes and should not be used as persistent identities.
|
|
550
|
-
|
|
551
|
-
`GroupRoster` adds GUID-based keys, full-name/realm fallback, pet ownership and
|
|
552
|
-
raid subgroup metadata. Pass it directly to `ForEach` to retain mounted cells and
|
|
553
|
-
their resource subscriptions while the roster changes:
|
|
554
|
-
|
|
555
|
-
```tsx
|
|
556
|
-
import { UI, Wuira } from "wuira";
|
|
557
|
-
import { ForEach, ProgressBar, type Element } from "wuira/ui";
|
|
558
|
-
import { GroupRoster, HP, UnitSource } from "wuira/datasources";
|
|
559
|
-
|
|
560
|
-
const roster = new GroupRoster("party", { pets: true, includeSelfWhenSolo: true });
|
|
561
|
-
const units = new Map<Element, UnitSource>();
|
|
562
|
-
const view = (
|
|
563
|
-
<ForEach items={roster} key={member => member.key}
|
|
564
|
-
direction="vertical" wrapAfter={5} gap={4} lineGap={8}
|
|
565
|
-
onUpdate={(view, member) => units.get(view)!.Retarget(member.unit)}
|
|
566
|
-
onDispose={view => { units.delete(view); }}>
|
|
567
|
-
{member => {
|
|
568
|
-
const unit = new UnitSource(member.unit);
|
|
569
|
-
const view = <ProgressBar width={160} height={24} source={new HP(unit)} />;
|
|
570
|
-
units.set(view, unit);
|
|
571
|
-
return view;
|
|
572
|
-
}}
|
|
573
|
-
</ForEach>
|
|
574
|
-
);
|
|
575
|
-
const frame = Wuira.Mount(view)!;
|
|
576
|
-
// On teardown: Wuira.Unmount(frame); roster.Dispose();
|
|
577
|
-
```
|
|
578
|
-
|
|
579
|
-
Observable inputs expose `Value` and `onChange`, and require unique, public keys.
|
|
580
|
-
Only new keys call the child factory; removed keys unmount and call `onDispose`.
|
|
581
|
-
Use `onUpdate` to update retained views when their unit token or metadata changes.
|
|
582
|
-
Structural changes wait until combat ends. The observable `ForEach` owns its
|
|
583
|
-
layout and sizes its native host; mount it as a root or reserve an explicit slot
|
|
584
|
-
in its parent layout. `groupBy` can arrange raid subgroups in separate columns.
|
|
585
|
-
|
|
586
|
-
### Player class and specialization
|
|
587
|
-
|
|
588
|
-
```ts
|
|
589
|
-
import { UnitSource, ClassSource, SpecSource, Unit } from "wuira/datasources";
|
|
590
|
-
|
|
591
|
-
const target = new UnitSource(Unit.Target);
|
|
592
|
-
const playerClass = new ClassSource(target);
|
|
593
|
-
const specs = new SpecSource(target);
|
|
594
|
-
|
|
595
|
-
console.log(target.Is.PC); // boolean | undefined when restricted
|
|
596
|
-
console.log(playerClass.Value?.token);
|
|
597
|
-
specs.RequestInspect();
|
|
598
|
-
// Later, after INSPECT_READY: specs.Value contains native specialization data.
|
|
599
|
-
// Each entry exposes id, name, icon, role, and pointsSpent when provided.
|
|
600
|
-
// Call specs.Dispose() when the owning feature is removed or cancels the request.
|
|
601
|
-
```
|
|
602
|
-
|
|
603
|
-
These sources accept real players only; NPCs produce no data or inspect request.
|
|
604
|
-
The same `UnitSource` can be passed to `Buffs`, `Debuffs`, `HP`, and `Mana`.
|
|
605
|
-
Its `Value` contains the current token and GUID, rather than a cached identity.
|
|
606
|
-
`Retarget(token)` changes a shared reference outside combat; bound HP/Mana widgets
|
|
607
|
-
refresh without replacing their sources. `onChange` observes these explicit token
|
|
608
|
-
changes, not every change to the native unit's identity.
|
|
609
|
-
`Is.PC` and `Is.Self` return `undefined` when their native result is secret.
|
|
610
|
-
String unit tokens are still supported as a convenience shorthand.
|
|
611
|
-
Own specialization data is read directly. Other players require an explicit,
|
|
612
|
-
out-of-combat inspect request and a matching response GUID. An unknown result
|
|
613
|
-
remains `undefined`, and a changed unit token cannot expose another player's
|
|
614
|
-
cached result. Secret values are guarded by the datasource API. Requests are
|
|
615
|
-
not globally queued or automatically retried; callers own cancellation and
|
|
616
|
-
request timing. Inspect permission, range, hostile-player support, and the
|
|
617
|
-
meaning of talent-point fields must be verified on Forever. Being a player
|
|
618
|
-
does not by itself guarantee inspect access.
|
|
619
|
-
|
|
620
|
-
### Aura ownership
|
|
621
|
-
|
|
622
|
-
Buff and debuff sources expose fresh snapshots with caster information. Application
|
|
623
|
-
code decides which blessings or other effects should be displayed.
|
|
624
|
-
|
|
625
|
-
```ts
|
|
626
|
-
import { Buffs, Debuffs, Unit } from "wuira/datasources";
|
|
627
|
-
|
|
628
|
-
const buffs = new Buffs(Unit.Target);
|
|
629
|
-
const debuffs = new Debuffs(Unit.Target);
|
|
630
|
-
|
|
631
|
-
buffs.Compute(auras => {
|
|
632
|
-
for (const aura of auras) {
|
|
633
|
-
if (aura.isSourceSelf() === true) {
|
|
634
|
-
console.log(aura.name, "applied by me");
|
|
635
|
-
} else if (aura.isSourceSelf() === false) {
|
|
636
|
-
console.log(aura.name, "applied by", aura.sourceUnit);
|
|
637
|
-
}
|
|
638
|
-
// undefined means ownership is unknown, not that another player cast it.
|
|
639
|
-
}
|
|
640
|
-
});
|
|
641
|
-
```
|
|
642
|
-
|
|
643
|
-
`sourceStatus` is `known`, `unknown`, or `restricted`. Restricted caster values
|
|
644
|
-
are not exposed. Secret spell/name/icon data blocks `.Value` and `.Compute` through
|
|
645
|
-
the normal datasource guard; `.IsSecret()` reports it. Missing native API support
|
|
646
|
-
raises an explicit error instead of returning an apparently empty list. Reads do
|
|
647
|
-
not install event listeners or poll automatically. The current adapter requires
|
|
648
|
-
`C_UnitAuras.GetAuraDataByIndex`; Forever availability and restrictions still need
|
|
649
|
-
in-game verification. Unit tokens refer to snapshot time and are not persistent
|
|
650
|
-
caster identities. No Paladin-specific spell catalog or filtering is built in.
|
|
651
|
-
|
|
652
|
-
### Native tooltips
|
|
653
|
-
|
|
654
|
-
`Tooltip` attaches to any Cell through decorations. It forwards hover to the
|
|
655
|
-
game's native tooltip API and creates no visual regions or update loop.
|
|
656
|
-
|
|
657
|
-
```tsx
|
|
658
|
-
import { Cell, Tooltip } from "wuira/ui";
|
|
659
|
-
|
|
660
|
-
<Cell width={40} height={40} decorations={{
|
|
661
|
-
tooltip: new Tooltip({ text: "My tooltip", lines: [{ text: "Extra details" }] }),
|
|
662
|
-
}} />;
|
|
663
|
-
|
|
664
|
-
<Cell width={40} height={40} decorations={{
|
|
665
|
-
tooltip: new Tooltip({ show: tooltip => tooltip.SetSpellByID(635) }),
|
|
666
|
-
}} />;
|
|
667
|
-
```
|
|
668
|
-
|
|
669
|
-
The same attachment works on SecureButton. Call SetItemByID, SetUnit, SetHyperlink
|
|
670
|
-
or SetAction inside `show` for other native content. `text` may be a function
|
|
671
|
-
evaluated on hover; `show` may return false to suppress display. Optional `target`
|
|
672
|
-
selects another native tooltip, and `anchor` defaults to ANCHOR_RIGHT.
|
|
673
|
-
Hover preserves click-through and existing scripts. Leave, hide and unmount close
|
|
674
|
-
only a tooltip still owned by that Cell. Use one decoration instance per Cell.
|
|
675
|
-
|
|
676
|
-
### Visual styling
|
|
677
|
-
|
|
678
|
-
<!-- Screenshot: a dark Cell with a thin colored border. -->
|
|
679
|
-
|
|
680
|
-
Configure backgrounds, borders, textures, and glow through reusable decoration objects.
|
|
681
|
-
|
|
682
|
-
```tsx
|
|
683
|
-
import { Background, Border } from "wuira/ui";
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
const view = (
|
|
687
|
-
<cell width={200} height={64} decorations={{
|
|
688
|
-
background: new Background({ color: { r: 0.06, g: 0.08, b: 0.1 } }),
|
|
689
|
-
border: new Border({ thickness: 1, color: { r: 0.3, g: 0.8, b: 0.7 } }),
|
|
690
|
-
}} />
|
|
691
|
-
);
|
|
692
|
-
|
|
693
|
-
Wuira.Mount(view);
|
|
694
|
-
```
|
|
695
|
-
|
|
696
|
-
### Addon tooling
|
|
697
|
-
|
|
698
|
-
<!-- Screenshot: creating and building an addon in the terminal. -->
|
|
699
|
-
|
|
700
|
-
Create a project, compile TypeScriptToLua, and generate the TOC and module-loading files through one CLI.
|
|
43
|
+
Requires Node.js 22.13 or newer.
|
|
701
44
|
|
|
702
45
|
```sh
|
|
703
46
|
npx wuira@latest create MyAddon
|
|
@@ -706,170 +49,78 @@ npm install
|
|
|
706
49
|
npm run build
|
|
707
50
|
```
|
|
708
51
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
<!-- Screenshot: source files alongside their emitted Lua modules. -->
|
|
712
|
-
|
|
713
|
-
Emitted modules remain split across files following the source structure.
|
|
714
|
-
|
|
715
|
-
```text
|
|
716
|
-
src/index.tsx → dist/index.lua
|
|
717
|
-
src/components/Panel.tsx → dist/components/Panel.lua
|
|
718
|
-
wuira/ui dependency → dist/lua_modules/wuira/lib/ui/index.lua
|
|
719
|
-
|
|
720
|
-
MyAddon.toc loads the module registry, emitted files, and startup entry.
|
|
721
|
-
```
|
|
722
|
-
|
|
723
|
-
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.
|
|
724
|
-
|
|
725
|
-
## Scope and current status
|
|
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`.
|
|
726
53
|
|
|
727
|
-
|
|
54
|
+
## Data sources
|
|
728
55
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
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.
|
|
732
|
-
|
|
733
|
-
## Getting started
|
|
734
|
-
|
|
735
|
-
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";
|
|
736
58
|
|
|
737
|
-
|
|
59
|
+
const hp = new HP(Unit.Player).UseHealPrediction().UseAbsorbPrediction();
|
|
60
|
+
const mana = new Mana(Unit.Player).UseRegenTimer();
|
|
738
61
|
|
|
739
|
-
|
|
740
|
-
|
|
62
|
+
<progress height={24} source={hp} />
|
|
63
|
+
<progress height={24} source={mana} />
|
|
741
64
|
```
|
|
742
65
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
## CLI
|
|
66
|
+
Other sources include `AuraTimer`, `GlobalCooldown`, `CastTimer`, `SpellCooldown`, `SwingTimer`, `GroupMembers`, `PartyMembers`, `RaidMembers`, `Buffs`, `Debuffs`, `KnownSpells`, `Spells`, `UnitSource`, `ClassSource`, `SpecSource`, and `SpellSource`.
|
|
746
67
|
|
|
747
|
-
|
|
68
|
+
## Secure buttons
|
|
748
69
|
|
|
749
|
-
```
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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>
|
|
754
75
|
```
|
|
755
76
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
`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`:
|
|
759
|
-
|
|
760
|
-
```tsx
|
|
761
|
-
import "wuira/console-polyfill";
|
|
762
|
-
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.
|
|
763
78
|
|
|
79
|
+
## Stores
|
|
764
80
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
81
|
+
```ts
|
|
82
|
+
class LumaSettings extends Store {
|
|
83
|
+
static readonly Token = "luma.settings";
|
|
84
|
+
CastSpellId = 1232;
|
|
85
|
+
Width = 220;
|
|
768
86
|
}
|
|
769
87
|
|
|
770
|
-
|
|
88
|
+
const store = new StoreRegistry({ addon: "LumaUI", variable: "LumaUIDB" });
|
|
89
|
+
store.onReady(() => console.log(store.get(LumaSettings).Width));
|
|
771
90
|
```
|
|
772
91
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
`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.
|
|
776
|
-
|
|
777
|
-
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.
|
|
778
93
|
|
|
779
94
|
## Build and distribution
|
|
780
95
|
|
|
781
|
-
`wuira build` transpiles through `tsconfig.json`. It writes
|
|
782
|
-
|
|
783
|
-
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.
|
|
784
|
-
|
|
785
|
-
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.
|
|
786
|
-
|
|
787
|
-
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`.
|
|
788
|
-
|
|
789
|
-
`npm run build` resolves the locally installed `wuira` command automatically. Use `npx wuira build` when invoking the CLI directly.
|
|
790
|
-
|
|
791
|
-
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.
|
|
792
97
|
|
|
793
98
|
```js
|
|
99
|
+
// wuira.config.mjs
|
|
794
100
|
export default {
|
|
795
101
|
name: "MyAddon",
|
|
796
102
|
outDir: "./dist",
|
|
797
|
-
// Or: outDir: "G:/Games/WoW/Interface/AddOns/MyAddon",
|
|
798
103
|
};
|
|
799
104
|
```
|
|
800
105
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
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.
|
|
804
|
-
|
|
805
|
-
## Stores
|
|
806
|
-
|
|
807
|
-
Use data classes to define defaults. Without a static token, records live only
|
|
808
|
-
in memory and are keyed by the class itself:
|
|
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.
|
|
809
107
|
|
|
810
|
-
|
|
811
|
-
import { Store, StoreRegistry } from "wuira/store";
|
|
812
|
-
|
|
813
|
-
class SessionSettings extends Store {
|
|
814
|
-
CastSpellId = 1232;
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
const store = new StoreRegistry();
|
|
818
|
-
const snapshot = store.get(SessionSettings); // One-time, deeply readonly copy.
|
|
819
|
-
|
|
820
|
-
const unsubscribe = store.subscribe(SessionSettings, settings => {
|
|
821
|
-
console.log("New spell:", settings.CastSpellId);
|
|
822
|
-
});
|
|
823
|
-
|
|
824
|
-
store.set(SessionSettings, settings => {
|
|
825
|
-
settings.CastSpellId = 635;
|
|
826
|
-
return settings;
|
|
827
|
-
});
|
|
108
|
+
## CLI
|
|
828
109
|
|
|
829
|
-
|
|
110
|
+
```sh
|
|
111
|
+
npx wuira create MyAddon
|
|
112
|
+
npx wuira build
|
|
830
113
|
```
|
|
831
114
|
|
|
832
|
-
`
|
|
833
|
-
unsubscribe function; call `get()` for initial data. Each subscriber receives an
|
|
834
|
-
independent snapshot. `set()` works on a detached draft, validates the returned
|
|
835
|
-
data, commits it, and sends one notification per observer. If the updater throws,
|
|
836
|
-
the previous record is preserved. Stores contain data fields, not class methods.
|
|
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.
|
|
837
116
|
|
|
838
|
-
|
|
117
|
+
## Scope and status
|
|
839
118
|
|
|
840
|
-
|
|
841
|
-
## SavedVariables: LumaUIDB
|
|
842
|
-
```
|
|
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.
|
|
843
120
|
|
|
844
|
-
|
|
845
|
-
class LumaSettings extends Store {
|
|
846
|
-
static readonly Token = "luma.settings";
|
|
847
|
-
CastSpellId = 1232;
|
|
848
|
-
Width = 220;
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
const store = new StoreRegistry({ addon: "LumaUI", variable: "LumaUIDB" });
|
|
852
|
-
store.onReady(() => {
|
|
853
|
-
console.log(store.get(LumaSettings).Width);
|
|
854
|
-
store.set(LumaSettings, settings => {
|
|
855
|
-
settings.Width = 240;
|
|
856
|
-
return settings;
|
|
857
|
-
});
|
|
858
|
-
});
|
|
859
|
-
```
|
|
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.
|
|
860
122
|
|
|
861
|
-
|
|
862
|
-
`ADDON_LOADED` before opening persistent records. Tokenless records can still be
|
|
863
|
-
used immediately. WoW writes the saved table on normal logout/reload; `set()`
|
|
864
|
-
does not force a disk flush. Keep tokens stable across versions. New default
|
|
865
|
-
fields are filled in and incompatible saved field types fall back to defaults;
|
|
866
|
-
renaming keys or changing schema meaning still requires an application migration.
|
|
867
|
-
|
|
868
|
-
Supported values are finite numbers, strings, booleans, arrays, and nested data
|
|
869
|
-
objects. Functions, cycles, and secret values cannot be saved. Two classes cannot
|
|
870
|
-
claim the same token within a registry. Share one registry instance per database.
|
|
871
|
-
`Dispose()` removes its observers and pending initialization listener. Profile
|
|
872
|
-
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.
|
|
873
124
|
|
|
874
125
|
## Development
|
|
875
126
|
|
|
@@ -880,36 +131,16 @@ npm run check
|
|
|
880
131
|
npm test
|
|
881
132
|
```
|
|
882
133
|
|
|
883
|
-
|
|
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.
|
|
884
135
|
|
|
885
|
-
|
|
136
|
+
## License
|
|
886
137
|
|
|
887
|
-
|
|
138
|
+
MIT — see [LICENSE](LICENSE).
|
|
888
139
|
|
|
889
140
|
## AI disclosure
|
|
890
141
|
|
|
891
|
-
wuira is developed with AI assistance for implementation, documentation, and
|
|
892
|
-
tests. AI-assisted code is reviewed line by line. Releases also undergo
|
|
893
|
-
TypeScript checks, Lua runtime tests, and package build validation. Project
|
|
894
|
-
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.
|
|
895
143
|
|
|
896
144
|
## Releases
|
|
897
145
|
|
|
898
|
-
See [CHANGELOG.md](CHANGELOG.md) for release history. Record new changes under
|
|
899
|
-
`Unreleased`, then move them into a dated version section before publishing.
|
|
900
|
-
|
|
901
|
-
The first version is published. To publish the next version locally, validate the project, increment the patch version, and publish:
|
|
902
|
-
|
|
903
|
-
```sh
|
|
904
|
-
npm run check
|
|
905
|
-
npm run build
|
|
906
|
-
npm test
|
|
907
|
-
npm version patch --no-git-tag-version
|
|
908
|
-
npm publish --access public
|
|
909
|
-
```
|
|
910
|
-
|
|
911
|
-
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.
|
|
912
|
-
|
|
913
|
-
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.
|
|
914
|
-
|
|
915
|
-
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.
|