@postxl/ui-components 1.3.1 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +237 -211
- package/dist/index.js +95 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6092,9 +6092,52 @@ function useIsMobile(mobileBreakpoint = 768) {
|
|
|
6092
6092
|
//#endregion
|
|
6093
6093
|
//#region src/input/number-input.tsx
|
|
6094
6094
|
/**
|
|
6095
|
+
* Formats a number according to the provided formatter
|
|
6096
|
+
*/
|
|
6097
|
+
function formatNumber(value, formatter, intlFormatter) {
|
|
6098
|
+
if (typeof formatter === "function") return formatter(value);
|
|
6099
|
+
if (intlFormatter) return intlFormatter.format(value);
|
|
6100
|
+
const { locale, options } = formatter;
|
|
6101
|
+
return new Intl.NumberFormat(locale, options).format(value);
|
|
6102
|
+
}
|
|
6103
|
+
/**
|
|
6104
|
+
* Parses a formatted string back to a number using the format configuration
|
|
6105
|
+
* This is more robust than heuristics as it uses the actual locale/format settings
|
|
6106
|
+
*/
|
|
6107
|
+
function parseFormattedNumber(formatted, formatter, intlFormatter) {
|
|
6108
|
+
if (formatted === "") return void 0;
|
|
6109
|
+
if (typeof formatter === "function") return parseFormattedNumberHeuristic(formatted);
|
|
6110
|
+
const formatterToUse = intlFormatter ?? new Intl.NumberFormat(formatter.locale, formatter.options);
|
|
6111
|
+
const parts = formatterToUse.formatToParts(12345.6);
|
|
6112
|
+
const groupSeparator = parts.find((p) => p.type === "group")?.value ?? "";
|
|
6113
|
+
const decimalSeparator = parts.find((p) => p.type === "decimal")?.value ?? ".";
|
|
6114
|
+
const normalized = formatted.replaceAll(groupSeparator, "").replaceAll(decimalSeparator, ".").replaceAll(/[^\d.-]/g, "");
|
|
6115
|
+
let parsed = Number(normalized);
|
|
6116
|
+
if (Number.isNaN(parsed)) return void 0;
|
|
6117
|
+
if (formatter.options?.style === "percent") parsed = parsed / 100;
|
|
6118
|
+
return parsed;
|
|
6119
|
+
}
|
|
6120
|
+
/**
|
|
6121
|
+
* Fallback parser for custom format functions where we don't know the format rules
|
|
6122
|
+
* Uses heuristics to guess the decimal separator
|
|
6123
|
+
*/
|
|
6124
|
+
function parseFormattedNumberHeuristic(formatted) {
|
|
6125
|
+
let cleaned = formatted.trim();
|
|
6126
|
+
const lastComma = cleaned.lastIndexOf(",");
|
|
6127
|
+
const lastPeriod = cleaned.lastIndexOf(".");
|
|
6128
|
+
if (lastComma > lastPeriod) {
|
|
6129
|
+
cleaned = cleaned.replaceAll(/[.\s']/g, "");
|
|
6130
|
+
cleaned = cleaned.replace(",", ".");
|
|
6131
|
+
} else if (lastPeriod > lastComma) cleaned = cleaned.replaceAll(/[,\s']/g, "");
|
|
6132
|
+
else cleaned = cleaned.replaceAll(/[,.\s']/g, "");
|
|
6133
|
+
cleaned = cleaned.replaceAll(/[^0-9.-]/g, "");
|
|
6134
|
+
const parsed = Number(cleaned);
|
|
6135
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
6136
|
+
}
|
|
6137
|
+
/**
|
|
6095
6138
|
* Wrapper variants that mirror inputVariants but use focus-within (for wrapper)
|
|
6096
6139
|
* instead of focus-visible (for input element).
|
|
6097
|
-
*
|
|
6140
|
+
* Note: When modifying variants, also update inputVariants in input.tsx
|
|
6098
6141
|
*/
|
|
6099
6142
|
const numberInputWrapperVariants = cva("border-input bg-background grid grid-cols-[auto_1fr_auto] items-center overflow-hidden rounded-md border shadow-xs transition-[color,box-shadow] has-[input:disabled]:pointer-events-none has-[input:disabled]:cursor-not-allowed has-[input:disabled]:opacity-50", {
|
|
6100
6143
|
variants: { variant: {
|
|
@@ -6103,19 +6146,26 @@ const numberInputWrapperVariants = cva("border-input bg-background grid grid-col
|
|
|
6103
6146
|
} },
|
|
6104
6147
|
defaultVariants: { variant: "default" }
|
|
6105
6148
|
});
|
|
6106
|
-
const NumberInput = React$10.forwardRef(({ className, wrapperClassName, prefix, suffix, variant, showSpinButtons = false, __e2e_test_id__, onEnter, onChange,...props }, ref) => {
|
|
6149
|
+
const NumberInput = React$10.forwardRef(({ className, wrapperClassName, prefix, suffix, variant, showSpinButtons = false, __e2e_test_id__, onEnter, onChange, format: format$1, value: controlledValue,...props }, ref) => {
|
|
6150
|
+
const [isFocused, setIsFocused] = React$10.useState(false);
|
|
6151
|
+
const [inputString, setInputString] = React$10.useState("");
|
|
6152
|
+
const intlFormatter = React$10.useMemo(() => {
|
|
6153
|
+
if (!format$1 || typeof format$1 === "function") return void 0;
|
|
6154
|
+
return new Intl.NumberFormat(format$1.locale, format$1.options);
|
|
6155
|
+
}, [format$1]);
|
|
6107
6156
|
const focusInputAtPosition = (element, cursor) => {
|
|
6108
6157
|
const parent = element.parentElement;
|
|
6109
6158
|
if (!parent) return;
|
|
6110
6159
|
const input = parent.querySelector("input");
|
|
6111
6160
|
if (!input) return;
|
|
6112
|
-
input.type
|
|
6161
|
+
const originalType = input.type;
|
|
6162
|
+
if (originalType === "number") input.type = "text";
|
|
6113
6163
|
if (cursor === "start") input.setSelectionRange(0, 0);
|
|
6114
6164
|
else {
|
|
6115
6165
|
const length = input.value.length;
|
|
6116
6166
|
input.setSelectionRange(length, length);
|
|
6117
6167
|
}
|
|
6118
|
-
input.type =
|
|
6168
|
+
input.type = originalType;
|
|
6119
6169
|
input.click();
|
|
6120
6170
|
input.focus();
|
|
6121
6171
|
};
|
|
@@ -6131,9 +6181,44 @@ const NumberInput = React$10.forwardRef(({ className, wrapperClassName, prefix,
|
|
|
6131
6181
|
};
|
|
6132
6182
|
const handleChange = (e) => {
|
|
6133
6183
|
const value = e.target.value;
|
|
6134
|
-
|
|
6184
|
+
if (format$1) setInputString(value);
|
|
6185
|
+
let parsedValue;
|
|
6186
|
+
if (format$1) parsedValue = parseFormattedNumber(value, format$1, intlFormatter);
|
|
6187
|
+
else parsedValue = value === "" ? void 0 : Number(value);
|
|
6135
6188
|
onChange?.(parsedValue);
|
|
6136
6189
|
};
|
|
6190
|
+
const handleFocus = (e) => {
|
|
6191
|
+
setIsFocused(true);
|
|
6192
|
+
props.onFocus?.(e);
|
|
6193
|
+
};
|
|
6194
|
+
const handleBlur = (e) => {
|
|
6195
|
+
setIsFocused(false);
|
|
6196
|
+
setInputString("");
|
|
6197
|
+
props.onBlur?.(e);
|
|
6198
|
+
};
|
|
6199
|
+
const displayValue = React$10.useMemo(() => {
|
|
6200
|
+
if (isFocused && format$1) {
|
|
6201
|
+
if (inputString !== "") return inputString;
|
|
6202
|
+
if (controlledValue !== void 0) {
|
|
6203
|
+
const formatted = formatNumber(controlledValue, format$1, intlFormatter);
|
|
6204
|
+
if (typeof format$1 !== "function" && intlFormatter) {
|
|
6205
|
+
const parts = intlFormatter.formatToParts(controlledValue);
|
|
6206
|
+
const groupSeparator = parts.find((p) => p.type === "group")?.value ?? "";
|
|
6207
|
+
return formatted.replaceAll(groupSeparator, "");
|
|
6208
|
+
}
|
|
6209
|
+
return controlledValue.toString();
|
|
6210
|
+
}
|
|
6211
|
+
return "";
|
|
6212
|
+
}
|
|
6213
|
+
if (!isFocused && format$1 && controlledValue !== void 0) return formatNumber(controlledValue, format$1, intlFormatter);
|
|
6214
|
+
return controlledValue?.toString() ?? "";
|
|
6215
|
+
}, [
|
|
6216
|
+
isFocused,
|
|
6217
|
+
format$1,
|
|
6218
|
+
inputString,
|
|
6219
|
+
controlledValue,
|
|
6220
|
+
intlFormatter
|
|
6221
|
+
]);
|
|
6137
6222
|
return /* @__PURE__ */ jsxs("div", {
|
|
6138
6223
|
className: cn(numberInputWrapperVariants({ variant }), wrapperClassName),
|
|
6139
6224
|
children: [
|
|
@@ -6144,7 +6229,7 @@ const NumberInput = React$10.forwardRef(({ className, wrapperClassName, prefix,
|
|
|
6144
6229
|
children: prefix
|
|
6145
6230
|
}),
|
|
6146
6231
|
/* @__PURE__ */ jsx("input", {
|
|
6147
|
-
type: "number",
|
|
6232
|
+
type: format$1 ? "text" : "number",
|
|
6148
6233
|
"data-slot": "input",
|
|
6149
6234
|
className: cn(
|
|
6150
6235
|
inputVariants({ variant }),
|
|
@@ -6155,12 +6240,15 @@ const NumberInput = React$10.forwardRef(({ className, wrapperClassName, prefix,
|
|
|
6155
6240
|
"text-right",
|
|
6156
6241
|
!prefix && "pl-2",
|
|
6157
6242
|
!suffix && (showSpinButtons ? "pr-1" : "pr-2"),
|
|
6158
|
-
!showSpinButtons && "appearance-none",
|
|
6243
|
+
!showSpinButtons && !format$1 && "appearance-none",
|
|
6159
6244
|
className
|
|
6160
6245
|
),
|
|
6161
6246
|
"data-test-id": __e2e_test_id__,
|
|
6162
6247
|
ref,
|
|
6248
|
+
value: displayValue,
|
|
6163
6249
|
onChange: handleChange,
|
|
6250
|
+
onFocus: handleFocus,
|
|
6251
|
+
onBlur: handleBlur,
|
|
6164
6252
|
onKeyDown: (e) => {
|
|
6165
6253
|
props.onKeyDown?.(e);
|
|
6166
6254
|
if (e.key === "Enter") onEnter?.();
|