diff --git a/package.json b/package.json index c340e5ab..95f2e1c8 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build": "turbo run build --filter=!infra", "clean": "turbo run clean", "dev": "turbo run dev --concurrency 12", - "format": "prettier --write \"packages/**/*.{js,jsx,ts,tsx}\"", + "format": "prettier --write \"packages/*/src/**/*.{js,jsx,ts,tsx}\"", "lint": "turbo run lint", "pages": "rm -rf node_modules && npm i -g pnpm turbo && pnpm i && pnpm build && ln -sf ./apps/spotlight/dist _site", "test": "vitest run --coverage.enabled --coverage.reporter=text --coverage.reporter=json-summary --coverage.reporter=json --coverage.reportOnFailure --reporter vitest-github-actions-reporter", diff --git a/packages/design/sass/styles.scss b/packages/design/sass/styles.scss index 0a97bb9e..e5d0afb4 100644 --- a/packages/design/sass/styles.scss +++ b/packages/design/sass/styles.scss @@ -186,96 +186,6 @@ nav { padding-top: 2.6rem; } } -.previewForm { - padding-left: 1rem; - - >fieldset { - margin-bottom: 10em; - - >legend { - border-bottom: 1px #DCDEE0 solid; - font-size: 2rem; - } - - >.usa-form-group { - margin-left: 3rem; - } - - .usa-form-group { - >fieldset { - border-bottom: 1px #DCDEE0 solid; - padding-bottom: 2rem; - - legend { - font-size: 1.5rem; - border-bottom: none; - } - - p { - font-weight: bold; - } - } - } - } - - .usa-button { - margin: 1rem 0 0 1rem; - } -} - -.innerPageTopNavWrapper { - margin-bottom: 50px; - - .innerPageTopNav { - ul { - list-style: none; - padding: 0; - margin: 0 auto; - - li { - margin: 0 1px; - - &.currentPage { - a, - a:hover { - background: #454540; - color: white; - } - } - - a { - display: block; - background: #ede3ff; - color: #39215e; - border: 1px solid #ede3ff; - } - - a, - .usa-button { - text-decoration: none; - padding: 15px; - font-size: 20px; - text-align: center; - - &:hover { - background: #f5f2ff; - border: 1px solid #e4deff; - } - } - - .usa-button { - margin-top: 0; - } - } - } - - .usa-button { - margin: 0; - height: 100%; - width: 100%; - } - } -} [contentEditable=true]:focus, [href]:focus, @@ -284,162 +194,6 @@ iframe:focus { outline: 0.25rem solid #783cb9; } -.editFormPage { - - .usa-intro { - padding-bottom: 2rem; - border-bottom: 1px solid #bbb; - width: 100%; - max-width: 100%; - margin-bottom: 50px; - } - - fieldset { - border: none; - padding: 0; - margin-bottom: 2em; - } - - h1 { - margin-top: 0; - } - - .form-group-row { - padding: 1rem; - cursor: pointer; - display: flex; - - &:first-child { - .edit-button-icon { - padding: 0 10px; - flex: none; - } - } - - &.field-selected { - background: #e7e3fa; - } - - .usa-form-group-wrapper { - flex: 12; - } - - .edit-button-icon { - flex: 1; - text-align: center; - padding: 30px 0 0; - align-self: flex-end; - margin: 0 0 14px 7px; - } - - label { - cursor: pointer; - } - - .usa-button { - background: none; - margin: 0; - padding: 2px; - } - - .usa-icon { - height: 1.75em; - width: 1.75em; - fill: #777; - } - } - - .usa-form-group { - margin: 0 0 0.5rem; - } - - legend { - margin-top: 0; - } - - .editFormContentWrapper { - .preview { - .grid-col-9 { - width: 100%; - } - - .usa-form--large { - max-width: 46rem; - } - } - } -} - -.editFormWrapper { - list-style: none; - padding: 0; - border-top: 1px solid #c9c9c9; - display: none; - - li { - padding: 15px 0 20px; - border-bottom: 1px solid #c9c9c9; - } - - .usa-label { - margin-top: 0; - } -} - -.editPageGrabButtonWrapper { - font-size: 20px; - text-align: center; - width: 45px; - - span { - display: inline-block; - vertical-align: top; - } - - .grabber2 { - margin-top: -6px; - } -} - -.settingsContainer { - background: #e7e3fa; - padding: 30px; - position: -webkit-sticky; /* Safari */ - top: 10px; - - h3 { - margin-top: 0; - font-size: 1.2em; - overflow-wrap: break-word; - word-wrap: break-word; - hyphens: auto; - } - - .formRowEditFields { - .grid-col { - width: 100%; - } - } - - .usa-button { - width: 100%; - padding: 1rem 1.25rem; - margin: 20px 0 0; - - &.close-button { - background-color: #76766a; - - &:hover { - background-color: #929285; - } - } - } - - .usa-checkbox { - margin-top: 1.5rem; - } -} - .usa-step-indicator__segment { max-width: 22rem; @@ -493,78 +247,3 @@ iframe:focus { color: #39215e; } } - -@media screen and (max-width: 879px) { - - .editFormPage { - - .usa-intro { - margin-bottom: 20px; - } - - .editFormContentWrapper { - .grid-col, - .grid-col-8 { - width: 100%; - } - - .usa-button{ - margin-left: 0; - } - - .grid-col-4 { - position: static; - } - - .form-group-row { - padding: 0 0 1rem; - } - - .settingsContainer { - position: fixed; - top: 10%; - left: 50%; - transform: translate(-50%, 0); - width: 90%; - margin: 0 auto; - height: auto; - box-shadow: 1px 4px 12px #76766a; - } - } - } - -} - - -@media screen and (max-width: 480px) { - .usa-legend, - .usa-label { - word-break: break-all; - } - - .innerPageTopNavWrapper { - .innerPageTopNav { - ul { - li { - flex: 0 1 auto; - width: 100%; - margin: 0 0 4px; - } - } - } - } - - .editFieldsWrapper { - width: 85%; - - .grid-col { - width: 100%; - flex: 0 1 auto; - } - - .usa-label { - margin-bottom: 16px; - } - } - -} diff --git a/packages/design/src/Form/index.tsx b/packages/design/src/Form/index.tsx index ee60b9e3..a821467f 100644 --- a/packages/design/src/Form/index.tsx +++ b/packages/design/src/Form/index.tsx @@ -138,112 +138,26 @@ export default function Form({ )}
-
{ - updatePrompt(data); - if (onSubmit) { - console.log('Submitting form...'); - onSubmit(data); - } else { - console.warn('Skipping form submission...'); - } - })} - > - {false && ( -
- - Request to Change Name - -
-
-
- - County where you live - - - -
-
-
- -
-
-
- - Your current name - - - - - - - -
-
-

- To ask the court to change your name, you must fill - out this form, and: -

-
    -
  • - Attach a certified copy of your birth certificate - and a copy of your photo ID, and -
  • -
  • - File your form and attachements in the same county - where you live. -
  • -
-
-
-
-
- )} - -
- {prompt.components.map((component, index) => { - return ( - - ); + {!isPreview ? ( + { + updatePrompt(data); + if (onSubmit) { + console.log('Submitting form...'); + onSubmit(data); + } else { + console.warn('Skipping form submission...'); + } })} -
- - + > + + + ) : ( +
+ +
+ )}
@@ -251,6 +165,110 @@ export default function Form({ ); } +const FormContents = ({ + context, + prompt, +}: { + context: FormUIContext; + prompt: Prompt; +}) => { + return ( + <> + {false && ( +
+ + Request to Change Name + +
+
+
+ + County where you live + + + +
+
+
+ +
+
+
+ + Your current name + + + + + + + +
+
+

+ To ask the court to change your name, you must fill out this + form, and: +

+
    +
  • + Attach a certified copy of your birth certificate and a copy + of your photo ID, and +
  • +
  • + File your form and attachements in the same county where you + live. +
  • +
+
+
+
+
+ )} + +
+ {prompt.components.map((component, index) => { + return ( + + ); + })} +
+ + + ); +}; + const PromptComponent = ({ context, component, diff --git a/packages/design/src/FormManager/FormEdit/DraggableList.tsx b/packages/design/src/FormManager/FormEdit/DraggableList.tsx index 8aa15c63..165621cc 100644 --- a/packages/design/src/FormManager/FormEdit/DraggableList.tsx +++ b/packages/design/src/FormManager/FormEdit/DraggableList.tsx @@ -16,6 +16,7 @@ import { verticalListSortingStrategy, } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; +import { useFormEditStore } from './store'; const SortableItem = ({ id, @@ -26,6 +27,7 @@ const SortableItem = ({ }) => { const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id }); + const context = useFormEditStore(state => state.context); return (
-
+
- ::: - ::: +
-
{children}
+
{children}
); diff --git a/packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx b/packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx index 1e25dbb1..c8121e5d 100644 --- a/packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx +++ b/packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx @@ -30,8 +30,8 @@ export default { export const FormEditTest: StoryObj = { play: async ({ canvasElement }) => { - await editFieldLabel(canvasElement, 1, 'First field label'); - await editFieldLabel(canvasElement, 2, 'Second field label'); + await editFieldLabel(canvasElement, 'Pattern 1', 'First field label'); + await editFieldLabel(canvasElement, 'Pattern 2', 'Second field label'); }, }; @@ -39,20 +39,14 @@ export const FormEditAddPattern: StoryObj = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - // Select the first pattern for editing - const button = (await canvas.findAllByLabelText('Edit form group'))[0]; - await userEvent.click(button); - // Get the initial count of inputs - const initialCount = (await canvas.findAllByLabelText('Edit form group')) - .length; + const initialCount = (await canvas.findAllByRole('textbox')).length; const select = canvas.getByLabelText('Add a pattern'); await userEvent.selectOptions(select, 'Text input'); - const finalCount = (await canvas.findAllByLabelText('Edit form group')) - .length; - expect(finalCount).toEqual(initialCount + 1); + const finalCount = (await canvas.findAllByRole('textbox')).length; + expect(finalCount).toBeGreaterThan(initialCount); }, }; @@ -89,27 +83,29 @@ export const FormEditReorderPattern: StoryObj = { const editFieldLabel = async ( element: HTMLElement, - buttonIndex: number, - fieldLabel: string + currentLabel: string, + updatedLabel: string ) => { const canvas = within(element); - // Click "edit form" button for first field - await userEvent.click( - (await canvas.findAllByLabelText('Edit form group'))[buttonIndex] - ); + // Give focus to the field matching `currentLabel` + await userEvent.click(await canvas.findByLabelText(currentLabel)); // Enter new text for first field const input = canvas.getByLabelText('Field label'); await userEvent.clear(input); - await userEvent.type(input, fieldLabel); + await userEvent.type(input, updatedLabel); // Save the field to the form - await userEvent.click(canvas.getByRole('button', { name: 'Save' })); + await userEvent.click( + canvas.getByRole('button', { + name: 'Exit editing mode for this pattern', + }) + ); waitFor( async () => { - const newLabel = await canvas.getByLabelText(fieldLabel); + const newLabel = await canvas.getByLabelText(updatedLabel); await expect(newLabel).toBeInTheDocument(); }, { interval: 5 } diff --git a/packages/design/src/FormManager/FormEdit/PatternEdit.tsx b/packages/design/src/FormManager/FormEdit/PatternEdit.tsx index e813dde6..b3a3c5f6 100644 --- a/packages/design/src/FormManager/FormEdit/PatternEdit.tsx +++ b/packages/design/src/FormManager/FormEdit/PatternEdit.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from 'react'; +import React, { useEffect } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { type PatternMap } from '@atj/forms'; @@ -7,87 +7,48 @@ import { useFormEditStore } from './store'; export const PatternEdit = () => { const context = useFormEditStore(state => state.context); const form = useFormEditStore(state => state.form); - const selectedPattern = useFormEditStore(state => state.selectedPattern); - const { setSelectedPattern, updateSelectedPattern } = useFormEditStore( - state => ({ - setSelectedPattern: state.setSelectedPattern, - updateSelectedPattern: state.updateSelectedPattern, - }) - ); + const focusedPattern = useFormEditStore(state => state.focusedPattern); + const { updateSelectedPattern } = useFormEditStore(state => ({ + updateSelectedPattern: state.updateSelectedPattern, + })); const methods = useForm({ - defaultValues: selectedPattern + defaultValues: focusedPattern ? { - [selectedPattern.id]: selectedPattern, + [focusedPattern.id]: focusedPattern, } : {}, }); - const settingsContainerRef = useRef(null); useEffect(() => { - if (selectedPattern === undefined) { + if (focusedPattern === undefined) { return; } methods.reset(); - methods.setValue(selectedPattern.id, selectedPattern); - }, [selectedPattern]); - - // Updates the scroll position of the edit form when it's visible - useEffect(() => { - let frameId: number; - const updatePosition = () => { - if (window.innerWidth > 879) { - if (selectedPattern) { - const element = document.querySelector( - `[data-id="${selectedPattern.id}"]` - ); - if (element && settingsContainerRef.current) { - const rect = element.getBoundingClientRect(); - settingsContainerRef.current.style.top = `${rect.top}px`; - } - } - } - frameId = requestAnimationFrame(updatePosition); - }; - frameId = requestAnimationFrame(updatePosition); - return () => { - cancelAnimationFrame(frameId); - }; - }, [selectedPattern]); + methods.setValue(focusedPattern.id, focusedPattern); + }, [focusedPattern]); - if (!selectedPattern) { + if (!focusedPattern) { return; } - const SelectedEditComponent = context.editComponents[selectedPattern.type]; + const SelectedEditComponent = context.editComponents[focusedPattern.type]; return ( -
+
{SelectedEditComponent ? (
{ updateSelectedPattern(formData); })} > -

Editing "{selectedPattern.data.label}"...

- -

- - setSelectedPattern(undefined)} - className="usa-button close-button" - type="submit" - value="Cancel" +

+ -

+
) : null}
diff --git a/packages/design/src/FormManager/FormEdit/PatternEditActions.tsx b/packages/design/src/FormManager/FormEdit/PatternEditActions.tsx new file mode 100644 index 00000000..805b0016 --- /dev/null +++ b/packages/design/src/FormManager/FormEdit/PatternEditActions.tsx @@ -0,0 +1,92 @@ +import React, { PropsWithChildren, ReactElement } from 'react'; +import { useFormEditStore } from './store'; +import classNames from 'classnames'; + +type PatternEditActionsProps = PropsWithChildren<{ + children?: ReactElement; +}>; + +export const PatternEditActions = ({ children }: PatternEditActionsProps) => { + children; + const context = useFormEditStore(state => state.context); + const { deleteSelectedPattern } = useFormEditStore(state => ({ + deleteSelectedPattern: state.deleteSelectedPattern, + })); + + return ( + <> +
+ + + + + {children ? ( + + {children} + + ) : null} + +
+ + ); +}; diff --git a/packages/design/src/FormManager/FormEdit/PreviewPattern.tsx b/packages/design/src/FormManager/FormEdit/PreviewPattern.tsx index 26272726..ee74bca4 100644 --- a/packages/design/src/FormManager/FormEdit/PreviewPattern.tsx +++ b/packages/design/src/FormManager/FormEdit/PreviewPattern.tsx @@ -2,48 +2,32 @@ import React from 'react'; import { PatternComponent } from '../../Form'; import { useFormEditStore } from './store'; +import { PatternEdit } from './PatternEdit'; export const PreviewPattern: PatternComponent = function PreviewPattern(props) { const context = useFormEditStore(state => state.context); - const selectedPattern = useFormEditStore(state => state.selectedPattern); - const handleEditClick = useFormEditStore(state => state.handleEditClick); + const focusedPattern = useFormEditStore(state => state.focusedPattern); + const setFocus = useFormEditStore(state => state.setFocus); - const isSelected = selectedPattern?.id === props._patternId; + const isSelected = focusedPattern?.id === props._patternId; const divClassNames = isSelected ? 'form-group-row field-selected' : 'form-group-row'; const Component = context.components[props.type]; const EditComponent = context.editComponents[props.type]; + const selected = focusedPattern?.id === props._patternId; return ( -
- - - {EditComponent ? ( - - ) : null} - +
{ + if (EditComponent) { + setFocus(props._patternId); + } + }} + > + {selected ? : }
); }; diff --git a/packages/design/src/FormManager/FormEdit/index.tsx b/packages/design/src/FormManager/FormEdit/index.tsx index 0e71c3b5..d773f095 100644 --- a/packages/design/src/FormManager/FormEdit/index.tsx +++ b/packages/design/src/FormManager/FormEdit/index.tsx @@ -3,8 +3,9 @@ import React, { useEffect } from 'react'; import { Blueprint } from '@atj/forms'; import { type FormService } from '@atj/form-service'; +import ManagerNav from '../ManagerNav'; + import { AddPattern } from './AddPattern'; -import { PatternEdit } from './PatternEdit'; import { PreviewForm } from './Preview'; import { FormEditProvider, useFormEditStore } from './store'; import { type FormEditUIContext } from './types'; @@ -25,36 +26,34 @@ export default function FormEdit({ const form = result.data; return ( -
+ <> +

Edit form

Your form has been imported for web delivery.

formService.saveForm(formId, form)} /> -
+ ); } const EditForm = ({ saveForm }: { saveForm: (form: Blueprint) => void }) => { - const { form, selectedPattern } = useFormEditStore(); + const { form } = useFormEditStore(); useEffect(() => { saveForm(form); }, [form]); return ( -
+
-
+
-
- -
); diff --git a/packages/design/src/FormManager/FormEdit/store.tsx b/packages/design/src/FormManager/FormEdit/store.tsx index 02d4ed65..546b2193 100644 --- a/packages/design/src/FormManager/FormEdit/store.tsx +++ b/packages/design/src/FormManager/FormEdit/store.tsx @@ -31,14 +31,15 @@ export const FormEditProvider = (props: { type FormEditState = { context: FormEditUIContext; form: Blueprint; - selectedPattern?: Pattern; + focusedPattern?: Pattern; availablePatterns: { patternType: string; displayName: string; }[]; addPattern: (patternType: string) => void; - handleEditClick: (patternId: PatternId) => void; + deleteSelectedPattern: () => void; + setFocus: (patternId: PatternId) => void; setSelectedPattern: (element?: Pattern) => void; updatePattern: (data: Pattern) => void; updateSelectedPattern: (formData: PatternMap) => void; @@ -64,18 +65,28 @@ const createFormEditStore = ({ const state = get(); const builder = new BlueprintBuilder(state.form); const newPattern = builder.addPattern(state.context.config, patternType); - set({ form: builder.form, selectedPattern: newPattern }); + set({ form: builder.form, focusedPattern: newPattern }); }, - handleEditClick: (patternId: PatternId) => { + deleteSelectedPattern: () => { const state = get(); - if (state.selectedPattern?.id === patternId) { - set({ selectedPattern: undefined }); - } else { - const elementToSet = getPattern(state.form, patternId); - set({ selectedPattern: elementToSet }); + if (state.focusedPattern === undefined) { + return; + } + const builder = new BlueprintBuilder(state.form); + console.log('delete', state.focusedPattern.id); + builder.removePattern(state.context.config, state.focusedPattern.id); + set({ focusedPattern: undefined, form: builder.form }); + console.log(builder.form); + }, + setFocus: (patternId: PatternId) => { + const state = get(); + if (state.focusedPattern?.id === patternId) { + return; } + const elementToSet = getPattern(state.form, patternId); + set({ focusedPattern: elementToSet }); }, - setSelectedPattern: selectedPattern => set({ selectedPattern }), + setSelectedPattern: focusedPattern => set({ focusedPattern }), updatePattern: (pattern: Pattern) => { const state = get(); const builder = new BlueprintBuilder(state.form); @@ -87,23 +98,23 @@ const createFormEditStore = ({ } ); if (success) { - set({ form: builder.form, selectedPattern: undefined }); + set({ form: builder.form, focusedPattern: undefined }); } }, updateSelectedPattern: (formData: PatternMap) => { const state = get(); - if (state.selectedPattern === undefined) { + if (state.focusedPattern === undefined) { console.warn('No selected element'); return; } const builder = new BlueprintBuilder(state.form); const success = builder.updatePattern( state.context.config, - state.selectedPattern, + state.focusedPattern, formData ); if (success) { - set({ form: builder.form, selectedPattern: undefined }); + set({ form: builder.form, focusedPattern: undefined }); } }, })); diff --git a/packages/design/src/FormManager/FormPreview/index.tsx b/packages/design/src/FormManager/FormPreview/index.tsx index 7dd933f8..cb429bad 100644 --- a/packages/design/src/FormManager/FormPreview/index.tsx +++ b/packages/design/src/FormManager/FormPreview/index.tsx @@ -13,7 +13,7 @@ export default function FormPreview({ form: Blueprint; }) { const session = createFormSession(form); - return
; + return ; } export const FormPreviewById = ({ diff --git a/packages/design/src/FormManager/ManagerNav.tsx b/packages/design/src/FormManager/ManagerNav.tsx new file mode 100644 index 00000000..85efd6f0 --- /dev/null +++ b/packages/design/src/FormManager/ManagerNav.tsx @@ -0,0 +1,43 @@ +import React from 'react'; + +export default function ManagerNav() { + return ( +
+
    +
  1. + + Upload completed + +
  2. +
  3. + + Create completed + +
  4. +
  5. + Configure +
  6. +
  7. + + Publish not completed + +
  8. +
+ {/*
    + {(isPreviewPage || isEditPage || isImportDocuments) && ( +
  • + + {isImportDocuments ? 'Back to Edit Page' : 'Edit'} + +
  • + )} +
  • + View all Forms +
  • +
*/} +
+ ); +} diff --git a/packages/design/src/FormManager/import-document.tsx b/packages/design/src/FormManager/import-document.tsx index 66f85ace..84c7d81a 100644 --- a/packages/design/src/FormManager/import-document.tsx +++ b/packages/design/src/FormManager/import-document.tsx @@ -1,19 +1,19 @@ import React from 'react'; -import { FormConfig } from '@atj/forms'; import { type FormService } from '@atj/form-service'; +import { type FormUIContext } from '../Form'; import DocumentImporter from './DocumentImporter'; -import InnerPageTopNav from './internalPageTopNav'; +import ManagerNav from './ManagerNav'; export const FormDocumentImport = ({ baseUrl, - config, + context, formId, formService, }: { baseUrl: string; - config: FormConfig; + context: FormUIContext; formId: string; formService: FormService; }) => { @@ -24,9 +24,9 @@ export const FormDocumentImport = ({ } return ( <> - + -
-
    - {(isPreviewPage || isEditPage || isImportDocuments) && ( -
  • - - {isImportDocuments ? 'Back to Edit Page' : 'Edit'} - -
  • - )} - {/* {(isPreviewPage || isEditPage) && ( -
  • - Preview -
  • - )} */} -
  • - View all Forms -
  • -
-
-
- ); -} diff --git a/packages/design/src/config/edit/InputPatternEdit.tsx b/packages/design/src/config/edit/InputPatternEdit.tsx index 111f9abe..a1688de8 100644 --- a/packages/design/src/config/edit/InputPatternEdit.tsx +++ b/packages/design/src/config/edit/InputPatternEdit.tsx @@ -3,12 +3,13 @@ import { useFormContext } from 'react-hook-form'; import { type InputPattern } from '@atj/forms/src/patterns/input'; import { PatternEditComponent } from '../../FormManager/FormEdit/types'; +import { PatternEditActions } from '../../FormManager/FormEdit/PatternEditActions'; const InputPatternEdit: PatternEditComponent = ({ pattern }) => { const { register } = useFormContext(); return ( -
-
+
+
@@ -20,7 +21,7 @@ const InputPatternEdit: PatternEditComponent = ({ pattern }) => { type="text" >
-
+
@@ -31,7 +32,7 @@ const InputPatternEdit: PatternEditComponent = ({ pattern }) => { {...register(`${pattern.id}.data.default`)} >
-
+
@@ -42,7 +43,7 @@ const InputPatternEdit: PatternEditComponent = ({ pattern }) => { {...register(`${pattern.id}.data.maxLength`)} >
-
+
@@ -54,21 +55,25 @@ const InputPatternEdit: PatternEditComponent = ({ pattern }) => {
-
-
- - -
+
+ + + + + +
); diff --git a/packages/design/src/config/edit/SequencePatternEdit.tsx b/packages/design/src/config/edit/SequencePatternEdit.tsx index 11cf6ccf..927eea1f 100644 --- a/packages/design/src/config/edit/SequencePatternEdit.tsx +++ b/packages/design/src/config/edit/SequencePatternEdit.tsx @@ -44,9 +44,9 @@ const SortableItem = ({ id, form, pattern, context }: ItemProps) => { return (
  • -
    +
    = ({ items={patterns} strategy={verticalListSortingStrategy} > -
      +
        diff --git a/packages/forms/src/builder/builder.test.ts b/packages/forms/src/builder/builder.test.ts index 2efb176e..81b03255 100644 --- a/packages/forms/src/builder/builder.test.ts +++ b/packages/forms/src/builder/builder.test.ts @@ -23,6 +23,31 @@ describe('form builder', () => { newBuilder.form.patterns[newBuilder.form.root].data.patterns ).toEqual([...initial.patterns[initial.root].data.patterns, newPattern.id]); }); + + it('removePattern removes pattern and sequence reference', () => { + const initial = createTestBlueprint(); + const builder = new BlueprintBuilder(initial); + builder.removePattern(defaultFormConfig, 'element-2'); + expect(builder.form.patterns).toEqual({ + root: { + type: 'sequence', + id: 'root', + data: { + patterns: ['element-1'], + }, + } satisfies SequencePattern, + 'element-1': { + type: 'input', + id: 'element-1', + data: { + label: 'Pattern 1', + initial: '', + required: true, + maxLength: 128, + }, + } satisfies InputPattern, + }); + }); }); export const createTestBlueprint = () => { diff --git a/packages/forms/src/builder/index.ts b/packages/forms/src/builder/index.ts index adacdf65..38eac1f7 100644 --- a/packages/forms/src/builder/index.ts +++ b/packages/forms/src/builder/index.ts @@ -3,13 +3,15 @@ import { type FormConfig, type FormSummary, type Pattern, + type PatternId, type PatternMap, addDocument, + addPatternToRoot, + createPattern, nullBlueprint, + removePatternFromBlueprint, updateFormSummary, updatePatternFromFormData, - createPattern, - addPatternToRoot, } from '..'; export class BlueprintBuilder { @@ -38,6 +40,10 @@ export class BlueprintBuilder { return pattern; } + removePattern(config: FormConfig, id: PatternId) { + this._bp = removePatternFromBlueprint(config, this._bp, id); + } + updatePattern(config: FormConfig, pattern: Pattern, formData: PatternMap) { const updatedBlueprint = updatePatternFromFormData( config, diff --git a/packages/forms/src/index.ts b/packages/forms/src/index.ts index e471939e..838fda60 100644 --- a/packages/forms/src/index.ts +++ b/packages/forms/src/index.ts @@ -6,6 +6,7 @@ import { type PatternId, type PatternMap, getPatternMap, + removeChildPattern, } from './pattern'; export * from './builder'; @@ -216,20 +217,48 @@ export const updatePattern = (form: Blueprint, pattern: Pattern): Blueprint => { }; }; -export const addFormOutput = (form: Blueprint, document: FormOutput) => { +export const addFormOutput = ( + form: Blueprint, + document: FormOutput +): Blueprint => { return { ...form, outputs: [...form.outputs, document], }; }; -export const getPattern = (form: Blueprint, id: PatternId) => { +export const getPattern = (form: Blueprint, id: PatternId): Pattern => { return form.patterns[id]; }; -export const updateFormSummary = (form: Blueprint, summary: FormSummary) => { +export const updateFormSummary = ( + form: Blueprint, + summary: FormSummary +): Blueprint => { return { ...form, summary, }; }; + +export const removePatternFromBlueprint = ( + config: FormConfig, + blueprint: Blueprint, + id: PatternId +) => { + // Iterate over each pattern in the blueprint, and remove the target pattern + // if it is a child. + const patterns = Object.values(blueprint.patterns).reduce( + (patterns, pattern) => { + patterns[pattern.id] = removeChildPattern(config, pattern, id); + return patterns; + }, + {} as PatternMap + ); + // Remove the pattern itself + delete patterns[id]; + return { + ...blueprint, + patterns, + }; +}; diff --git a/packages/forms/src/pattern.ts b/packages/forms/src/pattern.ts index fedf8a26..5676b7e9 100644 --- a/packages/forms/src/pattern.ts +++ b/packages/forms/src/pattern.ts @@ -24,6 +24,11 @@ type ParsePatternConfigData = ( patternData: unknown ) => Result; +type RemoveChildPattern

        = ( + pattern: P, + patternId: PatternId +) => P; + export const getPattern: GetPattern = (form, patternId) => { return form.patterns[patternId]; }; @@ -40,6 +45,7 @@ export type PatternConfig< pattern: ThisPattern, patterns: Record ) => Pattern[]; + removeChildPattern?: RemoveChildPattern; createPrompt: CreatePrompt; }; @@ -139,3 +145,15 @@ export const createPattern = ( data: config.patterns[patternType].initial, }; }; + +export const removeChildPattern = ( + config: FormConfig, + pattern: Pattern, + id: PatternId +) => { + const remove = config.patterns[pattern.type].removeChildPattern; + if (!remove) { + return pattern; + } + return remove(pattern, id); +}; diff --git a/packages/forms/src/patterns/fieldset.ts b/packages/forms/src/patterns/fieldset.ts index 58e89179..a199bab5 100644 --- a/packages/forms/src/patterns/fieldset.ts +++ b/packages/forms/src/patterns/fieldset.ts @@ -30,6 +30,21 @@ export const fieldsetConfig: PatternConfig = { (patternId: string) => patterns[patternId] ); }, + removeChildPattern(pattern, patternId) { + const newPatterns = pattern.data.patterns.filter( + (id: string) => patternId !== id + ); + if (newPatterns.length === pattern.data.patterns.length) { + return pattern; + } + return { + ...pattern, + data: { + ...pattern.data, + patterns: newPatterns, + }, + }; + }, createPrompt(config, session, pattern, options) { const children = pattern.data.patterns.map((patternId: string) => { const childPattern = getPattern(session.form, patternId); diff --git a/packages/forms/src/patterns/sequence.ts b/packages/forms/src/patterns/sequence.ts index fff149f0..aee5a7db 100644 --- a/packages/forms/src/patterns/sequence.ts +++ b/packages/forms/src/patterns/sequence.ts @@ -28,6 +28,21 @@ export const sequenceConfig: PatternConfig = { (patternId: string) => patterns[patternId] ); }, + removeChildPattern(pattern, patternId) { + const newPatterns = pattern.data.patterns.filter( + (id: string) => patternId !== id + ); + if (newPatterns.length === pattern.data.patterns.length) { + return pattern; + } + return { + ...pattern, + data: { + ...pattern.data, + patterns: newPatterns, + }, + }; + }, createPrompt(config, session, pattern, options) { const children = pattern.data.patterns.map((patternId: string) => { const childPattern = getPattern(session.form, patternId);