@hkdigital/lib-sveltekit 0.0.90 → 0.0.92

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 (29) hide show
  1. package/dist/classes/svelte/audio/AudioScene.svelte.js +1 -1
  2. package/dist/classes/svelte/audio/mocks.js +2 -2
  3. package/dist/classes/svelte/image/ImageLoader.svelte.d.ts +11 -0
  4. package/dist/classes/svelte/image/ImageLoader.svelte.js +28 -10
  5. package/dist/classes/svelte/image/ImageScene.svelte.d.ts +78 -0
  6. package/dist/classes/svelte/image/ImageScene.svelte.js +256 -0
  7. package/dist/classes/svelte/image/ImageVariantsLoader.svelte.d.ts +2 -6
  8. package/dist/classes/svelte/image/ImageVariantsLoader.svelte.js +3 -3
  9. package/dist/classes/svelte/image/mocks.js +2 -5
  10. package/dist/classes/svelte/image/typedef.js +0 -2
  11. package/dist/classes/svelte/loading-state-machine/LoadingStateMachine.svelte.js +2 -0
  12. package/dist/classes/svelte/network-loader/NetworkLoader.svelte.d.ts +5 -8
  13. package/dist/classes/svelte/network-loader/NetworkLoader.svelte.js +24 -19
  14. package/dist/classes/svelte/network-loader/mocks.js +2 -2
  15. package/dist/classes/svelte/network-loader/typedef.d.ts +7 -0
  16. package/dist/classes/svelte/network-loader/typedef.js +8 -0
  17. package/dist/components/image/ImageBox.svelte +242 -0
  18. package/dist/components/image/ImageBox.svelte.d.ts +60 -0
  19. package/dist/components/image/ResponsiveImage.svelte +1 -1
  20. package/dist/components/image/typedef.d.ts +4 -0
  21. package/dist/components/image/typedef.js +32 -0
  22. package/dist/config/imagetools-config.js +6 -1
  23. package/dist/{types → config}/imagetools.d.ts +31 -21
  24. package/dist/config/typedef.d.ts +7 -0
  25. package/dist/config/typedef.js +8 -0
  26. package/dist/util/http/mocks.js +1 -1
  27. package/dist/util/image/index.d.ts +9 -0
  28. package/dist/util/image/index.js +24 -0
  29. package/package.json +19 -17
@@ -1,4 +1,4 @@
1
- import * as expect from '@hkdigital/lib-sveltekit/util/expect/index.js';
1
+ import * as expect from '../../../util/expect/index.js';
2
2
 
