@caperjs/core 0.2.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/assetpack.mjs +90 -0
- package/build/index.mjs +3 -1
- package/build/internal/discovery.mjs +1 -1
- package/build/internal/manifest.mjs +8 -3
- package/build/internal/validate.mjs +3 -1
- package/build/plugins/assetTypes.mjs +4 -4
- package/build/plugins/caperConfig.mjs +12 -5
- package/build/plugins/lists.mjs +29 -9
- package/build/plugins/pruneFallbacks.mjs +25 -2
- package/cli/create.mjs +57 -6
- package/cli/create.test.mjs +36 -0
- package/cli/update.mjs +1 -1
- package/lib/{CaptionsPlugin-WPAcfAkx.js → CaptionsPlugin-cSJj-R9M.js} +5 -5
- package/lib/{CaptionsPlugin-WPAcfAkx.js.map → CaptionsPlugin-cSJj-R9M.js.map} +1 -1
- package/lib/{DataAdapter-DS-twowe.js → DataAdapter-D2tc6hx9.js} +15 -10
- package/lib/DataAdapter-D2tc6hx9.js.map +1 -0
- package/lib/{DebugRenderer-D1ryrRYx.js → DebugRenderer-CGUKy6Fy.js} +2 -2
- package/lib/{DebugRenderer-D1ryrRYx.js.map → DebugRenderer-CGUKy6Fy.js.map} +1 -1
- package/lib/{DevToolsPlugin-DH7zX7HU.js → DevToolsPlugin-B8GX3H9b.js} +2 -2
- package/lib/{DevToolsPlugin-DH7zX7HU.js.map → DevToolsPlugin-B8GX3H9b.js.map} +1 -1
- package/lib/{GSAPPlugin-BBtRjUqe.js → GSAPPlugin-Br9AjuTD.js} +6 -6
- package/lib/{GSAPPlugin-BBtRjUqe.js.map → GSAPPlugin-Br9AjuTD.js.map} +1 -1
- package/lib/{LayoutPlugin-Co6vcoV4.js → LayoutPlugin-BX3OP7pw.js} +4 -4
- package/lib/{LayoutPlugin-Co6vcoV4.js.map → LayoutPlugin-BX3OP7pw.js.map} +1 -1
- package/lib/{SpinePlugin-uN641Hmk.js → SpinePlugin-C-JDdXpf.js} +690 -793
- package/lib/SpinePlugin-C-JDdXpf.js.map +1 -0
- package/lib/{StatsPlugin-vZQRm6wh.js → StatsPlugin-yJlJtoQ2.js} +7 -7
- package/lib/StatsPlugin-yJlJtoQ2.js.map +1 -0
- package/lib/{VoiceOverPlugin-KlZdGj1G.js → VoiceOverPlugin-DjSZp4Ko.js} +7 -6
- package/lib/VoiceOverPlugin-DjSZp4Ko.js.map +1 -0
- package/lib/caper.mjs +159 -1917
- package/lib/caper.mjs.map +1 -1
- package/lib/const-C3sxeYhl.js +1746 -0
- package/lib/const-C3sxeYhl.js.map +1 -0
- package/lib/core/Application.d.ts +8 -3
- package/lib/core/Application.d.ts.map +1 -1
- package/lib/core/Application.test.d.ts +2 -0
- package/lib/core/Application.test.d.ts.map +1 -0
- package/lib/core/config.d.ts +6 -1
- package/lib/core/config.d.ts.map +1 -1
- package/lib/core/config.test.d.ts +2 -0
- package/lib/core/config.test.d.ts.map +1 -0
- package/lib/core/create.d.ts.map +1 -1
- package/lib/core/interfaces/IApplication.d.ts +6 -6
- package/lib/core/interfaces/IApplication.d.ts.map +1 -1
- package/lib/core/interfaces/ICoreSignals.d.ts +2 -1
- package/lib/core/interfaces/ICoreSignals.d.ts.map +1 -1
- package/lib/display/Camera.test.d.ts +2 -0
- package/lib/display/Camera.test.d.ts.map +1 -0
- package/lib/display/Entity.d.ts +2 -2
- package/lib/display/SceneTransition.test.d.ts +1 -0
- package/lib/display/SceneTransition.test.d.ts.map +1 -0
- package/lib/display/SpineAnimation.d.ts +3 -3
- package/lib/mixins/factory/methods.d.ts.map +1 -1
- package/lib/padding-1kHDpGlQ.js +41 -0
- package/lib/padding-1kHDpGlQ.js.map +1 -0
- package/lib/plugins/AssetsPlugin.d.ts.map +1 -1
- package/lib/plugins/AssetsPlugin.test.d.ts +2 -0
- package/lib/plugins/AssetsPlugin.test.d.ts.map +1 -0
- package/lib/plugins/DataAdapter.d.ts.map +1 -1
- package/lib/plugins/DataAdapter.test.d.ts +2 -0
- package/lib/plugins/DataAdapter.test.d.ts.map +1 -0
- package/lib/plugins/FullScreenPlugin.d.ts +0 -5
- package/lib/plugins/FullScreenPlugin.d.ts.map +1 -1
- package/lib/plugins/FullScreenPlugin.test.d.ts +2 -0
- package/lib/plugins/FullScreenPlugin.test.d.ts.map +1 -0
- package/lib/plugins/GSAPPlugin.d.ts +0 -2
- package/lib/plugins/GSAPPlugin.d.ts.map +1 -1
- package/lib/plugins/GSAPPlugin.test.d.ts +2 -0
- package/lib/plugins/GSAPPlugin.test.d.ts.map +1 -0
- package/lib/plugins/KeyboardPlugin.d.ts +0 -1
- package/lib/plugins/KeyboardPlugin.d.ts.map +1 -1
- package/lib/plugins/LookupPlugin.d.ts +6 -1
- package/lib/plugins/LookupPlugin.d.ts.map +1 -1
- package/lib/plugins/LookupPlugin.test.d.ts +2 -0
- package/lib/plugins/LookupPlugin.test.d.ts.map +1 -0
- package/lib/plugins/Plugin.d.ts +101 -2
- package/lib/plugins/Plugin.d.ts.map +1 -1
- package/lib/plugins/PopupManagerPlugin.d.ts.map +1 -1
- package/lib/plugins/PopupManagerPlugin.test.d.ts +1 -0
- package/lib/plugins/PopupManagerPlugin.test.d.ts.map +1 -0
- package/lib/plugins/SceneManagerPlugin.d.ts +1 -1
- package/lib/plugins/SceneManagerPlugin.d.ts.map +1 -1
- package/lib/plugins/SceneManagerPlugin.test.d.ts +2 -0
- package/lib/plugins/SceneManagerPlugin.test.d.ts.map +1 -0
- package/lib/plugins/StatsPlugin.d.ts.map +1 -1
- package/lib/plugins/TimerPlugin.d.ts.map +1 -1
- package/lib/plugins/WebEventsPlugin.d.ts +0 -1
- package/lib/plugins/WebEventsPlugin.d.ts.map +1 -1
- package/lib/plugins/actions/ActionsPlugin.test.d.ts +2 -0
- package/lib/plugins/actions/ActionsPlugin.test.d.ts.map +1 -0
- package/lib/plugins/audio/AudioChannel.d.ts.map +1 -1
- package/lib/plugins/audio/AudioManagerPlugin.d.ts.map +1 -1
- package/lib/plugins/audio/AudioManagerPlugin.test.d.ts +2 -0
- package/lib/plugins/audio/AudioManagerPlugin.test.d.ts.map +1 -0
- package/lib/plugins/audio/VoiceOverPlugin.d.ts.map +1 -1
- package/lib/plugins/breakpoints/BreakpointPlugin.d.ts.map +1 -1
- package/lib/plugins/captions/CaptionsPlugin.test.d.ts +2 -0
- package/lib/plugins/captions/CaptionsPlugin.test.d.ts.map +1 -0
- package/lib/plugins/focus/FocusManagerPlugin.d.ts.map +1 -1
- package/lib/plugins/focus/FocusManagerPlugin.test.d.ts +2 -0
- package/lib/plugins/focus/FocusManagerPlugin.test.d.ts.map +1 -0
- package/lib/plugins/gesture/GesturePlugin.d.ts.map +1 -1
- package/lib/plugins/input/AbstractControls.d.ts +47 -3
- package/lib/plugins/input/AbstractControls.d.ts.map +1 -1
- package/lib/plugins/input/Controls.test.d.ts +2 -0
- package/lib/plugins/input/Controls.test.d.ts.map +1 -0
- package/lib/plugins/input/InputPlugin.d.ts.map +1 -1
- package/lib/plugins/input/controlsCore.d.ts +58 -0
- package/lib/plugins/input/controlsCore.d.ts.map +1 -0
- package/lib/plugins/input/controlsCore.test.d.ts +2 -0
- package/lib/plugins/input/controlsCore.test.d.ts.map +1 -0
- package/lib/plugins/input/keyboard/KeyboardControls.d.ts +15 -0
- package/lib/plugins/input/keyboard/KeyboardControls.d.ts.map +1 -0
- package/lib/plugins/input/keyboard/index.d.ts +1 -1
- package/lib/plugins/input/keyboard/index.d.ts.map +1 -1
- package/lib/plugins/input/touch/VirtualControls.d.ts +8 -14
- package/lib/plugins/input/touch/VirtualControls.d.ts.map +1 -1
- package/lib/plugins/input/touch/VirtualControls.test.d.ts +2 -0
- package/lib/plugins/input/touch/VirtualControls.test.d.ts.map +1 -0
- package/lib/{registries-BLrxALZ0.js → registries-BfV-VF7a.js} +665 -597
- package/lib/registries-BfV-VF7a.js.map +1 -0
- package/lib/store/Store.d.ts.map +1 -1
- package/lib/ui/Button.d.ts.map +1 -1
- package/lib/ui/UICanvas.d.ts +10 -1
- package/lib/ui/UICanvas.d.ts.map +1 -1
- package/lib/ui/UICanvas.test.d.ts +0 -1
- package/lib/ui/UICanvas.test.d.ts.map +1 -1
- package/lib/utils/color.d.ts.map +1 -1
- package/lib/utils/color.test.d.ts +2 -0
- package/lib/utils/color.test.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/core/Application.test.ts +168 -0
- package/src/core/Application.ts +37 -27
- package/src/core/config.test.ts +79 -0
- package/src/core/config.ts +28 -6
- package/src/core/create.ts +11 -0
- package/src/core/interfaces/IApplication.ts +6 -6
- package/src/core/interfaces/ICoreSignals.ts +2 -0
- package/src/display/Camera.test.ts +40 -0
- package/src/display/Camera.ts +3 -3
- package/src/display/Entity.ts +2 -2
- package/src/display/SceneTransition.test.ts +58 -0
- package/src/display/SceneTransition.ts +1 -1
- package/src/display/SpineAnimation.ts +4 -4
- package/src/mixins/factory/methods.ts +0 -1
- package/src/plugins/AssetsPlugin.test.ts +48 -0
- package/src/plugins/AssetsPlugin.ts +3 -1
- package/src/plugins/DataAdapter.test.ts +32 -0
- package/src/plugins/DataAdapter.ts +5 -1
- package/src/plugins/FullScreenPlugin.test.ts +48 -0
- package/src/plugins/FullScreenPlugin.ts +9 -18
- package/src/plugins/GSAPPlugin.test.ts +31 -0
- package/src/plugins/GSAPPlugin.ts +1 -4
- package/src/plugins/KeyboardPlugin.ts +3 -10
- package/src/plugins/LookupPlugin.test.ts +42 -0
- package/src/plugins/LookupPlugin.ts +15 -3
- package/src/plugins/Plugin.test.ts +130 -2
- package/src/plugins/Plugin.ts +159 -3
- package/src/plugins/PopupManagerPlugin.test.ts +53 -0
- package/src/plugins/PopupManagerPlugin.ts +5 -3
- package/src/plugins/SceneManagerPlugin.test.ts +76 -0
- package/src/plugins/SceneManagerPlugin.ts +18 -13
- package/src/plugins/StatsPlugin.ts +3 -2
- package/src/plugins/TimerPlugin.ts +1 -2
- package/src/plugins/WebEventsPlugin.test.ts +32 -0
- package/src/plugins/WebEventsPlugin.ts +9 -16
- package/src/plugins/actions/ActionsPlugin.test.ts +68 -0
- package/src/plugins/actions/ActionsPlugin.ts +5 -5
- package/src/plugins/audio/AudioChannel.test.ts +19 -0
- package/src/plugins/audio/AudioChannel.ts +8 -1
- package/src/plugins/audio/AudioManagerPlugin.test.ts +41 -0
- package/src/plugins/audio/AudioManagerPlugin.ts +3 -2
- package/src/plugins/audio/VoiceOverPlugin.ts +1 -0
- package/src/plugins/breakpoints/BreakpointPlugin.ts +1 -2
- package/src/plugins/captions/CaptionsPlugin.test.ts +40 -0
- package/src/plugins/captions/CaptionsPlugin.ts +2 -2
- package/src/plugins/focus/FocusManagerPlugin.test.ts +71 -0
- package/src/plugins/focus/FocusManagerPlugin.ts +7 -0
- package/src/plugins/gesture/GesturePlugin.ts +4 -8
- package/src/plugins/input/AbstractControls.ts +104 -3
- package/src/plugins/input/Controls.test.ts +205 -0
- package/src/plugins/input/InputPlugin.ts +6 -14
- package/src/plugins/input/controlsCore.test.ts +151 -0
- package/src/plugins/input/controlsCore.ts +162 -0
- package/src/plugins/input/keyboard/KeyboardControls.ts +84 -0
- package/src/plugins/input/keyboard/index.ts +1 -1
- package/src/plugins/input/touch/VirtualControls.test.ts +38 -0
- package/src/plugins/input/touch/VirtualControls.ts +53 -120
- package/src/plugins/spine/SpinePlugin.ts +2 -2
- package/src/store/Store.test.ts +15 -0
- package/src/store/Store.ts +4 -1
- package/src/ui/Button.test.ts +12 -0
- package/src/ui/Button.ts +1 -0
- package/src/ui/UICanvas.test.ts +109 -6
- package/src/ui/UICanvas.ts +41 -3
- package/src/utils/color.test.ts +16 -0
- package/src/utils/color.ts +2 -1
- package/src/utils/platform.ts +1 -1
- package/src/version.ts +1 -1
- package/lib/DataAdapter-DS-twowe.js.map +0 -1
- package/lib/SpinePlugin-uN641Hmk.js.map +0 -1
- package/lib/StatsPlugin-vZQRm6wh.js.map +0 -1
- package/lib/VoiceOverPlugin-KlZdGj1G.js.map +0 -1
- package/lib/plugins/input/keyboard/KeyboardContols.d.ts +0 -28
- package/lib/plugins/input/keyboard/KeyboardContols.d.ts.map +0 -1
- package/lib/registries-BLrxALZ0.js.map +0 -1
- package/src/plugins/input/keyboard/KeyboardContols.ts +0 -182
|
@@ -44,24 +44,17 @@ export class WebEventsPlugin extends Plugin implements IWebEventsPlugin {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
public initialize(): void {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
this.listen(document, 'visibilitychange', this._onVisibilityChanged, false);
|
|
48
|
+
this.listen(window, 'pagehide', this._onPageHide, false);
|
|
49
|
+
this.listen(window, 'pageshow', this._onPageShow, false);
|
|
50
|
+
this.listen(window, 'resize', this._onResize);
|
|
51
|
+
this.listen(document, 'fullscreenchange', this._onResize);
|
|
52
|
+
this.listen(window, 'orientationchange', this._onOrientationChanged);
|
|
53
53
|
// installed PWAs (standalone/fullscreen display modes) can miss or mistime window.resize
|
|
54
54
|
// during launch transitions; visualViewport reports the settled size
|
|
55
|
-
window.visualViewport
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
public destroy() {
|
|
59
|
-
document.removeEventListener('visibilitychange', this._onVisibilityChanged, false);
|
|
60
|
-
window.removeEventListener('resize', this._onResize);
|
|
61
|
-
document.removeEventListener('fullscreenchange', this._onResize);
|
|
62
|
-
window.removeEventListener('pagehide', this._onPageHide, false);
|
|
63
|
-
window.removeEventListener('pageshow', this._onPageShow, false);
|
|
64
|
-
window.visualViewport?.removeEventListener('resize', this._onVisualViewportResize);
|
|
55
|
+
if (window.visualViewport) {
|
|
56
|
+
this.listen(window.visualViewport, 'resize', this._onVisualViewportResize);
|
|
57
|
+
}
|
|
65
58
|
}
|
|
66
59
|
|
|
67
60
|
protected getCoreSignals(): string[] {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
// ActionsPlugin -> Plugin.ts transitively imports Application → Pixi display graph. Stub it.
|
|
4
|
+
vi.mock('../../core', () => ({
|
|
5
|
+
coreFunctionRegistry: {},
|
|
6
|
+
coreSignalRegistry: {},
|
|
7
|
+
}));
|
|
8
|
+
vi.mock('../../core/Application', () => ({
|
|
9
|
+
Application: { getInstance: () => ({}) },
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
import type { IApplication } from '../../core';
|
|
13
|
+
import { ActionsPlugin } from './ActionsPlugin';
|
|
14
|
+
|
|
15
|
+
function makePlugin(actions: Record<string, { context: string | string[] }>) {
|
|
16
|
+
const plugin = new ActionsPlugin();
|
|
17
|
+
plugin.initialize({}, { config: { actions } } as unknown as IApplication);
|
|
18
|
+
return plugin;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('ActionsPlugin', () => {
|
|
22
|
+
it('does not dispatch an action whose context merely contains the current context', () => {
|
|
23
|
+
const plugin = makePlugin({ foo: { context: 'popup' } });
|
|
24
|
+
const handler = vi.fn();
|
|
25
|
+
plugin.getAction('foo').connect(handler);
|
|
26
|
+
|
|
27
|
+
plugin.setActionContext('pop');
|
|
28
|
+
plugin.sendAction('foo');
|
|
29
|
+
|
|
30
|
+
expect(handler).not.toHaveBeenCalled();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('dispatches an action when the string context matches exactly', () => {
|
|
34
|
+
const plugin = makePlugin({ foo: { context: 'popup' } });
|
|
35
|
+
const handler = vi.fn();
|
|
36
|
+
plugin.getAction('foo').connect(handler);
|
|
37
|
+
|
|
38
|
+
plugin.setActionContext('popup');
|
|
39
|
+
plugin.sendAction('foo');
|
|
40
|
+
|
|
41
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('dispatches an action with a wildcard context in any context', () => {
|
|
45
|
+
const plugin = makePlugin({ foo: { context: '*' } });
|
|
46
|
+
const handler = vi.fn();
|
|
47
|
+
plugin.getAction('foo').connect(handler);
|
|
48
|
+
|
|
49
|
+
plugin.setActionContext('anything');
|
|
50
|
+
plugin.sendAction('foo');
|
|
51
|
+
|
|
52
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('dispatches an action when an array context includes the current context', () => {
|
|
56
|
+
const plugin = makePlugin({ foo: { context: ['menu', 'popup'] } });
|
|
57
|
+
const handler = vi.fn();
|
|
58
|
+
plugin.getAction('foo').connect(handler);
|
|
59
|
+
|
|
60
|
+
plugin.setActionContext('menu');
|
|
61
|
+
plugin.sendAction('foo');
|
|
62
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
63
|
+
|
|
64
|
+
plugin.setActionContext('men');
|
|
65
|
+
plugin.sendAction('foo');
|
|
66
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -80,11 +80,11 @@ export class ActionsPlugin extends Plugin<IActionsPluginOptions> implements IAct
|
|
|
80
80
|
|
|
81
81
|
// check if action is allowed for current context
|
|
82
82
|
// send action if allowed
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
this.
|
|
87
|
-
) {
|
|
83
|
+
const actionContext = this._actions[actionId]!.context;
|
|
84
|
+
const allowed = Array.isArray(actionContext)
|
|
85
|
+
? actionContext.includes(this.context)
|
|
86
|
+
: actionContext === this.context || actionContext === '*';
|
|
87
|
+
if (allowed) {
|
|
88
88
|
const detail: ActionDetail<TActionData> = { id: actionId, context: this.context, data };
|
|
89
89
|
this.getAction<TActionData>(actionId).emit(detail);
|
|
90
90
|
// notify automation / observers of the dispatched (allowed) action
|
|
@@ -80,6 +80,25 @@ describe('AudioChannel', () => {
|
|
|
80
80
|
expect(media.volume).toBeCloseTo(0.8 * 0.5 * 1);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
+
it('destroy() stops/destroys every tracked instance and clears them', () => {
|
|
84
|
+
const manager = createManager();
|
|
85
|
+
const channel = new AudioChannel('sfx', manager);
|
|
86
|
+
|
|
87
|
+
const instance1 = channel.add('boom', new AudioInstance('boom', channel, manager));
|
|
88
|
+
const media1 = createMedia();
|
|
89
|
+
instance1.media = media1 as never;
|
|
90
|
+
|
|
91
|
+
const instance2 = channel.add('splat', new AudioInstance('splat', channel, manager));
|
|
92
|
+
const media2 = createMedia();
|
|
93
|
+
instance2.media = media2 as never;
|
|
94
|
+
|
|
95
|
+
channel.destroy();
|
|
96
|
+
|
|
97
|
+
expect(media1.stop).toHaveBeenCalled();
|
|
98
|
+
expect(media2.stop).toHaveBeenCalled();
|
|
99
|
+
expect(channel.instances).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
|
|
83
102
|
it('remove(id) stops all instances tracked under that alias', () => {
|
|
84
103
|
const manager = createManager();
|
|
85
104
|
const channel = new AudioChannel('sfx', manager);
|
|
@@ -160,5 +160,12 @@ export class AudioChannel<C extends ChannelName = ChannelName> {
|
|
|
160
160
|
this.volume = this._volume;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
destroy() {
|
|
163
|
+
destroy() {
|
|
164
|
+
this._sounds.forEach((bucket) => {
|
|
165
|
+
// destroy() -> stop() -> emits onEnd, whose handler may call
|
|
166
|
+
// removeInstance() and mutate `bucket` mid-iteration. Iterate a copy.
|
|
167
|
+
[...bucket].forEach((instance) => instance.destroy());
|
|
168
|
+
});
|
|
169
|
+
this._sounds.clear();
|
|
170
|
+
}
|
|
164
171
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { sound } from '@pixi/sound';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
// AudioManagerPlugin -> Plugin.ts transitively imports Application → Pixi display graph. Stub it.
|
|
5
|
+
vi.mock('../../core', () => ({
|
|
6
|
+
coreFunctionRegistry: {},
|
|
7
|
+
coreSignalRegistry: {},
|
|
8
|
+
}));
|
|
9
|
+
vi.mock('../../core/Application', () => ({
|
|
10
|
+
Application: { getInstance: () => ({}) },
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
import { AudioManagerPlugin } from './AudioManagerPlugin';
|
|
14
|
+
|
|
15
|
+
describe('AudioManagerPlugin', () => {
|
|
16
|
+
it('destroy() disconnects onChannelMuted along with the other signals', () => {
|
|
17
|
+
const manager = new AudioManagerPlugin();
|
|
18
|
+
const handler = vi.fn();
|
|
19
|
+
manager.onChannelMuted.connect(handler);
|
|
20
|
+
|
|
21
|
+
manager.destroy();
|
|
22
|
+
|
|
23
|
+
manager.onChannelMuted.emit({ channel: {} as never, muted: true });
|
|
24
|
+
expect(handler).not.toHaveBeenCalled();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('caches a resolved sound id under the original id so probing is not repeated', () => {
|
|
28
|
+
const exists = vi.spyOn(sound, 'exists').mockImplementation((id: string) => id === 'foo.mp3');
|
|
29
|
+
const manager = new AudioManagerPlugin();
|
|
30
|
+
|
|
31
|
+
const resolved = (manager as any)._verifySoundId('foo');
|
|
32
|
+
expect(resolved).toBe('foo.mp3');
|
|
33
|
+
expect(((manager as any)._idMap as Map<string, string>).get('foo')).toBe('foo.mp3');
|
|
34
|
+
|
|
35
|
+
exists.mockClear();
|
|
36
|
+
expect((manager as any)._verifySoundId('foo')).toBe('foo.mp3');
|
|
37
|
+
expect(exists).not.toHaveBeenCalled();
|
|
38
|
+
|
|
39
|
+
exists.mockRestore();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -272,6 +272,7 @@ export class AudioManagerPlugin<C extends ChannelName = ChannelName> extends Plu
|
|
|
272
272
|
this.onMuted.disconnectAll();
|
|
273
273
|
this.onMasterVolumeChanged.disconnectAll();
|
|
274
274
|
this.onChannelVolumeChanged.disconnectAll();
|
|
275
|
+
this.onChannelMuted.disconnectAll();
|
|
275
276
|
|
|
276
277
|
super.destroy();
|
|
277
278
|
}
|
|
@@ -701,7 +702,7 @@ export class AudioManagerPlugin<C extends ChannelName = ChannelName> extends Plu
|
|
|
701
702
|
}
|
|
702
703
|
for (let id of soundId) {
|
|
703
704
|
if (this._idMap.has(id)) {
|
|
704
|
-
|
|
705
|
+
id = this._idMap.get(id) as string;
|
|
705
706
|
}
|
|
706
707
|
const channel = this._channels.get(channelName);
|
|
707
708
|
if (channel) {
|
|
@@ -795,7 +796,7 @@ export class AudioManagerPlugin<C extends ChannelName = ChannelName> extends Plu
|
|
|
795
796
|
}
|
|
796
797
|
}
|
|
797
798
|
}
|
|
798
|
-
this._idMap.set(
|
|
799
|
+
this._idMap.set(originalSoundId, soundId);
|
|
799
800
|
return soundId;
|
|
800
801
|
}
|
|
801
802
|
|
|
@@ -150,7 +150,6 @@ export class BreakpointPlugin extends Plugin<BreakpointPluginOptions> implements
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
public destroy(): void {
|
|
153
|
-
this._pointerQuery?.removeEventListener('change', this._onPointerChange);
|
|
154
153
|
this._pointerQuery = null;
|
|
155
154
|
for (const signal of this._enter.values()) signal.disconnectAll();
|
|
156
155
|
for (const signal of this._leave.values()) signal.disconnectAll();
|
|
@@ -259,7 +258,7 @@ export class BreakpointPlugin extends Plugin<BreakpointPluginOptions> implements
|
|
|
259
258
|
}
|
|
260
259
|
this._pointerQuery = window.matchMedia('(pointer: coarse)');
|
|
261
260
|
this._pointer = this._pointerQuery.matches ? 'coarse' : 'fine';
|
|
262
|
-
this._pointerQuery
|
|
261
|
+
this.listen(this._pointerQuery, 'change', this._onPointerChange as EventListener);
|
|
263
262
|
}
|
|
264
263
|
|
|
265
264
|
private _signal(store: Map<string, Signal<() => void>>, name: string): Signal<() => void> {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Assets } from 'pixi.js';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
// CaptionsPlugin -> Plugin.ts transitively imports Application → Pixi display graph. Stub it.
|
|
5
|
+
vi.mock('../../core', () => ({
|
|
6
|
+
coreFunctionRegistry: {},
|
|
7
|
+
coreSignalRegistry: {},
|
|
8
|
+
}));
|
|
9
|
+
vi.mock('../../core/Application', () => ({
|
|
10
|
+
Application: { getInstance: () => ({}) },
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
import { CaptionsPlugin } from './CaptionsPlugin';
|
|
14
|
+
|
|
15
|
+
describe('CaptionsPlugin', () => {
|
|
16
|
+
it('loadLocale returns early when the locale is already loaded', async () => {
|
|
17
|
+
const load = vi.spyOn(Assets, 'load').mockResolvedValue({});
|
|
18
|
+
const plugin = new CaptionsPlugin();
|
|
19
|
+
(plugin as any)._locale = 'en';
|
|
20
|
+
(plugin as any)._dicts = { en: { hello: 'hello' } };
|
|
21
|
+
(plugin as any)._options = { files: [{ id: 'en', json: 'en.json' }] };
|
|
22
|
+
|
|
23
|
+
await plugin.loadLocale('en');
|
|
24
|
+
|
|
25
|
+
expect(load).not.toHaveBeenCalled();
|
|
26
|
+
load.mockRestore();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('loadLocale still loads the current locale when its dictionary is missing', async () => {
|
|
30
|
+
const load = vi.spyOn(Assets, 'load').mockResolvedValue({ a: 1 });
|
|
31
|
+
const plugin = new CaptionsPlugin();
|
|
32
|
+
(plugin as any)._locale = 'en';
|
|
33
|
+
(plugin as any)._options = { files: [{ id: 'en', json: 'en.json' }] };
|
|
34
|
+
|
|
35
|
+
await plugin.loadLocale('en');
|
|
36
|
+
|
|
37
|
+
expect(load).toHaveBeenCalledWith('en.json');
|
|
38
|
+
load.mockRestore();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -298,7 +298,7 @@ export class CaptionsPlugin extends Plugin<CaptionsOptions> implements ICaptions
|
|
|
298
298
|
const RendererClass: CaptionRendererConstructor = this.options.renderer;
|
|
299
299
|
this.renderer = this.view.addChild(new RendererClass(this)) as CaptionsRenderer;
|
|
300
300
|
|
|
301
|
-
this.
|
|
301
|
+
this.addTickerCallback(this.update);
|
|
302
302
|
this.app.scenes.onSceneChangeStart.connect(this.stopAllCaptions);
|
|
303
303
|
}
|
|
304
304
|
|
|
@@ -327,7 +327,7 @@ export class CaptionsPlugin extends Plugin<CaptionsOptions> implements ICaptions
|
|
|
327
327
|
* @returns Promise<void>
|
|
328
328
|
*/
|
|
329
329
|
async loadLocale(localeId: string) {
|
|
330
|
-
if (this.
|
|
330
|
+
if (this._dicts[localeId]) {
|
|
331
331
|
return;
|
|
332
332
|
}
|
|
333
333
|
const file = this._options.files.find((file) => localeId === file.id);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const mockApp = vi.hoisted(() => ({
|
|
4
|
+
config: { focus: {} },
|
|
5
|
+
renderer: {
|
|
6
|
+
accessibility: {
|
|
7
|
+
isActive: false,
|
|
8
|
+
destroy: vi.fn(),
|
|
9
|
+
postrender: vi.fn(),
|
|
10
|
+
_deactivate: vi.fn(),
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
scenes: { onSceneChangeStart: { connect: vi.fn() } },
|
|
14
|
+
ticker: { addOnce: vi.fn(), add: vi.fn(), remove: vi.fn() },
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// FocusManagerPlugin transitively imports Application → Pixi display graph. Stub it.
|
|
18
|
+
vi.mock('../../core', () => ({
|
|
19
|
+
coreFunctionRegistry: {},
|
|
20
|
+
coreSignalRegistry: {},
|
|
21
|
+
}));
|
|
22
|
+
vi.mock('../../core/Application', () => ({
|
|
23
|
+
Application: { getInstance: () => mockApp },
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
import { FocusManagerPlugin } from './FocusManagerPlugin';
|
|
27
|
+
|
|
28
|
+
describe('FocusManagerPlugin destroy', () => {
|
|
29
|
+
let plugin: FocusManagerPlugin;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
plugin = new FocusManagerPlugin();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('stops handling window keydown after destroy', () => {
|
|
36
|
+
const spy = vi.spyOn(plugin as any, '_onKeyDown');
|
|
37
|
+
plugin.initialize({}, mockApp as never);
|
|
38
|
+
|
|
39
|
+
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }));
|
|
40
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
41
|
+
|
|
42
|
+
plugin.destroy();
|
|
43
|
+
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }));
|
|
44
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('stops handling window keyup after destroy', () => {
|
|
48
|
+
const spy = vi.spyOn(plugin as any, '_onKeyUp');
|
|
49
|
+
plugin.initialize({}, mockApp as never);
|
|
50
|
+
|
|
51
|
+
window.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }));
|
|
52
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
53
|
+
|
|
54
|
+
plugin.destroy();
|
|
55
|
+
window.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }));
|
|
56
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('stops handling the capture-phase document mousemove after destroy', () => {
|
|
60
|
+
const spy = vi.spyOn(plugin as any, '_onMouseMove');
|
|
61
|
+
plugin.initialize({}, mockApp as never);
|
|
62
|
+
(plugin as any)._activate();
|
|
63
|
+
|
|
64
|
+
document.dispatchEvent(new MouseEvent('mousemove'));
|
|
65
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
66
|
+
|
|
67
|
+
plugin.destroy();
|
|
68
|
+
document.dispatchEvent(new MouseEvent('mousemove'));
|
|
69
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -734,6 +734,13 @@ export class FocusManagerPlugin extends Plugin<FocusManagerPluginOptions> implem
|
|
|
734
734
|
private _removeGlobalListeners() {
|
|
735
735
|
globalThis.document.removeEventListener('mousemove', this._handleGlobalMouseMove);
|
|
736
736
|
globalThis.document.removeEventListener('pointerdown', this._handleGlobalPointerDown);
|
|
737
|
+
// keydown is added by _setupKeyboardListeners (window) and again, when pixi
|
|
738
|
+
// accessibility is off, by _updatePixiAccessibility (globalThis)
|
|
739
|
+
window.removeEventListener('keydown', this._onKeyDown, false);
|
|
740
|
+
globalThis.removeEventListener('keydown', this._onKeyDown, false);
|
|
741
|
+
globalThis.removeEventListener('keyup', this._onKeyUp, false);
|
|
742
|
+
// capture-phase listener added by _activate()
|
|
743
|
+
globalThis.document.removeEventListener('mousemove', this._onMouseMove, true);
|
|
737
744
|
}
|
|
738
745
|
|
|
739
746
|
private _handleGlobalMouseMove() {
|
|
@@ -74,23 +74,19 @@ export class GesturePlugin extends Plugin<GesturePluginOptions> implements IGest
|
|
|
74
74
|
canvas.style.touchAction = 'none';
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
this.listen(canvas, 'pointerdown', this._onPointerDown as EventListener);
|
|
78
|
+
this.listen(window, 'pointermove', this._onPointerMove as EventListener);
|
|
79
|
+
this.listen(window, 'pointerup', this._onPointerUp as EventListener);
|
|
80
|
+
this.listen(window, 'pointercancel', this._onPointerCancel as EventListener);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
public destroy(): void {
|
|
84
84
|
if (this._canvas) {
|
|
85
|
-
this._canvas.removeEventListener('pointerdown', this._onPointerDown);
|
|
86
85
|
if (this._options.preventDefault) {
|
|
87
86
|
this._canvas.style.touchAction = this._previousTouchAction;
|
|
88
87
|
}
|
|
89
88
|
this._canvas = null;
|
|
90
89
|
}
|
|
91
|
-
window.removeEventListener('pointermove', this._onPointerMove);
|
|
92
|
-
window.removeEventListener('pointerup', this._onPointerUp);
|
|
93
|
-
window.removeEventListener('pointercancel', this._onPointerCancel);
|
|
94
90
|
|
|
95
91
|
this._pointers.clear();
|
|
96
92
|
this._state = 'idle';
|
|
@@ -1,15 +1,116 @@
|
|
|
1
1
|
import type { ControlsActionMap } from '../..';
|
|
2
|
-
import { IApplication } from '../../core';
|
|
3
2
|
import { Application } from '../../core/Application';
|
|
3
|
+
import { WithSignals } from '../../mixins/signals';
|
|
4
|
+
import { Logger } from '../../utils';
|
|
5
|
+
import type { Action } from '../actions';
|
|
6
|
+
import { buildDownMaps, buildUpMap, isInputActive, type SchemeSection } from './controlsCore';
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Terminates the destroy chain — WithSignals' destroy() calls super.destroy().
|
|
10
|
+
* Also the binding root: `bindAllMethods` in an adapter binds everything down
|
|
11
|
+
* to here and stops, exactly like `Application` / `Plugin` / `Container`.
|
|
12
|
+
*/
|
|
13
|
+
class ControlsRoot {
|
|
14
|
+
private static readonly __caper_method_binding_root = true;
|
|
15
|
+
|
|
16
|
+
destroy(): void {}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Shared machinery for every controls adapter: scheme storage, context-gated
|
|
21
|
+
* down/up map building (rebuilt whenever the action context changes), the
|
|
22
|
+
* `isActionActive` query and the ticker hookup. Adapters supply the scheme
|
|
23
|
+
* sections, the "is this input down" predicates and the dispatch loop.
|
|
24
|
+
*/
|
|
25
|
+
export abstract class AbstractControls extends WithSignals(ControlsRoot) {
|
|
6
26
|
protected scheme: any;
|
|
27
|
+
/** `+` combinations, longest first — the update loop relies on that order. */
|
|
28
|
+
protected combinations: string[][] = [];
|
|
29
|
+
protected combinationsMap: Map<string[], Action> = new Map();
|
|
30
|
+
protected activeDownInputs: Map<string, Action> = new Map();
|
|
31
|
+
protected activeUpInputs: Map<string, Action> = new Map();
|
|
32
|
+
private _warnedMissingActions: Set<string> = new Set();
|
|
7
33
|
|
|
8
|
-
get app()
|
|
34
|
+
get app() {
|
|
9
35
|
return Application.getInstance();
|
|
10
36
|
}
|
|
11
37
|
|
|
12
38
|
initialize(scheme: Partial<ControlsActionMap>) {
|
|
13
39
|
this.scheme = scheme;
|
|
40
|
+
this.addSignalConnection(this.app.signal.onActionContextChanged.connect(this._sortActions));
|
|
41
|
+
this._sortActions();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public connect(): void {
|
|
45
|
+
this.app.ticker.add(this._update);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public destroy(): void {
|
|
49
|
+
this.app.ticker.remove(this._update);
|
|
50
|
+
super.destroy();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
isActionActive(action: Action): boolean {
|
|
54
|
+
const input = this.scheme?.down?.[action] ?? null;
|
|
55
|
+
if (!input) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
if (Array.isArray(input)) {
|
|
59
|
+
return input.some((item: string) => this._isInputActive(item));
|
|
60
|
+
}
|
|
61
|
+
return this._isInputActive(input);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The `down` section of the adapter's scheme. */
|
|
65
|
+
protected get downSection(): SchemeSection | undefined {
|
|
66
|
+
return this.scheme?.down;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The `up` section of the adapter's scheme. */
|
|
70
|
+
protected get upSection(): SchemeSection | undefined {
|
|
71
|
+
return this.scheme?.up;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Prefix for scheme warnings — a field, so minification cannot rename it away. */
|
|
75
|
+
protected abstract readonly logLabel: string;
|
|
76
|
+
|
|
77
|
+
/** Whether a single input is currently down. */
|
|
78
|
+
protected abstract isInputDown(id: string): boolean;
|
|
79
|
+
|
|
80
|
+
/** Whether an input counts as down *as part of a combination*. */
|
|
81
|
+
protected isComboPartDown(id: string): boolean {
|
|
82
|
+
return this.isInputDown(id);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Dispatch actions for the current input state — added to the ticker by {@link connect}. */
|
|
86
|
+
protected abstract _update(): void;
|
|
87
|
+
|
|
88
|
+
protected _isInputActive(input: string): boolean {
|
|
89
|
+
return isInputActive(input, this.isInputDown, this.isComboPartDown);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Rebuild the down/up maps for the current action context. */
|
|
93
|
+
protected _sortActions(): void {
|
|
94
|
+
const actions = this.app.actionsPlugin.getActions();
|
|
95
|
+
const context = this.app.actionContext as string;
|
|
96
|
+
|
|
97
|
+
const { combinations, combinationsMap, singles } = buildDownMaps<Action>(
|
|
98
|
+
this.downSection,
|
|
99
|
+
actions,
|
|
100
|
+
context,
|
|
101
|
+
this._warnMissingAction,
|
|
102
|
+
);
|
|
103
|
+
this.combinations = combinations;
|
|
104
|
+
this.combinationsMap = combinationsMap;
|
|
105
|
+
this.activeDownInputs = singles;
|
|
106
|
+
this.activeUpInputs = buildUpMap<Action>(this.upSection, actions, context, this._warnMissingAction);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
protected _warnMissingAction(key: string): void {
|
|
110
|
+
if (this._warnedMissingActions.has(key)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this._warnedMissingActions.add(key);
|
|
114
|
+
Logger.warn(`${this.logLabel}: scheme references unknown action "${key}"`);
|
|
14
115
|
}
|
|
15
116
|
}
|