diff --git a/CHANGELOG_v6.md b/CHANGELOG_v6.md index 5653da1c66..ef797c1d39 100644 --- a/CHANGELOG_v6.md +++ b/CHANGELOG_v6.md @@ -79,7 +79,7 @@ should change the heading of the (upcoming) version to include a major version b - `findFieldInSchema(validator: ValidatorType, rootSchema: S, path: string | string[], schema: S, formData?: T, experimental_customMergeAllOf?: Experimental_CustomMergeAllOf): FoundFieldType`: Finds the field specified by the `path` within the root or recursed `schema` - `findSelectedOptionInXxxOf(validator: ValidatorType, rootSchema: S, schema: S, fallbackField: string,xxx: 'anyOf' | 'oneOf', formData?: T, experimental_customMergeAllOf?: Experimental_CustomMergeAllOf): S | undefined`: Finds the option that matches the selector field in the `schema` or undefined if nothing is selected - `getFromSchema(validator: ValidatorType, rootSchema: S, schema: S, path: string | string[], defaultValue: T | S, experimental_customMergeAllOf?: Experimental_CustomMergeAllOf): T | S`: Helper that acts like lodash's `get` but additionally retrieves `$ref`s as needed to get the path for schemas - +- Exported a `browser` version of the libraries with a browser-safe version of `getTestIds()` ## @rjsf/validator-ajv6 @@ -91,6 +91,7 @@ should change the heading of the (upcoming) version to include a major version b - Updated the `custom-templates.md` documentation for the changes to the `ArrayFieldTemplateItem` and add the two new templates - Updated the `utility-functions.md` documentation to add the `buttonId()` function - Added the `v6.x upgrade guide.md` documentation +- Updated the `playground` to add a `Layout Grid` example and made the selected example now be part of the shared export # 6.0.0-alpha.0 diff --git a/packages/core/src/components/fields/LayoutMultiSchemaField.tsx b/packages/core/src/components/fields/LayoutMultiSchemaField.tsx index e59749a4dd..d2dbdce2dc 100644 --- a/packages/core/src/components/fields/LayoutMultiSchemaField.tsx +++ b/packages/core/src/components/fields/LayoutMultiSchemaField.tsx @@ -110,7 +110,7 @@ export default function LayoutMultiSchemaField< errorSchema, hideError = false, } = props; - const { widgets, schemaUtils } = registry; + const { widgets, schemaUtils, globalUiOptions } = registry; const [enumOptions, setEnumOptions] = useState(computeEnumOptions(schema, options, schemaUtils, uiSchema, formData)!); const id = get(idSchema, ID_KEY); const discriminator = getDiscriminatorFieldFromSchema(schema); @@ -128,6 +128,7 @@ export default function LayoutMultiSchemaField< const { widget = discriminator ? 'radio' : 'select', title = '', + placeholder = '', optionsSchemaSelector: selectorField = discriminator, hideError: uiSchemaHideError, ...uiOptions @@ -148,6 +149,7 @@ export default function LayoutMultiSchemaField< const rawErrors = get(errorSchema, [ERRORS_KEY], []) as string[]; const fieldErrorSchema = omit(errorSchema, [ERRORS_KEY]); + const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions); /** Callback function that updates the selected option and adjusts the form data based on the structure of the new * option, calling the `onChange` callback with the adjusted formData. @@ -167,7 +169,7 @@ export default function LayoutMultiSchemaField< if (newFormData) { set(newFormData, selectorField, opt); } - onChange(newFormData); + onChange(newFormData, undefined, id); }; // filtering the options based on the type of widget because `selectField` does not recognize the `convertOther` prop @@ -190,8 +192,9 @@ export default function LayoutMultiSchemaField< multiple={false} rawErrors={rawErrors} hideError={hideFieldError} + hideLabel={!displayLabel} errorSchema={fieldErrorSchema} - placeholder='' + placeholder={placeholder} onChange={onOptionChange} onBlur={onBlur} onFocus={onFocus} diff --git a/packages/core/src/components/templates/GridTemplate.tsx b/packages/core/src/components/templates/GridTemplate.tsx index b92a7af768..f3bb38afc1 100644 --- a/packages/core/src/components/templates/GridTemplate.tsx +++ b/packages/core/src/components/templates/GridTemplate.tsx @@ -7,9 +7,8 @@ import { GridTemplateProps } from '@rjsf/utils'; */ export default function GridTemplate(props: GridTemplateProps) { const { children, column, className, ...rest } = props; - const classNames = column ? className : `row ${className}`; return ( -
+
{children}
); diff --git a/packages/core/test/LayoutGridField.test.tsx b/packages/core/test/LayoutGridField.test.tsx index 397dd205b8..5d48a4106d 100644 --- a/packages/core/test/LayoutGridField.test.tsx +++ b/packages/core/test/LayoutGridField.test.tsx @@ -1386,7 +1386,7 @@ describe('LayoutGridField', () => { render(); // Renders an outer grid row const row = screen.getByTestId(LayoutGridField.TEST_IDS.row); - expect(row).toHaveClass('row'); + expect(row).toBeInTheDocument(); // Renders 2 fields in the row const fields = within(row).getAllByTestId(LayoutGridField.TEST_IDS.field); expect(fields).toHaveLength(GRID_CHILDREN.length); @@ -1407,7 +1407,7 @@ describe('LayoutGridField', () => { render(); // Renders an outer grid row item with width 6 const row = screen.getByTestId(LayoutGridField.TEST_IDS.row); - expect(row).toHaveClass(`row ${ColumnWidth6}`); + expect(row).toHaveClass(ColumnWidth6); // Renders 2 fields in the row const fields = within(row).getAllByTestId(LayoutGridField.TEST_IDS.field); expect(fields).toHaveLength(GRID_CHILDREN.length); @@ -1428,7 +1428,6 @@ describe('LayoutGridField', () => { // Renders an outer grid item with width 6, but not a row const col = screen.getByTestId(LayoutGridField.TEST_IDS.col); expect(col).toHaveClass(ColumnWidth6); - expect(col).not.toHaveClass('row'); // Renders 2 fields in the column const fields = within(col).getAllByTestId(LayoutGridField.TEST_IDS.field); expect(fields).toHaveLength(GRID_CHILDREN.length); @@ -1451,13 +1450,11 @@ describe('LayoutGridField', () => { expect(cols).toHaveLength(GRID_CHILDREN.length); // First column is a grid item with width 6 expect(cols[0]).toHaveClass(ColumnWidth6); - expect(cols[0]).not.toHaveClass('row'); // Renders first field in the first column let field = within(cols[0]).getByTestId(LayoutGridField.TEST_IDS.field); expect(field).toHaveTextContent(stringifyProps(getExpectedPropsForField(props, GRID_CHILDREN[0]))); // First column is a grid item with 6 expect(cols[1]).toHaveClass(ColumnWidth6); - expect(cols[1]).not.toHaveClass('row'); // Renders second field in the second column field = within(cols[1]).getByTestId(LayoutGridField.TEST_IDS.field); expect(field).toHaveTextContent(stringifyProps(getExpectedPropsForField(props, GRID_CHILDREN[1]))); diff --git a/packages/core/test/LayoutMultiSchemaField.test.tsx b/packages/core/test/LayoutMultiSchemaField.test.tsx index 8e9201d4e2..e546c49b47 100644 --- a/packages/core/test/LayoutMultiSchemaField.test.tsx +++ b/packages/core/test/LayoutMultiSchemaField.test.tsx @@ -277,7 +277,7 @@ describe('LayoutMultiSchemaField', () => { await user.click(input); // OnChange was called with the correct event - expect(props.onChange).toHaveBeenCalledWith({ [selectorField]: '2' }); + expect(props.onChange).toHaveBeenCalledWith({ [selectorField]: '2' }, undefined, DEFAULT_ID); // Rerender to simulate the onChange updating the value const newFormData = { [selectorField]: SIMPLE_ONEOF_OPTIONS[1].value }; @@ -347,10 +347,14 @@ describe('LayoutMultiSchemaField', () => { props.formData ); await waitFor(() => { - expect(props.onChange).toHaveBeenCalledWith({ - ...props.registry.schemaUtils.getDefaultFormState(retrievedOptions[0], sanitizedFormData), - [selectorField]: 'first_option', - }); + expect(props.onChange).toHaveBeenCalledWith( + { + ...props.registry.schemaUtils.getDefaultFormState(retrievedOptions[0], sanitizedFormData), + [selectorField]: 'first_option', + }, + undefined, + DEFAULT_ID + ); }); }); test('custom selector field, ui:hideError false, props.hideError true, required true, autofocus true', async () => { @@ -404,7 +408,7 @@ describe('LayoutMultiSchemaField', () => { await user.selectOptions(button, ''); // OnChange was called with the correct event - expect(props.onChange).toHaveBeenCalledWith(undefined); + expect(props.onChange).toHaveBeenCalledWith(undefined, undefined, DEFAULT_ID); }); test('no options for radio widget, ui:hideError true, props.hideError false, no errors to hide', () => { const props = getProps({ options: [], uiSchema: { 'ui:hideError': true }, hideError: false }); diff --git a/packages/fluentui-rc/src/GridTemplate/GridTemplate.tsx b/packages/fluentui-rc/src/GridTemplate/GridTemplate.tsx index 657e597232..4a99b5e962 100644 --- a/packages/fluentui-rc/src/GridTemplate/GridTemplate.tsx +++ b/packages/fluentui-rc/src/GridTemplate/GridTemplate.tsx @@ -1,12 +1,31 @@ -import { Flex } from '@fluentui/react-migration-v0-v9'; +import { GridShim, grid } from '@fluentui/react-migration-v0-v9'; import { GridTemplateProps } from '@rjsf/utils'; /** Renders a `GridTemplate` for fluentui-rc, which is expecting the column sizing information coming in via the - * extra props provided by the caller, which are spread directly on the `Flex`. + * `style` by the caller, which are spread directly on the `GridShim` if `columns` or `rows` are provided. Otherwise, + * the `style` is added to a simple grid. This was done because `fluentui-rc` uses the CSS Grid which defines all of + * the column/row/grid information via style. * * @param props - The GridTemplateProps, including the extra props containing the fluentui-rc grid positioning details */ export default function GridTemplate(props: GridTemplateProps) { - const { children, column, ...rest } = props; - return {children}; + const { children, column, columns, rows, style, ...rest } = props; + if (columns || rows) { + // Use the `grid` rows/columns functions to generate the additional grid styling + const styles = { + ...style, + ...(rows ? grid.rows(rows) : undefined), + ...(columns ? grid.columns(columns) : undefined), + }; + return ( + + {children} + + ); + } + return ( +
+ {children} +
+ ); } diff --git a/packages/fluentui-rc/src/RadioWidget/RadioWidget.tsx b/packages/fluentui-rc/src/RadioWidget/RadioWidget.tsx index 8d0aff9738..4fc7812ecc 100644 --- a/packages/fluentui-rc/src/RadioWidget/RadioWidget.tsx +++ b/packages/fluentui-rc/src/RadioWidget/RadioWidget.tsx @@ -30,7 +30,7 @@ export default function RadioWidget) { - const { enumOptions, enumDisabled, emptyValue } = options; + const { enumOptions, enumDisabled, emptyValue, inline } = options; const _onChange = (_: any, data: RadioGroupOnChangeData) => onChange(enumOptionsValueForIndex(data.value, enumOptions, emptyValue)); @@ -52,6 +52,7 @@ export default function RadioWidget diff --git a/packages/playground/src/components/Editors.tsx b/packages/playground/src/components/Editors.tsx index 12d7f0897a..b88a0750d2 100644 --- a/packages/playground/src/components/Editors.tsx +++ b/packages/playground/src/components/Editors.tsx @@ -13,7 +13,7 @@ const monacoEditorOptions = { type EditorProps = { title: string; code: string; - onChange: (code: string) => void; + onChange: (data: any) => void; }; function Editor({ title, code, onChange }: EditorProps) { @@ -69,6 +69,7 @@ type EditorsProps = { extraErrors: ErrorSchema | undefined; setExtraErrors: React.Dispatch>; setShareURL: React.Dispatch>; + hasUiSchemaGenerator: boolean; }; export default function Editors({ @@ -81,9 +82,10 @@ export default function Editors({ setSchema, setShareURL, setUiSchema, + hasUiSchemaGenerator, }: EditorsProps) { const onSchemaEdited = useCallback( - (newSchema) => { + (newSchema: any) => { setSchema(newSchema); setShareURL(null); }, @@ -91,7 +93,7 @@ export default function Editors({ ); const onUISchemaEdited = useCallback( - (newUiSchema) => { + (newUiSchema: any) => { setUiSchema(newUiSchema); setShareURL(null); }, @@ -99,7 +101,7 @@ export default function Editors({ ); const onFormDataEdited = useCallback( - (newFormData) => { + (newFormData: any) => { if ( !isEqualWith(newFormData, formData, (newValue, oldValue) => { // Since this is coming from the editor which uses JSON.stringify to trim undefined values compare the values @@ -116,19 +118,20 @@ export default function Editors({ ); const onExtraErrorsEdited = useCallback( - (newExtraErrors) => { + (newExtraErrors: any) => { setExtraErrors(newExtraErrors); setShareURL(null); }, [setExtraErrors, setShareURL] ); + const uiSchemaTitle = hasUiSchemaGenerator ? 'UISchema (regenerated on theme change)' : 'UiSchema'; return (
- +
diff --git a/packages/playground/src/components/Header.tsx b/packages/playground/src/components/Header.tsx index 16ca4d2595..2ac555c050 100644 --- a/packages/playground/src/components/Header.tsx +++ b/packages/playground/src/components/Header.tsx @@ -6,7 +6,7 @@ import base64 from '../utils/base64'; import CopyLink from './CopyLink'; import ThemeSelector, { ThemesType } from './ThemeSelector'; -import Selector from './Selector'; +import SampleSelector, { SampleSelectorProps } from './SampleSelector'; import ValidatorSelector from './ValidatorSelector'; import SubthemeSelector from './SubthemeSelector'; import RawValidatorTest from './RawValidatorTest'; @@ -236,13 +236,14 @@ type HeaderProps = { themes: { [themeName: string]: ThemesType }; theme: string; subtheme: string | null; + sampleName: string; validators: { [validatorName: string]: ValidatorType; }; validator: string; liveSettings: LiveSettings; playGroundFormRef: React.MutableRefObject; - load: (data: any) => void; + onSampleSelected: SampleSelectorProps['onSelected']; onThemeSelected: (theme: string, themeObj: ThemesType) => void; setSubtheme: React.Dispatch>; setStylesheet: React.Dispatch>; @@ -263,13 +264,14 @@ export default function Header({ validator, liveSettings, playGroundFormRef, - load, onThemeSelected, setSubtheme, setStylesheet, setValidator, setLiveSettings, setShareURL, + sampleName, + onSampleSelected, }: HeaderProps) { const onSubthemeSelected = useCallback( (subtheme: any, { stylesheet }: { stylesheet: any }) => { @@ -307,6 +309,7 @@ export default function Header({ theme, liveSettings, validator, + sampleName, }) ); @@ -322,7 +325,7 @@ export default function Header({

react-jsonschema-form

- +
(samples.Simple.schema as RJSFSchema); const [uiSchema, setUiSchema] = useState(samples.Simple.uiSchema!); + // Store the generator inside of an object, otherwise react treats it as an initializer function + const [uiSchemaGenerator, setUiSchemaGenerator] = useState<{ generator: UiSchemaForTheme } | undefined>(undefined); const [formData, setFormData] = useState(samples.Simple.formData); const [extraErrors, setExtraErrors] = useState(); const [shareURL, setShareURL] = useState(null); const [theme, setTheme] = useState('default'); + const [sampleName, setSampleName] = useState('Simple'); const [subtheme, setSubtheme] = useState(null); const [stylesheet, setStylesheet] = useState(null); const [validator, setValidator] = useState('AJV8'); @@ -51,12 +55,15 @@ export default function Playground({ themes, validators }: PlaygroundProps) { setSubtheme(null); setFormComponent(withTheme(themeObj)); setStylesheet(stylesheet); + if (uiSchemaGenerator) { + setUiSchema(uiSchemaGenerator.generator(theme)); + } }, - [setTheme, setSubtheme, setFormComponent, setStylesheet] + [uiSchemaGenerator, setTheme, setSubtheme, setFormComponent, setStylesheet] ); const load = useCallback( - (data: Sample & { theme: string; liveSettings: LiveSettings }) => { + (data: Sample & { theme: string; liveSettings: LiveSettings; sampleName?: string }) => { const { schema, // uiSchema is missing on some examples. Provide a default to @@ -70,23 +77,40 @@ export default function Playground({ themes, validators }: PlaygroundProps) { extraErrors, liveSettings, validator, + sampleName, ...rest } = data; // To support mui v6 `material-ui-5` was change to `mui` fix the load to update that as well const theTheme = dataTheme === 'material-ui-5' ? 'mui' : dataTheme; + onThemeSelected(theTheme, themes[theTheme]); + let theUiSchema: UiSchema; + if (isFunction(uiSchema)) { + theUiSchema = uiSchema(theme); + } else { + theUiSchema = uiSchema; + } + if (sampleName) { + setSampleName(sampleName); + const sample = samples[sampleName]; + if (isFunction(sample.uiSchema)) { + setUiSchemaGenerator({ generator: sample.uiSchema }); + } else { + setUiSchemaGenerator(undefined); + } + } + // force resetting form component instance setShowForm(false); setSchema(schema); - setUiSchema(uiSchema); + setUiSchema(theUiSchema); setFormData(formData); setExtraErrors(extraErrors); - setTheme(theTheme); setShowForm(true); setLiveSettings(liveSettings); - if ('validator' in data) { + if ('validator' in data && validator !== undefined) { setValidator(validator); } setOtherFormProps({ fields, templates, ...rest }); @@ -94,6 +118,13 @@ export default function Playground({ themes, validators }: PlaygroundProps) { [theme, onThemeSelected, themes] ); + const onSampleSelected = useCallback( + (sampleName: string) => { + load({ ...samples[sampleName], sampleName, liveSettings, theme }); + }, + [load, liveSettings, theme, samples] + ); + useEffect(() => { const hash = document.location.hash.match(/#(.*)/); @@ -147,8 +178,9 @@ export default function Playground({ themes, validators }: PlaygroundProps) { validators={validators} validator={validator} liveSettings={liveSettings} + sampleName={sampleName} playGroundFormRef={playGroundFormRef} - load={load} + onSampleSelected={onSampleSelected} onThemeSelected={onThemeSelected} setSubtheme={setSubtheme} setStylesheet={setStylesheet} @@ -166,6 +198,7 @@ export default function Playground({ themes, validators }: PlaygroundProps) { extraErrors={extraErrors} setExtraErrors={setExtraErrors} setShareURL={setShareURL} + hasUiSchemaGenerator={!!uiSchemaGenerator} />
diff --git a/packages/playground/src/components/Selector.tsx b/packages/playground/src/components/SampleSelector.tsx similarity index 51% rename from packages/playground/src/components/Selector.tsx rename to packages/playground/src/components/SampleSelector.tsx index 9e9707cc2b..75cfecf91a 100644 --- a/packages/playground/src/components/Selector.tsx +++ b/packages/playground/src/components/SampleSelector.tsx @@ -1,18 +1,16 @@ -import { useState, MouseEvent } from 'react'; +import { MouseEvent } from 'react'; import { samples } from '../samples'; -interface SelectorProps { - onSelected: (data: any) => void; +export interface SampleSelectorProps { + onSelected: (sampleName: string) => void; + selectedSample: string; } -export default function Selector({ onSelected }: SelectorProps) { - const [current, setCurrent] = useState('Simple'); - +export default function SampleSelector({ onSelected, selectedSample }: SampleSelectorProps) { function onLabelClick(label: string) { return (event: MouseEvent) => { event.preventDefault(); - setCurrent(label); - setTimeout(() => onSelected(samples[label]), 0); + setTimeout(() => onSelected(label), 0); }; } @@ -20,7 +18,7 @@ export default function Selector({ onSelected }: SelectorProps) {
    {Object.keys(samples).map((label, i) => { return ( -
  • +
  • {label} diff --git a/packages/playground/src/samples/Sample.ts b/packages/playground/src/samples/Sample.ts index 5855d22019..59b0d46d29 100644 --- a/packages/playground/src/samples/Sample.ts +++ b/packages/playground/src/samples/Sample.ts @@ -1,5 +1,9 @@ +import { UiSchema } from '@rjsf/utils'; import { FormProps } from '@rjsf/core'; -export interface Sample extends Omit { - validator: string; +export type UiSchemaForTheme = (theme: string) => UiSchema; + +export interface Sample extends Omit { + validator?: string; + uiSchema: FormProps['uiSchema'] | UiSchemaForTheme; } diff --git a/packages/playground/src/samples/index.ts b/packages/playground/src/samples/index.ts index 7f1a5793a8..619f522864 100644 --- a/packages/playground/src/samples/index.ts +++ b/packages/playground/src/samples/index.ts @@ -31,9 +31,12 @@ import defaults from './defaults'; import options from './options'; import ifThenElse from './ifThenElse'; import customField from './customField'; +import layoutGrid from './layoutGrid'; import { Sample } from './Sample'; import deepFreeze from 'deep-freeze-es6'; +export type { Sample }; + const _samples: Record = { Blank: { schema: {}, uiSchema: {}, formData: {} }, Simple: simple, @@ -69,6 +72,7 @@ const _samples: Record = { ErrorSchema: errorSchema, Defaults: defaults, 'Custom Field': customField, + 'Layout Grid': layoutGrid, }; export const samples = deepFreeze(_samples); diff --git a/packages/playground/src/samples/layoutGrid.tsx b/packages/playground/src/samples/layoutGrid.tsx new file mode 100644 index 0000000000..67e7907e80 --- /dev/null +++ b/packages/playground/src/samples/layoutGrid.tsx @@ -0,0 +1,1690 @@ +import { RJSFSchema } from '@rjsf/utils'; + +import { Sample } from './Sample'; + +const layoutGrid: Sample = { + schema: { + type: 'object', + properties: { + person: { title: 'Person', $ref: '#/definitions/Person' }, + employment: { + title: 'Employment', + discriminator: { + propertyName: 'job_type', + }, + oneOf: [ + { $ref: '#/definitions/Company' }, + { $ref: '#/definitions/Education' }, + { $ref: '#/definitions/Other' }, + ], + }, + }, + definitions: { + PersonName: { + type: 'object', + properties: { + first: { + title: 'First Name', + minLength: 1, + maxLength: 200, + type: 'string', + }, + middle: { + title: 'Middle', + minLength: 1, + maxLength: 200, + type: 'string', + }, + last: { + title: 'Last Name', + minLength: 1, + maxLength: 1000, + type: 'string', + }, + }, + required: ['first', 'last'], + }, + Race: { + title: 'Race', + type: 'array', + items: { + type: 'string', + oneOf: [ + { + const: 'native_american', + title: 'American Indian / Alaska Native', + }, + { + const: 'asian', + title: 'Asian', + }, + { + const: 'black', + title: 'Black / African American', + }, + { + const: 'pacific_islander', + title: 'Native Hawaiian / Other Pacific Islander', + }, + { + const: 'white', + title: 'White', + }, + ], + }, + uniqueItems: true, + }, + Person: { + type: 'object', + properties: { + name: { $ref: '#/definitions/PersonName' }, + birth_date: { + title: 'Date of Birth', + type: 'string', + format: 'date', + }, + race: { + title: 'Race', + description: '(Check all that apply)', + $ref: '#/definitions/Race', + }, + address: { + title: 'Address', + $ref: '#/definitions/Address', + }, + }, + required: ['name', 'birth_date', 'race', 'address'], + }, + StateAbrv: { + title: 'StateAbrv', + enum: [ + 'AL', + 'AK', + 'AS', + 'AZ', + 'AR', + 'CA', + 'CO', + 'CT', + 'DE', + 'DC', + 'FL', + 'GA', + 'GU', + 'HI', + 'ID', + 'IL', + 'IN', + 'IA', + 'KS', + 'KY', + 'LA', + 'ME', + 'MD', + 'MA', + 'MI', + 'MN', + 'MS', + 'MO', + 'MT', + 'NE', + 'NV', + 'NH', + 'NJ', + 'NM', + 'NY', + 'NC', + 'ND', + 'MP', + 'OH', + 'OK', + 'OR', + 'PA', + 'PR', + 'RI', + 'SC', + 'SD', + 'TN', + 'TX', + 'UT', + 'VT', + 'VA', + 'VI', + 'WA', + 'WV', + 'WI', + 'WY', + ], + type: 'string', + }, + Address: { + type: 'object', + properties: { + line_1: { + title: 'Address', + minLength: 1, + maxLength: 200, + type: 'string', + }, + line_2: { + title: 'Address 2', + minLength: 1, + maxLength: 200, + type: 'string', + }, + city: { + title: 'City', + minLength: 1, + maxLength: 50, + type: 'string', + }, + state: { title: 'State', $ref: '#/definitions/StateAbrv' }, + postal_code: { + title: 'ZIP Code', + pattern: '^\\d{5}(?:[-\\s]\\d{4})?$', + type: 'string', + }, + }, + required: ['line_1', 'city', 'state', 'postal_code'], + }, + Location: { + type: 'object', + properties: { + city: { + title: 'City', + type: 'string', + }, + state: { + title: 'State', + $ref: '#/definitions/StateAbrv', + }, + }, + required: ['city', 'state'], + }, + Company: { + type: 'object', + properties: { + job_type: { + title: 'Company', + default: 'company', + enum: ['company'], + type: 'string', + }, + business: { + title: 'Company Name', + type: 'string', + }, + title: { + title: 'Job Title', + type: 'string', + }, + location: { + $ref: '#/definitions/Location', + }, + }, + required: ['job_type', 'business', 'location'], + }, + Education: { + type: 'object', + properties: { + job_type: { + title: 'Education', + default: 'education', + enum: ['education'], + type: 'string', + }, + district: { + title: 'District Name', + type: 'string', + }, + school: { + title: 'School Name', + type: 'string', + }, + title: { + title: 'Job Title', + type: 'string', + }, + location: { + $ref: '#/definitions/Location', + }, + }, + required: ['job_type', 'school', 'location'], + }, + Other: { + type: 'object', + properties: { + job_type: { + title: 'Other', + default: 'other', + enum: ['other'], + type: 'string', + }, + description: { + title: 'Describe your job', + type: 'string', + }, + }, + required: ['job_type', 'description'], + }, + }, + } as RJSFSchema, + uiSchema(theme: string) { + switch (theme) { + case 'antd': + return { + 'ui:field': 'LayoutGridField', + 'ui:layoutGrid': { + 'ui:row': { + children: [ + { + 'ui:row': { + gutter: [6, 0], + children: [ + { + 'ui:columns': { + xs: 8, + children: ['person.name.first', 'person.name.middle', 'person.name.last'], + }, + }, + ], + }, + }, + { + 'ui:row': { + gutter: [6, 0], + children: [ + { + 'ui:col': { + xs: 8, + children: [ + { + name: 'person.birth_date', + placeholder: '$lookup=PlaceholderText', + }, + ], + }, + }, + { + 'ui:col': { + xs: 16, + children: ['person.race'], + }, + }, + ], + }, + }, + { + 'ui:row': { + gutter: [6, 0], + children: [ + { + 'ui:col': { + xs: 12, + children: ['person.address'], + }, + }, + { + 'ui:col': { + xs: 12, + children: [ + { + 'ui:row': { + children: [ + { + 'ui:col': { + xs: 24, + style: { margin: '44px 0 30px' }, + children: ['employment'], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'company', + operator: 'all', + children: [ + { + 'ui:columns': { + xs: 24, + children: ['employment.business', 'employment.title'], + }, + }, + { + 'ui:row': { + gutter: [6, 0], + children: [ + { + 'ui:col': { + xs: 16, + children: ['employment.location.city'], + }, + }, + { + 'ui:col': { + xs: 8, + children: ['employment.location.state'], + }, + }, + ], + }, + }, + ], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'education', + operator: 'all', + children: [ + { + 'ui:columns': { + xs: 24, + children: ['employment.district', 'employment.school', 'employment.title'], + }, + }, + { + 'ui:row': { + gutter: [6, 0], + children: [ + { + 'ui:col': { + xs: 16, + children: ['employment.location.city'], + }, + }, + { + 'ui:col': { + xs: 8, + children: ['employment.location.state'], + }, + }, + ], + }, + }, + ], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'other', + operator: 'all', + children: [ + { + 'ui:columns': { + xs: 24, + children: [ + { + name: 'employment.description', + rows: 6, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + person: { + race: { + 'ui:options': { + widget: 'checkboxes', + }, + }, + address: { + 'ui:field': 'LayoutGridField', + 'ui:layoutGrid': { + 'ui:row': { + children: [ + { + 'ui:columns': { + xs: 24, + children: ['line_1', 'line_2', 'city'], + }, + }, + { + 'ui:row': { + gutter: [6, 0], + children: [ + { + 'ui:columns': { + xs: 12, + children: ['state', 'postal_code'], + }, + }, + ], + }, + }, + ], + }, + }, + }, + }, + employment: { + 'ui:options': { + inline: true, + }, + description: { + 'ui:widget': 'textarea', + }, + }, + }; + case 'chakra-ui': + return { + 'ui:field': 'LayoutGridField', + 'ui:layoutGrid': { + 'ui:row': { + gap: 2, + children: [ + { + 'ui:row': { + gap: 2, + templateColumns: 'repeat(3, 1fr)', + children: [ + { + 'ui:columns': ['person.name.first', 'person.name.middle', 'person.name.last'], + }, + ], + }, + }, + { + 'ui:row': { + gap: 2, + templateColumns: 'repeat(3, 1fr)', + children: [ + { + 'ui:col': { + children: [ + { + name: 'person.birth_date', + placeholder: '$lookup=PlaceholderText', + }, + ], + }, + }, + { + 'ui:col': { + colSpan: 2, + children: ['person.race'], + }, + }, + ], + }, + }, + { + 'ui:row': { + gap: 2, + templateColumns: 'repeat(2, 1fr)', + children: [ + { + 'ui:col': { + children: ['person.address'], + }, + }, + { + 'ui:col': { + children: [ + { + 'ui:row': { + gap: 2, + templateColumns: 'repeat(3, 1fr)', + children: [ + { + 'ui:col': { + colSpan: 3, + style: { margin: '0 0 16px' }, + children: ['employment'], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'company', + operator: 'all', + children: [ + { + 'ui:columns': { + colSpan: 3, + children: ['employment.business', 'employment.title'], + }, + }, + { + 'ui:col': { + colSpan: 2, + children: ['employment.location.city'], + }, + }, + { + 'ui:col': { + children: ['employment.location.state'], + }, + }, + ], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'education', + operator: 'all', + children: [ + { + 'ui:columns': { + colSpan: 3, + children: ['employment.district', 'employment.school', 'employment.title'], + }, + }, + { + 'ui:col': { + colSpan: 2, + children: ['employment.location.city'], + }, + }, + { + 'ui:col': { + children: ['employment.location.state'], + }, + }, + ], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'other', + operator: 'all', + children: [ + { + 'ui:columns': { + colSpan: 3, + children: [ + { + name: 'employment.description', + rows: 6, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + person: { + race: { + 'ui:options': { + widget: 'checkboxes', + }, + }, + address: { + 'ui:field': 'LayoutGridField', + 'ui:layoutGrid': { + 'ui:row': { + gap: 2, + templateColumns: 'repeat(2, 1fr)', + children: [ + { + 'ui:columns': { + colSpan: 2, + children: ['line_1', 'line_2', 'city'], + }, + }, + { + 'ui:columns': { + size: 6, + children: ['state', 'postal_code'], + }, + }, + ], + }, + }, + }, + }, + employment: { + 'ui:options': { + inline: true, + }, + description: { + 'ui:widget': 'textarea', + }, + }, + }; + case 'fluentui-rc': + return { + 'ui:field': 'LayoutGridField', + 'ui:layoutGrid': { + 'ui:row': { + rows: 'repeat(1, 1fr)', + columns: 'repeat(12, 1fr)', + style: { columnGap: '5px', rowGap: '5px' }, + children: [ + { + 'ui:col': { + style: { gridRow: '1 / auto', gridColumn: '1 / span 4' }, + children: ['person.name.first'], + }, + }, + { + 'ui:col': { + style: { gridRow: '1 / auto', gridColumn: '5 / span 4' }, + children: ['person.name.middle'], + }, + }, + { + 'ui:col': { + style: { gridRow: '1 / auto', gridColumn: '9 / span 4' }, + children: ['person.name.last'], + }, + }, + { + 'ui:col': { + style: { gridRow: '2 / auto', gridColumn: '1 / span 4' }, + children: [ + { + name: 'person.birth_date', + placeholder: '$lookup=PlaceholderText', + }, + ], + }, + }, + { + 'ui:col': { + style: { gridRow: '2 / auto', gridColumn: '5 / span 8', marginTop: '3px' }, + children: ['person.race'], + }, + }, + { + 'ui:col': { + style: { gridRow: '3 / auto', gridColumn: '1 / span 5' }, + children: ['line_1'], + }, + }, + { + 'ui:col': { + style: { gridRow: '4 / auto', gridColumn: '1 / span 5' }, + children: ['line_2'], + }, + }, + { + 'ui:col': { + style: { gridRow: '5 / auto', gridColumn: '1 / span 5' }, + children: ['city'], + }, + }, + { + 'ui:col': { + style: { gridRow: '3 / auto', gridColumn: '1 / span 5' }, + children: ['person.address'], + }, + }, + { + 'ui:row': { + style: { gridRow: '3 / auto', gridColumn: '6 / span 7' }, + children: [ + { + 'ui:col': { + style: { padding: '3px 0' }, + children: [ + { + name: 'employment', + }, + ], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'company', + operator: 'all', + children: [ + { + 'ui:columns': { + children: ['employment.business', 'employment.title'], + }, + }, + { + 'ui:row': { + children: [ + { + 'ui:col': { + children: ['employment.location.city'], + }, + }, + { + 'ui:col': { + children: ['employment.location.state'], + }, + }, + ], + }, + }, + ], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'education', + operator: 'all', + children: [ + { + 'ui:columns': { + children: ['employment.district', 'employment.school', 'employment.title'], + }, + }, + { + 'ui:row': { + children: [ + { + 'ui:col': { + children: ['employment.location.city'], + }, + }, + { + 'ui:col': { + children: ['employment.location.state'], + }, + }, + ], + }, + }, + ], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'other', + operator: 'all', + children: [ + { + 'ui:columns': { + children: [ + { + name: 'employment.description', + rows: 6, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + person: { + race: { + 'ui:options': { + widget: 'checkboxes', + }, + }, + address: { + 'ui:field': 'LayoutGridField', + 'ui:layoutGrid': { + 'ui:row': { + className: 'row', + children: [ + { + 'ui:columns': { + children: ['line_1', 'line_2', 'city'], + }, + }, + { + 'ui:row': { + children: [ + { + 'ui:columns': { + children: ['state', 'postal_code'], + }, + }, + ], + }, + }, + ], + }, + }, + }, + }, + employment: { + 'ui:options': { + inline: true, + }, + description: { + 'ui:widget': 'textarea', + }, + }, + }; + case 'mui': + return { + 'ui:field': 'LayoutGridField', + 'ui:layoutGrid': { + 'ui:row': { + mt: 1, + spacing: 2, + children: [ + { + 'ui:row': { + spacing: 2, + children: [ + { + 'ui:columns': { + size: 4, + children: [ + { + name: 'person.name.first', + fullWidth: true, + }, + { + name: 'person.name.middle', + fullWidth: true, + }, + { + name: 'person.name.last', + fullWidth: true, + }, + ], + }, + }, + ], + }, + }, + { + 'ui:row': { + spacing: 2, + children: [ + { + 'ui:col': { + size: 4, + children: [ + { + name: 'person.birth_date', + placeholder: '$lookup=PlaceholderText', + }, + ], + }, + }, + { + 'ui:col': { + size: 8, + children: ['person.race'], + }, + }, + ], + }, + }, + { + 'ui:row': { + spacing: 2, + children: [ + { + 'ui:col': { + size: 5, + children: ['person.address'], + }, + }, + { + 'ui:col': { + size: 7, + children: [ + { + 'ui:row': { + spacing: 2, + mt: 1.75, + children: [ + { + 'ui:col': { + size: 12, + children: ['employment'], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'company', + operator: 'all', + children: [ + { + 'ui:columns': { + size: 12, + children: ['employment.business', 'employment.title'], + }, + }, + { + 'ui:col': { + size: 8, + children: ['employment.location.city'], + }, + }, + { + 'ui:col': { + size: 4, + children: ['employment.location.state'], + }, + }, + ], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'education', + operator: 'all', + children: [ + { + 'ui:columns': { + size: 12, + children: ['employment.district', 'employment.school', 'employment.title'], + }, + }, + { + 'ui:col': { + size: 8, + children: ['employment.location.city'], + }, + }, + { + 'ui:col': { + size: 4, + children: ['employment.location.state'], + }, + }, + ], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'other', + operator: 'all', + children: [ + { + 'ui:columns': { + size: 12, + children: [ + { + name: 'employment.description', + rows: 6, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + person: { + race: { + 'ui:options': { + widget: 'checkboxes', + }, + }, + address: { + 'ui:field': 'LayoutGridField', + 'ui:layoutGrid': { + 'ui:row': { + spacing: 2, + children: [ + { + 'ui:columns': { + size: 12, + children: ['line_1', 'line_2', 'city'], + }, + }, + { + 'ui:row': { + spacing: 2, + children: [ + { + 'ui:columns': { + size: 6, + children: ['state', 'postal_code'], + }, + }, + ], + }, + }, + ], + }, + }, + }, + }, + employment: { + 'ui:options': { + inline: true, + label: false, + }, + description: { + 'ui:widget': 'textarea', + }, + }, + }; + case 'react-bootstrap': + return { + 'ui:field': 'LayoutGridField', + 'ui:layoutGrid': { + 'ui:row': { + children: [ + { + 'ui:row': { + children: [ + { + 'ui:columns': { + xs: 4, + children: ['person.name.first', 'person.name.middle', 'person.name.last'], + }, + }, + ], + }, + }, + { + 'ui:row': { + children: [ + { + 'ui:col': { + xs: 4, + children: [ + { + name: 'person.birth_date', + placeholder: '$lookup=PlaceholderText', + }, + ], + }, + }, + { + 'ui:col': { + xs: 8, + children: ['person.race'], + }, + }, + ], + }, + }, + { + 'ui:row': { + children: [ + { + 'ui:col': { + xs: 5, + children: ['person.address'], + }, + }, + { + 'ui:col': { + xs: 7, + children: [ + { + 'ui:row': { + children: [ + { + 'ui:col': { + xs: 12, + style: { margin: '38px 0 6px' }, + children: ['employment'], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'company', + operator: 'all', + children: [ + { + 'ui:columns': { + xs: 12, + children: ['employment.business', 'employment.title'], + }, + }, + { + 'ui:col': { + xs: 8, + children: ['employment.location.city'], + }, + }, + { + 'ui:col': { + xs: 4, + children: ['employment.location.state'], + }, + }, + ], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'education', + operator: 'all', + children: [ + { + 'ui:columns': { + xs: 12, + children: ['employment.district', 'employment.school', 'employment.title'], + }, + }, + { + 'ui:col': { + xs: 8, + children: ['employment.location.city'], + }, + }, + { + 'ui:col': { + xs: 4, + children: ['employment.location.state'], + }, + }, + ], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'other', + operator: 'all', + children: [ + { + 'ui:columns': { + xs: 12, + children: [ + { + name: 'employment.description', + rows: 6, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + person: { + race: { + 'ui:options': { + widget: 'checkboxes', + }, + }, + address: { + 'ui:field': 'LayoutGridField', + 'ui:layoutGrid': { + 'ui:row': { + children: [ + { + 'ui:columns': { + xs: 12, + children: ['line_1', 'line_2', 'city'], + }, + }, + { + 'ui:row': { + spacing: 2, + children: [ + { + 'ui:columns': { + xs: 6, + children: ['state', 'postal_code'], + }, + }, + ], + }, + }, + ], + }, + }, + }, + }, + employment: { + 'ui:options': { + inline: true, + }, + description: { + 'ui:widget': 'textarea', + }, + }, + }; + case 'semantic-ui': + return { + 'ui:field': 'LayoutGridField', + 'ui:layoutGrid': { + 'ui:row': { + container: true, + children: [ + { + 'ui:row': { + style: { width: '100%' }, + children: [ + { + 'ui:columns': { + width: 5, + style: { paddingBottom: 0 }, + children: ['person.name.first', 'person.name.middle', 'person.name.last'], + }, + }, + ], + }, + }, + { + 'ui:row': { + style: { paddingBottom: 0, marginTop: 0, width: '100%' }, + children: [ + { + 'ui:col': { + width: 6, + style: { paddingBottom: 0 }, + children: [ + { + name: 'person.birth_date', + placeholder: '$lookup=PlaceholderText', + }, + ], + }, + }, + { + 'ui:col': { + width: 10, + style: { paddingBottom: 0 }, + children: ['person.race'], + }, + }, + ], + }, + }, + { + 'ui:row': { + style: { paddingBottom: 0, marginTop: 0 }, + children: [ + { + 'ui:col': { + width: 7, + children: ['person.address'], + }, + }, + { + 'ui:col': { + width: 9, + children: [ + { + 'ui:row': { + children: [ + { + 'ui:col': { + width: 16, + style: { paddingTop: '56px' }, + children: ['employment'], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'company', + operator: 'all', + children: [ + { + 'ui:columns': { + width: 16, + style: { paddingBottom: 0, paddingTop: 0 }, + children: ['employment.business', 'employment.title'], + }, + }, + { + 'ui:col': { + width: 10, + style: { paddingBottom: 0, paddingTop: 0 }, + children: ['employment.location.city'], + }, + }, + { + 'ui:col': { + width: 6, + style: { paddingBottom: 0, paddingTop: 0 }, + children: ['employment.location.state'], + }, + }, + ], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'education', + operator: 'all', + children: [ + { + 'ui:columns': { + width: 16, + style: { paddingBottom: 0, paddingTop: 0 }, + children: ['employment.district', 'employment.school', 'employment.title'], + }, + }, + { + 'ui:col': { + width: 10, + style: { paddingBottom: 0, paddingTop: 0 }, + children: ['employment.location.city'], + }, + }, + { + 'ui:col': { + width: 6, + style: { paddingBottom: 0, paddingTop: 0 }, + children: ['employment.location.state'], + }, + }, + ], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'other', + operator: 'all', + children: [ + { + 'ui:columns': { + width: 16, + style: { paddingBottom: 0, paddingTop: 0 }, + children: [ + { + name: 'employment.description', + rows: 6, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + person: { + race: { + 'ui:options': { + widget: 'checkboxes', + }, + }, + address: { + 'ui:field': 'LayoutGridField', + 'ui:layoutGrid': { + 'ui:row': { + style: { paddingBottom: 0, marginTop: 0 }, + children: [ + { + 'ui:columns': { + width: 16, + style: { paddingBottom: 0, paddingTop: 0 }, + children: ['line_1', 'line_2', 'city'], + }, + }, + { + 'ui:row': { + style: { width: '100%' }, + children: [ + { + 'ui:columns': { + width: 8, + style: { paddingBottom: 0 }, + children: ['state', 'postal_code'], + }, + }, + ], + }, + }, + ], + }, + }, + }, + }, + employment: { + 'ui:options': { + inline: true, + }, + description: { + 'ui:widget': 'textarea', + }, + }, + }; + default: + return { + 'ui:field': 'LayoutGridField', + 'ui:layoutGrid': { + 'ui:row': [ + { + 'ui:row': { + className: 'row', + children: [ + { + 'ui:columns': { + className: 'col-xs-4', + children: ['person.name.first', 'person.name.middle', 'person.name.last'], + }, + }, + ], + }, + }, + { + 'ui:row': { + className: 'row', + children: [ + { + 'ui:col': { + className: 'col-xs-3', + children: [ + { + name: 'person.birth_date', + placeholder: '$lookup=PlaceholderText', + }, + ], + }, + }, + { + 'ui:col': { + className: 'col-xs-6', + children: ['person.race'], + }, + }, + ], + }, + }, + { + 'ui:row': { + className: 'row', + children: [ + { + 'ui:col': { + className: 'col-xs-6', + children: ['person.address'], + }, + }, + { + 'ui:col': { + className: 'col-xs-6', + style: { margin: '26px 0' }, + children: ['employment'], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'company', + operator: 'all', + children: [ + { + 'ui:columns': { + className: 'col-xs-6', + children: ['employment.business', 'employment.title'], + }, + }, + { + 'ui:col': { + className: 'col-xs-4', + children: ['employment.location.city'], + }, + }, + { + 'ui:col': { + className: 'col-xs-2', + children: ['employment.location.state'], + }, + }, + ], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'education', + operator: 'all', + children: [ + { + 'ui:columns': { + className: 'col-xs-6', + children: ['employment.district', 'employment.school', 'employment.title'], + }, + }, + { + 'ui:col': { + className: 'col-xs-4', + children: ['employment.location.city'], + }, + }, + { + 'ui:col': { + className: 'col-xs-2', + children: ['employment.location.state'], + }, + }, + ], + }, + }, + { + 'ui:condition': { + field: 'employment.job_type', + value: 'other', + operator: 'all', + children: [ + { + 'ui:columns': { + className: 'col-xs-6', + children: [ + { + name: 'employment.description', + rows: 6, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + person: { + race: { + 'ui:options': { + widget: 'checkboxes', + }, + }, + address: { + 'ui:field': 'LayoutGridField', + 'ui:layoutGrid': { + 'ui:row': { + className: 'row', + children: [ + { + 'ui:columns': { + className: 'col-xs-12', + children: ['line_1', 'line_2', 'city'], + }, + }, + { + 'ui:row': { + children: [ + { + 'ui:columns': { + className: 'col-xs-6', + children: ['state', 'postal_code'], + }, + }, + ], + }, + }, + ], + }, + }, + }, + }, + employment: { + 'ui:options': { + inline: true, + }, + description: { + 'ui:widget': 'textarea', + }, + }, + }; + } + }, +}; + +export default layoutGrid; diff --git a/packages/playground/vite.config.ts b/packages/playground/vite.config.ts index 5c0345e0e8..952a0e0750 100644 --- a/packages/playground/vite.config.ts +++ b/packages/playground/vite.config.ts @@ -22,7 +22,8 @@ export default defineConfig({ '@rjsf/mui': path.resolve(__dirname, '../mui/src'), '@rjsf/react-bootstrap': path.resolve(__dirname, '../react-bootstrap/src'), '@rjsf/semantic-ui': path.resolve(__dirname, '../semantic-ui/src'), - '@rjsf/utils': path.resolve(__dirname, '../utils/src'), + // We want to pick up the browser version of the utils + '@rjsf/utils': path.resolve(__dirname, '../utils/src/indexBrowser'), '@rjsf/validator-ajv8': path.resolve(__dirname, '../validator-ajv8/src'), }, }, diff --git a/packages/utils/package.json b/packages/utils/package.json index 156994a86d..35eac2f018 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -4,6 +4,7 @@ "main": "dist/index.js", "module": "lib/index.js", "typings": "lib/index.d.ts", + "browser": "dist/indexBrowser.js", "description": "Utility functions for @rjsf/core", "files": [ "dist", @@ -16,10 +17,13 @@ }, "scripts": { "build:ts": "tsc -b", + "build:esm-browser": "esbuild ./src/indexBrowser.ts --bundle --outfile=dist/utils-browser.esm.js --sourcemap --packages=external --format=esm", + "build:cjs-browser": "esbuild ./src/indexBrowser.ts --bundle --outfile=dist/indexBrowser.js --sourcemap --packages=external --format=cjs", + "build:umd-browser": "rollup dist/utils-browser.esm.js --format=umd --file=dist/utils-browser.umd.js --name=@rjsf/utils", "build:cjs": "esbuild ./src/index.ts --bundle --outfile=dist/index.js --sourcemap --packages=external --format=cjs", "build:esm": "esbuild ./src/index.ts --bundle --outfile=dist/utils.esm.js --sourcemap --packages=external --format=esm", "build:umd": "rollup dist/utils.esm.js --format=umd --file=dist/utils.umd.js --name=@rjsf/utils", - "build": "npm run build:ts && npm run build:cjs && npm run build:esm && npm run build:umd", + "build": "npm run build:ts && npm run build:cjs && npm run build:cjs-browser && npm run build:esm && npm run build:esm-browser && npm run build:umd && npm run build:umd-browser", "cs-check": "prettier -l \"{src,test}/**/*.ts?(x)\"", "cs-format": "prettier \"{src,test}/**/*.ts?(x)\" --write", "lint": "eslint src test", diff --git a/packages/utils/src/getTestIds.ts b/packages/utils/src/getTestIds.ts index 8d9460251e..cbdccf8c85 100644 --- a/packages/utils/src/getTestIds.ts +++ b/packages/utils/src/getTestIds.ts @@ -3,6 +3,12 @@ import get from 'lodash/get'; import { TestIdShape } from './types'; +/** Returns an empty object for use on the browser since we'll never use test ids there. + */ +export function getTestIdsBrowser(): TestIdShape { + return {}; +} + /** Returns an object of test IDs that can only be used in test mode. If the function is called in a test environment * (`NODE_ENV === 'test'`, this is set by jest) then a Proxy object will be returned. If a key within the returned * object is accessed, if the value already exists the object will return that value, otherwise it will create that key @@ -22,7 +28,7 @@ export default function getTestIds(): TestIdShape { // For some reason, even though process.env contains the value of `test` for NODE_ENV, accessing it directly returns // 'development'. Using `get()` does, in fact, return test so sticking with it if (get(process, 'env.NODE_ENV') !== 'test') { - return {}; + return getTestIdsBrowser(); } const ids = new Map(); diff --git a/packages/utils/src/indexBrowser.ts b/packages/utils/src/indexBrowser.ts new file mode 100644 index 0000000000..e55cc5d455 --- /dev/null +++ b/packages/utils/src/indexBrowser.ts @@ -0,0 +1,144 @@ +import allowAdditionalItems from './allowAdditionalItems'; +import asNumber from './asNumber'; +import canExpand from './canExpand'; +import createErrorHandler from './createErrorHandler'; +import createSchemaUtils from './createSchemaUtils'; +import dataURItoBlob from './dataURItoBlob'; +import dateRangeOptions from './dateRangeOptions'; +import deepEquals from './deepEquals'; +import englishStringTranslator from './englishStringTranslator'; +import enumOptionsDeselectValue from './enumOptionsDeselectValue'; +import enumOptionsIndexForValue from './enumOptionsIndexForValue'; +import enumOptionsIsSelected from './enumOptionsIsSelected'; +import enumOptionsSelectValue from './enumOptionsSelectValue'; +import enumOptionsValueForIndex from './enumOptionsValueForIndex'; +import ErrorSchemaBuilder from './ErrorSchemaBuilder'; +import findSchemaDefinition from './findSchemaDefinition'; +import getDateElementProps, { type DateElementFormat } from './getDateElementProps'; +import getDiscriminatorFieldFromSchema from './getDiscriminatorFieldFromSchema'; +import getInputProps from './getInputProps'; +import getSchemaType from './getSchemaType'; +import getSubmitButtonOptions from './getSubmitButtonOptions'; +import getTemplate from './getTemplate'; +import { getTestIdsBrowser as getTestIds } from './getTestIds'; +import getUiOptions from './getUiOptions'; +import getWidget from './getWidget'; +import guessType from './guessType'; +import hashForSchema, { hashObject, hashString, sortedJSONStringify } from './hashForSchema'; +import hasWidget from './hasWidget'; +import { + ariaDescribedByIds, + buttonId, + descriptionId, + errorId, + examplesId, + helpId, + optionId, + titleId, +} from './idGenerators'; +import isConstant from './isConstant'; +import isCustomWidget from './isCustomWidget'; +import isFixedItems from './isFixedItems'; +import isObject from './isObject'; +import labelValue from './labelValue'; +import localToUTC from './localToUTC'; +import lookupFromFormContext from './lookupFromFormContext'; +import mergeDefaultsWithFormData from './mergeDefaultsWithFormData'; +import mergeObjects from './mergeObjects'; +import mergeSchemas from './mergeSchemas'; +import optionsList from './optionsList'; +import orderProperties from './orderProperties'; +import pad from './pad'; +import parseDateString from './parseDateString'; +import rangeSpec from './rangeSpec'; +import replaceStringParameters from './replaceStringParameters'; +import schemaRequiresTrueValue from './schemaRequiresTrueValue'; +import shouldRender from './shouldRender'; +import toConstant from './toConstant'; +import toDateString from './toDateString'; +import toErrorList from './toErrorList'; +import toErrorSchema from './toErrorSchema'; +import unwrapErrorHandler from './unwrapErrorHandler'; +import utcToLocal from './utcToLocal'; +import validationDataMerge from './validationDataMerge'; +import withIdRefPrefix from './withIdRefPrefix'; +import getOptionMatchingSimpleDiscriminator from './getOptionMatchingSimpleDiscriminator'; +import getChangedFields from './getChangedFields'; + +export * from './types'; +export * from './enums'; + +export * from './constants'; +export * from './parser'; +export * from './schema'; + +export { + allowAdditionalItems, + ariaDescribedByIds, + asNumber, + buttonId, + canExpand, + createErrorHandler, + createSchemaUtils, + DateElementFormat, + dataURItoBlob, + dateRangeOptions, + deepEquals, + descriptionId, + englishStringTranslator, + enumOptionsDeselectValue, + enumOptionsIndexForValue, + enumOptionsIsSelected, + enumOptionsSelectValue, + enumOptionsValueForIndex, + errorId, + examplesId, + ErrorSchemaBuilder, + findSchemaDefinition, + getChangedFields, + getDateElementProps, + getDiscriminatorFieldFromSchema, + getInputProps, + getOptionMatchingSimpleDiscriminator, + getSchemaType, + getSubmitButtonOptions, + getTemplate, + getTestIds, + getUiOptions, + getWidget, + guessType, + hasWidget, + hashForSchema, + hashObject, + hashString, + helpId, + isConstant, + isCustomWidget, + isFixedItems, + isObject, + labelValue, + localToUTC, + lookupFromFormContext, + mergeDefaultsWithFormData, + mergeObjects, + mergeSchemas, + optionId, + optionsList, + orderProperties, + pad, + parseDateString, + rangeSpec, + replaceStringParameters, + schemaRequiresTrueValue, + shouldRender, + sortedJSONStringify, + titleId, + toConstant, + toDateString, + toErrorList, + toErrorSchema, + unwrapErrorHandler, + utcToLocal, + validationDataMerge, + withIdRefPrefix, +}; diff --git a/packages/utils/src/schema/findSelectedOptionInXxxOf.ts b/packages/utils/src/schema/findSelectedOptionInXxxOf.ts index 7e7ba720c2..6b6b0ea8ac 100644 --- a/packages/utils/src/schema/findSelectedOptionInXxxOf.ts +++ b/packages/utils/src/schema/findSelectedOptionInXxxOf.ts @@ -8,7 +8,7 @@ import getDiscriminatorFieldFromSchema from '../getDiscriminatorFieldFromSchema' /** Finds the option inside the `schema['any/oneOf']` list which has the `properties[selectorField].default` or * `properties[selectorField].const` that matches the `formData[selectorField]` value. For the purposes of this - * function, `selectorField` is either `schema.discriminator.propertyName` or `fallbackField`. The `LayoutGridForm` + * function, `selectorField` is either `schema.discriminator.propertyName` or `fallbackField`. The `LayoutGridField` * works directly with schemas in a recursive manner, making this faster than `getFirstMatchingOption()`. * * @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs