@simpludify/web 0.1.3 → 0.1.4

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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "./style.css";
2
- import { ActionController, Aspect, Camera, CanvasButton as CanvasButton$1, CanvasUI, Engine, EngineError, FixedStepRunner, GeometryTilemapLayer, PointerRouting, Renderable, ReplayPlayer, Scene, ScenePointer, SceneState, Shape, TilemapLayer, Transform, canvasControlText, canvasHoverHighlight, canvasOpacity, canvasScrollbars, canvasSliderGeometry, cleanupAll, containsScreenPoint, createViewTransform, imageSize, intersectUI, isKeyCode, positive, screenPoint, screenToWorld, spriteFrameBounds, worldToScreen, wrapCanvasText } from "@simpludify/core";
2
+ import { ActionController, Aspect, Camera, CanvasButton as CanvasButton$1, CanvasUI, Engine, EngineError, EventBus, FixedStepRunner, GeometryTilemapLayer, Lifetime, PointerRouting, Renderable, ReplayPlayer, Scene, ScenePointer, SceneState, Shape, TilemapLayer, Transform, canvasControlText, canvasHoverHighlight, canvasOpacity, canvasScrollbars, canvasSliderGeometry, cleanupAll, containsScreenPoint, createViewTransform, defineEvent, imageSize, intersectUI, isKeyCode, positive, screenPoint, screenToWorld, spriteFrameBounds, worldToScreen, wrapCanvasText } from "@simpludify/core";
3
3
  import { Application, CanvasTextMetrics, Container, Graphics, Rectangle, RendererType, Sprite, Text, TextStyle, Texture } from "pixi.js";
4
4
  export * from "@simpludify/core";
5
5
  // packages/web/src/canvas-node.ts
@@ -17,6 +17,415 @@ function CanvasButton(text, click, style = {}) {
17
17
  return click(signal);
18
18
  }, style);
19
19
  }
