@sustaina/shared-ui 1.53.0 → 1.54.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.d.mts CHANGED
@@ -1735,8 +1735,9 @@ type InputNumberProps = NumericFormatProps<InputProps> & {
1735
1735
  min?: number;
1736
1736
  max?: number;
1737
1737
  onStepChange?: (value: number) => void;
1738
+ autoFormatDecimal?: boolean;
1738
1739
  };
1739
- declare const InputNumber: ({ customInputProps, showStepper, step, min, max, onStepChange, onValueChange, value, defaultValue, disabled, onBlur, ...props }: InputNumberProps) => react_jsx_runtime.JSX.Element;
1740
+ declare const InputNumber: ({ customInputProps, showStepper, step, min, max, onStepChange, onValueChange, value, defaultValue, disabled, onBlur, isAllowed: isAllowedProp, autoFormatDecimal, decimalScale: decimalScaleProp, fixedDecimalScale: fixedDecimalScaleProp, ...props }: InputNumberProps) => react_jsx_runtime.JSX.Element;
1740
1741
 
1741
1742
  type PermissionAction = "CREATE" | "READ" | "EDIT" | "DELETE" | "NOTIFY" | "CREATE_DRAFT" | "REQUIRE_SITE";
1742
1743
  type PermissionString = `${string}.${PermissionAction}`;
package/dist/index.d.ts CHANGED
@@ -1735,8 +1735,9 @@ type InputNumberProps = NumericFormatProps<InputProps> & {
1735
1735
  min?: number;
1736
1736
  max?: number;
1737
1737
  onStepChange?: (value: number) => void;
1738
+ autoFormatDecimal?: boolean;
1738
1739
  };
1739
- declare const InputNumber: ({ customInputProps, showStepper, step, min, max, onStepChange, onValueChange, value, defaultValue, disabled, onBlur, ...props }: InputNumberProps) => react_jsx_runtime.JSX.Element;
1740
+ declare const InputNumber: ({ customInputProps, showStepper, step, min, max, onStepChange, onValueChange, value, defaultValue, disabled, onBlur, isAllowed: isAllowedProp, autoFormatDecimal, decimalScale: decimalScaleProp, fixedDecimalScale: fixedDecimalScaleProp, ...props }: InputNumberProps) => react_jsx_runtime.JSX.Element;
1740
1741
 
1741
1742
  type PermissionAction = "CREATE" | "READ" | "EDIT" | "DELETE" | "NOTIFY" | "CREATE_DRAFT" | "REQUIRE_SITE";
1742
1743
  type PermissionString = `${string}.${PermissionAction}`;
package/dist/index.js CHANGED
@@ -15857,8 +15857,11 @@ var Input2 = React__namespace.forwardRef(
15857
15857
  }
15858
15858
  );
15859
15859
  Input2.displayName = "Input";
