@conduction/nextcloud-vue 2.83.0 → 2.85.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/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +131 -3
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +54 -18
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +232 -4
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +95 -51
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnBodySections/CnBodySections.vue.js +1 -1
- package/dist/esm/components/CnBodySections/CnBodySections.vue2.js +22 -1
- package/dist/esm/components/CnBodySections/CnBodySections.vue2.js.map +1 -1
- package/dist/esm/components/CnBodySections/CnBodySections.vue3.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +47 -4
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue.js +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +239 -3
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +5 -5
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +50 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +22 -12
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue.js +1 -1
- package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js +1 -1
- package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +226 -8
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +3 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +18 -5
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +38 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +45 -5
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue.js +1 -1
- package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js +1 -1
- package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +548 -77
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +191 -82
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +11 -2
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js +29 -6
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +126 -2
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +24 -2
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +106 -3
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js +67 -5
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js +181 -124
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue.js +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js +135 -2
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js +95 -43
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +5 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue.js +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +72 -5
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +33 -8
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +135 -11
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +6 -2
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue.js +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +17 -2
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +39 -6
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +140 -7
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +37 -9
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +6 -5
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue.js +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +16 -0
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +2 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js +53 -1
- package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js +3 -2
- package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +135 -0
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +19 -0
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +37 -19
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
- package/dist/esm/composables/useListView.js +21 -0
- package/dist/esm/composables/useListView.js.map +1 -1
- package/dist/esm/dialogs/CnTransitionInputDialog.vue.js +1 -1
- package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js +68 -6
- package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js.map +1 -1
- package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js +133 -105
- package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +38 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +78 -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 +1030 -533
- package/dist/esm/store/useObjectStore.js +16 -2
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/boardColumns.js +8 -1
- package/dist/esm/utils/boardColumns.js.map +1 -1
- package/dist/esm/utils/boardReferenceColumns.js +278 -0
- package/dist/esm/utils/boardReferenceColumns.js.map +1 -0
- package/dist/esm/utils/buildManifest.js +41 -4
- package/dist/esm/utils/buildManifest.js.map +1 -1
- package/dist/esm/utils/detailWidgetVisibility.js +149 -0
- package/dist/esm/utils/detailWidgetVisibility.js.map +1 -0
- package/dist/esm/utils/formatMetric.js +39 -3
- package/dist/esm/utils/formatMetric.js.map +1 -1
- package/dist/esm/utils/gridStack.js +40 -1
- package/dist/esm/utils/gridStack.js.map +1 -1
- package/dist/esm/utils/humaniseKey.js +41 -0
- package/dist/esm/utils/humaniseKey.js.map +1 -0
- package/dist/esm/utils/notificationRuleLabel.js +6 -23
- package/dist/esm/utils/notificationRuleLabel.js.map +1 -1
- package/dist/esm/utils/resolveManifestTokens.js +1 -0
- package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +9 -0
- package/dist/esm/utils/sentinelTokens.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/widgetDispatch.js +8 -2
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +17159 -13422
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1030 -533
- package/l10n/en.json +38 -1
- package/l10n/nl.json +78 -1
- package/package.json +1 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +160 -8
- package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +1 -1
- package/src/components/CnBoardView/CnBoardView.vue +262 -6
- package/src/components/CnBodySections/CnBodySections.vue +22 -1
- package/src/components/CnCountdownWidget/CnCountdownWidget.vue +47 -4
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +244 -8
- package/src/components/CnDashboardPage/CnDashboardPage.vue +62 -2
- package/src/components/CnDeltaWidget/CnDeltaWidget.vue +1 -1
- package/src/components/CnDetailPage/CnDetailPage.md +3 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +226 -8
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +3 -1
- package/src/components/CnFactsWidget/CnFactsWidget.vue +18 -5
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +63 -1
- package/src/components/CnGaugeWidget/CnGaugeWidget.vue +1 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +586 -25
- package/src/components/CnIndexPage/useSelfFetchList.js +10 -1
- package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +29 -6
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.md +1 -1
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +154 -5
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +106 -3
- package/src/components/CnRegisterMapping/CnRegisterMapping.md +5 -0
- package/src/components/CnRegisterMapping/CnRegisterMapping.vue +231 -96
- package/src/components/CnReportsPage/CnReportsPage.vue +179 -1
- package/src/components/CnSettingsPage/CnSettingsPage.vue +5 -1
- package/src/components/CnStagesWidget/CnStagesWidget.vue +103 -6
- package/src/components/CnStatWidget/CnStatWidget.vue +137 -12
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +34 -3
- package/src/components/CnTabsWidget/CnTabsWidget.vue +39 -6
- package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +140 -7
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.md +2 -1
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +42 -13
- package/src/components/CnWalkthrough/CnWalkthrough.vue +16 -0
- package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +2 -1
- package/src/components/CnWidgetCardGrid/CnWidgetCardGrid.vue +55 -2
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +135 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +10 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +44 -1
- package/src/composables/useListView.js +21 -0
- package/src/css/board.css +65 -4
- package/src/css/dialog.css +4 -2
- package/src/css/form-field.css +14 -0
- package/src/css/form-widget.css +11 -0
- package/src/css/index-page.css +49 -0
- package/src/css/kpi-card.css +13 -0
- package/src/css/look-board-reports.css +58 -0
- package/src/css/look-board.css +108 -7
- package/src/dialogs/CnTransitionInputDialog.vue +87 -7
- package/src/index.js +2 -0
- package/src/schemas/app-manifest-v2.schema.json +137 -6
- package/src/store/useObjectStore.js +16 -2
- package/src/utils/boardColumns.js +8 -1
- package/src/utils/boardReferenceColumns.js +274 -0
- package/src/utils/buildManifest.js +40 -3
- package/src/utils/detailWidgetVisibility.js +145 -0
- package/src/utils/formatMetric.js +38 -2
- package/src/utils/gridStack.js +39 -0
- package/src/utils/humaniseKey.js +38 -0
- package/src/utils/notificationRuleLabel.js +5 -23
- package/src/utils/resolveManifestTokens.js +1 -0
- package/src/utils/sentinelTokens.js +9 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/widgetDispatch.js +8 -2
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* detailWidgetVisibility: whether a widget on a detail page shows for the
|
|
6
|
+
* loaded record, through the shared `visibleWhen` grammar.
|
|
7
|
+
*
|
|
8
|
+
* A detail page can name records it reads beside the loaded one
|
|
9
|
+
* (`visibilityContext`, e.g. a case's case type). A condition reads such a
|
|
10
|
+
* record as `@context.<name>.<path>`, so a widget can be shown for some case types
|
|
11
|
+
* only while the configuration stays in the app manifest.
|
|
12
|
+
*
|
|
13
|
+
* @module utils/detailWidgetVisibility
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { cnFetchJson } from './cnFetch.js'
|
|
17
|
+
import { evaluateVisibleWhenLocal, isLocallyDecidableVisibleWhen, isWorkspaceCondition } from './visibleWhen.js'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Read a reference value as an id: a plain string or number, or an object
|
|
21
|
+
* carrying `id`, `uuid` or `@self.id` (an extended reference).
|
|
22
|
+
*
|
|
23
|
+
* @param {unknown} value The value of the reference field.
|
|
24
|
+
* @return {string} The id, or '' when there is none.
|
|
25
|
+
*/
|
|
26
|
+
function referenceId(value) {
|
|
27
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
28
|
+
return String(value)
|
|
29
|
+
}
|
|
30
|
+
if (value && typeof value === 'object') {
|
|
31
|
+
const id = value.id ?? value.uuid ?? (value['@self'] && value['@self'].id)
|
|
32
|
+
return id === undefined || id === null ? '' : String(id)
|
|
33
|
+
}
|
|
34
|
+
return ''
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The records a `visibilityContext` names for one loaded record.
|
|
39
|
+
*
|
|
40
|
+
* @param {object} context `{ <name>: { field, register, schema } }`.
|
|
41
|
+
* @param {object|null} record The loaded record.
|
|
42
|
+
* @return {Array<{key: string, register: string, schema: string, id: string}>} One entry per usable name; `id` is '' when the record holds no reference.
|
|
43
|
+
* @spec openspec/changes/detail-widgets-visible-when/specs/detail-widgets-visible-when/spec.md#requirement-a-detail-page-can-load-records-a-condition-names
|
|
44
|
+
*/
|
|
45
|
+
export function visibilityContextRefs(context, record) {
|
|
46
|
+
if (!context || typeof context !== 'object' || !record || typeof record !== 'object') {
|
|
47
|
+
return []
|
|
48
|
+
}
|
|
49
|
+
return Object.entries(context)
|
|
50
|
+
.filter(([name, ref]) => name !== '' && ref && typeof ref === 'object'
|
|
51
|
+
&& typeof ref.field === 'string' && ref.field !== ''
|
|
52
|
+
&& typeof ref.register === 'string' && ref.register !== ''
|
|
53
|
+
&& typeof ref.schema === 'string' && ref.schema !== '')
|
|
54
|
+
.map(([name, ref]) => ({
|
|
55
|
+
key: name,
|
|
56
|
+
register: ref.register,
|
|
57
|
+
schema: ref.schema,
|
|
58
|
+
id: referenceId(ref.field.split('.').reduce((obj, part) => (obj === null || obj === undefined ? obj : obj[part]), record)),
|
|
59
|
+
}))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Read the records `refs` names. A reference without an id, and a read that
|
|
64
|
+
* fails, resolve `null`: the record is absent, and a condition on it fails
|
|
65
|
+
* safe rather than throwing out of the page.
|
|
66
|
+
*
|
|
67
|
+
* @param {Array<{key: string, register: string, schema: string, id: string}>} refs From {@link visibilityContextRefs}.
|
|
68
|
+
* @param {Map<string, object|null>} [cache] Rows already read, by `register/schema/id`.
|
|
69
|
+
* @return {Promise<object>} `{ <name>: row | null }`.
|
|
70
|
+
* @spec openspec/changes/detail-widgets-visible-when/specs/detail-widgets-visible-when/spec.md#requirement-a-detail-page-can-load-records-a-condition-names
|
|
71
|
+
*/
|
|
72
|
+
export async function fetchVisibilityRows(refs, cache) {
|
|
73
|
+
const rows = {}
|
|
74
|
+
await Promise.all((refs || []).map(async (ref) => {
|
|
75
|
+
if (!ref.id) {
|
|
76
|
+
rows[ref.key] = null
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
const cacheKey = `${ref.register}/${ref.schema}/${ref.id}`
|
|
80
|
+
if (cache && cache.has(cacheKey)) {
|
|
81
|
+
rows[ref.key] = cache.get(cacheKey)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
let row
|
|
85
|
+
try {
|
|
86
|
+
const body = await cnFetchJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}/${encodeURIComponent(ref.id)}`)
|
|
87
|
+
row = body && typeof body === 'object' ? body : null
|
|
88
|
+
} catch {
|
|
89
|
+
row = null
|
|
90
|
+
}
|
|
91
|
+
if (cache) {
|
|
92
|
+
cache.set(cacheKey, row)
|
|
93
|
+
}
|
|
94
|
+
rows[ref.key] = row
|
|
95
|
+
}))
|
|
96
|
+
return rows
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The data a widget condition reads: the record, with the named records
|
|
101
|
+
* beside it under `@context.<name>`. Null while the record has not loaded.
|
|
102
|
+
*
|
|
103
|
+
* @param {object|null} record The loaded record.
|
|
104
|
+
* @param {object} [rows] `{ <name>: row | null }`.
|
|
105
|
+
* @return {object|null} The condition data.
|
|
106
|
+
* @spec openspec/changes/detail-widgets-visible-when/specs/detail-widgets-visible-when/spec.md#requirement-a-detail-grid-widget-shows-only-while-its-visiblewhen-holds-for-the-record
|
|
107
|
+
*/
|
|
108
|
+
export function widgetConditionData(record, rows) {
|
|
109
|
+
if (!record || typeof record !== 'object') {
|
|
110
|
+
return null
|
|
111
|
+
}
|
|
112
|
+
return { ...record, '@context': { ...(rows || {}) } }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Whether a widget's own `visibleWhen` lets it show on a detail page.
|
|
117
|
+
*
|
|
118
|
+
* No condition: shown, as before. A workspace condition: shown, a detail page
|
|
119
|
+
* has no workspace. While the data is not there (`data` null, or `pending`):
|
|
120
|
+
* hidden, so the widget never shows and then vanishes. A local condition is
|
|
121
|
+
* decided here; an endpoint or source condition takes its answer from
|
|
122
|
+
* `answers` (by widget id) and is hidden until it has one.
|
|
123
|
+
*
|
|
124
|
+
* @param {object|null} widget The widget definition.
|
|
125
|
+
* @param {object|null} data From {@link widgetConditionData}.
|
|
126
|
+
* @param {{pending?: boolean, answers?: object}} [state] Whether named records are still on their way, and the remote answers.
|
|
127
|
+
* @return {boolean} Whether the widget shows.
|
|
128
|
+
* @spec openspec/changes/detail-widgets-visible-when/specs/detail-widgets-visible-when/spec.md#requirement-a-detail-grid-widget-shows-only-while-its-visiblewhen-holds-for-the-record
|
|
129
|
+
*/
|
|
130
|
+
export function detailWidgetVisible(widget, data, state = {}) {
|
|
131
|
+
const cond = widget && typeof widget === 'object' ? widget.visibleWhen : null
|
|
132
|
+
if (cond === null || cond === undefined) {
|
|
133
|
+
return true
|
|
134
|
+
}
|
|
135
|
+
if (typeof cond === 'object' && !Array.isArray(cond) && isWorkspaceCondition(cond)) {
|
|
136
|
+
return true
|
|
137
|
+
}
|
|
138
|
+
if (!data || state.pending === true) {
|
|
139
|
+
return false
|
|
140
|
+
}
|
|
141
|
+
if (isLocallyDecidableVisibleWhen(cond)) {
|
|
142
|
+
return evaluateVisibleWhenLocal(cond, data)
|
|
143
|
+
}
|
|
144
|
+
return Boolean(state.answers && state.answers[widget.id] === true)
|
|
145
|
+
}
|
|
@@ -226,6 +226,35 @@ export function metricLocale() {
|
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
/**
|
|
230
|
+
* A format spec with its literal `prefix` / `suffix` run through the host
|
|
231
|
+
* translate function. A `@`-token, an empty string, or a missing translate
|
|
232
|
+
* leaves the affix as written.
|
|
233
|
+
*
|
|
234
|
+
* @param {object} format The `content.format` spec.
|
|
235
|
+
* @param {((text: string) => string)|null} translate The host translate function.
|
|
236
|
+
* @return {object} A copy of the spec (the same object when nothing changes).
|
|
237
|
+
* @spec openspec/changes/screens-metric-affix-translate/specs/dashboard-page/spec.md#requirement-a-metric-prefix-and-suffix-are-translated
|
|
238
|
+
*/
|
|
239
|
+
export function translateMetricAffixes(format, translate) {
|
|
240
|
+
const fmt = format || {}
|
|
241
|
+
if (typeof translate !== 'function') {
|
|
242
|
+
return fmt
|
|
243
|
+
}
|
|
244
|
+
let out = fmt
|
|
245
|
+
for (const key of ['prefix', 'suffix']) {
|
|
246
|
+
const text = fmt[key]
|
|
247
|
+
if (typeof text === 'string' && text.trim() !== '' && text.charAt(0) !== '@') {
|
|
248
|
+
const translated = translate(text)
|
|
249
|
+
if (typeof translated === 'string' && translated !== text) {
|
|
250
|
+
out = out === fmt ? { ...fmt } : out
|
|
251
|
+
out[key] = translated
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return out
|
|
256
|
+
}
|
|
257
|
+
|
|
229
258
|
/**
|
|
230
259
|
* Format a numeric value per a manifest `content.format` spec.
|
|
231
260
|
*
|
|
@@ -250,11 +279,18 @@ export function metricLocale() {
|
|
|
250
279
|
* @param {unknown} value The raw value to format.
|
|
251
280
|
* @param {object} format The `content.format` spec (`{ style, currency, currencyField, decimals, prefix, suffix }`).
|
|
252
281
|
* @param {object} configCtx The page-level app-config map for `@config.<key>` resolution.
|
|
282
|
+
* A literal `prefix` / `suffix` (one that is not a `@config.` or `@object.`
|
|
283
|
+
* token) is run through `translate` when one is given, as every other
|
|
284
|
+
* manifest-authored string is, so an app's catalogue entry for " days"
|
|
285
|
+
* (" dagen") applies. A token is resolved, never translated.
|
|
286
|
+
*
|
|
253
287
|
* @param {object|null} [object] The detail page's object, for `currencyField` and `@object.<field>`.
|
|
288
|
+
* @param {((text: string) => string)|null} [translate] The host app's translate function (`cnTranslate`).
|
|
254
289
|
* @return {string} The formatted display string.
|
|
255
290
|
* @spec openspec/changes/stat-currency-from-object/specs/dashboard-page/spec.md#requirement-a-money-value-is-shown-in-the-currency-the-entry-names
|
|
291
|
+
* @spec openspec/changes/screens-metric-affix-translate/specs/dashboard-page/spec.md#requirement-a-metric-prefix-and-suffix-are-translated
|
|
256
292
|
*/
|
|
257
|
-
export function formatMetricValue(value, format, configCtx, object = null) {
|
|
293
|
+
export function formatMetricValue(value, format, configCtx, object = null, translate = null) {
|
|
258
294
|
if (value === null || value === undefined) {
|
|
259
295
|
return '—'
|
|
260
296
|
}
|
|
@@ -263,7 +299,7 @@ export function formatMetricValue(value, format, configCtx, object = null) {
|
|
|
263
299
|
return String(value)
|
|
264
300
|
}
|
|
265
301
|
|
|
266
|
-
const fmt = resolveConfigFormat(format, configCtx, object)
|
|
302
|
+
const fmt = resolveConfigFormat(translateMetricAffixes(format, translate), configCtx, object)
|
|
267
303
|
const locale = metricLocale()
|
|
268
304
|
// `duration-hours` and `decimal` default to ONE fraction digit; the
|
|
269
305
|
// other styles keep the pre-existing default of 0.
|
package/src/utils/gridStack.js
CHANGED
|
@@ -53,3 +53,42 @@ export function readGridGeometry(items) {
|
|
|
53
53
|
}
|
|
54
54
|
return map
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Where the items under a growing cell go so they keep their order: every item
|
|
59
|
+
* the growth would overlap moves down by the growth, and so does every item
|
|
60
|
+
* that move would overlap in turn. Items beside the cell stay put.
|
|
61
|
+
*
|
|
62
|
+
* GridStack resolves the same collision itself, but not in order: when
|
|
63
|
+
* pipelinq's size-to-content banner grew from two rows to three, the
|
|
64
|
+
* full-width card under the stat tiles landed below the lists.
|
|
65
|
+
*
|
|
66
|
+
* @spec openspec/changes/dashboard-growth-keeps-order/specs/dashboard-grid/spec.md#requirement-a-growing-cell-keeps-the-order-of-the-cards-under-it
|
|
67
|
+
* @param {Array<{x: number, y: number, w: number, h: number}>} nodes The grid's items (the growing one among them).
|
|
68
|
+
* @param {{x: number, y: number, w: number, h: number}} node The growing item.
|
|
69
|
+
* @param {number} h Its new height, in grid rows.
|
|
70
|
+
* @return {Array<{node: object, y: number}>} The items to move and their new row, bottom-most first.
|
|
71
|
+
*/
|
|
72
|
+
export function planGrowth(nodes, node, h) {
|
|
73
|
+
const delta = h - node.h
|
|
74
|
+
if (!(delta > 0)) {
|
|
75
|
+
return []
|
|
76
|
+
}
|
|
77
|
+
const moved = new Map()
|
|
78
|
+
const overlaps = (a, b) => a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h
|
|
79
|
+
const queue = [{ x: node.x, y: node.y, w: node.w, h }]
|
|
80
|
+
while (queue.length > 0) {
|
|
81
|
+
const rect = queue.shift()
|
|
82
|
+
for (const other of nodes) {
|
|
83
|
+
if (other === node || moved.has(other) || !overlaps(rect, other)) {
|
|
84
|
+
continue
|
|
85
|
+
}
|
|
86
|
+
const next = { x: other.x, y: other.y + delta, w: other.w, h: other.h }
|
|
87
|
+
moved.set(other, next.y)
|
|
88
|
+
queue.push(next)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return [...moved.entries()]
|
|
92
|
+
.map(([item, y]) => ({ node: item, y }))
|
|
93
|
+
.sort((a, b) => b.y - a.y)
|
|
94
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* Make an identifier-shaped key readable for a person. Pure: no Vue.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Split camelCase, snake_case and kebab-case into words, keep acronyms, and
|
|
10
|
+
* capitalise only the first word.
|
|
11
|
+
*
|
|
12
|
+
* `caseAssigned` → "Case assigned", `object_created` → "Object created",
|
|
13
|
+
* `newBRPRecord` → "New BRP record".
|
|
14
|
+
*
|
|
15
|
+
* @param {string} key The key.
|
|
16
|
+
* @return {string} A readable sentence-case label, or the key when it has no words.
|
|
17
|
+
* @spec openspec/changes/data-widget-nested-objects/specs/data-widget-nested-objects/spec.md#requirement-a-nested-object-reads-as-a-list-per-key
|
|
18
|
+
*/
|
|
19
|
+
export function humaniseKey(key) {
|
|
20
|
+
const words = String(key || '')
|
|
21
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
22
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
23
|
+
.split(/[\s_\-.]+/)
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
if (words.length === 0) {
|
|
26
|
+
return String(key || '')
|
|
27
|
+
}
|
|
28
|
+
return words
|
|
29
|
+
.map((word, index) => {
|
|
30
|
+
const isAcronym = word.length > 1 && word === word.toUpperCase() && /[A-Z]/.test(word)
|
|
31
|
+
if (isAcronym) {
|
|
32
|
+
return word
|
|
33
|
+
}
|
|
34
|
+
const lower = word.toLowerCase()
|
|
35
|
+
return index === 0 ? lower.charAt(0).toUpperCase() + lower.slice(1) : lower
|
|
36
|
+
})
|
|
37
|
+
.join(' ')
|
|
38
|
+
}
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
* @spec openspec/changes/notification-rule-labels-and-runtime-version/specs/notification-preferences/spec.md
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
+
import { humaniseKey } from './humaniseKey.js'
|
|
32
|
+
|
|
31
33
|
/**
|
|
32
34
|
* Pick the string for the current language out of a string or per-locale map.
|
|
33
35
|
*
|
|
@@ -54,34 +56,14 @@ export function resolveLocalised(value, language = 'en') {
|
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
/**
|
|
57
|
-
* Make a rule key readable
|
|
58
|
-
* words, keep acronyms, and capitalise only the first word.
|
|
59
|
-
*
|
|
60
|
-
* `caseAssigned` → "Case assigned", `object_created` → "Object created",
|
|
61
|
-
* `newBRPRecord` → "New BRP record".
|
|
59
|
+
* Make a rule key readable (`caseAssigned` → "Case assigned"); see `humaniseKey`.
|
|
62
60
|
*
|
|
63
61
|
* @param {string} key The rule key.
|
|
64
62
|
* @return {string} A readable sentence-case label, or the key when it has no words.
|
|
63
|
+
* @spec openspec/changes/notification-rule-labels-and-runtime-version/specs/notification-preferences/spec.md#requirement-a-notification-rule-reads-as-a-label
|
|
65
64
|
*/
|
|
66
65
|
export function humaniseRuleKey(key) {
|
|
67
|
-
|
|
68
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
69
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
70
|
-
.split(/[\s_\-.]+/)
|
|
71
|
-
.filter(Boolean)
|
|
72
|
-
if (words.length === 0) {
|
|
73
|
-
return String(key || '')
|
|
74
|
-
}
|
|
75
|
-
return words
|
|
76
|
-
.map((word, index) => {
|
|
77
|
-
const isAcronym = word.length > 1 && word === word.toUpperCase() && /[A-Z]/.test(word)
|
|
78
|
-
if (isAcronym) {
|
|
79
|
-
return word
|
|
80
|
-
}
|
|
81
|
-
const lower = word.toLowerCase()
|
|
82
|
-
return index === 0 ? lower.charAt(0).toUpperCase() + lower.slice(1) : lower
|
|
83
|
-
})
|
|
84
|
-
.join(' ')
|
|
66
|
+
return humaniseKey(key)
|
|
85
67
|
}
|
|
86
68
|
|
|
87
69
|
/**
|
|
@@ -56,6 +56,7 @@ export const SENTINEL_RESOLVERS = Object.freeze({
|
|
|
56
56
|
answer: 'resolveAssignValue (utils/formAssign.js)',
|
|
57
57
|
row: 'CnFieldListWidget (rowActions request)',
|
|
58
58
|
subject: 'CnBannerWidget (action route)',
|
|
59
|
+
context: 'detailWidgetVisible (utils/detailWidgetVisibility.js)',
|
|
59
60
|
})
|
|
60
61
|
|
|
61
62
|
/**
|
|
@@ -89,6 +89,9 @@ export const SENTINEL_TOKEN_PATTERNS = Object.freeze({
|
|
|
89
89
|
// A field of the record a start-here attention card names (`@subject.<field>`, dot path),
|
|
90
90
|
// read by the banner widget's action routes.
|
|
91
91
|
subject: '^@subject\\.[A-Za-z][A-Za-z0-9_.]*$',
|
|
92
|
+
// A record a detail page reads beside the loaded one (`visibilityContext`), read by a
|
|
93
|
+
// grid widget's or a tab's `visibleWhen` as `@context.<name>.<field>` (dot path).
|
|
94
|
+
context: '^@context\\.[A-Za-z][A-Za-z0-9_.]*$',
|
|
92
95
|
// Deprecated-but-not-yet-removed tokens. Kept in the schema union so
|
|
93
96
|
// deployed manifests still validate during the migration window; the gate
|
|
94
97
|
// downgrades them to a WARN (see SENTINEL_DEPRECATIONS).
|
|
@@ -112,6 +115,7 @@ export const SENTINEL_CONTEXTS = Object.freeze([
|
|
|
112
115
|
'answer',
|
|
113
116
|
'row',
|
|
114
117
|
'subject',
|
|
118
|
+
'context',
|
|
115
119
|
])
|
|
116
120
|
|
|
117
121
|
/**
|
|
@@ -172,6 +176,11 @@ export const SENTINEL_VOCABULARY = Object.freeze({
|
|
|
172
176
|
description: 'A field of the record an attention card names (the first row of its ordered `visibleWhen` source query), read when the card builds an action\'s route. Dot paths are allowed (`@subject.owner.id`).',
|
|
173
177
|
members: ['@subject.<field>'],
|
|
174
178
|
},
|
|
179
|
+
context: {
|
|
180
|
+
resolver: 'CnDetailPage visibilityContext (detailWidgetVisibility), read by a grid widget\'s or a tab\'s visibleWhen',
|
|
181
|
+
description: 'A record the detail page reads beside the loaded one, named in `config.visibilityContext` (a case\'s case type). A `visibleWhen` field reads it as `@context.<name>.<field>`, dot paths allowed (`@context.caseType.category`).',
|
|
182
|
+
members: ['@context.<name>.<field>'],
|
|
183
|
+
},
|
|
175
184
|
})
|
|
176
185
|
|
|
177
186
|
/**
|