@dispatchcms/react 0.0.8 → 0.0.9
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/README.md +6 -2
- package/dist/index.d.mts +16 -3
- package/dist/index.d.ts +16 -3
- package/dist/index.js +31 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +31 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +46 -10
package/README.md
CHANGED
|
@@ -127,13 +127,17 @@ export default function PostPage({ slug }) {
|
|
|
127
127
|
|
|
128
128
|
## Images
|
|
129
129
|
|
|
130
|
-
An image field is a URL until an editor
|
|
130
|
+
An image field is a URL until an editor crops it, then `{ url, crop }` with a unit rectangle (`x`, `y`, `width`, `height` in `0…1`). The original file is unchanged. Wrap the image in a sized `overflow-hidden` frame and spread `imageProps` onto the `<img>`:
|
|
131
131
|
|
|
132
132
|
```tsx
|
|
133
133
|
import { imageProps } from "@dispatchcms/react";
|
|
134
134
|
|
|
135
135
|
const photo = imageProps(entry.data.hero);
|
|
136
|
-
photo ?
|
|
136
|
+
photo ? (
|
|
137
|
+
<div className="relative h-64 w-full overflow-hidden">
|
|
138
|
+
<img src={photo.src} alt="" style={photo.style} />
|
|
139
|
+
</div>
|
|
140
|
+
) : null;
|
|
137
141
|
```
|
|
138
142
|
|
|
139
143
|
## License
|
package/dist/index.d.mts
CHANGED
|
@@ -39,12 +39,25 @@ type UseEntriesResult = {
|
|
|
39
39
|
};
|
|
40
40
|
declare function useEntries(collection: string | undefined): UseEntriesResult;
|
|
41
41
|
|
|
42
|
+
type ImageCrop = {
|
|
43
|
+
x: number;
|
|
44
|
+
y: number;
|
|
45
|
+
width: number;
|
|
46
|
+
height: number;
|
|
47
|
+
};
|
|
48
|
+
/** Styles for an <img> inside a sized overflow:hidden frame — shows only the cropped region. */
|
|
42
49
|
declare function imageProps(value: unknown): {
|
|
43
50
|
src: string;
|
|
51
|
+
crop: ImageCrop;
|
|
44
52
|
style: {
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
position: "absolute";
|
|
54
|
+
left: string;
|
|
55
|
+
top: string;
|
|
56
|
+
width: string;
|
|
57
|
+
height: string;
|
|
58
|
+
maxWidth: "none";
|
|
59
|
+
maxHeight: "none";
|
|
47
60
|
};
|
|
48
61
|
} | null;
|
|
49
62
|
|
|
50
|
-
export { DispatchProvider, type Entry, type UseEntriesResult, type UseEntryResult, imageProps, useDispatchContext, useEntries, useEntry };
|
|
63
|
+
export { DispatchProvider, type Entry, type ImageCrop, type UseEntriesResult, type UseEntryResult, imageProps, useDispatchContext, useEntries, useEntry };
|
package/dist/index.d.ts
CHANGED
|
@@ -39,12 +39,25 @@ type UseEntriesResult = {
|
|
|
39
39
|
};
|
|
40
40
|
declare function useEntries(collection: string | undefined): UseEntriesResult;
|
|
41
41
|
|
|
42
|
+
type ImageCrop = {
|
|
43
|
+
x: number;
|
|
44
|
+
y: number;
|
|
45
|
+
width: number;
|
|
46
|
+
height: number;
|
|
47
|
+
};
|
|
48
|
+
/** Styles for an <img> inside a sized overflow:hidden frame — shows only the cropped region. */
|
|
42
49
|
declare function imageProps(value: unknown): {
|
|
43
50
|
src: string;
|
|
51
|
+
crop: ImageCrop;
|
|
44
52
|
style: {
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
position: "absolute";
|
|
54
|
+
left: string;
|
|
55
|
+
top: string;
|
|
56
|
+
width: string;
|
|
57
|
+
height: string;
|
|
58
|
+
maxWidth: "none";
|
|
59
|
+
maxHeight: "none";
|
|
47
60
|
};
|
|
48
61
|
} | null;
|
|
49
62
|
|
|
50
|
-
export { DispatchProvider, type Entry, type UseEntriesResult, type UseEntryResult, imageProps, useDispatchContext, useEntries, useEntry };
|
|
63
|
+
export { DispatchProvider, type Entry, type ImageCrop, type UseEntriesResult, type UseEntryResult, imageProps, useDispatchContext, useEntries, useEntry };
|
package/dist/index.js
CHANGED
|
@@ -172,26 +172,49 @@ function useEntries(collection) {
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
// src/index.ts
|
|
175
|
+
function clampCropSide(n, fallback) {
|
|
176
|
+
return typeof n === "number" && Number.isFinite(n) ? Math.min(1, Math.max(0, n)) : fallback;
|
|
177
|
+
}
|
|
178
|
+
function readCrop(value) {
|
|
179
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
180
|
+
return { x: 0, y: 0, width: 1, height: 1 };
|
|
181
|
+
}
|
|
182
|
+
const row = value;
|
|
183
|
+
let x = clampCropSide(row.x, 0);
|
|
184
|
+
let y = clampCropSide(row.y, 0);
|
|
185
|
+
let width = clampCropSide(row.width, 1);
|
|
186
|
+
let height = clampCropSide(row.height, 1);
|
|
187
|
+
width = Math.max(0.05, Math.min(width, 1 - x));
|
|
188
|
+
height = Math.max(0.05, Math.min(height, 1 - y));
|
|
189
|
+
return { x, y, width, height };
|
|
190
|
+
}
|
|
175
191
|
function imageProps(value) {
|
|
176
192
|
let src = "";
|
|
177
|
-
let
|
|
178
|
-
let y = 0.5;
|
|
193
|
+
let crop = { x: 0, y: 0, width: 1, height: 1 };
|
|
179
194
|
if (typeof value === "string") {
|
|
180
195
|
src = value;
|
|
181
196
|
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
182
197
|
const row = value;
|
|
183
198
|
if (typeof row.url !== "string") return null;
|
|
184
199
|
src = row.url;
|
|
185
|
-
if (row.
|
|
186
|
-
const focus = row.focus;
|
|
187
|
-
if (typeof focus.x === "number" && Number.isFinite(focus.x)) x = Math.min(1, Math.max(0, focus.x));
|
|
188
|
-
if (typeof focus.y === "number" && Number.isFinite(focus.y)) y = Math.min(1, Math.max(0, focus.y));
|
|
189
|
-
}
|
|
200
|
+
if (row.crop != null) crop = readCrop(row.crop);
|
|
190
201
|
} else {
|
|
191
202
|
return null;
|
|
192
203
|
}
|
|
193
204
|
if (!src) return null;
|
|
194
|
-
return {
|
|
205
|
+
return {
|
|
206
|
+
src,
|
|
207
|
+
crop,
|
|
208
|
+
style: {
|
|
209
|
+
position: "absolute",
|
|
210
|
+
left: `${-crop.x / crop.width * 100}%`,
|
|
211
|
+
top: `${-crop.y / crop.height * 100}%`,
|
|
212
|
+
width: `${1 / crop.width * 100}%`,
|
|
213
|
+
height: `${1 / crop.height * 100}%`,
|
|
214
|
+
maxWidth: "none",
|
|
215
|
+
maxHeight: "none"
|
|
216
|
+
}
|
|
217
|
+
};
|
|
195
218
|
}
|
|
196
219
|
// Annotate the CommonJS export names for ESM import in node:
|
|
197
220
|
0 && (module.exports = {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/context.tsx","../src/config.ts","../src/hooks.ts"],"sourcesContent":["export type { Entry } from \"./types\";\nexport { DispatchProvider, useDispatchContext } from \"./context\";\nexport { useEntry, useEntries } from \"./hooks\";\nexport type { UseEntryResult, UseEntriesResult } from \"./hooks\";\n\n// ponytail: percentage object-position. Measure the image against the frame if a subject still clips.\nexport function imageProps(value: unknown): {\n src: string;\n style: { objectFit: \"cover\"; objectPosition: string };\n} | null {\n let src = \"\";\n let x = 0.5;\n let y = 0.5;\n if (typeof value === \"string\") {\n src = value;\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n const row = value as Record<string, unknown>;\n if (typeof row.url !== \"string\") return null;\n src = row.url;\n if (row.focus && typeof row.focus === \"object\" && !Array.isArray(row.focus)) {\n const focus = row.focus as Record<string, unknown>;\n if (typeof focus.x === \"number\" && Number.isFinite(focus.x)) x = Math.min(1, Math.max(0, focus.x));\n if (typeof focus.y === \"number\" && Number.isFinite(focus.y)) y = Math.min(1, Math.max(0, focus.y));\n }\n } else {\n return null;\n }\n if (!src) return null;\n return { src, style: { objectFit: \"cover\", objectPosition: `${x * 100}% ${y * 100}%` } };\n}\n","\"use client\";\n\nimport React, { createContext, useContext } from \"react\";\nimport { getApiBase } from \"./config\";\n\ntype DispatchContextValue = {\n siteKey: string | null;\n apiBase: string;\n};\n\nconst DispatchContext = createContext<DispatchContextValue | null>(null);\n\nexport function useDispatchContext(): DispatchContextValue | null {\n return useContext(DispatchContext);\n}\n\ntype DispatchProviderProps = {\n siteKey?: string | null;\n apiBase?: string;\n children?: unknown;\n};\n\nexport function DispatchProvider({\n siteKey = null,\n apiBase = getApiBase(),\n children,\n}: DispatchProviderProps) {\n const value: DispatchContextValue = {\n siteKey: siteKey?.trim() || null,\n apiBase,\n };\n\n if (!value.siteKey) {\n if (typeof console !== \"undefined\" && console.warn) {\n console.warn(\n \"[Dispatch] No siteKey provided. Pass siteKey to DispatchProvider. Hooks will return empty/error state.\"\n );\n }\n }\n\n return (\n <DispatchContext.Provider value={value}>\n {children as React.ReactNode}\n </DispatchContext.Provider>\n );\n}\n","const DEFAULT_API_BASE = \"https://dispatch-cms.vercel.app\";\n\nexport function getApiBase(): string {\n return DEFAULT_API_BASE;\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { Entry } from \"./types\";\nimport { useDispatchContext } from \"./context\";\n\nexport type UseEntryResult = {\n entry: Entry | null;\n isLoading: boolean;\n error?: string;\n};\n\nexport function useEntry(collection: string | undefined, slug: string | undefined): UseEntryResult {\n const ctx = useDispatchContext();\n const [entry, setEntry] = useState<Entry | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | undefined>(undefined);\n\n useEffect(() => {\n if (!collection?.trim() || !slug?.trim()) {\n setEntry(null);\n setIsLoading(false);\n setError(undefined);\n return;\n }\n\n if (!ctx?.siteKey) {\n setEntry(null);\n setIsLoading(false);\n setError(\"Missing site key\");\n return;\n }\n\n let cancelled = false;\n setEntry(null);\n setError(undefined);\n setIsLoading(true);\n\n const url = `${ctx.apiBase}/api/content/${encodeURIComponent(collection.trim())}/${encodeURIComponent(slug.trim())}`;\n fetch(url, {\n headers: { \"X-Site-Key\": ctx.siteKey },\n })\n .then((res) => {\n if (cancelled) return;\n if (res.status === 404) {\n setEntry(null);\n setIsLoading(false);\n return;\n }\n if (!res.ok) {\n setEntry(null);\n setIsLoading(false);\n setError(\"Failed to load\");\n return;\n }\n return res.json();\n })\n .then((data) => {\n if (cancelled || data == null) return;\n setEntry(data as Entry);\n setIsLoading(false);\n })\n .catch(() => {\n if (cancelled) return;\n setEntry(null);\n setIsLoading(false);\n setError(\"Failed to load\");\n });\n\n return () => {\n cancelled = true;\n };\n }, [collection, slug, ctx?.siteKey, ctx?.apiBase]);\n\n return { entry, isLoading, error };\n}\n\nexport type UseEntriesResult = {\n entries: Entry[];\n isLoading: boolean;\n error?: string;\n};\n\nexport function useEntries(collection: string | undefined): UseEntriesResult {\n const ctx = useDispatchContext();\n const [entries, setEntries] = useState<Entry[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | undefined>(undefined);\n\n useEffect(() => {\n if (!collection?.trim()) {\n setEntries([]);\n setIsLoading(false);\n setError(undefined);\n return;\n }\n\n if (!ctx?.siteKey) {\n setEntries([]);\n setIsLoading(false);\n setError(\"Missing site key\");\n return;\n }\n\n let cancelled = false;\n setEntries([]);\n setError(undefined);\n setIsLoading(true);\n\n const url = `${ctx.apiBase}/api/content/${encodeURIComponent(collection.trim())}`;\n fetch(url, {\n headers: { \"X-Site-Key\": ctx.siteKey },\n })\n .then((res) => {\n if (cancelled) return;\n if (!res.ok) {\n setEntries([]);\n setIsLoading(false);\n setError(\"Failed to load\");\n return;\n }\n return res.json();\n })\n .then((data) => {\n if (cancelled || data == null) return;\n setEntries(Array.isArray(data) ? data : []);\n setIsLoading(false);\n })\n .catch(() => {\n if (cancelled) return;\n setEntries([]);\n setIsLoading(false);\n setError(\"Failed to load\");\n });\n\n return () => {\n cancelled = true;\n };\n }, [collection, ctx?.siteKey, ctx?.apiBase]);\n\n return { entries, isLoading, error };\n}\n"],"mappings":"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","names":["import_react"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/context.tsx","../src/config.ts","../src/hooks.ts"],"sourcesContent":["export type { Entry } from \"./types\";\nexport { DispatchProvider, useDispatchContext } from \"./context\";\nexport { useEntry, useEntries } from \"./hooks\";\nexport type { UseEntryResult, UseEntriesResult } from \"./hooks\";\n\nexport type ImageCrop = { x: number; y: number; width: number; height: number };\n\nfunction clampCropSide(n: number, fallback: number) {\n return typeof n === \"number\" && Number.isFinite(n) ? Math.min(1, Math.max(0, n)) : fallback;\n}\n\nfunction readCrop(value: unknown): ImageCrop {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return { x: 0, y: 0, width: 1, height: 1 };\n }\n const row = value as Record<string, unknown>;\n let x = clampCropSide(row.x as number, 0);\n let y = clampCropSide(row.y as number, 0);\n let width = clampCropSide(row.width as number, 1);\n let height = clampCropSide(row.height as number, 1);\n width = Math.max(0.05, Math.min(width, 1 - x));\n height = Math.max(0.05, Math.min(height, 1 - y));\n return { x, y, width, height };\n}\n\n/** Styles for an <img> inside a sized overflow:hidden frame — shows only the cropped region. */\nexport function imageProps(value: unknown): {\n src: string;\n crop: ImageCrop;\n style: {\n position: \"absolute\";\n left: string;\n top: string;\n width: string;\n height: string;\n maxWidth: \"none\";\n maxHeight: \"none\";\n };\n} | null {\n let src = \"\";\n let crop: ImageCrop = { x: 0, y: 0, width: 1, height: 1 };\n if (typeof value === \"string\") {\n src = value;\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n const row = value as Record<string, unknown>;\n if (typeof row.url !== \"string\") return null;\n src = row.url;\n if (row.crop != null) crop = readCrop(row.crop);\n } else {\n return null;\n }\n if (!src) return null;\n return {\n src,\n crop,\n style: {\n position: \"absolute\",\n left: `${(-crop.x / crop.width) * 100}%`,\n top: `${(-crop.y / crop.height) * 100}%`,\n width: `${(1 / crop.width) * 100}%`,\n height: `${(1 / crop.height) * 100}%`,\n maxWidth: \"none\",\n maxHeight: \"none\",\n },\n };\n}\n","\"use client\";\n\nimport React, { createContext, useContext } from \"react\";\nimport { getApiBase } from \"./config\";\n\ntype DispatchContextValue = {\n siteKey: string | null;\n apiBase: string;\n};\n\nconst DispatchContext = createContext<DispatchContextValue | null>(null);\n\nexport function useDispatchContext(): DispatchContextValue | null {\n return useContext(DispatchContext);\n}\n\ntype DispatchProviderProps = {\n siteKey?: string | null;\n apiBase?: string;\n children?: unknown;\n};\n\nexport function DispatchProvider({\n siteKey = null,\n apiBase = getApiBase(),\n children,\n}: DispatchProviderProps) {\n const value: DispatchContextValue = {\n siteKey: siteKey?.trim() || null,\n apiBase,\n };\n\n if (!value.siteKey) {\n if (typeof console !== \"undefined\" && console.warn) {\n console.warn(\n \"[Dispatch] No siteKey provided. Pass siteKey to DispatchProvider. Hooks will return empty/error state.\"\n );\n }\n }\n\n return (\n <DispatchContext.Provider value={value}>\n {children as React.ReactNode}\n </DispatchContext.Provider>\n );\n}\n","const DEFAULT_API_BASE = \"https://dispatch-cms.vercel.app\";\n\nexport function getApiBase(): string {\n return DEFAULT_API_BASE;\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { Entry } from \"./types\";\nimport { useDispatchContext } from \"./context\";\n\nexport type UseEntryResult = {\n entry: Entry | null;\n isLoading: boolean;\n error?: string;\n};\n\nexport function useEntry(collection: string | undefined, slug: string | undefined): UseEntryResult {\n const ctx = useDispatchContext();\n const [entry, setEntry] = useState<Entry | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | undefined>(undefined);\n\n useEffect(() => {\n if (!collection?.trim() || !slug?.trim()) {\n setEntry(null);\n setIsLoading(false);\n setError(undefined);\n return;\n }\n\n if (!ctx?.siteKey) {\n setEntry(null);\n setIsLoading(false);\n setError(\"Missing site key\");\n return;\n }\n\n let cancelled = false;\n setEntry(null);\n setError(undefined);\n setIsLoading(true);\n\n const url = `${ctx.apiBase}/api/content/${encodeURIComponent(collection.trim())}/${encodeURIComponent(slug.trim())}`;\n fetch(url, {\n headers: { \"X-Site-Key\": ctx.siteKey },\n })\n .then((res) => {\n if (cancelled) return;\n if (res.status === 404) {\n setEntry(null);\n setIsLoading(false);\n return;\n }\n if (!res.ok) {\n setEntry(null);\n setIsLoading(false);\n setError(\"Failed to load\");\n return;\n }\n return res.json();\n })\n .then((data) => {\n if (cancelled || data == null) return;\n setEntry(data as Entry);\n setIsLoading(false);\n })\n .catch(() => {\n if (cancelled) return;\n setEntry(null);\n setIsLoading(false);\n setError(\"Failed to load\");\n });\n\n return () => {\n cancelled = true;\n };\n }, [collection, slug, ctx?.siteKey, ctx?.apiBase]);\n\n return { entry, isLoading, error };\n}\n\nexport type UseEntriesResult = {\n entries: Entry[];\n isLoading: boolean;\n error?: string;\n};\n\nexport function useEntries(collection: string | undefined): UseEntriesResult {\n const ctx = useDispatchContext();\n const [entries, setEntries] = useState<Entry[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | undefined>(undefined);\n\n useEffect(() => {\n if (!collection?.trim()) {\n setEntries([]);\n setIsLoading(false);\n setError(undefined);\n return;\n }\n\n if (!ctx?.siteKey) {\n setEntries([]);\n setIsLoading(false);\n setError(\"Missing site key\");\n return;\n }\n\n let cancelled = false;\n setEntries([]);\n setError(undefined);\n setIsLoading(true);\n\n const url = `${ctx.apiBase}/api/content/${encodeURIComponent(collection.trim())}`;\n fetch(url, {\n headers: { \"X-Site-Key\": ctx.siteKey },\n })\n .then((res) => {\n if (cancelled) return;\n if (!res.ok) {\n setEntries([]);\n setIsLoading(false);\n setError(\"Failed to load\");\n return;\n }\n return res.json();\n })\n .then((data) => {\n if (cancelled || data == null) return;\n setEntries(Array.isArray(data) ? data : []);\n setIsLoading(false);\n })\n .catch(() => {\n if (cancelled) return;\n setEntries([]);\n setIsLoading(false);\n setError(\"Failed to load\");\n });\n\n return () => {\n cancelled = true;\n };\n }, [collection, ctx?.siteKey, ctx?.apiBase]);\n\n return { entries, isLoading, error };\n}\n"],"mappings":"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","names":["import_react"]}
|
package/dist/index.mjs
CHANGED
|
@@ -143,26 +143,49 @@ function useEntries(collection) {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
// src/index.ts
|
|
146
|
+
function clampCropSide(n, fallback) {
|
|
147
|
+
return typeof n === "number" && Number.isFinite(n) ? Math.min(1, Math.max(0, n)) : fallback;
|
|
148
|
+
}
|
|
149
|
+
function readCrop(value) {
|
|
150
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
151
|
+
return { x: 0, y: 0, width: 1, height: 1 };
|
|
152
|
+
}
|
|
153
|
+
const row = value;
|
|
154
|
+
let x = clampCropSide(row.x, 0);
|
|
155
|
+
let y = clampCropSide(row.y, 0);
|
|
156
|
+
let width = clampCropSide(row.width, 1);
|
|
157
|
+
let height = clampCropSide(row.height, 1);
|
|
158
|
+
width = Math.max(0.05, Math.min(width, 1 - x));
|
|
159
|
+
height = Math.max(0.05, Math.min(height, 1 - y));
|
|
160
|
+
return { x, y, width, height };
|
|
161
|
+
}
|
|
146
162
|
function imageProps(value) {
|
|
147
163
|
let src = "";
|
|
148
|
-
let
|
|
149
|
-
let y = 0.5;
|
|
164
|
+
let crop = { x: 0, y: 0, width: 1, height: 1 };
|
|
150
165
|
if (typeof value === "string") {
|
|
151
166
|
src = value;
|
|
152
167
|
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
153
168
|
const row = value;
|
|
154
169
|
if (typeof row.url !== "string") return null;
|
|
155
170
|
src = row.url;
|
|
156
|
-
if (row.
|
|
157
|
-
const focus = row.focus;
|
|
158
|
-
if (typeof focus.x === "number" && Number.isFinite(focus.x)) x = Math.min(1, Math.max(0, focus.x));
|
|
159
|
-
if (typeof focus.y === "number" && Number.isFinite(focus.y)) y = Math.min(1, Math.max(0, focus.y));
|
|
160
|
-
}
|
|
171
|
+
if (row.crop != null) crop = readCrop(row.crop);
|
|
161
172
|
} else {
|
|
162
173
|
return null;
|
|
163
174
|
}
|
|
164
175
|
if (!src) return null;
|
|
165
|
-
return {
|
|
176
|
+
return {
|
|
177
|
+
src,
|
|
178
|
+
crop,
|
|
179
|
+
style: {
|
|
180
|
+
position: "absolute",
|
|
181
|
+
left: `${-crop.x / crop.width * 100}%`,
|
|
182
|
+
top: `${-crop.y / crop.height * 100}%`,
|
|
183
|
+
width: `${1 / crop.width * 100}%`,
|
|
184
|
+
height: `${1 / crop.height * 100}%`,
|
|
185
|
+
maxWidth: "none",
|
|
186
|
+
maxHeight: "none"
|
|
187
|
+
}
|
|
188
|
+
};
|
|
166
189
|
}
|
|
167
190
|
export {
|
|
168
191
|
DispatchProvider,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/context.tsx","../src/config.ts","../src/hooks.ts","../src/index.ts"],"sourcesContent":["\"use client\";\n\nimport React, { createContext, useContext } from \"react\";\nimport { getApiBase } from \"./config\";\n\ntype DispatchContextValue = {\n siteKey: string | null;\n apiBase: string;\n};\n\nconst DispatchContext = createContext<DispatchContextValue | null>(null);\n\nexport function useDispatchContext(): DispatchContextValue | null {\n return useContext(DispatchContext);\n}\n\ntype DispatchProviderProps = {\n siteKey?: string | null;\n apiBase?: string;\n children?: unknown;\n};\n\nexport function DispatchProvider({\n siteKey = null,\n apiBase = getApiBase(),\n children,\n}: DispatchProviderProps) {\n const value: DispatchContextValue = {\n siteKey: siteKey?.trim() || null,\n apiBase,\n };\n\n if (!value.siteKey) {\n if (typeof console !== \"undefined\" && console.warn) {\n console.warn(\n \"[Dispatch] No siteKey provided. Pass siteKey to DispatchProvider. Hooks will return empty/error state.\"\n );\n }\n }\n\n return (\n <DispatchContext.Provider value={value}>\n {children as React.ReactNode}\n </DispatchContext.Provider>\n );\n}\n","const DEFAULT_API_BASE = \"https://dispatch-cms.vercel.app\";\n\nexport function getApiBase(): string {\n return DEFAULT_API_BASE;\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { Entry } from \"./types\";\nimport { useDispatchContext } from \"./context\";\n\nexport type UseEntryResult = {\n entry: Entry | null;\n isLoading: boolean;\n error?: string;\n};\n\nexport function useEntry(collection: string | undefined, slug: string | undefined): UseEntryResult {\n const ctx = useDispatchContext();\n const [entry, setEntry] = useState<Entry | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | undefined>(undefined);\n\n useEffect(() => {\n if (!collection?.trim() || !slug?.trim()) {\n setEntry(null);\n setIsLoading(false);\n setError(undefined);\n return;\n }\n\n if (!ctx?.siteKey) {\n setEntry(null);\n setIsLoading(false);\n setError(\"Missing site key\");\n return;\n }\n\n let cancelled = false;\n setEntry(null);\n setError(undefined);\n setIsLoading(true);\n\n const url = `${ctx.apiBase}/api/content/${encodeURIComponent(collection.trim())}/${encodeURIComponent(slug.trim())}`;\n fetch(url, {\n headers: { \"X-Site-Key\": ctx.siteKey },\n })\n .then((res) => {\n if (cancelled) return;\n if (res.status === 404) {\n setEntry(null);\n setIsLoading(false);\n return;\n }\n if (!res.ok) {\n setEntry(null);\n setIsLoading(false);\n setError(\"Failed to load\");\n return;\n }\n return res.json();\n })\n .then((data) => {\n if (cancelled || data == null) return;\n setEntry(data as Entry);\n setIsLoading(false);\n })\n .catch(() => {\n if (cancelled) return;\n setEntry(null);\n setIsLoading(false);\n setError(\"Failed to load\");\n });\n\n return () => {\n cancelled = true;\n };\n }, [collection, slug, ctx?.siteKey, ctx?.apiBase]);\n\n return { entry, isLoading, error };\n}\n\nexport type UseEntriesResult = {\n entries: Entry[];\n isLoading: boolean;\n error?: string;\n};\n\nexport function useEntries(collection: string | undefined): UseEntriesResult {\n const ctx = useDispatchContext();\n const [entries, setEntries] = useState<Entry[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | undefined>(undefined);\n\n useEffect(() => {\n if (!collection?.trim()) {\n setEntries([]);\n setIsLoading(false);\n setError(undefined);\n return;\n }\n\n if (!ctx?.siteKey) {\n setEntries([]);\n setIsLoading(false);\n setError(\"Missing site key\");\n return;\n }\n\n let cancelled = false;\n setEntries([]);\n setError(undefined);\n setIsLoading(true);\n\n const url = `${ctx.apiBase}/api/content/${encodeURIComponent(collection.trim())}`;\n fetch(url, {\n headers: { \"X-Site-Key\": ctx.siteKey },\n })\n .then((res) => {\n if (cancelled) return;\n if (!res.ok) {\n setEntries([]);\n setIsLoading(false);\n setError(\"Failed to load\");\n return;\n }\n return res.json();\n })\n .then((data) => {\n if (cancelled || data == null) return;\n setEntries(Array.isArray(data) ? data : []);\n setIsLoading(false);\n })\n .catch(() => {\n if (cancelled) return;\n setEntries([]);\n setIsLoading(false);\n setError(\"Failed to load\");\n });\n\n return () => {\n cancelled = true;\n };\n }, [collection, ctx?.siteKey, ctx?.apiBase]);\n\n return { entries, isLoading, error };\n}\n","export type { Entry } from \"./types\";\nexport { DispatchProvider, useDispatchContext } from \"./context\";\nexport { useEntry, useEntries } from \"./hooks\";\nexport type { UseEntryResult, UseEntriesResult } from \"./hooks\";\n\n// ponytail: percentage object-position. Measure the image against the frame if a subject still clips.\nexport function imageProps(value: unknown): {\n src: string;\n style: { objectFit: \"cover\"; objectPosition: string };\n} | null {\n let src = \"\";\n let x = 0.5;\n let y = 0.5;\n if (typeof value === \"string\") {\n src = value;\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n const row = value as Record<string, unknown>;\n if (typeof row.url !== \"string\") return null;\n src = row.url;\n if (row.focus && typeof row.focus === \"object\" && !Array.isArray(row.focus)) {\n const focus = row.focus as Record<string, unknown>;\n if (typeof focus.x === \"number\" && Number.isFinite(focus.x)) x = Math.min(1, Math.max(0, focus.x));\n if (typeof focus.y === \"number\" && Number.isFinite(focus.y)) y = Math.min(1, Math.max(0, focus.y));\n }\n } else {\n return null;\n }\n if (!src) return null;\n return { src, style: { objectFit: \"cover\", objectPosition: `${x * 100}% ${y * 100}%` } };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/context.tsx","../src/config.ts","../src/hooks.ts","../src/index.ts"],"sourcesContent":["\"use client\";\n\nimport React, { createContext, useContext } from \"react\";\nimport { getApiBase } from \"./config\";\n\ntype DispatchContextValue = {\n siteKey: string | null;\n apiBase: string;\n};\n\nconst DispatchContext = createContext<DispatchContextValue | null>(null);\n\nexport function useDispatchContext(): DispatchContextValue | null {\n return useContext(DispatchContext);\n}\n\ntype DispatchProviderProps = {\n siteKey?: string | null;\n apiBase?: string;\n children?: unknown;\n};\n\nexport function DispatchProvider({\n siteKey = null,\n apiBase = getApiBase(),\n children,\n}: DispatchProviderProps) {\n const value: DispatchContextValue = {\n siteKey: siteKey?.trim() || null,\n apiBase,\n };\n\n if (!value.siteKey) {\n if (typeof console !== \"undefined\" && console.warn) {\n console.warn(\n \"[Dispatch] No siteKey provided. Pass siteKey to DispatchProvider. Hooks will return empty/error state.\"\n );\n }\n }\n\n return (\n <DispatchContext.Provider value={value}>\n {children as React.ReactNode}\n </DispatchContext.Provider>\n );\n}\n","const DEFAULT_API_BASE = \"https://dispatch-cms.vercel.app\";\n\nexport function getApiBase(): string {\n return DEFAULT_API_BASE;\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { Entry } from \"./types\";\nimport { useDispatchContext } from \"./context\";\n\nexport type UseEntryResult = {\n entry: Entry | null;\n isLoading: boolean;\n error?: string;\n};\n\nexport function useEntry(collection: string | undefined, slug: string | undefined): UseEntryResult {\n const ctx = useDispatchContext();\n const [entry, setEntry] = useState<Entry | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | undefined>(undefined);\n\n useEffect(() => {\n if (!collection?.trim() || !slug?.trim()) {\n setEntry(null);\n setIsLoading(false);\n setError(undefined);\n return;\n }\n\n if (!ctx?.siteKey) {\n setEntry(null);\n setIsLoading(false);\n setError(\"Missing site key\");\n return;\n }\n\n let cancelled = false;\n setEntry(null);\n setError(undefined);\n setIsLoading(true);\n\n const url = `${ctx.apiBase}/api/content/${encodeURIComponent(collection.trim())}/${encodeURIComponent(slug.trim())}`;\n fetch(url, {\n headers: { \"X-Site-Key\": ctx.siteKey },\n })\n .then((res) => {\n if (cancelled) return;\n if (res.status === 404) {\n setEntry(null);\n setIsLoading(false);\n return;\n }\n if (!res.ok) {\n setEntry(null);\n setIsLoading(false);\n setError(\"Failed to load\");\n return;\n }\n return res.json();\n })\n .then((data) => {\n if (cancelled || data == null) return;\n setEntry(data as Entry);\n setIsLoading(false);\n })\n .catch(() => {\n if (cancelled) return;\n setEntry(null);\n setIsLoading(false);\n setError(\"Failed to load\");\n });\n\n return () => {\n cancelled = true;\n };\n }, [collection, slug, ctx?.siteKey, ctx?.apiBase]);\n\n return { entry, isLoading, error };\n}\n\nexport type UseEntriesResult = {\n entries: Entry[];\n isLoading: boolean;\n error?: string;\n};\n\nexport function useEntries(collection: string | undefined): UseEntriesResult {\n const ctx = useDispatchContext();\n const [entries, setEntries] = useState<Entry[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | undefined>(undefined);\n\n useEffect(() => {\n if (!collection?.trim()) {\n setEntries([]);\n setIsLoading(false);\n setError(undefined);\n return;\n }\n\n if (!ctx?.siteKey) {\n setEntries([]);\n setIsLoading(false);\n setError(\"Missing site key\");\n return;\n }\n\n let cancelled = false;\n setEntries([]);\n setError(undefined);\n setIsLoading(true);\n\n const url = `${ctx.apiBase}/api/content/${encodeURIComponent(collection.trim())}`;\n fetch(url, {\n headers: { \"X-Site-Key\": ctx.siteKey },\n })\n .then((res) => {\n if (cancelled) return;\n if (!res.ok) {\n setEntries([]);\n setIsLoading(false);\n setError(\"Failed to load\");\n return;\n }\n return res.json();\n })\n .then((data) => {\n if (cancelled || data == null) return;\n setEntries(Array.isArray(data) ? data : []);\n setIsLoading(false);\n })\n .catch(() => {\n if (cancelled) return;\n setEntries([]);\n setIsLoading(false);\n setError(\"Failed to load\");\n });\n\n return () => {\n cancelled = true;\n };\n }, [collection, ctx?.siteKey, ctx?.apiBase]);\n\n return { entries, isLoading, error };\n}\n","export type { Entry } from \"./types\";\nexport { DispatchProvider, useDispatchContext } from \"./context\";\nexport { useEntry, useEntries } from \"./hooks\";\nexport type { UseEntryResult, UseEntriesResult } from \"./hooks\";\n\nexport type ImageCrop = { x: number; y: number; width: number; height: number };\n\nfunction clampCropSide(n: number, fallback: number) {\n return typeof n === \"number\" && Number.isFinite(n) ? Math.min(1, Math.max(0, n)) : fallback;\n}\n\nfunction readCrop(value: unknown): ImageCrop {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return { x: 0, y: 0, width: 1, height: 1 };\n }\n const row = value as Record<string, unknown>;\n let x = clampCropSide(row.x as number, 0);\n let y = clampCropSide(row.y as number, 0);\n let width = clampCropSide(row.width as number, 1);\n let height = clampCropSide(row.height as number, 1);\n width = Math.max(0.05, Math.min(width, 1 - x));\n height = Math.max(0.05, Math.min(height, 1 - y));\n return { x, y, width, height };\n}\n\n/** Styles for an <img> inside a sized overflow:hidden frame — shows only the cropped region. */\nexport function imageProps(value: unknown): {\n src: string;\n crop: ImageCrop;\n style: {\n position: \"absolute\";\n left: string;\n top: string;\n width: string;\n height: string;\n maxWidth: \"none\";\n maxHeight: \"none\";\n };\n} | null {\n let src = \"\";\n let crop: ImageCrop = { x: 0, y: 0, width: 1, height: 1 };\n if (typeof value === \"string\") {\n src = value;\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n const row = value as Record<string, unknown>;\n if (typeof row.url !== \"string\") return null;\n src = row.url;\n if (row.crop != null) crop = readCrop(row.crop);\n } else {\n return null;\n }\n if (!src) return null;\n return {\n src,\n crop,\n style: {\n position: \"absolute\",\n left: `${(-crop.x / crop.width) * 100}%`,\n top: `${(-crop.y / crop.height) * 100}%`,\n width: `${(1 / crop.width) * 100}%`,\n height: `${(1 / crop.height) * 100}%`,\n maxWidth: \"none\",\n maxHeight: \"none\",\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -3,28 +3,64 @@ export { DispatchProvider, useDispatchContext } from "./context";
|
|
|
3
3
|
export { useEntry, useEntries } from "./hooks";
|
|
4
4
|
export type { UseEntryResult, UseEntriesResult } from "./hooks";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
export type ImageCrop = { x: number; y: number; width: number; height: number };
|
|
7
|
+
|
|
8
|
+
function clampCropSide(n: number, fallback: number) {
|
|
9
|
+
return typeof n === "number" && Number.isFinite(n) ? Math.min(1, Math.max(0, n)) : fallback;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function readCrop(value: unknown): ImageCrop {
|
|
13
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
14
|
+
return { x: 0, y: 0, width: 1, height: 1 };
|
|
15
|
+
}
|
|
16
|
+
const row = value as Record<string, unknown>;
|
|
17
|
+
let x = clampCropSide(row.x as number, 0);
|
|
18
|
+
let y = clampCropSide(row.y as number, 0);
|
|
19
|
+
let width = clampCropSide(row.width as number, 1);
|
|
20
|
+
let height = clampCropSide(row.height as number, 1);
|
|
21
|
+
width = Math.max(0.05, Math.min(width, 1 - x));
|
|
22
|
+
height = Math.max(0.05, Math.min(height, 1 - y));
|
|
23
|
+
return { x, y, width, height };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Styles for an <img> inside a sized overflow:hidden frame — shows only the cropped region. */
|
|
7
27
|
export function imageProps(value: unknown): {
|
|
8
28
|
src: string;
|
|
9
|
-
|
|
29
|
+
crop: ImageCrop;
|
|
30
|
+
style: {
|
|
31
|
+
position: "absolute";
|
|
32
|
+
left: string;
|
|
33
|
+
top: string;
|
|
34
|
+
width: string;
|
|
35
|
+
height: string;
|
|
36
|
+
maxWidth: "none";
|
|
37
|
+
maxHeight: "none";
|
|
38
|
+
};
|
|
10
39
|
} | null {
|
|
11
40
|
let src = "";
|
|
12
|
-
let
|
|
13
|
-
let y = 0.5;
|
|
41
|
+
let crop: ImageCrop = { x: 0, y: 0, width: 1, height: 1 };
|
|
14
42
|
if (typeof value === "string") {
|
|
15
43
|
src = value;
|
|
16
44
|
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
17
45
|
const row = value as Record<string, unknown>;
|
|
18
46
|
if (typeof row.url !== "string") return null;
|
|
19
47
|
src = row.url;
|
|
20
|
-
if (row.
|
|
21
|
-
const focus = row.focus as Record<string, unknown>;
|
|
22
|
-
if (typeof focus.x === "number" && Number.isFinite(focus.x)) x = Math.min(1, Math.max(0, focus.x));
|
|
23
|
-
if (typeof focus.y === "number" && Number.isFinite(focus.y)) y = Math.min(1, Math.max(0, focus.y));
|
|
24
|
-
}
|
|
48
|
+
if (row.crop != null) crop = readCrop(row.crop);
|
|
25
49
|
} else {
|
|
26
50
|
return null;
|
|
27
51
|
}
|
|
28
52
|
if (!src) return null;
|
|
29
|
-
return {
|
|
53
|
+
return {
|
|
54
|
+
src,
|
|
55
|
+
crop,
|
|
56
|
+
style: {
|
|
57
|
+
position: "absolute",
|
|
58
|
+
left: `${(-crop.x / crop.width) * 100}%`,
|
|
59
|
+
top: `${(-crop.y / crop.height) * 100}%`,
|
|
60
|
+
width: `${(1 / crop.width) * 100}%`,
|
|
61
|
+
height: `${(1 / crop.height) * 100}%`,
|
|
62
|
+
maxWidth: "none",
|
|
63
|
+
maxHeight: "none",
|
|
64
|
+
},
|
|
65
|
+
};
|
|
30
66
|
}
|