@hkdigital/lib-core 0.4.23 → 0.4.25

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.
Files changed (42) hide show
  1. package/dist/logging/internal/adapters/pino.d.ts +7 -3
  2. package/dist/logging/internal/adapters/pino.js +200 -67
  3. package/dist/logging/internal/transports/pretty-transport.d.ts +17 -0
  4. package/dist/logging/internal/transports/pretty-transport.js +104 -0
  5. package/dist/logging/internal/transports/test-transport.d.ts +19 -0
  6. package/dist/logging/internal/transports/test-transport.js +79 -0
  7. package/dist/network/loaders/audio/AudioScene.svelte.js +2 -2
  8. package/dist/network/loaders/image/ImageScene.svelte.js +18 -28
  9. package/dist/network/states/NetworkLoader.svelte.d.ts +7 -1
  10. package/dist/network/states/NetworkLoader.svelte.js +17 -4
  11. package/dist/services/README.md +23 -0
  12. package/dist/services/service-base/ServiceBase.d.ts +12 -8
  13. package/dist/services/service-base/ServiceBase.js +8 -6
  14. package/dist/state/classes.d.ts +0 -2
  15. package/dist/state/classes.js +0 -2
  16. package/dist/state/{classes → machines}/finite-state-machine/FiniteStateMachine.svelte.d.ts +13 -7
  17. package/dist/state/machines/finite-state-machine/FiniteStateMachine.svelte.js +181 -0
  18. package/dist/state/machines/finite-state-machine/README.md +547 -0
  19. package/dist/state/machines/finite-state-machine/constants.d.ts +13 -0
  20. package/dist/state/machines/finite-state-machine/constants.js +15 -0
  21. package/dist/state/{classes → machines}/finite-state-machine/index.d.ts +2 -1
  22. package/dist/state/{classes → machines}/finite-state-machine/index.js +2 -1
  23. package/dist/state/machines/finite-state-machine/typedef.d.ts +29 -0
  24. package/dist/state/machines/finite-state-machine/typedef.js +28 -0
  25. package/dist/state/machines/loading-state-machine/LoadingStateMachine.svelte.d.ts +22 -0
  26. package/dist/state/{classes → machines}/loading-state-machine/LoadingStateMachine.svelte.js +34 -29
  27. package/dist/state/machines/loading-state-machine/README.md +592 -0
  28. package/dist/state/{classes → machines}/loading-state-machine/constants.d.ts +2 -0
  29. package/dist/state/{classes → machines}/loading-state-machine/constants.js +2 -0
  30. package/dist/state/machines/typedef.d.ts +1 -0
  31. package/dist/state/machines/typedef.js +1 -0
  32. package/dist/state/machines.d.ts +2 -0
  33. package/dist/state/machines.js +2 -0
  34. package/dist/state/typedef.d.ts +1 -0
  35. package/dist/state/typedef.js +1 -0
  36. package/dist/ui/components/game-box/README.md +245 -0
  37. package/package.json +1 -1
  38. package/dist/logging/internal/adapters/pino.js__ +0 -260
  39. package/dist/state/classes/finite-state-machine/FiniteStateMachine.svelte.js +0 -133
  40. package/dist/state/classes/loading-state-machine/LoadingStateMachine.svelte.d.ts +0 -12
  41. /package/dist/state/{classes → machines}/loading-state-machine/index.d.ts +0 -0
  42. /package/dist/state/{classes → machines}/loading-state-machine/index.js +0 -0
@@ -5,6 +5,7 @@ export const STATE_LOADED = 'loaded';
5
5
 
6
6
  export const STATE_CANCELLED = 'cancelled';
7
7
  export const STATE_ERROR = 'error';
8
+ export const STATE_TIMEOUT = 'timeout';
8
9
 
9
10
  // > Signals
10
11
 
@@ -14,3 +15,4 @@ export const CANCEL = 'cancel';
14
15
  export const ERROR = 'error';
15
16
  export const LOADED = 'loaded';
16
17
  export const UNLOAD = 'unload';
