Migration guide
0.6.x → 0.7.0: vue-forms 3.0
0.7.0 requires @dynamicforms/vue-forms 3.0.0, @dynamicforms/vuetify-inputs 0.14.1 and @dynamicforms/translatable 0.3.0. Upgrade the four together, and work through the vue-forms and vuetify-inputs migration guides first.
npm install @dynamicforms/vue-forms@^3.0.0-alpha.1 @dynamicforms/vuetify-inputs@^0.14.1 @dynamicforms/translatable@^0.3.0 \
@dynamicforms/vue-grid@^0.7.0Render content is imported from vuetify-inputs
vue-forms 3.0 exports no rendering. RenderableValue, SimpleComponentDef and MdString, which a renderer function, preRender, postRender and setCellRenderer() return, are imported from @dynamicforms/vuetify-inputs:
// before
import { RenderableValue, SimpleComponentDef } from '@dynamicforms/vue-forms';
// after
import { RenderableValue, SimpleComponentDef } from '@dynamicforms/vuetify-inputs';RenderableValue takes (content, classes?). It has no code, origin or sameAs(), and getTextType is kind.
A value is rendered as text
A cell value, and a string returned by transform, preRender or postRender, is rendered as text: markup in it is shown as written. link and file render a URL whose scheme is not allowed as text (by default http, https, mailto and tel are; see URL schemes), and file renders a link with target="_blank" in place of an inline onclick script. HTML is returned as an HtmlString; an element with props or handlers as a SimpleComponentDef:
// before
preRender: (_value, row) => `<span class="dot" style="background:${row.colour}"></span>`,
// after
preRender: (_value, row) =>
new RenderableValue({ componentName: 'span', componentProps: { class: 'dot', style: { background: row.colour } } }),A renderer passed to setCellRenderer() returns HTML as an HtmlString; SimpleComponentDef has no componentVHtml.
translateStrings takes the translation function
translateStrings takes the application's translation function, shaped like vue-i18n's t, and the namespace that holds the grid's keys. The grid passes the placeholder values, and the translation function substitutes them:
// before
translateStrings((key, defaultValue) => dictionary[key] ?? defaultValue);
// after, with vue-i18n
translateStrings(i18n.global.t, 'grid');
// after, with a plain dictionary
translateStrings((key, named) => interpolate(dictionary[key] ?? key, named));One translateStrings call with vue-i18n's t serves every locale: t follows a locale switch on its own.
translatableStrings is removed. translate(key, params?) returns the current text of a key:
// before
const text = interpolate(translatableStrings.ActiveFilters, { count });
// after
const text = translate('ActiveFilters', { count });See Translation.
0.5.x → 0.6.0: vue-forms 2.0
0.6.0 requires @dynamicforms/vue-forms 2.0.2 and @dynamicforms/vuetify-inputs 0.12.1. Upgrade the three together, and work through the vue-forms and vuetify-inputs migration guides first: they cover what changes in forms and inputs. This section covers what changes in the grid's filtering on top of them.
npm install @dynamicforms/vue-forms@^2.0.2 @dynamicforms/vuetify-inputs@^0.12.1 @dynamicforms/vue-grid@^0.6.0A filter field's access decides what it contributes
FilterState is a vue-forms Group, and in vue-forms 2.0.2 a member's access decides what it contributes to the group's value. A filter field that is 'disabled' is left out of filterValues, as a disabled field was before; one that is 'disabled-null' carries null whatever it holds. Code that disabled a filter field writes its access:
// before
filterState.fields.status.enabled = false;
// after
filterState.fields.status.access = 'disabled'; // left out of filterValues
filterState.fields.status.access = 'disabled-null'; // sent as status: nullNeither filters locally or counts in the status bar's active-filter count, and while every filterExternal column's field is 'disabled' the grid filters locally. A field's visibility changes none of this: it decides how the filter input is drawn and nothing else.
filter is emitted when the filter state becomes empty
This change is silent - code keeps compiling and behaves differently. A Group whose members are all left out reads {} in vue-forms 2.0, where it read null. The grid emits update:filterState and filter for that change as for any other, with filterValues: {}:
Object.values(filterState.fields).forEach((field) => (field.access = 'disabled'));
// before: no event, filterValues stayed what they were last sent
// after: @filter with filterValues {}The same happens when a filterState without filterable columns is passed in. An @filter handler that fetches filtered records now also runs with no filters at all and should fetch the unfiltered set; a handler that assumed at least one key in filterValues needs to accept an empty object.
0.4.x → 0.5.0: shared-grid row layout
Every row moved from being its own independent CSS grid to being a direct item of one shared grid. This changes what your row/card CSS has to target.
Move the grid declaration off .df-grid.card, onto .df-record-grid
Before, .df-grid.card was itself display: grid:
.my-grid .df-grid.card {
display: grid;
grid-template-columns: 3.5em 1fr 1fr 3em;
gap: 0.1em 0.5em;
}Now the grid lives on .df-record-grid — the marker every place that lays out a record's fields carries: the real scrolling body, the header, and the filter row alike. .df-grid.card is the row-anchor: a styleable but otherwise-empty box (zebra background, border, selection highlight), not a grid container.
.my-grid .df-record-grid {
display: grid;
grid-template-columns: 3.5em 1fr 1fr 3em;
gap: 0.1em 0.5em;
}.df-grid.body-grid still exists — it's the real scrolling body specifically, not a synonym for .df-record-grid — but a rule written only against it, for something that should look the same everywhere (the track template, gap, font-size), is exactly how the header and filter row end up silently out of step with the body: they sit outside the body's own scroller and can never be native items of it, so they need the identical declaration on their own, separate boxes. Reach for .df-grid.body-grid only when a rule is meant for the real scrolling body and nowhere else — a responsive layout's own per-record cell-placement rules are the main example, since the header and filter row use a different, offset placement scheme (see Card layout CSS for the df-anchored/df-unanchored detail).
If your layout is responsive, target .df-record-grid.<layoutCssClass> the same way you previously targeted .df-grid.card.<layoutCssClass>.
.df-grid.card is a sibling of its cells now, not their ancestor
If you had your own click handling that walked up from a clicked cell with something like event.target.closest('.df-grid.card') to work out which row was clicked — rather than using the grid's own click event or rowClass — it will now silently return null. A record's cells are children of a display: contents wrapper next to the row-anchor, not inside it; .closest() from a cell can only reach the row through that wrapper. Use .closest('[data-idx]') (or [data-pk]) instead — the wrapper carries both, same as the row-anchor does. See Row attributes.
The same no-longer-an-ancestor change means any padding you had on .df-grid.card to inset cell content is dead now, silently — .df-grid.card no longer wraps the cells, so padding on it doesn't reach them. Move it onto .df-grid.cell instead, or, if your layout gives every cell an explicit grid-column, opt .df-grid.card back into a real, in-flow box via --card-position — see Row-anchor position for what that buys you and what it requires.
Multi-row layouts need an explicit grid-row per field
Plain CSS auto-placement has no notion of "record boundaries": once every record's cells are items of the same shared grid, an unplaced cell's grid-row: auto keeps advancing across the whole grid rather than restarting for each record. .df-grid.cell defaults to grid-row: calc(var(--row-base) + 1), correct for a plain single row per record — nothing to add there. A layout that places more than one row of fields per record needs more, two things that used to be implicit now explicit:
Briefly stricter in 0.5.0–0.5.1
Those two versions required every cell, including a plain single row per record, to declare grid-row: calc(var(--row-base) + 1) itself — 0.5.2 made that the library's own default, closing the gap. See Card layout CSS.
Declare how many grid rows the layout's card occupies via
rowson itsResponsiveColumnDefinition(default1):tsconst columnsResponsive: ResponsiveColumnDefinitions = [ { cssClass: 'card', rows: 2, columns }, ];Give every cell its own explicit
grid-row, relative tocalc(var(--row-base) + N)rather than an absolute row number. With every record's cells sharing one grid,grid-row: 2would put every record's second row on the very same physical row instead of each record getting its own band:css/* before */ .df-grid.cell.title { grid-column: 1 / 3; grid-row: 1; } .df-grid.cell.id { grid-column: 1; grid-row: 2; } /* after */ .df-grid.cell.title { grid-column: 1 / 3; grid-row: calc(var(--row-base) + 1); } .df-grid.cell.id { grid-column: 1; grid-row: calc(var(--row-base) + 2); }
--row-base is published automatically per record — you read it, you don't set it. It advances by rows from one mounted record to the next; it is not the record's own index in your dataset (see Card layout CSS for why), so don't compute or compare it yourself. See the Cookbook for a full worked example.
mainShadowCount is gone
It configured the primary shadow grid, which no longer exists — column widths are resolved natively now. If you had set it, remove it; there's no replacement to configure for the same purpose.
Two new props take its place for a related but different concern — how many rows stay mounted for smooth scrolling and for native column auto-sizing to have a representative sample:
minRenderedRows(default100) — records kept mounted on each side of the visible range.estimatedRowHeight(default30) — assumed height for a not-yet-rendered record, sizing the placeholder standing in for windowed-out rows. Set this close to your actual row height; the grid does not average measured heights to refine it for you.
@pdanpdan/virtual-scroll is no longer a peer dependency
If you depended on it only because this package required it as a peer, you can remove it too — row windowing no longer uses it.
