@hkdigital/lib-sveltekit 0.2.1 → 0.2.3

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.
@@ -9,11 +9,7 @@ import {
9
9
  STATE_CANCELLED,
10
10
  STATE_ERROR,
11
11
  LOAD,
12
- // CANCEL,
13
- ERROR,
14
- LOADED,
15
- UNLOAD,
16
- INITIAL
12
+ LOADED
17
13
  } from '../loading-state-machine/index.js';
18
14
 
19
15
  import AudioLoader from './AudioLoader.svelte.js';
@@ -4,8 +4,6 @@ import { toSingleImageMeta } from '../../../util/image/index.js';
4
4
 
5
5
  import {
6
6
  NetworkLoader
7
- // ERROR_NOT_LOADED
8
- // ERROR_TRANSFERRED
9
7
  } from '../network-loader/index.js';
10
8
 
11
9
  /**
@@ -11,11 +11,7 @@ import {
11
11
  STATE_CANCELLED,
12
12
  STATE_ERROR,
13
13
  LOAD,
14
- // CANCEL,
15
- ERROR,
16
- LOADED,
17
- UNLOAD,
18
- INITIAL
14
+ LOADED
19
15
  } from '../loading-state-machine/index.js';
20
16
 
21
17
  import ImageLoader from './ImageLoader.svelte.js';
@@ -9,7 +9,6 @@ import {
9
9
  STATE_CANCELLED,
10
10
  STATE_ERROR,
11
11
  LOAD,
12
- // CANCEL,
13
12
  ERROR,
14
13
  LOADED,
15
14
  UNLOAD,
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * Schema to validate an URL
3
3
  */
4
- export const ValidateUrl: v.SchemaWithPipe<[v.StringSchema<undefined>, v.UrlAction<string, undefined>, v.CustomSchema<any, undefined>]>;
4
+ export const ValidateUrl: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.UrlAction<string, undefined>, v.CustomSchema<any, undefined>]>;
5
5
  /**
6
6
  * Schema to validate an URL or empty string.
7
7
  */
8
- export const ValidateUrlOrEmptyString: v.UnionSchema<[v.LiteralSchema<"", undefined>, v.SchemaWithPipe<[v.StringSchema<undefined>, v.UrlAction<string, undefined>]>], undefined>;
8
+ export const ValidateUrlOrEmptyString: v.UnionSchema<[v.LiteralSchema<"", undefined>, v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.UrlAction<string, undefined>]>], undefined>;
9
9
  /**
10
10
  * Schema to validate an URL path
11
11
  */
12
- export const ValidateUrlPath: v.SchemaWithPipe<[v.StringSchema<undefined>, v.CustomSchema<any, undefined>, v.TransformAction<any, {
12
+ export const ValidateUrlPath: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.CustomSchema<any, undefined>, v.TransformAction<any, {
13
13
  url: URL;
14
14
  value: any;
15
15
  }>, v.CustomSchema<{
@@ -25,6 +25,6 @@ export const ValidateUrlPath: v.SchemaWithPipe<[v.StringSchema<undefined>, v.Cus
25
25
  /**
26
26
  * Schema to validate a relative URL
27
27
  */
28
- export const ValidateRelativeUrl: v.SchemaWithPipe<[v.StringSchema<undefined>, v.CustomSchema<any, undefined>, v.CustomSchema<any, undefined>]>;
29
- export const ValidateAbsOrRelUrl: v.SchemaWithPipe<[v.StringSchema<undefined>, v.CustomSchema<any, undefined>, v.CustomSchema<any, undefined>]>;
28
+ export const ValidateRelativeUrl: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.CustomSchema<any, undefined>, v.CustomSchema<any, undefined>]>;
29
+ export const ValidateAbsOrRelUrl: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.CustomSchema<any, undefined>, v.CustomSchema<any, undefined>]>;
30
30
  import * as v from 'valibot';
@@ -5,7 +5,7 @@
5
5
  // AbortError,
6
6
  // TimeoutError,
7
7
  // TypeOrValueError
8
- // } from '../../constants/errors/index.js';
8
+ // } from '$lib/constants/errors/index.js';
9
9
 
10
10
  // import { setRequestHeaders } from './headers.js';
11
11
  // import { getErrorFromResponse } from './errors.js';
@@ -1,26 +1,26 @@
1
1
  /**
2
2
  * Schema to validate an URL or empty string.
3
3
  */
4
- export const UrlOrEmptyString: v.SchemaWithPipe<[v.SchemaWithPipe<[v.StringSchema<undefined>, v.TrimAction]>, v.UnionSchema<[v.LiteralSchema<"", undefined>, v.SchemaWithPipe<[v.StringSchema<undefined>, v.UrlAction<string, undefined>]>], undefined>]>;
4
+ export const UrlOrEmptyString: v.SchemaWithPipe<readonly [v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction]>, v.UnionSchema<[v.LiteralSchema<"", undefined>, v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.UrlAction<string, undefined>]>], undefined>]>;
5
5
  /**
6
6
  * Schema to validate an url that may miss the protocol part
7
7
  *
8
8
  * @note an empty string is not allowed!
9
9
  */
