@conduction/nextcloud-vue 2.140.0 → 2.141.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/CnAssignDialog/CnAssignDialog.vue.js +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +10 -2
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +11 -3
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +12 -0
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +57 -40
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +1 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +5 -3
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +1 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +65 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +120 -69
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +47 -9
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +3 -3
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +2 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js +10 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js +34 -26
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +2 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +2 -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 +652 -391
- package/dist/esm/utils/groupSublines.js +120 -0
- package/dist/esm/utils/groupSublines.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +974 -638
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +652 -391
- package/l10n/en.json +2 -1
- package/l10n/nl.json +2 -1
- package/package.json +1 -1
- package/src/components/CnAssignDialog/CnAssignDialog.vue +10 -2
- package/src/components/CnCellRenderer/CnCellRenderer.vue +11 -3
- package/src/components/CnChartWidget/CnChartWidget.md +1 -0
- package/src/components/CnChartWidget/CnChartWidget.vue +49 -4
- package/src/components/CnDashboardPage/CnDashboardPage.vue +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +6 -3
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +101 -3
- package/src/components/CnMapWidget/CnMapWidget.vue +47 -9
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +3 -3
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +20 -1
- package/src/components/CnRecordListWidget/CnRecordNoteRows.vue +33 -9
- package/src/css/dialog.css +13 -1
- package/src/css/look-board-detail.css +12 -9
- package/src/css/look-board-index.css +90 -7
- package/src/css/look-board.css +92 -0
- package/src/css/table.css +12 -0
- package/src/utils/groupSublines.js +125 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnCellRenderer.vue3.js","sources":["../../../../src/components/CnCellRenderer/CnCellRenderer.vue?vue&type=template&id=2a3bc230&lang.js"],"sourcesContent":["<template>\n\t<span class=\"cn-cell-renderer\" :class=\"cellClass\">\n\t\t<!-- OpenRegister's withheld marker ({ \"@withheld\": true, reason }),\n\t\t before every other path: a lock and \"Withheld\", the reason as the\n\t\t tooltip. The icon is decoration; the text carries the meaning. -->\n\t\t<span v-if=\"withheld\"\n\t\t\tclass=\"cn-cell-renderer__withheld\"\n\t\t\t:title=\"withheldHint\"\n\t\t\tdata-testid=\"cn-cell-withheld\">\n\t\t\t<Lock :size=\"14\" class=\"cn-cell-renderer__withheld-icon\" aria-hidden=\"true\" />\n\t\t\t<span class=\"cn-cell-renderer__withheld-text\">{{ withheldText }}</span>\n\t\t</span>\n\n\t\t<!-- Consumer-registered cell widget (cnCellWidgets[column.widget]) -->\n\t\t<component\n\t\t\t:is=\"widgetComponent\"\n\t\t\tv-else-if=\"widgetComponent\"\n\t\t\t:value=\"value\"\n\t\t\t:row=\"row\"\n\t\t\t:property=\"property\"\n\t\t\t:formatted=\"formattedValue\"\n\t\t\tv-bind=\"widgetProps\" />\n\n\t\t<!-- Built-in \"group\" widget: a Nextcloud group id (or a list of them)\n\t\t shown as the group's display name, cached per id. Used on its own\n\t\t for a property marked `referenceType: nextcloud-group` /\n\t\t `format: nc-group` unless the column has a formatter. -->\n\t\t<template v-else-if=\"isGroupCell\">\n\t\t\t<span v-if=\"!groupIds.length\" class=\"cn-cell-renderer__dash\">—</span>\n\t\t\t<span v-else class=\"cn-cell-renderer__groups\">\n\t\t\t\t<template v-for=\"(gid, i) in groupIds\" :key=\"gid\">\n\t\t\t\t\t<span v-if=\"i > 0\">, </span>\n\t\t\t\t\t<CnGroupNameCell :gid=\"gid\" />\n\t\t\t\t</template>\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"actions\" widget: row actions as inline buttons (\"Aan zaak\n\t\t koppelen\", \"Nieuwe zaak\"). The page resolves the buttons\n\t\t (widgetProps.items) and runs a click (widgetProps.run). -->\n\t\t<template v-else-if=\"widget === 'actions'\">\n\t\t\t<span class=\"cn-cell-renderer__actions\" data-testid=\"cn-cell-actions\">\n\t\t\t\t<NcButton\n\t\t\t\t\tv-for=\"item in actionItems\"\n\t\t\t\t\t:key=\"item.key\"\n\t\t\t\t\t:variant=\"item.variant\"\n\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t:disabled=\"item.disabled\"\n\t\t\t\t\t:data-testid=\"`cn-cell-action-${item.key}`\"\n\t\t\t\t\t@click.stop=\"runAction(item)\">\n\t\t\t\t\t{{ item.label }}\n\t\t\t\t</NcButton>\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"label\" widget: the value's label (an enum's translated\n\t\t label) as plain text, where a list wants words and no pill. -->\n\t\t<template v-else-if=\"widget === 'label'\">\n\t\t\t<span v-if=\"hasValue\" :title=\"rawTitle\">{{ shownLabel }}</span>\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"badge\" widget: renders the value as a status pill. An enum\n\t\t value shows its label (x-enum-labels, translated) unless a formatter\n\t\t shapes it; the colour stays keyed on the raw value. -->\n\t\t<template v-else-if=\"widget === 'badge'\">\n\t\t\t<CnStatusBadge v-if=\"hasValue\"\n\t\t\t\t:label=\"shownLabel\"\n\t\t\t\t:variant=\"badgeVariant\"\n\t\t\t\t:colorMap=\"badgeColorMap\"\n\t\t\t\t:colorKey=\"badgeColorKey\"\n\t\t\t\t:title=\"badgeTitle\">\n\t\t\t\t<template v-if=\"badgeLook.icon\" #icon>\n\t\t\t\t\t<CnIcon :name=\"badgeLook.icon\" :size=\"14\" class=\"cn-cell-renderer__badge-icon\" />\n\t\t\t\t</template>\n\t\t\t</CnStatusBadge>\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"fkResolve\" widget — resolves a reference uuid to the related\n\t\t object's display label via the object store (per-schema caching).\n\t\t Config: widgetProps { register, schema, labelField, matchField?,\n\t\t refField?, labelTemplate?, labelPath? }, or schemaFrom { field,\n\t\t map } to read the schema off the row. -->\n\t\t<template v-else-if=\"widget === 'fkResolve'\">\n\t\t\t<CnFkResolveCell\n\t\t\t\tv-if=\"hasValue || fkRefValue !== null\"\n\t\t\t\t:value=\"value\"\n\t\t\t\t:register=\"(widgetProps && widgetProps.register) || ''\"\n\t\t\t\t:schema=\"fkSchema\"\n\t\t\t\t:labelField=\"(widgetProps && widgetProps.labelField) || 'name'\"\n\t\t\t\t:matchField=\"(widgetProps && widgetProps.matchField) || ''\"\n\t\t\t\t:refValue=\"fkRefValue\"\n\t\t\t\t:labelTemplate=\"(widgetProps && widgetProps.labelTemplate) || ''\"\n\t\t\t\t:labelPath=\"(widgetProps && widgetProps.labelPath) || ''\" />\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"refLabel\" widget — shows the label of the referenced object(s)\n\t\t from the batch CnIndexPage resolved for the column\n\t\t (widgetProps { labels: { id: label|null }, route? }). An id that is not\n\t\t in `labels` yet shows a placeholder; one that resolved to null shows the\n\t\t id in mono. With widgetProps.route (a manifest page id) each label links\n\t\t to that page for the referenced id. -->\n\t\t<template v-else-if=\"widget === 'refLabel'\">\n\t\t\t<span v-if=\"!refLabelItems.length\" class=\"cn-cell-renderer__dash\">—</span>\n\t\t\t<span v-else class=\"cn-cell-renderer__ref-labels\" data-testid=\"cn-cell-ref-labels\">\n\t\t\t\t<template v-for=\"(item, i) in refLabelItems\" :key=\"item.id\">\n\t\t\t\t\t<span v-if=\"i > 0\">, </span>\n\t\t\t\t\t<component\n\t\t\t\t\t\t:is=\"item.to ? 'router-link' : 'span'\"\n\t\t\t\t\t\t:to=\"item.to || undefined\"\n\t\t\t\t\t\t:class=\"[item.to ? 'cn-cell-renderer__link' : '', item.state === 'missing' ? 'cn-cell-renderer__mono' : '']\"\n\t\t\t\t\t\t:title=\"item.state === 'label' ? item.id : undefined\">\n\t\t\t\t\t\t{{ item.text }}\n\t\t\t\t\t</component>\n\t\t\t\t</template>\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"link\" widget — renders the (possibly formatter-shaped) value\n\t\t as a router-link (when widgetProps.route is a manifest page id, or\n\t\t widgetProps.routeMap picks one per row by widgetProps.routeField) or\n\t\t an external anchor (when widgetProps.href is set). Falls back to\n\t\t plain text + a once-per-session console.warn when neither resolves. -->\n\t\t<template v-else-if=\"widget === 'link'\">\n\t\t\t<router-link\n\t\t\t\tv-if=\"linkRoute\"\n\t\t\t\t:to=\"linkRoute\"\n\t\t\t\tclass=\"cn-cell-renderer__link\">\n\t\t\t\t{{ labelledValue }}\n\t\t\t</router-link>\n\t\t\t<a\n\t\t\t\tv-else-if=\"linkHref\"\n\t\t\t\t:href=\"linkHref\"\n\t\t\t\t:target=\"linkTarget\"\n\t\t\t\t:rel=\"linkTarget ? 'noopener' : null\"\n\t\t\t\tclass=\"cn-cell-renderer__link\">\n\t\t\t\t{{ labelledValue }}\n\t\t\t</a>\n\t\t\t<span v-else :title=\"rawTitle\">{{ labelledValue }}</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"avatar\" widget: a person as an avatar with the name beside\n\t\t it. A user id (widgetProps.userField, or widgetProps.user for a cell\n\t\t that holds the id itself) gets the Nextcloud avatar; a free name\n\t\t gets its initials. The picture is decoration: the name is the text. -->\n\t\t<!-- The avatar of a REFERENCED object (widgetProps.resolve\n\t\t { register, schema, labelField }): the cell holds a uuid, the name\n\t\t is resolved as an fkResolve cell resolves it, and its initials sit\n\t\t beside it. -->\n\t\t<template v-else-if=\"widget === 'avatar' && avatarResolve\">\n\t\t\t<CnFkResolveCell\n\t\t\t\tv-if=\"hasValue && typeof value !== 'object'\"\n\t\t\t\tv-slot=\"{ label, resolved }\"\n\t\t\t\t:value=\"value\"\n\t\t\t\t:register=\"avatarResolve.register\"\n\t\t\t\t:schema=\"avatarResolve.schema\"\n\t\t\t\t:labelField=\"avatarResolve.labelField || 'name'\">\n\t\t\t\t<span class=\"cn-cell-renderer__avatar\" data-testid=\"cn-cell-avatar-resolved\">\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-cell-renderer__avatar-initials\"\n\t\t\t\t\t\t:style=\"{ width: avatarSize + 'px', height: avatarSize + 'px' }\"\n\t\t\t\t\t\taria-hidden=\"true\">{{ resolved ? initialsOf(label) : '' }}</span>\n\t\t\t\t\t<span class=\"cn-cell-renderer__avatar-text\">\n\t\t\t\t\t\t<span class=\"cn-cell-renderer__avatar-name\" :title=\"label\">{{ label }}</span>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-if=\"secondary\"\n\t\t\t\t\t\t\tclass=\"cn-cell-renderer__avatar-secondary\"\n\t\t\t\t\t\t\tdata-testid=\"cn-cell-secondary\">{{ secondary }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t</span>\n\t\t\t</CnFkResolveCell>\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\t\t<template v-else-if=\"widget === 'avatar'\">\n\t\t\t<span v-if=\"avatarName || avatarUserId\" class=\"cn-cell-renderer__avatar\">\n\t\t\t\t<span v-if=\"avatarUserId\" class=\"cn-cell-renderer__avatar-picture\" aria-hidden=\"true\">\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t:user=\"avatarUserId\"\n\t\t\t\t\t\t:displayName=\"avatarName || avatarUserId\"\n\t\t\t\t\t\t:size=\"avatarSize\"\n\t\t\t\t\t\t:disableMenu=\"true\"\n\t\t\t\t\t\t:disableTooltip=\"true\" />\n\t\t\t\t</span>\n\t\t\t\t<span\n\t\t\t\t\tv-else\n\t\t\t\t\tclass=\"cn-cell-renderer__avatar-initials\"\n\t\t\t\t\t:style=\"{ width: avatarSize + 'px', height: avatarSize + 'px' }\"\n\t\t\t\t\taria-hidden=\"true\">{{ avatarInitials }}</span>\n\t\t\t\t<span class=\"cn-cell-renderer__avatar-text\">\n\t\t\t\t\t<span class=\"cn-cell-renderer__avatar-name\" :title=\"avatarName || avatarUserId\">{{ avatarName || avatarUserId }}</span>\n\t\t\t\t\t<!-- The column's secondary line (e-mail), under the name and beside the picture. -->\n\t\t\t\t\t<span\n\t\t\t\t\t\tv-if=\"secondary\"\n\t\t\t\t\t\tclass=\"cn-cell-renderer__avatar-secondary\"\n\t\t\t\t\t\tdata-testid=\"cn-cell-secondary\">{{ secondary }}</span>\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"age\" widget: how long something has waited (\"4 uur\",\n\t\t \"3 dagen\"), coloured by widgetProps.variantWhen rules on the days\n\t\t the cell shows. The full date is the tooltip. -->\n\t\t<template v-else-if=\"widget === 'age'\">\n\t\t\t<time\n\t\t\t\tv-if=\"dateTimestamp\"\n\t\t\t\tclass=\"cn-cell-renderer__age\"\n\t\t\t\t:class=\"ageVariant ? 'cn-cell-renderer__age--' + ageVariant : null\"\n\t\t\t\t:datetime=\"dateTimestamp.toISOString()\"\n\t\t\t\t:title=\"absoluteDateLabel\">{{ ageLabel }}</time>\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"date\" widget: a date whose colour follows\n\t\t widgetProps.variantWhen, rules on the number of days until it\n\t\t (0 = today, negative = overdue). -->\n\t\t<template v-else-if=\"widget === 'date'\">\n\t\t\t<!-- `widgetProps.due`: the due text with its tone (\"3 dagen te laat\",\n\t\t\t \"Vandaag\", the date, or the empty text). -->\n\t\t\t<template v-if=\"dueResult\">\n\t\t\t\t<time\n\t\t\t\t\tv-if=\"dateTimestamp\"\n\t\t\t\t\tclass=\"cn-cell-renderer__date\"\n\t\t\t\t\t:class=\"dueClass\"\n\t\t\t\t\t:datetime=\"dateTimestamp.toISOString()\"\n\t\t\t\t\t:title=\"absoluteDateLabel\"\n\t\t\t\t\tdata-testid=\"cn-cell-renderer-due\">{{ dueResult.text }}</time>\n\t\t\t\t<span v-else class=\"cn-cell-renderer__date cn-cell-renderer__due--empty\" data-testid=\"cn-cell-renderer-due\">{{ dueResult.text }}</span>\n\t\t\t</template>\n\t\t\t<!-- `widgetProps.layout: \"block\"`: a date tile (day over short month)\n\t\t\t beside the weekday and the time range to `widgetProps.endField`. -->\n\t\t\t<time\n\t\t\t\tv-else-if=\"dateTimestamp && dateBlock\"\n\t\t\t\tclass=\"cn-cell-renderer__date-block\"\n\t\t\t\t:datetime=\"dateTimestamp.toISOString()\"\n\t\t\t\t:title=\"absoluteDateLabel\"\n\t\t\t\tdata-testid=\"cn-cell-date-block\">\n\t\t\t\t<span class=\"cn-cell-renderer__date-tile\">\n\t\t\t\t\t<strong class=\"cn-cell-renderer__date-day\">{{ dateBlock.day }}</strong>\n\t\t\t\t\t<span class=\"cn-cell-renderer__date-month\">{{ dateBlock.month }}</span>\n\t\t\t\t</span>\n\t\t\t\t<span class=\"cn-cell-renderer__date-lines\">\n\t\t\t\t\t<span class=\"cn-cell-renderer__date-weekday\">{{ dateBlock.weekday }}</span>\n\t\t\t\t\t<span v-if=\"dateBlock.time\" class=\"cn-cell-renderer__date-time\">{{ dateBlock.time }}</span>\n\t\t\t\t</span>\n\t\t\t</time>\n\t\t\t<time\n\t\t\t\tv-else-if=\"dateTimestamp\"\n\t\t\t\tclass=\"cn-cell-renderer__date\"\n\t\t\t\t:class=\"dateVariant ? 'cn-cell-renderer__date--' + dateVariant : null\"\n\t\t\t\t:datetime=\"dateTimestamp.toISOString()\"\n\t\t\t\t:title=\"isRelativeDate ? absoluteDateLabel : rawTitle\">{{ dateLabel }}</time>\n\t\t\t<span v-else-if=\"!dateStateText\" class=\"cn-cell-renderer__dash\">—</span>\n\t\t\t<!-- `widgetProps.pastText` / `otherText`: a state beside the date (\"Ended\", \"Active\"). -->\n\t\t\t<span v-if=\"dateStateText\"\n\t\t\t\tclass=\"cn-cell-renderer__date-state\"\n\t\t\t\t:class=\"{ 'cn-cell-renderer__date-state--past': dateIsPast }\"\n\t\t\t\tdata-testid=\"cn-cell-date-state\">{{ dateStateText }}</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"number\" widget: the value with its unit\n\t\t (widgetProps.unit { one, other }) and, when widgetProps.flagWhen\n\t\t holds on the row, a chip with text beside it. An empty value is an\n\t\t empty cell, never 0. -->\n\t\t<template v-else-if=\"widget === 'number'\">\n\t\t\t<span v-if=\"numberLabel !== ''\"\n\t\t\t\tclass=\"cn-cell-renderer__number\"\n\t\t\t\t:class=\"numberFlag ? 'cn-cell-renderer__number--' + numberFlag.variant : null\"\n\t\t\t\tdata-testid=\"cn-cell-number\">\n\t\t\t\t{{ numberLabel }}\n\t\t\t\t<CnStatusBadge v-if=\"numberFlag\"\n\t\t\t\t\tclass=\"cn-cell-renderer__number-flag\"\n\t\t\t\t\t:label=\"numberFlag.text\"\n\t\t\t\t\t:variant=\"numberFlag.variant\"\n\t\t\t\t\tsize=\"small\"\n\t\t\t\t\tdata-testid=\"cn-cell-number-flag\" />\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"progress\" widget: a bar with the percentage beside it,\n\t\t toned by widgetProps.thresholds, and an optional age badge\n\t\t (\"Out of date 12 days\") from widgetProps.badge. -->\n\t\t<template v-else-if=\"widget === 'progress'\">\n\t\t\t<span v-if=\"progressPercent === null\" class=\"cn-cell-renderer__dash\">—</span>\n\t\t\t<span v-else class=\"cn-cell-renderer__progress\">\n\t\t\t\t<span class=\"cn-cell-renderer__progress-line\">\n\t\t\t\t\t<span class=\"cn-cell-renderer__progress-bar\" aria-hidden=\"true\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"cn-cell-renderer__progress-fill\"\n\t\t\t\t\t\t\t:class=\"'cn-cell-renderer__progress-fill--' + progressTone\"\n\t\t\t\t\t\t\t:style=\"{ width: progressPercent + '%' }\" />\n\t\t\t\t\t</span>\n\t\t\t\t\t<strong\n\t\t\t\t\t\tclass=\"cn-cell-renderer__progress-value\"\n\t\t\t\t\t\t:class=\"'cn-cell-renderer__progress-value--' + progressTone\">{{ progressPercent }}%</strong>\n\t\t\t\t</span>\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"progressBadge\"\n\t\t\t\t\tclass=\"cn-cell-renderer__progress-badge\"\n\t\t\t\t\t:class=\"'cn-cell-renderer__progress-badge--' + progressBadge.tone\">{{ progressBadge.label }}</span>\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"switch\" widget: an on/off switch with the value as its\n\t\t label. widgetProps { checked(row) => boolean, onToggle(row, on) =>\n\t\t Promise }. The switch shows the new state at once and returns to\n\t\t the old one when onToggle rejects. Clicks stay in the cell, so\n\t\t toggling never opens the row. -->\n\t\t<template v-else-if=\"widget === 'switch'\">\n\t\t\t<span class=\"cn-cell-renderer__switch\"\n\t\t\t\tdata-testid=\"cn-cell-switch\"\n\t\t\t\t@click.stop\n\t\t\t\t@keydown.stop>\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t:modelValue=\"switchChecked\"\n\t\t\t\t\t:disabled=\"switchBusy || typeof (widgetProps && widgetProps.onToggle) !== 'function'\"\n\t\t\t\t\t@update:modelValue=\"onSwitchToggle\">\n\t\t\t\t\t{{ hasValue ? labelledValue : '' }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"iconLabel\" widget: an icon before the value. widgetProps\n\t\t { icon }: a component or an icon name, or a function of the row\n\t\t returning one. The icon is decorative; the text carries the meaning. -->\n\t\t<template v-else-if=\"widget === 'iconLabel'\">\n\t\t\t<span class=\"cn-cell-renderer__icon-label\" data-testid=\"cn-cell-icon-label\">\n\t\t\t\t<CnIcon v-if=\"typeof iconLabelIcon === 'string' && iconLabelIcon\"\n\t\t\t\t\t:name=\"iconLabelIcon\"\n\t\t\t\t\t:size=\"16\"\n\t\t\t\t\taria-hidden=\"true\" />\n\t\t\t\t<component :is=\"iconLabelIcon\"\n\t\t\t\t\tv-else-if=\"iconLabelIcon\"\n\t\t\t\t\t:size=\"16\"\n\t\t\t\t\taria-hidden=\"true\" />\n\t\t\t\t<span v-if=\"hasValue\">{{ labelledValue }}</span>\n\t\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Explicit column formatter — overrides the type-aware paths below -->\n\t\t<template v-else-if=\"hasFormatter\">\n\t\t\t<span :title=\"rawTitle\">{{ formattedValue }}</span>\n\t\t</template>\n\n\t\t<!-- Declarative swatch — a colour dot (from a sibling row field) + text -->\n\t\t<template v-else-if=\"isSwatch\">\n\t\t\t<span class=\"cn-cell-renderer__swatch-wrap\">\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"swatchColor\"\n\t\t\t\t\tclass=\"cn-cell-renderer__swatch-dot\"\n\t\t\t\t\t:style=\"{ backgroundColor: swatchColor }\"\n\t\t\t\t\taria-hidden=\"true\" />\n\t\t\t\t<span v-if=\"hasValue\" :title=\"rawTitle\">{{ labelledValue }}</span>\n\t\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Declarative built-in format (currency / duration / number / percent) -->\n\t\t<template v-else-if=\"hasBuiltinFormat\">\n\t\t\t<span :title=\"rawTitle\" class=\"cn-cell-renderer--number\">{{ formattedValue }}</span>\n\t\t</template>\n\n\t\t<!-- Date / date-time: dynamic NcDateTime (relative time, absolute on hover) -->\n\t\t<template v-else-if=\"isDate\">\n\t\t\t<time\n\t\t\t\tv-if=\"dateTimestamp && isBoardLook\"\n\t\t\t\tclass=\"cn-cell-renderer__date\"\n\t\t\t\t:datetime=\"dateTimestamp.toISOString()\"\n\t\t\t\t:title=\"absoluteDateLabel\">{{ boardDateLabel }}</time>\n\t\t\t<NcDateTime v-else-if=\"dateTimestamp\" :timestamp=\"dateTimestamp\" />\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- URI / URL: external link -->\n\t\t<template v-else-if=\"isUri\">\n\t\t\t<a\n\t\t\t\tv-if=\"uriHref\"\n\t\t\t\t:href=\"uriHref\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\trel=\"noopener\"\n\t\t\t\tclass=\"cn-cell-renderer__link\">\n\t\t\t\t{{ formattedValue }}\n\t\t\t</a>\n\t\t\t<span v-else-if=\"hasValue\" :title=\"rawTitle\">{{ formattedValue }}</span>\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- Boolean: words when `widgetProps.trueLabel` / `falseLabel` name\n\t\t them (the value `widgetProps.pill` names as a pill), else an icon -->\n\t\t<template v-else-if=\"propertyType === 'boolean' && booleanWord !== ''\">\n\t\t\t<CnStatusBadge v-if=\"booleanPilled\" :label=\"booleanWord\" />\n\t\t\t<span v-else>{{ booleanWord }}</span>\n\t\t</template>\n\t\t<template v-else-if=\"propertyType === 'boolean'\">\n\t\t\t<CheckBold v-if=\"value\" :size=\"16\" class=\"cn-cell-renderer__icon cn-cell-renderer__icon--success\" />\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- Enum: status badge (auto-coloured from the property's optional colorMap) -->\n\t\t<template v-else-if=\"isEnum\">\n\t\t\t<CnStatusBadge v-if=\"value\"\n\t\t\t\t:label=\"enumLabel\"\n\t\t\t\t:colorKey=\"String(value)\"\n\t\t\t\t:colorMap=\"enumColorMap\" />\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- Array: comma-joined or count -->\n\t\t<template v-else-if=\"propertyType === 'array'\">\n\t\t\t<span v-if=\"!value || !value.length\" class=\"cn-cell-renderer__dash\">—</span>\n\t\t\t<span v-else :title=\"Array.isArray(value) ? value.join(', ') : ''\">\n\t\t\t\t{{ formattedValue }}\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Default: formatted text -->\n\t\t<template v-else>\n\t\t\t<span :title=\"rawTitle\">{{ formattedValue }}</span>\n\t\t</template>\n\t</span>\n</template>\n\n<script>\nimport { getCanonicalLocale, translate as t } from '@nextcloud/l10n'\nimport { NcAvatar, NcButton, NcCheckboxRadioSwitch, NcDateTime } from '@nextcloud/vue'\nimport CheckBold from 'vue-material-design-icons/CheckBold.vue'\nimport Lock from 'vue-material-design-icons/Lock.vue'\nimport CnFkResolveCell from '../CnFkResolveCell/CnFkResolveCell.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport CnGroupNameCell from './CnGroupNameCell.vue'\nimport { normalizeLook } from '../../composables/useLook.js'\nimport { isBadgeVariant } from '../../utils/badgeVariants.js'\nimport { ageInDays, boardDateLocale, formatAge, formatBoardDate, formatRelativeDate, formatTimeOfDay, resolveAgeVariant } from '../../utils/boardDate.js'\nimport { daysUntil, parseDateValue, resolveDateVariant } from '../../utils/dateVariant.js'\nimport { pillLook } from '../../utils/detailActionModel.js'\nimport { resolveDue } from '../../utils/dueText.js'\nimport { fillTemplate, formatBoardDateTime } from '../../utils/fillTemplate.js'\nimport { formatMoney } from '../../utils/formatMetric.js'\nimport { groupDisplayName, loadGroupDisplayName } from '../../utils/groupAutocomplete.js'\nimport { objectFieldValue } from '../../utils/objectName.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { indicatorApplies } from '../../utils/rowIndicators.js'\nimport { safeHref } from '../../utils/safeHref.js'\nimport { formatValue, isGroupProp } from '../../utils/schema.js'\nimport { userDisplayName } from '../../utils/userDisplayName.js'\nimport { isWithheld, withheldReason } from '../../utils/withheldValue.js'\nimport { CnStatusBadge } from '../CnStatusBadge/index.js'\n\n/** A uuid in any version, the shape OpenRegister ids take. */\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\n/**\n * Module-level set of column keys already warned about for a\n * `widget:\"link\"` declaration with no resolvable target — guarantees\n * one warning per (page, column-key) rather than per row × render.\n */\nconst WARNED_LINK_KEYS = new Set()\n\n/**\n * CnCellRenderer — Type-aware cell renderer for schema-driven tables.\n *\n * Renders a single cell value based on its schema property definition.\n * Booleans render as icons, enums as status badges, dates as formatted strings,\n * and everything else as truncated text via `formatValue()`.\n *\n * ```vue\n * <CnCellRenderer :value=\"row.status\" :property=\"schema.properties.status\" />\n * ```\n */\nexport default {\n\tname: 'CnCellRenderer',\n\n\tcomponents: {\n\t\tCnIcon,\n\t\tCnStatusBadge,\n\t\tNcCheckboxRadioSwitch,\n\t\tCnFkResolveCell,\n\t\tCnGroupNameCell,\n\t\tCheckBold,\n\t\tLock,\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcDateTime,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Cell-formatter registry, provided by CnAppRoot (`cnFormatters`).\n\t\t * Map of formatter-id → `(value, row, property, options, context) => string|number`;\n\t\t * `context` is the page record, `{ objectId, object }`.\n\t\t * Defaults to an empty object so standalone use (no CnAppRoot\n\t\t * ancestor) is unaffected.\n\t\t */\n\t\tcnFormatters: { default: () => ({}) },\n\t\t/** The detail page's record context (`{ objectId, object }`), for a formatter's fifth argument. */\n\t\tcnObjectContext: { default: null },\n\t\t/** The v2 grid's record holder (`{ value: { objectId, objectData } }`), the same. */\n\t\tcnDetailObjectContext: { default: null },\n\t\t/**\n\t\t * Cell-widget registry, provided by CnAppRoot (`cnCellWidgets`).\n\t\t * Map of widget-id → Vue component. Resolves a column's `widget` id;\n\t\t * the built-in `\"badge\"` is handled inline (no registry entry needed).\n\t\t * Defaults to an empty object.\n\t\t */\n\t\tcnCellWidgets: { default: () => ({}) },\n\t\t/**\n\t\t * Display-layer translation function, provided by CnAppRoot\n\t\t * (`cnTranslate: this.translate`, bound to the host app's id). Applied\n\t\t * to the enum badge label only — the underlying value is untouched.\n\t\t * Defaults to identity so standalone use is unaffected.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t\t/**\n\t\t * The look the app is drawn in, provided by CnAppRoot (or a page with\n\t\t * `config.look`). Under `board` a date cell reads the board's short\n\t\t * form (\"5 okt\"). Defaults to `nextcloud`, which renders as before.\n\t\t */\n\t\tcnLook: { default: 'nextcloud' },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The raw cell value. String precedes Boolean: with Boolean first Vue\n\t\t * casts an empty string to `true`, and an empty cell drew a check mark.\n\t\t */\n\t\tvalue: {\n\t\t\t// eslint-disable-next-line vue/prefer-prop-type-boolean-first -- an empty string must stay a string\n\t\t\ttype: [String, Number, Boolean, Array, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Schema property definition: { type, format, enum, items, title } */\n\t\tproperty: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Optional cell-formatter id (e.g. `currency`, `automationTrigger`).\n\t\t * When set and resolvable in the injected `cnFormatters` registry,\n\t\t * the cell renders `cnFormatters[formatter](value, row, property,\n\t\t * formatterOptions)` as text — an explicit override of the type-aware\n\t\t * rendering below.\n\t\t */\n\t\tformatter: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Declarative options map passed as the formatter's fourth argument\n\t\t * (e.g. `{ currency: 'USD' }` for the built-in `currency` formatter,\n\t\t * or `{ negative, zero, positive }` phrases for `conditionalPhrase`).\n\t\t * Undefined-safe: three-argument formatters simply ignore it.\n\t\t */\n\t\tformatterOptions: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Optional cell-widget id (e.g. `badge`, or a consumer-registered\n\t\t * name). When it resolves in `cnCellWidgets` the cell renders that\n\t\t * component with `{ value, row, property, formatted, ...widgetProps }`;\n\t\t * the built-in id `\"badge\"` renders `CnStatusBadge` and the built-in\n\t\t * id `\"fkResolve\"` renders `CnFkResolveCell` (uuid → related object\n\t\t * label, config via `widgetProps { register, schema, labelField }`),\n\t\t * and the built-in id `\"refLabel\"` shows labels already resolved in a\n\t\t * batch (`widgetProps { labels, route? }`, set by CnIndexPage for a\n\t\t * column with `labelField`).\n\t\t * The built-in id `\"avatar\"` renders a person as an avatar with the\n\t\t * name beside it (`widgetProps { userField?, user?, nameField?, size?, groupField?, groupLabel? }`; a row nobody holds names its first `groupField` group, in `groupLabel` (\"{group}, unclaimed\");\n\t\t * a free name gets its initials), and the built-in id `\"date\"` renders\n\t\t * a date whose colour follows `widgetProps.variantWhen`, rules on the\n\t\t * number of days until the date (`[{ op: \"lt\", value: 0, variant:\n\t\t * \"error\" }, { op: \"lte\", value: 5, variant: \"warning\" }]`).\n\t\t * The built-in id `\"age\"` renders how long the row has waited since\n\t\t * the date (\"4 hours\", \"3 days\"), coloured by `widgetProps.variantWhen`\n\t\t * rules on the days it shows (`[{ op: \"gte\", value: 3, variant:\n\t\t * \"error\" }]`).\n\t\t * The built-in id `\"group\"` shows a Nextcloud group id (or a list of\n\t\t * them) as the group's display name; a column whose property is\n\t\t * marked `referenceType: \"nextcloud-group\"` gets it without asking.\n\t\t * Takes precedence over `formatter`/the type-aware rendering, but the\n\t\t * value handed to the widget is the formatter-shaped `formatted` when\n\t\t * `formatter` is also set.\n\t\t */\n\t\twidget: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Extra props spread onto the resolved cell-widget component. The\n\t\t * built-in `\"link\"` reads its target from here: `route` (a manifest\n\t\t * page id), `params` (route param to row field), `href` (URL with\n\t\t * `{field}` placeholders; a new tab unless `target: \"_self\"` or a `#`\n\t\t * anchor), and `routeField` + `routeMap` to pick the\n\t\t * page per row from a sibling field (`{ person: 'ContactDetail' }`),\n\t\t * with `route` as the fallback for a value the map does not hold.\n\t\t */\n\t\twidgetProps: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * The column's secondary line (a muted second line under the value).\n\t\t * The `avatar` widget draws it under the name, beside the picture;\n\t\t * other cells ignore it, because the table draws it under the cell.\n\t\t */\n\t\tsecondary: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional declarative cell-format spec — a no-code alternative to a\n\t\t * registry `formatter`. Recognised `style` values:\n\t\t *\n\t\t * - `'currency'` → `Intl.NumberFormat` currency (e.g. `€ 1.234,56`),\n\t\t * honouring `currency` (ISO code, default `'EUR'`) and `decimals`\n\t\t * (default 2).\n\t\t * - `'number'` / `'percent'` → localized number (percent appends `%`),\n\t\t * honouring `decimals` (default 0).\n\t\t * - `'duration'` → a seconds value rendered compact (`1u 23m`, `45m 10s`,\n\t\t * `12s`); pass `unit: 'milliseconds'` / `'minutes'` / `'hours'` when the\n\t\t * raw value is not in seconds. Sub-second millisecond values render as\n\t\t * `245ms` rather than rounding down to `0s`.\n\t\t * - `'swatch'` → a colour dot read from a sibling row field named by\n\t\t * `colorField` (the value renders as the cell text beside it).\n\t\t *\n\t\t * `prefix` / `suffix` are prepended/appended to the numeric styles.\n\t\t * Resolved AFTER `formatter` / `widget` (those win), but BEFORE the\n\t\t * type-aware rendering, so a manifest column can opt into currency or a\n\t\t * colour swatch without registering a function.\n\t\t *\n\t\t * @type {{style?: 'currency'|'number'|'decimal'|'percent'|'duration'|'swatch', currency?: string, decimals?: number, unit?: 'milliseconds'|'seconds'|'minutes'|'hours', prefix?: string, suffix?: string, colorField?: string}}\n\t\t */\n\t\tformat: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The full row object — passed so a formatter can be a function of\n\t\t * the whole record (e.g. \"days since `@self.updated`\"), not just\n\t\t * this one cell value.\n\t\t */\n\t\trow: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Maximum string length before truncation */\n\t\ttruncate: {\n\t\t\ttype: Number,\n\t\t\tdefault: 100,\n\t\t},\n\n\t\t/**\n\t\t * Row identifier field — used by the built-in `widget:\"link\"` when\n\t\t * the manifest doesn't specify an explicit `widgetProps.params`\n\t\t * map. Defaults to `'id'` so router-link param resolution works\n\t\t * with the manifest convention (`/x/:id`).\n\t\t */\n\t\trowKey: {\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t// The switch's state while its onToggle is in flight, or null.\n\t\t\tswitchPending: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The schema an `fkResolve` cell reads: `widgetProps.schema`, or the one\n\t\t * `widgetProps.schemaFrom` (`{ field, map }`) names for this row, for a\n\t\t * reference that points at objects of more than one schema.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tfkSchema() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tconst from = props.schemaFrom\n\t\t\tif (from && typeof from === 'object' && from.field && this.row) {\n\t\t\t\tconst mapped = (from.map || {})[String(this.row[from.field])]\n\t\t\t\tif (mapped) {\n\t\t\t\t\treturn mapped\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn props.schema || ''\n\t\t},\n\n\t\t/**\n\t\t * The `actions` widget's buttons for this row: the page's resolved\n\t\t * `widgetProps.items` (a list, or a function of the row returning\n\t\t * one), each `{ key, label, variant?, disabled? }`.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, variant: string, disabled: boolean}>}\n\t\t * @spec openspec/changes/table-actions-column/specs/index-page/spec.md#requirement-a-column-can-show-row-actions-as-buttons\n\t\t */\n\t\tactionItems() {\n\t\t\tconst raw = typeof this.widgetProps?.items === 'function' ? this.widgetProps.items(this.row) : this.widgetProps?.items\n\t\t\tconst items = Array.isArray(raw) ? raw : []\n\t\t\treturn items.map((item) => ({\n\t\t\t\t...item,\n\t\t\t\tvariant: item.variant || 'secondary',\n\t\t\t\tdisabled: item.disabled === true,\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Whether the \"switch\" widget is on: the pending state while a toggle\n\t\t * is in flight, else `widgetProps.checked(row)`, else the value.\n\t\t *\n\t\t * @return {boolean} The switch state.\n\t\t */\n\t\tswitchChecked() {\n\t\t\tif (this.switchPending !== null) {\n\t\t\t\treturn this.switchPending\n\t\t\t}\n\t\t\tconst checked = this.widgetProps && this.widgetProps.checked\n\t\t\tif (typeof checked === 'function') {\n\t\t\t\treturn checked(this.row) === true\n\t\t\t}\n\t\t\treturn this.value === true\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} Whether a toggle is in flight.\n\t\t */\n\t\tswitchBusy() {\n\t\t\treturn this.switchPending !== null\n\t\t},\n\n\t\t/**\n\t\t * The \"iconLabel\" widget's icon, resolved against the row.\n\t\t *\n\t\t * @return {object|string|null} A component, an icon name, or null.\n\t\t */\n\t\ticonLabelIcon() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\t// `widgetProps.icons`: an icon per stored value, `icon` for the rest.\n\t\t\t// @spec openspec/changes/board-log-table-plain-cells/specs/board-log-table-plain-cells/spec.md#requirement-an-icon-label-cell-can-pick-its-icon-by-value\n\t\t\tif (props.icons && typeof props.icons === 'object' && this.hasValue && typeof this.value !== 'object' && Object.hasOwn(props.icons, String(this.value))) {\n\t\t\t\treturn props.icons[String(this.value)] || null\n\t\t\t}\n\t\t\tconst icon = props.icon\n\t\t\tconst resolved = typeof icon === 'function' ? icon(this.row) : icon\n\t\t\treturn resolved || null\n\t\t},\n\n\t\tpropertyType() {\n\t\t\treturn this.property?.type || (typeof this.value)\n\t\t},\n\n\t\t/**\n\t\t * A boolean's word from `widgetProps.trueLabel` / `falseLabel`,\n\t\t * translated, or '' when the column names none.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tbooleanWord() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tconst word = this.value === true ? props.trueLabel : (this.value === false ? props.falseLabel : '')\n\t\t\treturn typeof word === 'string' && word !== '' ? this.cnTranslate(word) : ''\n\t\t},\n\n\t\t/**\n\t\t * Whether this boolean's word is drawn as a pill: `widgetProps.pill`\n\t\t * names the value that is.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tbooleanPilled() {\n\t\t\tconst pill = (this.widgetProps || {}).pill\n\t\t\treturn typeof pill === 'boolean' && pill === this.value\n\t\t},\n\n\t\tisEnum() {\n\t\t\treturn !!(this.property?.enum && this.property.enum.length > 0)\n\t\t},\n\n\t\t/**\n\t\t * Badge text for an enum cell. Schema enum values are English source\n\t\t * codes, so a Dutch session otherwise reads `submitted` in a table whose\n\t\t * headers are Dutch. Only the label is translated — the cell's value,\n\t\t * sorting and filtering keep using the raw code.\n\t\t */\n\t\tenumLabel() {\n\t\t\tconst raw = String(this.value)\n\t\t\tconst labels = (this.property && (this.property.enumLabels || this.property['x-enum-labels'])) || {}\n\t\t\treturn this.cnTranslate(labels[raw] || raw)\n\t\t},\n\n\t\t/** True when the property is a date / date-time (rendered via NcDateTime). */\n\t\tisDate() {\n\t\t\treturn this.property?.format === 'date-time' || this.property?.format === 'date'\n\t\t},\n\n\t\t/**\n\t\t * The cell value as a Date for NcDateTime, or `null` when absent or\n\t\t * unparseable (the cell then falls back to a dash / plain text).\n\t\t *\n\t\t * @return {Date|null}\n\t\t */\n\t\tdateTimestamp() {\n\t\t\tif (!this.hasValue) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst date = new Date(this.value)\n\t\t\treturn Number.isNaN(date.getTime()) ? null : date\n\t\t},\n\n\t\t/**\n\t\t * `avatar` widget: the Nextcloud user id whose avatar to show, or ''\n\t\t * for a free name. Read from the row field `widgetProps.userField`,\n\t\t * or from the cell value itself when `widgetProps.user` is true.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-avatar-and-date-cells\n\t\t */\n\t\tavatarUserId() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tlet id = ''\n\t\t\tif (typeof props.userField === 'string' && props.userField !== '') {\n\t\t\t\tid = objectFieldValue(this.row, props.userField)\n\t\t\t} else if (props.user === true) {\n\t\t\t\tid = this.value\n\t\t\t}\n\t\t\treturn (typeof id === 'string' || typeof id === 'number') ? String(id) : ''\n\t\t},\n\n\t\t/**\n\t\t * `avatar` widget: the name shown beside the picture. Read from the\n\t\t * row field `widgetProps.nameField` when set; a cell holding a user id\n\t\t * (`widgetProps.user`) shows that user's display name once it is\n\t\t * known; else the cell value.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-avatar-and-date-cells\n\t\t * @spec openspec/changes/avatar-cell-user-display-name/specs/data-table/spec.md#requirement-an-avatar-cell-holding-a-user-id-shows-the-users-display-name\n\t\t */\n\t\tavatarName() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tif (typeof props.nameField === 'string' && props.nameField !== '') {\n\t\t\t\tconst name = objectFieldValue(this.row, props.nameField)\n\t\t\t\tif (typeof name === 'string' && name !== '') {\n\t\t\t\t\treturn name\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (props.user === true && this.avatarUserId !== '') {\n\t\t\t\tconst displayName = userDisplayName(this.avatarUserId)\n\t\t\t\tif (displayName !== '') {\n\t\t\t\t\treturn displayName\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.avatarGroupId !== '') {\n\t\t\t\tconst label = typeof props.groupLabel === 'string' && props.groupLabel !== '' ? this.cnTranslate(props.groupLabel) : '{group}'\n\t\t\t\treturn label.replace(/\\{group\\}/g, this.avatarGroupName)\n\t\t\t}\n\t\t\tif (!this.hasValue || typeof this.value === 'object') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn String(this.hasFormatter ? this.formattedValue : this.value)\n\t\t},\n\n\t\t/**\n\t\t * `avatar` widget: the initials of a free name, at most two letters\n\t\t * (first and last word).\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-avatar-and-date-cells\n\t\t */\n\t\tavatarInitials() {\n\t\t\treturn this.initialsOf(this.avatarGroupId !== '' ? this.avatarGroupName : this.avatarName)\n\t\t},\n\n\t\t/**\n\t\t * `avatar` widget: the group the row waits on when nobody holds it,\n\t\t * the first id in the row field `widgetProps.groupField`, or ''.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/avatar-cell-group-fallback/specs/data-table/spec.md#requirement-an-empty-avatar-cell-can-name-the-rows-group\n\t\t */\n\t\tavatarGroupId() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tif (this.widget !== 'avatar' || typeof props.groupField !== 'string' || props.groupField === '' || this.avatarUserId !== '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (this.hasValue && typeof this.value !== 'object') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst groups = objectFieldValue(this.row, props.groupField)\n\t\t\tconst first = Array.isArray(groups) ? groups.find((gid) => typeof gid === 'string' && gid !== '') : groups\n\t\t\treturn typeof first === 'string' ? first : ''\n\t\t},\n\n\t\t/**\n\t\t * `avatar` widget: the display name of `avatarGroupId`, the id until it is known.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tavatarGroupName() {\n\t\t\treturn groupDisplayName(this.avatarGroupId) || this.avatarGroupId\n\t\t},\n\n\t\t/**\n\t\t * `avatar` widget: where a REFERENCED person's name comes from\n\t\t * (`widgetProps.resolve`, `{ register, schema, labelField? }`, the\n\t\t * shape the facts widget's `resolve` takes), or null when the cell\n\t\t * holds the name or user id itself.\n\t\t *\n\t\t * @return {{register: string, schema: string, labelField?: string}|null}\n\t\t * @spec openspec/changes/screens-avatar-cell-resolve/specs/data-table/spec.md\n\t\t */\n\t\tavatarResolve() {\n\t\t\tconst resolve = this.widgetProps && this.widgetProps.resolve\n\t\t\tif (!resolve || typeof resolve !== 'object') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof resolve.register !== 'string' || resolve.register === '' || typeof resolve.schema !== 'string' || resolve.schema === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn resolve\n\t\t},\n\n\t\t/**\n\t\t * `avatar` widget: the picture size in pixels (`widgetProps.size`, default 24,\n\t\t * 32 under the board look).\n\t\t *\n\t\t * @return {number}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-avatar-and-date-cells\n\t\t */\n\t\tavatarSize() {\n\t\t\tconst size = Number(this.widgetProps && this.widgetProps.size)\n\t\t\treturn Number.isFinite(size) && size > 0 ? size : (this.isBoardLook ? 32 : 24)\n\t\t},\n\n\t\t/**\n\t\t * `date` widget with `widgetProps.due`: the colour class. The\n\t\t * `variantWhen` rules win when they match; else an overdue date reads\n\t\t * as an error and a soon one (`due.soonWithin`) as a warning.\n\t\t *\n\t\t * @return {string|null} The class, or null for the plain colour.\n\t\t * @spec openspec/changes/due-text-soon/specs/due-text-soon/spec.md#requirement-a-due-date-within-a-few-days-reads-as-soon\n\t\t */\n\t\tdueClass() {\n\t\t\tif (this.dateVariant) {\n\t\t\t\treturn 'cn-cell-renderer__date--' + this.dateVariant\n\t\t\t}\n\t\t\tif (this.dueResult && this.dueResult.tone === 'danger') {\n\t\t\t\treturn 'cn-cell-renderer__date--error'\n\t\t\t}\n\t\t\tif (this.dueResult && this.dueResult.tone === 'muted') {\n\t\t\t\treturn 'cn-cell-renderer__date--muted'\n\t\t\t}\n\t\t\treturn this.dueResult && this.dueResult.soon ? 'cn-cell-renderer__date--warning' : null\n\t\t},\n\n\t\t/**\n\t\t * `date` widget: whether the date lies before today.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/object-list-by-field/specs/object-list-widget/spec.md#requirement-a-date-cell-can-say-its-state-and-groups-keep-a-set-order\n\t\t */\n\t\tdateIsPast() {\n\t\t\tconst days = daysUntil(this.value)\n\t\t\treturn days !== null && days < 0\n\t\t},\n\n\t\t/**\n\t\t * `date` widget: `widgetProps.pastText` for a past date, else\n\t\t * `widgetProps.otherText` (also for an empty one), translated; '' without either.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/object-list-by-field/specs/object-list-widget/spec.md#requirement-a-date-cell-can-say-its-state-and-groups-keep-a-set-order\n\t\t */\n\t\tdateStateText() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tconst text = this.dateIsPast ? props.pastText : props.otherText\n\t\t\treturn typeof text === 'string' && text !== '' ? this.cnTranslate(text) : ''\n\t\t},\n\n\t\t/**\n\t\t * `date` widget: the variant of the first `widgetProps.variantWhen`\n\t\t * rule the date matches, or '' for none.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-avatar-and-date-cells\n\t\t */\n\t\tdateVariant() {\n\t\t\tconst rules = this.widgetProps && this.widgetProps.variantWhen\n\t\t\tconst variant = resolveDateVariant(this.value, rules)\n\t\t\treturn variant === 'default' ? '' : variant\n\t\t},\n\n\t\t/**\n\t\t * Whether the app takes the board look (`cnLook` is `board`).\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-a-board-date-cell-reads-day-and-short-month-in-the-user-language\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/**\n\t\t * The board's short date (\"5 okt\", \"14 feb 2024\" outside this year).\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-a-board-date-cell-reads-day-and-short-month-in-the-user-language\n\t\t */\n\t\tboardDateLabel() {\n\t\t\treturn formatBoardDate(this.value)\n\t\t},\n\n\t\t/**\n\t\t * The full date for a tooltip: day, month and year in the user's\n\t\t * locale, with the time for a date-time value.\n\t\t *\n\t\t * @return {string|undefined}\n\t\t * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-says-how-long-something-has-waited\n\t\t */\n\t\tabsoluteDateLabel() {\n\t\t\tconst date = parseDateValue(this.value)\n\t\t\tif (date === null) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tconst withTime = this.property?.format === 'date-time' || this.widget === 'age' || (this.widget === 'date' && this.widgetProps?.showTime === true)\n\t\t\tconst options = { day: 'numeric', month: 'long', year: 'numeric', ...(withTime ? { hour: '2-digit', minute: '2-digit' } : {}) }\n\t\t\ttry {\n\t\t\t\treturn new Intl.DateTimeFormat(getCanonicalLocale() || undefined, options).format(date)\n\t\t\t} catch {\n\t\t\t\treturn new Intl.DateTimeFormat(undefined, options).format(date)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * `date` widget with `widgetProps.due`: the due text and its tone.\n\t\t * Null without the key, or when the field is empty and no `emptyText`.\n\t\t *\n\t\t * @return {{text: string, tone: string}|null}\n\t\t * @spec openspec/changes/screens-due-text/specs/due-text/spec.md#requirement-a-due-date-reads-as-text-with-a-tone\n\t\t */\n\t\tdueResult() {\n\t\t\tconst due = this.widgetProps && this.widgetProps.due\n\t\t\tif (this.widget !== 'date' || !due || typeof due !== 'object') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn resolveDue(this.value, due, (text) => this.cnTranslate(text), new Date(), this.row)\n\t\t},\n\n\t\t/**\n\t\t * Whether the `date` widget reads relatively (`widgetProps.relative`).\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/screens-relative-dates/specs/relative-dates/spec.md#requirement-a-relative-date-reads-today-yesterday-then-a-short-date\n\t\t */\n\t\tisRelativeDate() {\n\t\t\treturn this.widgetProps?.relative === true\n\t\t},\n\n\t\t/**\n\t\t * `age` widget: how long the row has waited (\"4 uur\", \"3 dagen\").\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-says-how-long-something-has-waited\n\t\t */\n\t\tageLabel() {\n\t\t\treturn formatAge(this.value)\n\t\t},\n\n\t\t/**\n\t\t * `progress` widget: the value as a whole percentage of\n\t\t * `widgetProps.max` (default 100), clamped to 0..100. An empty value\n\t\t * falls back to the first `widgetProps.fallbackFields` row field that\n\t\t * holds a number. Null when nothing holds one.\n\t\t *\n\t\t * @return {number|null}\n\t\t * @spec openspec/changes/abstract-progress-cell/specs/progress-cell/spec.md#requirement-a-progress-cell-draws-a-toned-bar-with-its-percentage\n\t\t */\n\t\tprogressPercent() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tconst fallbacks = Array.isArray(props.fallbackFields) ? props.fallbackFields : []\n\t\t\tconst candidates = [this.value, ...fallbacks.map((field) => readPath(this.row || {}, field))]\n\t\t\tconst raw = candidates.find((v) => v !== null && v !== undefined && v !== '' && !Number.isNaN(Number(v)))\n\t\t\tif (raw === undefined) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst max = Number(props.max) > 0 ? Number(props.max) : 100\n\t\t\treturn Math.max(0, Math.min(100, Math.round((Number(raw) / max) * 100)))\n\t\t},\n\n\t\t/**\n\t\t * `progress` widget: the tone. `widgetProps.thresholds`\n\t\t * `{ good: 60, warn: 30 }` (the defaults): at or above `good` is\n\t\t * success, at or above `warn` is warning, below is error. With\n\t\t * `widgetProps.lowerIsBetter` the scale turns around.\n\t\t *\n\t\t * @return {'success'|'warning'|'error'}\n\t\t * @spec openspec/changes/abstract-progress-cell/specs/progress-cell/spec.md#requirement-a-progress-cell-draws-a-toned-bar-with-its-percentage\n\t\t */\n\t\tprogressTone() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tconst thresholds = props.thresholds || {}\n\t\t\tconst good = Number.isFinite(Number(thresholds.good)) ? Number(thresholds.good) : 60\n\t\t\tconst warn = Number.isFinite(Number(thresholds.warn)) ? Number(thresholds.warn) : 30\n\t\t\tconst value = props.lowerIsBetter === true ? 100 - this.progressPercent : this.progressPercent\n\t\t\tif (value >= good) {\n\t\t\t\treturn 'success'\n\t\t\t}\n\t\t\treturn value >= warn ? 'warning' : 'error'\n\t\t},\n\n\t\t/**\n\t\t * `progress` widget: the age badge, or null. `widgetProps.badge`\n\t\t * `{ label, dateField, afterDays, when?, tone? }`: shown when the row's\n\t\t * `dateField` (default `@self.updated`) lies `afterDays` or more days\n\t\t * back and every `when` field equals its value. `label` is translated\n\t\t * and takes `{days}`.\n\t\t *\n\t\t * @return {{label: string, tone: string}|null}\n\t\t * @spec openspec/changes/abstract-progress-cell/specs/progress-cell/spec.md#requirement-a-progress-cell-can-carry-an-age-badge\n\t\t */\n\t\tprogressBadge() {\n\t\t\tconst badge = this.widgetProps && this.widgetProps.badge\n\t\t\tif (!badge || typeof badge !== 'object' || !badge.label) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst row = this.row || {}\n\t\t\tconst when = badge.when && typeof badge.when === 'object' ? badge.when : {}\n\t\t\tfor (const [field, expected] of Object.entries(when)) {\n\t\t\t\tconst actual = readPath(row, field)\n\t\t\t\tif (actual !== undefined && actual !== null && actual !== '' && String(actual) !== String(expected)) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst days = ageInDays(readPath(row, badge.dateField || '@self.updated'))\n\t\t\tconst after = Number(badge.afterDays)\n\t\t\tif (days === null || !Number.isFinite(after) || days < after) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst label = this.cnTranslate(badge.label)\n\t\t\treturn {\n\t\t\t\tlabel: String(label).replace(/\\{days\\}/g, String(days)),\n\t\t\t\ttone: ['error', 'warning', 'success'].includes(badge.tone) ? badge.tone : 'error',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * `age` widget: the variant of the first `widgetProps.variantWhen`\n\t\t * rule the shown days match, or '' for none.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-turns-red-past-its-threshold\n\t\t */\n\t\tageVariant() {\n\t\t\tconst variant = resolveAgeVariant(this.value, this.widgetProps && this.widgetProps.variantWhen)\n\t\t\treturn variant === 'default' ? '' : variant\n\t\t},\n\n\t\t/**\n\t\t * `date` widget: the date written out in the user's locale.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-avatar-and-date-cells\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-small-opt-ins-from-the-decidiq-and-learniq-lanes\n\t\t */\n\t\t/**\n\t\t * `date` widget with `widgetProps.layout: \"block\"`: the parts of the\n\t\t * date tile (day number, short month), the weekday and the time range\n\t\t * (\"09.30 tot 12.00 uur\") to the row field `widgetProps.endField`.\n\t\t * Null for any other cell.\n\t\t *\n\t\t * @return {{day: string, month: string, weekday: string, time: string}|null}\n\t\t * @spec openspec/changes/screens-date-tile-cell/specs/data-table/spec.md\n\t\t */\n\t\tdateBlock() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tif (this.widget !== 'date' || props.layout !== 'block' || !this.dateTimestamp) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst start = this.dateTimestamp\n\t\t\tconst format = (options) => {\n\t\t\t\ttry {\n\t\t\t\t\treturn new Intl.DateTimeFormat(boardDateLocale(), options).format(start)\n\t\t\t\t} catch {\n\t\t\t\t\treturn new Intl.DateTimeFormat(undefined, options).format(start)\n\t\t\t\t}\n\t\t\t}\n\t\t\t// A date without a time of day (format `date`, or a bare\n\t\t\t// YYYY-MM-DD) draws no time line.\n\t\t\tconst dateOnly = this.property?.format === 'date' || (typeof this.value === 'string' && /^\\d{4}-\\d{2}-\\d{2}$/.test(this.value))\n\t\t\tlet time = ''\n\t\t\tif (!dateOnly) {\n\t\t\t\tconst startTime = formatTimeOfDay(start)\n\t\t\t\tlet end = null\n\t\t\t\tif (typeof props.endField === 'string' && props.endField !== '') {\n\t\t\t\t\tconst raw = objectFieldValue(this.row, props.endField)\n\t\t\t\t\tconst parsed = (raw === null || raw === undefined || raw === '') ? null : new Date(raw)\n\t\t\t\t\tend = parsed && !Number.isNaN(parsed.getTime()) ? parsed : null\n\t\t\t\t}\n\t\t\t\ttime = end\n\t\t\t\t\t? t('nextcloud-vue', '{start} to {end}', { start: startTime, end: formatTimeOfDay(end) })\n\t\t\t\t\t: startTime\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tday: format({ day: 'numeric' }),\n\t\t\t\t// \"okt.\" in Dutch: the board draws the month without the dot.\n\t\t\t\tmonth: format({ month: 'short' }).replace(/\\.$/, ''),\n\t\t\t\tweekday: format({ weekday: 'long' }),\n\t\t\t\ttime,\n\t\t\t}\n\t\t},\n\n\t\tdateLabel() {\n\t\t\tconst date = parseDateValue(this.value)\n\t\t\tif (date === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// `widgetProps.showTime: true` adds the time of day (\"5 Oct 2026,\n\t\t\t// 08:30\"); `widgetProps.timeOnly: true` shows just the time\n\t\t\t// (\"08:30\"), for a timetable row. Default: the date alone.\n\t\t\tconst props = this.widgetProps || {}\n\t\t\t// `widgetProps.relative: true` says \"today\" and \"yesterday\" and then\n\t\t\t// the short date (\"vandaag, 09.42\" with `showTime`), in any look.\n\t\t\tif (props.relative === true) {\n\t\t\t\treturn formatRelativeDate(date, { withTime: props.showTime === true })\n\t\t\t}\n\t\t\t// `widgetProps.short: true` writes the board's short date in any\n\t\t\t// look, with `showTime` followed by the time (\"5 okt, 09:15\").\n\t\t\t// @spec openspec/changes/board-log-table-plain-cells/specs/board-log-table-plain-cells/spec.md#requirement-a-date-cell-can-write-the-boards-short-date-with-its-time\n\t\t\tif (props.short === true && props.timeOnly !== true) {\n\t\t\t\treturn props.showTime === true ? formatBoardDateTime(date) : formatBoardDate(date)\n\t\t\t}\n\t\t\t// The board look writes a plain date in its short form (\"5 okt\");\n\t\t\t// a time of day keeps the full form below.\n\t\t\tif (this.isBoardLook && props.timeOnly !== true && props.showTime !== true) {\n\t\t\t\treturn formatBoardDate(date)\n\t\t\t}\n\t\t\tconst options = props.timeOnly === true\n\t\t\t\t? { hour: '2-digit', minute: '2-digit' }\n\t\t\t\t: { day: 'numeric', month: 'short', year: 'numeric', ...(props.showTime === true ? { hour: '2-digit', minute: '2-digit' } : {}) }\n\t\t\tlet locale\n\t\t\ttry {\n\t\t\t\tlocale = getCanonicalLocale()\n\t\t\t} catch {\n\t\t\t\tlocale = undefined\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Intl.DateTimeFormat(locale || undefined, options).format(date)\n\t\t\t} catch {\n\t\t\t\treturn new Intl.DateTimeFormat(undefined, options).format(date)\n\t\t\t}\n\t\t},\n\n\t\t/** True when the property is a URI / URL (rendered as an external link). */\n\t\tisUri() {\n\t\t\treturn this.property?.format === 'uri' || this.property?.format === 'url'\n\t\t},\n\n\t\t/**\n\t\t * Safe external href for a URI/URL cell, or `null` when the value is\n\t\t * absent or fails `safeHref` validation (e.g. an unsafe scheme).\n\t\t *\n\t\t * @return {string|null}\n\t\t */\n\t\turiHref() {\n\t\t\tif (!this.hasValue) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn safeHref(String(this.value))\n\t\t},\n\n\t\t/** True when the cell has a renderable value (not null/undefined/empty string). */\n\t\thasValue() {\n\t\t\treturn this.value !== null && this.value !== undefined && this.value !== ''\n\t\t},\n\n\t\t/**\n\t\t * The row's own reference for an `fkResolve` cell (`widgetProps.refField`),\n\t\t * or null when the row has none.\n\t\t *\n\t\t * @return {string|number|Array|null}\n\t\t * @spec openspec/changes/fk-resolve-lookup/specs/fk-resolve-lookup/spec.md#requirement-a-reference-cell-can-look-its-record-up-by-a-property\n\t\t */\n\t\tfkRefValue() {\n\t\t\tconst field = this.widgetProps && this.widgetProps.refField\n\t\t\tif (!field || !this.row) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst ref = readPath(this.row, field)\n\t\t\tconst empty = ref === null || ref === undefined || ref === '' || (Array.isArray(ref) && ref.length === 0)\n\t\t\treturn empty || (typeof ref === 'object' && !Array.isArray(ref)) ? null : ref\n\t\t},\n\n\t\t/**\n\t\t * Whether the value is OpenRegister's withheld marker, which every\n\t\t * other path would stringify to `[object Object]`.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/cell-config-growth/specs/cell-config-growth/spec.md#requirement-a-withheld-value-reads-as-withheld\n\t\t */\n\t\twithheld() {\n\t\t\treturn isWithheld(this.value)\n\t\t},\n\n\t\t/**\n\t\t * The text a withheld value reads as.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/cell-config-growth/specs/cell-config-growth/spec.md#requirement-a-withheld-value-reads-as-withheld\n\t\t */\n\t\twithheldText() {\n\t\t\treturn t('nextcloud-vue', 'Withheld')\n\t\t},\n\n\t\t/**\n\t\t * Why a value is withheld, as the cell's tooltip.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/cell-config-growth/specs/cell-config-growth/spec.md#requirement-a-withheld-value-reads-as-withheld\n\t\t */\n\t\twithheldHint() {\n\t\t\treturn withheldReason(this.value) === 'access'\n\t\t\t\t? t('nextcloud-vue', 'You may not open the linked object, so its value is not shown.')\n\t\t\t\t: t('nextcloud-vue', 'This value is not shown.')\n\t\t},\n\n\t\t/**\n\t\t * `number` widget: the value with `widgetProps.unit` (`{ one, other }`\n\t\t * with `{count}`, through the app's translate), else the number in the\n\t\t * user's locale. '' for an empty or unreadable value.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/cell-config-growth/specs/cell-config-growth/spec.md#requirement-a-number-cell-reads-with-its-unit-and-a-flag\n\t\t */\n\t\tnumberLabel() {\n\t\t\tif (!this.hasValue || typeof this.value === 'object' || typeof this.value === 'boolean') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst count = Number(this.value)\n\t\t\tif (!Number.isFinite(count)) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst unit = this.widgetProps && this.widgetProps.unit\n\t\t\tif (unit && typeof unit === 'object' && (unit.one || unit.other)) {\n\t\t\t\tconst template = this.cnTranslate((count === 1 ? unit.one : unit.other) || unit.other || unit.one)\n\t\t\t\treturn template.replace(/\\{count\\}/g, String(count))\n\t\t\t}\n\t\t\treturn count.toLocaleString(getCanonicalLocale() || undefined)\n\t\t},\n\n\t\t/**\n\t\t * `number` widget: the chip `widgetProps.flagWhen` asks for on this\n\t\t * row (`{ field, equals | in, text, variant }`), or null.\n\t\t *\n\t\t * @return {{text: string, variant: string}|null}\n\t\t * @spec openspec/changes/cell-config-growth/specs/cell-config-growth/spec.md#requirement-a-number-cell-reads-with-its-unit-and-a-flag\n\t\t */\n\t\tnumberFlag() {\n\t\t\tconst flag = this.widgetProps && this.widgetProps.flagWhen\n\t\t\tif (!flag || typeof flag !== 'object' || typeof flag.text !== 'string' || flag.text === '' || !indicatorApplies(flag, this.row || {})) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn { text: this.cnTranslate(flag.text), variant: isBadgeVariant(flag.variant) ? flag.variant : 'warning' }\n\t\t},\n\n\t\t/**\n\t\t * Built-in `badge`: the tooltip `widgetProps.titleWhen`\n\t\t * (`{ field, equals | in, text }`) asks for on this row, or undefined.\n\t\t *\n\t\t * @return {string|undefined}\n\t\t * @spec openspec/changes/cell-config-growth/specs/cell-config-growth/spec.md#requirement-a-badge-cell-can-carry-a-tooltip\n\t\t */\n\t\tbadgeTitle() {\n\t\t\tconst when = this.widgetProps && this.widgetProps.titleWhen\n\t\t\tif (!when || typeof when !== 'object' || typeof when.text !== 'string' || when.text === '' || !indicatorApplies(when, this.row || {})) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\treturn this.cnTranslate(when.text)\n\t\t},\n\n\t\t/**\n\t\t * Resolved cell-widget component for this column, or `null`. A column's\n\t\t * `widget` id resolves against the injected `cnCellWidgets` registry;\n\t\t * the built-in `\"badge\"` is NOT resolved here (handled inline in the\n\t\t * template) so apps can still override `\"badge\"` via the registry.\n\t\t *\n\t\t * @return {import('vue').Component|null}\n\t\t */\n\t\twidgetComponent() {\n\t\t\tif (!this.widget) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst c = this.cnCellWidgets && this.cnCellWidgets[this.widget]\n\t\t\treturn c || null\n\t\t},\n\n\t\t/**\n\t\t * Whether this cell shows Nextcloud group names: the column asks for\n\t\t * `widget: \"group\"`, or it names no widget and no formatter and its\n\t\t * property is a group (or a list of groups).\n\t\t *\n\t\t * @spec openspec/changes/nextcloud-group-surfaces/specs/data-display/spec.md#requirement-a-group-cell-shows-the-groups-display-name\n\t\t * @return {boolean}\n\t\t */\n\t\tisGroupCell() {\n\t\t\tif (this.widget === 'group') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif (this.widget || this.hasFormatter) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst p = this.property\n\t\t\tif (!p || typeof p !== 'object') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn isGroupProp(p) || (p.type === 'array' && isGroupProp(p.items))\n\t\t},\n\n\t\t/**\n\t\t * The group ids in this cell, empty ones dropped.\n\t\t *\n\t\t * @spec openspec/changes/nextcloud-group-surfaces/specs/data-display/spec.md#requirement-a-group-cell-shows-the-groups-display-name\n\t\t * @return {string[]}\n\t\t */\n\t\tgroupIds() {\n\t\t\tconst raw = this.value\n\t\t\tconst list = Array.isArray(raw) ? raw : [raw]\n\t\t\treturn list\n\t\t\t\t.filter((gid) => (typeof gid === 'string' && gid !== '') || typeof gid === 'number')\n\t\t\t\t.map((gid) => String(gid))\n\t\t},\n\n\t\t/**\n\t\t * The text a built-in widget (`badge`, `link`) or a swatch shows. An\n\t\t * enum value reads its label the way the plain enum cell does\n\t\t * (`enumLabel`: `x-enum-labels`, translated), so a list says \"Active\"\n\t\t * where the detail page does, not the stored \"active\". A column\n\t\t * formatter still wins: an app that shapes the value itself gets\n\t\t * exactly what its formatter returns.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/cell-labels-and-draft-indicator/specs/cell-labels-and-draft-indicator/spec.md#requirement-a-built-in-cell-widget-shows-an-enum-value-by-its-label\n\t\t */\n\t\t/**\n\t\t * What a `label` or `badge` cell shows: its label, worded by\n\t\t * `widgetProps.textTemplate` when given (translated, filled from the\n\t\t * row, `{value}` the label; DqPostvak \"Lijkt op 2026-0079\").\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/cell-text-template/specs/cell-text-template/spec.md\n\t\t */\n\t\tshownLabel() {\n\t\t\tconst template = this.widgetProps && this.widgetProps.textTemplate\n\t\t\tif (typeof template !== 'string' || template === '') {\n\t\t\t\treturn this.labelledValue\n\t\t\t}\n\t\t\treturn fillTemplate(this.cnTranslate(template), { ...(this.row || {}), value: this.labelledValue })\n\t\t},\n\n\t\tlabelledValue() {\n\t\t\tif (!this.hasFormatter && this.isEnum && this.hasValue && typeof this.value !== 'object') {\n\t\t\t\treturn this.enumLabel\n\t\t\t}\n\t\t\treturn String(this.formattedValue)\n\t\t},\n\n\t\t/**\n\t\t * The key the built-in `badge` looks its colour up by: the raw value\n\t\t * when `widgetProps.colorMap` has an entry for it (case-insensitive),\n\t\t * so a translated label keeps the colour the manifest keyed on the\n\t\t * stored code. Otherwise '' and the badge looks up its label, as before,\n\t\t * which keeps a map keyed on a formatter's output working.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/cell-labels-and-draft-indicator/specs/cell-labels-and-draft-indicator/spec.md#requirement-a-built-in-cell-widget-shows-an-enum-value-by-its-label\n\t\t */\n\t\tbadgeColorKey() {\n\t\t\tconst map = this.badgeColorMap\n\t\t\tif (!map || !this.hasValue || typeof this.value === 'object') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst raw = String(this.value).toLowerCase()\n\t\t\treturn Object.keys(map).some((key) => key.toLowerCase() === raw) ? String(this.value) : ''\n\t\t},\n\n\t\t/**\n\t\t * What the built-in `badge` widget takes from the record its value\n\t\t * points at (`widgetProps.looks`, filled by CnIndexPage from\n\t\t * `widgetProps.resolve`): a tone and an icon, or nothing.\n\t\t *\n\t\t * @return {{variant?: string, colorMap?: null, icon?: string}}\n\t\t * @spec openspec/changes/pill-color-field/specs/pill-color-field/spec.md#requirement-a-badge-cell-takes-its-tone-and-icon-from-the-referenced-record\n\t\t */\n\t\tbadgeLook() {\n\t\t\tconst looks = this.widgetProps && this.widgetProps.looks\n\t\t\tif (!looks || typeof looks !== 'object' || !this.hasValue || typeof this.value === 'object') {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\treturn pillLook(looks[String(this.value)])\n\t\t},\n\n\t\t/** Variant for the built-in `badge` widget: the referenced record's tone, else `widgetProps.variant`, else `'default'`. */\n\t\tbadgeVariant() {\n\t\t\treturn this.badgeLook.variant || (this.widgetProps && this.widgetProps.variant) || 'default'\n\t\t},\n\n\t\t/**\n\t\t * Color map for the built-in `badge` widget — `widgetProps.colorMap`,\n\t\t * a `{ value: variant }` map (e.g. `{ placed: 'primary', delivered: 'success' }`)\n\t\t * that CnStatusBadge resolves per label. Null when unset (falls back to\n\t\t * `badgeVariant`). Lets a manifest colour a status column without code.\n\t\t * A column-level `colorMap` (it reaches the cell as the property's\n\t\t * `colorMap`) works too, so `{ widget: 'badge', colorMap }` and\n\t\t * `{ widget: 'badge', widgetProps: { colorMap } }` read the same.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tbadgeColorMap() {\n\t\t\tif (this.badgeLook.variant) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (this.widgetProps && this.widgetProps.colorMap) || this.enumColorMap || null\n\t\t},\n\n\t\t/**\n\t\t * Color map for the auto-rendered enum badge — read from the schema\n\t\t * property's optional `colorMap` (or `x-color-map`). Null when unset, so\n\t\t * enum cells stay the default grey unless the schema opts into colours.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tenumColorMap() {\n\t\t\treturn (this.property && (this.property.colorMap || this.property['x-color-map'])) || null\n\t\t},\n\n\t\t/**\n\t\t * The manifest page id the built-in `widget:\"link\"` routes this row\n\t\t * to, or null.\n\t\t *\n\t\t * `widgetProps.routeField` names a sibling field on the row, and\n\t\t * `widgetProps.routeMap` maps that field's values to page ids, so one\n\t\t * column can link a person to `ContactDetail` and an organisation to\n\t\t * `OrganisationDetail` off `initiatorType`. A row whose value has no\n\t\t * entry in the map falls back to the fixed `widgetProps.route`, and\n\t\t * without one the cell renders as text. The map is the whole\n\t\t * vocabulary: a row value is only ever used as a KEY into it, never\n\t\t * as a route name itself, so row data cannot send a link to a page\n\t\t * the manifest did not name.\n\t\t *\n\t\t * @return {string|null} The page id, or null when none resolves.\n\t\t */\n\t\tlinkRouteName() {\n\t\t\tconst wp = this.widgetProps || {}\n\t\t\tconst map = wp.routeMap\n\t\t\tif (typeof wp.routeField === 'string' && wp.routeField !== '' && map && typeof map === 'object') {\n\t\t\t\tconst key = this.row ? this.row[wp.routeField] : undefined\n\t\t\t\tif (key !== undefined && key !== null && Object.hasOwn(map, String(key))) {\n\t\t\t\t\tconst mapped = map[String(key)]\n\t\t\t\t\tif (typeof mapped === 'string' && mapped !== '') {\n\t\t\t\t\t\treturn mapped\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn (typeof wp.route === 'string' && wp.route !== '') ? wp.route : null\n\t\t},\n\n\t\t/**\n\t\t * Items of the built-in `widget:\"refLabel\"`: one per referenced id with\n\t\t * its text, state (`label`, `loading` or `missing`) and optional link.\n\t\t *\n\t\t * @return {Array<{id: string, text: string, state: string, to: (object|null)}>}\n\t\t */\n\t\trefLabelItems() {\n\t\t\tif (this.widget !== 'refLabel') {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst wp = this.widgetProps || {}\n\t\t\tconst labels = wp.labels || {}\n\t\t\tconst raw = this.value\n\t\t\tconst list = Array.isArray(raw) ? raw : [raw]\n\t\t\treturn list\n\t\t\t\t.filter((id) => id !== null && id !== undefined && id !== '')\n\t\t\t\t.map((id) => {\n\t\t\t\t\tconst key = String(id)\n\t\t\t\t\tconst known = Object.hasOwn(labels, key)\n\t\t\t\t\tconst label = known ? labels[key] : undefined\n\t\t\t\t\tconst state = !known ? 'loading' : (label ? 'label' : 'missing')\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: key,\n\t\t\t\t\t\tstate,\n\t\t\t\t\t\ttext: state === 'label' ? label : (state === 'loading' ? '…' : key),\n\t\t\t\t\t\tto: (state === 'label' && typeof wp.route === 'string' && wp.route !== '') ? { name: wp.route, params: { id: key } } : null,\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Resolved router-link target for the built-in `widget:\"link\"`. When\n\t\t * a page id resolves (`widgetProps.route`, or the row's entry in\n\t\t * `widgetProps.routeMap`, see `linkRouteName`), returns\n\t\t * `{ name: route, params }`. Param map is `widgetProps.params`\n\t\t * (a map of route-param-name → row-field-name); when omitted,\n\t\t * defaults to `{ id: row[rowKey] }`. Returns null when no page id\n\t\t * resolves (the template falls through to `linkHref` or plain\n\t\t * text).\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tlinkRoute() {\n\t\t\tif (this.widget !== 'link') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst route = this.linkRouteName\n\t\t\tif (!route) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst paramMap = (this.widgetProps && this.widgetProps.params)\n\t\t\t\t|| { id: this.rowKey || 'id' }\n\t\t\tconst params = {}\n\t\t\tfor (const [routeParam, rowField] of Object.entries(paramMap)) {\n\t\t\t\tparams[routeParam] = this.row && this.row[rowField]\n\t\t\t}\n\t\t\treturn { name: route, params }\n\t\t},\n\n\t\t/**\n\t\t * Resolved external href for the built-in `widget:\"link\"` when\n\t\t * `widgetProps.href` is set. `{key}` placeholders in the href\n\t\t * are substituted from the row (`\"/x/{id}\"` + `row.id === \"42\"`\n\t\t * → `\"/x/42\"`). The final computed value is validated with\n\t\t * `safeHref` so row-injected values cannot introduce unsafe\n\t\t * schemes: a `javascript:` or `data:` result renders as plain text\n\t\t * instead of a link. Returns null when `href` isn't set.\n\t\t *\n\t\t * @return {string|null}\n\t\t */\n\t\tlinkHref() {\n\t\t\tif (this.widget !== 'link') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst href = this.widgetProps && this.widgetProps.href\n\t\t\tif (!href) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst resolved = String(href).replace(/\\{(\\w+)\\}/g, (_, key) => this.row && this.row[key] !== null && this.row[key] !== undefined ? String(this.row[key]) : '')\n\t\t\t// The safeHref retrofit (18700fd94) put this check here and a later\n\t\t\t// lint pass (e5ea00d51) dropped it, leaving the docblock above\n\t\t\t// promising a check the code no longer made: a template of `{url}`\n\t\t\t// put a row's `javascript:` value straight into `:href`. safeHref\n\t\t\t// answers '#' for anything unsafe; that is not a link worth\n\t\t\t// rendering, so fall through to plain text.\n\t\t\tconst safe = safeHref(resolved)\n\t\t\treturn safe === '#' ? null : safe\n\t\t},\n\n\t\t/**\n\t\t * Where a `widgetProps.href` link opens: a new tab, unless\n\t\t * `widgetProps.target` is `_self` or the href is an anchor on this\n\t\t * page (`#…`), so a row can open a card on the same page\n\t\t * (DqInstellingen: a connection row opens its settings card).\n\t\t *\n\t\t * @return {string|null} `_blank`, or null for the same tab.\n\t\t * @spec openspec/changes/cell-link-same-tab/specs/cell-link-same-tab/spec.md\n\t\t */\n\t\tlinkTarget() {\n\t\t\tconst target = this.widgetProps && this.widgetProps.target\n\t\t\tif (target === '_self' || (typeof this.linkHref === 'string' && this.linkHref.startsWith('#'))) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn '_blank'\n\t\t},\n\n\t\t/**\n\t\t * Resolved formatter function for this cell, or `null`. A column's\n\t\t * `formatter` id resolves against the injected `cnFormatters` registry.\n\t\t *\n\t\t * @return {((value: unknown, row: object, property: object, options: object) => unknown)|null}\n\t\t */\n\t\tformatterFn() {\n\t\t\tconst fn = this.formatter && this.cnFormatters && this.cnFormatters[this.formatter]\n\t\t\treturn typeof fn === 'function' ? fn : null\n\t\t},\n\n\t\t/**\n\t\t * The record of the page the table sits on, for a formatter that needs\n\t\t * more than its row: `{ objectId, object }`, both null outside a detail page.\n\t\t *\n\t\t * @return {{objectId: (string|null), object: (object|null)}}\n\t\t * @spec openspec/changes/cell-formatter-page-record/specs/cell-renderer/spec.md#requirement-a-cell-formatter-can-read-the-page-record\n\t\t */\n\t\tformatterContext() {\n\t\t\tconst unwrap = (c) => (c && typeof c === 'object' && 'value' in c ? c.value : c)\n\t\t\tconst ctx = unwrap(this.cnObjectContext) || unwrap(this.cnDetailObjectContext) || {}\n\t\t\treturn { objectId: ctx.objectId ?? null, object: ctx.object || ctx.objectData || null }\n\t\t},\n\n\t\t/** True when an explicit formatter is in play for this cell. */\n\t\thasFormatter() {\n\t\t\treturn this.formatterFn !== null\n\t\t},\n\n\t\t/**\n\t\t * The declarative format `style`, or null when no (recognised) `format`\n\t\t * spec is set. Drives the built-in currency / duration / number / percent\n\t\t * / swatch rendering paths.\n\t\t *\n\t\t * @return {string|null}\n\t\t */\n\t\tformatStyle() {\n\t\t\tconst s = this.format && this.format.style\n\t\t\treturn typeof s === 'string' ? s : null\n\t\t},\n\n\t\t/** True when a built-in NUMERIC format (currency/number/decimal/percent/duration) applies. */\n\t\thasBuiltinFormat() {\n\t\t\treturn ['currency', 'number', 'decimal', 'percent', 'duration'].includes(this.formatStyle)\n\t\t},\n\n\t\t/** True when this cell renders as a colour swatch (`format.style:\"swatch\"`). */\n\t\tisSwatch() {\n\t\t\treturn this.formatStyle === 'swatch'\n\t\t},\n\n\t\t/**\n\t\t * The swatch colour — read from the sibling row field named by\n\t\t * `format.colorField` (default `'color'`). Returns null when no usable\n\t\t * colour string is present (the dot is then omitted).\n\t\t *\n\t\t * @return {string|null}\n\t\t */\n\t\tswatchColor() {\n\t\t\tif (!this.isSwatch) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst field = (this.format && this.format.colorField) || 'color'\n\t\t\tconst c = this.row && this.row[field]\n\t\t\treturn (typeof c === 'string' && c.trim() !== '') ? c : null\n\t\t},\n\n\t\tformattedValue() {\n\t\t\tif (this.formatterFn) {\n\t\t\t\ttry {\n\t\t\t\t\treturn this.formatterFn(this.value, this.row, this.property, this.formatterOptions || undefined, this.formatterContext)\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(`[CnCellRenderer] formatter \"${this.formatter}\" threw; falling back`, e)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.hasBuiltinFormat) {\n\t\t\t\treturn this.applyBuiltinFormat()\n\t\t\t}\n\t\t\treturn formatValue(this.value, this.property, { truncate: this.truncate })\n\t\t},\n\n\t\trawTitle() {\n\t\t\t// Show full value on hover if it was truncated\n\t\t\tconst raw = this.value\n\t\t\tif (typeof raw === 'string' && raw.length > this.truncate) {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn undefined\n\t\t},\n\n\t\t/**\n\t\t * Whether the cell draws a uuid, and so takes the monospace uuid\n\t\t * style. The property's `format: \"uuid\"` alone used to decide this,\n\t\t * so a reference that a formatter or a widget had resolved to its\n\t\t * name was still drawn as code. Now the value has to be a uuid AND\n\t\t * nothing resolves it: a resolving widget or formatter, or a value\n\t\t * that is not a uuid, renders in the normal text style.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-resolved-reference-is-not-drawn-as-a-uuid\n\t\t * @return {boolean}\n\t\t */\n\t\tshowsUuid() {\n\t\t\tif (this.property?.format !== 'uuid') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (this.widgetComponent || this.widget || this.hasFormatter) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn typeof this.value === 'string' && UUID_PATTERN.test(this.value)\n\t\t},\n\n\t\tcellClass() {\n\t\t\tconst classes = []\n\t\t\tif (this.propertyType === 'boolean') {\n\t\t\t\tclasses.push('cn-cell-renderer--boolean')\n\t\t\t}\n\t\t\tif (this.isEnum) {\n\t\t\t\tclasses.push('cn-cell-renderer--enum')\n\t\t\t}\n\t\t\tif (this.property?.format === 'date-time' || this.property?.format === 'date') {\n\t\t\t\tclasses.push('cn-cell-renderer--date')\n\t\t\t}\n\t\t\tif (this.showsUuid) {\n\t\t\t\tclasses.push('cn-cell-renderer--uuid')\n\t\t\t}\n\t\t\tif (this.propertyType === 'integer' || this.propertyType === 'number') {\n\t\t\t\tclasses.push('cn-cell-renderer--number')\n\t\t\t}\n\t\t\treturn classes\n\t\t},\n\t},\n\n\twatch: {\n\t\tavatarGroupId: {\n\t\t\timmediate: true,\n\t\t\t/**\n\t\t\t * Look the group's display name up (cached, once per id per page).\n\t\t\t *\n\t\t\t * @param {string} gid The group id.\n\t\t\t */\n\t\t\thandler(gid) {\n\t\t\t\tif (gid !== '') {\n\t\t\t\t\tloadGroupDisplayName(gid)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmounted() {\n\t\t// Warn once per column-property-name when `widget:\"link\"` resolves\n\t\t// to neither a `route` nor an `href` — silent fallback hides\n\t\t// manifest mistakes, an every-row warn is too noisy.\n\t\t// A column with a `routeMap` has declared its targets; a row whose\n\t\t// value is not in the map (an empty requester, say) is data, not a\n\t\t// manifest mistake, so it renders as text without the warning. The\n\t\t// same holds for a declared `href` whose resolved value safeHref\n\t\t// refused: the manifest is fine, the row value is not.\n\t\tif (\n\t\t\tthis.widget === 'link'\n\t\t\t&& !this.linkRoute\n\t\t\t&& !this.widgetProps?.href\n\t\t\t&& this.widgetProps?.fallback !== 'silent'\n\t\t\t&& !(this.widgetProps?.routeMap && typeof this.widgetProps.routeMap === 'object')\n\t\t) {\n\t\t\tconst key = this.property?.title || String(this.value).slice(0, 20)\n\t\t\tif (!WARNED_LINK_KEYS.has(key)) {\n\t\t\t\tWARNED_LINK_KEYS.add(key)\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn(`[CnCellRenderer] widget:\"link\" on \"${key}\" has no resolvable target — set widgetProps.route (page id) or widgetProps.href (URL with optional {field} placeholders); cell falls back to plain text.`)\n\t\t\t}\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Run an `actions` widget button for this row through the page.\n\t\t *\n\t\t * @param {{key: string}} item The button.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/table-actions-column/specs/index-page/spec.md#requirement-a-column-can-show-row-actions-as-buttons\n\t\t */\n\t\trunAction(item) {\n\t\t\tif (typeof this.widgetProps?.run === 'function') {\n\t\t\t\tthis.widgetProps.run(item.key, this.row)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The \"switch\" widget was flipped: show the new state and hand it to\n\t\t * `widgetProps.onToggle(row, on)`. A rejection puts the switch back.\n\t\t *\n\t\t * @param {boolean} on The new state.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onSwitchToggle(on) {\n\t\t\tconst toggle = this.widgetProps && this.widgetProps.onToggle\n\t\t\tif (typeof toggle !== 'function' || this.switchPending !== null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.switchPending = on === true\n\t\t\ttry {\n\t\t\t\tawait toggle(this.row, on === true)\n\t\t\t} catch (error) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnCellRenderer] switch toggle failed', error)\n\t\t\t} finally {\n\t\t\t\tthis.switchPending = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The initials of a name, at most two letters (first and last word).\n\t\t *\n\t\t * @param {string} name The name.\n\t\t * @return {string} The initials, upper case, or ''.\n\t\t * @spec openspec/changes/screens-avatar-cell-resolve/specs/data-table/spec.md\n\t\t */\n\t\tinitialsOf(name) {\n\t\t\tconst words = String(name || '').trim().split(/\\s+/).filter((word) => word !== '')\n\t\t\tif (words.length === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst first = Array.from(words[0])[0] || ''\n\t\t\tconst last = words.length > 1 ? (Array.from(words[words.length - 1])[0] || '') : ''\n\t\t\treturn (first + last).toLocaleUpperCase()\n\t\t},\n\n\t\t/**\n\t\t * Render the cell value per the declarative `format` spec (currency /\n\t\t * number / percent / duration). Mirrors CnStatWidget's formatting so a\n\t\t * KPI tile and a table column read identically. Non-numeric values fall\n\t\t * back to `formatValue`; an empty value renders an em-dash.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tapplyBuiltinFormat() {\n\t\t\tif (!this.hasValue) {\n\t\t\t\treturn '—'\n\t\t\t}\n\t\t\tconst fmt = this.format || {}\n\t\t\tif (fmt.style === 'duration') {\n\t\t\t\treturn this.formatDuration()\n\t\t\t}\n\t\t\tconst num = Number(this.value)\n\t\t\tif (!Number.isFinite(num)) {\n\t\t\t\treturn formatValue(this.value, this.property, { truncate: this.truncate })\n\t\t\t}\n\t\t\t// `decimal` defaults to one fraction digit, as formatMetricValue does.\n\t\t\tconst defaultDecimals = { currency: 2, decimal: 1 }\n\t\t\tconst decimals = Number.isFinite(fmt.decimals)\n\t\t\t\t? fmt.decimals\n\t\t\t\t: (defaultDecimals[fmt.style] ?? 0)\n\t\t\tlet body\n\t\t\tif (fmt.style === 'currency') {\n\t\t\t\t// `format.currencyField` reads the code from the row's own field.\n\t\t\t\tbody = formatMoney(num, { currency: fmt.currency, currencyField: fmt.currencyField, decimals }, this.row)\n\t\t\t} else if (fmt.style === 'percent') {\n\t\t\t\t// Values are stored as the literal percent (83.3), not a 0–1 ratio.\n\t\t\t\tbody = new Intl.NumberFormat(undefined, {\n\t\t\t\t\tminimumFractionDigits: decimals,\n\t\t\t\t\tmaximumFractionDigits: decimals,\n\t\t\t\t}).format(num) + '%'\n\t\t\t} else {\n\t\t\t\tbody = new Intl.NumberFormat(undefined, {\n\t\t\t\t\tminimumFractionDigits: decimals,\n\t\t\t\t\tmaximumFractionDigits: decimals,\n\t\t\t\t}).format(num)\n\t\t\t}\n\t\t\treturn `${fmt.prefix || ''}${body}${fmt.suffix || ''}`\n\t\t},\n\n\t\t/**\n\t\t * Render a numeric duration compactly (`1u 23m`, `45m 10s`, `12s`). The\n\t\t * raw value is seconds unless `format.unit` is `'milliseconds'` /\n\t\t * `'minutes'` / `'hours'`. The hour suffix is `u` (uur) to read naturally\n\t\t * under NL theming while staying digit-led for other locales.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tformatDuration() {\n\t\t\tconst fmt = this.format || {}\n\t\t\tlet secs = Number(this.value)\n\t\t\tif (!Number.isFinite(secs)) {\n\t\t\t\treturn formatValue(this.value, this.property, { truncate: this.truncate })\n\t\t\t}\n\t\t\tif (fmt.unit === 'milliseconds') {\n\t\t\t\t// Sub-second durations are the common case for a millisecond\n\t\t\t\t// field (request/step timings), and they would all collapse to\n\t\t\t\t// the `0s` floor below — so render them in ms directly.\n\t\t\t\tconst ms = Math.round(secs)\n\t\t\t\tif (Math.abs(ms) < 1000) {\n\t\t\t\t\treturn `${fmt.prefix || ''}${ms}ms${fmt.suffix || ''}`\n\t\t\t\t}\n\t\t\t\tsecs = ms / 1000\n\t\t\t} else if (fmt.unit === 'minutes') {\n\t\t\t\tsecs *= 60\n\t\t\t} else if (fmt.unit === 'hours') {\n\t\t\t\tsecs *= 3600\n\t\t\t}\n\t\t\tsecs = Math.round(secs)\n\t\t\tconst sign = secs < 0 ? '-' : ''\n\t\t\tsecs = Math.abs(secs)\n\t\t\tconst h = Math.floor(secs / 3600)\n\t\t\tconst m = Math.floor((secs % 3600) / 60)\n\t\t\tconst s = secs % 60\n\t\t\tconst parts = []\n\t\t\tif (h > 0) {\n\t\t\t\tparts.push(`${h}u`)\n\t\t\t}\n\t\t\tif (m > 0) {\n\t\t\t\tparts.push(`${m}m`)\n\t\t\t}\n\t\t\tif (s > 0 && h === 0) {\n\t\t\t\tparts.push(`${s}s`)\n\t\t\t}\n\t\t\tif (parts.length === 0) {\n\t\t\t\tparts.push('0s')\n\t\t\t}\n\t\t\treturn `${fmt.prefix || ''}${sign}${parts.join(' ')}${fmt.suffix || ''}`\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-cell-renderer__actions {\n\tdisplay: inline-flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n}\n\n.cn-cell-renderer__icon-label {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-weight: 600;\n}\n\n.cn-cell-renderer__switch {\n\tdisplay: inline-flex;\n}\n\n.cn-cell-renderer__mono {\n\tfont-family: monospace;\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cell-renderer--uuid {\n\tfont-family: monospace;\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cell-renderer--number {\n\tfont-variant-numeric: tabular-nums;\n}\n\n.cn-cell-renderer--date {\n\twhite-space: nowrap;\n}\n\n.cn-cell-renderer__date-state {\n\tmargin-inline-start: 8px;\n\tcolor: var(--color-success-text, var(--color-main-text));\n}\n\n.cn-cell-renderer__date-state--past {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cell-renderer__dash {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cell-renderer__avatar {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 8px;\n\tmax-width: 100%;\n\tvertical-align: middle;\n}\n\n.cn-cell-renderer__avatar-picture {\n\tdisplay: inline-flex;\n\tflex: none;\n}\n\n.cn-cell-renderer__date-block {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 12px;\n\twhite-space: nowrap;\n}\n\n.cn-cell-renderer__date-tile {\n\tdisplay: flex;\n\tflex: 0 0 46px;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: 5px 0;\n\tborder-radius: 8px;\n\tbackground-color: var(--color-background-dark);\n}\n\n.cn-cell-renderer__date-day {\n\tfont-size: 17px;\n\tline-height: 1;\n}\n\n.cn-cell-renderer__date-month,\n.cn-cell-renderer__date-time {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cell-renderer__date-month {\n\tfont-size: 12px;\n}\n\n.cn-cell-renderer__date-lines {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-cell-renderer__date-weekday {\n\tfont-weight: 600;\n}\n\n.cn-cell-renderer__avatar-initials {\n\tdisplay: inline-flex;\n\tflex: none;\n\talign-items: center;\n\tjustify-content: center;\n\tborder-radius: 50%;\n\tbackground-color: var(--color-primary-element-light);\n\tcolor: var(--color-primary-element-light-text);\n\tfont-size: 0.75em;\n\tfont-weight: 700;\n\tline-height: 1;\n}\n\n.cn-cell-renderer__avatar-text {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmin-width: 0;\n}\n\n.cn-cell-renderer__avatar-name {\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\t/* The name is the cell's text: it keeps the weight, size and colour the\n\t cell sets, as a plain value does. */\n\tfont: inherit;\n\tcolor: inherit;\n}\n\n.cn-cell-renderer__avatar-secondary {\n\tmin-width: 0;\n\toverflow: hidden;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n\tfont-weight: 400;\n\tline-height: 1.35;\n\ttext-overflow: ellipsis;\n\ttext-decoration: none;\n\twhite-space: nowrap;\n}\n\n/* A date that matched a rule is also set in a heavier weight, so the signal\n does not rest on colour alone. */\n.cn-cell-renderer__date--success,\n.cn-cell-renderer__date--warning,\n.cn-cell-renderer__date--error,\n.cn-cell-renderer__age--success,\n.cn-cell-renderer__age--warning,\n.cn-cell-renderer__age--error {\n\tfont-weight: 600;\n}\n\n.cn-cell-renderer__age {\n\twhite-space: nowrap;\n}\n\n/* The progress cell (abstract-progress-cell): a 56 x 8 bar, the percentage\n 8px beside it in the bar's tone, and an optional age badge after them, as\n PqLeads' Winkans column. The value is bold, so the tone is not the only signal. */\n.cn-cell-renderer__progress {\n\tdisplay: inline-flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 4px 8px;\n}\n\n.cn-cell-renderer__progress-line {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 8px;\n\twhite-space: nowrap;\n}\n\n.cn-cell-renderer__progress-bar {\n\tdisplay: inline-block;\n\twidth: 56px;\n\theight: 8px;\n\tborder-radius: 4px;\n\tbackground: var(--color-background-dark);\n\toverflow: hidden;\n}\n\n.cn-cell-renderer__progress-fill {\n\tdisplay: block;\n\theight: 100%;\n}\n\n.cn-cell-renderer__progress-fill--success {\n\tbackground: var(--color-element-success, var(--color-success));\n}\n\n.cn-cell-renderer__progress-fill--warning {\n\tbackground: var(--color-element-warning, var(--color-warning));\n}\n\n.cn-cell-renderer__progress-fill--error {\n\tbackground: var(--color-element-error, var(--color-error));\n}\n\n.cn-cell-renderer__progress-value {\n\tfont-size: 14px;\n\tfont-weight: 700;\n}\n\n.cn-cell-renderer__progress-value--success {\n\tcolor: var(--color-text-success, var(--color-success-text));\n}\n\n.cn-cell-renderer__progress-value--warning {\n\tcolor: var(--color-text-warning, var(--color-warning-text));\n}\n\n.cn-cell-renderer__progress-value--error {\n\tcolor: var(--color-text-error, var(--color-error-text));\n}\n\n.cn-cell-renderer__progress-badge {\n\tdisplay: inline-block;\n\tpadding: 3px 10px;\n\tborder-radius: 12px;\n\tfont-size: 13px;\n\tfont-weight: 600;\n\twhite-space: nowrap;\n}\n\n.cn-cell-renderer__progress-badge--error {\n\tcolor: var(--color-text-error, var(--color-error-text));\n\tbackground: var(--color-error-hover, var(--color-background-dark));\n\tbackground: color-mix(in srgb, var(--color-error) 12%, transparent);\n}\n\n.cn-cell-renderer__progress-badge--warning {\n\tcolor: var(--color-text-warning, var(--color-warning-text));\n\tbackground: color-mix(in srgb, var(--color-warning) 14%, transparent);\n}\n\n.cn-cell-renderer__progress-badge--success {\n\tcolor: var(--color-text-success, var(--color-success-text));\n\tbackground: color-mix(in srgb, var(--color-success) 12%, transparent);\n}\n\n.cn-cell-renderer__age--success {\n\tcolor: var(--color-text-success, var(--color-success-text));\n}\n\n.cn-cell-renderer__age--warning {\n\tcolor: var(--color-text-warning, var(--color-warning-text));\n}\n\n.cn-cell-renderer__age--error {\n\tcolor: var(--color-text-error, var(--color-error-text));\n}\n\n.cn-cell-renderer__due--empty {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* A withheld value: the lock and the word in the muted text colour. */\n.cn-cell-renderer__withheld {\n\tdisplay: inline-flex;\n\tgap: 4px;\n\talign-items: center;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cell-renderer__withheld-icon {\n\tflex: 0 0 auto;\n}\n\n.cn-cell-renderer__withheld-text {\n\tfont-style: italic;\n}\n\n/* A number cell: the flagged number reads in its variant's text colour, with\n the chip saying why (never colour alone). */\n.cn-cell-renderer__number {\n\tdisplay: inline-flex;\n\tgap: 4px;\n\talign-items: center;\n\twhite-space: nowrap;\n}\n\n.cn-cell-renderer__number--warning {\n\tcolor: var(--color-text-warning, var(--color-warning-text));\n\tfont-weight: 600;\n}\n\n.cn-cell-renderer__number--error {\n\tcolor: var(--color-text-error, var(--color-error-text));\n\tfont-weight: 600;\n}\n\n.cn-cell-renderer__date--success {\n\tcolor: var(--color-text-success, var(--color-success-text));\n}\n\n.cn-cell-renderer__date--warning {\n\tcolor: var(--color-text-warning, var(--color-warning-text));\n}\n\n.cn-cell-renderer__date--error {\n\tcolor: var(--color-text-error, var(--color-error-text));\n}\n\n.cn-cell-renderer__date--muted {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* The text variant, not the fill: from Nextcloud 32 `--color-success` is a\n background colour and too pale for a 16px glyph on a light theme. */\n.cn-cell-renderer__icon--success {\n\tcolor: var(--color-text-success, var(--color-success-text, var(--color-success)));\n}\n\n.cn-cell-renderer__swatch-wrap {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-cell-renderer__swatch-dot {\n\tdisplay: inline-block;\n\twidth: 12px;\n\theight: 12px;\n\tborder-radius: 50%;\n\tflex-shrink: 0;\n\tborder: 1px solid var(--color-border-dark);\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_createVNode","_createElementVNode","_toDisplayString","_createBlock","_resolveDynamicComponent","_mergeProps","_Fragment","_openBlock","_renderList","_withModifiers","_createTextVNode","_normalizeStyle"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnCellRenderer.vue3.js","sources":["../../../../src/components/CnCellRenderer/CnCellRenderer.vue?vue&type=template&id=abd08d9e&lang.js"],"sourcesContent":["<template>\n\t<span class=\"cn-cell-renderer\" :class=\"cellClass\">\n\t\t<!-- OpenRegister's withheld marker ({ \"@withheld\": true, reason }),\n\t\t before every other path: a lock and \"Withheld\", the reason as the\n\t\t tooltip. The icon is decoration; the text carries the meaning. -->\n\t\t<span v-if=\"withheld\"\n\t\t\tclass=\"cn-cell-renderer__withheld\"\n\t\t\t:title=\"withheldHint\"\n\t\t\tdata-testid=\"cn-cell-withheld\">\n\t\t\t<Lock :size=\"14\" class=\"cn-cell-renderer__withheld-icon\" aria-hidden=\"true\" />\n\t\t\t<span class=\"cn-cell-renderer__withheld-text\">{{ withheldText }}</span>\n\t\t</span>\n\n\t\t<!-- Consumer-registered cell widget (cnCellWidgets[column.widget]) -->\n\t\t<component\n\t\t\t:is=\"widgetComponent\"\n\t\t\tv-else-if=\"widgetComponent\"\n\t\t\t:value=\"value\"\n\t\t\t:row=\"row\"\n\t\t\t:property=\"property\"\n\t\t\t:formatted=\"formattedValue\"\n\t\t\tv-bind=\"widgetProps\" />\n\n\t\t<!-- Built-in \"group\" widget: a Nextcloud group id (or a list of them)\n\t\t shown as the group's display name, cached per id. Used on its own\n\t\t for a property marked `referenceType: nextcloud-group` /\n\t\t `format: nc-group` unless the column has a formatter. -->\n\t\t<template v-else-if=\"isGroupCell\">\n\t\t\t<span v-if=\"!groupIds.length\" class=\"cn-cell-renderer__dash\">—</span>\n\t\t\t<span v-else class=\"cn-cell-renderer__groups\">\n\t\t\t\t<template v-for=\"(gid, i) in groupIds\" :key=\"gid\">\n\t\t\t\t\t<span v-if=\"i > 0\">, </span>\n\t\t\t\t\t<CnGroupNameCell :gid=\"gid\" />\n\t\t\t\t</template>\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"actions\" widget: row actions as inline buttons (\"Aan zaak\n\t\t koppelen\", \"Nieuwe zaak\"). The page resolves the buttons\n\t\t (widgetProps.items) and runs a click (widgetProps.run). -->\n\t\t<template v-else-if=\"widget === 'actions'\">\n\t\t\t<span class=\"cn-cell-renderer__actions\" data-testid=\"cn-cell-actions\">\n\t\t\t\t<NcButton\n\t\t\t\t\tv-for=\"item in actionItems\"\n\t\t\t\t\t:key=\"item.key\"\n\t\t\t\t\t:variant=\"item.variant\"\n\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t:disabled=\"item.disabled\"\n\t\t\t\t\t:data-testid=\"`cn-cell-action-${item.key}`\"\n\t\t\t\t\t@click.stop=\"runAction(item)\">\n\t\t\t\t\t{{ item.label }}\n\t\t\t\t</NcButton>\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"label\" widget: the value's label (an enum's translated\n\t\t label) as plain text, where a list wants words and no pill. -->\n\t\t<template v-else-if=\"widget === 'label'\">\n\t\t\t<span v-if=\"hasValue\" :title=\"rawTitle\">{{ shownLabel }}</span>\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"badge\" widget: renders the value as a status pill. An enum\n\t\t value shows its label (x-enum-labels, translated) unless a formatter\n\t\t shapes it; the colour stays keyed on the raw value. -->\n\t\t<template v-else-if=\"widget === 'badge'\">\n\t\t\t<CnStatusBadge v-if=\"hasValue\"\n\t\t\t\t:label=\"shownLabel\"\n\t\t\t\t:variant=\"badgeVariant\"\n\t\t\t\t:colorMap=\"badgeColorMap\"\n\t\t\t\t:colorKey=\"badgeColorKey\"\n\t\t\t\t:title=\"badgeTitle\">\n\t\t\t\t<template v-if=\"badgeLook.icon\" #icon>\n\t\t\t\t\t<CnIcon :name=\"badgeLook.icon\" :size=\"14\" class=\"cn-cell-renderer__badge-icon\" />\n\t\t\t\t</template>\n\t\t\t</CnStatusBadge>\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"fkResolve\" widget — resolves a reference uuid to the related\n\t\t object's display label via the object store (per-schema caching).\n\t\t Config: widgetProps { register, schema, labelField, matchField?,\n\t\t refField?, labelTemplate?, labelPath? }, or schemaFrom { field,\n\t\t map } to read the schema off the row. -->\n\t\t<template v-else-if=\"widget === 'fkResolve'\">\n\t\t\t<CnFkResolveCell\n\t\t\t\tv-if=\"hasValue || fkRefValue !== null\"\n\t\t\t\t:value=\"value\"\n\t\t\t\t:register=\"(widgetProps && widgetProps.register) || ''\"\n\t\t\t\t:schema=\"fkSchema\"\n\t\t\t\t:labelField=\"(widgetProps && widgetProps.labelField) || 'name'\"\n\t\t\t\t:matchField=\"(widgetProps && widgetProps.matchField) || ''\"\n\t\t\t\t:refValue=\"fkRefValue\"\n\t\t\t\t:labelTemplate=\"(widgetProps && widgetProps.labelTemplate) || ''\"\n\t\t\t\t:labelPath=\"(widgetProps && widgetProps.labelPath) || ''\" />\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"refLabel\" widget — shows the label of the referenced object(s)\n\t\t from the batch CnIndexPage resolved for the column\n\t\t (widgetProps { labels: { id: label|null }, route? }). An id that is not\n\t\t in `labels` yet shows a placeholder; one that resolved to null shows the\n\t\t id in mono. With widgetProps.route (a manifest page id) each label links\n\t\t to that page for the referenced id. -->\n\t\t<template v-else-if=\"widget === 'refLabel'\">\n\t\t\t<span v-if=\"!refLabelItems.length\" class=\"cn-cell-renderer__dash\">—</span>\n\t\t\t<span v-else class=\"cn-cell-renderer__ref-labels\" data-testid=\"cn-cell-ref-labels\">\n\t\t\t\t<template v-for=\"(item, i) in refLabelItems\" :key=\"item.id\">\n\t\t\t\t\t<span v-if=\"i > 0\">, </span>\n\t\t\t\t\t<component\n\t\t\t\t\t\t:is=\"item.to ? 'router-link' : 'span'\"\n\t\t\t\t\t\t:to=\"item.to || undefined\"\n\t\t\t\t\t\t:class=\"[item.to ? 'cn-cell-renderer__link' : '', item.state === 'missing' ? 'cn-cell-renderer__mono' : '']\"\n\t\t\t\t\t\t:title=\"item.state === 'label' ? item.id : undefined\">\n\t\t\t\t\t\t{{ item.text }}\n\t\t\t\t\t</component>\n\t\t\t\t</template>\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"link\" widget — renders the (possibly formatter-shaped) value\n\t\t as a router-link (when widgetProps.route is a manifest page id, or\n\t\t widgetProps.routeMap picks one per row by widgetProps.routeField) or\n\t\t an external anchor (when widgetProps.href is set). Falls back to\n\t\t plain text + a once-per-session console.warn when neither resolves. -->\n\t\t<template v-else-if=\"widget === 'link'\">\n\t\t\t<router-link\n\t\t\t\tv-if=\"linkRoute\"\n\t\t\t\t:to=\"linkRoute\"\n\t\t\t\tclass=\"cn-cell-renderer__link\">\n\t\t\t\t{{ labelledValue }}\n\t\t\t</router-link>\n\t\t\t<a\n\t\t\t\tv-else-if=\"linkHref\"\n\t\t\t\t:href=\"linkHref\"\n\t\t\t\t:target=\"linkTarget\"\n\t\t\t\t:rel=\"linkTarget ? 'noopener' : null\"\n\t\t\t\tclass=\"cn-cell-renderer__link\">\n\t\t\t\t{{ labelledValue }}\n\t\t\t</a>\n\t\t\t<span v-else :title=\"rawTitle\">{{ labelledValue }}</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"avatar\" widget: a person as an avatar with the name beside\n\t\t it. A user id (widgetProps.userField, or widgetProps.user for a cell\n\t\t that holds the id itself) gets the Nextcloud avatar; a free name\n\t\t gets its initials. The picture is decoration: the name is the text. -->\n\t\t<!-- The avatar of a REFERENCED object (widgetProps.resolve\n\t\t { register, schema, labelField }): the cell holds a uuid, the name\n\t\t is resolved as an fkResolve cell resolves it, and its initials sit\n\t\t beside it. -->\n\t\t<template v-else-if=\"widget === 'avatar' && avatarResolve\">\n\t\t\t<CnFkResolveCell\n\t\t\t\tv-if=\"hasValue && typeof value !== 'object'\"\n\t\t\t\tv-slot=\"{ label, resolved }\"\n\t\t\t\t:value=\"value\"\n\t\t\t\t:register=\"avatarResolve.register\"\n\t\t\t\t:schema=\"avatarResolve.schema\"\n\t\t\t\t:labelField=\"avatarResolve.labelField || 'name'\">\n\t\t\t\t<span class=\"cn-cell-renderer__avatar\" data-testid=\"cn-cell-avatar-resolved\">\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-cell-renderer__avatar-initials\"\n\t\t\t\t\t\t:style=\"{ width: avatarSize + 'px', height: avatarSize + 'px' }\"\n\t\t\t\t\t\taria-hidden=\"true\">{{ resolved ? initialsOf(label) : '' }}</span>\n\t\t\t\t\t<span class=\"cn-cell-renderer__avatar-text\">\n\t\t\t\t\t\t<span class=\"cn-cell-renderer__avatar-name\" :title=\"label\">{{ label }}</span>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-if=\"secondary\"\n\t\t\t\t\t\t\tclass=\"cn-cell-renderer__avatar-secondary\"\n\t\t\t\t\t\t\tdata-testid=\"cn-cell-secondary\">{{ secondary }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t</span>\n\t\t\t</CnFkResolveCell>\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\t\t<template v-else-if=\"widget === 'avatar'\">\n\t\t\t<span v-if=\"avatarName || avatarUserId\" class=\"cn-cell-renderer__avatar\">\n\t\t\t\t<span v-if=\"avatarUserId\" class=\"cn-cell-renderer__avatar-picture\" aria-hidden=\"true\">\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t:user=\"avatarUserId\"\n\t\t\t\t\t\t:displayName=\"avatarName || avatarUserId\"\n\t\t\t\t\t\t:size=\"avatarSize\"\n\t\t\t\t\t\t:disableMenu=\"true\"\n\t\t\t\t\t\t:disableTooltip=\"true\" />\n\t\t\t\t</span>\n\t\t\t\t<span\n\t\t\t\t\tv-else\n\t\t\t\t\tclass=\"cn-cell-renderer__avatar-initials\"\n\t\t\t\t\t:style=\"{ width: avatarSize + 'px', height: avatarSize + 'px' }\"\n\t\t\t\t\taria-hidden=\"true\">{{ avatarInitials }}</span>\n\t\t\t\t<span class=\"cn-cell-renderer__avatar-text\">\n\t\t\t\t\t<span class=\"cn-cell-renderer__avatar-name\" :title=\"avatarName || avatarUserId\">{{ avatarName || avatarUserId }}</span>\n\t\t\t\t\t<!-- The column's secondary line (e-mail), under the name and beside the picture. -->\n\t\t\t\t\t<span\n\t\t\t\t\t\tv-if=\"secondary\"\n\t\t\t\t\t\tclass=\"cn-cell-renderer__avatar-secondary\"\n\t\t\t\t\t\tdata-testid=\"cn-cell-secondary\">{{ secondary }}</span>\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"age\" widget: how long something has waited (\"4 uur\",\n\t\t \"3 dagen\"), coloured by widgetProps.variantWhen rules on the days\n\t\t the cell shows. The full date is the tooltip. -->\n\t\t<template v-else-if=\"widget === 'age'\">\n\t\t\t<time\n\t\t\t\tv-if=\"dateTimestamp\"\n\t\t\t\tclass=\"cn-cell-renderer__age\"\n\t\t\t\t:class=\"ageVariant ? 'cn-cell-renderer__age--' + ageVariant : null\"\n\t\t\t\t:datetime=\"dateTimestamp.toISOString()\"\n\t\t\t\t:title=\"absoluteDateLabel\">{{ ageLabel }}</time>\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"date\" widget: a date whose colour follows\n\t\t widgetProps.variantWhen, rules on the number of days until it\n\t\t (0 = today, negative = overdue). -->\n\t\t<template v-else-if=\"widget === 'date'\">\n\t\t\t<!-- `widgetProps.due`: the due text with its tone (\"3 dagen te laat\",\n\t\t\t \"Vandaag\", the date, or the empty text). -->\n\t\t\t<template v-if=\"dueResult\">\n\t\t\t\t<time\n\t\t\t\t\tv-if=\"dateTimestamp\"\n\t\t\t\t\tclass=\"cn-cell-renderer__date\"\n\t\t\t\t\t:class=\"dueClass\"\n\t\t\t\t\t:datetime=\"dateTimestamp.toISOString()\"\n\t\t\t\t\t:title=\"absoluteDateLabel\"\n\t\t\t\t\tdata-testid=\"cn-cell-renderer-due\">{{ dueResult.text }}</time>\n\t\t\t\t<span v-else class=\"cn-cell-renderer__date cn-cell-renderer__due--empty\" data-testid=\"cn-cell-renderer-due\">{{ dueResult.text }}</span>\n\t\t\t</template>\n\t\t\t<!-- `widgetProps.layout: \"block\"`: a date tile (day over short month)\n\t\t\t beside the weekday and the time range to `widgetProps.endField`. -->\n\t\t\t<time\n\t\t\t\tv-else-if=\"dateTimestamp && dateBlock\"\n\t\t\t\tclass=\"cn-cell-renderer__date-block\"\n\t\t\t\t:datetime=\"dateTimestamp.toISOString()\"\n\t\t\t\t:title=\"absoluteDateLabel\"\n\t\t\t\tdata-testid=\"cn-cell-date-block\">\n\t\t\t\t<span class=\"cn-cell-renderer__date-tile\">\n\t\t\t\t\t<strong class=\"cn-cell-renderer__date-day\">{{ dateBlock.day }}</strong>\n\t\t\t\t\t<span class=\"cn-cell-renderer__date-month\">{{ dateBlock.month }}</span>\n\t\t\t\t</span>\n\t\t\t\t<span class=\"cn-cell-renderer__date-lines\">\n\t\t\t\t\t<span class=\"cn-cell-renderer__date-weekday\">{{ dateBlock.weekday }}</span>\n\t\t\t\t\t<span v-if=\"dateBlock.time\" class=\"cn-cell-renderer__date-time\">{{ dateBlock.time }}</span>\n\t\t\t\t</span>\n\t\t\t</time>\n\t\t\t<time\n\t\t\t\tv-else-if=\"dateTimestamp\"\n\t\t\t\tclass=\"cn-cell-renderer__date\"\n\t\t\t\t:class=\"dateVariant ? 'cn-cell-renderer__date--' + dateVariant : null\"\n\t\t\t\t:datetime=\"dateTimestamp.toISOString()\"\n\t\t\t\t:title=\"isRelativeDate ? absoluteDateLabel : rawTitle\">{{ dateLabel }}</time>\n\t\t\t<span v-else-if=\"!dateStateText\" class=\"cn-cell-renderer__dash\">—</span>\n\t\t\t<!-- `widgetProps.pastText` / `otherText`: a state beside the date (\"Ended\", \"Active\"). -->\n\t\t\t<span v-if=\"dateStateText\"\n\t\t\t\tclass=\"cn-cell-renderer__date-state\"\n\t\t\t\t:class=\"{ 'cn-cell-renderer__date-state--past': dateIsPast }\"\n\t\t\t\tdata-testid=\"cn-cell-date-state\">{{ dateStateText }}</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"number\" widget: the value with its unit\n\t\t (widgetProps.unit { one, other }) and, when widgetProps.flagWhen\n\t\t holds on the row, a chip with text beside it. An empty value is an\n\t\t empty cell, never 0. -->\n\t\t<template v-else-if=\"widget === 'number'\">\n\t\t\t<span v-if=\"numberLabel !== ''\"\n\t\t\t\tclass=\"cn-cell-renderer__number\"\n\t\t\t\t:class=\"numberFlag ? 'cn-cell-renderer__number--' + numberFlag.variant : null\"\n\t\t\t\tdata-testid=\"cn-cell-number\">\n\t\t\t\t{{ numberLabel }}\n\t\t\t\t<CnStatusBadge v-if=\"numberFlag\"\n\t\t\t\t\tclass=\"cn-cell-renderer__number-flag\"\n\t\t\t\t\t:label=\"numberFlag.text\"\n\t\t\t\t\t:variant=\"numberFlag.variant\"\n\t\t\t\t\tsize=\"small\"\n\t\t\t\t\tdata-testid=\"cn-cell-number-flag\" />\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"progress\" widget: a bar with the percentage beside it,\n\t\t toned by widgetProps.thresholds, and an optional age badge\n\t\t (\"Out of date 12 days\") from widgetProps.badge. -->\n\t\t<template v-else-if=\"widget === 'progress'\">\n\t\t\t<span v-if=\"progressPercent === null\" class=\"cn-cell-renderer__dash\">—</span>\n\t\t\t<span v-else class=\"cn-cell-renderer__progress\">\n\t\t\t\t<span class=\"cn-cell-renderer__progress-line\">\n\t\t\t\t\t<span class=\"cn-cell-renderer__progress-bar\" aria-hidden=\"true\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"cn-cell-renderer__progress-fill\"\n\t\t\t\t\t\t\t:class=\"'cn-cell-renderer__progress-fill--' + progressTone\"\n\t\t\t\t\t\t\t:style=\"{ width: progressPercent + '%' }\" />\n\t\t\t\t\t</span>\n\t\t\t\t\t<strong\n\t\t\t\t\t\tclass=\"cn-cell-renderer__progress-value\"\n\t\t\t\t\t\t:class=\"'cn-cell-renderer__progress-value--' + progressTone\">{{ progressPercent }}%</strong>\n\t\t\t\t</span>\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"progressBadge\"\n\t\t\t\t\tclass=\"cn-cell-renderer__progress-badge\"\n\t\t\t\t\t:class=\"'cn-cell-renderer__progress-badge--' + progressBadge.tone\">{{ progressBadge.label }}</span>\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"switch\" widget: an on/off switch with the value as its\n\t\t label. widgetProps { checked(row) => boolean, onToggle(row, on) =>\n\t\t Promise }. The switch shows the new state at once and returns to\n\t\t the old one when onToggle rejects. Clicks stay in the cell, so\n\t\t toggling never opens the row. -->\n\t\t<template v-else-if=\"widget === 'switch'\">\n\t\t\t<span class=\"cn-cell-renderer__switch\"\n\t\t\t\tdata-testid=\"cn-cell-switch\"\n\t\t\t\t@click.stop\n\t\t\t\t@keydown.stop>\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t:modelValue=\"switchChecked\"\n\t\t\t\t\t:disabled=\"switchBusy || typeof (widgetProps && widgetProps.onToggle) !== 'function'\"\n\t\t\t\t\t@update:modelValue=\"onSwitchToggle\">\n\t\t\t\t\t{{ hasValue ? labelledValue : '' }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Built-in \"iconLabel\" widget: an icon before the value. widgetProps\n\t\t { icon }: a component or an icon name, or a function of the row\n\t\t returning one. The icon is decorative; the text carries the meaning. -->\n\t\t<template v-else-if=\"widget === 'iconLabel'\">\n\t\t\t<span class=\"cn-cell-renderer__icon-label\" data-testid=\"cn-cell-icon-label\">\n\t\t\t\t<CnIcon v-if=\"typeof iconLabelIcon === 'string' && iconLabelIcon\"\n\t\t\t\t\t:name=\"iconLabelIcon\"\n\t\t\t\t\t:size=\"16\"\n\t\t\t\t\taria-hidden=\"true\" />\n\t\t\t\t<component :is=\"iconLabelIcon\"\n\t\t\t\t\tv-else-if=\"iconLabelIcon\"\n\t\t\t\t\t:size=\"16\"\n\t\t\t\t\taria-hidden=\"true\" />\n\t\t\t\t<span v-if=\"hasValue\">{{ labelledValue }}</span>\n\t\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Explicit column formatter — overrides the type-aware paths below -->\n\t\t<template v-else-if=\"hasFormatter\">\n\t\t\t<span :title=\"rawTitle\">{{ formattedValue }}</span>\n\t\t</template>\n\n\t\t<!-- Declarative swatch — a colour dot (from a sibling row field) + text -->\n\t\t<template v-else-if=\"isSwatch\">\n\t\t\t<span class=\"cn-cell-renderer__swatch-wrap\">\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"swatchColor\"\n\t\t\t\t\tclass=\"cn-cell-renderer__swatch-dot\"\n\t\t\t\t\t:style=\"{ backgroundColor: swatchColor }\"\n\t\t\t\t\taria-hidden=\"true\" />\n\t\t\t\t<span v-if=\"hasValue\" :title=\"rawTitle\">{{ labelledValue }}</span>\n\t\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Declarative built-in format (currency / duration / number / percent) -->\n\t\t<template v-else-if=\"hasBuiltinFormat\">\n\t\t\t<span :title=\"rawTitle\" class=\"cn-cell-renderer--number\">{{ formattedValue }}</span>\n\t\t</template>\n\n\t\t<!-- Date / date-time: dynamic NcDateTime (relative time, absolute on hover) -->\n\t\t<template v-else-if=\"isDate\">\n\t\t\t<time\n\t\t\t\tv-if=\"dateTimestamp && isBoardLook\"\n\t\t\t\tclass=\"cn-cell-renderer__date\"\n\t\t\t\t:datetime=\"dateTimestamp.toISOString()\"\n\t\t\t\t:title=\"absoluteDateLabel\">{{ boardDateLabel }}</time>\n\t\t\t<NcDateTime v-else-if=\"dateTimestamp\" :timestamp=\"dateTimestamp\" />\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- URI / URL: external link -->\n\t\t<template v-else-if=\"isUri\">\n\t\t\t<a\n\t\t\t\tv-if=\"uriHref\"\n\t\t\t\t:href=\"uriHref\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\trel=\"noopener\"\n\t\t\t\tclass=\"cn-cell-renderer__link\">\n\t\t\t\t{{ formattedValue }}\n\t\t\t</a>\n\t\t\t<span v-else-if=\"hasValue\" :title=\"rawTitle\">{{ formattedValue }}</span>\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- Boolean: words when `widgetProps.trueLabel` / `falseLabel` name\n\t\t them (the value `widgetProps.pill` names as a pill), else an icon -->\n\t\t<template v-else-if=\"propertyType === 'boolean' && booleanWord !== ''\">\n\t\t\t<CnStatusBadge v-if=\"booleanPilled\" :label=\"booleanWord\" />\n\t\t\t<span v-else>{{ booleanWord }}</span>\n\t\t</template>\n\t\t<template v-else-if=\"propertyType === 'boolean'\">\n\t\t\t<CheckBold v-if=\"value\" :size=\"16\" class=\"cn-cell-renderer__icon cn-cell-renderer__icon--success\" />\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- Enum: status badge (auto-coloured from the property's optional colorMap) -->\n\t\t<template v-else-if=\"isEnum\">\n\t\t\t<CnStatusBadge v-if=\"value\"\n\t\t\t\t:label=\"enumLabel\"\n\t\t\t\t:colorKey=\"String(value)\"\n\t\t\t\t:colorMap=\"enumColorMap\" />\n\t\t\t<span v-else class=\"cn-cell-renderer__dash\">—</span>\n\t\t</template>\n\n\t\t<!-- Array: comma-joined or count -->\n\t\t<template v-else-if=\"propertyType === 'array'\">\n\t\t\t<span v-if=\"!value || !value.length\" class=\"cn-cell-renderer__dash\">—</span>\n\t\t\t<span v-else :title=\"Array.isArray(value) ? value.join(', ') : ''\">\n\t\t\t\t{{ formattedValue }}\n\t\t\t</span>\n\t\t</template>\n\n\t\t<!-- Default: formatted text -->\n\t\t<template v-else>\n\t\t\t<span :title=\"rawTitle\">{{ formattedValue }}</span>\n\t\t</template>\n\t</span>\n</template>\n\n<script>\nimport { getCanonicalLocale, translate as t } from '@nextcloud/l10n'\nimport { NcAvatar, NcButton, NcCheckboxRadioSwitch, NcDateTime } from '@nextcloud/vue'\nimport CheckBold from 'vue-material-design-icons/CheckBold.vue'\nimport Lock from 'vue-material-design-icons/Lock.vue'\nimport CnFkResolveCell from '../CnFkResolveCell/CnFkResolveCell.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport CnGroupNameCell from './CnGroupNameCell.vue'\nimport { normalizeLook } from '../../composables/useLook.js'\nimport { isBadgeVariant } from '../../utils/badgeVariants.js'\nimport { ageInDays, boardDateLocale, formatAge, formatBoardDate, formatRelativeDate, formatTimeOfDay, resolveAgeVariant } from '../../utils/boardDate.js'\nimport { daysUntil, parseDateValue, resolveDateVariant } from '../../utils/dateVariant.js'\nimport { pillLook } from '../../utils/detailActionModel.js'\nimport { resolveDue } from '../../utils/dueText.js'\nimport { fillTemplate, formatBoardDateTime } from '../../utils/fillTemplate.js'\nimport { formatMoney } from '../../utils/formatMetric.js'\nimport { groupDisplayName, loadGroupDisplayName } from '../../utils/groupAutocomplete.js'\nimport { objectFieldValue } from '../../utils/objectName.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { indicatorApplies } from '../../utils/rowIndicators.js'\nimport { safeHref } from '../../utils/safeHref.js'\nimport { formatValue, isGroupProp } from '../../utils/schema.js'\nimport { userDisplayName } from '../../utils/userDisplayName.js'\nimport { isWithheld, withheldReason } from '../../utils/withheldValue.js'\nimport { CnStatusBadge } from '../CnStatusBadge/index.js'\n\n/** A uuid in any version, the shape OpenRegister ids take. */\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\n/**\n * Module-level set of column keys already warned about for a\n * `widget:\"link\"` declaration with no resolvable target — guarantees\n * one warning per (page, column-key) rather than per row × render.\n */\nconst WARNED_LINK_KEYS = new Set()\n\n/**\n * CnCellRenderer — Type-aware cell renderer for schema-driven tables.\n *\n * Renders a single cell value based on its schema property definition.\n * Booleans render as icons, enums as status badges, dates as formatted strings,\n * and everything else as truncated text via `formatValue()`.\n *\n * ```vue\n * <CnCellRenderer :value=\"row.status\" :property=\"schema.properties.status\" />\n * ```\n */\nexport default {\n\tname: 'CnCellRenderer',\n\n\tcomponents: {\n\t\tCnIcon,\n\t\tCnStatusBadge,\n\t\tNcCheckboxRadioSwitch,\n\t\tCnFkResolveCell,\n\t\tCnGroupNameCell,\n\t\tCheckBold,\n\t\tLock,\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcDateTime,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Cell-formatter registry, provided by CnAppRoot (`cnFormatters`).\n\t\t * Map of formatter-id → `(value, row, property, options, context) => string|number`;\n\t\t * `context` is the page record, `{ objectId, object }`.\n\t\t * Defaults to an empty object so standalone use (no CnAppRoot\n\t\t * ancestor) is unaffected.\n\t\t */\n\t\tcnFormatters: { default: () => ({}) },\n\t\t/** The detail page's record context (`{ objectId, object }`), for a formatter's fifth argument. */\n\t\tcnObjectContext: { default: null },\n\t\t/** The v2 grid's record holder (`{ value: { objectId, objectData } }`), the same. */\n\t\tcnDetailObjectContext: { default: null },\n\t\t/**\n\t\t * Cell-widget registry, provided by CnAppRoot (`cnCellWidgets`).\n\t\t * Map of widget-id → Vue component. Resolves a column's `widget` id;\n\t\t * the built-in `\"badge\"` is handled inline (no registry entry needed).\n\t\t * Defaults to an empty object.\n\t\t */\n\t\tcnCellWidgets: { default: () => ({}) },\n\t\t/**\n\t\t * Display-layer translation function, provided by CnAppRoot\n\t\t * (`cnTranslate: this.translate`, bound to the host app's id). Applied\n\t\t * to the enum badge label only — the underlying value is untouched.\n\t\t * Defaults to identity so standalone use is unaffected.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t\t/**\n\t\t * The look the app is drawn in, provided by CnAppRoot (or a page with\n\t\t * `config.look`). Under `board` a date cell reads the board's short\n\t\t * form (\"5 okt\"). Defaults to `nextcloud`, which renders as before.\n\t\t */\n\t\tcnLook: { default: 'nextcloud' },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The raw cell value. String precedes Boolean: with Boolean first Vue\n\t\t * casts an empty string to `true`, and an empty cell drew a check mark.\n\t\t */\n\t\tvalue: {\n\t\t\t// eslint-disable-next-line vue/prefer-prop-type-boolean-first -- an empty string must stay a string\n\t\t\ttype: [String, Number, Boolean, Array, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Schema property definition: { type, format, enum, items, title } */\n\t\tproperty: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Optional cell-formatter id (e.g. `currency`, `automationTrigger`).\n\t\t * When set and resolvable in the injected `cnFormatters` registry,\n\t\t * the cell renders `cnFormatters[formatter](value, row, property,\n\t\t * formatterOptions)` as text — an explicit override of the type-aware\n\t\t * rendering below.\n\t\t */\n\t\tformatter: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Declarative options map passed as the formatter's fourth argument\n\t\t * (e.g. `{ currency: 'USD' }` for the built-in `currency` formatter,\n\t\t * or `{ negative, zero, positive }` phrases for `conditionalPhrase`).\n\t\t * Undefined-safe: three-argument formatters simply ignore it.\n\t\t */\n\t\tformatterOptions: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Optional cell-widget id (e.g. `badge`, or a consumer-registered\n\t\t * name). When it resolves in `cnCellWidgets` the cell renders that\n\t\t * component with `{ value, row, property, formatted, ...widgetProps }`;\n\t\t * the built-in id `\"badge\"` renders `CnStatusBadge` and the built-in\n\t\t * id `\"fkResolve\"` renders `CnFkResolveCell` (uuid → related object\n\t\t * label, config via `widgetProps { register, schema, labelField }`),\n\t\t * and the built-in id `\"refLabel\"` shows labels already resolved in a\n\t\t * batch (`widgetProps { labels, route? }`, set by CnIndexPage for a\n\t\t * column with `labelField`).\n\t\t * The built-in id `\"avatar\"` renders a person as an avatar with the\n\t\t * name beside it (`widgetProps { userField?, user?, nameField?, size?, groupField?, groupLabel? }`; a row nobody holds names its first `groupField` group, in `groupLabel` (\"{group}, unclaimed\");\n\t\t * a free name gets its initials), and the built-in id `\"date\"` renders\n\t\t * a date whose colour follows `widgetProps.variantWhen`, rules on the\n\t\t * number of days until the date (`[{ op: \"lt\", value: 0, variant:\n\t\t * \"error\" }, { op: \"lte\", value: 5, variant: \"warning\" }]`).\n\t\t * The built-in id `\"age\"` renders how long the row has waited since\n\t\t * the date (\"4 hours\", \"3 days\"), coloured by `widgetProps.variantWhen`\n\t\t * rules on the days it shows (`[{ op: \"gte\", value: 3, variant:\n\t\t * \"error\" }]`).\n\t\t * The built-in id `\"group\"` shows a Nextcloud group id (or a list of\n\t\t * them) as the group's display name; a column whose property is\n\t\t * marked `referenceType: \"nextcloud-group\"` gets it without asking.\n\t\t * Takes precedence over `formatter`/the type-aware rendering, but the\n\t\t * value handed to the widget is the formatter-shaped `formatted` when\n\t\t * `formatter` is also set.\n\t\t */\n\t\twidget: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Extra props spread onto the resolved cell-widget component. The\n\t\t * built-in `\"link\"` reads its target from here: `route` (a manifest\n\t\t * page id), `params` (route param to row field), `href` (URL with\n\t\t * `{field}` placeholders; a new tab unless `target: \"_self\"` or a `#`\n\t\t * anchor), and `routeField` + `routeMap` to pick the\n\t\t * page per row from a sibling field (`{ person: 'ContactDetail' }`),\n\t\t * with `route` as the fallback for a value the map does not hold.\n\t\t */\n\t\twidgetProps: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * The column's secondary line (a muted second line under the value).\n\t\t * The `avatar` widget draws it under the name, beside the picture;\n\t\t * other cells ignore it, because the table draws it under the cell.\n\t\t */\n\t\tsecondary: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional declarative cell-format spec — a no-code alternative to a\n\t\t * registry `formatter`. Recognised `style` values:\n\t\t *\n\t\t * - `'currency'` → `Intl.NumberFormat` currency (e.g. `€ 1.234,56`),\n\t\t * honouring `currency` (ISO code, default `'EUR'`) and `decimals`\n\t\t * (default 2).\n\t\t * - `'number'` / `'percent'` → localized number (percent appends `%`),\n\t\t * honouring `decimals` (default 0).\n\t\t * - `'duration'` → a seconds value rendered compact (`1u 23m`, `45m 10s`,\n\t\t * `12s`); pass `unit: 'milliseconds'` / `'minutes'` / `'hours'` when the\n\t\t * raw value is not in seconds. Sub-second millisecond values render as\n\t\t * `245ms` rather than rounding down to `0s`.\n\t\t * - `'swatch'` → a colour dot read from a sibling row field named by\n\t\t * `colorField` (the value renders as the cell text beside it).\n\t\t *\n\t\t * `prefix` / `suffix` are prepended/appended to the numeric styles.\n\t\t * Resolved AFTER `formatter` / `widget` (those win), but BEFORE the\n\t\t * type-aware rendering, so a manifest column can opt into currency or a\n\t\t * colour swatch without registering a function.\n\t\t *\n\t\t * @type {{style?: 'currency'|'number'|'decimal'|'percent'|'duration'|'swatch', currency?: string, decimals?: number, unit?: 'milliseconds'|'seconds'|'minutes'|'hours', prefix?: string, suffix?: string, colorField?: string}}\n\t\t */\n\t\tformat: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The full row object — passed so a formatter can be a function of\n\t\t * the whole record (e.g. \"days since `@self.updated`\"), not just\n\t\t * this one cell value.\n\t\t */\n\t\trow: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Maximum string length before truncation */\n\t\ttruncate: {\n\t\t\ttype: Number,\n\t\t\tdefault: 100,\n\t\t},\n\n\t\t/**\n\t\t * Row identifier field — used by the built-in `widget:\"link\"` when\n\t\t * the manifest doesn't specify an explicit `widgetProps.params`\n\t\t * map. Defaults to `'id'` so router-link param resolution works\n\t\t * with the manifest convention (`/x/:id`).\n\t\t */\n\t\trowKey: {\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t// The switch's state while its onToggle is in flight, or null.\n\t\t\tswitchPending: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The schema an `fkResolve` cell reads: `widgetProps.schema`, or the one\n\t\t * `widgetProps.schemaFrom` (`{ field, map }`) names for this row, for a\n\t\t * reference that points at objects of more than one schema.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tfkSchema() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tconst from = props.schemaFrom\n\t\t\tif (from && typeof from === 'object' && from.field && this.row) {\n\t\t\t\tconst mapped = (from.map || {})[String(this.row[from.field])]\n\t\t\t\tif (mapped) {\n\t\t\t\t\treturn mapped\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn props.schema || ''\n\t\t},\n\n\t\t/**\n\t\t * The `actions` widget's buttons for this row: the page's resolved\n\t\t * `widgetProps.items` (a list, or a function of the row returning\n\t\t * one), each `{ key, label, variant?, disabled? }`.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, variant: string, disabled: boolean}>}\n\t\t * @spec openspec/changes/table-actions-column/specs/index-page/spec.md#requirement-a-column-can-show-row-actions-as-buttons\n\t\t */\n\t\tactionItems() {\n\t\t\tconst raw = typeof this.widgetProps?.items === 'function' ? this.widgetProps.items(this.row) : this.widgetProps?.items\n\t\t\tconst items = Array.isArray(raw) ? raw : []\n\t\t\treturn items.map((item) => ({\n\t\t\t\t...item,\n\t\t\t\tvariant: item.variant || 'secondary',\n\t\t\t\tdisabled: item.disabled === true,\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Whether the \"switch\" widget is on: the pending state while a toggle\n\t\t * is in flight, else `widgetProps.checked(row)`, else the value.\n\t\t *\n\t\t * @return {boolean} The switch state.\n\t\t */\n\t\tswitchChecked() {\n\t\t\tif (this.switchPending !== null) {\n\t\t\t\treturn this.switchPending\n\t\t\t}\n\t\t\tconst checked = this.widgetProps && this.widgetProps.checked\n\t\t\tif (typeof checked === 'function') {\n\t\t\t\treturn checked(this.row) === true\n\t\t\t}\n\t\t\treturn this.value === true\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} Whether a toggle is in flight.\n\t\t */\n\t\tswitchBusy() {\n\t\t\treturn this.switchPending !== null\n\t\t},\n\n\t\t/**\n\t\t * The \"iconLabel\" widget's icon, resolved against the row.\n\t\t *\n\t\t * @return {object|string|null} A component, an icon name, or null.\n\t\t */\n\t\ticonLabelIcon() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\t// `widgetProps.icons`: an icon per stored value, `icon` for the rest.\n\t\t\t// @spec openspec/changes/board-log-table-plain-cells/specs/board-log-table-plain-cells/spec.md#requirement-an-icon-label-cell-can-pick-its-icon-by-value\n\t\t\tif (props.icons && typeof props.icons === 'object' && this.hasValue && typeof this.value !== 'object' && Object.hasOwn(props.icons, String(this.value))) {\n\t\t\t\treturn props.icons[String(this.value)] || null\n\t\t\t}\n\t\t\tconst icon = props.icon\n\t\t\tconst resolved = typeof icon === 'function' ? icon(this.row) : icon\n\t\t\treturn resolved || null\n\t\t},\n\n\t\tpropertyType() {\n\t\t\treturn this.property?.type || (typeof this.value)\n\t\t},\n\n\t\t/**\n\t\t * A boolean's word from `widgetProps.trueLabel` / `falseLabel`,\n\t\t * translated, or '' when the column names none.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tbooleanWord() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tconst word = this.value === true ? props.trueLabel : (this.value === false ? props.falseLabel : '')\n\t\t\treturn typeof word === 'string' && word !== '' ? this.cnTranslate(word) : ''\n\t\t},\n\n\t\t/**\n\t\t * Whether this boolean's word is drawn as a pill: `widgetProps.pill`\n\t\t * names the value that is.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tbooleanPilled() {\n\t\t\tconst pill = (this.widgetProps || {}).pill\n\t\t\treturn typeof pill === 'boolean' && pill === this.value\n\t\t},\n\n\t\tisEnum() {\n\t\t\treturn !!(this.property?.enum && this.property.enum.length > 0)\n\t\t},\n\n\t\t/**\n\t\t * Badge text for an enum cell. Schema enum values are English source\n\t\t * codes, so a Dutch session otherwise reads `submitted` in a table whose\n\t\t * headers are Dutch. Only the label is translated — the cell's value,\n\t\t * sorting and filtering keep using the raw code.\n\t\t */\n\t\tenumLabel() {\n\t\t\tconst raw = String(this.value)\n\t\t\tconst labels = (this.property && (this.property.enumLabels || this.property['x-enum-labels'])) || {}\n\t\t\treturn this.cnTranslate(labels[raw] || raw)\n\t\t},\n\n\t\t/** True when the property is a date / date-time (rendered via NcDateTime). */\n\t\tisDate() {\n\t\t\treturn this.property?.format === 'date-time' || this.property?.format === 'date'\n\t\t},\n\n\t\t/**\n\t\t * The cell value as a Date for NcDateTime, or `null` when absent or\n\t\t * unparseable (the cell then falls back to a dash / plain text).\n\t\t *\n\t\t * @return {Date|null}\n\t\t */\n\t\tdateTimestamp() {\n\t\t\tif (!this.hasValue) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst date = new Date(this.value)\n\t\t\treturn Number.isNaN(date.getTime()) ? null : date\n\t\t},\n\n\t\t/**\n\t\t * `avatar` widget: the Nextcloud user id whose avatar to show, or ''\n\t\t * for a free name. Read from the row field `widgetProps.userField`,\n\t\t * or from the cell value itself when `widgetProps.user` is true.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-avatar-and-date-cells\n\t\t */\n\t\tavatarUserId() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tlet id = ''\n\t\t\tif (typeof props.userField === 'string' && props.userField !== '') {\n\t\t\t\tid = objectFieldValue(this.row, props.userField)\n\t\t\t} else if (props.user === true) {\n\t\t\t\tid = this.value\n\t\t\t}\n\t\t\treturn (typeof id === 'string' || typeof id === 'number') ? String(id) : ''\n\t\t},\n\n\t\t/**\n\t\t * `avatar` widget: the name shown beside the picture. Read from the\n\t\t * row field `widgetProps.nameField` when set; a cell holding a user id\n\t\t * (`widgetProps.user`) shows that user's display name once it is\n\t\t * known; else the cell value.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-avatar-and-date-cells\n\t\t * @spec openspec/changes/avatar-cell-user-display-name/specs/data-table/spec.md#requirement-an-avatar-cell-holding-a-user-id-shows-the-users-display-name\n\t\t */\n\t\tavatarName() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tif (typeof props.nameField === 'string' && props.nameField !== '') {\n\t\t\t\tconst name = objectFieldValue(this.row, props.nameField)\n\t\t\t\tif (typeof name === 'string' && name !== '') {\n\t\t\t\t\treturn name\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (props.user === true && this.avatarUserId !== '') {\n\t\t\t\tconst displayName = userDisplayName(this.avatarUserId)\n\t\t\t\tif (displayName !== '') {\n\t\t\t\t\treturn displayName\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.avatarGroupId !== '') {\n\t\t\t\tconst label = typeof props.groupLabel === 'string' && props.groupLabel !== '' ? this.cnTranslate(props.groupLabel) : '{group}'\n\t\t\t\treturn label.replace(/\\{group\\}/g, this.avatarGroupName)\n\t\t\t}\n\t\t\tif (!this.hasValue || typeof this.value === 'object') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn String(this.hasFormatter ? this.formattedValue : this.value)\n\t\t},\n\n\t\t/**\n\t\t * `avatar` widget: the initials of a free name, at most two letters\n\t\t * (first and last word).\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-avatar-and-date-cells\n\t\t */\n\t\tavatarInitials() {\n\t\t\treturn this.initialsOf(this.avatarGroupId !== '' ? this.avatarGroupName : this.avatarName)\n\t\t},\n\n\t\t/**\n\t\t * `avatar` widget: the group the row waits on when nobody holds it,\n\t\t * the first id in the row field `widgetProps.groupField`, or ''.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/avatar-cell-group-fallback/specs/data-table/spec.md#requirement-an-empty-avatar-cell-can-name-the-rows-group\n\t\t */\n\t\tavatarGroupId() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tif (this.widget !== 'avatar' || typeof props.groupField !== 'string' || props.groupField === '' || this.avatarUserId !== '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (this.hasValue && typeof this.value !== 'object') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst groups = objectFieldValue(this.row, props.groupField)\n\t\t\tconst first = Array.isArray(groups) ? groups.find((gid) => typeof gid === 'string' && gid !== '') : groups\n\t\t\treturn typeof first === 'string' ? first : ''\n\t\t},\n\n\t\t/**\n\t\t * `avatar` widget: the display name of `avatarGroupId`, the id until it is known.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tavatarGroupName() {\n\t\t\treturn groupDisplayName(this.avatarGroupId) || this.avatarGroupId\n\t\t},\n\n\t\t/**\n\t\t * `avatar` widget: where a REFERENCED person's name comes from\n\t\t * (`widgetProps.resolve`, `{ register, schema, labelField? }`, the\n\t\t * shape the facts widget's `resolve` takes), or null when the cell\n\t\t * holds the name or user id itself.\n\t\t *\n\t\t * @return {{register: string, schema: string, labelField?: string}|null}\n\t\t * @spec openspec/changes/screens-avatar-cell-resolve/specs/data-table/spec.md\n\t\t */\n\t\tavatarResolve() {\n\t\t\tconst resolve = this.widgetProps && this.widgetProps.resolve\n\t\t\tif (!resolve || typeof resolve !== 'object') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof resolve.register !== 'string' || resolve.register === '' || typeof resolve.schema !== 'string' || resolve.schema === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn resolve\n\t\t},\n\n\t\t/**\n\t\t * `avatar` widget: the picture size in pixels (`widgetProps.size`, default 24,\n\t\t * 32 under the board look).\n\t\t *\n\t\t * @return {number}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-avatar-and-date-cells\n\t\t */\n\t\tavatarSize() {\n\t\t\tconst size = Number(this.widgetProps && this.widgetProps.size)\n\t\t\treturn Number.isFinite(size) && size > 0 ? size : (this.isBoardLook ? 32 : 24)\n\t\t},\n\n\t\t/**\n\t\t * `date` widget with `widgetProps.due`: the colour class. The\n\t\t * `variantWhen` rules win when they match; else an overdue date reads\n\t\t * as an error and a soon one (`due.soonWithin`) as a warning.\n\t\t *\n\t\t * @return {string|null} The class, or null for the plain colour.\n\t\t * @spec openspec/changes/due-text-soon/specs/due-text-soon/spec.md#requirement-a-due-date-within-a-few-days-reads-as-soon\n\t\t */\n\t\tdueClass() {\n\t\t\tif (this.dateVariant) {\n\t\t\t\treturn 'cn-cell-renderer__date--' + this.dateVariant\n\t\t\t}\n\t\t\tif (this.dueResult && this.dueResult.tone === 'danger') {\n\t\t\t\treturn 'cn-cell-renderer__date--error'\n\t\t\t}\n\t\t\tif (this.dueResult && this.dueResult.tone === 'muted') {\n\t\t\t\treturn 'cn-cell-renderer__date--muted'\n\t\t\t}\n\t\t\treturn this.dueResult && this.dueResult.soon ? 'cn-cell-renderer__date--warning' : null\n\t\t},\n\n\t\t/**\n\t\t * `date` widget: whether the date lies before today.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/object-list-by-field/specs/object-list-widget/spec.md#requirement-a-date-cell-can-say-its-state-and-groups-keep-a-set-order\n\t\t */\n\t\tdateIsPast() {\n\t\t\tconst days = daysUntil(this.value)\n\t\t\treturn days !== null && days < 0\n\t\t},\n\n\t\t/**\n\t\t * `date` widget: `widgetProps.pastText` for a past date, else\n\t\t * `widgetProps.otherText` (also for an empty one), translated; '' without either.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/object-list-by-field/specs/object-list-widget/spec.md#requirement-a-date-cell-can-say-its-state-and-groups-keep-a-set-order\n\t\t */\n\t\tdateStateText() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tconst text = this.dateIsPast ? props.pastText : props.otherText\n\t\t\treturn typeof text === 'string' && text !== '' ? this.cnTranslate(text) : ''\n\t\t},\n\n\t\t/**\n\t\t * `date` widget: the variant of the first `widgetProps.variantWhen`\n\t\t * rule the date matches, or '' for none.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-avatar-and-date-cells\n\t\t */\n\t\tdateVariant() {\n\t\t\tconst rules = this.widgetProps && this.widgetProps.variantWhen\n\t\t\tconst variant = resolveDateVariant(this.value, rules)\n\t\t\treturn variant === 'default' ? '' : variant\n\t\t},\n\n\t\t/**\n\t\t * Whether the app takes the board look (`cnLook` is `board`).\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-a-board-date-cell-reads-day-and-short-month-in-the-user-language\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/**\n\t\t * The board's short date (\"5 okt\", \"14 feb 2024\" outside this year).\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-a-board-date-cell-reads-day-and-short-month-in-the-user-language\n\t\t */\n\t\tboardDateLabel() {\n\t\t\treturn formatBoardDate(this.value)\n\t\t},\n\n\t\t/**\n\t\t * The full date for a tooltip: day, month and year in the user's\n\t\t * locale, with the time for a date-time value.\n\t\t *\n\t\t * @return {string|undefined}\n\t\t * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-says-how-long-something-has-waited\n\t\t */\n\t\tabsoluteDateLabel() {\n\t\t\tconst date = parseDateValue(this.value)\n\t\t\tif (date === null) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tconst withTime = this.property?.format === 'date-time' || this.widget === 'age' || (this.widget === 'date' && this.widgetProps?.showTime === true)\n\t\t\tconst options = { day: 'numeric', month: 'long', year: 'numeric', ...(withTime ? { hour: '2-digit', minute: '2-digit' } : {}) }\n\t\t\ttry {\n\t\t\t\treturn new Intl.DateTimeFormat(getCanonicalLocale() || undefined, options).format(date)\n\t\t\t} catch {\n\t\t\t\treturn new Intl.DateTimeFormat(undefined, options).format(date)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * `date` widget with `widgetProps.due`: the due text and its tone.\n\t\t * Null without the key, or when the field is empty and no `emptyText`.\n\t\t *\n\t\t * @return {{text: string, tone: string}|null}\n\t\t * @spec openspec/changes/screens-due-text/specs/due-text/spec.md#requirement-a-due-date-reads-as-text-with-a-tone\n\t\t */\n\t\tdueResult() {\n\t\t\tconst due = this.widgetProps && this.widgetProps.due\n\t\t\tif (this.widget !== 'date' || !due || typeof due !== 'object') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn resolveDue(this.value, due, (text) => this.cnTranslate(text), new Date(), this.row)\n\t\t},\n\n\t\t/**\n\t\t * Whether the `date` widget reads relatively (`widgetProps.relative`).\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/screens-relative-dates/specs/relative-dates/spec.md#requirement-a-relative-date-reads-today-yesterday-then-a-short-date\n\t\t */\n\t\tisRelativeDate() {\n\t\t\treturn this.widgetProps?.relative === true\n\t\t},\n\n\t\t/**\n\t\t * `age` widget: how long the row has waited (\"4 uur\", \"3 dagen\").\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-says-how-long-something-has-waited\n\t\t */\n\t\tageLabel() {\n\t\t\treturn formatAge(this.value)\n\t\t},\n\n\t\t/**\n\t\t * `progress` widget: the value as a whole percentage of\n\t\t * `widgetProps.max` (default 100), clamped to 0..100. An empty value\n\t\t * falls back to the first `widgetProps.fallbackFields` row field that\n\t\t * holds a number. Null when nothing holds one.\n\t\t *\n\t\t * @return {number|null}\n\t\t * @spec openspec/changes/abstract-progress-cell/specs/progress-cell/spec.md#requirement-a-progress-cell-draws-a-toned-bar-with-its-percentage\n\t\t */\n\t\tprogressPercent() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tconst fallbacks = Array.isArray(props.fallbackFields) ? props.fallbackFields : []\n\t\t\tconst candidates = [this.value, ...fallbacks.map((field) => readPath(this.row || {}, field))]\n\t\t\tconst raw = candidates.find((v) => v !== null && v !== undefined && v !== '' && !Number.isNaN(Number(v)))\n\t\t\tif (raw === undefined) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst max = Number(props.max) > 0 ? Number(props.max) : 100\n\t\t\treturn Math.max(0, Math.min(100, Math.round((Number(raw) / max) * 100)))\n\t\t},\n\n\t\t/**\n\t\t * `progress` widget: the tone. `widgetProps.thresholds`\n\t\t * `{ good: 60, warn: 30 }` (the defaults): at or above `good` is\n\t\t * success, at or above `warn` is warning, below is error. With\n\t\t * `widgetProps.lowerIsBetter` the scale turns around.\n\t\t *\n\t\t * @return {'success'|'warning'|'error'}\n\t\t * @spec openspec/changes/abstract-progress-cell/specs/progress-cell/spec.md#requirement-a-progress-cell-draws-a-toned-bar-with-its-percentage\n\t\t */\n\t\tprogressTone() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tconst thresholds = props.thresholds || {}\n\t\t\tconst good = Number.isFinite(Number(thresholds.good)) ? Number(thresholds.good) : 60\n\t\t\tconst warn = Number.isFinite(Number(thresholds.warn)) ? Number(thresholds.warn) : 30\n\t\t\tconst value = props.lowerIsBetter === true ? 100 - this.progressPercent : this.progressPercent\n\t\t\tif (value >= good) {\n\t\t\t\treturn 'success'\n\t\t\t}\n\t\t\treturn value >= warn ? 'warning' : 'error'\n\t\t},\n\n\t\t/**\n\t\t * `progress` widget: the age badge, or null. `widgetProps.badge`\n\t\t * `{ label, dateField, afterDays, when?, tone? }`: shown when the row's\n\t\t * `dateField` (default `@self.updated`) lies `afterDays` or more days\n\t\t * back and every `when` field equals its value. `label` is translated\n\t\t * and takes `{days}`.\n\t\t *\n\t\t * @return {{label: string, tone: string}|null}\n\t\t * @spec openspec/changes/abstract-progress-cell/specs/progress-cell/spec.md#requirement-a-progress-cell-can-carry-an-age-badge\n\t\t */\n\t\tprogressBadge() {\n\t\t\tconst badge = this.widgetProps && this.widgetProps.badge\n\t\t\tif (!badge || typeof badge !== 'object' || !badge.label) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst row = this.row || {}\n\t\t\tconst when = badge.when && typeof badge.when === 'object' ? badge.when : {}\n\t\t\tfor (const [field, expected] of Object.entries(when)) {\n\t\t\t\tconst actual = readPath(row, field)\n\t\t\t\tif (actual !== undefined && actual !== null && actual !== '' && String(actual) !== String(expected)) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst days = ageInDays(readPath(row, badge.dateField || '@self.updated'))\n\t\t\tconst after = Number(badge.afterDays)\n\t\t\tif (days === null || !Number.isFinite(after) || days < after) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst label = this.cnTranslate(badge.label)\n\t\t\treturn {\n\t\t\t\tlabel: String(label).replace(/\\{days\\}/g, String(days)),\n\t\t\t\ttone: ['error', 'warning', 'success'].includes(badge.tone) ? badge.tone : 'error',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * `age` widget: the variant of the first `widgetProps.variantWhen`\n\t\t * rule the shown days match, or '' for none.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-turns-red-past-its-threshold\n\t\t */\n\t\tageVariant() {\n\t\t\tconst variant = resolveAgeVariant(this.value, this.widgetProps && this.widgetProps.variantWhen)\n\t\t\treturn variant === 'default' ? '' : variant\n\t\t},\n\n\t\t/**\n\t\t * `date` widget: the date written out in the user's locale.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-avatar-and-date-cells\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-small-opt-ins-from-the-decidiq-and-learniq-lanes\n\t\t */\n\t\t/**\n\t\t * `date` widget with `widgetProps.layout: \"block\"`: the parts of the\n\t\t * date tile (day number, short month), the weekday and the time range\n\t\t * (\"09.30 tot 12.00 uur\") to the row field `widgetProps.endField`.\n\t\t * Null for any other cell.\n\t\t *\n\t\t * @return {{day: string, month: string, weekday: string, time: string}|null}\n\t\t * @spec openspec/changes/screens-date-tile-cell/specs/data-table/spec.md\n\t\t */\n\t\tdateBlock() {\n\t\t\tconst props = this.widgetProps || {}\n\t\t\tif (this.widget !== 'date' || props.layout !== 'block' || !this.dateTimestamp) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst start = this.dateTimestamp\n\t\t\tconst format = (options) => {\n\t\t\t\ttry {\n\t\t\t\t\treturn new Intl.DateTimeFormat(boardDateLocale(), options).format(start)\n\t\t\t\t} catch {\n\t\t\t\t\treturn new Intl.DateTimeFormat(undefined, options).format(start)\n\t\t\t\t}\n\t\t\t}\n\t\t\t// A date without a time of day (format `date`, or a bare\n\t\t\t// YYYY-MM-DD) draws no time line.\n\t\t\tconst dateOnly = this.property?.format === 'date' || (typeof this.value === 'string' && /^\\d{4}-\\d{2}-\\d{2}$/.test(this.value))\n\t\t\tlet time = ''\n\t\t\tif (!dateOnly) {\n\t\t\t\tconst startTime = formatTimeOfDay(start)\n\t\t\t\tlet end = null\n\t\t\t\tif (typeof props.endField === 'string' && props.endField !== '') {\n\t\t\t\t\tconst raw = objectFieldValue(this.row, props.endField)\n\t\t\t\t\tconst parsed = (raw === null || raw === undefined || raw === '') ? null : new Date(raw)\n\t\t\t\t\tend = parsed && !Number.isNaN(parsed.getTime()) ? parsed : null\n\t\t\t\t}\n\t\t\t\ttime = end\n\t\t\t\t\t? t('nextcloud-vue', '{start} to {end}', { start: startTime, end: formatTimeOfDay(end) })\n\t\t\t\t\t: startTime\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tday: format({ day: 'numeric' }),\n\t\t\t\t// \"okt.\" in Dutch: the board draws the month without the dot.\n\t\t\t\tmonth: format({ month: 'short' }).replace(/\\.$/, ''),\n\t\t\t\tweekday: format({ weekday: 'long' }),\n\t\t\t\ttime,\n\t\t\t}\n\t\t},\n\n\t\tdateLabel() {\n\t\t\tconst date = parseDateValue(this.value)\n\t\t\tif (date === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// `widgetProps.showTime: true` adds the time of day (\"5 Oct 2026,\n\t\t\t// 08:30\"); `widgetProps.timeOnly: true` shows just the time\n\t\t\t// (\"08:30\"), for a timetable row. Default: the date alone.\n\t\t\tconst props = this.widgetProps || {}\n\t\t\t// `widgetProps.relative: true` says \"today\" and \"yesterday\" and then\n\t\t\t// the short date (\"vandaag, 09.42\" with `showTime`), in any look.\n\t\t\tif (props.relative === true) {\n\t\t\t\treturn formatRelativeDate(date, { withTime: props.showTime === true })\n\t\t\t}\n\t\t\t// `widgetProps.short: true` writes the board's short date in any\n\t\t\t// look, with `showTime` followed by the time (\"5 okt, 09:15\").\n\t\t\t// @spec openspec/changes/board-log-table-plain-cells/specs/board-log-table-plain-cells/spec.md#requirement-a-date-cell-can-write-the-boards-short-date-with-its-time\n\t\t\tif (props.short === true && props.timeOnly !== true) {\n\t\t\t\treturn props.showTime === true ? formatBoardDateTime(date) : formatBoardDate(date)\n\t\t\t}\n\t\t\t// The board look writes a plain date in its short form (\"5 okt\");\n\t\t\t// a time of day keeps the full form below.\n\t\t\tif (this.isBoardLook && props.timeOnly !== true && props.showTime !== true) {\n\t\t\t\treturn formatBoardDate(date)\n\t\t\t}\n\t\t\tconst options = props.timeOnly === true\n\t\t\t\t? { hour: '2-digit', minute: '2-digit' }\n\t\t\t\t: { day: 'numeric', month: 'short', year: 'numeric', ...(props.showTime === true ? { hour: '2-digit', minute: '2-digit' } : {}) }\n\t\t\tlet locale\n\t\t\ttry {\n\t\t\t\tlocale = getCanonicalLocale()\n\t\t\t} catch {\n\t\t\t\tlocale = undefined\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Intl.DateTimeFormat(locale || undefined, options).format(date)\n\t\t\t} catch {\n\t\t\t\treturn new Intl.DateTimeFormat(undefined, options).format(date)\n\t\t\t}\n\t\t},\n\n\t\t/** True when the property is a URI / URL (rendered as an external link). */\n\t\tisUri() {\n\t\t\treturn this.property?.format === 'uri' || this.property?.format === 'url'\n\t\t},\n\n\t\t/**\n\t\t * Safe external href for a URI/URL cell, or `null` when the value is\n\t\t * absent or fails `safeHref` validation (e.g. an unsafe scheme).\n\t\t *\n\t\t * @return {string|null}\n\t\t */\n\t\turiHref() {\n\t\t\tif (!this.hasValue) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn safeHref(String(this.value))\n\t\t},\n\n\t\t/** True when the cell has a renderable value (not null/undefined/empty string). */\n\t\thasValue() {\n\t\t\treturn this.value !== null && this.value !== undefined && this.value !== ''\n\t\t},\n\n\t\t/**\n\t\t * The row's own reference for an `fkResolve` cell (`widgetProps.refField`),\n\t\t * or null when the row has none.\n\t\t *\n\t\t * @return {string|number|Array|null}\n\t\t * @spec openspec/changes/fk-resolve-lookup/specs/fk-resolve-lookup/spec.md#requirement-a-reference-cell-can-look-its-record-up-by-a-property\n\t\t */\n\t\tfkRefValue() {\n\t\t\tconst field = this.widgetProps && this.widgetProps.refField\n\t\t\tif (!field || !this.row) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst ref = readPath(this.row, field)\n\t\t\tconst empty = ref === null || ref === undefined || ref === '' || (Array.isArray(ref) && ref.length === 0)\n\t\t\treturn empty || (typeof ref === 'object' && !Array.isArray(ref)) ? null : ref\n\t\t},\n\n\t\t/**\n\t\t * Whether the value is OpenRegister's withheld marker, which every\n\t\t * other path would stringify to `[object Object]`.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/cell-config-growth/specs/cell-config-growth/spec.md#requirement-a-withheld-value-reads-as-withheld\n\t\t */\n\t\twithheld() {\n\t\t\treturn isWithheld(this.value)\n\t\t},\n\n\t\t/**\n\t\t * The text a withheld value reads as.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/cell-config-growth/specs/cell-config-growth/spec.md#requirement-a-withheld-value-reads-as-withheld\n\t\t */\n\t\twithheldText() {\n\t\t\treturn t('nextcloud-vue', 'Withheld')\n\t\t},\n\n\t\t/**\n\t\t * Why a value is withheld, as the cell's tooltip.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/cell-config-growth/specs/cell-config-growth/spec.md#requirement-a-withheld-value-reads-as-withheld\n\t\t */\n\t\twithheldHint() {\n\t\t\treturn withheldReason(this.value) === 'access'\n\t\t\t\t? t('nextcloud-vue', 'You may not open the linked object, so its value is not shown.')\n\t\t\t\t: t('nextcloud-vue', 'This value is not shown.')\n\t\t},\n\n\t\t/**\n\t\t * `number` widget: the value with `widgetProps.unit` (`{ one, other }`\n\t\t * with `{count}`, through the app's translate), else the number in the\n\t\t * user's locale. '' for an empty or unreadable value.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/cell-config-growth/specs/cell-config-growth/spec.md#requirement-a-number-cell-reads-with-its-unit-and-a-flag\n\t\t */\n\t\tnumberLabel() {\n\t\t\tif (!this.hasValue || typeof this.value === 'object' || typeof this.value === 'boolean') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst count = Number(this.value)\n\t\t\tif (!Number.isFinite(count)) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst unit = this.widgetProps && this.widgetProps.unit\n\t\t\tif (unit && typeof unit === 'object' && (unit.one || unit.other)) {\n\t\t\t\tconst template = this.cnTranslate((count === 1 ? unit.one : unit.other) || unit.other || unit.one)\n\t\t\t\treturn template.replace(/\\{count\\}/g, String(count))\n\t\t\t}\n\t\t\treturn count.toLocaleString(getCanonicalLocale() || undefined)\n\t\t},\n\n\t\t/**\n\t\t * `number` widget: the chip `widgetProps.flagWhen` asks for on this\n\t\t * row (`{ field, equals | in, text, variant }`), or null.\n\t\t *\n\t\t * @return {{text: string, variant: string}|null}\n\t\t * @spec openspec/changes/cell-config-growth/specs/cell-config-growth/spec.md#requirement-a-number-cell-reads-with-its-unit-and-a-flag\n\t\t */\n\t\tnumberFlag() {\n\t\t\tconst flag = this.widgetProps && this.widgetProps.flagWhen\n\t\t\tif (!flag || typeof flag !== 'object' || typeof flag.text !== 'string' || flag.text === '' || !indicatorApplies(flag, this.row || {})) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn { text: this.cnTranslate(flag.text), variant: isBadgeVariant(flag.variant) ? flag.variant : 'warning' }\n\t\t},\n\n\t\t/**\n\t\t * Built-in `badge`: the tooltip `widgetProps.titleWhen`\n\t\t * (`{ field, equals | in, text }`) asks for on this row, or undefined.\n\t\t *\n\t\t * @return {string|undefined}\n\t\t * @spec openspec/changes/cell-config-growth/specs/cell-config-growth/spec.md#requirement-a-badge-cell-can-carry-a-tooltip\n\t\t */\n\t\tbadgeTitle() {\n\t\t\tconst when = this.widgetProps && this.widgetProps.titleWhen\n\t\t\tif (!when || typeof when !== 'object' || typeof when.text !== 'string' || when.text === '' || !indicatorApplies(when, this.row || {})) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\treturn this.cnTranslate(when.text)\n\t\t},\n\n\t\t/**\n\t\t * Resolved cell-widget component for this column, or `null`. A column's\n\t\t * `widget` id resolves against the injected `cnCellWidgets` registry;\n\t\t * the built-in `\"badge\"` is NOT resolved here (handled inline in the\n\t\t * template) so apps can still override `\"badge\"` via the registry.\n\t\t *\n\t\t * @return {import('vue').Component|null}\n\t\t */\n\t\twidgetComponent() {\n\t\t\tif (!this.widget) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst c = this.cnCellWidgets && this.cnCellWidgets[this.widget]\n\t\t\treturn c || null\n\t\t},\n\n\t\t/**\n\t\t * Whether this cell shows Nextcloud group names: the column asks for\n\t\t * `widget: \"group\"`, or it names no widget and no formatter and its\n\t\t * property is a group (or a list of groups).\n\t\t *\n\t\t * @spec openspec/changes/nextcloud-group-surfaces/specs/data-display/spec.md#requirement-a-group-cell-shows-the-groups-display-name\n\t\t * @return {boolean}\n\t\t */\n\t\tisGroupCell() {\n\t\t\tif (this.widget === 'group') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif (this.widget || this.hasFormatter) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst p = this.property\n\t\t\tif (!p || typeof p !== 'object') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn isGroupProp(p) || (p.type === 'array' && isGroupProp(p.items))\n\t\t},\n\n\t\t/**\n\t\t * The group ids in this cell, empty ones dropped.\n\t\t *\n\t\t * @spec openspec/changes/nextcloud-group-surfaces/specs/data-display/spec.md#requirement-a-group-cell-shows-the-groups-display-name\n\t\t * @return {string[]}\n\t\t */\n\t\tgroupIds() {\n\t\t\tconst raw = this.value\n\t\t\tconst list = Array.isArray(raw) ? raw : [raw]\n\t\t\treturn list\n\t\t\t\t.filter((gid) => (typeof gid === 'string' && gid !== '') || typeof gid === 'number')\n\t\t\t\t.map((gid) => String(gid))\n\t\t},\n\n\t\t/**\n\t\t * The text a built-in widget (`badge`, `link`) or a swatch shows. An\n\t\t * enum value reads its label the way the plain enum cell does\n\t\t * (`enumLabel`: `x-enum-labels`, translated), so a list says \"Active\"\n\t\t * where the detail page does, not the stored \"active\". A column\n\t\t * formatter still wins: an app that shapes the value itself gets\n\t\t * exactly what its formatter returns.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/cell-labels-and-draft-indicator/specs/cell-labels-and-draft-indicator/spec.md#requirement-a-built-in-cell-widget-shows-an-enum-value-by-its-label\n\t\t */\n\t\t/**\n\t\t * What a `label` or `badge` cell shows: its label, worded by\n\t\t * `widgetProps.textTemplate` when given (translated, filled from the\n\t\t * row, `{value}` the label; DqPostvak \"Lijkt op 2026-0079\").\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/cell-text-template/specs/cell-text-template/spec.md\n\t\t */\n\t\tshownLabel() {\n\t\t\tconst template = this.widgetProps && this.widgetProps.textTemplate\n\t\t\tif (typeof template !== 'string' || template === '') {\n\t\t\t\treturn this.labelledValue\n\t\t\t}\n\t\t\treturn fillTemplate(this.cnTranslate(template), { ...(this.row || {}), value: this.labelledValue })\n\t\t},\n\n\t\tlabelledValue() {\n\t\t\tif (!this.hasFormatter && this.isEnum && this.hasValue && typeof this.value !== 'object') {\n\t\t\t\treturn this.enumLabel\n\t\t\t}\n\t\t\treturn String(this.formattedValue)\n\t\t},\n\n\t\t/**\n\t\t * The key the built-in `badge` looks its colour up by: the raw value\n\t\t * when `widgetProps.colorMap` has an entry for it (case-insensitive),\n\t\t * so a translated label keeps the colour the manifest keyed on the\n\t\t * stored code. Otherwise '' and the badge looks up its label, as before,\n\t\t * which keeps a map keyed on a formatter's output working.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/cell-labels-and-draft-indicator/specs/cell-labels-and-draft-indicator/spec.md#requirement-a-built-in-cell-widget-shows-an-enum-value-by-its-label\n\t\t */\n\t\tbadgeColorKey() {\n\t\t\tconst map = this.badgeColorMap\n\t\t\tif (!map || !this.hasValue || typeof this.value === 'object') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst raw = String(this.value).toLowerCase()\n\t\t\treturn Object.keys(map).some((key) => key.toLowerCase() === raw) ? String(this.value) : ''\n\t\t},\n\n\t\t/**\n\t\t * What the built-in `badge` widget takes from the record its value\n\t\t * points at (`widgetProps.looks`, filled by CnIndexPage from\n\t\t * `widgetProps.resolve`): a tone and an icon, or nothing.\n\t\t *\n\t\t * @return {{variant?: string, colorMap?: null, icon?: string}}\n\t\t * @spec openspec/changes/pill-color-field/specs/pill-color-field/spec.md#requirement-a-badge-cell-takes-its-tone-and-icon-from-the-referenced-record\n\t\t */\n\t\tbadgeLook() {\n\t\t\tconst looks = this.widgetProps && this.widgetProps.looks\n\t\t\tif (!looks || typeof looks !== 'object' || !this.hasValue || typeof this.value === 'object') {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\treturn pillLook(looks[String(this.value)])\n\t\t},\n\n\t\t/** Variant for the built-in `badge` widget: the referenced record's tone, else `widgetProps.variant`, else `'default'`. */\n\t\tbadgeVariant() {\n\t\t\treturn this.badgeLook.variant || (this.widgetProps && this.widgetProps.variant) || 'default'\n\t\t},\n\n\t\t/**\n\t\t * Color map for the built-in `badge` widget — `widgetProps.colorMap`,\n\t\t * a `{ value: variant }` map (e.g. `{ placed: 'primary', delivered: 'success' }`)\n\t\t * that CnStatusBadge resolves per label. Null when unset (falls back to\n\t\t * `badgeVariant`). Lets a manifest colour a status column without code.\n\t\t * A column-level `colorMap` (it reaches the cell as the property's\n\t\t * `colorMap`) works too, so `{ widget: 'badge', colorMap }` and\n\t\t * `{ widget: 'badge', widgetProps: { colorMap } }` read the same.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tbadgeColorMap() {\n\t\t\tif (this.badgeLook.variant) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (this.widgetProps && this.widgetProps.colorMap) || this.enumColorMap || null\n\t\t},\n\n\t\t/**\n\t\t * Color map for the auto-rendered enum badge — read from the schema\n\t\t * property's optional `colorMap` (or `x-color-map`). Null when unset, so\n\t\t * enum cells stay the default grey unless the schema opts into colours.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tenumColorMap() {\n\t\t\treturn (this.property && (this.property.colorMap || this.property['x-color-map'])) || null\n\t\t},\n\n\t\t/**\n\t\t * The manifest page id the built-in `widget:\"link\"` routes this row\n\t\t * to, or null.\n\t\t *\n\t\t * `widgetProps.routeField` names a sibling field on the row, and\n\t\t * `widgetProps.routeMap` maps that field's values to page ids, so one\n\t\t * column can link a person to `ContactDetail` and an organisation to\n\t\t * `OrganisationDetail` off `initiatorType`. A row whose value has no\n\t\t * entry in the map falls back to the fixed `widgetProps.route`, and\n\t\t * without one the cell renders as text. The map is the whole\n\t\t * vocabulary: a row value is only ever used as a KEY into it, never\n\t\t * as a route name itself, so row data cannot send a link to a page\n\t\t * the manifest did not name.\n\t\t *\n\t\t * @return {string|null} The page id, or null when none resolves.\n\t\t */\n\t\tlinkRouteName() {\n\t\t\tconst wp = this.widgetProps || {}\n\t\t\tconst map = wp.routeMap\n\t\t\tif (typeof wp.routeField === 'string' && wp.routeField !== '' && map && typeof map === 'object') {\n\t\t\t\tconst key = this.row ? this.row[wp.routeField] : undefined\n\t\t\t\tif (key !== undefined && key !== null && Object.hasOwn(map, String(key))) {\n\t\t\t\t\tconst mapped = map[String(key)]\n\t\t\t\t\tif (typeof mapped === 'string' && mapped !== '') {\n\t\t\t\t\t\treturn mapped\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn (typeof wp.route === 'string' && wp.route !== '') ? wp.route : null\n\t\t},\n\n\t\t/**\n\t\t * Items of the built-in `widget:\"refLabel\"`: one per referenced id with\n\t\t * its text, state (`label`, `loading` or `missing`) and optional link.\n\t\t *\n\t\t * @return {Array<{id: string, text: string, state: string, to: (object|null)}>}\n\t\t */\n\t\trefLabelItems() {\n\t\t\tif (this.widget !== 'refLabel') {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst wp = this.widgetProps || {}\n\t\t\tconst labels = wp.labels || {}\n\t\t\tconst raw = this.value\n\t\t\tconst list = Array.isArray(raw) ? raw : [raw]\n\t\t\treturn list\n\t\t\t\t.filter((id) => id !== null && id !== undefined && id !== '')\n\t\t\t\t.map((id) => {\n\t\t\t\t\tconst key = String(id)\n\t\t\t\t\tconst known = Object.hasOwn(labels, key)\n\t\t\t\t\tconst label = known ? labels[key] : undefined\n\t\t\t\t\tconst state = !known ? 'loading' : (label ? 'label' : 'missing')\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: key,\n\t\t\t\t\t\tstate,\n\t\t\t\t\t\ttext: state === 'label' ? label : (state === 'loading' ? '…' : key),\n\t\t\t\t\t\tto: (state === 'label' && typeof wp.route === 'string' && wp.route !== '') ? { name: wp.route, params: { id: key } } : null,\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Resolved router-link target for the built-in `widget:\"link\"`. When\n\t\t * a page id resolves (`widgetProps.route`, or the row's entry in\n\t\t * `widgetProps.routeMap`, see `linkRouteName`), returns\n\t\t * `{ name: route, params }`. Param map is `widgetProps.params`\n\t\t * (a map of route-param-name → row-field-name); when omitted,\n\t\t * defaults to `{ id: row[rowKey] }`. Returns null when no page id\n\t\t * resolves (the template falls through to `linkHref` or plain\n\t\t * text).\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tlinkRoute() {\n\t\t\tif (this.widget !== 'link') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst route = this.linkRouteName\n\t\t\tif (!route) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst paramMap = (this.widgetProps && this.widgetProps.params)\n\t\t\t\t|| { id: this.rowKey || 'id' }\n\t\t\tconst params = {}\n\t\t\tfor (const [routeParam, rowField] of Object.entries(paramMap)) {\n\t\t\t\tparams[routeParam] = this.row && this.row[rowField]\n\t\t\t}\n\t\t\treturn { name: route, params }\n\t\t},\n\n\t\t/**\n\t\t * Resolved external href for the built-in `widget:\"link\"` when\n\t\t * `widgetProps.href` is set. `{key}` placeholders in the href\n\t\t * are substituted from the row (`\"/x/{id}\"` + `row.id === \"42\"`\n\t\t * → `\"/x/42\"`). The final computed value is validated with\n\t\t * `safeHref` so row-injected values cannot introduce unsafe\n\t\t * schemes: a `javascript:` or `data:` result renders as plain text\n\t\t * instead of a link. Returns null when `href` isn't set.\n\t\t *\n\t\t * @return {string|null}\n\t\t */\n\t\tlinkHref() {\n\t\t\tif (this.widget !== 'link') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst href = this.widgetProps && this.widgetProps.href\n\t\t\tif (!href) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst resolved = String(href).replace(/\\{(\\w+)\\}/g, (_, key) => this.row && this.row[key] !== null && this.row[key] !== undefined ? String(this.row[key]) : '')\n\t\t\t// The safeHref retrofit (18700fd94) put this check here and a later\n\t\t\t// lint pass (e5ea00d51) dropped it, leaving the docblock above\n\t\t\t// promising a check the code no longer made: a template of `{url}`\n\t\t\t// put a row's `javascript:` value straight into `:href`. safeHref\n\t\t\t// answers '#' for anything unsafe; that is not a link worth\n\t\t\t// rendering, so fall through to plain text.\n\t\t\tconst safe = safeHref(resolved)\n\t\t\treturn safe === '#' ? null : safe\n\t\t},\n\n\t\t/**\n\t\t * Where a `widgetProps.href` link opens: a new tab, unless\n\t\t * `widgetProps.target` is `_self` or the href is an anchor on this\n\t\t * page (`#…`), so a row can open a card on the same page\n\t\t * (DqInstellingen: a connection row opens its settings card).\n\t\t *\n\t\t * @return {string|null} `_blank`, or null for the same tab.\n\t\t * @spec openspec/changes/cell-link-same-tab/specs/cell-link-same-tab/spec.md\n\t\t */\n\t\tlinkTarget() {\n\t\t\tconst target = this.widgetProps && this.widgetProps.target\n\t\t\tif (target === '_self' || (typeof this.linkHref === 'string' && this.linkHref.startsWith('#'))) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn '_blank'\n\t\t},\n\n\t\t/**\n\t\t * Resolved formatter function for this cell, or `null`. A column's\n\t\t * `formatter` id resolves against the injected `cnFormatters` registry.\n\t\t *\n\t\t * @return {((value: unknown, row: object, property: object, options: object) => unknown)|null}\n\t\t */\n\t\tformatterFn() {\n\t\t\tconst fn = this.formatter && this.cnFormatters && this.cnFormatters[this.formatter]\n\t\t\treturn typeof fn === 'function' ? fn : null\n\t\t},\n\n\t\t/**\n\t\t * The record of the page the table sits on, for a formatter that needs\n\t\t * more than its row: `{ objectId, object }`, both null outside a detail page.\n\t\t *\n\t\t * @return {{objectId: (string|null), object: (object|null)}}\n\t\t * @spec openspec/changes/cell-formatter-page-record/specs/cell-renderer/spec.md#requirement-a-cell-formatter-can-read-the-page-record\n\t\t */\n\t\tformatterContext() {\n\t\t\tconst unwrap = (c) => (c && typeof c === 'object' && 'value' in c ? c.value : c)\n\t\t\tconst ctx = unwrap(this.cnObjectContext) || unwrap(this.cnDetailObjectContext) || {}\n\t\t\treturn { objectId: ctx.objectId ?? null, object: ctx.object || ctx.objectData || null }\n\t\t},\n\n\t\t/** True when an explicit formatter is in play for this cell. */\n\t\thasFormatter() {\n\t\t\treturn this.formatterFn !== null\n\t\t},\n\n\t\t/**\n\t\t * The declarative format `style`, or null when no (recognised) `format`\n\t\t * spec is set. Drives the built-in currency / duration / number / percent\n\t\t * / swatch rendering paths.\n\t\t *\n\t\t * @return {string|null}\n\t\t */\n\t\tformatStyle() {\n\t\t\tconst s = this.format && this.format.style\n\t\t\treturn typeof s === 'string' ? s : null\n\t\t},\n\n\t\t/** True when a built-in NUMERIC format (currency/number/decimal/percent/duration) applies. */\n\t\thasBuiltinFormat() {\n\t\t\treturn ['currency', 'number', 'decimal', 'percent', 'duration'].includes(this.formatStyle)\n\t\t},\n\n\t\t/** True when this cell renders as a colour swatch (`format.style:\"swatch\"`). */\n\t\tisSwatch() {\n\t\t\treturn this.formatStyle === 'swatch'\n\t\t},\n\n\t\t/**\n\t\t * The swatch colour — read from the sibling row field named by\n\t\t * `format.colorField` (default `'color'`), through `format.colorMap`\n\t\t * when given (a stored name such as `blue` to a CSS colour). Returns\n\t\t * null when no usable colour is present (the dot is then omitted).\n\t\t *\n\t\t * @return {string|null}\n\t\t */\n\t\tswatchColor() {\n\t\t\tif (!this.isSwatch) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst field = (this.format && this.format.colorField) || 'color'\n\t\t\tconst c = this.row && this.row[field]\n\t\t\tif (typeof c !== 'string' || c.trim() === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst map = this.format && this.format.colorMap\n\t\t\tif (map && typeof map === 'object') {\n\t\t\t\treturn typeof map[c] === 'string' && map[c] !== '' ? map[c] : null\n\t\t\t}\n\t\t\treturn c\n\t\t},\n\n\t\tformattedValue() {\n\t\t\tif (this.formatterFn) {\n\t\t\t\ttry {\n\t\t\t\t\treturn this.formatterFn(this.value, this.row, this.property, this.formatterOptions || undefined, this.formatterContext)\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(`[CnCellRenderer] formatter \"${this.formatter}\" threw; falling back`, e)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.hasBuiltinFormat) {\n\t\t\t\treturn this.applyBuiltinFormat()\n\t\t\t}\n\t\t\treturn formatValue(this.value, this.property, { truncate: this.truncate })\n\t\t},\n\n\t\trawTitle() {\n\t\t\t// Show full value on hover if it was truncated\n\t\t\tconst raw = this.value\n\t\t\tif (typeof raw === 'string' && raw.length > this.truncate) {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn undefined\n\t\t},\n\n\t\t/**\n\t\t * Whether the cell draws a uuid, and so takes the monospace uuid\n\t\t * style. The property's `format: \"uuid\"` alone used to decide this,\n\t\t * so a reference that a formatter or a widget had resolved to its\n\t\t * name was still drawn as code. Now the value has to be a uuid AND\n\t\t * nothing resolves it: a resolving widget or formatter, or a value\n\t\t * that is not a uuid, renders in the normal text style.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-resolved-reference-is-not-drawn-as-a-uuid\n\t\t * @return {boolean}\n\t\t */\n\t\tshowsUuid() {\n\t\t\tif (this.property?.format !== 'uuid') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (this.widgetComponent || this.widget || this.hasFormatter) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn typeof this.value === 'string' && UUID_PATTERN.test(this.value)\n\t\t},\n\n\t\tcellClass() {\n\t\t\tconst classes = []\n\t\t\tif (this.propertyType === 'boolean') {\n\t\t\t\tclasses.push('cn-cell-renderer--boolean')\n\t\t\t}\n\t\t\tif (this.isEnum) {\n\t\t\t\tclasses.push('cn-cell-renderer--enum')\n\t\t\t}\n\t\t\tif (this.property?.format === 'date-time' || this.property?.format === 'date') {\n\t\t\t\tclasses.push('cn-cell-renderer--date')\n\t\t\t}\n\t\t\tif (this.showsUuid) {\n\t\t\t\tclasses.push('cn-cell-renderer--uuid')\n\t\t\t}\n\t\t\tif (this.propertyType === 'integer' || this.propertyType === 'number') {\n\t\t\t\tclasses.push('cn-cell-renderer--number')\n\t\t\t}\n\t\t\treturn classes\n\t\t},\n\t},\n\n\twatch: {\n\t\tavatarGroupId: {\n\t\t\timmediate: true,\n\t\t\t/**\n\t\t\t * Look the group's display name up (cached, once per id per page).\n\t\t\t *\n\t\t\t * @param {string} gid The group id.\n\t\t\t */\n\t\t\thandler(gid) {\n\t\t\t\tif (gid !== '') {\n\t\t\t\t\tloadGroupDisplayName(gid)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmounted() {\n\t\t// Warn once per column-property-name when `widget:\"link\"` resolves\n\t\t// to neither a `route` nor an `href` — silent fallback hides\n\t\t// manifest mistakes, an every-row warn is too noisy.\n\t\t// A column with a `routeMap` has declared its targets; a row whose\n\t\t// value is not in the map (an empty requester, say) is data, not a\n\t\t// manifest mistake, so it renders as text without the warning. The\n\t\t// same holds for a declared `href` whose resolved value safeHref\n\t\t// refused: the manifest is fine, the row value is not.\n\t\tif (\n\t\t\tthis.widget === 'link'\n\t\t\t&& !this.linkRoute\n\t\t\t&& !this.widgetProps?.href\n\t\t\t&& this.widgetProps?.fallback !== 'silent'\n\t\t\t&& !(this.widgetProps?.routeMap && typeof this.widgetProps.routeMap === 'object')\n\t\t) {\n\t\t\tconst key = this.property?.title || String(this.value).slice(0, 20)\n\t\t\tif (!WARNED_LINK_KEYS.has(key)) {\n\t\t\t\tWARNED_LINK_KEYS.add(key)\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn(`[CnCellRenderer] widget:\"link\" on \"${key}\" has no resolvable target — set widgetProps.route (page id) or widgetProps.href (URL with optional {field} placeholders); cell falls back to plain text.`)\n\t\t\t}\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Run an `actions` widget button for this row through the page.\n\t\t *\n\t\t * @param {{key: string}} item The button.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/table-actions-column/specs/index-page/spec.md#requirement-a-column-can-show-row-actions-as-buttons\n\t\t */\n\t\trunAction(item) {\n\t\t\tif (typeof this.widgetProps?.run === 'function') {\n\t\t\t\tthis.widgetProps.run(item.key, this.row)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The \"switch\" widget was flipped: show the new state and hand it to\n\t\t * `widgetProps.onToggle(row, on)`. A rejection puts the switch back.\n\t\t *\n\t\t * @param {boolean} on The new state.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onSwitchToggle(on) {\n\t\t\tconst toggle = this.widgetProps && this.widgetProps.onToggle\n\t\t\tif (typeof toggle !== 'function' || this.switchPending !== null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.switchPending = on === true\n\t\t\ttry {\n\t\t\t\tawait toggle(this.row, on === true)\n\t\t\t} catch (error) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnCellRenderer] switch toggle failed', error)\n\t\t\t} finally {\n\t\t\t\tthis.switchPending = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The initials of a name, at most two letters (first and last word).\n\t\t *\n\t\t * @param {string} name The name.\n\t\t * @return {string} The initials, upper case, or ''.\n\t\t * @spec openspec/changes/screens-avatar-cell-resolve/specs/data-table/spec.md\n\t\t */\n\t\tinitialsOf(name) {\n\t\t\tconst words = String(name || '').trim().split(/\\s+/).filter((word) => word !== '')\n\t\t\tif (words.length === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst first = Array.from(words[0])[0] || ''\n\t\t\tconst last = words.length > 1 ? (Array.from(words[words.length - 1])[0] || '') : ''\n\t\t\treturn (first + last).toLocaleUpperCase()\n\t\t},\n\n\t\t/**\n\t\t * Render the cell value per the declarative `format` spec (currency /\n\t\t * number / percent / duration). Mirrors CnStatWidget's formatting so a\n\t\t * KPI tile and a table column read identically. Non-numeric values fall\n\t\t * back to `formatValue`; an empty value renders an em-dash.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tapplyBuiltinFormat() {\n\t\t\tif (!this.hasValue) {\n\t\t\t\treturn '—'\n\t\t\t}\n\t\t\tconst fmt = this.format || {}\n\t\t\tif (fmt.style === 'duration') {\n\t\t\t\treturn this.formatDuration()\n\t\t\t}\n\t\t\tconst num = Number(this.value)\n\t\t\tif (!Number.isFinite(num)) {\n\t\t\t\treturn formatValue(this.value, this.property, { truncate: this.truncate })\n\t\t\t}\n\t\t\t// `decimal` defaults to one fraction digit, as formatMetricValue does.\n\t\t\tconst defaultDecimals = { currency: 2, decimal: 1 }\n\t\t\tconst decimals = Number.isFinite(fmt.decimals)\n\t\t\t\t? fmt.decimals\n\t\t\t\t: (defaultDecimals[fmt.style] ?? 0)\n\t\t\tlet body\n\t\t\tif (fmt.style === 'currency') {\n\t\t\t\t// `format.currencyField` reads the code from the row's own field.\n\t\t\t\tbody = formatMoney(num, { currency: fmt.currency, currencyField: fmt.currencyField, decimals }, this.row)\n\t\t\t} else if (fmt.style === 'percent') {\n\t\t\t\t// Values are stored as the literal percent (83.3), not a 0–1 ratio.\n\t\t\t\tbody = new Intl.NumberFormat(undefined, {\n\t\t\t\t\tminimumFractionDigits: decimals,\n\t\t\t\t\tmaximumFractionDigits: decimals,\n\t\t\t\t}).format(num) + '%'\n\t\t\t} else {\n\t\t\t\tbody = new Intl.NumberFormat(undefined, {\n\t\t\t\t\tminimumFractionDigits: decimals,\n\t\t\t\t\tmaximumFractionDigits: decimals,\n\t\t\t\t}).format(num)\n\t\t\t}\n\t\t\treturn `${fmt.prefix || ''}${body}${fmt.suffix || ''}`\n\t\t},\n\n\t\t/**\n\t\t * Render a numeric duration compactly (`1u 23m`, `45m 10s`, `12s`). The\n\t\t * raw value is seconds unless `format.unit` is `'milliseconds'` /\n\t\t * `'minutes'` / `'hours'`. The hour suffix is `u` (uur) to read naturally\n\t\t * under NL theming while staying digit-led for other locales.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tformatDuration() {\n\t\t\tconst fmt = this.format || {}\n\t\t\tlet secs = Number(this.value)\n\t\t\tif (!Number.isFinite(secs)) {\n\t\t\t\treturn formatValue(this.value, this.property, { truncate: this.truncate })\n\t\t\t}\n\t\t\tif (fmt.unit === 'milliseconds') {\n\t\t\t\t// Sub-second durations are the common case for a millisecond\n\t\t\t\t// field (request/step timings), and they would all collapse to\n\t\t\t\t// the `0s` floor below — so render them in ms directly.\n\t\t\t\tconst ms = Math.round(secs)\n\t\t\t\tif (Math.abs(ms) < 1000) {\n\t\t\t\t\treturn `${fmt.prefix || ''}${ms}ms${fmt.suffix || ''}`\n\t\t\t\t}\n\t\t\t\tsecs = ms / 1000\n\t\t\t} else if (fmt.unit === 'minutes') {\n\t\t\t\tsecs *= 60\n\t\t\t} else if (fmt.unit === 'hours') {\n\t\t\t\tsecs *= 3600\n\t\t\t}\n\t\t\tsecs = Math.round(secs)\n\t\t\tconst sign = secs < 0 ? '-' : ''\n\t\t\tsecs = Math.abs(secs)\n\t\t\tconst h = Math.floor(secs / 3600)\n\t\t\tconst m = Math.floor((secs % 3600) / 60)\n\t\t\tconst s = secs % 60\n\t\t\tconst parts = []\n\t\t\tif (h > 0) {\n\t\t\t\tparts.push(`${h}u`)\n\t\t\t}\n\t\t\tif (m > 0) {\n\t\t\t\tparts.push(`${m}m`)\n\t\t\t}\n\t\t\tif (s > 0 && h === 0) {\n\t\t\t\tparts.push(`${s}s`)\n\t\t\t}\n\t\t\tif (parts.length === 0) {\n\t\t\t\tparts.push('0s')\n\t\t\t}\n\t\t\treturn `${fmt.prefix || ''}${sign}${parts.join(' ')}${fmt.suffix || ''}`\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-cell-renderer__actions {\n\tdisplay: inline-flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n}\n\n.cn-cell-renderer__icon-label {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-weight: 600;\n}\n\n.cn-cell-renderer__switch {\n\tdisplay: inline-flex;\n}\n\n.cn-cell-renderer__mono {\n\tfont-family: monospace;\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cell-renderer--uuid {\n\tfont-family: monospace;\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cell-renderer--number {\n\tfont-variant-numeric: tabular-nums;\n}\n\n.cn-cell-renderer--date {\n\twhite-space: nowrap;\n}\n\n.cn-cell-renderer__date-state {\n\tmargin-inline-start: 8px;\n\tcolor: var(--color-success-text, var(--color-main-text));\n}\n\n.cn-cell-renderer__date-state--past {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cell-renderer__dash {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cell-renderer__avatar {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 8px;\n\tmax-width: 100%;\n\tvertical-align: middle;\n}\n\n.cn-cell-renderer__avatar-picture {\n\tdisplay: inline-flex;\n\tflex: none;\n}\n\n.cn-cell-renderer__date-block {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 12px;\n\twhite-space: nowrap;\n}\n\n.cn-cell-renderer__date-tile {\n\tdisplay: flex;\n\tflex: 0 0 46px;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: 5px 0;\n\tborder-radius: 8px;\n\tbackground-color: var(--color-background-dark);\n}\n\n.cn-cell-renderer__date-day {\n\tfont-size: 17px;\n\tline-height: 1;\n}\n\n.cn-cell-renderer__date-month,\n.cn-cell-renderer__date-time {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cell-renderer__date-month {\n\tfont-size: 12px;\n}\n\n.cn-cell-renderer__date-lines {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-cell-renderer__date-weekday {\n\tfont-weight: 600;\n}\n\n.cn-cell-renderer__avatar-initials {\n\tdisplay: inline-flex;\n\tflex: none;\n\talign-items: center;\n\tjustify-content: center;\n\tborder-radius: 50%;\n\tbackground-color: var(--color-primary-element-light);\n\tcolor: var(--color-primary-element-light-text);\n\tfont-size: 0.75em;\n\tfont-weight: 700;\n\tline-height: 1;\n}\n\n.cn-cell-renderer__avatar-text {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmin-width: 0;\n}\n\n.cn-cell-renderer__avatar-name {\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\t/* The name is the cell's text: it keeps the weight, size and colour the\n\t cell sets, as a plain value does. */\n\tfont: inherit;\n\tcolor: inherit;\n}\n\n.cn-cell-renderer__avatar-secondary {\n\tmin-width: 0;\n\toverflow: hidden;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n\tfont-weight: 400;\n\tline-height: 1.35;\n\ttext-overflow: ellipsis;\n\ttext-decoration: none;\n\twhite-space: nowrap;\n}\n\n/* A date that matched a rule is also set in a heavier weight, so the signal\n does not rest on colour alone. */\n.cn-cell-renderer__date--success,\n.cn-cell-renderer__date--warning,\n.cn-cell-renderer__date--error,\n.cn-cell-renderer__age--success,\n.cn-cell-renderer__age--warning,\n.cn-cell-renderer__age--error {\n\tfont-weight: 600;\n}\n\n.cn-cell-renderer__age {\n\twhite-space: nowrap;\n}\n\n/* The progress cell (abstract-progress-cell): a 56 x 8 bar, the percentage\n 8px beside it in the bar's tone, and an optional age badge after them, as\n PqLeads' Winkans column. The value is bold, so the tone is not the only signal. */\n.cn-cell-renderer__progress {\n\tdisplay: inline-flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 4px 8px;\n}\n\n.cn-cell-renderer__progress-line {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 8px;\n\twhite-space: nowrap;\n}\n\n.cn-cell-renderer__progress-bar {\n\tdisplay: inline-block;\n\twidth: 56px;\n\theight: 8px;\n\tborder-radius: 4px;\n\tbackground: var(--color-background-dark);\n\toverflow: hidden;\n}\n\n.cn-cell-renderer__progress-fill {\n\tdisplay: block;\n\theight: 100%;\n}\n\n.cn-cell-renderer__progress-fill--success {\n\tbackground: var(--color-element-success, var(--color-success));\n}\n\n.cn-cell-renderer__progress-fill--warning {\n\tbackground: var(--color-element-warning, var(--color-warning));\n}\n\n.cn-cell-renderer__progress-fill--error {\n\tbackground: var(--color-element-error, var(--color-error));\n}\n\n.cn-cell-renderer__progress-value {\n\tfont-size: 14px;\n\tfont-weight: 700;\n}\n\n.cn-cell-renderer__progress-value--success {\n\tcolor: var(--color-text-success, var(--color-success-text));\n}\n\n.cn-cell-renderer__progress-value--warning {\n\tcolor: var(--color-text-warning, var(--color-warning-text));\n}\n\n.cn-cell-renderer__progress-value--error {\n\tcolor: var(--color-text-error, var(--color-error-text));\n}\n\n.cn-cell-renderer__progress-badge {\n\tdisplay: inline-block;\n\tpadding: 3px 10px;\n\tborder-radius: 12px;\n\tfont-size: 13px;\n\tfont-weight: 600;\n\twhite-space: nowrap;\n}\n\n.cn-cell-renderer__progress-badge--error {\n\tcolor: var(--color-text-error, var(--color-error-text));\n\tbackground: var(--color-error-hover, var(--color-background-dark));\n\tbackground: color-mix(in srgb, var(--color-error) 12%, transparent);\n}\n\n.cn-cell-renderer__progress-badge--warning {\n\tcolor: var(--color-text-warning, var(--color-warning-text));\n\tbackground: color-mix(in srgb, var(--color-warning) 14%, transparent);\n}\n\n.cn-cell-renderer__progress-badge--success {\n\tcolor: var(--color-text-success, var(--color-success-text));\n\tbackground: color-mix(in srgb, var(--color-success) 12%, transparent);\n}\n\n.cn-cell-renderer__age--success {\n\tcolor: var(--color-text-success, var(--color-success-text));\n}\n\n.cn-cell-renderer__age--warning {\n\tcolor: var(--color-text-warning, var(--color-warning-text));\n}\n\n.cn-cell-renderer__age--error {\n\tcolor: var(--color-text-error, var(--color-error-text));\n}\n\n.cn-cell-renderer__due--empty {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* A withheld value: the lock and the word in the muted text colour. */\n.cn-cell-renderer__withheld {\n\tdisplay: inline-flex;\n\tgap: 4px;\n\talign-items: center;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cell-renderer__withheld-icon {\n\tflex: 0 0 auto;\n}\n\n.cn-cell-renderer__withheld-text {\n\tfont-style: italic;\n}\n\n/* A number cell: the flagged number reads in its variant's text colour, with\n the chip saying why (never colour alone). */\n.cn-cell-renderer__number {\n\tdisplay: inline-flex;\n\tgap: 4px;\n\talign-items: center;\n\twhite-space: nowrap;\n}\n\n.cn-cell-renderer__number--warning {\n\tcolor: var(--color-text-warning, var(--color-warning-text));\n\tfont-weight: 600;\n}\n\n.cn-cell-renderer__number--error {\n\tcolor: var(--color-text-error, var(--color-error-text));\n\tfont-weight: 600;\n}\n\n.cn-cell-renderer__date--success {\n\tcolor: var(--color-text-success, var(--color-success-text));\n}\n\n.cn-cell-renderer__date--warning {\n\tcolor: var(--color-text-warning, var(--color-warning-text));\n}\n\n.cn-cell-renderer__date--error {\n\tcolor: var(--color-text-error, var(--color-error-text));\n}\n\n.cn-cell-renderer__date--muted {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* The text variant, not the fill: from Nextcloud 32 `--color-success` is a\n background colour and too pale for a 16px glyph on a light theme. */\n.cn-cell-renderer__icon--success {\n\tcolor: var(--color-text-success, var(--color-success-text, var(--color-success)));\n}\n\n.cn-cell-renderer__swatch-wrap {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-cell-renderer__swatch-dot {\n\tdisplay: inline-block;\n\twidth: 12px;\n\theight: 12px;\n\tborder-radius: 50%;\n\tflex-shrink: 0;\n\tborder: 1px solid var(--color-border-dark);\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_createVNode","_createElementVNode","_toDisplayString","_createBlock","_resolveDynamicComponent","_mergeProps","_Fragment","_openBlock","_renderList","_withModifiers","_createTextVNode","_normalizeStyle"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import script from './CnChartWidget.vue2.js';
|
|
|
2
2
|
import { render } from './CnChartWidget.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-b6e600c4";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -302,6 +302,18 @@ var script = {
|
|
|
302
302
|
default: null,
|
|
303
303
|
},
|
|
304
304
|
|
|
305
|
+
/**
|
|
306
|
+
* A `bar-list` without a track: each bar as long as its share and its
|
|
307
|
+
* value right after the bar's end (DqProcesanalyse "Doorlooptijd per
|
|
308
|
+
* status"). False, the default, keeps the track and the value column.
|
|
309
|
+
*
|
|
310
|
+
* @spec openspec/changes/chart-bar-list-value-at-end/specs/chart-bar-list-value-at-end/spec.md#requirement-a-bar-list-can-put-each-value-at-its-bars-end
|
|
311
|
+
*/
|
|
312
|
+
barValueAtEnd: {
|
|
313
|
+
type: Boolean,
|
|
314
|
+
default: false,
|
|
315
|
+
},
|
|
316
|
+
|
|
305
317
|
/**
|
|
306
318
|
* A `matrix`'s row-label column width in px. Null, the default, sizes
|
|
307
319
|
* the column to its longest label (at least 120px); a width wraps a
|