@caperjs/core 0.4.1 → 0.5.1

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 (85) hide show
  1. package/build/internal/manifest.mjs +2 -2
  2. package/build/internal/validate.mjs +2 -1
  3. package/build/plugins/assetTypes.mjs +27 -23
  4. package/build/plugins/caperConfig.mjs +5 -3
  5. package/build/plugins/lists.mjs +0 -15
  6. package/lib/{CaptionsPlugin-cSJj-R9M.js → CaptionsPlugin-CDbGZrN9.js} +11 -11
  7. package/lib/CaptionsPlugin-CDbGZrN9.js.map +1 -0
  8. package/lib/{DataAdapter-D2tc6hx9.js → DataAdapter-BTlNzWqg.js} +2 -2
  9. package/lib/{DataAdapter-D2tc6hx9.js.map → DataAdapter-BTlNzWqg.js.map} +1 -1
  10. package/lib/{DebugRenderer-CGUKy6Fy.js → DebugRenderer-eSVpon4c.js} +2 -2
  11. package/lib/{DebugRenderer-CGUKy6Fy.js.map → DebugRenderer-eSVpon4c.js.map} +1 -1
  12. package/lib/{DevToolsPlugin-B8GX3H9b.js → DevToolsPlugin-Ci6r9j-m.js} +2 -2
  13. package/lib/{DevToolsPlugin-B8GX3H9b.js.map → DevToolsPlugin-Ci6r9j-m.js.map} +1 -1
  14. package/lib/{GSAPPlugin-Br9AjuTD.js → GSAPPlugin-C8zo-fyt.js} +2 -2
  15. package/lib/{GSAPPlugin-Br9AjuTD.js.map → GSAPPlugin-C8zo-fyt.js.map} +1 -1
  16. package/lib/{LayoutPlugin-BX3OP7pw.js → LayoutPlugin-Cs2lcXKb.js} +4 -4
  17. package/lib/{LayoutPlugin-BX3OP7pw.js.map → LayoutPlugin-Cs2lcXKb.js.map} +1 -1
  18. package/lib/{SpinePlugin-C-JDdXpf.js → SpinePlugin-CLgSXBQm.js} +2 -2
  19. package/lib/{SpinePlugin-C-JDdXpf.js.map → SpinePlugin-CLgSXBQm.js.map} +1 -1
  20. package/lib/{StatsPlugin-yJlJtoQ2.js → StatsPlugin-D_90U3BG.js} +4 -4
  21. package/lib/{StatsPlugin-yJlJtoQ2.js.map → StatsPlugin-D_90U3BG.js.map} +1 -1
  22. package/lib/{VoiceOverPlugin-DjSZp4Ko.js → VoiceOverPlugin-CAE1dtOw.js} +33 -33
  23. package/lib/{VoiceOverPlugin-DjSZp4Ko.js.map → VoiceOverPlugin-CAE1dtOw.js.map} +1 -1
  24. package/lib/caper.mjs +87 -87
  25. package/lib/caper.mjs.map +1 -1
  26. package/lib/{const-C3sxeYhl.js → const-BTDpIdRO.js} +192 -199
  27. package/lib/const-BTDpIdRO.js.map +1 -0
  28. package/lib/display/Container.d.ts +1 -28
  29. package/lib/display/Container.d.ts.map +1 -1
  30. package/lib/display/ParticleContainer.d.ts +20 -29
  31. package/lib/display/ParticleContainer.d.ts.map +1 -1
  32. package/lib/display/ParticleContainer.test.d.ts +2 -0
  33. package/lib/display/ParticleContainer.test.d.ts.map +1 -0
  34. package/lib/display/SpineAnimation.d.ts +1 -2
  35. package/lib/display/SpineAnimation.d.ts.map +1 -1
  36. package/lib/display/SpineAnimation.test.d.ts +1 -0
  37. package/lib/display/SpineAnimation.test.d.ts.map +1 -0
  38. package/lib/mixins/factory/const.d.ts.map +1 -1
  39. package/lib/mixins/focus.d.ts.map +1 -1
  40. package/lib/mixins/index.d.ts +1 -0
  41. package/lib/mixins/index.d.ts.map +1 -1
  42. package/lib/mixins/lifecycle.d.ts +56 -0
  43. package/lib/mixins/lifecycle.d.ts.map +1 -0
  44. package/lib/plugins/FullScreenPlugin.d.ts.map +1 -1
  45. package/lib/plugins/KeyboardPlugin.d.ts.map +1 -1
  46. package/lib/plugins/Plugin.d.ts.map +1 -1
  47. package/lib/plugins/WebEventsPlugin.d.ts +1 -0
  48. package/lib/plugins/WebEventsPlugin.d.ts.map +1 -1
  49. package/lib/plugins/audio/AudioInstance.d.ts +3 -3
  50. package/lib/plugins/audio/AudioInstance.d.ts.map +1 -1
  51. package/lib/plugins/captions/CaptionsRenderer.d.ts.map +1 -1
  52. package/lib/plugins/focus/FocusManagerPlugin.d.ts.map +1 -1
  53. package/lib/{registries-BfV-VF7a.js → registries-nN_hU44y.js} +109 -77
  54. package/lib/registries-nN_hU44y.js.map +1 -0
  55. package/lib/utils/types.d.ts.map +1 -1
  56. package/package.json +2 -2
  57. package/src/core/Application.ts +2 -2
  58. package/src/display/Container.ts +5 -72
  59. package/src/display/ParticleContainer.test.ts +127 -0
  60. package/src/display/ParticleContainer.ts +28 -62
  61. package/src/display/SpineAnimation.test.ts +79 -0
  62. package/src/display/SpineAnimation.ts +4 -6
  63. package/src/mixins/factory/const.ts +2 -1
  64. package/src/mixins/focus.ts +0 -2
  65. package/src/mixins/index.ts +4 -0
  66. package/src/mixins/lifecycle.ts +160 -0
  67. package/src/plugins/DataAdapter.ts +1 -1
  68. package/src/plugins/FullScreenPlugin.test.ts +9 -0
  69. package/src/plugins/FullScreenPlugin.ts +0 -1
  70. package/src/plugins/KeyboardPlugin.ts +0 -1
  71. package/src/plugins/Plugin.ts +0 -2
  72. package/src/plugins/TimerPlugin.ts +1 -1
  73. package/src/plugins/WebEventsPlugin.test.ts +13 -0
  74. package/src/plugins/WebEventsPlugin.ts +12 -1
  75. package/src/plugins/audio/AudioInstance.ts +2 -2
  76. package/src/plugins/captions/CaptionsPlugin.test.ts +48 -3
  77. package/src/plugins/captions/CaptionsPlugin.ts +5 -5
  78. package/src/plugins/captions/CaptionsRenderer.ts +0 -1
  79. package/src/plugins/focus/FocusManagerPlugin.ts +0 -1
  80. package/src/ui/Input.ts +2 -2
  81. package/src/utils/types.ts +0 -1
  82. package/src/version.ts +1 -1
  83. package/lib/CaptionsPlugin-cSJj-R9M.js.map +0 -1
  84. package/lib/const-C3sxeYhl.js.map +0 -1
  85. package/lib/registries-BfV-VF7a.js.map +0 -1
