@lonik/oh-image 2.1.2 → 2.2.0

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.
package/dist/react.js CHANGED
@@ -84,7 +84,7 @@ function useImgLoaded(src) {
84
84
  * RegExpr to determine whether a src in a srcset is using width descriptors.
85
85
  * Should match something like: "100w, 200w".
86
86
  */
87
- const VALID_WIDTH_DESCRIPTOR_SRCSET = /^((\s*\d+w\s*(,|$)){1,})$/;
87
+ const VALID_WIDTH_DESCRIPTOR_SRCSET = /^(\s*\S+\s+\d+w\s*(,|$)\s*)+$/;
88
88
  function resolveOptions(prop, defaultOptions) {
89
89
  const resolved = {
90
90
  ...defaultOptions,
@@ -102,15 +102,14 @@ function resolveOptions(prop, defaultOptions) {
102
102
  return resolved;
103
103
  }
104
104
  function resolveDecoding(prop) {
105
- return prop.priority || prop.asap ? "async" : prop.decoding;
105
+ return prop.priority ? "async" : prop.decoding;
106
106
  }
107
107
  function resolveFetchPriority(prop) {
108
- if (prop.priority || prop.asap) return "high";
108
+ if (prop.priority) return "high";
109
109
  return prop.fetchPriority ?? "auto";
110
110
  }
111
111
  function resolveSrcSet(prop) {
112
112
  if (prop.srcSet) return prop.srcSet;
113
- if (typeof prop.src === "object") return prop.src.srcSets;
114
113
  if (!prop.breakpoints) return;
115
114
  const baseSrc = prop.src;
116
115
  const entries = [];
@@ -123,7 +122,7 @@ function resolveSrcSet(prop) {
123
122
  return entries.join(", ");
124
123
  }
125
124
  function resolveLoading(prop) {
126
- const priority = prop.priority || prop.asap;
125
+ const priority = prop.priority;
127
126
  if (!priority && prop.loading !== void 0) return prop.loading;
128
127
  return priority ? "eager" : "lazy";
129
128
  }
@@ -138,7 +137,6 @@ function resolveSizes(prop, resolvedSrcSet, resolvedLoading) {
138
137
  return sizes;
139
138
  }
140
139
  function resolveSrc(prop) {
141
- if (typeof prop.src === "object") return prop.src.src;
142
140
  if (prop.loader) return prop.loader({
143
141
  src: prop.src,
144
142
  width: prop.width,
@@ -148,17 +146,16 @@ function resolveSrc(prop) {
148
146
  }
149
147
  function resolveWidth(prop) {
150
148
  if (prop.width) return prop.width;
151
- if (typeof prop.src === "object") return prop.src.width;
152
149
  }
153
150
  function resolveHeight(prop) {
154
151
  if (prop.height) return prop.height;
155
- if (typeof prop.src === "object") return prop.src.height;
156
152
  }
157
153
  function resolvePlaceholder(prop, src) {
158
154
  if (!prop.placeholder) return null;
159
155
  if (typeof prop.placeholder === "string") return prop.placeholder;
160
- if (typeof prop.placeholder === "function") return prop.placeholder({
156
+ if (prop.loader) return prop.loader({
161
157
  src,
158
+ isPlaceholder: true,
162
159
  width: prop.width,
163
160
  height: prop.height
164
161
  });
@@ -169,11 +166,9 @@ function resolvePlaceholder(prop, src) {
169
166
  //#region src/react/prop-asserts.ts
170
167
  function assertProps(prop) {
171
168
  try {
172
- if (prop.asap !== void 0) console.warn("The `asap` prop is deprecated and will be removed in a future version. Please use `priority` instead.");
173
169
  assertLoadingProp(prop);
174
170
  assertDecodingProp(prop);
175
171
  assertFetchPriorityProp(prop);
176
- assertBreakpointsProp(prop);
177
172
  assertFillProp(prop);
178
173
  assertDimensionsProp(prop);
179
174
  } catch (err) {
@@ -185,17 +180,13 @@ function assert(assertion, message) {
185
180
  if (assertion()) throw new Error(message || void 0);
186
181
  }
187
182
  function assertLoadingProp(prop) {
188
- assert(() => prop.loading && (prop.priority || prop.asap), `Do not use \`loading\` on a priority image — priority images are always eagerly loaded.`);
183
+ assert(() => prop.loading && prop.priority, `Do not use \`loading\` on a priority image — priority images are always eagerly loaded.`);
189
184
  }
190
185
  function assertDecodingProp(prop) {
191
- assert(() => prop.decoding && (prop.priority || prop.asap), `Do not use \`decoding\` on a priority image — priority images always use async decoding.`);
186
+ assert(() => prop.decoding && prop.priority, `Do not use \`decoding\` on a priority image — priority images always use async decoding.`);
192
187
  }
193
188
  function assertFetchPriorityProp(prop) {
194
- assert(() => prop.fetchPriority && (prop.priority || prop.asap), `Do not use \`fetchPriority\` on a priority image — priority images always use high fetch priority.`);
195
- }
196
- function assertBreakpointsProp(prop) {
197
- assert(() => prop.breakpoints && typeof prop.src === "object", `Do not use \`breakpoints\` when \`src\` is an imported image — the image's built-in srcSets are used instead.`);
198
- assert(() => prop.breakpoints && typeof prop.src === "string" && !prop.loader, `Do not use \`breakpoints\` without a \`loader\` — breakpoints require a loader to generate srcSet entries.`);
189
+ assert(() => prop.fetchPriority && prop.priority, `Do not use \`fetchPriority\` on a priority image — priority images always use high fetch priority.`);
199
190
  }
200
191
  function assertFillProp(prop) {
201
192
  assert(() => prop.fill && (prop.width !== void 0 || prop.height !== void 0), `Do not use \`width\` or \`height\` with \`fill\` — fill mode makes the image fill its container.`);
@@ -220,8 +211,8 @@ const ImageContext = createContext({
220
211
  1200,
221
212
  1920
222
213
  ],
223
- loading: "lazy",
224
- loader: null
214
+ loader: null,
215
+ placeholder: true
225
216
  });
226
217
  function useImageContext() {
227
218
  return useContext(ImageContext);
@@ -268,7 +259,7 @@ function Image(props) {
268
259
  ...fillStyles,
269
260
  ...props.style
270
261
  };
271
- if (preload && options.asap) preload(options.src, {
262
+ if (preload && options.priority) preload(options.src, {
272
263
  as: "image",
273
264
  fetchPriority: "high"
274
265
  });
@@ -289,373 +280,15 @@ function Image(props) {
289
280
  }
290
281
 
291
282
  //#endregion
292
- //#region src/react/loaders/loader-utils.ts
293
- function resolveOption(key, value, separator) {
294
- return `${key}${separator}${value}`;
295
- }
296
- function resolveDeprecatedParams(source, separator) {
297
- const params = [];
298
- for (const key of Object.keys(params)) {
299
- const value = source[key];
300
- if (value !== void 0) params.push(resolveOption(key, value, separator));
301
- }
302
- return params;
303
- }
304
-
305
- //#endregion
306
- //#region src/react/loaders/imgproxy/create-imgproxy-url.ts
307
- const stringifyOptions = (opCode, values) => {
308
- return [opCode, ...values.map((v) => v == null ? "" : encodeURIComponent(v))].join(":").replace(/:+$/, "");
309
- };
310
- const resolveObjectParam = (key, source) => {
311
- if (source === void 0) return;
312
- if (key === "size") {
313
- const tSource = source;
314
- if (!tSource) return;
315
- return stringifyOptions(key, [
316
- tSource.width,
317
- tSource.height,
318
- tSource.enlarge,
319
- tSource.extend
320
- ]);
321
- }
322
- if (key === "resize") {
323
- const tSource = source;
324
- if (!tSource) return;
325
- return stringifyOptions(key, [
326
- tSource.resizing_type,
327
- tSource.width,
328
- tSource.height,
329
- tSource.enlarge,
330
- tSource.extend
331
- ]);
332
- }
333
- if (key === "extend") {
334
- const tSource = source;
335
- if (!tSource) return;
336
- if (typeof tSource === "boolean") return stringifyOptions(key, [tSource]);
337
- return stringifyOptions(key, [tSource.extend, tSource.gravity]);
338
- }
339
- if (key === "gravity") {
340
- const tSource = source;
341
- if (!tSource) return;
342
- return stringifyOptions(key, [
343
- tSource.type,
344
- tSource.x_offset,
345
- tSource.y_offset
346
- ]);
347
- }
348
- if (key === "crop") {
349
- const tSource = source;
350
- if (!tSource) return;
351
- return stringifyOptions(key, [
352
- tSource.width,
353
- tSource.height,
354
- tSource.gravity
355
- ]);
356
- }
357
- if (key === "trim") {
358
- const tSource = source;
359
- if (!tSource) return;
360
- return stringifyOptions(key, [
361
- tSource.threshold,
362
- tSource.color,
363
- tSource.equal_hor,
364
- tSource.equal_ver
365
- ]);
366
- }
367
- if (key === "padding") {
368
- const tSource = source;
369
- if (!tSource) return;
370
- return stringifyOptions(key, [
371
- tSource.top,
372
- tSource.right,
373
- tSource.bottom,
374
- tSource.left
375
- ]);
376
- }
377
- if (key === "background") {
378
- const tSource = source;
379
- if (!tSource) return;
380
- if (typeof tSource === "string") return stringifyOptions(key, [tSource]);
381
- return stringifyOptions(key, [
382
- tSource.r,
383
- tSource.g,
384
- tSource.b
385
- ]);
386
- }
387
- if (key === "adjust") {
388
- const tSource = source;
389
- if (!tSource) return;
390
- return stringifyOptions(key, [
391
- tSource.brightness,
392
- tSource.contrast,
393
- tSource.saturation
394
- ]);
395
- }
396
- if (key === "blur_detections") {
397
- const tSource = source;
398
- if (!tSource) return;
399
- return stringifyOptions(key, [tSource.sigma, ...tSource.class_names]);
400
- }
401
- if (key === "draw_detections") {
402
- const tSource = source;
403
- if (!tSource) return;
404
- return stringifyOptions(key, [tSource.draw, ...tSource.class_names]);
405
- }
406
- if (key === "watermark") {
407
- const tSource = source;
408
- if (!tSource) return;
409
- return stringifyOptions(key, [
410
- tSource.opacity,
411
- tSource.position,
412
- tSource.x_offset,
413
- tSource.y_offset,
414
- tSource.scale
415
- ]);
416
- }
417
- if (key === "watermark_size") {
418
- const tSource = source;
419
- if (!tSource) return;
420
- return stringifyOptions(key, [tSource.width, tSource.height]);
421
- }
422
- if (key === "unsharpening") {
423
- const tSource = source;
424
- if (!tSource) return;
425
- return stringifyOptions(key, [
426
- tSource.mode,
427
- tSource.weight,
428
- tSource.dividor
429
- ]);
430
- }
431
- if (key === "autoquality") {
432
- const tSource = source;
433
- if (!tSource) return;
434
- return stringifyOptions(key, [
435
- tSource.method,
436
- tSource.target,
437
- tSource.min_quality,
438
- tSource.max_quality,
439
- tSource.allowed_error
440
- ]);
441
- }
442
- if (key === "jpeg_options") {
443
- const tSource = source;
444
- if (!tSource) return;
445
- return stringifyOptions(key, [
446
- tSource.progressive,
447
- tSource.no_subsample,
448
- tSource.trellis_quant,
449
- tSource.overshoot_deringing,
450
- tSource.optimize_scans,
451
- tSource.quant_table
452
- ]);
453
- }
454
- if (key === "png_options") {
455
- const tSource = source;
456
- if (!tSource) return;
457
- return stringifyOptions(key, [
458
- tSource.interlaced,
459
- tSource.quantize,
460
- tSource.quantization_colors
461
- ]);
462
- }
463
- if (key === "zoom") {
464
- const tSource = source;
465
- if (!tSource) return;
466
- if (typeof tSource === "number") return stringifyOptions(key, [tSource]);
467
- return stringifyOptions(key, [tSource.x, tSource.y]);
468
- }
469
- };
470
- const resolveTransforms = (transforms) => {
471
- if (!transforms) return "";
472
- const params = [];
473
- for (const key of Object.keys(transforms)) {
474
- const value = transforms[key];
475
- const keyCast = key;
476
- if (value === void 0) continue;
477
- if (typeof value === "object") {
478
- const objectParams = resolveObjectParam(keyCast, value);
479
- if (objectParams) params.push(objectParams);
480
- } else params.push(stringifyOptions(key, [value]));
481
- }
482
- return params;
483
- };
484
- function createImgproxyUrl(path, transforms, imageOptions) {
485
- if (!path) throw new Error("Path must be provided");
486
- const params = [];
487
- if (imageOptions.width) params.push(stringifyOptions("width", [imageOptions.width]));
488
- if (imageOptions.height) params.push(stringifyOptions("height", [imageOptions.height]));
489
- params.push(...resolveTransforms(transforms));
490
- if (params) params.push(...resolveDeprecatedParams(params, ":"));
491
- return `${path}/${params.join("/")}/plain/${imageOptions.src}`;
492
- }
493
-
494
- //#endregion
495
- //#region src/react/loaders/imgproxy/imgproxy-context.tsx
496
- const ImgproxyContext = createContext({
497
- transforms: { format: "webp" },
498
- placeholderTransforms: {
499
- quality: 10,
500
- format: "webp"
501
- }
502
- });
503
- function useImgproxyContext() {
504
- return useContext(ImgproxyContext);
505
- }
506
- function ImgproxyLoaderProvider({ children, ...props }) {
507
- const ctx = useImgproxyContext();
508
- return /* @__PURE__ */ jsx(ImgproxyContext.Provider, {
509
- value: {
510
- ...ctx,
511
- ...props
512
- },
513
- children
514
- });
515
- }
516
-
517
- //#endregion
518
- //#region src/react/loaders/imgproxy/use-imgproxy-loader.tsx
519
- function useImgproxyLoader(options) {
520
- const context = useImgproxyContext();
521
- const path = options?.path || context.path;
522
- const transforms = {
523
- ...context.transforms,
524
- ...options?.transforms
525
- };
526
- return (imageOptions) => createImgproxyUrl(path, transforms, imageOptions);
527
- }
528
-
529
- //#endregion
530
- //#region src/react/loaders/imgproxy/use-imgproxy-placeholder.tsx
531
- function useImgproxyPlaceholder(options) {
532
- const context = useImgproxyContext();
533
- const path = options?.path || context.path;
534
- const transforms = {
535
- ...context.placeholderTransforms,
536
- ...options?.transforms
537
- };
538
- return (imageOptions) => createImgproxyUrl(path, transforms, imageOptions);
539
- }
540
-
541
- //#endregion
542
- //#region src/react/loaders/image-loader-utils.ts
543
- function normalizeLoaderParams(params, separator) {
544
- return Object.entries(params).map(([key, value]) => `${key}${separator}${value}`);
545
- }
546
- function isAbsoluteUrl(src) {
547
- return /^https?:\/\//.test(src);
548
- }
549
- function assertPath(path) {
550
- assert(() => !path?.trim(), import.meta.env.DEV && `Path is required`);
551
- assert(() => {
552
- try {
553
- new URL(path);
554
- return !isAbsoluteUrl(path);
555
- } catch {
556
- return true;
557
- }
558
- }, import.meta.env.DEV && `Path is invalid url: ${path}`);
559
- }
560
-
561
- //#endregion
562
- //#region src/react/loaders/cloudflare-loader.tsx
563
- const CloudflareContext = createContext({
564
- path: "",
565
- placeholder: true,
566
- format: "auto",
567
- placeholderParams: { quality: "low" }
568
- });
569
- function useCloudflareContext() {
570
- return useContext(CloudflareContext);
571
- }
572
- function CloudflareLoaderProvider({ children, ...props }) {
573
- const ctx = useCloudflareContext();
574
- return /* @__PURE__ */ jsx(CloudflareContext.Provider, {
575
- value: {
576
- ...ctx,
577
- ...props
578
- },
579
- children
580
- });
581
- }
582
- function useCloudflareLoader(options) {
583
- const resolvedOptions = {
584
- ...useCloudflareContext(),
585
- ...options
586
- };
587
- assertPath(resolvedOptions.path);
588
- return (imageOptions) => {
589
- const parts = [];
590
- const format = resolvedOptions.format;
591
- if (format) parts.push(`format=${format}`);
592
- if (imageOptions.width) parts.push(`width=${imageOptions.width}`);
593
- if (imageOptions.height) parts.push(`height=${imageOptions.height}`);
594
- if (resolvedOptions.params) parts.push(...normalizeLoaderParams(resolvedOptions.params, "="));
595
- if (imageOptions.isPlaceholder) {
596
- if (resolvedOptions.placeholderParams) {
597
- const placeholderParams = normalizeLoaderParams(resolvedOptions.placeholderParams, "=");
598
- parts.push(...placeholderParams);
599
- }
600
- } else if (resolvedOptions.params) {
601
- const params = normalizeLoaderParams(resolvedOptions.params, "=");
602
- parts.push(...params);
603
- }
604
- const processingOptions = parts.join(",");
605
- return `${resolvedOptions.path}/cdn-cgi/image/${processingOptions}/${imageOptions.src}`;
606
- };
607
- }
608
-
609
- //#endregion
610
- //#region src/react/loaders/cloudinary-loader.tsx
611
- const CloudinaryContext = createContext({
612
- path: "",
613
- placeholder: true,
614
- format: "auto",
615
- placeholderParams: {
616
- e_blur: ":1000",
617
- q: "_1"
618
- }
619
- });
620
- function useCloudinaryContext() {
621
- return useContext(CloudinaryContext);
622
- }
623
- function CloudinaryLoaderProvider({ children, ...props }) {
624
- const ctx = useCloudinaryContext();
625
- return /* @__PURE__ */ jsx(CloudinaryContext.Provider, {
626
- value: {
627
- ...ctx,
283
+ //#region src/react/image-factory.tsx
284
+ function __imageFactory(defaultProps) {
285
+ return (props) => {
286
+ return /* @__PURE__ */ jsx(Image, {
287
+ ...defaultProps,
628
288
  ...props
629
- },
630
- children
631
- });
632
- }
633
- function useCloudinaryLoader(options) {
634
- const resolvedOptions = {
635
- ...useCloudinaryContext(),
636
- ...options
637
- };
638
- assertPath(resolvedOptions.path);
639
- return (imageOptions) => {
640
- const parts = [];
641
- const format = `f_${resolvedOptions.format}`;
642
- parts.push(`${format}`);
643
- if (imageOptions.width) parts.push(`w_${imageOptions.width}`);
644
- if (imageOptions.height) parts.push(`h_${imageOptions.height}`);
645
- if (imageOptions.isPlaceholder) {
646
- if (resolvedOptions.placeholderParams) {
647
- const placeholderParams = normalizeLoaderParams(resolvedOptions.placeholderParams, "");
648
- parts.push(...placeholderParams);
649
- }
650
- } else if (resolvedOptions.params) {
651
- const params = normalizeLoaderParams(resolvedOptions.params, "");
652
- parts.push(...params);
653
- }
654
- let src = imageOptions.src;
655
- if (src.startsWith("/")) src = src.slice(1);
656
- return `${resolvedOptions.path.endsWith("/") ? resolvedOptions.path.slice(0, -1) : resolvedOptions.path}/image/upload/${parts.join(",")}/${src}`;
289
+ });
657
290
  };
658
291
  }
659
292
 
660
293
  //#endregion
661
- export { CloudflareLoaderProvider, CloudinaryLoaderProvider, Image, ImageProvider, ImgproxyLoaderProvider, useCloudflareContext, useCloudflareLoader, useCloudinaryContext, useCloudinaryLoader, useImageContext, useImgLoaded, useImgproxyContext, useImgproxyLoader, useImgproxyPlaceholder };
294
+ export { Image, ImageProvider, __imageFactory, useImageContext, useImgLoaded };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lonik/oh-image",
3
3
  "type": "module",
4
- "version": "2.1.2",
4
+ "version": "2.2.0",
5
5
  "description": "A React component library for optimized image handling.",
6
6
  "author": "Luka Onikadze <lukonik@gmail.com>",
7
7
  "license": "MIT",
@@ -24,6 +24,18 @@
24
24
  "types": "./dist/react.d.ts",
25
25
  "default": "./dist/react.js"
26
26
  },
27
+ "./cloudflare": {
28
+ "types": "./dist/cloudflare.d.ts",
29
+ "default": "./dist/cloudflare.js"
30
+ },
31
+ "./cloudinary": {
32
+ "types": "./dist/cloudinary.d.ts",
33
+ "default": "./dist/cloudinary.js"
34
+ },
35
+ "./imgproxy": {
36
+ "types": "./dist/imgproxy.d.ts",
37
+ "default": "./dist/imgproxy.js"
38
+ },
27
39
  "./client": {
28
40
  "types": "./dist/client.d.ts"
29
41
  }
@@ -55,6 +67,7 @@
55
67
  "devDependencies": {
56
68
  "@commitlint/config-conventional": "^20.4.1",
57
69
  "@eslint/js": "^9.39.2",
70
+ "@tailwindcss/vite": "^4.1.18",
58
71
  "@tanstack/react-router": "^1.160.0",
59
72
  "@tanstack/router-plugin": "^1.160.0",
60
73
  "@testing-library/jest-dom": "^6.9.1",
@@ -74,16 +87,17 @@
74
87
  "eslint-plugin-react-refresh": "^0.5.0",
75
88
  "globals": "^17.3.0",
76
89
  "happy-dom": "^20.6.0",
90
+ "json-edit-react": "^1.29.0",
77
91
  "memfs": "^4.56.10",
78
92
  "supertest": "^7.2.2",
93
+ "tailwindcss": "^4.1.18",
79
94
  "tsdown": "^0.18.1",
80
95
  "typescript": "^5.9.3",
81
96
  "typescript-eslint": "^8.54.0",
82
97
  "vite": "^7.3.0",
83
- "vitest": "^4.0.16",
84
- "tailwindcss": "^4.1.18",
85
- "@tailwindcss/vite": "^4.1.18",
86
- "json-edit-react": "^1.29.0"
98
+ "vite-plugin-inspect": "^11.3.3",
99
+ "vite-tsconfig-paths": "^6.1.1",
100
+ "vitest": "^4.0.16"
87
101
  },
88
102
  "dependencies": {
89
103
  "p-limit": "^7.3.0",