diff --git a/adminforth/documentation/docs/tutorial/03-Customization/08-pageInjections.md b/adminforth/documentation/docs/tutorial/03-Customization/08-pageInjections.md
index 8e329daf..45e7a6a8 100644
--- a/adminforth/documentation/docs/tutorial/03-Customization/08-pageInjections.md
+++ b/adminforth/documentation/docs/tutorial/03-Customization/08-pageInjections.md
@@ -396,6 +396,79 @@ cd custom
npm i @iconify-prerendered/vue-mdi
```
+## List table row replace injection
+
+`tableRowReplace` lets you fully control how each list table row is rendered. Instead of the default table `
…
` markup, AdminForth will mount your Vue component per record and use its returned DOM to display the row. Use this when you need custom row layouts, extra controls, or conditional styling that goes beyond column-level customization.
+
+Supported forms:
+- Single component: `pageInjections.list.tableRowReplace = '@@/MyRowRenderer.vue'`
+- Object form with meta: `pageInjections.list.tableRowReplace = { file: '@@/MyRowRenderer.vue', meta: { /* optional */ } }`
+- If an array is provided, the first element is used.
+
+Example configuration:
+
+```ts title="/resources/apartments.ts"
+{
+ resourceId: 'aparts',
+ ...
+ options: {
+ pageInjections: {
+ list: {
+ tableRowReplace: {
+ file: '@@/ApartRowRenderer.vue',
+ meta: {
+ // You can pass any meta your component may read
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+Minimal component example (decorate default row with a border):
+
+```vue title="/custom/ApartRowRenderer.vue"
+
+
+
+
+
+
+
+
+```
+
+Component contract:
+- Inputs
+ - `record`: the current record object
+ - `resource`: the resource config object
+ - `meta`: the meta object passed in the injection config
+- Slots
+ - Default slot: the table’s standard row content (cells) will be projected here. Your component can wrap or style it.
+- Output
+ - Render a full `…
` fragment. For example, to replace the standard set of cells with a single full‑width cell, render:
+
+```vue
+
+ |
+
+ |
+
+```
+
+Notes and tips:
+- Requirements:
+ - Required `
` structure around ``
+
## List table beforeActionButtons
`beforeActionButtons` allows injecting one or more compact components into the header bar of the list page, directly to the left of the default action buttons (`Create`, `Filter`, bulk actions, three‑dots menu). Use it for small inputs (quick search, toggle, status chip) rather than large panels.
diff --git a/adminforth/modules/configValidator.ts b/adminforth/modules/configValidator.ts
index 3ff78e4a..d525da8a 100644
--- a/adminforth/modules/configValidator.ts
+++ b/adminforth/modules/configValidator.ts
@@ -45,6 +45,11 @@ export default class ConfigValidator implements IConfigValidator {
}
validateAndListifyInjection(obj, key, errors) {
+ if (key.includes('tableRowReplace')) {
+ if (obj[key].length > 1) {
+ throw new Error(`tableRowReplace injection supports only one element, but received ${obj[key].length}.`);
+ }
+ }
if (!Array.isArray(obj[key])) {
// not array
obj[key] = [obj[key]];
@@ -869,7 +874,7 @@ export default class ConfigValidator implements IConfigValidator {
// Validate page-specific allowed injection keys
const possiblePages = ['list', 'show', 'create', 'edit'];
const allowedInjectionsByPage: Record = {
- list: ['beforeBreadcrumbs', 'afterBreadcrumbs', 'beforeActionButtons', 'bottom', 'threeDotsDropdownItems', 'customActionIcons', 'tableBodyStart'],
+ list: ['beforeBreadcrumbs', 'afterBreadcrumbs', 'beforeActionButtons', 'bottom', 'threeDotsDropdownItems', 'customActionIcons', 'tableBodyStart', 'tableRowReplace'],
show: ['beforeBreadcrumbs', 'afterBreadcrumbs', 'bottom', 'threeDotsDropdownItems'],
edit: ['beforeBreadcrumbs', 'afterBreadcrumbs', 'bottom', 'threeDotsDropdownItems', 'saveButton'],
create: ['beforeBreadcrumbs', 'afterBreadcrumbs', 'bottom', 'threeDotsDropdownItems', 'saveButton'],
diff --git a/adminforth/spa/src/components/ResourceListTable.vue b/adminforth/spa/src/components/ResourceListTable.vue
index aaef9cce..d940068b 100644
--- a/adminforth/spa/src/components/ResourceListTable.vue
+++ b/adminforth/spa/src/components/ResourceListTable.vue
@@ -83,13 +83,20 @@
-
+
| e.stopPropagation()">
|
-
+
@@ -328,7 +335,7 @@ import {
} from '@iconify-prerendered/vue-flowbite';
import router from '@/router';
import { Tooltip } from '@/afcl';
-import type { AdminForthResourceCommon, AdminForthResourceColumnInputCommon, AdminForthResourceColumnCommon } from '@/types/Common';
+import type { AdminForthResourceCommon, AdminForthResourceColumnInputCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclaration } from '@/types/Common';
import adminforth from '@/adminforth';
import Checkbox from '@/afcl/Checkbox.vue';
@@ -345,6 +352,7 @@ const props = defineProps<{
noRoundings?: boolean,
customActionsInjection?: any[],
tableBodyStartInjection?: any[],
+ tableRowReplaceInjection?: AdminForthComponentDeclaration,
}>();
// emits, update page
diff --git a/adminforth/spa/src/components/ResourceListTableVirtual.vue b/adminforth/spa/src/components/ResourceListTableVirtual.vue
index 4e5a900c..900ecba9 100644
--- a/adminforth/spa/src/components/ResourceListTableVirtual.vue
+++ b/adminforth/spa/src/components/ResourceListTableVirtual.vue
@@ -93,14 +93,21 @@
- updateRowHeight(`row_${row._primaryKeyValue}`, el.offsetHeight)"
- >
+ updateRowHeight(`row_${row._primaryKeyValue}`, el.offsetHeight)"
+ >
| e.stopPropagation()">
|
-
+
@@ -350,7 +357,7 @@ import {
} from '@iconify-prerendered/vue-flowbite';
import router from '@/router';
import { Tooltip } from '@/afcl';
-import type { AdminForthResourceCommon, AdminForthResourceColumnCommon } from '@/types/Common';
+import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclaration } from '@/types/Common';
import adminforth from '@/adminforth';
import Checkbox from '@/afcl/Checkbox.vue';
@@ -370,6 +377,7 @@ const props = defineProps<{
containerHeight?: number,
itemHeight?: number,
bufferSize?: number,
+ tableRowReplaceInjection?: AdminForthComponentDeclaration
}>();
// emits, update page
diff --git a/adminforth/spa/src/views/ListView.vue b/adminforth/spa/src/views/ListView.vue
index 22f39a3a..b863520e 100644
--- a/adminforth/spa/src/views/ListView.vue
+++ b/adminforth/spa/src/views/ListView.vue
@@ -143,6 +143,9 @@
? [coreStore.resourceOptions.pageInjections.list.tableBodyStart]
: []
"
+ :tableRowReplaceInjection="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace)
+ ? coreStore.resourceOptions.pageInjections.list.tableRowReplace[0]
+ : coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace || undefined"
:container-height="1100"
:item-height="52.5"
:buffer-size="listBufferSize"
@@ -173,6 +176,9 @@
? [coreStore.resourceOptions.pageInjections.list.tableBodyStart]
: []
"
+ :tableRowReplaceInjection="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace)
+ ? coreStore.resourceOptions.pageInjections.list.tableRowReplace[0]
+ : coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace || undefined"
/>
,
customActionIcons?: AdminForthComponentDeclaration | Array,
tableBodyStart?: AdminForthComponentDeclaration | Array,
+ tableRowReplace?: AdminForthComponentDeclaration | Array,
},
/**