@hkdigital/lib-sveltekit 0.0.91 → 0.0.93

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.
@@ -4,6 +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;
7
15
  get url(): string;
16
+ #private;
8
17
  }
18
+ export type ImageMeta = import("./typedef.js").ImageMeta;
9
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,31 +14,29 @@ import {
10
14
  * - The loading process can be monitored
11
15
  */
12
16
  export default class ImageLoader extends NetworkLoader {
13
- // onenter(label) {
14
- // console.log('ImageLoader:onenter', label);
15
- // }
17
+ /** @type {ImageMeta} */
18
+ #imageMeta;
19
+
20
+ /**
21
+ * @param {object} _
22
+ * @param {ImageMeta|ImageMeta[]} _.imageMeta
23
+ */
24
+ constructor({ imageMeta }) {
25
+ imageMeta = toSingleImageMeta(imageMeta);
16
26
 
17
- // constructor({ url }) {
18
- // super({ url });
27
+ super({ url: imageMeta.src });
19
28
 
20
- // $effect(() => {
21
- // console.log('ImageLoader: state', this.state);
22
- // });
23
- // }
29
+ this.#imageMeta = imageMeta;
30
+ }
31
+
32
+ get imageMeta() {
33
+ return this.#imageMeta;
34
+ }
24
35
 
25
36
  get url() {
26
37
  return this._url;
27
38
  }
28
39
 
29
- //
30
- // /**
31
- // * Construct ImageLoader
32
- // *
33
- // * @param {object} _
34
- // * @param {string} _.url
35
- // */
36
- // constructor( { url } ) {}
37
- //
38
40
  // /**
39
41
  // * Get object URL that can be used as src parameter of an HTML image
40
42
  // *
@@ -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
+ }
@@ -83,9 +83,9 @@ export default class NetworkLoader {
83
83
  *
84
84
  * @note the objectURL should be revoked when no longer used
85
85
  *
86
- * @returns {string|null}
86
+ * @returns {string}
87
87
  */
88
- getObjectURL(): string | null;
88
+ getObjectURL(): string;
89
89
  #private;
90
90
  }
91
91
  import { LoadingStateMachine } from '../loading-state-machine/index.js';
@@ -227,7 +227,7 @@ export default class NetworkLoader {
227
227
  *
228
228
  * @note the objectURL should be revoked when no longer used
229
229
  *
230
- * @returns {string|null}
230
+ * @returns {string}
231
231
  */
232
232
  getObjectURL() {
233
233
  //
@@ -247,9 +247,7 @@ export default class NetworkLoader {
247
247
  // };
248
248
  // });
249
249
 
250
- const blob = this.getBlob();
251
-
252
- return blob ? URL.createObjectURL(blob) : null;
250
+ return URL.createObjectURL(this.getBlob());
253
251
  }
254
252
 
255
253
  /**
@@ -3,6 +3,8 @@
3
3
 
4
4
  import { ImageLoader } from '../../classes/svelte/image/index.js';
5
5
 
6
+ import { toSingleImageMeta } from '../../util/image/index.js';
7
+
6
8
  /**
7
9
  * @example
8
10
  * import { ImageBox } from '/path/to/ImageBox/index.js';
@@ -73,7 +75,13 @@
73
75
  * @property {string} [overflow] - Overflow behavior
74
76
  * @property {ObjectFit} [fit] - Object-fit property
75
77
  * @property {ObjectPosition} [position] - Object-position property
76
- * @property {ImageMeta|ImageMeta[]} image - Image metadata or array of image metadata
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
+ *
77
85
  * @property {string} [alt] - Alternative text for the image
78
86
  * @property {() => LoadingProgress} [onProgress] - Progress callback function
79
87
  * @property {*} [attr] - Additional arbitrary attributes
@@ -94,8 +102,10 @@
94
102
  fit = 'contain',
95
103
  position = 'left top',
96
104
 
97
- // Image meta
98
- image,
105
+ // Image meta data
106
+ imageMeta,
107
+
108
+ imageLoader,
99
109
 
100
110
  alt = '',
101
111
 
@@ -118,16 +128,12 @@
118
128
  let metaWidth = $state(0);
119
129
  let metaHeight = $state(0);
120
130
 
121
- let singleImage = $state();
131
+ let imageMeta_ = $state();
122
132
 
123
133
  $effect(() => {
124
- if (image instanceof Array && image[0]) {
125
- throw new Error('Responsive image is not supported (yet)');
126
- } else if (typeof image === 'object') {
127
- // expect image.src
128
- // expect image.width
129
- // expect image.height
130
- singleImage = image;
134
+ if( imageMeta )
135
+ {
136
+ imageMeta_ = toSingleImageMeta( imageMeta );
131
137
  }
132
138
  });
133
139
 
@@ -135,19 +141,30 @@
135
141
  //
136
142
  // Set meta width and height
137
143
  //
138
- if (singleImage) {
139
- if (singleImage.width) {
140
- metaWidth = singleImage.width;
144
+ if (imageMeta_) {
145
+ if (imageMeta_.width) {
146
+ metaWidth = imageMeta_.width;
141
147
  }
142
148
 
143
- if (singleImage.height) {
144
- metaHeight = singleImage.height;
149
+ if (imageMeta_.height) {
150
+ metaHeight = imageMeta_.height;
145
151
  }
146
152
  }
147
153
  });
148
154
 
149
155
  /** @type {ImageLoader|undefined} */
150
- let imageLoader = $state();
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
+ } );
151
168
 
152
169
  /** @type {string|null} */
153
170
  let objectUrl = $state(null);
@@ -156,20 +173,19 @@
156
173
  //
157
174
  // Create image loader
158
175
  //
159
- if (singleImage.src && !imageLoader) {
160
- const url = singleImage.src;
161
- imageLoader = new ImageLoader({ url });
176
+ if (imageMeta_ && !imageLoader_) {
177
+ imageLoader_ = new ImageLoader({ imageMeta: imageMeta_ });
162
178
  }
163
179
  });
