@hkdigital/lib-sveltekit 0.2.6 → 0.2.8

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 (243) hide show
  1. package/README.md +135 -135
  2. package/dist/assets/autospuiten/car-paint-picker.js +41 -41
  3. package/dist/assets/autospuiten/labels.js +7 -7
  4. package/dist/classes/cache/IndexedDbCache.js +1407 -1407
  5. package/dist/classes/cache/MemoryResponseCache.js +138 -138
  6. package/dist/classes/cache/index.js +5 -5
  7. package/dist/classes/cache/typedef.js +41 -41
  8. package/dist/classes/data/IterableTree.js +243 -243
  9. package/dist/classes/data/Selector.js +190 -190
  10. package/dist/classes/data/index.js +2 -2
  11. package/dist/classes/events/EventEmitter.js +275 -275
  12. package/dist/classes/events/index.js +2 -2
  13. package/dist/classes/index.js +4 -4
  14. package/dist/classes/logging/Logger.js +158 -158
  15. package/dist/classes/logging/constants.js +18 -18
  16. package/dist/classes/logging/index.js +4 -4
  17. package/dist/classes/promise/HkPromise.js +377 -377
  18. package/dist/classes/promise/index.js +1 -1
  19. package/dist/classes/services/ServiceBase.js +409 -409
  20. package/dist/classes/services/ServiceManager.js +1114 -1114
  21. package/dist/classes/services/constants.js +12 -12
  22. package/dist/classes/services/index.js +5 -5
  23. package/dist/classes/stores/SubscribersCount.js +107 -107
  24. package/dist/classes/stores/index.js +1 -1
  25. package/dist/classes/streams/LogTransformStream.js +19 -19
  26. package/dist/classes/streams/ServerEventsStore.js +110 -110
  27. package/dist/classes/streams/TimeStampSource.js +26 -26
  28. package/dist/classes/streams/index.js +3 -3
  29. package/dist/classes/svelte/audio/AudioLoader.svelte.js +58 -58
  30. package/dist/classes/svelte/audio/AudioScene.svelte.js +324 -324
  31. package/dist/classes/svelte/audio/mocks.js +35 -35
  32. package/dist/classes/svelte/finite-state-machine/FiniteStateMachine.svelte.js +133 -133
  33. package/dist/classes/svelte/finite-state-machine/index.js +1 -1
  34. package/dist/classes/svelte/image/ImageLoader.svelte.js +45 -45
  35. package/dist/classes/svelte/image/ImageScene.svelte.js +249 -249
  36. package/dist/classes/svelte/image/ImageVariantsLoader.svelte.js +152 -152
  37. package/dist/classes/svelte/image/index.js +4 -4
  38. package/dist/classes/svelte/image/mocks.js +35 -35
  39. package/dist/classes/svelte/image/typedef.js +8 -8
  40. package/dist/classes/svelte/index.js +14 -14
  41. package/dist/classes/svelte/loading-state-machine/LoadingStateMachine.svelte.js +109 -109
  42. package/dist/classes/svelte/loading-state-machine/constants.js +16 -16
  43. package/dist/classes/svelte/loading-state-machine/index.js +3 -3
  44. package/dist/classes/svelte/network-loader/NetworkLoader.svelte.js +338 -338
  45. package/dist/classes/svelte/network-loader/constants.js +3 -3
  46. package/dist/classes/svelte/network-loader/index.js +3 -3
  47. package/dist/classes/svelte/network-loader/mocks.js +30 -30
  48. package/dist/classes/svelte/network-loader/typedef.js +8 -8
  49. package/dist/components/area/HkArea.svelte +49 -49
  50. package/dist/components/area/HkGridArea.svelte +77 -77
  51. package/dist/components/area/index.js +2 -2
  52. package/dist/components/buttons/button/Button.svelte +82 -82
  53. package/dist/components/buttons/button-icon-steeze/SteezeIconButton.svelte +30 -30
  54. package/dist/components/buttons/button-text/TextButton.svelte +21 -21
  55. package/dist/components/buttons/index.js +3 -3
  56. package/dist/components/debug/debug-panel-design-scaling/DebugPanelDesignScaling.svelte +146 -146
  57. package/dist/components/debug/index.js +1 -1
  58. package/dist/components/drag-drop/DragController.d.ts +0 -20
  59. package/dist/components/drag-drop/DragController.js +44 -113
  60. package/dist/components/drag-drop/DragDropContext.svelte +110 -103
  61. package/dist/components/drag-drop/Draggable.svelte +512 -492
  62. package/dist/components/drag-drop/Draggable.svelte.d.ts +8 -2
  63. package/dist/components/drag-drop/DropZoneArea.svelte +119 -119
  64. package/dist/components/drag-drop/DropZoneList.svelte +125 -125
  65. package/dist/components/drag-drop/{DropZone.svelte → Dropzone.svelte} +258 -258
  66. package/dist/components/drag-drop/drag-state.svelte.js +319 -323
  67. package/dist/components/drag-drop/index.js +7 -7
  68. package/dist/components/drag-drop/util.js +85 -85
  69. package/dist/components/hkdev/blocks/TextBlock.svelte +46 -46
  70. package/dist/components/hkdev/buttons/CheckButton.svelte +62 -62
  71. package/dist/components/icons/HkIcon.svelte +86 -86
  72. package/dist/components/icons/HkTabIcon.svelte +116 -116
  73. package/dist/components/icons/SteezeIcon.svelte +97 -97
  74. package/dist/components/icons/index.js +6 -6
  75. package/dist/components/icons/typedef.js +16 -16
  76. package/dist/components/index.js +2 -2
  77. package/dist/components/inputs/index.js +1 -1
  78. package/dist/components/inputs/text-input/TestTextInput.svelte__ +102 -102
  79. package/dist/components/inputs/text-input/TextInput.svelte +223 -223
  80. package/dist/components/inputs/text-input/TextInput.svelte___ +83 -83
  81. package/dist/components/inputs/text-input/assets/IconInvalid.svelte +14 -14
  82. package/dist/components/inputs/text-input/assets/IconValid.svelte +12 -12
  83. package/dist/components/layout/grid-layers/GridLayers.svelte +63 -63
  84. package/dist/components/layout/grid-layers/util.js +74 -74
  85. package/dist/components/layout/index.js +1 -1
  86. package/dist/components/panels/index.js +1 -1
  87. package/dist/components/panels/panel/Panel.svelte +43 -43
  88. package/dist/components/rows/index.js +3 -3
  89. package/dist/components/rows/panel-grid-row/PanelGridRow.svelte +104 -104
  90. package/dist/components/rows/panel-row-2/PanelRow2.svelte +40 -40
  91. package/dist/components/tab-bar/HkTabBar.state.svelte.js +149 -149
  92. package/dist/components/tab-bar/HkTabBar.svelte +74 -74
  93. package/dist/components/tab-bar/HkTabBarSelector.state.svelte.js +93 -93
  94. package/dist/components/tab-bar/HkTabBarSelector.svelte +49 -49
  95. package/dist/components/tab-bar/index.js +17 -17
  96. package/dist/components/tab-bar/typedef.js +11 -11
  97. package/dist/config/imagetools-config.js +189 -189
  98. package/dist/config/imagetools.d.ts +72 -72
  99. package/dist/constants/bases.js +13 -13
  100. package/dist/constants/errors/api.js +9 -9
  101. package/dist/constants/errors/generic.js +5 -5
  102. package/dist/constants/errors/index.js +3 -3
  103. package/dist/constants/errors/jwt.js +5 -5
  104. package/dist/constants/http/headers.js +6 -6
  105. package/dist/constants/http/index.js +2 -2
  106. package/dist/constants/http/methods.js +2 -2
  107. package/dist/constants/index.js +3 -3
  108. package/dist/constants/mime/application.js +5 -5
  109. package/dist/constants/mime/audio.js +13 -13
  110. package/dist/constants/mime/image.js +3 -3
  111. package/dist/constants/mime/index.js +4 -4
  112. package/dist/constants/mime/text.js +2 -2
  113. package/dist/constants/regexp/index.js +31 -31
  114. package/dist/constants/regexp/inspiratie.js__ +95 -95
  115. package/dist/constants/regexp/text.js +49 -49
  116. package/dist/constants/regexp/user.js +32 -32
  117. package/dist/constants/regexp/web.js +3 -3
  118. package/dist/constants/state-labels/drag-states.js +6 -6
  119. package/dist/constants/state-labels/drop-states.js +6 -6
  120. package/dist/constants/state-labels/input-states.js +11 -11
  121. package/dist/constants/state-labels/submit-states.js +4 -4
  122. package/dist/constants/time.js +28 -28
  123. package/dist/css/utilities.css +43 -43
  124. package/dist/design/design-config.js +73 -73
  125. package/dist/design/tailwind-theme-extend.js +158 -158
  126. package/dist/features/button-group/ButtonGroup.svelte +82 -82
  127. package/dist/features/button-group/typedef.js +10 -10
  128. package/dist/features/compare-left-right/CompareLeftRight.svelte +179 -179
  129. package/dist/features/compare-left-right/index.js +1 -1
  130. package/dist/features/game-box/GameBox.svelte +577 -577
  131. package/dist/features/game-box/gamebox.util.js +83 -83
  132. package/dist/features/hk-app-layout/HkAppLayout.state.svelte.js +25 -25
  133. package/dist/features/hk-app-layout/HkAppLayout.svelte +251 -251
  134. package/dist/features/image-box/ImageBox.svelte +210 -210
  135. package/dist/features/image-box/index.js +5 -5
  136. package/dist/features/image-box/typedef.js +32 -32
  137. package/dist/features/index.js +23 -23
  138. package/dist/features/presenter/ImageSlide.svelte +64 -64
  139. package/dist/features/presenter/Presenter.state.svelte.js +638 -638
  140. package/dist/features/presenter/Presenter.svelte +142 -142
  141. package/dist/features/presenter/constants.js +7 -7
  142. package/dist/features/presenter/index.js +10 -10
  143. package/dist/features/presenter/typedef.js +106 -106
  144. package/dist/features/presenter/util.js +210 -210
  145. package/dist/features/virtual-viewport/VirtualViewport.svelte +196 -196
  146. package/dist/schemas/index.js +1 -1
  147. package/dist/schemas/validate-url.js +180 -180
  148. package/dist/server/index.js +1 -1
  149. package/dist/server/logger.js +94 -94
  150. package/dist/states/index.js +1 -1
  151. package/dist/states/navigation.svelte.js +55 -55
  152. package/dist/stores/index.js +1 -1
  153. package/dist/stores/theme.js +80 -80
  154. package/dist/themes/hkdev/components/blocks/text-block.css +41 -41
  155. package/dist/themes/hkdev/components/boxes/game-box.css +12 -12
  156. package/dist/themes/hkdev/components/buttons/button-icon-steeze.css +22 -22
  157. package/dist/themes/hkdev/components/buttons/button-text.css +32 -32
  158. package/dist/themes/hkdev/components/buttons/button.css +146 -146
  159. package/dist/themes/hkdev/components/buttons/skip-button.css +6 -6
  160. package/dist/themes/hkdev/components/drag-drop/draggable.css +73 -73
  161. package/dist/themes/hkdev/components/drag-drop/drop-zone.css +48 -48
  162. package/dist/themes/hkdev/components/icons/icon-steeze.css +22 -22
  163. package/dist/themes/hkdev/components/inputs/text-input.css +104 -104
  164. package/dist/themes/hkdev/components/panels/panel.css +27 -27
  165. package/dist/themes/hkdev/components/rows/panel-grid-row.css +6 -6
  166. package/dist/themes/hkdev/components/rows/panel-row-2.css +7 -7
  167. package/dist/themes/hkdev/components.css +53 -53
  168. package/dist/themes/hkdev/debug.css +1 -1
  169. package/dist/themes/hkdev/global/layout.css +39 -39
  170. package/dist/themes/hkdev/global/on-colors.css +53 -53
  171. package/dist/themes/hkdev/globals.css +11 -11
  172. package/dist/themes/hkdev/responsive.css +12 -12
  173. package/dist/themes/hkdev/theme-ext.js +15 -15
  174. package/dist/themes/hkdev/theme.js +235 -235
  175. package/dist/themes/index.js +1 -1
  176. package/dist/typedef/context.js +6 -6
  177. package/dist/typedef/drag.js +25 -25
  178. package/dist/typedef/drop.js +12 -12
  179. package/dist/typedef/image.js +38 -38
  180. package/dist/typedef/index.js +4 -4
  181. package/dist/util/array/index.js +436 -436
  182. package/dist/util/bases/base58.js +262 -262
  183. package/dist/util/bases/index.js +1 -1
  184. package/dist/util/compare/index.js +247 -247
  185. package/dist/util/css/css-vars.js +83 -83
  186. package/dist/util/css/index.js +1 -1
  187. package/dist/util/design-system/components/states.js +22 -22
  188. package/dist/util/design-system/css/clamp.js +66 -66
  189. package/dist/util/design-system/css/root-design-vars.js +102 -102
  190. package/dist/util/design-system/index.js +5 -5
  191. package/dist/util/design-system/layout/scaling.js +228 -228
  192. package/dist/util/design-system/skeleton.js +208 -208
  193. package/dist/util/design-system/tailwind.js +288 -288
  194. package/dist/util/env/index.js +9 -9
  195. package/dist/util/expect/arrays.js +47 -47
  196. package/dist/util/expect/index.js +259 -259
  197. package/dist/util/expect/primitives.js +55 -55
  198. package/dist/util/expect/url.js +60 -60
  199. package/dist/util/function/index.js +218 -218
  200. package/dist/util/geo/index.js +26 -26
  201. package/dist/util/http/caching.js +263 -263
  202. package/dist/util/http/errors.js +97 -97
  203. package/dist/util/http/headers.js +75 -75
  204. package/dist/util/http/http-request.js +379 -379
  205. package/dist/util/http/index.js +22 -22
  206. package/dist/util/http/json-request.js +224 -224
  207. package/dist/util/http/mocks.js +65 -65
  208. package/dist/util/http/response.js +294 -294
  209. package/dist/util/http/typedef.js +93 -93
  210. package/dist/util/http/url.js +52 -52
  211. package/dist/util/image/index.js +86 -86
  212. package/dist/util/index.js +2 -2
  213. package/dist/util/is/index.js +140 -140
  214. package/dist/util/iterate/index.js +234 -234
  215. package/dist/util/object/index.js +1361 -1361
  216. package/dist/util/singleton/index.js +97 -97
  217. package/dist/util/string/array-path.js +75 -75
  218. package/dist/util/string/convert.js +54 -54
  219. package/dist/util/string/fs.js +226 -226
  220. package/dist/util/string/index.js +5 -5
  221. package/dist/util/string/interpolate.js +61 -61
  222. package/dist/util/string/pad.js +10 -10
  223. package/dist/util/svelte/index.js +4 -4
  224. package/dist/util/svelte/loading/loading-tracker.svelte.js +108 -108
  225. package/dist/util/svelte/observe/index.js +49 -49
  226. package/dist/util/svelte/state-context/index.js +117 -117
  227. package/dist/util/svelte/wait/index.js +38 -38
  228. package/dist/util/sveltekit/index.js +1 -1
  229. package/dist/util/sveltekit/route-folders/index.js +101 -101
  230. package/dist/util/time/index.js +323 -323
  231. package/dist/util/unique/index.js +249 -249
  232. package/dist/valibot/date.js__ +10 -10
  233. package/dist/valibot/index.js +9 -9
  234. package/dist/valibot/url.js +95 -95
  235. package/dist/valibot/user.js +23 -23
  236. package/dist/zod/all.js +33 -33
  237. package/dist/zod/generic.js +11 -11
  238. package/dist/zod/javascript.js +32 -32
  239. package/dist/zod/user.js +16 -16
  240. package/dist/zod/web.js +52 -52
  241. package/package.json +112 -112
  242. package/dist/components/layout/grid-layers/GridLayers.svelte__heightFrom__ +0 -372
  243. package/dist/util/http/test-data__/content-length-test-hkdigital-small.V4HfZyBQ.avif +0 -0
