@teamnovu/nuxt-image 0.5.5 → 1.0.0-beta.1

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 (91) hide show
  1. package/README.md +229 -24
  2. package/dist/module.d.mts +70 -0
  3. package/dist/module.json +12 -0
  4. package/dist/module.mjs +67 -0
  5. package/dist/runtime/components/NovuBunnyImage.d.vue.ts +11 -0
  6. package/dist/runtime/components/NovuBunnyImage.vue +67 -0
  7. package/dist/runtime/components/NovuBunnyImage.vue.d.ts +11 -0
  8. package/dist/runtime/components/NovuCloudinaryImage.d.vue.ts +19 -0
  9. package/dist/runtime/components/NovuCloudinaryImage.vue +79 -0
  10. package/dist/runtime/components/NovuCloudinaryImage.vue.d.ts +19 -0
  11. package/dist/runtime/components/NovuImage.d.vue.ts +28 -0
  12. package/dist/runtime/components/NovuImage.vue +90 -0
  13. package/dist/runtime/components/NovuImage.vue.d.ts +28 -0
  14. package/dist/runtime/components/NovuImgproxyImage.d.vue.ts +12 -0
  15. package/dist/runtime/components/NovuImgproxyImage.vue +68 -0
  16. package/dist/runtime/components/NovuImgproxyImage.vue.d.ts +12 -0
  17. package/dist/runtime/components/NovuStatamicImage.d.vue.ts +17 -0
  18. package/dist/runtime/components/NovuStatamicImage.vue +87 -0
  19. package/dist/runtime/components/NovuStatamicImage.vue.d.ts +17 -0
  20. package/dist/runtime/composables/useResponsiveImage.d.ts +42 -0
  21. package/dist/runtime/composables/useResponsiveImage.js +115 -0
  22. package/dist/runtime/providers/imgproxy.d.ts +68 -0
  23. package/dist/runtime/providers/imgproxy.js +161 -0
  24. package/dist/runtime/types.d.ts +56 -0
  25. package/dist/runtime/types.js +0 -0
  26. package/dist/runtime/utils/focal.d.ts +11 -0
  27. package/dist/runtime/utils/focal.js +33 -0
  28. package/dist/runtime/utils/i18n.d.ts +1 -0
  29. package/dist/runtime/utils/i18n.js +10 -0
  30. package/dist/runtime/utils/numbers.d.ts +1 -0
  31. package/dist/runtime/utils/numbers.js +6 -0
  32. package/dist/runtime/utils/providerModifiers.d.ts +4 -0
  33. package/dist/runtime/utils/providerModifiers.js +36 -0
  34. package/dist/runtime/utils/screens.d.ts +5 -0
  35. package/dist/runtime/utils/screens.js +19 -0
  36. package/dist/runtime/utils/statamic.d.ts +3 -0
  37. package/dist/runtime/utils/statamic.js +143 -0
  38. package/dist/types.d.mts +3 -0
  39. package/package.json +65 -68
  40. package/CHANGELOG.md +0 -373
  41. package/LICENSE +0 -21
  42. package/dist/module.js +0 -482
  43. package/dist/runtime/components/image.mixin.d.ts +0 -46
  44. package/dist/runtime/components/image.mixin.js +0 -58
  45. package/dist/runtime/components/nuxt-img.vue +0 -49
  46. package/dist/runtime/components/nuxt-img.vue.d.ts +0 -12
  47. package/dist/runtime/components/nuxt-picture.vue +0 -86
  48. package/dist/runtime/components/nuxt-picture.vue.d.ts +0 -15
  49. package/dist/runtime/image.d.ts +0 -2
  50. package/dist/runtime/image.js +0 -194
  51. package/dist/runtime/index.d.ts +0 -2
  52. package/dist/runtime/index.js +0 -2
  53. package/dist/runtime/ipx.d.ts +0 -3
  54. package/dist/runtime/ipx.js +0 -3
  55. package/dist/runtime/plugin.d.ts +0 -1
  56. package/dist/runtime/plugin.js +0 -31
  57. package/dist/runtime/providers/cloudinary.d.ts +0 -2
  58. package/dist/runtime/providers/cloudinary.js +0 -96
  59. package/dist/runtime/providers/fastly.d.ts +0 -2
  60. package/dist/runtime/providers/fastly.js +0 -21
  61. package/dist/runtime/providers/glide.d.ts +0 -2
  62. package/dist/runtime/providers/glide.js +0 -49
  63. package/dist/runtime/providers/imagekit.d.ts +0 -2
  64. package/dist/runtime/providers/imagekit.js +0 -172
  65. package/dist/runtime/providers/imgix.d.ts +0 -3
  66. package/dist/runtime/providers/imgix.js +0 -153
  67. package/dist/runtime/providers/ipx.d.ts +0 -4
  68. package/dist/runtime/providers/ipx.js +0 -28
  69. package/dist/runtime/providers/netlify.d.ts +0 -3
  70. package/dist/runtime/providers/netlify.js +0 -40
  71. package/dist/runtime/providers/prismic.d.ts +0 -2
  72. package/dist/runtime/providers/prismic.js +0 -10
  73. package/dist/runtime/providers/sanity.d.ts +0 -2
  74. package/dist/runtime/providers/sanity.js +0 -87
  75. package/dist/runtime/providers/static.d.ts +0 -3
  76. package/dist/runtime/providers/static.js +0 -6
  77. package/dist/runtime/providers/storyblok.d.ts +0 -2
  78. package/dist/runtime/providers/storyblok.js +0 -27
  79. package/dist/runtime/providers/twicpics.d.ts +0 -2
  80. package/dist/runtime/providers/twicpics.js +0 -58
  81. package/dist/runtime/providers/vercel.d.ts +0 -3
  82. package/dist/runtime/providers/vercel.js +0 -28
  83. package/dist/runtime/utils/index.d.ts +0 -17
  84. package/dist/runtime/utils/index.js +0 -72
  85. package/dist/runtime/utils/meta.d.ts +0 -2
  86. package/dist/runtime/utils/meta.js +0 -67
  87. package/dist/runtime/utils/static-map.d.ts +0 -2
  88. package/dist/runtime/utils/static-map.js +0 -20
  89. package/dist/types.d.ts +0 -172
  90. package/vetur/attributes.json +0 -32
  91. package/vetur/tags.json +0 -32
