@anzusystems/common-admin 1.47.0-beta.dev-1789897769 → 1.47.0-beta.dev-1790102667
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/{AFormRemoteAutocomplete-DIYc08Pe.mjs → AFormRemoteAutocomplete-BIW7Twfr.mjs} +2528 -1696
- package/dist/AFormRemoteAutocomplete-BIW7Twfr.mjs.map +1 -0
- package/dist/common-admin.css +1 -1
- package/dist/common-admin.d.ts +930 -475
- package/dist/common-admin.js +12067 -10445
- package/dist/common-admin.js.map +1 -1
- package/dist/{esm-Dov5R7kE.mjs → esm-Dvz_E-iW.mjs} +3106 -3085
- package/dist/esm-Dvz_E-iW.mjs.map +1 -0
- package/dist/labs.d.ts +2566 -36
- package/dist/labs.js +4948 -1342
- package/dist/labs.js.map +1 -1
- package/dist/{webfontloader-Db3zCeVG.mjs → webfontloader-BVmLxgC5.mjs} +2 -2
- package/dist/{webfontloader-Db3zCeVG.mjs.map → webfontloader-BVmLxgC5.mjs.map} +1 -1
- package/package.json +7 -7
- package/src/eslint/plugin.mjs +128 -33
- package/dist/AFormRemoteAutocomplete-DIYc08Pe.mjs.map +0 -1
- package/dist/esm-Dov5R7kE.mjs.map +0 -1
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","$slots","$slots","$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","../src/labs/permissionConfig/permissionConfigApi.ts","../src/labs/permissionConfig/permissionConfigStore.ts","../src/labs/permissionConfig/permissionConfigActions.ts","../src/labs/permission/APermissionEditor.vue","../src/labs/permission/APermissionEditor.vue","../src/labs/permissionGroup/permissionGroupApi.ts","../src/labs/permissionGroup/cachedPermissionGroups.ts","../src/labs/permission/permissionActions.ts","../src/labs/permissionGroup/APermissionGroupFilter.vue","../src/labs/permissionGroup/APermissionGroupFilter.vue","../src/labs/permissionGroup/permissionGroupStore.ts","../src/labs/permissionGroup/permissionGroupActions.ts","../src/labs/permissionGroup/permissionGroupFilter.ts","../src/labs/permissionGroup/APermissionGroupDatatable.vue","../src/labs/permissionGroup/APermissionGroupDatatable.vue","../src/labs/permissionGroup/APermissionGroupDetail.vue","../src/labs/permissionGroup/APermissionGroupDetail.vue","../src/labs/permissionGroup/permissionGroupValidations.ts","../src/labs/permissionGroup/APermissionGroupManage.vue","../src/labs/permissionGroup/APermissionGroupManage.vue","../src/labs/permissionGroup/APermissionGroupRemoteAutocomplete.vue","../src/labs/permissionGroup/APermissionGroupRemoteAutocomplete.vue","../src/labs/anzuUser/AAnzuUserFilter.vue","../src/labs/anzuUser/AAnzuUserFilter.vue","../src/labs/anzuUser/anzuUserApi.ts","../src/labs/anzuUser/anzuUserStore.ts","../src/labs/anzuUser/anzuUserActions.ts","../src/labs/anzuUser/anzuUserFilter.ts","../src/labs/anzuUser/AAnzuUserDatatable.vue","../src/labs/anzuUser/AAnzuUserDatatable.vue","../src/labs/anzuUser/AUserEnabledSwitch.vue","../src/labs/anzuUser/AUserEnabledSwitch.vue","../src/labs/anzuUser/anzuUserValidations.ts","../src/labs/anzuUser/AUserMetadataForm.vue","../src/labs/anzuUser/AUserMetadataForm.vue","../src/labs/anzuUser/AAnzuUserRoleAutocomplete.vue","../src/labs/anzuUser/AAnzuUserRoleAutocomplete.vue","../src/labs/anzuUser/AAnzuUserForm.vue","../src/labs/anzuUser/AAnzuUserForm.vue","../src/labs/anzuUser/AUserCopyPermissionsDialog.vue","../src/labs/anzuUser/AUserCopyPermissionsDialog.vue","../src/labs/anzuUser/userSystemDescriptor.ts","../src/labs/anzuUser/AUserCreateInSystemButton.vue","../src/labs/anzuUser/AUserCreateInSystemButton.vue","../src/labs/anzuUser/AUserManageButton.vue","../src/labs/anzuUser/AUserManageButton.vue","../src/labs/anzuUser/userSystemState.ts","../src/labs/anzuUser/AUserSystemStatusChip.vue","../src/labs/anzuUser/AUserSystemStatusChip.vue","../src/labs/anzuUser/userSystemProbe.ts","../src/labs/anzuUser/AUserSystemPanel.vue","../src/labs/anzuUser/AUserSystemPanel.vue","../src/labs/anzuUser/AUserOtherSystemsTable.vue","../src/labs/anzuUser/AUserOtherSystemsTable.vue","../src/labs/anzuUser/userCrossSystemStore.ts","../src/labs/anzuUser/userMetadataDiff.ts","../src/labs/anzuUser/userCrossSystemWrites.ts","../src/labs/anzuUser/userTabs.ts","../src/labs/anzuUser/AUserTabsShell.vue","../src/labs/anzuUser/AUserTabsShell.vue","../src/labs/anzuUser/AUserBulkActionDialog.vue","../src/labs/anzuUser/AUserBulkActionDialog.vue","../src/labs/anzuUser/AUserMetadataRepairDialog.vue","../src/labs/anzuUser/AUserMetadataRepairDialog.vue","../src/labs/anzuUser/userCrossSystemSearch.ts","../src/labs/anzuUser/AUserSystemOverview.vue","../src/labs/anzuUser/AUserSystemOverview.vue"],"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 /** i18n scope for field-level validation failures; defaults to `system` / `entity`. */\n validationSystem?: string\n validationEntity?: 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 {\n client,\n system,\n entity,\n validationSystem,\n validationEntity,\n urlTemplate,\n urlParams,\n options = {},\n cancelPrevious = false,\n } = 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 validationSystem,\n validationEntity,\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","import type { AxiosClientFn } from '@/labs/api/client'\nimport { useApiRequest } from '@/labs/api/useApiRequest'\nimport type { PermissionConfig } from '@/types/PermissionConfig'\n\nexport const PERMISSION_CONFIG_ENTITY = 'permissionConfig'\nexport const PERMISSION_CONFIG_ENDPOINT = '/adm/v1/permissions/config'\n\nexport interface PermissionConfigApiParams {\n client: AxiosClientFn\n /**\n * Which backend this instance talks to. It reaches `useApiRequest` for error context and is the\n * key everything downstream is stored under, so two systems can be loaded side by side.\n */\n system: string\n /** Goes into every `useApiRequest` call; see the per-system entity note in `permissionGroupApi`. */\n entity?: string\n endPoint?: string\n}\n\nexport const usePermissionConfigApi = ({\n client,\n system,\n entity = PERMISSION_CONFIG_ENTITY,\n endPoint = PERMISSION_CONFIG_ENDPOINT,\n}: PermissionConfigApiParams) => {\n const useFetchPermissionConfig = () =>\n useApiRequest<PermissionConfig, null>({\n client,\n method: 'GET',\n system,\n entity,\n // The config endpoint never answers a field-level validation failure, but the pair is kept\n // consistent with the rest of the shared api so a future one lands on real labels.\n validationSystem: 'common',\n validationEntity: entity,\n urlTemplate: endPoint,\n })\n\n return {\n useFetchPermissionConfig,\n }\n}\n","import { acceptHMRUpdate, defineStore } from 'pinia'\nimport { ref } from 'vue'\nimport { usePermissionConfigFactory } from '@/model/factory/PermissionConfigFactory'\nimport type { PermissionConfig } from '@/types/PermissionConfig'\n\n/**\n * One entry per system. The admins this replaces kept `loading` and `initialized` as two module\n * booleans shared by every system they fetched, which is wrong the moment the fetches run in\n * parallel: the first one to finish cleared the flag for all of them, and the first one to fail\n * left it set forever because there was no `finally`.\n */\nexport interface PermissionConfigEntry {\n config: PermissionConfig\n loading: boolean\n initialized: boolean\n}\n\nexport const usePermissionConfigStore = defineStore('labsPermissionConfigStore', () => {\n const { createPermissionConfig } = usePermissionConfigFactory()\n\n const entries = ref(new Map<string, PermissionConfigEntry>())\n\n const emptyEntry = (): PermissionConfigEntry => ({\n config: createPermissionConfig(),\n loading: false,\n initialized: false,\n })\n\n /**\n * Never returns undefined, so a template can read `.config` before the first fetch lands. The\n * empty config is a fresh object per call rather than a shared constant: the editor writes into\n * what it is handed, and one frozen blank would be shared by every system that has not answered.\n */\n function getEntry(system: string): PermissionConfigEntry {\n return entries.value.get(system) ?? emptyEntry()\n }\n\n function setLoading(system: string, loading: boolean) {\n const entry = entries.value.get(system) ?? emptyEntry()\n entries.value.set(system, { ...entry, loading })\n }\n\n function setConfig(system: string, config: PermissionConfig) {\n const entry = entries.value.get(system) ?? emptyEntry()\n entries.value.set(system, { ...entry, config, initialized: true })\n }\n\n /** Drops one system, leaving the others loaded. Used when a system's client is swapped out. */\n function resetSystem(system: string) {\n entries.value.delete(system)\n }\n\n function reset() {\n entries.value = new Map()\n }\n\n return {\n entries,\n getEntry,\n setLoading,\n setConfig,\n resetSystem,\n reset,\n }\n})\n\nif (import.meta.hot) {\n import.meta.hot.accept(acceptHMRUpdate(usePermissionConfigStore, import.meta.hot))\n}\n","import { computed } from 'vue'\nimport { useAlerts } from '@/composables/system/alerts'\nimport { useLanguageSettings } from '@/composables/languageSettings'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport {\n PERMISSION_CONFIG_ENDPOINT,\n PERMISSION_CONFIG_ENTITY,\n usePermissionConfigApi,\n} from '@/labs/permissionConfig/permissionConfigApi'\nimport { usePermissionConfigStore } from '@/labs/permissionConfig/permissionConfigStore'\nimport type { PermissionTranslationGroup } from '@/types/PermissionConfig'\nimport { objectGetValueByPath } from '@/utils/object'\n\nexport interface PermissionConfigActionsParams {\n client: AxiosClientFn\n /**\n * The system this config belongs to. It is a parameter and not a global -- admin-inhouse used to\n * read it from a `curSystem` ref, which made the whole module impossible to use from a page that\n * shows two systems at once and impossible to move here at all.\n */\n system: string\n entity?: string\n endPoint?: string\n /**\n * Fetch as soon as the composable is built, the way every admin does today. Turn it off where\n * the caller wants to decide when (a dialog that may never open, a test).\n */\n autoFetch?: boolean\n}\n\n/**\n * One request per system, however many components ask at once. The map lives at module scope\n * because the de-duplication has to span composable instances: a page with the editor open twice\n * builds two of these, and both would otherwise fire the same GET.\n */\nconst inFlight = new Map<string, Promise<void>>()\n\nexport const usePermissionConfigActions = (params: PermissionConfigActionsParams) => {\n const {\n client,\n system,\n entity = PERMISSION_CONFIG_ENTITY,\n endPoint = PERMISSION_CONFIG_ENDPOINT,\n autoFetch = true,\n } = params\n\n const { showErrorsDefault } = useAlerts()\n const { useFetchPermissionConfig } = usePermissionConfigApi({ client, system, entity, endPoint })\n const store = usePermissionConfigStore()\n\n const entry = computed(() => store.getEntry(system))\n const permissionConfig = computed(() => entry.value.config)\n const loadingPermissionConfig = computed(() => entry.value.loading)\n const isPermissionConfigInitialized = computed(() => entry.value.initialized)\n\n const runFetch = async () => {\n store.setLoading(system, true)\n try {\n const { execute } = useFetchPermissionConfig()\n store.setConfig(system, await execute())\n } catch (error) {\n showErrorsDefault(error)\n } finally {\n // Always, including on the failure path. Without it a backend that is down leaves the\n // editor's card spinning for the rest of the session.\n store.setLoading(system, false)\n inFlight.delete(system)\n }\n }\n\n const fetchPermissionConfig = async (force = false) => {\n if (!force && isPermissionConfigInitialized.value) return\n const running = inFlight.get(system)\n if (running) return running\n const started = runFetch()\n inFlight.set(system, started)\n return started\n }\n\n const { currentLanguageCode } = useLanguageSettings()\n\n /**\n * Falls back to the raw key, which is the contract the admins rely on: the ACL vocabulary lives\n * in the backend yaml, never in front-end i18n, so an untranslated action is shown as\n * `weather_location_ui` rather than as a blank cell.\n */\n const translatePermission = (group: PermissionTranslationGroup, key: string): string => {\n const translated = objectGetValueByPath(\n permissionConfig.value.translation,\n group + '.' + key + '.' + currentLanguageCode.value\n )\n\n return translated ?? key\n }\n\n if (autoFetch) void fetchPermissionConfig()\n\n return {\n permissionConfig,\n loadingPermissionConfig,\n isPermissionConfigInitialized,\n fetchPermissionConfig,\n translatePermission,\n resetPermissionConfig: () => store.resetSystem(system),\n }\n}\n","<script lang=\"ts\" setup>\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport { useAlerts } from '@/composables/system/alerts'\nimport { ROLE_SUPER_ADMIN } from '@/composables/auth/defineAuth'\nimport APermissionGrantEditor from '@/components/permission/APermissionGrantEditor.vue'\nimport APermissionValueChip from '@/components/permission/APermissionValueChip.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport { usePermissionConfigActions } from '@/labs/permissionConfig/permissionConfigActions'\nimport { Grant, type GrantType } from '@/model/valueObject/Grant'\nimport { GrantOrigin, GrantOriginDefault } from '@/model/valueObject/GrantOrigin'\nimport type { Permissions } from '@/types/Permission'\nimport { cloneDeep, isUndefined } from '@/utils/common'\nimport { objectDeletePropertyByPath, objectGetValueByPath, objectSetValueByPath } from '@/utils/object'\n\nconst props = withDefaults(\n defineProps<{\n client: AxiosClientFn\n /** Which backend's permission vocabulary to draw. Never a global -- see `permissionConfigActions`. */\n system: string\n entity?: string | undefined\n endPoint?: string | undefined\n /**\n * The grants the subject already has from roles and groups. Undefined means \"this model is a\n * group, not a user\": the origin column then reads every explicit grant as coming from the\n * group itself rather than from the user.\n */\n resolvedPermissions?: Permissions | undefined\n roles?: string[]\n /** One component for detail and edit (decision 27). Readonly drops the grant column. */\n readonly?: boolean\n }>(),\n {\n entity: undefined,\n endPoint: undefined,\n resolvedPermissions: undefined,\n roles: () => [],\n readonly: false,\n }\n)\n\nconst modelValue = defineModel<Permissions>({ required: true })\n\nconst { t } = useI18n()\nconst { showWarning } = useAlerts()\n\n// A clone, because every mutation below writes a whole object back through `modelValue`: editing\n// the model in place would not re-trigger the parent's watchers that guard unsaved changes.\nconst permissions = computed(() => cloneDeep(modelValue.value))\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { permissionConfig, loadingPermissionConfig, isPermissionConfigInitialized, translatePermission } =\n usePermissionConfigActions({\n client: props.client,\n system: props.system,\n entity: props.entity,\n endPoint: props.endPoint,\n })\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst changeGrant = (subject: string, action: string, grant?: GrantType) => {\n const permissionName = subject + '_' + action\n if (isUndefined(grant) && Object.hasOwn(permissions.value, permissionName)) {\n objectDeletePropertyByPath(permissions.value, permissionName)\n modelValue.value = permissions.value\n return\n }\n objectSetValueByPath(permissions.value, permissionName, grant)\n modelValue.value = permissions.value\n}\n\nconst getSelectedGrant = (subject: string, action: string) => {\n return objectGetValueByPath(permissions.value, subject + '_' + action)\n}\n\nconst getAvailableGrants = (subject: string, action: string): GrantType[] => {\n const grants = objectGetValueByPath(permissionConfig.value.config, subject + '.' + action + '.grants')\n if (isUndefined(grants)) {\n return permissionConfig.value.defaultGrants\n }\n return grants\n}\n\nconst RESTRICTIVE_GRANTS: GrantType[] = [Grant.Deny, Grant.AllowOwner]\n\nconst isExplicitRestrictiveGrant = (subject: string, action: string) => {\n const permissionName = subject + '_' + action\n if (!Object.hasOwn(permissions.value, permissionName)) return false\n return RESTRICTIVE_GRANTS.includes(getSelectedGrant(subject, action))\n}\n\n/**\n * \"Allow everything in this subject\", but it refuses to undo a deliberate restriction: an action\n * someone explicitly set to deny or owner-only is left alone and counted, and the count is\n * reported. Silently widening those is the one way this button could grant access nobody asked\n * for.\n */\nconst allowAllActions = (subject: string) => {\n let keptRestrictiveGrantCount = 0\n for (const action of Object.keys(permissionConfig.value.config[subject])) {\n if (!getAvailableGrants(subject, action).includes(Grant.Allow)) continue\n if (isExplicitRestrictiveGrant(subject, action)) {\n keptRestrictiveGrantCount++\n continue\n }\n objectSetValueByPath(permissions.value, subject + '_' + action, Grant.Allow)\n }\n modelValue.value = permissions.value\n if (keptRestrictiveGrantCount > 0) {\n showWarning(\n t('common.permissionEditor.restrictiveGrantKept', { count: keptRestrictiveGrantCount }, keptRestrictiveGrantCount)\n )\n }\n}\n\nconst hasSuperAdminRole = computed(() => props.roles.includes(ROLE_SUPER_ADMIN))\n\nconst getResolvedGrant = (subject: string, action: string) => {\n if (hasSuperAdminRole.value) return Grant.Allow\n const permissionName = subject + '_' + action\n if (props.resolvedPermissions && Object.hasOwn(props.resolvedPermissions, permissionName)) {\n return objectGetValueByPath(props.resolvedPermissions, permissionName)\n }\n if (Object.hasOwn(permissions.value, permissionName)) {\n return objectGetValueByPath(permissions.value, permissionName)\n }\n return Grant.Deny\n}\n\nconst getGrantOrigin = (subject: string, action: string) => {\n if (hasSuperAdminRole.value) return GrantOrigin.Role\n const permissionName = subject + '_' + action\n if (isUndefined(props.resolvedPermissions)) {\n if (Object.hasOwn(permissions.value, permissionName)) return GrantOrigin.Group\n return GrantOriginDefault\n }\n if (Object.hasOwn(permissions.value, permissionName)) return GrantOrigin.User\n if (Object.hasOwn(props.resolvedPermissions, permissionName)) return GrantOrigin.Group\n return GrantOriginDefault\n}\n</script>\n\n<template>\n <VCard :loading=\"loadingPermissionConfig\">\n <VCardText>\n <VTable v-if=\"isPermissionConfigInitialized\">\n <thead>\n <tr>\n <th>{{ t('common.anzuUser.table.grants') }}</th>\n <th v-if=\"!readonly\">\n {{ t('common.anzuUser.table.permissions') }}\n </th>\n <th>{{ t('common.anzuUser.table.resolvedPermissions') }}</th>\n </tr>\n </thead>\n <tbody>\n <template\n v-for=\"permissionSubject in Object.keys(permissionConfig.config)\"\n :key=\"permissionSubject\"\n >\n <tr>\n <td\n :colspan=\"readonly ? 2 : 3\"\n class=\"text-headline-small font-weight-bold\"\n >\n {{ translatePermission('subjects', permissionSubject) }}\n <VBtn\n v-if=\"!readonly\"\n size=\"x-small\"\n variant=\"text\"\n class=\"ml-2\"\n :data-cy=\"`permission-allow-all-${permissionSubject}`\"\n @click=\"allowAllActions(permissionSubject)\"\n >\n {{ t('common.permissionEditor.allowAll') }}\n </VBtn>\n </td>\n </tr>\n <tr\n v-for=\"permissionAction in Object.keys(permissionConfig.config[permissionSubject])\"\n :key=\"permissionAction\"\n >\n <td>\n {{ translatePermission('actions', permissionAction) }}\n <div class=\"text-body-small text-disabled\">{{ permissionSubject }}_{{ permissionAction }}</div>\n </td>\n <td v-if=\"!readonly\">\n <APermissionGrantEditor\n :available-grants=\"getAvailableGrants(permissionSubject, permissionAction)\"\n :selected-grant=\"getSelectedGrant(permissionSubject, permissionAction)\"\n @change=\"changeGrant(permissionSubject, permissionAction, $event)\"\n />\n </td>\n <td>\n <APermissionValueChip\n :grant=\"getResolvedGrant(permissionSubject, permissionAction)\"\n :grant-origin=\"getGrantOrigin(permissionSubject, permissionAction)\"\n />\n </td>\n </tr>\n </template>\n </tbody>\n </VTable>\n </VCardText>\n </VCard>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport { useAlerts } from '@/composables/system/alerts'\nimport { ROLE_SUPER_ADMIN } from '@/composables/auth/defineAuth'\nimport APermissionGrantEditor from '@/components/permission/APermissionGrantEditor.vue'\nimport APermissionValueChip from '@/components/permission/APermissionValueChip.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport { usePermissionConfigActions } from '@/labs/permissionConfig/permissionConfigActions'\nimport { Grant, type GrantType } from '@/model/valueObject/Grant'\nimport { GrantOrigin, GrantOriginDefault } from '@/model/valueObject/GrantOrigin'\nimport type { Permissions } from '@/types/Permission'\nimport { cloneDeep, isUndefined } from '@/utils/common'\nimport { objectDeletePropertyByPath, objectGetValueByPath, objectSetValueByPath } from '@/utils/object'\n\nconst props = withDefaults(\n defineProps<{\n client: AxiosClientFn\n /** Which backend's permission vocabulary to draw. Never a global -- see `permissionConfigActions`. */\n system: string\n entity?: string | undefined\n endPoint?: string | undefined\n /**\n * The grants the subject already has from roles and groups. Undefined means \"this model is a\n * group, not a user\": the origin column then reads every explicit grant as coming from the\n * group itself rather than from the user.\n */\n resolvedPermissions?: Permissions | undefined\n roles?: string[]\n /** One component for detail and edit (decision 27). Readonly drops the grant column. */\n readonly?: boolean\n }>(),\n {\n entity: undefined,\n endPoint: undefined,\n resolvedPermissions: undefined,\n roles: () => [],\n readonly: false,\n }\n)\n\nconst modelValue = defineModel<Permissions>({ required: true })\n\nconst { t } = useI18n()\nconst { showWarning } = useAlerts()\n\n// A clone, because every mutation below writes a whole object back through `modelValue`: editing\n// the model in place would not re-trigger the parent's watchers that guard unsaved changes.\nconst permissions = computed(() => cloneDeep(modelValue.value))\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { permissionConfig, loadingPermissionConfig, isPermissionConfigInitialized, translatePermission } =\n usePermissionConfigActions({\n client: props.client,\n system: props.system,\n entity: props.entity,\n endPoint: props.endPoint,\n })\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst changeGrant = (subject: string, action: string, grant?: GrantType) => {\n const permissionName = subject + '_' + action\n if (isUndefined(grant) && Object.hasOwn(permissions.value, permissionName)) {\n objectDeletePropertyByPath(permissions.value, permissionName)\n modelValue.value = permissions.value\n return\n }\n objectSetValueByPath(permissions.value, permissionName, grant)\n modelValue.value = permissions.value\n}\n\nconst getSelectedGrant = (subject: string, action: string) => {\n return objectGetValueByPath(permissions.value, subject + '_' + action)\n}\n\nconst getAvailableGrants = (subject: string, action: string): GrantType[] => {\n const grants = objectGetValueByPath(permissionConfig.value.config, subject + '.' + action + '.grants')\n if (isUndefined(grants)) {\n return permissionConfig.value.defaultGrants\n }\n return grants\n}\n\nconst RESTRICTIVE_GRANTS: GrantType[] = [Grant.Deny, Grant.AllowOwner]\n\nconst isExplicitRestrictiveGrant = (subject: string, action: string) => {\n const permissionName = subject + '_' + action\n if (!Object.hasOwn(permissions.value, permissionName)) return false\n return RESTRICTIVE_GRANTS.includes(getSelectedGrant(subject, action))\n}\n\n/**\n * \"Allow everything in this subject\", but it refuses to undo a deliberate restriction: an action\n * someone explicitly set to deny or owner-only is left alone and counted, and the count is\n * reported. Silently widening those is the one way this button could grant access nobody asked\n * for.\n */\nconst allowAllActions = (subject: string) => {\n let keptRestrictiveGrantCount = 0\n for (const action of Object.keys(permissionConfig.value.config[subject])) {\n if (!getAvailableGrants(subject, action).includes(Grant.Allow)) continue\n if (isExplicitRestrictiveGrant(subject, action)) {\n keptRestrictiveGrantCount++\n continue\n }\n objectSetValueByPath(permissions.value, subject + '_' + action, Grant.Allow)\n }\n modelValue.value = permissions.value\n if (keptRestrictiveGrantCount > 0) {\n showWarning(\n t('common.permissionEditor.restrictiveGrantKept', { count: keptRestrictiveGrantCount }, keptRestrictiveGrantCount)\n )\n }\n}\n\nconst hasSuperAdminRole = computed(() => props.roles.includes(ROLE_SUPER_ADMIN))\n\nconst getResolvedGrant = (subject: string, action: string) => {\n if (hasSuperAdminRole.value) return Grant.Allow\n const permissionName = subject + '_' + action\n if (props.resolvedPermissions && Object.hasOwn(props.resolvedPermissions, permissionName)) {\n return objectGetValueByPath(props.resolvedPermissions, permissionName)\n }\n if (Object.hasOwn(permissions.value, permissionName)) {\n return objectGetValueByPath(permissions.value, permissionName)\n }\n return Grant.Deny\n}\n\nconst getGrantOrigin = (subject: string, action: string) => {\n if (hasSuperAdminRole.value) return GrantOrigin.Role\n const permissionName = subject + '_' + action\n if (isUndefined(props.resolvedPermissions)) {\n if (Object.hasOwn(permissions.value, permissionName)) return GrantOrigin.Group\n return GrantOriginDefault\n }\n if (Object.hasOwn(permissions.value, permissionName)) return GrantOrigin.User\n if (Object.hasOwn(props.resolvedPermissions, permissionName)) return GrantOrigin.Group\n return GrantOriginDefault\n}\n</script>\n\n<template>\n <VCard :loading=\"loadingPermissionConfig\">\n <VCardText>\n <VTable v-if=\"isPermissionConfigInitialized\">\n <thead>\n <tr>\n <th>{{ t('common.anzuUser.table.grants') }}</th>\n <th v-if=\"!readonly\">\n {{ t('common.anzuUser.table.permissions') }}\n </th>\n <th>{{ t('common.anzuUser.table.resolvedPermissions') }}</th>\n </tr>\n </thead>\n <tbody>\n <template\n v-for=\"permissionSubject in Object.keys(permissionConfig.config)\"\n :key=\"permissionSubject\"\n >\n <tr>\n <td\n :colspan=\"readonly ? 2 : 3\"\n class=\"text-headline-small font-weight-bold\"\n >\n {{ translatePermission('subjects', permissionSubject) }}\n <VBtn\n v-if=\"!readonly\"\n size=\"x-small\"\n variant=\"text\"\n class=\"ml-2\"\n :data-cy=\"`permission-allow-all-${permissionSubject}`\"\n @click=\"allowAllActions(permissionSubject)\"\n >\n {{ t('common.permissionEditor.allowAll') }}\n </VBtn>\n </td>\n </tr>\n <tr\n v-for=\"permissionAction in Object.keys(permissionConfig.config[permissionSubject])\"\n :key=\"permissionAction\"\n >\n <td>\n {{ translatePermission('actions', permissionAction) }}\n <div class=\"text-body-small text-disabled\">{{ permissionSubject }}_{{ permissionAction }}</div>\n </td>\n <td v-if=\"!readonly\">\n <APermissionGrantEditor\n :available-grants=\"getAvailableGrants(permissionSubject, permissionAction)\"\n :selected-grant=\"getSelectedGrant(permissionSubject, permissionAction)\"\n @change=\"changeGrant(permissionSubject, permissionAction, $event)\"\n />\n </td>\n <td>\n <APermissionValueChip\n :grant=\"getResolvedGrant(permissionSubject, permissionAction)\"\n :grant-origin=\"getGrantOrigin(permissionSubject, permissionAction)\"\n />\n </td>\n </tr>\n </template>\n </tbody>\n </VTable>\n </VCardText>\n </VCard>\n</template>\n","import type { AxiosClientFn } from '@/labs/api/client'\nimport { useApiCommand, useApiRequest } from '@/labs/api/useApiRequest'\nimport { useApiFetchByIds } from '@/labs/api/useApiFetchByIds'\nimport { useApiFetchList } from '@/labs/api/useApiFetchList'\nimport type { PermissionGroup } from '@/types/PermissionGroup'\n\nexport const PERMISSION_GROUP_ENTITY = 'permissionGroup'\nexport const PERMISSION_GROUP_ENDPOINT = '/adm/v1/permission-group'\n\nexport interface PermissionGroupApiParams {\n client: AxiosClientFn\n /**\n * Both of these reach every `useApiRequest` this builds, and both have to be taken rather than\n * assumed. admin-cms is the proof: its `permissionGroupApi.ts` declared `SYSTEM_COMMON` at the\n * top and then sent `SYSTEM_CMS` on one of its four calls, so the same file reported two\n * different systems for the same endpoint. A copy of that file per system multiplies the bug;\n * one file that is told which system it is cannot have it.\n */\n system: string\n entity?: string\n /**\n * i18n scope for a server-side validation failure, which is a different question from which\n * backend answered. `AnzuApiValidationError` builds `<system>.<entity>.model.<field>` keys, and\n * for a component shared by nine backends the labels are always the library's own `common.*` --\n * `weather.anzuUser.model.email` exists nowhere. Defaults to `common` plus the entity;\n * contentHub passes its own pair to keep the keys it already has.\n */\n validationSystem?: string\n validationEntity?: string\n endPoint?: string\n}\n\nexport const usePermissionGroupApi = ({\n client,\n system,\n entity = PERMISSION_GROUP_ENTITY,\n validationSystem = 'common',\n validationEntity = entity,\n endPoint = PERMISSION_GROUP_ENDPOINT,\n}: PermissionGroupApiParams) => {\n const scope = { validationSystem, validationEntity }\n const useFetchPermissionGroupListByIds = () =>\n useApiFetchByIds<PermissionGroup>({ client, system, entity, ...scope, urlTemplate: endPoint })\n\n const useFetchPermissionGroupList = () =>\n useApiFetchList<PermissionGroup>({ client, system, entity, ...scope, urlTemplate: endPoint })\n\n const useFetchPermissionGroup = () =>\n useApiRequest<PermissionGroup, null>({\n client,\n ...scope,\n method: 'GET',\n system,\n entity,\n urlTemplate: endPoint + '/:id',\n })\n\n const useCreatePermissionGroup = () =>\n useApiRequest<PermissionGroup, PermissionGroup>({\n client,\n ...scope,\n method: 'POST',\n system,\n entity,\n urlTemplate: endPoint,\n })\n\n const useUpdatePermissionGroup = () =>\n useApiRequest<PermissionGroup, PermissionGroup>({\n client,\n ...scope,\n method: 'PUT',\n system,\n entity,\n urlTemplate: endPoint + '/:id',\n })\n\n const useDeletePermissionGroup = () =>\n useApiCommand<null>({\n client,\n ...scope,\n method: 'DELETE',\n system,\n entity,\n urlTemplate: endPoint + '/:id',\n })\n\n return {\n useFetchPermissionGroupListByIds,\n useFetchPermissionGroupList,\n useFetchPermissionGroup,\n useCreatePermissionGroup,\n useUpdatePermissionGroup,\n useDeletePermissionGroup,\n }\n}\n","import { defineCached } from '@/composables/system/defineCached'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport {\n PERMISSION_GROUP_ENDPOINT,\n PERMISSION_GROUP_ENTITY,\n usePermissionGroupApi,\n} from '@/labs/permissionGroup/permissionGroupApi'\nimport type { IntegerId } from '@/types/common'\nimport type { PermissionGroup, PermissionGroupMinimal } from '@/types/PermissionGroup'\n\nexport interface CachedPermissionGroupsParams {\n client: AxiosClientFn\n system: string\n entity?: string\n endPoint?: string\n}\n\ntype Registry = ReturnType<typeof buildRegistry>\n\n/**\n * One cache per system, not one shared by all of them.\n *\n * Permission group ids are per system -- group 42 in weather and group 42 in blog are different\n * rows -- and the admins' caches were keyed by id alone. That was harmless while an admin only\n * ever talked to one backend, and stops being harmless the moment a cross-system page renders a\n * chip for both: whichever answered first won, and the other system's group was drawn under the\n * wrong title.\n */\nconst registries = new Map<string, Registry>()\n\nconst mapFullToMinimal = (permissionGroup: PermissionGroup): PermissionGroupMinimal => ({\n id: permissionGroup.id,\n title: permissionGroup.title,\n permissions: permissionGroup.permissions,\n})\n\nconst mapIdToMinimal = (id: IntegerId): PermissionGroupMinimal => ({\n id,\n title: '',\n permissions: {},\n})\n\nconst buildRegistry = (params: Required<CachedPermissionGroupsParams>) => {\n const { useFetchPermissionGroupListByIds } = usePermissionGroupApi(params)\n\n return defineCached<IntegerId, PermissionGroup, PermissionGroupMinimal>(\n mapFullToMinimal,\n mapIdToMinimal,\n async (ids: IntegerId[]) => {\n const { execute } = useFetchPermissionGroupListByIds()\n return execute(ids)\n }\n )\n}\n\nexport const useCachedPermissionGroups = (params: CachedPermissionGroupsParams) => {\n const resolved = {\n client: params.client,\n system: params.system,\n entity: params.entity ?? PERMISSION_GROUP_ENTITY,\n endPoint: params.endPoint ?? PERMISSION_GROUP_ENDPOINT,\n }\n\n let registry = registries.get(resolved.system)\n if (!registry) {\n registry = buildRegistry(resolved)\n registries.set(resolved.system, registry)\n }\n\n const { cache, toFetch, fetch, immediateFetch, add, addManual, addManualMinimal, has, get, isLoaded, clear } =\n registry\n\n return {\n cachedPermissionGroups: cache,\n toFetchCachedPermissionGroups: toFetch,\n fetchCachedPermissionGroups: fetch,\n immediateFetchCachedPermissionGroups: immediateFetch,\n addToCachedPermissionGroups: add,\n addManualToCachedPermissionGroups: addManual,\n addManualMinimalToCachedPermissionGroups: addManualMinimal,\n hasCachedPermissionGroup: has,\n getCachedPermissionGroup: get,\n isLoadedCachedPermissionGroup: isLoaded,\n clearCachedPermissionGroups: clear,\n }\n}\n\n/** Test seam. Registries are module state and would otherwise leak between cases. */\nexport const resetCachedPermissionGroupRegistries = () => {\n registries.clear()\n}\n","import type { AxiosClientFn } from '@/labs/api/client'\nimport { useCachedPermissionGroups } from '@/labs/permissionGroup/cachedPermissionGroups'\nimport type { AnzuUser } from '@/types/AnzuUser'\nimport type { IntegerId } from '@/types/common'\nimport type { Permissions } from '@/types/Permission'\nimport { isUndefined } from '@/utils/common'\nimport { objectGetValueByPath, objectSetValueByPath } from '@/utils/object'\n\nexport interface PermissionActionsParams {\n client: AxiosClientFn\n system: string\n entity?: string\n endPoint?: string\n}\n\n/**\n * Builds the grant map a user would end up with, for the editor's \"resolved\" column, out of what\n * the page already holds: the groups the user is in plus the grants set on the user directly.\n *\n * It is not the backend's `resolvedPermissions`. That one arrives with the record and includes\n * roles; this one is what the screen would send if saved now, so the editor can show the effect of\n * a group the operator has just ticked without a round trip.\n */\nexport const usePermissionActions = (params: PermissionActionsParams) => {\n const { getCachedPermissionGroup } = useCachedPermissionGroups(params)\n\n const resolveGroupPermissions = (permissionGroupIds: IntegerId[]): Permissions => {\n const permissions: Permissions = {}\n const permissionGroups = permissionGroupIds.map((id) => getCachedPermissionGroup(id))\n\n for (const permissionGroup of permissionGroups) {\n if (isUndefined(permissionGroup)) continue\n for (const permissionName in permissionGroup.permissions) {\n const grant = objectGetValueByPath(permissionGroup.permissions, permissionName)\n if (!Object.hasOwn(permissions, permissionName)) {\n objectSetValueByPath(permissions, permissionName, grant)\n continue\n }\n // Grants are ordered deny < allowOwner < allow, so the widest group wins. Two groups that\n // disagree do not cancel out.\n if (objectGetValueByPath(permissions, permissionName) < grant) {\n objectSetValueByPath(permissions, permissionName, grant)\n }\n }\n }\n return permissions\n }\n\n /** A grant set on the user directly always beats whatever the groups say. */\n const resolvePermissions = (user: AnzuUser): Permissions => ({\n ...resolveGroupPermissions(user.permissionGroups),\n ...user.permissions,\n })\n\n return {\n resolvePermissions,\n resolveGroupPermissions,\n }\n}\n","<script lang=\"ts\" setup>\nimport { inject } from 'vue'\nimport AFilterString from '@/labs/filters/AFilterString.vue'\nimport AFilterWrapper from '@/labs/filters/AFilterWrapper.vue'\nimport { FilterConfigKey, FilterDataKey } from '@/labs/filters/filterInjectionKeys'\nimport { isUndefined } from '@/utils/common'\n\nconst emit = defineEmits<{\n (e: 'submit'): void\n (e: 'reset'): void\n}>()\n\nconst filterConfig = inject(FilterConfigKey)\nconst filterData = inject(FilterDataKey)\nif (isUndefined(filterConfig) || isUndefined(filterData)) {\n throw new Error('[APermissionGroupFilter] Incorrect provide/inject config.')\n}\n</script>\n\n<template>\n <AFilterWrapper\n @submit=\"emit('submit')\"\n @reset=\"emit('reset')\"\n >\n <template #search>\n <AFilterString name=\"title\" />\n </template>\n </AFilterWrapper>\n</template>\n","<script lang=\"ts\" setup>\nimport { inject } from 'vue'\nimport AFilterString from '@/labs/filters/AFilterString.vue'\nimport AFilterWrapper from '@/labs/filters/AFilterWrapper.vue'\nimport { FilterConfigKey, FilterDataKey } from '@/labs/filters/filterInjectionKeys'\nimport { isUndefined } from '@/utils/common'\n\nconst emit = defineEmits<{\n (e: 'submit'): void\n (e: 'reset'): void\n}>()\n\nconst filterConfig = inject(FilterConfigKey)\nconst filterData = inject(FilterDataKey)\nif (isUndefined(filterConfig) || isUndefined(filterData)) {\n throw new Error('[APermissionGroupFilter] Incorrect provide/inject config.')\n}\n</script>\n\n<template>\n <AFilterWrapper\n @submit=\"emit('submit')\"\n @reset=\"emit('reset')\"\n >\n <template #search>\n <AFilterString name=\"title\" />\n </template>\n </AFilterWrapper>\n</template>\n","import { acceptHMRUpdate, defineStore } from 'pinia'\nimport { ref } from 'vue'\nimport { usePermissionGroupFactory } from '@/model/factory/PermissionGroupFactory'\nimport type { PermissionGroup } from '@/types/PermissionGroup'\n\n/**\n * A single open group, not a map keyed by system.\n *\n * Decision 9 of the plan: one system is one route and one view, and there is no cross-system view\n * of permission groups at all. Only one group is ever open, so a per-system map would be state\n * nothing reads -- unlike the config store next door, which really is asked about several systems\n * at once by the cross-system user pages.\n */\nexport const usePermissionGroupOneStore = defineStore('labsPermissionGroupOneStore', () => {\n const { createPermissionGroup } = usePermissionGroupFactory()\n\n const permissionGroup = ref<PermissionGroup>(createPermissionGroup())\n const loadingPermissionGroup = ref(false)\n\n function setPermissionGroup(newPermissionGroup: PermissionGroup) {\n permissionGroup.value = newPermissionGroup\n }\n\n function setLoadingPermissionGroup(loading: boolean) {\n loadingPermissionGroup.value = loading\n }\n\n function reset() {\n permissionGroup.value = createPermissionGroup()\n loadingPermissionGroup.value = false\n }\n\n return {\n permissionGroup,\n loadingPermissionGroup,\n setPermissionGroup,\n setLoadingPermissionGroup,\n reset,\n }\n})\n\nif (import.meta.hot) {\n import.meta.hot.accept(acceptHMRUpdate(usePermissionGroupOneStore, import.meta.hot))\n}\n","import { ref, type Ref } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport useVuelidate from '@vuelidate/core'\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 { useCachedPermissionGroups } from '@/labs/permissionGroup/cachedPermissionGroups'\nimport {\n PERMISSION_GROUP_ENDPOINT,\n PERMISSION_GROUP_ENTITY,\n usePermissionGroupApi,\n} from '@/labs/permissionGroup/permissionGroupApi'\nimport { usePermissionGroupOneStore } from '@/labs/permissionGroup/permissionGroupStore'\nimport type { IntegerId } from '@/types/common'\nimport type { PermissionGroup } from '@/types/PermissionGroup'\nimport type { ValueObjectOption } from '@/types/ValueObject'\n\nexport interface PermissionGroupActionsParams {\n client: AxiosClientFn\n system: string\n entity?: string\n /**\n * i18n scope for a server-side validation failure, which is a different question from which\n * backend answered. `AnzuApiValidationError` builds `<system>.<entity>.model.<field>` keys, so\n * the pair has to name the namespace the *labels* live under. It defaults to `common` +\n * `permissionGroup`, which is where the shared form's own fields are; an admin whose form carries\n * system fields as well passes its own pair, and keeps the base keys beside them.\n */\n validationSystem?: string\n validationEntity?: string\n endPoint?: string\n}\n\n/**\n * Everything below is per instance. The admins kept `listLoading`, `detailLoading` and\n * `datatableHiddenColumns` at module scope, which is a single set of flags shared by every caller\n * -- fine for one list on one backend, wrong for a library a page may instantiate twice.\n */\nexport const usePermissionGroupActions = (params: PermissionGroupActionsParams) => {\n const { client, system, entity = PERMISSION_GROUP_ENTITY, endPoint = PERMISSION_GROUP_ENDPOINT } = params\n\n const { showValidationError, showRecordWas, showErrorsDefault } = useAlerts()\n const {\n useFetchPermissionGroupList,\n useFetchPermissionGroup,\n useDeletePermissionGroup,\n useUpdatePermissionGroup,\n useCreatePermissionGroup,\n useFetchPermissionGroupListByIds,\n } = usePermissionGroupApi({\n client,\n system,\n entity,\n validationSystem: params.validationSystem,\n validationEntity: params.validationEntity,\n endPoint,\n })\n\n const { execute: executeList, abort: abortList } = useFetchPermissionGroupList()\n const { execute: executeFetchByIds } = useFetchPermissionGroupListByIds()\n\n const datatableHiddenColumns = ref<Array<string>>([])\n const permissionGroupList = ref<PermissionGroup[]>([])\n const loadingPermissionGroupList = ref(false)\n\n // Same reason as in `useLogListActions`: axios rejects an aborted request with `CanceledError`,\n // which reaches the catch like any other failure. Without a token the user gets an error alert\n // for a navigation they performed themselves.\n let listGeneration = 0\n\n const fetchPermissionGroupList = async (\n pagination: Ref<Pagination>,\n filterData: FilterData<any>,\n filterConfig: FilterConfig<any>\n ) => {\n const token = ++listGeneration\n loadingPermissionGroupList.value = true\n try {\n const items = await executeList(pagination, filterData, filterConfig)\n if (token !== listGeneration) return\n permissionGroupList.value = items\n } catch (error) {\n if (token !== listGeneration) return\n permissionGroupList.value = []\n showErrorsDefault(error)\n } finally {\n if (token === listGeneration) loadingPermissionGroupList.value = false\n }\n }\n\n const cancelPermissionGroupList = () => {\n listGeneration++\n abortList()\n }\n\n const permissionGroupOneStore = usePermissionGroupOneStore()\n const { permissionGroup, loadingPermissionGroup } = storeToRefs(permissionGroupOneStore)\n\n const fetchPermissionGroup = async (id: IntegerId) => {\n permissionGroupOneStore.setLoadingPermissionGroup(true)\n try {\n const { execute } = useFetchPermissionGroup()\n permissionGroupOneStore.setPermissionGroup(await execute({ urlParams: { id } }))\n } catch (error) {\n showErrorsDefault(error)\n } finally {\n permissionGroupOneStore.setLoadingPermissionGroup(false)\n }\n }\n\n const loadingDeletePermissionGroup = ref(false)\n /** Resolves true when the row is gone, so the caller can decide where to navigate. */\n const deletePermissionGroup = async (id: IntegerId): Promise<boolean> => {\n try {\n loadingDeletePermissionGroup.value = true\n const { execute } = useDeletePermissionGroup()\n await execute({ urlParams: { id } })\n showRecordWas('deleted')\n return true\n } catch (error) {\n showErrorsDefault(error)\n return false\n } finally {\n loadingDeletePermissionGroup.value = false\n }\n }\n\n const v$ = useVuelidate()\n\n const loadingUpdatePermissionGroup = ref(false)\n const updatePermissionGroup = async (): Promise<boolean> => {\n try {\n loadingUpdatePermissionGroup.value = true\n v$.value.$touch()\n if (v$.value.$invalid) {\n showValidationError()\n return false\n }\n const { execute } = useUpdatePermissionGroup()\n await execute({\n urlParams: { id: permissionGroupOneStore.permissionGroup.id },\n body: permissionGroupOneStore.permissionGroup,\n })\n showRecordWas('updated')\n return true\n } catch (error) {\n showErrorsDefault(error)\n return false\n } finally {\n loadingUpdatePermissionGroup.value = false\n }\n }\n\n const loadingCreatePermissionGroup = ref(false)\n /** Resolves the created row, so the caller can route to its detail; null on failure. */\n const createPermissionGroup = async (): Promise<PermissionGroup | null> => {\n try {\n loadingCreatePermissionGroup.value = true\n v$.value.$touch()\n if (v$.value.$invalid) {\n showValidationError()\n return null\n }\n const { execute } = useCreatePermissionGroup()\n const created = await execute({ body: permissionGroupOneStore.permissionGroup })\n showRecordWas('created')\n return created\n } catch (error) {\n showErrorsDefault(error)\n return null\n } finally {\n loadingCreatePermissionGroup.value = false\n }\n }\n\n const { addManualToCachedPermissionGroups } = useCachedPermissionGroups({ client, system, entity, endPoint })\n\n const fetchPermissionGroupOptions = async (\n pagination: Ref<Pagination>,\n filterData: FilterData<any>,\n filterConfig: FilterConfig<any>\n ): Promise<ValueObjectOption<IntegerId>[]> => {\n const permissionGroups = await executeList(pagination, filterData, filterConfig)\n permissionGroups.forEach((item) => addManualToCachedPermissionGroups(item))\n\n return permissionGroups.map((item) => ({ title: item.title, value: item.id }))\n }\n\n const fetchPermissionGroupOptionsByIds = async (ids: IntegerId[]): Promise<ValueObjectOption<IntegerId>[]> => {\n const permissionGroups = await executeFetchByIds(ids)\n permissionGroups.forEach((item) => addManualToCachedPermissionGroups(item))\n\n return permissionGroups.map((item) => ({ title: item.title, value: item.id }))\n }\n\n return {\n datatableHiddenColumns,\n permissionGroupList,\n loadingPermissionGroupList,\n fetchPermissionGroupList,\n cancelPermissionGroupList,\n permissionGroup,\n loadingPermissionGroup,\n fetchPermissionGroup,\n createPermissionGroup,\n updatePermissionGroup,\n deletePermissionGroup,\n loadingCreatePermissionGroup,\n loadingUpdatePermissionGroup,\n loadingDeletePermissionGroup,\n fetchPermissionGroupOptions,\n fetchPermissionGroupOptionsByIds,\n resetPermissionGroupStore: permissionGroupOneStore.reset,\n }\n}\n","import {\n createFilter,\n createFilterStore,\n type FilterConfig,\n type FilterData,\n type MakeFilterOption,\n} from '@/labs/filters/filterFactory'\nimport { PERMISSION_GROUP_ENTITY } from '@/labs/permissionGroup/permissionGroupApi'\n\nconst permissionGroupListFields = [\n { name: 'id' as const, default: null, type: 'integer' },\n { name: 'title' as const, variant: 'startsWith', default: null, type: 'string', render: { skip: true } },\n] satisfies readonly MakeFilterOption[]\n\ntype ListFields = typeof permissionGroupListFields\n\n/**\n * One filter store per system.\n *\n * The admins kept theirs at module scope, which is correct while an admin has a single backend and\n * wrong here: admin-inhouse has four, and a shared store would carry a title typed under `weather`\n * straight into the `brick` list.\n */\nconst listStores = new Map<string, FilterData<ListFields>>()\n\n/**\n * Local-storage key for the list filter. Named rather than derived so it cannot collide with the\n * per-admin views this replaces, which store under `tableFilter_common_permissionGroup`; while\n * both versions are on screen they must not share remembered filters (rule 8.0).\n */\nexport const permissionGroupFilterStorageKey = (system: string) => `labsPermissionGroup_${system}`\n\nexport function usePermissionGroupListFilter(system: string): {\n filterConfig: FilterConfig<ListFields>\n filterData: FilterData<ListFields>\n} {\n let store = listStores.get(system)\n if (!store) {\n store = createFilterStore(permissionGroupListFields)\n listStores.set(system, store)\n }\n\n // `system: 'common'` is the translation namespace, not the backend: the labels live in the\n // library's own `common.permissionGroup.filter.*` and are the same text whichever backend the\n // rows came from. The backend identity is what keys the store above.\n const { filterConfig, filterData } = createFilter(permissionGroupListFields, store, {\n system: 'common',\n subject: PERMISSION_GROUP_ENTITY,\n })\n\n return { filterConfig, filterData }\n}\n\nexport function usePermissionGroupInnerFilter() {\n // `title` is the autocomplete's `filter-by-field`. Built per call and never registered: an\n // autocomplete's filter is scratch state for one open menu, and two of them on a page must not\n // type into each other.\n const innerFields = [\n { name: 'title' as const, variant: 'startsWith', default: null, type: 'string' },\n ] satisfies readonly MakeFilterOption[]\n\n const { filterConfig, filterData } = createFilter(innerFields, createFilterStore(innerFields), {\n system: 'common',\n subject: PERMISSION_GROUP_ENTITY,\n })\n\n return { filterConfig, filterData }\n}\n\n/** Test seam; the list stores are module state. */\nexport const resetPermissionGroupListFilters = () => {\n listStores.clear()\n}\n","<script lang=\"ts\" setup>\nimport { onBeforeUnmount, onMounted, provide } from 'vue'\nimport type { RouteLocationRaw } from 'vue-router'\nimport { useDebounceFn } from '@vueuse/core'\nimport ADatatableConfigButton from '@/components/ADatatableConfigButton.vue'\nimport ADatetime from '@/components/datetime/ADatetime.vue'\nimport ATableCopyIdButton from '@/components/buttons/table/ATableCopyIdButton.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport ADatatableOrdering from '@/labs/filters/ADatatableOrdering.vue'\nimport ADatatablePagination from '@/labs/filters/ADatatablePagination.vue'\nimport { createDatatableColumnsConfig } from '@/labs/filters/datatableColumns'\nimport { useFilterHelpers } from '@/labs/filters/filterFactory'\nimport { DatatablePaginationKey, FilterConfigKey, FilterDataKey } from '@/labs/filters/filterInjectionKeys'\nimport { usePagination } from '@/labs/filters/pagination'\nimport APermissionGroupFilter from '@/labs/permissionGroup/APermissionGroupFilter.vue'\nimport { usePermissionGroupActions } from '@/labs/permissionGroup/permissionGroupActions'\nimport { PERMISSION_GROUP_ENTITY } from '@/labs/permissionGroup/permissionGroupApi'\nimport {\n permissionGroupFilterStorageKey,\n usePermissionGroupListFilter,\n} from '@/labs/permissionGroup/permissionGroupFilter'\nimport { SORT_BY_ID } from '@/composables/system/datatableColumns'\nimport type { PermissionGroup } from '@/types/PermissionGroup'\n\ntype DatatableItem = PermissionGroup\n\nconst props = withDefaults(\n defineProps<{\n client: AxiosClientFn\n /** Backend identity: keys the filter store and the cache, and reaches every api call. */\n system: string\n entity?: string | undefined\n endPoint?: string | undefined\n /** Where a row and the detail button go. The library cannot know an app's typed route names. */\n detailRoute: (permissionGroup: PermissionGroup) => RouteLocationRaw\n editRoute: (permissionGroup: PermissionGroup) => RouteLocationRaw\n /** Already-evaluated ACL. A boolean, not an `AclValue`: see 5.1 of the plan. */\n canUpdate?: boolean\n }>(),\n {\n entity: undefined,\n endPoint: undefined,\n canUpdate: false,\n }\n)\n\nconst emit = defineEmits<{\n (e: 'rowClick', permissionGroup: PermissionGroup): void\n}>()\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { filterConfig, filterData } = usePermissionGroupListFilter(props.system)\nprovide(FilterConfigKey, filterConfig)\nprovide(FilterDataKey, filterData)\n\nconst {\n fetchPermissionGroupList,\n cancelPermissionGroupList,\n permissionGroupList,\n loadingPermissionGroupList,\n datatableHiddenColumns,\n} = usePermissionGroupActions({\n client: props.client,\n system: props.system,\n entity: props.entity,\n endPoint: props.endPoint,\n})\n\nconst { resetFilter, submitFilter, loadStoredFilters } = useFilterHelpers(filterData, filterConfig, {\n storeFiltersLocalStorage: permissionGroupFilterStorageKey(props.system),\n})\n\nconst { pagination } = usePagination(SORT_BY_ID)\nprovide(DatatablePaginationKey, pagination)\n\nconst { columnsVisible, columnsAll, columnsHidden } = createDatatableColumnsConfig(\n [\n { key: 'id' },\n { key: 'title' },\n { key: 'description' },\n { key: 'permissions' },\n { key: 'createdAt' },\n { key: 'modifiedAt' },\n ],\n datatableHiddenColumns,\n 'common',\n PERMISSION_GROUP_ENTITY,\n { storeColumnsLocalStorage: `table_labs_permissionGroup_${props.system}` }\n)\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst getList = useDebounceFn(async () => {\n await fetchPermissionGroupList(pagination, filterData, filterConfig)\n})\n\n// Named here rather than called inline in the template: `pagination` is a ref, and the template\n// would hand these its unwrapped value.\nconst submitFilterAction = () => submitFilter(pagination, getList)\nconst resetFilterAction = () => resetFilter(pagination, getList)\n\nconst onRowClick = (_event: unknown, { item }: { item: DatatableItem }) => {\n if (item.id) emit('rowClick', item)\n}\n\nonMounted(() => {\n loadStoredFilters(pagination, getList)\n})\n\nonBeforeUnmount(() => {\n // Order matters: a pending debounce would otherwise fire after the abort and start a request\n // nothing is left to receive.\n getList.cancel()\n cancelPermissionGroupList()\n})\n\ndefineExpose({\n refresh: getList,\n resetFilter: resetFilterAction,\n submitFilter: submitFilterAction,\n})\n</script>\n\n<template>\n <div>\n <APermissionGroupFilter\n @submit=\"submitFilterAction\"\n @reset=\"resetFilterAction\"\n />\n <div>\n <div class=\"d-flex align-center\">\n <VSpacer />\n <ADatatableOrdering\n variant=\"id\"\n @sort-by-change=\"submitFilterAction\"\n />\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=\"permissionGroupList\"\n :items-length=\"permissionGroupList.length\"\n :loading=\"loadingPermissionGroupList\"\n item-value=\"id\"\n @click:row=\"onRowClick\"\n >\n <template #item.permissions=\"{ item }: { item: DatatableItem }\">\n <VChip>{{ Object.keys(item.permissions).length }}</VChip>\n </template>\n <template #item.createdAt=\"{ item }: { item: DatatableItem }\">\n <ADatetime :date-time=\"item.createdAt\" />\n <!-- The user chip is per app: its cache, its route, its own `_system`. An app without\n one leaves the slot empty, and then there is no line break either. -->\n <template v-if=\"$slots.createdBy\">\n <br />\n <slot\n :id=\"item.createdBy\"\n name=\"createdBy\"\n />\n </template>\n </template>\n <template #item.modifiedAt=\"{ item }: { item: DatatableItem }\">\n <ADatetime :date-time=\"item.modifiedAt\" />\n <template v-if=\"$slots.modifiedBy\">\n <br />\n <slot\n :id=\"item.modifiedBy\"\n name=\"modifiedBy\"\n />\n </template>\n </template>\n <template #item.actions=\"{ item }: { item: DatatableItem }\">\n <div class=\"d-flex justify-end\">\n <ATableCopyIdButton :id=\"item.id\" />\n <!--\n `:to` rather than a click handler, so middle-click and open-in-new-tab work on a list\n people open several rows from.\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 <VBtn\n v-if=\"canUpdate\"\n :to=\"editRoute(item)\"\n class=\"ml-1\"\n icon=\"mdi-pencil\"\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 </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { onBeforeUnmount, onMounted, provide } from 'vue'\nimport type { RouteLocationRaw } from 'vue-router'\nimport { useDebounceFn } from '@vueuse/core'\nimport ADatatableConfigButton from '@/components/ADatatableConfigButton.vue'\nimport ADatetime from '@/components/datetime/ADatetime.vue'\nimport ATableCopyIdButton from '@/components/buttons/table/ATableCopyIdButton.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport ADatatableOrdering from '@/labs/filters/ADatatableOrdering.vue'\nimport ADatatablePagination from '@/labs/filters/ADatatablePagination.vue'\nimport { createDatatableColumnsConfig } from '@/labs/filters/datatableColumns'\nimport { useFilterHelpers } from '@/labs/filters/filterFactory'\nimport { DatatablePaginationKey, FilterConfigKey, FilterDataKey } from '@/labs/filters/filterInjectionKeys'\nimport { usePagination } from '@/labs/filters/pagination'\nimport APermissionGroupFilter from '@/labs/permissionGroup/APermissionGroupFilter.vue'\nimport { usePermissionGroupActions } from '@/labs/permissionGroup/permissionGroupActions'\nimport { PERMISSION_GROUP_ENTITY } from '@/labs/permissionGroup/permissionGroupApi'\nimport {\n permissionGroupFilterStorageKey,\n usePermissionGroupListFilter,\n} from '@/labs/permissionGroup/permissionGroupFilter'\nimport { SORT_BY_ID } from '@/composables/system/datatableColumns'\nimport type { PermissionGroup } from '@/types/PermissionGroup'\n\ntype DatatableItem = PermissionGroup\n\nconst props = withDefaults(\n defineProps<{\n client: AxiosClientFn\n /** Backend identity: keys the filter store and the cache, and reaches every api call. */\n system: string\n entity?: string | undefined\n endPoint?: string | undefined\n /** Where a row and the detail button go. The library cannot know an app's typed route names. */\n detailRoute: (permissionGroup: PermissionGroup) => RouteLocationRaw\n editRoute: (permissionGroup: PermissionGroup) => RouteLocationRaw\n /** Already-evaluated ACL. A boolean, not an `AclValue`: see 5.1 of the plan. */\n canUpdate?: boolean\n }>(),\n {\n entity: undefined,\n endPoint: undefined,\n canUpdate: false,\n }\n)\n\nconst emit = defineEmits<{\n (e: 'rowClick', permissionGroup: PermissionGroup): void\n}>()\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { filterConfig, filterData } = usePermissionGroupListFilter(props.system)\nprovide(FilterConfigKey, filterConfig)\nprovide(FilterDataKey, filterData)\n\nconst {\n fetchPermissionGroupList,\n cancelPermissionGroupList,\n permissionGroupList,\n loadingPermissionGroupList,\n datatableHiddenColumns,\n} = usePermissionGroupActions({\n client: props.client,\n system: props.system,\n entity: props.entity,\n endPoint: props.endPoint,\n})\n\nconst { resetFilter, submitFilter, loadStoredFilters } = useFilterHelpers(filterData, filterConfig, {\n storeFiltersLocalStorage: permissionGroupFilterStorageKey(props.system),\n})\n\nconst { pagination } = usePagination(SORT_BY_ID)\nprovide(DatatablePaginationKey, pagination)\n\nconst { columnsVisible, columnsAll, columnsHidden } = createDatatableColumnsConfig(\n [\n { key: 'id' },\n { key: 'title' },\n { key: 'description' },\n { key: 'permissions' },\n { key: 'createdAt' },\n { key: 'modifiedAt' },\n ],\n datatableHiddenColumns,\n 'common',\n PERMISSION_GROUP_ENTITY,\n { storeColumnsLocalStorage: `table_labs_permissionGroup_${props.system}` }\n)\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst getList = useDebounceFn(async () => {\n await fetchPermissionGroupList(pagination, filterData, filterConfig)\n})\n\n// Named here rather than called inline in the template: `pagination` is a ref, and the template\n// would hand these its unwrapped value.\nconst submitFilterAction = () => submitFilter(pagination, getList)\nconst resetFilterAction = () => resetFilter(pagination, getList)\n\nconst onRowClick = (_event: unknown, { item }: { item: DatatableItem }) => {\n if (item.id) emit('rowClick', item)\n}\n\nonMounted(() => {\n loadStoredFilters(pagination, getList)\n})\n\nonBeforeUnmount(() => {\n // Order matters: a pending debounce would otherwise fire after the abort and start a request\n // nothing is left to receive.\n getList.cancel()\n cancelPermissionGroupList()\n})\n\ndefineExpose({\n refresh: getList,\n resetFilter: resetFilterAction,\n submitFilter: submitFilterAction,\n})\n</script>\n\n<template>\n <div>\n <APermissionGroupFilter\n @submit=\"submitFilterAction\"\n @reset=\"resetFilterAction\"\n />\n <div>\n <div class=\"d-flex align-center\">\n <VSpacer />\n <ADatatableOrdering\n variant=\"id\"\n @sort-by-change=\"submitFilterAction\"\n />\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=\"permissionGroupList\"\n :items-length=\"permissionGroupList.length\"\n :loading=\"loadingPermissionGroupList\"\n item-value=\"id\"\n @click:row=\"onRowClick\"\n >\n <template #item.permissions=\"{ item }: { item: DatatableItem }\">\n <VChip>{{ Object.keys(item.permissions).length }}</VChip>\n </template>\n <template #item.createdAt=\"{ item }: { item: DatatableItem }\">\n <ADatetime :date-time=\"item.createdAt\" />\n <!-- The user chip is per app: its cache, its route, its own `_system`. An app without\n one leaves the slot empty, and then there is no line break either. -->\n <template v-if=\"$slots.createdBy\">\n <br />\n <slot\n :id=\"item.createdBy\"\n name=\"createdBy\"\n />\n </template>\n </template>\n <template #item.modifiedAt=\"{ item }: { item: DatatableItem }\">\n <ADatetime :date-time=\"item.modifiedAt\" />\n <template v-if=\"$slots.modifiedBy\">\n <br />\n <slot\n :id=\"item.modifiedBy\"\n name=\"modifiedBy\"\n />\n </template>\n </template>\n <template #item.actions=\"{ item }: { item: DatatableItem }\">\n <div class=\"d-flex justify-end\">\n <ATableCopyIdButton :id=\"item.id\" />\n <!--\n `:to` rather than a click handler, so middle-click and open-in-new-tab work on a list\n people open several rows from.\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 <VBtn\n v-if=\"canUpdate\"\n :to=\"editRoute(item)\"\n class=\"ml-1\"\n icon=\"mdi-pencil\"\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 </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { storeToRefs } from 'pinia'\nimport { useI18n } from 'vue-i18n'\nimport ACopyText from '@/components/ACopyText.vue'\nimport ARow from '@/components/ARow.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport APermissionEditor from '@/labs/permission/APermissionEditor.vue'\nimport { usePermissionGroupOneStore } from '@/labs/permissionGroup/permissionGroupStore'\n\ndefineProps<{\n client: AxiosClientFn\n system: string\n entity?: string | undefined\n endPoint?: string | undefined\n}>()\n\nconst { permissionGroup, loadingPermissionGroup } = storeToRefs(usePermissionGroupOneStore())\n\nconst { t } = useI18n()\n</script>\n\n<template>\n <VRow>\n <VCol\n cols=\"12\"\n sm=\"8\"\n >\n <VRow>\n <VCol cols=\"12\">\n <VCard :loading=\"loadingPermissionGroup\">\n <VCardText>\n <ARow\n :title=\"t('common.permissionGroup.model.title')\"\n :value=\"permissionGroup.title\"\n />\n <ARow\n :title=\"t('common.permissionGroup.model.description')\"\n :value=\"permissionGroup.description\"\n />\n </VCardText>\n </VCard>\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"12\">\n <!--\n A group has no resolved permissions of its own: `resolvedPermissions` is left undefined\n so the origin column reads every grant as the group's own rather than a user's.\n -->\n <APermissionEditor\n v-model=\"permissionGroup.permissions\"\n :client=\"client\"\n :system=\"system\"\n :entity=\"entity\"\n :end-point=\"endPoint\"\n readonly\n />\n </VCol>\n </VRow>\n </VCol>\n <VCol\n cols=\"12\"\n sm=\"4\"\n >\n <ARow :title=\"t('common.permissionGroup.model.id')\">\n <ACopyText :value=\"permissionGroup.id\" />\n </ARow>\n <!-- Created/modified by whom: each app resolves users against its own cache and routes. -->\n <slot\n name=\"tracking\"\n :permission-group=\"permissionGroup\"\n />\n </VCol>\n </VRow>\n</template>\n","<script lang=\"ts\" setup>\nimport { storeToRefs } from 'pinia'\nimport { useI18n } from 'vue-i18n'\nimport ACopyText from '@/components/ACopyText.vue'\nimport ARow from '@/components/ARow.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport APermissionEditor from '@/labs/permission/APermissionEditor.vue'\nimport { usePermissionGroupOneStore } from '@/labs/permissionGroup/permissionGroupStore'\n\ndefineProps<{\n client: AxiosClientFn\n system: string\n entity?: string | undefined\n endPoint?: string | undefined\n}>()\n\nconst { permissionGroup, loadingPermissionGroup } = storeToRefs(usePermissionGroupOneStore())\n\nconst { t } = useI18n()\n</script>\n\n<template>\n <VRow>\n <VCol\n cols=\"12\"\n sm=\"8\"\n >\n <VRow>\n <VCol cols=\"12\">\n <VCard :loading=\"loadingPermissionGroup\">\n <VCardText>\n <ARow\n :title=\"t('common.permissionGroup.model.title')\"\n :value=\"permissionGroup.title\"\n />\n <ARow\n :title=\"t('common.permissionGroup.model.description')\"\n :value=\"permissionGroup.description\"\n />\n </VCardText>\n </VCard>\n </VCol>\n </VRow>\n <VRow>\n <VCol cols=\"12\">\n <!--\n A group has no resolved permissions of its own: `resolvedPermissions` is left undefined\n so the origin column reads every grant as the group's own rather than a user's.\n -->\n <APermissionEditor\n v-model=\"permissionGroup.permissions\"\n :client=\"client\"\n :system=\"system\"\n :entity=\"entity\"\n :end-point=\"endPoint\"\n readonly\n />\n </VCol>\n </VRow>\n </VCol>\n <VCol\n cols=\"12\"\n sm=\"4\"\n >\n <ARow :title=\"t('common.permissionGroup.model.id')\">\n <ACopyText :value=\"permissionGroup.id\" />\n </ARow>\n <!-- Created/modified by whom: each app resolves users against its own cache and routes. -->\n <slot\n name=\"tracking\"\n :permission-group=\"permissionGroup\"\n />\n </VCol>\n </VRow>\n</template>\n","import useVuelidate from '@vuelidate/core'\nimport type { Ref } from 'vue'\nimport { useValidate } from '@/validators/vuelidate/useValidate'\nimport type { PermissionGroup } from '@/types/PermissionGroup'\n\n/**\n * The same three rules all five admins carry today, verbatim. They are not derived from the\n * backend: `PermissionGroupDto` only requires a title, and the lengths here are the front-end's\n * own stricter profile.\n */\nexport function usePermissionGroupValidation(permissionGroup: Ref<PermissionGroup>) {\n const { maxLength, minLength, required } = useValidate()\n\n const rules = {\n permissionGroup: {\n title: {\n required,\n minLength: minLength(3),\n maxLength: maxLength(255),\n },\n description: {\n maxLength: maxLength(2000),\n },\n },\n }\n\n const v$ = useVuelidate(rules, { permissionGroup })\n\n return {\n v$,\n }\n}\n","<script lang=\"ts\" setup>\nimport { storeToRefs } from 'pinia'\nimport AFormTextField from '@/components/form/AFormTextField.vue'\nimport ASystemEntityScope from '@/components/form/ASystemEntityScope.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport APermissionEditor from '@/labs/permission/APermissionEditor.vue'\nimport { usePermissionGroupValidation } from '@/labs/permissionGroup/permissionGroupValidations'\nimport { PERMISSION_GROUP_ENTITY } from '@/labs/permissionGroup/permissionGroupApi'\nimport { usePermissionGroupOneStore } from '@/labs/permissionGroup/permissionGroupStore'\n\ndefineProps<{\n client: AxiosClientFn\n /** Backend identity for the permission config fetch, not the i18n namespace below. */\n system: string\n entity?: string | undefined\n endPoint?: string | undefined\n}>()\n\nconst { permissionGroup } = storeToRefs(usePermissionGroupOneStore())\nconst { v$ } = usePermissionGroupValidation(permissionGroup)\n</script>\n\n<template>\n <!--\n `common` / `permissionGroup` is the translation namespace the field labels are looked up under,\n and it is the library's own -- the labels read the same whichever backend the row came from.\n The backend identity travels in `system` above.\n -->\n <ASystemEntityScope\n system=\"common\"\n :subject=\"PERMISSION_GROUP_ENTITY\"\n >\n <VRow>\n <VCol cols=\"12\">\n <VCard>\n <VCardText>\n <AFormTextField\n v-model=\"permissionGroup.title\"\n :v=\"v$.permissionGroup.title\"\n data-cy=\"permissionGroup-title\"\n />\n <AFormTextField\n v-model=\"permissionGroup.description\"\n :v=\"v$.permissionGroup.description\"\n data-cy=\"permissionGroup-description\"\n />\n </VCardText>\n </VCard>\n </VCol>\n <VCol cols=\"12\">\n <APermissionEditor\n v-model=\"permissionGroup.permissions\"\n :client=\"client\"\n :system=\"system\"\n :entity=\"entity\"\n :end-point=\"endPoint\"\n />\n </VCol>\n </VRow>\n </ASystemEntityScope>\n</template>\n","<script lang=\"ts\" setup>\nimport { storeToRefs } from 'pinia'\nimport AFormTextField from '@/components/form/AFormTextField.vue'\nimport ASystemEntityScope from '@/components/form/ASystemEntityScope.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport APermissionEditor from '@/labs/permission/APermissionEditor.vue'\nimport { usePermissionGroupValidation } from '@/labs/permissionGroup/permissionGroupValidations'\nimport { PERMISSION_GROUP_ENTITY } from '@/labs/permissionGroup/permissionGroupApi'\nimport { usePermissionGroupOneStore } from '@/labs/permissionGroup/permissionGroupStore'\n\ndefineProps<{\n client: AxiosClientFn\n /** Backend identity for the permission config fetch, not the i18n namespace below. */\n system: string\n entity?: string | undefined\n endPoint?: string | undefined\n}>()\n\nconst { permissionGroup } = storeToRefs(usePermissionGroupOneStore())\nconst { v$ } = usePermissionGroupValidation(permissionGroup)\n</script>\n\n<template>\n <!--\n `common` / `permissionGroup` is the translation namespace the field labels are looked up under,\n and it is the library's own -- the labels read the same whichever backend the row came from.\n The backend identity travels in `system` above.\n -->\n <ASystemEntityScope\n system=\"common\"\n :subject=\"PERMISSION_GROUP_ENTITY\"\n >\n <VRow>\n <VCol cols=\"12\">\n <VCard>\n <VCardText>\n <AFormTextField\n v-model=\"permissionGroup.title\"\n :v=\"v$.permissionGroup.title\"\n data-cy=\"permissionGroup-title\"\n />\n <AFormTextField\n v-model=\"permissionGroup.description\"\n :v=\"v$.permissionGroup.description\"\n data-cy=\"permissionGroup-description\"\n />\n </VCardText>\n </VCard>\n </VCol>\n <VCol cols=\"12\">\n <APermissionEditor\n v-model=\"permissionGroup.permissions\"\n :client=\"client\"\n :system=\"system\"\n :entity=\"entity\"\n :end-point=\"endPoint\"\n />\n </VCol>\n </VRow>\n </ASystemEntityScope>\n</template>\n","<script lang=\"ts\" setup>\nimport { provide } from 'vue'\nimport { SortOrder } from '@/composables/system/datatableColumns'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport AFormRemoteAutocomplete from '@/labs/form/AFormRemoteAutocomplete.vue'\nimport { FilterInnerConfigKey, FilterInnerDataKey } from '@/labs/filters/filterInjectionKeys'\nimport { usePermissionGroupActions } from '@/labs/permissionGroup/permissionGroupActions'\nimport { usePermissionGroupInnerFilter } from '@/labs/permissionGroup/permissionGroupFilter'\nimport type { IntegerId } from '@/types/common'\n\nconst props = withDefaults(\n defineProps<{\n client: AxiosClientFn\n system: string\n entity?: string | undefined\n endPoint?: string | undefined\n label?: string | undefined\n required?: boolean | undefined\n multiple?: boolean\n clearable?: boolean\n readonly?: boolean\n dataCy?: string\n }>(),\n {\n entity: undefined,\n endPoint: undefined,\n label: undefined,\n required: undefined,\n multiple: false,\n clearable: false,\n readonly: false,\n dataCy: 'permissionGroup-select',\n }\n)\n\nconst modelValue = defineModel<IntegerId | IntegerId[] | null>({ required: true })\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { fetchPermissionGroupOptions, fetchPermissionGroupOptionsByIds } = usePermissionGroupActions({\n client: props.client,\n system: props.system,\n entity: props.entity,\n endPoint: props.endPoint,\n})\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\n// Per instance, never registered: two autocompletes on one page must not type into each other.\nconst { filterData, filterConfig } = usePermissionGroupInnerFilter()\nprovide(FilterInnerConfigKey, filterConfig)\nprovide(FilterInnerDataKey, filterData)\n</script>\n\n<template>\n <AFormRemoteAutocomplete\n v-model=\"modelValue\"\n :required=\"required\"\n :label=\"label\"\n :readonly=\"readonly\"\n :fetch-items=\"fetchPermissionGroupOptions\"\n :fetch-items-by-ids=\"fetchPermissionGroupOptionsByIds\"\n :multiple=\"multiple\"\n :clearable=\"clearable && !readonly\"\n filter-by-field=\"title\"\n :filter-sort-by=\"{ key: 'createdAt', order: SortOrder.Desc }\"\n prefetch=\"mounted\"\n disable-auto-single-select\n :data-cy=\"dataCy\"\n chips\n closable-chips\n hide-details\n />\n</template>\n","<script lang=\"ts\" setup>\nimport { provide } from 'vue'\nimport { SortOrder } from '@/composables/system/datatableColumns'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport AFormRemoteAutocomplete from '@/labs/form/AFormRemoteAutocomplete.vue'\nimport { FilterInnerConfigKey, FilterInnerDataKey } from '@/labs/filters/filterInjectionKeys'\nimport { usePermissionGroupActions } from '@/labs/permissionGroup/permissionGroupActions'\nimport { usePermissionGroupInnerFilter } from '@/labs/permissionGroup/permissionGroupFilter'\nimport type { IntegerId } from '@/types/common'\n\nconst props = withDefaults(\n defineProps<{\n client: AxiosClientFn\n system: string\n entity?: string | undefined\n endPoint?: string | undefined\n label?: string | undefined\n required?: boolean | undefined\n multiple?: boolean\n clearable?: boolean\n readonly?: boolean\n dataCy?: string\n }>(),\n {\n entity: undefined,\n endPoint: undefined,\n label: undefined,\n required: undefined,\n multiple: false,\n clearable: false,\n readonly: false,\n dataCy: 'permissionGroup-select',\n }\n)\n\nconst modelValue = defineModel<IntegerId | IntegerId[] | null>({ required: true })\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { fetchPermissionGroupOptions, fetchPermissionGroupOptionsByIds } = usePermissionGroupActions({\n client: props.client,\n system: props.system,\n entity: props.entity,\n endPoint: props.endPoint,\n})\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\n// Per instance, never registered: two autocompletes on one page must not type into each other.\nconst { filterData, filterConfig } = usePermissionGroupInnerFilter()\nprovide(FilterInnerConfigKey, filterConfig)\nprovide(FilterInnerDataKey, filterData)\n</script>\n\n<template>\n <AFormRemoteAutocomplete\n v-model=\"modelValue\"\n :required=\"required\"\n :label=\"label\"\n :readonly=\"readonly\"\n :fetch-items=\"fetchPermissionGroupOptions\"\n :fetch-items-by-ids=\"fetchPermissionGroupOptionsByIds\"\n :multiple=\"multiple\"\n :clearable=\"clearable && !readonly\"\n filter-by-field=\"title\"\n :filter-sort-by=\"{ key: 'createdAt', order: SortOrder.Desc }\"\n prefetch=\"mounted\"\n disable-auto-single-select\n :data-cy=\"dataCy\"\n chips\n closable-chips\n hide-details\n />\n</template>\n","<script lang=\"ts\" setup>\nimport { inject } from 'vue'\nimport type { AxiosInstance } from 'axios'\nimport AFilterString from '@/labs/filters/AFilterString.vue'\nimport AFilterWrapper from '@/labs/filters/AFilterWrapper.vue'\nimport type { FilterStoreIdentifier } from '@/labs/filters/filterFactory'\nimport { FilterConfigKey, FilterDataKey } from '@/labs/filters/filterInjectionKeys'\nimport type { IntegerIdNullable } from '@/types/common'\nimport { isUndefined } from '@/utils/common'\n\nwithDefaults(\n defineProps<{\n /**\n * Both are what switches the bookmark UI on in `AFilterWrapper`, and both have to be there for\n * it to appear -- bookmarks are stored per user. Left unset, the filter is drawn without them,\n * which is what admin-inhouse, blog and forum have.\n */\n client?: (() => AxiosInstance) | undefined\n userId?: IntegerIdNullable | undefined\n /**\n * What the saved bookmarks are filed under on the server.\n *\n * Left unset it is the filter's own `common` / `anzuUser`. cms overrides it with the pair its\n * old list used, so the bookmarks operators already have keep showing up -- the field names are\n * the same, and a new key would simply orphan them.\n */\n store?: FilterStoreIdentifier | undefined\n }>(),\n {\n client: undefined,\n userId: undefined,\n store: undefined,\n }\n)\n\nconst emit = defineEmits<{\n (e: 'submit'): void\n (e: 'reset'): void\n}>()\n\n/** The column set a bookmark restores along with the filter values. */\nconst datatableHiddenColumns = defineModel<string[] | undefined>('datatableHiddenColumns', {\n default: undefined,\n required: false,\n})\n\nconst filterConfig = inject(FilterConfigKey)\nconst filterData = inject(FilterDataKey)\nif (isUndefined(filterConfig) || isUndefined(filterData)) {\n throw new Error('[AAnzuUserFilter] Incorrect provide/inject config.')\n}\n</script>\n\n<template>\n <AFilterWrapper\n v-model:datatable-hidden-columns=\"datatableHiddenColumns\"\n :client=\"client\"\n :user-id=\"userId\"\n :store=\"store ?? true\"\n @submit=\"emit('submit')\"\n @reset=\"emit('reset')\"\n @bookmark-load-after=\"emit('submit')\"\n >\n <template #search>\n <AFilterString name=\"email\" />\n </template>\n <!--\n Every other slot straight through, so a system's own field can bring its own widget: the\n generic renderer draws a plain input, and a permission group or a site is a remote\n autocomplete that only the app can build.\n -->\n <template\n v-for=\"(_, name) in $slots\"\n :key=\"name\"\n #[name]=\"slotProps\"\n >\n <slot\n :name=\"name\"\n v-bind=\"slotProps ?? {}\"\n />\n </template>\n </AFilterWrapper>\n</template>\n","<script lang=\"ts\" setup>\nimport { inject } from 'vue'\nimport type { AxiosInstance } from 'axios'\nimport AFilterString from '@/labs/filters/AFilterString.vue'\nimport AFilterWrapper from '@/labs/filters/AFilterWrapper.vue'\nimport type { FilterStoreIdentifier } from '@/labs/filters/filterFactory'\nimport { FilterConfigKey, FilterDataKey } from '@/labs/filters/filterInjectionKeys'\nimport type { IntegerIdNullable } from '@/types/common'\nimport { isUndefined } from '@/utils/common'\n\nwithDefaults(\n defineProps<{\n /**\n * Both are what switches the bookmark UI on in `AFilterWrapper`, and both have to be there for\n * it to appear -- bookmarks are stored per user. Left unset, the filter is drawn without them,\n * which is what admin-inhouse, blog and forum have.\n */\n client?: (() => AxiosInstance) | undefined\n userId?: IntegerIdNullable | undefined\n /**\n * What the saved bookmarks are filed under on the server.\n *\n * Left unset it is the filter's own `common` / `anzuUser`. cms overrides it with the pair its\n * old list used, so the bookmarks operators already have keep showing up -- the field names are\n * the same, and a new key would simply orphan them.\n */\n store?: FilterStoreIdentifier | undefined\n }>(),\n {\n client: undefined,\n userId: undefined,\n store: undefined,\n }\n)\n\nconst emit = defineEmits<{\n (e: 'submit'): void\n (e: 'reset'): void\n}>()\n\n/** The column set a bookmark restores along with the filter values. */\nconst datatableHiddenColumns = defineModel<string[] | undefined>('datatableHiddenColumns', {\n default: undefined,\n required: false,\n})\n\nconst filterConfig = inject(FilterConfigKey)\nconst filterData = inject(FilterDataKey)\nif (isUndefined(filterConfig) || isUndefined(filterData)) {\n throw new Error('[AAnzuUserFilter] Incorrect provide/inject config.')\n}\n</script>\n\n<template>\n <AFilterWrapper\n v-model:datatable-hidden-columns=\"datatableHiddenColumns\"\n :client=\"client\"\n :user-id=\"userId\"\n :store=\"store ?? true\"\n @submit=\"emit('submit')\"\n @reset=\"emit('reset')\"\n @bookmark-load-after=\"emit('submit')\"\n >\n <template #search>\n <AFilterString name=\"email\" />\n </template>\n <!--\n Every other slot straight through, so a system's own field can bring its own widget: the\n generic renderer draws a plain input, and a permission group or a site is a remote\n autocomplete that only the app can build.\n -->\n <template\n v-for=\"(_, name) in $slots\"\n :key=\"name\"\n #[name]=\"slotProps\"\n >\n <slot\n :name=\"name\"\n v-bind=\"slotProps ?? {}\"\n />\n </template>\n </AFilterWrapper>\n</template>\n","import type { AxiosClientFn } from '@/labs/api/client'\nimport { useApiFetchByIds } from '@/labs/api/useApiFetchByIds'\nimport { useApiFetchList } from '@/labs/api/useApiFetchList'\nimport { useApiRequest } from '@/labs/api/useApiRequest'\nimport type { AnzuUser } from '@/types/AnzuUser'\n\nexport const ANZU_USER_ENTITY = 'anzuUser'\nexport const ANZU_USER_ENDPOINT = '/adm/v1/anzu-user'\n\nexport interface AnzuUserApiParams {\n client: AxiosClientFn\n /**\n * Both reach every `useApiRequest` built here. The admins' copies declared `system = 'common'`\n * and `entity = 'anzuUser'` as file constants, which is why a shared version has to take them:\n * the same file now serves nine backends, and the error context has to name the right one.\n */\n system: string\n entity?: string\n /**\n * i18n scope for a server-side validation failure, which is a different question from which\n * backend answered. `AnzuApiValidationError` builds `<system>.<entity>.model.<field>` keys, and\n * for a component shared by nine backends the labels are always the library's own `common.*` --\n * `weather.anzuUser.model.email` exists nowhere. Defaults to `common` plus the entity;\n * contentHub passes its own pair to keep the keys it already has.\n */\n validationSystem?: string\n validationEntity?: string\n endPoint?: string\n}\n\n/**\n * Generic in the user type, because the three systems that answer on `/adm/users` return a DTO of\n * their own -- `CmsUserDto`, `DamUserDto`, `ContentHubUserDto` -- each extending `UserDto` with\n * system fields. Losing those in the round trip is exactly what the write invariant forbids: the\n * body of a write is the whole object the preceding GET returned.\n */\nexport const useAnzuUserApi = <T extends AnzuUser = AnzuUser>({\n client,\n system,\n entity = ANZU_USER_ENTITY,\n validationSystem = 'common',\n validationEntity = entity,\n endPoint = ANZU_USER_ENDPOINT,\n}: AnzuUserApiParams) => {\n const scope = { validationSystem, validationEntity }\n const useFetchAnzuUserListByIds = () =>\n useApiFetchByIds<T>({ client, system, entity, ...scope, urlTemplate: endPoint })\n\n const useFetchAnzuUserList = () => useApiFetchList<T>({ client, system, entity, ...scope, urlTemplate: endPoint })\n\n const useFetchAnzuUser = () =>\n useApiRequest<T, null>({ client, method: 'GET', system, entity, ...scope, urlTemplate: endPoint + '/:id' })\n\n const useCreateAnzuUser = () =>\n useApiRequest<T, T>({ client, method: 'POST', system, entity, ...scope, urlTemplate: endPoint })\n\n const useUpdateAnzuUser = () =>\n useApiRequest<T, T>({ client, method: 'PUT', system, entity, ...scope, urlTemplate: endPoint + '/:id' })\n\n /**\n * Only cms, dam and contentHub have this, and it is the safe path for metadata: the method\n * behind it writes e-mail, locale, avatar and person and has no setter for anything else.\n *\n * The body is the whole record the preceding read returned -- typed `T`, not `BaseUser`. Two\n * reasons, and both are the invariant: `BaseUser` has no `locale`, and the method behind this\n * path calls `setLocale` unconditionally, so a body shaped like `BaseUser` would erase the\n * interface language of whoever was edited. And `id` has to be in it: `BaseUserDto` declares a\n * uniqueness constraint on both `id` and `email`, and the validator only excludes the record\n * itself when the DTO carries a non-empty `id` -- without it the server reports the user's own\n * e-mail as taken, on every account.\n */\n const usePatchBaseUser = (patchEndPoint: string) =>\n useApiRequest<T, T>({ client, method: 'PATCH', system, entity, ...scope, urlTemplate: patchEndPoint })\n\n return {\n useFetchAnzuUserListByIds,\n useFetchAnzuUserList,\n useFetchAnzuUser,\n useCreateAnzuUser,\n useUpdateAnzuUser,\n usePatchBaseUser,\n }\n}\n","import { acceptHMRUpdate, defineStore } from 'pinia'\nimport { ref } from 'vue'\nimport { useAnzuUserFactory } from '@/model/factory/AnzuUserFactory'\nimport type { AnzuUser } from '@/types/AnzuUser'\n\n/**\n * The one user the owning form is editing. Cross-system pages do not use this -- they hold a\n * result per system and never a single \"current\" user -- so it stays a single record, the way the\n * admins have it.\n */\nexport const useAnzuUserOneStore = defineStore('labsAnzuUserOneStore', () => {\n const { createAnzuUser } = useAnzuUserFactory()\n\n const anzuUser = ref<AnzuUser>(createAnzuUser())\n const loadingAnzuUser = ref(false)\n\n function setAnzuUser(newAnzuUser: AnzuUser) {\n anzuUser.value = newAnzuUser\n }\n\n function setLoadingAnzuUser(loading: boolean) {\n loadingAnzuUser.value = loading\n }\n\n function reset(system = '', resourceName = 'user') {\n anzuUser.value = createAnzuUser(system, resourceName)\n loadingAnzuUser.value = false\n }\n\n return {\n anzuUser,\n loadingAnzuUser,\n setAnzuUser,\n setLoadingAnzuUser,\n reset,\n }\n})\n\nif (import.meta.hot) {\n import.meta.hot.accept(acceptHMRUpdate(useAnzuUserOneStore, import.meta.hot))\n}\n","import { computed, nextTick, ref, type Ref } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport useVuelidate from '@vuelidate/core'\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 { ANZU_USER_ENDPOINT, ANZU_USER_ENTITY, useAnzuUserApi } from '@/labs/anzuUser/anzuUserApi'\nimport { useAnzuUserOneStore } from '@/labs/anzuUser/anzuUserStore'\nimport type { AnzuUser } from '@/types/AnzuUser'\nimport type { IntegerId } from '@/types/common'\nimport { AnzuFatalError } from '@/model/error/AnzuFatalError'\nimport { isNull, isUndefined } from '@/utils/common'\n\nexport interface AnzuUserActionsParams {\n client: AxiosClientFn\n system: string\n entity?: string\n /**\n * i18n scope for a server-side validation failure, which is a different question from which\n * backend answered. `AnzuApiValidationError` builds `<system>.<entity>.model.<field>` keys, so\n * the pair has to name the namespace the *labels* live under. It defaults to `common` +\n * `anzuUser`, which is where the shared form's own fields are; an admin whose form carries\n * system fields as well passes its own pair, and keeps the base keys beside them.\n */\n validationSystem?: string\n validationEntity?: string\n endPoint?: string\n /** Vuelidate scope, when the page collects its fields under one of its own. */\n validationScope?: string | symbol\n}\n\n/**\n * Per instance, like every other actions composable here. The admins' versions kept their loading\n * flags at module scope.\n */\nexport const useAnzuUserActions = (params: AnzuUserActionsParams) => {\n const { client, system, entity = ANZU_USER_ENTITY, endPoint = ANZU_USER_ENDPOINT } = params\n\n const { showErrorsDefault, showRecordWas, showValidationError } = useAlerts()\n const { useFetchAnzuUserList, useFetchAnzuUser, useCreateAnzuUser, useUpdateAnzuUser } = useAnzuUserApi({\n client,\n system,\n entity,\n validationSystem: params.validationSystem,\n validationEntity: params.validationEntity,\n endPoint,\n })\n\n const { execute: executeList, abort: abortList } = useFetchAnzuUserList()\n\n const anzuUserList = ref<AnzuUser[]>([])\n const loadingAnzuUserList = ref(false)\n const datatableHiddenColumns = ref<Array<string>>([])\n\n let listGeneration = 0\n\n const fetchAnzuUserList = async (\n pagination: Ref<Pagination>,\n filterData: FilterData<any>,\n filterConfig: FilterConfig<any>\n ) => {\n const token = ++listGeneration\n loadingAnzuUserList.value = true\n try {\n const items = await executeList(pagination, filterData, filterConfig)\n if (token !== listGeneration) return\n anzuUserList.value = items\n } catch (error) {\n if (token !== listGeneration) return\n anzuUserList.value = []\n showErrorsDefault(error)\n } finally {\n if (token === listGeneration) loadingAnzuUserList.value = false\n }\n }\n\n const cancelAnzuUserList = () => {\n listGeneration++\n abortList()\n }\n\n const anzuUserOneStore = useAnzuUserOneStore()\n const { anzuUser, loadingAnzuUser } = storeToRefs(anzuUserOneStore)\n\n /**\n * What the server last said, so the page can tell whether anything has been edited.\n *\n * A page needs this for two things that look cosmetic and are not: the dot on the owning tab,\n * and the guard that asks before leaving. Switching a tab is not navigation -- the form stays\n * mounted and dirty -- so the moment the operator does leave is through a button inside a panel,\n * and by then nothing else remembers there was unsaved work.\n */\n const pristine = ref<string>('')\n const snapshot = () => {\n pristine.value = JSON.stringify(anzuUserOneStore.anzuUser)\n }\n\n const isDirty = computed(() => pristine.value !== '' && pristine.value !== JSON.stringify(anzuUser.value))\n\n const fetchAnzuUser = async (id: IntegerId) => {\n anzuUserOneStore.setLoadingAnzuUser(true)\n try {\n const { execute } = useFetchAnzuUser()\n anzuUserOneStore.setAnzuUser(await execute({ urlParams: { id } }))\n // After the form has settled, not before it. The derived fields recompute in a watcher, and\n // a baseline taken ahead of that would make an account whose stored full name is empty read\n // as edited the instant it opens -- an unsaved dot and a leave prompt before anyone typed.\n await nextTick()\n snapshot()\n } catch (error) {\n showErrorsDefault(error)\n } finally {\n anzuUserOneStore.setLoadingAnzuUser(false)\n }\n }\n\n // Collects whatever the form's children registered. With a scope it collects only that scope,\n // which is how an app keeps its own system fields in a separate one.\n const v$ = params.validationScope ? useVuelidate({}, {}, { $scope: params.validationScope }) : useVuelidate()\n\n const loadingCreateAnzuUser = ref(false)\n /**\n * The body is the store's record, which the library factory built: `roles: []` and\n * `enabled: false`. That matters -- a hand-assembled body of base fields would have the backend\n * fill in `ROLE_USER`, and the account would exist with a role nobody granted.\n */\n const createAnzuUser = async (): Promise<AnzuUser | null> => {\n try {\n loadingCreateAnzuUser.value = true\n v$.value.$touch()\n if (v$.value.$invalid) {\n showValidationError()\n return null\n }\n const { execute } = useCreateAnzuUser()\n const created = await execute({ body: anzuUserOneStore.anzuUser })\n showRecordWas('created')\n snapshot()\n return created\n } catch (error) {\n showErrorsDefault(error)\n return null\n } finally {\n loadingCreateAnzuUser.value = false\n }\n }\n\n const loadingUpdateAnzuUser = ref(false)\n /**\n * Sends the whole record the preceding GET returned, with only the edited fields changed.\n *\n * That is the write invariant, and it is not a style preference: an omitted field is destructive\n * rather than neutral. `UserDto` defaults `roles` to `[ROLE_USER]`, `enabled` to `true` and both\n * permission collections to empty, and the manager writes all four unconditionally -- so a body\n * carrying only the metadata resets roles, wipes grants, detaches groups and switches a disabled\n * account back on.\n */\n const updateAnzuUser = async (): Promise<boolean> => {\n try {\n loadingUpdateAnzuUser.value = true\n v$.value.$touch()\n if (v$.value.$invalid) {\n showValidationError()\n return false\n }\n const id = anzuUserOneStore.anzuUser.id\n if (isNull(id) || isUndefined(id)) {\n throw new AnzuFatalError(undefined, '[useAnzuUserActions] update called on a record with no id.')\n }\n const { execute } = useUpdateAnzuUser()\n await execute({\n urlParams: { id },\n body: anzuUserOneStore.anzuUser,\n })\n showRecordWas('updated')\n snapshot()\n return true\n } catch (error) {\n showErrorsDefault(error)\n return false\n } finally {\n loadingUpdateAnzuUser.value = false\n }\n }\n\n return {\n isDirty,\n /** Takes the record as it stands as the new baseline; the page calls it after it seeds one. */\n markPristine: snapshot,\n anzuUserList,\n loadingAnzuUserList,\n datatableHiddenColumns,\n fetchAnzuUserList,\n cancelAnzuUserList,\n anzuUser,\n loadingAnzuUser,\n fetchAnzuUser,\n createAnzuUser,\n updateAnzuUser,\n loadingCreateAnzuUser,\n loadingUpdateAnzuUser,\n resetAnzuUserStore: anzuUserOneStore.reset,\n }\n}\n","import {\n createFilter,\n createFilterStore,\n type FilterConfig,\n type FilterData,\n type MakeFilterOption,\n} from '@/labs/filters/filterFactory'\nimport { ANZU_USER_ENTITY } from '@/labs/anzuUser/anzuUserApi'\n\nconst anzuUserListFields = [\n { name: 'id' as const, default: null, type: 'integer' },\n { name: 'email' as const, variant: 'startsWith', default: null, type: 'string', render: { skip: true } },\n { name: 'enabled' as const, default: null, type: 'boolean' },\n] satisfies readonly MakeFilterOption[]\n\ntype ListFields = typeof anzuUserListFields\n\n/** One store per system; see the note in `permissionGroupFilter`. */\nconst listStores = new Map<string, FilterData<readonly MakeFilterOption[]>>()\n\nexport const anzuUserFilterStorageKey = (system: string) => `labsAnzuUser_${system}`\n\n/**\n * The three fields every system has, plus whatever that system adds.\n *\n * The extras are a parameter rather than fields in the list above because the same filter is not\n * the same query everywhere: cms asks for permission groups with `memberOf` and dam with `custom`,\n * and cms has `allowedSites`, which exists nowhere else. Hard-coding either variant would send the\n * wrong query to the other backend, and leaving them out costs those two lists filters they have\n * today.\n *\n * `AFilterWrapper` draws any field that is not `render.skip` by itself, so a plain extra field\n * needs nothing else; one that wants its own widget -- a remote autocomplete -- is passed through\n * the matching slot.\n */\nexport function useAnzuUserListFilter<E extends readonly MakeFilterOption[] = []>(\n system: string,\n extraFields?: E\n): {\n filterConfig: FilterConfig<readonly [...ListFields, ...E]>\n filterData: FilterData<readonly [...ListFields, ...E]>\n} {\n const extras = (extraFields ?? []) as readonly MakeFilterOption[]\n const fields = [...anzuUserListFields, ...extras] as unknown as readonly [...ListFields, ...E]\n // The extras are part of the identity: one list asking for `allowedSites` and another that does\n // not are two different stores, and sharing one would carry a value the second cannot clear.\n const key = [system, ...extras.map((field) => field.name)].join('|')\n\n let store = listStores.get(key)\n if (!store) {\n store = createFilterStore(fields) as FilterData<readonly MakeFilterOption[]>\n listStores.set(key, store)\n }\n\n const { filterConfig, filterData } = createFilter(fields, store as FilterData<readonly [...ListFields, ...E]>, {\n system: 'common',\n subject: ANZU_USER_ENTITY,\n })\n\n return { filterConfig, filterData }\n}\n\n/**\n * Exact match on e-mail, for the cross-system search.\n *\n * `startsWith` -- what the list filter uses and what admin-inhouse's search used -- cannot answer\n * the question that search asks. \"Is this person here under this address\" has to be `eq`, or\n * `jozko@sme.sk` matches `jozko.mrkvicka@sme.sk` and the fan-out reports the wrong identity.\n */\nexport function useAnzuUserEmailLookupFilter() {\n const fields = [\n { name: 'email' as const, variant: 'eq', default: null, type: 'string' },\n ] satisfies readonly MakeFilterOption[]\n\n const { filterConfig, filterData } = createFilter(fields, createFilterStore(fields), {\n system: 'common',\n subject: ANZU_USER_ENTITY,\n })\n\n return { filterConfig, filterData }\n}\n\n/** Test seam; the list stores are module state. */\nexport const resetAnzuUserListFilters = () => {\n listStores.clear()\n}\n","<script lang=\"ts\" setup>\nimport { computed, onBeforeUnmount, onMounted, provide, useSlots, watch } from 'vue'\nimport type { RouteLocationRaw } from 'vue-router'\nimport type { AxiosInstance } from 'axios'\nimport { useDebounceFn } from '@vueuse/core'\nimport ABooleanValue from '@/components/ABooleanValue.vue'\nimport ADatatableConfigButton from '@/components/ADatatableConfigButton.vue'\nimport ADatetime from '@/components/datetime/ADatetime.vue'\nimport ATableCopyIdButton from '@/components/buttons/table/ATableCopyIdButton.vue'\nimport { SORT_BY_ID } from '@/composables/system/datatableColumns'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport ADatatableOrdering from '@/labs/filters/ADatatableOrdering.vue'\nimport ADatatablePagination from '@/labs/filters/ADatatablePagination.vue'\nimport { createDatatableColumnsConfig } from '@/labs/filters/datatableColumns'\nimport { useFilterHelpers, type FilterStoreIdentifier, type MakeFilterOption } from '@/labs/filters/filterFactory'\nimport { DatatablePaginationKey, FilterConfigKey, FilterDataKey } from '@/labs/filters/filterInjectionKeys'\nimport { usePagination } from '@/labs/filters/pagination'\nimport AAnzuUserFilter from '@/labs/anzuUser/AAnzuUserFilter.vue'\nimport { useAnzuUserActions } from '@/labs/anzuUser/anzuUserActions'\nimport { ANZU_USER_ENTITY } from '@/labs/anzuUser/anzuUserApi'\nimport { anzuUserFilterStorageKey, useAnzuUserListFilter } from '@/labs/anzuUser/anzuUserFilter'\nimport { usePermissionConfigActions } from '@/labs/permissionConfig/permissionConfigActions'\nimport { useCachedPermissionGroups } from '@/labs/permissionGroup/cachedPermissionGroups'\nimport { PERMISSION_GROUP_ENDPOINT } from '@/labs/permissionGroup/permissionGroupApi'\nimport type { AnzuUser } from '@/types/AnzuUser'\nimport type { IntegerIdNullable } from '@/types/common'\n\ntype DatatableItem = AnzuUser\n\nconst props = withDefaults(\n defineProps<{\n client: AxiosClientFn\n system: string\n entity?: string | undefined\n endPoint?: string | undefined\n detailRoute: (user: AnzuUser) => RouteLocationRaw\n editRoute: (user: AnzuUser) => RouteLocationRaw\n /** Already-evaluated ACL of the owning system. */\n canUpdate?: boolean\n /** The permission-group endpoint, which is not the user one. */\n permissionGroupEndPoint?: string\n /**\n * Extra columns the app draws through `#systemColumns`, appended after the shared ones.\n *\n * A bare key takes its header from the library's own `common.anzuUser.model.*`, which holds\n * eight keys and none of any system's -- so a system column passes its title already\n * translated, out of the namespace that has it.\n */\n systemColumns?: Array<string | { key: string; title: string }>\n /**\n * Signing in as somebody else, as a switchable feature.\n *\n * A callback rather than a path, because there is more than one mechanism: cms posts to\n * `/adm/users/{id}/impersonate` and gets a JWT with an impersonation claim, blog opens the\n * public site through `window.open`. Only the cms one reaches a shared component -- blog's\n * hangs off its product user, which is a different view -- so one prop is enough for now.\n * Left unset, nothing is drawn.\n */\n impersonate?: ((user: AnzuUser) => void) | undefined\n canImpersonate?: boolean\n /**\n * Filter fields this system adds to the shared three.\n *\n * A parameter and not part of the shared list, because the same filter is not the same query\n * everywhere: cms asks for permission groups with `memberOf`, dam with `custom`, and\n * `allowedSites` exists only in cms. A plain field needs nothing more -- the generic renderer\n * draws it -- while one that wants a remote autocomplete comes in through `#filter.<name>`.\n */\n filterFields?: readonly MakeFilterOption[]\n /**\n * Saved filters, which are stored per user, so both are needed for the button to appear. cms\n * and dam have them on the list this replaces; the other three admins do not.\n */\n filterClient?: (() => AxiosInstance) | undefined\n filterUserId?: IntegerIdNullable | undefined\n /** Where saved bookmarks are filed; see `AAnzuUserFilter`. */\n filterStore?: FilterStoreIdentifier | undefined\n }>(),\n {\n entity: undefined,\n endPoint: undefined,\n permissionGroupEndPoint: PERMISSION_GROUP_ENDPOINT,\n canUpdate: false,\n systemColumns: () => [],\n impersonate: undefined,\n canImpersonate: false,\n filterFields: () => [],\n filterClient: undefined,\n filterUserId: undefined,\n filterStore: undefined,\n }\n)\n\nconst emit = defineEmits<{\n (e: 'rowClick', user: AnzuUser): void\n}>()\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { filterConfig, filterData } = useAnzuUserListFilter(props.system, props.filterFields)\nprovide(FilterConfigKey, filterConfig)\nprovide(FilterDataKey, filterData)\n\nconst { fetchAnzuUserList, cancelAnzuUserList, anzuUserList, loadingAnzuUserList, datatableHiddenColumns } =\n useAnzuUserActions({\n client: props.client,\n system: props.system,\n entity: props.entity,\n endPoint: props.endPoint,\n })\n\n// No `entity` here on purpose: this one reads the permission config, which is its own resource.\n// `props.entity` names the user.\nconst { translatePermission } = usePermissionConfigActions({\n client: props.client,\n system: props.system,\n})\n\n/**\n * Group titles for the column, resolved against this system's own cache.\n *\n * Drawn here rather than left to a slot: a group is a group in every admin, the library already\n * has the cache -- keyed per system, because id 42 in weather and id 42 in blog are different rows\n * -- and leaving it to the call sites is how the column ends up empty in every one of them. An app\n * that wants its own chip still overrides through the slot.\n */\nconst { addToCachedPermissionGroups, fetchCachedPermissionGroups, getCachedPermissionGroup } =\n useCachedPermissionGroups({\n client: props.client,\n system: props.system,\n endPoint: props.permissionGroupEndPoint,\n })\n\nconst { resetFilter, submitFilter, loadStoredFilters } = useFilterHelpers(filterData, filterConfig, {\n storeFiltersLocalStorage: anzuUserFilterStorageKey(props.system),\n})\n\nconst { pagination } = usePagination(SORT_BY_ID)\nprovide(DatatablePaginationKey, pagination)\n\nconst { columnsVisible, columnsAll, columnsHidden } = createDatatableColumnsConfig(\n [\n { key: 'id' },\n { key: 'email' },\n { key: 'enabled' },\n { key: 'roles' },\n { key: 'permissionGroups' },\n { key: 'permissions' },\n ...props.systemColumns.map((column) => (typeof column === 'string' ? { key: column } : column)),\n { key: 'createdAt' },\n { key: 'modifiedAt' },\n ],\n datatableHiddenColumns,\n 'common',\n ANZU_USER_ENTITY,\n { storeColumnsLocalStorage: `table_labs_anzuUser_${props.system}` }\n)\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst getList = useDebounceFn(async () => {\n await fetchAnzuUserList(pagination, filterData, filterConfig)\n})\n\nwatch(anzuUserList, (users) => {\n users.forEach((user) => addToCachedPermissionGroups(user.permissionGroups))\n void fetchCachedPermissionGroups()\n})\n\nconst systemColumnKeys = computed(() =>\n props.systemColumns.map((column) => (typeof column === 'string' ? column : column.key))\n)\n\n// `#filter.allowedSites` here becomes `#item.allowedSites` inside the filter, which is the name\n// `AFilterWrapper` renders a field's own widget under. The prefix is what keeps a field slot apart\n// from this table's own `item.*` columns.\nconst slots = useSlots()\nconst FILTER_SLOT_PREFIX = 'filter.'\nconst filterSlotNames = computed(() =>\n Object.keys(slots)\n .filter((name) => name.startsWith(FILTER_SLOT_PREFIX))\n .map((name) => name.slice(FILTER_SLOT_PREFIX.length))\n)\n\nconst submitFilterAction = () => submitFilter(pagination, getList)\nconst resetFilterAction = () => resetFilter(pagination, getList)\n\nconst onRowClick = (_event: unknown, { item }: { item: DatatableItem }) => {\n if (item.id) emit('rowClick', item)\n}\n\nonMounted(() => {\n loadStoredFilters(pagination, getList)\n})\n\nonBeforeUnmount(() => {\n getList.cancel()\n cancelAnzuUserList()\n})\n\ndefineExpose({\n refresh: getList,\n resetFilter: resetFilterAction,\n submitFilter: submitFilterAction,\n})\n</script>\n\n<template>\n <div>\n <AAnzuUserFilter\n v-model:datatable-hidden-columns=\"columnsHidden\"\n :client=\"filterClient\"\n :user-id=\"filterUserId\"\n :store=\"filterStore\"\n @submit=\"submitFilterAction\"\n @reset=\"resetFilterAction\"\n >\n <template\n v-for=\"name in filterSlotNames\"\n :key=\"name\"\n #[`item.${name}`]=\"slotProps\"\n >\n <slot\n :name=\"`${FILTER_SLOT_PREFIX}${name}`\"\n v-bind=\"slotProps ?? {}\"\n />\n </template>\n </AAnzuUserFilter>\n <div>\n <div class=\"d-flex align-center\">\n <VSpacer />\n <ADatatableOrdering\n variant=\"id\"\n @sort-by-change=\"submitFilterAction\"\n />\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=\"anzuUserList\"\n :items-length=\"anzuUserList.length\"\n :loading=\"loadingAnzuUserList\"\n item-value=\"id\"\n @click:row=\"onRowClick\"\n >\n <template #item.enabled=\"{ item }: { item: DatatableItem }\">\n <ABooleanValue\n chip\n :value=\"item.enabled\"\n />\n </template>\n <template #item.roles=\"{ item }: { item: DatatableItem }\">\n <VChip\n v-for=\"role in item.roles\"\n :key=\"role\"\n class=\"mr-1 mb-1\"\n >\n {{ translatePermission('roles', role) }}\n </VChip>\n </template>\n <template #item.permissionGroups=\"{ item }: { item: DatatableItem }\">\n <slot\n name=\"permissionGroups\"\n :ids=\"item.permissionGroups\"\n >\n <VChip\n v-for=\"permissionGroupId in item.permissionGroups\"\n :key=\"permissionGroupId\"\n class=\"mr-1 mb-1\"\n size=\"small\"\n label\n >\n {{ getCachedPermissionGroup(permissionGroupId)?.title || permissionGroupId }}\n </VChip>\n </slot>\n </template>\n <template #item.permissions=\"{ item }: { item: DatatableItem }\">\n <VChip>{{ Object.keys(item.permissions).length }}</VChip>\n </template>\n <template\n v-for=\"column in systemColumnKeys\"\n :key=\"column\"\n #[`item.${column}`]=\"{ item }: { item: DatatableItem }\"\n >\n <slot\n name=\"systemColumns\"\n :column=\"column\"\n :user=\"item\"\n />\n </template>\n <template #item.createdAt=\"{ item }: { item: DatatableItem }\">\n <ADatetime :date-time=\"item.createdAt\" />\n <template v-if=\"$slots.createdBy\">\n <br />\n <slot\n :id=\"item.createdBy\"\n name=\"createdBy\"\n />\n </template>\n </template>\n <template #item.modifiedAt=\"{ item }: { item: DatatableItem }\">\n <ADatetime :date-time=\"item.modifiedAt\" />\n <template v-if=\"$slots.modifiedBy\">\n <br />\n <slot\n :id=\"item.modifiedBy\"\n name=\"modifiedBy\"\n />\n </template>\n </template>\n <template #item.actions=\"{ item }: { item: DatatableItem }\">\n <div\n v-if=\"item.id\"\n class=\"d-flex justify-end\"\n >\n <ATableCopyIdButton :id=\"item.id\" />\n <VBtn\n :to=\"detailRoute(item)\"\n class=\"ml-1\"\n icon=\"mdi-information-outline\"\n size=\"x-small\"\n variant=\"text\"\n />\n <VBtn\n v-if=\"canUpdate\"\n :to=\"editRoute(item)\"\n class=\"ml-1\"\n icon=\"mdi-pencil\"\n size=\"x-small\"\n variant=\"text\"\n />\n <!-- A disabled account cannot sign in, so it cannot be signed in as either. -->\n <VBtn\n v-if=\"impersonate && canImpersonate\"\n :disabled=\"!item.enabled\"\n class=\"ml-1\"\n icon=\"mdi-account-switch\"\n size=\"x-small\"\n variant=\"text\"\n data-cy=\"user-impersonate\"\n @click.stop=\"impersonate(item)\"\n />\n <slot\n name=\"rowActions\"\n :user=\"item\"\n />\n </div>\n </template>\n <template #bottom>\n <ADatatablePagination @change=\"getList\" />\n </template>\n </VDataTableServer>\n </div>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed, onBeforeUnmount, onMounted, provide, useSlots, watch } from 'vue'\nimport type { RouteLocationRaw } from 'vue-router'\nimport type { AxiosInstance } from 'axios'\nimport { useDebounceFn } from '@vueuse/core'\nimport ABooleanValue from '@/components/ABooleanValue.vue'\nimport ADatatableConfigButton from '@/components/ADatatableConfigButton.vue'\nimport ADatetime from '@/components/datetime/ADatetime.vue'\nimport ATableCopyIdButton from '@/components/buttons/table/ATableCopyIdButton.vue'\nimport { SORT_BY_ID } from '@/composables/system/datatableColumns'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport ADatatableOrdering from '@/labs/filters/ADatatableOrdering.vue'\nimport ADatatablePagination from '@/labs/filters/ADatatablePagination.vue'\nimport { createDatatableColumnsConfig } from '@/labs/filters/datatableColumns'\nimport { useFilterHelpers, type FilterStoreIdentifier, type MakeFilterOption } from '@/labs/filters/filterFactory'\nimport { DatatablePaginationKey, FilterConfigKey, FilterDataKey } from '@/labs/filters/filterInjectionKeys'\nimport { usePagination } from '@/labs/filters/pagination'\nimport AAnzuUserFilter from '@/labs/anzuUser/AAnzuUserFilter.vue'\nimport { useAnzuUserActions } from '@/labs/anzuUser/anzuUserActions'\nimport { ANZU_USER_ENTITY } from '@/labs/anzuUser/anzuUserApi'\nimport { anzuUserFilterStorageKey, useAnzuUserListFilter } from '@/labs/anzuUser/anzuUserFilter'\nimport { usePermissionConfigActions } from '@/labs/permissionConfig/permissionConfigActions'\nimport { useCachedPermissionGroups } from '@/labs/permissionGroup/cachedPermissionGroups'\nimport { PERMISSION_GROUP_ENDPOINT } from '@/labs/permissionGroup/permissionGroupApi'\nimport type { AnzuUser } from '@/types/AnzuUser'\nimport type { IntegerIdNullable } from '@/types/common'\n\ntype DatatableItem = AnzuUser\n\nconst props = withDefaults(\n defineProps<{\n client: AxiosClientFn\n system: string\n entity?: string | undefined\n endPoint?: string | undefined\n detailRoute: (user: AnzuUser) => RouteLocationRaw\n editRoute: (user: AnzuUser) => RouteLocationRaw\n /** Already-evaluated ACL of the owning system. */\n canUpdate?: boolean\n /** The permission-group endpoint, which is not the user one. */\n permissionGroupEndPoint?: string\n /**\n * Extra columns the app draws through `#systemColumns`, appended after the shared ones.\n *\n * A bare key takes its header from the library's own `common.anzuUser.model.*`, which holds\n * eight keys and none of any system's -- so a system column passes its title already\n * translated, out of the namespace that has it.\n */\n systemColumns?: Array<string | { key: string; title: string }>\n /**\n * Signing in as somebody else, as a switchable feature.\n *\n * A callback rather than a path, because there is more than one mechanism: cms posts to\n * `/adm/users/{id}/impersonate` and gets a JWT with an impersonation claim, blog opens the\n * public site through `window.open`. Only the cms one reaches a shared component -- blog's\n * hangs off its product user, which is a different view -- so one prop is enough for now.\n * Left unset, nothing is drawn.\n */\n impersonate?: ((user: AnzuUser) => void) | undefined\n canImpersonate?: boolean\n /**\n * Filter fields this system adds to the shared three.\n *\n * A parameter and not part of the shared list, because the same filter is not the same query\n * everywhere: cms asks for permission groups with `memberOf`, dam with `custom`, and\n * `allowedSites` exists only in cms. A plain field needs nothing more -- the generic renderer\n * draws it -- while one that wants a remote autocomplete comes in through `#filter.<name>`.\n */\n filterFields?: readonly MakeFilterOption[]\n /**\n * Saved filters, which are stored per user, so both are needed for the button to appear. cms\n * and dam have them on the list this replaces; the other three admins do not.\n */\n filterClient?: (() => AxiosInstance) | undefined\n filterUserId?: IntegerIdNullable | undefined\n /** Where saved bookmarks are filed; see `AAnzuUserFilter`. */\n filterStore?: FilterStoreIdentifier | undefined\n }>(),\n {\n entity: undefined,\n endPoint: undefined,\n permissionGroupEndPoint: PERMISSION_GROUP_ENDPOINT,\n canUpdate: false,\n systemColumns: () => [],\n impersonate: undefined,\n canImpersonate: false,\n filterFields: () => [],\n filterClient: undefined,\n filterUserId: undefined,\n filterStore: undefined,\n }\n)\n\nconst emit = defineEmits<{\n (e: 'rowClick', user: AnzuUser): void\n}>()\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { filterConfig, filterData } = useAnzuUserListFilter(props.system, props.filterFields)\nprovide(FilterConfigKey, filterConfig)\nprovide(FilterDataKey, filterData)\n\nconst { fetchAnzuUserList, cancelAnzuUserList, anzuUserList, loadingAnzuUserList, datatableHiddenColumns } =\n useAnzuUserActions({\n client: props.client,\n system: props.system,\n entity: props.entity,\n endPoint: props.endPoint,\n })\n\n// No `entity` here on purpose: this one reads the permission config, which is its own resource.\n// `props.entity` names the user.\nconst { translatePermission } = usePermissionConfigActions({\n client: props.client,\n system: props.system,\n})\n\n/**\n * Group titles for the column, resolved against this system's own cache.\n *\n * Drawn here rather than left to a slot: a group is a group in every admin, the library already\n * has the cache -- keyed per system, because id 42 in weather and id 42 in blog are different rows\n * -- and leaving it to the call sites is how the column ends up empty in every one of them. An app\n * that wants its own chip still overrides through the slot.\n */\nconst { addToCachedPermissionGroups, fetchCachedPermissionGroups, getCachedPermissionGroup } =\n useCachedPermissionGroups({\n client: props.client,\n system: props.system,\n endPoint: props.permissionGroupEndPoint,\n })\n\nconst { resetFilter, submitFilter, loadStoredFilters } = useFilterHelpers(filterData, filterConfig, {\n storeFiltersLocalStorage: anzuUserFilterStorageKey(props.system),\n})\n\nconst { pagination } = usePagination(SORT_BY_ID)\nprovide(DatatablePaginationKey, pagination)\n\nconst { columnsVisible, columnsAll, columnsHidden } = createDatatableColumnsConfig(\n [\n { key: 'id' },\n { key: 'email' },\n { key: 'enabled' },\n { key: 'roles' },\n { key: 'permissionGroups' },\n { key: 'permissions' },\n ...props.systemColumns.map((column) => (typeof column === 'string' ? { key: column } : column)),\n { key: 'createdAt' },\n { key: 'modifiedAt' },\n ],\n datatableHiddenColumns,\n 'common',\n ANZU_USER_ENTITY,\n { storeColumnsLocalStorage: `table_labs_anzuUser_${props.system}` }\n)\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst getList = useDebounceFn(async () => {\n await fetchAnzuUserList(pagination, filterData, filterConfig)\n})\n\nwatch(anzuUserList, (users) => {\n users.forEach((user) => addToCachedPermissionGroups(user.permissionGroups))\n void fetchCachedPermissionGroups()\n})\n\nconst systemColumnKeys = computed(() =>\n props.systemColumns.map((column) => (typeof column === 'string' ? column : column.key))\n)\n\n// `#filter.allowedSites` here becomes `#item.allowedSites` inside the filter, which is the name\n// `AFilterWrapper` renders a field's own widget under. The prefix is what keeps a field slot apart\n// from this table's own `item.*` columns.\nconst slots = useSlots()\nconst FILTER_SLOT_PREFIX = 'filter.'\nconst filterSlotNames = computed(() =>\n Object.keys(slots)\n .filter((name) => name.startsWith(FILTER_SLOT_PREFIX))\n .map((name) => name.slice(FILTER_SLOT_PREFIX.length))\n)\n\nconst submitFilterAction = () => submitFilter(pagination, getList)\nconst resetFilterAction = () => resetFilter(pagination, getList)\n\nconst onRowClick = (_event: unknown, { item }: { item: DatatableItem }) => {\n if (item.id) emit('rowClick', item)\n}\n\nonMounted(() => {\n loadStoredFilters(pagination, getList)\n})\n\nonBeforeUnmount(() => {\n getList.cancel()\n cancelAnzuUserList()\n})\n\ndefineExpose({\n refresh: getList,\n resetFilter: resetFilterAction,\n submitFilter: submitFilterAction,\n})\n</script>\n\n<template>\n <div>\n <AAnzuUserFilter\n v-model:datatable-hidden-columns=\"columnsHidden\"\n :client=\"filterClient\"\n :user-id=\"filterUserId\"\n :store=\"filterStore\"\n @submit=\"submitFilterAction\"\n @reset=\"resetFilterAction\"\n >\n <template\n v-for=\"name in filterSlotNames\"\n :key=\"name\"\n #[`item.${name}`]=\"slotProps\"\n >\n <slot\n :name=\"`${FILTER_SLOT_PREFIX}${name}`\"\n v-bind=\"slotProps ?? {}\"\n />\n </template>\n </AAnzuUserFilter>\n <div>\n <div class=\"d-flex align-center\">\n <VSpacer />\n <ADatatableOrdering\n variant=\"id\"\n @sort-by-change=\"submitFilterAction\"\n />\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=\"anzuUserList\"\n :items-length=\"anzuUserList.length\"\n :loading=\"loadingAnzuUserList\"\n item-value=\"id\"\n @click:row=\"onRowClick\"\n >\n <template #item.enabled=\"{ item }: { item: DatatableItem }\">\n <ABooleanValue\n chip\n :value=\"item.enabled\"\n />\n </template>\n <template #item.roles=\"{ item }: { item: DatatableItem }\">\n <VChip\n v-for=\"role in item.roles\"\n :key=\"role\"\n class=\"mr-1 mb-1\"\n >\n {{ translatePermission('roles', role) }}\n </VChip>\n </template>\n <template #item.permissionGroups=\"{ item }: { item: DatatableItem }\">\n <slot\n name=\"permissionGroups\"\n :ids=\"item.permissionGroups\"\n >\n <VChip\n v-for=\"permissionGroupId in item.permissionGroups\"\n :key=\"permissionGroupId\"\n class=\"mr-1 mb-1\"\n size=\"small\"\n label\n >\n {{ getCachedPermissionGroup(permissionGroupId)?.title || permissionGroupId }}\n </VChip>\n </slot>\n </template>\n <template #item.permissions=\"{ item }: { item: DatatableItem }\">\n <VChip>{{ Object.keys(item.permissions).length }}</VChip>\n </template>\n <template\n v-for=\"column in systemColumnKeys\"\n :key=\"column\"\n #[`item.${column}`]=\"{ item }: { item: DatatableItem }\"\n >\n <slot\n name=\"systemColumns\"\n :column=\"column\"\n :user=\"item\"\n />\n </template>\n <template #item.createdAt=\"{ item }: { item: DatatableItem }\">\n <ADatetime :date-time=\"item.createdAt\" />\n <template v-if=\"$slots.createdBy\">\n <br />\n <slot\n :id=\"item.createdBy\"\n name=\"createdBy\"\n />\n </template>\n </template>\n <template #item.modifiedAt=\"{ item }: { item: DatatableItem }\">\n <ADatetime :date-time=\"item.modifiedAt\" />\n <template v-if=\"$slots.modifiedBy\">\n <br />\n <slot\n :id=\"item.modifiedBy\"\n name=\"modifiedBy\"\n />\n </template>\n </template>\n <template #item.actions=\"{ item }: { item: DatatableItem }\">\n <div\n v-if=\"item.id\"\n class=\"d-flex justify-end\"\n >\n <ATableCopyIdButton :id=\"item.id\" />\n <VBtn\n :to=\"detailRoute(item)\"\n class=\"ml-1\"\n icon=\"mdi-information-outline\"\n size=\"x-small\"\n variant=\"text\"\n />\n <VBtn\n v-if=\"canUpdate\"\n :to=\"editRoute(item)\"\n class=\"ml-1\"\n icon=\"mdi-pencil\"\n size=\"x-small\"\n variant=\"text\"\n />\n <!-- A disabled account cannot sign in, so it cannot be signed in as either. -->\n <VBtn\n v-if=\"impersonate && canImpersonate\"\n :disabled=\"!item.enabled\"\n class=\"ml-1\"\n icon=\"mdi-account-switch\"\n size=\"x-small\"\n variant=\"text\"\n data-cy=\"user-impersonate\"\n @click.stop=\"impersonate(item)\"\n />\n <slot\n name=\"rowActions\"\n :user=\"item\"\n />\n </div>\n </template>\n <template #bottom>\n <ADatatablePagination @change=\"getList\" />\n </template>\n </VDataTableServer>\n </div>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { useI18n } from 'vue-i18n'\nimport ABooleanValue from '@/components/ABooleanValue.vue'\nimport ARow from '@/components/ARow.vue'\n\nwithDefaults(\n defineProps<{\n readonly?: boolean\n /**\n * The system's `enabledNote`. A static string from the descriptor rather than a slot: a slot\n * would have to be filled by every admin separately, could drift from the wording in the bulk\n * dialog, and -- worst -- could be forgotten in admin-blog, which is the one place the sentence\n * matters most. The slot below stays as an escape hatch for more than a line of text.\n */\n note?: string | undefined\n }>(),\n {\n readonly: false,\n note: undefined,\n }\n)\n\nconst modelValue = defineModel<boolean>({ required: true })\n\nconst { t } = useI18n()\n</script>\n\n<template>\n <div>\n <!--\n Outside `AUserMetadataForm` on purpose: that one mirrors `BaseUserDto`, which has no\n `enabled`, and is also used by the cross-system metadata repair -- which must not write this\n field and would otherwise have to hide it.\n -->\n <ARow\n v-if=\"readonly\"\n :title=\"t('common.anzuUser.model.enabled')\"\n >\n <ABooleanValue\n :value=\"modelValue\"\n chip\n />\n </ARow>\n <VSwitch\n v-else\n v-model=\"modelValue\"\n :label=\"t('common.anzuUser.model.enabled')\"\n color=\"success\"\n density=\"compact\"\n hide-details\n data-cy=\"user-enabled\"\n />\n <div class=\"text-body-small text-medium-emphasis\">\n <slot name=\"note\">\n <template v-if=\"note\">{{ note }}</template>\n </slot>\n </div>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { useI18n } from 'vue-i18n'\nimport ABooleanValue from '@/components/ABooleanValue.vue'\nimport ARow from '@/components/ARow.vue'\n\nwithDefaults(\n defineProps<{\n readonly?: boolean\n /**\n * The system's `enabledNote`. A static string from the descriptor rather than a slot: a slot\n * would have to be filled by every admin separately, could drift from the wording in the bulk\n * dialog, and -- worst -- could be forgotten in admin-blog, which is the one place the sentence\n * matters most. The slot below stays as an escape hatch for more than a line of text.\n */\n note?: string | undefined\n }>(),\n {\n readonly: false,\n note: undefined,\n }\n)\n\nconst modelValue = defineModel<boolean>({ required: true })\n\nconst { t } = useI18n()\n</script>\n\n<template>\n <div>\n <!--\n Outside `AUserMetadataForm` on purpose: that one mirrors `BaseUserDto`, which has no\n `enabled`, and is also used by the cross-system metadata repair -- which must not write this\n field and would otherwise have to hide it.\n -->\n <ARow\n v-if=\"readonly\"\n :title=\"t('common.anzuUser.model.enabled')\"\n >\n <ABooleanValue\n :value=\"modelValue\"\n chip\n />\n </ARow>\n <VSwitch\n v-else\n v-model=\"modelValue\"\n :label=\"t('common.anzuUser.model.enabled')\"\n color=\"success\"\n density=\"compact\"\n hide-details\n data-cy=\"user-enabled\"\n />\n <div class=\"text-body-small text-medium-emphasis\">\n <slot name=\"note\">\n <template v-if=\"note\">{{ note }}</template>\n </slot>\n </div>\n </div>\n</template>\n","import useVuelidate from '@vuelidate/core'\nimport { computed, type Ref } from 'vue'\nimport { useValidate } from '@/validators/vuelidate/useValidate'\nimport type { BaseUser } from '@/types/AnzuUser'\n\nexport interface UserMetadataValidationOptions {\n /**\n * The system's profile (`requiredMetadata` on its descriptor). `false` requires only the\n * e-mail; `true` also requires first name, last name, full name, avatar text and avatar colour.\n * It applies the same way to creating and to editing -- within a system it is one rule.\n */\n required: boolean\n /** Whether the id is a field at all. When it is, and this is a create, it is required. */\n idInput: boolean\n isEdit: boolean\n /** Scope key, so an owning form can validate its own fields alongside the app's system fields. */\n scope?: string | symbol | false\n}\n\n/**\n * The `BaseUserDto` rules, per profile.\n *\n * The lengths come from the backend constraints (`email` 256, first/last 120, full 242, avatar\n * text 2-3). The backend requires only the e-mail; the front end is deliberately stricter, and\n * `requiredMetadata` is what says by how much.\n */\nexport function useUserMetadataValidation(user: Ref<BaseUser>, options: UserMetadataValidationOptions) {\n const { email, hexColor, maxLength, minLength, required } = useValidate()\n\n const rules = computed(() => {\n const requiredWhenProfileSays = options.required ? { required } : {}\n\n return {\n user: {\n // Only on a create, and only where the id is typed at all: on an edit it is a primary key\n // being displayed, and in cms the backend resolves it from SSO.\n id: options.idInput && !options.isEdit ? { required } : {},\n email: {\n required,\n email,\n maxLength: maxLength(256),\n },\n person: {\n firstName: { ...requiredWhenProfileSays, minLength: minLength(2), maxLength: maxLength(120) },\n lastName: { ...requiredWhenProfileSays, minLength: minLength(2), maxLength: maxLength(120) },\n fullName: { ...requiredWhenProfileSays, minLength: minLength(3), maxLength: maxLength(242) },\n },\n avatar: {\n // Shape, not length: `minLength(7)` lets `#GGGGGG` through and the server rejects it.\n // The rule passes an empty value, so a profile that does not require a colour still saves.\n color: { ...requiredWhenProfileSays, hexColor },\n text: { ...requiredWhenProfileSays, minLength: minLength(2), maxLength: maxLength(3) },\n },\n },\n }\n })\n\n const v$ = useVuelidate(rules, { user }, options.scope === undefined ? undefined : { $scope: options.scope })\n\n return {\n v$,\n }\n}\n","<script lang=\"ts\" setup>\nimport { onMounted, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport AAvatarColorPicker from '@/components/AAvatarColorPicker.vue'\nimport ACopyText from '@/components/ACopyText.vue'\nimport ARow from '@/components/ARow.vue'\nimport AFormTextField from '@/components/form/AFormTextField.vue'\nimport ASystemEntityScope from '@/components/form/ASystemEntityScope.vue'\nimport { ANZU_USER_ENTITY } from '@/labs/anzuUser/anzuUserApi'\nimport { useUserMetadataValidation } from '@/labs/anzuUser/anzuUserValidations'\nimport type { BaseUser } from '@/types/AnzuUser'\n\nconst props = withDefaults(\n defineProps<{\n /**\n * The system's validation profile (`requiredMetadata`). The cross-system repair form passes\n * `true` outright: it writes to nine places at once, and a loose form could put an empty name\n * into cms -- a record cms's own form would then refuse to save.\n */\n required?: boolean\n /**\n * Whether the id is typed in at all. False only in cms, whose backend resolves it from SSO.\n * With `isEdit` this makes three states, which one `readonly` could not express: not drawn,\n * editable, or shown read-only.\n */\n idInput?: boolean\n isEdit?: boolean\n readonly?: boolean\n /**\n * Random colour on an empty value, as all four admins do today. Deliberately not passed by the\n * cross-system repair form: it is always strict, so it would invent a colour on open and mark\n * it as a change in every system at once.\n */\n randomColor?: boolean\n /** The system's `metadataNote` -- the same sentence the cross-system dialog shows. */\n note?: string | undefined\n /** i18n namespace for the field labels; the backend identity does not belong here. */\n scopeSystem?: string\n scopeSubject?: string\n /** Vuelidate scope. Omitted means the page's own collector picks these rules up. */\n validationScope?: string | symbol | false | undefined\n }>(),\n {\n required: false,\n idInput: true,\n isEdit: false,\n readonly: false,\n randomColor: false,\n note: undefined,\n scopeSystem: 'common',\n scopeSubject: ANZU_USER_ENTITY,\n validationScope: undefined,\n }\n)\n\nconst user = defineModel<BaseUser>('user', { required: true })\n\nconst { t } = useI18n()\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { v$ } = useUserMetadataValidation(user, {\n required: props.required,\n idInput: props.idInput,\n isEdit: props.isEdit,\n scope: props.validationScope,\n})\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst derivedFullName = () => {\n const { firstName, lastName } = user.value.person\n if (firstName.length === 0 || lastName.length === 0) return ''\n return firstName + ' ' + lastName\n}\n\nconst derivedAvatarText = () => {\n const { firstName, lastName } = user.value.person\n if (firstName.length === 0 || lastName.length === 0) return ''\n return firstName.slice(0, 1) + lastName.slice(0, 1)\n}\n\n/**\n * A dirty flag, not a checkbox: the two derived fields stay ordinary inputs that recompute while\n * nobody has touched them, and stop the moment somebody does.\n *\n * The flags are seeded from the data rather than starting clean. A stored value that differs from\n * what the names would produce was set on purpose -- the cms fixtures carry `Anzu` + `User 1` with\n * a full name of `Anzu User1` -- and renaming the person must not silently overwrite it.\n */\nconst fullNameTouched = ref(false)\nconst avatarTextTouched = ref(false)\n\nconst seedTouchedFlags = () => {\n fullNameTouched.value = user.value.person.fullName.length > 0 && user.value.person.fullName !== derivedFullName()\n avatarTextTouched.value = user.value.avatar.text.length > 0 && user.value.avatar.text !== derivedAvatarText()\n}\n\nonMounted(seedTouchedFlags)\n\n/**\n * Re-seeded whenever the record itself is replaced, not only when its id changes.\n *\n * The id is not enough: the metadata repair dialog swaps between two systems' records *for the\n * same person*, so the id never moves. Without this the flags would still describe the record\n * before the swap, and a deliberate full name in the newly chosen source would be overwritten by\n * one derived from its names -- silently discarding the very value the operator picked as the\n * source of truth.\n *\n * Declared before the derive watcher below so it runs first: the flags are up to date before\n * anything decides whether to recompute.\n */\nwatch(\n () => user.value,\n () => seedTouchedFlags()\n)\n\n// Keyed on the names themselves rather than on an array, whose identity is new on every read --\n// that alone would make this fire on any object replacement, including one that changed nothing.\nwatch(\n () => `${user.value.person.firstName}\\u0000${user.value.person.lastName}`,\n (next, previous) => {\n if (props.readonly || next === previous) return\n if (!fullNameTouched.value) user.value.person.fullName = derivedFullName()\n if (!avatarTextTouched.value) user.value.avatar.text = derivedAvatarText()\n }\n)\n</script>\n\n<template>\n <ASystemEntityScope\n :system=\"scopeSystem\"\n :subject=\"scopeSubject\"\n >\n <VRow>\n <VCol cols=\"12\">\n <VAlert\n v-if=\"note\"\n type=\"info\"\n variant=\"tonal\"\n density=\"compact\"\n class=\"mb-4\"\n >\n {{ note }}\n </VAlert>\n <VRow>\n <VCol\n cols=\"12\"\n sm=\"3\"\n >\n <!--\n Three states, not two. Not drawn where the backend resolves the id itself; a plain\n field while creating; read-only once the record exists, because it is the primary key\n every other system looks this person up by.\n -->\n <!--\n `.number` is not decoration and it is not inert on a component: Vue applies model\n modifiers inside `emit`, so without it the field hands back the string \"42\" against a\n declared `IntegerIdNullable`, and that string is what the create body carries. Every\n admin that draws this field binds it the same way.\n -->\n <AFormTextField\n v-if=\"idInput && !isEdit && !readonly\"\n v-model.number=\"user.id\"\n type=\"number\"\n :v=\"v$.user.id\"\n :label=\"t('common.anzuUser.model.id')\"\n :hint=\"t('common.anzuUser.hint.idReadonlyOnEdit')\"\n persistent-hint\n />\n <ARow\n v-else-if=\"user.id\"\n :title=\"t('common.anzuUser.model.id')\"\n >\n <ACopyText :value=\"user.id\" />\n </ARow>\n </VCol>\n <VCol\n cols=\"12\"\n sm=\"9\"\n >\n <ARow\n v-if=\"readonly\"\n :title=\"t('common.anzuUser.model.email')\"\n :value=\"user.email\"\n />\n <AFormTextField\n v-else\n v-model=\"user.email\"\n :v=\"v$.user.email\"\n data-cy=\"user-email\"\n />\n </VCol>\n </VRow>\n <VRow>\n <VCol\n cols=\"12\"\n sm=\"3\"\n >\n <ARow\n v-if=\"readonly\"\n :title=\"t('common.anzuUser.model.person.firstName')\"\n :value=\"user.person.firstName\"\n />\n <AFormTextField\n v-else\n v-model=\"user.person.firstName\"\n :v=\"v$.user.person.firstName\"\n data-cy=\"user-firstName\"\n />\n </VCol>\n <VCol\n cols=\"12\"\n sm=\"3\"\n >\n <ARow\n v-if=\"readonly\"\n :title=\"t('common.anzuUser.model.person.lastName')\"\n :value=\"user.person.lastName\"\n />\n <AFormTextField\n v-else\n v-model=\"user.person.lastName\"\n :v=\"v$.user.person.lastName\"\n data-cy=\"user-lastName\"\n />\n </VCol>\n <VCol\n cols=\"12\"\n sm=\"6\"\n >\n <ARow\n v-if=\"readonly\"\n :title=\"t('common.anzuUser.model.person.fullName')\"\n :value=\"user.person.fullName\"\n />\n <!--\n `@update:model-value` only fires on a real edit: assigning from the watcher above goes\n through the model, not through the field, so recomputing never marks the field touched.\n -->\n <AFormTextField\n v-else\n v-model=\"user.person.fullName\"\n :v=\"v$.user.person.fullName\"\n :hint=\"fullNameTouched ? undefined : t('common.anzuUser.hint.derivedFromName')\"\n :persistent-hint=\"!fullNameTouched\"\n data-cy=\"user-fullName\"\n @update:model-value=\"fullNameTouched = true\"\n />\n </VCol>\n </VRow>\n <VRow>\n <VCol\n cols=\"12\"\n sm=\"3\"\n >\n <AAvatarColorPicker\n v-model=\"user.avatar.color\"\n :label=\"t('common.anzuUser.model.avatar.color')\"\n :readonly=\"readonly\"\n :required=\"required\"\n :random-color=\"randomColor\"\n />\n <div\n v-if=\"v$.user.avatar.color.$error\"\n class=\"text-error text-body-small\"\n >\n {{ v$.user.avatar.color.$errors[0]?.$message }}\n </div>\n </VCol>\n <VCol\n cols=\"12\"\n sm=\"3\"\n >\n <ARow\n v-if=\"readonly\"\n :title=\"t('common.anzuUser.model.avatar.text')\"\n :value=\"user.avatar.text\"\n />\n <AFormTextField\n v-else\n v-model=\"user.avatar.text\"\n :v=\"v$.user.avatar.text\"\n :maxlength=\"3\"\n :hint=\"avatarTextTouched ? undefined : t('common.anzuUser.hint.derivedFromName')\"\n :persistent-hint=\"!avatarTextTouched\"\n data-cy=\"user-avatarText\"\n @update:model-value=\"avatarTextTouched = true\"\n />\n </VCol>\n </VRow>\n </VCol>\n </VRow>\n </ASystemEntityScope>\n</template>\n","<script lang=\"ts\" setup>\nimport { onMounted, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport AAvatarColorPicker from '@/components/AAvatarColorPicker.vue'\nimport ACopyText from '@/components/ACopyText.vue'\nimport ARow from '@/components/ARow.vue'\nimport AFormTextField from '@/components/form/AFormTextField.vue'\nimport ASystemEntityScope from '@/components/form/ASystemEntityScope.vue'\nimport { ANZU_USER_ENTITY } from '@/labs/anzuUser/anzuUserApi'\nimport { useUserMetadataValidation } from '@/labs/anzuUser/anzuUserValidations'\nimport type { BaseUser } from '@/types/AnzuUser'\n\nconst props = withDefaults(\n defineProps<{\n /**\n * The system's validation profile (`requiredMetadata`). The cross-system repair form passes\n * `true` outright: it writes to nine places at once, and a loose form could put an empty name\n * into cms -- a record cms's own form would then refuse to save.\n */\n required?: boolean\n /**\n * Whether the id is typed in at all. False only in cms, whose backend resolves it from SSO.\n * With `isEdit` this makes three states, which one `readonly` could not express: not drawn,\n * editable, or shown read-only.\n */\n idInput?: boolean\n isEdit?: boolean\n readonly?: boolean\n /**\n * Random colour on an empty value, as all four admins do today. Deliberately not passed by the\n * cross-system repair form: it is always strict, so it would invent a colour on open and mark\n * it as a change in every system at once.\n */\n randomColor?: boolean\n /** The system's `metadataNote` -- the same sentence the cross-system dialog shows. */\n note?: string | undefined\n /** i18n namespace for the field labels; the backend identity does not belong here. */\n scopeSystem?: string\n scopeSubject?: string\n /** Vuelidate scope. Omitted means the page's own collector picks these rules up. */\n validationScope?: string | symbol | false | undefined\n }>(),\n {\n required: false,\n idInput: true,\n isEdit: false,\n readonly: false,\n randomColor: false,\n note: undefined,\n scopeSystem: 'common',\n scopeSubject: ANZU_USER_ENTITY,\n validationScope: undefined,\n }\n)\n\nconst user = defineModel<BaseUser>('user', { required: true })\n\nconst { t } = useI18n()\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { v$ } = useUserMetadataValidation(user, {\n required: props.required,\n idInput: props.idInput,\n isEdit: props.isEdit,\n scope: props.validationScope,\n})\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst derivedFullName = () => {\n const { firstName, lastName } = user.value.person\n if (firstName.length === 0 || lastName.length === 0) return ''\n return firstName + ' ' + lastName\n}\n\nconst derivedAvatarText = () => {\n const { firstName, lastName } = user.value.person\n if (firstName.length === 0 || lastName.length === 0) return ''\n return firstName.slice(0, 1) + lastName.slice(0, 1)\n}\n\n/**\n * A dirty flag, not a checkbox: the two derived fields stay ordinary inputs that recompute while\n * nobody has touched them, and stop the moment somebody does.\n *\n * The flags are seeded from the data rather than starting clean. A stored value that differs from\n * what the names would produce was set on purpose -- the cms fixtures carry `Anzu` + `User 1` with\n * a full name of `Anzu User1` -- and renaming the person must not silently overwrite it.\n */\nconst fullNameTouched = ref(false)\nconst avatarTextTouched = ref(false)\n\nconst seedTouchedFlags = () => {\n fullNameTouched.value = user.value.person.fullName.length > 0 && user.value.person.fullName !== derivedFullName()\n avatarTextTouched.value = user.value.avatar.text.length > 0 && user.value.avatar.text !== derivedAvatarText()\n}\n\nonMounted(seedTouchedFlags)\n\n/**\n * Re-seeded whenever the record itself is replaced, not only when its id changes.\n *\n * The id is not enough: the metadata repair dialog swaps between two systems' records *for the\n * same person*, so the id never moves. Without this the flags would still describe the record\n * before the swap, and a deliberate full name in the newly chosen source would be overwritten by\n * one derived from its names -- silently discarding the very value the operator picked as the\n * source of truth.\n *\n * Declared before the derive watcher below so it runs first: the flags are up to date before\n * anything decides whether to recompute.\n */\nwatch(\n () => user.value,\n () => seedTouchedFlags()\n)\n\n// Keyed on the names themselves rather than on an array, whose identity is new on every read --\n// that alone would make this fire on any object replacement, including one that changed nothing.\nwatch(\n () => `${user.value.person.firstName}\\u0000${user.value.person.lastName}`,\n (next, previous) => {\n if (props.readonly || next === previous) return\n if (!fullNameTouched.value) user.value.person.fullName = derivedFullName()\n if (!avatarTextTouched.value) user.value.avatar.text = derivedAvatarText()\n }\n)\n</script>\n\n<template>\n <ASystemEntityScope\n :system=\"scopeSystem\"\n :subject=\"scopeSubject\"\n >\n <VRow>\n <VCol cols=\"12\">\n <VAlert\n v-if=\"note\"\n type=\"info\"\n variant=\"tonal\"\n density=\"compact\"\n class=\"mb-4\"\n >\n {{ note }}\n </VAlert>\n <VRow>\n <VCol\n cols=\"12\"\n sm=\"3\"\n >\n <!--\n Three states, not two. Not drawn where the backend resolves the id itself; a plain\n field while creating; read-only once the record exists, because it is the primary key\n every other system looks this person up by.\n -->\n <!--\n `.number` is not decoration and it is not inert on a component: Vue applies model\n modifiers inside `emit`, so without it the field hands back the string \"42\" against a\n declared `IntegerIdNullable`, and that string is what the create body carries. Every\n admin that draws this field binds it the same way.\n -->\n <AFormTextField\n v-if=\"idInput && !isEdit && !readonly\"\n v-model.number=\"user.id\"\n type=\"number\"\n :v=\"v$.user.id\"\n :label=\"t('common.anzuUser.model.id')\"\n :hint=\"t('common.anzuUser.hint.idReadonlyOnEdit')\"\n persistent-hint\n />\n <ARow\n v-else-if=\"user.id\"\n :title=\"t('common.anzuUser.model.id')\"\n >\n <ACopyText :value=\"user.id\" />\n </ARow>\n </VCol>\n <VCol\n cols=\"12\"\n sm=\"9\"\n >\n <ARow\n v-if=\"readonly\"\n :title=\"t('common.anzuUser.model.email')\"\n :value=\"user.email\"\n />\n <AFormTextField\n v-else\n v-model=\"user.email\"\n :v=\"v$.user.email\"\n data-cy=\"user-email\"\n />\n </VCol>\n </VRow>\n <VRow>\n <VCol\n cols=\"12\"\n sm=\"3\"\n >\n <ARow\n v-if=\"readonly\"\n :title=\"t('common.anzuUser.model.person.firstName')\"\n :value=\"user.person.firstName\"\n />\n <AFormTextField\n v-else\n v-model=\"user.person.firstName\"\n :v=\"v$.user.person.firstName\"\n data-cy=\"user-firstName\"\n />\n </VCol>\n <VCol\n cols=\"12\"\n sm=\"3\"\n >\n <ARow\n v-if=\"readonly\"\n :title=\"t('common.anzuUser.model.person.lastName')\"\n :value=\"user.person.lastName\"\n />\n <AFormTextField\n v-else\n v-model=\"user.person.lastName\"\n :v=\"v$.user.person.lastName\"\n data-cy=\"user-lastName\"\n />\n </VCol>\n <VCol\n cols=\"12\"\n sm=\"6\"\n >\n <ARow\n v-if=\"readonly\"\n :title=\"t('common.anzuUser.model.person.fullName')\"\n :value=\"user.person.fullName\"\n />\n <!--\n `@update:model-value` only fires on a real edit: assigning from the watcher above goes\n through the model, not through the field, so recomputing never marks the field touched.\n -->\n <AFormTextField\n v-else\n v-model=\"user.person.fullName\"\n :v=\"v$.user.person.fullName\"\n :hint=\"fullNameTouched ? undefined : t('common.anzuUser.hint.derivedFromName')\"\n :persistent-hint=\"!fullNameTouched\"\n data-cy=\"user-fullName\"\n @update:model-value=\"fullNameTouched = true\"\n />\n </VCol>\n </VRow>\n <VRow>\n <VCol\n cols=\"12\"\n sm=\"3\"\n >\n <AAvatarColorPicker\n v-model=\"user.avatar.color\"\n :label=\"t('common.anzuUser.model.avatar.color')\"\n :readonly=\"readonly\"\n :required=\"required\"\n :random-color=\"randomColor\"\n />\n <div\n v-if=\"v$.user.avatar.color.$error\"\n class=\"text-error text-body-small\"\n >\n {{ v$.user.avatar.color.$errors[0]?.$message }}\n </div>\n </VCol>\n <VCol\n cols=\"12\"\n sm=\"3\"\n >\n <ARow\n v-if=\"readonly\"\n :title=\"t('common.anzuUser.model.avatar.text')\"\n :value=\"user.avatar.text\"\n />\n <AFormTextField\n v-else\n v-model=\"user.avatar.text\"\n :v=\"v$.user.avatar.text\"\n :maxlength=\"3\"\n :hint=\"avatarTextTouched ? undefined : t('common.anzuUser.hint.derivedFromName')\"\n :persistent-hint=\"!avatarTextTouched\"\n data-cy=\"user-avatarText\"\n @update:model-value=\"avatarTextTouched = true\"\n />\n </VCol>\n </VRow>\n </VCol>\n </VRow>\n </ASystemEntityScope>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport { usePermissionConfigActions } from '@/labs/permissionConfig/permissionConfigActions'\n\nconst props = withDefaults(\n defineProps<{\n client: AxiosClientFn\n /** Roles are per backend: they come from that system's `/adm/v1/permissions/config`. */\n system: string\n entity?: string | undefined\n endPoint?: string | undefined\n readonly?: boolean\n }>(),\n {\n entity: undefined,\n endPoint: undefined,\n readonly: false,\n }\n)\n\nconst modelValue = defineModel<string[]>({ required: true })\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { permissionConfig, translatePermission } = usePermissionConfigActions({\n client: props.client,\n system: props.system,\n entity: props.entity,\n endPoint: props.endPoint,\n})\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst items = computed(() =>\n permissionConfig.value.roles.map((role) => ({\n title: translatePermission('roles', role),\n value: role,\n }))\n)\n\nconst { t } = useI18n()\n</script>\n\n<template>\n <VAutocomplete\n v-model=\"modelValue\"\n :items=\"items\"\n item-value=\"value\"\n item-title=\"title\"\n :label=\"t('common.anzuUser.model.roles')\"\n :readonly=\"readonly\"\n :clearable=\"!readonly\"\n multiple\n chips\n closable-chips\n hide-details\n autocomplete=\"off\"\n data-cy=\"user-roles\"\n />\n</template>\n","<script lang=\"ts\" setup>\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport { usePermissionConfigActions } from '@/labs/permissionConfig/permissionConfigActions'\n\nconst props = withDefaults(\n defineProps<{\n client: AxiosClientFn\n /** Roles are per backend: they come from that system's `/adm/v1/permissions/config`. */\n system: string\n entity?: string | undefined\n endPoint?: string | undefined\n readonly?: boolean\n }>(),\n {\n entity: undefined,\n endPoint: undefined,\n readonly: false,\n }\n)\n\nconst modelValue = defineModel<string[]>({ required: true })\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { permissionConfig, translatePermission } = usePermissionConfigActions({\n client: props.client,\n system: props.system,\n entity: props.entity,\n endPoint: props.endPoint,\n})\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst items = computed(() =>\n permissionConfig.value.roles.map((role) => ({\n title: translatePermission('roles', role),\n value: role,\n }))\n)\n\nconst { t } = useI18n()\n</script>\n\n<template>\n <VAutocomplete\n v-model=\"modelValue\"\n :items=\"items\"\n item-value=\"value\"\n item-title=\"title\"\n :label=\"t('common.anzuUser.model.roles')\"\n :readonly=\"readonly\"\n :clearable=\"!readonly\"\n multiple\n chips\n closable-chips\n hide-details\n autocomplete=\"off\"\n data-cy=\"user-roles\"\n />\n</template>\n","<script lang=\"ts\" setup>\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport ASystemEntityScope from '@/components/form/ASystemEntityScope.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport AUserEnabledSwitch from '@/labs/anzuUser/AUserEnabledSwitch.vue'\nimport AUserMetadataForm from '@/labs/anzuUser/AUserMetadataForm.vue'\nimport AAnzuUserRoleAutocomplete from '@/labs/anzuUser/AAnzuUserRoleAutocomplete.vue'\nimport { ANZU_USER_ENTITY } from '@/labs/anzuUser/anzuUserApi'\nimport APermissionEditor from '@/labs/permission/APermissionEditor.vue'\nimport { usePermissionActions } from '@/labs/permission/permissionActions'\nimport APermissionGroupRemoteAutocomplete from '@/labs/permissionGroup/APermissionGroupRemoteAutocomplete.vue'\nimport { PERMISSION_GROUP_ENDPOINT } from '@/labs/permissionGroup/permissionGroupApi'\nimport type { AnzuUser } from '@/types/AnzuUser'\n\nconst props = withDefaults(\n defineProps<{\n client: AxiosClientFn\n system: string\n /**\n * The permission-group endpoint, which is not the user one.\n *\n * There is deliberately no `entity` prop here. `entity` names a *resource*, not a system, and\n * it is what a server validation failure is filed under -- so the group autocomplete, the role\n * list and the permission editor each use their own, and passing the user's down would file a\n * group's error under the user's labels.\n */\n permissionGroupEndPoint?: string\n /**\n * One component for detail and edit (decision 27). Two parallel components drift: a field\n * added to the editable one and forgotten in the other is a silent divergence with nothing to\n * fail.\n */\n readonly?: boolean\n isEdit?: boolean\n /** From the system's descriptor. */\n requiredMetadata?: boolean\n idInput?: boolean\n randomColor?: boolean\n enabledNote?: string | undefined\n metadataNote?: string | undefined\n }>(),\n {\n permissionGroupEndPoint: PERMISSION_GROUP_ENDPOINT,\n readonly: false,\n isEdit: false,\n requiredMetadata: false,\n idInput: true,\n randomColor: false,\n enabledNote: undefined,\n metadataNote: undefined,\n }\n)\n\nconst user = defineModel<AnzuUser>('user', { required: true })\n\nconst { t } = useI18n()\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { resolvePermissions } = usePermissionActions({\n client: props.client,\n system: props.system,\n endPoint: props.permissionGroupEndPoint,\n})\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\n/**\n * What the account would end up with if saved now: the grants of the groups currently ticked, plus\n * the ones set on the user. Recomputed locally so ticking a group shows its effect without a round\n * trip -- the server's own `resolvedPermissions` describes the record as it was loaded.\n */\nconst resolvedPermissions = computed(() => resolvePermissions(user.value))\n</script>\n\n<template>\n <ASystemEntityScope\n system=\"common\"\n :subject=\"ANZU_USER_ENTITY\"\n >\n <VRow>\n <VCol cols=\"12\">\n <AUserEnabledSwitch\n v-model=\"user.enabled\"\n :readonly=\"readonly\"\n :note=\"enabledNote\"\n />\n </VCol>\n <VCol cols=\"12\">\n <AUserMetadataForm\n v-model:user=\"user\"\n :required=\"requiredMetadata\"\n :id-input=\"idInput\"\n :is-edit=\"isEdit\"\n :readonly=\"readonly\"\n :random-color=\"randomColor\"\n :note=\"metadataNote\"\n />\n </VCol>\n <VCol\n v-if=\"isEdit && !readonly && $slots.copyPermissions\"\n cols=\"12\"\n >\n <!-- Copying roles and grants from another account. Only cms offers it today. -->\n <slot name=\"copyPermissions\" />\n </VCol>\n <VCol\n v-if=\"$slots.systemFields\"\n cols=\"12\"\n >\n <!--\n The system's own fields: autocompletes over that backend's entities, which the library\n cannot build. The same `readonly` reaches them, so the app swaps its autocompletes for\n chips inside.\n -->\n <slot\n name=\"systemFields\"\n :readonly=\"readonly\"\n :is-edit=\"isEdit\"\n />\n </VCol>\n <VCol cols=\"12\">\n <AAnzuUserRoleAutocomplete\n v-model=\"user.roles\"\n :client=\"client\"\n :system=\"system\"\n :readonly=\"readonly\"\n />\n </VCol>\n <VCol cols=\"12\">\n <APermissionGroupRemoteAutocomplete\n v-model=\"user.permissionGroups\"\n :client=\"client\"\n :system=\"system\"\n :end-point=\"permissionGroupEndPoint\"\n :label=\"t('common.anzuUser.model.permissionGroups')\"\n :readonly=\"readonly\"\n multiple\n clearable\n />\n </VCol>\n <VCol\n v-if=\"$slots.actions\"\n cols=\"12\"\n >\n <!-- Impersonation and anything else the app hangs off this form. -->\n <slot\n name=\"actions\"\n :user=\"user\"\n />\n </VCol>\n <VCol cols=\"12\">\n <APermissionEditor\n v-model=\"user.permissions\"\n :resolved-permissions=\"resolvedPermissions\"\n :roles=\"user.roles\"\n :client=\"client\"\n :system=\"system\"\n :readonly=\"readonly\"\n />\n </VCol>\n </VRow>\n </ASystemEntityScope>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport ASystemEntityScope from '@/components/form/ASystemEntityScope.vue'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport AUserEnabledSwitch from '@/labs/anzuUser/AUserEnabledSwitch.vue'\nimport AUserMetadataForm from '@/labs/anzuUser/AUserMetadataForm.vue'\nimport AAnzuUserRoleAutocomplete from '@/labs/anzuUser/AAnzuUserRoleAutocomplete.vue'\nimport { ANZU_USER_ENTITY } from '@/labs/anzuUser/anzuUserApi'\nimport APermissionEditor from '@/labs/permission/APermissionEditor.vue'\nimport { usePermissionActions } from '@/labs/permission/permissionActions'\nimport APermissionGroupRemoteAutocomplete from '@/labs/permissionGroup/APermissionGroupRemoteAutocomplete.vue'\nimport { PERMISSION_GROUP_ENDPOINT } from '@/labs/permissionGroup/permissionGroupApi'\nimport type { AnzuUser } from '@/types/AnzuUser'\n\nconst props = withDefaults(\n defineProps<{\n client: AxiosClientFn\n system: string\n /**\n * The permission-group endpoint, which is not the user one.\n *\n * There is deliberately no `entity` prop here. `entity` names a *resource*, not a system, and\n * it is what a server validation failure is filed under -- so the group autocomplete, the role\n * list and the permission editor each use their own, and passing the user's down would file a\n * group's error under the user's labels.\n */\n permissionGroupEndPoint?: string\n /**\n * One component for detail and edit (decision 27). Two parallel components drift: a field\n * added to the editable one and forgotten in the other is a silent divergence with nothing to\n * fail.\n */\n readonly?: boolean\n isEdit?: boolean\n /** From the system's descriptor. */\n requiredMetadata?: boolean\n idInput?: boolean\n randomColor?: boolean\n enabledNote?: string | undefined\n metadataNote?: string | undefined\n }>(),\n {\n permissionGroupEndPoint: PERMISSION_GROUP_ENDPOINT,\n readonly: false,\n isEdit: false,\n requiredMetadata: false,\n idInput: true,\n randomColor: false,\n enabledNote: undefined,\n metadataNote: undefined,\n }\n)\n\nconst user = defineModel<AnzuUser>('user', { required: true })\n\nconst { t } = useI18n()\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { resolvePermissions } = usePermissionActions({\n client: props.client,\n system: props.system,\n endPoint: props.permissionGroupEndPoint,\n})\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\n/**\n * What the account would end up with if saved now: the grants of the groups currently ticked, plus\n * the ones set on the user. Recomputed locally so ticking a group shows its effect without a round\n * trip -- the server's own `resolvedPermissions` describes the record as it was loaded.\n */\nconst resolvedPermissions = computed(() => resolvePermissions(user.value))\n</script>\n\n<template>\n <ASystemEntityScope\n system=\"common\"\n :subject=\"ANZU_USER_ENTITY\"\n >\n <VRow>\n <VCol cols=\"12\">\n <AUserEnabledSwitch\n v-model=\"user.enabled\"\n :readonly=\"readonly\"\n :note=\"enabledNote\"\n />\n </VCol>\n <VCol cols=\"12\">\n <AUserMetadataForm\n v-model:user=\"user\"\n :required=\"requiredMetadata\"\n :id-input=\"idInput\"\n :is-edit=\"isEdit\"\n :readonly=\"readonly\"\n :random-color=\"randomColor\"\n :note=\"metadataNote\"\n />\n </VCol>\n <VCol\n v-if=\"isEdit && !readonly && $slots.copyPermissions\"\n cols=\"12\"\n >\n <!-- Copying roles and grants from another account. Only cms offers it today. -->\n <slot name=\"copyPermissions\" />\n </VCol>\n <VCol\n v-if=\"$slots.systemFields\"\n cols=\"12\"\n >\n <!--\n The system's own fields: autocompletes over that backend's entities, which the library\n cannot build. The same `readonly` reaches them, so the app swaps its autocompletes for\n chips inside.\n -->\n <slot\n name=\"systemFields\"\n :readonly=\"readonly\"\n :is-edit=\"isEdit\"\n />\n </VCol>\n <VCol cols=\"12\">\n <AAnzuUserRoleAutocomplete\n v-model=\"user.roles\"\n :client=\"client\"\n :system=\"system\"\n :readonly=\"readonly\"\n />\n </VCol>\n <VCol cols=\"12\">\n <APermissionGroupRemoteAutocomplete\n v-model=\"user.permissionGroups\"\n :client=\"client\"\n :system=\"system\"\n :end-point=\"permissionGroupEndPoint\"\n :label=\"t('common.anzuUser.model.permissionGroups')\"\n :readonly=\"readonly\"\n multiple\n clearable\n />\n </VCol>\n <VCol\n v-if=\"$slots.actions\"\n cols=\"12\"\n >\n <!-- Impersonation and anything else the app hangs off this form. -->\n <slot\n name=\"actions\"\n :user=\"user\"\n />\n </VCol>\n <VCol cols=\"12\">\n <APermissionEditor\n v-model=\"user.permissions\"\n :resolved-permissions=\"resolvedPermissions\"\n :roles=\"user.roles\"\n :client=\"client\"\n :system=\"system\"\n :readonly=\"readonly\"\n />\n </VCol>\n </VRow>\n </ASystemEntityScope>\n</template>\n","<script lang=\"ts\" setup>\nimport { ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n// `ABtnPrimary` / `ABtnTertiary` are Vuetify aliases registered globally by\n// `commonVuetifyConfig`, so they are not imported.\nimport ADialogToolbar from '@/components/ADialogToolbar.vue'\nimport { useAlerts } from '@/composables/system/alerts'\nimport type { AnzuUser } from '@/types/AnzuUser'\nimport type { IntegerIdNullable } from '@/types/common'\nimport { cloneDeep, isNull } from '@/utils/common'\n\nconst props = defineProps<{\n /**\n * Reads the source account from the server. It is a prop because the shape of a user differs per\n * system -- cms answers `CmsUserDto`, dam `DamUserDto` -- and because the page already holds an\n * api instance pointed at the right backend.\n */\n fetchSourceUser: (id: number) => Promise<AnzuUser>\n /**\n * Copies whatever the system keeps beyond roles, groups and grants. cms has nineteen such\n * fields; systems with none leave this out.\n */\n copySystemFields?: ((target: AnzuUser, source: AnzuUser) => void) | undefined\n /** i18n keys listed in the dialog, so the operator sees exactly what will be overwritten. */\n copiedSettingKeys?: string[]\n}>()\n\nconst user = defineModel<AnzuUser>('user', { required: true })\n\nconst dialog = ref(false)\nconst loading = ref(false)\nconst srcUserId = ref<IntegerIdNullable>(null)\n\nconst { t } = useI18n()\nconst { showErrorsDefault } = useAlerts()\n\nconst showDialog = () => {\n srcUserId.value = null\n dialog.value = true\n}\n\nconst confirmDialog = async () => {\n if (isNull(srcUserId.value)) return\n loading.value = true\n try {\n const source = await props.fetchSourceUser(srcUserId.value)\n user.value.roles = cloneDeep(source.roles)\n user.value.permissionGroups = cloneDeep(source.permissionGroups)\n user.value.permissions = cloneDeep(source.permissions)\n props.copySystemFields?.(user.value, source)\n dialog.value = false\n } catch (error) {\n showErrorsDefault(error)\n } finally {\n loading.value = false\n }\n}\n</script>\n\n<template>\n <div>\n <p class=\"text-body-small\">\n {{ t('common.anzuUser.copyPermissions.desc') }}\n </p>\n <div class=\"text-right\">\n <ABtnTertiary\n size=\"small\"\n data-cy=\"user-copy-permissions\"\n @click.stop=\"showDialog\"\n >\n {{ t('common.anzuUser.copyPermissions.title') }}\n </ABtnTertiary>\n </div>\n <VDialog\n v-if=\"dialog\"\n :model-value=\"dialog\"\n :max-width=\"900\"\n >\n <VCard>\n <ADialogToolbar @on-cancel=\"dialog = false\">\n {{ t('common.anzuUser.copyPermissions.title') }}\n </ADialogToolbar>\n <VCardText>\n <!-- Whose account to copy from. The app owns the picker: it knows its own user list. -->\n <slot\n name=\"sourcePicker\"\n :select=\"(id: IntegerIdNullable) => (srcUserId = id)\"\n :selected=\"srcUserId\"\n />\n <strong>{{ t('common.anzuUser.copyPermissions.copiedSettings') }}</strong>\n <ul class=\"ml-5 text-body-small\">\n <li>{{ t('common.anzuUser.model.roles') }}</li>\n <li>{{ t('common.anzuUser.model.permissionGroups') }}</li>\n <li>{{ t('common.anzuUser.model.permissions') }}</li>\n <li\n v-for=\"key in copiedSettingKeys\"\n :key=\"key\"\n >\n {{ t(key) }}\n </li>\n </ul>\n </VCardText>\n <VCardActions>\n <ABtnTertiary @click.stop=\"dialog = false\">\n {{ t('common.button.cancel') }}\n </ABtnTertiary>\n <VSpacer />\n <ABtnPrimary\n :disabled=\"isNull(srcUserId)\"\n :loading=\"loading\"\n data-cy=\"user-copy-permissions-confirm\"\n @click.stop=\"confirmDialog\"\n >\n {{ t('common.button.confirm') }}\n </ABtnPrimary>\n </VCardActions>\n </VCard>\n </VDialog>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n// `ABtnPrimary` / `ABtnTertiary` are Vuetify aliases registered globally by\n// `commonVuetifyConfig`, so they are not imported.\nimport ADialogToolbar from '@/components/ADialogToolbar.vue'\nimport { useAlerts } from '@/composables/system/alerts'\nimport type { AnzuUser } from '@/types/AnzuUser'\nimport type { IntegerIdNullable } from '@/types/common'\nimport { cloneDeep, isNull } from '@/utils/common'\n\nconst props = defineProps<{\n /**\n * Reads the source account from the server. It is a prop because the shape of a user differs per\n * system -- cms answers `CmsUserDto`, dam `DamUserDto` -- and because the page already holds an\n * api instance pointed at the right backend.\n */\n fetchSourceUser: (id: number) => Promise<AnzuUser>\n /**\n * Copies whatever the system keeps beyond roles, groups and grants. cms has nineteen such\n * fields; systems with none leave this out.\n */\n copySystemFields?: ((target: AnzuUser, source: AnzuUser) => void) | undefined\n /** i18n keys listed in the dialog, so the operator sees exactly what will be overwritten. */\n copiedSettingKeys?: string[]\n}>()\n\nconst user = defineModel<AnzuUser>('user', { required: true })\n\nconst dialog = ref(false)\nconst loading = ref(false)\nconst srcUserId = ref<IntegerIdNullable>(null)\n\nconst { t } = useI18n()\nconst { showErrorsDefault } = useAlerts()\n\nconst showDialog = () => {\n srcUserId.value = null\n dialog.value = true\n}\n\nconst confirmDialog = async () => {\n if (isNull(srcUserId.value)) return\n loading.value = true\n try {\n const source = await props.fetchSourceUser(srcUserId.value)\n user.value.roles = cloneDeep(source.roles)\n user.value.permissionGroups = cloneDeep(source.permissionGroups)\n user.value.permissions = cloneDeep(source.permissions)\n props.copySystemFields?.(user.value, source)\n dialog.value = false\n } catch (error) {\n showErrorsDefault(error)\n } finally {\n loading.value = false\n }\n}\n</script>\n\n<template>\n <div>\n <p class=\"text-body-small\">\n {{ t('common.anzuUser.copyPermissions.desc') }}\n </p>\n <div class=\"text-right\">\n <ABtnTertiary\n size=\"small\"\n data-cy=\"user-copy-permissions\"\n @click.stop=\"showDialog\"\n >\n {{ t('common.anzuUser.copyPermissions.title') }}\n </ABtnTertiary>\n </div>\n <VDialog\n v-if=\"dialog\"\n :model-value=\"dialog\"\n :max-width=\"900\"\n >\n <VCard>\n <ADialogToolbar @on-cancel=\"dialog = false\">\n {{ t('common.anzuUser.copyPermissions.title') }}\n </ADialogToolbar>\n <VCardText>\n <!-- Whose account to copy from. The app owns the picker: it knows its own user list. -->\n <slot\n name=\"sourcePicker\"\n :select=\"(id: IntegerIdNullable) => (srcUserId = id)\"\n :selected=\"srcUserId\"\n />\n <strong>{{ t('common.anzuUser.copyPermissions.copiedSettings') }}</strong>\n <ul class=\"ml-5 text-body-small\">\n <li>{{ t('common.anzuUser.model.roles') }}</li>\n <li>{{ t('common.anzuUser.model.permissionGroups') }}</li>\n <li>{{ t('common.anzuUser.model.permissions') }}</li>\n <li\n v-for=\"key in copiedSettingKeys\"\n :key=\"key\"\n >\n {{ t(key) }}\n </li>\n </ul>\n </VCardText>\n <VCardActions>\n <ABtnTertiary @click.stop=\"dialog = false\">\n {{ t('common.button.cancel') }}\n </ABtnTertiary>\n <VSpacer />\n <ABtnPrimary\n :disabled=\"isNull(srcUserId)\"\n :loading=\"loading\"\n data-cy=\"user-copy-permissions-confirm\"\n @click.stop=\"confirmDialog\"\n >\n {{ t('common.button.confirm') }}\n </ABtnPrimary>\n </VCardActions>\n </VCard>\n </VDialog>\n </div>\n</template>\n","import type { RouteLocationRaw } from 'vue-router'\nimport type { AxiosClientFn } from '@/labs/api/client'\nimport type { AnzuUser } from '@/types/AnzuUser'\n\n/**\n * A state a system has on top of `enabled`, shown next to it. Today only two systems have one:\n * blog's `status` (guest / waiting_approval / active / ban / gdpr_deleted) and forum's\n * `settings.banned`. Both arrive with the record -- they are `#[Serialize]` on the entity -- so\n * rendering one costs no extra call.\n */\nexport interface UserSystemExtraState {\n /** \"blogger\", \"commenter\" -- the app translates it; the library only prints it. */\n label: string\n /** \"active\", \"banned\". */\n value: string\n tone: 'ok' | 'warn' | 'error'\n}\n\n/** `/adm/v1/anzu-user`. Absent in contentHub, which has only `/adm/users`. */\nexport interface AnzuUserEndpointPair {\n get: string\n put: string\n post: string\n}\n\n/** `/adm/users`. Only cms, dam and contentHub have it; it is the only path with a `PATCH`. */\nexport interface BaseUserEndpointPair extends AnzuUserEndpointPair {\n patch: string\n}\n\n/**\n * Paired GET + write paths, never loose urls.\n *\n * This is what makes the write invariant expressible: a write body is always the whole object the\n * preceding GET on the *same path* returned. The two paths return different DTOs -- `/adm/users`\n * in cms answers `CmsUserDto` and its PUT runs `updateRelations()` over seven scalars and twelve\n * collections -- so a GET from one path and a PUT to the other silently drops whatever the other\n * DTO carries.\n */\nexport interface UserSystemEndpoints {\n anzuUser: AnzuUserEndpointPair | null\n base: BaseUserEndpointPair | null\n permissionGroup: string\n /**\n * Per system, not one constant. contentHub's is `/adm/users/current`, and the admins had this\n * hard-coded as a single path shared by every backend.\n */\n currentUser: string\n list: string\n /**\n * Which pair the probe reads through. A field rather than something derived, because in dam the\n * choice is visible to the operator: `GET /adm/v1/anzu-user/{id}` has no gating at all while\n * `GET /adm/users/{id}` sits behind `DAM_USER_READ`, so only the second can ever render \"you\n * have no access\". The rule is \"the probe goes the way the write goes\".\n */\n probe: 'anzuUser' | 'base'\n}\n\n/**\n * Where \"manage in X\" leads. A callback over the user rather than a static value: a global\n * descriptor does not know which row is being drawn.\n *\n * The return type is a discriminated union so the two cannot be confused in code either -- an\n * internal target is pushed through the router, an external one is an `<a href>` opened in a new\n * tab, and `href: null` is a configured-but-missing url, which renders disabled with a reason\n * rather than as a dead button.\n */\nexport type UserSystemManageTarget =\n | { kind: 'internal'; to: RouteLocationRaw }\n | { kind: 'external'; href: string | null }\n\nexport interface UserSystemDescriptor<TSystem extends string = string, TUser extends AnzuUser = AnzuUser> {\n system: TSystem\n client: AxiosClientFn\n /** Reaches every `useApiRequest` built for this system; see `permissionGroupApi`. */\n entity: string\n /**\n * Whether the system is switched on in this admin's configuration. Mandatory, not optional: the\n * \"disabled in configuration\" state has to be *shown*, so leaving the system out of the array is\n * not the same thing. The value is app-specific (`envConfig[system].enabled`) and the library has\n * no way to read it.\n */\n isEnabled: () => boolean\n /**\n * Which validation profile this system's form uses. `false` means only the e-mail is required;\n * `true` adds first name, last name, full name, avatar text and avatar colour. It is a property\n * of the system, and it is the same for creating and for editing.\n */\n requiredMetadata: boolean\n /**\n * Whether the id is typed in. False only in cms, where the backend looks it up in SSO from the\n * e-mail. On an edit the field is read-only regardless.\n */\n idInput: boolean\n extraState?: (user: TUser) => UserSystemExtraState | null\n /** Above the `enabled` switch and in the row of a bulk action. */\n enabledNote?: string\n /** In the metadata repair dialog and in the owning form. */\n metadataNote?: string\n /** In the confirmation dialog when an account is created here. */\n createNote?: string\n endpoints: UserSystemEndpoints\n /** Added in task 2: the system's name as the app translates it. */\n label?: string\n /** Added in task 2. */\n manage?: (user: TUser) => UserSystemManageTarget\n}\n\n/**\n * The erasure boundary for a heterogeneous array.\n *\n * Descriptors differ in `TUser` -- blog's `extraState` reaches for `status`, forum's for\n * `settings.banned` -- so an array of them cannot keep each one's type. Without a single place to\n * erase it, `strict: true` would demand a cast in every admin, at every call site. This does it\n * once, where the array is built.\n */\nexport type AnyUserSystemDescriptor = UserSystemDescriptor<string, AnzuUser>\n\nexport const defineUserSystemDescriptor = <TSystem extends string, TUser extends AnzuUser>(\n descriptor: UserSystemDescriptor<TSystem, TUser>\n): AnyUserSystemDescriptor => descriptor as unknown as AnyUserSystemDescriptor\n\n/**\n * The pair the probe reads through, with the one invariant the type cannot state: both branches of\n * `endpoints` are nullable, so nothing stops a descriptor naming `probe: 'base'` on a system that\n * has no `/adm/users`. Every admin's descriptor test asserts this resolves for each of its\n * systems.\n */\nexport const resolveProbeEndpoint = (descriptor: AnyUserSystemDescriptor): AnzuUserEndpointPair => {\n const pair = descriptor.endpoints[descriptor.endpoints.probe]\n if (pair === null) {\n throw new Error(\n `[userSystemDescriptor] '${descriptor.system}' names probe '${descriptor.endpoints.probe}', which it does not have.`\n )\n }\n return pair\n}\n\n/**\n * Metadata is written through `PATCH /adm/users/{id}` wherever that path exists: the method behind\n * it can only write e-mail, locale, avatar and person -- it has no setter for roles, permissions,\n * groups or `enabled`. That is a stronger guarantee than being careful about what goes in the body.\n */\nexport const resolveMetadataWrite = (\n descriptor: AnyUserSystemDescriptor\n): { method: 'PATCH'; get: string; url: string } | { method: 'PUT'; get: string; url: string } => {\n const base = descriptor.endpoints.base\n if (base !== null) return { method: 'PATCH', get: base.get, url: base.patch }\n const anzuUser = descriptor.endpoints.anzuUser\n if (anzuUser === null) {\n throw new Error(`[userSystemDescriptor] '${descriptor.system}' has neither write path.`)\n }\n return { method: 'PUT', get: anzuUser.get, url: anzuUser.put }\n}\n\n/**\n * `enabled` has no `PATCH` anywhere: the only method that calls `setEnabled` is the full update.\n * So it goes GET + PUT, on the same path, with the whole DTO the GET returned.\n */\nexport const resolveEnabledWrite = (descriptor: AnyUserSystemDescriptor): { get: string; url: string } => {\n const base = descriptor.endpoints.base\n if (base !== null) return { get: base.get, url: base.put }\n const anzuUser = descriptor.endpoints.anzuUser\n if (anzuUser === null) {\n throw new Error(`[userSystemDescriptor] '${descriptor.system}' has neither write path.`)\n }\n return { get: anzuUser.get, url: anzuUser.put }\n}\n\n/** Creating an account goes the same way a write does. */\nexport const resolveCreateEndpoint = (descriptor: AnyUserSystemDescriptor): string => {\n const base = descriptor.endpoints.base\n if (base !== null) return base.post\n const anzuUser = descriptor.endpoints.anzuUser\n if (anzuUser === null) {\n throw new Error(`[userSystemDescriptor] '${descriptor.system}' has no create path.`)\n }\n return anzuUser.post\n}\n","<script lang=\"ts\" setup>\nimport { ref, watch } from 'vue'\nimport useVuelidate from '@vuelidate/core'\nimport { useI18n } from 'vue-i18n'\nimport ADialogToolbar from '@/components/ADialogToolbar.vue'\nimport AUserMetadataForm from '@/labs/anzuUser/AUserMetadataForm.vue'\nimport { useAnzuUserFactory } from '@/model/factory/AnzuUserFactory'\nimport type { BaseUser } from '@/types/AnzuUser'\nimport type { IntegerIdNullable } from '@/types/common'\nimport { cloneDeep, isNull } from '@/utils/common'\n\nconst props = withDefaults(\n defineProps<{\n /** The system's name as the app translates it. */\n systemLabel: string\n /** The system's `createNote` -- what is special about having an account there. */\n createNote?: string | undefined\n /**\n * Whether the account about to be created will be switched on.\n *\n * The sentence about a working public account is composed here rather than carried by the\n * descriptor, because it depends on the value being sent: creating from a panel copies the\n * source account's `enabled` -- usually true -- while creating after a search that found\n * nobody sends false, since there is no source to copy from.\n */\n willBeEnabled?: boolean\n loading?: boolean\n disabled?: boolean\n /** The target system's `requiredMetadata`: the profile the form validates against. */\n requiredMetadata?: boolean\n /** The person's id. Always drawn and never editable here -- this path is about somebody who exists. */\n userId?: IntegerIdNullable\n /**\n * What is already known about them, to prefill the form.\n *\n * Not the body that gets posted: the caller reads the source again immediately before the POST.\n * This is what the operator sees and completes, and completing it is the point -- a person known\n * only in blog usually has an empty `person`, while the target may require it, so a silent copy\n * would create a record the target's own form then refuses to save.\n */\n source?: BaseUser | null\n }>(),\n {\n createNote: undefined,\n willBeEnabled: false,\n loading: false,\n disabled: false,\n requiredMetadata: false,\n userId: null,\n source: null,\n }\n)\n\nconst emit = defineEmits<{\n (e: 'confirm', user: BaseUser): void\n}>()\n\nconst { t } = useI18n()\nconst { createAnzuUser } = useAnzuUserFactory()\nconst dialog = ref(false)\n\nconst user = ref<BaseUser>(createAnzuUser())\nconst v$ = useVuelidate()\n\n// Seeded on every open, not once: the row behind it is re-probed after each write, and a stale\n// prefill would put what the account looked like two writes ago into the body.\nwatch(dialog, (open) => {\n if (!open) return\n const seed = isNull(props.source) ? createAnzuUser() : cloneDeep(props.source)\n if (!isNull(props.userId)) seed.id = props.userId\n user.value = seed\n v$.value.$reset()\n})\n\nconst confirm = async () => {\n if (!(await v$.value.$validate())) return\n dialog.value = false\n emit('confirm', cloneDeep(user.value))\n}\n</script>\n\n<template>\n <div class=\"d-inline-flex\">\n <VBtn\n :disabled=\"disabled\"\n :loading=\"loading\"\n size=\"small\"\n variant=\"flat\"\n color=\"primary\"\n data-cy=\"user-system-create\"\n @click.stop=\"dialog = true\"\n >\n {{ t('common.userSystem.button.create') }}\n </VBtn>\n <VDialog\n v-model=\"dialog\"\n :max-width=\"640\"\n >\n <VCard>\n <ADialogToolbar @on-cancel=\"dialog = false\">\n {{ t('common.userSystem.create.title', { system: systemLabel }) }}\n </ADialogToolbar>\n <VCardText>\n <!--\n The id is shown and locked: the person exists, this is their global id, and the whole\n operation is about giving that same identity an account here.\n -->\n <AUserMetadataForm\n v-model:user=\"user\"\n :required=\"requiredMetadata\"\n id-input\n is-edit\n />\n <!--\n Said out loud, because neither is obvious and neither can be undone: the account is\n created with nothing granted, and no admin in the fleet can delete an AnzuUser.\n -->\n <p class=\"mt-2\">{{ t('common.userSystem.create.note') }}</p>\n <p\n v-if=\"createNote\"\n class=\"mt-2\"\n >\n {{ createNote }}\n </p>\n <p class=\"mt-2\">\n {{\n props.willBeEnabled\n ? t('common.userSystem.create.willBeEnabled')\n : t('common.userSystem.create.willBeDisabled')\n }}\n </p>\n </VCardText>\n <VCardActions>\n <ABtnTertiary @click.stop=\"dialog = false\">\n {{ t('common.button.cancel') }}\n </ABtnTertiary>\n <VSpacer />\n <ABtnPrimary\n :loading=\"loading\"\n data-cy=\"user-system-create-confirm\"\n @click.stop=\"confirm\"\n >\n {{ t('common.button.confirm') }}\n </ABtnPrimary>\n </VCardActions>\n </VCard>\n </VDialog>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { ref, watch } from 'vue'\nimport useVuelidate from '@vuelidate/core'\nimport { useI18n } from 'vue-i18n'\nimport ADialogToolbar from '@/components/ADialogToolbar.vue'\nimport AUserMetadataForm from '@/labs/anzuUser/AUserMetadataForm.vue'\nimport { useAnzuUserFactory } from '@/model/factory/AnzuUserFactory'\nimport type { BaseUser } from '@/types/AnzuUser'\nimport type { IntegerIdNullable } from '@/types/common'\nimport { cloneDeep, isNull } from '@/utils/common'\n\nconst props = withDefaults(\n defineProps<{\n /** The system's name as the app translates it. */\n systemLabel: string\n /** The system's `createNote` -- what is special about having an account there. */\n createNote?: string | undefined\n /**\n * Whether the account about to be created will be switched on.\n *\n * The sentence about a working public account is composed here rather than carried by the\n * descriptor, because it depends on the value being sent: creating from a panel copies the\n * source account's `enabled` -- usually true -- while creating after a search that found\n * nobody sends false, since there is no source to copy from.\n */\n willBeEnabled?: boolean\n loading?: boolean\n disabled?: boolean\n /** The target system's `requiredMetadata`: the profile the form validates against. */\n requiredMetadata?: boolean\n /** The person's id. Always drawn and never editable here -- this path is about somebody who exists. */\n userId?: IntegerIdNullable\n /**\n * What is already known about them, to prefill the form.\n *\n * Not the body that gets posted: the caller reads the source again immediately before the POST.\n * This is what the operator sees and completes, and completing it is the point -- a person known\n * only in blog usually has an empty `person`, while the target may require it, so a silent copy\n * would create a record the target's own form then refuses to save.\n */\n source?: BaseUser | null\n }>(),\n {\n createNote: undefined,\n willBeEnabled: false,\n loading: false,\n disabled: false,\n requiredMetadata: false,\n userId: null,\n source: null,\n }\n)\n\nconst emit = defineEmits<{\n (e: 'confirm', user: BaseUser): void\n}>()\n\nconst { t } = useI18n()\nconst { createAnzuUser } = useAnzuUserFactory()\nconst dialog = ref(false)\n\nconst user = ref<BaseUser>(createAnzuUser())\nconst v$ = useVuelidate()\n\n// Seeded on every open, not once: the row behind it is re-probed after each write, and a stale\n// prefill would put what the account looked like two writes ago into the body.\nwatch(dialog, (open) => {\n if (!open) return\n const seed = isNull(props.source) ? createAnzuUser() : cloneDeep(props.source)\n if (!isNull(props.userId)) seed.id = props.userId\n user.value = seed\n v$.value.$reset()\n})\n\nconst confirm = async () => {\n if (!(await v$.value.$validate())) return\n dialog.value = false\n emit('confirm', cloneDeep(user.value))\n}\n</script>\n\n<template>\n <div class=\"d-inline-flex\">\n <VBtn\n :disabled=\"disabled\"\n :loading=\"loading\"\n size=\"small\"\n variant=\"flat\"\n color=\"primary\"\n data-cy=\"user-system-create\"\n @click.stop=\"dialog = true\"\n >\n {{ t('common.userSystem.button.create') }}\n </VBtn>\n <VDialog\n v-model=\"dialog\"\n :max-width=\"640\"\n >\n <VCard>\n <ADialogToolbar @on-cancel=\"dialog = false\">\n {{ t('common.userSystem.create.title', { system: systemLabel }) }}\n </ADialogToolbar>\n <VCardText>\n <!--\n The id is shown and locked: the person exists, this is their global id, and the whole\n operation is about giving that same identity an account here.\n -->\n <AUserMetadataForm\n v-model:user=\"user\"\n :required=\"requiredMetadata\"\n id-input\n is-edit\n />\n <!--\n Said out loud, because neither is obvious and neither can be undone: the account is\n created with nothing granted, and no admin in the fleet can delete an AnzuUser.\n -->\n <p class=\"mt-2\">{{ t('common.userSystem.create.note') }}</p>\n <p\n v-if=\"createNote\"\n class=\"mt-2\"\n >\n {{ createNote }}\n </p>\n <p class=\"mt-2\">\n {{\n props.willBeEnabled\n ? t('common.userSystem.create.willBeEnabled')\n : t('common.userSystem.create.willBeDisabled')\n }}\n </p>\n </VCardText>\n <VCardActions>\n <ABtnTertiary @click.stop=\"dialog = false\">\n {{ t('common.button.cancel') }}\n </ABtnTertiary>\n <VSpacer />\n <ABtnPrimary\n :loading=\"loading\"\n data-cy=\"user-system-create-confirm\"\n @click.stop=\"confirm\"\n >\n {{ t('common.button.confirm') }}\n </ABtnPrimary>\n </VCardActions>\n </VCard>\n </VDialog>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport type { UserSystemManageTarget } from '@/labs/anzuUser/userSystemDescriptor'\n\nconst props = withDefaults(\n defineProps<{\n target: UserSystemManageTarget\n /** The system's name as the app translates it. */\n systemLabel: string\n /** \"Edit\" instead of \"Manage in X\" -- used where the operator is already in that admin. */\n internalLabel?: string | undefined\n }>(),\n {\n internalLabel: undefined,\n }\n)\n\nconst { t } = useI18n()\n\nconst isExternal = computed(() => props.target.kind === 'external')\n\n/**\n * A missing url is a configured-but-absent template, and it says so rather than sitting there dead.\n * The prototype this replaces did `:disabled=\"!userEditUrl\"` and gave no reason -- while the\n * variable it depended on was in fact misconfigured in every environment.\n */\nconst missingUrl = computed(() => props.target.kind === 'external' && props.target.href === null)\n\nconst href = computed(() => (props.target.kind === 'external' ? (props.target.href ?? undefined) : undefined))\nconst to = computed(() => (props.target.kind === 'internal' ? props.target.to : undefined))\n\nconst label = computed(() =>\n isExternal.value\n ? t('common.userSystem.button.manageIn', { system: props.systemLabel })\n : (props.internalLabel ?? t('common.userSystem.button.edit'))\n)\n</script>\n\n<template>\n <VTooltip\n :disabled=\"!missingUrl\"\n location=\"top\"\n >\n <template #activator=\"{ props: tooltipProps }\">\n <!--\n An anchor, not a click handler. This is what leaves the page, so middle-click, open in a new\n tab and the screen reader's announcement all have to work -- which is also why the tab bar\n never navigates: `VTab` renders `role=\"tab\"` and could not carry this.\n -->\n <span v-bind=\"tooltipProps\">\n <VBtn\n :to=\"to\"\n :href=\"href\"\n :target=\"isExternal ? '_blank' : undefined\"\n :rel=\"isExternal ? 'noopener' : undefined\"\n :append-icon=\"isExternal ? 'mdi-open-in-new' : undefined\"\n :variant=\"isExternal ? 'outlined' : 'flat'\"\n :color=\"isExternal ? undefined : 'primary'\"\n :disabled=\"missingUrl\"\n size=\"small\"\n data-cy=\"user-system-manage\"\n >\n {{ label }}\n </VBtn>\n </span>\n </template>\n {{ t('common.userSystem.manageMissingUrl') }}\n </VTooltip>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport type { UserSystemManageTarget } from '@/labs/anzuUser/userSystemDescriptor'\n\nconst props = withDefaults(\n defineProps<{\n target: UserSystemManageTarget\n /** The system's name as the app translates it. */\n systemLabel: string\n /** \"Edit\" instead of \"Manage in X\" -- used where the operator is already in that admin. */\n internalLabel?: string | undefined\n }>(),\n {\n internalLabel: undefined,\n }\n)\n\nconst { t } = useI18n()\n\nconst isExternal = computed(() => props.target.kind === 'external')\n\n/**\n * A missing url is a configured-but-absent template, and it says so rather than sitting there dead.\n * The prototype this replaces did `:disabled=\"!userEditUrl\"` and gave no reason -- while the\n * variable it depended on was in fact misconfigured in every environment.\n */\nconst missingUrl = computed(() => props.target.kind === 'external' && props.target.href === null)\n\nconst href = computed(() => (props.target.kind === 'external' ? (props.target.href ?? undefined) : undefined))\nconst to = computed(() => (props.target.kind === 'internal' ? props.target.to : undefined))\n\nconst label = computed(() =>\n isExternal.value\n ? t('common.userSystem.button.manageIn', { system: props.systemLabel })\n : (props.internalLabel ?? t('common.userSystem.button.edit'))\n)\n</script>\n\n<template>\n <VTooltip\n :disabled=\"!missingUrl\"\n location=\"top\"\n >\n <template #activator=\"{ props: tooltipProps }\">\n <!--\n An anchor, not a click handler. This is what leaves the page, so middle-click, open in a new\n tab and the screen reader's announcement all have to work -- which is also why the tab bar\n never navigates: `VTab` renders `role=\"tab\"` and could not carry this.\n -->\n <span v-bind=\"tooltipProps\">\n <VBtn\n :to=\"to\"\n :href=\"href\"\n :target=\"isExternal ? '_blank' : undefined\"\n :rel=\"isExternal ? 'noopener' : undefined\"\n :append-icon=\"isExternal ? 'mdi-open-in-new' : undefined\"\n :variant=\"isExternal ? 'outlined' : 'flat'\"\n :color=\"isExternal ? undefined : 'primary'\"\n :disabled=\"missingUrl\"\n size=\"small\"\n data-cy=\"user-system-manage\"\n >\n {{ label }}\n </VBtn>\n </span>\n </template>\n {{ t('common.userSystem.manageMissingUrl') }}\n </VTooltip>\n</template>\n","/**\n * Four independent axes inside, one derived state outside.\n *\n * The axes are kept apart because they answer different questions and can move independently: a\n * system can be loaded and forbidden, or absent and still enabled in configuration. Collapsing\n * them into one enum early is what made the per-admin prototypes conflate \"no access\" with \"no\n * account here\" -- admin-cms's panel leaves `userExists` at null on any non-404 and offers\n * \"Create\" in a system the operator cannot even read.\n */\nexport const UserSystemLoad = {\n Idle: 'idle',\n Loading: 'loading',\n Loaded: 'loaded',\n Error: 'error',\n} as const\nexport type UserSystemLoadType = (typeof UserSystemLoad)[keyof typeof UserSystemLoad]\n\nexport const UserSystemPresence = {\n Present: 'present',\n Absent: 'absent',\n Unknown: 'unknown',\n} as const\nexport type UserSystemPresenceType = (typeof UserSystemPresence)[keyof typeof UserSystemPresence]\n\nexport const UserSystemAccess = {\n Ok: 'ok',\n Forbidden: 'forbidden',\n Unauthenticated: 'unauthenticated',\n Unavailable: 'unavailable',\n ConfigDisabled: 'configDisabled',\n} as const\nexport type UserSystemAccessType = (typeof UserSystemAccess)[keyof typeof UserSystemAccess]\n\n/**\n * `enabled` is one of the axes and not a detail: without it the two states the cross-system view\n * shows most often -- \"the account is here and on\" versus \"here and off\" -- cannot be told apart.\n *\n * Ownership is deliberately *not* an axis. None of the six visual states below distinguishes it;\n * it decides which button is drawn, and that is the descriptor's `manage` union.\n */\nexport interface UserSystemAxes {\n load: UserSystemLoadType\n presence: UserSystemPresenceType\n access: UserSystemAccessType\n enabled: boolean | 'unknown'\n}\n\nexport const UserSystemState = {\n /** Nothing has been asked yet, or the answer is still on its way. */\n Loading: 'loading',\n Enabled: 'enabled',\n Disabled: 'disabled',\n Absent: 'absent',\n Forbidden: 'forbidden',\n Unavailable: 'unavailable',\n ConfigDisabled: 'configDisabled',\n} as const\nexport type UserSystemStateType = (typeof UserSystemState)[keyof typeof UserSystemState]\n\nexport const emptyUserSystemAxes = (): UserSystemAxes => ({\n load: UserSystemLoad.Idle,\n presence: UserSystemPresence.Unknown,\n access: UserSystemAccess.Ok,\n enabled: 'unknown',\n})\n\n/**\n * The single closed function from axes to what is drawn. Order matters: a system switched off in\n * configuration is reported as such whatever else is known about it, and access is decided before\n * presence because a forbidden record is not an absent one.\n */\nexport const resolveUserSystemState = (axes: UserSystemAxes): UserSystemStateType => {\n if (axes.access === UserSystemAccess.ConfigDisabled) return UserSystemState.ConfigDisabled\n if (axes.access === UserSystemAccess.Forbidden) return UserSystemState.Forbidden\n if (axes.access === UserSystemAccess.Unauthenticated || axes.access === UserSystemAccess.Unavailable) {\n return UserSystemState.Unavailable\n }\n if (axes.load === UserSystemLoad.Idle || axes.load === UserSystemLoad.Loading) return UserSystemState.Loading\n if (axes.presence === UserSystemPresence.Absent) return UserSystemState.Absent\n if (axes.presence === UserSystemPresence.Unknown) return UserSystemState.Unavailable\n return axes.enabled === true ? UserSystemState.Enabled : UserSystemState.Disabled\n}\n\n/**\n * How a probe response becomes axes.\n *\n * 401 is its own case and it is not a detail. A first 401 is ambiguous -- an expired session looks\n * exactly like \"no account here\" -- and since the buttons are gated by this answer alone, showing\n * an expired token as \"no account here\" is the one confusion decision 16 forbids. The caller is\n * expected to have retried through its refresh hook before classifying; what arrives here is the\n * verdict after that retry.\n *\n * 422 deliberately has no case. A detail GET loads the record and returns it; it does not validate\n * a body. A 422 belongs to the log of writes.\n */\nexport const classifyProbeStatus = (status: number | null): Pick<UserSystemAxes, 'presence' | 'access'> => {\n if (status === null) {\n // A timeout, a network failure, or anything that never reached a status.\n return { presence: UserSystemPresence.Unknown, access: UserSystemAccess.Unavailable }\n }\n if (status === 404) return { presence: UserSystemPresence.Absent, access: UserSystemAccess.Ok }\n if (status === 403) return { presence: UserSystemPresence.Unknown, access: UserSystemAccess.Forbidden }\n if (status === 401) return { presence: UserSystemPresence.Unknown, access: UserSystemAccess.Unauthenticated }\n if (status >= 500) return { presence: UserSystemPresence.Unknown, access: UserSystemAccess.Unavailable }\n if (status >= 200 && status < 300) return { presence: UserSystemPresence.Present, access: UserSystemAccess.Ok }\n return { presence: UserSystemPresence.Unknown, access: UserSystemAccess.Unavailable }\n}\n\n/**\n * Whether the probe answered authoritatively that nobody is there.\n *\n * Creating an account is offered only when every relevant system said so. A record that could not\n * be read is not a record that does not exist, and since no admin can delete an AnzuUser, a\n * duplicate identity created on a guess cannot be taken back.\n */\nexport const isAuthoritativelyAbsent = (axes: UserSystemAxes): boolean =>\n axes.load === UserSystemLoad.Loaded &&\n axes.presence === UserSystemPresence.Absent &&\n axes.access === UserSystemAccess.Ok\n\n/** A system a bulk action may act on: it answered, the account is there, and access was granted. */\nexport const isActionable = (axes: UserSystemAxes): boolean =>\n axes.load === UserSystemLoad.Loaded &&\n axes.presence === UserSystemPresence.Present &&\n axes.access === UserSystemAccess.Ok\n","<script lang=\"ts\" setup>\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport {\n resolveUserSystemState,\n UserSystemState,\n type UserSystemAxes,\n type UserSystemStateType,\n} from '@/labs/anzuUser/userSystemState'\n\nconst props = defineProps<{\n axes: UserSystemAxes\n}>()\n\nconst { t } = useI18n()\n\nconst state = computed<UserSystemStateType>(() => resolveUserSystemState(props.axes))\n\n/**\n * One definition, one set of colours, one set of icons -- shown in the \"other systems\" table, in a\n * cross-system row, in a bulk-action row and in the log. A text chip rather than a hover tooltip:\n * a tooltip does not exist on touch, cannot be scanned down a column, and would let \"no account\n * here\" and \"no access\" look nearly the same.\n */\nconst APPEARANCE: Record<UserSystemStateType, { color: string; icon: string; muted: boolean }> = {\n [UserSystemState.Loading]: { color: 'default', icon: 'mdi-dots-horizontal', muted: true },\n [UserSystemState.Enabled]: { color: 'success', icon: 'mdi-check-circle-outline', muted: false },\n [UserSystemState.Disabled]: { color: 'warning', icon: 'mdi-alert-circle-outline', muted: false },\n [UserSystemState.Absent]: { color: 'default', icon: 'mdi-close-circle-outline', muted: false },\n [UserSystemState.Forbidden]: { color: 'default', icon: 'mdi-cancel', muted: false },\n [UserSystemState.Unavailable]: { color: 'error', icon: 'mdi-alert-outline', muted: false },\n [UserSystemState.ConfigDisabled]: { color: 'default', icon: 'mdi-cancel', muted: true },\n}\n\nconst appearance = computed(() => APPEARANCE[state.value])\n</script>\n\n<template>\n <VChip\n :color=\"appearance.color\"\n :prepend-icon=\"appearance.icon\"\n :class=\"{ 'text-disabled': appearance.muted }\"\n :data-cy=\"`user-system-state-${state}`\"\n label\n size=\"small\"\n >\n {{ t(`common.userSystem.state.${state}`) }}\n </VChip>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport {\n resolveUserSystemState,\n UserSystemState,\n type UserSystemAxes,\n type UserSystemStateType,\n} from '@/labs/anzuUser/userSystemState'\n\nconst props = defineProps<{\n axes: UserSystemAxes\n}>()\n\nconst { t } = useI18n()\n\nconst state = computed<UserSystemStateType>(() => resolveUserSystemState(props.axes))\n\n/**\n * One definition, one set of colours, one set of icons -- shown in the \"other systems\" table, in a\n * cross-system row, in a bulk-action row and in the log. A text chip rather than a hover tooltip:\n * a tooltip does not exist on touch, cannot be scanned down a column, and would let \"no account\n * here\" and \"no access\" look nearly the same.\n */\nconst APPEARANCE: Record<UserSystemStateType, { color: string; icon: string; muted: boolean }> = {\n [UserSystemState.Loading]: { color: 'default', icon: 'mdi-dots-horizontal', muted: true },\n [UserSystemState.Enabled]: { color: 'success', icon: 'mdi-check-circle-outline', muted: false },\n [UserSystemState.Disabled]: { color: 'warning', icon: 'mdi-alert-circle-outline', muted: false },\n [UserSystemState.Absent]: { color: 'default', icon: 'mdi-close-circle-outline', muted: false },\n [UserSystemState.Forbidden]: { color: 'default', icon: 'mdi-cancel', muted: false },\n [UserSystemState.Unavailable]: { color: 'error', icon: 'mdi-alert-outline', muted: false },\n [UserSystemState.ConfigDisabled]: { color: 'default', icon: 'mdi-cancel', muted: true },\n}\n\nconst appearance = computed(() => APPEARANCE[state.value])\n</script>\n\n<template>\n <VChip\n :color=\"appearance.color\"\n :prepend-icon=\"appearance.icon\"\n :class=\"{ 'text-disabled': appearance.muted }\"\n :data-cy=\"`user-system-state-${state}`\"\n label\n size=\"small\"\n >\n {{ t(`common.userSystem.state.${state}`) }}\n </VChip>\n</template>\n","import { ref, type InjectionKey, type Ref } from 'vue'\nimport axios from 'axios'\nimport { AnzuApiAxiosError } from '@/model/error/AnzuApiAxiosError'\nimport { AnzuApiCancelledError } from '@/model/error/AnzuApiCancelledError'\nimport { AnzuApiForbiddenError } from '@/model/error/AnzuApiForbiddenError'\nimport { AnzuApiResponseCodeError } from '@/model/error/AnzuApiResponseCodeError'\nimport { HTTP_STATUS_FORBIDDEN, HTTP_STATUS_UNAUTHORIZED } from '@/composables/statusCodes'\nimport { useApiRequest } from '@/labs/api/useApiRequest'\nimport { type AnyUserSystemDescriptor, resolveProbeEndpoint } from '@/labs/anzuUser/userSystemDescriptor'\nimport {\n classifyProbeStatus,\n emptyUserSystemAxes,\n UserSystemAccess,\n UserSystemLoad,\n UserSystemPresence,\n type UserSystemAxes,\n} from '@/labs/anzuUser/userSystemState'\nimport type { AnzuUser } from '@/types/AnzuUser'\nimport type { IntegerId } from '@/types/common'\n\n/**\n * How an app refreshes a session for one system, and retries.\n *\n * The library cannot do this itself: a first 401 is ambiguous -- expired session or no account --\n * and the only way to tell them apart is to refresh and ask again. Every admin has its own\n * mechanism (`refreshSession.ts` in inhouse, an interceptor in cms, `requestRefreshToken.ts` in\n * blog) and none of them lives in `common-admin`. Without this hook a panel would show an expired\n * token as \"no account here\", which is exactly the confusion decision 16 forbids.\n *\n * Returns true when the refresh succeeded and the call is worth repeating.\n */\nexport type UserSystemRefreshHook = (system: string) => Promise<boolean>\n\n/**\n * What the probe calls itself in the error context.\n *\n * A 404 here is an answer, not a failure -- it is how a system says \"no account of yours\". It still\n * reaches the api error logger like any other, so an app filters on this together with the status\n * rather than on the url, which would also silence a real detail read of the same path. The\n * validation scope stays the user's, so a field error would still name a real label.\n */\nexport const USER_PROBE_ENTITY = 'anzuUserProbe'\n\nexport const UserSystemRefreshHookKey: InjectionKey<UserSystemRefreshHook> = Symbol('UserSystemRefreshHook')\n\n/**\n * The status the backend answered with, or null when nothing ever answered.\n *\n * Read off the axios failure the mapped error kept as its cause, not off the class. Going by class\n * alone misses exactly the answers that exist *because* the backend replied with a body: a\n * validation failure is `AnzuApiValidationError`, a dependency conflict is\n * `AnzuApiDependencyExistsError`, and both are 422s that have to be reported as such. Reported as\n * \"the system did not answer\" they would send the operator looking for an outage instead of at the\n * account whose stored data no longer validates -- which, with legacy rows, is the failure that\n * happens most.\n */\nexport const probeStatusFromError = (error: unknown): number | null => {\n if (error instanceof AnzuApiResponseCodeError) return error.code\n if (error instanceof AnzuApiForbiddenError) return HTTP_STATUS_FORBIDDEN\n if (error instanceof AnzuApiAxiosError) return error.cause.response?.status ?? null\n const cause = (error as { cause?: unknown } | null)?.cause\n if (axios.isAxiosError(cause)) return cause.response?.status ?? null\n if (axios.isAxiosError(error)) return error.response?.status ?? null\n\n return null\n}\n\nexport interface UserSystemProbeResult {\n axes: Ref<UserSystemAxes>\n user: Ref<AnzuUser | null>\n probe: (id: IntegerId) => Promise<void>\n reset: () => void\n cancel: () => void\n}\n\nexport interface UserSystemProbeParams {\n descriptor: AnyUserSystemDescriptor\n /** Usually injected once per page; passing it explicitly is for tests and one-off panels. */\n refreshHook?: UserSystemRefreshHook | undefined\n}\n\n/**\n * One system's answer about one user.\n *\n * Reads through `endpoints[probe].get`, never a hard-coded path: in dam the choice is visible to\n * the operator, because `/adm/v1/anzu-user/{id}` has no gating there while `/adm/users/{id}` sits\n * behind `DAM_USER_READ` -- only the second can ever render \"you have no access\".\n *\n * Note for the app: a probe answering 404 is an expected result here, not a failure, but it still\n * reaches the api error logger. Filter on `status === 404 && entity === USER_PROBE_ENTITY`, or\n * every absent account writes a line into the log.\n */\nexport const useUserSystemProbe = (params: UserSystemProbeParams): UserSystemProbeResult => {\n const { descriptor, refreshHook } = params\n\n const axes = ref<UserSystemAxes>(emptyUserSystemAxes())\n const user = ref<AnzuUser | null>(null)\n\n let generation = 0\n /**\n * The helper's abort handle for the request that is in flight.\n *\n * The generation token alone already stops a stale answer from being written; this is what stops\n * the request itself. Leaving a page mid-search would otherwise leave up to eighteen calls\n * running against backends nobody is waiting for any more.\n */\n let abortInFlight: (() => void) | null = null\n\n const reset = () => {\n axes.value = emptyUserSystemAxes()\n user.value = null\n }\n\n const cancel = () => {\n generation++\n abortInFlight?.()\n abortInFlight = null\n }\n\n const probe = async (id: IntegerId) => {\n // A system switched off in configuration is a state to show, not a call to make.\n if (!descriptor.isEnabled()) {\n axes.value = {\n load: UserSystemLoad.Loaded,\n presence: UserSystemPresence.Unknown,\n access: UserSystemAccess.ConfigDisabled,\n enabled: 'unknown',\n }\n user.value = null\n return\n }\n\n const token = ++generation\n axes.value = { ...axes.value, load: UserSystemLoad.Loading }\n\n const endpoint = resolveProbeEndpoint(descriptor)\n\n const ask = async () => {\n const { execute, abort } = useApiRequest<AnzuUser, null>({\n client: descriptor.client,\n method: 'GET',\n system: descriptor.system,\n entity: USER_PROBE_ENTITY,\n validationSystem: 'common',\n validationEntity: descriptor.entity,\n urlTemplate: endpoint.get,\n })\n abortInFlight = abort\n try {\n return await execute({ urlParams: { id } })\n } finally {\n abortInFlight = null\n }\n }\n\n try {\n const found = await ask()\n if (token !== generation) return\n user.value = found\n axes.value = {\n load: UserSystemLoad.Loaded,\n presence: UserSystemPresence.Present,\n access: UserSystemAccess.Ok,\n enabled: found.enabled,\n }\n } catch (error) {\n if (token !== generation) return\n if (error instanceof AnzuApiCancelledError) return\n\n let status = probeStatusFromError(error)\n\n // The one retry the classification depends on. Only after it has happened is a 401 a verdict\n // rather than a question.\n if (status === HTTP_STATUS_UNAUTHORIZED && refreshHook) {\n const refreshed = await refreshHook(descriptor.system).catch(() => false)\n if (token !== generation) return\n if (refreshed) {\n try {\n const found = await ask()\n if (token !== generation) return\n user.value = found\n axes.value = {\n load: UserSystemLoad.Loaded,\n presence: UserSystemPresence.Present,\n access: UserSystemAccess.Ok,\n enabled: found.enabled,\n }\n return\n } catch (retryError) {\n if (token !== generation) return\n status = probeStatusFromError(retryError)\n }\n }\n }\n\n user.value = null\n axes.value = {\n load: UserSystemLoad.Loaded,\n ...classifyProbeStatus(status),\n enabled: 'unknown',\n }\n }\n }\n\n return { axes, user, probe, reset, cancel }\n}\n","<script lang=\"ts\" setup>\nimport { computed, inject, onBeforeUnmount, onMounted, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport AUserCreateInSystemButton from '@/labs/anzuUser/AUserCreateInSystemButton.vue'\nimport AUserManageButton from '@/labs/anzuUser/AUserManageButton.vue'\nimport AUserSystemStatusChip from '@/labs/anzuUser/AUserSystemStatusChip.vue'\nimport type { AnyUserSystemDescriptor } from '@/labs/anzuUser/userSystemDescriptor'\nimport { useUserSystemProbe, UserSystemRefreshHookKey } from '@/labs/anzuUser/userSystemProbe'\nimport { isAuthoritativelyAbsent, UserSystemAccess, UserSystemLoad } from '@/labs/anzuUser/userSystemState'\nimport type { BaseUser } from '@/types/AnzuUser'\nimport type { IntegerId } from '@/types/common'\nimport { isUndefined } from '@/utils/common'\n\nconst props = withDefaults(\n defineProps<{\n descriptor: AnyUserSystemDescriptor\n userId: IntegerId\n /**\n * On a viewing page there is no Create. A write action on a read-only screen would be the first\n * in the fleet; Manage stays, because it only leads somewhere.\n */\n readonly?: boolean\n /** Whether the account about to be created would be switched on. */\n createEnabled?: boolean\n /** What the owning system holds, to prefill the create form. */\n source?: BaseUser | null\n creating?: boolean\n }>(),\n {\n readonly: false,\n createEnabled: false,\n source: null,\n creating: false,\n }\n)\n\nconst emit = defineEmits<{\n (e: 'create', descriptor: AnyUserSystemDescriptor, user: BaseUser): void\n}>()\n\nconst { t } = useI18n()\n\n// Provided once per page rather than passed down through every row.\nconst refreshHook = inject(UserSystemRefreshHookKey, undefined)\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { axes, user, probe, cancel } = useUserSystemProbe({ descriptor: props.descriptor, refreshHook })\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst label = computed(() => props.descriptor.label ?? props.descriptor.system)\n\n/**\n * The buttons are decided by the probe's answer, never by `<Acl>`.\n *\n * On a cross-system surface there is no current user loaded for the other backends, so `can()`\n * would throw and `<Acl>` would silently render nothing at all. The probe is the only signal there\n * is -- which is also why its 401 handling has to be right.\n */\nconst showManage = computed(\n () => axes.value.load === UserSystemLoad.Loaded && user.value !== null && !isUndefined(props.descriptor.manage)\n)\nconst showCreate = computed(() => !props.readonly && isAuthoritativelyAbsent(axes.value))\n\nconst manageTarget = computed(() => {\n if (user.value === null || isUndefined(props.descriptor.manage)) return null\n return props.descriptor.manage(user.value)\n})\n\nconst extraState = computed(() => {\n if (user.value === null || isUndefined(props.descriptor.extraState)) return null\n return props.descriptor.extraState(user.value)\n})\n\nconst EXTRA_STATE_COLOR = { ok: 'success', warn: 'warning', error: 'error' } as const\n\nconst canRetry = computed(\n () => axes.value.access === UserSystemAccess.Unavailable || axes.value.access === UserSystemAccess.Unauthenticated\n)\n\nconst run = () => probe(props.userId)\n\nonMounted(run)\n\n// The panel is mounted once and never re-created -- `VWindowItem` hides it with `v-show` -- so a\n// new id has to reach it this way.\nwatch(() => props.userId, run)\n\nonBeforeUnmount(cancel)\n\ndefineExpose({\n refresh: run,\n})\n</script>\n\n<template>\n <div\n class=\"d-flex align-center flex-wrap ga-2\"\n :data-cy=\"`user-system-panel-${descriptor.system}`\"\n >\n <div class=\"font-weight-medium\">{{ label }}</div>\n <AUserSystemStatusChip :axes=\"axes\" />\n <VChip\n v-if=\"extraState\"\n :color=\"EXTRA_STATE_COLOR[extraState.tone]\"\n label\n size=\"small\"\n >\n {{ extraState.label }}: {{ extraState.value }}\n </VChip>\n <div\n v-if=\"user\"\n class=\"text-body-small text-medium-emphasis\"\n >\n {{ user.person.fullName }} · {{ user.email }}\n </div>\n <div\n v-if=\"descriptor.enabledNote\"\n class=\"text-body-small text-medium-emphasis w-100\"\n >\n {{ descriptor.enabledNote }}\n </div>\n <VSpacer />\n <AUserManageButton\n v-if=\"showManage && manageTarget\"\n :target=\"manageTarget\"\n :system-label=\"label\"\n />\n <AUserCreateInSystemButton\n v-if=\"showCreate\"\n :system-label=\"label\"\n :create-note=\"descriptor.createNote\"\n :will-be-enabled=\"createEnabled\"\n :loading=\"creating\"\n :required-metadata=\"descriptor.requiredMetadata\"\n :user-id=\"userId\"\n :source=\"source\"\n @confirm=\"(user) => emit('create', descriptor, user)\"\n />\n <VBtn\n v-if=\"canRetry\"\n size=\"small\"\n variant=\"text\"\n data-cy=\"user-system-retry\"\n @click=\"run\"\n >\n {{ t('common.userSystem.button.retry') }}\n </VBtn>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed, inject, onBeforeUnmount, onMounted, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport AUserCreateInSystemButton from '@/labs/anzuUser/AUserCreateInSystemButton.vue'\nimport AUserManageButton from '@/labs/anzuUser/AUserManageButton.vue'\nimport AUserSystemStatusChip from '@/labs/anzuUser/AUserSystemStatusChip.vue'\nimport type { AnyUserSystemDescriptor } from '@/labs/anzuUser/userSystemDescriptor'\nimport { useUserSystemProbe, UserSystemRefreshHookKey } from '@/labs/anzuUser/userSystemProbe'\nimport { isAuthoritativelyAbsent, UserSystemAccess, UserSystemLoad } from '@/labs/anzuUser/userSystemState'\nimport type { BaseUser } from '@/types/AnzuUser'\nimport type { IntegerId } from '@/types/common'\nimport { isUndefined } from '@/utils/common'\n\nconst props = withDefaults(\n defineProps<{\n descriptor: AnyUserSystemDescriptor\n userId: IntegerId\n /**\n * On a viewing page there is no Create. A write action on a read-only screen would be the first\n * in the fleet; Manage stays, because it only leads somewhere.\n */\n readonly?: boolean\n /** Whether the account about to be created would be switched on. */\n createEnabled?: boolean\n /** What the owning system holds, to prefill the create form. */\n source?: BaseUser | null\n creating?: boolean\n }>(),\n {\n readonly: false,\n createEnabled: false,\n source: null,\n creating: false,\n }\n)\n\nconst emit = defineEmits<{\n (e: 'create', descriptor: AnyUserSystemDescriptor, user: BaseUser): void\n}>()\n\nconst { t } = useI18n()\n\n// Provided once per page rather than passed down through every row.\nconst refreshHook = inject(UserSystemRefreshHookKey, undefined)\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { axes, user, probe, cancel } = useUserSystemProbe({ descriptor: props.descriptor, refreshHook })\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst label = computed(() => props.descriptor.label ?? props.descriptor.system)\n\n/**\n * The buttons are decided by the probe's answer, never by `<Acl>`.\n *\n * On a cross-system surface there is no current user loaded for the other backends, so `can()`\n * would throw and `<Acl>` would silently render nothing at all. The probe is the only signal there\n * is -- which is also why its 401 handling has to be right.\n */\nconst showManage = computed(\n () => axes.value.load === UserSystemLoad.Loaded && user.value !== null && !isUndefined(props.descriptor.manage)\n)\nconst showCreate = computed(() => !props.readonly && isAuthoritativelyAbsent(axes.value))\n\nconst manageTarget = computed(() => {\n if (user.value === null || isUndefined(props.descriptor.manage)) return null\n return props.descriptor.manage(user.value)\n})\n\nconst extraState = computed(() => {\n if (user.value === null || isUndefined(props.descriptor.extraState)) return null\n return props.descriptor.extraState(user.value)\n})\n\nconst EXTRA_STATE_COLOR = { ok: 'success', warn: 'warning', error: 'error' } as const\n\nconst canRetry = computed(\n () => axes.value.access === UserSystemAccess.Unavailable || axes.value.access === UserSystemAccess.Unauthenticated\n)\n\nconst run = () => probe(props.userId)\n\nonMounted(run)\n\n// The panel is mounted once and never re-created -- `VWindowItem` hides it with `v-show` -- so a\n// new id has to reach it this way.\nwatch(() => props.userId, run)\n\nonBeforeUnmount(cancel)\n\ndefineExpose({\n refresh: run,\n})\n</script>\n\n<template>\n <div\n class=\"d-flex align-center flex-wrap ga-2\"\n :data-cy=\"`user-system-panel-${descriptor.system}`\"\n >\n <div class=\"font-weight-medium\">{{ label }}</div>\n <AUserSystemStatusChip :axes=\"axes\" />\n <VChip\n v-if=\"extraState\"\n :color=\"EXTRA_STATE_COLOR[extraState.tone]\"\n label\n size=\"small\"\n >\n {{ extraState.label }}: {{ extraState.value }}\n </VChip>\n <div\n v-if=\"user\"\n class=\"text-body-small text-medium-emphasis\"\n >\n {{ user.person.fullName }} · {{ user.email }}\n </div>\n <div\n v-if=\"descriptor.enabledNote\"\n class=\"text-body-small text-medium-emphasis w-100\"\n >\n {{ descriptor.enabledNote }}\n </div>\n <VSpacer />\n <AUserManageButton\n v-if=\"showManage && manageTarget\"\n :target=\"manageTarget\"\n :system-label=\"label\"\n />\n <AUserCreateInSystemButton\n v-if=\"showCreate\"\n :system-label=\"label\"\n :create-note=\"descriptor.createNote\"\n :will-be-enabled=\"createEnabled\"\n :loading=\"creating\"\n :required-metadata=\"descriptor.requiredMetadata\"\n :user-id=\"userId\"\n :source=\"source\"\n @confirm=\"(user) => emit('create', descriptor, user)\"\n />\n <VBtn\n v-if=\"canRetry\"\n size=\"small\"\n variant=\"text\"\n data-cy=\"user-system-retry\"\n @click=\"run\"\n >\n {{ t('common.userSystem.button.retry') }}\n </VBtn>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { ref } from 'vue'\nimport AUserSystemPanel from '@/labs/anzuUser/AUserSystemPanel.vue'\nimport type { AnyUserSystemDescriptor } from '@/labs/anzuUser/userSystemDescriptor'\nimport type { BaseUser } from '@/types/AnzuUser'\nimport type { IntegerId } from '@/types/common'\n\nconst props = defineProps<{\n descriptors: AnyUserSystemDescriptor[]\n userId: IntegerId\n readonly?: boolean\n /** Whether an account created from here would be switched on -- the dialog says so out loud. */\n createEnabled?: boolean\n /** What the owning system holds, to prefill the create form. */\n source?: BaseUser | null\n /** The system a create is currently running for. */\n creatingSystem?: string | null\n}>()\n\nconst emit = defineEmits<{\n (e: 'create', descriptor: AnyUserSystemDescriptor, user: BaseUser): void\n}>()\n\n/**\n * A read-only list of panels, not `AUserSystemOverview`.\n *\n * The overview adds a search, bulk actions and inconsistency detection, and belongs to task 3.\n * Building this out of it would make task 2 depend on task 3, which the plan's task table says it\n * does not.\n */\nconst panels = ref<InstanceType<typeof AUserSystemPanel>[]>([])\n\nconst refreshSystem = (system: string) => {\n const index = props.descriptors.findIndex((descriptor) => descriptor.system === system)\n panels.value[index]?.refresh()\n}\n\ndefineExpose({\n refresh: () => panels.value.forEach((panel) => panel?.refresh()),\n refreshSystem,\n})\n</script>\n\n<template>\n <VCard variant=\"flat\">\n <VCardText class=\"d-flex flex-column ga-4\">\n <AUserSystemPanel\n v-for=\"descriptor in descriptors\"\n :key=\"descriptor.system\"\n ref=\"panels\"\n :descriptor=\"descriptor\"\n :user-id=\"userId\"\n :readonly=\"readonly\"\n :create-enabled=\"createEnabled\"\n :source=\"source\"\n :creating=\"creatingSystem === descriptor.system\"\n @create=\"(target, user) => emit('create', target, user)\"\n />\n </VCardText>\n </VCard>\n</template>\n","<script lang=\"ts\" setup>\nimport { ref } from 'vue'\nimport AUserSystemPanel from '@/labs/anzuUser/AUserSystemPanel.vue'\nimport type { AnyUserSystemDescriptor } from '@/labs/anzuUser/userSystemDescriptor'\nimport type { BaseUser } from '@/types/AnzuUser'\nimport type { IntegerId } from '@/types/common'\n\nconst props = defineProps<{\n descriptors: AnyUserSystemDescriptor[]\n userId: IntegerId\n readonly?: boolean\n /** Whether an account created from here would be switched on -- the dialog says so out loud. */\n createEnabled?: boolean\n /** What the owning system holds, to prefill the create form. */\n source?: BaseUser | null\n /** The system a create is currently running for. */\n creatingSystem?: string | null\n}>()\n\nconst emit = defineEmits<{\n (e: 'create', descriptor: AnyUserSystemDescriptor, user: BaseUser): void\n}>()\n\n/**\n * A read-only list of panels, not `AUserSystemOverview`.\n *\n * The overview adds a search, bulk actions and inconsistency detection, and belongs to task 3.\n * Building this out of it would make task 2 depend on task 3, which the plan's task table says it\n * does not.\n */\nconst panels = ref<InstanceType<typeof AUserSystemPanel>[]>([])\n\nconst refreshSystem = (system: string) => {\n const index = props.descriptors.findIndex((descriptor) => descriptor.system === system)\n panels.value[index]?.refresh()\n}\n\ndefineExpose({\n refresh: () => panels.value.forEach((panel) => panel?.refresh()),\n refreshSystem,\n})\n</script>\n\n<template>\n <VCard variant=\"flat\">\n <VCardText class=\"d-flex flex-column ga-4\">\n <AUserSystemPanel\n v-for=\"descriptor in descriptors\"\n :key=\"descriptor.system\"\n ref=\"panels\"\n :descriptor=\"descriptor\"\n :user-id=\"userId\"\n :readonly=\"readonly\"\n :create-enabled=\"createEnabled\"\n :source=\"source\"\n :creating=\"creatingSystem === descriptor.system\"\n @create=\"(target, user) => emit('create', target, user)\"\n />\n </VCardText>\n </VCard>\n</template>\n","import { acceptHMRUpdate, defineStore } from 'pinia'\nimport { ref } from 'vue'\nimport { emptyUserSystemAxes, type UserSystemAxes } from '@/labs/anzuUser/userSystemState'\nimport type { AnzuUser } from '@/types/AnzuUser'\nimport type { IntegerId, IntegerIdNullable } from '@/types/common'\n\nexport interface UserSystemResult {\n axes: UserSystemAxes\n user: AnzuUser | null\n}\n\nexport const CrossSystemPhase = {\n Idle: 'idle',\n /** First round of an e-mail search: find the person and their id. */\n MatchingEmail: 'matchingEmail',\n /** Second round: ask every system by that id, to catch the ones holding a different e-mail. */\n VerifyingById: 'verifyingById',\n Done: 'done',\n} as const\nexport type CrossSystemPhaseType = (typeof CrossSystemPhase)[keyof typeof CrossSystemPhase]\n\nexport const BulkAction = {\n Enable: 'enable',\n Disable: 'disable',\n Metadata: 'metadata',\n} as const\nexport type BulkActionType = (typeof BulkAction)[keyof typeof BulkAction]\n\nexport const BulkOutcome = {\n Pending: 'pending',\n Running: 'running',\n Done: 'done',\n Forbidden: 'forbidden',\n Unauthenticated: 'unauthenticated',\n NotFound: 'notFound',\n Invalid: 'invalid',\n Unavailable: 'unavailable',\n} as const\nexport type BulkOutcomeType = (typeof BulkOutcome)[keyof typeof BulkOutcome]\n\nexport interface BulkLogEntry {\n system: string\n outcome: BulkOutcomeType\n /**\n * Which run the line belongs to.\n *\n * The log survives a run so that a retry does not erase the systems that succeeded a minute ago,\n * and the retry button acts on whatever the log calls failed. Without these two fields a failed\n * metadata write would be retried as a disable, and a previous person's failures would be retried\n * against the id currently on screen -- neither `setEnabled` nor `writeMetadata` can tell it is\n * being asked about a different action or a different account.\n */\n action: BulkActionType\n subjectId: IntegerIdNullable\n detail?: string\n}\n\n/**\n * Search results and the bulk run live here, not inside the dialog's component.\n *\n * The dialog can be closed while the calls are still going -- one dead backend running into its\n * timeout must not lock the screen -- and state owned by the component would be thrown away with\n * it. The results stay on the page either way.\n */\nexport const useUserCrossSystemStore = defineStore('labsUserCrossSystemStore', () => {\n const searchTerm = ref('')\n const resolvedId = ref<IntegerIdNullable>(null)\n const phase = ref<CrossSystemPhaseType>(CrossSystemPhase.Idle)\n const results = ref(new Map<string, UserSystemResult>())\n\n /**\n * The same e-mail answered with two different ids.\n *\n * Bulk actions stop when this is set. Picking the first one quietly would have every later\n * fan-out follow the wrong identity, and since no admin can delete an AnzuUser, anything written\n * under it stays.\n */\n const identityConflict = ref<Map<string, IntegerId> | null>(null)\n\n const bulkRunning = ref(false)\n const bulkAction = ref<BulkActionType | null>(null)\n const bulkSubjectId = ref<IntegerIdNullable>(null)\n const bulkLog = ref<BulkLogEntry[]>([])\n\n /** The term was neither an address nor an id, so nothing was asked and there is nothing to show. */\n const termRejected = ref(false)\n\n function resetResults() {\n results.value = new Map()\n resolvedId.value = null\n identityConflict.value = null\n phase.value = CrossSystemPhase.Idle\n termRejected.value = false\n // A new search is a new person. Leaving the log behind would put the previous one's failures on\n // screen under a live retry button, and that retry writes to `resolvedId` -- somebody else by\n // then.\n bulkLog.value = []\n bulkAction.value = null\n bulkSubjectId.value = null\n }\n\n function setResult(system: string, result: UserSystemResult) {\n results.value.set(system, result)\n }\n\n function ensureResult(system: string) {\n if (!results.value.has(system)) {\n results.value.set(system, { axes: emptyUserSystemAxes(), user: null })\n }\n }\n\n function patchAxes(system: string, axes: UserSystemAxes) {\n ensureResult(system)\n const current = results.value.get(system)!\n results.value.set(system, { ...current, axes })\n }\n\n function setUser(system: string, user: AnzuUser | null) {\n ensureResult(system)\n const current = results.value.get(system)!\n results.value.set(system, { ...current, user })\n }\n\n /**\n * Opens a run over `systems`, keeping what the same run already said about the others.\n *\n * The retry is why anything is kept at all: \"try the failed ones again\" runs a subset, and\n * resetting the whole log would erase the systems that succeeded a minute ago, so a partial\n * success would read as if it had never happened. What is kept is scoped to this action and this\n * account, because everything else in the log belongs to a run the operator is no longer looking\n * at.\n */\n function startBulk(action: BulkActionType, systems: string[], subjectId: IntegerIdNullable) {\n bulkRunning.value = true\n bulkAction.value = action\n bulkSubjectId.value = subjectId\n const queued = new Set(systems)\n const kept = bulkLog.value.filter(\n (entry) => entry.action === action && entry.subjectId === subjectId && !queued.has(entry.system)\n )\n bulkLog.value = [...kept, ...systems.map((system) => ({ system, outcome: BulkOutcome.Pending, action, subjectId }))]\n }\n\n function setBulkEntry(system: string, outcome: BulkOutcomeType, detail?: string) {\n const index = bulkLog.value.findIndex(\n (entry) => entry.system === system && entry.action === bulkAction.value && entry.subjectId === bulkSubjectId.value\n )\n if (index === -1) return\n const entry: BulkLogEntry = { ...bulkLog.value[index]!, outcome, detail }\n bulkLog.value = bulkLog.value.map((old, i) => (i === index ? entry : old))\n }\n\n function finishBulk() {\n bulkRunning.value = false\n }\n\n function reset() {\n searchTerm.value = ''\n resetResults()\n bulkRunning.value = false\n bulkAction.value = null\n bulkLog.value = []\n }\n\n return {\n searchTerm,\n resolvedId,\n phase,\n results,\n identityConflict,\n bulkRunning,\n bulkAction,\n bulkSubjectId,\n bulkLog,\n termRejected,\n resetResults,\n setResult,\n patchAxes,\n setUser,\n startBulk,\n setBulkEntry,\n finishBulk,\n reset,\n }\n})\n\nif (import.meta.hot) {\n import.meta.hot.accept(acceptHMRUpdate(useUserCrossSystemStore, import.meta.hot))\n}\n","import type { BaseUser } from '@/types/AnzuUser'\n\n/**\n * The six fields the cross-system view compares.\n *\n * `email` is in the list and is also the reason the e-mail search runs twice: if a person is\n * `jozko@sme.sk` in weather and `j.mrkvicka@sme.sk` in blog, a search by e-mail finds weather and\n * blog answers nothing -- which looks exactly like \"no account here\". The second round, by the id\n * the first round found, is what turns that silence into the inconsistency it actually is.\n */\nexport const USER_METADATA_FIELDS = [\n 'email',\n 'person.firstName',\n 'person.lastName',\n 'person.fullName',\n 'avatar.color',\n 'avatar.text',\n] as const\n\nexport type UserMetadataField = (typeof USER_METADATA_FIELDS)[number]\n\nexport const readMetadataField = (user: BaseUser, field: UserMetadataField): string => {\n switch (field) {\n case 'email':\n return user.email\n case 'person.firstName':\n return user.person.firstName\n case 'person.lastName':\n return user.person.lastName\n case 'person.fullName':\n return user.person.fullName\n case 'avatar.color':\n return user.avatar.color\n case 'avatar.text':\n return user.avatar.text\n }\n}\n\nexport const writeMetadataField = (user: BaseUser, field: UserMetadataField, value: string): void => {\n switch (field) {\n case 'email':\n user.email = value\n break\n case 'person.firstName':\n user.person.firstName = value\n break\n case 'person.lastName':\n user.person.lastName = value\n break\n case 'person.fullName':\n user.person.fullName = value\n break\n case 'avatar.color':\n user.avatar.color = value\n break\n case 'avatar.text':\n user.avatar.text = value\n break\n }\n}\n\nexport interface MetadataDifference {\n field: UserMetadataField\n /** System to the value it holds, for every system that answered with a record. */\n values: Map<string, string>\n}\n\n/**\n * Which of the six fields the systems disagree about.\n *\n * Blog is compared on `person.*` like everything else, and its own root `firstName`/`lastName`\n * columns are deliberately left out of this: those are the byline under an article, the blog's slug\n * and the public API, not back-office metadata. That blog's `person` is empty for most bloggers and\n * will therefore read as different is correct -- the back-office identity really is unset there,\n * and the first repair fills it in.\n */\nexport const findMetadataDifferences = (records: Map<string, BaseUser>): MetadataDifference[] => {\n const differences: MetadataDifference[] = []\n if (records.size < 2) return differences\n\n for (const field of USER_METADATA_FIELDS) {\n const values = new Map<string, string>()\n for (const [system, user] of records) {\n values.set(system, readMetadataField(user, field))\n }\n const distinct = new Set(values.values())\n if (distinct.size > 1) differences.push({ field, values })\n }\n\n return differences\n}\n\n/**\n * Which record the repair form is pre-filled from: the first system in the configured order that\n * has one.\n *\n * Never \"whichever answered first\" -- the fan-out is parallel, so that would be a different answer\n * on every run. The order is a default rather than a hidden rule: the dialog names the source and\n * lets it be changed.\n */\nexport const pickPrefillSource = (order: string[], records: Map<string, BaseUser>): string | null => {\n for (const system of order) {\n if (records.has(system)) return system\n }\n return null\n}\n\n/** Systems whose stored value differs from the one about to be written. Nothing else is touched. */\nexport const systemsNeedingWrite = (\n target: BaseUser,\n records: Map<string, BaseUser>,\n fields: readonly UserMetadataField[] = USER_METADATA_FIELDS\n): string[] => {\n const systems: string[] = []\n for (const [system, user] of records) {\n const differs = fields.some((field) => readMetadataField(user, field) !== readMetadataField(target, field))\n if (differs) systems.push(system)\n }\n return systems\n}\n","import { useApiRequest } from '@/labs/api/useApiRequest'\nimport { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_UNPROCESSABLE_ENTITY } from '@/composables/statusCodes'\nimport {\n type AnyUserSystemDescriptor,\n resolveCreateEndpoint,\n resolveEnabledWrite,\n resolveMetadataWrite,\n} from '@/labs/anzuUser/userSystemDescriptor'\nimport { BulkOutcome, type BulkOutcomeType } from '@/labs/anzuUser/userCrossSystemStore'\nimport { probeStatusFromError } from '@/labs/anzuUser/userSystemProbe'\nimport {\n readMetadataField,\n USER_METADATA_FIELDS,\n writeMetadataField,\n type UserMetadataField,\n} from '@/labs/anzuUser/userMetadataDiff'\nimport type { AnzuUser, BaseUser } from '@/types/AnzuUser'\nimport type { IntegerId } from '@/types/common'\nimport { cloneDeep, isNull } from '@/utils/common'\n\nexport interface WriteResult {\n outcome: BulkOutcomeType\n /** What the server holds afterwards, when it answered with the record. */\n user?: AnzuUser\n /**\n * Fields whose stored value had moved away from both the target and what the search saw.\n *\n * The write still goes through -- decision 24 says the operator's value wins -- but somebody\n * edited the record in between, and the log line says so rather than letting it pass unnoticed.\n */\n concurrentFields?: UserMetadataField[]\n detail?: string\n}\n\n/**\n * What the operator is told about a failed write.\n *\n * 422 and 400 are their own line and not a generic failure: a PUT validates the whole body, and an\n * account carrying a colour or an e-mail that no longer passes validation will fail even on a plain\n * \"disable\". With legacy data that is an ordinary outcome, and \"could not be saved\" would send\n * somebody looking for a bug that is not there.\n */\nexport const classifyWriteStatus = (status: number | null): BulkOutcomeType => {\n if (status === null) return BulkOutcome.Unavailable\n if (status === 403) return BulkOutcome.Forbidden\n if (status === 401) return BulkOutcome.Unauthenticated\n if (status === 404) return BulkOutcome.NotFound\n if (status === HTTP_STATUS_UNPROCESSABLE_ENTITY || status === HTTP_STATUS_BAD_REQUEST) return BulkOutcome.Invalid\n return BulkOutcome.Unavailable\n}\n\nconst request = <R extends object, B>(\n descriptor: AnyUserSystemDescriptor,\n method: 'GET' | 'PUT' | 'PATCH' | 'POST',\n url: string\n) =>\n useApiRequest<R, B>({\n client: descriptor.client,\n method,\n system: descriptor.system,\n entity: descriptor.entity,\n urlTemplate: url,\n })\n\nexport const useUserCrossSystemWrites = () => {\n /**\n * Switching an account on or off.\n *\n * There is no PATCH for this anywhere -- the only method that calls `setEnabled` is the full\n * update -- so it is a read followed by a write on the same path, with the whole record the read\n * returned. That is not caution, it is the contract: the DTO defaults roles to `[ROLE_USER]`,\n * `enabled` to true and both permission collections to empty, and the manager writes all four\n * unconditionally. A body of \"just the enabled flag\" resets the roles, wipes the grants, detaches\n * the groups and switches a disabled account back on.\n *\n * On `/adm/users` in cms it goes further still: that PUT runs `updateRelations()` over seven\n * scalars and twelve collections, so an omitted `desks` does not mean \"unchanged\", it means\n * \"remove from every desk\".\n */\n const setEnabled = async (\n descriptor: AnyUserSystemDescriptor,\n id: IntegerId,\n enabled: boolean\n ): Promise<WriteResult> => {\n const { get, url } = resolveEnabledWrite(descriptor)\n try {\n const { execute: read } = request<AnzuUser, null>(descriptor, 'GET', get)\n const current = await read({ urlParams: { id } })\n if (current.enabled === enabled) {\n // Already there. A no-op PUT would still run `updateRelations()` in cms -- risk for nothing.\n return { outcome: BulkOutcome.Done, user: current }\n }\n const body = cloneDeep(current)\n body.enabled = enabled\n const { execute: write } = request<AnzuUser, AnzuUser>(descriptor, 'PUT', url)\n const saved = await write({ urlParams: { id }, body })\n return { outcome: BulkOutcome.Done, user: saved }\n } catch (error) {\n return { outcome: classifyWriteStatus(probeStatusFromError(error)) }\n }\n }\n\n /**\n * Writing the shared metadata.\n *\n * Through `PATCH /adm/users/{id}` wherever that path exists, because the method behind it\n * physically cannot write roles, permissions, groups or `enabled` -- it has no setters for them.\n * That is a stronger guarantee than assembling a careful body, and it is why the plan sends\n * metadata this way rather than through the full update.\n *\n * The body is still the whole record the read returned. `id` in particular has to be in it: the\n * uniqueness validator only excludes the record itself when the DTO carries a non-empty id, and\n * without it the server reports the user's own e-mail as already taken -- on every account.\n */\n const writeMetadata = async (\n descriptor: AnyUserSystemDescriptor,\n id: IntegerId,\n target: BaseUser,\n fields: readonly UserMetadataField[] = USER_METADATA_FIELDS,\n /** What the search showed for this system, so a change made in between can be recognised. */\n seen: BaseUser | null = null\n ): Promise<WriteResult & { changed: boolean }> => {\n const { method, get, url } = resolveMetadataWrite(descriptor)\n try {\n const { execute: read } = request<AnzuUser, null>(descriptor, 'GET', get)\n // Read again immediately before writing, not reused from the search: those results can be\n // hours old, and a system whose value already matches must not be written to at all.\n const current = await read({ urlParams: { id } })\n\n const differs = fields.some((field) => readMetadataField(current, field) !== readMetadataField(target, field))\n if (!differs) return { outcome: BulkOutcome.Done, user: current, changed: false }\n\n // Different from what the operator chose *and* from what they were shown when they chose it:\n // somebody else wrote to this record in between. The value still goes in -- that is what was\n // asked for -- and the log names the fields.\n const concurrentFields = isNull(seen)\n ? []\n : fields.filter(\n (field) =>\n readMetadataField(current, field) !== readMetadataField(seen, field) &&\n readMetadataField(current, field) !== readMetadataField(target, field)\n )\n\n const body = cloneDeep(current)\n fields.forEach((field) => writeMetadataField(body, field, readMetadataField(target, field)))\n\n const { execute: write } = request<AnzuUser, AnzuUser>(descriptor, method, url)\n const saved = await write({ urlParams: { id }, body })\n return { outcome: BulkOutcome.Done, user: saved, changed: true, concurrentFields }\n } catch (error) {\n return { outcome: classifyWriteStatus(probeStatusFromError(error)), changed: false }\n }\n }\n\n /**\n * Creating an account in another system.\n *\n * The body comes from the library factory -- `roles: []`, `enabled: false` -- with the metadata\n * copied over. Assembling it by hand from base fields is what would have the backend fill in\n * `ROLE_USER`, leaving an account with a role nobody granted.\n */\n const createInSystem = async (descriptor: AnyUserSystemDescriptor, body: AnzuUser): Promise<WriteResult> => {\n try {\n const { execute } = request<AnzuUser, AnzuUser>(descriptor, 'POST', resolveCreateEndpoint(descriptor))\n const created = await execute({ body })\n return { outcome: BulkOutcome.Done, user: created }\n } catch (error) {\n return { outcome: classifyWriteStatus(probeStatusFromError(error)) }\n }\n }\n\n /** The source account, read fresh from the server rather than taken from a store or a form. */\n const readSourceUser = async (descriptor: AnyUserSystemDescriptor, id: IntegerId): Promise<AnzuUser | null> => {\n const { get } = resolveEnabledWrite(descriptor)\n try {\n const { execute } = request<AnzuUser, null>(descriptor, 'GET', get)\n return await execute({ urlParams: { id } })\n } catch {\n return null\n }\n }\n\n return {\n setEnabled,\n writeMetadata,\n createInSystem,\n readSourceUser,\n }\n}\n","/**\n * The one tab that is not a system.\n *\n * A module of its own because `<script setup>` cannot export, and both the shell and the pages that\n * read `?tab=` need to name it.\n */\nexport const OTHER_SYSTEMS_TAB = '__otherSystems'\n","<script lang=\"ts\" setup>\nimport { computed, onMounted, provide, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport { useRoute, useRouter } from 'vue-router'\nimport { useAlerts } from '@/composables/system/alerts'\nimport { useAnzuUserFactory } from '@/model/factory/AnzuUserFactory'\nimport { cloneDeep, isNull, isUndefined } from '@/utils/common'\nimport { BulkOutcome } from '@/labs/anzuUser/userCrossSystemStore'\nimport { useUserCrossSystemWrites } from '@/labs/anzuUser/userCrossSystemWrites'\nimport AUnsavedConfirmDialog from '@/labs/unsavedGuard/AUnsavedConfirmDialog.vue'\nimport { useUnsavedChangesGuard } from '@/labs/unsavedGuard/useUnsavedChangesGuard'\nimport AUserOtherSystemsTable from '@/labs/anzuUser/AUserOtherSystemsTable.vue'\nimport AUserSystemPanel from '@/labs/anzuUser/AUserSystemPanel.vue'\nimport type { AnyUserSystemDescriptor } from '@/labs/anzuUser/userSystemDescriptor'\nimport type { AnzuUser, BaseUser } from '@/types/AnzuUser'\nimport type { IntegerId } from '@/types/common'\nimport { UserSystemRefreshHookKey, type UserSystemRefreshHook } from '@/labs/anzuUser/userSystemProbe'\nimport { OTHER_SYSTEMS_TAB } from '@/labs/anzuUser/userTabs'\n\nconst props = withDefaults(\n defineProps<{\n userId: IntegerId\n /** Which tab carries the owning form, i.e. gets the slot. */\n owner: string\n /**\n * The tabs, in the order they are drawn, plus \"other systems\" appended by this component.\n *\n * A prop and not something derived from the descriptor array, because the list depends on the\n * route rather than on what the admin owns: admin-inhouse owns four systems and has two tabs,\n * since the system is part of the path.\n */\n ownedTabs: string[]\n /** Every system this admin can reach. The \"other systems\" tab lists the ones not in a tab. */\n descriptors: AnyUserSystemDescriptor[]\n /**\n * \"After a 401, refresh once and try again.\"\n *\n * The library cannot decide this for itself: the mechanism exists only in the apps, and each\n * one does it differently -- inhouse `refreshSession`, cms and contentHub their own\n * `refresh-token` call. Without it an expired token renders as \"no account in this system\",\n * which is the one confusion decision 16 forbids. Every panel this shell draws gets it.\n */\n refreshHook?: UserSystemRefreshHook | undefined\n readonly?: boolean\n /**\n * Whether the owning form holds edits that have not been saved.\n *\n * It drives two things. The dot on the owning tab, so the operator can see it from the other\n * tabs -- the Save button itself is visible everywhere, by decision, because a button that\n * disappeared would read as \"this is read-only\". And the guard below, which is the only thing\n * standing between unsaved work and the button in a panel that leaves the page: switching a tab\n * is not navigation, so nothing else ever asks.\n */\n unsavedChanges?: boolean\n /** Query key for the deep link. */\n tabQueryKey?: string\n }>(),\n {\n refreshHook: undefined,\n readonly: false,\n unsavedChanges: false,\n tabQueryKey: 'tab',\n }\n)\n\nconst emit = defineEmits<{\n /** After an account was created in another system, so a page can react if it wants to. */\n (e: 'created', descriptor: AnyUserSystemDescriptor): void\n}>()\n\nconst activeTab = defineModel<string>('activeTab', { required: false, default: '' })\n\nconst { t } = useI18n()\nconst route = useRoute()\nconst router = useRouter()\n\nconst bySystem = computed(() => new Map(props.descriptors.map((item) => [item.system, item])))\n\nconst tabs = computed(() => [...props.ownedTabs, OTHER_SYSTEMS_TAB])\n\nconst otherDescriptors = computed(() => props.descriptors.filter((item) => !props.ownedTabs.includes(item.system)))\n\nconst labelFor = (system: string) => bySystem.value.get(system)?.label ?? system\n\nconst isValidTab = (value: unknown): value is string => typeof value === 'string' && tabs.value.includes(value)\n\nonMounted(() => {\n const fromUrl = route.query[props.tabQueryKey]\n // An unknown tab -- a system dropped out of the configuration since the link was sent -- is\n // ignored without a word and the owning tab opens. An error here would help nobody.\n activeTab.value = isValidTab(fromUrl) ? fromUrl : props.owner\n})\n\nwatch(activeTab, (value) => {\n if (!isValidTab(value)) return\n // `replace`, not `push`: with `push` the back button would walk through the tabs instead of\n // leaving the page.\n void router.replace({ query: { ...route.query, [props.tabQueryKey]: value } })\n})\n\n/**\n * Registered here rather than in the page, because this is the component the leaving happens from:\n * an internal \"manage in X\" is a router link inside a panel, and `onBeforeRouteLeave` has to be\n * bound somewhere inside the route. An external one opens a new tab and leaves this page standing,\n * so it needs no guard at all.\n */\nconst unsavedSource = computed(() => props.unsavedChanges)\nconst { hasUnsavedChanges, dirtyLabels, promptOpen, resolvePrompt } = useUnsavedChangesGuard({\n sources: [unsavedSource],\n})\n\nconst otherSystems = ref<InstanceType<typeof AUserOtherSystemsTable> | null>(null)\nconst ownedPanels = ref<InstanceType<typeof AUserSystemPanel>[]>([])\n\n/**\n * Creating this person's account in another system.\n *\n * Handled here rather than by the page, because the panel that offers it is here and the whole\n * operation is the library's: read the owning system's record, copy the metadata, post it. The\n * page would only be passing it through.\n */\nconst writes = useUserCrossSystemWrites()\nconst { createAnzuUser } = useAnzuUserFactory()\nconst { showError, showRecordWas } = useAlerts()\n\n// Only when there is one: the panel's own `inject` default is `undefined`, and providing that\n// explicitly would say \"there is a hook\" to anything that merely checks for the key.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss\nif (!isUndefined(props.refreshHook)) provide(UserSystemRefreshHookKey, props.refreshHook)\n\nconst ownerDescriptor = computed(() => bySystem.value.get(props.owner))\n/**\n * The owning system's record as the server holds it, not as the form does.\n *\n * Read when the panels are, so the owning tab costs nothing, and read again before each POST: a\n * switch flipped in the form and not saved must not be copied into another system, where in blog\n * and forum it would mean a working public account. That is the whole point of the fix commit this\n * rule comes from.\n */\nconst sourceUser = ref<AnzuUser | null>(null)\nconst creatingSystem = ref<string | null>(null)\n\nconst loadSource = async () => {\n const descriptor = ownerDescriptor.value\n if (isUndefined(descriptor)) return\n sourceUser.value = await writes.readSourceUser(descriptor, props.userId)\n}\n\n/** Copying a person into another system copies whether they are switched on. Roles stay empty. */\nconst createEnabled = computed(() => sourceUser.value?.enabled === true)\n\nconst onCreate = async (descriptor: AnyUserSystemDescriptor, metadata: BaseUser) => {\n const owner = ownerDescriptor.value\n if (isUndefined(owner)) return\n creatingSystem.value = descriptor.system\n try {\n const source = await writes.readSourceUser(owner, props.userId)\n if (isNull(source)) {\n showError(t('common.userSystem.create.sourceUnreadable', { system: labelFor(owner.system) }))\n return\n }\n // The dialog has already promised one of two sentences -- created ready to use, or created\n // switched off -- from the copy read when the panels were. If the server no longer says that,\n // the promise was wrong, and in blog and forum the difference is a working public account. The\n // commonest way to get here is that first read having failed, which leaves `sourceUser` null\n // and the dialog saying \"disabled\" about an account that is on. Nothing is written until the\n // corrected row has been confirmed again.\n if (source.enabled !== createEnabled.value) {\n sourceUser.value = source\n showError(t('common.userSystem.create.sourceChanged'))\n return\n }\n // From the library factory, never assembled by hand: a body of base fields alone would have the\n // backend fill in `ROLE_USER`, leaving an account with a role nobody granted.\n //\n // The metadata is the dialog's, not the source's: the form was validated against the target's\n // profile and the operator may have filled in what the source leaves empty -- blog fills\n // `person` for nobody, and a system that requires it would get a record its own form refuses\n // to save. The id and `enabled` still come from the fresh read.\n const body = createAnzuUser(descriptor.system)\n body.id = source.id\n body.email = metadata.email\n body.person = cloneDeep(metadata.person)\n body.avatar = cloneDeep(metadata.avatar)\n body.enabled = source.enabled\n const result = await writes.createInSystem(descriptor, body)\n if (result.outcome !== BulkOutcome.Done) {\n // Silence here read as success: the row was re-probed, still said \"absent\", and nothing\n // explained why. The per-admin pages this replaces reported every write.\n showError(labelFor(descriptor.system) + ': ' + t('common.userSystem.bulk.outcome.' + result.outcome))\n return\n }\n showRecordWas('created')\n emit('created', descriptor)\n otherSystems.value?.refreshSystem(descriptor.system)\n ownedPanels.value.forEach((panel) => panel?.refresh())\n } finally {\n creatingSystem.value = null\n }\n}\n\n/**\n * Called by the page after the owning form saved.\n *\n * `VWindowItem` takes only `hasContent` from `useLazy` and hides the panel with `v-show`, so once a\n * panel is mounted it is never unmounted and never re-fetches on its own. Without this the panel\n * would keep showing what it read before the save -- and the create-in-another-system action,\n * which reads from it, would copy that stale snapshot.\n *\n * Deliberately not a refetch on every tab activation: that would turn cheap tab switching into\n * eight calls each time, which is the cost the pseudo-tab was dropped to avoid.\n */\nconst refresh = () => {\n void loadSource()\n otherSystems.value?.refresh()\n ownedPanels.value.forEach((panel) => panel?.refresh())\n}\n\n// Read alongside the panels, on the first activation of a tab that is not the owning one -- so the\n// owning tab still costs no foreign call at all.\nwatch(\n activeTab,\n (value) => {\n // `isValidTab` first: before the mount hook settles it, the model is an empty string, and\n // treating that as \"not the owning tab\" would fetch on the owning tab too -- the one call this\n // whole lazy arrangement exists to avoid.\n if (!isValidTab(value) || value === props.owner) return\n if (isNull(sourceUser.value)) void loadSource()\n },\n { immediate: true }\n)\n\ndefineExpose({ refresh })\n</script>\n\n<template>\n <div>\n <!--\n No tab navigates. Every one of them redraws the content, and what leaves the page is the\n Manage button inside a panel -- which is an anchor. `VTab` renders `role=\"tab\"` and could not\n carry a navigation semantically.\n -->\n <VTabs\n v-model=\"activeTab\"\n data-cy=\"user-tabs\"\n >\n <VTab\n v-for=\"tab in tabs\"\n :key=\"tab\"\n :value=\"tab\"\n :data-cy=\"`user-tab-${tab}`\"\n >\n {{ tab === OTHER_SYSTEMS_TAB ? t('common.userSystem.tab.otherSystems') : labelFor(tab) }}\n <!-- The owning tab says it holds unsaved work; the Save button itself is always visible. -->\n <VIcon\n v-if=\"tab === owner && hasUnsavedChanges\"\n class=\"ml-1\"\n size=\"x-small\"\n color=\"warning\"\n icon=\"mdi-circle\"\n data-cy=\"user-tab-unsaved\"\n />\n </VTab>\n </VTabs>\n <VWindow\n v-model=\"activeTab\"\n class=\"mt-4\"\n >\n <VWindowItem\n v-for=\"tab in ownedTabs\"\n :key=\"tab\"\n :value=\"tab\"\n >\n <!-- The owning system of the current route: the full form. -->\n <slot v-if=\"tab === owner\" />\n <!--\n Another system this admin owns, but whose route is not open: a probe with a summary and a\n Manage button, exactly like any other system. No tab takes the operator anywhere.\n -->\n <AUserSystemPanel\n v-else-if=\"!isUndefined(bySystem.get(tab))\"\n ref=\"ownedPanels\"\n :descriptor=\"bySystem.get(tab)!\"\n :user-id=\"userId\"\n :readonly=\"readonly\"\n :create-enabled=\"createEnabled\"\n :source=\"sourceUser\"\n :creating=\"creatingSystem === tab\"\n @create=\"onCreate\"\n />\n </VWindowItem>\n <VWindowItem :value=\"OTHER_SYSTEMS_TAB\">\n <!--\n Lazy by construction: `VWindowItem` renders its content on first activation, so opening a\n user costs no foreign calls at all.\n -->\n <AUserOtherSystemsTable\n ref=\"otherSystems\"\n :descriptors=\"otherDescriptors\"\n :user-id=\"userId\"\n :readonly=\"readonly\"\n :create-enabled=\"createEnabled\"\n :source=\"sourceUser\"\n :creating-system=\"creatingSystem\"\n @create=\"onCreate\"\n />\n </VWindowItem>\n </VWindow>\n <AUnsavedConfirmDialog\n v-model=\"promptOpen\"\n :dirty-labels=\"dirtyLabels\"\n @resolve=\"resolvePrompt\"\n />\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed, onMounted, provide, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport { useRoute, useRouter } from 'vue-router'\nimport { useAlerts } from '@/composables/system/alerts'\nimport { useAnzuUserFactory } from '@/model/factory/AnzuUserFactory'\nimport { cloneDeep, isNull, isUndefined } from '@/utils/common'\nimport { BulkOutcome } from '@/labs/anzuUser/userCrossSystemStore'\nimport { useUserCrossSystemWrites } from '@/labs/anzuUser/userCrossSystemWrites'\nimport AUnsavedConfirmDialog from '@/labs/unsavedGuard/AUnsavedConfirmDialog.vue'\nimport { useUnsavedChangesGuard } from '@/labs/unsavedGuard/useUnsavedChangesGuard'\nimport AUserOtherSystemsTable from '@/labs/anzuUser/AUserOtherSystemsTable.vue'\nimport AUserSystemPanel from '@/labs/anzuUser/AUserSystemPanel.vue'\nimport type { AnyUserSystemDescriptor } from '@/labs/anzuUser/userSystemDescriptor'\nimport type { AnzuUser, BaseUser } from '@/types/AnzuUser'\nimport type { IntegerId } from '@/types/common'\nimport { UserSystemRefreshHookKey, type UserSystemRefreshHook } from '@/labs/anzuUser/userSystemProbe'\nimport { OTHER_SYSTEMS_TAB } from '@/labs/anzuUser/userTabs'\n\nconst props = withDefaults(\n defineProps<{\n userId: IntegerId\n /** Which tab carries the owning form, i.e. gets the slot. */\n owner: string\n /**\n * The tabs, in the order they are drawn, plus \"other systems\" appended by this component.\n *\n * A prop and not something derived from the descriptor array, because the list depends on the\n * route rather than on what the admin owns: admin-inhouse owns four systems and has two tabs,\n * since the system is part of the path.\n */\n ownedTabs: string[]\n /** Every system this admin can reach. The \"other systems\" tab lists the ones not in a tab. */\n descriptors: AnyUserSystemDescriptor[]\n /**\n * \"After a 401, refresh once and try again.\"\n *\n * The library cannot decide this for itself: the mechanism exists only in the apps, and each\n * one does it differently -- inhouse `refreshSession`, cms and contentHub their own\n * `refresh-token` call. Without it an expired token renders as \"no account in this system\",\n * which is the one confusion decision 16 forbids. Every panel this shell draws gets it.\n */\n refreshHook?: UserSystemRefreshHook | undefined\n readonly?: boolean\n /**\n * Whether the owning form holds edits that have not been saved.\n *\n * It drives two things. The dot on the owning tab, so the operator can see it from the other\n * tabs -- the Save button itself is visible everywhere, by decision, because a button that\n * disappeared would read as \"this is read-only\". And the guard below, which is the only thing\n * standing between unsaved work and the button in a panel that leaves the page: switching a tab\n * is not navigation, so nothing else ever asks.\n */\n unsavedChanges?: boolean\n /** Query key for the deep link. */\n tabQueryKey?: string\n }>(),\n {\n refreshHook: undefined,\n readonly: false,\n unsavedChanges: false,\n tabQueryKey: 'tab',\n }\n)\n\nconst emit = defineEmits<{\n /** After an account was created in another system, so a page can react if it wants to. */\n (e: 'created', descriptor: AnyUserSystemDescriptor): void\n}>()\n\nconst activeTab = defineModel<string>('activeTab', { required: false, default: '' })\n\nconst { t } = useI18n()\nconst route = useRoute()\nconst router = useRouter()\n\nconst bySystem = computed(() => new Map(props.descriptors.map((item) => [item.system, item])))\n\nconst tabs = computed(() => [...props.ownedTabs, OTHER_SYSTEMS_TAB])\n\nconst otherDescriptors = computed(() => props.descriptors.filter((item) => !props.ownedTabs.includes(item.system)))\n\nconst labelFor = (system: string) => bySystem.value.get(system)?.label ?? system\n\nconst isValidTab = (value: unknown): value is string => typeof value === 'string' && tabs.value.includes(value)\n\nonMounted(() => {\n const fromUrl = route.query[props.tabQueryKey]\n // An unknown tab -- a system dropped out of the configuration since the link was sent -- is\n // ignored without a word and the owning tab opens. An error here would help nobody.\n activeTab.value = isValidTab(fromUrl) ? fromUrl : props.owner\n})\n\nwatch(activeTab, (value) => {\n if (!isValidTab(value)) return\n // `replace`, not `push`: with `push` the back button would walk through the tabs instead of\n // leaving the page.\n void router.replace({ query: { ...route.query, [props.tabQueryKey]: value } })\n})\n\n/**\n * Registered here rather than in the page, because this is the component the leaving happens from:\n * an internal \"manage in X\" is a router link inside a panel, and `onBeforeRouteLeave` has to be\n * bound somewhere inside the route. An external one opens a new tab and leaves this page standing,\n * so it needs no guard at all.\n */\nconst unsavedSource = computed(() => props.unsavedChanges)\nconst { hasUnsavedChanges, dirtyLabels, promptOpen, resolvePrompt } = useUnsavedChangesGuard({\n sources: [unsavedSource],\n})\n\nconst otherSystems = ref<InstanceType<typeof AUserOtherSystemsTable> | null>(null)\nconst ownedPanels = ref<InstanceType<typeof AUserSystemPanel>[]>([])\n\n/**\n * Creating this person's account in another system.\n *\n * Handled here rather than by the page, because the panel that offers it is here and the whole\n * operation is the library's: read the owning system's record, copy the metadata, post it. The\n * page would only be passing it through.\n */\nconst writes = useUserCrossSystemWrites()\nconst { createAnzuUser } = useAnzuUserFactory()\nconst { showError, showRecordWas } = useAlerts()\n\n// Only when there is one: the panel's own `inject` default is `undefined`, and providing that\n// explicitly would say \"there is a hook\" to anything that merely checks for the key.\n// eslint-disable-next-line vue/no-setup-props-reactivity-loss\nif (!isUndefined(props.refreshHook)) provide(UserSystemRefreshHookKey, props.refreshHook)\n\nconst ownerDescriptor = computed(() => bySystem.value.get(props.owner))\n/**\n * The owning system's record as the server holds it, not as the form does.\n *\n * Read when the panels are, so the owning tab costs nothing, and read again before each POST: a\n * switch flipped in the form and not saved must not be copied into another system, where in blog\n * and forum it would mean a working public account. That is the whole point of the fix commit this\n * rule comes from.\n */\nconst sourceUser = ref<AnzuUser | null>(null)\nconst creatingSystem = ref<string | null>(null)\n\nconst loadSource = async () => {\n const descriptor = ownerDescriptor.value\n if (isUndefined(descriptor)) return\n sourceUser.value = await writes.readSourceUser(descriptor, props.userId)\n}\n\n/** Copying a person into another system copies whether they are switched on. Roles stay empty. */\nconst createEnabled = computed(() => sourceUser.value?.enabled === true)\n\nconst onCreate = async (descriptor: AnyUserSystemDescriptor, metadata: BaseUser) => {\n const owner = ownerDescriptor.value\n if (isUndefined(owner)) return\n creatingSystem.value = descriptor.system\n try {\n const source = await writes.readSourceUser(owner, props.userId)\n if (isNull(source)) {\n showError(t('common.userSystem.create.sourceUnreadable', { system: labelFor(owner.system) }))\n return\n }\n // The dialog has already promised one of two sentences -- created ready to use, or created\n // switched off -- from the copy read when the panels were. If the server no longer says that,\n // the promise was wrong, and in blog and forum the difference is a working public account. The\n // commonest way to get here is that first read having failed, which leaves `sourceUser` null\n // and the dialog saying \"disabled\" about an account that is on. Nothing is written until the\n // corrected row has been confirmed again.\n if (source.enabled !== createEnabled.value) {\n sourceUser.value = source\n showError(t('common.userSystem.create.sourceChanged'))\n return\n }\n // From the library factory, never assembled by hand: a body of base fields alone would have the\n // backend fill in `ROLE_USER`, leaving an account with a role nobody granted.\n //\n // The metadata is the dialog's, not the source's: the form was validated against the target's\n // profile and the operator may have filled in what the source leaves empty -- blog fills\n // `person` for nobody, and a system that requires it would get a record its own form refuses\n // to save. The id and `enabled` still come from the fresh read.\n const body = createAnzuUser(descriptor.system)\n body.id = source.id\n body.email = metadata.email\n body.person = cloneDeep(metadata.person)\n body.avatar = cloneDeep(metadata.avatar)\n body.enabled = source.enabled\n const result = await writes.createInSystem(descriptor, body)\n if (result.outcome !== BulkOutcome.Done) {\n // Silence here read as success: the row was re-probed, still said \"absent\", and nothing\n // explained why. The per-admin pages this replaces reported every write.\n showError(labelFor(descriptor.system) + ': ' + t('common.userSystem.bulk.outcome.' + result.outcome))\n return\n }\n showRecordWas('created')\n emit('created', descriptor)\n otherSystems.value?.refreshSystem(descriptor.system)\n ownedPanels.value.forEach((panel) => panel?.refresh())\n } finally {\n creatingSystem.value = null\n }\n}\n\n/**\n * Called by the page after the owning form saved.\n *\n * `VWindowItem` takes only `hasContent` from `useLazy` and hides the panel with `v-show`, so once a\n * panel is mounted it is never unmounted and never re-fetches on its own. Without this the panel\n * would keep showing what it read before the save -- and the create-in-another-system action,\n * which reads from it, would copy that stale snapshot.\n *\n * Deliberately not a refetch on every tab activation: that would turn cheap tab switching into\n * eight calls each time, which is the cost the pseudo-tab was dropped to avoid.\n */\nconst refresh = () => {\n void loadSource()\n otherSystems.value?.refresh()\n ownedPanels.value.forEach((panel) => panel?.refresh())\n}\n\n// Read alongside the panels, on the first activation of a tab that is not the owning one -- so the\n// owning tab still costs no foreign call at all.\nwatch(\n activeTab,\n (value) => {\n // `isValidTab` first: before the mount hook settles it, the model is an empty string, and\n // treating that as \"not the owning tab\" would fetch on the owning tab too -- the one call this\n // whole lazy arrangement exists to avoid.\n if (!isValidTab(value) || value === props.owner) return\n if (isNull(sourceUser.value)) void loadSource()\n },\n { immediate: true }\n)\n\ndefineExpose({ refresh })\n</script>\n\n<template>\n <div>\n <!--\n No tab navigates. Every one of them redraws the content, and what leaves the page is the\n Manage button inside a panel -- which is an anchor. `VTab` renders `role=\"tab\"` and could not\n carry a navigation semantically.\n -->\n <VTabs\n v-model=\"activeTab\"\n data-cy=\"user-tabs\"\n >\n <VTab\n v-for=\"tab in tabs\"\n :key=\"tab\"\n :value=\"tab\"\n :data-cy=\"`user-tab-${tab}`\"\n >\n {{ tab === OTHER_SYSTEMS_TAB ? t('common.userSystem.tab.otherSystems') : labelFor(tab) }}\n <!-- The owning tab says it holds unsaved work; the Save button itself is always visible. -->\n <VIcon\n v-if=\"tab === owner && hasUnsavedChanges\"\n class=\"ml-1\"\n size=\"x-small\"\n color=\"warning\"\n icon=\"mdi-circle\"\n data-cy=\"user-tab-unsaved\"\n />\n </VTab>\n </VTabs>\n <VWindow\n v-model=\"activeTab\"\n class=\"mt-4\"\n >\n <VWindowItem\n v-for=\"tab in ownedTabs\"\n :key=\"tab\"\n :value=\"tab\"\n >\n <!-- The owning system of the current route: the full form. -->\n <slot v-if=\"tab === owner\" />\n <!--\n Another system this admin owns, but whose route is not open: a probe with a summary and a\n Manage button, exactly like any other system. No tab takes the operator anywhere.\n -->\n <AUserSystemPanel\n v-else-if=\"!isUndefined(bySystem.get(tab))\"\n ref=\"ownedPanels\"\n :descriptor=\"bySystem.get(tab)!\"\n :user-id=\"userId\"\n :readonly=\"readonly\"\n :create-enabled=\"createEnabled\"\n :source=\"sourceUser\"\n :creating=\"creatingSystem === tab\"\n @create=\"onCreate\"\n />\n </VWindowItem>\n <VWindowItem :value=\"OTHER_SYSTEMS_TAB\">\n <!--\n Lazy by construction: `VWindowItem` renders its content on first activation, so opening a\n user costs no foreign calls at all.\n -->\n <AUserOtherSystemsTable\n ref=\"otherSystems\"\n :descriptors=\"otherDescriptors\"\n :user-id=\"userId\"\n :readonly=\"readonly\"\n :create-enabled=\"createEnabled\"\n :source=\"sourceUser\"\n :creating-system=\"creatingSystem\"\n @create=\"onCreate\"\n />\n </VWindowItem>\n </VWindow>\n <AUnsavedConfirmDialog\n v-model=\"promptOpen\"\n :dirty-labels=\"dirtyLabels\"\n @resolve=\"resolvePrompt\"\n />\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport ADialogToolbar from '@/components/ADialogToolbar.vue'\nimport AUserSystemStatusChip from '@/labs/anzuUser/AUserSystemStatusChip.vue'\nimport {\n BulkAction,\n BulkOutcome,\n useUserCrossSystemStore,\n type BulkActionType,\n} from '@/labs/anzuUser/userCrossSystemStore'\nimport type { AnyUserSystemDescriptor } from '@/labs/anzuUser/userSystemDescriptor'\nimport { isActionable, UserSystemAccess } from '@/labs/anzuUser/userSystemState'\nimport { isUndefined } from '@/utils/common'\n\nconst props = defineProps<{\n action: BulkActionType\n descriptors: AnyUserSystemDescriptor[]\n}>()\n\nconst emit = defineEmits<{\n (e: 'run', systems: string[]): void\n}>()\n\nconst open = defineModel<boolean>('open', { required: true })\n\nconst { t } = useI18n()\nconst store = useUserCrossSystemStore()\n\nconst selected = ref<string[]>([])\n/** The dialog shows the list first and the log after the operator confirms. */\nconst showingLog = ref(false)\n\ninterface Row {\n descriptor: AnyUserSystemDescriptor\n selectable: boolean\n reason: string | null\n}\n\n/**\n * Which systems can be ticked, decided by the probe and not by ACL.\n *\n * This is a cross-system surface: no current user is loaded for the other backends, so `<Acl>`\n * would return false everywhere and quietly tick nothing. Not selectable are the systems the probe\n * can speak for -- 403, 404, switched off in configuration -- plus the ones that did not answer at\n * all, because acting on those would be a guess. \"May read but may not write\" cannot be seen from\n * here and ends up as a 403 in the log.\n */\nconst rows = computed<Row[]>(() =>\n props.descriptors.map((descriptor) => {\n const result = store.results.get(descriptor.system)\n if (!descriptor.isEnabled()) {\n return { descriptor, selectable: false, reason: t('common.userSystem.state.configDisabled') }\n }\n if (isUndefined(result) || !isActionable(result.axes)) {\n const access = result?.axes.access\n const reason =\n access === UserSystemAccess.Forbidden\n ? t('common.userSystem.state.forbidden')\n : access === UserSystemAccess.Ok\n ? t('common.userSystem.state.absent')\n : t('common.userSystem.state.unavailable')\n return { descriptor, selectable: false, reason }\n }\n // Already where the action would take it. A no-op PUT would still run `updateRelations()` in\n // cms over seven scalars and twelve collections -- risk for nothing.\n if (props.action === BulkAction.Enable && result.axes.enabled === true) {\n return { descriptor, selectable: false, reason: t('common.userSystem.bulk.alreadyInState') }\n }\n if (props.action === BulkAction.Disable && result.axes.enabled === false) {\n return { descriptor, selectable: false, reason: t('common.userSystem.bulk.alreadyInState') }\n }\n return { descriptor, selectable: true, reason: null }\n })\n)\n\nconst selectableSystems = computed(() => rows.value.filter((row) => row.selectable).map((row) => row.descriptor.system))\n\n// Everything that can be acted on, ticked. Blog and forum included: in practice a departing\n// employee is switched off there too in almost every case, so leaving them out would mean an extra\n// click nearly always. The note in their row stays -- as information, not as a reason to untick.\nwatch(\n open,\n (value) => {\n if (!value) return\n showingLog.value = false\n selected.value = [...selectableSystems.value]\n },\n { immediate: true }\n)\n\nconst titleKey = computed(() => `common.userSystem.bulk.${props.action}.title`)\nconst confirmKey = computed(() => `common.userSystem.bulk.${props.action}.confirm`)\n\n/**\n * This run's lines only.\n *\n * The log outlives a run on purpose, so without the filter the dialog would show a metadata repair's\n * failures and offer to retry them -- as an enable or a disable, against whichever account is on\n * screen now.\n */\nconst currentLog = computed(() =>\n store.bulkLog.filter((entry) => entry.action === props.action && entry.subjectId === store.resolvedId)\n)\n\nconst failed = computed(() =>\n currentLog.value.filter((entry) => entry.outcome !== BulkOutcome.Done && entry.outcome !== BulkOutcome.Pending)\n)\n\nconst run = () => {\n showingLog.value = true\n emit('run', [...selected.value])\n}\n\n/** Retries everything that failed, 403 included: the operator may have been granted the right meanwhile. */\nconst retryFailed = () => {\n emit(\n 'run',\n failed.value.map((entry) => entry.system)\n )\n}\n\nconst labelFor = (descriptor: AnyUserSystemDescriptor) => descriptor.label ?? descriptor.system\n</script>\n\n<template>\n <VDialog\n v-model=\"open\"\n :max-width=\"720\"\n data-cy=\"user-bulk-dialog\"\n >\n <VCard>\n <!-- Closing while the calls are still going is allowed; the run lives in the store. -->\n <ADialogToolbar @on-cancel=\"open = false\">\n {{ t(titleKey) }}\n </ADialogToolbar>\n <VCardText v-if=\"!showingLog\">\n <VAlert\n v-if=\"store.identityConflict\"\n type=\"error\"\n variant=\"tonal\"\n density=\"compact\"\n class=\"mb-3\"\n >\n {{ t('common.userSystem.search.identityConflict') }}\n </VAlert>\n <div\n v-for=\"row in rows\"\n :key=\"row.descriptor.system\"\n class=\"d-flex align-center ga-2 py-1\"\n >\n <VCheckbox\n v-model=\"selected\"\n :value=\"row.descriptor.system\"\n :disabled=\"!row.selectable || !!store.identityConflict\"\n :data-cy=\"`bulk-system-${row.descriptor.system}`\"\n density=\"compact\"\n hide-details\n />\n <div class=\"font-weight-medium\">{{ labelFor(row.descriptor) }}</div>\n <AUserSystemStatusChip\n v-if=\"store.results.get(row.descriptor.system)\"\n :axes=\"store.results.get(row.descriptor.system)!.axes\"\n />\n <div\n v-if=\"row.reason\"\n class=\"text-body-small text-medium-emphasis\"\n >\n {{ row.reason }}\n </div>\n <div\n v-if=\"row.descriptor.enabledNote && action !== BulkAction.Metadata\"\n class=\"text-body-small text-warning w-100\"\n >\n {{ row.descriptor.enabledNote }}\n </div>\n <div\n v-if=\"row.descriptor.metadataNote && action === BulkAction.Metadata\"\n class=\"text-body-small text-warning w-100\"\n >\n {{ row.descriptor.metadataNote }}\n </div>\n </div>\n </VCardText>\n <VCardText v-else>\n <div class=\"text-label-large mb-2\">{{ t('common.userSystem.bulk.log') }}</div>\n <!--\n `polite`, never `assertive`: nine systems finishing one after another would flood a screen\n reader. Focus deliberately stays where it is when the dialog switches to this view.\n -->\n <div\n aria-live=\"polite\"\n data-cy=\"user-bulk-log\"\n >\n <div\n v-for=\"entry in currentLog\"\n :key=\"entry.system\"\n class=\"d-flex align-center ga-2 py-1\"\n >\n <div class=\"font-weight-medium\">{{ entry.system }}</div>\n <div class=\"text-body-small\">{{ t(`common.userSystem.bulk.outcome.${entry.outcome}`) }}</div>\n <div\n v-if=\"entry.detail\"\n class=\"text-body-small text-medium-emphasis\"\n >\n {{ entry.detail }}\n </div>\n </div>\n </div>\n </VCardText>\n <VCardActions>\n <ABtnTertiary @click.stop=\"open = false\">\n {{ showingLog ? t('common.userSystem.bulk.close') : t('common.button.cancel') }}\n </ABtnTertiary>\n <VSpacer />\n <ABtnTertiary\n v-if=\"showingLog && failed.length > 0 && !store.bulkRunning\"\n data-cy=\"user-bulk-retry\"\n @click.stop=\"retryFailed\"\n >\n {{ t('common.userSystem.bulk.retryFailed') }}\n </ABtnTertiary>\n <ABtnPrimary\n v-if=\"!showingLog\"\n :disabled=\"selected.length === 0 || !!store.identityConflict\"\n data-cy=\"user-bulk-confirm\"\n @click.stop=\"run\"\n >\n {{ t(confirmKey, { count: selected.length }, selected.length) }}\n </ABtnPrimary>\n </VCardActions>\n </VCard>\n </VDialog>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport ADialogToolbar from '@/components/ADialogToolbar.vue'\nimport AUserSystemStatusChip from '@/labs/anzuUser/AUserSystemStatusChip.vue'\nimport {\n BulkAction,\n BulkOutcome,\n useUserCrossSystemStore,\n type BulkActionType,\n} from '@/labs/anzuUser/userCrossSystemStore'\nimport type { AnyUserSystemDescriptor } from '@/labs/anzuUser/userSystemDescriptor'\nimport { isActionable, UserSystemAccess } from '@/labs/anzuUser/userSystemState'\nimport { isUndefined } from '@/utils/common'\n\nconst props = defineProps<{\n action: BulkActionType\n descriptors: AnyUserSystemDescriptor[]\n}>()\n\nconst emit = defineEmits<{\n (e: 'run', systems: string[]): void\n}>()\n\nconst open = defineModel<boolean>('open', { required: true })\n\nconst { t } = useI18n()\nconst store = useUserCrossSystemStore()\n\nconst selected = ref<string[]>([])\n/** The dialog shows the list first and the log after the operator confirms. */\nconst showingLog = ref(false)\n\ninterface Row {\n descriptor: AnyUserSystemDescriptor\n selectable: boolean\n reason: string | null\n}\n\n/**\n * Which systems can be ticked, decided by the probe and not by ACL.\n *\n * This is a cross-system surface: no current user is loaded for the other backends, so `<Acl>`\n * would return false everywhere and quietly tick nothing. Not selectable are the systems the probe\n * can speak for -- 403, 404, switched off in configuration -- plus the ones that did not answer at\n * all, because acting on those would be a guess. \"May read but may not write\" cannot be seen from\n * here and ends up as a 403 in the log.\n */\nconst rows = computed<Row[]>(() =>\n props.descriptors.map((descriptor) => {\n const result = store.results.get(descriptor.system)\n if (!descriptor.isEnabled()) {\n return { descriptor, selectable: false, reason: t('common.userSystem.state.configDisabled') }\n }\n if (isUndefined(result) || !isActionable(result.axes)) {\n const access = result?.axes.access\n const reason =\n access === UserSystemAccess.Forbidden\n ? t('common.userSystem.state.forbidden')\n : access === UserSystemAccess.Ok\n ? t('common.userSystem.state.absent')\n : t('common.userSystem.state.unavailable')\n return { descriptor, selectable: false, reason }\n }\n // Already where the action would take it. A no-op PUT would still run `updateRelations()` in\n // cms over seven scalars and twelve collections -- risk for nothing.\n if (props.action === BulkAction.Enable && result.axes.enabled === true) {\n return { descriptor, selectable: false, reason: t('common.userSystem.bulk.alreadyInState') }\n }\n if (props.action === BulkAction.Disable && result.axes.enabled === false) {\n return { descriptor, selectable: false, reason: t('common.userSystem.bulk.alreadyInState') }\n }\n return { descriptor, selectable: true, reason: null }\n })\n)\n\nconst selectableSystems = computed(() => rows.value.filter((row) => row.selectable).map((row) => row.descriptor.system))\n\n// Everything that can be acted on, ticked. Blog and forum included: in practice a departing\n// employee is switched off there too in almost every case, so leaving them out would mean an extra\n// click nearly always. The note in their row stays -- as information, not as a reason to untick.\nwatch(\n open,\n (value) => {\n if (!value) return\n showingLog.value = false\n selected.value = [...selectableSystems.value]\n },\n { immediate: true }\n)\n\nconst titleKey = computed(() => `common.userSystem.bulk.${props.action}.title`)\nconst confirmKey = computed(() => `common.userSystem.bulk.${props.action}.confirm`)\n\n/**\n * This run's lines only.\n *\n * The log outlives a run on purpose, so without the filter the dialog would show a metadata repair's\n * failures and offer to retry them -- as an enable or a disable, against whichever account is on\n * screen now.\n */\nconst currentLog = computed(() =>\n store.bulkLog.filter((entry) => entry.action === props.action && entry.subjectId === store.resolvedId)\n)\n\nconst failed = computed(() =>\n currentLog.value.filter((entry) => entry.outcome !== BulkOutcome.Done && entry.outcome !== BulkOutcome.Pending)\n)\n\nconst run = () => {\n showingLog.value = true\n emit('run', [...selected.value])\n}\n\n/** Retries everything that failed, 403 included: the operator may have been granted the right meanwhile. */\nconst retryFailed = () => {\n emit(\n 'run',\n failed.value.map((entry) => entry.system)\n )\n}\n\nconst labelFor = (descriptor: AnyUserSystemDescriptor) => descriptor.label ?? descriptor.system\n</script>\n\n<template>\n <VDialog\n v-model=\"open\"\n :max-width=\"720\"\n data-cy=\"user-bulk-dialog\"\n >\n <VCard>\n <!-- Closing while the calls are still going is allowed; the run lives in the store. -->\n <ADialogToolbar @on-cancel=\"open = false\">\n {{ t(titleKey) }}\n </ADialogToolbar>\n <VCardText v-if=\"!showingLog\">\n <VAlert\n v-if=\"store.identityConflict\"\n type=\"error\"\n variant=\"tonal\"\n density=\"compact\"\n class=\"mb-3\"\n >\n {{ t('common.userSystem.search.identityConflict') }}\n </VAlert>\n <div\n v-for=\"row in rows\"\n :key=\"row.descriptor.system\"\n class=\"d-flex align-center ga-2 py-1\"\n >\n <VCheckbox\n v-model=\"selected\"\n :value=\"row.descriptor.system\"\n :disabled=\"!row.selectable || !!store.identityConflict\"\n :data-cy=\"`bulk-system-${row.descriptor.system}`\"\n density=\"compact\"\n hide-details\n />\n <div class=\"font-weight-medium\">{{ labelFor(row.descriptor) }}</div>\n <AUserSystemStatusChip\n v-if=\"store.results.get(row.descriptor.system)\"\n :axes=\"store.results.get(row.descriptor.system)!.axes\"\n />\n <div\n v-if=\"row.reason\"\n class=\"text-body-small text-medium-emphasis\"\n >\n {{ row.reason }}\n </div>\n <div\n v-if=\"row.descriptor.enabledNote && action !== BulkAction.Metadata\"\n class=\"text-body-small text-warning w-100\"\n >\n {{ row.descriptor.enabledNote }}\n </div>\n <div\n v-if=\"row.descriptor.metadataNote && action === BulkAction.Metadata\"\n class=\"text-body-small text-warning w-100\"\n >\n {{ row.descriptor.metadataNote }}\n </div>\n </div>\n </VCardText>\n <VCardText v-else>\n <div class=\"text-label-large mb-2\">{{ t('common.userSystem.bulk.log') }}</div>\n <!--\n `polite`, never `assertive`: nine systems finishing one after another would flood a screen\n reader. Focus deliberately stays where it is when the dialog switches to this view.\n -->\n <div\n aria-live=\"polite\"\n data-cy=\"user-bulk-log\"\n >\n <div\n v-for=\"entry in currentLog\"\n :key=\"entry.system\"\n class=\"d-flex align-center ga-2 py-1\"\n >\n <div class=\"font-weight-medium\">{{ entry.system }}</div>\n <div class=\"text-body-small\">{{ t(`common.userSystem.bulk.outcome.${entry.outcome}`) }}</div>\n <div\n v-if=\"entry.detail\"\n class=\"text-body-small text-medium-emphasis\"\n >\n {{ entry.detail }}\n </div>\n </div>\n </div>\n </VCardText>\n <VCardActions>\n <ABtnTertiary @click.stop=\"open = false\">\n {{ showingLog ? t('common.userSystem.bulk.close') : t('common.button.cancel') }}\n </ABtnTertiary>\n <VSpacer />\n <ABtnTertiary\n v-if=\"showingLog && failed.length > 0 && !store.bulkRunning\"\n data-cy=\"user-bulk-retry\"\n @click.stop=\"retryFailed\"\n >\n {{ t('common.userSystem.bulk.retryFailed') }}\n </ABtnTertiary>\n <ABtnPrimary\n v-if=\"!showingLog\"\n :disabled=\"selected.length === 0 || !!store.identityConflict\"\n data-cy=\"user-bulk-confirm\"\n @click.stop=\"run\"\n >\n {{ t(confirmKey, { count: selected.length }, selected.length) }}\n </ABtnPrimary>\n </VCardActions>\n </VCard>\n </VDialog>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport ADialogToolbar from '@/components/ADialogToolbar.vue'\nimport AUserMetadataForm from '@/labs/anzuUser/AUserMetadataForm.vue'\nimport AUserSystemStatusChip from '@/labs/anzuUser/AUserSystemStatusChip.vue'\nimport type { AnyUserSystemDescriptor } from '@/labs/anzuUser/userSystemDescriptor'\nimport {\n pickPrefillSource,\n readMetadataField,\n systemsNeedingWrite,\n USER_METADATA_FIELDS,\n} from '@/labs/anzuUser/userMetadataDiff'\nimport { BulkAction, BulkOutcome, useUserCrossSystemStore } from '@/labs/anzuUser/userCrossSystemStore'\nimport type { BaseUser } from '@/types/AnzuUser'\nimport { cloneDeep, isUndefined } from '@/utils/common'\n\nconst props = defineProps<{\n descriptors: AnyUserSystemDescriptor[]\n}>()\n\nconst emit = defineEmits<{\n (e: 'confirm', payload: { target: BaseUser; systems: string[] }): void\n}>()\n\nconst open = defineModel<boolean>('open', { required: true })\n\nconst { t } = useI18n()\nconst store = useUserCrossSystemStore()\n\nconst records = computed(() => {\n const map = new Map<string, BaseUser>()\n for (const descriptor of props.descriptors) {\n const user = store.results.get(descriptor.system)?.user\n if (user) map.set(descriptor.system, user)\n }\n return map\n})\n\nconst sourceSystem = ref<string | null>(null)\nconst target = ref<BaseUser | null>(null)\n/**\n * Which systems the write goes to. Ticked by default, and untickable one by one -- the same shape\n * as the enable/disable dialog, because an operator who knows one system's value is deliberately\n * different needs a way to leave it out that is not \"abort the whole repair\".\n */\nconst selected = ref<string[]>([])\n/**\n * The dialog stays open after confirm and shows the run, exactly as the enable/disable one does.\n *\n * Closing on confirm is what made a failed repair invisible: the outcomes went into the shared log\n * with nothing rendering them, so a 422 on one system looked the same as a repair that worked.\n */\nconst showingLog = ref(false)\n\n/**\n * The pre-fill comes from the first system in the configured order that has a record -- never from\n * whichever answered first, since the fan-out is parallel and that would differ every run.\n *\n * And the source is not hidden: it is named here and can be changed, so the order in the config is\n * a default rather than a rule nobody can see.\n */\nwatch(\n open,\n (value) => {\n if (!value) return\n showingLog.value = false\n const order = props.descriptors.map((descriptor) => descriptor.system)\n sourceSystem.value = pickPrefillSource(order, records.value)\n const source = sourceSystem.value === null ? undefined : records.value.get(sourceSystem.value)\n target.value = isUndefined(source)\n ? null\n : cloneDeep({ id: source.id, email: source.email, person: source.person, avatar: source.avatar })\n },\n { immediate: true }\n)\n\nwatch(sourceSystem, (system) => {\n if (system === null) return\n const source = records.value.get(system)\n if (isUndefined(source)) return\n target.value = cloneDeep({ id: source.id, email: source.email, person: source.person, avatar: source.avatar })\n})\n\nconst sourceOptions = computed(() =>\n props.descriptors\n .filter((descriptor) => records.value.has(descriptor.system))\n .map((descriptor) => ({ value: descriptor.system, title: descriptor.label ?? descriptor.system }))\n)\n\n/** Only the systems whose stored value differs. Nothing else is offered, let alone written to. */\nconst differing = computed(() => (target.value === null ? [] : systemsNeedingWrite(target.value, records.value)))\n\n/** What confirm would actually write to: the differing systems that are still ticked. */\nconst affected = computed(() => differing.value.filter((system) => selected.value.includes(system)))\n\n// Everything that differs, ticked, whenever the set changes -- on open and on a source switch.\nwatch(\n differing,\n (systems) => {\n selected.value = [...systems]\n },\n { immediate: true }\n)\n\nconst changes = computed(() => {\n if (target.value === null) return []\n return differing.value.flatMap((system) => {\n const current = records.value.get(system)\n if (isUndefined(current)) return []\n return USER_METADATA_FIELDS.filter(\n (field) => readMetadataField(current, field) !== readMetadataField(target.value as BaseUser, field)\n ).map((field) => ({\n system,\n field,\n from: readMetadataField(current, field),\n to: readMetadataField(target.value as BaseUser, field),\n }))\n })\n})\n\nconst currentLog = computed(() =>\n store.bulkLog.filter((entry) => entry.action === BulkAction.Metadata && entry.subjectId === store.resolvedId)\n)\n\nconst failed = computed(() =>\n currentLog.value.filter((entry) => entry.outcome !== BulkOutcome.Done && entry.outcome !== BulkOutcome.Pending)\n)\n\nconst noteFor = (system: string) => props.descriptors.find((item) => item.system === system)?.metadataNote\n\nconst labelFor = (system: string) => props.descriptors.find((item) => item.system === system)?.label ?? system\n\nconst confirm = () => {\n if (target.value === null || store.identityConflict !== null || affected.value.length === 0) return\n showingLog.value = true\n emit('confirm', { target: target.value, systems: affected.value })\n}\n\n/** The same value again, to the systems it did not reach. 403 included: rights may have been granted since. */\nconst retryFailed = () => {\n if (target.value === null) return\n emit('confirm', { target: target.value, systems: failed.value.map((entry) => entry.system) })\n}\n</script>\n\n<template>\n <VDialog\n v-model=\"open\"\n :max-width=\"900\"\n data-cy=\"user-metadata-repair-dialog\"\n >\n <VCard>\n <ADialogToolbar @on-cancel=\"open = false\">\n {{ t('common.userSystem.repair.title') }}\n </ADialogToolbar>\n <VCardText v-if=\"target && !showingLog\">\n <!--\n Two ids behind one e-mail. Writing under either of them would put this person's metadata\n on somebody else's account, and no admin can delete an AnzuUser afterwards.\n -->\n <VAlert\n v-if=\"store.identityConflict\"\n type=\"error\"\n variant=\"tonal\"\n density=\"compact\"\n class=\"mb-3\"\n data-cy=\"repair-identity-conflict\"\n >\n {{ t('common.userSystem.search.identityConflict') }}\n </VAlert>\n <VSelect\n v-model=\"sourceSystem\"\n :items=\"sourceOptions\"\n :label=\"t('common.userSystem.repair.source')\"\n density=\"compact\"\n data-cy=\"repair-source\"\n />\n <!--\n Always strict, whatever the target systems' own profiles say: this writes to up to nine\n places at once, and a loose form could put an empty name into cms -- a record cms's own\n form would then refuse to save.\n\n `randomColor` is deliberately not passed. The form is strict, so it would invent a colour\n the moment the dialog opens and mark it as a change in every system at once.\n -->\n <AUserMetadataForm\n v-model:user=\"target\"\n required\n :id-input=\"false\"\n is-edit\n />\n <div class=\"mt-4 text-label-large\">{{ t('common.userSystem.repair.changes') }}</div>\n <div\n v-for=\"system in differing\"\n :key=\"system\"\n class=\"mt-2 d-flex align-start ga-2\"\n >\n <VCheckbox\n v-model=\"selected\"\n :value=\"system\"\n :disabled=\"!!store.identityConflict\"\n :data-cy=\"`repair-system-${system}`\"\n density=\"compact\"\n hide-details\n />\n <div>\n <div class=\"d-flex align-center ga-2\">\n <span class=\"font-weight-medium\">{{ labelFor(system) }}</span>\n <AUserSystemStatusChip\n v-if=\"store.results.get(system)\"\n :axes=\"store.results.get(system)!.axes\"\n />\n </div>\n <div\n v-if=\"noteFor(system)\"\n class=\"text-body-small text-warning\"\n >\n {{ noteFor(system) }}\n </div>\n <div\n v-for=\"change in changes.filter((item) => item.system === system)\"\n :key=\"change.field\"\n class=\"text-body-small\"\n >\n {{ change.field }}: <s>{{ change.from || '—' }}</s> → {{ change.to || '—' }}\n </div>\n </div>\n </div>\n </VCardText>\n <VCardText v-else-if=\"showingLog\">\n <div class=\"text-label-large mb-2\">{{ t('common.userSystem.bulk.log') }}</div>\n <!-- `polite`: nine systems finishing one after another would flood a screen reader. -->\n <div\n aria-live=\"polite\"\n data-cy=\"repair-log\"\n >\n <div\n v-for=\"entry in currentLog\"\n :key=\"entry.system\"\n class=\"d-flex align-center ga-2 py-1\"\n >\n <div class=\"font-weight-medium\">{{ labelFor(entry.system) }}</div>\n <div class=\"text-body-small\">{{ t(`common.userSystem.bulk.outcome.${entry.outcome}`) }}</div>\n <div\n v-if=\"entry.detail\"\n class=\"text-body-small text-warning\"\n >\n {{ entry.detail }}\n </div>\n </div>\n </div>\n </VCardText>\n <VCardActions>\n <ABtnTertiary @click.stop=\"open = false\">\n {{ showingLog ? t('common.userSystem.bulk.close') : t('common.button.cancel') }}\n </ABtnTertiary>\n <VSpacer />\n <ABtnTertiary\n v-if=\"showingLog && failed.length > 0 && !store.bulkRunning\"\n data-cy=\"repair-retry\"\n @click.stop=\"retryFailed\"\n >\n {{ t('common.userSystem.bulk.retryFailed') }}\n </ABtnTertiary>\n <ABtnPrimary\n v-if=\"!showingLog\"\n :disabled=\"affected.length === 0 || !!store.identityConflict\"\n data-cy=\"repair-confirm\"\n @click.stop=\"confirm\"\n >\n {{ t('common.userSystem.bulk.metadata.confirm', { count: affected.length }, affected.length) }}\n </ABtnPrimary>\n </VCardActions>\n </VCard>\n </VDialog>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport ADialogToolbar from '@/components/ADialogToolbar.vue'\nimport AUserMetadataForm from '@/labs/anzuUser/AUserMetadataForm.vue'\nimport AUserSystemStatusChip from '@/labs/anzuUser/AUserSystemStatusChip.vue'\nimport type { AnyUserSystemDescriptor } from '@/labs/anzuUser/userSystemDescriptor'\nimport {\n pickPrefillSource,\n readMetadataField,\n systemsNeedingWrite,\n USER_METADATA_FIELDS,\n} from '@/labs/anzuUser/userMetadataDiff'\nimport { BulkAction, BulkOutcome, useUserCrossSystemStore } from '@/labs/anzuUser/userCrossSystemStore'\nimport type { BaseUser } from '@/types/AnzuUser'\nimport { cloneDeep, isUndefined } from '@/utils/common'\n\nconst props = defineProps<{\n descriptors: AnyUserSystemDescriptor[]\n}>()\n\nconst emit = defineEmits<{\n (e: 'confirm', payload: { target: BaseUser; systems: string[] }): void\n}>()\n\nconst open = defineModel<boolean>('open', { required: true })\n\nconst { t } = useI18n()\nconst store = useUserCrossSystemStore()\n\nconst records = computed(() => {\n const map = new Map<string, BaseUser>()\n for (const descriptor of props.descriptors) {\n const user = store.results.get(descriptor.system)?.user\n if (user) map.set(descriptor.system, user)\n }\n return map\n})\n\nconst sourceSystem = ref<string | null>(null)\nconst target = ref<BaseUser | null>(null)\n/**\n * Which systems the write goes to. Ticked by default, and untickable one by one -- the same shape\n * as the enable/disable dialog, because an operator who knows one system's value is deliberately\n * different needs a way to leave it out that is not \"abort the whole repair\".\n */\nconst selected = ref<string[]>([])\n/**\n * The dialog stays open after confirm and shows the run, exactly as the enable/disable one does.\n *\n * Closing on confirm is what made a failed repair invisible: the outcomes went into the shared log\n * with nothing rendering them, so a 422 on one system looked the same as a repair that worked.\n */\nconst showingLog = ref(false)\n\n/**\n * The pre-fill comes from the first system in the configured order that has a record -- never from\n * whichever answered first, since the fan-out is parallel and that would differ every run.\n *\n * And the source is not hidden: it is named here and can be changed, so the order in the config is\n * a default rather than a rule nobody can see.\n */\nwatch(\n open,\n (value) => {\n if (!value) return\n showingLog.value = false\n const order = props.descriptors.map((descriptor) => descriptor.system)\n sourceSystem.value = pickPrefillSource(order, records.value)\n const source = sourceSystem.value === null ? undefined : records.value.get(sourceSystem.value)\n target.value = isUndefined(source)\n ? null\n : cloneDeep({ id: source.id, email: source.email, person: source.person, avatar: source.avatar })\n },\n { immediate: true }\n)\n\nwatch(sourceSystem, (system) => {\n if (system === null) return\n const source = records.value.get(system)\n if (isUndefined(source)) return\n target.value = cloneDeep({ id: source.id, email: source.email, person: source.person, avatar: source.avatar })\n})\n\nconst sourceOptions = computed(() =>\n props.descriptors\n .filter((descriptor) => records.value.has(descriptor.system))\n .map((descriptor) => ({ value: descriptor.system, title: descriptor.label ?? descriptor.system }))\n)\n\n/** Only the systems whose stored value differs. Nothing else is offered, let alone written to. */\nconst differing = computed(() => (target.value === null ? [] : systemsNeedingWrite(target.value, records.value)))\n\n/** What confirm would actually write to: the differing systems that are still ticked. */\nconst affected = computed(() => differing.value.filter((system) => selected.value.includes(system)))\n\n// Everything that differs, ticked, whenever the set changes -- on open and on a source switch.\nwatch(\n differing,\n (systems) => {\n selected.value = [...systems]\n },\n { immediate: true }\n)\n\nconst changes = computed(() => {\n if (target.value === null) return []\n return differing.value.flatMap((system) => {\n const current = records.value.get(system)\n if (isUndefined(current)) return []\n return USER_METADATA_FIELDS.filter(\n (field) => readMetadataField(current, field) !== readMetadataField(target.value as BaseUser, field)\n ).map((field) => ({\n system,\n field,\n from: readMetadataField(current, field),\n to: readMetadataField(target.value as BaseUser, field),\n }))\n })\n})\n\nconst currentLog = computed(() =>\n store.bulkLog.filter((entry) => entry.action === BulkAction.Metadata && entry.subjectId === store.resolvedId)\n)\n\nconst failed = computed(() =>\n currentLog.value.filter((entry) => entry.outcome !== BulkOutcome.Done && entry.outcome !== BulkOutcome.Pending)\n)\n\nconst noteFor = (system: string) => props.descriptors.find((item) => item.system === system)?.metadataNote\n\nconst labelFor = (system: string) => props.descriptors.find((item) => item.system === system)?.label ?? system\n\nconst confirm = () => {\n if (target.value === null || store.identityConflict !== null || affected.value.length === 0) return\n showingLog.value = true\n emit('confirm', { target: target.value, systems: affected.value })\n}\n\n/** The same value again, to the systems it did not reach. 403 included: rights may have been granted since. */\nconst retryFailed = () => {\n if (target.value === null) return\n emit('confirm', { target: target.value, systems: failed.value.map((entry) => entry.system) })\n}\n</script>\n\n<template>\n <VDialog\n v-model=\"open\"\n :max-width=\"900\"\n data-cy=\"user-metadata-repair-dialog\"\n >\n <VCard>\n <ADialogToolbar @on-cancel=\"open = false\">\n {{ t('common.userSystem.repair.title') }}\n </ADialogToolbar>\n <VCardText v-if=\"target && !showingLog\">\n <!--\n Two ids behind one e-mail. Writing under either of them would put this person's metadata\n on somebody else's account, and no admin can delete an AnzuUser afterwards.\n -->\n <VAlert\n v-if=\"store.identityConflict\"\n type=\"error\"\n variant=\"tonal\"\n density=\"compact\"\n class=\"mb-3\"\n data-cy=\"repair-identity-conflict\"\n >\n {{ t('common.userSystem.search.identityConflict') }}\n </VAlert>\n <VSelect\n v-model=\"sourceSystem\"\n :items=\"sourceOptions\"\n :label=\"t('common.userSystem.repair.source')\"\n density=\"compact\"\n data-cy=\"repair-source\"\n />\n <!--\n Always strict, whatever the target systems' own profiles say: this writes to up to nine\n places at once, and a loose form could put an empty name into cms -- a record cms's own\n form would then refuse to save.\n\n `randomColor` is deliberately not passed. The form is strict, so it would invent a colour\n the moment the dialog opens and mark it as a change in every system at once.\n -->\n <AUserMetadataForm\n v-model:user=\"target\"\n required\n :id-input=\"false\"\n is-edit\n />\n <div class=\"mt-4 text-label-large\">{{ t('common.userSystem.repair.changes') }}</div>\n <div\n v-for=\"system in differing\"\n :key=\"system\"\n class=\"mt-2 d-flex align-start ga-2\"\n >\n <VCheckbox\n v-model=\"selected\"\n :value=\"system\"\n :disabled=\"!!store.identityConflict\"\n :data-cy=\"`repair-system-${system}`\"\n density=\"compact\"\n hide-details\n />\n <div>\n <div class=\"d-flex align-center ga-2\">\n <span class=\"font-weight-medium\">{{ labelFor(system) }}</span>\n <AUserSystemStatusChip\n v-if=\"store.results.get(system)\"\n :axes=\"store.results.get(system)!.axes\"\n />\n </div>\n <div\n v-if=\"noteFor(system)\"\n class=\"text-body-small text-warning\"\n >\n {{ noteFor(system) }}\n </div>\n <div\n v-for=\"change in changes.filter((item) => item.system === system)\"\n :key=\"change.field\"\n class=\"text-body-small\"\n >\n {{ change.field }}: <s>{{ change.from || '—' }}</s> → {{ change.to || '—' }}\n </div>\n </div>\n </div>\n </VCardText>\n <VCardText v-else-if=\"showingLog\">\n <div class=\"text-label-large mb-2\">{{ t('common.userSystem.bulk.log') }}</div>\n <!-- `polite`: nine systems finishing one after another would flood a screen reader. -->\n <div\n aria-live=\"polite\"\n data-cy=\"repair-log\"\n >\n <div\n v-for=\"entry in currentLog\"\n :key=\"entry.system\"\n class=\"d-flex align-center ga-2 py-1\"\n >\n <div class=\"font-weight-medium\">{{ labelFor(entry.system) }}</div>\n <div class=\"text-body-small\">{{ t(`common.userSystem.bulk.outcome.${entry.outcome}`) }}</div>\n <div\n v-if=\"entry.detail\"\n class=\"text-body-small text-warning\"\n >\n {{ entry.detail }}\n </div>\n </div>\n </div>\n </VCardText>\n <VCardActions>\n <ABtnTertiary @click.stop=\"open = false\">\n {{ showingLog ? t('common.userSystem.bulk.close') : t('common.button.cancel') }}\n </ABtnTertiary>\n <VSpacer />\n <ABtnTertiary\n v-if=\"showingLog && failed.length > 0 && !store.bulkRunning\"\n data-cy=\"repair-retry\"\n @click.stop=\"retryFailed\"\n >\n {{ t('common.userSystem.bulk.retryFailed') }}\n </ABtnTertiary>\n <ABtnPrimary\n v-if=\"!showingLog\"\n :disabled=\"affected.length === 0 || !!store.identityConflict\"\n data-cy=\"repair-confirm\"\n @click.stop=\"confirm\"\n >\n {{ t('common.userSystem.bulk.metadata.confirm', { count: affected.length }, affected.length) }}\n </ABtnPrimary>\n </VCardActions>\n </VCard>\n </VDialog>\n</template>\n","import { computed } from 'vue'\nimport { useApiFetchList } from '@/labs/api/useApiFetchList'\nimport { usePagination } from '@/labs/filters/pagination'\nimport { SortOrder } from '@/composables/system/datatableColumns'\nimport { useAnzuUserEmailLookupFilter } from '@/labs/anzuUser/anzuUserFilter'\nimport { CrossSystemPhase, useUserCrossSystemStore, type UserSystemResult } from '@/labs/anzuUser/userCrossSystemStore'\nimport type { AnyUserSystemDescriptor } from '@/labs/anzuUser/userSystemDescriptor'\nimport { useUserSystemProbe, type UserSystemRefreshHook } from '@/labs/anzuUser/userSystemProbe'\nimport {\n isAuthoritativelyAbsent,\n UserSystemAccess,\n UserSystemLoad,\n UserSystemPresence,\n} from '@/labs/anzuUser/userSystemState'\nimport type { AnzuUser } from '@/types/AnzuUser'\nimport type { IntegerId } from '@/types/common'\n\nexport interface CrossSystemSearchParams {\n descriptors: AnyUserSystemDescriptor[]\n refreshHook?: UserSystemRefreshHook | undefined\n}\n\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+$/\n\nexport const isEmailTerm = (term: string): boolean => EMAIL_PATTERN.test(term.trim())\n\n/**\n * The cross-system fan-out.\n *\n * No calls to the current-user endpoints go out at all: the buttons in a row are decided by the\n * probe's answer rather than by `<Acl>`, so there is nothing to load nine current users for. The\n * budget is nine calls when searching by id, eighteen when searching by e-mail.\n */\nexport const useUserCrossSystemSearch = (params: CrossSystemSearchParams) => {\n const store = useUserCrossSystemStore()\n const { descriptors, refreshHook } = params\n\n // One probe instance per system, built once: each keeps its own abort token, and rebuilding them\n // per search would lose the ability to invalidate what is still in flight.\n const probes = new Map(\n descriptors.map((descriptor) => [descriptor.system, useUserSystemProbe({ descriptor, refreshHook })])\n )\n\n /**\n * Every search gets a number. A late answer from the previous one is dropped rather than written\n * over the results of the search the operator is looking at now.\n */\n let generation = 0\n\n const cancel = () => {\n generation++\n probes.forEach((probe) => probe.cancel())\n }\n\n const runProbe = async (descriptor: AnyUserSystemDescriptor, id: IntegerId, token: number) => {\n const probe = probes.get(descriptor.system)\n if (!probe) return\n store.patchAxes(descriptor.system, { ...probe.axes.value, load: UserSystemLoad.Loading })\n await probe.probe(id)\n if (token !== generation) return\n store.setResult(descriptor.system, { axes: probe.axes.value, user: probe.user.value })\n }\n\n /** Results are written as they arrive, never through `Promise.all`: one dead backend must not hold up the rest. */\n const searchById = async (id: IntegerId) => {\n const token = ++generation\n store.resetResults()\n store.resolvedId = id\n store.phase = CrossSystemPhase.VerifyingById\n await Promise.allSettled(descriptors.map((descriptor) => runProbe(descriptor, id, token)))\n if (token !== generation) return\n store.phase = CrossSystemPhase.Done\n }\n\n /** Exact match, `filter_eq[email]`, one row. `startsWith` would match a different person. */\n const lookupByEmail = async (descriptor: AnyUserSystemDescriptor, email: string): Promise<AnzuUser | null> => {\n if (!descriptor.isEnabled()) return null\n const { filterData, filterConfig } = useAnzuUserEmailLookupFilter()\n filterData.email = email\n // One row: an exact match either exists or does not, and asking for a page of them would only\n // cost the backend more.\n const { pagination } = usePagination('id', SortOrder.Desc, { rowsPerPage: 1 })\n\n const { execute } = useApiFetchList<AnzuUser>({\n client: descriptor.client,\n system: descriptor.system,\n entity: descriptor.entity,\n urlTemplate: descriptor.endpoints.list,\n })\n\n try {\n const rows = await execute(pagination, filterData, filterConfig)\n return rows[0] ?? null\n } catch {\n // The second round asks every system by id anyway, so a failure here is not a verdict about\n // that system -- it only means this round could not contribute an id.\n return null\n }\n }\n\n /**\n * Two rounds, and the first one must not lie while the second is still running.\n *\n * Between them a row is neither present nor absent: showing \"no account here\" in that window\n * would invite the operator to press Create for an account that does exist under another address.\n */\n const searchByEmail = async (email: string) => {\n const token = ++generation\n store.resetResults()\n store.phase = CrossSystemPhase.MatchingEmail\n\n const found = await Promise.all(\n descriptors.map(async (descriptor) => ({\n system: descriptor.system,\n user: await lookupByEmail(descriptor, email),\n }))\n )\n if (token !== generation) return\n\n const ids = new Map<string, IntegerId>()\n for (const row of found) {\n if (row.user?.id) ids.set(row.system, row.user.id)\n }\n\n const distinctIds = new Set(ids.values())\n if (distinctIds.size > 1) {\n // The same address under two identities. Bulk actions stop here rather than guessing.\n store.identityConflict = ids\n store.phase = CrossSystemPhase.Done\n for (const row of found) {\n if (!row.user) continue\n store.setResult(row.system, {\n axes: {\n load: UserSystemLoad.Loaded,\n presence: UserSystemPresence.Present,\n access: UserSystemAccess.Ok,\n enabled: row.user.enabled,\n },\n user: row.user,\n })\n }\n return\n }\n\n const id = distinctIds.values().next().value\n if (id === undefined) {\n // Nobody anywhere under this address. Every row is still unknown rather than absent: an\n // address search cannot prove absence, only an id search can.\n store.phase = CrossSystemPhase.Done\n return\n }\n\n await searchById(id)\n }\n\n /**\n * Runs the fan-out, and answers whether it ran at all.\n *\n * A term that is neither an address nor an id has to clear the screen rather than be ignored.\n * The rows left standing are live: their buttons and all three bulk actions write to\n * `resolvedId`, which is still the previous person, while the box above them now holds a name\n * that was never searched for. The plan expects this operator -- the one who knows only a\n * surname -- so the answer has to be an empty result, not the last one.\n */\n const search = async (term: string): Promise<boolean> => {\n const trimmed = term.trim()\n if (trimmed.length === 0) return false\n store.searchTerm = trimmed\n if (isEmailTerm(trimmed)) {\n await searchByEmail(trimmed)\n return true\n }\n const id = Number(trimmed)\n if (!Number.isInteger(id) || id <= 0) {\n cancel()\n store.resetResults()\n store.termRejected = true\n return false\n }\n await searchById(id)\n return true\n }\n\n /** Re-reads one system after a write, so the row stops showing what it showed before it. */\n const refreshSystem = async (system: string) => {\n const descriptor = descriptors.find((item) => item.system === system)\n const id = store.resolvedId\n if (!descriptor || id === null) return\n await runProbe(descriptor, id, generation)\n }\n\n const resultFor = (system: string): UserSystemResult | undefined => store.results.get(system)\n\n /**\n * Whether creating an account may be offered when nobody was found.\n *\n * Only when every relevant probe answered an authoritative 404. A 401, a 403 or a timeout means\n * the record could not be read, which is not the same as not existing -- and a duplicate identity\n * cannot be taken back.\n */\n const unresolvedSystems = computed(() =>\n descriptors\n .filter((descriptor) => descriptor.isEnabled())\n .filter((descriptor) => {\n const result = store.results.get(descriptor.system)\n return result === undefined || !isAuthoritativelyAbsent(result.axes)\n })\n .map((descriptor) => descriptor.system)\n )\n\n return {\n search,\n searchById,\n searchByEmail,\n refreshSystem,\n resultFor,\n unresolvedSystems,\n cancel,\n }\n}\n","<script lang=\"ts\" setup>\nimport { computed, onBeforeUnmount, ref } from 'vue'\nimport useVuelidate from '@vuelidate/core'\nimport { useI18n } from 'vue-i18n'\nimport ADialogToolbar from '@/components/ADialogToolbar.vue'\nimport { useAlerts } from '@/composables/system/alerts'\nimport { useAnzuUserFactory } from '@/model/factory/AnzuUserFactory'\nimport AUserBulkActionDialog from '@/labs/anzuUser/AUserBulkActionDialog.vue'\nimport AUserCreateInSystemButton from '@/labs/anzuUser/AUserCreateInSystemButton.vue'\nimport AUserManageButton from '@/labs/anzuUser/AUserManageButton.vue'\nimport AUserMetadataForm from '@/labs/anzuUser/AUserMetadataForm.vue'\nimport AUserMetadataRepairDialog from '@/labs/anzuUser/AUserMetadataRepairDialog.vue'\nimport AUserSystemStatusChip from '@/labs/anzuUser/AUserSystemStatusChip.vue'\nimport {\n BulkAction,\n BulkOutcome,\n CrossSystemPhase,\n useUserCrossSystemStore,\n type BulkActionType,\n type BulkOutcomeType,\n} from '@/labs/anzuUser/userCrossSystemStore'\nimport { isEmailTerm, useUserCrossSystemSearch } from '@/labs/anzuUser/userCrossSystemSearch'\nimport { useUserCrossSystemWrites } from '@/labs/anzuUser/userCrossSystemWrites'\nimport { findMetadataDifferences, type UserMetadataField } from '@/labs/anzuUser/userMetadataDiff'\nimport type { AnyUserSystemDescriptor } from '@/labs/anzuUser/userSystemDescriptor'\nimport type { UserSystemRefreshHook } from '@/labs/anzuUser/userSystemProbe'\nimport { isActionable } from '@/labs/anzuUser/userSystemState'\nimport type { AnzuUser, BaseUser } from '@/types/AnzuUser'\nimport { cloneDeep, isNull, isUndefined } from '@/utils/common'\n\nconst props = defineProps<{\n /** Order matters twice: the order of the rows, and the priority when pre-filling. */\n descriptors: AnyUserSystemDescriptor[]\n refreshHook?: UserSystemRefreshHook | undefined\n}>()\n\nconst emit = defineEmits<{\n (e: 'searched', term: string): void\n}>()\n\nconst { t } = useI18n()\nconst { showError } = useAlerts()\nconst store = useUserCrossSystemStore()\nconst { createAnzuUser } = useAnzuUserFactory()\nconst writes = useUserCrossSystemWrites()\n\n// A collector for the metadata form inside the create dialog. Without one its rules register\n// against whatever ancestor happens to have an instance, and the dialog's confirm could not ask\n// whether the fields it is about to send are valid.\nconst v$ = useVuelidate()\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { search, refreshSystem, unresolvedSystems, cancel } = useUserCrossSystemSearch({\n descriptors: props.descriptors,\n refreshHook: props.refreshHook,\n})\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst term = ref('')\nconst rowBusy = ref(new Set<string>())\n\nconst runSearch = async () => {\n const value = term.value.trim()\n if (value.length === 0) return\n emit('searched', value)\n await search(value)\n}\n\nconst records = computed(() => {\n const map = new Map<string, BaseUser>()\n for (const descriptor of props.descriptors) {\n const user = store.results.get(descriptor.system)?.user\n if (user) map.set(descriptor.system, user)\n }\n return map\n})\n\nconst differences = computed(() => findMetadataDifferences(records.value))\n\nconst foundAnywhere = computed(() => records.value.size > 0)\nconst searchDone = computed(() => store.phase === CrossSystemPhase.Done)\n\nconst searchedByEmail = computed(() => isEmailTerm(store.searchTerm))\n\n/**\n * When creating an account may be offered after a search that found nobody.\n *\n * By id: only when every relevant probe answered an authoritative 404. A record that could not be\n * read is not a record that does not exist, and no admin can delete an AnzuUser, so a duplicate\n * identity created on a guess cannot be taken back.\n *\n * By e-mail: always, because an address search cannot prove absence at all -- a system holding the\n * same person under a different address answers nothing, which is why the second round exists.\n * What makes it safe is that the dialog asks for the id and runs its own authoritative fan-out on\n * it before writing anything; refusing here would put the flow decision 21 describes out of reach\n * of the search people actually use, since the id is the thing they do not know yet.\n */\nconst canOfferCreateAnywhere = computed(\n () => searchDone.value && !foundAnywhere.value && (searchedByEmail.value || unresolvedSystems.value.length === 0)\n)\n\n/** A conflict is not a state to act from: writes would land on somebody else's account. */\nconst blockedByConflict = computed(() => store.identityConflict !== null)\n\nconst labelFor = (descriptor: AnyUserSystemDescriptor) => descriptor.label ?? descriptor.system\n\nconst manageTargetFor = (descriptor: AnyUserSystemDescriptor) => {\n const user = store.results.get(descriptor.system)?.user\n if (isUndefined(user) || isNull(user) || isUndefined(descriptor.manage)) return null\n return descriptor.manage(user)\n}\n\nconst extraStateFor = (descriptor: AnyUserSystemDescriptor) => {\n const user = store.results.get(descriptor.system)?.user\n if (isUndefined(user) || isNull(user) || isUndefined(descriptor.extraState)) return null\n return descriptor.extraState(user)\n}\n\nconst EXTRA_STATE_COLOR = { ok: 'success', warn: 'warning', error: 'error' } as const\n\n/**\n * A failed row action says so.\n *\n * The bulk dialog has its log; a single row had nothing -- a 403 on Vypnúť left a spinner, an\n * unchanged row and silence, and the api helpers only write to the logger. The per-admin pages this\n * replaces put every write through `showErrorsDefault`.\n */\nconst reportOutcome = (label: string, outcome: BulkOutcomeType) => {\n if (outcome === BulkOutcome.Done) return\n showError(label + ': ' + t('common.userSystem.bulk.outcome.' + outcome))\n}\n\nconst detailFor = (fields: UserMetadataField[] | undefined) =>\n isUndefined(fields) || fields.length === 0\n ? undefined\n : t('common.userSystem.bulk.concurrentChange', { fields: fields.join(', ') })\n\nconst withRowBusy = async (system: string, run: () => Promise<void>) => {\n rowBusy.value = new Set([...rowBusy.value, system])\n try {\n await run()\n } finally {\n const next = new Set(rowBusy.value)\n next.delete(system)\n rowBusy.value = next\n }\n}\n\n/** After every write the touched row is read again -- otherwise it keeps showing the old state. */\nconst toggleOne = async (descriptor: AnyUserSystemDescriptor, enabled: boolean) => {\n const id = store.resolvedId\n if (isNull(id)) return\n await withRowBusy(descriptor.system, async () => {\n const result = await writes.setEnabled(descriptor, id, enabled)\n reportOutcome(labelFor(descriptor), result.outcome)\n await refreshSystem(descriptor.system)\n })\n}\n\nconst createFromSource = async (descriptor: AnyUserSystemDescriptor, metadata: BaseUser) => {\n const id = store.resolvedId\n if (isNull(id)) return\n const sourceSystem = props.descriptors.find((item) => records.value.has(item.system))\n if (isUndefined(sourceSystem)) return\n\n await withRowBusy(descriptor.system, async () => {\n // Read fresh from the server, never from the search results or a form's store: a switch flipped\n // and not saved would otherwise be copied into a system where it means a working public account.\n const source = await writes.readSourceUser(sourceSystem, id)\n if (isNull(source)) {\n showError(t('common.userSystem.create.sourceUnreadable', { system: labelFor(sourceSystem) }))\n return\n }\n // The same promise the panel makes: the dialog has already said whether this account will be\n // switched on, taken from the search result. If the server no longer agrees, somebody changed\n // it in between, and in blog and forum the difference is a working public account.\n if (source.enabled !== sourceEnabled.value) {\n store.setUser(sourceSystem.system, source)\n showError(t('common.userSystem.create.sourceChanged'))\n return\n }\n // Metadata from the dialog, which was validated against the target's profile: the source may\n // leave `person` empty -- blog fills it for nobody -- where the target requires it.\n const body = createAnzuUser(descriptor.system)\n body.id = source.id\n body.email = metadata.email\n body.person = cloneDeep(metadata.person)\n body.avatar = cloneDeep(metadata.avatar)\n // Copying a person into another system copies whether they are switched on. Roles stay empty.\n body.enabled = source.enabled\n const result = await writes.createInSystem(descriptor, body)\n reportOutcome(labelFor(descriptor), result.outcome)\n await refreshSystem(descriptor.system)\n })\n}\n\n/** The record the create form is prefilled from: the first configured system that has one. */\nconst prefillSource = computed(() => {\n const first = props.descriptors.find((item) => records.value.has(item.system))\n return isUndefined(first) ? null : (records.value.get(first.system) ?? null)\n})\n\nconst sourceEnabled = computed(() => {\n const first = props.descriptors.find((item) => records.value.has(item.system))\n if (isUndefined(first)) return false\n return store.results.get(first.system)?.user?.enabled === true\n})\n\n/* ---- bulk ---- */\n\nconst bulkOpen = ref(false)\nconst bulkAction = ref<BulkActionType>(BulkAction.Disable)\nconst repairOpen = ref(false)\n\nconst openBulk = (action: BulkActionType) => {\n bulkAction.value = action\n bulkOpen.value = true\n}\n\nconst runBulk = async (systems: string[]) => {\n const id = store.resolvedId\n if (isNull(id)) return\n const enabled = bulkAction.value === BulkAction.Enable\n store.startBulk(bulkAction.value, systems, id)\n await Promise.allSettled(\n systems.map(async (system) => {\n const descriptor = props.descriptors.find((item) => item.system === system)\n if (isUndefined(descriptor)) return\n store.setBulkEntry(system, BulkOutcome.Running)\n const result = await writes.setEnabled(descriptor, id, enabled)\n store.setBulkEntry(system, result.outcome)\n await refreshSystem(system)\n })\n )\n store.finishBulk()\n}\n\nconst runRepair = async (payload: { target: BaseUser; systems: string[] }) => {\n const id = store.resolvedId\n if (isNull(id)) return\n store.startBulk(BulkAction.Metadata, payload.systems, id)\n // What the search showed is captured before anything is written, so the comparison is against the\n // values the operator was actually looking at rather than against a row a sibling write refreshed.\n const seenBefore = new Map(records.value)\n await Promise.allSettled(\n payload.systems.map(async (system) => {\n const descriptor = props.descriptors.find((item) => item.system === system)\n if (isUndefined(descriptor)) return\n store.setBulkEntry(system, BulkOutcome.Running)\n const result = await writes.writeMetadata(\n descriptor,\n id,\n payload.target,\n undefined,\n seenBefore.get(system) ?? null\n )\n store.setBulkEntry(system, result.outcome, detailFor(result.concurrentFields))\n await refreshSystem(system)\n })\n )\n store.finishBulk()\n}\n\n/* ---- create where nobody was found ---- */\n\nconst createAnywhereOpen = ref(false)\nconst createAnywhereSystem = ref<string | null>(null)\nconst createAnywhereUser = ref<AnzuUser>(createAnzuUser())\nconst createAnywhereChecking = ref(false)\nconst createAnywhereConflict = ref<string[]>([])\nconst createAnywhereError = ref<string | null>(null)\n\nconst openCreateAnywhere = () => {\n createAnywhereSystem.value = props.descriptors[0]?.system ?? null\n createAnywhereUser.value = createAnzuUser(createAnywhereSystem.value ?? '')\n createAnywhereConflict.value = []\n createAnywhereError.value = null\n createAnywhereOpen.value = true\n}\n\n/**\n * Before creating, the typed id is looked up across the systems.\n *\n * cms, dam and contentHub skip the SSO lookup for a numeric id, and the validators check the id and\n * the e-mail separately rather than as a pair -- so a typo creates an account with one person's id\n * and another's e-mail, and every later fan-out then follows the wrong identity.\n *\n * The check has to be authoritative too: if a probe answers 401, 403 or nothing, creating is not\n * allowed, because whether that id belongs to somebody is unknown.\n */\nconst confirmCreateAnywhere = async () => {\n const id = createAnywhereUser.value.id\n const system = createAnywhereSystem.value\n createAnywhereConflict.value = []\n createAnywhereError.value = null\n if (isNull(system)) return\n // The id is checked here and not only by the form, because everything below depends on the\n // fan-out having run: a term the search refuses probes nothing, and the empty result that leaves\n // behind would otherwise read as \"this id belongs to nobody\".\n if (isNull(id) || isUndefined(id) || !Number.isInteger(id) || id <= 0) {\n createAnywhereError.value = t('common.userSystem.create.invalidId')\n return\n }\n if (!(await v$.value.$validate())) return\n createAnywhereChecking.value = true\n try {\n if (!(await search(String(id)))) {\n createAnywhereError.value = t('common.userSystem.create.invalidId')\n return\n }\n if (records.value.size > 0) {\n createAnywhereConflict.value = [...records.value.keys()]\n return\n }\n if (unresolvedSystems.value.length > 0) {\n createAnywhereConflict.value = unresolvedSystems.value\n return\n }\n const descriptor = props.descriptors.find((item) => item.system === system)\n if (isUndefined(descriptor)) return\n const body = createAnzuUser(system)\n body.id = id\n body.email = createAnywhereUser.value.email\n body.person = cloneDeep(createAnywhereUser.value.person)\n body.avatar = cloneDeep(createAnywhereUser.value.avatar)\n // Nobody to copy from, so the account is created switched off, as on an ordinary new-user page.\n body.enabled = false\n const result = await writes.createInSystem(descriptor, body)\n reportOutcome(labelFor(descriptor), result.outcome)\n if (result.outcome !== BulkOutcome.Done) return\n createAnywhereOpen.value = false\n await search(String(id))\n } finally {\n createAnywhereChecking.value = false\n }\n}\n\n/**\n * The chosen target's own profile, not a blanket strict one.\n *\n * The repair dialog is strict on purpose -- it writes to nine places at once -- but this creates\n * one account in one system, and the plan asks for that system's profile. Strict here would demand\n * an avatar colour for a blog account that does not require one.\n */\nconst createAnywhereRequiredMetadata = computed(\n () => props.descriptors.find((item) => item.system === createAnywhereSystem.value)?.requiredMetadata ?? true\n)\n\n/** What is special about having an account in the chosen system -- for blog and forum, a public one. */\nconst createAnywhereNote = computed(\n () => props.descriptors.find((item) => item.system === createAnywhereSystem.value)?.createNote\n)\n\nconst createSystemOptions = computed(() =>\n props.descriptors.filter((item) => item.isEnabled()).map((item) => ({ value: item.system, title: labelFor(item) }))\n)\n\nonBeforeUnmount(cancel)\n\ndefineExpose({\n search: async (value: string) => {\n term.value = value\n await runSearch()\n },\n})\n</script>\n\n<template>\n <div>\n <VCard variant=\"flat\">\n <VCardText>\n <div class=\"d-flex align-center ga-2\">\n <VTextField\n v-model=\"term\"\n :label=\"t('common.userSystem.search.label')\"\n :hint=\"t('common.userSystem.search.hint')\"\n persistent-hint\n density=\"compact\"\n data-cy=\"cross-system-search\"\n @keyup.enter=\"runSearch\"\n />\n <ABtnPrimary\n data-cy=\"cross-system-search-submit\"\n @click.stop=\"runSearch\"\n >\n {{ t('common.userSystem.search.submit') }}\n </ABtnPrimary>\n </div>\n <!--\n The two rounds are named while they run. Between them a row is neither present nor absent,\n and calling it \"no account here\" would invite a Create for an account that exists under\n another address.\n -->\n <div\n v-if=\"store.phase === CrossSystemPhase.MatchingEmail\"\n class=\"text-body-small mt-2\"\n >\n {{ t('common.userSystem.search.phase.matchingEmail') }}\n </div>\n <div\n v-else-if=\"store.phase === CrossSystemPhase.VerifyingById\"\n class=\"text-body-small mt-2\"\n >\n {{ t('common.userSystem.search.phase.verifyingById') }}\n </div>\n </VCardText>\n </VCard>\n\n <VAlert\n v-if=\"store.identityConflict\"\n type=\"error\"\n variant=\"tonal\"\n density=\"compact\"\n class=\"my-3\"\n data-cy=\"identity-conflict\"\n >\n {{ t('common.userSystem.search.identityConflict') }}\n </VAlert>\n\n <!-- Neither an address nor an id. The previous person's rows are gone, and this says why. -->\n <VAlert\n v-if=\"store.termRejected\"\n type=\"warning\"\n variant=\"tonal\"\n density=\"compact\"\n class=\"my-3\"\n data-cy=\"cross-system-term-rejected\"\n >\n {{ t('common.userSystem.search.termRejected') }}\n </VAlert>\n\n <VCard\n v-if=\"searchDone\"\n variant=\"flat\"\n class=\"mt-3\"\n >\n <VCardText class=\"d-flex flex-column ga-3\">\n <div\n v-for=\"descriptor in descriptors\"\n :key=\"descriptor.system\"\n class=\"d-flex align-center flex-wrap ga-2\"\n :data-cy=\"`cross-system-row-${descriptor.system}`\"\n >\n <div\n class=\"font-weight-medium\"\n style=\"min-width: 8rem\"\n >\n {{ labelFor(descriptor) }}\n </div>\n <AUserSystemStatusChip\n v-if=\"store.results.get(descriptor.system)\"\n :axes=\"store.results.get(descriptor.system)!.axes\"\n />\n <VChip\n v-if=\"extraStateFor(descriptor)\"\n :color=\"EXTRA_STATE_COLOR[extraStateFor(descriptor)!.tone]\"\n label\n size=\"small\"\n >\n {{ extraStateFor(descriptor)!.label }}: {{ extraStateFor(descriptor)!.value }}\n </VChip>\n <div class=\"text-body-small text-medium-emphasis\">\n {{ store.results.get(descriptor.system)?.user?.person.fullName }}\n <template v-if=\"store.results.get(descriptor.system)?.user\">\n · {{ store.results.get(descriptor.system)?.user?.email }}\n </template>\n </div>\n <VSpacer />\n <template\n v-if=\"store.results.get(descriptor.system) && isActionable(store.results.get(descriptor.system)!.axes)\"\n >\n <VBtn\n size=\"small\"\n variant=\"text\"\n :loading=\"rowBusy.has(descriptor.system)\"\n :disabled=\"blockedByConflict\"\n :data-cy=\"`cross-system-toggle-${descriptor.system}`\"\n @click=\"toggleOne(descriptor, !store.results.get(descriptor.system)!.user!.enabled)\"\n >\n {{\n store.results.get(descriptor.system)!.user!.enabled\n ? t('common.userSystem.bulk.disable.title')\n : t('common.userSystem.bulk.enable.title')\n }}\n </VBtn>\n <AUserManageButton\n v-if=\"manageTargetFor(descriptor)\"\n :target=\"manageTargetFor(descriptor)!\"\n :system-label=\"labelFor(descriptor)\"\n />\n </template>\n <!--\n Create is offered on a row only when the person exists somewhere -- that is the only way\n their global id is known.\n -->\n <AUserCreateInSystemButton\n v-else-if=\"\n foundAnywhere &&\n store.results.get(descriptor.system) &&\n store.results.get(descriptor.system)!.axes.presence === 'absent'\n \"\n :system-label=\"labelFor(descriptor)\"\n :create-note=\"descriptor.createNote\"\n :will-be-enabled=\"sourceEnabled\"\n :loading=\"rowBusy.has(descriptor.system)\"\n :disabled=\"blockedByConflict\"\n :required-metadata=\"descriptor.requiredMetadata\"\n :user-id=\"store.resolvedId\"\n :source=\"prefillSource\"\n @confirm=\"(user) => createFromSource(descriptor, user)\"\n />\n </div>\n </VCardText>\n </VCard>\n\n <VCard\n v-if=\"differences.length > 0\"\n variant=\"flat\"\n class=\"mt-3\"\n data-cy=\"cross-system-differences\"\n >\n <VCardText>\n <div class=\"text-label-large\">{{ t('common.userSystem.inconsistency.title') }}</div>\n <div\n v-for=\"difference in differences\"\n :key=\"difference.field\"\n class=\"text-body-small\"\n >\n <strong>{{ difference.field }}</strong>\n <span\n v-for=\"[system, systemValue] in difference.values\"\n :key=\"system\"\n class=\"ml-2\"\n >\n {{ system }}: {{ systemValue || '—' }}\n </span>\n </div>\n </VCardText>\n </VCard>\n\n <div\n v-if=\"searchDone && foundAnywhere\"\n class=\"d-flex ga-2 mt-3\"\n >\n <!-- All three open a dialog. None of them acts straight away. -->\n <ABtnTertiary\n :disabled=\"store.bulkRunning || blockedByConflict\"\n data-cy=\"cross-system-bulk-disable\"\n @click=\"openBulk(BulkAction.Disable)\"\n >\n {{ t('common.userSystem.bulk.openDisable') }}\n </ABtnTertiary>\n <ABtnTertiary\n :disabled=\"store.bulkRunning || blockedByConflict\"\n data-cy=\"cross-system-bulk-enable\"\n @click=\"openBulk(BulkAction.Enable)\"\n >\n {{ t('common.userSystem.bulk.openEnable') }}\n </ABtnTertiary>\n <ABtnTertiary\n :disabled=\"store.bulkRunning || blockedByConflict\"\n data-cy=\"cross-system-bulk-metadata\"\n @click=\"repairOpen = true\"\n >\n {{ t('common.userSystem.bulk.openMetadata') }}\n </ABtnTertiary>\n <div\n v-if=\"store.bulkRunning\"\n class=\"text-body-small align-self-center\"\n >\n {{ t('common.userSystem.bulk.runningElsewhere') }}\n </div>\n </div>\n\n <VCard\n v-if=\"searchDone && !foundAnywhere\"\n variant=\"flat\"\n class=\"mt-3\"\n >\n <VCardText>\n <div data-cy=\"cross-system-not-found\">{{ t('common.userSystem.search.notFound') }}</div>\n <ABtnPrimary\n v-if=\"canOfferCreateAnywhere\"\n class=\"mt-2\"\n data-cy=\"cross-system-create-anywhere\"\n @click=\"openCreateAnywhere\"\n >\n {{ t('common.userSystem.search.createAnywhere') }}\n </ABtnPrimary>\n <template v-else>\n <div class=\"text-body-small mt-2\">\n {{ t('common.userSystem.create.notAuthoritative', { systems: unresolvedSystems.join(', ') }) }}\n </div>\n <ABtnTertiary\n class=\"mt-2\"\n data-cy=\"cross-system-retry-search\"\n @click=\"runSearch\"\n >\n {{ t('common.userSystem.button.retry') }}\n </ABtnTertiary>\n </template>\n </VCardText>\n </VCard>\n\n <AUserBulkActionDialog\n v-model:open=\"bulkOpen\"\n :action=\"bulkAction\"\n :descriptors=\"descriptors\"\n @run=\"runBulk\"\n />\n\n <AUserMetadataRepairDialog\n v-model:open=\"repairOpen\"\n :descriptors=\"descriptors\"\n @confirm=\"runRepair\"\n />\n\n <VDialog\n v-model=\"createAnywhereOpen\"\n :max-width=\"720\"\n >\n <VCard>\n <ADialogToolbar @on-cancel=\"createAnywhereOpen = false\">\n {{ t('common.userSystem.search.createAnywhere') }}\n </ADialogToolbar>\n <VCardText>\n <VSelect\n v-model=\"createAnywhereSystem\"\n :items=\"createSystemOptions\"\n :label=\"t('common.userSystem.table.system')\"\n density=\"compact\"\n data-cy=\"create-anywhere-system\"\n />\n <!-- Id and e-mail are both asked for: the e-mail is required by the backend. -->\n <AUserMetadataForm\n v-model:user=\"createAnywhereUser\"\n :required=\"createAnywhereRequiredMetadata\"\n id-input\n />\n <p\n v-if=\"createAnywhereNote\"\n class=\"mt-2\"\n >\n {{ createAnywhereNote }}\n </p>\n <VAlert\n v-if=\"createAnywhereError\"\n type=\"error\"\n variant=\"tonal\"\n density=\"compact\"\n class=\"mt-2\"\n data-cy=\"create-anywhere-error\"\n >\n {{ createAnywhereError }}\n </VAlert>\n <VAlert\n v-if=\"createAnywhereConflict.length > 0\"\n type=\"warning\"\n variant=\"tonal\"\n density=\"compact\"\n class=\"mt-2\"\n data-cy=\"create-anywhere-conflict\"\n >\n {{ t('common.userSystem.create.notAuthoritative', { systems: createAnywhereConflict.join(', ') }) }}\n </VAlert>\n <p class=\"mt-2\">{{ t('common.userSystem.create.note') }}</p>\n <p>{{ t('common.userSystem.create.willBeDisabled') }}</p>\n </VCardText>\n <VCardActions>\n <ABtnTertiary @click.stop=\"createAnywhereOpen = false\">\n {{ t('common.button.cancel') }}\n </ABtnTertiary>\n <VSpacer />\n <ABtnPrimary\n :loading=\"createAnywhereChecking\"\n data-cy=\"create-anywhere-confirm\"\n @click.stop=\"confirmCreateAnywhere\"\n >\n {{ t('common.button.confirm') }}\n </ABtnPrimary>\n </VCardActions>\n </VCard>\n </VDialog>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed, onBeforeUnmount, ref } from 'vue'\nimport useVuelidate from '@vuelidate/core'\nimport { useI18n } from 'vue-i18n'\nimport ADialogToolbar from '@/components/ADialogToolbar.vue'\nimport { useAlerts } from '@/composables/system/alerts'\nimport { useAnzuUserFactory } from '@/model/factory/AnzuUserFactory'\nimport AUserBulkActionDialog from '@/labs/anzuUser/AUserBulkActionDialog.vue'\nimport AUserCreateInSystemButton from '@/labs/anzuUser/AUserCreateInSystemButton.vue'\nimport AUserManageButton from '@/labs/anzuUser/AUserManageButton.vue'\nimport AUserMetadataForm from '@/labs/anzuUser/AUserMetadataForm.vue'\nimport AUserMetadataRepairDialog from '@/labs/anzuUser/AUserMetadataRepairDialog.vue'\nimport AUserSystemStatusChip from '@/labs/anzuUser/AUserSystemStatusChip.vue'\nimport {\n BulkAction,\n BulkOutcome,\n CrossSystemPhase,\n useUserCrossSystemStore,\n type BulkActionType,\n type BulkOutcomeType,\n} from '@/labs/anzuUser/userCrossSystemStore'\nimport { isEmailTerm, useUserCrossSystemSearch } from '@/labs/anzuUser/userCrossSystemSearch'\nimport { useUserCrossSystemWrites } from '@/labs/anzuUser/userCrossSystemWrites'\nimport { findMetadataDifferences, type UserMetadataField } from '@/labs/anzuUser/userMetadataDiff'\nimport type { AnyUserSystemDescriptor } from '@/labs/anzuUser/userSystemDescriptor'\nimport type { UserSystemRefreshHook } from '@/labs/anzuUser/userSystemProbe'\nimport { isActionable } from '@/labs/anzuUser/userSystemState'\nimport type { AnzuUser, BaseUser } from '@/types/AnzuUser'\nimport { cloneDeep, isNull, isUndefined } from '@/utils/common'\n\nconst props = defineProps<{\n /** Order matters twice: the order of the rows, and the priority when pre-filling. */\n descriptors: AnyUserSystemDescriptor[]\n refreshHook?: UserSystemRefreshHook | undefined\n}>()\n\nconst emit = defineEmits<{\n (e: 'searched', term: string): void\n}>()\n\nconst { t } = useI18n()\nconst { showError } = useAlerts()\nconst store = useUserCrossSystemStore()\nconst { createAnzuUser } = useAnzuUserFactory()\nconst writes = useUserCrossSystemWrites()\n\n// A collector for the metadata form inside the create dialog. Without one its rules register\n// against whatever ancestor happens to have an instance, and the dialog's confirm could not ask\n// whether the fields it is about to send are valid.\nconst v$ = useVuelidate()\n\n/* eslint-disable vue/no-setup-props-reactivity-loss */\nconst { search, refreshSystem, unresolvedSystems, cancel } = useUserCrossSystemSearch({\n descriptors: props.descriptors,\n refreshHook: props.refreshHook,\n})\n/* eslint-enable vue/no-setup-props-reactivity-loss */\n\nconst term = ref('')\nconst rowBusy = ref(new Set<string>())\n\nconst runSearch = async () => {\n const value = term.value.trim()\n if (value.length === 0) return\n emit('searched', value)\n await search(value)\n}\n\nconst records = computed(() => {\n const map = new Map<string, BaseUser>()\n for (const descriptor of props.descriptors) {\n const user = store.results.get(descriptor.system)?.user\n if (user) map.set(descriptor.system, user)\n }\n return map\n})\n\nconst differences = computed(() => findMetadataDifferences(records.value))\n\nconst foundAnywhere = computed(() => records.value.size > 0)\nconst searchDone = computed(() => store.phase === CrossSystemPhase.Done)\n\nconst searchedByEmail = computed(() => isEmailTerm(store.searchTerm))\n\n/**\n * When creating an account may be offered after a search that found nobody.\n *\n * By id: only when every relevant probe answered an authoritative 404. A record that could not be\n * read is not a record that does not exist, and no admin can delete an AnzuUser, so a duplicate\n * identity created on a guess cannot be taken back.\n *\n * By e-mail: always, because an address search cannot prove absence at all -- a system holding the\n * same person under a different address answers nothing, which is why the second round exists.\n * What makes it safe is that the dialog asks for the id and runs its own authoritative fan-out on\n * it before writing anything; refusing here would put the flow decision 21 describes out of reach\n * of the search people actually use, since the id is the thing they do not know yet.\n */\nconst canOfferCreateAnywhere = computed(\n () => searchDone.value && !foundAnywhere.value && (searchedByEmail.value || unresolvedSystems.value.length === 0)\n)\n\n/** A conflict is not a state to act from: writes would land on somebody else's account. */\nconst blockedByConflict = computed(() => store.identityConflict !== null)\n\nconst labelFor = (descriptor: AnyUserSystemDescriptor) => descriptor.label ?? descriptor.system\n\nconst manageTargetFor = (descriptor: AnyUserSystemDescriptor) => {\n const user = store.results.get(descriptor.system)?.user\n if (isUndefined(user) || isNull(user) || isUndefined(descriptor.manage)) return null\n return descriptor.manage(user)\n}\n\nconst extraStateFor = (descriptor: AnyUserSystemDescriptor) => {\n const user = store.results.get(descriptor.system)?.user\n if (isUndefined(user) || isNull(user) || isUndefined(descriptor.extraState)) return null\n return descriptor.extraState(user)\n}\n\nconst EXTRA_STATE_COLOR = { ok: 'success', warn: 'warning', error: 'error' } as const\n\n/**\n * A failed row action says so.\n *\n * The bulk dialog has its log; a single row had nothing -- a 403 on Vypnúť left a spinner, an\n * unchanged row and silence, and the api helpers only write to the logger. The per-admin pages this\n * replaces put every write through `showErrorsDefault`.\n */\nconst reportOutcome = (label: string, outcome: BulkOutcomeType) => {\n if (outcome === BulkOutcome.Done) return\n showError(label + ': ' + t('common.userSystem.bulk.outcome.' + outcome))\n}\n\nconst detailFor = (fields: UserMetadataField[] | undefined) =>\n isUndefined(fields) || fields.length === 0\n ? undefined\n : t('common.userSystem.bulk.concurrentChange', { fields: fields.join(', ') })\n\nconst withRowBusy = async (system: string, run: () => Promise<void>) => {\n rowBusy.value = new Set([...rowBusy.value, system])\n try {\n await run()\n } finally {\n const next = new Set(rowBusy.value)\n next.delete(system)\n rowBusy.value = next\n }\n}\n\n/** After every write the touched row is read again -- otherwise it keeps showing the old state. */\nconst toggleOne = async (descriptor: AnyUserSystemDescriptor, enabled: boolean) => {\n const id = store.resolvedId\n if (isNull(id)) return\n await withRowBusy(descriptor.system, async () => {\n const result = await writes.setEnabled(descriptor, id, enabled)\n reportOutcome(labelFor(descriptor), result.outcome)\n await refreshSystem(descriptor.system)\n })\n}\n\nconst createFromSource = async (descriptor: AnyUserSystemDescriptor, metadata: BaseUser) => {\n const id = store.resolvedId\n if (isNull(id)) return\n const sourceSystem = props.descriptors.find((item) => records.value.has(item.system))\n if (isUndefined(sourceSystem)) return\n\n await withRowBusy(descriptor.system, async () => {\n // Read fresh from the server, never from the search results or a form's store: a switch flipped\n // and not saved would otherwise be copied into a system where it means a working public account.\n const source = await writes.readSourceUser(sourceSystem, id)\n if (isNull(source)) {\n showError(t('common.userSystem.create.sourceUnreadable', { system: labelFor(sourceSystem) }))\n return\n }\n // The same promise the panel makes: the dialog has already said whether this account will be\n // switched on, taken from the search result. If the server no longer agrees, somebody changed\n // it in between, and in blog and forum the difference is a working public account.\n if (source.enabled !== sourceEnabled.value) {\n store.setUser(sourceSystem.system, source)\n showError(t('common.userSystem.create.sourceChanged'))\n return\n }\n // Metadata from the dialog, which was validated against the target's profile: the source may\n // leave `person` empty -- blog fills it for nobody -- where the target requires it.\n const body = createAnzuUser(descriptor.system)\n body.id = source.id\n body.email = metadata.email\n body.person = cloneDeep(metadata.person)\n body.avatar = cloneDeep(metadata.avatar)\n // Copying a person into another system copies whether they are switched on. Roles stay empty.\n body.enabled = source.enabled\n const result = await writes.createInSystem(descriptor, body)\n reportOutcome(labelFor(descriptor), result.outcome)\n await refreshSystem(descriptor.system)\n })\n}\n\n/** The record the create form is prefilled from: the first configured system that has one. */\nconst prefillSource = computed(() => {\n const first = props.descriptors.find((item) => records.value.has(item.system))\n return isUndefined(first) ? null : (records.value.get(first.system) ?? null)\n})\n\nconst sourceEnabled = computed(() => {\n const first = props.descriptors.find((item) => records.value.has(item.system))\n if (isUndefined(first)) return false\n return store.results.get(first.system)?.user?.enabled === true\n})\n\n/* ---- bulk ---- */\n\nconst bulkOpen = ref(false)\nconst bulkAction = ref<BulkActionType>(BulkAction.Disable)\nconst repairOpen = ref(false)\n\nconst openBulk = (action: BulkActionType) => {\n bulkAction.value = action\n bulkOpen.value = true\n}\n\nconst runBulk = async (systems: string[]) => {\n const id = store.resolvedId\n if (isNull(id)) return\n const enabled = bulkAction.value === BulkAction.Enable\n store.startBulk(bulkAction.value, systems, id)\n await Promise.allSettled(\n systems.map(async (system) => {\n const descriptor = props.descriptors.find((item) => item.system === system)\n if (isUndefined(descriptor)) return\n store.setBulkEntry(system, BulkOutcome.Running)\n const result = await writes.setEnabled(descriptor, id, enabled)\n store.setBulkEntry(system, result.outcome)\n await refreshSystem(system)\n })\n )\n store.finishBulk()\n}\n\nconst runRepair = async (payload: { target: BaseUser; systems: string[] }) => {\n const id = store.resolvedId\n if (isNull(id)) return\n store.startBulk(BulkAction.Metadata, payload.systems, id)\n // What the search showed is captured before anything is written, so the comparison is against the\n // values the operator was actually looking at rather than against a row a sibling write refreshed.\n const seenBefore = new Map(records.value)\n await Promise.allSettled(\n payload.systems.map(async (system) => {\n const descriptor = props.descriptors.find((item) => item.system === system)\n if (isUndefined(descriptor)) return\n store.setBulkEntry(system, BulkOutcome.Running)\n const result = await writes.writeMetadata(\n descriptor,\n id,\n payload.target,\n undefined,\n seenBefore.get(system) ?? null\n )\n store.setBulkEntry(system, result.outcome, detailFor(result.concurrentFields))\n await refreshSystem(system)\n })\n )\n store.finishBulk()\n}\n\n/* ---- create where nobody was found ---- */\n\nconst createAnywhereOpen = ref(false)\nconst createAnywhereSystem = ref<string | null>(null)\nconst createAnywhereUser = ref<AnzuUser>(createAnzuUser())\nconst createAnywhereChecking = ref(false)\nconst createAnywhereConflict = ref<string[]>([])\nconst createAnywhereError = ref<string | null>(null)\n\nconst openCreateAnywhere = () => {\n createAnywhereSystem.value = props.descriptors[0]?.system ?? null\n createAnywhereUser.value = createAnzuUser(createAnywhereSystem.value ?? '')\n createAnywhereConflict.value = []\n createAnywhereError.value = null\n createAnywhereOpen.value = true\n}\n\n/**\n * Before creating, the typed id is looked up across the systems.\n *\n * cms, dam and contentHub skip the SSO lookup for a numeric id, and the validators check the id and\n * the e-mail separately rather than as a pair -- so a typo creates an account with one person's id\n * and another's e-mail, and every later fan-out then follows the wrong identity.\n *\n * The check has to be authoritative too: if a probe answers 401, 403 or nothing, creating is not\n * allowed, because whether that id belongs to somebody is unknown.\n */\nconst confirmCreateAnywhere = async () => {\n const id = createAnywhereUser.value.id\n const system = createAnywhereSystem.value\n createAnywhereConflict.value = []\n createAnywhereError.value = null\n if (isNull(system)) return\n // The id is checked here and not only by the form, because everything below depends on the\n // fan-out having run: a term the search refuses probes nothing, and the empty result that leaves\n // behind would otherwise read as \"this id belongs to nobody\".\n if (isNull(id) || isUndefined(id) || !Number.isInteger(id) || id <= 0) {\n createAnywhereError.value = t('common.userSystem.create.invalidId')\n return\n }\n if (!(await v$.value.$validate())) return\n createAnywhereChecking.value = true\n try {\n if (!(await search(String(id)))) {\n createAnywhereError.value = t('common.userSystem.create.invalidId')\n return\n }\n if (records.value.size > 0) {\n createAnywhereConflict.value = [...records.value.keys()]\n return\n }\n if (unresolvedSystems.value.length > 0) {\n createAnywhereConflict.value = unresolvedSystems.value\n return\n }\n const descriptor = props.descriptors.find((item) => item.system === system)\n if (isUndefined(descriptor)) return\n const body = createAnzuUser(system)\n body.id = id\n body.email = createAnywhereUser.value.email\n body.person = cloneDeep(createAnywhereUser.value.person)\n body.avatar = cloneDeep(createAnywhereUser.value.avatar)\n // Nobody to copy from, so the account is created switched off, as on an ordinary new-user page.\n body.enabled = false\n const result = await writes.createInSystem(descriptor, body)\n reportOutcome(labelFor(descriptor), result.outcome)\n if (result.outcome !== BulkOutcome.Done) return\n createAnywhereOpen.value = false\n await search(String(id))\n } finally {\n createAnywhereChecking.value = false\n }\n}\n\n/**\n * The chosen target's own profile, not a blanket strict one.\n *\n * The repair dialog is strict on purpose -- it writes to nine places at once -- but this creates\n * one account in one system, and the plan asks for that system's profile. Strict here would demand\n * an avatar colour for a blog account that does not require one.\n */\nconst createAnywhereRequiredMetadata = computed(\n () => props.descriptors.find((item) => item.system === createAnywhereSystem.value)?.requiredMetadata ?? true\n)\n\n/** What is special about having an account in the chosen system -- for blog and forum, a public one. */\nconst createAnywhereNote = computed(\n () => props.descriptors.find((item) => item.system === createAnywhereSystem.value)?.createNote\n)\n\nconst createSystemOptions = computed(() =>\n props.descriptors.filter((item) => item.isEnabled()).map((item) => ({ value: item.system, title: labelFor(item) }))\n)\n\nonBeforeUnmount(cancel)\n\ndefineExpose({\n search: async (value: string) => {\n term.value = value\n await runSearch()\n },\n})\n</script>\n\n<template>\n <div>\n <VCard variant=\"flat\">\n <VCardText>\n <div class=\"d-flex align-center ga-2\">\n <VTextField\n v-model=\"term\"\n :label=\"t('common.userSystem.search.label')\"\n :hint=\"t('common.userSystem.search.hint')\"\n persistent-hint\n density=\"compact\"\n data-cy=\"cross-system-search\"\n @keyup.enter=\"runSearch\"\n />\n <ABtnPrimary\n data-cy=\"cross-system-search-submit\"\n @click.stop=\"runSearch\"\n >\n {{ t('common.userSystem.search.submit') }}\n </ABtnPrimary>\n </div>\n <!--\n The two rounds are named while they run. Between them a row is neither present nor absent,\n and calling it \"no account here\" would invite a Create for an account that exists under\n another address.\n -->\n <div\n v-if=\"store.phase === CrossSystemPhase.MatchingEmail\"\n class=\"text-body-small mt-2\"\n >\n {{ t('common.userSystem.search.phase.matchingEmail') }}\n </div>\n <div\n v-else-if=\"store.phase === CrossSystemPhase.VerifyingById\"\n class=\"text-body-small mt-2\"\n >\n {{ t('common.userSystem.search.phase.verifyingById') }}\n </div>\n </VCardText>\n </VCard>\n\n <VAlert\n v-if=\"store.identityConflict\"\n type=\"error\"\n variant=\"tonal\"\n density=\"compact\"\n class=\"my-3\"\n data-cy=\"identity-conflict\"\n >\n {{ t('common.userSystem.search.identityConflict') }}\n </VAlert>\n\n <!-- Neither an address nor an id. The previous person's rows are gone, and this says why. -->\n <VAlert\n v-if=\"store.termRejected\"\n type=\"warning\"\n variant=\"tonal\"\n density=\"compact\"\n class=\"my-3\"\n data-cy=\"cross-system-term-rejected\"\n >\n {{ t('common.userSystem.search.termRejected') }}\n </VAlert>\n\n <VCard\n v-if=\"searchDone\"\n variant=\"flat\"\n class=\"mt-3\"\n >\n <VCardText class=\"d-flex flex-column ga-3\">\n <div\n v-for=\"descriptor in descriptors\"\n :key=\"descriptor.system\"\n class=\"d-flex align-center flex-wrap ga-2\"\n :data-cy=\"`cross-system-row-${descriptor.system}`\"\n >\n <div\n class=\"font-weight-medium\"\n style=\"min-width: 8rem\"\n >\n {{ labelFor(descriptor) }}\n </div>\n <AUserSystemStatusChip\n v-if=\"store.results.get(descriptor.system)\"\n :axes=\"store.results.get(descriptor.system)!.axes\"\n />\n <VChip\n v-if=\"extraStateFor(descriptor)\"\n :color=\"EXTRA_STATE_COLOR[extraStateFor(descriptor)!.tone]\"\n label\n size=\"small\"\n >\n {{ extraStateFor(descriptor)!.label }}: {{ extraStateFor(descriptor)!.value }}\n </VChip>\n <div class=\"text-body-small text-medium-emphasis\">\n {{ store.results.get(descriptor.system)?.user?.person.fullName }}\n <template v-if=\"store.results.get(descriptor.system)?.user\">\n · {{ store.results.get(descriptor.system)?.user?.email }}\n </template>\n </div>\n <VSpacer />\n <template\n v-if=\"store.results.get(descriptor.system) && isActionable(store.results.get(descriptor.system)!.axes)\"\n >\n <VBtn\n size=\"small\"\n variant=\"text\"\n :loading=\"rowBusy.has(descriptor.system)\"\n :disabled=\"blockedByConflict\"\n :data-cy=\"`cross-system-toggle-${descriptor.system}`\"\n @click=\"toggleOne(descriptor, !store.results.get(descriptor.system)!.user!.enabled)\"\n >\n {{\n store.results.get(descriptor.system)!.user!.enabled\n ? t('common.userSystem.bulk.disable.title')\n : t('common.userSystem.bulk.enable.title')\n }}\n </VBtn>\n <AUserManageButton\n v-if=\"manageTargetFor(descriptor)\"\n :target=\"manageTargetFor(descriptor)!\"\n :system-label=\"labelFor(descriptor)\"\n />\n </template>\n <!--\n Create is offered on a row only when the person exists somewhere -- that is the only way\n their global id is known.\n -->\n <AUserCreateInSystemButton\n v-else-if=\"\n foundAnywhere &&\n store.results.get(descriptor.system) &&\n store.results.get(descriptor.system)!.axes.presence === 'absent'\n \"\n :system-label=\"labelFor(descriptor)\"\n :create-note=\"descriptor.createNote\"\n :will-be-enabled=\"sourceEnabled\"\n :loading=\"rowBusy.has(descriptor.system)\"\n :disabled=\"blockedByConflict\"\n :required-metadata=\"descriptor.requiredMetadata\"\n :user-id=\"store.resolvedId\"\n :source=\"prefillSource\"\n @confirm=\"(user) => createFromSource(descriptor, user)\"\n />\n </div>\n </VCardText>\n </VCard>\n\n <VCard\n v-if=\"differences.length > 0\"\n variant=\"flat\"\n class=\"mt-3\"\n data-cy=\"cross-system-differences\"\n >\n <VCardText>\n <div class=\"text-label-large\">{{ t('common.userSystem.inconsistency.title') }}</div>\n <div\n v-for=\"difference in differences\"\n :key=\"difference.field\"\n class=\"text-body-small\"\n >\n <strong>{{ difference.field }}</strong>\n <span\n v-for=\"[system, systemValue] in difference.values\"\n :key=\"system\"\n class=\"ml-2\"\n >\n {{ system }}: {{ systemValue || '—' }}\n </span>\n </div>\n </VCardText>\n </VCard>\n\n <div\n v-if=\"searchDone && foundAnywhere\"\n class=\"d-flex ga-2 mt-3\"\n >\n <!-- All three open a dialog. None of them acts straight away. -->\n <ABtnTertiary\n :disabled=\"store.bulkRunning || blockedByConflict\"\n data-cy=\"cross-system-bulk-disable\"\n @click=\"openBulk(BulkAction.Disable)\"\n >\n {{ t('common.userSystem.bulk.openDisable') }}\n </ABtnTertiary>\n <ABtnTertiary\n :disabled=\"store.bulkRunning || blockedByConflict\"\n data-cy=\"cross-system-bulk-enable\"\n @click=\"openBulk(BulkAction.Enable)\"\n >\n {{ t('common.userSystem.bulk.openEnable') }}\n </ABtnTertiary>\n <ABtnTertiary\n :disabled=\"store.bulkRunning || blockedByConflict\"\n data-cy=\"cross-system-bulk-metadata\"\n @click=\"repairOpen = true\"\n >\n {{ t('common.userSystem.bulk.openMetadata') }}\n </ABtnTertiary>\n <div\n v-if=\"store.bulkRunning\"\n class=\"text-body-small align-self-center\"\n >\n {{ t('common.userSystem.bulk.runningElsewhere') }}\n </div>\n </div>\n\n <VCard\n v-if=\"searchDone && !foundAnywhere\"\n variant=\"flat\"\n class=\"mt-3\"\n >\n <VCardText>\n <div data-cy=\"cross-system-not-found\">{{ t('common.userSystem.search.notFound') }}</div>\n <ABtnPrimary\n v-if=\"canOfferCreateAnywhere\"\n class=\"mt-2\"\n data-cy=\"cross-system-create-anywhere\"\n @click=\"openCreateAnywhere\"\n >\n {{ t('common.userSystem.search.createAnywhere') }}\n </ABtnPrimary>\n <template v-else>\n <div class=\"text-body-small mt-2\">\n {{ t('common.userSystem.create.notAuthoritative', { systems: unresolvedSystems.join(', ') }) }}\n </div>\n <ABtnTertiary\n class=\"mt-2\"\n data-cy=\"cross-system-retry-search\"\n @click=\"runSearch\"\n >\n {{ t('common.userSystem.button.retry') }}\n </ABtnTertiary>\n </template>\n </VCardText>\n </VCard>\n\n <AUserBulkActionDialog\n v-model:open=\"bulkOpen\"\n :action=\"bulkAction\"\n :descriptors=\"descriptors\"\n @run=\"runBulk\"\n />\n\n <AUserMetadataRepairDialog\n v-model:open=\"repairOpen\"\n :descriptors=\"descriptors\"\n @confirm=\"runRepair\"\n />\n\n <VDialog\n v-model=\"createAnywhereOpen\"\n :max-width=\"720\"\n >\n <VCard>\n <ADialogToolbar @on-cancel=\"createAnywhereOpen = false\">\n {{ t('common.userSystem.search.createAnywhere') }}\n </ADialogToolbar>\n <VCardText>\n <VSelect\n v-model=\"createAnywhereSystem\"\n :items=\"createSystemOptions\"\n :label=\"t('common.userSystem.table.system')\"\n density=\"compact\"\n data-cy=\"create-anywhere-system\"\n />\n <!-- Id and e-mail are both asked for: the e-mail is required by the backend. -->\n <AUserMetadataForm\n v-model:user=\"createAnywhereUser\"\n :required=\"createAnywhereRequiredMetadata\"\n id-input\n />\n <p\n v-if=\"createAnywhereNote\"\n class=\"mt-2\"\n >\n {{ createAnywhereNote }}\n </p>\n <VAlert\n v-if=\"createAnywhereError\"\n type=\"error\"\n variant=\"tonal\"\n density=\"compact\"\n class=\"mt-2\"\n data-cy=\"create-anywhere-error\"\n >\n {{ createAnywhereError }}\n </VAlert>\n <VAlert\n v-if=\"createAnywhereConflict.length > 0\"\n type=\"warning\"\n variant=\"tonal\"\n density=\"compact\"\n class=\"mt-2\"\n data-cy=\"create-anywhere-conflict\"\n >\n {{ t('common.userSystem.create.notAuthoritative', { systems: createAnywhereConflict.join(', ') }) }}\n </VAlert>\n <p class=\"mt-2\">{{ t('common.userSystem.create.note') }}</p>\n <p>{{ t('common.userSystem.create.willBeDisabled') }}</p>\n </VCardText>\n <VCardActions>\n <ABtnTertiary @click.stop=\"createAnywhereOpen = false\">\n {{ t('common.button.cancel') }}\n </ABtnTertiary>\n <VSpacer />\n <ABtnPrimary\n :loading=\"createAnywhereChecking\"\n data-cy=\"create-anywhere-confirm\"\n @click.stop=\"confirmCreateAnywhere\"\n >\n {{ t('common.button.confirm') }}\n </ABtnPrimary>\n </VCardActions>\n </VCard>\n </VDialog>\n </div>\n</template>\n"],"mappings":"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"}
|