@anzusystems/common-admin 1.47.0-beta.dev-1789897769 → 1.47.0-beta.dev-1789936376

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/labs.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"labs.js","names":["$attrs","$slots","$slots"],"sources":["../src/labs/filters/AFilterWrapperSidebar.vue","../src/labs/filters/AFilterWrapperSidebar.vue","../src/labs/filters/ADatatableOrdering.vue","../src/labs/filters/ADatatableOrdering.vue","../src/labs/filters/ADatatablePagination.vue","../src/labs/filters/ADatatablePagination.vue","../src/labs/api/useApiFetchItems.ts","../src/labs/api/useApiFetchListBatch.ts","../src/labs/job/jobApi.ts","../src/labs/filters/datatableColumns.ts","../src/labs/log/logApi.ts","../src/labs/log/logActions.ts","../src/labs/log/logType.ts","../src/labs/log/ALogDatatable.vue","../src/labs/log/ALogDatatable.vue","../src/labs/log/ALogFilter.vue","../src/labs/log/ALogFilter.vue","../src/labs/log/logFilter.ts","../src/labs/log/ALogListView.vue","../src/labs/log/ALogListView.vue","../src/utils/json.ts","../src/labs/log/ALogDetail.vue","../src/labs/log/ALogDetail.vue","../src/labs/log/ALogDetailView.vue","../src/labs/log/ALogDetailView.vue","../src/labs/subjectSelect/useSubjectSelect.ts","../src/labs/subjectSelect/ASubjectSelect.vue","../src/labs/subjectSelect/ASubjectSelect.vue","../src/labs/listEditor/AListEditor.vue","../src/labs/listEditor/AListEditor.vue","../src/labs/listEditor/composables/useDragInstruction.ts","../src/labs/listEditor/internal/LeNestedRow.vue","../src/labs/listEditor/internal/LeNestedRow.vue","../src/labs/listEditor/internal/LeChangeParentDialog.vue","../src/labs/listEditor/internal/LeChangeParentDialog.vue","../src/labs/listEditor/ANestedSortableListEditor.vue","../src/labs/listEditor/ANestedSortableListEditor.vue","../src/labs/unsavedGuard/useGuardedDelete.ts","../src/labs/listEditor/composables/useListEditor.ts","../src/labs/listEditor/composables/useNestedUnsavedKeys.ts"],"sourcesContent":["<script lang=\"ts\" setup>\nimport { inject, provide, ref } from 'vue'\nimport {\n FilterConfigKey,\n FilterDataKey,\n FilterSelectedKey,\n FilterSubmitResetCounterKey,\n} from '@/labs/filters/filterInjectionKeys'\nimport type { ValueObjectOption } from '@/types/ValueObject'\nimport { isUndefined } from '@/utils/common'\nimport { useFilterClearHelpers } from '@/labs/filters/filterFactory'\n\nwithDefaults(\n defineProps<{\n formName?: string\n }>(),\n {\n formName: 'search',\n }\n)\nconst emit = defineEmits<{\n (e: 'submit'): void\n}>()\n\nconst filterConfig = inject(FilterConfigKey)\nconst filterData = inject(FilterDataKey)\nif (isUndefined(filterConfig) || isUndefined(filterData)) {\n throw new Error('Incorrect provide/inject config.')\n}\n\nprovide(FilterSubmitResetCounterKey, ref(0))\nconst filterSelected = ref<Map<string, ValueObjectOption<string | number>[]>>(new Map())\nprovide(FilterSelectedKey, filterSelected)\n\nconst { clearAll, clearAllFilterSelected } = useFilterClearHelpers()\n\nconst submitFilter = () => {\n emit('submit')\n}\n\n// AFilterWrapperSidebar has no reset button of its own (the consumer renders it, e.g. in the drawer\n// footer). Expose the same reset AFilterWrapper does so that button also clears the filter values and\n// the selected-filter chips.\nconst resetFilter = () => {\n clearAll(filterData, filterConfig)\n clearAllFilterSelected(filterData, filterConfig, filterSelected)\n}\n\ndefineExpose({ resetFilter })\n</script>\n\n<template>\n <VForm\n :name=\"formName\"\n @submit.prevent=\"submitFilter\"\n @keyup.enter=\"submitFilter\"\n >\n <slot />\n </VForm>\n</template>\n","<script lang=\"ts\" setup>\nimport { inject, provide, ref } from 'vue'\nimport {\n FilterConfigKey,\n FilterDataKey,\n FilterSelectedKey,\n FilterSubmitResetCounterKey,\n} from '@/labs/filters/filterInjectionKeys'\nimport type { ValueObjectOption } from '@/types/ValueObject'\nimport { isUndefined } from '@/utils/common'\nimport { useFilterClearHelpers } from '@/labs/filters/filterFactory'\n\nwithDefaults(\n defineProps<{\n formName?: string\n }>(),\n {\n formName: 'search',\n }\n)\nconst emit = defineEmits<{\n (e: 'submit'): void\n}>()\n\nconst filterConfig = inject(FilterConfigKey)\nconst filterData = inject(FilterDataKey)\nif (isUndefined(filterConfig) || isUndefined(filterData)) {\n throw new Error('Incorrect provide/inject config.')\n}\n\nprovide(FilterSubmitResetCounterKey, ref(0))\nconst filterSelected = ref<Map<string, ValueObjectOption<string | number>[]>>(new Map())\nprovide(FilterSelectedKey, filterSelected)\n\nconst { clearAll, clearAllFilterSelected } = useFilterClearHelpers()\n\nconst submitFilter = () => {\n emit('submit')\n}\n\n// AFilterWrapperSidebar has no reset button of its own (the consumer renders it, e.g. in the drawer\n// footer). Expose the same reset AFilterWrapper does so that button also clears the filter values and\n// the selected-filter chips.\nconst resetFilter = () => {\n clearAll(filterData, filterConfig)\n clearAllFilterSelected(filterData, filterConfig, filterSelected)\n}\n\ndefineExpose({ resetFilter })\n</script>\n\n<template>\n <VForm\n :name=\"formName\"\n @submit.prevent=\"submitFilter\"\n @keyup.enter=\"submitFilter\"\n >\n <slot />\n </VForm>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed, inject, type Ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport {\n type DatatableOrderingOption,\n type DatatableOrderingOptions,\n SORT_BY_SCORE,\n SortOrder,\n} from '@/composables/system/datatableColumns'\nimport { isUndefined } from '@/utils/common'\nimport { DatatablePaginationKey } from '@/labs/filters/filterInjectionKeys'\n\nimport type { Pagination } from '@/labs/filters/pagination'\n\nconst props = withDefaults(\n defineProps<{\n variant?: 'id' | 'createdAt' | 'most-relevant'\n customOptions?: undefined | DatatableOrderingOptions\n paginationUpdateCustomCb?: ((option: DatatableOrderingOption, pagination: Ref<Pagination>) => void) | undefined\n }>(),\n {\n variant: 'id',\n customOptions: undefined,\n paginationUpdateCustomCb: undefined,\n }\n)\nconst emit = defineEmits<{\n (e: 'sortByChange', data: DatatableOrderingOption): void\n}>()\n\nconst modelValue = defineModel<number>('modelValue', {\n default: 1,\n required: false,\n})\n\nconst pagination = inject(DatatablePaginationKey)\n\nif (isUndefined(pagination)) {\n throw new Error('Incorrect provide/inject config.')\n}\n\nconst { t } = useI18n()\n\nconst defaultItemsCreatedAt: DatatableOrderingOptions = [\n {\n id: 1,\n titleT: 'common.system.datatable.ordering.mostRecent',\n sortBy: { key: 'createdAt', order: SortOrder.Desc },\n },\n {\n id: 2,\n titleT: 'common.system.datatable.ordering.oldest',\n sortBy: { key: 'createdAt', order: SortOrder.Asc },\n },\n]\n\nconst defaultItemsId: DatatableOrderingOptions = [\n {\n id: 1,\n titleT: 'common.system.datatable.ordering.mostRecent',\n sortBy: { key: 'id', order: SortOrder.Desc },\n },\n {\n id: 2,\n titleT: 'common.system.datatable.ordering.oldest',\n sortBy: { key: 'id', order: SortOrder.Asc },\n },\n]\n\nconst defaultItemsMostRelevant: DatatableOrderingOptions = [\n {\n id: 3,\n titleT: 'common.system.datatable.ordering.mostRelevant',\n sortBy: { key: SORT_BY_SCORE, order: SortOrder.Desc },\n },\n {\n id: 1,\n titleT: 'common.system.datatable.ordering.mostRecent',\n sortBy: { key: 'createdAt', order: SortOrder.Desc },\n },\n {\n id: 2,\n titleT: 'common.system.datatable.ordering.oldest',\n sortBy: { key: 'createdAt', order: SortOrder.Asc },\n },\n]\n\nconst activeTitle = computed(() => {\n const found = options.value.find((item: any) => item.id === modelValue.value)\n if (found) return t(found.titleT)\n return ''\n})\n\nconst options = computed(() => {\n if (props.customOptions) return props.customOptions\n if (props.variant === 'most-relevant') return defaultItemsMostRelevant\n if (props.variant === 'id') return defaultItemsId\n return defaultItemsCreatedAt\n})\n\nconst onItemClick = (item: DatatableOrderingOption) => {\n modelValue.value = item.id\n}\n\nconst paginationUpdateDefault = (found: DatatableOrderingOption) => {\n if (!found.sortBy) {\n pagination.value = {\n ...pagination.value,\n sortBy: null,\n }\n emit('sortByChange', found)\n return\n }\n pagination.value.sortBy = found.sortBy\n emit('sortByChange', found)\n}\n\nwatch(modelValue, (newValue, oldValue) => {\n if (newValue === oldValue) return\n const found = options.value.find((option) => {\n return option.id === newValue\n })\n if (isUndefined(found)) {\n return\n }\n if (isUndefined(props.paginationUpdateCustomCb)) {\n paginationUpdateDefault(found)\n return\n }\n props.paginationUpdateCustomCb(found, pagination)\n})\n\nwatch(\n pagination,\n (newValue, oldValue) => {\n if (newValue.sortBy?.key === oldValue?.sortBy?.key && newValue.sortBy?.order === oldValue?.sortBy?.order) {\n return\n }\n const found = options.value.find((option) => {\n return newValue.sortBy?.key === option.sortBy?.key && newValue.sortBy?.order === option.sortBy?.order\n })\n if (isUndefined(found)) {\n return\n }\n modelValue.value = found.id\n },\n { immediate: true }\n)\n</script>\n\n<template>\n <div class=\"d-flex align-center justify-center\">\n <div class=\"text-body-small mr-1\">{{ t('common.system.datatable.ordering.title') }}:</div>\n <VBtn\n variant=\"text\"\n rounded=\"xl\"\n size=\"small\"\n class=\"text-medium-emphasis\"\n append-icon=\"mdi-chevron-down\"\n >\n {{ activeTitle }}\n <VMenu\n activator=\"parent\"\n location=\"bottom\"\n close-on-content-click\n >\n <VList\n density=\"compact\"\n class=\"pa-0\"\n >\n <VListItem\n v-for=\"item in options\"\n :key=\"item.id\"\n @click=\"onItemClick(item)\"\n >\n <VListItemTitle :class=\"{ 'font-weight-bold': item.id === modelValue }\">\n {{ t(item.titleT) }}\n </VListItemTitle>\n </VListItem>\n </VList>\n </VMenu>\n </VBtn>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed, inject, type Ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport {\n type DatatableOrderingOption,\n type DatatableOrderingOptions,\n SORT_BY_SCORE,\n SortOrder,\n} from '@/composables/system/datatableColumns'\nimport { isUndefined } from '@/utils/common'\nimport { DatatablePaginationKey } from '@/labs/filters/filterInjectionKeys'\n\nimport type { Pagination } from '@/labs/filters/pagination'\n\nconst props = withDefaults(\n defineProps<{\n variant?: 'id' | 'createdAt' | 'most-relevant'\n customOptions?: undefined | DatatableOrderingOptions\n paginationUpdateCustomCb?: ((option: DatatableOrderingOption, pagination: Ref<Pagination>) => void) | undefined\n }>(),\n {\n variant: 'id',\n customOptions: undefined,\n paginationUpdateCustomCb: undefined,\n }\n)\nconst emit = defineEmits<{\n (e: 'sortByChange', data: DatatableOrderingOption): void\n}>()\n\nconst modelValue = defineModel<number>('modelValue', {\n default: 1,\n required: false,\n})\n\nconst pagination = inject(DatatablePaginationKey)\n\nif (isUndefined(pagination)) {\n throw new Error('Incorrect provide/inject config.')\n}\n\nconst { t } = useI18n()\n\nconst defaultItemsCreatedAt: DatatableOrderingOptions = [\n {\n id: 1,\n titleT: 'common.system.datatable.ordering.mostRecent',\n sortBy: { key: 'createdAt', order: SortOrder.Desc },\n },\n {\n id: 2,\n titleT: 'common.system.datatable.ordering.oldest',\n sortBy: { key: 'createdAt', order: SortOrder.Asc },\n },\n]\n\nconst defaultItemsId: DatatableOrderingOptions = [\n {\n id: 1,\n titleT: 'common.system.datatable.ordering.mostRecent',\n sortBy: { key: 'id', order: SortOrder.Desc },\n },\n {\n id: 2,\n titleT: 'common.system.datatable.ordering.oldest',\n sortBy: { key: 'id', order: SortOrder.Asc },\n },\n]\n\nconst defaultItemsMostRelevant: DatatableOrderingOptions = [\n {\n id: 3,\n titleT: 'common.system.datatable.ordering.mostRelevant',\n sortBy: { key: SORT_BY_SCORE, order: SortOrder.Desc },\n },\n {\n id: 1,\n titleT: 'common.system.datatable.ordering.mostRecent',\n sortBy: { key: 'createdAt', order: SortOrder.Desc },\n },\n {\n id: 2,\n titleT: 'common.system.datatable.ordering.oldest',\n sortBy: { key: 'createdAt', order: SortOrder.Asc },\n },\n]\n\nconst activeTitle = computed(() => {\n const found = options.value.find((item: any) => item.id === modelValue.value)\n if (found) return t(found.titleT)\n return ''\n})\n\nconst options = computed(() => {\n if (props.customOptions) return props.customOptions\n if (props.variant === 'most-relevant') return defaultItemsMostRelevant\n if (props.variant === 'id') return defaultItemsId\n return defaultItemsCreatedAt\n})\n\nconst onItemClick = (item: DatatableOrderingOption) => {\n modelValue.value = item.id\n}\n\nconst paginationUpdateDefault = (found: DatatableOrderingOption) => {\n if (!found.sortBy) {\n pagination.value = {\n ...pagination.value,\n sortBy: null,\n }\n emit('sortByChange', found)\n return\n }\n pagination.value.sortBy = found.sortBy\n emit('sortByChange', found)\n}\n\nwatch(modelValue, (newValue, oldValue) => {\n if (newValue === oldValue) return\n const found = options.value.find((option) => {\n return option.id === newValue\n })\n if (isUndefined(found)) {\n return\n }\n if (isUndefined(props.paginationUpdateCustomCb)) {\n paginationUpdateDefault(found)\n return\n }\n props.paginationUpdateCustomCb(found, pagination)\n})\n\nwatch(\n pagination,\n (newValue, oldValue) => {\n if (newValue.sortBy?.key === oldValue?.sortBy?.key && newValue.sortBy?.order === oldValue?.sortBy?.order) {\n return\n }\n const found = options.value.find((option) => {\n return newValue.sortBy?.key === option.sortBy?.key && newValue.sortBy?.order === option.sortBy?.order\n })\n if (isUndefined(found)) {\n return\n }\n modelValue.value = found.id\n },\n { immediate: true }\n)\n</script>\n\n<template>\n <div class=\"d-flex align-center justify-center\">\n <div class=\"text-body-small mr-1\">{{ t('common.system.datatable.ordering.title') }}:</div>\n <VBtn\n variant=\"text\"\n rounded=\"xl\"\n size=\"small\"\n class=\"text-medium-emphasis\"\n append-icon=\"mdi-chevron-down\"\n >\n {{ activeTitle }}\n <VMenu\n activator=\"parent\"\n location=\"bottom\"\n close-on-content-click\n >\n <VList\n density=\"compact\"\n class=\"pa-0\"\n >\n <VListItem\n v-for=\"item in options\"\n :key=\"item.id\"\n @click=\"onItemClick(item)\"\n >\n <VListItemTitle :class=\"{ 'font-weight-bold': item.id === modelValue }\">\n {{ t(item.titleT) }}\n </VListItemTitle>\n </VListItem>\n </VList>\n </VMenu>\n </VBtn>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed, inject, watch } from 'vue'\nimport { isNull, isUndefined } from '@/utils/common'\nimport { useI18n } from 'vue-i18n'\nimport { DatatablePageStoreKey, DatatablePaginationKey, FilterConfigKey } from '@/labs/filters/filterInjectionKeys'\nimport { useThrottleFn } from '@vueuse/core'\nimport { datatablePageKey, useDatatablePageStore } from '@/composables/system/datatablePageStore'\n\nwithDefaults(\n defineProps<{\n itemsPerPageOptions?: number[]\n hideRecordsPerPage?: boolean\n }>(),\n {\n itemsPerPageOptions: () => [10, 25, 50],\n hideRecordsPerPage: false,\n }\n)\nconst emit = defineEmits<{\n (e: 'change'): void\n}>()\n\nconst pagination = inject(DatatablePaginationKey)\n\nif (isUndefined(pagination)) {\n throw new Error('Incorrect provide/inject config.')\n}\n\nconst { t } = useI18n()\nconst { setStoredPage } = useDatatablePageStore()\n\n// `useFilterHelpers` provides the key, so tables sharing a system/subject stay apart. Both\n// fallbacks are optional on purpose: a datatable may provide pagination without either, and it\n// then keeps the shared bucket it had before the store was keyed.\nconst providedPageKey = inject(DatatablePageStoreKey, undefined)\nconst filterConfig = inject(FilterConfigKey, undefined)\nconst pageStoreKey = computed(\n () => providedPageKey ?? datatablePageKey(filterConfig?.general.system, filterConfig?.general.subject)\n)\n\nconst lastPage = computed(() => {\n return Math.ceil(pagination.value.totalCount / pagination.value.rowsPerPage)\n})\n\nconst displayedFrom = computed(() => {\n return pagination.value.page * pagination.value.rowsPerPage - pagination.value.rowsPerPage + 1\n})\n\nconst displayedTo = computed(() => {\n return (\n pagination.value.page * pagination.value.rowsPerPage -\n pagination.value.rowsPerPage +\n pagination.value.currentViewCount\n )\n})\n\nconst disabledFirstAndPrev = computed(() => {\n return pagination.value.page === 1\n})\n\n// `>=`, not `===`: an empty list has `totalCount: 0`, so `lastPage` is 0 while `page` is never below\n// 1, and the two can never meet. The page would then count as \"not the last one\" on a list with\n// nothing in it -- and the same holds before any request has been made, because that initial state is\n// the same numbers.\nconst disabledLast = computed(() => {\n return !isNull(pagination.value.hasNextPage) || pagination.value.page >= lastPage.value\n})\n\nconst disabledNext = computed(() => {\n return (\n (isNull(pagination.value.hasNextPage) && pagination.value.page >= lastPage.value) ||\n pagination.value.hasNextPage === false\n )\n})\n\nconst computedTotalCountText = computed(() => {\n if (!isNull(pagination.value.hasNextPage)) {\n return pagination.value.hasNextPage ? displayedTo.value + 1 + '+' : displayedTo.value\n }\n return pagination.value.totalCount\n})\n\nwatch(\n () => pagination.value.rowsPerPage,\n (newValue, oldValue) => {\n if (newValue !== oldValue) {\n pagination.value.page = 1\n emit('change')\n }\n }\n)\n\nwatch(\n () => pagination.value.page,\n (newValue, oldValue) => {\n if (newValue !== oldValue) {\n setStoredPage(pageStoreKey.value, newValue)\n emit('change')\n }\n }\n)\n\n// Leading edge only: the throttle is a click-spam guard, not a rate limiter. VueUse 15 flipped\n// `trailing` to true by default, which replays the last click once the window closes -- stepping\n// prev/next a second time, and past `lastPage`, because the queued callback never re-reads the\n// disabled state.\nconst onClickFirst = useThrottleFn(\n () => {\n pagination.value.page = 1\n },\n 300,\n false\n)\n\nconst onClickLast = useThrottleFn(\n () => {\n pagination.value.page = lastPage.value\n },\n 300,\n false\n)\n\nconst onClickPrev = useThrottleFn(\n () => {\n pagination.value.page = pagination.value.page - 1\n },\n 300,\n false\n)\n\nconst onClickNext = useThrottleFn(\n () => {\n pagination.value.page = pagination.value.page + 1\n },\n 300,\n false\n)\n</script>\n\n<template>\n <div class=\"anzu-data-footer\">\n <div\n v-if=\"!hideRecordsPerPage\"\n class=\"anzu-data-footer__page-limit\"\n >\n {{ t('common.system.datatable.itemsPerPage') }}:\n <VBtnToggle\n v-model=\"pagination.rowsPerPage\"\n class=\"ml-2\"\n density=\"compact\"\n mandatory\n data-cy=\"table-size\"\n >\n <VBtn\n v-for=\"item in itemsPerPageOptions\"\n :key=\"item\"\n :color=\"item === pagination.rowsPerPage ? 'secondary' : ''\"\n :value=\"item\"\n density=\"compact\"\n size=\"small\"\n variant=\"text\"\n >\n {{ item }}\n </VBtn>\n </VBtnToggle>\n </div>\n <div class=\"anzu-data-footer__pagination\">\n {{ displayedFrom }} - {{ displayedTo }} {{ t('common.system.datatable.from') }}\n {{ computedTotalCountText }}\n </div>\n <div class=\"anzu-data-footer__icons-before\">\n <VBtn\n :disabled=\"disabledFirstAndPrev\"\n icon=\"mdi-page-first\"\n size=\"small\"\n variant=\"text\"\n @click.stop=\"onClickFirst\"\n />\n <VBtn\n :disabled=\"disabledFirstAndPrev\"\n icon=\"mdi-chevron-left\"\n size=\"small\"\n variant=\"text\"\n @click.stop=\"onClickPrev\"\n />\n </div>\n <div class=\"current-page\">\n <span>{{ pagination.page }}</span>\n </div>\n <div class=\"anzu-data-footer__icons-after\">\n <VBtn\n :disabled=\"disabledNext\"\n icon=\"mdi-chevron-right\"\n size=\"small\"\n variant=\"text\"\n @click.stop=\"onClickNext\"\n />\n <VBtn\n :disabled=\"disabledLast\"\n icon=\"mdi-page-last\"\n size=\"small\"\n variant=\"text\"\n @click.stop=\"onClickLast\"\n />\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n.anzu-data-footer {\n margin-top: 10px;\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n align-items: center;\n font-size: 0.75rem;\n padding: 0 8px;\n\n > div.current-page {\n padding-left: 10px;\n padding-right: 10px;\n user-select: none;\n min-width: 40px;\n display: flex;\n text-align: center;\n align-items: center;\n justify-content: center;\n }\n\n &__page-limit {\n display: flex;\n align-items: center;\n flex: 0 0 0;\n justify-content: flex-end;\n white-space: nowrap;\n margin-right: 14px;\n }\n\n &__pagination {\n display: block;\n text-align: center;\n margin: 0 32px 0 24px;\n }\n}\n</style>\n","<script lang=\"ts\" setup>\nimport { computed, inject, watch } from 'vue'\nimport { isNull, isUndefined } from '@/utils/common'\nimport { useI18n } from 'vue-i18n'\nimport { DatatablePageStoreKey, DatatablePaginationKey, FilterConfigKey } from '@/labs/filters/filterInjectionKeys'\nimport { useThrottleFn } from '@vueuse/core'\nimport { datatablePageKey, useDatatablePageStore } from '@/composables/system/datatablePageStore'\n\nwithDefaults(\n defineProps<{\n itemsPerPageOptions?: number[]\n hideRecordsPerPage?: boolean\n }>(),\n {\n itemsPerPageOptions: () => [10, 25, 50],\n hideRecordsPerPage: false,\n }\n)\nconst emit = defineEmits<{\n (e: 'change'): void\n}>()\n\nconst pagination = inject(DatatablePaginationKey)\n\nif (isUndefined(pagination)) {\n throw new Error('Incorrect provide/inject config.')\n}\n\nconst { t } = useI18n()\nconst { setStoredPage } = useDatatablePageStore()\n\n// `useFilterHelpers` provides the key, so tables sharing a system/subject stay apart. Both\n// fallbacks are optional on purpose: a datatable may provide pagination without either, and it\n// then keeps the shared bucket it had before the store was keyed.\nconst providedPageKey = inject(DatatablePageStoreKey, undefined)\nconst filterConfig = inject(FilterConfigKey, undefined)\nconst pageStoreKey = computed(\n () => providedPageKey ?? datatablePageKey(filterConfig?.general.system, filterConfig?.general.subject)\n)\n\nconst lastPage = computed(() => {\n return Math.ceil(pagination.value.totalCount / pagination.value.rowsPerPage)\n})\n\nconst displayedFrom = computed(() => {\n return pagination.value.page * pagination.value.rowsPerPage - pagination.value.rowsPerPage + 1\n})\n\nconst displayedTo = computed(() => {\n return (\n pagination.value.page * pagination.value.rowsPerPage -\n pagination.value.rowsPerPage +\n pagination.value.currentViewCount\n )\n})\n\nconst disabledFirstAndPrev = computed(() => {\n return pagination.value.page === 1\n})\n\n// `>=`, not `===`: an empty list has `totalCount: 0`, so `lastPage` is 0 while `page` is never below\n// 1, and the two can never meet. The page would then count as \"not the last one\" on a list with\n// nothing in it -- and the same holds before any request has been made, because that initial state is\n// the same numbers.\nconst disabledLast = computed(() => {\n return !isNull(pagination.value.hasNextPage) || pagination.value.page >= lastPage.value\n})\n\nconst disabledNext = computed(() => {\n return (\n (isNull(pagination.value.hasNextPage) && pagination.value.page >= lastPage.value) ||\n pagination.value.hasNextPage === false\n )\n})\n\nconst computedTotalCountText = computed(() => {\n if (!isNull(pagination.value.hasNextPage)) {\n return pagination.value.hasNextPage ? displayedTo.value + 1 + '+' : displayedTo.value\n }\n return pagination.value.totalCount\n})\n\nwatch(\n () => pagination.value.rowsPerPage,\n (newValue, oldValue) => {\n if (newValue !== oldValue) {\n pagination.value.page = 1\n emit('change')\n }\n }\n)\n\nwatch(\n () => pagination.value.page,\n (newValue, oldValue) => {\n if (newValue !== oldValue) {\n setStoredPage(pageStoreKey.value, newValue)\n emit('change')\n }\n }\n)\n\n// Leading edge only: the throttle is a click-spam guard, not a rate limiter. VueUse 15 flipped\n// `trailing` to true by default, which replays the last click once the window closes -- stepping\n// prev/next a second time, and past `lastPage`, because the queued callback never re-reads the\n// disabled state.\nconst onClickFirst = useThrottleFn(\n () => {\n pagination.value.page = 1\n },\n 300,\n false\n)\n\nconst onClickLast = useThrottleFn(\n () => {\n pagination.value.page = lastPage.value\n },\n 300,\n false\n)\n\nconst onClickPrev = useThrottleFn(\n () => {\n pagination.value.page = pagination.value.page - 1\n },\n 300,\n false\n)\n\nconst onClickNext = useThrottleFn(\n () => {\n pagination.value.page = pagination.value.page + 1\n },\n 300,\n false\n)\n</script>\n\n<template>\n <div class=\"anzu-data-footer\">\n <div\n v-if=\"!hideRecordsPerPage\"\n class=\"anzu-data-footer__page-limit\"\n >\n {{ t('common.system.datatable.itemsPerPage') }}:\n <VBtnToggle\n v-model=\"pagination.rowsPerPage\"\n class=\"ml-2\"\n density=\"compact\"\n mandatory\n data-cy=\"table-size\"\n >\n <VBtn\n v-for=\"item in itemsPerPageOptions\"\n :key=\"item\"\n :color=\"item === pagination.rowsPerPage ? 'secondary' : ''\"\n :value=\"item\"\n density=\"compact\"\n size=\"small\"\n variant=\"text\"\n >\n {{ item }}\n </VBtn>\n </VBtnToggle>\n </div>\n <div class=\"anzu-data-footer__pagination\">\n {{ displayedFrom }} - {{ displayedTo }} {{ t('common.system.datatable.from') }}\n {{ computedTotalCountText }}\n </div>\n <div class=\"anzu-data-footer__icons-before\">\n <VBtn\n :disabled=\"disabledFirstAndPrev\"\n icon=\"mdi-page-first\"\n size=\"small\"\n variant=\"text\"\n @click.stop=\"onClickFirst\"\n />\n <VBtn\n :disabled=\"disabledFirstAndPrev\"\n icon=\"mdi-chevron-left\"\n size=\"small\"\n variant=\"text\"\n @click.stop=\"onClickPrev\"\n />\n </div>\n <div class=\"current-page\">\n <span>{{ pagination.page }}</span>\n </div>\n <div class=\"anzu-data-footer__icons-after\">\n <VBtn\n :disabled=\"disabledNext\"\n icon=\"mdi-chevron-right\"\n size=\"small\"\n variant=\"text\"\n @click.stop=\"onClickNext\"\n />\n <VBtn\n :disabled=\"disabledLast\"\n icon=\"mdi-page-last\"\n size=\"small\"\n variant=\"text\"\n @click.stop=\"onClickLast\"\n />\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n.anzu-data-footer {\n margin-top: 10px;\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n align-items: center;\n font-size: 0.75rem;\n padding: 0 8px;\n\n > div.current-page {\n padding-left: 10px;\n padding-right: 10px;\n user-select: none;\n min-width: 40px;\n display: flex;\n text-align: center;\n align-items: center;\n justify-content: center;\n }\n\n &__page-limit {\n display: flex;\n align-items: center;\n flex: 0 0 0;\n justify-content: flex-end;\n white-space: nowrap;\n margin-right: 14px;\n }\n\n &__pagination {\n display: block;\n text-align: center;\n margin: 0 32px 0 24px;\n }\n}\n</style>\n","import { AnzuFatalError } from '@/model/error/AnzuFatalError'\nimport { AnzuApiResponseCodeError } from '@/model/error/AnzuApiResponseCodeError'\nimport { replaceUrlParameters, type UrlParams } from '@/services/api/apiHelper'\nimport { isValidHTTPStatus } from '@/utils/response'\nimport type { AxiosRequestConfig } from 'axios'\nimport { HTTP_STATUS_NO_CONTENT } from '@/composables/statusCodes'\nimport { isDefined, isUndefined } from '@/utils/common'\nimport type { Ref } from 'vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport { mapApiError, report } from '@/labs/api/apiErrors'\nimport { createAbortable, hasBody, ownedByHelper, requestedUrl } from '@/labs/api/request'\nimport { readListBody } from '@/labs/api/listBody'\n\nexport type UseApiFetchItemsParams = {\n client: AxiosClientFn\n system: string\n entity: string\n /**\n * The url, query and all: whatever built the query has already run by the time it gets here.\n *\n * `urlParams` are substituted into the PATH only -- `/article/:id/routes?locale=:locale` fills in\n * `:id` and sends `:locale` as it stands. This is the helper written for hand-made queries, so it\n * is the one place someone would reach for a placeholder in the query; put the value in the query\n * when building it, or pass it through `options.params` and let axios serialise it.\n */\n urlTemplate?: string\n urlParams?: UrlParams\n /** Anything axios takes except what this helper decides: the method, the url, the body and the signal. */\n options?: Omit<AxiosRequestConfig, 'method' | 'url' | 'data' | 'signal'>\n cancelPrevious?: boolean\n}\n\nexport type FetchItemsParams = {\n urlTemplate?: string\n urlParams?: UrlParams\n /** Stops this one call, leaving the instance's other calls alone. */\n signal?: AbortSignal\n}\n\n/**\n * A list read from an endpoint the caller has already written the query for, with no pagination.\n *\n * It exists because the fleet kept doing this through `useApiRequest<ApiResponseList<T[]>>`: build a\n * query by hand -- an order, a limit, a fixed filter -- bake it into the url, and read `.data` off\n * the envelope that comes back. That is a list fetch with the list part missing: nothing checks that\n * `data` is an array, so a malformed answer still hands back `undefined` typed as `T[]`, which is\n * the bug this whole family was built to end.\n *\n * `useApiFetchList` cannot serve them: it asks for a pagination ref, a `filterData` and a\n * `filterConfig`, and these calls have none -- their filters are decided at the call site and are\n * not the user's to change. So this is that helper with the pagination taken out: same core, same\n * error mapping, same list-body check, and it answers with the items rather than the envelope.\n *\n * It writes no metadata. An endpoint whose paging the user drives is `useApiFetchList`; a list read\n * page by page to the end is `useApiFetchListBatch`.\n */\nexport const useApiFetchItems = <T>(params: UseApiFetchItemsParams): UseApiFetchItemsReturnType<T> => {\n const { client, system, entity, urlTemplate, urlParams, options = {}, cancelPrevious = false } = params\n const abortable = createAbortable({ cancelPrevious })\n\n const execute = async (fetchParams: FetchItemsParams = {}): Promise<T[]> => {\n const { urlTemplate: urlTemplateOverride, urlParams: urlParamsOverride, signal } = fetchParams\n\n // Assigned inside `run`, at the moment there is a url to ask for; undefined until then, which is\n // what a failure before dispatch honestly reports.\n let url: string | undefined\n let dispatched: AxiosRequestConfig | undefined\n\n try {\n const res = await abortable.run((abortSignal) => {\n // Derived inside `run`, the way the rest of the family does it: `run` opens the generation\n // and, with `cancelPrevious`, stops the call before this one.\n const template = isDefined(urlTemplateOverride) ? urlTemplateOverride : urlTemplate\n if (isUndefined(template)) throw new AnzuFatalError(undefined, 'Url template is undefined')\n\n const resolvedParams = isDefined(urlParamsOverride) ? urlParamsOverride : urlParams\n const requestUrl = isUndefined(resolvedParams) ? template : replaceUrlParameters(template, resolvedParams)\n const instance = client()\n\n url = requestUrl\n\n return instance.get(requestUrl, { ...ownedByHelper(options), signal: abortSignal })\n }, signal)\n\n dispatched = res.config\n // The url the report will name, so a message and the context beside it cannot disagree about\n // which request failed when an interceptor rewrote it on the way out.\n const asSent = () => requestedUrl(client, url, options, undefined, dispatched)\n if (!isValidHTTPStatus(res.status)) throw new AnzuApiResponseCodeError(res.status)\n\n // Nothing matched -- an empty answer, not a broken one.\n if (res.status === HTTP_STATUS_NO_CONTENT) return []\n\n if (!hasBody(res)) {\n throw new AnzuApiResponseCodeError(res.status, undefined, 'Expected a response body, url: ' + asSent())\n }\n\n return readListBody<T>(res.data, res.status, asSent()).items\n } catch (err: unknown) {\n const context = { system, entity, url: requestedUrl(client, url, options, err, dispatched) }\n\n throw report(mapApiError(err, context), context)\n }\n }\n\n return { execute, abort: abortable.abort, loading: abortable.loading }\n}\n\nexport type UseApiFetchItemsReturnType<T> = {\n execute: (params?: FetchItemsParams) => Promise<T[]>\n abort: () => void\n /**\n * True while this instance has a request in flight, false once the last one settles.\n *\n * It is here so a caller does not have to keep its own: the hand-rolled version is a ref cleared\n * in a `finally`, and that `finally` belongs to whichever call ended -- including one that\n * `cancelPrevious` just superseded, which turns the spinner off while the call the user is waiting\n * for is still running. This one never dips between a superseded call and the one that replaced it.\n */\n loading: Ref<boolean>\n}\n","import { AnzuFatalError } from '@/model/error/AnzuFatalError'\nimport { AnzuApiResponseCodeError } from '@/model/error/AnzuApiResponseCodeError'\nimport { replaceUrlParameters, type UrlParams } from '@/services/api/apiHelper'\nimport { isValidHTTPStatus } from '@/utils/response'\nimport type { AxiosRequestConfig, AxiosResponse } from 'axios'\nimport { HTTP_STATUS_NO_CONTENT } from '@/composables/statusCodes'\nimport type { FilterConfig, FilterData } from '@/labs/filters/filterFactory'\nimport { type Ref, ref } from 'vue'\nimport { isDefined, isUndefined } from '@/utils/common'\nimport { type Pagination, usePagination } from '@/labs/filters/pagination'\nimport { SortOrder } from '@/composables/system/datatableColumns'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport { generateListQuery } from '@/labs/api/useApiFetchList'\nimport { mapApiError, report } from '@/labs/api/apiErrors'\nimport { createAbortable, hasBody, ownedByHelper, requestedUrl } from '@/labs/api/request'\nimport { readListBody } from '@/labs/api/listBody'\n\nexport type UseApiFetchListBatchParams = {\n client: AxiosClientFn\n system: string\n entity: string\n urlTemplate?: string\n urlParams?: UrlParams\n /** Anything axios takes except what this helper decides: the method, the url, the body and the signal. */\n options?: Omit<AxiosRequestConfig, 'method' | 'url' | 'data' | 'signal'>\n cancelPrevious?: boolean\n}\n\nexport type FetchListBatchParams = {\n /** Stops this one call, leaving the instance's other calls alone. */\n signal?: AbortSignal\n urlTemplate?: string\n urlParams?: UrlParams\n sortBy?: string\n sortDesc?: boolean\n batchSize?: number\n forceElastic?: boolean\n}\n\n/**\n * Carries the url of the page a failure happened on out to the catch that reports it.\n *\n * Nothing outside this file sees it: the catch unwraps it before mapping, so the caller gets the\n * same classes it would get from any other helper.\n */\nclass PageFailure extends Error {\n constructor(\n readonly pageUrl: string,\n readonly failure: unknown,\n /**\n * The config axios used for this page, when the page answered and it was this helper that\n * refused what came back. A page that failed in transport needs none: axios kept its own.\n */\n readonly dispatched?: AxiosRequestConfig\n ) {\n super('Batch page failed')\n this.name = 'PageFailure'\n }\n}\n\nexport const useApiFetchListBatch = <T>(params: UseApiFetchListBatchParams): UseApiFetchListBatchReturnType<T> => {\n const { client, system, entity, urlTemplate, urlParams, options = {}, cancelPrevious = false } = params\n const abortable = createAbortable({ cancelPrevious })\n\n const execute = async (\n filterData: FilterData<any>,\n filterConfig: FilterConfig<any>,\n fetchParams: FetchListBatchParams = {}\n ): Promise<T[]> => {\n const {\n urlTemplate: urlTemplateOverride,\n urlParams: urlParamsOverride,\n sortBy = 'id',\n sortDesc = true,\n batchSize = 100,\n forceElastic = false,\n signal,\n } = fetchParams\n\n try {\n return await abortable.run(async (abortSignal) => {\n // Derived inside `run`, the way the list does it: `run` opens the generation and, with\n // `cancelPrevious`, stops the call before this one, so a call that throws on a missing\n // template or a malformed filter config would otherwise leave its predecessor running.\n // Inside the try as well, so that failure arrives as the same error class as every other.\n const template = isDefined(urlTemplateOverride) ? urlTemplateOverride : urlTemplate\n if (isUndefined(template)) throw new AnzuFatalError(undefined, 'Url template is undefined')\n\n // A page size below one cannot end the walk: every page is then \"not shorter than the one\n // asked for\", so the loop has no exit and the helper becomes an unbounded stream of\n // requests. It is the caller's mistake, and it arrives as the same error class as the\n // caller's other mistakes rather than as a backend that never stops answering.\n if (batchSize < 1) throw new AnzuFatalError(undefined, 'batchSize must be at least 1, got ' + batchSize)\n\n const resolvedParams = isDefined(urlParamsOverride) ? urlParamsOverride : urlParams\n const searchApi = filterConfig.general.elastic || forceElastic ? '/search' : ''\n const baseUrl =\n (isUndefined(resolvedParams) ? template : replaceUrlParameters(template, resolvedParams)) + searchApi\n // One instance for the whole batch: a call is one call, and a factory that answers\n // differently per page would have its pages talking to two backends.\n const instance = client()\n\n const { pagination } = usePagination(sortBy, sortDesc ? SortOrder.Desc : SortOrder.Asc, {\n rowsPerPage: batchSize,\n })\n\n // The page's own url travels with its response: a batch that failed on page three has to\n // be able to say which page that was, and the base url cannot.\n const get = async (page: Ref<Pagination>) => {\n const pageUrl = baseUrl + generateListQuery(page, filterData, filterConfig)\n\n try {\n return { res: await instance.get(pageUrl, { ...ownedByHelper(options), signal: abortSignal }), pageUrl }\n } catch (err: unknown) {\n // Tagged here, where the page is still known. A shared variable cannot do this job: the\n // counted branch dispatches every page before any of them answers, so it would always\n // hold the last one and name the wrong page for the one that actually failed.\n throw new PageFailure(pageUrl, err)\n }\n }\n\n // The url the report will name for this page, so a message and the context beside it cannot\n // disagree about which request failed when an interceptor rewrote it on the way out.\n const asSent = (res: AxiosResponse, pageUrl: string) =>\n requestedUrl(client, pageUrl, options, undefined, res.config)\n\n // Every page is read the same way, the ones after the first included: a 2xx with no body at\n // all has broken its contract, wherever in the sequence it sits -- a server that fails on\n // page three is not \"finished\". The 204 branch is defensive rather than expected: these\n // backends answer an empty list as a 200 with a body, so a list never arrives as a 204, and\n // the branch is here for an endpoint that one day does.\n\n const readPage = (res: AxiosResponse, pageUrl: string) => {\n if (!isValidHTTPStatus(res.status)) {\n throw new PageFailure(pageUrl, new AnzuApiResponseCodeError(res.status), res.config)\n }\n if (res.status === HTTP_STATUS_NO_CONTENT) {\n return {\n items: [] as T[],\n mode: 'unknown' as const,\n totalCount: 0,\n hasNextPage: false,\n countIsBound: false,\n empty: true,\n }\n }\n if (!hasBody(res)) {\n throw new PageFailure(\n pageUrl,\n new AnzuApiResponseCodeError(\n res.status,\n undefined,\n 'Expected a response body, url: ' + asSent(res, pageUrl)\n ),\n res.config\n )\n }\n\n let body\n try {\n body = readListBody<T>(res.data, res.status, asSent(res, pageUrl))\n } catch (err: unknown) {\n throw new PageFailure(pageUrl, err, res.config)\n }\n\n return {\n items: body.items,\n mode: body.mode,\n totalCount: body.pagination.totalCount ?? 0,\n hasNextPage: body.pagination.hasNextPage === true,\n countIsBound: body.countIsBound,\n empty: false,\n }\n }\n\n const firstPage = await get(pagination)\n const first = readPage(firstPage.res, firstPage.pageUrl)\n const results: T[] = [...first.items]\n if (first.empty) return results\n\n // A first page shorter than the one asked for means the offsets cannot be trusted: every page\n // after it is addressed by arithmetic on a page size, and the server has just shown that the\n // size it uses is not the size requested. Read as the server's real page size -- a capped\n // `limit`, which backends do -- and taken from page one, which has already answered.\n //\n // Before the branch, not inside it: both ways of walking a list address their pages this way,\n // and hardening one of them is how the counted branch was fixed while the infinite one went\n // on losing rows.\n //\n // It is a reading, not a fact. The other reading would be a server that applies `limit` and\n // then drops rows -- permissions, soft deletes -- while counting `totalCount` before that:\n // its window really is the requested size, so stepping by the shorter one overlaps and rows\n // repeat at each boundary. A sweep of the shared path found no such dropping, so the reading\n // taken here is the one these backends support; the trade is deliberate anyway, because\n // repeated rows are visible to whoever reads the list and missing ones are not.\n //\n // What the ambiguity does buy is the caution below: once the arithmetic is in doubt, the\n // pages are walked one at a time rather than fired off together, so a wrong guess costs\n // requests one by one rather than a burst of them, and stops at the first empty page.\n const capped = first.items.length > 0 && first.items.length < pagination.value.rowsPerPage\n if (capped) pagination.value.rowsPerPage = first.items.length\n\n // Infinite lists walk page by page, because only the answer says whether another exists.\n if (first.mode === 'infinite') {\n let hasNextPage = first.hasNextPage\n while (hasNextPage) {\n pagination.value.page++\n const nextPage = await get(pagination)\n const next = readPage(nextPage.res, nextPage.pageUrl)\n results.push(...next.items)\n if (next.empty) break\n hasNextPage = next.hasNextPage\n }\n\n return results\n }\n\n // A list that named neither mode has said nothing about further pages, so the first is all\n // there is.\n if (first.mode !== 'counted') return results\n\n // Two ways the count cannot be divided into a page count. The server may have said so itself\n // -- `bigTable`, which is on by default, makes `totalCount` a look-ahead of `offset + limit + 1`\n // rather than a total, corrected to the real number only on the page that comes back short.\n // Dividing that by the page size answers 2, whatever the list's real length: a batch over\n // 5000 rows would return 200 of them and resolve as though it were finished. Or the page size\n // itself had to be guessed, in which case the count divides by a number the server has\n // already contradicted.\n //\n // Either way the walk is sequential and ends where the data does -- a page shorter than the\n // one asked for is the server saying there is no more, and it is the same signal the\n // repository uses to correct the count in the first place.\n // Already holding as many rows as the count names, so there is nothing to walk to. This is\n // also what makes a short first page unambiguous: the repository corrects `totalCount` to the\n // real total exactly when a page comes back shorter than the limit it applied, so a list that\n // simply ends satisfies this, while a server that capped the limit reports its look-ahead\n // instead and does not.\n if (results.length >= first.totalCount) return results\n\n if (capped || first.countIsBound) {\n for (;;) {\n pagination.value.page++\n const nextPage = await get(pagination)\n const next = readPage(nextPage.res, nextPage.pageUrl)\n results.push(...next.items)\n if (next.items.length < pagination.value.rowsPerPage) break\n }\n\n return results\n }\n\n // Counted lists know how many pages there are, so the rest go out together.\n const pageCount = Math.ceil(first.totalCount / pagination.value.rowsPerPage)\n if (pageCount <= 1) return results\n\n // Each page is read as it arrives, not after all of them have. Reading them afterwards meant\n // a page that answered with a broken body was not noticed until every sibling had settled --\n // and a sibling that never answers made that never, so the call hung on a failure it was\n // already holding. Rejecting here reaches `run`, which stops the siblings.\n const rest = await Promise.all(\n Array.from({ length: pageCount - 1 }, (_unused, index) =>\n get(ref({ ...pagination.value, page: index + 2 })).then((page) => readPage(page.res, page.pageUrl))\n )\n )\n // A 204 among them contributes nothing and the others are kept -- there is no sequence to\n // stop here, they all went out at once.\n rest.forEach((page) => results.push(...page.items))\n\n return results\n }, signal)\n } catch (err: unknown) {\n const failed = err instanceof PageFailure ? err : null\n // Only a page can name a url here, and it names its own. The base url is not an answer: it was\n // never requested, and a failure that belongs to no page -- a missing template, a filter config\n // that will not render -- happened before any page existed. Nothing else can fail after a page\n // has gone out, because everything from the dispatch onwards is tagged as a `PageFailure`.\n const context = {\n system,\n entity,\n url: requestedUrl(client, failed?.pageUrl, options, failed?.failure ?? err, failed?.dispatched),\n }\n\n throw report(mapApiError(failed?.failure ?? err, context), context)\n }\n }\n\n return { execute, abort: abortable.abort, loading: abortable.loading }\n}\n\nexport type UseApiFetchListBatchReturnType<T> = {\n execute: (filterData: FilterData<any>, filterConfig: FilterConfig<any>, params?: FetchListBatchParams) => Promise<T[]>\n abort: () => void\n /**\n * True while this instance has a request in flight, false once the last one settles.\n *\n * It is here so a caller does not have to keep its own: the hand-rolled version is a ref cleared\n * in a `finally`, and that `finally` belongs to whichever call ended -- including one that\n * `cancelPrevious` just superseded, which turns the spinner off while the call the user is waiting\n * for is still running. This one never dips between a superseded call and the one that replaced it.\n */\n loading: Ref<boolean>\n}\n","import type { AxiosInstance } from 'axios'\nimport type { JobBase } from '@/types/Job'\nimport { stringToKebabCase } from '@/utils/string'\nimport { useApiFetchList } from '@/labs/api/useApiFetchList'\nimport { useApiCommand, useApiRequest } from '@/labs/api/useApiRequest'\n\nconst END_POINT = '/adm/v1/job'\nexport const ENTITY = 'job'\n\nexport function useJobApi<JobType extends JobBase = JobBase>(client: () => AxiosInstance, system: string) {\n const useFetchJobList = () => useApiFetchList<JobType>({ client, system, entity: ENTITY, urlTemplate: END_POINT })\n\n // Each of these builds its own request rather than sharing one: `useApiRequest` keeps a set of\n // abort controllers per instance, so a shared instance would put every caller of that instance in\n // one abort scope. It matters for the two `jobActions.ts` that call this at module scope and hold\n // `fetchJob` across the whole app; the other 25 consumers are `<script setup>`, one instance each.\n // They stay bound functions instead of being handed out as factories -- no caller anywhere in the\n // fleet aborts a job request, and exposing the handle would move 27 call sites to pass around\n // something unused.\n const fetchJob = (id: number) => {\n const { execute } = useApiRequest<JobType, null>({\n client,\n method: 'GET',\n system,\n entity: ENTITY,\n urlTemplate: END_POINT + '/:id',\n })\n\n return execute({ urlParams: { id } })\n }\n\n const createJob = (data: JobType) => {\n const type = stringToKebabCase(data._resourceName)\n .slice(4) // remove \"job-\" prefix\n .replace('-kind-', '-kind/') // replace \"-kind-\" with \"-kind/\" if the needle is found\n\n const { execute } = useApiRequest<JobType, JobType>({\n client,\n method: 'POST',\n system,\n entity: ENTITY,\n urlTemplate: END_POINT + '/:type',\n })\n\n return execute({ urlParams: { type }, body: data })\n }\n\n const deleteJob = (id: number) => {\n const { execute } = useApiCommand({\n client,\n method: 'DELETE',\n system,\n entity: ENTITY,\n urlTemplate: END_POINT + '/:id',\n })\n\n return execute({ urlParams: { id } })\n }\n\n return {\n useFetchJobList,\n fetchJob,\n createJob,\n deleteJob,\n }\n}\n","import {\n type ColumnConfig,\n type ColumnInternalValues,\n DATETIME_AUTO_LABEL_TRACKING,\n type StoredData,\n} from '@/composables/system/datatableColumns'\nimport { computed, onMounted, type Ref, watch } from 'vue'\nimport { i18n } from '@/plugins/i18n'\nimport { isArray, isBoolean, isObject, isString, isUndefined } from '@/utils/common'\n\nconst defaultColumn: ColumnInternalValues = {\n key: '',\n title: undefined,\n sortable: false,\n fixed: false,\n}\n\ninterface DatatableColumnsConfigMoreOptions {\n storeColumnsLocalStorage: string | boolean // false to disable, string to override store key\n disableActions: boolean\n customI18n: any\n showExpand: boolean\n}\n\nconst DatatableColumnsConfigMoreOptionsDefault = {\n storeColumnsLocalStorage: true,\n disableActions: false,\n customI18n: undefined,\n showExpand: false,\n}\n\nexport function createDatatableColumnsConfig(\n config: ColumnConfig[],\n columnsHidden: Ref<Array<string>>,\n system: string,\n subject: string,\n moreOptions: Partial<DatatableColumnsConfigMoreOptions> = {}\n) {\n const options = { ...DatatableColumnsConfigMoreOptionsDefault, ...moreOptions }\n const localI18n = options.customI18n ?? i18n\n const { t } = localI18n.global || localI18n\n\n let storeKey: undefined | string = undefined\n if (isString(options.storeColumnsLocalStorage)) {\n storeKey = options.storeColumnsLocalStorage\n } else if (isBoolean(options.storeColumnsLocalStorage) && true === options.storeColumnsLocalStorage) {\n storeKey = 'table_' + system + '_' + subject\n }\n\n const columnsAll = config.map((item) => {\n const obj = { ...defaultColumn, ...item }\n if (!isUndefined(obj.title)) {\n // do not modify\n } else if (isUndefined(obj.title) && DATETIME_AUTO_LABEL_TRACKING.includes(obj.key)) {\n obj.title = t('common.model.tracking.' + obj.key)\n } else if (isUndefined(obj.title) && system && subject) {\n obj.title = t(system + '.' + subject + '.model.' + obj.key)\n } else {\n obj.title = ''\n }\n return obj\n })\n\n const columnsVisible = computed(() => {\n const columns: any = []\n if (options.showExpand) columns.push({ key: 'data-table-expand', sortable: false })\n columnsAll.forEach((column) => {\n if (!columnsHidden.value.includes(column.key)) {\n columns.push(column)\n }\n })\n if (!options.disableActions) columns.push({ key: 'actions', sortable: false, fixed: 'end' })\n return columns\n })\n\n const loadStoredColumns = () => {\n if (!storeKey || !localStorage) return\n const stored = localStorage.getItem(storeKey)\n if (!stored) return\n // A corrupted entry would otherwise throw inside onMounted and take the whole table with it.\n let storedData: StoredData\n try {\n storedData = JSON.parse(stored) as StoredData\n } catch {\n localStorage.removeItem(storeKey)\n return\n }\n if (!isObject(storedData)) return\n if (!isArray(storedData.hidden)) return\n columnsHidden.value = storedData.hidden as string[]\n }\n\n const storeColumns = (columns: string[]) => {\n if (!storeKey || !localStorage) return\n localStorage.setItem(storeKey, JSON.stringify({ hidden: columns }))\n }\n\n onMounted(() => {\n loadStoredColumns()\n })\n\n watch(columnsHidden, (newValue) => {\n storeColumns(newValue)\n })\n\n return {\n columnsAll,\n columnsVisible,\n columnsHidden,\n }\n}\n","import type { AxiosClientFn } from '@/labs/api/client'\nimport { useApiFetchList } from '@/labs/api/useApiFetchList'\nimport { useApiRequest } from '@/labs/api/useApiRequest'\nimport type { LogPaths, LogTypeType } from '@/labs/log/logType'\nimport type { Log } from '@/types/Log'\n\nexport const LOG_ENTITY = 'log'\n\ninterface LogApiParams {\n client: AxiosClientFn\n system: string\n logPaths: LogPaths\n type: LogTypeType\n}\n\n/**\n * The backend and the endpoint both come from the caller, so these cannot be hoisted the way an\n * entity fetcher is -- every mount builds its own, which is also what gives each one its own\n * abort controller set.\n */\nexport const useFetchLogList = ({ client, system, logPaths, type }: LogApiParams) =>\n useApiFetchList<Log>({\n client,\n system,\n entity: LOG_ENTITY,\n urlTemplate: logPaths[type],\n })\n\nexport const useFetchLog = ({ client, system, logPaths, type }: LogApiParams) =>\n useApiRequest<Log, null>({\n client,\n method: 'GET',\n system,\n entity: LOG_ENTITY,\n urlTemplate: logPaths[type] + '/:id',\n })\n","import { ref, type Ref } from 'vue'\nimport { useAlerts } from '@/composables/system/alerts'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport type { FilterConfig, FilterData } from '@/labs/filters/filterFactory'\nimport type { Pagination } from '@/labs/filters/pagination'\nimport { useFetchLog, useFetchLogList } from '@/labs/log/logApi'\nimport type { LogPaths, LogTypeType } from '@/labs/log/logType'\nimport type { Log } from '@/types/Log'\n\ninterface LogActionsParams {\n client: AxiosClientFn\n system: string\n logPaths: LogPaths\n type: LogTypeType\n /**\n * Lets an ancestor own the flag, the way `usePagination` takes an external pagination ref --\n * the card that shows the spinner sits above the table that does the fetching.\n */\n loading?: Ref<boolean>\n}\n\n/**\n * Everything here is per instance, unlike the module-level refs the admins used to keep: the\n * views are remounted on every system/type change, and a `finally` belonging to the instance\n * that just went away would otherwise clear the new one's loading flag.\n */\nexport function useLogListActions(params: LogActionsParams) {\n const { showErrorsDefault } = useAlerts()\n const { execute, abort } = useFetchLogList(params)\n\n const listItems = ref<Log[]>([])\n const listLoading = params.loading ?? ref(false)\n const datatableHiddenColumns = ref<Array<string>>([])\n\n // Aborting is not enough on its own: axios rejects a cancelled request with `CanceledError`,\n // which is an axios error rather than a `DOMException` named `AbortError`, so it slips past the\n // helper's swallow and arrives here as a normal failure. Without this token the user would get\n // an error alert for an action they took themselves.\n let generation = 0\n const isCurrent = (token: number) => token === generation\n\n const fetchList = async (\n pagination: Ref<Pagination>,\n filterData: FilterData<any>,\n filterConfig: FilterConfig<any>\n ) => {\n const token = ++generation\n listLoading.value = true\n // `execute` writes the counts into the ref it is handed, inside the call -- before\n // anything out here could decide to discard them. It gets a copy, and the counts are\n // committed to the real one only if this call is still the current one.\n const scratch = ref<Pagination>({ ...pagination.value })\n try {\n const items = await execute(scratch, filterData, filterConfig)\n if (!isCurrent(token)) return\n listItems.value = items\n // Reading the scratch ref is the whole point -- these are the counts the helper just wrote\n // into it, and this is the one moment they are known to belong to the current request.\n /* eslint-disable vue/no-ref-object-reactivity-loss */\n pagination.value = {\n ...pagination.value,\n hasNextPage: scratch.value.hasNextPage,\n currentViewCount: scratch.value.currentViewCount,\n totalCount: scratch.value.totalCount,\n }\n /* eslint-enable vue/no-ref-object-reactivity-loss */\n } catch (error) {\n if (!isCurrent(token)) return\n // A failed fetch must not leave the previous type's rows on screen under the new url.\n listItems.value = []\n showErrorsDefault(error)\n } finally {\n if (isCurrent(token)) listLoading.value = false\n }\n }\n\n /** Invalidates whatever is in flight, then cancels it. Order matters: the token first. */\n const cancel = () => {\n generation++\n abort()\n }\n\n return {\n listItems,\n listLoading,\n datatableHiddenColumns,\n fetchList,\n cancel,\n }\n}\n\nexport function useLogDetailActions(params: LogActionsParams) {\n const { showErrorsDefault } = useAlerts()\n const { execute, abort } = useFetchLog(params)\n\n const log = ref<Log | null>(null)\n const detailLoading = params.loading ?? ref(false)\n\n let generation = 0\n const isCurrent = (token: number) => token === generation\n\n const fetchData = async (id: string) => {\n const token = ++generation\n detailLoading.value = true\n try {\n const res = await execute({ urlParams: { id } })\n if (!isCurrent(token)) return\n log.value = res\n } catch (error) {\n if (!isCurrent(token)) return\n log.value = null\n showErrorsDefault(error)\n } finally {\n if (isCurrent(token)) detailLoading.value = false\n }\n }\n\n const cancel = () => {\n generation++\n abort()\n }\n\n return {\n log,\n detailLoading,\n fetchData,\n cancel,\n }\n}\n","import { ref } from 'vue'\nimport type { ValueObjectOption } from '@/types/ValueObject'\n\n/**\n * The two log stores every anzu backend exposes. This is a runtime value on purpose: the router\n * guard validates the `:type` path segment before any component mounts, and a bare TypeScript\n * type is erased by then. It is the single source of the valid set -- the dropdown iterates the\n * same object the guard checks against, so the two cannot drift apart.\n */\nexport const LogType = {\n App: 'app',\n Audit: 'audit',\n} as const\nexport type LogTypeType = (typeof LogType)[keyof typeof LogType]\nexport const LogTypeDefault = LogType.App\n\nexport const isLogType = (value: unknown): value is LogTypeType => Object.values(LogType).includes(value as LogTypeType)\n\n/**\n * Full paths per type, not a segment appended to a shared prefix: brick exposes its app log at\n * `/adm/v1/log/journal` rather than `/adm/v1/log/app`, and nothing says the next backend will\n * differ in the same place.\n */\nexport type LogPaths = Record<LogTypeType, string>\n\nexport const DEFAULT_LOG_PATHS: LogPaths = {\n app: '/adm/v1/log/app',\n audit: '/adm/v1/log/audit',\n}\n\nexport function useLogType() {\n // Raw `app` / `audit` titles, as every admin shows them today. `common.log.filter.type` is the\n // select's own label, not a per-option name -- one translation cannot title two options.\n const logTypeOptions = ref<ValueObjectOption<LogTypeType>[]>(\n Object.values(LogType).map((value) => ({ value, title: value }))\n )\n\n const getLogTypeOption = (value: LogTypeType) => {\n return logTypeOptions.value.find((item) => item.value === value)\n }\n\n return {\n logTypeOptions,\n getLogTypeOption,\n }\n}\n","<script lang=\"ts\" setup>\nimport { inject, onMounted, onBeforeUnmount, provide, watch } from 'vue'\nimport type { RouteLocationRaw } from 'vue-router'\nimport { useDebounceFn } from '@vueuse/core'\nimport ACopyText from '@/components/ACopyText.vue'\nimport ADatatableConfigButton from '@/components/ADatatableConfigButton.vue'\nimport ADatetime from '@/components/datetime/ADatetime.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport ADatatablePagination from '@/labs/filters/ADatatablePagination.vue'\nimport { createDatatableColumnsConfig } from '@/labs/filters/datatableColumns'\nimport { DatatablePaginationKey, FilterConfigKey, FilterDataKey } from '@/labs/filters/filterInjectionKeys'\nimport { useFilterHelpers } from '@/labs/filters/filterFactory'\nimport { usePagination } from '@/labs/filters/pagination'\nimport { SORT_BY_ID } from '@/composables/system/datatableColumns'\nimport { useLogListActions } from '@/labs/log/logActions'\nimport { LOG_ENTITY } from '@/labs/log/logApi'\nimport { LogType, type LogPaths, type LogTypeType } from '@/labs/log/logType'\nimport type { Log } from '@/types/Log'\nimport { isNull, isUndefined } from '@/utils/common'\n\ntype DatatableItem = Log\n\nconst props = defineProps<{\n client: AxiosClientFn\n system: string\n type: LogTypeType\n logPaths: LogPaths\n /** Same mapper the view uses. Needed here so the actions cell stays a real link. */\n detailRoute: (log: Log) => RouteLocationRaw\n}>()\n\nconst emit = defineEmits<{\n (e: 'rowClick', log: Log): void\n}>()\n\n// The spinner belongs to the card above this table, so the flag is owned there.\nconst loading = defineModel<boolean>('loading', { default: false })\n\nconst filterConfig = inject(FilterConfigKey)\nconst filterData = inject(FilterDataKey)\nif (isUndefined(filterConfig) || isUndefined(filterData)) {\n throw new Error('[ALogDatatable] Incorrect provide/inject config.')\n}\n\n// Read once on purpose. Every one of these is fixed for the lifetime of this instance: the page\n// remounts the whole view through a `:key` when the system or the type changes, precisely because\n// `useApiFetchList` closes over its client and url, and reacting here could not undo that.\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { fetchList, listItems, datatableHiddenColumns, cancel } = useLogListActions({\n client: props.client,\n system: props.system,\n logPaths: props.logPaths,\n type: props.type,\n loading,\n})\n\n// Neither the url hash nor local storage: the filter is kept per view in memory by the registry\n// in `logFilter`, and nothing about a log listing is worth persisting across a reload.\nconst { resetFilter, submitFilter, loadStoredFilters } = useFilterHelpers(filterData, filterConfig, {\n storeFiltersLocalStorage: false,\n populateUrlParams: false,\n})\n\nconst { pagination } = usePagination(SORT_BY_ID)\nprovide(DatatablePaginationKey, pagination)\n\n// Keyed by type, not by system: the sets differ between app and audit, and a column hidden in\n// audit must not disappear from the app table.\nconst { columnsVisible, columnsAll, columnsHidden } = createDatatableColumnsConfig(\n [\n { key: 'id' },\n { key: 'datetime' },\n { key: 'levelName' },\n { key: 'message' },\n { key: 'context.appVersion' },\n { key: 'context.contextId' },\n ...(props.type === LogType.Audit\n ? [{ key: 'context.resourceName' }, { key: 'context.resourceIds' }]\n : [{ key: 'context.httpStatus' }]),\n { key: 'context.userId' },\n { key: 'context.ip' },\n ],\n datatableHiddenColumns,\n 'common',\n LOG_ENTITY,\n { storeColumnsLocalStorage: `table_common_log_${props.type}` }\n)\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst getList = useDebounceFn(async () => {\n await fetchList(pagination, filterData, filterConfig)\n})\n\nconst onRowClick = (_event: unknown, { item }: { item: DatatableItem }) => {\n if (item.id) emit('rowClick', item)\n}\n\nonMounted(() => {\n // Reads nothing back -- both persistences are off -- but it is what restores the page a user\n // was on when they close a detail, which `AActionCloseButton` flags for the next list.\n loadStoredFilters(pagination, getList)\n})\n\n// Everything above reads its props once, which is only correct while the page remounts this view\n// through a `:key`. Nothing would otherwise report a page that forgot: the table would keep\n// querying the old endpoint with the old columns under a new url, quietly.\nif (import.meta.env.DEV) {\n watch(\n () => [props.system, props.type],\n () => {\n console.error(\n '[ALogDatatable] `system` or `type` changed in place. This view must be remounted -- ' +\n 'give it :key=\"`${system}/${type}`\". The endpoint and columns are bound at mount.'\n )\n }\n )\n}\n\nonBeforeUnmount(() => {\n // Both, and in this order. `cancel()` only invalidates what is already in flight; a debounce\n // still pending would fire after unmount, bump the generation past that invalidation and start\n // a request the token would then consider current.\n getList.cancel()\n cancel()\n})\n\ndefineExpose({\n refresh: getList,\n resetFilter: () => resetFilter(pagination, getList),\n submitFilter: () => submitFilter(pagination, getList),\n})\n</script>\n\n<template>\n <div>\n <div class=\"d-flex align-center\">\n <VSpacer />\n <ADatatableConfigButton\n v-model:columns-hidden=\"columnsHidden\"\n :columns-all=\"columnsAll\"\n />\n </div>\n <VDataTableServer\n class=\"a-datatable\"\n :headers=\"columnsVisible\"\n :items=\"listItems\"\n :items-length=\"listItems.length\"\n item-value=\"id\"\n @click:row=\"onRowClick\"\n >\n <template #item.datetime=\"{ item }: { item: DatatableItem }\">\n <ADatetime :date-time=\"item.datetime\" />\n </template>\n <template #item.message=\"{ item }: { item: DatatableItem }\">\n <div class=\"line-clamp-2\">\n {{ item.message }}\n </div>\n </template>\n <template #item.context.resourceIds=\"{ item }: { item: DatatableItem }\">\n {{ item.context.resourceIds.join(', ') }}\n </template>\n <template #item.context.contextId=\"{ item }: { item: DatatableItem }\">\n <ACopyText :value=\"item.context.contextId\" />\n </template>\n <template #item.context.userId=\"{ item }: { item: DatatableItem }\">\n <ACopyText\n v-if=\"!isNull(item.context.userId)\"\n :value=\"item.context.userId\"\n />\n </template>\n <template #item.actions=\"{ item }: { item: DatatableItem }\">\n <div class=\"d-flex justify-end\">\n <!--\n `:to`, not a click handler: an anchor is what makes middle-click and open-in-new-tab\n work on a list people open several entries from, and it is what the smoke spec scrapes\n to find a row id.\n -->\n <VBtn\n :to=\"detailRoute(item)\"\n class=\"ml-1\"\n icon=\"mdi-information-outline\"\n size=\"x-small\"\n variant=\"text\"\n />\n </div>\n </template>\n <template #bottom>\n <ADatatablePagination @change=\"getList\" />\n </template>\n </VDataTableServer>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { inject, onMounted, onBeforeUnmount, provide, watch } from 'vue'\nimport type { RouteLocationRaw } from 'vue-router'\nimport { useDebounceFn } from '@vueuse/core'\nimport ACopyText from '@/components/ACopyText.vue'\nimport ADatatableConfigButton from '@/components/ADatatableConfigButton.vue'\nimport ADatetime from '@/components/datetime/ADatetime.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport ADatatablePagination from '@/labs/filters/ADatatablePagination.vue'\nimport { createDatatableColumnsConfig } from '@/labs/filters/datatableColumns'\nimport { DatatablePaginationKey, FilterConfigKey, FilterDataKey } from '@/labs/filters/filterInjectionKeys'\nimport { useFilterHelpers } from '@/labs/filters/filterFactory'\nimport { usePagination } from '@/labs/filters/pagination'\nimport { SORT_BY_ID } from '@/composables/system/datatableColumns'\nimport { useLogListActions } from '@/labs/log/logActions'\nimport { LOG_ENTITY } from '@/labs/log/logApi'\nimport { LogType, type LogPaths, type LogTypeType } from '@/labs/log/logType'\nimport type { Log } from '@/types/Log'\nimport { isNull, isUndefined } from '@/utils/common'\n\ntype DatatableItem = Log\n\nconst props = defineProps<{\n client: AxiosClientFn\n system: string\n type: LogTypeType\n logPaths: LogPaths\n /** Same mapper the view uses. Needed here so the actions cell stays a real link. */\n detailRoute: (log: Log) => RouteLocationRaw\n}>()\n\nconst emit = defineEmits<{\n (e: 'rowClick', log: Log): void\n}>()\n\n// The spinner belongs to the card above this table, so the flag is owned there.\nconst loading = defineModel<boolean>('loading', { default: false })\n\nconst filterConfig = inject(FilterConfigKey)\nconst filterData = inject(FilterDataKey)\nif (isUndefined(filterConfig) || isUndefined(filterData)) {\n throw new Error('[ALogDatatable] Incorrect provide/inject config.')\n}\n\n// Read once on purpose. Every one of these is fixed for the lifetime of this instance: the page\n// remounts the whole view through a `:key` when the system or the type changes, precisely because\n// `useApiFetchList` closes over its client and url, and reacting here could not undo that.\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { fetchList, listItems, datatableHiddenColumns, cancel } = useLogListActions({\n client: props.client,\n system: props.system,\n logPaths: props.logPaths,\n type: props.type,\n loading,\n})\n\n// Neither the url hash nor local storage: the filter is kept per view in memory by the registry\n// in `logFilter`, and nothing about a log listing is worth persisting across a reload.\nconst { resetFilter, submitFilter, loadStoredFilters } = useFilterHelpers(filterData, filterConfig, {\n storeFiltersLocalStorage: false,\n populateUrlParams: false,\n})\n\nconst { pagination } = usePagination(SORT_BY_ID)\nprovide(DatatablePaginationKey, pagination)\n\n// Keyed by type, not by system: the sets differ between app and audit, and a column hidden in\n// audit must not disappear from the app table.\nconst { columnsVisible, columnsAll, columnsHidden } = createDatatableColumnsConfig(\n [\n { key: 'id' },\n { key: 'datetime' },\n { key: 'levelName' },\n { key: 'message' },\n { key: 'context.appVersion' },\n { key: 'context.contextId' },\n ...(props.type === LogType.Audit\n ? [{ key: 'context.resourceName' }, { key: 'context.resourceIds' }]\n : [{ key: 'context.httpStatus' }]),\n { key: 'context.userId' },\n { key: 'context.ip' },\n ],\n datatableHiddenColumns,\n 'common',\n LOG_ENTITY,\n { storeColumnsLocalStorage: `table_common_log_${props.type}` }\n)\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst getList = useDebounceFn(async () => {\n await fetchList(pagination, filterData, filterConfig)\n})\n\nconst onRowClick = (_event: unknown, { item }: { item: DatatableItem }) => {\n if (item.id) emit('rowClick', item)\n}\n\nonMounted(() => {\n // Reads nothing back -- both persistences are off -- but it is what restores the page a user\n // was on when they close a detail, which `AActionCloseButton` flags for the next list.\n loadStoredFilters(pagination, getList)\n})\n\n// Everything above reads its props once, which is only correct while the page remounts this view\n// through a `:key`. Nothing would otherwise report a page that forgot: the table would keep\n// querying the old endpoint with the old columns under a new url, quietly.\nif (import.meta.env.DEV) {\n watch(\n () => [props.system, props.type],\n () => {\n console.error(\n '[ALogDatatable] `system` or `type` changed in place. This view must be remounted -- ' +\n 'give it :key=\"`${system}/${type}`\". The endpoint and columns are bound at mount.'\n )\n }\n )\n}\n\nonBeforeUnmount(() => {\n // Both, and in this order. `cancel()` only invalidates what is already in flight; a debounce\n // still pending would fire after unmount, bump the generation past that invalidation and start\n // a request the token would then consider current.\n getList.cancel()\n cancel()\n})\n\ndefineExpose({\n refresh: getList,\n resetFilter: () => resetFilter(pagination, getList),\n submitFilter: () => submitFilter(pagination, getList),\n})\n</script>\n\n<template>\n <div>\n <div class=\"d-flex align-center\">\n <VSpacer />\n <ADatatableConfigButton\n v-model:columns-hidden=\"columnsHidden\"\n :columns-all=\"columnsAll\"\n />\n </div>\n <VDataTableServer\n class=\"a-datatable\"\n :headers=\"columnsVisible\"\n :items=\"listItems\"\n :items-length=\"listItems.length\"\n item-value=\"id\"\n @click:row=\"onRowClick\"\n >\n <template #item.datetime=\"{ item }: { item: DatatableItem }\">\n <ADatetime :date-time=\"item.datetime\" />\n </template>\n <template #item.message=\"{ item }: { item: DatatableItem }\">\n <div class=\"line-clamp-2\">\n {{ item.message }}\n </div>\n </template>\n <template #item.context.resourceIds=\"{ item }: { item: DatatableItem }\">\n {{ item.context.resourceIds.join(', ') }}\n </template>\n <template #item.context.contextId=\"{ item }: { item: DatatableItem }\">\n <ACopyText :value=\"item.context.contextId\" />\n </template>\n <template #item.context.userId=\"{ item }: { item: DatatableItem }\">\n <ACopyText\n v-if=\"!isNull(item.context.userId)\"\n :value=\"item.context.userId\"\n />\n </template>\n <template #item.actions=\"{ item }: { item: DatatableItem }\">\n <div class=\"d-flex justify-end\">\n <!--\n `:to`, not a click handler: an anchor is what makes middle-click and open-in-new-tab\n work on a list people open several entries from, and it is what the smoke spec scrapes\n to find a row id.\n -->\n <VBtn\n :to=\"detailRoute(item)\"\n class=\"ml-1\"\n icon=\"mdi-information-outline\"\n size=\"x-small\"\n variant=\"text\"\n />\n </div>\n </template>\n <template #bottom>\n <ADatatablePagination @change=\"getList\" />\n </template>\n </VDataTableServer>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { useI18n } from 'vue-i18n'\nimport AFilterString from '@/labs/filters/AFilterString.vue'\nimport AFilterTimeInterval from '@/labs/filters/AFilterTimeInterval.vue'\nimport AFilterValueObjectOptionsSelect from '@/labs/filters/AFilterValueObjectOptionsSelect.vue'\nimport AFilterWrapper from '@/labs/filters/AFilterWrapper.vue'\nimport type { TimeIntervalToolsValue } from '@/labs/filters/filterTimeIntervalTools'\nimport { type LogTypeType, useLogType } from '@/labs/log/logType'\nimport { useLogLevel } from '@/model/valueObject/LogLevel'\n\ndefineProps<{\n type: LogTypeType\n allowedTimeIntervals?: TimeIntervalToolsValue[] | undefined\n}>()\n\nconst emit = defineEmits<{\n (e: 'submit'): void\n (e: 'reset'): void\n (e: 'changeType', type: LogTypeType): void\n}>()\n\nconst { t } = useI18n()\nconst { logLevelOptions } = useLogLevel()\nconst { logTypeOptions } = useLogType()\n</script>\n\n<template>\n <AFilterWrapper\n enable-top\n @submit=\"emit('submit')\"\n @reset=\"emit('reset')\"\n >\n <template #top>\n <VRow class=\"align-start\">\n <VCol\n class=\"pb-0\"\n cols=\"12\"\n sm=\"4\"\n >\n <!--\n A plain select, not `AFilterValueObjectOptionsSelect`: that one throws unless the name\n it is given is a live filter field, and the type is not one. It picks the endpoint and\n lives in the route, which is also what makes it survive a filter reset.\n -->\n <VSelect\n :model-value=\"type\"\n :items=\"logTypeOptions\"\n :label=\"t('common.log.filter.type')\"\n item-title=\"title\"\n item-value=\"value\"\n density=\"compact\"\n variant=\"outlined\"\n hide-details\n data-cy=\"filter-log-type\"\n @update:model-value=\"emit('changeType', $event)\"\n />\n </VCol>\n </VRow>\n <VDivider class=\"mb-4 mt-4\" />\n </template>\n <template #search>\n <AFilterString\n name=\"contextId\"\n class=\"mr-2\"\n />\n <AFilterString\n name=\"message\"\n class=\"mr-2\"\n />\n <AFilterTimeInterval\n name-from=\"datetimeFrom\"\n name-until=\"datetimeTo\"\n :allowed=\"allowedTimeIntervals\"\n />\n </template>\n <template #item.levelName>\n <AFilterValueObjectOptionsSelect\n name=\"levelName\"\n :items=\"logLevelOptions\"\n />\n </template>\n </AFilterWrapper>\n</template>\n","<script lang=\"ts\" setup>\nimport { useI18n } from 'vue-i18n'\nimport AFilterString from '@/labs/filters/AFilterString.vue'\nimport AFilterTimeInterval from '@/labs/filters/AFilterTimeInterval.vue'\nimport AFilterValueObjectOptionsSelect from '@/labs/filters/AFilterValueObjectOptionsSelect.vue'\nimport AFilterWrapper from '@/labs/filters/AFilterWrapper.vue'\nimport type { TimeIntervalToolsValue } from '@/labs/filters/filterTimeIntervalTools'\nimport { type LogTypeType, useLogType } from '@/labs/log/logType'\nimport { useLogLevel } from '@/model/valueObject/LogLevel'\n\ndefineProps<{\n type: LogTypeType\n allowedTimeIntervals?: TimeIntervalToolsValue[] | undefined\n}>()\n\nconst emit = defineEmits<{\n (e: 'submit'): void\n (e: 'reset'): void\n (e: 'changeType', type: LogTypeType): void\n}>()\n\nconst { t } = useI18n()\nconst { logLevelOptions } = useLogLevel()\nconst { logTypeOptions } = useLogType()\n</script>\n\n<template>\n <AFilterWrapper\n enable-top\n @submit=\"emit('submit')\"\n @reset=\"emit('reset')\"\n >\n <template #top>\n <VRow class=\"align-start\">\n <VCol\n class=\"pb-0\"\n cols=\"12\"\n sm=\"4\"\n >\n <!--\n A plain select, not `AFilterValueObjectOptionsSelect`: that one throws unless the name\n it is given is a live filter field, and the type is not one. It picks the endpoint and\n lives in the route, which is also what makes it survive a filter reset.\n -->\n <VSelect\n :model-value=\"type\"\n :items=\"logTypeOptions\"\n :label=\"t('common.log.filter.type')\"\n item-title=\"title\"\n item-value=\"value\"\n density=\"compact\"\n variant=\"outlined\"\n hide-details\n data-cy=\"filter-log-type\"\n @update:model-value=\"emit('changeType', $event)\"\n />\n </VCol>\n </VRow>\n <VDivider class=\"mb-4 mt-4\" />\n </template>\n <template #search>\n <AFilterString\n name=\"contextId\"\n class=\"mr-2\"\n />\n <AFilterString\n name=\"message\"\n class=\"mr-2\"\n />\n <AFilterTimeInterval\n name-from=\"datetimeFrom\"\n name-until=\"datetimeTo\"\n :allowed=\"allowedTimeIntervals\"\n />\n </template>\n <template #item.levelName>\n <AFilterValueObjectOptionsSelect\n name=\"levelName\"\n :items=\"logLevelOptions\"\n />\n </template>\n </AFilterWrapper>\n</template>\n","import {\n createFilter,\n createFilterStore,\n type FilterConfig,\n type FilterData,\n type MakeFilterOption,\n} from '@/labs/filters/filterFactory'\nimport { LOG_ENTITY } from '@/labs/log/logApi'\nimport { LogType, type LogTypeType } from '@/labs/log/logType'\nimport type { DatetimeUTC } from '@/types/common'\nimport { isDefined } from '@/utils/common'\n\nexport interface LogTimeWindow {\n from: DatetimeUTC\n to: DatetimeUTC\n}\n\n/**\n * Fields that only make sense for the audit store. Switching to `app` hides them and keeps them\n * out of the query -- it never clears them, so a value typed here survives a round trip through\n * `app` and is still there when the user comes back.\n */\nconst AUDIT_ONLY_FIELDS = ['resourceName', 'resourceIds'] as const\n\n/**\n * A factory rather than a constant, because `defaultTimeWindow` cannot be expressed as a seeded\n * value: what makes the window always reach the query is `mandatory` plus `default` on the two\n * interval fields, and both are read out of this array when `createFilter` runs.\n */\nconst logFilterFields = (window: LogTimeWindow | undefined) =>\n [\n { name: 'levelName' as const, variant: 'in', default: [] },\n { name: 'id' as const, default: null, type: 'string' },\n // Both drawn by hand in the `#search` slot of `ALogFilter`; without `skip` they would appear\n // a second time in the \"more\" panel.\n {\n name: 'contextId' as const,\n apiName: 'context.contextId',\n default: null,\n type: 'string',\n render: { skip: true },\n },\n { name: 'message' as const, variant: 'startsWith', default: null, type: 'string', render: { skip: true } },\n {\n name: 'appVersion' as const,\n variant: 'startsWith',\n apiName: 'context.appVersion',\n default: null,\n type: 'string',\n },\n { name: 'userId' as const, apiName: 'context.userId', default: null, type: 'integer' },\n { name: 'resourceName' as const, apiName: 'context.resourceName', default: null, type: 'string' },\n {\n name: 'resourceIds' as const,\n apiName: 'context.resourceIds',\n variant: 'in',\n default: [],\n type: 'string',\n },\n // Both rendered by hand in the `#search` slot of `ALogFilter`; without `skip` they would be\n // drawn a second time in the \"more\" panel, where `FilterDetailItem` has no case for\n // `timeInterval` and falls through to `AFilterEmpty`, which prints the bare field name.\n {\n name: 'datetimeFrom' as const,\n apiName: 'datetime',\n type: 'timeInterval',\n related: 'datetimeTo',\n default: window?.from ?? null,\n mandatory: isDefined(window),\n render: { skip: true },\n },\n {\n name: 'datetimeTo' as const,\n apiName: 'datetime',\n type: 'timeInterval',\n exclude: true,\n default: window?.to ?? null,\n mandatory: isDefined(window),\n render: { skip: true },\n },\n ] satisfies readonly MakeFilterOption[]\n\ntype LogFilterFields = ReturnType<typeof logFilterFields>\n\nexport interface LogFilter {\n filterConfig: FilterConfig<LogFilterFields>\n filterData: FilterData<LogFilterFields>\n}\n\n/**\n * Keyed by system, so `/weather/logs` and `/brick/logs` cannot overwrite each other's values --\n * and deliberately *not* by type, so everything the two stores share (context id, level, time\n * range, user, message) survives a switch between app and audit. That carry-over is the whole\n * reason this is one view instead of two pages.\n *\n * Module level, so the values outlive the component: `createFilterStore` is only a factory, and\n * one created in `setup()` would die with the instance that made it.\n */\nconst registry = new Map<string, LogFilter>()\n\nexport function useLogFilter(system: string, window: LogTimeWindow | undefined): LogFilter {\n const existing = registry.get(system)\n if (existing) return existing\n\n const fields = logFilterFields(window)\n const created = createFilter(fields, createFilterStore(fields), {\n system: 'common',\n subject: LOG_ENTITY,\n })\n registry.set(system, created)\n\n return created\n}\n\n/**\n * Called on every mount, not only when the type changes. The registry hands back the same\n * `filterConfig` the previous visit left behind, so these flags still describe whichever type was\n * open last. Landing straight on an `app` url after an audit visit -- a link, a bookmark, F5 --\n * would otherwise send that visit's `resourceName` to the app endpoint.\n *\n * Three flags, not two -- `FiltersSelected` hides a chip on `render.selected`, so without the\n * third one the bar would advertise a filter that is not being applied, and its close button\n * would destroy the very value this approach exists to keep.\n */\nexport const applyLogTypeVisibility = (filter: LogFilter, type: LogTypeType): void => {\n const hide = type !== LogType.Audit\n\n AUDIT_ONLY_FIELDS.forEach((name) => {\n const field = filter.filterConfig.fields[name]\n field.exclude = hide\n field.render.skip = hide\n field.render.selected = !hide\n })\n}\n\n/** For tests and for anything that needs a clean slate; nothing in the views calls it. */\nexport const resetLogFilterRegistry = (): void => {\n registry.clear()\n}\n","<script lang=\"ts\" setup>\nimport { provide, ref, useTemplateRef } from 'vue'\nimport type { RouteLocationRaw } from 'vue-router'\nimport { useRouter } from 'vue-router'\nimport ACard from '@/components/ACard.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport { FilterConfigKey, FilterDataKey } from '@/labs/filters/filterInjectionKeys'\nimport type { TimeIntervalToolsValue } from '@/labs/filters/filterTimeIntervalTools'\nimport ALogDatatable from '@/labs/log/ALogDatatable.vue'\nimport ALogFilter from '@/labs/log/ALogFilter.vue'\nimport { applyLogTypeVisibility, type LogTimeWindow, useLogFilter } from '@/labs/log/logFilter'\nimport type { LogPaths, LogTypeType } from '@/labs/log/logType'\nimport type { Log } from '@/types/Log'\n\nconst props = defineProps<{\n /** Which backend to ask. */\n client: AxiosClientFn\n /** Identity used for api naming and error reporting -- `coreDam`, not `dam`, where they differ. */\n system: string\n /** From the route, never component state: it picks the endpoint and has to survive a filter reset. */\n type: LogTypeType\n /** Whole paths per type. Comes from the route-side descriptor, which the guard reads too. */\n logPaths: LogPaths\n /** Where a row goes. The library cannot know an app's typed route names, so the app maps it. */\n detailRoute: (log: Log) => RouteLocationRaw\n /** Where the app/audit dropdown goes. Same reason. */\n typeRoute: (type: LogTypeType) => RouteLocationRaw\n /** Mandatory time bound, as admin-dam has today. Omitted means no bound at all. */\n defaultTimeWindow?: LogTimeWindow | undefined\n /** `allowed` for the interval filter. Omitted means the library's full preset list. */\n allowedTimeIntervals?: TimeIntervalToolsValue[] | undefined\n}>()\n\nconst router = useRouter()\n\n// Read once: keyed remount, as in the datatable below.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss\nconst filter = useLogFilter(props.system, props.defaultTimeWindow)\n// Every mount, not only on a type change: the registry returns the config the last visit left,\n// so the flags still describe that visit's type. See `applyLogTypeVisibility`.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss\napplyLogTypeVisibility(filter, props.type)\n\nprovide(FilterConfigKey, filter.filterConfig)\nprovide(FilterDataKey, filter.filterData)\n\nconst datatable = useTemplateRef<InstanceType<typeof ALogDatatable>>('datatable')\nconst listLoading = ref(false)\n\nconst onRowClick = (log: Log) => {\n router.push(props.detailRoute(log))\n}\n\nconst onChangeType = (type: LogTypeType) => {\n if (type === props.type) return\n router.replace(props.typeRoute(type))\n}\n</script>\n\n<template>\n <ACard :loading=\"listLoading\">\n <VCardText>\n <ALogFilter\n :type=\"type\"\n :allowed-time-intervals=\"allowedTimeIntervals\"\n @submit=\"datatable?.submitFilter()\"\n @reset=\"datatable?.resetFilter()\"\n @change-type=\"onChangeType\"\n />\n <ALogDatatable\n ref=\"datatable\"\n v-model:loading=\"listLoading\"\n :client=\"client\"\n :system=\"system\"\n :type=\"type\"\n :log-paths=\"logPaths\"\n :detail-route=\"detailRoute\"\n @row-click=\"onRowClick\"\n />\n </VCardText>\n </ACard>\n</template>\n","<script lang=\"ts\" setup>\nimport { provide, ref, useTemplateRef } from 'vue'\nimport type { RouteLocationRaw } from 'vue-router'\nimport { useRouter } from 'vue-router'\nimport ACard from '@/components/ACard.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport { FilterConfigKey, FilterDataKey } from '@/labs/filters/filterInjectionKeys'\nimport type { TimeIntervalToolsValue } from '@/labs/filters/filterTimeIntervalTools'\nimport ALogDatatable from '@/labs/log/ALogDatatable.vue'\nimport ALogFilter from '@/labs/log/ALogFilter.vue'\nimport { applyLogTypeVisibility, type LogTimeWindow, useLogFilter } from '@/labs/log/logFilter'\nimport type { LogPaths, LogTypeType } from '@/labs/log/logType'\nimport type { Log } from '@/types/Log'\n\nconst props = defineProps<{\n /** Which backend to ask. */\n client: AxiosClientFn\n /** Identity used for api naming and error reporting -- `coreDam`, not `dam`, where they differ. */\n system: string\n /** From the route, never component state: it picks the endpoint and has to survive a filter reset. */\n type: LogTypeType\n /** Whole paths per type. Comes from the route-side descriptor, which the guard reads too. */\n logPaths: LogPaths\n /** Where a row goes. The library cannot know an app's typed route names, so the app maps it. */\n detailRoute: (log: Log) => RouteLocationRaw\n /** Where the app/audit dropdown goes. Same reason. */\n typeRoute: (type: LogTypeType) => RouteLocationRaw\n /** Mandatory time bound, as admin-dam has today. Omitted means no bound at all. */\n defaultTimeWindow?: LogTimeWindow | undefined\n /** `allowed` for the interval filter. Omitted means the library's full preset list. */\n allowedTimeIntervals?: TimeIntervalToolsValue[] | undefined\n}>()\n\nconst router = useRouter()\n\n// Read once: keyed remount, as in the datatable below.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss\nconst filter = useLogFilter(props.system, props.defaultTimeWindow)\n// Every mount, not only on a type change: the registry returns the config the last visit left,\n// so the flags still describe that visit's type. See `applyLogTypeVisibility`.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss\napplyLogTypeVisibility(filter, props.type)\n\nprovide(FilterConfigKey, filter.filterConfig)\nprovide(FilterDataKey, filter.filterData)\n\nconst datatable = useTemplateRef<InstanceType<typeof ALogDatatable>>('datatable')\nconst listLoading = ref(false)\n\nconst onRowClick = (log: Log) => {\n router.push(props.detailRoute(log))\n}\n\nconst onChangeType = (type: LogTypeType) => {\n if (type === props.type) return\n router.replace(props.typeRoute(type))\n}\n</script>\n\n<template>\n <ACard :loading=\"listLoading\">\n <VCardText>\n <ALogFilter\n :type=\"type\"\n :allowed-time-intervals=\"allowedTimeIntervals\"\n @submit=\"datatable?.submitFilter()\"\n @reset=\"datatable?.resetFilter()\"\n @change-type=\"onChangeType\"\n />\n <ALogDatatable\n ref=\"datatable\"\n v-model:loading=\"listLoading\"\n :client=\"client\"\n :system=\"system\"\n :type=\"type\"\n :log-paths=\"logPaths\"\n :detail-route=\"detailRoute\"\n @row-click=\"onRowClick\"\n />\n </VCardText>\n </ACard>\n</template>\n","/**\n * Pretty-prints a JSON string, and hands back whatever it was given when that is not what it is.\n * Log context carries request and response bodies that are usually but not always JSON.\n */\nexport const formatJson = (data: string): string => {\n try {\n return JSON.stringify(JSON.parse(data), null, 2)\n } catch {\n return data\n }\n}\n","<script lang=\"ts\" setup>\nimport { useI18n } from 'vue-i18n'\nimport ALogLevelChip from '@/components/log/ALogLevelChip.vue'\nimport type { Log } from '@/types/Log'\nimport { dateTimeFriendly } from '@/utils/datetime'\nimport { formatJson } from '@/utils/json'\n\ndefineProps<{\n log: Log\n}>()\n\nconst { t } = useI18n()\n</script>\n\n<template>\n <VRow>\n <VCol>\n <VRow>\n <VCol cols=\"3\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.id') }}\n </h4>\n {{ log.id }}\n </VCol>\n <VCol cols=\"3\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.levelName') }}\n </h4>\n <ALogLevelChip :level=\"log.levelName\" />\n </VCol>\n <VCol cols=\"2\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.userId') }}\n </h4>\n {{ log.context.userId }}\n </VCol>\n <VCol cols=\"2\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.appVersion') }}\n </h4>\n {{ log.context.appVersion }}\n </VCol>\n <VCol cols=\"2\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.requestOriginAppVersion') }}\n </h4>\n {{ log.context.requestOriginAppVersion }}\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"4\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.contextId') }}\n </h4>\n {{ log.context.contextId }}\n </VCol>\n <VCol cols=\"4\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.ip') }}\n </h4>\n {{ log.context.ip }}\n </VCol>\n <VCol cols=\"4\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.datetime') }}\n </h4>\n {{ dateTimeFriendly(log.datetime) }}\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"12\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.message') }}\n </h4>\n <pre>{{ log.message }}</pre>\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"6\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.path') }}\n </h4>\n {{ log.context.method }} {{ log.context.path }}\n </VCol>\n <VCol cols=\"6\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.httpStatus') }}\n </h4>\n {{ log.context.httpStatus }}\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"12\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.content') }}\n </h4>\n <pre>{{ formatJson(log.context.content ?? '') }}</pre>\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"12\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.response') }}\n </h4>\n <pre>{{ formatJson(log.context.response) }}</pre>\n </VCol>\n </VRow>\n </VCol>\n </VRow>\n</template>\n\n<style lang=\"scss\" scoped>\npre {\n overflow: auto;\n}\n</style>\n","<script lang=\"ts\" setup>\nimport { useI18n } from 'vue-i18n'\nimport ALogLevelChip from '@/components/log/ALogLevelChip.vue'\nimport type { Log } from '@/types/Log'\nimport { dateTimeFriendly } from '@/utils/datetime'\nimport { formatJson } from '@/utils/json'\n\ndefineProps<{\n log: Log\n}>()\n\nconst { t } = useI18n()\n</script>\n\n<template>\n <VRow>\n <VCol>\n <VRow>\n <VCol cols=\"3\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.id') }}\n </h4>\n {{ log.id }}\n </VCol>\n <VCol cols=\"3\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.levelName') }}\n </h4>\n <ALogLevelChip :level=\"log.levelName\" />\n </VCol>\n <VCol cols=\"2\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.userId') }}\n </h4>\n {{ log.context.userId }}\n </VCol>\n <VCol cols=\"2\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.appVersion') }}\n </h4>\n {{ log.context.appVersion }}\n </VCol>\n <VCol cols=\"2\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.requestOriginAppVersion') }}\n </h4>\n {{ log.context.requestOriginAppVersion }}\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"4\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.contextId') }}\n </h4>\n {{ log.context.contextId }}\n </VCol>\n <VCol cols=\"4\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.ip') }}\n </h4>\n {{ log.context.ip }}\n </VCol>\n <VCol cols=\"4\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.datetime') }}\n </h4>\n {{ dateTimeFriendly(log.datetime) }}\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"12\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.message') }}\n </h4>\n <pre>{{ log.message }}</pre>\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"6\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.path') }}\n </h4>\n {{ log.context.method }} {{ log.context.path }}\n </VCol>\n <VCol cols=\"6\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.httpStatus') }}\n </h4>\n {{ log.context.httpStatus }}\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"12\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.content') }}\n </h4>\n <pre>{{ formatJson(log.context.content ?? '') }}</pre>\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"12\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.response') }}\n </h4>\n <pre>{{ formatJson(log.context.response) }}</pre>\n </VCol>\n </VRow>\n </VCol>\n </VRow>\n</template>\n\n<style lang=\"scss\" scoped>\npre {\n overflow: auto;\n}\n</style>\n","<script lang=\"ts\" setup>\nimport { onBeforeUnmount, onMounted } from 'vue'\nimport ACard from '@/components/ACard.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport ALogDetail from '@/labs/log/ALogDetail.vue'\nimport { useLogDetailActions } from '@/labs/log/logActions'\nimport type { LogPaths, LogTypeType } from '@/labs/log/logType'\nimport { isNull } from '@/utils/common'\n\nconst props = defineProps<{\n client: AxiosClientFn\n system: string\n /** From the route. A log id means nothing without the store it came from. */\n type: LogTypeType\n id: string\n logPaths: LogPaths\n}>()\n\n// Read once: the page keys this view on `${system}/${type}/${id}`, so a change remounts it.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss\nconst { log, detailLoading, fetchData, cancel } = useLogDetailActions({\n client: props.client,\n system: props.system,\n logPaths: props.logPaths,\n type: props.type,\n})\n\nonMounted(() => {\n fetchData(props.id)\n})\n\nonBeforeUnmount(() => {\n cancel()\n})\n</script>\n\n<template>\n <ACard :loading=\"detailLoading\">\n <VCardText>\n <ALogDetail\n v-if=\"!isNull(log)\"\n :log=\"log\"\n />\n </VCardText>\n </ACard>\n</template>\n","<script lang=\"ts\" setup>\nimport { onBeforeUnmount, onMounted } from 'vue'\nimport ACard from '@/components/ACard.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport ALogDetail from '@/labs/log/ALogDetail.vue'\nimport { useLogDetailActions } from '@/labs/log/logActions'\nimport type { LogPaths, LogTypeType } from '@/labs/log/logType'\nimport { isNull } from '@/utils/common'\n\nconst props = defineProps<{\n client: AxiosClientFn\n system: string\n /** From the route. A log id means nothing without the store it came from. */\n type: LogTypeType\n id: string\n logPaths: LogPaths\n}>()\n\n// Read once: the page keys this view on `${system}/${type}/${id}`, so a change remounts it.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss\nconst { log, detailLoading, fetchData, cancel } = useLogDetailActions({\n client: props.client,\n system: props.system,\n logPaths: props.logPaths,\n type: props.type,\n})\n\nonMounted(() => {\n fetchData(props.id)\n})\n\nonBeforeUnmount(() => {\n cancel()\n})\n</script>\n\n<template>\n <ACard :loading=\"detailLoading\">\n <VCardText>\n <ALogDetail\n v-if=\"!isNull(log)\"\n :log=\"log\"\n />\n </VCardText>\n </ACard>\n</template>\n","import { type DatatableOrderingOption, type DatatableSortBy } from '@/composables/system/datatableColumns'\nimport type { Ref } from 'vue'\nimport { ref } from 'vue'\nimport { useAlerts } from '@/composables/system/alerts'\nimport { type FilterConfig, type FilterData, useFilterHelpers } from '@/labs/filters/filterFactory'\nimport { createDatatableColumnsConfig } from '@/labs/filters/datatableColumns'\nimport { type Pagination, usePagination } from '@/labs/filters/pagination'\nimport type { FetchListParams } from '@/labs/api/useApiFetchList'\nimport { useDebounceFn } from '@vueuse/core'\nimport { isNull } from '@/utils/common'\n\nexport function useSubjectSelect<TItem>(\n datatableConfig: any,\n datatableHiddenColumns: any,\n system: string,\n subject: string,\n execute: (\n pagination: Ref<Pagination>,\n filterData: FilterData<any>,\n filterConfig: FilterConfig<any>,\n params?: FetchListParams\n ) => Promise<TItem[]>,\n filterData: FilterData<any>,\n filterConfig: FilterConfig<any>,\n filterSortBy: DatatableSortBy | null = null,\n fetchParams: FetchListParams | undefined = undefined,\n enableActions: boolean = false\n) {\n const filterTouched: Ref<boolean> = ref(false)\n const items: Ref<Array<TItem>> = ref([])\n const selected: Ref<Array<TItem>> = ref([])\n const loading = ref(false)\n const { pagination, setSortBy, incrementPage } = usePagination(\n isNull(filterSortBy) ? null : filterSortBy.key,\n filterSortBy?.order\n )\n\n const { resetFilter, submitFilter } = useFilterHelpers(filterData, filterConfig, {\n storeFiltersLocalStorage: false,\n populateUrlParams: false,\n })\n const { showErrorsDefault } = useAlerts()\n\n const { columnsVisible, columnsAll, columnsHidden } = createDatatableColumnsConfig(\n datatableConfig,\n datatableHiddenColumns,\n system,\n subject,\n { storeColumnsLocalStorage: false, disableActions: !enableActions }\n )\n\n const onOpen = () => {\n resetState()\n getListDebounced()\n }\n\n const sortByChange = (option: DatatableOrderingOption) => {\n setSortBy(option.sortBy)\n getListDebounced()\n }\n\n const onFetchNextPage = async () => {\n loading.value = true\n incrementPage()\n try {\n const res = (await execute(pagination, filterData, filterConfig, fetchParams)) as TItem[]\n items.value.push(...res)\n } catch (e) {\n showErrorsDefault(e)\n } finally {\n loading.value = false\n }\n }\n\n const resetState = () => {\n items.value = []\n selected.value = []\n }\n\n const getList = async () => {\n loading.value = true\n try {\n items.value = (await execute(pagination, filterData, filterConfig, fetchParams)) as TItem[]\n } catch (e) {\n showErrorsDefault(e)\n } finally {\n loading.value = false\n }\n }\n\n const getListDebounced = useDebounceFn(async () => {\n await getList()\n })\n\n const onRowClick = (event: Event) => {\n const eventTarget = event.target as HTMLElement | null\n if (!eventTarget || (eventTarget.tagName === 'INPUT' && (eventTarget as HTMLInputElement).type === 'checkbox')) {\n return\n }\n const parent = eventTarget.closest('.v-data-table__tr')\n if (!parent || !parent.classList.contains('v-data-table__tr')) return\n const firstTd = parent.firstElementChild\n if (!firstTd || !firstTd.classList.contains('v-data-table__td')) return\n const input = firstTd.querySelector('input')\n if (!input) return\n input.click()\n }\n\n return {\n items,\n selected,\n pagination,\n columnsVisible,\n columnsHidden,\n columnsAll,\n filterTouched,\n loading,\n onRowClick,\n onFetchNextPage,\n onOpen,\n sortByChange,\n getList: getListDebounced,\n resetFilter: () => resetFilter(pagination, getListDebounced),\n submitFilter: () => submitFilter(pagination, getListDebounced),\n }\n}\n","<script lang=\"ts\" setup generic=\"TItem\">\nimport { computed, onMounted, provide, ref, toRaw, withModifiers } from 'vue'\nimport { isNull, isUndefined } from '@/utils/common'\nimport ADialogToolbar from '@/components/ADialogToolbar.vue'\nimport { useI18n } from 'vue-i18n'\nimport { useDisplay } from 'vuetify'\nimport type { Pagination } from '@/labs/filters/pagination'\nimport ADatatablePagination from '@/labs/filters/ADatatablePagination.vue'\nimport { SubjectSelectCloseSidebarKey } from '@/labs/filters/filterInjectionKeys'\n\nconst props = withDefaults(\n defineProps<{\n selectedItems: Array<TItem>\n pagination: Pagination\n loading?: boolean\n minCount?: number\n maxCount?: number\n modelValue?: boolean | undefined // dialog\n dialogTitleT?: string\n paginationMode?: 'standard' | 'more'\n autoOpen?: boolean\n }>(),\n {\n loading: false,\n minCount: 1,\n maxCount: 1,\n modelValue: undefined,\n dialogTitleT: 'common.subjectSelect.texts.title',\n paginationMode: 'standard',\n autoOpen: false,\n }\n)\nconst emit = defineEmits<{\n (e: 'update:modelValue', data: boolean): void\n (e: 'update:pagination', data: Pagination): void\n (e: 'onOpen'): void\n (e: 'onPageChange'): void\n (e: 'onConfirm', data: Array<TItem>): void\n (e: 'onFetchNextPage'): void\n}>()\n\nconst dialogLocal = ref(false)\nconst dialog = computed({\n get() {\n if (isUndefined(props.modelValue)) return dialogLocal.value\n return props.modelValue\n },\n set(newValue: boolean) {\n dialogLocal.value = newValue\n emit('update:modelValue', newValue)\n },\n})\n\nconst paginationComputed = computed({\n get: () => props.pagination,\n set: (newValue) => {\n emit('update:pagination', { ...toRaw(newValue) })\n },\n})\n\nconst { mdAndDown } = useDisplay()\nconst sidebarLeft = ref(!mdAndDown.value)\n\nconst closeSidebarOnMobile = () => {\n if (mdAndDown.value) sidebarLeft.value = false\n}\n\nprovide(SubjectSelectCloseSidebarKey, closeSidebarOnMobile)\n\nconst { t } = useI18n()\n\nconst selectedItemsCount = computed(() => {\n return props.selectedItems.length\n})\n\nconst disabledSubmit = computed(() => {\n return selectedItemsCount.value < props.minCount || selectedItemsCount.value > props.maxCount\n})\n\nconst onOpen = () => {\n emit('onOpen')\n sidebarLeft.value = !mdAndDown.value\n dialog.value = true\n}\n\nconst toggleSidebar = () => {\n sidebarLeft.value = !sidebarLeft.value\n}\n\nconst onClose = () => {\n dialog.value = false\n}\n\nconst onConfirm = () => {\n emit(\n 'onConfirm',\n props.selectedItems.map((item) => toRaw(item))\n )\n onClose()\n}\n\nconst fetchNextPage = () => {\n emit('onFetchNextPage')\n}\n\nconst onPageChange = () => {\n emit('onPageChange')\n}\n\nconst lastPage = computed(() => {\n return Math.ceil(paginationComputed.value.totalCount / paginationComputed.value.rowsPerPage)\n})\n\n// `>=`, not `===`: an empty list has `totalCount: 0`, so `lastPage` is 0 while `page` is never below\n// 1, and the two can never meet. The page would then count as \"not the last one\" on a list with\n// nothing in it -- and the same holds before any request has been made, because that initial state is\n// the same numbers.\nconst hasNextPage = computed(() => {\n return !(\n (isNull(paginationComputed.value.hasNextPage) && paginationComputed.value.page >= lastPage.value) ||\n paginationComputed.value.hasNextPage === false\n )\n})\n\nconst autoloadOnIntersect = (isIntersecting: boolean) => {\n if (isIntersecting && hasNextPage.value && !props.loading) {\n fetchNextPage()\n }\n}\n\nonMounted(() => {\n if (props.autoOpen) {\n onOpen()\n }\n})\n\ndefineExpose({\n open: onOpen,\n closeSidebarOnMobile,\n})\n</script>\n\n<template>\n <slot\n name=\"activator\"\n :props=\"{ onClick: withModifiers(() => onOpen(), ['stop']) }\"\n />\n <VDialog\n v-bind=\"$attrs\"\n :model-value=\"dialog\"\n fullscreen\n class=\"subject-select\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <VCard\n v-if=\"dialog\"\n class=\"subject-select__card\"\n >\n <ADialogToolbar\n class=\"subject-select__toolbar system-border-b\"\n @on-cancel=\"onClose\"\n >\n <slot name=\"title\">\n {{ t(dialogTitleT) }}\n </slot>\n </ADialogToolbar>\n <VToolbar\n density=\"compact\"\n color=\"transparent\"\n :height=\"46\"\n elevation=\"0\"\n class=\"system-border-b subject-select__second-bar\"\n >\n <slot name=\"second-bar\">\n <div class=\"d-flex flex-column w-100 px-1 align-center\">\n <div class=\"d-flex justify-space-between w-100 align-center\">\n <div class=\"d-flex align-center\">\n <VBtn\n icon\n :width=\"30\"\n :height=\"30\"\n @click=\"toggleSidebar\"\n >\n <VIcon\n icon=\"mdi-tune\"\n :size=\"16\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n >\n {{ t('common.subjectSelect.filter.toggle') }}\n </VTooltip>\n </VBtn>\n <slot name=\"second-bar-left\" />\n </div>\n <div class=\"d-flex align-center\">\n <slot name=\"second-bar-right\" />\n </div>\n </div>\n </div>\n </slot>\n </VToolbar>\n <div\n class=\"subject-select__main\"\n :class=\"{ 'subject-select__main--sidebar-active': sidebarLeft }\"\n >\n <div class=\"subject-select__sidebar system-border-r\">\n <div class=\"subject-select-filter\">\n <slot name=\"filter\" />\n </div>\n </div>\n <div class=\"subject-select__content\">\n <slot name=\"content\" />\n <div\n v-if=\"paginationMode === 'more'\"\n class=\"d-flex w-100 align-center justify-center pa-4\"\n >\n <ABtnSecondary\n v-show=\"hasNextPage || loading\"\n v-intersect.quiet=\"autoloadOnIntersect\"\n :loading=\"loading\"\n size=\"small\"\n @click=\"fetchNextPage\"\n >\n <slot name=\"button-confirm-title\">\n {{ t('common.button.loadMore') }}\n </slot>\n </ABtnSecondary>\n </div>\n <ADatatablePagination\n v-else\n v-model=\"paginationComputed\"\n @change=\"onPageChange\"\n />\n </div>\n </div>\n <div class=\"subject-select__actions system-border-t\">\n <div v-if=\"minCount === maxCount\">\n {{\n t('common.subjectSelect.texts.pickExactCount', {\n count: minCount,\n selected: selectedItemsCount,\n })\n }}\n </div>\n <div v-else>\n {{\n t('common.subjectSelect.texts.pickRangeCount', {\n minCount: minCount,\n maxCount: maxCount,\n selected: selectedItemsCount,\n })\n }}\n </div>\n <VSpacer />\n <ABtnPrimary\n :disabled=\"disabledSubmit\"\n @click.stop=\"onConfirm\"\n >\n <slot name=\"button-confirm-title\">\n {{ t('common.button.confirm') }}\n </slot>\n </ABtnPrimary>\n </div>\n </VCard>\n </VDialog>\n</template>\n","<script lang=\"ts\" setup generic=\"TItem\">\nimport { computed, onMounted, provide, ref, toRaw, withModifiers } from 'vue'\nimport { isNull, isUndefined } from '@/utils/common'\nimport ADialogToolbar from '@/components/ADialogToolbar.vue'\nimport { useI18n } from 'vue-i18n'\nimport { useDisplay } from 'vuetify'\nimport type { Pagination } from '@/labs/filters/pagination'\nimport ADatatablePagination from '@/labs/filters/ADatatablePagination.vue'\nimport { SubjectSelectCloseSidebarKey } from '@/labs/filters/filterInjectionKeys'\n\nconst props = withDefaults(\n defineProps<{\n selectedItems: Array<TItem>\n pagination: Pagination\n loading?: boolean\n minCount?: number\n maxCount?: number\n modelValue?: boolean | undefined // dialog\n dialogTitleT?: string\n paginationMode?: 'standard' | 'more'\n autoOpen?: boolean\n }>(),\n {\n loading: false,\n minCount: 1,\n maxCount: 1,\n modelValue: undefined,\n dialogTitleT: 'common.subjectSelect.texts.title',\n paginationMode: 'standard',\n autoOpen: false,\n }\n)\nconst emit = defineEmits<{\n (e: 'update:modelValue', data: boolean): void\n (e: 'update:pagination', data: Pagination): void\n (e: 'onOpen'): void\n (e: 'onPageChange'): void\n (e: 'onConfirm', data: Array<TItem>): void\n (e: 'onFetchNextPage'): void\n}>()\n\nconst dialogLocal = ref(false)\nconst dialog = computed({\n get() {\n if (isUndefined(props.modelValue)) return dialogLocal.value\n return props.modelValue\n },\n set(newValue: boolean) {\n dialogLocal.value = newValue\n emit('update:modelValue', newValue)\n },\n})\n\nconst paginationComputed = computed({\n get: () => props.pagination,\n set: (newValue) => {\n emit('update:pagination', { ...toRaw(newValue) })\n },\n})\n\nconst { mdAndDown } = useDisplay()\nconst sidebarLeft = ref(!mdAndDown.value)\n\nconst closeSidebarOnMobile = () => {\n if (mdAndDown.value) sidebarLeft.value = false\n}\n\nprovide(SubjectSelectCloseSidebarKey, closeSidebarOnMobile)\n\nconst { t } = useI18n()\n\nconst selectedItemsCount = computed(() => {\n return props.selectedItems.length\n})\n\nconst disabledSubmit = computed(() => {\n return selectedItemsCount.value < props.minCount || selectedItemsCount.value > props.maxCount\n})\n\nconst onOpen = () => {\n emit('onOpen')\n sidebarLeft.value = !mdAndDown.value\n dialog.value = true\n}\n\nconst toggleSidebar = () => {\n sidebarLeft.value = !sidebarLeft.value\n}\n\nconst onClose = () => {\n dialog.value = false\n}\n\nconst onConfirm = () => {\n emit(\n 'onConfirm',\n props.selectedItems.map((item) => toRaw(item))\n )\n onClose()\n}\n\nconst fetchNextPage = () => {\n emit('onFetchNextPage')\n}\n\nconst onPageChange = () => {\n emit('onPageChange')\n}\n\nconst lastPage = computed(() => {\n return Math.ceil(paginationComputed.value.totalCount / paginationComputed.value.rowsPerPage)\n})\n\n// `>=`, not `===`: an empty list has `totalCount: 0`, so `lastPage` is 0 while `page` is never below\n// 1, and the two can never meet. The page would then count as \"not the last one\" on a list with\n// nothing in it -- and the same holds before any request has been made, because that initial state is\n// the same numbers.\nconst hasNextPage = computed(() => {\n return !(\n (isNull(paginationComputed.value.hasNextPage) && paginationComputed.value.page >= lastPage.value) ||\n paginationComputed.value.hasNextPage === false\n )\n})\n\nconst autoloadOnIntersect = (isIntersecting: boolean) => {\n if (isIntersecting && hasNextPage.value && !props.loading) {\n fetchNextPage()\n }\n}\n\nonMounted(() => {\n if (props.autoOpen) {\n onOpen()\n }\n})\n\ndefineExpose({\n open: onOpen,\n closeSidebarOnMobile,\n})\n</script>\n\n<template>\n <slot\n name=\"activator\"\n :props=\"{ onClick: withModifiers(() => onOpen(), ['stop']) }\"\n />\n <VDialog\n v-bind=\"$attrs\"\n :model-value=\"dialog\"\n fullscreen\n class=\"subject-select\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <VCard\n v-if=\"dialog\"\n class=\"subject-select__card\"\n >\n <ADialogToolbar\n class=\"subject-select__toolbar system-border-b\"\n @on-cancel=\"onClose\"\n >\n <slot name=\"title\">\n {{ t(dialogTitleT) }}\n </slot>\n </ADialogToolbar>\n <VToolbar\n density=\"compact\"\n color=\"transparent\"\n :height=\"46\"\n elevation=\"0\"\n class=\"system-border-b subject-select__second-bar\"\n >\n <slot name=\"second-bar\">\n <div class=\"d-flex flex-column w-100 px-1 align-center\">\n <div class=\"d-flex justify-space-between w-100 align-center\">\n <div class=\"d-flex align-center\">\n <VBtn\n icon\n :width=\"30\"\n :height=\"30\"\n @click=\"toggleSidebar\"\n >\n <VIcon\n icon=\"mdi-tune\"\n :size=\"16\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n >\n {{ t('common.subjectSelect.filter.toggle') }}\n </VTooltip>\n </VBtn>\n <slot name=\"second-bar-left\" />\n </div>\n <div class=\"d-flex align-center\">\n <slot name=\"second-bar-right\" />\n </div>\n </div>\n </div>\n </slot>\n </VToolbar>\n <div\n class=\"subject-select__main\"\n :class=\"{ 'subject-select__main--sidebar-active': sidebarLeft }\"\n >\n <div class=\"subject-select__sidebar system-border-r\">\n <div class=\"subject-select-filter\">\n <slot name=\"filter\" />\n </div>\n </div>\n <div class=\"subject-select__content\">\n <slot name=\"content\" />\n <div\n v-if=\"paginationMode === 'more'\"\n class=\"d-flex w-100 align-center justify-center pa-4\"\n >\n <ABtnSecondary\n v-show=\"hasNextPage || loading\"\n v-intersect.quiet=\"autoloadOnIntersect\"\n :loading=\"loading\"\n size=\"small\"\n @click=\"fetchNextPage\"\n >\n <slot name=\"button-confirm-title\">\n {{ t('common.button.loadMore') }}\n </slot>\n </ABtnSecondary>\n </div>\n <ADatatablePagination\n v-else\n v-model=\"paginationComputed\"\n @change=\"onPageChange\"\n />\n </div>\n </div>\n <div class=\"subject-select__actions system-border-t\">\n <div v-if=\"minCount === maxCount\">\n {{\n t('common.subjectSelect.texts.pickExactCount', {\n count: minCount,\n selected: selectedItemsCount,\n })\n }}\n </div>\n <div v-else>\n {{\n t('common.subjectSelect.texts.pickRangeCount', {\n minCount: minCount,\n maxCount: maxCount,\n selected: selectedItemsCount,\n })\n }}\n </div>\n <VSpacer />\n <ABtnPrimary\n :disabled=\"disabledSubmit\"\n @click.stop=\"onConfirm\"\n >\n <slot name=\"button-confirm-title\">\n {{ t('common.button.confirm') }}\n </slot>\n </ABtnPrimary>\n </div>\n </VCard>\n </VDialog>\n</template>\n","<script setup lang=\"ts\" generic=\"TItem extends Record<string, any>\">\nimport { computed, ref, useSlots, useTemplateRef, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport { useDisplay } from 'vuetify'\nimport { useKeyboardNav } from '@/labs/listEditor/composables/useKeyboardNav'\nimport {\n useListEditorController,\n type GetKey,\n type ListEditorHandle,\n type ListEditorValidationResult,\n type PositionAction,\n type PositionOption,\n type PositionStrategy,\n} from '@/labs/listEditor/composables/useListEditorController'\nimport {\n provideListEditorStateScope,\n useListEditorStateEntry,\n type ListEditorStateBindings,\n} from '@/labs/listEditor/composables/useListEditorStateScope'\nimport { resolveCompactText as resolveCompactTextUtil } from '@/labs/listEditor/composables/resolveCompactText'\nimport { useUnsavedSection } from '@/labs/unsavedGuard/useUnsavedSection'\nimport { useDeleteDialog } from '@/labs/listEditor/composables/useDeleteDialog'\nimport { useInlineEditing } from '@/labs/listEditor/composables/useInlineEditing'\nimport { validateAllAndReveal } from '@/labs/listEditor/utils/revealInvalidRows'\nimport { useListEditorScopeValidity } from '@/labs/listEditor/composables/useListEditorScopeValidity'\nimport LeDeleteDialog from '@/labs/listEditor/internal/LeDeleteDialog.vue'\nimport LeEmptyState from '@/labs/listEditor/internal/LeEmptyState.vue'\nimport LeUnsavedLabel from '@/labs/listEditor/internal/LeUnsavedLabel.vue'\nimport type {\n ListEditorKey,\n ListEditorValidationScope,\n ListEditorValidationState,\n ListViewItem,\n} from '@/labs/listEditor/types/listEditorTypes'\n\nexport interface DecoratedViewItem<T> extends ListViewItem<T> {\n editing: boolean\n expanded: boolean\n loading: boolean\n dirty: boolean\n unsaved: boolean\n validationState: ListEditorValidationState\n}\n\n// Hoisted for vite-plugin-dts d.ts rollup.\nexport interface RowActions<TItem> {\n edit: () => void\n save: () => Promise<void> | void\n cancel: () => void\n close: () => void\n delete: () => Promise<void>\n addAfter: () => void\n toggleExpand: () => void\n update: (next: TItem | Partial<TItem> | ((current: TItem) => TItem)) => void\n}\nexport interface RowSlotProps<TItem extends Record<string, any>> {\n item: DecoratedViewItem<TItem>\n raw: TItem\n index: number\n key: ListEditorKey\n readonly: boolean\n disabled: boolean\n expanded: boolean\n editing: boolean\n dirty: boolean\n /** Aliases `dirty` here (no reorder mode → no `moved` to OR). Same field name as on sortable/nested variants. */\n unsaved: boolean\n touch: boolean\n /**\n * Row-scoped `state-key` prefix. Thread it down to any list editor rendered in this slot and set\n * its `:state-key=\"`${stateKeyPrefix}:someList`\"` — that editor's controller then lives in THIS\n * editor's scope and survives the row collapsing (which unmounts the whole slot subtree). Entries\n * under this prefix are disposed when the row is removed.\n */\n stateKeyPrefix: string\n actions: RowActions<TItem>\n}\nexport interface EmptySlotProps {\n readonly: boolean\n disabled: boolean\n actions: { add: () => void }\n}\nexport interface AddButtonSlotProps {\n readonly: boolean\n disabled: boolean\n props: { onClick: () => void }\n actions: { add: () => void }\n}\nexport interface HeaderSlotProps {\n title: string | null\n}\n\nexport interface Props<TItem extends Record<string, any>> {\n /**\n * New-row factory. Add / \"add after\" insert `factory()` through the controller\n * (positions renumbered) — no consumer `@add` push handler is needed.\n */\n factory?: () => TItem\n /**\n * Stable row identity. Default `'id'`; never the position field. Typed `string`\n * not `keyof TItem` because the latter compiles to a Boolean-only runtime prop\n * type that silently coerces `get-key=\"id\"` to `true`.\n */\n getKey?: string | ((item: TItem) => ListEditorKey)\n /**\n * Managed order field. Default `'position'`; `false` opts out. `string` must be\n * listed before `false` (and not be `keyof TItem`): otherwise Vue's runtime\n * type goes Boolean-first and coerces `position=\"position\"` to `true`.\n */\n position?:\n | string\n | false\n | {\n field: string\n multiplier?: number\n strategy?: PositionStrategy\n strategyOverrides?: Partial<Record<PositionAction, PositionStrategy>>\n }\n /**\n * Extra fields to drop from the dirty content-hash (position is always dropped).\n * Use when a SEPARATE nested editor already tracks a child collection, so editing\n * a child doesn't flip the parent row amber. Still saved; only the diff ignores it.\n */\n dirtyExclude?: string[]\n /** Per-row validity — `true` (or `{ valid: true }`) = VALID. Drives the red rail + save guard. */\n validate?: (item: TItem) => ListEditorValidationResult\n /**\n * Vuelidate `$scope` (the same one the consumer's row forms / save-gate collector use). When set,\n * the editor registers its aggregate validity under it, so a plain `v$.$invalid` save gate blocks\n * AND reveals a collapsed invalid row — no `validateAll()` call needed in the save flow. Omit to\n * keep legacy behavior (gate the save on the exposed `validateAll()` yourself); `false` opts out.\n */\n validationScope?: ListEditorValidationScope | false\n /**\n * Opt-in lifted controller from `useListEditorController()` — pass it so editor\n * state survives this component's unmount/remount. Omitted: the editor owns one\n * internally. Either way the `ListEditorHandle` is reachable via `useTemplateRef`.\n */\n editor?: ListEditorHandle<TItem>\n /**\n * Persist this editor's controller in the nearest ANCESTOR editor's row-state scope under this\n * key, instead of owning it. Use it for an editor rendered in another editor's `#item` slot: that\n * slot is behind a `v-if`, so collapsing the row unmounts this editor and a component-owned\n * controller would re-baseline the already-edited data on re-expand (losing amber, tombstones and\n * the red rail). Derive the key from the row slot's `stateKeyPrefix`, e.g.\n * `:state-key=\"`${stateKeyPrefix}:adverts`\"`. Options stay declared here, on this tag: the\n * controller is built from THIS editor's `factory`/`get-key`/`position`/`dirty-exclude`/`validate`\n * and rebound to each new instance's model on remount. Ignored when `:editor` is passed, and a\n * no-op without an ancestor scope.\n */\n stateKey?: string\n\n readonly?: boolean\n disabled?: boolean\n loading?: boolean\n error?: string | null\n\n /** Heading above the list. A ready translated string, like every text prop here. */\n title?: string | null\n\n /**\n * Field shown in a collapsed row when no `#item-compact` slot is given. A dotted path reads\n * through nested objects (`texts.title`); a path that runs into nothing renders empty.\n */\n compactField?: string | null\n statusField?: string | null\n twoRows?: 'never' | 'mobile' | 'always'\n chips?: boolean\n\n showAddButton?: boolean\n showDeleteButton?: boolean\n showEditButton?: boolean\n showAddAfterAction?: boolean\n\n /** Label of the add button. A ready translated string; omit it for the library's own wording. */\n addLabel?: string | null\n /** Heading of the empty state. A ready translated string. */\n emptyTitle?: string | null\n\n disableRowClick?: boolean\n disableDeleteConfirm?: boolean\n /** Disable unsaved-state tracking — no dirty markers, never reads as unsaved. */\n disableUnsaved?: boolean\n deleteConfirmTitle?: string | null\n deleteConfirmText?: string | null\n /**\n * How a row delete persists. `deferred` (default): the row disappears but the deletion counts as an\n * unconfirmed change (`unsavedCount`) until save, and is revertible until then. `immediate`: the\n * consumer deletes on the backend (`:on-delete`) — the dialog states it is irreversible and the\n * delete does NOT read as unsaved.\n */\n deleteMode?: 'immediate' | 'deferred'\n\n closeVariant?: 'auto' | 'icon' | 'labeled'\n\n /**\n * Render every `#item` slot expanded — no edit pencil, inline footer, or row-click\n * toggle. Use when all forms should be visible at once (e.g. ThirdPartyTracker).\n */\n defaultExpanded?: boolean\n\n loadingKeys?: Set<ListEditorKey> | null\n\n onDeleteConfirm?: (item: TItem) => Promise<boolean> | boolean\n onDelete?: (item: TItem) => Promise<void> | void\n onItemSave?: (item: TItem) => Promise<void> | void\n\n /**\n * Registers this editor as a named unsaved-changes section under this (already\n * translated) label — replaces a per-consumer `useUnsavedSection` call.\n */\n unsavedSectionLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props<TItem>>(), {\n factory: undefined,\n getKey: undefined,\n position: undefined,\n dirtyExclude: undefined,\n validate: undefined,\n validationScope: undefined,\n editor: undefined,\n stateKey: undefined,\n readonly: false,\n disabled: false,\n loading: false,\n error: null,\n title: null,\n compactField: null,\n statusField: null,\n twoRows: 'never',\n chips: false,\n showAddButton: true,\n showDeleteButton: true,\n showEditButton: true,\n showAddAfterAction: false,\n addLabel: null,\n emptyTitle: null,\n disableRowClick: false,\n disableDeleteConfirm: false,\n disableUnsaved: false,\n deleteConfirmTitle: null,\n deleteConfirmText: null,\n deleteMode: 'deferred',\n closeVariant: 'auto',\n defaultExpanded: false,\n loadingKeys: null,\n onDeleteConfirm: undefined,\n onDelete: undefined,\n onItemSave: undefined,\n unsavedSectionLabel: undefined,\n})\n\nconst emit = defineEmits<{\n edit: [item: ListViewItem<TItem>]\n deleted: [item: ListViewItem<TItem>]\n close: [item: ListViewItem<TItem>]\n 'item-saved': [item: ListViewItem<TItem>]\n 'item-expand': [item: ListViewItem<TItem>, expanded: boolean]\n}>()\n\ndefineSlots<{\n header?: (props: HeaderSlotProps) => unknown\n empty?: (props: EmptySlotProps) => unknown\n 'add-button'?: (props: AddButtonSlotProps) => unknown\n item?: (props: RowSlotProps<TItem>) => unknown\n 'item-compact'?: (props: RowSlotProps<TItem>) => unknown\n 'item-readonly'?: (props: RowSlotProps<TItem>) => unknown\n 'item-status'?: (props: RowSlotProps<TItem>) => unknown\n 'item-actions'?: (props: RowSlotProps<TItem>) => unknown\n 'item-footer'?: (props: RowSlotProps<TItem>) => unknown\n 'before-item'?: (props: RowSlotProps<TItem>) => unknown\n 'after-item'?: (props: RowSlotProps<TItem>) => unknown\n}>()\n\nconst modelValue = defineModel<TItem[]>({ required: true })\n\nconst { t } = useI18n()\nconst slots = useSlots()\nconst display = useDisplay()\n\nconst rootEl = useTemplateRef<HTMLElement>('rootEl')\n\nconst isTouch = computed<boolean>(() => display.platform.value.touch)\n\n// State controller (v2). `factory`/`getKey`/`position`/`validate` are construction-time options read\n// once to seed it (undefined → controller defaults 'id'/'position'/always-valid), not reactive props\n// — so the reactivity-loss rule is intentionally suppressed here.\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst controllerOptions: ListEditorStateBindings<TItem> = {\n get: () => modelValue.value,\n set: (v) => (modelValue.value = v),\n factory: props.factory,\n getKey: props.getKey as GetKey<TItem> | undefined,\n position: props.position as PositionOption<TItem> | undefined,\n dirtyExclude: () => props.dirtyExclude ?? [],\n validate: props.validate,\n}\n// Precedence: an explicitly lifted `:editor` wins; then a `state-key`'d entry persisted in the\n// nearest ancestor editor's row-state scope (built from the options above, rebound to this instance\n// on every remount); else this component owns the controller — today's behaviour, unchanged.\nconst stateEntry = props.editor ? null : useListEditorStateEntry<TItem>(props.stateKey, controllerOptions)\nconst controller = props.editor ?? stateEntry?.handle ?? useListEditorController<TItem>(controllerOptions)\n\n// Mirror the controller's key resolution so rendered rows key the way it tracks them.\nconst getKeyOpt = props.getKey ?? 'id'\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\n// Row-state scope for THIS editor's `#item` slot subtree: a nested editor with a `state-key` builds\n// its controller in here, so it outlives the row collapsing (which unmounts the slot). When this\n// editor is itself persisted, the scope comes from its own entry — so the chain holds at any depth.\nconst rowStateScope = provideListEditorStateScope(stateEntry?.childScope ?? null)\nconst keyOf = (item: TItem): ListEditorKey =>\n typeof getKeyOpt === 'function' ? getKeyOpt(item) : (item[getKeyOpt as keyof TItem] as ListEditorKey)\n\nconst keyFieldName = computed<string>(() => (typeof getKeyOpt === 'function' ? '(fn)' : (getKeyOpt as string)))\n\n// Managed position field name, mirroring the controller's resolution.\nconst positionFieldName = computed<string>(() => {\n const p = props.position\n if (p === undefined) return 'position'\n if (p === false) return 'position'\n if (typeof p === 'object') return p.field as string\n return p as string\n})\n\n// Render projection only — the controller owns the data (key + index + raw + position).\nconst viewItems = computed<ListViewItem<TItem>[]>(() =>\n modelValue.value.map((raw, index) => ({\n key: keyOf(raw),\n index,\n raw,\n position: raw[positionFieldName.value] as number | undefined,\n }))\n)\n\n// Surfaces row-key wiring bugs loudly: undefined or duplicate keys silently break\n// dirty tracking, validity rails and reorder targeting and are hard to trace from\n// symptoms. Deduped per signature so a stable bad state warns once, a new one again.\nconst warnedKeySignatures = new Set<string>()\nconst warnOnBadKeys = (items: ListViewItem<TItem>[]): void => {\n const seen = new Set<ListEditorKey>()\n const duplicates = new Set<string>()\n let missing = 0\n for (const vi of items) {\n if (vi.key === undefined || vi.key === null) {\n missing++\n continue\n }\n if (seen.has(vi.key)) duplicates.add(String(vi.key))\n seen.add(vi.key)\n }\n if (missing === 0 && duplicates.size === 0) return\n const signature = `${missing}|${[...duplicates].sort().join(',')}`\n if (warnedKeySignatures.has(signature)) return\n warnedKeySignatures.add(signature)\n if (missing > 0) {\n console.warn(\n `[list-editor] ${missing} row(s) resolve to an undefined key (key-field \"${keyFieldName.value}\"). ` +\n 'Point get-key at a field every item has, or give new items unique temp ids ' +\n '(see nextListEditorTempId).'\n )\n }\n if (duplicates.size > 0) {\n console.warn(\n `[list-editor] duplicate row keys (key-field \"${keyFieldName.value}\"): ${[...duplicates].join(', ')}. ` +\n 'Row keys must be unique — dirty tracking and validation rails target rows by key.'\n )\n }\n}\nwatch(viewItems, (items) => warnOnBadKeys(items), { immediate: true })\n\n// Drop the persisted controllers of rows that no longer exist. `post` so the removed row's subtree\n// has already unmounted (its editors released their entries) before we stop their effect scopes.\nwatch(\n () => viewItems.value.map((vi) => String(vi.key)).join('|'),\n () => rowStateScope.retainOwners(viewItems.value.map((vi) => vi.key)),\n { flush: 'post' }\n)\n\nconst expandedKeys = ref<Set<ListEditorKey>>(new Set())\n\nconst rowsContainer = useTemplateRef<HTMLElement>('rowsContainer')\n\nconst isInlineEdit = computed(() => !props.chips && !!slots.item)\nconst hasReadonlyDetail = computed(() => !props.chips && !!slots['item-readonly'])\n\nconst { editingKeys, editingSnapshots, beginEdit, cancelEdit, commitEdit, closeEdit, requestAutoOpen } =\n useInlineEditing<TItem, ListViewItem<TItem>>({\n rowsContainer,\n rowSelector: '.a-le-row',\n isInlineEdit,\n restoreSnapshot: (key, data) => controller.updateItem(key, data),\n watchKeys: () => modelValue.value.map((it) => keyOf(it)),\n findEntry: (key) => {\n const hit = modelValue.value.find((it) => keyOf(it) === key)\n return hit ? { data: hit } : null\n },\n afterAutoOpen: (key) => {\n expandedKeys.value.delete(key)\n },\n })\n\nconst addLabelResolved = computed(() => props.addLabel ?? t('common.sortable.add'))\nconst emptyTitleResolved = computed(() => props.emptyTitle ?? t('common.sortable.emptyTitle'))\nconst deleteConfirmTitleResolved = computed(() => props.deleteConfirmTitle ?? t('common.sortable.deleteConfirmTitle'))\nconst deleteConfirmTextResolved = computed(\n () =>\n props.deleteConfirmText ??\n (props.deleteMode === 'immediate'\n ? t('common.sortable.deleteConfirmText')\n : t('common.sortable.deleteConfirmTextDeferred'))\n)\n\nconst canInteract = computed(() => !props.readonly && !props.disabled && !props.loading)\nconst canAdd = computed(() => canInteract.value && props.showAddButton)\n\n// Total unconfirmed-change count (added/edited/moved rows + deferred deletions). A delete lights this\n// up even though its row is gone. Shown as a header badge + exposed on the handle.\nconst unsavedCount = controller.unsavedCount\nconst unsavedCountVisible = computed(() => !props.readonly && !props.disableUnsaved && unsavedCount.value > 0)\nconst headerVisible = computed(() => !!(props.title || slots.header) || unsavedCountVisible.value)\n\n// Per-row Save/Cancel footer only makes sense with a per-item persist callback;\n// without one the parent form's global save flushes everything, so hide it.\nconst showInlineSaveFooter = computed(() => !!props.onItemSave)\n\n// Per-key decorator cache: reuse the cached object when base item AND every flag\n// match, giving slot consumers a stable reference for unchanged rows. `disableUnsaved`\n// suppresses only the amber marker — the validation rail still shows, since hiding\n// dirty-state shouldn't hide a real error.\nconst decoratorCache = new Map<ListEditorKey, DecoratedViewItem<TItem>>()\nconst viewItemsDecorated = computed<DecoratedViewItem<TItem>[]>(() => {\n const next: DecoratedViewItem<TItem>[] = []\n const liveKeys = new Set<ListEditorKey>()\n for (const vi of viewItems.value) {\n liveKeys.add(vi.key)\n const editing = editingKeys.value.has(vi.key)\n const expanded = expandedKeys.value.has(vi.key)\n const loading = props.loadingKeys?.has(vi.key) ?? false\n // Readonly can't have user-unsaved changes; never paint amber there (also avoids\n // a mount-before-load baseline marking every loaded row added). (QA 85050 sweep)\n const unsaved = props.disableUnsaved || props.readonly ? false : controller.isUnsaved(vi.key)\n const dirty = unsaved\n // `editing` → the controller reads amber (not red) while the row is being filled in. (QA 85050 b7)\n const validationState = controller.rowState(vi.raw, vi.key, editing)\n const cached = decoratorCache.get(vi.key)\n if (\n cached &&\n cached.raw === vi.raw &&\n cached.index === vi.index &&\n cached.position === vi.position &&\n cached.editing === editing &&\n cached.expanded === expanded &&\n cached.loading === loading &&\n cached.dirty === dirty &&\n cached.unsaved === unsaved &&\n cached.validationState === validationState\n ) {\n next.push(cached)\n continue\n }\n const decorated: DecoratedViewItem<TItem> = {\n ...vi,\n editing,\n expanded,\n loading,\n dirty,\n unsaved,\n validationState,\n }\n decoratorCache.set(vi.key, decorated)\n next.push(decorated)\n }\n for (const key of decoratorCache.keys()) {\n if (!liveKeys.has(key)) decoratorCache.delete(key)\n }\n return next\n})\n\nconst isEmpty = computed(() => viewItemsDecorated.value.length === 0)\n\nconst findVi = (key: ListEditorKey): DecoratedViewItem<TItem> | undefined =>\n viewItemsDecorated.value.find((v) => v.key === key)\n\nconst keyboardNav = useKeyboardNav({\n viewItems: computed(() => viewItemsDecorated.value.map((vi) => ({ key: vi.key }))),\n variant: 'flat',\n isReorderMode: ref(false),\n disabled: computed(() => !canInteract.value),\n isEditing: (key) => editingKeys.value.has(key),\n onToggleEdit: (key) => {\n const vi = findVi(key)\n if (vi) onEditClick(vi)\n },\n onCancelEdit: (key) => {\n const vi = findVi(key)\n if (vi) onCloseClick(vi)\n },\n})\n\nconst resolveCompactText = (raw: TItem): string => resolveCompactTextUtil(raw, { compactField: props.compactField })\n\n// Managed add: the controller inserts `factory()` and renumbers; the inline-editing\n// watch picks up the new key off the model change and auto-opens it.\nconst onAddClick = () => {\n if (!canAdd.value) return\n requestAutoOpen()\n controller.addItem(undefined, undefined)\n}\n\nconst onRowAddAfterClick = (vi: ListViewItem<TItem>) => {\n if (!canInteract.value) return\n requestAutoOpen()\n controller.addItem(undefined, { afterId: vi.key })\n}\n\nconst onEditClick = (vi: ListViewItem<TItem>) => {\n if (!canInteract.value) return\n // Toggle: edit while already editing closes the form, matching row-header click.\n if (editingKeys.value.has(vi.key)) {\n onCloseClick(vi)\n return\n }\n if (isInlineEdit.value) {\n beginEdit(vi)\n expandedKeys.value.delete(vi.key)\n }\n emit('edit', vi)\n}\n\nconst onExpandClick = (vi: ListViewItem<TItem>) => {\n if (props.disabled || props.loading) return\n const key = vi.key\n const currentlyExpanded = expandedKeys.value.has(key)\n if (currentlyExpanded) {\n expandedKeys.value.delete(key)\n } else {\n for (const k of expandedKeys.value) expandedKeys.value.delete(k)\n expandedKeys.value.add(key)\n }\n emit('item-expand', vi, !currentlyExpanded)\n}\n\nconst isRowClickable = (vi: DecoratedViewItem<TItem>): boolean => {\n if (props.chips) return false\n if (props.disableRowClick) return false\n if (props.defaultExpanded) return false\n if (props.disabled || props.loading) return false\n if (vi.editing || vi.expanded) return true\n if (!props.readonly && props.showEditButton) return true\n return false\n}\n\nconst onRowClick = (vi: DecoratedViewItem<TItem>) => {\n if (!isRowClickable(vi)) return\n if (vi.editing || vi.expanded) {\n onCloseClick(vi)\n return\n }\n if (props.readonly && hasReadonlyDetail.value) {\n onExpandClick(vi)\n } else {\n onEditClick(vi)\n }\n}\n\nconst {\n deleteDialog,\n deleteInFlight,\n deleteError,\n onDeleteClick: triggerDeleteClick,\n onDeleteDialogConfirm,\n onDeleteDialogCancel,\n} = useDeleteDialog<TItem, ListViewItem<TItem>>({\n onDeleteConfirm: (raw) => (props.onDeleteConfirm ? props.onDeleteConfirm(raw) : true),\n onDelete: (raw) => props.onDelete?.(raw),\n onDeleted: (vi) => {\n editingKeys.value.delete(vi.key)\n editingSnapshots.value.delete(vi.key)\n expandedKeys.value.delete(vi.key)\n // Controller owns removal: a temp row vanishes; a saved row is recorded in `getChanges().deleted`\n // and counts as an unconfirmed change (deferred) — unless immediate mode (already deleted on the\n // backend), which skips the tombstone.\n controller.deleteItem(vi.key, { trackDeleted: props.deleteMode === 'deferred' })\n emit('deleted', vi)\n },\n disableDeleteConfirm: () => props.disableDeleteConfirm || props.chips,\n})\n\nconst onDeleteClick = async (vi: ListViewItem<TItem>) => {\n if (!canInteract.value) return\n await triggerDeleteClick(vi)\n}\n\nconst onSaveClick = async (vi: ListViewItem<TItem>) => {\n if (props.onItemSave) {\n await props.onItemSave(vi.raw)\n }\n commitEdit(vi)\n emit('item-saved', vi)\n}\n\nconst onCancelClick = (vi: ListViewItem<TItem>) => {\n cancelEdit(vi)\n}\n\nconst onCloseClick = (vi: ListViewItem<TItem>) => {\n closeEdit(vi)\n expandedKeys.value.delete(vi.key)\n emit('close', vi)\n}\n\n// Per-key actions cache: each row's `actions` bundle is allocated once so slot\n// consumers get a stable identity (no prop-ref churn → no spurious re-renders).\n// Closures capture the stable key and look up the live decorator via `findVi(key)`.\ntype ActionsBundle = {\n edit: () => void\n save: () => Promise<void> | void\n cancel: () => void\n close: () => void\n delete: () => Promise<void>\n addAfter: () => void\n toggleExpand: () => void\n update: (next: TItem | Partial<TItem> | ((current: TItem) => TItem)) => void\n}\nconst actionsCache = new Map<ListEditorKey, ActionsBundle>()\nconst getActions = (key: ListEditorKey): ActionsBundle => {\n let actions = actionsCache.get(key)\n if (!actions) {\n actions = {\n edit: () => {\n const vi = findVi(key)\n if (vi) onEditClick(vi)\n },\n save: () => {\n const vi = findVi(key)\n if (vi) return onSaveClick(vi)\n },\n cancel: () => {\n const vi = findVi(key)\n if (vi) onCancelClick(vi)\n },\n close: () => {\n const vi = findVi(key)\n if (vi) onCloseClick(vi)\n },\n delete: async () => {\n const vi = findVi(key)\n if (vi) await onDeleteClick(vi)\n },\n addAfter: () => {\n const vi = findVi(key)\n if (vi) onRowAddAfterClick(vi)\n },\n toggleExpand: () => {\n const vi = findVi(key)\n if (vi) onExpandClick(vi)\n },\n update: (next) => controller.updateItem(key, next),\n }\n actionsCache.set(key, actions)\n }\n return actions\n}\n// Garbage-collect cache entries for keys that have left the model.\nwatch(\n () => viewItemsDecorated.value,\n (now) => {\n if (actionsCache.size === 0) return\n const liveKeys = new Set(now.map((v) => v.key))\n for (const key of actionsCache.keys()) {\n if (!liveKeys.has(key)) actionsCache.delete(key)\n }\n }\n)\n\nconst buildSlotProps = (vi: DecoratedViewItem<TItem>) => ({\n item: vi,\n raw: vi.raw,\n index: vi.index,\n key: vi.key,\n readonly: props.readonly,\n disabled: props.disabled,\n expanded: vi.expanded,\n editing: vi.editing || props.defaultExpanded,\n dirty: vi.dirty,\n unsaved: vi.unsaved,\n touch: isTouch.value,\n stateKeyPrefix: rowStateScope.prefixFor(vi.key),\n actions: getActions(vi.key),\n})\n\n// Named unsaved-changes section, registered only when a label is passed.\nuseUnsavedSection(() =>\n props.unsavedSectionLabel && !props.disableUnsaved\n ? { label: props.unsavedSectionLabel, dirty: controller.hasUnsaved.value }\n : []\n)\n\n// Opens invalid rows so a blocked save surfaces WHICH rows are wrong instead of a collapsed red\n// rail. The body is gated on `editing` not `expanded`, so drive `beginEdit` not `expandedKeys`\n// (QA 85050 B4-17). Shared by the exposed `validateAll` and the scope-collector reveal-on-touch.\nconst runValidateAndReveal = (): boolean =>\n validateAllAndReveal(controller, (key) => {\n const vi = viewItems.value.find((v) => v.key === key)\n if (vi) beginEdit(vi)\n })\n\n// Auto-bridge the editor's aggregate validity into the consumer's `validation-scope` collector so a\n// plain `$invalid` save gate blocks (and reveals) a collapsed invalid row. No-op without the prop.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss -- construction-time opt-in, read once\nuseListEditorScopeValidity({\n hasErrors: controller.hasErrors,\n validationScope: props.validationScope,\n validateProvided: props.validate !== undefined,\n reveal: runValidateAndReveal,\n})\n\n// Expose the controller handle via `useTemplateRef<ListEditorHandle<TItem>>`.\ndefineExpose<ListEditorHandle<TItem>>({\n ...controller,\n validateAll: runValidateAndReveal,\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"a-list-editor\"\n :class=\"[\n `a-list-editor--two-rows-${twoRows}`,\n {\n 'a-list-editor--readonly': readonly,\n 'a-list-editor--disabled': disabled,\n 'a-list-editor--touch': isTouch,\n 'a-list-editor--chips': chips,\n },\n ]\"\n >\n <div\n ref=\"rowsContainer\"\n class=\"a-le-card\"\n >\n <div\n v-if=\"headerVisible\"\n class=\"a-le-header\"\n >\n <slot\n name=\"header\"\n :title=\"title\"\n >\n <h3\n v-if=\"title\"\n class=\"a-le-title-heading\"\n >\n {{ title }}\n </h3>\n </slot>\n <span\n v-if=\"unsavedCountVisible\"\n class=\"a-le-unsaved-count\"\n :data-unsaved-count=\"unsavedCount\"\n >\n {{ t('common.sortable.pendingChanges', { count: unsavedCount }) }}\n </span>\n </div>\n\n <div\n v-if=\"loading\"\n class=\"a-le-state a-le-state--loading\"\n >\n <VProgressCircular\n indeterminate\n color=\"primary\"\n size=\"32\"\n />\n </div>\n\n <div\n v-else-if=\"error\"\n class=\"a-le-state a-le-state--error\"\n >\n <VAlert\n type=\"error\"\n variant=\"tonal\"\n density=\"compact\"\n border=\"start\"\n >\n {{ error }}\n </VAlert>\n </div>\n\n <div\n v-else-if=\"isEmpty\"\n class=\"a-le-state a-le-state--empty\"\n >\n <slot\n name=\"empty\"\n :readonly=\"readonly\"\n :disabled=\"disabled\"\n :actions=\"{ add: onAddClick }\"\n >\n <LeEmptyState\n :title=\"emptyTitleResolved\"\n :add-label=\"addLabelResolved\"\n :can-add=\"canAdd\"\n @add=\"onAddClick\"\n />\n </slot>\n </div>\n\n <template v-else>\n <div\n v-for=\"vi in viewItemsDecorated\"\n :key=\"String(vi.key)\"\n class=\"a-le-row-wrapper\"\n >\n <!-- Interstitial slots render as SIBLINGS of `.a-le-row` (inside this\n wrapper), so consumer content placed between rows never inherits the\n row's unsaved/editing/validation tint. (QA 85050 BUG-04/05) -->\n <slot\n name=\"before-item\"\n v-bind=\"buildSlotProps(vi)\"\n />\n\n <div\n :data-id=\"String(vi.key)\"\n role=\"listitem\"\n :tabindex=\"keyboardNav.rowTabindex(vi.key)\"\n class=\"a-le-row\"\n :class=\"{\n 'a-le-row--two-rows': twoRows === 'always',\n 'a-le-row--editing': vi.editing,\n 'a-le-row--expanded': vi.expanded,\n 'a-le-row--unsaved': vi.unsaved,\n 'a-le-row--clickable': isRowClickable(vi),\n [`a-le-row--validation-${vi.validationState}`]: vi.validationState !== null,\n }\"\n @keydown=\"keyboardNav.handleKeydown(vi.key, $event)\"\n >\n <div\n class=\"a-le-row-header\"\n @click=\"onRowClick(vi)\"\n >\n <div class=\"a-le-row-main\">\n <slot\n name=\"item-compact\"\n v-bind=\"buildSlotProps(vi)\"\n >\n <span class=\"a-le-title\">\n {{ resolveCompactText(vi.raw) }}\n </span>\n </slot>\n <LeUnsavedLabel\n v-if=\"vi.unsaved\"\n :dot-only=\"chips\"\n />\n </div>\n\n <div\n v-if=\"!chips\"\n class=\"a-le-status\"\n >\n <slot\n name=\"item-status\"\n v-bind=\"buildSlotProps(vi)\"\n >\n <span\n v-if=\"statusField && vi.raw[statusField] != null && vi.raw[statusField] !== ''\"\n class=\"a-le-status-badge\"\n >\n {{ vi.raw[statusField] }}\n </span>\n </slot>\n </div>\n\n <div class=\"a-le-actions\">\n <slot\n name=\"item-actions\"\n v-bind=\"buildSlotProps(vi)\"\n >\n <VBtn\n v-if=\"chips && showDeleteButton && canInteract\"\n icon\n size=\"x-small\"\n variant=\"text\"\n density=\"compact\"\n :active=\"false\"\n class=\"a-le-action a-le-action--chip-close\"\n @click.stop=\"onDeleteClick(vi)\"\n >\n <VIcon\n icon=\"mdi-close\"\n size=\"14\"\n />\n </VBtn>\n <template v-else>\n <VBtn\n v-if=\"showEditButton && canInteract && !defaultExpanded\"\n icon\n size=\"small\"\n variant=\"tonal\"\n color=\"primary\"\n density=\"comfortable\"\n class=\"mx-1 a-le-action a-le-action--edit\"\n @click.stop=\"onEditClick(vi)\"\n >\n <VIcon\n icon=\"mdi-pencil-outline\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.edit')\"\n />\n </VBtn>\n <VBtn\n v-if=\"showDeleteButton && canInteract\"\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n class=\"mx-1 a-le-action a-le-action--delete\"\n @click.stop=\"onDeleteClick(vi)\"\n >\n <VIcon\n icon=\"mdi-trash-can-outline\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.delete')\"\n />\n </VBtn>\n <VBtn\n v-if=\"showAddAfterAction && canInteract\"\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :active=\"false\"\n class=\"mx-1 a-le-action a-le-action--menu\"\n >\n <VIcon\n icon=\"mdi-dots-vertical\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.more')\"\n />\n <VMenu activator=\"parent\">\n <VList density=\"compact\">\n <VListItem @click=\"onRowAddAfterClick(vi)\">\n <template #prepend>\n <VIcon icon=\"mdi-plus\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.addAfter') }}\n </VListItemTitle>\n </VListItem>\n </VList>\n </VMenu>\n </VBtn>\n </template>\n </slot>\n </div>\n </div>\n\n <template v-if=\"(vi.editing || defaultExpanded) && $slots.item\">\n <div class=\"a-le-row-body\">\n <div class=\"a-le-form\">\n <slot\n name=\"item\"\n v-bind=\"buildSlotProps(vi)\"\n />\n </div>\n </div>\n <slot\n name=\"item-footer\"\n v-bind=\"buildSlotProps(vi)\"\n >\n <div\n v-if=\"showInlineSaveFooter && !defaultExpanded\"\n class=\"a-le-row-footer\"\n >\n <div class=\"a-le-row-footer-spacer\" />\n <VBtn\n variant=\"text\"\n :disabled=\"vi.loading\"\n @click.stop=\"onCancelClick(vi)\"\n >\n {{ t('common.button.cancel') }}\n </VBtn>\n <VBtn\n color=\"primary\"\n variant=\"flat\"\n prepend-icon=\"mdi-check\"\n :disabled=\"vi.loading\"\n @click.stop=\"onSaveClick(vi)\"\n >\n {{ t('common.button.save') }}\n </VBtn>\n </div>\n </slot>\n </template>\n\n <div\n v-else-if=\"$slots['item-readonly'] && (readonly || vi.expanded)\"\n class=\"a-le-row-body\"\n >\n <div class=\"a-le-form\">\n <slot\n name=\"item-readonly\"\n v-bind=\"buildSlotProps(vi)\"\n />\n </div>\n </div>\n </div>\n\n <slot\n name=\"after-item\"\n v-bind=\"buildSlotProps(vi)\"\n />\n </div>\n </template>\n\n <slot\n v-if=\"canAdd && !loading && !error && !isEmpty\"\n name=\"add-button\"\n :readonly=\"readonly\"\n :disabled=\"disabled\"\n :props=\"{ onClick: onAddClick }\"\n :actions=\"{ add: onAddClick }\"\n >\n <button\n type=\"button\"\n class=\"a-le-row-add\"\n @click=\"onAddClick\"\n >\n <VIcon\n icon=\"mdi-plus\"\n size=\"18\"\n />\n <span>{{ addLabelResolved }}</span>\n </button>\n </slot>\n </div>\n\n <LeDeleteDialog\n v-model=\"deleteDialog\"\n :title=\"deleteConfirmTitleResolved\"\n :text=\"deleteConfirmTextResolved\"\n :confirm-label=\"t('common.sortable.delete')\"\n :cancel-label=\"t('common.button.cancel')\"\n :error=\"deleteError\"\n :in-flight=\"deleteInFlight\"\n @confirm=\"onDeleteDialogConfirm\"\n @cancel=\"onDeleteDialogCancel\"\n />\n\n <div\n class=\"a-le-sr-only\"\n aria-live=\"polite\"\n role=\"status\"\n >\n {{\n keyboardNav.grabbedKey.value !== null\n ? t('common.sortable.keyboardGrab.status', {\n n: keyboardNav.grabbedIndex.value + 1,\n total: keyboardNav.totalCount.value,\n })\n : ''\n }}\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n@use './styles/tokens';\n@use './styles/shared';\n\n// Variant-specific rules, scoped under the AListEditor root.\n.a-list-editor {\n // Chips: 12px left padding (flat) vs 8px in sortable, which reserves drag-handle room.\n &--chips .a-le-row-header {\n padding: 2px 4px 2px 12px;\n gap: 4px;\n min-height: 28px;\n }\n}\n\n// Narrow/mobile layout: taller rows, always-visible actions, status badge dropped for room.\n/* stylelint-disable selector-max-compound-selectors */\n@container le-shell (max-width: 768px) {\n .a-list-editor {\n --le-row-min-height: 48px;\n --le-row-pad-y: 10px;\n\n .a-le-row:not(.a-le-row--editing) .a-le-status {\n display: none;\n }\n\n .a-le-row .a-le-action--edit,\n .a-le-row .a-le-action--delete,\n .a-le-row .a-le-action--menu {\n opacity: 1;\n }\n }\n}\n/* stylelint-enable selector-max-compound-selectors */\n</style>\n","<script setup lang=\"ts\" generic=\"TItem extends Record<string, any>\">\nimport { computed, ref, useSlots, useTemplateRef, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport { useDisplay } from 'vuetify'\nimport { useKeyboardNav } from '@/labs/listEditor/composables/useKeyboardNav'\nimport {\n useListEditorController,\n type GetKey,\n type ListEditorHandle,\n type ListEditorValidationResult,\n type PositionAction,\n type PositionOption,\n type PositionStrategy,\n} from '@/labs/listEditor/composables/useListEditorController'\nimport {\n provideListEditorStateScope,\n useListEditorStateEntry,\n type ListEditorStateBindings,\n} from '@/labs/listEditor/composables/useListEditorStateScope'\nimport { resolveCompactText as resolveCompactTextUtil } from '@/labs/listEditor/composables/resolveCompactText'\nimport { useUnsavedSection } from '@/labs/unsavedGuard/useUnsavedSection'\nimport { useDeleteDialog } from '@/labs/listEditor/composables/useDeleteDialog'\nimport { useInlineEditing } from '@/labs/listEditor/composables/useInlineEditing'\nimport { validateAllAndReveal } from '@/labs/listEditor/utils/revealInvalidRows'\nimport { useListEditorScopeValidity } from '@/labs/listEditor/composables/useListEditorScopeValidity'\nimport LeDeleteDialog from '@/labs/listEditor/internal/LeDeleteDialog.vue'\nimport LeEmptyState from '@/labs/listEditor/internal/LeEmptyState.vue'\nimport LeUnsavedLabel from '@/labs/listEditor/internal/LeUnsavedLabel.vue'\nimport type {\n ListEditorKey,\n ListEditorValidationScope,\n ListEditorValidationState,\n ListViewItem,\n} from '@/labs/listEditor/types/listEditorTypes'\n\nexport interface DecoratedViewItem<T> extends ListViewItem<T> {\n editing: boolean\n expanded: boolean\n loading: boolean\n dirty: boolean\n unsaved: boolean\n validationState: ListEditorValidationState\n}\n\n// Hoisted for vite-plugin-dts d.ts rollup.\nexport interface RowActions<TItem> {\n edit: () => void\n save: () => Promise<void> | void\n cancel: () => void\n close: () => void\n delete: () => Promise<void>\n addAfter: () => void\n toggleExpand: () => void\n update: (next: TItem | Partial<TItem> | ((current: TItem) => TItem)) => void\n}\nexport interface RowSlotProps<TItem extends Record<string, any>> {\n item: DecoratedViewItem<TItem>\n raw: TItem\n index: number\n key: ListEditorKey\n readonly: boolean\n disabled: boolean\n expanded: boolean\n editing: boolean\n dirty: boolean\n /** Aliases `dirty` here (no reorder mode → no `moved` to OR). Same field name as on sortable/nested variants. */\n unsaved: boolean\n touch: boolean\n /**\n * Row-scoped `state-key` prefix. Thread it down to any list editor rendered in this slot and set\n * its `:state-key=\"`${stateKeyPrefix}:someList`\"` — that editor's controller then lives in THIS\n * editor's scope and survives the row collapsing (which unmounts the whole slot subtree). Entries\n * under this prefix are disposed when the row is removed.\n */\n stateKeyPrefix: string\n actions: RowActions<TItem>\n}\nexport interface EmptySlotProps {\n readonly: boolean\n disabled: boolean\n actions: { add: () => void }\n}\nexport interface AddButtonSlotProps {\n readonly: boolean\n disabled: boolean\n props: { onClick: () => void }\n actions: { add: () => void }\n}\nexport interface HeaderSlotProps {\n title: string | null\n}\n\nexport interface Props<TItem extends Record<string, any>> {\n /**\n * New-row factory. Add / \"add after\" insert `factory()` through the controller\n * (positions renumbered) — no consumer `@add` push handler is needed.\n */\n factory?: () => TItem\n /**\n * Stable row identity. Default `'id'`; never the position field. Typed `string`\n * not `keyof TItem` because the latter compiles to a Boolean-only runtime prop\n * type that silently coerces `get-key=\"id\"` to `true`.\n */\n getKey?: string | ((item: TItem) => ListEditorKey)\n /**\n * Managed order field. Default `'position'`; `false` opts out. `string` must be\n * listed before `false` (and not be `keyof TItem`): otherwise Vue's runtime\n * type goes Boolean-first and coerces `position=\"position\"` to `true`.\n */\n position?:\n | string\n | false\n | {\n field: string\n multiplier?: number\n strategy?: PositionStrategy\n strategyOverrides?: Partial<Record<PositionAction, PositionStrategy>>\n }\n /**\n * Extra fields to drop from the dirty content-hash (position is always dropped).\n * Use when a SEPARATE nested editor already tracks a child collection, so editing\n * a child doesn't flip the parent row amber. Still saved; only the diff ignores it.\n */\n dirtyExclude?: string[]\n /** Per-row validity — `true` (or `{ valid: true }`) = VALID. Drives the red rail + save guard. */\n validate?: (item: TItem) => ListEditorValidationResult\n /**\n * Vuelidate `$scope` (the same one the consumer's row forms / save-gate collector use). When set,\n * the editor registers its aggregate validity under it, so a plain `v$.$invalid` save gate blocks\n * AND reveals a collapsed invalid row — no `validateAll()` call needed in the save flow. Omit to\n * keep legacy behavior (gate the save on the exposed `validateAll()` yourself); `false` opts out.\n */\n validationScope?: ListEditorValidationScope | false\n /**\n * Opt-in lifted controller from `useListEditorController()` — pass it so editor\n * state survives this component's unmount/remount. Omitted: the editor owns one\n * internally. Either way the `ListEditorHandle` is reachable via `useTemplateRef`.\n */\n editor?: ListEditorHandle<TItem>\n /**\n * Persist this editor's controller in the nearest ANCESTOR editor's row-state scope under this\n * key, instead of owning it. Use it for an editor rendered in another editor's `#item` slot: that\n * slot is behind a `v-if`, so collapsing the row unmounts this editor and a component-owned\n * controller would re-baseline the already-edited data on re-expand (losing amber, tombstones and\n * the red rail). Derive the key from the row slot's `stateKeyPrefix`, e.g.\n * `:state-key=\"`${stateKeyPrefix}:adverts`\"`. Options stay declared here, on this tag: the\n * controller is built from THIS editor's `factory`/`get-key`/`position`/`dirty-exclude`/`validate`\n * and rebound to each new instance's model on remount. Ignored when `:editor` is passed, and a\n * no-op without an ancestor scope.\n */\n stateKey?: string\n\n readonly?: boolean\n disabled?: boolean\n loading?: boolean\n error?: string | null\n\n /** Heading above the list. A ready translated string, like every text prop here. */\n title?: string | null\n\n /**\n * Field shown in a collapsed row when no `#item-compact` slot is given. A dotted path reads\n * through nested objects (`texts.title`); a path that runs into nothing renders empty.\n */\n compactField?: string | null\n statusField?: string | null\n twoRows?: 'never' | 'mobile' | 'always'\n chips?: boolean\n\n showAddButton?: boolean\n showDeleteButton?: boolean\n showEditButton?: boolean\n showAddAfterAction?: boolean\n\n /** Label of the add button. A ready translated string; omit it for the library's own wording. */\n addLabel?: string | null\n /** Heading of the empty state. A ready translated string. */\n emptyTitle?: string | null\n\n disableRowClick?: boolean\n disableDeleteConfirm?: boolean\n /** Disable unsaved-state tracking — no dirty markers, never reads as unsaved. */\n disableUnsaved?: boolean\n deleteConfirmTitle?: string | null\n deleteConfirmText?: string | null\n /**\n * How a row delete persists. `deferred` (default): the row disappears but the deletion counts as an\n * unconfirmed change (`unsavedCount`) until save, and is revertible until then. `immediate`: the\n * consumer deletes on the backend (`:on-delete`) — the dialog states it is irreversible and the\n * delete does NOT read as unsaved.\n */\n deleteMode?: 'immediate' | 'deferred'\n\n closeVariant?: 'auto' | 'icon' | 'labeled'\n\n /**\n * Render every `#item` slot expanded — no edit pencil, inline footer, or row-click\n * toggle. Use when all forms should be visible at once (e.g. ThirdPartyTracker).\n */\n defaultExpanded?: boolean\n\n loadingKeys?: Set<ListEditorKey> | null\n\n onDeleteConfirm?: (item: TItem) => Promise<boolean> | boolean\n onDelete?: (item: TItem) => Promise<void> | void\n onItemSave?: (item: TItem) => Promise<void> | void\n\n /**\n * Registers this editor as a named unsaved-changes section under this (already\n * translated) label — replaces a per-consumer `useUnsavedSection` call.\n */\n unsavedSectionLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props<TItem>>(), {\n factory: undefined,\n getKey: undefined,\n position: undefined,\n dirtyExclude: undefined,\n validate: undefined,\n validationScope: undefined,\n editor: undefined,\n stateKey: undefined,\n readonly: false,\n disabled: false,\n loading: false,\n error: null,\n title: null,\n compactField: null,\n statusField: null,\n twoRows: 'never',\n chips: false,\n showAddButton: true,\n showDeleteButton: true,\n showEditButton: true,\n showAddAfterAction: false,\n addLabel: null,\n emptyTitle: null,\n disableRowClick: false,\n disableDeleteConfirm: false,\n disableUnsaved: false,\n deleteConfirmTitle: null,\n deleteConfirmText: null,\n deleteMode: 'deferred',\n closeVariant: 'auto',\n defaultExpanded: false,\n loadingKeys: null,\n onDeleteConfirm: undefined,\n onDelete: undefined,\n onItemSave: undefined,\n unsavedSectionLabel: undefined,\n})\n\nconst emit = defineEmits<{\n edit: [item: ListViewItem<TItem>]\n deleted: [item: ListViewItem<TItem>]\n close: [item: ListViewItem<TItem>]\n 'item-saved': [item: ListViewItem<TItem>]\n 'item-expand': [item: ListViewItem<TItem>, expanded: boolean]\n}>()\n\ndefineSlots<{\n header?: (props: HeaderSlotProps) => unknown\n empty?: (props: EmptySlotProps) => unknown\n 'add-button'?: (props: AddButtonSlotProps) => unknown\n item?: (props: RowSlotProps<TItem>) => unknown\n 'item-compact'?: (props: RowSlotProps<TItem>) => unknown\n 'item-readonly'?: (props: RowSlotProps<TItem>) => unknown\n 'item-status'?: (props: RowSlotProps<TItem>) => unknown\n 'item-actions'?: (props: RowSlotProps<TItem>) => unknown\n 'item-footer'?: (props: RowSlotProps<TItem>) => unknown\n 'before-item'?: (props: RowSlotProps<TItem>) => unknown\n 'after-item'?: (props: RowSlotProps<TItem>) => unknown\n}>()\n\nconst modelValue = defineModel<TItem[]>({ required: true })\n\nconst { t } = useI18n()\nconst slots = useSlots()\nconst display = useDisplay()\n\nconst rootEl = useTemplateRef<HTMLElement>('rootEl')\n\nconst isTouch = computed<boolean>(() => display.platform.value.touch)\n\n// State controller (v2). `factory`/`getKey`/`position`/`validate` are construction-time options read\n// once to seed it (undefined → controller defaults 'id'/'position'/always-valid), not reactive props\n// — so the reactivity-loss rule is intentionally suppressed here.\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst controllerOptions: ListEditorStateBindings<TItem> = {\n get: () => modelValue.value,\n set: (v) => (modelValue.value = v),\n factory: props.factory,\n getKey: props.getKey as GetKey<TItem> | undefined,\n position: props.position as PositionOption<TItem> | undefined,\n dirtyExclude: () => props.dirtyExclude ?? [],\n validate: props.validate,\n}\n// Precedence: an explicitly lifted `:editor` wins; then a `state-key`'d entry persisted in the\n// nearest ancestor editor's row-state scope (built from the options above, rebound to this instance\n// on every remount); else this component owns the controller — today's behaviour, unchanged.\nconst stateEntry = props.editor ? null : useListEditorStateEntry<TItem>(props.stateKey, controllerOptions)\nconst controller = props.editor ?? stateEntry?.handle ?? useListEditorController<TItem>(controllerOptions)\n\n// Mirror the controller's key resolution so rendered rows key the way it tracks them.\nconst getKeyOpt = props.getKey ?? 'id'\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\n// Row-state scope for THIS editor's `#item` slot subtree: a nested editor with a `state-key` builds\n// its controller in here, so it outlives the row collapsing (which unmounts the slot). When this\n// editor is itself persisted, the scope comes from its own entry — so the chain holds at any depth.\nconst rowStateScope = provideListEditorStateScope(stateEntry?.childScope ?? null)\nconst keyOf = (item: TItem): ListEditorKey =>\n typeof getKeyOpt === 'function' ? getKeyOpt(item) : (item[getKeyOpt as keyof TItem] as ListEditorKey)\n\nconst keyFieldName = computed<string>(() => (typeof getKeyOpt === 'function' ? '(fn)' : (getKeyOpt as string)))\n\n// Managed position field name, mirroring the controller's resolution.\nconst positionFieldName = computed<string>(() => {\n const p = props.position\n if (p === undefined) return 'position'\n if (p === false) return 'position'\n if (typeof p === 'object') return p.field as string\n return p as string\n})\n\n// Render projection only — the controller owns the data (key + index + raw + position).\nconst viewItems = computed<ListViewItem<TItem>[]>(() =>\n modelValue.value.map((raw, index) => ({\n key: keyOf(raw),\n index,\n raw,\n position: raw[positionFieldName.value] as number | undefined,\n }))\n)\n\n// Surfaces row-key wiring bugs loudly: undefined or duplicate keys silently break\n// dirty tracking, validity rails and reorder targeting and are hard to trace from\n// symptoms. Deduped per signature so a stable bad state warns once, a new one again.\nconst warnedKeySignatures = new Set<string>()\nconst warnOnBadKeys = (items: ListViewItem<TItem>[]): void => {\n const seen = new Set<ListEditorKey>()\n const duplicates = new Set<string>()\n let missing = 0\n for (const vi of items) {\n if (vi.key === undefined || vi.key === null) {\n missing++\n continue\n }\n if (seen.has(vi.key)) duplicates.add(String(vi.key))\n seen.add(vi.key)\n }\n if (missing === 0 && duplicates.size === 0) return\n const signature = `${missing}|${[...duplicates].sort().join(',')}`\n if (warnedKeySignatures.has(signature)) return\n warnedKeySignatures.add(signature)\n if (missing > 0) {\n console.warn(\n `[list-editor] ${missing} row(s) resolve to an undefined key (key-field \"${keyFieldName.value}\"). ` +\n 'Point get-key at a field every item has, or give new items unique temp ids ' +\n '(see nextListEditorTempId).'\n )\n }\n if (duplicates.size > 0) {\n console.warn(\n `[list-editor] duplicate row keys (key-field \"${keyFieldName.value}\"): ${[...duplicates].join(', ')}. ` +\n 'Row keys must be unique — dirty tracking and validation rails target rows by key.'\n )\n }\n}\nwatch(viewItems, (items) => warnOnBadKeys(items), { immediate: true })\n\n// Drop the persisted controllers of rows that no longer exist. `post` so the removed row's subtree\n// has already unmounted (its editors released their entries) before we stop their effect scopes.\nwatch(\n () => viewItems.value.map((vi) => String(vi.key)).join('|'),\n () => rowStateScope.retainOwners(viewItems.value.map((vi) => vi.key)),\n { flush: 'post' }\n)\n\nconst expandedKeys = ref<Set<ListEditorKey>>(new Set())\n\nconst rowsContainer = useTemplateRef<HTMLElement>('rowsContainer')\n\nconst isInlineEdit = computed(() => !props.chips && !!slots.item)\nconst hasReadonlyDetail = computed(() => !props.chips && !!slots['item-readonly'])\n\nconst { editingKeys, editingSnapshots, beginEdit, cancelEdit, commitEdit, closeEdit, requestAutoOpen } =\n useInlineEditing<TItem, ListViewItem<TItem>>({\n rowsContainer,\n rowSelector: '.a-le-row',\n isInlineEdit,\n restoreSnapshot: (key, data) => controller.updateItem(key, data),\n watchKeys: () => modelValue.value.map((it) => keyOf(it)),\n findEntry: (key) => {\n const hit = modelValue.value.find((it) => keyOf(it) === key)\n return hit ? { data: hit } : null\n },\n afterAutoOpen: (key) => {\n expandedKeys.value.delete(key)\n },\n })\n\nconst addLabelResolved = computed(() => props.addLabel ?? t('common.sortable.add'))\nconst emptyTitleResolved = computed(() => props.emptyTitle ?? t('common.sortable.emptyTitle'))\nconst deleteConfirmTitleResolved = computed(() => props.deleteConfirmTitle ?? t('common.sortable.deleteConfirmTitle'))\nconst deleteConfirmTextResolved = computed(\n () =>\n props.deleteConfirmText ??\n (props.deleteMode === 'immediate'\n ? t('common.sortable.deleteConfirmText')\n : t('common.sortable.deleteConfirmTextDeferred'))\n)\n\nconst canInteract = computed(() => !props.readonly && !props.disabled && !props.loading)\nconst canAdd = computed(() => canInteract.value && props.showAddButton)\n\n// Total unconfirmed-change count (added/edited/moved rows + deferred deletions). A delete lights this\n// up even though its row is gone. Shown as a header badge + exposed on the handle.\nconst unsavedCount = controller.unsavedCount\nconst unsavedCountVisible = computed(() => !props.readonly && !props.disableUnsaved && unsavedCount.value > 0)\nconst headerVisible = computed(() => !!(props.title || slots.header) || unsavedCountVisible.value)\n\n// Per-row Save/Cancel footer only makes sense with a per-item persist callback;\n// without one the parent form's global save flushes everything, so hide it.\nconst showInlineSaveFooter = computed(() => !!props.onItemSave)\n\n// Per-key decorator cache: reuse the cached object when base item AND every flag\n// match, giving slot consumers a stable reference for unchanged rows. `disableUnsaved`\n// suppresses only the amber marker — the validation rail still shows, since hiding\n// dirty-state shouldn't hide a real error.\nconst decoratorCache = new Map<ListEditorKey, DecoratedViewItem<TItem>>()\nconst viewItemsDecorated = computed<DecoratedViewItem<TItem>[]>(() => {\n const next: DecoratedViewItem<TItem>[] = []\n const liveKeys = new Set<ListEditorKey>()\n for (const vi of viewItems.value) {\n liveKeys.add(vi.key)\n const editing = editingKeys.value.has(vi.key)\n const expanded = expandedKeys.value.has(vi.key)\n const loading = props.loadingKeys?.has(vi.key) ?? false\n // Readonly can't have user-unsaved changes; never paint amber there (also avoids\n // a mount-before-load baseline marking every loaded row added). (QA 85050 sweep)\n const unsaved = props.disableUnsaved || props.readonly ? false : controller.isUnsaved(vi.key)\n const dirty = unsaved\n // `editing` → the controller reads amber (not red) while the row is being filled in. (QA 85050 b7)\n const validationState = controller.rowState(vi.raw, vi.key, editing)\n const cached = decoratorCache.get(vi.key)\n if (\n cached &&\n cached.raw === vi.raw &&\n cached.index === vi.index &&\n cached.position === vi.position &&\n cached.editing === editing &&\n cached.expanded === expanded &&\n cached.loading === loading &&\n cached.dirty === dirty &&\n cached.unsaved === unsaved &&\n cached.validationState === validationState\n ) {\n next.push(cached)\n continue\n }\n const decorated: DecoratedViewItem<TItem> = {\n ...vi,\n editing,\n expanded,\n loading,\n dirty,\n unsaved,\n validationState,\n }\n decoratorCache.set(vi.key, decorated)\n next.push(decorated)\n }\n for (const key of decoratorCache.keys()) {\n if (!liveKeys.has(key)) decoratorCache.delete(key)\n }\n return next\n})\n\nconst isEmpty = computed(() => viewItemsDecorated.value.length === 0)\n\nconst findVi = (key: ListEditorKey): DecoratedViewItem<TItem> | undefined =>\n viewItemsDecorated.value.find((v) => v.key === key)\n\nconst keyboardNav = useKeyboardNav({\n viewItems: computed(() => viewItemsDecorated.value.map((vi) => ({ key: vi.key }))),\n variant: 'flat',\n isReorderMode: ref(false),\n disabled: computed(() => !canInteract.value),\n isEditing: (key) => editingKeys.value.has(key),\n onToggleEdit: (key) => {\n const vi = findVi(key)\n if (vi) onEditClick(vi)\n },\n onCancelEdit: (key) => {\n const vi = findVi(key)\n if (vi) onCloseClick(vi)\n },\n})\n\nconst resolveCompactText = (raw: TItem): string => resolveCompactTextUtil(raw, { compactField: props.compactField })\n\n// Managed add: the controller inserts `factory()` and renumbers; the inline-editing\n// watch picks up the new key off the model change and auto-opens it.\nconst onAddClick = () => {\n if (!canAdd.value) return\n requestAutoOpen()\n controller.addItem(undefined, undefined)\n}\n\nconst onRowAddAfterClick = (vi: ListViewItem<TItem>) => {\n if (!canInteract.value) return\n requestAutoOpen()\n controller.addItem(undefined, { afterId: vi.key })\n}\n\nconst onEditClick = (vi: ListViewItem<TItem>) => {\n if (!canInteract.value) return\n // Toggle: edit while already editing closes the form, matching row-header click.\n if (editingKeys.value.has(vi.key)) {\n onCloseClick(vi)\n return\n }\n if (isInlineEdit.value) {\n beginEdit(vi)\n expandedKeys.value.delete(vi.key)\n }\n emit('edit', vi)\n}\n\nconst onExpandClick = (vi: ListViewItem<TItem>) => {\n if (props.disabled || props.loading) return\n const key = vi.key\n const currentlyExpanded = expandedKeys.value.has(key)\n if (currentlyExpanded) {\n expandedKeys.value.delete(key)\n } else {\n for (const k of expandedKeys.value) expandedKeys.value.delete(k)\n expandedKeys.value.add(key)\n }\n emit('item-expand', vi, !currentlyExpanded)\n}\n\nconst isRowClickable = (vi: DecoratedViewItem<TItem>): boolean => {\n if (props.chips) return false\n if (props.disableRowClick) return false\n if (props.defaultExpanded) return false\n if (props.disabled || props.loading) return false\n if (vi.editing || vi.expanded) return true\n if (!props.readonly && props.showEditButton) return true\n return false\n}\n\nconst onRowClick = (vi: DecoratedViewItem<TItem>) => {\n if (!isRowClickable(vi)) return\n if (vi.editing || vi.expanded) {\n onCloseClick(vi)\n return\n }\n if (props.readonly && hasReadonlyDetail.value) {\n onExpandClick(vi)\n } else {\n onEditClick(vi)\n }\n}\n\nconst {\n deleteDialog,\n deleteInFlight,\n deleteError,\n onDeleteClick: triggerDeleteClick,\n onDeleteDialogConfirm,\n onDeleteDialogCancel,\n} = useDeleteDialog<TItem, ListViewItem<TItem>>({\n onDeleteConfirm: (raw) => (props.onDeleteConfirm ? props.onDeleteConfirm(raw) : true),\n onDelete: (raw) => props.onDelete?.(raw),\n onDeleted: (vi) => {\n editingKeys.value.delete(vi.key)\n editingSnapshots.value.delete(vi.key)\n expandedKeys.value.delete(vi.key)\n // Controller owns removal: a temp row vanishes; a saved row is recorded in `getChanges().deleted`\n // and counts as an unconfirmed change (deferred) — unless immediate mode (already deleted on the\n // backend), which skips the tombstone.\n controller.deleteItem(vi.key, { trackDeleted: props.deleteMode === 'deferred' })\n emit('deleted', vi)\n },\n disableDeleteConfirm: () => props.disableDeleteConfirm || props.chips,\n})\n\nconst onDeleteClick = async (vi: ListViewItem<TItem>) => {\n if (!canInteract.value) return\n await triggerDeleteClick(vi)\n}\n\nconst onSaveClick = async (vi: ListViewItem<TItem>) => {\n if (props.onItemSave) {\n await props.onItemSave(vi.raw)\n }\n commitEdit(vi)\n emit('item-saved', vi)\n}\n\nconst onCancelClick = (vi: ListViewItem<TItem>) => {\n cancelEdit(vi)\n}\n\nconst onCloseClick = (vi: ListViewItem<TItem>) => {\n closeEdit(vi)\n expandedKeys.value.delete(vi.key)\n emit('close', vi)\n}\n\n// Per-key actions cache: each row's `actions` bundle is allocated once so slot\n// consumers get a stable identity (no prop-ref churn → no spurious re-renders).\n// Closures capture the stable key and look up the live decorator via `findVi(key)`.\ntype ActionsBundle = {\n edit: () => void\n save: () => Promise<void> | void\n cancel: () => void\n close: () => void\n delete: () => Promise<void>\n addAfter: () => void\n toggleExpand: () => void\n update: (next: TItem | Partial<TItem> | ((current: TItem) => TItem)) => void\n}\nconst actionsCache = new Map<ListEditorKey, ActionsBundle>()\nconst getActions = (key: ListEditorKey): ActionsBundle => {\n let actions = actionsCache.get(key)\n if (!actions) {\n actions = {\n edit: () => {\n const vi = findVi(key)\n if (vi) onEditClick(vi)\n },\n save: () => {\n const vi = findVi(key)\n if (vi) return onSaveClick(vi)\n },\n cancel: () => {\n const vi = findVi(key)\n if (vi) onCancelClick(vi)\n },\n close: () => {\n const vi = findVi(key)\n if (vi) onCloseClick(vi)\n },\n delete: async () => {\n const vi = findVi(key)\n if (vi) await onDeleteClick(vi)\n },\n addAfter: () => {\n const vi = findVi(key)\n if (vi) onRowAddAfterClick(vi)\n },\n toggleExpand: () => {\n const vi = findVi(key)\n if (vi) onExpandClick(vi)\n },\n update: (next) => controller.updateItem(key, next),\n }\n actionsCache.set(key, actions)\n }\n return actions\n}\n// Garbage-collect cache entries for keys that have left the model.\nwatch(\n () => viewItemsDecorated.value,\n (now) => {\n if (actionsCache.size === 0) return\n const liveKeys = new Set(now.map((v) => v.key))\n for (const key of actionsCache.keys()) {\n if (!liveKeys.has(key)) actionsCache.delete(key)\n }\n }\n)\n\nconst buildSlotProps = (vi: DecoratedViewItem<TItem>) => ({\n item: vi,\n raw: vi.raw,\n index: vi.index,\n key: vi.key,\n readonly: props.readonly,\n disabled: props.disabled,\n expanded: vi.expanded,\n editing: vi.editing || props.defaultExpanded,\n dirty: vi.dirty,\n unsaved: vi.unsaved,\n touch: isTouch.value,\n stateKeyPrefix: rowStateScope.prefixFor(vi.key),\n actions: getActions(vi.key),\n})\n\n// Named unsaved-changes section, registered only when a label is passed.\nuseUnsavedSection(() =>\n props.unsavedSectionLabel && !props.disableUnsaved\n ? { label: props.unsavedSectionLabel, dirty: controller.hasUnsaved.value }\n : []\n)\n\n// Opens invalid rows so a blocked save surfaces WHICH rows are wrong instead of a collapsed red\n// rail. The body is gated on `editing` not `expanded`, so drive `beginEdit` not `expandedKeys`\n// (QA 85050 B4-17). Shared by the exposed `validateAll` and the scope-collector reveal-on-touch.\nconst runValidateAndReveal = (): boolean =>\n validateAllAndReveal(controller, (key) => {\n const vi = viewItems.value.find((v) => v.key === key)\n if (vi) beginEdit(vi)\n })\n\n// Auto-bridge the editor's aggregate validity into the consumer's `validation-scope` collector so a\n// plain `$invalid` save gate blocks (and reveals) a collapsed invalid row. No-op without the prop.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss -- construction-time opt-in, read once\nuseListEditorScopeValidity({\n hasErrors: controller.hasErrors,\n validationScope: props.validationScope,\n validateProvided: props.validate !== undefined,\n reveal: runValidateAndReveal,\n})\n\n// Expose the controller handle via `useTemplateRef<ListEditorHandle<TItem>>`.\ndefineExpose<ListEditorHandle<TItem>>({\n ...controller,\n validateAll: runValidateAndReveal,\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"a-list-editor\"\n :class=\"[\n `a-list-editor--two-rows-${twoRows}`,\n {\n 'a-list-editor--readonly': readonly,\n 'a-list-editor--disabled': disabled,\n 'a-list-editor--touch': isTouch,\n 'a-list-editor--chips': chips,\n },\n ]\"\n >\n <div\n ref=\"rowsContainer\"\n class=\"a-le-card\"\n >\n <div\n v-if=\"headerVisible\"\n class=\"a-le-header\"\n >\n <slot\n name=\"header\"\n :title=\"title\"\n >\n <h3\n v-if=\"title\"\n class=\"a-le-title-heading\"\n >\n {{ title }}\n </h3>\n </slot>\n <span\n v-if=\"unsavedCountVisible\"\n class=\"a-le-unsaved-count\"\n :data-unsaved-count=\"unsavedCount\"\n >\n {{ t('common.sortable.pendingChanges', { count: unsavedCount }) }}\n </span>\n </div>\n\n <div\n v-if=\"loading\"\n class=\"a-le-state a-le-state--loading\"\n >\n <VProgressCircular\n indeterminate\n color=\"primary\"\n size=\"32\"\n />\n </div>\n\n <div\n v-else-if=\"error\"\n class=\"a-le-state a-le-state--error\"\n >\n <VAlert\n type=\"error\"\n variant=\"tonal\"\n density=\"compact\"\n border=\"start\"\n >\n {{ error }}\n </VAlert>\n </div>\n\n <div\n v-else-if=\"isEmpty\"\n class=\"a-le-state a-le-state--empty\"\n >\n <slot\n name=\"empty\"\n :readonly=\"readonly\"\n :disabled=\"disabled\"\n :actions=\"{ add: onAddClick }\"\n >\n <LeEmptyState\n :title=\"emptyTitleResolved\"\n :add-label=\"addLabelResolved\"\n :can-add=\"canAdd\"\n @add=\"onAddClick\"\n />\n </slot>\n </div>\n\n <template v-else>\n <div\n v-for=\"vi in viewItemsDecorated\"\n :key=\"String(vi.key)\"\n class=\"a-le-row-wrapper\"\n >\n <!-- Interstitial slots render as SIBLINGS of `.a-le-row` (inside this\n wrapper), so consumer content placed between rows never inherits the\n row's unsaved/editing/validation tint. (QA 85050 BUG-04/05) -->\n <slot\n name=\"before-item\"\n v-bind=\"buildSlotProps(vi)\"\n />\n\n <div\n :data-id=\"String(vi.key)\"\n role=\"listitem\"\n :tabindex=\"keyboardNav.rowTabindex(vi.key)\"\n class=\"a-le-row\"\n :class=\"{\n 'a-le-row--two-rows': twoRows === 'always',\n 'a-le-row--editing': vi.editing,\n 'a-le-row--expanded': vi.expanded,\n 'a-le-row--unsaved': vi.unsaved,\n 'a-le-row--clickable': isRowClickable(vi),\n [`a-le-row--validation-${vi.validationState}`]: vi.validationState !== null,\n }\"\n @keydown=\"keyboardNav.handleKeydown(vi.key, $event)\"\n >\n <div\n class=\"a-le-row-header\"\n @click=\"onRowClick(vi)\"\n >\n <div class=\"a-le-row-main\">\n <slot\n name=\"item-compact\"\n v-bind=\"buildSlotProps(vi)\"\n >\n <span class=\"a-le-title\">\n {{ resolveCompactText(vi.raw) }}\n </span>\n </slot>\n <LeUnsavedLabel\n v-if=\"vi.unsaved\"\n :dot-only=\"chips\"\n />\n </div>\n\n <div\n v-if=\"!chips\"\n class=\"a-le-status\"\n >\n <slot\n name=\"item-status\"\n v-bind=\"buildSlotProps(vi)\"\n >\n <span\n v-if=\"statusField && vi.raw[statusField] != null && vi.raw[statusField] !== ''\"\n class=\"a-le-status-badge\"\n >\n {{ vi.raw[statusField] }}\n </span>\n </slot>\n </div>\n\n <div class=\"a-le-actions\">\n <slot\n name=\"item-actions\"\n v-bind=\"buildSlotProps(vi)\"\n >\n <VBtn\n v-if=\"chips && showDeleteButton && canInteract\"\n icon\n size=\"x-small\"\n variant=\"text\"\n density=\"compact\"\n :active=\"false\"\n class=\"a-le-action a-le-action--chip-close\"\n @click.stop=\"onDeleteClick(vi)\"\n >\n <VIcon\n icon=\"mdi-close\"\n size=\"14\"\n />\n </VBtn>\n <template v-else>\n <VBtn\n v-if=\"showEditButton && canInteract && !defaultExpanded\"\n icon\n size=\"small\"\n variant=\"tonal\"\n color=\"primary\"\n density=\"comfortable\"\n class=\"mx-1 a-le-action a-le-action--edit\"\n @click.stop=\"onEditClick(vi)\"\n >\n <VIcon\n icon=\"mdi-pencil-outline\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.edit')\"\n />\n </VBtn>\n <VBtn\n v-if=\"showDeleteButton && canInteract\"\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n class=\"mx-1 a-le-action a-le-action--delete\"\n @click.stop=\"onDeleteClick(vi)\"\n >\n <VIcon\n icon=\"mdi-trash-can-outline\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.delete')\"\n />\n </VBtn>\n <VBtn\n v-if=\"showAddAfterAction && canInteract\"\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :active=\"false\"\n class=\"mx-1 a-le-action a-le-action--menu\"\n >\n <VIcon\n icon=\"mdi-dots-vertical\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.more')\"\n />\n <VMenu activator=\"parent\">\n <VList density=\"compact\">\n <VListItem @click=\"onRowAddAfterClick(vi)\">\n <template #prepend>\n <VIcon icon=\"mdi-plus\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.addAfter') }}\n </VListItemTitle>\n </VListItem>\n </VList>\n </VMenu>\n </VBtn>\n </template>\n </slot>\n </div>\n </div>\n\n <template v-if=\"(vi.editing || defaultExpanded) && $slots.item\">\n <div class=\"a-le-row-body\">\n <div class=\"a-le-form\">\n <slot\n name=\"item\"\n v-bind=\"buildSlotProps(vi)\"\n />\n </div>\n </div>\n <slot\n name=\"item-footer\"\n v-bind=\"buildSlotProps(vi)\"\n >\n <div\n v-if=\"showInlineSaveFooter && !defaultExpanded\"\n class=\"a-le-row-footer\"\n >\n <div class=\"a-le-row-footer-spacer\" />\n <VBtn\n variant=\"text\"\n :disabled=\"vi.loading\"\n @click.stop=\"onCancelClick(vi)\"\n >\n {{ t('common.button.cancel') }}\n </VBtn>\n <VBtn\n color=\"primary\"\n variant=\"flat\"\n prepend-icon=\"mdi-check\"\n :disabled=\"vi.loading\"\n @click.stop=\"onSaveClick(vi)\"\n >\n {{ t('common.button.save') }}\n </VBtn>\n </div>\n </slot>\n </template>\n\n <div\n v-else-if=\"$slots['item-readonly'] && (readonly || vi.expanded)\"\n class=\"a-le-row-body\"\n >\n <div class=\"a-le-form\">\n <slot\n name=\"item-readonly\"\n v-bind=\"buildSlotProps(vi)\"\n />\n </div>\n </div>\n </div>\n\n <slot\n name=\"after-item\"\n v-bind=\"buildSlotProps(vi)\"\n />\n </div>\n </template>\n\n <slot\n v-if=\"canAdd && !loading && !error && !isEmpty\"\n name=\"add-button\"\n :readonly=\"readonly\"\n :disabled=\"disabled\"\n :props=\"{ onClick: onAddClick }\"\n :actions=\"{ add: onAddClick }\"\n >\n <button\n type=\"button\"\n class=\"a-le-row-add\"\n @click=\"onAddClick\"\n >\n <VIcon\n icon=\"mdi-plus\"\n size=\"18\"\n />\n <span>{{ addLabelResolved }}</span>\n </button>\n </slot>\n </div>\n\n <LeDeleteDialog\n v-model=\"deleteDialog\"\n :title=\"deleteConfirmTitleResolved\"\n :text=\"deleteConfirmTextResolved\"\n :confirm-label=\"t('common.sortable.delete')\"\n :cancel-label=\"t('common.button.cancel')\"\n :error=\"deleteError\"\n :in-flight=\"deleteInFlight\"\n @confirm=\"onDeleteDialogConfirm\"\n @cancel=\"onDeleteDialogCancel\"\n />\n\n <div\n class=\"a-le-sr-only\"\n aria-live=\"polite\"\n role=\"status\"\n >\n {{\n keyboardNav.grabbedKey.value !== null\n ? t('common.sortable.keyboardGrab.status', {\n n: keyboardNav.grabbedIndex.value + 1,\n total: keyboardNav.totalCount.value,\n })\n : ''\n }}\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n@use './styles/tokens';\n@use './styles/shared';\n\n// Variant-specific rules, scoped under the AListEditor root.\n.a-list-editor {\n // Chips: 12px left padding (flat) vs 8px in sortable, which reserves drag-handle room.\n &--chips .a-le-row-header {\n padding: 2px 4px 2px 12px;\n gap: 4px;\n min-height: 28px;\n }\n}\n\n// Narrow/mobile layout: taller rows, always-visible actions, status badge dropped for room.\n/* stylelint-disable selector-max-compound-selectors */\n@container le-shell (max-width: 768px) {\n .a-list-editor {\n --le-row-min-height: 48px;\n --le-row-pad-y: 10px;\n\n .a-le-row:not(.a-le-row--editing) .a-le-status {\n display: none;\n }\n\n .a-le-row .a-le-action--edit,\n .a-le-row .a-le-action--delete,\n .a-le-row .a-le-action--menu {\n opacity: 1;\n }\n }\n}\n/* stylelint-enable selector-max-compound-selectors */\n</style>\n","import type { ListEditorKey } from '@/labs/listEditor/types/listEditorTypes'\nimport type { NestedViewItem } from '@/labs/listEditor/composables/useNestedListEditor'\n\n/**\n * Drop instruction — where the dragged item will land + how to paint the line.\n *\n * Outliner model (dnd-kit / Workflowy style):\n * - Pointer Y picks which gap the drop lands in: the upper 50% of the\n * hovered row = the gap above it, the lower 50% = the gap below.\n * - Pointer X picks the depth within that gap. Depth is measured in indent\n * columns of `indentPx` each, rounded to the nearest column, and clamped\n * to `[0, anchor.depth + 1]` where `anchor` is the visible row immediately\n * above the target gap. You can never skip more than one level deeper\n * than what's already on the row above — same rule as dnd-kit's\n * SortableTree — which keeps the tree structure coherent.\n *\n * `makeChild: true` when the chosen depth is one level deeper than the\n * anchor. The target becomes a new parent and the consumer should\n * auto-expand `parentKey` so the inserted row stays visible.\n *\n * `blocked` wraps the would-be instruction when the move would breach\n * `maxDepth` — the overlay keeps rendering the line in a warning colour\n * so the intent stays legible but the mutation is refused.\n */\n\nexport type ExecutableInstruction = {\n type: 'insert'\n refKey: ListEditorKey\n refEdge: 'top' | 'bottom'\n parentKey: ListEditorKey | null\n index: number\n depth: number\n makeChild: boolean\n // Row whose level the inserted item will match — the sibling we're\n // inserting after (sibling case) or the ancestor we're joining as sibling\n // of (reparent case). `null` for make-child (creating a brand new deeper\n // level, no existing peer to anchor against) and the root-top fallback.\n // Consumers render a thin connector from the drop line up to this row so\n // the user can see which row dictates the new level.\n levelRowKey: ListEditorKey | null\n}\n\nexport type Instruction =\n | ExecutableInstruction\n | {\n type: 'blocked'\n desired: ExecutableInstruction\n reason: 'maxDepth'\n }\n\nexport interface HoveredRow {\n key: ListEditorKey\n rect: DOMRect\n depth: number\n parentKey: ListEditorKey | null\n siblingIndex: number\n siblingCount: number\n}\n\nexport interface ComputeInstructionArgs {\n pointer: { x: number; y: number }\n hoveredRow: HoveredRow\n sourceKey: ListEditorKey\n sourceSubtreeDepth: number\n viewItems: NestedViewItem<any>[]\n maxDepth: number\n indentPx: number\n containerLeft: number\n containerPaddingLeft: number\n}\n\nconst findByKey = (viewItems: NestedViewItem<any>[], key: ListEditorKey): NestedViewItem<any> | null =>\n viewItems.find((v) => v.key === key) ?? null\n\nconst isDescendantOf = (\n viewItems: NestedViewItem<any>[],\n ancestorKey: ListEditorKey,\n candidateKey: ListEditorKey\n): boolean => {\n let current = findByKey(viewItems, candidateKey)\n while (current && current.parentKey !== null) {\n if (current.parentKey === ancestorKey) return true\n current = findByKey(viewItems, current.parentKey)\n }\n return false\n}\n\nconst findAncestorAtDepth = (\n viewItems: NestedViewItem<any>[],\n startKey: ListEditorKey,\n targetDepth: number\n): NestedViewItem<any> | null => {\n let current = findByKey(viewItems, startKey)\n while (current && current.depth > targetDepth) {\n if (current.parentKey === null) return null\n current = findByKey(viewItems, current.parentKey)\n }\n return current && current.depth === targetDepth ? current : null\n}\n\nconst isInSourceSubtree = (\n viewItems: NestedViewItem<any>[],\n sourceKey: ListEditorKey,\n candidateKey: ListEditorKey\n): boolean => candidateKey === sourceKey || isDescendantOf(viewItems, sourceKey, candidateKey)\n\n// Walk the flat view-items list in `dir` (±1) starting at `startIdx`, skipping\n// any row that belongs to the dragged subtree — those rows aren't visible drop\n// targets so they shouldn't define the gap boundaries for depth clamping.\nconst findSiblingNotInSource = (\n viewItems: NestedViewItem<any>[],\n startIdx: number,\n dir: 1 | -1,\n sourceKey: ListEditorKey\n): NestedViewItem<any> | null => {\n for (let i = startIdx; i >= 0 && i < viewItems.length; i += dir) {\n const vi = viewItems[i]\n if (!isInSourceSubtree(viewItems, sourceKey, vi.key)) return vi\n }\n return null\n}\n\nexport const computeInstruction = (args: ComputeInstructionArgs): Instruction | null => {\n const {\n pointer,\n hoveredRow,\n sourceKey,\n sourceSubtreeDepth,\n viewItems,\n maxDepth,\n indentPx,\n containerLeft,\n containerPaddingLeft,\n } = args\n\n if (hoveredRow.key === sourceKey) return null\n if (isDescendantOf(viewItems, sourceKey, hoveredRow.key)) return null\n\n const y = pointer.y - hoveredRow.rect.top\n const h = hoveredRow.rect.height\n const inTopHalf = y < h / 2\n const refEdge: 'top' | 'bottom' = inTopHalf ? 'top' : 'bottom'\n\n // The gap between two flat-adjacent rows frames the drop. `prev` is the\n // row immediately above the gap, `next` is the row immediately below it.\n // Rows belonging to the dragged subtree don't exist for clamping purposes —\n // they'll disappear from the flat list once the move completes, so they\n // can't define the depth bounds of the drop.\n const flatIdx = viewItems.findIndex((v) => v.key === hoveredRow.key)\n const prev: NestedViewItem<any> | null = inTopHalf\n ? findSiblingNotInSource(viewItems, flatIdx - 1, -1, sourceKey)\n : findSiblingNotInSource(viewItems, flatIdx, -1, sourceKey)\n const next: NestedViewItem<any> | null = inTopHalf\n ? findSiblingNotInSource(viewItems, flatIdx, 1, sourceKey)\n : findSiblingNotInSource(viewItems, flatIdx + 1, 1, sourceKey)\n\n // Depth bounds for this gap:\n // maxDepth = prev.depth + 1 (you can nest one level deeper than prev).\n // minDepth = next.depth (shallower would insert AFTER next's parent\n // subtree, i.e., at a different Y than the\n // user is pointing at — so that's not a valid\n // option for THIS gap).\n // If prev is missing (gap at top of tree) or next is missing (bottom of\n // tree), the missing side defaults to 0.\n const pointerRelX = pointer.x - containerLeft - containerPaddingLeft\n const rawDepth = Math.round(pointerRelX / indentPx)\n const depthCeiling = prev ? prev.depth + 1 : 0\n const depthFloor = next ? next.depth : 0\n const clampedDepth = Math.max(depthFloor, Math.min(rawDepth, depthCeiling))\n\n let desired: ExecutableInstruction\n\n if (!prev) {\n // Gap at the very top of the tree — only landing is root index 0.\n desired = {\n type: 'insert',\n refKey: hoveredRow.key,\n refEdge: 'top',\n parentKey: null,\n index: 0,\n depth: 0,\n makeChild: false,\n levelRowKey: null,\n }\n } else if (clampedDepth === prev.depth + 1) {\n desired = {\n type: 'insert',\n refKey: hoveredRow.key,\n refEdge,\n parentKey: prev.key,\n index: 0,\n depth: clampedDepth,\n makeChild: true,\n levelRowKey: null,\n }\n } else if (clampedDepth === prev.depth) {\n desired = {\n type: 'insert',\n refKey: hoveredRow.key,\n refEdge,\n parentKey: prev.parentKey,\n index: prev.siblingIndex + 1,\n depth: clampedDepth,\n makeChild: false,\n levelRowKey: prev.key,\n }\n } else {\n // clampedDepth < prev.depth → reparent to ancestor at clampedDepth.\n const ancestor = findAncestorAtDepth(viewItems, prev.key, clampedDepth)\n if (ancestor) {\n desired = {\n type: 'insert',\n refKey: hoveredRow.key,\n refEdge,\n parentKey: ancestor.parentKey,\n index: ancestor.siblingIndex + 1,\n depth: clampedDepth,\n makeChild: false,\n levelRowKey: ancestor.key,\n }\n } else {\n // Shouldn't hit with clampedDepth ≥ 0 but keep a safe fallback.\n desired = {\n type: 'insert',\n refKey: hoveredRow.key,\n refEdge,\n parentKey: prev.parentKey,\n index: prev.siblingIndex + 1,\n depth: prev.depth,\n makeChild: false,\n levelRowKey: prev.key,\n }\n }\n }\n\n if (desired.depth + sourceSubtreeDepth - 1 >= maxDepth) {\n return { type: 'blocked', desired, reason: 'maxDepth' }\n }\n\n return desired\n}\n","<script setup lang=\"ts\" generic=\"TItem extends Record<string, any>\">\nimport { useI18n } from 'vue-i18n'\nimport LeNestedRowSelf from './LeNestedRow.vue'\nimport LeDragHandle from '@/labs/listEditor/internal/LeDragHandle.vue'\nimport LeUnsavedLabel from '@/labs/listEditor/internal/LeUnsavedLabel.vue'\nimport type { ListEditorKey, ListEditorValidationState } from '@/labs/listEditor/types/listEditorTypes'\n\n// Self-recursive row. Interior uses `any` — strong typing lives on the public ANestedSortableListEditor wrapper.\n\nexport type Props = {\n vi: any\n viewItems: any[]\n dragState: any\n context: any\n callbacks: any\n}\nconst props = defineProps<Props>()\n\n// `as any` breaks Vue's circular type inference on self-import.\nconst LeNestedRow = LeNestedRowSelf as any\n\nconst { t } = useI18n()\n\nconst GROUP_CLASS = 'a-nested-list-editor__group'\nconst HANDLE_CLASS = 'a-le-drag-handle'\n\nconst anchorName = (key: ListEditorKey): string => `--row-${String(key).replace(/\\W/g, '_')}`\n\n// Delegate to the parent editor's resolver so the registry + getValidationState\n// prop apply consistently. Falls back to reading raw.validationState if context\n// doesn't supply one.\nconst resolveValidation = (raw: TItem): ListEditorValidationState => {\n if (typeof props.context.resolveValidation === 'function') {\n return props.context.resolveValidation(raw, props.vi.key, props.vi.index)\n }\n const v = raw.validationState\n if (v === 'valid' || v === 'invalid' || v === 'warning') return v\n return null\n}\n\nconst buildSlotProps = () => props.context.buildSlotProps(props.vi)\n\nconst directChildren = (): any[] => props.viewItems.filter((v) => v.parentKey === props.vi.key)\n</script>\n\n<template>\n <div\n :class=\"[\n 'a-le-row-wrapper',\n {\n 'a-le-row-wrapper--drop-disabled': dragState !== null && dragState.sourceKey === vi.key,\n },\n ]\"\n :data-id=\"String(vi.key)\"\n role=\"treeitem\"\n :aria-level=\"vi.depth + 1\"\n :tabindex=\"context.keyboardNav ? context.keyboardNav.rowTabindex(vi.key) : undefined\"\n @keydown=\"context.keyboardNav ? context.keyboardNav.handleKeydown(vi.key, $event) : undefined\"\n >\n <!-- Interstitial slots are SIBLINGS of `.a-le-row` (children of this wrapper),\n so between-row content never inherits the row's unsaved/editing/grabbed/\n validation tint. (QA 85050 BUG-04/05) -->\n <slot\n name=\"before-item\"\n v-bind=\"buildSlotProps()\"\n />\n\n <div\n :class=\"[\n 'a-le-row',\n vi.depth > 0 ? 'a-le-row--child' : null,\n {\n 'a-le-row--editing': vi.editing,\n 'a-le-row--expanded': vi.expanded,\n 'a-le-row--unsaved': vi.unsaved,\n 'a-le-row--reorder': context.reorderMode,\n 'a-le-row--grabbed': context.keyboardNav && context.keyboardNav.isGrabbed(vi.key),\n 'a-le-row--clickable': context.isRowClickable(vi),\n 'a-le-row--drop-source': dragState !== null && dragState.sourceKey === vi.key,\n [`a-le-row--validation-${resolveValidation(vi.raw)}`]: resolveValidation(vi.raw) !== null,\n },\n ]\"\n :style=\"{\n '--nested-depth': vi.depth,\n 'anchor-name': anchorName(vi.key),\n '--parent-anchor': vi.parentKey !== null ? anchorName(vi.parentKey) : 'none',\n }\"\n >\n <div\n class=\"a-le-row-header\"\n @click=\"callbacks.onRowClick(vi)\"\n >\n <LeDragHandle\n v-if=\"context.dragEnabled\"\n :class=\"HANDLE_CLASS\"\n />\n\n <!-- Expand toggle — a small triangular caret rendered uniformly for both\n root and nested rows. For leaf rows (no children) the toggle is\n hidden via `visibility: hidden` so the column stays aligned. -->\n <button\n v-if=\"context.showExpandToggle && vi.childrenAllowed\"\n type=\"button\"\n :class=\"[\n 'a-nested-list-editor__tree-toggle',\n {\n 'a-nested-list-editor__tree-toggle--empty': !vi.hasChildren,\n 'a-nested-list-editor__tree-toggle--open': vi.childrenExpanded,\n },\n ]\"\n :aria-label=\"vi.childrenExpanded ? t('common.sortable.close') : t('common.sortable.edit')\"\n @click.stop=\"vi.hasChildren && callbacks.onChevronClick(vi)\"\n >\n <span class=\"a-nested-list-editor__tree-toggle-caret\" />\n </button>\n <!-- Spacer for leaf rows that have no caret, so titles align with\n parents having one. -->\n <span\n v-else-if=\"context.showExpandToggle\"\n class=\"a-nested-list-editor__tree-toggle a-nested-list-editor__tree-toggle--spacer\"\n aria-hidden=\"true\"\n />\n\n <div class=\"a-le-row-main\">\n <slot\n name=\"item-compact\"\n v-bind=\"buildSlotProps()\"\n >\n <span class=\"a-le-title\">\n {{ context.resolveCompactText(vi.raw, vi.key) }}\n </span>\n </slot>\n <!-- Visible only inside the SortableJS drag clone — signals that the\n item being moved carries a subtree so the user realises the whole\n branch will follow. Sits right after the title. Hidden in the\n normal DOM via CSS. -->\n <span\n v-if=\"vi.hasChildren\"\n class=\"a-nested-list-editor__drag-count\"\n aria-hidden=\"true\"\n >\n <VIcon\n icon=\"mdi-file-tree\"\n size=\"14\"\n />\n +{{ vi.childrenCount }}\n </span>\n <LeUnsavedLabel v-if=\"vi.unsaved\" />\n </div>\n\n <div\n v-if=\"!context.reorderMode\"\n class=\"a-le-status\"\n >\n <slot\n name=\"item-status\"\n v-bind=\"buildSlotProps()\"\n >\n <span\n v-if=\"context.statusField && vi.raw[context.statusField] != null && vi.raw[context.statusField] !== ''\"\n class=\"a-le-status-badge\"\n >\n {{ vi.raw[context.statusField] }}\n </span>\n </slot>\n </div>\n\n <div class=\"a-le-actions\">\n <slot\n name=\"item-actions\"\n v-bind=\"buildSlotProps()\"\n >\n <template v-if=\"context.reorderMode\">\n <VBtn\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :disabled=\"vi.firstInParent\"\n class=\"mx-1 a-le-action a-le-action--up\"\n @click.stop=\"callbacks.moveUp(vi.key)\"\n >\n <VIcon\n icon=\"mdi-arrow-up\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.moveUp')\"\n />\n </VBtn>\n <VBtn\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :disabled=\"vi.lastInParent\"\n class=\"mx-1 a-le-action a-le-action--down\"\n @click.stop=\"callbacks.moveDown(vi.key)\"\n >\n <VIcon\n icon=\"mdi-arrow-down\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.moveDown')\"\n />\n </VBtn>\n <VBtn\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :active=\"false\"\n class=\"mx-1 a-le-action a-le-action--menu\"\n >\n <VIcon\n icon=\"mdi-dots-vertical\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.more')\"\n />\n <VMenu activator=\"parent\">\n <VList density=\"compact\">\n <VListItem\n :disabled=\"vi.firstInParent\"\n @click.stop=\"callbacks.moveTop(vi.key)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-arrow-collapse-up\" />\n </template>\n <VListItemTitle>{{ t('common.sortable.moveToTop') }}</VListItemTitle>\n </VListItem>\n <VListItem\n :disabled=\"vi.lastInParent\"\n @click.stop=\"callbacks.moveBottom(vi.key)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-arrow-collapse-down\" />\n </template>\n <VListItemTitle>{{ t('common.sortable.moveToBottom') }}</VListItemTitle>\n </VListItem>\n <VListItem\n v-if=\"context.showMoveToPosition && (!vi.firstInParent || !vi.lastInParent)\"\n @click.stop=\"callbacks.openMoveToPosition(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-target\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.moveToPosition.action') }}\n </VListItemTitle>\n </VListItem>\n <VListItem\n v-if=\"context.showChangeParent\"\n @click.stop=\"callbacks.openChangeParent(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-folder-move-outline\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.changeParent.action') }}\n </VListItemTitle>\n </VListItem>\n <VListItem\n :disabled=\"!vi.canIndent\"\n @click.stop=\"callbacks.indent(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-format-indent-increase\" />\n </template>\n <VListItemTitle>{{ t('common.sortable.indent') }}</VListItemTitle>\n </VListItem>\n <VListItem\n :disabled=\"!vi.canOutdent\"\n @click.stop=\"callbacks.outdent(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-format-indent-decrease\" />\n </template>\n <VListItemTitle>{{ t('common.sortable.outdent') }}</VListItemTitle>\n </VListItem>\n <VListItem\n v-if=\"context.showDeleteButton && context.canInteract\"\n @click.stop=\"callbacks.onDeleteClick(vi)\"\n >\n <template #prepend>\n <VIcon\n icon=\"mdi-trash-can-outline\"\n color=\"error\"\n />\n </template>\n <VListItemTitle class=\"text-error\">\n {{ t('common.sortable.delete') }}\n </VListItemTitle>\n </VListItem>\n </VList>\n </VMenu>\n </VBtn>\n </template>\n <template v-else>\n <VBtn\n v-if=\"context.showEditButton && context.canInteract\"\n icon\n size=\"small\"\n variant=\"tonal\"\n color=\"primary\"\n density=\"comfortable\"\n class=\"mx-1 a-le-action a-le-action--edit\"\n @click.stop=\"callbacks.onEditClick(vi)\"\n >\n <VIcon\n icon=\"mdi-pencil-outline\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.edit')\"\n />\n </VBtn>\n <VBtn\n v-if=\"context.showDeleteButton && context.canInteract\"\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n class=\"mx-1 a-le-action a-le-action--delete\"\n @click.stop=\"callbacks.onDeleteClick(vi)\"\n >\n <VIcon\n icon=\"mdi-trash-can-outline\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.delete')\"\n />\n </VBtn>\n <VBtn\n v-if=\"\n context.canInteract && ((context.showAddChildButton && vi.canAddChild) || context.showAddAfterAction)\n \"\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :active=\"false\"\n class=\"mx-1 a-le-action a-le-action--menu\"\n >\n <VIcon\n icon=\"mdi-dots-vertical\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.more')\"\n />\n <VMenu activator=\"parent\">\n <VList density=\"compact\">\n <VListItem\n v-if=\"context.showAddAfterAction\"\n @click.stop=\"callbacks.onAddAfterClick(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-playlist-plus\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.addAfter') }}\n </VListItemTitle>\n </VListItem>\n <VListItem\n v-if=\"context.showAddChildButton && vi.canAddChild\"\n @click.stop=\"callbacks.onAddChildClick(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-subdirectory-arrow-right\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.addChild') }}\n </VListItemTitle>\n </VListItem>\n </VList>\n </VMenu>\n </VBtn>\n </template>\n </slot>\n </div>\n </div>\n\n <!-- Inline edit body — status badge intentionally NOT rendered here,\n the compact header's status is enough; stacking it in the form body\n was visually noisy. -->\n <template v-if=\"vi.editing && !context.reorderMode && $slots.item\">\n <div class=\"a-le-row-body\">\n <div class=\"a-le-form\">\n <slot\n name=\"item\"\n v-bind=\"buildSlotProps()\"\n />\n </div>\n </div>\n <slot\n name=\"item-footer\"\n v-bind=\"buildSlotProps()\"\n >\n <div\n v-if=\"context.showInlineSaveFooter\"\n class=\"a-le-row-footer\"\n >\n <div class=\"a-le-row-footer-spacer\" />\n <VBtn\n variant=\"text\"\n :disabled=\"vi.loading\"\n @click.stop=\"callbacks.onCancelClick(vi)\"\n >\n {{ t('common.button.cancel') }}\n </VBtn>\n <VBtn\n color=\"primary\"\n variant=\"flat\"\n prepend-icon=\"mdi-check\"\n :disabled=\"vi.loading\"\n @click.stop=\"callbacks.onSaveClick(vi)\"\n >\n {{ t('common.button.save') }}\n </VBtn>\n </div>\n </slot>\n </template>\n\n <div\n v-else-if=\"$slots['item-readonly'] && (context.readonly || vi.expanded) && !context.reorderMode\"\n class=\"a-le-row-body\"\n >\n <div class=\"a-le-form\">\n <slot\n name=\"item-readonly\"\n v-bind=\"buildSlotProps()\"\n />\n </div>\n </div>\n </div>\n\n <slot\n name=\"after-item\"\n v-bind=\"buildSlotProps()\"\n />\n\n <!-- Recursive children — any depth level. The `::before` on this wrapper\n renders the single continuous vertical tree line at the parent's\n chevron column (see CSS). One line per group = no per-row gaps. -->\n <div\n v-if=\"vi.hasChildren && vi.childrenExpanded\"\n class=\"a-nested-list-editor__children\"\n :style=\"{ '--parent-depth': vi.depth }\"\n >\n <div\n :class=\"[GROUP_CLASS]\"\n :data-parent-id=\"String(vi.key)\"\n >\n <LeNestedRow\n v-for=\"child in directChildren()\"\n :key=\"String(child.key)\"\n :vi=\"child\"\n :view-items=\"viewItems\"\n :drag-state=\"dragState\"\n :context=\"context\"\n :callbacks=\"callbacks\"\n >\n <template #item=\"slotScope\">\n <slot\n name=\"item\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-compact=\"slotScope\">\n <slot\n name=\"item-compact\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-readonly=\"slotScope\">\n <slot\n name=\"item-readonly\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-status=\"slotScope\">\n <slot\n name=\"item-status\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-footer=\"slotScope\">\n <slot\n name=\"item-footer\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-actions=\"slotScope\">\n <slot\n name=\"item-actions\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #before-item=\"slotScope\">\n <slot\n name=\"before-item\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #after-item=\"slotScope\">\n <slot\n name=\"after-item\"\n v-bind=\"slotScope\"\n />\n </template>\n </LeNestedRow>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\" generic=\"TItem extends Record<string, any>\">\nimport { useI18n } from 'vue-i18n'\nimport LeNestedRowSelf from './LeNestedRow.vue'\nimport LeDragHandle from '@/labs/listEditor/internal/LeDragHandle.vue'\nimport LeUnsavedLabel from '@/labs/listEditor/internal/LeUnsavedLabel.vue'\nimport type { ListEditorKey, ListEditorValidationState } from '@/labs/listEditor/types/listEditorTypes'\n\n// Self-recursive row. Interior uses `any` — strong typing lives on the public ANestedSortableListEditor wrapper.\n\nexport type Props = {\n vi: any\n viewItems: any[]\n dragState: any\n context: any\n callbacks: any\n}\nconst props = defineProps<Props>()\n\n// `as any` breaks Vue's circular type inference on self-import.\nconst LeNestedRow = LeNestedRowSelf as any\n\nconst { t } = useI18n()\n\nconst GROUP_CLASS = 'a-nested-list-editor__group'\nconst HANDLE_CLASS = 'a-le-drag-handle'\n\nconst anchorName = (key: ListEditorKey): string => `--row-${String(key).replace(/\\W/g, '_')}`\n\n// Delegate to the parent editor's resolver so the registry + getValidationState\n// prop apply consistently. Falls back to reading raw.validationState if context\n// doesn't supply one.\nconst resolveValidation = (raw: TItem): ListEditorValidationState => {\n if (typeof props.context.resolveValidation === 'function') {\n return props.context.resolveValidation(raw, props.vi.key, props.vi.index)\n }\n const v = raw.validationState\n if (v === 'valid' || v === 'invalid' || v === 'warning') return v\n return null\n}\n\nconst buildSlotProps = () => props.context.buildSlotProps(props.vi)\n\nconst directChildren = (): any[] => props.viewItems.filter((v) => v.parentKey === props.vi.key)\n</script>\n\n<template>\n <div\n :class=\"[\n 'a-le-row-wrapper',\n {\n 'a-le-row-wrapper--drop-disabled': dragState !== null && dragState.sourceKey === vi.key,\n },\n ]\"\n :data-id=\"String(vi.key)\"\n role=\"treeitem\"\n :aria-level=\"vi.depth + 1\"\n :tabindex=\"context.keyboardNav ? context.keyboardNav.rowTabindex(vi.key) : undefined\"\n @keydown=\"context.keyboardNav ? context.keyboardNav.handleKeydown(vi.key, $event) : undefined\"\n >\n <!-- Interstitial slots are SIBLINGS of `.a-le-row` (children of this wrapper),\n so between-row content never inherits the row's unsaved/editing/grabbed/\n validation tint. (QA 85050 BUG-04/05) -->\n <slot\n name=\"before-item\"\n v-bind=\"buildSlotProps()\"\n />\n\n <div\n :class=\"[\n 'a-le-row',\n vi.depth > 0 ? 'a-le-row--child' : null,\n {\n 'a-le-row--editing': vi.editing,\n 'a-le-row--expanded': vi.expanded,\n 'a-le-row--unsaved': vi.unsaved,\n 'a-le-row--reorder': context.reorderMode,\n 'a-le-row--grabbed': context.keyboardNav && context.keyboardNav.isGrabbed(vi.key),\n 'a-le-row--clickable': context.isRowClickable(vi),\n 'a-le-row--drop-source': dragState !== null && dragState.sourceKey === vi.key,\n [`a-le-row--validation-${resolveValidation(vi.raw)}`]: resolveValidation(vi.raw) !== null,\n },\n ]\"\n :style=\"{\n '--nested-depth': vi.depth,\n 'anchor-name': anchorName(vi.key),\n '--parent-anchor': vi.parentKey !== null ? anchorName(vi.parentKey) : 'none',\n }\"\n >\n <div\n class=\"a-le-row-header\"\n @click=\"callbacks.onRowClick(vi)\"\n >\n <LeDragHandle\n v-if=\"context.dragEnabled\"\n :class=\"HANDLE_CLASS\"\n />\n\n <!-- Expand toggle — a small triangular caret rendered uniformly for both\n root and nested rows. For leaf rows (no children) the toggle is\n hidden via `visibility: hidden` so the column stays aligned. -->\n <button\n v-if=\"context.showExpandToggle && vi.childrenAllowed\"\n type=\"button\"\n :class=\"[\n 'a-nested-list-editor__tree-toggle',\n {\n 'a-nested-list-editor__tree-toggle--empty': !vi.hasChildren,\n 'a-nested-list-editor__tree-toggle--open': vi.childrenExpanded,\n },\n ]\"\n :aria-label=\"vi.childrenExpanded ? t('common.sortable.close') : t('common.sortable.edit')\"\n @click.stop=\"vi.hasChildren && callbacks.onChevronClick(vi)\"\n >\n <span class=\"a-nested-list-editor__tree-toggle-caret\" />\n </button>\n <!-- Spacer for leaf rows that have no caret, so titles align with\n parents having one. -->\n <span\n v-else-if=\"context.showExpandToggle\"\n class=\"a-nested-list-editor__tree-toggle a-nested-list-editor__tree-toggle--spacer\"\n aria-hidden=\"true\"\n />\n\n <div class=\"a-le-row-main\">\n <slot\n name=\"item-compact\"\n v-bind=\"buildSlotProps()\"\n >\n <span class=\"a-le-title\">\n {{ context.resolveCompactText(vi.raw, vi.key) }}\n </span>\n </slot>\n <!-- Visible only inside the SortableJS drag clone — signals that the\n item being moved carries a subtree so the user realises the whole\n branch will follow. Sits right after the title. Hidden in the\n normal DOM via CSS. -->\n <span\n v-if=\"vi.hasChildren\"\n class=\"a-nested-list-editor__drag-count\"\n aria-hidden=\"true\"\n >\n <VIcon\n icon=\"mdi-file-tree\"\n size=\"14\"\n />\n +{{ vi.childrenCount }}\n </span>\n <LeUnsavedLabel v-if=\"vi.unsaved\" />\n </div>\n\n <div\n v-if=\"!context.reorderMode\"\n class=\"a-le-status\"\n >\n <slot\n name=\"item-status\"\n v-bind=\"buildSlotProps()\"\n >\n <span\n v-if=\"context.statusField && vi.raw[context.statusField] != null && vi.raw[context.statusField] !== ''\"\n class=\"a-le-status-badge\"\n >\n {{ vi.raw[context.statusField] }}\n </span>\n </slot>\n </div>\n\n <div class=\"a-le-actions\">\n <slot\n name=\"item-actions\"\n v-bind=\"buildSlotProps()\"\n >\n <template v-if=\"context.reorderMode\">\n <VBtn\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :disabled=\"vi.firstInParent\"\n class=\"mx-1 a-le-action a-le-action--up\"\n @click.stop=\"callbacks.moveUp(vi.key)\"\n >\n <VIcon\n icon=\"mdi-arrow-up\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.moveUp')\"\n />\n </VBtn>\n <VBtn\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :disabled=\"vi.lastInParent\"\n class=\"mx-1 a-le-action a-le-action--down\"\n @click.stop=\"callbacks.moveDown(vi.key)\"\n >\n <VIcon\n icon=\"mdi-arrow-down\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.moveDown')\"\n />\n </VBtn>\n <VBtn\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :active=\"false\"\n class=\"mx-1 a-le-action a-le-action--menu\"\n >\n <VIcon\n icon=\"mdi-dots-vertical\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.more')\"\n />\n <VMenu activator=\"parent\">\n <VList density=\"compact\">\n <VListItem\n :disabled=\"vi.firstInParent\"\n @click.stop=\"callbacks.moveTop(vi.key)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-arrow-collapse-up\" />\n </template>\n <VListItemTitle>{{ t('common.sortable.moveToTop') }}</VListItemTitle>\n </VListItem>\n <VListItem\n :disabled=\"vi.lastInParent\"\n @click.stop=\"callbacks.moveBottom(vi.key)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-arrow-collapse-down\" />\n </template>\n <VListItemTitle>{{ t('common.sortable.moveToBottom') }}</VListItemTitle>\n </VListItem>\n <VListItem\n v-if=\"context.showMoveToPosition && (!vi.firstInParent || !vi.lastInParent)\"\n @click.stop=\"callbacks.openMoveToPosition(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-target\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.moveToPosition.action') }}\n </VListItemTitle>\n </VListItem>\n <VListItem\n v-if=\"context.showChangeParent\"\n @click.stop=\"callbacks.openChangeParent(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-folder-move-outline\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.changeParent.action') }}\n </VListItemTitle>\n </VListItem>\n <VListItem\n :disabled=\"!vi.canIndent\"\n @click.stop=\"callbacks.indent(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-format-indent-increase\" />\n </template>\n <VListItemTitle>{{ t('common.sortable.indent') }}</VListItemTitle>\n </VListItem>\n <VListItem\n :disabled=\"!vi.canOutdent\"\n @click.stop=\"callbacks.outdent(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-format-indent-decrease\" />\n </template>\n <VListItemTitle>{{ t('common.sortable.outdent') }}</VListItemTitle>\n </VListItem>\n <VListItem\n v-if=\"context.showDeleteButton && context.canInteract\"\n @click.stop=\"callbacks.onDeleteClick(vi)\"\n >\n <template #prepend>\n <VIcon\n icon=\"mdi-trash-can-outline\"\n color=\"error\"\n />\n </template>\n <VListItemTitle class=\"text-error\">\n {{ t('common.sortable.delete') }}\n </VListItemTitle>\n </VListItem>\n </VList>\n </VMenu>\n </VBtn>\n </template>\n <template v-else>\n <VBtn\n v-if=\"context.showEditButton && context.canInteract\"\n icon\n size=\"small\"\n variant=\"tonal\"\n color=\"primary\"\n density=\"comfortable\"\n class=\"mx-1 a-le-action a-le-action--edit\"\n @click.stop=\"callbacks.onEditClick(vi)\"\n >\n <VIcon\n icon=\"mdi-pencil-outline\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.edit')\"\n />\n </VBtn>\n <VBtn\n v-if=\"context.showDeleteButton && context.canInteract\"\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n class=\"mx-1 a-le-action a-le-action--delete\"\n @click.stop=\"callbacks.onDeleteClick(vi)\"\n >\n <VIcon\n icon=\"mdi-trash-can-outline\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.delete')\"\n />\n </VBtn>\n <VBtn\n v-if=\"\n context.canInteract && ((context.showAddChildButton && vi.canAddChild) || context.showAddAfterAction)\n \"\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :active=\"false\"\n class=\"mx-1 a-le-action a-le-action--menu\"\n >\n <VIcon\n icon=\"mdi-dots-vertical\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.more')\"\n />\n <VMenu activator=\"parent\">\n <VList density=\"compact\">\n <VListItem\n v-if=\"context.showAddAfterAction\"\n @click.stop=\"callbacks.onAddAfterClick(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-playlist-plus\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.addAfter') }}\n </VListItemTitle>\n </VListItem>\n <VListItem\n v-if=\"context.showAddChildButton && vi.canAddChild\"\n @click.stop=\"callbacks.onAddChildClick(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-subdirectory-arrow-right\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.addChild') }}\n </VListItemTitle>\n </VListItem>\n </VList>\n </VMenu>\n </VBtn>\n </template>\n </slot>\n </div>\n </div>\n\n <!-- Inline edit body — status badge intentionally NOT rendered here,\n the compact header's status is enough; stacking it in the form body\n was visually noisy. -->\n <template v-if=\"vi.editing && !context.reorderMode && $slots.item\">\n <div class=\"a-le-row-body\">\n <div class=\"a-le-form\">\n <slot\n name=\"item\"\n v-bind=\"buildSlotProps()\"\n />\n </div>\n </div>\n <slot\n name=\"item-footer\"\n v-bind=\"buildSlotProps()\"\n >\n <div\n v-if=\"context.showInlineSaveFooter\"\n class=\"a-le-row-footer\"\n >\n <div class=\"a-le-row-footer-spacer\" />\n <VBtn\n variant=\"text\"\n :disabled=\"vi.loading\"\n @click.stop=\"callbacks.onCancelClick(vi)\"\n >\n {{ t('common.button.cancel') }}\n </VBtn>\n <VBtn\n color=\"primary\"\n variant=\"flat\"\n prepend-icon=\"mdi-check\"\n :disabled=\"vi.loading\"\n @click.stop=\"callbacks.onSaveClick(vi)\"\n >\n {{ t('common.button.save') }}\n </VBtn>\n </div>\n </slot>\n </template>\n\n <div\n v-else-if=\"$slots['item-readonly'] && (context.readonly || vi.expanded) && !context.reorderMode\"\n class=\"a-le-row-body\"\n >\n <div class=\"a-le-form\">\n <slot\n name=\"item-readonly\"\n v-bind=\"buildSlotProps()\"\n />\n </div>\n </div>\n </div>\n\n <slot\n name=\"after-item\"\n v-bind=\"buildSlotProps()\"\n />\n\n <!-- Recursive children — any depth level. The `::before` on this wrapper\n renders the single continuous vertical tree line at the parent's\n chevron column (see CSS). One line per group = no per-row gaps. -->\n <div\n v-if=\"vi.hasChildren && vi.childrenExpanded\"\n class=\"a-nested-list-editor__children\"\n :style=\"{ '--parent-depth': vi.depth }\"\n >\n <div\n :class=\"[GROUP_CLASS]\"\n :data-parent-id=\"String(vi.key)\"\n >\n <LeNestedRow\n v-for=\"child in directChildren()\"\n :key=\"String(child.key)\"\n :vi=\"child\"\n :view-items=\"viewItems\"\n :drag-state=\"dragState\"\n :context=\"context\"\n :callbacks=\"callbacks\"\n >\n <template #item=\"slotScope\">\n <slot\n name=\"item\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-compact=\"slotScope\">\n <slot\n name=\"item-compact\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-readonly=\"slotScope\">\n <slot\n name=\"item-readonly\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-status=\"slotScope\">\n <slot\n name=\"item-status\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-footer=\"slotScope\">\n <slot\n name=\"item-footer\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-actions=\"slotScope\">\n <slot\n name=\"item-actions\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #before-item=\"slotScope\">\n <slot\n name=\"before-item\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #after-item=\"slotScope\">\n <slot\n name=\"after-item\"\n v-bind=\"slotScope\"\n />\n </template>\n </LeNestedRow>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\" generic=\"TItem extends Record<string, any>\">\nimport { computed, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport type { ListEditorKey, NestedTree, NestedTreeNode } from '@/labs/listEditor/types/listEditorTypes'\n\nexport interface Props<TItem extends Record<string, any>> {\n tree: NestedTree<TItem>\n sourceKey: ListEditorKey | null\n keyField: string\n maxDepth: number\n resolveLabel: (raw: TItem, key: ListEditorKey) => string\n calculateSubtreeDepth: (node: NestedTreeNode<TItem>) => number\n}\n\ninterface Candidate {\n key: ListEditorKey | null\n depth: number\n label: string\n hasChildren: boolean\n disallowed: boolean\n reason: 'self' | 'descendant' | 'maxDepth' | 'currentParent' | null\n}\n\nconst props = defineProps<Props<TItem>>()\n\nconst emit = defineEmits<{\n confirm: [parentId: ListEditorKey | null, position: 'first' | 'last']\n}>()\n\nconst open = defineModel<boolean>({ required: true })\n\nconst { t } = useI18n()\n\nconst selectedKey = ref<ListEditorKey | null | undefined>(undefined)\nconst placement = ref<'first' | 'last'>('last')\n\nwatch(open, (now) => {\n if (now) {\n selectedKey.value = undefined\n placement.value = 'last'\n }\n})\n\n// Walk the source's subtree to gather descendant keys (cycle prevention).\nconst descendantKeys = computed<Set<ListEditorKey>>(() => {\n const out = new Set<ListEditorKey>()\n if (props.sourceKey === null) return out\n const walk = (nodes: NestedTreeNode<TItem>[]): boolean => {\n for (const n of nodes) {\n const k = n.data[props.keyField] as ListEditorKey\n if (k === props.sourceKey) {\n const collect = (sub: NestedTreeNode<TItem>[]) => {\n for (const s of sub) {\n out.add(s.data[props.keyField] as ListEditorKey)\n if (s.children?.length) collect(s.children)\n }\n }\n if (n.children?.length) collect(n.children)\n return true\n }\n if (n.children?.length && walk(n.children)) return true\n }\n return false\n }\n walk(props.tree.children)\n return out\n})\n\nconst sourceNode = computed<NestedTreeNode<TItem> | null>(() => {\n if (props.sourceKey === null) return null\n const find = (nodes: NestedTreeNode<TItem>[]): NestedTreeNode<TItem> | null => {\n for (const n of nodes) {\n if ((n.data[props.keyField] as ListEditorKey) === props.sourceKey) return n\n if (n.children?.length) {\n const hit = find(n.children)\n if (hit) return hit\n }\n }\n return null\n }\n return find(props.tree.children)\n})\n\n// `currentParentKey` = parent of the source row (null if source is at root).\n// We exclude this from candidates because moving to \"the same parent\" is not a\n// \"change parent\" operation (use Move-to-position instead).\nconst currentParentKey = computed<ListEditorKey | null | undefined>(() => {\n if (props.sourceKey === null) return undefined\n const find = (nodes: NestedTreeNode<TItem>[], parentKey: ListEditorKey | null): ListEditorKey | null | undefined => {\n for (const n of nodes) {\n if ((n.data[props.keyField] as ListEditorKey) === props.sourceKey) return parentKey\n if (n.children?.length) {\n const hit = find(n.children, n.data[props.keyField] as ListEditorKey)\n if (hit !== undefined) return hit\n }\n }\n return undefined\n }\n return find(props.tree.children, null)\n})\n\nconst candidates = computed<Candidate[]>(() => {\n const out: Candidate[] = []\n const subtreeDepth = sourceNode.value ? props.calculateSubtreeDepth(sourceNode.value) : 1\n\n out.push({\n key: null,\n depth: 0,\n label: t('common.sortable.changeParent.rootLabel'),\n hasChildren: props.tree.children.length > 0,\n disallowed: currentParentKey.value === null,\n reason: currentParentKey.value === null ? 'currentParent' : null,\n })\n\n const walk = (nodes: NestedTreeNode<TItem>[], depth: number) => {\n for (const n of nodes) {\n const key = n.data[props.keyField] as ListEditorKey\n let disallowed = false\n let reason: Candidate['reason'] = null\n if (key === props.sourceKey) {\n disallowed = true\n reason = 'self'\n } else if (descendantKeys.value.has(key)) {\n disallowed = true\n reason = 'descendant'\n } else if (currentParentKey.value !== undefined && currentParentKey.value === key) {\n disallowed = true\n reason = 'currentParent'\n } else if (depth + 1 + subtreeDepth > props.maxDepth) {\n disallowed = true\n reason = 'maxDepth'\n }\n out.push({\n key,\n depth: depth + 1,\n label: props.resolveLabel(n.data as TItem, key),\n hasChildren: !!(n.children && n.children.length > 0),\n disallowed,\n reason,\n })\n if (n.children?.length) walk(n.children, depth + 1)\n }\n }\n walk(props.tree.children, 0)\n return out\n})\n\nconst validCandidates = computed(() => candidates.value.filter((c) => !c.disallowed))\n\nconst selectedCandidate = computed<Candidate | null>(() =>\n selectedKey.value === undefined ? null : (candidates.value.find((c) => c.key === selectedKey.value) ?? null)\n)\n\nconst showPlacementPicker = computed<boolean>(() => !!selectedCandidate.value && selectedCandidate.value.hasChildren)\n\nconst onCandidateClick = (c: Candidate) => {\n if (c.disallowed) return\n selectedKey.value = c.key\n}\n\nconst onConfirm = () => {\n if (!selectedCandidate.value) return\n emit('confirm', selectedCandidate.value.key, placement.value)\n open.value = false\n}\n\nconst onCancel = () => {\n open.value = false\n}\n\nconst indent = (depth: number): string => `${depth * 16}px`\n\nconst reasonLabel = (reason: Candidate['reason']): string => {\n if (reason === 'maxDepth') return t('common.sortable.changeParent.errorMaxDepth')\n return ''\n}\n</script>\n\n<template>\n <VDialog\n v-model=\"open\"\n max-width=\"540\"\n persistent\n >\n <VCard>\n <VCardTitle>\n {{ t('common.sortable.changeParent.title') }}\n </VCardTitle>\n\n <VCardText\n v-if=\"validCandidates.length === 0\"\n class=\"text-body-medium\"\n >\n <VAlert\n type=\"info\"\n variant=\"tonal\"\n density=\"compact\"\n >\n <strong>{{ t('common.sortable.changeParent.noTargetsTitle') }}</strong>\n <div>{{ t('common.sortable.changeParent.noTargetsText') }}</div>\n </VAlert>\n </VCardText>\n\n <template v-else>\n <VCardText class=\"pb-1\">\n <VList\n density=\"compact\"\n class=\"le-change-parent__list\"\n >\n <VListItem\n v-for=\"c in candidates\"\n :key=\"c.key === null ? '__root__' : String(c.key)\"\n :disabled=\"c.disallowed\"\n :active=\"selectedKey === c.key\"\n :title=\"c.label\"\n :subtitle=\"reasonLabel(c.reason)\"\n :style=\"{ paddingInlineStart: indent(c.depth) }\"\n :data-disallowed=\"c.disallowed ? 'true' : 'false'\"\n :data-reason=\"c.reason ?? ''\"\n @click=\"onCandidateClick(c)\"\n >\n <template #prepend>\n <VIcon\n v-if=\"c.key === null\"\n icon=\"mdi-folder-home-outline\"\n size=\"18\"\n />\n <VIcon\n v-else-if=\"c.hasChildren\"\n icon=\"mdi-folder-outline\"\n size=\"18\"\n />\n <VIcon\n v-else\n icon=\"mdi-file-outline\"\n size=\"18\"\n />\n </template>\n </VListItem>\n </VList>\n </VCardText>\n\n <VCardText\n v-if=\"showPlacementPicker\"\n class=\"pt-0\"\n >\n <div class=\"text-body-medium mb-2\">\n {{\n t('common.sortable.changeParent.placementHeading', {\n label: selectedCandidate?.label ?? '',\n })\n }}\n </div>\n <VRadioGroup\n v-model=\"placement\"\n density=\"compact\"\n hide-details\n >\n <VRadio\n value=\"first\"\n :label=\"t('common.sortable.changeParent.placeFirst')\"\n />\n <VRadio\n value=\"last\"\n :label=\"t('common.sortable.changeParent.placeLast')\"\n />\n </VRadioGroup>\n </VCardText>\n </template>\n\n <VCardActions>\n <VSpacer />\n <VBtn\n variant=\"text\"\n @click=\"onCancel\"\n >\n {{ t('common.sortable.reorderCancel') }}\n </VBtn>\n <VBtn\n color=\"primary\"\n variant=\"elevated\"\n :disabled=\"!selectedCandidate || selectedCandidate.disallowed\"\n @click=\"onConfirm\"\n >\n {{ t('common.sortable.changeParent.confirm') }}\n </VBtn>\n </VCardActions>\n </VCard>\n </VDialog>\n</template>\n\n<style lang=\"scss\" scoped>\n.le-change-parent__list {\n max-height: 340px;\n overflow-y: auto;\n}\n</style>\n","<script setup lang=\"ts\" generic=\"TItem extends Record<string, any>\">\nimport { computed, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport type { ListEditorKey, NestedTree, NestedTreeNode } from '@/labs/listEditor/types/listEditorTypes'\n\nexport interface Props<TItem extends Record<string, any>> {\n tree: NestedTree<TItem>\n sourceKey: ListEditorKey | null\n keyField: string\n maxDepth: number\n resolveLabel: (raw: TItem, key: ListEditorKey) => string\n calculateSubtreeDepth: (node: NestedTreeNode<TItem>) => number\n}\n\ninterface Candidate {\n key: ListEditorKey | null\n depth: number\n label: string\n hasChildren: boolean\n disallowed: boolean\n reason: 'self' | 'descendant' | 'maxDepth' | 'currentParent' | null\n}\n\nconst props = defineProps<Props<TItem>>()\n\nconst emit = defineEmits<{\n confirm: [parentId: ListEditorKey | null, position: 'first' | 'last']\n}>()\n\nconst open = defineModel<boolean>({ required: true })\n\nconst { t } = useI18n()\n\nconst selectedKey = ref<ListEditorKey | null | undefined>(undefined)\nconst placement = ref<'first' | 'last'>('last')\n\nwatch(open, (now) => {\n if (now) {\n selectedKey.value = undefined\n placement.value = 'last'\n }\n})\n\n// Walk the source's subtree to gather descendant keys (cycle prevention).\nconst descendantKeys = computed<Set<ListEditorKey>>(() => {\n const out = new Set<ListEditorKey>()\n if (props.sourceKey === null) return out\n const walk = (nodes: NestedTreeNode<TItem>[]): boolean => {\n for (const n of nodes) {\n const k = n.data[props.keyField] as ListEditorKey\n if (k === props.sourceKey) {\n const collect = (sub: NestedTreeNode<TItem>[]) => {\n for (const s of sub) {\n out.add(s.data[props.keyField] as ListEditorKey)\n if (s.children?.length) collect(s.children)\n }\n }\n if (n.children?.length) collect(n.children)\n return true\n }\n if (n.children?.length && walk(n.children)) return true\n }\n return false\n }\n walk(props.tree.children)\n return out\n})\n\nconst sourceNode = computed<NestedTreeNode<TItem> | null>(() => {\n if (props.sourceKey === null) return null\n const find = (nodes: NestedTreeNode<TItem>[]): NestedTreeNode<TItem> | null => {\n for (const n of nodes) {\n if ((n.data[props.keyField] as ListEditorKey) === props.sourceKey) return n\n if (n.children?.length) {\n const hit = find(n.children)\n if (hit) return hit\n }\n }\n return null\n }\n return find(props.tree.children)\n})\n\n// `currentParentKey` = parent of the source row (null if source is at root).\n// We exclude this from candidates because moving to \"the same parent\" is not a\n// \"change parent\" operation (use Move-to-position instead).\nconst currentParentKey = computed<ListEditorKey | null | undefined>(() => {\n if (props.sourceKey === null) return undefined\n const find = (nodes: NestedTreeNode<TItem>[], parentKey: ListEditorKey | null): ListEditorKey | null | undefined => {\n for (const n of nodes) {\n if ((n.data[props.keyField] as ListEditorKey) === props.sourceKey) return parentKey\n if (n.children?.length) {\n const hit = find(n.children, n.data[props.keyField] as ListEditorKey)\n if (hit !== undefined) return hit\n }\n }\n return undefined\n }\n return find(props.tree.children, null)\n})\n\nconst candidates = computed<Candidate[]>(() => {\n const out: Candidate[] = []\n const subtreeDepth = sourceNode.value ? props.calculateSubtreeDepth(sourceNode.value) : 1\n\n out.push({\n key: null,\n depth: 0,\n label: t('common.sortable.changeParent.rootLabel'),\n hasChildren: props.tree.children.length > 0,\n disallowed: currentParentKey.value === null,\n reason: currentParentKey.value === null ? 'currentParent' : null,\n })\n\n const walk = (nodes: NestedTreeNode<TItem>[], depth: number) => {\n for (const n of nodes) {\n const key = n.data[props.keyField] as ListEditorKey\n let disallowed = false\n let reason: Candidate['reason'] = null\n if (key === props.sourceKey) {\n disallowed = true\n reason = 'self'\n } else if (descendantKeys.value.has(key)) {\n disallowed = true\n reason = 'descendant'\n } else if (currentParentKey.value !== undefined && currentParentKey.value === key) {\n disallowed = true\n reason = 'currentParent'\n } else if (depth + 1 + subtreeDepth > props.maxDepth) {\n disallowed = true\n reason = 'maxDepth'\n }\n out.push({\n key,\n depth: depth + 1,\n label: props.resolveLabel(n.data as TItem, key),\n hasChildren: !!(n.children && n.children.length > 0),\n disallowed,\n reason,\n })\n if (n.children?.length) walk(n.children, depth + 1)\n }\n }\n walk(props.tree.children, 0)\n return out\n})\n\nconst validCandidates = computed(() => candidates.value.filter((c) => !c.disallowed))\n\nconst selectedCandidate = computed<Candidate | null>(() =>\n selectedKey.value === undefined ? null : (candidates.value.find((c) => c.key === selectedKey.value) ?? null)\n)\n\nconst showPlacementPicker = computed<boolean>(() => !!selectedCandidate.value && selectedCandidate.value.hasChildren)\n\nconst onCandidateClick = (c: Candidate) => {\n if (c.disallowed) return\n selectedKey.value = c.key\n}\n\nconst onConfirm = () => {\n if (!selectedCandidate.value) return\n emit('confirm', selectedCandidate.value.key, placement.value)\n open.value = false\n}\n\nconst onCancel = () => {\n open.value = false\n}\n\nconst indent = (depth: number): string => `${depth * 16}px`\n\nconst reasonLabel = (reason: Candidate['reason']): string => {\n if (reason === 'maxDepth') return t('common.sortable.changeParent.errorMaxDepth')\n return ''\n}\n</script>\n\n<template>\n <VDialog\n v-model=\"open\"\n max-width=\"540\"\n persistent\n >\n <VCard>\n <VCardTitle>\n {{ t('common.sortable.changeParent.title') }}\n </VCardTitle>\n\n <VCardText\n v-if=\"validCandidates.length === 0\"\n class=\"text-body-medium\"\n >\n <VAlert\n type=\"info\"\n variant=\"tonal\"\n density=\"compact\"\n >\n <strong>{{ t('common.sortable.changeParent.noTargetsTitle') }}</strong>\n <div>{{ t('common.sortable.changeParent.noTargetsText') }}</div>\n </VAlert>\n </VCardText>\n\n <template v-else>\n <VCardText class=\"pb-1\">\n <VList\n density=\"compact\"\n class=\"le-change-parent__list\"\n >\n <VListItem\n v-for=\"c in candidates\"\n :key=\"c.key === null ? '__root__' : String(c.key)\"\n :disabled=\"c.disallowed\"\n :active=\"selectedKey === c.key\"\n :title=\"c.label\"\n :subtitle=\"reasonLabel(c.reason)\"\n :style=\"{ paddingInlineStart: indent(c.depth) }\"\n :data-disallowed=\"c.disallowed ? 'true' : 'false'\"\n :data-reason=\"c.reason ?? ''\"\n @click=\"onCandidateClick(c)\"\n >\n <template #prepend>\n <VIcon\n v-if=\"c.key === null\"\n icon=\"mdi-folder-home-outline\"\n size=\"18\"\n />\n <VIcon\n v-else-if=\"c.hasChildren\"\n icon=\"mdi-folder-outline\"\n size=\"18\"\n />\n <VIcon\n v-else\n icon=\"mdi-file-outline\"\n size=\"18\"\n />\n </template>\n </VListItem>\n </VList>\n </VCardText>\n\n <VCardText\n v-if=\"showPlacementPicker\"\n class=\"pt-0\"\n >\n <div class=\"text-body-medium mb-2\">\n {{\n t('common.sortable.changeParent.placementHeading', {\n label: selectedCandidate?.label ?? '',\n })\n }}\n </div>\n <VRadioGroup\n v-model=\"placement\"\n density=\"compact\"\n hide-details\n >\n <VRadio\n value=\"first\"\n :label=\"t('common.sortable.changeParent.placeFirst')\"\n />\n <VRadio\n value=\"last\"\n :label=\"t('common.sortable.changeParent.placeLast')\"\n />\n </VRadioGroup>\n </VCardText>\n </template>\n\n <VCardActions>\n <VSpacer />\n <VBtn\n variant=\"text\"\n @click=\"onCancel\"\n >\n {{ t('common.sortable.reorderCancel') }}\n </VBtn>\n <VBtn\n color=\"primary\"\n variant=\"elevated\"\n :disabled=\"!selectedCandidate || selectedCandidate.disallowed\"\n @click=\"onConfirm\"\n >\n {{ t('common.sortable.changeParent.confirm') }}\n </VBtn>\n </VCardActions>\n </VCard>\n </VDialog>\n</template>\n\n<style lang=\"scss\" scoped>\n.le-change-parent__list {\n max-height: 340px;\n overflow-y: auto;\n}\n</style>\n","<script setup lang=\"ts\" generic=\"TItem extends Record<string, any>\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, useSlots, useTemplateRef, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport { useContainerWidth } from '@/labs/listEditor/composables/useContainerWidth'\nimport { useIsTouchDevice } from '@/labs/listEditor/composables/useIsTouchDevice'\nimport { useKeyboardNav } from '@/labs/listEditor/composables/useKeyboardNav'\nimport { useSortable } from '@vueuse/integrations/useSortable'\nimport { type NestedViewItem } from '@/labs/listEditor/composables/useNestedListEditor'\nimport {\n useNestedListEditorController,\n type GetKey,\n type ListEditorValidationResult,\n type NestedListEditorHandle,\n type PositionOption,\n type PositionStrategy,\n} from '@/labs/listEditor/composables/useNestedListEditorController'\nimport {\n computeInstruction,\n type ExecutableInstruction,\n type Instruction,\n} from '@/labs/listEditor/composables/useDragInstruction'\nimport {\n provideListEditorStateScope,\n useNestedListEditorStateEntry,\n type NestedListEditorStateBindings,\n} from '@/labs/listEditor/composables/useListEditorStateScope'\nimport { resolveCompactText as resolveCompactTextUtil } from '@/labs/listEditor/composables/resolveCompactText'\nimport { useUnsavedSection } from '@/labs/unsavedGuard/useUnsavedSection'\nimport { useDeleteDialog } from '@/labs/listEditor/composables/useDeleteDialog'\nimport { useInlineEditing } from '@/labs/listEditor/composables/useInlineEditing'\nimport { validateAllAndReveal } from '@/labs/listEditor/utils/revealInvalidRows'\nimport { useListEditorScopeValidity } from '@/labs/listEditor/composables/useListEditorScopeValidity'\nimport { useReorderMode } from '@/labs/listEditor/composables/useReorderMode'\nimport { cloneDeep } from '@/utils/common'\nimport type {\n ListEditorKey,\n ListEditorValidationScope,\n ListEditorValidationState,\n NestedPositionHint,\n NestedTree,\n NestedTreeNode,\n} from '@/labs/listEditor/types/listEditorTypes'\nimport { useAlerts } from '@/composables/system/alerts'\nimport { stringToInt } from '@/utils/string'\nimport LeNestedRow from '@/labs/listEditor/internal/LeNestedRow.vue'\nimport LeDeleteDialog from '@/labs/listEditor/internal/LeDeleteDialog.vue'\nimport LeMoveToPositionDialog from '@/labs/listEditor/internal/LeMoveToPositionDialog.vue'\nimport LeChangeParentDialog from '@/labs/listEditor/internal/LeChangeParentDialog.vue'\nimport LeEmptyState from '@/labs/listEditor/internal/LeEmptyState.vue'\nimport LeStatus from '@/labs/listEditor/internal/LeStatus.vue'\nimport { DRAG_GHOST_CLASS, DRAG_CHOSEN_CLASS, DRAG_CLASS } from '@/labs/listEditor/internal/constants'\n\nexport interface DecoratedNestedViewItem<T> extends NestedViewItem<T> {\n editing: boolean\n expanded: boolean\n childrenExpanded: boolean\n loading: boolean\n dirty: boolean\n moved: boolean\n unsaved: boolean\n}\n\nexport interface DragState {\n sourceKey: ListEditorKey\n sourceSubtreeDepth: number\n instruction: Instruction | null\n}\n\nexport type ReorderMode = 'view' | 'reorder'\n\n// Hoisted for vite-plugin-dts d.ts rollup.\nexport interface RowActions<TItem> {\n edit: () => void\n save: () => Promise<void> | void\n cancel: () => void\n close: () => void\n delete: () => Promise<void>\n addAfter: () => void\n addChild: () => void\n toggleExpand: () => void\n toggleDetail: () => void\n moveUp: () => void\n moveDown: () => void\n moveTop: () => void\n moveBottom: () => void\n indent: () => void\n outdent: () => void\n update: (data: TItem) => void\n}\nexport interface RowSlotProps<TItem extends Record<string, any>> {\n item: DecoratedNestedViewItem<TItem> & { validationState: ListEditorValidationState }\n raw: TItem\n index: number\n key: ListEditorKey\n depth: number\n parent: TItem | null\n parentKey: ListEditorKey | null\n childrenCount: number\n hasChildren: boolean\n childrenAllowed: boolean\n firstInParent: boolean\n lastInParent: boolean\n readonly: boolean\n disabled: boolean\n expanded: boolean\n childrenExpanded: boolean\n editing: boolean\n dirty: boolean\n unsaved: boolean\n moved: boolean\n reorderMode: boolean\n canMoveUp: boolean\n canMoveDown: boolean\n canIndent: boolean\n canOutdent: boolean\n canAddChild: boolean\n touch: boolean\n /**\n * Row-scoped `state-key` prefix. Thread it down to any list editor rendered in this slot and set\n * its `:state-key=\"`${stateKeyPrefix}:someList`\"` — that editor's controller then lives in THIS\n * editor's scope and survives the row collapsing (which unmounts the whole slot subtree). Entries\n * under this prefix are disposed when the row is removed.\n */\n stateKeyPrefix: string\n actions: RowActions<TItem>\n}\nexport interface ToolbarSlotProps {\n applying: boolean\n hasPendingChanges: boolean\n movedCount: number\n error: string | null\n actions: { apply: () => Promise<void>; cancel: () => void }\n}\nexport interface ReorderToggleSlotProps {\n mode: ReorderMode\n disabled: boolean\n hasPendingChanges: boolean\n actions: { enterReorderMode: () => void; exitReorderMode: () => void }\n}\nexport interface HeaderSlotProps extends ReorderToggleSlotProps {\n title: string | null\n}\nexport interface EmptySlotProps {\n readonly: boolean\n disabled: boolean\n actions: { add: () => void }\n}\nexport interface AddButtonSlotProps {\n readonly: boolean\n disabled: boolean\n props: { onClick: () => void }\n actions: { add: () => void }\n}\n\nexport interface Props<TItem extends Record<string, any>> {\n maxDepth: number\n\n /**\n * New-row factory; if set, add/add-after/add-inside insert through the\n * controller (no `@add` handler needed). Optional for read-only trees.\n */\n factory?: () => TItem\n /**\n * Stable row identity. Default `'id'`. Never point at the position field.\n * Typed `string` (not `keyof TItem`): a bare keyof compiles to a Boolean-only\n * runtime type that silently coerces `get-key=\"id\"` to `true`.\n */\n getKey?: string | ((item: TItem) => ListEditorKey)\n /**\n * Managed order field. Default `'position'`; `false` opts out. Typed\n * `[String, Boolean, Object]` with `string` before `false`: a bare keyof\n * compiles Boolean-only, and `false`-first would coerce `position=\"position\"`\n * (value == prop name) to `true`. Object form folds in `{ field, multiplier }`.\n */\n position?: string | false | { field: string; multiplier?: number; strategy?: PositionStrategy }\n /** Parent-key field written onto reparented rows. Default `'parent'`. */\n parentField?: string\n /**\n * Extra fields dropped from the dirty content-hash (position + parent always\n * dropped), e.g. a child collection tracked by a separate editor.\n */\n dirtyExclude?: string[]\n /** Per-row validity — `true` (or `{ valid: true }`) = VALID. Drives the red rail + save guard. */\n validate?: (item: TItem) => ListEditorValidationResult\n /**\n * Vuelidate `$scope` (the same one the consumer's row forms / save-gate collector use). When set,\n * the editor registers its aggregate validity under it, so a plain `v$.$invalid` save gate blocks\n * AND reveals a collapsed invalid row — no `validateAll()` call needed in the save flow. Omit to\n * keep legacy behavior (gate the save on the exposed `validateAll()` yourself); `false` opts out.\n */\n validationScope?: ListEditorValidationScope | false\n /**\n * Opt-in lifted controller (`useNestedListEditorController()`) so state\n * survives unmount/remount; omitted = internal controller. Same handle\n * reachable via `useTemplateRef`.\n */\n editor?: NestedListEditorHandle<TItem>\n /**\n * Persist this editor's controller in the nearest ANCESTOR editor's row-state scope under this\n * key, instead of owning it. Use it for an editor rendered in another editor's `#item` slot (that\n * slot is behind a `v-if`, so collapsing the row unmounts this editor and a component-owned\n * controller would re-baseline the already-edited tree on re-expand). Derive the key from the row\n * slot's `stateKeyPrefix`. Options stay declared here, on this tag. Ignored when `:editor` is\n * passed, and a no-op without an ancestor scope.\n */\n stateKey?: string\n\n readonly?: boolean\n disabled?: boolean\n loading?: boolean\n error?: string | null\n\n /** Heading above the list. A ready translated string, like every text prop here. */\n title?: string | null\n\n /**\n * Field shown in a collapsed row when no `#item-compact` slot is given. A dotted path reads\n * through nested objects (`texts.title`); a path that runs into nothing renders empty.\n */\n compactField?: string | null\n statusField?: string | null\n\n showAddButton?: boolean\n showAddChildButton?: boolean\n showDeleteButton?: boolean\n showEditButton?: boolean\n showAddAfterAction?: boolean\n showMoveToPosition?: boolean\n showChangeParent?: boolean\n showExpandToggle?: boolean\n\n /** Label of the add button. A ready translated string; omit it for the library's own wording. */\n addLabel?: string | null\n /** Heading of the empty state. A ready translated string. */\n emptyTitle?: string | null\n\n disableRowClick?: boolean\n disableDeleteConfirm?: boolean\n /**\n * Disable unsaved-state tracking — no dirty markers, never reads as unsaved. For a list that is a\n * VIEW of data owned elsewhere (a display cache rebuilt from a parent's field, say): nothing in\n * such a component can ever clear an amber row, so it must not raise one. Mirrors `AListEditor`.\n */\n disableUnsaved?: boolean\n deleteConfirmTitle?: string | null\n deleteConfirmText?: string | null\n /**\n * How a row delete persists. `deferred` (default): the row disappears but the deletion counts as an\n * unconfirmed change until save (revertible until then, incl. via reorder Cancel). `immediate`: the\n * consumer deletes on the backend — the dialog says it is irreversible and it does NOT read as unsaved.\n */\n deleteMode?: 'immediate' | 'deferred'\n\n closeVariant?: 'auto' | 'icon' | 'labeled'\n\n loadingKeys?: Set<ListEditorKey> | null\n\n showReorderToggle?: boolean\n disableReorder?: boolean\n disableDrag?: boolean\n\n onDeleteConfirm?: (item: TItem) => Promise<boolean> | boolean\n onDelete?: (item: TItem) => Promise<void> | void\n onItemSave?: (item: TItem) => Promise<void> | void\n onReorderApply?: (tree: NestedTree<TItem>) => Promise<void> | void\n\n /** Registers this editor as a named unsaved-changes section under the given (translated) label. */\n unsavedSectionLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props<TItem>>(), {\n unsavedSectionLabel: undefined,\n factory: undefined,\n getKey: undefined,\n position: undefined,\n parentField: 'parent',\n dirtyExclude: undefined,\n validate: undefined,\n validationScope: undefined,\n editor: undefined,\n stateKey: undefined,\n readonly: false,\n disabled: false,\n loading: false,\n error: null,\n title: null,\n compactField: null,\n statusField: null,\n showAddButton: true,\n showAddChildButton: true,\n showDeleteButton: true,\n showEditButton: true,\n showAddAfterAction: false,\n showMoveToPosition: false,\n showChangeParent: false,\n showExpandToggle: true,\n addLabel: null,\n emptyTitle: null,\n disableRowClick: false,\n disableDeleteConfirm: false,\n disableUnsaved: false,\n deleteConfirmTitle: null,\n deleteConfirmText: null,\n deleteMode: 'deferred',\n closeVariant: 'auto',\n loadingKeys: null,\n showReorderToggle: true,\n disableReorder: false,\n disableDrag: false,\n onDeleteConfirm: undefined,\n onDelete: undefined,\n onItemSave: undefined,\n onReorderApply: undefined,\n})\n\nconst emit = defineEmits<{\n add: [hint: NestedPositionHint | undefined]\n 'add-child': [parent: NestedViewItem<TItem>]\n edit: [item: NestedViewItem<TItem>]\n deleted: [item: NestedViewItem<TItem>]\n close: [item: NestedViewItem<TItem>]\n 'item-saved': [item: NestedViewItem<TItem>]\n 'item-expand': [item: NestedViewItem<TItem>, expanded: boolean]\n 'reorder-start': []\n 'reorder-cancel': []\n 'reorder-applied': [tree: NestedTree<TItem>]\n 'reorder-apply-error': [error: unknown]\n 'reorder-end': []\n indent: [item: NestedViewItem<TItem>]\n outdent: [item: NestedViewItem<TItem>]\n}>()\n\nconst modelValue = defineModel<NestedTree<TItem>>({ required: true })\nconst mode = defineModel<ReorderMode>('mode', { default: 'view' })\n\ndefineSlots<{\n header?: (props: HeaderSlotProps) => unknown\n 'reorder-toggle'?: (props: ReorderToggleSlotProps) => unknown\n 'reorder-toolbar'?: (props: ToolbarSlotProps) => unknown\n empty?: (props: EmptySlotProps) => unknown\n 'add-button'?: (props: AddButtonSlotProps) => unknown\n item?: (props: RowSlotProps<TItem>) => unknown\n 'item-compact'?: (props: RowSlotProps<TItem>) => unknown\n 'item-readonly'?: (props: RowSlotProps<TItem>) => unknown\n 'item-status'?: (props: RowSlotProps<TItem>) => unknown\n 'item-footer'?: (props: RowSlotProps<TItem>) => unknown\n 'item-actions'?: (props: RowSlotProps<TItem>) => unknown\n 'before-item'?: (props: RowSlotProps<TItem>) => unknown\n 'after-item'?: (props: RowSlotProps<TItem>) => unknown\n}>()\n\nconst { t } = useI18n()\nconst slots = useSlots()\nconst { showWarningT } = useAlerts()\n\nconst rootEl = useTemplateRef<HTMLElement>('rootEl')\nconst { isNarrow } = useContainerWidth(rootEl)\n\nconst isTouch = useIsTouchDevice()\n\nconst effectiveCloseVariant = computed<'icon' | 'labeled'>(() => {\n if (props.closeVariant === 'icon') return 'icon'\n if (props.closeVariant === 'labeled') return 'labeled'\n return isNarrow.value ? 'icon' : 'labeled'\n})\n\nconst childrenExpandedKeys = ref<Set<ListEditorKey>>(new Set())\n// Readonly detail body visibility — independent of subtree expansion.\nconst detailExpandedKeys = ref<Set<ListEditorKey>>(new Set())\n\n// Local mirror of the controller's key resolution so walkers + the template key\n// rows the same way the controller tracks them. Construction-time config, read once.\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst getKeyOpt = props.getKey ?? 'id'\nconst keyFieldName = (typeof getKeyOpt === 'function' ? 'id' : getKeyOpt) as string\nconst keyOf = (data: TItem): ListEditorKey =>\n typeof getKeyOpt === 'function' ? getKeyOpt(data) : (data[getKeyOpt as keyof TItem] as ListEditorKey)\n\n// Managed position field (row position, move-to-position dialog, flatViewItems). Mirrors the controller.\nconst positionFieldName = ((): string => {\n const p = props.position\n if (p === undefined || p === false) return 'position'\n if (typeof p === 'object') return p.field\n return p\n})()\n\nconst initChildrenExpanded = (tree: NestedTree<TItem>) => {\n const walk = (nodes: NestedTreeNode<TItem>[]) => {\n for (const n of nodes) {\n if (n.children && n.children.length > 0) {\n childrenExpandedKeys.value.add(keyOf(n.data))\n walk(n.children)\n }\n }\n }\n walk(tree.children)\n}\n// eslint-disable-next-line vue/no-ref-object-reactivity-loss\ninitChildrenExpanded(modelValue.value)\n\n// State controller (v2). Undefined getKey/position/validate fall back to controller defaults\n// ('id' / 'position' / always-valid). Its `viewItems` ignores expand state, so we re-project an\n// expand-aware list below (flatViewItems).\nconst controllerOptions: NestedListEditorStateBindings<TItem> = {\n get: () => modelValue.value,\n set: (v) => (modelValue.value = v),\n factory: props.factory,\n getKey: props.getKey as GetKey<TItem> | undefined,\n position: props.position as PositionOption<TItem> | undefined,\n parentField: props.parentField,\n maxDepth: props.maxDepth,\n dirtyExclude: () => props.dirtyExclude ?? [],\n validate: props.validate,\n}\n// Precedence: an explicitly lifted `:editor` wins; then a `state-key`'d entry persisted in the\n// nearest ancestor editor's row-state scope (rebound to this instance on every remount); else this\n// component owns the controller — today's behaviour, unchanged.\nconst stateEntry = props.editor ? null : useNestedListEditorStateEntry<TItem>(props.stateKey, controllerOptions)\nconst controller = props.editor ?? stateEntry?.handle ?? useNestedListEditorController<TItem>(controllerOptions)\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\n// Row-state scope for THIS editor's `#item` slot subtree — see ASortableListEditor.\nconst rowStateScope = provideListEditorStateScope(stateEntry?.childScope ?? null)\n\n// FULL flattened list (collapsed-branch rows included) built directly off the\n// tree: the recursive renderer (LeNestedRow) filters by parentKey + childrenExpandedKeys,\n// so it needs every row present in viewItemsDecorated even when hidden.\nconst flatViewItems = computed<NestedViewItem<TItem>[]>(() => {\n const flat: NestedViewItem<TItem>[] = []\n let flatIndex = 0\n const maxDepth = props.maxDepth\n const walk = (nodes: NestedTreeNode<TItem>[], depth: number, parentNode: NestedTreeNode<TItem> | null) => {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]\n const key = keyOf(node.data)\n const childrenAllowed = node.children !== undefined\n const hasChildren = childrenAllowed && (node.children?.length ?? 0) > 0\n const childrenCount = node.children?.length ?? 0\n const remainingDepth = maxDepth - (depth + 1)\n flat.push({\n key,\n index: flatIndex++,\n raw: node.data,\n position: node.data[positionFieldName] as number | undefined,\n node,\n depth,\n parent: parentNode?.data ?? null,\n parentKey: parentNode ? keyOf(parentNode.data) : null,\n childrenCount,\n hasChildren,\n childrenAllowed,\n siblingIndex: i,\n siblingCount: nodes.length,\n firstInParent: i === 0,\n lastInParent: i === nodes.length - 1,\n canAddChild: childrenAllowed && remainingDepth > 0,\n canIndent: i > 0,\n canOutdent: depth > 0,\n })\n if (hasChildren) walk(node.children as NestedTreeNode<TItem>[], depth + 1, node)\n }\n }\n walk(modelValue.value.children, 0, null)\n return flat\n})\n\n// Drop the persisted controllers of rows that no longer exist. `post` so the removed row's subtree\n// has already unmounted (its editors released their entries) before we stop their effect scopes.\nwatch(\n () => flatViewItems.value.map((vi) => String(vi.key)).join('|'),\n () => rowStateScope.retainOwners(flatViewItems.value.map((vi) => vi.key)),\n { flush: 'post' }\n)\n\n// Snapshot for reorder-cancel restore only; dirty detection lives in the controller.\n// `movedKeys` is component view state driving the toolbar \"N pending\" counter,\n// cleared on every mode transition by `useReorderMode`.\nconst snapshot = shallowRef<NestedTree<TItem> | null>(null)\nconst movedKeys = ref<Set<ListEditorKey>>(new Set())\n// Deletes made during the current reorder session, split by mode — deferred ones count in the toolbar\n// and revert on Cancel; immediate (backend) ones are re-applied on Cancel so they stay gone (else the\n// snapshot-restore would resurrect a server-deleted row). Mirrors ASortableListEditor.\nconst sessionDeferredDeletes = ref<Set<ListEditorKey>>(new Set())\nconst sessionImmediateDeletes = ref<Set<ListEditorKey>>(new Set())\n\n// Re-baseline current tree as saved. Legacy name kept; maps onto `controller.commit()`.\nconst resetDirtyBaseline = () => {\n controller.commit()\n movedKeys.value = new Set()\n}\n// Mark row + every descendant moved — a moved parent visually carries its subtree.\nconst markMoved = (key: ListEditorKey) => {\n const { node } = controller.findNode(key)\n if (!node) {\n movedKeys.value.add(key)\n return\n }\n const collect = (n: NestedTreeNode<TItem>) => {\n movedKeys.value.add(keyOf(n.data))\n if (n.children) n.children.forEach(collect)\n }\n collect(node)\n}\n\nconst decoratorCache = new Map<ListEditorKey, DecoratedNestedViewItem<TItem>>()\nconst viewItemsDecorated = computed<DecoratedNestedViewItem<TItem>[]>(() => {\n const next: DecoratedNestedViewItem<TItem>[] = []\n const liveKeys = new Set<ListEditorKey>()\n for (const vi of flatViewItems.value) {\n liveKeys.add(vi.key)\n const editing = editingKeys.value.has(vi.key)\n const expanded = detailExpandedKeys.value.has(vi.key)\n const childrenExpanded = childrenExpandedKeys.value.has(vi.key)\n const loading = props.loadingKeys?.has(vi.key) ?? false\n const moved = movedKeys.value.has(vi.key)\n // Amber = controller dirty OR reorder-session moved. readonly suppresses it\n // (read-only views can't be unsaved; also dodges a mount-before-load baseline). (QA 85050 sweep)\n const dirty = props.readonly ? false : controller.isUnsaved(vi.key)\n // `disableUnsaved` suppresses only the amber marker — the validation rail still shows, since\n // hiding dirty state should not hide a real error.\n const unsaved = props.disableUnsaved || props.readonly ? false : dirty || moved\n const cached = decoratorCache.get(vi.key)\n if (\n cached &&\n cached.raw === vi.raw &&\n cached.index === vi.index &&\n cached.position === vi.position &&\n cached.depth === vi.depth &&\n cached.parentKey === vi.parentKey &&\n cached.childrenCount === vi.childrenCount &&\n cached.firstInParent === vi.firstInParent &&\n cached.lastInParent === vi.lastInParent &&\n cached.editing === editing &&\n cached.expanded === expanded &&\n cached.childrenExpanded === childrenExpanded &&\n cached.loading === loading &&\n cached.dirty === dirty &&\n cached.moved === moved &&\n cached.unsaved === unsaved\n ) {\n next.push(cached)\n continue\n }\n const decorated: DecoratedNestedViewItem<TItem> = {\n ...vi,\n editing,\n expanded,\n childrenExpanded,\n loading,\n dirty,\n moved,\n unsaved,\n }\n decoratorCache.set(vi.key, decorated)\n next.push(decorated)\n }\n for (const key of decoratorCache.keys()) {\n if (!liveKeys.has(key)) decoratorCache.delete(key)\n }\n return next\n})\n\nconst isEmpty = computed(() => modelValue.value.children.length === 0)\nconst totalItemCount = computed(() => flatViewItems.value.length)\n\nconst rowsContainer = useTemplateRef<HTMLElement>('rowsContainer')\n\nconst canInteract = computed(() => !props.readonly && !props.disabled && !props.loading)\nconst canEnterReorder = computed(() => canInteract.value && !props.disableReorder && totalItemCount.value > 1)\n\nconst isInlineEdit = computed(() => !!(slots as Record<string, unknown>).item)\nconst hasReadonlyDetail = computed(() => !!(slots as Record<string, unknown>)['item-readonly'])\nconst showInlineSaveFooter = computed(() => !!props.onItemSave)\n\nconst { editingKeys, editingSnapshots, clearEditing, beginEdit, cancelEdit, commitEdit, closeEdit, requestAutoOpen } =\n useInlineEditing<TItem, NestedViewItem<TItem>>({\n rowsContainer,\n rowSelector: '.a-le-row-wrapper',\n isInlineEdit,\n // markDirty=false: restoring the pre-edit snapshot on cancel must not flag the\n // node dirty (it would otherwise be resent by partial-multi saves).\n restoreSnapshot: (key, data) => controller.updateItem(key, data, false),\n watchKeys: () => {\n const keys: ListEditorKey[] = []\n const walk = (nodes: NestedTreeNode<TItem>[]) => {\n for (const n of nodes) {\n keys.push(keyOf(n.data))\n if (n.children && n.children.length) walk(n.children)\n }\n }\n walk(modelValue.value.children)\n return keys\n },\n findEntry: (key) => {\n const { node } = controller.findNode(key)\n return node ? { data: node.data } : null\n },\n afterAutoOpen: (key) => {\n const { parent } = controller.findNode(key)\n if (parent) {\n childrenExpandedKeys.value.add(keyOf(parent.data))\n }\n },\n })\n\nconst {\n applying,\n applyError,\n hasPendingChanges,\n movedCount,\n reorderMode,\n enterReorderMode,\n cancelReorderMode,\n applyReorder,\n} = useReorderMode<NestedTree<TItem>>({\n mode,\n snapshot,\n movedKeys,\n modelValue,\n cloneModel: (m) => cloneDeep(m) as NestedTree<TItem>,\n applyModel: (m) => {\n modelValue.value = m\n },\n canEnterReorder,\n onEnter: () => {\n sessionDeferredDeletes.value = new Set()\n sessionImmediateDeletes.value = new Set()\n clearEditing()\n // Expand every branch so all rows are reachable drag targets.\n for (const k of expandableKeys.value) childrenExpandedKeys.value.add(k)\n nextTick(() => {\n if (dragEnabled.value) initSortables()\n })\n },\n onExternalEnter: () => {\n sessionDeferredDeletes.value = new Set()\n sessionImmediateDeletes.value = new Set()\n clearEditing()\n },\n // Cancel: the snapshot-restore has brought every deleted row back; reconcile the controller so a\n // deferred delete truly reverts (clear tombstone, row stays) and an immediate delete stays gone (row\n // removed again — it is deleted on the backend and must not resurrect / be re-created by a save).\n onCancel: () => {\n for (const key of sessionDeferredDeletes.value) controller.restoreDeleted(key)\n for (const key of sessionImmediateDeletes.value) controller.deleteItem(key, { trackDeleted: false })\n sessionDeferredDeletes.value = new Set()\n sessionImmediateDeletes.value = new Set()\n destroySortables()\n },\n onApplyEnd: () => {\n sessionDeferredDeletes.value = new Set()\n sessionImmediateDeletes.value = new Set()\n destroySortables()\n },\n onReorderApply: (tree) => props.onReorderApply?.(tree),\n emit: {\n reorderStart: () => emit('reorder-start'),\n reorderCancel: () => emit('reorder-cancel'),\n reorderApplied: (payload) => emit('reorder-applied', payload),\n reorderApplyError: (err) => emit('reorder-apply-error', err),\n reorderEnd: () => emit('reorder-end'),\n },\n})\n\n// Reorder-toolbar pending indicator = session moves + deferred deletes made in the session, so a delete\n// in reorder mode no longer reads as \"no pending changes\" and Apply stays enabled.\nconst nestedPendingCount = computed<number>(() => movedCount.value + sessionDeferredDeletes.value.size)\nconst nestedPendingChanges = computed<boolean>(() => hasPendingChanges.value || sessionDeferredDeletes.value.size > 0)\n\nconst canAdd = computed(() => canInteract.value && props.showAddButton && !reorderMode.value)\nconst dragEnabled = computed(() => reorderMode.value && !isTouch.value && !props.disableDrag)\n\nconst addLabelResolved = computed(() => props.addLabel ?? t('common.sortable.add'))\nconst emptyTitleResolved = computed(() => props.emptyTitle ?? t('common.sortable.emptyTitle'))\nconst deleteConfirmTitleResolved = computed(() => props.deleteConfirmTitle ?? t('common.sortable.deleteConfirmTitle'))\nconst deleteConfirmTextResolved = computed(\n () =>\n props.deleteConfirmText ??\n (props.deleteMode === 'immediate'\n ? t('common.sortable.deleteConfirmText')\n : t('common.sortable.deleteConfirmTextDeferred'))\n)\n\nconst reorderToggleVisible = computed<boolean>(\n (): boolean => !props.readonly && props.showReorderToggle && !reorderMode.value && totalItemCount.value > 0\n)\n\nconst compactReorderButton = computed<boolean>((): boolean => !!props.title && isNarrow.value)\n\n// Keys of every node that has children — the expand/collapse candidates.\nconst expandableKeys = computed<ListEditorKey[]>(() => {\n const out: ListEditorKey[] = []\n const walk = (nodes: NestedTreeNode<TItem>[]) => {\n for (const n of nodes) {\n if (n.children && n.children.length > 0) {\n out.push(keyOf(n.data))\n walk(n.children)\n }\n }\n }\n walk(modelValue.value.children)\n return out\n})\n\nconst allExpanded = computed<boolean>(\n () => expandableKeys.value.length > 0 && expandableKeys.value.every((k) => childrenExpandedKeys.value.has(k))\n)\n\nconst expandAllVisible = computed<boolean>(() => !reorderMode.value && expandableKeys.value.length > 0)\n\nconst toggleExpandAll = () => {\n if (allExpanded.value) {\n for (const k of expandableKeys.value) childrenExpandedKeys.value.delete(k)\n } else {\n for (const k of expandableKeys.value) childrenExpandedKeys.value.add(k)\n }\n}\n\nconst headerVisible = computed<boolean>(\n (): boolean =>\n !!(\n props.title ||\n (slots as Record<string, unknown>).header ||\n (slots as Record<string, unknown>)['reorder-toggle'] ||\n reorderToggleVisible.value ||\n expandAllVisible.value ||\n reorderMode.value\n )\n)\n\n// One Sortable instance per group so drag/drop moves items within/between groups;\n// SortableJS owns the pointer events, onEnd reconciles via editor.moveTo().\nconst sortableInstances = ref<Array<{ stop: () => void; option?: (k: string, v: unknown) => void }>>([])\nconst forceRerender = ref(0)\n\nconst destroySortables = () => {\n for (const s of sortableInstances.value) {\n try {\n s.stop()\n } catch {\n // no-op\n }\n }\n sortableInstances.value = []\n}\n\nconst GROUP_CLASS = 'a-nested-list-editor__group'\nconst HANDLE_CLASS = 'a-le-drag-handle'\n\n// Live drag state. `instruction` is recomputed on every pointermove and encodes\n// where + at what depth the dragged item lands (sibling-above/below/make-child/blocked);\n// the overlay reads it, onEnd applies it via `editor.moveTo`. SortableJS never moves\n// the DOM (onMove always returns false) — hit-testing, depth and the mutation are all ours.\nconst dragState = ref<DragState | null>(null)\n\n// Drop indicator anchor geometry, kept in sync with CSS (reorder-mode header\n// padding-left = 12 + depth*24, drag handle = 20px). ANCHOR_X = 12 + half handle\n// (10) = 22 puts the dot on the handle's centre; the line steps 24px per depth.\nconst INDENT_PX = 24\nconst ANCHOR_X = 22\n\nconst hitTestRow = (\n clientX: number,\n clientY: number\n): { el: HTMLElement; viewItem: DecoratedNestedViewItem<TItem> } | null => {\n const hit = document.elementFromPoint(clientX, clientY) as HTMLElement | null\n if (!hit) return null\n const wrapper = hit.closest('.a-le-row-wrapper') as HTMLElement | null\n if (!wrapper) return null\n // Only wrappers inside our rowsContainer — elementFromPoint could hit a\n // different ANestedSortableListEditor instance on the same page.\n if (!rowsContainer.value || !rowsContainer.value.contains(wrapper)) return null\n const key = parseKey(wrapper.getAttribute('data-id'))\n if (key === null) return null\n const vi = viewItemsDecorated.value.find((v) => v.key === key)\n if (!vi) return null\n return { el: wrapper, viewItem: vi }\n}\n\nconst recomputeInstruction = (clientX: number, clientY: number) => {\n if (!dragState.value || !rowsContainer.value) return\n const hit = hitTestRow(clientX, clientY)\n if (!hit) {\n dragState.value.instruction = null\n return\n }\n const containerRect = rowsContainer.value.getBoundingClientRect()\n // Hit-test the `.a-le-row` child, not the wrapper — the wrapper's height\n // balloons when children are rendered.\n const rowEl = hit.el.querySelector(':scope > .a-le-row') as HTMLElement | null\n const rowRect = (rowEl ?? hit.el).getBoundingClientRect()\n dragState.value.instruction = computeInstruction({\n pointer: { x: clientX, y: clientY },\n hoveredRow: {\n key: hit.viewItem.key,\n rect: rowRect,\n depth: hit.viewItem.depth,\n parentKey: hit.viewItem.parentKey,\n siblingIndex: hit.viewItem.siblingIndex,\n siblingCount: hit.viewItem.siblingCount,\n },\n sourceKey: dragState.value.sourceKey,\n sourceSubtreeDepth: dragState.value.sourceSubtreeDepth,\n viewItems: viewItemsDecorated.value,\n maxDepth: props.maxDepth,\n indentPx: INDENT_PX,\n containerLeft: containerRect.left,\n containerPaddingLeft: ANCHOR_X,\n })\n}\n\nconst onPointerMove = (e: PointerEvent) => {\n recomputeInstruction(e.clientX, e.clientY)\n}\n\nconst applyInstruction = (inst: ExecutableInstruction, sourceKey: ListEditorKey) => {\n const ok = controller.moveTo(sourceKey, inst.parentKey, inst.index)\n if (!ok) {\n showWarningT('common.sortable.error.maxDeepExceed')\n forceRerender.value++\n nextTick(() => initSortables())\n return\n }\n markMoved(sourceKey)\n // makeChild: expand the new parent so the just-moved row doesn't vanish into a collapsed branch.\n if (inst.makeChild && inst.parentKey !== null) {\n childrenExpandedKeys.value.add(inst.parentKey)\n }\n}\n\nconst initSortables = () => {\n destroySortables()\n if (!dragEnabled.value) return\n if (!rowsContainer.value) return\n const groups = Array.from(rowsContainer.value.querySelectorAll<HTMLElement>('.' + GROUP_CLASS))\n for (const group of groups) {\n const sortable = useSortable(group, [], {\n group: { name: 'a-nested', pull: true, put: true },\n handle: '.' + HANDLE_CLASS,\n animation: 0,\n ghostClass: DRAG_GHOST_CLASS,\n chosenClass: DRAG_CHOSEN_CLASS,\n dragClass: DRAG_CLASS,\n fallbackOnBody: true,\n forceFallback: true,\n fallbackTolerance: 3,\n onStart: (event) => {\n const draggedEl = event.item as HTMLElement\n const id = parseKey(draggedEl.getAttribute('data-id'))\n if (id === null) return\n const draggedNode = controller.findNode(id).node\n const subtreeDepth = draggedNode ? controller.calculateSubtreeDepth(draggedNode) : 1\n dragState.value = {\n sourceKey: id,\n sourceSubtreeDepth: subtreeDepth,\n instruction: null,\n }\n document.addEventListener('pointermove', onPointerMove, { passive: true })\n },\n onMove: (event) => {\n // Also track the pointer via SortableJS's stream — keeps `instruction`\n // in sync when the browser coalesces pointermove events during fast drags.\n const orig = (event as unknown as { originalEvent?: Event }).originalEvent\n if (orig) {\n if ('clientX' in orig && 'clientY' in orig) {\n recomputeInstruction((orig as MouseEvent).clientX, (orig as MouseEvent).clientY)\n } else if ('touches' in orig) {\n const tev = orig as TouchEvent\n if (tev.touches.length > 0) {\n recomputeInstruction(tev.touches[0].clientX, tev.touches[0].clientY)\n }\n }\n }\n // Refuse SortableJS's own DOM insertion — onEnd's `editor.moveTo` drives the move.\n return false\n },\n onEnd: () => {\n document.removeEventListener('pointermove', onPointerMove)\n const state = dragState.value\n dragState.value = null\n if (!state) return\n const inst = state.instruction\n if (inst === null) return\n if (inst.type === 'blocked') return\n applyInstruction(inst, state.sourceKey)\n },\n })\n sortableInstances.value.push(sortable)\n }\n}\n\nconst parseKey = (raw: string | null): ListEditorKey | null => {\n if (raw === null || raw === '') return null\n // Numeric key only when `data-id` is a pure integer string (incl. negative temp\n // ids like \"-1\"); else a DocId/UUID. The old `n > 0` test sent negative temp ids\n // to the string branch, so a freshly-added row's move marked the wrong key.\n const n = stringToInt(raw)\n return String(n) === raw ? n : raw\n}\n\nwatch(\n () => dragEnabled.value,\n () => {\n if (dragEnabled.value) nextTick(() => initSortables())\n else destroySortables()\n }\n)\n\nwatch(\n () => forceRerender.value,\n () => {\n if (dragEnabled.value) nextTick(() => initSortables())\n }\n)\n\n// Rebuild sortables when the tree shape changes during drag mode, else newly rendered groups aren't draggable.\nwatch(\n () => viewItemsDecorated.value.map((v) => v.key).join('|'),\n () => {\n if (dragEnabled.value) nextTick(() => initSortables())\n }\n)\n\nonMounted(() => {\n if (dragEnabled.value) initSortables()\n})\nonBeforeUnmount(() => {\n destroySortables()\n document.removeEventListener('pointermove', onPointerMove)\n})\n\n// Overlay visual props from the current instruction; `null` = hidden (no\n// instruction, or blocked). The optional connector is a rail from the drop line\n// up to the row whose level dictates the insert. Measured via getBoundingClientRect\n// rather than CSS anchor positioning: Chrome's anchor-reachability rule excludes\n// some deeply-nested descendants, so JS measurement is simpler and more reliable.\ntype OverlayVisual = {\n line: { top: number; left: number; right: number }\n connector: { top: number; height: number; left: number } | null\n}\n\nconst overlayVisual = computed<OverlayVisual | null>(() => {\n const state = dragState.value\n if (!state || state.instruction === null || !rowsContainer.value) return null\n const inst = state.instruction\n // Blocked drops render nothing — the silent empty space is the \"not here\" signal.\n if (inst.type === 'blocked') return null\n const refWrapper = rowsContainer.value.querySelector<HTMLElement>(\n `.a-le-row-wrapper[data-id=\"${CSS.escape(String(inst.refKey))}\"]`\n )\n if (!refWrapper) return null\n const rowEl = refWrapper.querySelector<HTMLElement>(':scope > .a-le-row')\n const containerRect = rowsContainer.value.getBoundingClientRect()\n const rowRect = (rowEl ?? refWrapper).getBoundingClientRect()\n\n const lineLeft = ANCHOR_X + inst.depth * INDENT_PX\n const lineTop = inst.refEdge === 'top' ? rowRect.top - containerRect.top : rowRect.bottom - containerRect.top\n const line = { top: lineTop, left: lineLeft, right: 16 }\n\n let connector: OverlayVisual['connector'] = null\n if (inst.levelRowKey !== null) {\n const levelWrapper = rowsContainer.value.querySelector<HTMLElement>(\n `.a-le-row-wrapper[data-id=\"${CSS.escape(String(inst.levelRowKey))}\"]`\n )\n if (levelWrapper) {\n const levelRow = levelWrapper.querySelector<HTMLElement>(':scope > .a-le-row')\n const levelRect = (levelRow ?? levelWrapper).getBoundingClientRect()\n const levelCentreY = levelRect.top - containerRect.top + levelRect.height / 2\n if (levelCentreY < lineTop) {\n connector = {\n top: levelCentreY,\n height: lineTop - levelCentreY,\n left: lineLeft,\n }\n }\n }\n }\n\n return { line, connector }\n})\n\n// With a `factory`, the editor inserts through the controller; without one it\n// stays emit-only so legacy `@add` consumers drive the insert. The `add` event\n// fires either way.\nconst onAddClick = () => {\n if (!canAdd.value) return\n requestAutoOpen()\n if (props.factory) controller.addItem()\n emit('add', undefined)\n}\n\nconst onRowAddAfterClick = (vi: NestedViewItem<TItem>) => {\n if (!canInteract.value) return\n requestAutoOpen()\n if (props.factory) controller.addAfter(vi.key, undefined, vi.childrenAllowed)\n emit('add', { afterId: vi.key, childrenAllowed: vi.childrenAllowed })\n}\n\nconst onAddChildClick = (vi: NestedViewItem<TItem>) => {\n if (!canInteract.value) return\n if (!vi.canAddChild) return\n requestAutoOpen()\n childrenExpandedKeys.value.add(vi.key)\n emit('add-child', vi)\n // Append to the end of children, matching the root \"Add item\" semantic.\n // (Drag-drop make-child still lands at index 0 — its drop line sits just below the parent.)\n if (props.factory) controller.addChild(vi.key, undefined, true)\n emit('add', { parentId: vi.key, childrenAllowed: true })\n}\n\nconst onEditClick = (vi: NestedViewItem<TItem>) => {\n if (!canInteract.value || reorderMode.value) return\n // Clicking edit while already editing closes the form, so the pencil toggles like the row header.\n if (editingKeys.value.has(vi.key)) {\n onCloseClick(vi)\n return\n }\n if (isInlineEdit.value) {\n beginEdit(vi)\n }\n emit('edit', vi)\n}\n\n// Toggles tree children visibility (distinct from the readonly-detail body below).\nconst onChevronClick = (vi: NestedViewItem<TItem>) => {\n if (props.disabled || props.loading) return\n const key = vi.key\n const currently = childrenExpandedKeys.value.has(key)\n if (currently) childrenExpandedKeys.value.delete(key)\n else childrenExpandedKeys.value.add(key)\n emit('item-expand', vi, !currently)\n}\n\n// Toggles the row's readonly-detail body (separate from tree children).\nconst onDetailToggle = (vi: NestedViewItem<TItem>) => {\n if (props.disabled || props.loading) return\n const key = vi.key\n const currently = detailExpandedKeys.value.has(key)\n if (currently) detailExpandedKeys.value.delete(key)\n else detailExpandedKeys.value.add(key)\n emit('item-expand', vi, !currently)\n}\n\nconst isRowClickable = (vi: DecoratedNestedViewItem<TItem>): boolean => {\n if (props.disableRowClick) return false\n if (props.disabled || props.loading) return false\n if (reorderMode.value) return false\n if (vi.editing || vi.expanded) return true\n if (!props.readonly && props.showEditButton) return true\n return false\n}\n\nconst onRowClick = (vi: DecoratedNestedViewItem<TItem>) => {\n if (!isRowClickable(vi)) return\n if (vi.editing || vi.expanded) {\n onCloseClick(vi)\n return\n }\n if (props.readonly && hasReadonlyDetail.value) onDetailToggle(vi)\n else onEditClick(vi)\n}\n\nconst {\n deleteDialog,\n deleteInFlight,\n deleteError,\n onDeleteClick: triggerDeleteClick,\n onDeleteDialogConfirm,\n onDeleteDialogCancel,\n} = useDeleteDialog<TItem, NestedViewItem<TItem>>({\n onDeleteConfirm: (raw) => (props.onDeleteConfirm ? props.onDeleteConfirm(raw) : true),\n onDelete: (raw) => props.onDelete?.(raw),\n onDeleted: (vi) => {\n editingKeys.value.delete(vi.key)\n editingSnapshots.value.delete(vi.key)\n detailExpandedKeys.value.delete(vi.key)\n childrenExpandedKeys.value.delete(vi.key)\n const deferred = props.deleteMode === 'deferred'\n // A delete in reorder mode is a session change: deferred reverts on Cancel, immediate stays gone\n // (re-applied after Cancel's snapshot-restore).\n if (reorderMode.value) {\n if (deferred) sessionDeferredDeletes.value.add(vi.key)\n else sessionImmediateDeletes.value.add(vi.key)\n }\n // Controller owns removal (mirrors the flat editors): a deferred saved-row delete is tombstoned\n // (counts as unsaved); immediate skips it (already deleted on the backend). Runs before the\n // `deleted` emit so a consumer's handler still receives `vi.raw`.\n controller.deleteItem(vi.key, { trackDeleted: deferred })\n emit('deleted', vi)\n },\n disableDeleteConfirm: () => props.disableDeleteConfirm,\n})\n\nconst onDeleteClick = async (vi: NestedViewItem<TItem>) => {\n if (!canInteract.value) return\n await triggerDeleteClick(vi)\n}\n\nconst onSaveClick = async (vi: NestedViewItem<TItem>) => {\n if (props.onItemSave) await props.onItemSave(vi.raw)\n commitEdit(vi)\n emit('item-saved', vi)\n}\n\nconst onCancelClick = (vi: NestedViewItem<TItem>) => {\n cancelEdit(vi)\n}\n\nconst onCloseClick = (vi: NestedViewItem<TItem>) => {\n closeEdit(vi)\n emit('close', vi)\n}\n\nconst moveUp = (id: ListEditorKey) => {\n if (controller.moveUp(id)) markMoved(id)\n}\nconst moveDown = (id: ListEditorKey) => {\n if (controller.moveDown(id)) markMoved(id)\n}\nconst moveTop = (id: ListEditorKey) => {\n if (controller.moveTop(id)) markMoved(id)\n}\nconst moveBottom = (id: ListEditorKey) => {\n if (controller.moveBottom(id)) markMoved(id)\n}\nconst doIndent = (vi: NestedViewItem<TItem>) => {\n if (!controller.indent(vi.key)) {\n showWarningT('common.sortable.error.maxDeepExceed')\n return\n }\n markMoved(vi.key)\n emit('indent', vi)\n}\nconst doOutdent = (vi: NestedViewItem<TItem>) => {\n if (!controller.outdent(vi.key)) return\n markMoved(vi.key)\n emit('outdent', vi)\n}\n\nconst resolveCompactText = (raw: TItem): string => resolveCompactTextUtil(raw, { compactField: props.compactField })\n\n// Row validation from the controller's gated `rowState` — red rail shows only\n// once the row is unsaved or `validateAll()` ran.\n// `editing` → the controller reads amber (not red) while the row is being filled in. (QA 85050 b7)\nconst resolveValidation = (raw: TItem, key?: ListEditorKey): ListEditorValidationState =>\n key === undefined ? null : controller.rowState(raw, key, editingKeys.value.has(key))\n\n// Per-key actions cache for stable per-row identity (see AListEditor). Closures\n// capture the stable key and look up the current vi via findVi at call time.\ntype ActionsBundle = {\n edit: () => void\n save: () => Promise<void> | void\n cancel: () => void\n close: () => void\n delete: () => Promise<void>\n addAfter: () => void\n addChild: () => void\n toggleExpand: () => void\n toggleDetail: () => void\n moveUp: () => void\n moveDown: () => void\n moveTop: () => void\n moveBottom: () => void\n indent: () => void\n outdent: () => void\n update: (data: TItem) => void\n}\nconst actionsCache = new Map<ListEditorKey, ActionsBundle>()\nconst getActions = (key: ListEditorKey): ActionsBundle => {\n let actions = actionsCache.get(key)\n if (!actions) {\n actions = {\n edit: () => {\n const vi = findVi(key)\n if (vi) onEditClick(vi)\n },\n save: () => {\n const vi = findVi(key)\n if (vi) return onSaveClick(vi)\n },\n cancel: () => {\n const vi = findVi(key)\n if (vi) onCancelClick(vi)\n },\n close: () => {\n const vi = findVi(key)\n if (vi) onCloseClick(vi)\n },\n delete: async () => {\n const vi = findVi(key)\n if (vi) await onDeleteClick(vi)\n },\n addAfter: () => {\n const vi = findVi(key)\n if (vi) onRowAddAfterClick(vi)\n },\n addChild: () => {\n const vi = findVi(key)\n if (vi) onAddChildClick(vi)\n },\n toggleExpand: () => {\n const vi = findVi(key)\n if (vi) onChevronClick(vi)\n },\n toggleDetail: () => {\n const vi = findVi(key)\n if (vi) onDetailToggle(vi)\n },\n moveUp: () => moveUp(key),\n moveDown: () => moveDown(key),\n moveTop: () => moveTop(key),\n moveBottom: () => moveBottom(key),\n indent: () => {\n const vi = findVi(key)\n if (vi) doIndent(vi)\n },\n outdent: () => {\n const vi = findVi(key)\n if (vi) doOutdent(vi)\n },\n update: (data: TItem) => controller.updateItem(key, data),\n }\n actionsCache.set(key, actions)\n }\n return actions\n}\nwatch(\n () => viewItemsDecorated.value,\n (now) => {\n if (actionsCache.size === 0) return\n const liveKeys = new Set(now.map((v) => v.key))\n for (const key of actionsCache.keys()) {\n if (!liveKeys.has(key)) actionsCache.delete(key)\n }\n }\n)\n\nconst buildSlotProps = (vi: DecoratedNestedViewItem<TItem>) => ({\n item: { ...vi, validationState: resolveValidation(vi.raw as TItem, vi.key) },\n raw: vi.raw,\n index: vi.index,\n key: vi.key,\n depth: vi.depth,\n parent: vi.parent,\n parentKey: vi.parentKey,\n childrenCount: vi.childrenCount,\n hasChildren: vi.hasChildren,\n childrenAllowed: vi.childrenAllowed,\n firstInParent: vi.firstInParent,\n lastInParent: vi.lastInParent,\n readonly: props.readonly,\n disabled: props.disabled,\n expanded: vi.expanded,\n childrenExpanded: vi.childrenExpanded,\n editing: vi.editing,\n dirty: vi.dirty,\n unsaved: vi.unsaved,\n moved: vi.moved,\n reorderMode: reorderMode.value,\n canMoveUp: !vi.firstInParent,\n canMoveDown: !vi.lastInParent,\n canIndent: vi.canIndent,\n canOutdent: vi.canOutdent,\n canAddChild: vi.canAddChild,\n touch: isTouch.value,\n stateKeyPrefix: rowStateScope.prefixFor(vi.key),\n actions: getActions(vi.key),\n})\n\nconst toolbarSlotProps = computed(() => ({\n applying: applying.value,\n hasPendingChanges: nestedPendingChanges.value,\n movedCount: nestedPendingCount.value,\n error: applyError.value,\n actions: {\n apply: applyReorder,\n cancel: cancelReorderMode,\n },\n}))\n\nconst reorderToggleSlotProps = computed(() => ({\n mode: mode.value,\n disabled: !canEnterReorder.value,\n hasPendingChanges: hasPendingChanges.value,\n actions: {\n enterReorderMode,\n exitReorderMode: cancelReorderMode,\n },\n}))\n\nconst expandableKeySet = computed(() => new Set(expandableKeys.value))\n\nconst findVi = (key: ListEditorKey): DecoratedNestedViewItem<TItem> | undefined =>\n viewItemsDecorated.value.find((v) => v.key === key)\n\nconst keyboardNav = useKeyboardNav({\n viewItems: computed(() =>\n viewItemsDecorated.value.map((vi) => ({\n key: vi.key,\n expanded: vi.childrenExpanded,\n canExpand: expandableKeySet.value.has(vi.key),\n canIndent: true,\n canOutdent: true,\n }))\n ),\n variant: 'nested',\n isReorderMode: reorderMode,\n disabled: computed(() => !canInteract.value),\n isEditing: (key) => editingKeys.value.has(key),\n onToggleEdit: (key) => {\n const vi = findVi(key)\n if (vi) onEditClick(vi)\n },\n onCancelEdit: (key) => {\n const vi = findVi(key)\n if (vi) onCloseClick(vi)\n },\n onMoveUp: (key) => moveUp(key),\n onMoveDown: (key) => moveDown(key),\n onMoveTop: (key) => moveTop(key),\n onMoveBottom: (key) => moveBottom(key),\n onIndent: (key) => {\n const vi = findVi(key)\n if (vi) doIndent(vi)\n },\n onOutdent: (key) => {\n const vi = findVi(key)\n if (vi) doOutdent(vi)\n },\n onToggleChildren: (key) => {\n const vi = findVi(key)\n if (vi) onChevronClick(vi)\n },\n onCancelReorder: () => cancelReorderMode(),\n})\n\nconst moveToPositionDialogOpen = ref<boolean>(false)\nconst moveToPositionTarget = shallowRef<DecoratedNestedViewItem<TItem> | null>(null)\nconst moveToPositionContext = computed<{\n parentId: ListEditorKey | null\n total: number\n currentIndex: number\n} | null>(() => {\n const target = moveToPositionTarget.value\n if (!target) return null\n const found = controller.findNode(target.key)\n const siblings = found.parent?.children ?? modelValue.value.children\n const idx = siblings.findIndex((s) => keyOf(s.data) === target.key)\n return {\n parentId: found.parent ? keyOf(found.parent.data) : null,\n total: siblings.length,\n currentIndex: idx,\n }\n})\nconst moveToPositionLabel = computed<string>(() =>\n moveToPositionTarget.value ? resolveCompactText(moveToPositionTarget.value.raw) : ''\n)\nconst openMoveToPosition = (vi: DecoratedNestedViewItem<TItem>) => {\n if (!props.showMoveToPosition) return\n moveToPositionTarget.value = vi\n moveToPositionDialogOpen.value = true\n}\nconst onMoveToPositionConfirm = (newIndex: number) => {\n const ctx = moveToPositionContext.value\n const target = moveToPositionTarget.value\n moveToPositionTarget.value = null\n if (!ctx || !target) return\n if (newIndex === ctx.currentIndex) return\n if (controller.moveTo(target.key, ctx.parentId, newIndex)) {\n markMoved(target.key)\n }\n}\n\nconst changeParentDialogOpen = ref<boolean>(false)\nconst changeParentTarget = shallowRef<DecoratedNestedViewItem<TItem> | null>(null)\nconst openChangeParent = (vi: DecoratedNestedViewItem<TItem>) => {\n if (!props.showChangeParent) return\n changeParentTarget.value = vi\n changeParentDialogOpen.value = true\n}\nconst onChangeParentConfirm = (parentId: ListEditorKey | null, position: 'first' | 'last') => {\n const target = changeParentTarget.value\n changeParentTarget.value = null\n if (!target) return\n // Sibling count under the new parent so 'last' resolves to the right index ('first' = 0).\n let siblingCount = 0\n if (parentId === null) {\n siblingCount = modelValue.value.children.length\n } else {\n const found = controller.findNode(parentId)\n siblingCount = found.node?.children?.length ?? 0\n }\n const targetIndex = position === 'first' ? 0 : siblingCount\n if (controller.moveTo(target.key, parentId, targetIndex)) {\n markMoved(target.key)\n if (parentId !== null) childrenExpandedKeys.value.add(parentId)\n }\n}\n\n// Aggregated display flags + helpers passed into each <LeNestedRow>.\nconst rowContext = computed(() => ({\n reorderMode: reorderMode.value,\n readonly: props.readonly,\n canInteract: canInteract.value,\n dragEnabled: dragEnabled.value,\n showExpandToggle: props.showExpandToggle,\n showEditButton: props.showEditButton,\n showDeleteButton: props.showDeleteButton,\n showAddChildButton: props.showAddChildButton,\n showAddAfterAction: props.showAddAfterAction,\n showMoveToPosition: props.showMoveToPosition,\n showChangeParent: props.showChangeParent,\n showInlineSaveFooter: showInlineSaveFooter.value,\n statusField: props.statusField,\n effectiveCloseVariant: effectiveCloseVariant.value,\n keyboardNav,\n isRowClickable,\n resolveCompactText,\n resolveValidation: (raw: TItem, key?: ListEditorKey) => resolveValidation(raw, key),\n buildSlotProps,\n}))\n\n// Event callback bundle passed to each row.\nconst rowCallbacks = {\n onRowClick,\n onChevronClick,\n onEditClick,\n onDeleteClick,\n onCloseClick,\n onSaveClick,\n onCancelClick,\n onAddAfterClick: onRowAddAfterClick,\n onAddChildClick,\n moveUp: (id: ListEditorKey) => moveUp(id),\n moveDown: (id: ListEditorKey) => moveDown(id),\n moveTop: (id: ListEditorKey) => moveTop(id),\n moveBottom: (id: ListEditorKey) => moveBottom(id),\n indent: doIndent,\n outdent: doOutdent,\n openMoveToPosition,\n openChangeParent,\n}\n\nconst rootViewItems = computed(() => viewItemsDecorated.value.filter((v) => v.parentKey === null))\n\n// Imperative aliases mirroring the legacy ASortableNested signatures so existing\n// consumers keep working. They assume the caller persists server-side, so they\n// re-baseline via `controller.commit()` (rows don't render \"unsaved\" after the\n// call) and return the live model. New consumers should use the controller handle directly.\n\n/** @deprecated Use `addItem(data, { afterId, childrenAllowed })` on the handle. */\nconst addAfterId = (targetId: ListEditorKey | null, data: TItem, childrenAllowed: boolean) => {\n controller.addItem(data, { afterId: targetId ?? undefined, childrenAllowed })\n nextTick(() => controller.commit())\n return modelValue.value\n}\n/** @deprecated Use `addChild(parentKey, data, childrenAllowed)` on the handle. */\nconst addChildToId = (targetId: ListEditorKey, data: TItem, childrenAllowed: boolean) => {\n childrenExpandedKeys.value.add(targetId)\n // Legacy \"prepend as first child\" semantic (canonical `addChild` appends to the end).\n controller.addItem(data, { parentId: targetId, asFirstChild: true, childrenAllowed })\n nextTick(() => controller.commit())\n return modelValue.value\n}\n/** @deprecated Use `deleteItem(id)` on the handle. */\nconst removeById = (id: ListEditorKey) => {\n controller.deleteItem(id)\n editingKeys.value.delete(id)\n editingSnapshots.value.delete(id)\n detailExpandedKeys.value.delete(id)\n childrenExpandedKeys.value.delete(id)\n nextTick(() => controller.commit())\n}\n/** @deprecated Use `updateItem(id, data)` on the handle. */\nconst updateData = (\n id: ListEditorKey,\n data: TItem,\n _children: unknown = null,\n _position: unknown = null,\n _markUnsaved: unknown = null\n) => {\n controller.updateItem(id, data)\n nextTick(() => controller.commit())\n}\n\n// Total distinct unconfirmed changes (added/edited/moved/reparented rows + deferred deletions) — a\n// delete counts even though its row is gone. Drives the unsaved-section label + the exposed handle.\nconst unsavedCount = controller.unsavedCount\n\n// Re-baseline as saved, clear the moved set, close open edits. Legacy name kept.\nconst clearUnsavedState = () => {\n controller.commit()\n movedKeys.value = new Set()\n clearEditing()\n}\n\n// Registers this editor as a named unsaved-changes section when a label is passed.\nuseUnsavedSection(() =>\n props.unsavedSectionLabel && !props.disableUnsaved\n ? { label: props.unsavedSectionLabel, dirty: controller.hasUnsaved.value }\n : []\n)\n\n// Expose the controller handle plus legacy aliases and reorder/expand controls.\n// Entries after the spread override controller methods where the historic name or\n// return shape differs (e.g. `viewItems` is this component's expand-aware list).\n// Reveal for a blocked save: open invalid rows AND expand the offender's ancestor chain so a\n// collapsed nested row becomes visible. Shared by the exposed validateAll + scope reveal-on-touch.\nconst revealNestedInvalid = (): boolean =>\n validateAllAndReveal(controller, (key) => {\n let ancestor = controller.findNode(key).parent\n while (ancestor) {\n childrenExpandedKeys.value.add(keyOf(ancestor.data))\n ancestor = controller.findNode(keyOf(ancestor.data)).parent\n }\n const vi = controller.viewItems.value.find((v) => v.key === key)\n if (vi) beginEdit(vi)\n })\n\n// Auto-bridge the editor's aggregate validity into the consumer's `validation-scope` collector so a\n// plain `$invalid` save gate blocks (and reveals) a collapsed invalid row. No-op without the prop.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss -- construction-time opt-in, read once\nuseListEditorScopeValidity({\n hasErrors: controller.hasErrors,\n validationScope: props.validationScope,\n validateProvided: props.validate !== undefined,\n reveal: revealNestedInvalid,\n})\n\ndefineExpose<\n NestedListEditorHandle<TItem> & {\n addAfterId: typeof addAfterId\n addChildToId: typeof addChildToId\n removeById: typeof removeById\n updateData: typeof updateData\n resetDirtyBaseline: () => void\n hasUnsavedChanges: typeof controller.hasUnsaved\n unsavedCount: typeof unsavedCount\n clearUnsavedState: () => void\n enterReorderMode: () => void\n cancelReorderMode: () => void\n applyReorder: () => Promise<void>\n expand: (id: ListEditorKey) => void\n collapse: (id: ListEditorKey) => void\n toggleExpand: (id: ListEditorKey) => void\n expandDetail: (id: ListEditorKey) => void\n collapseDetail: (id: ListEditorKey) => void\n }\n>({\n ...controller,\n validateAll: revealNestedInvalid,\n // Legacy aliases (pre-v2 ASortableNested API).\n addAfterId,\n addChildToId,\n removeById,\n updateData,\n resetDirtyBaseline,\n hasUnsavedChanges: controller.hasUnsaved,\n unsavedCount,\n clearUnsavedState,\n // Expand-aware flattened view (controller's own `viewItems` ignores collapse state).\n viewItems: flatViewItems,\n enterReorderMode,\n cancelReorderMode,\n applyReorder,\n expand: (id: ListEditorKey) => childrenExpandedKeys.value.add(id),\n collapse: (id: ListEditorKey) => childrenExpandedKeys.value.delete(id),\n toggleExpand: (id: ListEditorKey) => {\n if (childrenExpandedKeys.value.has(id)) childrenExpandedKeys.value.delete(id)\n else childrenExpandedKeys.value.add(id)\n },\n expandDetail: (id: ListEditorKey) => detailExpandedKeys.value.add(id),\n collapseDetail: (id: ListEditorKey) => detailExpandedKeys.value.delete(id),\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"a-nested-list-editor\"\n :class=\"[\n `a-nested-list-editor--mode-${mode}`,\n {\n 'a-nested-list-editor--readonly': readonly,\n 'a-nested-list-editor--disabled': disabled,\n 'a-nested-list-editor--touch': isTouch,\n 'a-nested-list-editor--drag-enabled': dragEnabled,\n },\n ]\"\n >\n <div class=\"a-le-card\">\n <div\n v-if=\"headerVisible\"\n class=\"a-le-header\"\n >\n <slot\n name=\"header\"\n :title=\"title\"\n v-bind=\"reorderToggleSlotProps\"\n >\n <h3\n v-if=\"title\"\n class=\"a-le-title-heading\"\n >\n {{ title }}\n </h3>\n <div class=\"a-le-header-actions\">\n <template v-if=\"reorderMode\">\n <!-- Reorder-mode header: pending count + Cancel/Apply, sitting where\n the \"Reorder\" button lives in view mode (not a bottom toolbar). -->\n <slot\n name=\"reorder-toolbar\"\n v-bind=\"toolbarSlotProps\"\n >\n <LeStatus\n :class=\"{ 'a-le-toolbar-status--pending': nestedPendingChanges }\"\n :has-pending-changes=\"nestedPendingChanges\"\n :pending-count=\"nestedPendingCount\"\n :error=\"applyError\"\n />\n <VBtn\n variant=\"text\"\n size=\"small\"\n :disabled=\"applying\"\n @click=\"cancelReorderMode\"\n >\n {{ t('common.sortable.reorderCancel') }}\n </VBtn>\n <VBtn\n color=\"primary\"\n variant=\"flat\"\n size=\"small\"\n prepend-icon=\"mdi-check\"\n :loading=\"applying\"\n :disabled=\"applying || !nestedPendingChanges\"\n @click=\"applyReorder\"\n >\n {{ t('common.sortable.reorderApply') }}\n </VBtn>\n </slot>\n </template>\n <template v-else>\n <VBtn\n v-if=\"expandAllVisible && compactReorderButton\"\n variant=\"tonal\"\n color=\"primary\"\n icon\n size=\"x-small\"\n @click=\"toggleExpandAll\"\n >\n <VIcon\n :icon=\"allExpanded ? 'mdi-unfold-less-horizontal' : 'mdi-unfold-more-horizontal'\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"allExpanded ? t('common.sortable.collapseAll') : t('common.sortable.expandAll')\"\n />\n </VBtn>\n <VBtn\n v-else-if=\"expandAllVisible\"\n variant=\"tonal\"\n color=\"primary\"\n size=\"small\"\n :prepend-icon=\"allExpanded ? 'mdi-unfold-less-horizontal' : 'mdi-unfold-more-horizontal'\"\n @click=\"toggleExpandAll\"\n >\n {{ allExpanded ? t('common.sortable.collapseAll') : t('common.sortable.expandAll') }}\n </VBtn>\n <slot\n v-if=\"reorderToggleVisible\"\n name=\"reorder-toggle\"\n v-bind=\"reorderToggleSlotProps\"\n >\n <VBtn\n v-if=\"compactReorderButton\"\n variant=\"tonal\"\n color=\"primary\"\n icon\n size=\"x-small\"\n :disabled=\"!canEnterReorder\"\n @click=\"enterReorderMode\"\n >\n <VIcon\n icon=\"mdi-sort\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.reorder')\"\n />\n </VBtn>\n <VBtn\n v-else\n variant=\"tonal\"\n color=\"primary\"\n prepend-icon=\"mdi-sort\"\n size=\"small\"\n :disabled=\"!canEnterReorder\"\n @click=\"enterReorderMode\"\n >\n {{ t('common.sortable.reorder') }}\n </VBtn>\n </slot>\n </template>\n </div>\n </slot>\n </div>\n\n <div\n v-if=\"loading\"\n class=\"a-le-state a-le-state--loading\"\n >\n <VProgressCircular\n indeterminate\n color=\"primary\"\n size=\"32\"\n />\n </div>\n\n <div\n v-else-if=\"error\"\n class=\"a-le-state a-le-state--error\"\n >\n <VAlert\n type=\"error\"\n variant=\"tonal\"\n density=\"compact\"\n border=\"start\"\n >\n {{ error }}\n </VAlert>\n </div>\n\n <div\n v-else-if=\"isEmpty\"\n class=\"a-le-state a-le-state--empty\"\n >\n <slot\n name=\"empty\"\n :readonly=\"readonly\"\n :disabled=\"disabled\"\n :actions=\"{ add: onAddClick }\"\n >\n <LeEmptyState\n :title=\"emptyTitleResolved\"\n :add-label=\"addLabelResolved\"\n :can-add=\"canAdd\"\n @add=\"onAddClick\"\n />\n </slot>\n </div>\n\n <div\n v-else\n :key=\"forceRerender\"\n ref=\"rowsContainer\"\n class=\"a-nested-list-editor__rows\"\n :class=\"{ 'a-nested-list-editor__rows--dragging': dragState !== null }\"\n >\n <div\n :class=\"[GROUP_CLASS, 'a-nested-list-editor__group--root']\"\n data-parent-id=\"\"\n >\n <LeNestedRow\n v-for=\"vi in rootViewItems\"\n :key=\"String(vi.key)\"\n :vi=\"vi\"\n :view-items=\"viewItemsDecorated\"\n :drag-state=\"dragState\"\n :context=\"rowContext\"\n :callbacks=\"rowCallbacks\"\n >\n <template #item=\"slotScope\">\n <slot\n name=\"item\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-compact=\"slotScope\">\n <slot\n name=\"item-compact\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-readonly=\"slotScope\">\n <slot\n name=\"item-readonly\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-status=\"slotScope\">\n <slot\n name=\"item-status\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-footer=\"slotScope\">\n <slot\n name=\"item-footer\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-actions=\"slotScope\">\n <slot\n name=\"item-actions\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #before-item=\"slotScope\">\n <slot\n name=\"before-item\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #after-item=\"slotScope\">\n <slot\n name=\"after-item\"\n v-bind=\"slotScope\"\n />\n </template>\n </LeNestedRow>\n </div>\n\n <!-- Drop indicator overlay (valid drops only): horizontal line at a row\n edge (depth in `left`) + an optional vertical connector rail to the\n row whose level the insert matches. -->\n <template v-if=\"overlayVisual !== null\">\n <div\n v-if=\"overlayVisual.connector !== null\"\n class=\"a-nested-list-editor__drop-connector\"\n :style=\"{\n left: `${overlayVisual.connector.left}px`,\n top: `${overlayVisual.connector.top}px`,\n height: `${overlayVisual.connector.height}px`,\n }\"\n />\n <div\n class=\"a-nested-list-editor__drop-line\"\n :style=\"{\n top: `${overlayVisual.line.top}px`,\n left: `${overlayVisual.line.left}px`,\n right: `${overlayVisual.line.right}px`,\n }\"\n >\n <span class=\"a-nested-list-editor__drop-line-dot\" />\n </div>\n </template>\n </div>\n\n <slot\n v-if=\"canAdd && !loading && !error && !isEmpty\"\n name=\"add-button\"\n :readonly=\"readonly\"\n :disabled=\"disabled\"\n :props=\"{ onClick: onAddClick }\"\n :actions=\"{ add: onAddClick }\"\n >\n <button\n type=\"button\"\n class=\"a-le-row-add\"\n @click=\"onAddClick\"\n >\n <VIcon\n icon=\"mdi-plus\"\n size=\"18\"\n />\n <span>{{ addLabelResolved }}</span>\n </button>\n </slot>\n </div>\n\n <LeMoveToPositionDialog\n v-model=\"moveToPositionDialogOpen\"\n :total=\"moveToPositionContext?.total ?? 0\"\n :current-index=\"moveToPositionContext?.currentIndex ?? 0\"\n :item-label=\"moveToPositionLabel\"\n @confirm=\"onMoveToPositionConfirm\"\n />\n\n <LeChangeParentDialog\n v-model=\"changeParentDialogOpen\"\n :tree=\"modelValue\"\n :source-key=\"changeParentTarget?.key ?? null\"\n :key-field=\"keyFieldName\"\n :max-depth=\"maxDepth\"\n :resolve-label=\"resolveCompactText\"\n :calculate-subtree-depth=\"controller.calculateSubtreeDepth\"\n @confirm=\"onChangeParentConfirm\"\n />\n\n <LeDeleteDialog\n v-model=\"deleteDialog\"\n :title=\"deleteConfirmTitleResolved\"\n :text=\"deleteConfirmTextResolved\"\n :confirm-label=\"t('common.sortable.delete')\"\n :cancel-label=\"t('common.button.cancel')\"\n :error=\"deleteError\"\n :in-flight=\"deleteInFlight\"\n @confirm=\"onDeleteDialogConfirm\"\n @cancel=\"onDeleteDialogCancel\"\n />\n\n <div\n class=\"a-le-sr-only\"\n aria-live=\"polite\"\n role=\"status\"\n >\n {{\n keyboardNav.grabbedKey.value !== null\n ? t('common.sortable.keyboardGrab.status', {\n n: keyboardNav.grabbedIndex.value + 1,\n total: keyboardNav.totalCount.value,\n })\n : ''\n }}\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n@use './styles/tokens';\n@use './styles/shared';\n\n// Nested-variant rules — depth-aware padding, tree toggle caret, children\n// groups, drop indicator overlay, drag clone.\n.a-nested-list-editor {\n // Depth-aware left padding — caret column + indent per depth level.\n .a-le-row-header {\n padding: var(--le-row-pad-y) 12px var(--le-row-pad-y) calc(16px + var(--nested-depth, 0) * var(--le-indent));\n }\n\n // Row wrappers + inter-row group layout.\n &__rows {\n position: relative;\n display: flex;\n flex-direction: column;\n }\n\n &__group {\n display: flex;\n flex-direction: column;\n }\n\n // Re-apply the `font` shorthand (shared class uses longhand for flat variants)\n // so the nested variant keeps its explicit line-height reset.\n .a-le-toolbar-status {\n font: 500 13px/1 var(--v-font-body, inherit);\n }\n}\n\n.a-le-row-wrapper {\n display: flex;\n flex-direction: column;\n}\n\n.a-nested-list-editor {\n // Root rows get a bolder title weight to anchor the hierarchy without header rows.\n .a-le-row:not(.a-le-row--child) .a-le-title {\n font-weight: 600;\n }\n\n // Reorder mode — flat-variant padding offset by the row's indent level.\n .a-le-row--reorder .a-le-row-header {\n padding-left: calc(12px + var(--nested-depth, 0) * var(--le-indent));\n padding-right: 8px;\n gap: 8px;\n }\n\n // Active (editing / readonly-expanded) tree-toggle picks up the primary tint\n // so the header reads \"active\" together with the title color.\n .a-le-row--editing &__tree-toggle,\n .a-le-row--expanded &__tree-toggle {\n color: var(--le-primary);\n }\n\n // Children wrapper — depth conveyed by the row's padding-left indent alone.\n &__children {\n position: relative;\n }\n\n // Triangle-caret toggle — circular text-style button (24×24), transparent\n // with a subtle tinted circle on hover.\n &__tree-toggle {\n width: 24px;\n height: 24px;\n padding: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n border-radius: 50%;\n cursor: pointer;\n pointer-events: auto;\n color: rgb(0 0 0 / 54%);\n z-index: 2;\n flex-shrink: 0;\n transition:\n background 0.15s,\n color 0.15s;\n\n &:hover {\n background: rgb(0 0 0 / 5%);\n color: var(--le-on-surface);\n }\n\n &:focus-visible {\n outline: none;\n background: rgb(0 0 0 / 8%);\n }\n }\n\n // Reserve caret column width on leaf rows so titles align across siblings.\n &__tree-toggle--spacer,\n &__tree-toggle--empty {\n width: 24px;\n height: 24px;\n background: transparent;\n visibility: hidden;\n pointer-events: none;\n }\n\n // Pure CSS right-pointing triangle; rotates to down when the row is open.\n &__tree-toggle-caret {\n width: 0;\n height: 0;\n border-left: 5px solid currentcolor;\n border-top: 4px solid transparent;\n border-bottom: 4px solid transparent;\n border-right: 0;\n transition: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1);\n transform: translateX(1px); // optical centering — triangle leans left\n }\n\n &__tree-toggle--open &__tree-toggle-caret {\n transform: translate(0, 1px) rotate(90deg);\n }\n\n // Dragged subtree (source + descendants) dimmed and non-hittable. `!important`\n // on display beats SortableJS's inline `display: none` under `forceFallback`.\n .a-le-row-wrapper.a-le-row--chosen,\n .a-le-row-wrapper.a-le-row-wrapper--drop-disabled {\n display: flex !important;\n opacity: 0.4 !important;\n pointer-events: none !important;\n }\n\n // Hide SortableJS's placeholder — our overlay is the sole landing indicator.\n .a-le-row--ghost {\n display: none !important;\n }\n\n // No floating clone — the line overlay carries all the \"where it lands\" info.\n // Wrapper selector beats the `.row-wrapper--drop-disabled` rule the clone inherits.\n .a-le-row-wrapper.a-le-row--drag {\n display: none !important;\n }\n\n // Drop indicator line — 2px primary stroke with an 8px left terminal dot,\n // absolute-positioned inside `__rows`.\n &__drop-line {\n position: absolute;\n height: 2px;\n margin-top: -1px;\n background: var(--le-primary);\n pointer-events: none;\n z-index: 4;\n border-radius: 1px;\n }\n\n &__drop-line-dot {\n position: absolute;\n left: -4px;\n top: 50%;\n width: 8px;\n height: 8px;\n background: var(--le-primary);\n border-radius: 50%;\n transform: translateY(-50%);\n }\n\n // Connector rail — vertical line linking the drop line up to the level-match row.\n &__drop-connector {\n position: absolute;\n width: 2px;\n margin-left: -1px;\n background: var(--le-primary);\n opacity: 0.5;\n pointer-events: none;\n z-index: 3;\n }\n\n // While dragging, dim the add-button so focus stays on the drag target.\n &__rows--dragging .a-le-row-add {\n opacity: 0.4;\n }\n\n // \"+N\" children indicator — hidden in the normal DOM, visible only inside the\n // SortableJS drag clone so the user sees the whole branch will follow.\n &__drag-count {\n display: none;\n align-items: center;\n gap: 4px;\n margin-left: 8px;\n padding: 2px 8px;\n font: 500 11px/1 var(--v-font-body, inherit);\n letter-spacing: 0.02em;\n color: var(--le-primary);\n background: var(--le-primary-container);\n border-radius: var(--le-radius-pill);\n flex-shrink: 0;\n }\n\n .a-le-row--drag &__drag-count {\n display: inline-flex;\n }\n}\n\n// Desktop — depth-aware padding-left aligns the inline form with the title column:\n// 16 (pad) + 24 (caret) + 10 (gap) = 50, plus depth*indent.\n@container le-shell (min-width: 769px) {\n .a-nested-list-editor {\n .a-le-row--editing .a-le-row-body,\n .a-le-row--expanded .a-le-row-body,\n .a-le-row--editing .a-le-row-footer,\n .a-le-row--expanded .a-le-row-footer {\n padding-left: calc(50px + var(--nested-depth, 0) * var(--le-indent));\n padding-right: 16px;\n }\n }\n}\n\n// Narrow / mobile — tighter indent so deep branches fit, taller touch rows,\n// always-visible actions, status badge dropped to make room for the title.\n/* stylelint-disable selector-max-compound-selectors */\n@container le-shell (max-width: 768px) {\n .a-nested-list-editor {\n --le-indent: 18px;\n --le-row-min-height: 48px;\n --le-row-pad-y: 10px;\n\n .a-le-row:not(.a-le-row--editing) .a-le-status {\n display: none;\n }\n\n .a-le-row .a-le-action--edit,\n .a-le-row .a-le-action--delete,\n .a-le-row .a-le-action--menu,\n .a-le-row .a-le-action--up,\n .a-le-row .a-le-action--down,\n .a-le-row .a-le-action--add-child {\n opacity: 1;\n }\n }\n}\n/* stylelint-enable selector-max-compound-selectors */\n</style>\n","<script setup lang=\"ts\" generic=\"TItem extends Record<string, any>\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, useSlots, useTemplateRef, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport { useContainerWidth } from '@/labs/listEditor/composables/useContainerWidth'\nimport { useIsTouchDevice } from '@/labs/listEditor/composables/useIsTouchDevice'\nimport { useKeyboardNav } from '@/labs/listEditor/composables/useKeyboardNav'\nimport { useSortable } from '@vueuse/integrations/useSortable'\nimport { type NestedViewItem } from '@/labs/listEditor/composables/useNestedListEditor'\nimport {\n useNestedListEditorController,\n type GetKey,\n type ListEditorValidationResult,\n type NestedListEditorHandle,\n type PositionOption,\n type PositionStrategy,\n} from '@/labs/listEditor/composables/useNestedListEditorController'\nimport {\n computeInstruction,\n type ExecutableInstruction,\n type Instruction,\n} from '@/labs/listEditor/composables/useDragInstruction'\nimport {\n provideListEditorStateScope,\n useNestedListEditorStateEntry,\n type NestedListEditorStateBindings,\n} from '@/labs/listEditor/composables/useListEditorStateScope'\nimport { resolveCompactText as resolveCompactTextUtil } from '@/labs/listEditor/composables/resolveCompactText'\nimport { useUnsavedSection } from '@/labs/unsavedGuard/useUnsavedSection'\nimport { useDeleteDialog } from '@/labs/listEditor/composables/useDeleteDialog'\nimport { useInlineEditing } from '@/labs/listEditor/composables/useInlineEditing'\nimport { validateAllAndReveal } from '@/labs/listEditor/utils/revealInvalidRows'\nimport { useListEditorScopeValidity } from '@/labs/listEditor/composables/useListEditorScopeValidity'\nimport { useReorderMode } from '@/labs/listEditor/composables/useReorderMode'\nimport { cloneDeep } from '@/utils/common'\nimport type {\n ListEditorKey,\n ListEditorValidationScope,\n ListEditorValidationState,\n NestedPositionHint,\n NestedTree,\n NestedTreeNode,\n} from '@/labs/listEditor/types/listEditorTypes'\nimport { useAlerts } from '@/composables/system/alerts'\nimport { stringToInt } from '@/utils/string'\nimport LeNestedRow from '@/labs/listEditor/internal/LeNestedRow.vue'\nimport LeDeleteDialog from '@/labs/listEditor/internal/LeDeleteDialog.vue'\nimport LeMoveToPositionDialog from '@/labs/listEditor/internal/LeMoveToPositionDialog.vue'\nimport LeChangeParentDialog from '@/labs/listEditor/internal/LeChangeParentDialog.vue'\nimport LeEmptyState from '@/labs/listEditor/internal/LeEmptyState.vue'\nimport LeStatus from '@/labs/listEditor/internal/LeStatus.vue'\nimport { DRAG_GHOST_CLASS, DRAG_CHOSEN_CLASS, DRAG_CLASS } from '@/labs/listEditor/internal/constants'\n\nexport interface DecoratedNestedViewItem<T> extends NestedViewItem<T> {\n editing: boolean\n expanded: boolean\n childrenExpanded: boolean\n loading: boolean\n dirty: boolean\n moved: boolean\n unsaved: boolean\n}\n\nexport interface DragState {\n sourceKey: ListEditorKey\n sourceSubtreeDepth: number\n instruction: Instruction | null\n}\n\nexport type ReorderMode = 'view' | 'reorder'\n\n// Hoisted for vite-plugin-dts d.ts rollup.\nexport interface RowActions<TItem> {\n edit: () => void\n save: () => Promise<void> | void\n cancel: () => void\n close: () => void\n delete: () => Promise<void>\n addAfter: () => void\n addChild: () => void\n toggleExpand: () => void\n toggleDetail: () => void\n moveUp: () => void\n moveDown: () => void\n moveTop: () => void\n moveBottom: () => void\n indent: () => void\n outdent: () => void\n update: (data: TItem) => void\n}\nexport interface RowSlotProps<TItem extends Record<string, any>> {\n item: DecoratedNestedViewItem<TItem> & { validationState: ListEditorValidationState }\n raw: TItem\n index: number\n key: ListEditorKey\n depth: number\n parent: TItem | null\n parentKey: ListEditorKey | null\n childrenCount: number\n hasChildren: boolean\n childrenAllowed: boolean\n firstInParent: boolean\n lastInParent: boolean\n readonly: boolean\n disabled: boolean\n expanded: boolean\n childrenExpanded: boolean\n editing: boolean\n dirty: boolean\n unsaved: boolean\n moved: boolean\n reorderMode: boolean\n canMoveUp: boolean\n canMoveDown: boolean\n canIndent: boolean\n canOutdent: boolean\n canAddChild: boolean\n touch: boolean\n /**\n * Row-scoped `state-key` prefix. Thread it down to any list editor rendered in this slot and set\n * its `:state-key=\"`${stateKeyPrefix}:someList`\"` — that editor's controller then lives in THIS\n * editor's scope and survives the row collapsing (which unmounts the whole slot subtree). Entries\n * under this prefix are disposed when the row is removed.\n */\n stateKeyPrefix: string\n actions: RowActions<TItem>\n}\nexport interface ToolbarSlotProps {\n applying: boolean\n hasPendingChanges: boolean\n movedCount: number\n error: string | null\n actions: { apply: () => Promise<void>; cancel: () => void }\n}\nexport interface ReorderToggleSlotProps {\n mode: ReorderMode\n disabled: boolean\n hasPendingChanges: boolean\n actions: { enterReorderMode: () => void; exitReorderMode: () => void }\n}\nexport interface HeaderSlotProps extends ReorderToggleSlotProps {\n title: string | null\n}\nexport interface EmptySlotProps {\n readonly: boolean\n disabled: boolean\n actions: { add: () => void }\n}\nexport interface AddButtonSlotProps {\n readonly: boolean\n disabled: boolean\n props: { onClick: () => void }\n actions: { add: () => void }\n}\n\nexport interface Props<TItem extends Record<string, any>> {\n maxDepth: number\n\n /**\n * New-row factory; if set, add/add-after/add-inside insert through the\n * controller (no `@add` handler needed). Optional for read-only trees.\n */\n factory?: () => TItem\n /**\n * Stable row identity. Default `'id'`. Never point at the position field.\n * Typed `string` (not `keyof TItem`): a bare keyof compiles to a Boolean-only\n * runtime type that silently coerces `get-key=\"id\"` to `true`.\n */\n getKey?: string | ((item: TItem) => ListEditorKey)\n /**\n * Managed order field. Default `'position'`; `false` opts out. Typed\n * `[String, Boolean, Object]` with `string` before `false`: a bare keyof\n * compiles Boolean-only, and `false`-first would coerce `position=\"position\"`\n * (value == prop name) to `true`. Object form folds in `{ field, multiplier }`.\n */\n position?: string | false | { field: string; multiplier?: number; strategy?: PositionStrategy }\n /** Parent-key field written onto reparented rows. Default `'parent'`. */\n parentField?: string\n /**\n * Extra fields dropped from the dirty content-hash (position + parent always\n * dropped), e.g. a child collection tracked by a separate editor.\n */\n dirtyExclude?: string[]\n /** Per-row validity — `true` (or `{ valid: true }`) = VALID. Drives the red rail + save guard. */\n validate?: (item: TItem) => ListEditorValidationResult\n /**\n * Vuelidate `$scope` (the same one the consumer's row forms / save-gate collector use). When set,\n * the editor registers its aggregate validity under it, so a plain `v$.$invalid` save gate blocks\n * AND reveals a collapsed invalid row — no `validateAll()` call needed in the save flow. Omit to\n * keep legacy behavior (gate the save on the exposed `validateAll()` yourself); `false` opts out.\n */\n validationScope?: ListEditorValidationScope | false\n /**\n * Opt-in lifted controller (`useNestedListEditorController()`) so state\n * survives unmount/remount; omitted = internal controller. Same handle\n * reachable via `useTemplateRef`.\n */\n editor?: NestedListEditorHandle<TItem>\n /**\n * Persist this editor's controller in the nearest ANCESTOR editor's row-state scope under this\n * key, instead of owning it. Use it for an editor rendered in another editor's `#item` slot (that\n * slot is behind a `v-if`, so collapsing the row unmounts this editor and a component-owned\n * controller would re-baseline the already-edited tree on re-expand). Derive the key from the row\n * slot's `stateKeyPrefix`. Options stay declared here, on this tag. Ignored when `:editor` is\n * passed, and a no-op without an ancestor scope.\n */\n stateKey?: string\n\n readonly?: boolean\n disabled?: boolean\n loading?: boolean\n error?: string | null\n\n /** Heading above the list. A ready translated string, like every text prop here. */\n title?: string | null\n\n /**\n * Field shown in a collapsed row when no `#item-compact` slot is given. A dotted path reads\n * through nested objects (`texts.title`); a path that runs into nothing renders empty.\n */\n compactField?: string | null\n statusField?: string | null\n\n showAddButton?: boolean\n showAddChildButton?: boolean\n showDeleteButton?: boolean\n showEditButton?: boolean\n showAddAfterAction?: boolean\n showMoveToPosition?: boolean\n showChangeParent?: boolean\n showExpandToggle?: boolean\n\n /** Label of the add button. A ready translated string; omit it for the library's own wording. */\n addLabel?: string | null\n /** Heading of the empty state. A ready translated string. */\n emptyTitle?: string | null\n\n disableRowClick?: boolean\n disableDeleteConfirm?: boolean\n /**\n * Disable unsaved-state tracking — no dirty markers, never reads as unsaved. For a list that is a\n * VIEW of data owned elsewhere (a display cache rebuilt from a parent's field, say): nothing in\n * such a component can ever clear an amber row, so it must not raise one. Mirrors `AListEditor`.\n */\n disableUnsaved?: boolean\n deleteConfirmTitle?: string | null\n deleteConfirmText?: string | null\n /**\n * How a row delete persists. `deferred` (default): the row disappears but the deletion counts as an\n * unconfirmed change until save (revertible until then, incl. via reorder Cancel). `immediate`: the\n * consumer deletes on the backend — the dialog says it is irreversible and it does NOT read as unsaved.\n */\n deleteMode?: 'immediate' | 'deferred'\n\n closeVariant?: 'auto' | 'icon' | 'labeled'\n\n loadingKeys?: Set<ListEditorKey> | null\n\n showReorderToggle?: boolean\n disableReorder?: boolean\n disableDrag?: boolean\n\n onDeleteConfirm?: (item: TItem) => Promise<boolean> | boolean\n onDelete?: (item: TItem) => Promise<void> | void\n onItemSave?: (item: TItem) => Promise<void> | void\n onReorderApply?: (tree: NestedTree<TItem>) => Promise<void> | void\n\n /** Registers this editor as a named unsaved-changes section under the given (translated) label. */\n unsavedSectionLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props<TItem>>(), {\n unsavedSectionLabel: undefined,\n factory: undefined,\n getKey: undefined,\n position: undefined,\n parentField: 'parent',\n dirtyExclude: undefined,\n validate: undefined,\n validationScope: undefined,\n editor: undefined,\n stateKey: undefined,\n readonly: false,\n disabled: false,\n loading: false,\n error: null,\n title: null,\n compactField: null,\n statusField: null,\n showAddButton: true,\n showAddChildButton: true,\n showDeleteButton: true,\n showEditButton: true,\n showAddAfterAction: false,\n showMoveToPosition: false,\n showChangeParent: false,\n showExpandToggle: true,\n addLabel: null,\n emptyTitle: null,\n disableRowClick: false,\n disableDeleteConfirm: false,\n disableUnsaved: false,\n deleteConfirmTitle: null,\n deleteConfirmText: null,\n deleteMode: 'deferred',\n closeVariant: 'auto',\n loadingKeys: null,\n showReorderToggle: true,\n disableReorder: false,\n disableDrag: false,\n onDeleteConfirm: undefined,\n onDelete: undefined,\n onItemSave: undefined,\n onReorderApply: undefined,\n})\n\nconst emit = defineEmits<{\n add: [hint: NestedPositionHint | undefined]\n 'add-child': [parent: NestedViewItem<TItem>]\n edit: [item: NestedViewItem<TItem>]\n deleted: [item: NestedViewItem<TItem>]\n close: [item: NestedViewItem<TItem>]\n 'item-saved': [item: NestedViewItem<TItem>]\n 'item-expand': [item: NestedViewItem<TItem>, expanded: boolean]\n 'reorder-start': []\n 'reorder-cancel': []\n 'reorder-applied': [tree: NestedTree<TItem>]\n 'reorder-apply-error': [error: unknown]\n 'reorder-end': []\n indent: [item: NestedViewItem<TItem>]\n outdent: [item: NestedViewItem<TItem>]\n}>()\n\nconst modelValue = defineModel<NestedTree<TItem>>({ required: true })\nconst mode = defineModel<ReorderMode>('mode', { default: 'view' })\n\ndefineSlots<{\n header?: (props: HeaderSlotProps) => unknown\n 'reorder-toggle'?: (props: ReorderToggleSlotProps) => unknown\n 'reorder-toolbar'?: (props: ToolbarSlotProps) => unknown\n empty?: (props: EmptySlotProps) => unknown\n 'add-button'?: (props: AddButtonSlotProps) => unknown\n item?: (props: RowSlotProps<TItem>) => unknown\n 'item-compact'?: (props: RowSlotProps<TItem>) => unknown\n 'item-readonly'?: (props: RowSlotProps<TItem>) => unknown\n 'item-status'?: (props: RowSlotProps<TItem>) => unknown\n 'item-footer'?: (props: RowSlotProps<TItem>) => unknown\n 'item-actions'?: (props: RowSlotProps<TItem>) => unknown\n 'before-item'?: (props: RowSlotProps<TItem>) => unknown\n 'after-item'?: (props: RowSlotProps<TItem>) => unknown\n}>()\n\nconst { t } = useI18n()\nconst slots = useSlots()\nconst { showWarningT } = useAlerts()\n\nconst rootEl = useTemplateRef<HTMLElement>('rootEl')\nconst { isNarrow } = useContainerWidth(rootEl)\n\nconst isTouch = useIsTouchDevice()\n\nconst effectiveCloseVariant = computed<'icon' | 'labeled'>(() => {\n if (props.closeVariant === 'icon') return 'icon'\n if (props.closeVariant === 'labeled') return 'labeled'\n return isNarrow.value ? 'icon' : 'labeled'\n})\n\nconst childrenExpandedKeys = ref<Set<ListEditorKey>>(new Set())\n// Readonly detail body visibility — independent of subtree expansion.\nconst detailExpandedKeys = ref<Set<ListEditorKey>>(new Set())\n\n// Local mirror of the controller's key resolution so walkers + the template key\n// rows the same way the controller tracks them. Construction-time config, read once.\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst getKeyOpt = props.getKey ?? 'id'\nconst keyFieldName = (typeof getKeyOpt === 'function' ? 'id' : getKeyOpt) as string\nconst keyOf = (data: TItem): ListEditorKey =>\n typeof getKeyOpt === 'function' ? getKeyOpt(data) : (data[getKeyOpt as keyof TItem] as ListEditorKey)\n\n// Managed position field (row position, move-to-position dialog, flatViewItems). Mirrors the controller.\nconst positionFieldName = ((): string => {\n const p = props.position\n if (p === undefined || p === false) return 'position'\n if (typeof p === 'object') return p.field\n return p\n})()\n\nconst initChildrenExpanded = (tree: NestedTree<TItem>) => {\n const walk = (nodes: NestedTreeNode<TItem>[]) => {\n for (const n of nodes) {\n if (n.children && n.children.length > 0) {\n childrenExpandedKeys.value.add(keyOf(n.data))\n walk(n.children)\n }\n }\n }\n walk(tree.children)\n}\n// eslint-disable-next-line vue/no-ref-object-reactivity-loss\ninitChildrenExpanded(modelValue.value)\n\n// State controller (v2). Undefined getKey/position/validate fall back to controller defaults\n// ('id' / 'position' / always-valid). Its `viewItems` ignores expand state, so we re-project an\n// expand-aware list below (flatViewItems).\nconst controllerOptions: NestedListEditorStateBindings<TItem> = {\n get: () => modelValue.value,\n set: (v) => (modelValue.value = v),\n factory: props.factory,\n getKey: props.getKey as GetKey<TItem> | undefined,\n position: props.position as PositionOption<TItem> | undefined,\n parentField: props.parentField,\n maxDepth: props.maxDepth,\n dirtyExclude: () => props.dirtyExclude ?? [],\n validate: props.validate,\n}\n// Precedence: an explicitly lifted `:editor` wins; then a `state-key`'d entry persisted in the\n// nearest ancestor editor's row-state scope (rebound to this instance on every remount); else this\n// component owns the controller — today's behaviour, unchanged.\nconst stateEntry = props.editor ? null : useNestedListEditorStateEntry<TItem>(props.stateKey, controllerOptions)\nconst controller = props.editor ?? stateEntry?.handle ?? useNestedListEditorController<TItem>(controllerOptions)\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\n// Row-state scope for THIS editor's `#item` slot subtree — see ASortableListEditor.\nconst rowStateScope = provideListEditorStateScope(stateEntry?.childScope ?? null)\n\n// FULL flattened list (collapsed-branch rows included) built directly off the\n// tree: the recursive renderer (LeNestedRow) filters by parentKey + childrenExpandedKeys,\n// so it needs every row present in viewItemsDecorated even when hidden.\nconst flatViewItems = computed<NestedViewItem<TItem>[]>(() => {\n const flat: NestedViewItem<TItem>[] = []\n let flatIndex = 0\n const maxDepth = props.maxDepth\n const walk = (nodes: NestedTreeNode<TItem>[], depth: number, parentNode: NestedTreeNode<TItem> | null) => {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]\n const key = keyOf(node.data)\n const childrenAllowed = node.children !== undefined\n const hasChildren = childrenAllowed && (node.children?.length ?? 0) > 0\n const childrenCount = node.children?.length ?? 0\n const remainingDepth = maxDepth - (depth + 1)\n flat.push({\n key,\n index: flatIndex++,\n raw: node.data,\n position: node.data[positionFieldName] as number | undefined,\n node,\n depth,\n parent: parentNode?.data ?? null,\n parentKey: parentNode ? keyOf(parentNode.data) : null,\n childrenCount,\n hasChildren,\n childrenAllowed,\n siblingIndex: i,\n siblingCount: nodes.length,\n firstInParent: i === 0,\n lastInParent: i === nodes.length - 1,\n canAddChild: childrenAllowed && remainingDepth > 0,\n canIndent: i > 0,\n canOutdent: depth > 0,\n })\n if (hasChildren) walk(node.children as NestedTreeNode<TItem>[], depth + 1, node)\n }\n }\n walk(modelValue.value.children, 0, null)\n return flat\n})\n\n// Drop the persisted controllers of rows that no longer exist. `post` so the removed row's subtree\n// has already unmounted (its editors released their entries) before we stop their effect scopes.\nwatch(\n () => flatViewItems.value.map((vi) => String(vi.key)).join('|'),\n () => rowStateScope.retainOwners(flatViewItems.value.map((vi) => vi.key)),\n { flush: 'post' }\n)\n\n// Snapshot for reorder-cancel restore only; dirty detection lives in the controller.\n// `movedKeys` is component view state driving the toolbar \"N pending\" counter,\n// cleared on every mode transition by `useReorderMode`.\nconst snapshot = shallowRef<NestedTree<TItem> | null>(null)\nconst movedKeys = ref<Set<ListEditorKey>>(new Set())\n// Deletes made during the current reorder session, split by mode — deferred ones count in the toolbar\n// and revert on Cancel; immediate (backend) ones are re-applied on Cancel so they stay gone (else the\n// snapshot-restore would resurrect a server-deleted row). Mirrors ASortableListEditor.\nconst sessionDeferredDeletes = ref<Set<ListEditorKey>>(new Set())\nconst sessionImmediateDeletes = ref<Set<ListEditorKey>>(new Set())\n\n// Re-baseline current tree as saved. Legacy name kept; maps onto `controller.commit()`.\nconst resetDirtyBaseline = () => {\n controller.commit()\n movedKeys.value = new Set()\n}\n// Mark row + every descendant moved — a moved parent visually carries its subtree.\nconst markMoved = (key: ListEditorKey) => {\n const { node } = controller.findNode(key)\n if (!node) {\n movedKeys.value.add(key)\n return\n }\n const collect = (n: NestedTreeNode<TItem>) => {\n movedKeys.value.add(keyOf(n.data))\n if (n.children) n.children.forEach(collect)\n }\n collect(node)\n}\n\nconst decoratorCache = new Map<ListEditorKey, DecoratedNestedViewItem<TItem>>()\nconst viewItemsDecorated = computed<DecoratedNestedViewItem<TItem>[]>(() => {\n const next: DecoratedNestedViewItem<TItem>[] = []\n const liveKeys = new Set<ListEditorKey>()\n for (const vi of flatViewItems.value) {\n liveKeys.add(vi.key)\n const editing = editingKeys.value.has(vi.key)\n const expanded = detailExpandedKeys.value.has(vi.key)\n const childrenExpanded = childrenExpandedKeys.value.has(vi.key)\n const loading = props.loadingKeys?.has(vi.key) ?? false\n const moved = movedKeys.value.has(vi.key)\n // Amber = controller dirty OR reorder-session moved. readonly suppresses it\n // (read-only views can't be unsaved; also dodges a mount-before-load baseline). (QA 85050 sweep)\n const dirty = props.readonly ? false : controller.isUnsaved(vi.key)\n // `disableUnsaved` suppresses only the amber marker — the validation rail still shows, since\n // hiding dirty state should not hide a real error.\n const unsaved = props.disableUnsaved || props.readonly ? false : dirty || moved\n const cached = decoratorCache.get(vi.key)\n if (\n cached &&\n cached.raw === vi.raw &&\n cached.index === vi.index &&\n cached.position === vi.position &&\n cached.depth === vi.depth &&\n cached.parentKey === vi.parentKey &&\n cached.childrenCount === vi.childrenCount &&\n cached.firstInParent === vi.firstInParent &&\n cached.lastInParent === vi.lastInParent &&\n cached.editing === editing &&\n cached.expanded === expanded &&\n cached.childrenExpanded === childrenExpanded &&\n cached.loading === loading &&\n cached.dirty === dirty &&\n cached.moved === moved &&\n cached.unsaved === unsaved\n ) {\n next.push(cached)\n continue\n }\n const decorated: DecoratedNestedViewItem<TItem> = {\n ...vi,\n editing,\n expanded,\n childrenExpanded,\n loading,\n dirty,\n moved,\n unsaved,\n }\n decoratorCache.set(vi.key, decorated)\n next.push(decorated)\n }\n for (const key of decoratorCache.keys()) {\n if (!liveKeys.has(key)) decoratorCache.delete(key)\n }\n return next\n})\n\nconst isEmpty = computed(() => modelValue.value.children.length === 0)\nconst totalItemCount = computed(() => flatViewItems.value.length)\n\nconst rowsContainer = useTemplateRef<HTMLElement>('rowsContainer')\n\nconst canInteract = computed(() => !props.readonly && !props.disabled && !props.loading)\nconst canEnterReorder = computed(() => canInteract.value && !props.disableReorder && totalItemCount.value > 1)\n\nconst isInlineEdit = computed(() => !!(slots as Record<string, unknown>).item)\nconst hasReadonlyDetail = computed(() => !!(slots as Record<string, unknown>)['item-readonly'])\nconst showInlineSaveFooter = computed(() => !!props.onItemSave)\n\nconst { editingKeys, editingSnapshots, clearEditing, beginEdit, cancelEdit, commitEdit, closeEdit, requestAutoOpen } =\n useInlineEditing<TItem, NestedViewItem<TItem>>({\n rowsContainer,\n rowSelector: '.a-le-row-wrapper',\n isInlineEdit,\n // markDirty=false: restoring the pre-edit snapshot on cancel must not flag the\n // node dirty (it would otherwise be resent by partial-multi saves).\n restoreSnapshot: (key, data) => controller.updateItem(key, data, false),\n watchKeys: () => {\n const keys: ListEditorKey[] = []\n const walk = (nodes: NestedTreeNode<TItem>[]) => {\n for (const n of nodes) {\n keys.push(keyOf(n.data))\n if (n.children && n.children.length) walk(n.children)\n }\n }\n walk(modelValue.value.children)\n return keys\n },\n findEntry: (key) => {\n const { node } = controller.findNode(key)\n return node ? { data: node.data } : null\n },\n afterAutoOpen: (key) => {\n const { parent } = controller.findNode(key)\n if (parent) {\n childrenExpandedKeys.value.add(keyOf(parent.data))\n }\n },\n })\n\nconst {\n applying,\n applyError,\n hasPendingChanges,\n movedCount,\n reorderMode,\n enterReorderMode,\n cancelReorderMode,\n applyReorder,\n} = useReorderMode<NestedTree<TItem>>({\n mode,\n snapshot,\n movedKeys,\n modelValue,\n cloneModel: (m) => cloneDeep(m) as NestedTree<TItem>,\n applyModel: (m) => {\n modelValue.value = m\n },\n canEnterReorder,\n onEnter: () => {\n sessionDeferredDeletes.value = new Set()\n sessionImmediateDeletes.value = new Set()\n clearEditing()\n // Expand every branch so all rows are reachable drag targets.\n for (const k of expandableKeys.value) childrenExpandedKeys.value.add(k)\n nextTick(() => {\n if (dragEnabled.value) initSortables()\n })\n },\n onExternalEnter: () => {\n sessionDeferredDeletes.value = new Set()\n sessionImmediateDeletes.value = new Set()\n clearEditing()\n },\n // Cancel: the snapshot-restore has brought every deleted row back; reconcile the controller so a\n // deferred delete truly reverts (clear tombstone, row stays) and an immediate delete stays gone (row\n // removed again — it is deleted on the backend and must not resurrect / be re-created by a save).\n onCancel: () => {\n for (const key of sessionDeferredDeletes.value) controller.restoreDeleted(key)\n for (const key of sessionImmediateDeletes.value) controller.deleteItem(key, { trackDeleted: false })\n sessionDeferredDeletes.value = new Set()\n sessionImmediateDeletes.value = new Set()\n destroySortables()\n },\n onApplyEnd: () => {\n sessionDeferredDeletes.value = new Set()\n sessionImmediateDeletes.value = new Set()\n destroySortables()\n },\n onReorderApply: (tree) => props.onReorderApply?.(tree),\n emit: {\n reorderStart: () => emit('reorder-start'),\n reorderCancel: () => emit('reorder-cancel'),\n reorderApplied: (payload) => emit('reorder-applied', payload),\n reorderApplyError: (err) => emit('reorder-apply-error', err),\n reorderEnd: () => emit('reorder-end'),\n },\n})\n\n// Reorder-toolbar pending indicator = session moves + deferred deletes made in the session, so a delete\n// in reorder mode no longer reads as \"no pending changes\" and Apply stays enabled.\nconst nestedPendingCount = computed<number>(() => movedCount.value + sessionDeferredDeletes.value.size)\nconst nestedPendingChanges = computed<boolean>(() => hasPendingChanges.value || sessionDeferredDeletes.value.size > 0)\n\nconst canAdd = computed(() => canInteract.value && props.showAddButton && !reorderMode.value)\nconst dragEnabled = computed(() => reorderMode.value && !isTouch.value && !props.disableDrag)\n\nconst addLabelResolved = computed(() => props.addLabel ?? t('common.sortable.add'))\nconst emptyTitleResolved = computed(() => props.emptyTitle ?? t('common.sortable.emptyTitle'))\nconst deleteConfirmTitleResolved = computed(() => props.deleteConfirmTitle ?? t('common.sortable.deleteConfirmTitle'))\nconst deleteConfirmTextResolved = computed(\n () =>\n props.deleteConfirmText ??\n (props.deleteMode === 'immediate'\n ? t('common.sortable.deleteConfirmText')\n : t('common.sortable.deleteConfirmTextDeferred'))\n)\n\nconst reorderToggleVisible = computed<boolean>(\n (): boolean => !props.readonly && props.showReorderToggle && !reorderMode.value && totalItemCount.value > 0\n)\n\nconst compactReorderButton = computed<boolean>((): boolean => !!props.title && isNarrow.value)\n\n// Keys of every node that has children — the expand/collapse candidates.\nconst expandableKeys = computed<ListEditorKey[]>(() => {\n const out: ListEditorKey[] = []\n const walk = (nodes: NestedTreeNode<TItem>[]) => {\n for (const n of nodes) {\n if (n.children && n.children.length > 0) {\n out.push(keyOf(n.data))\n walk(n.children)\n }\n }\n }\n walk(modelValue.value.children)\n return out\n})\n\nconst allExpanded = computed<boolean>(\n () => expandableKeys.value.length > 0 && expandableKeys.value.every((k) => childrenExpandedKeys.value.has(k))\n)\n\nconst expandAllVisible = computed<boolean>(() => !reorderMode.value && expandableKeys.value.length > 0)\n\nconst toggleExpandAll = () => {\n if (allExpanded.value) {\n for (const k of expandableKeys.value) childrenExpandedKeys.value.delete(k)\n } else {\n for (const k of expandableKeys.value) childrenExpandedKeys.value.add(k)\n }\n}\n\nconst headerVisible = computed<boolean>(\n (): boolean =>\n !!(\n props.title ||\n (slots as Record<string, unknown>).header ||\n (slots as Record<string, unknown>)['reorder-toggle'] ||\n reorderToggleVisible.value ||\n expandAllVisible.value ||\n reorderMode.value\n )\n)\n\n// One Sortable instance per group so drag/drop moves items within/between groups;\n// SortableJS owns the pointer events, onEnd reconciles via editor.moveTo().\nconst sortableInstances = ref<Array<{ stop: () => void; option?: (k: string, v: unknown) => void }>>([])\nconst forceRerender = ref(0)\n\nconst destroySortables = () => {\n for (const s of sortableInstances.value) {\n try {\n s.stop()\n } catch {\n // no-op\n }\n }\n sortableInstances.value = []\n}\n\nconst GROUP_CLASS = 'a-nested-list-editor__group'\nconst HANDLE_CLASS = 'a-le-drag-handle'\n\n// Live drag state. `instruction` is recomputed on every pointermove and encodes\n// where + at what depth the dragged item lands (sibling-above/below/make-child/blocked);\n// the overlay reads it, onEnd applies it via `editor.moveTo`. SortableJS never moves\n// the DOM (onMove always returns false) — hit-testing, depth and the mutation are all ours.\nconst dragState = ref<DragState | null>(null)\n\n// Drop indicator anchor geometry, kept in sync with CSS (reorder-mode header\n// padding-left = 12 + depth*24, drag handle = 20px). ANCHOR_X = 12 + half handle\n// (10) = 22 puts the dot on the handle's centre; the line steps 24px per depth.\nconst INDENT_PX = 24\nconst ANCHOR_X = 22\n\nconst hitTestRow = (\n clientX: number,\n clientY: number\n): { el: HTMLElement; viewItem: DecoratedNestedViewItem<TItem> } | null => {\n const hit = document.elementFromPoint(clientX, clientY) as HTMLElement | null\n if (!hit) return null\n const wrapper = hit.closest('.a-le-row-wrapper') as HTMLElement | null\n if (!wrapper) return null\n // Only wrappers inside our rowsContainer — elementFromPoint could hit a\n // different ANestedSortableListEditor instance on the same page.\n if (!rowsContainer.value || !rowsContainer.value.contains(wrapper)) return null\n const key = parseKey(wrapper.getAttribute('data-id'))\n if (key === null) return null\n const vi = viewItemsDecorated.value.find((v) => v.key === key)\n if (!vi) return null\n return { el: wrapper, viewItem: vi }\n}\n\nconst recomputeInstruction = (clientX: number, clientY: number) => {\n if (!dragState.value || !rowsContainer.value) return\n const hit = hitTestRow(clientX, clientY)\n if (!hit) {\n dragState.value.instruction = null\n return\n }\n const containerRect = rowsContainer.value.getBoundingClientRect()\n // Hit-test the `.a-le-row` child, not the wrapper — the wrapper's height\n // balloons when children are rendered.\n const rowEl = hit.el.querySelector(':scope > .a-le-row') as HTMLElement | null\n const rowRect = (rowEl ?? hit.el).getBoundingClientRect()\n dragState.value.instruction = computeInstruction({\n pointer: { x: clientX, y: clientY },\n hoveredRow: {\n key: hit.viewItem.key,\n rect: rowRect,\n depth: hit.viewItem.depth,\n parentKey: hit.viewItem.parentKey,\n siblingIndex: hit.viewItem.siblingIndex,\n siblingCount: hit.viewItem.siblingCount,\n },\n sourceKey: dragState.value.sourceKey,\n sourceSubtreeDepth: dragState.value.sourceSubtreeDepth,\n viewItems: viewItemsDecorated.value,\n maxDepth: props.maxDepth,\n indentPx: INDENT_PX,\n containerLeft: containerRect.left,\n containerPaddingLeft: ANCHOR_X,\n })\n}\n\nconst onPointerMove = (e: PointerEvent) => {\n recomputeInstruction(e.clientX, e.clientY)\n}\n\nconst applyInstruction = (inst: ExecutableInstruction, sourceKey: ListEditorKey) => {\n const ok = controller.moveTo(sourceKey, inst.parentKey, inst.index)\n if (!ok) {\n showWarningT('common.sortable.error.maxDeepExceed')\n forceRerender.value++\n nextTick(() => initSortables())\n return\n }\n markMoved(sourceKey)\n // makeChild: expand the new parent so the just-moved row doesn't vanish into a collapsed branch.\n if (inst.makeChild && inst.parentKey !== null) {\n childrenExpandedKeys.value.add(inst.parentKey)\n }\n}\n\nconst initSortables = () => {\n destroySortables()\n if (!dragEnabled.value) return\n if (!rowsContainer.value) return\n const groups = Array.from(rowsContainer.value.querySelectorAll<HTMLElement>('.' + GROUP_CLASS))\n for (const group of groups) {\n const sortable = useSortable(group, [], {\n group: { name: 'a-nested', pull: true, put: true },\n handle: '.' + HANDLE_CLASS,\n animation: 0,\n ghostClass: DRAG_GHOST_CLASS,\n chosenClass: DRAG_CHOSEN_CLASS,\n dragClass: DRAG_CLASS,\n fallbackOnBody: true,\n forceFallback: true,\n fallbackTolerance: 3,\n onStart: (event) => {\n const draggedEl = event.item as HTMLElement\n const id = parseKey(draggedEl.getAttribute('data-id'))\n if (id === null) return\n const draggedNode = controller.findNode(id).node\n const subtreeDepth = draggedNode ? controller.calculateSubtreeDepth(draggedNode) : 1\n dragState.value = {\n sourceKey: id,\n sourceSubtreeDepth: subtreeDepth,\n instruction: null,\n }\n document.addEventListener('pointermove', onPointerMove, { passive: true })\n },\n onMove: (event) => {\n // Also track the pointer via SortableJS's stream — keeps `instruction`\n // in sync when the browser coalesces pointermove events during fast drags.\n const orig = (event as unknown as { originalEvent?: Event }).originalEvent\n if (orig) {\n if ('clientX' in orig && 'clientY' in orig) {\n recomputeInstruction((orig as MouseEvent).clientX, (orig as MouseEvent).clientY)\n } else if ('touches' in orig) {\n const tev = orig as TouchEvent\n if (tev.touches.length > 0) {\n recomputeInstruction(tev.touches[0].clientX, tev.touches[0].clientY)\n }\n }\n }\n // Refuse SortableJS's own DOM insertion — onEnd's `editor.moveTo` drives the move.\n return false\n },\n onEnd: () => {\n document.removeEventListener('pointermove', onPointerMove)\n const state = dragState.value\n dragState.value = null\n if (!state) return\n const inst = state.instruction\n if (inst === null) return\n if (inst.type === 'blocked') return\n applyInstruction(inst, state.sourceKey)\n },\n })\n sortableInstances.value.push(sortable)\n }\n}\n\nconst parseKey = (raw: string | null): ListEditorKey | null => {\n if (raw === null || raw === '') return null\n // Numeric key only when `data-id` is a pure integer string (incl. negative temp\n // ids like \"-1\"); else a DocId/UUID. The old `n > 0` test sent negative temp ids\n // to the string branch, so a freshly-added row's move marked the wrong key.\n const n = stringToInt(raw)\n return String(n) === raw ? n : raw\n}\n\nwatch(\n () => dragEnabled.value,\n () => {\n if (dragEnabled.value) nextTick(() => initSortables())\n else destroySortables()\n }\n)\n\nwatch(\n () => forceRerender.value,\n () => {\n if (dragEnabled.value) nextTick(() => initSortables())\n }\n)\n\n// Rebuild sortables when the tree shape changes during drag mode, else newly rendered groups aren't draggable.\nwatch(\n () => viewItemsDecorated.value.map((v) => v.key).join('|'),\n () => {\n if (dragEnabled.value) nextTick(() => initSortables())\n }\n)\n\nonMounted(() => {\n if (dragEnabled.value) initSortables()\n})\nonBeforeUnmount(() => {\n destroySortables()\n document.removeEventListener('pointermove', onPointerMove)\n})\n\n// Overlay visual props from the current instruction; `null` = hidden (no\n// instruction, or blocked). The optional connector is a rail from the drop line\n// up to the row whose level dictates the insert. Measured via getBoundingClientRect\n// rather than CSS anchor positioning: Chrome's anchor-reachability rule excludes\n// some deeply-nested descendants, so JS measurement is simpler and more reliable.\ntype OverlayVisual = {\n line: { top: number; left: number; right: number }\n connector: { top: number; height: number; left: number } | null\n}\n\nconst overlayVisual = computed<OverlayVisual | null>(() => {\n const state = dragState.value\n if (!state || state.instruction === null || !rowsContainer.value) return null\n const inst = state.instruction\n // Blocked drops render nothing — the silent empty space is the \"not here\" signal.\n if (inst.type === 'blocked') return null\n const refWrapper = rowsContainer.value.querySelector<HTMLElement>(\n `.a-le-row-wrapper[data-id=\"${CSS.escape(String(inst.refKey))}\"]`\n )\n if (!refWrapper) return null\n const rowEl = refWrapper.querySelector<HTMLElement>(':scope > .a-le-row')\n const containerRect = rowsContainer.value.getBoundingClientRect()\n const rowRect = (rowEl ?? refWrapper).getBoundingClientRect()\n\n const lineLeft = ANCHOR_X + inst.depth * INDENT_PX\n const lineTop = inst.refEdge === 'top' ? rowRect.top - containerRect.top : rowRect.bottom - containerRect.top\n const line = { top: lineTop, left: lineLeft, right: 16 }\n\n let connector: OverlayVisual['connector'] = null\n if (inst.levelRowKey !== null) {\n const levelWrapper = rowsContainer.value.querySelector<HTMLElement>(\n `.a-le-row-wrapper[data-id=\"${CSS.escape(String(inst.levelRowKey))}\"]`\n )\n if (levelWrapper) {\n const levelRow = levelWrapper.querySelector<HTMLElement>(':scope > .a-le-row')\n const levelRect = (levelRow ?? levelWrapper).getBoundingClientRect()\n const levelCentreY = levelRect.top - containerRect.top + levelRect.height / 2\n if (levelCentreY < lineTop) {\n connector = {\n top: levelCentreY,\n height: lineTop - levelCentreY,\n left: lineLeft,\n }\n }\n }\n }\n\n return { line, connector }\n})\n\n// With a `factory`, the editor inserts through the controller; without one it\n// stays emit-only so legacy `@add` consumers drive the insert. The `add` event\n// fires either way.\nconst onAddClick = () => {\n if (!canAdd.value) return\n requestAutoOpen()\n if (props.factory) controller.addItem()\n emit('add', undefined)\n}\n\nconst onRowAddAfterClick = (vi: NestedViewItem<TItem>) => {\n if (!canInteract.value) return\n requestAutoOpen()\n if (props.factory) controller.addAfter(vi.key, undefined, vi.childrenAllowed)\n emit('add', { afterId: vi.key, childrenAllowed: vi.childrenAllowed })\n}\n\nconst onAddChildClick = (vi: NestedViewItem<TItem>) => {\n if (!canInteract.value) return\n if (!vi.canAddChild) return\n requestAutoOpen()\n childrenExpandedKeys.value.add(vi.key)\n emit('add-child', vi)\n // Append to the end of children, matching the root \"Add item\" semantic.\n // (Drag-drop make-child still lands at index 0 — its drop line sits just below the parent.)\n if (props.factory) controller.addChild(vi.key, undefined, true)\n emit('add', { parentId: vi.key, childrenAllowed: true })\n}\n\nconst onEditClick = (vi: NestedViewItem<TItem>) => {\n if (!canInteract.value || reorderMode.value) return\n // Clicking edit while already editing closes the form, so the pencil toggles like the row header.\n if (editingKeys.value.has(vi.key)) {\n onCloseClick(vi)\n return\n }\n if (isInlineEdit.value) {\n beginEdit(vi)\n }\n emit('edit', vi)\n}\n\n// Toggles tree children visibility (distinct from the readonly-detail body below).\nconst onChevronClick = (vi: NestedViewItem<TItem>) => {\n if (props.disabled || props.loading) return\n const key = vi.key\n const currently = childrenExpandedKeys.value.has(key)\n if (currently) childrenExpandedKeys.value.delete(key)\n else childrenExpandedKeys.value.add(key)\n emit('item-expand', vi, !currently)\n}\n\n// Toggles the row's readonly-detail body (separate from tree children).\nconst onDetailToggle = (vi: NestedViewItem<TItem>) => {\n if (props.disabled || props.loading) return\n const key = vi.key\n const currently = detailExpandedKeys.value.has(key)\n if (currently) detailExpandedKeys.value.delete(key)\n else detailExpandedKeys.value.add(key)\n emit('item-expand', vi, !currently)\n}\n\nconst isRowClickable = (vi: DecoratedNestedViewItem<TItem>): boolean => {\n if (props.disableRowClick) return false\n if (props.disabled || props.loading) return false\n if (reorderMode.value) return false\n if (vi.editing || vi.expanded) return true\n if (!props.readonly && props.showEditButton) return true\n return false\n}\n\nconst onRowClick = (vi: DecoratedNestedViewItem<TItem>) => {\n if (!isRowClickable(vi)) return\n if (vi.editing || vi.expanded) {\n onCloseClick(vi)\n return\n }\n if (props.readonly && hasReadonlyDetail.value) onDetailToggle(vi)\n else onEditClick(vi)\n}\n\nconst {\n deleteDialog,\n deleteInFlight,\n deleteError,\n onDeleteClick: triggerDeleteClick,\n onDeleteDialogConfirm,\n onDeleteDialogCancel,\n} = useDeleteDialog<TItem, NestedViewItem<TItem>>({\n onDeleteConfirm: (raw) => (props.onDeleteConfirm ? props.onDeleteConfirm(raw) : true),\n onDelete: (raw) => props.onDelete?.(raw),\n onDeleted: (vi) => {\n editingKeys.value.delete(vi.key)\n editingSnapshots.value.delete(vi.key)\n detailExpandedKeys.value.delete(vi.key)\n childrenExpandedKeys.value.delete(vi.key)\n const deferred = props.deleteMode === 'deferred'\n // A delete in reorder mode is a session change: deferred reverts on Cancel, immediate stays gone\n // (re-applied after Cancel's snapshot-restore).\n if (reorderMode.value) {\n if (deferred) sessionDeferredDeletes.value.add(vi.key)\n else sessionImmediateDeletes.value.add(vi.key)\n }\n // Controller owns removal (mirrors the flat editors): a deferred saved-row delete is tombstoned\n // (counts as unsaved); immediate skips it (already deleted on the backend). Runs before the\n // `deleted` emit so a consumer's handler still receives `vi.raw`.\n controller.deleteItem(vi.key, { trackDeleted: deferred })\n emit('deleted', vi)\n },\n disableDeleteConfirm: () => props.disableDeleteConfirm,\n})\n\nconst onDeleteClick = async (vi: NestedViewItem<TItem>) => {\n if (!canInteract.value) return\n await triggerDeleteClick(vi)\n}\n\nconst onSaveClick = async (vi: NestedViewItem<TItem>) => {\n if (props.onItemSave) await props.onItemSave(vi.raw)\n commitEdit(vi)\n emit('item-saved', vi)\n}\n\nconst onCancelClick = (vi: NestedViewItem<TItem>) => {\n cancelEdit(vi)\n}\n\nconst onCloseClick = (vi: NestedViewItem<TItem>) => {\n closeEdit(vi)\n emit('close', vi)\n}\n\nconst moveUp = (id: ListEditorKey) => {\n if (controller.moveUp(id)) markMoved(id)\n}\nconst moveDown = (id: ListEditorKey) => {\n if (controller.moveDown(id)) markMoved(id)\n}\nconst moveTop = (id: ListEditorKey) => {\n if (controller.moveTop(id)) markMoved(id)\n}\nconst moveBottom = (id: ListEditorKey) => {\n if (controller.moveBottom(id)) markMoved(id)\n}\nconst doIndent = (vi: NestedViewItem<TItem>) => {\n if (!controller.indent(vi.key)) {\n showWarningT('common.sortable.error.maxDeepExceed')\n return\n }\n markMoved(vi.key)\n emit('indent', vi)\n}\nconst doOutdent = (vi: NestedViewItem<TItem>) => {\n if (!controller.outdent(vi.key)) return\n markMoved(vi.key)\n emit('outdent', vi)\n}\n\nconst resolveCompactText = (raw: TItem): string => resolveCompactTextUtil(raw, { compactField: props.compactField })\n\n// Row validation from the controller's gated `rowState` — red rail shows only\n// once the row is unsaved or `validateAll()` ran.\n// `editing` → the controller reads amber (not red) while the row is being filled in. (QA 85050 b7)\nconst resolveValidation = (raw: TItem, key?: ListEditorKey): ListEditorValidationState =>\n key === undefined ? null : controller.rowState(raw, key, editingKeys.value.has(key))\n\n// Per-key actions cache for stable per-row identity (see AListEditor). Closures\n// capture the stable key and look up the current vi via findVi at call time.\ntype ActionsBundle = {\n edit: () => void\n save: () => Promise<void> | void\n cancel: () => void\n close: () => void\n delete: () => Promise<void>\n addAfter: () => void\n addChild: () => void\n toggleExpand: () => void\n toggleDetail: () => void\n moveUp: () => void\n moveDown: () => void\n moveTop: () => void\n moveBottom: () => void\n indent: () => void\n outdent: () => void\n update: (data: TItem) => void\n}\nconst actionsCache = new Map<ListEditorKey, ActionsBundle>()\nconst getActions = (key: ListEditorKey): ActionsBundle => {\n let actions = actionsCache.get(key)\n if (!actions) {\n actions = {\n edit: () => {\n const vi = findVi(key)\n if (vi) onEditClick(vi)\n },\n save: () => {\n const vi = findVi(key)\n if (vi) return onSaveClick(vi)\n },\n cancel: () => {\n const vi = findVi(key)\n if (vi) onCancelClick(vi)\n },\n close: () => {\n const vi = findVi(key)\n if (vi) onCloseClick(vi)\n },\n delete: async () => {\n const vi = findVi(key)\n if (vi) await onDeleteClick(vi)\n },\n addAfter: () => {\n const vi = findVi(key)\n if (vi) onRowAddAfterClick(vi)\n },\n addChild: () => {\n const vi = findVi(key)\n if (vi) onAddChildClick(vi)\n },\n toggleExpand: () => {\n const vi = findVi(key)\n if (vi) onChevronClick(vi)\n },\n toggleDetail: () => {\n const vi = findVi(key)\n if (vi) onDetailToggle(vi)\n },\n moveUp: () => moveUp(key),\n moveDown: () => moveDown(key),\n moveTop: () => moveTop(key),\n moveBottom: () => moveBottom(key),\n indent: () => {\n const vi = findVi(key)\n if (vi) doIndent(vi)\n },\n outdent: () => {\n const vi = findVi(key)\n if (vi) doOutdent(vi)\n },\n update: (data: TItem) => controller.updateItem(key, data),\n }\n actionsCache.set(key, actions)\n }\n return actions\n}\nwatch(\n () => viewItemsDecorated.value,\n (now) => {\n if (actionsCache.size === 0) return\n const liveKeys = new Set(now.map((v) => v.key))\n for (const key of actionsCache.keys()) {\n if (!liveKeys.has(key)) actionsCache.delete(key)\n }\n }\n)\n\nconst buildSlotProps = (vi: DecoratedNestedViewItem<TItem>) => ({\n item: { ...vi, validationState: resolveValidation(vi.raw as TItem, vi.key) },\n raw: vi.raw,\n index: vi.index,\n key: vi.key,\n depth: vi.depth,\n parent: vi.parent,\n parentKey: vi.parentKey,\n childrenCount: vi.childrenCount,\n hasChildren: vi.hasChildren,\n childrenAllowed: vi.childrenAllowed,\n firstInParent: vi.firstInParent,\n lastInParent: vi.lastInParent,\n readonly: props.readonly,\n disabled: props.disabled,\n expanded: vi.expanded,\n childrenExpanded: vi.childrenExpanded,\n editing: vi.editing,\n dirty: vi.dirty,\n unsaved: vi.unsaved,\n moved: vi.moved,\n reorderMode: reorderMode.value,\n canMoveUp: !vi.firstInParent,\n canMoveDown: !vi.lastInParent,\n canIndent: vi.canIndent,\n canOutdent: vi.canOutdent,\n canAddChild: vi.canAddChild,\n touch: isTouch.value,\n stateKeyPrefix: rowStateScope.prefixFor(vi.key),\n actions: getActions(vi.key),\n})\n\nconst toolbarSlotProps = computed(() => ({\n applying: applying.value,\n hasPendingChanges: nestedPendingChanges.value,\n movedCount: nestedPendingCount.value,\n error: applyError.value,\n actions: {\n apply: applyReorder,\n cancel: cancelReorderMode,\n },\n}))\n\nconst reorderToggleSlotProps = computed(() => ({\n mode: mode.value,\n disabled: !canEnterReorder.value,\n hasPendingChanges: hasPendingChanges.value,\n actions: {\n enterReorderMode,\n exitReorderMode: cancelReorderMode,\n },\n}))\n\nconst expandableKeySet = computed(() => new Set(expandableKeys.value))\n\nconst findVi = (key: ListEditorKey): DecoratedNestedViewItem<TItem> | undefined =>\n viewItemsDecorated.value.find((v) => v.key === key)\n\nconst keyboardNav = useKeyboardNav({\n viewItems: computed(() =>\n viewItemsDecorated.value.map((vi) => ({\n key: vi.key,\n expanded: vi.childrenExpanded,\n canExpand: expandableKeySet.value.has(vi.key),\n canIndent: true,\n canOutdent: true,\n }))\n ),\n variant: 'nested',\n isReorderMode: reorderMode,\n disabled: computed(() => !canInteract.value),\n isEditing: (key) => editingKeys.value.has(key),\n onToggleEdit: (key) => {\n const vi = findVi(key)\n if (vi) onEditClick(vi)\n },\n onCancelEdit: (key) => {\n const vi = findVi(key)\n if (vi) onCloseClick(vi)\n },\n onMoveUp: (key) => moveUp(key),\n onMoveDown: (key) => moveDown(key),\n onMoveTop: (key) => moveTop(key),\n onMoveBottom: (key) => moveBottom(key),\n onIndent: (key) => {\n const vi = findVi(key)\n if (vi) doIndent(vi)\n },\n onOutdent: (key) => {\n const vi = findVi(key)\n if (vi) doOutdent(vi)\n },\n onToggleChildren: (key) => {\n const vi = findVi(key)\n if (vi) onChevronClick(vi)\n },\n onCancelReorder: () => cancelReorderMode(),\n})\n\nconst moveToPositionDialogOpen = ref<boolean>(false)\nconst moveToPositionTarget = shallowRef<DecoratedNestedViewItem<TItem> | null>(null)\nconst moveToPositionContext = computed<{\n parentId: ListEditorKey | null\n total: number\n currentIndex: number\n} | null>(() => {\n const target = moveToPositionTarget.value\n if (!target) return null\n const found = controller.findNode(target.key)\n const siblings = found.parent?.children ?? modelValue.value.children\n const idx = siblings.findIndex((s) => keyOf(s.data) === target.key)\n return {\n parentId: found.parent ? keyOf(found.parent.data) : null,\n total: siblings.length,\n currentIndex: idx,\n }\n})\nconst moveToPositionLabel = computed<string>(() =>\n moveToPositionTarget.value ? resolveCompactText(moveToPositionTarget.value.raw) : ''\n)\nconst openMoveToPosition = (vi: DecoratedNestedViewItem<TItem>) => {\n if (!props.showMoveToPosition) return\n moveToPositionTarget.value = vi\n moveToPositionDialogOpen.value = true\n}\nconst onMoveToPositionConfirm = (newIndex: number) => {\n const ctx = moveToPositionContext.value\n const target = moveToPositionTarget.value\n moveToPositionTarget.value = null\n if (!ctx || !target) return\n if (newIndex === ctx.currentIndex) return\n if (controller.moveTo(target.key, ctx.parentId, newIndex)) {\n markMoved(target.key)\n }\n}\n\nconst changeParentDialogOpen = ref<boolean>(false)\nconst changeParentTarget = shallowRef<DecoratedNestedViewItem<TItem> | null>(null)\nconst openChangeParent = (vi: DecoratedNestedViewItem<TItem>) => {\n if (!props.showChangeParent) return\n changeParentTarget.value = vi\n changeParentDialogOpen.value = true\n}\nconst onChangeParentConfirm = (parentId: ListEditorKey | null, position: 'first' | 'last') => {\n const target = changeParentTarget.value\n changeParentTarget.value = null\n if (!target) return\n // Sibling count under the new parent so 'last' resolves to the right index ('first' = 0).\n let siblingCount = 0\n if (parentId === null) {\n siblingCount = modelValue.value.children.length\n } else {\n const found = controller.findNode(parentId)\n siblingCount = found.node?.children?.length ?? 0\n }\n const targetIndex = position === 'first' ? 0 : siblingCount\n if (controller.moveTo(target.key, parentId, targetIndex)) {\n markMoved(target.key)\n if (parentId !== null) childrenExpandedKeys.value.add(parentId)\n }\n}\n\n// Aggregated display flags + helpers passed into each <LeNestedRow>.\nconst rowContext = computed(() => ({\n reorderMode: reorderMode.value,\n readonly: props.readonly,\n canInteract: canInteract.value,\n dragEnabled: dragEnabled.value,\n showExpandToggle: props.showExpandToggle,\n showEditButton: props.showEditButton,\n showDeleteButton: props.showDeleteButton,\n showAddChildButton: props.showAddChildButton,\n showAddAfterAction: props.showAddAfterAction,\n showMoveToPosition: props.showMoveToPosition,\n showChangeParent: props.showChangeParent,\n showInlineSaveFooter: showInlineSaveFooter.value,\n statusField: props.statusField,\n effectiveCloseVariant: effectiveCloseVariant.value,\n keyboardNav,\n isRowClickable,\n resolveCompactText,\n resolveValidation: (raw: TItem, key?: ListEditorKey) => resolveValidation(raw, key),\n buildSlotProps,\n}))\n\n// Event callback bundle passed to each row.\nconst rowCallbacks = {\n onRowClick,\n onChevronClick,\n onEditClick,\n onDeleteClick,\n onCloseClick,\n onSaveClick,\n onCancelClick,\n onAddAfterClick: onRowAddAfterClick,\n onAddChildClick,\n moveUp: (id: ListEditorKey) => moveUp(id),\n moveDown: (id: ListEditorKey) => moveDown(id),\n moveTop: (id: ListEditorKey) => moveTop(id),\n moveBottom: (id: ListEditorKey) => moveBottom(id),\n indent: doIndent,\n outdent: doOutdent,\n openMoveToPosition,\n openChangeParent,\n}\n\nconst rootViewItems = computed(() => viewItemsDecorated.value.filter((v) => v.parentKey === null))\n\n// Imperative aliases mirroring the legacy ASortableNested signatures so existing\n// consumers keep working. They assume the caller persists server-side, so they\n// re-baseline via `controller.commit()` (rows don't render \"unsaved\" after the\n// call) and return the live model. New consumers should use the controller handle directly.\n\n/** @deprecated Use `addItem(data, { afterId, childrenAllowed })` on the handle. */\nconst addAfterId = (targetId: ListEditorKey | null, data: TItem, childrenAllowed: boolean) => {\n controller.addItem(data, { afterId: targetId ?? undefined, childrenAllowed })\n nextTick(() => controller.commit())\n return modelValue.value\n}\n/** @deprecated Use `addChild(parentKey, data, childrenAllowed)` on the handle. */\nconst addChildToId = (targetId: ListEditorKey, data: TItem, childrenAllowed: boolean) => {\n childrenExpandedKeys.value.add(targetId)\n // Legacy \"prepend as first child\" semantic (canonical `addChild` appends to the end).\n controller.addItem(data, { parentId: targetId, asFirstChild: true, childrenAllowed })\n nextTick(() => controller.commit())\n return modelValue.value\n}\n/** @deprecated Use `deleteItem(id)` on the handle. */\nconst removeById = (id: ListEditorKey) => {\n controller.deleteItem(id)\n editingKeys.value.delete(id)\n editingSnapshots.value.delete(id)\n detailExpandedKeys.value.delete(id)\n childrenExpandedKeys.value.delete(id)\n nextTick(() => controller.commit())\n}\n/** @deprecated Use `updateItem(id, data)` on the handle. */\nconst updateData = (\n id: ListEditorKey,\n data: TItem,\n _children: unknown = null,\n _position: unknown = null,\n _markUnsaved: unknown = null\n) => {\n controller.updateItem(id, data)\n nextTick(() => controller.commit())\n}\n\n// Total distinct unconfirmed changes (added/edited/moved/reparented rows + deferred deletions) — a\n// delete counts even though its row is gone. Drives the unsaved-section label + the exposed handle.\nconst unsavedCount = controller.unsavedCount\n\n// Re-baseline as saved, clear the moved set, close open edits. Legacy name kept.\nconst clearUnsavedState = () => {\n controller.commit()\n movedKeys.value = new Set()\n clearEditing()\n}\n\n// Registers this editor as a named unsaved-changes section when a label is passed.\nuseUnsavedSection(() =>\n props.unsavedSectionLabel && !props.disableUnsaved\n ? { label: props.unsavedSectionLabel, dirty: controller.hasUnsaved.value }\n : []\n)\n\n// Expose the controller handle plus legacy aliases and reorder/expand controls.\n// Entries after the spread override controller methods where the historic name or\n// return shape differs (e.g. `viewItems` is this component's expand-aware list).\n// Reveal for a blocked save: open invalid rows AND expand the offender's ancestor chain so a\n// collapsed nested row becomes visible. Shared by the exposed validateAll + scope reveal-on-touch.\nconst revealNestedInvalid = (): boolean =>\n validateAllAndReveal(controller, (key) => {\n let ancestor = controller.findNode(key).parent\n while (ancestor) {\n childrenExpandedKeys.value.add(keyOf(ancestor.data))\n ancestor = controller.findNode(keyOf(ancestor.data)).parent\n }\n const vi = controller.viewItems.value.find((v) => v.key === key)\n if (vi) beginEdit(vi)\n })\n\n// Auto-bridge the editor's aggregate validity into the consumer's `validation-scope` collector so a\n// plain `$invalid` save gate blocks (and reveals) a collapsed invalid row. No-op without the prop.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss -- construction-time opt-in, read once\nuseListEditorScopeValidity({\n hasErrors: controller.hasErrors,\n validationScope: props.validationScope,\n validateProvided: props.validate !== undefined,\n reveal: revealNestedInvalid,\n})\n\ndefineExpose<\n NestedListEditorHandle<TItem> & {\n addAfterId: typeof addAfterId\n addChildToId: typeof addChildToId\n removeById: typeof removeById\n updateData: typeof updateData\n resetDirtyBaseline: () => void\n hasUnsavedChanges: typeof controller.hasUnsaved\n unsavedCount: typeof unsavedCount\n clearUnsavedState: () => void\n enterReorderMode: () => void\n cancelReorderMode: () => void\n applyReorder: () => Promise<void>\n expand: (id: ListEditorKey) => void\n collapse: (id: ListEditorKey) => void\n toggleExpand: (id: ListEditorKey) => void\n expandDetail: (id: ListEditorKey) => void\n collapseDetail: (id: ListEditorKey) => void\n }\n>({\n ...controller,\n validateAll: revealNestedInvalid,\n // Legacy aliases (pre-v2 ASortableNested API).\n addAfterId,\n addChildToId,\n removeById,\n updateData,\n resetDirtyBaseline,\n hasUnsavedChanges: controller.hasUnsaved,\n unsavedCount,\n clearUnsavedState,\n // Expand-aware flattened view (controller's own `viewItems` ignores collapse state).\n viewItems: flatViewItems,\n enterReorderMode,\n cancelReorderMode,\n applyReorder,\n expand: (id: ListEditorKey) => childrenExpandedKeys.value.add(id),\n collapse: (id: ListEditorKey) => childrenExpandedKeys.value.delete(id),\n toggleExpand: (id: ListEditorKey) => {\n if (childrenExpandedKeys.value.has(id)) childrenExpandedKeys.value.delete(id)\n else childrenExpandedKeys.value.add(id)\n },\n expandDetail: (id: ListEditorKey) => detailExpandedKeys.value.add(id),\n collapseDetail: (id: ListEditorKey) => detailExpandedKeys.value.delete(id),\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"a-nested-list-editor\"\n :class=\"[\n `a-nested-list-editor--mode-${mode}`,\n {\n 'a-nested-list-editor--readonly': readonly,\n 'a-nested-list-editor--disabled': disabled,\n 'a-nested-list-editor--touch': isTouch,\n 'a-nested-list-editor--drag-enabled': dragEnabled,\n },\n ]\"\n >\n <div class=\"a-le-card\">\n <div\n v-if=\"headerVisible\"\n class=\"a-le-header\"\n >\n <slot\n name=\"header\"\n :title=\"title\"\n v-bind=\"reorderToggleSlotProps\"\n >\n <h3\n v-if=\"title\"\n class=\"a-le-title-heading\"\n >\n {{ title }}\n </h3>\n <div class=\"a-le-header-actions\">\n <template v-if=\"reorderMode\">\n <!-- Reorder-mode header: pending count + Cancel/Apply, sitting where\n the \"Reorder\" button lives in view mode (not a bottom toolbar). -->\n <slot\n name=\"reorder-toolbar\"\n v-bind=\"toolbarSlotProps\"\n >\n <LeStatus\n :class=\"{ 'a-le-toolbar-status--pending': nestedPendingChanges }\"\n :has-pending-changes=\"nestedPendingChanges\"\n :pending-count=\"nestedPendingCount\"\n :error=\"applyError\"\n />\n <VBtn\n variant=\"text\"\n size=\"small\"\n :disabled=\"applying\"\n @click=\"cancelReorderMode\"\n >\n {{ t('common.sortable.reorderCancel') }}\n </VBtn>\n <VBtn\n color=\"primary\"\n variant=\"flat\"\n size=\"small\"\n prepend-icon=\"mdi-check\"\n :loading=\"applying\"\n :disabled=\"applying || !nestedPendingChanges\"\n @click=\"applyReorder\"\n >\n {{ t('common.sortable.reorderApply') }}\n </VBtn>\n </slot>\n </template>\n <template v-else>\n <VBtn\n v-if=\"expandAllVisible && compactReorderButton\"\n variant=\"tonal\"\n color=\"primary\"\n icon\n size=\"x-small\"\n @click=\"toggleExpandAll\"\n >\n <VIcon\n :icon=\"allExpanded ? 'mdi-unfold-less-horizontal' : 'mdi-unfold-more-horizontal'\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"allExpanded ? t('common.sortable.collapseAll') : t('common.sortable.expandAll')\"\n />\n </VBtn>\n <VBtn\n v-else-if=\"expandAllVisible\"\n variant=\"tonal\"\n color=\"primary\"\n size=\"small\"\n :prepend-icon=\"allExpanded ? 'mdi-unfold-less-horizontal' : 'mdi-unfold-more-horizontal'\"\n @click=\"toggleExpandAll\"\n >\n {{ allExpanded ? t('common.sortable.collapseAll') : t('common.sortable.expandAll') }}\n </VBtn>\n <slot\n v-if=\"reorderToggleVisible\"\n name=\"reorder-toggle\"\n v-bind=\"reorderToggleSlotProps\"\n >\n <VBtn\n v-if=\"compactReorderButton\"\n variant=\"tonal\"\n color=\"primary\"\n icon\n size=\"x-small\"\n :disabled=\"!canEnterReorder\"\n @click=\"enterReorderMode\"\n >\n <VIcon\n icon=\"mdi-sort\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.reorder')\"\n />\n </VBtn>\n <VBtn\n v-else\n variant=\"tonal\"\n color=\"primary\"\n prepend-icon=\"mdi-sort\"\n size=\"small\"\n :disabled=\"!canEnterReorder\"\n @click=\"enterReorderMode\"\n >\n {{ t('common.sortable.reorder') }}\n </VBtn>\n </slot>\n </template>\n </div>\n </slot>\n </div>\n\n <div\n v-if=\"loading\"\n class=\"a-le-state a-le-state--loading\"\n >\n <VProgressCircular\n indeterminate\n color=\"primary\"\n size=\"32\"\n />\n </div>\n\n <div\n v-else-if=\"error\"\n class=\"a-le-state a-le-state--error\"\n >\n <VAlert\n type=\"error\"\n variant=\"tonal\"\n density=\"compact\"\n border=\"start\"\n >\n {{ error }}\n </VAlert>\n </div>\n\n <div\n v-else-if=\"isEmpty\"\n class=\"a-le-state a-le-state--empty\"\n >\n <slot\n name=\"empty\"\n :readonly=\"readonly\"\n :disabled=\"disabled\"\n :actions=\"{ add: onAddClick }\"\n >\n <LeEmptyState\n :title=\"emptyTitleResolved\"\n :add-label=\"addLabelResolved\"\n :can-add=\"canAdd\"\n @add=\"onAddClick\"\n />\n </slot>\n </div>\n\n <div\n v-else\n :key=\"forceRerender\"\n ref=\"rowsContainer\"\n class=\"a-nested-list-editor__rows\"\n :class=\"{ 'a-nested-list-editor__rows--dragging': dragState !== null }\"\n >\n <div\n :class=\"[GROUP_CLASS, 'a-nested-list-editor__group--root']\"\n data-parent-id=\"\"\n >\n <LeNestedRow\n v-for=\"vi in rootViewItems\"\n :key=\"String(vi.key)\"\n :vi=\"vi\"\n :view-items=\"viewItemsDecorated\"\n :drag-state=\"dragState\"\n :context=\"rowContext\"\n :callbacks=\"rowCallbacks\"\n >\n <template #item=\"slotScope\">\n <slot\n name=\"item\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-compact=\"slotScope\">\n <slot\n name=\"item-compact\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-readonly=\"slotScope\">\n <slot\n name=\"item-readonly\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-status=\"slotScope\">\n <slot\n name=\"item-status\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-footer=\"slotScope\">\n <slot\n name=\"item-footer\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-actions=\"slotScope\">\n <slot\n name=\"item-actions\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #before-item=\"slotScope\">\n <slot\n name=\"before-item\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #after-item=\"slotScope\">\n <slot\n name=\"after-item\"\n v-bind=\"slotScope\"\n />\n </template>\n </LeNestedRow>\n </div>\n\n <!-- Drop indicator overlay (valid drops only): horizontal line at a row\n edge (depth in `left`) + an optional vertical connector rail to the\n row whose level the insert matches. -->\n <template v-if=\"overlayVisual !== null\">\n <div\n v-if=\"overlayVisual.connector !== null\"\n class=\"a-nested-list-editor__drop-connector\"\n :style=\"{\n left: `${overlayVisual.connector.left}px`,\n top: `${overlayVisual.connector.top}px`,\n height: `${overlayVisual.connector.height}px`,\n }\"\n />\n <div\n class=\"a-nested-list-editor__drop-line\"\n :style=\"{\n top: `${overlayVisual.line.top}px`,\n left: `${overlayVisual.line.left}px`,\n right: `${overlayVisual.line.right}px`,\n }\"\n >\n <span class=\"a-nested-list-editor__drop-line-dot\" />\n </div>\n </template>\n </div>\n\n <slot\n v-if=\"canAdd && !loading && !error && !isEmpty\"\n name=\"add-button\"\n :readonly=\"readonly\"\n :disabled=\"disabled\"\n :props=\"{ onClick: onAddClick }\"\n :actions=\"{ add: onAddClick }\"\n >\n <button\n type=\"button\"\n class=\"a-le-row-add\"\n @click=\"onAddClick\"\n >\n <VIcon\n icon=\"mdi-plus\"\n size=\"18\"\n />\n <span>{{ addLabelResolved }}</span>\n </button>\n </slot>\n </div>\n\n <LeMoveToPositionDialog\n v-model=\"moveToPositionDialogOpen\"\n :total=\"moveToPositionContext?.total ?? 0\"\n :current-index=\"moveToPositionContext?.currentIndex ?? 0\"\n :item-label=\"moveToPositionLabel\"\n @confirm=\"onMoveToPositionConfirm\"\n />\n\n <LeChangeParentDialog\n v-model=\"changeParentDialogOpen\"\n :tree=\"modelValue\"\n :source-key=\"changeParentTarget?.key ?? null\"\n :key-field=\"keyFieldName\"\n :max-depth=\"maxDepth\"\n :resolve-label=\"resolveCompactText\"\n :calculate-subtree-depth=\"controller.calculateSubtreeDepth\"\n @confirm=\"onChangeParentConfirm\"\n />\n\n <LeDeleteDialog\n v-model=\"deleteDialog\"\n :title=\"deleteConfirmTitleResolved\"\n :text=\"deleteConfirmTextResolved\"\n :confirm-label=\"t('common.sortable.delete')\"\n :cancel-label=\"t('common.button.cancel')\"\n :error=\"deleteError\"\n :in-flight=\"deleteInFlight\"\n @confirm=\"onDeleteDialogConfirm\"\n @cancel=\"onDeleteDialogCancel\"\n />\n\n <div\n class=\"a-le-sr-only\"\n aria-live=\"polite\"\n role=\"status\"\n >\n {{\n keyboardNav.grabbedKey.value !== null\n ? t('common.sortable.keyboardGrab.status', {\n n: keyboardNav.grabbedIndex.value + 1,\n total: keyboardNav.totalCount.value,\n })\n : ''\n }}\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n@use './styles/tokens';\n@use './styles/shared';\n\n// Nested-variant rules — depth-aware padding, tree toggle caret, children\n// groups, drop indicator overlay, drag clone.\n.a-nested-list-editor {\n // Depth-aware left padding — caret column + indent per depth level.\n .a-le-row-header {\n padding: var(--le-row-pad-y) 12px var(--le-row-pad-y) calc(16px + var(--nested-depth, 0) * var(--le-indent));\n }\n\n // Row wrappers + inter-row group layout.\n &__rows {\n position: relative;\n display: flex;\n flex-direction: column;\n }\n\n &__group {\n display: flex;\n flex-direction: column;\n }\n\n // Re-apply the `font` shorthand (shared class uses longhand for flat variants)\n // so the nested variant keeps its explicit line-height reset.\n .a-le-toolbar-status {\n font: 500 13px/1 var(--v-font-body, inherit);\n }\n}\n\n.a-le-row-wrapper {\n display: flex;\n flex-direction: column;\n}\n\n.a-nested-list-editor {\n // Root rows get a bolder title weight to anchor the hierarchy without header rows.\n .a-le-row:not(.a-le-row--child) .a-le-title {\n font-weight: 600;\n }\n\n // Reorder mode — flat-variant padding offset by the row's indent level.\n .a-le-row--reorder .a-le-row-header {\n padding-left: calc(12px + var(--nested-depth, 0) * var(--le-indent));\n padding-right: 8px;\n gap: 8px;\n }\n\n // Active (editing / readonly-expanded) tree-toggle picks up the primary tint\n // so the header reads \"active\" together with the title color.\n .a-le-row--editing &__tree-toggle,\n .a-le-row--expanded &__tree-toggle {\n color: var(--le-primary);\n }\n\n // Children wrapper — depth conveyed by the row's padding-left indent alone.\n &__children {\n position: relative;\n }\n\n // Triangle-caret toggle — circular text-style button (24×24), transparent\n // with a subtle tinted circle on hover.\n &__tree-toggle {\n width: 24px;\n height: 24px;\n padding: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n border-radius: 50%;\n cursor: pointer;\n pointer-events: auto;\n color: rgb(0 0 0 / 54%);\n z-index: 2;\n flex-shrink: 0;\n transition:\n background 0.15s,\n color 0.15s;\n\n &:hover {\n background: rgb(0 0 0 / 5%);\n color: var(--le-on-surface);\n }\n\n &:focus-visible {\n outline: none;\n background: rgb(0 0 0 / 8%);\n }\n }\n\n // Reserve caret column width on leaf rows so titles align across siblings.\n &__tree-toggle--spacer,\n &__tree-toggle--empty {\n width: 24px;\n height: 24px;\n background: transparent;\n visibility: hidden;\n pointer-events: none;\n }\n\n // Pure CSS right-pointing triangle; rotates to down when the row is open.\n &__tree-toggle-caret {\n width: 0;\n height: 0;\n border-left: 5px solid currentcolor;\n border-top: 4px solid transparent;\n border-bottom: 4px solid transparent;\n border-right: 0;\n transition: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1);\n transform: translateX(1px); // optical centering — triangle leans left\n }\n\n &__tree-toggle--open &__tree-toggle-caret {\n transform: translate(0, 1px) rotate(90deg);\n }\n\n // Dragged subtree (source + descendants) dimmed and non-hittable. `!important`\n // on display beats SortableJS's inline `display: none` under `forceFallback`.\n .a-le-row-wrapper.a-le-row--chosen,\n .a-le-row-wrapper.a-le-row-wrapper--drop-disabled {\n display: flex !important;\n opacity: 0.4 !important;\n pointer-events: none !important;\n }\n\n // Hide SortableJS's placeholder — our overlay is the sole landing indicator.\n .a-le-row--ghost {\n display: none !important;\n }\n\n // No floating clone — the line overlay carries all the \"where it lands\" info.\n // Wrapper selector beats the `.row-wrapper--drop-disabled` rule the clone inherits.\n .a-le-row-wrapper.a-le-row--drag {\n display: none !important;\n }\n\n // Drop indicator line — 2px primary stroke with an 8px left terminal dot,\n // absolute-positioned inside `__rows`.\n &__drop-line {\n position: absolute;\n height: 2px;\n margin-top: -1px;\n background: var(--le-primary);\n pointer-events: none;\n z-index: 4;\n border-radius: 1px;\n }\n\n &__drop-line-dot {\n position: absolute;\n left: -4px;\n top: 50%;\n width: 8px;\n height: 8px;\n background: var(--le-primary);\n border-radius: 50%;\n transform: translateY(-50%);\n }\n\n // Connector rail — vertical line linking the drop line up to the level-match row.\n &__drop-connector {\n position: absolute;\n width: 2px;\n margin-left: -1px;\n background: var(--le-primary);\n opacity: 0.5;\n pointer-events: none;\n z-index: 3;\n }\n\n // While dragging, dim the add-button so focus stays on the drag target.\n &__rows--dragging .a-le-row-add {\n opacity: 0.4;\n }\n\n // \"+N\" children indicator — hidden in the normal DOM, visible only inside the\n // SortableJS drag clone so the user sees the whole branch will follow.\n &__drag-count {\n display: none;\n align-items: center;\n gap: 4px;\n margin-left: 8px;\n padding: 2px 8px;\n font: 500 11px/1 var(--v-font-body, inherit);\n letter-spacing: 0.02em;\n color: var(--le-primary);\n background: var(--le-primary-container);\n border-radius: var(--le-radius-pill);\n flex-shrink: 0;\n }\n\n .a-le-row--drag &__drag-count {\n display: inline-flex;\n }\n}\n\n// Desktop — depth-aware padding-left aligns the inline form with the title column:\n// 16 (pad) + 24 (caret) + 10 (gap) = 50, plus depth*indent.\n@container le-shell (min-width: 769px) {\n .a-nested-list-editor {\n .a-le-row--editing .a-le-row-body,\n .a-le-row--expanded .a-le-row-body,\n .a-le-row--editing .a-le-row-footer,\n .a-le-row--expanded .a-le-row-footer {\n padding-left: calc(50px + var(--nested-depth, 0) * var(--le-indent));\n padding-right: 16px;\n }\n }\n}\n\n// Narrow / mobile — tighter indent so deep branches fit, taller touch rows,\n// always-visible actions, status badge dropped to make room for the title.\n/* stylelint-disable selector-max-compound-selectors */\n@container le-shell (max-width: 768px) {\n .a-nested-list-editor {\n --le-indent: 18px;\n --le-row-min-height: 48px;\n --le-row-pad-y: 10px;\n\n .a-le-row:not(.a-le-row--editing) .a-le-status {\n display: none;\n }\n\n .a-le-row .a-le-action--edit,\n .a-le-row .a-le-action--delete,\n .a-le-row .a-le-action--menu,\n .a-le-row .a-le-action--up,\n .a-le-row .a-le-action--down,\n .a-le-row .a-le-action--add-child {\n opacity: 1;\n }\n }\n}\n/* stylelint-enable selector-max-compound-selectors */\n</style>\n","import type { UseUnsavedChangesGuardApi } from '@/labs/unsavedGuard/useUnsavedChangesGuard'\n\n/**\n * Wrap an entity-delete handler so it bypasses the unsaved-changes leave guard.\n *\n * An entity delete destroys the record together with any unsaved edits, so the \"you have unsaved\n * changes — really leave?\" prompt that the delete's `router.push` would otherwise trigger is\n * meaningless: the user already confirmed the delete, and there is nothing left to keep. Without this\n * the delete runs, then the leave-guard fires on the post-delete navigation and choosing \"stay\" leaves\n * the view on an already-deleted entity (system error). This acknowledges the guard right before the\n * delete so the follow-up navigation proceeds without prompting. (QA 85050 batch 7 — BUG-08)\n *\n * If `onDelete` REJECTS (the delete failed and never navigated), the acknowledgement is undone\n * immediately (`unacknowledge()`) so a subsequently-dirtied navigation is still guarded (M1) — the\n * error is re-thrown so the caller's own handling runs. For this to catch failures, `onDelete` must\n * let its error propagate (don't swallow it in the action's try/catch).\n *\n * Usage: `@delete-record=\"guardedDelete(() => onDelete(id))\"` or wrap the handler once.\n */\nexport function useGuardedDelete<A extends unknown[], R>(\n guard: Pick<UseUnsavedChangesGuardApi, 'acknowledge' | 'unacknowledge'>,\n onDelete: (...args: A) => R\n): (...args: A) => R | Promise<Awaited<R>> {\n return (...args: A): R | Promise<Awaited<R>> => {\n guard.acknowledge()\n let result: R\n try {\n result = onDelete(...args)\n } catch (err) {\n // Synchronous throw before any navigation — disarm and re-throw.\n guard.unacknowledge()\n throw err\n }\n // Async handler: undo the acknowledgement if the delete rejects (no navigation happened).\n if (result instanceof Promise) {\n return (result as Promise<Awaited<R>>).catch((err) => {\n guard.unacknowledge()\n throw err\n })\n }\n return result\n }\n}\n","import { computed, type ComputedRef, type Ref } from 'vue'\nimport type {\n ListEditorKey,\n ListViewItem,\n PositionHint,\n UseListEditorOptions,\n} from '@/labs/listEditor/types/listEditorTypes'\nimport { renumberPositions } from '@/labs/listEditor/utils/positions'\n\nexport interface ListEditorApi<TItem> {\n viewItems: ComputedRef<ListViewItem<TItem>[]>\n addItem: (data: TItem, positionHint?: PositionHint) => TItem[]\n deleteItem: (idOrItem: ListEditorKey | TItem) => TItem[]\n updateItem: (idOrItem: ListEditorKey | TItem, data: TItem) => TItem[]\n moveItem: (fromIndex: number, toIndex: number) => TItem[]\n recalculatePositions: (items: TItem[]) => TItem[]\n}\n\n/**\n * Flat list editor core. Pure data behavior shared by AListEditor and\n * ASortableListEditor. Does not touch the DOM, SortableJS, or emits.\n *\n * The caller passes a writable ref (typically `defineModel<TItem[]>`).\n * Mutators write a new array to `model.value` and also return it.\n * Input items are never mutated — when positions need to change, a new\n * item object is produced with `{ ...item, [positionField]: n }`.\n */\nexport function useListEditor<TItem extends Record<string, any>>(\n model: Ref<TItem[]>,\n options: UseListEditorOptions = {}\n): ListEditorApi<TItem> {\n const keyField = options.keyField ?? 'id'\n const positionField = options.positionField ?? 'position'\n const positionMultiplier = options.positionMultiplier ?? 1\n const updatePositionEnabled = options.updatePosition === true\n\n // Using the position field as the row key while `update-position` is on is\n // self-defeating: every reorder renumbers positions, which changes the keys,\n // so dirty/validity tracking flags the displaced rows as unsaved (QA 85050\n // BUG-09). Point key-field at a stable identity field (e.g. \"id\").\n if (updatePositionEnabled && keyField === positionField) {\n console.warn(\n `[list-editor] key-field === position-field (\"${keyField}\") with update-position enabled. ` +\n 'Reordering renumbers positions and thus changes row keys, breaking dirty/validity ' +\n 'tracking. Point key-field at a stable identity field (e.g. \"id\").'\n )\n }\n\n // Surfaces row-key wiring bugs loudly: rows keyed `undefined` (item lacks the\n // key-field) or two rows sharing one key silently break dirty tracking,\n // validity rails and reorder targeting in ways that are hard to trace from\n // symptoms (QA 85050 BUG-09 class). Deduplicated per signature so a stable\n // bad state warns once, but a new offender warns again.\n const warnedKeySignatures = new Set<string>()\n const warnOnBadKeys = (items: ListViewItem<TItem>[]): void => {\n const seen = new Set<ListEditorKey>()\n const duplicates = new Set<string>()\n let missing = 0\n for (const vi of items) {\n if (vi.key === undefined || vi.key === null) {\n missing++\n continue\n }\n if (seen.has(vi.key)) duplicates.add(String(vi.key))\n seen.add(vi.key)\n }\n if (missing === 0 && duplicates.size === 0) return\n const signature = `${missing}|${[...duplicates].sort().join(',')}`\n if (warnedKeySignatures.has(signature)) return\n warnedKeySignatures.add(signature)\n if (missing > 0) {\n console.warn(\n `[list-editor] ${missing} row(s) resolve to an undefined key (key-field \"${keyField}\"). ` +\n 'Point key-field at a field every item has, or give new items unique temp ids ' +\n '(see nextListEditorTempId).'\n )\n }\n if (duplicates.size > 0) {\n console.warn(\n `[list-editor] duplicate row keys (key-field \"${keyField}\"): ${[...duplicates].join(', ')}. ` +\n 'Row keys must be unique — dirty tracking and validation rails target rows by key.'\n )\n }\n }\n\n const viewItems = computed<ListViewItem<TItem>[]>(() => {\n const items = model.value.map((raw, index) => ({\n key: raw[keyField] as ListEditorKey,\n index,\n raw,\n position: raw[positionField] as number | undefined,\n }))\n warnOnBadKeys(items)\n return items\n })\n\n const recalculatePositions = (items: TItem[]): TItem[] =>\n renumberPositions(items, { positionField, positionMultiplier })\n\n const finalize = (arr: TItem[]): TItem[] => {\n const result = updatePositionEnabled ? recalculatePositions(arr) : arr\n model.value = result\n return result\n }\n\n const isItem = (value: ListEditorKey | TItem): value is TItem => value !== null && typeof value === 'object'\n\n const resolveIndexByKey = (items: TItem[], key: ListEditorKey): number => items.findIndex((x) => x[keyField] === key)\n\n const resolveIndex = (items: TItem[], idOrItem: ListEditorKey | TItem): number => {\n const key = isItem(idOrItem) ? (idOrItem[keyField] as ListEditorKey) : idOrItem\n return resolveIndexByKey(items, key)\n }\n\n const resolveInsertIndex = (items: TItem[], hint?: PositionHint): number => {\n if (!hint) return items.length\n if (hint.afterId !== undefined) {\n const idx = resolveIndexByKey(items, hint.afterId)\n return idx === -1 ? items.length : idx + 1\n }\n if (hint.afterIndex !== undefined) {\n if (hint.afterIndex < 0) return items.length\n return Math.min(hint.afterIndex + 1, items.length)\n }\n if (hint.index !== undefined) {\n return Math.max(0, Math.min(hint.index, items.length))\n }\n return items.length\n }\n\n const addItem = (data: TItem, positionHint?: PositionHint): TItem[] => {\n const arr = [...model.value]\n const insertIndex = resolveInsertIndex(arr, positionHint)\n arr.splice(insertIndex, 0, data)\n return finalize(arr)\n }\n\n const deleteItem = (idOrItem: ListEditorKey | TItem): TItem[] => {\n const idx = resolveIndex(model.value, idOrItem)\n if (idx === -1) return model.value\n const arr = [...model.value]\n arr.splice(idx, 1)\n return finalize(arr)\n }\n\n const updateItem = (idOrItem: ListEditorKey | TItem, data: TItem): TItem[] => {\n const idx = resolveIndex(model.value, idOrItem)\n if (idx === -1) return model.value\n const arr = [...model.value]\n arr[idx] = data\n return finalize(arr)\n }\n\n const moveItem = (fromIndex: number, toIndex: number): TItem[] => {\n const len = model.value.length\n if (fromIndex === toIndex) return model.value\n if (fromIndex < 0 || fromIndex >= len) return model.value\n if (toIndex < 0 || toIndex >= len) return model.value\n const arr = [...model.value]\n const [el] = arr.splice(fromIndex, 1)\n arr.splice(toIndex, 0, el)\n return finalize(arr)\n }\n\n return {\n viewItems,\n addItem,\n deleteItem,\n updateItem,\n moveItem,\n recalculatePositions,\n }\n}\n","import { computed, ref, type ComputedRef, type Ref } from 'vue'\nimport type { ListEditorKey } from '@/labs/listEditor/types/listEditorTypes'\n\nexport interface UseNestedUnsavedKeysApi {\n /** Set of unsaved keys for one parent row — used as the inner editor's `v-model:unsaved-keys`. */\n getForParent: (parentKey: ListEditorKey) => Set<ListEditorKey>\n /** Push a child editor's unsaved-key set up under `parentKey`. */\n setForParent: (parentKey: ListEditorKey, set: Set<ListEditorKey>) => void\n /**\n * Aggregated set with prefix-merged keys (`${parentKey}:${childKey}`) so two\n * different parents can't collide on a shared child key (typical for newly\n * created rows whose `id` is `0` until the API assigns one).\n */\n merged: ComputedRef<Set<ListEditorKey>>\n /** Total count across all parents — handy for the parent v-model:unsaved-keys. */\n count: ComputedRef<number>\n}\n\n/**\n * Aggregates per-parent child-editor unsaved-keys into one prefix-merged set.\n * Used by stacked editors (e.g. quiz: questions → answers) so the outer\n * editor's parent gets a single merged `Set<ListEditorKey>` to thread into a\n * unsaved-changes guard, without losing per-parent identity.\n *\n * Key collision matters: a brand-new question and a brand-new answer both\n * have `id: 0` from the factory; without the prefix the answers from two\n * different questions would dedupe into one entry and the count would\n * under-report. Prefix `${parentKey}:${childKey}` keeps them distinct.\n *\n * Usage:\n * const childSets = useNestedUnsavedKeys()\n * <ChildEditor\n * :unsaved-keys=\"childSets.getForParent(parent.id)\"\n * @update:unsaved-keys=\"(s) => childSets.setForParent(parent.id, s)\"\n * />\n * // emit `childSets.merged.value` upward\n *\n * IMPORTANT: when the parent editor uses `manage-delete`, clear the removed\n * parent's entry on its `@deleted` — `setForParent(deletedKey, new Set())` —\n * otherwise the deleted row's stale child-unsaved keys keep `merged` non-empty\n * and the unsaved-changes guard lingers after an add-then-delete.\n */\nexport function useNestedUnsavedKeys(): UseNestedUnsavedKeysApi {\n const byParent: Ref<Map<ListEditorKey, Set<ListEditorKey>>> = ref(new Map())\n\n const empty = new Set<ListEditorKey>()\n\n const getForParent = (parentKey: ListEditorKey): Set<ListEditorKey> => byParent.value.get(parentKey) ?? empty\n\n const setForParent = (parentKey: ListEditorKey, set: Set<ListEditorKey>) => {\n if (set.size === 0) {\n byParent.value.delete(parentKey)\n } else {\n byParent.value.set(parentKey, set)\n }\n // Trigger reactivity for `merged` — Map mutations are tracked in Vue 3,\n // but we want a defensive re-emit by reassigning on every call so\n // consumers using shallow equality see a new identity.\n byParent.value = new Map(byParent.value)\n }\n\n const merged = computed<Set<ListEditorKey>>(() => {\n const out = new Set<ListEditorKey>()\n for (const [parentKey, set] of byParent.value.entries()) {\n for (const childKey of set) {\n out.add(`${String(parentKey)}:${String(childKey)}`)\n }\n }\n return out\n })\n\n const count = computed<number>(() => merged.value.size)\n\n return { getForParent, setForParent, merged, count }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"labs.js","names":["$attrs","$slots","$slots"],"sources":["../src/labs/filters/AFilterWrapperSidebar.vue","../src/labs/filters/AFilterWrapperSidebar.vue","../src/labs/filters/ADatatableOrdering.vue","../src/labs/filters/ADatatableOrdering.vue","../src/labs/filters/ADatatablePagination.vue","../src/labs/filters/ADatatablePagination.vue","../src/labs/api/useApiFetchListBatch.ts","../src/labs/job/jobApi.ts","../src/labs/filters/datatableColumns.ts","../src/labs/log/logApi.ts","../src/labs/log/logActions.ts","../src/labs/log/logType.ts","../src/labs/log/ALogDatatable.vue","../src/labs/log/ALogDatatable.vue","../src/labs/log/ALogFilter.vue","../src/labs/log/ALogFilter.vue","../src/labs/log/logFilter.ts","../src/labs/log/ALogListView.vue","../src/labs/log/ALogListView.vue","../src/utils/json.ts","../src/labs/log/ALogDetail.vue","../src/labs/log/ALogDetail.vue","../src/labs/log/ALogDetailView.vue","../src/labs/log/ALogDetailView.vue","../src/labs/subjectSelect/useSubjectSelect.ts","../src/labs/subjectSelect/ASubjectSelect.vue","../src/labs/subjectSelect/ASubjectSelect.vue","../src/labs/listEditor/AListEditor.vue","../src/labs/listEditor/AListEditor.vue","../src/labs/listEditor/composables/useDragInstruction.ts","../src/labs/listEditor/internal/LeNestedRow.vue","../src/labs/listEditor/internal/LeNestedRow.vue","../src/labs/listEditor/internal/LeChangeParentDialog.vue","../src/labs/listEditor/internal/LeChangeParentDialog.vue","../src/labs/listEditor/ANestedSortableListEditor.vue","../src/labs/listEditor/ANestedSortableListEditor.vue","../src/labs/unsavedGuard/useGuardedDelete.ts","../src/labs/listEditor/composables/useListEditor.ts","../src/labs/listEditor/composables/useNestedUnsavedKeys.ts"],"sourcesContent":["<script lang=\"ts\" setup>\nimport { inject, provide, ref } from 'vue'\nimport {\n FilterConfigKey,\n FilterDataKey,\n FilterSelectedKey,\n FilterSubmitResetCounterKey,\n} from '@/labs/filters/filterInjectionKeys'\nimport type { ValueObjectOption } from '@/types/ValueObject'\nimport { isUndefined } from '@/utils/common'\nimport { useFilterClearHelpers } from '@/labs/filters/filterFactory'\n\nwithDefaults(\n defineProps<{\n formName?: string\n }>(),\n {\n formName: 'search',\n }\n)\nconst emit = defineEmits<{\n (e: 'submit'): void\n}>()\n\nconst filterConfig = inject(FilterConfigKey)\nconst filterData = inject(FilterDataKey)\nif (isUndefined(filterConfig) || isUndefined(filterData)) {\n throw new Error('Incorrect provide/inject config.')\n}\n\nprovide(FilterSubmitResetCounterKey, ref(0))\nconst filterSelected = ref<Map<string, ValueObjectOption<string | number>[]>>(new Map())\nprovide(FilterSelectedKey, filterSelected)\n\nconst { clearAll, clearAllFilterSelected } = useFilterClearHelpers()\n\nconst submitFilter = () => {\n emit('submit')\n}\n\n// AFilterWrapperSidebar has no reset button of its own (the consumer renders it, e.g. in the drawer\n// footer). Expose the same reset AFilterWrapper does so that button also clears the filter values and\n// the selected-filter chips.\nconst resetFilter = () => {\n clearAll(filterData, filterConfig)\n clearAllFilterSelected(filterData, filterConfig, filterSelected)\n}\n\ndefineExpose({ resetFilter })\n</script>\n\n<template>\n <VForm\n :name=\"formName\"\n @submit.prevent=\"submitFilter\"\n @keyup.enter=\"submitFilter\"\n >\n <slot />\n </VForm>\n</template>\n","<script lang=\"ts\" setup>\nimport { inject, provide, ref } from 'vue'\nimport {\n FilterConfigKey,\n FilterDataKey,\n FilterSelectedKey,\n FilterSubmitResetCounterKey,\n} from '@/labs/filters/filterInjectionKeys'\nimport type { ValueObjectOption } from '@/types/ValueObject'\nimport { isUndefined } from '@/utils/common'\nimport { useFilterClearHelpers } from '@/labs/filters/filterFactory'\n\nwithDefaults(\n defineProps<{\n formName?: string\n }>(),\n {\n formName: 'search',\n }\n)\nconst emit = defineEmits<{\n (e: 'submit'): void\n}>()\n\nconst filterConfig = inject(FilterConfigKey)\nconst filterData = inject(FilterDataKey)\nif (isUndefined(filterConfig) || isUndefined(filterData)) {\n throw new Error('Incorrect provide/inject config.')\n}\n\nprovide(FilterSubmitResetCounterKey, ref(0))\nconst filterSelected = ref<Map<string, ValueObjectOption<string | number>[]>>(new Map())\nprovide(FilterSelectedKey, filterSelected)\n\nconst { clearAll, clearAllFilterSelected } = useFilterClearHelpers()\n\nconst submitFilter = () => {\n emit('submit')\n}\n\n// AFilterWrapperSidebar has no reset button of its own (the consumer renders it, e.g. in the drawer\n// footer). Expose the same reset AFilterWrapper does so that button also clears the filter values and\n// the selected-filter chips.\nconst resetFilter = () => {\n clearAll(filterData, filterConfig)\n clearAllFilterSelected(filterData, filterConfig, filterSelected)\n}\n\ndefineExpose({ resetFilter })\n</script>\n\n<template>\n <VForm\n :name=\"formName\"\n @submit.prevent=\"submitFilter\"\n @keyup.enter=\"submitFilter\"\n >\n <slot />\n </VForm>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed, inject, type Ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport {\n type DatatableOrderingOption,\n type DatatableOrderingOptions,\n SORT_BY_SCORE,\n SortOrder,\n} from '@/composables/system/datatableColumns'\nimport { isUndefined } from '@/utils/common'\nimport { DatatablePaginationKey } from '@/labs/filters/filterInjectionKeys'\n\nimport type { Pagination } from '@/labs/filters/pagination'\n\nconst props = withDefaults(\n defineProps<{\n variant?: 'id' | 'createdAt' | 'most-relevant'\n customOptions?: undefined | DatatableOrderingOptions\n paginationUpdateCustomCb?: ((option: DatatableOrderingOption, pagination: Ref<Pagination>) => void) | undefined\n }>(),\n {\n variant: 'id',\n customOptions: undefined,\n paginationUpdateCustomCb: undefined,\n }\n)\nconst emit = defineEmits<{\n (e: 'sortByChange', data: DatatableOrderingOption): void\n}>()\n\nconst modelValue = defineModel<number>('modelValue', {\n default: 1,\n required: false,\n})\n\nconst pagination = inject(DatatablePaginationKey)\n\nif (isUndefined(pagination)) {\n throw new Error('Incorrect provide/inject config.')\n}\n\nconst { t } = useI18n()\n\nconst defaultItemsCreatedAt: DatatableOrderingOptions = [\n {\n id: 1,\n titleT: 'common.system.datatable.ordering.mostRecent',\n sortBy: { key: 'createdAt', order: SortOrder.Desc },\n },\n {\n id: 2,\n titleT: 'common.system.datatable.ordering.oldest',\n sortBy: { key: 'createdAt', order: SortOrder.Asc },\n },\n]\n\nconst defaultItemsId: DatatableOrderingOptions = [\n {\n id: 1,\n titleT: 'common.system.datatable.ordering.mostRecent',\n sortBy: { key: 'id', order: SortOrder.Desc },\n },\n {\n id: 2,\n titleT: 'common.system.datatable.ordering.oldest',\n sortBy: { key: 'id', order: SortOrder.Asc },\n },\n]\n\nconst defaultItemsMostRelevant: DatatableOrderingOptions = [\n {\n id: 3,\n titleT: 'common.system.datatable.ordering.mostRelevant',\n sortBy: { key: SORT_BY_SCORE, order: SortOrder.Desc },\n },\n {\n id: 1,\n titleT: 'common.system.datatable.ordering.mostRecent',\n sortBy: { key: 'createdAt', order: SortOrder.Desc },\n },\n {\n id: 2,\n titleT: 'common.system.datatable.ordering.oldest',\n sortBy: { key: 'createdAt', order: SortOrder.Asc },\n },\n]\n\nconst activeTitle = computed(() => {\n const found = options.value.find((item: any) => item.id === modelValue.value)\n if (found) return t(found.titleT)\n return ''\n})\n\nconst options = computed(() => {\n if (props.customOptions) return props.customOptions\n if (props.variant === 'most-relevant') return defaultItemsMostRelevant\n if (props.variant === 'id') return defaultItemsId\n return defaultItemsCreatedAt\n})\n\nconst onItemClick = (item: DatatableOrderingOption) => {\n modelValue.value = item.id\n}\n\nconst paginationUpdateDefault = (found: DatatableOrderingOption) => {\n if (!found.sortBy) {\n pagination.value = {\n ...pagination.value,\n sortBy: null,\n }\n emit('sortByChange', found)\n return\n }\n pagination.value.sortBy = found.sortBy\n emit('sortByChange', found)\n}\n\nwatch(modelValue, (newValue, oldValue) => {\n if (newValue === oldValue) return\n const found = options.value.find((option) => {\n return option.id === newValue\n })\n if (isUndefined(found)) {\n return\n }\n if (isUndefined(props.paginationUpdateCustomCb)) {\n paginationUpdateDefault(found)\n return\n }\n props.paginationUpdateCustomCb(found, pagination)\n})\n\nwatch(\n pagination,\n (newValue, oldValue) => {\n if (newValue.sortBy?.key === oldValue?.sortBy?.key && newValue.sortBy?.order === oldValue?.sortBy?.order) {\n return\n }\n const found = options.value.find((option) => {\n return newValue.sortBy?.key === option.sortBy?.key && newValue.sortBy?.order === option.sortBy?.order\n })\n if (isUndefined(found)) {\n return\n }\n modelValue.value = found.id\n },\n { immediate: true }\n)\n</script>\n\n<template>\n <div class=\"d-flex align-center justify-center\">\n <div class=\"text-body-small mr-1\">{{ t('common.system.datatable.ordering.title') }}:</div>\n <VBtn\n variant=\"text\"\n rounded=\"xl\"\n size=\"small\"\n class=\"text-medium-emphasis\"\n append-icon=\"mdi-chevron-down\"\n >\n {{ activeTitle }}\n <VMenu\n activator=\"parent\"\n location=\"bottom\"\n close-on-content-click\n >\n <VList\n density=\"compact\"\n class=\"pa-0\"\n >\n <VListItem\n v-for=\"item in options\"\n :key=\"item.id\"\n @click=\"onItemClick(item)\"\n >\n <VListItemTitle :class=\"{ 'font-weight-bold': item.id === modelValue }\">\n {{ t(item.titleT) }}\n </VListItemTitle>\n </VListItem>\n </VList>\n </VMenu>\n </VBtn>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed, inject, type Ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport {\n type DatatableOrderingOption,\n type DatatableOrderingOptions,\n SORT_BY_SCORE,\n SortOrder,\n} from '@/composables/system/datatableColumns'\nimport { isUndefined } from '@/utils/common'\nimport { DatatablePaginationKey } from '@/labs/filters/filterInjectionKeys'\n\nimport type { Pagination } from '@/labs/filters/pagination'\n\nconst props = withDefaults(\n defineProps<{\n variant?: 'id' | 'createdAt' | 'most-relevant'\n customOptions?: undefined | DatatableOrderingOptions\n paginationUpdateCustomCb?: ((option: DatatableOrderingOption, pagination: Ref<Pagination>) => void) | undefined\n }>(),\n {\n variant: 'id',\n customOptions: undefined,\n paginationUpdateCustomCb: undefined,\n }\n)\nconst emit = defineEmits<{\n (e: 'sortByChange', data: DatatableOrderingOption): void\n}>()\n\nconst modelValue = defineModel<number>('modelValue', {\n default: 1,\n required: false,\n})\n\nconst pagination = inject(DatatablePaginationKey)\n\nif (isUndefined(pagination)) {\n throw new Error('Incorrect provide/inject config.')\n}\n\nconst { t } = useI18n()\n\nconst defaultItemsCreatedAt: DatatableOrderingOptions = [\n {\n id: 1,\n titleT: 'common.system.datatable.ordering.mostRecent',\n sortBy: { key: 'createdAt', order: SortOrder.Desc },\n },\n {\n id: 2,\n titleT: 'common.system.datatable.ordering.oldest',\n sortBy: { key: 'createdAt', order: SortOrder.Asc },\n },\n]\n\nconst defaultItemsId: DatatableOrderingOptions = [\n {\n id: 1,\n titleT: 'common.system.datatable.ordering.mostRecent',\n sortBy: { key: 'id', order: SortOrder.Desc },\n },\n {\n id: 2,\n titleT: 'common.system.datatable.ordering.oldest',\n sortBy: { key: 'id', order: SortOrder.Asc },\n },\n]\n\nconst defaultItemsMostRelevant: DatatableOrderingOptions = [\n {\n id: 3,\n titleT: 'common.system.datatable.ordering.mostRelevant',\n sortBy: { key: SORT_BY_SCORE, order: SortOrder.Desc },\n },\n {\n id: 1,\n titleT: 'common.system.datatable.ordering.mostRecent',\n sortBy: { key: 'createdAt', order: SortOrder.Desc },\n },\n {\n id: 2,\n titleT: 'common.system.datatable.ordering.oldest',\n sortBy: { key: 'createdAt', order: SortOrder.Asc },\n },\n]\n\nconst activeTitle = computed(() => {\n const found = options.value.find((item: any) => item.id === modelValue.value)\n if (found) return t(found.titleT)\n return ''\n})\n\nconst options = computed(() => {\n if (props.customOptions) return props.customOptions\n if (props.variant === 'most-relevant') return defaultItemsMostRelevant\n if (props.variant === 'id') return defaultItemsId\n return defaultItemsCreatedAt\n})\n\nconst onItemClick = (item: DatatableOrderingOption) => {\n modelValue.value = item.id\n}\n\nconst paginationUpdateDefault = (found: DatatableOrderingOption) => {\n if (!found.sortBy) {\n pagination.value = {\n ...pagination.value,\n sortBy: null,\n }\n emit('sortByChange', found)\n return\n }\n pagination.value.sortBy = found.sortBy\n emit('sortByChange', found)\n}\n\nwatch(modelValue, (newValue, oldValue) => {\n if (newValue === oldValue) return\n const found = options.value.find((option) => {\n return option.id === newValue\n })\n if (isUndefined(found)) {\n return\n }\n if (isUndefined(props.paginationUpdateCustomCb)) {\n paginationUpdateDefault(found)\n return\n }\n props.paginationUpdateCustomCb(found, pagination)\n})\n\nwatch(\n pagination,\n (newValue, oldValue) => {\n if (newValue.sortBy?.key === oldValue?.sortBy?.key && newValue.sortBy?.order === oldValue?.sortBy?.order) {\n return\n }\n const found = options.value.find((option) => {\n return newValue.sortBy?.key === option.sortBy?.key && newValue.sortBy?.order === option.sortBy?.order\n })\n if (isUndefined(found)) {\n return\n }\n modelValue.value = found.id\n },\n { immediate: true }\n)\n</script>\n\n<template>\n <div class=\"d-flex align-center justify-center\">\n <div class=\"text-body-small mr-1\">{{ t('common.system.datatable.ordering.title') }}:</div>\n <VBtn\n variant=\"text\"\n rounded=\"xl\"\n size=\"small\"\n class=\"text-medium-emphasis\"\n append-icon=\"mdi-chevron-down\"\n >\n {{ activeTitle }}\n <VMenu\n activator=\"parent\"\n location=\"bottom\"\n close-on-content-click\n >\n <VList\n density=\"compact\"\n class=\"pa-0\"\n >\n <VListItem\n v-for=\"item in options\"\n :key=\"item.id\"\n @click=\"onItemClick(item)\"\n >\n <VListItemTitle :class=\"{ 'font-weight-bold': item.id === modelValue }\">\n {{ t(item.titleT) }}\n </VListItemTitle>\n </VListItem>\n </VList>\n </VMenu>\n </VBtn>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed, inject, watch } from 'vue'\nimport { isNull, isUndefined } from '@/utils/common'\nimport { useI18n } from 'vue-i18n'\nimport { DatatablePageStoreKey, DatatablePaginationKey, FilterConfigKey } from '@/labs/filters/filterInjectionKeys'\nimport { useThrottleFn } from '@vueuse/core'\nimport { datatablePageKey, useDatatablePageStore } from '@/composables/system/datatablePageStore'\n\nwithDefaults(\n defineProps<{\n itemsPerPageOptions?: number[]\n hideRecordsPerPage?: boolean\n }>(),\n {\n itemsPerPageOptions: () => [10, 25, 50],\n hideRecordsPerPage: false,\n }\n)\nconst emit = defineEmits<{\n (e: 'change'): void\n}>()\n\nconst pagination = inject(DatatablePaginationKey)\n\nif (isUndefined(pagination)) {\n throw new Error('Incorrect provide/inject config.')\n}\n\nconst { t } = useI18n()\nconst { setStoredPage } = useDatatablePageStore()\n\n// `useFilterHelpers` provides the key, so tables sharing a system/subject stay apart. Both\n// fallbacks are optional on purpose: a datatable may provide pagination without either, and it\n// then keeps the shared bucket it had before the store was keyed.\nconst providedPageKey = inject(DatatablePageStoreKey, undefined)\nconst filterConfig = inject(FilterConfigKey, undefined)\nconst pageStoreKey = computed(\n () => providedPageKey ?? datatablePageKey(filterConfig?.general.system, filterConfig?.general.subject)\n)\n\nconst lastPage = computed(() => {\n return Math.ceil(pagination.value.totalCount / pagination.value.rowsPerPage)\n})\n\nconst displayedFrom = computed(() => {\n return pagination.value.page * pagination.value.rowsPerPage - pagination.value.rowsPerPage + 1\n})\n\nconst displayedTo = computed(() => {\n return (\n pagination.value.page * pagination.value.rowsPerPage -\n pagination.value.rowsPerPage +\n pagination.value.currentViewCount\n )\n})\n\nconst disabledFirstAndPrev = computed(() => {\n return pagination.value.page === 1\n})\n\n// `>=`, not `===`: an empty list has `totalCount: 0`, so `lastPage` is 0 while `page` is never below\n// 1, and the two can never meet. The page would then count as \"not the last one\" on a list with\n// nothing in it -- and the same holds before any request has been made, because that initial state is\n// the same numbers.\nconst disabledLast = computed(() => {\n return !isNull(pagination.value.hasNextPage) || pagination.value.page >= lastPage.value\n})\n\nconst disabledNext = computed(() => {\n return (\n (isNull(pagination.value.hasNextPage) && pagination.value.page >= lastPage.value) ||\n pagination.value.hasNextPage === false\n )\n})\n\nconst computedTotalCountText = computed(() => {\n if (!isNull(pagination.value.hasNextPage)) {\n return pagination.value.hasNextPage ? displayedTo.value + 1 + '+' : displayedTo.value\n }\n return pagination.value.totalCount\n})\n\nwatch(\n () => pagination.value.rowsPerPage,\n (newValue, oldValue) => {\n if (newValue !== oldValue) {\n pagination.value.page = 1\n emit('change')\n }\n }\n)\n\nwatch(\n () => pagination.value.page,\n (newValue, oldValue) => {\n if (newValue !== oldValue) {\n setStoredPage(pageStoreKey.value, newValue)\n emit('change')\n }\n }\n)\n\n// Leading edge only: the throttle is a click-spam guard, not a rate limiter. VueUse 15 flipped\n// `trailing` to true by default, which replays the last click once the window closes -- stepping\n// prev/next a second time, and past `lastPage`, because the queued callback never re-reads the\n// disabled state.\nconst onClickFirst = useThrottleFn(\n () => {\n pagination.value.page = 1\n },\n 300,\n false\n)\n\nconst onClickLast = useThrottleFn(\n () => {\n pagination.value.page = lastPage.value\n },\n 300,\n false\n)\n\nconst onClickPrev = useThrottleFn(\n () => {\n pagination.value.page = pagination.value.page - 1\n },\n 300,\n false\n)\n\nconst onClickNext = useThrottleFn(\n () => {\n pagination.value.page = pagination.value.page + 1\n },\n 300,\n false\n)\n</script>\n\n<template>\n <div class=\"anzu-data-footer\">\n <div\n v-if=\"!hideRecordsPerPage\"\n class=\"anzu-data-footer__page-limit\"\n >\n {{ t('common.system.datatable.itemsPerPage') }}:\n <VBtnToggle\n v-model=\"pagination.rowsPerPage\"\n class=\"ml-2\"\n density=\"compact\"\n mandatory\n data-cy=\"table-size\"\n >\n <VBtn\n v-for=\"item in itemsPerPageOptions\"\n :key=\"item\"\n :color=\"item === pagination.rowsPerPage ? 'secondary' : ''\"\n :value=\"item\"\n density=\"compact\"\n size=\"small\"\n variant=\"text\"\n >\n {{ item }}\n </VBtn>\n </VBtnToggle>\n </div>\n <div class=\"anzu-data-footer__pagination\">\n {{ displayedFrom }} - {{ displayedTo }} {{ t('common.system.datatable.from') }}\n {{ computedTotalCountText }}\n </div>\n <div class=\"anzu-data-footer__icons-before\">\n <VBtn\n :disabled=\"disabledFirstAndPrev\"\n icon=\"mdi-page-first\"\n size=\"small\"\n variant=\"text\"\n @click.stop=\"onClickFirst\"\n />\n <VBtn\n :disabled=\"disabledFirstAndPrev\"\n icon=\"mdi-chevron-left\"\n size=\"small\"\n variant=\"text\"\n @click.stop=\"onClickPrev\"\n />\n </div>\n <div class=\"current-page\">\n <span>{{ pagination.page }}</span>\n </div>\n <div class=\"anzu-data-footer__icons-after\">\n <VBtn\n :disabled=\"disabledNext\"\n icon=\"mdi-chevron-right\"\n size=\"small\"\n variant=\"text\"\n @click.stop=\"onClickNext\"\n />\n <VBtn\n :disabled=\"disabledLast\"\n icon=\"mdi-page-last\"\n size=\"small\"\n variant=\"text\"\n @click.stop=\"onClickLast\"\n />\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n.anzu-data-footer {\n margin-top: 10px;\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n align-items: center;\n font-size: 0.75rem;\n padding: 0 8px;\n\n > div.current-page {\n padding-left: 10px;\n padding-right: 10px;\n user-select: none;\n min-width: 40px;\n display: flex;\n text-align: center;\n align-items: center;\n justify-content: center;\n }\n\n &__page-limit {\n display: flex;\n align-items: center;\n flex: 0 0 0;\n justify-content: flex-end;\n white-space: nowrap;\n margin-right: 14px;\n }\n\n &__pagination {\n display: block;\n text-align: center;\n margin: 0 32px 0 24px;\n }\n}\n</style>\n","<script lang=\"ts\" setup>\nimport { computed, inject, watch } from 'vue'\nimport { isNull, isUndefined } from '@/utils/common'\nimport { useI18n } from 'vue-i18n'\nimport { DatatablePageStoreKey, DatatablePaginationKey, FilterConfigKey } from '@/labs/filters/filterInjectionKeys'\nimport { useThrottleFn } from '@vueuse/core'\nimport { datatablePageKey, useDatatablePageStore } from '@/composables/system/datatablePageStore'\n\nwithDefaults(\n defineProps<{\n itemsPerPageOptions?: number[]\n hideRecordsPerPage?: boolean\n }>(),\n {\n itemsPerPageOptions: () => [10, 25, 50],\n hideRecordsPerPage: false,\n }\n)\nconst emit = defineEmits<{\n (e: 'change'): void\n}>()\n\nconst pagination = inject(DatatablePaginationKey)\n\nif (isUndefined(pagination)) {\n throw new Error('Incorrect provide/inject config.')\n}\n\nconst { t } = useI18n()\nconst { setStoredPage } = useDatatablePageStore()\n\n// `useFilterHelpers` provides the key, so tables sharing a system/subject stay apart. Both\n// fallbacks are optional on purpose: a datatable may provide pagination without either, and it\n// then keeps the shared bucket it had before the store was keyed.\nconst providedPageKey = inject(DatatablePageStoreKey, undefined)\nconst filterConfig = inject(FilterConfigKey, undefined)\nconst pageStoreKey = computed(\n () => providedPageKey ?? datatablePageKey(filterConfig?.general.system, filterConfig?.general.subject)\n)\n\nconst lastPage = computed(() => {\n return Math.ceil(pagination.value.totalCount / pagination.value.rowsPerPage)\n})\n\nconst displayedFrom = computed(() => {\n return pagination.value.page * pagination.value.rowsPerPage - pagination.value.rowsPerPage + 1\n})\n\nconst displayedTo = computed(() => {\n return (\n pagination.value.page * pagination.value.rowsPerPage -\n pagination.value.rowsPerPage +\n pagination.value.currentViewCount\n )\n})\n\nconst disabledFirstAndPrev = computed(() => {\n return pagination.value.page === 1\n})\n\n// `>=`, not `===`: an empty list has `totalCount: 0`, so `lastPage` is 0 while `page` is never below\n// 1, and the two can never meet. The page would then count as \"not the last one\" on a list with\n// nothing in it -- and the same holds before any request has been made, because that initial state is\n// the same numbers.\nconst disabledLast = computed(() => {\n return !isNull(pagination.value.hasNextPage) || pagination.value.page >= lastPage.value\n})\n\nconst disabledNext = computed(() => {\n return (\n (isNull(pagination.value.hasNextPage) && pagination.value.page >= lastPage.value) ||\n pagination.value.hasNextPage === false\n )\n})\n\nconst computedTotalCountText = computed(() => {\n if (!isNull(pagination.value.hasNextPage)) {\n return pagination.value.hasNextPage ? displayedTo.value + 1 + '+' : displayedTo.value\n }\n return pagination.value.totalCount\n})\n\nwatch(\n () => pagination.value.rowsPerPage,\n (newValue, oldValue) => {\n if (newValue !== oldValue) {\n pagination.value.page = 1\n emit('change')\n }\n }\n)\n\nwatch(\n () => pagination.value.page,\n (newValue, oldValue) => {\n if (newValue !== oldValue) {\n setStoredPage(pageStoreKey.value, newValue)\n emit('change')\n }\n }\n)\n\n// Leading edge only: the throttle is a click-spam guard, not a rate limiter. VueUse 15 flipped\n// `trailing` to true by default, which replays the last click once the window closes -- stepping\n// prev/next a second time, and past `lastPage`, because the queued callback never re-reads the\n// disabled state.\nconst onClickFirst = useThrottleFn(\n () => {\n pagination.value.page = 1\n },\n 300,\n false\n)\n\nconst onClickLast = useThrottleFn(\n () => {\n pagination.value.page = lastPage.value\n },\n 300,\n false\n)\n\nconst onClickPrev = useThrottleFn(\n () => {\n pagination.value.page = pagination.value.page - 1\n },\n 300,\n false\n)\n\nconst onClickNext = useThrottleFn(\n () => {\n pagination.value.page = pagination.value.page + 1\n },\n 300,\n false\n)\n</script>\n\n<template>\n <div class=\"anzu-data-footer\">\n <div\n v-if=\"!hideRecordsPerPage\"\n class=\"anzu-data-footer__page-limit\"\n >\n {{ t('common.system.datatable.itemsPerPage') }}:\n <VBtnToggle\n v-model=\"pagination.rowsPerPage\"\n class=\"ml-2\"\n density=\"compact\"\n mandatory\n data-cy=\"table-size\"\n >\n <VBtn\n v-for=\"item in itemsPerPageOptions\"\n :key=\"item\"\n :color=\"item === pagination.rowsPerPage ? 'secondary' : ''\"\n :value=\"item\"\n density=\"compact\"\n size=\"small\"\n variant=\"text\"\n >\n {{ item }}\n </VBtn>\n </VBtnToggle>\n </div>\n <div class=\"anzu-data-footer__pagination\">\n {{ displayedFrom }} - {{ displayedTo }} {{ t('common.system.datatable.from') }}\n {{ computedTotalCountText }}\n </div>\n <div class=\"anzu-data-footer__icons-before\">\n <VBtn\n :disabled=\"disabledFirstAndPrev\"\n icon=\"mdi-page-first\"\n size=\"small\"\n variant=\"text\"\n @click.stop=\"onClickFirst\"\n />\n <VBtn\n :disabled=\"disabledFirstAndPrev\"\n icon=\"mdi-chevron-left\"\n size=\"small\"\n variant=\"text\"\n @click.stop=\"onClickPrev\"\n />\n </div>\n <div class=\"current-page\">\n <span>{{ pagination.page }}</span>\n </div>\n <div class=\"anzu-data-footer__icons-after\">\n <VBtn\n :disabled=\"disabledNext\"\n icon=\"mdi-chevron-right\"\n size=\"small\"\n variant=\"text\"\n @click.stop=\"onClickNext\"\n />\n <VBtn\n :disabled=\"disabledLast\"\n icon=\"mdi-page-last\"\n size=\"small\"\n variant=\"text\"\n @click.stop=\"onClickLast\"\n />\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n.anzu-data-footer {\n margin-top: 10px;\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n align-items: center;\n font-size: 0.75rem;\n padding: 0 8px;\n\n > div.current-page {\n padding-left: 10px;\n padding-right: 10px;\n user-select: none;\n min-width: 40px;\n display: flex;\n text-align: center;\n align-items: center;\n justify-content: center;\n }\n\n &__page-limit {\n display: flex;\n align-items: center;\n flex: 0 0 0;\n justify-content: flex-end;\n white-space: nowrap;\n margin-right: 14px;\n }\n\n &__pagination {\n display: block;\n text-align: center;\n margin: 0 32px 0 24px;\n }\n}\n</style>\n","import { AnzuFatalError } from '@/model/error/AnzuFatalError'\nimport { AnzuApiResponseCodeError } from '@/model/error/AnzuApiResponseCodeError'\nimport { replaceUrlParameters, type UrlParams } from '@/services/api/apiHelper'\nimport { isValidHTTPStatus } from '@/utils/response'\nimport type { AxiosRequestConfig, AxiosResponse } from 'axios'\nimport { HTTP_STATUS_NO_CONTENT } from '@/composables/statusCodes'\nimport type { FilterConfig, FilterData } from '@/labs/filters/filterFactory'\nimport { type Ref, ref } from 'vue'\nimport { isDefined, isUndefined } from '@/utils/common'\nimport { type Pagination, usePagination } from '@/labs/filters/pagination'\nimport { SortOrder } from '@/composables/system/datatableColumns'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport { generateListQuery } from '@/labs/api/useApiFetchList'\nimport { mapApiError, report } from '@/labs/api/apiErrors'\nimport { createAbortable, hasBody, ownedByHelper, requestedUrl } from '@/labs/api/request'\nimport { readListBody } from '@/labs/api/listBody'\n\nexport type UseApiFetchListBatchParams = {\n client: AxiosClientFn\n system: string\n entity: string\n urlTemplate?: string\n urlParams?: UrlParams\n /** Anything axios takes except what this helper decides: the method, the url, the body and the signal. */\n options?: Omit<AxiosRequestConfig, 'method' | 'url' | 'data' | 'signal'>\n cancelPrevious?: boolean\n}\n\nexport type FetchListBatchParams = {\n /** Stops this one call, leaving the instance's other calls alone. */\n signal?: AbortSignal\n urlTemplate?: string\n urlParams?: UrlParams\n sortBy?: string\n sortDesc?: boolean\n batchSize?: number\n forceElastic?: boolean\n}\n\n/**\n * Carries the url of the page a failure happened on out to the catch that reports it.\n *\n * Nothing outside this file sees it: the catch unwraps it before mapping, so the caller gets the\n * same classes it would get from any other helper.\n */\nclass PageFailure extends Error {\n constructor(\n readonly pageUrl: string,\n readonly failure: unknown,\n /**\n * The config axios used for this page, when the page answered and it was this helper that\n * refused what came back. A page that failed in transport needs none: axios kept its own.\n */\n readonly dispatched?: AxiosRequestConfig\n ) {\n super('Batch page failed')\n this.name = 'PageFailure'\n }\n}\n\nexport const useApiFetchListBatch = <T>(params: UseApiFetchListBatchParams): UseApiFetchListBatchReturnType<T> => {\n const { client, system, entity, urlTemplate, urlParams, options = {}, cancelPrevious = false } = params\n const abortable = createAbortable({ cancelPrevious })\n\n const execute = async (\n filterData: FilterData<any>,\n filterConfig: FilterConfig<any>,\n fetchParams: FetchListBatchParams = {}\n ): Promise<T[]> => {\n const {\n urlTemplate: urlTemplateOverride,\n urlParams: urlParamsOverride,\n sortBy = 'id',\n sortDesc = true,\n batchSize = 100,\n forceElastic = false,\n signal,\n } = fetchParams\n\n try {\n return await abortable.run(async (abortSignal) => {\n // Derived inside `run`, the way the list does it: `run` opens the generation and, with\n // `cancelPrevious`, stops the call before this one, so a call that throws on a missing\n // template or a malformed filter config would otherwise leave its predecessor running.\n // Inside the try as well, so that failure arrives as the same error class as every other.\n const template = isDefined(urlTemplateOverride) ? urlTemplateOverride : urlTemplate\n if (isUndefined(template)) throw new AnzuFatalError(undefined, 'Url template is undefined')\n\n // A page size below one cannot end the walk: every page is then \"not shorter than the one\n // asked for\", so the loop has no exit and the helper becomes an unbounded stream of\n // requests. It is the caller's mistake, and it arrives as the same error class as the\n // caller's other mistakes rather than as a backend that never stops answering.\n if (batchSize < 1) throw new AnzuFatalError(undefined, 'batchSize must be at least 1, got ' + batchSize)\n\n const resolvedParams = isDefined(urlParamsOverride) ? urlParamsOverride : urlParams\n const searchApi = filterConfig.general.elastic || forceElastic ? '/search' : ''\n const baseUrl =\n (isUndefined(resolvedParams) ? template : replaceUrlParameters(template, resolvedParams)) + searchApi\n // One instance for the whole batch: a call is one call, and a factory that answers\n // differently per page would have its pages talking to two backends.\n const instance = client()\n\n const { pagination } = usePagination(sortBy, sortDesc ? SortOrder.Desc : SortOrder.Asc, {\n rowsPerPage: batchSize,\n })\n\n // The page's own url travels with its response: a batch that failed on page three has to\n // be able to say which page that was, and the base url cannot.\n const get = async (page: Ref<Pagination>) => {\n const pageUrl = baseUrl + generateListQuery(page, filterData, filterConfig)\n\n try {\n return { res: await instance.get(pageUrl, { ...ownedByHelper(options), signal: abortSignal }), pageUrl }\n } catch (err: unknown) {\n // Tagged here, where the page is still known. A shared variable cannot do this job: the\n // counted branch dispatches every page before any of them answers, so it would always\n // hold the last one and name the wrong page for the one that actually failed.\n throw new PageFailure(pageUrl, err)\n }\n }\n\n // The url the report will name for this page, so a message and the context beside it cannot\n // disagree about which request failed when an interceptor rewrote it on the way out.\n const asSent = (res: AxiosResponse, pageUrl: string) =>\n requestedUrl(client, pageUrl, options, undefined, res.config)\n\n // Every page is read the same way, the ones after the first included: a 2xx with no body at\n // all has broken its contract, wherever in the sequence it sits -- a server that fails on\n // page three is not \"finished\". The 204 branch is defensive rather than expected: these\n // backends answer an empty list as a 200 with a body, so a list never arrives as a 204, and\n // the branch is here for an endpoint that one day does.\n\n const readPage = (res: AxiosResponse, pageUrl: string) => {\n if (!isValidHTTPStatus(res.status)) {\n throw new PageFailure(pageUrl, new AnzuApiResponseCodeError(res.status), res.config)\n }\n if (res.status === HTTP_STATUS_NO_CONTENT) {\n return {\n items: [] as T[],\n mode: 'unknown' as const,\n totalCount: 0,\n hasNextPage: false,\n countIsBound: false,\n empty: true,\n }\n }\n if (!hasBody(res)) {\n throw new PageFailure(\n pageUrl,\n new AnzuApiResponseCodeError(\n res.status,\n undefined,\n 'Expected a response body, url: ' + asSent(res, pageUrl)\n ),\n res.config\n )\n }\n\n let body\n try {\n body = readListBody<T>(res.data, res.status, asSent(res, pageUrl))\n } catch (err: unknown) {\n throw new PageFailure(pageUrl, err, res.config)\n }\n\n return {\n items: body.items,\n mode: body.mode,\n totalCount: body.pagination.totalCount ?? 0,\n hasNextPage: body.pagination.hasNextPage === true,\n countIsBound: body.countIsBound,\n empty: false,\n }\n }\n\n const firstPage = await get(pagination)\n const first = readPage(firstPage.res, firstPage.pageUrl)\n const results: T[] = [...first.items]\n if (first.empty) return results\n\n // A first page shorter than the one asked for means the offsets cannot be trusted: every page\n // after it is addressed by arithmetic on a page size, and the server has just shown that the\n // size it uses is not the size requested. Read as the server's real page size -- a capped\n // `limit`, which backends do -- and taken from page one, which has already answered.\n //\n // Before the branch, not inside it: both ways of walking a list address their pages this way,\n // and hardening one of them is how the counted branch was fixed while the infinite one went\n // on losing rows.\n //\n // It is a reading, not a fact. The other reading would be a server that applies `limit` and\n // then drops rows -- permissions, soft deletes -- while counting `totalCount` before that:\n // its window really is the requested size, so stepping by the shorter one overlaps and rows\n // repeat at each boundary. A sweep of the shared path found no such dropping, so the reading\n // taken here is the one these backends support; the trade is deliberate anyway, because\n // repeated rows are visible to whoever reads the list and missing ones are not.\n //\n // What the ambiguity does buy is the caution below: once the arithmetic is in doubt, the\n // pages are walked one at a time rather than fired off together, so a wrong guess costs\n // requests one by one rather than a burst of them, and stops at the first empty page.\n const capped = first.items.length > 0 && first.items.length < pagination.value.rowsPerPage\n if (capped) pagination.value.rowsPerPage = first.items.length\n\n // Infinite lists walk page by page, because only the answer says whether another exists.\n if (first.mode === 'infinite') {\n let hasNextPage = first.hasNextPage\n while (hasNextPage) {\n pagination.value.page++\n const nextPage = await get(pagination)\n const next = readPage(nextPage.res, nextPage.pageUrl)\n results.push(...next.items)\n if (next.empty) break\n hasNextPage = next.hasNextPage\n }\n\n return results\n }\n\n // A list that named neither mode has said nothing about further pages, so the first is all\n // there is.\n if (first.mode !== 'counted') return results\n\n // Two ways the count cannot be divided into a page count. The server may have said so itself\n // -- `bigTable`, which is on by default, makes `totalCount` a look-ahead of `offset + limit + 1`\n // rather than a total, corrected to the real number only on the page that comes back short.\n // Dividing that by the page size answers 2, whatever the list's real length: a batch over\n // 5000 rows would return 200 of them and resolve as though it were finished. Or the page size\n // itself had to be guessed, in which case the count divides by a number the server has\n // already contradicted.\n //\n // Either way the walk is sequential and ends where the data does -- a page shorter than the\n // one asked for is the server saying there is no more, and it is the same signal the\n // repository uses to correct the count in the first place.\n // Already holding as many rows as the count names, so there is nothing to walk to. This is\n // also what makes a short first page unambiguous: the repository corrects `totalCount` to the\n // real total exactly when a page comes back shorter than the limit it applied, so a list that\n // simply ends satisfies this, while a server that capped the limit reports its look-ahead\n // instead and does not.\n if (results.length >= first.totalCount) return results\n\n if (capped || first.countIsBound) {\n for (;;) {\n pagination.value.page++\n const nextPage = await get(pagination)\n const next = readPage(nextPage.res, nextPage.pageUrl)\n results.push(...next.items)\n if (next.items.length < pagination.value.rowsPerPage) break\n }\n\n return results\n }\n\n // Counted lists know how many pages there are, so the rest go out together.\n const pageCount = Math.ceil(first.totalCount / pagination.value.rowsPerPage)\n if (pageCount <= 1) return results\n\n // Each page is read as it arrives, not after all of them have. Reading them afterwards meant\n // a page that answered with a broken body was not noticed until every sibling had settled --\n // and a sibling that never answers made that never, so the call hung on a failure it was\n // already holding. Rejecting here reaches `run`, which stops the siblings.\n const rest = await Promise.all(\n Array.from({ length: pageCount - 1 }, (_unused, index) =>\n get(ref({ ...pagination.value, page: index + 2 })).then((page) => readPage(page.res, page.pageUrl))\n )\n )\n // A 204 among them contributes nothing and the others are kept -- there is no sequence to\n // stop here, they all went out at once.\n rest.forEach((page) => results.push(...page.items))\n\n return results\n }, signal)\n } catch (err: unknown) {\n const failed = err instanceof PageFailure ? err : null\n // Only a page can name a url here, and it names its own. The base url is not an answer: it was\n // never requested, and a failure that belongs to no page -- a missing template, a filter config\n // that will not render -- happened before any page existed. Nothing else can fail after a page\n // has gone out, because everything from the dispatch onwards is tagged as a `PageFailure`.\n const context = {\n system,\n entity,\n url: requestedUrl(client, failed?.pageUrl, options, failed?.failure ?? err, failed?.dispatched),\n }\n\n throw report(mapApiError(failed?.failure ?? err, context), context)\n }\n }\n\n return { execute, abort: abortable.abort, loading: abortable.loading }\n}\n\nexport type UseApiFetchListBatchReturnType<T> = {\n execute: (filterData: FilterData<any>, filterConfig: FilterConfig<any>, params?: FetchListBatchParams) => Promise<T[]>\n abort: () => void\n /**\n * True while this instance is working, false once it stops.\n *\n * Here that is the whole walk, not one request: a batch reads every page, and the flag stays up\n * across the gaps between them and across the reading of each answer.\n *\n * It is here so a caller does not have to keep its own: the hand-rolled version is a ref cleared in\n * a `finally`, and that `finally` belongs to whichever call ended -- including one that\n * `cancelPrevious` just superseded, which turns the spinner off while the call the user is waiting\n * for is still running. This one never dips between a superseded call and the one that replaced it.\n *\n * Two things to know before replacing a hand-rolled flag with it.\n *\n * It covers what this helper does and nothing the caller does afterwards. A flag that also spans a\n * follow-up cached fetch, a mapping pass or a second endpoint is not the same flag, and swapping it\n * for this one turns the spinner off too early. Keep your own where it means more than \"this helper\n * is working\".\n *\n * It belongs to the instance, so it is only reachable if you hold the instance. A factory that\n * builds one per call and hands back the promise alone -- `const { execute } = useX()` inside the\n * function that calls it -- cannot expose `loading`, `abort` or `cancelPrevious` at all. Hoist the\n * instance to where the caller lives first.\n */\n loading: Ref<boolean>\n}\n","import type { AxiosInstance } from 'axios'\nimport type { JobBase } from '@/types/Job'\nimport { stringToKebabCase } from '@/utils/string'\nimport { useApiFetchList } from '@/labs/api/useApiFetchList'\nimport { useApiCommand, useApiRequest } from '@/labs/api/useApiRequest'\n\nconst END_POINT = '/adm/v1/job'\nexport const ENTITY = 'job'\n\nexport function useJobApi<JobType extends JobBase = JobBase>(client: () => AxiosInstance, system: string) {\n const useFetchJobList = () => useApiFetchList<JobType>({ client, system, entity: ENTITY, urlTemplate: END_POINT })\n\n // Each of these builds its own request rather than sharing one: `useApiRequest` keeps a set of\n // abort controllers per instance, so a shared instance would put every caller of that instance in\n // one abort scope. It matters for the two `jobActions.ts` that call this at module scope and hold\n // `fetchJob` across the whole app; the other 25 consumers are `<script setup>`, one instance each.\n // They stay bound functions instead of being handed out as factories -- no caller anywhere in the\n // fleet aborts a job request, and exposing the handle would move 27 call sites to pass around\n // something unused.\n const fetchJob = (id: number) => {\n const { execute } = useApiRequest<JobType, null>({\n client,\n method: 'GET',\n system,\n entity: ENTITY,\n urlTemplate: END_POINT + '/:id',\n })\n\n return execute({ urlParams: { id } })\n }\n\n const createJob = (data: JobType) => {\n const type = stringToKebabCase(data._resourceName)\n .slice(4) // remove \"job-\" prefix\n .replace('-kind-', '-kind/') // replace \"-kind-\" with \"-kind/\" if the needle is found\n\n const { execute } = useApiRequest<JobType, JobType>({\n client,\n method: 'POST',\n system,\n entity: ENTITY,\n urlTemplate: END_POINT + '/:type',\n })\n\n return execute({ urlParams: { type }, body: data })\n }\n\n const deleteJob = (id: number) => {\n const { execute } = useApiCommand({\n client,\n method: 'DELETE',\n system,\n entity: ENTITY,\n urlTemplate: END_POINT + '/:id',\n })\n\n return execute({ urlParams: { id } })\n }\n\n return {\n useFetchJobList,\n fetchJob,\n createJob,\n deleteJob,\n }\n}\n","import {\n type ColumnConfig,\n type ColumnInternalValues,\n DATETIME_AUTO_LABEL_TRACKING,\n type StoredData,\n} from '@/composables/system/datatableColumns'\nimport { computed, onMounted, type Ref, watch } from 'vue'\nimport { i18n } from '@/plugins/i18n'\nimport { isArray, isBoolean, isObject, isString, isUndefined } from '@/utils/common'\n\nconst defaultColumn: ColumnInternalValues = {\n key: '',\n title: undefined,\n sortable: false,\n fixed: false,\n}\n\ninterface DatatableColumnsConfigMoreOptions {\n storeColumnsLocalStorage: string | boolean // false to disable, string to override store key\n disableActions: boolean\n customI18n: any\n showExpand: boolean\n}\n\nconst DatatableColumnsConfigMoreOptionsDefault = {\n storeColumnsLocalStorage: true,\n disableActions: false,\n customI18n: undefined,\n showExpand: false,\n}\n\nexport function createDatatableColumnsConfig(\n config: ColumnConfig[],\n columnsHidden: Ref<Array<string>>,\n system: string,\n subject: string,\n moreOptions: Partial<DatatableColumnsConfigMoreOptions> = {}\n) {\n const options = { ...DatatableColumnsConfigMoreOptionsDefault, ...moreOptions }\n const localI18n = options.customI18n ?? i18n\n const { t } = localI18n.global || localI18n\n\n let storeKey: undefined | string = undefined\n if (isString(options.storeColumnsLocalStorage)) {\n storeKey = options.storeColumnsLocalStorage\n } else if (isBoolean(options.storeColumnsLocalStorage) && true === options.storeColumnsLocalStorage) {\n storeKey = 'table_' + system + '_' + subject\n }\n\n const columnsAll = config.map((item) => {\n const obj = { ...defaultColumn, ...item }\n if (!isUndefined(obj.title)) {\n // do not modify\n } else if (isUndefined(obj.title) && DATETIME_AUTO_LABEL_TRACKING.includes(obj.key)) {\n obj.title = t('common.model.tracking.' + obj.key)\n } else if (isUndefined(obj.title) && system && subject) {\n obj.title = t(system + '.' + subject + '.model.' + obj.key)\n } else {\n obj.title = ''\n }\n return obj\n })\n\n const columnsVisible = computed(() => {\n const columns: any = []\n if (options.showExpand) columns.push({ key: 'data-table-expand', sortable: false })\n columnsAll.forEach((column) => {\n if (!columnsHidden.value.includes(column.key)) {\n columns.push(column)\n }\n })\n if (!options.disableActions) columns.push({ key: 'actions', sortable: false, fixed: 'end' })\n return columns\n })\n\n const loadStoredColumns = () => {\n if (!storeKey || !localStorage) return\n const stored = localStorage.getItem(storeKey)\n if (!stored) return\n // A corrupted entry would otherwise throw inside onMounted and take the whole table with it.\n let storedData: StoredData\n try {\n storedData = JSON.parse(stored) as StoredData\n } catch {\n localStorage.removeItem(storeKey)\n return\n }\n if (!isObject(storedData)) return\n if (!isArray(storedData.hidden)) return\n columnsHidden.value = storedData.hidden as string[]\n }\n\n const storeColumns = (columns: string[]) => {\n if (!storeKey || !localStorage) return\n localStorage.setItem(storeKey, JSON.stringify({ hidden: columns }))\n }\n\n onMounted(() => {\n loadStoredColumns()\n })\n\n watch(columnsHidden, (newValue) => {\n storeColumns(newValue)\n })\n\n return {\n columnsAll,\n columnsVisible,\n columnsHidden,\n }\n}\n","import type { AxiosClientFn } from '@/labs/api/client'\nimport { useApiFetchList } from '@/labs/api/useApiFetchList'\nimport { useApiRequest } from '@/labs/api/useApiRequest'\nimport type { LogPaths, LogTypeType } from '@/labs/log/logType'\nimport type { Log } from '@/types/Log'\n\nexport const LOG_ENTITY = 'log'\n\ninterface LogApiParams {\n client: AxiosClientFn\n system: string\n logPaths: LogPaths\n type: LogTypeType\n}\n\n/**\n * The backend and the endpoint both come from the caller, so these cannot be hoisted the way an\n * entity fetcher is -- every mount builds its own, which is also what gives each one its own\n * abort controller set.\n */\nexport const useFetchLogList = ({ client, system, logPaths, type }: LogApiParams) =>\n useApiFetchList<Log>({\n client,\n system,\n entity: LOG_ENTITY,\n urlTemplate: logPaths[type],\n })\n\nexport const useFetchLog = ({ client, system, logPaths, type }: LogApiParams) =>\n useApiRequest<Log, null>({\n client,\n method: 'GET',\n system,\n entity: LOG_ENTITY,\n urlTemplate: logPaths[type] + '/:id',\n })\n","import { ref, type Ref } from 'vue'\nimport { useAlerts } from '@/composables/system/alerts'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport type { FilterConfig, FilterData } from '@/labs/filters/filterFactory'\nimport type { Pagination } from '@/labs/filters/pagination'\nimport { useFetchLog, useFetchLogList } from '@/labs/log/logApi'\nimport type { LogPaths, LogTypeType } from '@/labs/log/logType'\nimport type { Log } from '@/types/Log'\n\ninterface LogActionsParams {\n client: AxiosClientFn\n system: string\n logPaths: LogPaths\n type: LogTypeType\n /**\n * Lets an ancestor own the flag, the way `usePagination` takes an external pagination ref --\n * the card that shows the spinner sits above the table that does the fetching.\n */\n loading?: Ref<boolean>\n}\n\n/**\n * Everything here is per instance, unlike the module-level refs the admins used to keep: the\n * views are remounted on every system/type change, and a `finally` belonging to the instance\n * that just went away would otherwise clear the new one's loading flag.\n */\nexport function useLogListActions(params: LogActionsParams) {\n const { showErrorsDefault } = useAlerts()\n const { execute, abort } = useFetchLogList(params)\n\n const listItems = ref<Log[]>([])\n const listLoading = params.loading ?? ref(false)\n const datatableHiddenColumns = ref<Array<string>>([])\n\n // Aborting is not enough on its own: axios rejects a cancelled request with `CanceledError`,\n // which is an axios error rather than a `DOMException` named `AbortError`, so it slips past the\n // helper's swallow and arrives here as a normal failure. Without this token the user would get\n // an error alert for an action they took themselves.\n let generation = 0\n const isCurrent = (token: number) => token === generation\n\n const fetchList = async (\n pagination: Ref<Pagination>,\n filterData: FilterData<any>,\n filterConfig: FilterConfig<any>\n ) => {\n const token = ++generation\n listLoading.value = true\n // `execute` writes the counts into the ref it is handed, inside the call -- before\n // anything out here could decide to discard them. It gets a copy, and the counts are\n // committed to the real one only if this call is still the current one.\n const scratch = ref<Pagination>({ ...pagination.value })\n try {\n const items = await execute(scratch, filterData, filterConfig)\n if (!isCurrent(token)) return\n listItems.value = items\n // Reading the scratch ref is the whole point -- these are the counts the helper just wrote\n // into it, and this is the one moment they are known to belong to the current request.\n /* eslint-disable vue/no-ref-object-reactivity-loss */\n pagination.value = {\n ...pagination.value,\n hasNextPage: scratch.value.hasNextPage,\n currentViewCount: scratch.value.currentViewCount,\n totalCount: scratch.value.totalCount,\n }\n /* eslint-enable vue/no-ref-object-reactivity-loss */\n } catch (error) {\n if (!isCurrent(token)) return\n // A failed fetch must not leave the previous type's rows on screen under the new url.\n listItems.value = []\n showErrorsDefault(error)\n } finally {\n if (isCurrent(token)) listLoading.value = false\n }\n }\n\n /** Invalidates whatever is in flight, then cancels it. Order matters: the token first. */\n const cancel = () => {\n generation++\n abort()\n }\n\n return {\n listItems,\n listLoading,\n datatableHiddenColumns,\n fetchList,\n cancel,\n }\n}\n\nexport function useLogDetailActions(params: LogActionsParams) {\n const { showErrorsDefault } = useAlerts()\n const { execute, abort } = useFetchLog(params)\n\n const log = ref<Log | null>(null)\n const detailLoading = params.loading ?? ref(false)\n\n let generation = 0\n const isCurrent = (token: number) => token === generation\n\n const fetchData = async (id: string) => {\n const token = ++generation\n detailLoading.value = true\n try {\n const res = await execute({ urlParams: { id } })\n if (!isCurrent(token)) return\n log.value = res\n } catch (error) {\n if (!isCurrent(token)) return\n log.value = null\n showErrorsDefault(error)\n } finally {\n if (isCurrent(token)) detailLoading.value = false\n }\n }\n\n const cancel = () => {\n generation++\n abort()\n }\n\n return {\n log,\n detailLoading,\n fetchData,\n cancel,\n }\n}\n","import { ref } from 'vue'\nimport type { ValueObjectOption } from '@/types/ValueObject'\n\n/**\n * The two log stores every anzu backend exposes. This is a runtime value on purpose: the router\n * guard validates the `:type` path segment before any component mounts, and a bare TypeScript\n * type is erased by then. It is the single source of the valid set -- the dropdown iterates the\n * same object the guard checks against, so the two cannot drift apart.\n */\nexport const LogType = {\n App: 'app',\n Audit: 'audit',\n} as const\nexport type LogTypeType = (typeof LogType)[keyof typeof LogType]\nexport const LogTypeDefault = LogType.App\n\nexport const isLogType = (value: unknown): value is LogTypeType => Object.values(LogType).includes(value as LogTypeType)\n\n/**\n * Full paths per type, not a segment appended to a shared prefix: brick exposes its app log at\n * `/adm/v1/log/journal` rather than `/adm/v1/log/app`, and nothing says the next backend will\n * differ in the same place.\n */\nexport type LogPaths = Record<LogTypeType, string>\n\nexport const DEFAULT_LOG_PATHS: LogPaths = {\n app: '/adm/v1/log/app',\n audit: '/adm/v1/log/audit',\n}\n\nexport function useLogType() {\n // Raw `app` / `audit` titles, as every admin shows them today. `common.log.filter.type` is the\n // select's own label, not a per-option name -- one translation cannot title two options.\n const logTypeOptions = ref<ValueObjectOption<LogTypeType>[]>(\n Object.values(LogType).map((value) => ({ value, title: value }))\n )\n\n const getLogTypeOption = (value: LogTypeType) => {\n return logTypeOptions.value.find((item) => item.value === value)\n }\n\n return {\n logTypeOptions,\n getLogTypeOption,\n }\n}\n","<script lang=\"ts\" setup>\nimport { inject, onMounted, onBeforeUnmount, provide, watch } from 'vue'\nimport type { RouteLocationRaw } from 'vue-router'\nimport { useDebounceFn } from '@vueuse/core'\nimport ACopyText from '@/components/ACopyText.vue'\nimport ADatatableConfigButton from '@/components/ADatatableConfigButton.vue'\nimport ADatetime from '@/components/datetime/ADatetime.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport ADatatablePagination from '@/labs/filters/ADatatablePagination.vue'\nimport { createDatatableColumnsConfig } from '@/labs/filters/datatableColumns'\nimport { DatatablePaginationKey, FilterConfigKey, FilterDataKey } from '@/labs/filters/filterInjectionKeys'\nimport { useFilterHelpers } from '@/labs/filters/filterFactory'\nimport { usePagination } from '@/labs/filters/pagination'\nimport { SORT_BY_ID } from '@/composables/system/datatableColumns'\nimport { useLogListActions } from '@/labs/log/logActions'\nimport { LOG_ENTITY } from '@/labs/log/logApi'\nimport { LogType, type LogPaths, type LogTypeType } from '@/labs/log/logType'\nimport type { Log } from '@/types/Log'\nimport { isNull, isUndefined } from '@/utils/common'\n\ntype DatatableItem = Log\n\nconst props = defineProps<{\n client: AxiosClientFn\n system: string\n type: LogTypeType\n logPaths: LogPaths\n /** Same mapper the view uses. Needed here so the actions cell stays a real link. */\n detailRoute: (log: Log) => RouteLocationRaw\n}>()\n\nconst emit = defineEmits<{\n (e: 'rowClick', log: Log): void\n}>()\n\n// The spinner belongs to the card above this table, so the flag is owned there.\nconst loading = defineModel<boolean>('loading', { default: false })\n\nconst filterConfig = inject(FilterConfigKey)\nconst filterData = inject(FilterDataKey)\nif (isUndefined(filterConfig) || isUndefined(filterData)) {\n throw new Error('[ALogDatatable] Incorrect provide/inject config.')\n}\n\n// Read once on purpose. Every one of these is fixed for the lifetime of this instance: the page\n// remounts the whole view through a `:key` when the system or the type changes, precisely because\n// `useApiFetchList` closes over its client and url, and reacting here could not undo that.\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { fetchList, listItems, datatableHiddenColumns, cancel } = useLogListActions({\n client: props.client,\n system: props.system,\n logPaths: props.logPaths,\n type: props.type,\n loading,\n})\n\n// Neither the url hash nor local storage: the filter is kept per view in memory by the registry\n// in `logFilter`, and nothing about a log listing is worth persisting across a reload.\nconst { resetFilter, submitFilter, loadStoredFilters } = useFilterHelpers(filterData, filterConfig, {\n storeFiltersLocalStorage: false,\n populateUrlParams: false,\n})\n\nconst { pagination } = usePagination(SORT_BY_ID)\nprovide(DatatablePaginationKey, pagination)\n\n// Keyed by type, not by system: the sets differ between app and audit, and a column hidden in\n// audit must not disappear from the app table.\nconst { columnsVisible, columnsAll, columnsHidden } = createDatatableColumnsConfig(\n [\n { key: 'id' },\n { key: 'datetime' },\n { key: 'levelName' },\n { key: 'message' },\n { key: 'context.appVersion' },\n { key: 'context.contextId' },\n ...(props.type === LogType.Audit\n ? [{ key: 'context.resourceName' }, { key: 'context.resourceIds' }]\n : [{ key: 'context.httpStatus' }]),\n { key: 'context.userId' },\n { key: 'context.ip' },\n ],\n datatableHiddenColumns,\n 'common',\n LOG_ENTITY,\n { storeColumnsLocalStorage: `table_common_log_${props.type}` }\n)\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst getList = useDebounceFn(async () => {\n await fetchList(pagination, filterData, filterConfig)\n})\n\nconst onRowClick = (_event: unknown, { item }: { item: DatatableItem }) => {\n if (item.id) emit('rowClick', item)\n}\n\nonMounted(() => {\n // Reads nothing back -- both persistences are off -- but it is what restores the page a user\n // was on when they close a detail, which `AActionCloseButton` flags for the next list.\n loadStoredFilters(pagination, getList)\n})\n\n// Everything above reads its props once, which is only correct while the page remounts this view\n// through a `:key`. Nothing would otherwise report a page that forgot: the table would keep\n// querying the old endpoint with the old columns under a new url, quietly.\nif (import.meta.env.DEV) {\n watch(\n () => [props.system, props.type],\n () => {\n console.error(\n '[ALogDatatable] `system` or `type` changed in place. This view must be remounted -- ' +\n 'give it :key=\"`${system}/${type}`\". The endpoint and columns are bound at mount.'\n )\n }\n )\n}\n\nonBeforeUnmount(() => {\n // Both, and in this order. `cancel()` only invalidates what is already in flight; a debounce\n // still pending would fire after unmount, bump the generation past that invalidation and start\n // a request the token would then consider current.\n getList.cancel()\n cancel()\n})\n\ndefineExpose({\n refresh: getList,\n resetFilter: () => resetFilter(pagination, getList),\n submitFilter: () => submitFilter(pagination, getList),\n})\n</script>\n\n<template>\n <div>\n <div class=\"d-flex align-center\">\n <VSpacer />\n <ADatatableConfigButton\n v-model:columns-hidden=\"columnsHidden\"\n :columns-all=\"columnsAll\"\n />\n </div>\n <VDataTableServer\n class=\"a-datatable\"\n :headers=\"columnsVisible\"\n :items=\"listItems\"\n :items-length=\"listItems.length\"\n item-value=\"id\"\n @click:row=\"onRowClick\"\n >\n <template #item.datetime=\"{ item }: { item: DatatableItem }\">\n <ADatetime :date-time=\"item.datetime\" />\n </template>\n <template #item.message=\"{ item }: { item: DatatableItem }\">\n <div class=\"line-clamp-2\">\n {{ item.message }}\n </div>\n </template>\n <template #item.context.resourceIds=\"{ item }: { item: DatatableItem }\">\n {{ item.context.resourceIds.join(', ') }}\n </template>\n <template #item.context.contextId=\"{ item }: { item: DatatableItem }\">\n <ACopyText :value=\"item.context.contextId\" />\n </template>\n <template #item.context.userId=\"{ item }: { item: DatatableItem }\">\n <ACopyText\n v-if=\"!isNull(item.context.userId)\"\n :value=\"item.context.userId\"\n />\n </template>\n <template #item.actions=\"{ item }: { item: DatatableItem }\">\n <div class=\"d-flex justify-end\">\n <!--\n `:to`, not a click handler: an anchor is what makes middle-click and open-in-new-tab\n work on a list people open several entries from, and it is what the smoke spec scrapes\n to find a row id.\n -->\n <VBtn\n :to=\"detailRoute(item)\"\n class=\"ml-1\"\n icon=\"mdi-information-outline\"\n size=\"x-small\"\n variant=\"text\"\n />\n </div>\n </template>\n <template #bottom>\n <ADatatablePagination @change=\"getList\" />\n </template>\n </VDataTableServer>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { inject, onMounted, onBeforeUnmount, provide, watch } from 'vue'\nimport type { RouteLocationRaw } from 'vue-router'\nimport { useDebounceFn } from '@vueuse/core'\nimport ACopyText from '@/components/ACopyText.vue'\nimport ADatatableConfigButton from '@/components/ADatatableConfigButton.vue'\nimport ADatetime from '@/components/datetime/ADatetime.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport ADatatablePagination from '@/labs/filters/ADatatablePagination.vue'\nimport { createDatatableColumnsConfig } from '@/labs/filters/datatableColumns'\nimport { DatatablePaginationKey, FilterConfigKey, FilterDataKey } from '@/labs/filters/filterInjectionKeys'\nimport { useFilterHelpers } from '@/labs/filters/filterFactory'\nimport { usePagination } from '@/labs/filters/pagination'\nimport { SORT_BY_ID } from '@/composables/system/datatableColumns'\nimport { useLogListActions } from '@/labs/log/logActions'\nimport { LOG_ENTITY } from '@/labs/log/logApi'\nimport { LogType, type LogPaths, type LogTypeType } from '@/labs/log/logType'\nimport type { Log } from '@/types/Log'\nimport { isNull, isUndefined } from '@/utils/common'\n\ntype DatatableItem = Log\n\nconst props = defineProps<{\n client: AxiosClientFn\n system: string\n type: LogTypeType\n logPaths: LogPaths\n /** Same mapper the view uses. Needed here so the actions cell stays a real link. */\n detailRoute: (log: Log) => RouteLocationRaw\n}>()\n\nconst emit = defineEmits<{\n (e: 'rowClick', log: Log): void\n}>()\n\n// The spinner belongs to the card above this table, so the flag is owned there.\nconst loading = defineModel<boolean>('loading', { default: false })\n\nconst filterConfig = inject(FilterConfigKey)\nconst filterData = inject(FilterDataKey)\nif (isUndefined(filterConfig) || isUndefined(filterData)) {\n throw new Error('[ALogDatatable] Incorrect provide/inject config.')\n}\n\n// Read once on purpose. Every one of these is fixed for the lifetime of this instance: the page\n// remounts the whole view through a `:key` when the system or the type changes, precisely because\n// `useApiFetchList` closes over its client and url, and reacting here could not undo that.\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { fetchList, listItems, datatableHiddenColumns, cancel } = useLogListActions({\n client: props.client,\n system: props.system,\n logPaths: props.logPaths,\n type: props.type,\n loading,\n})\n\n// Neither the url hash nor local storage: the filter is kept per view in memory by the registry\n// in `logFilter`, and nothing about a log listing is worth persisting across a reload.\nconst { resetFilter, submitFilter, loadStoredFilters } = useFilterHelpers(filterData, filterConfig, {\n storeFiltersLocalStorage: false,\n populateUrlParams: false,\n})\n\nconst { pagination } = usePagination(SORT_BY_ID)\nprovide(DatatablePaginationKey, pagination)\n\n// Keyed by type, not by system: the sets differ between app and audit, and a column hidden in\n// audit must not disappear from the app table.\nconst { columnsVisible, columnsAll, columnsHidden } = createDatatableColumnsConfig(\n [\n { key: 'id' },\n { key: 'datetime' },\n { key: 'levelName' },\n { key: 'message' },\n { key: 'context.appVersion' },\n { key: 'context.contextId' },\n ...(props.type === LogType.Audit\n ? [{ key: 'context.resourceName' }, { key: 'context.resourceIds' }]\n : [{ key: 'context.httpStatus' }]),\n { key: 'context.userId' },\n { key: 'context.ip' },\n ],\n datatableHiddenColumns,\n 'common',\n LOG_ENTITY,\n { storeColumnsLocalStorage: `table_common_log_${props.type}` }\n)\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst getList = useDebounceFn(async () => {\n await fetchList(pagination, filterData, filterConfig)\n})\n\nconst onRowClick = (_event: unknown, { item }: { item: DatatableItem }) => {\n if (item.id) emit('rowClick', item)\n}\n\nonMounted(() => {\n // Reads nothing back -- both persistences are off -- but it is what restores the page a user\n // was on when they close a detail, which `AActionCloseButton` flags for the next list.\n loadStoredFilters(pagination, getList)\n})\n\n// Everything above reads its props once, which is only correct while the page remounts this view\n// through a `:key`. Nothing would otherwise report a page that forgot: the table would keep\n// querying the old endpoint with the old columns under a new url, quietly.\nif (import.meta.env.DEV) {\n watch(\n () => [props.system, props.type],\n () => {\n console.error(\n '[ALogDatatable] `system` or `type` changed in place. This view must be remounted -- ' +\n 'give it :key=\"`${system}/${type}`\". The endpoint and columns are bound at mount.'\n )\n }\n )\n}\n\nonBeforeUnmount(() => {\n // Both, and in this order. `cancel()` only invalidates what is already in flight; a debounce\n // still pending would fire after unmount, bump the generation past that invalidation and start\n // a request the token would then consider current.\n getList.cancel()\n cancel()\n})\n\ndefineExpose({\n refresh: getList,\n resetFilter: () => resetFilter(pagination, getList),\n submitFilter: () => submitFilter(pagination, getList),\n})\n</script>\n\n<template>\n <div>\n <div class=\"d-flex align-center\">\n <VSpacer />\n <ADatatableConfigButton\n v-model:columns-hidden=\"columnsHidden\"\n :columns-all=\"columnsAll\"\n />\n </div>\n <VDataTableServer\n class=\"a-datatable\"\n :headers=\"columnsVisible\"\n :items=\"listItems\"\n :items-length=\"listItems.length\"\n item-value=\"id\"\n @click:row=\"onRowClick\"\n >\n <template #item.datetime=\"{ item }: { item: DatatableItem }\">\n <ADatetime :date-time=\"item.datetime\" />\n </template>\n <template #item.message=\"{ item }: { item: DatatableItem }\">\n <div class=\"line-clamp-2\">\n {{ item.message }}\n </div>\n </template>\n <template #item.context.resourceIds=\"{ item }: { item: DatatableItem }\">\n {{ item.context.resourceIds.join(', ') }}\n </template>\n <template #item.context.contextId=\"{ item }: { item: DatatableItem }\">\n <ACopyText :value=\"item.context.contextId\" />\n </template>\n <template #item.context.userId=\"{ item }: { item: DatatableItem }\">\n <ACopyText\n v-if=\"!isNull(item.context.userId)\"\n :value=\"item.context.userId\"\n />\n </template>\n <template #item.actions=\"{ item }: { item: DatatableItem }\">\n <div class=\"d-flex justify-end\">\n <!--\n `:to`, not a click handler: an anchor is what makes middle-click and open-in-new-tab\n work on a list people open several entries from, and it is what the smoke spec scrapes\n to find a row id.\n -->\n <VBtn\n :to=\"detailRoute(item)\"\n class=\"ml-1\"\n icon=\"mdi-information-outline\"\n size=\"x-small\"\n variant=\"text\"\n />\n </div>\n </template>\n <template #bottom>\n <ADatatablePagination @change=\"getList\" />\n </template>\n </VDataTableServer>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { useI18n } from 'vue-i18n'\nimport AFilterString from '@/labs/filters/AFilterString.vue'\nimport AFilterTimeInterval from '@/labs/filters/AFilterTimeInterval.vue'\nimport AFilterValueObjectOptionsSelect from '@/labs/filters/AFilterValueObjectOptionsSelect.vue'\nimport AFilterWrapper from '@/labs/filters/AFilterWrapper.vue'\nimport type { TimeIntervalToolsValue } from '@/labs/filters/filterTimeIntervalTools'\nimport { type LogTypeType, useLogType } from '@/labs/log/logType'\nimport { useLogLevel } from '@/model/valueObject/LogLevel'\n\ndefineProps<{\n type: LogTypeType\n allowedTimeIntervals?: TimeIntervalToolsValue[] | undefined\n}>()\n\nconst emit = defineEmits<{\n (e: 'submit'): void\n (e: 'reset'): void\n (e: 'changeType', type: LogTypeType): void\n}>()\n\nconst { t } = useI18n()\nconst { logLevelOptions } = useLogLevel()\nconst { logTypeOptions } = useLogType()\n</script>\n\n<template>\n <AFilterWrapper\n enable-top\n @submit=\"emit('submit')\"\n @reset=\"emit('reset')\"\n >\n <template #top>\n <VRow class=\"align-start\">\n <VCol\n class=\"pb-0\"\n cols=\"12\"\n sm=\"4\"\n >\n <!--\n A plain select, not `AFilterValueObjectOptionsSelect`: that one throws unless the name\n it is given is a live filter field, and the type is not one. It picks the endpoint and\n lives in the route, which is also what makes it survive a filter reset.\n -->\n <VSelect\n :model-value=\"type\"\n :items=\"logTypeOptions\"\n :label=\"t('common.log.filter.type')\"\n item-title=\"title\"\n item-value=\"value\"\n density=\"compact\"\n variant=\"outlined\"\n hide-details\n data-cy=\"filter-log-type\"\n @update:model-value=\"emit('changeType', $event)\"\n />\n </VCol>\n </VRow>\n <VDivider class=\"mb-4 mt-4\" />\n </template>\n <template #search>\n <AFilterString\n name=\"contextId\"\n class=\"mr-2\"\n />\n <AFilterString\n name=\"message\"\n class=\"mr-2\"\n />\n <AFilterTimeInterval\n name-from=\"datetimeFrom\"\n name-until=\"datetimeTo\"\n :allowed=\"allowedTimeIntervals\"\n />\n </template>\n <template #item.levelName>\n <AFilterValueObjectOptionsSelect\n name=\"levelName\"\n :items=\"logLevelOptions\"\n />\n </template>\n </AFilterWrapper>\n</template>\n","<script lang=\"ts\" setup>\nimport { useI18n } from 'vue-i18n'\nimport AFilterString from '@/labs/filters/AFilterString.vue'\nimport AFilterTimeInterval from '@/labs/filters/AFilterTimeInterval.vue'\nimport AFilterValueObjectOptionsSelect from '@/labs/filters/AFilterValueObjectOptionsSelect.vue'\nimport AFilterWrapper from '@/labs/filters/AFilterWrapper.vue'\nimport type { TimeIntervalToolsValue } from '@/labs/filters/filterTimeIntervalTools'\nimport { type LogTypeType, useLogType } from '@/labs/log/logType'\nimport { useLogLevel } from '@/model/valueObject/LogLevel'\n\ndefineProps<{\n type: LogTypeType\n allowedTimeIntervals?: TimeIntervalToolsValue[] | undefined\n}>()\n\nconst emit = defineEmits<{\n (e: 'submit'): void\n (e: 'reset'): void\n (e: 'changeType', type: LogTypeType): void\n}>()\n\nconst { t } = useI18n()\nconst { logLevelOptions } = useLogLevel()\nconst { logTypeOptions } = useLogType()\n</script>\n\n<template>\n <AFilterWrapper\n enable-top\n @submit=\"emit('submit')\"\n @reset=\"emit('reset')\"\n >\n <template #top>\n <VRow class=\"align-start\">\n <VCol\n class=\"pb-0\"\n cols=\"12\"\n sm=\"4\"\n >\n <!--\n A plain select, not `AFilterValueObjectOptionsSelect`: that one throws unless the name\n it is given is a live filter field, and the type is not one. It picks the endpoint and\n lives in the route, which is also what makes it survive a filter reset.\n -->\n <VSelect\n :model-value=\"type\"\n :items=\"logTypeOptions\"\n :label=\"t('common.log.filter.type')\"\n item-title=\"title\"\n item-value=\"value\"\n density=\"compact\"\n variant=\"outlined\"\n hide-details\n data-cy=\"filter-log-type\"\n @update:model-value=\"emit('changeType', $event)\"\n />\n </VCol>\n </VRow>\n <VDivider class=\"mb-4 mt-4\" />\n </template>\n <template #search>\n <AFilterString\n name=\"contextId\"\n class=\"mr-2\"\n />\n <AFilterString\n name=\"message\"\n class=\"mr-2\"\n />\n <AFilterTimeInterval\n name-from=\"datetimeFrom\"\n name-until=\"datetimeTo\"\n :allowed=\"allowedTimeIntervals\"\n />\n </template>\n <template #item.levelName>\n <AFilterValueObjectOptionsSelect\n name=\"levelName\"\n :items=\"logLevelOptions\"\n />\n </template>\n </AFilterWrapper>\n</template>\n","import {\n createFilter,\n createFilterStore,\n type FilterConfig,\n type FilterData,\n type MakeFilterOption,\n} from '@/labs/filters/filterFactory'\nimport { LOG_ENTITY } from '@/labs/log/logApi'\nimport { LogType, type LogTypeType } from '@/labs/log/logType'\nimport type { DatetimeUTC } from '@/types/common'\nimport { isDefined } from '@/utils/common'\n\nexport interface LogTimeWindow {\n from: DatetimeUTC\n to: DatetimeUTC\n}\n\n/**\n * Fields that only make sense for the audit store. Switching to `app` hides them and keeps them\n * out of the query -- it never clears them, so a value typed here survives a round trip through\n * `app` and is still there when the user comes back.\n */\nconst AUDIT_ONLY_FIELDS = ['resourceName', 'resourceIds'] as const\n\n/**\n * A factory rather than a constant, because `defaultTimeWindow` cannot be expressed as a seeded\n * value: what makes the window always reach the query is `mandatory` plus `default` on the two\n * interval fields, and both are read out of this array when `createFilter` runs.\n */\nconst logFilterFields = (window: LogTimeWindow | undefined) =>\n [\n { name: 'levelName' as const, variant: 'in', default: [] },\n { name: 'id' as const, default: null, type: 'string' },\n // Both drawn by hand in the `#search` slot of `ALogFilter`; without `skip` they would appear\n // a second time in the \"more\" panel.\n {\n name: 'contextId' as const,\n apiName: 'context.contextId',\n default: null,\n type: 'string',\n render: { skip: true },\n },\n { name: 'message' as const, variant: 'startsWith', default: null, type: 'string', render: { skip: true } },\n {\n name: 'appVersion' as const,\n variant: 'startsWith',\n apiName: 'context.appVersion',\n default: null,\n type: 'string',\n },\n { name: 'userId' as const, apiName: 'context.userId', default: null, type: 'integer' },\n { name: 'resourceName' as const, apiName: 'context.resourceName', default: null, type: 'string' },\n {\n name: 'resourceIds' as const,\n apiName: 'context.resourceIds',\n variant: 'in',\n default: [],\n type: 'string',\n },\n // Both rendered by hand in the `#search` slot of `ALogFilter`; without `skip` they would be\n // drawn a second time in the \"more\" panel, where `FilterDetailItem` has no case for\n // `timeInterval` and falls through to `AFilterEmpty`, which prints the bare field name.\n {\n name: 'datetimeFrom' as const,\n apiName: 'datetime',\n type: 'timeInterval',\n related: 'datetimeTo',\n default: window?.from ?? null,\n mandatory: isDefined(window),\n render: { skip: true },\n },\n {\n name: 'datetimeTo' as const,\n apiName: 'datetime',\n type: 'timeInterval',\n exclude: true,\n default: window?.to ?? null,\n mandatory: isDefined(window),\n render: { skip: true },\n },\n ] satisfies readonly MakeFilterOption[]\n\ntype LogFilterFields = ReturnType<typeof logFilterFields>\n\nexport interface LogFilter {\n filterConfig: FilterConfig<LogFilterFields>\n filterData: FilterData<LogFilterFields>\n}\n\n/**\n * Keyed by system, so `/weather/logs` and `/brick/logs` cannot overwrite each other's values --\n * and deliberately *not* by type, so everything the two stores share (context id, level, time\n * range, user, message) survives a switch between app and audit. That carry-over is the whole\n * reason this is one view instead of two pages.\n *\n * Module level, so the values outlive the component: `createFilterStore` is only a factory, and\n * one created in `setup()` would die with the instance that made it.\n */\nconst registry = new Map<string, LogFilter>()\n\nexport function useLogFilter(system: string, window: LogTimeWindow | undefined): LogFilter {\n const existing = registry.get(system)\n if (existing) return existing\n\n const fields = logFilterFields(window)\n const created = createFilter(fields, createFilterStore(fields), {\n system: 'common',\n subject: LOG_ENTITY,\n })\n registry.set(system, created)\n\n return created\n}\n\n/**\n * Called on every mount, not only when the type changes. The registry hands back the same\n * `filterConfig` the previous visit left behind, so these flags still describe whichever type was\n * open last. Landing straight on an `app` url after an audit visit -- a link, a bookmark, F5 --\n * would otherwise send that visit's `resourceName` to the app endpoint.\n *\n * Three flags, not two -- `FiltersSelected` hides a chip on `render.selected`, so without the\n * third one the bar would advertise a filter that is not being applied, and its close button\n * would destroy the very value this approach exists to keep.\n */\nexport const applyLogTypeVisibility = (filter: LogFilter, type: LogTypeType): void => {\n const hide = type !== LogType.Audit\n\n AUDIT_ONLY_FIELDS.forEach((name) => {\n const field = filter.filterConfig.fields[name]\n field.exclude = hide\n field.render.skip = hide\n field.render.selected = !hide\n })\n}\n\n/** For tests and for anything that needs a clean slate; nothing in the views calls it. */\nexport const resetLogFilterRegistry = (): void => {\n registry.clear()\n}\n","<script lang=\"ts\" setup>\nimport { provide, ref, useTemplateRef } from 'vue'\nimport type { RouteLocationRaw } from 'vue-router'\nimport { useRouter } from 'vue-router'\nimport ACard from '@/components/ACard.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport { FilterConfigKey, FilterDataKey } from '@/labs/filters/filterInjectionKeys'\nimport type { TimeIntervalToolsValue } from '@/labs/filters/filterTimeIntervalTools'\nimport ALogDatatable from '@/labs/log/ALogDatatable.vue'\nimport ALogFilter from '@/labs/log/ALogFilter.vue'\nimport { applyLogTypeVisibility, type LogTimeWindow, useLogFilter } from '@/labs/log/logFilter'\nimport type { LogPaths, LogTypeType } from '@/labs/log/logType'\nimport type { Log } from '@/types/Log'\n\nconst props = defineProps<{\n /** Which backend to ask. */\n client: AxiosClientFn\n /** Identity used for api naming and error reporting -- `coreDam`, not `dam`, where they differ. */\n system: string\n /** From the route, never component state: it picks the endpoint and has to survive a filter reset. */\n type: LogTypeType\n /** Whole paths per type. Comes from the route-side descriptor, which the guard reads too. */\n logPaths: LogPaths\n /** Where a row goes. The library cannot know an app's typed route names, so the app maps it. */\n detailRoute: (log: Log) => RouteLocationRaw\n /** Where the app/audit dropdown goes. Same reason. */\n typeRoute: (type: LogTypeType) => RouteLocationRaw\n /** Mandatory time bound, as admin-dam has today. Omitted means no bound at all. */\n defaultTimeWindow?: LogTimeWindow | undefined\n /** `allowed` for the interval filter. Omitted means the library's full preset list. */\n allowedTimeIntervals?: TimeIntervalToolsValue[] | undefined\n}>()\n\nconst router = useRouter()\n\n// Read once: keyed remount, as in the datatable below.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss\nconst filter = useLogFilter(props.system, props.defaultTimeWindow)\n// Every mount, not only on a type change: the registry returns the config the last visit left,\n// so the flags still describe that visit's type. See `applyLogTypeVisibility`.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss\napplyLogTypeVisibility(filter, props.type)\n\nprovide(FilterConfigKey, filter.filterConfig)\nprovide(FilterDataKey, filter.filterData)\n\nconst datatable = useTemplateRef<InstanceType<typeof ALogDatatable>>('datatable')\nconst listLoading = ref(false)\n\nconst onRowClick = (log: Log) => {\n router.push(props.detailRoute(log))\n}\n\nconst onChangeType = (type: LogTypeType) => {\n if (type === props.type) return\n router.replace(props.typeRoute(type))\n}\n</script>\n\n<template>\n <ACard :loading=\"listLoading\">\n <VCardText>\n <ALogFilter\n :type=\"type\"\n :allowed-time-intervals=\"allowedTimeIntervals\"\n @submit=\"datatable?.submitFilter()\"\n @reset=\"datatable?.resetFilter()\"\n @change-type=\"onChangeType\"\n />\n <ALogDatatable\n ref=\"datatable\"\n v-model:loading=\"listLoading\"\n :client=\"client\"\n :system=\"system\"\n :type=\"type\"\n :log-paths=\"logPaths\"\n :detail-route=\"detailRoute\"\n @row-click=\"onRowClick\"\n />\n </VCardText>\n </ACard>\n</template>\n","<script lang=\"ts\" setup>\nimport { provide, ref, useTemplateRef } from 'vue'\nimport type { RouteLocationRaw } from 'vue-router'\nimport { useRouter } from 'vue-router'\nimport ACard from '@/components/ACard.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport { FilterConfigKey, FilterDataKey } from '@/labs/filters/filterInjectionKeys'\nimport type { TimeIntervalToolsValue } from '@/labs/filters/filterTimeIntervalTools'\nimport ALogDatatable from '@/labs/log/ALogDatatable.vue'\nimport ALogFilter from '@/labs/log/ALogFilter.vue'\nimport { applyLogTypeVisibility, type LogTimeWindow, useLogFilter } from '@/labs/log/logFilter'\nimport type { LogPaths, LogTypeType } from '@/labs/log/logType'\nimport type { Log } from '@/types/Log'\n\nconst props = defineProps<{\n /** Which backend to ask. */\n client: AxiosClientFn\n /** Identity used for api naming and error reporting -- `coreDam`, not `dam`, where they differ. */\n system: string\n /** From the route, never component state: it picks the endpoint and has to survive a filter reset. */\n type: LogTypeType\n /** Whole paths per type. Comes from the route-side descriptor, which the guard reads too. */\n logPaths: LogPaths\n /** Where a row goes. The library cannot know an app's typed route names, so the app maps it. */\n detailRoute: (log: Log) => RouteLocationRaw\n /** Where the app/audit dropdown goes. Same reason. */\n typeRoute: (type: LogTypeType) => RouteLocationRaw\n /** Mandatory time bound, as admin-dam has today. Omitted means no bound at all. */\n defaultTimeWindow?: LogTimeWindow | undefined\n /** `allowed` for the interval filter. Omitted means the library's full preset list. */\n allowedTimeIntervals?: TimeIntervalToolsValue[] | undefined\n}>()\n\nconst router = useRouter()\n\n// Read once: keyed remount, as in the datatable below.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss\nconst filter = useLogFilter(props.system, props.defaultTimeWindow)\n// Every mount, not only on a type change: the registry returns the config the last visit left,\n// so the flags still describe that visit's type. See `applyLogTypeVisibility`.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss\napplyLogTypeVisibility(filter, props.type)\n\nprovide(FilterConfigKey, filter.filterConfig)\nprovide(FilterDataKey, filter.filterData)\n\nconst datatable = useTemplateRef<InstanceType<typeof ALogDatatable>>('datatable')\nconst listLoading = ref(false)\n\nconst onRowClick = (log: Log) => {\n router.push(props.detailRoute(log))\n}\n\nconst onChangeType = (type: LogTypeType) => {\n if (type === props.type) return\n router.replace(props.typeRoute(type))\n}\n</script>\n\n<template>\n <ACard :loading=\"listLoading\">\n <VCardText>\n <ALogFilter\n :type=\"type\"\n :allowed-time-intervals=\"allowedTimeIntervals\"\n @submit=\"datatable?.submitFilter()\"\n @reset=\"datatable?.resetFilter()\"\n @change-type=\"onChangeType\"\n />\n <ALogDatatable\n ref=\"datatable\"\n v-model:loading=\"listLoading\"\n :client=\"client\"\n :system=\"system\"\n :type=\"type\"\n :log-paths=\"logPaths\"\n :detail-route=\"detailRoute\"\n @row-click=\"onRowClick\"\n />\n </VCardText>\n </ACard>\n</template>\n","/**\n * Pretty-prints a JSON string, and hands back whatever it was given when that is not what it is.\n * Log context carries request and response bodies that are usually but not always JSON.\n */\nexport const formatJson = (data: string): string => {\n try {\n return JSON.stringify(JSON.parse(data), null, 2)\n } catch {\n return data\n }\n}\n","<script lang=\"ts\" setup>\nimport { useI18n } from 'vue-i18n'\nimport ALogLevelChip from '@/components/log/ALogLevelChip.vue'\nimport type { Log } from '@/types/Log'\nimport { dateTimeFriendly } from '@/utils/datetime'\nimport { formatJson } from '@/utils/json'\n\ndefineProps<{\n log: Log\n}>()\n\nconst { t } = useI18n()\n</script>\n\n<template>\n <VRow>\n <VCol>\n <VRow>\n <VCol cols=\"3\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.id') }}\n </h4>\n {{ log.id }}\n </VCol>\n <VCol cols=\"3\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.levelName') }}\n </h4>\n <ALogLevelChip :level=\"log.levelName\" />\n </VCol>\n <VCol cols=\"2\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.userId') }}\n </h4>\n {{ log.context.userId }}\n </VCol>\n <VCol cols=\"2\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.appVersion') }}\n </h4>\n {{ log.context.appVersion }}\n </VCol>\n <VCol cols=\"2\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.requestOriginAppVersion') }}\n </h4>\n {{ log.context.requestOriginAppVersion }}\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"4\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.contextId') }}\n </h4>\n {{ log.context.contextId }}\n </VCol>\n <VCol cols=\"4\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.ip') }}\n </h4>\n {{ log.context.ip }}\n </VCol>\n <VCol cols=\"4\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.datetime') }}\n </h4>\n {{ dateTimeFriendly(log.datetime) }}\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"12\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.message') }}\n </h4>\n <pre>{{ log.message }}</pre>\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"6\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.path') }}\n </h4>\n {{ log.context.method }} {{ log.context.path }}\n </VCol>\n <VCol cols=\"6\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.httpStatus') }}\n </h4>\n {{ log.context.httpStatus }}\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"12\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.content') }}\n </h4>\n <pre>{{ formatJson(log.context.content ?? '') }}</pre>\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"12\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.response') }}\n </h4>\n <pre>{{ formatJson(log.context.response) }}</pre>\n </VCol>\n </VRow>\n </VCol>\n </VRow>\n</template>\n\n<style lang=\"scss\" scoped>\npre {\n overflow: auto;\n}\n</style>\n","<script lang=\"ts\" setup>\nimport { useI18n } from 'vue-i18n'\nimport ALogLevelChip from '@/components/log/ALogLevelChip.vue'\nimport type { Log } from '@/types/Log'\nimport { dateTimeFriendly } from '@/utils/datetime'\nimport { formatJson } from '@/utils/json'\n\ndefineProps<{\n log: Log\n}>()\n\nconst { t } = useI18n()\n</script>\n\n<template>\n <VRow>\n <VCol>\n <VRow>\n <VCol cols=\"3\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.id') }}\n </h4>\n {{ log.id }}\n </VCol>\n <VCol cols=\"3\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.levelName') }}\n </h4>\n <ALogLevelChip :level=\"log.levelName\" />\n </VCol>\n <VCol cols=\"2\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.userId') }}\n </h4>\n {{ log.context.userId }}\n </VCol>\n <VCol cols=\"2\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.appVersion') }}\n </h4>\n {{ log.context.appVersion }}\n </VCol>\n <VCol cols=\"2\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.requestOriginAppVersion') }}\n </h4>\n {{ log.context.requestOriginAppVersion }}\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"4\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.contextId') }}\n </h4>\n {{ log.context.contextId }}\n </VCol>\n <VCol cols=\"4\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.ip') }}\n </h4>\n {{ log.context.ip }}\n </VCol>\n <VCol cols=\"4\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.datetime') }}\n </h4>\n {{ dateTimeFriendly(log.datetime) }}\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"12\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.message') }}\n </h4>\n <pre>{{ log.message }}</pre>\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"6\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.path') }}\n </h4>\n {{ log.context.method }} {{ log.context.path }}\n </VCol>\n <VCol cols=\"6\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.httpStatus') }}\n </h4>\n {{ log.context.httpStatus }}\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"12\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.content') }}\n </h4>\n <pre>{{ formatJson(log.context.content ?? '') }}</pre>\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"12\">\n <h4 class=\"text-label-large\">\n {{ t('common.log.model.context.response') }}\n </h4>\n <pre>{{ formatJson(log.context.response) }}</pre>\n </VCol>\n </VRow>\n </VCol>\n </VRow>\n</template>\n\n<style lang=\"scss\" scoped>\npre {\n overflow: auto;\n}\n</style>\n","<script lang=\"ts\" setup>\nimport { onBeforeUnmount, onMounted } from 'vue'\nimport ACard from '@/components/ACard.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport ALogDetail from '@/labs/log/ALogDetail.vue'\nimport { useLogDetailActions } from '@/labs/log/logActions'\nimport type { LogPaths, LogTypeType } from '@/labs/log/logType'\nimport { isNull } from '@/utils/common'\n\nconst props = defineProps<{\n client: AxiosClientFn\n system: string\n /** From the route. A log id means nothing without the store it came from. */\n type: LogTypeType\n id: string\n logPaths: LogPaths\n}>()\n\n// Read once: the page keys this view on `${system}/${type}/${id}`, so a change remounts it.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss\nconst { log, detailLoading, fetchData, cancel } = useLogDetailActions({\n client: props.client,\n system: props.system,\n logPaths: props.logPaths,\n type: props.type,\n})\n\nonMounted(() => {\n fetchData(props.id)\n})\n\nonBeforeUnmount(() => {\n cancel()\n})\n</script>\n\n<template>\n <ACard :loading=\"detailLoading\">\n <VCardText>\n <ALogDetail\n v-if=\"!isNull(log)\"\n :log=\"log\"\n />\n </VCardText>\n </ACard>\n</template>\n","<script lang=\"ts\" setup>\nimport { onBeforeUnmount, onMounted } from 'vue'\nimport ACard from '@/components/ACard.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport ALogDetail from '@/labs/log/ALogDetail.vue'\nimport { useLogDetailActions } from '@/labs/log/logActions'\nimport type { LogPaths, LogTypeType } from '@/labs/log/logType'\nimport { isNull } from '@/utils/common'\n\nconst props = defineProps<{\n client: AxiosClientFn\n system: string\n /** From the route. A log id means nothing without the store it came from. */\n type: LogTypeType\n id: string\n logPaths: LogPaths\n}>()\n\n// Read once: the page keys this view on `${system}/${type}/${id}`, so a change remounts it.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss\nconst { log, detailLoading, fetchData, cancel } = useLogDetailActions({\n client: props.client,\n system: props.system,\n logPaths: props.logPaths,\n type: props.type,\n})\n\nonMounted(() => {\n fetchData(props.id)\n})\n\nonBeforeUnmount(() => {\n cancel()\n})\n</script>\n\n<template>\n <ACard :loading=\"detailLoading\">\n <VCardText>\n <ALogDetail\n v-if=\"!isNull(log)\"\n :log=\"log\"\n />\n </VCardText>\n </ACard>\n</template>\n","import { type DatatableOrderingOption, type DatatableSortBy } from '@/composables/system/datatableColumns'\nimport type { Ref } from 'vue'\nimport { ref } from 'vue'\nimport { useAlerts } from '@/composables/system/alerts'\nimport { type FilterConfig, type FilterData, useFilterHelpers } from '@/labs/filters/filterFactory'\nimport { createDatatableColumnsConfig } from '@/labs/filters/datatableColumns'\nimport { type Pagination, usePagination } from '@/labs/filters/pagination'\nimport type { FetchListParams } from '@/labs/api/useApiFetchList'\nimport { useDebounceFn } from '@vueuse/core'\nimport { isNull } from '@/utils/common'\n\nexport function useSubjectSelect<TItem>(\n datatableConfig: any,\n datatableHiddenColumns: any,\n system: string,\n subject: string,\n execute: (\n pagination: Ref<Pagination>,\n filterData: FilterData<any>,\n filterConfig: FilterConfig<any>,\n params?: FetchListParams\n ) => Promise<TItem[]>,\n filterData: FilterData<any>,\n filterConfig: FilterConfig<any>,\n filterSortBy: DatatableSortBy | null = null,\n fetchParams: FetchListParams | undefined = undefined,\n enableActions: boolean = false\n) {\n const filterTouched: Ref<boolean> = ref(false)\n const items: Ref<Array<TItem>> = ref([])\n const selected: Ref<Array<TItem>> = ref([])\n const loading = ref(false)\n const { pagination, setSortBy, incrementPage } = usePagination(\n isNull(filterSortBy) ? null : filterSortBy.key,\n filterSortBy?.order\n )\n\n const { resetFilter, submitFilter } = useFilterHelpers(filterData, filterConfig, {\n storeFiltersLocalStorage: false,\n populateUrlParams: false,\n })\n const { showErrorsDefault } = useAlerts()\n\n const { columnsVisible, columnsAll, columnsHidden } = createDatatableColumnsConfig(\n datatableConfig,\n datatableHiddenColumns,\n system,\n subject,\n { storeColumnsLocalStorage: false, disableActions: !enableActions }\n )\n\n const onOpen = () => {\n resetState()\n getListDebounced()\n }\n\n const sortByChange = (option: DatatableOrderingOption) => {\n setSortBy(option.sortBy)\n getListDebounced()\n }\n\n const onFetchNextPage = async () => {\n loading.value = true\n incrementPage()\n try {\n const res = (await execute(pagination, filterData, filterConfig, fetchParams)) as TItem[]\n items.value.push(...res)\n } catch (e) {\n showErrorsDefault(e)\n } finally {\n loading.value = false\n }\n }\n\n const resetState = () => {\n items.value = []\n selected.value = []\n }\n\n const getList = async () => {\n loading.value = true\n try {\n items.value = (await execute(pagination, filterData, filterConfig, fetchParams)) as TItem[]\n } catch (e) {\n showErrorsDefault(e)\n } finally {\n loading.value = false\n }\n }\n\n const getListDebounced = useDebounceFn(async () => {\n await getList()\n })\n\n const onRowClick = (event: Event) => {\n const eventTarget = event.target as HTMLElement | null\n if (!eventTarget || (eventTarget.tagName === 'INPUT' && (eventTarget as HTMLInputElement).type === 'checkbox')) {\n return\n }\n const parent = eventTarget.closest('.v-data-table__tr')\n if (!parent || !parent.classList.contains('v-data-table__tr')) return\n const firstTd = parent.firstElementChild\n if (!firstTd || !firstTd.classList.contains('v-data-table__td')) return\n const input = firstTd.querySelector('input')\n if (!input) return\n input.click()\n }\n\n return {\n items,\n selected,\n pagination,\n columnsVisible,\n columnsHidden,\n columnsAll,\n filterTouched,\n loading,\n onRowClick,\n onFetchNextPage,\n onOpen,\n sortByChange,\n getList: getListDebounced,\n resetFilter: () => resetFilter(pagination, getListDebounced),\n submitFilter: () => submitFilter(pagination, getListDebounced),\n }\n}\n","<script lang=\"ts\" setup generic=\"TItem\">\nimport { computed, onMounted, provide, ref, toRaw, withModifiers } from 'vue'\nimport { isNull, isUndefined } from '@/utils/common'\nimport ADialogToolbar from '@/components/ADialogToolbar.vue'\nimport { useI18n } from 'vue-i18n'\nimport { useDisplay } from 'vuetify'\nimport type { Pagination } from '@/labs/filters/pagination'\nimport ADatatablePagination from '@/labs/filters/ADatatablePagination.vue'\nimport { SubjectSelectCloseSidebarKey } from '@/labs/filters/filterInjectionKeys'\n\nconst props = withDefaults(\n defineProps<{\n selectedItems: Array<TItem>\n pagination: Pagination\n loading?: boolean\n minCount?: number\n maxCount?: number\n modelValue?: boolean | undefined // dialog\n dialogTitleT?: string\n paginationMode?: 'standard' | 'more'\n autoOpen?: boolean\n }>(),\n {\n loading: false,\n minCount: 1,\n maxCount: 1,\n modelValue: undefined,\n dialogTitleT: 'common.subjectSelect.texts.title',\n paginationMode: 'standard',\n autoOpen: false,\n }\n)\nconst emit = defineEmits<{\n (e: 'update:modelValue', data: boolean): void\n (e: 'update:pagination', data: Pagination): void\n (e: 'onOpen'): void\n (e: 'onPageChange'): void\n (e: 'onConfirm', data: Array<TItem>): void\n (e: 'onFetchNextPage'): void\n}>()\n\nconst dialogLocal = ref(false)\nconst dialog = computed({\n get() {\n if (isUndefined(props.modelValue)) return dialogLocal.value\n return props.modelValue\n },\n set(newValue: boolean) {\n dialogLocal.value = newValue\n emit('update:modelValue', newValue)\n },\n})\n\nconst paginationComputed = computed({\n get: () => props.pagination,\n set: (newValue) => {\n emit('update:pagination', { ...toRaw(newValue) })\n },\n})\n\nconst { mdAndDown } = useDisplay()\nconst sidebarLeft = ref(!mdAndDown.value)\n\nconst closeSidebarOnMobile = () => {\n if (mdAndDown.value) sidebarLeft.value = false\n}\n\nprovide(SubjectSelectCloseSidebarKey, closeSidebarOnMobile)\n\nconst { t } = useI18n()\n\nconst selectedItemsCount = computed(() => {\n return props.selectedItems.length\n})\n\nconst disabledSubmit = computed(() => {\n return selectedItemsCount.value < props.minCount || selectedItemsCount.value > props.maxCount\n})\n\nconst onOpen = () => {\n emit('onOpen')\n sidebarLeft.value = !mdAndDown.value\n dialog.value = true\n}\n\nconst toggleSidebar = () => {\n sidebarLeft.value = !sidebarLeft.value\n}\n\nconst onClose = () => {\n dialog.value = false\n}\n\nconst onConfirm = () => {\n emit(\n 'onConfirm',\n props.selectedItems.map((item) => toRaw(item))\n )\n onClose()\n}\n\nconst fetchNextPage = () => {\n emit('onFetchNextPage')\n}\n\nconst onPageChange = () => {\n emit('onPageChange')\n}\n\nconst lastPage = computed(() => {\n return Math.ceil(paginationComputed.value.totalCount / paginationComputed.value.rowsPerPage)\n})\n\n// `>=`, not `===`: an empty list has `totalCount: 0`, so `lastPage` is 0 while `page` is never below\n// 1, and the two can never meet. The page would then count as \"not the last one\" on a list with\n// nothing in it -- and the same holds before any request has been made, because that initial state is\n// the same numbers.\nconst hasNextPage = computed(() => {\n return !(\n (isNull(paginationComputed.value.hasNextPage) && paginationComputed.value.page >= lastPage.value) ||\n paginationComputed.value.hasNextPage === false\n )\n})\n\nconst autoloadOnIntersect = (isIntersecting: boolean) => {\n if (isIntersecting && hasNextPage.value && !props.loading) {\n fetchNextPage()\n }\n}\n\nonMounted(() => {\n if (props.autoOpen) {\n onOpen()\n }\n})\n\ndefineExpose({\n open: onOpen,\n closeSidebarOnMobile,\n})\n</script>\n\n<template>\n <slot\n name=\"activator\"\n :props=\"{ onClick: withModifiers(() => onOpen(), ['stop']) }\"\n />\n <VDialog\n v-bind=\"$attrs\"\n :model-value=\"dialog\"\n fullscreen\n class=\"subject-select\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <VCard\n v-if=\"dialog\"\n class=\"subject-select__card\"\n >\n <ADialogToolbar\n class=\"subject-select__toolbar system-border-b\"\n @on-cancel=\"onClose\"\n >\n <slot name=\"title\">\n {{ t(dialogTitleT) }}\n </slot>\n </ADialogToolbar>\n <VToolbar\n density=\"compact\"\n color=\"transparent\"\n :height=\"46\"\n elevation=\"0\"\n class=\"system-border-b subject-select__second-bar\"\n >\n <slot name=\"second-bar\">\n <div class=\"d-flex flex-column w-100 px-1 align-center\">\n <div class=\"d-flex justify-space-between w-100 align-center\">\n <div class=\"d-flex align-center\">\n <VBtn\n icon\n :width=\"30\"\n :height=\"30\"\n @click=\"toggleSidebar\"\n >\n <VIcon\n icon=\"mdi-tune\"\n :size=\"16\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n >\n {{ t('common.subjectSelect.filter.toggle') }}\n </VTooltip>\n </VBtn>\n <slot name=\"second-bar-left\" />\n </div>\n <div class=\"d-flex align-center\">\n <slot name=\"second-bar-right\" />\n </div>\n </div>\n </div>\n </slot>\n </VToolbar>\n <div\n class=\"subject-select__main\"\n :class=\"{ 'subject-select__main--sidebar-active': sidebarLeft }\"\n >\n <div class=\"subject-select__sidebar system-border-r\">\n <div class=\"subject-select-filter\">\n <slot name=\"filter\" />\n </div>\n </div>\n <div class=\"subject-select__content\">\n <slot name=\"content\" />\n <div\n v-if=\"paginationMode === 'more'\"\n class=\"d-flex w-100 align-center justify-center pa-4\"\n >\n <ABtnSecondary\n v-show=\"hasNextPage || loading\"\n v-intersect.quiet=\"autoloadOnIntersect\"\n :loading=\"loading\"\n size=\"small\"\n @click=\"fetchNextPage\"\n >\n <slot name=\"button-confirm-title\">\n {{ t('common.button.loadMore') }}\n </slot>\n </ABtnSecondary>\n </div>\n <ADatatablePagination\n v-else\n v-model=\"paginationComputed\"\n @change=\"onPageChange\"\n />\n </div>\n </div>\n <div class=\"subject-select__actions system-border-t\">\n <div v-if=\"minCount === maxCount\">\n {{\n t('common.subjectSelect.texts.pickExactCount', {\n count: minCount,\n selected: selectedItemsCount,\n })\n }}\n </div>\n <div v-else>\n {{\n t('common.subjectSelect.texts.pickRangeCount', {\n minCount: minCount,\n maxCount: maxCount,\n selected: selectedItemsCount,\n })\n }}\n </div>\n <VSpacer />\n <ABtnPrimary\n :disabled=\"disabledSubmit\"\n @click.stop=\"onConfirm\"\n >\n <slot name=\"button-confirm-title\">\n {{ t('common.button.confirm') }}\n </slot>\n </ABtnPrimary>\n </div>\n </VCard>\n </VDialog>\n</template>\n","<script lang=\"ts\" setup generic=\"TItem\">\nimport { computed, onMounted, provide, ref, toRaw, withModifiers } from 'vue'\nimport { isNull, isUndefined } from '@/utils/common'\nimport ADialogToolbar from '@/components/ADialogToolbar.vue'\nimport { useI18n } from 'vue-i18n'\nimport { useDisplay } from 'vuetify'\nimport type { Pagination } from '@/labs/filters/pagination'\nimport ADatatablePagination from '@/labs/filters/ADatatablePagination.vue'\nimport { SubjectSelectCloseSidebarKey } from '@/labs/filters/filterInjectionKeys'\n\nconst props = withDefaults(\n defineProps<{\n selectedItems: Array<TItem>\n pagination: Pagination\n loading?: boolean\n minCount?: number\n maxCount?: number\n modelValue?: boolean | undefined // dialog\n dialogTitleT?: string\n paginationMode?: 'standard' | 'more'\n autoOpen?: boolean\n }>(),\n {\n loading: false,\n minCount: 1,\n maxCount: 1,\n modelValue: undefined,\n dialogTitleT: 'common.subjectSelect.texts.title',\n paginationMode: 'standard',\n autoOpen: false,\n }\n)\nconst emit = defineEmits<{\n (e: 'update:modelValue', data: boolean): void\n (e: 'update:pagination', data: Pagination): void\n (e: 'onOpen'): void\n (e: 'onPageChange'): void\n (e: 'onConfirm', data: Array<TItem>): void\n (e: 'onFetchNextPage'): void\n}>()\n\nconst dialogLocal = ref(false)\nconst dialog = computed({\n get() {\n if (isUndefined(props.modelValue)) return dialogLocal.value\n return props.modelValue\n },\n set(newValue: boolean) {\n dialogLocal.value = newValue\n emit('update:modelValue', newValue)\n },\n})\n\nconst paginationComputed = computed({\n get: () => props.pagination,\n set: (newValue) => {\n emit('update:pagination', { ...toRaw(newValue) })\n },\n})\n\nconst { mdAndDown } = useDisplay()\nconst sidebarLeft = ref(!mdAndDown.value)\n\nconst closeSidebarOnMobile = () => {\n if (mdAndDown.value) sidebarLeft.value = false\n}\n\nprovide(SubjectSelectCloseSidebarKey, closeSidebarOnMobile)\n\nconst { t } = useI18n()\n\nconst selectedItemsCount = computed(() => {\n return props.selectedItems.length\n})\n\nconst disabledSubmit = computed(() => {\n return selectedItemsCount.value < props.minCount || selectedItemsCount.value > props.maxCount\n})\n\nconst onOpen = () => {\n emit('onOpen')\n sidebarLeft.value = !mdAndDown.value\n dialog.value = true\n}\n\nconst toggleSidebar = () => {\n sidebarLeft.value = !sidebarLeft.value\n}\n\nconst onClose = () => {\n dialog.value = false\n}\n\nconst onConfirm = () => {\n emit(\n 'onConfirm',\n props.selectedItems.map((item) => toRaw(item))\n )\n onClose()\n}\n\nconst fetchNextPage = () => {\n emit('onFetchNextPage')\n}\n\nconst onPageChange = () => {\n emit('onPageChange')\n}\n\nconst lastPage = computed(() => {\n return Math.ceil(paginationComputed.value.totalCount / paginationComputed.value.rowsPerPage)\n})\n\n// `>=`, not `===`: an empty list has `totalCount: 0`, so `lastPage` is 0 while `page` is never below\n// 1, and the two can never meet. The page would then count as \"not the last one\" on a list with\n// nothing in it -- and the same holds before any request has been made, because that initial state is\n// the same numbers.\nconst hasNextPage = computed(() => {\n return !(\n (isNull(paginationComputed.value.hasNextPage) && paginationComputed.value.page >= lastPage.value) ||\n paginationComputed.value.hasNextPage === false\n )\n})\n\nconst autoloadOnIntersect = (isIntersecting: boolean) => {\n if (isIntersecting && hasNextPage.value && !props.loading) {\n fetchNextPage()\n }\n}\n\nonMounted(() => {\n if (props.autoOpen) {\n onOpen()\n }\n})\n\ndefineExpose({\n open: onOpen,\n closeSidebarOnMobile,\n})\n</script>\n\n<template>\n <slot\n name=\"activator\"\n :props=\"{ onClick: withModifiers(() => onOpen(), ['stop']) }\"\n />\n <VDialog\n v-bind=\"$attrs\"\n :model-value=\"dialog\"\n fullscreen\n class=\"subject-select\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <VCard\n v-if=\"dialog\"\n class=\"subject-select__card\"\n >\n <ADialogToolbar\n class=\"subject-select__toolbar system-border-b\"\n @on-cancel=\"onClose\"\n >\n <slot name=\"title\">\n {{ t(dialogTitleT) }}\n </slot>\n </ADialogToolbar>\n <VToolbar\n density=\"compact\"\n color=\"transparent\"\n :height=\"46\"\n elevation=\"0\"\n class=\"system-border-b subject-select__second-bar\"\n >\n <slot name=\"second-bar\">\n <div class=\"d-flex flex-column w-100 px-1 align-center\">\n <div class=\"d-flex justify-space-between w-100 align-center\">\n <div class=\"d-flex align-center\">\n <VBtn\n icon\n :width=\"30\"\n :height=\"30\"\n @click=\"toggleSidebar\"\n >\n <VIcon\n icon=\"mdi-tune\"\n :size=\"16\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n >\n {{ t('common.subjectSelect.filter.toggle') }}\n </VTooltip>\n </VBtn>\n <slot name=\"second-bar-left\" />\n </div>\n <div class=\"d-flex align-center\">\n <slot name=\"second-bar-right\" />\n </div>\n </div>\n </div>\n </slot>\n </VToolbar>\n <div\n class=\"subject-select__main\"\n :class=\"{ 'subject-select__main--sidebar-active': sidebarLeft }\"\n >\n <div class=\"subject-select__sidebar system-border-r\">\n <div class=\"subject-select-filter\">\n <slot name=\"filter\" />\n </div>\n </div>\n <div class=\"subject-select__content\">\n <slot name=\"content\" />\n <div\n v-if=\"paginationMode === 'more'\"\n class=\"d-flex w-100 align-center justify-center pa-4\"\n >\n <ABtnSecondary\n v-show=\"hasNextPage || loading\"\n v-intersect.quiet=\"autoloadOnIntersect\"\n :loading=\"loading\"\n size=\"small\"\n @click=\"fetchNextPage\"\n >\n <slot name=\"button-confirm-title\">\n {{ t('common.button.loadMore') }}\n </slot>\n </ABtnSecondary>\n </div>\n <ADatatablePagination\n v-else\n v-model=\"paginationComputed\"\n @change=\"onPageChange\"\n />\n </div>\n </div>\n <div class=\"subject-select__actions system-border-t\">\n <div v-if=\"minCount === maxCount\">\n {{\n t('common.subjectSelect.texts.pickExactCount', {\n count: minCount,\n selected: selectedItemsCount,\n })\n }}\n </div>\n <div v-else>\n {{\n t('common.subjectSelect.texts.pickRangeCount', {\n minCount: minCount,\n maxCount: maxCount,\n selected: selectedItemsCount,\n })\n }}\n </div>\n <VSpacer />\n <ABtnPrimary\n :disabled=\"disabledSubmit\"\n @click.stop=\"onConfirm\"\n >\n <slot name=\"button-confirm-title\">\n {{ t('common.button.confirm') }}\n </slot>\n </ABtnPrimary>\n </div>\n </VCard>\n </VDialog>\n</template>\n","<script setup lang=\"ts\" generic=\"TItem extends Record<string, any>\">\nimport { computed, ref, useSlots, useTemplateRef, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport { useDisplay } from 'vuetify'\nimport { useKeyboardNav } from '@/labs/listEditor/composables/useKeyboardNav'\nimport {\n useListEditorController,\n type GetKey,\n type ListEditorHandle,\n type ListEditorValidationResult,\n type PositionAction,\n type PositionOption,\n type PositionStrategy,\n} from '@/labs/listEditor/composables/useListEditorController'\nimport {\n provideListEditorStateScope,\n useListEditorStateEntry,\n type ListEditorStateBindings,\n} from '@/labs/listEditor/composables/useListEditorStateScope'\nimport { resolveCompactText as resolveCompactTextUtil } from '@/labs/listEditor/composables/resolveCompactText'\nimport { useUnsavedSection } from '@/labs/unsavedGuard/useUnsavedSection'\nimport { useDeleteDialog } from '@/labs/listEditor/composables/useDeleteDialog'\nimport { useInlineEditing } from '@/labs/listEditor/composables/useInlineEditing'\nimport { validateAllAndReveal } from '@/labs/listEditor/utils/revealInvalidRows'\nimport { useListEditorScopeValidity } from '@/labs/listEditor/composables/useListEditorScopeValidity'\nimport LeDeleteDialog from '@/labs/listEditor/internal/LeDeleteDialog.vue'\nimport LeEmptyState from '@/labs/listEditor/internal/LeEmptyState.vue'\nimport LeUnsavedLabel from '@/labs/listEditor/internal/LeUnsavedLabel.vue'\nimport type {\n ListEditorKey,\n ListEditorValidationScope,\n ListEditorValidationState,\n ListViewItem,\n} from '@/labs/listEditor/types/listEditorTypes'\n\nexport interface DecoratedViewItem<T> extends ListViewItem<T> {\n editing: boolean\n expanded: boolean\n loading: boolean\n dirty: boolean\n unsaved: boolean\n validationState: ListEditorValidationState\n}\n\n// Hoisted for vite-plugin-dts d.ts rollup.\nexport interface RowActions<TItem> {\n edit: () => void\n save: () => Promise<void> | void\n cancel: () => void\n close: () => void\n delete: () => Promise<void>\n addAfter: () => void\n toggleExpand: () => void\n update: (next: TItem | Partial<TItem> | ((current: TItem) => TItem)) => void\n}\nexport interface RowSlotProps<TItem extends Record<string, any>> {\n item: DecoratedViewItem<TItem>\n raw: TItem\n index: number\n key: ListEditorKey\n readonly: boolean\n disabled: boolean\n expanded: boolean\n editing: boolean\n dirty: boolean\n /** Aliases `dirty` here (no reorder mode → no `moved` to OR). Same field name as on sortable/nested variants. */\n unsaved: boolean\n touch: boolean\n /**\n * Row-scoped `state-key` prefix. Thread it down to any list editor rendered in this slot and set\n * its `:state-key=\"`${stateKeyPrefix}:someList`\"` — that editor's controller then lives in THIS\n * editor's scope and survives the row collapsing (which unmounts the whole slot subtree). Entries\n * under this prefix are disposed when the row is removed.\n */\n stateKeyPrefix: string\n actions: RowActions<TItem>\n}\nexport interface EmptySlotProps {\n readonly: boolean\n disabled: boolean\n actions: { add: () => void }\n}\nexport interface AddButtonSlotProps {\n readonly: boolean\n disabled: boolean\n props: { onClick: () => void }\n actions: { add: () => void }\n}\nexport interface HeaderSlotProps {\n title: string | null\n}\n\nexport interface Props<TItem extends Record<string, any>> {\n /**\n * New-row factory. Add / \"add after\" insert `factory()` through the controller\n * (positions renumbered) — no consumer `@add` push handler is needed.\n */\n factory?: () => TItem\n /**\n * Stable row identity. Default `'id'`; never the position field. Typed `string`\n * not `keyof TItem` because the latter compiles to a Boolean-only runtime prop\n * type that silently coerces `get-key=\"id\"` to `true`.\n */\n getKey?: string | ((item: TItem) => ListEditorKey)\n /**\n * Managed order field. Default `'position'`; `false` opts out. `string` must be\n * listed before `false` (and not be `keyof TItem`): otherwise Vue's runtime\n * type goes Boolean-first and coerces `position=\"position\"` to `true`.\n */\n position?:\n | string\n | false\n | {\n field: string\n multiplier?: number\n strategy?: PositionStrategy\n strategyOverrides?: Partial<Record<PositionAction, PositionStrategy>>\n }\n /**\n * Extra fields to drop from the dirty content-hash (position is always dropped).\n * Use when a SEPARATE nested editor already tracks a child collection, so editing\n * a child doesn't flip the parent row amber. Still saved; only the diff ignores it.\n */\n dirtyExclude?: string[]\n /** Per-row validity — `true` (or `{ valid: true }`) = VALID. Drives the red rail + save guard. */\n validate?: (item: TItem) => ListEditorValidationResult\n /**\n * Vuelidate `$scope` (the same one the consumer's row forms / save-gate collector use). When set,\n * the editor registers its aggregate validity under it, so a plain `v$.$invalid` save gate blocks\n * AND reveals a collapsed invalid row — no `validateAll()` call needed in the save flow. Omit to\n * keep legacy behavior (gate the save on the exposed `validateAll()` yourself); `false` opts out.\n */\n validationScope?: ListEditorValidationScope | false\n /**\n * Opt-in lifted controller from `useListEditorController()` — pass it so editor\n * state survives this component's unmount/remount. Omitted: the editor owns one\n * internally. Either way the `ListEditorHandle` is reachable via `useTemplateRef`.\n */\n editor?: ListEditorHandle<TItem>\n /**\n * Persist this editor's controller in the nearest ANCESTOR editor's row-state scope under this\n * key, instead of owning it. Use it for an editor rendered in another editor's `#item` slot: that\n * slot is behind a `v-if`, so collapsing the row unmounts this editor and a component-owned\n * controller would re-baseline the already-edited data on re-expand (losing amber, tombstones and\n * the red rail). Derive the key from the row slot's `stateKeyPrefix`, e.g.\n * `:state-key=\"`${stateKeyPrefix}:adverts`\"`. Options stay declared here, on this tag: the\n * controller is built from THIS editor's `factory`/`get-key`/`position`/`dirty-exclude`/`validate`\n * and rebound to each new instance's model on remount. Ignored when `:editor` is passed, and a\n * no-op without an ancestor scope.\n */\n stateKey?: string\n\n readonly?: boolean\n disabled?: boolean\n loading?: boolean\n error?: string | null\n\n /** Heading above the list. A ready translated string, like every text prop here. */\n title?: string | null\n\n /**\n * Field shown in a collapsed row when no `#item-compact` slot is given. A dotted path reads\n * through nested objects (`texts.title`); a path that runs into nothing renders empty.\n */\n compactField?: string | null\n statusField?: string | null\n twoRows?: 'never' | 'mobile' | 'always'\n chips?: boolean\n\n showAddButton?: boolean\n showDeleteButton?: boolean\n showEditButton?: boolean\n showAddAfterAction?: boolean\n\n /** Label of the add button. A ready translated string; omit it for the library's own wording. */\n addLabel?: string | null\n /** Heading of the empty state. A ready translated string. */\n emptyTitle?: string | null\n\n disableRowClick?: boolean\n disableDeleteConfirm?: boolean\n /** Disable unsaved-state tracking — no dirty markers, never reads as unsaved. */\n disableUnsaved?: boolean\n deleteConfirmTitle?: string | null\n deleteConfirmText?: string | null\n /**\n * How a row delete persists. `deferred` (default): the row disappears but the deletion counts as an\n * unconfirmed change (`unsavedCount`) until save, and is revertible until then. `immediate`: the\n * consumer deletes on the backend (`:on-delete`) — the dialog states it is irreversible and the\n * delete does NOT read as unsaved.\n */\n deleteMode?: 'immediate' | 'deferred'\n\n closeVariant?: 'auto' | 'icon' | 'labeled'\n\n /**\n * Render every `#item` slot expanded — no edit pencil, inline footer, or row-click\n * toggle. Use when all forms should be visible at once (e.g. ThirdPartyTracker).\n */\n defaultExpanded?: boolean\n\n loadingKeys?: Set<ListEditorKey> | null\n\n onDeleteConfirm?: (item: TItem) => Promise<boolean> | boolean\n onDelete?: (item: TItem) => Promise<void> | void\n onItemSave?: (item: TItem) => Promise<void> | void\n\n /**\n * Registers this editor as a named unsaved-changes section under this (already\n * translated) label — replaces a per-consumer `useUnsavedSection` call.\n */\n unsavedSectionLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props<TItem>>(), {\n factory: undefined,\n getKey: undefined,\n position: undefined,\n dirtyExclude: undefined,\n validate: undefined,\n validationScope: undefined,\n editor: undefined,\n stateKey: undefined,\n readonly: false,\n disabled: false,\n loading: false,\n error: null,\n title: null,\n compactField: null,\n statusField: null,\n twoRows: 'never',\n chips: false,\n showAddButton: true,\n showDeleteButton: true,\n showEditButton: true,\n showAddAfterAction: false,\n addLabel: null,\n emptyTitle: null,\n disableRowClick: false,\n disableDeleteConfirm: false,\n disableUnsaved: false,\n deleteConfirmTitle: null,\n deleteConfirmText: null,\n deleteMode: 'deferred',\n closeVariant: 'auto',\n defaultExpanded: false,\n loadingKeys: null,\n onDeleteConfirm: undefined,\n onDelete: undefined,\n onItemSave: undefined,\n unsavedSectionLabel: undefined,\n})\n\nconst emit = defineEmits<{\n edit: [item: ListViewItem<TItem>]\n deleted: [item: ListViewItem<TItem>]\n close: [item: ListViewItem<TItem>]\n 'item-saved': [item: ListViewItem<TItem>]\n 'item-expand': [item: ListViewItem<TItem>, expanded: boolean]\n}>()\n\ndefineSlots<{\n header?: (props: HeaderSlotProps) => unknown\n empty?: (props: EmptySlotProps) => unknown\n 'add-button'?: (props: AddButtonSlotProps) => unknown\n item?: (props: RowSlotProps<TItem>) => unknown\n 'item-compact'?: (props: RowSlotProps<TItem>) => unknown\n 'item-readonly'?: (props: RowSlotProps<TItem>) => unknown\n 'item-status'?: (props: RowSlotProps<TItem>) => unknown\n 'item-actions'?: (props: RowSlotProps<TItem>) => unknown\n 'item-footer'?: (props: RowSlotProps<TItem>) => unknown\n 'before-item'?: (props: RowSlotProps<TItem>) => unknown\n 'after-item'?: (props: RowSlotProps<TItem>) => unknown\n}>()\n\nconst modelValue = defineModel<TItem[]>({ required: true })\n\nconst { t } = useI18n()\nconst slots = useSlots()\nconst display = useDisplay()\n\nconst rootEl = useTemplateRef<HTMLElement>('rootEl')\n\nconst isTouch = computed<boolean>(() => display.platform.value.touch)\n\n// State controller (v2). `factory`/`getKey`/`position`/`validate` are construction-time options read\n// once to seed it (undefined → controller defaults 'id'/'position'/always-valid), not reactive props\n// — so the reactivity-loss rule is intentionally suppressed here.\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst controllerOptions: ListEditorStateBindings<TItem> = {\n get: () => modelValue.value,\n set: (v) => (modelValue.value = v),\n factory: props.factory,\n getKey: props.getKey as GetKey<TItem> | undefined,\n position: props.position as PositionOption<TItem> | undefined,\n dirtyExclude: () => props.dirtyExclude ?? [],\n validate: props.validate,\n}\n// Precedence: an explicitly lifted `:editor` wins; then a `state-key`'d entry persisted in the\n// nearest ancestor editor's row-state scope (built from the options above, rebound to this instance\n// on every remount); else this component owns the controller — today's behaviour, unchanged.\nconst stateEntry = props.editor ? null : useListEditorStateEntry<TItem>(props.stateKey, controllerOptions)\nconst controller = props.editor ?? stateEntry?.handle ?? useListEditorController<TItem>(controllerOptions)\n\n// Mirror the controller's key resolution so rendered rows key the way it tracks them.\nconst getKeyOpt = props.getKey ?? 'id'\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\n// Row-state scope for THIS editor's `#item` slot subtree: a nested editor with a `state-key` builds\n// its controller in here, so it outlives the row collapsing (which unmounts the slot). When this\n// editor is itself persisted, the scope comes from its own entry — so the chain holds at any depth.\nconst rowStateScope = provideListEditorStateScope(stateEntry?.childScope ?? null)\nconst keyOf = (item: TItem): ListEditorKey =>\n typeof getKeyOpt === 'function' ? getKeyOpt(item) : (item[getKeyOpt as keyof TItem] as ListEditorKey)\n\nconst keyFieldName = computed<string>(() => (typeof getKeyOpt === 'function' ? '(fn)' : (getKeyOpt as string)))\n\n// Managed position field name, mirroring the controller's resolution.\nconst positionFieldName = computed<string>(() => {\n const p = props.position\n if (p === undefined) return 'position'\n if (p === false) return 'position'\n if (typeof p === 'object') return p.field as string\n return p as string\n})\n\n// Render projection only — the controller owns the data (key + index + raw + position).\nconst viewItems = computed<ListViewItem<TItem>[]>(() =>\n modelValue.value.map((raw, index) => ({\n key: keyOf(raw),\n index,\n raw,\n position: raw[positionFieldName.value] as number | undefined,\n }))\n)\n\n// Surfaces row-key wiring bugs loudly: undefined or duplicate keys silently break\n// dirty tracking, validity rails and reorder targeting and are hard to trace from\n// symptoms. Deduped per signature so a stable bad state warns once, a new one again.\nconst warnedKeySignatures = new Set<string>()\nconst warnOnBadKeys = (items: ListViewItem<TItem>[]): void => {\n const seen = new Set<ListEditorKey>()\n const duplicates = new Set<string>()\n let missing = 0\n for (const vi of items) {\n if (vi.key === undefined || vi.key === null) {\n missing++\n continue\n }\n if (seen.has(vi.key)) duplicates.add(String(vi.key))\n seen.add(vi.key)\n }\n if (missing === 0 && duplicates.size === 0) return\n const signature = `${missing}|${[...duplicates].sort().join(',')}`\n if (warnedKeySignatures.has(signature)) return\n warnedKeySignatures.add(signature)\n if (missing > 0) {\n console.warn(\n `[list-editor] ${missing} row(s) resolve to an undefined key (key-field \"${keyFieldName.value}\"). ` +\n 'Point get-key at a field every item has, or give new items unique temp ids ' +\n '(see nextListEditorTempId).'\n )\n }\n if (duplicates.size > 0) {\n console.warn(\n `[list-editor] duplicate row keys (key-field \"${keyFieldName.value}\"): ${[...duplicates].join(', ')}. ` +\n 'Row keys must be unique — dirty tracking and validation rails target rows by key.'\n )\n }\n}\nwatch(viewItems, (items) => warnOnBadKeys(items), { immediate: true })\n\n// Drop the persisted controllers of rows that no longer exist. `post` so the removed row's subtree\n// has already unmounted (its editors released their entries) before we stop their effect scopes.\nwatch(\n () => viewItems.value.map((vi) => String(vi.key)).join('|'),\n () => rowStateScope.retainOwners(viewItems.value.map((vi) => vi.key)),\n { flush: 'post' }\n)\n\nconst expandedKeys = ref<Set<ListEditorKey>>(new Set())\n\nconst rowsContainer = useTemplateRef<HTMLElement>('rowsContainer')\n\nconst isInlineEdit = computed(() => !props.chips && !!slots.item)\nconst hasReadonlyDetail = computed(() => !props.chips && !!slots['item-readonly'])\n\nconst { editingKeys, editingSnapshots, beginEdit, cancelEdit, commitEdit, closeEdit, requestAutoOpen } =\n useInlineEditing<TItem, ListViewItem<TItem>>({\n rowsContainer,\n rowSelector: '.a-le-row',\n isInlineEdit,\n restoreSnapshot: (key, data) => controller.updateItem(key, data),\n watchKeys: () => modelValue.value.map((it) => keyOf(it)),\n findEntry: (key) => {\n const hit = modelValue.value.find((it) => keyOf(it) === key)\n return hit ? { data: hit } : null\n },\n afterAutoOpen: (key) => {\n expandedKeys.value.delete(key)\n },\n })\n\nconst addLabelResolved = computed(() => props.addLabel ?? t('common.sortable.add'))\nconst emptyTitleResolved = computed(() => props.emptyTitle ?? t('common.sortable.emptyTitle'))\nconst deleteConfirmTitleResolved = computed(() => props.deleteConfirmTitle ?? t('common.sortable.deleteConfirmTitle'))\nconst deleteConfirmTextResolved = computed(\n () =>\n props.deleteConfirmText ??\n (props.deleteMode === 'immediate'\n ? t('common.sortable.deleteConfirmText')\n : t('common.sortable.deleteConfirmTextDeferred'))\n)\n\nconst canInteract = computed(() => !props.readonly && !props.disabled && !props.loading)\nconst canAdd = computed(() => canInteract.value && props.showAddButton)\n\n// Total unconfirmed-change count (added/edited/moved rows + deferred deletions). A delete lights this\n// up even though its row is gone. Shown as a header badge + exposed on the handle.\nconst unsavedCount = controller.unsavedCount\nconst unsavedCountVisible = computed(() => !props.readonly && !props.disableUnsaved && unsavedCount.value > 0)\nconst headerVisible = computed(() => !!(props.title || slots.header) || unsavedCountVisible.value)\n\n// Per-row Save/Cancel footer only makes sense with a per-item persist callback;\n// without one the parent form's global save flushes everything, so hide it.\nconst showInlineSaveFooter = computed(() => !!props.onItemSave)\n\n// Per-key decorator cache: reuse the cached object when base item AND every flag\n// match, giving slot consumers a stable reference for unchanged rows. `disableUnsaved`\n// suppresses only the amber marker — the validation rail still shows, since hiding\n// dirty-state shouldn't hide a real error.\nconst decoratorCache = new Map<ListEditorKey, DecoratedViewItem<TItem>>()\nconst viewItemsDecorated = computed<DecoratedViewItem<TItem>[]>(() => {\n const next: DecoratedViewItem<TItem>[] = []\n const liveKeys = new Set<ListEditorKey>()\n for (const vi of viewItems.value) {\n liveKeys.add(vi.key)\n const editing = editingKeys.value.has(vi.key)\n const expanded = expandedKeys.value.has(vi.key)\n const loading = props.loadingKeys?.has(vi.key) ?? false\n // Readonly can't have user-unsaved changes; never paint amber there (also avoids\n // a mount-before-load baseline marking every loaded row added). (QA 85050 sweep)\n const unsaved = props.disableUnsaved || props.readonly ? false : controller.isUnsaved(vi.key)\n const dirty = unsaved\n // `editing` → the controller reads amber (not red) while the row is being filled in. (QA 85050 b7)\n const validationState = controller.rowState(vi.raw, vi.key, editing)\n const cached = decoratorCache.get(vi.key)\n if (\n cached &&\n cached.raw === vi.raw &&\n cached.index === vi.index &&\n cached.position === vi.position &&\n cached.editing === editing &&\n cached.expanded === expanded &&\n cached.loading === loading &&\n cached.dirty === dirty &&\n cached.unsaved === unsaved &&\n cached.validationState === validationState\n ) {\n next.push(cached)\n continue\n }\n const decorated: DecoratedViewItem<TItem> = {\n ...vi,\n editing,\n expanded,\n loading,\n dirty,\n unsaved,\n validationState,\n }\n decoratorCache.set(vi.key, decorated)\n next.push(decorated)\n }\n for (const key of decoratorCache.keys()) {\n if (!liveKeys.has(key)) decoratorCache.delete(key)\n }\n return next\n})\n\nconst isEmpty = computed(() => viewItemsDecorated.value.length === 0)\n\nconst findVi = (key: ListEditorKey): DecoratedViewItem<TItem> | undefined =>\n viewItemsDecorated.value.find((v) => v.key === key)\n\nconst keyboardNav = useKeyboardNav({\n viewItems: computed(() => viewItemsDecorated.value.map((vi) => ({ key: vi.key }))),\n variant: 'flat',\n isReorderMode: ref(false),\n disabled: computed(() => !canInteract.value),\n isEditing: (key) => editingKeys.value.has(key),\n onToggleEdit: (key) => {\n const vi = findVi(key)\n if (vi) onEditClick(vi)\n },\n onCancelEdit: (key) => {\n const vi = findVi(key)\n if (vi) onCloseClick(vi)\n },\n})\n\nconst resolveCompactText = (raw: TItem): string => resolveCompactTextUtil(raw, { compactField: props.compactField })\n\n// Managed add: the controller inserts `factory()` and renumbers; the inline-editing\n// watch picks up the new key off the model change and auto-opens it.\nconst onAddClick = () => {\n if (!canAdd.value) return\n requestAutoOpen()\n controller.addItem(undefined, undefined)\n}\n\nconst onRowAddAfterClick = (vi: ListViewItem<TItem>) => {\n if (!canInteract.value) return\n requestAutoOpen()\n controller.addItem(undefined, { afterId: vi.key })\n}\n\nconst onEditClick = (vi: ListViewItem<TItem>) => {\n if (!canInteract.value) return\n // Toggle: edit while already editing closes the form, matching row-header click.\n if (editingKeys.value.has(vi.key)) {\n onCloseClick(vi)\n return\n }\n if (isInlineEdit.value) {\n beginEdit(vi)\n expandedKeys.value.delete(vi.key)\n }\n emit('edit', vi)\n}\n\nconst onExpandClick = (vi: ListViewItem<TItem>) => {\n if (props.disabled || props.loading) return\n const key = vi.key\n const currentlyExpanded = expandedKeys.value.has(key)\n if (currentlyExpanded) {\n expandedKeys.value.delete(key)\n } else {\n for (const k of expandedKeys.value) expandedKeys.value.delete(k)\n expandedKeys.value.add(key)\n }\n emit('item-expand', vi, !currentlyExpanded)\n}\n\nconst isRowClickable = (vi: DecoratedViewItem<TItem>): boolean => {\n if (props.chips) return false\n if (props.disableRowClick) return false\n if (props.defaultExpanded) return false\n if (props.disabled || props.loading) return false\n if (vi.editing || vi.expanded) return true\n if (!props.readonly && props.showEditButton) return true\n return false\n}\n\nconst onRowClick = (vi: DecoratedViewItem<TItem>) => {\n if (!isRowClickable(vi)) return\n if (vi.editing || vi.expanded) {\n onCloseClick(vi)\n return\n }\n if (props.readonly && hasReadonlyDetail.value) {\n onExpandClick(vi)\n } else {\n onEditClick(vi)\n }\n}\n\nconst {\n deleteDialog,\n deleteInFlight,\n deleteError,\n onDeleteClick: triggerDeleteClick,\n onDeleteDialogConfirm,\n onDeleteDialogCancel,\n} = useDeleteDialog<TItem, ListViewItem<TItem>>({\n onDeleteConfirm: (raw) => (props.onDeleteConfirm ? props.onDeleteConfirm(raw) : true),\n onDelete: (raw) => props.onDelete?.(raw),\n onDeleted: (vi) => {\n editingKeys.value.delete(vi.key)\n editingSnapshots.value.delete(vi.key)\n expandedKeys.value.delete(vi.key)\n // Controller owns removal: a temp row vanishes; a saved row is recorded in `getChanges().deleted`\n // and counts as an unconfirmed change (deferred) — unless immediate mode (already deleted on the\n // backend), which skips the tombstone.\n controller.deleteItem(vi.key, { trackDeleted: props.deleteMode === 'deferred' })\n emit('deleted', vi)\n },\n disableDeleteConfirm: () => props.disableDeleteConfirm || props.chips,\n})\n\nconst onDeleteClick = async (vi: ListViewItem<TItem>) => {\n if (!canInteract.value) return\n await triggerDeleteClick(vi)\n}\n\nconst onSaveClick = async (vi: ListViewItem<TItem>) => {\n if (props.onItemSave) {\n await props.onItemSave(vi.raw)\n }\n commitEdit(vi)\n emit('item-saved', vi)\n}\n\nconst onCancelClick = (vi: ListViewItem<TItem>) => {\n cancelEdit(vi)\n}\n\nconst onCloseClick = (vi: ListViewItem<TItem>) => {\n closeEdit(vi)\n expandedKeys.value.delete(vi.key)\n emit('close', vi)\n}\n\n// Per-key actions cache: each row's `actions` bundle is allocated once so slot\n// consumers get a stable identity (no prop-ref churn → no spurious re-renders).\n// Closures capture the stable key and look up the live decorator via `findVi(key)`.\ntype ActionsBundle = {\n edit: () => void\n save: () => Promise<void> | void\n cancel: () => void\n close: () => void\n delete: () => Promise<void>\n addAfter: () => void\n toggleExpand: () => void\n update: (next: TItem | Partial<TItem> | ((current: TItem) => TItem)) => void\n}\nconst actionsCache = new Map<ListEditorKey, ActionsBundle>()\nconst getActions = (key: ListEditorKey): ActionsBundle => {\n let actions = actionsCache.get(key)\n if (!actions) {\n actions = {\n edit: () => {\n const vi = findVi(key)\n if (vi) onEditClick(vi)\n },\n save: () => {\n const vi = findVi(key)\n if (vi) return onSaveClick(vi)\n },\n cancel: () => {\n const vi = findVi(key)\n if (vi) onCancelClick(vi)\n },\n close: () => {\n const vi = findVi(key)\n if (vi) onCloseClick(vi)\n },\n delete: async () => {\n const vi = findVi(key)\n if (vi) await onDeleteClick(vi)\n },\n addAfter: () => {\n const vi = findVi(key)\n if (vi) onRowAddAfterClick(vi)\n },\n toggleExpand: () => {\n const vi = findVi(key)\n if (vi) onExpandClick(vi)\n },\n update: (next) => controller.updateItem(key, next),\n }\n actionsCache.set(key, actions)\n }\n return actions\n}\n// Garbage-collect cache entries for keys that have left the model.\nwatch(\n () => viewItemsDecorated.value,\n (now) => {\n if (actionsCache.size === 0) return\n const liveKeys = new Set(now.map((v) => v.key))\n for (const key of actionsCache.keys()) {\n if (!liveKeys.has(key)) actionsCache.delete(key)\n }\n }\n)\n\nconst buildSlotProps = (vi: DecoratedViewItem<TItem>) => ({\n item: vi,\n raw: vi.raw,\n index: vi.index,\n key: vi.key,\n readonly: props.readonly,\n disabled: props.disabled,\n expanded: vi.expanded,\n editing: vi.editing || props.defaultExpanded,\n dirty: vi.dirty,\n unsaved: vi.unsaved,\n touch: isTouch.value,\n stateKeyPrefix: rowStateScope.prefixFor(vi.key),\n actions: getActions(vi.key),\n})\n\n// Named unsaved-changes section, registered only when a label is passed.\nuseUnsavedSection(() =>\n props.unsavedSectionLabel && !props.disableUnsaved\n ? { label: props.unsavedSectionLabel, dirty: controller.hasUnsaved.value }\n : []\n)\n\n// Opens invalid rows so a blocked save surfaces WHICH rows are wrong instead of a collapsed red\n// rail. The body is gated on `editing` not `expanded`, so drive `beginEdit` not `expandedKeys`\n// (QA 85050 B4-17). Shared by the exposed `validateAll` and the scope-collector reveal-on-touch.\nconst runValidateAndReveal = (): boolean =>\n validateAllAndReveal(controller, (key) => {\n const vi = viewItems.value.find((v) => v.key === key)\n if (vi) beginEdit(vi)\n })\n\n// Auto-bridge the editor's aggregate validity into the consumer's `validation-scope` collector so a\n// plain `$invalid` save gate blocks (and reveals) a collapsed invalid row. No-op without the prop.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss -- construction-time opt-in, read once\nuseListEditorScopeValidity({\n hasErrors: controller.hasErrors,\n validationScope: props.validationScope,\n validateProvided: props.validate !== undefined,\n reveal: runValidateAndReveal,\n})\n\n// Expose the controller handle via `useTemplateRef<ListEditorHandle<TItem>>`.\ndefineExpose<ListEditorHandle<TItem>>({\n ...controller,\n validateAll: runValidateAndReveal,\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"a-list-editor\"\n :class=\"[\n `a-list-editor--two-rows-${twoRows}`,\n {\n 'a-list-editor--readonly': readonly,\n 'a-list-editor--disabled': disabled,\n 'a-list-editor--touch': isTouch,\n 'a-list-editor--chips': chips,\n },\n ]\"\n >\n <div\n ref=\"rowsContainer\"\n class=\"a-le-card\"\n >\n <div\n v-if=\"headerVisible\"\n class=\"a-le-header\"\n >\n <slot\n name=\"header\"\n :title=\"title\"\n >\n <h3\n v-if=\"title\"\n class=\"a-le-title-heading\"\n >\n {{ title }}\n </h3>\n </slot>\n <span\n v-if=\"unsavedCountVisible\"\n class=\"a-le-unsaved-count\"\n :data-unsaved-count=\"unsavedCount\"\n >\n {{ t('common.sortable.pendingChanges', { count: unsavedCount }) }}\n </span>\n </div>\n\n <div\n v-if=\"loading\"\n class=\"a-le-state a-le-state--loading\"\n >\n <VProgressCircular\n indeterminate\n color=\"primary\"\n size=\"32\"\n />\n </div>\n\n <div\n v-else-if=\"error\"\n class=\"a-le-state a-le-state--error\"\n >\n <VAlert\n type=\"error\"\n variant=\"tonal\"\n density=\"compact\"\n border=\"start\"\n >\n {{ error }}\n </VAlert>\n </div>\n\n <div\n v-else-if=\"isEmpty\"\n class=\"a-le-state a-le-state--empty\"\n >\n <slot\n name=\"empty\"\n :readonly=\"readonly\"\n :disabled=\"disabled\"\n :actions=\"{ add: onAddClick }\"\n >\n <LeEmptyState\n :title=\"emptyTitleResolved\"\n :add-label=\"addLabelResolved\"\n :can-add=\"canAdd\"\n @add=\"onAddClick\"\n />\n </slot>\n </div>\n\n <template v-else>\n <div\n v-for=\"vi in viewItemsDecorated\"\n :key=\"String(vi.key)\"\n class=\"a-le-row-wrapper\"\n >\n <!-- Interstitial slots render as SIBLINGS of `.a-le-row` (inside this\n wrapper), so consumer content placed between rows never inherits the\n row's unsaved/editing/validation tint. (QA 85050 BUG-04/05) -->\n <slot\n name=\"before-item\"\n v-bind=\"buildSlotProps(vi)\"\n />\n\n <div\n :data-id=\"String(vi.key)\"\n role=\"listitem\"\n :tabindex=\"keyboardNav.rowTabindex(vi.key)\"\n class=\"a-le-row\"\n :class=\"{\n 'a-le-row--two-rows': twoRows === 'always',\n 'a-le-row--editing': vi.editing,\n 'a-le-row--expanded': vi.expanded,\n 'a-le-row--unsaved': vi.unsaved,\n 'a-le-row--clickable': isRowClickable(vi),\n [`a-le-row--validation-${vi.validationState}`]: vi.validationState !== null,\n }\"\n @keydown=\"keyboardNav.handleKeydown(vi.key, $event)\"\n >\n <div\n class=\"a-le-row-header\"\n @click=\"onRowClick(vi)\"\n >\n <div class=\"a-le-row-main\">\n <slot\n name=\"item-compact\"\n v-bind=\"buildSlotProps(vi)\"\n >\n <span class=\"a-le-title\">\n {{ resolveCompactText(vi.raw) }}\n </span>\n </slot>\n <LeUnsavedLabel\n v-if=\"vi.unsaved\"\n :dot-only=\"chips\"\n />\n </div>\n\n <div\n v-if=\"!chips\"\n class=\"a-le-status\"\n >\n <slot\n name=\"item-status\"\n v-bind=\"buildSlotProps(vi)\"\n >\n <span\n v-if=\"statusField && vi.raw[statusField] != null && vi.raw[statusField] !== ''\"\n class=\"a-le-status-badge\"\n >\n {{ vi.raw[statusField] }}\n </span>\n </slot>\n </div>\n\n <div class=\"a-le-actions\">\n <slot\n name=\"item-actions\"\n v-bind=\"buildSlotProps(vi)\"\n >\n <VBtn\n v-if=\"chips && showDeleteButton && canInteract\"\n icon\n size=\"x-small\"\n variant=\"text\"\n density=\"compact\"\n :active=\"false\"\n class=\"a-le-action a-le-action--chip-close\"\n @click.stop=\"onDeleteClick(vi)\"\n >\n <VIcon\n icon=\"mdi-close\"\n size=\"14\"\n />\n </VBtn>\n <template v-else>\n <VBtn\n v-if=\"showEditButton && canInteract && !defaultExpanded\"\n icon\n size=\"small\"\n variant=\"tonal\"\n color=\"primary\"\n density=\"comfortable\"\n class=\"mx-1 a-le-action a-le-action--edit\"\n @click.stop=\"onEditClick(vi)\"\n >\n <VIcon\n icon=\"mdi-pencil-outline\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.edit')\"\n />\n </VBtn>\n <VBtn\n v-if=\"showDeleteButton && canInteract\"\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n class=\"mx-1 a-le-action a-le-action--delete\"\n @click.stop=\"onDeleteClick(vi)\"\n >\n <VIcon\n icon=\"mdi-trash-can-outline\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.delete')\"\n />\n </VBtn>\n <VBtn\n v-if=\"showAddAfterAction && canInteract\"\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :active=\"false\"\n class=\"mx-1 a-le-action a-le-action--menu\"\n >\n <VIcon\n icon=\"mdi-dots-vertical\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.more')\"\n />\n <VMenu activator=\"parent\">\n <VList density=\"compact\">\n <VListItem @click=\"onRowAddAfterClick(vi)\">\n <template #prepend>\n <VIcon icon=\"mdi-plus\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.addAfter') }}\n </VListItemTitle>\n </VListItem>\n </VList>\n </VMenu>\n </VBtn>\n </template>\n </slot>\n </div>\n </div>\n\n <template v-if=\"(vi.editing || defaultExpanded) && $slots.item\">\n <div class=\"a-le-row-body\">\n <div class=\"a-le-form\">\n <slot\n name=\"item\"\n v-bind=\"buildSlotProps(vi)\"\n />\n </div>\n </div>\n <slot\n name=\"item-footer\"\n v-bind=\"buildSlotProps(vi)\"\n >\n <div\n v-if=\"showInlineSaveFooter && !defaultExpanded\"\n class=\"a-le-row-footer\"\n >\n <div class=\"a-le-row-footer-spacer\" />\n <VBtn\n variant=\"text\"\n :disabled=\"vi.loading\"\n @click.stop=\"onCancelClick(vi)\"\n >\n {{ t('common.button.cancel') }}\n </VBtn>\n <VBtn\n color=\"primary\"\n variant=\"flat\"\n prepend-icon=\"mdi-check\"\n :disabled=\"vi.loading\"\n @click.stop=\"onSaveClick(vi)\"\n >\n {{ t('common.button.save') }}\n </VBtn>\n </div>\n </slot>\n </template>\n\n <div\n v-else-if=\"$slots['item-readonly'] && (readonly || vi.expanded)\"\n class=\"a-le-row-body\"\n >\n <div class=\"a-le-form\">\n <slot\n name=\"item-readonly\"\n v-bind=\"buildSlotProps(vi)\"\n />\n </div>\n </div>\n </div>\n\n <slot\n name=\"after-item\"\n v-bind=\"buildSlotProps(vi)\"\n />\n </div>\n </template>\n\n <slot\n v-if=\"canAdd && !loading && !error && !isEmpty\"\n name=\"add-button\"\n :readonly=\"readonly\"\n :disabled=\"disabled\"\n :props=\"{ onClick: onAddClick }\"\n :actions=\"{ add: onAddClick }\"\n >\n <button\n type=\"button\"\n class=\"a-le-row-add\"\n @click=\"onAddClick\"\n >\n <VIcon\n icon=\"mdi-plus\"\n size=\"18\"\n />\n <span>{{ addLabelResolved }}</span>\n </button>\n </slot>\n </div>\n\n <LeDeleteDialog\n v-model=\"deleteDialog\"\n :title=\"deleteConfirmTitleResolved\"\n :text=\"deleteConfirmTextResolved\"\n :confirm-label=\"t('common.sortable.delete')\"\n :cancel-label=\"t('common.button.cancel')\"\n :error=\"deleteError\"\n :in-flight=\"deleteInFlight\"\n @confirm=\"onDeleteDialogConfirm\"\n @cancel=\"onDeleteDialogCancel\"\n />\n\n <div\n class=\"a-le-sr-only\"\n aria-live=\"polite\"\n role=\"status\"\n >\n {{\n keyboardNav.grabbedKey.value !== null\n ? t('common.sortable.keyboardGrab.status', {\n n: keyboardNav.grabbedIndex.value + 1,\n total: keyboardNav.totalCount.value,\n })\n : ''\n }}\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n@use './styles/tokens';\n@use './styles/shared';\n\n// Variant-specific rules, scoped under the AListEditor root.\n.a-list-editor {\n // Chips: 12px left padding (flat) vs 8px in sortable, which reserves drag-handle room.\n &--chips .a-le-row-header {\n padding: 2px 4px 2px 12px;\n gap: 4px;\n min-height: 28px;\n }\n}\n\n// Narrow/mobile layout: taller rows, always-visible actions, status badge dropped for room.\n/* stylelint-disable selector-max-compound-selectors */\n@container le-shell (max-width: 768px) {\n .a-list-editor {\n --le-row-min-height: 48px;\n --le-row-pad-y: 10px;\n\n .a-le-row:not(.a-le-row--editing) .a-le-status {\n display: none;\n }\n\n .a-le-row .a-le-action--edit,\n .a-le-row .a-le-action--delete,\n .a-le-row .a-le-action--menu {\n opacity: 1;\n }\n }\n}\n/* stylelint-enable selector-max-compound-selectors */\n</style>\n","<script setup lang=\"ts\" generic=\"TItem extends Record<string, any>\">\nimport { computed, ref, useSlots, useTemplateRef, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport { useDisplay } from 'vuetify'\nimport { useKeyboardNav } from '@/labs/listEditor/composables/useKeyboardNav'\nimport {\n useListEditorController,\n type GetKey,\n type ListEditorHandle,\n type ListEditorValidationResult,\n type PositionAction,\n type PositionOption,\n type PositionStrategy,\n} from '@/labs/listEditor/composables/useListEditorController'\nimport {\n provideListEditorStateScope,\n useListEditorStateEntry,\n type ListEditorStateBindings,\n} from '@/labs/listEditor/composables/useListEditorStateScope'\nimport { resolveCompactText as resolveCompactTextUtil } from '@/labs/listEditor/composables/resolveCompactText'\nimport { useUnsavedSection } from '@/labs/unsavedGuard/useUnsavedSection'\nimport { useDeleteDialog } from '@/labs/listEditor/composables/useDeleteDialog'\nimport { useInlineEditing } from '@/labs/listEditor/composables/useInlineEditing'\nimport { validateAllAndReveal } from '@/labs/listEditor/utils/revealInvalidRows'\nimport { useListEditorScopeValidity } from '@/labs/listEditor/composables/useListEditorScopeValidity'\nimport LeDeleteDialog from '@/labs/listEditor/internal/LeDeleteDialog.vue'\nimport LeEmptyState from '@/labs/listEditor/internal/LeEmptyState.vue'\nimport LeUnsavedLabel from '@/labs/listEditor/internal/LeUnsavedLabel.vue'\nimport type {\n ListEditorKey,\n ListEditorValidationScope,\n ListEditorValidationState,\n ListViewItem,\n} from '@/labs/listEditor/types/listEditorTypes'\n\nexport interface DecoratedViewItem<T> extends ListViewItem<T> {\n editing: boolean\n expanded: boolean\n loading: boolean\n dirty: boolean\n unsaved: boolean\n validationState: ListEditorValidationState\n}\n\n// Hoisted for vite-plugin-dts d.ts rollup.\nexport interface RowActions<TItem> {\n edit: () => void\n save: () => Promise<void> | void\n cancel: () => void\n close: () => void\n delete: () => Promise<void>\n addAfter: () => void\n toggleExpand: () => void\n update: (next: TItem | Partial<TItem> | ((current: TItem) => TItem)) => void\n}\nexport interface RowSlotProps<TItem extends Record<string, any>> {\n item: DecoratedViewItem<TItem>\n raw: TItem\n index: number\n key: ListEditorKey\n readonly: boolean\n disabled: boolean\n expanded: boolean\n editing: boolean\n dirty: boolean\n /** Aliases `dirty` here (no reorder mode → no `moved` to OR). Same field name as on sortable/nested variants. */\n unsaved: boolean\n touch: boolean\n /**\n * Row-scoped `state-key` prefix. Thread it down to any list editor rendered in this slot and set\n * its `:state-key=\"`${stateKeyPrefix}:someList`\"` — that editor's controller then lives in THIS\n * editor's scope and survives the row collapsing (which unmounts the whole slot subtree). Entries\n * under this prefix are disposed when the row is removed.\n */\n stateKeyPrefix: string\n actions: RowActions<TItem>\n}\nexport interface EmptySlotProps {\n readonly: boolean\n disabled: boolean\n actions: { add: () => void }\n}\nexport interface AddButtonSlotProps {\n readonly: boolean\n disabled: boolean\n props: { onClick: () => void }\n actions: { add: () => void }\n}\nexport interface HeaderSlotProps {\n title: string | null\n}\n\nexport interface Props<TItem extends Record<string, any>> {\n /**\n * New-row factory. Add / \"add after\" insert `factory()` through the controller\n * (positions renumbered) — no consumer `@add` push handler is needed.\n */\n factory?: () => TItem\n /**\n * Stable row identity. Default `'id'`; never the position field. Typed `string`\n * not `keyof TItem` because the latter compiles to a Boolean-only runtime prop\n * type that silently coerces `get-key=\"id\"` to `true`.\n */\n getKey?: string | ((item: TItem) => ListEditorKey)\n /**\n * Managed order field. Default `'position'`; `false` opts out. `string` must be\n * listed before `false` (and not be `keyof TItem`): otherwise Vue's runtime\n * type goes Boolean-first and coerces `position=\"position\"` to `true`.\n */\n position?:\n | string\n | false\n | {\n field: string\n multiplier?: number\n strategy?: PositionStrategy\n strategyOverrides?: Partial<Record<PositionAction, PositionStrategy>>\n }\n /**\n * Extra fields to drop from the dirty content-hash (position is always dropped).\n * Use when a SEPARATE nested editor already tracks a child collection, so editing\n * a child doesn't flip the parent row amber. Still saved; only the diff ignores it.\n */\n dirtyExclude?: string[]\n /** Per-row validity — `true` (or `{ valid: true }`) = VALID. Drives the red rail + save guard. */\n validate?: (item: TItem) => ListEditorValidationResult\n /**\n * Vuelidate `$scope` (the same one the consumer's row forms / save-gate collector use). When set,\n * the editor registers its aggregate validity under it, so a plain `v$.$invalid` save gate blocks\n * AND reveals a collapsed invalid row — no `validateAll()` call needed in the save flow. Omit to\n * keep legacy behavior (gate the save on the exposed `validateAll()` yourself); `false` opts out.\n */\n validationScope?: ListEditorValidationScope | false\n /**\n * Opt-in lifted controller from `useListEditorController()` — pass it so editor\n * state survives this component's unmount/remount. Omitted: the editor owns one\n * internally. Either way the `ListEditorHandle` is reachable via `useTemplateRef`.\n */\n editor?: ListEditorHandle<TItem>\n /**\n * Persist this editor's controller in the nearest ANCESTOR editor's row-state scope under this\n * key, instead of owning it. Use it for an editor rendered in another editor's `#item` slot: that\n * slot is behind a `v-if`, so collapsing the row unmounts this editor and a component-owned\n * controller would re-baseline the already-edited data on re-expand (losing amber, tombstones and\n * the red rail). Derive the key from the row slot's `stateKeyPrefix`, e.g.\n * `:state-key=\"`${stateKeyPrefix}:adverts`\"`. Options stay declared here, on this tag: the\n * controller is built from THIS editor's `factory`/`get-key`/`position`/`dirty-exclude`/`validate`\n * and rebound to each new instance's model on remount. Ignored when `:editor` is passed, and a\n * no-op without an ancestor scope.\n */\n stateKey?: string\n\n readonly?: boolean\n disabled?: boolean\n loading?: boolean\n error?: string | null\n\n /** Heading above the list. A ready translated string, like every text prop here. */\n title?: string | null\n\n /**\n * Field shown in a collapsed row when no `#item-compact` slot is given. A dotted path reads\n * through nested objects (`texts.title`); a path that runs into nothing renders empty.\n */\n compactField?: string | null\n statusField?: string | null\n twoRows?: 'never' | 'mobile' | 'always'\n chips?: boolean\n\n showAddButton?: boolean\n showDeleteButton?: boolean\n showEditButton?: boolean\n showAddAfterAction?: boolean\n\n /** Label of the add button. A ready translated string; omit it for the library's own wording. */\n addLabel?: string | null\n /** Heading of the empty state. A ready translated string. */\n emptyTitle?: string | null\n\n disableRowClick?: boolean\n disableDeleteConfirm?: boolean\n /** Disable unsaved-state tracking — no dirty markers, never reads as unsaved. */\n disableUnsaved?: boolean\n deleteConfirmTitle?: string | null\n deleteConfirmText?: string | null\n /**\n * How a row delete persists. `deferred` (default): the row disappears but the deletion counts as an\n * unconfirmed change (`unsavedCount`) until save, and is revertible until then. `immediate`: the\n * consumer deletes on the backend (`:on-delete`) — the dialog states it is irreversible and the\n * delete does NOT read as unsaved.\n */\n deleteMode?: 'immediate' | 'deferred'\n\n closeVariant?: 'auto' | 'icon' | 'labeled'\n\n /**\n * Render every `#item` slot expanded — no edit pencil, inline footer, or row-click\n * toggle. Use when all forms should be visible at once (e.g. ThirdPartyTracker).\n */\n defaultExpanded?: boolean\n\n loadingKeys?: Set<ListEditorKey> | null\n\n onDeleteConfirm?: (item: TItem) => Promise<boolean> | boolean\n onDelete?: (item: TItem) => Promise<void> | void\n onItemSave?: (item: TItem) => Promise<void> | void\n\n /**\n * Registers this editor as a named unsaved-changes section under this (already\n * translated) label — replaces a per-consumer `useUnsavedSection` call.\n */\n unsavedSectionLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props<TItem>>(), {\n factory: undefined,\n getKey: undefined,\n position: undefined,\n dirtyExclude: undefined,\n validate: undefined,\n validationScope: undefined,\n editor: undefined,\n stateKey: undefined,\n readonly: false,\n disabled: false,\n loading: false,\n error: null,\n title: null,\n compactField: null,\n statusField: null,\n twoRows: 'never',\n chips: false,\n showAddButton: true,\n showDeleteButton: true,\n showEditButton: true,\n showAddAfterAction: false,\n addLabel: null,\n emptyTitle: null,\n disableRowClick: false,\n disableDeleteConfirm: false,\n disableUnsaved: false,\n deleteConfirmTitle: null,\n deleteConfirmText: null,\n deleteMode: 'deferred',\n closeVariant: 'auto',\n defaultExpanded: false,\n loadingKeys: null,\n onDeleteConfirm: undefined,\n onDelete: undefined,\n onItemSave: undefined,\n unsavedSectionLabel: undefined,\n})\n\nconst emit = defineEmits<{\n edit: [item: ListViewItem<TItem>]\n deleted: [item: ListViewItem<TItem>]\n close: [item: ListViewItem<TItem>]\n 'item-saved': [item: ListViewItem<TItem>]\n 'item-expand': [item: ListViewItem<TItem>, expanded: boolean]\n}>()\n\ndefineSlots<{\n header?: (props: HeaderSlotProps) => unknown\n empty?: (props: EmptySlotProps) => unknown\n 'add-button'?: (props: AddButtonSlotProps) => unknown\n item?: (props: RowSlotProps<TItem>) => unknown\n 'item-compact'?: (props: RowSlotProps<TItem>) => unknown\n 'item-readonly'?: (props: RowSlotProps<TItem>) => unknown\n 'item-status'?: (props: RowSlotProps<TItem>) => unknown\n 'item-actions'?: (props: RowSlotProps<TItem>) => unknown\n 'item-footer'?: (props: RowSlotProps<TItem>) => unknown\n 'before-item'?: (props: RowSlotProps<TItem>) => unknown\n 'after-item'?: (props: RowSlotProps<TItem>) => unknown\n}>()\n\nconst modelValue = defineModel<TItem[]>({ required: true })\n\nconst { t } = useI18n()\nconst slots = useSlots()\nconst display = useDisplay()\n\nconst rootEl = useTemplateRef<HTMLElement>('rootEl')\n\nconst isTouch = computed<boolean>(() => display.platform.value.touch)\n\n// State controller (v2). `factory`/`getKey`/`position`/`validate` are construction-time options read\n// once to seed it (undefined → controller defaults 'id'/'position'/always-valid), not reactive props\n// — so the reactivity-loss rule is intentionally suppressed here.\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst controllerOptions: ListEditorStateBindings<TItem> = {\n get: () => modelValue.value,\n set: (v) => (modelValue.value = v),\n factory: props.factory,\n getKey: props.getKey as GetKey<TItem> | undefined,\n position: props.position as PositionOption<TItem> | undefined,\n dirtyExclude: () => props.dirtyExclude ?? [],\n validate: props.validate,\n}\n// Precedence: an explicitly lifted `:editor` wins; then a `state-key`'d entry persisted in the\n// nearest ancestor editor's row-state scope (built from the options above, rebound to this instance\n// on every remount); else this component owns the controller — today's behaviour, unchanged.\nconst stateEntry = props.editor ? null : useListEditorStateEntry<TItem>(props.stateKey, controllerOptions)\nconst controller = props.editor ?? stateEntry?.handle ?? useListEditorController<TItem>(controllerOptions)\n\n// Mirror the controller's key resolution so rendered rows key the way it tracks them.\nconst getKeyOpt = props.getKey ?? 'id'\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\n// Row-state scope for THIS editor's `#item` slot subtree: a nested editor with a `state-key` builds\n// its controller in here, so it outlives the row collapsing (which unmounts the slot). When this\n// editor is itself persisted, the scope comes from its own entry — so the chain holds at any depth.\nconst rowStateScope = provideListEditorStateScope(stateEntry?.childScope ?? null)\nconst keyOf = (item: TItem): ListEditorKey =>\n typeof getKeyOpt === 'function' ? getKeyOpt(item) : (item[getKeyOpt as keyof TItem] as ListEditorKey)\n\nconst keyFieldName = computed<string>(() => (typeof getKeyOpt === 'function' ? '(fn)' : (getKeyOpt as string)))\n\n// Managed position field name, mirroring the controller's resolution.\nconst positionFieldName = computed<string>(() => {\n const p = props.position\n if (p === undefined) return 'position'\n if (p === false) return 'position'\n if (typeof p === 'object') return p.field as string\n return p as string\n})\n\n// Render projection only — the controller owns the data (key + index + raw + position).\nconst viewItems = computed<ListViewItem<TItem>[]>(() =>\n modelValue.value.map((raw, index) => ({\n key: keyOf(raw),\n index,\n raw,\n position: raw[positionFieldName.value] as number | undefined,\n }))\n)\n\n// Surfaces row-key wiring bugs loudly: undefined or duplicate keys silently break\n// dirty tracking, validity rails and reorder targeting and are hard to trace from\n// symptoms. Deduped per signature so a stable bad state warns once, a new one again.\nconst warnedKeySignatures = new Set<string>()\nconst warnOnBadKeys = (items: ListViewItem<TItem>[]): void => {\n const seen = new Set<ListEditorKey>()\n const duplicates = new Set<string>()\n let missing = 0\n for (const vi of items) {\n if (vi.key === undefined || vi.key === null) {\n missing++\n continue\n }\n if (seen.has(vi.key)) duplicates.add(String(vi.key))\n seen.add(vi.key)\n }\n if (missing === 0 && duplicates.size === 0) return\n const signature = `${missing}|${[...duplicates].sort().join(',')}`\n if (warnedKeySignatures.has(signature)) return\n warnedKeySignatures.add(signature)\n if (missing > 0) {\n console.warn(\n `[list-editor] ${missing} row(s) resolve to an undefined key (key-field \"${keyFieldName.value}\"). ` +\n 'Point get-key at a field every item has, or give new items unique temp ids ' +\n '(see nextListEditorTempId).'\n )\n }\n if (duplicates.size > 0) {\n console.warn(\n `[list-editor] duplicate row keys (key-field \"${keyFieldName.value}\"): ${[...duplicates].join(', ')}. ` +\n 'Row keys must be unique — dirty tracking and validation rails target rows by key.'\n )\n }\n}\nwatch(viewItems, (items) => warnOnBadKeys(items), { immediate: true })\n\n// Drop the persisted controllers of rows that no longer exist. `post` so the removed row's subtree\n// has already unmounted (its editors released their entries) before we stop their effect scopes.\nwatch(\n () => viewItems.value.map((vi) => String(vi.key)).join('|'),\n () => rowStateScope.retainOwners(viewItems.value.map((vi) => vi.key)),\n { flush: 'post' }\n)\n\nconst expandedKeys = ref<Set<ListEditorKey>>(new Set())\n\nconst rowsContainer = useTemplateRef<HTMLElement>('rowsContainer')\n\nconst isInlineEdit = computed(() => !props.chips && !!slots.item)\nconst hasReadonlyDetail = computed(() => !props.chips && !!slots['item-readonly'])\n\nconst { editingKeys, editingSnapshots, beginEdit, cancelEdit, commitEdit, closeEdit, requestAutoOpen } =\n useInlineEditing<TItem, ListViewItem<TItem>>({\n rowsContainer,\n rowSelector: '.a-le-row',\n isInlineEdit,\n restoreSnapshot: (key, data) => controller.updateItem(key, data),\n watchKeys: () => modelValue.value.map((it) => keyOf(it)),\n findEntry: (key) => {\n const hit = modelValue.value.find((it) => keyOf(it) === key)\n return hit ? { data: hit } : null\n },\n afterAutoOpen: (key) => {\n expandedKeys.value.delete(key)\n },\n })\n\nconst addLabelResolved = computed(() => props.addLabel ?? t('common.sortable.add'))\nconst emptyTitleResolved = computed(() => props.emptyTitle ?? t('common.sortable.emptyTitle'))\nconst deleteConfirmTitleResolved = computed(() => props.deleteConfirmTitle ?? t('common.sortable.deleteConfirmTitle'))\nconst deleteConfirmTextResolved = computed(\n () =>\n props.deleteConfirmText ??\n (props.deleteMode === 'immediate'\n ? t('common.sortable.deleteConfirmText')\n : t('common.sortable.deleteConfirmTextDeferred'))\n)\n\nconst canInteract = computed(() => !props.readonly && !props.disabled && !props.loading)\nconst canAdd = computed(() => canInteract.value && props.showAddButton)\n\n// Total unconfirmed-change count (added/edited/moved rows + deferred deletions). A delete lights this\n// up even though its row is gone. Shown as a header badge + exposed on the handle.\nconst unsavedCount = controller.unsavedCount\nconst unsavedCountVisible = computed(() => !props.readonly && !props.disableUnsaved && unsavedCount.value > 0)\nconst headerVisible = computed(() => !!(props.title || slots.header) || unsavedCountVisible.value)\n\n// Per-row Save/Cancel footer only makes sense with a per-item persist callback;\n// without one the parent form's global save flushes everything, so hide it.\nconst showInlineSaveFooter = computed(() => !!props.onItemSave)\n\n// Per-key decorator cache: reuse the cached object when base item AND every flag\n// match, giving slot consumers a stable reference for unchanged rows. `disableUnsaved`\n// suppresses only the amber marker — the validation rail still shows, since hiding\n// dirty-state shouldn't hide a real error.\nconst decoratorCache = new Map<ListEditorKey, DecoratedViewItem<TItem>>()\nconst viewItemsDecorated = computed<DecoratedViewItem<TItem>[]>(() => {\n const next: DecoratedViewItem<TItem>[] = []\n const liveKeys = new Set<ListEditorKey>()\n for (const vi of viewItems.value) {\n liveKeys.add(vi.key)\n const editing = editingKeys.value.has(vi.key)\n const expanded = expandedKeys.value.has(vi.key)\n const loading = props.loadingKeys?.has(vi.key) ?? false\n // Readonly can't have user-unsaved changes; never paint amber there (also avoids\n // a mount-before-load baseline marking every loaded row added). (QA 85050 sweep)\n const unsaved = props.disableUnsaved || props.readonly ? false : controller.isUnsaved(vi.key)\n const dirty = unsaved\n // `editing` → the controller reads amber (not red) while the row is being filled in. (QA 85050 b7)\n const validationState = controller.rowState(vi.raw, vi.key, editing)\n const cached = decoratorCache.get(vi.key)\n if (\n cached &&\n cached.raw === vi.raw &&\n cached.index === vi.index &&\n cached.position === vi.position &&\n cached.editing === editing &&\n cached.expanded === expanded &&\n cached.loading === loading &&\n cached.dirty === dirty &&\n cached.unsaved === unsaved &&\n cached.validationState === validationState\n ) {\n next.push(cached)\n continue\n }\n const decorated: DecoratedViewItem<TItem> = {\n ...vi,\n editing,\n expanded,\n loading,\n dirty,\n unsaved,\n validationState,\n }\n decoratorCache.set(vi.key, decorated)\n next.push(decorated)\n }\n for (const key of decoratorCache.keys()) {\n if (!liveKeys.has(key)) decoratorCache.delete(key)\n }\n return next\n})\n\nconst isEmpty = computed(() => viewItemsDecorated.value.length === 0)\n\nconst findVi = (key: ListEditorKey): DecoratedViewItem<TItem> | undefined =>\n viewItemsDecorated.value.find((v) => v.key === key)\n\nconst keyboardNav = useKeyboardNav({\n viewItems: computed(() => viewItemsDecorated.value.map((vi) => ({ key: vi.key }))),\n variant: 'flat',\n isReorderMode: ref(false),\n disabled: computed(() => !canInteract.value),\n isEditing: (key) => editingKeys.value.has(key),\n onToggleEdit: (key) => {\n const vi = findVi(key)\n if (vi) onEditClick(vi)\n },\n onCancelEdit: (key) => {\n const vi = findVi(key)\n if (vi) onCloseClick(vi)\n },\n})\n\nconst resolveCompactText = (raw: TItem): string => resolveCompactTextUtil(raw, { compactField: props.compactField })\n\n// Managed add: the controller inserts `factory()` and renumbers; the inline-editing\n// watch picks up the new key off the model change and auto-opens it.\nconst onAddClick = () => {\n if (!canAdd.value) return\n requestAutoOpen()\n controller.addItem(undefined, undefined)\n}\n\nconst onRowAddAfterClick = (vi: ListViewItem<TItem>) => {\n if (!canInteract.value) return\n requestAutoOpen()\n controller.addItem(undefined, { afterId: vi.key })\n}\n\nconst onEditClick = (vi: ListViewItem<TItem>) => {\n if (!canInteract.value) return\n // Toggle: edit while already editing closes the form, matching row-header click.\n if (editingKeys.value.has(vi.key)) {\n onCloseClick(vi)\n return\n }\n if (isInlineEdit.value) {\n beginEdit(vi)\n expandedKeys.value.delete(vi.key)\n }\n emit('edit', vi)\n}\n\nconst onExpandClick = (vi: ListViewItem<TItem>) => {\n if (props.disabled || props.loading) return\n const key = vi.key\n const currentlyExpanded = expandedKeys.value.has(key)\n if (currentlyExpanded) {\n expandedKeys.value.delete(key)\n } else {\n for (const k of expandedKeys.value) expandedKeys.value.delete(k)\n expandedKeys.value.add(key)\n }\n emit('item-expand', vi, !currentlyExpanded)\n}\n\nconst isRowClickable = (vi: DecoratedViewItem<TItem>): boolean => {\n if (props.chips) return false\n if (props.disableRowClick) return false\n if (props.defaultExpanded) return false\n if (props.disabled || props.loading) return false\n if (vi.editing || vi.expanded) return true\n if (!props.readonly && props.showEditButton) return true\n return false\n}\n\nconst onRowClick = (vi: DecoratedViewItem<TItem>) => {\n if (!isRowClickable(vi)) return\n if (vi.editing || vi.expanded) {\n onCloseClick(vi)\n return\n }\n if (props.readonly && hasReadonlyDetail.value) {\n onExpandClick(vi)\n } else {\n onEditClick(vi)\n }\n}\n\nconst {\n deleteDialog,\n deleteInFlight,\n deleteError,\n onDeleteClick: triggerDeleteClick,\n onDeleteDialogConfirm,\n onDeleteDialogCancel,\n} = useDeleteDialog<TItem, ListViewItem<TItem>>({\n onDeleteConfirm: (raw) => (props.onDeleteConfirm ? props.onDeleteConfirm(raw) : true),\n onDelete: (raw) => props.onDelete?.(raw),\n onDeleted: (vi) => {\n editingKeys.value.delete(vi.key)\n editingSnapshots.value.delete(vi.key)\n expandedKeys.value.delete(vi.key)\n // Controller owns removal: a temp row vanishes; a saved row is recorded in `getChanges().deleted`\n // and counts as an unconfirmed change (deferred) — unless immediate mode (already deleted on the\n // backend), which skips the tombstone.\n controller.deleteItem(vi.key, { trackDeleted: props.deleteMode === 'deferred' })\n emit('deleted', vi)\n },\n disableDeleteConfirm: () => props.disableDeleteConfirm || props.chips,\n})\n\nconst onDeleteClick = async (vi: ListViewItem<TItem>) => {\n if (!canInteract.value) return\n await triggerDeleteClick(vi)\n}\n\nconst onSaveClick = async (vi: ListViewItem<TItem>) => {\n if (props.onItemSave) {\n await props.onItemSave(vi.raw)\n }\n commitEdit(vi)\n emit('item-saved', vi)\n}\n\nconst onCancelClick = (vi: ListViewItem<TItem>) => {\n cancelEdit(vi)\n}\n\nconst onCloseClick = (vi: ListViewItem<TItem>) => {\n closeEdit(vi)\n expandedKeys.value.delete(vi.key)\n emit('close', vi)\n}\n\n// Per-key actions cache: each row's `actions` bundle is allocated once so slot\n// consumers get a stable identity (no prop-ref churn → no spurious re-renders).\n// Closures capture the stable key and look up the live decorator via `findVi(key)`.\ntype ActionsBundle = {\n edit: () => void\n save: () => Promise<void> | void\n cancel: () => void\n close: () => void\n delete: () => Promise<void>\n addAfter: () => void\n toggleExpand: () => void\n update: (next: TItem | Partial<TItem> | ((current: TItem) => TItem)) => void\n}\nconst actionsCache = new Map<ListEditorKey, ActionsBundle>()\nconst getActions = (key: ListEditorKey): ActionsBundle => {\n let actions = actionsCache.get(key)\n if (!actions) {\n actions = {\n edit: () => {\n const vi = findVi(key)\n if (vi) onEditClick(vi)\n },\n save: () => {\n const vi = findVi(key)\n if (vi) return onSaveClick(vi)\n },\n cancel: () => {\n const vi = findVi(key)\n if (vi) onCancelClick(vi)\n },\n close: () => {\n const vi = findVi(key)\n if (vi) onCloseClick(vi)\n },\n delete: async () => {\n const vi = findVi(key)\n if (vi) await onDeleteClick(vi)\n },\n addAfter: () => {\n const vi = findVi(key)\n if (vi) onRowAddAfterClick(vi)\n },\n toggleExpand: () => {\n const vi = findVi(key)\n if (vi) onExpandClick(vi)\n },\n update: (next) => controller.updateItem(key, next),\n }\n actionsCache.set(key, actions)\n }\n return actions\n}\n// Garbage-collect cache entries for keys that have left the model.\nwatch(\n () => viewItemsDecorated.value,\n (now) => {\n if (actionsCache.size === 0) return\n const liveKeys = new Set(now.map((v) => v.key))\n for (const key of actionsCache.keys()) {\n if (!liveKeys.has(key)) actionsCache.delete(key)\n }\n }\n)\n\nconst buildSlotProps = (vi: DecoratedViewItem<TItem>) => ({\n item: vi,\n raw: vi.raw,\n index: vi.index,\n key: vi.key,\n readonly: props.readonly,\n disabled: props.disabled,\n expanded: vi.expanded,\n editing: vi.editing || props.defaultExpanded,\n dirty: vi.dirty,\n unsaved: vi.unsaved,\n touch: isTouch.value,\n stateKeyPrefix: rowStateScope.prefixFor(vi.key),\n actions: getActions(vi.key),\n})\n\n// Named unsaved-changes section, registered only when a label is passed.\nuseUnsavedSection(() =>\n props.unsavedSectionLabel && !props.disableUnsaved\n ? { label: props.unsavedSectionLabel, dirty: controller.hasUnsaved.value }\n : []\n)\n\n// Opens invalid rows so a blocked save surfaces WHICH rows are wrong instead of a collapsed red\n// rail. The body is gated on `editing` not `expanded`, so drive `beginEdit` not `expandedKeys`\n// (QA 85050 B4-17). Shared by the exposed `validateAll` and the scope-collector reveal-on-touch.\nconst runValidateAndReveal = (): boolean =>\n validateAllAndReveal(controller, (key) => {\n const vi = viewItems.value.find((v) => v.key === key)\n if (vi) beginEdit(vi)\n })\n\n// Auto-bridge the editor's aggregate validity into the consumer's `validation-scope` collector so a\n// plain `$invalid` save gate blocks (and reveals) a collapsed invalid row. No-op without the prop.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss -- construction-time opt-in, read once\nuseListEditorScopeValidity({\n hasErrors: controller.hasErrors,\n validationScope: props.validationScope,\n validateProvided: props.validate !== undefined,\n reveal: runValidateAndReveal,\n})\n\n// Expose the controller handle via `useTemplateRef<ListEditorHandle<TItem>>`.\ndefineExpose<ListEditorHandle<TItem>>({\n ...controller,\n validateAll: runValidateAndReveal,\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"a-list-editor\"\n :class=\"[\n `a-list-editor--two-rows-${twoRows}`,\n {\n 'a-list-editor--readonly': readonly,\n 'a-list-editor--disabled': disabled,\n 'a-list-editor--touch': isTouch,\n 'a-list-editor--chips': chips,\n },\n ]\"\n >\n <div\n ref=\"rowsContainer\"\n class=\"a-le-card\"\n >\n <div\n v-if=\"headerVisible\"\n class=\"a-le-header\"\n >\n <slot\n name=\"header\"\n :title=\"title\"\n >\n <h3\n v-if=\"title\"\n class=\"a-le-title-heading\"\n >\n {{ title }}\n </h3>\n </slot>\n <span\n v-if=\"unsavedCountVisible\"\n class=\"a-le-unsaved-count\"\n :data-unsaved-count=\"unsavedCount\"\n >\n {{ t('common.sortable.pendingChanges', { count: unsavedCount }) }}\n </span>\n </div>\n\n <div\n v-if=\"loading\"\n class=\"a-le-state a-le-state--loading\"\n >\n <VProgressCircular\n indeterminate\n color=\"primary\"\n size=\"32\"\n />\n </div>\n\n <div\n v-else-if=\"error\"\n class=\"a-le-state a-le-state--error\"\n >\n <VAlert\n type=\"error\"\n variant=\"tonal\"\n density=\"compact\"\n border=\"start\"\n >\n {{ error }}\n </VAlert>\n </div>\n\n <div\n v-else-if=\"isEmpty\"\n class=\"a-le-state a-le-state--empty\"\n >\n <slot\n name=\"empty\"\n :readonly=\"readonly\"\n :disabled=\"disabled\"\n :actions=\"{ add: onAddClick }\"\n >\n <LeEmptyState\n :title=\"emptyTitleResolved\"\n :add-label=\"addLabelResolved\"\n :can-add=\"canAdd\"\n @add=\"onAddClick\"\n />\n </slot>\n </div>\n\n <template v-else>\n <div\n v-for=\"vi in viewItemsDecorated\"\n :key=\"String(vi.key)\"\n class=\"a-le-row-wrapper\"\n >\n <!-- Interstitial slots render as SIBLINGS of `.a-le-row` (inside this\n wrapper), so consumer content placed between rows never inherits the\n row's unsaved/editing/validation tint. (QA 85050 BUG-04/05) -->\n <slot\n name=\"before-item\"\n v-bind=\"buildSlotProps(vi)\"\n />\n\n <div\n :data-id=\"String(vi.key)\"\n role=\"listitem\"\n :tabindex=\"keyboardNav.rowTabindex(vi.key)\"\n class=\"a-le-row\"\n :class=\"{\n 'a-le-row--two-rows': twoRows === 'always',\n 'a-le-row--editing': vi.editing,\n 'a-le-row--expanded': vi.expanded,\n 'a-le-row--unsaved': vi.unsaved,\n 'a-le-row--clickable': isRowClickable(vi),\n [`a-le-row--validation-${vi.validationState}`]: vi.validationState !== null,\n }\"\n @keydown=\"keyboardNav.handleKeydown(vi.key, $event)\"\n >\n <div\n class=\"a-le-row-header\"\n @click=\"onRowClick(vi)\"\n >\n <div class=\"a-le-row-main\">\n <slot\n name=\"item-compact\"\n v-bind=\"buildSlotProps(vi)\"\n >\n <span class=\"a-le-title\">\n {{ resolveCompactText(vi.raw) }}\n </span>\n </slot>\n <LeUnsavedLabel\n v-if=\"vi.unsaved\"\n :dot-only=\"chips\"\n />\n </div>\n\n <div\n v-if=\"!chips\"\n class=\"a-le-status\"\n >\n <slot\n name=\"item-status\"\n v-bind=\"buildSlotProps(vi)\"\n >\n <span\n v-if=\"statusField && vi.raw[statusField] != null && vi.raw[statusField] !== ''\"\n class=\"a-le-status-badge\"\n >\n {{ vi.raw[statusField] }}\n </span>\n </slot>\n </div>\n\n <div class=\"a-le-actions\">\n <slot\n name=\"item-actions\"\n v-bind=\"buildSlotProps(vi)\"\n >\n <VBtn\n v-if=\"chips && showDeleteButton && canInteract\"\n icon\n size=\"x-small\"\n variant=\"text\"\n density=\"compact\"\n :active=\"false\"\n class=\"a-le-action a-le-action--chip-close\"\n @click.stop=\"onDeleteClick(vi)\"\n >\n <VIcon\n icon=\"mdi-close\"\n size=\"14\"\n />\n </VBtn>\n <template v-else>\n <VBtn\n v-if=\"showEditButton && canInteract && !defaultExpanded\"\n icon\n size=\"small\"\n variant=\"tonal\"\n color=\"primary\"\n density=\"comfortable\"\n class=\"mx-1 a-le-action a-le-action--edit\"\n @click.stop=\"onEditClick(vi)\"\n >\n <VIcon\n icon=\"mdi-pencil-outline\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.edit')\"\n />\n </VBtn>\n <VBtn\n v-if=\"showDeleteButton && canInteract\"\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n class=\"mx-1 a-le-action a-le-action--delete\"\n @click.stop=\"onDeleteClick(vi)\"\n >\n <VIcon\n icon=\"mdi-trash-can-outline\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.delete')\"\n />\n </VBtn>\n <VBtn\n v-if=\"showAddAfterAction && canInteract\"\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :active=\"false\"\n class=\"mx-1 a-le-action a-le-action--menu\"\n >\n <VIcon\n icon=\"mdi-dots-vertical\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.more')\"\n />\n <VMenu activator=\"parent\">\n <VList density=\"compact\">\n <VListItem @click=\"onRowAddAfterClick(vi)\">\n <template #prepend>\n <VIcon icon=\"mdi-plus\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.addAfter') }}\n </VListItemTitle>\n </VListItem>\n </VList>\n </VMenu>\n </VBtn>\n </template>\n </slot>\n </div>\n </div>\n\n <template v-if=\"(vi.editing || defaultExpanded) && $slots.item\">\n <div class=\"a-le-row-body\">\n <div class=\"a-le-form\">\n <slot\n name=\"item\"\n v-bind=\"buildSlotProps(vi)\"\n />\n </div>\n </div>\n <slot\n name=\"item-footer\"\n v-bind=\"buildSlotProps(vi)\"\n >\n <div\n v-if=\"showInlineSaveFooter && !defaultExpanded\"\n class=\"a-le-row-footer\"\n >\n <div class=\"a-le-row-footer-spacer\" />\n <VBtn\n variant=\"text\"\n :disabled=\"vi.loading\"\n @click.stop=\"onCancelClick(vi)\"\n >\n {{ t('common.button.cancel') }}\n </VBtn>\n <VBtn\n color=\"primary\"\n variant=\"flat\"\n prepend-icon=\"mdi-check\"\n :disabled=\"vi.loading\"\n @click.stop=\"onSaveClick(vi)\"\n >\n {{ t('common.button.save') }}\n </VBtn>\n </div>\n </slot>\n </template>\n\n <div\n v-else-if=\"$slots['item-readonly'] && (readonly || vi.expanded)\"\n class=\"a-le-row-body\"\n >\n <div class=\"a-le-form\">\n <slot\n name=\"item-readonly\"\n v-bind=\"buildSlotProps(vi)\"\n />\n </div>\n </div>\n </div>\n\n <slot\n name=\"after-item\"\n v-bind=\"buildSlotProps(vi)\"\n />\n </div>\n </template>\n\n <slot\n v-if=\"canAdd && !loading && !error && !isEmpty\"\n name=\"add-button\"\n :readonly=\"readonly\"\n :disabled=\"disabled\"\n :props=\"{ onClick: onAddClick }\"\n :actions=\"{ add: onAddClick }\"\n >\n <button\n type=\"button\"\n class=\"a-le-row-add\"\n @click=\"onAddClick\"\n >\n <VIcon\n icon=\"mdi-plus\"\n size=\"18\"\n />\n <span>{{ addLabelResolved }}</span>\n </button>\n </slot>\n </div>\n\n <LeDeleteDialog\n v-model=\"deleteDialog\"\n :title=\"deleteConfirmTitleResolved\"\n :text=\"deleteConfirmTextResolved\"\n :confirm-label=\"t('common.sortable.delete')\"\n :cancel-label=\"t('common.button.cancel')\"\n :error=\"deleteError\"\n :in-flight=\"deleteInFlight\"\n @confirm=\"onDeleteDialogConfirm\"\n @cancel=\"onDeleteDialogCancel\"\n />\n\n <div\n class=\"a-le-sr-only\"\n aria-live=\"polite\"\n role=\"status\"\n >\n {{\n keyboardNav.grabbedKey.value !== null\n ? t('common.sortable.keyboardGrab.status', {\n n: keyboardNav.grabbedIndex.value + 1,\n total: keyboardNav.totalCount.value,\n })\n : ''\n }}\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n@use './styles/tokens';\n@use './styles/shared';\n\n// Variant-specific rules, scoped under the AListEditor root.\n.a-list-editor {\n // Chips: 12px left padding (flat) vs 8px in sortable, which reserves drag-handle room.\n &--chips .a-le-row-header {\n padding: 2px 4px 2px 12px;\n gap: 4px;\n min-height: 28px;\n }\n}\n\n// Narrow/mobile layout: taller rows, always-visible actions, status badge dropped for room.\n/* stylelint-disable selector-max-compound-selectors */\n@container le-shell (max-width: 768px) {\n .a-list-editor {\n --le-row-min-height: 48px;\n --le-row-pad-y: 10px;\n\n .a-le-row:not(.a-le-row--editing) .a-le-status {\n display: none;\n }\n\n .a-le-row .a-le-action--edit,\n .a-le-row .a-le-action--delete,\n .a-le-row .a-le-action--menu {\n opacity: 1;\n }\n }\n}\n/* stylelint-enable selector-max-compound-selectors */\n</style>\n","import type { ListEditorKey } from '@/labs/listEditor/types/listEditorTypes'\nimport type { NestedViewItem } from '@/labs/listEditor/composables/useNestedListEditor'\n\n/**\n * Drop instruction — where the dragged item will land + how to paint the line.\n *\n * Outliner model (dnd-kit / Workflowy style):\n * - Pointer Y picks which gap the drop lands in: the upper 50% of the\n * hovered row = the gap above it, the lower 50% = the gap below.\n * - Pointer X picks the depth within that gap. Depth is measured in indent\n * columns of `indentPx` each, rounded to the nearest column, and clamped\n * to `[0, anchor.depth + 1]` where `anchor` is the visible row immediately\n * above the target gap. You can never skip more than one level deeper\n * than what's already on the row above — same rule as dnd-kit's\n * SortableTree — which keeps the tree structure coherent.\n *\n * `makeChild: true` when the chosen depth is one level deeper than the\n * anchor. The target becomes a new parent and the consumer should\n * auto-expand `parentKey` so the inserted row stays visible.\n *\n * `blocked` wraps the would-be instruction when the move would breach\n * `maxDepth` — the overlay keeps rendering the line in a warning colour\n * so the intent stays legible but the mutation is refused.\n */\n\nexport type ExecutableInstruction = {\n type: 'insert'\n refKey: ListEditorKey\n refEdge: 'top' | 'bottom'\n parentKey: ListEditorKey | null\n index: number\n depth: number\n makeChild: boolean\n // Row whose level the inserted item will match — the sibling we're\n // inserting after (sibling case) or the ancestor we're joining as sibling\n // of (reparent case). `null` for make-child (creating a brand new deeper\n // level, no existing peer to anchor against) and the root-top fallback.\n // Consumers render a thin connector from the drop line up to this row so\n // the user can see which row dictates the new level.\n levelRowKey: ListEditorKey | null\n}\n\nexport type Instruction =\n | ExecutableInstruction\n | {\n type: 'blocked'\n desired: ExecutableInstruction\n reason: 'maxDepth'\n }\n\nexport interface HoveredRow {\n key: ListEditorKey\n rect: DOMRect\n depth: number\n parentKey: ListEditorKey | null\n siblingIndex: number\n siblingCount: number\n}\n\nexport interface ComputeInstructionArgs {\n pointer: { x: number; y: number }\n hoveredRow: HoveredRow\n sourceKey: ListEditorKey\n sourceSubtreeDepth: number\n viewItems: NestedViewItem<any>[]\n maxDepth: number\n indentPx: number\n containerLeft: number\n containerPaddingLeft: number\n}\n\nconst findByKey = (viewItems: NestedViewItem<any>[], key: ListEditorKey): NestedViewItem<any> | null =>\n viewItems.find((v) => v.key === key) ?? null\n\nconst isDescendantOf = (\n viewItems: NestedViewItem<any>[],\n ancestorKey: ListEditorKey,\n candidateKey: ListEditorKey\n): boolean => {\n let current = findByKey(viewItems, candidateKey)\n while (current && current.parentKey !== null) {\n if (current.parentKey === ancestorKey) return true\n current = findByKey(viewItems, current.parentKey)\n }\n return false\n}\n\nconst findAncestorAtDepth = (\n viewItems: NestedViewItem<any>[],\n startKey: ListEditorKey,\n targetDepth: number\n): NestedViewItem<any> | null => {\n let current = findByKey(viewItems, startKey)\n while (current && current.depth > targetDepth) {\n if (current.parentKey === null) return null\n current = findByKey(viewItems, current.parentKey)\n }\n return current && current.depth === targetDepth ? current : null\n}\n\nconst isInSourceSubtree = (\n viewItems: NestedViewItem<any>[],\n sourceKey: ListEditorKey,\n candidateKey: ListEditorKey\n): boolean => candidateKey === sourceKey || isDescendantOf(viewItems, sourceKey, candidateKey)\n\n// Walk the flat view-items list in `dir` (±1) starting at `startIdx`, skipping\n// any row that belongs to the dragged subtree — those rows aren't visible drop\n// targets so they shouldn't define the gap boundaries for depth clamping.\nconst findSiblingNotInSource = (\n viewItems: NestedViewItem<any>[],\n startIdx: number,\n dir: 1 | -1,\n sourceKey: ListEditorKey\n): NestedViewItem<any> | null => {\n for (let i = startIdx; i >= 0 && i < viewItems.length; i += dir) {\n const vi = viewItems[i]\n if (!isInSourceSubtree(viewItems, sourceKey, vi.key)) return vi\n }\n return null\n}\n\nexport const computeInstruction = (args: ComputeInstructionArgs): Instruction | null => {\n const {\n pointer,\n hoveredRow,\n sourceKey,\n sourceSubtreeDepth,\n viewItems,\n maxDepth,\n indentPx,\n containerLeft,\n containerPaddingLeft,\n } = args\n\n if (hoveredRow.key === sourceKey) return null\n if (isDescendantOf(viewItems, sourceKey, hoveredRow.key)) return null\n\n const y = pointer.y - hoveredRow.rect.top\n const h = hoveredRow.rect.height\n const inTopHalf = y < h / 2\n const refEdge: 'top' | 'bottom' = inTopHalf ? 'top' : 'bottom'\n\n // The gap between two flat-adjacent rows frames the drop. `prev` is the\n // row immediately above the gap, `next` is the row immediately below it.\n // Rows belonging to the dragged subtree don't exist for clamping purposes —\n // they'll disappear from the flat list once the move completes, so they\n // can't define the depth bounds of the drop.\n const flatIdx = viewItems.findIndex((v) => v.key === hoveredRow.key)\n const prev: NestedViewItem<any> | null = inTopHalf\n ? findSiblingNotInSource(viewItems, flatIdx - 1, -1, sourceKey)\n : findSiblingNotInSource(viewItems, flatIdx, -1, sourceKey)\n const next: NestedViewItem<any> | null = inTopHalf\n ? findSiblingNotInSource(viewItems, flatIdx, 1, sourceKey)\n : findSiblingNotInSource(viewItems, flatIdx + 1, 1, sourceKey)\n\n // Depth bounds for this gap:\n // maxDepth = prev.depth + 1 (you can nest one level deeper than prev).\n // minDepth = next.depth (shallower would insert AFTER next's parent\n // subtree, i.e., at a different Y than the\n // user is pointing at — so that's not a valid\n // option for THIS gap).\n // If prev is missing (gap at top of tree) or next is missing (bottom of\n // tree), the missing side defaults to 0.\n const pointerRelX = pointer.x - containerLeft - containerPaddingLeft\n const rawDepth = Math.round(pointerRelX / indentPx)\n const depthCeiling = prev ? prev.depth + 1 : 0\n const depthFloor = next ? next.depth : 0\n const clampedDepth = Math.max(depthFloor, Math.min(rawDepth, depthCeiling))\n\n let desired: ExecutableInstruction\n\n if (!prev) {\n // Gap at the very top of the tree — only landing is root index 0.\n desired = {\n type: 'insert',\n refKey: hoveredRow.key,\n refEdge: 'top',\n parentKey: null,\n index: 0,\n depth: 0,\n makeChild: false,\n levelRowKey: null,\n }\n } else if (clampedDepth === prev.depth + 1) {\n desired = {\n type: 'insert',\n refKey: hoveredRow.key,\n refEdge,\n parentKey: prev.key,\n index: 0,\n depth: clampedDepth,\n makeChild: true,\n levelRowKey: null,\n }\n } else if (clampedDepth === prev.depth) {\n desired = {\n type: 'insert',\n refKey: hoveredRow.key,\n refEdge,\n parentKey: prev.parentKey,\n index: prev.siblingIndex + 1,\n depth: clampedDepth,\n makeChild: false,\n levelRowKey: prev.key,\n }\n } else {\n // clampedDepth < prev.depth → reparent to ancestor at clampedDepth.\n const ancestor = findAncestorAtDepth(viewItems, prev.key, clampedDepth)\n if (ancestor) {\n desired = {\n type: 'insert',\n refKey: hoveredRow.key,\n refEdge,\n parentKey: ancestor.parentKey,\n index: ancestor.siblingIndex + 1,\n depth: clampedDepth,\n makeChild: false,\n levelRowKey: ancestor.key,\n }\n } else {\n // Shouldn't hit with clampedDepth ≥ 0 but keep a safe fallback.\n desired = {\n type: 'insert',\n refKey: hoveredRow.key,\n refEdge,\n parentKey: prev.parentKey,\n index: prev.siblingIndex + 1,\n depth: prev.depth,\n makeChild: false,\n levelRowKey: prev.key,\n }\n }\n }\n\n if (desired.depth + sourceSubtreeDepth - 1 >= maxDepth) {\n return { type: 'blocked', desired, reason: 'maxDepth' }\n }\n\n return desired\n}\n","<script setup lang=\"ts\" generic=\"TItem extends Record<string, any>\">\nimport { useI18n } from 'vue-i18n'\nimport LeNestedRowSelf from './LeNestedRow.vue'\nimport LeDragHandle from '@/labs/listEditor/internal/LeDragHandle.vue'\nimport LeUnsavedLabel from '@/labs/listEditor/internal/LeUnsavedLabel.vue'\nimport type { ListEditorKey, ListEditorValidationState } from '@/labs/listEditor/types/listEditorTypes'\n\n// Self-recursive row. Interior uses `any` — strong typing lives on the public ANestedSortableListEditor wrapper.\n\nexport type Props = {\n vi: any\n viewItems: any[]\n dragState: any\n context: any\n callbacks: any\n}\nconst props = defineProps<Props>()\n\n// `as any` breaks Vue's circular type inference on self-import.\nconst LeNestedRow = LeNestedRowSelf as any\n\nconst { t } = useI18n()\n\nconst GROUP_CLASS = 'a-nested-list-editor__group'\nconst HANDLE_CLASS = 'a-le-drag-handle'\n\nconst anchorName = (key: ListEditorKey): string => `--row-${String(key).replace(/\\W/g, '_')}`\n\n// Delegate to the parent editor's resolver so the registry + getValidationState\n// prop apply consistently. Falls back to reading raw.validationState if context\n// doesn't supply one.\nconst resolveValidation = (raw: TItem): ListEditorValidationState => {\n if (typeof props.context.resolveValidation === 'function') {\n return props.context.resolveValidation(raw, props.vi.key, props.vi.index)\n }\n const v = raw.validationState\n if (v === 'valid' || v === 'invalid' || v === 'warning') return v\n return null\n}\n\nconst buildSlotProps = () => props.context.buildSlotProps(props.vi)\n\nconst directChildren = (): any[] => props.viewItems.filter((v) => v.parentKey === props.vi.key)\n</script>\n\n<template>\n <div\n :class=\"[\n 'a-le-row-wrapper',\n {\n 'a-le-row-wrapper--drop-disabled': dragState !== null && dragState.sourceKey === vi.key,\n },\n ]\"\n :data-id=\"String(vi.key)\"\n role=\"treeitem\"\n :aria-level=\"vi.depth + 1\"\n :tabindex=\"context.keyboardNav ? context.keyboardNav.rowTabindex(vi.key) : undefined\"\n @keydown=\"context.keyboardNav ? context.keyboardNav.handleKeydown(vi.key, $event) : undefined\"\n >\n <!-- Interstitial slots are SIBLINGS of `.a-le-row` (children of this wrapper),\n so between-row content never inherits the row's unsaved/editing/grabbed/\n validation tint. (QA 85050 BUG-04/05) -->\n <slot\n name=\"before-item\"\n v-bind=\"buildSlotProps()\"\n />\n\n <div\n :class=\"[\n 'a-le-row',\n vi.depth > 0 ? 'a-le-row--child' : null,\n {\n 'a-le-row--editing': vi.editing,\n 'a-le-row--expanded': vi.expanded,\n 'a-le-row--unsaved': vi.unsaved,\n 'a-le-row--reorder': context.reorderMode,\n 'a-le-row--grabbed': context.keyboardNav && context.keyboardNav.isGrabbed(vi.key),\n 'a-le-row--clickable': context.isRowClickable(vi),\n 'a-le-row--drop-source': dragState !== null && dragState.sourceKey === vi.key,\n [`a-le-row--validation-${resolveValidation(vi.raw)}`]: resolveValidation(vi.raw) !== null,\n },\n ]\"\n :style=\"{\n '--nested-depth': vi.depth,\n 'anchor-name': anchorName(vi.key),\n '--parent-anchor': vi.parentKey !== null ? anchorName(vi.parentKey) : 'none',\n }\"\n >\n <div\n class=\"a-le-row-header\"\n @click=\"callbacks.onRowClick(vi)\"\n >\n <LeDragHandle\n v-if=\"context.dragEnabled\"\n :class=\"HANDLE_CLASS\"\n />\n\n <!-- Expand toggle — a small triangular caret rendered uniformly for both\n root and nested rows. For leaf rows (no children) the toggle is\n hidden via `visibility: hidden` so the column stays aligned. -->\n <button\n v-if=\"context.showExpandToggle && vi.childrenAllowed\"\n type=\"button\"\n :class=\"[\n 'a-nested-list-editor__tree-toggle',\n {\n 'a-nested-list-editor__tree-toggle--empty': !vi.hasChildren,\n 'a-nested-list-editor__tree-toggle--open': vi.childrenExpanded,\n },\n ]\"\n :aria-label=\"vi.childrenExpanded ? t('common.sortable.close') : t('common.sortable.edit')\"\n @click.stop=\"vi.hasChildren && callbacks.onChevronClick(vi)\"\n >\n <span class=\"a-nested-list-editor__tree-toggle-caret\" />\n </button>\n <!-- Spacer for leaf rows that have no caret, so titles align with\n parents having one. -->\n <span\n v-else-if=\"context.showExpandToggle\"\n class=\"a-nested-list-editor__tree-toggle a-nested-list-editor__tree-toggle--spacer\"\n aria-hidden=\"true\"\n />\n\n <div class=\"a-le-row-main\">\n <slot\n name=\"item-compact\"\n v-bind=\"buildSlotProps()\"\n >\n <span class=\"a-le-title\">\n {{ context.resolveCompactText(vi.raw, vi.key) }}\n </span>\n </slot>\n <!-- Visible only inside the SortableJS drag clone — signals that the\n item being moved carries a subtree so the user realises the whole\n branch will follow. Sits right after the title. Hidden in the\n normal DOM via CSS. -->\n <span\n v-if=\"vi.hasChildren\"\n class=\"a-nested-list-editor__drag-count\"\n aria-hidden=\"true\"\n >\n <VIcon\n icon=\"mdi-file-tree\"\n size=\"14\"\n />\n +{{ vi.childrenCount }}\n </span>\n <LeUnsavedLabel v-if=\"vi.unsaved\" />\n </div>\n\n <div\n v-if=\"!context.reorderMode\"\n class=\"a-le-status\"\n >\n <slot\n name=\"item-status\"\n v-bind=\"buildSlotProps()\"\n >\n <span\n v-if=\"context.statusField && vi.raw[context.statusField] != null && vi.raw[context.statusField] !== ''\"\n class=\"a-le-status-badge\"\n >\n {{ vi.raw[context.statusField] }}\n </span>\n </slot>\n </div>\n\n <div class=\"a-le-actions\">\n <slot\n name=\"item-actions\"\n v-bind=\"buildSlotProps()\"\n >\n <template v-if=\"context.reorderMode\">\n <VBtn\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :disabled=\"vi.firstInParent\"\n class=\"mx-1 a-le-action a-le-action--up\"\n @click.stop=\"callbacks.moveUp(vi.key)\"\n >\n <VIcon\n icon=\"mdi-arrow-up\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.moveUp')\"\n />\n </VBtn>\n <VBtn\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :disabled=\"vi.lastInParent\"\n class=\"mx-1 a-le-action a-le-action--down\"\n @click.stop=\"callbacks.moveDown(vi.key)\"\n >\n <VIcon\n icon=\"mdi-arrow-down\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.moveDown')\"\n />\n </VBtn>\n <VBtn\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :active=\"false\"\n class=\"mx-1 a-le-action a-le-action--menu\"\n >\n <VIcon\n icon=\"mdi-dots-vertical\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.more')\"\n />\n <VMenu activator=\"parent\">\n <VList density=\"compact\">\n <VListItem\n :disabled=\"vi.firstInParent\"\n @click.stop=\"callbacks.moveTop(vi.key)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-arrow-collapse-up\" />\n </template>\n <VListItemTitle>{{ t('common.sortable.moveToTop') }}</VListItemTitle>\n </VListItem>\n <VListItem\n :disabled=\"vi.lastInParent\"\n @click.stop=\"callbacks.moveBottom(vi.key)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-arrow-collapse-down\" />\n </template>\n <VListItemTitle>{{ t('common.sortable.moveToBottom') }}</VListItemTitle>\n </VListItem>\n <VListItem\n v-if=\"context.showMoveToPosition && (!vi.firstInParent || !vi.lastInParent)\"\n @click.stop=\"callbacks.openMoveToPosition(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-target\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.moveToPosition.action') }}\n </VListItemTitle>\n </VListItem>\n <VListItem\n v-if=\"context.showChangeParent\"\n @click.stop=\"callbacks.openChangeParent(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-folder-move-outline\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.changeParent.action') }}\n </VListItemTitle>\n </VListItem>\n <VListItem\n :disabled=\"!vi.canIndent\"\n @click.stop=\"callbacks.indent(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-format-indent-increase\" />\n </template>\n <VListItemTitle>{{ t('common.sortable.indent') }}</VListItemTitle>\n </VListItem>\n <VListItem\n :disabled=\"!vi.canOutdent\"\n @click.stop=\"callbacks.outdent(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-format-indent-decrease\" />\n </template>\n <VListItemTitle>{{ t('common.sortable.outdent') }}</VListItemTitle>\n </VListItem>\n <VListItem\n v-if=\"context.showDeleteButton && context.canInteract\"\n @click.stop=\"callbacks.onDeleteClick(vi)\"\n >\n <template #prepend>\n <VIcon\n icon=\"mdi-trash-can-outline\"\n color=\"error\"\n />\n </template>\n <VListItemTitle class=\"text-error\">\n {{ t('common.sortable.delete') }}\n </VListItemTitle>\n </VListItem>\n </VList>\n </VMenu>\n </VBtn>\n </template>\n <template v-else>\n <VBtn\n v-if=\"context.showEditButton && context.canInteract\"\n icon\n size=\"small\"\n variant=\"tonal\"\n color=\"primary\"\n density=\"comfortable\"\n class=\"mx-1 a-le-action a-le-action--edit\"\n @click.stop=\"callbacks.onEditClick(vi)\"\n >\n <VIcon\n icon=\"mdi-pencil-outline\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.edit')\"\n />\n </VBtn>\n <VBtn\n v-if=\"context.showDeleteButton && context.canInteract\"\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n class=\"mx-1 a-le-action a-le-action--delete\"\n @click.stop=\"callbacks.onDeleteClick(vi)\"\n >\n <VIcon\n icon=\"mdi-trash-can-outline\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.delete')\"\n />\n </VBtn>\n <VBtn\n v-if=\"\n context.canInteract && ((context.showAddChildButton && vi.canAddChild) || context.showAddAfterAction)\n \"\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :active=\"false\"\n class=\"mx-1 a-le-action a-le-action--menu\"\n >\n <VIcon\n icon=\"mdi-dots-vertical\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.more')\"\n />\n <VMenu activator=\"parent\">\n <VList density=\"compact\">\n <VListItem\n v-if=\"context.showAddAfterAction\"\n @click.stop=\"callbacks.onAddAfterClick(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-playlist-plus\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.addAfter') }}\n </VListItemTitle>\n </VListItem>\n <VListItem\n v-if=\"context.showAddChildButton && vi.canAddChild\"\n @click.stop=\"callbacks.onAddChildClick(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-subdirectory-arrow-right\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.addChild') }}\n </VListItemTitle>\n </VListItem>\n </VList>\n </VMenu>\n </VBtn>\n </template>\n </slot>\n </div>\n </div>\n\n <!-- Inline edit body — status badge intentionally NOT rendered here,\n the compact header's status is enough; stacking it in the form body\n was visually noisy. -->\n <template v-if=\"vi.editing && !context.reorderMode && $slots.item\">\n <div class=\"a-le-row-body\">\n <div class=\"a-le-form\">\n <slot\n name=\"item\"\n v-bind=\"buildSlotProps()\"\n />\n </div>\n </div>\n <slot\n name=\"item-footer\"\n v-bind=\"buildSlotProps()\"\n >\n <div\n v-if=\"context.showInlineSaveFooter\"\n class=\"a-le-row-footer\"\n >\n <div class=\"a-le-row-footer-spacer\" />\n <VBtn\n variant=\"text\"\n :disabled=\"vi.loading\"\n @click.stop=\"callbacks.onCancelClick(vi)\"\n >\n {{ t('common.button.cancel') }}\n </VBtn>\n <VBtn\n color=\"primary\"\n variant=\"flat\"\n prepend-icon=\"mdi-check\"\n :disabled=\"vi.loading\"\n @click.stop=\"callbacks.onSaveClick(vi)\"\n >\n {{ t('common.button.save') }}\n </VBtn>\n </div>\n </slot>\n </template>\n\n <div\n v-else-if=\"$slots['item-readonly'] && (context.readonly || vi.expanded) && !context.reorderMode\"\n class=\"a-le-row-body\"\n >\n <div class=\"a-le-form\">\n <slot\n name=\"item-readonly\"\n v-bind=\"buildSlotProps()\"\n />\n </div>\n </div>\n </div>\n\n <slot\n name=\"after-item\"\n v-bind=\"buildSlotProps()\"\n />\n\n <!-- Recursive children — any depth level. The `::before` on this wrapper\n renders the single continuous vertical tree line at the parent's\n chevron column (see CSS). One line per group = no per-row gaps. -->\n <div\n v-if=\"vi.hasChildren && vi.childrenExpanded\"\n class=\"a-nested-list-editor__children\"\n :style=\"{ '--parent-depth': vi.depth }\"\n >\n <div\n :class=\"[GROUP_CLASS]\"\n :data-parent-id=\"String(vi.key)\"\n >\n <LeNestedRow\n v-for=\"child in directChildren()\"\n :key=\"String(child.key)\"\n :vi=\"child\"\n :view-items=\"viewItems\"\n :drag-state=\"dragState\"\n :context=\"context\"\n :callbacks=\"callbacks\"\n >\n <template #item=\"slotScope\">\n <slot\n name=\"item\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-compact=\"slotScope\">\n <slot\n name=\"item-compact\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-readonly=\"slotScope\">\n <slot\n name=\"item-readonly\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-status=\"slotScope\">\n <slot\n name=\"item-status\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-footer=\"slotScope\">\n <slot\n name=\"item-footer\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-actions=\"slotScope\">\n <slot\n name=\"item-actions\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #before-item=\"slotScope\">\n <slot\n name=\"before-item\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #after-item=\"slotScope\">\n <slot\n name=\"after-item\"\n v-bind=\"slotScope\"\n />\n </template>\n </LeNestedRow>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\" generic=\"TItem extends Record<string, any>\">\nimport { useI18n } from 'vue-i18n'\nimport LeNestedRowSelf from './LeNestedRow.vue'\nimport LeDragHandle from '@/labs/listEditor/internal/LeDragHandle.vue'\nimport LeUnsavedLabel from '@/labs/listEditor/internal/LeUnsavedLabel.vue'\nimport type { ListEditorKey, ListEditorValidationState } from '@/labs/listEditor/types/listEditorTypes'\n\n// Self-recursive row. Interior uses `any` — strong typing lives on the public ANestedSortableListEditor wrapper.\n\nexport type Props = {\n vi: any\n viewItems: any[]\n dragState: any\n context: any\n callbacks: any\n}\nconst props = defineProps<Props>()\n\n// `as any` breaks Vue's circular type inference on self-import.\nconst LeNestedRow = LeNestedRowSelf as any\n\nconst { t } = useI18n()\n\nconst GROUP_CLASS = 'a-nested-list-editor__group'\nconst HANDLE_CLASS = 'a-le-drag-handle'\n\nconst anchorName = (key: ListEditorKey): string => `--row-${String(key).replace(/\\W/g, '_')}`\n\n// Delegate to the parent editor's resolver so the registry + getValidationState\n// prop apply consistently. Falls back to reading raw.validationState if context\n// doesn't supply one.\nconst resolveValidation = (raw: TItem): ListEditorValidationState => {\n if (typeof props.context.resolveValidation === 'function') {\n return props.context.resolveValidation(raw, props.vi.key, props.vi.index)\n }\n const v = raw.validationState\n if (v === 'valid' || v === 'invalid' || v === 'warning') return v\n return null\n}\n\nconst buildSlotProps = () => props.context.buildSlotProps(props.vi)\n\nconst directChildren = (): any[] => props.viewItems.filter((v) => v.parentKey === props.vi.key)\n</script>\n\n<template>\n <div\n :class=\"[\n 'a-le-row-wrapper',\n {\n 'a-le-row-wrapper--drop-disabled': dragState !== null && dragState.sourceKey === vi.key,\n },\n ]\"\n :data-id=\"String(vi.key)\"\n role=\"treeitem\"\n :aria-level=\"vi.depth + 1\"\n :tabindex=\"context.keyboardNav ? context.keyboardNav.rowTabindex(vi.key) : undefined\"\n @keydown=\"context.keyboardNav ? context.keyboardNav.handleKeydown(vi.key, $event) : undefined\"\n >\n <!-- Interstitial slots are SIBLINGS of `.a-le-row` (children of this wrapper),\n so between-row content never inherits the row's unsaved/editing/grabbed/\n validation tint. (QA 85050 BUG-04/05) -->\n <slot\n name=\"before-item\"\n v-bind=\"buildSlotProps()\"\n />\n\n <div\n :class=\"[\n 'a-le-row',\n vi.depth > 0 ? 'a-le-row--child' : null,\n {\n 'a-le-row--editing': vi.editing,\n 'a-le-row--expanded': vi.expanded,\n 'a-le-row--unsaved': vi.unsaved,\n 'a-le-row--reorder': context.reorderMode,\n 'a-le-row--grabbed': context.keyboardNav && context.keyboardNav.isGrabbed(vi.key),\n 'a-le-row--clickable': context.isRowClickable(vi),\n 'a-le-row--drop-source': dragState !== null && dragState.sourceKey === vi.key,\n [`a-le-row--validation-${resolveValidation(vi.raw)}`]: resolveValidation(vi.raw) !== null,\n },\n ]\"\n :style=\"{\n '--nested-depth': vi.depth,\n 'anchor-name': anchorName(vi.key),\n '--parent-anchor': vi.parentKey !== null ? anchorName(vi.parentKey) : 'none',\n }\"\n >\n <div\n class=\"a-le-row-header\"\n @click=\"callbacks.onRowClick(vi)\"\n >\n <LeDragHandle\n v-if=\"context.dragEnabled\"\n :class=\"HANDLE_CLASS\"\n />\n\n <!-- Expand toggle — a small triangular caret rendered uniformly for both\n root and nested rows. For leaf rows (no children) the toggle is\n hidden via `visibility: hidden` so the column stays aligned. -->\n <button\n v-if=\"context.showExpandToggle && vi.childrenAllowed\"\n type=\"button\"\n :class=\"[\n 'a-nested-list-editor__tree-toggle',\n {\n 'a-nested-list-editor__tree-toggle--empty': !vi.hasChildren,\n 'a-nested-list-editor__tree-toggle--open': vi.childrenExpanded,\n },\n ]\"\n :aria-label=\"vi.childrenExpanded ? t('common.sortable.close') : t('common.sortable.edit')\"\n @click.stop=\"vi.hasChildren && callbacks.onChevronClick(vi)\"\n >\n <span class=\"a-nested-list-editor__tree-toggle-caret\" />\n </button>\n <!-- Spacer for leaf rows that have no caret, so titles align with\n parents having one. -->\n <span\n v-else-if=\"context.showExpandToggle\"\n class=\"a-nested-list-editor__tree-toggle a-nested-list-editor__tree-toggle--spacer\"\n aria-hidden=\"true\"\n />\n\n <div class=\"a-le-row-main\">\n <slot\n name=\"item-compact\"\n v-bind=\"buildSlotProps()\"\n >\n <span class=\"a-le-title\">\n {{ context.resolveCompactText(vi.raw, vi.key) }}\n </span>\n </slot>\n <!-- Visible only inside the SortableJS drag clone — signals that the\n item being moved carries a subtree so the user realises the whole\n branch will follow. Sits right after the title. Hidden in the\n normal DOM via CSS. -->\n <span\n v-if=\"vi.hasChildren\"\n class=\"a-nested-list-editor__drag-count\"\n aria-hidden=\"true\"\n >\n <VIcon\n icon=\"mdi-file-tree\"\n size=\"14\"\n />\n +{{ vi.childrenCount }}\n </span>\n <LeUnsavedLabel v-if=\"vi.unsaved\" />\n </div>\n\n <div\n v-if=\"!context.reorderMode\"\n class=\"a-le-status\"\n >\n <slot\n name=\"item-status\"\n v-bind=\"buildSlotProps()\"\n >\n <span\n v-if=\"context.statusField && vi.raw[context.statusField] != null && vi.raw[context.statusField] !== ''\"\n class=\"a-le-status-badge\"\n >\n {{ vi.raw[context.statusField] }}\n </span>\n </slot>\n </div>\n\n <div class=\"a-le-actions\">\n <slot\n name=\"item-actions\"\n v-bind=\"buildSlotProps()\"\n >\n <template v-if=\"context.reorderMode\">\n <VBtn\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :disabled=\"vi.firstInParent\"\n class=\"mx-1 a-le-action a-le-action--up\"\n @click.stop=\"callbacks.moveUp(vi.key)\"\n >\n <VIcon\n icon=\"mdi-arrow-up\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.moveUp')\"\n />\n </VBtn>\n <VBtn\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :disabled=\"vi.lastInParent\"\n class=\"mx-1 a-le-action a-le-action--down\"\n @click.stop=\"callbacks.moveDown(vi.key)\"\n >\n <VIcon\n icon=\"mdi-arrow-down\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.moveDown')\"\n />\n </VBtn>\n <VBtn\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :active=\"false\"\n class=\"mx-1 a-le-action a-le-action--menu\"\n >\n <VIcon\n icon=\"mdi-dots-vertical\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.more')\"\n />\n <VMenu activator=\"parent\">\n <VList density=\"compact\">\n <VListItem\n :disabled=\"vi.firstInParent\"\n @click.stop=\"callbacks.moveTop(vi.key)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-arrow-collapse-up\" />\n </template>\n <VListItemTitle>{{ t('common.sortable.moveToTop') }}</VListItemTitle>\n </VListItem>\n <VListItem\n :disabled=\"vi.lastInParent\"\n @click.stop=\"callbacks.moveBottom(vi.key)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-arrow-collapse-down\" />\n </template>\n <VListItemTitle>{{ t('common.sortable.moveToBottom') }}</VListItemTitle>\n </VListItem>\n <VListItem\n v-if=\"context.showMoveToPosition && (!vi.firstInParent || !vi.lastInParent)\"\n @click.stop=\"callbacks.openMoveToPosition(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-target\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.moveToPosition.action') }}\n </VListItemTitle>\n </VListItem>\n <VListItem\n v-if=\"context.showChangeParent\"\n @click.stop=\"callbacks.openChangeParent(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-folder-move-outline\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.changeParent.action') }}\n </VListItemTitle>\n </VListItem>\n <VListItem\n :disabled=\"!vi.canIndent\"\n @click.stop=\"callbacks.indent(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-format-indent-increase\" />\n </template>\n <VListItemTitle>{{ t('common.sortable.indent') }}</VListItemTitle>\n </VListItem>\n <VListItem\n :disabled=\"!vi.canOutdent\"\n @click.stop=\"callbacks.outdent(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-format-indent-decrease\" />\n </template>\n <VListItemTitle>{{ t('common.sortable.outdent') }}</VListItemTitle>\n </VListItem>\n <VListItem\n v-if=\"context.showDeleteButton && context.canInteract\"\n @click.stop=\"callbacks.onDeleteClick(vi)\"\n >\n <template #prepend>\n <VIcon\n icon=\"mdi-trash-can-outline\"\n color=\"error\"\n />\n </template>\n <VListItemTitle class=\"text-error\">\n {{ t('common.sortable.delete') }}\n </VListItemTitle>\n </VListItem>\n </VList>\n </VMenu>\n </VBtn>\n </template>\n <template v-else>\n <VBtn\n v-if=\"context.showEditButton && context.canInteract\"\n icon\n size=\"small\"\n variant=\"tonal\"\n color=\"primary\"\n density=\"comfortable\"\n class=\"mx-1 a-le-action a-le-action--edit\"\n @click.stop=\"callbacks.onEditClick(vi)\"\n >\n <VIcon\n icon=\"mdi-pencil-outline\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.edit')\"\n />\n </VBtn>\n <VBtn\n v-if=\"context.showDeleteButton && context.canInteract\"\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n class=\"mx-1 a-le-action a-le-action--delete\"\n @click.stop=\"callbacks.onDeleteClick(vi)\"\n >\n <VIcon\n icon=\"mdi-trash-can-outline\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.delete')\"\n />\n </VBtn>\n <VBtn\n v-if=\"\n context.canInteract && ((context.showAddChildButton && vi.canAddChild) || context.showAddAfterAction)\n \"\n icon\n size=\"small\"\n variant=\"text\"\n density=\"comfortable\"\n :active=\"false\"\n class=\"mx-1 a-le-action a-le-action--menu\"\n >\n <VIcon\n icon=\"mdi-dots-vertical\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.more')\"\n />\n <VMenu activator=\"parent\">\n <VList density=\"compact\">\n <VListItem\n v-if=\"context.showAddAfterAction\"\n @click.stop=\"callbacks.onAddAfterClick(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-playlist-plus\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.addAfter') }}\n </VListItemTitle>\n </VListItem>\n <VListItem\n v-if=\"context.showAddChildButton && vi.canAddChild\"\n @click.stop=\"callbacks.onAddChildClick(vi)\"\n >\n <template #prepend>\n <VIcon icon=\"mdi-subdirectory-arrow-right\" />\n </template>\n <VListItemTitle>\n {{ t('common.sortable.addChild') }}\n </VListItemTitle>\n </VListItem>\n </VList>\n </VMenu>\n </VBtn>\n </template>\n </slot>\n </div>\n </div>\n\n <!-- Inline edit body — status badge intentionally NOT rendered here,\n the compact header's status is enough; stacking it in the form body\n was visually noisy. -->\n <template v-if=\"vi.editing && !context.reorderMode && $slots.item\">\n <div class=\"a-le-row-body\">\n <div class=\"a-le-form\">\n <slot\n name=\"item\"\n v-bind=\"buildSlotProps()\"\n />\n </div>\n </div>\n <slot\n name=\"item-footer\"\n v-bind=\"buildSlotProps()\"\n >\n <div\n v-if=\"context.showInlineSaveFooter\"\n class=\"a-le-row-footer\"\n >\n <div class=\"a-le-row-footer-spacer\" />\n <VBtn\n variant=\"text\"\n :disabled=\"vi.loading\"\n @click.stop=\"callbacks.onCancelClick(vi)\"\n >\n {{ t('common.button.cancel') }}\n </VBtn>\n <VBtn\n color=\"primary\"\n variant=\"flat\"\n prepend-icon=\"mdi-check\"\n :disabled=\"vi.loading\"\n @click.stop=\"callbacks.onSaveClick(vi)\"\n >\n {{ t('common.button.save') }}\n </VBtn>\n </div>\n </slot>\n </template>\n\n <div\n v-else-if=\"$slots['item-readonly'] && (context.readonly || vi.expanded) && !context.reorderMode\"\n class=\"a-le-row-body\"\n >\n <div class=\"a-le-form\">\n <slot\n name=\"item-readonly\"\n v-bind=\"buildSlotProps()\"\n />\n </div>\n </div>\n </div>\n\n <slot\n name=\"after-item\"\n v-bind=\"buildSlotProps()\"\n />\n\n <!-- Recursive children — any depth level. The `::before` on this wrapper\n renders the single continuous vertical tree line at the parent's\n chevron column (see CSS). One line per group = no per-row gaps. -->\n <div\n v-if=\"vi.hasChildren && vi.childrenExpanded\"\n class=\"a-nested-list-editor__children\"\n :style=\"{ '--parent-depth': vi.depth }\"\n >\n <div\n :class=\"[GROUP_CLASS]\"\n :data-parent-id=\"String(vi.key)\"\n >\n <LeNestedRow\n v-for=\"child in directChildren()\"\n :key=\"String(child.key)\"\n :vi=\"child\"\n :view-items=\"viewItems\"\n :drag-state=\"dragState\"\n :context=\"context\"\n :callbacks=\"callbacks\"\n >\n <template #item=\"slotScope\">\n <slot\n name=\"item\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-compact=\"slotScope\">\n <slot\n name=\"item-compact\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-readonly=\"slotScope\">\n <slot\n name=\"item-readonly\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-status=\"slotScope\">\n <slot\n name=\"item-status\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-footer=\"slotScope\">\n <slot\n name=\"item-footer\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-actions=\"slotScope\">\n <slot\n name=\"item-actions\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #before-item=\"slotScope\">\n <slot\n name=\"before-item\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #after-item=\"slotScope\">\n <slot\n name=\"after-item\"\n v-bind=\"slotScope\"\n />\n </template>\n </LeNestedRow>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\" generic=\"TItem extends Record<string, any>\">\nimport { computed, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport type { ListEditorKey, NestedTree, NestedTreeNode } from '@/labs/listEditor/types/listEditorTypes'\n\nexport interface Props<TItem extends Record<string, any>> {\n tree: NestedTree<TItem>\n sourceKey: ListEditorKey | null\n keyField: string\n maxDepth: number\n resolveLabel: (raw: TItem, key: ListEditorKey) => string\n calculateSubtreeDepth: (node: NestedTreeNode<TItem>) => number\n}\n\ninterface Candidate {\n key: ListEditorKey | null\n depth: number\n label: string\n hasChildren: boolean\n disallowed: boolean\n reason: 'self' | 'descendant' | 'maxDepth' | 'currentParent' | null\n}\n\nconst props = defineProps<Props<TItem>>()\n\nconst emit = defineEmits<{\n confirm: [parentId: ListEditorKey | null, position: 'first' | 'last']\n}>()\n\nconst open = defineModel<boolean>({ required: true })\n\nconst { t } = useI18n()\n\nconst selectedKey = ref<ListEditorKey | null | undefined>(undefined)\nconst placement = ref<'first' | 'last'>('last')\n\nwatch(open, (now) => {\n if (now) {\n selectedKey.value = undefined\n placement.value = 'last'\n }\n})\n\n// Walk the source's subtree to gather descendant keys (cycle prevention).\nconst descendantKeys = computed<Set<ListEditorKey>>(() => {\n const out = new Set<ListEditorKey>()\n if (props.sourceKey === null) return out\n const walk = (nodes: NestedTreeNode<TItem>[]): boolean => {\n for (const n of nodes) {\n const k = n.data[props.keyField] as ListEditorKey\n if (k === props.sourceKey) {\n const collect = (sub: NestedTreeNode<TItem>[]) => {\n for (const s of sub) {\n out.add(s.data[props.keyField] as ListEditorKey)\n if (s.children?.length) collect(s.children)\n }\n }\n if (n.children?.length) collect(n.children)\n return true\n }\n if (n.children?.length && walk(n.children)) return true\n }\n return false\n }\n walk(props.tree.children)\n return out\n})\n\nconst sourceNode = computed<NestedTreeNode<TItem> | null>(() => {\n if (props.sourceKey === null) return null\n const find = (nodes: NestedTreeNode<TItem>[]): NestedTreeNode<TItem> | null => {\n for (const n of nodes) {\n if ((n.data[props.keyField] as ListEditorKey) === props.sourceKey) return n\n if (n.children?.length) {\n const hit = find(n.children)\n if (hit) return hit\n }\n }\n return null\n }\n return find(props.tree.children)\n})\n\n// `currentParentKey` = parent of the source row (null if source is at root).\n// We exclude this from candidates because moving to \"the same parent\" is not a\n// \"change parent\" operation (use Move-to-position instead).\nconst currentParentKey = computed<ListEditorKey | null | undefined>(() => {\n if (props.sourceKey === null) return undefined\n const find = (nodes: NestedTreeNode<TItem>[], parentKey: ListEditorKey | null): ListEditorKey | null | undefined => {\n for (const n of nodes) {\n if ((n.data[props.keyField] as ListEditorKey) === props.sourceKey) return parentKey\n if (n.children?.length) {\n const hit = find(n.children, n.data[props.keyField] as ListEditorKey)\n if (hit !== undefined) return hit\n }\n }\n return undefined\n }\n return find(props.tree.children, null)\n})\n\nconst candidates = computed<Candidate[]>(() => {\n const out: Candidate[] = []\n const subtreeDepth = sourceNode.value ? props.calculateSubtreeDepth(sourceNode.value) : 1\n\n out.push({\n key: null,\n depth: 0,\n label: t('common.sortable.changeParent.rootLabel'),\n hasChildren: props.tree.children.length > 0,\n disallowed: currentParentKey.value === null,\n reason: currentParentKey.value === null ? 'currentParent' : null,\n })\n\n const walk = (nodes: NestedTreeNode<TItem>[], depth: number) => {\n for (const n of nodes) {\n const key = n.data[props.keyField] as ListEditorKey\n let disallowed = false\n let reason: Candidate['reason'] = null\n if (key === props.sourceKey) {\n disallowed = true\n reason = 'self'\n } else if (descendantKeys.value.has(key)) {\n disallowed = true\n reason = 'descendant'\n } else if (currentParentKey.value !== undefined && currentParentKey.value === key) {\n disallowed = true\n reason = 'currentParent'\n } else if (depth + 1 + subtreeDepth > props.maxDepth) {\n disallowed = true\n reason = 'maxDepth'\n }\n out.push({\n key,\n depth: depth + 1,\n label: props.resolveLabel(n.data as TItem, key),\n hasChildren: !!(n.children && n.children.length > 0),\n disallowed,\n reason,\n })\n if (n.children?.length) walk(n.children, depth + 1)\n }\n }\n walk(props.tree.children, 0)\n return out\n})\n\nconst validCandidates = computed(() => candidates.value.filter((c) => !c.disallowed))\n\nconst selectedCandidate = computed<Candidate | null>(() =>\n selectedKey.value === undefined ? null : (candidates.value.find((c) => c.key === selectedKey.value) ?? null)\n)\n\nconst showPlacementPicker = computed<boolean>(() => !!selectedCandidate.value && selectedCandidate.value.hasChildren)\n\nconst onCandidateClick = (c: Candidate) => {\n if (c.disallowed) return\n selectedKey.value = c.key\n}\n\nconst onConfirm = () => {\n if (!selectedCandidate.value) return\n emit('confirm', selectedCandidate.value.key, placement.value)\n open.value = false\n}\n\nconst onCancel = () => {\n open.value = false\n}\n\nconst indent = (depth: number): string => `${depth * 16}px`\n\nconst reasonLabel = (reason: Candidate['reason']): string => {\n if (reason === 'maxDepth') return t('common.sortable.changeParent.errorMaxDepth')\n return ''\n}\n</script>\n\n<template>\n <VDialog\n v-model=\"open\"\n max-width=\"540\"\n persistent\n >\n <VCard>\n <VCardTitle>\n {{ t('common.sortable.changeParent.title') }}\n </VCardTitle>\n\n <VCardText\n v-if=\"validCandidates.length === 0\"\n class=\"text-body-medium\"\n >\n <VAlert\n type=\"info\"\n variant=\"tonal\"\n density=\"compact\"\n >\n <strong>{{ t('common.sortable.changeParent.noTargetsTitle') }}</strong>\n <div>{{ t('common.sortable.changeParent.noTargetsText') }}</div>\n </VAlert>\n </VCardText>\n\n <template v-else>\n <VCardText class=\"pb-1\">\n <VList\n density=\"compact\"\n class=\"le-change-parent__list\"\n >\n <VListItem\n v-for=\"c in candidates\"\n :key=\"c.key === null ? '__root__' : String(c.key)\"\n :disabled=\"c.disallowed\"\n :active=\"selectedKey === c.key\"\n :title=\"c.label\"\n :subtitle=\"reasonLabel(c.reason)\"\n :style=\"{ paddingInlineStart: indent(c.depth) }\"\n :data-disallowed=\"c.disallowed ? 'true' : 'false'\"\n :data-reason=\"c.reason ?? ''\"\n @click=\"onCandidateClick(c)\"\n >\n <template #prepend>\n <VIcon\n v-if=\"c.key === null\"\n icon=\"mdi-folder-home-outline\"\n size=\"18\"\n />\n <VIcon\n v-else-if=\"c.hasChildren\"\n icon=\"mdi-folder-outline\"\n size=\"18\"\n />\n <VIcon\n v-else\n icon=\"mdi-file-outline\"\n size=\"18\"\n />\n </template>\n </VListItem>\n </VList>\n </VCardText>\n\n <VCardText\n v-if=\"showPlacementPicker\"\n class=\"pt-0\"\n >\n <div class=\"text-body-medium mb-2\">\n {{\n t('common.sortable.changeParent.placementHeading', {\n label: selectedCandidate?.label ?? '',\n })\n }}\n </div>\n <VRadioGroup\n v-model=\"placement\"\n density=\"compact\"\n hide-details\n >\n <VRadio\n value=\"first\"\n :label=\"t('common.sortable.changeParent.placeFirst')\"\n />\n <VRadio\n value=\"last\"\n :label=\"t('common.sortable.changeParent.placeLast')\"\n />\n </VRadioGroup>\n </VCardText>\n </template>\n\n <VCardActions>\n <VSpacer />\n <VBtn\n variant=\"text\"\n @click=\"onCancel\"\n >\n {{ t('common.sortable.reorderCancel') }}\n </VBtn>\n <VBtn\n color=\"primary\"\n variant=\"elevated\"\n :disabled=\"!selectedCandidate || selectedCandidate.disallowed\"\n @click=\"onConfirm\"\n >\n {{ t('common.sortable.changeParent.confirm') }}\n </VBtn>\n </VCardActions>\n </VCard>\n </VDialog>\n</template>\n\n<style lang=\"scss\" scoped>\n.le-change-parent__list {\n max-height: 340px;\n overflow-y: auto;\n}\n</style>\n","<script setup lang=\"ts\" generic=\"TItem extends Record<string, any>\">\nimport { computed, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport type { ListEditorKey, NestedTree, NestedTreeNode } from '@/labs/listEditor/types/listEditorTypes'\n\nexport interface Props<TItem extends Record<string, any>> {\n tree: NestedTree<TItem>\n sourceKey: ListEditorKey | null\n keyField: string\n maxDepth: number\n resolveLabel: (raw: TItem, key: ListEditorKey) => string\n calculateSubtreeDepth: (node: NestedTreeNode<TItem>) => number\n}\n\ninterface Candidate {\n key: ListEditorKey | null\n depth: number\n label: string\n hasChildren: boolean\n disallowed: boolean\n reason: 'self' | 'descendant' | 'maxDepth' | 'currentParent' | null\n}\n\nconst props = defineProps<Props<TItem>>()\n\nconst emit = defineEmits<{\n confirm: [parentId: ListEditorKey | null, position: 'first' | 'last']\n}>()\n\nconst open = defineModel<boolean>({ required: true })\n\nconst { t } = useI18n()\n\nconst selectedKey = ref<ListEditorKey | null | undefined>(undefined)\nconst placement = ref<'first' | 'last'>('last')\n\nwatch(open, (now) => {\n if (now) {\n selectedKey.value = undefined\n placement.value = 'last'\n }\n})\n\n// Walk the source's subtree to gather descendant keys (cycle prevention).\nconst descendantKeys = computed<Set<ListEditorKey>>(() => {\n const out = new Set<ListEditorKey>()\n if (props.sourceKey === null) return out\n const walk = (nodes: NestedTreeNode<TItem>[]): boolean => {\n for (const n of nodes) {\n const k = n.data[props.keyField] as ListEditorKey\n if (k === props.sourceKey) {\n const collect = (sub: NestedTreeNode<TItem>[]) => {\n for (const s of sub) {\n out.add(s.data[props.keyField] as ListEditorKey)\n if (s.children?.length) collect(s.children)\n }\n }\n if (n.children?.length) collect(n.children)\n return true\n }\n if (n.children?.length && walk(n.children)) return true\n }\n return false\n }\n walk(props.tree.children)\n return out\n})\n\nconst sourceNode = computed<NestedTreeNode<TItem> | null>(() => {\n if (props.sourceKey === null) return null\n const find = (nodes: NestedTreeNode<TItem>[]): NestedTreeNode<TItem> | null => {\n for (const n of nodes) {\n if ((n.data[props.keyField] as ListEditorKey) === props.sourceKey) return n\n if (n.children?.length) {\n const hit = find(n.children)\n if (hit) return hit\n }\n }\n return null\n }\n return find(props.tree.children)\n})\n\n// `currentParentKey` = parent of the source row (null if source is at root).\n// We exclude this from candidates because moving to \"the same parent\" is not a\n// \"change parent\" operation (use Move-to-position instead).\nconst currentParentKey = computed<ListEditorKey | null | undefined>(() => {\n if (props.sourceKey === null) return undefined\n const find = (nodes: NestedTreeNode<TItem>[], parentKey: ListEditorKey | null): ListEditorKey | null | undefined => {\n for (const n of nodes) {\n if ((n.data[props.keyField] as ListEditorKey) === props.sourceKey) return parentKey\n if (n.children?.length) {\n const hit = find(n.children, n.data[props.keyField] as ListEditorKey)\n if (hit !== undefined) return hit\n }\n }\n return undefined\n }\n return find(props.tree.children, null)\n})\n\nconst candidates = computed<Candidate[]>(() => {\n const out: Candidate[] = []\n const subtreeDepth = sourceNode.value ? props.calculateSubtreeDepth(sourceNode.value) : 1\n\n out.push({\n key: null,\n depth: 0,\n label: t('common.sortable.changeParent.rootLabel'),\n hasChildren: props.tree.children.length > 0,\n disallowed: currentParentKey.value === null,\n reason: currentParentKey.value === null ? 'currentParent' : null,\n })\n\n const walk = (nodes: NestedTreeNode<TItem>[], depth: number) => {\n for (const n of nodes) {\n const key = n.data[props.keyField] as ListEditorKey\n let disallowed = false\n let reason: Candidate['reason'] = null\n if (key === props.sourceKey) {\n disallowed = true\n reason = 'self'\n } else if (descendantKeys.value.has(key)) {\n disallowed = true\n reason = 'descendant'\n } else if (currentParentKey.value !== undefined && currentParentKey.value === key) {\n disallowed = true\n reason = 'currentParent'\n } else if (depth + 1 + subtreeDepth > props.maxDepth) {\n disallowed = true\n reason = 'maxDepth'\n }\n out.push({\n key,\n depth: depth + 1,\n label: props.resolveLabel(n.data as TItem, key),\n hasChildren: !!(n.children && n.children.length > 0),\n disallowed,\n reason,\n })\n if (n.children?.length) walk(n.children, depth + 1)\n }\n }\n walk(props.tree.children, 0)\n return out\n})\n\nconst validCandidates = computed(() => candidates.value.filter((c) => !c.disallowed))\n\nconst selectedCandidate = computed<Candidate | null>(() =>\n selectedKey.value === undefined ? null : (candidates.value.find((c) => c.key === selectedKey.value) ?? null)\n)\n\nconst showPlacementPicker = computed<boolean>(() => !!selectedCandidate.value && selectedCandidate.value.hasChildren)\n\nconst onCandidateClick = (c: Candidate) => {\n if (c.disallowed) return\n selectedKey.value = c.key\n}\n\nconst onConfirm = () => {\n if (!selectedCandidate.value) return\n emit('confirm', selectedCandidate.value.key, placement.value)\n open.value = false\n}\n\nconst onCancel = () => {\n open.value = false\n}\n\nconst indent = (depth: number): string => `${depth * 16}px`\n\nconst reasonLabel = (reason: Candidate['reason']): string => {\n if (reason === 'maxDepth') return t('common.sortable.changeParent.errorMaxDepth')\n return ''\n}\n</script>\n\n<template>\n <VDialog\n v-model=\"open\"\n max-width=\"540\"\n persistent\n >\n <VCard>\n <VCardTitle>\n {{ t('common.sortable.changeParent.title') }}\n </VCardTitle>\n\n <VCardText\n v-if=\"validCandidates.length === 0\"\n class=\"text-body-medium\"\n >\n <VAlert\n type=\"info\"\n variant=\"tonal\"\n density=\"compact\"\n >\n <strong>{{ t('common.sortable.changeParent.noTargetsTitle') }}</strong>\n <div>{{ t('common.sortable.changeParent.noTargetsText') }}</div>\n </VAlert>\n </VCardText>\n\n <template v-else>\n <VCardText class=\"pb-1\">\n <VList\n density=\"compact\"\n class=\"le-change-parent__list\"\n >\n <VListItem\n v-for=\"c in candidates\"\n :key=\"c.key === null ? '__root__' : String(c.key)\"\n :disabled=\"c.disallowed\"\n :active=\"selectedKey === c.key\"\n :title=\"c.label\"\n :subtitle=\"reasonLabel(c.reason)\"\n :style=\"{ paddingInlineStart: indent(c.depth) }\"\n :data-disallowed=\"c.disallowed ? 'true' : 'false'\"\n :data-reason=\"c.reason ?? ''\"\n @click=\"onCandidateClick(c)\"\n >\n <template #prepend>\n <VIcon\n v-if=\"c.key === null\"\n icon=\"mdi-folder-home-outline\"\n size=\"18\"\n />\n <VIcon\n v-else-if=\"c.hasChildren\"\n icon=\"mdi-folder-outline\"\n size=\"18\"\n />\n <VIcon\n v-else\n icon=\"mdi-file-outline\"\n size=\"18\"\n />\n </template>\n </VListItem>\n </VList>\n </VCardText>\n\n <VCardText\n v-if=\"showPlacementPicker\"\n class=\"pt-0\"\n >\n <div class=\"text-body-medium mb-2\">\n {{\n t('common.sortable.changeParent.placementHeading', {\n label: selectedCandidate?.label ?? '',\n })\n }}\n </div>\n <VRadioGroup\n v-model=\"placement\"\n density=\"compact\"\n hide-details\n >\n <VRadio\n value=\"first\"\n :label=\"t('common.sortable.changeParent.placeFirst')\"\n />\n <VRadio\n value=\"last\"\n :label=\"t('common.sortable.changeParent.placeLast')\"\n />\n </VRadioGroup>\n </VCardText>\n </template>\n\n <VCardActions>\n <VSpacer />\n <VBtn\n variant=\"text\"\n @click=\"onCancel\"\n >\n {{ t('common.sortable.reorderCancel') }}\n </VBtn>\n <VBtn\n color=\"primary\"\n variant=\"elevated\"\n :disabled=\"!selectedCandidate || selectedCandidate.disallowed\"\n @click=\"onConfirm\"\n >\n {{ t('common.sortable.changeParent.confirm') }}\n </VBtn>\n </VCardActions>\n </VCard>\n </VDialog>\n</template>\n\n<style lang=\"scss\" scoped>\n.le-change-parent__list {\n max-height: 340px;\n overflow-y: auto;\n}\n</style>\n","<script setup lang=\"ts\" generic=\"TItem extends Record<string, any>\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, useSlots, useTemplateRef, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport { useContainerWidth } from '@/labs/listEditor/composables/useContainerWidth'\nimport { useIsTouchDevice } from '@/labs/listEditor/composables/useIsTouchDevice'\nimport { useKeyboardNav } from '@/labs/listEditor/composables/useKeyboardNav'\nimport { useSortable } from '@vueuse/integrations/useSortable'\nimport { type NestedViewItem } from '@/labs/listEditor/composables/useNestedListEditor'\nimport {\n useNestedListEditorController,\n type GetKey,\n type ListEditorValidationResult,\n type NestedListEditorHandle,\n type PositionOption,\n type PositionStrategy,\n} from '@/labs/listEditor/composables/useNestedListEditorController'\nimport {\n computeInstruction,\n type ExecutableInstruction,\n type Instruction,\n} from '@/labs/listEditor/composables/useDragInstruction'\nimport {\n provideListEditorStateScope,\n useNestedListEditorStateEntry,\n type NestedListEditorStateBindings,\n} from '@/labs/listEditor/composables/useListEditorStateScope'\nimport { resolveCompactText as resolveCompactTextUtil } from '@/labs/listEditor/composables/resolveCompactText'\nimport { useUnsavedSection } from '@/labs/unsavedGuard/useUnsavedSection'\nimport { useDeleteDialog } from '@/labs/listEditor/composables/useDeleteDialog'\nimport { useInlineEditing } from '@/labs/listEditor/composables/useInlineEditing'\nimport { validateAllAndReveal } from '@/labs/listEditor/utils/revealInvalidRows'\nimport { useListEditorScopeValidity } from '@/labs/listEditor/composables/useListEditorScopeValidity'\nimport { useReorderMode } from '@/labs/listEditor/composables/useReorderMode'\nimport { cloneDeep } from '@/utils/common'\nimport type {\n ListEditorKey,\n ListEditorValidationScope,\n ListEditorValidationState,\n NestedPositionHint,\n NestedTree,\n NestedTreeNode,\n} from '@/labs/listEditor/types/listEditorTypes'\nimport { useAlerts } from '@/composables/system/alerts'\nimport { stringToInt } from '@/utils/string'\nimport LeNestedRow from '@/labs/listEditor/internal/LeNestedRow.vue'\nimport LeDeleteDialog from '@/labs/listEditor/internal/LeDeleteDialog.vue'\nimport LeMoveToPositionDialog from '@/labs/listEditor/internal/LeMoveToPositionDialog.vue'\nimport LeChangeParentDialog from '@/labs/listEditor/internal/LeChangeParentDialog.vue'\nimport LeEmptyState from '@/labs/listEditor/internal/LeEmptyState.vue'\nimport LeStatus from '@/labs/listEditor/internal/LeStatus.vue'\nimport { DRAG_GHOST_CLASS, DRAG_CHOSEN_CLASS, DRAG_CLASS } from '@/labs/listEditor/internal/constants'\n\nexport interface DecoratedNestedViewItem<T> extends NestedViewItem<T> {\n editing: boolean\n expanded: boolean\n childrenExpanded: boolean\n loading: boolean\n dirty: boolean\n moved: boolean\n unsaved: boolean\n}\n\nexport interface DragState {\n sourceKey: ListEditorKey\n sourceSubtreeDepth: number\n instruction: Instruction | null\n}\n\nexport type ReorderMode = 'view' | 'reorder'\n\n// Hoisted for vite-plugin-dts d.ts rollup.\nexport interface RowActions<TItem> {\n edit: () => void\n save: () => Promise<void> | void\n cancel: () => void\n close: () => void\n delete: () => Promise<void>\n addAfter: () => void\n addChild: () => void\n toggleExpand: () => void\n toggleDetail: () => void\n moveUp: () => void\n moveDown: () => void\n moveTop: () => void\n moveBottom: () => void\n indent: () => void\n outdent: () => void\n update: (data: TItem) => void\n}\nexport interface RowSlotProps<TItem extends Record<string, any>> {\n item: DecoratedNestedViewItem<TItem> & { validationState: ListEditorValidationState }\n raw: TItem\n index: number\n key: ListEditorKey\n depth: number\n parent: TItem | null\n parentKey: ListEditorKey | null\n childrenCount: number\n hasChildren: boolean\n childrenAllowed: boolean\n firstInParent: boolean\n lastInParent: boolean\n readonly: boolean\n disabled: boolean\n expanded: boolean\n childrenExpanded: boolean\n editing: boolean\n dirty: boolean\n unsaved: boolean\n moved: boolean\n reorderMode: boolean\n canMoveUp: boolean\n canMoveDown: boolean\n canIndent: boolean\n canOutdent: boolean\n canAddChild: boolean\n touch: boolean\n /**\n * Row-scoped `state-key` prefix. Thread it down to any list editor rendered in this slot and set\n * its `:state-key=\"`${stateKeyPrefix}:someList`\"` — that editor's controller then lives in THIS\n * editor's scope and survives the row collapsing (which unmounts the whole slot subtree). Entries\n * under this prefix are disposed when the row is removed.\n */\n stateKeyPrefix: string\n actions: RowActions<TItem>\n}\nexport interface ToolbarSlotProps {\n applying: boolean\n hasPendingChanges: boolean\n movedCount: number\n error: string | null\n actions: { apply: () => Promise<void>; cancel: () => void }\n}\nexport interface ReorderToggleSlotProps {\n mode: ReorderMode\n disabled: boolean\n hasPendingChanges: boolean\n actions: { enterReorderMode: () => void; exitReorderMode: () => void }\n}\nexport interface HeaderSlotProps extends ReorderToggleSlotProps {\n title: string | null\n}\nexport interface EmptySlotProps {\n readonly: boolean\n disabled: boolean\n actions: { add: () => void }\n}\nexport interface AddButtonSlotProps {\n readonly: boolean\n disabled: boolean\n props: { onClick: () => void }\n actions: { add: () => void }\n}\n\nexport interface Props<TItem extends Record<string, any>> {\n maxDepth: number\n\n /**\n * New-row factory; if set, add/add-after/add-inside insert through the\n * controller (no `@add` handler needed). Optional for read-only trees.\n */\n factory?: () => TItem\n /**\n * Stable row identity. Default `'id'`. Never point at the position field.\n * Typed `string` (not `keyof TItem`): a bare keyof compiles to a Boolean-only\n * runtime type that silently coerces `get-key=\"id\"` to `true`.\n */\n getKey?: string | ((item: TItem) => ListEditorKey)\n /**\n * Managed order field. Default `'position'`; `false` opts out. Typed\n * `[String, Boolean, Object]` with `string` before `false`: a bare keyof\n * compiles Boolean-only, and `false`-first would coerce `position=\"position\"`\n * (value == prop name) to `true`. Object form folds in `{ field, multiplier }`.\n */\n position?: string | false | { field: string; multiplier?: number; strategy?: PositionStrategy }\n /** Parent-key field written onto reparented rows. Default `'parent'`. */\n parentField?: string\n /**\n * Extra fields dropped from the dirty content-hash (position + parent always\n * dropped), e.g. a child collection tracked by a separate editor.\n */\n dirtyExclude?: string[]\n /** Per-row validity — `true` (or `{ valid: true }`) = VALID. Drives the red rail + save guard. */\n validate?: (item: TItem) => ListEditorValidationResult\n /**\n * Vuelidate `$scope` (the same one the consumer's row forms / save-gate collector use). When set,\n * the editor registers its aggregate validity under it, so a plain `v$.$invalid` save gate blocks\n * AND reveals a collapsed invalid row — no `validateAll()` call needed in the save flow. Omit to\n * keep legacy behavior (gate the save on the exposed `validateAll()` yourself); `false` opts out.\n */\n validationScope?: ListEditorValidationScope | false\n /**\n * Opt-in lifted controller (`useNestedListEditorController()`) so state\n * survives unmount/remount; omitted = internal controller. Same handle\n * reachable via `useTemplateRef`.\n */\n editor?: NestedListEditorHandle<TItem>\n /**\n * Persist this editor's controller in the nearest ANCESTOR editor's row-state scope under this\n * key, instead of owning it. Use it for an editor rendered in another editor's `#item` slot (that\n * slot is behind a `v-if`, so collapsing the row unmounts this editor and a component-owned\n * controller would re-baseline the already-edited tree on re-expand). Derive the key from the row\n * slot's `stateKeyPrefix`. Options stay declared here, on this tag. Ignored when `:editor` is\n * passed, and a no-op without an ancestor scope.\n */\n stateKey?: string\n\n readonly?: boolean\n disabled?: boolean\n loading?: boolean\n error?: string | null\n\n /** Heading above the list. A ready translated string, like every text prop here. */\n title?: string | null\n\n /**\n * Field shown in a collapsed row when no `#item-compact` slot is given. A dotted path reads\n * through nested objects (`texts.title`); a path that runs into nothing renders empty.\n */\n compactField?: string | null\n statusField?: string | null\n\n showAddButton?: boolean\n showAddChildButton?: boolean\n showDeleteButton?: boolean\n showEditButton?: boolean\n showAddAfterAction?: boolean\n showMoveToPosition?: boolean\n showChangeParent?: boolean\n showExpandToggle?: boolean\n\n /** Label of the add button. A ready translated string; omit it for the library's own wording. */\n addLabel?: string | null\n /** Heading of the empty state. A ready translated string. */\n emptyTitle?: string | null\n\n disableRowClick?: boolean\n disableDeleteConfirm?: boolean\n /**\n * Disable unsaved-state tracking — no dirty markers, never reads as unsaved. For a list that is a\n * VIEW of data owned elsewhere (a display cache rebuilt from a parent's field, say): nothing in\n * such a component can ever clear an amber row, so it must not raise one. Mirrors `AListEditor`.\n */\n disableUnsaved?: boolean\n deleteConfirmTitle?: string | null\n deleteConfirmText?: string | null\n /**\n * How a row delete persists. `deferred` (default): the row disappears but the deletion counts as an\n * unconfirmed change until save (revertible until then, incl. via reorder Cancel). `immediate`: the\n * consumer deletes on the backend — the dialog says it is irreversible and it does NOT read as unsaved.\n */\n deleteMode?: 'immediate' | 'deferred'\n\n closeVariant?: 'auto' | 'icon' | 'labeled'\n\n loadingKeys?: Set<ListEditorKey> | null\n\n showReorderToggle?: boolean\n disableReorder?: boolean\n disableDrag?: boolean\n\n onDeleteConfirm?: (item: TItem) => Promise<boolean> | boolean\n onDelete?: (item: TItem) => Promise<void> | void\n onItemSave?: (item: TItem) => Promise<void> | void\n onReorderApply?: (tree: NestedTree<TItem>) => Promise<void> | void\n\n /** Registers this editor as a named unsaved-changes section under the given (translated) label. */\n unsavedSectionLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props<TItem>>(), {\n unsavedSectionLabel: undefined,\n factory: undefined,\n getKey: undefined,\n position: undefined,\n parentField: 'parent',\n dirtyExclude: undefined,\n validate: undefined,\n validationScope: undefined,\n editor: undefined,\n stateKey: undefined,\n readonly: false,\n disabled: false,\n loading: false,\n error: null,\n title: null,\n compactField: null,\n statusField: null,\n showAddButton: true,\n showAddChildButton: true,\n showDeleteButton: true,\n showEditButton: true,\n showAddAfterAction: false,\n showMoveToPosition: false,\n showChangeParent: false,\n showExpandToggle: true,\n addLabel: null,\n emptyTitle: null,\n disableRowClick: false,\n disableDeleteConfirm: false,\n disableUnsaved: false,\n deleteConfirmTitle: null,\n deleteConfirmText: null,\n deleteMode: 'deferred',\n closeVariant: 'auto',\n loadingKeys: null,\n showReorderToggle: true,\n disableReorder: false,\n disableDrag: false,\n onDeleteConfirm: undefined,\n onDelete: undefined,\n onItemSave: undefined,\n onReorderApply: undefined,\n})\n\nconst emit = defineEmits<{\n add: [hint: NestedPositionHint | undefined]\n 'add-child': [parent: NestedViewItem<TItem>]\n edit: [item: NestedViewItem<TItem>]\n deleted: [item: NestedViewItem<TItem>]\n close: [item: NestedViewItem<TItem>]\n 'item-saved': [item: NestedViewItem<TItem>]\n 'item-expand': [item: NestedViewItem<TItem>, expanded: boolean]\n 'reorder-start': []\n 'reorder-cancel': []\n 'reorder-applied': [tree: NestedTree<TItem>]\n 'reorder-apply-error': [error: unknown]\n 'reorder-end': []\n indent: [item: NestedViewItem<TItem>]\n outdent: [item: NestedViewItem<TItem>]\n}>()\n\nconst modelValue = defineModel<NestedTree<TItem>>({ required: true })\nconst mode = defineModel<ReorderMode>('mode', { default: 'view' })\n\ndefineSlots<{\n header?: (props: HeaderSlotProps) => unknown\n 'reorder-toggle'?: (props: ReorderToggleSlotProps) => unknown\n 'reorder-toolbar'?: (props: ToolbarSlotProps) => unknown\n empty?: (props: EmptySlotProps) => unknown\n 'add-button'?: (props: AddButtonSlotProps) => unknown\n item?: (props: RowSlotProps<TItem>) => unknown\n 'item-compact'?: (props: RowSlotProps<TItem>) => unknown\n 'item-readonly'?: (props: RowSlotProps<TItem>) => unknown\n 'item-status'?: (props: RowSlotProps<TItem>) => unknown\n 'item-footer'?: (props: RowSlotProps<TItem>) => unknown\n 'item-actions'?: (props: RowSlotProps<TItem>) => unknown\n 'before-item'?: (props: RowSlotProps<TItem>) => unknown\n 'after-item'?: (props: RowSlotProps<TItem>) => unknown\n}>()\n\nconst { t } = useI18n()\nconst slots = useSlots()\nconst { showWarningT } = useAlerts()\n\nconst rootEl = useTemplateRef<HTMLElement>('rootEl')\nconst { isNarrow } = useContainerWidth(rootEl)\n\nconst isTouch = useIsTouchDevice()\n\nconst effectiveCloseVariant = computed<'icon' | 'labeled'>(() => {\n if (props.closeVariant === 'icon') return 'icon'\n if (props.closeVariant === 'labeled') return 'labeled'\n return isNarrow.value ? 'icon' : 'labeled'\n})\n\nconst childrenExpandedKeys = ref<Set<ListEditorKey>>(new Set())\n// Readonly detail body visibility — independent of subtree expansion.\nconst detailExpandedKeys = ref<Set<ListEditorKey>>(new Set())\n\n// Local mirror of the controller's key resolution so walkers + the template key\n// rows the same way the controller tracks them. Construction-time config, read once.\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst getKeyOpt = props.getKey ?? 'id'\nconst keyFieldName = (typeof getKeyOpt === 'function' ? 'id' : getKeyOpt) as string\nconst keyOf = (data: TItem): ListEditorKey =>\n typeof getKeyOpt === 'function' ? getKeyOpt(data) : (data[getKeyOpt as keyof TItem] as ListEditorKey)\n\n// Managed position field (row position, move-to-position dialog, flatViewItems). Mirrors the controller.\nconst positionFieldName = ((): string => {\n const p = props.position\n if (p === undefined || p === false) return 'position'\n if (typeof p === 'object') return p.field\n return p\n})()\n\nconst initChildrenExpanded = (tree: NestedTree<TItem>) => {\n const walk = (nodes: NestedTreeNode<TItem>[]) => {\n for (const n of nodes) {\n if (n.children && n.children.length > 0) {\n childrenExpandedKeys.value.add(keyOf(n.data))\n walk(n.children)\n }\n }\n }\n walk(tree.children)\n}\n// eslint-disable-next-line vue/no-ref-object-reactivity-loss\ninitChildrenExpanded(modelValue.value)\n\n// State controller (v2). Undefined getKey/position/validate fall back to controller defaults\n// ('id' / 'position' / always-valid). Its `viewItems` ignores expand state, so we re-project an\n// expand-aware list below (flatViewItems).\nconst controllerOptions: NestedListEditorStateBindings<TItem> = {\n get: () => modelValue.value,\n set: (v) => (modelValue.value = v),\n factory: props.factory,\n getKey: props.getKey as GetKey<TItem> | undefined,\n position: props.position as PositionOption<TItem> | undefined,\n parentField: props.parentField,\n maxDepth: props.maxDepth,\n dirtyExclude: () => props.dirtyExclude ?? [],\n validate: props.validate,\n}\n// Precedence: an explicitly lifted `:editor` wins; then a `state-key`'d entry persisted in the\n// nearest ancestor editor's row-state scope (rebound to this instance on every remount); else this\n// component owns the controller — today's behaviour, unchanged.\nconst stateEntry = props.editor ? null : useNestedListEditorStateEntry<TItem>(props.stateKey, controllerOptions)\nconst controller = props.editor ?? stateEntry?.handle ?? useNestedListEditorController<TItem>(controllerOptions)\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\n// Row-state scope for THIS editor's `#item` slot subtree — see ASortableListEditor.\nconst rowStateScope = provideListEditorStateScope(stateEntry?.childScope ?? null)\n\n// FULL flattened list (collapsed-branch rows included) built directly off the\n// tree: the recursive renderer (LeNestedRow) filters by parentKey + childrenExpandedKeys,\n// so it needs every row present in viewItemsDecorated even when hidden.\nconst flatViewItems = computed<NestedViewItem<TItem>[]>(() => {\n const flat: NestedViewItem<TItem>[] = []\n let flatIndex = 0\n const maxDepth = props.maxDepth\n const walk = (nodes: NestedTreeNode<TItem>[], depth: number, parentNode: NestedTreeNode<TItem> | null) => {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]\n const key = keyOf(node.data)\n const childrenAllowed = node.children !== undefined\n const hasChildren = childrenAllowed && (node.children?.length ?? 0) > 0\n const childrenCount = node.children?.length ?? 0\n const remainingDepth = maxDepth - (depth + 1)\n flat.push({\n key,\n index: flatIndex++,\n raw: node.data,\n position: node.data[positionFieldName] as number | undefined,\n node,\n depth,\n parent: parentNode?.data ?? null,\n parentKey: parentNode ? keyOf(parentNode.data) : null,\n childrenCount,\n hasChildren,\n childrenAllowed,\n siblingIndex: i,\n siblingCount: nodes.length,\n firstInParent: i === 0,\n lastInParent: i === nodes.length - 1,\n canAddChild: childrenAllowed && remainingDepth > 0,\n canIndent: i > 0,\n canOutdent: depth > 0,\n })\n if (hasChildren) walk(node.children as NestedTreeNode<TItem>[], depth + 1, node)\n }\n }\n walk(modelValue.value.children, 0, null)\n return flat\n})\n\n// Drop the persisted controllers of rows that no longer exist. `post` so the removed row's subtree\n// has already unmounted (its editors released their entries) before we stop their effect scopes.\nwatch(\n () => flatViewItems.value.map((vi) => String(vi.key)).join('|'),\n () => rowStateScope.retainOwners(flatViewItems.value.map((vi) => vi.key)),\n { flush: 'post' }\n)\n\n// Snapshot for reorder-cancel restore only; dirty detection lives in the controller.\n// `movedKeys` is component view state driving the toolbar \"N pending\" counter,\n// cleared on every mode transition by `useReorderMode`.\nconst snapshot = shallowRef<NestedTree<TItem> | null>(null)\nconst movedKeys = ref<Set<ListEditorKey>>(new Set())\n// Deletes made during the current reorder session, split by mode — deferred ones count in the toolbar\n// and revert on Cancel; immediate (backend) ones are re-applied on Cancel so they stay gone (else the\n// snapshot-restore would resurrect a server-deleted row). Mirrors ASortableListEditor.\nconst sessionDeferredDeletes = ref<Set<ListEditorKey>>(new Set())\nconst sessionImmediateDeletes = ref<Set<ListEditorKey>>(new Set())\n\n// Re-baseline current tree as saved. Legacy name kept; maps onto `controller.commit()`.\nconst resetDirtyBaseline = () => {\n controller.commit()\n movedKeys.value = new Set()\n}\n// Mark row + every descendant moved — a moved parent visually carries its subtree.\nconst markMoved = (key: ListEditorKey) => {\n const { node } = controller.findNode(key)\n if (!node) {\n movedKeys.value.add(key)\n return\n }\n const collect = (n: NestedTreeNode<TItem>) => {\n movedKeys.value.add(keyOf(n.data))\n if (n.children) n.children.forEach(collect)\n }\n collect(node)\n}\n\nconst decoratorCache = new Map<ListEditorKey, DecoratedNestedViewItem<TItem>>()\nconst viewItemsDecorated = computed<DecoratedNestedViewItem<TItem>[]>(() => {\n const next: DecoratedNestedViewItem<TItem>[] = []\n const liveKeys = new Set<ListEditorKey>()\n for (const vi of flatViewItems.value) {\n liveKeys.add(vi.key)\n const editing = editingKeys.value.has(vi.key)\n const expanded = detailExpandedKeys.value.has(vi.key)\n const childrenExpanded = childrenExpandedKeys.value.has(vi.key)\n const loading = props.loadingKeys?.has(vi.key) ?? false\n const moved = movedKeys.value.has(vi.key)\n // Amber = controller dirty OR reorder-session moved. readonly suppresses it\n // (read-only views can't be unsaved; also dodges a mount-before-load baseline). (QA 85050 sweep)\n const dirty = props.readonly ? false : controller.isUnsaved(vi.key)\n // `disableUnsaved` suppresses only the amber marker — the validation rail still shows, since\n // hiding dirty state should not hide a real error.\n const unsaved = props.disableUnsaved || props.readonly ? false : dirty || moved\n const cached = decoratorCache.get(vi.key)\n if (\n cached &&\n cached.raw === vi.raw &&\n cached.index === vi.index &&\n cached.position === vi.position &&\n cached.depth === vi.depth &&\n cached.parentKey === vi.parentKey &&\n cached.childrenCount === vi.childrenCount &&\n cached.firstInParent === vi.firstInParent &&\n cached.lastInParent === vi.lastInParent &&\n cached.editing === editing &&\n cached.expanded === expanded &&\n cached.childrenExpanded === childrenExpanded &&\n cached.loading === loading &&\n cached.dirty === dirty &&\n cached.moved === moved &&\n cached.unsaved === unsaved\n ) {\n next.push(cached)\n continue\n }\n const decorated: DecoratedNestedViewItem<TItem> = {\n ...vi,\n editing,\n expanded,\n childrenExpanded,\n loading,\n dirty,\n moved,\n unsaved,\n }\n decoratorCache.set(vi.key, decorated)\n next.push(decorated)\n }\n for (const key of decoratorCache.keys()) {\n if (!liveKeys.has(key)) decoratorCache.delete(key)\n }\n return next\n})\n\nconst isEmpty = computed(() => modelValue.value.children.length === 0)\nconst totalItemCount = computed(() => flatViewItems.value.length)\n\nconst rowsContainer = useTemplateRef<HTMLElement>('rowsContainer')\n\nconst canInteract = computed(() => !props.readonly && !props.disabled && !props.loading)\nconst canEnterReorder = computed(() => canInteract.value && !props.disableReorder && totalItemCount.value > 1)\n\nconst isInlineEdit = computed(() => !!(slots as Record<string, unknown>).item)\nconst hasReadonlyDetail = computed(() => !!(slots as Record<string, unknown>)['item-readonly'])\nconst showInlineSaveFooter = computed(() => !!props.onItemSave)\n\nconst { editingKeys, editingSnapshots, clearEditing, beginEdit, cancelEdit, commitEdit, closeEdit, requestAutoOpen } =\n useInlineEditing<TItem, NestedViewItem<TItem>>({\n rowsContainer,\n rowSelector: '.a-le-row-wrapper',\n isInlineEdit,\n // markDirty=false: restoring the pre-edit snapshot on cancel must not flag the\n // node dirty (it would otherwise be resent by partial-multi saves).\n restoreSnapshot: (key, data) => controller.updateItem(key, data, false),\n watchKeys: () => {\n const keys: ListEditorKey[] = []\n const walk = (nodes: NestedTreeNode<TItem>[]) => {\n for (const n of nodes) {\n keys.push(keyOf(n.data))\n if (n.children && n.children.length) walk(n.children)\n }\n }\n walk(modelValue.value.children)\n return keys\n },\n findEntry: (key) => {\n const { node } = controller.findNode(key)\n return node ? { data: node.data } : null\n },\n afterAutoOpen: (key) => {\n const { parent } = controller.findNode(key)\n if (parent) {\n childrenExpandedKeys.value.add(keyOf(parent.data))\n }\n },\n })\n\nconst {\n applying,\n applyError,\n hasPendingChanges,\n movedCount,\n reorderMode,\n enterReorderMode,\n cancelReorderMode,\n applyReorder,\n} = useReorderMode<NestedTree<TItem>>({\n mode,\n snapshot,\n movedKeys,\n modelValue,\n cloneModel: (m) => cloneDeep(m) as NestedTree<TItem>,\n applyModel: (m) => {\n modelValue.value = m\n },\n canEnterReorder,\n onEnter: () => {\n sessionDeferredDeletes.value = new Set()\n sessionImmediateDeletes.value = new Set()\n clearEditing()\n // Expand every branch so all rows are reachable drag targets.\n for (const k of expandableKeys.value) childrenExpandedKeys.value.add(k)\n nextTick(() => {\n if (dragEnabled.value) initSortables()\n })\n },\n onExternalEnter: () => {\n sessionDeferredDeletes.value = new Set()\n sessionImmediateDeletes.value = new Set()\n clearEditing()\n },\n // Cancel: the snapshot-restore has brought every deleted row back; reconcile the controller so a\n // deferred delete truly reverts (clear tombstone, row stays) and an immediate delete stays gone (row\n // removed again — it is deleted on the backend and must not resurrect / be re-created by a save).\n onCancel: () => {\n for (const key of sessionDeferredDeletes.value) controller.restoreDeleted(key)\n for (const key of sessionImmediateDeletes.value) controller.deleteItem(key, { trackDeleted: false })\n sessionDeferredDeletes.value = new Set()\n sessionImmediateDeletes.value = new Set()\n destroySortables()\n },\n onApplyEnd: () => {\n sessionDeferredDeletes.value = new Set()\n sessionImmediateDeletes.value = new Set()\n destroySortables()\n },\n onReorderApply: (tree) => props.onReorderApply?.(tree),\n emit: {\n reorderStart: () => emit('reorder-start'),\n reorderCancel: () => emit('reorder-cancel'),\n reorderApplied: (payload) => emit('reorder-applied', payload),\n reorderApplyError: (err) => emit('reorder-apply-error', err),\n reorderEnd: () => emit('reorder-end'),\n },\n})\n\n// Reorder-toolbar pending indicator = session moves + deferred deletes made in the session, so a delete\n// in reorder mode no longer reads as \"no pending changes\" and Apply stays enabled.\nconst nestedPendingCount = computed<number>(() => movedCount.value + sessionDeferredDeletes.value.size)\nconst nestedPendingChanges = computed<boolean>(() => hasPendingChanges.value || sessionDeferredDeletes.value.size > 0)\n\nconst canAdd = computed(() => canInteract.value && props.showAddButton && !reorderMode.value)\nconst dragEnabled = computed(() => reorderMode.value && !isTouch.value && !props.disableDrag)\n\nconst addLabelResolved = computed(() => props.addLabel ?? t('common.sortable.add'))\nconst emptyTitleResolved = computed(() => props.emptyTitle ?? t('common.sortable.emptyTitle'))\nconst deleteConfirmTitleResolved = computed(() => props.deleteConfirmTitle ?? t('common.sortable.deleteConfirmTitle'))\nconst deleteConfirmTextResolved = computed(\n () =>\n props.deleteConfirmText ??\n (props.deleteMode === 'immediate'\n ? t('common.sortable.deleteConfirmText')\n : t('common.sortable.deleteConfirmTextDeferred'))\n)\n\nconst reorderToggleVisible = computed<boolean>(\n (): boolean => !props.readonly && props.showReorderToggle && !reorderMode.value && totalItemCount.value > 0\n)\n\nconst compactReorderButton = computed<boolean>((): boolean => !!props.title && isNarrow.value)\n\n// Keys of every node that has children — the expand/collapse candidates.\nconst expandableKeys = computed<ListEditorKey[]>(() => {\n const out: ListEditorKey[] = []\n const walk = (nodes: NestedTreeNode<TItem>[]) => {\n for (const n of nodes) {\n if (n.children && n.children.length > 0) {\n out.push(keyOf(n.data))\n walk(n.children)\n }\n }\n }\n walk(modelValue.value.children)\n return out\n})\n\nconst allExpanded = computed<boolean>(\n () => expandableKeys.value.length > 0 && expandableKeys.value.every((k) => childrenExpandedKeys.value.has(k))\n)\n\nconst expandAllVisible = computed<boolean>(() => !reorderMode.value && expandableKeys.value.length > 0)\n\nconst toggleExpandAll = () => {\n if (allExpanded.value) {\n for (const k of expandableKeys.value) childrenExpandedKeys.value.delete(k)\n } else {\n for (const k of expandableKeys.value) childrenExpandedKeys.value.add(k)\n }\n}\n\nconst headerVisible = computed<boolean>(\n (): boolean =>\n !!(\n props.title ||\n (slots as Record<string, unknown>).header ||\n (slots as Record<string, unknown>)['reorder-toggle'] ||\n reorderToggleVisible.value ||\n expandAllVisible.value ||\n reorderMode.value\n )\n)\n\n// One Sortable instance per group so drag/drop moves items within/between groups;\n// SortableJS owns the pointer events, onEnd reconciles via editor.moveTo().\nconst sortableInstances = ref<Array<{ stop: () => void; option?: (k: string, v: unknown) => void }>>([])\nconst forceRerender = ref(0)\n\nconst destroySortables = () => {\n for (const s of sortableInstances.value) {\n try {\n s.stop()\n } catch {\n // no-op\n }\n }\n sortableInstances.value = []\n}\n\nconst GROUP_CLASS = 'a-nested-list-editor__group'\nconst HANDLE_CLASS = 'a-le-drag-handle'\n\n// Live drag state. `instruction` is recomputed on every pointermove and encodes\n// where + at what depth the dragged item lands (sibling-above/below/make-child/blocked);\n// the overlay reads it, onEnd applies it via `editor.moveTo`. SortableJS never moves\n// the DOM (onMove always returns false) — hit-testing, depth and the mutation are all ours.\nconst dragState = ref<DragState | null>(null)\n\n// Drop indicator anchor geometry, kept in sync with CSS (reorder-mode header\n// padding-left = 12 + depth*24, drag handle = 20px). ANCHOR_X = 12 + half handle\n// (10) = 22 puts the dot on the handle's centre; the line steps 24px per depth.\nconst INDENT_PX = 24\nconst ANCHOR_X = 22\n\nconst hitTestRow = (\n clientX: number,\n clientY: number\n): { el: HTMLElement; viewItem: DecoratedNestedViewItem<TItem> } | null => {\n const hit = document.elementFromPoint(clientX, clientY) as HTMLElement | null\n if (!hit) return null\n const wrapper = hit.closest('.a-le-row-wrapper') as HTMLElement | null\n if (!wrapper) return null\n // Only wrappers inside our rowsContainer — elementFromPoint could hit a\n // different ANestedSortableListEditor instance on the same page.\n if (!rowsContainer.value || !rowsContainer.value.contains(wrapper)) return null\n const key = parseKey(wrapper.getAttribute('data-id'))\n if (key === null) return null\n const vi = viewItemsDecorated.value.find((v) => v.key === key)\n if (!vi) return null\n return { el: wrapper, viewItem: vi }\n}\n\nconst recomputeInstruction = (clientX: number, clientY: number) => {\n if (!dragState.value || !rowsContainer.value) return\n const hit = hitTestRow(clientX, clientY)\n if (!hit) {\n dragState.value.instruction = null\n return\n }\n const containerRect = rowsContainer.value.getBoundingClientRect()\n // Hit-test the `.a-le-row` child, not the wrapper — the wrapper's height\n // balloons when children are rendered.\n const rowEl = hit.el.querySelector(':scope > .a-le-row') as HTMLElement | null\n const rowRect = (rowEl ?? hit.el).getBoundingClientRect()\n dragState.value.instruction = computeInstruction({\n pointer: { x: clientX, y: clientY },\n hoveredRow: {\n key: hit.viewItem.key,\n rect: rowRect,\n depth: hit.viewItem.depth,\n parentKey: hit.viewItem.parentKey,\n siblingIndex: hit.viewItem.siblingIndex,\n siblingCount: hit.viewItem.siblingCount,\n },\n sourceKey: dragState.value.sourceKey,\n sourceSubtreeDepth: dragState.value.sourceSubtreeDepth,\n viewItems: viewItemsDecorated.value,\n maxDepth: props.maxDepth,\n indentPx: INDENT_PX,\n containerLeft: containerRect.left,\n containerPaddingLeft: ANCHOR_X,\n })\n}\n\nconst onPointerMove = (e: PointerEvent) => {\n recomputeInstruction(e.clientX, e.clientY)\n}\n\nconst applyInstruction = (inst: ExecutableInstruction, sourceKey: ListEditorKey) => {\n const ok = controller.moveTo(sourceKey, inst.parentKey, inst.index)\n if (!ok) {\n showWarningT('common.sortable.error.maxDeepExceed')\n forceRerender.value++\n nextTick(() => initSortables())\n return\n }\n markMoved(sourceKey)\n // makeChild: expand the new parent so the just-moved row doesn't vanish into a collapsed branch.\n if (inst.makeChild && inst.parentKey !== null) {\n childrenExpandedKeys.value.add(inst.parentKey)\n }\n}\n\nconst initSortables = () => {\n destroySortables()\n if (!dragEnabled.value) return\n if (!rowsContainer.value) return\n const groups = Array.from(rowsContainer.value.querySelectorAll<HTMLElement>('.' + GROUP_CLASS))\n for (const group of groups) {\n const sortable = useSortable(group, [], {\n group: { name: 'a-nested', pull: true, put: true },\n handle: '.' + HANDLE_CLASS,\n animation: 0,\n ghostClass: DRAG_GHOST_CLASS,\n chosenClass: DRAG_CHOSEN_CLASS,\n dragClass: DRAG_CLASS,\n fallbackOnBody: true,\n forceFallback: true,\n fallbackTolerance: 3,\n onStart: (event) => {\n const draggedEl = event.item as HTMLElement\n const id = parseKey(draggedEl.getAttribute('data-id'))\n if (id === null) return\n const draggedNode = controller.findNode(id).node\n const subtreeDepth = draggedNode ? controller.calculateSubtreeDepth(draggedNode) : 1\n dragState.value = {\n sourceKey: id,\n sourceSubtreeDepth: subtreeDepth,\n instruction: null,\n }\n document.addEventListener('pointermove', onPointerMove, { passive: true })\n },\n onMove: (event) => {\n // Also track the pointer via SortableJS's stream — keeps `instruction`\n // in sync when the browser coalesces pointermove events during fast drags.\n const orig = (event as unknown as { originalEvent?: Event }).originalEvent\n if (orig) {\n if ('clientX' in orig && 'clientY' in orig) {\n recomputeInstruction((orig as MouseEvent).clientX, (orig as MouseEvent).clientY)\n } else if ('touches' in orig) {\n const tev = orig as TouchEvent\n if (tev.touches.length > 0) {\n recomputeInstruction(tev.touches[0].clientX, tev.touches[0].clientY)\n }\n }\n }\n // Refuse SortableJS's own DOM insertion — onEnd's `editor.moveTo` drives the move.\n return false\n },\n onEnd: () => {\n document.removeEventListener('pointermove', onPointerMove)\n const state = dragState.value\n dragState.value = null\n if (!state) return\n const inst = state.instruction\n if (inst === null) return\n if (inst.type === 'blocked') return\n applyInstruction(inst, state.sourceKey)\n },\n })\n sortableInstances.value.push(sortable)\n }\n}\n\nconst parseKey = (raw: string | null): ListEditorKey | null => {\n if (raw === null || raw === '') return null\n // Numeric key only when `data-id` is a pure integer string (incl. negative temp\n // ids like \"-1\"); else a DocId/UUID. The old `n > 0` test sent negative temp ids\n // to the string branch, so a freshly-added row's move marked the wrong key.\n const n = stringToInt(raw)\n return String(n) === raw ? n : raw\n}\n\nwatch(\n () => dragEnabled.value,\n () => {\n if (dragEnabled.value) nextTick(() => initSortables())\n else destroySortables()\n }\n)\n\nwatch(\n () => forceRerender.value,\n () => {\n if (dragEnabled.value) nextTick(() => initSortables())\n }\n)\n\n// Rebuild sortables when the tree shape changes during drag mode, else newly rendered groups aren't draggable.\nwatch(\n () => viewItemsDecorated.value.map((v) => v.key).join('|'),\n () => {\n if (dragEnabled.value) nextTick(() => initSortables())\n }\n)\n\nonMounted(() => {\n if (dragEnabled.value) initSortables()\n})\nonBeforeUnmount(() => {\n destroySortables()\n document.removeEventListener('pointermove', onPointerMove)\n})\n\n// Overlay visual props from the current instruction; `null` = hidden (no\n// instruction, or blocked). The optional connector is a rail from the drop line\n// up to the row whose level dictates the insert. Measured via getBoundingClientRect\n// rather than CSS anchor positioning: Chrome's anchor-reachability rule excludes\n// some deeply-nested descendants, so JS measurement is simpler and more reliable.\ntype OverlayVisual = {\n line: { top: number; left: number; right: number }\n connector: { top: number; height: number; left: number } | null\n}\n\nconst overlayVisual = computed<OverlayVisual | null>(() => {\n const state = dragState.value\n if (!state || state.instruction === null || !rowsContainer.value) return null\n const inst = state.instruction\n // Blocked drops render nothing — the silent empty space is the \"not here\" signal.\n if (inst.type === 'blocked') return null\n const refWrapper = rowsContainer.value.querySelector<HTMLElement>(\n `.a-le-row-wrapper[data-id=\"${CSS.escape(String(inst.refKey))}\"]`\n )\n if (!refWrapper) return null\n const rowEl = refWrapper.querySelector<HTMLElement>(':scope > .a-le-row')\n const containerRect = rowsContainer.value.getBoundingClientRect()\n const rowRect = (rowEl ?? refWrapper).getBoundingClientRect()\n\n const lineLeft = ANCHOR_X + inst.depth * INDENT_PX\n const lineTop = inst.refEdge === 'top' ? rowRect.top - containerRect.top : rowRect.bottom - containerRect.top\n const line = { top: lineTop, left: lineLeft, right: 16 }\n\n let connector: OverlayVisual['connector'] = null\n if (inst.levelRowKey !== null) {\n const levelWrapper = rowsContainer.value.querySelector<HTMLElement>(\n `.a-le-row-wrapper[data-id=\"${CSS.escape(String(inst.levelRowKey))}\"]`\n )\n if (levelWrapper) {\n const levelRow = levelWrapper.querySelector<HTMLElement>(':scope > .a-le-row')\n const levelRect = (levelRow ?? levelWrapper).getBoundingClientRect()\n const levelCentreY = levelRect.top - containerRect.top + levelRect.height / 2\n if (levelCentreY < lineTop) {\n connector = {\n top: levelCentreY,\n height: lineTop - levelCentreY,\n left: lineLeft,\n }\n }\n }\n }\n\n return { line, connector }\n})\n\n// With a `factory`, the editor inserts through the controller; without one it\n// stays emit-only so legacy `@add` consumers drive the insert. The `add` event\n// fires either way.\nconst onAddClick = () => {\n if (!canAdd.value) return\n requestAutoOpen()\n if (props.factory) controller.addItem()\n emit('add', undefined)\n}\n\nconst onRowAddAfterClick = (vi: NestedViewItem<TItem>) => {\n if (!canInteract.value) return\n requestAutoOpen()\n if (props.factory) controller.addAfter(vi.key, undefined, vi.childrenAllowed)\n emit('add', { afterId: vi.key, childrenAllowed: vi.childrenAllowed })\n}\n\nconst onAddChildClick = (vi: NestedViewItem<TItem>) => {\n if (!canInteract.value) return\n if (!vi.canAddChild) return\n requestAutoOpen()\n childrenExpandedKeys.value.add(vi.key)\n emit('add-child', vi)\n // Append to the end of children, matching the root \"Add item\" semantic.\n // (Drag-drop make-child still lands at index 0 — its drop line sits just below the parent.)\n if (props.factory) controller.addChild(vi.key, undefined, true)\n emit('add', { parentId: vi.key, childrenAllowed: true })\n}\n\nconst onEditClick = (vi: NestedViewItem<TItem>) => {\n if (!canInteract.value || reorderMode.value) return\n // Clicking edit while already editing closes the form, so the pencil toggles like the row header.\n if (editingKeys.value.has(vi.key)) {\n onCloseClick(vi)\n return\n }\n if (isInlineEdit.value) {\n beginEdit(vi)\n }\n emit('edit', vi)\n}\n\n// Toggles tree children visibility (distinct from the readonly-detail body below).\nconst onChevronClick = (vi: NestedViewItem<TItem>) => {\n if (props.disabled || props.loading) return\n const key = vi.key\n const currently = childrenExpandedKeys.value.has(key)\n if (currently) childrenExpandedKeys.value.delete(key)\n else childrenExpandedKeys.value.add(key)\n emit('item-expand', vi, !currently)\n}\n\n// Toggles the row's readonly-detail body (separate from tree children).\nconst onDetailToggle = (vi: NestedViewItem<TItem>) => {\n if (props.disabled || props.loading) return\n const key = vi.key\n const currently = detailExpandedKeys.value.has(key)\n if (currently) detailExpandedKeys.value.delete(key)\n else detailExpandedKeys.value.add(key)\n emit('item-expand', vi, !currently)\n}\n\nconst isRowClickable = (vi: DecoratedNestedViewItem<TItem>): boolean => {\n if (props.disableRowClick) return false\n if (props.disabled || props.loading) return false\n if (reorderMode.value) return false\n if (vi.editing || vi.expanded) return true\n if (!props.readonly && props.showEditButton) return true\n return false\n}\n\nconst onRowClick = (vi: DecoratedNestedViewItem<TItem>) => {\n if (!isRowClickable(vi)) return\n if (vi.editing || vi.expanded) {\n onCloseClick(vi)\n return\n }\n if (props.readonly && hasReadonlyDetail.value) onDetailToggle(vi)\n else onEditClick(vi)\n}\n\nconst {\n deleteDialog,\n deleteInFlight,\n deleteError,\n onDeleteClick: triggerDeleteClick,\n onDeleteDialogConfirm,\n onDeleteDialogCancel,\n} = useDeleteDialog<TItem, NestedViewItem<TItem>>({\n onDeleteConfirm: (raw) => (props.onDeleteConfirm ? props.onDeleteConfirm(raw) : true),\n onDelete: (raw) => props.onDelete?.(raw),\n onDeleted: (vi) => {\n editingKeys.value.delete(vi.key)\n editingSnapshots.value.delete(vi.key)\n detailExpandedKeys.value.delete(vi.key)\n childrenExpandedKeys.value.delete(vi.key)\n const deferred = props.deleteMode === 'deferred'\n // A delete in reorder mode is a session change: deferred reverts on Cancel, immediate stays gone\n // (re-applied after Cancel's snapshot-restore).\n if (reorderMode.value) {\n if (deferred) sessionDeferredDeletes.value.add(vi.key)\n else sessionImmediateDeletes.value.add(vi.key)\n }\n // Controller owns removal (mirrors the flat editors): a deferred saved-row delete is tombstoned\n // (counts as unsaved); immediate skips it (already deleted on the backend). Runs before the\n // `deleted` emit so a consumer's handler still receives `vi.raw`.\n controller.deleteItem(vi.key, { trackDeleted: deferred })\n emit('deleted', vi)\n },\n disableDeleteConfirm: () => props.disableDeleteConfirm,\n})\n\nconst onDeleteClick = async (vi: NestedViewItem<TItem>) => {\n if (!canInteract.value) return\n await triggerDeleteClick(vi)\n}\n\nconst onSaveClick = async (vi: NestedViewItem<TItem>) => {\n if (props.onItemSave) await props.onItemSave(vi.raw)\n commitEdit(vi)\n emit('item-saved', vi)\n}\n\nconst onCancelClick = (vi: NestedViewItem<TItem>) => {\n cancelEdit(vi)\n}\n\nconst onCloseClick = (vi: NestedViewItem<TItem>) => {\n closeEdit(vi)\n emit('close', vi)\n}\n\nconst moveUp = (id: ListEditorKey) => {\n if (controller.moveUp(id)) markMoved(id)\n}\nconst moveDown = (id: ListEditorKey) => {\n if (controller.moveDown(id)) markMoved(id)\n}\nconst moveTop = (id: ListEditorKey) => {\n if (controller.moveTop(id)) markMoved(id)\n}\nconst moveBottom = (id: ListEditorKey) => {\n if (controller.moveBottom(id)) markMoved(id)\n}\nconst doIndent = (vi: NestedViewItem<TItem>) => {\n if (!controller.indent(vi.key)) {\n showWarningT('common.sortable.error.maxDeepExceed')\n return\n }\n markMoved(vi.key)\n emit('indent', vi)\n}\nconst doOutdent = (vi: NestedViewItem<TItem>) => {\n if (!controller.outdent(vi.key)) return\n markMoved(vi.key)\n emit('outdent', vi)\n}\n\nconst resolveCompactText = (raw: TItem): string => resolveCompactTextUtil(raw, { compactField: props.compactField })\n\n// Row validation from the controller's gated `rowState` — red rail shows only\n// once the row is unsaved or `validateAll()` ran.\n// `editing` → the controller reads amber (not red) while the row is being filled in. (QA 85050 b7)\nconst resolveValidation = (raw: TItem, key?: ListEditorKey): ListEditorValidationState =>\n key === undefined ? null : controller.rowState(raw, key, editingKeys.value.has(key))\n\n// Per-key actions cache for stable per-row identity (see AListEditor). Closures\n// capture the stable key and look up the current vi via findVi at call time.\ntype ActionsBundle = {\n edit: () => void\n save: () => Promise<void> | void\n cancel: () => void\n close: () => void\n delete: () => Promise<void>\n addAfter: () => void\n addChild: () => void\n toggleExpand: () => void\n toggleDetail: () => void\n moveUp: () => void\n moveDown: () => void\n moveTop: () => void\n moveBottom: () => void\n indent: () => void\n outdent: () => void\n update: (data: TItem) => void\n}\nconst actionsCache = new Map<ListEditorKey, ActionsBundle>()\nconst getActions = (key: ListEditorKey): ActionsBundle => {\n let actions = actionsCache.get(key)\n if (!actions) {\n actions = {\n edit: () => {\n const vi = findVi(key)\n if (vi) onEditClick(vi)\n },\n save: () => {\n const vi = findVi(key)\n if (vi) return onSaveClick(vi)\n },\n cancel: () => {\n const vi = findVi(key)\n if (vi) onCancelClick(vi)\n },\n close: () => {\n const vi = findVi(key)\n if (vi) onCloseClick(vi)\n },\n delete: async () => {\n const vi = findVi(key)\n if (vi) await onDeleteClick(vi)\n },\n addAfter: () => {\n const vi = findVi(key)\n if (vi) onRowAddAfterClick(vi)\n },\n addChild: () => {\n const vi = findVi(key)\n if (vi) onAddChildClick(vi)\n },\n toggleExpand: () => {\n const vi = findVi(key)\n if (vi) onChevronClick(vi)\n },\n toggleDetail: () => {\n const vi = findVi(key)\n if (vi) onDetailToggle(vi)\n },\n moveUp: () => moveUp(key),\n moveDown: () => moveDown(key),\n moveTop: () => moveTop(key),\n moveBottom: () => moveBottom(key),\n indent: () => {\n const vi = findVi(key)\n if (vi) doIndent(vi)\n },\n outdent: () => {\n const vi = findVi(key)\n if (vi) doOutdent(vi)\n },\n update: (data: TItem) => controller.updateItem(key, data),\n }\n actionsCache.set(key, actions)\n }\n return actions\n}\nwatch(\n () => viewItemsDecorated.value,\n (now) => {\n if (actionsCache.size === 0) return\n const liveKeys = new Set(now.map((v) => v.key))\n for (const key of actionsCache.keys()) {\n if (!liveKeys.has(key)) actionsCache.delete(key)\n }\n }\n)\n\nconst buildSlotProps = (vi: DecoratedNestedViewItem<TItem>) => ({\n item: { ...vi, validationState: resolveValidation(vi.raw as TItem, vi.key) },\n raw: vi.raw,\n index: vi.index,\n key: vi.key,\n depth: vi.depth,\n parent: vi.parent,\n parentKey: vi.parentKey,\n childrenCount: vi.childrenCount,\n hasChildren: vi.hasChildren,\n childrenAllowed: vi.childrenAllowed,\n firstInParent: vi.firstInParent,\n lastInParent: vi.lastInParent,\n readonly: props.readonly,\n disabled: props.disabled,\n expanded: vi.expanded,\n childrenExpanded: vi.childrenExpanded,\n editing: vi.editing,\n dirty: vi.dirty,\n unsaved: vi.unsaved,\n moved: vi.moved,\n reorderMode: reorderMode.value,\n canMoveUp: !vi.firstInParent,\n canMoveDown: !vi.lastInParent,\n canIndent: vi.canIndent,\n canOutdent: vi.canOutdent,\n canAddChild: vi.canAddChild,\n touch: isTouch.value,\n stateKeyPrefix: rowStateScope.prefixFor(vi.key),\n actions: getActions(vi.key),\n})\n\nconst toolbarSlotProps = computed(() => ({\n applying: applying.value,\n hasPendingChanges: nestedPendingChanges.value,\n movedCount: nestedPendingCount.value,\n error: applyError.value,\n actions: {\n apply: applyReorder,\n cancel: cancelReorderMode,\n },\n}))\n\nconst reorderToggleSlotProps = computed(() => ({\n mode: mode.value,\n disabled: !canEnterReorder.value,\n hasPendingChanges: hasPendingChanges.value,\n actions: {\n enterReorderMode,\n exitReorderMode: cancelReorderMode,\n },\n}))\n\nconst expandableKeySet = computed(() => new Set(expandableKeys.value))\n\nconst findVi = (key: ListEditorKey): DecoratedNestedViewItem<TItem> | undefined =>\n viewItemsDecorated.value.find((v) => v.key === key)\n\nconst keyboardNav = useKeyboardNav({\n viewItems: computed(() =>\n viewItemsDecorated.value.map((vi) => ({\n key: vi.key,\n expanded: vi.childrenExpanded,\n canExpand: expandableKeySet.value.has(vi.key),\n canIndent: true,\n canOutdent: true,\n }))\n ),\n variant: 'nested',\n isReorderMode: reorderMode,\n disabled: computed(() => !canInteract.value),\n isEditing: (key) => editingKeys.value.has(key),\n onToggleEdit: (key) => {\n const vi = findVi(key)\n if (vi) onEditClick(vi)\n },\n onCancelEdit: (key) => {\n const vi = findVi(key)\n if (vi) onCloseClick(vi)\n },\n onMoveUp: (key) => moveUp(key),\n onMoveDown: (key) => moveDown(key),\n onMoveTop: (key) => moveTop(key),\n onMoveBottom: (key) => moveBottom(key),\n onIndent: (key) => {\n const vi = findVi(key)\n if (vi) doIndent(vi)\n },\n onOutdent: (key) => {\n const vi = findVi(key)\n if (vi) doOutdent(vi)\n },\n onToggleChildren: (key) => {\n const vi = findVi(key)\n if (vi) onChevronClick(vi)\n },\n onCancelReorder: () => cancelReorderMode(),\n})\n\nconst moveToPositionDialogOpen = ref<boolean>(false)\nconst moveToPositionTarget = shallowRef<DecoratedNestedViewItem<TItem> | null>(null)\nconst moveToPositionContext = computed<{\n parentId: ListEditorKey | null\n total: number\n currentIndex: number\n} | null>(() => {\n const target = moveToPositionTarget.value\n if (!target) return null\n const found = controller.findNode(target.key)\n const siblings = found.parent?.children ?? modelValue.value.children\n const idx = siblings.findIndex((s) => keyOf(s.data) === target.key)\n return {\n parentId: found.parent ? keyOf(found.parent.data) : null,\n total: siblings.length,\n currentIndex: idx,\n }\n})\nconst moveToPositionLabel = computed<string>(() =>\n moveToPositionTarget.value ? resolveCompactText(moveToPositionTarget.value.raw) : ''\n)\nconst openMoveToPosition = (vi: DecoratedNestedViewItem<TItem>) => {\n if (!props.showMoveToPosition) return\n moveToPositionTarget.value = vi\n moveToPositionDialogOpen.value = true\n}\nconst onMoveToPositionConfirm = (newIndex: number) => {\n const ctx = moveToPositionContext.value\n const target = moveToPositionTarget.value\n moveToPositionTarget.value = null\n if (!ctx || !target) return\n if (newIndex === ctx.currentIndex) return\n if (controller.moveTo(target.key, ctx.parentId, newIndex)) {\n markMoved(target.key)\n }\n}\n\nconst changeParentDialogOpen = ref<boolean>(false)\nconst changeParentTarget = shallowRef<DecoratedNestedViewItem<TItem> | null>(null)\nconst openChangeParent = (vi: DecoratedNestedViewItem<TItem>) => {\n if (!props.showChangeParent) return\n changeParentTarget.value = vi\n changeParentDialogOpen.value = true\n}\nconst onChangeParentConfirm = (parentId: ListEditorKey | null, position: 'first' | 'last') => {\n const target = changeParentTarget.value\n changeParentTarget.value = null\n if (!target) return\n // Sibling count under the new parent so 'last' resolves to the right index ('first' = 0).\n let siblingCount = 0\n if (parentId === null) {\n siblingCount = modelValue.value.children.length\n } else {\n const found = controller.findNode(parentId)\n siblingCount = found.node?.children?.length ?? 0\n }\n const targetIndex = position === 'first' ? 0 : siblingCount\n if (controller.moveTo(target.key, parentId, targetIndex)) {\n markMoved(target.key)\n if (parentId !== null) childrenExpandedKeys.value.add(parentId)\n }\n}\n\n// Aggregated display flags + helpers passed into each <LeNestedRow>.\nconst rowContext = computed(() => ({\n reorderMode: reorderMode.value,\n readonly: props.readonly,\n canInteract: canInteract.value,\n dragEnabled: dragEnabled.value,\n showExpandToggle: props.showExpandToggle,\n showEditButton: props.showEditButton,\n showDeleteButton: props.showDeleteButton,\n showAddChildButton: props.showAddChildButton,\n showAddAfterAction: props.showAddAfterAction,\n showMoveToPosition: props.showMoveToPosition,\n showChangeParent: props.showChangeParent,\n showInlineSaveFooter: showInlineSaveFooter.value,\n statusField: props.statusField,\n effectiveCloseVariant: effectiveCloseVariant.value,\n keyboardNav,\n isRowClickable,\n resolveCompactText,\n resolveValidation: (raw: TItem, key?: ListEditorKey) => resolveValidation(raw, key),\n buildSlotProps,\n}))\n\n// Event callback bundle passed to each row.\nconst rowCallbacks = {\n onRowClick,\n onChevronClick,\n onEditClick,\n onDeleteClick,\n onCloseClick,\n onSaveClick,\n onCancelClick,\n onAddAfterClick: onRowAddAfterClick,\n onAddChildClick,\n moveUp: (id: ListEditorKey) => moveUp(id),\n moveDown: (id: ListEditorKey) => moveDown(id),\n moveTop: (id: ListEditorKey) => moveTop(id),\n moveBottom: (id: ListEditorKey) => moveBottom(id),\n indent: doIndent,\n outdent: doOutdent,\n openMoveToPosition,\n openChangeParent,\n}\n\nconst rootViewItems = computed(() => viewItemsDecorated.value.filter((v) => v.parentKey === null))\n\n// Imperative aliases mirroring the legacy ASortableNested signatures so existing\n// consumers keep working. They assume the caller persists server-side, so they\n// re-baseline via `controller.commit()` (rows don't render \"unsaved\" after the\n// call) and return the live model. New consumers should use the controller handle directly.\n\n/** @deprecated Use `addItem(data, { afterId, childrenAllowed })` on the handle. */\nconst addAfterId = (targetId: ListEditorKey | null, data: TItem, childrenAllowed: boolean) => {\n controller.addItem(data, { afterId: targetId ?? undefined, childrenAllowed })\n nextTick(() => controller.commit())\n return modelValue.value\n}\n/** @deprecated Use `addChild(parentKey, data, childrenAllowed)` on the handle. */\nconst addChildToId = (targetId: ListEditorKey, data: TItem, childrenAllowed: boolean) => {\n childrenExpandedKeys.value.add(targetId)\n // Legacy \"prepend as first child\" semantic (canonical `addChild` appends to the end).\n controller.addItem(data, { parentId: targetId, asFirstChild: true, childrenAllowed })\n nextTick(() => controller.commit())\n return modelValue.value\n}\n/** @deprecated Use `deleteItem(id)` on the handle. */\nconst removeById = (id: ListEditorKey) => {\n controller.deleteItem(id)\n editingKeys.value.delete(id)\n editingSnapshots.value.delete(id)\n detailExpandedKeys.value.delete(id)\n childrenExpandedKeys.value.delete(id)\n nextTick(() => controller.commit())\n}\n/** @deprecated Use `updateItem(id, data)` on the handle. */\nconst updateData = (\n id: ListEditorKey,\n data: TItem,\n _children: unknown = null,\n _position: unknown = null,\n _markUnsaved: unknown = null\n) => {\n controller.updateItem(id, data)\n nextTick(() => controller.commit())\n}\n\n// Total distinct unconfirmed changes (added/edited/moved/reparented rows + deferred deletions) — a\n// delete counts even though its row is gone. Drives the unsaved-section label + the exposed handle.\nconst unsavedCount = controller.unsavedCount\n\n// Re-baseline as saved, clear the moved set, close open edits. Legacy name kept.\nconst clearUnsavedState = () => {\n controller.commit()\n movedKeys.value = new Set()\n clearEditing()\n}\n\n// Registers this editor as a named unsaved-changes section when a label is passed.\nuseUnsavedSection(() =>\n props.unsavedSectionLabel && !props.disableUnsaved\n ? { label: props.unsavedSectionLabel, dirty: controller.hasUnsaved.value }\n : []\n)\n\n// Expose the controller handle plus legacy aliases and reorder/expand controls.\n// Entries after the spread override controller methods where the historic name or\n// return shape differs (e.g. `viewItems` is this component's expand-aware list).\n// Reveal for a blocked save: open invalid rows AND expand the offender's ancestor chain so a\n// collapsed nested row becomes visible. Shared by the exposed validateAll + scope reveal-on-touch.\nconst revealNestedInvalid = (): boolean =>\n validateAllAndReveal(controller, (key) => {\n let ancestor = controller.findNode(key).parent\n while (ancestor) {\n childrenExpandedKeys.value.add(keyOf(ancestor.data))\n ancestor = controller.findNode(keyOf(ancestor.data)).parent\n }\n const vi = controller.viewItems.value.find((v) => v.key === key)\n if (vi) beginEdit(vi)\n })\n\n// Auto-bridge the editor's aggregate validity into the consumer's `validation-scope` collector so a\n// plain `$invalid` save gate blocks (and reveals) a collapsed invalid row. No-op without the prop.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss -- construction-time opt-in, read once\nuseListEditorScopeValidity({\n hasErrors: controller.hasErrors,\n validationScope: props.validationScope,\n validateProvided: props.validate !== undefined,\n reveal: revealNestedInvalid,\n})\n\ndefineExpose<\n NestedListEditorHandle<TItem> & {\n addAfterId: typeof addAfterId\n addChildToId: typeof addChildToId\n removeById: typeof removeById\n updateData: typeof updateData\n resetDirtyBaseline: () => void\n hasUnsavedChanges: typeof controller.hasUnsaved\n unsavedCount: typeof unsavedCount\n clearUnsavedState: () => void\n enterReorderMode: () => void\n cancelReorderMode: () => void\n applyReorder: () => Promise<void>\n expand: (id: ListEditorKey) => void\n collapse: (id: ListEditorKey) => void\n toggleExpand: (id: ListEditorKey) => void\n expandDetail: (id: ListEditorKey) => void\n collapseDetail: (id: ListEditorKey) => void\n }\n>({\n ...controller,\n validateAll: revealNestedInvalid,\n // Legacy aliases (pre-v2 ASortableNested API).\n addAfterId,\n addChildToId,\n removeById,\n updateData,\n resetDirtyBaseline,\n hasUnsavedChanges: controller.hasUnsaved,\n unsavedCount,\n clearUnsavedState,\n // Expand-aware flattened view (controller's own `viewItems` ignores collapse state).\n viewItems: flatViewItems,\n enterReorderMode,\n cancelReorderMode,\n applyReorder,\n expand: (id: ListEditorKey) => childrenExpandedKeys.value.add(id),\n collapse: (id: ListEditorKey) => childrenExpandedKeys.value.delete(id),\n toggleExpand: (id: ListEditorKey) => {\n if (childrenExpandedKeys.value.has(id)) childrenExpandedKeys.value.delete(id)\n else childrenExpandedKeys.value.add(id)\n },\n expandDetail: (id: ListEditorKey) => detailExpandedKeys.value.add(id),\n collapseDetail: (id: ListEditorKey) => detailExpandedKeys.value.delete(id),\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"a-nested-list-editor\"\n :class=\"[\n `a-nested-list-editor--mode-${mode}`,\n {\n 'a-nested-list-editor--readonly': readonly,\n 'a-nested-list-editor--disabled': disabled,\n 'a-nested-list-editor--touch': isTouch,\n 'a-nested-list-editor--drag-enabled': dragEnabled,\n },\n ]\"\n >\n <div class=\"a-le-card\">\n <div\n v-if=\"headerVisible\"\n class=\"a-le-header\"\n >\n <slot\n name=\"header\"\n :title=\"title\"\n v-bind=\"reorderToggleSlotProps\"\n >\n <h3\n v-if=\"title\"\n class=\"a-le-title-heading\"\n >\n {{ title }}\n </h3>\n <div class=\"a-le-header-actions\">\n <template v-if=\"reorderMode\">\n <!-- Reorder-mode header: pending count + Cancel/Apply, sitting where\n the \"Reorder\" button lives in view mode (not a bottom toolbar). -->\n <slot\n name=\"reorder-toolbar\"\n v-bind=\"toolbarSlotProps\"\n >\n <LeStatus\n :class=\"{ 'a-le-toolbar-status--pending': nestedPendingChanges }\"\n :has-pending-changes=\"nestedPendingChanges\"\n :pending-count=\"nestedPendingCount\"\n :error=\"applyError\"\n />\n <VBtn\n variant=\"text\"\n size=\"small\"\n :disabled=\"applying\"\n @click=\"cancelReorderMode\"\n >\n {{ t('common.sortable.reorderCancel') }}\n </VBtn>\n <VBtn\n color=\"primary\"\n variant=\"flat\"\n size=\"small\"\n prepend-icon=\"mdi-check\"\n :loading=\"applying\"\n :disabled=\"applying || !nestedPendingChanges\"\n @click=\"applyReorder\"\n >\n {{ t('common.sortable.reorderApply') }}\n </VBtn>\n </slot>\n </template>\n <template v-else>\n <VBtn\n v-if=\"expandAllVisible && compactReorderButton\"\n variant=\"tonal\"\n color=\"primary\"\n icon\n size=\"x-small\"\n @click=\"toggleExpandAll\"\n >\n <VIcon\n :icon=\"allExpanded ? 'mdi-unfold-less-horizontal' : 'mdi-unfold-more-horizontal'\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"allExpanded ? t('common.sortable.collapseAll') : t('common.sortable.expandAll')\"\n />\n </VBtn>\n <VBtn\n v-else-if=\"expandAllVisible\"\n variant=\"tonal\"\n color=\"primary\"\n size=\"small\"\n :prepend-icon=\"allExpanded ? 'mdi-unfold-less-horizontal' : 'mdi-unfold-more-horizontal'\"\n @click=\"toggleExpandAll\"\n >\n {{ allExpanded ? t('common.sortable.collapseAll') : t('common.sortable.expandAll') }}\n </VBtn>\n <slot\n v-if=\"reorderToggleVisible\"\n name=\"reorder-toggle\"\n v-bind=\"reorderToggleSlotProps\"\n >\n <VBtn\n v-if=\"compactReorderButton\"\n variant=\"tonal\"\n color=\"primary\"\n icon\n size=\"x-small\"\n :disabled=\"!canEnterReorder\"\n @click=\"enterReorderMode\"\n >\n <VIcon\n icon=\"mdi-sort\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.reorder')\"\n />\n </VBtn>\n <VBtn\n v-else\n variant=\"tonal\"\n color=\"primary\"\n prepend-icon=\"mdi-sort\"\n size=\"small\"\n :disabled=\"!canEnterReorder\"\n @click=\"enterReorderMode\"\n >\n {{ t('common.sortable.reorder') }}\n </VBtn>\n </slot>\n </template>\n </div>\n </slot>\n </div>\n\n <div\n v-if=\"loading\"\n class=\"a-le-state a-le-state--loading\"\n >\n <VProgressCircular\n indeterminate\n color=\"primary\"\n size=\"32\"\n />\n </div>\n\n <div\n v-else-if=\"error\"\n class=\"a-le-state a-le-state--error\"\n >\n <VAlert\n type=\"error\"\n variant=\"tonal\"\n density=\"compact\"\n border=\"start\"\n >\n {{ error }}\n </VAlert>\n </div>\n\n <div\n v-else-if=\"isEmpty\"\n class=\"a-le-state a-le-state--empty\"\n >\n <slot\n name=\"empty\"\n :readonly=\"readonly\"\n :disabled=\"disabled\"\n :actions=\"{ add: onAddClick }\"\n >\n <LeEmptyState\n :title=\"emptyTitleResolved\"\n :add-label=\"addLabelResolved\"\n :can-add=\"canAdd\"\n @add=\"onAddClick\"\n />\n </slot>\n </div>\n\n <div\n v-else\n :key=\"forceRerender\"\n ref=\"rowsContainer\"\n class=\"a-nested-list-editor__rows\"\n :class=\"{ 'a-nested-list-editor__rows--dragging': dragState !== null }\"\n >\n <div\n :class=\"[GROUP_CLASS, 'a-nested-list-editor__group--root']\"\n data-parent-id=\"\"\n >\n <LeNestedRow\n v-for=\"vi in rootViewItems\"\n :key=\"String(vi.key)\"\n :vi=\"vi\"\n :view-items=\"viewItemsDecorated\"\n :drag-state=\"dragState\"\n :context=\"rowContext\"\n :callbacks=\"rowCallbacks\"\n >\n <template #item=\"slotScope\">\n <slot\n name=\"item\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-compact=\"slotScope\">\n <slot\n name=\"item-compact\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-readonly=\"slotScope\">\n <slot\n name=\"item-readonly\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-status=\"slotScope\">\n <slot\n name=\"item-status\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-footer=\"slotScope\">\n <slot\n name=\"item-footer\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-actions=\"slotScope\">\n <slot\n name=\"item-actions\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #before-item=\"slotScope\">\n <slot\n name=\"before-item\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #after-item=\"slotScope\">\n <slot\n name=\"after-item\"\n v-bind=\"slotScope\"\n />\n </template>\n </LeNestedRow>\n </div>\n\n <!-- Drop indicator overlay (valid drops only): horizontal line at a row\n edge (depth in `left`) + an optional vertical connector rail to the\n row whose level the insert matches. -->\n <template v-if=\"overlayVisual !== null\">\n <div\n v-if=\"overlayVisual.connector !== null\"\n class=\"a-nested-list-editor__drop-connector\"\n :style=\"{\n left: `${overlayVisual.connector.left}px`,\n top: `${overlayVisual.connector.top}px`,\n height: `${overlayVisual.connector.height}px`,\n }\"\n />\n <div\n class=\"a-nested-list-editor__drop-line\"\n :style=\"{\n top: `${overlayVisual.line.top}px`,\n left: `${overlayVisual.line.left}px`,\n right: `${overlayVisual.line.right}px`,\n }\"\n >\n <span class=\"a-nested-list-editor__drop-line-dot\" />\n </div>\n </template>\n </div>\n\n <slot\n v-if=\"canAdd && !loading && !error && !isEmpty\"\n name=\"add-button\"\n :readonly=\"readonly\"\n :disabled=\"disabled\"\n :props=\"{ onClick: onAddClick }\"\n :actions=\"{ add: onAddClick }\"\n >\n <button\n type=\"button\"\n class=\"a-le-row-add\"\n @click=\"onAddClick\"\n >\n <VIcon\n icon=\"mdi-plus\"\n size=\"18\"\n />\n <span>{{ addLabelResolved }}</span>\n </button>\n </slot>\n </div>\n\n <LeMoveToPositionDialog\n v-model=\"moveToPositionDialogOpen\"\n :total=\"moveToPositionContext?.total ?? 0\"\n :current-index=\"moveToPositionContext?.currentIndex ?? 0\"\n :item-label=\"moveToPositionLabel\"\n @confirm=\"onMoveToPositionConfirm\"\n />\n\n <LeChangeParentDialog\n v-model=\"changeParentDialogOpen\"\n :tree=\"modelValue\"\n :source-key=\"changeParentTarget?.key ?? null\"\n :key-field=\"keyFieldName\"\n :max-depth=\"maxDepth\"\n :resolve-label=\"resolveCompactText\"\n :calculate-subtree-depth=\"controller.calculateSubtreeDepth\"\n @confirm=\"onChangeParentConfirm\"\n />\n\n <LeDeleteDialog\n v-model=\"deleteDialog\"\n :title=\"deleteConfirmTitleResolved\"\n :text=\"deleteConfirmTextResolved\"\n :confirm-label=\"t('common.sortable.delete')\"\n :cancel-label=\"t('common.button.cancel')\"\n :error=\"deleteError\"\n :in-flight=\"deleteInFlight\"\n @confirm=\"onDeleteDialogConfirm\"\n @cancel=\"onDeleteDialogCancel\"\n />\n\n <div\n class=\"a-le-sr-only\"\n aria-live=\"polite\"\n role=\"status\"\n >\n {{\n keyboardNav.grabbedKey.value !== null\n ? t('common.sortable.keyboardGrab.status', {\n n: keyboardNav.grabbedIndex.value + 1,\n total: keyboardNav.totalCount.value,\n })\n : ''\n }}\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n@use './styles/tokens';\n@use './styles/shared';\n\n// Nested-variant rules — depth-aware padding, tree toggle caret, children\n// groups, drop indicator overlay, drag clone.\n.a-nested-list-editor {\n // Depth-aware left padding — caret column + indent per depth level.\n .a-le-row-header {\n padding: var(--le-row-pad-y) 12px var(--le-row-pad-y) calc(16px + var(--nested-depth, 0) * var(--le-indent));\n }\n\n // Row wrappers + inter-row group layout.\n &__rows {\n position: relative;\n display: flex;\n flex-direction: column;\n }\n\n &__group {\n display: flex;\n flex-direction: column;\n }\n\n // Re-apply the `font` shorthand (shared class uses longhand for flat variants)\n // so the nested variant keeps its explicit line-height reset.\n .a-le-toolbar-status {\n font: 500 13px/1 var(--v-font-body, inherit);\n }\n}\n\n.a-le-row-wrapper {\n display: flex;\n flex-direction: column;\n}\n\n.a-nested-list-editor {\n // Root rows get a bolder title weight to anchor the hierarchy without header rows.\n .a-le-row:not(.a-le-row--child) .a-le-title {\n font-weight: 600;\n }\n\n // Reorder mode — flat-variant padding offset by the row's indent level.\n .a-le-row--reorder .a-le-row-header {\n padding-left: calc(12px + var(--nested-depth, 0) * var(--le-indent));\n padding-right: 8px;\n gap: 8px;\n }\n\n // Active (editing / readonly-expanded) tree-toggle picks up the primary tint\n // so the header reads \"active\" together with the title color.\n .a-le-row--editing &__tree-toggle,\n .a-le-row--expanded &__tree-toggle {\n color: var(--le-primary);\n }\n\n // Children wrapper — depth conveyed by the row's padding-left indent alone.\n &__children {\n position: relative;\n }\n\n // Triangle-caret toggle — circular text-style button (24×24), transparent\n // with a subtle tinted circle on hover.\n &__tree-toggle {\n width: 24px;\n height: 24px;\n padding: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n border-radius: 50%;\n cursor: pointer;\n pointer-events: auto;\n color: rgb(0 0 0 / 54%);\n z-index: 2;\n flex-shrink: 0;\n transition:\n background 0.15s,\n color 0.15s;\n\n &:hover {\n background: rgb(0 0 0 / 5%);\n color: var(--le-on-surface);\n }\n\n &:focus-visible {\n outline: none;\n background: rgb(0 0 0 / 8%);\n }\n }\n\n // Reserve caret column width on leaf rows so titles align across siblings.\n &__tree-toggle--spacer,\n &__tree-toggle--empty {\n width: 24px;\n height: 24px;\n background: transparent;\n visibility: hidden;\n pointer-events: none;\n }\n\n // Pure CSS right-pointing triangle; rotates to down when the row is open.\n &__tree-toggle-caret {\n width: 0;\n height: 0;\n border-left: 5px solid currentcolor;\n border-top: 4px solid transparent;\n border-bottom: 4px solid transparent;\n border-right: 0;\n transition: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1);\n transform: translateX(1px); // optical centering — triangle leans left\n }\n\n &__tree-toggle--open &__tree-toggle-caret {\n transform: translate(0, 1px) rotate(90deg);\n }\n\n // Dragged subtree (source + descendants) dimmed and non-hittable. `!important`\n // on display beats SortableJS's inline `display: none` under `forceFallback`.\n .a-le-row-wrapper.a-le-row--chosen,\n .a-le-row-wrapper.a-le-row-wrapper--drop-disabled {\n display: flex !important;\n opacity: 0.4 !important;\n pointer-events: none !important;\n }\n\n // Hide SortableJS's placeholder — our overlay is the sole landing indicator.\n .a-le-row--ghost {\n display: none !important;\n }\n\n // No floating clone — the line overlay carries all the \"where it lands\" info.\n // Wrapper selector beats the `.row-wrapper--drop-disabled` rule the clone inherits.\n .a-le-row-wrapper.a-le-row--drag {\n display: none !important;\n }\n\n // Drop indicator line — 2px primary stroke with an 8px left terminal dot,\n // absolute-positioned inside `__rows`.\n &__drop-line {\n position: absolute;\n height: 2px;\n margin-top: -1px;\n background: var(--le-primary);\n pointer-events: none;\n z-index: 4;\n border-radius: 1px;\n }\n\n &__drop-line-dot {\n position: absolute;\n left: -4px;\n top: 50%;\n width: 8px;\n height: 8px;\n background: var(--le-primary);\n border-radius: 50%;\n transform: translateY(-50%);\n }\n\n // Connector rail — vertical line linking the drop line up to the level-match row.\n &__drop-connector {\n position: absolute;\n width: 2px;\n margin-left: -1px;\n background: var(--le-primary);\n opacity: 0.5;\n pointer-events: none;\n z-index: 3;\n }\n\n // While dragging, dim the add-button so focus stays on the drag target.\n &__rows--dragging .a-le-row-add {\n opacity: 0.4;\n }\n\n // \"+N\" children indicator — hidden in the normal DOM, visible only inside the\n // SortableJS drag clone so the user sees the whole branch will follow.\n &__drag-count {\n display: none;\n align-items: center;\n gap: 4px;\n margin-left: 8px;\n padding: 2px 8px;\n font: 500 11px/1 var(--v-font-body, inherit);\n letter-spacing: 0.02em;\n color: var(--le-primary);\n background: var(--le-primary-container);\n border-radius: var(--le-radius-pill);\n flex-shrink: 0;\n }\n\n .a-le-row--drag &__drag-count {\n display: inline-flex;\n }\n}\n\n// Desktop — depth-aware padding-left aligns the inline form with the title column:\n// 16 (pad) + 24 (caret) + 10 (gap) = 50, plus depth*indent.\n@container le-shell (min-width: 769px) {\n .a-nested-list-editor {\n .a-le-row--editing .a-le-row-body,\n .a-le-row--expanded .a-le-row-body,\n .a-le-row--editing .a-le-row-footer,\n .a-le-row--expanded .a-le-row-footer {\n padding-left: calc(50px + var(--nested-depth, 0) * var(--le-indent));\n padding-right: 16px;\n }\n }\n}\n\n// Narrow / mobile — tighter indent so deep branches fit, taller touch rows,\n// always-visible actions, status badge dropped to make room for the title.\n/* stylelint-disable selector-max-compound-selectors */\n@container le-shell (max-width: 768px) {\n .a-nested-list-editor {\n --le-indent: 18px;\n --le-row-min-height: 48px;\n --le-row-pad-y: 10px;\n\n .a-le-row:not(.a-le-row--editing) .a-le-status {\n display: none;\n }\n\n .a-le-row .a-le-action--edit,\n .a-le-row .a-le-action--delete,\n .a-le-row .a-le-action--menu,\n .a-le-row .a-le-action--up,\n .a-le-row .a-le-action--down,\n .a-le-row .a-le-action--add-child {\n opacity: 1;\n }\n }\n}\n/* stylelint-enable selector-max-compound-selectors */\n</style>\n","<script setup lang=\"ts\" generic=\"TItem extends Record<string, any>\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, useSlots, useTemplateRef, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport { useContainerWidth } from '@/labs/listEditor/composables/useContainerWidth'\nimport { useIsTouchDevice } from '@/labs/listEditor/composables/useIsTouchDevice'\nimport { useKeyboardNav } from '@/labs/listEditor/composables/useKeyboardNav'\nimport { useSortable } from '@vueuse/integrations/useSortable'\nimport { type NestedViewItem } from '@/labs/listEditor/composables/useNestedListEditor'\nimport {\n useNestedListEditorController,\n type GetKey,\n type ListEditorValidationResult,\n type NestedListEditorHandle,\n type PositionOption,\n type PositionStrategy,\n} from '@/labs/listEditor/composables/useNestedListEditorController'\nimport {\n computeInstruction,\n type ExecutableInstruction,\n type Instruction,\n} from '@/labs/listEditor/composables/useDragInstruction'\nimport {\n provideListEditorStateScope,\n useNestedListEditorStateEntry,\n type NestedListEditorStateBindings,\n} from '@/labs/listEditor/composables/useListEditorStateScope'\nimport { resolveCompactText as resolveCompactTextUtil } from '@/labs/listEditor/composables/resolveCompactText'\nimport { useUnsavedSection } from '@/labs/unsavedGuard/useUnsavedSection'\nimport { useDeleteDialog } from '@/labs/listEditor/composables/useDeleteDialog'\nimport { useInlineEditing } from '@/labs/listEditor/composables/useInlineEditing'\nimport { validateAllAndReveal } from '@/labs/listEditor/utils/revealInvalidRows'\nimport { useListEditorScopeValidity } from '@/labs/listEditor/composables/useListEditorScopeValidity'\nimport { useReorderMode } from '@/labs/listEditor/composables/useReorderMode'\nimport { cloneDeep } from '@/utils/common'\nimport type {\n ListEditorKey,\n ListEditorValidationScope,\n ListEditorValidationState,\n NestedPositionHint,\n NestedTree,\n NestedTreeNode,\n} from '@/labs/listEditor/types/listEditorTypes'\nimport { useAlerts } from '@/composables/system/alerts'\nimport { stringToInt } from '@/utils/string'\nimport LeNestedRow from '@/labs/listEditor/internal/LeNestedRow.vue'\nimport LeDeleteDialog from '@/labs/listEditor/internal/LeDeleteDialog.vue'\nimport LeMoveToPositionDialog from '@/labs/listEditor/internal/LeMoveToPositionDialog.vue'\nimport LeChangeParentDialog from '@/labs/listEditor/internal/LeChangeParentDialog.vue'\nimport LeEmptyState from '@/labs/listEditor/internal/LeEmptyState.vue'\nimport LeStatus from '@/labs/listEditor/internal/LeStatus.vue'\nimport { DRAG_GHOST_CLASS, DRAG_CHOSEN_CLASS, DRAG_CLASS } from '@/labs/listEditor/internal/constants'\n\nexport interface DecoratedNestedViewItem<T> extends NestedViewItem<T> {\n editing: boolean\n expanded: boolean\n childrenExpanded: boolean\n loading: boolean\n dirty: boolean\n moved: boolean\n unsaved: boolean\n}\n\nexport interface DragState {\n sourceKey: ListEditorKey\n sourceSubtreeDepth: number\n instruction: Instruction | null\n}\n\nexport type ReorderMode = 'view' | 'reorder'\n\n// Hoisted for vite-plugin-dts d.ts rollup.\nexport interface RowActions<TItem> {\n edit: () => void\n save: () => Promise<void> | void\n cancel: () => void\n close: () => void\n delete: () => Promise<void>\n addAfter: () => void\n addChild: () => void\n toggleExpand: () => void\n toggleDetail: () => void\n moveUp: () => void\n moveDown: () => void\n moveTop: () => void\n moveBottom: () => void\n indent: () => void\n outdent: () => void\n update: (data: TItem) => void\n}\nexport interface RowSlotProps<TItem extends Record<string, any>> {\n item: DecoratedNestedViewItem<TItem> & { validationState: ListEditorValidationState }\n raw: TItem\n index: number\n key: ListEditorKey\n depth: number\n parent: TItem | null\n parentKey: ListEditorKey | null\n childrenCount: number\n hasChildren: boolean\n childrenAllowed: boolean\n firstInParent: boolean\n lastInParent: boolean\n readonly: boolean\n disabled: boolean\n expanded: boolean\n childrenExpanded: boolean\n editing: boolean\n dirty: boolean\n unsaved: boolean\n moved: boolean\n reorderMode: boolean\n canMoveUp: boolean\n canMoveDown: boolean\n canIndent: boolean\n canOutdent: boolean\n canAddChild: boolean\n touch: boolean\n /**\n * Row-scoped `state-key` prefix. Thread it down to any list editor rendered in this slot and set\n * its `:state-key=\"`${stateKeyPrefix}:someList`\"` — that editor's controller then lives in THIS\n * editor's scope and survives the row collapsing (which unmounts the whole slot subtree). Entries\n * under this prefix are disposed when the row is removed.\n */\n stateKeyPrefix: string\n actions: RowActions<TItem>\n}\nexport interface ToolbarSlotProps {\n applying: boolean\n hasPendingChanges: boolean\n movedCount: number\n error: string | null\n actions: { apply: () => Promise<void>; cancel: () => void }\n}\nexport interface ReorderToggleSlotProps {\n mode: ReorderMode\n disabled: boolean\n hasPendingChanges: boolean\n actions: { enterReorderMode: () => void; exitReorderMode: () => void }\n}\nexport interface HeaderSlotProps extends ReorderToggleSlotProps {\n title: string | null\n}\nexport interface EmptySlotProps {\n readonly: boolean\n disabled: boolean\n actions: { add: () => void }\n}\nexport interface AddButtonSlotProps {\n readonly: boolean\n disabled: boolean\n props: { onClick: () => void }\n actions: { add: () => void }\n}\n\nexport interface Props<TItem extends Record<string, any>> {\n maxDepth: number\n\n /**\n * New-row factory; if set, add/add-after/add-inside insert through the\n * controller (no `@add` handler needed). Optional for read-only trees.\n */\n factory?: () => TItem\n /**\n * Stable row identity. Default `'id'`. Never point at the position field.\n * Typed `string` (not `keyof TItem`): a bare keyof compiles to a Boolean-only\n * runtime type that silently coerces `get-key=\"id\"` to `true`.\n */\n getKey?: string | ((item: TItem) => ListEditorKey)\n /**\n * Managed order field. Default `'position'`; `false` opts out. Typed\n * `[String, Boolean, Object]` with `string` before `false`: a bare keyof\n * compiles Boolean-only, and `false`-first would coerce `position=\"position\"`\n * (value == prop name) to `true`. Object form folds in `{ field, multiplier }`.\n */\n position?: string | false | { field: string; multiplier?: number; strategy?: PositionStrategy }\n /** Parent-key field written onto reparented rows. Default `'parent'`. */\n parentField?: string\n /**\n * Extra fields dropped from the dirty content-hash (position + parent always\n * dropped), e.g. a child collection tracked by a separate editor.\n */\n dirtyExclude?: string[]\n /** Per-row validity — `true` (or `{ valid: true }`) = VALID. Drives the red rail + save guard. */\n validate?: (item: TItem) => ListEditorValidationResult\n /**\n * Vuelidate `$scope` (the same one the consumer's row forms / save-gate collector use). When set,\n * the editor registers its aggregate validity under it, so a plain `v$.$invalid` save gate blocks\n * AND reveals a collapsed invalid row — no `validateAll()` call needed in the save flow. Omit to\n * keep legacy behavior (gate the save on the exposed `validateAll()` yourself); `false` opts out.\n */\n validationScope?: ListEditorValidationScope | false\n /**\n * Opt-in lifted controller (`useNestedListEditorController()`) so state\n * survives unmount/remount; omitted = internal controller. Same handle\n * reachable via `useTemplateRef`.\n */\n editor?: NestedListEditorHandle<TItem>\n /**\n * Persist this editor's controller in the nearest ANCESTOR editor's row-state scope under this\n * key, instead of owning it. Use it for an editor rendered in another editor's `#item` slot (that\n * slot is behind a `v-if`, so collapsing the row unmounts this editor and a component-owned\n * controller would re-baseline the already-edited tree on re-expand). Derive the key from the row\n * slot's `stateKeyPrefix`. Options stay declared here, on this tag. Ignored when `:editor` is\n * passed, and a no-op without an ancestor scope.\n */\n stateKey?: string\n\n readonly?: boolean\n disabled?: boolean\n loading?: boolean\n error?: string | null\n\n /** Heading above the list. A ready translated string, like every text prop here. */\n title?: string | null\n\n /**\n * Field shown in a collapsed row when no `#item-compact` slot is given. A dotted path reads\n * through nested objects (`texts.title`); a path that runs into nothing renders empty.\n */\n compactField?: string | null\n statusField?: string | null\n\n showAddButton?: boolean\n showAddChildButton?: boolean\n showDeleteButton?: boolean\n showEditButton?: boolean\n showAddAfterAction?: boolean\n showMoveToPosition?: boolean\n showChangeParent?: boolean\n showExpandToggle?: boolean\n\n /** Label of the add button. A ready translated string; omit it for the library's own wording. */\n addLabel?: string | null\n /** Heading of the empty state. A ready translated string. */\n emptyTitle?: string | null\n\n disableRowClick?: boolean\n disableDeleteConfirm?: boolean\n /**\n * Disable unsaved-state tracking — no dirty markers, never reads as unsaved. For a list that is a\n * VIEW of data owned elsewhere (a display cache rebuilt from a parent's field, say): nothing in\n * such a component can ever clear an amber row, so it must not raise one. Mirrors `AListEditor`.\n */\n disableUnsaved?: boolean\n deleteConfirmTitle?: string | null\n deleteConfirmText?: string | null\n /**\n * How a row delete persists. `deferred` (default): the row disappears but the deletion counts as an\n * unconfirmed change until save (revertible until then, incl. via reorder Cancel). `immediate`: the\n * consumer deletes on the backend — the dialog says it is irreversible and it does NOT read as unsaved.\n */\n deleteMode?: 'immediate' | 'deferred'\n\n closeVariant?: 'auto' | 'icon' | 'labeled'\n\n loadingKeys?: Set<ListEditorKey> | null\n\n showReorderToggle?: boolean\n disableReorder?: boolean\n disableDrag?: boolean\n\n onDeleteConfirm?: (item: TItem) => Promise<boolean> | boolean\n onDelete?: (item: TItem) => Promise<void> | void\n onItemSave?: (item: TItem) => Promise<void> | void\n onReorderApply?: (tree: NestedTree<TItem>) => Promise<void> | void\n\n /** Registers this editor as a named unsaved-changes section under the given (translated) label. */\n unsavedSectionLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props<TItem>>(), {\n unsavedSectionLabel: undefined,\n factory: undefined,\n getKey: undefined,\n position: undefined,\n parentField: 'parent',\n dirtyExclude: undefined,\n validate: undefined,\n validationScope: undefined,\n editor: undefined,\n stateKey: undefined,\n readonly: false,\n disabled: false,\n loading: false,\n error: null,\n title: null,\n compactField: null,\n statusField: null,\n showAddButton: true,\n showAddChildButton: true,\n showDeleteButton: true,\n showEditButton: true,\n showAddAfterAction: false,\n showMoveToPosition: false,\n showChangeParent: false,\n showExpandToggle: true,\n addLabel: null,\n emptyTitle: null,\n disableRowClick: false,\n disableDeleteConfirm: false,\n disableUnsaved: false,\n deleteConfirmTitle: null,\n deleteConfirmText: null,\n deleteMode: 'deferred',\n closeVariant: 'auto',\n loadingKeys: null,\n showReorderToggle: true,\n disableReorder: false,\n disableDrag: false,\n onDeleteConfirm: undefined,\n onDelete: undefined,\n onItemSave: undefined,\n onReorderApply: undefined,\n})\n\nconst emit = defineEmits<{\n add: [hint: NestedPositionHint | undefined]\n 'add-child': [parent: NestedViewItem<TItem>]\n edit: [item: NestedViewItem<TItem>]\n deleted: [item: NestedViewItem<TItem>]\n close: [item: NestedViewItem<TItem>]\n 'item-saved': [item: NestedViewItem<TItem>]\n 'item-expand': [item: NestedViewItem<TItem>, expanded: boolean]\n 'reorder-start': []\n 'reorder-cancel': []\n 'reorder-applied': [tree: NestedTree<TItem>]\n 'reorder-apply-error': [error: unknown]\n 'reorder-end': []\n indent: [item: NestedViewItem<TItem>]\n outdent: [item: NestedViewItem<TItem>]\n}>()\n\nconst modelValue = defineModel<NestedTree<TItem>>({ required: true })\nconst mode = defineModel<ReorderMode>('mode', { default: 'view' })\n\ndefineSlots<{\n header?: (props: HeaderSlotProps) => unknown\n 'reorder-toggle'?: (props: ReorderToggleSlotProps) => unknown\n 'reorder-toolbar'?: (props: ToolbarSlotProps) => unknown\n empty?: (props: EmptySlotProps) => unknown\n 'add-button'?: (props: AddButtonSlotProps) => unknown\n item?: (props: RowSlotProps<TItem>) => unknown\n 'item-compact'?: (props: RowSlotProps<TItem>) => unknown\n 'item-readonly'?: (props: RowSlotProps<TItem>) => unknown\n 'item-status'?: (props: RowSlotProps<TItem>) => unknown\n 'item-footer'?: (props: RowSlotProps<TItem>) => unknown\n 'item-actions'?: (props: RowSlotProps<TItem>) => unknown\n 'before-item'?: (props: RowSlotProps<TItem>) => unknown\n 'after-item'?: (props: RowSlotProps<TItem>) => unknown\n}>()\n\nconst { t } = useI18n()\nconst slots = useSlots()\nconst { showWarningT } = useAlerts()\n\nconst rootEl = useTemplateRef<HTMLElement>('rootEl')\nconst { isNarrow } = useContainerWidth(rootEl)\n\nconst isTouch = useIsTouchDevice()\n\nconst effectiveCloseVariant = computed<'icon' | 'labeled'>(() => {\n if (props.closeVariant === 'icon') return 'icon'\n if (props.closeVariant === 'labeled') return 'labeled'\n return isNarrow.value ? 'icon' : 'labeled'\n})\n\nconst childrenExpandedKeys = ref<Set<ListEditorKey>>(new Set())\n// Readonly detail body visibility — independent of subtree expansion.\nconst detailExpandedKeys = ref<Set<ListEditorKey>>(new Set())\n\n// Local mirror of the controller's key resolution so walkers + the template key\n// rows the same way the controller tracks them. Construction-time config, read once.\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst getKeyOpt = props.getKey ?? 'id'\nconst keyFieldName = (typeof getKeyOpt === 'function' ? 'id' : getKeyOpt) as string\nconst keyOf = (data: TItem): ListEditorKey =>\n typeof getKeyOpt === 'function' ? getKeyOpt(data) : (data[getKeyOpt as keyof TItem] as ListEditorKey)\n\n// Managed position field (row position, move-to-position dialog, flatViewItems). Mirrors the controller.\nconst positionFieldName = ((): string => {\n const p = props.position\n if (p === undefined || p === false) return 'position'\n if (typeof p === 'object') return p.field\n return p\n})()\n\nconst initChildrenExpanded = (tree: NestedTree<TItem>) => {\n const walk = (nodes: NestedTreeNode<TItem>[]) => {\n for (const n of nodes) {\n if (n.children && n.children.length > 0) {\n childrenExpandedKeys.value.add(keyOf(n.data))\n walk(n.children)\n }\n }\n }\n walk(tree.children)\n}\n// eslint-disable-next-line vue/no-ref-object-reactivity-loss\ninitChildrenExpanded(modelValue.value)\n\n// State controller (v2). Undefined getKey/position/validate fall back to controller defaults\n// ('id' / 'position' / always-valid). Its `viewItems` ignores expand state, so we re-project an\n// expand-aware list below (flatViewItems).\nconst controllerOptions: NestedListEditorStateBindings<TItem> = {\n get: () => modelValue.value,\n set: (v) => (modelValue.value = v),\n factory: props.factory,\n getKey: props.getKey as GetKey<TItem> | undefined,\n position: props.position as PositionOption<TItem> | undefined,\n parentField: props.parentField,\n maxDepth: props.maxDepth,\n dirtyExclude: () => props.dirtyExclude ?? [],\n validate: props.validate,\n}\n// Precedence: an explicitly lifted `:editor` wins; then a `state-key`'d entry persisted in the\n// nearest ancestor editor's row-state scope (rebound to this instance on every remount); else this\n// component owns the controller — today's behaviour, unchanged.\nconst stateEntry = props.editor ? null : useNestedListEditorStateEntry<TItem>(props.stateKey, controllerOptions)\nconst controller = props.editor ?? stateEntry?.handle ?? useNestedListEditorController<TItem>(controllerOptions)\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\n// Row-state scope for THIS editor's `#item` slot subtree — see ASortableListEditor.\nconst rowStateScope = provideListEditorStateScope(stateEntry?.childScope ?? null)\n\n// FULL flattened list (collapsed-branch rows included) built directly off the\n// tree: the recursive renderer (LeNestedRow) filters by parentKey + childrenExpandedKeys,\n// so it needs every row present in viewItemsDecorated even when hidden.\nconst flatViewItems = computed<NestedViewItem<TItem>[]>(() => {\n const flat: NestedViewItem<TItem>[] = []\n let flatIndex = 0\n const maxDepth = props.maxDepth\n const walk = (nodes: NestedTreeNode<TItem>[], depth: number, parentNode: NestedTreeNode<TItem> | null) => {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]\n const key = keyOf(node.data)\n const childrenAllowed = node.children !== undefined\n const hasChildren = childrenAllowed && (node.children?.length ?? 0) > 0\n const childrenCount = node.children?.length ?? 0\n const remainingDepth = maxDepth - (depth + 1)\n flat.push({\n key,\n index: flatIndex++,\n raw: node.data,\n position: node.data[positionFieldName] as number | undefined,\n node,\n depth,\n parent: parentNode?.data ?? null,\n parentKey: parentNode ? keyOf(parentNode.data) : null,\n childrenCount,\n hasChildren,\n childrenAllowed,\n siblingIndex: i,\n siblingCount: nodes.length,\n firstInParent: i === 0,\n lastInParent: i === nodes.length - 1,\n canAddChild: childrenAllowed && remainingDepth > 0,\n canIndent: i > 0,\n canOutdent: depth > 0,\n })\n if (hasChildren) walk(node.children as NestedTreeNode<TItem>[], depth + 1, node)\n }\n }\n walk(modelValue.value.children, 0, null)\n return flat\n})\n\n// Drop the persisted controllers of rows that no longer exist. `post` so the removed row's subtree\n// has already unmounted (its editors released their entries) before we stop their effect scopes.\nwatch(\n () => flatViewItems.value.map((vi) => String(vi.key)).join('|'),\n () => rowStateScope.retainOwners(flatViewItems.value.map((vi) => vi.key)),\n { flush: 'post' }\n)\n\n// Snapshot for reorder-cancel restore only; dirty detection lives in the controller.\n// `movedKeys` is component view state driving the toolbar \"N pending\" counter,\n// cleared on every mode transition by `useReorderMode`.\nconst snapshot = shallowRef<NestedTree<TItem> | null>(null)\nconst movedKeys = ref<Set<ListEditorKey>>(new Set())\n// Deletes made during the current reorder session, split by mode — deferred ones count in the toolbar\n// and revert on Cancel; immediate (backend) ones are re-applied on Cancel so they stay gone (else the\n// snapshot-restore would resurrect a server-deleted row). Mirrors ASortableListEditor.\nconst sessionDeferredDeletes = ref<Set<ListEditorKey>>(new Set())\nconst sessionImmediateDeletes = ref<Set<ListEditorKey>>(new Set())\n\n// Re-baseline current tree as saved. Legacy name kept; maps onto `controller.commit()`.\nconst resetDirtyBaseline = () => {\n controller.commit()\n movedKeys.value = new Set()\n}\n// Mark row + every descendant moved — a moved parent visually carries its subtree.\nconst markMoved = (key: ListEditorKey) => {\n const { node } = controller.findNode(key)\n if (!node) {\n movedKeys.value.add(key)\n return\n }\n const collect = (n: NestedTreeNode<TItem>) => {\n movedKeys.value.add(keyOf(n.data))\n if (n.children) n.children.forEach(collect)\n }\n collect(node)\n}\n\nconst decoratorCache = new Map<ListEditorKey, DecoratedNestedViewItem<TItem>>()\nconst viewItemsDecorated = computed<DecoratedNestedViewItem<TItem>[]>(() => {\n const next: DecoratedNestedViewItem<TItem>[] = []\n const liveKeys = new Set<ListEditorKey>()\n for (const vi of flatViewItems.value) {\n liveKeys.add(vi.key)\n const editing = editingKeys.value.has(vi.key)\n const expanded = detailExpandedKeys.value.has(vi.key)\n const childrenExpanded = childrenExpandedKeys.value.has(vi.key)\n const loading = props.loadingKeys?.has(vi.key) ?? false\n const moved = movedKeys.value.has(vi.key)\n // Amber = controller dirty OR reorder-session moved. readonly suppresses it\n // (read-only views can't be unsaved; also dodges a mount-before-load baseline). (QA 85050 sweep)\n const dirty = props.readonly ? false : controller.isUnsaved(vi.key)\n // `disableUnsaved` suppresses only the amber marker — the validation rail still shows, since\n // hiding dirty state should not hide a real error.\n const unsaved = props.disableUnsaved || props.readonly ? false : dirty || moved\n const cached = decoratorCache.get(vi.key)\n if (\n cached &&\n cached.raw === vi.raw &&\n cached.index === vi.index &&\n cached.position === vi.position &&\n cached.depth === vi.depth &&\n cached.parentKey === vi.parentKey &&\n cached.childrenCount === vi.childrenCount &&\n cached.firstInParent === vi.firstInParent &&\n cached.lastInParent === vi.lastInParent &&\n cached.editing === editing &&\n cached.expanded === expanded &&\n cached.childrenExpanded === childrenExpanded &&\n cached.loading === loading &&\n cached.dirty === dirty &&\n cached.moved === moved &&\n cached.unsaved === unsaved\n ) {\n next.push(cached)\n continue\n }\n const decorated: DecoratedNestedViewItem<TItem> = {\n ...vi,\n editing,\n expanded,\n childrenExpanded,\n loading,\n dirty,\n moved,\n unsaved,\n }\n decoratorCache.set(vi.key, decorated)\n next.push(decorated)\n }\n for (const key of decoratorCache.keys()) {\n if (!liveKeys.has(key)) decoratorCache.delete(key)\n }\n return next\n})\n\nconst isEmpty = computed(() => modelValue.value.children.length === 0)\nconst totalItemCount = computed(() => flatViewItems.value.length)\n\nconst rowsContainer = useTemplateRef<HTMLElement>('rowsContainer')\n\nconst canInteract = computed(() => !props.readonly && !props.disabled && !props.loading)\nconst canEnterReorder = computed(() => canInteract.value && !props.disableReorder && totalItemCount.value > 1)\n\nconst isInlineEdit = computed(() => !!(slots as Record<string, unknown>).item)\nconst hasReadonlyDetail = computed(() => !!(slots as Record<string, unknown>)['item-readonly'])\nconst showInlineSaveFooter = computed(() => !!props.onItemSave)\n\nconst { editingKeys, editingSnapshots, clearEditing, beginEdit, cancelEdit, commitEdit, closeEdit, requestAutoOpen } =\n useInlineEditing<TItem, NestedViewItem<TItem>>({\n rowsContainer,\n rowSelector: '.a-le-row-wrapper',\n isInlineEdit,\n // markDirty=false: restoring the pre-edit snapshot on cancel must not flag the\n // node dirty (it would otherwise be resent by partial-multi saves).\n restoreSnapshot: (key, data) => controller.updateItem(key, data, false),\n watchKeys: () => {\n const keys: ListEditorKey[] = []\n const walk = (nodes: NestedTreeNode<TItem>[]) => {\n for (const n of nodes) {\n keys.push(keyOf(n.data))\n if (n.children && n.children.length) walk(n.children)\n }\n }\n walk(modelValue.value.children)\n return keys\n },\n findEntry: (key) => {\n const { node } = controller.findNode(key)\n return node ? { data: node.data } : null\n },\n afterAutoOpen: (key) => {\n const { parent } = controller.findNode(key)\n if (parent) {\n childrenExpandedKeys.value.add(keyOf(parent.data))\n }\n },\n })\n\nconst {\n applying,\n applyError,\n hasPendingChanges,\n movedCount,\n reorderMode,\n enterReorderMode,\n cancelReorderMode,\n applyReorder,\n} = useReorderMode<NestedTree<TItem>>({\n mode,\n snapshot,\n movedKeys,\n modelValue,\n cloneModel: (m) => cloneDeep(m) as NestedTree<TItem>,\n applyModel: (m) => {\n modelValue.value = m\n },\n canEnterReorder,\n onEnter: () => {\n sessionDeferredDeletes.value = new Set()\n sessionImmediateDeletes.value = new Set()\n clearEditing()\n // Expand every branch so all rows are reachable drag targets.\n for (const k of expandableKeys.value) childrenExpandedKeys.value.add(k)\n nextTick(() => {\n if (dragEnabled.value) initSortables()\n })\n },\n onExternalEnter: () => {\n sessionDeferredDeletes.value = new Set()\n sessionImmediateDeletes.value = new Set()\n clearEditing()\n },\n // Cancel: the snapshot-restore has brought every deleted row back; reconcile the controller so a\n // deferred delete truly reverts (clear tombstone, row stays) and an immediate delete stays gone (row\n // removed again — it is deleted on the backend and must not resurrect / be re-created by a save).\n onCancel: () => {\n for (const key of sessionDeferredDeletes.value) controller.restoreDeleted(key)\n for (const key of sessionImmediateDeletes.value) controller.deleteItem(key, { trackDeleted: false })\n sessionDeferredDeletes.value = new Set()\n sessionImmediateDeletes.value = new Set()\n destroySortables()\n },\n onApplyEnd: () => {\n sessionDeferredDeletes.value = new Set()\n sessionImmediateDeletes.value = new Set()\n destroySortables()\n },\n onReorderApply: (tree) => props.onReorderApply?.(tree),\n emit: {\n reorderStart: () => emit('reorder-start'),\n reorderCancel: () => emit('reorder-cancel'),\n reorderApplied: (payload) => emit('reorder-applied', payload),\n reorderApplyError: (err) => emit('reorder-apply-error', err),\n reorderEnd: () => emit('reorder-end'),\n },\n})\n\n// Reorder-toolbar pending indicator = session moves + deferred deletes made in the session, so a delete\n// in reorder mode no longer reads as \"no pending changes\" and Apply stays enabled.\nconst nestedPendingCount = computed<number>(() => movedCount.value + sessionDeferredDeletes.value.size)\nconst nestedPendingChanges = computed<boolean>(() => hasPendingChanges.value || sessionDeferredDeletes.value.size > 0)\n\nconst canAdd = computed(() => canInteract.value && props.showAddButton && !reorderMode.value)\nconst dragEnabled = computed(() => reorderMode.value && !isTouch.value && !props.disableDrag)\n\nconst addLabelResolved = computed(() => props.addLabel ?? t('common.sortable.add'))\nconst emptyTitleResolved = computed(() => props.emptyTitle ?? t('common.sortable.emptyTitle'))\nconst deleteConfirmTitleResolved = computed(() => props.deleteConfirmTitle ?? t('common.sortable.deleteConfirmTitle'))\nconst deleteConfirmTextResolved = computed(\n () =>\n props.deleteConfirmText ??\n (props.deleteMode === 'immediate'\n ? t('common.sortable.deleteConfirmText')\n : t('common.sortable.deleteConfirmTextDeferred'))\n)\n\nconst reorderToggleVisible = computed<boolean>(\n (): boolean => !props.readonly && props.showReorderToggle && !reorderMode.value && totalItemCount.value > 0\n)\n\nconst compactReorderButton = computed<boolean>((): boolean => !!props.title && isNarrow.value)\n\n// Keys of every node that has children — the expand/collapse candidates.\nconst expandableKeys = computed<ListEditorKey[]>(() => {\n const out: ListEditorKey[] = []\n const walk = (nodes: NestedTreeNode<TItem>[]) => {\n for (const n of nodes) {\n if (n.children && n.children.length > 0) {\n out.push(keyOf(n.data))\n walk(n.children)\n }\n }\n }\n walk(modelValue.value.children)\n return out\n})\n\nconst allExpanded = computed<boolean>(\n () => expandableKeys.value.length > 0 && expandableKeys.value.every((k) => childrenExpandedKeys.value.has(k))\n)\n\nconst expandAllVisible = computed<boolean>(() => !reorderMode.value && expandableKeys.value.length > 0)\n\nconst toggleExpandAll = () => {\n if (allExpanded.value) {\n for (const k of expandableKeys.value) childrenExpandedKeys.value.delete(k)\n } else {\n for (const k of expandableKeys.value) childrenExpandedKeys.value.add(k)\n }\n}\n\nconst headerVisible = computed<boolean>(\n (): boolean =>\n !!(\n props.title ||\n (slots as Record<string, unknown>).header ||\n (slots as Record<string, unknown>)['reorder-toggle'] ||\n reorderToggleVisible.value ||\n expandAllVisible.value ||\n reorderMode.value\n )\n)\n\n// One Sortable instance per group so drag/drop moves items within/between groups;\n// SortableJS owns the pointer events, onEnd reconciles via editor.moveTo().\nconst sortableInstances = ref<Array<{ stop: () => void; option?: (k: string, v: unknown) => void }>>([])\nconst forceRerender = ref(0)\n\nconst destroySortables = () => {\n for (const s of sortableInstances.value) {\n try {\n s.stop()\n } catch {\n // no-op\n }\n }\n sortableInstances.value = []\n}\n\nconst GROUP_CLASS = 'a-nested-list-editor__group'\nconst HANDLE_CLASS = 'a-le-drag-handle'\n\n// Live drag state. `instruction` is recomputed on every pointermove and encodes\n// where + at what depth the dragged item lands (sibling-above/below/make-child/blocked);\n// the overlay reads it, onEnd applies it via `editor.moveTo`. SortableJS never moves\n// the DOM (onMove always returns false) — hit-testing, depth and the mutation are all ours.\nconst dragState = ref<DragState | null>(null)\n\n// Drop indicator anchor geometry, kept in sync with CSS (reorder-mode header\n// padding-left = 12 + depth*24, drag handle = 20px). ANCHOR_X = 12 + half handle\n// (10) = 22 puts the dot on the handle's centre; the line steps 24px per depth.\nconst INDENT_PX = 24\nconst ANCHOR_X = 22\n\nconst hitTestRow = (\n clientX: number,\n clientY: number\n): { el: HTMLElement; viewItem: DecoratedNestedViewItem<TItem> } | null => {\n const hit = document.elementFromPoint(clientX, clientY) as HTMLElement | null\n if (!hit) return null\n const wrapper = hit.closest('.a-le-row-wrapper') as HTMLElement | null\n if (!wrapper) return null\n // Only wrappers inside our rowsContainer — elementFromPoint could hit a\n // different ANestedSortableListEditor instance on the same page.\n if (!rowsContainer.value || !rowsContainer.value.contains(wrapper)) return null\n const key = parseKey(wrapper.getAttribute('data-id'))\n if (key === null) return null\n const vi = viewItemsDecorated.value.find((v) => v.key === key)\n if (!vi) return null\n return { el: wrapper, viewItem: vi }\n}\n\nconst recomputeInstruction = (clientX: number, clientY: number) => {\n if (!dragState.value || !rowsContainer.value) return\n const hit = hitTestRow(clientX, clientY)\n if (!hit) {\n dragState.value.instruction = null\n return\n }\n const containerRect = rowsContainer.value.getBoundingClientRect()\n // Hit-test the `.a-le-row` child, not the wrapper — the wrapper's height\n // balloons when children are rendered.\n const rowEl = hit.el.querySelector(':scope > .a-le-row') as HTMLElement | null\n const rowRect = (rowEl ?? hit.el).getBoundingClientRect()\n dragState.value.instruction = computeInstruction({\n pointer: { x: clientX, y: clientY },\n hoveredRow: {\n key: hit.viewItem.key,\n rect: rowRect,\n depth: hit.viewItem.depth,\n parentKey: hit.viewItem.parentKey,\n siblingIndex: hit.viewItem.siblingIndex,\n siblingCount: hit.viewItem.siblingCount,\n },\n sourceKey: dragState.value.sourceKey,\n sourceSubtreeDepth: dragState.value.sourceSubtreeDepth,\n viewItems: viewItemsDecorated.value,\n maxDepth: props.maxDepth,\n indentPx: INDENT_PX,\n containerLeft: containerRect.left,\n containerPaddingLeft: ANCHOR_X,\n })\n}\n\nconst onPointerMove = (e: PointerEvent) => {\n recomputeInstruction(e.clientX, e.clientY)\n}\n\nconst applyInstruction = (inst: ExecutableInstruction, sourceKey: ListEditorKey) => {\n const ok = controller.moveTo(sourceKey, inst.parentKey, inst.index)\n if (!ok) {\n showWarningT('common.sortable.error.maxDeepExceed')\n forceRerender.value++\n nextTick(() => initSortables())\n return\n }\n markMoved(sourceKey)\n // makeChild: expand the new parent so the just-moved row doesn't vanish into a collapsed branch.\n if (inst.makeChild && inst.parentKey !== null) {\n childrenExpandedKeys.value.add(inst.parentKey)\n }\n}\n\nconst initSortables = () => {\n destroySortables()\n if (!dragEnabled.value) return\n if (!rowsContainer.value) return\n const groups = Array.from(rowsContainer.value.querySelectorAll<HTMLElement>('.' + GROUP_CLASS))\n for (const group of groups) {\n const sortable = useSortable(group, [], {\n group: { name: 'a-nested', pull: true, put: true },\n handle: '.' + HANDLE_CLASS,\n animation: 0,\n ghostClass: DRAG_GHOST_CLASS,\n chosenClass: DRAG_CHOSEN_CLASS,\n dragClass: DRAG_CLASS,\n fallbackOnBody: true,\n forceFallback: true,\n fallbackTolerance: 3,\n onStart: (event) => {\n const draggedEl = event.item as HTMLElement\n const id = parseKey(draggedEl.getAttribute('data-id'))\n if (id === null) return\n const draggedNode = controller.findNode(id).node\n const subtreeDepth = draggedNode ? controller.calculateSubtreeDepth(draggedNode) : 1\n dragState.value = {\n sourceKey: id,\n sourceSubtreeDepth: subtreeDepth,\n instruction: null,\n }\n document.addEventListener('pointermove', onPointerMove, { passive: true })\n },\n onMove: (event) => {\n // Also track the pointer via SortableJS's stream — keeps `instruction`\n // in sync when the browser coalesces pointermove events during fast drags.\n const orig = (event as unknown as { originalEvent?: Event }).originalEvent\n if (orig) {\n if ('clientX' in orig && 'clientY' in orig) {\n recomputeInstruction((orig as MouseEvent).clientX, (orig as MouseEvent).clientY)\n } else if ('touches' in orig) {\n const tev = orig as TouchEvent\n if (tev.touches.length > 0) {\n recomputeInstruction(tev.touches[0].clientX, tev.touches[0].clientY)\n }\n }\n }\n // Refuse SortableJS's own DOM insertion — onEnd's `editor.moveTo` drives the move.\n return false\n },\n onEnd: () => {\n document.removeEventListener('pointermove', onPointerMove)\n const state = dragState.value\n dragState.value = null\n if (!state) return\n const inst = state.instruction\n if (inst === null) return\n if (inst.type === 'blocked') return\n applyInstruction(inst, state.sourceKey)\n },\n })\n sortableInstances.value.push(sortable)\n }\n}\n\nconst parseKey = (raw: string | null): ListEditorKey | null => {\n if (raw === null || raw === '') return null\n // Numeric key only when `data-id` is a pure integer string (incl. negative temp\n // ids like \"-1\"); else a DocId/UUID. The old `n > 0` test sent negative temp ids\n // to the string branch, so a freshly-added row's move marked the wrong key.\n const n = stringToInt(raw)\n return String(n) === raw ? n : raw\n}\n\nwatch(\n () => dragEnabled.value,\n () => {\n if (dragEnabled.value) nextTick(() => initSortables())\n else destroySortables()\n }\n)\n\nwatch(\n () => forceRerender.value,\n () => {\n if (dragEnabled.value) nextTick(() => initSortables())\n }\n)\n\n// Rebuild sortables when the tree shape changes during drag mode, else newly rendered groups aren't draggable.\nwatch(\n () => viewItemsDecorated.value.map((v) => v.key).join('|'),\n () => {\n if (dragEnabled.value) nextTick(() => initSortables())\n }\n)\n\nonMounted(() => {\n if (dragEnabled.value) initSortables()\n})\nonBeforeUnmount(() => {\n destroySortables()\n document.removeEventListener('pointermove', onPointerMove)\n})\n\n// Overlay visual props from the current instruction; `null` = hidden (no\n// instruction, or blocked). The optional connector is a rail from the drop line\n// up to the row whose level dictates the insert. Measured via getBoundingClientRect\n// rather than CSS anchor positioning: Chrome's anchor-reachability rule excludes\n// some deeply-nested descendants, so JS measurement is simpler and more reliable.\ntype OverlayVisual = {\n line: { top: number; left: number; right: number }\n connector: { top: number; height: number; left: number } | null\n}\n\nconst overlayVisual = computed<OverlayVisual | null>(() => {\n const state = dragState.value\n if (!state || state.instruction === null || !rowsContainer.value) return null\n const inst = state.instruction\n // Blocked drops render nothing — the silent empty space is the \"not here\" signal.\n if (inst.type === 'blocked') return null\n const refWrapper = rowsContainer.value.querySelector<HTMLElement>(\n `.a-le-row-wrapper[data-id=\"${CSS.escape(String(inst.refKey))}\"]`\n )\n if (!refWrapper) return null\n const rowEl = refWrapper.querySelector<HTMLElement>(':scope > .a-le-row')\n const containerRect = rowsContainer.value.getBoundingClientRect()\n const rowRect = (rowEl ?? refWrapper).getBoundingClientRect()\n\n const lineLeft = ANCHOR_X + inst.depth * INDENT_PX\n const lineTop = inst.refEdge === 'top' ? rowRect.top - containerRect.top : rowRect.bottom - containerRect.top\n const line = { top: lineTop, left: lineLeft, right: 16 }\n\n let connector: OverlayVisual['connector'] = null\n if (inst.levelRowKey !== null) {\n const levelWrapper = rowsContainer.value.querySelector<HTMLElement>(\n `.a-le-row-wrapper[data-id=\"${CSS.escape(String(inst.levelRowKey))}\"]`\n )\n if (levelWrapper) {\n const levelRow = levelWrapper.querySelector<HTMLElement>(':scope > .a-le-row')\n const levelRect = (levelRow ?? levelWrapper).getBoundingClientRect()\n const levelCentreY = levelRect.top - containerRect.top + levelRect.height / 2\n if (levelCentreY < lineTop) {\n connector = {\n top: levelCentreY,\n height: lineTop - levelCentreY,\n left: lineLeft,\n }\n }\n }\n }\n\n return { line, connector }\n})\n\n// With a `factory`, the editor inserts through the controller; without one it\n// stays emit-only so legacy `@add` consumers drive the insert. The `add` event\n// fires either way.\nconst onAddClick = () => {\n if (!canAdd.value) return\n requestAutoOpen()\n if (props.factory) controller.addItem()\n emit('add', undefined)\n}\n\nconst onRowAddAfterClick = (vi: NestedViewItem<TItem>) => {\n if (!canInteract.value) return\n requestAutoOpen()\n if (props.factory) controller.addAfter(vi.key, undefined, vi.childrenAllowed)\n emit('add', { afterId: vi.key, childrenAllowed: vi.childrenAllowed })\n}\n\nconst onAddChildClick = (vi: NestedViewItem<TItem>) => {\n if (!canInteract.value) return\n if (!vi.canAddChild) return\n requestAutoOpen()\n childrenExpandedKeys.value.add(vi.key)\n emit('add-child', vi)\n // Append to the end of children, matching the root \"Add item\" semantic.\n // (Drag-drop make-child still lands at index 0 — its drop line sits just below the parent.)\n if (props.factory) controller.addChild(vi.key, undefined, true)\n emit('add', { parentId: vi.key, childrenAllowed: true })\n}\n\nconst onEditClick = (vi: NestedViewItem<TItem>) => {\n if (!canInteract.value || reorderMode.value) return\n // Clicking edit while already editing closes the form, so the pencil toggles like the row header.\n if (editingKeys.value.has(vi.key)) {\n onCloseClick(vi)\n return\n }\n if (isInlineEdit.value) {\n beginEdit(vi)\n }\n emit('edit', vi)\n}\n\n// Toggles tree children visibility (distinct from the readonly-detail body below).\nconst onChevronClick = (vi: NestedViewItem<TItem>) => {\n if (props.disabled || props.loading) return\n const key = vi.key\n const currently = childrenExpandedKeys.value.has(key)\n if (currently) childrenExpandedKeys.value.delete(key)\n else childrenExpandedKeys.value.add(key)\n emit('item-expand', vi, !currently)\n}\n\n// Toggles the row's readonly-detail body (separate from tree children).\nconst onDetailToggle = (vi: NestedViewItem<TItem>) => {\n if (props.disabled || props.loading) return\n const key = vi.key\n const currently = detailExpandedKeys.value.has(key)\n if (currently) detailExpandedKeys.value.delete(key)\n else detailExpandedKeys.value.add(key)\n emit('item-expand', vi, !currently)\n}\n\nconst isRowClickable = (vi: DecoratedNestedViewItem<TItem>): boolean => {\n if (props.disableRowClick) return false\n if (props.disabled || props.loading) return false\n if (reorderMode.value) return false\n if (vi.editing || vi.expanded) return true\n if (!props.readonly && props.showEditButton) return true\n return false\n}\n\nconst onRowClick = (vi: DecoratedNestedViewItem<TItem>) => {\n if (!isRowClickable(vi)) return\n if (vi.editing || vi.expanded) {\n onCloseClick(vi)\n return\n }\n if (props.readonly && hasReadonlyDetail.value) onDetailToggle(vi)\n else onEditClick(vi)\n}\n\nconst {\n deleteDialog,\n deleteInFlight,\n deleteError,\n onDeleteClick: triggerDeleteClick,\n onDeleteDialogConfirm,\n onDeleteDialogCancel,\n} = useDeleteDialog<TItem, NestedViewItem<TItem>>({\n onDeleteConfirm: (raw) => (props.onDeleteConfirm ? props.onDeleteConfirm(raw) : true),\n onDelete: (raw) => props.onDelete?.(raw),\n onDeleted: (vi) => {\n editingKeys.value.delete(vi.key)\n editingSnapshots.value.delete(vi.key)\n detailExpandedKeys.value.delete(vi.key)\n childrenExpandedKeys.value.delete(vi.key)\n const deferred = props.deleteMode === 'deferred'\n // A delete in reorder mode is a session change: deferred reverts on Cancel, immediate stays gone\n // (re-applied after Cancel's snapshot-restore).\n if (reorderMode.value) {\n if (deferred) sessionDeferredDeletes.value.add(vi.key)\n else sessionImmediateDeletes.value.add(vi.key)\n }\n // Controller owns removal (mirrors the flat editors): a deferred saved-row delete is tombstoned\n // (counts as unsaved); immediate skips it (already deleted on the backend). Runs before the\n // `deleted` emit so a consumer's handler still receives `vi.raw`.\n controller.deleteItem(vi.key, { trackDeleted: deferred })\n emit('deleted', vi)\n },\n disableDeleteConfirm: () => props.disableDeleteConfirm,\n})\n\nconst onDeleteClick = async (vi: NestedViewItem<TItem>) => {\n if (!canInteract.value) return\n await triggerDeleteClick(vi)\n}\n\nconst onSaveClick = async (vi: NestedViewItem<TItem>) => {\n if (props.onItemSave) await props.onItemSave(vi.raw)\n commitEdit(vi)\n emit('item-saved', vi)\n}\n\nconst onCancelClick = (vi: NestedViewItem<TItem>) => {\n cancelEdit(vi)\n}\n\nconst onCloseClick = (vi: NestedViewItem<TItem>) => {\n closeEdit(vi)\n emit('close', vi)\n}\n\nconst moveUp = (id: ListEditorKey) => {\n if (controller.moveUp(id)) markMoved(id)\n}\nconst moveDown = (id: ListEditorKey) => {\n if (controller.moveDown(id)) markMoved(id)\n}\nconst moveTop = (id: ListEditorKey) => {\n if (controller.moveTop(id)) markMoved(id)\n}\nconst moveBottom = (id: ListEditorKey) => {\n if (controller.moveBottom(id)) markMoved(id)\n}\nconst doIndent = (vi: NestedViewItem<TItem>) => {\n if (!controller.indent(vi.key)) {\n showWarningT('common.sortable.error.maxDeepExceed')\n return\n }\n markMoved(vi.key)\n emit('indent', vi)\n}\nconst doOutdent = (vi: NestedViewItem<TItem>) => {\n if (!controller.outdent(vi.key)) return\n markMoved(vi.key)\n emit('outdent', vi)\n}\n\nconst resolveCompactText = (raw: TItem): string => resolveCompactTextUtil(raw, { compactField: props.compactField })\n\n// Row validation from the controller's gated `rowState` — red rail shows only\n// once the row is unsaved or `validateAll()` ran.\n// `editing` → the controller reads amber (not red) while the row is being filled in. (QA 85050 b7)\nconst resolveValidation = (raw: TItem, key?: ListEditorKey): ListEditorValidationState =>\n key === undefined ? null : controller.rowState(raw, key, editingKeys.value.has(key))\n\n// Per-key actions cache for stable per-row identity (see AListEditor). Closures\n// capture the stable key and look up the current vi via findVi at call time.\ntype ActionsBundle = {\n edit: () => void\n save: () => Promise<void> | void\n cancel: () => void\n close: () => void\n delete: () => Promise<void>\n addAfter: () => void\n addChild: () => void\n toggleExpand: () => void\n toggleDetail: () => void\n moveUp: () => void\n moveDown: () => void\n moveTop: () => void\n moveBottom: () => void\n indent: () => void\n outdent: () => void\n update: (data: TItem) => void\n}\nconst actionsCache = new Map<ListEditorKey, ActionsBundle>()\nconst getActions = (key: ListEditorKey): ActionsBundle => {\n let actions = actionsCache.get(key)\n if (!actions) {\n actions = {\n edit: () => {\n const vi = findVi(key)\n if (vi) onEditClick(vi)\n },\n save: () => {\n const vi = findVi(key)\n if (vi) return onSaveClick(vi)\n },\n cancel: () => {\n const vi = findVi(key)\n if (vi) onCancelClick(vi)\n },\n close: () => {\n const vi = findVi(key)\n if (vi) onCloseClick(vi)\n },\n delete: async () => {\n const vi = findVi(key)\n if (vi) await onDeleteClick(vi)\n },\n addAfter: () => {\n const vi = findVi(key)\n if (vi) onRowAddAfterClick(vi)\n },\n addChild: () => {\n const vi = findVi(key)\n if (vi) onAddChildClick(vi)\n },\n toggleExpand: () => {\n const vi = findVi(key)\n if (vi) onChevronClick(vi)\n },\n toggleDetail: () => {\n const vi = findVi(key)\n if (vi) onDetailToggle(vi)\n },\n moveUp: () => moveUp(key),\n moveDown: () => moveDown(key),\n moveTop: () => moveTop(key),\n moveBottom: () => moveBottom(key),\n indent: () => {\n const vi = findVi(key)\n if (vi) doIndent(vi)\n },\n outdent: () => {\n const vi = findVi(key)\n if (vi) doOutdent(vi)\n },\n update: (data: TItem) => controller.updateItem(key, data),\n }\n actionsCache.set(key, actions)\n }\n return actions\n}\nwatch(\n () => viewItemsDecorated.value,\n (now) => {\n if (actionsCache.size === 0) return\n const liveKeys = new Set(now.map((v) => v.key))\n for (const key of actionsCache.keys()) {\n if (!liveKeys.has(key)) actionsCache.delete(key)\n }\n }\n)\n\nconst buildSlotProps = (vi: DecoratedNestedViewItem<TItem>) => ({\n item: { ...vi, validationState: resolveValidation(vi.raw as TItem, vi.key) },\n raw: vi.raw,\n index: vi.index,\n key: vi.key,\n depth: vi.depth,\n parent: vi.parent,\n parentKey: vi.parentKey,\n childrenCount: vi.childrenCount,\n hasChildren: vi.hasChildren,\n childrenAllowed: vi.childrenAllowed,\n firstInParent: vi.firstInParent,\n lastInParent: vi.lastInParent,\n readonly: props.readonly,\n disabled: props.disabled,\n expanded: vi.expanded,\n childrenExpanded: vi.childrenExpanded,\n editing: vi.editing,\n dirty: vi.dirty,\n unsaved: vi.unsaved,\n moved: vi.moved,\n reorderMode: reorderMode.value,\n canMoveUp: !vi.firstInParent,\n canMoveDown: !vi.lastInParent,\n canIndent: vi.canIndent,\n canOutdent: vi.canOutdent,\n canAddChild: vi.canAddChild,\n touch: isTouch.value,\n stateKeyPrefix: rowStateScope.prefixFor(vi.key),\n actions: getActions(vi.key),\n})\n\nconst toolbarSlotProps = computed(() => ({\n applying: applying.value,\n hasPendingChanges: nestedPendingChanges.value,\n movedCount: nestedPendingCount.value,\n error: applyError.value,\n actions: {\n apply: applyReorder,\n cancel: cancelReorderMode,\n },\n}))\n\nconst reorderToggleSlotProps = computed(() => ({\n mode: mode.value,\n disabled: !canEnterReorder.value,\n hasPendingChanges: hasPendingChanges.value,\n actions: {\n enterReorderMode,\n exitReorderMode: cancelReorderMode,\n },\n}))\n\nconst expandableKeySet = computed(() => new Set(expandableKeys.value))\n\nconst findVi = (key: ListEditorKey): DecoratedNestedViewItem<TItem> | undefined =>\n viewItemsDecorated.value.find((v) => v.key === key)\n\nconst keyboardNav = useKeyboardNav({\n viewItems: computed(() =>\n viewItemsDecorated.value.map((vi) => ({\n key: vi.key,\n expanded: vi.childrenExpanded,\n canExpand: expandableKeySet.value.has(vi.key),\n canIndent: true,\n canOutdent: true,\n }))\n ),\n variant: 'nested',\n isReorderMode: reorderMode,\n disabled: computed(() => !canInteract.value),\n isEditing: (key) => editingKeys.value.has(key),\n onToggleEdit: (key) => {\n const vi = findVi(key)\n if (vi) onEditClick(vi)\n },\n onCancelEdit: (key) => {\n const vi = findVi(key)\n if (vi) onCloseClick(vi)\n },\n onMoveUp: (key) => moveUp(key),\n onMoveDown: (key) => moveDown(key),\n onMoveTop: (key) => moveTop(key),\n onMoveBottom: (key) => moveBottom(key),\n onIndent: (key) => {\n const vi = findVi(key)\n if (vi) doIndent(vi)\n },\n onOutdent: (key) => {\n const vi = findVi(key)\n if (vi) doOutdent(vi)\n },\n onToggleChildren: (key) => {\n const vi = findVi(key)\n if (vi) onChevronClick(vi)\n },\n onCancelReorder: () => cancelReorderMode(),\n})\n\nconst moveToPositionDialogOpen = ref<boolean>(false)\nconst moveToPositionTarget = shallowRef<DecoratedNestedViewItem<TItem> | null>(null)\nconst moveToPositionContext = computed<{\n parentId: ListEditorKey | null\n total: number\n currentIndex: number\n} | null>(() => {\n const target = moveToPositionTarget.value\n if (!target) return null\n const found = controller.findNode(target.key)\n const siblings = found.parent?.children ?? modelValue.value.children\n const idx = siblings.findIndex((s) => keyOf(s.data) === target.key)\n return {\n parentId: found.parent ? keyOf(found.parent.data) : null,\n total: siblings.length,\n currentIndex: idx,\n }\n})\nconst moveToPositionLabel = computed<string>(() =>\n moveToPositionTarget.value ? resolveCompactText(moveToPositionTarget.value.raw) : ''\n)\nconst openMoveToPosition = (vi: DecoratedNestedViewItem<TItem>) => {\n if (!props.showMoveToPosition) return\n moveToPositionTarget.value = vi\n moveToPositionDialogOpen.value = true\n}\nconst onMoveToPositionConfirm = (newIndex: number) => {\n const ctx = moveToPositionContext.value\n const target = moveToPositionTarget.value\n moveToPositionTarget.value = null\n if (!ctx || !target) return\n if (newIndex === ctx.currentIndex) return\n if (controller.moveTo(target.key, ctx.parentId, newIndex)) {\n markMoved(target.key)\n }\n}\n\nconst changeParentDialogOpen = ref<boolean>(false)\nconst changeParentTarget = shallowRef<DecoratedNestedViewItem<TItem> | null>(null)\nconst openChangeParent = (vi: DecoratedNestedViewItem<TItem>) => {\n if (!props.showChangeParent) return\n changeParentTarget.value = vi\n changeParentDialogOpen.value = true\n}\nconst onChangeParentConfirm = (parentId: ListEditorKey | null, position: 'first' | 'last') => {\n const target = changeParentTarget.value\n changeParentTarget.value = null\n if (!target) return\n // Sibling count under the new parent so 'last' resolves to the right index ('first' = 0).\n let siblingCount = 0\n if (parentId === null) {\n siblingCount = modelValue.value.children.length\n } else {\n const found = controller.findNode(parentId)\n siblingCount = found.node?.children?.length ?? 0\n }\n const targetIndex = position === 'first' ? 0 : siblingCount\n if (controller.moveTo(target.key, parentId, targetIndex)) {\n markMoved(target.key)\n if (parentId !== null) childrenExpandedKeys.value.add(parentId)\n }\n}\n\n// Aggregated display flags + helpers passed into each <LeNestedRow>.\nconst rowContext = computed(() => ({\n reorderMode: reorderMode.value,\n readonly: props.readonly,\n canInteract: canInteract.value,\n dragEnabled: dragEnabled.value,\n showExpandToggle: props.showExpandToggle,\n showEditButton: props.showEditButton,\n showDeleteButton: props.showDeleteButton,\n showAddChildButton: props.showAddChildButton,\n showAddAfterAction: props.showAddAfterAction,\n showMoveToPosition: props.showMoveToPosition,\n showChangeParent: props.showChangeParent,\n showInlineSaveFooter: showInlineSaveFooter.value,\n statusField: props.statusField,\n effectiveCloseVariant: effectiveCloseVariant.value,\n keyboardNav,\n isRowClickable,\n resolveCompactText,\n resolveValidation: (raw: TItem, key?: ListEditorKey) => resolveValidation(raw, key),\n buildSlotProps,\n}))\n\n// Event callback bundle passed to each row.\nconst rowCallbacks = {\n onRowClick,\n onChevronClick,\n onEditClick,\n onDeleteClick,\n onCloseClick,\n onSaveClick,\n onCancelClick,\n onAddAfterClick: onRowAddAfterClick,\n onAddChildClick,\n moveUp: (id: ListEditorKey) => moveUp(id),\n moveDown: (id: ListEditorKey) => moveDown(id),\n moveTop: (id: ListEditorKey) => moveTop(id),\n moveBottom: (id: ListEditorKey) => moveBottom(id),\n indent: doIndent,\n outdent: doOutdent,\n openMoveToPosition,\n openChangeParent,\n}\n\nconst rootViewItems = computed(() => viewItemsDecorated.value.filter((v) => v.parentKey === null))\n\n// Imperative aliases mirroring the legacy ASortableNested signatures so existing\n// consumers keep working. They assume the caller persists server-side, so they\n// re-baseline via `controller.commit()` (rows don't render \"unsaved\" after the\n// call) and return the live model. New consumers should use the controller handle directly.\n\n/** @deprecated Use `addItem(data, { afterId, childrenAllowed })` on the handle. */\nconst addAfterId = (targetId: ListEditorKey | null, data: TItem, childrenAllowed: boolean) => {\n controller.addItem(data, { afterId: targetId ?? undefined, childrenAllowed })\n nextTick(() => controller.commit())\n return modelValue.value\n}\n/** @deprecated Use `addChild(parentKey, data, childrenAllowed)` on the handle. */\nconst addChildToId = (targetId: ListEditorKey, data: TItem, childrenAllowed: boolean) => {\n childrenExpandedKeys.value.add(targetId)\n // Legacy \"prepend as first child\" semantic (canonical `addChild` appends to the end).\n controller.addItem(data, { parentId: targetId, asFirstChild: true, childrenAllowed })\n nextTick(() => controller.commit())\n return modelValue.value\n}\n/** @deprecated Use `deleteItem(id)` on the handle. */\nconst removeById = (id: ListEditorKey) => {\n controller.deleteItem(id)\n editingKeys.value.delete(id)\n editingSnapshots.value.delete(id)\n detailExpandedKeys.value.delete(id)\n childrenExpandedKeys.value.delete(id)\n nextTick(() => controller.commit())\n}\n/** @deprecated Use `updateItem(id, data)` on the handle. */\nconst updateData = (\n id: ListEditorKey,\n data: TItem,\n _children: unknown = null,\n _position: unknown = null,\n _markUnsaved: unknown = null\n) => {\n controller.updateItem(id, data)\n nextTick(() => controller.commit())\n}\n\n// Total distinct unconfirmed changes (added/edited/moved/reparented rows + deferred deletions) — a\n// delete counts even though its row is gone. Drives the unsaved-section label + the exposed handle.\nconst unsavedCount = controller.unsavedCount\n\n// Re-baseline as saved, clear the moved set, close open edits. Legacy name kept.\nconst clearUnsavedState = () => {\n controller.commit()\n movedKeys.value = new Set()\n clearEditing()\n}\n\n// Registers this editor as a named unsaved-changes section when a label is passed.\nuseUnsavedSection(() =>\n props.unsavedSectionLabel && !props.disableUnsaved\n ? { label: props.unsavedSectionLabel, dirty: controller.hasUnsaved.value }\n : []\n)\n\n// Expose the controller handle plus legacy aliases and reorder/expand controls.\n// Entries after the spread override controller methods where the historic name or\n// return shape differs (e.g. `viewItems` is this component's expand-aware list).\n// Reveal for a blocked save: open invalid rows AND expand the offender's ancestor chain so a\n// collapsed nested row becomes visible. Shared by the exposed validateAll + scope reveal-on-touch.\nconst revealNestedInvalid = (): boolean =>\n validateAllAndReveal(controller, (key) => {\n let ancestor = controller.findNode(key).parent\n while (ancestor) {\n childrenExpandedKeys.value.add(keyOf(ancestor.data))\n ancestor = controller.findNode(keyOf(ancestor.data)).parent\n }\n const vi = controller.viewItems.value.find((v) => v.key === key)\n if (vi) beginEdit(vi)\n })\n\n// Auto-bridge the editor's aggregate validity into the consumer's `validation-scope` collector so a\n// plain `$invalid` save gate blocks (and reveals) a collapsed invalid row. No-op without the prop.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss -- construction-time opt-in, read once\nuseListEditorScopeValidity({\n hasErrors: controller.hasErrors,\n validationScope: props.validationScope,\n validateProvided: props.validate !== undefined,\n reveal: revealNestedInvalid,\n})\n\ndefineExpose<\n NestedListEditorHandle<TItem> & {\n addAfterId: typeof addAfterId\n addChildToId: typeof addChildToId\n removeById: typeof removeById\n updateData: typeof updateData\n resetDirtyBaseline: () => void\n hasUnsavedChanges: typeof controller.hasUnsaved\n unsavedCount: typeof unsavedCount\n clearUnsavedState: () => void\n enterReorderMode: () => void\n cancelReorderMode: () => void\n applyReorder: () => Promise<void>\n expand: (id: ListEditorKey) => void\n collapse: (id: ListEditorKey) => void\n toggleExpand: (id: ListEditorKey) => void\n expandDetail: (id: ListEditorKey) => void\n collapseDetail: (id: ListEditorKey) => void\n }\n>({\n ...controller,\n validateAll: revealNestedInvalid,\n // Legacy aliases (pre-v2 ASortableNested API).\n addAfterId,\n addChildToId,\n removeById,\n updateData,\n resetDirtyBaseline,\n hasUnsavedChanges: controller.hasUnsaved,\n unsavedCount,\n clearUnsavedState,\n // Expand-aware flattened view (controller's own `viewItems` ignores collapse state).\n viewItems: flatViewItems,\n enterReorderMode,\n cancelReorderMode,\n applyReorder,\n expand: (id: ListEditorKey) => childrenExpandedKeys.value.add(id),\n collapse: (id: ListEditorKey) => childrenExpandedKeys.value.delete(id),\n toggleExpand: (id: ListEditorKey) => {\n if (childrenExpandedKeys.value.has(id)) childrenExpandedKeys.value.delete(id)\n else childrenExpandedKeys.value.add(id)\n },\n expandDetail: (id: ListEditorKey) => detailExpandedKeys.value.add(id),\n collapseDetail: (id: ListEditorKey) => detailExpandedKeys.value.delete(id),\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"a-nested-list-editor\"\n :class=\"[\n `a-nested-list-editor--mode-${mode}`,\n {\n 'a-nested-list-editor--readonly': readonly,\n 'a-nested-list-editor--disabled': disabled,\n 'a-nested-list-editor--touch': isTouch,\n 'a-nested-list-editor--drag-enabled': dragEnabled,\n },\n ]\"\n >\n <div class=\"a-le-card\">\n <div\n v-if=\"headerVisible\"\n class=\"a-le-header\"\n >\n <slot\n name=\"header\"\n :title=\"title\"\n v-bind=\"reorderToggleSlotProps\"\n >\n <h3\n v-if=\"title\"\n class=\"a-le-title-heading\"\n >\n {{ title }}\n </h3>\n <div class=\"a-le-header-actions\">\n <template v-if=\"reorderMode\">\n <!-- Reorder-mode header: pending count + Cancel/Apply, sitting where\n the \"Reorder\" button lives in view mode (not a bottom toolbar). -->\n <slot\n name=\"reorder-toolbar\"\n v-bind=\"toolbarSlotProps\"\n >\n <LeStatus\n :class=\"{ 'a-le-toolbar-status--pending': nestedPendingChanges }\"\n :has-pending-changes=\"nestedPendingChanges\"\n :pending-count=\"nestedPendingCount\"\n :error=\"applyError\"\n />\n <VBtn\n variant=\"text\"\n size=\"small\"\n :disabled=\"applying\"\n @click=\"cancelReorderMode\"\n >\n {{ t('common.sortable.reorderCancel') }}\n </VBtn>\n <VBtn\n color=\"primary\"\n variant=\"flat\"\n size=\"small\"\n prepend-icon=\"mdi-check\"\n :loading=\"applying\"\n :disabled=\"applying || !nestedPendingChanges\"\n @click=\"applyReorder\"\n >\n {{ t('common.sortable.reorderApply') }}\n </VBtn>\n </slot>\n </template>\n <template v-else>\n <VBtn\n v-if=\"expandAllVisible && compactReorderButton\"\n variant=\"tonal\"\n color=\"primary\"\n icon\n size=\"x-small\"\n @click=\"toggleExpandAll\"\n >\n <VIcon\n :icon=\"allExpanded ? 'mdi-unfold-less-horizontal' : 'mdi-unfold-more-horizontal'\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"allExpanded ? t('common.sortable.collapseAll') : t('common.sortable.expandAll')\"\n />\n </VBtn>\n <VBtn\n v-else-if=\"expandAllVisible\"\n variant=\"tonal\"\n color=\"primary\"\n size=\"small\"\n :prepend-icon=\"allExpanded ? 'mdi-unfold-less-horizontal' : 'mdi-unfold-more-horizontal'\"\n @click=\"toggleExpandAll\"\n >\n {{ allExpanded ? t('common.sortable.collapseAll') : t('common.sortable.expandAll') }}\n </VBtn>\n <slot\n v-if=\"reorderToggleVisible\"\n name=\"reorder-toggle\"\n v-bind=\"reorderToggleSlotProps\"\n >\n <VBtn\n v-if=\"compactReorderButton\"\n variant=\"tonal\"\n color=\"primary\"\n icon\n size=\"x-small\"\n :disabled=\"!canEnterReorder\"\n @click=\"enterReorderMode\"\n >\n <VIcon\n icon=\"mdi-sort\"\n size=\"18\"\n />\n <VTooltip\n activator=\"parent\"\n location=\"bottom\"\n :text=\"t('common.sortable.reorder')\"\n />\n </VBtn>\n <VBtn\n v-else\n variant=\"tonal\"\n color=\"primary\"\n prepend-icon=\"mdi-sort\"\n size=\"small\"\n :disabled=\"!canEnterReorder\"\n @click=\"enterReorderMode\"\n >\n {{ t('common.sortable.reorder') }}\n </VBtn>\n </slot>\n </template>\n </div>\n </slot>\n </div>\n\n <div\n v-if=\"loading\"\n class=\"a-le-state a-le-state--loading\"\n >\n <VProgressCircular\n indeterminate\n color=\"primary\"\n size=\"32\"\n />\n </div>\n\n <div\n v-else-if=\"error\"\n class=\"a-le-state a-le-state--error\"\n >\n <VAlert\n type=\"error\"\n variant=\"tonal\"\n density=\"compact\"\n border=\"start\"\n >\n {{ error }}\n </VAlert>\n </div>\n\n <div\n v-else-if=\"isEmpty\"\n class=\"a-le-state a-le-state--empty\"\n >\n <slot\n name=\"empty\"\n :readonly=\"readonly\"\n :disabled=\"disabled\"\n :actions=\"{ add: onAddClick }\"\n >\n <LeEmptyState\n :title=\"emptyTitleResolved\"\n :add-label=\"addLabelResolved\"\n :can-add=\"canAdd\"\n @add=\"onAddClick\"\n />\n </slot>\n </div>\n\n <div\n v-else\n :key=\"forceRerender\"\n ref=\"rowsContainer\"\n class=\"a-nested-list-editor__rows\"\n :class=\"{ 'a-nested-list-editor__rows--dragging': dragState !== null }\"\n >\n <div\n :class=\"[GROUP_CLASS, 'a-nested-list-editor__group--root']\"\n data-parent-id=\"\"\n >\n <LeNestedRow\n v-for=\"vi in rootViewItems\"\n :key=\"String(vi.key)\"\n :vi=\"vi\"\n :view-items=\"viewItemsDecorated\"\n :drag-state=\"dragState\"\n :context=\"rowContext\"\n :callbacks=\"rowCallbacks\"\n >\n <template #item=\"slotScope\">\n <slot\n name=\"item\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-compact=\"slotScope\">\n <slot\n name=\"item-compact\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-readonly=\"slotScope\">\n <slot\n name=\"item-readonly\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-status=\"slotScope\">\n <slot\n name=\"item-status\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-footer=\"slotScope\">\n <slot\n name=\"item-footer\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #item-actions=\"slotScope\">\n <slot\n name=\"item-actions\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #before-item=\"slotScope\">\n <slot\n name=\"before-item\"\n v-bind=\"slotScope\"\n />\n </template>\n <template #after-item=\"slotScope\">\n <slot\n name=\"after-item\"\n v-bind=\"slotScope\"\n />\n </template>\n </LeNestedRow>\n </div>\n\n <!-- Drop indicator overlay (valid drops only): horizontal line at a row\n edge (depth in `left`) + an optional vertical connector rail to the\n row whose level the insert matches. -->\n <template v-if=\"overlayVisual !== null\">\n <div\n v-if=\"overlayVisual.connector !== null\"\n class=\"a-nested-list-editor__drop-connector\"\n :style=\"{\n left: `${overlayVisual.connector.left}px`,\n top: `${overlayVisual.connector.top}px`,\n height: `${overlayVisual.connector.height}px`,\n }\"\n />\n <div\n class=\"a-nested-list-editor__drop-line\"\n :style=\"{\n top: `${overlayVisual.line.top}px`,\n left: `${overlayVisual.line.left}px`,\n right: `${overlayVisual.line.right}px`,\n }\"\n >\n <span class=\"a-nested-list-editor__drop-line-dot\" />\n </div>\n </template>\n </div>\n\n <slot\n v-if=\"canAdd && !loading && !error && !isEmpty\"\n name=\"add-button\"\n :readonly=\"readonly\"\n :disabled=\"disabled\"\n :props=\"{ onClick: onAddClick }\"\n :actions=\"{ add: onAddClick }\"\n >\n <button\n type=\"button\"\n class=\"a-le-row-add\"\n @click=\"onAddClick\"\n >\n <VIcon\n icon=\"mdi-plus\"\n size=\"18\"\n />\n <span>{{ addLabelResolved }}</span>\n </button>\n </slot>\n </div>\n\n <LeMoveToPositionDialog\n v-model=\"moveToPositionDialogOpen\"\n :total=\"moveToPositionContext?.total ?? 0\"\n :current-index=\"moveToPositionContext?.currentIndex ?? 0\"\n :item-label=\"moveToPositionLabel\"\n @confirm=\"onMoveToPositionConfirm\"\n />\n\n <LeChangeParentDialog\n v-model=\"changeParentDialogOpen\"\n :tree=\"modelValue\"\n :source-key=\"changeParentTarget?.key ?? null\"\n :key-field=\"keyFieldName\"\n :max-depth=\"maxDepth\"\n :resolve-label=\"resolveCompactText\"\n :calculate-subtree-depth=\"controller.calculateSubtreeDepth\"\n @confirm=\"onChangeParentConfirm\"\n />\n\n <LeDeleteDialog\n v-model=\"deleteDialog\"\n :title=\"deleteConfirmTitleResolved\"\n :text=\"deleteConfirmTextResolved\"\n :confirm-label=\"t('common.sortable.delete')\"\n :cancel-label=\"t('common.button.cancel')\"\n :error=\"deleteError\"\n :in-flight=\"deleteInFlight\"\n @confirm=\"onDeleteDialogConfirm\"\n @cancel=\"onDeleteDialogCancel\"\n />\n\n <div\n class=\"a-le-sr-only\"\n aria-live=\"polite\"\n role=\"status\"\n >\n {{\n keyboardNav.grabbedKey.value !== null\n ? t('common.sortable.keyboardGrab.status', {\n n: keyboardNav.grabbedIndex.value + 1,\n total: keyboardNav.totalCount.value,\n })\n : ''\n }}\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n@use './styles/tokens';\n@use './styles/shared';\n\n// Nested-variant rules — depth-aware padding, tree toggle caret, children\n// groups, drop indicator overlay, drag clone.\n.a-nested-list-editor {\n // Depth-aware left padding — caret column + indent per depth level.\n .a-le-row-header {\n padding: var(--le-row-pad-y) 12px var(--le-row-pad-y) calc(16px + var(--nested-depth, 0) * var(--le-indent));\n }\n\n // Row wrappers + inter-row group layout.\n &__rows {\n position: relative;\n display: flex;\n flex-direction: column;\n }\n\n &__group {\n display: flex;\n flex-direction: column;\n }\n\n // Re-apply the `font` shorthand (shared class uses longhand for flat variants)\n // so the nested variant keeps its explicit line-height reset.\n .a-le-toolbar-status {\n font: 500 13px/1 var(--v-font-body, inherit);\n }\n}\n\n.a-le-row-wrapper {\n display: flex;\n flex-direction: column;\n}\n\n.a-nested-list-editor {\n // Root rows get a bolder title weight to anchor the hierarchy without header rows.\n .a-le-row:not(.a-le-row--child) .a-le-title {\n font-weight: 600;\n }\n\n // Reorder mode — flat-variant padding offset by the row's indent level.\n .a-le-row--reorder .a-le-row-header {\n padding-left: calc(12px + var(--nested-depth, 0) * var(--le-indent));\n padding-right: 8px;\n gap: 8px;\n }\n\n // Active (editing / readonly-expanded) tree-toggle picks up the primary tint\n // so the header reads \"active\" together with the title color.\n .a-le-row--editing &__tree-toggle,\n .a-le-row--expanded &__tree-toggle {\n color: var(--le-primary);\n }\n\n // Children wrapper — depth conveyed by the row's padding-left indent alone.\n &__children {\n position: relative;\n }\n\n // Triangle-caret toggle — circular text-style button (24×24), transparent\n // with a subtle tinted circle on hover.\n &__tree-toggle {\n width: 24px;\n height: 24px;\n padding: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n border-radius: 50%;\n cursor: pointer;\n pointer-events: auto;\n color: rgb(0 0 0 / 54%);\n z-index: 2;\n flex-shrink: 0;\n transition:\n background 0.15s,\n color 0.15s;\n\n &:hover {\n background: rgb(0 0 0 / 5%);\n color: var(--le-on-surface);\n }\n\n &:focus-visible {\n outline: none;\n background: rgb(0 0 0 / 8%);\n }\n }\n\n // Reserve caret column width on leaf rows so titles align across siblings.\n &__tree-toggle--spacer,\n &__tree-toggle--empty {\n width: 24px;\n height: 24px;\n background: transparent;\n visibility: hidden;\n pointer-events: none;\n }\n\n // Pure CSS right-pointing triangle; rotates to down when the row is open.\n &__tree-toggle-caret {\n width: 0;\n height: 0;\n border-left: 5px solid currentcolor;\n border-top: 4px solid transparent;\n border-bottom: 4px solid transparent;\n border-right: 0;\n transition: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1);\n transform: translateX(1px); // optical centering — triangle leans left\n }\n\n &__tree-toggle--open &__tree-toggle-caret {\n transform: translate(0, 1px) rotate(90deg);\n }\n\n // Dragged subtree (source + descendants) dimmed and non-hittable. `!important`\n // on display beats SortableJS's inline `display: none` under `forceFallback`.\n .a-le-row-wrapper.a-le-row--chosen,\n .a-le-row-wrapper.a-le-row-wrapper--drop-disabled {\n display: flex !important;\n opacity: 0.4 !important;\n pointer-events: none !important;\n }\n\n // Hide SortableJS's placeholder — our overlay is the sole landing indicator.\n .a-le-row--ghost {\n display: none !important;\n }\n\n // No floating clone — the line overlay carries all the \"where it lands\" info.\n // Wrapper selector beats the `.row-wrapper--drop-disabled` rule the clone inherits.\n .a-le-row-wrapper.a-le-row--drag {\n display: none !important;\n }\n\n // Drop indicator line — 2px primary stroke with an 8px left terminal dot,\n // absolute-positioned inside `__rows`.\n &__drop-line {\n position: absolute;\n height: 2px;\n margin-top: -1px;\n background: var(--le-primary);\n pointer-events: none;\n z-index: 4;\n border-radius: 1px;\n }\n\n &__drop-line-dot {\n position: absolute;\n left: -4px;\n top: 50%;\n width: 8px;\n height: 8px;\n background: var(--le-primary);\n border-radius: 50%;\n transform: translateY(-50%);\n }\n\n // Connector rail — vertical line linking the drop line up to the level-match row.\n &__drop-connector {\n position: absolute;\n width: 2px;\n margin-left: -1px;\n background: var(--le-primary);\n opacity: 0.5;\n pointer-events: none;\n z-index: 3;\n }\n\n // While dragging, dim the add-button so focus stays on the drag target.\n &__rows--dragging .a-le-row-add {\n opacity: 0.4;\n }\n\n // \"+N\" children indicator — hidden in the normal DOM, visible only inside the\n // SortableJS drag clone so the user sees the whole branch will follow.\n &__drag-count {\n display: none;\n align-items: center;\n gap: 4px;\n margin-left: 8px;\n padding: 2px 8px;\n font: 500 11px/1 var(--v-font-body, inherit);\n letter-spacing: 0.02em;\n color: var(--le-primary);\n background: var(--le-primary-container);\n border-radius: var(--le-radius-pill);\n flex-shrink: 0;\n }\n\n .a-le-row--drag &__drag-count {\n display: inline-flex;\n }\n}\n\n// Desktop — depth-aware padding-left aligns the inline form with the title column:\n// 16 (pad) + 24 (caret) + 10 (gap) = 50, plus depth*indent.\n@container le-shell (min-width: 769px) {\n .a-nested-list-editor {\n .a-le-row--editing .a-le-row-body,\n .a-le-row--expanded .a-le-row-body,\n .a-le-row--editing .a-le-row-footer,\n .a-le-row--expanded .a-le-row-footer {\n padding-left: calc(50px + var(--nested-depth, 0) * var(--le-indent));\n padding-right: 16px;\n }\n }\n}\n\n// Narrow / mobile — tighter indent so deep branches fit, taller touch rows,\n// always-visible actions, status badge dropped to make room for the title.\n/* stylelint-disable selector-max-compound-selectors */\n@container le-shell (max-width: 768px) {\n .a-nested-list-editor {\n --le-indent: 18px;\n --le-row-min-height: 48px;\n --le-row-pad-y: 10px;\n\n .a-le-row:not(.a-le-row--editing) .a-le-status {\n display: none;\n }\n\n .a-le-row .a-le-action--edit,\n .a-le-row .a-le-action--delete,\n .a-le-row .a-le-action--menu,\n .a-le-row .a-le-action--up,\n .a-le-row .a-le-action--down,\n .a-le-row .a-le-action--add-child {\n opacity: 1;\n }\n }\n}\n/* stylelint-enable selector-max-compound-selectors */\n</style>\n","import type { UseUnsavedChangesGuardApi } from '@/labs/unsavedGuard/useUnsavedChangesGuard'\n\n/**\n * Wrap an entity-delete handler so it bypasses the unsaved-changes leave guard.\n *\n * An entity delete destroys the record together with any unsaved edits, so the \"you have unsaved\n * changes — really leave?\" prompt that the delete's `router.push` would otherwise trigger is\n * meaningless: the user already confirmed the delete, and there is nothing left to keep. Without this\n * the delete runs, then the leave-guard fires on the post-delete navigation and choosing \"stay\" leaves\n * the view on an already-deleted entity (system error). This acknowledges the guard right before the\n * delete so the follow-up navigation proceeds without prompting. (QA 85050 batch 7 — BUG-08)\n *\n * If `onDelete` REJECTS (the delete failed and never navigated), the acknowledgement is undone\n * immediately (`unacknowledge()`) so a subsequently-dirtied navigation is still guarded (M1) — the\n * error is re-thrown so the caller's own handling runs. For this to catch failures, `onDelete` must\n * let its error propagate (don't swallow it in the action's try/catch).\n *\n * Usage: `@delete-record=\"guardedDelete(() => onDelete(id))\"` or wrap the handler once.\n */\nexport function useGuardedDelete<A extends unknown[], R>(\n guard: Pick<UseUnsavedChangesGuardApi, 'acknowledge' | 'unacknowledge'>,\n onDelete: (...args: A) => R\n): (...args: A) => R | Promise<Awaited<R>> {\n return (...args: A): R | Promise<Awaited<R>> => {\n guard.acknowledge()\n let result: R\n try {\n result = onDelete(...args)\n } catch (err) {\n // Synchronous throw before any navigation — disarm and re-throw.\n guard.unacknowledge()\n throw err\n }\n // Async handler: undo the acknowledgement if the delete rejects (no navigation happened).\n if (result instanceof Promise) {\n return (result as Promise<Awaited<R>>).catch((err) => {\n guard.unacknowledge()\n throw err\n })\n }\n return result\n }\n}\n","import { computed, type ComputedRef, type Ref } from 'vue'\nimport type {\n ListEditorKey,\n ListViewItem,\n PositionHint,\n UseListEditorOptions,\n} from '@/labs/listEditor/types/listEditorTypes'\nimport { renumberPositions } from '@/labs/listEditor/utils/positions'\n\nexport interface ListEditorApi<TItem> {\n viewItems: ComputedRef<ListViewItem<TItem>[]>\n addItem: (data: TItem, positionHint?: PositionHint) => TItem[]\n deleteItem: (idOrItem: ListEditorKey | TItem) => TItem[]\n updateItem: (idOrItem: ListEditorKey | TItem, data: TItem) => TItem[]\n moveItem: (fromIndex: number, toIndex: number) => TItem[]\n recalculatePositions: (items: TItem[]) => TItem[]\n}\n\n/**\n * Flat list editor core. Pure data behavior shared by AListEditor and\n * ASortableListEditor. Does not touch the DOM, SortableJS, or emits.\n *\n * The caller passes a writable ref (typically `defineModel<TItem[]>`).\n * Mutators write a new array to `model.value` and also return it.\n * Input items are never mutated — when positions need to change, a new\n * item object is produced with `{ ...item, [positionField]: n }`.\n */\nexport function useListEditor<TItem extends Record<string, any>>(\n model: Ref<TItem[]>,\n options: UseListEditorOptions = {}\n): ListEditorApi<TItem> {\n const keyField = options.keyField ?? 'id'\n const positionField = options.positionField ?? 'position'\n const positionMultiplier = options.positionMultiplier ?? 1\n const updatePositionEnabled = options.updatePosition === true\n\n // Using the position field as the row key while `update-position` is on is\n // self-defeating: every reorder renumbers positions, which changes the keys,\n // so dirty/validity tracking flags the displaced rows as unsaved (QA 85050\n // BUG-09). Point key-field at a stable identity field (e.g. \"id\").\n if (updatePositionEnabled && keyField === positionField) {\n console.warn(\n `[list-editor] key-field === position-field (\"${keyField}\") with update-position enabled. ` +\n 'Reordering renumbers positions and thus changes row keys, breaking dirty/validity ' +\n 'tracking. Point key-field at a stable identity field (e.g. \"id\").'\n )\n }\n\n // Surfaces row-key wiring bugs loudly: rows keyed `undefined` (item lacks the\n // key-field) or two rows sharing one key silently break dirty tracking,\n // validity rails and reorder targeting in ways that are hard to trace from\n // symptoms (QA 85050 BUG-09 class). Deduplicated per signature so a stable\n // bad state warns once, but a new offender warns again.\n const warnedKeySignatures = new Set<string>()\n const warnOnBadKeys = (items: ListViewItem<TItem>[]): void => {\n const seen = new Set<ListEditorKey>()\n const duplicates = new Set<string>()\n let missing = 0\n for (const vi of items) {\n if (vi.key === undefined || vi.key === null) {\n missing++\n continue\n }\n if (seen.has(vi.key)) duplicates.add(String(vi.key))\n seen.add(vi.key)\n }\n if (missing === 0 && duplicates.size === 0) return\n const signature = `${missing}|${[...duplicates].sort().join(',')}`\n if (warnedKeySignatures.has(signature)) return\n warnedKeySignatures.add(signature)\n if (missing > 0) {\n console.warn(\n `[list-editor] ${missing} row(s) resolve to an undefined key (key-field \"${keyField}\"). ` +\n 'Point key-field at a field every item has, or give new items unique temp ids ' +\n '(see nextListEditorTempId).'\n )\n }\n if (duplicates.size > 0) {\n console.warn(\n `[list-editor] duplicate row keys (key-field \"${keyField}\"): ${[...duplicates].join(', ')}. ` +\n 'Row keys must be unique — dirty tracking and validation rails target rows by key.'\n )\n }\n }\n\n const viewItems = computed<ListViewItem<TItem>[]>(() => {\n const items = model.value.map((raw, index) => ({\n key: raw[keyField] as ListEditorKey,\n index,\n raw,\n position: raw[positionField] as number | undefined,\n }))\n warnOnBadKeys(items)\n return items\n })\n\n const recalculatePositions = (items: TItem[]): TItem[] =>\n renumberPositions(items, { positionField, positionMultiplier })\n\n const finalize = (arr: TItem[]): TItem[] => {\n const result = updatePositionEnabled ? recalculatePositions(arr) : arr\n model.value = result\n return result\n }\n\n const isItem = (value: ListEditorKey | TItem): value is TItem => value !== null && typeof value === 'object'\n\n const resolveIndexByKey = (items: TItem[], key: ListEditorKey): number => items.findIndex((x) => x[keyField] === key)\n\n const resolveIndex = (items: TItem[], idOrItem: ListEditorKey | TItem): number => {\n const key = isItem(idOrItem) ? (idOrItem[keyField] as ListEditorKey) : idOrItem\n return resolveIndexByKey(items, key)\n }\n\n const resolveInsertIndex = (items: TItem[], hint?: PositionHint): number => {\n if (!hint) return items.length\n if (hint.afterId !== undefined) {\n const idx = resolveIndexByKey(items, hint.afterId)\n return idx === -1 ? items.length : idx + 1\n }\n if (hint.afterIndex !== undefined) {\n if (hint.afterIndex < 0) return items.length\n return Math.min(hint.afterIndex + 1, items.length)\n }\n if (hint.index !== undefined) {\n return Math.max(0, Math.min(hint.index, items.length))\n }\n return items.length\n }\n\n const addItem = (data: TItem, positionHint?: PositionHint): TItem[] => {\n const arr = [...model.value]\n const insertIndex = resolveInsertIndex(arr, positionHint)\n arr.splice(insertIndex, 0, data)\n return finalize(arr)\n }\n\n const deleteItem = (idOrItem: ListEditorKey | TItem): TItem[] => {\n const idx = resolveIndex(model.value, idOrItem)\n if (idx === -1) return model.value\n const arr = [...model.value]\n arr.splice(idx, 1)\n return finalize(arr)\n }\n\n const updateItem = (idOrItem: ListEditorKey | TItem, data: TItem): TItem[] => {\n const idx = resolveIndex(model.value, idOrItem)\n if (idx === -1) return model.value\n const arr = [...model.value]\n arr[idx] = data\n return finalize(arr)\n }\n\n const moveItem = (fromIndex: number, toIndex: number): TItem[] => {\n const len = model.value.length\n if (fromIndex === toIndex) return model.value\n if (fromIndex < 0 || fromIndex >= len) return model.value\n if (toIndex < 0 || toIndex >= len) return model.value\n const arr = [...model.value]\n const [el] = arr.splice(fromIndex, 1)\n arr.splice(toIndex, 0, el)\n return finalize(arr)\n }\n\n return {\n viewItems,\n addItem,\n deleteItem,\n updateItem,\n moveItem,\n recalculatePositions,\n }\n}\n","import { computed, ref, type ComputedRef, type Ref } from 'vue'\nimport type { ListEditorKey } from '@/labs/listEditor/types/listEditorTypes'\n\nexport interface UseNestedUnsavedKeysApi {\n /** Set of unsaved keys for one parent row — used as the inner editor's `v-model:unsaved-keys`. */\n getForParent: (parentKey: ListEditorKey) => Set<ListEditorKey>\n /** Push a child editor's unsaved-key set up under `parentKey`. */\n setForParent: (parentKey: ListEditorKey, set: Set<ListEditorKey>) => void\n /**\n * Aggregated set with prefix-merged keys (`${parentKey}:${childKey}`) so two\n * different parents can't collide on a shared child key (typical for newly\n * created rows whose `id` is `0` until the API assigns one).\n */\n merged: ComputedRef<Set<ListEditorKey>>\n /** Total count across all parents — handy for the parent v-model:unsaved-keys. */\n count: ComputedRef<number>\n}\n\n/**\n * Aggregates per-parent child-editor unsaved-keys into one prefix-merged set.\n * Used by stacked editors (e.g. quiz: questions → answers) so the outer\n * editor's parent gets a single merged `Set<ListEditorKey>` to thread into a\n * unsaved-changes guard, without losing per-parent identity.\n *\n * Key collision matters: a brand-new question and a brand-new answer both\n * have `id: 0` from the factory; without the prefix the answers from two\n * different questions would dedupe into one entry and the count would\n * under-report. Prefix `${parentKey}:${childKey}` keeps them distinct.\n *\n * Usage:\n * const childSets = useNestedUnsavedKeys()\n * <ChildEditor\n * :unsaved-keys=\"childSets.getForParent(parent.id)\"\n * @update:unsaved-keys=\"(s) => childSets.setForParent(parent.id, s)\"\n * />\n * // emit `childSets.merged.value` upward\n *\n * IMPORTANT: when the parent editor uses `manage-delete`, clear the removed\n * parent's entry on its `@deleted` — `setForParent(deletedKey, new Set())` —\n * otherwise the deleted row's stale child-unsaved keys keep `merged` non-empty\n * and the unsaved-changes guard lingers after an add-then-delete.\n */\nexport function useNestedUnsavedKeys(): UseNestedUnsavedKeysApi {\n const byParent: Ref<Map<ListEditorKey, Set<ListEditorKey>>> = ref(new Map())\n\n const empty = new Set<ListEditorKey>()\n\n const getForParent = (parentKey: ListEditorKey): Set<ListEditorKey> => byParent.value.get(parentKey) ?? empty\n\n const setForParent = (parentKey: ListEditorKey, set: Set<ListEditorKey>) => {\n if (set.size === 0) {\n byParent.value.delete(parentKey)\n } else {\n byParent.value.set(parentKey, set)\n }\n // Trigger reactivity for `merged` — Map mutations are tracked in Vue 3,\n // but we want a defensive re-emit by reassigning on every call so\n // consumers using shallow equality see a new identity.\n byParent.value = new Map(byParent.value)\n }\n\n const merged = computed<Set<ListEditorKey>>(() => {\n const out = new Set<ListEditorKey>()\n for (const [parentKey, set] of byParent.value.entries()) {\n for (const childKey of set) {\n out.add(`${String(parentKey)}:${String(childKey)}`)\n }\n }\n return out\n })\n\n const count = computed<number>(() => merged.value.size)\n\n return { getForParent, setForParent, merged, count }\n}\n"],"mappings":"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"}