273273</template >
274274
275275<script lang="ts" setup>
276- import {
277- ref ,
278- computed ,
279- toRefs ,
280- watch ,
281- unref ,
282- nextTick ,
283- onMounted ,
284- onUnmounted ,
285- type Ref ,
286- } from ' vue' ;
276+ import { ref , computed , toRefs , watch , unref , onMounted , onUnmounted , type Ref } from ' vue' ;
287277import { useI18n } from ' vue-i18n' ;
288278import { useRouter } from ' vue-router' ;
289279import { debounce } from ' lodash' ;
290280import { useStores , useCollection , useSync , useApi } from ' @directus/extensions-sdk' ;
291281import { formatTitle } from ' @directus/format-title' ;
292282import { getDefaultDisplayForType } from ' ./utils/getDefaultDisplayForType' ;
293- import { filterValidFields } from ' ./utils/fieldValidity' ;
283+ import { filterValidFields , filterValidColumnDisplays } from ' ./utils/fieldValidity' ;
294284import { useTableApi } from ' ./composables/api' ;
295285import { useAliasFields } from ' ./composables/useAliasFields' ;
296286import { useLanguageSelector } from ' ./composables/useLanguageSelector' ;
@@ -485,8 +475,16 @@ const fieldsForAliasing = computed(() => {
485475// Use alias fields for proper relational data handling.
486476// Issue #48: forward the columnDisplays override map so the API query expands
487477// override template paths (e.g. `{{ user.first_name }}`) into deep field
488- // requests via `adjustFieldsForDisplays`.
489- const columnDisplaysRef = computed (() => (layoutOptions .value as any )?.columnDisplays ?? {});
478+ // requests via `adjustFieldsForDisplays`. Drop entries that point at fields
479+ // the user has since deleted from the collection (mirrors filterValidFields).
480+ type ColumnDisplayShape = { template: string ; display? : string };
481+ const columnDisplaysRef = computed (() =>
482+ filterValidColumnDisplays <ColumnDisplayShape >(
483+ (layoutOptions .value as any )?.columnDisplays ,
484+ collection .value ,
485+ fieldsStore
486+ )
487+ );
490488
491489const { aliasedFields, aliasQuery, getFromAliasedItem } = useAliasFields (
492490 fieldsForAliasing ,
@@ -941,32 +939,18 @@ async function handleQuickFilterSaved(event: any) {
941939 }
942940}
943941
944- // Issue #48: when columnDisplays change locally, our own reactive chain handles
945- // the rebuild AND the watch on fieldsWithRelational fires. But when the change
946- // originates in options.vue (a sibling slot), the prop round-trip through the
947- // parent layout takes a few ticks. We watch our own layoutOptions for changes
948- // to the columnDisplays map and force a refetch after nextTick so aliasedFields
949- // has fully settled.
942+ // Issue #48: when columnDisplays change in options.vue (sibling slot), the prop
943+ // round-trip through the parent layout takes a few ticks. flush: 'post' makes
944+ // the watcher run after the DOM update so aliasedFields has fully settled
945+ // before we refetch.
950946watch (
951947 () => (layoutOptions .value as any )?.columnDisplays ,
952- async () => {
953- await nextTick ();
948+ () => {
954949 getItems ();
955950 },
956- { deep: true }
951+ { deep: true , flush: ' post ' }
957952);
958953
959- // Window-event fallback: options.vue dispatches this after setOverride/removeOverride.
960- // Helps in cases where the prop sync hasn't propagated by the time our local
961- // watch (above) would have fired. Both paths converge on the same getItems call;
962- // directus' API layer dedupes in-flight requests so this isn't a double-fetch.
963- function handleColumnDisplaysChanged() {
964- // 250ms ≈ enough for the options.vue → parent layout → super-table.vue prop
965- // round-trip to land. Microtask-precise nextTick is too eager here because
966- // the round-trip spans a full event-loop turn (parent re-render).
967- setTimeout (() => getItems (), 250 );
968- }
969-
970954// Setup event listeners on mount
971955onMounted (() => {
972956 // Load presets from layoutOptions (no localStorage needed)
@@ -977,12 +961,10 @@ onMounted(() => {
977961
978962 // Listen for save events from actions
979963 window .addEventListener (' quick-filter-saved' , handleQuickFilterSaved );
980- window .addEventListener (' super-layout-table:refresh' , handleColumnDisplaysChanged );
981964});
982965
983966onUnmounted (() => {
984967 window .removeEventListener (' quick-filter-saved' , handleQuickFilterSaved );
985- window .removeEventListener (' super-layout-table:refresh' , handleColumnDisplaysChanged );
986968});
987969
988970// Update manual filters when props.filter changes (from native filter interface)
0 commit comments