@eintrek/erp-theme 1.4.8 → 1.4.9
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/components/ui/formatted-number-input.d.ts +17 -0
- package/dist/components/ui/masked-text-input.d.ts +32 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +217 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +221 -0
- package/dist/index.js.map +1 -1
- package/package.json +13 -11
package/dist/index.js
CHANGED
|
@@ -13458,6 +13458,222 @@ function HoverCardContent({ className, align = "center", sideOffset = 4, ...prop
|
|
|
13458
13458
|
return (require$$1.jsx(HoverCardPrimitive__namespace.Portal, { "data-slot": "hover-card-portal", children: require$$1.jsx(HoverCardPrimitive__namespace.Content, { "data-slot": "hover-card-content", align: align, sideOffset: sideOffset, className: cn$1("bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden", className), ...props }) }));
|
|
13459
13459
|
}
|
|
13460
13460
|
|
|
13461
|
+
function countDigitsBefore$1(str, caret) {
|
|
13462
|
+
let n = 0;
|
|
13463
|
+
for (let i = 0; i < caret && i < str.length; i++) {
|
|
13464
|
+
if (/\d/.test(str[i]))
|
|
13465
|
+
n++;
|
|
13466
|
+
}
|
|
13467
|
+
return n;
|
|
13468
|
+
}
|
|
13469
|
+
function caretAfterDigits$1(str, digitCount) {
|
|
13470
|
+
if (digitCount <= 0)
|
|
13471
|
+
return 0;
|
|
13472
|
+
let seen = 0;
|
|
13473
|
+
for (let i = 0; i < str.length; i++) {
|
|
13474
|
+
if (/\d/.test(str[i])) {
|
|
13475
|
+
seen++;
|
|
13476
|
+
if (seen >= digitCount)
|
|
13477
|
+
return i + 1;
|
|
13478
|
+
}
|
|
13479
|
+
}
|
|
13480
|
+
return str.length;
|
|
13481
|
+
}
|
|
13482
|
+
function stripToRaw(input, allowNegative) {
|
|
13483
|
+
let s = input.replace(/,/g, "");
|
|
13484
|
+
const neg = allowNegative && s.startsWith("-");
|
|
13485
|
+
s = s.replace(/[^\d.]/g, "");
|
|
13486
|
+
// Keep only the first decimal point.
|
|
13487
|
+
const dot = s.indexOf(".");
|
|
13488
|
+
if (dot !== -1) {
|
|
13489
|
+
s = s.slice(0, dot + 1) + s.slice(dot + 1).replace(/\./g, "");
|
|
13490
|
+
}
|
|
13491
|
+
return neg ? `-${s}` : s;
|
|
13492
|
+
}
|
|
13493
|
+
function formatRaw(raw, decimals) {
|
|
13494
|
+
if (raw === "" || raw === "-" || raw === ".")
|
|
13495
|
+
return raw === "." ? "0." : raw;
|
|
13496
|
+
const neg = raw.startsWith("-");
|
|
13497
|
+
const body = neg ? raw.slice(1) : raw;
|
|
13498
|
+
const [intPart = "", fracPart] = body.split(".");
|
|
13499
|
+
const intFormatted = intPart === ""
|
|
13500
|
+
? ""
|
|
13501
|
+
: Number(intPart).toLocaleString("en-US", { maximumFractionDigits: 0 });
|
|
13502
|
+
let out = neg ? `-${intFormatted}` : intFormatted;
|
|
13503
|
+
if (fracPart !== undefined) {
|
|
13504
|
+
out += `.${fracPart.slice(0, decimals)}`;
|
|
13505
|
+
}
|
|
13506
|
+
return out;
|
|
13507
|
+
}
|
|
13508
|
+
function parseRaw(raw) {
|
|
13509
|
+
if (raw === "" || raw === "-" || raw === "." || raw === "-.")
|
|
13510
|
+
return "";
|
|
13511
|
+
const n = Number(raw);
|
|
13512
|
+
return Number.isFinite(n) ? n : "";
|
|
13513
|
+
}
|
|
13514
|
+
/**
|
|
13515
|
+
* Number input that shows thousand separators while typing (e.g. 100,000).
|
|
13516
|
+
* Emits a numeric model value without commas.
|
|
13517
|
+
*/
|
|
13518
|
+
function FormattedNumberInput({ value, onChange, decimals = 2, allowNegative = false, className, onBlur, ...props }) {
|
|
13519
|
+
const inputRef = React__namespace.useRef(null);
|
|
13520
|
+
const numeric = typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
13521
|
+
const toRawFromNumeric = (n) => {
|
|
13522
|
+
if (decimals <= 0)
|
|
13523
|
+
return String(Math.trunc(n));
|
|
13524
|
+
// Avoid scientific notation; trim trailing zeros for cleaner edits.
|
|
13525
|
+
const fixed = n.toFixed(decimals);
|
|
13526
|
+
return fixed.replace(/\.?0+$/, "") || "0";
|
|
13527
|
+
};
|
|
13528
|
+
const [raw, setRaw] = React__namespace.useState(() => numeric === undefined ? "" : toRawFromNumeric(numeric));
|
|
13529
|
+
// Sync from external value when not actively mismatched (controlled reset).
|
|
13530
|
+
React__namespace.useEffect(() => {
|
|
13531
|
+
if (numeric === undefined) {
|
|
13532
|
+
if (raw !== "" && raw !== "-" && parseRaw(raw) === "") {
|
|
13533
|
+
// keep incomplete draft
|
|
13534
|
+
return;
|
|
13535
|
+
}
|
|
13536
|
+
if (parseRaw(raw) === "")
|
|
13537
|
+
setRaw("");
|
|
13538
|
+
return;
|
|
13539
|
+
}
|
|
13540
|
+
if (parseRaw(raw) === numeric)
|
|
13541
|
+
return;
|
|
13542
|
+
setRaw(toRawFromNumeric(numeric));
|
|
13543
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- only react to external value
|
|
13544
|
+
}, [numeric]);
|
|
13545
|
+
const display = formatRaw(raw, decimals);
|
|
13546
|
+
const accepts = React__namespace.useMemo(() => {
|
|
13547
|
+
const sign = allowNegative ? "-?" : "";
|
|
13548
|
+
return new RegExp(`^${sign}\\d*\\.?\\d{0,${decimals}}$`);
|
|
13549
|
+
}, [allowNegative, decimals]);
|
|
13550
|
+
return (require$$1.jsx(Input, { ...props, ref: inputRef, type: "text", inputMode: "decimal", autoComplete: "off", className: cn$1(className), value: display, onChange: (event) => {
|
|
13551
|
+
const el = event.target;
|
|
13552
|
+
const nextRaw = stripToRaw(el.value, allowNegative);
|
|
13553
|
+
if (!accepts.test(nextRaw) && nextRaw !== "" && nextRaw !== "-") {
|
|
13554
|
+
return;
|
|
13555
|
+
}
|
|
13556
|
+
const digitsBefore = countDigitsBefore$1(el.value, el.selectionStart ?? el.value.length);
|
|
13557
|
+
setRaw(nextRaw);
|
|
13558
|
+
onChange(parseRaw(nextRaw));
|
|
13559
|
+
const nextDisplay = formatRaw(nextRaw, decimals);
|
|
13560
|
+
requestAnimationFrame(() => {
|
|
13561
|
+
const node = inputRef.current;
|
|
13562
|
+
if (!node)
|
|
13563
|
+
return;
|
|
13564
|
+
const pos = caretAfterDigits$1(nextDisplay, digitsBefore);
|
|
13565
|
+
node.setSelectionRange(pos, pos);
|
|
13566
|
+
});
|
|
13567
|
+
}, onBlur: (event) => {
|
|
13568
|
+
const parsed = parseRaw(raw);
|
|
13569
|
+
if (parsed === "") {
|
|
13570
|
+
setRaw("");
|
|
13571
|
+
onChange("");
|
|
13572
|
+
}
|
|
13573
|
+
else {
|
|
13574
|
+
const normalized = toRawFromNumeric(parsed);
|
|
13575
|
+
setRaw(normalized);
|
|
13576
|
+
onChange(parsed);
|
|
13577
|
+
}
|
|
13578
|
+
onBlur?.(event);
|
|
13579
|
+
} }));
|
|
13580
|
+
}
|
|
13581
|
+
|
|
13582
|
+
/** `#` = digit slot; any other character is a literal shown while typing. */
|
|
13583
|
+
const MASK_PRESETS = {
|
|
13584
|
+
/** Thai bank-style account: 555-5-55555-5 */
|
|
13585
|
+
bankAccount: "###-#-#####-#",
|
|
13586
|
+
/** Thai mobile (10 digits): 081-234-5678 */
|
|
13587
|
+
thaiPhone: "###-###-####",
|
|
13588
|
+
/** Thai national ID (13 digits): 1-2345-67890-12-3 */
|
|
13589
|
+
thaiNationalId: "#-####-#####-##-#",
|
|
13590
|
+
};
|
|
13591
|
+
function resolveMask(mask) {
|
|
13592
|
+
if (mask in MASK_PRESETS) {
|
|
13593
|
+
return MASK_PRESETS[mask];
|
|
13594
|
+
}
|
|
13595
|
+
return mask;
|
|
13596
|
+
}
|
|
13597
|
+
function maxDigits(pattern) {
|
|
13598
|
+
let n = 0;
|
|
13599
|
+
for (const ch of pattern)
|
|
13600
|
+
if (ch === "#")
|
|
13601
|
+
n++;
|
|
13602
|
+
return n;
|
|
13603
|
+
}
|
|
13604
|
+
/**
|
|
13605
|
+
* Map digits onto `#` slots. Literals appear as soon as the previous `#` is
|
|
13606
|
+
* filled; after the last typed digit, the next separator is shown so the mask
|
|
13607
|
+
* is visible while typing (e.g. "555-").
|
|
13608
|
+
*/
|
|
13609
|
+
function formatMasked(digits, pattern) {
|
|
13610
|
+
if (!digits)
|
|
13611
|
+
return "";
|
|
13612
|
+
let di = 0;
|
|
13613
|
+
let out = "";
|
|
13614
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
13615
|
+
const ch = pattern[i];
|
|
13616
|
+
if (ch === "#") {
|
|
13617
|
+
if (di >= digits.length)
|
|
13618
|
+
break;
|
|
13619
|
+
out += digits[di++];
|
|
13620
|
+
}
|
|
13621
|
+
else {
|
|
13622
|
+
if (di === 0)
|
|
13623
|
+
break;
|
|
13624
|
+
out += ch;
|
|
13625
|
+
}
|
|
13626
|
+
}
|
|
13627
|
+
return out;
|
|
13628
|
+
}
|
|
13629
|
+
function parseDigits(input, pattern) {
|
|
13630
|
+
return input.replace(/\D/g, "").slice(0, maxDigits(pattern));
|
|
13631
|
+
}
|
|
13632
|
+
function countDigitsBefore(str, caret) {
|
|
13633
|
+
let n = 0;
|
|
13634
|
+
for (let i = 0; i < caret && i < str.length; i++) {
|
|
13635
|
+
if (/\d/.test(str[i]))
|
|
13636
|
+
n++;
|
|
13637
|
+
}
|
|
13638
|
+
return n;
|
|
13639
|
+
}
|
|
13640
|
+
function caretAfterDigits(str, digitCount) {
|
|
13641
|
+
if (digitCount <= 0)
|
|
13642
|
+
return 0;
|
|
13643
|
+
let seen = 0;
|
|
13644
|
+
for (let i = 0; i < str.length; i++) {
|
|
13645
|
+
if (/\d/.test(str[i])) {
|
|
13646
|
+
seen++;
|
|
13647
|
+
if (seen >= digitCount)
|
|
13648
|
+
return i + 1;
|
|
13649
|
+
}
|
|
13650
|
+
}
|
|
13651
|
+
return str.length;
|
|
13652
|
+
}
|
|
13653
|
+
/**
|
|
13654
|
+
* Masked text input that shows separators while typing (e.g. 555-5-55555-5).
|
|
13655
|
+
* `value` / `onChange` use digits only.
|
|
13656
|
+
*/
|
|
13657
|
+
function MaskedTextInput({ value, onChange, mask, className, ...props }) {
|
|
13658
|
+
const inputRef = React__namespace.useRef(null);
|
|
13659
|
+
const pattern = resolveMask(mask);
|
|
13660
|
+
const display = formatMasked(value ?? "", pattern);
|
|
13661
|
+
return (require$$1.jsx(Input, { ...props, ref: inputRef, type: "text", inputMode: "numeric", autoComplete: "off", className: cn$1(className), value: display, onChange: (event) => {
|
|
13662
|
+
const el = event.target;
|
|
13663
|
+
const digitsBefore = countDigitsBefore(el.value, el.selectionStart ?? el.value.length);
|
|
13664
|
+
const next = parseDigits(el.value, pattern);
|
|
13665
|
+
onChange(next);
|
|
13666
|
+
const nextDisplay = formatMasked(next, pattern);
|
|
13667
|
+
requestAnimationFrame(() => {
|
|
13668
|
+
const node = inputRef.current;
|
|
13669
|
+
if (!node)
|
|
13670
|
+
return;
|
|
13671
|
+
const pos = caretAfterDigits(nextDisplay, digitsBefore);
|
|
13672
|
+
node.setSelectionRange(pos, pos);
|
|
13673
|
+
});
|
|
13674
|
+
} }));
|
|
13675
|
+
}
|
|
13676
|
+
|
|
13461
13677
|
function InputField({ id, label, helperText, error, required, containerClassName, labelClassName, messageClassName, className, ref, ...props }) {
|
|
13462
13678
|
const reactId = React__namespace.useId();
|
|
13463
13679
|
const inputId = id ?? reactId;
|
|
@@ -34767,6 +34983,7 @@ exports.FormItem = FormItem;
|
|
|
34767
34983
|
exports.FormLabel = FormLabel;
|
|
34768
34984
|
exports.FormMessage = FormMessage;
|
|
34769
34985
|
exports.FormSkeleton = FormSkeleton;
|
|
34986
|
+
exports.FormattedNumberInput = FormattedNumberInput;
|
|
34770
34987
|
exports.Header = Header;
|
|
34771
34988
|
exports.HoverCard = HoverCard;
|
|
34772
34989
|
exports.HoverCardContent = HoverCardContent;
|
|
@@ -34778,6 +34995,8 @@ exports.InputOTPGroup = InputOTPGroup;
|
|
|
34778
34995
|
exports.InputOTPSlot = InputOTPSlot;
|
|
34779
34996
|
exports.Label = Label;
|
|
34780
34997
|
exports.LoadingScreen = LoadingScreen;
|
|
34998
|
+
exports.MASK_PRESETS = MASK_PRESETS;
|
|
34999
|
+
exports.MaskedTextInput = MaskedTextInput;
|
|
34781
35000
|
exports.Menubar = Menubar;
|
|
34782
35001
|
exports.MenubarCheckboxItem = MenubarCheckboxItem;
|
|
34783
35002
|
exports.MenubarContent = MenubarContent;
|
|
@@ -34898,6 +35117,7 @@ exports.formatDate = formatDate;
|
|
|
34898
35117
|
exports.formatDateThai = formatDateThai;
|
|
34899
35118
|
exports.formatDateThaiShort = formatDateThaiShort;
|
|
34900
35119
|
exports.formatFileSize = formatFileSize;
|
|
35120
|
+
exports.formatMaskedValue = formatMasked;
|
|
34901
35121
|
exports.formatNumber = formatNumber;
|
|
34902
35122
|
exports.formatThaiMonth = formatThaiMonth;
|
|
34903
35123
|
exports.formatThaiMonthShort = formatThaiMonthShort;
|
|
@@ -34914,6 +35134,7 @@ exports.getThaiMonthName = getThaiMonthName;
|
|
|
34914
35134
|
exports.getValidFilters = getValidFilters;
|
|
34915
35135
|
exports.isImageFile = isImageFile;
|
|
34916
35136
|
exports.navigationMenuTriggerStyle = navigationMenuTriggerStyle;
|
|
35137
|
+
exports.parseMaskedDigits = parseDigits;
|
|
34917
35138
|
exports.siteConfig = siteConfig;
|
|
34918
35139
|
exports.thaiBahtText = thaiBahtText;
|
|
34919
35140
|
exports.toBuddhistIso = toBuddhistIso;
|