@conduction/nextcloud-vue 2.79.0 → 2.80.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/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js +3 -1
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +32 -3
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +10 -8
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +39 -3
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +79 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +133 -96
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- 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/CnDetailPage/CnDetailPage.vue2.js +62 -4
- 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.vue2.js +24 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +43 -4
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +11 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +736 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +238 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/index.js +30 -0
- package/dist/esm/components/CnFactsWidget/index.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +10 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +551 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +183 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/index.js +29 -0
- package/dist/esm/components/CnFieldListWidget/index.js.map +1 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +65 -17
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +4 -4
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +18 -3
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +305 -72
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +31 -13
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +10 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js.map +1 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +99 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +48 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -0
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js +29 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +10 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +387 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +169 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/index.js +31 -0
- package/dist/esm/components/CnRecordListWidget/index.js.map +1 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js +10 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js +351 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js +71 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSectionsWidget/index.js +35 -0
- package/dist/esm/components/CnSectionsWidget/index.js.map +1 -0
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +81 -2
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +1 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +122 -7
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +84 -23
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +4 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +106 -2
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +105 -53
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
- package/dist/esm/composables/useFormDraft.js +140 -9
- package/dist/esm/composables/useFormDraft.js.map +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +28 -20
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js +9 -0
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection.js +21 -0
- package/dist/esm/integrations/builtin/field-inspection.js.map +1 -1
- package/dist/esm/integrations/offline/checklistSubmission.js +141 -0
- package/dist/esm/integrations/offline/checklistSubmission.js.map +1 -0
- package/dist/esm/integrations/offline/fieldCollectionHelpers.js +70 -5
- package/dist/esm/integrations/offline/fieldCollectionHelpers.js.map +1 -1
- package/dist/esm/integrations/offline/index.js +2 -1
- package/dist/esm/integrations/offline/index.js.map +1 -1
- package/dist/esm/integrations/offline/offlineDb.js +6 -1
- package/dist/esm/integrations/offline/offlineDb.js.map +1 -1
- package/dist/esm/integrations/offline/syncReplayService.js +8 -2
- package/dist/esm/integrations/offline/syncReplayService.js.map +1 -1
- package/dist/esm/l10n/en.json.js +12 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +16 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +1000 -59
- package/dist/esm/utils/detailActionModel.js +60 -3
- package/dist/esm/utils/detailActionModel.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +114 -0
- package/dist/esm/utils/fillTemplate.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +4 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/queryPresets.js +96 -0
- package/dist/esm/utils/queryPresets.js.map +1 -0
- package/dist/esm/utils/resolveFilterTokens.js +11 -0
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/schema.js +21 -0
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +3 -3
- 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 +7 -1
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +12368 -7717
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1000 -59
- package/l10n/en.json +12 -1
- package/l10n/nl.json +16 -2
- package/package.json +1 -1
- package/src/components/CnActionButtons/CnActionButtons.vue +2 -0
- package/src/components/CnActionsBar/CnActionsBar.md +4 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +42 -10
- package/src/components/CnBannerWidget/CnBannerWidget.vue +39 -3
- package/src/components/CnCellRenderer/CnCellRenderer.vue +188 -1
- package/src/components/CnDataTable/CnDataTable.vue +1 -0
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +63 -3
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +66 -0
- package/src/components/CnFactsWidget/CnFactsWidget.vue +1050 -0
- package/src/components/CnFactsWidget/index.js +29 -0
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +746 -0
- package/src/components/CnFieldListWidget/index.js +28 -0
- package/src/components/CnFormDialog/CnFormDialog.md +9 -3
- package/src/components/CnFormDialog/CnFormDialog.vue +69 -21
- package/src/components/CnFormPage/CnFormPage.vue +18 -3
- package/src/components/CnIndexPage/CnIndexPage.vue +254 -6
- package/src/components/CnIndexPage/CnIndexPageWidgets.vue +169 -0
- package/src/components/CnPageRenderer/CnPageRenderer.vue +29 -1
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +603 -0
- package/src/components/CnRecordListWidget/index.js +30 -0
- package/src/components/CnSectionsWidget/CnSectionsWidget.vue +489 -0
- package/src/components/CnSectionsWidget/index.js +34 -0
- package/src/components/CnStagesWidget/CnStagesWidget.vue +81 -2
- package/src/components/CnStatWidget/CnStatWidget.vue +1 -1
- package/src/components/CnTabsWidget/CnTabsWidget.vue +184 -8
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +4 -0
- package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +188 -4
- package/src/components/index.js +4 -0
- package/src/composables/__tests__/useFormDraft.spec.js +69 -0
- package/src/composables/useFormDraft.js +139 -8
- package/src/css/form-field.css +14 -0
- package/src/css/index-page.css +71 -0
- package/src/css/kpi-card.css +32 -0
- package/src/css/look-board-detail.css +77 -0
- package/src/css/look-board-index.css +51 -0
- package/src/css/look-board.css +37 -2
- package/src/css/table.css +19 -0
- package/src/index.js +4 -0
- package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +35 -20
- package/src/integrations/builtin/field-inspection.js +21 -0
- package/src/integrations/offline/checklistSubmission.js +137 -0
- package/src/integrations/offline/fieldCollectionHelpers.js +69 -4
- package/src/integrations/offline/index.js +3 -0
- package/src/integrations/offline/offlineDb.js +6 -1
- package/src/integrations/offline/syncReplayService.js +8 -2
- package/src/schemas/app-manifest-v2.schema.json +104 -5
- package/src/utils/detailActionModel.js +59 -2
- package/src/utils/fillTemplate.js +110 -0
- package/src/utils/libraryWidgetKeys.js +4 -0
- package/src/utils/queryPresets.js +93 -0
- package/src/utils/resolveFilterTokens.js +11 -0
- package/src/utils/schema.js +21 -0
- package/src/utils/sentinelTokens.js +3 -3
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/widgetDispatch.js +7 -1
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* value, and the audit trail is read by people answering "who changed this".
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
+
import { getCurrentUser } from '@nextcloud/auth'
|
|
26
27
|
import { translate as t } from '@nextcloud/l10n'
|
|
27
28
|
|
|
28
29
|
/** How long a draft stays interesting, in milliseconds (7 days). */
|
|
@@ -34,6 +35,9 @@ export const DRAFT_DEBOUNCE_MS = 500
|
|
|
34
35
|
/** The prefix every draft key carries, so a sweep can find them. */
|
|
35
36
|
export const DRAFT_KEY_PREFIX = 'cn-form-draft'
|
|
36
37
|
|
|
38
|
+
/** The user part of a draft key when nobody is signed in. */
|
|
39
|
+
export const DRAFT_ANONYMOUS_USER = 'anonymous'
|
|
40
|
+
|
|
37
41
|
/**
|
|
38
42
|
* The storage key one form's draft lives under.
|
|
39
43
|
*
|
|
@@ -54,10 +58,124 @@ export function draftKey({ appId = '', schema = '', objectId = '', userId = '' }
|
|
|
54
58
|
String(appId || 'app'),
|
|
55
59
|
String(schema || 'schema'),
|
|
56
60
|
String(objectId || 'new'),
|
|
57
|
-
String(userId ||
|
|
61
|
+
String(userId || DRAFT_ANONYMOUS_USER),
|
|
58
62
|
].join(':')
|
|
59
63
|
}
|
|
60
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Who a draft belongs to: the id the host passed, else the signed-in
|
|
67
|
+
* Nextcloud user, else '' (which {@link draftKey} stores as `anonymous`).
|
|
68
|
+
*
|
|
69
|
+
* 🔴 A HOST THAT PASSES NOTHING STILL GETS PER-USER DRAFTS. Leaving the user
|
|
70
|
+
* to every host meant no host passed it, and every draft on a shared device
|
|
71
|
+
* was keyed `anonymous`: the next person at the counter was offered what the
|
|
72
|
+
* last one typed.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} [explicit] The id the host passed (`draftUserId`).
|
|
75
|
+
* @return {string} The user id, or '' when nobody is signed in.
|
|
76
|
+
* @spec openspec/changes/form-draft-on-change-and-help-text-spacing/specs/form-draft-on-change-and-help-text-spacing/spec.md#requirement-a-draft-belongs-to-the-signed-in-user
|
|
77
|
+
*/
|
|
78
|
+
export function resolveDraftUserId(explicit = '') {
|
|
79
|
+
if (explicit) {
|
|
80
|
+
return String(explicit)
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const uid = getCurrentUser()?.uid
|
|
84
|
+
return uid ? String(uid) : ''
|
|
85
|
+
} catch {
|
|
86
|
+
// No Nextcloud page around us (a test, an embed): nobody is signed in.
|
|
87
|
+
return ''
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Remove every draft stored under the anonymous user.
|
|
93
|
+
*
|
|
94
|
+
* Called once a signed-in user is known. Such a draft was written by a page
|
|
95
|
+
* that did not know who was typing, so it may be somebody else's, and it is
|
|
96
|
+
* deleted rather than moved: moving it would hand it to whoever opened the
|
|
97
|
+
* form next.
|
|
98
|
+
*
|
|
99
|
+
* @return {number} How many drafts were removed.
|
|
100
|
+
* @spec openspec/changes/form-draft-on-change-and-help-text-spacing/specs/form-draft-on-change-and-help-text-spacing/spec.md#requirement-a-draft-belongs-to-the-signed-in-user
|
|
101
|
+
*/
|
|
102
|
+
export function clearAnonymousDrafts() {
|
|
103
|
+
let removed = 0
|
|
104
|
+
try {
|
|
105
|
+
const storage = window.localStorage
|
|
106
|
+
const suffix = ':' + DRAFT_ANONYMOUS_USER
|
|
107
|
+
const doomed = []
|
|
108
|
+
for (let i = 0; i < storage.length; i++) {
|
|
109
|
+
const key = storage.key(i)
|
|
110
|
+
if (key && key.startsWith(DRAFT_KEY_PREFIX + ':') && key.endsWith(suffix)) {
|
|
111
|
+
doomed.push(key)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
for (const key of doomed) {
|
|
115
|
+
storage.removeItem(key)
|
|
116
|
+
removed++
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
// Blocked storage holds no drafts to leak.
|
|
120
|
+
}
|
|
121
|
+
return removed
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Whether a value counts as empty for a draft: null, undefined, '' and [].
|
|
126
|
+
*
|
|
127
|
+
* @param {unknown} value The value.
|
|
128
|
+
* @return {boolean} True when empty.
|
|
129
|
+
*/
|
|
130
|
+
function isEmptyDraftValue(value) {
|
|
131
|
+
return value === null || value === undefined || value === '' || (Array.isArray(value) && value.length === 0)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Whether two field values are the same for the purpose of a draft.
|
|
136
|
+
*
|
|
137
|
+
* Empty values are equal to each other: an input reports a missing value back
|
|
138
|
+
* as '', and that is the control normalising, not a person typing.
|
|
139
|
+
*
|
|
140
|
+
* @param {unknown} a One value.
|
|
141
|
+
* @param {unknown} b The other.
|
|
142
|
+
* @return {boolean} True when they are the same.
|
|
143
|
+
*/
|
|
144
|
+
export function sameDraftValue(a, b) {
|
|
145
|
+
if (isEmptyDraftValue(a) && isEmptyDraftValue(b)) {
|
|
146
|
+
return true
|
|
147
|
+
}
|
|
148
|
+
if (a === b) {
|
|
149
|
+
return true
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
return JSON.stringify(a) === JSON.stringify(b)
|
|
153
|
+
} catch {
|
|
154
|
+
return false
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Whether a person changed the form: one of the fields they edited holds a
|
|
160
|
+
* value different from the one the form opened with.
|
|
161
|
+
*
|
|
162
|
+
* 🔴 ONLY EDITED FIELDS COUNT. A form fills in values by itself after it
|
|
163
|
+
* opens (defaults, normalised dates, the active organisation, options that
|
|
164
|
+
* arrive later). Counting those wrote a draft and said "Saved just now" on a
|
|
165
|
+
* dialog nobody had touched.
|
|
166
|
+
*
|
|
167
|
+
* @param {object} values The current values.
|
|
168
|
+
* @param {object} baseline The values the form opened with.
|
|
169
|
+
* @param {string[]} editedKeys The fields a person edited.
|
|
170
|
+
* @return {boolean} True when there is something worth keeping.
|
|
171
|
+
* @spec openspec/changes/form-draft-on-change-and-help-text-spacing/specs/form-draft-on-change-and-help-text-spacing/spec.md#requirement-a-draft-is-written-only-after-a-person-changes-a-field
|
|
172
|
+
*/
|
|
173
|
+
export function hasDraftChanges(values, baseline, editedKeys) {
|
|
174
|
+
const now = values || {}
|
|
175
|
+
const base = baseline || {}
|
|
176
|
+
return (editedKeys || []).some((key) => !sameDraftValue(now[key], base[key]))
|
|
177
|
+
}
|
|
178
|
+
|
|
61
179
|
/**
|
|
62
180
|
* Read one draft back, or null when there is nothing usable.
|
|
63
181
|
*
|
|
@@ -218,13 +336,6 @@ export function formDraftMixin() {
|
|
|
218
336
|
draftSavedAt: 0,
|
|
219
337
|
/** Non-reactive handle for the debounce. */
|
|
220
338
|
draftTimeout: null,
|
|
221
|
-
/**
|
|
222
|
-
* Whether the form's own initial population has happened.
|
|
223
|
-
*
|
|
224
|
-
* The first change to the values is the component building
|
|
225
|
-
* them, not a person typing. See the watcher that reads this.
|
|
226
|
-
*/
|
|
227
|
-
draftSeeded: false,
|
|
228
339
|
}
|
|
229
340
|
},
|
|
230
341
|
|
|
@@ -265,6 +376,26 @@ export function formDraftMixin() {
|
|
|
265
376
|
}, DRAFT_DEBOUNCE_MS)
|
|
266
377
|
},
|
|
267
378
|
|
|
379
|
+
/**
|
|
380
|
+
* The form is back to what it opened with: cancel a pending write,
|
|
381
|
+
* remove a draft written earlier and stop announcing anything.
|
|
382
|
+
*
|
|
383
|
+
* @param {string} key The storage key.
|
|
384
|
+
* @return {void}
|
|
385
|
+
* @spec openspec/changes/form-draft-on-change-and-help-text-spacing/specs/form-draft-on-change-and-help-text-spacing/spec.md#requirement-a-draft-is-written-only-after-a-person-changes-a-field
|
|
386
|
+
*/
|
|
387
|
+
settleUnchangedDraft(key) {
|
|
388
|
+
if (this.draftTimeout) {
|
|
389
|
+
clearTimeout(this.draftTimeout)
|
|
390
|
+
this.draftTimeout = null
|
|
391
|
+
}
|
|
392
|
+
if (this.draftState !== 'idle') {
|
|
393
|
+
clearDraft(key)
|
|
394
|
+
this.draftState = 'idle'
|
|
395
|
+
this.draftSavedAt = 0
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
|
|
268
399
|
/**
|
|
269
400
|
* Drop the draft and stop announcing anything about it.
|
|
270
401
|
*
|
package/src/css/form-field.css
CHANGED
|
@@ -82,6 +82,20 @@
|
|
|
82
82
|
min-height: var(--cn-field-height);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/* NcTextField draws its input inside `.input-field__main-wrapper`, which has a
|
|
86
|
+
FIXED height (`--default-clickable-area`, 34px on Nextcloud 34) and a 2px
|
|
87
|
+
padding ring for the focus outline. The min-height above makes the input
|
|
88
|
+
40px, so without this the input overflowed its wrapper by 6px and the help
|
|
89
|
+
line under it, which starts at the wrapper's bottom edge, sat behind the
|
|
90
|
+
input's lower border. NcSelect grows with its content, which is why only
|
|
91
|
+
plain inputs showed it. The wrapper now takes the field height plus its
|
|
92
|
+
padding ring, so the input fits and the help line starts below it.
|
|
93
|
+
@spec openspec/changes/form-draft-on-change-and-help-text-spacing/specs/form-draft-on-change-and-help-text-spacing/spec.md#requirement-help-text-starts-below-every-input */
|
|
94
|
+
.cn-look-board .cn-form-dialog__field .input-field__main-wrapper,
|
|
95
|
+
.cn-look-board .cn-form-page__field .input-field__main-wrapper {
|
|
96
|
+
height: calc(var(--cn-field-height) + 2 * var(--border-width-input-focused, 2px));
|
|
97
|
+
}
|
|
98
|
+
|
|
85
99
|
.cn-look-board .cn-form-dialog__field textarea,
|
|
86
100
|
.cn-look-board .cn-form-page__field textarea {
|
|
87
101
|
padding: 10px 12px;
|
package/src/css/index-page.css
CHANGED
|
@@ -240,3 +240,74 @@
|
|
|
240
240
|
color: var(--color-text-maxcontrast);
|
|
241
241
|
font-size: 0.9em;
|
|
242
242
|
}
|
|
243
|
+
|
|
244
|
+
/* An index page with cards beside its list (`sidePanel`,
|
|
245
|
+
screens-index-stat-row): the header rows span the page, the toolbar and the
|
|
246
|
+
list take the first column and the side panel the second (300px, 20px
|
|
247
|
+
apart), spanning both. Every other child (dialogs, the inline sidebar) sits
|
|
248
|
+
in a zero-height area after them. The list row is the flexible one, so a
|
|
249
|
+
side panel taller than the toolbar never opens a gap under the toolbar.
|
|
250
|
+
Below 1024px the panel moves above the toolbar. */
|
|
251
|
+
.cn-index-page--with-side-panel {
|
|
252
|
+
display: grid;
|
|
253
|
+
grid-template-columns: minmax(0, 1fr) var(--cn-index-side-panel-width, 300px);
|
|
254
|
+
grid-template-rows: auto auto auto auto minmax(0, 1fr) auto;
|
|
255
|
+
grid-template-areas:
|
|
256
|
+
"head head"
|
|
257
|
+
"below below"
|
|
258
|
+
"stats stats"
|
|
259
|
+
"bar side"
|
|
260
|
+
"body side"
|
|
261
|
+
"extra extra";
|
|
262
|
+
column-gap: 20px;
|
|
263
|
+
align-content: start;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.cn-index-page--with-side-panel > * {
|
|
267
|
+
grid-area: extra;
|
|
268
|
+
min-width: 0;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.cn-index-page--with-side-panel > :first-child {
|
|
272
|
+
grid-area: head;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.cn-index-page--with-side-panel > .cn-index-page__below-header {
|
|
276
|
+
grid-area: below;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.cn-index-page--with-side-panel > .cn-index-page__stat-row {
|
|
280
|
+
grid-area: stats;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.cn-index-page--with-side-panel > .cn-actions-bar {
|
|
284
|
+
grid-area: bar;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.cn-index-page--with-side-panel > .cn-index-page__body {
|
|
288
|
+
grid-area: body;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.cn-index-page--with-side-panel > .cn-index-page__side-panel {
|
|
292
|
+
grid-area: side;
|
|
293
|
+
align-self: start;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
@media (max-width: 1024px) {
|
|
297
|
+
.cn-index-page--with-side-panel {
|
|
298
|
+
grid-template-columns: minmax(0, 1fr);
|
|
299
|
+
grid-template-areas:
|
|
300
|
+
"head"
|
|
301
|
+
"below"
|
|
302
|
+
"stats"
|
|
303
|
+
"side"
|
|
304
|
+
"bar"
|
|
305
|
+
"body"
|
|
306
|
+
"extra";
|
|
307
|
+
grid-template-rows: auto auto auto auto auto minmax(0, 1fr) auto;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.cn-index-page--with-side-panel > .cn-index-page__side-panel {
|
|
311
|
+
margin-bottom: 20px;
|
|
312
|
+
}
|
|
313
|
+
}
|
package/src/css/kpi-card.css
CHANGED
|
@@ -450,6 +450,38 @@ a.cn-kpi-card--stacked:focus-visible {
|
|
|
450
450
|
font-weight: 400;
|
|
451
451
|
}
|
|
452
452
|
|
|
453
|
+
/* A caption on a line of its own wraps, up to two lines (DqDashboard: "haalden
|
|
454
|
+
hun termijn, naar doorlooptijd" runs over two lines in a narrow tile). The
|
|
455
|
+
one-line rule of `.cn-kpi-card__label` is for a caption BESIDE the number,
|
|
456
|
+
where a second line would push the tile taller; this line has the width of
|
|
457
|
+
the tile to itself. The clamp is safe here because the element is a plain
|
|
458
|
+
span, not a flex row (compare the title note above).
|
|
459
|
+
@spec openspec/changes/dashboard-tile-text-fits/specs/dashboard-tile-text-fits/spec.md#requirement-a-kpi-tiles-caption-and-board-label-wrap-to-two-lines */
|
|
460
|
+
.cn-kpi-card--stacked .cn-kpi-card__caption-line {
|
|
461
|
+
display: -webkit-box;
|
|
462
|
+
-webkit-box-orient: vertical;
|
|
463
|
+
-webkit-line-clamp: 2;
|
|
464
|
+
line-clamp: 2;
|
|
465
|
+
white-space: normal;
|
|
466
|
+
overflow-wrap: break-word;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/* Board look: the label wraps too, up to two lines, beside its icon badge
|
|
470
|
+
("Afgehandeld deze maand"). The clamp sits on the label's own span
|
|
471
|
+
(`cn-kpi-card__title-text`), not on the title row, which stays the flex row
|
|
472
|
+
that carries the badge or the range select. */
|
|
473
|
+
.cn-kpi-card--board .cn-kpi-card__title-text {
|
|
474
|
+
flex: 1 1 auto;
|
|
475
|
+
min-width: 0;
|
|
476
|
+
display: -webkit-box;
|
|
477
|
+
-webkit-box-orient: vertical;
|
|
478
|
+
-webkit-line-clamp: 2;
|
|
479
|
+
line-clamp: 2;
|
|
480
|
+
overflow: hidden;
|
|
481
|
+
white-space: normal;
|
|
482
|
+
overflow-wrap: break-word;
|
|
483
|
+
}
|
|
484
|
+
|
|
453
485
|
/* The 18px glyph before the label, drawn only on a link tile. */
|
|
454
486
|
.cn-kpi-card--board .cn-kpi-card__title {
|
|
455
487
|
display: flex;
|
|
@@ -87,6 +87,61 @@
|
|
|
87
87
|
margin-bottom: 0;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/* The breadcrumb is a line of 14px text between the pills and the meta, as
|
|
91
|
+
the boards draw it (screens-detail-header-row-parity). NcBreadcrumbs grows to
|
|
92
|
+
the full row and draws each crumb as a 34px tertiary button, which pushed the
|
|
93
|
+
pills above it and the meta under it. The doubled class outranks its scoped
|
|
94
|
+
rules. */
|
|
95
|
+
.cn-look-board .cn-detail-page__header-row2 .cn-detail-page__breadcrumbs.cn-detail-page__breadcrumbs {
|
|
96
|
+
flex: 0 1 auto;
|
|
97
|
+
height: auto;
|
|
98
|
+
min-height: 0;
|
|
99
|
+
min-width: 0;
|
|
100
|
+
width: auto;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.cn-look-board .cn-detail-page__header-row2 .cn-detail-page__breadcrumbs .breadcrumb__crumbs {
|
|
104
|
+
gap: 8px;
|
|
105
|
+
margin: 0;
|
|
106
|
+
min-height: 0;
|
|
107
|
+
padding: 0;
|
|
108
|
+
vertical-align: middle;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.cn-look-board .cn-detail-page__header-row2 .cn-detail-page__breadcrumbs .vue-crumb.vue-crumb {
|
|
112
|
+
gap: 8px;
|
|
113
|
+
height: auto;
|
|
114
|
+
min-height: 0;
|
|
115
|
+
min-width: 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.cn-look-board .cn-detail-page__header-row2 .cn-detail-page__breadcrumbs .vue-crumb .button-vue.button-vue {
|
|
119
|
+
background: transparent;
|
|
120
|
+
border: 0;
|
|
121
|
+
font-size: 14px;
|
|
122
|
+
font-weight: 400;
|
|
123
|
+
height: auto;
|
|
124
|
+
min-height: 0;
|
|
125
|
+
min-width: 0;
|
|
126
|
+
padding: 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.cn-look-board .cn-detail-page__header-row2 .cn-detail-page__breadcrumbs .vue-crumb .button-vue__text {
|
|
130
|
+
font-weight: inherit;
|
|
131
|
+
line-height: 20px;
|
|
132
|
+
padding: 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* The list is the link; the record's own crumb is plain muted text. */
|
|
136
|
+
.cn-look-board .cn-detail-page__header-row2 .cn-detail-page__breadcrumbs .vue-crumb .button-vue[aria-current='page'] {
|
|
137
|
+
color: var(--color-text-maxcontrast);
|
|
138
|
+
cursor: default;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.cn-look-board .cn-detail-page__header-row2 .cn-detail-page__breadcrumbs .vue-crumb__separator {
|
|
142
|
+
margin: 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
90
145
|
.cn-look-board .cn-detail-page__header-dot,
|
|
91
146
|
.cn-look-board .cn-detail-page__header-meta {
|
|
92
147
|
color: var(--color-text-maxcontrast);
|
|
@@ -229,6 +284,22 @@
|
|
|
229
284
|
border-radius: 0;
|
|
230
285
|
}
|
|
231
286
|
|
|
287
|
+
/* A tab that holds a stack (`widgetIds`, screens-relation-pages): the panel
|
|
288
|
+
draws no frame and no padding; each widget is its own card. The first card
|
|
289
|
+
joins the open tab (the first-card rule above), the next ones stand 20px
|
|
290
|
+
apart, as the Overzicht of DqContact and PqOrganisatie draws them. */
|
|
291
|
+
.cn-look-board .cn-tabs--board.cn-tabs-widget__tabs .cn-tabs__content:has(> .cn-tab:not([hidden]) > .cn-tabs-widget__stack) {
|
|
292
|
+
background: transparent;
|
|
293
|
+
border: 0;
|
|
294
|
+
border-radius: 0;
|
|
295
|
+
padding: 0;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* The stack's next cards are whole cards, not joined to the strip. */
|
|
299
|
+
.cn-look-board .cn-tabs--board .cn-tabs__content .cn-tab .cn-tabs-widget__stack-item .cn-widget-wrapper {
|
|
300
|
+
border-radius: var(--cn-board-card-radius);
|
|
301
|
+
}
|
|
302
|
+
|
|
232
303
|
/* ---- Body cards and side cards ----------------------------------------- */
|
|
233
304
|
|
|
234
305
|
.cn-look-board .cn-detail-page .cn-widget-wrapper,
|
|
@@ -308,6 +379,12 @@
|
|
|
308
379
|
display: none;
|
|
309
380
|
}
|
|
310
381
|
|
|
382
|
+
/* A side card whose widget reports a header action (screens-relation-pages:
|
|
383
|
+
the "+ Toevoegen" of Contactkanalen and Relaties) keeps that one button. */
|
|
384
|
+
.cn-look-board .cn-detail-page__side .cn-widget-wrapper__actions:has(.cn-detail-page__catalog-action) {
|
|
385
|
+
display: flex;
|
|
386
|
+
}
|
|
387
|
+
|
|
311
388
|
/* The notice at the top of the side column: radius 12, padding 14px 16px. */
|
|
312
389
|
.cn-look-board .cn-detail-page__side-item--notice .cn-widget-wrapper,
|
|
313
390
|
.cn-look-board .cn-detail-page__side-item--notice .cn-banner-widget,
|
|
@@ -301,6 +301,15 @@
|
|
|
301
301
|
color: var(--color-main-text);
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
+
/* A segment the page cannot open: drawn, as the boards do, but faded and inert. */
|
|
305
|
+
.cn-look-board .cn-actions-bar--board .cn-actions-bar__view-toggle .cn-actions-bar__view-toggle-btn--disabled,
|
|
306
|
+
.cn-look-board .cn-actions-bar--board .cn-actions-bar__view-toggle .cn-actions-bar__view-toggle-btn--disabled:hover {
|
|
307
|
+
background: transparent !important;
|
|
308
|
+
color: var(--color-text-maxcontrast);
|
|
309
|
+
opacity: 0.5;
|
|
310
|
+
cursor: not-allowed;
|
|
311
|
+
}
|
|
312
|
+
|
|
304
313
|
/* The search field: 40px, radius 8, not a pill. */
|
|
305
314
|
.cn-look-board .cn-actions-bar--board .cn-actions-bar__search {
|
|
306
315
|
flex: 1 1 260px;
|
|
@@ -448,6 +457,48 @@
|
|
|
448
457
|
border-bottom: 0;
|
|
449
458
|
}
|
|
450
459
|
|
|
460
|
+
/* The table fits its card (screens-index-table-fit-parity). Under the auto
|
|
461
|
+
layout every one-line cell asked for its whole text up to the 22rem cap, so
|
|
462
|
+
seven text columns made a 1500px table in a 1118px card at a 1440px
|
|
463
|
+
viewport: the last columns and the row menus sat past the card's edge. The
|
|
464
|
+
fixed layout shares the card's width: the selection and menu columns keep
|
|
465
|
+
their own width, the first data column takes 18%, the others split the
|
|
466
|
+
rest, and a value that does not fit is cut with an ellipsis (its full text
|
|
467
|
+
stays in the cell's tooltip). A column's own `width` still wins. */
|
|
468
|
+
.cn-look-board .cn-index-page__main--table .cn-table-container--board .cn-data-table {
|
|
469
|
+
table-layout: fixed;
|
|
470
|
+
width: 100%;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.cn-look-board .cn-index-page__main--table .cn-table-container--board .cn-data-table th.cn-table-head--title {
|
|
474
|
+
width: 18%;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/* The board's data cells sit 12px apart (PqTickets), the first one 18px from
|
|
478
|
+
the selection column; the selection and menu columns keep 16px. */
|
|
479
|
+
.cn-look-board .cn-index-page__main--table .cn-table-container--board .cn-data-table th:not(.cn-table-col--checkbox):not(.cn-table-col--actions):not(.cn-table-col--icon),
|
|
480
|
+
.cn-look-board .cn-index-page__main--table .cn-table-container--board .cn-data-table td:not(.cn-table-col--checkbox):not(.cn-table-col--actions):not(.cn-table-col--icon) {
|
|
481
|
+
padding-inline: 12px;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.cn-look-board .cn-index-page__main--table .cn-table-container--board .cn-data-table th.cn-table-head--title,
|
|
485
|
+
.cn-look-board .cn-index-page__main--table .cn-table-container--board .cn-data-table td.cn-table-col--title {
|
|
486
|
+
padding-inline-start: 18px;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.cn-look-board .cn-index-page__main--table .cn-table-container--board .cn-data-table td > .cn-cell-renderer {
|
|
490
|
+
display: block;
|
|
491
|
+
min-width: 0;
|
|
492
|
+
overflow: hidden;
|
|
493
|
+
text-overflow: ellipsis;
|
|
494
|
+
white-space: nowrap;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/* A narrow header cuts its label, not its filter funnel. */
|
|
498
|
+
.cn-look-board .cn-index-page__main--table .cn-table-container--board .cn-data-table th .cn-table-header__sort:has(~ .cn-table-header__filter) {
|
|
499
|
+
max-width: calc(100% - 26px);
|
|
500
|
+
}
|
|
501
|
+
|
|
451
502
|
/* The selection column: 28px, 18px checkboxes. */
|
|
452
503
|
.cn-look-board .cn-table-container--board .cn-data-table .cn-table-col--checkbox {
|
|
453
504
|
box-sizing: content-box;
|
package/src/css/look-board.css
CHANGED
|
@@ -12,9 +12,14 @@
|
|
|
12
12
|
* counterpart default to one.
|
|
13
13
|
*/
|
|
14
14
|
.cn-look-board {
|
|
15
|
-
/* Page content area: padding, max width, gap between blocks.
|
|
15
|
+
/* Page content area: padding, max width, gap between blocks. The content
|
|
16
|
+
spans the whole content area (screens-one-scroller-parity): grids grow
|
|
17
|
+
with the window and shrink only when the sidebar takes its share. A
|
|
18
|
+
1240px cap left dead space right of the content, and on a dashboard,
|
|
19
|
+
which scrolled itself, put the scrollbar beside the content instead of
|
|
20
|
+
at the window edge. */
|
|
16
21
|
--cn-board-content-padding: 24px 28px;
|
|
17
|
-
--cn-board-content-max-width:
|
|
22
|
+
--cn-board-content-max-width: none;
|
|
18
23
|
--cn-board-section-gap: 20px;
|
|
19
24
|
/* Buttons and inputs. */
|
|
20
25
|
--cn-board-control-height: 40px;
|
|
@@ -54,6 +59,15 @@
|
|
|
54
59
|
gap: var(--cn-board-section-gap);
|
|
55
60
|
}
|
|
56
61
|
|
|
62
|
+
/* One scroller: the content area. CnDashboardPage scrolls itself
|
|
63
|
+
(`height: 100%; overflow-y: auto`), which nested a second scrollbar inside
|
|
64
|
+
NcAppContent's; under the board look the page grows with its widgets and
|
|
65
|
+
NcAppContent, which already spans to the window edge, scrolls it. */
|
|
66
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) {
|
|
67
|
+
height: auto;
|
|
68
|
+
overflow-y: visible;
|
|
69
|
+
}
|
|
70
|
+
|
|
57
71
|
.cn-look-board .cn-index-page:not(.cn-look-nextcloud *) .cn-page-header,
|
|
58
72
|
.cn-look-board .cn-settings-page:not(.cn-look-nextcloud *) .cn-page-header,
|
|
59
73
|
.cn-look-board .cn-index-page:not(.cn-look-nextcloud *) .cn-index-page__below-header,
|
|
@@ -167,6 +181,27 @@
|
|
|
167
181
|
gap: 2px;
|
|
168
182
|
}
|
|
169
183
|
|
|
184
|
+
/* No scrollbars inside the navigation (screens-one-scroller-parity).
|
|
185
|
+
NcAppNavigation gives the region above the list `overflow-y: scroll`, an
|
|
186
|
+
always-on track (with arrow buttons on classic scrollbars) beside the
|
|
187
|
+
primary action that has nothing to scroll. It takes its content's height
|
|
188
|
+
instead. A menu taller than the window still scrolls (wheel, touch, focus),
|
|
189
|
+
without drawing a bar of its own; the one visible scrollbar is the page's. */
|
|
190
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation__body:not(.app-navigation__body--no-list) {
|
|
191
|
+
flex-shrink: 0;
|
|
192
|
+
overflow: visible;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation__list,
|
|
196
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation__body--no-list {
|
|
197
|
+
scrollbar-width: none;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation__list::-webkit-scrollbar,
|
|
201
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation__body--no-list::-webkit-scrollbar {
|
|
202
|
+
display: none;
|
|
203
|
+
}
|
|
204
|
+
|
|
170
205
|
/* The board's vertical rhythm (AppZijbalk): the primary action 20px from the
|
|
171
206
|
top, the list 20px under it, entries 2px apart. An empty search slot is a
|
|
172
207
|
flex child too and pushed everything 20px down, so it goes. */
|
package/src/css/table.css
CHANGED
|
@@ -258,6 +258,25 @@
|
|
|
258
258
|
white-space: nowrap;
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
/* The secondary line of the growing column ("2026-0004" under a case title)
|
|
262
|
+
is a line of its own with its own ellipsis. The cell does not wrap, so in
|
|
263
|
+
the board look, where the secondary line is an inline block (the title
|
|
264
|
+
underline cannot reach an atomic inline box), it stayed on the title's line
|
|
265
|
+
and ran out of the tile. A full-width float is a block (computed `display:
|
|
266
|
+
block`) that always starts under the line before it, and, being out of the
|
|
267
|
+
flow, the cell's underline does not reach it either. The cell is a table
|
|
268
|
+
cell, which contains its floats, so the row grows to hold the line.
|
|
269
|
+
@spec openspec/changes/dashboard-tile-text-fits/specs/dashboard-tile-text-fits/spec.md#requirement-the-secondary-line-of-a-narrow-tables-growing-column-sits-under-the-value */
|
|
270
|
+
.cn-data-table.cn-data-table--fit td.cn-table-col--grow .cn-table-cell__secondary {
|
|
271
|
+
float: inline-start;
|
|
272
|
+
clear: both;
|
|
273
|
+
box-sizing: border-box;
|
|
274
|
+
width: 100%;
|
|
275
|
+
overflow: hidden;
|
|
276
|
+
text-overflow: ellipsis;
|
|
277
|
+
white-space: nowrap;
|
|
278
|
+
}
|
|
279
|
+
|
|
261
280
|
.cn-data-table th {
|
|
262
281
|
background: var(--color-background-dark);
|
|
263
282
|
font-weight: 500;
|
package/src/index.js
CHANGED
|
@@ -112,6 +112,7 @@ export {
|
|
|
112
112
|
CnEnvironmentBanner,
|
|
113
113
|
CnExportWizard,
|
|
114
114
|
CnFacetSidebar,
|
|
115
|
+
CnFactsWidget,
|
|
115
116
|
CnFavouriteToggle,
|
|
116
117
|
CnFeaturesAndRoadmapLink,
|
|
117
118
|
CnFeaturesAndRoadmapPage,
|
|
@@ -120,6 +121,7 @@ export {
|
|
|
120
121
|
CnFeaturesTab,
|
|
121
122
|
CnFederationStatus,
|
|
122
123
|
CnFieldHelper,
|
|
124
|
+
CnFieldListWidget,
|
|
123
125
|
CnFileField,
|
|
124
126
|
CnFileManager,
|
|
125
127
|
CnFilePreview,
|
|
@@ -236,6 +238,7 @@ export {
|
|
|
236
238
|
CnQuickFilterBar,
|
|
237
239
|
CnQuicklinksWidget,
|
|
238
240
|
CnQuicklinksWidgetForm,
|
|
241
|
+
CnRecordListWidget,
|
|
239
242
|
CnReferencePreview,
|
|
240
243
|
CnRegisterMapping,
|
|
241
244
|
CnRegisterSchemaSelect,
|
|
@@ -258,6 +261,7 @@ export {
|
|
|
258
261
|
CnSaveViewDialog,
|
|
259
262
|
CnSchemaFormDialog,
|
|
260
263
|
CnSearchPage,
|
|
264
|
+
CnSectionsWidget,
|
|
261
265
|
CnSettingsCard,
|
|
262
266
|
CnSettingsPage,
|
|
263
267
|
CnSettingsSection,
|