@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.
Files changed (99) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/build/browser/action-form/ActionFormApi.js.map +1 -1
  3. package/build/browser/action-form/BaseForm.js +100 -12
  4. package/build/browser/action-form/BaseForm.js.map +1 -1
  5. package/build/browser/action-form/BaseForm.module.css +21 -1
  6. package/build/browser/action-form/BaseForm.module.css.js +4 -1
  7. package/build/browser/action-form/FormField.js +3 -1
  8. package/build/browser/action-form/FormField.js.map +1 -1
  9. package/build/browser/action-form/FormFieldApi.js.map +1 -1
  10. package/build/browser/action-form/fields/BaseInput.module.css +4 -0
  11. package/build/browser/action-form/fields/DatetimePickerField.js +3 -1
  12. package/build/browser/action-form/fields/DatetimePickerField.js.map +1 -1
  13. package/build/browser/action-form/fields/DatetimePickerField.module.css +4 -0
  14. package/build/browser/action-form/fields/FieldBridge.js +31 -4
  15. package/build/browser/action-form/fields/FieldBridge.js.map +1 -1
  16. package/build/browser/action-form/fields/FilePickerField.js +6 -2
  17. package/build/browser/action-form/fields/FilePickerField.js.map +1 -1
  18. package/build/browser/action-form/fields/FilePickerField.module.css +4 -0
  19. package/build/browser/action-form/fields/FormFieldRenderer.js +24 -12
  20. package/build/browser/action-form/fields/FormFieldRenderer.js.map +1 -1
  21. package/build/browser/action-form/fields/NumberInputField.js +12 -8
  22. package/build/browser/action-form/fields/NumberInputField.js.map +1 -1
  23. package/build/browser/action-form/fields/NumberInputField.module.css +4 -0
  24. package/build/browser/action-form/fields/TextAreaField.js +3 -1
  25. package/build/browser/action-form/fields/TextAreaField.js.map +1 -1
  26. package/build/browser/action-form/fields/TextInputField.js +3 -1
  27. package/build/browser/action-form/fields/TextInputField.js.map +1 -1
  28. package/build/browser/action-form/utils/extractValidationRules.js +186 -0
  29. package/build/browser/action-form/utils/extractValidationRules.js.map +1 -0
  30. package/build/browser/public/experimental.js.map +1 -1
  31. package/build/browser/shared/hooks/useAsyncAction.js +56 -0
  32. package/build/browser/shared/hooks/useAsyncAction.js.map +1 -0
  33. package/build/browser/shared/hooks/useIsMounted.js +32 -0
  34. package/build/browser/shared/hooks/useIsMounted.js.map +1 -0
  35. package/build/browser/styles.css +37 -1
  36. package/build/cjs/public/experimental.cjs +361 -37
  37. package/build/cjs/public/experimental.cjs.map +1 -1
  38. package/build/cjs/public/experimental.css +30 -1
  39. package/build/cjs/public/experimental.css.map +1 -1
  40. package/build/cjs/public/experimental.d.cts +40 -13
  41. package/build/esm/action-form/ActionFormApi.js.map +1 -1
  42. package/build/esm/action-form/BaseForm.js +100 -12
  43. package/build/esm/action-form/BaseForm.js.map +1 -1
  44. package/build/esm/action-form/BaseForm.module.css +21 -1
  45. package/build/esm/action-form/FormField.js +3 -1
  46. package/build/esm/action-form/FormField.js.map +1 -1
  47. package/build/esm/action-form/FormFieldApi.js.map +1 -1
  48. package/build/esm/action-form/fields/BaseInput.module.css +4 -0
  49. package/build/esm/action-form/fields/DatetimePickerField.js +3 -1
  50. package/build/esm/action-form/fields/DatetimePickerField.js.map +1 -1
  51. package/build/esm/action-form/fields/DatetimePickerField.module.css +4 -0
  52. package/build/esm/action-form/fields/FieldBridge.js +31 -4
  53. package/build/esm/action-form/fields/FieldBridge.js.map +1 -1
  54. package/build/esm/action-form/fields/FilePickerField.js +6 -2
  55. package/build/esm/action-form/fields/FilePickerField.js.map +1 -1
  56. package/build/esm/action-form/fields/FilePickerField.module.css +4 -0
  57. package/build/esm/action-form/fields/FormFieldRenderer.js +24 -12
  58. package/build/esm/action-form/fields/FormFieldRenderer.js.map +1 -1
  59. package/build/esm/action-form/fields/NumberInputField.js +12 -8
  60. package/build/esm/action-form/fields/NumberInputField.js.map +1 -1
  61. package/build/esm/action-form/fields/NumberInputField.module.css +4 -0
  62. package/build/esm/action-form/fields/TextAreaField.js +3 -1
  63. package/build/esm/action-form/fields/TextAreaField.js.map +1 -1
  64. package/build/esm/action-form/fields/TextInputField.js +3 -1
  65. package/build/esm/action-form/fields/TextInputField.js.map +1 -1
  66. package/build/esm/action-form/utils/extractValidationRules.js +186 -0
  67. package/build/esm/action-form/utils/extractValidationRules.js.map +1 -0
  68. package/build/esm/public/experimental.js.map +1 -1
  69. package/build/esm/shared/hooks/useAsyncAction.js +56 -0
  70. package/build/esm/shared/hooks/useAsyncAction.js.map +1 -0
  71. package/build/esm/shared/hooks/useIsMounted.js +32 -0
  72. package/build/esm/shared/hooks/useIsMounted.js.map +1 -0
  73. package/build/types/action-form/ActionFormApi.d.ts +1 -1
  74. package/build/types/action-form/ActionFormApi.d.ts.map +1 -1
  75. package/build/types/action-form/BaseForm.d.ts.map +1 -1
  76. package/build/types/action-form/FormField.d.ts +1 -0
  77. package/build/types/action-form/FormField.d.ts.map +1 -1
  78. package/build/types/action-form/FormFieldApi.d.ts +39 -12
  79. package/build/types/action-form/FormFieldApi.d.ts.map +1 -1
  80. package/build/types/action-form/fields/DatetimePickerField.d.ts +1 -1
  81. package/build/types/action-form/fields/DatetimePickerField.d.ts.map +1 -1
  82. package/build/types/action-form/fields/FieldBridge.d.ts.map +1 -1
  83. package/build/types/action-form/fields/FormFieldRenderer.d.ts +2 -0
  84. package/build/types/action-form/fields/FormFieldRenderer.d.ts.map +1 -1
  85. package/build/types/action-form/fields/NumberInputField.d.ts +1 -1
  86. package/build/types/action-form/fields/NumberInputField.d.ts.map +1 -1
  87. package/build/types/action-form/fields/TextAreaField.d.ts +1 -1
  88. package/build/types/action-form/fields/TextAreaField.d.ts.map +1 -1
  89. package/build/types/action-form/fields/TextInputField.d.ts +1 -1
  90. package/build/types/action-form/fields/TextInputField.d.ts.map +1 -1
  91. package/build/types/action-form/utils/extractValidationRules.d.ts +13 -0
  92. package/build/types/action-form/utils/extractValidationRules.d.ts.map +1 -0
  93. package/build/types/public/experimental.d.ts +1 -1
  94. package/build/types/public/experimental.d.ts.map +1 -1
  95. package/build/types/shared/hooks/useAsyncAction.d.ts +16 -0
  96. package/build/types/shared/hooks/useAsyncAction.d.ts.map +1 -0
  97. package/build/types/shared/hooks/useIsMounted.d.ts +5 -0
  98. package/build/types/shared/hooks/useIsMounted.d.ts.map +1 -0
  99. package/package.json +8 -8
