Thanks Sonja,
That’s probably the case I’ve tried some things like basing the custom plugin on another component like section and re-define css classes in the vue styling block but as soon as I use k-table the display: grid property gets inherited and the table visually vanishes in the panel while still existing. I’ve managed to remove the k-table object and replace it with vanilla html table with kirby-look-alike css. For me this is currently acceptable.
To be complete my full vue.js file below.
Thanks for your support Sonja.
<template>
<k-field :label="label" :help="help" :disabled="disabled" :required="required">
<div v-if="fields.length > 0" class="field-selector-simple">
<div class="kirby-native-table">
<table>
<thead>
<tr>
<th></th>
<th data-align="left">Field</th>
<th data-align="left">Type</th>
<th data-align="left">Party</th>
<th data-align="left">Category</th>
</tr>
</thead>
<tbody>
<tr v-for="field in fields" :key="field.id">
<td>
<k-button @click="addField(field)"
:title="`Add ${field.label || field.title} to view`" icon="angle-left">
</k-button>
</td>
<td>
<strong>{{ field.label || field.title }}</strong>
</td>
<td>
<span>
{{ field.type }}
</span>
</td>
<td>
<span :style="`background: ${field.party === 1 ? '#3498db' : '#27ae60'}; color: white; padding: 2px 6px; border-radius: 3px; font-size: 12px;`">
{{ field.party_label }}
</span>
</td>
<td>
{{ field.category }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div v-else-if="!loading" style="text-align: center; padding: 2rem; color: #666; font-style: italic;">
<p>Select a relationship schema to see available fields</p>
</div>
<div v-if="loading" style="text-align: center; padding: 2rem; color: #666;">
<p>Loading fields...</p>
</div>
</k-field>
</template>
<script>
export default {
props: {
label: String,
help: String,
disabled: Boolean,
required: Boolean,
value: [String, Array, Object],
relationship: {
type: String,
default: ''
},
filter: {
type: String,
default: 'relation_fields'
},
search: {
type: String,
default: ''
}
},
data() {
return {
fields: [],
loading: false,
lastKnownRelationship: null,
relationshipCheckInterval: null
};
},
created() {
this.loadFields();
},
methods: {
async getRelationshipFromPage() {
try {
// Method 1: Check direct parent
if (this.$parent?.value?.relationship_reference) {
const parentValue = this.$parent.value.relationship_reference;
let pageId = null;
if (Array.isArray(parentValue) && parentValue.length > 0) {
const firstItem = parentValue[0];
try {
pageId = firstItem.id || firstItem.uuid || firstItem.link || firstItem.url || firstItem;
} catch (error) {
pageId = firstItem;
}
} else if (typeof parentValue === 'object' && parentValue !== null) {
try {
pageId = parentValue.id || parentValue.uuid || parentValue.link || parentValue.url || parentValue;
} catch (error) {
pageId = parentValue;
}
} else {
pageId = parentValue;
}
return pageId;
}
// Method 2: Traverse component tree to find form
let current = this.$parent;
while (current) {
if (current.value && current.value.relationship_reference) {
const relationshipValue = current.value.relationship_reference;
let pageId = null;
if (Array.isArray(relationshipValue) && relationshipValue.length > 0) {
const firstItem = relationshipValue[0];
try {
pageId = firstItem.id || firstItem.uuid || firstItem.link || firstItem.url || firstItem;
} catch (error) {
pageId = firstItem;
}
} else if (typeof relationshipValue === 'object' && relationshipValue !== null) {
try {
pageId = relationshipValue.id || relationshipValue.uuid || relationshipValue.link || relationshipValue.url || relationshipValue;
} catch (error) {
pageId = relationshipValue;
}
} else {
pageId = relationshipValue;
}
return pageId;
}
current = current.$parent;
}
// Method 3: Try to access form data through $refs or global state
if (this.$root && this.$root.$children) {
const formComponent = this.$root.$children.find(child => child.value && child.value.relationship_reference);
if (formComponent) {
const relationshipValue = formComponent.value.relationship_reference;
let pageId = null;
if (Array.isArray(relationshipValue) && relationshipValue.length > 0) {
const firstItem = relationshipValue[0];
try {
pageId = firstItem.id || firstItem.uuid || firstItem.link || firstItem.url || firstItem;
} catch (error) {
pageId = firstItem;
}
} else if (typeof relationshipValue === 'object' && relationshipValue !== null) {
try {
pageId = relationshipValue.id || relationshipValue.uuid || relationshipValue.link || relationshipValue.url || relationshipValue;
} catch (error) {
pageId = relationshipValue;
}
} else {
pageId = relationshipValue;
}
return pageId;
}
}
} catch (error) {
// Silent error handling
}
return null;
},
async loadFields() {
this.loading = true;
try {
// Get relationship from prop or try to get it dynamically
let relationshipId = this.relationship || await this.getRelationshipFromPage();
if (!relationshipId) {
this.fields = [];
this.loading = false;
return;
}
const filter = this.filter;
const apiUrl = `view-builder/field-library/${encodeURIComponent(relationshipId)}/${encodeURIComponent(filter)}`;
// Call the API to get field data
const response = await this.$api.get(apiUrl);
if (response && response.fields) {
this.fields = response.fields;
}
} catch (error) {
this.fields = [];
} finally {
this.loading = false;
}
},
addField(field) {
// Get current form data from parent
let current = this.$parent;
let formData = null;
while (current && !formData) {
if (current.value) {
formData = current.value;
break;
}
current = current.$parent;
}
if (!formData) {
return;
}
// Determine which structure field to update based on current context
const currentContext = formData.current_view_context || 'party_1';
const targetField = currentContext === 'party_1' ? 'party_1_view_fields' : 'party_2_view_fields';
// Get current structure value
const currentStructure = formData[targetField] || [];
// Create new field entry
const newFieldEntry = {
field_reference: field.id,
field_label: field.title,
field_type: field.type
};
// Add to structure
const updatedStructure = [...currentStructure, newFieldEntry];
// Trigger form update through the parent component
if (current && current.$emit) {
current.$emit('input', {
...formData,
[targetField]: updatedStructure
});
}
},
async checkForRelationshipChanges() {
const currentRelationship = await this.getRelationshipFromPage();
if (currentRelationship && currentRelationship !== this.lastKnownRelationship) {
this.lastKnownRelationship = currentRelationship;
this.loadFields();
}
}
},
watch: {
relationship: {
handler() {
this.loadFields();
}
},
filter: {
handler() {
this.loadFields();
}
}
},
mounted() {
// Set up polling to check for relationship changes
this.relationshipCheckInterval = setInterval(() => {
this.checkForRelationshipChanges();
}, 1000);
},
beforeDestroy() {
if (this.relationshipCheckInterval) {
clearInterval(this.relationshipCheckInterval);
}
},
}
</script>
<style scoped>
/* Scoped styles to prevent leakage */
.field-selector-simple {
width: 100%;
overflow: visible;
}
.kirby-native-table {
border: 1px solid var(--color-border);
border-radius: var(--rounded);
overflow-x: auto;
overflow-y: hidden;
background: var(--color-white);
width: 100%;
max-width: 100%;
display: block;
scroll-behavior: smooth;
scrollbar-width: thin;
scrollbar-color: var(--color-gray-400) var(--color-gray-100);
position: relative;
}
.kirby-native-table::-webkit-scrollbar {
height: 8px;
}
.kirby-native-table::-webkit-scrollbar-track {
background: var(--color-gray-100);
border-radius: 4px;
}
.kirby-native-table::-webkit-scrollbar-thumb {
background: var(--color-gray-400);
border-radius: 4px;
}
.kirby-native-table::-webkit-scrollbar-thumb:hover {
background: var(--color-gray-500);
}
.kirby-native-table table {
min-width: 700px;
width: 100%;
border-collapse: collapse;
border-spacing: 0;
font-size: var(--text-sm);
background: transparent;
table-layout: auto; /* Changed from fixed to allow dynamic column shifting */
}
.kirby-native-table th {
background: var(--color-gray-100);
border-bottom: 1px solid var(--color-border);
padding: var(--spacing-1) 0;
text-align: left;
font-weight: var(--font-medium);
color: var(--color-gray-700);
font-size: var(--text-xs);
text-transform: uppercase;
letter-spacing: 0.05em;
white-space: nowrap;
line-height: 1.2;
vertical-align: middle; /* Ensure consistent vertical alignment */
}
.kirby-native-table td {
padding: var(--spacing-1) 0;
border-bottom: 1px solid var(--color-border);
vertical-align: middle; /* Ensure consistent vertical alignment */
color: var(--color-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.2;
font-size: 0.8125rem;
box-sizing: border-box;
}
.kirby-native-table td:first-child {
text-overflow: clip;
white-space: normal;
overflow: visible;
}
.kirby-native-table td:nth-child(2) {
max-width: 100px; /* Constrain to ~16 characters */
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
/* Removed display: block to maintain table-cell behavior */
}
.kirby-native-table th:nth-child(2) {
max-width: 100px;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
/* Removed display: block to maintain table-cell behavior */
}
.kirby-native-table td::before,
.kirby-native-table td::after {
content: none;
}
.kirby-native-table tr:last-child td {
border-bottom: none;
}
.kirby-native-table tr:hover {
background: var(--color-gray-50);
}
/* Column sizing */
.kirby-native-table th:first-child,
.kirby-native-table td:first-child {
width: 2rem;
text-align: center;
}
.kirby-native-table th:nth-child(2),
.kirby-native-table td:nth-child(2) {
max-width: 30px; /* Constrain width */
min-width: 0; /* Allow shrinking */
}
.kirby-native-table th:nth-child(3),
.kirby-native-table td:nth-child(3) {
width: 10%;
min-width: 0; /* Allow flexible resizing */
}
.kirby-native-table th:nth-child(4),
.kirby-native-table td:nth-child(4) {
width: 12%;
min-width: 0; /* Allow flexible resizing */
}
.kirby-native-table th:nth-child(5),
.kirby-native-table td:nth-child(5) {
width: auto; /* Allow to take remaining space */
min-width: 0;
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
.kirby-native-table {
background: var(--color-back);
border-color: var(--color-border);
}
.kirby-native-table th {
background: var(--color-gray-900);
color: var(--color-gray-400);
}
.kirby-native-table tr:hover {
background: var(--color-gray-800);
}
}
.k-table-text {
color: var(--color-gray-700);
font-size: 0.875rem;
}
</style>