@hkdigital/lib-sveltekit 0.0.81 → 0.0.83

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 (44) hide show
  1. package/dist/classes/svelte/audio/AudioLoader.svelte.d.ts +30 -0
  2. package/dist/classes/svelte/audio/AudioLoader.svelte.js +58 -0
  3. package/dist/classes/svelte/audio/AudioScene.svelte.d.ts +52 -0
  4. package/dist/classes/svelte/audio/AudioScene.svelte.js +282 -0
  5. package/dist/classes/svelte/audio/mocks.d.ts +7 -0
  6. package/dist/classes/svelte/audio/mocks.js +35 -0
  7. package/dist/classes/svelte/final-state-machine/FiniteStateMachine.svelte.d.ts +50 -0
  8. package/dist/classes/svelte/final-state-machine/FiniteStateMachine.svelte.js +133 -0
  9. package/dist/classes/svelte/final-state-machine/index.d.ts +1 -0
  10. package/dist/classes/svelte/final-state-machine/index.js +1 -0
  11. package/dist/classes/svelte/image/ImageLoader.svelte.d.ts +8 -0
  12. package/dist/classes/svelte/image/ImageLoader.svelte.js +12 -0
  13. package/dist/classes/svelte/image/ImageVariantsLoader.svelte.d.ts +39 -0
  14. package/dist/classes/svelte/image/ImageVariantsLoader.svelte.js +151 -0
  15. package/dist/classes/svelte/image/index.d.ts +2 -0
  16. package/dist/classes/svelte/image/index.js +4 -0
  17. package/dist/classes/svelte/image/mocks.d.ts +7 -0
  18. package/dist/classes/svelte/image/mocks.js +38 -0
  19. package/dist/classes/svelte/image/typedef.d.ts +16 -0
  20. package/dist/classes/svelte/image/typedef.js +10 -0
  21. package/dist/classes/svelte/loading-state-machine/LoadingStateMachine.svelte.d.ts +12 -0
  22. package/dist/classes/svelte/loading-state-machine/LoadingStateMachine.svelte.js +107 -0
  23. package/dist/classes/svelte/loading-state-machine/constants.d.ts +12 -0
  24. package/dist/classes/svelte/loading-state-machine/constants.js +16 -0
  25. package/dist/classes/svelte/loading-state-machine/index.d.ts +2 -0
  26. package/dist/classes/svelte/loading-state-machine/index.js +3 -0
  27. package/dist/classes/svelte/network-loader/NetworkLoader.svelte.d.ts +85 -0
  28. package/dist/classes/svelte/network-loader/NetworkLoader.svelte.js +295 -0
  29. package/dist/classes/svelte/network-loader/constants.d.ts +2 -0
  30. package/dist/classes/svelte/network-loader/constants.js +3 -0
  31. package/dist/classes/svelte/network-loader/index.d.ts +2 -0
  32. package/dist/classes/svelte/network-loader/index.js +3 -0
  33. package/dist/components/image/ResponsiveImage.svelte +103 -0
  34. package/dist/components/image/ResponsiveImage.svelte.d.ts +15 -0
  35. package/dist/components/image/index.d.ts +4 -1
  36. package/dist/components/image/index.js +5 -1
  37. package/dist/types/imagetools.d.ts +16 -13
  38. package/dist/types/imagetools.js +8 -0
  39. package/dist/util/expect/arrays.d.ts +4 -0
  40. package/dist/util/expect/arrays.js +6 -1
  41. package/dist/util/http/response.js +1 -0
  42. package/dist/util/svelte/wait/index.d.ts +15 -0
  43. package/dist/util/svelte/wait/index.js +38 -0
  44. package/package.json +3 -1
