Merge "Update CodeMirror"
diff --git a/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow.ts b/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow.ts
index 89769a1..59b7c19 100644
--- a/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow.ts
+++ b/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow.ts
@@ -3,7 +3,7 @@
  * Copyright 2025 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import {customElement, property, state} from 'lit/decorators.js';
+import {customElement, property, query, state} from 'lit/decorators.js';
 import {css, html, LitElement, PropertyValues} from 'lit';
 import {sharedStyles} from '../../../styles/shared-styles';
 import {grFormStyles} from '../../../styles/gr-form-styles';
@@ -11,6 +11,7 @@
 import {getAppContext} from '../../../services/app-context';
 import {NumericChangeId, ServerInfo} from '../../../types/common';
 import '../../shared/gr-button/gr-button';
+import '../../shared/gr-dialog/gr-dialog';
 import '../../core/gr-search-autocomplete/gr-search-autocomplete';
 import '@material/web/select/outlined-select.js';
 import '@material/web/select/select-option.js';
@@ -22,6 +23,7 @@
 import {flowsModelToken} from '../../../models/flows/flows-model';
 import {subscribe} from '../../lit/subscription-controller';
 import {throwingErrorCallback} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
+import {modalStyles} from '../../../styles/gr-modal-styles';
 import {
   AutocompleteSuggestion,
   fetchAccountSuggestions,
@@ -45,6 +47,9 @@
   // Property so that we can mock it in tests
   @property({type: String}) hostUrl?: string;
 
+  @query('#createModal')
+  private createModal?: HTMLDialogElement;
+
   @state()
   private stages: Stage[] = [];
 
@@ -114,6 +119,7 @@
     return [
       sharedStyles,
       grFormStyles,
+      modalStyles,
       css`
         .raw-flow-container {
           display: flex;
@@ -235,89 +241,115 @@
 
   override render() {
     return html`
-      <div class="raw-flow-container">
-        <gr-autogrow-textarea
-          placeholder="raw flow"
-          label="Raw Flow"
-          .value=${this.flowString}
-          @input=${(e: InputEvent) => {
-            this.flowString = (e.target as HTMLTextAreaElement).value;
-            this.parseStagesFromRawFlow(this.flowString);
-          }}
-        ></gr-autogrow-textarea>
-        <gr-copy-clipboard
-          .text=${this.flowString}
-          buttonTitle="Copy raw flow to clipboard"
-          hideinput
-        ></gr-copy-clipboard>
-      </div>
-      <div>${this.renderTable()}</div>
-      <div class="add-stage-row">
-        <md-outlined-select
-          value=${this.currentConditionPrefix}
-          @change=${(e: Event) => {
-            const select = e.target as HTMLSelectElement;
-            this.currentConditionPrefix = select.value;
-          }}
-        >
-          <md-select-option value="Gerrit">
-            <div slot="headline">Gerrit</div>
-          </md-select-option>
-          <md-select-option value="Other">
-            <div slot="headline">Other</div>
-          </md-select-option>
-        </md-outlined-select>
-        ${this.currentConditionPrefix === 'Gerrit'
-          ? html`<gr-search-autocomplete
-              .placeholder=${'Create condition'}
-              .value=${this.currentCondition}
-              .projectSuggestions=${this.projectSuggestions}
-              .groupSuggestions=${this.groupSuggestions}
-              .accountSuggestions=${this.accountSuggestions}
-              @text-changed=${this.handleGerritConditionTextChanged}
-            ></gr-search-autocomplete>`
-          : html`<md-outlined-text-field
-              label="Condition"
-              .value=${this.currentCondition}
-              @input=${(e: InputEvent) =>
-                (this.currentCondition = (
-                  e.target as MdOutlinedTextField
-                ).value)}
-            ></md-outlined-text-field>`}
-        <span> -> </span>
-        <md-outlined-select
-          label="Action"
-          .value=${this.currentAction}
-          @change=${(e: Event) => {
-            const select = e.target as HTMLSelectElement;
-            this.currentAction = select.value;
-          }}
-        >
-          ${this.flowActions.map(
-            action => html`
-              <md-select-option .value=${action.name}>
-                <div slot="headline">${action.name}</div>
-              </md-select-option>
-            `
-          )}
-        </md-outlined-select>
-        <md-outlined-text-field
-          label="Parameters"
-          .value=${this.currentParameter}
-          @input=${(e: InputEvent) =>
-            (this.currentParameter = (e.target as MdOutlinedTextField).value)}
-        ></md-outlined-text-field>
-        <gr-button aria-label="Add Stage" @click=${this.handleAddStage}
-          >Add Stage</gr-button
-        >
-      </div>
       <gr-button
         aria-label="Create Flow"
-        ?disabled=${this.loading}
-        @click=${this.handleCreateFlow}
+        @click=${() => {
+          this.createModal?.showModal();
+        }}
       >
         Create Flow
       </gr-button>
+      ${this.renderCreateFlowDialog()}
+    `;
+  }
+
+  private renderCreateFlowDialog() {
+    return html`
+      <dialog id="createModal" tabindex="-1">
+        <gr-dialog
+          confirm-label="Create"
+          ?disabled=${this.loading}
+          @confirm=${this.handleCreateFlow}
+          @cancel=${() => {
+            this.createModal?.close();
+          }}
+        >
+          <div slot="header">Create new flow</div>
+          <div class="main" slot="main">
+            <div class="raw-flow-container">
+              <gr-autogrow-textarea
+                placeholder="raw flow"
+                label="Raw Flow"
+                .value=${this.flowString}
+                @input=${(e: InputEvent) => {
+                  this.flowString = (e.target as HTMLTextAreaElement).value;
+
+                  this.parseStagesFromRawFlow(this.flowString);
+                }}
+              ></gr-autogrow-textarea>
+              <gr-copy-clipboard
+                .text=${this.flowString}
+                buttonTitle="Copy raw flow to clipboard"
+                hideinput
+              ></gr-copy-clipboard>
+            </div>
+            <div>${this.renderTable()}</div>
+            <div class="add-stage-row">
+              <md-outlined-select
+                value=${this.currentConditionPrefix}
+                @change=${(e: Event) => {
+                  const select = e.target as HTMLSelectElement;
+
+                  this.currentConditionPrefix = select.value;
+                }}
+              >
+                <md-select-option value="Gerrit">
+                  <div slot="headline">Gerrit</div>
+                </md-select-option>
+                <md-select-option value="Other">
+                  <div slot="headline">Other</div>
+                </md-select-option>
+              </md-outlined-select>
+              ${this.currentConditionPrefix === 'Gerrit'
+                ? html`<gr-search-autocomplete
+                    .placeholder=${'Create condition'}
+                    .value=${this.currentCondition}
+                    .projectSuggestions=${this.projectSuggestions}
+                    .groupSuggestions=${this.groupSuggestions}
+                    .accountSuggestions=${this.accountSuggestions}
+                    @text-changed=${this.handleGerritConditionTextChanged}
+                  ></gr-search-autocomplete>`
+                : html`<md-outlined-text-field
+                    label="Condition"
+                    .value=${this.currentCondition}
+                    @input=${(e: InputEvent) =>
+                      (this.currentCondition = (
+                        e.target as MdOutlinedTextField
+                      ).value)}
+                  ></md-outlined-text-field>`}
+              <span> -> </span>
+              <md-outlined-select
+                label="Action"
+                .value=${this.currentAction}
+                @change=${(e: Event) => {
+                  const select = e.target as HTMLSelectElement;
+
+                  this.currentAction = select.value;
+                }}
+              >
+                ${this.flowActions.map(
+                  action => html`
+                    <md-select-option .value=${action.name}>
+                      <div slot="headline">${action.name}</div>
+                    </md-select-option>
+                  `
+                )}
+              </md-outlined-select>
+              <md-outlined-text-field
+                label="Parameters"
+                .value=${this.currentParameter}
+                @input=${(e: InputEvent) =>
+                  (this.currentParameter = (
+                    e.target as MdOutlinedTextField
+                  ).value)}
+              ></md-outlined-text-field>
+              <gr-button aria-label="Add Stage" @click=${this.handleAddStage}
+                >Add Stage</gr-button
+              >
+            </div>
+          </div>
+        </gr-dialog>
+      </dialog>
     `;
   }
 
@@ -438,6 +470,7 @@
     this.currentAction = '';
     this.currentParameter = '';
     this.loading = false;
+    this.createModal?.close();
   }
 }
 
diff --git a/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow_test.ts b/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow_test.ts
index 3d77901..7de5659 100644
--- a/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow_test.ts
@@ -18,6 +18,7 @@
 import {getAppContext} from '../../../services/app-context';
 import {FlowActionInfo} from '../../../api/rest-api';
 import {MdOutlinedSelect} from '@material/web/select/outlined-select';
+import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
 
 suite('gr-create-flow tests', () => {
   let element: GrCreateFlow;
@@ -46,32 +47,75 @@
   });
 
   test('renders initially', () => {
-    assert.isDefined(queryAndAssert(element, 'gr-search-autocomplete'));
+    const createButton = queryAndAssert<GrButton>(
+      element,
+      'gr-button[aria-label="Create Flow"]'
+    );
+    createButton.click();
+    const createModal = queryAndAssert<HTMLDialogElement>(
+      element,
+      '#createModal'
+    );
+    assert.isTrue(createModal.open);
+
+    const grDialog = queryAndAssert<GrDialog>(createModal, 'gr-dialog');
+
+    assert.isDefined(queryAndAssert(grDialog, 'gr-search-autocomplete'));
     assert.isDefined(
-      queryAndAssert(element, 'md-outlined-select[label="Action"]')
+      queryAndAssert(grDialog, 'md-outlined-select[label="Action"]')
     );
     assert.isDefined(
-      queryAndAssert(element, 'md-outlined-text-field[label="Parameters"]')
+      queryAndAssert(grDialog, 'md-outlined-text-field[label="Parameters"]')
     );
     assert.isDefined(
-      queryAndAssert(element, 'gr-button[aria-label="Add Stage"]')
-    );
-    assert.isDefined(
-      queryAndAssert(element, 'gr-button[aria-label="Create Flow"]')
+      queryAndAssert(grDialog, 'gr-button[aria-label="Add Stage"]')
     );
   });
 
-  test('adds and removes stages', async () => {
-    const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+  test('opens and closes dialog', async () => {
+    const createButton = queryAndAssert<GrButton>(
       element,
+      'gr-button[aria-label="Create Flow"]'
+    );
+    const createModal = queryAndAssert<HTMLDialogElement>(
+      element,
+      '#createModal'
+    );
+
+    createButton.click();
+    await element.updateComplete;
+    assert.isTrue(createModal.open);
+
+    const grDialog = queryAndAssert<GrDialog>(createModal, 'gr-dialog');
+    const cancelButton = queryAndAssert<GrButton>(grDialog, '#cancel');
+    cancelButton.click();
+    await element.updateComplete;
+    assert.isFalse(createModal.open);
+  });
+
+  test('adds and removes stages', async () => {
+    const createButton = queryAndAssert<GrButton>(
+      element,
+      'gr-button[aria-label="Create Flow"]'
+    );
+    createButton.click();
+    await element.updateComplete;
+    const createModal = queryAndAssert<HTMLDialogElement>(
+      element,
+      '#createModal'
+    );
+    const grDialog = queryAndAssert<GrDialog>(createModal, 'gr-dialog');
+
+    const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+      grDialog,
       'gr-search-autocomplete'
     );
     const actionInput = queryAndAssert<MdOutlinedSelect>(
-      element,
+      grDialog,
       'md-outlined-select[label="Action"]'
     );
     const addButton = queryAndAssert<GrButton>(
-      element,
+      grDialog,
       'gr-button[aria-label="Add Stage"]'
     );
 
@@ -117,7 +161,7 @@
       },
     ]);
 
-    let removeButtons = queryAll<GrButton>(element, 'tr gr-button');
+    let removeButtons = queryAll<GrButton>(grDialog, 'tr gr-button');
     assert.lengthOf(removeButtons, 2);
 
     removeButtons[0].click();
@@ -131,19 +175,31 @@
         parameterStr: '',
       },
     ]);
-    removeButtons = queryAll<GrButton>(element, 'tr gr-button');
+    removeButtons = queryAll<GrButton>(grDialog, 'tr gr-button');
     assert.lengthOf(removeButtons, 1);
   });
 
   test('creates a flow with one stage', async () => {
     const createFlowStub = sinon.stub(flowsModel, 'createFlow');
 
-    const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+    const createButton = queryAndAssert<GrButton>(
       element,
+      'gr-button[aria-label="Create Flow"]'
+    );
+    createButton.click();
+    await element.updateComplete;
+    const createModal = queryAndAssert<HTMLDialogElement>(
+      element,
+      '#createModal'
+    );
+    const grDialog = queryAndAssert<GrDialog>(createModal, 'gr-dialog');
+
+    const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+      grDialog,
       'gr-search-autocomplete'
     );
     const actionInput = queryAndAssert<MdOutlinedSelect>(
-      element,
+      grDialog,
       'md-outlined-select[label="Action"]'
     );
     searchAutocomplete.value = 'single condition';
@@ -152,11 +208,8 @@
     actionInput.dispatchEvent(new Event('change'));
     await element.updateComplete;
 
-    const createButton = queryAndAssert<GrButton>(
-      element,
-      'gr-button[aria-label="Create Flow"]'
-    );
-    createButton.click();
+    const confirmButton = queryAndAssert<GrButton>(grDialog, '#confirm');
+    confirmButton.click();
     await element.updateComplete;
 
     assert.isTrue(createFlowStub.calledOnce);
@@ -168,21 +221,34 @@
         action: {name: 'single action'},
       },
     ]);
+    assert.isFalse(createModal.open);
   });
 
   test('creates a flow with parameters', async () => {
     const createFlowStub = sinon.stub(flowsModel, 'createFlow');
 
-    const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+    const createButton = queryAndAssert<GrButton>(
       element,
+      'gr-button[aria-label="Create Flow"]'
+    );
+    createButton.click();
+    await element.updateComplete;
+    const createModal = queryAndAssert<HTMLDialogElement>(
+      element,
+      '#createModal'
+    );
+    const grDialog = queryAndAssert<GrDialog>(createModal, 'gr-dialog');
+
+    const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+      grDialog,
       'gr-search-autocomplete'
     );
     const actionInput = queryAndAssert<MdOutlinedSelect>(
-      element,
+      grDialog,
       'md-outlined-select[label="Action"]'
     );
     const parametersInput = queryAndAssert<MdOutlinedTextField>(
-      element,
+      grDialog,
       'md-outlined-text-field[label="Parameters"]'
     );
     searchAutocomplete.value = 'single condition';
@@ -194,11 +260,8 @@
     parametersInput.dispatchEvent(new Event('input'));
     await element.updateComplete;
 
-    const createButton = queryAndAssert<GrButton>(
-      element,
-      'gr-button[aria-label="Create Flow"]'
-    );
-    createButton.click();
+    const confirmButton = queryAndAssert<GrButton>(grDialog, '#confirm');
+    confirmButton.click();
     await element.updateComplete;
 
     assert.isTrue(createFlowStub.calledOnce);
@@ -210,21 +273,34 @@
         action: {name: 'single action', parameters: ['param1', 'param2']},
       },
     ]);
+    assert.isFalse(createModal.open);
   });
 
   test('creates a flow with multiple stages', async () => {
     const createFlowStub = sinon.stub(flowsModel, 'createFlow');
 
-    const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+    const createButton = queryAndAssert<GrButton>(
       element,
+      'gr-button[aria-label="Create Flow"]'
+    );
+    createButton.click();
+    await element.updateComplete;
+    const createModal = queryAndAssert<HTMLDialogElement>(
+      element,
+      '#createModal'
+    );
+    const grDialog = queryAndAssert<GrDialog>(createModal, 'gr-dialog');
+
+    const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+      grDialog,
       'gr-search-autocomplete'
     );
     const actionInput = queryAndAssert<MdOutlinedSelect>(
-      element,
+      grDialog,
       'md-outlined-select[label="Action"]'
     );
     const addButton = queryAndAssert<GrButton>(
-      element,
+      grDialog,
       'gr-button[aria-label="Add Stage"]'
     );
 
@@ -244,11 +320,8 @@
     addButton.click();
     await element.updateComplete;
 
-    const createButton = queryAndAssert<GrButton>(
-      element,
-      'gr-button[aria-label="Create Flow"]'
-    );
-    createButton.click();
+    const confirmButton = queryAndAssert<GrButton>(grDialog, '#confirm');
+    confirmButton.click();
     await element.updateComplete;
 
     assert.isTrue(createFlowStub.calledOnce);
@@ -265,21 +338,34 @@
         action: {name: 'act-2'},
       },
     ]);
+    assert.isFalse(createModal.open);
   });
 
   test('create flow with added stages and current input', async () => {
     const createFlowStub = sinon.stub(flowsModel, 'createFlow');
 
-    const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+    const createButton = queryAndAssert<GrButton>(
       element,
+      'gr-button[aria-label="Create Flow"]'
+    );
+    createButton.click();
+    await element.updateComplete;
+    const createModal = queryAndAssert<HTMLDialogElement>(
+      element,
+      '#createModal'
+    );
+    const grDialog = queryAndAssert<GrDialog>(createModal, 'gr-dialog');
+
+    const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
+      grDialog,
       'gr-search-autocomplete'
     );
     const actionInput = queryAndAssert<MdOutlinedSelect>(
-      element,
+      grDialog,
       'md-outlined-select[label="Action"]'
     );
     const addButton = queryAndAssert<GrButton>(
-      element,
+      grDialog,
       'gr-button[aria-label="Add Stage"]'
     );
 
@@ -296,11 +382,8 @@
     actionInput.dispatchEvent(new Event('change'));
     await element.updateComplete;
 
-    const createButton = queryAndAssert<GrButton>(
-      element,
-      'gr-button[aria-label="Create Flow"]'
-    );
-    createButton.click();
+    const confirmButton = queryAndAssert<GrButton>(grDialog, '#confirm');
+    confirmButton.click();
     await element.updateComplete;
 
     assert.isTrue(createFlowStub.calledOnce);
@@ -317,30 +400,43 @@
         action: {name: 'act-2'},
       },
     ]);
+    assert.isFalse(createModal.open);
   });
 
   test('raw flow textarea is updated', async () => {
-    const rawFlowTextarea = queryAndAssert<GrAutogrowTextarea>(
+    const createButton = queryAndAssert<GrButton>(
       element,
+      'gr-button[aria-label="Create Flow"]'
+    );
+    createButton.click();
+    await element.updateComplete;
+    const createModal = queryAndAssert<HTMLDialogElement>(
+      element,
+      '#createModal'
+    );
+    const grDialog = queryAndAssert<GrDialog>(createModal, 'gr-dialog');
+
+    const rawFlowTextarea = queryAndAssert<GrAutogrowTextarea>(
+      grDialog,
       'gr-autogrow-textarea[label="Raw Flow"]'
     );
     assert.isDefined(rawFlowTextarea);
     assert.equal(rawFlowTextarea.value, '');
 
     const searchAutocomplete = queryAndAssert<GrSearchAutocomplete>(
-      element,
+      grDialog,
       'gr-search-autocomplete'
     );
     const actionInput = queryAndAssert<MdOutlinedSelect>(
-      element,
+      grDialog,
       'md-outlined-select[label="Action"]'
     );
     const paramsInput = queryAndAssert<MdOutlinedTextField>(
-      element,
+      grDialog,
       'md-outlined-text-field[label="Parameters"]'
     );
     const addButton = queryAndAssert<GrButton>(
-      element,
+      grDialog,
       'gr-button[aria-label="Add Stage"]'
     );
 
@@ -376,7 +472,7 @@
     );
 
     // Remove first stage
-    const removeButtons = queryAll<GrButton>(element, 'tr gr-button');
+    const removeButtons = queryAll<GrButton>(grDialog, 'tr gr-button');
     removeButtons[0].click();
     await element.updateComplete;