@@ -1,638 +1,638 @@
1
- import { tick } from 'svelte';
2
-
3
- import { findFirst } from '../../util/array/index.js';
4
-
5
- import { untrack } from 'svelte';
6
-
7
- import { HkPromise } from '../../classes/promise/index.js';
8
-
9
- import { STAGE_BEFORE, STAGE_SHOW } from './constants.js';
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 {import("./typedef").PresenterRef} PresenterRef
25
- */
26
-
27
- /**
28
- * @typedef {import("./typedef").LoadController} LoadController
29
- */
30
-
31
- /**
32
- * @typedef {import("./typedef").ListenerParams} ListenerParams
33
- */
34
-
35
- const Z_BACK = 0;
36
- const Z_FRONT = 10;
37
-
38
- const LABEL_A = 'A';
39
- const LABEL_B = 'B';
40
-
41
- export class PresenterState {
42
- /** @type {Slide[]} */
43
- slides = $state.raw([]);
44
-
45
- /** @type {Layer} */
46
- layerA = $state.raw({ z: Z_BACK, visible: false, stageIdle: true });
47
-
48
- /** @type {Layer} */
49
- layerB = $state.raw({ z: Z_FRONT, visible: false, stageIdle: true });
50
-
51
- /** @type {Slide|null} */
52
- slideA = $state.raw(null);
53
-
54
- /** @type {Slide|null} */
55
- slideB = $state.raw(null);
56
-
57
- /** @type {string} */
58
- currentLayerLabel = $state(LABEL_B);
59
-
60
- /** @type {string} */
61
- nextLayerLabel = $state(LABEL_A);
62
-
63
- /** @type {HkPromise[]} */
64
- transitionPromises = $state.raw([]);
65
-
66
- /** @type {HkPromise} */
67
- slideLoadingPromise = null;
68
-
69
- /** @type {boolean} */
70
- isSlideLoading = $state(false);
71
-
72
- /** @type {boolean} */
73
- loadingSpinner = $state(false);
74
-
75
- /** @type {boolean} */
76
- busy = $derived.by(() => {
77
- const { layerA, layerB, isSlideLoading } = this;
78
-
79
- const layerAStable =
80
- layerA.stageShow || layerA.stageAfter || layerA.stageIdle;
81
- const layerBStable =
82
- layerB.stageShow || layerB.stageAfter || layerB.stageIdle;
83
-
84
- return !(layerAStable && layerBStable) || isSlideLoading;
85
- });
86
-
87
- /** @type {string} */
88
- currentSlideName = $derived.by(() => {
89
- const currentSlide = this.#getSlide(this.currentLayerLabel);
90
- return currentSlide?.name || '';
91
- });
92
-
93
- /** @type {string} */
94
- nextSlideName;
95
-
96
- /** @type {string} */
97
- pendingSlideName;
98
-
99
- /** @type {boolean} */
100
- configured = false;
101
-
102
- /** @type {Map<Symbol, ( params: ListenerParams ) => void>} */
103
- onBeforeListeners = new Map();
104
-
105
- /** @type {Map<Symbol, ( params: ListenerParams ) => void>} */
106
- onShowListeners = new Map();
107
-
108
- /**
109
- * Initialize the presenter state and set up reactivity
110
- */
111
- constructor() {
112
- // this.#setupStageTransitions();
113
-
114
- let timeout;
115
-
116
- $effect((slideLoadingPromise) => {
117
- if (this.isSlideLoading) {
118
- // Enable spinner after a short delay
119
- clearTimeout(timeout);
120
- setTimeout(() => {
121
- untrack(() => {
122
- if (this.isSlideLoading) {
123
- this.loadingSpinner = true;
124
- } else {
125
- this.loadingSpinner = false;
126
- }
127
- });
128
- }, 500);
129
- } else {
130
- this.loadingSpinner = false;
131
- }
132
- });
133
- }
134
-
135
- /**
136
- * Set up reactivity for stage transitions between the before/after states
137
- * This handles the animation timing for both layers
138
- */
139
- // #setupStageTransitions() {
140
- // // Handle layer A stage transitions
141
- // $effect(() => {
142
- // if (this.layerA.stageBeforeIn || this.layerA.stageBeforeOut) {
143
- // this.layerA = this.#processStageTransition(this.layerA);
144
- // }
145
- // });
146
-
147
- // // Handle layer B stage transitions
148
- // $effect(() => {
149
- // if (this.layerB.stageBeforeIn || this.layerB.stageBeforeOut) {
150
- // this.layerB = this.#processStageTransition(this.layerB);
151
- // }
152
- // });
153
- // }
154
-
155
- /**
156
- * Process a single stage transition for a layer
157
- *
158
- * @param {Layer} layer - The layer to process
159
- * @returns {Layer} - The updated layer with new stage
160
- */
161
- #processStageTransition(layer) {
162
- const updatedLayer = { ...layer };
163
-
164
- if (updatedLayer.stageBeforeIn) {
165
- delete updatedLayer.stageBeforeIn;
166
- updatedLayer.stageIn = true;
167
- } else if (updatedLayer.stageBeforeOut) {
168
- delete updatedLayer.stageBeforeOut;
169
- updatedLayer.stageOut = true;
170
- }
171
-
172
- return updatedLayer;
173
- }
174
-
175
- /**
176
- * Waiting for all transition timing promises to finish,
177
- * this should be the same amount of time as it takes for the real
178
- * transitions to finish
179
- *
180
- * @param {HkPromise[]} promises
181
- * Array of transition promises to wait for
182
- */
183
- async #waitForTransitionPromises(promises) {
184
- try {
185
- // console.debug('waitForTransitionPromises', promises);
186
-
187
- await Promise.allSettled(promises);
188
-
189
- // console.debug('waitForTransitionPromises:done', promises);
190
-
191
- untrack(() => {
192
- this.#completeTransition();
193
- });
194
- } catch (error) {
195
- console.log('transition promises cancelled', error);
196
- }
197
- }
198
-
199
- /**
200
- * Complete the transition by updating layers and swapping them
201
- */
202
- #completeTransition() {
203
- // Hide current layer and set stage to AFTER
204
- this.#updateLayer(this.currentLayerLabel, {
205
- z: Z_BACK,
206
- visible: false,
207
- stageAfter: true
208
- });
209
-
210
- // Set next layer stage to SHOW
211
- this.#updateLayer(this.nextLayerLabel, {
212
- z: Z_FRONT,
213
- visible: true,
214
- stageShow: true
215
- });
216
-
217
- // Remove slide from current layer
218
- this.#updateSlide(this.currentLayerLabel, null);
219
-
220
- // Swap current and next layer labels
221
- this.#swapLayers();
222
- }
223
-
224
- /**
225
- * Swap the current and next layer labels
226
- */
227
- #swapLayers() {
228
- if (this.currentLayerLabel === LABEL_A) {
229
- this.currentLayerLabel = LABEL_B;
230
- this.nextLayerLabel = LABEL_A;
231
- } else {
232
- this.currentLayerLabel = LABEL_A;
233
- this.nextLayerLabel = LABEL_B;
234
- }
235
- }
236
-
237
- /**
238
- * Configure the presentation
239
- *
240
- * @param {object} _
241
- * @param {Slide[]} [_.slides] - Array of slides for the presentation
242
- */
243
- configure({ slides }) {
244
- this.configured = true;
245
-
246
- if (slides) {
247
- // Only update slides if provided
248
- this.slides = slides;
249
- }
250
- }
251
-
252
- /**
253
- * Configure the presentation slides
254
- *
255
- * @param {Slide[]} slides - Array of slides for the presentation
256
- */
257
- configureSlides(slides) {
258
- this.slides = slides ?? [];
259
- }
260
-
261
- /**
262
- * Transition to another slide by name
263
- *
264
- * @param {string} name - Name of the slide to transition to
265
- */
266
- async gotoSlide(name) {
267
- // throw new Error('gotoSlide');
268
-
269
- untrack(() => {
270
- const slide = findFirst(this.slides, { name });
271
-
272
- if (!slide) {
273
- console.log('available slides', this.slides);
274
- throw new Error(`Slide [${name}] has not been defined`);
275
- }
276
-
277
- this.#gotoSlide(slide);
278
- });
279
- }
280
-
281
- /**
282
- * Internal method to transition to another slide
283
- *
284
- * @param {Slide} slide - The slide to transition to
285
- */
286
- async #gotoSlide(slide) {
287
- if (!this.configured) {
288
- throw new Error('Not configured yet');
289
- }
290
-
291
- if (slide.name === this.currentSlideName) {
292
- //throw new Error(`gotoSlide cannot transition to current slide`);
293
- console.error(`gotoSlide cannot transition to current slide`);
294
- return;
295
- }
296
-
297
- this.nextSlideName = slide.name;
298
-
299
- if (this.busy) {
300
- this.pendingSlideName = slide.name;
301
- return;
302
- }
303
-
304
- this.#callOnBeforeListeners();
305
-
306
- this.slideLoadingPromise = null;
307
-
308
- // Get a presenter reference to pass to the slide
309
- const presenterRef = this.getPresenterRef();
310
-
311
- // Create a copy of the slide to avoid mutating the original
312
- const slideWithProps = {
313
- ...slide,
314
- data: {
315
- ...slide.data,
316
- props: {
317
- ...(slide.data.props || {}),
318
- getLoadingController: () => {
319
- this.isSlideLoading = true;
320
- this.slideLoadingPromise = new HkPromise(() => {});
321
-
322
- return this.#getLoadingController();
323
- // this.slideLoadingPromise should be a HkPromise now
324
- // console.log('slideLoadingPromise', this.slideLoadingPromise);
325
- },
326
- presenter: presenterRef // Add presenter reference to props
327
- }
328
- }
329
- };
330
-
331
- // console.debug('Checkpoint 1');
332
-
333
- // Add next slide to next layer
334
- this.#updateSlide(this.nextLayerLabel, slideWithProps);
335
-
336
- // console.debug('Checkpoint 2');
337
-
338
- await tick();
339
-
340
- // console.debug('Checkpoint 3');
341
-
342
- if (this.slideLoadingPromise) {
343
- // console.debug('Waiting for slide to load');
344
- // @ts-ignore
345
- await this.slideLoadingPromise;
346
- this.isSlideLoading = false;
347
- // console.debug('Done waiting for slide loading');
348
- }
349
-
350
- const currentSlide = this.#getSlide(this.currentLayerLabel);
351
- const nextSlide = this.#getSlide(this.nextLayerLabel);
352
-
353
- // console.debug('Checkpoint 4', currentSlide, nextSlide);
354
-
355
- // Make next layer visible, move to front, and prepare for
356
- // transition in
357
- this.#updateLayer(this.nextLayerLabel, {
358
- z: Z_FRONT,
359
- visible: true,
360
- stageBeforeIn: true,
361
- transitions: nextSlide?.intro ?? []
362
- });
363
-
364
- // Move current layer to back, keep visible, and prepare for
365
- // transition out
366
- this.#updateLayer(this.currentLayerLabel, {
367
- z: Z_BACK,
368
- visible: true,
369
- stageBeforeOut: true,
370
- transitions: currentSlide?.outro ?? []
371
- });
372
-
373
- // console.debug('Checkpoint 5');
374
-
375
- // Wait briefly to ensure the stageBeforeIn/stageBeforeOut states are rendered
376
- await tick();
377
-
378
- // Now manually process the transitions for both layers
379
- const layerA = this.layerA;
380
- const layerB = this.layerB;
381
-
382
- // Process stageBeforeIn transition for both layers
383
- if (layerA.stageBeforeIn) {
384
- this.layerA = this.#processStageTransition(layerA);
385
- }
386
-
387
- if (layerB.stageBeforeIn) {
388
- this.layerB = this.#processStageTransition(layerB);
389
- }
390
-
391
- // Wait for another tick to ensure the stageIn states are rendered
392
- await tick();
393
-
394
- // Process stageBeforeOut transition for both layers
395
- if (layerA.stageBeforeOut) {
396
- this.layerA = this.#processStageTransition(layerA);
397
- }
398
-
399
- if (layerB.stageBeforeOut) {
400
- this.layerB = this.#processStageTransition(layerB);
401
- }
402
-
403
- // console.debug('Checkpoint 7');
404
-
405
- // Start transitions
406
- this.#createTransitionPromises();
407
-
408
- // console.debug('Checkpoint 8');
409
-
410
- await this.#waitForTransitionPromises(this.transitionPromises);
411
-
412
- // Check if there's a pending slide transition
413
- if (this.pendingSlideName) {
414
- const pendingName = this.pendingSlideName;
415
-
416
- this.nextSlideName = pendingName;
417
- this.pendingSlideName = null;
418
-
419
- untrack(() => {
420
- if (pendingName !== this.currentSlideName) {
421
- this.gotoSlide(pendingName);
422
- }
423
- });
424
- } else {
425
- this.nextSlideName = null;
426
- }
427
-
428
- this.#callOnShowListeners();
429
- }
430
-
431
- #callOnBeforeListeners() {
432
- let nextSlideName = this.nextSlideName;
433
-
434
- for (const fn of this.onBeforeListeners.values()) {
435
- fn({ stage: STAGE_BEFORE, slideName: nextSlideName });
436
- }
437
- }
438
-
439
- #callOnShowListeners() {
440
- let currentSlideName = this.currentSlideName;
441
-
442
- for (const fn of this.onShowListeners.values()) {
443
- fn({ stage: STAGE_SHOW, slideName: currentSlideName });
444
- }
445
- }
446
-
447
- /**
448
- * Create transition promises that can be used to determine the timing
449
- * of the transitions between current and next slide
450
- */
451
- #createTransitionPromises() {
452
- // Cancel existing transitions
453
- let transitionPromises = this.transitionPromises;
454
-
455
- for (const current of transitionPromises) {
456
- current.tryCancel();
457
- }
458
-
459
- // Start new transitions
460
- transitionPromises = [];
461
-
462
- const currentSlide = this.#getSlide(this.currentLayerLabel);
463
- const nextSlide = this.#getSlide(this.nextLayerLabel);
464
-
465
- // Apply transitions `out` from currentslide
466
- const transitionsOut = currentSlide?.outro;
467
-
468
- // console.log('transitionsOut', transitionsOut);
469
-
470
- if (transitionsOut) {
471
- for (const transition of transitionsOut) {
472
- const promise = this.#createTransitionPromise(transition);
473
- transitionPromises.push(promise);
474
- }
475
- }
476
-
477
- // Apply transitions `in` from next slide
478
- const transitionsIn = nextSlide?.intro;
479
-
480
- // console.log('transitionsIn', transitionsIn);
481
-
482
- if (transitionsIn) {
483
- for (const transition of transitionsIn) {
484
- const promise = this.#createTransitionPromise(transition);
485
- transitionPromises.push(promise);
486
- }
487
- }
488
-
489
- this.transitionPromises = transitionPromises;
490
- }
491
-
492
- /**
493
- * Create a transition promise for the specified transition
494
- *
495
- *
496
- * @param {Transition} transition - The transition to apply
497
- *
498
- * @returns {HkPromise}
499
- * Promise that resolves after the same amount of time that it
500
- * takes for the transition to finish
501
- */
502
- #createTransitionPromise(transition) {
503
- const delay = (transition.delay ?? 0) + (transition.duration ?? 0);
504
-
505
- if (0 === delay) {
506
- const promise = new HkPromise(() => {});
507
- promise.resolve(true);
508
- return promise;
509
- }
510
-
511
- let promise = new HkPromise((/** @type {function} */ resolve) => {
512
- if (delay) {
513
- setTimeout(() => {
514
- resolve(true);
515
- }, delay);
516
- }
517
- });
518
-
519
- return promise;
520
- }
521
-
522
- /**
523
- * Get slide by layer label
524
- *
525
- * @param {string} label - Layer label (A or B)
526
- * @returns {Slide|null} The slide for the specified layer or null
527
- */
528
- #getSlide(label) {
529
- if (label === LABEL_A) {
530
- return this.slideA;
531
- }
532
-
533
- if (label === LABEL_B) {
534
- return this.slideB;
535
- }
536
-
537
- return null;
538
- }
539
-
540
- /**
541
- * Update layer by label
542
- *
543
- * @param {string} label - Layer label (A or B)
544
- * @param {Layer} data - Layer data to update
545
- */
546
- #updateLayer(label, data) {
547
- if (label === LABEL_A) {
548
- this.layerA = data;
549
- return;
550
- }
551
-
552
- if (label === LABEL_B) {
553
- this.layerB = data;
554
- return;
555
- }
556
-
557
- throw new Error(`Missing layer [${label}]`);
558
- }
559
-
560
- /**
561
- * Update slide by label
562
- *
563
- * @param {string} label - Layer label (A or B)
564
- * @param {Slide|null} data - Slide data to update or null to clear
565
- */
566
- #updateSlide(label, data) {
567
- if (label === LABEL_A) {
568
- this.slideA = data;
569
- return;
570
- }
571
-
572
- if (label === LABEL_B) {
573
- this.slideB = data;
574
- return;
575
- }
576
-
577
- throw new Error(`Missing slide [${label}]`);
578
- }
579
-
580
- /**
581
- * Returns a simplified presenter reference with essential methods
582
- * for slide components to use
583
- *
584
- * @returns {PresenterRef} A reference object with presenter methods
585
- */
586
- getPresenterRef() {
587
- return {
588
- gotoSlide: (name) => this.gotoSlide(name),
589
- getCurrentSlideName: () => this.currentSlideName,
590
- onBefore: (callback) => {
591
- const key = Symbol();
592
- this.onBeforeListeners.set(key, callback);
593
-
594
- return () => {
595
- this.onBeforeListeners.delete(key);
596
- };
597
- },
598
- onShow: (callback) => {
599
- const key = Symbol();
600
- this.onShowListeners.set(key, callback);
601
-
602
- return () => {
603
- this.onShowListeners.delete(key);
604
- };
605
- }
606
- };
607
- }
608
-
609
- /**
610
- * Returns a controller object for managing manual loading
611
- * Components can use this to signal when they're done loading
612
- * or to cancel and go back to the previous slide
613
- *
614
- * @returns {LoadController}
615
- * Object with loaded() and cancel() methods
616
- */
617
- #getLoadingController() {
618
- // console.debug('getLoadingController was called');
619
-
620
- return {
621
- /**
622
- * Call when component has finished loading
623
- */
624
- loaded: () => {
625
- // console.debug('Slide said loading has completed');
626
- this.slideLoadingPromise?.tryResolve();
627
- },
628
-
629
- /**
630
- * Call to cancel loading and return to previous slide
631
- */
632
- cancel: () => {
633
- // console.debug('Slide said loading has cancelled');
634
- this.slideLoadingPromise?.tryReject();
635
- }
636
- };
637
- }
638
- }
1
+ import { tick } from 'svelte';
2
+
3
+ import { findFirst } from '../../util/array/index.js';
4
+
5
+ import { untrack } from 'svelte';
6
+
7
+ import { HkPromise } from '../../classes/promise/index.js';
8
+
9
+ import { STAGE_BEFORE, STAGE_SHOW } from './constants.js';
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 {import("./typedef").PresenterRef} PresenterRef
25
+ */
26
+
27
+ /**
28
+ * @typedef {import("./typedef").LoadController} LoadController
29
+ */
30
+
31
+ /**
32
+ * @typedef {import("./typedef").ListenerParams} ListenerParams
33
+ */
34
+
35
+ const Z_BACK = 0;
36
+ const Z_FRONT = 10;
37
+
38
+ const LABEL_A = 'A';
39
+ const LABEL_B = 'B';
40
+
41
+ export class PresenterState {
42
+ /** @type {Slide[]} */
43
+ slides = $state.raw([]);
44
+
45
+ /** @type {Layer} */
46
+ layerA = $state.raw({ z: Z_BACK, visible: false, stageIdle: true });
47
+
48
+ /** @type {Layer} */
49
+ layerB = $state.raw({ z: Z_FRONT, visible: false, stageIdle: true });
50
+
51
+ /** @type {Slide|null} */
52
+ slideA = $state.raw(null);
53
+
54
+ /** @type {Slide|null} */
55
+ slideB = $state.raw(null);
56
+
57
+ /** @type {string} */
58
+ currentLayerLabel = $state(LABEL_B);
59
+
60
+ /** @type {string} */
61
+ nextLayerLabel = $state(LABEL_A);
62
+
63
+ /** @type {HkPromise[]} */
64
+ transitionPromises = $state.raw([]);
65
+
66
+ /** @type {HkPromise} */
67
+ slideLoadingPromise = null;
68
+
69
+ /** @type {boolean} */
70
+ isSlideLoading = $state(false);
71
+
72
+ /** @type {boolean} */
73
+ loadingSpinner = $state(false);
74
+
75
+ /** @type {boolean} */
76
+ busy = $derived.by(() => {
77
+ const { layerA, layerB, isSlideLoading } = this;
78
+
79
+ const layerAStable =
80
+ layerA.stageShow || layerA.stageAfter || layerA.stageIdle;
81
+ const layerBStable =
82
+ layerB.stageShow || layerB.stageAfter || layerB.stageIdle;
83
+
84
+ return !(layerAStable && layerBStable) || isSlideLoading;
85
+ });
86
+
87
+ /** @type {string} */
88
+ currentSlideName = $derived.by(() => {
89
+ const currentSlide = this.#getSlide(this.currentLayerLabel);
90
+ return currentSlide?.name || '';
91
+ });
92
+
93
+ /** @type {string} */
94
+ nextSlideName;
95
+
96
+ /** @type {string} */
97
+ pendingSlideName;
98
+
99
+ /** @type {boolean} */
100
+ configured = false;
101
+
102
+ /** @type {Map<Symbol, ( params: ListenerParams ) => void>} */
103
+ onBeforeListeners = new Map();
104
+
105
+ /** @type {Map<Symbol, ( params: ListenerParams ) => void>} */
106
+ onShowListeners = new Map();
107
+
108
+ /**
109
+ * Initialize the presenter state and set up reactivity
110
+ */
111
+ constructor() {
112
+ // this.#setupStageTransitions();
113
+
114
+ let timeout;
115
+
116
+ $effect((slideLoadingPromise) => {
117
+ if (this.isSlideLoading) {
118
+ // Enable spinner after a short delay
119
+ clearTimeout(timeout);
120
+ setTimeout(() => {
121
+ untrack(() => {
122
+ if (this.isSlideLoading) {
123
+ this.loadingSpinner = true;
124
+ } else {
125
+ this.loadingSpinner = false;
126
+ }
127
+ });
128
+ }, 500);
129
+ } else {
130
+ this.loadingSpinner = false;
131
+ }
132
+ });
133
+ }
134
+
135
+ /**
136
+ * Set up reactivity for stage transitions between the before/after states
137
+ * This handles the animation timing for both layers
138
+ */
139
+ // #setupStageTransitions() {
140
+ // // Handle layer A stage transitions
141
+ // $effect(() => {
142
+ // if (this.layerA.stageBeforeIn || this.layerA.stageBeforeOut) {
143
+ // this.layerA = this.#processStageTransition(this.layerA);
144
+ // }
145
+ // });
146
+
147
+ // // Handle layer B stage transitions
148
+ // $effect(() => {
149
+ // if (this.layerB.stageBeforeIn || this.layerB.stageBeforeOut) {
150
+ // this.layerB = this.#processStageTransition(this.layerB);
151
+ // }
152
+ // });
153
+ // }
154
+
155
+ /**
156
+ * Process a single stage transition for a layer
157
+ *
158
+ * @param {Layer} layer - The layer to process
159
+ * @returns {Layer} - The updated layer with new stage
160
+ */
161
+ #processStageTransition(layer) {
162
+ const updatedLayer = { ...layer };
163
+
164
+ if (updatedLayer.stageBeforeIn) {
165
+ delete updatedLayer.stageBeforeIn;
166
+ updatedLayer.stageIn = true;
167
+ } else if (updatedLayer.stageBeforeOut) {
168
+ delete updatedLayer.stageBeforeOut;
169
+ updatedLayer.stageOut = true;
170
+ }
171
+
172
+ return updatedLayer;
173
+ }
174
+
175
+ /**
176
+ * Waiting for all transition timing promises to finish,
177
+ * this should be the same amount of time as it takes for the real
178
+ * transitions to finish
179
+ *
180
+ * @param {HkPromise[]} promises
181
+ * Array of transition promises to wait for
182
+ */
183
+ async #waitForTransitionPromises(promises) {
184
+ try {
185
+ // console.debug('waitForTransitionPromises', promises);
186
+
187
+ await Promise.allSettled(promises);
188
+
189
+ // console.debug('waitForTransitionPromises:done', promises);
190
+
191
+ untrack(() => {
192
+ this.#completeTransition();
193
+ });
194
+ } catch (error) {
195
+ console.log('transition promises cancelled', error);
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Complete the transition by updating layers and swapping them
201
+ */
202
+ #completeTransition() {
203
+ // Hide current layer and set stage to AFTER
204
+ this.#updateLayer(this.currentLayerLabel, {
205
+ z: Z_BACK,
206
+ visible: false,
207
+ stageAfter: true
208
+ });
209
+
210
+ // Set next layer stage to SHOW
211
+ this.#updateLayer(this.nextLayerLabel, {
212
+ z: Z_FRONT,
213
+ visible: true,
214
+ stageShow: true
215
+ });
216
+
217
+ // Remove slide from current layer
218
+ this.#updateSlide(this.currentLayerLabel, null);
219
+
220
+ // Swap current and next layer labels
221
+ this.#swapLayers();
222
+ }
223
+
224
+ /**
225
+ * Swap the current and next layer labels
226
+ */
227
+ #swapLayers() {
228
+ if (this.currentLayerLabel === LABEL_A) {
229
+ this.currentLayerLabel = LABEL_B;
230
+ this.nextLayerLabel = LABEL_A;
231
+ } else {
232
+ this.currentLayerLabel = LABEL_A;
233
+ this.nextLayerLabel = LABEL_B;
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Configure the presentation
239
+ *
240
+ * @param {object} _
241
+ * @param {Slide[]} [_.slides] - Array of slides for the presentation
242
+ */
243
+ configure({ slides }) {
244
+ this.configured = true;
245
+
246
+ if (slides) {
247
+ // Only update slides if provided
248
+ this.slides = slides;
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Configure the presentation slides
254
+ *
255
+ * @param {Slide[]} slides - Array of slides for the presentation
256
+ */
257
+ configureSlides(slides) {
258
+ this.slides = slides ?? [];
259
+ }
260
+
261
+ /**
262
+ * Transition to another slide by name
263
+ *
264
+ * @param {string} name - Name of the slide to transition to
265
+ */
266
+ async gotoSlide(name) {
267
+ // throw new Error('gotoSlide');
268
+
269
+ untrack(() => {
270
+ const slide = findFirst(this.slides, { name });
271
+
272
+ if (!slide) {
273
+ console.log('available slides', this.slides);
274
+ throw new Error(`Slide [${name}] has not been defined`);
275
+ }
276
+
277
+ this.#gotoSlide(slide);
278
+ });
279
+ }
280
+
281
+ /**
282
+ * Internal method to transition to another slide
283
+ *
284
+ * @param {Slide} slide - The slide to transition to
285
+ */
286
+ async #gotoSlide(slide) {
287
+ if (!this.configured) {
288
+ throw new Error('Not configured yet');
289
+ }
290
+
291
+ if (slide.name === this.currentSlideName) {
292
+ //throw new Error(`gotoSlide cannot transition to current slide`);
293
+ console.error(`gotoSlide cannot transition to current slide`);
294
+ return;
295
+ }
296
+
297
+ this.nextSlideName = slide.name;
298
+
299
+ if (this.busy) {
300
+ this.pendingSlideName = slide.name;
301
+ return;
302
+ }
303
+
304
+ this.#callOnBeforeListeners();
305
+
306
+ this.slideLoadingPromise = null;
307
+
308
+ // Get a presenter reference to pass to the slide
309
+ const presenterRef = this.getPresenterRef();
310
+
311
+ // Create a copy of the slide to avoid mutating the original
312
+ const slideWithProps = {
313
+ ...slide,
314
+ data: {
315
+ ...slide.data,
316
+ props: {
317
+ ...(slide.data.props || {}),
318
+ getLoadingController: () => {
319
+ this.isSlideLoading = true;
320
+ this.slideLoadingPromise = new HkPromise(() => {});
321
+
322
+ return this.#getLoadingController();
323
+ // this.slideLoadingPromise should be a HkPromise now
324
+ // console.log('slideLoadingPromise', this.slideLoadingPromise);
325
+ },
326
+ presenter: presenterRef // Add presenter reference to props
327
+ }
328
+ }
329
+ };
330
+
331
+ // console.debug('Checkpoint 1');
332
+
333
+ // Add next slide to next layer
334
+ this.#updateSlide(this.nextLayerLabel, slideWithProps);
335
+
336
+ // console.debug('Checkpoint 2');
337
+
338
+ await tick();
339
+
340
+ // console.debug('Checkpoint 3');
341
+
342
+ if (this.slideLoadingPromise) {
343
+ // console.debug('Waiting for slide to load');
344
+ // @ts-ignore
345
+ await this.slideLoadingPromise;
346
+ this.isSlideLoading = false;
347
+ // console.debug('Done waiting for slide loading');
348
+ }
349
+
350
+ const currentSlide = this.#getSlide(this.currentLayerLabel);
351
+ const nextSlide = this.#getSlide(this.nextLayerLabel);
352
+
353
+ // console.debug('Checkpoint 4', currentSlide, nextSlide);
354
+
355
+ // Make next layer visible, move to front, and prepare for
356
+ // transition in
357
+ this.#updateLayer(this.nextLayerLabel, {
358
+ z: Z_FRONT,
359
+ visible: true,
360
+ stageBeforeIn: true,
361
+ transitions: nextSlide?.intro ?? []
362
+ });
363
+
364
+ // Move current layer to back, keep visible, and prepare for
365
+ // transition out
366
+ this.#updateLayer(this.currentLayerLabel, {
367
+ z: Z_BACK,
368
+ visible: true,
369
+ stageBeforeOut: true,
370
+ transitions: currentSlide?.outro ?? []
371
+ });
372
+
373
+ // console.debug('Checkpoint 5');
374
+
375
+ // Wait briefly to ensure the stageBeforeIn/stageBeforeOut states are rendered
376
+ await tick();
377
+
378
+ // Now manually process the transitions for both layers
379
+ const layerA = this.layerA;
380
+ const layerB = this.layerB;
381
+
382
+ // Process stageBeforeIn transition for both layers
383
+ if (layerA.stageBeforeIn) {
384
+ this.layerA = this.#processStageTransition(layerA);
385
+ }
386
+
387
+ if (layerB.stageBeforeIn) {
388
+ this.layerB = this.#processStageTransition(layerB);
389
+ }
390
+
391
+ // Wait for another tick to ensure the stageIn states are rendered
392
+ await tick();
393
+
394
+ // Process stageBeforeOut transition for both layers
395
+ if (layerA.stageBeforeOut) {
396
+ this.layerA = this.#processStageTransition(layerA);
397
+ }
398
+
399
+ if (layerB.stageBeforeOut) {
400
+ this.layerB = this.#processStageTransition(layerB);
401
+ }
402
+
403
+ // console.debug('Checkpoint 7');
404
+
405
+ // Start transitions
406
+ this.#createTransitionPromises();
407
+
408
+ // console.debug('Checkpoint 8');
409
+
410
+ await this.#waitForTransitionPromises(this.transitionPromises);
411
+
412
+ // Check if there's a pending slide transition
413
+ if (this.pendingSlideName) {
414
+ const pendingName = this.pendingSlideName;
415
+
416
+ this.nextSlideName = pendingName;
417
+ this.pendingSlideName = null;
418
+
419
+ untrack(() => {
420
+ if (pendingName !== this.currentSlideName) {
421
+ this.gotoSlide(pendingName);
422
+ }
423
+ });
424
+ } else {
425
+ this.nextSlideName = null;
426
+ }
427
+
428
+ this.#callOnShowListeners();
429
+ }
430
+
431
+ #callOnBeforeListeners() {
432
+ let nextSlideName = this.nextSlideName;
433
+
434
+ for (const fn of this.onBeforeListeners.values()) {
435
+ fn({ stage: STAGE_BEFORE, slideName: nextSlideName });
436
+ }
437
+ }
438
+
439
+ #callOnShowListeners() {
440
+ let currentSlideName = this.currentSlideName;
441
+
442
+ for (const fn of this.onShowListeners.values()) {
443
+ fn({ stage: STAGE_SHOW, slideName: currentSlideName });
444
+ }
445
+ }
446
+
447
+ /**
448
+ * Create transition promises that can be used to determine the timing
449
+ * of the transitions between current and next slide
450
+ */
451
+ #createTransitionPromises() {
452
+ // Cancel existing transitions
453
+ let transitionPromises = this.transitionPromises;
454
+
455
+ for (const current of transitionPromises) {
456
+ current.tryCancel();
457
+ }
458
+
459
+ // Start new transitions
460
+ transitionPromises = [];
461
+
462
+ const currentSlide = this.#getSlide(this.currentLayerLabel);
463
+ const nextSlide = this.#getSlide(this.nextLayerLabel);
464
+
465
+ // Apply transitions `out` from currentslide
466
+ const transitionsOut = currentSlide?.outro;
467
+
468
+ // console.log('transitionsOut', transitionsOut);
469
+
470
+ if (transitionsOut) {
471
+ for (const transition of transitionsOut) {
472
+ const promise = this.#createTransitionPromise(transition);
473
+ transitionPromises.push(promise);
474
+ }
475
+ }
476
+
477
+ // Apply transitions `in` from next slide
478
+ const transitionsIn = nextSlide?.intro;
479
+
480
+ // console.log('transitionsIn', transitionsIn);
481
+
482
+ if (transitionsIn) {
483
+ for (const transition of transitionsIn) {
484
+ const promise = this.#createTransitionPromise(transition);
485
+ transitionPromises.push(promise);
486
+ }
487
+ }
488
+
489
+ this.transitionPromises = transitionPromises;
490
+ }
491
+
492
+ /**
493
+ * Create a transition promise for the specified transition
494
+ *
495
+ *
496
+ * @param {Transition} transition - The transition to apply
497
+ *
498
+ * @returns {HkPromise}
499
+ * Promise that resolves after the same amount of time that it
500
+ * takes for the transition to finish
501
+ */
502
+ #createTransitionPromise(transition) {
503
+ const delay = (transition.delay ?? 0) + (transition.duration ?? 0);
504
+
505
+ if (0 === delay) {
506
+ const promise = new HkPromise(() => {});
507
+ promise.resolve(true);
508
+ return promise;
509
+ }
510
+
511
+ let promise = new HkPromise((/** @type {function} */ resolve) => {
512
+ if (delay) {
513
+ setTimeout(() => {
514
+ resolve(true);
515
+ }, delay);
516
+ }
517
+ });
518
+
519
+ return promise;
520
+ }
521
+
522
+ /**
523
+ * Get slide by layer label
524
+ *
525
+ * @param {string} label - Layer label (A or B)
526
+ * @returns {Slide|null} The slide for the specified layer or null
527
+ */
528
+ #getSlide(label) {
529
+ if (label === LABEL_A) {
530
+ return this.slideA;
531
+ }
532
+
533
+ if (label === LABEL_B) {
534
+ return this.slideB;
535
+ }
536
+
537
+ return null;
538
+ }
539
+
540
+ /**
541
+ * Update layer by label
542
+ *
543
+ * @param {string} label - Layer label (A or B)
544
+ * @param {Layer} data - Layer data to update
545
+ */
546
+ #updateLayer(label, data) {
547
+ if (label === LABEL_A) {
548
+ this.layerA = data;
549
+ return;
550
+ }
551
+
552
+ if (label === LABEL_B) {
553
+ this.layerB = data;
554
+ return;
555
+ }
556
+
557
+ throw new Error(`Missing layer [${label}]`);
558
+ }
559
+
560
+ /**
561
+ * Update slide by label
562
+ *
563
+ * @param {string} label - Layer label (A or B)
564
+ * @param {Slide|null} data - Slide data to update or null to clear
565
+ */
566
+ #updateSlide(label, data) {
567
+ if (label === LABEL_A) {
568
+ this.slideA = data;
569
+ return;
570
+ }
571
+
572
+ if (label === LABEL_B) {
573
+ this.slideB = data;
574
+ return;
575
+ }
576
+
577
+ throw new Error(`Missing slide [${label}]`);
578
+ }
579
+
580
+ /**
581
+ * Returns a simplified presenter reference with essential methods
582
+ * for slide components to use
583
+ *
584
+ * @returns {PresenterRef} A reference object with presenter methods
585
+ */
586
+ getPresenterRef() {
587
+ return {
588
+ gotoSlide: (name) => this.gotoSlide(name),
589
+ getCurrentSlideName: () => this.currentSlideName,
590
+ onBefore: (callback) => {
591
+ const key = Symbol();
592
+ this.onBeforeListeners.set(key, callback);
593
+
594
+ return () => {
595
+ this.onBeforeListeners.delete(key);
596
+ };
597
+ },
598
+ onShow: (callback) => {
599
+ const key = Symbol();
600
+ this.onShowListeners.set(key, callback);
601
+
602
+ return () => {
603
+ this.onShowListeners.delete(key);
604
+ };
605
+ }
606
+ };
607
+ }
608
+
609
+ /**
610
+ * Returns a controller object for managing manual loading
611
+ * Components can use this to signal when they're done loading
612
+ * or to cancel and go back to the previous slide
613
+ *
614
+ * @returns {LoadController}
615
+ * Object with loaded() and cancel() methods
616
+ */
617
+ #getLoadingController() {
618
+ // console.debug('getLoadingController was called');
619
+
620
+ return {
621
+ /**
622
+ * Call when component has finished loading
623
+ */
624
+ loaded: () => {
625
+ // console.debug('Slide said loading has completed');
626
+ this.slideLoadingPromise?.tryResolve();
627
+ },
628
+
629
+ /**
630
+ * Call to cancel loading and return to previous slide
631
+ */
632
+ cancel: () => {
633
+ // console.debug('Slide said loading has cancelled');
634
+ this.slideLoadingPromise?.tryReject();
635
+ }
636
+ };
637
+ }
638
+ }