@@ -0,0 +1,295 @@
1
+ import { CONTENT_TYPE } from '@hkdigital/lib-sveltekit/constants/http/index.js';
2
+
3
+ import {
4
+ LoadingStateMachine,
5
+ STATE_INITIAL,
6
+ STATE_LOADING,
7
+ STATE_UNLOADING,
8
+ STATE_LOADED,
9
+ STATE_CANCELLED,
10
+ STATE_ERROR,
11
+ LOAD,
12
+ // CANCEL,
13
+ ERROR,
14
+ LOADED,
15
+ UNLOAD,
16
+ INITIAL
17
+ } from '../loading-state-machine/index.js';
18
+
19
+ import * as expect from '@hkdigital/lib-sveltekit/util/expect/index.js';
20
+
21
+ import {
22
+ httpGet,
23
+ loadResponseBuffer
24
+ } from '@hkdigital/lib-sveltekit/util/http/index.js';
25
+
26
+ import { ERROR_NOT_LOADED, ERROR_TRANSFERRED } from './constants.js';
27
+
28
+ /**
29
+ * NetworkLoader instance
30
+ * - Loads network data from network into an ArrayBuffer
31
+ * - Loaded data can be transferred to an AudioBufferSourceNode
32
+ */
33
+ export default class NetworkLoader {
34
+ _state = new LoadingStateMachine();
35
+
36
+ // @note this exported state is set by $effect's
37
+ state = $state(STATE_INITIAL);
38
+
39
+ loaded = $derived.by(() => {
40
+ return this.state === STATE_LOADED;
41
+ });
42
+
43
+ /** @type {string|null} */
44
+ _url = null;
45
+
46
+ /** @type {number} */
47
+ _bytesLoaded = $state(0);
48
+
49
+ /** @type {number} */
50
+ _size = $state(0);
51
+
52
+ /**
53
+ * Response headers
54
+ * @type {Headers|null}
55
+ */
56
+ _headers = $state(null);
57
+
58
+ /** @type {ArrayBuffer|null} */
59
+ _buffer = null;
60
+
61
+ // Export state property as readonly
62
+ progress = $derived.by(() => {
63
+ return {
64
+ bytesLoaded: this._bytesLoaded,
65
+ size: this._size,
66
+ loaded: this.loaded
67
+ };
68
+ });
69
+
70
+ /** @type {null|(()=>void)} */
71
+ _abortLoading = null;
72
+
73
+ /**
74
+ * Construct NetworkLoader
75
+ *
76
+ * @param {object} _
77
+ * @param {string} _.url
78
+ */
79
+ constructor({ url }) {
80
+ expect.absOrRelUrl(url);
81
+
82
+ this._url = url;
83
+
84
+ const state = this._state;
85
+
86
+ $effect(() => {
87
+ switch (state.current) {
88
+ case STATE_LOADING:
89
+ {
90
+ // console.log('NetworkLoader:loading');
91
+ this.#load();
92
+ }
93
+ break;
94
+
95
+ case STATE_UNLOADING:
96
+ {
97
+ // console.log('NetworkLoader:unloading');
98
+ this.#unload();
99
+ }
100
+ break;
101
+
102
+ case STATE_LOADED:
103
+ {
104
+ // console.error(
105
+ // 'NetworkLoader:loaded',
106
+ // $state.snapshot({ bytes: this.size })
107
+ // );
108
+
109
+ // Abort function is no longer needed
110
+ this._abortLoading = null;
111
+ }
112
+ break;
113
+
114
+ case STATE_CANCELLED:
115
+ {
116
+ // console.log('NetworkLoader:cancelled');
117
+ // TODO
118
+ }
119
+ break;
120
+
121
+ case STATE_ERROR:
122
+ {
123
+ console.log('NetworkLoader:error', state.error);
124
+ }
125
+ break;
126
+ } // end switch
127
+
128
+ this.state = state.current;
129
+ });
130
+ }
131
+
132
+ /**
133
+ * Start loading all network data
134
+ */
135
+ load() {
136
+ this._state.send(LOAD);
137
+ }
138
+
139
+ /**
140
+ * Unoad all network data
141
+ */
142
+ unload() {
143
+ this._state.send(UNLOAD);
144
+ }
145
+
146
+ /**
147
+ * Get network data size in bytes
148
+ * - Info comes from the content length response header
149
+ *
150
+ * @returns {number}
151
+ */
152
+ get size() {
153
+ return this._size;
154
+ }
155
+
156
+ /**
157
+ * Get content type from response header
158
+ *
159
+ * @returns {string|null}
160
+ */
161
+ getContentType() {
162
+ if (!this._headers) {
163
+ throw new Error(ERROR_NOT_LOADED);
164
+ }
165
+
166
+ return this._headers.get(CONTENT_TYPE);
167
+ }
168
+
169
+ /**
170
+ * Get data as array buffer
171
+ *
172
+ * @note If the data has been transferred, the data is
173
+ * no longer available as ArrayBuffer
174
+ *
175
+ * @returns {ArrayBuffer}
176
+ */
177
+ getArrayBuffer() {
178
+ if (!this._buffer) {
179
+ throw new Error(ERROR_NOT_LOADED);
180
+ }
181
+
182
+ if (this._buffer.detached) {
183
+ throw new Error(ERROR_TRANSFERRED);
184
+ }
185
+
186
+ return this._buffer;
187
+ }
188
+
189
+ /**
190
+ * Get data as Uint8Array
191
+ *
192
+ * @returns {Uint8Array}
193
+ */
194
+ getUint8Array() {
195
+ return new Uint8Array(this.getArrayBuffer());
196
+ }
197
+
198
+ /**
199
+ * Get data as Blob
200
+ * - The Blob type is set using the response header
201
+ * content type
202
+ *
203
+ * @returns {Blob}
204
+ */
205
+ getBlob() {
206
+ const type = this.getContentType();
207
+
208
+ const options = {};
209
+
210
+ if (type) {
211
+ options.type = type;
212
+ }
213
+
214
+ return new Blob([this.getArrayBuffer()], options);
215
+ }
216
+
217
+ /**
218
+ * Internal method that initializes the loading process
219
+ * and transitions to state LOADED when done
220
+ */
221
+ async #load() {
222
+ try {
223
+ // console.log('#load', this._url);
224
+
225
+ if (this._abortLoading) {
226
+ // console.log('Abort loading');
227
+ this._abortLoading();
228
+ this._abortLoading = null;
229
+ }
230
+
231
+ /** @type {()=>void} */
232
+ let abortRequest;
233
+
234
+ /**
235
+ * @param {object} _
236
+ * @param {()=>void} _.abort
237
+ */
238
+ const requestHandler = ({ abort }) => {
239
+ abortRequest = abort;
240
+ };
241
+
242
+ this._bytesLoaded = 0;
243
+ this._size = 0;
244
+
245
+ // @ts-ignore
246
+ const response = await httpGet({ url: this._url, requestHandler });
247
+
248
+ this._headers = response.headers;
249
+
250
+ // console.log('headers', this._headers);
251
+ // console.log('response', response);
252
+
253
+ const { bufferPromise, abort: abortLoadBody } = loadResponseBuffer(
254
+ response,
255
+ ({ bytesLoaded, size }) => {
256
+ this._bytesLoaded = bytesLoaded;
257
+ this._size = size;
258
+ }
259
+ );
260
+
261
+ this._abortLoading = () => {
262
+ abortRequest();
263
+ abortLoadBody();
264
+ };
265
+
266
+ this._buffer = await bufferPromise;
267
+
268
+ this._state.send(LOADED);
269
+ } catch (e) {
270
+ this._state.send(ERROR, e);
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Internal method that initializes the unloading process
276
+ * and transitions to state INITIAL when done
277
+ */
278
+ async #unload() {
279
+ try {
280
+ if (this._abortLoading) {
281
+ this._abortLoading();
282
+ this._abortLoading = null;
283
+ }
284
+
285
+ this._bytesLoaded = 0;
286
+ this._size = 0;
287
+ this._headers = null;
288
+ this._buffer = null;
289
+
290
+ this._state.send(INITIAL);
291
+ } catch (e) {
292
+ this._state.send(ERROR, e);
293
+ }
294
+ }
295
+ } // end class
@@ -0,0 +1,2 @@
1
+ export const ERROR_NOT_LOADED: "No data loaded yet";
2
+ export const ERROR_TRANSFERRED: "Buffer data has been transferred and is no longer available";
@@ -0,0 +1,3 @@
1
+ export const ERROR_NOT_LOADED = 'No data loaded yet';
2
+ export const ERROR_TRANSFERRED =
3
+ 'Buffer data has been transferred and is no longer available';
@@ -0,0 +1,2 @@
1
+ export { default as NetworkLoader } from "./NetworkLoader.svelte";
2
+ export * from "./constants.js";
@@ -0,0 +1,3 @@
1
+ export { default as NetworkLoader } from './NetworkLoader.svelte';
2
+
3
+ export * from './constants.js';
@@ -0,0 +1,103 @@
1
+ <script>
2
+ /** @typedef {import('../../types/imagetools.js').ImageVariant} ImageVariant */
3
+
4
+ import { ImageVariantsLoader } from '../../classes/svelte/image/index.js';
5
+
6
+ /**
7
+ * @type {{
8
+ * base?: string,
9
+ * classes?: string
10
+ * boxBase?: string,
11
+ * boxClasses?: string
12
+ * boxAttrs?: { [attr: string]: * },
13
+ * images: ImageVariant[],
14
+ * alt?: string
15
+ * } & { [attr: string]: * }}
16
+ */
17
+ const {
18
+ base,
19
+ classes,
20
+ boxBase,
21
+ boxClasses,
22
+ boxAttrs,
23
+
24
+ // Functional
25
+ images,
26
+ alt = '',
27
+
28
+ // Attributes
29
+ ...attrs
30
+ } = $props();
31
+
32
+ let variantsLoader = new ImageVariantsLoader(images);
33
+
34
+ let containerWidth = $state(0);
35
+
36
+ /** @type {ImageVariant|null} */
37
+ let imageVariant = $state(null);
38
+
39
+ $effect(() => {
40
+ variantsLoader.updateOptimalImageVariant(containerWidth);
41
+ });
42
+
43
+ // $effect(() => {
44
+ // console.log('imageVariant', $state.snapshot(imageVariant));
45
+ // });
46
+
47
+ /** @type {string|null} */
48
+ let imageUrl = $state(null);
49
+
50
+ $effect(() => {
51
+ let image;
52
+
53
+ if (variantsLoader.loaded) {
54
+ // @ts-ignore
55
+ imageUrl = variantsLoader.getObjectUrl();
56
+
57
+ // image = new Image();
58
+ // image.src = url;
59
+
60
+ // image.onload = () => {
61
+ // console.log('loaded');
62
+ // imageUrl = url;
63
+ // };
64
+ }
65
+
66
+ return () => {
67
+ // if (image) {
68
+ // image.onload = null;
69
+ // image = undefined;
70
+ // }
71
+
72
+ if (imageUrl) {
73
+ URL.revokeObjectURL(imageUrl);
74
+ imageUrl = null;
75
+ }
76
+ };
77
+ });
78
+
79
+ let variant = $derived(variantsLoader.variant);
80
+
81
+ // let image = $derived(variantsLoader.image);
82
+ </script>
83
+
84
+ <div
85
+ bind:clientWidth={containerWidth}
86
+ data-hk--responsive-image-box
87
+ class="{boxBase} {boxClasses}"
88
+ {...boxAttrs}
89
+ >
90
+ <!-- <p class="p text-white">variant: {JSON.stringify(variant)}</p> -->
91
+
92
+ {#if variant}
93
+ <img
94
+ data-responsive-image
95
+ src={imageUrl ? imageUrl : ''}
96
+ width={variant.width}
97
+ height={variant.height}
98
+ {alt}
99
+ class="{boxBase} {boxClasses}"
100
+ {...attrs}
101
+ />
102
+ {/if}
103
+ </div>
@@ -0,0 +1,15 @@
1
+ export default ResponsiveImage;
2
+ export type ImageVariant = any;
3
+ declare const ResponsiveImage: import("svelte").Component<{
4
+ base?: string;
5
+ classes?: string;
6
+ boxBase?: string;
7
+ boxClasses?: string;
8
+ boxAttrs?: {
9
+ [attr: string]: any;
10
+ };
11
+ images: ImageVariant[];
12
+ alt?: string;
13
+ } & {
14
+ [attr: string]: any;
15
+ }, {}, "">;
@@ -1 +1,4 @@
1
- export { default as EnhancedImage } from "./EnhancedImage.svelte";
1
+ export { default as ResponsiveImage } from "./ResponsiveImage.svelte";
2
+ declare const _default: {};
3
+ export default _default;
4
+ export type ImageVariant = any;
@@ -1 +1,5 @@
1
- export { default as EnhancedImage } from './EnhancedImage.svelte';
1
+ /** @typedef {import('../../types/imagetools.js').ImageVariant} ImageVariant */
2
+
3
+ export { default as ResponsiveImage } from './ResponsiveImage.svelte';
4
+
5
+ export default {};
@@ -1,68 +1,71 @@
1
1
 
2
+
3
+ // Reference the JSDoc types (doesn't work?)
4
+ // export type ImageVariant = import('./imagetools.js').ImageVariant;
5
+
2
6
  interface ImageVariant {
3
7
  src: string;
4
8
  width?: number;
5
9
  height?: number;
6
-
7
10
  }
8
11
 
9
12
  declare module '*?responsive' {
10
- const out: Promise<ImageVariant[]|string[]|string>;
13
+ const out: ImageVariant[];
11
14
  export default out;
12
15
  }
13
16
 
14
17
  declare module '*&responsive' {
15
- const out: Promise<ImageVariant[]|string[]|string>;
18
+ const out: ImageVariant[];
16
19
  export default out;
17
20
  }
18
21
 
19
22
  declare module '*?preset=gradient' {
20
- const out: Promise<ImageVariant[]|string[]|string>;
23
+ const out: ImageVariant[]|string[]|string;
21
24
  export default out;
22
25
  }
23
26
 
24
27
  declare module '*&preset=gradient' {
25
- const out: Promise<ImageVariant[]|string[]|string>;
28
+ const out: ImageVariant[]|string[]|string;
26
29
  export default out;
27
30
  }
28
31
 
29
32
  declare module '*?preset=photo' {
30
- const out: Promise<ImageVariant[]|string[]|string>;
33
+ const out: ImageVariant[]|string[]|string;
31
34
  export default out;
32
35
  }
33
36
 
34
37
  declare module '*&preset=photo' {
35
- const out: Promise<ImageVariant[]|string[]|string>;
38
+ const out: ImageVariant[]|string[]|string;
36
39
  export default out;
37
40
  }
38
41
 
39
42
  declare module '*?preset=drawing' {
40
- const out: Promise<ImageVariant[]|string[]|string>;
43
+ const out: ImageVariant[]|string[]|string;
41
44
  export default out;
42
45
  }
43
46
 
44
47
  declare module '*&preset=drawing' {
45
- const out: Promise<ImageVariant[]|string[]|string>;
48
+ const out: ImageVariant[]|string[]|string;
46
49
  export default out;
47
50
  }
48
51
 
49
52
  declare module '*?preset=savedata' {
50
- const out: Promise<ImageVariant[]|string[]|string>;
53
+ const out: ImageVariant[]|string[]|string;
51
54
  export default out;
52
55
  }
53
56
 
54
57
  declare module '*&preset=savedata' {
55
- const out: Promise<ImageVariant[]|string[]|string>;
58
+ const out: ImageVariant[]|string[]|string;
56
59
  export default out;
57
60
  }
58
61
 
59
62
  declare module '*?preset=blur' {
60
- const out: Promise<ImageVariant[]|string[]|string>;
63
+ const out: ImageVariant[]|string[]|string;
61
64
  export default out;
62
65
  }
63
66
 
64
67
  declare module '*&preset=blur' {
65
- const out: Promise<ImageVariant[]|string[]|string>;
68
+ const out: ImageVariant[]|string[]|string;
66
69
  export default out;
67
70
  }
68
71
 
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @typedef {Object} ImageVariant
3
+ * @property {string} src - URL of the image
4
+ * @property {number} width - Width of the image
5
+ * @property {number} height - Height of the image
6
+ */
7
+
8
+ export default {};
@@ -10,6 +10,10 @@ export function stringArray(value: any): void;
10
10
  * @param {any} value
11
11
  */
12
12
  export function objectArray(value: any): void;
13
+ /**
14
+ * Throws an exception if the value is not an Array or the array is empty
15
+ */
16
+ export function notEmptyArray(value: any): void;
13
17
  /**
14
18
  * Throws a validation error if value is not array like
15
19
  * - Checks if the value is an object and has a property `length`
@@ -29,7 +29,12 @@ export function objectArray(value) {
29
29
  v.parse(v.array(v.looseObject({})), value);
30
30
  }
31
31
 
32
- // notEmptyArray
32
+ /**
33
+ * Throws an exception if the value is not an Array or the array is empty
34
+ */
35
+ export function notEmptyArray(value) {
36
+ v.parse(v.pipe(v.instance(Array), v.nonEmpty()), value);
37
+ }
33
38
 
34
39
  /**
35
40
  * Throws a validation error if value is not array like
@@ -143,6 +143,7 @@ export async function waitForAndCheckResponse(responsePromise, url) {
143
143
  */
144
144
  export function loadResponseBuffer(response, onProgress) {
145
145
  // @note size might be 0
146
+ // @note might not be send by server in dev mode
146
147
  const size = getResponseSize(response);
147
148
 
148
149
  let bytesLoaded = 0;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Waits for a state condition to be met by running the checkFn
3
+ * function after each Svelte tick
4
+ *
5
+ * @param {() => boolean} checkFn -
6
+ * Should return true when desired state is reached
7
+ *
8
+ * @param {number} [maxWaitMs=1000]
9
+ *
10
+ * @returns {Promise<void>} Promise that resolves when the check
11
+ * function returns true
12
+ * @throws {Error} if maxAttempts is reached before
13
+ * predicate returns true
14
+ */
15
+ export function waitForState(checkFn: () => boolean, maxWaitMs?: number): Promise<void>;
@@ -0,0 +1,38 @@
1
+ import { tick } from 'svelte';
2
+
3
+ /**
4
+ * Waits for a state condition to be met by running the checkFn
5
+ * function after each Svelte tick
6
+ *
7
+ * @param {() => boolean} checkFn -
8
+ * Should return true when desired state is reached
9
+ *
10
+ * @param {number} [maxWaitMs=1000]
11
+ *
12
+ * @returns {Promise<void>} Promise that resolves when the check
13
+ * function returns true
14
+ * @throws {Error} if maxAttempts is reached before
15
+ * predicate returns true
16
+ */
17
+ export function waitForState(checkFn, maxWaitMs = 1000) {
18
+ let startedAt = Date.now();
19
+
20
+ return new Promise((resolve, reject) => {
21
+ async function checkLoop() {
22
+ if (checkFn()) {
23
+ resolve();
24
+ return;
25
+ }
26
+
27
+ if (Date.now() - startedAt >= maxWaitMs) {
28
+ reject(new Error(`State change timeout`));
29
+ return;
30
+ }
31
+
32
+ await tick();
33
+ checkLoop();
34
+ }
35
+
36
+ checkLoop();
37
+ });
38
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-sveltekit",
3
- "version": "0.0.81",
3
+ "version": "0.0.83",
4
4
  "author": "Jens Kleinhout, HKdigital (https://hkdigital.nl)",
5
5
  "license": "ISC",
6
6
  "repository": {
@@ -61,10 +61,12 @@
61
61
  "eslint-config-prettier": "^9.1.0",
62
62
  "eslint-plugin-svelte": "^2.46.1",
63
63
  "globals": "^15.14.0",
64
+ "jsdom": "^26.0.0",
64
65
  "prettier": "^3.4.2",
65
66
  "prettier-plugin-svelte": "^3.3.2",
66
67
  "prettier-plugin-tailwindcss": "^0.6.9",
67
68
  "publint": "^0.2.12",
69
+ "standardized-audio-context-mock": "^9.7.15",
68
70
  "svelte": "^5.16.0",
69
71
  "svelte-check": "^4.1.1",
70
72
  "tailwindcss": "^3.4.17",