@@ -0,0 +1,160 @@
1
+ import type { DestroyOptions, Ticker } from 'pixi.js';
2
+
3
+ import { Application } from '../core/Application';
4
+ import { Signal } from '../signals';
5
+ import type { SignalOrder } from '../signals/Signal';
6
+ import type { AppTypeOverrides, Constructor, Size } from '../utils';
7
+
8
+ /**
9
+ * Configuration for the lifecycle mixin.
10
+ */
11
+ export type LifecycleConfig = {
12
+ /**
13
+ * Connect `resize()` to `app.onResize` while the object is on the stage.
14
+ */
15
+ autoResize: boolean;
16
+ /**
17
+ * Add `update()` to `app.ticker` while the object is on the stage.
18
+ */
19
+ autoUpdate: boolean;
20
+ /**
21
+ * Signal order for the `app.onResize` connection.
22
+ */
23
+ resizePriority: SignalOrder;
24
+ /**
25
+ * Ticker priority for the `update()` callback.
26
+ */
27
+ updatePriority: number;
28
+ };
29
+
30
+ const defaultLifecycleConfig: LifecycleConfig = {
31
+ autoResize: true,
32
+ autoUpdate: false,
33
+ resizePriority: 0,
34
+ updatePriority: -999999,
35
+ };
36
+
37
+ /**
38
+ * Interface for objects with a Caper display lifecycle.
39
+ */
40
+ export interface ILifecycle {
41
+ app: AppTypeOverrides['App'];
42
+
43
+ onDestroy: Signal<() => void>;
44
+
45
+ /**
46
+ * Wire the stage lifecycle up. Call this once, from the constructor of the
47
+ * class the mixin is applied to, after `bindAllMethods()`.
48
+ * @param config - The lifecycle configuration.
49
+ */
50
+ _initLifecycle(config?: Partial<LifecycleConfig>): void;
51
+
52
+ added(): Promise<void> | void;
53
+
54
+ removed(): Promise<void> | void;
55
+
56
+ resize(size?: Size): void;
57
+
58
+ update(ticker?: Ticker | number): void;
59
+
60
+ destroy(options?: DestroyOptions): void;
61
+ }
62
+
63
+ /**
64
+ * The WithLifecycle function is a higher-order function that gives a class the
65
+ * shared Caper display lifecycle: the overridable `added` / `removed` /
66
+ * `resize` / `update` hooks, the `onDestroy` signal, and the stage-scoped
67
+ * `app.onResize` / `app.ticker` connections that back `autoResize` /
68
+ * `autoUpdate` — all torn down again on `removed` and on `destroy`.
69
+ *
70
+ * @param {TBase extends Constructor<any>} Base - The base class to add the lifecycle to.
71
+ *
72
+ * @returns {TBase & Constructor<ILifecycle>} The modified class with the lifecycle.
73
+ */
74
+ export function WithLifecycle<TBase extends Constructor<any>>(Base: TBase): TBase & Constructor<ILifecycle> {
75
+ return class extends Base implements ILifecycle {
76
+ // `bindAllMethods()` stops walking the prototype chain here, so everything
77
+ // this mixin adds is bound to the instance — `resize` in particular has to
78
+ // be, since Signal.connect() takes a callback with no context.
79
+ private static readonly __caper_method_binding_root = true;
80
+
81
+ public onDestroy: Signal<() => void> = new Signal();
82
+
83
+ private __lifecycleConfig: LifecycleConfig = { ...defaultLifecycleConfig };
84
+
85
+ /**
86
+ * Get the application instance.
87
+ */
88
+ public get app(): AppTypeOverrides['App'] {
89
+ return Application.getInstance();
90
+ }
91
+
92
+ public _initLifecycle(config: Partial<LifecycleConfig> = {}) {
93
+ this.__lifecycleConfig = { ...defaultLifecycleConfig, ...config };
94
+ // Add an event listener for the 'added' event.
95
+ this.on('added', this._added);
96
+ this.on('removed', this._removed);
97
+ }
98
+
99
+ /**
100
+ * This method is called when the object is added to the stage. It is meant to be overridden by subclasses.
101
+ */
102
+ public added() {}
103
+
104
+ /**
105
+ * This method is called when the object is removed from the stage. It is meant to be overridden by subclasses.
106
+ */
107
+ public removed() {}
108
+
109
+ /**
110
+ * Resize the object. This method is meant to be overridden by subclasses.
111
+ * @param size
112
+ */
113
+ public resize(size?: Size) {
114
+ void size;
115
+ }
116
+
117
+ /**
118
+ * Update the object. This method is meant to be overridden by subclasses.
119
+ * @param ticker
120
+ */
121
+ public update(ticker?: Ticker | number) {
122
+ void ticker;
123
+ }
124
+
125
+ destroy(options?: DestroyOptions): void {
126
+ if (this.__lifecycleConfig.autoUpdate) {
127
+ this.app.ticker.remove(this.update, this);
128
+ }
129
+ this.onDestroy.emit();
130
+ super.destroy(options);
131
+ }
132
+
133
+ /**
134
+ * This method is called when the object is added to the stage. It sets up auto-resizing and auto-updating if enabled.
135
+ */
136
+ private _added() {
137
+ if (this.__lifecycleConfig.autoResize) {
138
+ this.addSignalConnection(this.app.onResize.connect(this.resize, this.__lifecycleConfig.resizePriority));
139
+ }
140
+
141
+ if (this.__lifecycleConfig.autoUpdate) {
142
+ this.app.ticker.add(this.update, this, this.__lifecycleConfig.updatePriority);
143
+ }
144
+
145
+ this.added();
146
+ }
147
+
148
+ private _removed() {
149
+ if (this.__lifecycleConfig.autoResize) {
150
+ this.app.onResize.disconnect(this.resize);
151
+ }
152
+
153
+ if (this.__lifecycleConfig.autoUpdate) {
154
+ this.app.ticker.remove(this.update, this);
155
+ }
156
+
157
+ this.removed();
158
+ }
159
+ } as unknown as TBase & Constructor<ILifecycle>;
160
+ }
@@ -245,7 +245,7 @@ export class DataAdapter extends Plugin<Partial<IDataAdapterOptions>> implements
245
245
  try {
246
246
  const parsedData = JSON.parse(loadedData);
247
247
  this.data[key] = parsedData;
248
- } catch (parseError) {
248
+ } catch {
249
249
  console.warn(
250
250
  `Failed to parse data for key "${String(key)}" in namespace "${this.namespace}". Using default value.`,
251
251
  );
@@ -45,4 +45,13 @@ describe('FullScreenPlugin fullscreenchange handling', () => {
45
45
  expect(plugin.isFullScreen).toBe(true);
46
46
  expect(spy).toHaveBeenCalledWith(true);
47
47
  });
48
+
49
+ it('handles a single fullscreenchange event exactly once', () => {
50
+ const spy = vi.fn();
51
+ plugin.onFullScreenChange.connect(spy);
52
+
53
+ document.dispatchEvent(new Event('fullscreenchange'));
54
+
55
+ expect(spy).toHaveBeenCalledTimes(1);
56
+ });
48
57
  });