18
+ export const TIMEOUT = 'timeout';
@@ -0,0 +1 @@
1
+ export * from "./finite-state-machine/typedef.js";
@@ -0,0 +1 @@
1
+ export * from './finite-state-machine/typedef.js';
@@ -0,0 +1,2 @@
1
+ export * from "./machines/finite-state-machine/index.js";
2
+ export * from "./machines/loading-state-machine/index.js";
@@ -0,0 +1,2 @@
1
+ export * from './machines/finite-state-machine/index.js';
2
+ export * from './machines/loading-state-machine/index.js';
@@ -1,3 +1,4 @@
1
1
  export * from "./context/typedef.js";
2
+ export * from "./machines/typedef.js";
2
3
  declare const _default: {};
3
4
  export default _default;
@@ -1,3 +1,4 @@
1
1
  export * from './context/typedef.js';
2
+ export * from './machines/typedef.js';
2
3
 
3
4
  export default {};
@@ -0,0 +1,245 @@
1
+ # GameBox Component
2
+
3
+ A responsive container component designed for creating games and fullscreen
4
+ applications with adaptive scaling, orientation handling, and mobile PWA
5
+ support.
6
+
7
+ ## Features
8
+
9
+ - **Responsive Layout**: Automatically adapts to landscape and portrait
10
+ orientations
11
+ - **Aspect Ratio Control**: Configurable aspect ratios for different
12
+ orientations
13
+ - **Mobile PWA Support**: Handles iOS Safari PWA quirks and fullscreen modes
14
+ - **Dynamic Scaling**: Optional design system scaling based on container size
15
+ - **Fullscreen Management**: Built-in fullscreen request handling
16
+ - **Device Detection**: Automatic mobile and OS detection
17
+
18
+ ## Basic Usage
19
+
20
+ ```svelte
21
+ <script>
22
+ import { GameBox } from '$lib/ui/components/game-box/index.js';
23
+ </script>
24
+
25
+ <GameBox
26
+ aspectOnLandscape={16/9}
27
+ aspectOnPortrait={9/16}
28
+ snippetLandscape={landscapeContent}
29
+ snippetPortrait={portraitContent}
30
+ />
31
+
32
+ {#snippet landscapeContent({ gameWidth, gameHeight, isMobile })}
33
+ <div>Game content for landscape - {gameWidth}x{gameHeight}</div>
34
+ {/snippet}
35
+
36
+ {#snippet portraitContent({ gameWidth, gameHeight, isMobile })}
37
+ <div>Game content for portrait - {gameWidth}x{gameHeight}</div>
38
+ {/snippet}
39
+ ```
40
+
41
+ ## Props
42
+
43
+ ### Styling Props
44
+ - `base?: string` - Base CSS classes
45
+ - `bg?: string` - Background CSS classes
46
+ - `classes?: string` - Additional CSS classes
47
+ - `style?: string` - Inline styles
48
+
49
+ ### Layout Props
50
+ - `aspectOnLandscape?: number` - Aspect ratio for landscape mode (e.g., 16/9)
51
+ - `aspectOnPortrait?: number` - Aspect ratio for portrait mode (e.g., 9/16)
52
+ - `marginLeft?: number` - Left margin in pixels (default: 0)
53
+ - `marginRight?: number` - Right margin in pixels (default: 0)
54
+ - `marginTop?: number` - Top margin in pixels (default: 0)
55
+ - `marginBottom?: number` - Bottom margin in pixels (default: 0)
56
+ - `center?: boolean` - Center the game box in viewport
57
+
58
+ ### Scaling Props
59
+ - `enableScaling?: boolean` - Enable design system scaling (default: false)
60
+ - `designLandscape?: {width: number, height: number}` - Design dimensions
61
+ for landscape (default: {width: 1920, height: 1080})
62
+ - `designPortrait?: {width: number, height: number}` - Design dimensions
63
+ for portrait (default: {width: 1920, height: 1080})
64
+ - `clamping?: object` - Scaling limits for different elements
65
+
66
+ ### Snippet Props
67
+ - `snippetLandscape?: Snippet` - Content for landscape orientation
68
+ - `snippetPortrait?: Snippet` - Content for portrait orientation
69
+ - `snippetRequireFullscreen?: Snippet` - Content when fullscreen is required
70
+ - `snippetInstallOnHomeScreen?: Snippet` - Content for home screen install
71
+ prompt
72
+
73
+ ## Snippet Parameters
74
+
75
+ All snippets receive these parameters:
76
+
77
+ ```js
78
+ {
79
+ isMobile: boolean, // Is running on mobile device
80
+ os: 'Android'|'iOS', // Operating system
81
+ isFullscreen: boolean, // Is in fullscreen mode
82
+ isDevMode: boolean, // Is in development mode
83
+ requestDevmode: function, // Function to enable dev mode
84
+ requestFullscreen: function, // Function to request fullscreen
85
+ gameWidth: number, // Calculated game width
86
+ gameHeight: number // Calculated game height
87
+ }
88
+ ```
89
+
90
+ ## Examples
91
+
92
+ ### Simple Game Container
93
+
94
+ ```svelte
95
+ <GameBox
96
+ aspectOnLandscape={16/9}
97
+ aspectOnPortrait={9/16}
98
+ center={true}
99
+ snippetLandscape={gameContent}
100
+ snippetPortrait={gameContent}
101
+ />
102
+
103
+ {#snippet gameContent({ gameWidth, gameHeight })}
104
+ <div class="game-area bg-surface-900 rounded-lg p-20up">
105
+ <h1 class="type-heading-h1 text-center mb-20up">My Game</h1>
106
+ <div class="game-content" style="width: 100%; height: 100%;">
107
+ Game content goes here
108
+ </div>
109
+ </div>
110
+ {/snippet}
111
+ ```
112
+
113
+ ### With Fullscreen Requirement
114
+
115
+ ```svelte
116
+ <GameBox
117
+ aspectOnLandscape={21/9}
118
+ center={true}
119
+ snippetLandscape={gameContent}
120
+ snippetRequireFullscreen={fullscreenPrompt}
121
+ />
122
+
123
+ {#snippet gameContent({ gameWidth, gameHeight, isFullscreen })}
124
+ <div class="cinematic-game">
125
+ Ultra-wide cinematic game experience
126
+ </div>
127
+ {/snippet}
128
+
129
+ {#snippet fullscreenPrompt({ requestFullscreen })}
130
+ <div class="fullscreen-prompt text-center">
131
+ <h2 class="type-heading-h2 mb-16bt">Fullscreen Required</h2>
132
+ <p class="type-base-md mb-20bt">
133
+ This game requires fullscreen mode for the best experience.
134
+ </p>
135
+ <button class="btn-primary" onclick={requestFullscreen}>
136
+ Enter Fullscreen
137
+ </button>
138
+ </div>
139
+ {/snippet}
140
+ ```
141
+
142
+ ### With Scaling Enabled
143
+
144
+ ```svelte
145
+ <GameBox
146
+ aspectOnLandscape={16/9}
147
+ enableScaling={true}
148
+ designLandscape={{ width: 1920, height: 1080 }}
149
+ clamping={{
150
+ ui: { min: 0.5, max: 1.5 },
151
+ textBase: { min: 0.8, max: 1.2 },
152
+ textHeading: { min: 0.75, max: 2 },
153
+ textUi: { min: 0.6, max: 1.1 }
154
+ }}
155
+ snippetLandscape={scaledContent}
156
+ />
157
+
158
+ {#snippet scaledContent()}
159
+ <div class="game-ui">
160
+ <!-- UI elements will automatically scale based on container size -->
161
+ <div class="hud">
162
+ <span class="type-ui-sm">Score: 1000</span>
163
+ </div>
164
+ </div>
165
+ {/snippet}
166
+ ```
167
+
168
+ ### Mobile-Specific Handling
169
+
170
+ ```svelte
171
+ <GameBox
172
+ aspectOnLandscape={16/9}
173
+ aspectOnPortrait={9/16}
174
+ snippetLandscape={landscapeGame}
175
+ snippetPortrait={portraitGame}
176
+ snippetInstallOnHomeScreen={installPrompt}
177
+ />
178
+
179
+ {#snippet landscapeGame({ isMobile, os })}
180
+ <div class="landscape-game">
181
+ {#if isMobile}
182
+ <div class="mobile-controls">
183
+ <!-- Touch controls for mobile -->
184
+ </div>
185
+ {:else}
186
+ <div class="desktop-controls">
187
+ <!-- Keyboard/mouse controls for desktop -->
188
+ </div>
189
+ {/if}
190
+ </div>
191
+ {/snippet}
192
+
193
+ {#snippet portraitGame({ isMobile })}
194
+ <div class="portrait-game">
195
+ <!-- Optimized for portrait mobile gameplay -->
196
+ </div>
197
+ {/snippet}
198
+
199
+ {#snippet installPrompt({ os })}
200
+ <div class="install-prompt text-center">
201
+ <h2 class="type-heading-h2 mb-16bt">Install App</h2>
202
+ <p class="type-base-md mb-20bt">
203
+ {#if os === 'iOS'}
204
+ Tap the share button and select "Add to Home Screen"
205
+ {:else}
206
+ Install this app for the best gaming experience
207
+ {/if}
208
+ </p>
209
+ </div>
210
+ {/snippet}
211
+ ```
212
+
213
+ ## CSS Custom Properties
214
+
215
+ The component exposes these CSS custom properties:
216
+
217
+ - `--game-width`: Current game width in pixels
218
+ - `--game-height`: Current game height in pixels
219
+
220
+ These can be used in your CSS for responsive styling:
221
+
222
+ ```css
223
+ .game-element {
224
+ width: calc(var(--game-width) * 0.5);
225
+ height: calc(var(--game-height) * 0.2);
226
+ }
227
+ ```
228
+
229
+ ## Mobile PWA Considerations
230
+
231
+ The component includes special handling for mobile PWAs:
232
+
233
+ - **iOS Safari PWA**: Handles viewport quirks and orientation changes
234
+ - **Fullscreen Detection**: Properly detects PWA fullscreen mode
235
+ - **Screen Orientation**: Listens for orientation changes and updates layout
236
+ - **No Scroll**: Automatically prevents scrolling when active
237
+
238
+ ## Development Mode
239
+
240
+ The component automatically enables development mode when:
241
+ - Running on `localhost`
242
+ - Called via `requestDevmode()` function
243
+
244
+ In development mode, fullscreen and installation requirements are bypassed
245
+ for easier testing.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-core",
3
- "version": "0.4.23",
3
+ "version": "0.4.25",
4
4
  "author": {
5
5
  "name": "HKdigital",
6
6
  "url": "https://hkdigital.nl"
@@ -1,260 +0,0 @@
1
- /**
2
- * Pino adapter for server-side logging
3
- */
4
- import pino from 'pino';
5
- import { dev } from '$app/environment';
6
-
7
- import {
8
- detectErrorMeta,
9
- findRelevantFrameIndex,
10
- formatErrorDisplay
11
- } from './formatting.js';
12
-
13
- /**
14
- * Pino adapter that bridges Logger events to pino
15
- */
16
- export class PinoAdapter {
17
- #projectRoot = null;
18
-
19
- /**
20
- * Create a new PinoAdapter
21
- *
22
- * @param {Object} [options] - Pino configuration options
23
- */
24
- constructor(options = {}) {
25
- // Determine project root once for stack trace cleaning
26
- this.#projectRoot = import.meta.env.VITE_PROJECT_ROOT || process.cwd();
27
- const baseOptions = {
28
- serializers: {
29
- errors: (err) => {
30
-
31
- /** @type {import('./typedef').ErrorSummary[]} */
32
- const chain = [];
33
- let loggedAt = null;
34
-
35
- let current = err;
36
- let isFirst = true;
37
-
38
- while (current && current instanceof Error) {
39
- // Check if this is the first error and it's a LoggerError - extract logging context
40
- if (isFirst && current.name === 'LoggerError') {
41
- if (current.stack) {
42
- const cleanedStackString = this.#cleanStackTrace(current.stack);
43
- const cleanedStackArray = cleanedStackString
44
- .split('\n')
45
- .map((line) => line.trim())
46
- .filter(
47
- (line) =>
48
- line && line !== current.name + ': ' + current.message
49
- );
50
-
51
- // For LoggerError, we know it's a logger.error call, so find the relevant frame
52
- const loggerErrorIndex = cleanedStackArray.findIndex(frame =>
53
- (frame.includes('Logger.error') && frame.includes('logger/Logger.js')) ||
54
- (frame.includes('error@') && frame.includes('logger/Logger.js'))
55
- );
56
-
57
- if (loggerErrorIndex >= 0 && loggerErrorIndex + 1 < cleanedStackArray.length) {
58
- const relevantFrame = cleanedStackArray[loggerErrorIndex + 1];
59
-
60
- // Extract function name from the relevant frame
61
- // const functionName = parseFunctionName(relevantFrame);
62
-
63
- // const errorType = functionName ? `logger.error in ${functionName}` : 'logger.error';
64
- loggedAt = relevantFrame.slice(3); // remove "at "
65
- }
66
- }
67
-
68
- // Skip the LoggerError and move to the actual error
69
- current = current.cause;
70
- isFirst = false;
71
- continue;
72
- }
73
- /** @type {import('./typedef').ErrorSummary} */
74
- const serialized = {
75
- name: current.name,
76
- message: current.message
77
- };
78
-
79
- // Add error metadata for structured logging and terminal display
80
- if (current.stack) {
81
- // Convert cleaned stack string to array format expected by formatting functions
82
- const cleanedStackString = this.#cleanStackTrace(current.stack);
83
- const cleanedStackArray = cleanedStackString
84
- .split('\n')
85
- .map((line) => line.trim())
86
- .filter(
87
- (line) =>
88
- line && line !== current.name + ': ' + current.message
89
- );
90
-
91
- const errorMeta = detectErrorMeta(current, cleanedStackArray);
92
- const relevantFrameIndex = findRelevantFrameIndex(
93
- current,
94
- cleanedStackArray
95
- );
96
-
97
- serialized.meta = errorMeta;
98
- serialized.errorType = formatErrorDisplay(errorMeta);
99
-
100
- // Include stack frames for terminal display
101
- serialized.stackFrames = cleanedStackArray
102
- .slice(0, 9)
103
- .map((frame, index) => {
104
- const marker = index === relevantFrameIndex ? '→' : ' ';
105
-
106
- return `${marker} ${frame}`;
107
- });
108
- }
109
-
110
- // Include HttpError-specific properties
111
- const httpError = /** @type {import('$lib/network/errors.js').HttpError} */ (current);
112
- if (httpError.status !== undefined) {
113
- serialized.status = httpError.status;
114
- }
115
- if (httpError.details !== undefined) {
116
- serialized.details = httpError.details;
117
- }
118
-
119
- chain.push(serialized);
120
- current = current.cause;
121
- isFirst = false;
122
- }
123
-
124
- return loggedAt ? { chain, loggedAt } : chain;
125
- }
126
- }
127
- };
128
-
129
- // Add error handling for missing pino-pretty in dev
130
- if ( dev) {
131
- const devOptions = {
132
- level: 'debug',
133
- transport: {
134
- target: 'pino-pretty',
135
- options: {
136
- colorize: true,
137
- ignore: 'hostname,pid'
138
- }
139
- }
140
- };
141
-
142
- try {
143
- this.pino = pino({ ...baseOptions, ...devOptions, ...options });
144
- } catch (error) {
145
- if (
146
- error.message.includes('Cannot find module') &&
147
- error.message.includes('pino-pretty')
148
- ) {
149
- const errorMessage = `
150
- ╭─────────────────────────────────────────────────────────────╮
151
- │ Missing Dependency │
152
- ├─────────────────────────────────────────────────────────────┤
153
- │ 'pino-pretty' is required for development logging │
154
- │ Install it with: pnpm add -D pino-pretty │
155
- ╰─────────────────────────────────────────────────────────────╯`;
156
- console.error(errorMessage);
157
- throw new Error('pino-pretty is required for development mode');
158
- }
159
- throw error;
160
- }
161
- } else {
162
- this.pino = pino({ ...baseOptions, ...options });
163
- }
164
- }
165
-
166
- /**
167
- * Clean stack trace by removing project root path and simplifying node_modules
168
- *
169
- * @param {string} stack - Original stack trace
170
- * @returns {string} Cleaned stack trace
171
- */
172
- #cleanStackTrace(stack) {
173
- if (!stack || !this.#projectRoot) {
174
- return stack;
175
- }
176
-
177
- let cleaned = stack;
178
-
179
- // Escape special regex characters in the project root path
180
- const escapedRoot = this.#projectRoot.replace(
181
- /[.*+?^${}()|[\]\\]/g,
182
- '\\$&'
183
- );
184
-
185
- // Replace project root path with relative path, handling file:// protocol
186
- // Match both regular paths and file:// URLs
187
- const rootRegex = new RegExp(
188
- `(\\s+at\\s+.*\\()(file://)?${escapedRoot}[\\/\\\\]`,
189
- 'g'
190
- );
191
- cleaned = cleaned.replace(rootRegex, '$1');
192
-
193
- // Simplify pnpm paths: node_modules/.pnpm/package@version_deps/node_modules/package
194
- // becomes: node_modules/package
195
- const pnpmRegex =
196
- /node_modules\/\.pnpm\/([^@\/]+)@[^\/]+\/node_modules\/\1/g;
197
- cleaned = cleaned.replace(pnpmRegex, 'node_modules/$1');
198
-
199
- // Also handle cases where the package name might be different in the final path
200
- const pnpmRegex2 = /node_modules\/\.pnpm\/[^\/]+\/node_modules\/([^\/]+)/g;
201
- cleaned = cleaned.replace(pnpmRegex2, 'node_modules/$1');
202
-
203
- return cleaned;
204
- }
205
-
206
- /**
207
- * Handle log events from Logger
208
- *
209
- * @param {Object} logEvent - Log event from Logger
210
- */
211
- handleLog(logEvent) {
212
- const { level, message, details, source, timestamp } = logEvent;
213
-
214
- const logData = {
215
- source,
216
- timestamp
217
- };
218
-
219
- // Check if details contains an error and promote it to err property for pino serializer
220
- if (details) {
221
- if (details instanceof Error) {
222
- // details is directly an error
223
- logData.err = details;
224
- } else if (details.error instanceof Error) {
225
- // details has an error property
226
- logData.err = details.error;
227
- // Include other details except the error
228
- // eslint-disable-next-line no-unused-vars
229
- const { error, ...otherDetails } = details;
230
- if (Object.keys(otherDetails).length > 0) {
231
- logData.details = otherDetails;
232
- }
233
- } else {
234
- // No error found in details, include all details
235
- logData.details = details;
236
- }
237
- }
238
-
239
- // Check if we have loggedAt info from the serializer
240
- if (logData.err && typeof logData.err === 'object' && logData.err.loggedAt) {
241
- logData.loggedAt = logData.err.loggedAt;
242
- logData.err = logData.err.chain;
243
- }
244
-
245
- this.pino[level](logData, message);
246
- }
247
-
248
- /**
249
- * Create a child logger with additional context
250
- *
251
- * @param {Object} context - Additional context data
252
- * @returns {PinoAdapter} New adapter instance with context
253
- */
254
- child(context) {
255
- const childPino = this.pino.child(context);
256
- const adapter = new PinoAdapter();
257
- adapter.pino = childPino;
258
- return adapter;
259
- }
260
- }
@@ -1,133 +0,0 @@
1
- /**
2
- * Initial code borrowed from:
3
- *
4
- * @see {@link https://runed.dev/docs/utilities/finite-state-machine}
5
- */
6
-
7
- /**
8
- * Check if the value is valid meta data
9
- *
10
- * @param {any} meta
11
- */
12
- export function isLifecycleFnMeta(meta) {
13
- return (
14
- !!meta &&
15
- typeof meta === 'object' &&
16
- 'to' in meta &&
17
- 'from' in meta &&
18
- 'event' in meta &&
19
- 'args' in meta
20
- );
21
- }
22
-
23
- /**
24
- * Defines a Finite State Machine
25
- */
26
- export default class FiniteStateMachine {
27
- #current = $state();
28
- states;
29
- #timeout = {};
30
-
31
- /**
32
- * Constructor
33
- *
34
- * @param {string} initial
35
- * @param {{ [key: string]: { [key: string]: (string|((...args: any[])=>void)) } }} states
36
- */
37
- constructor(initial, states) {
38
- this.#current = initial;
39
- this.states = states;
40
-
41
- // synthetically trigger _enter for the initial state.
42
- this.#dispatch('_enter', {
43
- from: null,
44
- to: initial,
45
- event: null,
46
- args: []
47
- });
48
- }
49
-
50
- /**
51
- * Transition to new state
52
- *
53
- * @param {string} newState
54
- * @param {string} event
55
- * @param {any[]} [args]
56
- */
57
- #transition(newState, event, args) {
58
- const metadata = { from: this.#current, to: newState, event, args };
59
- this.#dispatch('_exit', metadata);
60
- this.#current = newState;
61
- this.#dispatch('_enter', metadata);
62
- }
63
-
64
- /**
65
- * Dispatch an event
66
- *
67
- * @param {string} event
68
- * @param {any[]} args
69
- */
70
- #dispatch(event, ...args) {
71
- const action =
72
- this.states[this.#current]?.[event] ?? this.states['*']?.[event];
73
- if (action instanceof Function) {
74
- if (event === '_enter' || event === '_exit') {
75
- if (isLifecycleFnMeta(args[0])) {
76
- action(args[0]);
77
- } else {
78
- console.warn(
79
- 'Invalid metadata passed to lifecycle function of the FSM.'
80
- );
81
- }
82
- } else {
83
- return action(...args);
84
- }
85
- } else if (typeof action === 'string') {
86
- return action;
87
- } else if (event !== '_enter' && event !== '_exit') {
88
- console.warn(
89
- 'No action defined for event',
90
- event,
91
- 'in state',
92
- this.#current
93
- );
94
- }
95
- }
96
- /**
97
- * Triggers a new event and returns the new state.
98
- *
99
- * @param {string} event
100
- * @param {any[]} args
101
- */
102
- send(event, ...args) {
103
- const newState = this.#dispatch(event, ...args);
104
- if (newState && newState !== this.#current) {
105
- this.#transition(newState, event, args);
106
- }
107
-
108
- return this.#current;
109
- }
110
- /**
111
- * Debounces the triggering of an event.
112
- *
113
- * @param {number} wait
114
- * @param {string} event
115
- * @param {any[]} args
116
- */
117
- async debounce(wait = 500, event, ...args) {
118
- if (this.#timeout[event]) {
119
- clearTimeout(this.#timeout[event]);
120
- }
121
- return new Promise((resolve) => {
122
- this.#timeout[event] = setTimeout(() => {
123
- delete this.#timeout[event];
124
- resolve(this.send(event, ...args));
125
- }, wait);
126
- });
127
- }
128
-
129
- /** The current state. */
130
- get current() {
131
- return this.#current;
132
- }
133
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * extends FiniteStateMachine<StatesT, EventsT>
3
- */
4
- export default class LoadingStateMachine extends FiniteStateMachine {
5
- constructor();
6
- /** @type {(( state: string )=>void)|null} */
7
- onenter: ((state: string) => void) | null;
8
- /** The last error */
9
- get error(): Error;
10
- #private;
11
- }
12
- import FiniteStateMachine from '../finite-state-machine/FiniteStateMachine.svelte.js';