{"version":3,"sources":["../../../src/lib/utils.ts"],"sourcesContent":["import { ListMetadata, Member } from \"../api/endpoint.js\";\nimport Bowser from \"bowser\";\nimport cx from \"clsx\";\nimport {\n  WIDGETS_CLASS_NAMESPACE,\n  WIDGETS_DATA_ATTRIBUTE_NAMESPACE,\n} from \"./constants.js\";\nimport type { Elements } from \"./elements.js\";\n\nexport const canUseDOM = !!(\n  typeof window !== \"undefined\" &&\n  window.document &&\n  window.document.createElement\n);\n\nexport function getBestName({\n  firstName,\n  lastName,\n}: Pick<Member, \"firstName\" | \"lastName\">) {\n  return [firstName, lastName].filter(Boolean).join(\" \") || null;\n}\n\nexport function getComparativeReadableDate(\n  now: Date,\n  then: Date,\n  options: { locale: string; timeZone?: string },\n): string {\n  const locale = options.locale ?? \"en-US\";\n  const timeSince = now.getTime() - then.getTime();\n  const formatter = new Intl.RelativeTimeFormat(locale, { numeric: \"auto\" });\n\n  // Has it been less than a minute?\n  if (timeSince < 60_000) {\n    return formatter.format(0, \"second\"); // Returns \"now\" / \"maintenant\" / \"jetzt\" etc.\n  }\n\n  // Has it been less than an hour?\n  if (timeSince < 3_600_000) {\n    const timePassed = Math.floor(timeSince / 60_000);\n    return formatter.format(-timePassed, \"minute\");\n  }\n\n  // Has it been less than a day?\n  if (timeSince < 86_400_000) {\n    const timePassed = Math.floor(timeSince / 3_600_000);\n    return formatter.format(-timePassed, \"hour\");\n  }\n\n  // Has it been less than a week?\n  if (timeSince < 604_800_000) {\n    const timePassed = Math.floor(timeSince / 86_400_000);\n    return formatter.format(-timePassed, \"day\");\n  }\n\n  // Has it been less than a month?\n  if (timeSince < 2_592_000_000) {\n    const timePassed = Math.floor(timeSince / 604_800_000);\n    return formatter.format(-timePassed, \"week\");\n  }\n\n  // Any later?\n  return then.toLocaleDateString(locale, {\n    timeZone: options.timeZone,\n    month: \"long\",\n    day: \"numeric\",\n    // omit year if it's the same as the current year\n    year: now.getFullYear() !== then.getFullYear() ? \"numeric\" : undefined,\n  });\n}\n\nexport function isObjectLike(value: unknown): value is Record<string, unknown> {\n  return typeof value === \"object\" && value !== null;\n}\n\nexport function isErrorLike(\n  value: unknown,\n): value is Record<string, unknown> & { message: string } {\n  return isObjectLike(value) && typeof value.message === \"string\";\n}\n\nexport function isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n  return (\n    typeof value === \"object\" &&\n    value !== null &&\n    \"then\" in value &&\n    typeof value.then === \"function\"\n  );\n}\n\nexport async function parseErrorResponse(\n  response: Response,\n): Promise<{ message: string; status: number }> {\n  try {\n    const json = await response.json();\n    if (!isObjectLike(json) || typeof json.message !== \"string\") {\n      return {\n        status: response.status,\n        message: response.statusText,\n      };\n    }\n    return {\n      ...json,\n      status: response.status,\n      message: json.message || response.statusText,\n    };\n  } catch {\n    return {\n      status: response.status,\n      message: response.statusText,\n    };\n  }\n}\n\nexport function namespaceClassNames(\n  ...classNames: (string | undefined | null | boolean)[]\n): string {\n  return classNames\n    .filter(Boolean)\n    .map((className) => `${WIDGETS_CLASS_NAMESPACE}-${className}`)\n    .join(\" \");\n}\n\ninterface CommonDomArgs {\n  className?: string | undefined | null;\n  dataAttributes?: Record<string, string | boolean | undefined | null>;\n}\n\ninterface WidgetRootDomArgs {\n  isWidgetRoot: true;\n  widgetId: string;\n  elementId?: never;\n  widgetState: WidgetRootState;\n}\n\ninterface WidgetElementDomArgs {\n  isWidgetRoot?: false;\n  widgetId?: string;\n  elementId?: keyof Elements;\n  widgetState?: never;\n  className: string | undefined | null;\n}\n\ntype DomArgs = CommonDomArgs & (WidgetRootDomArgs | WidgetElementDomArgs);\n\ntype DataAttributeProps = {\n  [K in `data-${string}`]?: string | boolean;\n};\n\ntype DomProps = {\n  className: string | undefined;\n} & DataAttributeProps;\n\nexport function getDomProps(args: DomArgs) {\n  let {\n    className,\n    dataAttributes,\n    isWidgetRoot = false,\n    widgetId,\n    elementId,\n    widgetState,\n    ...passthroughProps\n  } = args;\n\n  const elementClassName = elementId\n    ? (() => {\n        // TODO: Overrides for specific elements are mostly here for backwards\n        // compatibility. They should be removed in the next major version.\n        if (\n          elementId === \"primaryButton\" ||\n          elementId === \"secondaryButton\" ||\n          elementId === \"destructiveButton\"\n        ) {\n          return namespaceClassNames(\"button\");\n        }\n\n        if (elementId === \"iconButton\") {\n          return namespaceClassNames(\"button\", \"icon-button\");\n        }\n\n        if (\n          elementId === \"primaryMenuItem\" ||\n          elementId === \"destructiveMenuItem\"\n        ) {\n          return namespaceClassNames(\"menu-item\");\n        }\n\n        if (elementId === \"textfield\") {\n          return namespaceClassNames(elementId, \"text-field\");\n        }\n\n        return namespaceClassNames(fastKebabCase(elementId));\n      })()\n    : undefined;\n\n  const props: DomProps = {\n    ...passthroughProps,\n    className: cx(\n      className,\n      isWidgetRoot && namespaceClassNames(\"widget\"),\n      elementClassName,\n    ),\n  };\n\n  if (widgetId) {\n    props[`data-${WIDGETS_DATA_ATTRIBUTE_NAMESPACE}-widget-id`] = widgetId;\n  }\n\n  if (isWidgetRoot) {\n    widgetState ??= \"resolved\";\n    props[`data-${WIDGETS_DATA_ATTRIBUTE_NAMESPACE}-widget`] = true;\n    props[`data-${WIDGETS_DATA_ATTRIBUTE_NAMESPACE}-widget-state`] =\n      widgetState;\n  }\n\n  if (dataAttributes) {\n    for (const [key, value] of Object.entries(dataAttributes)) {\n      if (value != null) {\n        const parts = [widgetId, elementId, key]\n          .filter((v) => v != null)\n          .map(fastKebabCase)\n          .join(\"-\");\n        if (parts) {\n          props[`data-${WIDGETS_DATA_ATTRIBUTE_NAMESPACE}-${parts}`] = value;\n        }\n      }\n    }\n  }\n\n  if (elementId) {\n    props[`data-${WIDGETS_DATA_ATTRIBUTE_NAMESPACE}-element`] =\n      fastKebabCase(elementId);\n  }\n\n  return props;\n}\n\n/**\n * Opt for simplicity and performance over capturing edge cases. Input strings\n * are expected to only be alphabetic.\n */\nfunction fastKebabCase(alphaString: string) {\n  return alphaString.replace(/([A-Z])/g, \"-$1\").toLowerCase();\n}\n\nexport function parseUserAgent(userAgent?: string | null) {\n  const browser = Bowser.getParser(userAgent ?? \"\");\n  const browserName = browser.getBrowserName().replace(/^Mobile\\s*/i, \"\");\n  const osName = browser.getOSName();\n  const pretty = [browserName, osName].filter(Boolean).join(\" on \");\n\n  return { pretty, isMobile: browser.getPlatformType() === \"mobile\" };\n}\n\nexport function getUserLocation(\n  location?: {\n    cityName: string;\n    countryISOCode: string;\n  } | null,\n  ipAddress?: string | null,\n) {\n  if (location) {\n    return (\n      [location.cityName, location.countryISOCode].filter(Boolean).join(\", \") ||\n      \"Unknown location\"\n    );\n  }\n\n  if (ipAddress) {\n    return ipAddress;\n  }\n\n  return \"Unknown location\";\n}\n\nexport function unreachable(value: never): never {\n  throw new TypeError(`Unreachable code: ${value}`);\n}\n\nexport function pluralize(word = \"\", amount = 0, showAmount = true): string {\n  return `${showAmount ? `${amount} ` : \"\"}${word}${amount === 1 ? \"\" : \"s\"}`;\n}\n\n// Props that all widgets should accept.\nexport interface WidgetRootDomProps {\n  className?: string;\n}\n\nexport type WidgetRootState = \"loading\" | \"error\" | \"resolved\";\n\n// The API response previously returned an object with the `listMetadata`\n// property where `before` and `after` fields were not strictly required.\ntype Pagination = Nullable<{ before: string; after: string }>;\ntype DeprecatedListMetadataResponse = {\n  listMetadata: Pagination;\n};\ntype NewListMetadataResponse = {\n  list_metadata: Pagination;\n};\n\nexport function getListMetadata<\n  T extends DeprecatedListMetadataResponse | NewListMetadataResponse,\n>(responseObject: T | undefined | null): ListMetadata {\n  const listMetadata =\n    responseObject && \"listMetadata\" in responseObject\n      ? responseObject.listMetadata\n      : responseObject?.list_metadata;\n\n  return {\n    before: listMetadata?.before ?? null,\n    after: listMetadata?.after ?? null,\n  };\n}\n\nexport type Nullable<T> = {\n  [K in keyof T]?: T[K] | null;\n};\n/**\n * Opens the Admin Portal via form submission instead of URL query parameters.\n * This keeps the API token in the request body rather than the URL,\n * reducing exposure in server logs, browser history, and referrer headers.\n */\nexport function openAdminPortalViaForm(\n  link: string,\n  options: { target?: string; rel?: string } = {},\n): void {\n  let url: URL;\n  try {\n    url = new URL(link);\n  } catch {\n    throw new TypeError(\"Invalid URL\");\n  }\n\n  let { target, rel } = options;\n  const apiToken = url.searchParams.get(\"apiToken\");\n  // clear the apiToken from the URL search params to avoid exposing it to the\n  // browser console\n  url.searchParams.delete(\"apiToken\");\n\n  const formAction = url.toString();\n  const form = document.createElement(\"form\");\n  form.method = \"POST\";\n  form.action = formAction;\n  if (target) {\n    form.target = target;\n  }\n\n  if (!rel && target === \"_blank\") {\n    rel = \"noopener noreferrer\";\n  }\n\n  if (rel) {\n    form.rel = rel;\n  }\n\n  const tokenInput = document.createElement(\"input\");\n  tokenInput.type = \"hidden\";\n  tokenInput.name = \"apiToken\";\n  tokenInput.value = apiToken ?? \"\";\n  form.appendChild(tokenInput);\n\n  document.body.appendChild(form);\n  form.submit();\n  document.body.removeChild(form);\n}\n"],"mappings":"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","names":["cx","Bowser"]}