@trackunit/react-form-components 2.6.20 → 2.6.22
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/index.cjs.js
CHANGED
|
@@ -4880,19 +4880,38 @@ const FormFieldSelectAdapter = ({ className, "data-testid": dataTestId, helpText
|
|
|
4880
4880
|
// eslint-disable-next-line @trackunit/no-typescript-assertion, @typescript-eslint/no-non-null-assertion
|
|
4881
4881
|
react.useImperativeHandle(ref, () => innerRef.current, []);
|
|
4882
4882
|
// Bridges a react-select pick back to the hidden native <select> so `register`/native form
|
|
4883
|
-
// consumers see a real change event.
|
|
4884
|
-
//
|
|
4885
|
-
//
|
|
4883
|
+
// consumers see a real change event.
|
|
4884
|
+
//
|
|
4885
|
+
// Only picks are bridged. A value arriving from the `value` prop is something the parent already
|
|
4886
|
+
// holds, so reporting it back would be an echo - and through `register(...).onChange` that echo
|
|
4887
|
+
// is indistinguishable from typing, marking the consumer's form dirty with no user interaction.
|
|
4886
4888
|
const lastNotifiedValueRef = react.useRef(innerValue);
|
|
4889
|
+
// A box, not a bare value: `null` then unambiguously means "no pick pending", so the origin gate
|
|
4890
|
+
// below cannot be satisfied by a parent-driven change that merely matches the ref's empty state.
|
|
4891
|
+
const pendingUserPickRef = react.useRef(null);
|
|
4887
4892
|
react.useEffect(() => {
|
|
4888
|
-
if (
|
|
4893
|
+
if (lastNotifiedValueRef.current === innerValue) {
|
|
4889
4894
|
return;
|
|
4890
4895
|
}
|
|
4891
4896
|
lastNotifiedValueRef.current = innerValue;
|
|
4892
|
-
|
|
4897
|
+
// Keep the hidden select in sync whatever the origin - this is state, not notification.
|
|
4893
4898
|
if (innerValue === "" && innerRef.current) {
|
|
4894
4899
|
innerRef.current.value = ""; // Set the desired option value
|
|
4895
4900
|
}
|
|
4901
|
+
// Keyed to the value the pick expected, not a boolean: react-select reports a re-pick of the
|
|
4902
|
+
// current option, which leaves `innerValue` untouched and so never runs this effect. A boolean
|
|
4903
|
+
// would still be set when the parent's next value arrives, and that value would be reported
|
|
4904
|
+
// back as a user edit.
|
|
4905
|
+
const pendingUserPick = pendingUserPickRef.current;
|
|
4906
|
+
pendingUserPickRef.current = null;
|
|
4907
|
+
const isUserChange = pendingUserPick !== null && pendingUserPick.value === innerValue;
|
|
4908
|
+
// No `undefined` guard needed: a pick is always a string or number (react-select's clear maps
|
|
4909
|
+
// to ""), so matching one narrows `innerValue` away from `undefined`. Only the parent can put
|
|
4910
|
+
// `undefined` there, and the gate above already rejects parent-driven values.
|
|
4911
|
+
if (!isUserChange) {
|
|
4912
|
+
return;
|
|
4913
|
+
}
|
|
4914
|
+
const event = new Event("change");
|
|
4896
4915
|
innerRef.current?.dispatchEvent(event);
|
|
4897
4916
|
onChangeRef.current?.(event);
|
|
4898
4917
|
}, [innerValue]);
|
|
@@ -4931,7 +4950,9 @@ const FormFieldSelectAdapter = ({ className, "data-testid": dataTestId, helpText
|
|
|
4931
4950
|
// If theres no value, set the inner value to an empty string
|
|
4932
4951
|
// A native select can not have a null value
|
|
4933
4952
|
// So even if react-select sends a null value, we need to convert it to an empty string
|
|
4934
|
-
|
|
4953
|
+
const nextValue = !e ? "" : e.value;
|
|
4954
|
+
pendingUserPickRef.current = { value: nextValue };
|
|
4955
|
+
setInnerValue(nextValue);
|
|
4935
4956
|
},
|
|
4936
4957
|
"data-testid": dataTestId,
|
|
4937
4958
|
value: selectedOption,
|
package/index.esm.js
CHANGED
|
@@ -4879,19 +4879,38 @@ const FormFieldSelectAdapter = ({ className, "data-testid": dataTestId, helpText
|
|
|
4879
4879
|
// eslint-disable-next-line @trackunit/no-typescript-assertion, @typescript-eslint/no-non-null-assertion
|
|
4880
4880
|
useImperativeHandle(ref, () => innerRef.current, []);
|
|
4881
4881
|
// Bridges a react-select pick back to the hidden native <select> so `register`/native form
|
|
4882
|
-
// consumers see a real change event.
|
|
4883
|
-
//
|
|
4884
|
-
//
|
|
4882
|
+
// consumers see a real change event.
|
|
4883
|
+
//
|
|
4884
|
+
// Only picks are bridged. A value arriving from the `value` prop is something the parent already
|
|
4885
|
+
// holds, so reporting it back would be an echo - and through `register(...).onChange` that echo
|
|
4886
|
+
// is indistinguishable from typing, marking the consumer's form dirty with no user interaction.
|
|
4885
4887
|
const lastNotifiedValueRef = useRef(innerValue);
|
|
4888
|
+
// A box, not a bare value: `null` then unambiguously means "no pick pending", so the origin gate
|
|
4889
|
+
// below cannot be satisfied by a parent-driven change that merely matches the ref's empty state.
|
|
4890
|
+
const pendingUserPickRef = useRef(null);
|
|
4886
4891
|
useEffect(() => {
|
|
4887
|
-
if (
|
|
4892
|
+
if (lastNotifiedValueRef.current === innerValue) {
|
|
4888
4893
|
return;
|
|
4889
4894
|
}
|
|
4890
4895
|
lastNotifiedValueRef.current = innerValue;
|
|
4891
|
-
|
|
4896
|
+
// Keep the hidden select in sync whatever the origin - this is state, not notification.
|
|
4892
4897
|
if (innerValue === "" && innerRef.current) {
|
|
4893
4898
|
innerRef.current.value = ""; // Set the desired option value
|
|
4894
4899
|
}
|
|
4900
|
+
// Keyed to the value the pick expected, not a boolean: react-select reports a re-pick of the
|
|
4901
|
+
// current option, which leaves `innerValue` untouched and so never runs this effect. A boolean
|
|
4902
|
+
// would still be set when the parent's next value arrives, and that value would be reported
|
|
4903
|
+
// back as a user edit.
|
|
4904
|
+
const pendingUserPick = pendingUserPickRef.current;
|
|
4905
|
+
pendingUserPickRef.current = null;
|
|
4906
|
+
const isUserChange = pendingUserPick !== null && pendingUserPick.value === innerValue;
|
|
4907
|
+
// No `undefined` guard needed: a pick is always a string or number (react-select's clear maps
|
|
4908
|
+
// to ""), so matching one narrows `innerValue` away from `undefined`. Only the parent can put
|
|
4909
|
+
// `undefined` there, and the gate above already rejects parent-driven values.
|
|
4910
|
+
if (!isUserChange) {
|
|
4911
|
+
return;
|
|
4912
|
+
}
|
|
4913
|
+
const event = new Event("change");
|
|
4895
4914
|
innerRef.current?.dispatchEvent(event);
|
|
4896
4915
|
onChangeRef.current?.(event);
|
|
4897
4916
|
}, [innerValue]);
|
|
@@ -4930,7 +4949,9 @@ const FormFieldSelectAdapter = ({ className, "data-testid": dataTestId, helpText
|
|
|
4930
4949
|
// If theres no value, set the inner value to an empty string
|
|
4931
4950
|
// A native select can not have a null value
|
|
4932
4951
|
// So even if react-select sends a null value, we need to convert it to an empty string
|
|
4933
|
-
|
|
4952
|
+
const nextValue = !e ? "" : e.value;
|
|
4953
|
+
pendingUserPickRef.current = { value: nextValue };
|
|
4954
|
+
setInnerValue(nextValue);
|
|
4934
4955
|
},
|
|
4935
4956
|
"data-testid": dataTestId,
|
|
4936
4957
|
value: selectedOption,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-form-components",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.22",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"migrations": "./migrations.json",
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
"zod": "^3.25.76",
|
|
17
17
|
"tailwind-merge": "^2.0.0",
|
|
18
18
|
"@trackunit/css-class-variance-utilities": "1.14.19",
|
|
19
|
-
"@trackunit/react-components": "2.10.
|
|
19
|
+
"@trackunit/react-components": "2.10.15",
|
|
20
20
|
"@trackunit/ui-icons": "1.14.18",
|
|
21
21
|
"@trackunit/shared-utils": "1.16.22",
|
|
22
22
|
"@trackunit/ui-design-tokens": "1.15.9",
|
|
23
|
-
"@trackunit/i18n-library-translation": "2.4.
|
|
23
|
+
"@trackunit/i18n-library-translation": "2.4.21",
|
|
24
24
|
"string-ts": "^2.0.0",
|
|
25
25
|
"es-toolkit": "^1.39.10"
|
|
26
26
|
},
|
|
@@ -32,6 +32,11 @@ export interface SelectFieldProps<TOption extends BaseOptionType = BaseOptionTyp
|
|
|
32
32
|
/**
|
|
33
33
|
* The onChange handler for the SelectField.
|
|
34
34
|
*
|
|
35
|
+
* Called when the user picks a different option. It is not called when the `value` prop changes,
|
|
36
|
+
* since that value is one the parent already holds, and not when the user re-picks the option that
|
|
37
|
+
* is already selected. It is also forwarded to the hidden native `<select>`, so a change event
|
|
38
|
+
* dispatched on that node invokes it too.
|
|
39
|
+
*
|
|
35
40
|
* @important
|
|
36
41
|
*/
|
|
37
42
|
onChange?: (event: {
|