@@ -0,0 +1,161 @@
1
+ import { joinURL } from "ufo";
2
+ import { hmac } from "@noble/hashes/hmac.js";
3
+ import { sha256 } from "@noble/hashes/sha2.js";
4
+ import { defu } from "defu";
5
+ const KEY_MAP = {
6
+ resize: "rs",
7
+ size: "s",
8
+ resizingType: "rt",
9
+ width: "w",
10
+ height: "h",
11
+ minWidth: "mw",
12
+ minHeight: "mh",
13
+ zoom: "z",
14
+ dpr: "dpr",
15
+ enlarge: "el",
16
+ extend: "ex",
17
+ extendAspectRatio: "exar",
18
+ gravity: "g",
19
+ crop: "c",
20
+ autoRotate: "ar",
21
+ rotate: "rot",
22
+ background: "bg",
23
+ blur: "bl",
24
+ sharpen: "sh",
25
+ pixelate: "pix",
26
+ stripMetadata: "sm",
27
+ keepCopyright: "kcr",
28
+ stripColorProfile: "scp",
29
+ enforceThumbnail: "eth",
30
+ quality: "q",
31
+ maxBytes: "mb",
32
+ format: "f",
33
+ raw: "raw",
34
+ cachebuster: "cb",
35
+ expires: "exp",
36
+ filename: "fn",
37
+ returnAttachment: "att",
38
+ preset: "pr",
39
+ maxSrcResolution: "msr",
40
+ maxSrcFileSize: "msfs",
41
+ maxAnimationFrames: "maf",
42
+ maxAnimationFrameResolution: "mafr",
43
+ maxResultDimension: "mrd"
44
+ };
45
+ function booleanMap(value) {
46
+ if (typeof value === "boolean") return value ? 1 : 0;
47
+ if (value === 1 || value === "true" || value === "t") return 1;
48
+ return 0;
49
+ }
50
+ function cropMap(value) {
51
+ if (typeof value === "string") return value;
52
+ if (value && typeof value === "object") {
53
+ return `${value.width}:${value.height}${value.gravity ? `:${value.gravity}` : ""}`;
54
+ }
55
+ throw new TypeError("Wrong crop format");
56
+ }
57
+ function rotateMap(value) {
58
+ if (typeof value !== "number" || value < 0 || value > 359) {
59
+ throw new TypeError("Wrong rotate format - must be a number between 0 and 359");
60
+ }
61
+ return value - value % 90;
62
+ }
63
+ const VALUE_MAP = {
64
+ crop: (v) => cropMap(v),
65
+ enlarge: booleanMap,
66
+ extend: booleanMap,
67
+ autoRotate: booleanMap,
68
+ stripMetadata: booleanMap,
69
+ keepCopyright: booleanMap,
70
+ stripColorProfile: booleanMap,
71
+ enforceThumbnail: booleanMap,
72
+ raw: booleanMap,
73
+ returnAttachment: booleanMap,
74
+ rotate: (v) => rotateMap(v)
75
+ };
76
+ function generateOperations(modifiers) {
77
+ const ops = [];
78
+ for (const key in modifiers) {
79
+ const value = modifiers[key];
80
+ if (value === void 0 || value === null || value === "") continue;
81
+ const mappedKey = KEY_MAP[key] ?? key;
82
+ const mappedValue = VALUE_MAP[key] ? VALUE_MAP[key](value) : value;
83
+ ops.push(`${mappedKey}:${mappedValue}`);
84
+ }
85
+ return ops.join("/");
86
+ }
87
+ function hexToBytes(hex, label = "signing key/salt") {
88
+ if (hex.length === 0 || hex.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(hex)) {
89
+ throw new Error(`Invalid hex string for ${label}: must be non-empty, even-length hex`);
90
+ }
91
+ const bytes = new Uint8Array(hex.length / 2);
92
+ for (let i = 0; i < hex.length; i += 2) {
93
+ bytes[i / 2] = Number.parseInt(hex.slice(i, i + 2), 16);
94
+ }
95
+ return bytes;
96
+ }
97
+ function urlSafeBase64(input) {
98
+ const bytes = typeof input === "string" ? new TextEncoder().encode(input) : input;
99
+ let binary = "";
100
+ for (let i = 0; i < bytes.length; i++) {
101
+ binary += String.fromCharCode(bytes[i]);
102
+ }
103
+ return btoa(binary).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
104
+ }
105
+ function sign(salt, target, secret) {
106
+ const mac = hmac.create(sha256, hexToBytes(secret, "signing key"));
107
+ mac.update(hexToBytes(salt, "signing salt"));
108
+ mac.update(new TextEncoder().encode(target));
109
+ return urlSafeBase64(mac.digest());
110
+ }
111
+ const defaultModifiers = {
112
+ resizingType: "auto",
113
+ gravity: "ce",
114
+ format: "webp"
115
+ };
116
+ function resolveModifiers(modifiers) {
117
+ const resolved = { ...modifiers };
118
+ if (resolved.fit) {
119
+ const hasW = typeof resolved.width === "number" && resolved.width > 0;
120
+ const hasH = typeof resolved.height === "number" && resolved.height > 0;
121
+ const hasBoth = hasW && hasH;
122
+ switch (resolved.fit) {
123
+ case "cover":
124
+ resolved.resizingType = hasBoth ? "fill" : "fit";
125
+ break;
126
+ case "contain":
127
+ resolved.resizingType = "fit";
128
+ resolved.extend = hasBoth;
129
+ break;
130
+ case "fill":
131
+ resolved.resizingType = hasBoth ? "force" : "fit";
132
+ break;
133
+ case "inside":
134
+ resolved.resizingType = "fit";
135
+ break;
136
+ case "outside":
137
+ resolved.resizingType = hasBoth ? "fill" : "fit";
138
+ break;
139
+ }
140
+ delete resolved.fit;
141
+ }
142
+ return resolved;
143
+ }
144
+ export const getImage = (src, ctx) => {
145
+ const baseURL = ctx?.baseURL ?? "";
146
+ const key = ctx?.key ?? "";
147
+ const salt = ctx?.salt ?? "";
148
+ const modifiers = ctx?.modifiers ?? {};
149
+ if (!key || !salt) {
150
+ throw new Error("[imgproxy] `key` and `salt` are required - configure them in `image.imgproxy`.");
151
+ }
152
+ const merged = resolveModifiers(defu(modifiers, defaultModifiers));
153
+ const encodedUrl = urlSafeBase64(src);
154
+ const operations = generateOperations(merged);
155
+ const path = joinURL("/", operations, encodedUrl);
156
+ const signature = sign(salt, path, key);
157
+ return {
158
+ url: joinURL(baseURL, signature, path)
159
+ };
160
+ };
161
+ export default () => ({ getImage });
@@ -0,0 +1,56 @@
1
+ import type { FocalPoint } from './utils/focal.js';
2
+ export type { FocalPoint };
3
+ export interface StatamicPlaceholder {
4
+ uri?: string | null;
5
+ type?: string | null;
6
+ hash?: string | null;
7
+ }
8
+ /**
9
+ * Statamic asset shape used by `<NovuStatamicImage>` and `resolveStatamicAsset`.
10
+ *
11
+ * Maps to a Statamic image asset with focal-point and per-locale alt text fields.
12
+ * Per-locale alt fields are addressed as `alt_${locale}` (e.g. `alt_de`, `alt_fr`).
13
+ */
14
+ export interface StatamicAsset {
15
+ permalink?: string;
16
+ url?: string;
17
+ alt?: string;
18
+ width?: number;
19
+ height?: number;
20
+ /** Statamic native focal-point format `"x-y"` where x and y are 0-100. */
21
+ focus?: string;
22
+ /** CSS-friendly focal-point format `"x% y%"` (Statamic's `focus_css` augmentation). */
23
+ focus_css?: string;
24
+ aspectRatio?: number;
25
+ placeholder?: string | StatamicPlaceholder | null;
26
+ lqip?: string | StatamicPlaceholder | null;
27
+ thumbhash?: string | StatamicPlaceholder | null;
28
+ /** Per-locale alt fields like `alt_de`, `alt_fr`. */
29
+ [key: `alt_${string}`]: string | undefined;
30
+ [key: string]: unknown;
31
+ }
32
+ export interface ResolvedStatamicAsset {
33
+ src: string | undefined;
34
+ placeholderSrc: string | undefined;
35
+ alt: string | undefined;
36
+ focal: FocalPoint | undefined;
37
+ width: number | undefined;
38
+ height: number | undefined;
39
+ sourceWidth: number | undefined;
40
+ sourceHeight: number | undefined;
41
+ aspectRatio: number | undefined;
42
+ }
43
+ export interface ResolveStatamicAssetProps {
44
+ alt?: string;
45
+ fallbackAlt?: string;
46
+ focal?: FocalPoint;
47
+ width?: number;
48
+ height?: number;
49
+ aspectRatio?: number;
50
+ placeholderField?: string | string[];
51
+ }
52
+ export interface ResolveStatamicAssetOptions {
53
+ locale?: string;
54
+ provider?: string;
55
+ statamicPlaceholderFields?: string[];
56
+ }
File without changes
@@ -0,0 +1,11 @@
1
+ export type FocalPoint = 'auto' | readonly [number, number];
2
+ export interface FocalContext {
3
+ /** Original (intrinsic) image width in pixels - required to normalize for imgproxy. */
4
+ width?: number;
5
+ /** Original (intrinsic) image height in pixels - required to normalize for imgproxy. */
6
+ height?: number;
7
+ }
8
+ export type FocalMapper = (focal: FocalPoint, ctx: FocalContext) => Record<string, unknown>;
9
+ export declare const focalMappers: Record<string, FocalMapper>;
10
+ export declare function defaultFocalForProvider(provider: string | undefined): FocalPoint | undefined;
11
+ export declare function applyFocal(provider: string | undefined, focal: FocalPoint | undefined, ctx: FocalContext): Record<string, unknown>;
@@ -0,0 +1,33 @@
1
+ const cloudinary = (focal) => {
2
+ if (focal === "auto") return { gravity: "auto", fit: "fill" };
3
+ return { x: focal[0], y: focal[1], gravity: "xy_center", fit: "fill" };
4
+ };
5
+ const bunny = (focal) => {
6
+ if (focal === "auto") return {};
7
+ return {};
8
+ };
9
+ const imgproxy = (focal, ctx) => {
10
+ if (focal === "auto") return { gravity: "sm" };
11
+ if (!ctx.width || !ctx.height) return {};
12
+ const nx = clamp01(focal[0] / ctx.width);
13
+ const ny = clamp01(focal[1] / ctx.height);
14
+ return { gravity: `fp:${nx}:${ny}` };
15
+ };
16
+ function clamp01(value) {
17
+ return Math.max(0, Math.min(1, Number.isFinite(value) ? value : 0.5));
18
+ }
19
+ export const focalMappers = {
20
+ cloudinary,
21
+ bunny,
22
+ imgproxy
23
+ };
24
+ const AUTO_FOCAL_PROVIDERS = /* @__PURE__ */ new Set(["cloudinary", "imgproxy"]);
25
+ export function defaultFocalForProvider(provider) {
26
+ return provider && AUTO_FOCAL_PROVIDERS.has(provider) ? "auto" : void 0;
27
+ }
28
+ export function applyFocal(provider, focal, ctx) {
29
+ if (!focal || !provider) return {};
30
+ const mapper = focalMappers[provider];
31
+ if (!mapper) return {};
32
+ return mapper(focal, ctx);
33
+ }
@@ -0,0 +1 @@
1
+ export declare function getI18nLocale(nuxtApp: unknown): string | undefined;
@@ -0,0 +1,10 @@
1
+ import { isRef, unref } from "vue";
2
+ export function getI18nLocale(nuxtApp) {
3
+ const $i18n = nuxtApp.$i18n;
4
+ if (!$i18n) return void 0;
5
+ const value = $i18n.locale;
6
+ if (isRef(value)) return unref(value);
7
+ if (value && typeof value === "object" && "value" in value) return value.value;
8
+ if (typeof value === "string") return value;
9
+ return void 0;
10
+ }
@@ -0,0 +1 @@
1
+ export declare function toFiniteNumber(value: unknown): number | undefined;
@@ -0,0 +1,6 @@
1
+ export function toFiniteNumber(value) {
2
+ if (value === void 0 || value === null || value === "") return void 0;
3
+ if (typeof value !== "number" && typeof value !== "string") return void 0;
4
+ const parsed = typeof value === "number" ? value : Number.parseFloat(value);
5
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
6
+ }
@@ -0,0 +1,4 @@
1
+ type ModifierTransform = (modifiers: Record<string, unknown>) => Record<string, unknown>;
2
+ export declare const providerModifierTransforms: Record<string, ModifierTransform>;
3
+ export declare function transformProviderModifiers(provider: string | undefined, modifiers: Record<string, unknown>): Record<string, unknown>;
4
+ export {};
@@ -0,0 +1,36 @@
1
+ import { toFiniteNumber } from "./numbers.js";
2
+ const FILL_FITS = /* @__PURE__ */ new Set(["cover", "fill", "crop"]);
3
+ const bunny = (m) => {
4
+ const wantsFaceCrop = m.faceCrop !== void 0 && m.faceCrop !== false;
5
+ const wantsCrop = m.cropGravity !== void 0 || typeof m.fit === "string" && FILL_FITS.has(m.fit);
6
+ if (!wantsFaceCrop && !wantsCrop) return m;
7
+ if (wantsFaceCrop && typeof m.faceCrop === "string") {
8
+ const { fit: _fit2, width: _w2, height: _h2, crop: _crop2, cropGravity: _cg, faceCrop: _fc2, ...rest2 } = m;
9
+ return { ...rest2, face_crop: m.faceCrop };
10
+ }
11
+ if (m.width === void 0 || m.height === void 0) {
12
+ if (wantsFaceCrop) {
13
+ const { fit: _fit2, crop: _crop2, cropGravity: _cg, faceCrop: _fc2, ...rest2 } = m;
14
+ return { ...rest2, face_crop: true };
15
+ }
16
+ return m;
17
+ }
18
+ const width = toFiniteNumber(m.width);
19
+ const height = toFiniteNumber(m.height);
20
+ if (width === void 0 || height === void 0) return m;
21
+ const { fit: _fit, width: _w, height: _h, crop: _crop, cropGravity, faceCrop: _fc, ...rest } = m;
22
+ if (wantsFaceCrop) return { ...rest, face_crop: `${width},${height}` };
23
+ return {
24
+ ...rest,
25
+ ...cropGravity !== void 0 ? { cropGravity } : {},
26
+ crop: `${width},${height}`
27
+ };
28
+ };
29
+ export const providerModifierTransforms = {
30
+ bunny
31
+ };
32
+ export function transformProviderModifiers(provider, modifiers) {
33
+ if (!provider) return modifiers;
34
+ const transform = providerModifierTransforms[provider];
35
+ return transform ? transform(modifiers) : modifiers;
36
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Parse a `screens` config into a sorted descending list of breakpoint widths
3
+ * in pixels. Accepts either plain numbers or strings with `px` suffix.
4
+ */
5
+ export declare function parseScreens(screens: Record<string, number | string>): number[];
@@ -0,0 +1,19 @@
1
+ export function parseScreens(screens) {
2
+ const widths = [];
3
+ for (const value of Object.values(screens)) {
4
+ const px = parseScreenValue(value);
5
+ if (px !== void 0) widths.push(px);
6
+ }
7
+ return widths.sort((a, b) => b - a);
8
+ }
9
+ function parseScreenValue(value) {
10
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
11
+ if (typeof value === "string") {
12
+ const match = value.trim().match(/^(\d+(?:\.\d+)?)\s*px?$/i) ?? value.trim().match(/^(\d+(?:\.\d+)?)$/);
13
+ if (match) {
14
+ const parsed = Number.parseFloat(match[1]);
15
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
16
+ }
17
+ }
18
+ return void 0;
19
+ }
@@ -0,0 +1,3 @@
1
+ import type { ResolveStatamicAssetOptions, ResolveStatamicAssetProps, ResolvedStatamicAsset, StatamicAsset } from '../types.js';
2
+ export declare const DEFAULT_STATAMIC_PLACEHOLDER_FIELDS: string[];
3
+ export declare function resolveStatamicAsset(src: string | StatamicAsset | null | undefined, props?: ResolveStatamicAssetProps, options?: ResolveStatamicAssetOptions): ResolvedStatamicAsset;
@@ -0,0 +1,143 @@
1
+ import { thumbHashToDataURL } from "thumbhash";
2
+ import { toFiniteNumber } from "./numbers.js";
3
+ export const DEFAULT_STATAMIC_PLACEHOLDER_FIELDS = ["placeholder", "lqip", "thumbhash"];
4
+ export function resolveStatamicAsset(src, props = {}, options = {}) {
5
+ const asset = normalizeAsset(src);
6
+ const dims = resolveDimensions(asset, props);
7
+ return {
8
+ src: resolveSrc(asset, options.provider),
9
+ placeholderSrc: resolvePlaceholderSrc(asset, props, options),
10
+ alt: resolveAlt(asset, props, options.locale),
11
+ focal: props.focal ?? resolveFocal(asset),
12
+ width: dims.width,
13
+ height: dims.height,
14
+ sourceWidth: toFiniteNumber(asset?.width),
15
+ sourceHeight: toFiniteNumber(asset?.height),
16
+ aspectRatio: resolveAspectRatio(asset, props, dims)
17
+ };
18
+ }
19
+ function resolveDimensions(asset, props) {
20
+ const propW = toFiniteNumber(props.width);
21
+ const propH = toFiniteNumber(props.height);
22
+ if (propW !== void 0 && propH !== void 0) {
23
+ return { width: propW, height: propH };
24
+ }
25
+ const aspect = intrinsicAspectRatio(asset, props);
26
+ if (propW !== void 0) {
27
+ const derivedH = aspect ? Math.round(propW / aspect) : asset?.height;
28
+ return { width: propW, height: toFiniteNumber(derivedH) };
29
+ }
30
+ if (propH !== void 0) {
31
+ const derivedW = aspect ? Math.round(propH * aspect) : asset?.width;
32
+ return { width: toFiniteNumber(derivedW), height: propH };
33
+ }
34
+ return { width: toFiniteNumber(asset?.width), height: toFiniteNumber(asset?.height) };
35
+ }
36
+ function intrinsicAspectRatio(asset, props) {
37
+ if (props.aspectRatio && props.aspectRatio > 0) return props.aspectRatio;
38
+ if (asset?.aspectRatio && asset.aspectRatio > 0) return asset.aspectRatio;
39
+ if (asset?.width && asset?.height && asset.width > 0 && asset.height > 0) {
40
+ return asset.width / asset.height;
41
+ }
42
+ return void 0;
43
+ }
44
+ function normalizeAsset(src) {
45
+ if (src == null) return void 0;
46
+ if (typeof src === "string") return { permalink: src };
47
+ return src;
48
+ }
49
+ const CLOUDINARY_PATH_RE = /^\/[^/]+\/image\/(?:upload|fetch|private|authenticated)\/(?:s--[^/]+--\/)?/i;
50
+ function resolveSrc(asset, provider) {
51
+ const permalink = asset?.permalink ?? asset?.url;
52
+ if (!permalink) return void 0;
53
+ if (provider === "imgproxy" && /^https?:\/\//i.test(permalink)) return permalink;
54
+ let path = permalink.replace(/^https?:\/\/[^/]+/i, "");
55
+ path = path.replace(CLOUDINARY_PATH_RE, "");
56
+ return path.replace(/^\//, "");
57
+ }
58
+ function resolvePlaceholderSrc(asset, props, options) {
59
+ if (!asset) return void 0;
60
+ for (const field of resolvePlaceholderFields(props.placeholderField, options.statamicPlaceholderFields)) {
61
+ const src = resolvePlaceholderValue(asset[field]);
62
+ if (src) return src;
63
+ }
64
+ return void 0;
65
+ }
66
+ function resolvePlaceholderFields(propFields, optionFields) {
67
+ const fields = [
68
+ ...toFieldList(propFields),
69
+ ...optionFields ?? DEFAULT_STATAMIC_PLACEHOLDER_FIELDS
70
+ ];
71
+ return [...new Set(fields.map((field) => field.trim()).filter(Boolean))];
72
+ }
73
+ function toFieldList(fields) {
74
+ if (fields === void 0) return [];
75
+ return Array.isArray(fields) ? fields : [fields];
76
+ }
77
+ function resolvePlaceholderValue(value) {
78
+ if (typeof value === "string") return value || void 0;
79
+ if (!isPlaceholderObject(value)) return void 0;
80
+ if (typeof value.uri === "string" && value.uri) return value.uri;
81
+ const type = typeof value.type === "string" ? value.type.toLowerCase() : void 0;
82
+ if (type !== void 0 && type !== "thumbhash") return void 0;
83
+ if (typeof value.hash !== "string" || !value.hash) return void 0;
84
+ return decodeThumbHash(value.hash);
85
+ }
86
+ function isPlaceholderObject(value) {
87
+ return typeof value === "object" && value !== null;
88
+ }
89
+ function decodeThumbHash(hash) {
90
+ try {
91
+ const bytes = decodeBase64Bytes(hash);
92
+ if (!bytes.length) return void 0;
93
+ return thumbHashToDataURL(bytes);
94
+ } catch {
95
+ return void 0;
96
+ }
97
+ }
98
+ function decodeBase64Bytes(value) {
99
+ const normalized = value.trim().replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.trim().length / 4) * 4, "=");
100
+ const binary = globalThis.atob(normalized);
101
+ return Uint8Array.from(binary, (char) => char.charCodeAt(0));
102
+ }
103
+ function resolveAlt(asset, props, locale) {
104
+ if (props.alt) return props.alt;
105
+ if (asset && locale) {
106
+ const localized = asset[`alt_${locale}`];
107
+ if (localized) return localized;
108
+ }
109
+ if (asset?.alt) return asset.alt;
110
+ if (props.fallbackAlt) return props.fallbackAlt;
111
+ return void 0;
112
+ }
113
+ function resolveFocal(asset) {
114
+ if (!asset?.width || !asset?.height) return void 0;
115
+ let xRaw;
116
+ let yRaw;
117
+ if (asset.focus) {
118
+ const parts = asset.focus.split("-");
119
+ xRaw = parts[0];
120
+ yRaw = parts[1];
121
+ } else if (asset.focus_css) {
122
+ const parts = asset.focus_css.replace(/%/g, "").trim().split(/\s+/);
123
+ xRaw = parts[0];
124
+ yRaw = parts[1];
125
+ }
126
+ if (xRaw === void 0 || yRaw === void 0) return void 0;
127
+ const xPct = Number.parseFloat(xRaw);
128
+ const yPct = Number.parseFloat(yRaw);
129
+ if (!Number.isFinite(xPct) || !Number.isFinite(yPct)) return void 0;
130
+ if (xPct === 50 && yPct === 50) return void 0;
131
+ return [
132
+ Math.round(xPct * asset.width / 100),
133
+ Math.round(yPct * asset.height / 100)
134
+ ];
135
+ }
136
+ function resolveAspectRatio(asset, props, dims) {
137
+ if (props.aspectRatio && props.aspectRatio > 0) return props.aspectRatio;
138
+ if (asset?.aspectRatio && asset.aspectRatio > 0) return asset.aspectRatio;
139
+ const w = toFiniteNumber(dims.width);
140
+ const h = toFiniteNumber(dims.height);
141
+ if (w && h) return w / h;
142
+ return void 0;
143
+ }
@@ -0,0 +1,3 @@
1
+ export { default } from './module.mjs'
2
+
3
+ export { type ModuleOptions } from './module.mjs'
package/package.json CHANGED
@@ -1,80 +1,77 @@
1
1
  {
2
2
  "name": "@teamnovu/nuxt-image",
3
- "version": "0.5.5",
4
- "description": "Nuxt Image Module",
5
- "repository": "nuxt/image",
3
+ "version": "1.0.0-beta.1",
4
+ "description": "Responsive image module for Nuxt 4 with provider-agnostic focal-point handling and a Statamic adapter built on @nuxt/image.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/teamnovu/novu-nuxt-image.git"
8
+ },
6
9
  "license": "MIT",
7
- "sideEffects": false,
8
- "main": "dist/module.js",
9
- "types": "dist/types.d.ts",
10
+ "type": "module",
11
+ "keywords": [
12
+ "nuxt",
13
+ "nuxt-module",
14
+ "image",
15
+ "responsive-images",
16
+ "cloudinary",
17
+ "bunny",
18
+ "imgproxy",
19
+ "statamic"
20
+ ],
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/types.d.mts",
24
+ "import": "./dist/module.mjs"
25
+ }
26
+ },
27
+ "main": "./dist/module.mjs",
28
+ "typesVersions": {
29
+ "*": {
30
+ ".": [
31
+ "./dist/types.d.mts"
32
+ ]
33
+ }
34
+ },
10
35
  "files": [
11
- "dist",
12
- "vetur"
36
+ "dist"
13
37
  ],
