diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50fcd7a..bd7e300 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [Unreleased]
+### Added
+
+- **Toolbar end slot** (Solid, React, Vue, vanilla): `toolbarEnd` (`JSXElement`/`ReactNode`) on `DataTableViewProps`/`DataTableProps`, Vue's `#toolbar-end` slot, and vanilla's `toolbarEnd` option + `setToolbarEnd(node)` render consumer controls (e.g. "Share view"/"Reset view") at the right end of the toolbar's action row, in a `.dt-toolbar-end` (Vue: `.dt__toolbar-end`) wrapper β instead of a separate row above the table, or DOM appended into the toolbar's internal markup.
+
### Changed
- **React**: `table.focus.consumeDomFocus` now keeps a stable identity across renders, so `DataTableView`'s scroll/focus effect lists it as a dependency (clears a `react-hooks/exhaustive-deps` lint warning).
diff --git a/demo/react/src/App.tsx b/demo/react/src/App.tsx
index 363b380..7996880 100644
--- a/demo/react/src/App.tsx
+++ b/demo/react/src/App.tsx
@@ -514,7 +514,8 @@ const VIEW_CONTROL_BTN_STYLE = {
// Shared by every section below: "Copy share link" copies the whole page URL (every section's
// state round-trips through its own query param, see VIEW_KEYS); "Reset" clears just this one
// table's own storageKey/paramName via resetView, back to its construction-time defaults.
-function ViewControls({ onReset }: { onReset: () => void }) {
+// `inToolbar` drops the bottom margin when rendered in the table's own toolbar (`toolbarEnd`).
+function ViewControls({ onReset, inToolbar }: { onReset: () => void; inToolbar?: boolean }) {
const [copied, setCopied] = useState(false)
function copyShareLink() {
navigator.clipboard.writeText(window.location.href)
@@ -522,7 +523,7 @@ function ViewControls({ onReset }: { onReset: () => void }) {
setTimeout(() => setCopied(false), 1500)
}
return (
-
+
@@ -643,10 +644,13 @@ function PersistedTable({ labels }: { labels?: Partial }) {
})
const { reset } = usePersistence(table, VIEW_KEYS.persisted)
return (
- <>
-
-
- >
+ }
+ />
)
}
diff --git a/demo/solid/src/App.tsx b/demo/solid/src/App.tsx
index debcfcb..c602091 100644
--- a/demo/solid/src/App.tsx
+++ b/demo/solid/src/App.tsx
@@ -519,7 +519,8 @@ const VIEW_CONTROL_BTN_STYLE = {
// Shared by every section below: "Copy share link" copies the whole page URL (every section's
// state round-trips through its own query param, see VIEW_KEYS); "Reset" clears just this one
// table's own storageKey/paramName via resetView, back to its construction-time defaults.
-function ViewControls(props: { onReset: () => void }) {
+// `inToolbar` drops the bottom margin when rendered in the table's own toolbar (`toolbarEnd`).
+function ViewControls(props: { onReset: () => void; inToolbar?: boolean }) {
const [copied, setCopied] = createSignal(false)
function copyShareLink() {
navigator.clipboard.writeText(window.location.href)
@@ -527,7 +528,7 @@ function ViewControls(props: { onReset: () => void }) {
setTimeout(() => setCopied(false), 1500)
}
return (
-
+
@@ -659,10 +660,11 @@ function PersistedTable(props: { labels?: Partial }) {
}))
const { reset } = usePersistence(table, VIEW_KEYS.persisted)
return (
- <>
-
-
- >
+ }
+ />
)
}
diff --git a/demo/vanilla/src/main.ts b/demo/vanilla/src/main.ts
index 1641678..c7c984d 100644
--- a/demo/vanilla/src/main.ts
+++ b/demo/vanilla/src/main.ts
@@ -489,15 +489,23 @@ const VIEW_BTN_STYLE =
// table's own container div (a table's render() rebuilds its container's innerHTML on every
// change, which would wipe these out) and is wired via one delegated click listener on `app`,
// dispatching on `data-view-copy`/`data-view-reset` β see the bottom of this file.
-function renderViewControls(key: string): string {
+function renderViewControls(key: string, inToolbar = false): string {
return `
-
+
`
}
+// The same pair as a node, for a table's own `toolbarEnd` β the delegated listener on `app`
+// still reaches it there, since the table's container is inside `app`.
+function viewControlsNode(key: string): Node {
+ const tpl = document.createElement('template')
+ tpl.innerHTML = renderViewControls(key, true).trim()
+ return tpl.content.firstChild!
+}
+
type ViewStateTable = {
getViewState(): TableViewState
setViewState(view: TableViewState): void
@@ -599,7 +607,6 @@ app.innerHTML = `
and open it in a new tab.
${docLink('view-persistence--sharing', 'π Docs')}
- ${renderViewControls('persisted')}
Dynamic data
@@ -768,6 +775,7 @@ const tablePersist = createDataTable(document.getElementById('table-pe
rowKey: 'id',
initialViewState: { visibleCols: PERSISTED_VISIBLE, pageSize: 5 },
labels: LOCALES[currentLocale],
+ toolbarEnd: viewControlsNode('persisted'),
})
wireViewPersistence(tablePersist, 'persisted')
diff --git a/demo/vue/src/App.vue b/demo/vue/src/App.vue
index 46f236e..dbd9087 100644
--- a/demo/vue/src/App.vue
+++ b/demo/vue/src/App.vue
@@ -1033,8 +1033,10 @@ function fmtSalary(n: number | null) {
useTableState instance you own instead β reorder or hide a column, then reload
the page.
-
+
+
+
diff --git a/demo/vue/src/ViewControls.vue b/demo/vue/src/ViewControls.vue
index d74dff5..ea8f902 100644
--- a/demo/vue/src/ViewControls.vue
+++ b/demo/vue/src/ViewControls.vue
@@ -4,6 +4,8 @@ import { ref } from 'vue'
// Shared by every table section in App.vue: "Copy share link" copies the whole page URL (every
// section's state round-trips through its own query param, see VIEW_KEYS in App.vue); "Reset"
// emits so the caller can clear just its own table's storageKey/paramName via resetView.
+// `inToolbar` drops the bottom margin when rendered in the table's own `#toolbar-end` slot.
+defineProps<{ inToolbar?: boolean }>()
const emit = defineEmits<{ reset: [] }>()
const copied = ref(false)
@@ -26,7 +28,7 @@ const btnStyle = {
-
+
diff --git a/docs/toolbar.md b/docs/toolbar.md
index 1d549e9..6923540 100644
--- a/docs/toolbar.md
+++ b/docs/toolbar.md
@@ -37,6 +37,14 @@ Together, showSearch + showColumns + the Sort/Filter fixes above fully close the
- **React/Solid**: `DataTable.tsx` must explicitly forward `showSearch` through to `` β it isn't covered by a prop spread, so a new `DataTableViewProps` field needs a matching line added at each `` wrapper's own call site (a gap this feature's own tests caught: forgetting this bullet left `showSearch` silently inert through `` while still working through `` directly).
- **Vanilla**: `showSearch` is backed by its own `createSignal` (`options.showSearch ?? true`) mirroring `selectable`, with a matching `setShowSearch(value: boolean)` on `DataTableInstance`.
+## Consumer toolbar content (`toolbarEnd`)
+
+Consumer controls (e.g. "Share view"/"Reset view") go at the end of the toolbar's action row through a public API rather than DOM appended into an internal toolbar class: `toolbarEnd` (`JSXElement` in Solid, `ReactNode` in React), Vue's `#toolbar-end` slot, vanilla's `toolbarEnd: Node | null` option + `setToolbarEnd`. Rendered last, after "Clear all", in a `dt-toolbar-end`/`dt__toolbar-end` wrapper with `margin-left: auto` β reset to `0` when "Clear all" (itself `margin-left: auto`) is shown, so the two sit together on the right instead of splitting the free space. Absent β no wrapper.
+
+- **Solid**: resolved once via `children()` β reading a JSX prop in both `` and the body would build it twice.
+- **Solid**: typed `JSXElement`, not `JSX.Element` β the latter fails `vite:dts`'s type rollup ("Unable to follow symbol for JSX"), only on `npm run build`, not `type-check`.
+- **React**: inline styles can't express the sibling rule, so the margin is picked from `hasActiveState`.
+
## Active-bar chip click actions
Previously a chip's body (the label/icon text) was inert β only its `Γ` did anything. Each chip's body is now a real, focusable `
diff --git a/packages/react/src/__tests__/DataTable.test.tsx b/packages/react/src/__tests__/DataTable.test.tsx
index 13f8a8f..d85d2e3 100644
--- a/packages/react/src/__tests__/DataTable.test.tsx
+++ b/packages/react/src/__tests__/DataTable.test.tsx
@@ -1178,6 +1178,35 @@ describe('DataTable β showSearch', () => {
})
})
+describe('DataTable β toolbarEnd', () => {
+ it('renders toolbarEnd as the last child of the toolbar actions row', () => {
+ const { container, getByText } = render(
+ Share} />,
+ )
+ const end = container.querySelector('.dt-toolbar-end')!
+ expect(end.contains(getByText('Share'))).toBe(true)
+ expect(end.parentElement!.lastElementChild).toBe(end)
+ expect(end.style.marginLeft).toBe('auto')
+ })
+
+ it('sits right after "Clear all" when that is shown', () => {
+ const { container, getAllByPlaceholderText } = render(
+ Share} />,
+ )
+ fireEvent.change(getAllByPlaceholderText('Searchβ¦')[0], { target: { value: 'ali' } })
+ const end = container.querySelector('.dt-toolbar-end')!
+ expect(end.previousElementSibling?.textContent).toContain('Clear all')
+ expect(end.style.marginLeft).toBe('0px')
+ })
+
+ it('renders no toolbar-end wrapper when toolbarEnd is absent or false', () => {
+ const { container, rerender } = render()
+ expect(container.querySelector('.dt-toolbar-end')).toBeNull()
+ rerender()
+ expect(container.querySelector('.dt-toolbar-end')).toBeNull()
+ })
+})
+
// Draggable dropdown rows (active sort/group/column entries) vs. the table's own draggable
//
headers are told apart by tag β this excludes the headers so container-wide queries only
// ever see the dropdown's own rows.
diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts
index 61a70f8..7de3956 100644
--- a/packages/react/src/types.ts
+++ b/packages/react/src/types.ts
@@ -61,7 +61,12 @@ export interface DataTableViewProps> extends Omit<
diff --git a/packages/solid/README.md b/packages/solid/README.md
index ad5a15a..7b89c33 100644
--- a/packages/solid/README.md
+++ b/packages/solid/README.md
@@ -78,7 +78,7 @@ const table = createTableState(
return
```
-`DataTableViewProps` only takes `table` (plus `rowKey`/`selectable`/`onRowClick`/`showSearch`/`showColumns`) β no separate `data`/`columns` props, since `table.data()`/`table.columns.list()` already are that value. `showSearch` (default `true`) shows/hides the toolbar's search box β the Sort/Group/Filter dropdowns already auto-hide themselves when no column qualifies for them, so search is the one toolbar control that needs an explicit opt-out (e.g. when the page already has its own search input). `showColumns` (default `true`) is the same kind of opt-out for the Columns button, which also auto-hides on its own once `columns.length < 2`.
+`DataTableViewProps` only takes `table` (plus `rowKey`/`selectable`/`onRowClick`/`showSearch`/`showColumns`/`toolbarEnd`) β no separate `data`/`columns` props, since `table.data()`/`table.columns.list()` already are that value. `showSearch` (default `true`) shows/hides the toolbar's search box β the Sort/Group/Filter dropdowns already auto-hide themselves when no column qualifies for them, so search is the one toolbar control that needs an explicit opt-out (e.g. when the page already has its own search input). `showColumns` (default `true`) is the same kind of opt-out for the Columns button, which also auto-hides on its own once `columns.length < 2`. `toolbarEnd` (`JSXElement`) renders your own controls at the right end of the toolbar's action row, in a `.dt-toolbar-end` wrapper β e.g. "Share view"/"Reset view" buttons; no wrapper is rendered without it.
βΆ [Try it in the demo](https://vatesfr.github.io/data-table/solid/#custom-layout)
diff --git a/packages/solid/src/DataTable.tsx b/packages/solid/src/DataTable.tsx
index 1cefa43..8f09532 100644
--- a/packages/solid/src/DataTable.tsx
+++ b/packages/solid/src/DataTable.tsx
@@ -74,6 +74,7 @@ export function DataTable(props: DataTableProps) {
onRowClick={props.onRowClick}
showSearch={props.showSearch}
showColumns={props.showColumns}
+ toolbarEnd={props.toolbarEnd}
/>
)
}
diff --git a/packages/solid/src/DataTableView.tsx b/packages/solid/src/DataTableView.tsx
index 0886ff7..c467ec2 100644
--- a/packages/solid/src/DataTableView.tsx
+++ b/packages/solid/src/DataTableView.tsx
@@ -1,4 +1,4 @@
-import { Show, createSignal } from 'solid-js'
+import { Show, children, createSignal, type JSXElement } from 'solid-js'
import type { TableState } from './createTableState'
import { injectStyles } from './styles'
import { SearchBox } from './components/SearchBox'
@@ -39,6 +39,13 @@ export interface DataTableViewProps {
* mirrors `showSearch`'s reasoning rather than adding a `showable`-style column flag.
*/
showColumns?: boolean
+ /**
+ * Consumer content rendered at the end of the toolbar's action row (after "Clear all"), inside
+ * a right-aligned `.dt-toolbar-end` wrapper β e.g. "Share view"/"Reset view" buttons. Omitted:
+ * no wrapper is rendered at all.
+ */
+ // `JSXElement`, not `JSX.Element`: the d.ts rollup (vite:dts) can't follow solid-js's `JSX` namespace.
+ toolbarEnd?: JSXElement
}
type DropdownId = 'cols' | 'sort' | 'group' | 'filter'
@@ -55,6 +62,8 @@ export function DataTableView(props: DataTableViewProps table.columns.list().filter((c) => c.groupable === true)
const sortableCols = () => table.columns.list().filter((c) => c.sortable !== false)
const filterableCols = () => table.columns.list().filter((c) => c.filterable !== false)
+ // Resolved once: reading a JSX prop twice (the `when` and the body) would build it twice.
+ const toolbarEnd = children(() => props.toolbarEnd)
function toggleDd(id: DropdownId): void {
setOpenDropdown((cur) => (cur === id ? null : id))
@@ -119,6 +128,9 @@ export function DataTableView(props: DataTableViewProps
+ 0}>
+
{toolbarEnd()}
+ {
expect(container.querySelector('.dt-search-input')).toBeNull()
dispose()
})
+
+ it('renders toolbarEnd once, as the last child of the toolbar actions row', () => {
+ const container = document.createElement('div')
+ document.body.appendChild(container)
+ let built = 0
+ const shareButton = () => {
+ built++
+ return Share
+ }
+ const dispose = createRoot((d) => {
+ render(
+ () => ,
+ container,
+ )
+ return d
+ })
+ const end = container.querySelector('.dt-toolbar-actions > .dt-toolbar-end:last-child')
+ expect(end?.querySelectorAll('.share')).toHaveLength(1)
+ expect(built).toBe(1)
+ dispose()
+ })
+
+ it('renders no toolbar-end wrapper when toolbarEnd is absent, and follows a reactive one', () => {
+ const container = document.createElement('div')
+ document.body.appendChild(container)
+ const [show, setShow] = createSignal(false)
+ const dispose = createRoot((d) => {
+ render(
+ () => (
+ Share : undefined}
+ />
+ ),
+ container,
+ )
+ return d
+ })
+ expect(container.querySelector('.dt-toolbar-end')).toBeNull()
+ setShow(true)
+ expect(container.querySelector('.dt-toolbar-end .share')).not.toBeNull()
+ setShow(false)
+ expect(container.querySelector('.dt-toolbar-end')).toBeNull()
+ dispose()
+ })
})
diff --git a/packages/solid/src/styles.ts b/packages/solid/src/styles.ts
index fc67ecf..59ca1a3 100644
--- a/packages/solid/src/styles.ts
+++ b/packages/solid/src/styles.ts
@@ -7,6 +7,8 @@ ${renderThemeCss()}
.dt-toolbar-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.dt-toolbar-divider{width:1px;height:22px;background:var(--color-border-secondary,#dddcd8);flex-shrink:0;margin:0 2px}
.dt-clear-all{margin-left:auto}
+.dt-toolbar-end{display:flex;align-items:center;gap:8px;margin-left:auto}
+.dt-clear-all~.dt-toolbar-end{margin-left:0}
.dt-stats{margin-left:auto;font-size:12px;color:var(--color-text-secondary,#6b6a66);white-space:nowrap}
.dt-btn{display:inline-flex;align-items:center;gap:4px;padding:5px 10px;background:none;border:0.5px solid var(--color-border-secondary,#dddcd8);border-radius:6px;font-size:13px;cursor:pointer;color:var(--color-text-primary,#1a1916);font-family:inherit;line-height:1}
.dt-btn--active{background:var(--color-background-secondary,#f7f6f3)}
diff --git a/packages/vanilla/README.md b/packages/vanilla/README.md
index dcd1280..5085ec4 100644
--- a/packages/vanilla/README.md
+++ b/packages/vanilla/README.md
@@ -367,20 +367,21 @@ Drag a column header to reorder it, or drag a row (or press Alt+ArrowUp/Alt+Arro
## Options
-| Option | Type | Default | Description |
-| ------------------------ | --------------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------- |
-| `data` | `TRow[]` | β | Row data |
-| `columns` | `ColumnDef[]` | β | Column definitions |
-| `rowKey` | `keyof TRow & string` | β | DOM key only β not selection identity |
-| `labels` | `Partial` | English | UI string overrides |
-| `defaultGroupsCollapsed` | `boolean` | `true` | Whether newly-grouped groups start collapsed |
-| `initialViewState` | `TableViewState` | `{}` | Construction-time defaults for columns/sort/filters/grouping/page/search β also what `resetView` restores |
-| `getRowId` | `(row: TRow) => string \| number` | β | Opt-in id-based selection identity (see "Row selection" above) |
-| `selectable` | `boolean` | `false` | Show checkbox column for row selection |
-| `onSelectionChange` | `(rows: TRow[]) => void` | β | Called when selection changes |
-| `onRowClick` | `(row: TRow, event: MouseEvent \| KeyboardEvent) => void` | β | Called when a data row is clicked |
-| `showSearch` | `boolean` | `true` | Shows/hides the toolbar's search box (Sort/Group/Filter already auto-hide when no column qualifies) |
-| `showColumns` | `boolean` | `true` | Shows/hides the Columns toolbar button; also auto-hides when `columns.length < 2` |
+| Option | Type | Default | Description |
+| ------------------------ | --------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `data` | `TRow[]` | β | Row data |
+| `columns` | `ColumnDef[]` | β | Column definitions |
+| `rowKey` | `keyof TRow & string` | β | DOM key only β not selection identity |
+| `labels` | `Partial` | English | UI string overrides |
+| `defaultGroupsCollapsed` | `boolean` | `true` | Whether newly-grouped groups start collapsed |
+| `initialViewState` | `TableViewState` | `{}` | Construction-time defaults for columns/sort/filters/grouping/page/search β also what `resetView` restores |
+| `getRowId` | `(row: TRow) => string \| number` | β | Opt-in id-based selection identity (see "Row selection" above) |
+| `selectable` | `boolean` | `false` | Show checkbox column for row selection |
+| `onSelectionChange` | `(rows: TRow[]) => void` | β | Called when selection changes |
+| `onRowClick` | `(row: TRow, event: MouseEvent \| KeyboardEvent) => void` | β | Called when a data row is clicked |
+| `showSearch` | `boolean` | `true` | Shows/hides the toolbar's search box (Sort/Group/Filter already auto-hide when no column qualifies) |
+| `showColumns` | `boolean` | `true` | Shows/hides the Columns toolbar button; also auto-hides when `columns.length < 2` |
+| `toolbarEnd` | `Node \| null` | β | Content at the right end of the toolbar's action row, in a `.dt-toolbar-end` wrapper (e.g. "Share view"/"Reset view" buttons); no wrapper when omitted |
## Column definition
@@ -424,6 +425,7 @@ interface ColumnDef {
| `setSelectable(value: boolean)` | Toggles whether rows show selection checkboxes after construction |
| `setShowSearch(value: boolean)` | Shows/hides the toolbar's search box after construction |
| `setShowColumns(value: boolean)` | Shows/hides the Columns toolbar button after construction |
+| `setToolbarEnd(node: Node \| null)` | Replaces (or removes, with `null`) the toolbar-end node after construction |
| `setOnRowClick(cb \| undefined)` | Changes (or clears) the row-click callback after construction |
| `setLabels(labels \| undefined)` | Replaces the label overrides after construction |
| `setDefaultGroupsCollapsed(value: boolean)` | Changes whether newly-grouped groups start collapsed after construction |
diff --git a/packages/vanilla/src/__tests__/createDataTable.toolbar.test.tsx b/packages/vanilla/src/__tests__/createDataTable.toolbar.test.tsx
index 3262ca0..2c82e68 100644
--- a/packages/vanilla/src/__tests__/createDataTable.toolbar.test.tsx
+++ b/packages/vanilla/src/__tests__/createDataTable.toolbar.test.tsx
@@ -511,6 +511,31 @@ describe('createDataTable', () => {
instance.setShowSearch(true)
expect(container.querySelector('.dt-search-input')).not.toBeNull()
})
+
+ it('renders no toolbar-end wrapper by default', () => {
+ createDataTable(container, { data: ROWS, columns: COLS })
+ expect(container.querySelector('.dt-toolbar-end')).toBeNull()
+ })
+
+ it('renders toolbarEnd as the last child of the toolbar actions row', () => {
+ const share = document.createElement('button')
+ createDataTable(container, { data: ROWS, columns: COLS, toolbarEnd: share })
+ const end = container.querySelector('.dt-toolbar-actions > .dt-toolbar-end:last-child')
+ expect(end?.firstChild).toBe(share)
+ })
+
+ it('setToolbarEnd replaces and removes the toolbar-end node after construction', () => {
+ const instance = createDataTable(container, { data: ROWS, columns: COLS })
+ const share = document.createElement('button')
+ instance.setToolbarEnd(share)
+ expect(container.querySelector('.dt-toolbar-end')?.firstChild).toBe(share)
+ const reset = document.createElement('button')
+ instance.setToolbarEnd(reset)
+ expect(container.querySelector('.dt-toolbar-end')?.firstChild).toBe(reset)
+ expect(share.isConnected).toBe(false)
+ instance.setToolbarEnd(null)
+ expect(container.querySelector('.dt-toolbar-end')).toBeNull()
+ })
})
// PRUNED:
diff --git a/packages/vanilla/src/index.tsx b/packages/vanilla/src/index.tsx
index 9f51f66..14dc487 100644
--- a/packages/vanilla/src/index.tsx
+++ b/packages/vanilla/src/index.tsx
@@ -89,6 +89,7 @@ export function createDataTable(
const [selectable, setSelectableSignal] = createSignal(options.selectable ?? false)
const [showSearch, setShowSearchSignal] = createSignal(options.showSearch ?? true)
const [showColumns, setShowColumnsSignal] = createSignal(options.showColumns ?? true)
+ const [toolbarEnd, setToolbarEndSignal] = createSignal(options.toolbarEnd ?? null)
// `onRowClick`/`getRowId` are themselves functions, so Solid's setter overloads can't tell them
// apart from a functional updater β wrap each write in a thunk, the same workaround
// `createTableState`'s own `selectionAnchor` signal already uses for the same reason.
@@ -149,6 +150,7 @@ export function createDataTable(
onRowClick={onRowClick()}
showSearch={showSearch()}
showColumns={showColumns()}
+ toolbarEnd={toolbarEnd()}
/>
),
container,
@@ -185,6 +187,7 @@ export function createDataTable(
setSelectable: (value: boolean) => setSelectableSignal(value),
setShowSearch: (value: boolean) => setShowSearchSignal(value),
setShowColumns: (value: boolean) => setShowColumnsSignal(value),
+ setToolbarEnd: (node: Node | null) => setToolbarEndSignal(() => node),
setOnRowClick: (cb: ((row: TRow, event: MouseEvent | KeyboardEvent) => void) | undefined) =>
setOnRowClickSignal(() => cb),
setLabels: (next: DataTableOptions['labels']) => setLabelsSignal(next),
diff --git a/packages/vanilla/src/types.ts b/packages/vanilla/src/types.ts
index b277141..59b1282 100644
--- a/packages/vanilla/src/types.ts
+++ b/packages/vanilla/src/types.ts
@@ -72,6 +72,12 @@ export interface DataTableOptions>
* mirrors `showSearch`'s reasoning rather than adding a `showable`-style column flag.
*/
showColumns?: boolean
+ /**
+ * A node rendered at the end of the toolbar's action row (after "Clear all"), inside a
+ * right-aligned `.dt-toolbar-end` wrapper β e.g. "Share view"/"Reset view" buttons. Omitted or
+ * `null`: no wrapper is rendered at all.
+ */
+ toolbarEnd?: Node | null
}
export interface DataTableInstance> {
@@ -131,6 +137,8 @@ export interface DataTableInstance
setShowSearch(value: boolean): void
/** Shows/hides the Columns toolbar button after construction β see `DataTableOptions.showColumns`. */
setShowColumns(value: boolean): void
+ /** Replaces (or removes, passing `null`) the toolbar-end node β see `DataTableOptions.toolbarEnd`. */
+ setToolbarEnd(node: Node | null): void
/** Changes (or clears, passing `undefined`) the row-click callback after construction. */
setOnRowClick(cb: ((row: TRow, event: MouseEvent | KeyboardEvent) => void) | undefined): void
/** Replaces the label overrides after construction β see `DataTableOptions.labels`. */
diff --git a/packages/vue/README.md b/packages/vue/README.md
index 945ff43..f65a9f3 100644
--- a/packages/vue/README.md
+++ b/packages/vue/README.md
@@ -78,6 +78,8 @@ Use named scoped slots to customize how cells, filter labels, and group headers
Slot naming: `#cell-{key}`, `#filter-{key}`, `#group-{key}` where `{key}` matches the column's `key`.
+`#toolbar-end` (no scope) renders your own controls at the right end of the toolbar's action row, in a `.dt__toolbar-end` wrapper β e.g. "Share view"/"Reset view" buttons. No wrapper is rendered without it.
+
`#group-{key}` applies to group header rows when that column is used for grouping, so values display with the same visual as table cells.
## Multi-value (array) columns
@@ -549,7 +551,7 @@ usePersistence(table, { storageKey: 'employee-table-view', paramName: 'view' })
```
-`DataTableView` takes the same props as `` minus `labels`/`defaultGroupsCollapsed`/`initialViewState` (those only make sense at `useTableState` construction time) plus `table`, and supports the same `#cell-{key}`/`#filter-{key}`/`#group-{key}` scoped slots. In fact, `` is implemented as exactly this β a thin wrapper that calls `useTableState` and renders ``, forwarding its own slots straight through.
+`DataTableView` takes the same props as `` minus `labels`/`defaultGroupsCollapsed`/`initialViewState` (those only make sense at `useTableState` construction time) plus `table`, and supports the same `#cell-{key}`/`#filter-{key}`/`#group-{key}`/`#toolbar-end` slots. In fact, `` is implemented as exactly this β a thin wrapper that calls `useTableState` and renders ``, forwarding its own slots straight through.
## i18n
diff --git a/packages/vue/src/DataTableView.vue b/packages/vue/src/DataTableView.vue
index bb33c43..0e439be 100644
--- a/packages/vue/src/DataTableView.vue
+++ b/packages/vue/src/DataTableView.vue
@@ -2377,6 +2377,9 @@ async function onFilterDropdownKeydown(event: KeyboardEvent): Promise {
{{ L.clearAll }}
+