164
180
 
165
181
  $effect(() => {
166
182
  //
167
- // Start loading if imageLoader is in state 'initial'
183
+ // Start loading if imageLoader_ is in state 'initial'
168
184
  //
169
185
  // TODO: implement lazy flag
170
186
  //
171
- if (imageLoader?.initial) {
172
- imageLoader.load();
187
+ if (imageLoader_?.initial) {
188
+ imageLoader_.load();
173
189
  }
174
190
  });
175
191
 
@@ -177,9 +193,9 @@
177
193
  //
178
194
  // Get objectUrl when the image has finished loading
179
195
  //
180
- if (imageLoader.loaded) {
196
+ if (imageLoader_.loaded) {
181
197
  // @ts-ignore
182
- objectUrl = imageLoader.getObjectURL();
198
+ objectUrl = imageLoader_.getObjectURL();
183
199
  }
184
200
 
185
201
  return () => {
@@ -190,9 +206,6 @@
190
206
  };
191
207
  });
192
208
 
193
- $effect(() => {
194
- console.log('classes', classes);
195
- });
196
209
  </script>
197
210
 
198
211
  <div
@@ -37,9 +37,13 @@ declare const ImageBox: import("svelte").Component<{
37
37
  */
38
38
  position?: import("./typedef.js").ObjectPosition;
39
39
  /**
40
- * - Image metadata or array of image metadata
40
+ * Image metadata, TODO: array of image metadata for responsive image
41
41
  */
42
- image: import("../../config/typedef.js").ImageMeta | import("../../config/typedef.js").ImageMeta[];
42
+ imageMeta?: import("../../config/typedef.js").ImageMeta | import("../../config/typedef.js").ImageMeta[];
43
+ /**
44
+ * Image loader
45
+ */
46
+ imageLoader?: ImageLoader;
43
47
  /**
44
48
  * - Alternative text for the image
45
49
  */
@@ -53,3 +57,4 @@ declare const ImageBox: import("svelte").Component<{
53
57
  */
54
58
  attr?: any;
55
59
  }, {}, "">;
60
+ import { ImageLoader } from '../../classes/svelte/image/index.js';
@@ -1,5 +1,5 @@
1
1
  <script>
2
- /** @typedef {import('../../types/imagetools.js').ImageMeta} ImageMeta */
2
+ /** @typedef {import('../../config/typedef.js').ImageMeta} ImageMeta */
3
3
 
4
4
  import { ImageVariantsLoader } from '../../classes/svelte/image/index.js';
5
5
 
@@ -1,5 +1,5 @@
1
1
  export default ResponsiveImage;
2
- export type ImageMeta = any;
2
+ export type ImageMeta = import("../../config/typedef.js").ImageMeta;
3
3
  declare const ResponsiveImage: import("svelte").Component<{
4
4
  base?: string;
5
5
  classes?: string;
@@ -1,4 +1,5 @@
1
+ export { default as ImageBox } from "./ImageBox.svelte";
1
2
  export { default as ResponsiveImage } from "./ResponsiveImage.svelte";
2
3
  declare const _default: {};
3
4
  export default _default;
4
- export type ImageMeta = any;
5
+ export type ImageMeta = import("../../config/typedef.js").ImageMeta;
@@ -1,5 +1,6 @@
1
- /** @typedef {import('../../types/imagetools.js').ImageMeta} ImageMeta */
1
+ /** @typedef {import('../../config/typedef.js').ImageMeta} ImageMeta */
2
2
 
3
+ export { default as ImageBox } from './ImageBox.svelte';
3
4
  export { default as ResponsiveImage } from './ResponsiveImage.svelte';
4
5
 
5
6
  export default {};
@@ -1,9 +1,3 @@
1
- // export interface ImageMeta {
2
- // src: string;
3
- // width: number;
4
- // height: number;
5
- // }
6
-
7
1
  type ImageMeta = import('./typedef.js').ImageMeta;
8
2
 
9
3
  declare module '*?responsive' {
@@ -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.91",
3
+ "version": "0.0.93",
4
4
  "author": "Jens Kleinhout, HKdigital (https://hkdigital.nl)",
5
5
  "license": "ISC",
6
6
  "repository": {
@@ -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": {
@@ -58,18 +59,18 @@
58
59
  "@types/eslint": "^9.6.1",
59
60
  "autoprefixer": "^10.4.20",
60
61
  "cross-env": "^7.0.3",
61
- "eslint": "^9.17.0",
62
- "eslint-config-prettier": "^9.1.0",
62
+ "eslint": "^9.18.0",
63
+ "eslint-config-prettier": "^10.0.1",
63
64
  "eslint-plugin-svelte": "^2.46.1",
64
65
  "globals": "^15.14.0",
65
66
  "jsdom": "^26.0.0",
66
67
  "prettier": "^3.4.2",
67
- "prettier-plugin-svelte": "^3.3.2",
68
+ "prettier-plugin-svelte": "^3.3.3",
68
69
  "prettier-plugin-tailwindcss": "^0.6.9",
69
- "publint": "^0.3.0",
70
+ "publint": "^0.3.2",
70
71
  "standardized-audio-context-mock": "^9.7.15",
71
- "svelte": "^5.17.3",
72
- "svelte-check": "^4.1.3",
72
+ "svelte": "^5.17.5",
73
+ "svelte-check": "^4.1.4",
73
74
  "tailwindcss": "^3.4.17",
74
75
  "typescript": "^5.7.3",
75
76
  "vite": "^6.0.7",
@@ -77,7 +78,6 @@
77
78
  "vitest": "^2.1.8"
78
79
  },
79
80
  "dependencies": {
80
- "@sveltejs/kit": "^2.15.2",
81
81
  "runed": "^0.23.0",
82
82
  "valibot": "^0.42.1",
83
83
  "zod": "^3.24.1"
@@ -1,162 +0,0 @@
1
- <script>
2
- import { onMount } from 'svelte';
3
-
4
- /**
5
- * @example
6
- * import { EnhancedImage }
7
- * from '$lib/components/EnhancedImage/index.js';
8
- *
9
- * <EnhancedImage
10
- * classes="aspect-video max-h-svh w-full border-8 border-pink-500"
11
- * src={NeonLightsOff}
12
- * onload={() => {
13
- * console.log('loaded');
14
- * }}
15
- * />
16
- */
17
-
18
- /**
19
- * @type {{
20
- * base?: string,
21
- * bg?: string,
22
- * classes?: string,
23
- * overflow?: string,
24
- * aspect?: string,
25
- * fit?: string,
26
- * position?: string,
27
- * src: string | import('$lib/typedef/image.js').Picture,
28
- * alt?: string,
29
- * onload?: ( e: (Event|{ type: string, target: HTMLImageElement }) ) => void,
30
- * onerror?: ( e: (Event|{ type: string, target: HTMLImageElement }) ) => void,
31
- * loading?: string
32
- * } & { [attr: string]: * }}
33
- */
34
- let {
35
- // Style
36
- base,
37
- bg,
38
- classes,
39
-
40
- overflow = 'overflow-clip',
41
- aspect,
42
-
43
- fit = 'contain',
44
- position = 'left top',
45
-
46
- src,
47
- alt = '',
48
- onload,
49
- onerror,
50
- loading,
51
-
52
- // Attributes
53
- ...attrs
54
- } = $props();
55
-
56
- // let show = $state(false);
57
-
58
- /** @type {HTMLDivElement|undefined} */
59
- let imgBoxElem = $state();
60
-
61
- /** @type {HTMLImageElement|undefined} */
62
- let imgElem = $state();
63
-
64
- /** @type {string | import('$lib/typedef/image.js').Picture} */
65
- let src_;
66
-
67
- $effect(() => {
68
- if (src_ && src !== src_) {
69
- throw new Error('Property [src] change is not supported');
70
- }
71
- });
72
-
73
- let aspectStyle = $state('');
74
-
75
- // > Event names
76
- const LOAD = 'load';
77
- const ERROR = 'error';
78
-
79
- // > onMount
80
-
81
- onMount(() => {
82
- // show = true;
83
-
84
- imgElem = imgBoxElem?.getElementsByTagName('img')[0];
85
-
86
- if (!imgElem) {
87
- throw new Error('Missing IMG element');
88
- }
89
-
90
- // > Auto set box aspect to same as image aspect if not set
91
-
92
- if (!aspect) {
93
- aspectStyle = `${imgElem.width / imgElem.height}`;
94
- } else {
95
- aspectStyle = '';
96
- }
97
-
98
- // > Register image onload and onerror handlers
99
-
100
- /** @type {( e: Event ) => void} */
101
- let onloadFn;
102
-
103
- if (onload) {
104
- if (imgElem?.complete) {
105
- onload({ type: LOAD, target: imgElem });
106
- } else {
107
- onloadFn = onload;
108
- imgElem?.addEventListener(LOAD, onloadFn);
109
- }
110
- }
111
-
112
- /** @type {( e: Event ) => void} */
113
- let onerrorFn;
114
-
115
- if (onerror) {
116
- onerrorFn = onerror;
117
- imgElem?.addEventListener(ERROR, onerrorFn);
118
- }
119
-
120
- return () => {
121
- if (onloadFn) {
122
- imgElem?.removeEventListener(LOAD, onloadFn);
123
- }
124
-
125
- if (onerrorFn) {
126
- imgElem?.removeEventListener(ERROR, onerrorFn);
127
- }
128
- };
129
- });
130
- </script>
131
-
132
- <div
133
- data-hk-enhanced-image
134
- bind:this={imgBoxElem}
135
- class="{base} {bg} {aspect} {overflow} {classes}"
136
- style:--fit={fit}
137
- style:--pos={position}
138
- style:aspect-ratio={aspectStyle}
139
- {...attrs}
140
- >
141
- <enhanced:img {src} {alt} />
142
- </div>
143
-
144
- <style>
145
- [data-hk-enhanced-image],
146
- :global([data-hk-enhanced-image] picture),
147
- :global([data-hk-enhanced-image] img) {
148
- display: block;
149
- width: 100%;
150
- height: 100%;
151
- }
152
- :global([data-hk-enhanced-image] picture),
153
- :global([data-hk-enhanced-image] img) {
154
- max-width: 100%;
155
- max-height: 100%;
156
- }
157
-
158
- :global([data-hk-enhanced-image] img) {
159
- object-fit: var(--fit);
160
- object-position: var(--pos);
161
- }
162
- </style>