-
Notifications
You must be signed in to change notification settings - Fork 2
Extend FormRenderer to support all 28 OpenStack standard field types #180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a4ba21c
6a8d8e5
21c8952
ace48b3
8668ee2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,21 @@ import React from 'react'; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { UseFormReturn } from 'react-hook-form'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { FormField } from '@objectstack/spec/ui'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Extended form field with additional properties for complex widgets | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * These properties are not part of the standard FormField schema but may be | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * provided by specific implementations | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export interface ExtendedFormField extends FormField { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| multiple?: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| accept?: string[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| options?: Array<{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| disabled?: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+13
to
+26
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export interface FieldFactoryProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Field configuration from FormFieldSchema | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -36,11 +51,22 @@ export const FieldFactory: React.FC<FieldFactoryProps> = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { register, formState: { errors } } = methods; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Cast to extended field for properties not in base schema | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const extendedField = field as ExtendedFormField; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Determine the widget type | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const widgetType = field.widget || 'text'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fieldName = field.field; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const error = errors[fieldName]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Helper function to handle multiple select value conversion | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleMultipleSelectValue = (value: any) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (extendedField.multiple && value instanceof HTMLCollection) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Array.from(value as HTMLCollectionOf<HTMLOptionElement>).map((opt) => opt.value); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+64
to
+66
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (extendedField.multiple && value instanceof HTMLCollection) { | |
| return Array.from(value as HTMLCollectionOf<HTMLOptionElement>).map((opt) => opt.value); | |
| } | |
| // If the field is not configured for multiple values, return as-is | |
| if (!extendedField.multiple) { | |
| return value; | |
| } | |
| // If value is already an array (e.g., react-hook-form has normalized it), use it directly | |
| if (Array.isArray(value)) { | |
| return value; | |
| } | |
| if (value && typeof value === 'object') { | |
| // If we were passed a select element or similar, prefer its selectedOptions/options collections | |
| const collection = | |
| (value as any).selectedOptions || | |
| (value as any).options || | |
| value; | |
| // Treat any array-like collection with length and item() (HTMLCollection, HTMLOptionsCollection, NodeList) | |
| if ( | |
| collection && | |
| typeof (collection as any).length === 'number' && | |
| typeof (collection as any).item === 'function' | |
| ) { | |
| return Array.from(collection as ArrayLike<HTMLOptionElement>).map( | |
| (opt) => (opt as HTMLOptionElement).value, | |
| ); | |
| } | |
| } |
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The currency field hardcodes the dollar sign ($) symbol, but the field-types.ts definition includes a currency property to specify different currencies (e.g., EUR, GBP). Consider using the currency property from the field configuration to display the appropriate currency symbol dynamically, or at minimum document this limitation.
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The percent field hardcodes min="0" and max="100", but according to PercentFieldMetadata in field-types.ts, the min and max values should be configurable. Some use cases may require negative percentages or values above 100% (e.g., growth rates). Consider reading min/max from the field configuration instead of hardcoding these values.
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file and image fields do not validate file sizes (max_size) or number of files (max_files) as defined in FileFieldMetadata and ImageFieldMetadata. This could allow users to upload excessively large files or more files than intended. Consider adding client-side validation using the maxSize and maxFiles properties from the extended field configuration.
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The location field registers two separate form fields (field.lat and field.lng) using nested paths, but this creates a structure where the value is stored as an object { lat: number, lng: number }. This may not match the expected data structure for location fields. Consider documenting this behavior clearly or ensuring it aligns with how location data is typically represented in the ObjectStack ecosystem.
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The location field creates two input elements for latitude and longitude, but they lack proper labels. The inputs only have placeholder text, which is insufficient for screen readers. Each input should have an associated label element or aria-label attribute for proper accessibility. Consider wrapping each input with a label or adding aria-label attributes.
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The object field validates JSON syntax but does not validate against the optional schema property defined in ObjectFieldMetadata. If a schema is provided in the field configuration, the JSON should be validated against it to ensure data integrity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR title mentions "OpenStack" but the correct product name is "ObjectStack" (based on the package names @objectstack/spec and repository context). This appears to be a typo in the PR title/description, though not in the actual code.