10
- export const HumanUrl: v.SchemaWithPipe<[v.StringSchema<undefined>, v.TrimAction, v.TransformAction<string, string>, v.UrlAction<string, undefined>]>;
10
+ export const HumanUrl: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.TransformAction<string, string>, v.UrlAction<string, undefined>]>;
11
11
  /**
12
12
  * Schema to validate url path, without a search and hash part
13
13
  */
14
- export const UrlPath: v.SchemaWithPipe<[v.StringSchema<undefined>, v.TransformAction<string, URL>, v.CustomSchema<URL, "Invalid URL pathname">, v.TransformAction<URL, string>]>;
14
+ export const UrlPath: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TransformAction<string, URL>, v.CustomSchema<URL, "Invalid URL pathname">, v.TransformAction<URL, string>]>;
15
15
  /**
16
16
  * Schema to validate a url path, which consists of
17
17
  * a path and optionally search and hash parts
18
18
  */
19
- export const RelativeUrl: v.SchemaWithPipe<[v.StringSchema<undefined>, v.TransformAction<string, URL>, v.CustomSchema<URL, "Invalid URL pathname or search part">, v.TransformAction<URL, string>]>;
19
+ export const RelativeUrl: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TransformAction<string, URL>, v.CustomSchema<URL, "Invalid URL pathname or search part">, v.TransformAction<URL, string>]>;
20
20
  /**
21
21
  * Schema to validate an absolute or relative url
22
22
  *
23
23
  * @note an empty string is not allowed!
24
24
  */
25
- export const AbsOrRelUrl: v.UnionSchema<[v.SchemaWithPipe<[v.StringSchema<undefined>, v.TrimAction, v.UrlAction<string, undefined>]>, v.SchemaWithPipe<[v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>, v.SchemaWithPipe<[v.StringSchema<undefined>, v.TransformAction<string, URL>, v.CustomSchema<URL, "Invalid URL pathname or search part">, v.TransformAction<URL, string>]>]>], undefined>;
25
+ export const AbsOrRelUrl: v.UnionSchema<[v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.UrlAction<string, undefined>]>, v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>, v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TransformAction<string, URL>, v.CustomSchema<URL, "Invalid URL pathname or search part">, v.TransformAction<URL, string>]>]>], undefined>;
26
26
  import * as v from 'valibot';
@@ -1,6 +1,6 @@
1
- export const Name: v.SchemaWithPipe<[v.StringSchema<undefined>, v.TrimAction, v.RegexAction<string, undefined>]>;
2
- export const Fullname: v.SchemaWithPipe<[v.StringSchema<undefined>, v.TrimAction, v.RegexAction<string, undefined>]>;
3
- export const Username: v.SchemaWithPipe<[v.StringSchema<undefined>, v.TrimAction, v.RegexAction<string, undefined>]>;
4
- export const Surname: v.SchemaWithPipe<[v.StringSchema<undefined>, v.TrimAction, v.RegexAction<string, undefined>]>;
5
- export const PhoneNumber: v.SchemaWithPipe<[v.StringSchema<undefined>, v.TrimAction, v.RegexAction<string, undefined>]>;
1
+ export const Name: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.RegexAction<string, undefined>]>;
2
+ export const Fullname: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.RegexAction<string, undefined>]>;
3
+ export const Username: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.RegexAction<string, undefined>]>;
4
+ export const Surname: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.RegexAction<string, undefined>]>;
5
+ export const PhoneNumber: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.RegexAction<string, undefined>]>;
6
6
  import * as v from 'valibot';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-sveltekit",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "author": {
5
5
  "name": "HKdigital",
6
6
  "url": "https://hkdigital.nl"
@@ -57,56 +57,56 @@
57
57
  "./typedef": "./dist/typedef/index.js"
58
58
  },