3
3
  import {
4
4
  LoadingStateMachine,
@@ -1,6 +1,6 @@
1
- import { CONTENT_TYPE, CONTENT_LENGTH } from '@hkdigital/lib-sveltekit/constants/http/index.js';
1
+ import { CONTENT_TYPE, CONTENT_LENGTH } from '../../../constants/http/index.js';
2
2
 
3
- import { AUDIO_WAV } from '@hkdigital/lib-sveltekit/constants/mime/audio.js';
3
+ import { AUDIO_WAV } from '../../../constants/mime/audio.js';
4
4
 
5
5
  // import MockWav from './tiny-silence.wav?raw';
6
6
 
@@ -4,5 +4,16 @@
4
4
  * - The loading process can be monitored
5
5
  */
6
6
  export default class ImageLoader extends NetworkLoader {
7
+ /**
8
+ * @param {object} _
9
+ * @param {ImageMeta|ImageMeta[]} _.imageMeta
10
+ */
11
+ constructor({ imageMeta }: {
12
+ imageMeta: ImageMeta | ImageMeta[];
13
+ });
14
+ get imageMeta(): import("./typedef.js").ImageMeta;
15
+ get url(): string;
16
+ #private;
7
17
  }
18
+ export type ImageMeta = import("./typedef.js").ImageMeta;
8
19
  import { NetworkLoader } from '../network-loader/index.js';
@@ -1,3 +1,7 @@
1
+ /** @typedef {import('./typedef.js').ImageMeta} ImageMeta */
2
+
3
+ import { toSingleImageMeta } from '../../../util/image/index.js';
4
+
1
5
  import {
2
6
  NetworkLoader
3
7
  // ERROR_NOT_LOADED,
@@ -10,20 +14,34 @@ import {
10
14
  * - The loading process can be monitored
11
15
  */
12
16
  export default class ImageLoader extends NetworkLoader {
13
- //
14
- // /**
15
- // * Construct ImageLoader
16
- // *
17
- // * @param {object} _
18
- // * @param {string} _.url
19
- // */
20
- // constructor( { url } ) {}
21
- //
17
+ /** @type {ImageMeta} */
18
+ #imageMeta;
19
+
20
+ /**
21
+ * @param {object} _
22
+ * @param {ImageMeta|ImageMeta[]} _.imageMeta
23
+ */
24
+ constructor({ imageMeta }) {
25
+ imageMeta = toSingleImageMeta(imageMeta);
26
+
27
+ super({ url: imageMeta.src });
28
+
29
+ this.#imageMeta = imageMeta;
30
+ }
31
+
32
+ get imageMeta() {
33
+ return this.#imageMeta;
34
+ }
35
+
36
+ get url() {
37
+ return this._url;
38
+ }
39
+
22
40
  // /**
23
41
  // * Get object URL that can be used as src parameter of an HTML image
24
42
  // *
25
43
  // * @note the objectURL should be revoked when no longer used
26
44
  // */
27
- // getObjectUrl() {}
45
+ // getObjectURL() {}
28
46
  //
29
47
  } // end class
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @typedef {object} SourceConfig
3
+ * // property ...
4
+ */
5
+ /**
6
+ * @typedef {object} ImageSource
7
+ * @property {string} label
8
+ * @property {ImageLoader} imageLoader
9
+ * @property {ImageMeta} [imageMeta]
10
+ */
11
+ export default class ImageScene {
12
+ state: string;
13
+ loaded: boolean;
14
+ destroy(): void;
15
+ /**
16
+ * Add image source
17
+ * - Uses an ImageLoader instance to load image data from network
18
+ *
19
+ * @param {object} _
20
+ * @param {string} _.label
21
+ * @param {ImageMeta|ImageMeta[]} _.imageMeta
22
+ */
23
+ defineImage({ label, imageMeta }: {
24
+ label: string;
25
+ imageMeta: ImageMeta | ImageMeta[];
26
+ }): void;
27
+ /**
28
+ * Start loading all image sources
29
+ */
30
+ load(): void;
31
+ /**
32
+ * Get image scene loading progress
33
+ */
34
+ get progress(): {
35
+ totalBytesLoaded: number;
36
+ totalSize: number;
37
+ sourcesLoaded: number;
38
+ numberOfSources: number;
39
+ };
40
+ /**
41
+ * Get an image loader
42
+ *
43
+ * @param {string} label
44
+ *
45
+ * @returns {ImageLoader}
46
+ */
47
+ getImageLoader(label: string): ImageLoader;
48
+ /**
49
+ * Get object URL that can be used as src parameter of an HTML image
50
+ *
51
+ * @param {string} label
52
+ *
53
+ * @returns {ImageMeta}
54
+ */
55
+ getImageMeta(label: string): ImageMeta;
56
+ /**
57
+ * Get object URL that can be used as src parameter of an HTML image
58
+ *
59
+ * @param {string} label
60
+ *
61
+ * @note the objectURL should be revoked when no longer used
62
+ *
63
+ * @returns {string}
64
+ */
65
+ getObjectURL(label: string): string;
66
+ #private;
67
+ }
68
+ export type ImageMeta = import("./typedef.js").ImageMeta;
69
+ /**
70
+ * // property ...
71
+ */
72
+ export type SourceConfig = object;
73
+ export type ImageSource = {
74
+ label: string;
75
+ imageLoader: ImageLoader;
76
+ imageMeta?: ImageMeta;
77
+ };
78
+ import ImageLoader from './ImageLoader.svelte.js';
@@ -0,0 +1,256 @@
1
+ /** @typedef {import('./typedef.js').ImageMeta} ImageMeta */
2
+
3
+ import * as expect from '../../../util/expect/index.js';
4
+
5
+ import {
6
+ LoadingStateMachine,
7
+ STATE_INITIAL,
8
+ STATE_LOADING,
9
+ STATE_UNLOADING,
10
+ STATE_LOADED,
11
+ STATE_CANCELLED,
12
+ STATE_ERROR,
13
+ LOAD,
14
+ // CANCEL,
15
+ ERROR,
16
+ LOADED,
17
+ UNLOAD,
18
+ INITIAL
19
+ } from '../loading-state-machine/index.js';
20
+
21
+ import ImageLoader from './ImageLoader.svelte.js';
22
+
23
+ /**
24
+ * @typedef {object} SourceConfig
25
+ * // property ...
26
+ */
27
+
28
+ /**
29
+ * @typedef {object} ImageSource
30
+ * @property {string} label
31
+ * @property {ImageLoader} imageLoader
32
+ * @property {ImageMeta} [imageMeta]
33
+ */
34
+
35
+ export default class ImageScene {
36
+ #state = new LoadingStateMachine();
37
+
38
+ // @note this exported state is set by $effect's
39
+ state = $state(STATE_INITIAL);
40
+
41
+ // @note this exported state is set by $effect's
42
+ loaded = $derived.by(() => {
43
+ return this.state === STATE_LOADED;
44
+ });
45
+
46
+ /** @type {ImageSource[]} */
47
+ #imageSources = $state([]);
48
+
49
+ #progress = $derived.by(() => {
50
+ // console.log('update progress');
51
+
52
+ let totalSize = 0;
53
+ let totalBytesLoaded = 0;
54
+ let sourcesLoaded = 0;
55
+
56
+ const sources = this.#imageSources;
57
+ const numberOfSources = sources.length;
58
+
59
+ for (let j = 0; j < numberOfSources; j++) {
60
+ const source = sources[j];
61
+ const { imageLoader } = source;
62
+
63
+ const { bytesLoaded, size, loaded } = imageLoader.progress;
64
+
65
+ totalSize += size;
66
+ totalBytesLoaded += bytesLoaded;
67
+
68
+ if (loaded) {
69
+ sourcesLoaded++;
70
+ }
71
+ } // end for
72
+
73
+ return {
74
+ totalBytesLoaded,
75
+ totalSize,
76
+ sourcesLoaded,
77
+ numberOfSources
78
+ };
79
+ });
80
+
81
+ /**
82
+ * Construct AudioScene
83
+ *
84
+ * @param {object} _
85
+ * @param {AudioContext} _.audioContext
86
+ */
87
+ constructor() {
88
+ const state = this.#state;
89
+
90
+ $effect(() => {
91
+ if (state.current === STATE_LOADING) {
92
+ // console.log(
93
+ // 'progress',
94
+ // JSON.stringify($state.snapshot(this.#progress))
95
+ // );
96
+
97
+ const { sourcesLoaded, numberOfSources } = this.#progress;
98
+
99
+ if (sourcesLoaded === numberOfSources) {
100
+ // console.log(`All [${numberOfSources}] sources loaded`);
101
+ this.#state.send(LOADED);
102
+ }
103
+ }
104
+ });
105
+
106
+ $effect(() => {
107
+ switch (state.current) {
108
+ case STATE_LOADING:
109
+ {
110
+ // console.log('ImageScene:loading');
111
+ this.#startLoading();
112
+ }
113
+ break;
114
+
115
+ case STATE_UNLOADING:
116
+ {
117
+ // console.log('ImageScene:unloading');
118
+ // this.#startUnLoading();
119
+ }
120
+ break;
121
+
122
+ case STATE_LOADED:
123
+ {
124
+ // console.log('ImageScene:loaded');
125
+ // TODO
126
+ // this.#abortLoading = null;
127
+ }
128
+ break;
129
+
130
+ case STATE_CANCELLED:
131
+ {
132
+ // console.log('ImageScene:cancelled');
133
+ // TODO
134
+ }
135
+ break;
136
+
137
+ case STATE_ERROR:
138
+ {
139
+ console.log('ImageScene:error', state.error);
140
+ }
141
+ break;
142
+ } // end switch
143
+
144
+ this.state = state.current;
145
+ });
146
+ }
147
+
148
+ destroy() {
149
+ // TODO: disconnect all image sources?
150
+ // TODO: Unload ImageLoaders?
151
+ }
152
+
153
+ /**
154
+ * Add image source
155
+ * - Uses an ImageLoader instance to load image data from network
156
+ *
157
+ * @param {object} _
158
+ * @param {string} _.label
159
+ * @param {ImageMeta|ImageMeta[]} _.imageMeta
160
+ */
161
+ defineImage({ label, imageMeta }) {
162
+ expect.notEmptyString(label);
163
+
164
+ // expect.notEmptyString(url);
165
+
166
+ const imageLoader = new ImageLoader({ imageMeta });
167
+
168
+ this.#imageSources.push({ label, imageLoader, imageMeta });
169
+ }
170
+
171
+ /**
172
+ * Start loading all image sources
173
+ */
174
+ load() {
175
+ this.#state.send(LOAD);
176
+
177
+ // FIXME: in unit test when moved to startloading it hangs!
178
+
179
+ for (const { imageLoader } of this.#imageSources) {
180
+ imageLoader.load();
181
+ }
182
+ }
183
+
184
+ async #startLoading() {
185
+ // console.log('#startLoading');
186
+ // FIXME: in unit test when moved to startloading it hangs!
187
+ // for (const { audioLoader } of this.#memorySources) {
188
+ // audioLoader.load();
189
+ // }
190
+ }
191
+
192
+ /**
193
+ * Get Image source
194
+ *
195
+ * @param {string} label
196
+ *
197
+ * @returns {ImageSource}
198
+ */
199
+ #getImageSource(label) {
200
+ for (const source of this.#imageSources) {
201
+ if (label === source.label) {
202
+ return source;
203
+ }
204
+ }
205
+
206
+ throw new Error(`Source [${label}] has not been defined`);
207
+ }
208
+
209
+ /**
210
+ * Get image scene loading progress
211
+ */
212
+ get progress() {
213
+ return this.#progress;
214
+ }
215
+
216
+ /**
217
+ * Get an image loader
218
+ *
219
+ * @param {string} label
220
+ *
221
+ * @returns {ImageLoader}
222
+ */
223
+ getImageLoader(label) {
224
+ const source = this.#getImageSource(label);
225
+
226
+ return source.imageLoader;
227
+ }
228
+
229
+ /**
230
+ * Get object URL that can be used as src parameter of an HTML image
231
+ *
232
+ * @param {string} label
233
+ *
234
+ * @returns {ImageMeta}
235
+ */
236
+ getImageMeta(label) {
237
+ const source = this.#getImageSource(label);
238
+
239
+ return source.imageMeta;
240
+ }
241
+
242
+ /**
243
+ * Get object URL that can be used as src parameter of an HTML image
244
+ *
245
+ * @param {string} label
246
+ *
247
+ * @note the objectURL should be revoked when no longer used
248
+ *
249
+ * @returns {string}
250
+ */
251
+ getObjectURL(label) {
252
+ const source = this.#getImageSource(label);
253
+
254
+ return source.imageLoader.getObjectURL();
255
+ }
256
+ }
@@ -20,12 +20,8 @@ export default class ImageVariantsLoader {
20
20
  *
21
21
  * @returns {string|null}
22
22
  */
23
- getObjectUrl(): string | null;
24
- get progress(): {
25
- bytesLoaded: number;
26
- size: number;
27
- loaded: boolean;
28
- };
23
+ getObjectURL(): string | null;
24
+ get progress(): import("../network-loader/typedef.js").LoadingProgress;
29
25
  /**
30
26
  * Get optimal image variant
31
27
  *
@@ -1,6 +1,6 @@
1
1
  /** @typedef {import('./typedef.js').ImageMeta} ImageMeta */
2
2
 
3
- // import * as expect from '@hkdigital/lib-sveltekit/util/expect/index.js';
3
+ // import * as expect from '../../../util/expect/index.js';
4
4
 
5
5
  import { untrack } from 'svelte';
6
6
 
@@ -90,13 +90,13 @@ export default class ImageVariantsLoader {
90
90
  *
91
91
  * @returns {string|null}
92
92
  */
93
- getObjectUrl() {
93
+ getObjectURL() {
94
94
  // Example usage:
95
95
  //
96
96
  // $effect(() => {
97
97
  // if (variantsLoader.loaded) {
98
98
  // // @ts-ignore
99
- // imageUrl = variantsLoader.getObjectUrl();
99
+ // imageUrl = variantsLoader.getObjectURL();
100
100
  // }
101
101
 
102
102
  // return () => {
@@ -1,9 +1,6 @@
1
- import {
2
- CONTENT_TYPE,
3
- CONTENT_LENGTH
4
- } from '@hkdigital/lib-sveltekit/constants/http/index.js';
1
+ import { CONTENT_TYPE, CONTENT_LENGTH } from '../../../constants/http/index.js';
5
2
 
6
- import { IMAGE_PNG } from '@hkdigital/lib-sveltekit/constants/mime/image.js';
3
+ import { IMAGE_PNG } from '../../../constants/mime/image.js';
7
4
 
8
5
  /**
9
6
  * A minimal 1x1 black PNG encoded as base64
@@ -1,5 +1,3 @@
1
- // /** @typedef {import('@hkdigital/lib-sveltekit/types/imagetools.js').ImageMeta} ImageMeta */
2
-
3
1
  /**
4
2
  * @typedef {Object} ImageMeta
5
3
  * @property {string} src - URL of the image
@@ -47,6 +47,7 @@ export default class LoadingStateMachine extends FiniteStateMachine {
47
47
  },
48
48
  [STATE_LOADING]: {
49
49
  _enter: () => {
50
+ // console.log('LoadingStateMachine: enter LOADING');
50
51
  this.onenter?.(STATE_LOADING);
51
52
  },
52
53
  [CANCEL]: STATE_CANCELLED,
@@ -55,6 +56,7 @@ export default class LoadingStateMachine extends FiniteStateMachine {
55
56
  },
56
57
  [STATE_LOADED]: {
57
58
  _enter: () => {
59
+ // console.log('LoadingStateMachine: enter LOADED');
58
60
  this.onenter?.(STATE_LOADED);
59
61
  },
60
62
  [LOAD]: STATE_LOADING,
@@ -14,7 +14,7 @@ export default class NetworkLoader {
14
14
  url: string;
15
15
  });
16
16
  _state: LoadingStateMachine;
17
- state: string;
17
+ state: any;
18
18
  initial: boolean;
19
19
  loaded: boolean;
20
20
  /** @type {string|null} */
@@ -30,11 +30,8 @@ export default class NetworkLoader {
30
30
  _headers: Headers | null;
31
31
  /** @type {ArrayBuffer|null} */
32
32
  _buffer: ArrayBuffer | null;
33
- progress: {
34
- bytesLoaded: number;
35
- size: number;
36
- loaded: boolean;
37
- };
33
+ /** @type {import('./typedef.js').LoadingProgress} */
34
+ progress: import("./typedef.js").LoadingProgress;
38
35
  /** @type {null|(()=>void)} */
39
36
  _abortLoading: null | (() => void);
40
37
  /**
@@ -86,9 +83,9 @@ export default class NetworkLoader {
86
83
  *
87
84
  * @note the objectURL should be revoked when no longer used
88
85
  *
89
- * @returns {string|null}
86
+ * @returns {string}
90
87
  */
91
- getObjectUrl(): string | null;
88
+ getObjectURL(): string;
92
89
  #private;
93
90
  }
94
91
  import { LoadingStateMachine } from '../loading-state-machine/index.js';
@@ -1,4 +1,4 @@
1
- import { CONTENT_TYPE } from '@hkdigital/lib-sveltekit/constants/http/index.js';
1
+ import { CONTENT_TYPE } from '../../../constants/http/index.js';
2
2
 
3
3
  import {
4
4
  LoadingStateMachine,
@@ -16,9 +16,9 @@ import {
16
16
  INITIAL
17
17
  } from '../loading-state-machine/index.js';
18
18
 
19
- import * as expect from '@hkdigital/lib-sveltekit/util/expect/index.js';
19
+ import * as expect from '../../../util/expect/index.js';
20
20
 
21
- import { httpGet, loadResponseBuffer } from '@hkdigital/lib-sveltekit/util/http/index.js';
21
+ import { httpGet, loadResponseBuffer } from '../../../util/http/index.js';
22
22
 
23
23
  import { ERROR_NOT_LOADED, ERROR_TRANSFERRED } from './constants.js';
24
24
 
@@ -28,17 +28,19 @@ import { ERROR_NOT_LOADED, ERROR_TRANSFERRED } from './constants.js';
28
28
  * - Loaded data can be transferred to an AudioBufferSourceNode
29
29
  */
30
30
  export default class NetworkLoader {
31
+ // _state = $state(new LoadingStateMachine());
31
32
  _state = new LoadingStateMachine();
32
33
 
33
- // @note this exported state is set by $effect's
34
- state = $state(STATE_INITIAL);
34
+ state = $derived.by(() => {
35
+ return this._state.current;
36
+ });
35
37
 
36
38
  initial = $derived.by(() => {
37
- return this.state === STATE_INITIAL;
39
+ return this._state.current === STATE_INITIAL;
38
40
  });
39
41
 
40
42
  loaded = $derived.by(() => {
41
- return this.state === STATE_LOADED;
43
+ return this._state.current === STATE_LOADED;
42
44
  });
43
45
 
44
46
  /** @type {string|null} */
@@ -60,6 +62,8 @@ export default class NetworkLoader {
60
62
  _buffer = null;
61
63
 
62
64
  // Export state property as readonly
65
+
66
+ /** @type {import('./typedef.js').LoadingProgress} */
63
67
  progress = $derived.by(() => {
64
68
  return {
65
69
  bytesLoaded: this._bytesLoaded,
@@ -84,11 +88,15 @@ export default class NetworkLoader {
84
88
 
85
89
  const state = this._state;
86
90
 
87
- $effect(() => {
91
+ //
92
+ // ISSUE: $effect is not triggered by this._state changes,
93
+ // using onenter instead
94
+ //
95
+ this._state.onenter = () => {
88
96
  switch (state.current) {
89
97
  case STATE_LOADING:
90
98
  {
91
- // console.log('NetworkLoader:loading');
99
+ // console.log('**** NetworkLoader:loading');
92
100
  this.#load();
93
101
  }
94
102
  break;
@@ -125,15 +133,14 @@ export default class NetworkLoader {
125
133
  }
126
134
  break;
127
135
  } // end switch
128
-
129
- this.state = state.current;
130
- });
136
+ };
131
137
  }
132
138
 
133
139
  /**
134
140
  * Start loading all network data
135
141
  */
136
142
  load() {
143
+ // console.log('NetworkLoader: load() called');
137
144
  this._state.send(LOAD);
138
145
  }
139
146
 
@@ -220,16 +227,16 @@ export default class NetworkLoader {
220
227
  *
221
228
  * @note the objectURL should be revoked when no longer used
222
229
  *
223
- * @returns {string|null}
230
+ * @returns {string}
224
231
  */
225
- getObjectUrl() {
232
+ getObjectURL() {
226
233
  //
227
234
  // Example usage:
228
235
  //
229
236
  // $effect(() => {
230
237
  // if (loader.loaded) {
231
238
  // // @ts-ignore
232
- // objectUrl = loader.getObjectUrl();
239
+ // objectUrl = loader.getObjectURL();
233
240
  // }
234
241
  //
235
242
  // return () => {
@@ -240,9 +247,7 @@ export default class NetworkLoader {
240
247
  // };
241
248
  // });
242
249
 
243
- const blob = this.getBlob();
244
-
245
- return blob ? URL.createObjectURL(blob) : null;
250
+ return URL.createObjectURL(this.getBlob());
246
251
  }
247
252
 
248
253
  /**
@@ -251,7 +256,7 @@ export default class NetworkLoader {
251
256
  */
252
257
  async #load() {
253
258
  try {
254
- // console.log('#load', this._url);
259
+ // console.log('>>>> NetworkLoader:#load', this._url);
255
260
 
256
261
  if (this._abortLoading) {
257
262
  // console.log('Abort loading');
@@ -1,6 +1,6 @@
1
- import { CONTENT_TYPE, CONTENT_LENGTH } from '@hkdigital/lib-sveltekit/constants/http/index.js';
1
+ import { CONTENT_TYPE, CONTENT_LENGTH } from '../../../constants/http/index.js';
2
2
 
3
- import { OCTET_STREAM } from '@hkdigital/lib-sveltekit/constants/mime/application.js';
3
+ import { OCTET_STREAM } from '../../../constants/mime/application.js';
4
4
 
5
5
  const BASE64_DATA =
6
6
  'UklGRnwAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
@@ -0,0 +1,7 @@
1
+ declare const _default: {};
2
+ export default _default;
3
+ export type LoadingProgress = {
4
+ bytesLoaded: number;
5
+ size: number;
6
+ loaded: boolean;
7
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @typedef {Object} LoadingProgress
3
+ * @property {number} bytesLoaded
4
+ * @property {number} size
5
+ * @property {boolean} loaded
6
+ */
7
+
8
+ export default {};
@@ -0,0 +1,242 @@
1
+ <script>
2
+ import { onMount } from 'svelte';
3
+
4
+ import { ImageLoader } from '../../classes/svelte/image/index.js';
5
+
6
+ import { toSingleImageMeta } from '../../util/image/index.js';
7
+
8
+ /**
9
+ * @example
10
+ * import { ImageBox } from '/path/to/ImageBox/index.js';
11
+ *
12
+ * // @note 'as=metadata' is set by the preset
13
+ * import NeonLightsOff from '../../img/NeonLightsOff.jpg?preset=gradient';
14
+ *
15
+ * <!-- Example that fits in an outer-box -->
16
+ *
17
+ * <div class="outer-box">
18
+ * <ImageBox image={ArmyGreen} fit="contain" position="center center" />
19
+ * </div>
20
+ *
21
+ * <!-- Examples that has have width, height or aspect set -->
22
+ *
23
+ * <ImageBox
24
+ * image={ArmyGreen}
25
+ * fit="contain"
26
+ * position="center center"
27
+ * width="w-[200px]"
28
+ * height="h-[200px]"
29
+ * classes="border-8 border-green-500"
30
+ * />
31
+ *
32
+ * <ImageBox
33
+ * image={ArmyGreen}
34
+ * fit="contain"
35
+ * position="center center"
36
+ * width="w-[200px]"
37
+ * aspect="aspect-square"
38
+ * classes="border-8 border-green-500"
39
+ * />
40
+ *
41
+ * <ImageBox
42
+ * image={ArmyGreen}
43
+ * fit="contain"
44
+ * position="center center"
45
+ * height="h-[200px]"
46
+ * aspect="aspect-square"
47
+ * classes="border-8 border-green-500"
48
+ * />
49
+ *
50
+ * <!-- Or hack it using !important -->
51
+ *
52
+ * <ImageBox
53
+ * image={ArmyGreen}
54
+ * fit="contain"
55
+ * position="center center"
56
+ * classes="!w-[200px] !h-[200px] border-8 border-green-500"
57
+ * />
58
+ */
59
+
60
+ /**
61
+ * @typedef {import('./typedef.js').ObjectFit} ObjectFit
62
+ * @typedef {import('./typedef.js').ObjectPosition} ObjectPosition
63
+ *
64
+ * @typedef {import('../../classes/svelte/network-loader/typedef.js').LoadingProgress} LoadingProgress
65
+ *
66
+ * @typedef {import('../../config/typedef.js').ImageMeta} ImageMeta
67
+ *
68
+ * @typedef {Object} Props
69
+ * @property {string} [base] - Base styling class
70
+ * @property {string} [bg] - Background styling class
71
+ * @property {string} [classes] - Additional CSS classes
72
+ * @property {string} [width] - Width of the image container
73
+ * @property {string} [height] - Height of the image container
74
+ * @property {string} [aspect] - Aspect ratio of the image container
75
+ * @property {string} [overflow] - Overflow behavior
76
+ * @property {ObjectFit} [fit] - Object-fit property
77
+ * @property {ObjectPosition} [position] - Object-position property
78
+ *
79
+ * @property {ImageMeta|ImageMeta[]} [imageMeta]
80
+ * Image metadata, TODO: array of image metadata for responsive image
81
+ *
82
+ * @property {ImageLoader} [imageLoader]
83
+ * Image loader
84
+ *
85
+ * @property {string} [alt] - Alternative text for the image
86
+ * @property {() => LoadingProgress} [onProgress] - Progress callback function
87
+ * @property {*} [attr] - Additional arbitrary attributes
88
+ */
89
+
90
+ /** @type {Props} */
91
+ let {
92
+ // Style
93
+ base,
94
+ bg,
95
+ classes,
96
+ width,
97
+ height,
98
+ aspect,
99
+ overflow = 'overflow-clip',
100
+
101
+ // Fitting and positioning of image in its container
102
+ fit = 'contain',
103
+ position = 'left top',
104
+
105
+ // Image meta data
106
+ imageMeta,
107
+
108
+ imageLoader,
109
+
110
+ alt = '',
111
+
112
+ // Attributes
113
+ ...attrs
114
+ } = $props();
115
+
116
+ // let show = $state(false);
117
+
118
+ /** @type {HTMLDivElement|undefined} */
119
+ let imgBoxElem = $state();
120
+
121
+ /** @type {HTMLImageElement|undefined} */
122
+ let imgElem = $state();
123
+
124
+ let aspectStyle = $state('');
125
+
126
+ // > Loading
127
+
128
+ let metaWidth = $state(0);
129
+ let metaHeight = $state(0);
130
+
131
+ let imageMeta_ = $state();
132
+
133
+ $effect(() => {
134
+ if( imageMeta )
135
+ {
136
+ imageMeta_ = toSingleImageMeta( imageMeta );
137
+ }
138
+ });
139
+
140
+ $effect(() => {
141
+ //
142
+ // Set meta width and height
143
+ //
144
+ if (imageMeta_) {
145
+ if (imageMeta_.width) {
146
+ metaWidth = imageMeta_.width;
147
+ }
148
+
149
+ if (imageMeta_.height) {
150
+ metaHeight = imageMeta_.height;
151
+ }
152
+ }
153
+ });
154
+
155
+ /** @type {ImageLoader|undefined} */
156
+ let imageLoader_ = $state();
157
+
158
+ $effect( () => {
159
+ //
160
+ // User supplied imageLoader instead of imageMeta
161
+ //
162
+ if( !imageMeta && imageLoader && !imageLoader_ )
163
+ {
164
+ imageLoader_ = imageLoader;
165
+ imageMeta_ = imageLoader.imageMeta;
166
+ }
167
+ } );
168
+
169
+ /** @type {string|null} */
170
+ let objectUrl = $state(null);
171
+
172
+ $effect(() => {
173
+ //
174
+ // Create image loader
175
+ //
176
+ if (imageMeta_ && !imageLoader_) {
177
+ imageLoader_ = new ImageLoader({ imageMeta: imageMeta_ });
178
+ }
179
+ });
180
+
181
+ $effect(() => {
182
+ //
183
+ // Start loading if imageLoader_ is in state 'initial'
184
+ //
185
+ // TODO: implement lazy flag
186
+ //
187
+ if (imageLoader_?.initial) {
188
+ imageLoader_.load();
189
+ }
190
+ });
191
+
192
+ $effect(() => {
193
+ //
194
+ // Get objectUrl when the image has finished loading
195
+ //
196
+ if (imageLoader_.loaded) {
197
+ // @ts-ignore
198
+ objectUrl = imageLoader_.getObjectURL();
199
+ }
200
+
201
+ return () => {
202
+ if (objectUrl) {
203
+ URL.revokeObjectURL(objectUrl);
204
+ objectUrl = null;
205
+ }
206
+ };
207
+ });
208
+
209
+ </script>
210
+
211
+ <div
212
+ data-image-box
213
+ bind:this={imgBoxElem}
214
+ class="{base} {bg} {aspect} {overflow} {width} {height} {classes}"
215
+ style:--fit={fit}
216
+ style:--pos={position}
217
+ style:aspect-ratio={aspectStyle}
218
+ style:width={width || (height && aspect) ? undefined : '100%'}
219
+ style:height={height || (width && aspect) ? undefined : '100%'}
220
+ {...attrs}
221
+ >
222
+ {#if metaWidth && metaHeight}
223
+ <img src={objectUrl} {alt} width={metaWidth} height={metaHeight} />
224
+ {/if}
225
+ </div>
226
+
227
+ <style>
228
+ [data-image-box] {
229
+ max-width: 100%;
230
+ max-height: 100%;
231
+ }
232
+
233
+ img {
234
+ display: block;
235
+ width: 100%;
236
+ height: 100%;
237
+ max-width: 100%;
238
+ max-height: 100%;
239
+ object-fit: var(--fit);
240
+ object-position: var(--pos);
241
+ }
242
+ </style>
@@ -0,0 +1,60 @@
1
+ export default ImageBox;
2
+ declare const ImageBox: import("svelte").Component<{
3
+ /**
4
+ * - Base styling class
5
+ */
6
+ base?: string;
7
+ /**
8
+ * - Background styling class
9
+ */
10
+ bg?: string;
11
+ /**
12
+ * - Additional CSS classes
13
+ */
14
+ classes?: string;
15
+ /**
16
+ * - Width of the image container
17
+ */
18
+ width?: string;
19
+ /**
20
+ * - Height of the image container
21
+ */
22
+ height?: string;
23
+ /**
24
+ * - Aspect ratio of the image container
25
+ */
26
+ aspect?: string;
27
+ /**
28
+ * - Overflow behavior
29
+ */
30
+ overflow?: string;
31
+ /**
32
+ * - Object-fit property
33
+ */
34
+ fit?: import("./typedef.js").ObjectFit;
35
+ /**
36
+ * - Object-position property
37
+ */
38
+ position?: import("./typedef.js").ObjectPosition;
39
+ /**
40
+ * Image metadata, TODO: array of image metadata for responsive image
41
+ */
42
+ imageMeta?: import("../../config/typedef.js").ImageMeta | import("../../config/typedef.js").ImageMeta[];
43
+ /**
44
+ * Image loader
45
+ */
46
+ imageLoader?: ImageLoader;
47
+ /**
48
+ * - Alternative text for the image
49
+ */
50
+ alt?: string;
51
+ /**
52
+ * - Progress callback function
53
+ */
54
+ onProgress?: () => import("../../classes/svelte/network-loader/typedef.js").LoadingProgress;
55
+ /**
56
+ * - Additional arbitrary attributes
57
+ */
58
+ attr?: any;
59
+ }, {}, "">;
60
+ import { ImageLoader } from '../../classes/svelte/image/index.js';
@@ -52,7 +52,7 @@
52
52
 
53
53
  if (variantsLoader.loaded) {
54
54
  // @ts-ignore
55
- imageUrl = variantsLoader.getObjectUrl();
55
+ imageUrl = variantsLoader.getObjectURL();
56
56
 
57
57
  // image = new Image();
58
58
  // image.src = url;
@@ -0,0 +1,4 @@
1
+ declare const _default: {};
2
+ export default _default;
3
+ export type ObjectFit = ("contain" | "cover" | "fill" | "none" | "scale-down");
4
+ export type ObjectPosition = ("left" | "center" | "right" | "top" | "bottom" | "left top" | "left center" | "left bottom" | "center top" | "center center" | "center bottom" | "right top" | "right center" | "right bottom" | `${number}% ${number}%` | `${number}px ${number}px`);
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @typedef {(
3
+ * 'contain' |
4
+ * 'cover' |
5
+ * 'fill' |
6
+ * 'none' |
7
+ * 'scale-down'
8
+ * )} ObjectFit
9
+ */
10
+
11
+ /**
12
+ * @typedef {(
13
+ * 'left' |
14
+ * 'center' |
15
+ * 'right' |
16
+ * 'top' |
17
+ * 'bottom' |
18
+ * 'left top' |
19
+ * 'left center' |
20
+ * 'left bottom' |
21
+ * 'center top' |
22
+ * 'center center' |
23
+ * 'center bottom' |
24
+ * 'right top' |
25
+ * 'right center' |
26
+ * 'right bottom' |
27
+ * `${number}% ${number}%` |
28
+ * `${number}px ${number}px`
29
+ * )} ObjectPosition
30
+ */
31
+
32
+ export default {};
@@ -5,7 +5,7 @@ const DEFAULT_PRESETS = {
5
5
  format: 'avif',
6
6
  quality: '90'
7
7
  },
8
- gradient: {
8
+ render: {
9
9
  format: 'jpg',
10
10
  quality: '95',
11
11
  as: 'metadata'
@@ -15,6 +15,11 @@ const DEFAULT_PRESETS = {
15
15
  quality: '95',
16
16
  as: 'metadata'
17
17
  },
18
+ gradient: {
19
+ format: 'jpg',
20
+ quality: '95',
21
+ as: 'metadata'
22
+ },
18
23
  drawing: {
19
24
  format: 'avif',
20
25
  quality: '90',
@@ -1,10 +1,10 @@
1
+ // export interface ImageMeta {
2
+ // src: string;
3
+ // width: number;
4
+ // height: number;
5
+ // }
1
6
 
2
-
3
- interface ImageMeta {
4
- src: string;
5
- width?: number;
6
- height?: number;
7
- }
7
+ type ImageMeta = import('./typedef.js').ImageMeta;
8
8
 
9
9
  declare module '*?responsive' {
10
10
  const out: ImageMeta[];
@@ -16,52 +16,62 @@ declare module '*&responsive' {
16
16
  export default out;
17
17
  }
18
18
 
19
- declare module '*?preset=gradient' {
20
- const out: ImageMeta|ImageMeta[];
19
+ declare module '*?preset=photo' {
20
+ const out: ImageMeta | ImageMeta[];
21
21
  export default out;
22
22
  }
23
23
 
24
- declare module '*&preset=gradient' {
25
- const out: ImageMeta|ImageMeta[];
24
+ declare module '*&preset=photo' {
25
+ const out: ImageMeta | ImageMeta[];
26
26
  export default out;
27
27
  }
28
28
 
29
- declare module '*?preset=photo' {
30
- const out: ImageMeta|ImageMeta[];
29
+ declare module '*?preset=render' {
30
+ const out: ImageMeta | ImageMeta[];
31
31
  export default out;
32
32
  }
33
33
 
34
- declare module '*&preset=photo' {
35
- const out: ImageMeta|ImageMeta[];
34
+ declare module '*&preset=render' {
35
+ const out: ImageMeta | ImageMeta[];
36
+ export default out;
37
+ }
38
+
39
+ declare module '*?preset=gradient' {
40
+ const out: ImageMeta | ImageMeta[];
41
+ export default out;
42
+ }
43
+
44
+ declare module '*&preset=gradient' {
45
+ const out: ImageMeta | ImageMeta[];
36
46
  export default out;
37
47
  }
38
48
 
39
49
  declare module '*?preset=drawing' {
40
- const out: ImageMeta|ImageMeta[];
50
+ const out: ImageMeta | ImageMeta[];
41
51
  export default out;
42
52
  }
43
53
 
44
54
  declare module '*&preset=drawing' {
45
- const out: ImageMeta|ImageMeta[];
55
+ const out: ImageMeta | ImageMeta[];
46
56
  export default out;
47
57
  }
48
58
 
49
59
  declare module '*?preset=savedata' {
50
- const out: ImageMeta|ImageMeta[];
60
+ const out: ImageMeta | ImageMeta[];
51
61
  export default out;
52
62
  }
53
63
 
54
64
  declare module '*&preset=savedata' {
55
- const out: ImageMeta|ImageMeta[];
65
+ const out: ImageMeta | ImageMeta[];
56
66
  export default out;
57
67
  }
58
68
 
59
69
  declare module '*?preset=blur' {
60
- const out: ImageMeta|ImageMeta[];
70
+ const out: ImageMeta | ImageMeta[];
61
71
  export default out;
62
72
  }
63
73
 
64
74
  declare module '*&preset=blur' {
65
- const out: ImageMeta|ImageMeta[];
75
+ const out: ImageMeta | ImageMeta[];
66
76
  export default out;
67
- }
77
+ }
@@ -0,0 +1,7 @@
1
+ declare const _default: {};
2
+ export default _default;
3
+ export type ImageMeta = {
4
+ src: string;
5
+ width: number;
6
+ height: number;
7
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @typedef {object} ImageMeta
3
+ * @property {string} src
4
+ * @property {number} width
5
+ * @property {number} height
6
+ */
7
+
8
+ export default {};
@@ -1,4 +1,4 @@
1
- import { CONTENT_TYPE, CONTENT_LENGTH } from '@hkdigital/lib-sveltekit/constants/http/index.js';
1
+ import { CONTENT_TYPE, CONTENT_LENGTH } from '../../constants/http/index.js';
2
2
 
3
3
  import { OCTET_STREAM } from '../../constants/mime/index.js';
4
4
 
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Returns the unchanged image meta object or the last item of
3
+ * an array of ImageMeta objects. This is assumed to be a list
4
+ * of sorted responsive image formats, so it should be the
5
+ * largest image.
6
+ *
7
+ * @param {ImageMeta|ImageMeta[]} imageMeta
8
+ */
9
+ export function toSingleImageMeta(imageMeta: ImageMeta | ImageMeta[]): import("../../config/typedef").ImageMeta;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Returns the unchanged image meta object or the last item of
3
+ * an array of ImageMeta objects. This is assumed to be a list
4
+ * of sorted responsive image formats, so it should be the
5
+ * largest image.
6
+ *
7
+ * @param {ImageMeta|ImageMeta[]} imageMeta
8
+ */
9
+ export function toSingleImageMeta(imageMeta) {
10
+ if (Array.isArray(imageMeta)) {
11
+ if (!imageMeta.length) {
12
+ throw new Error('List of ImageMeta objects is empty');
13
+ }
14
+ imageMeta = imageMeta[imageMeta.length - 1];
15
+ }
16
+
17
+ if (typeof imageMeta === 'object') {
18
+ return imageMeta;
19
+ } else if (!imageMeta) {
20
+ throw new Error('Missing [imageMeta]');
21
+ }
22
+
23
+ throw new Error('Invalid value for parameter [imageMeta]');
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-sveltekit",
3
- "version": "0.0.90",
3
+ "version": "0.0.92",
4
4
  "author": "Jens Kleinhout, HKdigital (https://hkdigital.nl)",
5
5
  "license": "ISC",
6
6
  "repository": {
@@ -17,19 +17,19 @@
17
17
  ],
18
18
  "scripts": {
19
19
  "dev": "vite dev",
20
- "build": "vite build && npm run package",
20
+ "build": "cross-env vite build && npm run package",
21
21
  "preview": "vite preview",
22
- "package": "svelte-kit sync && svelte-package && publint",
23
- "prepublishOnly": "npm run package",
24
- "publish:npm": "npm version patch && npm publish --access public && git push",
25
- "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
26
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
22
+ "package": "cross-env svelte-kit sync && svelte-package && publint",
23
+ "prepublishOnly": "cross-env npm run package",
24
+ "publish:npm": "cross-env npm version patch && npm publish --access public && git push",
25
+ "check": "cross-env svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
26
+ "check:watch": "cross-env svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
27
27
  "format": "prettier --write .",
28
- "lint": "prettier --check . && eslint .",
28
+ "lint": "cross-env prettier --check . && eslint .",
29
29
  "test:unit": "vitest",
30
- "test": "npm run test:unit -- --run && npm run test:e2e",
30
+ "test": "cross-env npm run test:unit -- --run && npm run test:e2e",
31
31
  "test:e2e": "playwright test",
32
- "upgrade:all": "ncu -u && pnpm install"
32
+ "upgrade:all": "cross-env ncu -u && pnpm install"
33
33
  },
34
34
  "files": [
35
35
  "dist",
@@ -48,6 +48,7 @@
48
48
  "./*": "./dist/*"
49
49
  },
50
50
  "peerDependencies": {
51
+ "@sveltejs/kit": "^2.15.2",
51
52
  "svelte": "^5.0.0"
52
53
  },
53
54
  "devDependencies": {
@@ -57,25 +58,26 @@
57
58
  "@sveltejs/vite-plugin-svelte": "^5.0.3",
58
59
  "@types/eslint": "^9.6.1",
59
60
  "autoprefixer": "^10.4.20",
60
- "eslint": "^9.17.0",
61
- "eslint-config-prettier": "^9.1.0",
61
+ "cross-env": "^7.0.3",
62
+ "eslint": "^9.18.0",
63
+ "eslint-config-prettier": "^10.0.1",
62
64
  "eslint-plugin-svelte": "^2.46.1",
63
65
  "globals": "^15.14.0",
64
66
  "jsdom": "^26.0.0",
65
67
  "prettier": "^3.4.2",
66
- "prettier-plugin-svelte": "^3.3.2",
68
+ "prettier-plugin-svelte": "^3.3.3",
67
69
  "prettier-plugin-tailwindcss": "^0.6.9",
68
- "publint": "^0.3.0",
70
+ "publint": "^0.3.2",
69
71
  "standardized-audio-context-mock": "^9.7.15",
70
- "svelte": "^5.17.3",
71
- "svelte-check": "^4.1.3",
72
+ "svelte": "^5.17.5",
73
+ "svelte-check": "^4.1.4",
72
74
  "tailwindcss": "^3.4.17",
73
75
  "typescript": "^5.7.3",
74
76
  "vite": "^6.0.7",
77
+ "vite-imagetools": "^7.0.5",
75
78
  "vitest": "^2.1.8"
76
79
  },
77
80
  "dependencies": {
78
- "@sveltejs/kit": "^2.15.2",
79
81
  "runed": "^0.23.0",
80
82
  "valibot": "^0.42.1",
81
83
  "zod": "^3.24.1"