20
+ // packages/web/src/gamepad-bindings.ts
21
+ /** Standard mapping physical positions, not product-specific printed labels. */
22
+ var GamepadButton = Object.freeze({
23
+ South: 0,
24
+ East: 1,
25
+ West: 2,
26
+ North: 3,
27
+ LeftShoulder: 4,
28
+ RightShoulder: 5,
29
+ LeftTrigger: 6,
30
+ RightTrigger: 7,
31
+ Select: 8,
32
+ Start: 9,
33
+ LeftStick: 10,
34
+ RightStick: 11,
35
+ DPadUp: 12,
36
+ DPadDown: 13,
37
+ DPadLeft: 14,
38
+ DPadRight: 15,
39
+ Home: 16
40
+ });
41
+ var GamepadAxis = Object.freeze({
42
+ LeftX: 0,
43
+ LeftY: 1,
44
+ RightX: 2,
45
+ RightY: 3
46
+ });
47
+ var axis = (index) => Object.freeze({ axis: index });
48
+ /** Ready-to-use data; game Actions retain application-defined names. */
49
+ var GamepadBindings = Object.freeze({
50
+ LeftStick: Object.freeze({
51
+ x: axis(0),
52
+ y: axis(1)
53
+ }),
54
+ RightStick: Object.freeze({
55
+ x: axis(2),
56
+ y: axis(3)
57
+ }),
58
+ DPad: Object.freeze({
59
+ x: Object.freeze({
60
+ negative: Object.freeze([14]),
61
+ positive: Object.freeze([15])
62
+ }),
63
+ y: Object.freeze({
64
+ negative: Object.freeze([12]),
65
+ positive: Object.freeze([13])
66
+ })
67
+ }),
68
+ Move: Object.freeze({
69
+ x: Object.freeze({
70
+ axis: 0,
71
+ negative: Object.freeze([14]),
72
+ positive: Object.freeze([15])
73
+ }),
74
+ y: Object.freeze({
75
+ axis: 1,
76
+ negative: Object.freeze([12]),
77
+ positive: Object.freeze([13])
78
+ })
79
+ })
80
+ });
81
+ var fail = () => {
82
+ throw new EngineError("INVALID_GAMEPAD_BINDING", "Invalid gamepad binding");
83
+ };
84
+ function gamepadIndex(value) {
85
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) return fail();
86
+ return value;
87
+ }
88
+ function gamepadDeadZone(value) {
89
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value >= 1) return fail();
90
+ return value;
91
+ }
92
+ function record(value, keys) {
93
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((k) => !keys.includes(k))) return fail();
94
+ return Object.fromEntries(Object.entries(value));
95
+ }
96
+ function axisBinding(value) {
97
+ const v = record(value, [
98
+ "axis",
99
+ "invert",
100
+ "negative",
101
+ "positive",
102
+ "deadZone"
103
+ ]);
104
+ const indices = (value) => {
105
+ if (value === void 0) return Object.freeze([]);
106
+ if (!Array.isArray(value)) return fail();
107
+ return Object.freeze([...new Set(value.map(gamepadIndex))]);
108
+ };
109
+ if (v.invert !== void 0 && typeof v.invert !== "boolean") return fail();
110
+ return Object.freeze({
111
+ ...v.axis === void 0 ? {} : { axis: gamepadIndex(v.axis) },
112
+ invert: v.invert ?? false,
113
+ negative: indices(v.negative),
114
+ positive: indices(v.positive),
115
+ ...v.deadZone === void 0 ? {} : { deadZone: gamepadDeadZone(v.deadZone) }
116
+ });
117
+ }
118
+ function checkedGamepadBindings(actions, value) {
119
+ const v = record(value, Object.keys(actions));
120
+ return Object.freeze(Object.entries(v).map(([name, binding]) => {
121
+ if (!Object.hasOwn(actions, name)) return fail();
122
+ const action = actions[name];
123
+ if (!action) return fail();
124
+ let result;
125
+ if (action.kind === "button") {
126
+ const b = record(binding, ["buttons"]);
127
+ if (b.buttons !== void 0 && !Array.isArray(b.buttons)) return fail();
128
+ result = Object.freeze({ buttons: Object.freeze((b.buttons ?? []).map((item) => {
129
+ if (typeof item === "number") return gamepadIndex(item);
130
+ const b = record(item, ["button", "threshold"]), threshold = b.threshold ?? .5;
131
+ if (typeof threshold !== "number" || !Number.isFinite(threshold) || threshold <= 0 || threshold > 1) return fail();
132
+ return Object.freeze({
133
+ button: gamepadIndex(b.button),
134
+ threshold
135
+ });
136
+ })) });
137
+ } else if (action.kind === "axis1d") result = axisBinding(binding);
138
+ else {
139
+ const keys = action.kind === "axis2d" ? ["x", "y"] : [
140
+ "x",
141
+ "y",
142
+ "z"
143
+ ], b = record(binding, keys);
144
+ result = Object.freeze(Object.fromEntries(keys.map((k) => [k, axisBinding(b[k] ?? {})])));
145
+ }
146
+ return Object.freeze([action, result]);
147
+ }));
148
+ }
149
+ // packages/web/src/gamepad-layout.ts
150
+ var positions = Object.fromEntries(Object.entries(GamepadButton).map(([name, index]) => [index, name]));
151
+ var faces = Object.freeze({
152
+ generic: Object.freeze([
153
+ "South",
154
+ "East",
155
+ "West",
156
+ "North"
157
+ ]),
158
+ xbox: Object.freeze([
159
+ "A",
160
+ "B",
161
+ "X",
162
+ "Y"
163
+ ]),
164
+ playstation: Object.freeze([
165
+ "×",
166
+ "○",
167
+ "□",
168
+ "△"
169
+ ]),
170
+ nintendo: Object.freeze([
171
+ "B",
172
+ "A",
173
+ "Y",
174
+ "X"
175
+ ])
176
+ });
177
+ function gamepadButtonLabel(button, options = {}) {
178
+ gamepadIndex(button);
179
+ const layout = options.layout ?? "generic";
180
+ if (!Object.hasOwn(faces, layout)) throw new EngineError("INVALID_GAMEPAD_BINDING", "Unknown gamepad display layout");
181
+ if (options.overrides && Object.hasOwn(options.overrides, button)) {
182
+ const label = options.overrides[button];
183
+ if (typeof label !== "string") throw new EngineError("INVALID_GAMEPAD_BINDING", "Button label must be a string");
184
+ return label;
185
+ }
186
+ return faces[layout][button] ?? positions[button] ?? `Button ${button}`;
187
+ }
188
+ function inferredGamepadLayout(id) {
189
+ if (/dualsense|dualshock|playstation|054c/i.test(id)) return "playstation";
190
+ if (/nintendo|switch|057e/i.test(id)) return "nintendo";
191
+ if (/xbox|xinput|045e/i.test(id)) return "xbox";
192
+ return "generic";
193
+ }
194
+ // packages/web/src/gamepads.ts
195
+ var finite = (v, min, max) => typeof v === "number" && Number.isFinite(v) ? Math.max(min, Math.min(max, v)) : 0;
196
+ var buttonValue = (pad, index) => finite(pad.buttons[index]?.value, 0, 1);
197
+ function scalar(pad, binding, deadZone) {
198
+ const group = (indices) => (indices ?? []).reduce((maximum, index) => Math.max(maximum, buttonValue(pad, index)), 0);
199
+ const raw = finite((binding.axis === void 0 ? 0 : finite(pad.axes[binding.axis], -1, 1)) + group(binding.positive) - group(binding.negative), -1, 1) * (binding.invert ? -1 : 1);
200
+ const zone = binding.deadZone ?? deadZone;
201
+ return Math.abs(raw) <= zone ? 0 : Math.sign(raw) * (Math.abs(raw) - zone) / (1 - zone);
202
+ }
203
+ function values(pad, bindings, zone) {
204
+ return bindings.map(([action, binding]) => {
205
+ if (action.kind === "button") return [action, binding.buttons?.some((b) => buttonValue(pad, typeof b === "number" ? b : b.button) >= (typeof b === "number" ? .5 : b.threshold ?? .5)) ?? false];
206
+ if (action.kind === "axis1d") return [action, scalar(pad, binding, zone)];
207
+ const b = binding, x = scalar(pad, b.x ?? {}, zone), y = scalar(pad, b.y ?? {}, zone);
208
+ return [action, action.kind === "axis2d" ? {
209
+ x,
210
+ y
211
+ } : {
212
+ x,
213
+ y,
214
+ z: scalar(pad, b.z ?? {}, zone)
215
+ }];
216
+ });
217
+ }
218
+ function neutral(value) {
219
+ return typeof value === "boolean" ? !value : typeof value === "number" ? value === 0 : value.x === 0 && value.y === 0 && (value.z ?? 0) === 0;
220
+ }
221
+ /** A controller-owned device contribution. Reassignment does not replace its readers/Actors. */
222
+ var GamepadSource = class {
223
+ input;
224
+ controller;
225
+ life = new Lifetime();
226
+ source;
227
+ bindings;
228
+ assigned;
229
+ armed = false;
230
+ signature;
231
+ zone;
232
+ mapping;
233
+ constructor(input, controller, options) {
234
+ this.input = input;
235
+ this.controller = controller;
236
+ input.assertController(controller);
237
+ this.assigned = gamepadIndex(options.index);
238
+ this.zone = gamepadDeadZone(options.deadZone ?? .15);
239
+ this.mapping = options.mapping ?? "standard";
240
+ if (this.mapping !== "standard" && this.mapping !== "raw") throw new EngineError("INVALID_GAMEPAD_BINDING", "Unknown gamepad mapping policy");
241
+ this.bindings = checkedGamepadBindings(controller.actions, options.bindings);
242
+ this.source = controller.createSource();
243
+ this.life.onDispose(() => this.source.dispose());
244
+ this.life.onDispose(controller.onDispose(() => this.dispose()));
245
+ }
246
+ get disposed() {
247
+ return this.life.disposed;
248
+ }
249
+ get enabled() {
250
+ return this.source.enabled;
251
+ }
252
+ set enabled(value) {
253
+ this.alive();
254
+ if (value === this.source.enabled) return;
255
+ this.source.enabled = value;
256
+ this.clear();
257
+ }
258
+ get index() {
259
+ return this.assigned;
260
+ }
261
+ get info() {
262
+ return this.input.devices.find((d) => d.index === this.assigned);
263
+ }
264
+ get connected() {
265
+ return this.info?.connected ?? false;
266
+ }
267
+ get usable() {
268
+ return this.connected && (this.mapping === "raw" || this.info?.standardMapping === true);
269
+ }
270
+ assign(index) {
271
+ this.alive();
272
+ const checked = gamepadIndex(index);
273
+ if (checked === this.assigned) return;
274
+ this.clear();
275
+ this.signature = void 0;
276
+ this.assigned = checked;
277
+ }
278
+ rebind(bindings) {
279
+ this.alive();
280
+ const checked = checkedGamepadBindings(this.controller.actions, bindings);
281
+ this.clear();
282
+ this.bindings = checked;
283
+ }
284
+ clear() {
285
+ this.source.clear();
286
+ this.armed = false;
287
+ }
288
+ alive() {
289
+ if (this.disposed) throw new EngineError("OWNER_DISPOSED", "Gamepad source has been disposed");
290
+ }
291
+ /** @internal */ sample(pad, allowed) {
292
+ if (this.disposed) return;
293
+ if (!allowed || !this.enabled || !this.controller.active || !pad || this.mapping === "standard" && pad.mapping !== "standard") {
294
+ this.clear();
295
+ this.signature = void 0;
296
+ return;
297
+ }
298
+ const signature = JSON.stringify([
299
+ pad.id,
300
+ pad.mapping,
301
+ pad.buttons.length,
302
+ pad.axes.length
303
+ ]);
304
+ if (signature !== this.signature) {
305
+ this.clear();
306
+ this.signature = signature;
307
+ }
308
+ const next = values(pad, this.bindings, this.zone);
309
+ if (!this.armed) {
310
+ if (next.every(([, value]) => neutral(value))) this.armed = true;
311
+ return;
312
+ }
313
+ for (const [action, value] of next) this.source.set(action, value);
314
+ }
315
+ /** @internal */ onDispose(callback) {
316
+ return this.life.onDispose(callback);
317
+ }
318
+ dispose() {
319
+ this.life.dispose();
320
+ }
321
+ };
322
+ var devicesChanged = defineEvent("GamepadsChanged");
323
+ /** One Web host adapter; multiple sources share a single snapshot per Engine input boundary. */
324
+ var GamepadInput = class {
325
+ input;
326
+ life = new Lifetime();
327
+ changes = new EventBus();
328
+ sources = /* @__PURE__ */ new Set();
329
+ disconnected = /* @__PURE__ */ new Set();
330
+ listed = Object.freeze([]);
331
+ readable = false;
332
+ poll;
333
+ active;
334
+ constructor(input, options = {}) {
335
+ this.input = input;
336
+ this.life.onDispose(() => this.changes.dispose());
337
+ this.poll = options.poll ?? (() => typeof navigator !== "undefined" && typeof navigator.getGamepads === "function" ? navigator.getGamepads() : (() => {
338
+ throw new Error("Gamepad API unavailable");
339
+ })());
340
+ this.active = options.active ?? (() => true);
341
+ this.life.onDispose(input.addAdapter(this));
342
+ const events = options.events ?? (typeof window === "undefined" ? void 0 : window), abort = new AbortController();
343
+ this.life.onDispose(() => abort.abort());
344
+ events?.addEventListener("gamepaddisconnected", (event) => {
345
+ const index = event.gamepad.index;
346
+ this.disconnected.add(index);
347
+ for (const source of this.sources) if (source.index === index) source.clear();
348
+ this.publish(this.listed.filter((d) => d.index !== index));
349
+ }, { signal: abort.signal });
350
+ events?.addEventListener("gamepadconnected", (event) => {
351
+ const index = event.gamepad.index;
352
+ this.disconnected.delete(index);
353
+ for (const source of this.sources) if (source.index === index) source.clear();
354
+ }, { signal: abort.signal });
355
+ }
356
+ get disposed() {
357
+ return this.life.disposed;
358
+ }
359
+ /** Whether the most recent polling attempt completed; an empty list may still need a user gesture. */
360
+ get available() {
361
+ return this.readable;
362
+ }
363
+ get devices() {
364
+ return this.listed;
365
+ }
366
+ onChange(handler, owner) {
367
+ return this.changes.on(devicesChanged, handler, owner);
368
+ }
369
+ publish(devices, force = false) {
370
+ const changed = JSON.stringify(this.listed) !== JSON.stringify(devices);
371
+ this.listed = Object.freeze(devices);
372
+ if (changed || force) this.changes.emit(devicesChanged, void 0);
373
+ }
374
+ /** @internal */ assertController(controller) {
375
+ if (!this.input.hasController(controller)) throw new EngineError("INVALID_ACTION", "Gamepad controller must belong to this InputSystem");
376
+ }
377
+ bind(controller, options) {
378
+ if (this.disposed) throw new EngineError("OWNER_DISPOSED", "Gamepad input has been disposed");
379
+ const source = new GamepadSource(this, controller, options);
380
+ const entry = source;
381
+ this.sources.add(entry);
382
+ const remove = this.life.onDispose(() => source.dispose());
383
+ source.onDispose(() => {
384
+ this.sources.delete(entry);
385
+ remove();
386
+ });
387
+ return source;
388
+ }
389
+ sample() {
390
+ if (this.disposed) return;
391
+ const wasReadable = this.readable;
392
+ let snapshots;
393
+ try {
394
+ snapshots = this.poll();
395
+ if (!Array.isArray(snapshots)) throw new Error("Invalid gamepad snapshot");
396
+ this.readable = true;
397
+ } catch {
398
+ snapshots = [];
399
+ this.readable = false;
400
+ }
401
+ const pads = /* @__PURE__ */ new Map();
402
+ for (const pad of snapshots) if (pad?.connected && typeof pad.id === "string" && typeof pad.mapping === "string" && Array.isArray(pad.axes) && Array.isArray(pad.buttons) && Number.isSafeInteger(pad.index) && pad.index >= 0) pads.set(pad.index, pad);
403
+ for (const index of this.disconnected) if (!pads.has(index)) this.disconnected.delete(index);
404
+ for (const index of this.disconnected) pads.delete(index);
405
+ const listed = Object.freeze([...pads.values()].sort((a, b) => a.index - b.index).map((p) => Object.freeze({
406
+ id: p.id,
407
+ index: p.index,
408
+ connected: p.connected,
409
+ mapping: p.mapping,
410
+ standardMapping: p.mapping === "standard",
411
+ buttonCount: p.buttons.length,
412
+ axisCount: p.axes.length,
413
+ inferredLayout: inferredGamepadLayout(p.id)
414
+ })));
415
+ const allowed = this.active();
416
+ for (const source of this.sources) if (source.disposed) this.sources.delete(source);
417
+ else source.sample(pads.get(source.index), allowed);
418
+ this.publish(listed, wasReadable !== this.readable);
419
+ }
420
+ reset() {
421
+ for (const source of this.sources) source.clear();
422
+ }
423
+ dispose() {
424
+ this.life.dispose();
425
+ this.sources.clear();
426
+ this.listed = Object.freeze([]);
427
+ }
428
+ };
20
429
  // packages/web/src/ui-keyboard.ts
