@algenium/blocks 1.5.0-rc.2 → 1.5.0-rc.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.cjs CHANGED
@@ -16,6 +16,9 @@ var PopoverPrimitive = require('@radix-ui/react-popover');
16
16
  var ScrollAreaPrimitive = require('@radix-ui/react-scroll-area');
17
17
  var dateFns = require('date-fns');
18
18
  var reactDayPicker = require('react-day-picker');
19
+ var valid = require('card-validator');
20
+
21
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
19
22
 
20
23
  function _interopNamespace(e) {
21
24
  if (e && e.__esModule) return e;
@@ -43,6 +46,7 @@ var TogglePrimitive__namespace = /*#__PURE__*/_interopNamespace(TogglePrimitive)
43
46
  var DialogPrimitive__namespace = /*#__PURE__*/_interopNamespace(DialogPrimitive);
44
47
  var PopoverPrimitive__namespace = /*#__PURE__*/_interopNamespace(PopoverPrimitive);
45
48
  var ScrollAreaPrimitive__namespace = /*#__PURE__*/_interopNamespace(ScrollAreaPrimitive);
49
+ var valid__default = /*#__PURE__*/_interopDefault(valid);
46
50
 
47
51
  var CalendarContext = React2.createContext(null);
48
52
  function useCalendarContext() {
@@ -7350,6 +7354,783 @@ function formatRelativeTime(dateStr) {
7350
7354
  if (diffD < 7) return `${diffD}d`;
7351
7355
  return date.toLocaleDateString([], { month: "short", day: "numeric" });
7352
7356
  }
7357
+ function useDebouncedValue(value, delayMs) {
7358
+ const [debounced, setDebounced] = React2.useState(value);
7359
+ React2.useEffect(() => {
7360
+ const id = window.setTimeout(() => setDebounced(value), delayMs);
7361
+ return () => window.clearTimeout(id);
7362
+ }, [value, delayMs]);
7363
+ return debounced;
7364
+ }
7365
+ function useDebouncedValueStrict(value, delayMs) {
7366
+ const [debounced, setDebounced] = React2.useState(void 0);
7367
+ React2.useEffect(() => {
7368
+ setDebounced(void 0);
7369
+ const id = window.setTimeout(() => setDebounced(value), delayMs);
7370
+ return () => window.clearTimeout(id);
7371
+ }, [value, delayMs]);
7372
+ return debounced;
7373
+ }
7374
+ function onlyDigits(s, max) {
7375
+ return s.replace(/\D/g, "").slice(0, max);
7376
+ }
7377
+ function formatPan(digits) {
7378
+ const cardInfo = valid__default.default.number(digits).card;
7379
+ const gaps = cardInfo?.gaps ?? [4, 8, 12];
7380
+ const parts = [];
7381
+ let idx = 0;
7382
+ for (const g of gaps) {
7383
+ parts.push(digits.slice(idx, g));
7384
+ idx = g;
7385
+ }
7386
+ parts.push(digits.slice(idx));
7387
+ return parts.filter((p) => p.length > 0).join(" ");
7388
+ }
7389
+ function parseExpiry(raw) {
7390
+ const d = onlyDigits(raw, 4);
7391
+ if (d.length < 4) return null;
7392
+ const mm = Number.parseInt(d.slice(0, 2), 10);
7393
+ const yy = Number.parseInt(d.slice(2, 4), 10);
7394
+ if (mm < 1 || mm > 12) return null;
7395
+ const year = yy < 100 ? 2e3 + yy : yy;
7396
+ const exp = valid__default.default.expirationDate({ month: String(mm), year: String(year) });
7397
+ if (!exp.isValid) return null;
7398
+ return { month: mm, year };
7399
+ }
7400
+ function normalizeBrand(detected) {
7401
+ if (!detected) return "";
7402
+ if (detected === "american-express") return "amex";
7403
+ return detected;
7404
+ }
7405
+ function brandAllowed(detected, accepted) {
7406
+ if (!accepted?.length) return true;
7407
+ if (!detected) return true;
7408
+ const n = normalizeBrand(detected);
7409
+ return accepted.map((a) => a.toLowerCase()).includes(n.toLowerCase());
7410
+ }
7411
+ var inputClassName = "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm";
7412
+ function CardInput({
7413
+ tokenize,
7414
+ value,
7415
+ onChange,
7416
+ acceptedBrands,
7417
+ requireHolderName = false,
7418
+ labels,
7419
+ disabled = false,
7420
+ largeText = false,
7421
+ className,
7422
+ onError
7423
+ }) {
7424
+ const baseId = React2.useId();
7425
+ const [panDigits, setPanDigits] = React2.useState("");
7426
+ const [expiryRaw, setExpiryRaw] = React2.useState("");
7427
+ const [cvc, setCvc] = React2.useState("");
7428
+ const [holderName, setHolderName] = React2.useState("");
7429
+ const [panFocused, setPanFocused] = React2.useState(false);
7430
+ const [submitting, setSubmitting] = React2.useState(false);
7431
+ const inputClass = largeText ? "text-base py-3" : "";
7432
+ const labelClass = largeText ? "text-base" : "text-sm";
7433
+ const numberValidation = valid__default.default.number(panDigits);
7434
+ const rawBrand = numberValidation.card?.type;
7435
+ const brand = normalizeBrand(rawBrand) || "unknown";
7436
+ const maxPanLen = numberValidation.card?.lengths?.slice(-1)[0] ?? 19;
7437
+ const minPanLenForType = numberValidation.card?.lengths?.[0];
7438
+ const panPastMinimumLength = minPanLenForType !== void 0 && panDigits.length >= minPanLenForType;
7439
+ const cvcSize = rawBrand === "american-express" || brand === "amex" ? 4 : 3;
7440
+ const expiryParsed = parseExpiry(expiryRaw);
7441
+ const expiryValid = expiryRaw.replace(/\D/g, "").length >= 4 && expiryParsed !== null ? valid__default.default.expirationDate({
7442
+ month: String(expiryParsed.month),
7443
+ year: String(expiryParsed.year)
7444
+ }).isValid : false;
7445
+ const cvvValid = cvc.length >= cvcSize && valid__default.default.cvv(cvc, cvcSize).isValid;
7446
+ const panComplete = numberValidation.isValid && brandAllowed(rawBrand, acceptedBrands);
7447
+ const holderOk = !requireHolderName || holderName.trim().length > 1;
7448
+ const canSubmit = panComplete && expiryValid && cvvValid && holderOk && !disabled && !value;
7449
+ const handlePanChange = (e) => {
7450
+ const d = onlyDigits(e.target.value, maxPanLen);
7451
+ setPanDigits(d);
7452
+ };
7453
+ const handleExpiryChange = (e) => {
7454
+ let d = onlyDigits(e.target.value, 4);
7455
+ if (d.length >= 2) d = `${d.slice(0, 2)}/${d.slice(2)}`;
7456
+ setExpiryRaw(d);
7457
+ };
7458
+ const runTokenize = React2.useCallback(async () => {
7459
+ if (!expiryParsed) return;
7460
+ setSubmitting(true);
7461
+ try {
7462
+ const result = await tokenize({
7463
+ pan: panDigits,
7464
+ expMonth: expiryParsed.month,
7465
+ expYear: expiryParsed.year,
7466
+ cvc,
7467
+ holderName: holderName.trim() || void 0
7468
+ });
7469
+ onChange(result);
7470
+ setPanDigits("");
7471
+ setExpiryRaw("");
7472
+ setCvc("");
7473
+ setHolderName("");
7474
+ } catch (err) {
7475
+ const msg = err instanceof Error ? err.message : labels.tokenizeFailed;
7476
+ onError?.(msg);
7477
+ } finally {
7478
+ setSubmitting(false);
7479
+ }
7480
+ }, [
7481
+ cvc,
7482
+ expiryParsed,
7483
+ holderName,
7484
+ labels.tokenizeFailed,
7485
+ onChange,
7486
+ onError,
7487
+ panDigits,
7488
+ tokenize
7489
+ ]);
7490
+ const displayPan = panFocused || panDigits.length <= 4 ? formatPan(panDigits) : `\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 ${panDigits.slice(-4)}`;
7491
+ if (value?.tokenId) {
7492
+ return /* @__PURE__ */ jsxRuntime.jsxs(
7493
+ "div",
7494
+ {
7495
+ className: cn("space-y-3 rounded-lg border bg-muted/30 p-4", className),
7496
+ children: [
7497
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
7498
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-4 w-4 text-muted-foreground" }),
7499
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: value.masked }),
7500
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground", children: [
7501
+ String(value.expMonth).padStart(2, "0"),
7502
+ "/",
7503
+ String(value.expYear).slice(-2)
7504
+ ] })
7505
+ ] }),
7506
+ value.holderName ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: value.holderName }) : null,
7507
+ /* @__PURE__ */ jsxRuntime.jsx(
7508
+ Button,
7509
+ {
7510
+ type: "button",
7511
+ variant: "outline",
7512
+ size: "sm",
7513
+ disabled,
7514
+ onClick: () => onChange(null),
7515
+ children: labels.replace
7516
+ }
7517
+ )
7518
+ ]
7519
+ }
7520
+ );
7521
+ }
7522
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-4 rounded-lg border bg-card p-4", className), children: [
7523
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
7524
+ /* @__PURE__ */ jsxRuntime.jsx(
7525
+ "label",
7526
+ {
7527
+ htmlFor: `${baseId}-pan`,
7528
+ className: cn("font-medium leading-none", labelClass),
7529
+ children: labels.number
7530
+ }
7531
+ ),
7532
+ /* @__PURE__ */ jsxRuntime.jsx(
7533
+ "input",
7534
+ {
7535
+ id: `${baseId}-pan`,
7536
+ inputMode: "numeric",
7537
+ autoComplete: "cc-number",
7538
+ value: displayPan,
7539
+ onChange: handlePanChange,
7540
+ onFocus: () => setPanFocused(true),
7541
+ onBlur: () => setPanFocused(false),
7542
+ placeholder: "4242 4242 4242 4242",
7543
+ disabled,
7544
+ className: cn(inputClassName, inputClass)
7545
+ }
7546
+ ),
7547
+ panDigits.length > 0 && (!numberValidation.isPotentiallyValid || panPastMinimumLength && !numberValidation.isValid) && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: labels.invalidNumber }),
7548
+ panDigits.length > 0 && numberValidation.isPotentiallyValid && !brandAllowed(rawBrand, acceptedBrands) && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: labels.invalidNumber })
7549
+ ] }),
7550
+ requireHolderName && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
7551
+ /* @__PURE__ */ jsxRuntime.jsx(
7552
+ "label",
7553
+ {
7554
+ htmlFor: `${baseId}-holder`,
7555
+ className: cn("font-medium leading-none", labelClass),
7556
+ children: labels.holderName
7557
+ }
7558
+ ),
7559
+ /* @__PURE__ */ jsxRuntime.jsx(
7560
+ "input",
7561
+ {
7562
+ id: `${baseId}-holder`,
7563
+ autoComplete: "cc-name",
7564
+ value: holderName,
7565
+ onChange: (e) => setHolderName(e.target.value),
7566
+ disabled,
7567
+ className: cn(inputClassName, inputClass)
7568
+ }
7569
+ )
7570
+ ] }),
7571
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [
7572
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
7573
+ /* @__PURE__ */ jsxRuntime.jsx(
7574
+ "label",
7575
+ {
7576
+ htmlFor: `${baseId}-exp`,
7577
+ className: cn("font-medium leading-none", labelClass),
7578
+ children: labels.expiry
7579
+ }
7580
+ ),
7581
+ /* @__PURE__ */ jsxRuntime.jsx(
7582
+ "input",
7583
+ {
7584
+ id: `${baseId}-exp`,
7585
+ inputMode: "numeric",
7586
+ autoComplete: "cc-exp",
7587
+ value: expiryRaw,
7588
+ onChange: handleExpiryChange,
7589
+ placeholder: "MM/YY",
7590
+ disabled,
7591
+ className: cn(inputClassName, inputClass)
7592
+ }
7593
+ ),
7594
+ expiryRaw.replace(/\D/g, "").length >= 4 && !expiryValid && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: labels.expiredCard })
7595
+ ] }),
7596
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
7597
+ /* @__PURE__ */ jsxRuntime.jsx(
7598
+ "label",
7599
+ {
7600
+ htmlFor: `${baseId}-cvc`,
7601
+ className: cn("font-medium leading-none", labelClass),
7602
+ children: labels.cvc
7603
+ }
7604
+ ),
7605
+ /* @__PURE__ */ jsxRuntime.jsx(
7606
+ "input",
7607
+ {
7608
+ id: `${baseId}-cvc`,
7609
+ inputMode: "numeric",
7610
+ autoComplete: "cc-csc",
7611
+ value: cvc,
7612
+ onChange: (e) => setCvc(onlyDigits(e.target.value, cvcSize)),
7613
+ placeholder: cvcSize === 4 ? "0000" : "000",
7614
+ disabled,
7615
+ className: cn(inputClassName, inputClass),
7616
+ maxLength: cvcSize
7617
+ }
7618
+ ),
7619
+ cvc.length > 0 && !cvvValid && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: labels.invalidCvc })
7620
+ ] })
7621
+ ] }),
7622
+ /* @__PURE__ */ jsxRuntime.jsxs(
7623
+ Button,
7624
+ {
7625
+ type: "button",
7626
+ disabled: !canSubmit || submitting,
7627
+ onClick: () => void runTokenize(),
7628
+ className: "gap-2",
7629
+ children: [
7630
+ submitting ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-4 w-4" }),
7631
+ labels.tokenize
7632
+ ]
7633
+ }
7634
+ )
7635
+ ] });
7636
+ }
7637
+ function parseInitial(value) {
7638
+ if (!value) {
7639
+ return {
7640
+ street: "",
7641
+ street2: "",
7642
+ city: "",
7643
+ state: "",
7644
+ zip: "",
7645
+ country: "US"
7646
+ };
7647
+ }
7648
+ return {
7649
+ street: value.street ?? "",
7650
+ street2: value.street2 ?? "",
7651
+ city: value.city ?? "",
7652
+ state: value.state ?? "",
7653
+ zip: value.zip ?? "",
7654
+ country: "US"
7655
+ };
7656
+ }
7657
+ function addressSig(a) {
7658
+ return JSON.stringify({
7659
+ street: a.street.trim(),
7660
+ street2: (a.street2 ?? "").trim(),
7661
+ city: a.city.trim(),
7662
+ state: a.state.trim().toUpperCase(),
7663
+ zip: a.zip.replace(/\D/g, "").slice(0, 5)
7664
+ });
7665
+ }
7666
+ function isAddressComplete(a) {
7667
+ const zip5 = a.zip.replace(/\D/g, "").slice(0, 5);
7668
+ const st = a.state.trim();
7669
+ return a.street.trim().length > 0 && a.city.trim().length > 0 && /^[A-Za-z]{2}$/.test(st) && zip5.length === 5;
7670
+ }
7671
+ var inputClassName2 = "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm";
7672
+ function USAddressInput({
7673
+ value,
7674
+ onChange,
7675
+ lookupZip,
7676
+ validateAddress,
7677
+ autocomplete,
7678
+ labels,
7679
+ disabled = false,
7680
+ largeText = false,
7681
+ className,
7682
+ onError
7683
+ }) {
7684
+ const [searchQuery, setSearchQuery] = React2.useState("");
7685
+ const [showSuggestions, setShowSuggestions] = React2.useState(false);
7686
+ const [suggestions, setSuggestions] = React2.useState([]);
7687
+ const [address, setAddress] = React2.useState(
7688
+ () => parseInitial(value)
7689
+ );
7690
+ const [sessionToken] = React2.useState(
7691
+ () => typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`
7692
+ );
7693
+ const [loadingAutocomplete, setLoadingAutocomplete] = React2.useState(false);
7694
+ const [loadingZip, setLoadingZip] = React2.useState(false);
7695
+ const [loadingValidate, setLoadingValidate] = React2.useState(false);
7696
+ const [uspsSuggestion, setUspsSuggestion] = React2.useState(
7697
+ null
7698
+ );
7699
+ const [uspsVerifiedNoChange, setUspsVerifiedNoChange] = React2.useState(false);
7700
+ const searchRef = React2.useRef(null);
7701
+ const suggestionCache = React2.useRef(/* @__PURE__ */ new Map());
7702
+ const warnedRef = React2.useRef({
7703
+ autocomplete: false,
7704
+ zip: false,
7705
+ validate: false
7706
+ });
7707
+ const lastZipLookup = React2.useRef("");
7708
+ const addressRef = React2.useRef(address);
7709
+ addressRef.current = address;
7710
+ const lastValidatedSigRef = React2.useRef("");
7711
+ const debouncedSearch = useDebouncedValue(searchQuery, 400);
7712
+ const zipDigits = address.zip.replace(/\D/g, "").slice(0, 5);
7713
+ const debouncedZip = useDebouncedValue(zipDigits, 400);
7714
+ const debouncedAddressForValidate = useDebouncedValueStrict(address, 1200);
7715
+ const inputClass = largeText ? "text-base py-3" : "";
7716
+ const labelClass = largeText ? "text-base" : "text-sm";
7717
+ React2.useEffect(() => {
7718
+ const handleClickOutside = (event) => {
7719
+ if (searchRef.current && !searchRef.current.contains(event.target)) {
7720
+ setShowSuggestions(false);
7721
+ }
7722
+ };
7723
+ document.addEventListener("mousedown", handleClickOutside);
7724
+ return () => document.removeEventListener("mousedown", handleClickOutside);
7725
+ }, []);
7726
+ const valueSyncKey = value == null ? "" : `o:${JSON.stringify(value)}`;
7727
+ React2.useEffect(() => {
7728
+ setAddress(parseInitial(value));
7729
+ }, [valueSyncKey]);
7730
+ const handleFieldChange = (field, fieldValue) => {
7731
+ setAddress((prev) => {
7732
+ const next = field === "country" ? { ...prev, country: "US" } : { ...prev, [field]: fieldValue };
7733
+ onChange(next);
7734
+ return next;
7735
+ });
7736
+ };
7737
+ React2.useEffect(() => {
7738
+ const sig = addressSig(addressRef.current);
7739
+ if (sig !== lastValidatedSigRef.current) {
7740
+ setUspsVerifiedNoChange(false);
7741
+ setUspsSuggestion(null);
7742
+ }
7743
+ }, [address]);
7744
+ const allowAutocomplete = Boolean(autocomplete);
7745
+ const allowUspsValidation = Boolean(validateAddress);
7746
+ const allowZipLookup = Boolean(lookupZip);
7747
+ React2.useEffect(() => {
7748
+ if (!allowAutocomplete || !autocomplete) return;
7749
+ const q = debouncedSearch.trim();
7750
+ if (q.length < 3) {
7751
+ setSuggestions([]);
7752
+ setShowSuggestions(false);
7753
+ setLoadingAutocomplete(false);
7754
+ return;
7755
+ }
7756
+ const cacheKey = q.toLowerCase();
7757
+ const cached = suggestionCache.current.get(cacheKey);
7758
+ if (cached) {
7759
+ setSuggestions(cached);
7760
+ setShowSuggestions(true);
7761
+ setLoadingAutocomplete(false);
7762
+ return;
7763
+ }
7764
+ const ctrl = new AbortController();
7765
+ setLoadingAutocomplete(true);
7766
+ autocomplete.search(q, sessionToken, ctrl.signal).then((list) => {
7767
+ if (ctrl.signal.aborted) return;
7768
+ suggestionCache.current.set(cacheKey, list);
7769
+ setSuggestions(list);
7770
+ setShowSuggestions(list.length > 0);
7771
+ }).catch((e) => {
7772
+ if (e.name === "AbortError") return;
7773
+ if (!warnedRef.current.autocomplete) {
7774
+ onError?.(labels.autocompleteUnavailable);
7775
+ warnedRef.current.autocomplete = true;
7776
+ }
7777
+ setSuggestions([]);
7778
+ setShowSuggestions(false);
7779
+ }).finally(() => setLoadingAutocomplete(false));
7780
+ return () => ctrl.abort();
7781
+ }, [
7782
+ debouncedSearch,
7783
+ allowAutocomplete,
7784
+ autocomplete,
7785
+ sessionToken,
7786
+ labels.autocompleteUnavailable,
7787
+ onError
7788
+ ]);
7789
+ React2.useEffect(() => {
7790
+ if (!allowZipLookup || !lookupZip) return;
7791
+ if (debouncedZip.length !== 5) return;
7792
+ if (debouncedZip === lastZipLookup.current) return;
7793
+ const ctrl = new AbortController();
7794
+ setLoadingZip(true);
7795
+ lookupZip(debouncedZip, ctrl.signal).then((data) => {
7796
+ if (ctrl.signal.aborted) return;
7797
+ lastZipLookup.current = debouncedZip;
7798
+ setAddress((prev) => {
7799
+ const cityEmpty = !prev.city.trim();
7800
+ const stateEmpty = !prev.state.trim();
7801
+ const next = { ...prev };
7802
+ if (cityEmpty && data.city) next.city = data.city;
7803
+ if (stateEmpty && data.state) next.state = data.state;
7804
+ next.country = "US";
7805
+ onChange(next);
7806
+ return next;
7807
+ });
7808
+ }).catch(() => {
7809
+ if (!warnedRef.current.zip) {
7810
+ onError?.(labels.zipLookupFailed);
7811
+ warnedRef.current.zip = true;
7812
+ }
7813
+ }).finally(() => setLoadingZip(false));
7814
+ return () => ctrl.abort();
7815
+ }, [
7816
+ debouncedZip,
7817
+ allowZipLookup,
7818
+ lookupZip,
7819
+ onChange,
7820
+ labels.zipLookupFailed,
7821
+ onError
7822
+ ]);
7823
+ React2.useEffect(() => {
7824
+ if (!allowUspsValidation || !validateAddress || disabled) return;
7825
+ if (debouncedAddressForValidate === void 0) return;
7826
+ const addr = debouncedAddressForValidate;
7827
+ if (!isAddressComplete(addr)) return;
7828
+ const requestSig = addressSig(addr);
7829
+ if (requestSig === lastValidatedSigRef.current) return;
7830
+ const ctrl = new AbortController();
7831
+ const timeoutId = window.setTimeout(() => ctrl.abort(), 8e3);
7832
+ setLoadingValidate(true);
7833
+ setUspsVerifiedNoChange(false);
7834
+ setUspsSuggestion(null);
7835
+ validateAddress(addr, ctrl.signal).then((result) => {
7836
+ if (ctrl.signal.aborted) return;
7837
+ if (addressSig(addressRef.current) !== requestSig) return;
7838
+ lastValidatedSigRef.current = requestSig;
7839
+ const std = result.standardized;
7840
+ if (result.changed) {
7841
+ setUspsSuggestion(std);
7842
+ setUspsVerifiedNoChange(false);
7843
+ } else {
7844
+ setUspsSuggestion(null);
7845
+ setUspsVerifiedNoChange(true);
7846
+ }
7847
+ }).catch(() => {
7848
+ if (ctrl.signal.aborted) return;
7849
+ if (!warnedRef.current.validate) {
7850
+ onError?.(labels.uspsUnavailable);
7851
+ warnedRef.current.validate = true;
7852
+ }
7853
+ }).finally(() => {
7854
+ window.clearTimeout(timeoutId);
7855
+ setLoadingValidate(false);
7856
+ });
7857
+ return () => {
7858
+ window.clearTimeout(timeoutId);
7859
+ ctrl.abort();
7860
+ };
7861
+ }, [
7862
+ debouncedAddressForValidate,
7863
+ allowUspsValidation,
7864
+ validateAddress,
7865
+ disabled,
7866
+ labels.uspsUnavailable,
7867
+ onError
7868
+ ]);
7869
+ const handleSelectSuggestion = async (row) => {
7870
+ if (!autocomplete) return;
7871
+ try {
7872
+ const next = await autocomplete.details(
7873
+ row.placeId,
7874
+ AbortSignal.timeout(8e3)
7875
+ );
7876
+ const normalized = {
7877
+ ...next,
7878
+ country: "US"
7879
+ };
7880
+ setAddress(normalized);
7881
+ onChange(normalized);
7882
+ setShowSuggestions(false);
7883
+ setSearchQuery("");
7884
+ setUspsSuggestion(null);
7885
+ lastValidatedSigRef.current = "";
7886
+ setUspsVerifiedNoChange(false);
7887
+ } catch {
7888
+ onError?.(labels.placeLookupFailed);
7889
+ }
7890
+ };
7891
+ const applyUspsSuggestion = () => {
7892
+ if (!uspsSuggestion) return;
7893
+ setAddress(uspsSuggestion);
7894
+ onChange(uspsSuggestion);
7895
+ setUspsSuggestion(null);
7896
+ lastValidatedSigRef.current = addressSig(uspsSuggestion);
7897
+ setUspsVerifiedNoChange(true);
7898
+ };
7899
+ const dismissUspsSuggestion = () => {
7900
+ setUspsSuggestion(null);
7901
+ lastValidatedSigRef.current = addressSig(addressRef.current);
7902
+ setUspsVerifiedNoChange(true);
7903
+ };
7904
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-4", className), children: [
7905
+ allowAutocomplete && autocomplete && /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: searchRef, className: "relative", children: [
7906
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
7907
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
7908
+ /* @__PURE__ */ jsxRuntime.jsx(
7909
+ "input",
7910
+ {
7911
+ value: searchQuery,
7912
+ onChange: (e) => setSearchQuery(e.target.value),
7913
+ placeholder: labels.searchPlaceholder,
7914
+ disabled,
7915
+ className: cn(inputClassName2, "pl-9 pr-9", inputClass),
7916
+ "aria-label": labels.searchPlaceholder
7917
+ }
7918
+ ),
7919
+ loadingAutocomplete && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "absolute right-10 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-muted-foreground" }),
7920
+ searchQuery ? /* @__PURE__ */ jsxRuntime.jsx(
7921
+ "button",
7922
+ {
7923
+ type: "button",
7924
+ onClick: () => setSearchQuery(""),
7925
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
7926
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" })
7927
+ }
7928
+ ) : null
7929
+ ] }),
7930
+ showSuggestions && suggestions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute z-10 mt-1 max-h-48 w-full overflow-y-auto rounded-lg border bg-background shadow-lg", children: suggestions.map((suggestion) => /* @__PURE__ */ jsxRuntime.jsxs(
7931
+ "button",
7932
+ {
7933
+ type: "button",
7934
+ onClick: () => void handleSelectSuggestion(suggestion),
7935
+ className: "w-full border-b px-4 py-3 text-left transition-colors last:border-b-0 hover:bg-muted",
7936
+ children: [
7937
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: suggestion.mainText }),
7938
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: suggestion.secondaryText })
7939
+ ]
7940
+ },
7941
+ suggestion.placeId
7942
+ )) })
7943
+ ] }),
7944
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4", children: [
7945
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
7946
+ /* @__PURE__ */ jsxRuntime.jsx(
7947
+ "label",
7948
+ {
7949
+ htmlFor: "blocks-usaddress-street",
7950
+ className: cn("font-medium leading-none", labelClass),
7951
+ children: labels.street
7952
+ }
7953
+ ),
7954
+ /* @__PURE__ */ jsxRuntime.jsx(
7955
+ "input",
7956
+ {
7957
+ id: "blocks-usaddress-street",
7958
+ value: address.street,
7959
+ onChange: (e) => handleFieldChange("street", e.target.value),
7960
+ placeholder: labels.streetPlaceholder ?? "",
7961
+ disabled,
7962
+ className: cn(inputClassName2, inputClass)
7963
+ }
7964
+ )
7965
+ ] }),
7966
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
7967
+ /* @__PURE__ */ jsxRuntime.jsxs(
7968
+ "label",
7969
+ {
7970
+ htmlFor: "blocks-usaddress-street2",
7971
+ className: cn(
7972
+ `${labelClass} text-muted-foreground`,
7973
+ "leading-none"
7974
+ ),
7975
+ children: [
7976
+ labels.street2 ?? "Address line 2",
7977
+ " ",
7978
+ labels.street2OptionalHint ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs", children: [
7979
+ "(",
7980
+ labels.street2OptionalHint,
7981
+ ")"
7982
+ ] }) : null
7983
+ ]
7984
+ }
7985
+ ),
7986
+ /* @__PURE__ */ jsxRuntime.jsx(
7987
+ "input",
7988
+ {
7989
+ id: "blocks-usaddress-street2",
7990
+ value: address.street2 || "",
7991
+ onChange: (e) => handleFieldChange("street2", e.target.value),
7992
+ placeholder: labels.street2Placeholder ?? "",
7993
+ disabled,
7994
+ className: cn(inputClassName2, inputClass)
7995
+ }
7996
+ )
7997
+ ] }),
7998
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [
7999
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
8000
+ /* @__PURE__ */ jsxRuntime.jsx(
8001
+ "label",
8002
+ {
8003
+ htmlFor: "blocks-usaddress-city",
8004
+ className: cn("font-medium leading-none", labelClass),
8005
+ children: labels.city
8006
+ }
8007
+ ),
8008
+ /* @__PURE__ */ jsxRuntime.jsx(
8009
+ "input",
8010
+ {
8011
+ id: "blocks-usaddress-city",
8012
+ value: address.city,
8013
+ onChange: (e) => handleFieldChange("city", e.target.value),
8014
+ placeholder: labels.cityPlaceholder ?? "",
8015
+ disabled,
8016
+ className: cn(inputClassName2, inputClass)
8017
+ }
8018
+ )
8019
+ ] }),
8020
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
8021
+ /* @__PURE__ */ jsxRuntime.jsx(
8022
+ "label",
8023
+ {
8024
+ htmlFor: "blocks-usaddress-state",
8025
+ className: cn("font-medium leading-none", labelClass),
8026
+ children: labels.state
8027
+ }
8028
+ ),
8029
+ /* @__PURE__ */ jsxRuntime.jsx(
8030
+ "input",
8031
+ {
8032
+ id: "blocks-usaddress-state",
8033
+ value: address.state,
8034
+ onChange: (e) => handleFieldChange("state", e.target.value),
8035
+ placeholder: labels.statePlaceholder ?? "",
8036
+ disabled,
8037
+ className: cn(inputClassName2, inputClass)
8038
+ }
8039
+ )
8040
+ ] })
8041
+ ] }),
8042
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [
8043
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
8044
+ /* @__PURE__ */ jsxRuntime.jsx(
8045
+ "label",
8046
+ {
8047
+ htmlFor: "blocks-usaddress-zip",
8048
+ className: cn("font-medium leading-none", labelClass),
8049
+ children: labels.zip
8050
+ }
8051
+ ),
8052
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
8053
+ /* @__PURE__ */ jsxRuntime.jsx(
8054
+ "input",
8055
+ {
8056
+ id: "blocks-usaddress-zip",
8057
+ value: address.zip,
8058
+ onChange: (e) => handleFieldChange(
8059
+ "zip",
8060
+ e.target.value.replace(/\D/g, "").slice(0, 10)
8061
+ ),
8062
+ placeholder: labels.zipPlaceholder ?? "",
8063
+ disabled,
8064
+ className: cn(inputClassName2, inputClass)
8065
+ }
8066
+ ),
8067
+ loadingZip && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-muted-foreground" })
8068
+ ] })
8069
+ ] }),
8070
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
8071
+ /* @__PURE__ */ jsxRuntime.jsx(
8072
+ "label",
8073
+ {
8074
+ htmlFor: "blocks-usaddress-country",
8075
+ className: cn("font-medium leading-none", labelClass),
8076
+ children: labels.countryLabel ?? "Country"
8077
+ }
8078
+ ),
8079
+ /* @__PURE__ */ jsxRuntime.jsx(
8080
+ "input",
8081
+ {
8082
+ id: "blocks-usaddress-country",
8083
+ value: "United States",
8084
+ readOnly: true,
8085
+ disabled,
8086
+ className: cn(inputClassName2, inputClass, "bg-muted/50"),
8087
+ "aria-readonly": "true"
8088
+ }
8089
+ )
8090
+ ] })
8091
+ ] })
8092
+ ] }),
8093
+ allowUspsValidation && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8094
+ /* @__PURE__ */ jsxRuntime.jsx(
8095
+ "div",
8096
+ {
8097
+ className: "flex min-h-7 items-center gap-2 text-xs text-muted-foreground",
8098
+ "aria-live": "polite",
8099
+ children: loadingValidate ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8100
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-3.5 w-3.5 shrink-0 animate-spin" }),
8101
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: labels.uspsValidating })
8102
+ ] }) : uspsVerifiedNoChange && !uspsSuggestion ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8103
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { className: "h-3.5 w-3.5 shrink-0 text-emerald-600 dark:text-emerald-400" }),
8104
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: labels.uspsVerified })
8105
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "select-none opacity-0", "aria-hidden": true, children: "\xA0" })
8106
+ }
8107
+ ),
8108
+ uspsSuggestion ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-900 dark:bg-amber-950/30", children: [
8109
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-amber-900 dark:text-amber-100", children: labels.uspsSuggested }),
8110
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: [
8111
+ uspsSuggestion.street,
8112
+ uspsSuggestion.street2,
8113
+ uspsSuggestion.city,
8114
+ uspsSuggestion.state,
8115
+ uspsSuggestion.zip
8116
+ ].filter(Boolean).join(", ") }),
8117
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
8118
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", size: "sm", onClick: applyUspsSuggestion, children: labels.uspsUseSuggestion }),
8119
+ /* @__PURE__ */ jsxRuntime.jsx(
8120
+ Button,
8121
+ {
8122
+ type: "button",
8123
+ size: "sm",
8124
+ variant: "outline",
8125
+ onClick: dismissUspsSuggestion,
8126
+ children: labels.uspsKeepMine
8127
+ }
8128
+ )
8129
+ ] })
8130
+ ] }) : null
8131
+ ] })
8132
+ ] });
8133
+ }
7353
8134
 
7354
8135
  exports.AvatarEditor = AvatarEditor;
7355
8136
  exports.AvatarEditorDialog = AvatarEditorDialog;
@@ -7359,6 +8140,7 @@ exports.CalendarContext = CalendarContext;
7359
8140
  exports.CalendarSubscribeButton = CalendarSubscribeButton;
7360
8141
  exports.CalendarView = CalendarView;
7361
8142
  exports.CalendarWidget = CalendarWidget;
8143
+ exports.CardInput = CardInput;
7362
8144
  exports.ChatRoomView = ChatRoomView;
7363
8145
  exports.ChatSidebar = ChatSidebar;
7364
8146
  exports.ChatSidebarContext = ChatSidebarContext;
@@ -7423,6 +8205,7 @@ exports.Tooltip = Tooltip;
7423
8205
  exports.TooltipContent = TooltipContent;
7424
8206
  exports.TooltipProvider = TooltipProvider;
7425
8207
  exports.TooltipTrigger = TooltipTrigger;
8208
+ exports.USAddressInput = USAddressInput;
7426
8209
  exports.UpcomingEvents = UpcomingEvents;
7427
8210
  exports.buttonVariants = buttonVariants;
7428
8211
  exports.cn = cn;
@@ -7434,6 +8217,8 @@ exports.toggleVariants = toggleVariants;
7434
8217
  exports.useCalendarContext = useCalendarContext;
7435
8218
  exports.useChatRoom = useChatRoom;
7436
8219
  exports.useChatSidebar = useChatSidebar;
8220
+ exports.useDebouncedValue = useDebouncedValue;
8221
+ exports.useDebouncedValueStrict = useDebouncedValueStrict;
7437
8222
  exports.useEnvironmentContext = useEnvironmentContext;
7438
8223
  exports.useLanguageContext = useLanguageContext;
7439
8224
  exports.useNotificationsContext = useNotificationsContext;