@caperjs/core 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/build/index.mjs +18 -1
  2. package/build/plugins/pwa.mjs +114 -8
  3. package/build/plugins/viewport.mjs +52 -0
  4. package/lib/{CaptionsPlugin-DKhbmJvH.js → CaptionsPlugin-WPAcfAkx.js} +13 -12
  5. package/lib/CaptionsPlugin-WPAcfAkx.js.map +1 -0
  6. package/lib/{DataAdapter-BZFTkFQ4.js → DataAdapter-DS-twowe.js} +11 -11
  7. package/lib/{DataAdapter-BZFTkFQ4.js.map → DataAdapter-DS-twowe.js.map} +1 -1
  8. package/lib/{DebugRenderer-C7X-nlXE.js → DebugRenderer-D1ryrRYx.js} +2 -2
  9. package/lib/{DebugRenderer-C7X-nlXE.js.map → DebugRenderer-D1ryrRYx.js.map} +1 -1
  10. package/lib/{DevToolsPlugin-P-atJ-5U.js → DevToolsPlugin-DH7zX7HU.js} +2 -2
  11. package/lib/{DevToolsPlugin-P-atJ-5U.js.map → DevToolsPlugin-DH7zX7HU.js.map} +1 -1
  12. package/lib/{GSAPPlugin-CeH9275z.js → GSAPPlugin-BBtRjUqe.js} +2 -2
  13. package/lib/{GSAPPlugin-CeH9275z.js.map → GSAPPlugin-BBtRjUqe.js.map} +1 -1
  14. package/lib/{LayoutPlugin-iZ3k6RsK.js → LayoutPlugin-Co6vcoV4.js} +4 -4
  15. package/lib/{LayoutPlugin-iZ3k6RsK.js.map → LayoutPlugin-Co6vcoV4.js.map} +1 -1
  16. package/lib/{SpinePlugin-BWiavCpO.js → SpinePlugin-uN641Hmk.js} +2 -2
  17. package/lib/{SpinePlugin-BWiavCpO.js.map → SpinePlugin-uN641Hmk.js.map} +1 -1
  18. package/lib/{StatsPlugin-CBpzDJL0.js → StatsPlugin-vZQRm6wh.js} +4 -4
  19. package/lib/{StatsPlugin-CBpzDJL0.js.map → StatsPlugin-vZQRm6wh.js.map} +1 -1
  20. package/lib/{VoiceOverPlugin-YaiKJtJ6.js → VoiceOverPlugin-KlZdGj1G.js} +32 -32
  21. package/lib/{VoiceOverPlugin-YaiKJtJ6.js.map → VoiceOverPlugin-KlZdGj1G.js.map} +1 -1
  22. package/lib/caper.mjs +2107 -353
  23. package/lib/caper.mjs.map +1 -1
  24. package/lib/core/Application.d.ts +37 -2
  25. package/lib/core/Application.d.ts.map +1 -1
  26. package/lib/core/create.d.ts +28 -1
  27. package/lib/core/create.d.ts.map +1 -1
  28. package/lib/core/index.d.ts +1 -1
  29. package/lib/core/index.d.ts.map +1 -1
  30. package/lib/core/interfaces/IApplicationOptions.d.ts +1 -1
  31. package/lib/core/interfaces/IApplicationOptions.d.ts.map +1 -1
  32. package/lib/core/interfaces/ICoreFunctions.d.ts +1 -0
  33. package/lib/core/interfaces/ICoreFunctions.d.ts.map +1 -1
  34. package/lib/display/AnimatedSprite.d.ts +1 -1
  35. package/lib/display/AnimatedSprite.d.ts.map +1 -1
  36. package/lib/display/Container.d.ts.map +1 -1
  37. package/lib/display/SpineAnimation.d.ts +1 -1
  38. package/lib/display/SpineAnimation.d.ts.map +1 -1
  39. package/lib/importOrder.containerFirst.test.d.ts +2 -0
  40. package/lib/importOrder.containerFirst.test.d.ts.map +1 -0
  41. package/lib/importOrder.displayFirst.test.d.ts +2 -0
  42. package/lib/importOrder.displayFirst.test.d.ts.map +1 -0
  43. package/lib/importOrder.mixinsFirst.test.d.ts +2 -0
  44. package/lib/importOrder.mixinsFirst.test.d.ts.map +1 -0
  45. package/lib/importOrder.sceneFirst.test.d.ts +2 -0
  46. package/lib/importOrder.sceneFirst.test.d.ts.map +1 -0
  47. package/lib/importOrder.toastFirst.test.d.ts +2 -0
  48. package/lib/importOrder.toastFirst.test.d.ts.map +1 -0
  49. package/lib/importOrder.uiFirst.test.d.ts +2 -0
  50. package/lib/importOrder.uiFirst.test.d.ts.map +1 -0
  51. package/lib/mixins/factory/Factory.d.ts +1 -1
  52. package/lib/mixins/factory/Factory.d.ts.map +1 -1
  53. package/lib/mixins/factory/Factory.test.d.ts +1 -0
  54. package/lib/mixins/factory/Factory.test.d.ts.map +1 -0
  55. package/lib/mixins/factory/const.d.ts +9 -2
  56. package/lib/mixins/factory/const.d.ts.map +1 -1
  57. package/lib/mixins/factory/defaults.d.ts +5 -0
  58. package/lib/mixins/factory/defaults.d.ts.map +1 -0
  59. package/lib/mixins/factory/methods.d.ts +1 -1
  60. package/lib/mixins/factory/methods.d.ts.map +1 -1
  61. package/lib/mixins/factory/props.d.ts +4 -2
  62. package/lib/mixins/factory/props.d.ts.map +1 -1
  63. package/lib/plugins/PopupManagerPlugin.d.ts +1 -1
  64. package/lib/plugins/PopupManagerPlugin.d.ts.map +1 -1
  65. package/lib/plugins/ResizerPlugin.d.ts +29 -0
  66. package/lib/plugins/ResizerPlugin.d.ts.map +1 -1
  67. package/lib/plugins/ResizerPlugin.test.d.ts +2 -0
  68. package/lib/plugins/ResizerPlugin.test.d.ts.map +1 -0
  69. package/lib/plugins/WebEventsPlugin.d.ts +5 -0
  70. package/lib/plugins/WebEventsPlugin.d.ts.map +1 -1
  71. package/lib/plugins/WebEventsPlugin.test.d.ts +2 -0
  72. package/lib/plugins/WebEventsPlugin.test.d.ts.map +1 -0
  73. package/lib/plugins/captions/CaptionsRenderer.d.ts +1 -1
  74. package/lib/plugins/captions/CaptionsRenderer.d.ts.map +1 -1
  75. package/lib/plugins/i18nPlugin.d.ts +29 -3
  76. package/lib/plugins/i18nPlugin.d.ts.map +1 -1
  77. package/lib/plugins/i18nPlugin.test.d.ts +2 -0
  78. package/lib/plugins/i18nPlugin.test.d.ts.map +1 -0
  79. package/lib/plugins/input/touch/VirtualControls.d.ts.map +1 -1
  80. package/lib/plugins/spine/pixi-spine/require-shim.d.ts +0 -1
  81. package/lib/plugins/spine/pixi-spine/require-shim.d.ts.map +1 -1
  82. package/lib/{registries-qQmLeN0b.js → registries-BLrxALZ0.js} +1138 -2800
  83. package/lib/registries-BLrxALZ0.js.map +1 -0
  84. package/lib/ui/Button.d.ts +1 -1
  85. package/lib/ui/Button.d.ts.map +1 -1
  86. package/lib/ui/FlexContainer.d.ts +1 -1
  87. package/lib/ui/FlexContainer.d.ts.map +1 -1
  88. package/lib/ui/Input.d.ts +2 -2
  89. package/lib/ui/Input.d.ts.map +1 -1
  90. package/lib/ui/Joystick.d.ts +1 -1
  91. package/lib/ui/Joystick.d.ts.map +1 -1
  92. package/lib/ui/Toast.d.ts +1 -1
  93. package/lib/ui/Toast.d.ts.map +1 -1
  94. package/lib/ui/UICanvas.d.ts +11 -1
  95. package/lib/ui/UICanvas.d.ts.map +1 -1
  96. package/lib/ui/UICanvas.test.d.ts +2 -0
  97. package/lib/ui/UICanvas.test.d.ts.map +1 -0
  98. package/lib/utils/types.d.ts +1 -2
  99. package/lib/utils/types.d.ts.map +1 -1
  100. package/package.json +1 -1
  101. package/src/core/Application.ts +66 -2
  102. package/src/core/create.ts +30 -1
  103. package/src/core/index.ts +1 -1
  104. package/src/core/interfaces/IApplicationOptions.ts +1 -1
  105. package/src/core/interfaces/ICoreFunctions.ts +1 -0
  106. package/src/display/AnimatedSprite.ts +2 -1
  107. package/src/display/Container.ts +3 -1
  108. package/src/display/SpineAnimation.ts +3 -2
  109. package/src/importOrder.containerFirst.test.ts +32 -0
  110. package/src/importOrder.displayFirst.test.ts +19 -0
  111. package/src/importOrder.mixinsFirst.test.ts +18 -0
  112. package/src/importOrder.sceneFirst.test.ts +21 -0
  113. package/src/importOrder.toastFirst.test.ts +20 -0
  114. package/src/importOrder.uiFirst.test.ts +24 -0
  115. package/src/mixins/factory/Factory.test.ts +22 -0
  116. package/src/mixins/factory/Factory.ts +9 -2
  117. package/src/mixins/factory/const.ts +18 -11
  118. package/src/mixins/factory/defaults.ts +28 -0
  119. package/src/mixins/factory/methods.ts +1 -1
  120. package/src/mixins/factory/props.ts +5 -3
  121. package/src/plugins/LookupPlugin.ts +1 -1
  122. package/src/plugins/PopupManagerPlugin.ts +1 -1
  123. package/src/plugins/ResizerPlugin.test.ts +84 -0
  124. package/src/plugins/ResizerPlugin.ts +59 -1
  125. package/src/plugins/WebEventsPlugin.test.ts +75 -0
  126. package/src/plugins/WebEventsPlugin.ts +15 -0
  127. package/src/plugins/captions/CaptionsRenderer.ts +1 -1
  128. package/src/plugins/i18nPlugin.test.ts +124 -0
  129. package/src/plugins/i18nPlugin.ts +57 -18
  130. package/src/plugins/input/keyboard/KeyboardContols.ts +1 -1
  131. package/src/plugins/input/touch/VirtualControls.ts +2 -2
  132. package/src/plugins/spine/pixi-spine/require-shim.ts +3 -1
  133. package/src/ui/Button.ts +5 -1
  134. package/src/ui/FlexContainer.ts +3 -2
  135. package/src/ui/Input.ts +5 -2
  136. package/src/ui/Joystick.ts +1 -1
  137. package/src/ui/Toast.ts +2 -1
  138. package/src/ui/Toaster.ts +1 -1
  139. package/src/ui/UICanvas.test.ts +35 -0
  140. package/src/ui/UICanvas.ts +46 -8
  141. package/src/utils/types.ts +1 -1
  142. package/src/version.ts +1 -1
  143. package/lib/CaptionsPlugin-DKhbmJvH.js.map +0 -1
  144. package/lib/registries-qQmLeN0b.js.map +0 -1