14
38
  "scripts": {
15
- "build": "siroc build && mkdist --src src/runtime --dist dist/runtime -d --ext js",
16
- "dev": "yarn nuxt playground",
17
- "docs:build": "cd docs && nuxt generate",
18
- "docs:dev": "yarn nuxt dev docs",
19
- "lint": "eslint --ext .ts --ext .vue .",
20
- "prepublishOnly": "yarn build",
21
- "release": "standard-version && git push --follow-tags && npm publish",
22
- "test": "yarn lint && yarn jest --forceExit",
23
- "test:e2e": "jest test/e2e --forceExit",
24
- "test:unit": "jest test/unit --forceExit"
39
+ "prepack": "nuxt-module-build build",
40
+ "dev": "npm run dev:prepare && nuxt dev playground",
41
+ "dev:build": "nuxt build playground",
42
+ "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground",
43
+ "release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
44
+ "lint": "eslint .",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest watch",
47
+ "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
25
48
  },
26
49
  "dependencies": {
27
- "consola": "^2.15.3",
28
- "defu": "^5.0.0",
29
- "fs-extra": "^10.0.0",
30
- "hasha": "^5.2.2",
31
- "image-meta": "^0.0.1",
32
- "ipx": "^0.7.1",
33
- "is-https": "^4.0.0",
34
- "lru-cache": "^6.0.0",
35
- "node-fetch": "^2.6.1",
36
- "p-limit": "^3.1.0",
37
- "rc9": "^1.2.0",
38
- "requrl": "^3.0.2",
39
- "semver": "^7.3.5",
40
- "ufo": "^0.7.7",
41
- "upath": "^2.0.1"
50
+ "@noble/hashes": "^2.2.0",
51
+ "@nuxt/image": "^2.0.0",
52
+ "@nuxt/kit": "^4.4.5",
53
+ "@vueuse/core": "^14.3.0",
54
+ "defu": "^6.1.4",
55
+ "thumbhash": "^0.1.1",
56
+ "ufo": "^1.6.4"
42
57
  },
