@sevenfold/setto-client 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export { SettoIcon } from './SettoIcon';
12
12
  export type { SettoIconProps, SettoIconRenderProps } from './SettoIcon';
13
13
  export { SettoImage } from './SettoImage';
14
14
  export type { SettoImageProps, ImageToolsProps } from './SettoImage';
15
+ export { SETTO_IMAGE_QUALITY, SETTO_IMAGE_WIDTHS, isOptimizable, optimizedSrcSet, optimizedUrl, settoVercelImagesConfig, } from './lib/optimized-image';
15
16
  export { SettoRepeater } from './SettoRepeater';
16
17
  export type { SettoRepeaterProps } from './SettoRepeater';
17
18
  export { SettoForm, FORM_FIELD_TYPES, FORM_FIELD_TYPE_LABELS, fieldTypeHasOptions, fieldTypeHasPlaceholder, formFieldsPrefix, isFormFieldType, readLocaleFields, settoSubmissionUrl, } from './modules/forms';
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Images through Vercel's Image Optimization, for sites that turned it on.
3
+ *
4
+ * A site keeps one original per image in its repository; the CDN makes each
5
+ * width on first request, in AVIF or WebP as the browser accepts, and caches
6
+ * it by the file's content. A site turns it on with two things that must
7
+ * agree: `"images": { "optimize": true }` in `setto.config.json`, and an
8
+ * `images` block in `vercel.json` whose `sizes` and `qualities` are exactly
9
+ * the constants below — Vercel refuses a `w` or `q` it was not given. The
10
+ * block, for copying: `settoVercelImagesConfig`.
11
+ */
12
+ /** Phone, phone at 2–3×, tablet, laptop, wide and retina desktop. */
13
+ export declare const SETTO_IMAGE_WIDTHS: readonly [384, 640, 828, 1080, 1440, 1920, 2560];
14
+ /** One quality for every image, so each width is one cached variant rather than several. */
15
+ export declare const SETTO_IMAGE_QUALITY = 75;
16
+ /** The `images` block of `vercel.json` that matches `SettoImage`. */
17
+ export declare const settoVercelImagesConfig: {
18
+ readonly sizes: readonly [384, 640, 828, 1080, 1440, 1920, 2560];
19
+ readonly qualities: readonly [75];
20
+ readonly formats: readonly ["image/avif", "image/webp"];
21
+ readonly localPatterns: readonly [{
22
+ readonly pathname: "^/images/.*$";
23
+ readonly search: "";
24
+ }];
25
+ readonly minimumCacheTTL: 2678400;
26
+ };
27
+ /**
28
+ * Whether an image can go through the optimizer: a file of this site's own,
29
+ * under `/images/`, in a format it transforms. A draft's `blob:` preview, an
30
+ * external address, an SVG logo and a moving GIF cannot.
31
+ */
32
+ export declare function isOptimizable(src: string | null | undefined): src is string;
33
+ /**
34
+ * Where there is no optimizer to ask: the Vite dev server and a local
35
+ * preview. A deployment elsewhere without one (a build sandbox) is caught by
36
+ * the image's own error instead.
37
+ */
38
+ export declare function hasNoOptimizer(hostname: string): boolean;
39
+ export declare function optimizedUrl(src: string, width: number): string;
40
+ export declare function optimizedSrcSet(src: string): string;
@@ -37505,6 +37505,29 @@ function SettoIcon({ k, icons, size = 24, className, style }) {
37505
37505
  )
37506
37506
  ] });
37507
37507
  }