@@ -17,6 +17,17 @@ export type LocaleKey = AppTypeOverrides extends { LocaleKeys: infer K extends s
17
17
  ? K | (string & {})
18
18
  : string;
19
19
 
20
+ /**
21
+ * Base keys that have plural leaves (i.e. a `<base>.other` entry exists in the
22
+ * reference locale). These are the keys `tCount()` accepts.
23
+ *
24
+ * `(string & {})` preserves autocomplete for known keys while still
25
+ * accepting dynamic strings.
26
+ */
27
+ export type PluralLocaleKey = AppTypeOverrides extends { LocaleKeys: infer K extends string }
28
+ ? (K extends `${infer B}.other` ? B : never) | (string & {})
29
+ : string;
30
+
20
31
  /**
21
32
  * Type definition for i18n dictionary.
22
33
  */
@@ -73,6 +84,8 @@ export interface Ii18nPlugin extends IPlugin {
73
84
 
74
85
  t(key: LocaleKey, params?: i18nTParams, locale?: string): string;
75
86
 
87
+ tCount(key: PluralLocaleKey, count: number, params?: i18nTParams, locale?: string): string;
88
+
76
89
  parse(input: string, locale?: string): string;
77
90
  }
78
91
 
@@ -152,10 +165,13 @@ export class i18nPlugin extends Plugin<i18nOptions> implements Ii18nPlugin {
152
165
 
153
166
  /**
154
167
  * Translates a key into a string.
155
- * If the key is not found, it will return an empty string.
168
+ * If the key is not found (or no dictionary is loaded), it returns the key itself, so a typo
169
+ * renders visibly on screen instead of blanking the UI.
156
170
  * If the key is found, it will replace any placeholders in the string with the values from the params object.
157
- * If the key contains a variant, it will select a random variant if the variant param is set to 'random'.
158
- * If the key contains a number variant, it will select the variant based on the variant param.
171
+ * Every `[a|b|c]` group in the string is resolved, whether or not params were passed:
172
+ * `variant: 'random'` picks a random item per group, `variant: <number>` picks that index for every
173
+ * group (clamped to the group's last item), and with no variant param index 0 is used. Brackets never
174
+ * survive into the output.
159
175
  * @param key The key to translate.
160
176
  * @param params The parameters to replace in the string.
161
177
  * @param locale The locale to use for translation.
@@ -166,7 +182,7 @@ export class i18nPlugin extends Plugin<i18nOptions> implements Ii18nPlugin {
166
182
  const dict = this._dicts[locale];
167
183
  if (!dict) {
168
184
  Logger.error(`i18n:: No dictionary loaded for current locale: ${locale}`);
169
- return '';
185
+ return key as string;
170
186
  }
171
187
  // Dot-path resolution: `t('obj.nested')` walks the dict tree. Flat keys
172
188
  // still work (including keys that literally contain dots, via the
@@ -176,24 +192,26 @@ export class i18nPlugin extends Plugin<i18nOptions> implements Ii18nPlugin {
176
192
 
177
193
  if (!str) {
178
194
  Logger.error(`i18n:: No result found for the key ${key} in the locale: ${this._locale}`);
179
- return '';
195
+ return key as string;
180
196
  }
181
197
 
182
- if (params) {
183
- if (typeof params.variant === 'number' || params.variant === 'random') {
184
- const match = /\[(.*?)\]/.exec(str);
185
- if (match) {
186
- // Split the string by the "|" character to get an array of variations.
187
- const items = match[1].split('|');
188
-
189
- // Get the selected variant based on the "variation" param.
190
- const num = params.variant === 'random' ? Math.floor(Math.random() * items.length) : params.variant;
198
+ // Resolve every `[a|b|c]` group, whether or not params were passed, so brackets
199
+ // never leak into the rendered string.
200
+ const variant = params?.variant;
201
+ str = str.replace(/\[(.*?)\]/g, (_match, group: string) => {
202
+ // Split the group by the "|" character to get an array of variations.
203
+ const items = group.split('|');
191
204
 
192
- // Replace the original string with the selected variant
193
- str = str.replace(match[0], items[num]);
194
- }
205
+ if (variant === 'random') {
206
+ return items[Math.floor(Math.random() * items.length)];
195
207
  }
196
208
 
209
+ // A number picks that index for every group, clamped to the group's last item.
210
+ const num = typeof variant === 'number' ? Math.min(Math.max(variant, 0), items.length - 1) : 0;
211
+ return items[num];
212
+ });
213
+
214
+ if (params) {
197
215
  // Iterate over all params to replace placeholders in the string.
198
216
  for (const f in params) {
199
217
  // Create a regular expression to match the placeholder for the current param.
@@ -221,6 +239,27 @@ export class i18nPlugin extends Plugin<i18nOptions> implements Ii18nPlugin {
221
239
  return this.t(key, params, locale);
222
240
  }
223
241
 
242
+ /**
243
+ * Translates a pluralised key using the locale's own plural rules.
244
+ * The CLDR plural category for `count` (`one`, `other`, `few`, ...) is resolved against
245
+ * `<key>.<category>`, falling back to `<key>.other` when that leaf does not exist.
246
+ * `count` is passed through as a param, so `{count}` interpolates and variants still apply.
247
+ * @param key The base key holding the plural leaves.
248
+ * @param count The count used to pick the plural category.
249
+ * @param params Additional parameters to replace in the string.
250
+ * @param locale The locale to use for translation.
251
+ * @returns The translated string.
252
+ */
253
+ tCount(key: PluralLocaleKey, count: number, params?: i18nTParams, locale: string = this._locale): string {
254
+ const category = new Intl.PluralRules(locale).select(count);
255
+ const dict = this._dicts[locale];
256
+ let pluralKey = `${key}.${category}`;
257
+ if (!dict || typeof resolveLocalePath(dict, pluralKey) !== 'string') {
258
+ pluralKey = `${key}.other`;
259
+ }
260
+ return this.t(pluralKey, { count, ...params }, locale);
261
+ }
262
+
224
263
  /**
225
264
  * Parses the input string and replaces anything in between {} braces, assuming it is a key in the dictionary.
226
265
  * @param {string} input
@@ -263,7 +302,7 @@ export class i18nPlugin extends Plugin<i18nOptions> implements Ii18nPlugin {
263
302
  }
264
303
 
265
304
  protected getCoreFunctions(): string[] {
266
- return ['t', 'translate', 'setLocale'];
305
+ return ['t', 'translate', 'tCount', 'setLocale'];
267
306
  }
268
307
 
269
308
  protected getCoreSignals(): string[] {
@@ -1,5 +1,5 @@
1
1
  import { Application } from '../../../core/Application';
2
- import { WithSignals } from '../../../mixins';
2
+ import { WithSignals } from '../../../mixins/signals';
3
3
  import { bindAllMethods } from '../../../utils';
4
4
  import type { Action } from '../../actions';
5
5
  import { normalizeKey, type KeyboardEventDetail } from '../../KeyboardPlugin';
@@ -3,8 +3,8 @@ import type { ControlsActionMap, JoystickControlsScheme, TouchControlsMap, Touch
3
3
 
4
4
  import { JoystickDirection } from '..';
5
5
  import { Application } from '../../../core/Application';
6
- import { WithSignals } from '../../../mixins';
7
- import { type IButton, type IJoystick } from '../../../ui';
6
+ import { WithSignals } from '../../../mixins/signals';
7
+ import type { IButton, IJoystick } from '../../../ui';
8
8
  import type { Action } from '../../actions';
9
9
  import { AbstractControls } from '../AbstractControls';
10
10
 
@@ -28,7 +28,9 @@
28
28
  *****************************************************************************/
29
29
 
30
30
  declare global {
31
- var require: any;
31
+ // @types/node (present via typeRoots) already declares `require: Require`;
32
+ // redeclaring it as `any` is a TS2403 conflict, so declare only what node
33
+ // doesn't, and keep the shim's loose typing at the use sites below.
32
34
  var PIXI: any;
33
35
  }
34
36
 
package/src/ui/Button.ts CHANGED
@@ -12,7 +12,11 @@ import {
12
12
  import { type LayoutOptions } from '@pixi/layout';
13
13
  import type { IApplication } from '../core';
14
14
  import { Application } from '../core/Application';
15
- import { Factory, Focusable, HTMLTextProps, Interactive, type TextProps, WithSignals } from '../mixins';
15
+ import { Factory } from '../mixins/factory/Factory';
16
+ import type { HTMLTextProps, TextProps } from '../mixins/factory/props';
17
+ import { Focusable } from '../mixins/focus';
18
+ import { Interactive } from '../mixins/interaction';
19
+ import { WithSignals } from '../mixins/signals';
16
20
  import { Signal } from '../signals';
17
21
  import { AudioAsset, bindAllMethods, type SpritesheetAsset, type TextureAsset } from '../utils';
18
22
 
@@ -1,8 +1,9 @@
1
1
  import type { LayoutOptions, NumberValue } from '@pixi/layout';
2
2
  import { BitmapText, Graphics, Container as PIXIContainer, Text } from 'pixi.js';
3
3
  import { Application } from '../core/Application';
4
- import { Container } from '../display';
5
- import { Factory, WithSignals } from '../mixins';
4
+ import { Container } from '../display/Container';
5
+ import { Factory } from '../mixins/factory/Factory';
6
+ import { WithSignals } from '../mixins/signals';
6
7
  import { Signal } from '../signals';
7
8
  import {
8
9
  AppTypeOverrides,
package/src/ui/Input.ts CHANGED
@@ -11,7 +11,10 @@ import {
11
11
  Texture,
12
12
  } from 'pixi.js';
13
13
 
14
- import { Focusable, Interactive, TextProps, WithSignals } from '../mixins';
14
+ import type { TextProps } from '../mixins/factory/props';
15
+ import { Focusable } from '../mixins/focus';
16
+ import { Interactive } from '../mixins/interaction';
17
+ import { WithSignals } from '../mixins/signals';
15
18
 
16
19
  import {
17
20
  EaseString,
@@ -27,7 +30,7 @@ import {
27
30
  } from '../utils';
28
31
 
29
32
  import { gsap } from 'gsap';
30
- import { Container } from '../display';
33
+ import { Container } from '../display/Container';
31
34
  import { Signal } from '../signals';
32
35
 
33
36
  /**
@@ -4,7 +4,7 @@
4
4
  * ported to TypeScript and adapted for Caper
5
5
  */
6
6
  import { DestroyOptions, FederatedPointerEvent, Graphics, Point, Sprite } from 'pixi.js';
7
- import { Container } from '../display';
7
+ import { Container } from '../display/Container';
8
8
  import { JoystickDirection } from '../plugins';
9
9
  import { Signal } from '../signals';
10
10
 
package/src/ui/Toast.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { gsap } from 'gsap';
2
2
  import { Graphics, Text } from 'pixi.js';
3
3
  import { Container } from '../display/Container';
4
- import { type TextStyle, WithSignals } from '../mixins';
4
+ import type { TextStyle } from '../mixins/factory/props';
5
+ import { WithSignals } from '../mixins/signals';
5
6
  import { Signal } from '../signals';
6
7
  import { Button } from './Button';
7
8
  import { Toaster } from './Toaster';
package/src/ui/Toaster.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { gsap } from 'gsap';
2
2
  import { Container } from '../display/Container';
3
- import { WithSignals } from '../mixins';
3
+ import { WithSignals } from '../mixins/signals';
4
4
  import { Signal } from '../signals';
5
5
  import { defaultToastConfig, Toast, ToastConfig } from './Toast';
6
6
  import { UICanvasEdge } from './UICanvas';
@@ -0,0 +1,35 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+
3
+ // UICanvas transitively imports Application → Pixi display graph. Stub it — the
4
+ // pure padding helper needs none of it.
5
+ vi.mock('../core/Application', () => ({
6
+ Application: { getInstance: () => ({}) },
7
+ }));
8
+
9
+ import { computeEffectivePadding } from './UICanvas';
10
+
11
+ const zero = { top: 0, right: 0, bottom: 0, left: 0 };
12
+
13
+ describe('computeEffectivePadding', () => {
14
+ it('adds the safe area to the configured padding', () => {
15
+ expect(
16
+ computeEffectivePadding({ top: 10, right: 10, bottom: 10, left: 10 }, { ...zero, top: 44, bottom: 34 }),
17
+ ).toEqual({ top: 54, right: 10, bottom: 44, left: 10 });
18
+ });
19
+
20
+ it('returns the configured padding when there is no safe area', () => {
21
+ expect(computeEffectivePadding({ top: 10, right: 20, bottom: 30, left: 40 }, zero)).toEqual({
22
+ top: 10,
23
+ right: 20,
24
+ bottom: 30,
25
+ left: 40,
26
+ });
27
+ });
28
+
29
+ it('does not compound when applied repeatedly to the same base padding', () => {
30
+ const base = { top: 10, right: 10, bottom: 10, left: 10 };
31
+ const safeArea = { ...zero, top: 44 };
32
+ computeEffectivePadding(base, safeArea);
33
+ expect(computeEffectivePadding(base, safeArea).top).toBe(54);
34
+ });
35
+ });
@@ -1,8 +1,9 @@
1
1
  import type { LayoutOptions } from '@pixi/layout';
2
2
  import { ContainerChild, Graphics, RenderLayer, Container as PIXIContainer, Text } from 'pixi.js';
3
3
  import { Application } from '../core/Application';
4
- import { Container } from '../display';
5
- import { Factory, WithSignals } from '../mixins';
4
+ import { Container } from '../display/Container';
5
+ import { Factory } from '../mixins/factory/Factory';
6
+ import { WithSignals } from '../mixins/signals';
6
7
  import type { AppTypeOverrides, Padding, PointLike, Size, SizeLike } from '../utils';
7
8
  import {
8
9
  bindAllMethods,
@@ -72,21 +73,46 @@ export type UICanvasConfig = {
72
73
  padding: Padding;
73
74
  size: Size;
74
75
  useAppSize: boolean;
76
+ useSafeArea: boolean;
75
77
  layout?: Omit<LayoutOptions, 'target'> | null | boolean;
76
78
  autoLayoutChildren?: boolean;
77
79
  };
78
80
 
79
- export const UICanvasConfigKeys: (keyof UICanvasConfig)[] = ['debug', 'padding', 'size', 'useAppSize', 'layout'];
81
+ export const UICanvasConfigKeys: (keyof UICanvasConfig)[] = [
82
+ 'debug',
83
+ 'padding',
84
+ 'size',
85
+ 'useAppSize',
86
+ 'useSafeArea',
87
+ 'layout',
88
+ ];
80
89
 
81
90
  export type UICanvasProps = {
82
91
  debug: boolean;
83
92
  padding: Partial<Padding> | PointLike;
84
93
  size?: SizeLike;
85
94
  useAppSize?: boolean;
95
+ useSafeArea?: boolean;
86
96
  layout?: Omit<LayoutOptions, 'target'> | null | boolean;
87
97
  autoLayoutChildren?: boolean;
88
98
  };
89
99
 
100
+ const zeroPadding: Padding = { top: 0, right: 0, bottom: 0, left: 0 };
101
+
102
+ /**
103
+ * The padding the canvas actually lays out with: the configured padding plus the
104
+ * device's safe-area insets. Kept separate from `config.padding` so it can be
105
+ * re-derived on every resize without compounding.
106
+ */
107
+ export function computeEffectivePadding(padding: Padding, safeArea: Padding): Padding {
108
+ return {
109
+ top: padding.top + safeArea.top,
110
+ right: padding.right + safeArea.right,
111
+ bottom: padding.bottom + safeArea.bottom,
112
+ left: padding.left + safeArea.left,
113
+ };
114
+ }
115
+
90
116
  const defaultLayout = {
91
117
  flexGrow: 0,
92
118
  flexShrink: 0,
@@ -124,6 +150,7 @@ export class UICanvas extends _UICanvas {
124
150
  padding: ensurePadding(config?.padding ?? 0),
125
151
  size: config.size !== undefined ? resolveSizeLike(config.size) : { width: 0, height: 0 },
126
152
  useAppSize: config.useAppSize === true,
153
+ useSafeArea: config.useSafeArea ?? true,
127
154
  autoLayoutChildren: config.autoLayoutChildren ?? true,
128
155
  };
129
156
 
@@ -414,13 +441,22 @@ export class UICanvas extends _UICanvas {
414
441
  set padding(value: Partial<Padding> | PointLike) {
415
442
  this.config.padding = ensurePadding(value);
416
443
  // Update position to account for padding
444
+ this._applyPadding();
445
+ this._updateLayout();
446
+ }
447
+
448
+ /** Writes the effective padding (configured padding + safe-area insets) into the layout. */
449
+ private _applyPadding() {
450
+ const padding = computeEffectivePadding(
451
+ this.config.padding,
452
+ this.config.useSafeArea ? this.app.safeArea : zeroPadding,
453
+ );
417
454
  this.layout = {
418
- paddingLeft: this.config.padding.left,
419
- paddingTop: this.config.padding.top,
420
- paddingRight: this.config.padding.right,
421
- paddingBottom: this.config.padding.bottom,
455
+ paddingLeft: padding.left,
456
+ paddingTop: padding.top,
457
+ paddingRight: padding.right,
458
+ paddingBottom: padding.bottom,
422
459
  };
423
- this._updateLayout();
424
460
  }
425
461
 
426
462
  private static isFlexContainer(child: PIXIContainer): boolean {
@@ -494,6 +530,8 @@ export class UICanvas extends _UICanvas {
494
530
  }
495
531
 
496
532
  public resize() {
533
+ // Re-derived rather than cached: the safe area changes with the viewport.
534
+ this._applyPadding();
497
535
  if (this.config.useAppSize) {
498
536
  this.size = { width: this.app.size.width, height: this.app.size.height };
499
537
  this.position.set(-this.app.size.width * 0.5, -this.app.size.height * 0.5);
@@ -1,7 +1,7 @@
1
1
  import type { AssetInitOptions, AssetsManifest, AssetsPreferences, Texture, UnresolvedAsset } from 'pixi.js';
2
2
  import { Point } from 'pixi.js';
3
3
  import type { Scene } from '../display/Scene';
4
- import { SceneAssets } from '../display';
4
+ import type { SceneAssets } from '../display/Scene';
5
5
  import type { FilterBitmapFontNames, FilterCleanAssetNames, FilterSpineAssetNames } from './typefilters';
6
6
 
7
7
  /**
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  // This file is auto-generated during the build process.
2
- export const version: string = '0.2.1';
2
+ export const version: string = '0.2.3';
3
3
  export const pixiVersion: string = '8.19.0';
@@ -1 +0,0 @@
1
- {"version":3,"file":"CaptionsPlugin-DKhbmJvH.js","names":[],"sources":["../src/plugins/captions/CaptionsRenderer.ts","../src/plugins/captions/CaptionsPlugin.ts"],"sourcesContent":["import { gsap } from 'gsap';\nimport { BitmapText, Container as PIXIContainer, Sprite, Texture } from 'pixi.js';\nimport { Container } from '../../display';\nimport { type Size } from '../../utils';\nimport { CaptionsPlugin } from './CaptionsPlugin';\n\nexport type CaptionLine = {\n id: string;\n start: number;\n end: number;\n content: string;\n speaker?: string;\n};\n\nexport interface ICaptionRenderer extends PIXIContainer {\n visible: boolean;\n\n start(): void;\n\n stop(): void;\n\n lineBegin(line: CaptionLine): void;\n\n lineEnd(line: CaptionLine): void;\n\n resize(size?: Size): void;\n\n updateSettings(): void;\n\n destroy(): void;\n}\n\nexport class CaptionsRenderer extends Container implements ICaptionRenderer {\n private readonly _bg: Sprite;\n private readonly _text: BitmapText;\n private readonly _size: Size = { width: 0, height: 0 };\n private readonly fontSize = 48;\n\n constructor(private plugin: CaptionsPlugin) {\n super({ autoResize: false });\n\n this._bg = this.add.sprite({ asset: Texture.WHITE, anchor: [0.5, 0] });\n this._bg.tint = this.plugin.options.backgroundColor;\n this._bg.alpha = 0;\n\n this._text = this.add.bitmapText({\n font: plugin.options.fontName,\n resolution: 2,\n roundPixels: true,\n style: {\n fill: this.plugin.options.textColor,\n fontSize: this.fontSize * this.getSizeMultiplier(),\n fontWeight: 'bold',\n align: 'center',\n wordWrapWidth: this.plugin.maxWidth,\n wordWrap: true,\n lineHeight: this.fontSize * this.getSizeMultiplier() * 2 + 20,\n },\n });\n this._text.style.wordWrap = true;\n this._text.alpha = 0;\n this._text.visible = false;\n this._text.anchor.x = 0.5;\n this._text.anchor.y = 0;\n this.addChild(this._text);\n\n if (this.plugin.debug) {\n let isTapping = false;\n this._bg.eventMode = 'static';\n this._bg.on('pointertap', () => {\n if (isTapping) {\n return;\n }\n isTapping = true;\n setTimeout(() => {\n isTapping = false;\n void this.app.voiceover.stopVO();\n }, 100);\n });\n }\n }\n\n public start(): void {\n this._text.visible = true;\n this._bg.visible = true;\n\n gsap.killTweensOf([this._bg, this._text]);\n this.animate({ alpha: this.plugin.options.backgroundAlpha, visible: true, duration: 0.2 }, this._bg);\n this.animate({ alpha: 1, visible: true, duration: 0.2 }, this._text);\n }\n\n public stop(): void {\n gsap.killTweensOf([this._bg, this._text]);\n this.animate({ alpha: 0, visible: false, duration: 0.2 }, [this._bg, this._text]);\n }\n\n public lineBegin(line: CaptionLine): void {\n let text = line.content\n .replace(/<[^>]*>/gi, '') // remove html tags\n .replace(/\\[.*]\\W*/gi, ''); // remove brackets\n if (line.start === 0) {\n text = text.charAt(0).toUpperCase() + text.slice(1);\n }\n this._text.text = text;\n this.resize();\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n public lineEnd(_line: CaptionLine): void {}\n\n public resize(): void {\n const size = this.app.size;\n const opts = this.plugin.options;\n\n this._size.width = size.width;\n this._size.height = size.height;\n\n this._text.style.fontSize = this.fontSize * this.getSizeMultiplier();\n this._text.style.wordWrapWidth = opts.maxWidth * 2;\n this._text.scale.set(0.5);\n this._text.position.set(this._size.width * 0.5, 0);\n\n this._bg.position.set(this._size.width * 0.5, 0);\n\n let h = this._text.height;\n if (opts.padding.top) {\n h += opts.padding.top;\n }\n if (opts.padding.bottom) {\n h += opts.padding.bottom;\n }\n this._bg.height = h;\n\n this._text.y += opts.padding.top;\n\n if (opts.floating) {\n let w = this._text.width;\n\n if (opts.padding.left) {\n w += opts.padding.left;\n }\n if (opts.padding.right) {\n w += opts.padding.right;\n }\n\n this._bg.x += opts.padding.right * 0.5 - opts.padding.left * 0.5;\n this._bg.width = w;\n } else {\n this._bg.width = size.width;\n }\n\n if (opts.position === 'top') {\n this.position.set(-size.width * 0.5, Math.round(-size.height * 0.5));\n if (opts.floating) {\n this.position.y += Math.round(opts.distance - this._bg.y);\n }\n } else {\n this.position.set(-size.width * 0.5, Math.round(size.height * 0.5 - this._bg.height));\n if (opts.floating) {\n this.position.y -= Math.round(opts.distance + this._bg.y);\n }\n }\n }\n\n updateSettings() {\n if (this.plugin.options.enabled) {\n this._text.style.fill = this.plugin.options.textColor;\n this._bg.tint = this.plugin.options.backgroundColor;\n if (this._bg.visible) {\n this._bg.alpha = this.plugin.options.backgroundAlpha;\n this.resize();\n }\n } else {\n this.stop();\n this._bg.visible = false;\n this._text.visible = false;\n this._bg.alpha = this._text.alpha = 0;\n }\n }\n\n private getSizeMultiplier(): number {\n return this.plugin.options.fontSizeMultiplier;\n }\n}\n","import { Assets, Container, Ticker } from 'pixi.js';\nimport type { ImportListItem, ImportListItemModule, Padding, PointLike } from '../../utils';\nimport { ensurePadding, getDynamicModuleFromImportListItem, isDev, Logger } from '../../utils';\nimport type { IAudioInstance } from '../audio';\nimport type { IPlugin } from '../Plugin';\nimport { Plugin } from '../Plugin';\nimport type { ICaptionRenderer } from './CaptionsRenderer';\nimport { CaptionsRenderer } from './CaptionsRenderer';\n\nconst isDebug = isDev;\n\n/**\n * Type definition for i18n dictionary.\n */\ntype CaptionsDict = Record<string, any>;\n\n/**\n * Type definition for i18n import list item.\n */\ntype CaptionsImportListItem<T> = {\n id: string;\n namedExport?: string;\n options?: any;\n module?: ImportListItemModule<T>;\n} & {\n json?: string;\n};\n\ntype CaptionRendererConstructor = new (owner: CaptionsPlugin) => ICaptionRenderer;\n\n/**\n * Type definition for i18n options.\n */\nexport type CaptionsOptions = {\n renderer: CaptionRendererConstructor;\n defaultLocale: string;\n files: CaptionsImportListItem<CaptionsDict>[];\n fontFile: string;\n fontName: string;\n fontSizeMultiplier: number;\n maxWidth: number;\n textColor: number;\n backgroundColor: number;\n backgroundAlpha: number;\n enabled: boolean;\n floating: boolean;\n distance: number;\n padding: Padding;\n position: 'top' | 'bottom';\n};\n\n/**\n * Default options for i18n module.\n */\nconst defaultOptions: Partial<CaptionsOptions> = {\n renderer: CaptionsRenderer,\n defaultLocale: 'en',\n fontFile: './caper/font/Sans.fnt',\n fontName: 'Sans',\n fontSizeMultiplier: 1,\n maxWidth: 0.8,\n textColor: 0xffffff,\n backgroundColor: 0x0,\n backgroundAlpha: 0.4,\n enabled: true,\n floating: false,\n distance: 0,\n padding: { top: 20, left: 0, bottom: 20, right: 0 },\n position: 'top',\n};\n\nfunction resolveMaxWidth(value: number, appWidth: number) {\n if (value <= 0) {\n return 0;\n }\n if (value > 0 && value <= 1) {\n return appWidth * value;\n }\n return value;\n}\n\n/**\n * Interface for i18n module.\n */\nexport interface ICaptionsPlugin extends IPlugin {\n readonly locale: string;\n view: Container;\n enabled: boolean;\n floating: boolean;\n distance: number;\n padding: Padding;\n position: 'top' | 'bottom';\n backgroundColor: number;\n textColor: number;\n backgroundAlpha: number;\n fontSizeMultiplier: number;\n maxWidth: number;\n options: Partial<CaptionsOptions>;\n\n setLocale(localeId: string): Promise<string>;\n\n loadLocale(localeId: string): Promise<void>;\n\n render(): void;\n}\n\n/**\n * i18n module class.\n */\nexport class CaptionsPlugin extends Plugin<CaptionsOptions> implements ICaptionsPlugin {\n public readonly id = 'captions';\n public view: Container = new Container();\n public renderer: ICaptionRenderer;\n private _dicts: Record<string, CaptionsDict> = {};\n private _locales: string[];\n private _activeCaptionLine = -1;\n private _activeCaptionTime = 0;\n\n private _locale: string;\n\n /**\n * Getter for locale.\n */\n get locale(): string {\n return this._locale;\n }\n\n private _originalOptions: CaptionsOptions;\n\n get options(): CaptionsOptions {\n return this._options;\n }\n\n set options(value: Partial<CaptionsOptions>) {\n this._options = { ...this._options, ...value };\n\n if (value.padding) {\n this._options.padding = ensurePadding(value.padding);\n }\n }\n\n private _activeCaptionId?: string;\n\n get activeCaptionId() {\n return this._activeCaptionId;\n }\n\n private _paused: boolean = false;\n\n get paused(): boolean {\n return this._paused;\n }\n\n set paused(value: boolean) {\n this._paused = value;\n }\n\n get floating(): boolean {\n return this.options.floating;\n }\n\n set floating(value: boolean) {\n this.options.floating = value;\n this.updateRenderer();\n }\n\n public get enabled(): boolean {\n return this.options.enabled;\n }\n\n set enabled(value: boolean) {\n this.options.enabled = value;\n if (this.enabled) {\n this.view.visible = true;\n } else {\n this.view.visible = false;\n }\n }\n\n public get position(): 'top' | 'bottom' {\n return this.options.position;\n }\n\n public set position(value: 'top' | 'bottom') {\n this.options.position = value;\n this.renderer.updateSettings();\n }\n\n set backgroundColor(value: number) {\n this.options.backgroundColor = value;\n this.updateRenderer();\n }\n\n set textColor(value: number) {\n this.options.textColor = value;\n this.updateRenderer();\n }\n\n set backgroundAlpha(value: number) {\n this.options.backgroundAlpha = value;\n this.updateRenderer();\n }\n\n get padding(): Padding {\n return this.options.padding;\n }\n\n set padding(value: Partial<Padding> | PointLike) {\n this.options.padding = ensurePadding(value);\n this.updateRenderer();\n }\n\n get distance(): number {\n return this.options.distance;\n }\n\n set distance(value: number) {\n this.options.distance = value;\n this.updateRenderer();\n }\n\n public get fontSizeMultiplier(): number {\n return this.options.fontSizeMultiplier;\n }\n\n public set fontSizeMultiplier(value: number) {\n this.options.fontSizeMultiplier = value;\n this.renderer.resize();\n }\n\n public get maxWidth(): number {\n return this.options.maxWidth;\n }\n\n public set maxWidth(value: number) {\n this.options.maxWidth = resolveMaxWidth(value, this.app.size.width);\n this.renderer?.resize();\n }\n\n get list(): CaptionsDict {\n return this._dicts[this._locale];\n }\n\n get debug(): boolean {\n return isDebug;\n }\n\n /**\n * Initializes the i18n module.\n * sets the default locale and loads the locale files.\n * @param _app\n * @param options The i18n options.\n * @returns Promise<void>\n */\n public async initialize(options: Partial<CaptionsOptions>): Promise<void> {\n this._options = {\n ...defaultOptions,\n ...options,\n } as CaptionsOptions;\n\n this._originalOptions = { ...this._options };\n\n if (options?.padding) {\n this._options.padding = ensurePadding(options.padding);\n }\n\n this._locale = this.app.i18n.locale;\n this._locales = this.app.i18n.locales;\n\n if (this._options.fontFile) {\n await Assets.load(this._options.fontFile);\n }\n\n if (this._options?.files?.length > 0) {\n const files = this._options.files.filter((file) => this._locales.includes(file.id));\n for (const file of files) {\n await this.loadLocale(file.id);\n }\n }\n\n this.addSignalConnection(this.app.onResize.connect(this.handleResize, 'highest'));\n }\n\n private handleResize() {\n this.maxWidth = this._originalOptions.maxWidth;\n }\n\n public postInitialize(): void {\n this.app.i18n.onLocaleChanged.connect(this._handleLocaleChange);\n this.app.voiceover.onVoiceOverStart.connect(this._handleVoiceOverStart);\n this.app.voiceover.onVoiceOverPaused.connect(this._handleVoiceOverPaused);\n this.app.voiceover.onVoiceOverResumed.connect(this._handleVoiceOverResumed);\n this.app.voiceover.onVoiceOverComplete.connect(this._handleVoiceoverComplete);\n this.app.voiceover.onVoiceOverStopped.connect(this._handleVoiceoverStopped);\n\n this.app.stage.addChild(this.view);\n this._options.maxWidth = resolveMaxWidth(this._originalOptions.maxWidth, this.app.size.width);\n const RendererClass: CaptionRendererConstructor = this.options.renderer;\n this.renderer = this.view.addChild(new RendererClass(this)) as CaptionsRenderer;\n\n this.app.ticker.add(this.update);\n this.app.scenes.onSceneChangeStart.connect(this.stopAllCaptions);\n }\n\n /**\n * Sets the locale.\n * If the locale is not loaded, it will load it first.\n * @param localeId The locale id to set.\n * @returns Promise<string>\n */\n async setLocale(localeId: string) {\n if (this._locale === localeId) {\n return this._locale;\n }\n if (this._activeCaptionId) {\n this.stopCaption(this._activeCaptionId);\n }\n this._activeCaptionId = undefined;\n await this._loadAndSetLocale(localeId);\n this._locale = localeId;\n return this._locale;\n }\n\n /**\n * Loads a locale.\n * @param localeId The locale id to load.\n * @returns Promise<void>\n */\n async loadLocale(localeId: string) {\n if (this._locale === 'localeId') {\n return;\n }\n const file = this._options.files.find((file) => localeId === file.id);\n if (!file) {\n Logger.error(`i18n:: Could not find locale file for ${localeId}`);\n return;\n }\n this._dicts[localeId] = file.json\n ? await Assets.load(file.json)\n : await getDynamicModuleFromImportListItem(file as ImportListItem<CaptionsDict>);\n }\n\n render() {\n this.renderer.resize();\n }\n\n update(ticker: Ticker): void {\n if (this._paused) {\n return;\n }\n const dt: number = ticker.deltaMS / 1000;\n const id = this._activeCaptionId;\n if (id) {\n this._activeCaptionTime += dt;\n const ms = this._activeCaptionTime * 1000;\n const cc = this.list[id];\n const n = cc.length;\n for (let i = 0; i < n; i++) {\n if (ms >= cc[i].start && ms < cc[i].end) {\n if (this._activeCaptionLine !== i) {\n this.playLine(id, i);\n }\n break;\n }\n }\n if (ms >= cc[n - 1].end) {\n this.playLine(id, -1);\n }\n }\n }\n\n public playCaption(id: string): void {\n // remove extension from id\n id = this._getId(id);\n this.playLine(id, 0);\n }\n\n public stopCaption(id: string): void {\n // remove extension from id\n id = this._getId(id);\n if (this._activeCaptionId === id) {\n this.playLine(id, -1);\n }\n }\n\n public stopAllCaptions(): void {\n this.renderer?.stop();\n this._activeCaptionId = undefined;\n this._activeCaptionLine = -1;\n }\n\n private _getId(id: string) {\n return id.replace(/\\.[^/.]+$/, '');\n }\n\n private updateRenderer() {\n if (!this.enabled) {\n return;\n }\n this.renderer.updateSettings();\n }\n\n private _handleLocaleChange() {\n void this.setLocale(this.app.i18n.locale);\n }\n\n private _handleVoiceOverStart(vo: IAudioInstance) {\n this.playCaption(vo.id);\n }\n\n private _handleVoiceOverPaused() {\n this.paused = true;\n }\n\n private _handleVoiceOverResumed() {\n this.paused = false;\n }\n\n private _handleVoiceoverComplete(vo: IAudioInstance) {\n const id = this._getId(vo?.id || '');\n if (this._activeCaptionId === id) {\n this.stopCaption(id);\n }\n }\n\n private _handleVoiceoverStopped() {\n this.stopAllCaptions();\n }\n\n /**\n * Loads and sets a locale.\n * If the locale is not loaded, it will load it first.\n * @param localeId The locale id to load and set.\n */\n private async _loadAndSetLocale(localeId: string) {\n if (!this._dicts[localeId]) {\n await this.loadLocale(localeId);\n }\n }\n\n private playLine(id: string, index: number) {\n const cc = this.list[id];\n const line = cc ? cc[index] : undefined;\n if (line) {\n if (this._activeCaptionId !== id) {\n this._activeCaptionId = id;\n this._activeCaptionTime = 0;\n this._activeCaptionLine = -1;\n this.renderer?.start();\n }\n if (this._activeCaptionLine !== index) {\n this._activeCaptionLine = index;\n this.renderer?.lineBegin({\n id: this._activeCaptionId,\n ...cc[index],\n });\n }\n } else if (this._activeCaptionId === id && cc) {\n this.renderer?.lineEnd({\n id: this._activeCaptionId,\n ...cc[this._activeCaptionLine],\n });\n this.renderer?.stop();\n this._activeCaptionId = undefined;\n this._activeCaptionLine = -1;\n }\n }\n}\n"],"mappings":"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"}