Skip to content

feat(events): before hooks and cancelable removals (#281), edit panel events (#316) - #510

Merged
kevinchappell merged 13 commits into
mainfrom
feat/281-event-action-upgrades
Sep 28, 2026
Merged

kevinchappell merged 13 commits into
mainfrom
feat/281-event-action-upgrades

Conversation

@kevinchappell

Copy link
Copy Markdown
Collaborator

What

Apps can now confirm, cancel or hold (async) the user actions add, remove, clone and save in the editor, and get told when an edit panel opens or closes. Nothing changes for apps that don't opt in: with no hook returning a Promise, every flow stays synchronous and fires the same events in the same order.

Cancelable removals (#281 phase 1)

  • The canvas remove (×) button for rows, columns and fields now calls a new actions.remove.component({ component, componentType, componentId, removeAction }). The default calls removeAction() at once; a custom handler can skip it to cancel, or call it later (e.g. after a confirm dialog). removeAction() only works once, and does nothing if the component is already gone.
  • Removing a single attribute, option or condition with its × now goes through actions.remove.attrs|options|conditions (before, only "Clear All" did), with { type, itemKey, component, isClearAll: false, removeAction }. "Clear All" passes isClearAll: true. A held item removal does nothing once the panel has been rebuilt, so it can't remove the wrong option.
  • Every actions.* dispatcher falls back to the defaults before init() (the remove.page pattern).

Before hooks (#281 phase 2)

onBeforeAdd, onBeforeRemove, onBeforeClone, onBeforeSave option callbacks, plus cancelable formeoBeforeAdd|Remove|Clone|Save DOM events dispatched inside the editor and bubbling to document.

  • Order: the DOM event (a listener can preventDefault()), then the callback, then the action if there is one (actions.remove.component / actions.remove.page), then the change and its usual events.
  • The callback cancels by returning false or calling evt.preventDefault(). Returning a Promise makes Formeo wait; it cancels if the Promise resolves false or rejects. A throw or rejection is logged.
  • Covered: canvas × and page ×/Delete; clicking or dropping a control and the page + tab ({ componentType, controlId, data, parent, index, addedVia }); the clone button; the Save button ({ formData }, and an allowed save saves that same formData).
  • While a removal or save waits, repeat clicks are ignored. After an async hook, removal re-checks the page or component (so a page that got content meanwhile still gets its confirm dialog, and the last page is never offered). A drop whose target was removed while waiting adds nothing. After destroy(), a waiting hook never completes its change.
  • Not hooked: programmatic calls (addChild, remove(), pages.add(), clear(), formData =), drag-moving existing components, "Clear All" (it has confirmClearAll), "Move to page".

Edit panel events (#316)

onEditOpen / onEditClose and the formeoEditOpened / formeoEditClosed DOM events fire when a row, column or field's edit panel actually opens or closes, with { component, componentType, componentId }. formBuilder's onOpenFieldEdit/onCloseFieldEdit map to these filtered to componentType === 'field'.

Docs

docs/options/events/README.md (callbacks, DOM events, a "Before hooks" section, "Edit panel events"), docs/options/actions/README.md ("Confirm before deleting", "Removing single items"), docs/editor/pages.md.

Back-compat note

Existing custom actions.remove.attrs|options|conditions handlers now also run for single-item removals; check evt.isClearAll if yours should only handle "Clear All".

Testing

  • npm test: 632 pass (572 on main)
  • npm run lint: clean
  • Playwright: 164 passed, 3 skipped (the 3 skips are pre-existing test.skips). New specs: tests/before-hooks.spec.js, tests/edit-panel-events.spec.js

Follow-ups

  • Per-type before-hook variants (onBeforeAddField, …), and hooks for "Move to page" and drag-moves
  • Removing a column calls autoColumnWidths twice (also on main)

Fixes #281
Fixes #316

…ield

The canvas remove button now calls actions.remove.component with the component and a one-shot
removeAction. The default removes at once; a custom handler can skip removeAction to cancel, or
call it later, e.g. after a confirm dialog. Action dispatchers fall back to the defaults before
init().

Refs #281
…hrough actions.remove

Removing one item with its × button now calls actions.remove.attrs, options or conditions
(previously only 'Clear All' did), with { type, itemKey, component, isClearAll: false,
removeAction }. 'Clear All' passes isClearAll: true. The default still removes at once.

Refs #281
… action

Events#before dispatches a cancelable formeoBefore* DOM event and calls the matching
onBefore* callback, which can cancel with preventDefault(), by returning false, or through a
Promise. With no Promise it proceeds synchronously; after destroy() it never proceeds.

Refs #281
…nt or page

The canvas remove button and a page's ×/Delete now run the formeoBeforeRemove DOM event and
the onBeforeRemove callback before actions.remove.component or actions.remove.page. A second
request for the same component is ignored while one is waiting.

Refs #281
…ols or the page tab

Clicking or dropping a control, and the page + tab, now run formeoBeforeAdd and onBeforeAdd with
{ componentType, controlId, data, parent, index, addedVia }. A cancelled drop leaves nothing
behind; a held one is added once the hook resolves, unless its target was removed meanwhile.

Refs #281
The clone button and the Save button now run formeoBeforeClone/onBeforeClone and
formeoBeforeSave/onBeforeSave first, which can cancel or hold them. A held save saves the
formData its hook saw; a second Save while one waits is ignored.

Refs #281
…s or closes

Opening or closing a row, column or field's edit panel now dispatches formeoEditOpened or
formeoEditClosed from the component and calls onEditOpen or onEditClose with { component,
componentType, componentId }, only when the panel's state actually changes.

Refs #316
Copilot AI lite review requested due to automatic review settings September 28, 2026 12:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@kevinchappell
kevinchappell merged commit ec9694c into main Sep 28, 2026
2 checks passed
@kevinchappell
kevinchappell deleted the feat/281-event-action-upgrades branch September 28, 2026 12:48
@kevinchappell

Copy link
Copy Markdown
Collaborator Author

🎉 This PR is included in version 5.11.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Exist the event onOpenFieldEdit like at the Formbuilder? Feature: event/action upgrades

2 participants