59
59
  "peerDependencies": {
60
- "@eslint/js": "^9.27.0",
60
+ "@eslint/js": "^9.28.0",
61
61
  "@steeze-ui/heroicons": "^2.4.2",
62
- "@sveltejs/kit": "^2.15.2",
62
+ "@sveltejs/kit": "^2.21.2",
63
63
  "eslint-plugin-import": "^2.31.0",
64
- "pino": "^9.6.0",
64
+ "pino": "^9.7.0",
65
65
  "pino-pretty": "^13.0.0",
66
- "runed": "^0.23.0",
67
- "svelte": "^5.0.0",
66
+ "runed": "^0.23.1",
67
+ "svelte": "^5.33.16",
68
68
  "svelte-preprocess": "^6.0.3",
69
- "valibot": "^0.42.1",
69
+ "valibot": "^1.1.0",
70
70
  "zod": "^3.24.2"
71
71
  },
72
72
  "devDependencies": {
73
- "@eslint/js": "^9.27.0",
74
- "@playwright/test": "^1.50.1",
73
+ "@eslint/js": "^9.28.0",
74
+ "@playwright/test": "^1.52.0",
75
75
  "@skeletonlabs/skeleton": "3.0.0-next.2",
76
76
  "@skeletonlabs/skeleton-svelte": "1.0.0-next.4",
77
77
  "@steeze-ui/heroicons": "^2.4.2",
78
- "@sveltejs/adapter-auto": "^4.0.0",
79
- "@sveltejs/package": "2.3.7",
80
- "@sveltejs/vite-plugin-svelte": "^5.0.3",
78
+ "@sveltejs/adapter-auto": "^6.0.1",
79
+ "@sveltejs/package": "^2.3.11",
80
+ "@sveltejs/vite-plugin-svelte": "^5.1.0",
81
81
  "@tailwindcss/typography": "^0.5.16",
82
82
  "@testing-library/svelte": "^5.2.7",
83
83
  "@testing-library/user-event": "^14.6.1",
84
84
  "@types/eslint": "^9.6.1",
85
- "autoprefixer": "^10.4.20",
86
- "eslint": "^9.21.0",
87
- "eslint-config-prettier": "^10.0.2",
85
+ "autoprefixer": "^10.4.21",
86
+ "eslint": "^9.28.0",
87
+ "eslint-config-prettier": "^10.1.5",
88
88
  "eslint-plugin-import": "^2.31.0",
89
- "eslint-plugin-svelte": "^3.0.2",
89
+ "eslint-plugin-svelte": "^3.9.1",
90
90
  "fake-indexeddb": "^6.0.0",
91
- "globals": "^16.0.0",
92
- "jsdom": "^26.0.0",
93
- "pino": "^9.6.0",
91
+ "globals": "^16.2.0",
92
+ "jsdom": "^26.1.0",
93
+ "pino": "^9.7.0",
94
94
  "pino-pretty": "^13.0.0",
95
- "postcss": "^8.5.3",
95
+ "postcss": "^8.5.4",
96
96
  "postcss-mixins": "^11.0.3",
97
97
  "prettier": "^3.5.3",
98
- "prettier-plugin-svelte": "^3.3.3",
99
- "prettier-plugin-tailwindcss": "^0.6.11",
100
- "publint": "^0.3.7",
101
- "standardized-audio-context-mock": "^9.7.16",
102
- "svelte": "^5.20.5",
103
- "svelte-check": "^4.1.4",
98
+ "prettier-plugin-svelte": "^3.4.0",
99
+ "prettier-plugin-tailwindcss": "^0.6.12",
100
+ "publint": "^0.3.12",
101
+ "standardized-audio-context-mock": "^9.7.22",
102
+ "svelte": "^5.33.16",
103
+ "svelte-check": "^4.2.1",
104
104
  "svelte-preprocess": "^6.0.3",
105
105
  "tailwindcss": "^3.4.17",
106
- "typescript": "^5.8.2",
107
- "vite": "^6.2.0",
106
+ "typescript": "^5.8.3",
107
+ "vite": "^6.3.5",
108
108
  "vite-imagetools": "^7.1.0",
109
- "vitest": "^3.0.7",
109
+ "vitest": "^3.2.2",
110
110
  "zod": "^3.24.2"
111
111
  }
112
112
  }
