phaser-hooks 0.1.0 → 0.2.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/README.md +48 -5
- package/dist/hooks/validators.spec.d.ts +2 -0
- package/dist/hooks/validators.spec.d.ts.map +1 -0
- package/dist/hooks/validators.spec.js +64 -0
- package/dist/hooks/validators.spec.js.map +1 -0
- package/dist/hooks/with-global-state.d.ts +44 -125
- package/dist/hooks/with-global-state.d.ts.map +1 -1
- package/dist/hooks/with-global-state.js +52 -213
- package/dist/hooks/with-global-state.js.map +1 -1
- package/dist/hooks/with-local-state.d.ts.map +1 -1
- package/dist/hooks/with-local-state.js +1 -0
- package/dist/hooks/with-local-state.js.map +1 -1
- package/dist/hooks/with-persistent-state.d.ts +1 -1
- package/dist/hooks/with-persistent-state.d.ts.map +1 -1
- package/dist/hooks/with-persistent-state.js +8 -6
- package/dist/hooks/with-persistent-state.js.map +1 -1
- package/dist/hooks/with-state-def.d.ts +5 -0
- package/dist/hooks/with-state-def.d.ts.map +1 -1
- package/dist/hooks/with-state-def.js +8 -5
- package/dist/hooks/with-state-def.js.map +1 -1
- package/package.json +12 -11
package/README.md
CHANGED
|
@@ -2,6 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
A comprehensive state management library for Phaser games with React-like hooks pattern.
|
|
4
4
|
|
|
5
|
+
## Why phaser-hooks?
|
|
6
|
+
|
|
7
|
+
Phaser already gives you two ways of storing state:
|
|
8
|
+
|
|
9
|
+
- `registry` → global state across the game
|
|
10
|
+
- `data` → local state inside a scene or game object
|
|
11
|
+
|
|
12
|
+
They work, but the API is a bit… verbose:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
// Using registry (global)
|
|
16
|
+
this.game.registry.set('volume', 0.5); // too boring
|
|
17
|
+
const volume = this.game.registry.get('volume');
|
|
18
|
+
this.game.registry.events.on('changedata-volume', (game, value) => {
|
|
19
|
+
console.log('Volume changed to', value);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Using scene.data (local)
|
|
23
|
+
this.data.set('score', 42); // too boring too
|
|
24
|
+
const score = this.data.get('score');
|
|
25
|
+
this.data.events.on('changedata-score', (scene, value) => {
|
|
26
|
+
console.log('Score updated to', value);
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
With _phaser-hooks_, you get a simple, React-like API:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
// Global state
|
|
34
|
+
const volume = withGlobalState(scene, 'volume', 0.5); // woow! awesome
|
|
35
|
+
volume.get(); // 0.5
|
|
36
|
+
volume.set(0.8); // updates value
|
|
37
|
+
volume.onChange(v => console.log('Volume changed →', v)); // Nice callback <3
|
|
38
|
+
|
|
39
|
+
// Persisted state (localStorage / sessionStorage)
|
|
40
|
+
const score = withPersistState(scene, 'score', 0, { storage: 'local' }); // Wow! Saving in localStorage
|
|
41
|
+
score.set(100); // Update localStorage!! Wow! I love this lib <3
|
|
42
|
+
```
|
|
43
|
+
|
|
5
44
|
## Installation
|
|
6
45
|
|
|
7
46
|
```bash
|
|
@@ -41,7 +80,7 @@ const playerState = withLocalState<PlayerData>(scene, 'player', {
|
|
|
41
80
|
Application-wide state that persists across all scenes.
|
|
42
81
|
|
|
43
82
|
```typescript
|
|
44
|
-
const settingsState = withGlobalState<GameSettings>('settings', {
|
|
83
|
+
const settingsState = withGlobalState<GameSettings>(scene, 'settings', {
|
|
45
84
|
soundVolume: 0.8,
|
|
46
85
|
musicEnabled: true,
|
|
47
86
|
});
|
|
@@ -66,10 +105,14 @@ const customState = withStateDef<number>(scene, 'score', {
|
|
|
66
105
|
State with automatic localStorage persistence.
|
|
67
106
|
|
|
68
107
|
```typescript
|
|
69
|
-
const persistentSettings = withPersistentState<UserSettings>(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
108
|
+
const persistentSettings = withPersistentState<UserSettings>(
|
|
109
|
+
'settings',
|
|
110
|
+
{
|
|
111
|
+
volume: 0.8,
|
|
112
|
+
difficulty: 'normal',
|
|
113
|
+
},
|
|
114
|
+
'local' // If you want only in sessionStorage, you can set 'session'
|
|
115
|
+
);
|
|
73
116
|
```
|
|
74
117
|
|
|
75
118
|
#### `withComputedState`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validators.spec.d.ts","sourceRoot":"","sources":["../../src/hooks/validators.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/* eslint-disable no-magic-numbers */
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { validators } from './validators';
|
|
4
|
+
/* eslint-disable max-lines-per-function */
|
|
5
|
+
describe('validators', () => {
|
|
6
|
+
describe('numberRange', () => {
|
|
7
|
+
const validator = validators.numberRange(0, 100);
|
|
8
|
+
it('should return true for valid numbers in range', () => {
|
|
9
|
+
expect(validator(50)).toBe(true);
|
|
10
|
+
expect(validator(0)).toBe(true);
|
|
11
|
+
expect(validator(100)).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
it('should return error message for invalid numbers', () => {
|
|
14
|
+
expect(validator(-1)).toBe('Value must be between 0 and 100');
|
|
15
|
+
expect(validator(101)).toBe('Value must be between 0 and 100');
|
|
16
|
+
expect(validator('50')).toBe('Value must be a number');
|
|
17
|
+
expect(validator(NaN)).toBe('Value must be a number');
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
describe('nonEmptyString', () => {
|
|
21
|
+
it('should return true for valid non-empty strings', () => {
|
|
22
|
+
expect(validators.nonEmptyString('test')).toBe(true);
|
|
23
|
+
expect(validators.nonEmptyString(' test ')).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
it('should return error message for invalid strings', () => {
|
|
26
|
+
const message = 'Value must be a non-empty string';
|
|
27
|
+
expect(validators.nonEmptyString('')).toBe(message);
|
|
28
|
+
expect(validators.nonEmptyString(' ')).toBe(message);
|
|
29
|
+
expect(validators.nonEmptyString(123)).toBe(message);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('arrayLength', () => {
|
|
33
|
+
const validator = validators.arrayLength(2, 4);
|
|
34
|
+
it('should return true for arrays with valid length', () => {
|
|
35
|
+
expect(validator([1, 2])).toBe(true);
|
|
36
|
+
expect(validator([1, 2, 3])).toBe(true);
|
|
37
|
+
expect(validator([1, 2, 3, 4])).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
it('should return error message for invalid arrays', () => {
|
|
40
|
+
expect(validator([1])).toBe('Array must have at least 2 items');
|
|
41
|
+
expect(validator([1, 2, 3, 4, 5])).toBe('Array must have at most 4 items');
|
|
42
|
+
expect(validator('not an array')).toBe('Value must be an array');
|
|
43
|
+
});
|
|
44
|
+
it('should work with only minimum length specified', () => {
|
|
45
|
+
const minOnlyValidator = validators.arrayLength(2);
|
|
46
|
+
expect(minOnlyValidator([1, 2])).toBe(true);
|
|
47
|
+
expect(minOnlyValidator([1, 2, 3, 4, 5])).toBe(true);
|
|
48
|
+
expect(minOnlyValidator([1])).toBe('Array must have at least 2 items');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('oneOf', () => {
|
|
52
|
+
const validator = validators.oneOf(['a', 'b', 'c']);
|
|
53
|
+
it('should return true for allowed values', () => {
|
|
54
|
+
expect(validator('a')).toBe(true);
|
|
55
|
+
expect(validator('b')).toBe(true);
|
|
56
|
+
expect(validator('c')).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
it('should return error message for disallowed values', () => {
|
|
59
|
+
expect(validator('d')).toBe('Value must be one of: a, b, c');
|
|
60
|
+
expect(validator(123)).toBe('Value must be one of: a, b, c');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
//# sourceMappingURL=validators.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validators.spec.js","sourceRoot":"","sources":["../../src/hooks/validators.spec.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,2CAA2C;AAC3C,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QAC3B,MAAM,SAAS,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAEjD,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;YACvD,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAChC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;YACzD,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;YAC9D,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;YAC/D,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;YACvD,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;QAC9B,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;YACxD,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrD,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;YACzD,MAAM,OAAO,GAAG,kCAAkC,CAAC;YACnD,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACpD,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvD,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QAC3B,MAAM,SAAS,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAE/C,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;YACzD,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;YACxD,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;YAChE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CACrC,iCAAiC,CAClC,CAAC;YACF,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;YACxD,MAAM,gBAAgB,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YACnD,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC5C,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrD,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;QACzE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE;QACrB,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;QAEpD,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;YAC/C,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;YAC3D,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;YAC7D,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -1,150 +1,69 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Phaser from 'phaser';
|
|
2
|
+
import { type HookState } from './type';
|
|
3
|
+
import { type StateDefOptions } from './with-state-def';
|
|
2
4
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
declare class GlobalState {
|
|
7
|
-
private static registry;
|
|
8
|
-
private static listeners;
|
|
9
|
-
/**
|
|
10
|
-
* Set a value in the global state
|
|
11
|
-
*/
|
|
12
|
-
static set<T>(key: string, value: T): void;
|
|
13
|
-
/**
|
|
14
|
-
* Get a value from the global state
|
|
15
|
-
*/
|
|
16
|
-
static get<T>(key: string, defaultValue?: T): T | undefined;
|
|
17
|
-
/**
|
|
18
|
-
* Check if a key exists in the global state
|
|
19
|
-
*/
|
|
20
|
-
static has(key: string): boolean;
|
|
21
|
-
/**
|
|
22
|
-
* Register a callback for when a specific key changes
|
|
23
|
-
*/
|
|
24
|
-
static onChange<T>(key: string, callback: StateChangeCallback<T>): void;
|
|
25
|
-
/**
|
|
26
|
-
* Remove a callback for a specific key
|
|
27
|
-
*/
|
|
28
|
-
static offChange<T>(key: string, callback: StateChangeCallback<T>): void;
|
|
29
|
-
/**
|
|
30
|
-
* Clear all global state (useful for testing or resetting game)
|
|
31
|
-
*/
|
|
32
|
-
static clear(): void;
|
|
33
|
-
/**
|
|
34
|
-
* Get all keys in the global state
|
|
35
|
-
*/
|
|
36
|
-
static keys(): string[];
|
|
37
|
-
/**
|
|
38
|
-
* Get debug information about the global state
|
|
39
|
-
*/
|
|
40
|
-
static debug(): {
|
|
41
|
-
registry: Record<string, unknown>;
|
|
42
|
-
listeners: Record<string, number>;
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Configuration for global state management
|
|
47
|
-
*/
|
|
48
|
-
export type GlobalStateOptions = {
|
|
49
|
-
/**
|
|
50
|
-
* Validator function to validate state values
|
|
51
|
-
*/
|
|
52
|
-
validator?: (value: unknown) => boolean | string;
|
|
53
|
-
/**
|
|
54
|
-
* Enable debug logging
|
|
55
|
-
*/
|
|
56
|
-
debug?: boolean;
|
|
57
|
-
};
|
|
58
|
-
/**
|
|
59
|
-
* Creates a global state hook that persists across all scenes in the game.
|
|
60
|
-
* Global state is managed by a singleton registry, removing dependency on specific scenes.
|
|
61
|
-
* Perfect for user settings, game progress, authentication state, or any data that
|
|
62
|
-
* should persist throughout the game session.
|
|
5
|
+
* Creates a local state hook scoped to a specific Phaser scene.
|
|
6
|
+
* Local state is isolated to the scene instance and doesn't persist across scene changes.
|
|
7
|
+
* This is ideal for UI state, temporary game state, or scene-specific data.
|
|
63
8
|
*
|
|
64
9
|
* @template T The type of the state value
|
|
65
|
-
* @param scene Phaser scene instance
|
|
66
|
-
* @param key Unique identifier for the
|
|
10
|
+
* @param scene The Phaser scene instance that owns this state
|
|
11
|
+
* @param key Unique identifier for the state within this scene
|
|
67
12
|
* @param initialValue Optional initial value to set if state doesn't exist
|
|
68
|
-
* @param options Optional configuration for
|
|
69
|
-
* @returns HookState interface for managing the
|
|
13
|
+
* @param options Optional configuration for state behavior
|
|
14
|
+
* @returns HookState interface for managing the local state
|
|
70
15
|
*
|
|
71
|
-
* @throws {Error} When key is invalid
|
|
72
|
-
* @throws {Error} When validator rejects the initial value
|
|
16
|
+
* @throws {Error} When scene is not available or key is invalid
|
|
73
17
|
*
|
|
74
18
|
* @example
|
|
75
19
|
* ```typescript
|
|
76
|
-
* //
|
|
77
|
-
*
|
|
78
|
-
* soundVolume: number;
|
|
79
|
-
* musicEnabled: boolean;
|
|
80
|
-
* difficulty: 'easy' | 'normal' | 'hard';
|
|
81
|
-
* }
|
|
20
|
+
* // Simple counter state
|
|
21
|
+
* const counterState = withGlobalState<number>(scene, 'counter', 0);
|
|
82
22
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* });
|
|
88
|
-
*
|
|
89
|
-
* // Game progress
|
|
90
|
-
* interface GameProgress {
|
|
91
|
-
* currentLevel: number;
|
|
92
|
-
* unlockedLevels: number[];
|
|
93
|
-
* totalScore: number;
|
|
23
|
+
* // Complex object state
|
|
24
|
+
* interface GameUI {
|
|
25
|
+
* isMenuOpen: boolean;
|
|
26
|
+
* selectedTab: string;
|
|
94
27
|
* }
|
|
95
28
|
*
|
|
96
|
-
* const
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* totalScore: 0
|
|
29
|
+
* const uiState = withGlobalState<GameUI>(scene, 'ui', {
|
|
30
|
+
* isMenuOpen: false,
|
|
31
|
+
* selectedTab: 'main'
|
|
100
32
|
* });
|
|
101
33
|
*
|
|
102
|
-
* // Listen to changes
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
34
|
+
* // Listen to changes
|
|
35
|
+
* uiState.onChange((newUI, oldUI) => {
|
|
36
|
+
* if (newUI.isMenuOpen !== oldUI.isMenuOpen) {
|
|
37
|
+
* console.log('Menu visibility changed');
|
|
38
|
+
* }
|
|
106
39
|
* });
|
|
40
|
+
*
|
|
41
|
+
* // Update state
|
|
42
|
+
* uiState.set({ ...uiState.get(), isMenuOpen: true });
|
|
43
|
+
*
|
|
44
|
+
* // Array state example
|
|
45
|
+
* const inventoryState = withGlobalState<string[]>(scene, 'inventory', []);
|
|
46
|
+
* inventoryState.set([...inventoryState.get(), 'new-item']);
|
|
107
47
|
* ```
|
|
108
48
|
*
|
|
109
49
|
* @example
|
|
110
50
|
* ```typescript
|
|
111
51
|
* // With validation
|
|
112
|
-
* const
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
52
|
+
* const playerHealthState = withGlobalState<number>(
|
|
53
|
+
* scene,
|
|
54
|
+
* 'health',
|
|
55
|
+
* 100,
|
|
56
|
+
* {
|
|
57
|
+
* validator: (value) => {
|
|
58
|
+
* const health = value as number;
|
|
59
|
+
* return health >= 0 && health <= 100 ? true : 'Health must be 0-100';
|
|
116
60
|
* }
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* debug: true
|
|
120
|
-
* });
|
|
121
|
-
*
|
|
122
|
-
* // Authentication state example
|
|
123
|
-
* interface AuthState {
|
|
124
|
-
* isLoggedIn: boolean;
|
|
125
|
-
* username: string | null;
|
|
126
|
-
* token: string | null;
|
|
127
|
-
* }
|
|
128
|
-
*
|
|
129
|
-
* const authState = withGlobalState<AuthState>(scene, 'auth', {
|
|
130
|
-
* isLoggedIn: false,
|
|
131
|
-
* username: null,
|
|
132
|
-
* token: null
|
|
133
|
-
* });
|
|
134
|
-
*
|
|
135
|
-
* // Use from any scene - no dependency on Boot scene!
|
|
136
|
-
* if (authState.get().isLoggedIn) {
|
|
137
|
-
* // Show authenticated content
|
|
138
|
-
* }
|
|
61
|
+
* }
|
|
62
|
+
* );
|
|
139
63
|
* ```
|
|
140
64
|
*
|
|
141
|
-
* @see {@link
|
|
65
|
+
* @see {@link withGlobalState} For state that persists across scenes
|
|
142
66
|
* @see {@link withStateDef} For low-level state management
|
|
143
67
|
*/
|
|
144
|
-
export declare const withGlobalState: <T
|
|
145
|
-
/**
|
|
146
|
-
* Export the GlobalState class for advanced use cases
|
|
147
|
-
* (testing, debugging, manual state management)
|
|
148
|
-
*/
|
|
149
|
-
export { GlobalState };
|
|
68
|
+
export declare const withGlobalState: <T>(scene: Phaser.Scene, key: string, initialValue?: T, options?: StateDefOptions) => HookState<T>;
|
|
150
69
|
//# sourceMappingURL=with-global-state.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-global-state.d.ts","sourceRoot":"","sources":["../../src/hooks/with-global-state.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"with-global-state.d.ts","sourceRoot":"","sources":["../../src/hooks/with-global-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAEjC,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,EAC/B,OAAO,MAAM,CAAC,KAAK,EACnB,KAAK,MAAM,EACX,eAAe,CAAC,EAChB,UAAU,eAAe,KACxB,SAAS,CAAC,CAAC,CAcb,CAAC"}
|
|
@@ -1,240 +1,79 @@
|
|
|
1
|
+
import { withStateDef } from './with-state-def';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
class GlobalState {
|
|
6
|
-
static registry = new Map();
|
|
7
|
-
static listeners = new Map();
|
|
8
|
-
/**
|
|
9
|
-
* Set a value in the global state
|
|
10
|
-
*/
|
|
11
|
-
static set(key, value) {
|
|
12
|
-
const oldValue = this.registry.get(key);
|
|
13
|
-
this.registry.set(key, value);
|
|
14
|
-
// Trigger callbacks
|
|
15
|
-
const callbacks = this.listeners.get(key);
|
|
16
|
-
if (callbacks) {
|
|
17
|
-
callbacks.forEach(callback => {
|
|
18
|
-
try {
|
|
19
|
-
callback(value, oldValue);
|
|
20
|
-
}
|
|
21
|
-
catch (error) {
|
|
22
|
-
// eslint-disable-next-line no-console
|
|
23
|
-
console.error(`[GlobalState] Error in onChange callback for "${key}":`, error);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Get a value from the global state
|
|
30
|
-
*/
|
|
31
|
-
static get(key, defaultValue) {
|
|
32
|
-
return this.registry.has(key)
|
|
33
|
-
? this.registry.get(key)
|
|
34
|
-
: defaultValue;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Check if a key exists in the global state
|
|
38
|
-
*/
|
|
39
|
-
static has(key) {
|
|
40
|
-
return this.registry.has(key);
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Register a callback for when a specific key changes
|
|
44
|
-
*/
|
|
45
|
-
static onChange(key, callback) {
|
|
46
|
-
if (!this.listeners.has(key)) {
|
|
47
|
-
this.listeners.set(key, new Set());
|
|
48
|
-
}
|
|
49
|
-
this.listeners.get(key)?.add(callback);
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Remove a callback for a specific key
|
|
53
|
-
*/
|
|
54
|
-
static offChange(key, callback) {
|
|
55
|
-
const callbacks = this.listeners.get(key);
|
|
56
|
-
if (callbacks) {
|
|
57
|
-
callbacks.delete(callback);
|
|
58
|
-
if (callbacks.size === 0) {
|
|
59
|
-
this.listeners.delete(key);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Clear all global state (useful for testing or resetting game)
|
|
65
|
-
*/
|
|
66
|
-
static clear() {
|
|
67
|
-
this.registry.clear();
|
|
68
|
-
this.listeners.clear();
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Get all keys in the global state
|
|
72
|
-
*/
|
|
73
|
-
static keys() {
|
|
74
|
-
return Array.from(this.registry.keys());
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Get debug information about the global state
|
|
78
|
-
*/
|
|
79
|
-
static debug() {
|
|
80
|
-
const debugRegistry = {};
|
|
81
|
-
const debugListeners = {};
|
|
82
|
-
this.registry.forEach((value, key) => {
|
|
83
|
-
debugRegistry[key] = value;
|
|
84
|
-
});
|
|
85
|
-
this.listeners.forEach((callbacks, key) => {
|
|
86
|
-
debugListeners[key] = callbacks.size;
|
|
87
|
-
});
|
|
88
|
-
return { registry: debugRegistry, listeners: debugListeners };
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Creates a global state hook that persists across all scenes in the game.
|
|
93
|
-
* Global state is managed by a singleton registry, removing dependency on specific scenes.
|
|
94
|
-
* Perfect for user settings, game progress, authentication state, or any data that
|
|
95
|
-
* should persist throughout the game session.
|
|
3
|
+
* Creates a local state hook scoped to a specific Phaser scene.
|
|
4
|
+
* Local state is isolated to the scene instance and doesn't persist across scene changes.
|
|
5
|
+
* This is ideal for UI state, temporary game state, or scene-specific data.
|
|
96
6
|
*
|
|
97
7
|
* @template T The type of the state value
|
|
98
|
-
* @param scene Phaser scene instance
|
|
99
|
-
* @param key Unique identifier for the
|
|
8
|
+
* @param scene The Phaser scene instance that owns this state
|
|
9
|
+
* @param key Unique identifier for the state within this scene
|
|
100
10
|
* @param initialValue Optional initial value to set if state doesn't exist
|
|
101
|
-
* @param options Optional configuration for
|
|
102
|
-
* @returns HookState interface for managing the
|
|
11
|
+
* @param options Optional configuration for state behavior
|
|
12
|
+
* @returns HookState interface for managing the local state
|
|
103
13
|
*
|
|
104
|
-
* @throws {Error} When key is invalid
|
|
105
|
-
* @throws {Error} When validator rejects the initial value
|
|
14
|
+
* @throws {Error} When scene is not available or key is invalid
|
|
106
15
|
*
|
|
107
16
|
* @example
|
|
108
17
|
* ```typescript
|
|
109
|
-
* //
|
|
110
|
-
*
|
|
111
|
-
* soundVolume: number;
|
|
112
|
-
* musicEnabled: boolean;
|
|
113
|
-
* difficulty: 'easy' | 'normal' | 'hard';
|
|
114
|
-
* }
|
|
115
|
-
*
|
|
116
|
-
* const settingsState = withGlobalState<UserSettings>(scene, 'settings', {
|
|
117
|
-
* soundVolume: 0.8,
|
|
118
|
-
* musicEnabled: true,
|
|
119
|
-
* difficulty: 'normal'
|
|
120
|
-
* });
|
|
18
|
+
* // Simple counter state
|
|
19
|
+
* const counterState = withGlobalState<number>(scene, 'counter', 0);
|
|
121
20
|
*
|
|
122
|
-
* //
|
|
123
|
-
* interface
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* totalScore: number;
|
|
21
|
+
* // Complex object state
|
|
22
|
+
* interface GameUI {
|
|
23
|
+
* isMenuOpen: boolean;
|
|
24
|
+
* selectedTab: string;
|
|
127
25
|
* }
|
|
128
26
|
*
|
|
129
|
-
* const
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* totalScore: 0
|
|
27
|
+
* const uiState = withGlobalState<GameUI>(scene, 'ui', {
|
|
28
|
+
* isMenuOpen: false,
|
|
29
|
+
* selectedTab: 'main'
|
|
133
30
|
* });
|
|
134
31
|
*
|
|
135
|
-
* // Listen to changes
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
32
|
+
* // Listen to changes
|
|
33
|
+
* uiState.onChange((newUI, oldUI) => {
|
|
34
|
+
* if (newUI.isMenuOpen !== oldUI.isMenuOpen) {
|
|
35
|
+
* console.log('Menu visibility changed');
|
|
36
|
+
* }
|
|
139
37
|
* });
|
|
38
|
+
*
|
|
39
|
+
* // Update state
|
|
40
|
+
* uiState.set({ ...uiState.get(), isMenuOpen: true });
|
|
41
|
+
*
|
|
42
|
+
* // Array state example
|
|
43
|
+
* const inventoryState = withGlobalState<string[]>(scene, 'inventory', []);
|
|
44
|
+
* inventoryState.set([...inventoryState.get(), 'new-item']);
|
|
140
45
|
* ```
|
|
141
46
|
*
|
|
142
47
|
* @example
|
|
143
48
|
* ```typescript
|
|
144
49
|
* // With validation
|
|
145
|
-
* const
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
50
|
+
* const playerHealthState = withGlobalState<number>(
|
|
51
|
+
* scene,
|
|
52
|
+
* 'health',
|
|
53
|
+
* 100,
|
|
54
|
+
* {
|
|
55
|
+
* validator: (value) => {
|
|
56
|
+
* const health = value as number;
|
|
57
|
+
* return health >= 0 && health <= 100 ? true : 'Health must be 0-100';
|
|
149
58
|
* }
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* debug: true
|
|
153
|
-
* });
|
|
154
|
-
*
|
|
155
|
-
* // Authentication state example
|
|
156
|
-
* interface AuthState {
|
|
157
|
-
* isLoggedIn: boolean;
|
|
158
|
-
* username: string | null;
|
|
159
|
-
* token: string | null;
|
|
160
|
-
* }
|
|
161
|
-
*
|
|
162
|
-
* const authState = withGlobalState<AuthState>(scene, 'auth', {
|
|
163
|
-
* isLoggedIn: false,
|
|
164
|
-
* username: null,
|
|
165
|
-
* token: null
|
|
166
|
-
* });
|
|
167
|
-
*
|
|
168
|
-
* // Use from any scene - no dependency on Boot scene!
|
|
169
|
-
* if (authState.get().isLoggedIn) {
|
|
170
|
-
* // Show authenticated content
|
|
171
|
-
* }
|
|
59
|
+
* }
|
|
60
|
+
* );
|
|
172
61
|
* ```
|
|
173
62
|
*
|
|
174
|
-
* @see {@link
|
|
63
|
+
* @see {@link withGlobalState} For state that persists across scenes
|
|
175
64
|
* @see {@link withStateDef} For low-level state management
|
|
176
65
|
*/
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
export const withGlobalState = (key, initialValue, options = {}) => {
|
|
181
|
-
if (!key || typeof key !== 'string' || key.trim().length === 0) {
|
|
182
|
-
throw new Error('[withGlobalState] Key must be a non-empty string');
|
|
66
|
+
export const withGlobalState = (scene, key, initialValue, options) => {
|
|
67
|
+
if (!scene) {
|
|
68
|
+
throw new Error('[withGlobalState] Scene parameter is required');
|
|
183
69
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
throw new Error(`[withGlobalState] ${validationResult || 'Invalid initial value'}`);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
GlobalState.set(globalKey, initialValue);
|
|
196
|
-
if (debug) {
|
|
197
|
-
// eslint-disable-next-line no-console
|
|
198
|
-
console.debug(`[withGlobalState] Initialized "${key}" with value:`, initialValue);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return {
|
|
202
|
-
get: () => {
|
|
203
|
-
const value = GlobalState.get(globalKey);
|
|
204
|
-
if (debug) {
|
|
205
|
-
// eslint-disable-next-line no-console
|
|
206
|
-
console.debug(`[withGlobalState] Getting "${key}":`, value);
|
|
207
|
-
}
|
|
208
|
-
return value ?? initialValue;
|
|
209
|
-
},
|
|
210
|
-
set: (value) => {
|
|
211
|
-
if (validator) {
|
|
212
|
-
const validationResult = validator(value);
|
|
213
|
-
if (validationResult !== true) {
|
|
214
|
-
throw new Error(`[withGlobalState] Invalid value for key "${key}": ${validationResult}`);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
if (debug) {
|
|
218
|
-
const oldValue = GlobalState.get(globalKey);
|
|
219
|
-
// eslint-disable-next-line no-console
|
|
220
|
-
console.debug(`[withGlobalState] Setting "${key}":`, {
|
|
221
|
-
oldValue,
|
|
222
|
-
newValue: value,
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
GlobalState.set(globalKey, value);
|
|
226
|
-
},
|
|
227
|
-
onChange: (callback) => {
|
|
228
|
-
if (typeof callback !== 'function') {
|
|
229
|
-
throw new Error('[withGlobalState] onChange callback must be a function');
|
|
230
|
-
}
|
|
231
|
-
GlobalState.onChange(globalKey, callback);
|
|
232
|
-
},
|
|
233
|
-
};
|
|
70
|
+
// Prefix the key with scene key to ensure locality
|
|
71
|
+
const sceneKey = scene.scene?.key || 'unknown-scene';
|
|
72
|
+
const localKey = `local:${sceneKey}:${key}`;
|
|
73
|
+
return withStateDef(scene, localKey, initialValue, {
|
|
74
|
+
...options,
|
|
75
|
+
persistent: false, // Local state shouldn't persist across scene changes
|
|
76
|
+
global: true,
|
|
77
|
+
});
|
|
234
78
|
};
|
|
235
|
-
/**
|
|
236
|
-
* Export the GlobalState class for advanced use cases
|
|
237
|
-
* (testing, debugging, manual state management)
|
|
238
|
-
*/
|
|
239
|
-
export { GlobalState };
|
|
240
79
|
//# sourceMappingURL=with-global-state.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-global-state.js","sourceRoot":"","sources":["../../src/hooks/with-global-state.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"with-global-state.js","sourceRoot":"","sources":["../../src/hooks/with-global-state.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAwB,MAAM,kBAAkB,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,KAAmB,EACnB,GAAW,EACX,YAAgB,EAChB,OAAyB,EACX,EAAE;IAChB,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IAED,mDAAmD;IACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,IAAI,eAAe,CAAC;IACrD,MAAM,QAAQ,GAAG,SAAS,QAAQ,IAAI,GAAG,EAAE,CAAC;IAE5C,OAAO,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE;QACjD,GAAG,OAAO;QACV,UAAU,EAAE,KAAK,EAAE,qDAAqD;QACxE,MAAM,EAAE,IAAI;KACb,CAAC,CAAC;AACL,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-local-state.d.ts","sourceRoot":"","sources":["../../src/hooks/with-local-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAEjC,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,EAC9B,OAAO,MAAM,CAAC,KAAK,EACnB,KAAK,MAAM,EACX,eAAe,CAAC,EAChB,UAAU,eAAe,KACxB,SAAS,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"with-local-state.d.ts","sourceRoot":"","sources":["../../src/hooks/with-local-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAEjC,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,EAC9B,OAAO,MAAM,CAAC,KAAK,EACnB,KAAK,MAAM,EACX,eAAe,CAAC,EAChB,UAAU,eAAe,KACxB,SAAS,CAAC,CAAC,CAcb,CAAC"}
|
|
@@ -73,6 +73,7 @@ export const withLocalState = (scene, key, initialValue, options) => {
|
|
|
73
73
|
return withStateDef(scene, localKey, initialValue, {
|
|
74
74
|
...options,
|
|
75
75
|
persistent: false, // Local state shouldn't persist across scene changes
|
|
76
|
+
global: false,
|
|
76
77
|
});
|
|
77
78
|
};
|
|
78
79
|
//# sourceMappingURL=with-local-state.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-local-state.js","sourceRoot":"","sources":["../../src/hooks/with-local-state.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAwB,MAAM,kBAAkB,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,KAAmB,EACnB,GAAW,EACX,YAAgB,EAChB,OAAyB,EACX,EAAE;IAChB,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IAED,mDAAmD;IACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,IAAI,eAAe,CAAC;IACrD,MAAM,QAAQ,GAAG,SAAS,QAAQ,IAAI,GAAG,EAAE,CAAC;IAE5C,OAAO,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE;QACjD,GAAG,OAAO;QACV,UAAU,EAAE,KAAK,EAAE,qDAAqD;
|
|
1
|
+
{"version":3,"file":"with-local-state.js","sourceRoot":"","sources":["../../src/hooks/with-local-state.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAwB,MAAM,kBAAkB,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,KAAmB,EACnB,GAAW,EACX,YAAgB,EAChB,OAAyB,EACX,EAAE;IAChB,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IAED,mDAAmD;IACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,IAAI,eAAe,CAAC;IACrD,MAAM,QAAQ,GAAG,SAAS,QAAQ,IAAI,GAAG,EAAE,CAAC;IAE5C,OAAO,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE;QACjD,GAAG,OAAO;QACV,UAAU,EAAE,KAAK,EAAE,qDAAqD;QACxE,MAAM,EAAE,KAAK;KACd,CAAC,CAAC;AACL,CAAC,CAAC"}
|
|
@@ -15,5 +15,5 @@ import { type HookState } from './type';
|
|
|
15
15
|
* );
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
|
-
export declare const withPersistentState: <T>(key: string, initialValue: T, storageKey?: string) => HookState<T>;
|
|
18
|
+
export declare const withPersistentState: <T>(scene: Phaser.Scene, key: string, initialValue: T, storageKey?: string, storageType?: "session" | "local") => HookState<T>;
|
|
19
19
|
//# sourceMappingURL=with-persistent-state.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-persistent-state.d.ts","sourceRoot":"","sources":["../../src/hooks/with-persistent-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AAGxC;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,mBAAmB,GAAI,CAAC,EACnC,KAAK,MAAM,EACX,cAAc,CAAC,EACf,aAAa,MAAM,
|
|
1
|
+
{"version":3,"file":"with-persistent-state.d.ts","sourceRoot":"","sources":["../../src/hooks/with-persistent-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AAGxC;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,mBAAmB,GAAI,CAAC,EACnC,OAAO,MAAM,CAAC,KAAK,EACnB,KAAK,MAAM,EACX,cAAc,CAAC,EACf,aAAa,MAAM,EACnB,cAAa,SAAS,GAAG,OAAiB,KACzC,SAAS,CAAC,CAAC,CAoCb,CAAC"}
|
|
@@ -15,12 +15,12 @@ import { withGlobalState } from './with-global-state';
|
|
|
15
15
|
* );
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
|
-
export const withPersistentState = (key, initialValue, storageKey) => {
|
|
19
|
-
const actualStorageKey = storageKey ?? `phaser-state
|
|
18
|
+
export const withPersistentState = (scene, key, initialValue, storageKey, storageType = 'local') => {
|
|
19
|
+
const actualStorageKey = storageKey ?? `phaser-hooks-state:${key}`;
|
|
20
20
|
// Load from localStorage if available
|
|
21
21
|
let storedValue = initialValue;
|
|
22
22
|
try {
|
|
23
|
-
const stored = localStorage.getItem(actualStorageKey);
|
|
23
|
+
const stored = storageType === 'local' ? localStorage.getItem(actualStorageKey) : sessionStorage.getItem(actualStorageKey);
|
|
24
24
|
if (stored) {
|
|
25
25
|
storedValue = JSON.parse(stored);
|
|
26
26
|
}
|
|
@@ -29,11 +29,13 @@ export const withPersistentState = (key, initialValue, storageKey) => {
|
|
|
29
29
|
// eslint-disable-next-line no-console
|
|
30
30
|
console.warn(`[withPersistentState] Failed to load stored state for "${key}":`, error);
|
|
31
31
|
}
|
|
32
|
-
|
|
32
|
+
// @ts-ignore
|
|
33
|
+
const state = withGlobalState(scene, key, storedValue);
|
|
33
34
|
// Save to localStorage on changes
|
|
34
|
-
state.onChange(newValue => {
|
|
35
|
+
state.onChange((newValue) => {
|
|
35
36
|
try {
|
|
36
|
-
localStorage
|
|
37
|
+
const storage = storageType === 'local' ? localStorage : sessionStorage;
|
|
38
|
+
storage.setItem(actualStorageKey, JSON.stringify(newValue));
|
|
37
39
|
}
|
|
38
40
|
catch (error) {
|
|
39
41
|
// eslint-disable-next-line no-console
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-persistent-state.js","sourceRoot":"","sources":["../../src/hooks/with-persistent-state.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,GAAW,EACX,YAAe,EACf,UAAmB,
|
|
1
|
+
{"version":3,"file":"with-persistent-state.js","sourceRoot":"","sources":["../../src/hooks/with-persistent-state.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,KAAmB,EACnB,GAAW,EACX,YAAe,EACf,UAAmB,EACnB,cAAmC,OAAO,EAC5B,EAAE;IAChB,MAAM,gBAAgB,GAAG,UAAU,IAAI,sBAAsB,GAAG,EAAE,CAAC;IAEnE,sCAAsC;IACtC,IAAI,WAAW,GAAG,YAAY,CAAC;IAC/B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAC3H,IAAI,MAAM,EAAE,CAAC;YACX,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,sCAAsC;QACtC,OAAO,CAAC,IAAI,CACV,0DAA0D,GAAG,IAAI,EACjE,KAAK,CACN,CAAC;IACJ,CAAC;IAED,aAAa;IACb,MAAM,KAAK,GAAG,eAAe,CAAI,KAAK,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;IAE1D,kCAAkC;IAClC,KAAK,CAAC,QAAQ,CAAC,CAAC,QAAiB,EAAE,EAAE;QACnC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC;YACxE,OAAO,CAAC,OAAO,CAAC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,sCAAsC;YACtC,OAAO,CAAC,IAAI,CACV,mDAAmD,GAAG,IAAI,EAC1D,KAAK,CACN,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
|
|
@@ -20,6 +20,11 @@ export type StateDefOptions = {
|
|
|
20
20
|
* @default false
|
|
21
21
|
*/
|
|
22
22
|
debug?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Whether to use global state
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
global?: boolean;
|
|
23
28
|
};
|
|
24
29
|
/**
|
|
25
30
|
* Low-level state management hook that directly interfaces with Phaser's registry system.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-state-def.d.ts","sourceRoot":"","sources":["../../src/hooks/with-state-def.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAEjC,OAAO,EAAE,KAAK,SAAS,EAA4B,MAAM,QAAQ,CAAC;AAElE;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,GAAG,MAAM,CAAC;IAEjD;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"with-state-def.d.ts","sourceRoot":"","sources":["../../src/hooks/with-state-def.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAEjC,OAAO,EAAE,KAAK,SAAS,EAA4B,MAAM,QAAQ,CAAC;AAElE;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,GAAG,MAAM,CAAC;IAEjD;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAIH,eAAO,MAAM,YAAY,GAAI,CAAC,EAC5B,OAAO,MAAM,CAAC,KAAK,EACnB,KAAK,MAAM,EACX,eAAe,CAAC,EAChB,UAAS,eAAoB,KAC5B,SAAS,CAAC,CAAC,CA4Hb,CAAC"}
|
|
@@ -55,15 +55,17 @@ export const withStateDef = (scene, key, initialValue, options = {}) => {
|
|
|
55
55
|
if (!scene) {
|
|
56
56
|
throw new Error('[withStateDef] Scene parameter is required');
|
|
57
57
|
}
|
|
58
|
-
if (!scene.registry) {
|
|
58
|
+
if (options.global && !scene.registry) {
|
|
59
59
|
throw new Error('[withStateDef] Scene registry is not available. Ensure the scene is properly initialized.');
|
|
60
60
|
}
|
|
61
|
+
else if (!options.global && !scene.data) {
|
|
62
|
+
throw new Error('[withStateDef] Scene data is not available. Ensure the scene is properly initialized.');
|
|
63
|
+
}
|
|
61
64
|
if (!key || typeof key !== 'string' || key.trim().length === 0) {
|
|
62
65
|
throw new Error('[withStateDef] Key must be a non-empty string');
|
|
63
66
|
}
|
|
64
|
-
const { validator, debug = false } = options;
|
|
65
|
-
const registry = scene.registry;
|
|
66
|
-
const eventKey = `changedata-${key}`;
|
|
67
|
+
const { validator, debug = false, global = false } = options;
|
|
68
|
+
const registry = global ? scene.registry : scene.data;
|
|
67
69
|
// Validate and set initial value if provided
|
|
68
70
|
if (!registry.has(key) && initialValue !== undefined) {
|
|
69
71
|
if (validator) {
|
|
@@ -122,7 +124,8 @@ export const withStateDef = (scene, key, initialValue, options = {}) => {
|
|
|
122
124
|
if (!callback || typeof callback !== 'function') {
|
|
123
125
|
throw new Error('[withStateDef] onChange callback must be a function');
|
|
124
126
|
}
|
|
125
|
-
registry.events.on(
|
|
127
|
+
(global ? registry.events : scene.data.events).on(`changedata-${key}`, // reserved word in Phaser
|
|
128
|
+
(_parent, key, value, previousValue) => {
|
|
126
129
|
if (debug) {
|
|
127
130
|
// eslint-disable-next-line no-console
|
|
128
131
|
console.debug(`[withStateDef] Change detected for "${key}":`, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-state-def.js","sourceRoot":"","sources":["../../src/hooks/with-state-def.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"with-state-def.js","sourceRoot":"","sources":["../../src/hooks/with-state-def.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phaser-hooks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Hooks in react-style for Phaser games",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -34,29 +34,29 @@
|
|
|
34
34
|
"homepage": "https://github.com/renatocassino/phaser-toolkit/tree/main/packages/phaser-hooks#readme",
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@eslint-recommended/eslint-config": "^28.0.0",
|
|
37
|
-
"@types/node": "^24.
|
|
38
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
39
|
-
"@typescript-eslint/parser": "^
|
|
37
|
+
"@types/node": "^24.3.0",
|
|
38
|
+
"@typescript-eslint/eslint-plugin": "^8.40.0",
|
|
39
|
+
"@typescript-eslint/parser": "^8.41.0",
|
|
40
40
|
"eslint": "^8.55.0",
|
|
41
|
-
"eslint-config-prettier": "^
|
|
41
|
+
"eslint-config-prettier": "^10.1.8",
|
|
42
42
|
"eslint-plugin-import": "^2.29.1",
|
|
43
43
|
"eslint-plugin-node": "^11.1.0",
|
|
44
44
|
"eslint-plugin-prefer-arrow": "^1.2.3",
|
|
45
|
-
"eslint-plugin-promise": "^
|
|
46
|
-
"eslint-plugin-security": "^
|
|
47
|
-
"eslint-plugin-sonarjs": "^0.
|
|
45
|
+
"eslint-plugin-promise": "^7.2.1",
|
|
46
|
+
"eslint-plugin-security": "^3.0.1",
|
|
47
|
+
"eslint-plugin-sonarjs": "^3.0.5",
|
|
48
48
|
"eslint-plugin-unicorn": "^50.0.1",
|
|
49
49
|
"typescript": "^5.3.0",
|
|
50
50
|
"vitest": "^3.2.4"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"phaser": "
|
|
53
|
+
"phaser": "*"
|
|
54
54
|
},
|
|
55
55
|
"publishConfig": {
|
|
56
56
|
"access": "public"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
|
-
"build": "tsc --build",
|
|
59
|
+
"build": "tsc --build --force",
|
|
60
60
|
"dev": "tsc --build --watch",
|
|
61
61
|
"clean": "rm -rf dist",
|
|
62
62
|
"test": "vitest run",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"typecheck": "tsc --noEmit",
|
|
65
65
|
"lint": "eslint src --ext .ts,.tsx --max-warnings 0",
|
|
66
66
|
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
|
67
|
-
"lint:report": "eslint src --ext .ts,.tsx --format html --output-file eslint-report.html"
|
|
67
|
+
"lint:report": "eslint src --ext .ts,.tsx --format html --output-file eslint-report.html",
|
|
68
|
+
"validate": "pnpm run lint && pnpm run typecheck && pnpm run test && lint-staged"
|
|
68
69
|
}
|
|
69
70
|
}
|