@conduction/nextcloud-vue 2.133.0 → 2.134.0
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/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +85 -10
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +60 -36
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidget.vue.js +1 -1
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidget.vue2.js +10 -0
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js +202 -6
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js +137 -55
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js.map +1 -1
- package/dist/esm/dialogs/CnPartyAddDialog.vue.js +9 -0
- package/dist/esm/dialogs/CnPartyAddDialog.vue.js.map +1 -0
- package/dist/esm/dialogs/CnPartyAddDialog.vue2.js +184 -0
- package/dist/esm/dialogs/CnPartyAddDialog.vue2.js.map +1 -0
- package/dist/esm/dialogs/CnPartyAddDialog.vue3.js +168 -0
- package/dist/esm/dialogs/CnPartyAddDialog.vue3.js.map +1 -0
- package/dist/esm/l10n/en.json.js +11 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +11 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +95 -70
- package/dist/esm/utils/objectParties.js +79 -1
- package/dist/esm/utils/objectParties.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +5683 -4860
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +95 -70
- package/l10n/en.json +11 -1
- package/l10n/nl.json +11 -1
- package/package.json +1 -1
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +105 -12
- package/src/components/CnIntegrationWidget/CnIntegrationWidget.vue +10 -0
- package/src/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue +264 -3
- package/src/dialogs/CnPartyAddDialog.vue +309 -0
- package/src/utils/objectParties.js +78 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFieldListWidget.vue3.js","sources":["../../../../src/components/CnFieldListWidget/CnFieldListWidget.vue?vue&type=template&id=6df666da&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-field-list-widget\" data-testid=\"cn-field-list-widget\">\n\t\t<div v-if=\"canAdd && addTrigger && addPlacement === 'header'\" class=\"cn-field-list-widget__toolbar\">\n\t\t\t<NcButton\n\t\t\t\tvariant=\"secondary\"\n\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\tdata-testid=\"cn-field-list-open-add\"\n\t\t\t\t@click=\"adding = !adding\">\n\t\t\t\t+ {{ addLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<form\n\t\t\tv-if=\"canAdd && addTrigger && addAtTop && adding\"\n\t\t\tclass=\"cn-field-list-widget__add\"\n\t\t\tdata-testid=\"cn-field-list-add\"\n\t\t\t@submit.prevent=\"add\">\n\t\t\t<div\n\t\t\t\tv-for=\"field in addFields\"\n\t\t\t\t:key=\"field.key\"\n\t\t\t\tclass=\"cn-field-list-widget__field\"\n\t\t\t\t:data-testid=\"`cn-field-list-input-${field.key}`\">\n\t\t\t\t<NcSelect\n\t\t\t\t\tv-if=\"field.type === 'select'\"\n\t\t\t\t\tv-model=\"draft[field.key]\"\n\t\t\t\t\t:inputLabel=\"field.label\"\n\t\t\t\t\t:options=\"field.options\"\n\t\t\t\t\t:reduce=\"(option) => option.value\"\n\t\t\t\t\t:clearable=\"false\"\n\t\t\t\t\tlabel=\"label\" />\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-else-if=\"field.type === 'checkbox'\"\n\t\t\t\t\tv-model=\"draft[field.key]\">\n\t\t\t\t\t{{ field.label }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else\n\t\t\t\t\tv-model=\"draft[field.key]\"\n\t\t\t\t\t:label=\"field.label\"\n\t\t\t\t\t:error=\"Boolean(errors[field.key])\"\n\t\t\t\t\t:helperText=\"errors[field.key] || ''\" />\n\t\t\t</div>\n\t\t\t<NcButton type=\"submit\"\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\tdata-testid=\"cn-field-list-submit\">\n\t\t\t\t{{ editingKey !== null ? saveLabel : addLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"editingKey !== null\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\tdata-testid=\"cn-field-list-cancel-edit\"\n\t\t\t\t@click=\"cancelEdit\">\n\t\t\t\t{{ tr('Cancel') }}\n\t\t\t</NcButton>\n\t\t</form>\n\t\t<p v-if=\"lead\" class=\"cn-field-list-widget__lead\" data-testid=\"cn-field-list-lead\">\n\t\t\t{{ lead }}\n\t\t</p>\n\t\t<p v-if=\"rows.length === 0\" class=\"cn-field-list-widget__empty\" data-testid=\"cn-field-list-empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t\t<div v-else-if=\"isTable\" class=\"cn-field-list-widget__table-wrap\">\n\t\t\t<table class=\"cn-field-list-widget__table\" data-testid=\"cn-field-list-table\">\n\t\t\t\t<thead>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<th\n\t\t\t\t\t\t\tv-for=\"column in tableColumns\"\n\t\t\t\t\t\t\t:key=\"column.key\"\n\t\t\t\t\t\t\tscope=\"col\"\n\t\t\t\t\t\t\tclass=\"cn-field-list-widget__th\">\n\t\t\t\t\t\t\t{{ column.header }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<th v-if=\"hasMenu\" scope=\"col\" class=\"cn-field-list-widget__th cn-field-list-widget__th--menu\">\n\t\t\t\t\t\t\t<span class=\"cn-field-list-widget__sr\">{{ tr('Actions') }}</span>\n\t\t\t\t\t\t</th>\n\t\t\t\t\t</tr>\n\t\t\t\t</thead>\n\t\t\t\t<tbody>\n\t\t\t\t\t<tr\n\t\t\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t\t\t:key=\"row.key\"\n\t\t\t\t\t\tclass=\"cn-field-list-widget__tr\"\n\t\t\t\t\t\t:data-testid=\"`cn-field-list-row-${row.key}`\">\n\t\t\t\t\t\t<td\n\t\t\t\t\t\t\tv-for=\"(column, index) in tableColumns\"\n\t\t\t\t\t\t\t:key=\"column.key\"\n\t\t\t\t\t\t\tclass=\"cn-field-list-widget__td\">\n\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\tv-if=\"column.pill && row.cells[index].pill\"\n\t\t\t\t\t\t\t\t:label=\"row.cells[index].pill.label\"\n\t\t\t\t\t\t\t\t:variant=\"row.cells[index].pill.variant\"\n\t\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t\t<strong v-else-if=\"column.strong\">{{ row.cells[index].text }}</strong>\n\t\t\t\t\t\t\t<code v-else-if=\"column.code\" class=\"cn-field-list-widget__code\">{{ row.cells[index].text }}</code>\n\t\t\t\t\t\t\t<template v-else>\n\t\t\t\t\t\t\t\t{{ row.cells[index].text }}\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t\t<td v-if=\"hasMenu\" class=\"cn-field-list-widget__td cn-field-list-widget__td--menu\">\n\t\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\t\tv-for=\"action in inlineActions(row)\"\n\t\t\t\t\t\t\t\t:key=\"action.id\"\n\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\t\t\t\t\t:aria-label=\"action.ariaLabel || undefined\"\n\t\t\t\t\t\t\t\t:data-testid=\"`cn-field-list-action-${action.id}-${row.key}`\"\n\t\t\t\t\t\t\t\t@click=\"runAction(action, row)\">\n\t\t\t\t\t\t\t\t{{ action.label }}\n\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t<NcActions\n\t\t\t\t\t\t\t\tv-if=\"menuItems(row).length > 0\"\n\t\t\t\t\t\t\t\t:forceMenu=\"true\"\n\t\t\t\t\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\t\t\t\t\t:ariaLabel=\"`${tr('More actions')}: ${row.title}`\"\n\t\t\t\t\t\t\t\t:data-testid=\"`cn-field-list-menu-${row.key}`\">\n\t\t\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\t\t\tv-for=\"item in menuItems(row)\"\n\t\t\t\t\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\t\t\t\t\t:data-testid=\"`cn-field-list-menu-${item.id}-${row.key}`\"\n\t\t\t\t\t\t\t\t\t@click=\"item.run\">\n\t\t\t\t\t\t\t\t\t{{ item.label }}\n\t\t\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t\t</NcActions>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t\t<div\n\t\t\t\tv-for=\"row in bandRows\"\n\t\t\t\t:key=\"row.key\"\n\t\t\t\tclass=\"cn-field-list-widget__band\"\n\t\t\t\trole=\"note\"\n\t\t\t\t:data-testid=\"`cn-field-list-band-${row.key}`\">\n\t\t\t\t<span class=\"cn-field-list-widget__band-text\">{{ row.detail }}</span>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-for=\"copy in row.copies\"\n\t\t\t\t\t:key=\"`copy-${copy.path}`\"\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:data-testid=\"`cn-field-list-copy-${copy.path}-${row.key}`\"\n\t\t\t\t\t@click=\"copyValue(copy)\">\n\t\t\t\t\t{{ copy.label }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-for=\"action in row.bandActions\"\n\t\t\t\t\t:key=\"action.id\"\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\t\t:data-testid=\"`cn-field-list-band-action-${action.id}-${row.key}`\"\n\t\t\t\t\t@click=\"runAction(action, row)\">\n\t\t\t\t\t{{ action.label }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\t\t<ul v-else class=\"cn-field-list-widget__rows\">\n\t\t\t<li\n\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t:key=\"row.key\"\n\t\t\t\tclass=\"cn-field-list-widget__row\"\n\t\t\t\t:data-testid=\"`cn-field-list-row-${row.key}`\">\n\t\t\t\t<div class=\"cn-field-list-widget__line\">\n\t\t\t\t\t<span v-if=\"row.initials\" class=\"cn-field-list-widget__avatar\" aria-hidden=\"true\">{{ row.initials }}</span>\n\t\t\t\t\t<span class=\"cn-field-list-widget__main\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\tv-if=\"row.href\"\n\t\t\t\t\t\t\tclass=\"cn-field-list-widget__title\"\n\t\t\t\t\t\t\t:href=\"row.href\"\n\t\t\t\t\t\t\tv-bind=\"row.external ? { target: '_blank', rel: 'noopener noreferrer' } : {}\">{{ row.title }}</a>\n\t\t\t\t\t\t<span v-else class=\"cn-field-list-widget__title\">{{ row.title }}</span>\n\t\t\t\t\t\t<span v-if=\"row.meta\" class=\"cn-field-list-widget__meta\">{{ row.meta }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\tv-if=\"row.pill\"\n\t\t\t\t\t\t:label=\"row.pill.label\"\n\t\t\t\t\t\t:variant=\"row.pill.variant\"\n\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t<span v-if=\"row.aside\" class=\"cn-field-list-widget__aside\">{{ row.aside }}</span>\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-for=\"action in row.actions\"\n\t\t\t\t\t\t:key=\"action.id\"\n\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\t\t\t:data-testid=\"`cn-field-list-action-${action.id}-${row.key}`\"\n\t\t\t\t\t\t@click=\"runAction(action, row)\">\n\t\t\t\t\t\t{{ action.label }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-if=\"canEdit\"\n\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\t\t\t:aria-label=\"`${editLabel}: ${row.title}`\"\n\t\t\t\t\t\t:data-testid=\"`cn-field-list-edit-${row.index}`\"\n\t\t\t\t\t\t@click=\"startEdit(row)\">\n\t\t\t\t\t\t{{ editLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-if=\"canRemove\"\n\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\t\t\t:aria-label=\"removeAriaLabel(row)\"\n\t\t\t\t\t\t:data-testid=\"`cn-field-list-remove-${row.key}`\"\n\t\t\t\t\t\t@click=\"remove(row)\">\n\t\t\t\t\t\t{{ removeLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t\t<p v-if=\"row.detail\" class=\"cn-field-list-widget__detail\">\n\t\t\t\t\t{{ row.detail }}\n\t\t\t\t</p>\n\t\t\t\t<div v-if=\"row.detail && row.copies.length > 0\" class=\"cn-field-list-widget__copies\">\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-for=\"copy in row.copies\"\n\t\t\t\t\t\t:key=\"`copy-${copy.path}`\"\n\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t:data-testid=\"`cn-field-list-copy-${copy.path}-${row.key}`\"\n\t\t\t\t\t\t@click=\"copyValue(copy)\">\n\t\t\t\t\t\t{{ copy.label }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t</li>\n\t\t</ul>\n\n\t\t<p v-if=\"hint\" class=\"cn-field-list-widget__hint\">\n\t\t\t{{ hint }}\n\t\t</p>\n\n\t\t<div v-if=\"canAdd && addTrigger && addPlacement === 'footer' && !adding\" class=\"cn-field-list-widget__toolbar\">\n\t\t\t<NcButton\n\t\t\t\tvariant=\"secondary\"\n\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\tdata-testid=\"cn-field-list-open-add\"\n\t\t\t\t@click=\"adding = true\">\n\t\t\t\t+ {{ addLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<form\n\t\t\tv-if=\"canAdd && !(addTrigger && addAtTop) && (!addTrigger || adding)\"\n\t\t\tclass=\"cn-field-list-widget__add\"\n\t\t\tdata-testid=\"cn-field-list-add\"\n\t\t\t@submit.prevent=\"add\">\n\t\t\t<div\n\t\t\t\tv-for=\"field in addFields\"\n\t\t\t\t:key=\"field.key\"\n\t\t\t\tclass=\"cn-field-list-widget__field\"\n\t\t\t\t:data-testid=\"`cn-field-list-input-${field.key}`\">\n\t\t\t\t<NcSelect\n\t\t\t\t\tv-if=\"field.type === 'select'\"\n\t\t\t\t\tv-model=\"draft[field.key]\"\n\t\t\t\t\t:inputLabel=\"field.label\"\n\t\t\t\t\t:options=\"field.options\"\n\t\t\t\t\t:reduce=\"(option) => option.value\"\n\t\t\t\t\t:clearable=\"false\"\n\t\t\t\t\tlabel=\"label\" />\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-else-if=\"field.type === 'checkbox'\"\n\t\t\t\t\tv-model=\"draft[field.key]\">\n\t\t\t\t\t{{ field.label }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else\n\t\t\t\t\tv-model=\"draft[field.key]\"\n\t\t\t\t\t:label=\"field.label\"\n\t\t\t\t\t:error=\"Boolean(errors[field.key])\"\n\t\t\t\t\t:helperText=\"errors[field.key] || ''\" />\n\t\t\t</div>\n\t\t\t<NcButton type=\"submit\"\n\t\t\t\tvariant=\"secondary\"\n\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\tdata-testid=\"cn-field-list-submit\">\n\t\t\t\t{{ editingKey !== null ? saveLabel : addLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"editingKey !== null\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\tdata-testid=\"cn-field-list-cancel-edit\"\n\t\t\t\t@click=\"cancelEdit\">\n\t\t\t\t{{ tr('Cancel') }}\n\t\t\t</NcButton>\n\t\t</form>\n\n\t\t<span class=\"cn-field-list-widget__sr\" aria-live=\"polite\" data-testid=\"cn-field-list-copied\">{{ copied }}</span>\n\t\t<NcNoteCard v-if=\"notice\" :type=\"noticeType\" data-testid=\"cn-field-list-notice\">\n\t\t\t{{ notice }}\n\t\t</NcNoteCard>\n\t</div>\n</template>\n\n<script>\nimport { NcActionButton, NcActions, NcButton, NcCheckboxRadioSwitch, NcNoteCard, NcSelect, NcTextField } from '@nextcloud/vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport { isBadgeVariant } from '../../utils/badgeVariants.js'\nimport { formatBoardDate } from '../../utils/boardDate.js'\nimport { parseDateValue } from '../../utils/dateVariant.js'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { serverErrorMessage } from '../../utils/serverErrorMessage.js'\n\n/**\n * CnFieldListWidget — the `field-list` widget: a list stored INSIDE one field\n * of the current object (a portal's `domains[]`, its `editors[]`), shown as\n * rows and edited in place.\n *\n * Rows are drawn from configuration (title, meta line, pill, aside, a detail\n * line); a small form adds an entry, a button removes one, and each change is\n * saved as ONE PATCH of that field on the object, after re-reading the stored\n * value so a concurrent change to the same list is not overwritten with a\n * stale copy. Optional row actions call a configured endpoint (a \"check\n * again\"), and an optional `rowSource` merges live per-row state read from an\n * endpoint by key without writing it back.\n *\n * The server decides who may write: a refused PATCH shows the server's words\n * and leaves the list as stored.\n *\n * @spec openspec/changes/abstract-field-list/specs/field-list/spec.md\n */\n/**\n * A link a row title may carry: http(s) or root-relative, '' otherwise.\n *\n * @param {unknown} value The candidate.\n * @return {string} The link.\n */\nfunction safeRowHref(value) {\n\tconst text = typeof value === 'string' ? value.trim() : ''\n\treturn /^(\\/(?!\\/)|https?:\\/\\/)/i.test(text) ? text : ''\n}\n\n/**\n * The link of a row title: a path to a safe link, or `{ path, scheme }` for\n * a `mailto:` or `tel:` link to the value.\n *\n * @param {object} entry The row entry.\n * @param {string|{path: string, scheme: string}} spec The `row.href` config.\n * @return {string} The link, '' for none.\n */\nfunction rowHref(entry, spec) {\n\tif (spec && typeof spec === 'object') {\n\t\tconst value = readPath(entry, spec.path)\n\t\tconst text = typeof value === 'string' ? value.trim() : ''\n\t\tif (text === '' || !['mailto', 'tel'].includes(spec.scheme)) {\n\t\t\treturn ''\n\t\t}\n\t\treturn `${spec.scheme}:${spec.scheme === 'tel' ? text.replace(/[^\\d+]/g, '') : encodeURIComponent(text).replace(/%40/g, '@')}`\n\t}\n\treturn safeRowHref(readPath(entry, spec))\n}\n\nexport default {\n\tname: 'CnFieldListWidget',\n\n\tcomponents: { CnStatusBadge, NcActionButton, NcActions, NcButton, NcCheckboxRadioSwitch, NcNoteCard, NcSelect, NcTextField },\n\n\tinject: {\n\t\tcnTranslate: { default: () => (key) => key },\n\t\t/** Opens a registered modal (CnAppRoot), for a row action with `dialog`. */\n\t\tcnOpenModal: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget configuration.\n\t\t *\n\t\t * - `field` (required) — the object field holding the array.\n\t\t * - `key` — the item field that identifies an entry (default: the item itself for strings).\n\t\t * - `row` — `{ title, titleFallback?, avatar?, meta?: [paths|{ path, map?, format? }], pill?: { path, map?, variantMap? }, aside?: { path, format?, prefix?, fallback? }, detail?: { template, when?, copy?: [{ path, label }] } }`. `detail.copy` puts a copy button beside the detail line for each named value of the row (an empty value gets none), such as a DNS record's name and value.\n\t\t * - `add` — `{ label?, fields: [{ key, label, type?: 'text'|'select', options?, required?, pattern?, patternMessage? }], unique?, defaults? }`.\n\t\t * - `remove` — `{ label? }`; omitted = no remove.\n\t\t * - `rowActions` — `[{ id, label, ariaLabel?, when?, placement?: 'band'|'inline', request: { method?, url, payload? } }]`, or `{ id, label, dialog, props }` to open a modal registered in the app registry with filled props (shown in `readOnly` too, since it writes nothing, and only when every token in `props` fills); tokens `@object.<field>`, `@objectId`, `@row.<field>`, `@index`. In the table variant `placement: 'inline'` draws the action as a button in the row instead of in the \"...\" menu; `ariaLabel` is a template filled from the row.\n\t\t * - `when` (row actions, `detail`) — `{ field: value }`, every field equal; or per field `{ eq?, neq?, set? }`, where a value `@row.<field>` (or `@<field>`) is another field of the same row: `{ deliveredRef: { set: true, neq: '@originalRef' } }`.\n\t\t * - `rowSource` — `{ url, path?, key }`: live per-row state merged by key.\n\t\t * - `variant: 'table'` + `columns` — `[{ header, path, map?, format?, strong?, pill?, variantMap? }]`: rows as a table; a per-row \"...\" menu holds the row actions and remove; `detail` lines become a warning band under the table, with the row actions that have `placement: 'band'`.\n\t\t * - `add.trigger: 'button'`, `add.placement: 'header' | 'title' | 'footer'` — the add form opens from a button; `title` reports the button for the card's title row on the `header` event.\n\t\t * - `readOnly`, `hint`, `emptyText`.\n\t\t * - `row.href` — the path of a link for the row title; only http(s) and root-relative links, an absolute one opens in a new tab.\n\t\t * - `lead` — a line above the rows, translated, with `{path}` filled from the object.\n\t\t * - `edit` — `true` or `{ label?, saveLabel? }`: an Edit button per row opens the form prefilled and saves the entry in place.\n\t\t * - `add.fields[].type: 'checkbox'`; `add.exclusive` — flags only one entry holds; `add.requireOneOf` (+ `requireOneOfMessage`) — keys of which one must be filled.\n\t\t * - `row.href` may be `{ path, scheme: 'mailto'|'tel' }`.\n\t\t * - `afterSave` — `{ url, method?, body?, failureText? }`: called after every save that went through, with `@objectId` / `@object.<field>` tokens; when it fails the save stands and `failureText` shows.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The current detail-page object. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The current object id. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister register slug of the surface. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister schema slug of the surface. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: ['saved', 'header'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** The list as last saved by this widget; null = read it off objectData. */\n\t\t\tsaved: null,\n\t\t\t/** Live per-row state from `rowSource`, by key. */\n\t\t\tlive: {},\n\t\t\tdraft: {},\n\t\t\t/** Whether the add form is open (only with `add.trigger: 'button'`). */\n\t\t\tadding: false,\n\t\t\t/** The key of the entry the form edits; null while it adds. */\n\t\t\teditingKey: null,\n\t\t\terrors: {},\n\t\t\tbusy: '',\n\t\t\tnotice: '',\n\t\t\tnoticeType: 'error',\n\t\t\t// What the last copy button put on the clipboard, said to a screen reader.\n\t\t\tcopied: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The stored list.\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\titems() {\n\t\t\tconst list = this.saved !== null ? this.saved : readPath(this.objectData || {}, this.content.field)\n\t\t\treturn Array.isArray(list) ? list : []\n\t\t},\n\n\t\t/**\n\t\t * The rows to draw.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t * @spec openspec/changes/abstract-field-list/specs/field-list/spec.md#requirement-a-field-list-draws-the-entries-of-one-object-field\n\t\t */\n\t\trows() {\n\t\t\tconst spec = this.content.row || {}\n\t\t\treturn this.items.map((item, index) => {\n\t\t\t\tconst key = this.keyOf(item, index)\n\t\t\t\tconst entry = item && typeof item === 'object'\n\t\t\t\t\t? { ...item, ...(this.live[key] || {}) }\n\t\t\t\t\t: { value: item, ...(this.live[key] || {}) }\n\t\t\t\tconst title = this.text(entry, spec.title) || this.text(entry, spec.titleFallback) || (typeof item === 'string' ? item : key)\n\t\t\t\tconst meta = (Array.isArray(spec.meta) ? spec.meta : [])\n\t\t\t\t\t.map((part) => this.text(entry, part))\n\t\t\t\t\t.filter((part) => part !== '')\n\t\t\t\t\t.join(' · ')\n\t\t\t\tconst href = spec.href ? rowHref(entry, spec.href) : ''\n\t\t\t\treturn {\n\t\t\t\t\tkey,\n\t\t\t\t\tindex,\n\t\t\t\t\titem,\n\t\t\t\t\tentry,\n\t\t\t\t\ttitle,\n\t\t\t\t\thref,\n\t\t\t\t\texternal: /^https?:\\/\\//i.test(href),\n\t\t\t\t\tinitials: spec.avatar ? this.initials(title) : '',\n\t\t\t\t\tmeta,\n\t\t\t\t\tpill: this.pillOf(entry, spec.pill),\n\t\t\t\t\taside: spec.aside ? this.asideOf(entry, spec.aside) : '',\n\t\t\t\t\tdetail: spec.detail && this.matches(entry, spec.detail.when) ? this.fill(spec.detail.template, entry) : '',\n\t\t\t\t\tcopies: spec.detail && this.matches(entry, spec.detail.when) ? this.copiesOf(entry, spec.detail.copy) : [],\n\t\t\t\t\tactions: this.actionsFor(entry, index),\n\t\t\t\t\tcells: this.isTable ? this.cellsOf(entry) : [],\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/** Whether the list is read-only. */\n\t\treadOnly() {\n\t\t\treturn this.content.readOnly === true\n\t\t},\n\n\t\t/** Whether entries can be added. */\n\t\tcanAdd() {\n\t\t\treturn !this.readOnly && this.addFields.length > 0 && Boolean(this.content.field)\n\t\t},\n\n\t\t/** Whether entries can be edited in place (`edit`). */\n\t\tcanEdit() {\n\t\t\treturn !this.readOnly && Boolean(this.content.edit) && Boolean(this.content.field) && this.addFields.length > 0\n\t\t},\n\n\t\t/** @return {string} The edit button's label. */\n\t\teditLabel() {\n\t\t\tconst edit = this.content.edit\n\t\t\treturn this.tr((edit && typeof edit === 'object' && edit.label) || 'Edit')\n\t\t},\n\n\t\t/** @return {string} The submit label while editing. */\n\t\tsaveLabel() {\n\t\t\tconst edit = this.content.edit\n\t\t\treturn this.tr((edit && typeof edit === 'object' && edit.saveLabel) || 'Save')\n\t\t},\n\n\t\t/** Whether entries can be removed. */\n\t\tcanRemove() {\n\t\t\treturn !this.readOnly && Boolean(this.content.remove) && Boolean(this.content.field)\n\t\t},\n\n\t\t/**\n\t\t * The add form's fields, labels translated, select options translated.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\taddFields() {\n\t\t\tconst fields = this.content.add && Array.isArray(this.content.add.fields) ? this.content.add.fields : []\n\t\t\treturn fields\n\t\t\t\t.filter((field) => field && field.key)\n\t\t\t\t.map((field) => ({\n\t\t\t\t\t...field,\n\t\t\t\t\tlabel: this.tr(field.label || field.key),\n\t\t\t\t\toptions: (Array.isArray(field.options) ? field.options : []).map((option) => (\n\t\t\t\t\t\ttypeof option === 'object' ? { value: option.value, label: this.tr(option.label || String(option.value)) } : { value: option, label: this.tr(String(option)) }\n\t\t\t\t\t)),\n\t\t\t\t}))\n\t\t},\n\n\t\t/** Whether the rows are drawn as a table (`variant: 'table'`). */\n\t\tisTable() {\n\t\t\treturn this.content.variant === 'table' && this.tableColumns.length > 0\n\t\t},\n\n\t\t/**\n\t\t * The table columns, headers translated.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\ttableColumns() {\n\t\t\treturn (Array.isArray(this.content.columns) ? this.content.columns : [])\n\t\t\t\t.filter((column) => column && column.path)\n\t\t\t\t.map((column, index) => ({ ...column, key: `${column.path}-${index}`, header: this.tr(column.header || '') }))\n\t\t},\n\n\t\t/** Whether each row has an actions cell: inline row actions, or a \"more actions\" menu (row actions not placed in the band, and remove). */\n\t\thasMenu() {\n\t\t\treturn this.isTable && (this.canRemove || this.rows.some((row) => this.menuItems(row).length > 0 || this.inlineActions(row).length > 0))\n\t\t},\n\n\t\t/** Rows with a detail line: in the table variant it is a warning band under the table. */\n\t\tbandRows() {\n\t\t\treturn this.rows\n\t\t\t\t.filter((row) => row.detail !== '')\n\t\t\t\t.map((row) => ({ ...row, bandActions: row.actions.filter((action) => action.placement === 'band') }))\n\t\t},\n\n\t\t/** Whether the add form opens from a button (`add.trigger: 'button'`). */\n\t\taddTrigger() {\n\t\t\treturn Boolean(this.content.add && this.content.add.trigger === 'button')\n\t\t},\n\n\t\t/**\n\t\t * Where the add button sits: `header` (above the rows), `title` (the\n\t\t * card's title row, reported on the `header` event) or `footer` (default).\n\t\t */\n\t\taddPlacement() {\n\t\t\tconst placement = this.content.add && this.content.add.placement\n\t\t\treturn placement === 'header' || placement === 'title' ? placement : 'footer'\n\t\t},\n\n\t\t/** Whether the add form opens above the rows. */\n\t\taddAtTop() {\n\t\t\treturn this.addPlacement === 'header' || this.addPlacement === 'title'\n\t\t},\n\n\t\t/**\n\t\t * The card title row's actions: the add button with `add.placement: \"title\"`.\n\t\t *\n\t\t * @return {Array<{label: string, icon: string, run: () => void}>} The actions.\n\t\t */\n\t\ttitleActions() {\n\t\t\tif (!(this.canAdd && this.addTrigger && this.addPlacement === 'title')) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst run = () => {\n\t\t\t\tthis.adding = !this.adding\n\t\t\t}\n\t\t\treturn [{ label: this.addLabel, icon: 'Plus', run }]\n\t\t},\n\n\t\taddLabel() {\n\t\t\treturn this.tr((this.content.add && this.content.add.label) || 'Add')\n\t\t},\n\n\t\tremoveLabel() {\n\t\t\treturn this.tr((this.content.remove && this.content.remove.label) || 'Remove')\n\t\t},\n\n\t\t/**\n\t\t * The line above the rows, filled from the object.\n\t\t *\n\t\t * @spec openspec/changes/field-list-links-lead-after-save/specs/field-list/spec.md#requirement-a-field-list-links-its-rows-leads-with-the-object-and-calls-after-a-save\n\t\t * @return {string}\n\t\t */\n\t\tlead() {\n\t\t\tif (!this.content.lead) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.tr(this.content.lead).replace(/\\{([\\w.]+)\\}/g, (m, path) => {\n\t\t\t\tconst value = readPath(this.objectData || {}, path)\n\t\t\t\treturn value === undefined || value === null ? '' : String(value)\n\t\t\t})\n\t\t},\n\n\t\thint() {\n\t\t\treturn this.tr(this.content.hint || '')\n\t\t},\n\n\t\temptyLabel() {\n\t\t\treturn this.tr(this.content.emptyText || 'Nothing listed yet')\n\t\t},\n\n\t\t/** The object URL the list is saved to, or '' when it is not known. */\n\t\tobjectUrl() {\n\t\t\tconst id = this.objectId || (this.objectData && this.objectData.id)\n\t\t\tif (!this.register || !this.schema || !id || typeof this.register !== 'string' || typeof this.schema !== 'string') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `/apps/openregister/api/objects/${encodeURIComponent(this.register)}/${encodeURIComponent(this.schema)}/${encodeURIComponent(String(id))}`\n\t\t},\n\t},\n\n\twatch: {\n\t\ttitleActions: {\n\t\t\timmediate: true,\n\t\t\thandler(actions) {\n\t\t\t\t/**\n\t\t\t\t * @event header The card's title-row actions, `{ actions }`, for the host to draw.\n\t\t\t\t */\n\t\t\t\tthis.$emit('header', { actions })\n\t\t\t},\n\t\t},\n\n\t\tobjectData() {\n\t\t\tthis.saved = null\n\t\t\tthis.loadLive()\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.resetDraft()\n\t\tthis.loadLive()\n\t},\n\n\tmethods: {\n\t\ttr(text) {\n\t\t\tif (typeof text !== 'string' || text === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\t/**\n\t\t * The cells of one table row, one per column.\n\t\t *\n\t\t * @param {object} entry The entry.\n\t\t * @return {Array<{text: string, pill: object|null}>}\n\t\t */\n\t\tcellsOf(entry) {\n\t\t\treturn this.tableColumns.map((column) => ({\n\t\t\t\ttext: this.text(entry, column),\n\t\t\t\tpill: column.pill ? this.pillOf(entry, column) : null,\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * The items of a row's menu in the table variant: the row actions not\n\t\t * placed in the band, then remove.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {Array<{id: string, label: string, run: Function}>}\n\t\t */\n\t\tmenuItems(row) {\n\t\t\tconst items = row.actions\n\t\t\t\t.filter((action) => action.placement !== 'band' && action.placement !== 'inline')\n\t\t\t\t.map((action) => ({ id: action.id, label: action.label, run: () => this.runAction(action, row) }))\n\t\t\tif (this.canRemove) {\n\t\t\t\titems.push({ id: 'remove', label: this.removeLabel, run: () => this.remove(row) })\n\t\t\t}\n\t\t\treturn items\n\t\t},\n\n\t\t/**\n\t\t * The row actions drawn as buttons in a table row (`placement: 'inline'`).\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {Array<object>}\n\t\t */\n\t\tinlineActions(row) {\n\t\t\treturn row.actions.filter((action) => action.placement === 'inline')\n\t\t},\n\n\t\t/**\n\t\t * The identity of an entry.\n\t\t *\n\t\t * @param {unknown} item The entry.\n\t\t * @param {number} index Its position.\n\t\t * @return {string}\n\t\t */\n\t\tkeyOf(item, index) {\n\t\t\tif (item === null || typeof item !== 'object') {\n\t\t\t\treturn String(item)\n\t\t\t}\n\t\t\tconst key = this.content.key ? readPath(item, this.content.key) : undefined\n\t\t\treturn key === undefined || key === null || key === '' ? `item-${index}` : String(key)\n\t\t},\n\n\t\t/**\n\t\t * A display text off an entry: a path, or `{ path, map?, format? }`.\n\t\t *\n\t\t * @param {object} entry The entry.\n\t\t * @param {string|object} spec The spec.\n\t\t * @return {string}\n\t\t */\n\t\ttext(entry, spec) {\n\t\t\tif (!spec) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst def = typeof spec === 'string' ? { path: spec } : spec\n\t\t\tconst raw = readPath(entry, def.path)\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (def.format === 'date' || def.format === 'date-long') {\n\t\t\t\tconst date = parseDateValue(raw)\n\t\t\t\tif (date === null) {\n\t\t\t\t\treturn ''\n\t\t\t\t}\n\t\t\t\treturn def.format === 'date-long' ? formatBoardDate(date, new Date(date.getFullYear() + 1, 0, 1)) : formatBoardDate(date)\n\t\t\t}\n\t\t\tif (def.map && Object.hasOwn(def.map, String(raw))) {\n\t\t\t\treturn this.tr(def.map[String(raw)])\n\t\t\t}\n\t\t\treturn typeof raw === 'object' ? '' : String(raw)\n\t\t},\n\n\t\tpillOf(entry, spec) {\n\t\t\tif (!spec || !spec.path) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst raw = readPath(entry, spec.path)\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst label = this.text(entry, spec)\n\t\t\tconst variant = spec.variantMap && spec.variantMap[String(raw)]\n\t\t\treturn { label, variant: isBadgeVariant(variant) ? variant : 'default' }\n\t\t},\n\n\t\tasideOf(entry, spec) {\n\t\t\tconst value = this.text(entry, spec)\n\t\t\tif (value === '') {\n\t\t\t\t// No value: the optional `fallback` label, translated, instead of an empty aside.\n\t\t\t\treturn this.tr(spec.fallback)\n\t\t\t}\n\t\t\treturn spec.prefix ? `${this.tr(spec.prefix)} ${value}` : value\n\t\t},\n\n\t\t/**\n\t\t * Whether an entry matches a `{ field: value }` condition (every field equal).\n\t\t *\n\t\t * @param {object} entry The entry.\n\t\t * @param {object|undefined} when The condition.\n\t\t * @return {boolean}\n\t\t */\n\t\tmatches(entry, when) {\n\t\t\tif (!when || typeof when !== 'object') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn Object.entries(when).every(([field, expected]) => {\n\t\t\t\tconst actual = readPath(entry, field)\n\t\t\t\tif (expected && typeof expected === 'object' && !Array.isArray(expected)) {\n\t\t\t\t\treturn this.matchesOperators(entry, actual, expected)\n\t\t\t\t}\n\t\t\t\tif (expected === false) {\n\t\t\t\t\treturn actual === false || actual === undefined || actual === null || actual === ''\n\t\t\t\t}\n\t\t\t\treturn String(actual) === String(expected)\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Whether a value passes `{ eq?, neq?, set? }`. An operand `@row.<field>`\n\t\t * or `@<field>` is that field of the same entry.\n\t\t *\n\t\t * @param {object} entry The entry.\n\t\t * @param {unknown} actual The value tested.\n\t\t * @param {object} ops The operators.\n\t\t * @return {boolean}\n\t\t */\n\t\tmatchesOperators(entry, actual, ops) {\n\t\t\tconst empty = (v) => v === undefined || v === null || v === ''\n\t\t\tconst operand = (v) => {\n\t\t\t\tif (typeof v === 'string' && v.startsWith('@')) {\n\t\t\t\t\tconst path = v.startsWith('@row.') ? v.slice('@row.'.length) : v.slice(1)\n\t\t\t\t\treturn readPath(entry, path)\n\t\t\t\t}\n\t\t\t\treturn v\n\t\t\t}\n\t\t\tconst same = (a, b) => (empty(a) && empty(b)) || (!empty(a) && !empty(b) && String(a) === String(b))\n\t\t\tif (ops.set !== undefined && Boolean(ops.set) !== !empty(actual)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (Object.hasOwn(ops, 'eq') && !same(actual, operand(ops.eq))) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (Object.hasOwn(ops, 'neq') && same(actual, operand(ops.neq))) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\n\t\t/**\n\t\t * Fill `{path}` placeholders from an entry; '' when one stays empty.\n\t\t *\n\t\t * @param {string} template The template, translated first.\n\t\t * @param {object} entry The entry.\n\t\t * @return {string}\n\t\t */\n\t\tfill(template, entry) {\n\t\t\tif (typeof template !== 'string') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tlet missing = false\n\t\t\tconst out = this.tr(template).replace(/\\{([\\w.@]+)\\}/g, (match, path) => {\n\t\t\t\tconst value = readPath(entry, path)\n\t\t\t\tif (value === undefined || value === null || value === '') {\n\t\t\t\t\tmissing = true\n\t\t\t\t\treturn match\n\t\t\t\t}\n\t\t\t\treturn String(value)\n\t\t\t})\n\t\t\treturn missing ? '' : out\n\t\t},\n\n\t\t/**\n\t\t * The row actions an entry offers: request actions unless the list is\n\t\t * read-only, and dialog actions always, when their props all fill.\n\t\t *\n\t\t * @param {object} entry The entry.\n\t\t * @param {number} index Its position.\n\t\t * @return {Array<object>}\n\t\t */\n\t\tactionsFor(entry, index) {\n\t\t\tconst actions = Array.isArray(this.content.rowActions) ? this.content.rowActions : []\n\t\t\treturn actions\n\t\t\t\t.filter((action) => action && action.id && this.matches(entry, action.when))\n\t\t\t\t.filter((action) => (action.dialog\n\t\t\t\t\t? this.dialogPropsOf(action, entry, index) !== null\n\t\t\t\t\t: !this.readOnly && action.request && action.request.url))\n\t\t\t\t.map((action) => ({\n\t\t\t\t\t...action,\n\t\t\t\t\tlabel: this.tr(action.label || action.id),\n\t\t\t\t\tariaLabel: action.ariaLabel ? this.fill(action.ariaLabel, entry) : '',\n\t\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * A dialog action's props with their tokens filled, `/apps/` paths made\n\t\t * absolute; null when a token stays empty, so the dialog is not offered\n\t\t * with half an address.\n\t\t *\n\t\t * @param {object} action The action.\n\t\t * @param {object} entry The entry.\n\t\t * @param {number} index Its position.\n\t\t * @return {object|null}\n\t\t */\n\t\tdialogPropsOf(action, entry, index) {\n\t\t\tconst out = {}\n\t\t\tfor (const [name, value] of Object.entries(action.props || {})) {\n\t\t\t\tif (typeof value !== 'string') {\n\t\t\t\t\tout[name] = value\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tlet missing = false\n\t\t\t\tconst object = this.objectData || {}\n\t\t\t\tconst filled = value\n\t\t\t\t\t.replace(/@index\\b/g, () => String(index))\n\t\t\t\t\t.replace(/@objectId\\b/g, () => {\n\t\t\t\t\t\tconst id = String(this.objectId || object.id || '')\n\t\t\t\t\t\tmissing = missing || id === ''\n\t\t\t\t\t\treturn encodeURIComponent(id)\n\t\t\t\t\t})\n\t\t\t\t\t.replace(/@(row|object)\\.([\\w.@]+)/g, (m, scope, path) => {\n\t\t\t\t\t\tconst v = readPath((scope === 'row' ? entry : object) || {}, path)\n\t\t\t\t\t\tmissing = missing || v === undefined || v === null || v === ''\n\t\t\t\t\t\treturn encodeURIComponent(String(v ?? ''))\n\t\t\t\t\t})\n\t\t\t\tif (missing) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\tout[name] = filled.startsWith('/apps/') ? prefixUrl(filled) : filled\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\tinitials(name) {\n\t\t\treturn String(name || '').split(/[\\s@._-]+/).filter(Boolean).slice(0, 2).map((part) => part.charAt(0).toUpperCase()).join('')\n\t\t},\n\n\t\tremoveAriaLabel(row) {\n\t\t\treturn `${this.removeLabel}: ${row.title}`\n\t\t},\n\n\t\tresetDraft() {\n\t\t\tconst draft = {}\n\t\t\tfor (const field of this.addFields) {\n\t\t\t\tconst empty = field.type === 'checkbox' ? false : ''\n\t\t\t\tconst first = field.type === 'select' && field.options.length > 0 ? field.options[0].value : empty\n\t\t\t\tdraft[field.key] = field.default !== undefined ? field.default : first\n\t\t\t}\n\t\t\tthis.draft = draft\n\t\t\tthis.errors = {}\n\t\t},\n\n\t\t/**\n\t\t * Resolve `@objectId`, `@object.<field>` and `@row.<field>` inside a string or a payload.\n\t\t *\n\t\t * @param {unknown} value The value.\n\t\t * @param {object} entry The row entry.\n\t\t * @return {unknown}\n\t\t */\n\t\tresolve(value, entry) {\n\t\t\tif (Array.isArray(value)) {\n\t\t\t\treturn value.map((v) => this.resolve(v, entry))\n\t\t\t}\n\t\t\tif (value && typeof value === 'object') {\n\t\t\t\treturn Object.fromEntries(Object.entries(value).map(([k, v]) => [k, this.resolve(v, entry)]))\n\t\t\t}\n\t\t\tif (typeof value !== 'string') {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\tconst object = this.objectData || {}\n\t\t\tconst read = (source, path) => {\n\t\t\t\tconst v = readPath(source || {}, path)\n\t\t\t\treturn v === undefined || v === null ? '' : v\n\t\t\t}\n\t\t\tconst whole = value.match(/^@(row|object)\\.([\\w.@]+)$/)\n\t\t\tif (whole) {\n\t\t\t\treturn read(whole[1] === 'row' ? entry : object, whole[2])\n\t\t\t}\n\t\t\treturn value\n\t\t\t\t.replace(/@objectId/g, () => encodeURIComponent(String(this.objectId || object.id || '')))\n\t\t\t\t.replace(/@(row|object)\\.([\\w.@]+)/g, (m, scope, path) => encodeURIComponent(String(read(scope === 'row' ? entry : object, path))))\n\t\t},\n\n\t\tasync http() {\n\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\t\t\treturn { axios }\n\t\t},\n\n\t\t/**\n\t\t * Read live per-row state from `rowSource` and keep it by key.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadLive() {\n\t\t\tconst source = this.content.rowSource\n\t\t\tif (!source || !source.url || !source.key || !this.objectData) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst { axios } = await this.http()\n\t\t\t\tconst response = await axios.get(prefixUrl(this.resolve(source.url, {})))\n\t\t\t\tconst data = response && response.data\n\t\t\t\tconst list = source.path ? readPath(data, source.path) : data\n\t\t\t\tconst live = {}\n\t\t\t\tfor (const item of Array.isArray(list) ? list : []) {\n\t\t\t\t\tconst key = item && readPath(item, source.key)\n\t\t\t\t\tif (key !== undefined && key !== null) {\n\t\t\t\t\t\tlive[String(key)] = item\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.live = live\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn('[CnFieldListWidget] rowSource could not be read:', e?.message || e)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Save a new list: re-read the stored value, apply the change to it, and\n\t\t * PATCH that one field.\n\t\t *\n\t\t * @param {(stored: Array) => Array} change Builds the next list from the stored one, or throws an Error with a `userMessage`.\n\t\t * @param {string} busyKey What is busy.\n\t\t * @return {Promise<boolean>} Whether it saved.\n\t\t * @spec openspec/changes/abstract-field-list/specs/field-list/spec.md#requirement-a-field-list-saves-one-field-with-one-patch\n\t\t */\n\t\tasync save(change, busyKey) {\n\t\t\tif (!this.objectUrl) {\n\t\t\t\tthis.notify(this.tr('This list cannot be saved here.'))\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tthis.busy = busyKey\n\t\t\tthis.notice = ''\n\t\t\ttry {\n\t\t\t\tconst { axios } = await this.http()\n\t\t\t\tconst { data: stored } = await axios.get(prefixUrl(this.objectUrl))\n\t\t\t\tconst current = readPath(stored || {}, this.content.field)\n\t\t\t\tconst next = change(Array.isArray(current) ? current : [])\n\t\t\t\tconst { data } = await axios.patch(prefixUrl(this.objectUrl), { [this.content.field]: next })\n\t\t\t\tconst written = readPath(data || {}, this.content.field)\n\t\t\t\tthis.saved = Array.isArray(written) ? written : next\n\t\t\t\t/**\n\t\t\t\t * @event saved The list was saved: one PATCH of the field succeeded.\n\t\t\t\t * @type {{field: string, value: Array}}\n\t\t\t\t */\n\t\t\t\tthis.$emit('saved', { field: this.content.field, value: this.saved })\n\t\t\t\tawait this.runAfterSave(axios)\n\t\t\t\treturn true\n\t\t\t} catch (e) {\n\t\t\t\tthis.notify(e && e.userMessage ? e.userMessage : serverErrorMessage(e))\n\t\t\t\treturn false\n\t\t\t} finally {\n\t\t\t\tthis.busy = ''\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Call `afterSave` once a save went through. A failure leaves the save\n\t\t * standing and shows `failureText`.\n\t\t *\n\t\t * @spec openspec/changes/field-list-links-lead-after-save/specs/field-list/spec.md#requirement-a-field-list-links-its-rows-leads-with-the-object-and-calls-after-a-save\n\t\t * @param {object} axios The HTTP client.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync runAfterSave(axios) {\n\t\t\tconst after = this.content.afterSave\n\t\t\tif (!after || typeof after.url !== 'string' || after.url === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst method = String(after.method || 'POST').toLowerCase()\n\t\t\t\tconst url = prefixUrl(this.resolve(after.url, {}))\n\t\t\t\tif (method === 'get') {\n\t\t\t\t\tawait axios.get(url)\n\t\t\t\t} else {\n\t\t\t\t\tawait axios[method](url, this.resolve(after.body || {}, {}))\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tif (after.failureText) {\n\t\t\t\t\tthis.notify(this.tr(after.failureText), 'warning')\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tnotify(message, type = 'error') {\n\t\t\tthis.notice = message\n\t\t\tthis.noticeType = type\n\t\t},\n\n\t\t/**\n\t\t * Validate the draft and add it.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/abstract-field-list/specs/field-list/spec.md#requirement-a-field-list-adds-and-removes-entries\n\t\t */\n\t\tasync add() {\n\t\t\tconst errors = {}\n\t\t\tfor (const field of this.addFields) {\n\t\t\t\tconst value = this.draft[field.key]\n\t\t\t\tconst empty = value === undefined || value === null || String(value).trim() === ''\n\t\t\t\tif (field.required && empty) {\n\t\t\t\t\terrors[field.key] = this.tr('Fill this in.')\n\t\t\t\t} else if (!empty && field.pattern && !new RegExp(field.pattern).test(String(value).trim())) {\n\t\t\t\t\terrors[field.key] = this.tr(field.patternMessage || 'This value is not valid.')\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst oneOf = Array.isArray(this.content.add?.requireOneOf) ? this.content.add.requireOneOf : []\n\t\t\tif (oneOf.length > 0 && oneOf.every((key) => String(this.draft[key] ?? '').trim() === '')) {\n\t\t\t\terrors[oneOf[0]] = this.tr(this.content.add.requireOneOfMessage || 'Fill this in.')\n\t\t\t}\n\t\t\tthis.errors = errors\n\t\t\tif (Object.keys(errors).length > 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst entry = this.newEntry()\n\t\t\tconst scalar = typeof entry !== 'object'\n\t\t\tconst unique = this.content.add && this.content.add.unique\n\t\t\tconst editing = this.editingKey\n\t\t\tconst saved = await this.save((stored) => {\n\t\t\t\tconst others = stored.filter((item, index) => editing === null || this.keyOf(item, index) !== editing)\n\t\t\t\tconst duplicate = scalar\n\t\t\t\t\t? (unique && others.some((item) => String(item) === String(entry)))\n\t\t\t\t\t: (unique && others.some((item) => item && String(readPath(item, unique)) === String(entry[unique])))\n\t\t\t\tif (duplicate) {\n\t\t\t\t\tconst error = new Error('duplicate')\n\t\t\t\t\terror.userMessage = this.tr('This is already in the list.')\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t\tconst next = editing === null\n\t\t\t\t\t? [...stored, entry]\n\t\t\t\t\t: stored.map((item, index) => (this.keyOf(item, index) === editing ? (scalar ? entry : { ...item, ...entry }) : item))\n\t\t\t\treturn this.withExclusive(next, editing === null ? next.length - 1 : next.findIndex((item, index) => this.keyOf(stored[index], index) === editing))\n\t\t\t}, '*')\n\t\t\tif (saved) {\n\t\t\t\tthis.resetDraft()\n\t\t\t\tthis.adding = false\n\t\t\t\tthis.editingKey = null\n\t\t\t\tthis.loadLive()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Keep an `add.exclusive` flag on one entry: when the entry at `index`\n\t\t * has it, every other entry loses it (one primary address).\n\t\t *\n\t\t * @param {Array<object>} list The list to save.\n\t\t * @param {number} index The entry just added or edited.\n\t\t * @return {Array<object>} The list.\n\t\t * @spec openspec/changes/field-list-edit/specs/field-list/spec.md#requirement-a-field-list-entry-can-be-edited-and-one-entry-can-be-the-primary\n\t\t */\n\t\twithExclusive(list, index) {\n\t\t\tconst flags = Array.isArray(this.content.add?.exclusive) ? this.content.add.exclusive : []\n\t\t\tconst chosen = list[index]\n\t\t\tif (!chosen || typeof chosen !== 'object') {\n\t\t\t\treturn list\n\t\t\t}\n\t\t\treturn list.map((item, i) => {\n\t\t\t\tif (i === index || !item || typeof item !== 'object') {\n\t\t\t\t\treturn item\n\t\t\t\t}\n\t\t\t\tconst cleared = { ...item }\n\t\t\t\tfor (const flag of flags) {\n\t\t\t\t\tif (chosen[flag] === true && cleared[flag] === true) {\n\t\t\t\t\t\tcleared[flag] = false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn cleared\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Open an entry in the form, prefilled.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/field-list-edit/specs/field-list/spec.md#requirement-a-field-list-entry-can-be-edited-and-one-entry-can-be-the-primary\n\t\t */\n\t\tstartEdit(row) {\n\t\t\tconst draft = {}\n\t\t\tfor (const [index, field] of this.addFields.entries()) {\n\t\t\t\tconst scalarValue = this.content.add?.scalar === true && index === 0 ? row.item : undefined\n\t\t\t\tconst value = row.item && typeof row.item === 'object' ? readPath(row.item, field.key) : scalarValue\n\t\t\t\tdraft[field.key] = value === undefined || value === null ? (field.type === 'checkbox' ? false : '') : value\n\t\t\t}\n\t\t\tthis.draft = draft\n\t\t\tthis.errors = {}\n\t\t\tthis.editingKey = row.key\n\t\t\tthis.adding = true\n\t\t},\n\n\t\t/** Close the form without saving the edit. */\n\t\tcancelEdit() {\n\t\t\tthis.editingKey = null\n\t\t\tthis.adding = false\n\t\t\tthis.resetDraft()\n\t\t},\n\n\t\t/**\n\t\t * The entry the add form describes: an object of the filled add fields\n\t\t * over `add.defaults`, or, with `add.scalar`, the trimmed value of the\n\t\t * first add field itself (a list of strings such as `enumValues`).\n\t\t *\n\t\t * @return {object|string} The entry.\n\t\t * @spec openspec/changes/side-card-follows-row/specs/side-card-follows-row/spec.md#requirement-a-field-list-adds-plain-values\n\t\t */\n\t\tnewEntry() {\n\t\t\tconst add = this.content.add || {}\n\t\t\tconst trimmed = (value) => (typeof value === 'string' ? value.trim() : value)\n\t\t\tif (add.scalar === true) {\n\t\t\t\tconst first = this.addFields[0]\n\t\t\t\treturn first ? trimmed(this.draft[first.key]) : ''\n\t\t\t}\n\t\t\tconst entry = { ...(add.defaults || {}) }\n\t\t\tfor (const field of this.addFields) {\n\t\t\t\tconst value = this.draft[field.key]\n\t\t\t\tif (value !== undefined && value !== null && String(value).trim() !== '') {\n\t\t\t\t\tentry[field.key] = trimmed(value)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn entry\n\t\t},\n\n\t\t/**\n\t\t * Remove an entry by its key.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync remove(row) {\n\t\t\tawait this.save((stored) => stored.filter((item, index) => this.keyOf(item, index) !== row.key), row.key)\n\t\t},\n\n\t\t/**\n\t\t * Run a row action's request, then re-read the live state.\n\t\t *\n\t\t * @param {object} action The action.\n\t\t * @param {object} row The row.\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/abstract-field-list/specs/field-list/spec.md#requirement-a-field-list-row-can-run-a-configured-request\n\t\t */\n\t\t/**\n\t\t * The copy buttons of a row's detail line: one per named value that is not empty.\n\t\t *\n\t\t * @param {object} entry The entry.\n\t\t * @param {Array<{path: string, label?: string}>|undefined} declared The `detail.copy` list.\n\t\t * @return {Array<{path: string, label: string, value: string}>}\n\t\t */\n\t\tcopiesOf(entry, declared) {\n\t\t\treturn (Array.isArray(declared) ? declared : [])\n\t\t\t\t.filter((copy) => copy && typeof copy.path === 'string' && copy.path !== '')\n\t\t\t\t.map((copy) => ({\n\t\t\t\t\tpath: copy.path,\n\t\t\t\t\tlabel: this.tr(copy.label || 'Copy'),\n\t\t\t\t\tvalue: String(readPath(entry, copy.path) ?? ''),\n\t\t\t\t}))\n\t\t\t\t.filter((copy) => copy.value !== '')\n\t\t},\n\n\t\t/**\n\t\t * Put one value on the clipboard and say so; a browser that refuses says that instead.\n\t\t *\n\t\t * @param {{label: string, value: string}} copy The copy button.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync copyValue(copy) {\n\t\t\tthis.copied = ''\n\t\t\ttry {\n\t\t\t\tawait navigator.clipboard.writeText(copy.value)\n\t\t\t\tthis.copied = `${this.tr('Copied')}: ${copy.label}`\n\t\t\t} catch {\n\t\t\t\tthis.notify(this.tr('Copying did not work. Select the text and copy it yourself.'))\n\t\t\t}\n\t\t},\n\n\t\tasync runAction(action, row) {\n\t\t\tif (action.dialog) {\n\t\t\t\tconst props = this.dialogPropsOf(action, row.entry, row.index)\n\t\t\t\tif (props !== null && typeof this.cnOpenModal === 'function') {\n\t\t\t\t\tthis.cnOpenModal(action.dialog, props)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.busy = row.key\n\t\t\tthis.notice = ''\n\t\t\ttry {\n\t\t\t\tconst { axios } = await this.http()\n\t\t\t\tconst method = String(action.request.method || 'POST').toLowerCase()\n\t\t\t\tconst payload = this.resolve(action.request.payload || {}, row.entry)\n\t\t\t\tconst response = method === 'get'\n\t\t\t\t\t? await axios.get(prefixUrl(this.resolve(action.request.url, row.entry)))\n\t\t\t\t\t: await axios[method](prefixUrl(this.resolve(action.request.url, row.entry)), payload)\n\t\t\t\tconst message = response && response.data && typeof response.data.message === 'string' ? response.data.message : ''\n\t\t\t\tif (message) {\n\t\t\t\t\tthis.notify(message, 'info')\n\t\t\t\t}\n\t\t\t\tawait this.loadLive()\n\t\t\t} catch (e) {\n\t\t\t\tthis.notify(serverErrorMessage(e))\n\t\t\t} finally {\n\t\t\t\tthis.busy = ''\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-field-list-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tmin-width: 0;\n}\n\n.cn-field-list-widget__lead {\n\tmargin: 0;\n\tfont-weight: bold;\n}\n\n.cn-field-list-widget__rows {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-field-list-widget__row {\n\tpadding: 10px 0;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-field-list-widget__row:first-child {\n\tborder-top: none;\n\tpadding-top: 0;\n}\n\n.cn-field-list-widget__line {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tmin-width: 0;\n}\n\n.cn-field-list-widget__avatar {\n\tflex: 0 0 auto;\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 32px;\n\theight: 32px;\n\tborder-radius: 50%;\n\tbackground: var(--color-primary-element-light);\n\tcolor: var(--color-primary-element-light-text, var(--color-main-text));\n\tfont-size: 12px;\n\tfont-weight: 700;\n}\n\n.cn-field-list-widget__main {\n\tdisplay: flex;\n\tflex-direction: column;\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-field-list-widget__title {\n\tfont-weight: 600;\n\toverflow-wrap: anywhere;\n}\n\n.cn-field-list-widget__meta,\n.cn-field-list-widget__aside,\n.cn-field-list-widget__detail,\n.cn-field-list-widget__hint,\n.cn-field-list-widget__empty {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-list-widget__meta,\n.cn-field-list-widget__aside,\n.cn-field-list-widget__copies {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 4px;\n}\n\n.cn-field-list-widget__detail {\n\tfont-size: 13px;\n}\n\n.cn-field-list-widget__aside {\n\twhite-space: nowrap;\n}\n\n.cn-field-list-widget__detail,\n.cn-field-list-widget__hint,\n.cn-field-list-widget__empty {\n\tmargin: 4px 0 0;\n\toverflow-wrap: anywhere;\n}\n\n.cn-field-list-widget__add {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: flex-end;\n\tgap: 8px 12px;\n}\n\n.cn-field-list-widget__add > * {\n\tflex: 1 1 180px;\n}\n\n.cn-field-list-widget__add > button {\n\tflex: 0 0 auto;\n}\n\n.cn-field-list-widget__toolbar {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n}\n\n.cn-field-list-widget__table-wrap {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\toverflow-x: auto;\n}\n\n.cn-field-list-widget__table {\n\twidth: 100%;\n\tborder-collapse: collapse;\n}\n\n.cn-field-list-widget__th {\n\tpadding: 8px 12px;\n\ttext-align: start;\n\tfont-size: 13px;\n\tfont-weight: 600;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-field-list-widget__th--menu,\n.cn-field-list-widget__code {\n\tfont-size: 0.85em;\n\tword-break: break-all;\n}\n\n.cn-field-list-widget__td--menu {\n\twidth: 1%;\n\ttext-align: end;\n}\n\n.cn-field-list-widget__tr {\n\tborder-top: 1px solid var(--cn-board-hairline, var(--color-border));\n}\n\n.cn-field-list-widget__td {\n\tpadding: 10px 12px;\n\tvertical-align: middle;\n\toverflow-wrap: anywhere;\n}\n\n.cn-field-list-widget__sr {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n}\n\n.cn-field-list-widget__band {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 8px 12px;\n\tpadding: 12px 14px;\n\tborder: 1px solid var(--color-warning);\n\tborder-radius: 10px;\n\tbackground: color-mix(in srgb, var(--color-warning) 14%, var(--color-main-background));\n\tfont-size: 14px;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createTextVNode","_Fragment","_renderList","_createBlock","_toDisplayString","_createVNode","_createElementVNode","_mergeProps"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnFieldListWidget.vue3.js","sources":["../../../../src/components/CnFieldListWidget/CnFieldListWidget.vue?vue&type=template&id=91c65ff8&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-field-list-widget\" data-testid=\"cn-field-list-widget\">\n\t\t<div v-if=\"canAdd && addTrigger && addPlacement === 'header'\" class=\"cn-field-list-widget__toolbar\">\n\t\t\t<NcButton\n\t\t\t\tvariant=\"secondary\"\n\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\tdata-testid=\"cn-field-list-open-add\"\n\t\t\t\t@click=\"adding = !adding\">\n\t\t\t\t+ {{ addLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<form\n\t\t\tv-if=\"canAdd && addTrigger && addAtTop && adding\"\n\t\t\tclass=\"cn-field-list-widget__add\"\n\t\t\tdata-testid=\"cn-field-list-add\"\n\t\t\t@submit.prevent=\"add\">\n\t\t\t<div\n\t\t\t\tv-for=\"field in addFields\"\n\t\t\t\t:key=\"field.key\"\n\t\t\t\tclass=\"cn-field-list-widget__field\"\n\t\t\t\t:data-testid=\"`cn-field-list-input-${field.key}`\">\n\t\t\t\t<NcSelect\n\t\t\t\t\tv-if=\"field.type === 'multiselect'\"\n\t\t\t\t\tv-model=\"draft[field.key]\"\n\t\t\t\t\t:inputLabel=\"field.label\"\n\t\t\t\t\t:options=\"field.options\"\n\t\t\t\t\t:reduce=\"(option) => option.value\"\n\t\t\t\t\t:multiple=\"true\"\n\t\t\t\t\t:keepOpen=\"true\"\n\t\t\t\t\tlabel=\"label\" />\n\t\t\t\t<NcSelect\n\t\t\t\t\tv-else-if=\"field.type === 'select'\"\n\t\t\t\t\tv-model=\"draft[field.key]\"\n\t\t\t\t\t:inputLabel=\"field.label\"\n\t\t\t\t\t:options=\"field.options\"\n\t\t\t\t\t:reduce=\"(option) => option.value\"\n\t\t\t\t\t:clearable=\"false\"\n\t\t\t\t\tlabel=\"label\" />\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-else-if=\"field.type === 'checkbox'\"\n\t\t\t\t\tv-model=\"draft[field.key]\">\n\t\t\t\t\t{{ field.label }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else\n\t\t\t\t\tv-model=\"draft[field.key]\"\n\t\t\t\t\t:label=\"field.label\"\n\t\t\t\t\t:error=\"Boolean(errors[field.key])\"\n\t\t\t\t\t:helperText=\"errors[field.key] || ''\" />\n\t\t\t</div>\n\t\t\t<NcButton type=\"submit\"\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\tdata-testid=\"cn-field-list-submit\">\n\t\t\t\t{{ editingKey !== null ? saveLabel : addLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"editingKey !== null\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\tdata-testid=\"cn-field-list-cancel-edit\"\n\t\t\t\t@click=\"cancelEdit\">\n\t\t\t\t{{ tr('Cancel') }}\n\t\t\t</NcButton>\n\t\t</form>\n\t\t<p v-if=\"lead\" class=\"cn-field-list-widget__lead\" data-testid=\"cn-field-list-lead\">\n\t\t\t{{ lead }}\n\t\t</p>\n\t\t<p v-if=\"rows.length === 0\" class=\"cn-field-list-widget__empty\" data-testid=\"cn-field-list-empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t\t<div v-else-if=\"isTable\" class=\"cn-field-list-widget__table-wrap\">\n\t\t\t<table class=\"cn-field-list-widget__table\" data-testid=\"cn-field-list-table\">\n\t\t\t\t<thead>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<th\n\t\t\t\t\t\t\tv-for=\"column in tableColumns\"\n\t\t\t\t\t\t\t:key=\"column.key\"\n\t\t\t\t\t\t\tscope=\"col\"\n\t\t\t\t\t\t\tclass=\"cn-field-list-widget__th\">\n\t\t\t\t\t\t\t{{ column.header }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<th v-if=\"hasMenu\" scope=\"col\" class=\"cn-field-list-widget__th cn-field-list-widget__th--menu\">\n\t\t\t\t\t\t\t<span class=\"cn-field-list-widget__sr\">{{ tr('Actions') }}</span>\n\t\t\t\t\t\t</th>\n\t\t\t\t\t</tr>\n\t\t\t\t</thead>\n\t\t\t\t<tbody>\n\t\t\t\t\t<tr\n\t\t\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t\t\t:key=\"row.key\"\n\t\t\t\t\t\tclass=\"cn-field-list-widget__tr\"\n\t\t\t\t\t\t:data-testid=\"`cn-field-list-row-${row.key}`\">\n\t\t\t\t\t\t<td\n\t\t\t\t\t\t\tv-for=\"(column, index) in tableColumns\"\n\t\t\t\t\t\t\t:key=\"column.key\"\n\t\t\t\t\t\t\tclass=\"cn-field-list-widget__td\">\n\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\tv-if=\"column.pill && row.cells[index].pill\"\n\t\t\t\t\t\t\t\t:label=\"row.cells[index].pill.label\"\n\t\t\t\t\t\t\t\t:variant=\"row.cells[index].pill.variant\"\n\t\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t\t<strong v-else-if=\"column.strong\">{{ row.cells[index].text }}</strong>\n\t\t\t\t\t\t\t<code v-else-if=\"column.code\" class=\"cn-field-list-widget__code\">{{ row.cells[index].text }}</code>\n\t\t\t\t\t\t\t<template v-else>\n\t\t\t\t\t\t\t\t{{ row.cells[index].text }}\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t\t<td v-if=\"hasMenu\" class=\"cn-field-list-widget__td cn-field-list-widget__td--menu\">\n\t\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\t\tv-for=\"action in inlineActions(row)\"\n\t\t\t\t\t\t\t\t:key=\"action.id\"\n\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\t\t\t\t\t:aria-label=\"action.ariaLabel || undefined\"\n\t\t\t\t\t\t\t\t:data-testid=\"`cn-field-list-action-${action.id}-${row.key}`\"\n\t\t\t\t\t\t\t\t@click=\"runAction(action, row)\">\n\t\t\t\t\t\t\t\t{{ action.label }}\n\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t<NcActions\n\t\t\t\t\t\t\t\tv-if=\"menuItems(row).length > 0\"\n\t\t\t\t\t\t\t\t:forceMenu=\"true\"\n\t\t\t\t\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\t\t\t\t\t:ariaLabel=\"`${tr('More actions')}: ${row.title}`\"\n\t\t\t\t\t\t\t\t:data-testid=\"`cn-field-list-menu-${row.key}`\">\n\t\t\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\t\t\tv-for=\"item in menuItems(row)\"\n\t\t\t\t\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\t\t\t\t\t:data-testid=\"`cn-field-list-menu-${item.id}-${row.key}`\"\n\t\t\t\t\t\t\t\t\t@click=\"item.run\">\n\t\t\t\t\t\t\t\t\t{{ item.label }}\n\t\t\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t\t</NcActions>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t\t<div\n\t\t\t\tv-for=\"row in bandRows\"\n\t\t\t\t:key=\"row.key\"\n\t\t\t\tclass=\"cn-field-list-widget__band\"\n\t\t\t\trole=\"note\"\n\t\t\t\t:data-testid=\"`cn-field-list-band-${row.key}`\">\n\t\t\t\t<span class=\"cn-field-list-widget__band-text\">{{ row.detail }}</span>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-for=\"copy in row.copies\"\n\t\t\t\t\t:key=\"`copy-${copy.path}`\"\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:data-testid=\"`cn-field-list-copy-${copy.path}-${row.key}`\"\n\t\t\t\t\t@click=\"copyValue(copy)\">\n\t\t\t\t\t{{ copy.label }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-for=\"action in row.bandActions\"\n\t\t\t\t\t:key=\"action.id\"\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\t\t:data-testid=\"`cn-field-list-band-action-${action.id}-${row.key}`\"\n\t\t\t\t\t@click=\"runAction(action, row)\">\n\t\t\t\t\t{{ action.label }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\t\t<ul v-else class=\"cn-field-list-widget__rows\">\n\t\t\t<li\n\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t:key=\"row.key\"\n\t\t\t\tclass=\"cn-field-list-widget__row\"\n\t\t\t\t:data-testid=\"`cn-field-list-row-${row.key}`\">\n\t\t\t\t<div class=\"cn-field-list-widget__line\">\n\t\t\t\t\t<span v-if=\"row.initials\" class=\"cn-field-list-widget__avatar\" aria-hidden=\"true\">{{ row.initials }}</span>\n\t\t\t\t\t<span class=\"cn-field-list-widget__main\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\tv-if=\"row.href\"\n\t\t\t\t\t\t\tclass=\"cn-field-list-widget__title\"\n\t\t\t\t\t\t\t:href=\"row.href\"\n\t\t\t\t\t\t\tv-bind=\"row.external ? { target: '_blank', rel: 'noopener noreferrer' } : {}\">{{ row.title }}</a>\n\t\t\t\t\t\t<span v-else class=\"cn-field-list-widget__title\">{{ row.title }}</span>\n\t\t\t\t\t\t<span v-if=\"row.meta\" class=\"cn-field-list-widget__meta\">{{ row.meta }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\tv-if=\"row.pill\"\n\t\t\t\t\t\t:label=\"row.pill.label\"\n\t\t\t\t\t\t:variant=\"row.pill.variant\"\n\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t<span v-if=\"row.aside\" class=\"cn-field-list-widget__aside\">{{ row.aside }}</span>\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-for=\"action in row.actions\"\n\t\t\t\t\t\t:key=\"action.id\"\n\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\t\t\t:data-testid=\"`cn-field-list-action-${action.id}-${row.key}`\"\n\t\t\t\t\t\t@click=\"runAction(action, row)\">\n\t\t\t\t\t\t{{ action.label }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-if=\"canEdit\"\n\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\t\t\t:aria-label=\"`${editLabel}: ${row.title}`\"\n\t\t\t\t\t\t:data-testid=\"`cn-field-list-edit-${row.index}`\"\n\t\t\t\t\t\t@click=\"startEdit(row)\">\n\t\t\t\t\t\t{{ editLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-if=\"canRemove\"\n\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\t\t\t:aria-label=\"removeAriaLabel(row)\"\n\t\t\t\t\t\t:data-testid=\"`cn-field-list-remove-${row.key}`\"\n\t\t\t\t\t\t@click=\"remove(row)\">\n\t\t\t\t\t\t{{ removeLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t\t<p v-if=\"row.detail\" class=\"cn-field-list-widget__detail\">\n\t\t\t\t\t{{ row.detail }}\n\t\t\t\t</p>\n\t\t\t\t<div v-if=\"row.detail && row.copies.length > 0\" class=\"cn-field-list-widget__copies\">\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-for=\"copy in row.copies\"\n\t\t\t\t\t\t:key=\"`copy-${copy.path}`\"\n\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t:data-testid=\"`cn-field-list-copy-${copy.path}-${row.key}`\"\n\t\t\t\t\t\t@click=\"copyValue(copy)\">\n\t\t\t\t\t\t{{ copy.label }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t</li>\n\t\t</ul>\n\n\t\t<p v-if=\"hint\" class=\"cn-field-list-widget__hint\">\n\t\t\t{{ hint }}\n\t\t</p>\n\n\t\t<div v-if=\"canAdd && addTrigger && addPlacement === 'footer' && !adding\" class=\"cn-field-list-widget__toolbar\">\n\t\t\t<NcButton\n\t\t\t\tvariant=\"secondary\"\n\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\tdata-testid=\"cn-field-list-open-add\"\n\t\t\t\t@click=\"adding = true\">\n\t\t\t\t+ {{ addLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<form\n\t\t\tv-if=\"canAdd && !(addTrigger && addAtTop) && (!addTrigger || adding)\"\n\t\t\tclass=\"cn-field-list-widget__add\"\n\t\t\tdata-testid=\"cn-field-list-add\"\n\t\t\t@submit.prevent=\"add\">\n\t\t\t<div\n\t\t\t\tv-for=\"field in addFields\"\n\t\t\t\t:key=\"field.key\"\n\t\t\t\tclass=\"cn-field-list-widget__field\"\n\t\t\t\t:data-testid=\"`cn-field-list-input-${field.key}`\">\n\t\t\t\t<NcSelect\n\t\t\t\t\tv-if=\"field.type === 'multiselect'\"\n\t\t\t\t\tv-model=\"draft[field.key]\"\n\t\t\t\t\t:inputLabel=\"field.label\"\n\t\t\t\t\t:options=\"field.options\"\n\t\t\t\t\t:reduce=\"(option) => option.value\"\n\t\t\t\t\t:multiple=\"true\"\n\t\t\t\t\t:keepOpen=\"true\"\n\t\t\t\t\tlabel=\"label\" />\n\t\t\t\t<NcSelect\n\t\t\t\t\tv-else-if=\"field.type === 'select'\"\n\t\t\t\t\tv-model=\"draft[field.key]\"\n\t\t\t\t\t:inputLabel=\"field.label\"\n\t\t\t\t\t:options=\"field.options\"\n\t\t\t\t\t:reduce=\"(option) => option.value\"\n\t\t\t\t\t:clearable=\"false\"\n\t\t\t\t\tlabel=\"label\" />\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-else-if=\"field.type === 'checkbox'\"\n\t\t\t\t\tv-model=\"draft[field.key]\">\n\t\t\t\t\t{{ field.label }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else\n\t\t\t\t\tv-model=\"draft[field.key]\"\n\t\t\t\t\t:label=\"field.label\"\n\t\t\t\t\t:error=\"Boolean(errors[field.key])\"\n\t\t\t\t\t:helperText=\"errors[field.key] || ''\" />\n\t\t\t</div>\n\t\t\t<NcButton type=\"submit\"\n\t\t\t\tvariant=\"secondary\"\n\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\tdata-testid=\"cn-field-list-submit\">\n\t\t\t\t{{ editingKey !== null ? saveLabel : addLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"editingKey !== null\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\tdata-testid=\"cn-field-list-cancel-edit\"\n\t\t\t\t@click=\"cancelEdit\">\n\t\t\t\t{{ tr('Cancel') }}\n\t\t\t</NcButton>\n\t\t</form>\n\n\t\t<span class=\"cn-field-list-widget__sr\" aria-live=\"polite\" data-testid=\"cn-field-list-copied\">{{ copied }}</span>\n\t\t<NcNoteCard v-if=\"notice\" :type=\"noticeType\" data-testid=\"cn-field-list-notice\">\n\t\t\t{{ notice }}\n\t\t</NcNoteCard>\n\t</div>\n</template>\n\n<script>\nimport { NcActionButton, NcActions, NcButton, NcCheckboxRadioSwitch, NcNoteCard, NcSelect, NcTextField } from '@nextcloud/vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport { isBadgeVariant } from '../../utils/badgeVariants.js'\nimport { formatBoardDate } from '../../utils/boardDate.js'\nimport { parseDateValue } from '../../utils/dateVariant.js'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { serverErrorMessage } from '../../utils/serverErrorMessage.js'\n\n/**\n * CnFieldListWidget — the `field-list` widget: a list stored INSIDE one field\n * of the current object (a portal's `domains[]`, its `editors[]`), shown as\n * rows and edited in place.\n *\n * Rows are drawn from configuration (title, meta line, pill, aside, a detail\n * line); a small form adds an entry, a button removes one, and each change is\n * saved as ONE PATCH of that field on the object, after re-reading the stored\n * value so a concurrent change to the same list is not overwritten with a\n * stale copy. Optional row actions call a configured endpoint (a \"check\n * again\"), and an optional `rowSource` merges live per-row state read from an\n * endpoint by key without writing it back.\n *\n * The server decides who may write: a refused PATCH shows the server's words\n * and leaves the list as stored.\n *\n * @spec openspec/changes/abstract-field-list/specs/field-list/spec.md\n */\n/**\n * A link a row title may carry: http(s) or root-relative, '' otherwise.\n *\n * @param {unknown} value The candidate.\n * @return {string} The link.\n */\nfunction safeRowHref(value) {\n\tconst text = typeof value === 'string' ? value.trim() : ''\n\treturn /^(\\/(?!\\/)|https?:\\/\\/)/i.test(text) ? text : ''\n}\n\n/**\n * The link of a row title: a path to a safe link, or `{ path, scheme }` for\n * a `mailto:` or `tel:` link to the value.\n *\n * @param {object} entry The row entry.\n * @param {string|{path: string, scheme: string}} spec The `row.href` config.\n * @return {string} The link, '' for none.\n */\nfunction rowHref(entry, spec) {\n\tif (spec && typeof spec === 'object') {\n\t\tconst value = readPath(entry, spec.path)\n\t\tconst text = typeof value === 'string' ? value.trim() : ''\n\t\tif (text === '' || !['mailto', 'tel'].includes(spec.scheme)) {\n\t\t\treturn ''\n\t\t}\n\t\treturn `${spec.scheme}:${spec.scheme === 'tel' ? text.replace(/[^\\d+]/g, '') : encodeURIComponent(text).replace(/%40/g, '@')}`\n\t}\n\treturn safeRowHref(readPath(entry, spec))\n}\n\nexport default {\n\tname: 'CnFieldListWidget',\n\n\tcomponents: { CnStatusBadge, NcActionButton, NcActions, NcButton, NcCheckboxRadioSwitch, NcNoteCard, NcSelect, NcTextField },\n\n\tinject: {\n\t\tcnTranslate: { default: () => (key) => key },\n\t\t/** Opens a registered modal (CnAppRoot), for a row action with `dialog`. */\n\t\tcnOpenModal: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget configuration.\n\t\t *\n\t\t * - `field` (required) — the object field holding the array.\n\t\t * - `key` — the item field that identifies an entry (default: the item itself for strings).\n\t\t * - `row` — `{ title, titleFallback?, avatar?, meta?: [paths|{ path, map?, format? }], pill?: { path, map?, variantMap? }, aside?: { path, format?, prefix?, fallback? }, detail?: { template, when?, copy?: [{ path, label }] } }`. `detail.copy` puts a copy button beside the detail line for each named value of the row (an empty value gets none), such as a DNS record's name and value.\n\t\t * - `add` — `{ label?, fields: [{ key, label, type?: 'text'|'select'|'multiselect'|'checkbox', options?, optionsSource?, required?, pattern?, patternMessage? }], unique?, defaults? }`. A `multiselect` field holds a list of values. `optionsSource` — `{ url, path?, valuePath?, labelPath? }` (default `id` and `name`) — adds options read from an endpoint after the static ones, each value once (tokens `@objectId`, `@object.<field>`), for choices that live in other records such as a case type's statuses.\n\t\t * - `remove` — `{ label? }`; omitted = no remove.\n\t\t * - `rowActions` — `[{ id, label, ariaLabel?, when?, placement?: 'band'|'inline', request: { method?, url, payload? } }]`, or `{ id, label, dialog, props }` to open a modal registered in the app registry with filled props (shown in `readOnly` too, since it writes nothing, and only when every token in `props` fills); tokens `@object.<field>`, `@objectId`, `@row.<field>`, `@index`. In the table variant `placement: 'inline'` draws the action as a button in the row instead of in the \"...\" menu; `ariaLabel` is a template filled from the row.\n\t\t * - `when` (row actions, `detail`) — `{ field: value }`, every field equal; or per field `{ eq?, neq?, set? }`, where a value `@row.<field>` (or `@<field>`) is another field of the same row: `{ deliveredRef: { set: true, neq: '@originalRef' } }`.\n\t\t * - `rowSource` — `{ url, path?, key }`: live per-row state merged by key.\n\t\t * - `variant: 'table'` + `columns` — `[{ header, path, map?, format?, strong?, pill?, variantMap?, labelsFrom? }]` (a list value reads as its items joined; `labelsFrom` names an add field whose option labels word the values): rows as a table; a per-row \"...\" menu holds the row actions and remove; `detail` lines become a warning band under the table, with the row actions that have `placement: 'band'`.\n\t\t * - `add.trigger: 'button'`, `add.placement: 'header' | 'title' | 'footer'` — the add form opens from a button; `title` reports the button for the card's title row on the `header` event.\n\t\t * - `readOnly`, `hint`, `emptyText`.\n\t\t * - `row.href` — the path of a link for the row title; only http(s) and root-relative links, an absolute one opens in a new tab.\n\t\t * - `lead` — a line above the rows, translated, with `{path}` filled from the object.\n\t\t * - `edit` — `true` or `{ label?, saveLabel? }`: an Edit button per row opens the form prefilled and saves the entry in place.\n\t\t * - `add.fields[].type: 'checkbox'`; `add.exclusive` — flags only one entry holds; `add.requireOneOf` (+ `requireOneOfMessage`) — keys of which one must be filled.\n\t\t * - `row.href` may be `{ path, scheme: 'mailto'|'tel' }`.\n\t\t * - `afterSave` — `{ url, method?, body?, failureText? }`: called after every save that went through, with `@objectId` / `@object.<field>` tokens; when it fails the save stands and `failureText` shows.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The current detail-page object. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The current object id. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister register slug of the surface. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister schema slug of the surface. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: ['saved', 'header'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** The list as last saved by this widget; null = read it off objectData. */\n\t\t\tsaved: null,\n\t\t\t/** Live per-row state from `rowSource`, by key. */\n\t\t\tlive: {},\n\t\t\tdraft: {},\n\t\t\t/** Whether the add form is open (only with `add.trigger: 'button'`). */\n\t\t\tadding: false,\n\t\t\t/** The key of the entry the form edits; null while it adds. */\n\t\t\teditingKey: null,\n\t\t\terrors: {},\n\t\t\tbusy: '',\n\t\t\tnotice: '',\n\t\t\tnoticeType: 'error',\n\t\t\t// What the last copy button put on the clipboard, said to a screen reader.\n\t\t\tcopied: '',\n\t\t\t/** Options read through each add field's `optionsSource`, by field key. */\n\t\t\tsourcedOptions: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The stored list.\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\titems() {\n\t\t\tconst list = this.saved !== null ? this.saved : readPath(this.objectData || {}, this.content.field)\n\t\t\treturn Array.isArray(list) ? list : []\n\t\t},\n\n\t\t/**\n\t\t * The rows to draw.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t * @spec openspec/changes/abstract-field-list/specs/field-list/spec.md#requirement-a-field-list-draws-the-entries-of-one-object-field\n\t\t */\n\t\trows() {\n\t\t\tconst spec = this.content.row || {}\n\t\t\treturn this.items.map((item, index) => {\n\t\t\t\tconst key = this.keyOf(item, index)\n\t\t\t\tconst entry = item && typeof item === 'object'\n\t\t\t\t\t? { ...item, ...(this.live[key] || {}) }\n\t\t\t\t\t: { value: item, ...(this.live[key] || {}) }\n\t\t\t\tconst title = this.text(entry, spec.title) || this.text(entry, spec.titleFallback) || (typeof item === 'string' ? item : key)\n\t\t\t\tconst meta = (Array.isArray(spec.meta) ? spec.meta : [])\n\t\t\t\t\t.map((part) => this.text(entry, part))\n\t\t\t\t\t.filter((part) => part !== '')\n\t\t\t\t\t.join(' · ')\n\t\t\t\tconst href = spec.href ? rowHref(entry, spec.href) : ''\n\t\t\t\treturn {\n\t\t\t\t\tkey,\n\t\t\t\t\tindex,\n\t\t\t\t\titem,\n\t\t\t\t\tentry,\n\t\t\t\t\ttitle,\n\t\t\t\t\thref,\n\t\t\t\t\texternal: /^https?:\\/\\//i.test(href),\n\t\t\t\t\tinitials: spec.avatar ? this.initials(title) : '',\n\t\t\t\t\tmeta,\n\t\t\t\t\tpill: this.pillOf(entry, spec.pill),\n\t\t\t\t\taside: spec.aside ? this.asideOf(entry, spec.aside) : '',\n\t\t\t\t\tdetail: spec.detail && this.matches(entry, spec.detail.when) ? this.fill(spec.detail.template, entry) : '',\n\t\t\t\t\tcopies: spec.detail && this.matches(entry, spec.detail.when) ? this.copiesOf(entry, spec.detail.copy) : [],\n\t\t\t\t\tactions: this.actionsFor(entry, index),\n\t\t\t\t\tcells: this.isTable ? this.cellsOf(entry) : [],\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/** Whether the list is read-only. */\n\t\treadOnly() {\n\t\t\treturn this.content.readOnly === true\n\t\t},\n\n\t\t/** Whether entries can be added. */\n\t\tcanAdd() {\n\t\t\treturn !this.readOnly && this.addFields.length > 0 && Boolean(this.content.field)\n\t\t},\n\n\t\t/** Whether entries can be edited in place (`edit`). */\n\t\tcanEdit() {\n\t\t\treturn !this.readOnly && Boolean(this.content.edit) && Boolean(this.content.field) && this.addFields.length > 0\n\t\t},\n\n\t\t/** @return {string} The edit button's label. */\n\t\teditLabel() {\n\t\t\tconst edit = this.content.edit\n\t\t\treturn this.tr((edit && typeof edit === 'object' && edit.label) || 'Edit')\n\t\t},\n\n\t\t/** @return {string} The submit label while editing. */\n\t\tsaveLabel() {\n\t\t\tconst edit = this.content.edit\n\t\t\treturn this.tr((edit && typeof edit === 'object' && edit.saveLabel) || 'Save')\n\t\t},\n\n\t\t/** Whether entries can be removed. */\n\t\tcanRemove() {\n\t\t\treturn !this.readOnly && Boolean(this.content.remove) && Boolean(this.content.field)\n\t\t},\n\n\t\t/**\n\t\t * The add form's fields, labels translated, select options translated.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\taddFields() {\n\t\t\tconst fields = this.content.add && Array.isArray(this.content.add.fields) ? this.content.add.fields : []\n\t\t\treturn fields\n\t\t\t\t.filter((field) => field && field.key)\n\t\t\t\t.map((field) => ({\n\t\t\t\t\t...field,\n\t\t\t\t\tlabel: this.tr(field.label || field.key),\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t...(Array.isArray(field.options) ? field.options : []).map((option) => (\n\t\t\t\t\t\t\ttypeof option === 'object' ? { value: option.value, label: this.tr(option.label || String(option.value)) } : { value: option, label: this.tr(String(option)) }\n\t\t\t\t\t\t)),\n\t\t\t\t\t\t...(this.sourcedOptions[field.key] || []),\n\t\t\t\t\t],\n\t\t\t\t}))\n\t\t},\n\n\t\t/** Whether the rows are drawn as a table (`variant: 'table'`). */\n\t\tisTable() {\n\t\t\treturn this.content.variant === 'table' && this.tableColumns.length > 0\n\t\t},\n\n\t\t/**\n\t\t * The table columns, headers translated.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\ttableColumns() {\n\t\t\treturn (Array.isArray(this.content.columns) ? this.content.columns : [])\n\t\t\t\t.filter((column) => column && column.path)\n\t\t\t\t.map((column, index) => ({ ...column, key: `${column.path}-${index}`, header: this.tr(column.header || '') }))\n\t\t},\n\n\t\t/** Whether each row has an actions cell: inline row actions, or a \"more actions\" menu (row actions not placed in the band, and remove). */\n\t\thasMenu() {\n\t\t\treturn this.isTable && (this.canRemove || this.rows.some((row) => this.menuItems(row).length > 0 || this.inlineActions(row).length > 0))\n\t\t},\n\n\t\t/** Rows with a detail line: in the table variant it is a warning band under the table. */\n\t\tbandRows() {\n\t\t\treturn this.rows\n\t\t\t\t.filter((row) => row.detail !== '')\n\t\t\t\t.map((row) => ({ ...row, bandActions: row.actions.filter((action) => action.placement === 'band') }))\n\t\t},\n\n\t\t/** Whether the add form opens from a button (`add.trigger: 'button'`). */\n\t\taddTrigger() {\n\t\t\treturn Boolean(this.content.add && this.content.add.trigger === 'button')\n\t\t},\n\n\t\t/**\n\t\t * Where the add button sits: `header` (above the rows), `title` (the\n\t\t * card's title row, reported on the `header` event) or `footer` (default).\n\t\t */\n\t\taddPlacement() {\n\t\t\tconst placement = this.content.add && this.content.add.placement\n\t\t\treturn placement === 'header' || placement === 'title' ? placement : 'footer'\n\t\t},\n\n\t\t/** Whether the add form opens above the rows. */\n\t\taddAtTop() {\n\t\t\treturn this.addPlacement === 'header' || this.addPlacement === 'title'\n\t\t},\n\n\t\t/**\n\t\t * The card title row's actions: the add button with `add.placement: \"title\"`.\n\t\t *\n\t\t * @return {Array<{label: string, icon: string, run: () => void}>} The actions.\n\t\t */\n\t\ttitleActions() {\n\t\t\tif (!(this.canAdd && this.addTrigger && this.addPlacement === 'title')) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst run = () => {\n\t\t\t\tthis.adding = !this.adding\n\t\t\t}\n\t\t\treturn [{ label: this.addLabel, icon: 'Plus', run }]\n\t\t},\n\n\t\taddLabel() {\n\t\t\treturn this.tr((this.content.add && this.content.add.label) || 'Add')\n\t\t},\n\n\t\tremoveLabel() {\n\t\t\treturn this.tr((this.content.remove && this.content.remove.label) || 'Remove')\n\t\t},\n\n\t\t/**\n\t\t * The line above the rows, filled from the object.\n\t\t *\n\t\t * @spec openspec/changes/field-list-links-lead-after-save/specs/field-list/spec.md#requirement-a-field-list-links-its-rows-leads-with-the-object-and-calls-after-a-save\n\t\t * @return {string}\n\t\t */\n\t\tlead() {\n\t\t\tif (!this.content.lead) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.tr(this.content.lead).replace(/\\{([\\w.]+)\\}/g, (m, path) => {\n\t\t\t\tconst value = readPath(this.objectData || {}, path)\n\t\t\t\treturn value === undefined || value === null ? '' : String(value)\n\t\t\t})\n\t\t},\n\n\t\thint() {\n\t\t\treturn this.tr(this.content.hint || '')\n\t\t},\n\n\t\temptyLabel() {\n\t\t\treturn this.tr(this.content.emptyText || 'Nothing listed yet')\n\t\t},\n\n\t\t/** The object URL the list is saved to, or '' when it is not known. */\n\t\tobjectUrl() {\n\t\t\tconst id = this.objectId || (this.objectData && this.objectData.id)\n\t\t\tif (!this.register || !this.schema || !id || typeof this.register !== 'string' || typeof this.schema !== 'string') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `/apps/openregister/api/objects/${encodeURIComponent(this.register)}/${encodeURIComponent(this.schema)}/${encodeURIComponent(String(id))}`\n\t\t},\n\t},\n\n\twatch: {\n\t\ttitleActions: {\n\t\t\timmediate: true,\n\t\t\thandler(actions) {\n\t\t\t\t/**\n\t\t\t\t * @event header The card's title-row actions, `{ actions }`, for the host to draw.\n\t\t\t\t */\n\t\t\t\tthis.$emit('header', { actions })\n\t\t\t},\n\t\t},\n\n\t\tobjectData() {\n\t\t\tthis.saved = null\n\t\t\tthis.loadLive()\n\t\t\tthis.loadOptions()\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.resetDraft()\n\t\tthis.loadLive()\n\t\tthis.loadOptions()\n\t},\n\n\tmethods: {\n\t\ttr(text) {\n\t\t\tif (typeof text !== 'string' || text === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\t/**\n\t\t * The cells of one table row, one per column.\n\t\t *\n\t\t * @param {object} entry The entry.\n\t\t * @return {Array<{text: string, pill: object|null}>}\n\t\t */\n\t\tcellsOf(entry) {\n\t\t\treturn this.tableColumns.map((column) => ({\n\t\t\t\ttext: this.text(entry, column),\n\t\t\t\tpill: column.pill ? this.pillOf(entry, column) : null,\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * The items of a row's menu in the table variant: the row actions not\n\t\t * placed in the band, then remove.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {Array<{id: string, label: string, run: Function}>}\n\t\t */\n\t\tmenuItems(row) {\n\t\t\tconst items = row.actions\n\t\t\t\t.filter((action) => action.placement !== 'band' && action.placement !== 'inline')\n\t\t\t\t.map((action) => ({ id: action.id, label: action.label, run: () => this.runAction(action, row) }))\n\t\t\tif (this.canRemove) {\n\t\t\t\titems.push({ id: 'remove', label: this.removeLabel, run: () => this.remove(row) })\n\t\t\t}\n\t\t\treturn items\n\t\t},\n\n\t\t/**\n\t\t * The row actions drawn as buttons in a table row (`placement: 'inline'`).\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {Array<object>}\n\t\t */\n\t\tinlineActions(row) {\n\t\t\treturn row.actions.filter((action) => action.placement === 'inline')\n\t\t},\n\n\t\t/**\n\t\t * The identity of an entry.\n\t\t *\n\t\t * @param {unknown} item The entry.\n\t\t * @param {number} index Its position.\n\t\t * @return {string}\n\t\t */\n\t\tkeyOf(item, index) {\n\t\t\tif (item === null || typeof item !== 'object') {\n\t\t\t\treturn String(item)\n\t\t\t}\n\t\t\tconst key = this.content.key ? readPath(item, this.content.key) : undefined\n\t\t\treturn key === undefined || key === null || key === '' ? `item-${index}` : String(key)\n\t\t},\n\n\t\t/**\n\t\t * A display text off an entry: a path, or `{ path, map?, format? }`.\n\t\t *\n\t\t * @param {object} entry The entry.\n\t\t * @param {string|object} spec The spec.\n\t\t * @return {string}\n\t\t */\n\t\ttext(entry, spec) {\n\t\t\tif (!spec) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst def = typeof spec === 'string' ? { path: spec } : spec\n\t\t\tconst raw = readPath(entry, def.path)\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (def.format === 'date' || def.format === 'date-long') {\n\t\t\t\tconst date = parseDateValue(raw)\n\t\t\t\tif (date === null) {\n\t\t\t\t\treturn ''\n\t\t\t\t}\n\t\t\t\treturn def.format === 'date-long' ? formatBoardDate(date, new Date(date.getFullYear() + 1, 0, 1)) : formatBoardDate(date)\n\t\t\t}\n\t\t\tif (Array.isArray(raw)) {\n\t\t\t\treturn raw.map((item) => this.wordOf(item, def)).filter((word) => word !== '').join(', ')\n\t\t\t}\n\t\t\treturn this.wordOf(raw, def)\n\t\t},\n\n\t\t/**\n\t\t * One value in words: through the column's `map`, then the option labels\n\t\t * of the add field it names in `labelsFrom`, else the value itself.\n\t\t *\n\t\t * @param {string|number|boolean|object} raw The value.\n\t\t * @param {object} def The column.\n\t\t * @return {string}\n\t\t */\n\t\twordOf(raw, def) {\n\t\t\tif (def.map && Object.hasOwn(def.map, String(raw))) {\n\t\t\t\treturn this.tr(def.map[String(raw)])\n\t\t\t}\n\t\t\tif (def.labelsFrom) {\n\t\t\t\tconst field = this.addFields.find((candidate) => candidate.key === def.labelsFrom)\n\t\t\t\tconst option = field && field.options.find((candidate) => String(candidate.value) === String(raw))\n\t\t\t\tif (option) {\n\t\t\t\t\treturn option.label\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn typeof raw === 'object' ? '' : String(raw)\n\t\t},\n\n\t\t/**\n\t\t * The empty value of an add field: false for a checkbox, a list for a multiselect.\n\t\t *\n\t\t * @param {object} field The add field.\n\t\t * @return {boolean|Array|string}\n\t\t */\n\t\temptyValue(field) {\n\t\t\tif (field.type === 'checkbox') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn field.type === 'multiselect' ? [] : ''\n\t\t},\n\n\t\t/**\n\t\t * Read the options of every add field that names an `optionsSource`.\n\t\t *\n\t\t * A source that cannot be read leaves the static options alone.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadOptions() {\n\t\t\tconst fields = this.content.add && Array.isArray(this.content.add.fields) ? this.content.add.fields : []\n\t\t\tfor (const field of fields) {\n\t\t\t\tconst source = field && field.optionsSource\n\t\t\t\tif (!source || !source.url || !field.key) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst { axios } = await this.http()\n\t\t\t\t\tconst response = await axios.get(prefixUrl(this.resolve(source.url, {})))\n\t\t\t\t\tconst list = source.path ? readPath(response && response.data, source.path) : response && response.data\n\t\t\t\t\tconst options = (Array.isArray(list) ? list : [])\n\t\t\t\t\t\t.map((item) => ({ value: readPath(item, source.valuePath || 'id'), label: String(readPath(item, source.labelPath || 'name') ?? '') }))\n\t\t\t\t\t\t.filter((option, index, all) => option.value !== undefined && option.value !== null && option.value !== ''\n\t\t\t\t\t\t\t&& all.findIndex((other) => String(other.value) === String(option.value)) === index)\n\t\t\t\t\tthis.sourcedOptions = { ...this.sourcedOptions, [field.key]: options }\n\t\t\t\t} catch (e) {\n\t\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\t\tconsole.warn('[CnFieldListWidget] optionsSource could not be read:', e?.message || e)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tpillOf(entry, spec) {\n\t\t\tif (!spec || !spec.path) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst raw = readPath(entry, spec.path)\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst label = this.text(entry, spec)\n\t\t\tconst variant = spec.variantMap && spec.variantMap[String(raw)]\n\t\t\treturn { label, variant: isBadgeVariant(variant) ? variant : 'default' }\n\t\t},\n\n\t\tasideOf(entry, spec) {\n\t\t\tconst value = this.text(entry, spec)\n\t\t\tif (value === '') {\n\t\t\t\t// No value: the optional `fallback` label, translated, instead of an empty aside.\n\t\t\t\treturn this.tr(spec.fallback)\n\t\t\t}\n\t\t\treturn spec.prefix ? `${this.tr(spec.prefix)} ${value}` : value\n\t\t},\n\n\t\t/**\n\t\t * Whether an entry matches a `{ field: value }` condition (every field equal).\n\t\t *\n\t\t * @param {object} entry The entry.\n\t\t * @param {object|undefined} when The condition.\n\t\t * @return {boolean}\n\t\t */\n\t\tmatches(entry, when) {\n\t\t\tif (!when || typeof when !== 'object') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn Object.entries(when).every(([field, expected]) => {\n\t\t\t\tconst actual = readPath(entry, field)\n\t\t\t\tif (expected && typeof expected === 'object' && !Array.isArray(expected)) {\n\t\t\t\t\treturn this.matchesOperators(entry, actual, expected)\n\t\t\t\t}\n\t\t\t\tif (expected === false) {\n\t\t\t\t\treturn actual === false || actual === undefined || actual === null || actual === ''\n\t\t\t\t}\n\t\t\t\treturn String(actual) === String(expected)\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Whether a value passes `{ eq?, neq?, set? }`. An operand `@row.<field>`\n\t\t * or `@<field>` is that field of the same entry.\n\t\t *\n\t\t * @param {object} entry The entry.\n\t\t * @param {unknown} actual The value tested.\n\t\t * @param {object} ops The operators.\n\t\t * @return {boolean}\n\t\t */\n\t\tmatchesOperators(entry, actual, ops) {\n\t\t\tconst empty = (v) => v === undefined || v === null || v === ''\n\t\t\tconst operand = (v) => {\n\t\t\t\tif (typeof v === 'string' && v.startsWith('@')) {\n\t\t\t\t\tconst path = v.startsWith('@row.') ? v.slice('@row.'.length) : v.slice(1)\n\t\t\t\t\treturn readPath(entry, path)\n\t\t\t\t}\n\t\t\t\treturn v\n\t\t\t}\n\t\t\tconst same = (a, b) => (empty(a) && empty(b)) || (!empty(a) && !empty(b) && String(a) === String(b))\n\t\t\tif (ops.set !== undefined && Boolean(ops.set) !== !empty(actual)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (Object.hasOwn(ops, 'eq') && !same(actual, operand(ops.eq))) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (Object.hasOwn(ops, 'neq') && same(actual, operand(ops.neq))) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\n\t\t/**\n\t\t * Fill `{path}` placeholders from an entry; '' when one stays empty.\n\t\t *\n\t\t * @param {string} template The template, translated first.\n\t\t * @param {object} entry The entry.\n\t\t * @return {string}\n\t\t */\n\t\tfill(template, entry) {\n\t\t\tif (typeof template !== 'string') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tlet missing = false\n\t\t\tconst out = this.tr(template).replace(/\\{([\\w.@]+)\\}/g, (match, path) => {\n\t\t\t\tconst value = readPath(entry, path)\n\t\t\t\tif (value === undefined || value === null || value === '') {\n\t\t\t\t\tmissing = true\n\t\t\t\t\treturn match\n\t\t\t\t}\n\t\t\t\treturn String(value)\n\t\t\t})\n\t\t\treturn missing ? '' : out\n\t\t},\n\n\t\t/**\n\t\t * The row actions an entry offers: request actions unless the list is\n\t\t * read-only, and dialog actions always, when their props all fill.\n\t\t *\n\t\t * @param {object} entry The entry.\n\t\t * @param {number} index Its position.\n\t\t * @return {Array<object>}\n\t\t */\n\t\tactionsFor(entry, index) {\n\t\t\tconst actions = Array.isArray(this.content.rowActions) ? this.content.rowActions : []\n\t\t\treturn actions\n\t\t\t\t.filter((action) => action && action.id && this.matches(entry, action.when))\n\t\t\t\t.filter((action) => (action.dialog\n\t\t\t\t\t? this.dialogPropsOf(action, entry, index) !== null\n\t\t\t\t\t: !this.readOnly && action.request && action.request.url))\n\t\t\t\t.map((action) => ({\n\t\t\t\t\t...action,\n\t\t\t\t\tlabel: this.tr(action.label || action.id),\n\t\t\t\t\tariaLabel: action.ariaLabel ? this.fill(action.ariaLabel, entry) : '',\n\t\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * A dialog action's props with their tokens filled, `/apps/` paths made\n\t\t * absolute; null when a token stays empty, so the dialog is not offered\n\t\t * with half an address.\n\t\t *\n\t\t * @param {object} action The action.\n\t\t * @param {object} entry The entry.\n\t\t * @param {number} index Its position.\n\t\t * @return {object|null}\n\t\t */\n\t\tdialogPropsOf(action, entry, index) {\n\t\t\tconst out = {}\n\t\t\tfor (const [name, value] of Object.entries(action.props || {})) {\n\t\t\t\tif (typeof value !== 'string') {\n\t\t\t\t\tout[name] = value\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tlet missing = false\n\t\t\t\tconst object = this.objectData || {}\n\t\t\t\tconst filled = value\n\t\t\t\t\t.replace(/@index\\b/g, () => String(index))\n\t\t\t\t\t.replace(/@objectId\\b/g, () => {\n\t\t\t\t\t\tconst id = String(this.objectId || object.id || '')\n\t\t\t\t\t\tmissing = missing || id === ''\n\t\t\t\t\t\treturn encodeURIComponent(id)\n\t\t\t\t\t})\n\t\t\t\t\t.replace(/@(row|object)\\.([\\w.@]+)/g, (m, scope, path) => {\n\t\t\t\t\t\tconst v = readPath((scope === 'row' ? entry : object) || {}, path)\n\t\t\t\t\t\tmissing = missing || v === undefined || v === null || v === ''\n\t\t\t\t\t\treturn encodeURIComponent(String(v ?? ''))\n\t\t\t\t\t})\n\t\t\t\tif (missing) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\tout[name] = filled.startsWith('/apps/') ? prefixUrl(filled) : filled\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\tinitials(name) {\n\t\t\treturn String(name || '').split(/[\\s@._-]+/).filter(Boolean).slice(0, 2).map((part) => part.charAt(0).toUpperCase()).join('')\n\t\t},\n\n\t\tremoveAriaLabel(row) {\n\t\t\treturn `${this.removeLabel}: ${row.title}`\n\t\t},\n\n\t\tresetDraft() {\n\t\t\tconst draft = {}\n\t\t\tfor (const field of this.addFields) {\n\t\t\t\tconst empty = this.emptyValue(field)\n\t\t\t\tconst first = field.type === 'select' && field.options.length > 0 ? field.options[0].value : empty\n\t\t\t\tdraft[field.key] = field.default !== undefined ? field.default : first\n\t\t\t}\n\t\t\tthis.draft = draft\n\t\t\tthis.errors = {}\n\t\t},\n\n\t\t/**\n\t\t * Resolve `@objectId`, `@object.<field>` and `@row.<field>` inside a string or a payload.\n\t\t *\n\t\t * @param {unknown} value The value.\n\t\t * @param {object} entry The row entry.\n\t\t * @return {unknown}\n\t\t */\n\t\tresolve(value, entry) {\n\t\t\tif (Array.isArray(value)) {\n\t\t\t\treturn value.map((v) => this.resolve(v, entry))\n\t\t\t}\n\t\t\tif (value && typeof value === 'object') {\n\t\t\t\treturn Object.fromEntries(Object.entries(value).map(([k, v]) => [k, this.resolve(v, entry)]))\n\t\t\t}\n\t\t\tif (typeof value !== 'string') {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\tconst object = this.objectData || {}\n\t\t\tconst read = (source, path) => {\n\t\t\t\tconst v = readPath(source || {}, path)\n\t\t\t\treturn v === undefined || v === null ? '' : v\n\t\t\t}\n\t\t\tconst whole = value.match(/^@(row|object)\\.([\\w.@]+)$/)\n\t\t\tif (whole) {\n\t\t\t\treturn read(whole[1] === 'row' ? entry : object, whole[2])\n\t\t\t}\n\t\t\treturn value\n\t\t\t\t.replace(/@objectId/g, () => encodeURIComponent(String(this.objectId || object.id || '')))\n\t\t\t\t.replace(/@(row|object)\\.([\\w.@]+)/g, (m, scope, path) => encodeURIComponent(String(read(scope === 'row' ? entry : object, path))))\n\t\t},\n\n\t\tasync http() {\n\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\t\t\treturn { axios }\n\t\t},\n\n\t\t/**\n\t\t * Read live per-row state from `rowSource` and keep it by key.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadLive() {\n\t\t\tconst source = this.content.rowSource\n\t\t\tif (!source || !source.url || !source.key || !this.objectData) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst { axios } = await this.http()\n\t\t\t\tconst response = await axios.get(prefixUrl(this.resolve(source.url, {})))\n\t\t\t\tconst data = response && response.data\n\t\t\t\tconst list = source.path ? readPath(data, source.path) : data\n\t\t\t\tconst live = {}\n\t\t\t\tfor (const item of Array.isArray(list) ? list : []) {\n\t\t\t\t\tconst key = item && readPath(item, source.key)\n\t\t\t\t\tif (key !== undefined && key !== null) {\n\t\t\t\t\t\tlive[String(key)] = item\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.live = live\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn('[CnFieldListWidget] rowSource could not be read:', e?.message || e)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Save a new list: re-read the stored value, apply the change to it, and\n\t\t * PATCH that one field.\n\t\t *\n\t\t * @param {(stored: Array) => Array} change Builds the next list from the stored one, or throws an Error with a `userMessage`.\n\t\t * @param {string} busyKey What is busy.\n\t\t * @return {Promise<boolean>} Whether it saved.\n\t\t * @spec openspec/changes/abstract-field-list/specs/field-list/spec.md#requirement-a-field-list-saves-one-field-with-one-patch\n\t\t */\n\t\tasync save(change, busyKey) {\n\t\t\tif (!this.objectUrl) {\n\t\t\t\tthis.notify(this.tr('This list cannot be saved here.'))\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tthis.busy = busyKey\n\t\t\tthis.notice = ''\n\t\t\ttry {\n\t\t\t\tconst { axios } = await this.http()\n\t\t\t\tconst { data: stored } = await axios.get(prefixUrl(this.objectUrl))\n\t\t\t\tconst current = readPath(stored || {}, this.content.field)\n\t\t\t\tconst next = change(Array.isArray(current) ? current : [])\n\t\t\t\tconst { data } = await axios.patch(prefixUrl(this.objectUrl), { [this.content.field]: next })\n\t\t\t\tconst written = readPath(data || {}, this.content.field)\n\t\t\t\tthis.saved = Array.isArray(written) ? written : next\n\t\t\t\t/**\n\t\t\t\t * @event saved The list was saved: one PATCH of the field succeeded.\n\t\t\t\t * @type {{field: string, value: Array}}\n\t\t\t\t */\n\t\t\t\tthis.$emit('saved', { field: this.content.field, value: this.saved })\n\t\t\t\tawait this.runAfterSave(axios)\n\t\t\t\treturn true\n\t\t\t} catch (e) {\n\t\t\t\tthis.notify(e && e.userMessage ? e.userMessage : serverErrorMessage(e))\n\t\t\t\treturn false\n\t\t\t} finally {\n\t\t\t\tthis.busy = ''\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Call `afterSave` once a save went through. A failure leaves the save\n\t\t * standing and shows `failureText`.\n\t\t *\n\t\t * @spec openspec/changes/field-list-links-lead-after-save/specs/field-list/spec.md#requirement-a-field-list-links-its-rows-leads-with-the-object-and-calls-after-a-save\n\t\t * @param {object} axios The HTTP client.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync runAfterSave(axios) {\n\t\t\tconst after = this.content.afterSave\n\t\t\tif (!after || typeof after.url !== 'string' || after.url === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst method = String(after.method || 'POST').toLowerCase()\n\t\t\t\tconst url = prefixUrl(this.resolve(after.url, {}))\n\t\t\t\tif (method === 'get') {\n\t\t\t\t\tawait axios.get(url)\n\t\t\t\t} else {\n\t\t\t\t\tawait axios[method](url, this.resolve(after.body || {}, {}))\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tif (after.failureText) {\n\t\t\t\t\tthis.notify(this.tr(after.failureText), 'warning')\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tnotify(message, type = 'error') {\n\t\t\tthis.notice = message\n\t\t\tthis.noticeType = type\n\t\t},\n\n\t\t/**\n\t\t * Validate the draft and add it.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/abstract-field-list/specs/field-list/spec.md#requirement-a-field-list-adds-and-removes-entries\n\t\t */\n\t\tasync add() {\n\t\t\tconst errors = {}\n\t\t\tfor (const field of this.addFields) {\n\t\t\t\tconst value = this.draft[field.key]\n\t\t\t\tconst empty = value === undefined || value === null || (Array.isArray(value) ? value.length === 0 : String(value).trim() === '')\n\t\t\t\tif (field.required && empty) {\n\t\t\t\t\terrors[field.key] = this.tr('Fill this in.')\n\t\t\t\t} else if (!empty && field.pattern && !Array.isArray(value) && !new RegExp(field.pattern).test(String(value).trim())) {\n\t\t\t\t\terrors[field.key] = this.tr(field.patternMessage || 'This value is not valid.')\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst oneOf = Array.isArray(this.content.add?.requireOneOf) ? this.content.add.requireOneOf : []\n\t\t\tif (oneOf.length > 0 && oneOf.every((key) => String(this.draft[key] ?? '').trim() === '')) {\n\t\t\t\terrors[oneOf[0]] = this.tr(this.content.add.requireOneOfMessage || 'Fill this in.')\n\t\t\t}\n\t\t\tthis.errors = errors\n\t\t\tif (Object.keys(errors).length > 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst entry = this.newEntry()\n\t\t\tconst scalar = typeof entry !== 'object'\n\t\t\tconst unique = this.content.add && this.content.add.unique\n\t\t\tconst editing = this.editingKey\n\t\t\tconst saved = await this.save((stored) => {\n\t\t\t\tconst others = stored.filter((item, index) => editing === null || this.keyOf(item, index) !== editing)\n\t\t\t\tconst duplicate = scalar\n\t\t\t\t\t? (unique && others.some((item) => String(item) === String(entry)))\n\t\t\t\t\t: (unique && others.some((item) => item && String(readPath(item, unique)) === String(entry[unique])))\n\t\t\t\tif (duplicate) {\n\t\t\t\t\tconst error = new Error('duplicate')\n\t\t\t\t\terror.userMessage = this.tr('This is already in the list.')\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t\tconst next = editing === null\n\t\t\t\t\t? [...stored, entry]\n\t\t\t\t\t: stored.map((item, index) => (this.keyOf(item, index) === editing ? (scalar ? entry : { ...item, ...entry }) : item))\n\t\t\t\treturn this.withExclusive(next, editing === null ? next.length - 1 : next.findIndex((item, index) => this.keyOf(stored[index], index) === editing))\n\t\t\t}, '*')\n\t\t\tif (saved) {\n\t\t\t\tthis.resetDraft()\n\t\t\t\tthis.adding = false\n\t\t\t\tthis.editingKey = null\n\t\t\t\tthis.loadLive()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Keep an `add.exclusive` flag on one entry: when the entry at `index`\n\t\t * has it, every other entry loses it (one primary address).\n\t\t *\n\t\t * @param {Array<object>} list The list to save.\n\t\t * @param {number} index The entry just added or edited.\n\t\t * @return {Array<object>} The list.\n\t\t * @spec openspec/changes/field-list-edit/specs/field-list/spec.md#requirement-a-field-list-entry-can-be-edited-and-one-entry-can-be-the-primary\n\t\t */\n\t\twithExclusive(list, index) {\n\t\t\tconst flags = Array.isArray(this.content.add?.exclusive) ? this.content.add.exclusive : []\n\t\t\tconst chosen = list[index]\n\t\t\tif (!chosen || typeof chosen !== 'object') {\n\t\t\t\treturn list\n\t\t\t}\n\t\t\treturn list.map((item, i) => {\n\t\t\t\tif (i === index || !item || typeof item !== 'object') {\n\t\t\t\t\treturn item\n\t\t\t\t}\n\t\t\t\tconst cleared = { ...item }\n\t\t\t\tfor (const flag of flags) {\n\t\t\t\t\tif (chosen[flag] === true && cleared[flag] === true) {\n\t\t\t\t\t\tcleared[flag] = false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn cleared\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Open an entry in the form, prefilled.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/field-list-edit/specs/field-list/spec.md#requirement-a-field-list-entry-can-be-edited-and-one-entry-can-be-the-primary\n\t\t */\n\t\tstartEdit(row) {\n\t\t\tconst draft = {}\n\t\t\tfor (const [index, field] of this.addFields.entries()) {\n\t\t\t\tconst scalarValue = this.content.add?.scalar === true && index === 0 ? row.item : undefined\n\t\t\t\tconst value = row.item && typeof row.item === 'object' ? readPath(row.item, field.key) : scalarValue\n\t\t\t\tdraft[field.key] = value === undefined || value === null ? this.emptyValue(field) : value\n\t\t\t}\n\t\t\tthis.draft = draft\n\t\t\tthis.errors = {}\n\t\t\tthis.editingKey = row.key\n\t\t\tthis.adding = true\n\t\t},\n\n\t\t/** Close the form without saving the edit. */\n\t\tcancelEdit() {\n\t\t\tthis.editingKey = null\n\t\t\tthis.adding = false\n\t\t\tthis.resetDraft()\n\t\t},\n\n\t\t/**\n\t\t * The entry the add form describes: an object of the filled add fields\n\t\t * over `add.defaults`, or, with `add.scalar`, the trimmed value of the\n\t\t * first add field itself (a list of strings such as `enumValues`).\n\t\t *\n\t\t * @return {object|string} The entry.\n\t\t * @spec openspec/changes/side-card-follows-row/specs/side-card-follows-row/spec.md#requirement-a-field-list-adds-plain-values\n\t\t */\n\t\tnewEntry() {\n\t\t\tconst add = this.content.add || {}\n\t\t\tconst trimmed = (value) => (typeof value === 'string' ? value.trim() : value)\n\t\t\tif (add.scalar === true) {\n\t\t\t\tconst first = this.addFields[0]\n\t\t\t\treturn first ? trimmed(this.draft[first.key]) : ''\n\t\t\t}\n\t\t\tconst entry = { ...(add.defaults || {}) }\n\t\t\tfor (const field of this.addFields) {\n\t\t\t\tconst value = this.draft[field.key]\n\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\t// An emptied list is written as empty, so an edit can clear it.\n\t\t\t\t\tentry[field.key] = value\n\t\t\t\t} else if (value !== undefined && value !== null && String(value).trim() !== '') {\n\t\t\t\t\tentry[field.key] = trimmed(value)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn entry\n\t\t},\n\n\t\t/**\n\t\t * Remove an entry by its key.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync remove(row) {\n\t\t\tawait this.save((stored) => stored.filter((item, index) => this.keyOf(item, index) !== row.key), row.key)\n\t\t},\n\n\t\t/**\n\t\t * Run a row action's request, then re-read the live state.\n\t\t *\n\t\t * @param {object} action The action.\n\t\t * @param {object} row The row.\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/abstract-field-list/specs/field-list/spec.md#requirement-a-field-list-row-can-run-a-configured-request\n\t\t */\n\t\t/**\n\t\t * The copy buttons of a row's detail line: one per named value that is not empty.\n\t\t *\n\t\t * @param {object} entry The entry.\n\t\t * @param {Array<{path: string, label?: string}>|undefined} declared The `detail.copy` list.\n\t\t * @return {Array<{path: string, label: string, value: string}>}\n\t\t */\n\t\tcopiesOf(entry, declared) {\n\t\t\treturn (Array.isArray(declared) ? declared : [])\n\t\t\t\t.filter((copy) => copy && typeof copy.path === 'string' && copy.path !== '')\n\t\t\t\t.map((copy) => ({\n\t\t\t\t\tpath: copy.path,\n\t\t\t\t\tlabel: this.tr(copy.label || 'Copy'),\n\t\t\t\t\tvalue: String(readPath(entry, copy.path) ?? ''),\n\t\t\t\t}))\n\t\t\t\t.filter((copy) => copy.value !== '')\n\t\t},\n\n\t\t/**\n\t\t * Put one value on the clipboard and say so; a browser that refuses says that instead.\n\t\t *\n\t\t * @param {{label: string, value: string}} copy The copy button.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync copyValue(copy) {\n\t\t\tthis.copied = ''\n\t\t\ttry {\n\t\t\t\tawait navigator.clipboard.writeText(copy.value)\n\t\t\t\tthis.copied = `${this.tr('Copied')}: ${copy.label}`\n\t\t\t} catch {\n\t\t\t\tthis.notify(this.tr('Copying did not work. Select the text and copy it yourself.'))\n\t\t\t}\n\t\t},\n\n\t\tasync runAction(action, row) {\n\t\t\tif (action.dialog) {\n\t\t\t\tconst props = this.dialogPropsOf(action, row.entry, row.index)\n\t\t\t\tif (props !== null && typeof this.cnOpenModal === 'function') {\n\t\t\t\t\tthis.cnOpenModal(action.dialog, props)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.busy = row.key\n\t\t\tthis.notice = ''\n\t\t\ttry {\n\t\t\t\tconst { axios } = await this.http()\n\t\t\t\tconst method = String(action.request.method || 'POST').toLowerCase()\n\t\t\t\tconst payload = this.resolve(action.request.payload || {}, row.entry)\n\t\t\t\tconst response = method === 'get'\n\t\t\t\t\t? await axios.get(prefixUrl(this.resolve(action.request.url, row.entry)))\n\t\t\t\t\t: await axios[method](prefixUrl(this.resolve(action.request.url, row.entry)), payload)\n\t\t\t\tconst message = response && response.data && typeof response.data.message === 'string' ? response.data.message : ''\n\t\t\t\tif (message) {\n\t\t\t\t\tthis.notify(message, 'info')\n\t\t\t\t}\n\t\t\t\tawait this.loadLive()\n\t\t\t} catch (e) {\n\t\t\t\tthis.notify(serverErrorMessage(e))\n\t\t\t} finally {\n\t\t\t\tthis.busy = ''\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-field-list-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tmin-width: 0;\n}\n\n.cn-field-list-widget__lead {\n\tmargin: 0;\n\tfont-weight: bold;\n}\n\n.cn-field-list-widget__rows {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-field-list-widget__row {\n\tpadding: 10px 0;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-field-list-widget__row:first-child {\n\tborder-top: none;\n\tpadding-top: 0;\n}\n\n.cn-field-list-widget__line {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tmin-width: 0;\n}\n\n.cn-field-list-widget__avatar {\n\tflex: 0 0 auto;\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 32px;\n\theight: 32px;\n\tborder-radius: 50%;\n\tbackground: var(--color-primary-element-light);\n\tcolor: var(--color-primary-element-light-text, var(--color-main-text));\n\tfont-size: 12px;\n\tfont-weight: 700;\n}\n\n.cn-field-list-widget__main {\n\tdisplay: flex;\n\tflex-direction: column;\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-field-list-widget__title {\n\tfont-weight: 600;\n\toverflow-wrap: anywhere;\n}\n\n.cn-field-list-widget__meta,\n.cn-field-list-widget__aside,\n.cn-field-list-widget__detail,\n.cn-field-list-widget__hint,\n.cn-field-list-widget__empty {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-list-widget__meta,\n.cn-field-list-widget__aside,\n.cn-field-list-widget__copies {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 4px;\n}\n\n.cn-field-list-widget__detail {\n\tfont-size: 13px;\n}\n\n.cn-field-list-widget__aside {\n\twhite-space: nowrap;\n}\n\n.cn-field-list-widget__detail,\n.cn-field-list-widget__hint,\n.cn-field-list-widget__empty {\n\tmargin: 4px 0 0;\n\toverflow-wrap: anywhere;\n}\n\n.cn-field-list-widget__add {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: flex-end;\n\tgap: 8px 12px;\n}\n\n.cn-field-list-widget__add > * {\n\tflex: 1 1 180px;\n}\n\n.cn-field-list-widget__add > button {\n\tflex: 0 0 auto;\n}\n\n.cn-field-list-widget__toolbar {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n}\n\n.cn-field-list-widget__table-wrap {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\toverflow-x: auto;\n}\n\n.cn-field-list-widget__table {\n\twidth: 100%;\n\tborder-collapse: collapse;\n}\n\n.cn-field-list-widget__th {\n\tpadding: 8px 12px;\n\ttext-align: start;\n\tfont-size: 13px;\n\tfont-weight: 600;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-field-list-widget__th--menu,\n.cn-field-list-widget__code {\n\tfont-size: 0.85em;\n\tword-break: break-all;\n}\n\n.cn-field-list-widget__td--menu {\n\twidth: 1%;\n\ttext-align: end;\n}\n\n.cn-field-list-widget__tr {\n\tborder-top: 1px solid var(--cn-board-hairline, var(--color-border));\n}\n\n.cn-field-list-widget__td {\n\tpadding: 10px 12px;\n\tvertical-align: middle;\n\toverflow-wrap: anywhere;\n}\n\n.cn-field-list-widget__sr {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n}\n\n.cn-field-list-widget__band {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 8px 12px;\n\tpadding: 12px 14px;\n\tborder: 1px solid var(--color-warning);\n\tborder-radius: 10px;\n\tbackground: color-mix(in srgb, var(--color-warning) 14%, var(--color-main-background));\n\tfont-size: 14px;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createTextVNode","_Fragment","_renderList","_createBlock","_toDisplayString","_createVNode","_createElementVNode","_mergeProps"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import script from './CnIntegrationWidget.vue2.js';
|
|
|
2
2
|
import { render } from './CnIntegrationWidget.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-1b6b243b";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -111,6 +111,15 @@ var script = {
|
|
|
111
111
|
*/
|
|
112
112
|
objectLabel: { type: String, default: '' },
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Extra props for the leaf, set by the placement (a manifest widget's
|
|
116
|
+
* `props.placementProps`), for example the kinds a finance leaf shows. The
|
|
117
|
+
* record's own identity always wins over a key of the same name.
|
|
118
|
+
*
|
|
119
|
+
* @type {object}
|
|
120
|
+
*/
|
|
121
|
+
placementProps: { type: Object, default: () => ({}) },
|
|
122
|
+
|
|
114
123
|
/**
|
|
115
124
|
* The surface the leaf itself is told it renders on, when it differs
|
|
116
125
|
* from `surface` (a leaf placed in a dialog that draws its sidebar-tab
|
|
@@ -386,6 +395,7 @@ var script = {
|
|
|
386
395
|
*/
|
|
387
396
|
leafProps(provider) {
|
|
388
397
|
return {
|
|
398
|
+
...this.placementProps,
|
|
389
399
|
integrationId: provider.id,
|
|
390
400
|
register: this.resolvedRegister,
|
|
391
401
|
schema: this.resolvedSchema,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnIntegrationWidget.vue2.js","sources":["../../../../src/components/CnIntegrationWidget/CnIntegrationWidget.vue"],"sourcesContent":["<!--\n CnIntegrationWidget — tabbed, app-faithful integration widget for\n OpenRegister object detail pages and dashboards.\n\n Supersedes the generic Phase E pair (CnIntegrationCard /\n CnIntegrationWidgetGrid) which rendered one one-size surface that\n erased each integrated app's visual identity. This widget gives every\n integration its own tab (app icon + label + accent), composing the\n existing bespoke per-leaf content component (`provider.tab`), PLUS a\n single-integration mode for manifest multi-placement.\n\n TWO MODES\n ---------\n - Tabbed (default): a tab strip with one tab per integration available\n for `surface` (optionally narrowed by `include`), sorted by `.order`.\n The active tab renders that leaf's content. Unavailable integrations\n still get a tab; clicking shows an NcEmptyContent set-up state.\n - Single (`:only=\"deck\"`): NO tab strip — just one integration's\n content under a compact header (app icon + label). This is what an\n app manifest places several times on one detail page (ADR-024).\n\n AVAILABILITY (Phase J-B)\n ------------------------\n Per-integration availability is sourced, in order:\n 1. descriptor `available` (boolean) when the descriptor carries it,\n 2. else the OCS capability\n `openregister.integrations.providers[].available`\n (getCapabilities()), distinguishing missing-app from\n not-configured via a `reason` field when present,\n 3. else `isAppInstalled(requiredApp)` (OC.appswebroots / capabilities).\n When unavailable, the leaf renders NcEmptyContent: app icon, a\n \"{App} not available\" / \"not configured\" name, a short description, and\n an NcButton linking to the leaf's setup docs\n (`https://openregister.conduction.nl/docs/Integrations/{id}/`).\n\n DESCRIPTOR CONTRACT (filled by the per-app waves)\n -------------------------------------------------\n In addition to the registry's existing fields, the widget reads:\n - icon (MDI name string) — the app icon, rendered via CnIcon.\n - accentColor (hex) — per-app brand tint for the active tab / header.\n - appName (string) — backing-app display name for empty copy\n (defaults to `label`).\n - docsUrl (string) — setup-docs link (defaults to the\n openregister.conduction.nl path derived from `id`).\n - surfaces (string[]) — optional surface allowlist (eligible\n everywhere when omitted).\n These are normalised in `src/integrations/registry.js`; the per-app\n fidelity waves refine accentColor/appName per leaf.\n\n MANIFEST PLACEMENT (ADR-024)\n ----------------------------\n An app manifest references this component by name and passes config as\n props, e.g. to place two single-integration widgets on a detail page:\n\n \"widgets\": [\n { \"component\": \"CnIntegrationWidget\",\n \"props\": { \"only\": \"deck\", \"surface\": \"detail-page\" } },\n { \"component\": \"CnIntegrationWidget\",\n \"props\": { \"only\": \"calendar\", \"surface\": \"detail-page\" } }\n ]\n\n The host wires `register` / `schema` / `objectId` from the object\n context. Omit `only` (and optionally pass `include`) for the tabbed\n surface.\n\n See ADR-019 (registry), ADR-022 (sidebar tab contract), ADR-024\n (manifest widget placement), ADR-004 (component composition).\n\n @example\n Tabbed, all detail-page integrations:\n <CnIntegrationWidget\n register=\"decidesk\"\n schema=\"meeting\"\n object-id=\"obj-1\" />\n\n @example\n Single-integration, manifest-placeable:\n <CnIntegrationWidget\n only=\"deck\"\n register=\"decidesk\"\n schema=\"meeting\"\n object-id=\"obj-1\" />\n-->\n<template>\n\t<div class=\"cn-integration-widget\" :data-surface=\"surface\" :data-mode=\"mode\">\n\t\t<!-- SINGLE MODE: one integration, no tab strip. -->\n\t\t<template v-if=\"mode === 'single'\">\n\t\t\t<div v-if=\"singleProvider\" class=\"cn-integration-widget__single\">\n\t\t\t\t<header class=\"cn-integration-widget__header\" :style=\"accentStyle(singleProvider)\">\n\t\t\t\t\t<CnIcon\n\t\t\t\t\t\tv-if=\"singleProvider.icon\"\n\t\t\t\t\t\t:name=\"singleProvider.icon\"\n\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\tclass=\"cn-integration-widget__header-icon\" />\n\t\t\t\t\t<span class=\"cn-integration-widget__header-label\">{{ singleProvider.label }}</span>\n\t\t\t\t\t<div class=\"cn-integration-widget__header-actions\">\n\t\t\t\t\t\t<CnActionsMenu\n\t\t\t\t\t\t\t:documentationUrl=\"documentationUrl\"\n\t\t\t\t\t\t\t:widgetId=\"resolvedWidgetId\"\n\t\t\t\t\t\t\t:title=\"singleProvider.label\"\n\t\t\t\t\t\t\t:surface=\"`widget:${resolvedWidgetId}`\"\n\t\t\t\t\t\t\ttestidBase=\"cn-integration-widget\" />\n\t\t\t\t\t</div>\n\t\t\t\t</header>\n\t\t\t\t<div class=\"cn-integration-widget__panel\">\n\t\t\t\t\t<CnIntegrationWidgetEmpty\n\t\t\t\t\t\tv-if=\"!isAvailable(singleProvider)\"\n\t\t\t\t\t\t:provider=\"singleProvider\"\n\t\t\t\t\t\t:reason=\"unavailableReason(singleProvider)\" />\n\t\t\t\t\t<CnLeafMountHost\n\t\t\t\t\t\tv-else-if=\"isMountLeaf(singleProvider)\"\n\t\t\t\t\t\t:provider=\"singleProvider\"\n\t\t\t\t\t\t:mountProps=\"leafMountProps(singleProvider)\" />\n\t\t\t\t\t<component\n\t\t\t\t\t\t:is=\"leafComponent(singleProvider)\"\n\t\t\t\t\t\tv-else\n\t\t\t\t\t\tv-bind=\"leafProps(singleProvider)\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- The leaf is not registered: its app is absent or did not load. -->\n\t\t\t<CnIntegrationWidgetEmpty\n\t\t\t\tv-else-if=\"appName\"\n\t\t\t\t:provider=\"absentProvider\"\n\t\t\t\treason=\"missing-app\"\n\t\t\t\tdata-testid=\"cn-integration-widget-absent\" />\n\t\t\t<NcEmptyContent v-else :name=\"unknownLeafLabel\" />\n\t\t</template>\n\n\t\t<!-- TABBED MODE: tab strip + active panel. -->\n\t\t<template v-else>\n\t\t\t<NcEmptyContent\n\t\t\t\tv-if=\"visibleProviders.length === 0\"\n\t\t\t\t:name=\"noIntegrationsLabel\" />\n\t\t\t<template v-else>\n\t\t\t\t<div class=\"cn-integration-widget__tabbar\">\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"cn-integration-widget__tabs\"\n\t\t\t\t\t\trole=\"tablist\"\n\t\t\t\t\t\t:aria-label=\"tabListLabel\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tv-for=\"(provider, idx) in visibleProviders\"\n\t\t\t\t\t\t\t:id=\"tabId(provider)\"\n\t\t\t\t\t\t\t:key=\"provider.id\"\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t\t\tclass=\"cn-integration-widget__tab\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-integration-widget__tab--active': provider.id === activeId }\"\n\t\t\t\t\t\t\t:style=\"tabAccentStyle(provider)\"\n\t\t\t\t\t\t\t:aria-selected=\"provider.id === activeId ? 'true' : 'false'\"\n\t\t\t\t\t\t\t:tabindex=\"provider.id === activeId ? 0 : -1\"\n\t\t\t\t\t\t\t:aria-controls=\"panelId(provider)\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-integration-widget-tab-${provider.id}`\"\n\t\t\t\t\t\t\t@click=\"selectTab(provider.id)\"\n\t\t\t\t\t\t\t@keydown=\"onTabKeydown($event, idx)\">\n\t\t\t\t\t\t\t<CnIcon\n\t\t\t\t\t\t\t\tv-if=\"provider.icon\"\n\t\t\t\t\t\t\t\t:name=\"provider.icon\"\n\t\t\t\t\t\t\t\t:size=\"18\"\n\t\t\t\t\t\t\t\tclass=\"cn-integration-widget__tab-icon\" />\n\t\t\t\t\t\t\t<span class=\"cn-integration-widget__tab-label\">{{ provider.label }}</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-integration-widget__header-actions\">\n\t\t\t\t\t\t<CnActionsMenu\n\t\t\t\t\t\t\t:documentationUrl=\"documentationUrl\"\n\t\t\t\t\t\t\t:widgetId=\"resolvedWidgetId\"\n\t\t\t\t\t\t\t:title=\"headerTitle\"\n\t\t\t\t\t\t\t:surface=\"`widget:${resolvedWidgetId}`\"\n\t\t\t\t\t\t\ttestidBase=\"cn-integration-widget\" />\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\n\t\t\t\t<div\n\t\t\t\t\tv-if=\"activeProvider\"\n\t\t\t\t\t:id=\"panelId(activeProvider)\"\n\t\t\t\t\tclass=\"cn-integration-widget__panel\"\n\t\t\t\t\trole=\"tabpanel\"\n\t\t\t\t\t:aria-labelledby=\"tabId(activeProvider)\"\n\t\t\t\t\ttabindex=\"0\">\n\t\t\t\t\t<CnIntegrationWidgetEmpty\n\t\t\t\t\t\tv-if=\"!isAvailable(activeProvider)\"\n\t\t\t\t\t\t:provider=\"activeProvider\"\n\t\t\t\t\t\t:reason=\"unavailableReason(activeProvider)\" />\n\t\t\t\t\t<component\n\t\t\t\t\t\t:is=\"leafComponent(activeProvider)\"\n\t\t\t\t\t\tv-else\n\t\t\t\t\t\tv-bind=\"leafProps(activeProvider)\" />\n\t\t\t\t</div>\n\t\t\t</template>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcEmptyContent } from '@nextcloud/vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport CnLeafMountHost from '../CnLeafMountHost/CnLeafMountHost.vue'\nimport CnIntegrationWidgetEmpty from './CnIntegrationWidgetEmpty.vue'\nimport { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'\nimport { registerIntegrationIcons } from '../../integrations/icons.js'\nimport { isAppInstalled } from '../../utils/appInstalled.js'\nimport { CnActionsMenu } from '../CnActionsMenu/index.js'\nimport { resolveProviderAvailability } from './availability.js'\n\n// Ensure every descriptor's MDI icon resolves through CnIcon regardless\n// of what the host app registered at bootstrap — otherwise tabs fall\n// back to the generic HelpCircleOutline glyph. Idempotent.\nregisterIntegrationIcons()\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\n\n/**\n * CnIntegrationWidget — tabbed integration widget + single-integration\n * mode. See the file-level docblock for the contract.\n */\nexport default {\n\tname: 'CnIntegrationWidget',\n\n\tcomponents: { NcEmptyContent, CnIcon, CnIntegrationWidgetEmpty, CnActionsMenu, CnLeafMountHost },\n\n\tprops: {\n\t\t/**\n\t\t * Optional registry override (used in tests so registrations\n\t\t * don't leak across cases). Defaults to the singleton.\n\t\t */\n\t\tregistry: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister register id — a slug/uuid string, or the register\n\t\t * OBJECT a detail page injects. Accepts both for the same reason\n\t\t * `schema` does; see below.\n\t\t */\n\t\tregister: { type: [String, Object], default: '' },\n\t\t/**\n\t\t * OpenRegister schema — a slug/uuid string, or the schema OBJECT the\n\t\t * detail-page widget grid injects.\n\t\t *\n\t\t * This used to be `type: String`, and a detail page handed it the\n\t\t * resolved object instead. Vue reported the mismatch as a dev-only\n\t\t * warning (\"Expected String with value \\\"[object Object]\\\", got\n\t\t * Object\") and then passed the object through unchanged, so the leaf\n\t\t * built `/apps/openregister/api/schemas/[object Object]` and got a 404\n\t\t * — an unreadable URL rather than a named failure, and silent in\n\t\t * production where the warning is compiled out.\n\t\t *\n\t\t * `CnAuditTrailWidget` already accepted both shapes and collapsed the\n\t\t * object to its slug; this mirrors that so the two widgets cannot\n\t\t * disagree about what a detail page may hand them.\n\t\t */\n\t\tschema: { type: [String, Object], default: '' },\n\t\t/** Parent object id. Forwarded to each leaf. */\n\t\tobjectId: { type: [String, Number], default: '' },\n\t\t/**\n\t\t * Single-integration mode: render ONLY this leaf id with no tab\n\t\t * strip. The manifest multi-placement entry point (ADR-024).\n\t\t */\n\t\tonly: { type: String, default: '' },\n\t\t/**\n\t\t * Rendering surface — filters integrations by `surfaces.includes()`\n\t\t * when a descriptor declares a `surfaces` allowlist.\n\t\t */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (s) => VALID_SURFACES.includes(s),\n\t\t},\n\n\t\t/**\n\t\t * Explicit allowlist of leaf ids to show as tabs. When omitted,\n\t\t * all registered integrations eligible for `surface` are shown.\n\t\t *\n\t\t * @type {string[]|null}\n\t\t */\n\t\tinclude: { type: Array, default: null },\n\t\t/** Base API URL forwarded to each leaf content component. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Optional object type forwarded to each leaf content component. */\n\t\tobjectType: { type: String, default: '' },\n\t\t/**\n\t\t * Documentation link target for the header's shared Actions menu.\n\t\t * When non-empty a \"Documentation\" item opens it in a new tab;\n\t\t * empty (the default) hides the item.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocumentationUrl: { type: String, default: '' },\n\t\t/**\n\t\t * Stable id carried on the Actions-menu refresh / request-feature\n\t\t * payloads and surface string. When empty the header title is\n\t\t * slugified as a fallback (see `resolvedWidgetId`).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\twidgetId: { type: String, default: '' },\n\n\t\t/**\n\t\t * The record's display name, handed to the leaf as `objectLabel` (a\n\t\t * leaf titles its panel or its dialog with it).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tobjectLabel: { type: String, default: '' },\n\n\t\t/**\n\t\t * The surface the leaf itself is told it renders on, when it differs\n\t\t * from `surface` (a leaf placed in a dialog that draws its sidebar-tab\n\t\t * look: `sidebar-tab`). Empty passes `surface`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tleafSurface: { type: String, default: '' },\n\n\t\t/**\n\t\t * The app that provides the `only` leaf, named in the empty state when\n\t\t * the leaf is not registered (\"Decidiq not available\"). Without it an\n\t\t * unregistered leaf shows the plain \"not available\" line.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappName: { type: String, default: '' },\n\n\t\t/** The icon of that empty state (an MDI name). */\n\t\tappIcon: { type: String, default: '' },\n\t},\n\n\tsetup(props) {\n\t\tconst { integrations, resolveTab } = useIntegrationRegistry(props.registry || undefined)\n\t\t// `resolveTab` swaps a lib-owned entry for THIS bundle's component\n\t\t// (openregister#1958); the entry's own `tab` may belong to another\n\t\t// bundle's Vue and then renders its nested components as literal\n\t\t// unknown elements. See `leafComponent`.\n\t\treturn { registryIntegrations: integrations, resolveRegistryTab: resolveTab }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Currently selected tab id (tabbed mode only). */\n\t\t\tactiveId: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** 'single' when `only` is set, else 'tabbed'. */\n\t\tmode() {\n\t\t\treturn this.only ? 'single' : 'tabbed'\n\t\t},\n\n\t\t/**\n\t\t * The register SLUG. A register object collapses to its slug/name/id,\n\t\t * mirroring `CnAuditTrailWidget.resolvedRegister`.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedRegister() {\n\t\t\tconst r = this.register || ''\n\t\t\treturn typeof r === 'string' ? r : (r && (r.slug || r.name || r.id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * The schema SLUG. A schema object collapses to its slug/name/id.\n\t\t *\n\t\t * Leaves interpolate this straight into an OpenRegister URL, so an\n\t\t * object reaching them produces `/api/schemas/[object Object]` and a\n\t\t * 404 that names nothing. Collapsing here means every leaf receives\n\t\t * the string it already documents, and a caller that hands over the\n\t\t * whole schema object — which the detail-page widget grid does — is\n\t\t * served rather than silently broken.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedSchema() {\n\t\t\tconst s = this.schema || ''\n\t\t\treturn typeof s === 'string' ? s : (s && (s.slug || s.name || s.id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * The provider for single mode (resolved from `only`), or null\n\t\t * when the id is unknown / not eligible for the surface.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tsingleProvider() {\n\t\t\tif (!this.only) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst all = this.registryIntegrations || []\n\t\t\tfor (const provider of all) {\n\t\t\t\tif (provider.id === this.only && this.eligibleForSurface(provider)) {\n\t\t\t\t\treturn provider\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/**\n\t\t * Integrations shown as tabs: eligible for the surface, narrowed\n\t\t * by `include` when set, sorted by the registry's `order` (the\n\t\t * snapshot is already order-sorted, so we preserve that).\n\t\t *\n\t\t * @return {object[]}\n\t\t */\n\t\tvisibleProviders() {\n\t\t\tconst include = Array.isArray(this.include) ? this.include : null\n\t\t\treturn (this.registryIntegrations || []).filter((provider) => {\n\t\t\t\tif (!this.eligibleForSurface(provider)) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tif (include !== null && !include.includes(provider.id)) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The currently active provider in tabbed mode.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tactiveProvider() {\n\t\t\tconst list = this.visibleProviders\n\t\t\tfor (const provider of list) {\n\t\t\t\tif (provider.id === this.activeId) {\n\t\t\t\t\treturn provider\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn list.length > 0 ? list[0] : null\n\t\t},\n\n\t\tnoIntegrationsLabel() {\n\t\t\treturn t('nextcloud-vue', 'No integrations available yet.')\n\t\t},\n\n\t\t/**\n\t\t * The stand-in descriptor the empty state draws for an `only` leaf\n\t\t * that is not registered.\n\t\t *\n\t\t * @return {{id: string, appName: string, icon: string}}\n\t\t * @spec openspec/changes/integration-widget-mounts-leaves/specs/integration-widget-mounts-leaves/spec.md#requirement-a-single-integration-widget-mounts-a-mount-mode-leaf\n\t\t */\n\t\tabsentProvider() {\n\t\t\treturn { id: this.only, appName: this.appName, icon: this.appIcon }\n\t\t},\n\n\t\tunknownLeafLabel() {\n\t\t\treturn t('nextcloud-vue', 'This integration is not available.')\n\t\t},\n\n\t\ttabListLabel() {\n\t\t\treturn t('nextcloud-vue', 'Integrations')\n\t\t},\n\n\t\t/**\n\t\t * Label shown by the active header — the single-mode provider's\n\t\t * label, else the active tab's label, else the generic tab-list\n\t\t * label. Fed to the shared Actions menu as its `title`.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\theaderTitle() {\n\t\t\tif (this.mode === 'single') {\n\t\t\t\treturn this.singleProvider ? this.singleProvider.label : this.unknownLeafLabel\n\t\t\t}\n\t\t\treturn this.activeProvider ? this.activeProvider.label : this.tabListLabel\n\t\t},\n\n\t\t/**\n\t\t * Stable widget id for the Actions menu: the explicit `widgetId`\n\t\t * prop when set, else a slugified `headerTitle` (lowercase,\n\t\t * non-alphanumerics → '-'), falling back to 'integration'.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedWidgetId() {\n\t\t\tif (this.widgetId) {\n\t\t\t\treturn this.widgetId\n\t\t\t}\n\t\t\tconst slug = String(this.headerTitle || '')\n\t\t\t\t.toLowerCase()\n\t\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t\t.replace(/^-+|-+$/g, '')\n\t\t\treturn slug || 'integration'\n\t\t},\n\t},\n\n\twatch: {\n\t\t/**\n\t\t * Keep the active tab valid when the provider set changes (late\n\t\t * registration, surface/include change). Re-anchor to the first\n\t\t * provider if the current active id disappears.\n\t\t */\n\t\tvisibleProviders: {\n\t\t\timmediate: true,\n\t\t\thandler(list) {\n\t\t\t\tif (this.mode !== 'tabbed') {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst ids = (list || []).map((p) => p.id)\n\t\t\t\tif (ids.length === 0) {\n\t\t\t\t\tthis.activeId = ''\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif (!ids.includes(this.activeId)) {\n\t\t\t\t\tthis.activeId = ids[0]\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Whether a provider is eligible for the current surface. A\n\t\t * descriptor with a `surfaces` allowlist must include it; without\n\t\t * one, every provider is eligible (matches CnIntegrationWidgetGrid).\n\t\t *\n\t\t * @param {object} provider Normalised registry entry.\n\t\t * @return {boolean}\n\t\t */\n\t\teligibleForSurface(provider) {\n\t\t\tconst surfaces = provider.surfaces\n\t\t\tif (Array.isArray(surfaces) && surfaces.length > 0) {\n\t\t\t\treturn surfaces.includes(this.surface)\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\n\t\t/**\n\t\t * Resolve whether the backing app for a provider is available +\n\t\t * configured. See file docblock for the source order.\n\t\t *\n\t\t * @param {object} provider Normalised registry entry.\n\t\t * @return {boolean}\n\t\t */\n\t\tisAvailable(provider) {\n\t\t\treturn resolveProviderAvailability(provider, { isAppInstalled }).available\n\t\t},\n\n\t\t/**\n\t\t * The unavailable reason ('missing-app' | 'not-configured' |\n\t\t * 'unknown') for empty-state copy.\n\t\t *\n\t\t * @param {object} provider Normalised registry entry.\n\t\t * @return {string}\n\t\t */\n\t\tunavailableReason(provider) {\n\t\t\treturn resolveProviderAvailability(provider, { isAppInstalled }).reason\n\t\t},\n\n\t\t/**\n\t\t * The content component for a leaf panel. Resolved through the\n\t\t * registry composable so a lib-owned id renders THIS bundle's copy\n\t\t * of its `tab`; the entry's stored object is only used for a\n\t\t * consumer-custom id, which lives in the consumer's own bundle.\n\t\t *\n\t\t * @param {object} provider Normalised registry entry.\n\t\t * @return {object|null} Vue component, or null.\n\t\t */\n\t\tleafComponent(provider) {\n\t\t\tif (!provider || typeof provider.id !== 'string') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst local = typeof this.resolveRegistryTab === 'function'\n\t\t\t\t? this.resolveRegistryTab(provider.id)\n\t\t\t\t: null\n\t\t\treturn local || provider.tab || null\n\t\t},\n\n\t\t/**\n\t\t * Props forwarded to a leaf's content component. Matches the\n\t\t * shared shape consumed by the bespoke `Cn<X>Tab` components.\n\t\t *\n\t\t * @param {object} provider Normalised registry entry.\n\t\t * @return {object}\n\t\t */\n\t\tleafProps(provider) {\n\t\t\treturn {\n\t\t\t\tintegrationId: provider.id,\n\t\t\t\tregister: this.resolvedRegister,\n\t\t\t\tschema: this.resolvedSchema,\n\t\t\t\tobjectId: this.objectId !== '' && this.objectId !== null && this.objectId !== undefined\n\t\t\t\t\t? String(this.objectId)\n\t\t\t\t\t: '',\n\n\t\t\t\tobjectType: this.objectType,\n\t\t\t\tapiBase: this.apiBase,\n\t\t\t\tsurface: this.leafSurface || this.surface,\n\t\t\t\t...(this.objectLabel ? { objectLabel: this.objectLabel } : {}),\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Whether a leaf is a mount-mode leaf (ADR-066): it mounts its own\n\t\t * framework into a host element instead of being a component.\n\t\t *\n\t\t * @param {object} provider The descriptor.\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/integration-widget-mounts-leaves/specs/integration-widget-mounts-leaves/spec.md#requirement-a-single-integration-widget-mounts-a-mount-mode-leaf\n\t\t */\n\t\tisMountLeaf(provider) {\n\t\t\treturn Boolean(provider)\n\t\t\t\t&& provider.renderMode === 'mount'\n\t\t\t\t&& typeof provider.mount === 'function'\n\t\t\t\t&& typeof provider.unmount === 'function'\n\t\t},\n\n\t\t/**\n\t\t * What a mount-mode leaf is mounted with: the record context, also as\n\t\t * `integrationContext`, and the record's label.\n\t\t *\n\t\t * @param {object} provider The descriptor.\n\t\t * @return {object} The mount props.\n\t\t */\n\t\tleafMountProps(provider) {\n\t\t\tconst props = this.leafProps(provider)\n\t\t\treturn {\n\t\t\t\t...props,\n\t\t\t\tintegrationContext: { register: props.register, schema: props.schema, objectId: props.objectId },\n\t\t\t}\n\t\t},\n\n\t\tselectTab(id) {\n\t\t\tthis.activeId = id\n\t\t},\n\n\t\t/**\n\t\t * Roving-tabindex keyboard nav per the ARIA tablist pattern:\n\t\t * Left/Right move + activate, Home/End jump to the ends.\n\t\t *\n\t\t * @param {KeyboardEvent} event Key event.\n\t\t * @param {number} index Index of the focused tab.\n\t\t * @return {void}\n\t\t */\n\t\tonTabKeydown(event, index) {\n\t\t\tconst list = this.visibleProviders\n\t\t\tif (list.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlet next = -1\n\t\t\tif (event.key === 'ArrowRight' || event.key === 'ArrowDown') {\n\t\t\t\tnext = (index + 1) % list.length\n\t\t\t} else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {\n\t\t\t\tnext = (index - 1 + list.length) % list.length\n\t\t\t} else if (event.key === 'Home') {\n\t\t\t\tnext = 0\n\t\t\t} else if (event.key === 'End') {\n\t\t\t\tnext = list.length - 1\n\t\t\t}\n\t\t\tif (next === -1) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tconst provider = list[next]\n\t\t\tthis.activeId = provider.id\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tconst el = this.$el && this.$el.querySelector(`#${this.tabId(provider)}`)\n\t\t\t\tif (el && typeof el.focus === 'function') {\n\t\t\t\t\tel.focus()\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\ttabId(provider) {\n\t\t\treturn `cn-iw-tab-${provider.id}`\n\t\t},\n\n\t\tpanelId(provider) {\n\t\t\treturn `cn-iw-panel-${provider.id}`\n\t\t},\n\n\t\t/**\n\t\t * Accent tint for the active tab — a left border + subtle bg in\n\t\t * the app's brand colour. Inactive tabs get no accent var.\n\t\t *\n\t\t * @param {object} provider Normalised registry entry.\n\t\t * @return {object} Style object.\n\t\t */\n\t\ttabAccentStyle(provider) {\n\t\t\tif (provider.id !== this.activeId || !provider.accentColor) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\treturn { '--cn-iw-accent': provider.accentColor }\n\t\t},\n\n\t\t/**\n\t\t * Accent tint for the single-mode header.\n\t\t *\n\t\t * @param {object} provider Normalised registry entry.\n\t\t * @return {object} Style object.\n\t\t */\n\t\taccentStyle(provider) {\n\t\t\tif (!provider.accentColor) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\treturn { '--cn-iw-accent': provider.accentColor }\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-integration-widget {\n\twidth: 100%;\n\tdisplay: flex;\n\tflex-direction: column;\n\tmin-height: 0;\n}\n\n.cn-integration-widget__tabbar {\n\tdisplay: flex;\n\talign-items: flex-end;\n\tgap: 8px;\n\tborder-bottom: 1px solid var(--color-border);\n\tmargin-bottom: 12px;\n}\n\n.cn-integration-widget__tabs {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 2px;\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-integration-widget__tab {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 8px 12px;\n\tbackground: transparent;\n\tborder: none;\n\tborder-bottom: 2px solid transparent;\n\tborder-radius: var(--border-radius) var(--border-radius) 0 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.95em;\n\tcursor: pointer;\n}\n\n.cn-integration-widget__tab:hover {\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n}\n\n.cn-integration-widget__tab:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: -2px;\n}\n\n.cn-integration-widget__tab--active {\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n\tborder-bottom-color: var(--cn-iw-accent, var(--color-primary-element));\n}\n\n.cn-integration-widget__tab-icon {\n\tcolor: var(--cn-iw-accent, currentColor);\n\tflex-shrink: 0;\n}\n\n.cn-integration-widget__tab-label {\n\twhite-space: nowrap;\n}\n\n.cn-integration-widget__header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 4px;\n\tmargin-bottom: 8px;\n\tborder-bottom: 2px solid var(--cn-iw-accent, var(--color-border));\n}\n\n.cn-integration-widget__header-icon {\n\tcolor: var(--cn-iw-accent, var(--color-main-text));\n\tflex-shrink: 0;\n}\n\n.cn-integration-widget__header-label {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-integration-widget__header-actions {\n\tmargin-inline-start: auto;\n\tflex-shrink: 0;\n}\n\n.cn-integration-widget__panel {\n\tflex: 1;\n\tmin-height: 0;\n}\n\n.cn-integration-widget__panel:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: -2px;\n}\n</style>\n"],"names":["CnIcon","CnIntegrationWidgetEmpty","CnActionsMenu","CnLeafMountHost","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnIntegrationWidget.vue2.js","sources":["../../../../src/components/CnIntegrationWidget/CnIntegrationWidget.vue"],"sourcesContent":["<!--\n CnIntegrationWidget — tabbed, app-faithful integration widget for\n OpenRegister object detail pages and dashboards.\n\n Supersedes the generic Phase E pair (CnIntegrationCard /\n CnIntegrationWidgetGrid) which rendered one one-size surface that\n erased each integrated app's visual identity. This widget gives every\n integration its own tab (app icon + label + accent), composing the\n existing bespoke per-leaf content component (`provider.tab`), PLUS a\n single-integration mode for manifest multi-placement.\n\n TWO MODES\n ---------\n - Tabbed (default): a tab strip with one tab per integration available\n for `surface` (optionally narrowed by `include`), sorted by `.order`.\n The active tab renders that leaf's content. Unavailable integrations\n still get a tab; clicking shows an NcEmptyContent set-up state.\n - Single (`:only=\"deck\"`): NO tab strip — just one integration's\n content under a compact header (app icon + label). This is what an\n app manifest places several times on one detail page (ADR-024).\n\n AVAILABILITY (Phase J-B)\n ------------------------\n Per-integration availability is sourced, in order:\n 1. descriptor `available` (boolean) when the descriptor carries it,\n 2. else the OCS capability\n `openregister.integrations.providers[].available`\n (getCapabilities()), distinguishing missing-app from\n not-configured via a `reason` field when present,\n 3. else `isAppInstalled(requiredApp)` (OC.appswebroots / capabilities).\n When unavailable, the leaf renders NcEmptyContent: app icon, a\n \"{App} not available\" / \"not configured\" name, a short description, and\n an NcButton linking to the leaf's setup docs\n (`https://openregister.conduction.nl/docs/Integrations/{id}/`).\n\n DESCRIPTOR CONTRACT (filled by the per-app waves)\n -------------------------------------------------\n In addition to the registry's existing fields, the widget reads:\n - icon (MDI name string) — the app icon, rendered via CnIcon.\n - accentColor (hex) — per-app brand tint for the active tab / header.\n - appName (string) — backing-app display name for empty copy\n (defaults to `label`).\n - docsUrl (string) — setup-docs link (defaults to the\n openregister.conduction.nl path derived from `id`).\n - surfaces (string[]) — optional surface allowlist (eligible\n everywhere when omitted).\n These are normalised in `src/integrations/registry.js`; the per-app\n fidelity waves refine accentColor/appName per leaf.\n\n MANIFEST PLACEMENT (ADR-024)\n ----------------------------\n An app manifest references this component by name and passes config as\n props, e.g. to place two single-integration widgets on a detail page:\n\n \"widgets\": [\n { \"component\": \"CnIntegrationWidget\",\n \"props\": { \"only\": \"deck\", \"surface\": \"detail-page\" } },\n { \"component\": \"CnIntegrationWidget\",\n \"props\": { \"only\": \"calendar\", \"surface\": \"detail-page\" } }\n ]\n\n The host wires `register` / `schema` / `objectId` from the object\n context. Omit `only` (and optionally pass `include`) for the tabbed\n surface.\n\n See ADR-019 (registry), ADR-022 (sidebar tab contract), ADR-024\n (manifest widget placement), ADR-004 (component composition).\n\n @example\n Tabbed, all detail-page integrations:\n <CnIntegrationWidget\n register=\"decidesk\"\n schema=\"meeting\"\n object-id=\"obj-1\" />\n\n @example\n Single-integration, manifest-placeable:\n <CnIntegrationWidget\n only=\"deck\"\n register=\"decidesk\"\n schema=\"meeting\"\n object-id=\"obj-1\" />\n-->\n<template>\n\t<div class=\"cn-integration-widget\" :data-surface=\"surface\" :data-mode=\"mode\">\n\t\t<!-- SINGLE MODE: one integration, no tab strip. -->\n\t\t<template v-if=\"mode === 'single'\">\n\t\t\t<div v-if=\"singleProvider\" class=\"cn-integration-widget__single\">\n\t\t\t\t<header class=\"cn-integration-widget__header\" :style=\"accentStyle(singleProvider)\">\n\t\t\t\t\t<CnIcon\n\t\t\t\t\t\tv-if=\"singleProvider.icon\"\n\t\t\t\t\t\t:name=\"singleProvider.icon\"\n\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\tclass=\"cn-integration-widget__header-icon\" />\n\t\t\t\t\t<span class=\"cn-integration-widget__header-label\">{{ singleProvider.label }}</span>\n\t\t\t\t\t<div class=\"cn-integration-widget__header-actions\">\n\t\t\t\t\t\t<CnActionsMenu\n\t\t\t\t\t\t\t:documentationUrl=\"documentationUrl\"\n\t\t\t\t\t\t\t:widgetId=\"resolvedWidgetId\"\n\t\t\t\t\t\t\t:title=\"singleProvider.label\"\n\t\t\t\t\t\t\t:surface=\"`widget:${resolvedWidgetId}`\"\n\t\t\t\t\t\t\ttestidBase=\"cn-integration-widget\" />\n\t\t\t\t\t</div>\n\t\t\t\t</header>\n\t\t\t\t<div class=\"cn-integration-widget__panel\">\n\t\t\t\t\t<CnIntegrationWidgetEmpty\n\t\t\t\t\t\tv-if=\"!isAvailable(singleProvider)\"\n\t\t\t\t\t\t:provider=\"singleProvider\"\n\t\t\t\t\t\t:reason=\"unavailableReason(singleProvider)\" />\n\t\t\t\t\t<CnLeafMountHost\n\t\t\t\t\t\tv-else-if=\"isMountLeaf(singleProvider)\"\n\t\t\t\t\t\t:provider=\"singleProvider\"\n\t\t\t\t\t\t:mountProps=\"leafMountProps(singleProvider)\" />\n\t\t\t\t\t<component\n\t\t\t\t\t\t:is=\"leafComponent(singleProvider)\"\n\t\t\t\t\t\tv-else\n\t\t\t\t\t\tv-bind=\"leafProps(singleProvider)\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- The leaf is not registered: its app is absent or did not load. -->\n\t\t\t<CnIntegrationWidgetEmpty\n\t\t\t\tv-else-if=\"appName\"\n\t\t\t\t:provider=\"absentProvider\"\n\t\t\t\treason=\"missing-app\"\n\t\t\t\tdata-testid=\"cn-integration-widget-absent\" />\n\t\t\t<NcEmptyContent v-else :name=\"unknownLeafLabel\" />\n\t\t</template>\n\n\t\t<!-- TABBED MODE: tab strip + active panel. -->\n\t\t<template v-else>\n\t\t\t<NcEmptyContent\n\t\t\t\tv-if=\"visibleProviders.length === 0\"\n\t\t\t\t:name=\"noIntegrationsLabel\" />\n\t\t\t<template v-else>\n\t\t\t\t<div class=\"cn-integration-widget__tabbar\">\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"cn-integration-widget__tabs\"\n\t\t\t\t\t\trole=\"tablist\"\n\t\t\t\t\t\t:aria-label=\"tabListLabel\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tv-for=\"(provider, idx) in visibleProviders\"\n\t\t\t\t\t\t\t:id=\"tabId(provider)\"\n\t\t\t\t\t\t\t:key=\"provider.id\"\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t\t\tclass=\"cn-integration-widget__tab\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-integration-widget__tab--active': provider.id === activeId }\"\n\t\t\t\t\t\t\t:style=\"tabAccentStyle(provider)\"\n\t\t\t\t\t\t\t:aria-selected=\"provider.id === activeId ? 'true' : 'false'\"\n\t\t\t\t\t\t\t:tabindex=\"provider.id === activeId ? 0 : -1\"\n\t\t\t\t\t\t\t:aria-controls=\"panelId(provider)\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-integration-widget-tab-${provider.id}`\"\n\t\t\t\t\t\t\t@click=\"selectTab(provider.id)\"\n\t\t\t\t\t\t\t@keydown=\"onTabKeydown($event, idx)\">\n\t\t\t\t\t\t\t<CnIcon\n\t\t\t\t\t\t\t\tv-if=\"provider.icon\"\n\t\t\t\t\t\t\t\t:name=\"provider.icon\"\n\t\t\t\t\t\t\t\t:size=\"18\"\n\t\t\t\t\t\t\t\tclass=\"cn-integration-widget__tab-icon\" />\n\t\t\t\t\t\t\t<span class=\"cn-integration-widget__tab-label\">{{ provider.label }}</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-integration-widget__header-actions\">\n\t\t\t\t\t\t<CnActionsMenu\n\t\t\t\t\t\t\t:documentationUrl=\"documentationUrl\"\n\t\t\t\t\t\t\t:widgetId=\"resolvedWidgetId\"\n\t\t\t\t\t\t\t:title=\"headerTitle\"\n\t\t\t\t\t\t\t:surface=\"`widget:${resolvedWidgetId}`\"\n\t\t\t\t\t\t\ttestidBase=\"cn-integration-widget\" />\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\n\t\t\t\t<div\n\t\t\t\t\tv-if=\"activeProvider\"\n\t\t\t\t\t:id=\"panelId(activeProvider)\"\n\t\t\t\t\tclass=\"cn-integration-widget__panel\"\n\t\t\t\t\trole=\"tabpanel\"\n\t\t\t\t\t:aria-labelledby=\"tabId(activeProvider)\"\n\t\t\t\t\ttabindex=\"0\">\n\t\t\t\t\t<CnIntegrationWidgetEmpty\n\t\t\t\t\t\tv-if=\"!isAvailable(activeProvider)\"\n\t\t\t\t\t\t:provider=\"activeProvider\"\n\t\t\t\t\t\t:reason=\"unavailableReason(activeProvider)\" />\n\t\t\t\t\t<component\n\t\t\t\t\t\t:is=\"leafComponent(activeProvider)\"\n\t\t\t\t\t\tv-else\n\t\t\t\t\t\tv-bind=\"leafProps(activeProvider)\" />\n\t\t\t\t</div>\n\t\t\t</template>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcEmptyContent } from '@nextcloud/vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport CnLeafMountHost from '../CnLeafMountHost/CnLeafMountHost.vue'\nimport CnIntegrationWidgetEmpty from './CnIntegrationWidgetEmpty.vue'\nimport { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'\nimport { registerIntegrationIcons } from '../../integrations/icons.js'\nimport { isAppInstalled } from '../../utils/appInstalled.js'\nimport { CnActionsMenu } from '../CnActionsMenu/index.js'\nimport { resolveProviderAvailability } from './availability.js'\n\n// Ensure every descriptor's MDI icon resolves through CnIcon regardless\n// of what the host app registered at bootstrap — otherwise tabs fall\n// back to the generic HelpCircleOutline glyph. Idempotent.\nregisterIntegrationIcons()\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\n\n/**\n * CnIntegrationWidget — tabbed integration widget + single-integration\n * mode. See the file-level docblock for the contract.\n */\nexport default {\n\tname: 'CnIntegrationWidget',\n\n\tcomponents: { NcEmptyContent, CnIcon, CnIntegrationWidgetEmpty, CnActionsMenu, CnLeafMountHost },\n\n\tprops: {\n\t\t/**\n\t\t * Optional registry override (used in tests so registrations\n\t\t * don't leak across cases). Defaults to the singleton.\n\t\t */\n\t\tregistry: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister register id — a slug/uuid string, or the register\n\t\t * OBJECT a detail page injects. Accepts both for the same reason\n\t\t * `schema` does; see below.\n\t\t */\n\t\tregister: { type: [String, Object], default: '' },\n\t\t/**\n\t\t * OpenRegister schema — a slug/uuid string, or the schema OBJECT the\n\t\t * detail-page widget grid injects.\n\t\t *\n\t\t * This used to be `type: String`, and a detail page handed it the\n\t\t * resolved object instead. Vue reported the mismatch as a dev-only\n\t\t * warning (\"Expected String with value \\\"[object Object]\\\", got\n\t\t * Object\") and then passed the object through unchanged, so the leaf\n\t\t * built `/apps/openregister/api/schemas/[object Object]` and got a 404\n\t\t * — an unreadable URL rather than a named failure, and silent in\n\t\t * production where the warning is compiled out.\n\t\t *\n\t\t * `CnAuditTrailWidget` already accepted both shapes and collapsed the\n\t\t * object to its slug; this mirrors that so the two widgets cannot\n\t\t * disagree about what a detail page may hand them.\n\t\t */\n\t\tschema: { type: [String, Object], default: '' },\n\t\t/** Parent object id. Forwarded to each leaf. */\n\t\tobjectId: { type: [String, Number], default: '' },\n\t\t/**\n\t\t * Single-integration mode: render ONLY this leaf id with no tab\n\t\t * strip. The manifest multi-placement entry point (ADR-024).\n\t\t */\n\t\tonly: { type: String, default: '' },\n\t\t/**\n\t\t * Rendering surface — filters integrations by `surfaces.includes()`\n\t\t * when a descriptor declares a `surfaces` allowlist.\n\t\t */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (s) => VALID_SURFACES.includes(s),\n\t\t},\n\n\t\t/**\n\t\t * Explicit allowlist of leaf ids to show as tabs. When omitted,\n\t\t * all registered integrations eligible for `surface` are shown.\n\t\t *\n\t\t * @type {string[]|null}\n\t\t */\n\t\tinclude: { type: Array, default: null },\n\t\t/** Base API URL forwarded to each leaf content component. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Optional object type forwarded to each leaf content component. */\n\t\tobjectType: { type: String, default: '' },\n\t\t/**\n\t\t * Documentation link target for the header's shared Actions menu.\n\t\t * When non-empty a \"Documentation\" item opens it in a new tab;\n\t\t * empty (the default) hides the item.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocumentationUrl: { type: String, default: '' },\n\t\t/**\n\t\t * Stable id carried on the Actions-menu refresh / request-feature\n\t\t * payloads and surface string. When empty the header title is\n\t\t * slugified as a fallback (see `resolvedWidgetId`).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\twidgetId: { type: String, default: '' },\n\n\t\t/**\n\t\t * The record's display name, handed to the leaf as `objectLabel` (a\n\t\t * leaf titles its panel or its dialog with it).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tobjectLabel: { type: String, default: '' },\n\n\t\t/**\n\t\t * Extra props for the leaf, set by the placement (a manifest widget's\n\t\t * `props.placementProps`), for example the kinds a finance leaf shows. The\n\t\t * record's own identity always wins over a key of the same name.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tplacementProps: { type: Object, default: () => ({}) },\n\n\t\t/**\n\t\t * The surface the leaf itself is told it renders on, when it differs\n\t\t * from `surface` (a leaf placed in a dialog that draws its sidebar-tab\n\t\t * look: `sidebar-tab`). Empty passes `surface`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tleafSurface: { type: String, default: '' },\n\n\t\t/**\n\t\t * The app that provides the `only` leaf, named in the empty state when\n\t\t * the leaf is not registered (\"Decidiq not available\"). Without it an\n\t\t * unregistered leaf shows the plain \"not available\" line.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappName: { type: String, default: '' },\n\n\t\t/** The icon of that empty state (an MDI name). */\n\t\tappIcon: { type: String, default: '' },\n\t},\n\n\tsetup(props) {\n\t\tconst { integrations, resolveTab } = useIntegrationRegistry(props.registry || undefined)\n\t\t// `resolveTab` swaps a lib-owned entry for THIS bundle's component\n\t\t// (openregister#1958); the entry's own `tab` may belong to another\n\t\t// bundle's Vue and then renders its nested components as literal\n\t\t// unknown elements. See `leafComponent`.\n\t\treturn { registryIntegrations: integrations, resolveRegistryTab: resolveTab }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Currently selected tab id (tabbed mode only). */\n\t\t\tactiveId: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** 'single' when `only` is set, else 'tabbed'. */\n\t\tmode() {\n\t\t\treturn this.only ? 'single' : 'tabbed'\n\t\t},\n\n\t\t/**\n\t\t * The register SLUG. A register object collapses to its slug/name/id,\n\t\t * mirroring `CnAuditTrailWidget.resolvedRegister`.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedRegister() {\n\t\t\tconst r = this.register || ''\n\t\t\treturn typeof r === 'string' ? r : (r && (r.slug || r.name || r.id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * The schema SLUG. A schema object collapses to its slug/name/id.\n\t\t *\n\t\t * Leaves interpolate this straight into an OpenRegister URL, so an\n\t\t * object reaching them produces `/api/schemas/[object Object]` and a\n\t\t * 404 that names nothing. Collapsing here means every leaf receives\n\t\t * the string it already documents, and a caller that hands over the\n\t\t * whole schema object — which the detail-page widget grid does — is\n\t\t * served rather than silently broken.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedSchema() {\n\t\t\tconst s = this.schema || ''\n\t\t\treturn typeof s === 'string' ? s : (s && (s.slug || s.name || s.id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * The provider for single mode (resolved from `only`), or null\n\t\t * when the id is unknown / not eligible for the surface.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tsingleProvider() {\n\t\t\tif (!this.only) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst all = this.registryIntegrations || []\n\t\t\tfor (const provider of all) {\n\t\t\t\tif (provider.id === this.only && this.eligibleForSurface(provider)) {\n\t\t\t\t\treturn provider\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/**\n\t\t * Integrations shown as tabs: eligible for the surface, narrowed\n\t\t * by `include` when set, sorted by the registry's `order` (the\n\t\t * snapshot is already order-sorted, so we preserve that).\n\t\t *\n\t\t * @return {object[]}\n\t\t */\n\t\tvisibleProviders() {\n\t\t\tconst include = Array.isArray(this.include) ? this.include : null\n\t\t\treturn (this.registryIntegrations || []).filter((provider) => {\n\t\t\t\tif (!this.eligibleForSurface(provider)) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tif (include !== null && !include.includes(provider.id)) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The currently active provider in tabbed mode.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tactiveProvider() {\n\t\t\tconst list = this.visibleProviders\n\t\t\tfor (const provider of list) {\n\t\t\t\tif (provider.id === this.activeId) {\n\t\t\t\t\treturn provider\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn list.length > 0 ? list[0] : null\n\t\t},\n\n\t\tnoIntegrationsLabel() {\n\t\t\treturn t('nextcloud-vue', 'No integrations available yet.')\n\t\t},\n\n\t\t/**\n\t\t * The stand-in descriptor the empty state draws for an `only` leaf\n\t\t * that is not registered.\n\t\t *\n\t\t * @return {{id: string, appName: string, icon: string}}\n\t\t * @spec openspec/changes/integration-widget-mounts-leaves/specs/integration-widget-mounts-leaves/spec.md#requirement-a-single-integration-widget-mounts-a-mount-mode-leaf\n\t\t */\n\t\tabsentProvider() {\n\t\t\treturn { id: this.only, appName: this.appName, icon: this.appIcon }\n\t\t},\n\n\t\tunknownLeafLabel() {\n\t\t\treturn t('nextcloud-vue', 'This integration is not available.')\n\t\t},\n\n\t\ttabListLabel() {\n\t\t\treturn t('nextcloud-vue', 'Integrations')\n\t\t},\n\n\t\t/**\n\t\t * Label shown by the active header — the single-mode provider's\n\t\t * label, else the active tab's label, else the generic tab-list\n\t\t * label. Fed to the shared Actions menu as its `title`.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\theaderTitle() {\n\t\t\tif (this.mode === 'single') {\n\t\t\t\treturn this.singleProvider ? this.singleProvider.label : this.unknownLeafLabel\n\t\t\t}\n\t\t\treturn this.activeProvider ? this.activeProvider.label : this.tabListLabel\n\t\t},\n\n\t\t/**\n\t\t * Stable widget id for the Actions menu: the explicit `widgetId`\n\t\t * prop when set, else a slugified `headerTitle` (lowercase,\n\t\t * non-alphanumerics → '-'), falling back to 'integration'.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedWidgetId() {\n\t\t\tif (this.widgetId) {\n\t\t\t\treturn this.widgetId\n\t\t\t}\n\t\t\tconst slug = String(this.headerTitle || '')\n\t\t\t\t.toLowerCase()\n\t\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t\t.replace(/^-+|-+$/g, '')\n\t\t\treturn slug || 'integration'\n\t\t},\n\t},\n\n\twatch: {\n\t\t/**\n\t\t * Keep the active tab valid when the provider set changes (late\n\t\t * registration, surface/include change). Re-anchor to the first\n\t\t * provider if the current active id disappears.\n\t\t */\n\t\tvisibleProviders: {\n\t\t\timmediate: true,\n\t\t\thandler(list) {\n\t\t\t\tif (this.mode !== 'tabbed') {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst ids = (list || []).map((p) => p.id)\n\t\t\t\tif (ids.length === 0) {\n\t\t\t\t\tthis.activeId = ''\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif (!ids.includes(this.activeId)) {\n\t\t\t\t\tthis.activeId = ids[0]\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Whether a provider is eligible for the current surface. A\n\t\t * descriptor with a `surfaces` allowlist must include it; without\n\t\t * one, every provider is eligible (matches CnIntegrationWidgetGrid).\n\t\t *\n\t\t * @param {object} provider Normalised registry entry.\n\t\t * @return {boolean}\n\t\t */\n\t\teligibleForSurface(provider) {\n\t\t\tconst surfaces = provider.surfaces\n\t\t\tif (Array.isArray(surfaces) && surfaces.length > 0) {\n\t\t\t\treturn surfaces.includes(this.surface)\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\n\t\t/**\n\t\t * Resolve whether the backing app for a provider is available +\n\t\t * configured. See file docblock for the source order.\n\t\t *\n\t\t * @param {object} provider Normalised registry entry.\n\t\t * @return {boolean}\n\t\t */\n\t\tisAvailable(provider) {\n\t\t\treturn resolveProviderAvailability(provider, { isAppInstalled }).available\n\t\t},\n\n\t\t/**\n\t\t * The unavailable reason ('missing-app' | 'not-configured' |\n\t\t * 'unknown') for empty-state copy.\n\t\t *\n\t\t * @param {object} provider Normalised registry entry.\n\t\t * @return {string}\n\t\t */\n\t\tunavailableReason(provider) {\n\t\t\treturn resolveProviderAvailability(provider, { isAppInstalled }).reason\n\t\t},\n\n\t\t/**\n\t\t * The content component for a leaf panel. Resolved through the\n\t\t * registry composable so a lib-owned id renders THIS bundle's copy\n\t\t * of its `tab`; the entry's stored object is only used for a\n\t\t * consumer-custom id, which lives in the consumer's own bundle.\n\t\t *\n\t\t * @param {object} provider Normalised registry entry.\n\t\t * @return {object|null} Vue component, or null.\n\t\t */\n\t\tleafComponent(provider) {\n\t\t\tif (!provider || typeof provider.id !== 'string') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst local = typeof this.resolveRegistryTab === 'function'\n\t\t\t\t? this.resolveRegistryTab(provider.id)\n\t\t\t\t: null\n\t\t\treturn local || provider.tab || null\n\t\t},\n\n\t\t/**\n\t\t * Props forwarded to a leaf's content component. Matches the\n\t\t * shared shape consumed by the bespoke `Cn<X>Tab` components.\n\t\t *\n\t\t * @param {object} provider Normalised registry entry.\n\t\t * @return {object}\n\t\t */\n\t\tleafProps(provider) {\n\t\t\treturn {\n\t\t\t\t...this.placementProps,\n\t\t\t\tintegrationId: provider.id,\n\t\t\t\tregister: this.resolvedRegister,\n\t\t\t\tschema: this.resolvedSchema,\n\t\t\t\tobjectId: this.objectId !== '' && this.objectId !== null && this.objectId !== undefined\n\t\t\t\t\t? String(this.objectId)\n\t\t\t\t\t: '',\n\n\t\t\t\tobjectType: this.objectType,\n\t\t\t\tapiBase: this.apiBase,\n\t\t\t\tsurface: this.leafSurface || this.surface,\n\t\t\t\t...(this.objectLabel ? { objectLabel: this.objectLabel } : {}),\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Whether a leaf is a mount-mode leaf (ADR-066): it mounts its own\n\t\t * framework into a host element instead of being a component.\n\t\t *\n\t\t * @param {object} provider The descriptor.\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/integration-widget-mounts-leaves/specs/integration-widget-mounts-leaves/spec.md#requirement-a-single-integration-widget-mounts-a-mount-mode-leaf\n\t\t */\n\t\tisMountLeaf(provider) {\n\t\t\treturn Boolean(provider)\n\t\t\t\t&& provider.renderMode === 'mount'\n\t\t\t\t&& typeof provider.mount === 'function'\n\t\t\t\t&& typeof provider.unmount === 'function'\n\t\t},\n\n\t\t/**\n\t\t * What a mount-mode leaf is mounted with: the record context, also as\n\t\t * `integrationContext`, and the record's label.\n\t\t *\n\t\t * @param {object} provider The descriptor.\n\t\t * @return {object} The mount props.\n\t\t */\n\t\tleafMountProps(provider) {\n\t\t\tconst props = this.leafProps(provider)\n\t\t\treturn {\n\t\t\t\t...props,\n\t\t\t\tintegrationContext: { register: props.register, schema: props.schema, objectId: props.objectId },\n\t\t\t}\n\t\t},\n\n\t\tselectTab(id) {\n\t\t\tthis.activeId = id\n\t\t},\n\n\t\t/**\n\t\t * Roving-tabindex keyboard nav per the ARIA tablist pattern:\n\t\t * Left/Right move + activate, Home/End jump to the ends.\n\t\t *\n\t\t * @param {KeyboardEvent} event Key event.\n\t\t * @param {number} index Index of the focused tab.\n\t\t * @return {void}\n\t\t */\n\t\tonTabKeydown(event, index) {\n\t\t\tconst list = this.visibleProviders\n\t\t\tif (list.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlet next = -1\n\t\t\tif (event.key === 'ArrowRight' || event.key === 'ArrowDown') {\n\t\t\t\tnext = (index + 1) % list.length\n\t\t\t} else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {\n\t\t\t\tnext = (index - 1 + list.length) % list.length\n\t\t\t} else if (event.key === 'Home') {\n\t\t\t\tnext = 0\n\t\t\t} else if (event.key === 'End') {\n\t\t\t\tnext = list.length - 1\n\t\t\t}\n\t\t\tif (next === -1) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tconst provider = list[next]\n\t\t\tthis.activeId = provider.id\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tconst el = this.$el && this.$el.querySelector(`#${this.tabId(provider)}`)\n\t\t\t\tif (el && typeof el.focus === 'function') {\n\t\t\t\t\tel.focus()\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\ttabId(provider) {\n\t\t\treturn `cn-iw-tab-${provider.id}`\n\t\t},\n\n\t\tpanelId(provider) {\n\t\t\treturn `cn-iw-panel-${provider.id}`\n\t\t},\n\n\t\t/**\n\t\t * Accent tint for the active tab — a left border + subtle bg in\n\t\t * the app's brand colour. Inactive tabs get no accent var.\n\t\t *\n\t\t * @param {object} provider Normalised registry entry.\n\t\t * @return {object} Style object.\n\t\t */\n\t\ttabAccentStyle(provider) {\n\t\t\tif (provider.id !== this.activeId || !provider.accentColor) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\treturn { '--cn-iw-accent': provider.accentColor }\n\t\t},\n\n\t\t/**\n\t\t * Accent tint for the single-mode header.\n\t\t *\n\t\t * @param {object} provider Normalised registry entry.\n\t\t * @return {object} Style object.\n\t\t */\n\t\taccentStyle(provider) {\n\t\t\tif (!provider.accentColor) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\treturn { '--cn-iw-accent': provider.accentColor }\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-integration-widget {\n\twidth: 100%;\n\tdisplay: flex;\n\tflex-direction: column;\n\tmin-height: 0;\n}\n\n.cn-integration-widget__tabbar {\n\tdisplay: flex;\n\talign-items: flex-end;\n\tgap: 8px;\n\tborder-bottom: 1px solid var(--color-border);\n\tmargin-bottom: 12px;\n}\n\n.cn-integration-widget__tabs {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 2px;\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-integration-widget__tab {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 8px 12px;\n\tbackground: transparent;\n\tborder: none;\n\tborder-bottom: 2px solid transparent;\n\tborder-radius: var(--border-radius) var(--border-radius) 0 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.95em;\n\tcursor: pointer;\n}\n\n.cn-integration-widget__tab:hover {\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n}\n\n.cn-integration-widget__tab:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: -2px;\n}\n\n.cn-integration-widget__tab--active {\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n\tborder-bottom-color: var(--cn-iw-accent, var(--color-primary-element));\n}\n\n.cn-integration-widget__tab-icon {\n\tcolor: var(--cn-iw-accent, currentColor);\n\tflex-shrink: 0;\n}\n\n.cn-integration-widget__tab-label {\n\twhite-space: nowrap;\n}\n\n.cn-integration-widget__header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 4px;\n\tmargin-bottom: 8px;\n\tborder-bottom: 2px solid var(--cn-iw-accent, var(--color-border));\n}\n\n.cn-integration-widget__header-icon {\n\tcolor: var(--cn-iw-accent, var(--color-main-text));\n\tflex-shrink: 0;\n}\n\n.cn-integration-widget__header-label {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-integration-widget__header-actions {\n\tmargin-inline-start: auto;\n\tflex-shrink: 0;\n}\n\n.cn-integration-widget__panel {\n\tflex: 1;\n\tmin-height: 0;\n}\n\n.cn-integration-widget__panel:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: -2px;\n}\n</style>\n"],"names":["CnIcon","CnIntegrationWidgetEmpty","CnActionsMenu","CnLeafMountHost","t"],"mappings":"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"}
|