@@ -1,613 +0,0 @@
1
- import { defineStateContext } from '$lib/util/svelte/state-context/index.js';
2
-
3
- import { findFirst } from '$lib/util/array/index.js';
4
-
5
- import { untrack } from 'svelte';
6
-
7
- import { HkPromise } from '$lib/classes/promise/index.js';
8
-
9
- /* ----------------------------------------------------------------- typedefs */
10
-
11
- /**
12
- * @typedef {import("./typedef").Slide} Slide
13
- */
14
-
15
- /**
16
- * @typedef {import("./typedef").Transition} Transition
17
- */
18
-
19
- /**
20
- * @typedef {import("./typedef").Layer} Layer
21
- */
22
-
23
- /**
24
- * @typedef {Object} LoadController
25
- * @property {() => void} loaded - Function to call when loading is complete
26
- * @property {() => void} cancel - Function to return to the previous slide
27
- */
28
-
29
- /**
30
- * @typedef {Object} PresenterRef
31
- * @property {(name: string) => void} gotoSlide - Navigate to a slide by name
32
- * @property {() => string} getCurrentSlideName - Get the current slide name
33
- */
34
-
35
- /* -------------------------------------------------------------- Constants */
36
-
37
- const Z_BACK = 0;
38
- const Z_FRONT = 10;
39
-
40
- const LABEL_A = 'A';
41
- const LABEL_B = 'B';
42
-
43
- /* ------------------------------------------------------- Define state class */
44
-
45
- export class PresenterState {
46
- /** @type {Slide[]} */
47
- slides = $state.raw([]);
48
-
49
- /** @type {Layer} */
50
- layerA = $state.raw({ z: Z_BACK, visible: false, stageIdle: true });
51
-
52
- /** @type {Layer} */
53
- layerB = $state.raw({ z: Z_FRONT, visible: false, stageIdle: true });
54
-
55
- /** @type {Slide|null} */
56
- slideA = $state.raw(null);
57
-
58
- /** @type {Slide|null} */
59
- slideB = $state.raw(null);
60
-
61
- /** @type {string} */
62
- currentLayerLabel = $state(LABEL_B);
63
-
64
- /** @type {string} */
65
- nextLayerLabel = $state(LABEL_A);
66
-
67
- /** @type {HkPromise[]} */
68
- transitionPromises = $state.raw([]);
69
-
70
- /** @type {boolean} */
71
- isSlideLoading = $state(false);
72
-
73
- /** @type {boolean} */
74
- controllerRequested = $state(false);
75
-
76
- /** @type {number} Loading timeout in milliseconds (0 = disabled) */
77
- loadingTimeout = $state(1000);
78
-
79
- /** @type {boolean} */
80
- busy = $derived.by(() => {
81
- const { layerA, layerB } = this;
82
-
83
- const layerAStable =
84
- layerA.stageShow || layerA.stageAfter || layerA.stageIdle;
85
-
86
- const layerBStable =
87
- layerB.stageShow || layerB.stageAfter || layerB.stageIdle;
88
-
89
- return !(layerAStable && layerBStable);
90
- });
91
-
92
- /** @type {string} */
93
- currentSlideName = $derived.by(() => {
94
- const currentSlide = this.#getSlide(this.currentLayerLabel);
95
- return currentSlide?.name || '';
96
- });
97
-
98
- /**
99
- * Initialize the presenter state and set up reactivity
100
- */
101
- constructor() {
102
- this.#setupStageTransitions();
103
- this.#setupTransitionTracking();
104
- this.#setupLoadingTransitions();
105
- }
106
-
107
- /**
108
- * Returns a simplified presenter reference with essential methods
109
- * for slide components to use
110
- *
111
- * @returns {PresenterRef} A reference object with presenter methods
112
- */
113
- getPresenterRef() {
114
- return {
115
- gotoSlide: (name) => this.gotoSlide(name),
116
- getCurrentSlideName: () => this.currentSlideName
117
- };
118
- }
119
-
120
- /**
121
- * Set up reactivity for stage transitions between the before/after states
122
- * This handles the animation timing for both layers
123
- */
124
- #setupStageTransitions() {
125
- // Handle layer A stage transitions
126
- $effect(() => {
127
- if (this.layerA.stageBeforeIn || this.layerA.stageBeforeOut) {
128
- this.layerA = this.#processStageTransition(this.layerA);
129
- }
130
- });
131
-
132
- // Handle layer B stage transitions
133
- $effect(() => {
134
- if (this.layerB.stageBeforeIn || this.layerB.stageBeforeOut) {
135
- this.layerB = this.#processStageTransition(this.layerB);
136
- }
137
- });
138
- }
139
-
140
- /**
141
- * Process a single stage transition for a layer
142
- *
143
- * @param {Layer} layer - The layer to process
144
- * @returns {Layer} - The updated layer with new stage
145
- */
146
- #processStageTransition(layer) {
147
- const updatedLayer = { ...layer };
148
-
149
- if (updatedLayer.stageBeforeIn) {
150
- delete updatedLayer.stageBeforeIn;
151
- updatedLayer.stageIn = true;
152
- } else if (updatedLayer.stageBeforeOut) {
153
- delete updatedLayer.stageBeforeOut;
154
- updatedLayer.stageOut = true;
155
- }
156
-
157
- return updatedLayer;
158
- }
159
-
160
- /**
161
- * Set up reactivity for tracking transition promises
162
- * This handles the completion of animations and layer swapping
163
- */
164
- #setupTransitionTracking() {
165
- $effect(() => {
166
- const promises = this.transitionPromises;
167
-
168
- if (promises.length > 0) {
169
- const nextSlide = this.#getSlide(this.nextLayerLabel);
170
-
171
- if (!nextSlide) {
172
- return;
173
- }
174
-
175
- untrack(() => {
176
- this.#executeTransition(promises);
177
- });
178
- }
179
- });
180
- }
181
-
182
- /**
183
- * Set up reactivity to start transitions after component loading is complete
184
- */
185
- #setupLoadingTransitions() {
186
- $effect(() => {
187
- // Only start transitions when loading is complete and we have a next slide
188
- if (!this.isSlideLoading && this.#getSlide(this.nextLayerLabel)) {
189
- const currentSlide = this.#getSlide(this.currentLayerLabel);
190
- const nextSlide = this.#getSlide(this.nextLayerLabel);
191
-
192
- // Prepare the next layer for its entrance transition
193
- this.#updateLayer(this.nextLayerLabel, {
194
- z: Z_FRONT,
195
- visible: true,
196
- stageBeforeIn: true,
197
- transitions: nextSlide?.intro ?? []
198
- });
199
-
200
- // Prepare the current layer for its exit transition
201
- this.#updateLayer(this.currentLayerLabel, {
202
- z: Z_BACK,
203
- visible: true,
204
- stageBeforeOut: true,
205
- transitions: currentSlide?.outro ?? []
206
- });
207
-
208
- // Start transitions
209
- this.#applyTransitions();
210
- }
211
- });
212
- }
213
-
214
- /**
215
- * Execute the transition by waiting for all promises and then
216
- * completing the transition
217
- *
218
- * @param {HkPromise[]} promises - Array of transition promises to wait for
219
- */
220
- async #executeTransition(promises) {
221
- try {
222
- await Promise.allSettled(promises);
223
-
224
- untrack(() => {
225
- this.#completeTransition();
226
- });
227
- } catch (error) {
228
- console.log('transition promises cancelled', error);
229
- }
230
- }
231
-
232
- /**
233
- * Complete the transition by updating layers and swapping them
234
- */
235
- #completeTransition() {
236
- // Hide current layer and set stage to AFTER
237
- this.#updateLayer(this.currentLayerLabel, {
238
- z: Z_BACK,
239
- visible: false,
240
- stageAfter: true
241
- });
242
-
243
- // Set next layer stage to SHOW
244
- this.#updateLayer(this.nextLayerLabel, {
245
- z: Z_FRONT,
246
- visible: true,
247
- stageShow: true
248
- });
249
-
250
- // Remove slide from current layer
251
- this.#updateSlide(this.currentLayerLabel, null);
252
-
253
- // Swap current and next layer labels
254
- this.#swapLayers();
255
- }
256
-
257
- // /**
258
- // * Complete the transition by updating layers and swapping them
259
- // * Ensures proper cleanup of all stage states
260
- // */
261
- // #completeTransition() {
262
- // // Update current layer: hide it and set to AFTER state
263
- // this.#updateLayer(this.currentLayerLabel, {
264
- // z: Z_BACK,
265
- // visible: false,
266
- // stageIdle: false,
267
- // stageBeforeIn: false,
268
- // stageIn: false,
269
- // stageBeforeOut: false,
270
- // stageOut: false,
271
- // stageShow: false,
272
- // stageAfter: true
273
- // });
274
-
275
- // // Update next layer: show it and set to SHOW state
276
- // this.#updateLayer(this.nextLayerLabel, {
277
- // z: Z_FRONT,
278
- // visible: true,
279
- // stageIdle: false,
280
- // stageBeforeIn: false,
281
- // stageIn: false,
282
- // stageBeforeOut: false,
283
- // stageOut: false,
284
- // stageAfter: false,
285
- // stageShow: true
286
- // });
287
-
288
- // // Remove slide from current layer
289
- // this.#updateSlide(this.currentLayerLabel, null);
290
-
291
- // // Swap current and next layer labels
292
- // this.#swapLayers();
293
-
294
- // // Reset layer states after swap - crucial for next transition cycle
295
- // // Reset former next layer (now current) to idle after showing
296
- // setTimeout(() => {
297
- // this.#updateLayer(this.currentLayerLabel, {
298
- // stageShow: false,
299
- // stageIdle: true,
300
- // transitions: [] // Clear any lingering transitions
301
- // });
302
-
303
- // // Reset former current layer (now next) to idle after being hidden
304
- // this.#updateLayer(this.nextLayerLabel, {
305
- // stageAfter: false,
306
- // stageIdle: true,
307
- // transitions: [] // Clear any lingering transitions
308
- // });
309
- // }, 50); // Small delay to ensure DOM updates have completed
310
- // }
311
-
312
- /**
313
- * Swap the current and next layer labels
314
- */
315
- #swapLayers() {
316
- if (this.currentLayerLabel === LABEL_A) {
317
- this.currentLayerLabel = LABEL_B;
318
- this.nextLayerLabel = LABEL_A;
319
- } else {
320
- this.currentLayerLabel = LABEL_A;
321
- this.nextLayerLabel = LABEL_B;
322
- }
323
- }
324
-
325
- /**
326
- * Mark the slide as loaded, which triggers transitions to begin
327
- */
328
- finishSlideLoading() {
329
- this.isSlideLoading = false;
330
- }
331
-
332
- /**
333
- * Returns a controller object for managing manual loading
334
- * Components can use this to signal when they're done loading
335
- * or to cancel and go back to the previous slide
336
- *
337
- * @returns {LoadController} Object with loaded() and cancel() methods
338
- */
339
- getLoadingController() {
340
- // Mark that the controller was requested
341
- this.controllerRequested = true;
342
-
343
- console.debug('controllerRequested');
344
-
345
- return {
346
- /**
347
- * Call when component has finished loading
348
- */
349
- loaded: () => {
350
- this.finishSlideLoading();
351
- console.debug('finishSlideLoading');
352
- },
353
-
354
- /**
355
- * Call to cancel loading and return to previous slide
356
- */
357
- cancel: () => {
358
- // Return to previous slide if available
359
- const currentSlideName = this.currentSlideName;
360
- if (currentSlideName) {
361
- this.gotoSlide(currentSlideName);
362
- } else if (this.slides.length > 0) {
363
- // Fallback to first slide if no current slide
364
- this.gotoSlide(this.slides[0].name);
365
- }
366
- }
367
- };
368
- }
369
-
370
- /**
371
- * Configure the presentation
372
- *
373
- * @param {object} _
374
- * @param {boolean} [_.autostart=false] - Whether to start automatically
375
- * @param {string} [_.startSlide] - Name of the slide to start with
376
- * @param {Slide[]} [_.slides] - Array of slides for the presentation
377
- */
378
- configure({ slides, autostart = true, startSlide }) {
379
- untrack(() => {
380
- if (slides) {
381
- // Only update slides if provided
382
- this.slides = slides;
383
- }
384
-
385
- if ((autostart || startSlide) && this.slides?.length) {
386
- if (startSlide) {
387
- this.gotoSlide(startSlide);
388
- } else {
389
- this.#gotoSlide(this.slides[0]);
390
- }
391
- }
392
- });
393
- }
394
-
395
- /**
396
- * Configure the presentation slides
397
- *
398
- * @param {Slide[]} slides - Array of slides for the presentation
399
- */
400
- configureSlides(slides) {
401
- this.slides = slides ?? [];
402
- }
403
-
404
- /**
405
- * Transition to another slide by name
406
- *
407
- * @param {string} name - Name of the slide to transition to
408
- */
409
- async gotoSlide(name) {
410
- untrack(() => {
411
- const slide = findFirst(this.slides, { name });
412
-
413
- if (!slide) {
414
- console.log('available slides', this.slides);
415
- throw new Error(`Slide [${name}] has not been defined`);
416
- }
417
-
418
- this.#gotoSlide(slide);
419
- });
420
- }
421
-
422
- /**
423
- * Internal method to transition to another slide
424
- *
425
- * @param {Slide} slide - The slide to transition to
426
- */
427
- async #gotoSlide(slide) {
428
- if (this.busy) {
429
- throw new Error('Transition in progress');
430
- }
431
-
432
- // Reset controller requested flag
433
- this.controllerRequested = false;
434
-
435
- // Set loading state to true before starting transition
436
- this.isSlideLoading = true;
437
-
438
- // Add controller function to slide props if it has a component
439
- if (slide.data?.component) {
440
- // Get a presenter reference to pass to the slide
441
- const presenterRef = this.getPresenterRef();
442
-
443
- // Create a copy of the slide to avoid mutating the original
444
- const slideWithExtras = {
445
- ...slide,
446
- data: {
447
- ...slide.data,
448
- props: {
449
- ...(slide.data.props || {}),
450
- getLoadingController: () => this.getLoadingController(),
451
- presenter: presenterRef // Add presenter reference to props
452
- }
453
- }
454
- };
455
-
456
- // Add next slide to next layer with controller and presenter included
457
- this.#updateSlide(this.nextLayerLabel, slideWithExtras);
458
-
459
- // If a timeout is configured, automatically finish loading after delay
460
- if (this.loadingTimeout > 0) {
461
- setTimeout(() => {
462
- // Only auto-finish if the controller wasn't requested
463
- if (!this.controllerRequested && this.isSlideLoading) {
464
- // console.debug(
465
- // `Slide '${slide.name}' didn't request loading controller, auto-finishing.`
466
- // );
467
- this.finishSlideLoading();
468
- }
469
- }, this.loadingTimeout);
470
- }
471
- } else {
472
- // No component, so just use the slide as is
473
- this.#updateSlide(this.nextLayerLabel, slide);
474
- // No component to load, so finish loading immediately
475
- this.finishSlideLoading();
476
- }
477
-
478
- // Make next layer visible, move to front
479
- this.#updateLayer(this.nextLayerLabel, {
480
- z: Z_FRONT,
481
- visible: true
482
- });
483
- }
484
-
485
- /**
486
- * Apply transitions between current and next slide
487
- */
488
- #applyTransitions() {
489
- // Cancel existing transitions
490
- let transitionPromises = this.transitionPromises;
491
-
492
- for (const current of transitionPromises) {
493
- current.tryCancel();
494
- }
495
-
496
- // Start new transitions
497
- transitionPromises = [];
498
-
499
- const currentSlide = this.#getSlide(this.currentLayerLabel);
500
- const nextSlide = this.#getSlide(this.nextLayerLabel);
501
-
502
- // Apply transitions `out` from currentslide
503
- const transitionsOut = currentSlide?.outro;
504
-
505
- if (transitionsOut) {
506
- for (const transition of transitionsOut) {
507
- const promise = this.#applyTransition(transition);
508
- transitionPromises.push(promise);
509
- }
510
- }
511
-
512
- // Apply transitions `in` from next slide
513
- const transitionsIn = nextSlide?.intro;
514
-
515
- if (transitionsIn) {
516
- for (const transition of transitionsIn) {
517
- const promise = this.#applyTransition(transition);
518
- transitionPromises.push(promise);
519
- }
520
- }
521
-
522
- this.transitionPromises = transitionPromises;
523
- }
524
-
525
- /**
526
- * Apply a transition and return a transition promise
527
- *
528
- * @param {Transition} transition - The transition to apply
529
- * @returns {HkPromise} Promise that resolves when transition completes
530
- */
531
- #applyTransition(transition) {
532
- const delay = (transition.delay ?? 0) + (transition.duration ?? 0);
533
-
534
- if (0 === delay) {
535
- const promise = new HkPromise(() => {});
536
- promise.resolve(true);
537
- return promise;
538
- }
539
-
540
- let promise = new HkPromise((/** @type {function} */ resolve) => {
541
- if (delay) {
542
- setTimeout(() => {
543
- resolve(true);
544
- }, delay);
545
- }
546
- });
547
-
548
- return promise;
549
- }
550
-
551
- /**
552
- * Get slide by layer label
553
- *
554
- * @param {string} label - Layer label (A or B)
555
- * @returns {Slide|null} The slide for the specified layer or null
556
- */
557
- #getSlide(label) {
558
- if (label === LABEL_A) {
559
- return this.slideA;
560
- }
561
-
562
- if (label === LABEL_B) {
563
- return this.slideB;
564
- }
565
-
566
- return null;
567
- }
568
-
569
- /**
570
- * Update layer by label
571
- *
572
- * @param {string} label - Layer label (A or B)
573
- * @param {Layer} data - Layer data to update
574
- */
575
- #updateLayer(label, data) {
576
- if (label === LABEL_A) {
577
- this.layerA = data;
578
- return;
579
- }
580
-
581
- if (label === LABEL_B) {
582
- this.layerB = data;
583
- return;
584
- }
585
-
586
- throw new Error(`Missing layer [${label}]`);
587
- }
588
-
589
- /**
590
- * Update slide by label
591
- *
592
- * @param {string} label - Layer label (A or B)
593
- * @param {Slide|null} data - Slide data to update or null to clear
594
- */
595
- #updateSlide(label, data) {
596
- if (label === LABEL_A) {
597
- this.slideA = data;
598
- return;
599
- }
600
-
601
- if (label === LABEL_B) {
602
- this.slideB = data;
603
- return;
604
- }
605
-
606
- throw new Error(`Missing slide [${label}]`);
607
- }
608
- }
609
-
610
- /* -------------------------------------- Export create & get state functions */
611
-
612
- export const [createOrGetState, createState, getState] =
613
- defineStateContext(PresenterState);
@@ -1,125 +0,0 @@
1
- <script>
2
- /* ---------------------------------------------------------------- Imports */
3
-
4
- import { GridLayers } from '$lib/components/layout/index.js';
5
-
6
- import { createOrGetPresenterState } from './index.js';
7
- import { cssBefore, cssDuring } from './util.js';
8
-
9
- /* ------------------------------------------------------------------ Props */
10
-
11
- /**
12
- * @typedef {import("./typedef.js").Slide} Slide
13
- */
14
-
15
- /**
16
- * @typedef {import("./typedef.js").Layer} Layer
17
- */
18
-
19
- /**
20
- * @type {{
21
- * classes?: string,
22
- * slides?: import("./typedef.js").Slide[],
23
- * autostart?: boolean,
24
- * startSlide?: string,
25
- * instanceKey?: Symbol | string,
26
- * layoutSnippet: import('svelte').Snippet<[Slide|null, Layer]>
27
- * }}
28
- */
29
- let {
30
- // > Style
31
- classes,
32
-
33
- // > Functional
34
- slides,
35
- autostart = false,
36
- startSlide,
37
-
38
- // State
39
- instanceKey,
40
-
41
- // Snippets
42
- layoutSnippet
43
- } = $props();
44
-
45
- /* ------------------------------------------------------------------ State */
46
-
47
- const presenter = createOrGetPresenterState(instanceKey);
48
-
49
- $effect.pre(() => {
50
- // Configure presenter with slides if provided
51
- presenter.configure({ slides, autostart, startSlide });
52
- });
53
-
54
- let classesA = $state('');
55
- let classesB = $state('');
56
-
57
- let stylesA = $state('');
58
- let stylesB = $state('');
59
-
60
- //> Apply stage classes and styles
61
-
62
- $effect(() => {
63
- // > layerA
64
-
65
- const { stageBeforeIn, stageIn, stageBeforeOut, stageOut, transitions } =
66
- presenter.layerA;
67
-
68
- if (transitions && transitions.length) {
69
- if (stageBeforeIn || stageBeforeOut) {
70
- ({ style: stylesA, classes: classesA } = cssBefore(transitions));
71
- } else if (stageIn || stageOut) {
72
- setTimeout(() => {
73
- ({ style: stylesA, classes: classesA } = cssDuring(transitions));
74
- });
75
- }
76
- } else {
77
- stylesA = '';
78
- classesA = '';
79
- }
80
- });
81
-
82
- $effect(() => {
83
- // > layerB
84
-
85
- const { stageBeforeIn, stageIn, stageBeforeOut, stageOut, transitions } =
86
- presenter.layerB;
87
-
88
- if (transitions) {
89
- if (stageBeforeIn || stageBeforeOut) {
90
- ({ style: stylesB, classes: classesB } = cssBefore(transitions));
91
- } else if (stageIn || stageOut) {
92
- setTimeout(() => {
93
- ({ style: stylesB, classes: classesB } = cssDuring(transitions));
94
- });
95
- }
96
- } else {
97
- stylesB = '';
98
- classesB = '';
99
- }
100
- });
101
- </script>
102
-
103
- <GridLayers data-component="presenter" {classes}>
104
- <div
105
- style:z-index={presenter.layerA.z}
106
- style:visibility={presenter.layerA.visible ? 'visible' : 'hidden'}
107
- inert={presenter.busy}
108
- class="justify-self-stretch self-stretch overflow-hidden"
109
- >
110
- <div class={classesA} style={stylesA}>
111
- {@render layoutSnippet(presenter.slideA, presenter.layerA)}
112
- </div>
113
- </div>
114
-
115
- <div
116
- style:z-index={presenter.layerB.z}
117
- style:visibility={presenter.layerB.visible ? 'visible' : 'hidden'}
118
- inert={presenter.busy}
119
- class="justify-self-stretch self-stretch overflow-hidden"
120
- >
121
- <div class={classesB} style={stylesB}>
122
- {@render layoutSnippet(presenter.slideB, presenter.layerB)}
123
- </div>
124
- </div>
125
- </GridLayers>