@usethrottle/auth 0.2.0 → 0.2.2

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/README.md CHANGED
@@ -305,6 +305,7 @@ every top-level component renders:
305
305
  | `colorMuted` | `--throttle-color-muted` | `#eceef2` |
306
306
  | `colorMutedForeground` | `--throttle-color-muted-foreground` | `#5b6472` |
307
307
  | `colorBorder` | `--throttle-color-border` | `#e2e8f0` |
308
+ | `colorBorderControl` | `--throttle-color-border-control` | `#8b93a1` |
308
309
  | `colorDanger` | `--throttle-color-danger` | `#c2321f` |
309
310
  | `colorDangerSurface` | `--throttle-color-danger-surface` | `#fef2f2` |
310
311
  | `colorDangerBorder` | `--throttle-color-danger-border` | `#fecaca` |
@@ -595,6 +595,7 @@ var VARIABLE_PROPERTY = {
595
595
  colorMuted: "--throttle-color-muted",
596
596
  colorMutedForeground: "--throttle-color-muted-foreground",
597
597
  colorBorder: "--throttle-color-border",
598
+ colorBorderControl: "--throttle-color-border-control",
598
599
  colorDanger: "--throttle-color-danger",
599
600
  colorDangerSurface: "--throttle-color-danger-surface",
600
601
  colorDangerBorder: "--throttle-color-danger-border",
@@ -1209,6 +1210,13 @@ var THROTTLE_STYLES = `
1209
1210
  --throttle-color-muted: #eceef2;
1210
1211
  --throttle-color-muted-foreground: #5b6472;
1211
1212
  --throttle-color-border: #e2e8f0;
1213
+ /* The boundary of an INTERACTIVE control, which WCAG 1.4.11 requires to
1214
+ reach 3:1 against its adjacent colour \u2014 an input identified only by a
1215
+ 1.23:1 hairline is not identifiable at all. Decorative separators (card
1216
+ edges, table rules, dividers) keep --throttle-color-border: they do not
1217
+ identify a control, so the requirement does not apply and darkening
1218
+ them would only add noise. */
1219
+ --throttle-color-border-control: #8b93a1;
1212
1220
  --throttle-color-danger: #c2321f;
1213
1221
  --throttle-color-danger-surface: #fef2f2;
1214
1222
  --throttle-color-danger-border: #fecaca;
@@ -1261,6 +1269,7 @@ var THROTTLE_STYLES = `
1261
1269
  --throttle-color-muted: #21262d;
1262
1270
  --throttle-color-muted-foreground: #9aa4b2;
1263
1271
  --throttle-color-border: #30363d;
1272
+ --throttle-color-border-control: #5c6570;
1264
1273
  --throttle-color-danger: #ff6b58;
1265
1274
  --throttle-color-danger-surface: #2a1513;
1266
1275
  --throttle-color-danger-border: #5c2b25;
@@ -1283,6 +1292,7 @@ var THROTTLE_STYLES = `
1283
1292
  --throttle-color-muted: #21262d;
1284
1293
  --throttle-color-muted-foreground: #9aa4b2;
1285
1294
  --throttle-color-border: #30363d;
1295
+ --throttle-color-border-control: #5c6570;
1286
1296
  --throttle-color-danger: #ff6b58;
1287
1297
  --throttle-color-danger-surface: #2a1513;
1288
1298
  --throttle-color-danger-border: #5c2b25;
@@ -1330,7 +1340,7 @@ var THROTTLE_STYLES = `
1330
1340
  font-size: var(--throttle-font-size);
1331
1341
  color: var(--throttle-color-foreground);
1332
1342
  background: var(--throttle-color-background);
1333
- border: var(--throttle-border-width) solid var(--throttle-color-border);
1343
+ border: var(--throttle-border-width) solid var(--throttle-color-border-control);
1334
1344
  border-radius: var(--throttle-radius-sm);
1335
1345
  /* padding-block stays 0 so min-height alone owns the height, exactly as
1336
1346
  .throttle-button does. With 12px vertical padding the box computed to
@@ -1385,7 +1395,7 @@ var THROTTLE_STYLES = `
1385
1395
  .throttle-button:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--throttle-color-background), 0 0 0 4px var(--throttle-color-primary); }
1386
1396
  .throttle-button--primary { background: var(--throttle-color-primary); color: var(--throttle-color-primary-foreground); width: 100%; }
1387
1397
  .throttle-button--primary:hover:not(:disabled) { filter: brightness(0.92); }
1388
- .throttle-button--secondary { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); border-color: var(--throttle-color-border); }
1398
+ .throttle-button--secondary { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); border-color: var(--throttle-color-border-control); }
1389
1399
  .throttle-button--secondary:hover:not(:disabled) { background: var(--throttle-color-muted); }
1390
1400
  .throttle-button--danger { background: transparent; color: var(--throttle-color-danger); border-color: var(--throttle-color-border); }
1391
1401
  .throttle-button--danger:hover:not(:disabled) { background: color-mix(in srgb, var(--throttle-color-danger) 10%, transparent); }
@@ -1808,14 +1818,14 @@ function Modal(props) {
1808
1818
  e.preventDefault();
1809
1819
  return;
1810
1820
  }
1811
- const first = items[0];
1821
+ const first2 = items[0];
1812
1822
  const last = items[items.length - 1];
1813
- if (e.shiftKey && document.activeElement === first) {
1823
+ if (e.shiftKey && document.activeElement === first2) {
1814
1824
  e.preventDefault();
1815
1825
  last.focus();
1816
1826
  } else if (!e.shiftKey && document.activeElement === last) {
1817
1827
  e.preventDefault();
1818
- first.focus();
1828
+ first2.focus();
1819
1829
  }
1820
1830
  };
1821
1831
  document.addEventListener("keydown", onKeyDown);
@@ -2900,9 +2910,10 @@ function ProfilePanel(props) {
2900
2910
  const resource = useCustomer();
2901
2911
  const { submitting, error, notice, run } = useAction();
2902
2912
  const [form, setForm] = (0, import_react21.useState)({ firstName: "", lastName: "", phone: "", company: "", acceptsMarketing: false });
2913
+ const edited = (0, import_react21.useRef)(false);
2903
2914
  (0, import_react21.useEffect)(() => {
2904
2915
  const c = resource.data;
2905
- if (!c) return;
2916
+ if (!c || edited.current) return;
2906
2917
  setForm({
2907
2918
  firstName: c.firstName ?? "",
2908
2919
  lastName: c.lastName ?? "",
@@ -2926,6 +2937,7 @@ function ProfilePanel(props) {
2926
2937
  onSubmit: (e) => {
2927
2938
  e.preventDefault();
2928
2939
  void run(async () => {
2940
+ edited.current = false;
2929
2941
  await resource.update({
2930
2942
  firstName: form.firstName,
2931
2943
  lastName: form.lastName,
@@ -2955,7 +2967,10 @@ function ProfilePanel(props) {
2955
2967
  label: t.profile.firstName,
2956
2968
  value: form.firstName,
2957
2969
  autoComplete: "given-name",
2958
- onChange: (v) => setForm((f) => ({ ...f, firstName: v }))
2970
+ onChange: (v) => {
2971
+ edited.current = true;
2972
+ setForm((f) => ({ ...f, firstName: v }));
2973
+ }
2959
2974
  }
2960
2975
  ),
2961
2976
  /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
@@ -2964,7 +2979,10 @@ function ProfilePanel(props) {
2964
2979
  label: t.profile.lastName,
2965
2980
  value: form.lastName,
2966
2981
  autoComplete: "family-name",
2967
- onChange: (v) => setForm((f) => ({ ...f, lastName: v }))
2982
+ onChange: (v) => {
2983
+ edited.current = true;
2984
+ setForm((f) => ({ ...f, lastName: v }));
2985
+ }
2968
2986
  }
2969
2987
  ),
2970
2988
  /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
@@ -2974,7 +2992,10 @@ function ProfilePanel(props) {
2974
2992
  type: "tel",
2975
2993
  value: form.phone,
2976
2994
  autoComplete: "tel",
2977
- onChange: (v) => setForm((f) => ({ ...f, phone: v }))
2995
+ onChange: (v) => {
2996
+ edited.current = true;
2997
+ setForm((f) => ({ ...f, phone: v }));
2998
+ }
2978
2999
  }
2979
3000
  ),
2980
3001
  /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
@@ -2983,7 +3004,10 @@ function ProfilePanel(props) {
2983
3004
  label: t.profile.company,
2984
3005
  value: form.company,
2985
3006
  autoComplete: "organization",
2986
- onChange: (v) => setForm((f) => ({ ...f, company: v }))
3007
+ onChange: (v) => {
3008
+ edited.current = true;
3009
+ setForm((f) => ({ ...f, company: v }));
3010
+ }
2987
3011
  }
2988
3012
  ),
2989
3013
  /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("label", { className: "throttle-row", children: [
@@ -2992,7 +3016,10 @@ function ProfilePanel(props) {
2992
3016
  {
2993
3017
  type: "checkbox",
2994
3018
  checked: form.acceptsMarketing,
2995
- onChange: (e) => setForm((f) => ({ ...f, acceptsMarketing: e.target.checked }))
3019
+ onChange: (e) => {
3020
+ edited.current = true;
3021
+ setForm((f) => ({ ...f, acceptsMarketing: e.target.checked }));
3022
+ }
2996
3023
  }
2997
3024
  ),
2998
3025
  /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { children: t.profile.acceptsMarketing })
@@ -3433,7 +3460,15 @@ function OrderDetail(props) {
3433
3460
  const { t, locale } = useCopy(props.localization);
3434
3461
  const resource = useOrder(props.orderId);
3435
3462
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { ...a.rootProps, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { ...a.el("orderDetail.root", "throttle-panel"), children: [
3436
- props.onBack ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(Button, { variant: "link", elementKey: "orderDetail.backButton", onClick: props.onBack, children: t.common.back }) : null,
3463
+ props.onBack ? (
3464
+ // Wrapped on purpose. The panel is a column flex container, so a
3465
+ // direct child stretches to its full width however `width: auto`
3466
+ // the link variant is — the control rendered 748px wide with a
3467
+ // centred label. The wrapper takes the stretch; the button keeps
3468
+ // its own width. A CSS fix would need a compound selector, and
3469
+ // those outrank the merchant overrides in `appearance.elements`.
3470
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(Button, { variant: "link", elementKey: "orderDetail.backButton", onClick: props.onBack, children: t.common.back }) })
3471
+ ) : null,
3437
3472
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3438
3473
  PanelState,
3439
3474
  {
@@ -3575,7 +3610,15 @@ function InvoiceDetail(props) {
3575
3610
  const resource = useInvoice(props.invoiceId);
3576
3611
  const { submitting, error, run } = useAction();
3577
3612
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { ...a.rootProps, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { ...a.el("invoiceDetail.root", "throttle-panel"), children: [
3578
- props.onBack ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Button, { variant: "link", elementKey: "invoiceDetail.backButton", onClick: props.onBack, children: t.common.back }) : null,
3613
+ props.onBack ? (
3614
+ // Wrapped on purpose. The panel is a column flex container, so a
3615
+ // direct child stretches to its full width however `width: auto`
3616
+ // the link variant is — the control rendered 748px wide with a
3617
+ // centred label. The wrapper takes the stretch; the button keeps
3618
+ // its own width. A CSS fix would need a compound selector, and
3619
+ // those outrank the merchant overrides in `appearance.elements`.
3620
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Button, { variant: "link", elementKey: "invoiceDetail.backButton", onClick: props.onBack, children: t.common.back }) })
3621
+ ) : null,
3579
3622
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(PanelState, { resource, isEmpty: (i) => i === null, emptyTitle: t.invoices.empty, skeletonRows: 4, children: (invoice) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
3580
3623
  /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "throttle-row", children: [
3581
3624
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("h3", { children: invoice.number }),
@@ -3825,6 +3868,38 @@ function SubscriptionsPanel(props) {
3825
3868
 
3826
3869
  // src/react/components/account/SecurityPanel.tsx
3827
3870
  var import_react26 = require("react");
3871
+
3872
+ // src/react/components/account/device.ts
3873
+ var BROWSERS = [
3874
+ [/\bEdg(?:e|A|iOS)?\//, "Edge"],
3875
+ [/\bOPR\/|\bOpera\b/, "Opera"],
3876
+ // No leading \b: real agents ship prefixed builds (HeadlessChrome), and a
3877
+ // word boundary there sends them to the Safari rule instead.
3878
+ [/Chrome\/|CriOS\//, "Chrome"],
3879
+ [/\bFirefox\/|\bFxiOS\//, "Firefox"],
3880
+ [/\bSafari\//, "Safari"]
3881
+ ];
3882
+ var PLATFORMS = [
3883
+ [/\biPhone\b|\biPad\b|\biPod\b/, "iOS"],
3884
+ [/\bAndroid\b/, "Android"],
3885
+ [/\bMac OS X\b|\bMacintosh\b/, "macOS"],
3886
+ [/\bWindows\b/, "Windows"],
3887
+ [/\bLinux\b|\bX11\b/, "Linux"]
3888
+ ];
3889
+ function first(ua, table) {
3890
+ for (const [pattern, label] of table) if (pattern.test(ua)) return label;
3891
+ return null;
3892
+ }
3893
+ function describeDevice(userAgent) {
3894
+ const ua = (userAgent ?? "").trim();
3895
+ if (!ua) return "";
3896
+ const browser = first(ua, BROWSERS);
3897
+ if (!browser) return ua;
3898
+ const platform = first(ua, PLATFORMS);
3899
+ return platform ? `${browser} on ${platform}` : browser;
3900
+ }
3901
+
3902
+ // src/react/components/account/SecurityPanel.tsx
3828
3903
  var import_jsx_runtime40 = require("react/jsx-runtime");
3829
3904
  function SecurityPanel(props) {
3830
3905
  const a = useAppearance(props.appearance);
@@ -3905,7 +3980,7 @@ function SecurityPanel(props) {
3905
3980
  rows: sessions,
3906
3981
  columns: [
3907
3982
  { key: "device", label: t.security.columnDevice, render: (session) => /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "throttle-row", children: [
3908
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { children: session.userAgent || session.ipPrefix || session.id }),
3983
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { children: describeDevice(session.userAgent) || session.ipPrefix || session.id }),
3909
3984
  session.current ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Badge, { tone: "success", children: t.security.thisDevice }) : null
3910
3985
  ] }) },
3911
3986
  { key: "lastActive", label: t.security.lastActive, render: (session) => formatDate(session.lastUsedAt, locale) },
@@ -3927,7 +4002,7 @@ function SecurityPanel(props) {
3927
4002
  ) : (sessions ?? []).map((session) => /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { ...a.el("securityPanel.sessionItem", itemBase), children: [
3928
4003
  /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { children: [
3929
4004
  /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "throttle-row", children: [
3930
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("strong", { children: session.userAgent || session.ipPrefix || session.id }),
4005
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("strong", { children: describeDevice(session.userAgent) || session.ipPrefix || session.id }),
3931
4006
  session.current ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Badge, { tone: "success", children: t.security.thisDevice }) : null
3932
4007
  ] }),
3933
4008
  /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("p", { className: "throttle-muted", children: [