This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-8843-4c982a7a8abba119b326b19456f4ef7fda78f059 in repository https://gitbox.apache.org/repos/asf/texera.git
commit 397de92d779b3d8cb235116609cb4fe76d0c7da7 Author: yangzhang75 <[email protected]> AuthorDate: Wed Oct 7 19:32:00 2026 +0000 feat(workflow-form): render Projection's drag-reorder list in the form, with the drag (#8843) ### What changes were proposed in this PR? Closes #8761. Part of the Form View feature (#8011); from the #8439 refinements (item 2). An exposed drag-reorder property -- Projection's columns, formly type `repeat-section-dnd` -- fell back to a plain repeated list in the Form View (`CANVAS_ONLY_FORMLY_TYPES`): a reader could edit the rows but not reorder them, which is what the widget is for. The fallback assumed the drag had nowhere to attach on a form; the widget carries its own CDK drop list, and the row template it needs comes from the operator's own schema (the per-instance one, with the upstream columns), which the form already builds. - `repeat-section-dnd` leaves `CANVAS_ONLY_FORMLY_TYPES`; the set now holds the code editor only. On the form the widget renders exactly as on the operator property panel, drag handle and all, and a drop reaches the operator through the card's `valueChanges` write-back; the panel's `reorder` callback stays optional (the widget calls it only when the host provides one), so the same widget serves both hosts without a form-specific branch. Nothing is added to the widget's UI. - Two defects in the widget itself, found while reviewing this and in reach of the form for the first time: every button now has `type="button"` -- `nz-button` sets no type, so inside a form (the card's, the panel's) a typeless button is the submit button and Enter in a row's input clicked it, removing the first row (the fallback the form used before had typed buttons; the panel has had this since the widget exists); and the rows are re-keyed to their positions after a drop, as formly's own `remove()` does -- formly keys rows by position and writes a cell's value into the model by that key path, so a value typed into a moved row landed on the row that used to be there (the same after a drag on the panel, until the operator was re-selected). ### Any related issues, documentation, discussions? Closes #8761. Raised by @mengw15 on #8436; filed from #8439. Builds on the sub-field walk from #8438. #8760 (proper) also shrinks `CANVAS_ONLY_FORMLY_TYPES`; whichever lands second deletes the then-empty set. Known limitation, not this PR's code: the Form View rebuilds every card on every compilation, including the one a card's own write triggers, so on the form a drop's write redraws the rows about a second later, and a second drop made in the last 150 ms before that redraw is undone. The canvas panel has no such rebuild. Filed as #8844 and fixed in #8845 (the form rebuilds only when the schema, the set of inputs or a value it does not hold changed); this PR is independent of it. Other follow-ups: a card's model is seeded once and never re-seeded from a co-editor's edit of the same property, so a reorder writes the whole array back over such an edit (pre-existing for every array/object property; a reorder is one more trigger); the HuggingFace and codearea widgets also carry typeless buttons. ### How was this PR tested? Unit tests (vitest). The widget's spec: a drop reorders the model, the field configs and the form controls and then tells the parent; a section without a reorder callback still reorders; a keyless row's children are re-keyed to the row's new position, as formly's own remove() does; every button carries `type="button"`; the existing rendering tests (one row per entry, draggable rows, remove, add, disabled section) still hold. A second spec stands the widget up under real formly (`FormlyModule.forRoot`, a form, a model): a drop changes the model and the cells and keeps the rows' keys in step with their positions, and a value typed into a moved row lands on that row's entry. The form's spec: Projection's `attributes` renders through `repeat-section-dnd`. `custom-formly-type`'s spec pins the set. Each guard was deletion-checked (the re-keying dropped; the callback called unguarded; the remove button typeless; the type back in the canvas-only set) and turned a named test red. eslint, prettier and the production (AOT) build pass; the full frontend suite passes; every statement, function and branch this PR adds is covered. Checked on a flag-on instance (main-demo; a Python source with four columns feeding a Projection whose `attributes` is exposed), with the canvas and the form open side by side: on main the card is a plain list (no handle); with this PR the card shows the drag handles, as the panel does. Dragging the last row to the top: the operator's order follows in the canvas tab's panel within a second and in the saved workflow. An alias typed into the moved row lands on that column, not its neighbour; Enter in an alias input removes no row; a reload shows the same order. A drop made on the canvas panel reaches the form within a second. #### Video **Before** https://github.com/user-attachments/assets/5a78f6f5-3b27-4b93-955e-bb15b40fd854 **After** https://github.com/user-attachments/assets/b84c79fa-a0a4-4bbc-b734-e75147bdf0d6 ### Was this PR authored or co-authored using generative AI tooling? Yes. Generated-by: Claude Code (Claude Fable 5.1, Anthropic). Co-authored with Claude, reviewed line by line by the author before submission. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5.1 <[email protected]> Co-authored-by: Meng Wang <[email protected]> --- .../repeat-dnd/repeat-dnd.component.formly.spec.ts | 104 +++++++++++++++++++++ .../formly/repeat-dnd/repeat-dnd.component.html | 5 + .../formly/repeat-dnd/repeat-dnd.component.spec.ts | 58 ++++++++++-- .../formly/repeat-dnd/repeat-dnd.component.ts | 44 ++++++--- .../workflow-form/workflow-form.component.spec.ts | 16 ++++ .../workflow-form/workflow-form.component.ts | 6 +- .../app/workspace/util/custom-formly-type.spec.ts | 8 +- .../src/app/workspace/util/custom-formly-type.ts | 12 +-- 8 files changed, 216 insertions(+), 37 deletions(-) diff --git a/frontend/src/app/common/formly/repeat-dnd/repeat-dnd.component.formly.spec.ts b/frontend/src/app/common/formly/repeat-dnd/repeat-dnd.component.formly.spec.ts new file mode 100644 index 0000000000..d19552cffd --- /dev/null +++ b/frontend/src/app/common/formly/repeat-dnd/repeat-dnd.component.formly.spec.ts @@ -0,0 +1,104 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CdkDragDrop } from "@angular/cdk/drag-drop"; +import { Component } from "@angular/core"; +import { TestBed } from "@angular/core/testing"; +import { FormArray, ReactiveFormsModule, UntypedFormGroup } from "@angular/forms"; +import { By } from "@angular/platform-browser"; +import { FieldType, FieldTypeConfig, FormlyFieldConfig, FormlyForm, FormlyModule } from "@ngx-formly/core"; +import { FormlyRepeatDndComponent } from "./repeat-dnd.component"; + +/** The plainest cell formly can build a row's sub-field into. */ +@Component({ + template: '<input [formControl]="formControl" />', + imports: [ReactiveFormsModule], +}) +class PlainInputType extends FieldType<FieldTypeConfig> {} + +/** A form holding one repeated section, as the Form View's card and the operator property panel do. */ +@Component({ + template: + '<form [formGroup]="form"><formly-form [form]="form" [model]="model" [fields]="fields"></formly-form></form>', + imports: [ReactiveFormsModule, FormlyForm], +}) +class CardComponent { + form = new UntypedFormGroup({}); + model: { attributes: { alias: string }[] } = { attributes: [{ alias: "a" }, { alias: "b" }, { alias: "c" }] }; + fields: FormlyFieldConfig[] = [ + { + key: "attributes", + type: "repeat-section-dnd", + fieldArray: () => ({ fieldGroup: [{ key: "alias", type: "plain" }] }), + }, + ]; +} + +/** + * Real formly, no mocks: formly keys a repeated section's rows by position and writes a cell's value + * into the model by that key path, so a move that leaves the keys where they were sends a later edit + * to the wrong row. The widget's own spec drives the drops on flat rows; this one proves the model. + */ +describe("FormlyRepeatDndComponent under real formly", () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + CardComponent, + FormlyModule.forRoot({ + types: [ + { name: "repeat-section-dnd", component: FormlyRepeatDndComponent }, + { name: "plain", component: PlainInputType }, + ], + }), + ], + }).compileComponents(); + }); + + function renderCard() { + const fixture = TestBed.createComponent(CardComponent); + fixture.detectChanges(); + const widget = fixture.debugElement.query(By.directive(FormlyRepeatDndComponent)) + .componentInstance as FormlyRepeatDndComponent; + const aliases = () => fixture.componentInstance.model.attributes.map(row => row.alias); + const cells = () => Array.from(fixture.nativeElement.querySelectorAll("input") as NodeListOf<HTMLInputElement>); + return { fixture, widget, aliases, cells }; + } + + it("moves the row in the model and on screen, and keeps the rows' keys in step with their positions", () => { + const { fixture, widget, aliases, cells } = renderCard(); + + widget.onDrop({ previousIndex: 0, currentIndex: 1 } as CdkDragDrop<string[]>); + fixture.detectChanges(); + + expect(aliases()).toEqual(["b", "a", "c"]); + expect(cells().map(cell => cell.value)).toEqual(["b", "a", "c"]); + expect(widget.field.fieldGroup!.map(row => row.key)).toEqual(["0", "1", "2"]); + }); + + it("writes a value typed into a moved row onto that row's entry, not onto the row that used to be there", () => { + const { fixture, widget, aliases } = renderCard(); + widget.onDrop({ previousIndex: 0, currentIndex: 1 } as CdkDragDrop<string[]>); + fixture.detectChanges(); + + // The first row now shows "b"; typing there edits b's entry. + (fixture.componentInstance.form.get("attributes") as FormArray).at(0).get("alias")!.setValue("typed"); + + expect(aliases()).toEqual(["typed", "a", "c"]); + }); +}); diff --git a/frontend/src/app/common/formly/repeat-dnd/repeat-dnd.component.html b/frontend/src/app/common/formly/repeat-dnd/repeat-dnd.component.html index 7f53df1c67..88765e1ba1 100644 --- a/frontend/src/app/common/formly/repeat-dnd/repeat-dnd.component.html +++ b/frontend/src/app/common/formly/repeat-dnd/repeat-dnd.component.html @@ -40,9 +40,13 @@ [field]="subField"></formly-field> </div> + <!-- type="button" on every button here: nz-button sets no type, so inside a form (the Form View's + card, the operator property panel) a typeless button is the form's submit button, and Enter + in a row's input would click it -- here, removing the first row. --> <button nz-button nzType="default" + type="button" (click)="remove(i)" class="dnd-remove-button" [disabled]="field.templateOptions?.disabled"> @@ -57,6 +61,7 @@ <button nz-button nzType="default" + type="button" (click)="add()" [disabled]="field.templateOptions?.disabled"> {{ field.templateOptions?.addText || "Add" }} diff --git a/frontend/src/app/common/formly/repeat-dnd/repeat-dnd.component.spec.ts b/frontend/src/app/common/formly/repeat-dnd/repeat-dnd.component.spec.ts index cb37171b92..e1b5e2c214 100644 --- a/frontend/src/app/common/formly/repeat-dnd/repeat-dnd.component.spec.ts +++ b/frontend/src/app/common/formly/repeat-dnd/repeat-dnd.component.spec.ts @@ -35,7 +35,12 @@ describe("FormlyRepeatDndComponent", () => { component.field = { model: ["a", "b", "c"], - fieldGroup: [{ key: "a" }, { key: "b" }, { key: "c" }], + // Keyed by position, as formly keys rows; the id is the test's own marker for which row is which. + fieldGroup: [ + { key: "0", id: "a" }, + { key: "1", id: "b" }, + { key: "2", id: "c" }, + ], formControl, props: { reorder }, } as any; @@ -64,7 +69,7 @@ describe("FormlyRepeatDndComponent", () => { component.onDrop(createDropEvent(1, 1)); expect(component.model).toEqual(["a", "b", "c"]); - expect(component.field.fieldGroup?.map(field => field.key)).toEqual(["a", "b", "c"]); + expect(component.field.fieldGroup?.map(field => field.id)).toEqual(["a", "b", "c"]); expect((component.formControl as FormArray).controls.map(control => control.value)).toEqual(["a", "b", "c"]); expect(reorder).not.toHaveBeenCalled(); }); @@ -78,7 +83,7 @@ describe("FormlyRepeatDndComponent", () => { component.onDrop(createDropEvent(0, 2)); - expect(component.field.fieldGroup?.map(field => field.key)).toEqual(["a", "b", "c"]); + expect(component.field.fieldGroup?.map(field => field.id)).toEqual(["a", "b", "c"]); expect((component.formControl as FormArray).controls.map(control => control.value)).toEqual(["a", "b", "c"]); expect(reorder).not.toHaveBeenCalled(); }); @@ -90,12 +95,12 @@ describe("FormlyRepeatDndComponent", () => { // what makes that ordering observable — the final assertions below are order-insensitive, // and moveItemInArray mutates in place, so the captures must be copies. let seenModel: string[] | undefined; - let seenFieldKeys: unknown[] | undefined; + let seenFieldIds: unknown[] | undefined; let seenControls: unknown[] | undefined; const reorder = setComponentState( vi.fn(() => { seenModel = [...(component.model as string[])]; - seenFieldKeys = component.field.fieldGroup?.map(field => field.key); + seenFieldIds = component.field.fieldGroup?.map(field => field.id); seenControls = (component.formControl as FormArray).controls.map(control => control.value); }) ); @@ -103,11 +108,11 @@ describe("FormlyRepeatDndComponent", () => { component.onDrop(createDropEvent(0, 2)); expect(component.model).toEqual(["b", "c", "a"]); - expect(component.field.fieldGroup?.map(field => field.key)).toEqual(["b", "c", "a"]); + expect(component.field.fieldGroup?.map(field => field.id)).toEqual(["b", "c", "a"]); expect((component.formControl as FormArray).controls.map(control => control.value)).toEqual(["b", "c", "a"]); expect(reorder).toHaveBeenCalledOnce(); expect(seenModel).toEqual(["b", "c", "a"]); - expect(seenFieldKeys).toEqual(["b", "c", "a"]); + expect(seenFieldIds).toEqual(["b", "c", "a"]); expect(seenControls).toEqual(["b", "c", "a"]); }); @@ -122,9 +127,29 @@ describe("FormlyRepeatDndComponent", () => { expect(() => component.onDrop(createDropEvent(0, 2))).not.toThrow(); expect(component.model).toEqual(["b", "c", "a"]); - expect(component.field.fieldGroup?.map(field => field.key)).toEqual(["b", "c", "a"]); + expect(component.field.fieldGroup?.map(field => field.id)).toEqual(["b", "c", "a"]); expect((component.formControl as FormArray).controls.map(control => control.value)).toEqual(["b", "c", "a"]); }); + // formly keys a row by position unless the template says `key: null`; then the row's keyed children + // carry the position instead, and formly's own remove() re-keys those. The move mirrors it. + it("re-keys a keyless row's children to the row's new position, as formly does", () => { + setComponentState(); + component.field = { + ...component.field, + fieldGroup: [ + { key: null, id: "a", fieldGroup: [{ key: "0" }] }, + { key: null, id: "b", fieldGroup: [{ key: "1" }] }, + { key: null, id: "c", fieldGroup: [{ key: "2" }] }, + ], + } as any; + + component.onDrop(createDropEvent(0, 2)); + + expect(component.field.fieldGroup?.map(row => row.id)).toEqual(["b", "c", "a"]); + expect(component.field.fieldGroup?.map(row => row.fieldGroup?.[0].key)).toEqual(["0", "1", "2"]); + expect(component.field.fieldGroup?.every(row => row.key === null)).toBe(true); + }); + /** * The class-level tests above drive onDrop directly and never render. The template owns the rest * of the control: one row per entry, which index a row's remove button carries, and whether the @@ -136,7 +161,12 @@ describe("FormlyRepeatDndComponent", () => { setComponentState(); component.field = { ...component.field, - fieldGroup: [{ key: "a" }, { key: "b" }, { key: "c" }], + // Keyed by position, as formly keys rows; the id is the test's own marker for which row is which. + fieldGroup: [ + { key: "0", id: "a" }, + { key: "1", id: "b" }, + { key: "2", id: "c" }, + ], templateOptions, } as any; fixture.detectChanges(); @@ -156,6 +186,16 @@ describe("FormlyRepeatDndComponent", () => { )!; } + // nz-button sets no type, so a typeless button inside a form is its submit button and Enter in a + // row's input clicks it (HTML implicit submission) -- which here used to remove the first row. + it("gives every button an explicit type, so Enter in a row's input submits nothing", () => { + const el = render(); + + const buttons = Array.from(el.querySelectorAll("button")); + expect(buttons.length).toBeGreaterThan(3); + expect(buttons.every(button => button.getAttribute("type") === "button")).toBe(true); + }); + it("renders one row per entry", () => { const el = render(); diff --git a/frontend/src/app/common/formly/repeat-dnd/repeat-dnd.component.ts b/frontend/src/app/common/formly/repeat-dnd/repeat-dnd.component.ts index bd4782d8fd..118434b261 100644 --- a/frontend/src/app/common/formly/repeat-dnd/repeat-dnd.component.ts +++ b/frontend/src/app/common/formly/repeat-dnd/repeat-dnd.component.ts @@ -18,7 +18,7 @@ */ import { Component } from "@angular/core"; -import { FieldArrayType, FormlyModule } from "@ngx-formly/core"; +import { FieldArrayType, FormlyFieldConfig, FormlyModule } from "@ngx-formly/core"; import { CdkDragDrop, moveItemInArray, CdkDropList, CdkDrag, CdkDragHandle } from "@angular/cdk/drag-drop"; import { NgFor } from "@angular/common"; import { ɵNzTransitionPatchDirective } from "ng-zorro-antd/core/transition-patch"; @@ -45,25 +45,39 @@ import { NzWaveDirective } from "ng-zorro-antd/core/wave"; ], }) export class FormlyRepeatDndComponent extends FieldArrayType { + /** + * Move the dropped row, in step: the data model (the source of truth for the backend), the formly + * field configs (the UI definition) and the FormArray controls (the live form state), then the + * parent's optional reorder callback (the operator property panel's, which saves without redrawing + * the form; the Form View's card has none, its form's valueChanges persists the move). Nothing + * moves for no model or a drop on the same position. + */ onDrop(event: CdkDragDrop<string[]>) { - if (!this.model || event.previousIndex === event.currentIndex) { + const from = event.previousIndex; + const to = event.currentIndex; + if (!this.model || from === to) { return; } - - // 1. Reorder the data model. This is the source of truth for the backend. - moveItemInArray(this.model, event.previousIndex, event.currentIndex); - - // 2. Reorder the Formly field configurations. This keeps the UI definition in sync with the data. - moveItemInArray(this.field.fieldGroup!, event.previousIndex, event.currentIndex); - - // 3. Reorder the actual Angular FormArray controls. This keeps the live form state in sync. - const control = this.formControl.at(event.previousIndex); - this.formControl.removeAt(event.previousIndex); - this.formControl.insert(event.currentIndex, control); - - // 4. Notify the parent to save the changes. The parent should NOT redraw the form. + moveItemInArray(this.model, from, to); + moveItemInArray(this.field.fieldGroup!, from, to); + // formly keys a row by its position when it builds the rows, and writes a cell's value into + // the model by that key path; so the rows are re-keyed to where they now are, as formly's own + // remove() does, or a value typed into a moved row would land on the row that used to be there. + this.field.fieldGroup!.forEach((row, index) => this.rekey(row, `${index}`)); + const control = this.formControl.at(from); + this.formControl.removeAt(from); + this.formControl.insert(to, control); if (this.props.reorder) { this.props.reorder(); } } + + /** formly's updateArrayElementKey, which it keeps private: the row's own key, else its keyed children's. */ + private rekey(row: FormlyFieldConfig, key: string): void { + if (row.key !== undefined && row.key !== null && row.key !== "") { + row.key = key; + return; + } + row.fieldGroup?.forEach(child => this.rekey(child, key)); + } } diff --git a/frontend/src/app/workspace/component/workflow-form/workflow-form.component.spec.ts b/frontend/src/app/workspace/component/workflow-form/workflow-form.component.spec.ts index d276b87494..6273ffc92b 100644 --- a/frontend/src/app/workspace/component/workflow-form/workflow-form.component.spec.ts +++ b/frontend/src/app/workspace/component/workflow-form/workflow-form.component.spec.ts @@ -954,6 +954,22 @@ describe("WorkflowFormComponent", () => { expect(component.rendered[0].fields[0].type).toBe("huggingface"); }); + // Projection's column list is the drag-reorder widget on the canvas; the form renders the same + // widget, with its drag, rather than falling back to a plain list (#8761). + it("renders Projection's attributes through the drag-reorder widget, as the canvas does", () => { + build(formViewWorkflow).ngOnInit(); + h.graphOperators.push({ operatorID: "op-1", operatorType: "Projection" }); + (component as any).formlyJsonschema = { + toFieldConfig: (_schema: any, opts: any) => ({ + fieldGroup: [{ key: "attributes", type: "array", props: { label: "Attributes" } }].map(opts.map), + }), + }; + + renderOne("attributes"); + + expect(component.rendered[0].fields[0].type).toBe("repeat-section-dnd"); + }); + it("renders a file property through its own picker type", () => { build(formViewWorkflow).ngOnInit(); diff --git a/frontend/src/app/workspace/component/workflow-form/workflow-form.component.ts b/frontend/src/app/workspace/component/workflow-form/workflow-form.component.ts index d783c43b1c..30a780d0b9 100644 --- a/frontend/src/app/workspace/component/workflow-form/workflow-form.component.ts +++ b/frontend/src/app/workspace/component/workflow-form/workflow-form.component.ts @@ -869,9 +869,9 @@ export class WorkflowFormComponent implements OnInit, OnDestroy { description: (source as { description?: string })?.description, currentType: mapped.type, }); - // Canvas-only widgets (code editor, drag-reorder) do not work here; an older workflow may - // already carry one, so leave it to formly's default editable control rather than a widget - // that cannot function on a form. + // A canvas-only widget (the code editor) does not work here; an older workflow may already + // carry one, so leave it to formly's default editable control rather than a widget that + // cannot function on a form. if (customType && !CANVAS_ONLY_FORMLY_TYPES.has(customType)) { mapped.type = customType; } diff --git a/frontend/src/app/workspace/util/custom-formly-type.spec.ts b/frontend/src/app/workspace/util/custom-formly-type.spec.ts index 85ff479733..3f3de31c6e 100644 --- a/frontend/src/app/workspace/util/custom-formly-type.spec.ts +++ b/frontend/src/app/workspace/util/custom-formly-type.spec.ts @@ -22,16 +22,16 @@ import { customFormlyFieldType, NON_FORM_FIELD_TYPES, CANVAS_ONLY_FORMLY_TYPES } describe("NON_FORM_FIELD_TYPES", () => { it("blocks only the code editor from being a form field, not the drag-reorder list", () => { expect(NON_FORM_FIELD_TYPES.has("codearea")).toBe(true); - // a drag-reorder property is still a valid form field (it just renders without the drag) + // a drag-reorder property is still a valid form field, and the form renders it with its drag (#8761) expect(NON_FORM_FIELD_TYPES.has("repeat-section-dnd")).toBe(false); }); }); describe("CANVAS_ONLY_FORMLY_TYPES", () => { - it("holds the widgets the form falls back from: the code editor and the drag-reorder list", () => { + it("holds the widgets the form falls back from: the code editor, and the code editor only", () => { expect(CANVAS_ONLY_FORMLY_TYPES.has("codearea")).toBe(true); - // the drag has nowhere to attach on a form, so an exposed one degrades to the default control - expect(CANVAS_ONLY_FORMLY_TYPES.has("repeat-section-dnd")).toBe(true); + // the drag-reorder list carries its own drop list, so the form renders it, drag and all (#8761) + expect(CANVAS_ONLY_FORMLY_TYPES.has("repeat-section-dnd")).toBe(false); // an ordinary custom widget (a picker/uploader) is rendered as itself, not fallen back from expect(CANVAS_ONLY_FORMLY_TYPES.has("datasetversionselector")).toBe(false); }); diff --git a/frontend/src/app/workspace/util/custom-formly-type.ts b/frontend/src/app/workspace/util/custom-formly-type.ts index 0ce9086450..df9766afd3 100644 --- a/frontend/src/app/workspace/util/custom-formly-type.ts +++ b/frontend/src/app/workspace/util/custom-formly-type.ts @@ -21,18 +21,18 @@ * Widget types that cannot be a form field at all, so the property is not offered for exposure on * the Form View. Only the code editor: editing code is not "filling in a value", and a form reader * should not be writing code. (A drag-reorder property such as Projection's columns stays exposable - * -- it just renders without the drag in the form -- so it is deliberately NOT in this set.) + * and renders with its drag in the form, so it is deliberately NOT in this set.) */ export const NON_FORM_FIELD_TYPES: ReadonlySet<string> = new Set(["codearea"]); /** * Widgets that only work on the operator canvas, so the Form View does not render them: it falls - * back to formly's default control instead. The code editor (also blocked from exposure by - * {@link NON_FORM_FIELD_TYPES}) and the drag-reorder list, whose drag has nowhere to attach on a - * form -- a workflow may still carry an exposed drag-reorder property from before, and it degrades - * to a plain editable list rather than a control that cannot function here. + * back to formly's default control instead. Only the code editor (also blocked from exposure by + * {@link NON_FORM_FIELD_TYPES}), which opens into an outlet the canvas owns. The drag-reorder list + * used to be here too, as if its drag had nowhere to attach on a form; it carries its own drop + * list, and renders on the form with the drag, as on the panel (#8761). */ -export const CANVAS_ONLY_FORMLY_TYPES: ReadonlySet<string> = new Set(["codearea", "repeat-section-dnd"]); +export const CANVAS_ONLY_FORMLY_TYPES: ReadonlySet<string> = new Set(["codearea"]); /** * The custom formly widget an operator-schema property renders as, decided from the property key
