@hkdigital/lib-core 0.5.58 → 0.5.59
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.
|
@@ -1,45 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Route-aware data manager for page groups
|
|
3
|
-
*
|
|
4
|
-
* Tracks navigation within a route group and manages business/domain data.
|
|
5
|
-
* Does NOT enforce transitions - allows free navigation via browser.
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Current route tracking and synchronization
|
|
9
|
-
* - Start path management
|
|
10
|
-
* - Data properties for business/domain state
|
|
11
|
-
* - Visited routes tracking
|
|
12
|
-
*
|
|
13
|
-
* Basic usage:
|
|
14
|
-
* ```javascript
|
|
15
|
-
* const machine = new PageMachine({
|
|
16
|
-
* startPath: '/intro/start',
|
|
17
|
-
* routes: ['/intro/start', '/intro/profile', '/intro/complete']
|
|
18
|
-
* });
|
|
19
|
-
*
|
|
20
|
-
* // Sync machine with URL changes
|
|
21
|
-
* $effect(() => {
|
|
22
|
-
* machine.syncFromPath($page.url.pathname);
|
|
23
|
-
* });
|
|
24
|
-
*
|
|
25
|
-
* // Check current route
|
|
26
|
-
* if (machine.current === '/intro/profile') {
|
|
27
|
-
* // Do something
|
|
28
|
-
* }
|
|
29
|
-
* ```
|
|
30
|
-
*
|
|
31
|
-
* Animations and page-specific logic should use $effect in pages:
|
|
32
|
-
* ```javascript
|
|
33
|
-
* // In +page.svelte
|
|
34
|
-
* const animations = new PageAnimations();
|
|
35
|
-
*
|
|
36
|
-
* $effect(() => {
|
|
37
|
-
* if (someCondition) {
|
|
38
|
-
* animations.start();
|
|
39
|
-
* }
|
|
40
|
-
* });
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
1
|
export default class PageMachine {
|
|
44
2
|
/**
|
|
45
3
|
* Constructor
|
|
@@ -50,17 +8,22 @@ export default class PageMachine {
|
|
|
50
8
|
* @param {string[]} [config.routes=[]]
|
|
51
9
|
* Optional list of valid routes (for validation/dev tools)
|
|
52
10
|
* @param {Record<string, any>} [config.initialData={}]
|
|
53
|
-
* Initial data properties (
|
|
11
|
+
* Initial data properties (use KEY_ constants for keys)
|
|
54
12
|
* @param {import('../../../logging/client.js').Logger} [config.logger]
|
|
55
13
|
* Logger instance (optional)
|
|
56
14
|
*
|
|
57
15
|
* @example
|
|
58
16
|
* ```javascript
|
|
17
|
+
* const ROUTE_INTRO = '/intro/start';
|
|
18
|
+
* const KEY_INTRO_COMPLETED = 'intro-completed';
|
|
19
|
+
* const KEY_SCORE = 'score';
|
|
20
|
+
*
|
|
59
21
|
* const machine = new PageMachine({
|
|
60
|
-
* startPath:
|
|
61
|
-
* routes: [
|
|
22
|
+
* startPath: ROUTE_INTRO,
|
|
23
|
+
* routes: [ROUTE_INTRO, '/intro/profile'],
|
|
62
24
|
* initialData: {
|
|
63
|
-
*
|
|
25
|
+
* [KEY_INTRO_COMPLETED]: false,
|
|
26
|
+
* [KEY_SCORE]: 0
|
|
64
27
|
* }
|
|
65
28
|
* });
|
|
66
29
|
* ```
|
|
@@ -102,34 +65,53 @@ export default class PageMachine {
|
|
|
102
65
|
/**
|
|
103
66
|
* Set a data property value
|
|
104
67
|
*
|
|
105
|
-
*
|
|
68
|
+
* Automatically reactive - effects watching this key will re-run.
|
|
69
|
+
* Uses fine-grained reactivity, so only effects watching this specific
|
|
70
|
+
* key will be triggered.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} key - Property key (use KEY_ constant)
|
|
106
73
|
* @param {any} value - Property value
|
|
107
74
|
*
|
|
108
75
|
* @example
|
|
109
76
|
* ```javascript
|
|
110
|
-
*
|
|
111
|
-
*
|
|
77
|
+
* const KEY_HAS_STRONG_PROFILE = 'has-strong-profile';
|
|
78
|
+
* const KEY_PROFILE_SCORE = 'profile-score';
|
|
79
|
+
*
|
|
80
|
+
* machine.setData(KEY_HAS_STRONG_PROFILE, true);
|
|
81
|
+
* machine.setData(KEY_PROFILE_SCORE, 85);
|
|
112
82
|
* ```
|
|
113
83
|
*/
|
|
114
84
|
setData(key: string, value: any): void;
|
|
115
85
|
/**
|
|
116
86
|
* Get a data property value
|
|
117
87
|
*
|
|
118
|
-
*
|
|
88
|
+
* Automatically reactive - creates a dependency on this specific key.
|
|
89
|
+
* The effect will only re-run when THIS key changes, not when other
|
|
90
|
+
* keys change.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} key - Property key (use KEY_ constant)
|
|
119
93
|
*
|
|
120
94
|
* @returns {any} Property value or undefined
|
|
121
95
|
*
|
|
122
96
|
* @example
|
|
123
97
|
* ```javascript
|
|
124
|
-
* const
|
|
125
|
-
*
|
|
98
|
+
* const KEY_SCORE = 'score';
|
|
99
|
+
*
|
|
100
|
+
* // Reactive - re-runs only when KEY_SCORE changes
|
|
101
|
+
* $effect(() => {
|
|
102
|
+
* const score = machine.getData(KEY_SCORE);
|
|
103
|
+
* console.log('Score:', score);
|
|
104
|
+
* });
|
|
126
105
|
* ```
|
|
127
106
|
*/
|
|
128
107
|
getData(key: string): any;
|
|
129
108
|
/**
|
|
130
|
-
* Get all data properties
|
|
109
|
+
* Get all data properties as plain object
|
|
110
|
+
*
|
|
111
|
+
* Note: This returns a snapshot (plain object), not a reactive map.
|
|
112
|
+
* Use this for serialization or server sync, not for reactive tracking.
|
|
131
113
|
*
|
|
132
|
-
* @returns {Record<string, any>}
|
|
114
|
+
* @returns {Record<string, any>} Plain object with all data
|
|
133
115
|
*
|
|
134
116
|
* @example
|
|
135
117
|
* ```javascript
|
|
@@ -141,31 +123,89 @@ export default class PageMachine {
|
|
|
141
123
|
/**
|
|
142
124
|
* Update multiple data properties at once
|
|
143
125
|
*
|
|
126
|
+
* Each property update triggers fine-grained reactivity.
|
|
127
|
+
*
|
|
144
128
|
* @param {Record<string, any>} dataUpdates
|
|
145
|
-
* Object with key-value pairs
|
|
129
|
+
* Object with key-value pairs (use KEY_ constants for keys)
|
|
146
130
|
*
|
|
147
131
|
* @example
|
|
148
132
|
* ```javascript
|
|
133
|
+
* const KEY_HAS_STRONG_PROFILE = 'has-strong-profile';
|
|
134
|
+
* const KEY_PROFILE_SCORE = 'profile-score';
|
|
135
|
+
* const KEY_MATCHED_SECTOR = 'matched-sector';
|
|
136
|
+
*
|
|
149
137
|
* machine.updateData({
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
138
|
+
* [KEY_HAS_STRONG_PROFILE]: true,
|
|
139
|
+
* [KEY_PROFILE_SCORE]: 85,
|
|
140
|
+
* [KEY_MATCHED_SECTOR]: 'technology'
|
|
153
141
|
* });
|
|
154
142
|
* ```
|
|
155
143
|
*/
|
|
156
144
|
updateData(dataUpdates: Record<string, any>): void;
|
|
145
|
+
/**
|
|
146
|
+
* Delete a data property
|
|
147
|
+
*
|
|
148
|
+
* @param {string} key - Property key to delete (use KEY_ constant)
|
|
149
|
+
*
|
|
150
|
+
* @returns {boolean} True if the key existed and was deleted
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```javascript
|
|
154
|
+
* const KEY_TEMPORARY_FLAG = 'temporary-flag';
|
|
155
|
+
*
|
|
156
|
+
* machine.deleteData(KEY_TEMPORARY_FLAG);
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
deleteData(key: string): boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Check if data property exists
|
|
162
|
+
*
|
|
163
|
+
* @param {string} key - Property key to check (use KEY_ constant)
|
|
164
|
+
*
|
|
165
|
+
* @returns {boolean} True if the key exists
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```javascript
|
|
169
|
+
* const KEY_TUTORIAL_SEEN = 'tutorial-seen';
|
|
170
|
+
*
|
|
171
|
+
* if (machine.hasData(KEY_TUTORIAL_SEEN)) {
|
|
172
|
+
* // Skip tutorial
|
|
173
|
+
* }
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
hasData(key: string): boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Clear all data properties
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```javascript
|
|
182
|
+
* machine.clearData(); // Reset all game data
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
clearData(): void;
|
|
186
|
+
/**
|
|
187
|
+
* Get number of data properties
|
|
188
|
+
*
|
|
189
|
+
* @returns {number} Number of data entries
|
|
190
|
+
*/
|
|
191
|
+
get dataSize(): number;
|
|
157
192
|
/**
|
|
158
193
|
* Check if a route has been visited
|
|
159
194
|
*
|
|
195
|
+
* Automatically reactive - creates a dependency on the visited routes set.
|
|
196
|
+
*
|
|
160
197
|
* @param {string} route - Route path to check
|
|
161
198
|
*
|
|
162
199
|
* @returns {boolean} True if the route has been visited
|
|
163
200
|
*
|
|
164
201
|
* @example
|
|
165
202
|
* ```javascript
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
203
|
+
* // Reactive - re-runs when visited routes change
|
|
204
|
+
* $effect(() => {
|
|
205
|
+
* if (machine.hasVisited('/intro/profile')) {
|
|
206
|
+
* console.log('User has seen profile page');
|
|
207
|
+
* }
|
|
208
|
+
* });
|
|
169
209
|
* ```
|
|
170
210
|
*/
|
|
171
211
|
hasVisited(route: string): boolean;
|
|
@@ -183,16 +223,30 @@ export default class PageMachine {
|
|
|
183
223
|
*/
|
|
184
224
|
get hasVisitedStart(): boolean;
|
|
185
225
|
/**
|
|
186
|
-
* Get all visited routes
|
|
226
|
+
* Get all visited routes as array
|
|
227
|
+
*
|
|
228
|
+
* Note: Returns a snapshot (plain array), not reactive.
|
|
187
229
|
*
|
|
188
230
|
* @returns {string[]} Array of visited route paths
|
|
189
231
|
*/
|
|
190
232
|
getVisitedRoutes(): string[];
|
|
191
233
|
/**
|
|
192
234
|
* Reset visited routes tracking
|
|
193
|
-
*
|
|
235
|
+
*
|
|
236
|
+
* Clears all visited routes and marks only the current route as visited.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```javascript
|
|
240
|
+
* machine.resetVisitedRoutes(); // Reset progress tracking
|
|
241
|
+
* ```
|
|
194
242
|
*/
|
|
195
243
|
resetVisitedRoutes(): void;
|
|
244
|
+
/**
|
|
245
|
+
* Get number of visited routes
|
|
246
|
+
*
|
|
247
|
+
* @returns {number} Number of routes visited
|
|
248
|
+
*/
|
|
249
|
+
get visitedRoutesCount(): number;
|
|
196
250
|
/**
|
|
197
251
|
* Get the start path
|
|
198
252
|
*
|
|
@@ -7,14 +7,28 @@
|
|
|
7
7
|
* Features:
|
|
8
8
|
* - Current route tracking and synchronization
|
|
9
9
|
* - Start path management
|
|
10
|
-
* - Data properties for business/domain state
|
|
11
|
-
* - Visited routes tracking
|
|
10
|
+
* - Data properties for business/domain state (using SvelteMap)
|
|
11
|
+
* - Visited routes tracking (using SvelteSet)
|
|
12
|
+
* - Fine-grained reactivity without manual revision tracking
|
|
13
|
+
*
|
|
14
|
+
* Best practices:
|
|
15
|
+
* - Use constants for routes: `const ROUTE_INTRO = '/intro/start'`
|
|
16
|
+
* - Use KEY_ constants for data: `const KEY_SCORE = 'score'`
|
|
12
17
|
*
|
|
13
18
|
* Basic usage:
|
|
14
19
|
* ```javascript
|
|
20
|
+
* // Define constants
|
|
21
|
+
* const ROUTE_INTRO = '/intro/start';
|
|
22
|
+
* const KEY_SCORE = 'score';
|
|
23
|
+
* const KEY_TUTORIAL_SEEN = 'tutorial-seen';
|
|
24
|
+
*
|
|
15
25
|
* const machine = new PageMachine({
|
|
16
|
-
* startPath:
|
|
17
|
-
* routes: [
|
|
26
|
+
* startPath: ROUTE_INTRO,
|
|
27
|
+
* routes: [ROUTE_INTRO, '/intro/profile', '/intro/complete'],
|
|
28
|
+
* initialData: {
|
|
29
|
+
* [KEY_SCORE]: 0,
|
|
30
|
+
* [KEY_TUTORIAL_SEEN]: false
|
|
31
|
+
* }
|
|
18
32
|
* });
|
|
19
33
|
*
|
|
20
34
|
* // Sync machine with URL changes
|
|
@@ -22,10 +36,18 @@
|
|
|
22
36
|
* machine.syncFromPath($page.url.pathname);
|
|
23
37
|
* });
|
|
24
38
|
*
|
|
25
|
-
* // Check current route
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
39
|
+
* // Check current route (reactive)
|
|
40
|
+
* $effect(() => {
|
|
41
|
+
* if (machine.current === ROUTE_INTRO) {
|
|
42
|
+
* console.log('On intro page');
|
|
43
|
+
* }
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* // Access data (reactive, fine-grained)
|
|
47
|
+
* $effect(() => {
|
|
48
|
+
* const score = machine.getData(KEY_SCORE);
|
|
49
|
+
* console.log('Score changed:', score);
|
|
50
|
+
* });
|
|
29
51
|
* ```
|
|
30
52
|
*
|
|
31
53
|
* Animations and page-specific logic should use $effect in pages:
|
|
@@ -40,6 +62,8 @@
|
|
|
40
62
|
* });
|
|
41
63
|
* ```
|
|
42
64
|
*/
|
|
65
|
+
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
|
66
|
+
|
|
43
67
|
export default class PageMachine {
|
|
44
68
|
/**
|
|
45
69
|
* Logger instance
|
|
@@ -67,25 +91,18 @@ export default class PageMachine {
|
|
|
67
91
|
#routes = [];
|
|
68
92
|
|
|
69
93
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* @type {
|
|
94
|
+
* Reactive map for business/domain data
|
|
95
|
+
* Uses SvelteMap for fine-grained reactivity
|
|
96
|
+
* @type {SvelteMap<string, any>}
|
|
73
97
|
*/
|
|
74
|
-
#data
|
|
98
|
+
#data;
|
|
75
99
|
|
|
76
100
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* @type {
|
|
101
|
+
* Reactive set for visited routes
|
|
102
|
+
* Uses SvelteSet for automatic reactivity
|
|
103
|
+
* @type {SvelteSet<string>}
|
|
80
104
|
*/
|
|
81
|
-
|
|
82
|
-
#visitedRoutes = new Set();
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Revision counter for triggering reactivity
|
|
86
|
-
* @type {number}
|
|
87
|
-
*/
|
|
88
|
-
#revision = $state(0);
|
|
105
|
+
#visitedRoutes;
|
|
89
106
|
|
|
90
107
|
/**
|
|
91
108
|
* Constructor
|
|
@@ -96,17 +113,22 @@ export default class PageMachine {
|
|
|
96
113
|
* @param {string[]} [config.routes=[]]
|
|
97
114
|
* Optional list of valid routes (for validation/dev tools)
|
|
98
115
|
* @param {Record<string, any>} [config.initialData={}]
|
|
99
|
-
* Initial data properties (
|
|
116
|
+
* Initial data properties (use KEY_ constants for keys)
|
|
100
117
|
* @param {import('../../../logging/client.js').Logger} [config.logger]
|
|
101
118
|
* Logger instance (optional)
|
|
102
119
|
*
|
|
103
120
|
* @example
|
|
104
121
|
* ```javascript
|
|
122
|
+
* const ROUTE_INTRO = '/intro/start';
|
|
123
|
+
* const KEY_INTRO_COMPLETED = 'intro-completed';
|
|
124
|
+
* const KEY_SCORE = 'score';
|
|
125
|
+
*
|
|
105
126
|
* const machine = new PageMachine({
|
|
106
|
-
* startPath:
|
|
107
|
-
* routes: [
|
|
127
|
+
* startPath: ROUTE_INTRO,
|
|
128
|
+
* routes: [ROUTE_INTRO, '/intro/profile'],
|
|
108
129
|
* initialData: {
|
|
109
|
-
*
|
|
130
|
+
* [KEY_INTRO_COMPLETED]: false,
|
|
131
|
+
* [KEY_SCORE]: 0
|
|
110
132
|
* }
|
|
111
133
|
* });
|
|
112
134
|
* ```
|
|
@@ -119,9 +141,17 @@ export default class PageMachine {
|
|
|
119
141
|
this.logger = logger;
|
|
120
142
|
this.#startPath = startPath;
|
|
121
143
|
this.#routes = routes;
|
|
122
|
-
this.#data = initialData;
|
|
123
144
|
this.#current = startPath;
|
|
124
145
|
|
|
146
|
+
// Initialize reactive data structures
|
|
147
|
+
this.#data = new SvelteMap();
|
|
148
|
+
this.#visitedRoutes = new SvelteSet();
|
|
149
|
+
|
|
150
|
+
// Populate initial data
|
|
151
|
+
for (const [key, value] of Object.entries(initialData)) {
|
|
152
|
+
this.#data.set(key, value);
|
|
153
|
+
}
|
|
154
|
+
|
|
125
155
|
// Mark start path as visited
|
|
126
156
|
this.#visitedRoutes.add(startPath);
|
|
127
157
|
}
|
|
@@ -146,7 +176,6 @@ export default class PageMachine {
|
|
|
146
176
|
const oldRoute = this.#current;
|
|
147
177
|
this.#current = matchedRoute;
|
|
148
178
|
this.#visitedRoutes.add(matchedRoute);
|
|
149
|
-
this.#revision++;
|
|
150
179
|
|
|
151
180
|
this.logger?.debug(`Route changed: ${oldRoute} → ${matchedRoute}`);
|
|
152
181
|
|
|
@@ -208,43 +237,59 @@ export default class PageMachine {
|
|
|
208
237
|
/**
|
|
209
238
|
* Set a data property value
|
|
210
239
|
*
|
|
211
|
-
*
|
|
240
|
+
* Automatically reactive - effects watching this key will re-run.
|
|
241
|
+
* Uses fine-grained reactivity, so only effects watching this specific
|
|
242
|
+
* key will be triggered.
|
|
243
|
+
*
|
|
244
|
+
* @param {string} key - Property key (use KEY_ constant)
|
|
212
245
|
* @param {any} value - Property value
|
|
213
246
|
*
|
|
214
247
|
* @example
|
|
215
248
|
* ```javascript
|
|
216
|
-
*
|
|
217
|
-
*
|
|
249
|
+
* const KEY_HAS_STRONG_PROFILE = 'has-strong-profile';
|
|
250
|
+
* const KEY_PROFILE_SCORE = 'profile-score';
|
|
251
|
+
*
|
|
252
|
+
* machine.setData(KEY_HAS_STRONG_PROFILE, true);
|
|
253
|
+
* machine.setData(KEY_PROFILE_SCORE, 85);
|
|
218
254
|
* ```
|
|
219
255
|
*/
|
|
220
256
|
setData(key, value) {
|
|
221
|
-
this.#data
|
|
222
|
-
this.#revision++;
|
|
257
|
+
this.#data.set(key, value);
|
|
223
258
|
}
|
|
224
259
|
|
|
225
260
|
/**
|
|
226
261
|
* Get a data property value
|
|
227
262
|
*
|
|
228
|
-
*
|
|
263
|
+
* Automatically reactive - creates a dependency on this specific key.
|
|
264
|
+
* The effect will only re-run when THIS key changes, not when other
|
|
265
|
+
* keys change.
|
|
266
|
+
*
|
|
267
|
+
* @param {string} key - Property key (use KEY_ constant)
|
|
229
268
|
*
|
|
230
269
|
* @returns {any} Property value or undefined
|
|
231
270
|
*
|
|
232
271
|
* @example
|
|
233
272
|
* ```javascript
|
|
234
|
-
* const
|
|
235
|
-
*
|
|
273
|
+
* const KEY_SCORE = 'score';
|
|
274
|
+
*
|
|
275
|
+
* // Reactive - re-runs only when KEY_SCORE changes
|
|
276
|
+
* $effect(() => {
|
|
277
|
+
* const score = machine.getData(KEY_SCORE);
|
|
278
|
+
* console.log('Score:', score);
|
|
279
|
+
* });
|
|
236
280
|
* ```
|
|
237
281
|
*/
|
|
238
282
|
getData(key) {
|
|
239
|
-
|
|
240
|
-
this.#revision;
|
|
241
|
-
return this.#data[key];
|
|
283
|
+
return this.#data.get(key);
|
|
242
284
|
}
|
|
243
285
|
|
|
244
286
|
/**
|
|
245
|
-
* Get all data properties
|
|
287
|
+
* Get all data properties as plain object
|
|
246
288
|
*
|
|
247
|
-
*
|
|
289
|
+
* Note: This returns a snapshot (plain object), not a reactive map.
|
|
290
|
+
* Use this for serialization or server sync, not for reactive tracking.
|
|
291
|
+
*
|
|
292
|
+
* @returns {Record<string, any>} Plain object with all data
|
|
248
293
|
*
|
|
249
294
|
* @example
|
|
250
295
|
* ```javascript
|
|
@@ -253,31 +298,93 @@ export default class PageMachine {
|
|
|
253
298
|
* ```
|
|
254
299
|
*/
|
|
255
300
|
getAllData() {
|
|
256
|
-
|
|
257
|
-
this.#revision;
|
|
258
|
-
return { ...this.#data };
|
|
301
|
+
return Object.fromEntries(this.#data);
|
|
259
302
|
}
|
|
260
303
|
|
|
261
304
|
/**
|
|
262
305
|
* Update multiple data properties at once
|
|
263
306
|
*
|
|
307
|
+
* Each property update triggers fine-grained reactivity.
|
|
308
|
+
*
|
|
264
309
|
* @param {Record<string, any>} dataUpdates
|
|
265
|
-
* Object with key-value pairs
|
|
310
|
+
* Object with key-value pairs (use KEY_ constants for keys)
|
|
266
311
|
*
|
|
267
312
|
* @example
|
|
268
313
|
* ```javascript
|
|
314
|
+
* const KEY_HAS_STRONG_PROFILE = 'has-strong-profile';
|
|
315
|
+
* const KEY_PROFILE_SCORE = 'profile-score';
|
|
316
|
+
* const KEY_MATCHED_SECTOR = 'matched-sector';
|
|
317
|
+
*
|
|
269
318
|
* machine.updateData({
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
319
|
+
* [KEY_HAS_STRONG_PROFILE]: true,
|
|
320
|
+
* [KEY_PROFILE_SCORE]: 85,
|
|
321
|
+
* [KEY_MATCHED_SECTOR]: 'technology'
|
|
273
322
|
* });
|
|
274
323
|
* ```
|
|
275
324
|
*/
|
|
276
325
|
updateData(dataUpdates) {
|
|
277
326
|
for (const [key, value] of Object.entries(dataUpdates)) {
|
|
278
|
-
this.#data
|
|
327
|
+
this.#data.set(key, value);
|
|
279
328
|
}
|
|
280
|
-
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Delete a data property
|
|
333
|
+
*
|
|
334
|
+
* @param {string} key - Property key to delete (use KEY_ constant)
|
|
335
|
+
*
|
|
336
|
+
* @returns {boolean} True if the key existed and was deleted
|
|
337
|
+
*
|
|
338
|
+
* @example
|
|
339
|
+
* ```javascript
|
|
340
|
+
* const KEY_TEMPORARY_FLAG = 'temporary-flag';
|
|
341
|
+
*
|
|
342
|
+
* machine.deleteData(KEY_TEMPORARY_FLAG);
|
|
343
|
+
* ```
|
|
344
|
+
*/
|
|
345
|
+
deleteData(key) {
|
|
346
|
+
return this.#data.delete(key);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Check if data property exists
|
|
351
|
+
*
|
|
352
|
+
* @param {string} key - Property key to check (use KEY_ constant)
|
|
353
|
+
*
|
|
354
|
+
* @returns {boolean} True if the key exists
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* ```javascript
|
|
358
|
+
* const KEY_TUTORIAL_SEEN = 'tutorial-seen';
|
|
359
|
+
*
|
|
360
|
+
* if (machine.hasData(KEY_TUTORIAL_SEEN)) {
|
|
361
|
+
* // Skip tutorial
|
|
362
|
+
* }
|
|
363
|
+
* ```
|
|
364
|
+
*/
|
|
365
|
+
hasData(key) {
|
|
366
|
+
return this.#data.has(key);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Clear all data properties
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```javascript
|
|
374
|
+
* machine.clearData(); // Reset all game data
|
|
375
|
+
* ```
|
|
376
|
+
*/
|
|
377
|
+
clearData() {
|
|
378
|
+
this.#data.clear();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Get number of data properties
|
|
383
|
+
*
|
|
384
|
+
* @returns {number} Number of data entries
|
|
385
|
+
*/
|
|
386
|
+
get dataSize() {
|
|
387
|
+
return this.#data.size;
|
|
281
388
|
}
|
|
282
389
|
|
|
283
390
|
/* ===== Visited Routes Tracking ===== */
|
|
@@ -285,20 +392,23 @@ export default class PageMachine {
|
|
|
285
392
|
/**
|
|
286
393
|
* Check if a route has been visited
|
|
287
394
|
*
|
|
395
|
+
* Automatically reactive - creates a dependency on the visited routes set.
|
|
396
|
+
*
|
|
288
397
|
* @param {string} route - Route path to check
|
|
289
398
|
*
|
|
290
399
|
* @returns {boolean} True if the route has been visited
|
|
291
400
|
*
|
|
292
401
|
* @example
|
|
293
402
|
* ```javascript
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
403
|
+
* // Reactive - re-runs when visited routes change
|
|
404
|
+
* $effect(() => {
|
|
405
|
+
* if (machine.hasVisited('/intro/profile')) {
|
|
406
|
+
* console.log('User has seen profile page');
|
|
407
|
+
* }
|
|
408
|
+
* });
|
|
297
409
|
* ```
|
|
298
410
|
*/
|
|
299
411
|
hasVisited(route) {
|
|
300
|
-
// Access revision to ensure reactivity
|
|
301
|
-
this.#revision;
|
|
302
412
|
return this.#visitedRoutes.has(route);
|
|
303
413
|
}
|
|
304
414
|
|
|
@@ -319,24 +429,38 @@ export default class PageMachine {
|
|
|
319
429
|
}
|
|
320
430
|
|
|
321
431
|
/**
|
|
322
|
-
* Get all visited routes
|
|
432
|
+
* Get all visited routes as array
|
|
433
|
+
*
|
|
434
|
+
* Note: Returns a snapshot (plain array), not reactive.
|
|
323
435
|
*
|
|
324
436
|
* @returns {string[]} Array of visited route paths
|
|
325
437
|
*/
|
|
326
438
|
getVisitedRoutes() {
|
|
327
|
-
// Access revision to ensure reactivity
|
|
328
|
-
this.#revision;
|
|
329
439
|
return Array.from(this.#visitedRoutes);
|
|
330
440
|
}
|
|
331
441
|
|
|
332
442
|
/**
|
|
333
443
|
* Reset visited routes tracking
|
|
334
|
-
*
|
|
444
|
+
*
|
|
445
|
+
* Clears all visited routes and marks only the current route as visited.
|
|
446
|
+
*
|
|
447
|
+
* @example
|
|
448
|
+
* ```javascript
|
|
449
|
+
* machine.resetVisitedRoutes(); // Reset progress tracking
|
|
450
|
+
* ```
|
|
335
451
|
*/
|
|
336
452
|
resetVisitedRoutes() {
|
|
337
453
|
this.#visitedRoutes.clear();
|
|
338
454
|
this.#visitedRoutes.add(this.#current);
|
|
339
|
-
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Get number of visited routes
|
|
459
|
+
*
|
|
460
|
+
* @returns {number} Number of routes visited
|
|
461
|
+
*/
|
|
462
|
+
get visitedRoutesCount() {
|
|
463
|
+
return this.#visitedRoutes.size;
|
|
340
464
|
}
|
|
341
465
|
|
|
342
466
|
/* ===== Start Path Methods ===== */
|
|
@@ -91,7 +91,12 @@ export class PuzzleState extends PageMachine {
|
|
|
91
91
|
ROUTE_LEVEL1,
|
|
92
92
|
ROUTE_LEVEL2,
|
|
93
93
|
ROUTE_COMPLETE
|
|
94
|
-
]
|
|
94
|
+
],
|
|
95
|
+
initialData: {
|
|
96
|
+
[KEY_TUTORIAL_SEEN]: false,
|
|
97
|
+
[KEY_HIGHEST_LEVEL]: 1,
|
|
98
|
+
[KEY_DIFFICULTY]: 'normal'
|
|
99
|
+
}
|
|
95
100
|
});
|
|
96
101
|
|
|
97
102
|
this.#gameLogic = new PuzzleGameLogic();
|
|
@@ -293,15 +298,26 @@ Use PageMachine's data properties for **persistent settings and progress**:
|
|
|
293
298
|
- ✅ Settings that survive page navigation
|
|
294
299
|
- ✅ Data that might be saved to server
|
|
295
300
|
|
|
301
|
+
**IMPORTANT**: Use KEY_ constants for data keys to get:
|
|
302
|
+
- ✅ Autocomplete support
|
|
303
|
+
- ✅ Typo prevention
|
|
304
|
+
- ✅ Easy refactoring
|
|
305
|
+
- ✅ Self-documenting code
|
|
306
|
+
|
|
296
307
|
```javascript
|
|
308
|
+
// Define constants (at top of file)
|
|
297
309
|
const KEY_TUTORIAL_SEEN = 'tutorial-seen';
|
|
298
310
|
const KEY_DIFFICULTY = 'difficulty';
|
|
299
311
|
const KEY_HIGHEST_LEVEL = 'highest-level';
|
|
300
312
|
|
|
301
|
-
//
|
|
302
|
-
pageMachine.setData(KEY_TUTORIAL_SEEN, true);
|
|
303
|
-
pageMachine.setData(KEY_DIFFICULTY, 'hard');
|
|
304
|
-
pageMachine.setData(KEY_HIGHEST_LEVEL, 5);
|
|
313
|
+
// Use constants (not strings!)
|
|
314
|
+
pageMachine.setData(KEY_TUTORIAL_SEEN, true); // ✅ Good
|
|
315
|
+
pageMachine.setData(KEY_DIFFICULTY, 'hard'); // ✅ Good
|
|
316
|
+
pageMachine.setData(KEY_HIGHEST_LEVEL, 5); // ✅ Good
|
|
317
|
+
|
|
318
|
+
// DON'T use magic strings
|
|
319
|
+
pageMachine.setData('tutorial-seen', true); // ❌ Avoid
|
|
320
|
+
pageMachine.setData('TUTORIAL_SEEN', true); // ❌ Avoid
|
|
305
321
|
```
|
|
306
322
|
|
|
307
323
|
### When to use GameLogic with `$state`
|