@@ -147,7 +147,6 @@ export class FullScreenPlugin extends Plugin implements IFullScreenPlugin {
147
147
  this.listen(document, 'webkitfullscreenchange', this._onFullScreenChange);
148
148
  this.listen(document, 'mozfullscreenchange', this._onFullScreenChange);
149
149
  this.listen(document, 'msfullscreenchange', this._onFullScreenChange);
150
- this.listen(document, 'fullscreenchange', this._onFullScreenChange);
151
150
  }
152
151
 
153
152
  /**
@@ -58,7 +58,6 @@ export class KeyboardPlugin extends Plugin implements IKeyboardPlugin {
58
58
  this._enabled = value;
59
59
  }
60
60
 
61
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
62
61
  public initialize(_options: any, _app: IApplication): void {
63
62
  // track which keys are down
64
63
  this.listen(document, 'keydown', this._handleKeyDown as EventListener);
@@ -230,14 +230,12 @@ export class Plugin<O = any> implements IPlugin<O> {
230
230
 
231
231
  public initialize(options?: Partial<O>, _app?: IApplication): Promise<void> | void;
232
232
 
233
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
234
233
  public async initialize(_options: Partial<O>, _app?: IApplication): Promise<void> {
235
234
  return Promise.resolve(undefined);
236
235
  }
237
236
 
238
237
  public postInitialize(_app: IApplication): Promise<void> | void;
239
238
 
240
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
241
239
  public async postInitialize(_app: IApplication): Promise<void> {
242
240
  return Promise.resolve(undefined);
243
241
  }
@@ -132,7 +132,7 @@ function createTimerWorker() {
132
132
 
133
133
  const blob = new Blob([workerCode], { type: 'application/javascript' });
134
134
  return new Worker(URL.createObjectURL(blob));
135
- } catch (e) {
135
+ } catch {
136
136
  return null;
137
137
  }
138
138
  }
@@ -104,4 +104,17 @@ describe('WebEventsPlugin destroy', () => {
104
104
 
105
105
  expect(handler).not.toHaveBeenCalled();
106
106
  });
107
+
108
+ it('cancels a pending orientation timeout on destroy', async () => {
109
+ plugin.initialize();
110
+ const spy = vi.fn();
111
+ plugin.onOrientationChanged.connect(spy);
112
+
113
+ window.dispatchEvent(new Event('orientationchange'));
114
+ plugin.destroy();
115
+
116
+ await new Promise((resolve) => setTimeout(resolve, 20));
117
+
118
+ expect(spy).not.toHaveBeenCalled();
119
+ });
107
120
  });
@@ -30,6 +30,7 @@ export class WebEventsPlugin extends Plugin implements IWebEventsPlugin {
30
30
  private _debouncedEmitVisibility = debounce((value: boolean) => {
31
31
  this.onVisibilityChanged.emit(value);
32
32
  }, 1);
33
+ private _orientationTimeoutId: ReturnType<typeof setTimeout> | null = null;
33
34
 
34
35
  /**
35
36
  * Creates callback arrays and registers to web events.
@@ -55,6 +56,12 @@ export class WebEventsPlugin extends Plugin implements IWebEventsPlugin {
55
56
  if (window.visualViewport) {
56
57
  this.listen(window.visualViewport, 'resize', this._onVisualViewportResize);
57
58
  }
59
+ this.addDisposer(() => {
60
+ if (this._orientationTimeoutId !== null) {
61
+ clearTimeout(this._orientationTimeoutId);
62
+ this._orientationTimeoutId = null;
63
+ }
64
+ });
58
65
  }
59
66
 
60
67
  protected getCoreSignals(): string[] {
@@ -132,7 +139,11 @@ export class WebEventsPlugin extends Plugin implements IWebEventsPlugin {
132
139
  return;
133
140
  }
134
141
 
135
- setTimeout(() => {
142
+ if (this._orientationTimeoutId !== null) {
143
+ clearTimeout(this._orientationTimeoutId);
144
+ }
145
+ this._orientationTimeoutId = setTimeout(() => {
146
+ this._orientationTimeoutId = null;
136
147
  orientation = getOrientation();
137
148
  this.onOrientationChanged.emit({ orientation, screenOrientation });
138
149
  }, 10);
@@ -41,7 +41,7 @@ export interface IAudioInstance<C extends ChannelName = ChannelName> {
41
41
  resume(): void;
42
42
  }
43
43
 
44
- export class AudioInstance<C extends ChannelName = ChannelName> implements IAudioInstance {
44
+ export class AudioInstance<C extends ChannelName = ChannelName> implements IAudioInstance<C> {
45
45
  public storedVolume: number;
46
46
  public onStart: Signal<(instance: IAudioInstance) => void> = new Signal<(instance: IAudioInstance) => void>();
47
47
  public onStop: Signal<(instance: IAudioInstance) => void> = new Signal<(instance: IAudioInstance) => void>();
@@ -53,7 +53,7 @@ export class AudioInstance<C extends ChannelName = ChannelName> implements IAudi
53
53
  constructor(
54
54
  public id: string,
55
55
  public channel: IAudioChannel,
56
- public manager: IAudioManagerPlugin,
56
+ public manager: IAudioManagerPlugin<C>,
57
57
  ) {
58
58
  bindAllMethods(this);
59
59
  this.muted = this.channel.muted;
@@ -1,17 +1,24 @@
1
- import { Assets } from 'pixi.js';
2
- import { describe, expect, it, vi } from 'vitest';
1
+ import { Assets, Container } from 'pixi.js';
2
+ import { afterEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
4
  // CaptionsPlugin -> Plugin.ts transitively imports Application → Pixi display graph. Stub it.
5
5
  vi.mock('../../core', () => ({
6
6
  coreFunctionRegistry: {},
7
7
  coreSignalRegistry: {},
8
8
  }));
9
+ const { appMock } = vi.hoisted(() => ({ appMock: {} as any }));
9
10
  vi.mock('../../core/Application', () => ({
10
- Application: { getInstance: () => ({}) },
11
+ Application: { getInstance: () => appMock },
11
12
  }));
12
13
 
14
+ import { Signal } from '../../signals';
13
15
  import { CaptionsPlugin } from './CaptionsPlugin';
14
16
 
17
+ class FakeCaptionsRenderer extends Container {
18
+ updateSettings() {}
19
+ resize() {}
20
+ }
21
+
15
22
  describe('CaptionsPlugin', () => {
16
23
  it('loadLocale returns early when the locale is already loaded', async () => {
17
24
  const load = vi.spyOn(Assets, 'load').mockResolvedValue({});
@@ -38,3 +45,41 @@ describe('CaptionsPlugin', () => {
38
45
  load.mockRestore();
39
46
  });
40
47
  });
48
+
49
+ describe('CaptionsPlugin destroy', () => {
50
+ afterEach(() => {
51
+ for (const key of Object.keys(appMock)) delete appMock[key];
52
+ });
53
+
54
+ it('stops handling voiceover signals after destroy', () => {
55
+ appMock.voiceover = {
56
+ onVoiceOverStart: new Signal<(vo: any) => void>(),
57
+ onVoiceOverPaused: new Signal<() => void>(),
58
+ onVoiceOverResumed: new Signal<() => void>(),
59
+ onVoiceOverComplete: new Signal<(vo: any) => void>(),
60
+ onVoiceOverStopped: new Signal<() => void>(),
61
+ };
62
+ appMock.i18n = { onLocaleChanged: new Signal<() => void>() };
63
+ appMock.stage = { addChild: vi.fn() };
64
+ appMock.ticker = { add: vi.fn(), remove: vi.fn() };
65
+ appMock.scenes = { onSceneChangeStart: new Signal<() => void>() };
66
+ appMock.size = { width: 800 };
67
+
68
+ const plugin = new CaptionsPlugin();
69
+ (plugin as any)._options = { renderer: FakeCaptionsRenderer };
70
+ (plugin as any)._originalOptions = { maxWidth: 0.8 };
71
+ (plugin as any)._locale = 'en';
72
+ (plugin as any)._dicts = { en: {} };
73
+
74
+ const spy = vi.spyOn(plugin as any, '_handleVoiceOverStart');
75
+ plugin.postInitialize();
76
+
77
+ appMock.voiceover.onVoiceOverStart.emit({ id: 'x' });
78
+ expect(spy).toHaveBeenCalledTimes(1);
79
+
80
+ plugin.destroy();
81
+ appMock.voiceover.onVoiceOverStart.emit({ id: 'x' });
82
+
83
+ expect(spy).toHaveBeenCalledTimes(1);
84
+ });
85
+ });
@@ -287,11 +287,11 @@ export class CaptionsPlugin extends Plugin<CaptionsOptions> implements ICaptions
287
287
 
288
288
  public postInitialize(): void {
289
289
  this.app.i18n.onLocaleChanged.connect(this._handleLocaleChange);
290
- this.app.voiceover.onVoiceOverStart.connect(this._handleVoiceOverStart);
291
- this.app.voiceover.onVoiceOverPaused.connect(this._handleVoiceOverPaused);
292
- this.app.voiceover.onVoiceOverResumed.connect(this._handleVoiceOverResumed);
293
- this.app.voiceover.onVoiceOverComplete.connect(this._handleVoiceoverComplete);
294
- this.app.voiceover.onVoiceOverStopped.connect(this._handleVoiceoverStopped);
290
+ this.addSignalConnection(this.app.voiceover.onVoiceOverStart.connect(this._handleVoiceOverStart));
291
+ this.addSignalConnection(this.app.voiceover.onVoiceOverPaused.connect(this._handleVoiceOverPaused));
292
+ this.addSignalConnection(this.app.voiceover.onVoiceOverResumed.connect(this._handleVoiceOverResumed));
293
+ this.addSignalConnection(this.app.voiceover.onVoiceOverComplete.connect(this._handleVoiceoverComplete));
294
+ this.addSignalConnection(this.app.voiceover.onVoiceOverStopped.connect(this._handleVoiceoverStopped));
295
295
 
296
296
  this.app.stage.addChild(this.view);
297
297
  this._options.maxWidth = resolveMaxWidth(this._originalOptions.maxWidth, this.app.size.width);
@@ -105,7 +105,6 @@ export class CaptionsRenderer extends Container implements ICaptionRenderer {
105
105
  this.resize();
106
106
  }
107
107
 
108
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
109
108
  public lineEnd(_line: CaptionLine): void {}
110
109
 
111
110
  public resize(): void {
@@ -495,7 +495,6 @@ export class FocusManagerPlugin extends Plugin<FocusManagerPluginOptions> implem
495
495
  this.onFocusLayerChange.emit(this._currentLayerId);
496
496
  }
497
497
 
498
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
499
498
  public postInitialize(_app: IApplication): Promise<void> | void {}
500
499
 
501
500
  public clearFocus() {
package/src/ui/Input.ts CHANGED
@@ -389,7 +389,7 @@ export class Input extends Focusable(Interactive(WithSignals(Container))) {
389
389
 
390
390
  if (!this.options.placeholder) {
391
391
  this.options.placeholder = {
392
- color: Number(this.options.style?.fill) ?? 0x666666,
392
+ color: Number(this.options.style?.fill),
393
393
  };
394
394
  }
395
395
 
@@ -592,7 +592,7 @@ export class Input extends Focusable(Interactive(WithSignals(Container))) {
592
592
  } else {
593
593
  this.domElement.setSelectionRange(selection, selection, 'none');
594
594
  }
595
- } catch (e) {
595
+ } catch {
596
596
  // nothing
597
597
  }
598
598
  this._updateCaretAndSelection();
@@ -311,7 +311,6 @@ export interface AppTypeOverrides {}
311
311
 
312
312
  type _EntityClassMap = AppTypeOverrides extends { EntityClasses: infer E } ? E : {};
313
313
  type _PopupClassMap = AppTypeOverrides extends { PopupClasses: infer P } ? P : {};
314
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
315
314
  type _SceneClassMap = AppTypeOverrides extends { SceneClasses: infer S } ? S : {};
316
315
 
317
316
  /** Any-constructor shape. */
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.4.1';
2
+ export const version: string = '0.5.1';
3
3
  export const pixiVersion: string = '8.19.0';