21
430
  /** Host-only ownership metadata; native editing is never reconstructed from game keys. */
22
431
  var gameButtons = /* @__PURE__ */ new WeakSet();
@@ -459,7 +868,8 @@ function nativeGeometry(el, node, ui, clip) {
459
868
  el.disabled = !node.available || c.width <= 0 || c.height <= 0 || node.style.pointerEvents !== "auto";
460
869
  const background = style.background ?? 2316622;
461
870
  const color = `#${style.color.toString(16).padStart(6, "0")}`;
462
- el.style.cssText = `left:${b.x * scale}px;top:${b.y * scale}px;width:${b.width * scale}px;height:${b.height * scale}px;padding:${style.padding * scale}px;font-family:${style.fontFamily};font-size:${style.fontSize * scale}px;--s-native-color:${color};--s-native-background:#${background.toString(16).padStart(6, "0")};color-scheme:${(background >> 16) * .2126 + (background >> 8 & 255) * .7152 + (background & 255) * .0722 > 128 ? "light" : "dark"};clip-path:inset(${Math.max(0, c.y - b.y) * scale}px ${Math.max(0, b.x + b.width - c.x - c.width) * scale}px ${Math.max(0, b.y + b.height - c.y - c.height) * scale}px ${Math.max(0, c.x - b.x) * scale}px);`;
871
+ el.style.cssText = `left:${b.x * scale}px;top:${b.y * scale}px;width:${b.width * scale}px;height:${b.height * scale}px;padding:${style.padding * scale}px;font-size:${style.fontSize * scale}px;--s-native-color:${color};--s-native-background:#${background.toString(16).padStart(6, "0")};color-scheme:${(background >> 16) * .2126 + (background >> 8 & 255) * .7152 + (background & 255) * .0722 > 128 ? "light" : "dark"};clip-path:inset(${Math.max(0, c.y - b.y) * scale}px ${Math.max(0, b.x + b.width - c.x - c.width) * scale}px ${Math.max(0, b.y + b.height - c.y - c.height) * scale}px ${Math.max(0, c.x - b.x) * scale}px);`;
872
+ el.style.fontFamily = style.fontFamily;
463
873
  if (el.disabled && document.activeElement === el) el.blur();
464
874
  }