43
- "devDependencies": {
44
- "@babel/preset-env": "latest",
45
- "@babel/preset-typescript": "latest",
46
- "@cyrilf/vue-dat-gui": "latest",
47
- "@nuxt/test-utils": "latest",
48
- "@nuxt/types": "latest",
49
- "@nuxt/typescript-build": "latest",
50
- "@nuxt/typescript-runtime": "latest",
51
- "@nuxtjs/eslint-config-typescript": "latest",
52
- "@types/fs-extra": "latest",
53
- "@types/jest": "latest",
54
- "@types/lru-cache": "latest",
55
- "@types/node-fetch": "latest",
56
- "@types/semver": "^7.3.6",
57
- "@vue/test-utils": "latest",
58
- "babel-core": "^7.0.0-bridge.0",
59
- "babel-eslint": "latest",
60
- "eslint": "latest",
61
- "jest": "latest",
62
- "jsdom": "latest",
63
- "jsdom-global": "latest",
64
- "mkdist": "latest",
65
- "nuxt": "^2.15.7",
66
- "playwright": "latest",
67
- "siroc": "latest",
68
- "standard-version": "latest",
69
- "ts-loader": "^8",
70
- "typescript": "latest",
71
- "vue-jest": "latest"
72
- },
73
- "publishConfig": {
74
- "access": "public"
58
+ "peerDependencies": {
59
+ "nuxt": ">=3.13.0"
75
60
  },
76
- "vetur": {
77
- "tags": "vetur/tags.json",
78
- "attributes": "vetur/attributes.json"
61
+ "devDependencies": {
62
+ "@nuxt/devtools": "^3.2.4",
63
+ "@nuxt/eslint-config": "^1.15.2",
64
+ "@nuxt/module-builder": "^1.0.2",
65
+ "@nuxt/schema": "^4.4.5",
66
+ "@nuxt/test-utils": "^4.0.3",
67
+ "@types/node": "latest",
68
+ "@vue/test-utils": "^2.4.10",
69
+ "changelogen": "^0.6.2",
70
+ "eslint": "^10.3.0",
71
+ "happy-dom": "^20.0.0",
72
+ "nuxt": "^4.4.5",
73
+ "typescript": "~6.0.3",
74
+ "vitest": "^4.1.5",
75
+ "vue-tsc": "^3.2.8"
79
76
  }
80
77
  }