@pantheon-systems/p1-media 0.4.2 → 0.4.4

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.mjs CHANGED
@@ -17,6 +17,46 @@ var DEFAULT_MEDIA_PATTERNS = [
17
17
  // src/components/media-field.tsx
18
18
  import { useState as useState5 } from "react";
19
19
 
20
+ // src/crop.ts
21
+ function getBaseUrl(value) {
22
+ return value ? value.split("?")[0] : "";
23
+ }
24
+ function getParams(value) {
25
+ return new URLSearchParams(value.includes("?") ? value.split("?")[1] : "");
26
+ }
27
+ function getCropMode(value) {
28
+ if (!value) return "fit";
29
+ const params = getParams(value);
30
+ if (params.has("trim.left")) return "custom";
31
+ return params.get("fit") === "cover" ? "smart" : "fit";
32
+ }
33
+ function getTrimRect(value) {
34
+ if (!value) return null;
35
+ const params = getParams(value);
36
+ const read = (key) => {
37
+ const raw = params.get(key);
38
+ if (raw === null || raw === "") return null;
39
+ const n2 = Number(raw);
40
+ return Number.isFinite(n2) ? n2 : null;
41
+ };
42
+ const left = read("trim.left");
43
+ const top = read("trim.top");
44
+ const width = read("trim.width");
45
+ const height = read("trim.height");
46
+ if (left === null || top === null || width === null || height === null) return null;
47
+ return { left, top, width, height };
48
+ }
49
+ function buildValueWithCrop(baseUrl, crop) {
50
+ return crop === "smart" ? `${baseUrl}?fit=cover&gravity=auto` : `${baseUrl}?fit=scale-down`;
51
+ }
52
+ function buildValueWithTrim(baseUrl, rect) {
53
+ const left = Math.max(0, Math.round(rect.left));
54
+ const top = Math.max(0, Math.round(rect.top));
55
+ const width = Math.max(1, Math.round(rect.width));
56
+ const height = Math.max(1, Math.round(rect.height));
57
+ return `${baseUrl}?trim.left=${left}&trim.top=${top}&trim.width=${width}&trim.height=${height}`;
58
+ }
59
+
20
60
  // src/components/media-library.tsx
21
61
  import { useState as useState4, useEffect as useEffect4, useRef as useRef4, useCallback } from "react";
22
62
 
@@ -182,6 +222,9 @@ async function runUpload(target, file, metadata, assetId, progress, onStep) {
182
222
  uploaded = true;
183
223
  }
184
224
  const nextProgress = { presigned, uploaded };
225
+ if (presigned === void 0) {
226
+ throw new UploadFlowError("Resume state is missing its presign result", nextProgress);
227
+ }
185
228
  const presignedResult = presigned;
186
229
  onStep("finalizing");
187
230
  const finalizePath = assetId ? `/media/${encodeURIComponent(assetId)}/versions/finalize` : "/media/finalize";
@@ -1284,12 +1327,13 @@ function MediaLibrary({ isOpen, onClose, onSelect, onSelectItem }) {
1284
1327
  onClose();
1285
1328
  };
1286
1329
  const deleteItem = (item) => {
1287
- if (!item.assetId) return;
1330
+ const { assetId } = item;
1331
+ if (!assetId) return;
1288
1332
  (async () => {
1289
1333
  try {
1290
1334
  const params = siteParams(config);
1291
1335
  const response = await fetch(
1292
- config.workerUrl + "/media/" + encodeURIComponent(item.assetId) + "?" + params.toString(),
1336
+ config.workerUrl + "/media/" + encodeURIComponent(assetId) + "?" + params.toString(),
1293
1337
  {
1294
1338
  method: "DELETE",
1295
1339
  headers: await getAuthHeaders()
@@ -1427,46 +1471,6 @@ function MediaLibrary({ isOpen, onClose, onSelect, onSelectItem }) {
1427
1471
  );
1428
1472
  }
1429
1473
 
1430
- // src/crop.ts
1431
- function getBaseUrl(value) {
1432
- return value ? value.split("?")[0] : "";
1433
- }
1434
- function getParams(value) {
1435
- return new URLSearchParams(value.includes("?") ? value.split("?")[1] : "");
1436
- }
1437
- function getCropMode(value) {
1438
- if (!value) return "fit";
1439
- const params = getParams(value);
1440
- if (params.has("trim.left")) return "custom";
1441
- return params.get("fit") === "cover" ? "smart" : "fit";
1442
- }
1443
- function getTrimRect(value) {
1444
- if (!value) return null;
1445
- const params = getParams(value);
1446
- const read = (key) => {
1447
- const raw = params.get(key);
1448
- if (raw === null || raw === "") return null;
1449
- const n2 = Number(raw);
1450
- return Number.isFinite(n2) ? n2 : null;
1451
- };
1452
- const left = read("trim.left");
1453
- const top = read("trim.top");
1454
- const width = read("trim.width");
1455
- const height = read("trim.height");
1456
- if (left === null || top === null || width === null || height === null) return null;
1457
- return { left, top, width, height };
1458
- }
1459
- function buildValueWithCrop(baseUrl, crop) {
1460
- return crop === "smart" ? `${baseUrl}?fit=cover&gravity=auto` : `${baseUrl}?fit=scale-down`;
1461
- }
1462
- function buildValueWithTrim(baseUrl, rect) {
1463
- const left = Math.max(0, Math.round(rect.left));
1464
- const top = Math.max(0, Math.round(rect.top));
1465
- const width = Math.max(1, Math.round(rect.width));
1466
- const height = Math.max(1, Math.round(rect.height));
1467
- return `${baseUrl}?trim.left=${left}&trim.top=${top}&trim.width=${width}&trim.height=${height}`;
1468
- }
1469
-
1470
1474
  // src/components/media-field.tsx
1471
1475
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1472
1476
  function MediaFieldRender(props) {
@@ -1621,10 +1625,73 @@ function MediaFieldRender(props) {
1621
1625
  // src/components/media-object-field.tsx
1622
1626
  import { useState as useState7 } from "react";
1623
1627
 
1628
+ // src/media-value.ts
1629
+ var STRUCTURAL_MEDIA_KEYS = /* @__PURE__ */ new Set([
1630
+ "assetId",
1631
+ "versionId",
1632
+ "url",
1633
+ "metaSchemaVersion"
1634
+ ]);
1635
+ function isMediaValue(value) {
1636
+ return typeof value === "object" && value !== null;
1637
+ }
1638
+ function makeMediaValue(input) {
1639
+ if (!input.assetId || !input.versionId) {
1640
+ return input.url;
1641
+ }
1642
+ const value = {
1643
+ assetId: input.assetId,
1644
+ versionId: input.versionId,
1645
+ url: input.url
1646
+ };
1647
+ if (typeof input.metaSchemaVersion === "number") {
1648
+ value.metaSchemaVersion = input.metaSchemaVersion;
1649
+ }
1650
+ if (input.metadata) {
1651
+ for (const [k, v] of Object.entries(input.metadata)) {
1652
+ if (v === void 0 || v === "") continue;
1653
+ if (STRUCTURAL_MEDIA_KEYS.has(k)) continue;
1654
+ value[k] = v;
1655
+ }
1656
+ }
1657
+ return value;
1658
+ }
1659
+ function buildValueFromAsset(asset, crop) {
1660
+ const metadata = { ...asset.metadata ?? {} };
1661
+ if (typeof asset.width === "number") metadata.width = asset.width;
1662
+ if (typeof asset.height === "number") metadata.height = asset.height;
1663
+ return makeMediaValue({
1664
+ assetId: asset.assetId,
1665
+ versionId: asset.versionId,
1666
+ url: buildValueWithCrop(getBaseUrl(asset.url), crop),
1667
+ metaSchemaVersion: asset.metaSchemaVersion,
1668
+ metadata
1669
+ });
1670
+ }
1671
+ function applyCropToValue(value, crop) {
1672
+ const currentUrl = isMediaValue(value) ? value.url : value;
1673
+ const baseUrl = getBaseUrl(typeof currentUrl === "string" ? currentUrl : "");
1674
+ if (!baseUrl) return value;
1675
+ const url = buildValueWithCrop(baseUrl, crop);
1676
+ return isMediaValue(value) ? { ...value, url } : url;
1677
+ }
1678
+ function applyTrimToValue(value, rect) {
1679
+ const currentUrl = isMediaValue(value) ? value.url : value;
1680
+ const baseUrl = getBaseUrl(typeof currentUrl === "string" ? currentUrl : "");
1681
+ if (!baseUrl) return value;
1682
+ const url = buildValueWithTrim(baseUrl, rect);
1683
+ return isMediaValue(value) ? { ...value, url } : url;
1684
+ }
1685
+ function setMetaOnValue(value, fieldName, fieldValue) {
1686
+ if (!isMediaValue(value)) return value;
1687
+ if (STRUCTURAL_MEDIA_KEYS.has(fieldName)) return value;
1688
+ return { ...value, [fieldName]: fieldValue };
1689
+ }
1690
+
1624
1691
  // src/components/crop-dialog.tsx
1625
1692
  import { useEffect as useEffect5, useRef as useRef5, useState as useState6 } from "react";
1626
1693
 
1627
- // ../../node_modules/.pnpm/react-image-crop@11.1.2_react@19.2.6/node_modules/react-image-crop/dist/index.js
1694
+ // ../../node_modules/.pnpm/react-image-crop@11.1.2_react@19.2.7/node_modules/react-image-crop/dist/index.js
1628
1695
  import e, { PureComponent as t, createRef as n } from "react";
1629
1696
  var r = {
1630
1697
  x: 0,
@@ -2053,7 +2120,7 @@ function styleInject(css, { insertAt } = {}) {
2053
2120
  }
2054
2121
  }
2055
2122
 
2056
- // ../../node_modules/.pnpm/react-image-crop@11.1.2_react@19.2.6/node_modules/react-image-crop/dist/ReactCrop.css
2123
+ // ../../node_modules/.pnpm/react-image-crop@11.1.2_react@19.2.7/node_modules/react-image-crop/dist/ReactCrop.css
2057
2124
  styleInject('@keyframes marching-ants {\n 0% {\n background-position:\n 0 0,\n 0 100%,\n 0 0,\n 100% 0;\n }\n to {\n background-position:\n 20px 0,\n -20px 100%,\n 0 -20px,\n 100% 20px;\n }\n}\n:root {\n --rc-drag-handle-size:12px;\n --rc-drag-handle-mobile-size:24px;\n --rc-drag-handle-bg-colour:#0003;\n --rc-drag-bar-size:6px;\n --rc-border-color:#ffffffb3;\n --rc-focus-color:#08f;\n}\n.ReactCrop {\n cursor: crosshair;\n max-width: 100%;\n display: inline-block;\n position: relative;\n}\n.ReactCrop *,\n.ReactCrop :before,\n.ReactCrop :after {\n box-sizing: border-box;\n}\n.ReactCrop--disabled,\n.ReactCrop--locked {\n cursor: inherit;\n}\n.ReactCrop__child-wrapper {\n max-height: inherit;\n overflow: hidden;\n}\n.ReactCrop__child-wrapper > img,\n.ReactCrop__child-wrapper > video {\n max-width: 100%;\n max-height: inherit;\n display: block;\n}\n.ReactCrop:not(.ReactCrop--disabled) .ReactCrop__child-wrapper > img,\n.ReactCrop:not(.ReactCrop--disabled) .ReactCrop__child-wrapper > video,\n.ReactCrop:not(.ReactCrop--disabled) .ReactCrop__crop-selection {\n touch-action: none;\n}\n.ReactCrop__crop-mask {\n pointer-events: none;\n width: calc(100% + .5px);\n height: calc(100% + .5px);\n position: absolute;\n inset: 0;\n}\n.ReactCrop__crop-selection {\n cursor: move;\n position: absolute;\n top: 0;\n left: 0;\n transform: translate(0, 0);\n}\n.ReactCrop--disabled .ReactCrop__crop-selection {\n cursor: inherit;\n}\n.ReactCrop--circular-crop .ReactCrop__crop-selection {\n border-radius: 50%;\n}\n.ReactCrop--circular-crop .ReactCrop__crop-selection:after {\n pointer-events: none;\n content: "";\n border: 1px solid var(--rc-border-color);\n opacity: .3;\n position: absolute;\n inset: -1px;\n}\n.ReactCrop--no-animate .ReactCrop__crop-selection {\n outline: 1px dashed #fff;\n}\n.ReactCrop__crop-selection:not(.ReactCrop--no-animate .ReactCrop__crop-selection) {\n color: #fff;\n background-image:\n linear-gradient(\n 90deg,\n #fff 50%,\n #444 50%),\n linear-gradient(\n 90deg,\n #fff 50%,\n #444 50%),\n linear-gradient(#fff 50%, #444 50%),\n linear-gradient(#fff 50%, #444 50%);\n background-position:\n 0 0,\n 0 100%,\n 0 0,\n 100% 0;\n background-repeat:\n repeat-x,\n repeat-x,\n repeat-y,\n repeat-y;\n background-size:\n 10px 1px,\n 10px 1px,\n 1px 10px,\n 1px 10px;\n animation: 1s linear infinite marching-ants;\n}\n.ReactCrop__crop-selection:focus {\n outline: 2px solid var(--rc-focus-color);\n outline-offset: -1px;\n}\n.ReactCrop--invisible-crop .ReactCrop__crop-mask,\n.ReactCrop--invisible-crop .ReactCrop__crop-selection {\n display: none;\n}\n.ReactCrop__rule-of-thirds-vt:before,\n.ReactCrop__rule-of-thirds-vt:after,\n.ReactCrop__rule-of-thirds-hz:before,\n.ReactCrop__rule-of-thirds-hz:after {\n content: "";\n background-color: #fff6;\n display: block;\n position: absolute;\n}\n.ReactCrop__rule-of-thirds-vt:before,\n.ReactCrop__rule-of-thirds-vt:after {\n width: 1px;\n height: 100%;\n}\n.ReactCrop__rule-of-thirds-vt:before {\n left: 33.3333%;\n}\n.ReactCrop__rule-of-thirds-vt:after {\n left: 66.6667%;\n}\n.ReactCrop__rule-of-thirds-hz:before,\n.ReactCrop__rule-of-thirds-hz:after {\n width: 100%;\n height: 1px;\n}\n.ReactCrop__rule-of-thirds-hz:before {\n top: 33.3333%;\n}\n.ReactCrop__rule-of-thirds-hz:after {\n top: 66.6667%;\n}\n.ReactCrop__drag-handle {\n width: var(--rc-drag-handle-size);\n height: var(--rc-drag-handle-size);\n background-color: var(--rc-drag-handle-bg-colour);\n border: 1px solid var(--rc-border-color);\n position: absolute;\n}\n.ReactCrop__drag-handle:focus {\n background: var(--rc-focus-color);\n}\n.ReactCrop .ord-nw {\n cursor: nw-resize;\n top: 0;\n left: 0;\n transform: translate(-50%, -50%);\n}\n.ReactCrop .ord-n {\n cursor: n-resize;\n top: 0;\n left: 50%;\n transform: translate(-50%, -50%);\n}\n.ReactCrop .ord-ne {\n cursor: ne-resize;\n top: 0;\n right: 0;\n transform: translate(50%, -50%);\n}\n.ReactCrop .ord-e {\n cursor: e-resize;\n top: 50%;\n right: 0;\n transform: translate(50%, -50%);\n}\n.ReactCrop .ord-se {\n cursor: se-resize;\n bottom: 0;\n right: 0;\n transform: translate(50%, 50%);\n}\n.ReactCrop .ord-s {\n cursor: s-resize;\n bottom: 0;\n left: 50%;\n transform: translate(-50%, 50%);\n}\n.ReactCrop .ord-sw {\n cursor: sw-resize;\n bottom: 0;\n left: 0;\n transform: translate(-50%, 50%);\n}\n.ReactCrop .ord-w {\n cursor: w-resize;\n top: 50%;\n left: 0;\n transform: translate(-50%, -50%);\n}\n.ReactCrop__disabled .ReactCrop__drag-handle {\n cursor: inherit;\n}\n.ReactCrop__drag-bar {\n position: absolute;\n}\n.ReactCrop__drag-bar.ord-n {\n width: 100%;\n height: var(--rc-drag-bar-size);\n top: 0;\n left: 0;\n transform: translateY(-50%);\n}\n.ReactCrop__drag-bar.ord-e {\n width: var(--rc-drag-bar-size);\n height: 100%;\n top: 0;\n right: 0;\n transform: translate(50%);\n}\n.ReactCrop__drag-bar.ord-s {\n width: 100%;\n height: var(--rc-drag-bar-size);\n bottom: 0;\n left: 0;\n transform: translateY(50%);\n}\n.ReactCrop__drag-bar.ord-w {\n width: var(--rc-drag-bar-size);\n height: 100%;\n top: 0;\n left: 0;\n transform: translate(-50%);\n}\n.ReactCrop--new-crop .ReactCrop__drag-bar,\n.ReactCrop--new-crop .ReactCrop__drag-handle,\n.ReactCrop--fixed-aspect .ReactCrop__drag-bar,\n.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-n,\n.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-e,\n.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-s,\n.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-w {\n display: none;\n}\n@media (pointer: coarse) {\n .ReactCrop .ord-n,\n .ReactCrop .ord-e,\n .ReactCrop .ord-s,\n .ReactCrop .ord-w {\n display: none;\n }\n .ReactCrop__drag-handle {\n width: var(--rc-drag-handle-mobile-size);\n height: var(--rc-drag-handle-mobile-size);\n }\n}\n');
2058
2125
 
2059
2126
  // src/components/crop-dialog.tsx
@@ -2300,69 +2367,6 @@ function CropDialog(props) {
2300
2367
  );
2301
2368
  }
2302
2369
 
2303
- // src/media-value.ts
2304
- var STRUCTURAL_MEDIA_KEYS = /* @__PURE__ */ new Set([
2305
- "assetId",
2306
- "versionId",
2307
- "url",
2308
- "metaSchemaVersion"
2309
- ]);
2310
- function isMediaValue(value) {
2311
- return typeof value === "object" && value !== null;
2312
- }
2313
- function makeMediaValue(input) {
2314
- if (!input.assetId || !input.versionId) {
2315
- return input.url;
2316
- }
2317
- const value = {
2318
- assetId: input.assetId,
2319
- versionId: input.versionId,
2320
- url: input.url
2321
- };
2322
- if (typeof input.metaSchemaVersion === "number") {
2323
- value.metaSchemaVersion = input.metaSchemaVersion;
2324
- }
2325
- if (input.metadata) {
2326
- for (const [k, v] of Object.entries(input.metadata)) {
2327
- if (v === void 0 || v === "") continue;
2328
- if (STRUCTURAL_MEDIA_KEYS.has(k)) continue;
2329
- value[k] = v;
2330
- }
2331
- }
2332
- return value;
2333
- }
2334
- function buildValueFromAsset(asset, crop) {
2335
- const metadata = { ...asset.metadata ?? {} };
2336
- if (typeof asset.width === "number") metadata.width = asset.width;
2337
- if (typeof asset.height === "number") metadata.height = asset.height;
2338
- return makeMediaValue({
2339
- assetId: asset.assetId,
2340
- versionId: asset.versionId,
2341
- url: buildValueWithCrop(getBaseUrl(asset.url), crop),
2342
- metaSchemaVersion: asset.metaSchemaVersion,
2343
- metadata
2344
- });
2345
- }
2346
- function applyCropToValue(value, crop) {
2347
- const currentUrl = isMediaValue(value) ? value.url : value;
2348
- const baseUrl = getBaseUrl(typeof currentUrl === "string" ? currentUrl : "");
2349
- if (!baseUrl) return value;
2350
- const url = buildValueWithCrop(baseUrl, crop);
2351
- return isMediaValue(value) ? { ...value, url } : url;
2352
- }
2353
- function applyTrimToValue(value, rect) {
2354
- const currentUrl = isMediaValue(value) ? value.url : value;
2355
- const baseUrl = getBaseUrl(typeof currentUrl === "string" ? currentUrl : "");
2356
- if (!baseUrl) return value;
2357
- const url = buildValueWithTrim(baseUrl, rect);
2358
- return isMediaValue(value) ? { ...value, url } : url;
2359
- }
2360
- function setMetaOnValue(value, fieldName, fieldValue) {
2361
- if (!isMediaValue(value)) return value;
2362
- if (STRUCTURAL_MEDIA_KEYS.has(fieldName)) return value;
2363
- return { ...value, [fieldName]: fieldValue };
2364
- }
2365
-
2366
2370
  // src/components/media-object-field.tsx
2367
2371
  import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2368
2372
  function MediaObjectFieldRender(props) {
@@ -2769,6 +2773,8 @@ function MediaImage({
2769
2773
  mediaBaseUrl,
2770
2774
  transform,
2771
2775
  alt,
2776
+ loading = "lazy",
2777
+ decoding = "async",
2772
2778
  ...rest
2773
2779
  }) {
2774
2780
  const props = getMediaProps(image, { mediaBaseUrl, transform });
@@ -2778,6 +2784,8 @@ function MediaImage({
2778
2784
  {
2779
2785
  src: props.src,
2780
2786
  alt: alt ?? props.alt,
2787
+ loading,
2788
+ decoding,
2781
2789
  ...props.width !== void 0 ? { width: props.width } : {},
2782
2790
  ...props.height !== void 0 ? { height: props.height } : {},
2783
2791
  ...rest
@@ -2810,7 +2818,9 @@ function MediaFigure({
2810
2818
  mediaBaseUrl,
2811
2819
  transform,
2812
2820
  className,
2813
- captionClassName
2821
+ captionClassName,
2822
+ loading = "lazy",
2823
+ decoding = "async"
2814
2824
  }) {
2815
2825
  const props = getMediaProps(image, { mediaBaseUrl, transform });
2816
2826
  if (!props.src) return null;
@@ -2821,6 +2831,8 @@ function MediaFigure({
2821
2831
  {
2822
2832
  src: props.src,
2823
2833
  alt: props.alt,
2834
+ loading,
2835
+ decoding,
2824
2836
  ...props.width !== void 0 ? { width: props.width } : {},
2825
2837
  ...props.height !== void 0 ? { height: props.height } : {}
2826
2838
  }
@@ -2852,19 +2864,29 @@ function createMediaFigureBlock(options) {
2852
2864
  schema,
2853
2865
  className,
2854
2866
  captionClassName,
2855
- placeholder = "Choose a photo from the media library"
2867
+ placeholder = "Choose a photo from the media library",
2868
+ defaultLoading = "lazy"
2856
2869
  } = options;
2857
2870
  return {
2858
2871
  label,
2859
2872
  fields: {
2860
2873
  // `p1-media` is registered by createMediaPlugin via overrides.fieldTypes,
2861
2874
  // so it is not part of Puck's built-in Field union.
2862
- photo: { type: "p1-media", label: fieldLabel }
2875
+ photo: { type: "p1-media", label: fieldLabel },
2876
+ loading: {
2877
+ type: "radio",
2878
+ label: "Loading",
2879
+ options: [
2880
+ { label: "Lazy", value: "lazy" },
2881
+ { label: "Eager", value: "eager" }
2882
+ ]
2883
+ }
2863
2884
  },
2864
2885
  defaultProps: {
2865
- photo: null
2886
+ photo: null,
2887
+ loading: defaultLoading
2866
2888
  },
2867
- render: ({ photo }) => {
2889
+ render: ({ photo, loading }) => {
2868
2890
  const { src } = getMediaProps(photo ?? null, { mediaBaseUrl });
2869
2891
  if (!src) {
2870
2892
  return /* @__PURE__ */ jsx14("div", { style: placeholderStyle, children: placeholder });
@@ -2877,7 +2899,8 @@ function createMediaFigureBlock(options) {
2877
2899
  transform,
2878
2900
  schema,
2879
2901
  className,
2880
- captionClassName
2902
+ captionClassName,
2903
+ loading: loading === "eager" ? "eager" : "lazy"
2881
2904
  }
2882
2905
  );
2883
2906
  }