awing-library 2.1.2-dev.572 → 2.1.2-dev.574

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.
Files changed (29) hide show
  1. package/dist/AWING/DataForm/container.d.ts.map +1 -1
  2. package/dist/AWING/DataForm/container.js +7 -2
  3. package/dist/AWING/DataForm/container.test.js +264 -34
  4. package/dist/AWING/DataForm/index.d.ts.map +1 -1
  5. package/dist/AWING/DataForm/index.js +2 -7
  6. package/dist/AWING/DataInput/components/AsyncAutocompleteInput.d.ts.map +1 -1
  7. package/dist/AWING/DataInput/components/AsyncAutocompleteInput.js +2 -1
  8. package/dist/AWING/DataInput/components/AutocompleteInput.d.ts.map +1 -1
  9. package/dist/AWING/DataInput/components/AutocompleteInput.js +1 -1
  10. package/dist/AWING/DataInput/components/DateTimeInput.d.ts.map +1 -1
  11. package/dist/AWING/DataInput/components/DateTimeInput.js +4 -1
  12. package/dist/AWING/DataInput/components/LazyAutocompleteInput.d.ts.map +1 -1
  13. package/dist/AWING/DataInput/components/LazyAutocompleteInput.js +1 -1
  14. package/dist/AWING/DataInput/components/MonthTimeInput.d.ts.map +1 -1
  15. package/dist/AWING/DataInput/components/MonthTimeInput.js +4 -1
  16. package/dist/AWING/DataInput/components/NumberInput.d.ts +7 -1
  17. package/dist/AWING/DataInput/components/NumberInput.d.ts.map +1 -1
  18. package/dist/AWING/DataInput/components/NumberInput.js +36 -7
  19. package/dist/AWING/DataInput/interfaces.d.ts +1 -1
  20. package/dist/AWING/DataInput/interfaces.d.ts.map +1 -1
  21. package/dist/AWING/DataInput2/components/NumberInput.d.ts.map +1 -1
  22. package/dist/AWING/DataInput2/components/NumberInput.js +12 -1
  23. package/dist/AWING/DirectoryPermission/AddOrEdit/PageContent.js +1 -1
  24. package/dist/AWING/NumberFormat/index.d.ts.map +1 -1
  25. package/dist/AWING/NumberFormat/index.js +22 -7
  26. package/dist/AWING/PageManagement/utils.d.ts.map +1 -1
  27. package/dist/AWING/PageManagement/utils.js +1 -1
  28. package/package.json +1 -1
  29. package/dist/AWING/NumberFormat/index.test.js +0 -215
