canvasengine 2.0.0-beta.38 → 2.0.0-beta.39
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-DxJSMb9B.js → DebugRenderer-Rrw9FlTd.js} +2 -2
- package/dist/{DebugRenderer-DxJSMb9B.js.map → DebugRenderer-Rrw9FlTd.js.map} +1 -1
- package/dist/components/Button.d.ts +50 -3
- package/dist/components/Button.d.ts.map +1 -1
- package/dist/components/Joystick.d.ts +36 -0
- package/dist/components/Joystick.d.ts.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/directives/Controls.d.ts +16 -7
- package/dist/directives/Controls.d.ts.map +1 -1
- package/dist/directives/GamepadControls.d.ts +3 -1
- package/dist/directives/GamepadControls.d.ts.map +1 -1
- package/dist/directives/JoystickControls.d.ts +172 -0
- package/dist/directives/JoystickControls.d.ts.map +1 -0
- package/dist/directives/index.d.ts +1 -0
- package/dist/directives/index.d.ts.map +1 -1
- package/dist/engine/reactive.d.ts.map +1 -1
- package/dist/{index-BgNWflRE.js → index-BQ99FClW.js} +5543 -5141
- package/dist/index-BQ99FClW.js.map +1 -0
- package/dist/index.global.js +7 -7
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +59 -57
- package/package.json +1 -1
- package/src/components/Button.ts +168 -41
- package/src/components/Joystick.ts +361 -0
- package/src/components/index.ts +2 -1
- package/src/directives/Controls.ts +42 -8
- package/src/directives/GamepadControls.ts +40 -11
- package/src/directives/JoystickControls.ts +396 -0
- package/src/directives/index.ts +1 -0
- package/src/engine/reactive.ts +41 -14
- package/dist/index-BgNWflRE.js.map +0 -1
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { ControlsBase, Controls } from "./ControlsBase";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Joystick directions reported by the Joystick component
|
|
5
|
+
*/
|
|
6
|
+
export type JoystickDirection =
|
|
7
|
+
| 'left'
|
|
8
|
+
| 'right'
|
|
9
|
+
| 'top'
|
|
10
|
+
| 'bottom'
|
|
11
|
+
| 'top_left'
|
|
12
|
+
| 'top_right'
|
|
13
|
+
| 'bottom_left'
|
|
14
|
+
| 'bottom_right';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Joystick change event payload
|
|
18
|
+
*/
|
|
19
|
+
export interface JoystickChangeEvent {
|
|
20
|
+
angle: number;
|
|
21
|
+
direction: JoystickDirection;
|
|
22
|
+
power: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Joystick configuration interface
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const joystickConfig: JoystickConfig = {
|
|
31
|
+
* enabled: true,
|
|
32
|
+
* directionMapping: {
|
|
33
|
+
* 'top': 'up',
|
|
34
|
+
* 'bottom': 'down',
|
|
35
|
+
* 'left': 'left',
|
|
36
|
+
* 'right': 'right',
|
|
37
|
+
* 'top_left': ['up', 'left'],
|
|
38
|
+
* 'top_right': ['up', 'right'],
|
|
39
|
+
* 'bottom_left': ['down', 'left'],
|
|
40
|
+
* 'bottom_right': ['down', 'right']
|
|
41
|
+
* },
|
|
42
|
+
* moveInterval: 50,
|
|
43
|
+
* threshold: 0.1
|
|
44
|
+
* };
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export interface JoystickConfig {
|
|
48
|
+
/** Whether joystick is enabled (default: true) */
|
|
49
|
+
enabled?: boolean;
|
|
50
|
+
/** Mapping of joystick direction names to control names (can be single string or array for diagonals) */
|
|
51
|
+
directionMapping?: {
|
|
52
|
+
[joystickDirection: string]: string | string[]; // e.g., 'top' -> 'up', 'top_left' -> ['up', 'left']
|
|
53
|
+
};
|
|
54
|
+
/** Interval in milliseconds for repeating movement actions (default: 50) */
|
|
55
|
+
moveInterval?: number;
|
|
56
|
+
/** Threshold for power value to trigger movement (default: 0.1) */
|
|
57
|
+
threshold?: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Default direction mapping
|
|
62
|
+
*/
|
|
63
|
+
const DEFAULT_DIRECTION_MAPPING: { [direction: string]: string | string[] } = {
|
|
64
|
+
'top': 'up',
|
|
65
|
+
'bottom': 'down',
|
|
66
|
+
'left': 'left',
|
|
67
|
+
'right': 'right',
|
|
68
|
+
'top_left': ['up', 'left'],
|
|
69
|
+
'top_right': ['up', 'right'],
|
|
70
|
+
'bottom_left': ['down', 'left'],
|
|
71
|
+
'bottom_right': ['down', 'right']
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Joystick input controls implementation
|
|
76
|
+
*
|
|
77
|
+
* Handles joystick input events from the Joystick component and maps them to control actions.
|
|
78
|
+
* Supports directional movement with configurable mappings, including diagonal directions.
|
|
79
|
+
*
|
|
80
|
+
* The joystick controls work by receiving change events from a Joystick component instance.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* const joystickControls = new JoystickControls();
|
|
85
|
+
* joystickControls.setInputs({
|
|
86
|
+
* up: {
|
|
87
|
+
* repeat: true,
|
|
88
|
+
* bind: 'up',
|
|
89
|
+
* keyDown() {
|
|
90
|
+
* console.log('Up pressed');
|
|
91
|
+
* }
|
|
92
|
+
* }
|
|
93
|
+
* });
|
|
94
|
+
* joystickControls.updateJoystickConfig({
|
|
95
|
+
* enabled: true,
|
|
96
|
+
* directionMapping: {
|
|
97
|
+
* 'top': 'up'
|
|
98
|
+
* }
|
|
99
|
+
* });
|
|
100
|
+
* joystickControls.start();
|
|
101
|
+
*
|
|
102
|
+
* // Later, when joystick changes:
|
|
103
|
+
* joystickControls.handleJoystickChange({ angle: 90, direction: Direction.TOP, power: 0.8 });
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export class JoystickControls extends ControlsBase {
|
|
107
|
+
private joystickEnabled: boolean = true;
|
|
108
|
+
private joystickConfig: JoystickConfig = {
|
|
109
|
+
enabled: true,
|
|
110
|
+
directionMapping: DEFAULT_DIRECTION_MAPPING,
|
|
111
|
+
moveInterval: 50,
|
|
112
|
+
threshold: 0.1
|
|
113
|
+
};
|
|
114
|
+
private joystickMoving: boolean = false;
|
|
115
|
+
private joystickDirections: { [direction: string]: boolean } = {};
|
|
116
|
+
private joystickLastUpdate: number = 0;
|
|
117
|
+
private joystickMoveInterval: any = null;
|
|
118
|
+
private currentPower: number = 0;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Setup joystick event listeners
|
|
122
|
+
* Note: Joystick events are handled via handleJoystickChange() method
|
|
123
|
+
*/
|
|
124
|
+
protected setupListeners(): void {
|
|
125
|
+
// Joystick events are handled externally via handleJoystickChange()
|
|
126
|
+
// This method is kept for consistency with ControlsBase interface
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Cleanup joystick intervals
|
|
131
|
+
*/
|
|
132
|
+
protected cleanup(): void {
|
|
133
|
+
if (this.joystickMoveInterval) {
|
|
134
|
+
clearInterval(this.joystickMoveInterval);
|
|
135
|
+
this.joystickMoveInterval = null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Process joystick inputs each step
|
|
141
|
+
* Continuous actions are handled by the interval; no inactivity timeout here.
|
|
142
|
+
*/
|
|
143
|
+
protected preStep(): void {
|
|
144
|
+
if (this.stop) return;
|
|
145
|
+
// No-op: continuous movement is driven by processJoystickMovement interval
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Handle joystick change event
|
|
150
|
+
* Called by the Joystick component when its position changes
|
|
151
|
+
*
|
|
152
|
+
* @param event - Joystick change event containing angle, direction, and power
|
|
153
|
+
*/
|
|
154
|
+
handleJoystickChange(event: JoystickChangeEvent): void {
|
|
155
|
+
if (!this.joystickEnabled || !this.joystickConfig.enabled) return;
|
|
156
|
+
if (this.stop) return;
|
|
157
|
+
|
|
158
|
+
this.joystickLastUpdate = Date.now();
|
|
159
|
+
this.currentPower = event.power;
|
|
160
|
+
|
|
161
|
+
// Check threshold
|
|
162
|
+
if (event.power < (this.joystickConfig.threshold || 0.1)) {
|
|
163
|
+
// Power too low, stop all movements
|
|
164
|
+
this.stopAllMovements();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const directionMapping = this.joystickConfig.directionMapping || DEFAULT_DIRECTION_MAPPING;
|
|
169
|
+
const directionKey = event.direction;
|
|
170
|
+
const mappedControls = directionMapping[directionKey];
|
|
171
|
+
|
|
172
|
+
if (!mappedControls) {
|
|
173
|
+
// No mapping for this direction, stop all movements
|
|
174
|
+
this.stopAllMovements();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Convert to array if single string
|
|
179
|
+
const controlNames = Array.isArray(mappedControls) ? mappedControls : [mappedControls];
|
|
180
|
+
|
|
181
|
+
// Determine which directions to activate and deactivate
|
|
182
|
+
const previousDirections = this.joystickDirections;
|
|
183
|
+
const newDirections: { [dir: string]: boolean } = {};
|
|
184
|
+
controlNames.forEach(controlName => {
|
|
185
|
+
newDirections[controlName] = true;
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Deactivate directions that are no longer active
|
|
189
|
+
const allDirections = new Set([
|
|
190
|
+
...Object.keys(this.joystickDirections),
|
|
191
|
+
...Object.keys(newDirections)
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
for (const dir of allDirections) {
|
|
195
|
+
const wasActive = this.joystickDirections[dir];
|
|
196
|
+
const shouldBeActive = newDirections[dir] || false;
|
|
197
|
+
|
|
198
|
+
if (wasActive && !shouldBeActive) {
|
|
199
|
+
// Deactivate this direction
|
|
200
|
+
this.applyControl(dir, false).catch(() => {
|
|
201
|
+
// Ignore errors
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Update active directions
|
|
207
|
+
this.joystickDirections = { ...newDirections };
|
|
208
|
+
this.joystickMoving = true;
|
|
209
|
+
|
|
210
|
+
// Activate new directions
|
|
211
|
+
const directionsToActivate = controlNames.filter((name) => !previousDirections[name]);
|
|
212
|
+
for (const controlName of directionsToActivate) {
|
|
213
|
+
this.applyControl(controlName, true, { power: event.power }).catch(() => {
|
|
214
|
+
// Ignore errors
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Start movement interval if not already running
|
|
219
|
+
if (!this.joystickMoveInterval) {
|
|
220
|
+
this.joystickMoveInterval = setInterval(() => {
|
|
221
|
+
this.processJoystickMovement();
|
|
222
|
+
}, this.joystickConfig.moveInterval || 50);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Handle joystick start event
|
|
228
|
+
* Called when user starts interacting with the joystick
|
|
229
|
+
*/
|
|
230
|
+
handleJoystickStart(): void {
|
|
231
|
+
if (!this.joystickEnabled || !this.joystickConfig.enabled) return;
|
|
232
|
+
if (this.stop) return;
|
|
233
|
+
// Start event doesn't need special handling, change event will handle activation
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Handle joystick end event
|
|
238
|
+
* Called when user stops interacting with the joystick
|
|
239
|
+
*/
|
|
240
|
+
handleJoystickEnd(): void {
|
|
241
|
+
if (!this.joystickEnabled || !this.joystickConfig.enabled) return;
|
|
242
|
+
this.stopAllMovements();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Stop all active joystick movements
|
|
247
|
+
*/
|
|
248
|
+
private stopAllMovements(): void {
|
|
249
|
+
if (this.joystickMoveInterval) {
|
|
250
|
+
clearInterval(this.joystickMoveInterval);
|
|
251
|
+
this.joystickMoveInterval = null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const allDirections = Object.keys(this.joystickDirections);
|
|
255
|
+
for (const dir of allDirections) {
|
|
256
|
+
this.applyControl(dir, false).catch(() => {
|
|
257
|
+
// Ignore errors
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
this.joystickDirections = {};
|
|
262
|
+
this.joystickMoving = false;
|
|
263
|
+
this.currentPower = 0;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Process continuous joystick movement
|
|
268
|
+
* Called at intervals to repeat movement actions while joystick is active
|
|
269
|
+
*/
|
|
270
|
+
private processJoystickMovement(): void {
|
|
271
|
+
if (!this.joystickMoving) return;
|
|
272
|
+
if (this.stop) return;
|
|
273
|
+
|
|
274
|
+
for (const direction in this.joystickDirections) {
|
|
275
|
+
if (this.joystickDirections[direction]) {
|
|
276
|
+
this.applyControl(direction, true, { power: this.currentPower }).catch(() => {
|
|
277
|
+
// Ignore errors
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Update joystick configuration
|
|
285
|
+
* Merges provided config with defaults
|
|
286
|
+
*
|
|
287
|
+
* @param config - Partial joystick configuration
|
|
288
|
+
*/
|
|
289
|
+
updateJoystickConfig(config: Partial<JoystickConfig>): void {
|
|
290
|
+
this.joystickConfig = {
|
|
291
|
+
enabled: config.enabled !== undefined ? config.enabled : true,
|
|
292
|
+
directionMapping: config.directionMapping || DEFAULT_DIRECTION_MAPPING,
|
|
293
|
+
moveInterval: config.moveInterval || 50,
|
|
294
|
+
threshold: config.threshold || 0.1
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Extract joystick config from controls configuration and update
|
|
300
|
+
*
|
|
301
|
+
* @param inputs - Controls configuration that may contain a 'joystick' property
|
|
302
|
+
*/
|
|
303
|
+
extractJoystickConfig(inputs: Controls & { joystick?: JoystickConfig }): void {
|
|
304
|
+
if (inputs.joystick) {
|
|
305
|
+
this.updateJoystickConfig(inputs.joystick);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Get the current joystick configuration
|
|
311
|
+
*
|
|
312
|
+
* @returns The joystick configuration object
|
|
313
|
+
*/
|
|
314
|
+
getJoystickConfig(): JoystickConfig {
|
|
315
|
+
return this.joystickConfig;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Apply a control action programmatically
|
|
320
|
+
* Uses the bound controls to trigger actions
|
|
321
|
+
*
|
|
322
|
+
* @param controlName - Name of the control
|
|
323
|
+
* @param isDown - Whether the control is pressed (true) or released (false)
|
|
324
|
+
* @returns Promise that resolves when the action is complete
|
|
325
|
+
*/
|
|
326
|
+
async applyControl(controlName: string | number, isDown?: boolean, payload?: any): Promise<void> {
|
|
327
|
+
const control = this._controlsOptions[controlName];
|
|
328
|
+
if (!control) return;
|
|
329
|
+
|
|
330
|
+
// Find the bound key for this control
|
|
331
|
+
const boundKeys = Object.keys(this.boundKeys);
|
|
332
|
+
for (const keyName of boundKeys) {
|
|
333
|
+
const boundKey = this.boundKeys[keyName];
|
|
334
|
+
if (boundKey.actionName === String(controlName)) {
|
|
335
|
+
// Execute the control callback
|
|
336
|
+
if (isDown === undefined) {
|
|
337
|
+
// Press and release (simulate button press)
|
|
338
|
+
if (boundKey.options.keyDown) {
|
|
339
|
+
let parameters = payload ?? boundKey.parameters;
|
|
340
|
+
if (typeof parameters === "function") {
|
|
341
|
+
parameters = parameters();
|
|
342
|
+
}
|
|
343
|
+
boundKey.options.keyDown(boundKey, parameters);
|
|
344
|
+
}
|
|
345
|
+
// Release after a short delay (similar to keyboard)
|
|
346
|
+
return new Promise((resolve) => {
|
|
347
|
+
setTimeout(() => {
|
|
348
|
+
if (boundKey.options.keyUp) {
|
|
349
|
+
let parameters = payload ?? boundKey.parameters;
|
|
350
|
+
if (typeof parameters === "function") {
|
|
351
|
+
parameters = parameters();
|
|
352
|
+
}
|
|
353
|
+
boundKey.options.keyUp(boundKey, parameters);
|
|
354
|
+
}
|
|
355
|
+
resolve();
|
|
356
|
+
}, 200);
|
|
357
|
+
});
|
|
358
|
+
} else if (isDown) {
|
|
359
|
+
if (boundKey.options.keyDown) {
|
|
360
|
+
let parameters = payload ?? boundKey.parameters;
|
|
361
|
+
if (typeof parameters === "function") {
|
|
362
|
+
parameters = parameters();
|
|
363
|
+
}
|
|
364
|
+
boundKey.options.keyDown(boundKey, parameters);
|
|
365
|
+
}
|
|
366
|
+
} else {
|
|
367
|
+
if (boundKey.options.keyUp) {
|
|
368
|
+
let parameters = payload ?? boundKey.parameters;
|
|
369
|
+
if (typeof parameters === "function") {
|
|
370
|
+
parameters = parameters();
|
|
371
|
+
}
|
|
372
|
+
boundKey.options.keyUp(boundKey, parameters);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Override setInputs to extract joystick config
|
|
382
|
+
*/
|
|
383
|
+
setInputs(inputs: Controls & { joystick?: JoystickConfig }): void {
|
|
384
|
+
super.setInputs(inputs);
|
|
385
|
+
this.extractJoystickConfig(inputs);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Check if joystick is currently active
|
|
390
|
+
*
|
|
391
|
+
* @returns true if joystick is moving, false otherwise
|
|
392
|
+
*/
|
|
393
|
+
isActive(): boolean {
|
|
394
|
+
return this.joystickMoving;
|
|
395
|
+
}
|
|
396
|
+
}
|
package/src/directives/index.ts
CHANGED
package/src/engine/reactive.ts
CHANGED
|
@@ -301,8 +301,13 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
async function onMount(parent: Element, element: Element, index?: number) {
|
|
304
|
-
|
|
305
|
-
|
|
304
|
+
let actualParent = parent;
|
|
305
|
+
while (actualParent?.tag === 'fragment') {
|
|
306
|
+
actualParent = actualParent.parent;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
element.props.context = actualParent.props.context;
|
|
310
|
+
element.parent = actualParent;
|
|
306
311
|
|
|
307
312
|
// Check dependencies before mounting
|
|
308
313
|
if (element.props.dependencies && Array.isArray(element.props.dependencies)) {
|
|
@@ -310,12 +315,12 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
310
315
|
const ready = await checkDependencies(deps);
|
|
311
316
|
if (!ready) {
|
|
312
317
|
// Set up subscriptions for reactive signals to trigger mount later
|
|
313
|
-
setupDependencySubscriptions(
|
|
318
|
+
setupDependencySubscriptions(actualParent, element, deps, index);
|
|
314
319
|
return;
|
|
315
320
|
}
|
|
316
321
|
}
|
|
317
322
|
|
|
318
|
-
performMount(
|
|
323
|
+
performMount(actualParent, element, index);
|
|
319
324
|
};
|
|
320
325
|
|
|
321
326
|
async function propagateContext(element) {
|
|
@@ -494,6 +499,25 @@ export function loop<T>(
|
|
|
494
499
|
let elementMap = new Map<string | number, Element>();
|
|
495
500
|
let isFirstSubscription = true;
|
|
496
501
|
|
|
502
|
+
const ensureElement = (itemResult: any): Element | null => {
|
|
503
|
+
if (!itemResult) return null;
|
|
504
|
+
if (isElement(itemResult)) return itemResult;
|
|
505
|
+
return {
|
|
506
|
+
tag: 'fragment',
|
|
507
|
+
props: { children: Array.isArray(itemResult) ? itemResult : [itemResult] },
|
|
508
|
+
componentInstance: {} as any,
|
|
509
|
+
propSubscriptions: [],
|
|
510
|
+
effectSubscriptions: [],
|
|
511
|
+
effectMounts: [],
|
|
512
|
+
effectUnmounts: [],
|
|
513
|
+
propObservables: {},
|
|
514
|
+
parent: null,
|
|
515
|
+
directives: {},
|
|
516
|
+
destroy() { destroyElement(this) },
|
|
517
|
+
allElements: new Subject()
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
497
521
|
const isArraySignal = (signal: any): signal is WritableArraySignal<T[]> =>
|
|
498
522
|
Array.isArray(signal());
|
|
499
523
|
|
|
@@ -509,7 +533,7 @@ export function loop<T>(
|
|
|
509
533
|
const items = itemsSubject();
|
|
510
534
|
if (items) {
|
|
511
535
|
items.forEach((item, index) => {
|
|
512
|
-
const element = createElementFn(item, index);
|
|
536
|
+
const element = ensureElement(createElementFn(item, index));
|
|
513
537
|
if (element) {
|
|
514
538
|
elements.push(element);
|
|
515
539
|
elementMap.set(index, element);
|
|
@@ -534,7 +558,7 @@ export function loop<T>(
|
|
|
534
558
|
const items = itemsSubject();
|
|
535
559
|
if (items) {
|
|
536
560
|
items.forEach((item, index) => {
|
|
537
|
-
const element = createElementFn(item, index);
|
|
561
|
+
const element = ensureElement(createElementFn(item, index));
|
|
538
562
|
if (element) {
|
|
539
563
|
elements.push(element);
|
|
540
564
|
elementMap.set(index, element);
|
|
@@ -543,7 +567,7 @@ export function loop<T>(
|
|
|
543
567
|
}
|
|
544
568
|
} else if (change.type === 'add' && change.index !== undefined) {
|
|
545
569
|
const newElements = change.items.map((item, i) => {
|
|
546
|
-
const element = createElementFn(item as T, change.index! + i);
|
|
570
|
+
const element = ensureElement(createElementFn(item as T, change.index! + i));
|
|
547
571
|
if (element) {
|
|
548
572
|
elementMap.set(change.index! + i, element);
|
|
549
573
|
}
|
|
@@ -564,7 +588,7 @@ export function loop<T>(
|
|
|
564
588
|
// Check if the previous item at this index was effectively undefined or non-existent
|
|
565
589
|
if (index >= elements.length || elements[index] === undefined || !elementMap.has(index)) {
|
|
566
590
|
// Treat as add operation
|
|
567
|
-
const newElement = createElementFn(newItem as T, index);
|
|
591
|
+
const newElement = ensureElement(createElementFn(newItem as T, index));
|
|
568
592
|
if (newElement) {
|
|
569
593
|
elements.splice(index, 0, newElement); // Insert at the correct index
|
|
570
594
|
elementMap.set(index, newElement);
|
|
@@ -577,7 +601,7 @@ export function loop<T>(
|
|
|
577
601
|
// Treat as a standard update operation
|
|
578
602
|
const oldElement = elements[index];
|
|
579
603
|
destroyElement(oldElement)
|
|
580
|
-
const newElement = createElementFn(newItem as T, index);
|
|
604
|
+
const newElement = ensureElement(createElementFn(newItem as T, index));
|
|
581
605
|
if (newElement) {
|
|
582
606
|
elements[index] = newElement;
|
|
583
607
|
elementMap.set(index, newElement);
|
|
@@ -604,7 +628,7 @@ export function loop<T>(
|
|
|
604
628
|
const items = (itemsSubject as WritableObjectSignal<T>)();
|
|
605
629
|
if (items) {
|
|
606
630
|
Object.entries(items).forEach(([key, value]) => {
|
|
607
|
-
const element = createElementFn(value, key);
|
|
631
|
+
const element = ensureElement(createElementFn(value, key));
|
|
608
632
|
if (element) {
|
|
609
633
|
elements.push(element);
|
|
610
634
|
elementMap.set(key, element);
|
|
@@ -625,7 +649,7 @@ export function loop<T>(
|
|
|
625
649
|
const items = (itemsSubject as WritableObjectSignal<T>)();
|
|
626
650
|
if (items) {
|
|
627
651
|
Object.entries(items).forEach(([key, value]) => {
|
|
628
|
-
const element = createElementFn(value, key);
|
|
652
|
+
const element = ensureElement(createElementFn(value, key));
|
|
629
653
|
if (element) {
|
|
630
654
|
elements.push(element);
|
|
631
655
|
elementMap.set(key, element);
|
|
@@ -633,7 +657,7 @@ export function loop<T>(
|
|
|
633
657
|
});
|
|
634
658
|
}
|
|
635
659
|
} else if (change.type === 'add' && change.key && change.value !== undefined) {
|
|
636
|
-
const element = createElementFn(change.value as T, key);
|
|
660
|
+
const element = ensureElement(createElementFn(change.value as T, key));
|
|
637
661
|
if (element) {
|
|
638
662
|
elements.push(element);
|
|
639
663
|
elementMap.set(key, element);
|
|
@@ -650,7 +674,7 @@ export function loop<T>(
|
|
|
650
674
|
if (index !== -1) {
|
|
651
675
|
const oldElement = elements[index];
|
|
652
676
|
destroyElement(oldElement)
|
|
653
|
-
const newElement = createElementFn(change.value as T, key);
|
|
677
|
+
const newElement = ensureElement(createElementFn(change.value as T, key));
|
|
654
678
|
if (newElement) {
|
|
655
679
|
elements[index] = newElement;
|
|
656
680
|
elementMap.set(key, newElement);
|
|
@@ -663,7 +687,10 @@ export function loop<T>(
|
|
|
663
687
|
});
|
|
664
688
|
});
|
|
665
689
|
|
|
666
|
-
return
|
|
690
|
+
return () => {
|
|
691
|
+
subscription.unsubscribe();
|
|
692
|
+
elements.forEach(el => destroyElement(el));
|
|
693
|
+
};
|
|
667
694
|
});
|
|
668
695
|
});
|
|
669
696
|
}
|