@photonix/basic 3.9.0 → 4.1.0

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.mjs CHANGED
@@ -6476,7 +6476,7 @@ var Switch = React233.forwardRef(
6476
6476
  Switch.displayName = "Switch";
6477
6477
 
6478
6478
  // ../ultimate/src/components/text-field/TextField.tsx
6479
- import * as React239 from "react";
6479
+ import * as React240 from "react";
6480
6480
  import { createPortal as createPortal2 } from "react-dom";
6481
6481
 
6482
6482
  // ../ultimate/src/components/divider/Divider.tsx
@@ -6517,14 +6517,616 @@ var Divider = React234.forwardRef(
6517
6517
  );
6518
6518
  Divider.displayName = "Divider";
6519
6519
 
6520
+ // ../ultimate/src/hooks/useNumberSuggestions.ts
6521
+ import * as React235 from "react";
6522
+
6523
+ // ../ultimate/src/utils/format.utils.ts
6524
+ var formatCurrency = (value, separator = ",", allowDecimal = true, maxDecimals = 2) => {
6525
+ if (!value) return "";
6526
+ const decimalChar = separator === "," ? "." : ",";
6527
+ let cleaned = value.replace(new RegExp(`[^\\d\\${decimalChar}-]`, "g"), "");
6528
+ const isNegative = cleaned.startsWith("-");
6529
+ cleaned = cleaned.replace(/-/g, "");
6530
+ const parts = cleaned.split(decimalChar);
6531
+ let integerPart = parts[0] ?? "";
6532
+ const decimalPart = parts[1];
6533
+ integerPart = integerPart.replace(/^0+(?=\d)/, "");
6534
+ if (integerPart === "") integerPart = "0";
6535
+ integerPart = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
6536
+ let result = integerPart;
6537
+ if (allowDecimal && decimalPart !== void 0) {
6538
+ result += decimalChar + decimalPart.slice(0, maxDecimals);
6539
+ }
6540
+ if (isNegative && result !== "0") {
6541
+ result = "-" + result;
6542
+ }
6543
+ return result;
6544
+ };
6545
+ var VN_CALLING_CODE = "84";
6546
+ var E164_MAX_DIGITS = 15;
6547
+ var chunkDigits = (digits, sizes) => {
6548
+ const groups = [];
6549
+ let index = 0;
6550
+ for (const size of sizes) {
6551
+ if (index >= digits.length) break;
6552
+ groups.push(digits.slice(index, index + size));
6553
+ index += size;
6554
+ }
6555
+ return groups;
6556
+ };
6557
+ var genericPhoneGroups = (length) => {
6558
+ if (length <= 5) return [length];
6559
+ const threes = Math.floor(length / 3);
6560
+ switch (length % 3) {
6561
+ case 0:
6562
+ return Array(threes).fill(3);
6563
+ case 1:
6564
+ return [...Array(threes - 1).fill(3), 4];
6565
+ default:
6566
+ return [...Array(threes - 2).fill(3), 4, 4];
6567
+ }
6568
+ };
6569
+ var groupNationalNumber = (nsn, callingCode) => {
6570
+ if (callingCode === VN_CALLING_CODE) {
6571
+ if (/^[35789]/.test(nsn)) return chunkDigits(nsn.slice(0, 9), [3, 3, 3]);
6572
+ if (/^2[48]/.test(nsn)) return chunkDigits(nsn.slice(0, 10), [2, 4, 4]);
6573
+ if (nsn.startsWith("2")) return chunkDigits(nsn.slice(0, 10), [3, 3, 4]);
6574
+ }
6575
+ const capped = nsn.slice(0, E164_MAX_DIGITS - callingCode.length);
6576
+ return chunkDigits(capped, genericPhoneGroups(capped.length));
6577
+ };
6578
+ var parsePhone = (value, options = {}) => {
6579
+ const trimmed = value.trim();
6580
+ let digits = trimmed.replace(/\D/g, "");
6581
+ let hasPlus = trimmed.startsWith("+");
6582
+ if (!hasPlus && digits.startsWith("00")) {
6583
+ hasPlus = true;
6584
+ digits = digits.slice(2);
6585
+ }
6586
+ const fixedCode = options.countryCode?.replace(/\D/g, "");
6587
+ if (fixedCode) {
6588
+ if (hasPlus && digits.startsWith(fixedCode)) digits = digits.slice(fixedCode.length);
6589
+ return { kind: "national", callingCode: fixedCode, nsn: digits.replace(/^0/, "") };
6590
+ }
6591
+ if (hasPlus) {
6592
+ if (!digits.startsWith(VN_CALLING_CODE)) return { kind: "unknown", digits: digits.slice(0, E164_MAX_DIGITS) };
6593
+ return { kind: "international", callingCode: VN_CALLING_CODE, nsn: digits.slice(2).replace(/^0/, "") };
6594
+ }
6595
+ if (/^1[89]00/.test(digits)) return { kind: "service", digits: digits.slice(0, 10) };
6596
+ if (digits.startsWith("0")) return { kind: "trunk", callingCode: VN_CALLING_CODE, nsn: digits.slice(1) };
6597
+ return { kind: "national", callingCode: VN_CALLING_CODE, nsn: digits };
6598
+ };
6599
+ var formatPhone = (value, options) => {
6600
+ if (!value) return "";
6601
+ const parsed = parsePhone(value, options);
6602
+ switch (parsed.kind) {
6603
+ case "unknown":
6604
+ return `+${parsed.digits}`;
6605
+ case "service":
6606
+ return chunkDigits(parsed.digits, parsed.digits.length <= 8 ? [4, 4] : [4, 3, 3]).join(" ");
6607
+ case "international":
6608
+ return [`+${parsed.callingCode}`, ...groupNationalNumber(parsed.nsn, parsed.callingCode)].join(" ");
6609
+ case "trunk": {
6610
+ const groups = groupNationalNumber(parsed.nsn, parsed.callingCode);
6611
+ return groups.length ? `0${groups.join(" ")}` : "0";
6612
+ }
6613
+ case "national":
6614
+ return groupNationalNumber(parsed.nsn, parsed.callingCode).join(" ");
6615
+ }
6616
+ };
6617
+ var normalizePhone = (value, options) => {
6618
+ if (!value) return "";
6619
+ const parsed = parsePhone(value, options);
6620
+ switch (parsed.kind) {
6621
+ case "unknown":
6622
+ return parsed.digits ? `+${parsed.digits}` : "";
6623
+ case "service":
6624
+ return parsed.digits;
6625
+ default: {
6626
+ const nsn = groupNationalNumber(parsed.nsn, parsed.callingCode).join("");
6627
+ return nsn ? `+${parsed.callingCode}${nsn}` : "";
6628
+ }
6629
+ }
6630
+ };
6631
+ var BANK_ACCOUNT_MAX_DIGITS = 19;
6632
+ var formatBankAccount = (value) => {
6633
+ if (!value) return "";
6634
+ const digits = value.replace(/\D/g, "").slice(0, BANK_ACCOUNT_MAX_DIGITS);
6635
+ const groups = digits.match(/\d{1,4}/g) ?? [];
6636
+ if (groups.length > 1 && groups[groups.length - 1].length === 1) {
6637
+ const last = groups.pop();
6638
+ groups[groups.length - 1] += last;
6639
+ }
6640
+ return groups.join(" ");
6641
+ };
6642
+ var formatCardNumber = (value) => {
6643
+ if (!value) return "";
6644
+ const digits = value.replace(/[^0-9]/g, "").slice(0, 16);
6645
+ return digits.replace(/(\d{4})(?=\d)/g, "$1 ").trim();
6646
+ };
6647
+ var calculateCursorPosition = (prevValue, nextValue, prevCursorPos) => {
6648
+ if (prevCursorPos <= 0) return 0;
6649
+ const rawBefore = prevValue.slice(0, prevCursorPos).replace(/[^0-9]/g, "");
6650
+ const rawDigitCount = rawBefore.length;
6651
+ let digitsSeen = 0;
6652
+ for (let i = 0; i < nextValue.length; i++) {
6653
+ if (/\d/.test(nextValue[i])) {
6654
+ digitsSeen++;
6655
+ }
6656
+ if (digitsSeen === rawDigitCount) {
6657
+ return i + 1;
6658
+ }
6659
+ }
6660
+ return nextValue.length;
6661
+ };
6662
+
6663
+ // ../ultimate/src/utils/number-detect.utils.ts
6664
+ var NUMBER_SUGGESTION_THRESHOLD = 0.4;
6665
+ var CONFIDENT_SCORE = 0.7;
6666
+ var FALLBACK_CUTOFF = 0.5;
6667
+ var ACCOUNT_LABELS = [
6668
+ "stk",
6669
+ "so tk",
6670
+ "so tai khoan",
6671
+ "tai khoan",
6672
+ "tk",
6673
+ "tknh",
6674
+ "account",
6675
+ "acct",
6676
+ "acc",
6677
+ "a/c",
6678
+ "ck",
6679
+ "chuyen khoan",
6680
+ "chuyen tien",
6681
+ "chuyen vao",
6682
+ "nhan tien",
6683
+ "thu huong"
6684
+ ];
6685
+ var BANK_NAMES = [
6686
+ "vietcombank",
6687
+ "vcb",
6688
+ "techcombank",
6689
+ "tcb",
6690
+ "mbbank",
6691
+ "mb bank",
6692
+ "mb",
6693
+ "bidv",
6694
+ "vietinbank",
6695
+ "vietin",
6696
+ "ctg",
6697
+ "agribank",
6698
+ "acb",
6699
+ "vpbank",
6700
+ "vpb",
6701
+ "tpbank",
6702
+ "tpb",
6703
+ "sacombank",
6704
+ "stb",
6705
+ "hdbank",
6706
+ "hdb",
6707
+ "vib",
6708
+ "shb",
6709
+ "msb",
6710
+ "ocb",
6711
+ "seabank",
6712
+ "eximbank",
6713
+ "eib",
6714
+ "lpbank",
6715
+ "lienvietpostbank",
6716
+ "namabank",
6717
+ "nam a bank",
6718
+ "abbank",
6719
+ "pvcombank",
6720
+ "baovietbank",
6721
+ "bac a bank",
6722
+ "bacabank",
6723
+ "kienlongbank",
6724
+ "vietbank",
6725
+ "vietabank",
6726
+ "saigonbank",
6727
+ "scb",
6728
+ "pgbank",
6729
+ "ncb",
6730
+ "gpbank",
6731
+ "oceanbank",
6732
+ "cbbank",
6733
+ "uob",
6734
+ "hsbc",
6735
+ "shinhan",
6736
+ "woori",
6737
+ "citibank",
6738
+ "cake",
6739
+ "timo",
6740
+ "ubank",
6741
+ "ngan hang",
6742
+ "bank"
6743
+ ];
6744
+ var PHONE_LABELS = [
6745
+ "sdt",
6746
+ "so dt",
6747
+ "so dien thoai",
6748
+ "dien thoai",
6749
+ "dt",
6750
+ "dd",
6751
+ "di dong",
6752
+ "phone",
6753
+ "tel",
6754
+ "mobile",
6755
+ "hotline",
6756
+ "zalo",
6757
+ "viber",
6758
+ "whatsapp",
6759
+ "telegram",
6760
+ "sms",
6761
+ "lien he",
6762
+ "lh",
6763
+ "call",
6764
+ "nhan tin"
6765
+ ];
6766
+ var MONEY_MARKERS = [
6767
+ "d",
6768
+ "vnd",
6769
+ "dong",
6770
+ "k",
6771
+ "tr",
6772
+ "trieu",
6773
+ "ty",
6774
+ "nghin",
6775
+ "ngan",
6776
+ "usd",
6777
+ "dola",
6778
+ "xu",
6779
+ "%"
6780
+ ];
6781
+ var isDigit = (c) => c !== void 0 && c >= "0" && c <= "9";
6782
+ var isLetter = (c) => c !== void 0 && /\p{L}/u.test(c);
6783
+ var isAlnum = (c) => isDigit(c) || isLetter(c);
6784
+ var isGapChar = (c) => c === " " || c === "." || c === "-" || c === "\xA0" || c === "(" || c === ")";
6785
+ var foldChar = (c) => {
6786
+ if (c === "\u0111" || c === "\u0110" || c === "\u20AB") return "d";
6787
+ const base = c.normalize("NFD")[0] ?? c;
6788
+ return base.toLowerCase();
6789
+ };
6790
+ var fold = (text) => {
6791
+ let out = "";
6792
+ for (let i = 0; i < text.length; i++) out += foldChar(text[i]);
6793
+ return out;
6794
+ };
6795
+ var readRuns = (text) => {
6796
+ const runs = [];
6797
+ let i = 0;
6798
+ while (i < text.length) {
6799
+ let j = i;
6800
+ if (text[j] === "(") j++;
6801
+ const plus = text[j] === "+";
6802
+ if (plus) j++;
6803
+ if (!isDigit(text[j]) || j === i && !isDigit(text[i])) {
6804
+ i++;
6805
+ continue;
6806
+ }
6807
+ const start = i;
6808
+ const glued = isAlnum(text[start - 1]) || text[start - 1] === "+";
6809
+ const groups = [];
6810
+ let gap = "";
6811
+ let k = j;
6812
+ for (; ; ) {
6813
+ const groupStart = k;
6814
+ while (isDigit(text[k])) k++;
6815
+ groups.push({ digits: text.slice(groupStart, k), start: groupStart, end: k, gap });
6816
+ let g = k;
6817
+ while (g < text.length && g - k < 2 && isGapChar(text[g])) g++;
6818
+ if (g > k && isDigit(text[g])) {
6819
+ gap = text.slice(k, g);
6820
+ k = g;
6821
+ continue;
6822
+ }
6823
+ break;
6824
+ }
6825
+ i = k;
6826
+ if (glued || isLetter(text[k])) continue;
6827
+ runs.push({ plus, start, groups });
6828
+ }
6829
+ return runs;
6830
+ };
6831
+ var phoneShape = (digits, plus) => {
6832
+ let international = plus;
6833
+ let d = digits;
6834
+ if (!plus && d.startsWith("0084")) {
6835
+ international = true;
6836
+ d = d.slice(2);
6837
+ }
6838
+ if (international) {
6839
+ if (d.startsWith("84")) {
6840
+ const nsn = d.slice(2).replace(/^0/, "");
6841
+ return /^[35789]\d{8}$/.test(nsn) || /^2\d{9}$/.test(nsn) ? "strong" : null;
6842
+ }
6843
+ return d.length >= 8 && d.length <= 15 ? "weak" : null;
6844
+ }
6845
+ if (/^0[35789]\d{8}$/.test(d) || /^02\d{9}$/.test(d) || /^1[89]00(\d{4}|\d{6})$/.test(d)) return "strong";
6846
+ if (/^84[35789]\d{8}$/.test(d)) return "weak";
6847
+ return null;
6848
+ };
6849
+ var isCompletePhone = (digits, plus) => plus ? /^84[35789]\d{8}$/.test(digits) : /^0[35789]\d{8}$/.test(digits) || /^02\d{9}$/.test(digits);
6850
+ var ACCOUNT_MAX_DIGITS = 19;
6851
+ var E164_MAX_DIGITS2 = 15;
6852
+ var splitRun = (run) => {
6853
+ const segments = [];
6854
+ let current = [run.groups[0]];
6855
+ let plus = run.plus;
6856
+ let start = run.start;
6857
+ const close = () => {
6858
+ segments.push({
6859
+ plus,
6860
+ start,
6861
+ end: current[current.length - 1].end,
6862
+ groups: current,
6863
+ digits: current.map((g) => g.digits).join("")
6864
+ });
6865
+ };
6866
+ for (const group of run.groups.slice(1)) {
6867
+ const digits = current.map((g) => g.digits).join("");
6868
+ const max = plus ? E164_MAX_DIGITS2 : ACCOUNT_MAX_DIGITS;
6869
+ const breaks = digits.length >= 8 && group.digits.length >= 6 || isCompletePhone(digits, plus) && group.digits.startsWith("0") || digits.length + group.digits.length > max;
6870
+ if (breaks) {
6871
+ close();
6872
+ current = [group];
6873
+ plus = false;
6874
+ start = group.gap.endsWith("(") ? group.start - 1 : group.start;
6875
+ } else {
6876
+ current.push(group);
6877
+ }
6878
+ }
6879
+ close();
6880
+ return segments;
6881
+ };
6882
+ var looksLikeAmount = (segment) => {
6883
+ const [first, ...rest] = segment.groups;
6884
+ if (!first || rest.length === 0) return false;
6885
+ if (first.digits.length > 3 || first.digits.startsWith("0")) return false;
6886
+ return rest.every((g) => g.digits.length === 3 && g.gap === ".");
6887
+ };
6888
+ var startsWithWord = (folded, at, words) => words.some((w) => folded.startsWith(w, at) && !isAlnum(folded[at + w.length]));
6889
+ var isMoney = (text, folded, segment) => {
6890
+ if (looksLikeAmount(segment)) return true;
6891
+ if (segment.plus || segment.digits.startsWith("0")) return false;
6892
+ let before = segment.start - 1;
6893
+ while (before >= 0 && folded[before] === " ") before--;
6894
+ if (folded[before] === "$") return true;
6895
+ let after = segment.end;
6896
+ if ((folded[after] === "," || folded[after] === ".") && isDigit(folded[after + 1])) {
6897
+ after++;
6898
+ while (isDigit(folded[after])) after++;
6899
+ }
6900
+ while (after < folded.length && folded[after] === " ") after++;
6901
+ if (text[after] === "$") return true;
6902
+ return startsWithWord(folded, after, MONEY_MARKERS);
6903
+ };
6904
+ var BEFORE_WINDOW = 40;
6905
+ var AFTER_WINDOW = 28;
6906
+ var lastWordEnd = (folded, from, to, words) => {
6907
+ let best = -1;
6908
+ for (const w of words) {
6909
+ let at = folded.indexOf(w, from);
6910
+ while (at !== -1 && at + w.length <= to) {
6911
+ const end = at + w.length;
6912
+ if (!isAlnum(folded[at - 1]) && !isAlnum(folded[end]) && end > best) best = end;
6913
+ at = folded.indexOf(w, at + 1);
6914
+ }
6915
+ }
6916
+ return best;
6917
+ };
6918
+ var firstWordStart = (folded, from, to, words) => {
6919
+ let best = -1;
6920
+ for (const w of words) {
6921
+ const at = folded.indexOf(w, from);
6922
+ if (at === -1 || at + w.length > to) continue;
6923
+ if (!isAlnum(folded[at - 1]) && !isAlnum(folded[at + w.length]) && (best === -1 || at < best)) best = at;
6924
+ }
6925
+ return best;
6926
+ };
6927
+ var readContext = (folded, segment, prevEnd, nextStart) => {
6928
+ let lineStart = folded.lastIndexOf("\n", segment.start - 1) + 1;
6929
+ if (folded.slice(lineStart, segment.start).trim() === "" && lineStart >= 2) {
6930
+ lineStart = folded.lastIndexOf("\n", lineStart - 2) + 1;
6931
+ }
6932
+ const from = Math.max(lineStart, prevEnd, segment.start - BEFORE_WINDOW);
6933
+ const accountEnd = Math.max(
6934
+ lastWordEnd(folded, from, segment.start, ACCOUNT_LABELS),
6935
+ lastWordEnd(folded, from, segment.start, BANK_NAMES)
6936
+ );
6937
+ const phoneEnd = lastWordEnd(folded, from, segment.start, PHONE_LABELS);
6938
+ if (accountEnd !== -1 || phoneEnd !== -1) return accountEnd > phoneEnd ? "bankAccount" : "phone";
6939
+ let lineEnd = folded.indexOf("\n", segment.end);
6940
+ if (lineEnd === -1) lineEnd = folded.length;
6941
+ const to = Math.min(lineEnd, nextStart, segment.end + AFTER_WINDOW);
6942
+ return firstWordStart(folded, segment.end, to, BANK_NAMES) !== -1 ? "bankAccount" : null;
6943
+ };
6944
+ var scoreSegment = (segment, context) => {
6945
+ const { digits, plus } = segment;
6946
+ const shape = phoneShape(digits, plus);
6947
+ const scores = {};
6948
+ if (shape) {
6949
+ if (context === "phone") scores.phone = 1;
6950
+ else if (context === null) scores.phone = shape === "strong" ? 0.8 : 0.5;
6951
+ } else if (context === "phone" && digits.length >= 8 && digits.length <= E164_MAX_DIGITS2) {
6952
+ scores.phone = 0.6;
6953
+ }
6954
+ const international = plus || shape !== null && digits.startsWith("0084");
6955
+ const minAccount = context === "bankAccount" ? 6 : 8;
6956
+ if (!international && digits.length >= minAccount && digits.length <= ACCOUNT_MAX_DIGITS) {
6957
+ if (context === "bankAccount") scores.bankAccount = 1;
6958
+ else if (context === null) scores.bankAccount = shape ? 0.4 : 0.7;
6959
+ }
6960
+ return scores;
6961
+ };
6962
+ var detectNumbers = (input) => {
6963
+ if (!input) return [];
6964
+ const text = input.normalize("NFC");
6965
+ const folded = fold(text);
6966
+ const segments = readRuns(text).flatMap(splitRun);
6967
+ const result = [];
6968
+ segments.forEach((segment, index) => {
6969
+ if (isMoney(text, folded, segment)) return;
6970
+ const prevEnd = index > 0 ? segments[index - 1].end : 0;
6971
+ const nextStart = index < segments.length - 1 ? segments[index + 1].start : text.length;
6972
+ const scores = scoreSegment(segment, readContext(folded, segment, prevEnd, nextStart));
6973
+ if (scores.phone === void 0 && scores.bankAccount === void 0) return;
6974
+ const value = (segment.plus ? "+" : "") + segment.digits;
6975
+ const best = (scores.phone ?? 0) >= (scores.bankAccount ?? 0) ? "phone" : "bankAccount";
6976
+ result.push({
6977
+ value,
6978
+ formatted: best === "phone" ? formatPhone(value) : formatBankAccount(value),
6979
+ text: text.slice(segment.start, segment.end),
6980
+ start: segment.start,
6981
+ end: segment.end,
6982
+ scores
6983
+ });
6984
+ });
6985
+ return result;
6986
+ };
6987
+ var numberKey = (value, kind) => kind === "phone" ? normalizePhone(value) || value.replace(/\D/g, "") : value.replace(/\D/g, "");
6988
+ var suggestNumbers = (text, kind, options = {}) => {
6989
+ const seen = /* @__PURE__ */ new Map();
6990
+ for (const detected of detectNumbers(text)) {
6991
+ const found = { ...detected, formatted: kind === "phone" ? formatPhone(detected.value) : formatBankAccount(detected.value) };
6992
+ const score = found.scores[kind];
6993
+ if (score === void 0 || score < NUMBER_SUGGESTION_THRESHOLD) continue;
6994
+ const key = numberKey(found.value, kind);
6995
+ const known = seen.get(key);
6996
+ if (!known) seen.set(key, found);
6997
+ else if ((known.scores[kind] ?? 0) < score) seen.set(key, { ...found, start: known.start, end: known.end });
6998
+ }
6999
+ let ranked = [...seen.values()].sort(
7000
+ (a, b) => (b.scores[kind] ?? 0) - (a.scores[kind] ?? 0) || a.start - b.start
7001
+ );
7002
+ if ((ranked[0]?.scores[kind] ?? 0) >= CONFIDENT_SCORE) {
7003
+ ranked = ranked.filter((found) => (found.scores[kind] ?? 0) >= FALLBACK_CUTOFF);
7004
+ }
7005
+ return options.limit === void 0 ? ranked : ranked.slice(0, options.limit);
7006
+ };
7007
+ var isTextWithNumbers = (text) => {
7008
+ if (!text || !/\d/.test(text)) return false;
7009
+ if (/[\p{L}\n]/u.test(text)) return true;
7010
+ return detectNumbers(text).length > 1;
7011
+ };
7012
+
7013
+ // ../ultimate/src/hooks/useNumberSuggestions.ts
7014
+ var source = "";
7015
+ var listeners = /* @__PURE__ */ new Set();
7016
+ var emit = () => listeners.forEach((listener) => listener());
7017
+ var setNumberSuggestionSource = (text) => {
7018
+ if (text === source) return;
7019
+ source = text;
7020
+ emit();
7021
+ };
7022
+ var copyListenerCount = 0;
7023
+ var handleCopy = (event) => {
7024
+ const target = event.target;
7025
+ let text = "";
7026
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
7027
+ text = target.value.slice(target.selectionStart ?? 0, target.selectionEnd ?? 0);
7028
+ } else {
7029
+ text = window.getSelection()?.toString() ?? "";
7030
+ }
7031
+ if (/\d/.test(text) && suggestNumbersAny(text)) setNumberSuggestionSource(text);
7032
+ };
7033
+ var suggestNumbersAny = (text) => suggestNumbers(text, "phone", { limit: 1 }).length > 0 || suggestNumbers(text, "bankAccount", { limit: 1 }).length > 0;
7034
+ var subscribe = (listener) => {
7035
+ listeners.add(listener);
7036
+ if (typeof document !== "undefined" && copyListenerCount++ === 0) {
7037
+ document.addEventListener("copy", handleCopy, true);
7038
+ }
7039
+ return () => {
7040
+ listeners.delete(listener);
7041
+ if (typeof document !== "undefined" && --copyListenerCount === 0) {
7042
+ document.removeEventListener("copy", handleCopy, true);
7043
+ }
7044
+ };
7045
+ };
7046
+ var getSnapshot = () => source;
7047
+ var getServerSnapshot = () => "";
7048
+ var clipboardAccess = async () => {
7049
+ if (typeof navigator === "undefined" || !navigator.clipboard?.readText) return "none";
7050
+ try {
7051
+ const status = await navigator.permissions?.query({ name: "clipboard-read" });
7052
+ if (status?.state === "granted") return "granted";
7053
+ if (status?.state === "denied") return "none";
7054
+ } catch {
7055
+ }
7056
+ return "ask";
7057
+ };
7058
+ var readClipboard = async () => {
7059
+ try {
7060
+ return await navigator.clipboard.readText() ?? "";
7061
+ } catch {
7062
+ return "";
7063
+ }
7064
+ };
7065
+ var useNumberSuggestions = ({
7066
+ kind,
7067
+ value,
7068
+ onSelect,
7069
+ enabled = true,
7070
+ clipboard = true,
7071
+ limit = 5
7072
+ }) => {
7073
+ const text = React235.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
7074
+ const [canPasteFromClipboard, setCanPasteFromClipboard] = React235.useState(false);
7075
+ const onSelectRef = React235.useRef(onSelect);
7076
+ onSelectRef.current = onSelect;
7077
+ const suggestions = React235.useMemo(() => {
7078
+ if (!enabled || !text || limit <= 0) return [];
7079
+ const current = value ? numberKey(value, kind) : "";
7080
+ return suggestNumbers(text, kind).filter((found) => numberKey(found.value, kind) !== current).slice(0, limit);
7081
+ }, [enabled, text, value, kind, limit]);
7082
+ const take = React235.useCallback(
7083
+ (incoming, fill) => {
7084
+ setNumberSuggestionSource(incoming);
7085
+ const [best] = suggestNumbers(incoming, kind, { limit: 1 });
7086
+ if (fill && best) onSelectRef.current(best.value);
7087
+ },
7088
+ [kind]
7089
+ );
7090
+ const onPaste = React235.useCallback(
7091
+ (event) => {
7092
+ if (!enabled) return;
7093
+ const pasted = event.clipboardData.getData("text");
7094
+ if (!isTextWithNumbers(pasted)) return;
7095
+ event.preventDefault();
7096
+ take(pasted, true);
7097
+ },
7098
+ [enabled, take]
7099
+ );
7100
+ const onFocus = React235.useCallback(() => {
7101
+ if (!enabled || !clipboard) return;
7102
+ void clipboardAccess().then(async (access) => {
7103
+ if (access === "granted") {
7104
+ const read = await readClipboard();
7105
+ if (read && read !== source && isTextWithNumbers(read)) take(read, false);
7106
+ setCanPasteFromClipboard(false);
7107
+ } else {
7108
+ setCanPasteFromClipboard(access === "ask");
7109
+ }
7110
+ });
7111
+ }, [enabled, clipboard, take]);
7112
+ const pasteFromClipboard = React235.useCallback(async () => {
7113
+ const read = await readClipboard();
7114
+ setCanPasteFromClipboard(false);
7115
+ if (!read) return;
7116
+ if (isTextWithNumbers(read)) take(read, !value);
7117
+ else if (/\d/.test(read)) onSelectRef.current(read.trim());
7118
+ }, [take, value]);
7119
+ return { suggestions, onPaste, onFocus, canPasteFromClipboard, pasteFromClipboard };
7120
+ };
7121
+
6520
7122
  // ../ultimate/src/components/dropdown-list/DropdownList.tsx
