# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## [9.2.3] - 2026-10-23

### Fixed
- A block's html edited in place (the codemirror-inline plugin) in a flex or
  grid column no longer has the blocks below it laid over it. With the
  inline-style plugin, a flex or grid node takes its drawer out of the flow,
  and the editor, a drawer by class, was taken out with it. It now stays in
  the flow, where the block was.

## [9.2.2] - 2026-10-23

### Fixed
- The canvas no longer flickers with the pointer still between the source
  and preview buttons. The preview look could move the buttons (a scrollbar
  going, the toolbar reflowing) off the pointer, putting the editing look
  back, which moved them under it again. Hovering now holds the look until
  the pointer leaves where the preview button was when it came in.

## [9.2.1] - 2026-10-22

### Fixed
- A toolbar button dropped on the canvas lands where the marker showed. Where
  it went was worked out once the canvas had closed up, and without the
  marker and the gaps the drag opens the pointer could be over another
  region: dropped at the foot of a column in a tab, the block went to the
  column the tabs were in, after them.

## [9.2.0] - 2026-10-22

### Changed
- clipboard: Ctrl+V with no target pastes at the end of the canvas, in the
  row and column it lacks, as the toolbar's paste buttons do, rather than
  doing nothing. A top-level row or section cut with Ctrl+X comes back with
  Ctrl+V.

### Added
- `ge.placeInTarget(node, kind, source, { fallback: 'canvas' })` on the plugin
  handle: with no target, the node goes at the end of the canvas rather than
  nowhere.

## [9.1.1] - 2026-10-22

### Fixed
- `drawer_overflow: 'menu'`: the drawers are fitted all at once - every
  write, then every read - rather than one at a time, each measure laying the
  page out again. On a canvas of 150 rows a change of view takes half the
  time it did, and a `getHtml()` nearly half.
- inline-style: the drawers of flex and grid nodes are measured the same way,
  every read before any write.
- `getHtml({ keepEditing: true })`, and so history and autosave, copy the
  canvas without its drawers rather than copying them only to take them off:
  three times faster on a large canvas.
- The toolbar follows the page's scrolling once a frame: a scroll also comes
  when the page changes height, as it does in a `getHtml()`, and read there
  and then it laid the page out again.

## [9.1.0] - 2026-10-22

### Changed
- A settings panel's plugin sections and *Responsive* section are built when
  the panel opens and taken away when it closes, rather than built into every
  drawer up front. With inline-style they were most of the canvas: in the
  playground's sample, 42,000 of its 42,200 elements, now 2,300, with an add
  down from over 100 ms to under 10, and `getHtml({ keepEditing: true })` from
  60 ms to 2. `panelSection(node, kind)` is called each time the node's panel
  opens, from the node as it is then.
- `ge.detailsOf(node)` builds the rest of a panel that was not open;
  `ge.detailsOf(node, { build: false })` hands it back as it is.

### Fixed
- Every add, and every other change that brings the canvas up to date, made
  every row, column and region sortable again without undoing the instances
  already there, so each list gathered one more SortableJS instance, and its
  listeners, per change. A list made sortable keeps its one instance, and a
  list taken off the canvas loses its own.

## [9.0.0] - 2026-10-22

The first stable 9.0: what 9.0.0-beta.1 to 9.0.0-beta.8 below brought, and
this change on top of beta.8. See [UPGRADING.md](UPGRADING.md).

### Fixed
- `toolbar_groups`: the category tabs have the page's background, rather than
  none, so with the toolbar fixed at the top the canvas no longer shows through
  them.

## [9.0.0-beta.8] - 2026-10-22

### Added
- inline-style: `inline_style.browse`, the host's file browser. With it the
  background image field has a *Browse…* button that calls it with
  `(done, { node, property, value })`; the url it answers - `done(url)` or a
  promise - goes in as if it had been typed. Without it, the field is the text
  box it was.

### Changed
- `toolbar_overflow: 'menu'`: the more button comes right after the last add
  button on the line, joined to it as one more button of its group, rather
  than at the far end of the room the line had. The buttons on the right stay
  at the end.

## [9.0.0-beta.7] - 2026-10-21

### Added
- clipboard: cut. A *Cut* tool beside *Copy* in every drawer that has it, and
  Ctrl+X (⌘X) on the target, with `active_target`. It copies the node and
  removes it as its delete tool does, without asking whatever `confirm_delete`
  says. It is all or nothing: a `before-delete` that turns it away leaves the
  clipboard as it was. The target goes to what held the node before it leaves,
  so a paste right after puts it back. The `before-delete` and `after-delete`
  of a cut have `source: 'cut'`, and `after-copy` has `cut`, true for a cut.
- The plugin handle's `ge.removeNode(node, { source, confirm, before })`: a node
  removed as its own delete tool does, as asked.

### Changed
- The plugin handle's `ge.setActiveTarget(null)` takes the target away, and
  answers `true`.

### Fixed
- history: deleting a node with its tool was two steps, one of them the canvas
  halfway through the node's exit animation. A node animating out is marked
  `ge-leaving` while it goes, and history waits for it: a delete, and a cut,
  is one step. The mark never reaches `getHtml`.

## [9.0.0-beta.6] - 2026-10-21

### Changed
- **BREAKING:** the selection and the active target are one. With
  `active_target`, and so with `settings_panel: 'sidebar'`, a click - or the
  focus coming into a node - makes the innermost node with settings the
  target, in any panel mode: a row, a column, a section, a container or one of
  its panes, a text, an element. A click on the page outside the editor clears
  it, with every panel, and Escape clears it in one press. The sidebar shows the
  target's settings and the clipboard's Ctrl+C copies it, so a container, a row
  or an element can be copied from the keyboard.
- **BREAKING:** `getSelected()`, `setSelected()`, the event `selection-change`
  and `callbacks.selection_change` are removed, on the instance, on the plugin
  handle and on the jQuery adapter. Use `getActiveTarget()`,
  `setActiveTarget()` and `target-change`. `setActiveTarget` takes any node with
  settings, and its warning says so.
- **BREAKING:** the target's mark is the class `ge-selected`, in place of
  `ge-active-target`. A node with its settings open in the sidebar no longer
  has `ge-settings-target`.
- The toolbar's buttons and Ctrl+V add inside the target, at the end of what it
  holds, in the level it lacks: a row into a row goes in a new column of the
  row, a pasted column into a column goes in a new row, a text or an element
  target adds to its column, and a container adds to the pane it shows (the
  open tab, accordion item, the slide showing, the body of a popup, an
  offcanvas or a card). A section still goes to the canvas after the block the
  target is in.

### Added
- A container plugin's definition can have `regionOf(container, pane)`, where
  the rows of its visible pane, or of the pane given, go. The tabs, accordion,
  carousel, popup, offcanvas and card plugins have it.
- `ge.placeInTarget(node, kind, source)` on the plugin handle: a node made,
  added to the target as the toolbar adds what it makes.

## [9.0.0-beta.5] - 2026-10-21

### Added
- With the preview button pressed - clicked, not only hovered - the popups and
  offcanvases are closed as on the page, and their triggers and close buttons
  open and close them with Bootstrap's own javascript. Let go, an open one
  closes and each is unfolded in place again, as it was; ending the preview
  in the middle of Bootstrap's animation waits for it. `getHtml` in the middle
  of a preview gives them closed, and the preview goes on.
- The event `preview-change`, with `previewing`, when the preview button is
  pressed or let go. Opening the source view, a plugin's `setHtml` (an undo)
  and `destroy` let it go first.
- A plugin's `onPreviewChange(previewing)` hook, and `ge.previewing()` on the
  plugin handle.

## [9.0.0-beta.4] - 2026-10-20

