diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml new file mode 100644 index 0000000000..ab65e6faa4 --- /dev/null +++ b/.github/workflows/config.yml @@ -0,0 +1,131 @@ +# name: Build & Test + +# on: push + +# env: +# NODE_VERSION: 18.x + +# jobs: +# test: +# runs-on: ubuntu-latest +# steps: +# - run: echo "Triggered by ${{ github.event_name }} event." + +# - name: Check out repository code ${{ github.repository }} on ${{ github.ref }} +# uses: actions/checkout@v3 + +# - name: Set up Node.js ${{ env.NODE_VERSION }} +# uses: actions/setup-node@v3 +# with: +# node-version: ${{ env.NODE_VERSION }} +# cache: 'npm' + +# - name: Cache node modules +# uses: actions/cache@v3 +# with: +# path: node_modules +# key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }} +# restore-keys: | +# ${{ runner.os }}-node- + +# - name: Installing dependencies +# if: steps.cache.outputs.cache-hit != 'true' +# uses: borales/actions-yarn@v4 +# with: +# cmd: install --frozen-lockfile + +# - name: Lint +# uses: borales/actions-yarn@v4 +# with: +# cmd: lint + +# - name: Build +# uses: borales/actions-yarn@v4 +# with: +# cmd: build + +# - name: Test +# uses: borales/actions-yarn@v4 +# with: +# cmd: test + +name: Build & Test + +on: push + +env: + NODE_VERSION: 18.x + +jobs: + setup: + runs-on: ubuntu-latest + steps: + - run: echo "Triggered by ${{ github.event_name }} event." + + - name: Check out repository code ${{ github.repository }} on ${{ github.ref }} + uses: actions/checkout@v3 + + - name: Set up Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@v3 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + + - name: Cache node modules + uses: actions/cache@v3 + with: + path: node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Installing dependencies + if: steps.cache.outputs.cache-hit != 'true' + uses: borales/actions-yarn@v4 + with: + cmd: install --frozen-lockfile + + - name: Lint + uses: borales/actions-yarn@v4 + with: + cmd: lint + + build: + needs: setup + runs-on: ubuntu-latest + steps: + - name: Check out repository code ${{ github.repository }} on ${{ github.ref }} + uses: actions/checkout@v3 + + - name: Restore node modules from cache + uses: actions/cache@v3 + with: + path: node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Build + uses: borales/actions-yarn@v4 + with: + cmd: build + + test: + needs: setup + runs-on: ubuntu-latest + steps: + - name: Check out repository code ${{ github.repository }} on ${{ github.ref }} + uses: actions/checkout@v3 + + - name: Restore node modules from cache + uses: actions/cache@v3 + with: + path: node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Test + uses: borales/actions-yarn@v4 + with: + cmd: test \ No newline at end of file diff --git a/Changelog.md b/Changelog.md index 243974918f..eea6cb5340 100644 --- a/Changelog.md +++ b/Changelog.md @@ -16,6 +16,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - FIO-4816: fixed email submission: data display issues - [Snyk] Upgrade core-js from 3.32.0 to 3.32.1 - [Snyk] Upgrade vanilla-picker from 2.12.1 to 2.12.2 + - Fixed issue where content component would not save in the form. + - FIO-7206: fixed an issue where removed components keys stay in the DataGrid defaultValue property + - FIO-7309: fixed an issue where min/maxDate settings for Day component are getting evaluated in Form Builder + - FIO-7207: changed tooltip and removed 'Hide label' option from the list of layout components + - FIO-7074: fixed an issue where setting submission on wizard does not update data + - FIO-7082: Moved Wizard Breadcrumbs Type to form settings + - FIO-7224: Fixed issues with layout components when Condensed mode is enabled + - FIO-4833: Removes Hide Label setting from Well, Columns, Tabs and Table components since they do not render a label + - FIO-5910: allow manual input for w and W date formats + - FIO-6370: Fixes issues with PasswordStrength Addon settings + - FIO-7146: formiojs-circleci-to-ghactions + - FIO-6859: update-s3-to-accept-headers-from-signer-presign ## 5.0.0-rc.26 ### Changed diff --git a/src/Embed.js b/src/Embed.js index ad25434a47..97908a1f34 100644 --- a/src/Embed.js +++ b/src/Embed.js @@ -142,7 +142,7 @@ export class Formio { } // eslint-disable-next-line max-statements - static async init(element, builder = false) { + static async init(element, options = {}, builder = false) { Formio.cdn = new CDN(Formio.config.cdn); Formio.config.libs = Formio.config.libs || { uswds: { @@ -160,10 +160,19 @@ export class Formio { const id = Formio.config.id || `formio-${Math.random().toString(36).substring(7)}`; // Create a new wrapper and add the element inside of a new wrapper. - const wrapper = Formio.createElement('div', { + let wrapper = Formio.createElement('div', { 'id': `"${id}-wrapper"` }); element.parentNode.insertBefore(wrapper, element); + + // If we include the libraries, then we will attempt to run this in shadow dom. + if (Formio.config.includeLibs && (typeof wrapper.attachShadow === 'function') && !Formio.config.premium) { + wrapper = wrapper.attachShadow({ + mode: 'open' + }); + options.shadowRoot = wrapper; + } + element.parentNode.removeChild(element); wrapper.appendChild(element); @@ -246,7 +255,7 @@ export class Formio { } static async createForm(element, form, options) { - const wrapper = await Formio.init(element); + const wrapper = await Formio.init(element, options); return Formio.FormioClass.createForm(element, form, { ...options, ...{ noLoader: true } @@ -278,7 +287,7 @@ export class Formio { } static async builder(element, form, options) { - const wrapper = await Formio.init(element, true); + const wrapper = await Formio.init(element, options, true); return Formio.FormioClass.builder(element, form, options).then((instance) => { Formio.debug('Builder created', instance); Formio.debug('Removing loader'); diff --git a/src/WebformBuilder.js b/src/WebformBuilder.js index 6975e500b2..461e4559bd 100644 --- a/src/WebformBuilder.js +++ b/src/WebformBuilder.js @@ -417,7 +417,7 @@ export default class WebformBuilder extends Component { this.attachTooltip(component.refs.removeComponent, this.t('Remove')); component.addEventListener(component.refs.removeComponent, 'click', () => - this.removeComponent(component.schema, parent, component.component)); + this.removeComponent(component.schema, parent, component.component, component)); } return element; @@ -849,6 +849,10 @@ export default class WebformBuilder extends Component { } if (info) { + //if this is a custom component that was already assigned a key, don't stomp on it + if (!Components.components.hasOwnProperty(info.type) && info.key) { + return info; + } info.key = this.generateKey(info); } @@ -1081,7 +1085,7 @@ export default class WebformBuilder extends Component { } } - removeComponent(component, parent, original) { + removeComponent(component, parent, original, componentInstance) { if (!parent) { return; } @@ -1110,6 +1114,9 @@ export default class WebformBuilder extends Component { else if (parent.formioComponent && parent.formioComponent.removeChildComponent) { parent.formioComponent.removeChildComponent(component); } + if (component.input && componentInstance && componentInstance.parent) { + _.unset(componentInstance._data, componentInstance.key); + } const rebuild = parent.formioComponent.rebuild() || Promise.resolve(); rebuild.then(() => { this.emit('removeComponent', component, parent.formioComponent.schema, path, index); @@ -1451,7 +1458,11 @@ export default class WebformBuilder extends Component { { ..._.omit(this.options, ['hooks', 'builder', 'events', 'attachMode', 'skipInit']), language: this.options.language, - ...editFormOptions + ...editFormOptions, + evalContext: { + ...(editFormOptions?.evalContext || this.options?.evalContext || {}), + buildingForm: this.form, + }, } ); @@ -1577,8 +1588,15 @@ export default class WebformBuilder extends Component { } } + // If the edit form has any nested form inside, we get a partial data (nested form's data) in the + // event.data property + let editFormData; + if (event.changed.instance && event.changed.instance.root && event.changed.instance.root.id !== this.editForm.id) { + editFormData = this.editForm.data; + } + // Update the component. - this.updateComponent(event.data.componentJson || event.data, event.changed); + this.updateComponent(event.data.componentJson || editFormData || event.data, event.changed); } }); diff --git a/src/WebformBuilder.unit.js b/src/WebformBuilder.unit.js index 9311556ee4..5b3979ac16 100644 --- a/src/WebformBuilder.unit.js +++ b/src/WebformBuilder.unit.js @@ -206,4 +206,66 @@ describe('WebformBuilder tests', function() { done(); }).catch(done); }); + + it('Should keep min/max date validation settings with moment.js function', (done) => { + const builder = Harness.getBuilder(); + builder.setForm(columnsForm).then(() => { + const column1 = builder.webform.element.querySelector('[ref="columns-container"]'); + Harness.buildComponent('day', column1); + + setTimeout(() => { + const maxDateComp = builder.editForm.getComponent('maxDate'); + maxDateComp.setValue('moment().add(10, \'days\')'); + + setTimeout(() => { + Harness.saveComponent(); + + setTimeout(() => { + const dayComp = builder.webform.getComponent(['day']); + assert.equal(dayComp.component.maxDate, 'moment().add(10, \'days\')'); + done(); + }, 200); + }, 200); + }, 150); + }).catch(done); + }); + + it('Should remove deleted components keys from default value', (done) => { + const builder = Harness.getBuilder(); + builder.setForm({}).then(() => { + Harness.buildComponent('datagrid'); + + setTimeout(() => { + const dataGridDefaultValue = builder.editForm.getComponent('defaultValue'); + dataGridDefaultValue.removeRow(0); + + setTimeout(() => { + Harness.saveComponent(); + setTimeout(() => { + const dataGridContainer = builder.webform.element.querySelector('[ref="dataGrid-container"]'); + Harness.buildComponent('textfield', dataGridContainer); + + setTimeout(() => { + Harness.saveComponent(); + + setTimeout(() => { + const textField = builder.webform.getComponent(['dataGrid', 'textField'])[0]; + textField.refs.removeComponent.dispatchEvent( new MouseEvent('click', { + view: window, + bubbles: true, + cancelable: true + })); + + setTimeout(() => { + const dataGrid = builder.webform.getComponent(['dataGrid']); + assert.deepEqual(dataGrid.schema.defaultValue, [{}], 'Should remove TextField key'); + done(); + }, 300); + }); + }, 300); + }, 300); + }, 350); + }, 350); + }).catch(done); + }); }); diff --git a/src/Wizard.js b/src/Wizard.js index 7e258dd26a..da8bd99609 100644 --- a/src/Wizard.js +++ b/src/Wizard.js @@ -211,7 +211,9 @@ export default class Wizard extends Webform { } prepareHeaderSettings(ctx, headerType) { - if (this.currentPanel && this.currentPanel.breadcrumb === 'none' || ctx.isSubForm) { + const shouldHideBreadcrumbs = this.currentPanel?.breadcrumb === 'none' || + _.get(this.form, 'settings.wizardBreadcrumbsType', '') === 'none'; + if (shouldHideBreadcrumbs || ctx.isSubForm) { return null; } return this.renderTemplate(headerType, ctx); @@ -895,27 +897,30 @@ export default class Wizard extends Webform { } setValue(submission, flags = {}, ignoreEstablishment) { - this._submission = submission; - if ( - (flags && flags.fromSubmission && (this.options.readOnly || this.editMode) && !this.isHtmlRenderMode()) || + const changed = this.getPages({ all: true }).reduce((changed, page) => { + return this.setNestedValue(page, submission.data, flags, changed) || changed; + }, false); + + if (!flags.sanitize || (flags && flags.fromSubmission && (this.prefixComps.length || this.suffixComps.length) && submission._id) || (this.options.server && (this.prefixComps.length || this.suffixComps.length)) ) { - this._data = submission.data; + this.mergeData(this.data, submission.data); } - if (!ignoreEstablishment) { - this.establishPages(submission.data); - } - const changed = this.getPages({ all: true }).reduce((changed, page) => { - return this.setNestedValue(page, submission.data, flags, changed) || changed; - }, false); - if (changed) { this.pageFieldLogic(this.page); } + this.setEditMode(submission); + submission.data = this.data; + this._submission = submission; + + if (!ignoreEstablishment) { + this.establishPages(submission.data); + } + return changed; } diff --git a/src/Wizard.unit.js b/src/Wizard.unit.js index 549dfcaf10..1956a789d1 100644 --- a/src/Wizard.unit.js +++ b/src/Wizard.unit.js @@ -161,7 +161,7 @@ describe('Wizard tests', () => { }, 'Should contain correct submission data'); done(); - }, 200); + }, 500); }, 200); }, 200); }, 200); diff --git a/src/addons/index.js b/src/addons/index.js index 1555897676..fb579eb0c7 100644 --- a/src/addons/index.js +++ b/src/addons/index.js @@ -7,7 +7,11 @@ export const editForms = [ key: 'settings', display: 'form', input: true, - components, + components: components.map((comp) => { + comp.tableView = false; + return comp; + }), + tableView: false, defaultValue: { data: defaultSettings }, diff --git a/src/components/_classes/component/Component.js b/src/components/_classes/component/Component.js index 3fa7232550..c64abaa4eb 100644 --- a/src/components/_classes/component/Component.js +++ b/src/components/_classes/component/Component.js @@ -768,7 +768,7 @@ export default class Component extends Element { let contentMargin = ''; if (this.component.hideLabel) { - const margin = this.labelWidth + this.labelMargin; + const margin = isCondensed ? 0 : this.labelWidth + this.labelMargin; contentMargin = isRightPosition ? `margin-right: ${margin}%` : ''; contentMargin = isLeftPosition ? `margin-left: ${margin}%` : ''; } diff --git a/src/components/_classes/component/editForm/Component.edit.addons.js b/src/components/_classes/component/editForm/Component.edit.addons.js index b3ac3f7da1..7d63552572 100644 --- a/src/components/_classes/component/editForm/Component.edit.addons.js +++ b/src/components/_classes/component/editForm/Component.edit.addons.js @@ -10,6 +10,33 @@ export default [ input: true, key: 'addons', label: 'Addons', + templates: { + // eslint-disable-next-line quotes + header: `