465
875
  /** Native editors must not cover later Canvas hit surfaces or overlay scrollbars. */
@@ -1684,7 +2094,15 @@ var PixiRenderer = class {
1684
2094
  }
1685
2095
  acquireImage(url, signal) {
1686
2096
  try {
1687
- return this.images.acquire(new URL(url, document.baseURI).href, signal);
2097
+ const parsed = new URL(url, document.baseURI);
2098
+ if (![
2099
+ "http:",
2100
+ "https:",
2101
+ "data:",
2102
+ "blob:",
2103
+ "file:"
2104
+ ].includes(parsed.protocol)) throw new Error("Unsupported image URL protocol");
2105
+ return this.images.acquire(parsed.href, signal);
1688
2106
  } catch {
1689
2107
  return Promise.reject(new EngineError("ASSET_LOAD_FAILED", "Invalid image URL", { path: url }));
1690
2108
  }
@@ -2342,6 +2760,7 @@ var Game = class {
2342
2760
  engine;
2343
2761
  element;
2344
2762
  camera;
2763
+ gamepads;
2345
2764
  renderer;
2346
2765
  keyboard;
2347
2766
  pointers;
@@ -2425,8 +2844,10 @@ var Game = class {
2425
2844
  camera: this.camera
2426
2845
  });
2427
2846
  if (options.pixelRatio !== void 0) positive(options.pixelRatio, "pixelRatio");
2428
- const theme = Themes[options.theme ?? Theme.Dark];
2429
- if (!theme) throw new EngineError("INVALID_THEME", "Unknown theme");
2847
+ const themeName = options.theme ?? Theme.Dark;
2848
+ if (!Object.hasOwn(Themes, themeName)) throw new EngineError("INVALID_THEME", "Unknown theme");
2849
+ const theme = Themes[themeName];
2850
+ for (const [name, value] of Object.entries(options.tokens ?? {})) if (!Object.hasOwn(theme, name) || typeof value !== "string" || (name === "font" ? !validFontFamily(value) : !value.trim().length || /[;{}<>\r\n\0]|url\s*\(/i.test(value) || !CSS.supports("color", value))) throw new EngineError("INVALID_THEME_TOKEN", `Invalid token: ${name}`);
2430
2851
  const tokens = {
2431
2852
  ...theme,
2432
2853
  ...options.tokens
@@ -2454,6 +2875,7 @@ var Game = class {
2454
2875
  cancel: (handle) => cancelAnimationFrame(handle)
2455
2876
  };
2456
2877
  this.keyboard = new BrowserInput(this.element, this.engine.input);
2878
+ this.gamepads = new GamepadInput(this.engine.input, { active: () => !document.hidden && document.hasFocus() && this.element.contains(document.activeElement) && !blocksGameKeyboard(document.activeElement) });
2457
2879
  this.pointers = new BrowserPointer(this.element, (event) => {
2458
2880
  const rect = this.element.getBoundingClientRect();
2459
2881
  if (rect.width <= 0 || rect.height <= 0) throw new EngineError("VIEWPORT_UNAVAILABLE", "Game has no visible bounds");
@@ -2929,6 +3351,7 @@ var Game = class {
2929
3351
  () => this.stopReplay(),
2930
3352
  () => this.staging?.dispose(),
2931
3353
  () => this.pointers.dispose(),
3354
+ () => this.gamepads.dispose(),
2932
3355
  () => this.keyboard.dispose(),
2933
3356
  () => this.canvasPresentation?.dispose(),
2934
3357
  () => this.canvas?.dispose(),
@@ -2954,6 +3377,10 @@ var Game = class {
2954
3377
  this.pendingScenes.clear();
2955
3378
  }
2956
3379
  };
2957
- export { BrowserInput, Button, CanvasButton, Game, HoldButton, Label, Layout, Panel, PixiRenderer, Placement, Theme, Themes, browserAbortSignal, mountUI };
3380
+ function validFontFamily(value) {
3381
+ const syntax = value.replace(/"(?:\\[^\r\n]|[^"\\])*"|'(?:\\[^\r\n]|[^'\\])*'/g, "family").replace(/\\(?:[0-9a-f]{1,6}[ \t]?|[^\r\n])/gi, "x");
3382
+ return value.trim().length > 0 && !/[\r\n\0]/.test(value) && !/[;{}<>\\"']|url\s*\(/i.test(syntax);
3383
+ }
3384
+ export { BrowserInput, Button, CanvasButton, Game, GamepadAxis, GamepadBindings, GamepadButton, GamepadInput, GamepadSource, HoldButton, Label, Layout, Panel, PixiRenderer, Placement, Theme, Themes, browserAbortSignal, gamepadButtonLabel, mountUI };
2958
3385
 
2959
3386
  //# sourceMappingURL=index.js.map