6521
- import * as React237 from "react";
7123
+ import * as React238 from "react";
6522
7124
 
6523
7125
  // ../ultimate/src/components/search-field/SearchField.tsx
6524
- import * as React236 from "react";
7126
+ import * as React237 from "react";
6525
7127
 
6526
7128
  // ../ultimate/src/hooks/useComposedRefs.ts
6527
- import { useCallback as useCallback3 } from "react";
7129
+ import { useCallback as useCallback4 } from "react";
6528
7130
  function setRef(ref, value) {
6529
7131
  if (typeof ref === "function") {
6530
7132
  ref(value);
@@ -6533,7 +7135,7 @@ function setRef(ref, value) {
6533
7135
  }
6534
7136
  }
6535
7137
  function useComposedRefs(...refs) {
6536
- return useCallback3((node) => {
7138
+ return useCallback4((node) => {
6537
7139
  refs.forEach((ref) => setRef(ref, node));
6538
7140
  }, refs);
6539
7141
  }
@@ -6573,9 +7175,9 @@ function useClickOutside(ref, handler, enabled = true, ignoredRefs = []) {
6573
7175
  import { useContext as useContext4, useEffect as useEffect12 } from "react";
6574
7176
 
6575
7177
  // ../ultimate/src/internal/overlay/OverlayScope.tsx
6576
- import * as React235 from "react";
7178
+ import * as React236 from "react";
6577
7179
  import { jsx as jsx232 } from "react/jsx-runtime";
6578
- var OverlayDepthContext = React235.createContext(0);
7180
+ var OverlayDepthContext = React236.createContext(0);
6579
7181
 
6580
7182
  // ../ultimate/src/hooks/useEscapeKeydown.ts
6581
7183
  var layersByDocument = /* @__PURE__ */ new WeakMap();
@@ -6610,7 +7212,7 @@ function useEscapeKeydown(handler, enabled = true) {
6610
7212
  }
6611
7213
 
6612
7214
  // ../ultimate/src/hooks/usePopoverPosition.ts
6613
- import { useState as useState7, useLayoutEffect as useLayoutEffect4, useRef as useRef10 } from "react";
7215
+ import { useState as useState8, useLayoutEffect as useLayoutEffect4, useRef as useRef11 } from "react";
6614
7216
  var MOBILE_VIEWPORT_MARGIN = 16;
6615
7217
  var POPOVER_SURFACE_SELECTOR = "[data-popover-surface]";
6616
7218
  var resolvePopoverBoundary = (triggerEl, viewportWidth, viewportHeight, desktopMargin) => {
@@ -6638,11 +7240,11 @@ var resolvePopoverBoundary = (triggerEl, viewportWidth, viewportHeight, desktopM
6638
7240
  };
6639
7241
  function usePopoverPosition(triggerRef, popoverRef, isOpen, options = {}) {
6640
7242
  const { minHeight = 300, margin = 32, gap = 4, strategy = "absolute", matchTriggerWidth = false, onClose } = options;
6641
- const [position, setPosition] = useState7("bottom");
6642
- const [align, setAlign] = useState7("left");
6643
- const onCloseRef = useRef10(onClose);
7243
+ const [position, setPosition] = useState8("bottom");
7244
+ const [align, setAlign] = useState8("left");
7245
+ const onCloseRef = useRef11(onClose);
6644
7246
  onCloseRef.current = onClose;
6645
- const [coords, setCoords] = useState7({});
7247
+ const [coords, setCoords] = useState8({});
6646
7248
  useLayoutEffect4(() => {
6647
7249
  if (isOpen && triggerRef.current) {
6648
7250
  const updatePosition = () => {
@@ -6752,20 +7354,20 @@ function usePopoverPosition(triggerRef, popoverRef, isOpen, options = {}) {
6752
7354
  }
6753
7355
 
6754
7356
  // ../ultimate/src/hooks/useVirtualList.ts
6755
- import { useState as useState8, useMemo as useMemo4 } from "react";
7357
+ import { useState as useState9, useMemo as useMemo5 } from "react";
6756
7358
  function useVirtualList({
6757
7359
  itemCount,
6758
7360
  itemHeight,
6759
7361
  containerHeight,
6760
7362
  overscan = 3
6761
7363
  }) {
6762
- const [scrollTop, setScrollTop] = useState8(0);
7364
+ const [scrollTop, setScrollTop] = useState9(0);
6763
7365
  const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
6764
7366
  const endIndex = Math.min(
6765
7367
  itemCount - 1,
6766
7368
  Math.floor((scrollTop + containerHeight) / itemHeight) + overscan
6767
7369
  );
6768
- const virtualItems = useMemo4(() => {
7370
+ const virtualItems = useMemo5(() => {
6769
7371
  const items = [];
6770
7372
  for (let i = startIndex; i <= endIndex; i++) {
6771
7373
  items.push({
@@ -6828,7 +7430,7 @@ var SearchField_default = {};
6828
7430
 
6829
7431
  // ../ultimate/src/components/search-field/SearchField.tsx
6830
7432
  import { jsx as jsx233, jsxs as jsxs12 } from "react/jsx-runtime";
6831
- var SearchField = React236.forwardRef(
7433
+ var SearchField = React237.forwardRef(
6832
7434
  ({
6833
7435
  placeholder = "Search",
6834
7436
  variant = "default",
@@ -6848,16 +7450,19 @@ var SearchField = React236.forwardRef(
6848
7450
  defaultValue,
6849
7451
  fullWidth = true,
6850
7452
  width,
7453
+ bare = false,
7454
+ loading = false,
7455
+ onKeyDown,
6851
7456
  ...props
6852
7457
  }, ref) => {
6853
- const [focused, setFocused] = React236.useState(false);
7458
+ const [focused, setFocused] = React237.useState(false);
6854
7459
  const [valueState, setValueState] = useControllableState({
6855
7460
  value,
6856
7461
  defaultValue: defaultValue || "",
6857
7462
  onChange
6858
7463
  });
6859
- const inputRef = React236.useRef(null);
6860
- React236.useEffect(() => {
7464
+ const inputRef = React237.useRef(null);
7465
+ React237.useEffect(() => {
6861
7466
  if (!shortcutHint || disabled) return;
6862
7467
  const handleGlobalKeyDown = (e) => {
6863
7468
  if (!e.isComposing && !e.defaultPrevented && (e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
@@ -6868,7 +7473,7 @@ var SearchField = React236.forwardRef(
6868
7473
  window.addEventListener("keydown", handleGlobalKeyDown);
6869
7474
  return () => window.removeEventListener("keydown", handleGlobalKeyDown);
6870
7475
  }, [shortcutHint, disabled]);
6871
- React236.useImperativeHandle(ref, () => inputRef.current);
7476
+ React237.useImperativeHandle(ref, () => inputRef.current);
6872
7477
  const currentValue = valueState;
6873
7478
  const handleFocus = (e) => {
6874
7479
  setFocused(true);
@@ -6897,7 +7502,8 @@ var SearchField = React236.forwardRef(
6897
7502
  inputRef.current?.focus();
6898
7503
  };
6899
7504
  const handleKeyDown = (e) => {
6900
- if (disabled || e.nativeEvent.isComposing) return;
7505
+ onKeyDown?.(e);
7506
+ if (e.defaultPrevented || disabled || e.nativeEvent.isComposing) return;
6901
7507
  if (e.key === "Enter") {
6902
7508
  e.preventDefault();
6903
7509
  focusNextElement(e.currentTarget);
@@ -6912,6 +7518,7 @@ var SearchField = React236.forwardRef(
6912
7518
  SearchField_default[size],
6913
7519
  !fullWidth && !width && SearchField_default.widthAuto,
6914
7520
  variant === "rounded" && SearchField_default.searchFieldRounded,
7521
+ bare && SearchField_default.bare,
6915
7522
  focused && SearchField_default.searchFieldFocused,
6916
7523
  disabled && SearchField_default.searchFieldDisabled,
6917
7524
  className
@@ -6919,7 +7526,7 @@ var SearchField = React236.forwardRef(
6919
7526
  style: { ...style, width },
6920
7527
  onClick: () => !disabled && inputRef.current?.focus(),
6921
7528
  children: [
6922
- /* @__PURE__ */ jsx233("span", { className: SearchField_default.searchFieldIcon, children: /* @__PURE__ */ jsx233(SearchOutline, { size: size === "large" ? 24 : 20 }) }),
7529
+ /* @__PURE__ */ jsx233("span", { className: SearchField_default.searchFieldIcon, children: loading ? /* @__PURE__ */ jsx233(Spinner2, { size: "small" }) : /* @__PURE__ */ jsx233(SearchOutline, { size: size === "large" ? 24 : 20 }) }),
6923
7530
  /* @__PURE__ */ jsx233(
6924
7531
  "input",
6925
7532
  {
@@ -6935,6 +7542,7 @@ var SearchField = React236.forwardRef(
6935
7542
  value: currentValue,
6936
7543
  placeholder,
6937
7544
  type: "search",
7545
+ "aria-busy": loading || void 0,
6938
7546
  ...props
6939
7547
  }
6940
7548
  ),
@@ -6980,7 +7588,7 @@ function getItemId(collectionId, part, key) {
6980
7588
 
6981
7589
  // ../ultimate/src/components/dropdown-list/DropdownList.tsx
6982
7590
  import { jsx as jsx234, jsxs as jsxs13 } from "react/jsx-runtime";
6983
- var DropdownList = React237.forwardRef(
7591
+ var DropdownList = React238.forwardRef(
6984
7592
  ({
6985
7593
  items,
6986
7594
  role = "listbox",
@@ -6998,32 +7606,32 @@ var DropdownList = React237.forwardRef(
6998
7606
  className,
6999
7607
  style
7000
7608
  }, ref) => {
7001
- const typeaheadRef = React237.useRef({ text: "", time: 0 });
7002
- const [searchQuery, setSearchQuery] = React237.useState("");
7609
+ const typeaheadRef = React238.useRef({ text: "", time: 0 });
7610
+ const [searchQuery, setSearchQuery] = React238.useState("");
7003
7611
  const showSearch = role === "listbox" && items.length >= 10;
7004
- const instanceId = React237.useId();
7612
+ const instanceId = React238.useId();
7005
7613
  const itemId = (value2) => value2 === void 0 ? void 0 : getItemId(instanceId, "item", value2);
7006
- const selectedValues = React237.useMemo(() => {
7614
+ const selectedValues = React238.useMemo(() => {
7007
7615
  if (Array.isArray(value)) return value;
7008
7616
  return value ? [value] : [];
7009
7617
  }, [value]);
7010
- const filteredItems = React237.useMemo(() => {
7618
+ const filteredItems = React238.useMemo(() => {
7011
7619
  if (!searchQuery.trim()) return items;
7012
7620
  const query = searchQuery.toLowerCase();
7013
7621
  return items.filter(
7014
7622
  (item) => item.label.toLowerCase().includes(query)
7015
7623
  );
7016
7624
  }, [items, searchQuery]);
7017
- const selectableFilteredValues = React237.useMemo(() => filteredItems.filter((item) => !item.disabled).map((item) => item.value), [filteredItems]);
7625
+ const selectableFilteredValues = React238.useMemo(() => filteredItems.filter((item) => !item.disabled).map((item) => item.value), [filteredItems]);
7018
7626
  const showSelectAll = mode === "multi" && Boolean(selectAllLabel) && selectableFilteredValues.length > 0;
7019
7627
  const allFilteredSelected = showSelectAll && selectableFilteredValues.every((itemValue) => selectedValues.includes(itemValue));
7020
7628
  const someFilteredSelected = showSelectAll && !allFilteredSelected && selectableFilteredValues.some((itemValue) => selectedValues.includes(itemValue));
7021
- const handleSelectAllClick = React237.useCallback(() => {
7629
+ const handleSelectAllClick = React238.useCallback(() => {
7022
7630
  if (!showSelectAll) return;
7023
7631
  const nextValues = allFilteredSelected ? selectedValues.filter((itemValue) => !selectableFilteredValues.includes(itemValue)) : Array.from(/* @__PURE__ */ new Set([...selectedValues, ...selectableFilteredValues]));
7024
7632
  onChange?.(nextValues);
7025
7633
  }, [allFilteredSelected, onChange, selectableFilteredValues, selectedValues, showSelectAll]);
7026
- const displayItems = React237.useMemo(() => {
7634
+ const displayItems = React238.useMemo(() => {
7027
7635
  if (!showSelectAll || !selectAllLabel) return filteredItems;
7028
7636
  return [
7029
7637
  { value: "__phx_select_all__", label: selectAllLabel, __selectAll: true },
@@ -7046,10 +7654,10 @@ var DropdownList = React237.forwardRef(
7046
7654
  const isSelected = (itemValue) => {
7047
7655
  return selectedValues.includes(itemValue);
7048
7656
  };
7049
- const [showTopGradient, setShowTopGradient] = React237.useState(false);
7050
- const [showBottomGradient, setShowBottomGradient] = React237.useState(false);
7051
- const listRef = React237.useRef(null);
7052
- const handleScroll = React237.useCallback(() => {
7657
+ const [showTopGradient, setShowTopGradient] = React238.useState(false);
7658
+ const [showBottomGradient, setShowBottomGradient] = React238.useState(false);
7659
+ const listRef = React238.useRef(null);
7660
+ const handleScroll = React238.useCallback(() => {
7053
7661
  const el = listRef.current;
7054
7662
  if (!el) return;
7055
7663
  const scrollTop = el.scrollTop;
@@ -7058,7 +7666,7 @@ var DropdownList = React237.forwardRef(
7058
7666
  setShowTopGradient(scrollTop > 10);
7059
7667
  setShowBottomGradient(scrollTop + clientHeight < scrollHeight - 10);
7060
7668
  }, []);
7061
- React237.useEffect(() => {
7669
+ React238.useEffect(() => {
7062
7670
  handleScroll();
7063
7671
  }, [displayItems, handleScroll]);
7064
7672
  const ITEM_HEIGHT = size === "sm" ? 32 : 48;
@@ -7068,9 +7676,9 @@ var DropdownList = React237.forwardRef(
7068
7676
  itemHeight: ITEM_HEIGHT,
7069
7677
  containerHeight: parsedMaxHeight
7070
7678
  });
7071
- const [focusedIndex, setFocusedIndex] = React237.useState(-1);
7072
- const [isKeyboardMode, setIsKeyboardMode] = React237.useState(false);
7073
- React237.useEffect(() => {
7679
+ const [focusedIndex, setFocusedIndex] = React238.useState(-1);
7680
+ const [isKeyboardMode, setIsKeyboardMode] = React238.useState(false);
7681
+ React238.useEffect(() => {
7074
7682
  if (displayItems.length === 0) {
7075
7683
  setFocusedIndex(-1);
7076
7684
  return;
@@ -7087,7 +7695,7 @@ var DropdownList = React237.forwardRef(
7087
7695
  return prev;
7088
7696
  });
7089
7697
  }, [displayItems, value, mode]);
7090
- React237.useEffect(() => {
7698
+ React238.useEffect(() => {
7091
7699
  if (focusedIndex !== -1 && listRef.current) {
7092
7700
  const itemTop = focusedIndex * ITEM_HEIGHT;
7093
7701
  const itemBottom = itemTop + ITEM_HEIGHT;
@@ -7101,7 +7709,7 @@ var DropdownList = React237.forwardRef(
7101
7709
  }
7102
7710
  }
7103
7711
  }, [focusedIndex, ITEM_HEIGHT]);
7104
- const handleListScroll = React237.useCallback((e) => {
7712
+ const handleListScroll = React238.useCallback((e) => {
7105
7713
  handleScroll();
7106
7714
  if (isVirtual) onVirtualScroll(e);
7107
7715
  }, [handleScroll, isVirtual, onVirtualScroll]);
@@ -7257,7 +7865,7 @@ var DropdownList = React237.forwardRef(
7257
7865
  tabIndex: -1
7258
7866
  }
7259
7867
  ),
7260
- item.icon && /* @__PURE__ */ jsx234("span", { className: DropdownList_default.dropdownListItemIcon, children: React237.isValidElement(item.icon) ? React237.cloneElement(item.icon, { size: size === "sm" ? 20 : 24 }) : item.icon }),
7868
+ item.icon && /* @__PURE__ */ jsx234("span", { className: DropdownList_default.dropdownListItemIcon, children: React238.isValidElement(item.icon) ? React238.cloneElement(item.icon, { size: size === "sm" ? 20 : 24 }) : item.icon }),
7261
7869
  /* @__PURE__ */ jsx234("span", { className: DropdownList_default.dropdownListItemLabel, children: item.label }),
7262
7870
  mode === "single" && isSelected(item.value) && /* @__PURE__ */ jsx234("span", { className: DropdownList_default.dropdownListCheckmark, children: /* @__PURE__ */ jsx234(CheckSmallOutline, { size: size === "sm" ? 20 : 24 }) })
7263
7871
  ]
@@ -7304,7 +7912,7 @@ var DropdownList = React237.forwardRef(
7304
7912
  tabIndex: -1
7305
7913
  }
7306
7914
  ),
7307
- item.icon && /* @__PURE__ */ jsx234("span", { className: DropdownList_default.dropdownListItemIcon, children: React237.isValidElement(item.icon) ? React237.cloneElement(item.icon, { size: size === "sm" ? 20 : 24 }) : item.icon }),
7915
+ item.icon && /* @__PURE__ */ jsx234("span", { className: DropdownList_default.dropdownListItemIcon, children: React238.isValidElement(item.icon) ? React238.cloneElement(item.icon, { size: size === "sm" ? 20 : 24 }) : item.icon }),
7308
7916
  /* @__PURE__ */ jsx234("span", { className: DropdownList_default.dropdownListItemLabel, children: item.label }),
7309
7917
  mode === "single" && isSelected(item.value) && /* @__PURE__ */ jsx234("span", { className: DropdownList_default.dropdownListCheckmark, children: /* @__PURE__ */ jsx234(CheckSmallOutline, { size: size === "sm" ? 20 : 24 }) })
7310
7918
  ]
@@ -7329,137 +7937,11 @@ var DropdownList = React237.forwardRef(
7329
7937
  );
7330
7938
  DropdownList.displayName = "DropdownList";
7331
7939
 
7332
- // ../ultimate/src/utils/format.utils.ts
7333
- var formatCurrency = (value, separator = ",", allowDecimal = true, maxDecimals = 2) => {
7334
- if (!value) return "";
7335
- const decimalChar = separator === "," ? "." : ",";
7336
- let cleaned = value.replace(new RegExp(`[^\\d\\${decimalChar}-]`, "g"), "");
7337
- const isNegative = cleaned.startsWith("-");
7338
- cleaned = cleaned.replace(/-/g, "");
7339
- const parts = cleaned.split(decimalChar);
7340
- let integerPart = parts[0] ?? "";
7341
- const decimalPart = parts[1];
7342
- integerPart = integerPart.replace(/^0+(?=\d)/, "");
7343
- if (integerPart === "") integerPart = "0";
7344
- integerPart = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
7345
- let result = integerPart;
7346
- if (allowDecimal && decimalPart !== void 0) {
7347
- result += decimalChar + decimalPart.slice(0, maxDecimals);
7348
- }
7349
- if (isNegative && result !== "0") {
7350
- result = "-" + result;
7351
- }
7352
- return result;
7353
- };
7354
- var VN_CALLING_CODE = "84";
7355
- var E164_MAX_DIGITS = 15;
7356
- var chunkDigits = (digits, sizes) => {
7357
- const groups = [];
7358
- let index = 0;
7359
- for (const size of sizes) {
7360
- if (index >= digits.length) break;
7361
- groups.push(digits.slice(index, index + size));
7362
- index += size;
7363
- }
7364
- return groups;
7365
- };
7366
- var genericPhoneGroups = (length) => {
7367
- if (length <= 5) return [length];
7368
- const threes = Math.floor(length / 3);
7369
- switch (length % 3) {
7370
- case 0:
7371
- return Array(threes).fill(3);
7372
- case 1:
7373
- return [...Array(threes - 1).fill(3), 4];
7374
- default:
7375
- return [...Array(threes - 2).fill(3), 4, 4];
7376
- }
7377
- };
7378
- var groupNationalNumber = (nsn, callingCode) => {
7379
- if (callingCode === VN_CALLING_CODE) {
7380
- if (/^[35789]/.test(nsn)) return chunkDigits(nsn.slice(0, 9), [3, 3, 3]);
7381
- if (/^2[48]/.test(nsn)) return chunkDigits(nsn.slice(0, 10), [2, 4, 4]);
7382
- if (nsn.startsWith("2")) return chunkDigits(nsn.slice(0, 10), [3, 3, 4]);
7383
- }
7384
- const capped = nsn.slice(0, E164_MAX_DIGITS - callingCode.length);
7385
- return chunkDigits(capped, genericPhoneGroups(capped.length));
7386
- };
7387
- var parsePhone = (value, options = {}) => {
7388
- const trimmed = value.trim();
7389
- let digits = trimmed.replace(/\D/g, "");
7390
- let hasPlus = trimmed.startsWith("+");
7391
- if (!hasPlus && digits.startsWith("00")) {
7392
- hasPlus = true;
7393
- digits = digits.slice(2);
7394
- }
7395
- const fixedCode = options.countryCode?.replace(/\D/g, "");
7396
- if (fixedCode) {
7397
- if (hasPlus && digits.startsWith(fixedCode)) digits = digits.slice(fixedCode.length);
7398
- return { kind: "national", callingCode: fixedCode, nsn: digits.replace(/^0/, "") };
7399
- }
7400
- if (hasPlus) {
7401
- if (!digits.startsWith(VN_CALLING_CODE)) return { kind: "unknown", digits: digits.slice(0, E164_MAX_DIGITS) };
7402
- return { kind: "international", callingCode: VN_CALLING_CODE, nsn: digits.slice(2).replace(/^0/, "") };
7403
- }
7404
- if (/^1[89]00/.test(digits)) return { kind: "service", digits: digits.slice(0, 10) };
7405
- if (digits.startsWith("0")) return { kind: "trunk", callingCode: VN_CALLING_CODE, nsn: digits.slice(1) };
7406
- return { kind: "national", callingCode: VN_CALLING_CODE, nsn: digits };
7407
- };
7408
- var formatPhone = (value, options) => {
7409
- if (!value) return "";
7410
- const parsed = parsePhone(value, options);
7411
- switch (parsed.kind) {
7412
- case "unknown":
7413
- return `+${parsed.digits}`;
7414
- case "service":
7415
- return chunkDigits(parsed.digits, parsed.digits.length <= 8 ? [4, 4] : [4, 3, 3]).join(" ");
7416
- case "international":
7417
- return [`+${parsed.callingCode}`, ...groupNationalNumber(parsed.nsn, parsed.callingCode)].join(" ");
7418
- case "trunk": {
7419
- const groups = groupNationalNumber(parsed.nsn, parsed.callingCode);
7420
- return groups.length ? `0${groups.join(" ")}` : "0";
7421
- }
7422
- case "national":
7423
- return groupNationalNumber(parsed.nsn, parsed.callingCode).join(" ");
7424
- }
7425
- };
7426
- var BANK_ACCOUNT_MAX_DIGITS = 19;
7427
- var formatBankAccount = (value) => {
7428
- if (!value) return "";
7429
- const digits = value.replace(/\D/g, "").slice(0, BANK_ACCOUNT_MAX_DIGITS);
7430
- const groups = digits.match(/\d{1,4}/g) ?? [];
7431
- if (groups.length > 1 && groups[groups.length - 1].length === 1) {
7432
- const last = groups.pop();
7433
- groups[groups.length - 1] += last;
7434
- }
7435
- return groups.join(" ");
7436
- };
7437
- var formatCardNumber = (value) => {
7438
- if (!value) return "";
7439
- const digits = value.replace(/[^0-9]/g, "").slice(0, 16);
7440
- return digits.replace(/(\d{4})(?=\d)/g, "$1 ").trim();
7441
- };
7442
- var calculateCursorPosition = (prevValue, nextValue, prevCursorPos) => {
7443
- if (prevCursorPos <= 0) return 0;
7444
- const rawBefore = prevValue.slice(0, prevCursorPos).replace(/[^0-9]/g, "");
7445
- const rawDigitCount = rawBefore.length;
7446
- let digitsSeen = 0;
7447
- for (let i = 0; i < nextValue.length; i++) {
7448
- if (/\d/.test(nextValue[i])) {
7449
- digitsSeen++;
7450
- }
7451
- if (digitsSeen === rawDigitCount) {
7452
- return i + 1;
7453
- }
7454
- }
7455
- return nextValue.length;
7456
- };
7457
-
7458
7940
  // ../ultimate/src/components/text-field/TextField.module.css
7459
7941
  var TextField_default = {};
7460
7942
 
7461
7943
  // ../ultimate/src/internal/form/useNativeFieldReset.ts
7462
- import * as React238 from "react";
7944
+ import * as React239 from "react";
7463
7945
  function useNativeFieldReset(ref, resetState, resetValue, formId) {
7464
7946
  const reset = useCallbackRef((event) => {
7465
7947
  queueMicrotask(() => {
@@ -7468,7 +7950,7 @@ function useNativeFieldReset(ref, resetState, resetValue, formId) {
7468
7950
  ref.current.value = resetValue;
7469
7951
  });
7470
7952
  });
7471
- React238.useEffect(() => {
7953
+ React239.useEffect(() => {
7472
7954
  const form = ref.current?.form;
7473
7955
  form?.addEventListener("reset", reset);
7474
7956
  return () => form?.removeEventListener("reset", reset);
@@ -7477,7 +7959,7 @@ function useNativeFieldReset(ref, resetState, resetValue, formId) {
7477
7959
 
7478
7960
  // ../ultimate/src/components/text-field/TextField.tsx
7479
7961
  import { jsx as jsx235, jsxs as jsxs14 } from "react/jsx-runtime";
7480
- var TextField = React239.forwardRef(
7962
+ var TextField = React240.forwardRef(
7481
7963
  ({
7482
7964
  label,
7483
7965
  labelStyle = "outside",
@@ -7517,15 +7999,16 @@ var TextField = React239.forwardRef(
7517
7999
  maxLength,
7518
8000
  portal = true,
7519
8001
  selectOnFocus = false,
8002
+ smartPaste = true,
7520
8003
  ...props
7521
8004
  }, ref) => {
7522
- const generatedId = React239.useId();
8005
+ const generatedId = React240.useId();
7523
8006
  const inputId = props.id ?? generatedId;
7524
8007
  const helperId = `${inputId}-help`;
7525
- const [focused, setFocused] = React239.useState(false);
7526
- const [isDropdownOpen, setIsDropdownOpen] = React239.useState(false);
7527
- const [passwordVisible, setPasswordVisible] = React239.useState(false);
7528
- const inputRef = React239.useRef(null);
8008
+ const [focused, setFocused] = React240.useState(false);
8009
+ const [isDropdownOpen, setIsDropdownOpen] = React240.useState(false);
8010
+ const [passwordVisible, setPasswordVisible] = React240.useState(false);
8011
+ const inputRef = React240.useRef(null);
7529
8012
  const [valueState, setValueState, resetValueState] = useControllableState({
7530
8013
  value,
7531
8014
  defaultValue: defaultValue || "",
@@ -7543,8 +8026,8 @@ var TextField = React239.forwardRef(
7543
8026
  defaultValue: defaultTrailingDropdownValue || "",
7544
8027
  onChange: onTrailingDropdownChange
7545
8028
  });
7546
- const triggerWrapperRef = React239.useRef(null);
7547
- const popoverRef = React239.useRef(null);
8029
+ const triggerWrapperRef = React240.useRef(null);
8030
+ const popoverRef = React240.useRef(null);
7548
8031
  const { style: popoverStyle } = usePopoverPosition(triggerWrapperRef, popoverRef, isDropdownOpen, {
7549
8032
  margin: 8,
7550
8033
  strategy: portal ? "fixed" : "absolute",
@@ -7557,7 +8040,7 @@ var TextField = React239.forwardRef(
7557
8040
  [triggerWrapperRef]
7558
8041
  );
7559
8042
  useEscapeKeydown(() => setIsDropdownOpen(false), isDropdownOpen);
7560
- const keepSelectionOnMouseUpRef = React239.useRef(false);
8043
+ const keepSelectionOnMouseUpRef = React240.useRef(false);
7561
8044
  const handleFocus = (e) => {
7562
8045
  setFocused(true);
7563
8046
  if (selectOnFocus) e.currentTarget.select();
@@ -7574,14 +8057,14 @@ var TextField = React239.forwardRef(
7574
8057
  }
7575
8058
  props.onMouseUp?.(e);
7576
8059
  };
7577
- const isPhoneFormat = React239.useMemo(() => {
8060
+ const isPhoneFormat = React240.useMemo(() => {
7578
8061
  if (inputFormat) return false;
7579
8062
  if (!leadingText) return false;
7580
8063
  return (leadingText.startsWith("+") || /^\d+$/.test(leadingText.replace(/[\s()]/g, ""))) && !leadingText.includes("://");
7581
8064
  }, [leadingText, inputFormat]);
7582
8065
  const activeFormat = inputFormat || (isPhoneFormat ? "phone" : void 0);
7583
8066
  const isNumericFormat = activeFormat === "currency" || activeFormat === "phone" || activeFormat === "bankAccount" || activeFormat === "cardNumber";
7584
- const phoneCountryCode = React239.useMemo(() => {
8067
+ const phoneCountryCode = React240.useMemo(() => {
7585
8068
  if (activeFormat !== "phone") return void 0;
7586
8069
  if (formatOptions?.countryCode) return formatOptions.countryCode;
7587
8070
  const code = leadingText?.replace(/[\s()]/g, "");
@@ -7591,7 +8074,7 @@ var TextField = React239.forwardRef(
7591
8074
  const allowDecimal = formatOptions?.allowDecimal ?? true;
7592
8075
  const maxDigits = formatOptions?.maxDigits;
7593
8076
  const maxDecimals = formatOptions?.maxDecimals ?? 2;
7594
- const applyFormat = React239.useCallback((raw) => {
8077
+ const applyFormat = React240.useCallback((raw) => {
7595
8078
  switch (activeFormat) {
7596
8079
  case "currency": {
7597
8080
  let cleaned = raw;
@@ -7617,6 +8100,15 @@ var TextField = React239.forwardRef(
7617
8100
  return raw;
7618
8101
  }
7619
8102
  }, [activeFormat, separator, allowDecimal, maxDigits, maxDecimals, phoneCountryCode]);
8103
+ const suggestionKind = activeFormat === "phone" || activeFormat === "bankAccount" ? activeFormat : void 0;
8104
+ const numberSuggestions = useNumberSuggestions({
8105
+ kind: suggestionKind ?? "phone",
8106
+ value: String(currentValue ?? ""),
8107
+ enabled: Boolean(suggestionKind) && smartPaste && !disabled && !readOnly,
8108
+ clipboard: false,
8109
+ limit: 0,
8110
+ onSelect: (raw) => setValueState(applyFormat(raw))
8111
+ });
7620
8112
  const handleBlur = (e) => {
7621
8113
  setFocused(false);
7622
8114
  if (activeFormat && currentValue) {
@@ -7627,7 +8119,7 @@ var TextField = React239.forwardRef(
7627
8119
  }
7628
8120
  onBlur?.(e);
7629
8121
  };
7630
- React239.useEffect(() => {
8122
+ React240.useEffect(() => {
7631
8123
  if (activeFormat && currentValue) {
7632
8124
  const formatted = applyFormat(String(currentValue));
7633
8125
  if (currentValue !== formatted) {
@@ -7828,6 +8320,10 @@ var TextField = React239.forwardRef(
7828
8320
  onMouseDown: handleMouseDown,
7829
8321
  onMouseUp: handleMouseUp,
7830
8322
  onChange: handleChange,
8323
+ onPaste: (e) => {
8324
+ props.onPaste?.(e);
8325
+ if (!e.defaultPrevented) numberSuggestions.onPaste(e);
8326
+ },
7831
8327
  onKeyDown: handleKeyDown,
7832
8328
  value: currentValue,
7833
8329
  placeholder: shouldShowLabelInside ? "" : placeholder,
@@ -7956,14 +8452,14 @@ var TextField = React239.forwardRef(
7956
8452
  TextField.displayName = "TextField";
7957
8453
 
7958
8454
  // ../ultimate/src/components/text-area/TextArea.tsx
7959
- import * as React240 from "react";
8455
+ import * as React241 from "react";
7960
8456
 
7961
8457
  // ../ultimate/src/components/text-area/TextArea.module.css
7962
8458
  var TextArea_default = {};
7963
8459
 
7964
8460
  // ../ultimate/src/components/text-area/TextArea.tsx
7965
8461
  import { jsx as jsx236, jsxs as jsxs15 } from "react/jsx-runtime";
7966
- var TextArea = React240.forwardRef(
8462
+ var TextArea = React241.forwardRef(
7967
8463
  ({
7968
8464
  label,
7969
8465
  labelStyle = "outside",
@@ -7989,8 +8485,8 @@ var TextArea = React240.forwardRef(
7989
8485
  fullWidth = true,
7990
8486
  ...props
7991
8487
  }, ref) => {
7992
- const [focused, setFocused] = React240.useState(false);
7993
- const textareaRef = React240.useRef(null);
8488
+ const [focused, setFocused] = React241.useState(false);
8489
+ const textareaRef = React241.useRef(null);
7994
8490
  const [valueState, setValueState, resetValueState] = useControllableState({
7995
8491
  value,
7996
8492
  defaultValue: defaultValue || "",
@@ -8020,7 +8516,7 @@ var TextArea = React240.forwardRef(
8020
8516
  }
8021
8517
  setValueState(newValue);
8022
8518
  };
8023
- const generatedId = React240.useId();
8519
+ const generatedId = React241.useId();
8024
8520
  const shouldShowLabelOutside = label && labelStyle === "outside";
8025
8521
  const shouldShowLabelInside = label && labelStyle === "inside";
8026
8522
  const textareaId = id ?? generatedId;
@@ -8108,16 +8604,16 @@ var TextArea = React240.forwardRef(
8108
8604
  TextArea.displayName = "TextArea";
8109
8605
 
8110
8606
  // ../ultimate/src/components/avatar/Avatar.tsx
8111
- import * as React241 from "react";
8607
+ import * as React242 from "react";
8112
8608
 
8113
8609
  // ../ultimate/src/components/avatar/Avatar.module.css
8114
8610
  var Avatar_default = {};
8115
8611
 
8116
8612
  // ../ultimate/src/components/avatar/Avatar.tsx
8117
8613
  import { jsx as jsx237, jsxs as jsxs16 } from "react/jsx-runtime";
8118
- var Avatar = React241.forwardRef(
8614
+ var Avatar = React242.forwardRef(
8119
8615
  ({ src, alt, name, icon, size = "40", status, stroke, className, onClick, ...rest }, ref) => {
8120
- const [hasError, setHasError] = React241.useState(false);
8616
+ const [hasError, setHasError] = React242.useState(false);
8121
8617
  const isInteractive = !!onClick;
8122
8618
  const Component = isInteractive ? "button" : "div";
8123
8619
  const getInitials = (userName) => {
@@ -8175,16 +8671,16 @@ var Avatar = React241.forwardRef(
8175
8671
  Avatar.displayName = "Avatar";
8176
8672
 
8177
8673
  // ../ultimate/src/components/avatar/AvatarGroup.tsx
8178
- import * as React242 from "react";
8674
+ import * as React243 from "react";
8179
8675
 
8180
8676
  // ../ultimate/src/components/avatar/AvatarGroup.module.css
8181
8677
  var AvatarGroup_default = {};
8182
8678
 
8183
8679
  // ../ultimate/src/components/avatar/AvatarGroup.tsx
8184
8680
  import { jsx as jsx238, jsxs as jsxs17 } from "react/jsx-runtime";
8185
- var AvatarGroup = React242.forwardRef(
8681
+ var AvatarGroup = React243.forwardRef(
8186
8682
  ({ children, size = "40", max = 5, showStatus = false, className, ...rest }, ref) => {
8187
- const childrenArray = React242.Children.toArray(children);
8683
+ const childrenArray = React243.Children.toArray(children);
8188
8684
  const hasOverflow = childrenArray.length > max;
8189
8685
  const visibleCount = hasOverflow ? max - 1 : childrenArray.length;
8190
8686
  const visibleChildren = childrenArray.slice(0, visibleCount);
@@ -8197,8 +8693,8 @@ var AvatarGroup = React242.forwardRef(
8197
8693
  ].filter(Boolean).join(" ");
8198
8694
  return /* @__PURE__ */ jsxs17("div", { ref, className: groupClasses, ...rest, children: [
8199
8695
  visibleChildren.map((child, index) => {
8200
- if (React242.isValidElement(child)) {
8201
- return React242.cloneElement(child, {
8696
+ if (React243.isValidElement(child)) {
8697
+ return React243.cloneElement(child, {
8202
8698
  size,
8203
8699
  stroke: true,
8204
8700
  key: index,
@@ -8228,7 +8724,7 @@ var AvatarGroup = React242.forwardRef(
8228
8724
  AvatarGroup.displayName = "AvatarGroup";
8229
8725
 
8230
8726
  // ../ultimate/src/components/avatar/AvatarTemplate.tsx
8231
- import * as React243 from "react";
8727
+ import * as React244 from "react";
8232
8728
 
8233
8729
  // ../ultimate/src/img/Image=1.png
8234
8730
  var Image_1_default = "./Image=1-LTDZFXMH.png";
@@ -8260,7 +8756,7 @@ var toImageSrc = (image) => {
8260
8756
  return image.default;
8261
8757
  };
8262
8758
  var AVATAR_IMAGES = [Image_1_default, Image_2_default, Image_3_default, Image_4_default, Image_5_default, Image_6_default].map(toImageSrc);
8263
- var AvatarTemplate = React243.forwardRef(
8759
+ var AvatarTemplate = React244.forwardRef(
8264
8760
  ({ templateId = 1, ...rest }, ref) => {
8265
8761
  const src = AVATAR_IMAGES[templateId - 1];
8266
8762
  return /* @__PURE__ */ jsx239(Avatar, { ref, src, className: ["phx-avatar-template", rest.className].filter(Boolean).join(" "), ...rest });
@@ -8306,14 +8802,14 @@ var Image = ({
8306
8802
  };
8307
8803
 
8308
8804
  // ../ultimate/src/components/skeleton/Skeleton.tsx
8309
- import * as React244 from "react";
8805
+ import * as React245 from "react";
8310
8806
 
8311
8807
  // ../ultimate/src/components/skeleton/Skeleton.module.css
8312
8808
  var Skeleton_default = {};
8313
8809
 
8314
8810
  // ../ultimate/src/components/skeleton/Skeleton.tsx
8315
8811
  import { jsx as jsx241 } from "react/jsx-runtime";
8316
- var Skeleton = React244.forwardRef(
8812
+ var Skeleton = React245.forwardRef(
8317
8813
  ({ variant = "text", width, height, rounded, className, style, ...rest }, ref) => {
8318
8814
  const skeletonClasses = [
8319
8815
  "phx-skeleton",
@@ -8343,17 +8839,18 @@ var Skeleton = React244.forwardRef(
8343
8839
  Skeleton.displayName = "Skeleton";
8344
8840
 
8345
8841
  // ../ultimate/src/components/chip/Chip.tsx
8346
- import * as React245 from "react";
8842
+ import * as React246 from "react";
8347
8843
 
8348
8844
  // ../ultimate/src/components/chip/Chip.module.css
8349
8845
  var Chip_default = {};
8350
8846
 
8351
8847
  // ../ultimate/src/components/chip/Chip.tsx
8352
8848
  import { jsx as jsx242, jsxs as jsxs18 } from "react/jsx-runtime";
8353
- var Chip = React245.forwardRef(
8849
+ var Chip = React246.forwardRef(
8354
8850
  ({
8355
8851
  label,
8356
8852
  icon,
8853
+ iconOnly,
8357
8854
  avatar,
8358
8855
  badge,
8359
8856
  variant = "outline",
@@ -8374,6 +8871,8 @@ var Chip = React245.forwardRef(
8374
8871
  const isClickable = !disabled;
8375
8872
  const hasBadgeValue = badge !== void 0 && String(badge).trim() !== "";
8376
8873
  const shouldShowBadge = !disabled && hasBadgeValue;
8874
+ const showsIconOnly = Boolean(iconOnly && icon && !avatar);
8875
+ const isIconSquare = showsIconOnly && !shouldShowBadge && !onRemove && !hasDropdown && !(actionIcon && onAction);
8377
8876
  const chipClasses = [
8378
8877
  "phx-chip",
8379
8878
  Chip_default.chipRoot,
@@ -8384,6 +8883,7 @@ var Chip = React245.forwardRef(
8384
8883
  isClickable && Chip_default.clickable,
8385
8884
  avatar && Chip_default.hasAvatar,
8386
8885
  shouldShowBadge && Chip_default.hasBadge,
8886
+ isIconSquare && Chip_default.iconSquare,
8387
8887
  className
8388
8888
  ].filter(Boolean).join(" ");
8389
8889
  const badgeColor = variant === "filled" ? "white" : "black";
@@ -8433,8 +8933,9 @@ var Chip = React245.forwardRef(
8433
8933
  ref,
8434
8934
  className: chipClasses,
8435
8935
  onClick: isClickable ? onClick : void 0,
8436
- role: isClickable ? "button" : void 0,
8936
+ role: isClickable ? "button" : showsIconOnly ? "img" : void 0,
8437
8937
  tabIndex: isClickable ? 0 : void 0,
8938
+ "aria-label": showsIconOnly ? label : void 0,
8438
8939
  ...rest,
8439
8940
  onKeyDown: (event) => {
8440
8941
  rest.onKeyDown?.(event);
@@ -8444,13 +8945,13 @@ var Chip = React245.forwardRef(
8444
8945
  }
8445
8946
  },
8446
8947
  children: /* @__PURE__ */ jsxs18("div", { className: Chip_default.chipContainer, children: [
8447
- avatar && /* @__PURE__ */ jsx242("div", { className: Chip_default.chipAvatar, children: React245.isValidElement(avatar) ? React245.cloneElement(avatar, {
8948
+ avatar && /* @__PURE__ */ jsx242("div", { className: Chip_default.chipAvatar, children: React246.isValidElement(avatar) ? React246.cloneElement(avatar, {
8448
8949
  size: size === "large" ? "32" : "24",
8449
8950
  stroke: variant === "filled" || avatar.props.stroke
8450
8951
  }) : avatar }),
8451
8952
  !avatar && icon && /* @__PURE__ */ jsx242("span", { className: Chip_default.chipIcon, children: icon }),
8452
- /* @__PURE__ */ jsxs18("span", { className: Chip_default.chipContent, children: [
8453
- /* @__PURE__ */ jsx242("span", { className: Chip_default.chipLabel, children: label }),
8953
+ (!showsIconOnly || shouldShowBadge) && /* @__PURE__ */ jsxs18("span", { className: Chip_default.chipContent, children: [
8954
+ !showsIconOnly && /* @__PURE__ */ jsx242("span", { className: Chip_default.chipLabel, children: label }),
8454
8955
  shouldShowBadge && /* @__PURE__ */ jsx242(
8455
8956
  Badge,
8456
8957
  {
@@ -8471,23 +8972,23 @@ var Chip = React245.forwardRef(
8471
8972
  Chip.displayName = "Chip";
8472
8973
 
8473
8974
  // ../ultimate/src/components/chip-bar/ChipBar.tsx
8474
- import * as React247 from "react";
8975
+ import * as React248 from "react";
8475
8976
 
8476
8977
  // ../ultimate/src/components/sticky-stack/StickyStack.tsx
8477
- import * as React246 from "react";
8978
+ import * as React247 from "react";
8478
8979
 
8479
8980
  // ../ultimate/src/components/sticky-stack/StickyStack.module.css
8480
8981
  var StickyStack_default = {};
8481
8982
 
8482
8983
  // ../ultimate/src/components/sticky-stack/StickyStack.tsx
8483
8984
  import { jsx as jsx243 } from "react/jsx-runtime";
8484
- var BarSurfaceContext = React246.createContext(void 0);
8485
- var StickyStackItemContext = React246.createContext(false);
8985
+ var BarSurfaceContext = React247.createContext(void 0);
8986
+ var StickyStackItemContext = React247.createContext(false);
8486
8987
  function useInStickyStackItem() {
8487
- return React246.useContext(StickyStackItemContext);
8988
+ return React247.useContext(StickyStackItemContext);
8488
8989
  }
8489
8990
  function useBarSurface(surface) {
8490
- const inherited = React246.useContext(BarSurfaceContext);
8991
+ const inherited = React247.useContext(BarSurfaceContext);
8491
8992
  return surface ?? inherited;
8492
8993
  }
8493
8994
  function resolveStackSurfaces(entries) {
@@ -8498,26 +8999,26 @@ function resolveStackSurfaces(entries) {
8498
8999
  return covered ? "merged" : "elevated";
8499
9000
  });
8500
9001
  }
8501
- var StickyStackContext = React246.createContext(null);
9002
+ var StickyStackContext = React247.createContext(null);
8502
9003
  var findScrollParent = (element) => {
8503
9004
  for (let parent = element.parentElement; parent; parent = parent.parentElement) {
8504
9005
  if (/(auto|scroll|overlay)/.test(getComputedStyle(parent).overflowY)) return parent;
8505
9006
  }
8506
9007
  return null;
8507
9008
  };
8508
- var StickyStackItem = React246.forwardRef(
9009
+ var StickyStackItem = React247.forwardRef(
8509
9010
  ({ retracted = false, paint = true, onStuckChange, className, children, ...rest }, forwardedRef) => {
8510
- const id = React246.useId();
8511
- const stack = React246.useContext(StickyStackContext);
9011
+ const id = React247.useId();
9012
+ const stack = React247.useContext(StickyStackContext);
8512
9013
  const report = stack?.report;
8513
9014
  const stackSurface = stack?.surfaces.get(id);
8514
- const elementRef = React246.useRef(null);
9015
+ const elementRef = React247.useRef(null);
8515
9016
  const ref = useComposedRefs(forwardedRef, elementRef);
8516
- const [stuck, setStuck] = React246.useState(false);
8517
- const checkRef = React246.useRef(() => {
9017
+ const [stuck, setStuck] = React247.useState(false);
9018
+ const checkRef = React247.useRef(() => {
8518
9019
  });
8519
9020
  const notifyStuck = useCallbackRef(onStuckChange);
8520
- React246.useEffect(() => {
9021
+ React247.useEffect(() => {
8521
9022
  const element = elementRef.current;
8522
9023
  if (!element) return;
8523
9024
  const check = () => {
@@ -8540,8 +9041,8 @@ var StickyStackItem = React246.forwardRef(
8540
9041
  window.removeEventListener("resize", check);
8541
9042
  };
8542
9043
  }, []);
8543
- React246.useEffect(() => checkRef.current());
8544
- React246.useEffect(() => {
9044
+ React247.useEffect(() => checkRef.current());
9045
+ React247.useEffect(() => {
8545
9046
  notifyStuck(stuck);
8546
9047
  }, [stuck, notifyStuck]);
8547
9048
  useIsomorphicLayoutEffect(() => {
@@ -8583,7 +9084,7 @@ var ChipBar_default = {};
8583
9084
  import { jsx as jsx244, jsxs as jsxs19 } from "react/jsx-runtime";
8584
9085
  var OVERFLOW_BUTTON_SIZE = 32;
8585
9086
  var MASK_FADE_SIZE = 44;
8586
- var ChipBar = React247.forwardRef(
9087
+ var ChipBar = React248.forwardRef(
8587
9088
  ({
8588
9089
  leading,
8589
9090
  children,
@@ -8598,13 +9099,13 @@ var ChipBar = React247.forwardRef(
8598
9099
  }, ref) => {
8599
9100
  const surface = useBarSurface(surfaceProp);
8600
9101
  const sticky = useInStickyStackItem() ? false : stickyProp;
8601
- const [showLeftGradient, setShowLeftGradient] = React247.useState(false);
8602
- const [showRightGradient, setShowRightGradient] = React247.useState(false);
8603
- const [internalIsStuck, setInternalIsStuck] = React247.useState(false);
9102
+ const [showLeftGradient, setShowLeftGradient] = React248.useState(false);
9103
+ const [showRightGradient, setShowRightGradient] = React248.useState(false);
9104
+ const [internalIsStuck, setInternalIsStuck] = React248.useState(false);
8604
9105
  const isStuck = stuck !== void 0 ? stuck : internalIsStuck;
8605
- const listRef = React247.useRef(null);
8606
- const containerRef = React247.useRef(null);
8607
- const updateScrollGradients = React247.useCallback(() => {
9106
+ const listRef = React248.useRef(null);
9107
+ const containerRef = React248.useRef(null);
9108
+ const updateScrollGradients = React248.useCallback(() => {
8608
9109
  const el = listRef.current;
8609
9110
  if (el) {
8610
9111
  const { scrollLeft, scrollWidth, clientWidth } = el;
@@ -8612,7 +9113,7 @@ var ChipBar = React247.forwardRef(
8612
9113
  setShowRightGradient(Math.ceil(scrollLeft + clientWidth) < scrollWidth);
8613
9114
  }
8614
9115
  }, []);
8615
- React247.useEffect(() => {
9116
+ React248.useEffect(() => {
8616
9117
  const el = listRef.current;
8617
9118
  if (el) {
8618
9119
  updateScrollGradients();
@@ -8639,7 +9140,7 @@ var ChipBar = React247.forwardRef(
8639
9140
  });
8640
9141
  }
8641
9142
  };
8642
- React247.useEffect(() => {
9143
+ React248.useEffect(() => {
8643
9144
  if (!sticky || surface !== void 0) return;
8644
9145
  const container = containerRef.current;
8645
9146
  if (!container) return;
@@ -8671,7 +9172,7 @@ var ChipBar = React247.forwardRef(
8671
9172
  window.removeEventListener("resize", checkStuck);
8672
9173
  };
8673
9174
  }, [sticky, surface]);
8674
- const setRefs = React247.useCallback((node) => {
9175
+ const setRefs = React248.useCallback((node) => {
8675
9176
  containerRef.current = node;
8676
9177
  if (typeof ref === "function") {
8677
9178
  ref(node);
@@ -8707,7 +9208,7 @@ var ChipBar = React247.forwardRef(
8707
9208
  ...rest,
8708
9209
  children: [
8709
9210
  leading && /* @__PURE__ */ jsxs19("div", { className: ChipBar_default.chipBarLeading, children: [
8710
- React247.isValidElement(leading) ? React247.cloneElement(leading, {
9211
+ React248.isValidElement(leading) ? React248.cloneElement(leading, {
8711
9212
  size: size === "large" ? "large" : "medium"
8712
9213
  }) : leading,
8713
9214
  showDivider && /* @__PURE__ */ jsx244("div", { className: ChipBar_default.chipBarDivider })
@@ -8747,9 +9248,9 @@ var ChipBar = React247.forwardRef(
8747
9248
  "scrollbarHidden",
8748
9249
  "phx-chip-bar-list"
8749
9250
  ].filter(Boolean).join(" "),
8750
- children: React247.Children.map(children, (child) => {
8751
- if (React247.isValidElement(child)) {
8752
- return React247.cloneElement(child, {
9251
+ children: React248.Children.map(children, (child) => {
9252
+ if (React248.isValidElement(child)) {
9253
+ return React248.cloneElement(child, {
8753
9254
  size: size === "large" ? "large" : "medium"
8754
9255
  });
8755
9256
  }
@@ -8790,9 +9291,9 @@ var ChipBar = React247.forwardRef(
8790
9291
  ChipBar.displayName = "ChipBar";
8791
9292
 
8792
9293
  // ../ultimate/src/components/visually-hidden/VisuallyHidden.tsx
8793
- import * as React248 from "react";
9294
+ import * as React249 from "react";
8794
9295
  import { jsx as jsx245 } from "react/jsx-runtime";
8795
- var VisuallyHidden = React248.forwardRef(
9296
+ var VisuallyHidden = React249.forwardRef(
8796
9297
  ({ as, children, className, style, ...props }, ref) => {
8797
9298
  const Component = as || "span";
8798
9299
  return /* @__PURE__ */ jsx245(
@@ -8822,26 +9323,150 @@ var VisuallyHidden = React248.forwardRef(
8822
9323
  VisuallyHidden.displayName = "VisuallyHidden";
8823
9324
 
8824
9325
  // ../ultimate/src/components/layout-context/LayoutContext.tsx
8825
- import * as React249 from "react";
9326
+ import * as React251 from "react";
9327
+
9328
+ // ../ultimate/src/components/layout-context/shell.ts
9329
+ import * as React250 from "react";
9330
+ var ShellStore = class {
9331
+ state = {
9332
+ drawerFloating: false,
9333
+ drawerCount: 0,
9334
+ open: false,
9335
+ parentId: null,
9336
+ selectedRailId: null,
9337
+ noDrawer: /* @__PURE__ */ new Set()
9338
+ };
9339
+ listeners = /* @__PURE__ */ new Set();
9340
+ railClicks = /* @__PURE__ */ new Map();
9341
+ closeTimer;
9342
+ floatingDrawers = 0;
9343
+ /** Delay (ms) before the card closes, so the pointer can cross from the rail item to it. */
9344
+ closeDelay = 200;
9345
+ subscribe = (listener) => {
9346
+ this.listeners.add(listener);
9347
+ return () => {
9348
+ this.listeners.delete(listener);
9349
+ };
9350
+ };
9351
+ getSnapshot = () => this.state;
9352
+ set(patch) {
9353
+ this.state = { ...this.state, ...patch };
9354
+ this.listeners.forEach((listener) => listener());
9355
+ }
9356
+ reconcileQueued = false;
9357
+ /**
9358
+ * After a commit (rail items and drawers report in effects, in tree order), note whether the
9359
+ * selected item's page has a drawer.
9360
+ */
9361
+ reconcile() {
9362
+ if (this.reconcileQueued) return;
9363
+ this.reconcileQueued = true;
9364
+ queueMicrotask(() => {
9365
+ this.reconcileQueued = false;
9366
+ const { selectedRailId: id, drawerCount, noDrawer } = this.state;
9367
+ if (!id) return;
9368
+ const without = drawerCount === 0;
9369
+ if (without === noDrawer.has(id)) return;
9370
+ const next = new Set(noDrawer);
9371
+ if (without) next.add(id);
9372
+ else next.delete(id);
9373
+ this.set({ noDrawer: next });
9374
+ });
9375
+ }
9376
+ /** A drawer joins (`+1`) or leaves (`-1`) the layout. */
9377
+ drawerMounted(delta) {
9378
+ this.set({ drawerCount: Math.max(0, this.state.drawerCount + delta) });
9379
+ this.reconcile();
9380
+ if (this.state.drawerCount === 0) this.close();
9381
+ }
9382
+ /** A drawer starts (`true`) or stops floating. */
9383
+ drawerFloats(floating) {
9384
+ this.floatingDrawers = Math.max(0, this.floatingDrawers + (floating ? 1 : -1));
9385
+ const drawerFloating = this.floatingDrawers > 0;
9386
+ if (drawerFloating !== this.state.drawerFloating) this.set({ drawerFloating });
9387
+ if (!drawerFloating) this.close();
9388
+ }
9389
+ /** A rail item reports its click handler; the selected one also marks itself. */
9390
+ railItem(id, selected, onClick) {
9391
+ if (onClick) this.railClicks.set(id, onClick);
9392
+ else this.railClicks.delete(id);
9393
+ if (selected && this.state.selectedRailId !== id) this.set({ selectedRailId: id });
9394
+ if (selected) this.reconcile();
9395
+ return () => {
9396
+ if (this.railClicks.get(id) === onClick) this.railClicks.delete(id);
9397
+ };
9398
+ }
9399
+ /**
9400
+ * Whether a rail item reveals the floating drawer: any item, while a drawer floats in the
9401
+ * shell, except one whose page turned out to have none. `hint` (NavItem `hasDrawer`) decides
9402
+ * outright when given.
9403
+ */
9404
+ isParent(id, hint) {
9405
+ const { drawerFloating, drawerCount, noDrawer } = this.state;
9406
+ if (!drawerFloating || drawerCount === 0) return false;
9407
+ return hint ?? !noDrawer.has(id);
9408
+ }
9409
+ /** Opens at once; closing waits `closeDelay` and any open on the way cancels it. */
9410
+ setOpen = (open) => {
9411
+ clearTimeout(this.closeTimer);
9412
+ if (open) {
9413
+ if (!this.state.open) this.set({ open: true });
9414
+ } else {
9415
+ this.closeTimer = setTimeout(() => {
9416
+ if (this.state.open) this.set({ open: false });
9417
+ }, this.closeDelay);
9418
+ }
9419
+ };
9420
+ /** Pointer or focus entering / leaving a parent rail item. */
9421
+ hoverParent(id, hovering) {
9422
+ if (hovering && this.state.parentId !== id) this.set({ parentId: id });
9423
+ this.setOpen(hovering);
9424
+ }
9425
+ /** The app's own drawer toggle, while floating: show the card for the selected item, or hide it. */
9426
+ toggleForSelected() {
9427
+ if (this.state.open) {
9428
+ this.close();
9429
+ return;
9430
+ }
9431
+ clearTimeout(this.closeTimer);
9432
+ this.set({ open: true, parentId: this.state.selectedRailId });
9433
+ }
9434
+ close = () => {
9435
+ clearTimeout(this.closeTimer);
9436
+ if (this.state.open) this.set({ open: false });
9437
+ };
9438
+ /** An item was chosen in the floating card: go to the page it was opened from, then close. */
9439
+ choose() {
9440
+ const { parentId, selectedRailId, open } = this.state;
9441
+ if (!open) return;
9442
+ if (parentId && parentId !== selectedRailId) this.railClicks.get(parentId)?.(parentId);
9443
+ this.close();
9444
+ }
9445
+ };
9446
+ var defaultShellStore = new ShellStore();
9447
+
9448
+ // ../ultimate/src/components/layout-context/LayoutContext.tsx
8826
9449
  import { jsx as jsx246 } from "react/jsx-runtime";
8827
- var LayoutContext = React249.createContext(null);
9450
+ var LayoutContext = React251.createContext(null);
8828
9451
  var LayoutProvider = ({ children }) => {
8829
- const [drawerCount, setDrawerCount] = React249.useState(0);
8830
- const registerDrawer = React249.useCallback(() => setDrawerCount((count) => count + 1), []);
8831
- const unregisterDrawer = React249.useCallback(() => setDrawerCount((count) => Math.max(0, count - 1)), []);
8832
- const value = React249.useMemo(() => ({
9452
+ const [drawerCount, setDrawerCount] = React251.useState(0);
9453
+ const [shell] = React251.useState(() => new ShellStore());
9454
+ const registerDrawer = React251.useCallback(() => setDrawerCount((count) => count + 1), []);
9455
+ const unregisterDrawer = React251.useCallback(() => setDrawerCount((count) => Math.max(0, count - 1)), []);
9456
+ const value = React251.useMemo(() => ({
8833
9457
  hasDrawer: drawerCount > 0,
8834
9458
  registerDrawer,
8835
- unregisterDrawer
8836
- }), [drawerCount, registerDrawer, unregisterDrawer]);
9459
+ unregisterDrawer,
9460
+ shell
9461
+ }), [drawerCount, registerDrawer, unregisterDrawer, shell]);
8837
9462
  return /* @__PURE__ */ jsx246(LayoutContext.Provider, { value, children });
8838
9463
  };
8839
9464
  var useLayout = () => {
8840
- return React249.useContext(LayoutContext);
9465
+ return React251.useContext(LayoutContext);
8841
9466
  };
8842
9467
 
8843
9468
  // ../ultimate/src/components/number-field/NumberField.tsx
8844
- import * as React250 from "react";
9469
+ import * as React252 from "react";
8845
9470
  import { jsx as jsx247, jsxs as jsxs20 } from "react/jsx-runtime";
8846
9471
  var parseNumber = (text) => {
8847
9472
  const normalized = text.trim().replace(",", ".");
@@ -8873,8 +9498,8 @@ function NumberField({
8873
9498
  defaultValue,
8874
9499
  onChange
8875
9500
  });
8876
- const ref = React250.useRef(null);
8877
- const [draft, setDraft] = React250.useState(null);
9501
+ const ref = React252.useRef(null);
9502
+ const [draft, setDraft] = React252.useState(null);
8878
9503
  const display = draft ?? (current == null ? "" : String(current));
8879
9504
  const resetValue = String((value !== void 0 ? current : defaultValue) ?? "");
8880
9505
  useNativeFieldReset(
@@ -8904,7 +9529,7 @@ function NumberField({
8904
9529
  )
8905
9530
  );
8906
9531
  };
8907
- const generatedId = React250.useId();
9532
+ const generatedId = React252.useId();
8908
9533
  const inputId = props.id ?? generatedId;
8909
9534
  const descriptionId = description ? `${inputId}-description` : void 0;
8910
9535
  const errorId = error ? `${inputId}-error` : void 0;
@@ -9022,17 +9647,17 @@ function NumberField({
9022
9647
  }
9023
9648
 
9024
9649
  // ../ultimate/src/components/rating/Rating.tsx
9025
- import * as React252 from "react";
9650
+ import * as React254 from "react";
9026
9651
 
9027
9652
  // ../ultimate/src/components/star/Star.tsx
9028
- import * as React251 from "react";
9653
+ import * as React253 from "react";
9029
9654
 
9030
9655
  // ../ultimate/src/components/star/Star.module.css
9031
9656
  var Star_default = {};
9032
9657
 
9033
9658
  // ../ultimate/src/components/star/Star.tsx
9034
9659
  import { jsx as jsx248 } from "react/jsx-runtime";
9035
- var Star = React251.forwardRef(
9660
+ var Star = React253.forwardRef(
9036
9661
  ({
9037
9662
  className,
9038
9663
  checked = false,
@@ -9043,8 +9668,8 @@ var Star = React251.forwardRef(
9043
9668
  onClick,
9044
9669
  ...rest
9045
9670
  }, ref) => {
9046
- const [pressed, setPressed] = React251.useState(false);
9047
- const timeoutRef = React251.useRef(null);
9671
+ const [pressed, setPressed] = React253.useState(false);
9672
+ const timeoutRef = React253.useRef(null);
9048
9673
  const iconSize = size === "small" ? 20 : 24;
9049
9674
  const handleClick = (e) => {
9050
9675
  if (disabled) return;
@@ -9058,7 +9683,7 @@ var Star = React251.forwardRef(
9058
9683
  onClick(e);
9059
9684
  }
9060
9685
  };
9061
- React251.useEffect(() => {
9686
+ React253.useEffect(() => {
9062
9687
  return () => {
9063
9688
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
9064
9689
  };
@@ -9113,7 +9738,7 @@ function Rating({
9113
9738
  defaultValue,
9114
9739
  onChange
9115
9740
  });
9116
- const hidden = React252.useRef(null);
9741
+ const hidden = React254.useRef(null);
9117
9742
  const normalize = (rating) => Number.isFinite(rating) ? Math.max(0, Math.min(count, Math.round(rating))) : 0;
9118
9743
  const selected = normalize(current);
9119
9744
  useNativeFieldReset(
@@ -9194,7 +9819,7 @@ function Rating({
9194
9819
  }
9195
9820
 
9196
9821
  // ../ultimate/src/components/otp/OTP.tsx
9197
- import * as React253 from "react";
9822
+ import * as React255 from "react";
9198
9823
  import { flushSync } from "react-dom";
9199
9824
 
9200
9825
  // ../ultimate/src/components/otp/OTP.module.css
@@ -9233,27 +9858,27 @@ function OTP({
9233
9858
  defaultValue: normalize(defaultValue),
9234
9859
  onChange
9235
9860
  });
9236
- const [local, setLocal] = React253.useState(() => split(value ?? defaultValue));
9237
- const pending = React253.useRef(null);
9861
+ const [local, setLocal] = React255.useState(() => split(value ?? defaultValue));
9862
+ const pending = React255.useRef(null);
9238
9863
  const digits = value === void 0 ? Array.from({ length: count }, (_, index) => local[index] ?? "") : pending.current?.code === normalize(code) ? Array.from(
9239
9864
  { length: count },
9240
9865
  (_, index) => pending.current.digits[index] ?? ""
9241
9866
  ) : split(code);
9242
- const currentDigits = React253.useRef(digits);
9867
+ const currentDigits = React255.useRef(digits);
9243
9868
  currentDigits.current = digits;
9244
- const composing = React253.useRef(null);
9245
- const picked = React253.useRef(false);
9246
- const inputs = React253.useRef([]);
9247
- const first = React253.useRef(null);
9248
- const previousReadOnly = React253.useRef(readOnly);
9249
- React253.useEffect(() => {
9869
+ const composing = React255.useRef(null);
9870
+ const picked = React255.useRef(false);
9871
+ const inputs = React255.useRef([]);
9872
+ const first = React255.useRef(null);
9873
+ const previousReadOnly = React255.useRef(readOnly);
9874
+ React255.useEffect(() => {
9250
9875
  if (readOnly && !previousReadOnly.current) {
9251
9876
  const active = inputs.current.find((input) => input === document.activeElement);
9252
9877
  active?.blur();
9253
9878
  }
9254
9879
  previousReadOnly.current = readOnly;
9255
9880
  }, [readOnly]);
9256
- const id = React253.useId();
9881
+ const id = React255.useId();
9257
9882
  const supportingText = error || helperText;
9258
9883
  const supportingId = error ? `${id}-error` : helperText ? `${id}-helper` : void 0;
9259
9884
  const focus = (index) => inputs.current[index]?.focus({ preventScroll: true });
@@ -9290,10 +9915,10 @@ function OTP({
9290
9915
  const nextIndex = sequential ? nextCell(next) : Math.min(count - 1, start + text.length);
9291
9916
  if (text && !inputs.current[nextIndex]?.readOnly) focus(nextIndex);
9292
9917
  };
9293
- React253.useEffect(() => {
9918
+ React255.useEffect(() => {
9294
9919
  if (autoFocus && !disabled && !readOnly) focus(sequential ? nextCell(currentDigits.current) : 0);
9295
9920
  }, []);
9296
- React253.useEffect(() => {
9921
+ React255.useEffect(() => {
9297
9922
  const cleanups = inputs.current.slice(0, count).map((input, index) => {
9298
9923
  if (!input) return () => {
9299
9924
  };
@@ -9351,7 +9976,7 @@ function OTP({
9351
9976
  gap: dotFields ? "md" : "4xs",
9352
9977
  maxW: "100%",
9353
9978
  w: `calc(${columns} * ${cellWidth}px + ${columns - 1 + separatorCount} * var(${dotFields ? "--space-md" : "--space-4xs"}) + ${separatorCount} * var(--dimensions-12))`,
9354
- children: Array.from({ length: count }, (_, index) => /* @__PURE__ */ jsxs22(React253.Fragment, { children: [
9979
+ children: Array.from({ length: count }, (_, index) => /* @__PURE__ */ jsxs22(React255.Fragment, { children: [
9355
9980
  separates(index % columns) && /* @__PURE__ */ jsx250(Text, { as: "span", "aria-hidden": "true", className: OTP_default.separator, children: "-" }),
9356
9981
  /* @__PURE__ */ jsxs22(Text, { as: "span", className: `${OTP_default.cellSlot} ${OTP_default[size]} ${dotFields ? OTP_default.dotField : ""}`, variant: size === "large" ? "headline-sm" : size === "medium" ? "title-lg" : "title-md", weight: "medium", children: [
9357
9982
  /* @__PURE__ */ jsx250(
@@ -9490,7 +10115,7 @@ function PIN({ length = 4, "aria-label": accessibleLabel = "PIN", autoFocus = tr
9490
10115
  }
9491
10116
 
9492
10117
  // ../ultimate/src/components/empty/Empty.tsx
9493
- import * as React254 from "react";
10118
+ import * as React256 from "react";
9494
10119
  import { jsx as jsx252, jsxs as jsxs23 } from "react/jsx-runtime";
9495
10120
  function Empty({
9496
10121
  title = "No results",
@@ -9498,7 +10123,7 @@ function Empty({
9498
10123
  icon,
9499
10124
  action
9500
10125
  }) {
9501
- const titleId = React254.useId();
10126
+ const titleId = React256.useId();
9502
10127
  return /* @__PURE__ */ jsxs23(
9503
10128
  VStack,
9504
10129
  {
@@ -9521,7 +10146,7 @@ function Empty({
9521
10146
  }
9522
10147
 
9523
10148
  // ../ultimate/src/components/result/Result.tsx
9524
- import * as React255 from "react";
10149
+ import * as React257 from "react";
9525
10150
  import { CheckCircle, Cancel, Warning, Information } from "@photonix/illustrations";
9526
10151
  import { jsx as jsx253, jsxs as jsxs24 } from "react/jsx-runtime";
9527
10152
  var STATUS_ILLUSTRATION = {
@@ -9531,7 +10156,7 @@ var STATUS_ILLUSTRATION = {
9531
10156
  info: Information
9532
10157
  };
9533
10158
  function Result({ status, title, description, action }) {
9534
- const titleId = React255.useId();
10159
+ const titleId = React257.useId();
9535
10160
  const StatusIllustration = STATUS_ILLUSTRATION[status];
9536
10161
  return /* @__PURE__ */ jsxs24(
9537
10162
  VStack,