### Added
- offcanvas: a container plugin for Bootstrap's offcanvas, with its trigger.
  While editing the panel is unfolded in place and its body is a region, as a
  popup's is. `createContainer('offcanvas', { title, trigger, trigger_label,
  placement, backdrop, scroll, keyboard, responsive, dark })`, the `offcanvas`
  setting for the ones made new, and an *Offcanvas* section in its settings
  panel. With `responsive` the panel's own trigger gets `d-<breakpoint>-none`.
  The event `offcanvas-orphan`, for a trigger whose offcanvas is missing.
- A container plugin's definition can have `references`: the attributes of its
  own that hold an id, which `freshNode` renames along with the id when a copy
  is made.

### Changed
- The core no longer names the popup: its ids are followed through the popup
  plugin's `references`, and the trigger handling the popup and the offcanvas
  share moved out of the core with it. Nothing changes in the markup, the
  events or the behaviour of a popup.

## [9.0.0-beta.3] - 2026-10-20

### Added
- history: a plugin for undo and redo. Ctrl+Z goes back a step and Ctrl+Y or
  Ctrl+Shift+Z comes forward (⌘ on a Mac), and two buttons on the right of the
  toolbar do the same. A step is the canvas `delay` ms after the last change,
  and the node selected and the target are chosen again by where they were.
  `history: { shortcuts, buttons, max, delay }`; `undo()`, `redo()`,
  `canUndo()`, `canRedo()` and `clearHistory()`; the events `after-undo`,
  `after-redo` and `history-change`.
- A feature plugin's `toolbar` item can have an `action` in place of `create`:
  a button that does something instead of adding a block, and `disabled`.
- The plugin handle has `setSelected(node)` and `setActiveTarget(node)`.

## [9.0.0-beta.2] - 2026-10-20

### Added
- clipboard: Ctrl+C and Ctrl+V (⌘C and ⌘V) with `active_target`, and so with
  the sidebar. Ctrl+C copies the node selected or the target; Ctrl+V pastes
  into the target, or the node selected, by the paste rules the toolbar and
  the tools already have. The keys stay the browser's while a field or a
  text is edited, while text is selected, or while a panel or a dialog is
  open. They use the plugin's own store, never the system clipboard, and go
  to the editor used last. `clipboard: { shortcuts: false }` turns them off;
  `after-copy` says `source: 'shortcut'` for them.
- The plugin handle has `getSelected()`, `getActiveTarget()`, `panelOpen()`
  and `contains(node)`.

## [9.0.0-beta.1] - 2026-10-20

### Changed
- **BREAKING:** `settings_panel: 'sidebar'` turns `active_target` on. A click
  in a column or a section is also where the toolbar's buttons add, as with
  `active_target: true`, instead of the end of the canvas.
- **BREAKING:** `settings_panel: 'sidebar'` with `active_target: false` throws
  a `TypeError` from the constructor, which builds nothing.

## [8.10.1] - 2026-10-13

### Fixed
- clipboard: a toolbar built again (another plugin's buttons changing,
  `setLocale`) showed every paste button, not only the one that fits what was
  copied.

### Added
- For plugins: `hidden` on a toolbar item, a boolean or a function called
  whenever the toolbar is built, builds its button hidden.

## [8.10.0] - 2026-10-07

### Added
- For plugins: `ge.freshNode(html)`, the markup as a node with new ids where
  the page already has them and its references following, which is what the
  clipboard pastes. A plugin that inserts saved markup no longer needs its
  own copy.

## [8.9.0] - 2026-10-07

### Added
- `settings_panel: 'sidebar'`: the settings of a node in an offcanvas that stays
  on the right while you edit and makes room for itself on the page. A click on
  any node of the canvas shows its settings, and the gear is gone from the
  drawers. A button on its left edge folds it away and back; narrower than
  576px it covers the page instead, and starts folded. One editor per page can
  have it; a second warns once and uses `'offcanvas'`.
- `sidebar.empty` and the `sidebarEmpty` plugin hook: what the sidebar shows
  while nothing is selected, in place of its message.
- The `grideditor:selection-change` event (and the `selection_change` callback),
  and the `getSelected()` and `setSelected()` methods, in the jQuery API too.
- Locale keys `panel.sidebar_title`, `panel.sidebar_empty`, `panel.collapse`
  and `panel.expand`, in English and Spanish.

## [8.8.2] - 2026-10-07

### Changed
- carousel: Bootstrap's own arrows are no longer drawn on the canvas, where
  they sat over the slide's content. They stay in the markup and in what
  `getHtml` gives; the arrows in the container's drawer change the slide.

## [8.8.1] - 2026-10-06

### Fixed
- tabs: `getHtml` gives the tabs without the editor's `ge-tab` class, which
  it left on every tab since it was written. The canvas still uses it while
  editing.

## [8.8.0] - 2026-10-06

### Added
- The carousel plugin, `dist/plugins/grideditor.carousel.js`: a Bootstrap 5
  carousel whose slides are regions. One slide shows at a time, the arrows in
  the container's drawer move between them and each slide's drawer moves it a
  place; Bootstrap's own arrows and indicators are drawn but do not answer.
  `getHtml` gives the carousel starting at the first slide.
- A carousel's options - arrows, indicators, fade, dark theme, autoplay and
  its interval, pause, wrap, keyboard and touch - in a *Carousel* section of
  its settings panel, in the `carousel` setting for the ones made new, and in
  the options of `createContainer('carousel', …)`; a slide's own interval in
  its settings panel. The setting `carousel_tools` adds host tools to a slide's
  drawer.
- `addPane(container, options?)` adds a pane to a container of any type that
  has them, and `addTab` and `addAccordionItem` stay as they were.
- For plugins: a container's definition can have `paneClass` and
  `paneLabelKey`, which is how the editor names its panes in events and in
  their settings panel; and `ge.suspendToggles` and `ge.resumeToggles` put
  aside `data-bs-slide`, `data-bs-slide-to` and `data-bs-ride` as well.

## [8.7.1] - 2026-10-02

### Fixed
- autosave: `clearDraft()` drops the save still waiting, which wrote the
  draft back a moment later - a page that cleared it after saving to its
  server found it there again - and what there is when it is called is what
  a new draft starts from, so only a change after it saves again.

## [8.7.0] - 2026-10-02

### Added
- For plugins: `ge.refreshToolbar()` builds the toolbar again, and a feature
  plugin's `toolbar` can be a function, read each time the toolbar is built -
  for buttons that come and go, a user's saved widgets. A toolbar button
  makes the item it was built from, even if the plugin's list has changed
  since.

### Changed
- `package.json`'s `publishConfig` loses its `comment`, which npm warned
  about.

## [8.6.0] - 2026-10-02

### Added
- The autosave plugin, `grideditor.autosave.js`: what is edited is kept in
  `localStorage` or `sessionStorage` a second after each change and when the
  page is left, without closing the text being typed into. An editor that
  starts from the html a draft was made from asks, in its confirm modal,
  whether to restore it. Options `enabled`, `storage`, `key`, `delay` and
  `maxAge`; methods `enableAutosave`, `disableAutosave`, `saveDraft`,
  `getDraft` and `clearDraft`; events `after-autosave`, `after-restore-draft`
  and `autosave-error`.
- `getHtml({ keepEditing: true })` and `getPlainHtml({ keepEditing: true })`:
  the same markup, read on a copy of the canvas, so the canvas never leaves
  editing - open texts, settings panels and the focus stay where they are.
  `custom_filter` is run on the copy too.
- For plugins: `cleanMarkup(root, liveOf)`, what `unmark`, `onDeinit` and
  `onBeforeDeinit` take off, taken off the copy - a plugin that has one of
  those and no `cleanMarkup` makes `keepEditing` fall back on `getHtml`, with
  a warning; `onDestroy`; and on the handle `ge.snapshotHtml()`,
  `ge.setHtml(html)` and `ge.confirm(message, options, answer)`. A text editor
  of `GridEditor.texts` has `read(contentArea)` and `cleanCopy(copy, area)`.
  Every shipped plugin and text editor has them.
- A feature plugin's `methods` are the instance's, whatever their names: up
  to now only the editor's own documented ones were.

### Changed
- `example/autosave.html` is built on the plugin, with an on/off switch and
  a key per imported file; `AUTO_SAVE.md` is gone, its content in the README.
- The toolbar's add buttons on the left - rows, containers, the feature
  plugins' and the overflow menu's button - are grey (`btn-light`), and the
  `toolbar_groups` tabs `btn-outline-secondary`. Blue is left to the buttons
  on the right: source, preview, layout and the `align: 'end'` ones.

## [8.5.0] - 2026-10-01

### Added
- `elements.types`: the elements the toolbar offers, a button each. A type
  has its `type`, its `html` - a string, or a function called for each one
  made - a `label` or `labelKey`, an `iconClass` and a `group`. A button
  makes the html's root, marked with `data-ge-element` and `data-ge-label`
  the way a host marks its own elements, or a `div` around html with no
  single root. Declaring a type turns `enabled: 'auto'` on.
- `toolbar_groups`: a third core category, *Elements*, after *Content*. The
  element buttons go there unless they name another `group`.
- A feature plugin's `toolbar` item whose `create()` returns `null` adds
  nothing, rather than failing.

### Changed
- `toolbar_groups`: the *Text* buttons are in *Elements* now, not in
  *Content*, which keeps the containers and the sections.

## [8.4.0] - 2026-10-01

### Added
- `drawer_overflow`: the drawers of rows, columns, containers, elements and
  sections keep their tools to one line. The tools that don't fit are hidden
  from the end, behind a `⋯` tool that unfolds the drawer to show them all,
  and come back when there is room. `'wrap'` keeps the old behaviour.
- `toolbar_groups`: with `true`, the toolbar's add buttons are sorted into
  categories, with tabs at the start of the toolbar choosing which one shows:
  *Rows* and *Content* from the core, and a plugin's own. A `toolbar` item or
  a container names its category with `group`; one that names none has its
  plugin's. The label is the `group.<name>` locale string. Off by default.

### Changed
- `active_target`: a click again on the target's own background or on its
  drawer, away from its tools, clears it. A click on what it holds - a text,
  a tool, a nested column - works as before.

### Fixed
- `toolbar_overflow`: squeezed, the toolbar's buttons wrapped their labels
  onto a second line instead of going behind the more button.

## [8.3.0] - 2026-10-01

### Added
- `toolbar_overflow`: the toolbar keeps to one line. The add buttons that
  don't fit go behind a `⋯` button at its end, which opens them in a panel,
  and come back onto the line when there is room. `'wrap'` keeps the old
  behaviour of wrapping onto another line. The `tool.more` locale key is the
  button's title.

## [8.2.0] - 2026-09-30

### Added
- `active_target`: with `true`, a click in a column or a section makes it
  where the toolbar's buttons add, marked with a dashed outline, instead of
  the end of the canvas. What a button makes goes where a drop at the
  target's end would put it: a row into a column as a nested row, a text or
  a container as it is, a section after the block the target is in. The
  toolbar's paste button pastes there too. Escape, a click on no column or
  section, or deleting the target clears it. `getActiveTarget()`,
  `setActiveTarget(node | selector | null)` and the `grideditor:target-change`
  event (`callbacks.target_change`). Off by default.

## [8.1.0] - 2026-09-30

### Added
- `resize.tools`: `false` takes the narrower and wider tools (− +) out of
  the columns' drawers, leaving the handle on the edge. With
  `resize.enabled: false`, which takes the handle out, a column is resized
  with the tools only; the default keeps both.
- `indent.tools`: `false` takes the indent tools out of the columns'
  drawers. The offsets the markup has are kept, and `createColumn` still
  takes one.

### Fixed
- A shadow in the inline-style plugin's Border and Text sections, turned to
  text with *Edit as text*, can go back to the builder: *Edit with the
  builder* (`inline_style.shadow_builder_mode`), when the value is one
  shadow or none.

## [8.0.0] - 2026-09-30

The first stable 8.0: what 8.0.0-beta.1 to 8.0.0-beta.3 below brought, and
these changes on top of beta.3. See [UPGRADING.md](UPGRADING.md).

### Changed
- **BREAKING:** The style plugin is renamed `inline-style`, with the same
  behaviour: `grideditor.inline-style.js` (`plugins/inline-style` as a
  module), `'inline-style'` in `plugins`, `GridEditor.utilities['inline-style']`,
  the `inline_style` setting (`InlineStyleOptions` in the types), the
  `inline_style.*` locale keys and the `ge-inline-style-*` classes and
  attributes. There is no alias for the old names. `example/style.html` is
  `example/inline-style.html`.
- A node's settings panel names each of its sections: a heading over the id,
  the classes and the presets (`panel.section_general`, "Id and classes"),
  and over each plugin's section - Style, Tabs - where it is in the panel
  rather than behind a button. The sections are set off by a line.

### Fixed
- A row's drawer starts at the row's edge, as a column's does: Bootstrap's
  gutter padding for a row's children no longer pushes its tools in, nor its
  max width keeps the drawer from reaching the other side.

## [8.0.0-beta.3] - 2026-09-30

### Added
- Variants for the tabs container: style (`nav-tabs`, `nav-pills`,
  `nav-underline`), width (`nav-fill`, `nav-justified`), alignment
  (`justify-content-center`, `-end`) and a vertical layout, always or from a
  breakpoint up (`d-{bp}-flex`, `flex-{bp}-column`). They are chosen in a
  Tabs section of the container's settings panel, given to
  `createContainer('tabs', { variant, width, align, vertical })`, or set for
  the new ones with the `tabs` setting. While editing, each view shows the
  layout its breakpoint has.

### Fixed
- A tab strip without `nav-tabs` - pills or underline written by hand - is
  recognised as tabs.

## [8.0.0-beta.2] - 2026-09-29

### Added
- The style plugin's catalog has Bootstrap 5.3's colors: `bg-*-subtle`,
  `text-bg-*`, `text-*-emphasis`, `text-body-emphasis` and `border-*-subtle`,
  the opacities `bg-opacity-*`, `text-opacity-*` and `border-opacity-*`,
  `fixed-top` and `fixed-bottom`, and on an element `img-fluid`,
  `img-thumbnail` and `object-fit-*`.
- A Flex section in the style plugin: direction, wrap, `justify-content`,
  `align-items`, `align-content`, and on anything but a row `gap`,
  `row-gap`, `column-gap`, fill, grow and shrink, per breakpoint, with
  `vstack` and `hstack` as chips. `style.sections.flex` turns it off.
- `sticky-{bp}-top` and `sticky-{bp}-bottom`, per breakpoint, in the style
  plugin's Position section.
- On a node that is flex or grid in the view being edited, the drawer sits
  on top out of the flow, and the canvas lays the content out as the page
  will.

### Changed
- **BREAKING:** The style plugin's `visibility` utility is `display`, and
  edits every `d-{bp}-*` value from `none` to `inline-flex`. The eye shows a
  node again with the display it has below, so a flex column comes back
  flex. `utility.visibility` and `utility.visibility_shown` are gone from the
  locales, and `utility.display` is new.
- **BREAKING:** `justify-content-*` and `align-items-*` are the style
  plugin's, in its Flex section, on rows, columns, elements and containers.
  The alignment plugin keeps `align-self-*` only.
- A node Bootstrap hides in the view being edited is kept on the canvas with
  the display it would have, not as a block.
## [8.0.0-beta.1] - 2026-09-29

Not published: what it holds shipped in 8.0.0-beta.2.

### Removed
- **BREAKING:** `grideditor.spacing.js`, `grideditor.textalign.js`,
  `grideditor.visibility.js` and `grideditor.float.js`, deprecated in 7.3:
  the style plugin carries them. Their names in the `plugins` setting are
  plugins that are not loaded.
- **BREAKING:** `utilities.spacing` and `utilities.visibility` are no longer
  read; they are `style.spacing` and `style.visibility`.
- **BREAKING:** `replaces` on a plugin's factory, added in 7.3 for those
  plugins.
- **BREAKING:** The numeric layout mode indexes of 2.x in `changeView`,
  `getUtility` and `setUtility`: they are views that do not exist.
- **BREAKING:** The warnings about `sortable_options` and
  `resizable_options`, removed in 4.0, and about `tinymce.config.oninit`,
  removed in 7.0: they are ignored, and `oninit` is passed to tinyMCE as it
  is.
- **BREAKING:** `ckeditor.config.on.instanceReady`, CKEditor 4's, which 7.x
  called itself: it is not called, and `on` goes to CKEditor 5 as it is.
- **BREAKING:** The adapter's `$.fn.gridEditor.containers`, `.features`,
  `.utilities` and `.texts`, which kept a plugin written for 6.x from
  throwing: one throws as it loads now.
- The locale keys `warning.setting_removed`, `warning.oninit_removed` and
  `warning.plugin_6x`, and the words on CKEditor 4 in
  `error.ckeditor_missing`.
- Notes on earlier versions in the README, the docs and the code;
  UPGRADING.md keeps them. See [UPGRADING.md](UPGRADING.md).


## [7.3.0] - 2026-09-29

### Added
- The style plugin, `grideditor.style.js`. Every row, column, container,
  pane, element and section gets a *Style* accordion in its settings panel:
  size, spacing, border, background, text, typography, display, position
  and free css. It is written to the node's own `style` through the
  browser, so a value the browser refuses is marked and not written, and
  what the host wrote and nobody edits is kept. Each section offers
  Bootstrap 5.3's classes for what it styles as chips, which put a class in
  the classes field or take it off. A field whose property a Bootstrap
  utility on the node also sets says that the class takes priority. Where
  `settings_panel` is `popover` or `inline` the accordion opens in a
  dialog, from a *Style* button. The `style` setting chooses the sections,
  and in each the properties and whether it has chips. See the README and
  [example/style.html](example/style.html).
- The style plugin carries spacing, text alignment, visibility and float:
  their per breakpoint fields are in its Spacing, Text, Display and
  Position sections rather than in *Responsive*, and do what they did.
  `style.spacing` and `style.visibility` take what `utilities.spacing` and
  `utilities.visibility` took.
- For plugins: `panelSection(node, kind)`, a section of a plugin's own in a
  node's settings panel, between its general fields and *Responsive*;
  `ge.openDialog`/`ge.closeDialog`, a modal of the editor's own over the
  settings; `ge.hostStyle`/`ge.setHostStyle`, the host's own style under a
  breakpoint preview; and `replaces` on a factory, for a plugin that takes
  the place of others. A field made with `ge.utilityField` follows the view
  and the classes field wherever the plugin puts it. See
  [docs/plugins.md](docs/plugins.md).

### Deprecated
- `grideditor.spacing.js`, `grideditor.textalign.js`,
  `grideditor.visibility.js` and `grideditor.float.js`, which go in 8.0.
  Loaded on their own they do what they did, and say once per editor that
  they are deprecated; loaded beside the style plugin they stand down, and
  their names in the `plugins` setting ask for it. `utilities.spacing` and
  `utilities.visibility` are still read, when `style.spacing` and
  `style.visibility` are not there, and go in 8.0 too. See
  [UPGRADING.md](UPGRADING.md).


## [7.2.0] - 2026-09-29

### Changed
- **BREAKING:** The CKEditor integration is CKEditor 5, and the example and
  the tests use CKEditor 48.5.2, the latest release and one with no known
  advisories. CKEditor 4.22.1 has five (GHSA-fq6h-4g8v-qqvm,
  GHSA-6v96-m24v-f58j, GHSA-7r32-vfj5-c2jv, GHSA-wh5w-82f3-wrxh,
  GHSA-mw2c-vx6j-mg76), and every CKEditor 4 with them fixed is commercial.
  CKEditor 5 is GPL-2.0-or-later, and does not start without a `licenseKey`
  in `ckeditor.config`: `'GPL'`, or a commercial key. `ckeditor.config` is
  CKEditor 5's; the plugin gives it plugins, a toolbar and h1 to h6 headings
  when the config has none, and still calls `on.instanceReady`. The content
  type is still `ckeditor`, so saved markup loads as it is, and the `ck-*`
  classes CKEditor 5 puts on a content area stay out of it. See
  [UPGRADING.md](UPGRADING.md).

### Added
- `error.ckeditor_start`, logged when CKEditor 5 cannot start, and
  `warning.ckeditor_plugin`, for a plugin name the config gives that CKEditor
  does not have.


## [7.1.0] - 2026-09-29

### Changed
- The tinyMCE integration now targets tinyMCE 7, and the examples, the
  Angular app and the tests use TinyMCE 7.9.3. 6.8.6 has five known
  cross-site scripting advisories (GHSA-5359-pvf2-pw78, GHSA-mh5m-5hw4-5c69,
  GHSA-vg35-5wq7-3x7w, GHSA-q742-qvgc-gc2f, GHSA-v98h-vmpc-fpqv), and 7.9.3
  is the first release with none. This changes tinyMCE's licence from MIT to
  GPL-2.0-or-later: the examples pass `license_key: 'gpl'`, and a page that
  uses tinyMCE 7 passes that or its own commercial key in `tinymce.config`.
  grid-editor stays MIT. The integration still works with tinyMCE 6.

## [7.0.0] - 2026-09-28
grid-editor is plain DOM: no jQuery. See [UPGRADING.md](UPGRADING.md).

### Added
- `GridEditor`: `new GridEditor(target, options)`, `GridEditor.create` and
  `GridEditor.get`. The methods are the instance's own, and the ones that do
  something chain.
- The module build: `dist/grideditor.esm.js`, and each plugin and locale as
  a module that registers as it is imported. `package.json` has `module`,
  `exports` and `types`.
- `dist/grideditor.d.ts`: the public API's types.
- `grideditor.jquery.js`, the adapter: the 6.x jQuery API, unchanged, for a
  page that loads jQuery 4.
- `GridEditor.Sortable` and `GridEditor.bootstrap`, for an app that imports
  SortableJS and Bootstrap rather than loading them as globals.
- `nodes` in `after-edit-html`'s payload: every element written.

### Changed
- Events are DOM events on the canvas, `CustomEvent`s with the payload as
  their `detail`; they bubble. Payloads, `create*` results, the plugin handle
  and every plugin hook give and take elements.
- The classic scripts are `dist/grideditor.js`, `.min.js` and
  `grideditor.bundle.min.js`; the `jquery.grideditor*` names are gone.
- A `<script>` in html the editor writes is not run in the editor.
- A listener that throws no longer stops the operation.
- A second editor asked for on an element hands back the first, and warns.
- Plugins register on `GridEditor.containers`, `.features`, `.utilities` and
  `.texts`; the build is esbuild, from ES module sources.
- `error.ckeditor_missing` no longer mentions a jQuery adapter, and
  `error.summernote_missing` says summernote needs jQuery.

### Removed
- jQuery as a dependency. Only the summernote plugin uses it, since
  summernote does.
- `remove()`, the alias of `destroy()` deprecated since 3.1.
- `$.fn.gridEditor.RTEs`, ignored since 6.0.
- tinyMCE's `oninit` option, the name tinyMCE used before 6, kept until now: ignored, with a warning.

### Fixed
- `custom_filter` given as a function or an array of them runs.
- `destroy()`, or `setLocale()`, with a Bootstrap modal open or on its way in
  or out - the settings panel with `settings_panel: 'modal'`, or the delete
  confirmation - threw from Bootstrap's transition and left the page's body
  locked, with no scrolling. The modal is closed first now, and taken away
  once Bootstrap has hidden it.


## [6.0.0] - 2026-09-28
### Removed
- The *Add text* tool in each column's drawer, and its `tool.add_text` and
  `tool.add_text_type` locale keys. A text is added with the toolbar's *Text*
  button, clicked or dragged into a column, or with `createText`.


## [6.0.0-beta.5] - 2026-09-28
### Added
- `edit_source`: whether the toolbar has its source button, `true` by
  default.
- The codemirror plugin, `grideditor.codemirror.js`: the source view in
  CodeMirror 5, highlighted and with line numbers, where it was a plain
  textarea; `codemirror.config` for CodeMirror's own options. See
  [example/codemirror.html](example/codemirror.html).
- `onSourceOpen` and `onSourceClose` for a feature plugin: the source view
  opening, with the canvas's html in the textarea, and closing.
- The codemirror-inline plugin, `grideditor.codemirror-inline.js`: a </> tool
  in the drawer of every row, column, text, element, section and container,
  and of the host's plain content, that edits the block's html in place in
  CodeMirror - the block itself and everything in it - and puts what was
  written in its place on *Apply*, through the new `before-edit-html`
  (cancelable) and `after-edit-html` events.
- `plainTools` for a feature plugin: tools in the drawer of the host's plain
  content, which has no `drawerTools`.

### Fixed
- `destroy` with the source view open left the canvas hidden and the html
  being written unused; it closes the source first.


## [6.0.0-beta.4] - 2026-09-28
### Added
- `iconClass` for a container plugin and a text editor plugin: their toolbar
  button shows the icon alone, with the label as its title, where it showed a
  plus and the label.
- **Plain content.** What the editor wraps that is not the grid's - loose
  markup in a column, a page with no row - is the host's plain content: a
  content area with no text type, `<div class="ge-content">`. A block to move
  and delete, with no gear, edited in the source, and never made by the
  editor's tools; `kind: 'plain'` in the events. With a text editor loaded a
  click makes it a text of that editor, or of the one chosen when several are
  offered, through the new `before-convert` (cancelable) and `after-convert`
  events. See [docs/plugins.md](docs/plugins.md#text).
- `textTypes` for a feature plugin: the text types it edits, which is how the
  core knows whose a text is and what plain content can be made.
  [example/custom_editor.html](example/custom_editor.html) is a text editor
  written that way, with none of the shipped ones.
- `onBeforeDeinit` for a plugin, run by every `deinit` before the drawers come
  off; `inColumn`, `label` and `className` for a feature plugin's toolbar
  item; `ge.attachPicker`, `ge.openPicker` and `ge.closePicker` in the handle.

### Changed
- **Text is out of the core.** The text block's drawer, the *Text* buttons,
  the *Add text* tool, `createText`, opening and closing an editor and the
  `content_types`, `text_tools` and `text_classes` settings are the text
  editor plugins': each of `grideditor.tinymce.js`, `grideditor.ckeditor.js`
  and `grideditor.summernote.js` carries them, installed once however many a
  page loads. Without one, `createText` warns and returns null.
- **New columns start empty**, whatever makes them: the add column tool, the
  row buttons, `createRow`, `createColumn`, a container's or a pane's region.
  `createColumn(size, { content })` holds its content as a text of the first
  editor offered, or as plain content with none.
- Loose content was wrapped as a text of the first `content_types`; it is
  plain content now, and a click makes it a text.
- `content_types` defaults to every text editor loaded, in the order the page
  loaded them, where it was `['tinymce']`.
- A text whose type no plugin declares has a drawer of move, the missing
  editor's notice and delete, with no gear, whatever else is loaded.
- The *Text* buttons stand after the containers' in the toolbar.
- A feature plugin's toolbar item with an `iconClass` shows the icon alone,
  with its label as the title, wherever it is in the toolbar. It showed the
  icon and the label unless it had `align: 'end'`.

### Removed
- `$.fn.gridEditor.RTEs`, 5.x's registry of text editors: what is registered
  there is ignored, with a warning. `$.fn.gridEditor.texts` is no longer a
  documented contract: it is the shipped editors' own registry.


## [6.0.0-beta.3] - 2026-09-23
### Changed
- Which editor edits a text is shown first in its settings, as a read-only
  `Editor` field (`panel.editor`), where it was an info tool in its drawer.
  The `tool.text_info` locale key is gone. A text whose editor is not loaded
  still says so in its drawer.


## [6.0.0-beta.2] - 2026-09-23
### Added
- `settings_panel`: where a gear's settings open - a Bootstrap offcanvas at
  the side, the default; a popover under the gear; a modal; or `'inline'` in
  the drawer, as before. Each is titled after its node, the node is outlined
  while they are open, and Escape closes them. The editor opens and places
  them with Bootstrap's markup and css, and needs neither Popper nor
  Bootstrap's javascript for it. [example/utilities.html](example/utilities.html)
  switches between them.
- `ge.detailsOf(node)` in the plugin handle: a node's settings panel,
  wherever it is open.

### Changed
- The settings' fields are Bootstrap 5's own - labelled `form-control-sm`
  inputs and `form-select-sm` selects - and the preset class buttons
  `btn-outline-secondary`, where they carried Bootstrap 3's `btn-default`,
  which Bootstrap 5 has not got.

### Fixed
- While dragging, the copy that follows the pointer came out unstyled - its
  drawers laid out as plain links, their settings panels open - because it was
  appended to the page's body, outside the canvas the editor's styles apply
  to. It is appended to the list the drag started in, and looks like what it
  copies.
- With `drag_handle: 'drawer'` a text could not be dragged: its drawer is
  small and made of tools, and a drag never starts on a tool. It has a grip at
  its start now, and an info tool - the text's and an element's - which does
  nothing when clicked, starts a drag too.

## [6.0.0-beta.1] - 2026-09-23
### Changed
- **Elements are blocks of the column.** An element sits in a column beside
  its texts, rows and containers, never inside a content area's text, and
  drags within a column and between columns; the canvas and sections turn one
  away. Markup 5.x saved, with elements in the text, is converted as the editor
  starts: a content area is cut at each element among its own children, the
  text after going into a new content area of the same type. Rows and
  containers inside a content area come out the same way. `getPlainHtml`
  publishes what 5.x published, except that a content area's own id and classes
  stay on its first part. See [UPGRADING.md](UPGRADING.md).
- **The text editors are not in the main bundle**, nor in the bundle with
  SortableJS: load `dist/plugins/grideditor.<editor>.min.js`.
- `elements.auto: true` makes every loose node of a column an element.
- `createElement` into a content area puts the element beside it, and warns.
- A text dragged between columns is `kind: 'text'`, as when it is added or
  deleted; it was `kind: 'content'`.
- Elements carry no `contenteditable` and their drawers no `data-mce-bogus`.
- While a text is being edited its drawer stays faint, so as not to hide the
  end of the line being typed, until the pointer is on the drawer itself; with
  its settings open it stays whole.

### Added
- A feature plugin's `cuts`: the nodes that are never part of a text, which
  cut the content area they sit in.

### Removed
- The copies of the text editors in the main bundle, and the warning about
  using them.

## [5.3.1] - 2026-09-23
### Changed
- A text's drawer sits over the text's top right corner instead of in a bar
  above it, and takes no room: in 5.3.0 every text made its column about
  35px taller. It shows while the pointer is over the text, while the text is
  being edited or holds the focus, while its settings are open (across the
  text then, so the panel has room) and during a drag.

## [5.3.0] - 2026-09-23
### Added
- **Text as a block.** Each content area in a column is a text block while
  editing, with a drawer of its own: move, which editor edits it, settings,
  `text_tools`, delete. It drags between columns, the clipboard copies it
  (a new `text` category, pasted into columns), and spacing, text alignment
  and visibility apply to it. The drawer sits beside the content area rather
  than inside it, where the text editor would take it for text, in a wrapper
  `deinit` takes off: the markup is unchanged.
- An *Add text* tool in each column's drawer, which offers every editor when
  held, and a *Text* button in the toolbar per editor offered, to click or to
  drag into a column.
- `createText(type?, options?)`, and `before-add-text` / `after-add-text`,
  with the generic add pair. Adding and deleting a text is `kind: 'text'`;
  dragging one is still `kind: 'content'` until 6.0.
- `text_tools` and `text_classes` settings; `labelKey` for a text editor
  plugin; `ge.drawerOf(node)` in the handle.
- An empty column keeps room to drop a block into.

### Fixed
- A content area lost its id whenever its text editor closed: every
  integration took the id off, to remove the one an editor gives itself.
- With tinyMCE, an id, a class or an attribute given to a content area while
  its editor was open was lost when it closed: tinyMCE puts back the
  attributes it found. And CKEditor left `aria-readonly` behind in `getHtml`.
  The editor now plays the host's changes back onto the content area's own
  attributes, whichever editor it is, and leaves the editor's out.
- A drawer tool's title was spliced into markup, so a quote in it - a host
  tool's title, a type name - cut it short. It is set as text now.
- The content type a content area is edited with was read through jQuery's
  data cache, and a changed `data-ge-content-type` went unnoticed. It is read
  from the attribute.

## [5.2.0] - 2026-09-23
### Added
- **Text editors are plugins.** tinyMCE, CKEditor and summernote are
  `dist/plugins/grideditor.{tinymce,ckeditor,summernote}.js`, registered under
  `$.fn.gridEditor.texts`, with a contract a host can write its own editor to:
  `start`, `stop`, `initialContent`, `available`, and `ge.textReady` in the
  handle. They are chosen by `content_types`, not by the `plugins` setting.
  See [docs/plugins.md](docs/plugins.md#text).

### Deprecated
- The copies of the three text editors in the main bundle. A page that edits
  with one is told once to load the plugin; 6.0 leaves them out. See
  [UPGRADING.md](UPGRADING.md).
- `$.fn.gridEditor.RTEs`: an integration registered there still works,
  wrapped, with a warning. It goes in 7.0.

### Fixed
- Closing a content area edited with CKEditor destroyed every CKEditor
  instance on the page, other content areas' and the host's own included. It
  destroys its own now.
- CKEditor and summernote went on to start with their library missing, after
  saying it was; neither starts now, and a later click tries again.
- Summernote 0.9.1 could not open with jQuery 4, which removed the `$.now()`
  it calls. Its plugin gives it back, only where it is missing.

## [5.1.2] - 2026-09-23
### Added
- [example/attributes.html](example/attributes.html): a plugin of the page's
  own that puts a tool in the drawers, opens a modal, and saves what the user
  chose as a `data-animation` attribute that `getPlainHtml` keeps. It writes
  through tinyMCE's undo manager for an element inside text being edited, and
  [docs/plugins.md](docs/plugins.md) says why.

### Fixed
- Undo and redo in tinyMCE took an element's drawer away, until the editor
  was closed: they rewrite the content area from snapshots that leave the
  drawer out. It goes back in after each of them now.
- The toolbar's paste button lost the rounded corners on one side: it was in
  a btn-group with the other paste button, which is hidden but still counted
  by Bootstrap's rules. They are in a plain group now.

### Changed
- The toolbar's add row buttons and the container buttons beside them have a
  small gap between the two groups (`ms-1`).

## [5.1.1] - 2026-09-23
### Changed
- The clipboard plugin's toolbar paste button is an icon on the right of the
  toolbar, beside the source and preview buttons, rather than a labelled
  button beside the add ones.

### Added
- For plugins: a toolbar item with `align: 'end'` goes on the right of the
  toolbar, as its icon alone.

## [5.1.0] - 2026-09-23
### Added
- The `clipboard` plugin: copy and paste. Rows, columns, sections, containers
  and elements get a *Copy* tool; a *Paste* tool shows wherever what is copied
  can go, and the toolbar gets *Paste row* and *Paste section* buttons. What is
  copied is kept in `localStorage`, so it reaches other editors, reloads and
  other tabs. A pasted copy takes new ids where the page already has them, and
  its Bootstrap wiring follows. Pastes go through the add events with
  `source: 'paste'`; a copy fires `grideditor:after-copy`. See
  [example/clipboard.html](example/clipboard.html).
- For plugins: a feature plugin can declare `drawerTools`, a toolbar item can
  have an `iconClass` and a `source`, `ge.place` takes a `source`, and the
  handle has `ge.nodeHtml(node)` and `ge.toolbarItems(name)`.
- `getPlainHtml`: the markup `getHtml` returns without grid-editor's own
  marking, for a page that publishes it. The `ge-*` classes, the `column`
  class and the `data-ge-*` attributes are taken off, and a div left with
  nothing on it (each content area, a bare element or container wrapper) is
  replaced by its children. It is a one-way export; save `getHtml` to edit
  again.

### Fixed
- With `content_types: []`, content areas were given a
  `ge-content-type-undefined` class. They now name no type.
- The drawers of sections and of tabs and accordions had their add tool
  (*Add row*, *Add tab*, *Add item*) before the delete one, the other way
  round from rows and columns. They now have delete first, then add.

## [5.0.0] - 2026-09-23
### Changed
- **The all view writes one class.** A size or an offset set in the all view
  is written as the class with no breakpoint (`col-4`, `offset-2`), and the
  breakpoints' own sizes or offsets are taken off, as the utilities have done
  since 4.1. Up to 4.x it wrote all six breakpoints. The resize and indent
  payloads carry `cleared`, with what was taken off.
- Resize payloads' `from` and `to` can be `'equal'` or `'auto'`, `from` is
  `null` for a column its row sizes, and `source` can be `panel`.
- `valid_col_sizes` defaults to `[1, … 12, 'equal', 'auto']`.
- `ge.viewTiers()` returns the one breakpoint a view writes: `xs` in the all
  view.

See [UPGRADING.md](UPGRADING.md) for what these mean for a host.

### Added
- Equal and auto column sizes: Bootstrap's `col` and `col-auto`, at every
  breakpoint. The editor recognises them, previews them in each view, offers
  them in the add column picker and in `new_row_layouts`, `createRow` and
  `createColumn`, and the width tools turn them into a number starting from
  the width the column has. A column with only `col` used to be no column at
  all, and a `col-auto` one was given a `col-12` that overrode it.
- A *Width* field in each column's Responsive section, with every size.
- Columns per row: a *Columns per row* field in each row's panel writes
  `row-cols-{bp}-{1–6,auto}`. Columns with no size of their own share the
  line out and are left without a `col-12`; the preview, the budget and the
  tools settle row-cols against each column's own size as Bootstrap's css
  does, and the row says in a corner how many it puts per line. Layouts,
  `createRow` and `createColumn` take rows with row-cols, and `row_cols: false`
  takes the field away.
- The `sections` plugin: Bootstrap's `.container`, `.container-fluid` and
  `.container-{bp}` as sections on the canvas, grouping rows. Each gets a
  drawer with a width field, the toolbar a *Section* button, and the API
  `createSection`. Rows drag in and out of sections, sections drag along the
  canvas, and a breakpoint view gives each the max-width its container has
  there.
- Feature plugins can add blocks, regions and toolbar buttons (`blocks`,
  `regions`, `accepts`, `toolbar`), and the handle gains `rowFromLayout`.
- The `textalign` utility plugin: `text-{bp}-start`, `-center` and `-end` on
  rows, columns, elements and containers. With no class applying in a view, the
  preview shows what the node inherits from its parent or the host's css.
- The `float` utility plugin: `float-{bp}-start`, `-end` and `-none` on
  elements, with the text of the content area flowing round them.
- `ge.bareStyle(node, family, property)` on the plugin handle: a property's
  value with the family's classes out of the way, for previews whose "no
  class" is not a constant.
- `example/autocols.html`.

### Fixed
- The toolbar's layout icons nested each column inside the one before, since
  jQuery 4 stopped expanding `<div/>`: the icon of `[6, 6]` drew one block.
- A column's drawer no longer counts towards an auto column's width.

## [4.1.0] - 2026-09-22
### Added
- Utility plugins, a third kind beside containers and features:
  `$.fn.gridEditor.utilities`. A plugin declares families of Bootstrap's
  responsive utility classes and the editor reads them through the cascade,
  writes them per breakpoint (the all view writes the class with no infix and
  clears the rest), shows them in a folded *Responsive* section of the settings
  panel, and previews them in each breakpoint view. See docs/plugins.md.
- `getUtility` and `setUtility` methods, and the `utilities` setting for the
  plugins' options.
- `before-utility`, `after-utility` and `view-change` events. `view-change` is
  new for every host, not only for utility plugins.
- The plugin handle gains `kindOf`, `view`, `viewTiers`, `breakpoints`,
  `getUtility` and `setUtility`, and a utility plugin may add tools beside any
  drawer's gear and mark the canvas in `onRefresh`.
- The `visibility` utility plugin: hide a row, column, element or container at
  some breakpoints with `d-{bp}-none` and show it at others. An eye in each
  drawer toggles the view being edited with as few classes as that takes. A
  hidden node stays on the canvas, faded, and in the all view a badge says at
  which breakpoints it is hidden.
- The `order` utility plugin: a column's place in its row per breakpoint,
  with `order-{bp}-*`. Two arrows in each column drawer move it one place
  earlier or later in the view being edited without moving the markup, and
  take the classes off again once the row is back in the markup's order. A
  column ordered by class says so in a corner of the canvas.
- The `alignment` utility plugin: `justify-content-{bp}-*` and
  `align-items-{bp}-*` on rows, `align-self-{bp}-*` on columns, as fields in
  the settings panel.
- The `gutters` utility plugin: `g-{bp}-*`, `gx-{bp}-*` and `gy-{bp}-*` on
  rows. A row with gutter classes is drawn with its real gutters instead of the
  editor's frame, the preview settles `g` against `gx`/`gy` the way Bootstrap's
  css does, and choosing `g` takes that breakpoint's `gx` and `gy` off.
  `utilities.gutters.scale` follows a page that changed `$spacers`.
- The `spacing` utility plugin: padding and margin on rows, columns, elements
  and containers, `{p,m}{,x,y,t,b,s,e}-{bp}-*`. The panel has a padding and a
  margin group, each a side and a value, and the preview settles the fourteen
  families the way Bootstrap's css does. `utilities.spacing.values` and
  `.scale` narrow the steps and follow a changed `$spacers`.
- A utility plugin can build its own panel (`panel`, `ge.utilityField`,
  families with `panel: false`) and preview a node as a whole (`preview`).
- `example/utilities.html`.

## [4.0.0] - 2026-09-22
### Added
- Editing from a touchscreen. Sorting, resizing a column and carrying a toolbar
  button onto the canvas all work with a finger; a touch rests for
  `drag.touch_delay` before it moves anything, so the page can still be
  scrolled. `test/touch.js` holds every gesture to it, and none of them worked
  in 3.x.
- `dist/jquery.grideditor.bundle.min.js`: the editor with SortableJS inside it,
  for pages that would rather load one file than two. A page loads it or the
  pair, never both.
- A `drag` setting: `delay`, `touch_delay`, `threshold`, `animation` and
  `scroll`, named for the gesture rather than for the library underneath.
- A whole container can be dragged. Its move tool used to be a handle for a
  list that did not accept containers, so dragging one did nothing.

### Removed
- jQuery UI is no longer a dependency. A page loads jQuery, SortableJS and the
  editor, which comes to 81.6 kB gzip against 132.8 kB before: 51 kB less, 39%
  of the page.
- `sortable_options` and `resizable_options`. Both handed out the drag
  library's own options; `drag` and `resize` replace them, and passing either
  warns once naming the replacement.

### Changed
- Resizing a column and dragging a toolbar button onto the canvas are the
  editor's own pointer code, so jQuery UI is no longer used at all. A refused
  `before-resize` now stops the gesture before it starts instead of refusing
  every step of it, and the pointer is captured, so a fast drag that leaves the
  column behind keeps resizing it. `.ui-resizable-*` become `.ge-resize-*` and
  `.ge-resizing`; a palette button carries `.ge-palette-button`.
- Sorting is SortableJS instead of jQuery UI: rows, columns, blocks, tabs,
  accordion items and elements all move through it, with touch support that
  jQuery UI sortable never had. `.ui-sortable-helper` and
  `.ui-sortable-placeholder` become `.ge-drag-helper` and
  `.ge-drag-placeholder`.
- Every sortable list the editor makes, core's and a plugin's, goes through one
  internal seam that names the drag toolkit in one place. Groundwork for 4.0,
  which replaces jQuery UI with SortableJS.
- A plugin's `onSortable` hook receives that function instead of a jQuery UI
  options object, and describes a list rather than making one. See
  `UPGRADING.md`.
- Sortable groups are scoped to the editor instance, so two editors on one page
  no longer drag into each other.


## [3.2.1] - 2026-09-22
### Changed
- `example/containers.html` shows a card too: its own row, with two columns
  inside the card body, beside the tabs, the accordion and the popup.


## [3.2.0] - 2026-09-22
### Added
- A card container plugin, `dist/plugins/grideditor.card.js`: a bootstrap card
  whose body is an editable region, with an editable header and an optional
  footer. `header: false` leaves the title out, `footer` adds one.


## [3.1.0] - 2026-09-22
### Added
- A public API, dispatched from a table instead of a chain of string
  comparisons: `init`, `deinit`, `reset`, `destroy`, `changeView`, `getView`,
  `createRow`, `createColumn` and `createElement`, alongside the existing
  `getHtml`. `create*` methods return the node they made, everything else
  returns the jQuery set and chains.
- `create*` accepts `appendTo`, `prependTo`, `insertAfter` and `insertBefore`.
  Given one, grid-editor places the node and resets the canvas; given none,
  the node comes back detached for the host to place.
- `element.data('grideditor')` is documented API: the same methods, plus a
  frozen copy of `settings` and the `canvas`.
- `createContainer`, `addTab`, `addAccordionItem` and `setLocale` are
  registered but not implemented yet: calling one warns and returns `null`
  rather than doing nothing silently.
- Host callbacks. Every operation is announced twice: as a jQuery event on the
  canvas - the specific name, then the generic one - and as the matching
  `settings.callbacks` entry. `grideditor:before-add-row`,
  `-column`, `-element` and their `after-` counterparts, the generic
  `grideditor:before-add`/`after-add`, plus `before-delete`/`after-delete`,
  `before-move`/`after-move` and `before-resize`/`after-resize`. Every payload
  carries `kind`, `node`, `parent`, `canvas`, `breakpoint` and `source`
  (`tool`, `api` or `dragdrop`); a move adds `from`/`to` positions and a
  resize the sizes it moved between.
- Canceling. `preventDefault()` on either event, or `false` from the callback,
  cancels a `before-*`: nothing is inserted, deleted or resized, no `after-*`
  fires, and a canceled `create*` returns `null`. A canceled move is put back
  with jQuery UI's own `cancel`, since a drag cannot be refused once it has
  started.
- A re-entrancy queue: `init`, `reset` and the `create*` methods called from
  inside a handler run when the operation that called them has finished,
  instead of rebuilding the canvas underneath it.
- `callbacks`, `confirm_delete` and `sortable_options` settings.
- Translatable UI strings. Every user-visible string - the tool tooltips, the
  add-row buttons, the layout mode dropdown, the settings panel, the delete
  confirms and the "editor not available" console errors - now comes from
  `t(key)`, with `{name}` interpolation. Lookup order is `locale_strings`, the
  selected locale, English, then the key itself, which is shown and logged
  once rather than leaving an empty tooltip.
- `$.fn.gridEditor.locales`, with `locales.en` built into the main bundle as
  the fallback, and `$.fn.gridEditor.t(settings, key)` for editor
  integrations. `locale` and `locale_strings` settings, and a `setLocale`
  method that re-renders the controls.
- A Spanish locale, `dist/locales/grideditor.es.js`, built from
  `src/js/locales/grideditor.es.js`. It translates every key in `locales.en`,
  which `test/locales.js` asserts, so a change that adds a string adds its
  Spanish in the same commit.
- `docs/locale-keys.md`: the key catalogue, hand-written, with
  `test/locales.js` holding it to the source in both directions.
- `example/locale.html`: an example page with a language dropdown calling
  `setLocale`.
- The settings panel is on every drawer: a container, a tab, an accordion item
  and an element have the same id and class fields as a row and a column, with
  `container_classes`, `pane_classes` and `element_classes` for preset toggles.
- The settings panel on a row or a column has a css class field beside the id
  one, so classes can be set at all rather than only toggled from a list the
  host configured. It shows the host's own classes and leaves the grid's and
  the editor's alone, and an emptied id field takes the id away rather than
  leaving an empty one.
- The stylesheet is minified too: `dist/grideditor.min.css`, with a source map,
  built and watched alongside the readable one. The task existed and was never
  run.
- The build copies and minifies each `src/js/locales/*.js` into `dist/locales/`
  individually, with a watch target of its own. The main bundle is unchanged:
  the `src/js/*.js` glob does not descend.
- `ge-settings`, `ge-delete-row` and `ge-delete-column` classes on the tools
  that had none, so hosts and tests can find them without matching a tooltip
  that is now translated.
- All six Bootstrap 5 breakpoints, plus an `all` view that writes every one of
  them at once and is the new default. `changeView` takes a key (`xs`…`xxl` or
  `all`), `getView` returns it, `layout_modes` says which the dropdown offers
  and `default_view` says where the editor starts. The canvas gets
  `ge-layout-<key>`, which constrains it to that tier's width and makes that
  tier's classes the effective ones whatever the window is doing.
- Column offsets: two indent tools per column, `valid_col_offsets`, and
  `createColumn(size, { offset: n })`. Offsets are visualized in every layout
  mode, and shift-click takes a column to the row's edge or back to none.
- A sizing core that owns every size and offset class the editor reads or
  writes, with one 12 unit budget for all of them. Its getters answer for the
  tier they were asked about, and follow Bootstrap's own cascade downward when
  a tier says nothing, rather than returning the first value they happen to
  find.
- `before-indent` and `after-indent`, so the indent tools are not a silent
  operation. They carry the same payload as the resize pair, with `from` and
  `to` as offsets.
- `example/breakpoints.html`, showing the six tiers and the all view.
- Element level controls, the level below a column. A node inside a content
  area that the host marks with `data-ge-element` becomes an element: one
  movable, deletable thing rather than rich text. Each gets a drawer with
  move, an info tool named from `data-ge-element`/`data-ge-label`, the host's
  own `element_tools`, and delete. Elements sort within a content area and
  between content areas, and their events carry `kind: 'element'`.
- The `elements` setting: `selector` (default `[data-ge-element]`), `auto`
  (default `false`, treats every child of a content area as an element, for a
  page that configures no rich text editor) and `enabled` (default `'auto'`,
  which turns the feature on when the page has any elements).
- Elements inside a content area get `contenteditable="false"` while editing,
  so a rich text editor treats them as atomic rather than as text to rewrite.
  Checked against a real tinyMCE, not assumed.
- `example/elements.html`, including the pattern for an element with no visual
  output of its own: the host supplies a placeholder and its own tools, which
  is all spec 4.4 asks for.
- The element level controls are a plugin too,
  `dist/plugins/grideditor.elements.js`, registered under
  `$.fn.gridEditor.features`: a feature plugin hooks into the canvas rather
  than building a container type, and may contribute methods - `createElement`
  says so and returns null when the plugin is not loaded.
- Container plugins. Tabs, accordions and popups are not in the main bundle:
  each is a file under `dist/plugins/`, and loading it is what makes the type
  available, the way loading a locale file adds a language. A page takes the
  ones it offers, the toolbar shows a button per loaded plugin, and the
  `plugins` setting narrows that when a page loads more than it wants to show.
  A container in the markup whose plugin is not loaded is left alone and comes
  back out of `getHtml` untouched. `$.fn.gridEditor.containers` is the
  registry, and [docs/plugins.md](docs/plugins.md) is the contract: what a
  plugin returns, and the handle it gets to work through.
- Containers: tabs, accordions and popups. A container is marked with
  `data-ge-container` and holds panes, and every pane is an ordinary canvas
  region, so rows, columns, content areas and elements nest inside one exactly
  as they do at the top level. Containers nest too. Each gets a drawer with
  move, add pane, delete and the host's `container_tools`; panes get their own
  drawer, their own `tab_tools`/`accordion_tools`, and a label edited in place.
  The toolbar offers one button per type, listed in the `containers` setting.
- `createContainer(type, options)`, `addTab(container, options)` and
  `addAccordionItem(container, options)`, which complete the method table.
  Every container and pane operation goes through the add, delete and move
  events, with `kind` naming the type: `tabs`, `accordion`, `popup`, `tab`,
  `accordion-item`.
- Tabs: a sortable strip whose panes follow their tabs, so the output reads in
  tab order.
- Accordions: `stay_open`, items that open and close from their headers while
  editing - with the state kept in `data-ge-open`, so what is left open on the
  canvas is what the authored page opens with - and items that drag into any
  other accordion on the canvas, taking that accordion's `data-bs-parent` and
  its idea of whether several items may be open. The editor answers the click
  itself; Bootstrap's collapse is never asked to run over the canvas.
- Popups: a Bootstrap modal plus its trigger, rendered unfolded and static
  while editing so its body is an ordinary region - no backdrop, no focus trap,
  no `bootstrap.Modal` instantiated. The drawer folds it away. Any node the
  host marks with `data-ge-popup-target` is a trigger: grid-editor leaves the
  markup alone and writes Bootstrap's attributes onto it at `getHtml` time. A
  trigger whose popup is gone is re-pointed when exactly one popup is left in
  its column, and otherwise marked `.ge-popup-orphan` and reported through
  `grideditor:popup-orphan`. It is never deleted.
- `example/containers.html`, with all three types, a host trigger, two levels
  of nesting, and a button that opens the exported html as a real page with
  Bootstrap and no grid-editor.
- Resizing a column by dragging its edge. jQuery UI `resizable` on every
  column, east handle by default, with `resize.enabled`, `resize.handles`,
  `resize.balance` and `resizable_options` to steer it. The column follows the
  pointer in pixels, its drawer shows the class it would land on, and on drop
  the pixels are snapped to whole units with `round(width / rowWidth * 12)`,
  clamped by the same budget the tools obey. `resize.balance: 'next'` takes
  the units out of the following column so a full row stays full; `false`
  leaves the row to wrap. The resize handle is on the column's edge and the
  sort handle is the drawer, so the two gestures never share a pixel.
- A test runner, `test/run.js`, behind `npm test`. It shares one Chrome and one
  web server across every suite in `test/`, prints one summary and exits
  non-zero on any failure. `npm test -- rte` runs a single suite, and
  `node test/rte.js` still works on its own.
- Vendored test dependencies under `test/vendor` (jQuery, jQuery UI, Bootstrap
  and bootstrap-icons), and a base fixture page in `test/fixtures` that loads
  them, so the suite runs with the network switched off. The rich text editor
  suite keeps loading the editors from their CDNs and is skipped, with a
  reason, when there is no network.
- `npm run lint`, on a flat `eslint.config.js` for eslint 9, with eslint pinned
  in devDependencies. The old `.eslintrc` named `babel-eslint` and
  pre-flat-config rule names, so it had stopped running on a current eslint.

### Changed
- The example pages are named after what they show: the tinyMCE demo that was
  `example/index.html` is `example/basic.html`, and `example/index-autosave.html`
  is `example/autosave.html`. `example/index.html` is now an index of the demos,
  so opening `example/` lands on a list rather than on one of them.
- `$(el).gridEditor('remove')` returns the jQuery object instead of
  `undefined`, so it chains like the other methods.
- A method called on an element with no editor on it is a no-op that returns
  the set, instead of doing nothing in some cases and throwing in others.
  `getHtml` still reads the element's html.
- An unknown method name warns once and returns the set.
- The layout modes come from one table, which `changeView`, `getView`, the
  column classes and the mode dropdown all read. Clicking the dropdown now
  goes through `changeView`.

- Deleting asks in a Bootstrap modal rather than `window.confirm`: it is
  styled like the rest of the page, it is translated with everything else, and
  it does not block the page while it is up. The editor builds it outside the
  canvas, so it is never part of `getHtml`, and a page that loaded Bootstrap's
  css but not its javascript still gets asked by the browser.
- Deleting a row or a column goes through `before-delete`, then the question,
  then `after-delete` once the animation has finished. The host's handler runs
  first on purpose: a host that cancels to show its own dialog never wants the
  built-in confirm to have appeared already. `confirm_delete: false` skips the
  built-in one.
- `after-resize` fires once the column class has actually been written, rather
  than while jQuery UI is still animating the class swap.
- A drag that ends where it started is not reported as a move, and dragging a
  content area reports `kind: 'content'`.
- Adding a node brings the canvas up to date with `init()` rather than a full
  `deinit`/`init`, so inserting a row somewhere else no longer closes the rich
  text editor the user is typing in.

- The layout mode dropdown is built from the layout mode table rather than
  from a markup string, which also restores the closing tag the Tablet item
  had been missing.

- **BREAKING:** the layout mode classes on the canvas are `ge-layout-xs`
  through `ge-layout-xxl` and `ge-layout-all`, replacing `ge-layout-desktop`,
  `ge-layout-tablet` and `ge-layout-phone`.
- **BREAKING:** `addAllColClasses` is conservative now. A column that carries
  any size class is left exactly as authored, instead of being seeded with one
  class per tier; a column with no sizing at all gets a single `col-12`, which
  applies at every tier. With six tiers the old behaviour would have put six
  classes on every column.
- `toolbar_drag`, which makes the toolbar a palette: a button dragged onto the
  canvas creates its row or container where it is dropped, with a line showing
  where that will be and the canvas opening up so the gaps between rows are
  something a pointer can hit. A container dropped straight onto the canvas
  brings the row and column it needs; dropped into a column it goes straight
  in. `'auto'`, the default, follows `drag_handle: 'drawer'`.
- `drag_handle`, which says what a drag starts from: the move tool as before,
  or the whole tools drawer, in which case the move tool is not rendered at
  all. The other tools in a draggable drawer keep answering to a click, and a
  drag starting on one is not a move.
- Holding the add column tool offers the column widths instead of taking the
  default one: the sizes in `valid_col_sizes`, with the ones that no longer fit
  the row marked. It answers to a held finger as well as a hovering pointer,
  and the tooltip says so, because a gesture nobody can see is a gesture nobody
  finds. `add_column: { size, picker, delay }` configures it, and a click now
  adds a full width column rather than a three unit one.
- The add row tool in a column's drawer adds an empty row. It used to add one
  with two half width columns in it, which is a layout decision the tool has no
  business making: the new row's own drawer is where columns are added.
- The size a tool starts from is the one that applies at the tier being
  edited, following the cascade: in the all view that is the widest tier,
  which is what the unconstrained canvas is showing.
- Growing a column is refused when its indent leaves no room, rather than
  silently rewriting the indent. Growing an indent shrinks the column, because
  the indent is the thing the user just asked for.
- Size changes are written directly and animated by the stylesheet, rather
  than through jQuery UI's `switchClass`, so `after-resize` fires with the
  class already on the column.
- `getHtml` strips inline pixel widths, so a drag-resized column exports as
  its class and nothing else.
- The layout mode LESS is list-driven over the breakpoint table instead of
  taking exactly four tier arguments, and covers offsets as well as columns.
  A preview reproduces Bootstrap's cascade rather than showing one tier in
  isolation: a column carrying only `col-4` is four units wide in the `sm`
  preview too, and `col-sm-6` overrides it. The 2.x code switched every other
  tier off, which only worked because every column was seeded with a class per
  tier.

- A canceled `before-resize` on a drag is refused on every step of the drag
  rather than before it starts: jQuery UI's `resizable` ignores `false` from
  its start handler, unlike its `draggable`. Nothing is written either way.
- `getHtml` output no longer carries an empty `style` attribute where an
  inline width was stripped.

- Settings that are objects of grid-editor's own keys - `elements` and
  `resize` - are merged with their defaults rather than replaced, so naming
  one key no longer silently drops the others.
- The tinyMCE integration passes `promotion: false`, so the editor's "Upgrade"
  badge stays out of the menubar of an inline editor sitting in someone's
  page. A host that wants it passes `promotion: true` in its own config.
- The rich text editor integrations fire `ge-rte-ready` on the content area
  once their editor is up. An editor rewrites what is inside the content area
  as it takes over, which costs the element drawers in it; this is how they
  are put back.

- While editing, a Bootstrap toggle the editor needs to keep quiet is moved
  aside - `data-bs-toggle` becomes `data-ge-bs-toggle` - rather than fought
  with. Bootstrap binds its data-api handlers on the document in the capture
  phase, so a listener on the node itself cannot stop one.
- `wrapContent` no longer wraps the editor's own furniture. jQuery UI's resize
  handle was being treated as loose content and wrapped into a content area of
  its own on the next `init`, so a canvas collected an empty content area per
  column per reset. It also leaves containers alone, since a container sits in
  the column beside the content areas rather than inside one.
- The instance handle is in place before the first `init` runs, so a host
  handler that fires during initialization can already reach the editor.

- **BREAKING:** the `containers` setting is `plugins`, and it names the
  container plugins to use rather than a fixed list of built in types. It
  defaults to every plugin the page loaded.
- **BREAKING:** `row_classes` and `col_classes` default to `[]`. They used to
  default to a single `Example class` toggle, which shipped a placeholder into
  every host's interface.

### Deprecated
- `remove`, in favour of `destroy`, which does the same thing. `remove` still
  works and warns once per instance.

### Fixed
- Calling `deinit` twice threw from jQuery UI, which `reset` could reach now
  that both are public methods.

## [2.0.0] - 2026-09-21
### Changed
- **BREAKING:** Migrate from Bootstrap 4 to Bootstrap 5. The layout mode
  dropdown now uses `data-bs-toggle`, so consumers must load Bootstrap 5.
- **BREAKING:** Replace Font Awesome with Bootstrap Icons. Consumers must load
  bootstrap-icons instead of Font Awesome. The default `iconClass` for custom
  `row_tools` and `col_tools` is now `bi bi-wrench`.
  Based on work by [@vahidalvandi](https://github.com/vahidalvandi).
- **BREAKING:** The tinyMCE integration now targets tinyMCE 6 and no longer
  uses the `jquery.tinymce.js` plugin, which tinyMCE dropped after 5.x. Pages
  using `content_types: ['tinymce']` must load tinyMCE 6 and must stop loading
  the jQuery integration plugin. A `tinymce.config.oninit` callback still
  works, and `init_instance_callback` is now honoured too.

### Added
- Browser tests for the rich text editor integrations, run with `npm test`.
  They drive the example pages in a real Chrome over the DevTools protocol,
  against the real editors, since what breaks in these integrations only
  happens in a browser. They need node and Chrome, and nothing else: see
  BUILDING.md.
- `example/index-autosave.html`, demonstrating auto save to localStorage plus
  layout import/export, by [@Ka-Bar](https://github.com/Ka-Bar).
- Example dependencies updated to current releases: jQuery 4.0.0, jQuery UI
  1.14.2, Bootstrap 5.3.8, Bootstrap Icons 1.13.1, summernote 0.9.1,
  CKEditor 4.22.1 and TinyMCE 6.8.6. CKEditor stops short of the latest
  release on purpose: 4.22.1 is the last under the GPL/LGPL/MPL triple
  licence, as 4.23.0 and above are CKEditor 4 LTS under the commercial
  Extended Support Model. TinyMCE stops at 6.8.6 because it is the last MIT
  licensed release; 7.x is GPL-2.0-or-later and 8.x is commercial.

### Fixed
- Clear `ge-rte-active` after the rich text editor has been torn down rather
  than before. An inline editor may restore the class attribute it snapshotted
  when it was created, as tinyMCE does, which put the class straight back and
  made `initRTE` ignore every later click: no content area could be edited
  again after a single `getHtml()` or `remove()`. The tinyMCE integration now
  also cleans up after the late removal it can perform from
  `init_instance_callback`, which used to leave `active` behind and arm a
  pending-remove flag that destroyed the next editor created on that content
  area. This affected the pre-6 integration too.
- Guard the rich text editor lookup. A `content_types` entry with no matching
  registered editor, or an empty `content_types`, made `getHtml`, `remove`,
  add row and add column throw on an undefined editor. They now no-op for that
  content area, matching how `initRTE` already behaved.

## [1.0.8]
- Fix for moving rows in columns #117

## [1.0.3] - 2019-05-18
### Added
- Support for fontawesome 4 and 5

## [1.0.2] - 2019-05-18
### Added
- npm scripts for calling grunt

### Changed
- Migrate from glyphicon to fontawesome #98 by [@Nuranto](https://github.com/Nuranto)
- Wrap source_html content if neccessary. #101 by [@Nuranto](https://github.com/Nuranto)