@dispatchcms/react 0.0.7 → 0.0.8

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 CHANGED
@@ -125,6 +125,17 @@ export default function PostPage({ slug }) {
125
125
  }
126
126
  ```
127
127
 
128
+ ## Images
129
+
130
+ An image field is a URL until an editor drags a point, then `{ url, focus }`. `imageProps` reads both. It only changes which part of the photo stays in view when your frame crops it.
131
+
132
+ ```tsx
133
+ import { imageProps } from "@dispatchcms/react";
134
+
135
+ const photo = imageProps(entry.data.hero);
136
+ photo ? <img src={photo.src} alt="" className="h-full w-full" style={photo.style} /> : null
137
+ ```
138
+
128
139
  ## License
129
140
 
130
141
  MIT
package/dist/index.d.mts CHANGED
@@ -39,4 +39,12 @@ type UseEntriesResult = {
39
39
  };
40
40
  declare function useEntries(collection: string | undefined): UseEntriesResult;
41
41
 
42
- export { DispatchProvider, type Entry, type UseEntriesResult, type UseEntryResult, useDispatchContext, useEntries, useEntry };
42
+ declare function imageProps(value: unknown): {
43
+ src: string;
44
+ style: {
45
+ objectFit: "cover";
46
+ objectPosition: string;
47
+ };
48
+ } | null;
49
+
50
+ export { DispatchProvider, type Entry, type UseEntriesResult, type UseEntryResult, imageProps, useDispatchContext, useEntries, useEntry };
package/dist/index.d.ts CHANGED
@@ -39,4 +39,12 @@ type UseEntriesResult = {
39
39
  };
40
40
  declare function useEntries(collection: string | undefined): UseEntriesResult;
41
41
 
42
- export { DispatchProvider, type Entry, type UseEntriesResult, type UseEntryResult, useDispatchContext, useEntries, useEntry };
42
+ declare function imageProps(value: unknown): {
43
+ src: string;
44
+ style: {
45
+ objectFit: "cover";
46
+ objectPosition: string;
47
+ };
48
+ } | null;
49
+
50
+ export { DispatchProvider, type Entry, type UseEntriesResult, type UseEntryResult, imageProps, useDispatchContext, useEntries, useEntry };
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
22
22
  var index_exports = {};
23
23
  __export(index_exports, {
24
24
  DispatchProvider: () => DispatchProvider,
25
+ imageProps: () => imageProps,
25
26
  useDispatchContext: () => useDispatchContext,
26
27
  useEntries: () => useEntries,
27
28
  useEntry: () => useEntry
@@ -169,9 +170,33 @@ function useEntries(collection) {
169
170
  }, [collection, ctx?.siteKey, ctx?.apiBase]);
170
171
  return { entries, isLoading, error };
171
172
  }
173
+
174
+ // src/index.ts
175
+ function imageProps(value) {
176
+ let src = "";
177
+ let x = 0.5;
178
+ let y = 0.5;
179
+ if (typeof value === "string") {
180
+ src = value;
181
+ } else if (value && typeof value === "object" && !Array.isArray(value)) {
182
+ const row = value;
183
+ if (typeof row.url !== "string") return null;
184
+ src = row.url;
185
+ if (row.focus && typeof row.focus === "object" && !Array.isArray(row.focus)) {
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
+ }
190
+ } else {
191
+ return null;
192
+ }
193
+ if (!src) return null;
194
+ return { src, style: { objectFit: "cover", objectPosition: `${x * 100}% ${y * 100}%` } };
195
+ }
172
196
  // Annotate the CommonJS export names for ESM import in node:
173
197
  0 && (module.exports = {
174
198
  DispatchProvider,
199
+ imageProps,
175
200
  useDispatchContext,
176
201
  useEntries,
177
202
  useEntry
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","\"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\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"]}
package/dist/index.mjs CHANGED
@@ -141,8 +141,32 @@ function useEntries(collection) {
141
141
  }, [collection, ctx?.siteKey, ctx?.apiBase]);
142
142
  return { entries, isLoading, error };
143
143
  }
144
+
145
+ // src/index.ts
146
+ function imageProps(value) {
147
+ let src = "";
148
+ let x = 0.5;
149
+ let y = 0.5;
150
+ if (typeof value === "string") {
151
+ src = value;
152
+ } else if (value && typeof value === "object" && !Array.isArray(value)) {
153
+ const row = value;
154
+ if (typeof row.url !== "string") return null;
155
+ src = row.url;
156
+ if (row.focus && typeof row.focus === "object" && !Array.isArray(row.focus)) {
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
+ }
161
+ } else {
162
+ return null;
163
+ }
164
+ if (!src) return null;
165
+ return { src, style: { objectFit: "cover", objectPosition: `${x * 100}% ${y * 100}%` } };
166
+ }
144
167
  export {
145
168
  DispatchProvider,
169
+ imageProps,
146
170
  useDispatchContext,
147
171
  useEntries,
148
172
  useEntry
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/context.tsx","../src/config.ts","../src/hooks.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"],"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\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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dispatchcms/react",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "React hooks and provider for Dispatch CMS entries",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -12,6 +12,9 @@
12
12
  "require": "./dist/index.js"
13
13
  }
14
14
  },
15
+ "scripts": {
16
+ "build": "tsup"
17
+ },
15
18
  "peerDependencies": {
16
19
  "react": ">=18.0.0"
17
20
  },
@@ -24,15 +27,6 @@
24
27
  "engines": {
25
28
  "node": ">=20.9"
26
29
  },
27
- "keywords": [
28
- "dispatch",
29
- "cms",
30
- "react",
31
- "nextjs",
32
- "content"
33
- ],
34
- "license": "MIT",
35
- "scripts": {
36
- "build": "tsup"
37
- }
38
- }
30
+ "keywords": ["dispatch", "cms", "react", "nextjs", "content"],
31
+ "license": "MIT"
32
+ }
package/src/index.ts CHANGED
@@ -2,3 +2,29 @@ export type { Entry } from "./types";
2
2
  export { DispatchProvider, useDispatchContext } from "./context";
3
3
  export { useEntry, useEntries } from "./hooks";
4
4
  export type { UseEntryResult, UseEntriesResult } from "./hooks";
5
+
6
+ // ponytail: percentage object-position. Measure the image against the frame if a subject still clips.
7
+ export function imageProps(value: unknown): {
8
+ src: string;
9
+ style: { objectFit: "cover"; objectPosition: string };
10
+ } | null {
11
+ let src = "";
12
+ let x = 0.5;
13
+ let y = 0.5;
14
+ if (typeof value === "string") {
15
+ src = value;
16
+ } else if (value && typeof value === "object" && !Array.isArray(value)) {
17
+ const row = value as Record<string, unknown>;
18
+ if (typeof row.url !== "string") return null;
19
+ src = row.url;
20
+ if (row.focus && typeof row.focus === "object" && !Array.isArray(row.focus)) {
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
+ }
25
+ } else {
26
+ return null;
27
+ }
28
+ if (!src) return null;
29
+ return { src, style: { objectFit: "cover", objectPosition: `${x * 100}% ${y * 100}%` } };
30
+ }