@@ -1541,30 +1541,50 @@ interface FormFieldDefinition<Q extends ActionDefinition<unknown>, K extends Fie
1541
1541
  */
1542
1542
  isDisabled?: boolean;
1543
1543
  /**
1544
- * A callback to return a custom error message if validation failed
1544
+ * A callback to customize error messages when a built-in validation rule fails.
1545
+ * Receives a discriminated union with the constraint data (e.g., the min value
1546
+ * that was exceeded) so the message can reference the threshold.
1545
1547
  *
1546
- * @param validationRule the validation rule that failed with the error message
1547
- * @returns the error message to display
1548
+ * Return a string to override the default message, or `undefined` to keep it.
1548
1549
  */
1549
- onValidationError?: (error: ValidationError) => string;
1550
+ onValidationError?: (error: ValidationError) => string | undefined;
1550
1551
  /**
1551
- * Additional function to validate the field
1552
+ * Additional function to validate the field.
1552
1553
  *
1553
- * @param value the current field value
1554
- * @returns a boolean promise indicating whether the value is valid
1554
+ * Return `undefined` if valid, or an error message string if invalid.
1555
1555
  */
1556
- validate?: (value: FieldValueType<Q, K>) => Promise<boolean>;
1556
+ validate?: (value: FieldValueType<Q, K>) => Promise<string | undefined>;
1557
1557
  /**
1558
1558
  * The component props for the form field.
1559
1559
  * Excludes runtime props (value, onChange) which are managed by ActionForm.
1560
1560
  */
