@hkdigital/lib-core 0.5.45 → 0.5.47
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/state/context/README.md +58 -132
- package/dist/state/context.d.ts +0 -1
- package/dist/state/context.js +0 -1
- package/dist/state/machines/page-machine/PageMachine.svelte.d.ts +165 -35
- package/dist/state/machines/page-machine/PageMachine.svelte.js +336 -38
- package/dist/state/machines/page-machine/README.md +209 -63
- package/dist/ui/components/game-box/GameBox.svelte +1 -0
- package/dist/ui/components/game-box/ScaledContainer.svelte +2 -1
- package/dist/util/sveltekit/navigation.d.ts +7 -0
- package/dist/util/sveltekit/navigation.js +12 -0
- package/dist/util/sveltekit.d.ts +1 -0
- package/dist/util/sveltekit.js +1 -0
- package/dist/util/time/index.js +19 -8
- package/package.json +1 -1
- package/dist/state/context/RouteStateContext.svelte.d.ts +0 -44
- package/dist/state/context/RouteStateContext.svelte.js +0 -119
|
@@ -1,68 +1,72 @@
|
|
|
1
|
-
#
|
|
1
|
+
# State Context Utilities
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Helper functions for managing Svelte context in route-level state containers.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## defineStateContext
|
|
6
6
|
|
|
7
|
+
Creates context helper functions for a state container class.
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
import { defineStateContext } from '@hkdigital/lib-core/state/context.js';
|
|
11
|
+
|
|
12
|
+
class PuzzleState {
|
|
13
|
+
#pageMachine;
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
this.#pageMachine = new PuzzlePageMachine();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get pageMachine() {
|
|
20
|
+
return this.#pageMachine;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Export helper functions
|
|
25
|
+
export const [createOrGetPuzzleState, createPuzzleState, getPuzzleState] =
|
|
26
|
+
defineStateContext(PuzzleState);
|
|
7
27
|
```
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
│ - IMPORTANT: Syncs URL with pageMachine.syncFromPath() │
|
|
21
|
-
│ - Optional: Calls validateAndRedirect() for protection │
|
|
22
|
-
└────────────────┬────────────────────────────────────────┘
|
|
23
|
-
│
|
|
24
|
-
│ Context available to children
|
|
25
|
-
│
|
|
26
|
-
┌────────┴─────────┬────────────────┐
|
|
27
|
-
▼ ▼ ▼
|
|
28
|
-
┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
29
|
-
│ +page │ │ +page │ │ Component│
|
|
30
|
-
│ .svelte │ │ .svelte │ │ │
|
|
31
|
-
└──────────┘ └──────────┘ └──────────┘
|
|
32
|
-
Gets state via getPuzzleState()
|
|
28
|
+
|
|
29
|
+
## Helper Functions
|
|
30
|
+
|
|
31
|
+
The `defineStateContext` helper creates three functions:
|
|
32
|
+
|
|
33
|
+
### createOrGetPuzzleState()
|
|
34
|
+
|
|
35
|
+
Get existing instance or create new one. Use in `+layout.svelte`.
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
// routes/puzzle/+layout.svelte
|
|
39
|
+
const state = createOrGetPuzzleState();
|
|
33
40
|
```
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
### createPuzzleState()
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
2. **Apply enforceStartPath** - Control navigation flow (users must visit
|
|
39
|
-
start path before accessing subroutes)
|
|
40
|
-
3. **Provide validateAndRedirect** - Route protection via layout
|
|
44
|
+
Force create new instance (discards existing).
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
```javascript
|
|
47
|
+
const state = createPuzzleState();
|
|
48
|
+
```
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
- Persist state across navigation within the same route group
|
|
46
|
-
- Lifecycle methods for setup/teardown (preload, reset)
|
|
50
|
+
### getPuzzleState()
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
Get existing instance. Throws error if not found. Use in pages/components.
|
|
49
53
|
|
|
50
|
-
|
|
54
|
+
```javascript
|
|
55
|
+
// routes/puzzle/level1/+page.svelte
|
|
56
|
+
const state = getPuzzleState();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Complete Example
|
|
51
60
|
|
|
52
61
|
```javascript
|
|
53
62
|
// routes/puzzle/puzzle.state.svelte.js
|
|
54
63
|
import { defineStateContext } from '@hkdigital/lib-core/state/context.js';
|
|
55
|
-
import { RouteStateContext } from '$lib/state/context.js';
|
|
56
64
|
import PuzzlePageMachine from './puzzle.machine.svelte.js';
|
|
57
65
|
|
|
58
|
-
export class PuzzleState
|
|
66
|
+
export class PuzzleState {
|
|
59
67
|
#pageMachine;
|
|
60
68
|
|
|
61
69
|
constructor() {
|
|
62
|
-
super({
|
|
63
|
-
startPath: '/puzzle',
|
|
64
|
-
enforceStartPath: true // Optional: enforce route protection
|
|
65
|
-
});
|
|
66
70
|
this.#pageMachine = new PuzzlePageMachine();
|
|
67
71
|
}
|
|
68
72
|
|
|
@@ -81,34 +85,26 @@ export class PuzzleState extends RouteStateContext {
|
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
reset() {
|
|
84
|
-
|
|
88
|
+
this.#pageMachine = new PuzzlePageMachine();
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
91
|
|
|
88
|
-
// Export helper functions
|
|
89
92
|
export const [createOrGetPuzzleState, createPuzzleState, getPuzzleState] =
|
|
90
93
|
defineStateContext(PuzzleState);
|
|
91
94
|
```
|
|
92
95
|
|
|
93
|
-
### 2. Provide context in layout
|
|
94
|
-
|
|
95
96
|
```svelte
|
|
96
97
|
<!-- routes/puzzle/+layout.svelte -->
|
|
97
98
|
<script>
|
|
98
99
|
import { page } from '$app/stores';
|
|
99
100
|
import { createOrGetPuzzleState } from './puzzle.state.svelte.js';
|
|
100
101
|
|
|
101
|
-
// Create or get existing state container
|
|
102
102
|
const puzzleState = createOrGetPuzzleState();
|
|
103
|
+
const pageMachine = puzzleState.pageMachine;
|
|
103
104
|
|
|
104
105
|
// IMPORTANT: Sync URL with PageMachine state
|
|
105
106
|
$effect(() => {
|
|
106
|
-
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// Optional: Enforce start path (redirect if user skips intro)
|
|
110
|
-
$effect(() => {
|
|
111
|
-
puzzleState.validateAndRedirect($page.url.pathname);
|
|
107
|
+
pageMachine.syncFromPath($page.url.pathname);
|
|
112
108
|
});
|
|
113
109
|
|
|
114
110
|
// Optional: Preload assets
|
|
@@ -120,8 +116,6 @@ export const [createOrGetPuzzleState, createPuzzleState, getPuzzleState] =
|
|
|
120
116
|
<slot />
|
|
121
117
|
```
|
|
122
118
|
|
|
123
|
-
### 3. Consume context in pages
|
|
124
|
-
|
|
125
119
|
```svelte
|
|
126
120
|
<!-- routes/puzzle/level1/+page.svelte -->
|
|
127
121
|
<script>
|
|
@@ -134,81 +128,12 @@ export const [createOrGetPuzzleState, createPuzzleState, getPuzzleState] =
|
|
|
134
128
|
<div>Current state: {pageMachine.current}</div>
|
|
135
129
|
```
|
|
136
130
|
|
|
137
|
-
##
|
|
138
|
-
|
|
139
|
-
The `defineStateContext` helper creates three functions:
|
|
140
|
-
|
|
141
|
-
```javascript
|
|
142
|
-
// Get existing or create new (use in layout)
|
|
143
|
-
const state = createOrGetPuzzleState();
|
|
144
|
-
|
|
145
|
-
// Force create new instance
|
|
146
|
-
const state = createPuzzleState();
|
|
147
|
-
|
|
148
|
-
// Get existing (throws if not found, use in pages/components)
|
|
149
|
-
const state = getPuzzleState();
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
## Constructor Options
|
|
153
|
-
|
|
154
|
-
```javascript
|
|
155
|
-
constructor({ startPath, enforceStartPath })
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
- `startPath` **(required)** - The start path for this route
|
|
159
|
-
(e.g., `/puzzle`)
|
|
160
|
-
- `enforceStartPath` **(optional, default: false)** - If true, users must
|
|
161
|
-
visit the start path before accessing subroutes
|
|
162
|
-
|
|
163
|
-
## validateAndRedirect Method
|
|
164
|
-
|
|
165
|
-
Automatically redirects users if they try to access subroutes before visiting
|
|
166
|
-
the start path.
|
|
167
|
-
|
|
168
|
-
**How it works:**
|
|
169
|
-
- If `enforceStartPath: true` is set in constructor
|
|
170
|
-
- User tries to access a subroute (e.g., `/puzzle/level2`)
|
|
171
|
-
- But hasn't visited the start path yet (`/puzzle`)
|
|
172
|
-
- → Automatically redirects to start path
|
|
173
|
-
|
|
174
|
-
**Example use case:** Puzzle game where users must see the intro before
|
|
175
|
-
accessing puzzle levels.
|
|
176
|
-
|
|
177
|
-
```javascript
|
|
178
|
-
// In state constructor
|
|
179
|
-
export class PuzzleState extends RouteStateContext {
|
|
180
|
-
constructor() {
|
|
181
|
-
super({
|
|
182
|
-
startPath: '/puzzle',
|
|
183
|
-
enforceStartPath: true
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
```svelte
|
|
190
|
-
<!-- In +layout.svelte -->
|
|
191
|
-
<script>
|
|
192
|
-
import { page } from '$app/stores';
|
|
193
|
-
|
|
194
|
-
const puzzleState = createOrGetPuzzleState();
|
|
195
|
-
|
|
196
|
-
// Enforce route protection
|
|
197
|
-
$effect(() => {
|
|
198
|
-
puzzleState.validateAndRedirect($page.url.pathname);
|
|
199
|
-
});
|
|
200
|
-
</script>
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
**Result:** If user navigates directly to `/puzzle/level2`, they'll be
|
|
204
|
-
redirected to `/puzzle` first. After visiting `/puzzle`, they can freely
|
|
205
|
-
navigate to any subroute.
|
|
131
|
+
## Key Features
|
|
206
132
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
```
|
|
133
|
+
- Share state between layout and pages without prop drilling
|
|
134
|
+
- Persist state across navigation within the same route group
|
|
135
|
+
- Lifecycle methods for setup/teardown (preload, reset)
|
|
136
|
+
- PageMachine integration for route/state management
|
|
212
137
|
|
|
213
138
|
## Separation of Concerns
|
|
214
139
|
|
|
@@ -222,5 +147,6 @@ puzzleState.validateAndRedirect($page.url.pathname, '/puzzle/welcome');
|
|
|
222
147
|
**PageMachine** = Page/view state ONLY (SINGLE responsibility)
|
|
223
148
|
- Current page state
|
|
224
149
|
- Route mapping
|
|
150
|
+
- Start path management
|
|
225
151
|
- Visited states
|
|
226
152
|
- Computed properties for state checks
|
package/dist/state/context.d.ts
CHANGED
package/dist/state/context.js
CHANGED
|
@@ -7,12 +7,20 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Features:
|
|
9
9
|
* - State-to-route mapping and sync
|
|
10
|
+
* - Start path management
|
|
10
11
|
* - Data properties for business/domain state
|
|
11
12
|
* - Visited states tracking
|
|
13
|
+
* - onEnter hooks with abort/complete handlers for animations
|
|
12
14
|
*
|
|
13
15
|
* Basic usage:
|
|
14
16
|
* ```javascript
|
|
15
|
-
* const machine =
|
|
17
|
+
* const machine = new PageMachine({
|
|
18
|
+
* startPath: '/intro/start',
|
|
19
|
+
* routeMap: {
|
|
20
|
+
* [STATE_START]: '/intro/start',
|
|
21
|
+
* [STATE_PROFILE]: '/intro/profile'
|
|
22
|
+
* }
|
|
23
|
+
* });
|
|
16
24
|
*
|
|
17
25
|
* // Sync machine state with URL changes
|
|
18
26
|
* $effect(() => {
|
|
@@ -20,54 +28,77 @@
|
|
|
20
28
|
* });
|
|
21
29
|
* ```
|
|
22
30
|
*
|
|
23
|
-
* With
|
|
31
|
+
* With onEnter hooks (for animations):
|
|
24
32
|
* ```javascript
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
33
|
+
* const machine = new PageMachine({
|
|
34
|
+
* startPath: '/game/animate',
|
|
35
|
+
* routeMap: {
|
|
36
|
+
* [STATE_ANIMATE]: '/game/animate',
|
|
37
|
+
* [STATE_PLAY]: '/game/play'
|
|
38
|
+
* },
|
|
39
|
+
* onEnterHooks: {
|
|
40
|
+
* [STATE_ANIMATE]: (done) => {
|
|
41
|
+
* const animation = playAnimation(1000);
|
|
42
|
+
* animation.finished.then(() => done(STATE_PLAY));
|
|
32
43
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
44
|
+
* return {
|
|
45
|
+
* abort: () => animation.cancel(),
|
|
46
|
+
* complete: () => animation.finish()
|
|
47
|
+
* };
|
|
48
|
+
* }
|
|
49
|
+
* }
|
|
50
|
+
* });
|
|
37
51
|
*
|
|
38
|
-
* //
|
|
39
|
-
* machine.
|
|
52
|
+
* // Fast-forward animation
|
|
53
|
+
* machine.completeTransitions();
|
|
40
54
|
*
|
|
41
|
-
* //
|
|
42
|
-
*
|
|
43
|
-
* // User has seen profile page before
|
|
44
|
-
* }
|
|
55
|
+
* // Cancel animation
|
|
56
|
+
* machine.abortTransitions();
|
|
45
57
|
* ```
|
|
46
58
|
*/
|
|
47
59
|
export default class PageMachine {
|
|
48
60
|
/**
|
|
49
61
|
* Constructor
|
|
50
62
|
*
|
|
51
|
-
* @param {
|
|
52
|
-
* @param {
|
|
53
|
-
*
|
|
63
|
+
* @param {Object} config - Configuration object
|
|
64
|
+
* @param {string} config.startPath
|
|
65
|
+
* Start path for this route group (e.g., '/game/play')
|
|
66
|
+
* @param {Record<string, string>} [config.routeMap={}]
|
|
67
|
+
* Map of states to route paths
|
|
68
|
+
* @param {Record<string, any>} [config.initialData={}]
|
|
69
|
+
* Initial data properties (from server)
|
|
70
|
+
* @param {Record<string, Function>} [config.onEnterHooks={}]
|
|
71
|
+
* Map of states to onEnter hook functions
|
|
54
72
|
*
|
|
55
73
|
* @example
|
|
56
74
|
* ```javascript
|
|
57
|
-
* const
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
75
|
+
* const machine = new PageMachine({
|
|
76
|
+
* startPath: '/intro/start',
|
|
77
|
+
* routeMap: {
|
|
78
|
+
* [STATE_START]: '/intro/start',
|
|
79
|
+
* [STATE_ANIMATE]: '/intro/animate'
|
|
80
|
+
* },
|
|
81
|
+
* initialData: {
|
|
82
|
+
* INTRO_COMPLETED: false
|
|
83
|
+
* },
|
|
84
|
+
* onEnterHooks: {
|
|
85
|
+
* [STATE_ANIMATE]: (done) => {
|
|
86
|
+
* setTimeout(() => done(STATE_START), 1000);
|
|
87
|
+
* return {
|
|
88
|
+
* abort: () => clearTimeout(...),
|
|
89
|
+
* complete: () => done(STATE_START)
|
|
90
|
+
* };
|
|
91
|
+
* }
|
|
92
|
+
* }
|
|
93
|
+
* });
|
|
68
94
|
* ```
|
|
69
95
|
*/
|
|
70
|
-
constructor(
|
|
96
|
+
constructor({ startPath, routeMap, initialData, onEnterHooks }: {
|
|
97
|
+
startPath: string;
|
|
98
|
+
routeMap?: Record<string, string> | undefined;
|
|
99
|
+
initialData?: Record<string, any> | undefined;
|
|
100
|
+
onEnterHooks?: Record<string, Function> | undefined;
|
|
101
|
+
});
|
|
71
102
|
/**
|
|
72
103
|
* Synchronize machine state with URL path
|
|
73
104
|
*
|
|
@@ -81,10 +112,11 @@ export default class PageMachine {
|
|
|
81
112
|
syncFromPath(currentPath: string): boolean;
|
|
82
113
|
/**
|
|
83
114
|
* Set the current state directly
|
|
115
|
+
* Handles onEnter hooks and auto-transitions
|
|
84
116
|
*
|
|
85
117
|
* @param {string} newState - Target state
|
|
86
118
|
*/
|
|
87
|
-
setState(newState: string): void
|
|
119
|
+
setState(newState: string): Promise<void>;
|
|
88
120
|
/**
|
|
89
121
|
* Get route path for a given state
|
|
90
122
|
*
|
|
@@ -191,5 +223,103 @@ export default class PageMachine {
|
|
|
191
223
|
* Useful for testing or resetting experience
|
|
192
224
|
*/
|
|
193
225
|
resetVisitedStates(): void;
|
|
226
|
+
/**
|
|
227
|
+
* Get the start path
|
|
228
|
+
*
|
|
229
|
+
* @returns {string} Start path
|
|
230
|
+
*/
|
|
231
|
+
get startPath(): string;
|
|
232
|
+
/**
|
|
233
|
+
* Get the start state
|
|
234
|
+
*
|
|
235
|
+
* @returns {string} Start state name
|
|
236
|
+
*/
|
|
237
|
+
get startState(): string;
|
|
238
|
+
/**
|
|
239
|
+
* Check if the supplied path matches the start path
|
|
240
|
+
*
|
|
241
|
+
* @param {string} path - Path to check
|
|
242
|
+
*
|
|
243
|
+
* @returns {boolean} True if path matches start path
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```javascript
|
|
247
|
+
* if (machine.isStartPath('/game/play')) {
|
|
248
|
+
* // User is on the start page
|
|
249
|
+
* }
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
isStartPath(path: string): boolean;
|
|
253
|
+
/**
|
|
254
|
+
* Check if currently on the start state
|
|
255
|
+
*
|
|
256
|
+
* @returns {boolean} True if current state is the start state
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* ```javascript
|
|
260
|
+
* if (machine.isOnStartState) {
|
|
261
|
+
* // Show onboarding
|
|
262
|
+
* }
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
get isOnStartState(): boolean;
|
|
266
|
+
/**
|
|
267
|
+
* Navigate to the start path
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```javascript
|
|
271
|
+
* // Redirect user to start
|
|
272
|
+
* machine.redirectToStartPath();
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
redirectToStartPath(): void;
|
|
276
|
+
/**
|
|
277
|
+
* Abort current state's transitions
|
|
278
|
+
* Cancels animations/operations immediately (incomplete state)
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```javascript
|
|
282
|
+
* // User clicks "Cancel" button
|
|
283
|
+
* machine.abortTransitions();
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
abortTransitions(): void;
|
|
287
|
+
/**
|
|
288
|
+
* Complete current state's transitions immediately
|
|
289
|
+
* Fast-forwards animations/operations to completion (complete state)
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```javascript
|
|
293
|
+
* // User clicks "Skip" or "Next" button
|
|
294
|
+
* machine.completeTransitions();
|
|
295
|
+
* ```
|
|
296
|
+
*/
|
|
297
|
+
completeTransitions(): void;
|
|
298
|
+
/**
|
|
299
|
+
* Check if current state has transitions that can be completed
|
|
300
|
+
*
|
|
301
|
+
* @returns {boolean} True if completeTransitions() can be called
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```svelte
|
|
305
|
+
* {#if machine.canCompleteTransitions}
|
|
306
|
+
* <button onclick={() => machine.completeTransitions()}>Skip</button>
|
|
307
|
+
* {/if}
|
|
308
|
+
* ```
|
|
309
|
+
*/
|
|
310
|
+
get canCompleteTransitions(): boolean;
|
|
311
|
+
/**
|
|
312
|
+
* Check if current state has transitions that can be aborted
|
|
313
|
+
*
|
|
314
|
+
* @returns {boolean} True if abortTransitions() can be called
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```svelte
|
|
318
|
+
* {#if machine.canAbortTransitions}
|
|
319
|
+
* <button onclick={() => machine.abortTransitions()}>Cancel</button>
|
|
320
|
+
* {/if}
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
get canAbortTransitions(): boolean;
|
|
194
324
|
#private;
|
|
195
325
|
}
|