15860
- var createSourceInfo = () => ({
15861
- source: "prop"
15860
+
15861
+ // src/components/inputNumber/helper.ts
15862
+ var createSourceInfo = (event) => ({
15863
+ event,
15864
+ source: event ? "event" : "prop"
15862
15865
  });
15863
15866
  var parseToNumber = (val) => {
15864
15867
  if (typeof val === "number") return val;
@@ -15868,6 +15871,112 @@ var parseToNumber = (val) => {
15868
15871
  }
15869
15872
  return void 0;
15870
15873
  };
15874
+ var truncateToFixed = (num, scale) => {
15875
+ if (scale === 0) return Math.trunc(num).toString();
15876
+ const sign = num < 0 ? "-" : "";
15877
+ const abs = Math.abs(num);
15878
+ const str = abs.toString();
15879
+ const dotIndex = str.indexOf(".");
15880
+ let intPart;
15881
+ let fracPart;
15882
+ if (dotIndex === -1) {
15883
+ intPart = str;
15884
+ fracPart = "";
15885
+ } else {
15886
+ intPart = str.slice(0, dotIndex);
15887
+ fracPart = str.slice(dotIndex + 1, dotIndex + 1 + scale);
15888
+ }
15889
+ fracPart = fracPart.padEnd(scale, "0");
15890
+ return `${sign}${intPart}.${fracPart}`;
15891
+ };
15892
+ var truncateStringToFixed = (str, scale) => {
15893
+ const trimmed = str.trim();
15894
+ if (trimmed === "" || trimmed === "-") return "0" + (scale > 0 ? "." + "0".repeat(scale) : "");
15895
+ const negative = trimmed.startsWith("-");
15896
+ const unsigned = negative ? trimmed.slice(1) : trimmed;
15897
+ const dotIndex = unsigned.indexOf(".");
15898
+ let intPart;
15899
+ let fracPart;
15900
+ if (dotIndex === -1) {
15901
+ intPart = unsigned || "0";
15902
+ fracPart = "";
15903
+ } else {
15904
+ intPart = unsigned.slice(0, dotIndex) || "0";
15905
+ fracPart = unsigned.slice(dotIndex + 1, dotIndex + 1 + scale);
15906
+ }
15907
+ if (scale === 0) return (negative ? "-" : "") + intPart;
15908
+ fracPart = fracPart.padEnd(scale, "0");
15909
+ return (negative ? "-" : "") + intPart + "." + fracPart;
15910
+ };
15911
+ var clamp = (value, min, max) => {
15912
+ if (max !== void 0 && value > max) return max;
15913
+ if (min !== void 0 && value < min) return min;
15914
+ return value;
15915
+ };
15916
+ function useAutoFormatDecimal({
15917
+ enabled,
15918
+ decimalScale,
15919
+ value,
15920
+ defaultValue
15921
+ }) {
15922
+ const blurScale = enabled ? decimalScale ?? 2 : void 0;
15923
+ const isUserEditingRef = React__namespace.useRef(false);
15924
+ const [formattedValue, setFormattedValue] = React__namespace.useState(() => {
15925
+ if (!enabled) return void 0;
15926
+ const scale = decimalScale ?? 2;
15927
+ const initial = parseToNumber(value) ?? parseToNumber(defaultValue);
15928
+ if (initial !== void 0) return truncateToFixed(initial, scale);
15929
+ return void 0;
15930
+ });
15931
+ React__namespace.useEffect(() => {
15932
+ if (!enabled) return;
15933
+ const parsed = parseToNumber(value);
15934
+ if (parsed !== void 0) {
15935
+ if (blurScale !== void 0 && !isUserEditingRef.current) {
15936
+ setFormattedValue(truncateToFixed(parsed, blurScale));
15937
+ }
15938
+ } else if (value === void 0 || value === null || value === "") {
15939
+ setFormattedValue(void 0);
15940
+ }
15941
+ }, [value, enabled, blurScale]);
15942
+ const onEdit = React__namespace.useCallback(
15943
+ (values, sourceInfo) => {
15944
+ if (!enabled || sourceInfo.source !== "event") return;
15945
+ isUserEditingRef.current = true;
15946
+ setFormattedValue(values.value || void 0);
15947
+ },
15948
+ [enabled]
15949
+ );
15950
+ const onBlur = React__namespace.useCallback(
15951
+ (truncatedStr) => {
15952
+ isUserEditingRef.current = false;
15953
+ if (!enabled || blurScale === void 0) return;
15954
+ setFormattedValue(truncatedStr);
15955
+ },
15956
+ [enabled, blurScale]
15957
+ );
15958
+ const resetEditing = React__namespace.useCallback(() => {
15959
+ isUserEditingRef.current = false;
15960
+ }, []);
15961
+ return { formattedValue, blurScale, onEdit, onBlur, resetEditing, setFormattedValue };
15962
+ }
15963
+ function useStepper({ value, step, min, max, disabled, onStep }) {
15964
+ const [changed, setChanged] = React__namespace.useState(false);
15965
+ const changeValue = React__namespace.useCallback(
15966
+ (delta) => {
15967
+ const current = value ?? 0;
15968
+ const clamped = clamp(current + delta, min, max);
15969
+ setChanged(true);
15970
+ onStep(clamped);
15971
+ },
15972
+ [value, max, min, onStep]
15973
+ );
15974
+ const increment = React__namespace.useCallback(() => changeValue(step), [changeValue, step]);
15975
+ const decrement = React__namespace.useCallback(() => changeValue(-step), [changeValue, step]);
15976
+ const isIncrementDisabled = disabled || max !== void 0 && (value ?? 0) >= max;
15977
+ const isDecrementDisabled = disabled || min !== void 0 && (value ?? 0) <= min;
15978
+ return { changed, increment, decrement, isIncrementDisabled, isDecrementDisabled };
15979
+ }
15871
15980
  var InputNumber = ({
15872
15981
  customInputProps,
15873
15982
  showStepper = false,
@@ -15880,58 +15989,106 @@ var InputNumber = ({
15880
15989
  defaultValue,
15881
15990
  disabled,
15882
15991
  onBlur,
15992
+ isAllowed: isAllowedProp,
15993
+ autoFormatDecimal = false,
15994
+ decimalScale: decimalScaleProp,
15995
+ fixedDecimalScale: fixedDecimalScaleProp,
15883
15996
  ...props
15884
15997
  }) => {
15885
15998
  const [internalValue, setInternalValue] = React__namespace.useState(
15886
15999
  () => parseToNumber(value) ?? parseToNumber(defaultValue)
15887
16000
  );
15888
- const [stepperChanged, setStepperChanged] = React__namespace.useState(false);
16001
+ const internalValueRef = React__namespace.useRef(internalValue);
16002
+ const rawValueRef = React__namespace.useRef("");
16003
+ const isBlurClampedRef = React__namespace.useRef(false);
15889
16004
  React__namespace.useEffect(() => {
15890
16005
  const parsed = parseToNumber(value);
15891
- if (parsed !== void 0) setInternalValue(parsed);
16006
+ if (parsed !== void 0) {
16007
+ internalValueRef.current = parsed;
16008
+ setInternalValue(parsed);
16009
+ }
16010
+ isBlurClampedRef.current = false;
15892
16011
  }, [value]);
15893
- const notifyChange = (newValue) => {
15894
- onStepChange?.(newValue);
15895
- onValueChange?.(
15896
- {
15897
- floatValue: newValue,
15898
- formattedValue: String(newValue),
15899
- value: String(newValue)
16012
+ const autoFormat = useAutoFormatDecimal({
16013
+ enabled: autoFormatDecimal,
16014
+ decimalScale: decimalScaleProp,
16015
+ value,
16016
+ defaultValue
16017
+ });
16018
+ const notifyChange = React__namespace.useCallback(
16019
+ (newValue, event) => {
16020
+ internalValueRef.current = newValue;
16021
+ setInternalValue(newValue);
16022
+ onStepChange?.(newValue);
16023
+ onValueChange?.(
16024
+ { floatValue: newValue, formattedValue: String(newValue), value: String(newValue) },
16025
+ createSourceInfo(event)
16026
+ );
16027
+ },
16028
+ [onStepChange, onValueChange]
16029
+ );
16030
+ const stepper = useStepper({
16031
+ value: internalValue,
16032
+ step,
16033
+ min,
16034
+ max,
16035
+ disabled,
16036
+ onStep: React__namespace.useCallback(
16037
+ (clamped) => {
16038
+ setInternalValue(clamped);
16039
+ if (autoFormat.blurScale !== void 0) {
16040
+ autoFormat.setFormattedValue(truncateToFixed(clamped, autoFormat.blurScale));
16041
+ }
16042
+ notifyChange(clamped);
15900
16043
  },
15901
- createSourceInfo()
15902
- );
15903
- };
15904
- const handleValueChange = (values, sourceInfo) => {
15905
- setInternalValue(values.floatValue);
15906
- onValueChange?.(values, sourceInfo);
15907
- if (values.floatValue !== void 0) {
15908
- onStepChange?.(values.floatValue);
15909
- }
15910
- };
15911
- const handleBlur = (event) => {
15912
- onBlur?.(event);
15913
- if (internalValue === void 0) return;
15914
- const clamped = max !== void 0 && internalValue > max ? max : min !== void 0 && internalValue < min ? min : internalValue;
15915
- if (clamped !== internalValue) {
15916
- setInternalValue(clamped);
15917
- notifyChange(clamped);
15918
- }
15919
- };
15920
- const isAllowed = (values) => {
15921
- const { floatValue } = values;
15922
- if (floatValue !== void 0 && max !== void 0 && floatValue > max) return false;
15923
- if (props.isAllowed && !props.isAllowed(values)) return false;
15924
- return true;
15925
- };
15926
- const changeValue = (delta) => {
15927
- const current = internalValue ?? 0;
15928
- const clamped = delta > 0 && max !== void 0 ? Math.min(current + delta, max) : delta < 0 && min !== void 0 ? Math.max(current + delta, min) : current + delta;
15929
- setInternalValue(clamped);
15930
- setStepperChanged(true);
15931
- notifyChange(clamped);
15932
- };
15933
- const isIncrementDisabled = disabled || max !== void 0 && (internalValue ?? 0) >= max;
15934
- const isDecrementDisabled = disabled || min !== void 0 && (internalValue ?? 0) <= min;
16044
+ [autoFormat, notifyChange]
16045
+ )
16046
+ });
16047
+ const handleValueChange = React__namespace.useCallback(
16048
+ (values, sourceInfo) => {
16049
+ internalValueRef.current = values.floatValue;
16050
+ rawValueRef.current = values.value;
16051
+ setInternalValue(values.floatValue);
16052
+ if (sourceInfo.source === "event") {
16053
+ isBlurClampedRef.current = false;
16054
+ }
16055
+ onValueChange?.(values, sourceInfo);
16056
+ if (values.floatValue !== void 0) onStepChange?.(values.floatValue);
16057
+ autoFormat.onEdit(values, sourceInfo);
16058
+ },
16059
+ [onValueChange, onStepChange, autoFormat]
16060
+ );
16061
+ const handleBlur = React__namespace.useCallback(
16062
+ (event) => {
16063
+ onBlur?.(event);
16064
+ const latestValue = internalValueRef.current;
16065
+ if (latestValue === void 0) {
16066
+ autoFormat.resetEditing();
16067
+ return;
16068
+ }
16069
+ const clamped = clamp(latestValue, min, max);
16070
+ const wasClamped = clamped !== latestValue;
16071
+ if (wasClamped) {
16072
+ isBlurClampedRef.current = true;
16073
+ internalValueRef.current = clamped;
16074
+ rawValueRef.current = String(clamped);
16075
+ setInternalValue(clamped);
16076
+ onValueChange?.(
16077
+ { floatValue: clamped, formattedValue: String(clamped), value: String(clamped) },
16078
+ createSourceInfo()
16079
+ );
16080
+ onStepChange?.(clamped);
16081
+ }
16082
+ if (autoFormatDecimal && autoFormat.blurScale !== void 0) {
16083
+ const rawStr = wasClamped ? String(clamped) : rawValueRef.current || "0";
16084
+ autoFormat.onBlur(truncateStringToFixed(rawStr, autoFormat.blurScale));
16085
+ } else {
16086
+ autoFormat.resetEditing();
16087
+ }
16088
+ },
16089
+ [onBlur, autoFormatDecimal, autoFormat, min, max, onValueChange, onStepChange]
16090
+ );
16091
+ const effectiveValue = autoFormat.formattedValue !== void 0 ? autoFormat.formattedValue : isBlurClampedRef.current || stepper.changed || autoFormatDecimal ? internalValue : value;
15935
16092
  const buttonClass = cn(
15936
16093
  "flex items-center justify-center h-3 w-5 transition-colors outline-none",
15937
16094
  "text-neutral-400 hover:text-neutral-600 active:text-neutral-900",
@@ -15941,11 +16098,16 @@ var InputNumber = ({
15941
16098
  reactNumberFormat.NumericFormat,
15942
16099
  {
15943
16100
  customInput: Input2,
15944
- value: stepperChanged ? internalValue : value,
16101
+ value: effectiveValue?.toString(),
15945
16102
  defaultValue,
15946
16103
  onValueChange: handleValueChange,
15947
16104
  onBlur: handleBlur,
15948
- isAllowed,
16105
+ ...isAllowedProp && { isAllowed: isAllowedProp },
16106
+ ...!autoFormatDecimal && {
16107
+ decimalScale: decimalScaleProp,
16108
+ fixedDecimalScale: fixedDecimalScaleProp
16109
+ },
16110
+ ...autoFormat.formattedValue !== void 0 && { valueIsNumericString: true },
15949
16111
  ...props,
15950
16112
  disabled,
15951
16113
  ...customInputProps,
@@ -15954,8 +16116,8 @@ var InputNumber = ({
15954
16116
  "button",
15955
16117
  {
15956
16118
  type: "button",
15957
- onClick: () => changeValue(step),
15958
- disabled: isIncrementDisabled,
16119
+ onClick: stepper.increment,
16120
+ disabled: stepper.isIncrementDisabled,
15959
16121
  className: buttonClass,
15960
16122
  tabIndex: -1,
15961
16123
  "aria-label": "Increment",
@@ -15966,8 +16128,8 @@ var InputNumber = ({
15966
16128
  "button",
15967
16129
  {
15968
16130
  type: "button",
15969
- onClick: () => changeValue(-step),
15970
- disabled: isDecrementDisabled,
16131
+ onClick: stepper.decrement,
16132
+ disabled: stepper.isDecrementDisabled,
15971
16133
  className: buttonClass,
15972
16134
  tabIndex: -1,
15973
16135
  "aria-label": "Decrement",