@@ -1 +0,0 @@
1
- {"version":3,"file":"CaptionsPlugin-cSJj-R9M.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/Container';\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.addTickerCallback(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._dicts[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":";;;;;AAgCA,IAAa,IAAb,cAAsC,EAAsC;CAM1E,YAAY,GAAgC;AA4B1C,MA3BA,MAAM,EAAE,YAAY,IAAO,CAAC,EADV,KAAA,SAAA,gBAHW;GAAE,OAAO;GAAG,QAAQ;GAAG,kBAC1B,IAK1B,KAAK,MAAM,KAAK,IAAI,OAAO;GAAE,OAAO,EAAQ;GAAO,QAAQ,CAAC,IAAK,EAAE;GAAE,CAAC,EACtE,KAAK,IAAI,OAAO,KAAK,OAAO,QAAQ,iBACpC,KAAK,IAAI,QAAQ,GAEjB,KAAK,QAAQ,KAAK,IAAI,WAAW;GAC/B,MAAM,EAAO,QAAQ;GACrB,YAAY;GACZ,aAAa;GACb,OAAO;IACL,MAAM,KAAK,OAAO,QAAQ;IAC1B,UAAU,KAAK,WAAW,KAAK,mBAAmB;IAClD,YAAY;IACZ,OAAO;IACP,eAAe,KAAK,OAAO;IAC3B,UAAU;IACV,YAAY,KAAK,WAAW,KAAK,mBAAmB,GAAG,IAAI;IAC5D;GACF,CAAC,EACF,KAAK,MAAM,MAAM,WAAW,IAC5B,KAAK,MAAM,QAAQ,GACnB,KAAK,MAAM,UAAU,IACrB,KAAK,MAAM,OAAO,IAAI,IACtB,KAAK,MAAM,OAAO,IAAI,GACtB,KAAK,SAAS,KAAK,MAAM,EAErB,KAAK,OAAO,OAAO;GACrB,IAAI,IAAY;AAEhB,GADA,KAAK,IAAI,YAAY,UACrB,KAAK,IAAI,GAAG,oBAAoB;AAC1B,UAGJ,IAAY,IACZ,iBAAiB;AAEV,KADL,IAAY,IACP,KAAK,IAAI,UAAU,QAAQ;OAC/B,IAAI;KACP;;;CAIN,QAAqB;AAMnB,EALA,KAAK,MAAM,UAAU,IACrB,KAAK,IAAI,UAAU,IAEnB,EAAK,aAAa,CAAC,KAAK,KAAK,KAAK,MAAM,CAAC,EACzC,KAAK,QAAQ;GAAE,OAAO,KAAK,OAAO,QAAQ;GAAiB,SAAS;GAAM,UAAU;GAAK,EAAE,KAAK,IAAI,EACpG,KAAK,QAAQ;GAAE,OAAO;GAAG,SAAS;GAAM,UAAU;GAAK,EAAE,KAAK,MAAM;;CAGtE,OAAoB;AAElB,EADA,EAAK,aAAa,CAAC,KAAK,KAAK,KAAK,MAAM,CAAC,EACzC,KAAK,QAAQ;GAAE,OAAO;GAAG,SAAS;GAAO,UAAU;GAAK,EAAE,CAAC,KAAK,KAAK,KAAK,MAAM,CAAC;;CAGnF,UAAiB,GAAyB;EACxC,IAAI,IAAO,EAAK,QACb,QAAQ,aAAa,GAAG,CACxB,QAAQ,cAAc,GAAG;AAK5B,EAJI,EAAK,UAAU,MACjB,IAAO,EAAK,OAAO,EAAE,CAAC,aAAa,GAAG,EAAK,MAAM,EAAE,GAErD,KAAK,MAAM,OAAO,GAClB,KAAK,QAAQ;;CAIf,QAAe,GAA0B;CAEzC,SAAsB;EACpB,IAAM,IAAO,KAAK,IAAI,MAChB,IAAO,KAAK,OAAO;AAUzB,EARA,KAAK,MAAM,QAAQ,EAAK,OACxB,KAAK,MAAM,SAAS,EAAK,QAEzB,KAAK,MAAM,MAAM,WAAW,KAAK,WAAW,KAAK,mBAAmB,EACpE,KAAK,MAAM,MAAM,gBAAgB,EAAK,WAAW,GACjD,KAAK,MAAM,MAAM,IAAI,GAAI,EACzB,KAAK,MAAM,SAAS,IAAI,KAAK,MAAM,QAAQ,IAAK,EAAE,EAElD,KAAK,IAAI,SAAS,IAAI,KAAK,MAAM,QAAQ,IAAK,EAAE;EAEhD,IAAI,IAAI,KAAK,MAAM;AAWnB,MAVI,EAAK,QAAQ,QACf,KAAK,EAAK,QAAQ,MAEhB,EAAK,QAAQ,WACf,KAAK,EAAK,QAAQ,SAEpB,KAAK,IAAI,SAAS,GAElB,KAAK,MAAM,KAAK,EAAK,QAAQ,KAEzB,EAAK,UAAU;GACjB,IAAI,IAAI,KAAK,MAAM;AAUnB,GARI,EAAK,QAAQ,SACf,KAAK,EAAK,QAAQ,OAEhB,EAAK,QAAQ,UACf,KAAK,EAAK,QAAQ,QAGpB,KAAK,IAAI,KAAK,EAAK,QAAQ,QAAQ,KAAM,EAAK,QAAQ,OAAO,IAC7D,KAAK,IAAI,QAAQ;QAEjB,MAAK,IAAI,QAAQ,EAAK;AAGxB,EAAI,EAAK,aAAa,SACpB,KAAK,SAAS,IAAI,CAAC,EAAK,QAAQ,IAAK,KAAK,MAAM,CAAC,EAAK,SAAS,GAAI,CAAC,EAChE,EAAK,aACP,KAAK,SAAS,KAAK,KAAK,MAAM,EAAK,WAAW,KAAK,IAAI,EAAE,MAG3D,KAAK,SAAS,IAAI,CAAC,EAAK,QAAQ,IAAK,KAAK,MAAM,EAAK,SAAS,KAAM,KAAK,IAAI,OAAO,CAAC,EACjF,EAAK,aACP,KAAK,SAAS,KAAK,KAAK,MAAM,EAAK,WAAW,KAAK,IAAI,EAAE;;CAK/D,iBAAiB;AACf,EAAI,KAAK,OAAO,QAAQ,WACtB,KAAK,MAAM,MAAM,OAAO,KAAK,OAAO,QAAQ,WAC5C,KAAK,IAAI,OAAO,KAAK,OAAO,QAAQ,iBAChC,KAAK,IAAI,YACX,KAAK,IAAI,QAAQ,KAAK,OAAO,QAAQ,iBACrC,KAAK,QAAQ,MAGf,KAAK,MAAM,EACX,KAAK,IAAI,UAAU,IACnB,KAAK,MAAM,UAAU,IACrB,KAAK,IAAI,QAAQ,KAAK,MAAM,QAAQ;;CAIxC,oBAAoC;AAClC,SAAO,KAAK,OAAO,QAAQ;;GC5KzB,IAAU,GA6CV,IAA2C;CAC/C,UAAU;CACV,eAAe;CACf,UAAU;CACV,UAAU;CACV,oBAAoB;CACpB,UAAU;CACV,WAAW;CACX,iBAAiB;CACjB,iBAAiB;CACjB,SAAS;CACT,UAAU;CACV,UAAU;CACV,SAAS;EAAE,KAAK;EAAI,MAAM;EAAG,QAAQ;EAAI,OAAO;EAAG;CACnD,UAAU;CACX;AAED,SAAS,EAAgB,GAAe,GAAkB;AAOxD,QANI,KAAS,IACJ,IAEL,IAAQ,KAAK,KAAS,IACjB,IAAW,IAEb;;AA+BT,IAAa,IAAb,cAAoC,EAAmD;;yBAChE,wBACI,IAAI,GAAW,gBAEO,EAAE,4BAEpB,8BACA,kBA+BF;;CAxB3B,IAAI,SAAiB;AACnB,SAAO,KAAK;;CAKd,IAAI,UAA2B;AAC7B,SAAO,KAAK;;CAGd,IAAI,QAAQ,GAAiC;AAG3C,EAFA,KAAK,WAAW;GAAE,GAAG,KAAK;GAAU,GAAG;GAAO,EAE1C,EAAM,YACR,KAAK,SAAS,UAAU,EAAc,EAAM,QAAQ;;CAMxD,IAAI,kBAAkB;AACpB,SAAO,KAAK;;CAKd,IAAI,SAAkB;AACpB,SAAO,KAAK;;CAGd,IAAI,OAAO,GAAgB;AACzB,OAAK,UAAU;;CAGjB,IAAI,WAAoB;AACtB,SAAO,KAAK,QAAQ;;CAGtB,IAAI,SAAS,GAAgB;AAE3B,EADA,KAAK,QAAQ,WAAW,GACxB,KAAK,gBAAgB;;CAGvB,IAAW,UAAmB;AAC5B,SAAO,KAAK,QAAQ;;CAGtB,IAAI,QAAQ,GAAgB;AAE1B,EADA,KAAK,QAAQ,UAAU,GACnB,KAAK,UACP,KAAK,KAAK,UAAU,KAEpB,KAAK,KAAK,UAAU;;CAIxB,IAAW,WAA6B;AACtC,SAAO,KAAK,QAAQ;;CAGtB,IAAW,SAAS,GAAyB;AAE3C,EADA,KAAK,QAAQ,WAAW,GACxB,KAAK,SAAS,gBAAgB;;CAGhC,IAAI,gBAAgB,GAAe;AAEjC,EADA,KAAK,QAAQ,kBAAkB,GAC/B,KAAK,gBAAgB;;CAGvB,IAAI,UAAU,GAAe;AAE3B,EADA,KAAK,QAAQ,YAAY,GACzB,KAAK,gBAAgB;;CAGvB,IAAI,gBAAgB,GAAe;AAEjC,EADA,KAAK,QAAQ,kBAAkB,GAC/B,KAAK,gBAAgB;;CAGvB,IAAI,UAAmB;AACrB,SAAO,KAAK,QAAQ;;CAGtB,IAAI,QAAQ,GAAqC;AAE/C,EADA,KAAK,QAAQ,UAAU,EAAc,EAAM,EAC3C,KAAK,gBAAgB;;CAGvB,IAAI,WAAmB;AACrB,SAAO,KAAK,QAAQ;;CAGtB,IAAI,SAAS,GAAe;AAE1B,EADA,KAAK,QAAQ,WAAW,GACxB,KAAK,gBAAgB;;CAGvB,IAAW,qBAA6B;AACtC,SAAO,KAAK,QAAQ;;CAGtB,IAAW,mBAAmB,GAAe;AAE3C,EADA,KAAK,QAAQ,qBAAqB,GAClC,KAAK,SAAS,QAAQ;;CAGxB,IAAW,WAAmB;AAC5B,SAAO,KAAK,QAAQ;;CAGtB,IAAW,SAAS,GAAe;AAEjC,EADA,KAAK,QAAQ,WAAW,EAAgB,GAAO,KAAK,IAAI,KAAK,MAAM,EACnE,KAAK,UAAU,QAAQ;;CAGzB,IAAI,OAAqB;AACvB,SAAO,KAAK,OAAO,KAAK;;CAG1B,IAAI,QAAiB;AACnB,SAAO;;CAUT,MAAa,WAAW,GAAkD;AAmBxE,MAlBA,KAAK,WAAW;GACd,GAAG;GACH,GAAG;GACJ,EAED,KAAK,mBAAmB,EAAE,GAAG,KAAK,UAAU,EAExC,GAAS,YACX,KAAK,SAAS,UAAU,EAAc,EAAQ,QAAQ,GAGxD,KAAK,UAAU,KAAK,IAAI,KAAK,QAC7B,KAAK,WAAW,KAAK,IAAI,KAAK,SAE1B,KAAK,SAAS,YAChB,MAAM,EAAO,KAAK,KAAK,SAAS,SAAS,EAGvC,KAAK,UAAU,OAAO,SAAS,GAAG;GACpC,IAAM,IAAQ,KAAK,SAAS,MAAM,QAAQ,MAAS,KAAK,SAAS,SAAS,EAAK,GAAG,CAAC;AACnF,QAAK,IAAM,KAAQ,EACjB,OAAM,KAAK,WAAW,EAAK,GAAG;;AAIlC,OAAK,oBAAoB,KAAK,IAAI,SAAS,QAAQ,KAAK,cAAc,UAAU,CAAC;;CAGnF,eAAuB;AACrB,OAAK,WAAW,KAAK,iBAAiB;;CAGxC,iBAA8B;AAS5B,EARA,KAAK,IAAI,KAAK,gBAAgB,QAAQ,KAAK,oBAAoB,EAC/D,KAAK,IAAI,UAAU,iBAAiB,QAAQ,KAAK,sBAAsB,EACvE,KAAK,IAAI,UAAU,kBAAkB,QAAQ,KAAK,uBAAuB,EACzE,KAAK,IAAI,UAAU,mBAAmB,QAAQ,KAAK,wBAAwB,EAC3E,KAAK,IAAI,UAAU,oBAAoB,QAAQ,KAAK,yBAAyB,EAC7E,KAAK,IAAI,UAAU,mBAAmB,QAAQ,KAAK,wBAAwB,EAE3E,KAAK,IAAI,MAAM,SAAS,KAAK,KAAK,EAClC,KAAK,SAAS,WAAW,EAAgB,KAAK,iBAAiB,UAAU,KAAK,IAAI,KAAK,MAAM;EAC7F,IAAM,IAA4C,KAAK,QAAQ;AAI/D,EAHA,KAAK,WAAW,KAAK,KAAK,SAAS,IAAI,EAAc,KAAK,CAAC,EAE3D,KAAK,kBAAkB,KAAK,OAAO,EACnC,KAAK,IAAI,OAAO,mBAAmB,QAAQ,KAAK,gBAAgB;;CASlE,MAAM,UAAU,GAAkB;AAUhC,SATI,KAAK,YAAY,IACZ,KAAK,WAEV,KAAK,oBACP,KAAK,YAAY,KAAK,iBAAiB,EAEzC,KAAK,mBAAmB,KAAA,GACxB,MAAM,KAAK,kBAAkB,EAAS,EACtC,KAAK,UAAU,GACR,KAAK;;CAQd,MAAM,WAAW,GAAkB;AACjC,MAAI,KAAK,OAAO,GACd;EAEF,IAAM,IAAO,KAAK,SAAS,MAAM,MAAM,MAAS,MAAa,EAAK,GAAG;AACrE,MAAI,CAAC,GAAM;AACT,KAAO,MAAM,yCAAyC,IAAW;AACjE;;AAEF,OAAK,OAAO,KAAY,EAAK,OACzB,MAAM,EAAO,KAAK,EAAK,KAAK,GAC5B,MAAM,EAAmC,EAAqC;;CAGpF,SAAS;AACP,OAAK,SAAS,QAAQ;;CAGxB,OAAO,GAAsB;AAC3B,MAAI,KAAK,QACP;EAEF,IAAM,IAAa,EAAO,UAAU,KAC9B,IAAK,KAAK;AAChB,MAAI,GAAI;AACN,QAAK,sBAAsB;GAC3B,IAAM,IAAK,KAAK,qBAAqB,KAC/B,IAAK,KAAK,KAAK,IACf,IAAI,EAAG;AACb,QAAK,IAAI,IAAI,GAAG,IAAI,GAAG,IACrB,KAAI,KAAM,EAAG,GAAG,SAAS,IAAK,EAAG,GAAG,KAAK;AACvC,IAAI,KAAK,uBAAuB,KAC9B,KAAK,SAAS,GAAI,EAAE;AAEtB;;AAGJ,GAAI,KAAM,EAAG,IAAI,GAAG,OAClB,KAAK,SAAS,GAAI,GAAG;;;CAK3B,YAAmB,GAAkB;AAGnC,EADA,IAAK,KAAK,OAAO,EAAG,EACpB,KAAK,SAAS,GAAI,EAAE;;CAGtB,YAAmB,GAAkB;AAGnC,EADA,IAAK,KAAK,OAAO,EAAG,EAChB,KAAK,qBAAqB,KAC5B,KAAK,SAAS,GAAI,GAAG;;CAIzB,kBAA+B;AAG7B,EAFA,KAAK,UAAU,MAAM,EACrB,KAAK,mBAAmB,KAAA,GACxB,KAAK,qBAAqB;;CAG5B,OAAe,GAAY;AACzB,SAAO,EAAG,QAAQ,aAAa,GAAG;;CAGpC,iBAAyB;AAClB,OAAK,WAGV,KAAK,SAAS,gBAAgB;;CAGhC,sBAA8B;AACvB,OAAK,UAAU,KAAK,IAAI,KAAK,OAAO;;CAG3C,sBAA8B,GAAoB;AAChD,OAAK,YAAY,EAAG,GAAG;;CAGzB,yBAAiC;AAC/B,OAAK,SAAS;;CAGhB,0BAAkC;AAChC,OAAK,SAAS;;CAGhB,yBAAiC,GAAoB;EACnD,IAAM,IAAK,KAAK,OAAO,GAAI,MAAM,GAAG;AACpC,EAAI,KAAK,qBAAqB,KAC5B,KAAK,YAAY,EAAG;;CAIxB,0BAAkC;AAChC,OAAK,iBAAiB;;CAQxB,MAAc,kBAAkB,GAAkB;AAChD,EAAK,KAAK,OAAO,MACf,MAAM,KAAK,WAAW,EAAS;;CAInC,SAAiB,GAAY,GAAe;EAC1C,IAAM,IAAK,KAAK,KAAK;AAErB,EADa,KAAK,EAAG,MAEf,KAAK,qBAAqB,MAC5B,KAAK,mBAAmB,GACxB,KAAK,qBAAqB,GAC1B,KAAK,qBAAqB,IAC1B,KAAK,UAAU,OAAO,GAEpB,KAAK,uBAAuB,MAC9B,KAAK,qBAAqB,GAC1B,KAAK,UAAU,UAAU;GACvB,IAAI,KAAK;GACT,GAAG,EAAG;GACP,CAAC,KAEK,KAAK,qBAAqB,KAAM,MACzC,KAAK,UAAU,QAAQ;GACrB,IAAI,KAAK;GACT,GAAG,EAAG,KAAK;GACZ,CAAC,EACF,KAAK,UAAU,MAAM,EACrB,KAAK,mBAAmB,KAAA,GACxB,KAAK,qBAAqB"}