@@ -1 +1 @@
1
- {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../../src/AWING/DataForm/container.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,2CA4RzE"}
1
+ {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../../src/AWING/DataForm/container.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,2CAsSzE"}
@@ -36,7 +36,9 @@ function DataForm(props) {
36
36
  const isNewId = newId !== currentId;
37
37
  const isNewVersion = propsOldValue?.versionId !== oldValueRef.current?.versionId;
38
38
  const isContentDifferent = currentPropsStr !== lastPropsOldValueStr.current;
39
- if (isFirstLoad || isNewId || isNewVersion || isContentDifferent) {
39
+ const versionId = propsOldValue?.versionId;
40
+ const hasExplicitInvalidVersion = void 0 !== versionId && versionId <= 0;
41
+ if (isFirstLoad || isNewId || isNewVersion || isContentDifferent && !hasExplicitInvalidVersion) {
40
42
  lastPropsOldValueStr.current = currentPropsStr;
41
43
  oldValueRef.current = propsOldValue;
42
44
  setOldValue(propsOldValue);
@@ -155,7 +157,10 @@ function DataForm(props) {
155
157
  disabled: fieldPermissions && void 0 !== fieldPermissions[fieldName] ? !fieldPermissions[fieldName] || fieldDef.disabled : fieldDef.disabled,
156
158
  error: showError
157
159
  };
158
- fieldProps.onChange = (newValue, valid)=>handleChange(fieldName, newValue ?? '', valid ?? true);
160
+ fieldProps.onChange = (newValue, valid)=>{
161
+ const safeValue = void 0 === newValue ? '' : newValue;
162
+ handleChange(fieldName, safeValue, valid ?? true);
163
+ };
159
164
  fieldProps.value = fieldValue;
160
165
  const { key, ...otherFieldProps } = fieldProps;
161
166
  return /*#__PURE__*/ jsx(Grid, {
@@ -9,7 +9,7 @@ var __webpack_modules__ = {
9
9
  "AWING/DataInput": function(module) {
10
10
  module.exports = __WEBPACK_EXTERNAL_MODULE__DataInput_index_js_c7933a4f__;
11
11
  },
12
- "AWING/helper": function(module) {
12
+ "../helper": function(module) {
13
13
  module.exports = __WEBPACK_EXTERNAL_MODULE__helper_js_663c9e82__;
14
14
  }
15
15
  };
@@ -92,9 +92,9 @@ jest.mock('@mui/material', ()=>({
92
92
  })
93
93
  }));
94
94
  const mockInputFactory = __webpack_require__("AWING/DataInput")["default"];
95
- const mockCalculateValue = __webpack_require__("AWING/helper").calculateValue;
96
- const mockConvertFormulaToBinaryTree = __webpack_require__("AWING/helper").convertFormulaToBinaryTree;
97
- const mockReplaceFieldsValue = __webpack_require__("AWING/helper").replaceFieldsValue;
95
+ const mockCalculateValue = __webpack_require__("../helper").calculateValue;
96
+ const mockConvertFormulaToBinaryTree = __webpack_require__("../helper").convertFormulaToBinaryTree;
97
+ const mockReplaceFieldsValue = __webpack_require__("../helper").replaceFieldsValue;
98
98
  describe('DataForm Component', ()=>{
99
99
  const mockFields = [
100
100
  {
@@ -245,7 +245,7 @@ describe('DataForm Component', ()=>{
245
245
  }));
246
246
  expect(onFormValid).toHaveBeenCalledWith(false);
247
247
  });
248
- test.skip('calls onValidateForm when provided', ()=>{
248
+ test('calls onValidateForm when provided', ()=>{
249
249
  const onValidateForm = jest.fn().mockReturnValue(true);
250
250
  const onFormValid = jest.fn();
251
251
  render(/*#__PURE__*/ jsx(container, {
@@ -254,7 +254,10 @@ describe('DataForm Component', ()=>{
254
254
  onValidateForm: onValidateForm,
255
255
  onFormValid: onFormValid
256
256
  }));
257
- expect(onFormValid).toHaveBeenCalledWith(false);
257
+ expect(onValidateForm).toHaveBeenCalledWith(expect.objectContaining({
258
+ name: 'Jane Doe'
259
+ }));
260
+ expect(onFormValid).toHaveBeenCalledWith(true);
258
261
  });
259
262
  test('auto validates text fields when autoValidateFieldText is true', ()=>{
260
263
  const textFields = [
@@ -275,7 +278,7 @@ describe('DataForm Component', ()=>{
275
278
  }));
276
279
  });
277
280
  });
278
- describe.skip('Callbacks', ()=>{
281
+ describe('Callbacks', ()=>{
279
282
  test('calls onUpdate when fields change', ()=>{
280
283
  const onUpdate = jest.fn();
281
284
  render(/*#__PURE__*/ jsx(container, {
@@ -289,9 +292,9 @@ describe('DataForm Component', ()=>{
289
292
  value: 'Updated Name'
290
293
  }
291
294
  });
292
- expect(onUpdate).toHaveBeenCalledWith({
295
+ expect(onUpdate).toHaveBeenCalledWith(expect.objectContaining({
293
296
  name: 'Updated Name'
294
- }, false, 'name');
297
+ }), true, 'name');
295
298
  });
296
299
  test('calls onValues when fields change', ()=>{
297
300
  const onValues = jest.fn();
@@ -306,42 +309,25 @@ describe('DataForm Component', ()=>{
306
309
  value: 'Updated Name'
307
310
  }
308
311
  });
309
- expect(onValues).toHaveBeenCalledWith({
310
- ...mockOldValue,
312
+ expect(onValues).toHaveBeenCalledWith(expect.objectContaining({
311
313
  name: 'Updated Name'
312
- }, false, 'name');
314
+ }), true, 'name');
313
315
  });
314
- test('calls onFormValid when form validity changes', ()=>{
316
+ test('calls onFormValid on mount with complete oldValue', ()=>{
315
317
  const onFormValid = jest.fn();
316
318
  render(/*#__PURE__*/ jsx(container, {
317
319
  fields: mockFields,
318
320
  oldValue: mockOldValue,
319
321
  onFormValid: onFormValid
320
322
  }));
321
- expect(onFormValid).toHaveBeenCalledWith(false);
323
+ expect(onFormValid).toHaveBeenCalledWith(true);
322
324
  });
323
- test('form becomes valid when all fields are validated', ()=>{
325
+ test('form remains valid after field changes with valid values', ()=>{
324
326
  const onFormValid = jest.fn();
325
- const onUpdate = jest.fn();
326
- mockInputFactory.mockImplementation((props)=>/*#__PURE__*/ jsxs("div", {
327
- "data-testid": `input-${props.fieldName}`,
328
- children: [
329
- /*#__PURE__*/ jsx("input", {
330
- value: props.value || '',
331
- onChange: (e)=>props.onChange(e.target.value, true),
332
- "data-testid": `input-field-${props.fieldName}`
333
- }),
334
- props.error && /*#__PURE__*/ jsx("span", {
335
- "data-testid": `error-${props.fieldName}`,
336
- children: "Error"
337
- })
338
- ]
339
- }));
340
327
  render(/*#__PURE__*/ jsx(container, {
341
328
  fields: mockFields,
342
329
  oldValue: mockOldValue,
343
- onFormValid: onFormValid,
344
- onUpdate: onUpdate
330
+ onFormValid: onFormValid
345
331
  }));
346
332
  const nameInput = screen.getByTestId('input-field-name');
347
333
  fireEvent.change(nameInput, {
@@ -591,7 +577,7 @@ describe('DataForm Component', ()=>{
591
577
  expect(nameInput).not.toHaveAttribute('readonly');
592
578
  expect(nameInput).not.toHaveAttribute('disabled');
593
579
  });
594
- test.skip('does not set readOnly and disabled when fieldName not in fieldPermissions', ()=>{
580
+ test('does not set readOnly and disabled when fieldName not in fieldPermissions', ()=>{
595
581
  const fields = [
596
582
  {
597
583
  fieldName: 'name',
@@ -763,7 +749,7 @@ describe('DataForm Component', ()=>{
763
749
  oldValue: updatedOldValue
764
750
  }));
765
751
  const nameInput = screen.getByTestId('input-field-name');
766
- expect(nameInput).toHaveValue('No Version');
752
+ expect(nameInput).toHaveValue('Still No Version');
767
753
  });
768
754
  test('updates fieldsToUpdate when field values differ from oldValue', ()=>{
769
755
  const oldValueWithVersion = {
@@ -906,4 +892,248 @@ describe('DataForm Component', ()=>{
906
892
  expect(ageInput).toHaveValue('30');
907
893
  });
908
894
  });
895
+ describe('Hợp đồng preserve null (nhóm 1)', ()=>{
896
+ test('field emit null → form state giữ nguyên null (không bị coalesce thành "")', ()=>{
897
+ const onUpdate = jest.fn();
898
+ mockInputFactory.mockImplementation((props)=>/*#__PURE__*/ jsx("div", {
899
+ "data-testid": `input-${props.fieldName}`,
900
+ children: /*#__PURE__*/ jsx("button", {
901
+ "data-testid": `clear-${props.fieldName}`,
902
+ onClick: ()=>props.onChange(null, true),
903
+ children: "Clear"
904
+ })
905
+ }));
906
+ render(/*#__PURE__*/ jsx(container, {
907
+ fields: mockFields,
908
+ oldValue: mockOldValue,
909
+ onUpdate: onUpdate
910
+ }));
911
+ fireEvent.click(screen.getByTestId('clear-age'));
912
+ expect(onUpdate).toHaveBeenCalledWith(expect.objectContaining({
913
+ age: null
914
+ }), expect.any(Boolean), 'age');
915
+ });
916
+ test('field emit undefined → form state fallback "" (giữ tương thích cũ)', ()=>{
917
+ const onUpdate = jest.fn();
918
+ mockInputFactory.mockImplementation((props)=>/*#__PURE__*/ jsx("div", {
919
+ "data-testid": `input-${props.fieldName}`,
920
+ children: /*#__PURE__*/ jsx("button", {
921
+ "data-testid": `undef-${props.fieldName}`,
922
+ onClick: ()=>props.onChange(void 0, true),
923
+ children: "Undef"
924
+ })
925
+ }));
926
+ render(/*#__PURE__*/ jsx(container, {
927
+ fields: mockFields,
928
+ oldValue: mockOldValue,
929
+ onUpdate: onUpdate
930
+ }));
931
+ fireEvent.click(screen.getByTestId('undef-name'));
932
+ expect(onUpdate).toHaveBeenCalledWith(expect.objectContaining({
933
+ name: ''
934
+ }), expect.any(Boolean), 'name');
935
+ });
936
+ test('field emit 0 → form state giữ 0 (không bị coalesce dù 0 là falsy)', ()=>{
937
+ const onUpdate = jest.fn();
938
+ mockInputFactory.mockImplementation((props)=>/*#__PURE__*/ jsx("div", {
939
+ "data-testid": `input-${props.fieldName}`,
940
+ children: /*#__PURE__*/ jsx("button", {
941
+ "data-testid": `zero-${props.fieldName}`,
942
+ onClick: ()=>props.onChange(0, true),
943
+ children: "Zero"
944
+ })
945
+ }));
946
+ render(/*#__PURE__*/ jsx(container, {
947
+ fields: mockFields,
948
+ oldValue: mockOldValue,
949
+ onUpdate: onUpdate
950
+ }));
951
+ fireEvent.click(screen.getByTestId('zero-age'));
952
+ expect(onUpdate).toHaveBeenCalledWith(expect.objectContaining({
953
+ age: 0
954
+ }), expect.any(Boolean), 'age');
955
+ });
956
+ test('field emit false → form state giữ false', ()=>{
957
+ const onUpdate = jest.fn();
958
+ mockInputFactory.mockImplementation((props)=>/*#__PURE__*/ jsx("div", {
959
+ "data-testid": `input-${props.fieldName}`,
960
+ children: /*#__PURE__*/ jsx("button", {
961
+ "data-testid": `false-${props.fieldName}`,
962
+ onClick: ()=>props.onChange(false, true),
963
+ children: "False"
964
+ })
965
+ }));
966
+ render(/*#__PURE__*/ jsx(container, {
967
+ fields: mockFields,
968
+ oldValue: mockOldValue,
969
+ onUpdate: onUpdate
970
+ }));
971
+ fireEvent.click(screen.getByTestId('false-name'));
972
+ expect(onUpdate).toHaveBeenCalledWith(expect.objectContaining({
973
+ name: false
974
+ }), expect.any(Boolean), 'name');
975
+ });
976
+ test('null đi qua onValues (completeValues) — vẫn giữ null', ()=>{
977
+ const onValues = jest.fn();
978
+ mockInputFactory.mockImplementation((props)=>/*#__PURE__*/ jsx("div", {
979
+ "data-testid": `input-${props.fieldName}`,
980
+ children: /*#__PURE__*/ jsx("button", {
981
+ "data-testid": `null-${props.fieldName}`,
982
+ onClick: ()=>props.onChange(null, true),
983
+ children: "Null"
984
+ })
985
+ }));
986
+ render(/*#__PURE__*/ jsx(container, {
987
+ fields: mockFields,
988
+ oldValue: mockOldValue,
989
+ onValues: onValues
990
+ }));
991
+ fireEvent.click(screen.getByTestId('null-age'));
992
+ expect(onValues).toHaveBeenCalledWith(expect.objectContaining({
993
+ age: null
994
+ }), expect.any(Boolean), 'age');
995
+ });
996
+ test('chuỗi rỗng "" emit từ field → form state vẫn ""', ()=>{
997
+ const onUpdate = jest.fn();
998
+ mockInputFactory.mockImplementation((props)=>/*#__PURE__*/ jsx("div", {
999
+ "data-testid": `input-${props.fieldName}`,
1000
+ children: /*#__PURE__*/ jsx("button", {
1001
+ "data-testid": `empty-${props.fieldName}`,
1002
+ onClick: ()=>props.onChange('', true),
1003
+ children: "Empty"
1004
+ })
1005
+ }));
1006
+ render(/*#__PURE__*/ jsx(container, {
1007
+ fields: mockFields,
1008
+ oldValue: mockOldValue,
1009
+ onUpdate: onUpdate
1010
+ }));
1011
+ fireEvent.click(screen.getByTestId('empty-name'));
1012
+ expect(onUpdate).toHaveBeenCalledWith(expect.objectContaining({
1013
+ name: ''
1014
+ }), expect.any(Boolean), 'name');
1015
+ });
1016
+ });
1017
+ describe('isNewId Branch (EFFECT 1 reset)', ()=>{
1018
+ test('resets toàn bộ state khi id thay đổi', ()=>{
1019
+ const oldValueV1 = {
1020
+ name: 'Alice',
1021
+ age: 20,
1022
+ email: 'alice@example.com',
1023
+ id: 1
1024
+ };
1025
+ const { rerender } = render(/*#__PURE__*/ jsx(container, {
1026
+ fields: mockFields,
1027
+ oldValue: oldValueV1
1028
+ }));
1029
+ const nameInput = screen.getByTestId('input-field-name');
1030
+ fireEvent.change(nameInput, {
1031
+ target: {
1032
+ value: 'Modified Alice'
1033
+ }
1034
+ });
1035
+ expect(nameInput).toHaveValue('Modified Alice');
1036
+ const oldValueV2 = {
1037
+ name: 'Bob',
1038
+ age: 30,
1039
+ email: 'bob@example.com',
1040
+ id: 2
1041
+ };
1042
+ rerender(/*#__PURE__*/ jsx(container, {
1043
+ fields: mockFields,
1044
+ oldValue: oldValueV2
1045
+ }));
1046
+ expect(nameInput).toHaveValue('Bob');
1047
+ expect(screen.getByTestId('input-field-age')).toHaveValue('30');
1048
+ });
1049
+ test('reset state khi id từ có thành không có (undefined → có)', ()=>{
1050
+ const oldValueNoId = {
1051
+ name: 'NoId',
1052
+ age: 10,
1053
+ email: 'noid@example.com'
1054
+ };
1055
+ const { rerender } = render(/*#__PURE__*/ jsx(container, {
1056
+ fields: mockFields,
1057
+ oldValue: oldValueNoId
1058
+ }));
1059
+ const oldValueWithId = {
1060
+ name: 'WithId',
1061
+ age: 99,
1062
+ email: 'withid@example.com',
1063
+ id: 5
1064
+ };
1065
+ rerender(/*#__PURE__*/ jsx(container, {
1066
+ fields: mockFields,
1067
+ oldValue: oldValueWithId
1068
+ }));
1069
+ expect(screen.getByTestId('input-field-name')).toHaveValue('WithId');
1070
+ });
1071
+ });
1072
+ describe('fieldDependencies', ()=>{
1073
+ test('xóa các field phụ thuộc khi field cha thay đổi', ()=>{
1074
+ const fieldsWithDep = [
1075
+ {
1076
+ fieldName: 'country',
1077
+ type: index_js_.FIELD_TYPE.SELECT,
1078
+ label: 'Country'
1079
+ },
1080
+ {
1081
+ fieldName: 'city',
1082
+ type: index_js_.FIELD_TYPE.TEXT,
1083
+ label: 'City'
1084
+ },
1085
+ {
1086
+ fieldName: 'district',
1087
+ type: index_js_.FIELD_TYPE.TEXT,
1088
+ label: 'District'
1089
+ }
1090
+ ];
1091
+ const onUpdate = jest.fn();
1092
+ render(/*#__PURE__*/ jsx(container, {
1093
+ fields: fieldsWithDep,
1094
+ oldValue: {
1095
+ country: 'VN',
1096
+ city: 'HCM',
1097
+ district: 'Q1'
1098
+ },
1099
+ fieldDependencies: {
1100
+ country: [
1101
+ 'city',
1102
+ 'district'
1103
+ ]
1104
+ },
1105
+ onUpdate: onUpdate
1106
+ }));
1107
+ const countryInput = screen.getByTestId('input-field-country');
1108
+ fireEvent.change(countryInput, {
1109
+ target: {
1110
+ value: 'US'
1111
+ }
1112
+ });
1113
+ expect(onUpdate).toHaveBeenCalledWith(expect.objectContaining({
1114
+ country: 'US'
1115
+ }), expect.any(Boolean), 'country');
1116
+ const lastCall = onUpdate.mock.calls[onUpdate.mock.calls.length - 1][0];
1117
+ expect(lastCall.city).toBeUndefined();
1118
+ expect(lastCall.district).toBeUndefined();
1119
+ });
1120
+ test('không xóa field khi không có fieldDependencies', ()=>{
1121
+ const onUpdate = jest.fn();
1122
+ render(/*#__PURE__*/ jsx(container, {
1123
+ fields: mockFields,
1124
+ oldValue: mockOldValue,
1125
+ onUpdate: onUpdate
1126
+ }));
1127
+ const nameInput = screen.getByTestId('input-field-name');
1128
+ fireEvent.change(nameInput, {
1129
+ target: {
1130
+ value: 'Test'
1131
+ }
1132
+ });
1133
+ expect(onUpdate).toHaveBeenCalledWith(expect.objectContaining({
1134
+ name: 'Test',
1135
+ age: 25
1136
+ }), expect.any(Boolean), 'name');
1137
+ });
1138
+ });
909
1139
  });
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/AWING/DataForm/index.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,2CAM1E;AAED,cAAc,aAAa,CAAC;AAC5B,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/AWING/DataForm/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,2CAE1E;AAED,cAAc,aAAa,CAAC;AAC5B,eAAe,QAAQ,CAAC"}
@@ -1,14 +1,9 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { LocalizationProvider } from "@mui/x-date-pickers";
3
- import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment";
4
2
  import container from "./container.js";
5
3
  export * from "./interface.js";
6
4
  function DataForm_DataForm(props) {
7
- return /*#__PURE__*/ jsx(LocalizationProvider, {
8
- dateAdapter: AdapterMoment,
9
- children: /*#__PURE__*/ jsx(container, {
10
- ...props
11
- })
5
+ return /*#__PURE__*/ jsx(container, {
6
+ ...props
12
7
  });
13
8
  }
14
9
  const DataForm = DataForm_DataForm;
@@ -1 +1 @@
1
- {"version":3,"file":"AsyncAutocompleteInput.d.ts","sourceRoot":"","sources":["../../../../src/AWING/DataInput/components/AsyncAutocompleteInput.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAA0B,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE9E,MAAM,WAAW,gCAAgC,CAAC,CAAC,CAAE,SAAQ,mBAAmB,CAAC,CAAC,CAAC;IAC/E,IAAI,EAAE,UAAU,CAAC,kBAAkB,GAAG,oBAAoB,CAAC;IAC3D,YAAY,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAClC,cAAc,CAAC,GAAG,EAAE,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC;IACxC,cAAc,CAAC,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC;IAC/B,WAAW,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACnC;AAED,eAAO,MAAM,sBAAsB,GAAI,CAAC,mBAAoB,gCAAgC,CAAC,CAAC,CAAC,4CAuD9F,CAAC;AAEF,eAAe,sBAAsB,CAAC"}
1
+ {"version":3,"file":"AsyncAutocompleteInput.d.ts","sourceRoot":"","sources":["../../../../src/AWING/DataInput/components/AsyncAutocompleteInput.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAA0B,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE9E,MAAM,WAAW,gCAAgC,CAAC,CAAC,CAAE,SAAQ,mBAAmB,CAAC,CAAC,CAAC;IAC/E,IAAI,EAAE,UAAU,CAAC,kBAAkB,GAAG,oBAAoB,CAAC;IAC3D,YAAY,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAClC,cAAc,CAAC,GAAG,EAAE,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC;IACxC,cAAc,CAAC,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC;IAC/B,WAAW,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACnC;AAED,eAAO,MAAM,sBAAsB,GAAI,CAAC,mBAAoB,gCAAgC,CAAC,CAAC,CAAC,4CAyD9F,CAAC;AAEF,eAAe,sBAAsB,CAAC"}
@@ -25,7 +25,8 @@ const AsyncAutocompleteInput_AsyncAutocompleteInput = (fieldDefinition)=>{
25
25
  disableCloseOnSelect: multiple,
26
26
  value: value,
27
27
  onChange: (newValue)=>{
28
- onChange && onChange(newValue, onValidate(newValue));
28
+ const result = newValue ?? null;
29
+ onChange && onChange(result, onValidate(result));
29
30
  },
30
31
  disabled: disabled,
31
32
  TextFieldProps: {
@@ -1 +1 @@
1
- {"version":3,"file":"AutocompleteInput.d.ts","sourceRoot":"","sources":["../../../../src/AWING/DataInput/components/AutocompleteInput.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,MAAM,WAAW,2BAA2B,CAAC,CAAC,CAAE,SAAQ,mBAAmB,CAAC,CAAC,CAAC;IAC1E,IAAI,EAAE,UAAU,CAAC,YAAY,GAAG,cAAc,CAAC;IAC/C,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,eAAO,MAAM,iBAAiB,GAAI,CAAC,mBAAoB,2BAA2B,CAAC,CAAC,CAAC,4CAyGpF,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"AutocompleteInput.d.ts","sourceRoot":"","sources":["../../../../src/AWING/DataInput/components/AutocompleteInput.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,MAAM,WAAW,2BAA2B,CAAC,CAAC,CAAE,SAAQ,mBAAmB,CAAC,CAAC,CAAC;IAC1E,IAAI,EAAE,UAAU,CAAC,YAAY,GAAG,cAAc,CAAC;IAC/C,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,eAAO,MAAM,iBAAiB,GAAI,CAAC,mBAAoB,2BAA2B,CAAC,CAAC,CAAC,4CA2GpF,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -29,7 +29,7 @@ const AutocompleteInput = (fieldDefinition)=>{
29
29
  noOptionsText: t('Common.NoOptions'),
30
30
  loadingText: t('Common.Loading'),
31
31
  onChange: (_e, value)=>{
32
- const newValue = Array.isArray(value) ? value.map((x)=>x?.value) : value?.value;
32
+ const newValue = Array.isArray(value) ? value.map((x)=>x?.value) : value?.value ?? null;
33
33
  onChange && onChange(newValue, onValidate(newValue));
34
34
  },
35
35
  value: multiple ? Array.isArray(value) ? value.map((v)=>options.find((x)=>x.value === v)).filter((x)=>void 0 !== x) : [] : options.find((x)=>x.value === value) ?? null,
@@ -1 +1 @@
1
- {"version":3,"file":"DateTimeInput.d.ts","sourceRoot":"","sources":["../../../../src/AWING/DataInput/components/DateTimeInput.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,MAAM,WAAW,uBAAwB,SAAQ,mBAAmB,CAAC,IAAI,CAAC;IACtE,IAAI,EAAE,UAAU,CAAC,IAAI,GAAG,UAAU,CAAC,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;IAC5D,YAAY,CAAC,EAAE,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,IAAI,CAAC;QACf,OAAO,CAAC,EAAE,IAAI,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,eAAO,MAAM,aAAa,oBAAqB,uBAAuB,4CAgDrE,CAAC;AAEF,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"DateTimeInput.d.ts","sourceRoot":"","sources":["../../../../src/AWING/DataInput/components/DateTimeInput.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,MAAM,WAAW,uBAAwB,SAAQ,mBAAmB,CAAC,IAAI,CAAC;IACtE,IAAI,EAAE,UAAU,CAAC,IAAI,GAAG,UAAU,CAAC,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;IAC5D,YAAY,CAAC,EAAE,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,IAAI,CAAC;QACf,OAAO,CAAC,EAAE,IAAI,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,eAAO,MAAM,aAAa,oBAAqB,uBAAuB,4CAqDrE,CAAC;AAEF,eAAe,aAAa,CAAC"}
@@ -17,7 +17,10 @@ const DateTimeInput = (fieldDefinition)=>{
17
17
  defaultValue: defaultValue ? moment(defaultValue) : null,
18
18
  label: label,
19
19
  value: value ? moment(value) : null,
20
- onChange: (date)=>onChange && onChange(date?.toDate(), onValidate(date?.toDate())),
20
+ onChange: (date)=>{
21
+ const result = date?.toDate() ?? null;
22
+ onChange && onChange(result, onValidate(result ?? void 0));
23
+ },
21
24
  slotProps: {
22
25
  textField: {
23
26
  required: required,
@@ -1 +1 @@
1
- {"version":3,"file":"LazyAutocompleteInput.d.ts","sourceRoot":"","sources":["../../../../src/AWING/DataInput/components/LazyAutocompleteInput.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAGlE,KAAK,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAC7B,MAAM,WAAW,+BAA+B,CAAC,CAAC,CAAE,SAAQ,IAAI,CAAC,2BAA2B,CAAC,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAChH,IAAI,EAAE,UAAU,CAAC,iBAAiB,GAAG,kBAAkB,CAAC;IACxD,YAAY,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACjD,iBAAiB,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;CAC3C;AAED,eAAO,MAAM,qBAAqB,GAAI,CAAC,mBAAoB,+BAA+B,CAAC,CAAC,CAAC,4CA2H5F,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"LazyAutocompleteInput.d.ts","sourceRoot":"","sources":["../../../../src/AWING/DataInput/components/LazyAutocompleteInput.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAGlE,KAAK,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAC7B,MAAM,WAAW,+BAA+B,CAAC,CAAC,CAAE,SAAQ,IAAI,CAAC,2BAA2B,CAAC,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAChH,IAAI,EAAE,UAAU,CAAC,iBAAiB,GAAG,kBAAkB,CAAC;IACxD,YAAY,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACjD,iBAAiB,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;CAC3C;AAED,eAAO,MAAM,qBAAqB,GAAI,CAAC,mBAAoB,+BAA+B,CAAC,CAAC,CAAC,4CA4H5F,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
@@ -52,7 +52,7 @@ const LazyAutocompleteInput_LazyAutocompleteInput = (fieldDefinition)=>{
52
52
  noOptionsText: fetched ? t('Common.NoOptions') : t('Common.Loading'),
53
53
  loadingText: t('Common.Loading'),
54
54
  onChange: (_e, value)=>{
55
- const newValue = Array.isArray(value) ? value.map((x)=>x) : value;
55
+ const newValue = Array.isArray(value) ? value.map((x)=>x) : value ?? null;
56
56
  onChange && onChange(newValue, onValidate(newValue));
57
57
  },
58
58
  value: multiple ? value ?? [] : value ?? null,
@@ -1 +1 @@
1
- {"version":3,"file":"MonthTimeInput.d.ts","sourceRoot":"","sources":["../../../../src/AWING/DataInput/components/MonthTimeInput.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,eAAO,MAAM,cAAc,oBAAqB,uBAAuB,4CAyCtE,CAAC;AAEF,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"MonthTimeInput.d.ts","sourceRoot":"","sources":["../../../../src/AWING/DataInput/components/MonthTimeInput.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,eAAO,MAAM,cAAc,oBAAqB,uBAAuB,4CA6CtE,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -21,7 +21,10 @@ const MonthTimeInput = (fieldDefinition)=>{
21
21
  label: label,
22
22
  value: moment(value, 'MM-YYYY'),
23
23
  disabled: disabled,
24
- onChange: (date)=>onChange && onChange(date?.toDate(), onValidate(date?.toDate())),
24
+ onChange: (date)=>{
25
+ const result = date?.toDate() ?? null;
26
+ onChange && onChange(result, onValidate(result ?? void 0));
27
+ },
25
28
  readOnly: readOnly,
26
29
  slotProps: {
27
30
  textField: {
@@ -6,7 +6,13 @@ export interface NumberFieldDefinition extends BaseFieldDefinition<number> {
6
6
  autoFormula?: string;
7
7
  min?: number;
8
8
  max?: number;
9
- onChange?(newValue?: number, valid?: boolean, actualValue?: number): void;
9
+ /**
10
+ * Cho phép xoá trắng ô input.
11
+ * - `true` (mặc định): xoá trắng → onChange trả về `null`.
12
+ * - `false`: xoá trắng → fallback về `clampNumber(0, min, max)`.
13
+ */
14
+ allowEmpty?: boolean;
15
+ onChange?(newValue?: number | null, valid?: boolean, actualValue?: number | null): void;
10
16
  }
11
17
  export declare function clampNumber(value: number, min?: number, max?: number): number;
12
18
  export declare const NumberInput: (fieldDefinition: NumberFieldDefinition) => import("react/jsx-runtime").JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"NumberInput.d.ts","sourceRoot":"","sources":["../../../../src/AWING/DataInput/components/NumberInput.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGtC,MAAM,WAAW,qBAAsB,SAAQ,mBAAmB,CAAC,MAAM,CAAC;IACtE,IAAI,EAAE,UAAU,CAAC,MAAM,GAAG,QAAQ,CAAC;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7E;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,UAOpE;AAED,eAAO,MAAM,WAAW,oBAAqB,qBAAqB,4CAmFjE,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"NumberInput.d.ts","sourceRoot":"","sources":["../../../../src/AWING/DataInput/components/NumberInput.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGtC,MAAM,WAAW,qBAAsB,SAAQ,mBAAmB,CAAC,MAAM,CAAC;IACtE,IAAI,EAAE,UAAU,CAAC,MAAM,GAAG,QAAQ,CAAC;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;CAC3F;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,UAOpE;AAED,eAAO,MAAM,WAAW,oBAAqB,qBAAqB,4CAkIjE,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -14,25 +14,54 @@ const NumberInput = (fieldDefinition)=>{
14
14
  const { t } = useTranslation(void 0, {
15
15
  i18n: i18n
16
16
  });
17
- const { name, defaultValue, value, onChange, error, disableHelperText, helperText, min, max, onValidateCustom, ...other } = fieldDefinition;
18
- const [valueInput, setValueInput] = useState(defaultValue || value || '');
17
+ const { name, defaultValue, value, onChange, error, disableHelperText, helperText, min, max, onValidateCustom, allowEmpty = true, ...other } = fieldDefinition;
18
+ const [valueInput, setValueInput] = useState(defaultValue ?? value ?? '');
19
19
  const isComposing = useRef(false);
20
20
  useEffect(()=>{
21
- if (!isComposing.current && valueInput !== value) setValueInput(value ?? '');
21
+ if (isComposing.current) return;
22
+ const isIntermediateDecimal = 'string' == typeof valueInput && valueInput.endsWith('.') && Number(valueInput.slice(0, -1) || '0') === value;
23
+ if (!isIntermediateDecimal && valueInput !== value) setValueInput(value ?? '');
22
24
  }, [
23
25
  value
24
26
  ]);
25
27
  const onValidate = (val)=>{
26
- if (onValidateCustom) return onValidateCustom(Number(val));
27
- return (void 0 === min || Number(val) >= min) && (void 0 === max || Number(val) <= max) && numberNotNullValid(Number(val));
28
+ const isEmpty = '' === val || null == val;
29
+ if (onValidateCustom) return onValidateCustom(isEmpty ? void 0 : Number(val));
30
+ if (isEmpty) return !fieldDefinition.required;
31
+ const n = Number(val);
32
+ return (void 0 === min || n >= min) && (void 0 === max || n <= max) && numberNotNullValid(n);
28
33
  };
29
34
  const processChange = (newValue)=>{
30
- if ('-' === newValue || '' === newValue) return void ('-' === newValue && void 0 !== min && min >= 0 ? setValueInput(0) : setValueInput(newValue));
35
+ if ('-' === newValue || '' === newValue) {
36
+ if ('-' === newValue && void 0 !== min && min >= 0) {
37
+ setValueInput(0);
38
+ if (!isComposing.current) onChange?.(0, onValidate(0), 0);
39
+ return;
40
+ }
41
+ if (allowEmpty) {
42
+ setValueInput(newValue);
43
+ if (!isComposing.current) onChange?.(null, onValidate(null), null);
44
+ } else {
45
+ const fallback = clampNumber(0, min, max);
46
+ setValueInput(fallback);
47
+ if (!isComposing.current) onChange?.(fallback, onValidate(fallback), fallback);
48
+ }
49
+ return;
50
+ }
51
+ if (newValue.endsWith('.')) {
52
+ setValueInput(newValue);
53
+ if (!isComposing.current) {
54
+ const intPart = newValue.slice(0, -1);
55
+ const intNumber = '' === intPart || '-' === intPart ? null : clampNumber(Number(intPart), min, max);
56
+ onChange?.(intNumber, onValidate(intNumber), intNumber);
57
+ }
58
+ return;
59
+ }
31
60
  const numberValue = Number(newValue);
32
61
  if (!isNaN(numberValue)) {
33
62
  const currentValue = clampNumber(numberValue, min, max);
34
63
  setValueInput(currentValue);
35
- if (!isComposing.current) onChange && onChange(currentValue, onValidate(currentValue), numberValue);
64
+ if (!isComposing.current) onChange?.(currentValue, onValidate(currentValue), numberValue);
36
65
  }
37
66
  };
38
67
  const handleChange = (event)=>{
@@ -28,7 +28,7 @@ export interface BaseFieldDefinition<T> extends Omit<BaseTextFieldProps, 'value'
28
28
  type?: string;
29
29
  label?: string;
30
30
  customeFieldChange?(fieldValue: any): Partial<T>;
31
- onChange?(newValue?: T | undefined, valid?: boolean | undefined): void;
31
+ onChange?(newValue?: T | null | undefined, valid?: boolean | undefined): void;
32
32
  fieldName: string;
33
33
  onValidateCustom?(value?: T): boolean;
34
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../../../src/AWING/DataInput/interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,gCAAgC,EAAE,MAAM,qCAAqC,CAAC;AACvF,OAAO,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAC;AAC7E,OAAO,EAAE,+BAA+B,EAAE,MAAM,oCAAoC,CAAC;AACrF,OAAO,EAAE,oCAAoC,EAAE,MAAM,yCAAyC,CAAC;AAC/F,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAC;AAC7E,OAAO,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAC;AAC7E,OAAO,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,+BAA+B,EAAE,MAAM,6BAA6B,CAAC;AAE9E,MAAM,WAAW,mBAAmB,CAAC,CAAC,CAAE,SAAQ,IAAI,CAAC,kBAAkB,EAAE,OAAO,CAAC;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;IACtB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,CAAC,UAAU,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACjD,QAAQ,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAAC;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC;CACzC;AAED,MAAM,WAAW,eAAe;IAC5B,MAAM,IAAI,SAAS,CAAC;CACvB;AAED,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IACpD,gCAAgC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAC5C,2BAA2B,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GACvC,+BAA+B,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAC3C,oCAAoC,GACpC,yBAAyB,GACzB,+BAA+B,GAC/B,uBAAuB,GACvB,uBAAuB,GACvB,wBAAwB,GACxB,qBAAqB,GACrB,oBAAoB,GACpB,qBAAqB,GACrB,mBAAmB,GACnB,oBAAoB,GACpB,2BAA2B,GAC3B,2BAA2B,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GACvC,8BAA8B,GAC9B,qBAAqB,CAAC"}
1
+ {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../../../src/AWING/DataInput/interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,gCAAgC,EAAE,MAAM,qCAAqC,CAAC;AACvF,OAAO,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAC;AAC7E,OAAO,EAAE,+BAA+B,EAAE,MAAM,oCAAoC,CAAC;AACrF,OAAO,EAAE,oCAAoC,EAAE,MAAM,yCAAyC,CAAC;AAC/F,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAC;AAC7E,OAAO,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAC;AAC7E,OAAO,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,+BAA+B,EAAE,MAAM,6BAA6B,CAAC;AAE9E,MAAM,WAAW,mBAAmB,CAAC,CAAC,CAAE,SAAQ,IAAI,CAAC,kBAAkB,EAAE,OAAO,CAAC;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;IACtB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,CAAC,UAAU,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACjD,QAAQ,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAAC;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC;CACzC;AAED,MAAM,WAAW,eAAe;IAC5B,MAAM,IAAI,SAAS,CAAC;CACvB;AAED,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IACpD,gCAAgC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAC5C,2BAA2B,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GACvC,+BAA+B,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAC3C,oCAAoC,GACpC,yBAAyB,GACzB,+BAA+B,GAC/B,uBAAuB,GACvB,uBAAuB,GACvB,wBAAwB,GACxB,qBAAqB,GACrB,oBAAoB,GACpB,qBAAqB,GACrB,mBAAmB,GACnB,oBAAoB,GACpB,2BAA2B,GAC3B,2BAA2B,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GACvC,8BAA8B,GAC9B,qBAAqB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"NumberInput.d.ts","sourceRoot":"","sources":["../../../../src/AWING/DataInput2/components/NumberInput.tsx"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGtC,MAAM,WAAW,qBAAsB,SAAQ,mBAAmB,CAAC,MAAM,CAAC;IACtE,IAAI,EAAE,UAAU,CAAC,MAAM,GAAG,QAAQ,CAAC;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7E;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,UAKpE;AAsED,eAAO,MAAM,WAAW,wDApEmB,qBAAqB,6CAoE4B,CAAC;AAG7F,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"NumberInput.d.ts","sourceRoot":"","sources":["../../../../src/AWING/DataInput2/components/NumberInput.tsx"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGtC,MAAM,WAAW,qBAAsB,SAAQ,mBAAmB,CAAC,MAAM,CAAC;IACtE,IAAI,EAAE,UAAU,CAAC,MAAM,GAAG,QAAQ,CAAC;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7E;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,UAKpE;AAwED,eAAO,MAAM,WAAW,wDAtEmB,qBAAqB,6CAsE4B,CAAC;AAG7F,eAAe,WAAW,CAAC"}
@@ -24,11 +24,22 @@ const NumberInputInner = (fieldDefinition)=>{
24
24
  ]);
25
25
  const onValidate = (val)=>{
26
26
  if (onValidateCustom) return onValidateCustom(Number(val));
27
+ if (fieldDefinition.required && ('' === val || null == val)) return false;
27
28
  return (void 0 === min || Number(val) >= min) && (void 0 === max || Number(val) <= max) && numberNotNullValid(Number(val));
28
29
  };
29
30
  const handleChange = (event)=>{
30
31
  const newValue = event.target.value;
31
- if ('-' === newValue || '' === newValue) return void ('-' === newValue && void 0 !== min && min >= 0 ? setValueInput(0) : setValueInput(newValue));
32
+ if ('-' === newValue || '' === newValue) {
33
+ if ('-' === newValue && void 0 !== min && min >= 0) {
34
+ setValueInput(0);
35
+ onChange?.(0, onValidate(0), 0);
36
+ } else {
37
+ setValueInput(newValue);
38
+ const isValid = !fieldDefinition.required;
39
+ onChange?.(void 0, isValid, void 0);
40
+ }
41
+ return;
42
+ }
32
43
  const numberValue = Number(newValue);
33
44
  if (!isNaN(numberValue)) {
34
45
  const currentValue = clampNumber(numberValue, min, max);
@@ -10,7 +10,7 @@ import PermissionTable from "../components/PermissionTable.js";
10
10
  import AddOrEditHeader from "../components/AddOrEditHeader.js";
11
11
  import { useAtomValue } from "jotai";
12
12
  import { useGetDirectoryContext } from "../../../Features/SYSTEM/Directory/context.js";
13
- import { WorkspaceType } from "../../../index.js";
13
+ import { WorkspaceType } from "../../../Commons/Enums.js";
14
14
  function PageContent(props) {
15
15
  const { t } = useTranslation();
16
16
  const { explicitMatrixPermissions, inheritedMatrixPermissions, onExplicitMatrixPermissionsChange, isCreate, objectTypeCodeSelected, onDeleteAuthen, disableSelectSchema, explicitPermissions, onExplicitPermissionsChange, inheritedPermissions, authenPermissions, onChangeObjectTypeCode, onDrawerLevelChange, isFile } = props;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/AWING/NumberFormat/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAa,cAAc,EAAE,MAAM,eAAe,CAAC;AAI1D,eAAO,MAAM,YAAY,UAAW,cAAc,GAAG;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,4CAuDpE,CAAC;AAEF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/AWING/NumberFormat/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAa,cAAc,EAAE,MAAM,eAAe,CAAC;AAkB1D,eAAO,MAAM,YAAY,UAAW,cAAc,GAAG;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,4CA+DpE,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -2,9 +2,20 @@ import { jsx } from "react/jsx-runtime";
2
2
  import { TextField } from "@mui/material";
3
3
  import { useTranslation } from "react-i18next";
4
4
  import { formatNumberByLocale } from "../../Helpers/number.js";
5
+ function resolveDecimalSeparator(locale) {
6
+ try {
7
+ return new Intl.NumberFormat(locale).formatToParts(1.1).find((p)=>'decimal' === p.type)?.value ?? '.';
8
+ } catch {
9
+ return '.';
10
+ }
11
+ }
12
+ function escapeRegExp(str) {
13
+ return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
14
+ }
5
15
  const NumberFormat = (props)=>{
6
16
  const { value, onChange, min, ...other } = props;
7
17
  const { i18n } = useTranslation();
18
+ const decimalSeparator = resolveDecimalSeparator(i18n.language);
8
19
  const handleChange = (event)=>{
9
20
  const el = event.target;
10
21
  let newValue = el.value;
@@ -14,24 +25,28 @@ const NumberFormat = (props)=>{
14
25
  onChange(event);
15
26
  }
16
27
  };
17
- function escapeRegExp(str) {
18
- return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
19
- }
20
28
  function getFloatString(num = '') {
21
- const decimalSeparator = 'vi' === i18n.language ? ',' : '.';
22
29
  const hasNegation = '-' === num[0];
23
30
  if (hasNegation) num = num.replace('-', '');
24
31
  const numRegex = new RegExp('[0-9]|' + escapeRegExp(decimalSeparator), 'g');
25
32
  num = (num.match(numRegex) || []).join('').replace(decimalSeparator, '.');
26
33
  const firstDecimalIndex = num.indexOf('.');
27
- if (-1 !== firstDecimalIndex) num = `${num.substring(0, firstDecimalIndex)}.${num.substring(firstDecimalIndex + 1, num.length).replace(new RegExp(escapeRegExp(decimalSeparator), 'g'), '')}`;
28
- if (hasNegation && 0 !== min) num = '-' + num;
34
+ if (-1 !== firstDecimalIndex) num = `${num.substring(0, firstDecimalIndex)}.${num.substring(firstDecimalIndex + 1).replace(new RegExp(escapeRegExp(decimalSeparator), 'g'), '')}`;
35
+ if (hasNegation && (void 0 === min || min < 0)) num = '-' + num;
29
36
  return num;
30
37
  }
31
38
  function getDisplayString(val) {
32
39
  if (null == val || '' === val) return '';
33
40
  if ('-' === val) return '-';
34
- return formatNumberByLocale(Number(val));
41
+ const str = String(val);
42
+ if (str.endsWith('.')) {
43
+ const n = Number(str.slice(0, -1) || '0');
44
+ if (Number.isNaN(n)) return '';
45
+ return formatNumberByLocale(n) + decimalSeparator;
46
+ }
47
+ const n = Number(val);
48
+ if (Number.isNaN(n)) return '';
49
+ return formatNumberByLocale(n);
35
50
  }
36
51
  return /*#__PURE__*/ jsx(TextField, {
37
52
  value: getDisplayString(value),
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/AWING/PageManagement/utils.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,QACzB,MAAM,CACP,MAAM,EACN;IACI,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB,CACJ;;;CA4BJ,CAAC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/AWING/PageManagement/utils.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,QACzB,MAAM,CACP,MAAM,EACN;IACI,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB,CACJ;;;CAkCJ,CAAC"}
@@ -4,7 +4,7 @@ const getAdvanceSearchValue = (obj)=>{
4
4
  const newAdvancedObject = Object.create({});
5
5
  keys.forEach((key)=>{
6
6
  const currentValue = obj[key]?.value;
7
- if (Array.isArray(currentValue)) newAdvancedObject[key] = currentValue.map((item)=>item?.value);
7
+ if (Array.isArray(currentValue) && currentValue.every((item)=>item && 'object' == typeof item && 'value' in item)) newAdvancedObject[key] = currentValue.map((item)=>item?.value);
8
8
  else if (currentValue && 'object' == typeof currentValue && 'value' in currentValue) newAdvancedObject[key] = currentValue.value;
9
9
  else newAdvancedObject[key] = currentValue;
10
10
  if (void 0 !== currentValue) saveAdvancedObject[key] = obj[key];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "awing-library",
3
- "version": "2.1.2-dev.572",
3
+ "version": "2.1.2-dev.574",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": {
@@ -1,215 +0,0 @@
1
- import { jsx } from "react/jsx-runtime";
2
- import "@testing-library/jest-dom";
3
- import { fireEvent, render, screen } from "@testing-library/react";
4
- import index from "./index.js";
5
- import { AwingContext } from "../../Context/index.js";
6
- jest.mock('../helper', ()=>({
7
- formatNumberWithLanguage: (value, language)=>{
8
- if (!value) return '0';
9
- const numStr = String(value);
10
- if ('vi' === language) return numStr.replace('.', ',');
11
- return numStr;
12
- }
13
- }));
14
- describe('NumberFormat Component', ()=>{
15
- const mockOnChange = jest.fn();
16
- const renderWithContext = (props = {}, language = 'en')=>render(/*#__PURE__*/ jsx(AwingContext.Provider, {
17
- value: {
18
- i18next: {
19
- language
20
- },
21
- routes: [],
22
- appHelper: {},
23
- service: {},
24
- transactionType: {}
25
- },
26
- children: /*#__PURE__*/ jsx(index, {
27
- onChange: mockOnChange,
28
- ...props
29
- })
30
- }));
31
- beforeEach(()=>{
32
- mockOnChange.mockClear();
33
- });
34
- describe('Input Formatting', ()=>{
35
- test('formats number input correctly for English locale', ()=>{
36
- renderWithContext({
37
- value: '1234.56'
38
- });
39
- const input = screen.getByRole('textbox');
40
- expect(input).toHaveValue('1234.56');
41
- });
42
- test('formats number input correctly for Vietnamese locale', ()=>{
43
- renderWithContext({
44
- value: '1234.56'
45
- }, 'vi');
46
- const input = screen.getByRole('textbox');
47
- expect(input).toHaveValue('1234,56');
48
- });
49
- test('handles empty value', ()=>{
50
- renderWithContext({
51
- value: ''
52
- });
53
- const input = screen.getByRole('textbox');
54
- expect(input).toHaveValue('0');
55
- });
56
- test('handles null value', ()=>{
57
- renderWithContext({
58
- value: null
59
- });
60
- const input = screen.getByRole('textbox');
61
- expect(input).toHaveValue('0');
62
- });
63
- test('handles undefined value', ()=>{
64
- renderWithContext({
65
- value: void 0
66
- });
67
- const input = screen.getByRole('textbox');
68
- expect(input).toHaveValue('0');
69
- });
70
- });
71
- describe('Input Handling', ()=>{
72
- test('handles negative numbers when min is not 0', ()=>{
73
- renderWithContext({
74
- min: -100
75
- });
76
- const input = screen.getByRole('textbox');
77
- fireEvent.change(input, {
78
- target: {
79
- value: '-123.45'
80
- }
81
- });
82
- expect(mockOnChange).toHaveBeenCalled();
83
- });
84
- test('handles negative numbers when value is undefined', ()=>{
85
- renderWithContext();
86
- const input = screen.getByRole('textbox');
87
- fireEvent.change(input, {
88
- target: {
89
- value: null
90
- }
91
- });
92
- expect(mockOnChange).toHaveBeenCalled();
93
- });
94
- test('removes negative sign when min is 0', ()=>{
95
- renderWithContext({
96
- min: 0
97
- });
98
- const input = screen.getByRole('textbox');
99
- fireEvent.change(input, {
100
- target: {
101
- value: '-123.45'
102
- }
103
- });
104
- expect(mockOnChange).toHaveBeenCalled();
105
- });
106
- test('handles multiple decimal points correctly', ()=>{
107
- renderWithContext();
108
- const input = screen.getByRole('textbox');
109
- fireEvent.change(input, {
110
- target: {
111
- value: '123.45.67'
112
- }
113
- });
114
- expect(mockOnChange).toHaveBeenCalled();
115
- });
116
- });
117
- describe('Special Characters', ()=>{
118
- test('removes non-numeric characters except decimal separator', ()=>{
119
- renderWithContext();
120
- const input = screen.getByRole('textbox');
121
- fireEvent.change(input, {
122
- target: {
123
- value: 'abc123.45xyz'
124
- }
125
- });
126
- expect(mockOnChange).toHaveBeenCalled();
127
- });
128
- test('handles special characters in regex correctly', ()=>{
129
- renderWithContext();
130
- const input = screen.getByRole('textbox');
131
- fireEvent.change(input, {
132
- target: {
133
- value: '123$%^&*.45'
134
- }
135
- });
136
- expect(mockOnChange).toHaveBeenCalled();
137
- });
138
- });
139
- describe('Decimal Separator Handling', ()=>{
140
- test('converts Vietnamese decimal separator to dot for internal value', ()=>{
141
- renderWithContext({}, 'vi');
142
- const input = screen.getByRole('textbox');
143
- fireEvent.change(input, {
144
- target: {
145
- value: '123,45'
146
- }
147
- });
148
- expect(mockOnChange).toHaveBeenCalled();
149
- const event = mockOnChange.mock.calls[0][0];
150
- expect(event.target.value).toBe('0');
151
- });
152
- test('maintains decimal separator at the end of input', ()=>{
153
- renderWithContext();
154
- const input = screen.getByRole('textbox');
155
- fireEvent.change(input, {
156
- target: {
157
- value: '123.'
158
- }
159
- });
160
- expect(mockOnChange).toHaveBeenCalled();
161
- });
162
- test('-', ()=>{
163
- renderWithContext({
164
- value: '-'
165
- });
166
- const input = screen.getByRole('textbox');
167
- fireEvent.change(input, {
168
- target: {
169
- value: '424'
170
- }
171
- });
172
- const event = mockOnChange.mock.calls[0][0];
173
- expect(event.target.value).toBe('-');
174
- });
175
- test('Số thập với tiếng Việt', ()=>{
176
- renderWithContext({
177
- value: '123.',
178
- language: 'vi'
179
- });
180
- const input = screen.getByRole('textbox');
181
- fireEvent.change(input, {
182
- target: {
183
- value: '123.'
184
- }
185
- });
186
- const event = mockOnChange.mock.calls[0][0];
187
- expect(event.target.value).toBe('123..');
188
- });
189
- });
190
- describe('Props Handling', ()=>{
191
- test('passes through additional TextField props', ()=>{
192
- renderWithContext({
193
- placeholder: 'Enter number',
194
- disabled: true
195
- });
196
- const input = screen.getByRole('textbox');
197
- expect(input).toHaveAttribute('placeholder', 'Enter number');
198
- expect(input).toBeDisabled();
199
- });
200
- test('maintains type as text', ()=>{
201
- renderWithContext({
202
- type: 'number'
203
- });
204
- const input = screen.getByRole('textbox');
205
- expect(input).toHaveAttribute('type', 'text');
206
- });
207
- test('maintains type as text', ()=>{
208
- renderWithContext({
209
- type: 'number'
210
- });
211
- const input = screen.getByRole('textbox');
212
- expect(input).toHaveAttribute('type', 'text');
213
- });
214
- });
215
- });