@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.
- package/build/internal/manifest.mjs +2 -2
- package/build/internal/validate.mjs +2 -1
- package/build/plugins/assetTypes.mjs +27 -23
- package/build/plugins/caperConfig.mjs +5 -3
- package/build/plugins/lists.mjs +0 -15
- package/lib/{CaptionsPlugin-cSJj-R9M.js → CaptionsPlugin-CDbGZrN9.js} +11 -11
- package/lib/CaptionsPlugin-CDbGZrN9.js.map +1 -0
- package/lib/{DataAdapter-D2tc6hx9.js → DataAdapter-BTlNzWqg.js} +2 -2
- package/lib/{DataAdapter-D2tc6hx9.js.map → DataAdapter-BTlNzWqg.js.map} +1 -1
- package/lib/{DebugRenderer-CGUKy6Fy.js → DebugRenderer-eSVpon4c.js} +2 -2
- package/lib/{DebugRenderer-CGUKy6Fy.js.map → DebugRenderer-eSVpon4c.js.map} +1 -1
- package/lib/{DevToolsPlugin-B8GX3H9b.js → DevToolsPlugin-Ci6r9j-m.js} +2 -2
- package/lib/{DevToolsPlugin-B8GX3H9b.js.map → DevToolsPlugin-Ci6r9j-m.js.map} +1 -1
- package/lib/{GSAPPlugin-Br9AjuTD.js → GSAPPlugin-C8zo-fyt.js} +2 -2
- package/lib/{GSAPPlugin-Br9AjuTD.js.map → GSAPPlugin-C8zo-fyt.js.map} +1 -1
- package/lib/{LayoutPlugin-BX3OP7pw.js → LayoutPlugin-Cs2lcXKb.js} +4 -4
- package/lib/{LayoutPlugin-BX3OP7pw.js.map → LayoutPlugin-Cs2lcXKb.js.map} +1 -1
- package/lib/{SpinePlugin-C-JDdXpf.js → SpinePlugin-CLgSXBQm.js} +2 -2
- package/lib/{SpinePlugin-C-JDdXpf.js.map → SpinePlugin-CLgSXBQm.js.map} +1 -1
- package/lib/{StatsPlugin-yJlJtoQ2.js → StatsPlugin-D_90U3BG.js} +4 -4
- package/lib/{StatsPlugin-yJlJtoQ2.js.map → StatsPlugin-D_90U3BG.js.map} +1 -1
- package/lib/{VoiceOverPlugin-DjSZp4Ko.js → VoiceOverPlugin-CAE1dtOw.js} +33 -33
- package/lib/{VoiceOverPlugin-DjSZp4Ko.js.map → VoiceOverPlugin-CAE1dtOw.js.map} +1 -1
- package/lib/caper.mjs +87 -87
- package/lib/caper.mjs.map +1 -1
- package/lib/{const-C3sxeYhl.js → const-BTDpIdRO.js} +192 -199
- package/lib/const-BTDpIdRO.js.map +1 -0
- package/lib/display/Container.d.ts +1 -28
- package/lib/display/Container.d.ts.map +1 -1
- package/lib/display/ParticleContainer.d.ts +20 -29
- package/lib/display/ParticleContainer.d.ts.map +1 -1
- package/lib/display/ParticleContainer.test.d.ts +2 -0
- package/lib/display/ParticleContainer.test.d.ts.map +1 -0
- package/lib/display/SpineAnimation.d.ts +1 -2
- package/lib/display/SpineAnimation.d.ts.map +1 -1
- package/lib/display/SpineAnimation.test.d.ts +1 -0
- package/lib/display/SpineAnimation.test.d.ts.map +1 -0
- package/lib/mixins/factory/const.d.ts.map +1 -1
- package/lib/mixins/focus.d.ts.map +1 -1
- package/lib/mixins/index.d.ts +1 -0
- package/lib/mixins/index.d.ts.map +1 -1
- package/lib/mixins/lifecycle.d.ts +56 -0
- package/lib/mixins/lifecycle.d.ts.map +1 -0
- package/lib/plugins/FullScreenPlugin.d.ts.map +1 -1
- package/lib/plugins/KeyboardPlugin.d.ts.map +1 -1
- package/lib/plugins/Plugin.d.ts.map +1 -1
- package/lib/plugins/WebEventsPlugin.d.ts +1 -0
- package/lib/plugins/WebEventsPlugin.d.ts.map +1 -1
- package/lib/plugins/audio/AudioInstance.d.ts +3 -3
- package/lib/plugins/audio/AudioInstance.d.ts.map +1 -1
- package/lib/plugins/captions/CaptionsRenderer.d.ts.map +1 -1
- package/lib/plugins/focus/FocusManagerPlugin.d.ts.map +1 -1
- package/lib/{registries-BfV-VF7a.js → registries-nN_hU44y.js} +109 -77
- package/lib/registries-nN_hU44y.js.map +1 -0
- package/lib/utils/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/core/Application.ts +2 -2
- package/src/display/Container.ts +5 -72
- package/src/display/ParticleContainer.test.ts +127 -0
- package/src/display/ParticleContainer.ts +28 -62
- package/src/display/SpineAnimation.test.ts +79 -0
- package/src/display/SpineAnimation.ts +4 -6
- package/src/mixins/factory/const.ts +2 -1
- package/src/mixins/focus.ts +0 -2
- package/src/mixins/index.ts +4 -0
- package/src/mixins/lifecycle.ts +160 -0
- package/src/plugins/DataAdapter.ts +1 -1
- package/src/plugins/FullScreenPlugin.test.ts +9 -0
- package/src/plugins/FullScreenPlugin.ts +0 -1
- package/src/plugins/KeyboardPlugin.ts +0 -1
- package/src/plugins/Plugin.ts +0 -2
- package/src/plugins/TimerPlugin.ts +1 -1
- package/src/plugins/WebEventsPlugin.test.ts +13 -0
- package/src/plugins/WebEventsPlugin.ts +12 -1
- package/src/plugins/audio/AudioInstance.ts +2 -2
- package/src/plugins/captions/CaptionsPlugin.test.ts +48 -3
- package/src/plugins/captions/CaptionsPlugin.ts +5 -5
- package/src/plugins/captions/CaptionsRenderer.ts +0 -1
- package/src/plugins/focus/FocusManagerPlugin.ts +0 -1
- package/src/ui/Input.ts +2 -2
- package/src/utils/types.ts +0 -1
- package/src/version.ts +1 -1
- package/lib/CaptionsPlugin-cSJj-R9M.js.map +0 -1
- package/lib/const-C3sxeYhl.js.map +0 -1
- 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
|
|
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);
|
package/src/plugins/Plugin.ts
CHANGED
|
@@ -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
|
}
|
|
@@ -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
|
-
|
|
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);
|
|
@@ -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)
|
|
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
|
|
595
|
+
} catch {
|
|
596
596
|
// nothing
|
|
597
597
|
}
|
|
598
598
|
this._updateCaretAndSelection();
|
package/src/utils/types.ts
CHANGED
|
@@ -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 +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":"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"}
|