1561
1561
  fieldComponentProps: Omit<FormFieldPropsByType[ValidFormFieldForPropertyType<FieldDescriptorType<Q, K>>], FormManagedProps<ValidFormFieldForPropertyType<FieldDescriptorType<Q, K>>>>;
1562
1562
  }
1563
+ /**
1564
+ * A discriminated union describing which validation rule failed and the
1565
+ * constraint data the user needs to build a meaningful error message.
1566
+ */
1563
1567
  type ValidationError = {
1564
- type: ValidationRule;
1565
- error: string;
1568
+ type: "required";
1569
+ } | {
1570
+ type: "min";
1571
+ min: number | Date;
1572
+ } | {
1573
+ type: "max";
1574
+ max: number | Date;
1575
+ } | {
1576
+ type: "minLength";
1577
+ minLength: number;
1578
+ } | {
1579
+ type: "maxLength";
1580
+ maxLength: number;
1581
+ } | {
1582
+ type: "maxSize";
1583
+ maxSize: number;
1584
+ } | {
1585
+ type: "validate";
1586
+ message: string;
1566
1587
  };
1567
- type ValidationRule = "required" | "min" | "max" | "minLength" | "maxLength" | "pattern" | "validate";
1568
1588
  /**
1569
1589
  * Maps field types to their corresponding props
1570
1590
  */
@@ -1768,6 +1788,11 @@ interface BaseFormFieldProps<V> {
1768
1788
  * Used for `<label htmlFor>` association.
1769
1789
  */
1770
1790
  id?: string;
1791
+ /**
1792
+ * The validation error message for this field, if any.
1793
+ * When set, the field should display a visual error state.
1794
+ */
1795
+ error?: string;
1771
1796
  /**
1772
1797
  * The value of the form field
1773
1798
  */
@@ -1852,6 +1877,8 @@ type RendererFieldDefinition = {
1852
1877
  placeholder?: string;
1853
1878
  helperText?: string;
1854
1879
  helperTextPlacement?: "bottom" | "tooltip";
1880
+ validate?: (value: unknown) => Promise<string | undefined>;
1881
+ onValidationError?: (error: ValidationError) => string | undefined;
1855
1882
  fieldComponentProps: Omit<FormFieldPropsByType[K], FormManagedProps<K>>;
1856
1883
  };
1857
1884
  }[FieldComponent];
