@penabt/pixi-expo 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -160,11 +160,28 @@ const sprite = new Sprite(bunny);
160
160
 
161
161
  // Remote asset via URL
162
162
  const remote = await Assets.load('https://example.com/sprite.png');
163
+ ```
164
+
165
+ ### Array Loading — `Assets.load([])`
166
+
167
+ Load multiple assets at once with array destructuring:
168
+
169
+ ```tsx
170
+ import { Assets, Sprite } from '@penabt/pixi-expo';
163
171
 
164
- // Multiple assets
165
- const [a, b] = await Assets.load([require('./assets/a.png'), 'https://example.com/b.png']);
172
+ // Mix require() and remote URLs in a single call
173
+ const [frame1, frame2, enemy] = await Assets.load([
174
+ require('./assets/frame-1.png'),
175
+ require('./assets/frame-2.png'),
176
+ 'https://example.com/enemy.png',
177
+ ]);
178
+
179
+ const sprite1 = new Sprite(frame1);
180
+ const sprite2 = new Sprite(frame2);
166
181
  ```
167
182
 
183
+ > **Note:** Unlike standard PixiJS (which returns a `Record<string, Texture>` for arrays), `@penabt/pixi-expo` returns an array in the same order as the input — enabling convenient destructuring.
184
+
168
185
  ### Manifest & Bundles — `createExpoManifest()`
169
186
 
170
187
  For larger projects, group assets into bundles and load them on demand:
package/dist/index.d.mts CHANGED
@@ -368,11 +368,9 @@ declare const ExpoAdapter: {
368
368
  /**
369
369
  * Get the Canvas 2D rendering context constructor.
370
370
  *
371
- * @returns null - Canvas 2D is not supported in expo-gl
372
- *
373
- * @remarks
374
- * expo-gl only provides WebGL contexts. For 2D canvas operations,
375
- * consider using @shopify/react-native-skia as an alternative.
371
+ * Returns a mock constructor whose instances provide the mock Canvas2D
372
+ * context. PixiJS accesses `CanvasRenderingContext2D.prototype` for
373
+ * feature detection, so returning `null` causes a crash.
376
374
  */
377
375
  getCanvasRenderingContext2D: () => any;
378
376
  /**
package/dist/index.d.ts CHANGED
@@ -368,11 +368,9 @@ declare const ExpoAdapter: {
368
368
  /**
369
369
  * Get the Canvas 2D rendering context constructor.
370
370
  *
371
- * @returns null - Canvas 2D is not supported in expo-gl
372
- *
373
- * @remarks
374
- * expo-gl only provides WebGL contexts. For 2D canvas operations,
375
- * consider using @shopify/react-native-skia as an alternative.
371
+ * Returns a mock constructor whose instances provide the mock Canvas2D
372
+ * context. PixiJS accesses `CanvasRenderingContext2D.prototype` for
373
+ * feature detection, so returning `null` causes a crash.
376
374
  */
377
375
  getCanvasRenderingContext2D: () => any;
378
376
  /**
package/dist/index.js CHANGED
@@ -86,6 +86,160 @@ __export(index_exports, {
86
86
  });
87
87
  module.exports = __toCommonJS(index_exports);
88
88
 
89
+ // src/adapter/canvas2d.ts
90
+ function parseFontSize(font) {
91
+ const match = font.match(/(\d+(?:\.\d+)?)\s*px/);
92
+ return match ? parseFloat(match[1]) : 10;
93
+ }
94
+ function createMockGradient() {
95
+ return { addColorStop: () => {
96
+ } };
97
+ }
98
+ function createMockCanvas2DContext(canvas) {
99
+ let _font = "10px sans-serif";
100
+ const ctx = {
101
+ // -- Back-reference to owning canvas --
102
+ canvas,
103
+ // -- Font / text properties (critical for CanvasTextMetrics) --
104
+ get font() {
105
+ return _font;
106
+ },
107
+ set font(value) {
108
+ _font = value;
109
+ },
110
+ fillStyle: "#000",
111
+ strokeStyle: "#000",
112
+ lineWidth: 1,
113
+ lineCap: "butt",
114
+ lineJoin: "miter",
115
+ miterLimit: 10,
116
+ textAlign: "start",
117
+ textBaseline: "alphabetic",
118
+ direction: "ltr",
119
+ globalAlpha: 1,
120
+ globalCompositeOperation: "source-over",
121
+ imageSmoothingEnabled: true,
122
+ shadowBlur: 0,
123
+ shadowColor: "rgba(0, 0, 0, 0)",
124
+ shadowOffsetX: 0,
125
+ shadowOffsetY: 0,
126
+ letterSpacing: "0px",
127
+ wordSpacing: "0px",
128
+ fontKerning: "auto",
129
+ textRendering: "auto",
130
+ // -- Text measurement (primary reason this mock exists) --
131
+ measureText(text) {
132
+ const fontSize = parseFontSize(_font);
133
+ const width = text.length * fontSize * 0.6;
134
+ const ascent = fontSize * 0.8;
135
+ const descent = fontSize * 0.2;
136
+ return {
137
+ width,
138
+ actualBoundingBoxAscent: ascent,
139
+ actualBoundingBoxDescent: descent,
140
+ fontBoundingBoxAscent: ascent,
141
+ fontBoundingBoxDescent: descent,
142
+ actualBoundingBoxLeft: 0,
143
+ actualBoundingBoxRight: width,
144
+ alphabeticBaseline: 0,
145
+ emHeightAscent: ascent,
146
+ emHeightDescent: descent,
147
+ hangingBaseline: ascent * 0.8,
148
+ ideographicBaseline: -descent
149
+ };
150
+ },
151
+ // -- Text drawing (no-op) --
152
+ fillText: () => {
153
+ },
154
+ strokeText: () => {
155
+ },
156
+ // -- State stack --
157
+ save: () => {
158
+ },
159
+ restore: () => {
160
+ },
161
+ // -- Rect operations (no-op) --
162
+ clearRect: () => {
163
+ },
164
+ fillRect: () => {
165
+ },
166
+ strokeRect: () => {
167
+ },
168
+ // -- Path operations (no-op) --
169
+ beginPath: () => {
170
+ },
171
+ closePath: () => {
172
+ },
173
+ moveTo: () => {
174
+ },
175
+ lineTo: () => {
176
+ },
177
+ bezierCurveTo: () => {
178
+ },
179
+ quadraticCurveTo: () => {
180
+ },
181
+ arc: () => {
182
+ },
183
+ arcTo: () => {
184
+ },
185
+ ellipse: () => {
186
+ },
187
+ rect: () => {
188
+ },
189
+ roundRect: () => {
190
+ },
191
+ fill: () => {
192
+ },
193
+ stroke: () => {
194
+ },
195
+ clip: () => {
196
+ },
197
+ isPointInPath: () => false,
198
+ isPointInStroke: () => false,
199
+ // -- Transform (no-op) --
200
+ scale: () => {
201
+ },
202
+ rotate: () => {
203
+ },
204
+ translate: () => {
205
+ },
206
+ transform: () => {
207
+ },
208
+ setTransform: () => {
209
+ },
210
+ getTransform: () => ({ a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }),
211
+ resetTransform: () => {
212
+ },
213
+ // -- Drawing images (no-op) --
214
+ drawImage: () => {
215
+ },
216
+ // -- Pixel manipulation --
217
+ createImageData: (w, h) => ({
218
+ width: w,
219
+ height: h,
220
+ data: new Uint8ClampedArray(w * h * 4)
221
+ }),
222
+ getImageData: (_x, _y, w, h) => ({
223
+ width: w,
224
+ height: h,
225
+ data: new Uint8ClampedArray(w * h * 4)
226
+ }),
227
+ putImageData: () => {
228
+ },
229
+ // -- Gradients / Patterns --
230
+ createLinearGradient: () => createMockGradient(),
231
+ createRadialGradient: () => createMockGradient(),
232
+ createConicGradient: () => createMockGradient(),
233
+ createPattern: () => null,
234
+ // -- Line dash --
235
+ setLineDash: () => {
236
+ },
237
+ getLineDash: () => [],
238
+ lineDashOffset: 0
239
+ };
240
+ return ctx;
241
+ }
242
+
89
243
  // src/adapter/polyfills.ts
90
244
  if (typeof globalThis.requestAnimationFrame === "undefined") {
91
245
  globalThis.requestAnimationFrame = function requestAnimationFrame(callback) {
@@ -122,7 +276,7 @@ if (typeof globalThis.dispatchEvent === "undefined") {
122
276
  try {
123
277
  listener(event);
124
278
  } catch (e) {
125
- console.error("Error in event listener:", e);
279
+ if (__DEV__) console.error("Error in event listener:", e);
126
280
  }
127
281
  });
128
282
  }
@@ -157,8 +311,10 @@ var createMockElement = (tagName) => {
157
311
  const element = {
158
312
  tagName: tagName.toUpperCase(),
159
313
  style: createMockStyle(),
160
- getContext: () => null,
161
- // Canvas 2D not supported - use WebGL via expo-gl
314
+ getContext: (type) => {
315
+ if (type === "2d") return createMockCanvas2DContext(element);
316
+ return null;
317
+ },
162
318
  width: 0,
163
319
  height: 0,
164
320
  addEventListener: () => {
@@ -316,7 +472,7 @@ function dispatchWindowEvent(event) {
316
472
  listener.handleEvent(event);
317
473
  }
318
474
  } catch (error) {
319
- console.error("[Window] Error in event listener:", error);
475
+ if (__DEV__) console.error("[Window] Error in event listener:", error);
320
476
  }
321
477
  });
322
478
  return true;
@@ -387,7 +543,8 @@ if (typeof globalThis.HTMLCanvasElement === "undefined") {
387
543
  this.height = 0;
388
544
  this.style = createMockStyle();
389
545
  }
390
- getContext() {
546
+ getContext(type) {
547
+ if (type === "2d") return createMockCanvas2DContext(this);
391
548
  return null;
392
549
  }
393
550
  };
@@ -506,7 +663,7 @@ if (!("ontouchmove" in globalThis)) {
506
663
  if (!("ontouchend" in globalThis)) {
507
664
  globalThis.ontouchend = null;
508
665
  }
509
- console.log("PixiJS Expo Adapter: Polyfills loaded");
666
+ if (__DEV__) console.log("PixiJS Expo Adapter: Polyfills loaded");
510
667
 
511
668
  // src/adapter/ExpoCanvasElement.ts
512
669
  var MockCanvasStyle = class {
@@ -707,22 +864,21 @@ var ExpoCanvasElement = class {
707
864
  case "webgl":
708
865
  case "experimental-webgl":
709
866
  if (!this._gl) {
710
- console.warn(
711
- "ExpoCanvasElement: WebGL context not available. Make sure GLView.onContextCreate has been called first."
712
- );
867
+ if (__DEV__)
868
+ console.warn(
869
+ "ExpoCanvasElement: WebGL context not available. Make sure GLView.onContextCreate has been called first."
870
+ );
713
871
  return null;
714
872
  }
715
873
  return this._gl;
716
874
  case "2d":
717
- console.warn(
718
- "ExpoCanvasElement: 2D context is not supported in expo-gl. Consider using @shopify/react-native-skia for 2D rendering."
719
- );
720
- return null;
875
+ return createMockCanvas2DContext(this);
721
876
  case "webgl2":
722
877
  case "experimental-webgl2":
723
- console.warn(
724
- "ExpoCanvasElement: WebGL2 is not fully supported in expo-gl. Falling back to WebGL1."
725
- );
878
+ if (__DEV__)
879
+ console.warn(
880
+ "ExpoCanvasElement: WebGL2 is not fully supported in expo-gl. Falling back to WebGL1."
881
+ );
726
882
  return this._gl;
727
883
  default:
728
884
  return null;
@@ -745,14 +901,15 @@ var ExpoCanvasElement = class {
745
901
  */
746
902
  toDataURL(_type, _quality) {
747
903
  if (!this._gl) {
748
- console.warn("ExpoCanvasElement: Cannot create data URL without GL context");
904
+ if (__DEV__) console.warn("ExpoCanvasElement: Cannot create data URL without GL context");
749
905
  return "";
750
906
  }
751
907
  const width = this._width;
752
908
  const height = this._height;
753
909
  const pixels = new Uint8Array(width * height * 4);
754
910
  this._gl.readPixels(0, 0, width, height, this._gl.RGBA, this._gl.UNSIGNED_BYTE, pixels);
755
- console.warn("ExpoCanvasElement: toDataURL requires additional implementation for encoding");
911
+ if (__DEV__)
912
+ console.warn("ExpoCanvasElement: toDataURL requires additional implementation for encoding");
756
913
  return "";
757
914
  }
758
915
  /**
@@ -764,7 +921,7 @@ var ExpoCanvasElement = class {
764
921
  * @param _quality - Image quality (ignored)
765
922
  */
766
923
  toBlob(callback, _type, _quality) {
767
- console.warn("ExpoCanvasElement: toBlob is not implemented");
924
+ if (__DEV__) console.warn("ExpoCanvasElement: toBlob is not implemented");
768
925
  callback(null);
769
926
  }
770
927
  // ===========================================================================
@@ -833,7 +990,7 @@ var ExpoCanvasElement = class {
833
990
  listener.handleEvent(event);
834
991
  }
835
992
  } catch (error) {
836
- console.error("[ExpoCanvasElement] Error in event listener:", error);
993
+ if (__DEV__) console.error("[ExpoCanvasElement] Error in event listener:", error);
837
994
  }
838
995
  });
839
996
  return true;
@@ -940,15 +1097,16 @@ var ExpoAdapter = {
940
1097
  /**
941
1098
  * Get the Canvas 2D rendering context constructor.
942
1099
  *
943
- * @returns null - Canvas 2D is not supported in expo-gl
944
- *
945
- * @remarks
946
- * expo-gl only provides WebGL contexts. For 2D canvas operations,
947
- * consider using @shopify/react-native-skia as an alternative.
1100
+ * Returns a mock constructor whose instances provide the mock Canvas2D
1101
+ * context. PixiJS accesses `CanvasRenderingContext2D.prototype` for
1102
+ * feature detection, so returning `null` causes a crash.
948
1103
  */
949
1104
  getCanvasRenderingContext2D: () => {
950
- console.warn("ExpoAdapter: 2D context is not supported in expo-gl");
951
- return null;
1105
+ return class MockCanvasRenderingContext2D {
1106
+ constructor() {
1107
+ return createMockCanvas2DContext({ width: 0, height: 0 });
1108
+ }
1109
+ };
952
1110
  },
953
1111
  /**
954
1112
  * Get the WebGL rendering context constructor.
@@ -1026,11 +1184,11 @@ var ExpoAdapter = {
1026
1184
  return fetch(requestUrl, options);
1027
1185
  }
1028
1186
  if (requestUrl.startsWith("file://")) {
1029
- console.warn("ExpoAdapter: Local file loading requires expo-file-system");
1187
+ if (__DEV__) console.warn("ExpoAdapter: Local file loading requires expo-file-system");
1030
1188
  return fetch(requestUrl, options);
1031
1189
  }
1032
1190
  if (requestUrl.startsWith("asset://") || typeof url === "number") {
1033
- console.warn("ExpoAdapter: Asset loading requires expo-asset");
1191
+ if (__DEV__) console.warn("ExpoAdapter: Asset loading requires expo-asset");
1034
1192
  throw new Error("Asset loading not implemented");
1035
1193
  }
1036
1194
  return fetch(requestUrl, options);
@@ -1212,7 +1370,7 @@ var loadExpoAsset = {
1212
1370
  });
1213
1371
  return new import_pixi.Texture({ source });
1214
1372
  } catch (error) {
1215
- console.error(`[loadExpoAsset] Failed to load: ${url}`, error);
1373
+ if (__DEV__) console.error(`[loadExpoAsset] Failed to load: ${url}`, error);
1216
1374
  throw error;
1217
1375
  }
1218
1376
  },
@@ -1282,10 +1440,10 @@ var loadExpoFont = {
1282
1440
  await Font.loadAsync({
1283
1441
  [familyName]: fontSource
1284
1442
  });
1285
- console.log(`Font loaded: ${familyName}`);
1443
+ if (__DEV__) console.log(`Font loaded: ${familyName}`);
1286
1444
  return familyName;
1287
1445
  } catch (error) {
1288
- console.error(`Failed to load font: ${url}`, error);
1446
+ if (__DEV__) console.error(`Failed to load font: ${url}`, error);
1289
1447
  throw error;
1290
1448
  }
1291
1449
  },
@@ -1294,7 +1452,7 @@ var loadExpoFont = {
1294
1452
  * Note: expo-font doesn't have an unload API
1295
1453
  */
1296
1454
  unload(fontFamily) {
1297
- console.warn(`Font unloading is not supported in expo-font: ${fontFamily}`);
1455
+ if (__DEV__) console.warn(`Font unloading is not supported in expo-font: ${fontFamily}`);
1298
1456
  }
1299
1457
  };
1300
1458
 
@@ -1637,7 +1795,7 @@ var PixiView = (0, import_react.forwardRef)((props, ref) => {
1637
1795
  });
1638
1796
  onApplicationCreate?.(app);
1639
1797
  } catch (error) {
1640
- console.error("PixiJS initialization error:", error);
1798
+ if (__DEV__) console.error("PixiJS initialization error:", error);
1641
1799
  onError?.(error);
1642
1800
  }
1643
1801
  },
@@ -1653,7 +1811,7 @@ var PixiView = (0, import_react.forwardRef)((props, ref) => {
1653
1811
  try {
1654
1812
  appRef.current.destroy(true, { children: true });
1655
1813
  } catch (error) {
1656
- console.warn("Error destroying PixiJS application:", error);
1814
+ if (__DEV__) console.warn("Error destroying PixiJS application:", error);
1657
1815
  }
1658
1816
  appRef.current = null;
1659
1817
  }
@@ -1707,7 +1865,9 @@ import_pixi4.Assets.load = function patchedLoad(urls, onProgress) {
1707
1865
  }
1708
1866
  if (Array.isArray(urls)) {
1709
1867
  const resolved = urls.map((u) => typeof u === "number" ? registerModuleId(u) : u);
1710
- return _originalLoad(resolved, onProgress);
1868
+ return _originalLoad(resolved, onProgress).then((record) => {
1869
+ return resolved.map((key) => record[key]);
1870
+ });
1711
1871
  }
1712
1872
  return _originalLoad(urls, onProgress);
1713
1873
  };
package/dist/index.mjs CHANGED
@@ -1,3 +1,157 @@
1
+ // src/adapter/canvas2d.ts
2
+ function parseFontSize(font) {
3
+ const match = font.match(/(\d+(?:\.\d+)?)\s*px/);
4
+ return match ? parseFloat(match[1]) : 10;
5
+ }
6
+ function createMockGradient() {
7
+ return { addColorStop: () => {
8
+ } };
9
+ }
10
+ function createMockCanvas2DContext(canvas) {
11
+ let _font = "10px sans-serif";
12
+ const ctx = {
13
+ // -- Back-reference to owning canvas --
14
+ canvas,
15
+ // -- Font / text properties (critical for CanvasTextMetrics) --
16
+ get font() {
17
+ return _font;
18
+ },
19
+ set font(value) {
20
+ _font = value;
21
+ },
22
+ fillStyle: "#000",
23
+ strokeStyle: "#000",
24
+ lineWidth: 1,
25
+ lineCap: "butt",
26
+ lineJoin: "miter",
27
+ miterLimit: 10,
28
+ textAlign: "start",
29
+ textBaseline: "alphabetic",
30
+ direction: "ltr",
31
+ globalAlpha: 1,
32
+ globalCompositeOperation: "source-over",
33
+ imageSmoothingEnabled: true,
34
+ shadowBlur: 0,
35
+ shadowColor: "rgba(0, 0, 0, 0)",
36
+ shadowOffsetX: 0,
37
+ shadowOffsetY: 0,
38
+ letterSpacing: "0px",
39
+ wordSpacing: "0px",
40
+ fontKerning: "auto",
41
+ textRendering: "auto",
42
+ // -- Text measurement (primary reason this mock exists) --
43
+ measureText(text) {
44
+ const fontSize = parseFontSize(_font);
45
+ const width = text.length * fontSize * 0.6;
46
+ const ascent = fontSize * 0.8;
47
+ const descent = fontSize * 0.2;
48
+ return {
49
+ width,
50
+ actualBoundingBoxAscent: ascent,
51
+ actualBoundingBoxDescent: descent,
52
+ fontBoundingBoxAscent: ascent,
53
+ fontBoundingBoxDescent: descent,
54
+ actualBoundingBoxLeft: 0,
55
+ actualBoundingBoxRight: width,
56
+ alphabeticBaseline: 0,
57
+ emHeightAscent: ascent,
58
+ emHeightDescent: descent,
59
+ hangingBaseline: ascent * 0.8,
60
+ ideographicBaseline: -descent
61
+ };
62
+ },
63
+ // -- Text drawing (no-op) --
64
+ fillText: () => {
65
+ },
66
+ strokeText: () => {
67
+ },
68
+ // -- State stack --
69
+ save: () => {
70
+ },
71
+ restore: () => {
72
+ },
73
+ // -- Rect operations (no-op) --
74
+ clearRect: () => {
75
+ },
76
+ fillRect: () => {
77
+ },
78
+ strokeRect: () => {
79
+ },
80
+ // -- Path operations (no-op) --
81
+ beginPath: () => {
82
+ },
83
+ closePath: () => {
84
+ },
85
+ moveTo: () => {
86
+ },
87
+ lineTo: () => {
88
+ },
89
+ bezierCurveTo: () => {
90
+ },
91
+ quadraticCurveTo: () => {
92
+ },
93
+ arc: () => {
94
+ },
95
+ arcTo: () => {
96
+ },
97
+ ellipse: () => {
98
+ },
99
+ rect: () => {
100
+ },
101
+ roundRect: () => {
102
+ },
103
+ fill: () => {
104
+ },
105
+ stroke: () => {
106
+ },
107
+ clip: () => {
108
+ },
109
+ isPointInPath: () => false,
110
+ isPointInStroke: () => false,
111
+ // -- Transform (no-op) --
112
+ scale: () => {
113
+ },
114
+ rotate: () => {
115
+ },
116
+ translate: () => {
117
+ },
118
+ transform: () => {
119
+ },
120
+ setTransform: () => {
121
+ },
122
+ getTransform: () => ({ a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }),
123
+ resetTransform: () => {
124
+ },
125
+ // -- Drawing images (no-op) --
126
+ drawImage: () => {
127
+ },
128
+ // -- Pixel manipulation --
129
+ createImageData: (w, h) => ({
130
+ width: w,
131
+ height: h,
132
+ data: new Uint8ClampedArray(w * h * 4)
133
+ }),
134
+ getImageData: (_x, _y, w, h) => ({
135
+ width: w,
136
+ height: h,
137
+ data: new Uint8ClampedArray(w * h * 4)
138
+ }),
139
+ putImageData: () => {
140
+ },
141
+ // -- Gradients / Patterns --
142
+ createLinearGradient: () => createMockGradient(),
143
+ createRadialGradient: () => createMockGradient(),
144
+ createConicGradient: () => createMockGradient(),
145
+ createPattern: () => null,
146
+ // -- Line dash --
147
+ setLineDash: () => {
148
+ },
149
+ getLineDash: () => [],
150
+ lineDashOffset: 0
151
+ };
152
+ return ctx;
153
+ }
154
+
1
155
  // src/adapter/polyfills.ts
2
156
  if (typeof globalThis.requestAnimationFrame === "undefined") {
3
157
  globalThis.requestAnimationFrame = function requestAnimationFrame(callback) {
@@ -34,7 +188,7 @@ if (typeof globalThis.dispatchEvent === "undefined") {
34
188
  try {
35
189
  listener(event);
36
190
  } catch (e) {
37
- console.error("Error in event listener:", e);
191
+ if (__DEV__) console.error("Error in event listener:", e);
38
192
  }
39
193
  });
40
194
  }
@@ -69,8 +223,10 @@ var createMockElement = (tagName) => {
69
223
  const element = {
70
224
  tagName: tagName.toUpperCase(),
71
225
  style: createMockStyle(),
72
- getContext: () => null,
73
- // Canvas 2D not supported - use WebGL via expo-gl
226
+ getContext: (type) => {
227
+ if (type === "2d") return createMockCanvas2DContext(element);
228
+ return null;
229
+ },
74
230
  width: 0,
75
231
  height: 0,
76
232
  addEventListener: () => {
@@ -228,7 +384,7 @@ function dispatchWindowEvent(event) {
228
384
  listener.handleEvent(event);
229
385
  }
230
386
  } catch (error) {
231
- console.error("[Window] Error in event listener:", error);
387
+ if (__DEV__) console.error("[Window] Error in event listener:", error);
232
388
  }
233
389
  });
234
390
  return true;
@@ -299,7 +455,8 @@ if (typeof globalThis.HTMLCanvasElement === "undefined") {
299
455
  this.height = 0;
300
456
  this.style = createMockStyle();
301
457
  }
302
- getContext() {
458
+ getContext(type) {
459
+ if (type === "2d") return createMockCanvas2DContext(this);
303
460
  return null;
304
461
  }
305
462
  };
@@ -418,7 +575,7 @@ if (!("ontouchmove" in globalThis)) {
418
575
  if (!("ontouchend" in globalThis)) {
419
576
  globalThis.ontouchend = null;
420
577
  }
421
- console.log("PixiJS Expo Adapter: Polyfills loaded");
578
+ if (__DEV__) console.log("PixiJS Expo Adapter: Polyfills loaded");
422
579
 
423
580
  // src/adapter/ExpoCanvasElement.ts
424
581
  var MockCanvasStyle = class {
@@ -619,22 +776,21 @@ var ExpoCanvasElement = class {
619
776
  case "webgl":
620
777
  case "experimental-webgl":
621
778
  if (!this._gl) {
622
- console.warn(
623
- "ExpoCanvasElement: WebGL context not available. Make sure GLView.onContextCreate has been called first."
624
- );
779
+ if (__DEV__)
780
+ console.warn(
781
+ "ExpoCanvasElement: WebGL context not available. Make sure GLView.onContextCreate has been called first."
782
+ );
625
783
  return null;
626
784
  }
627
785
  return this._gl;
628
786
  case "2d":
629
- console.warn(
630
- "ExpoCanvasElement: 2D context is not supported in expo-gl. Consider using @shopify/react-native-skia for 2D rendering."
631
- );
632
- return null;
787
+ return createMockCanvas2DContext(this);
633
788
  case "webgl2":
634
789
  case "experimental-webgl2":
635
- console.warn(
636
- "ExpoCanvasElement: WebGL2 is not fully supported in expo-gl. Falling back to WebGL1."
637
- );
790
+ if (__DEV__)
791
+ console.warn(
792
+ "ExpoCanvasElement: WebGL2 is not fully supported in expo-gl. Falling back to WebGL1."
793
+ );
638
794
  return this._gl;
639
795
  default:
640
796
  return null;
@@ -657,14 +813,15 @@ var ExpoCanvasElement = class {
657
813
  */
658
814
  toDataURL(_type, _quality) {
659
815
  if (!this._gl) {
660
- console.warn("ExpoCanvasElement: Cannot create data URL without GL context");
816
+ if (__DEV__) console.warn("ExpoCanvasElement: Cannot create data URL without GL context");
661
817
  return "";
662
818
  }
663
819
  const width = this._width;
664
820
  const height = this._height;
665
821
  const pixels = new Uint8Array(width * height * 4);
666
822
  this._gl.readPixels(0, 0, width, height, this._gl.RGBA, this._gl.UNSIGNED_BYTE, pixels);
667
- console.warn("ExpoCanvasElement: toDataURL requires additional implementation for encoding");
823
+ if (__DEV__)
824
+ console.warn("ExpoCanvasElement: toDataURL requires additional implementation for encoding");
668
825
  return "";
669
826
  }
670
827
  /**
@@ -676,7 +833,7 @@ var ExpoCanvasElement = class {
676
833
  * @param _quality - Image quality (ignored)
677
834
  */
678
835
  toBlob(callback, _type, _quality) {
679
- console.warn("ExpoCanvasElement: toBlob is not implemented");
836
+ if (__DEV__) console.warn("ExpoCanvasElement: toBlob is not implemented");
680
837
  callback(null);
681
838
  }
682
839
  // ===========================================================================
@@ -745,7 +902,7 @@ var ExpoCanvasElement = class {
745
902
  listener.handleEvent(event);
746
903
  }
747
904
  } catch (error) {
748
- console.error("[ExpoCanvasElement] Error in event listener:", error);
905
+ if (__DEV__) console.error("[ExpoCanvasElement] Error in event listener:", error);
749
906
  }
750
907
  });
751
908
  return true;
@@ -852,15 +1009,16 @@ var ExpoAdapter = {
852
1009
  /**
853
1010
  * Get the Canvas 2D rendering context constructor.
854
1011
  *
855
- * @returns null - Canvas 2D is not supported in expo-gl
856
- *
857
- * @remarks
858
- * expo-gl only provides WebGL contexts. For 2D canvas operations,
859
- * consider using @shopify/react-native-skia as an alternative.
1012
+ * Returns a mock constructor whose instances provide the mock Canvas2D
1013
+ * context. PixiJS accesses `CanvasRenderingContext2D.prototype` for
1014
+ * feature detection, so returning `null` causes a crash.
860
1015
  */
861
1016
  getCanvasRenderingContext2D: () => {
862
- console.warn("ExpoAdapter: 2D context is not supported in expo-gl");
863
- return null;
1017
+ return class MockCanvasRenderingContext2D {
1018
+ constructor() {
1019
+ return createMockCanvas2DContext({ width: 0, height: 0 });
1020
+ }
1021
+ };
864
1022
  },
865
1023
  /**
866
1024
  * Get the WebGL rendering context constructor.
@@ -938,11 +1096,11 @@ var ExpoAdapter = {
938
1096
  return fetch(requestUrl, options);
939
1097
  }
940
1098
  if (requestUrl.startsWith("file://")) {
941
- console.warn("ExpoAdapter: Local file loading requires expo-file-system");
1099
+ if (__DEV__) console.warn("ExpoAdapter: Local file loading requires expo-file-system");
942
1100
  return fetch(requestUrl, options);
943
1101
  }
944
1102
  if (requestUrl.startsWith("asset://") || typeof url === "number") {
945
- console.warn("ExpoAdapter: Asset loading requires expo-asset");
1103
+ if (__DEV__) console.warn("ExpoAdapter: Asset loading requires expo-asset");
946
1104
  throw new Error("Asset loading not implemented");
947
1105
  }
948
1106
  return fetch(requestUrl, options);
@@ -1124,7 +1282,7 @@ var loadExpoAsset = {
1124
1282
  });
1125
1283
  return new Texture({ source });
1126
1284
  } catch (error) {
1127
- console.error(`[loadExpoAsset] Failed to load: ${url}`, error);
1285
+ if (__DEV__) console.error(`[loadExpoAsset] Failed to load: ${url}`, error);
1128
1286
  throw error;
1129
1287
  }
1130
1288
  },
@@ -1194,10 +1352,10 @@ var loadExpoFont = {
1194
1352
  await Font.loadAsync({
1195
1353
  [familyName]: fontSource
1196
1354
  });
1197
- console.log(`Font loaded: ${familyName}`);
1355
+ if (__DEV__) console.log(`Font loaded: ${familyName}`);
1198
1356
  return familyName;
1199
1357
  } catch (error) {
1200
- console.error(`Failed to load font: ${url}`, error);
1358
+ if (__DEV__) console.error(`Failed to load font: ${url}`, error);
1201
1359
  throw error;
1202
1360
  }
1203
1361
  },
@@ -1206,7 +1364,7 @@ var loadExpoFont = {
1206
1364
  * Note: expo-font doesn't have an unload API
1207
1365
  */
1208
1366
  unload(fontFamily) {
1209
- console.warn(`Font unloading is not supported in expo-font: ${fontFamily}`);
1367
+ if (__DEV__) console.warn(`Font unloading is not supported in expo-font: ${fontFamily}`);
1210
1368
  }
1211
1369
  };
1212
1370
 
@@ -1553,7 +1711,7 @@ var PixiView = forwardRef((props, ref) => {
1553
1711
  });
1554
1712
  onApplicationCreate?.(app);
1555
1713
  } catch (error) {
1556
- console.error("PixiJS initialization error:", error);
1714
+ if (__DEV__) console.error("PixiJS initialization error:", error);
1557
1715
  onError?.(error);
1558
1716
  }
1559
1717
  },
@@ -1569,7 +1727,7 @@ var PixiView = forwardRef((props, ref) => {
1569
1727
  try {
1570
1728
  appRef.current.destroy(true, { children: true });
1571
1729
  } catch (error) {
1572
- console.warn("Error destroying PixiJS application:", error);
1730
+ if (__DEV__) console.warn("Error destroying PixiJS application:", error);
1573
1731
  }
1574
1732
  appRef.current = null;
1575
1733
  }
@@ -1661,7 +1819,9 @@ Assets.load = function patchedLoad(urls, onProgress) {
1661
1819
  }
1662
1820
  if (Array.isArray(urls)) {
1663
1821
  const resolved = urls.map((u) => typeof u === "number" ? registerModuleId(u) : u);
1664
- return _originalLoad(resolved, onProgress);
1822
+ return _originalLoad(resolved, onProgress).then((record) => {
1823
+ return resolved.map((key) => record[key]);
1824
+ });
1665
1825
  }
1666
1826
  return _originalLoad(urls, onProgress);
1667
1827
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@penabt/pixi-expo",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "PixiJS v8 adapter for React Native Expo. Enables hardware-accelerated 2D graphics using expo-gl WebGL context.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -33,6 +33,7 @@
33
33
 
34
34
  import type { ExpoWebGLRenderingContext } from 'expo-gl';
35
35
  import { ExpoCanvasElement } from './ExpoCanvasElement';
36
+ import { createMockCanvas2DContext } from './canvas2d';
36
37
  import { DOMParser } from '@xmldom/xmldom';
37
38
 
38
39
  // =============================================================================
@@ -206,15 +207,16 @@ export const ExpoAdapter = {
206
207
  /**
207
208
  * Get the Canvas 2D rendering context constructor.
208
209
  *
209
- * @returns null - Canvas 2D is not supported in expo-gl
210
- *
211
- * @remarks
212
- * expo-gl only provides WebGL contexts. For 2D canvas operations,
213
- * consider using @shopify/react-native-skia as an alternative.
210
+ * Returns a mock constructor whose instances provide the mock Canvas2D
211
+ * context. PixiJS accesses `CanvasRenderingContext2D.prototype` for
212
+ * feature detection, so returning `null` causes a crash.
214
213
  */
215
214
  getCanvasRenderingContext2D: (): any => {
216
- console.warn('ExpoAdapter: 2D context is not supported in expo-gl');
217
- return null;
215
+ return class MockCanvasRenderingContext2D {
216
+ constructor() {
217
+ return createMockCanvas2DContext({ width: 0, height: 0 });
218
+ }
219
+ };
218
220
  },
219
221
 
220
222
  /**
@@ -303,13 +305,13 @@ export const ExpoAdapter = {
303
305
 
304
306
  // Local file URL
305
307
  if (requestUrl.startsWith('file://')) {
306
- console.warn('ExpoAdapter: Local file loading requires expo-file-system');
308
+ if (__DEV__) console.warn('ExpoAdapter: Local file loading requires expo-file-system');
307
309
  return fetch(requestUrl, options);
308
310
  }
309
311
 
310
312
  // Asset URL or require() number
311
313
  if (requestUrl.startsWith('asset://') || typeof url === 'number') {
312
- console.warn('ExpoAdapter: Asset loading requires expo-asset');
314
+ if (__DEV__) console.warn('ExpoAdapter: Asset loading requires expo-asset');
313
315
  throw new Error('Asset loading not implemented');
314
316
  }
315
317
 
@@ -33,8 +33,7 @@
33
33
  */
34
34
 
35
35
  import type { ExpoWebGLRenderingContext } from 'expo-gl';
36
-
37
- // ...
36
+ import { createMockCanvas2DContext } from './canvas2d';
38
37
 
39
38
  // =============================================================================
40
39
  // TYPE DEFINITIONS
@@ -332,27 +331,25 @@ export class ExpoCanvasElement {
332
331
  case 'webgl':
333
332
  case 'experimental-webgl':
334
333
  if (!this._gl) {
335
- console.warn(
336
- 'ExpoCanvasElement: WebGL context not available. ' +
337
- 'Make sure GLView.onContextCreate has been called first.',
338
- );
334
+ if (__DEV__)
335
+ console.warn(
336
+ 'ExpoCanvasElement: WebGL context not available. ' +
337
+ 'Make sure GLView.onContextCreate has been called first.',
338
+ );
339
339
  return null;
340
340
  }
341
341
  return this._gl as unknown as WebGLRenderingContext;
342
342
 
343
343
  case '2d':
344
- console.warn(
345
- 'ExpoCanvasElement: 2D context is not supported in expo-gl. ' +
346
- 'Consider using @shopify/react-native-skia for 2D rendering.',
347
- );
348
- return null;
344
+ return createMockCanvas2DContext(this) as any;
349
345
 
350
346
  case 'webgl2':
351
347
  case 'experimental-webgl2':
352
- console.warn(
353
- 'ExpoCanvasElement: WebGL2 is not fully supported in expo-gl. ' +
354
- 'Falling back to WebGL1.',
355
- );
348
+ if (__DEV__)
349
+ console.warn(
350
+ 'ExpoCanvasElement: WebGL2 is not fully supported in expo-gl. ' +
351
+ 'Falling back to WebGL1.',
352
+ );
356
353
  return this._gl as unknown as WebGLRenderingContext;
357
354
 
358
355
  default:
@@ -378,7 +375,7 @@ export class ExpoCanvasElement {
378
375
  */
379
376
  toDataURL(_type?: string, _quality?: number): string {
380
377
  if (!this._gl) {
381
- console.warn('ExpoCanvasElement: Cannot create data URL without GL context');
378
+ if (__DEV__) console.warn('ExpoCanvasElement: Cannot create data URL without GL context');
382
379
  return '';
383
380
  }
384
381
 
@@ -390,7 +387,8 @@ export class ExpoCanvasElement {
390
387
  this._gl.readPixels(0, 0, width, height, this._gl.RGBA, this._gl.UNSIGNED_BYTE, pixels);
391
388
 
392
389
  // Note: Full implementation would require encoding to PNG/JPEG
393
- console.warn('ExpoCanvasElement: toDataURL requires additional implementation for encoding');
390
+ if (__DEV__)
391
+ console.warn('ExpoCanvasElement: toDataURL requires additional implementation for encoding');
394
392
 
395
393
  return '';
396
394
  }
@@ -404,7 +402,7 @@ export class ExpoCanvasElement {
404
402
  * @param _quality - Image quality (ignored)
405
403
  */
406
404
  toBlob(callback: (blob: Blob | null) => void, _type?: string, _quality?: number): void {
407
- console.warn('ExpoCanvasElement: toBlob is not implemented');
405
+ if (__DEV__) console.warn('ExpoCanvasElement: toBlob is not implemented');
408
406
  callback(null);
409
407
  }
410
408
 
@@ -494,7 +492,7 @@ export class ExpoCanvasElement {
494
492
  listener.handleEvent(event);
495
493
  }
496
494
  } catch (error) {
497
- console.error('[ExpoCanvasElement] Error in event listener:', error);
495
+ if (__DEV__) console.error('[ExpoCanvasElement] Error in event listener:', error);
498
496
  }
499
497
  });
500
498
 
@@ -0,0 +1,159 @@
1
+ /**
2
+ * @fileoverview Mock Canvas 2D rendering context for React Native.
3
+ *
4
+ * Provides a minimal CanvasRenderingContext2D implementation that satisfies
5
+ * PixiJS's text measurement pipeline (CanvasTextMetrics, BitmapFont.from,
6
+ * @pixi/ui text components). expo-gl only supports WebGL, so actual pixel
7
+ * drawing is not possible — but PixiJS needs the context to exist so it can
8
+ * set `context.font` and call `measureText()`.
9
+ *
10
+ * @module @penabt/pixi-expo/canvas2d
11
+ * @internal
12
+ */
13
+
14
+ /** Parse font size from a CSS font string (e.g. "bold 24px Arial" → 24). */
15
+ function parseFontSize(font: string): number {
16
+ const match = font.match(/(\d+(?:\.\d+)?)\s*px/);
17
+ return match ? parseFloat(match[1]) : 10;
18
+ }
19
+
20
+ /** No-op gradient with addColorStop. */
21
+ function createMockGradient() {
22
+ return { addColorStop: () => {} };
23
+ }
24
+
25
+ /**
26
+ * Create a mock CanvasRenderingContext2D.
27
+ *
28
+ * The `measureText` implementation approximates character widths using
29
+ * `fontSize * 0.6` per character, which is close enough for layout purposes.
30
+ *
31
+ * @param canvas - The owning canvas element (or any object with width/height).
32
+ */
33
+ export function createMockCanvas2DContext(canvas: any) {
34
+ let _font = '10px sans-serif';
35
+
36
+ const ctx: any = {
37
+ // -- Back-reference to owning canvas --
38
+ canvas,
39
+
40
+ // -- Font / text properties (critical for CanvasTextMetrics) --
41
+ get font() {
42
+ return _font;
43
+ },
44
+ set font(value: string) {
45
+ _font = value;
46
+ },
47
+ fillStyle: '#000',
48
+ strokeStyle: '#000',
49
+ lineWidth: 1,
50
+ lineCap: 'butt',
51
+ lineJoin: 'miter',
52
+ miterLimit: 10,
53
+ textAlign: 'start' as CanvasTextAlign,
54
+ textBaseline: 'alphabetic' as CanvasTextBaseline,
55
+ direction: 'ltr',
56
+ globalAlpha: 1,
57
+ globalCompositeOperation: 'source-over',
58
+ imageSmoothingEnabled: true,
59
+ shadowBlur: 0,
60
+ shadowColor: 'rgba(0, 0, 0, 0)',
61
+ shadowOffsetX: 0,
62
+ shadowOffsetY: 0,
63
+ letterSpacing: '0px',
64
+ wordSpacing: '0px',
65
+ fontKerning: 'auto',
66
+ textRendering: 'auto',
67
+
68
+ // -- Text measurement (primary reason this mock exists) --
69
+ measureText(text: string) {
70
+ const fontSize = parseFontSize(_font);
71
+ const width = text.length * fontSize * 0.6;
72
+ const ascent = fontSize * 0.8;
73
+ const descent = fontSize * 0.2;
74
+ return {
75
+ width,
76
+ actualBoundingBoxAscent: ascent,
77
+ actualBoundingBoxDescent: descent,
78
+ fontBoundingBoxAscent: ascent,
79
+ fontBoundingBoxDescent: descent,
80
+ actualBoundingBoxLeft: 0,
81
+ actualBoundingBoxRight: width,
82
+ alphabeticBaseline: 0,
83
+ emHeightAscent: ascent,
84
+ emHeightDescent: descent,
85
+ hangingBaseline: ascent * 0.8,
86
+ ideographicBaseline: -descent,
87
+ };
88
+ },
89
+
90
+ // -- Text drawing (no-op) --
91
+ fillText: () => {},
92
+ strokeText: () => {},
93
+
94
+ // -- State stack --
95
+ save: () => {},
96
+ restore: () => {},
97
+
98
+ // -- Rect operations (no-op) --
99
+ clearRect: () => {},
100
+ fillRect: () => {},
101
+ strokeRect: () => {},
102
+
103
+ // -- Path operations (no-op) --
104
+ beginPath: () => {},
105
+ closePath: () => {},
106
+ moveTo: () => {},
107
+ lineTo: () => {},
108
+ bezierCurveTo: () => {},
109
+ quadraticCurveTo: () => {},
110
+ arc: () => {},
111
+ arcTo: () => {},
112
+ ellipse: () => {},
113
+ rect: () => {},
114
+ roundRect: () => {},
115
+ fill: () => {},
116
+ stroke: () => {},
117
+ clip: () => {},
118
+ isPointInPath: () => false,
119
+ isPointInStroke: () => false,
120
+
121
+ // -- Transform (no-op) --
122
+ scale: () => {},
123
+ rotate: () => {},
124
+ translate: () => {},
125
+ transform: () => {},
126
+ setTransform: () => {},
127
+ getTransform: () => ({ a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }),
128
+ resetTransform: () => {},
129
+
130
+ // -- Drawing images (no-op) --
131
+ drawImage: () => {},
132
+
133
+ // -- Pixel manipulation --
134
+ createImageData: (w: number, h: number) => ({
135
+ width: w,
136
+ height: h,
137
+ data: new Uint8ClampedArray(w * h * 4),
138
+ }),
139
+ getImageData: (_x: number, _y: number, w: number, h: number) => ({
140
+ width: w,
141
+ height: h,
142
+ data: new Uint8ClampedArray(w * h * 4),
143
+ }),
144
+ putImageData: () => {},
145
+
146
+ // -- Gradients / Patterns --
147
+ createLinearGradient: () => createMockGradient(),
148
+ createRadialGradient: () => createMockGradient(),
149
+ createConicGradient: () => createMockGradient(),
150
+ createPattern: () => null,
151
+
152
+ // -- Line dash --
153
+ setLineDash: () => {},
154
+ getLineDash: () => [],
155
+ lineDashOffset: 0,
156
+ };
157
+
158
+ return ctx;
159
+ }
@@ -260,7 +260,7 @@ export const loadExpoAsset = {
260
260
 
261
261
  return new Texture({ source });
262
262
  } catch (error) {
263
- console.error(`[loadExpoAsset] Failed to load: ${url}`, error);
263
+ if (__DEV__) console.error(`[loadExpoAsset] Failed to load: ${url}`, error);
264
264
  throw error;
265
265
  }
266
266
  },
@@ -118,12 +118,12 @@ export const loadExpoFont = {
118
118
  [familyName]: fontSource,
119
119
  });
120
120
 
121
- console.log(`Font loaded: ${familyName}`);
121
+ if (__DEV__) console.log(`Font loaded: ${familyName}`);
122
122
 
123
123
  // Return the font family name
124
124
  return familyName;
125
125
  } catch (error) {
126
- console.error(`Failed to load font: ${url}`, error);
126
+ if (__DEV__) console.error(`Failed to load font: ${url}`, error);
127
127
  throw error;
128
128
  }
129
129
  },
@@ -133,7 +133,7 @@ export const loadExpoFont = {
133
133
  * Note: expo-font doesn't have an unload API
134
134
  */
135
135
  unload(fontFamily: string): void {
136
- console.warn(`Font unloading is not supported in expo-font: ${fontFamily}`);
136
+ if (__DEV__) console.warn(`Font unloading is not supported in expo-font: ${fontFamily}`);
137
137
  },
138
138
  } as LoaderParser<string>;
139
139
 
@@ -18,7 +18,8 @@
18
18
  * @remarks
19
19
  * These polyfills are intentionally minimal. They provide just enough
20
20
  * functionality for PixiJS to initialize and run, but not full browser
21
- * compatibility. Features like Canvas 2D context are not supported.
21
+ * compatibility. A mock Canvas 2D context is provided for text measurement
22
+ * (CanvasTextMetrics, BitmapFont, @pixi/ui) but does not perform actual drawing.
22
23
  */
23
24
 
24
25
  // =============================================================================
@@ -74,7 +75,7 @@ if (typeof (globalThis as any).dispatchEvent === 'undefined') {
74
75
  try {
75
76
  listener(event);
76
77
  } catch (e) {
77
- console.error('Error in event listener:', e);
78
+ if (__DEV__) console.error('Error in event listener:', e);
78
79
  }
79
80
  });
80
81
  }
@@ -115,6 +116,12 @@ const createMockStyle = () => ({
115
116
  getPropertyValue: () => '',
116
117
  });
117
118
 
119
+ // =============================================================================
120
+ // MOCK CANVAS 2D CONTEXT (imported from shared module)
121
+ // =============================================================================
122
+
123
+ import { createMockCanvas2DContext } from './canvas2d';
124
+
118
125
  // =============================================================================
119
126
  // MOCK ELEMENT FACTORY
120
127
  // Creates HTMLElement-like objects for document.createElement compatibility.
@@ -131,7 +138,10 @@ const createMockElement = (tagName: string) => {
131
138
  const element: any = {
132
139
  tagName: tagName.toUpperCase(),
133
140
  style: createMockStyle(),
134
- getContext: () => null, // Canvas 2D not supported - use WebGL via expo-gl
141
+ getContext: (type: string) => {
142
+ if (type === '2d') return createMockCanvas2DContext(element);
143
+ return null; // WebGL contexts are provided by expo-gl via ExpoCanvasElement
144
+ },
135
145
  width: 0,
136
146
  height: 0,
137
147
  addEventListener: () => {},
@@ -306,7 +316,7 @@ export function dispatchWindowEvent(event: { type: string; [key: string]: any })
306
316
  listener.handleEvent(event);
307
317
  }
308
318
  } catch (error) {
309
- console.error('[Window] Error in event listener:', error);
319
+ if (__DEV__) console.error('[Window] Error in event listener:', error);
310
320
  }
311
321
  });
312
322
 
@@ -399,8 +409,9 @@ if (typeof (globalThis as any).HTMLCanvasElement === 'undefined') {
399
409
  width = 0;
400
410
  height = 0;
401
411
  style = createMockStyle();
402
- getContext() {
403
- return null;
412
+ getContext(type: string) {
413
+ if (type === '2d') return createMockCanvas2DContext(this);
414
+ return null; // WebGL contexts are provided by expo-gl via ExpoCanvasElement
404
415
  }
405
416
  };
406
417
  }
@@ -572,4 +583,4 @@ if (!('ontouchend' in globalThis)) {
572
583
  // Confirm polyfills are loaded (useful for debugging).
573
584
  // =============================================================================
574
585
 
575
- console.log('PixiJS Expo Adapter: Polyfills loaded');
586
+ if (__DEV__) console.log('PixiJS Expo Adapter: Polyfills loaded');
@@ -490,7 +490,7 @@ export const PixiView = forwardRef<PixiViewHandle, PixiViewProps>((props, ref) =
490
490
  // Notify application creation
491
491
  onApplicationCreate?.(app);
492
492
  } catch (error) {
493
- console.error('PixiJS initialization error:', error);
493
+ if (__DEV__) console.error('PixiJS initialization error:', error);
494
494
  onError?.(error as Error);
495
495
  }
496
496
  },
@@ -515,7 +515,7 @@ export const PixiView = forwardRef<PixiViewHandle, PixiViewProps>((props, ref) =
515
515
  try {
516
516
  appRef.current.destroy(true, { children: true });
517
517
  } catch (error) {
518
- console.warn('Error destroying PixiJS application:', error);
518
+ if (__DEV__) console.warn('Error destroying PixiJS application:', error);
519
519
  }
520
520
  appRef.current = null;
521
521
  }
package/src/index.ts CHANGED
@@ -108,7 +108,11 @@ const _originalLoad = Assets.load.bind(Assets);
108
108
  }
109
109
  if (Array.isArray(urls)) {
110
110
  const resolved = urls.map((u: any) => (typeof u === 'number' ? registerModuleId(u) : u));
111
- return _originalLoad(resolved, onProgress);
111
+ // Assets.load returns Record<string, T> for arrays. Convert to array so
112
+ // callers can destructure: const [a, b] = await Assets.load([url1, url2])
113
+ return _originalLoad(resolved, onProgress).then((record: any) => {
114
+ return resolved.map((key: string) => record[key]);
115
+ });
112
116
  }
113
117
  return _originalLoad(urls, onProgress);
114
118
  };