@osdk/react-components 0.3.0 → 0.4.0-main-90a01d202dfa1e497125bd6cfcdc8175278fb7ec
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/CHANGELOG.md +14 -0
- package/build/browser/action-form/ActionFormApi.js.map +1 -1
- package/build/browser/action-form/BaseForm.js +100 -12
- package/build/browser/action-form/BaseForm.js.map +1 -1
- package/build/browser/action-form/BaseForm.module.css +21 -1
- package/build/browser/action-form/BaseForm.module.css.js +4 -1
- package/build/browser/action-form/FormField.js +3 -1
- package/build/browser/action-form/FormField.js.map +1 -1
- package/build/browser/action-form/FormFieldApi.js.map +1 -1
- package/build/browser/action-form/fields/BaseInput.module.css +4 -0
- package/build/browser/action-form/fields/DatetimePickerField.js +3 -1
- package/build/browser/action-form/fields/DatetimePickerField.js.map +1 -1
- package/build/browser/action-form/fields/DatetimePickerField.module.css +4 -0
- package/build/browser/action-form/fields/FieldBridge.js +31 -4
- package/build/browser/action-form/fields/FieldBridge.js.map +1 -1
- package/build/browser/action-form/fields/FilePickerField.js +6 -2
- package/build/browser/action-form/fields/FilePickerField.js.map +1 -1
- package/build/browser/action-form/fields/FilePickerField.module.css +4 -0
- package/build/browser/action-form/fields/FormFieldRenderer.js +24 -12
- package/build/browser/action-form/fields/FormFieldRenderer.js.map +1 -1
- package/build/browser/action-form/fields/NumberInputField.js +12 -8
- package/build/browser/action-form/fields/NumberInputField.js.map +1 -1
- package/build/browser/action-form/fields/NumberInputField.module.css +4 -0
- package/build/browser/action-form/fields/TextAreaField.js +3 -1
- package/build/browser/action-form/fields/TextAreaField.js.map +1 -1
- package/build/browser/action-form/fields/TextInputField.js +3 -1
- package/build/browser/action-form/fields/TextInputField.js.map +1 -1
- package/build/browser/action-form/utils/extractValidationRules.js +186 -0
- package/build/browser/action-form/utils/extractValidationRules.js.map +1 -0
- package/build/browser/public/experimental.js.map +1 -1
- package/build/browser/shared/hooks/useAsyncAction.js +56 -0
- package/build/browser/shared/hooks/useAsyncAction.js.map +1 -0
- package/build/browser/shared/hooks/useIsMounted.js +32 -0
- package/build/browser/shared/hooks/useIsMounted.js.map +1 -0
- package/build/browser/styles.css +37 -1
- package/build/cjs/public/experimental.cjs +361 -37
- package/build/cjs/public/experimental.cjs.map +1 -1
- package/build/cjs/public/experimental.css +30 -1
- package/build/cjs/public/experimental.css.map +1 -1
- package/build/cjs/public/experimental.d.cts +40 -13
- package/build/esm/action-form/ActionFormApi.js.map +1 -1
- package/build/esm/action-form/BaseForm.js +100 -12
- package/build/esm/action-form/BaseForm.js.map +1 -1
- package/build/esm/action-form/BaseForm.module.css +21 -1
- package/build/esm/action-form/FormField.js +3 -1
- package/build/esm/action-form/FormField.js.map +1 -1
- package/build/esm/action-form/FormFieldApi.js.map +1 -1
- package/build/esm/action-form/fields/BaseInput.module.css +4 -0
- package/build/esm/action-form/fields/DatetimePickerField.js +3 -1
- package/build/esm/action-form/fields/DatetimePickerField.js.map +1 -1
- package/build/esm/action-form/fields/DatetimePickerField.module.css +4 -0
- package/build/esm/action-form/fields/FieldBridge.js +31 -4
- package/build/esm/action-form/fields/FieldBridge.js.map +1 -1
- package/build/esm/action-form/fields/FilePickerField.js +6 -2
- package/build/esm/action-form/fields/FilePickerField.js.map +1 -1
- package/build/esm/action-form/fields/FilePickerField.module.css +4 -0
- package/build/esm/action-form/fields/FormFieldRenderer.js +24 -12
- package/build/esm/action-form/fields/FormFieldRenderer.js.map +1 -1
- package/build/esm/action-form/fields/NumberInputField.js +12 -8
- package/build/esm/action-form/fields/NumberInputField.js.map +1 -1
- package/build/esm/action-form/fields/NumberInputField.module.css +4 -0
- package/build/esm/action-form/fields/TextAreaField.js +3 -1
- package/build/esm/action-form/fields/TextAreaField.js.map +1 -1
- package/build/esm/action-form/fields/TextInputField.js +3 -1
- package/build/esm/action-form/fields/TextInputField.js.map +1 -1
- package/build/esm/action-form/utils/extractValidationRules.js +186 -0
- package/build/esm/action-form/utils/extractValidationRules.js.map +1 -0
- package/build/esm/public/experimental.js.map +1 -1
- package/build/esm/shared/hooks/useAsyncAction.js +56 -0
- package/build/esm/shared/hooks/useAsyncAction.js.map +1 -0
- package/build/esm/shared/hooks/useIsMounted.js +32 -0
- package/build/esm/shared/hooks/useIsMounted.js.map +1 -0
- package/build/types/action-form/ActionFormApi.d.ts +1 -1
- package/build/types/action-form/ActionFormApi.d.ts.map +1 -1
- package/build/types/action-form/BaseForm.d.ts.map +1 -1
- package/build/types/action-form/FormField.d.ts +1 -0
- package/build/types/action-form/FormField.d.ts.map +1 -1
- package/build/types/action-form/FormFieldApi.d.ts +39 -12
- package/build/types/action-form/FormFieldApi.d.ts.map +1 -1
- package/build/types/action-form/fields/DatetimePickerField.d.ts +1 -1
- package/build/types/action-form/fields/DatetimePickerField.d.ts.map +1 -1
- package/build/types/action-form/fields/FieldBridge.d.ts.map +1 -1
- package/build/types/action-form/fields/FormFieldRenderer.d.ts +2 -0
- package/build/types/action-form/fields/FormFieldRenderer.d.ts.map +1 -1
- package/build/types/action-form/fields/NumberInputField.d.ts +1 -1
- package/build/types/action-form/fields/NumberInputField.d.ts.map +1 -1
- package/build/types/action-form/fields/TextAreaField.d.ts +1 -1
- package/build/types/action-form/fields/TextAreaField.d.ts.map +1 -1
- package/build/types/action-form/fields/TextInputField.d.ts +1 -1
- package/build/types/action-form/fields/TextInputField.d.ts.map +1 -1
- package/build/types/action-form/utils/extractValidationRules.d.ts +13 -0
- package/build/types/action-form/utils/extractValidationRules.d.ts.map +1 -0
- package/build/types/public/experimental.d.ts +1 -1
- package/build/types/public/experimental.d.ts.map +1 -1
- package/build/types/shared/hooks/useAsyncAction.d.ts +16 -0
- package/build/types/shared/hooks/useAsyncAction.d.ts.map +1 -0
- package/build/types/shared/hooks/useIsMounted.d.ts +5 -0
- package/build/types/shared/hooks/useIsMounted.d.ts.map +1 -0
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @osdk/react-components
|
|
2
2
|
|
|
3
|
+
## 0.4.0-main-90a01d202dfa1e497125bd6cfcdc8175278fb7ec
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c144b04: Add form field validation with onTouched mode, error display, and submit tooltip
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [0fb9c8a]
|
|
12
|
+
- Updated dependencies [f34a1ce]
|
|
13
|
+
- @osdk/client@2.9.1-main-90a01d202dfa1e497125bd6cfcdc8175278fb7ec
|
|
14
|
+
- @osdk/react@0.11.1-main-90a01d202dfa1e497125bd6cfcdc8175278fb7ec
|
|
15
|
+
- @osdk/api@2.9.1-main-90a01d202dfa1e497125bd6cfcdc8175278fb7ec
|
|
16
|
+
|
|
3
17
|
## 0.3.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActionFormApi.js","names":[],"sources":["ActionFormApi.ts"],"sourcesContent":["/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n ActionDefinition,\n ActionEditResponse,\n ActionValidationResponse,\n} from \"@osdk/api\";\nimport type { ActionValidationError } from \"@osdk/client\";\nimport type {\n ActionParameters,\n FieldKey,\n FieldValueType,\n FormFieldDefinition,\n RendererFieldDefinition,\n} from \"./FormFieldApi.js\";\n\n/**\n * Props for the ActionForm component.\n *\n * A discriminated union ensures that controlled mode (formState provided)\n * always requires onFormStateChange, and uncontrolled mode makes `onFormStateChange` optional\n */\nexport type ActionFormProps<Q extends ActionDefinition<unknown>> =\n | (ActionFormConfigProps<Q> & {\n formState: FormState<Q>;\n onFormStateChange: (\n updater: (prevState: FormState<Q>) => FormState<Q>,\n ) => void;\n })\n | (ActionFormConfigProps<Q> & {\n formState?: undefined;\n onFormStateChange?: (\n updater: (prevState: FormState<Q>) => FormState<Q>,\n ) => void;\n });\n\ninterface ActionFormConfigProps<Q extends ActionDefinition<unknown>>\n extends Pick<BaseFormProps, \"formTitle\" | \"isSubmitDisabled\">\n{\n actionDefinition: Q;\n\n /**\n * If not supplied, field definitions are constructed from `ActionParameters`.\n */\n formFieldDefinitions?: ReadonlyArray<FormFieldDefinition<Q>>;\n\n /**\n * If supplied, this will override the default submit action\n * By default, the action's applyAction will be called\n *\n * @param formState all field values when onSubmit is called\n * @param applyAction the function to execute the action\n * @returns a promise of the submission response\n */\n onSubmit?: (\n formState: FormState<Q>,\n applyAction: (\n args: ActionParameters<Q>,\n ) => Promise<ActionEditResponse | undefined>,\n ) => Promise<unknown> | void;\n\n /**\n * Called when the validation response is returned from a validateOnly submission\n *\n * @param results the validation response\n */\n onValidationResponse?: (results: ActionValidationResponse) => void;\n\n /**\n * Called when the action is successfully executed from a non-validateOnly submission\n *\n * @param results the submission response\n */\n onSuccess?: (results: ActionEditResponse | undefined) => void;\n\n /**\n * Called when there is an error in form submission\n *\n * @param error the error that occurred\n */\n onError?: (error: FormError) => void;\n}\n\n/**\n * Form values mapping parameter names to their values\n */\nexport type FormState<Q extends ActionDefinition<unknown>> = {\n [K in FieldKey<Q>]?: FieldValueType<Q, K>;\n};\n\n/**\n * Form error discriminated union\n */\nexport type FormError =\n | { type: \"validation\"; error: ActionValidationError }\n | { type: \"submission\"; error: unknown }\n | { type: \"unknown\"; error: unknown };\n\n/**\n * Props for the `BaseForm` component, which renders a form without\n * OSDK data fetching.\n *\n * Uses a discriminated union so that controlled mode (`formState` provided)\n * always requires `onFieldValueChange`, and uncontrolled mode omits both.\n * `onSubmit` receives the current form state so callers can access values\n * even in uncontrolled mode.\n */\nexport type BaseFormProps =\n & BaseFormCommonProps\n & (\n | {\n formState: Record<string, unknown>;\n onFieldValueChange: (fieldKey: string, value: unknown) => void;\n }\n | {\n formState?: undefined;\n onFieldValueChange?: (fieldKey: string, value: unknown) => void;\n }\n );\n\ninterface BaseFormCommonProps {\n formTitle?: string;\n fieldDefinitions: ReadonlyArray<RendererFieldDefinition>;\n onSubmit: (formState: Record<string, unknown>) => void;\n isSubmitDisabled?: boolean;\n isPending?: boolean;\n isLoading?: boolean;\n className?: string;\n}\n"],"mappings":"","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"ActionFormApi.js","names":[],"sources":["ActionFormApi.ts"],"sourcesContent":["/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n ActionDefinition,\n ActionEditResponse,\n ActionValidationResponse,\n} from \"@osdk/api\";\nimport type { ActionValidationError } from \"@osdk/client\";\nimport type {\n ActionParameters,\n FieldKey,\n FieldValueType,\n FormFieldDefinition,\n RendererFieldDefinition,\n} from \"./FormFieldApi.js\";\n\n/**\n * Props for the ActionForm component.\n *\n * A discriminated union ensures that controlled mode (formState provided)\n * always requires onFormStateChange, and uncontrolled mode makes `onFormStateChange` optional\n */\nexport type ActionFormProps<Q extends ActionDefinition<unknown>> =\n | (ActionFormConfigProps<Q> & {\n formState: FormState<Q>;\n onFormStateChange: (\n updater: (prevState: FormState<Q>) => FormState<Q>,\n ) => void;\n })\n | (ActionFormConfigProps<Q> & {\n formState?: undefined;\n onFormStateChange?: (\n updater: (prevState: FormState<Q>) => FormState<Q>,\n ) => void;\n });\n\ninterface ActionFormConfigProps<Q extends ActionDefinition<unknown>>\n extends Pick<BaseFormProps, \"formTitle\" | \"isSubmitDisabled\">\n{\n actionDefinition: Q;\n\n /**\n * If not supplied, field definitions are constructed from `ActionParameters`.\n */\n formFieldDefinitions?: ReadonlyArray<FormFieldDefinition<Q>>;\n\n /**\n * If supplied, this will override the default submit action\n * By default, the action's applyAction will be called\n *\n * @param formState all field values when onSubmit is called\n * @param applyAction the function to execute the action\n * @returns a promise of the submission response\n */\n onSubmit?: (\n formState: FormState<Q>,\n applyAction: (\n args: ActionParameters<Q>,\n ) => Promise<ActionEditResponse | undefined>,\n ) => Promise<unknown> | void;\n\n /**\n * Called when the validation response is returned from a validateOnly submission\n *\n * @param results the validation response\n */\n onValidationResponse?: (results: ActionValidationResponse) => void;\n\n /**\n * Called when the action is successfully executed from a non-validateOnly submission\n *\n * @param results the submission response\n */\n onSuccess?: (results: ActionEditResponse | undefined) => void;\n\n /**\n * Called when there is an error in form submission\n *\n * @param error the error that occurred\n */\n onError?: (error: FormError) => void;\n}\n\n/**\n * Form values mapping parameter names to their values\n */\nexport type FormState<Q extends ActionDefinition<unknown>> = {\n [K in FieldKey<Q>]?: FieldValueType<Q, K>;\n};\n\n/**\n * Form error discriminated union\n */\nexport type FormError =\n | { type: \"validation\"; error: ActionValidationError }\n | { type: \"submission\"; error: unknown }\n | { type: \"unknown\"; error: unknown };\n\n/**\n * Props for the `BaseForm` component, which renders a form without\n * OSDK data fetching.\n *\n * Uses a discriminated union so that controlled mode (`formState` provided)\n * always requires `onFieldValueChange`, and uncontrolled mode omits both.\n * `onSubmit` receives the current form state so callers can access values\n * even in uncontrolled mode.\n */\nexport type BaseFormProps =\n & BaseFormCommonProps\n & (\n | {\n formState: Record<string, unknown>;\n onFieldValueChange: (fieldKey: string, value: unknown) => void;\n }\n | {\n formState?: undefined;\n onFieldValueChange?: (fieldKey: string, value: unknown) => void;\n }\n );\n\ninterface BaseFormCommonProps {\n formTitle?: string;\n fieldDefinitions: ReadonlyArray<RendererFieldDefinition>;\n onSubmit: (formState: Record<string, unknown>) => Promise<void> | void;\n isSubmitDisabled?: boolean;\n isPending?: boolean;\n isLoading?: boolean;\n className?: string;\n}\n"],"mappings":"","ignoreList":[]}
|
|
@@ -14,13 +14,19 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { Error as ErrorIcon } from "@blueprintjs/icons";
|
|
17
18
|
import classNames from "classnames";
|
|
18
|
-
import React, { memo, useCallback, useMemo } from "react";
|
|
19
|
+
import React, { memo, useCallback, useMemo, useState } from "react";
|
|
19
20
|
import { useForm } from "react-hook-form";
|
|
20
21
|
import { ActionButton } from "../base-components/action-button/ActionButton.js";
|
|
22
|
+
import { Tooltip } from "../base-components/tooltip/Tooltip.js";
|
|
23
|
+
import { useAsyncAction } from "../shared/hooks/useAsyncAction.js";
|
|
21
24
|
import styles from "./BaseForm.module.css.js";
|
|
22
25
|
import { FieldBridge } from "./fields/FieldBridge.js";
|
|
23
26
|
import { FormHeader } from "./FormHeader.js";
|
|
27
|
+
|
|
28
|
+
// Using a lower trigger delay for tooltips in forms so the user can see the errors quickly
|
|
29
|
+
const TOOLTIP_TRIGGER_DELAY_MS = 200;
|
|
24
30
|
export const BaseForm = /*#__PURE__*/memo(function ({
|
|
25
31
|
formTitle,
|
|
26
32
|
fieldDefinitions,
|
|
@@ -35,25 +41,63 @@ export const BaseForm = /*#__PURE__*/memo(function ({
|
|
|
35
41
|
const defaultValues = useMemo(() => buildDefaultValues(fieldDefinitions), [fieldDefinitions]);
|
|
36
42
|
const {
|
|
37
43
|
control,
|
|
38
|
-
|
|
44
|
+
trigger,
|
|
45
|
+
getValues,
|
|
46
|
+
formState: {
|
|
47
|
+
errors
|
|
48
|
+
}
|
|
39
49
|
} = useForm({
|
|
50
|
+
// Validate on blur first, then revalidate on change after the first
|
|
51
|
+
// error. This gives the user a chance to finish typing before seeing
|
|
52
|
+
// errors, while staying responsive once an error is surfaced.
|
|
53
|
+
mode: "onTouched",
|
|
40
54
|
...(controlledFormState != null ? {
|
|
41
55
|
values: controlledFormState
|
|
42
56
|
} : {
|
|
43
57
|
defaultValues
|
|
44
58
|
})
|
|
45
59
|
});
|
|
46
|
-
const
|
|
60
|
+
const [hasAttemptedSubmit, setHasAttemptedSubmit] = useState(false);
|
|
61
|
+
const {
|
|
62
|
+
isPending: isSubmitting,
|
|
63
|
+
error: submissionError,
|
|
64
|
+
execute: executeSubmit,
|
|
65
|
+
clearError
|
|
66
|
+
} = useAsyncAction(onSubmit);
|
|
67
|
+
const submissionErrorMessage = submissionError != null ? submissionError instanceof Error ? submissionError.message
|
|
68
|
+
// TODO: provide better error message
|
|
69
|
+
: "Submission failed" : undefined;
|
|
70
|
+
const handleFormSubmit = useCallback(async e => {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
setHasAttemptedSubmit(true);
|
|
73
|
+
const isValid = await trigger();
|
|
74
|
+
if (!isValid) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
47
78
|
// In controlled mode, always submit the controlled state, not RHF's
|
|
48
79
|
// internal state. Between a user keystroke and the parent re-rendering,
|
|
49
80
|
// RHF's store may hold the user-typed value rather than the parent's
|
|
50
81
|
// value. Using controlledFormState directly preserves the existing
|
|
51
82
|
// guarantee that controlled mode submits the parent's state.
|
|
52
|
-
|
|
53
|
-
}, [
|
|
83
|
+
await executeSubmit(controlledFormState ?? getValues());
|
|
84
|
+
}, [trigger, executeSubmit, controlledFormState, getValues]);
|
|
85
|
+
const handleFieldChange = useCallback((fieldKey, value) => {
|
|
86
|
+
clearError();
|
|
87
|
+
onFieldValueChange?.(fieldKey, value);
|
|
88
|
+
}, [clearError, onFieldValueChange]);
|
|
89
|
+
const labelByFieldKey = useMemo(() => new Map(fieldDefinitions.map(d => [d.fieldKey, d.label])), [fieldDefinitions]);
|
|
90
|
+
|
|
91
|
+
// RHF reuses the same errors object reference across renders so we cannot memoize errorEntries
|
|
92
|
+
const errorEntries = Object.entries(errors).map(([key, entry]) => ({
|
|
93
|
+
label: labelByFieldKey.get(key) ?? key,
|
|
94
|
+
message: entry?.message ?? "Invalid"
|
|
95
|
+
}));
|
|
96
|
+
const areErrorsPresent = errorEntries.length > 0;
|
|
97
|
+
const buttonErrorMessage = areErrorsPresent ? "Some fields are invalid" : submissionErrorMessage;
|
|
54
98
|
return /*#__PURE__*/React.createElement("form", {
|
|
55
99
|
className: classNames(styles.osdkForm, className),
|
|
56
|
-
onSubmit:
|
|
100
|
+
onSubmit: handleFormSubmit
|
|
57
101
|
}, formTitle != null && /*#__PURE__*/React.createElement(FormHeader, {
|
|
58
102
|
title: formTitle
|
|
59
103
|
}), isLoading && fieldDefinitions.length === 0 && /*#__PURE__*/React.createElement("div", {
|
|
@@ -64,14 +108,18 @@ export const BaseForm = /*#__PURE__*/memo(function ({
|
|
|
64
108
|
key: fieldDef.fieldKey,
|
|
65
109
|
fieldDef: fieldDef,
|
|
66
110
|
control: control,
|
|
67
|
-
onExternalChange:
|
|
111
|
+
onExternalChange: handleFieldChange
|
|
68
112
|
}))), /*#__PURE__*/React.createElement("div", {
|
|
69
113
|
className: styles.osdkFormFooter
|
|
70
|
-
}, /*#__PURE__*/React.createElement(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
},
|
|
114
|
+
}, /*#__PURE__*/React.createElement(ErrorIndicator, {
|
|
115
|
+
errorEntries: errorEntries
|
|
116
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
117
|
+
className: styles.osdkFormSubmitButton
|
|
118
|
+
}, /*#__PURE__*/React.createElement(SubmitButton, {
|
|
119
|
+
isPending: isPending || isSubmitting,
|
|
120
|
+
isSubmitDisabled: isSubmitDisabled || hasAttemptedSubmit && areErrorsPresent,
|
|
121
|
+
errorMessage: buttonErrorMessage
|
|
122
|
+
}))));
|
|
75
123
|
});
|
|
76
124
|
function buildDefaultValues(fieldDefinitions) {
|
|
77
125
|
const values = {};
|
|
@@ -83,4 +131,44 @@ function buildDefaultValues(fieldDefinitions) {
|
|
|
83
131
|
}
|
|
84
132
|
return values;
|
|
85
133
|
}
|
|
134
|
+
const SubmitButton = /*#__PURE__*/memo(function ({
|
|
135
|
+
isPending,
|
|
136
|
+
isSubmitDisabled,
|
|
137
|
+
errorMessage
|
|
138
|
+
}) {
|
|
139
|
+
const buttonLabel = isPending ? "Submitting\u2026" : "Submit";
|
|
140
|
+
const button = /*#__PURE__*/React.createElement(ActionButton, {
|
|
141
|
+
type: "submit",
|
|
142
|
+
variant: "primary",
|
|
143
|
+
disabled: isSubmitDisabled || isPending
|
|
144
|
+
}, buttonLabel);
|
|
145
|
+
if (errorMessage == null) {
|
|
146
|
+
return button;
|
|
147
|
+
}
|
|
148
|
+
return /*#__PURE__*/React.createElement(Tooltip.Root, {
|
|
149
|
+
defaultOpen: true
|
|
150
|
+
}, /*#__PURE__*/React.createElement(Tooltip.Trigger, {
|
|
151
|
+
delay: TOOLTIP_TRIGGER_DELAY_MS
|
|
152
|
+
}, button), /*#__PURE__*/React.createElement(Tooltip.Portal, null, /*#__PURE__*/React.createElement(Tooltip.Positioner, null, /*#__PURE__*/React.createElement(Tooltip.Popup, null, /*#__PURE__*/React.createElement(Tooltip.Arrow, null), errorMessage))));
|
|
153
|
+
});
|
|
154
|
+
// memo omitted: errorEntries is always a new array (RHF reuses the same errors ref)
|
|
155
|
+
function ErrorIndicator({
|
|
156
|
+
errorEntries
|
|
157
|
+
}) {
|
|
158
|
+
if (errorEntries.length === 0) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const count = errorEntries.length;
|
|
162
|
+
return /*#__PURE__*/React.createElement(Tooltip.Root, null, /*#__PURE__*/React.createElement(Tooltip.Trigger, {
|
|
163
|
+
delay: TOOLTIP_TRIGGER_DELAY_MS
|
|
164
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
165
|
+
className: styles.osdkFormErrorIndicator
|
|
166
|
+
}, /*#__PURE__*/React.createElement(ErrorIcon, {
|
|
167
|
+
size: 14
|
|
168
|
+
}), count === 1 ? "1 issue" : `${count} issues`)), /*#__PURE__*/React.createElement(Tooltip.Portal, null, /*#__PURE__*/React.createElement(Tooltip.Positioner, null, /*#__PURE__*/React.createElement(Tooltip.Popup, null, /*#__PURE__*/React.createElement(Tooltip.Arrow, null), /*#__PURE__*/React.createElement("ul", {
|
|
169
|
+
className: styles.osdkFormErrorList
|
|
170
|
+
}, errorEntries.map(entry => /*#__PURE__*/React.createElement("li", {
|
|
171
|
+
key: entry.label
|
|
172
|
+
}, /*#__PURE__*/React.createElement("strong", null, entry.label, ":"), " ", entry.message)))))));
|
|
173
|
+
}
|
|
86
174
|
//# sourceMappingURL=BaseForm.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseForm.js","names":["classNames","React","memo","useCallback","useMemo","useForm","ActionButton","styles","FieldBridge","FormHeader","BaseForm","formTitle","fieldDefinitions","formState","controlledFormState","onFieldValueChange","onSubmit","isSubmitDisabled","isPending","isLoading","className","defaultValues","buildDefaultValues","control","handleSubmit","rhfHandleSubmit","values","onFormSubmit","rhfValues","createElement","osdkForm","title","length","role","osdkFormFields","map","fieldDef","key","fieldKey","onExternalChange","osdkFormFooter","type","variant","disabled","def","props","fieldComponentProps","defaultValue"],"sources":["BaseForm.tsx"],"sourcesContent":["/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport classNames from \"classnames\";\nimport React, { memo, useCallback, useMemo } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { ActionButton } from \"../base-components/action-button/ActionButton.js\";\nimport type { BaseFormProps } from \"./ActionFormApi.js\";\nimport styles from \"./BaseForm.module.css\";\nimport { FieldBridge } from \"./fields/FieldBridge.js\";\nimport type { RendererFieldDefinition } from \"./FormFieldApi.js\";\nimport { FormHeader } from \"./FormHeader.js\";\n\nexport const BaseForm: React.FC<BaseFormProps> = memo(function BaseFormFn({\n formTitle,\n fieldDefinitions,\n formState: controlledFormState,\n onFieldValueChange,\n onSubmit,\n isSubmitDisabled = false,\n isPending = false,\n isLoading = false,\n className,\n}: BaseFormProps): React.ReactElement {\n const isControlled = controlledFormState != null;\n\n const defaultValues = useMemo(\n () => buildDefaultValues(fieldDefinitions),\n [fieldDefinitions],\n );\n\n const { control, handleSubmit: rhfHandleSubmit } = useForm<\n Record<string, unknown>\n >({\n ...(isControlled ? { values: controlledFormState } : { defaultValues }),\n });\n\n const onFormSubmit = useCallback(\n (rhfValues: Record<string, unknown>) => {\n // In controlled mode, always submit the controlled state, not RHF's\n // internal state. Between a user keystroke and the parent re-rendering,\n // RHF's store may hold the user-typed value rather than the parent's\n // value. Using controlledFormState directly preserves the existing\n // guarantee that controlled mode submits the parent's state.\n onSubmit(controlledFormState ?? rhfValues);\n },\n [onSubmit, controlledFormState],\n );\n\n return (\n <form\n className={classNames(styles.osdkForm, className)}\n onSubmit={rhfHandleSubmit(onFormSubmit)}\n >\n {formTitle != null && <FormHeader title={formTitle} />}\n {isLoading && fieldDefinitions.length === 0 && (\n <div role=\"status\">Loading form fields...</div>\n )}\n <div className={styles.osdkFormFields}>\n {fieldDefinitions.map((fieldDef) => (\n <FieldBridge\n key={fieldDef.fieldKey}\n fieldDef={fieldDef}\n control={control}\n onExternalChange={onFieldValueChange}\n />\n ))}\n </div>\n <div className={styles.osdkFormFooter}>\n <ActionButton\n type=\"submit\"\n variant=\"primary\"\n disabled={isSubmitDisabled || isPending}\n >\n {isPending ? \"Submitting…\" : \"Submit\"}\n </ActionButton>\n </div>\n </form>\n );\n});\n\nfunction buildDefaultValues(\n fieldDefinitions: ReadonlyArray<RendererFieldDefinition>,\n): Record<string, unknown> {\n const values: Record<string, unknown> = {};\n for (const def of fieldDefinitions) {\n const props: Record<string, unknown> = def.fieldComponentProps;\n if (\"defaultValue\" in props) {\n values[def.fieldKey] = props.defaultValue;\n }\n }\n return values;\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"BaseForm.js","names":["Error","ErrorIcon","classNames","React","memo","useCallback","useMemo","useState","useForm","ActionButton","Tooltip","useAsyncAction","styles","FieldBridge","FormHeader","TOOLTIP_TRIGGER_DELAY_MS","BaseForm","formTitle","fieldDefinitions","formState","controlledFormState","onFieldValueChange","onSubmit","isSubmitDisabled","isPending","isLoading","className","defaultValues","buildDefaultValues","control","trigger","getValues","errors","mode","values","hasAttemptedSubmit","setHasAttemptedSubmit","isSubmitting","error","submissionError","execute","executeSubmit","clearError","submissionErrorMessage","message","undefined","handleFormSubmit","e","preventDefault","isValid","handleFieldChange","fieldKey","value","labelByFieldKey","Map","map","d","label","errorEntries","Object","entries","key","entry","get","areErrorsPresent","length","buttonErrorMessage","createElement","osdkForm","title","role","osdkFormFields","fieldDef","onExternalChange","osdkFormFooter","ErrorIndicator","osdkFormSubmitButton","SubmitButton","errorMessage","def","props","fieldComponentProps","defaultValue","buttonLabel","button","type","variant","disabled","Root","defaultOpen","Trigger","delay","Portal","Positioner","Popup","Arrow","count","osdkFormErrorIndicator","size","osdkFormErrorList"],"sources":["BaseForm.tsx"],"sourcesContent":["/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Error as ErrorIcon } from \"@blueprintjs/icons\";\nimport classNames from \"classnames\";\nimport React, { memo, useCallback, useMemo, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { ActionButton } from \"../base-components/action-button/ActionButton.js\";\nimport { Tooltip } from \"../base-components/tooltip/Tooltip.js\";\nimport { useAsyncAction } from \"../shared/hooks/useAsyncAction.js\";\nimport type { BaseFormProps } from \"./ActionFormApi.js\";\nimport styles from \"./BaseForm.module.css\";\nimport { FieldBridge } from \"./fields/FieldBridge.js\";\nimport type { RendererFieldDefinition } from \"./FormFieldApi.js\";\nimport { FormHeader } from \"./FormHeader.js\";\n\n// Using a lower trigger delay for tooltips in forms so the user can see the errors quickly\nconst TOOLTIP_TRIGGER_DELAY_MS = 200;\n\nexport const BaseForm: React.FC<BaseFormProps> = memo(function BaseFormFn({\n formTitle,\n fieldDefinitions,\n formState: controlledFormState,\n onFieldValueChange,\n onSubmit,\n isSubmitDisabled = false,\n isPending = false,\n isLoading = false,\n className,\n}: BaseFormProps): React.ReactElement {\n const isControlled = controlledFormState != null;\n\n const defaultValues = useMemo(\n () => buildDefaultValues(fieldDefinitions),\n [fieldDefinitions],\n );\n\n const {\n control,\n trigger,\n getValues,\n formState: { errors },\n } = useForm<Record<string, unknown>>({\n // Validate on blur first, then revalidate on change after the first\n // error. This gives the user a chance to finish typing before seeing\n // errors, while staying responsive once an error is surfaced.\n mode: \"onTouched\",\n ...(isControlled ? { values: controlledFormState } : { defaultValues }),\n });\n\n const [hasAttemptedSubmit, setHasAttemptedSubmit] = useState(false);\n\n const {\n isPending: isSubmitting,\n error: submissionError,\n execute: executeSubmit,\n clearError,\n } = useAsyncAction(onSubmit);\n const submissionErrorMessage = submissionError != null\n ? submissionError instanceof Error\n ? submissionError.message\n // TODO: provide better error message\n : \"Submission failed\"\n : undefined;\n\n const handleFormSubmit = useCallback(\n async (e: React.FormEvent) => {\n e.preventDefault();\n setHasAttemptedSubmit(true);\n\n const isValid = await trigger();\n if (!isValid) {\n return;\n }\n\n // In controlled mode, always submit the controlled state, not RHF's\n // internal state. Between a user keystroke and the parent re-rendering,\n // RHF's store may hold the user-typed value rather than the parent's\n // value. Using controlledFormState directly preserves the existing\n // guarantee that controlled mode submits the parent's state.\n await executeSubmit(controlledFormState ?? getValues());\n },\n [trigger, executeSubmit, controlledFormState, getValues],\n );\n\n const handleFieldChange = useCallback(\n (fieldKey: string, value: unknown) => {\n clearError();\n onFieldValueChange?.(fieldKey, value);\n },\n [clearError, onFieldValueChange],\n );\n\n const isFormPending = isPending || isSubmitting;\n\n const labelByFieldKey = useMemo(\n () => new Map(fieldDefinitions.map((d) => [d.fieldKey, d.label])),\n [fieldDefinitions],\n );\n\n // RHF reuses the same errors object reference across renders so we cannot memoize errorEntries\n const errorEntries = Object.entries(errors).map(([key, entry]) => ({\n label: labelByFieldKey.get(key) ?? key,\n message: entry?.message ?? \"Invalid\",\n }));\n const areErrorsPresent = errorEntries.length > 0;\n const buttonErrorMessage = areErrorsPresent\n ? \"Some fields are invalid\"\n : submissionErrorMessage;\n\n return (\n <form\n className={classNames(styles.osdkForm, className)}\n onSubmit={handleFormSubmit}\n >\n {formTitle != null && <FormHeader title={formTitle} />}\n {isLoading && fieldDefinitions.length === 0 && (\n <div role=\"status\">Loading form fields...</div>\n )}\n <div className={styles.osdkFormFields}>\n {fieldDefinitions.map((fieldDef) => (\n <FieldBridge\n key={fieldDef.fieldKey}\n fieldDef={fieldDef}\n control={control}\n onExternalChange={handleFieldChange}\n />\n ))}\n </div>\n <div className={styles.osdkFormFooter}>\n <ErrorIndicator errorEntries={errorEntries} />\n <div className={styles.osdkFormSubmitButton}>\n <SubmitButton\n isPending={isFormPending}\n isSubmitDisabled={isSubmitDisabled\n || (hasAttemptedSubmit && areErrorsPresent)}\n errorMessage={buttonErrorMessage}\n />\n </div>\n </div>\n </form>\n );\n});\n\nfunction buildDefaultValues(\n fieldDefinitions: ReadonlyArray<RendererFieldDefinition>,\n): Record<string, unknown> {\n const values: Record<string, unknown> = {};\n for (const def of fieldDefinitions) {\n const props: Record<string, unknown> = def.fieldComponentProps;\n if (\"defaultValue\" in props) {\n values[def.fieldKey] = props.defaultValue;\n }\n }\n return values;\n}\n\ninterface ErrorEntry {\n label: string;\n message: string;\n}\n\ninterface SubmitButtonProps {\n isPending: boolean;\n isSubmitDisabled: boolean;\n errorMessage: string | undefined;\n}\n\nconst SubmitButton = memo(function SubmitButtonFn({\n isPending,\n isSubmitDisabled,\n errorMessage,\n}: SubmitButtonProps): React.ReactElement {\n const buttonLabel = isPending ? \"Submitting\\u2026\" : \"Submit\";\n const button = (\n <ActionButton\n type=\"submit\"\n variant=\"primary\"\n disabled={isSubmitDisabled || isPending}\n >\n {buttonLabel}\n </ActionButton>\n );\n\n if (errorMessage == null) {\n return button;\n }\n\n return (\n <Tooltip.Root defaultOpen={true}>\n <Tooltip.Trigger delay={TOOLTIP_TRIGGER_DELAY_MS}>\n {button}\n </Tooltip.Trigger>\n <Tooltip.Portal>\n <Tooltip.Positioner>\n <Tooltip.Popup>\n <Tooltip.Arrow />\n {errorMessage}\n </Tooltip.Popup>\n </Tooltip.Positioner>\n </Tooltip.Portal>\n </Tooltip.Root>\n );\n});\n\ninterface ErrorIndicatorProps {\n errorEntries: ReadonlyArray<ErrorEntry>;\n}\n\n// memo omitted: errorEntries is always a new array (RHF reuses the same errors ref)\nfunction ErrorIndicator({\n errorEntries,\n}: ErrorIndicatorProps): React.ReactElement | null {\n if (errorEntries.length === 0) {\n return null;\n }\n\n const count = errorEntries.length;\n\n return (\n <Tooltip.Root>\n <Tooltip.Trigger delay={TOOLTIP_TRIGGER_DELAY_MS}>\n <span className={styles.osdkFormErrorIndicator}>\n <ErrorIcon size={14} />\n {count === 1 ? \"1 issue\" : `${count} issues`}\n </span>\n </Tooltip.Trigger>\n <Tooltip.Portal>\n <Tooltip.Positioner>\n <Tooltip.Popup>\n <Tooltip.Arrow />\n <ul className={styles.osdkFormErrorList}>\n {errorEntries.map((entry) => (\n <li key={entry.label}>\n <strong>{entry.label}:</strong> {entry.message}\n </li>\n ))}\n </ul>\n </Tooltip.Popup>\n </Tooltip.Positioner>\n </Tooltip.Portal>\n </Tooltip.Root>\n );\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -30,9 +30,29 @@
|
|
|
30
30
|
.osdkFormFooter {
|
|
31
31
|
display: flex;
|
|
32
32
|
flex-direction: row;
|
|
33
|
-
justify-content:
|
|
33
|
+
justify-content: space-between;
|
|
34
|
+
align-items: center;
|
|
34
35
|
border-top: var(--osdk-surface-border-width) solid
|
|
35
36
|
var(--osdk-form-footer-border-color);
|
|
36
37
|
padding-block: var(--osdk-form-content-padding);
|
|
37
38
|
padding-inline: var(--osdk-form-content-padding);
|
|
38
39
|
}
|
|
40
|
+
|
|
41
|
+
.osdkFormSubmitButton {
|
|
42
|
+
margin-inline-start: auto;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.osdkFormErrorIndicator {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: calc(var(--osdk-surface-spacing) * 2);
|
|
49
|
+
color: var(--osdk-form-footer-error-color);
|
|
50
|
+
font-size: var(--osdk-form-footer-error-font-size);
|
|
51
|
+
cursor: help;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.osdkFormErrorList {
|
|
55
|
+
list-style: none;
|
|
56
|
+
margin: 0;
|
|
57
|
+
padding: 0;
|
|
58
|
+
}
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
const styles = {
|
|
3
3
|
"osdkForm": "BaseForm-module__osdkForm___76UCas8f",
|
|
4
4
|
"osdkFormFields": "BaseForm-module__osdkFormFields___3DthDz5L",
|
|
5
|
-
"osdkFormFooter": "BaseForm-module__osdkFormFooter___ZRbu-oda"
|
|
5
|
+
"osdkFormFooter": "BaseForm-module__osdkFormFooter___ZRbu-oda",
|
|
6
|
+
"osdkFormSubmitButton": "BaseForm-module__osdkFormSubmitButton___sTo2Mti-",
|
|
7
|
+
"osdkFormErrorIndicator": "BaseForm-module__osdkFormErrorIndicator___3yfyBDCQ",
|
|
8
|
+
"osdkFormErrorList": "BaseForm-module__osdkFormErrorList___a2yRW4tD"
|
|
6
9
|
};
|
|
7
10
|
|
|
8
11
|
export default styles;
|
|
@@ -22,10 +22,12 @@ export const FormField = /*#__PURE__*/memo(function ({
|
|
|
22
22
|
isRequired,
|
|
23
23
|
helperText,
|
|
24
24
|
error,
|
|
25
|
+
onBlur,
|
|
25
26
|
children
|
|
26
27
|
}) {
|
|
27
28
|
return /*#__PURE__*/React.createElement("div", {
|
|
28
|
-
className: styles.osdkFormField
|
|
29
|
+
className: styles.osdkFormField,
|
|
30
|
+
onBlur: onBlur
|
|
29
31
|
}, label != null && /*#__PURE__*/React.createElement("label", {
|
|
30
32
|
className: styles.osdkFormFieldLabel,
|
|
31
33
|
htmlFor: fieldKey
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormField.js","names":["React","memo","styles","FormField","fieldKey","label","isRequired","helperText","error","children","createElement","className","osdkFormField","osdkFormFieldLabel","htmlFor","osdkFormFieldRequired","osdkFormFieldError","role","osdkFormFieldHelperText"],"sources":["FormField.tsx"],"sourcesContent":["/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { memo } from \"react\";\nimport styles from \"./FormField.module.css\";\n\ninterface FormFieldProps {\n fieldKey: string;\n label?: string;\n isRequired?: boolean;\n helperText?: string;\n error?: string;\n children: React.ReactNode;\n}\n\nexport const FormField: React.FC<FormFieldProps> = memo(\n function FormFieldFn({\n fieldKey,\n label,\n isRequired,\n helperText,\n error,\n children,\n }: FormFieldProps): React.ReactElement {\n return (\n <div className={styles.osdkFormField}>\n {label != null && (\n <label className={styles.osdkFormFieldLabel} htmlFor={fieldKey}>\n {label}\n {isRequired === true && (\n <span\n className={styles.osdkFormFieldRequired}\n aria-label=\"required\"\n >\n {\" \"}*\n </span>\n )}\n </label>\n )}\n {children}\n {error != null && (\n <div className={styles.osdkFormFieldError} role=\"alert\">\n {error}\n </div>\n )}\n {helperText != null && (\n <div className={styles.osdkFormFieldHelperText}>{helperText}</div>\n )}\n </div>\n );\n },\n);\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,OAAOA,KAAK,IAAIC,IAAI,QAAQ,OAAO;AACnC,OAAOC,MAAM,MAAM,wBAAwB;
|
|
1
|
+
{"version":3,"file":"FormField.js","names":["React","memo","styles","FormField","fieldKey","label","isRequired","helperText","error","onBlur","children","createElement","className","osdkFormField","osdkFormFieldLabel","htmlFor","osdkFormFieldRequired","osdkFormFieldError","role","osdkFormFieldHelperText"],"sources":["FormField.tsx"],"sourcesContent":["/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { memo } from \"react\";\nimport styles from \"./FormField.module.css\";\n\ninterface FormFieldProps {\n fieldKey: string;\n label?: string;\n isRequired?: boolean;\n helperText?: string;\n error?: string;\n onBlur?: (e: React.FocusEvent<HTMLDivElement>) => void;\n children: React.ReactNode;\n}\n\nexport const FormField: React.FC<FormFieldProps> = memo(\n function FormFieldFn({\n fieldKey,\n label,\n isRequired,\n helperText,\n error,\n onBlur,\n children,\n }: FormFieldProps): React.ReactElement {\n return (\n <div className={styles.osdkFormField} onBlur={onBlur}>\n {label != null && (\n <label className={styles.osdkFormFieldLabel} htmlFor={fieldKey}>\n {label}\n {isRequired === true && (\n <span\n className={styles.osdkFormFieldRequired}\n aria-label=\"required\"\n >\n {\" \"}*\n </span>\n )}\n </label>\n )}\n {children}\n {error != null && (\n <div className={styles.osdkFormFieldError} role=\"alert\">\n {error}\n </div>\n )}\n {helperText != null && (\n <div className={styles.osdkFormFieldHelperText}>{helperText}</div>\n )}\n </div>\n );\n },\n);\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,OAAOA,KAAK,IAAIC,IAAI,QAAQ,OAAO;AACnC,OAAOC,MAAM,MAAM,wBAAwB;AAY3C,OAAO,MAAMC,SAAmC,gBAAGF,IAAI,CACrD,UAAqB;EACnBG,QAAQ;EACRC,KAAK;EACLC,UAAU;EACVC,UAAU;EACVC,KAAK;EACLC,MAAM;EACNC;AACc,CAAC,EAAsB;EACrC,oBACEV,KAAA,CAAAW,aAAA;IAAKC,SAAS,EAAEV,MAAM,CAACW,aAAc;IAACJ,MAAM,EAAEA;EAAO,GAClDJ,KAAK,IAAI,IAAI,iBACZL,KAAA,CAAAW,aAAA;IAAOC,SAAS,EAAEV,MAAM,CAACY,kBAAmB;IAACC,OAAO,EAAEX;EAAS,GAC5DC,KAAK,EACLC,UAAU,KAAK,IAAI,iBAClBN,KAAA,CAAAW,aAAA;IACEC,SAAS,EAAEV,MAAM,CAACc,qBAAsB;IACxC,cAAW;EAAU,GAEpB,GAAG,EAAC,GACD,CAEH,CACR,EACAN,QAAQ,EACRF,KAAK,IAAI,IAAI,iBACZR,KAAA,CAAAW,aAAA;IAAKC,SAAS,EAAEV,MAAM,CAACe,kBAAmB;IAACC,IAAI,EAAC;EAAO,GACpDV,KACE,CACN,EACAD,UAAU,IAAI,IAAI,iBACjBP,KAAA,CAAAW,aAAA;IAAKC,SAAS,EAAEV,MAAM,CAACiB;EAAwB,GAAEZ,UAAgB,CAEhE,CAAC;AAEV,CACF,CAAC","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormFieldApi.js","names":[],"sources":["FormFieldApi.ts"],"sourcesContent":["/*\n * Copyright 2026 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n ActionDefinition,\n ActionMetadata,\n ActionParam,\n CompileTimeMetadata,\n DataValueClientToWire,\n ObjectSet,\n ObjectTypeDefinition,\n} from \"@osdk/api\";\nimport type React from \"react\";\n\n/**\n * A form field definition specifies configuration for a single field\n */\nexport interface FormFieldDefinition<\n Q extends ActionDefinition<unknown>,\n K extends FieldKey<Q> = FieldKey<Q>,\n> {\n /**\n * The field's unique key\n */\n fieldKey: K;\n\n /**\n * Display label for the field\n */\n label: string;\n\n /**\n * Default value of the field\n */\n defaultValue?: FieldValueType<Q, K>;\n\n /**\n * The form field component type to render\n */\n fieldComponent: ValidFormFieldForPropertyType<FieldDescriptorType<Q, K>>;\n\n /**\n * Whether the field is required\n */\n isRequired?: boolean;\n\n /**\n * Placeholder text\n */\n placeholder?: string;\n\n /**\n * Additional information to display on this field\n * The placement of helper text depends on the value of helperTextPlacement prop\n */\n helperText?: string;\n\n /**\n * The placement of the helper text either below the field or in a tooltip\n *\n * @default \"tooltip\"\n */\n helperTextPlacement?: \"bottom\" | \"tooltip\";\n\n /**\n * Whether the field is disabled\n */\n isDisabled?: boolean;\n\n /**\n * A callback to return a custom error message if validation failed\n *\n * @param validationRule the validation rule that failed with the error message\n * @returns the error message to display\n */\n onValidationError?: (error: ValidationError) => string;\n\n /**\n * Additional function to validate the field\n *\n * @param value the current field value\n * @returns a boolean promise indicating whether the value is valid\n */\n validate?: (value: FieldValueType<Q, K>) => Promise<boolean>;\n\n /**\n * The component props for the form field.\n * Excludes runtime props (value, onChange) which are managed by ActionForm.\n */\n fieldComponentProps: Omit<\n FormFieldPropsByType[\n ValidFormFieldForPropertyType<\n FieldDescriptorType<Q, K>\n >\n ],\n FormManagedProps<\n ValidFormFieldForPropertyType<FieldDescriptorType<Q, K>>\n >\n >;\n}\n\ntype ValidationError = { type: ValidationRule; error: string };\n\ntype ValidationRule =\n | \"required\"\n | \"min\"\n | \"max\"\n | \"minLength\"\n | \"maxLength\"\n | \"pattern\"\n | \"validate\";\n\n/**\n * Maps field types to their corresponding props\n */\nexport interface FormFieldPropsByType {\n DATETIME_PICKER: DatetimePickerFieldProps;\n DROPDOWN: DropdownFieldProps<unknown, boolean>;\n FILE_PICKER: FilePickerProps;\n NUMBER_INPUT: NumberInputFieldProps;\n OBJECT_SET: ObjectSetFieldProps<ObjectTypeDefinition>;\n RADIO_BUTTONS: RadioButtonsFieldProps<unknown>;\n TEXT_AREA: TextAreaFieldProps;\n TEXT_INPUT: TextInputFieldProps;\n CUSTOM: CustomFieldProps<unknown>;\n}\n\n/**\n * Datetime picker field props.\n *\n * When `formatDate` is omitted, ISO-like format is used (YYYY-MM-DD / YYYY-MM-DD HH:mm).\n */\nexport interface DatetimePickerFieldProps extends BaseFormFieldProps<Date> {\n /**\n * The earliest date the user can select.\n * If provided, this will be added to the field validation.\n */\n min?: Date;\n\n /**\n * The latest date the user can select.\n * If provided, this will be added to the field validation.\n */\n max?: Date;\n\n /**\n * Whether to show time picker.\n */\n showTime?: boolean;\n\n /**\n * Whether to close the popover after selecting a date.\n * @default true when `showTime` is false, false when `showTime` is true\n */\n closeOnSelection?: boolean;\n\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n\n /** Formats a Date for display in the trigger button. */\n formatDate?: (date: Date) => string;\n}\n\n/**\n * Dropdown field props with selectable items\n */\nexport interface DropdownFieldProps<V, Multiple extends boolean = false>\n extends BaseFormFieldProps<Multiple extends true ? V[] : V>\n{\n /**\n * Available items for the dropdown\n */\n items: V[];\n\n /**\n * Converts an item to a display string. Defaults to `String()`.\n */\n itemToStringLabel?: (item: V) => string;\n\n /**\n * Returns a unique string key for a list item. Used as the React `key`.\n * Falls back to the item's index when not provided.\n */\n itemToKey?: (item: V) => string;\n\n /**\n * Custom equality check for item values. Defaults to `Object.is`.\n * Required when items are objects to ensure correct selection matching.\n */\n isItemEqual?: (a: V, b: V) => boolean;\n\n /**\n * Whether the dropdown allows searching/filtering.\n * When true, renders a Combobox with a search input.\n * When false (default), renders a Select dropdown.\n */\n isSearchable?: boolean;\n\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n\n /**\n * Whether multiple values can be selected\n */\n isMultiple?: Multiple;\n}\n\nexport interface FilePickerProps extends BaseFormFieldProps<File | File[]> {\n /**\n * Whether multiple files can be selected\n */\n isMulti?: boolean;\n\n /**\n * Accepted file types (e.g., \"image/*\", \".pdf\")\n */\n accept?: string | string[];\n\n /**\n * Maximum file size in bytes\n */\n maxSize?: number;\n\n /**\n * The text displayed when no file is selected.\n *\n * @default \"No file chosen\"\n */\n text?: string;\n\n /**\n * The text displayed on the browse button.\n *\n * @default \"Browse\"\n */\n buttonText?: string;\n}\n\n/**\n * Text area field props\n */\nexport interface TextAreaFieldProps extends\n BaseFormFieldProps<string>,\n Pick<\n React.TextareaHTMLAttributes<HTMLTextAreaElement>,\n | \"rows\"\n | \"wrap\"\n /**\n * If provided, this will be added to the field validation\n */\n | \"minLength\"\n /**\n * If provided, this will be added to the field validation\n */\n | \"maxLength\"\n >\n{\n placeholder?: string;\n}\n\nexport interface TextInputFieldProps extends\n BaseFormFieldProps<string>,\n Pick<\n React.InputHTMLAttributes<HTMLInputElement>,\n /**\n * If provided, this will be added to the field validation\n */\n | \"minLength\"\n /**\n * If provided, this will be added to the field validation\n */\n | \"maxLength\"\n >\n{\n placeholder?: string;\n}\n\n/**\n * Number input field props\n */\nexport interface NumberInputFieldProps extends BaseFormFieldProps<number> {\n /**\n * Minimum allowed value.\n */\n min?: number;\n\n /**\n * Maximum allowed value.\n */\n max?: number;\n\n /**\n * Step increment for the input. Used by the stepper buttons and ArrowUp/ArrowDown keyboard stepping.\n *\n * @default 1\n */\n step?: number;\n\n /**\n * Placeholder text shown when the input is empty.\n */\n placeholder?: string;\n}\n\n/**\n * Radio buttons field props\n */\nexport interface RadioButtonsFieldProps<V> extends BaseFormFieldProps<V> {\n /**\n * Available options for radio buttons.\n *\n * Values are compared by reference equality (`===`). When options contain\n * non-primitive values, pass the same object references for `value` and\n * the corresponding option entry.\n */\n options: Option<V>[];\n}\n\n/**\n * Option interface for radio button options\n */\nexport interface Option<V> {\n label: string;\n value: V;\n}\n\n/**\n * Object set field displays the summary of the count of the given object set\n */\nexport interface ObjectSetFieldProps<T extends ObjectTypeDefinition>\n extends Pick<BaseFormFieldProps<ObjectSet<T>>, \"id\" | \"value\">\n{\n /**\n * Message displayed when no object set is provided.\n *\n * @default \"Object set is not defined\"\n */\n emptyMessage?: string;\n}\n\n/**\n * Custom field props for user-defined renderers\n */\nexport interface CustomFieldProps<V> extends BaseFormFieldProps<V> {\n /**\n * Custom renderer function\n */\n customRenderer: (props: BaseFormFieldProps<V>) => React.ReactNode;\n}\n\nexport interface BaseFormFieldProps<V> {\n /**\n * The HTML `id` attribute for the field input element.\n * Used for `<label htmlFor>` association.\n */\n id?: string;\n\n /**\n * The value of the form field\n */\n value: V | null;\n\n /**\n * The default value of the form field.\n */\n defaultValue?: V;\n\n /**\n * Called when the field value changes.\n *\n * ActionForm internally wraps this to pass the key to `onFieldValueChange`:\n * ```\n * <DropdownField\n * {...fieldDef}\n * onChange={(value) => onFieldValueChange(fieldDef.key, value)}\n * />\n * ```\n *\n * @param value The new value of the form field\n */\n onChange?: (value: V | null) => void;\n}\n\nexport type FieldKey<Q extends ActionDefinition<unknown>> =\n keyof ActionParameters<Q>;\n\n/**\n * Extracts parameters from an ActionDefinition\n */\nexport type ActionParameters<Q extends ActionDefinition<unknown>> =\n CompileTimeMetadata<Q>[\"parameters\"];\n\n/**\n * Extracts the value type for a specific parameter\n *\n * TODO: Re-use `BaseType`\n */\nexport type FieldValueType<\n Q extends ActionDefinition<unknown>,\n K extends keyof ActionParameters<Q> = keyof ActionParameters<Q>,\n> = ActionParameters<Q>[K][\"type\"] extends\n ActionMetadata.DataType.Object<infer T> ? ActionParam.ObjectType<T>\n : ActionParameters<Q>[K][\"type\"] extends ActionMetadata.DataType.ObjectSet<\n infer T\n > ? ActionParam.ObjectSetType<T>\n : ActionParameters<Q>[K][\"type\"] extends ActionMetadata.DataType.Struct<\n infer T\n > ? ActionParam.StructType<T>\n : ActionParameters<Q>[K][\"type\"] extends keyof DataValueClientToWire\n ? DataValueClientToWire[ActionParameters<Q>[K][\"type\"]]\n : never;\n\n/**\n * Extracts the parameter type descriptor for a specific action parameter.\n */\nexport type FieldDescriptorType<\n Q extends ActionDefinition<unknown> = ActionDefinition<unknown>,\n K extends keyof ActionParameters<Q> = keyof ActionParameters<Q>,\n> = ActionParameters<Q>[K][\"type\"];\n\n/**\n * Available form field component types\n */\nexport type FieldComponent =\n | \"DATETIME_PICKER\"\n | \"DROPDOWN\"\n | \"FILE_PICKER\"\n | \"NUMBER_INPUT\"\n | \"RADIO_BUTTONS\"\n | \"OBJECT_SET\"\n | \"TEXT_AREA\"\n | \"TEXT_INPUT\"\n | \"CUSTOM\";\n\n/**\n * Describes the data type of a form field, independent of OSDK.\n * Mirrors ActionMetadata.DataType to keep the rendering layer OSDK-agnostic.\n */\nexport type FieldType =\n | \"boolean\"\n | \"string\"\n | \"integer\"\n | \"long\"\n | \"double\"\n | \"datetime\"\n | \"timestamp\"\n | \"attachment\"\n | \"marking\"\n | \"mediaReference\"\n | \"objectType\"\n | \"geoshape\"\n | \"geohash\"\n | { type: \"object\"; object: string }\n | { type: \"objectSet\"; objectSet: string }\n | { type: \"interface\"; interface: string }\n | { type: \"struct\"; struct: Record<string, string> };\n\n/**\n * Props managed by form state infrastructure (FieldBridge / RHF).\n * Fields with onChange participate in form state → value and onChange are managed\n * externally. Read-only fields (no onChange, e.g. ObjectSetField) keep value in\n * fieldComponentProps so it bypasses form state cloning.\n */\ntype FormManagedProps<K extends FieldComponent> = \"onChange\" extends\n keyof FormFieldPropsByType[K] ? \"value\" | \"onChange\"\n : \"onChange\";\n\n/**\n * An OSDK-agnostic field definition used by BaseForm and FormFieldRenderer.\n * Contains only the information needed to render a single field — no generics,\n * no compile-time parameter constraints.\n *\n * Implemented as a distributed mapped type: switching on `fieldComponent`\n * narrows `fieldComponentProps` to the correct props type automatically.\n */\nexport type RendererFieldDefinition = {\n [K in FieldComponent]: {\n fieldKey: string;\n fieldComponent: K;\n fieldType?: FieldType;\n label: string;\n isRequired?: boolean;\n placeholder?: string;\n helperText?: string;\n helperTextPlacement?: \"bottom\" | \"tooltip\";\n fieldComponentProps: Omit<FormFieldPropsByType[K], FormManagedProps<K>>;\n };\n}[FieldComponent];\n\n/**\n * Gets valid form field types for a given property type\n */\nexport type ValidFormFieldForPropertyType<P extends FieldDescriptorType> =\n P extends \"objectSet\" ? \"OBJECT_SET\"\n : P extends \"object\" ? \"DROPDOWN\"\n : P extends \"mediaReference\" | \"attachment\" ? \"FILE_PICKER\"\n : P extends \"boolean\" ? \"RADIO_BUTTONS\" | \"DROPDOWN\"\n : P extends \"string\" ? \"TEXT_INPUT\" | \"TEXT_AREA\"\n : P extends \"datetime\" | \"timestamp\" ? \"DATETIME_PICKER\"\n : P extends\n | \"double\"\n | \"integer\"\n | \"long\"\n | \"float\"\n | \"short\"\n | \"byte\"\n | \"decimal\" ? \"NUMBER_INPUT\"\n : never;\n"],"mappings":"","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"FormFieldApi.js","names":[],"sources":["FormFieldApi.ts"],"sourcesContent":["/*\n * Copyright 2026 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n ActionDefinition,\n ActionMetadata,\n ActionParam,\n CompileTimeMetadata,\n DataValueClientToWire,\n ObjectSet,\n ObjectTypeDefinition,\n} from \"@osdk/api\";\nimport type React from \"react\";\n\n/**\n * A form field definition specifies configuration for a single field\n */\nexport interface FormFieldDefinition<\n Q extends ActionDefinition<unknown>,\n K extends FieldKey<Q> = FieldKey<Q>,\n> {\n /**\n * The field's unique key\n */\n fieldKey: K;\n\n /**\n * Display label for the field\n */\n label: string;\n\n /**\n * Default value of the field\n */\n defaultValue?: FieldValueType<Q, K>;\n\n /**\n * The form field component type to render\n */\n fieldComponent: ValidFormFieldForPropertyType<FieldDescriptorType<Q, K>>;\n\n /**\n * Whether the field is required\n */\n isRequired?: boolean;\n\n /**\n * Placeholder text\n */\n placeholder?: string;\n\n /**\n * Additional information to display on this field\n * The placement of helper text depends on the value of helperTextPlacement prop\n */\n helperText?: string;\n\n /**\n * The placement of the helper text either below the field or in a tooltip\n *\n * @default \"tooltip\"\n */\n helperTextPlacement?: \"bottom\" | \"tooltip\";\n\n /**\n * Whether the field is disabled\n */\n isDisabled?: boolean;\n\n /**\n * A callback to customize error messages when a built-in validation rule fails.\n * Receives a discriminated union with the constraint data (e.g., the min value\n * that was exceeded) so the message can reference the threshold.\n *\n * Return a string to override the default message, or `undefined` to keep it.\n */\n onValidationError?: (error: ValidationError) => string | undefined;\n\n /**\n * Additional function to validate the field.\n *\n * Return `undefined` if valid, or an error message string if invalid.\n */\n validate?: (value: FieldValueType<Q, K>) => Promise<string | undefined>;\n\n /**\n * The component props for the form field.\n * Excludes runtime props (value, onChange) which are managed by ActionForm.\n */\n fieldComponentProps: Omit<\n FormFieldPropsByType[\n ValidFormFieldForPropertyType<\n FieldDescriptorType<Q, K>\n >\n ],\n FormManagedProps<\n ValidFormFieldForPropertyType<FieldDescriptorType<Q, K>>\n >\n >;\n}\n\n/**\n * A discriminated union describing which validation rule failed and the\n * constraint data the user needs to build a meaningful error message.\n */\nexport type ValidationError =\n | { type: \"required\" }\n | { type: \"min\"; min: number | Date }\n | { type: \"max\"; max: number | Date }\n | { type: \"minLength\"; minLength: number }\n | { type: \"maxLength\"; maxLength: number }\n | { type: \"maxSize\"; maxSize: number }\n | { type: \"validate\"; message: string };\n\n/**\n * Maps field types to their corresponding props\n */\nexport interface FormFieldPropsByType {\n DATETIME_PICKER: DatetimePickerFieldProps;\n DROPDOWN: DropdownFieldProps<unknown, boolean>;\n FILE_PICKER: FilePickerProps;\n NUMBER_INPUT: NumberInputFieldProps;\n OBJECT_SET: ObjectSetFieldProps<ObjectTypeDefinition>;\n RADIO_BUTTONS: RadioButtonsFieldProps<unknown>;\n TEXT_AREA: TextAreaFieldProps;\n TEXT_INPUT: TextInputFieldProps;\n CUSTOM: CustomFieldProps<unknown>;\n}\n\n/**\n * Datetime picker field props.\n *\n * When `formatDate` is omitted, ISO-like format is used (YYYY-MM-DD / YYYY-MM-DD HH:mm).\n */\nexport interface DatetimePickerFieldProps extends BaseFormFieldProps<Date> {\n /**\n * The earliest date the user can select.\n * If provided, this will be added to the field validation.\n */\n min?: Date;\n\n /**\n * The latest date the user can select.\n * If provided, this will be added to the field validation.\n */\n max?: Date;\n\n /**\n * Whether to show time picker.\n */\n showTime?: boolean;\n\n /**\n * Whether to close the popover after selecting a date.\n * @default true when `showTime` is false, false when `showTime` is true\n */\n closeOnSelection?: boolean;\n\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n\n /** Formats a Date for display in the trigger button. */\n formatDate?: (date: Date) => string;\n}\n\n/**\n * Dropdown field props with selectable items\n */\nexport interface DropdownFieldProps<V, Multiple extends boolean = false>\n extends BaseFormFieldProps<Multiple extends true ? V[] : V>\n{\n /**\n * Available items for the dropdown\n */\n items: V[];\n\n /**\n * Converts an item to a display string. Defaults to `String()`.\n */\n itemToStringLabel?: (item: V) => string;\n\n /**\n * Returns a unique string key for a list item. Used as the React `key`.\n * Falls back to the item's index when not provided.\n */\n itemToKey?: (item: V) => string;\n\n /**\n * Custom equality check for item values. Defaults to `Object.is`.\n * Required when items are objects to ensure correct selection matching.\n */\n isItemEqual?: (a: V, b: V) => boolean;\n\n /**\n * Whether the dropdown allows searching/filtering.\n * When true, renders a Combobox with a search input.\n * When false (default), renders a Select dropdown.\n */\n isSearchable?: boolean;\n\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n\n /**\n * Whether multiple values can be selected\n */\n isMultiple?: Multiple;\n}\n\nexport interface FilePickerProps extends BaseFormFieldProps<File | File[]> {\n /**\n * Whether multiple files can be selected\n */\n isMulti?: boolean;\n\n /**\n * Accepted file types (e.g., \"image/*\", \".pdf\")\n */\n accept?: string | string[];\n\n /**\n * Maximum file size in bytes\n */\n maxSize?: number;\n\n /**\n * The text displayed when no file is selected.\n *\n * @default \"No file chosen\"\n */\n text?: string;\n\n /**\n * The text displayed on the browse button.\n *\n * @default \"Browse\"\n */\n buttonText?: string;\n}\n\n/**\n * Text area field props\n */\nexport interface TextAreaFieldProps extends\n BaseFormFieldProps<string>,\n Pick<\n React.TextareaHTMLAttributes<HTMLTextAreaElement>,\n | \"rows\"\n | \"wrap\"\n /**\n * If provided, this will be added to the field validation\n */\n | \"minLength\"\n /**\n * If provided, this will be added to the field validation\n */\n | \"maxLength\"\n >\n{\n placeholder?: string;\n}\n\nexport interface TextInputFieldProps extends\n BaseFormFieldProps<string>,\n Pick<\n React.InputHTMLAttributes<HTMLInputElement>,\n /**\n * If provided, this will be added to the field validation\n */\n | \"minLength\"\n /**\n * If provided, this will be added to the field validation\n */\n | \"maxLength\"\n >\n{\n placeholder?: string;\n}\n\n/**\n * Number input field props\n */\nexport interface NumberInputFieldProps extends BaseFormFieldProps<number> {\n /**\n * Minimum allowed value.\n */\n min?: number;\n\n /**\n * Maximum allowed value.\n */\n max?: number;\n\n /**\n * Step increment for the input. Used by the stepper buttons and ArrowUp/ArrowDown keyboard stepping.\n *\n * @default 1\n */\n step?: number;\n\n /**\n * Placeholder text shown when the input is empty.\n */\n placeholder?: string;\n}\n\n/**\n * Radio buttons field props\n */\nexport interface RadioButtonsFieldProps<V> extends BaseFormFieldProps<V> {\n /**\n * Available options for radio buttons.\n *\n * Values are compared by reference equality (`===`). When options contain\n * non-primitive values, pass the same object references for `value` and\n * the corresponding option entry.\n */\n options: Option<V>[];\n}\n\n/**\n * Option interface for radio button options\n */\nexport interface Option<V> {\n label: string;\n value: V;\n}\n\n/**\n * Object set field displays the summary of the count of the given object set\n */\nexport interface ObjectSetFieldProps<T extends ObjectTypeDefinition>\n extends Pick<BaseFormFieldProps<ObjectSet<T>>, \"id\" | \"value\">\n{\n /**\n * Message displayed when no object set is provided.\n *\n * @default \"Object set is not defined\"\n */\n emptyMessage?: string;\n}\n\n/**\n * Custom field props for user-defined renderers\n */\nexport interface CustomFieldProps<V> extends BaseFormFieldProps<V> {\n /**\n * Custom renderer function\n */\n customRenderer: (props: BaseFormFieldProps<V>) => React.ReactNode;\n}\n\nexport interface BaseFormFieldProps<V> {\n /**\n * The HTML `id` attribute for the field input element.\n * Used for `<label htmlFor>` association.\n */\n id?: string;\n\n /**\n * The validation error message for this field, if any.\n * When set, the field should display a visual error state.\n */\n error?: string;\n\n /**\n * The value of the form field\n */\n value: V | null;\n\n /**\n * The default value of the form field.\n */\n defaultValue?: V;\n\n /**\n * Called when the field value changes.\n *\n * ActionForm internally wraps this to pass the key to `onFieldValueChange`:\n * ```\n * <DropdownField\n * {...fieldDef}\n * onChange={(value) => onFieldValueChange(fieldDef.key, value)}\n * />\n * ```\n *\n * @param value The new value of the form field\n */\n onChange?: (value: V | null) => void;\n}\n\nexport type FieldKey<Q extends ActionDefinition<unknown>> =\n keyof ActionParameters<Q>;\n\n/**\n * Extracts parameters from an ActionDefinition\n */\nexport type ActionParameters<Q extends ActionDefinition<unknown>> =\n CompileTimeMetadata<Q>[\"parameters\"];\n\n/**\n * Extracts the value type for a specific parameter\n *\n * TODO: Re-use `BaseType`\n */\nexport type FieldValueType<\n Q extends ActionDefinition<unknown>,\n K extends keyof ActionParameters<Q> = keyof ActionParameters<Q>,\n> = ActionParameters<Q>[K][\"type\"] extends\n ActionMetadata.DataType.Object<infer T> ? ActionParam.ObjectType<T>\n : ActionParameters<Q>[K][\"type\"] extends ActionMetadata.DataType.ObjectSet<\n infer T\n > ? ActionParam.ObjectSetType<T>\n : ActionParameters<Q>[K][\"type\"] extends ActionMetadata.DataType.Struct<\n infer T\n > ? ActionParam.StructType<T>\n : ActionParameters<Q>[K][\"type\"] extends keyof DataValueClientToWire\n ? DataValueClientToWire[ActionParameters<Q>[K][\"type\"]]\n : never;\n\n/**\n * Extracts the parameter type descriptor for a specific action parameter.\n */\nexport type FieldDescriptorType<\n Q extends ActionDefinition<unknown> = ActionDefinition<unknown>,\n K extends keyof ActionParameters<Q> = keyof ActionParameters<Q>,\n> = ActionParameters<Q>[K][\"type\"];\n\n/**\n * Available form field component types\n */\nexport type FieldComponent =\n | \"DATETIME_PICKER\"\n | \"DROPDOWN\"\n | \"FILE_PICKER\"\n | \"NUMBER_INPUT\"\n | \"RADIO_BUTTONS\"\n | \"OBJECT_SET\"\n | \"TEXT_AREA\"\n | \"TEXT_INPUT\"\n | \"CUSTOM\";\n\n/**\n * Describes the data type of a form field, independent of OSDK.\n * Mirrors ActionMetadata.DataType to keep the rendering layer OSDK-agnostic.\n */\nexport type FieldType =\n | \"boolean\"\n | \"string\"\n | \"integer\"\n | \"long\"\n | \"double\"\n | \"datetime\"\n | \"timestamp\"\n | \"attachment\"\n | \"marking\"\n | \"mediaReference\"\n | \"objectType\"\n | \"geoshape\"\n | \"geohash\"\n | { type: \"object\"; object: string }\n | { type: \"objectSet\"; objectSet: string }\n | { type: \"interface\"; interface: string }\n | { type: \"struct\"; struct: Record<string, string> };\n\n/**\n * Props managed by form state infrastructure (FieldBridge / RHF).\n * Fields with onChange participate in form state → value and onChange are managed\n * externally. Read-only fields (no onChange, e.g. ObjectSetField) keep value in\n * fieldComponentProps so it bypasses form state cloning.\n */\ntype FormManagedProps<K extends FieldComponent> = \"onChange\" extends\n keyof FormFieldPropsByType[K] ? \"value\" | \"onChange\"\n : \"onChange\";\n\n/**\n * An OSDK-agnostic field definition used by BaseForm and FormFieldRenderer.\n * Contains only the information needed to render a single field — no generics,\n * no compile-time parameter constraints.\n *\n * Implemented as a distributed mapped type: switching on `fieldComponent`\n * narrows `fieldComponentProps` to the correct props type automatically.\n */\nexport type RendererFieldDefinition = {\n [K in FieldComponent]: {\n fieldKey: string;\n fieldComponent: K;\n fieldType?: FieldType;\n label: string;\n isRequired?: boolean;\n placeholder?: string;\n helperText?: string;\n helperTextPlacement?: \"bottom\" | \"tooltip\";\n validate?: (value: unknown) => Promise<string | undefined>;\n onValidationError?: (error: ValidationError) => string | undefined;\n fieldComponentProps: Omit<FormFieldPropsByType[K], FormManagedProps<K>>;\n };\n}[FieldComponent];\n\n/**\n * Gets valid form field types for a given property type\n */\nexport type ValidFormFieldForPropertyType<P extends FieldDescriptorType> =\n P extends \"objectSet\" ? \"OBJECT_SET\"\n : P extends \"object\" ? \"DROPDOWN\"\n : P extends \"mediaReference\" | \"attachment\" ? \"FILE_PICKER\"\n : P extends \"boolean\" ? \"RADIO_BUTTONS\" | \"DROPDOWN\"\n : P extends \"string\" ? \"TEXT_INPUT\" | \"TEXT_AREA\"\n : P extends \"datetime\" | \"timestamp\" ? \"DATETIME_PICKER\"\n : P extends\n | \"double\"\n | \"integer\"\n | \"long\"\n | \"float\"\n | \"short\"\n | \"byte\"\n | \"decimal\" ? \"NUMBER_INPUT\"\n : never;\n"],"mappings":"","ignoreList":[]}
|
|
@@ -31,6 +31,7 @@ export function DatetimePickerField({
|
|
|
31
31
|
id,
|
|
32
32
|
value,
|
|
33
33
|
onChange,
|
|
34
|
+
error,
|
|
34
35
|
min,
|
|
35
36
|
max,
|
|
36
37
|
placeholder,
|
|
@@ -82,7 +83,8 @@ export function DatetimePickerField({
|
|
|
82
83
|
}, /*#__PURE__*/React.createElement(Popover.Trigger, {
|
|
83
84
|
id: id,
|
|
84
85
|
className: classnames(styles.triggerButton, !hasValue && styles.triggerButtonPlaceholder),
|
|
85
|
-
"aria-label": hasValue ? undefined : "Select date"
|
|
86
|
+
"aria-label": hasValue ? undefined : "Select date",
|
|
87
|
+
"aria-invalid": error != null || undefined
|
|
86
88
|
}, displayText), /*#__PURE__*/React.createElement(Popover.Portal, null, /*#__PURE__*/React.createElement(Popover.Positioner, {
|
|
87
89
|
sideOffset: 4
|
|
88
90
|
}, /*#__PURE__*/React.createElement(Popover.Popup, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatetimePickerField.js","names":["Input","Popover","classnames","React","useCallback","useMemo","useState","formatDateForInput","formatDatetimeForDisplay","formatTime","styles","LazyDateCalendar","DATE_ZERO","Date","extractTime","date","DatetimePickerField","id","value","onChange","min","max","placeholder","formatDate","showTime","closeOnSelection","shouldCloseOnSelection","isOpen","setIsOpen","formatFn","formattedValue","handleCalendarSelect","selected","getTime","setHours","getHours","getMinutes","handleTimeChange","timeString","hoursStr","minutesStr","split","hours","parseInt","minutes","base","displayText","hasValue","timeValue","footer","createElement","className","timeFooter","type","onValueChange","timeInput","undefined","Root","open","onOpenChange","Trigger","triggerButton","triggerButtonPlaceholder","Portal","Positioner","sideOffset","Popup","popover","dateSelected","onSelect"],"sources":["DatetimePickerField.tsx"],"sourcesContent":["/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Input } from \"@base-ui/react/input\";\nimport { Popover } from \"@base-ui/react/popover\";\nimport classnames from \"classnames\";\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport {\n formatDateForInput,\n formatDatetimeForDisplay,\n formatTime,\n} from \"../../shared/dateUtils.js\";\nimport type { DatetimePickerFieldProps } from \"../FormFieldApi.js\";\nimport styles from \"./DatetimePickerField.module.css\";\nimport { LazyDateCalendar } from \"./LazyDateCalendar.js\";\n\n/** Midnight fallback so `extractTime` can reuse `formatTime` when no date is selected. */\nconst DATE_ZERO = new Date(2000, 0, 1, 0, 0, 0, 0);\n\nfunction extractTime(date: Date | null): string {\n return formatTime(date ?? DATE_ZERO);\n}\n\nexport function DatetimePickerField({\n id,\n value,\n onChange,\n min,\n max,\n placeholder,\n formatDate,\n showTime = false,\n closeOnSelection,\n}: DatetimePickerFieldProps): React.ReactElement {\n const shouldCloseOnSelection = closeOnSelection ?? !showTime;\n const [isOpen, setIsOpen] = useState(false);\n\n const formatFn = formatDate\n ?? (showTime ? formatDatetimeForDisplay : formatDateForInput);\n\n const formattedValue = value != null ? formatFn(value) : \"\";\n\n const handleCalendarSelect = useCallback(\n (selected: Date | undefined) => {\n if (selected == null) {\n onChange?.(null);\n return;\n }\n\n const date = new Date(selected.getTime());\n if (showTime && value != null) {\n date.setHours(value.getHours(), value.getMinutes());\n }\n\n onChange?.(date);\n\n if (shouldCloseOnSelection) {\n setIsOpen(false);\n }\n },\n [onChange, showTime, value, shouldCloseOnSelection],\n );\n\n const handleTimeChange = useCallback(\n (timeString: string) => {\n const [hoursStr, minutesStr] = timeString.split(\":\");\n const hours = parseInt(hoursStr ?? \"0\", 10);\n const minutes = parseInt(minutesStr ?? \"0\", 10);\n\n const base = value != null ? new Date(value.getTime()) : new Date();\n base.setHours(hours, minutes, 0, 0);\n\n onChange?.(base);\n },\n [value, onChange],\n );\n\n const displayText = formattedValue !== \"\"\n ? formattedValue\n : (placeholder ?? \"\");\n const hasValue = formattedValue !== \"\";\n\n const timeValue = extractTime(value);\n const footer = useMemo(\n () =>\n showTime\n ? (\n <div className={styles.timeFooter}>\n <Input\n type=\"time\"\n value={timeValue}\n onValueChange={handleTimeChange}\n className={styles.timeInput}\n aria-label=\"Time\"\n />\n </div>\n )\n : undefined,\n [showTime, timeValue, handleTimeChange],\n );\n\n return (\n <Popover.Root open={isOpen} onOpenChange={setIsOpen}>\n <Popover.Trigger\n id={id}\n className={classnames(\n styles.triggerButton,\n !hasValue && styles.triggerButtonPlaceholder,\n )}\n aria-label={hasValue ? undefined : \"Select date\"}\n >\n {displayText}\n </Popover.Trigger>\n <Popover.Portal>\n <Popover.Positioner sideOffset={4}>\n <Popover.Popup className={styles.popover}>\n <LazyDateCalendar\n dateSelected={value ?? undefined}\n onSelect={handleCalendarSelect}\n min={min}\n max={max}\n footer={footer}\n />\n </Popover.Popup>\n </Popover.Positioner>\n </Popover.Portal>\n </Popover.Root>\n );\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SAASA,KAAK,QAAQ,sBAAsB;AAC5C,SAASC,OAAO,QAAQ,wBAAwB;AAChD,OAAOC,UAAU,MAAM,YAAY;AACnC,OAAOC,KAAK,IAAIC,WAAW,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,OAAO;AAC7D,SACEC,kBAAkB,EAClBC,wBAAwB,EACxBC,UAAU,QACL,2BAA2B;AAElC,OAAOC,MAAM,MAAM,kCAAkC;AACrD,SAASC,gBAAgB,QAAQ,uBAAuB;;AAExD;AACA,MAAMC,SAAS,GAAG,IAAIC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;AAElD,SAASC,WAAWA,CAACC,IAAiB,EAAU;EAC9C,OAAON,UAAU,CAACM,IAAI,IAAIH,SAAS,CAAC;AACtC;AAEA,OAAO,SAASI,mBAAmBA,CAAC;EAClCC,EAAE;EACFC,KAAK;EACLC,QAAQ;EACRC,GAAG;EACHC,GAAG;EACHC,WAAW;EACXC,UAAU;EACVC,QAAQ,GAAG,KAAK;EAChBC;AACwB,CAAC,EAAsB;EAC/C,MAAMC,sBAAsB,GAAGD,gBAAgB,IAAI,CAACD,QAAQ;EAC5D,MAAM,CAACG,MAAM,EAAEC,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"DatetimePickerField.js","names":["Input","Popover","classnames","React","useCallback","useMemo","useState","formatDateForInput","formatDatetimeForDisplay","formatTime","styles","LazyDateCalendar","DATE_ZERO","Date","extractTime","date","DatetimePickerField","id","value","onChange","error","min","max","placeholder","formatDate","showTime","closeOnSelection","shouldCloseOnSelection","isOpen","setIsOpen","formatFn","formattedValue","handleCalendarSelect","selected","getTime","setHours","getHours","getMinutes","handleTimeChange","timeString","hoursStr","minutesStr","split","hours","parseInt","minutes","base","displayText","hasValue","timeValue","footer","createElement","className","timeFooter","type","onValueChange","timeInput","undefined","Root","open","onOpenChange","Trigger","triggerButton","triggerButtonPlaceholder","Portal","Positioner","sideOffset","Popup","popover","dateSelected","onSelect"],"sources":["DatetimePickerField.tsx"],"sourcesContent":["/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Input } from \"@base-ui/react/input\";\nimport { Popover } from \"@base-ui/react/popover\";\nimport classnames from \"classnames\";\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport {\n formatDateForInput,\n formatDatetimeForDisplay,\n formatTime,\n} from \"../../shared/dateUtils.js\";\nimport type { DatetimePickerFieldProps } from \"../FormFieldApi.js\";\nimport styles from \"./DatetimePickerField.module.css\";\nimport { LazyDateCalendar } from \"./LazyDateCalendar.js\";\n\n/** Midnight fallback so `extractTime` can reuse `formatTime` when no date is selected. */\nconst DATE_ZERO = new Date(2000, 0, 1, 0, 0, 0, 0);\n\nfunction extractTime(date: Date | null): string {\n return formatTime(date ?? DATE_ZERO);\n}\n\nexport function DatetimePickerField({\n id,\n value,\n onChange,\n error,\n min,\n max,\n placeholder,\n formatDate,\n showTime = false,\n closeOnSelection,\n}: DatetimePickerFieldProps): React.ReactElement {\n const shouldCloseOnSelection = closeOnSelection ?? !showTime;\n const [isOpen, setIsOpen] = useState(false);\n\n const formatFn = formatDate\n ?? (showTime ? formatDatetimeForDisplay : formatDateForInput);\n\n const formattedValue = value != null ? formatFn(value) : \"\";\n\n const handleCalendarSelect = useCallback(\n (selected: Date | undefined) => {\n if (selected == null) {\n onChange?.(null);\n return;\n }\n\n const date = new Date(selected.getTime());\n if (showTime && value != null) {\n date.setHours(value.getHours(), value.getMinutes());\n }\n\n onChange?.(date);\n\n if (shouldCloseOnSelection) {\n setIsOpen(false);\n }\n },\n [onChange, showTime, value, shouldCloseOnSelection],\n );\n\n const handleTimeChange = useCallback(\n (timeString: string) => {\n const [hoursStr, minutesStr] = timeString.split(\":\");\n const hours = parseInt(hoursStr ?? \"0\", 10);\n const minutes = parseInt(minutesStr ?? \"0\", 10);\n\n const base = value != null ? new Date(value.getTime()) : new Date();\n base.setHours(hours, minutes, 0, 0);\n\n onChange?.(base);\n },\n [value, onChange],\n );\n\n const displayText = formattedValue !== \"\"\n ? formattedValue\n : (placeholder ?? \"\");\n const hasValue = formattedValue !== \"\";\n\n const timeValue = extractTime(value);\n const footer = useMemo(\n () =>\n showTime\n ? (\n <div className={styles.timeFooter}>\n <Input\n type=\"time\"\n value={timeValue}\n onValueChange={handleTimeChange}\n className={styles.timeInput}\n aria-label=\"Time\"\n />\n </div>\n )\n : undefined,\n [showTime, timeValue, handleTimeChange],\n );\n\n return (\n <Popover.Root open={isOpen} onOpenChange={setIsOpen}>\n <Popover.Trigger\n id={id}\n className={classnames(\n styles.triggerButton,\n !hasValue && styles.triggerButtonPlaceholder,\n )}\n aria-label={hasValue ? undefined : \"Select date\"}\n aria-invalid={error != null || undefined}\n >\n {displayText}\n </Popover.Trigger>\n <Popover.Portal>\n <Popover.Positioner sideOffset={4}>\n <Popover.Popup className={styles.popover}>\n <LazyDateCalendar\n dateSelected={value ?? undefined}\n onSelect={handleCalendarSelect}\n min={min}\n max={max}\n footer={footer}\n />\n </Popover.Popup>\n </Popover.Positioner>\n </Popover.Portal>\n </Popover.Root>\n );\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
outline: var(--osdk-datetime-input-focus-outline);
|
|
20
20
|
outline-offset: var(--osdk-datetime-input-focus-outline-offset);
|
|
21
21
|
}
|
|
22
|
+
|
|
23
|
+
&[aria-invalid] {
|
|
24
|
+
border-color: var(--osdk-datetime-input-error-border-color);
|
|
25
|
+
}
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
.triggerButtonPlaceholder {
|
|
@@ -14,31 +14,58 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import React, { memo, useCallback } from "react";
|
|
17
|
+
import React, { memo, useCallback, useMemo } from "react";
|
|
18
18
|
import { useController } from "react-hook-form";
|
|
19
|
+
import { extractValidationRules } from "../utils/extractValidationRules.js";
|
|
19
20
|
import { FormFieldRenderer } from "./FormFieldRenderer.js";
|
|
21
|
+
const SELECT_LIKE_FIELDS = new Set(["RADIO_BUTTONS", "DROPDOWN"]);
|
|
20
22
|
export const FieldBridge = /*#__PURE__*/memo(function ({
|
|
21
23
|
fieldDef,
|
|
22
24
|
control,
|
|
23
25
|
onExternalChange
|
|
24
26
|
}) {
|
|
27
|
+
const rules = useMemo(() => extractValidationRules(fieldDef), [fieldDef]);
|
|
25
28
|
const {
|
|
26
29
|
field: {
|
|
27
30
|
onChange,
|
|
31
|
+
onBlur,
|
|
28
32
|
value
|
|
33
|
+
},
|
|
34
|
+
fieldState: {
|
|
35
|
+
error: fieldError
|
|
29
36
|
}
|
|
30
37
|
} = useController({
|
|
31
38
|
name: fieldDef.fieldKey,
|
|
32
|
-
control
|
|
39
|
+
control,
|
|
40
|
+
rules
|
|
33
41
|
});
|
|
42
|
+
const isSelectLike = SELECT_LIKE_FIELDS.has(fieldDef.fieldComponent);
|
|
34
43
|
const handleChange = useCallback(newValue => {
|
|
35
44
|
onChange(newValue);
|
|
36
45
|
onExternalChange?.(fieldDef.fieldKey, newValue);
|
|
37
|
-
|
|
46
|
+
// Select-like fields are "pick once" interactions — mark as touched
|
|
47
|
+
// immediately so RHF revalidates (clears errors) on selection rather
|
|
48
|
+
// than waiting for focus to leave the container.
|
|
49
|
+
if (isSelectLike) {
|
|
50
|
+
onBlur();
|
|
51
|
+
}
|
|
52
|
+
}, [onChange, onBlur, onExternalChange, fieldDef.fieldKey, isSelectLike]);
|
|
53
|
+
|
|
54
|
+
// Ignore blur events where focus stays within the field container
|
|
55
|
+
// (e.g. moving between radio buttons in a group). Only fire RHF's
|
|
56
|
+
// onBlur when focus truly leaves the field.
|
|
57
|
+
const handleBlur = useCallback(e => {
|
|
58
|
+
if (e.currentTarget.contains(e.relatedTarget)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
onBlur();
|
|
62
|
+
}, [onBlur]);
|
|
38
63
|
return /*#__PURE__*/React.createElement(FormFieldRenderer, {
|
|
39
64
|
value: value,
|
|
40
65
|
fieldDefinition: fieldDef,
|
|
41
|
-
onFieldValueChange: handleChange
|
|
66
|
+
onFieldValueChange: handleChange,
|
|
67
|
+
onBlur: handleBlur,
|
|
68
|
+
error: fieldError?.message
|
|
42
69
|
});
|
|
43
70
|
});
|
|
44
71
|
//# sourceMappingURL=FieldBridge.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FieldBridge.js","names":["React","memo","useCallback","useController","FormFieldRenderer","FieldBridge","fieldDef","control","onExternalChange","field","onChange","value","name","fieldKey","handleChange","newValue","createElement","fieldDefinition","onFieldValueChange"],"sources":["FieldBridge.tsx"],"sourcesContent":["/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { memo, useCallback } from \"react\";\nimport type { Control } from \"react-hook-form\";\nimport { useController } from \"react-hook-form\";\nimport type {
|
|
1
|
+
{"version":3,"file":"FieldBridge.js","names":["React","memo","useCallback","useMemo","useController","extractValidationRules","FormFieldRenderer","SELECT_LIKE_FIELDS","Set","FieldBridge","fieldDef","control","onExternalChange","rules","field","onChange","onBlur","value","fieldState","error","fieldError","name","fieldKey","isSelectLike","has","fieldComponent","handleChange","newValue","handleBlur","e","currentTarget","contains","relatedTarget","createElement","fieldDefinition","onFieldValueChange","message"],"sources":["FieldBridge.tsx"],"sourcesContent":["/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { memo, useCallback, useMemo } from \"react\";\nimport type { Control } from \"react-hook-form\";\nimport { useController } from \"react-hook-form\";\nimport type {\n FieldComponent,\n RendererFieldDefinition,\n} from \"../FormFieldApi.js\";\nimport { extractValidationRules } from \"../utils/extractValidationRules.js\";\nimport { FormFieldRenderer } from \"./FormFieldRenderer.js\";\n\nexport interface FieldBridgeProps {\n fieldDef: RendererFieldDefinition;\n control: Control<Record<string, unknown>>;\n onExternalChange?: (fieldKey: string, value: unknown) => void;\n}\nconst SELECT_LIKE_FIELDS: ReadonlySet<FieldComponent> = new Set<FieldComponent>(\n [\n \"RADIO_BUTTONS\",\n \"DROPDOWN\",\n ],\n);\n\nexport const FieldBridge: React.FC<FieldBridgeProps> = memo(\n function FieldBridgeFn({\n fieldDef,\n control,\n onExternalChange,\n }: FieldBridgeProps): React.ReactElement {\n const rules = useMemo(\n () => extractValidationRules(fieldDef),\n [fieldDef],\n );\n\n const {\n field: { onChange, onBlur, value },\n fieldState: { error: fieldError },\n } = useController({\n name: fieldDef.fieldKey,\n control,\n rules,\n });\n\n const isSelectLike = SELECT_LIKE_FIELDS.has(fieldDef.fieldComponent);\n\n const handleChange = useCallback(\n (newValue: unknown) => {\n onChange(newValue);\n onExternalChange?.(fieldDef.fieldKey, newValue);\n // Select-like fields are \"pick once\" interactions — mark as touched\n // immediately so RHF revalidates (clears errors) on selection rather\n // than waiting for focus to leave the container.\n if (isSelectLike) {\n onBlur();\n }\n },\n [onChange, onBlur, onExternalChange, fieldDef.fieldKey, isSelectLike],\n );\n\n // Ignore blur events where focus stays within the field container\n // (e.g. moving between radio buttons in a group). Only fire RHF's\n // onBlur when focus truly leaves the field.\n const handleBlur = useCallback(\n (e: React.FocusEvent<HTMLDivElement>) => {\n if (e.currentTarget.contains(e.relatedTarget)) {\n return;\n }\n onBlur();\n },\n [onBlur],\n );\n\n return (\n <FormFieldRenderer\n value={value}\n fieldDefinition={fieldDef}\n onFieldValueChange={handleChange}\n onBlur={handleBlur}\n error={fieldError?.message}\n />\n );\n },\n);\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,OAAOA,KAAK,IAAIC,IAAI,EAAEC,WAAW,EAAEC,OAAO,QAAQ,OAAO;AAEzD,SAASC,aAAa,QAAQ,iBAAiB;AAK/C,SAASC,sBAAsB,QAAQ,oCAAoC;AAC3E,SAASC,iBAAiB,QAAQ,wBAAwB;AAO1D,MAAMC,kBAA+C,GAAG,IAAIC,GAAG,CAC7D,CACE,eAAe,EACf,UAAU,CAEd,CAAC;AAED,OAAO,MAAMC,WAAuC,gBAAGR,IAAI,CACzD,UAAuB;EACrBS,QAAQ;EACRC,OAAO;EACPC;AACgB,CAAC,EAAsB;EACvC,MAAMC,KAAK,GAAGV,OAAO,CACnB,MAAME,sBAAsB,CAACK,QAAQ,CAAC,EACtC,CAACA,QAAQ,CACX,CAAC;EAED,MAAM;IACJI,KAAK,EAAE;MAAEC,QAAQ;MAAEC,MAAM;MAAEC;IAAM,CAAC;IAClCC,UAAU,EAAE;MAAEC,KAAK,EAAEC;IAAW;EAClC,CAAC,GAAGhB,aAAa,CAAC;IAChBiB,IAAI,EAAEX,QAAQ,CAACY,QAAQ;IACvBX,OAAO;IACPE;EACF,CAAC,CAAC;EAEF,MAAMU,YAAY,GAAGhB,kBAAkB,CAACiB,GAAG,CAACd,QAAQ,CAACe,cAAc,CAAC;EAEpE,MAAMC,YAAY,GAAGxB,WAAW,CAC7ByB,QAAiB,IAAK;IACrBZ,QAAQ,CAACY,QAAQ,CAAC;IAClBf,gBAAgB,GAAGF,QAAQ,CAACY,QAAQ,EAAEK,QAAQ,CAAC;IAC/C;IACA;IACA;IACA,IAAIJ,YAAY,EAAE;MAChBP,MAAM,CAAC,CAAC;IACV;EACF,CAAC,EACD,CAACD,QAAQ,EAAEC,MAAM,EAAEJ,gBAAgB,EAAEF,QAAQ,CAACY,QAAQ,EAAEC,YAAY,CACtE,CAAC;;EAED;EACA;EACA;EACA,MAAMK,UAAU,GAAG1B,WAAW,CAC3B2B,CAAmC,IAAK;IACvC,IAAIA,CAAC,CAACC,aAAa,CAACC,QAAQ,CAACF,CAAC,CAACG,aAAa,CAAC,EAAE;MAC7C;IACF;IACAhB,MAAM,CAAC,CAAC;EACV,CAAC,EACD,CAACA,MAAM,CACT,CAAC;EAED,oBACEhB,KAAA,CAAAiC,aAAA,CAAC3B,iBAAiB;IAChBW,KAAK,EAAEA,KAAM;IACbiB,eAAe,EAAExB,QAAS;IAC1ByB,kBAAkB,EAAET,YAAa;IACjCV,MAAM,EAAEY,UAAW;IACnBT,KAAK,EAAEC,UAAU,EAAEgB;EAAQ,CAC5B,CAAC;AAEN,CACF,CAAC","ignoreList":[]}
|