@@ -1945,7 +1972,7 @@ type BaseFormProps = BaseFormCommonProps & ({
1945
1972
  interface BaseFormCommonProps {
1946
1973
  formTitle?: string;
1947
1974
  fieldDefinitions: ReadonlyArray<RendererFieldDefinition>;
1948
- onSubmit: (formState: Record<string, unknown>) => void;
1975
+ onSubmit: (formState: Record<string, unknown>) => Promise<void> | void;
1949
1976
  isSubmitDisabled?: boolean;
1950
1977
  isPending?: boolean;
1951
1978
  isLoading?: boolean;
@@ -1956,4 +1983,4 @@ declare const ActionForm: <Q extends ActionDefinition<unknown>>(props: ActionFor
1956
1983
 
1957
1984
  declare const BaseForm: React__default.FC<BaseFormProps>;
1958
1985
 
1959
- export { ActionForm, type ActionFormProps, type ActionParameters, type AnnotationPortalTarget, type AnnotationType, BaseFilterList, type BaseFilterListProps, BaseForm, type BaseFormFieldProps, type BaseFormProps, BasePdfViewer, BaseTable, type BaseTableProps, type CellEditInfo, ColumnConfigDialog, type ColumnConfigDialogProps, type ColumnConfigOptions, type ColumnDefinition, type ColumnDefinitionLocator, type CustomColumnLocator, type CustomFieldProps, type DatetimePickerFieldProps, type DropdownFieldProps, type FieldComponent, type FieldValueType, type FilePickerProps, type FilterComponentType, type FilterDefinitionUnion, FilterList, type FilterListItemProps, type FilterListProps, type FilterState, type FormError, type FormFieldDefinition, type FormFieldPropsByType, type FormState, type FunctionColumnLocator, MultiColumnSortDialog, type MultiColumnSortDialogProps, type NumberInputFieldProps, type ObjectSetFieldProps, ObjectTable, type ObjectTableProps, type Option, type OutlineItem, type PdfAnnotation, type PdfAnnotationRenderProps, type PdfCustomAnnotation, type PdfDownloadResult, type PdfFormFieldValue, type PdfRect, type PdfTextHighlightEvent, PdfViewer, PdfViewerAnnotationLayer, type PdfViewerAnnotationLayerProps, PdfViewerContent, type PdfViewerContentProps, type PdfViewerContextValue, type PdfViewerHandle, type PdfViewerInstanceOptions, type PdfViewerMediaProps, PdfViewerOutlineSidebar, type PdfViewerOutlineSidebarProps, type PdfViewerProps, PdfViewerProvider, PdfViewerSearchBar, type PdfViewerSearchBarProps, PdfViewerSidebar, type PdfViewerSidebarProps, PdfViewerToolbar, type PdfViewerToolbarProps, type PropertyColumnLocator, type PropertyFilterDefinition, type RadioButtonsFieldProps, type RdpColumnLocator, type RenderFilterInput, type RendererFieldDefinition, type SidebarMode, type SortColumnItem, type TextAreaFieldProps, type TextInputFieldProps, type UsePdfFormFieldsOptions, type UsePdfFormFieldsResult, type UsePdfHighlightModeOptions, type UsePdfHighlightModeResult, type UsePdfViewerCoreOptions, type UsePdfViewerCoreResult, type UsePdfViewerResult, type UsePdfViewerSearchResult, type UsePdfViewerStateOptions, type UsePdfViewerStateResult, deserializeFilterStates, serializeFilterStates, usePdfAnnotationPortals, usePdfAnnotationsByPage, usePdfDocument, usePdfFormFields, usePdfHighlightMode, usePdfOutline, usePdfViewer, usePdfViewerContext, usePdfViewerCore, usePdfViewerInstance, usePdfViewerSearch, usePdfViewerState, usePdfViewerSync };
1986
+ export { ActionForm, type ActionFormProps, type ActionParameters, type AnnotationPortalTarget, type AnnotationType, BaseFilterList, type BaseFilterListProps, BaseForm, type BaseFormFieldProps, type BaseFormProps, BasePdfViewer, BaseTable, type BaseTableProps, type CellEditInfo, ColumnConfigDialog, type ColumnConfigDialogProps, type ColumnConfigOptions, type ColumnDefinition, type ColumnDefinitionLocator, type CustomColumnLocator, type CustomFieldProps, type DatetimePickerFieldProps, type DropdownFieldProps, type FieldComponent, type FieldValueType, type FilePickerProps, type FilterComponentType, type FilterDefinitionUnion, FilterList, type FilterListItemProps, type FilterListProps, type FilterState, type FormError, type FormFieldDefinition, type FormFieldPropsByType, type FormState, type FunctionColumnLocator, MultiColumnSortDialog, type MultiColumnSortDialogProps, type NumberInputFieldProps, type ObjectSetFieldProps, ObjectTable, type ObjectTableProps, type Option, type OutlineItem, type PdfAnnotation, type PdfAnnotationRenderProps, type PdfCustomAnnotation, type PdfDownloadResult, type PdfFormFieldValue, type PdfRect, type PdfTextHighlightEvent, PdfViewer, PdfViewerAnnotationLayer, type PdfViewerAnnotationLayerProps, PdfViewerContent, type PdfViewerContentProps, type PdfViewerContextValue, type PdfViewerHandle, type PdfViewerInstanceOptions, type PdfViewerMediaProps, PdfViewerOutlineSidebar, type PdfViewerOutlineSidebarProps, type PdfViewerProps, PdfViewerProvider, PdfViewerSearchBar, type PdfViewerSearchBarProps, PdfViewerSidebar, type PdfViewerSidebarProps, PdfViewerToolbar, type PdfViewerToolbarProps, type PropertyColumnLocator, type PropertyFilterDefinition, type RadioButtonsFieldProps, type RdpColumnLocator, type RenderFilterInput, type RendererFieldDefinition, type SidebarMode, type SortColumnItem, type TextAreaFieldProps, type TextInputFieldProps, type UsePdfFormFieldsOptions, type UsePdfFormFieldsResult, type UsePdfHighlightModeOptions, type UsePdfHighlightModeResult, type UsePdfViewerCoreOptions, type UsePdfViewerCoreResult, type UsePdfViewerResult, type UsePdfViewerSearchResult, type UsePdfViewerStateOptions, type UsePdfViewerStateResult, type ValidationError, deserializeFilterStates, serializeFilterStates, usePdfAnnotationPortals, usePdfAnnotationsByPage, usePdfDocument, usePdfFormFields, usePdfHighlightMode, usePdfOutline, usePdfViewer, usePdfViewerContext, usePdfViewerCore, usePdfViewerInstance, usePdfViewerSearch, usePdfViewerState, usePdfViewerSync };
@@ -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";
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
- handleSubmit: rhfHandleSubmit
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 onFormSubmit = useCallback(rhfValues => {
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
- onSubmit(controlledFormState ?? rhfValues);
53
- }, [onSubmit, controlledFormState]);
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: rhfHandleSubmit(onFormSubmit)
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: onFieldValueChange
111
+ onExternalChange: handleFieldChange
68
112
  }))), /*#__PURE__*/React.createElement("div", {
69
113
  className: styles.osdkFormFooter
70
- }, /*#__PURE__*/React.createElement(ActionButton, {
71
- type: "submit",
72
- variant: "primary",
73
- disabled: isSubmitDisabled || isPending
74
- }, isPending ? "Submitting…" : "Submit")));
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: flex-end;
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
+ }
@@ -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;AAW3C,OAAO,MAAMC,SAAmC,gBAAGF,IAAI,CACrD,UAAqB;EACnBG,QAAQ;EACRC,KAAK;EACLC,UAAU;EACVC,UAAU;EACVC,KAAK;EACLC;AACc,CAAC,EAAsB;EACrC,oBACET,KAAA,CAAAU,aAAA;IAAKC,SAAS,EAAET,MAAM,CAACU;EAAc,GAClCP,KAAK,IAAI,IAAI,iBACZL,KAAA,CAAAU,aAAA;IAAOC,SAAS,EAAET,MAAM,CAACW,kBAAmB;IAACC,OAAO,EAAEV;EAAS,GAC5DC,KAAK,EACLC,UAAU,KAAK,IAAI,iBAClBN,KAAA,CAAAU,aAAA;IACEC,SAAS,EAAET,MAAM,CAACa,qBAAsB;IACxC,cAAW;EAAU,GAEpB,GAAG,EAAC,GACD,CAEH,CACR,EACAN,QAAQ,EACRD,KAAK,IAAI,IAAI,iBACZR,KAAA,CAAAU,aAAA;IAAKC,SAAS,EAAET,MAAM,CAACc,kBAAmB;IAACC,IAAI,EAAC;EAAO,GACpDT,KACE,CACN,EACAD,UAAU,IAAI,IAAI,iBACjBP,KAAA,CAAAU,aAAA;IAAKC,SAAS,EAAET,MAAM,CAACgB;EAAwB,GAAEX,UAAgB,CAEhE,CAAC;AAEV,CACF,CAAC","ignoreList":[]}
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":[]}
@@ -53,4 +53,8 @@
53
53
  cursor: not-allowed;
54
54
  opacity: var(--osdk-input-disabled-opacity);
55
55
  }
56
+
57
+ &[aria-invalid] {
58
+ border-color: var(--osdk-input-border-color-error);
59
+ }
56
60
  }
@@ -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":"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","ignoreList":[]}
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 {