@orangelogic/orange-dam-content-browser-sdk 2.3.0 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.mjs CHANGED
@@ -12823,6 +12823,24 @@ __decorateClass$1([
12823
12823
  CxContentBrowserHeader = __decorateClass$1([
12824
12824
  customElement("cx-content-browser-header")
12825
12825
  ], CxContentBrowserHeader);
12826
+ const appendPathAndQuery = (baseUrl, pathSuffix, extraQueryParams = []) => {
12827
+ const questionMarkIndex = baseUrl.indexOf("?");
12828
+ const hashIndex = baseUrl.indexOf("#");
12829
+ const separatorIndex = [questionMarkIndex, hashIndex].filter((index) => index !== -1).reduce((lowest, index) => lowest === -1 ? index : Math.min(lowest, index), -1);
12830
+ const path = separatorIndex === -1 ? baseUrl : baseUrl.slice(0, separatorIndex);
12831
+ const tail = separatorIndex === -1 ? "" : baseUrl.slice(separatorIndex);
12832
+ const tailHashIndex = tail.indexOf("#");
12833
+ const fragment = tailHashIndex === -1 ? "" : tail.slice(tailHashIndex);
12834
+ const existingQuery = (tailHashIndex === -1 ? tail : tail.slice(0, tailHashIndex)).replace(/^\?/, "");
12835
+ const parameterName = (parameter) => parameter.split("=")[0].toLowerCase();
12836
+ const overriddenNames = new Set(extraQueryParams.map(parameterName));
12837
+ const mergedParams = [
12838
+ ...existingQuery.split("&").filter((parameter) => parameter && !overriddenNames.has(parameterName(parameter))),
12839
+ ...extraQueryParams
12840
+ ];
12841
+ const query = mergedParams.length > 0 ? `?${mergedParams.join("&")}` : "";
12842
+ return `${path}${pathSuffix}${query}${fragment}`;
12843
+ };
12826
12844
  const DEFAULT_VIEW_SIZE = "CoreField.LargeSizePreview";
12827
12845
  const FIELD_ALLOW_ATS_LINK = "AllowATSLink";
12828
12846
  const FIELD_DOC_CAPTION_LONG = "Document.CaptionLong";
@@ -12868,6 +12886,8 @@ function resolveAssetExtraFilters(selectedFacets) {
12868
12886
  function isCortexErrorResponse(response) {
12869
12887
  return response && typeof response === "object" && "ErrorCode" in response;
12870
12888
  }
12889
+ const POST_UNSUPPORTED_STATUSES = /* @__PURE__ */ new Set([405, 501]);
12890
+ const postUnsupportedOrigins = /* @__PURE__ */ new Set();
12871
12891
  function createId() {
12872
12892
  const cryptoApi = globalThis.crypto;
12873
12893
  if (cryptoApi == null ? void 0 : cryptoApi.randomUUID) {
@@ -12888,6 +12908,128 @@ function createId() {
12888
12908
  }
12889
12909
  return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
12890
12910
  }
12911
+ function buildAssetLinkImageUrl({
12912
+ asset,
12913
+ extension,
12914
+ parameters,
12915
+ permanentLink,
12916
+ rawResponse,
12917
+ transformations,
12918
+ useSession
12919
+ }) {
12920
+ let baseImageUrl = permanentLink || rawResponse.imageUrl;
12921
+ let transformationPath = "";
12922
+ if (transformations && transformations.length > 0) {
12923
+ transformationPath += "/t/";
12924
+ }
12925
+ transformations == null ? void 0 : transformations.forEach(({ key, value }) => {
12926
+ if (key === TransformationAction.Resize) {
12927
+ const validTransformations = [
12928
+ ...[
12929
+ {
12930
+ key: "re_w_",
12931
+ value: value.width
12932
+ },
12933
+ {
12934
+ key: "re_h_",
12935
+ value: value.height
12936
+ }
12937
+ ].filter((item) => item.value !== void 0).map((item) => ({
12938
+ key: item.key,
12939
+ value: Math.round(Number(item.value))
12940
+ })),
12941
+ {
12942
+ key: "re_rm_",
12943
+ value: "stretch"
12944
+ }
12945
+ ];
12946
+ validTransformations.forEach(({ key: vKey, value: vValue }, index) => {
12947
+ transformationPath += `${vKey}${vValue}${index < validTransformations.length - 1 ? "," : ""}`;
12948
+ });
12949
+ transformationPath += "/";
12950
+ }
12951
+ if (key === TransformationAction.Crop) {
12952
+ const validTransformations = [
12953
+ ...[
12954
+ {
12955
+ key: "c_w_",
12956
+ value: value.width
12957
+ },
12958
+ {
12959
+ key: "c_h_",
12960
+ value: value.height
12961
+ },
12962
+ {
12963
+ key: "c_x_",
12964
+ value: value.x
12965
+ },
12966
+ {
12967
+ key: "c_y_",
12968
+ value: value.y
12969
+ }
12970
+ ].filter((item) => item.value !== void 0).map((item) => ({
12971
+ key: item.key,
12972
+ value: Math.round(Number(item.value))
12973
+ })),
12974
+ {
12975
+ key: "c_whu_",
12976
+ value: "pixel"
12977
+ }
12978
+ ];
12979
+ validTransformations.forEach(({ key: vKey, value: vValue }, index) => {
12980
+ transformationPath += `${vKey}${vValue}${index < validTransformations.length - 1 ? "," : ""}`;
12981
+ });
12982
+ transformationPath += "/";
12983
+ }
12984
+ if (key === TransformationAction.Rotate) {
12985
+ const validTransformations = [{
12986
+ key: "r_a_",
12987
+ value: value.rotation
12988
+ }].filter((item) => item.value !== void 0).map((item) => ({ key: item.key, value: Math.round(Number(item.value)) }));
12989
+ validTransformations.forEach(({ key: vKey, value: vValue }, index) => {
12990
+ transformationPath += `${vKey}${vValue}${index < validTransformations.length - 1 ? "," : ""}`;
12991
+ });
12992
+ transformationPath += "/";
12993
+ }
12994
+ if (key === TransformationAction.Quality) {
12995
+ const validTransformations = [{
12996
+ key: "q_level_",
12997
+ value: value.quality
12998
+ }].filter((item) => item.value !== void 0).map((item) => ({ key: item.key, value: Math.round(Number(item.value)) }));
12999
+ validTransformations.forEach(({ key: vKey, value: vValue }, index) => {
13000
+ transformationPath += `${vKey}${vValue}${index < validTransformations.length - 1 ? "," : ""}`;
13001
+ });
13002
+ transformationPath += "/";
13003
+ }
13004
+ if (key === TransformationAction.Metadata) {
13005
+ if (value.keepMetadata === true) {
13006
+ transformationPath += "fl_keep_metadata/";
13007
+ }
13008
+ }
13009
+ });
13010
+ const hasTransformations = Boolean(transformations && transformations.length > 0);
13011
+ if (hasTransformations) {
13012
+ transformationPath += `${asset.identifier}`;
13013
+ }
13014
+ if (!permanentLink) {
13015
+ const assetExtension = extension != null ? extension : asset.extension;
13016
+ const normalizedExtension = assetExtension.startsWith(".") ? assetExtension : `.${assetExtension}`;
13017
+ if (hasTransformations) {
13018
+ transformationPath += normalizedExtension;
13019
+ } else {
13020
+ const [basePath, baseTail] = baseImageUrl.split(/([?#].*)$/, 2);
13021
+ baseImageUrl = basePath.replace(/\.[^./]+$/, "") + normalizedExtension + (baseTail != null ? baseTail : "");
13022
+ }
13023
+ }
13024
+ const queryParams = [];
13025
+ parameters == null ? void 0 : parameters.forEach(({ key, value }) => {
13026
+ queryParams.push(`${encodeURIComponent(key.trim())}=${encodeURIComponent(value.trim())}`);
13027
+ });
13028
+ if (useSession) {
13029
+ queryParams.push(`UseSession=${encodeURIComponent(useSession)}`);
13030
+ }
13031
+ return appendPathAndQuery(baseImageUrl, transformationPath, queryParams);
13032
+ }
12891
13033
  function apiGetAssetLinks(_0) {
12892
13034
  return __async(this, arguments, function* ({
12893
13035
  assets,
@@ -12899,161 +13041,83 @@ function apiGetAssetLinks(_0) {
12899
13041
  transformations,
12900
13042
  useSession
12901
13043
  }) {
13044
+ var _a2;
12902
13045
  const getAssetLinkErrors = {};
12903
13046
  const isOnlyOneAssetSelected = assets.length === 1;
12904
13047
  let hasError = false;
13048
+ const origin2 = (_a2 = http.defaults.baseURL) != null ? _a2 : window.location.origin;
12905
13049
  try {
12906
- const settled = yield Promise.allSettled(assets.map((asset) => {
12907
- return http.request({
12908
- method: "GET",
12909
- params: {
12910
- ExtraFields: extraFields,
12911
- GenerateAssetUrl: !permanentLink,
12912
- Parameters: parameters,
12913
- Proxy: proxyPreference,
12914
- RecordId: asset.recordId,
12915
- UseSession: useSession || void 0
12916
- },
12917
- paramsSerializer: {
12918
- indexes: null
12919
- },
12920
- transformResponse: [
12921
- ...Array.isArray(http.defaults.transformResponse) ? http.defaults.transformResponse : [],
12922
- (rawResponse) => {
12923
- if (isCortexErrorResponse(rawResponse)) {
12924
- hasError = true;
12925
- if (isOnlyOneAssetSelected) {
12926
- if (getAssetLinkErrors[rawResponse.ErrorCode]) {
12927
- getAssetLinkErrors[rawResponse.ErrorCode].push(asset);
12928
- } else {
12929
- getAssetLinkErrors[rawResponse.ErrorCode] = [asset];
12930
- }
12931
- }
12932
- return {
12933
- imageUrl: asset.imageUrl
12934
- };
12935
- }
12936
- const sourceUrl = permanentLink || rawResponse.imageUrl;
12937
- const [sourcePath, sourceQuery] = sourceUrl.split("?", 2);
12938
- const mergedParams = new URLSearchParams(sourceQuery != null ? sourceQuery : "");
12939
- let imageUrl = sourcePath;
12940
- if (transformations && transformations.length > 0) {
12941
- imageUrl += "/t/";
12942
- }
12943
- transformations == null ? void 0 : transformations.forEach(({ key, value }) => {
12944
- if (key === TransformationAction.Resize) {
12945
- const validTransformations = [
12946
- ...[
12947
- {
12948
- key: "re_w_",
12949
- value: value.width
12950
- },
12951
- {
12952
- key: "re_h_",
12953
- value: value.height
12954
- }
12955
- ].filter((item) => item.value !== void 0).map((item) => ({
12956
- key: item.key,
12957
- value: Math.round(Number(item.value))
12958
- })),
12959
- {
12960
- key: "re_rm_",
12961
- value: "stretch"
12962
- }
12963
- ];
12964
- validTransformations.forEach(({ key: vKey, value: vValue }, index) => {
12965
- imageUrl += `${vKey}${vValue}${index < validTransformations.length - 1 ? "," : ""}`;
12966
- });
12967
- imageUrl += "/";
12968
- }
12969
- if (key === TransformationAction.Crop) {
12970
- const validTransformations = [
12971
- ...[
12972
- {
12973
- key: "c_w_",
12974
- value: value.width
12975
- },
12976
- {
12977
- key: "c_h_",
12978
- value: value.height
12979
- },
12980
- {
12981
- key: "c_x_",
12982
- value: value.x
12983
- },
12984
- {
12985
- key: "c_y_",
12986
- value: value.y
12987
- }
12988
- ].filter((item) => item.value !== void 0).map((item) => ({
12989
- key: item.key,
12990
- value: Math.round(Number(item.value))
12991
- })),
12992
- {
12993
- key: "c_whu_",
12994
- value: "pixel"
12995
- }
12996
- ];
12997
- validTransformations.forEach(({ key: vKey, value: vValue }, index) => {
12998
- imageUrl += `${vKey}${vValue}${index < validTransformations.length - 1 ? "," : ""}`;
12999
- });
13000
- imageUrl += "/";
13001
- }
13002
- if (key === TransformationAction.Rotate) {
13003
- const validTransformations = [{
13004
- key: "r_a_",
13005
- value: value.rotation
13006
- }].filter((item) => item.value !== void 0).map((item) => ({ key: item.key, value: Math.round(Number(item.value)) }));
13007
- validTransformations.forEach(({ key: vKey, value: vValue }, index) => {
13008
- imageUrl += `${vKey}${vValue}${index < validTransformations.length - 1 ? "," : ""}`;
13009
- });
13010
- imageUrl += "/";
13011
- }
13012
- if (key === TransformationAction.Quality) {
13013
- const validTransformations = [{
13014
- key: "q_level_",
13015
- value: value.quality
13016
- }].filter((item) => item.value !== void 0).map((item) => ({ key: item.key, value: Math.round(Number(item.value)) }));
13017
- validTransformations.forEach(({ key: vKey, value: vValue }, index) => {
13018
- imageUrl += `${vKey}${vValue}${index < validTransformations.length - 1 ? "," : ""}`;
13019
- });
13020
- imageUrl += "/";
13021
- }
13022
- if (key === TransformationAction.Metadata) {
13023
- if (value.keepMetadata === true) {
13024
- imageUrl += "fl_keep_metadata/";
13025
- }
13026
- }
13027
- });
13028
- if (transformations && transformations.length > 0) {
13029
- imageUrl += `${asset.identifier}`;
13030
- }
13031
- if (!permanentLink) {
13032
- const assetExtension = extension != null ? extension : asset.extension;
13033
- const normalizedExtension = assetExtension.startsWith(".") ? assetExtension : `.${assetExtension}`;
13034
- imageUrl = imageUrl.replace(/\.[^./]+$/, "") + normalizedExtension;
13035
- }
13036
- parameters == null ? void 0 : parameters.forEach(({ key, value }) => {
13037
- mergedParams.set(key.trim(), value.trim());
13038
- });
13039
- if (useSession && !mergedParams.has("UseSession")) {
13040
- mergedParams.set("UseSession", useSession);
13041
- }
13042
- const finalQuery = mergedParams.toString();
13043
- if (finalQuery) {
13044
- imageUrl += `?${finalQuery}`;
13045
- }
13046
- return __spreadProps(__spreadValues({}, rawResponse), {
13047
- imageUrl
13048
- });
13050
+ const settled = yield Promise.allSettled(assets.map((asset) => __async(null, null, function* () {
13051
+ const requestBody = {
13052
+ GenerateAssetUrl: !permanentLink,
13053
+ Parameters: parameters,
13054
+ Proxy: proxyPreference,
13055
+ RecordId: asset.recordId
13056
+ };
13057
+ if (extraFields == null ? void 0 : extraFields.length) {
13058
+ requestBody.ExtraFields = extraFields;
13059
+ }
13060
+ const useSessionQuery = useSession ? `?${new URLSearchParams({ UseSession: useSession }).toString()}` : "";
13061
+ const attemptPost = !postUnsupportedOrigins.has(origin2);
13062
+ let rawResponse;
13063
+ if (attemptPost) {
13064
+ try {
13065
+ const postResult = yield http.request({
13066
+ data: requestBody,
13067
+ method: "POST",
13068
+ url: `${AssetApiEndpoint.GET_ASSET_LINK}${useSessionQuery}`,
13069
+ validateStatus: () => true
13070
+ });
13071
+ if (POST_UNSUPPORTED_STATUSES.has(postResult.status)) {
13072
+ postUnsupportedOrigins.add(origin2);
13073
+ } else {
13074
+ rawResponse = postResult.data;
13049
13075
  }
13050
- ],
13051
- url: AssetApiEndpoint.GET_ASSET_LINK
13076
+ } catch (e2) {
13077
+ }
13078
+ }
13079
+ if (rawResponse === void 0) {
13080
+ const getResult = yield http.request({
13081
+ method: "GET",
13082
+ params: __spreadProps(__spreadValues({}, requestBody), {
13083
+ UseSession: useSession || void 0
13084
+ }),
13085
+ paramsSerializer: {
13086
+ indexes: null
13087
+ },
13088
+ url: AssetApiEndpoint.GET_ASSET_LINK
13089
+ });
13090
+ rawResponse = getResult.data;
13091
+ }
13092
+ if (isCortexErrorResponse(rawResponse)) {
13093
+ hasError = true;
13094
+ if (isOnlyOneAssetSelected) {
13095
+ if (getAssetLinkErrors[rawResponse.ErrorCode]) {
13096
+ getAssetLinkErrors[rawResponse.ErrorCode].push(asset);
13097
+ } else {
13098
+ getAssetLinkErrors[rawResponse.ErrorCode] = [asset];
13099
+ }
13100
+ }
13101
+ return {
13102
+ imageUrl: asset.imageUrl
13103
+ };
13104
+ }
13105
+ const imageUrl = buildAssetLinkImageUrl({
13106
+ asset,
13107
+ extension,
13108
+ parameters,
13109
+ permanentLink,
13110
+ rawResponse,
13111
+ transformations,
13112
+ useSession
13052
13113
  });
13053
- }));
13114
+ return __spreadProps(__spreadValues({}, rawResponse), {
13115
+ imageUrl
13116
+ });
13117
+ })));
13054
13118
  const data = settled.flatMap((result) => {
13055
13119
  if (result.status === "fulfilled") {
13056
- return [result.value.data];
13120
+ return [result.value];
13057
13121
  }
13058
13122
  hasError = true;
13059
13123
  return [];
@@ -32740,6 +32804,87 @@ const App = ({
32740
32804
  }
32741
32805
  ) });
32742
32806
  };
32807
+ const MODAL_SELECTORS = [
32808
+ '[aria-modal="true"]',
32809
+ // ARIA-compliant dialogs (most libraries)
32810
+ "dialog[open]",
32811
+ // native <dialog> (showModal() sets no aria-modal attribute)
32812
+ ".ui-dialog",
32813
+ // jQuery UI Dialog (used by Drupal core modals)
32814
+ ".modal.show",
32815
+ // Bootstrap 4 / 5
32816
+ ".modal.in",
32817
+ // Bootstrap 3
32818
+ '[data-focus-lock-disabled="false"]'
32819
+ // react-focus-lock / focus-trap-react
32820
+ ].join(",");
32821
+ function viewOf(el) {
32822
+ var _a2;
32823
+ return (_a2 = el.ownerDocument.defaultView) != null ? _a2 : window;
32824
+ }
32825
+ function isVisible(el) {
32826
+ if (!el || !el.isConnected) {
32827
+ return false;
32828
+ }
32829
+ const view = viewOf(el);
32830
+ if (!(el instanceof view.HTMLElement)) {
32831
+ return false;
32832
+ }
32833
+ const style = view.getComputedStyle(el);
32834
+ if (style.visibility === "hidden" || style.display === "none") {
32835
+ return false;
32836
+ }
32837
+ const rect = el.getBoundingClientRect();
32838
+ return rect.width > 0 && rect.height > 0;
32839
+ }
32840
+ function establishesFixedContainingBlock(el) {
32841
+ var _a2;
32842
+ const doc = el.ownerDocument;
32843
+ const view = (_a2 = doc.defaultView) != null ? _a2 : window;
32844
+ let node2 = el;
32845
+ while (node2 && node2 !== doc.body && node2 !== doc.documentElement) {
32846
+ const style = view.getComputedStyle(node2);
32847
+ if (style.transform && style.transform !== "none" || style.perspective && style.perspective !== "none" || style.filter && style.filter !== "none" || /transform|perspective|filter/.test(style.willChange || "") || /paint|layout|strict|content/.test(style.contain || "")) {
32848
+ return true;
32849
+ }
32850
+ node2 = node2.parentElement;
32851
+ }
32852
+ return false;
32853
+ }
32854
+ function pickTopmost(candidates) {
32855
+ if (candidates.length === 0) {
32856
+ return null;
32857
+ }
32858
+ return candidates.reduce((top, el) => {
32859
+ const zTop = parseInt(viewOf(top).getComputedStyle(top).zIndex, 10) || 0;
32860
+ const zEl = parseInt(viewOf(el).getComputedStyle(el).zIndex, 10) || 0;
32861
+ if (zEl !== zTop) {
32862
+ return zEl > zTop ? el : top;
32863
+ }
32864
+ return el.compareDocumentPosition(top) & Node.DOCUMENT_POSITION_PRECEDING ? el : top;
32865
+ });
32866
+ }
32867
+ function findFocusContainmentHost(doc = document) {
32868
+ var _a2;
32869
+ const view = (_a2 = doc.defaultView) != null ? _a2 : window;
32870
+ const active = doc.activeElement;
32871
+ const fromActive = active instanceof view.HTMLElement ? active.closest(MODAL_SELECTORS) : null;
32872
+ const candidate = fromActive && isVisible(fromActive) && fromActive || pickTopmost(
32873
+ Array.from(doc.querySelectorAll(MODAL_SELECTORS)).filter(
32874
+ isVisible
32875
+ )
32876
+ );
32877
+ if (!candidate) {
32878
+ return null;
32879
+ }
32880
+ if (establishesFixedContainingBlock(candidate)) {
32881
+ console.warn(
32882
+ "[ContentBrowserSDK] An active modal was detected, but it establishes a CSS containing block that would clip the picker overlay. Falling back to document.body. Pass `containerId` (an element inside the modal) to embed the picker correctly."
32883
+ );
32884
+ return null;
32885
+ }
32886
+ return candidate;
32887
+ }
32743
32888
  globalThis.addEventListener("cx-unauthorized", refreshAccessToken);
32744
32889
  const assetsPickerRef = reactExports.createRef();
32745
32890
  const ContentBrowser = {
@@ -32841,11 +32986,12 @@ const ContentBrowser = {
32841
32986
  useSession,
32842
32987
  defaultGridView
32843
32988
  }) {
32989
+ var _a2;
32844
32990
  const customStorageHandlers = typeof customStorage === "object" && !!customStorage && typeof customStorage.delete === "function" && !!customStorage.delete && typeof customStorage.get === "function" && !!customStorage.get && typeof customStorage.set === "function" && !!customStorage.set ? customStorage : void 0;
32845
32991
  window.OrangeDAMContentBrowser._customStorage = customStorageHandlers;
32846
32992
  let container = containerId && document.getElementById(containerId);
32847
32993
  if (!containerId) {
32848
- container = document.body;
32994
+ container = (_a2 = findFocusContainmentHost()) != null ? _a2 : document.body;
32849
32995
  } else if (!container) {
32850
32996
  console.error(`Container with id ${containerId} is not found`);
32851
32997
  return;
@@ -0,0 +1 @@
1
+ export declare const appendPathAndQuery: (baseUrl: string, pathSuffix: string, extraQueryParams?: string[]) => string;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Focus-trap-aware popup anchoring (ticket 29KEV1).
3
+ *
4
+ * When the Content Browser SDK opens in popup mode (no `containerId`), its UI is
5
+ * appended to `document.body`, outside any host-page modal. If the host page has
6
+ * an active modal with a focus trap — Drupal's jQuery UI Dialog, Bootstrap,
7
+ * react-focus-lock / focus-trap, a native `<dialog>` opened with `showModal()`,
8
+ * etc. — that trap forces focus back into the modal (or makes the rest of the
9
+ * page `inert`) whenever focus lands on the CBSDK search field, making it
10
+ * impossible to type.
11
+ *
12
+ * Every containment-based focus trap allows focus for elements that are DOM
13
+ * descendants of its container (jQuery UI checks `closest('.ui-dialog')`,
14
+ * focus-trap checks `container.contains(target)`, native modal dialogs don't
15
+ * mark their own subtree `inert`, ...). So the robust, framework-agnostic fix is
16
+ * to mount the CBSDK popup *inside* the active modal container instead of
17
+ * `document.body`.
18
+ */
19
+ /**
20
+ * Finds the active host-page modal that CBSDK should mount inside so its inputs
21
+ * remain focusable. Returns `null` when there is no modal to anchor to (the
22
+ * normal case — CBSDK then mounts to `document.body` as before), or when
23
+ * anchoring would clip CBSDK's fixed overlay.
24
+ */
25
+ export declare function findFocusContainmentHost(doc?: Document): HTMLElement | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orangelogic/orange-dam-content-browser-sdk",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "description": "OrangeDAM Content Browser SDK source code",
5
5
  "main": "./build/OrangeDAMContentBrowserSDK.min.js",
6
6
  "module": "./build/index.mjs",