37508
+ const SETTO_IMAGE_WIDTHS = [384, 640, 828, 1080, 1440, 1920, 2560];
37509
+ const SETTO_IMAGE_QUALITY = 75;
37510
+ const settoVercelImagesConfig = {
37511
+ sizes: [...SETTO_IMAGE_WIDTHS],
37512
+ qualities: [SETTO_IMAGE_QUALITY],
37513
+ formats: ["image/avif", "image/webp"],
37514
+ localPatterns: [{ pathname: "^/images/.*$", search: "" }],
37515
+ minimumCacheTTL: 2678400
37516
+ };
37517
+ const OPTIMIZABLE = /\.(jpe?g|png|webp|avif)$/i;
37518
+ function isOptimizable(src) {
37519
+ if (!src || !src.startsWith("/images/")) return false;
37520
+ return OPTIMIZABLE.test(src.split(/[?#]/)[0]);
37521
+ }
37522
+ function hasNoOptimizer(hostname) {
37523
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname.endsWith(".localhost");
37524
+ }
37525
+ function optimizedUrl(src, width) {
37526
+ return `/_vercel/image?url=${encodeURIComponent(src)}&w=${width}&q=${SETTO_IMAGE_QUALITY}`;
37527
+ }
37528
+ function optimizedSrcSet(src) {
37529
+ return SETTO_IMAGE_WIDTHS.map((w) => `${optimizedUrl(src, w)} ${w}w`).join(", ");
37530
+ }
37508
37531
  const ACCEPT = "image/png,image/jpeg,image/webp,image/svg+xml";
37509
37532
  function setSrcAllLanguages(store, i18n, srcKey, value) {
37510
37533
  const langs = i18n.languages?.length ? i18n.languages : [i18n.language];
@@ -37512,9 +37535,34 @@ function setSrcAllLanguages(store, i18n, srcKey, value) {
37512
37535
  store.set(srcKey, lng, value);
37513
37536
  }
37514
37537
  }
37538
+ function PublishedImage({
37539
+ src,
37540
+ optimize,
37541
+ sizes,
37542
+ srcSet,
37543
+ onError,
37544
+ ...rest
37545
+ }) {
37546
+ const [direct, setDirect] = useState(false);
37547
+ const optimized = optimize && !direct && !srcSet && isOptimizable(src) && !(typeof window !== "undefined" && hasNoOptimizer(window.location.hostname));
37548
+ if (!optimized) return /* @__PURE__ */ jsx("img", { src, srcSet, sizes, onError, ...rest });
37549
+ return /* @__PURE__ */ jsx(
37550
+ "img",
37551
+ {
37552
+ src: optimizedUrl(src, 1080),
37553
+ srcSet: optimizedSrcSet(src),
37554
+ sizes: sizes ?? "100vw",
37555
+ onError: (e) => {
37556
+ setDirect(true);
37557
+ onError?.(e);
37558
+ },
37559
+ ...rest
37560
+ }
37561
+ );
37562
+ }
37515
37563
  function SettoImage({ srcKey, alt, className, style, ...rest }) {
37516
37564
  const { t, i18n } = useTranslation();
37517
- const { editMode, store, assetStore } = useSetto();
37565
+ const { editMode, store, assetStore, config } = useSetto();
37518
37566
  const fileRef = useRef(null);
37519
37567
  const [hovered, setHovered] = useState(false);
37520
37568
  const [uploading, setUploading] = useState(false);
@@ -37534,7 +37582,17 @@ function SettoImage({ srcKey, alt, className, style, ...rest }) {
37534
37582
  const previewUrl = assetStore?.getPreviewUrl(srcKey);
37535
37583
  const src = previewUrl ?? srcPath;
37536
37584
  if (!editMode) {
37537
- return /* @__PURE__ */ jsx("img", { src: srcPath, alt, className, style, ...rest });
37585
+ return /* @__PURE__ */ jsx(
37586
+ PublishedImage,
37587
+ {
37588
+ src: srcPath,
37589
+ optimize: config.images?.optimize === true,
37590
+ alt,
37591
+ className,
37592
+ style,
37593
+ ...rest
37594
+ }
37595
+ );
37538
37596
  }
37539
37597
  const isFullWidth = className?.includes("w-full");
37540
37598
  const isFullHeight = className?.includes("h-full");
@@ -40011,6 +40069,8 @@ export {
40011
40069
  PAGES_PATH,
40012
40070
  PageStore,
40013
40071
  SETTO_BASE,
40072
+ SETTO_IMAGE_QUALITY,
40073
+ SETTO_IMAGE_WIDTHS,
40014
40074
  SETTO_MODULES,
40015
40075
  SettoAdminApp,
40016
40076
  SettoAnimation,
@@ -40051,11 +40111,14 @@ export {
40051
40111
  isAdminRoute,
40052
40112
  isFormFieldType,
40053
40113
  isLinkContext,
40114
+ isOptimizable,
40054
40115
  isSafeHref,
40055
40116
  languageFromPath,
40056
40117
  languageName,
40057
40118
  nodeChildren,
40058
40119
  normalisePath,
40120
+ optimizedSrcSet,
40121
+ optimizedUrl,
40059
40122
  pageById,
40060
40123
  pageForPath,
40061
40124
  pageTitleKey,
@@ -40067,6 +40130,7 @@ export {
40067
40130
  redirectForVisitor,
40068
40131
  serialiseManifest,
40069
40132
  settoSubmissionUrl,
40133
+ settoVercelImagesConfig,
40070
40134
  siteHomeUrl,
40071
40135
  siteLanguages,
40072
40136
  useCoarsePointer,