canvasengine 2.0.0-beta.42 → 2.0.0-beta.43
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/dist/{DebugRenderer-BosXI2Pd.js → DebugRenderer-K2IZBznP.js} +2 -2
- package/dist/{DebugRenderer-BosXI2Pd.js.map → DebugRenderer-K2IZBznP.js.map} +1 -1
- package/dist/components/Button.d.ts +3 -0
- package/dist/components/Button.d.ts.map +1 -1
- package/dist/components/DOMElement.d.ts.map +1 -1
- package/dist/components/Graphic.d.ts +1 -1
- package/dist/components/Graphic.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/types/DisplayObject.d.ts +12 -16
- package/dist/components/types/DisplayObject.d.ts.map +1 -1
- package/dist/directives/FocusNavigation.d.ts +71 -0
- package/dist/directives/FocusNavigation.d.ts.map +1 -0
- package/dist/directives/KeyboardControls.d.ts.map +1 -1
- package/dist/directives/ViewportFollow.d.ts.map +1 -1
- package/dist/engine/FocusManager.d.ts +174 -0
- package/dist/engine/FocusManager.d.ts.map +1 -0
- package/dist/engine/reactive.d.ts.map +1 -1
- package/dist/hooks/useFocus.d.ts +61 -0
- package/dist/hooks/useFocus.d.ts.map +1 -0
- package/dist/{index-DNwqVzaq.js → index-B4hYyfVE.js} +5469 -4676
- package/dist/index-B4hYyfVE.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.global.js +7 -7
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +66 -60
- package/package.json +2 -2
- package/src/components/Button.ts +7 -4
- package/src/components/Canvas.ts +1 -1
- package/src/components/DOMContainer.ts +27 -2
- package/src/components/DOMElement.ts +37 -29
- package/src/components/DisplayObject.ts +15 -3
- package/src/components/FocusContainer.ts +372 -0
- package/src/components/Graphic.ts +43 -48
- package/src/components/Sprite.ts +4 -2
- package/src/components/Viewport.ts +65 -26
- package/src/components/index.ts +2 -1
- package/src/components/types/DisplayObject.ts +7 -4
- package/src/directives/Controls.ts +1 -1
- package/src/directives/ControlsBase.ts +1 -1
- package/src/directives/FocusNavigation.ts +252 -0
- package/src/directives/KeyboardControls.ts +12 -8
- package/src/directives/ViewportFollow.ts +8 -5
- package/src/engine/FocusManager.ts +495 -0
- package/src/engine/reactive.ts +20 -19
- package/src/hooks/useFocus.ts +94 -0
- package/src/index.ts +2 -0
- package/dist/index-DNwqVzaq.js.map +0 -1
|
@@ -3,6 +3,7 @@ import { Subscription } from 'rxjs';
|
|
|
3
3
|
import { createComponent, registerComponent, Element, Props } from '../engine/reactive';
|
|
4
4
|
import { DisplayObject, ComponentInstance } from './DisplayObject';
|
|
5
5
|
import { effect, Signal } from '@signe/reactive';
|
|
6
|
+
import { Graphics, Container } from 'pixi.js';
|
|
6
7
|
|
|
7
8
|
const EVENTS = [
|
|
8
9
|
'bounce-x-end',
|
|
@@ -43,27 +44,43 @@ export interface ViewportProps extends Props {
|
|
|
43
44
|
[key: string]: any;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
export class CanvasViewport extends DisplayObject(
|
|
47
|
+
export class CanvasViewport extends DisplayObject(Container) {
|
|
47
48
|
private tickSubscription: Subscription
|
|
48
49
|
overrideProps = ['wheel']
|
|
50
|
+
#mask: Graphics
|
|
51
|
+
public viewport: PixiViewport
|
|
49
52
|
|
|
50
53
|
constructor() {
|
|
54
|
+
super()
|
|
51
55
|
const defaultOptions = {
|
|
52
56
|
noTicker: true,
|
|
53
57
|
events: {
|
|
54
58
|
domElement: {
|
|
55
|
-
addEventListener: () => {}
|
|
59
|
+
addEventListener: () => { }
|
|
56
60
|
}
|
|
57
61
|
},
|
|
58
62
|
}
|
|
59
63
|
// @ts-ignore
|
|
60
|
-
|
|
64
|
+
this.viewport = new PixiViewport(defaultOptions)
|
|
65
|
+
super.addChild(this.viewport)
|
|
66
|
+
|
|
67
|
+
this.#mask = new Graphics()
|
|
68
|
+
super.addChild(this.#mask)
|
|
69
|
+
this.mask = this.#mask
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
addChild<U extends any[]>(...children: U): U[0] {
|
|
73
|
+
return this.viewport.addChild(...children)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
addChildAt<U extends any>(child: U, index: number): U {
|
|
77
|
+
return this.viewport.addChildAt(child, index)
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
onInit(props) {
|
|
64
81
|
super.onInit(props)
|
|
65
82
|
for (let event of EVENTS) {
|
|
66
|
-
if (props[event]) this.on(event, props[event])
|
|
83
|
+
if (props[event]) this.viewport.on(event, props[event])
|
|
67
84
|
}
|
|
68
85
|
}
|
|
69
86
|
|
|
@@ -74,14 +91,19 @@ export class CanvasViewport extends DisplayObject(PixiViewport) {
|
|
|
74
91
|
* @param {number} [index] - The index of the component among its siblings.
|
|
75
92
|
*/
|
|
76
93
|
async onMount(element: Element<CanvasViewport>, index?: number): Promise<void> {
|
|
94
|
+
element.props.context.viewport = this.viewport
|
|
77
95
|
await super.onMount(element, index);
|
|
78
96
|
const { props } = element;
|
|
79
97
|
const { tick, app, canvasSize } = props.context;
|
|
80
|
-
|
|
81
|
-
|
|
98
|
+
|
|
82
99
|
effect(() => {
|
|
83
|
-
|
|
84
|
-
|
|
100
|
+
if (props.screenWidth === undefined) {
|
|
101
|
+
this.viewport.screenWidth = canvasSize().width
|
|
102
|
+
}
|
|
103
|
+
if (props.screenHeight === undefined) {
|
|
104
|
+
this.viewport.screenHeight = canvasSize().height
|
|
105
|
+
}
|
|
106
|
+
this.updateMask()
|
|
85
107
|
})
|
|
86
108
|
|
|
87
109
|
effect(() => {
|
|
@@ -89,20 +111,19 @@ export class CanvasViewport extends DisplayObject(PixiViewport) {
|
|
|
89
111
|
if (!_app) return
|
|
90
112
|
|
|
91
113
|
const renderer = _app.renderer
|
|
92
|
-
|
|
114
|
+
|
|
93
115
|
renderer.events.domElement.addEventListener(
|
|
94
116
|
'wheel',
|
|
95
|
-
this.input.wheelFunction
|
|
117
|
+
this.viewport.input.wheelFunction
|
|
96
118
|
);
|
|
97
119
|
|
|
98
|
-
this.options.events = renderer.events
|
|
120
|
+
this.viewport.options.events = renderer.events
|
|
99
121
|
})
|
|
100
|
-
|
|
122
|
+
|
|
101
123
|
this.tickSubscription = tick.observable.subscribe(({ value }) => {
|
|
102
|
-
this.update(value.
|
|
124
|
+
this.viewport.update(value.deltaTime)
|
|
103
125
|
})
|
|
104
126
|
|
|
105
|
-
element.props.context.viewport = this
|
|
106
127
|
this.updateViewportSettings(props)
|
|
107
128
|
}
|
|
108
129
|
|
|
@@ -113,46 +134,55 @@ export class CanvasViewport extends DisplayObject(PixiViewport) {
|
|
|
113
134
|
|
|
114
135
|
private updateViewportSettings(props) {
|
|
115
136
|
if (props.screenWidth !== undefined) {
|
|
116
|
-
this.screenWidth = props.screenWidth
|
|
137
|
+
this.viewport.screenWidth = props.screenWidth
|
|
117
138
|
}
|
|
118
139
|
if (props.screenHeight !== undefined) {
|
|
119
|
-
this.screenHeight = props.screenHeight
|
|
140
|
+
this.viewport.screenHeight = props.screenHeight
|
|
120
141
|
}
|
|
142
|
+
this.updateMask()
|
|
121
143
|
if (props.worldWidth !== undefined) {
|
|
122
|
-
this.worldWidth = props.worldWidth
|
|
144
|
+
this.viewport.worldWidth = props.worldWidth
|
|
123
145
|
}
|
|
124
146
|
if (props.worldHeight !== undefined) {
|
|
125
|
-
this.worldHeight = props.worldHeight
|
|
147
|
+
this.viewport.worldHeight = props.worldHeight
|
|
126
148
|
}
|
|
127
149
|
if (props.drag) {
|
|
128
|
-
this.drag(props.drag)
|
|
150
|
+
this.viewport.drag(props.drag)
|
|
129
151
|
}
|
|
130
152
|
if (props.clamp) {
|
|
131
|
-
this.clamp(props.clamp.value ?? props.clamp)
|
|
153
|
+
this.viewport.clamp(props.clamp.value ?? props.clamp)
|
|
132
154
|
}
|
|
133
155
|
if (props.wheel) {
|
|
134
156
|
if (props.wheel === true) {
|
|
135
|
-
this.wheel()
|
|
157
|
+
this.viewport.wheel()
|
|
136
158
|
} else {
|
|
137
|
-
this.wheel(props.wheel)
|
|
159
|
+
this.viewport.wheel(props.wheel)
|
|
138
160
|
}
|
|
139
161
|
}
|
|
140
162
|
if (props.decelerate) {
|
|
141
163
|
if (props.decelerate === true) {
|
|
142
|
-
this.decelerate()
|
|
164
|
+
this.viewport.decelerate()
|
|
143
165
|
} else {
|
|
144
|
-
this.decelerate(props.decelerate)
|
|
166
|
+
this.viewport.decelerate(props.decelerate)
|
|
145
167
|
}
|
|
146
168
|
}
|
|
147
169
|
if (props.pinch) {
|
|
148
170
|
if (props.pinch === true) {
|
|
149
|
-
this.pinch()
|
|
171
|
+
this.viewport.pinch()
|
|
150
172
|
} else {
|
|
151
|
-
this.pinch(props.pinch)
|
|
173
|
+
this.viewport.pinch(props.pinch)
|
|
152
174
|
}
|
|
153
175
|
}
|
|
154
176
|
}
|
|
155
177
|
|
|
178
|
+
private updateMask() {
|
|
179
|
+
if (!this.#mask) return
|
|
180
|
+
this.#mask.clear()
|
|
181
|
+
this.#mask.beginFill(0xffffff)
|
|
182
|
+
this.#mask.drawRect(0, 0, this.viewport.screenWidth, this.viewport.screenHeight)
|
|
183
|
+
this.#mask.endFill()
|
|
184
|
+
}
|
|
185
|
+
|
|
156
186
|
/**
|
|
157
187
|
* Called when the component is about to be destroyed.
|
|
158
188
|
* Unsubscribes from the tick observable.
|
|
@@ -166,6 +196,15 @@ export class CanvasViewport extends DisplayObject(PixiViewport) {
|
|
|
166
196
|
}
|
|
167
197
|
await super.onDestroy(parent, _afterDestroy);
|
|
168
198
|
}
|
|
199
|
+
|
|
200
|
+
// Proxy methods for viewport plugins
|
|
201
|
+
follow(...args: any[]) {
|
|
202
|
+
return (this.viewport.follow as any)(...args)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
get plugins() {
|
|
206
|
+
return this.viewport.plugins
|
|
207
|
+
}
|
|
169
208
|
}
|
|
170
209
|
|
|
171
210
|
registerComponent('Viewport', CanvasViewport)
|
package/src/components/index.ts
CHANGED
|
@@ -14,4 +14,5 @@ export { type ComponentInstance } from './DisplayObject'
|
|
|
14
14
|
export { DOMContainer } from './DOMContainer'
|
|
15
15
|
export { DOMElement } from './DOMElement'
|
|
16
16
|
export { Button, ButtonState, type ButtonProps, type ButtonStyle } from './Button'
|
|
17
|
-
export { Joystick, type JoystickSettings, type JoystickChangeEvent } from './Joystick'
|
|
17
|
+
export { Joystick, type JoystickSettings, type JoystickChangeEvent } from './Joystick'
|
|
18
|
+
export { FocusContainer, type FocusContainerProps } from './FocusContainer'
|
|
@@ -15,6 +15,8 @@ export type ObjectPosition = string;
|
|
|
15
15
|
export type TransformOrigin = string;
|
|
16
16
|
export type PositionType = 'relative' | 'absolute' | 'static';
|
|
17
17
|
|
|
18
|
+
export type ObservablePointSignal = [number, number] | SignalOrPrimitive<[number, number]> | { x: number, y: number } | SignalOrPrimitive<{ x: number, y: number }>;
|
|
19
|
+
|
|
18
20
|
export interface DisplayObjectProps {
|
|
19
21
|
attach?: any;
|
|
20
22
|
ref?: string;
|
|
@@ -48,9 +50,9 @@ export interface DisplayObjectProps {
|
|
|
48
50
|
padding?: EdgeSize;
|
|
49
51
|
border?: EdgeSize;
|
|
50
52
|
absolute?: SignalOrPrimitive<boolean>;
|
|
51
|
-
scale?:
|
|
52
|
-
anchor?:
|
|
53
|
-
skew?:
|
|
53
|
+
scale?: ObservablePointSignal | number;
|
|
54
|
+
anchor?: ObservablePointSignal;
|
|
55
|
+
skew?: ObservablePointSignal;
|
|
54
56
|
tint?: SignalOrPrimitive<number>;
|
|
55
57
|
rotation?: SignalOrPrimitive<number>;
|
|
56
58
|
angle?: SignalOrPrimitive<number>;
|
|
@@ -58,7 +60,7 @@ export interface DisplayObjectProps {
|
|
|
58
60
|
roundPixels?: SignalOrPrimitive<boolean>;
|
|
59
61
|
cursor?: SignalOrPrimitive<string>;
|
|
60
62
|
visible?: SignalOrPrimitive<boolean>;
|
|
61
|
-
pivot?:
|
|
63
|
+
pivot?: ObservablePointSignal;
|
|
62
64
|
filters?: any[];
|
|
63
65
|
blendMode?: SignalOrPrimitive<PIXI.BLEND_MODES>;
|
|
64
66
|
blur?: SignalOrPrimitive<number>;
|
|
@@ -100,4 +102,5 @@ export interface DisplayObjectProps {
|
|
|
100
102
|
touchmove?: PIXI.FederatedEventHandler;
|
|
101
103
|
touchstart?: PIXI.FederatedEventHandler;
|
|
102
104
|
wheel?: PIXI.FederatedEventHandler<PIXI.FederatedWheelEvent>;
|
|
105
|
+
tabindex?: SignalOrPrimitive<number>;
|
|
103
106
|
}
|
|
@@ -118,7 +118,7 @@ export abstract class ControlsBase {
|
|
|
118
118
|
protected applyInput(keyName: string) {
|
|
119
119
|
const boundKey = this.boundKeys[keyName];
|
|
120
120
|
if (!boundKey) return;
|
|
121
|
-
|
|
121
|
+
|
|
122
122
|
const { repeat, keyDown } = boundKey.options;
|
|
123
123
|
// Default implementation - subclasses may override for state tracking
|
|
124
124
|
if (keyDown) {
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { Directive, registerDirective } from "../engine/directive";
|
|
2
|
+
import { Element } from "../engine/reactive";
|
|
3
|
+
import { focusManager } from "../engine/FocusManager";
|
|
4
|
+
import { ControlsDirective } from "./Controls";
|
|
5
|
+
import { Controls } from "./ControlsBase";
|
|
6
|
+
import { isSignal, Signal } from "@signe/reactive";
|
|
7
|
+
import { CanvasFocusContainer } from "../components/FocusContainer";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* FocusNavigation directive for automatic focus navigation via Controls
|
|
11
|
+
*
|
|
12
|
+
* This directive integrates with the Controls system to automatically navigate
|
|
13
|
+
* between focusable elements using keyboard arrows or gamepad input.
|
|
14
|
+
*
|
|
15
|
+
* The directive is automatically applied when a FocusContainer has a `controls` prop.
|
|
16
|
+
* It wraps the existing Controls configuration to add focus navigation behavior.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // Automatic navigation with Controls
|
|
21
|
+
* <FocusContainer tabindex={0} controls={controlsConfig}>
|
|
22
|
+
* <Button tabindex={0} text="Button 1" />
|
|
23
|
+
* <Button tabindex={1} text="Button 2" />
|
|
24
|
+
* </FocusContainer>
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export class FocusNavigationDirective extends Directive {
|
|
28
|
+
private element: Element<CanvasFocusContainer> | null = null;
|
|
29
|
+
private controlsDirective: ControlsDirective | null = null;
|
|
30
|
+
private containerId: string = '';
|
|
31
|
+
private controlsSubscription: any = null;
|
|
32
|
+
private originalControls: Controls | null = null;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Initialize the focus navigation directive
|
|
36
|
+
*
|
|
37
|
+
* @param element - FocusContainer element
|
|
38
|
+
*/
|
|
39
|
+
onInit(element: Element<CanvasFocusContainer>) {
|
|
40
|
+
this.element = element;
|
|
41
|
+
|
|
42
|
+
// Get container ID from component instance
|
|
43
|
+
const instance = element.componentInstance as CanvasFocusContainer;
|
|
44
|
+
if (instance && typeof instance.getContainerId === 'function') {
|
|
45
|
+
this.containerId = instance.getContainerId();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Get controls from props
|
|
49
|
+
const controlsProp = element.props.controls;
|
|
50
|
+
if (!controlsProp) return;
|
|
51
|
+
|
|
52
|
+
// Get controls value (handle signals)
|
|
53
|
+
const controlsValue = isSignal(controlsProp) ? controlsProp() : controlsProp;
|
|
54
|
+
if (!controlsValue) return;
|
|
55
|
+
|
|
56
|
+
this.originalControls = controlsValue;
|
|
57
|
+
|
|
58
|
+
// Note: Controls directive will be created/initialized in onMount
|
|
59
|
+
// We'll set up navigation controls there
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Set up navigation controls
|
|
64
|
+
*
|
|
65
|
+
* @param controls - Controls configuration
|
|
66
|
+
*/
|
|
67
|
+
private setupNavigationControls(controls: Controls) {
|
|
68
|
+
if (!this.controlsDirective) {
|
|
69
|
+
console.warn('FocusNavigation: Controls directive not found, cannot set up navigation');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
controls = (controls.value ?? controls) as Controls;
|
|
73
|
+
// Create navigation controls by wrapping existing ones
|
|
74
|
+
const navigationControls: Controls = {
|
|
75
|
+
...controls,
|
|
76
|
+
// Override or add navigation controls
|
|
77
|
+
up: {
|
|
78
|
+
...controls.up,
|
|
79
|
+
repeat: controls.up?.repeat ?? true,
|
|
80
|
+
bind: controls.up?.bind ?? ['up', 'top_left', 'top_right'],
|
|
81
|
+
keyDown: (boundKey?: any) => {
|
|
82
|
+
// Navigate up/previous first
|
|
83
|
+
this.navigate('previous');
|
|
84
|
+
// Call original handler if exists
|
|
85
|
+
controls.up?.keyDown?.(boundKey);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
down: {
|
|
89
|
+
...controls.down,
|
|
90
|
+
repeat: controls.down?.repeat ?? true,
|
|
91
|
+
bind: controls.down?.bind ?? ['down', 'bottom_left', 'bottom_right'],
|
|
92
|
+
keyDown: (boundKey?: any) => {
|
|
93
|
+
// Navigate down/next first
|
|
94
|
+
this.navigate('next');
|
|
95
|
+
// Call original handler if exists
|
|
96
|
+
controls.down?.keyDown?.(boundKey);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
left: {
|
|
100
|
+
...controls.left,
|
|
101
|
+
repeat: controls.left?.repeat ?? true,
|
|
102
|
+
bind: controls.left?.bind ?? 'left',
|
|
103
|
+
keyDown: (boundKey?: any) => {
|
|
104
|
+
// Navigate previous (for horizontal lists)
|
|
105
|
+
this.navigate('previous');
|
|
106
|
+
// Call original handler if exists
|
|
107
|
+
controls.left?.keyDown?.(boundKey);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
right: {
|
|
111
|
+
...controls.right,
|
|
112
|
+
repeat: controls.right?.repeat ?? true,
|
|
113
|
+
bind: controls.right?.bind ?? 'right',
|
|
114
|
+
keyDown: (boundKey?: any) => {
|
|
115
|
+
// Navigate next (for horizontal lists)
|
|
116
|
+
this.navigate('next');
|
|
117
|
+
// Call original handler if exists
|
|
118
|
+
controls.right?.keyDown?.(boundKey);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
action: {
|
|
122
|
+
...controls.action,
|
|
123
|
+
bind: controls.action?.bind ?? ['space', 'enter'],
|
|
124
|
+
keyDown: (boundKey?: any) => {
|
|
125
|
+
// Trigger action on focused element (e.g., click)
|
|
126
|
+
this.triggerAction();
|
|
127
|
+
// Call original handler if exists
|
|
128
|
+
controls.action?.keyDown?.(boundKey);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Update controls directive with navigation controls
|
|
134
|
+
this.controlsDirective.onUpdate({ controls: navigationControls }, this.element!);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Navigate to next or previous focusable element
|
|
139
|
+
*
|
|
140
|
+
* @param direction - Navigation direction
|
|
141
|
+
*/
|
|
142
|
+
private navigate(direction: 'next' | 'previous') {
|
|
143
|
+
if (!this.containerId) return;
|
|
144
|
+
focusManager.navigate(this.containerId, direction);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Trigger action on currently focused element
|
|
149
|
+
*/
|
|
150
|
+
private triggerAction() {
|
|
151
|
+
if (!this.containerId) return;
|
|
152
|
+
|
|
153
|
+
const focusedElementSignal = focusManager.getFocusedElementSignal(this.containerId);
|
|
154
|
+
if (!focusedElementSignal) return;
|
|
155
|
+
|
|
156
|
+
const focusedElement = focusedElementSignal();
|
|
157
|
+
if (!focusedElement) return;
|
|
158
|
+
|
|
159
|
+
// Try to trigger click/pointertap event on focused element
|
|
160
|
+
const instance = focusedElement.componentInstance;
|
|
161
|
+
if (instance && typeof instance.emit === 'function') {
|
|
162
|
+
// Emit pointertap event (equivalent to click)
|
|
163
|
+
instance.emit('pointertap', { target: instance });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Mount hook
|
|
169
|
+
*
|
|
170
|
+
* @param element - FocusContainer element
|
|
171
|
+
*/
|
|
172
|
+
onMount(element: Element<CanvasFocusContainer>) {
|
|
173
|
+
// Get container ID again (should be available now)
|
|
174
|
+
const instance = element.componentInstance as CanvasFocusContainer;
|
|
175
|
+
if (instance && typeof instance.getContainerId === 'function') {
|
|
176
|
+
this.containerId = instance.getContainerId();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Get or create Controls directive
|
|
180
|
+
this.controlsDirective = element.directives?.controls as ControlsDirective;
|
|
181
|
+
|
|
182
|
+
// If Controls directive doesn't exist, create it
|
|
183
|
+
if (!this.controlsDirective) {
|
|
184
|
+
const { applyDirective } = require("../engine/reactive");
|
|
185
|
+
const controlsDirective = applyDirective(element, 'controls');
|
|
186
|
+
if (controlsDirective) {
|
|
187
|
+
if (!element.directives) {
|
|
188
|
+
element.directives = {};
|
|
189
|
+
}
|
|
190
|
+
element.directives.controls = controlsDirective;
|
|
191
|
+
this.controlsDirective = controlsDirective as ControlsDirective;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Get controls from props
|
|
196
|
+
const controlsProp = element.props.controls;
|
|
197
|
+
if (controlsProp) {
|
|
198
|
+
const controlsValue = isSignal(controlsProp) ? controlsProp() : controlsProp;
|
|
199
|
+
if (controlsValue) {
|
|
200
|
+
this.originalControls = controlsValue;
|
|
201
|
+
this.setupNavigationControls(controlsValue);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Handle controls prop updates if it's a signal
|
|
206
|
+
if (isSignal(controlsProp)) {
|
|
207
|
+
this.controlsSubscription = (controlsProp as Signal<Controls>).observable.subscribe((controls) => {
|
|
208
|
+
if (controls) {
|
|
209
|
+
this.originalControls = controls;
|
|
210
|
+
this.setupNavigationControls(controls);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Update hook
|
|
218
|
+
*
|
|
219
|
+
* @param props - Updated properties
|
|
220
|
+
* @param element - FocusContainer element
|
|
221
|
+
*/
|
|
222
|
+
onUpdate(props: any, element: Element<CanvasFocusContainer>) {
|
|
223
|
+
// Update controls if changed
|
|
224
|
+
if (props.controls !== undefined) {
|
|
225
|
+
const controlsValue = isSignal(props.controls) ? props.controls() : props.controls;
|
|
226
|
+
if (controlsValue) {
|
|
227
|
+
this.originalControls = controlsValue;
|
|
228
|
+
this.setupNavigationControls(controlsValue);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Destroy hook
|
|
235
|
+
*
|
|
236
|
+
* @param element - FocusContainer element
|
|
237
|
+
*/
|
|
238
|
+
onDestroy(element: Element<CanvasFocusContainer>) {
|
|
239
|
+
// Cleanup subscription
|
|
240
|
+
if (this.controlsSubscription) {
|
|
241
|
+
this.controlsSubscription.unsubscribe();
|
|
242
|
+
this.controlsSubscription = null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
this.element = null;
|
|
246
|
+
this.controlsDirective = null;
|
|
247
|
+
this.originalControls = null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
registerDirective('focusNavigation', FocusNavigationDirective);
|
|
252
|
+
|
|
@@ -357,11 +357,11 @@ export class KeyboardControls extends ControlsBase {
|
|
|
357
357
|
left: boolean,
|
|
358
358
|
right: boolean
|
|
359
359
|
} = {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
360
|
+
up: false,
|
|
361
|
+
down: false,
|
|
362
|
+
left: false,
|
|
363
|
+
right: false
|
|
364
|
+
};
|
|
365
365
|
|
|
366
366
|
/**
|
|
367
367
|
* Setup keyboard event listeners
|
|
@@ -391,7 +391,7 @@ export class KeyboardControls extends ControlsBase {
|
|
|
391
391
|
const directionControl = this.boundKeys[direction];
|
|
392
392
|
if (directionControl) {
|
|
393
393
|
const { keyDown } = directionControl.options;
|
|
394
|
-
if (keyDown
|
|
394
|
+
if (keyDown) {
|
|
395
395
|
this.applyInput(direction);
|
|
396
396
|
}
|
|
397
397
|
}
|
|
@@ -410,11 +410,15 @@ export class KeyboardControls extends ControlsBase {
|
|
|
410
410
|
*/
|
|
411
411
|
protected applyInput(keyName: string) {
|
|
412
412
|
const keyState = this.keyState[keyName];
|
|
413
|
-
if (!keyState)
|
|
413
|
+
if (!keyState) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
414
416
|
const { isDown, count } = keyState;
|
|
415
417
|
if (isDown) {
|
|
416
418
|
const boundKey = this.boundKeys[keyName];
|
|
417
|
-
if (!boundKey)
|
|
419
|
+
if (!boundKey) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
418
422
|
const { repeat, keyDown } = boundKey.options;
|
|
419
423
|
if ((repeat || count == 0)) {
|
|
420
424
|
let parameters = boundKey.parameters;
|
|
@@ -18,12 +18,15 @@ export class ViewportFollow extends Directive {
|
|
|
18
18
|
|
|
19
19
|
}
|
|
20
20
|
onMount(element: Element) {
|
|
21
|
-
|
|
21
|
+
this.onUpdate(element.props.viewportFollow, element)
|
|
22
22
|
}
|
|
23
23
|
onUpdate(viewportFollow: any, element: Element) {
|
|
24
|
-
const
|
|
24
|
+
const viewport = element.props.context?.viewport
|
|
25
25
|
if (!viewport) {
|
|
26
|
-
|
|
26
|
+
if (viewportFollow) {
|
|
27
|
+
throw error('ViewportFollow directive requires a Viewport component to be mounted in the same context')
|
|
28
|
+
}
|
|
29
|
+
return
|
|
27
30
|
}
|
|
28
31
|
if (viewportFollow) {
|
|
29
32
|
if (viewportFollow === true) {
|
|
@@ -46,8 +49,8 @@ export class ViewportFollow extends Directive {
|
|
|
46
49
|
}
|
|
47
50
|
onDestroy(element: Element) {
|
|
48
51
|
const { viewportFollow } = element.props
|
|
49
|
-
const
|
|
50
|
-
if (viewportFollow) viewport.plugins.remove('follow')
|
|
52
|
+
const viewport = element.props.context?.viewport
|
|
53
|
+
if (viewportFollow && viewport) viewport.plugins.remove('follow')
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
|