@workos-inc/widgets 1.19.0 → 1.19.1-next.1790725314460
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/CHANGELOG.md +6 -0
- package/dist/cjs/lib/admin-portal-audit-log-streaming.cjs +5 -22
- package/dist/cjs/lib/admin-portal-audit-log-streaming.cjs.map +1 -1
- package/dist/cjs/lib/api-keys/relative-time.cjs +7 -12
- package/dist/cjs/lib/api-keys/relative-time.cjs.map +1 -1
- package/dist/cjs/lib/directory-sync.cjs +5 -30
- package/dist/cjs/lib/directory-sync.cjs.map +1 -1
- package/dist/cjs/lib/domain-item.cjs +5 -10
- package/dist/cjs/lib/domain-item.cjs.map +1 -1
- package/dist/cjs/lib/user-security.cjs +9 -17
- package/dist/cjs/lib/user-security.cjs.map +1 -1
- package/dist/cjs/lib/user-sessions.cjs +9 -13
- package/dist/cjs/lib/user-sessions.cjs.map +1 -1
- package/dist/cjs/lib/user-sessions.d.cts +2 -2
- package/dist/cjs/lib/users-management.cjs +14 -24
- package/dist/cjs/lib/users-management.cjs.map +1 -1
- package/dist/cjs/lib/utils.cjs +49 -13
- package/dist/cjs/lib/utils.cjs.map +1 -1
- package/dist/cjs/use-comparative-readable-date.cjs +128 -0
- package/dist/cjs/use-comparative-readable-date.cjs.map +1 -0
- package/dist/cjs/use-comparative-readable-date.d.cts +8 -0
- package/dist/esm/lib/admin-portal-audit-log-streaming.js +5 -22
- package/dist/esm/lib/admin-portal-audit-log-streaming.js.map +1 -1
- package/dist/esm/lib/api-keys/relative-time.js +7 -12
- package/dist/esm/lib/api-keys/relative-time.js.map +1 -1
- package/dist/esm/lib/directory-sync.js +5 -30
- package/dist/esm/lib/directory-sync.js.map +1 -1
- package/dist/esm/lib/domain-item.js +5 -10
- package/dist/esm/lib/domain-item.js.map +1 -1
- package/dist/esm/lib/identity-providers.d.ts +2 -2
- package/dist/esm/lib/user-security.js +9 -18
- package/dist/esm/lib/user-security.js.map +1 -1
- package/dist/esm/lib/user-sessions.d.ts +2 -2
- package/dist/esm/lib/user-sessions.js +9 -14
- package/dist/esm/lib/user-sessions.js.map +1 -1
- package/dist/esm/lib/users-management.js +14 -25
- package/dist/esm/lib/users-management.js.map +1 -1
- package/dist/esm/lib/utils.js +49 -13
- package/dist/esm/lib/utils.js.map +1 -1
- package/dist/esm/use-comparative-readable-date.d.ts +8 -0
- package/dist/esm/use-comparative-readable-date.js +94 -0
- package/dist/esm/use-comparative-readable-date.js.map +1 -0
- package/package.json +1 -1
package/dist/esm/lib/utils.js
CHANGED
|
@@ -15,24 +15,33 @@ function getComparativeReadableDate(now, then, options) {
|
|
|
15
15
|
const locale = options.locale ?? "en-US";
|
|
16
16
|
const timeSince = now.getTime() - then.getTime();
|
|
17
17
|
const formatter = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
|
|
18
|
-
|
|
18
|
+
const elapsed = Math.abs(timeSince);
|
|
19
|
+
if (elapsed < 6e4) {
|
|
19
20
|
return formatter.format(0, "second");
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const future = timeSince < 0;
|
|
23
|
+
const direction = future ? 1 : -1;
|
|
24
|
+
if (elapsed < 36e5) {
|
|
25
|
+
const timePassed = Math.floor(elapsed / 6e4);
|
|
26
|
+
return formatter.format(direction * timePassed, "minute");
|
|
24
27
|
}
|
|
25
|
-
if (
|
|
26
|
-
const timePassed = Math.floor(
|
|
27
|
-
return formatter.format(
|
|
28
|
+
if (elapsed < 864e5) {
|
|
29
|
+
const timePassed = Math.floor(elapsed / 36e5);
|
|
30
|
+
return formatter.format(direction * timePassed, "hour");
|
|
28
31
|
}
|
|
29
|
-
if (
|
|
30
|
-
const timePassed = Math.floor(
|
|
31
|
-
return formatter.format(
|
|
32
|
+
if (elapsed < 6048e5) {
|
|
33
|
+
const timePassed = Math.floor(elapsed / 864e5);
|
|
34
|
+
return formatter.format(direction * timePassed, "day");
|
|
32
35
|
}
|
|
33
|
-
if (
|
|
34
|
-
const timePassed = Math.floor(
|
|
35
|
-
return formatter.format(
|
|
36
|
+
if (elapsed < 2592e6) {
|
|
37
|
+
const timePassed = Math.floor(elapsed / 6048e5);
|
|
38
|
+
return formatter.format(direction * timePassed, "week");
|
|
39
|
+
}
|
|
40
|
+
if (elapsed < 7776e6) {
|
|
41
|
+
const months = future ? fullCalendarMonthsBetween(now, then, options.timeZone) : fullCalendarMonthsBetween(then, now, options.timeZone);
|
|
42
|
+
if (months === 1 || months === 2 || months === 3) {
|
|
43
|
+
return formatter.format(direction * months, "month");
|
|
44
|
+
}
|
|
36
45
|
}
|
|
37
46
|
return then.toLocaleDateString(locale, {
|
|
38
47
|
timeZone: options.timeZone,
|
|
@@ -42,6 +51,33 @@ function getComparativeReadableDate(now, then, options) {
|
|
|
42
51
|
year: now.getFullYear() !== then.getFullYear() ? "numeric" : void 0
|
|
43
52
|
});
|
|
44
53
|
}
|
|
54
|
+
function fullCalendarMonthsBetween(earlier, later, timeZone) {
|
|
55
|
+
const from = getCalendarParts(earlier, timeZone);
|
|
56
|
+
const to = getCalendarParts(later, timeZone);
|
|
57
|
+
const months = (to.year - from.year) * 12 + (to.month - from.month);
|
|
58
|
+
return to.day < from.day ? months - 1 : months;
|
|
59
|
+
}
|
|
60
|
+
function getCalendarParts(date, timeZone) {
|
|
61
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
62
|
+
timeZone,
|
|
63
|
+
year: "numeric",
|
|
64
|
+
month: "numeric",
|
|
65
|
+
day: "numeric"
|
|
66
|
+
}).formatToParts(date);
|
|
67
|
+
let year = 0;
|
|
68
|
+
let month = 0;
|
|
69
|
+
let day = 0;
|
|
70
|
+
for (const part of parts) {
|
|
71
|
+
if (part.type === "year") {
|
|
72
|
+
year = Number(part.value);
|
|
73
|
+
} else if (part.type === "month") {
|
|
74
|
+
month = Number(part.value);
|
|
75
|
+
} else if (part.type === "day") {
|
|
76
|
+
day = Number(part.value);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { year, month, day };
|
|
80
|
+
}
|
|
45
81
|
function isObjectLike(value) {
|
|
46
82
|
return typeof value === "object" && value !== null;
|
|
47
83
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils.ts"],"sourcesContent":["import { ListMetadata } from \"./api/types.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}: {\n firstName?: string | null;\n lastName?: string | null;\n}) {\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\n// Returns null when the device can't be determined. Sessions created through\n// the public authenticate API may have no user agent, and Bowser throws on\n// empty input rather than returning an empty result.\nexport function parseUserAgent(\n userAgent?: string | null,\n): { pretty: string; isMobile: boolean } | null {\n if (!userAgent?.trim()) {\n return null;\n }\n\n let browser: Bowser.Parser.Parser;\n try {\n browser = Bowser.getParser(userAgent);\n } catch {\n return null;\n }\n\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 if (!pretty) {\n return null;\n }\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\nexport function composeEventHandlers<E extends { defaultPrevented: boolean }>(\n originalEventHandler?: (event: E) => void,\n ourEventHandler?: (event: E) => void,\n) {\n return function handleEvent(event: E) {\n originalEventHandler?.(event);\n if (!event || !event.defaultPrevented) {\n return ourEventHandler?.(event);\n }\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/utils.ts"],"sourcesContent":["import { ListMetadata } from \"./api/types.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}: {\n firstName?: string | null;\n lastName?: string | null;\n}) {\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 // Sub-minute skew in either direction reads as \"now\". A larger gap ahead of\n // the comparison clock stays relative so a server/client discrepancy is visible.\n const elapsed = Math.abs(timeSince);\n\n if (elapsed < 60_000) {\n return formatter.format(0, \"second\"); // Returns \"now\" / \"maintenant\" / \"jetzt\" etc.\n }\n\n const future = timeSince < 0;\n const direction = future ? 1 : -1;\n\n // Has it been less than an hour?\n if (elapsed < 3_600_000) {\n const timePassed = Math.floor(elapsed / 60_000);\n return formatter.format(direction * timePassed, \"minute\");\n }\n\n // Has it been less than a day?\n if (elapsed < 86_400_000) {\n const timePassed = Math.floor(elapsed / 3_600_000);\n return formatter.format(direction * timePassed, \"hour\");\n }\n\n // Has it been less than a week?\n if (elapsed < 604_800_000) {\n const timePassed = Math.floor(elapsed / 86_400_000);\n return formatter.format(direction * timePassed, \"day\");\n }\n\n // Has it been less than a month?\n if (elapsed < 2_592_000_000) {\n const timePassed = Math.floor(elapsed / 604_800_000);\n return formatter.format(direction * timePassed, \"week\");\n }\n\n if (elapsed < 7_776_000_000) {\n const months = future\n ? fullCalendarMonthsBetween(now, then, options.timeZone)\n : fullCalendarMonthsBetween(then, now, options.timeZone);\n if (months === 1 || months === 2 || months === 3) {\n return formatter.format(direction * months, \"month\");\n }\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\nfunction fullCalendarMonthsBetween(\n earlier: Date,\n later: Date,\n timeZone?: string,\n): number {\n const from = getCalendarParts(earlier, timeZone);\n const to = getCalendarParts(later, timeZone);\n const months = (to.year - from.year) * 12 + (to.month - from.month);\n return to.day < from.day ? months - 1 : months;\n}\n\nfunction getCalendarParts(date: Date, timeZone?: string) {\n const parts = new Intl.DateTimeFormat(\"en-US\", {\n timeZone,\n year: \"numeric\",\n month: \"numeric\",\n day: \"numeric\",\n }).formatToParts(date);\n\n let year = 0;\n let month = 0;\n let day = 0;\n for (const part of parts) {\n if (part.type === \"year\") {\n year = Number(part.value);\n } else if (part.type === \"month\") {\n month = Number(part.value);\n } else if (part.type === \"day\") {\n day = Number(part.value);\n }\n }\n\n return { year, month, day };\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\n// Returns null when the device can't be determined. Sessions created through\n// the public authenticate API may have no user agent, and Bowser throws on\n// empty input rather than returning an empty result.\nexport function parseUserAgent(\n userAgent?: string | null,\n): { pretty: string; isMobile: boolean } | null {\n if (!userAgent?.trim()) {\n return null;\n }\n\n let browser: Bowser.Parser.Parser;\n try {\n browser = Bowser.getParser(userAgent);\n } catch {\n return null;\n }\n\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 if (!pretty) {\n return null;\n }\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\nexport function composeEventHandlers<E extends { defaultPrevented: boolean }>(\n originalEventHandler?: (event: E) => void,\n ourEventHandler?: (event: E) => void,\n) {\n return function handleEvent(event: E) {\n originalEventHandler?.(event);\n if (!event || !event.defaultPrevented) {\n return ourEventHandler?.(event);\n }\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useLocale } from "./lib/i18n/use-locale.js";
|
|
3
|
+
import { getComparativeReadableDate } from "./lib/utils.js";
|
|
4
|
+
const DEFAULT_TIME_ZONE = "America/Los_Angeles";
|
|
5
|
+
const TICK_MS = 6e4;
|
|
6
|
+
let clock = null;
|
|
7
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
8
|
+
let intervalId;
|
|
9
|
+
function getNow() {
|
|
10
|
+
if (clock === null) {
|
|
11
|
+
clock = /* @__PURE__ */ new Date();
|
|
12
|
+
}
|
|
13
|
+
return clock;
|
|
14
|
+
}
|
|
15
|
+
function getServerNow() {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
function emit(next) {
|
|
19
|
+
clock = next;
|
|
20
|
+
for (const listener of listeners) {
|
|
21
|
+
listener();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function startInterval() {
|
|
25
|
+
if (intervalId !== void 0) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
intervalId = window.setInterval(() => {
|
|
29
|
+
emit(/* @__PURE__ */ new Date());
|
|
30
|
+
}, TICK_MS);
|
|
31
|
+
}
|
|
32
|
+
function stopInterval() {
|
|
33
|
+
if (intervalId === void 0) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
window.clearInterval(intervalId);
|
|
37
|
+
intervalId = void 0;
|
|
38
|
+
}
|
|
39
|
+
function handleVisibilityChange() {
|
|
40
|
+
if (document.hidden) {
|
|
41
|
+
stopInterval();
|
|
42
|
+
} else {
|
|
43
|
+
emit(/* @__PURE__ */ new Date());
|
|
44
|
+
startInterval();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function subscribe(listener) {
|
|
48
|
+
if (listeners.size === 0) {
|
|
49
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
50
|
+
if (!document.hidden) {
|
|
51
|
+
startInterval();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
listeners.add(listener);
|
|
55
|
+
return () => {
|
|
56
|
+
listeners.delete(listener);
|
|
57
|
+
if (listeners.size === 0) {
|
|
58
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
59
|
+
stopInterval();
|
|
60
|
+
clock = null;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function useComparativeReadableDate({
|
|
65
|
+
date = null,
|
|
66
|
+
currentDate = null,
|
|
67
|
+
fallback,
|
|
68
|
+
timeZone
|
|
69
|
+
}) {
|
|
70
|
+
const locale = useLocale();
|
|
71
|
+
const now = React.useSyncExternalStore(subscribe, getNow, getServerNow);
|
|
72
|
+
return React.useMemo(() => {
|
|
73
|
+
if (!date) {
|
|
74
|
+
return fallback || null;
|
|
75
|
+
}
|
|
76
|
+
const comparedDate = currentDate || now;
|
|
77
|
+
if (!comparedDate) {
|
|
78
|
+
return date.toLocaleDateString(locale, {
|
|
79
|
+
month: "short",
|
|
80
|
+
day: "numeric",
|
|
81
|
+
year: "numeric",
|
|
82
|
+
timeZone: timeZone ?? DEFAULT_TIME_ZONE
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return getComparativeReadableDate(comparedDate, date, {
|
|
86
|
+
locale,
|
|
87
|
+
timeZone: timeZone ?? void 0
|
|
88
|
+
});
|
|
89
|
+
}, [date, currentDate, now, fallback, locale, timeZone]);
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
useComparativeReadableDate
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=use-comparative-readable-date.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/use-comparative-readable-date.ts"],"sourcesContent":["import * as React from \"react\";\nimport { useLocale } from \"./lib/i18n/use-locale.js\";\nimport { getComparativeReadableDate } from \"./lib/utils.js\";\n\nconst DEFAULT_TIME_ZONE = \"America/Los_Angeles\";\nconst TICK_MS = 60_000;\n\nlet clock: Date | null = null;\nconst listeners = new Set<() => void>();\nlet intervalId: number | undefined;\n\nfunction getNow(): Date {\n if (clock === null) {\n clock = new Date();\n }\n return clock;\n}\n\nfunction getServerNow(): null {\n return null;\n}\n\nfunction emit(next: Date) {\n clock = next;\n for (const listener of listeners) {\n listener();\n }\n}\n\nfunction startInterval() {\n if (intervalId !== undefined) {\n return;\n }\n intervalId = window.setInterval(() => {\n emit(new Date());\n }, TICK_MS);\n}\n\nfunction stopInterval() {\n if (intervalId === undefined) {\n return;\n }\n window.clearInterval(intervalId);\n intervalId = undefined;\n}\n\nfunction handleVisibilityChange() {\n if (document.hidden) {\n stopInterval();\n } else {\n emit(new Date());\n startInterval();\n }\n}\n\nfunction subscribe(listener: () => void) {\n if (listeners.size === 0) {\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n if (!document.hidden) {\n startInterval();\n }\n }\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0) {\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n stopInterval();\n // Drop the cached snapshot so the next mount reads a fresh time\n // during render, before paint.\n clock = null;\n }\n };\n}\n\nexport function useComparativeReadableDate({\n date = null,\n currentDate = null,\n fallback,\n timeZone,\n}: {\n date?: Date | null;\n currentDate?: Date | null;\n fallback?: string;\n timeZone?: string | null;\n}) {\n const locale = useLocale();\n const now = React.useSyncExternalStore(subscribe, getNow, getServerNow);\n\n return React.useMemo(() => {\n if (!date) {\n return fallback || null;\n }\n\n const comparedDate = currentDate || now;\n if (!comparedDate) {\n return date.toLocaleDateString(locale, {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n timeZone: timeZone ?? DEFAULT_TIME_ZONE,\n });\n }\n\n return getComparativeReadableDate(comparedDate, date, {\n locale,\n timeZone: timeZone ?? undefined,\n });\n }, [date, currentDate, now, fallback, locale, timeZone]);\n}\n"],"mappings":"AAAA,YAAY,WAAW;AACvB,SAAS,iBAAiB;AAC1B,SAAS,kCAAkC;AAE3C,MAAM,oBAAoB;AAC1B,MAAM,UAAU;AAEhB,IAAI,QAAqB;AACzB,MAAM,YAAY,oBAAI,IAAgB;AACtC,IAAI;AAEJ,SAAS,SAAe;AACtB,MAAI,UAAU,MAAM;AAClB,YAAQ,oBAAI,KAAK;AAAA,EACnB;AACA,SAAO;AACT;AAEA,SAAS,eAAqB;AAC5B,SAAO;AACT;AAEA,SAAS,KAAK,MAAY;AACxB,UAAQ;AACR,aAAW,YAAY,WAAW;AAChC,aAAS;AAAA,EACX;AACF;AAEA,SAAS,gBAAgB;AACvB,MAAI,eAAe,QAAW;AAC5B;AAAA,EACF;AACA,eAAa,OAAO,YAAY,MAAM;AACpC,SAAK,oBAAI,KAAK,CAAC;AAAA,EACjB,GAAG,OAAO;AACZ;AAEA,SAAS,eAAe;AACtB,MAAI,eAAe,QAAW;AAC5B;AAAA,EACF;AACA,SAAO,cAAc,UAAU;AAC/B,eAAa;AACf;AAEA,SAAS,yBAAyB;AAChC,MAAI,SAAS,QAAQ;AACnB,iBAAa;AAAA,EACf,OAAO;AACL,SAAK,oBAAI,KAAK,CAAC;AACf,kBAAc;AAAA,EAChB;AACF;AAEA,SAAS,UAAU,UAAsB;AACvC,MAAI,UAAU,SAAS,GAAG;AACxB,aAAS,iBAAiB,oBAAoB,sBAAsB;AACpE,QAAI,CAAC,SAAS,QAAQ;AACpB,oBAAc;AAAA,IAChB;AAAA,EACF;AACA,YAAU,IAAI,QAAQ;AAEtB,SAAO,MAAM;AACX,cAAU,OAAO,QAAQ;AACzB,QAAI,UAAU,SAAS,GAAG;AACxB,eAAS,oBAAoB,oBAAoB,sBAAsB;AACvE,mBAAa;AAGb,cAAQ;AAAA,IACV;AAAA,EACF;AACF;AAEO,SAAS,2BAA2B;AAAA,EACzC,OAAO;AAAA,EACP,cAAc;AAAA,EACd;AAAA,EACA;AACF,GAKG;AACD,QAAM,SAAS,UAAU;AACzB,QAAM,MAAM,MAAM,qBAAqB,WAAW,QAAQ,YAAY;AAEtE,SAAO,MAAM,QAAQ,MAAM;AACzB,QAAI,CAAC,MAAM;AACT,aAAO,YAAY;AAAA,IACrB;AAEA,UAAM,eAAe,eAAe;AACpC,QAAI,CAAC,cAAc;AACjB,aAAO,KAAK,mBAAmB,QAAQ;AAAA,QACrC,OAAO;AAAA,QACP,KAAK;AAAA,QACL,MAAM;AAAA,QACN,UAAU,YAAY;AAAA,MACxB,CAAC;AAAA,IACH;AAEA,WAAO,2BAA2B,cAAc,MAAM;AAAA,MACpD;AAAA,MACA,UAAU,YAAY;AAAA,IACxB,CAAC;AAAA,EACH,GAAG,CAAC,MAAM,aAAa,KAAK,UAAU,QAAQ,QAAQ,CAAC;AACzD;","names":[]}
|