@conduction/nextcloud-vue 2.83.0 → 2.84.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/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/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 +125 -3
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +3 -3
- 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 +5 -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/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 +203 -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/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/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 +31 -2
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- 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 +37 -2
- 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/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/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 +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- 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 +33 -3
- 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/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/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/l10n/en.json.js +16 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +38 -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 +448 -296
- package/dist/esm/utils/buildManifest.js +37 -3
- 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/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/nextcloud-vue.cjs.js +1706 -454
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +448 -296
- package/l10n/en.json +16 -1
- package/l10n/nl.json +38 -1
- package/package.json +1 -1
- package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +1 -1
- package/src/components/CnBodySections/CnBodySections.vue +22 -1
- package/src/components/CnCountdownWidget/CnCountdownWidget.vue +47 -4
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +128 -6
- package/src/components/CnDashboardPage/CnDashboardPage.vue +5 -0
- package/src/components/CnDeltaWidget/CnDeltaWidget.vue +1 -1
- package/src/components/CnDetailPage/CnDetailPage.md +2 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +203 -4
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +63 -1
- package/src/components/CnGaugeWidget/CnGaugeWidget.vue +1 -1
- package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +29 -6
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.md +1 -1
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +31 -2
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +37 -2
- package/src/components/CnReportsPage/CnReportsPage.vue +179 -1
- package/src/components/CnStagesWidget/CnStagesWidget.vue +103 -6
- package/src/components/CnStatWidget/CnStatWidget.vue +1 -1
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +34 -3
- package/src/components/CnTabsWidget/CnTabsWidget.vue +33 -3
- package/src/components/CnWalkthrough/CnWalkthrough.vue +16 -0
- package/src/css/form-field.css +14 -0
- package/src/css/form-widget.css +11 -0
- package/src/css/look-board-reports.css +58 -0
- package/src/css/look-board.css +12 -0
- package/src/dialogs/CnTransitionInputDialog.vue +87 -7
- package/src/schemas/app-manifest-v2.schema.json +23 -2
- package/src/utils/buildManifest.js +36 -2
- package/src/utils/detailWidgetVisibility.js +145 -0
- package/src/utils/formatMetric.js +38 -2
- package/src/utils/resolveManifestTokens.js +1 -0
- package/src/utils/sentinelTokens.js +9 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -34,7 +34,31 @@
|
|
|
34
34
|
<template>
|
|
35
35
|
<div class="cn-reports-page" :class="{ 'cn-reports-page--board': isBoard }">
|
|
36
36
|
<div class="cn-reports-page__header">
|
|
37
|
-
|
|
37
|
+
<!-- The header buttons (`headerButtons`) beside the title, as the
|
|
38
|
+
board's "Downloaden" and the buildiq square (board DcRapportages).
|
|
39
|
+
Without them, and outside the board look, the title stands alone
|
|
40
|
+
as before. -->
|
|
41
|
+
<div v-if="headerButtonsRow.length > 0" class="cn-reports-page__heading-row">
|
|
42
|
+
<h2 class="cn-reports-page__title">
|
|
43
|
+
{{ resolvedTitle }}
|
|
44
|
+
</h2>
|
|
45
|
+
<div class="cn-reports-page__header-buttons" data-testid="cn-reports-header-buttons">
|
|
46
|
+
<template v-for="button in headerButtonsRow" :key="button.key">
|
|
47
|
+
<CnBuildiqEditButton v-if="button.action === '__buildiq'" />
|
|
48
|
+
<NcButton
|
|
49
|
+
v-else
|
|
50
|
+
:variant="button.variant"
|
|
51
|
+
:data-testid="`cn-reports-header-button-${button.key}`"
|
|
52
|
+
@click="onHeaderButton(button)">
|
|
53
|
+
<template v-if="button.icon" #icon>
|
|
54
|
+
<CnIcon :name="button.icon" :size="20" />
|
|
55
|
+
</template>
|
|
56
|
+
{{ button.label }}
|
|
57
|
+
</NcButton>
|
|
58
|
+
</template>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
<h2 v-else class="cn-reports-page__title">
|
|
38
62
|
{{ resolvedTitle }}
|
|
39
63
|
</h2>
|
|
40
64
|
<p v-if="resolvedDescription" class="cn-reports-page__description">
|
|
@@ -134,9 +158,12 @@
|
|
|
134
158
|
* The Reports page: one surface listing an app's reports as cards.
|
|
135
159
|
*/
|
|
136
160
|
import { translate as t } from '@nextcloud/l10n'
|
|
161
|
+
import { NcButton } from '@nextcloud/vue'
|
|
137
162
|
import { defineAsyncComponent } from 'vue'
|
|
163
|
+
import CnBuildiqEditButton from '../CnBuildiqEditButton/CnBuildiqEditButton.vue'
|
|
138
164
|
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
139
165
|
import { useLook } from '../../composables/useLook.js'
|
|
166
|
+
import { resolveRegisteredHandler } from '../../utils/actionsDispatcher.js'
|
|
140
167
|
import { followLinkClick } from '../../utils/linkNavigation.js'
|
|
141
168
|
|
|
142
169
|
export default {
|
|
@@ -146,10 +173,18 @@ export default {
|
|
|
146
173
|
// Loaded only for a page that declares widgets: a cards-only reports
|
|
147
174
|
// page does not pull the dashboard in.
|
|
148
175
|
CnDashboardPage: defineAsyncComponent(() => import('../CnDashboardPage/CnDashboardPage.vue').then((m) => m.default)),
|
|
176
|
+
CnBuildiqEditButton,
|
|
149
177
|
CnIcon,
|
|
178
|
+
NcButton,
|
|
150
179
|
},
|
|
151
180
|
|
|
152
181
|
inject: {
|
|
182
|
+
/**
|
|
183
|
+
* The v2 registry and the legacy customComponents map, provided by
|
|
184
|
+
* CnAppRoot: a header action's named handler resolves against them.
|
|
185
|
+
*/
|
|
186
|
+
cnRegistry: { default: () => ({}) },
|
|
187
|
+
cnCustomComponents: { default: () => ({}) },
|
|
153
188
|
/**
|
|
154
189
|
* The consuming app's translate function, provided by CnAppRoot.
|
|
155
190
|
* Defaults to identity so the page still renders outside one.
|
|
@@ -268,6 +303,38 @@ export default {
|
|
|
268
303
|
default: null,
|
|
269
304
|
},
|
|
270
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Buttons beside the title (`config.headerButtons`), as the board's
|
|
308
|
+
* "Downloaden". Each is `{ label, action, variant?, icon?, id? }`:
|
|
309
|
+
* `action` is the id of a `headerActions` entry, which says what the
|
|
310
|
+
* click does. Under the board look the buildiq square sits between the
|
|
311
|
+
* secondary buttons and the primary one. Empty (the default) changes
|
|
312
|
+
* nothing.
|
|
313
|
+
*
|
|
314
|
+
* @spec openspec/changes/screens-reports-header-buttons/specs/reports-page/spec.md#requirement-a-reports-page-carries-header-buttons
|
|
315
|
+
* @type {Array<{label: string, action: string, variant?: ('primary'|'secondary'), icon?: string, id?: string}>|null}
|
|
316
|
+
*/
|
|
317
|
+
headerButtons: {
|
|
318
|
+
type: Array,
|
|
319
|
+
default: null,
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* The page's header actions (`config.headerActions`), in the shape of
|
|
324
|
+
* an index page's: a header button names one by id. `handler` is a
|
|
325
|
+
* function, `navigate` (with `route`, and `params`), `emit`, `none`,
|
|
326
|
+
* or a registry name; `type: "navigate"` with a `target` URL opens
|
|
327
|
+
* that URL in a new tab. Every click also emits `header-action`
|
|
328
|
+
* `{ action, id }`, except for `handler: "none"`.
|
|
329
|
+
*
|
|
330
|
+
* @spec openspec/changes/screens-reports-header-buttons/specs/reports-page/spec.md#requirement-a-reports-page-carries-header-buttons
|
|
331
|
+
* @type {Array<object>|null}
|
|
332
|
+
*/
|
|
333
|
+
headerActions: {
|
|
334
|
+
type: Array,
|
|
335
|
+
default: null,
|
|
336
|
+
},
|
|
337
|
+
|
|
271
338
|
/**
|
|
272
339
|
* The look to draw in: `board` or `nextcloud`. Absent: the look
|
|
273
340
|
* CnAppRoot provides (`cnLook`), else `nextcloud`.
|
|
@@ -290,6 +357,8 @@ export default {
|
|
|
290
357
|
},
|
|
291
358
|
},
|
|
292
359
|
|
|
360
|
+
emits: ['header-action'],
|
|
361
|
+
|
|
293
362
|
setup(props) {
|
|
294
363
|
const { isBoard } = useLook(props)
|
|
295
364
|
return { isBoard }
|
|
@@ -339,6 +408,8 @@ export default {
|
|
|
339
408
|
'emptyLabel',
|
|
340
409
|
'widgets',
|
|
341
410
|
'layout',
|
|
411
|
+
'headerButtons',
|
|
412
|
+
'headerActions',
|
|
342
413
|
]) {
|
|
343
414
|
if (this[key] !== null && this[key] !== undefined) {
|
|
344
415
|
fromProps[key] = this[key]
|
|
@@ -378,6 +449,51 @@ export default {
|
|
|
378
449
|
})
|
|
379
450
|
},
|
|
380
451
|
|
|
452
|
+
/**
|
|
453
|
+
* The declared header buttons that have a label and an action,
|
|
454
|
+
* translated, each with a stable key and a variant.
|
|
455
|
+
*
|
|
456
|
+
* @spec openspec/changes/screens-reports-header-buttons/specs/reports-page/spec.md#requirement-a-reports-page-carries-header-buttons
|
|
457
|
+
* @return {Array<{key: string, label: string, action: string, variant: string, icon: string}>} The buttons.
|
|
458
|
+
*/
|
|
459
|
+
resolvedHeaderButtons() {
|
|
460
|
+
const declared = Array.isArray(this.config.headerButtons) ? this.config.headerButtons : []
|
|
461
|
+
return declared
|
|
462
|
+
.filter((button) => button && typeof button === 'object'
|
|
463
|
+
&& typeof button.action === 'string' && button.action !== ''
|
|
464
|
+
&& typeof button.label === 'string' && button.label !== '')
|
|
465
|
+
.map((button) => ({
|
|
466
|
+
key: String(button.id || button.action),
|
|
467
|
+
label: this.tr(button.label),
|
|
468
|
+
action: button.action,
|
|
469
|
+
variant: button.variant === 'primary' ? 'primary' : 'secondary',
|
|
470
|
+
icon: typeof button.icon === 'string' ? button.icon : '',
|
|
471
|
+
}))
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* The header row in the order it renders. Without the board look:
|
|
476
|
+
* the declared buttons in the declared order. Under the board look:
|
|
477
|
+
* the secondary buttons, the buildiq square, then the primary ones,
|
|
478
|
+
* as the board draws "Downloaden" and the square. No declared button
|
|
479
|
+
* leaves the header as it was, without the square.
|
|
480
|
+
*
|
|
481
|
+
* @spec openspec/changes/screens-reports-header-buttons/specs/reports-page/spec.md#requirement-a-reports-page-carries-header-buttons
|
|
482
|
+
* @return {Array<object>} The buttons; the square is `{ key: '__buildiq', action: '__buildiq' }`.
|
|
483
|
+
*/
|
|
484
|
+
headerButtonsRow() {
|
|
485
|
+
const buttons = this.resolvedHeaderButtons
|
|
486
|
+
if (buttons.length === 0 || !this.isBoard) {
|
|
487
|
+
return buttons
|
|
488
|
+
}
|
|
489
|
+
const square = { key: '__buildiq', action: '__buildiq', variant: 'secondary', label: '', icon: '' }
|
|
490
|
+
return [
|
|
491
|
+
...buttons.filter((button) => button.variant !== 'primary'),
|
|
492
|
+
square,
|
|
493
|
+
...buttons.filter((button) => button.variant === 'primary'),
|
|
494
|
+
]
|
|
495
|
+
},
|
|
496
|
+
|
|
381
497
|
/**
|
|
382
498
|
* The heading.
|
|
383
499
|
*
|
|
@@ -567,6 +683,47 @@ export default {
|
|
|
567
683
|
}
|
|
568
684
|
},
|
|
569
685
|
|
|
686
|
+
/**
|
|
687
|
+
* A header button was clicked: run the `headerActions` entry it names
|
|
688
|
+
* and emit `header-action`, as an index page's header button does.
|
|
689
|
+
*
|
|
690
|
+
* @spec openspec/changes/screens-reports-header-buttons/specs/reports-page/spec.md#requirement-a-reports-page-carries-header-buttons
|
|
691
|
+
* @param {{action: string}} button The resolved button.
|
|
692
|
+
* @return {void}
|
|
693
|
+
*/
|
|
694
|
+
onHeaderButton(button) {
|
|
695
|
+
const id = button.action
|
|
696
|
+
const actions = Array.isArray(this.config.headerActions) ? this.config.headerActions : []
|
|
697
|
+
const entry = actions.find((action) => action && action.id === id) || null
|
|
698
|
+
const handler = entry ? entry.handler : undefined
|
|
699
|
+
if (handler === 'none') {
|
|
700
|
+
return
|
|
701
|
+
}
|
|
702
|
+
if (typeof handler === 'function') {
|
|
703
|
+
handler({ actionId: id })
|
|
704
|
+
} else if (handler === 'navigate' && entry.route) {
|
|
705
|
+
const params = (entry.params && typeof entry.params === 'object') ? entry.params : null
|
|
706
|
+
if (this.$router && typeof this.$router.push === 'function') {
|
|
707
|
+
this.$router.push(params ? { name: entry.route, params } : { name: entry.route })
|
|
708
|
+
}
|
|
709
|
+
} else if (entry && entry.type === 'navigate' && typeof entry.target === 'string' && entry.target !== '') {
|
|
710
|
+
window.open(entry.target, '_blank', 'noopener,noreferrer')
|
|
711
|
+
} else if (typeof handler === 'string' && handler !== 'emit') {
|
|
712
|
+
const resolved = resolveRegisteredHandler(handler, this.cnRegistry, this.cnCustomComponents)
|
|
713
|
+
if (typeof resolved === 'function') {
|
|
714
|
+
resolved({ actionId: id })
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* A header button was clicked (`headerButtons`), after its
|
|
719
|
+
* `headerActions` entry ran; not for `handler: "none"`.
|
|
720
|
+
*
|
|
721
|
+
* @event header-action
|
|
722
|
+
* @property {{action: string, id: string}} payload The id of the `headerActions` entry the button names, as `action` and `id`.
|
|
723
|
+
*/
|
|
724
|
+
this.$emit('header-action', { action: id, id })
|
|
725
|
+
},
|
|
726
|
+
|
|
570
727
|
/**
|
|
571
728
|
* Navigate by route NAME on a plain click; a modified or middle click
|
|
572
729
|
* is left to the browser so it can open the card's href elsewhere.
|
|
@@ -598,6 +755,27 @@ export default {
|
|
|
598
755
|
margin: 0 0 4px;
|
|
599
756
|
}
|
|
600
757
|
|
|
758
|
+
.cn-reports-page__heading-row {
|
|
759
|
+
display: flex;
|
|
760
|
+
flex-wrap: wrap;
|
|
761
|
+
align-items: flex-start;
|
|
762
|
+
justify-content: space-between;
|
|
763
|
+
gap: 12px 24px;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
.cn-reports-page__heading-row .cn-reports-page__title {
|
|
767
|
+
flex: 1 1 320px;
|
|
768
|
+
min-width: 0;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
.cn-reports-page__header-buttons {
|
|
772
|
+
display: flex;
|
|
773
|
+
flex: none;
|
|
774
|
+
flex-wrap: wrap;
|
|
775
|
+
align-items: center;
|
|
776
|
+
gap: 10px;
|
|
777
|
+
}
|
|
778
|
+
|
|
601
779
|
.cn-reports-page__description {
|
|
602
780
|
margin: 0 0 16px;
|
|
603
781
|
color: var(--color-text-maxcontrast);
|
|
@@ -59,8 +59,15 @@
|
|
|
59
59
|
class="cn-stages-widget__bar-label"
|
|
60
60
|
:title="stage.hint || stage.label"
|
|
61
61
|
:aria-disabled="stage.disabled && stage.clickable ? 'true' : null"
|
|
62
|
+
:aria-busy="movingTo === stage.id ? 'true' : null"
|
|
62
63
|
:data-testid="`cn-stages-widget-stage-${stage.id}`"
|
|
63
64
|
@click="onBarClick(stage)">
|
|
65
|
+
<NcLoadingIcon
|
|
66
|
+
v-if="movingTo === stage.id"
|
|
67
|
+
class="cn-stages-widget__moving"
|
|
68
|
+
:size="16"
|
|
69
|
+
:name="movingMessage"
|
|
70
|
+
data-testid="cn-stages-widget-moving" />
|
|
64
71
|
{{ stage.label }}<span v-if="stage.state === 'current'" class="cn-stages-widget__sr-only"> ({{ tr('current step') }})</span>
|
|
65
72
|
</component>
|
|
66
73
|
<span v-if="stage.date" class="cn-stages-widget__bar-date">{{ stage.date }}</span>
|
|
@@ -83,7 +90,19 @@
|
|
|
83
90
|
@stageClick="onStageClick"
|
|
84
91
|
@stageBlocked="onStageBlocked">
|
|
85
92
|
<template #label="{ stage }">
|
|
86
|
-
<span
|
|
93
|
+
<span
|
|
94
|
+
class="cn-timeline-stages__label"
|
|
95
|
+
:aria-busy="movingTo === stage.id ? 'true' : null"
|
|
96
|
+
:data-testid="`cn-stages-widget-stage-${stage.id}`">
|
|
97
|
+
<!-- The move in flight. A transition can take seconds, and
|
|
98
|
+
a strip that shows nothing until it returns reads as
|
|
99
|
+
one that never moved. -->
|
|
100
|
+
<NcLoadingIcon
|
|
101
|
+
v-if="movingTo === stage.id"
|
|
102
|
+
class="cn-stages-widget__moving"
|
|
103
|
+
:size="16"
|
|
104
|
+
:name="movingMessage"
|
|
105
|
+
data-testid="cn-stages-widget-moving" />
|
|
87
106
|
{{ stage.label }}
|
|
88
107
|
</span>
|
|
89
108
|
<span v-if="stage.subtitle" class="cn-timeline-stages__subtitle">
|
|
@@ -156,8 +175,11 @@
|
|
|
156
175
|
v-if="pendingAction"
|
|
157
176
|
:transition="pendingAction"
|
|
158
177
|
:schema="schema"
|
|
178
|
+
:error="inputError"
|
|
179
|
+
:fieldErrors="inputFieldErrors"
|
|
180
|
+
:busy="busy"
|
|
159
181
|
@confirm="onInputConfirm"
|
|
160
|
-
@close="
|
|
182
|
+
@close="closeInputDialog" />
|
|
161
183
|
</div>
|
|
162
184
|
</template>
|
|
163
185
|
|
|
@@ -177,6 +199,7 @@ import {
|
|
|
177
199
|
performTransition,
|
|
178
200
|
readAvailableActions,
|
|
179
201
|
transitionError,
|
|
202
|
+
transitionFieldErrors,
|
|
180
203
|
} from '../../composables/useLifecycleTransitions.js'
|
|
181
204
|
import { normalizeLook } from '../../composables/useLook.js'
|
|
182
205
|
import { useObjectStore } from '../../store/useObjectStore.js'
|
|
@@ -534,6 +557,17 @@ export default {
|
|
|
534
557
|
pendingAction: null,
|
|
535
558
|
/** @type {boolean} Whether a move is running. */
|
|
536
559
|
busy: false,
|
|
560
|
+
/**
|
|
561
|
+
* @type {string} The stage a running move leads to, or ''. Drives
|
|
562
|
+
* the loading indicator on that one stage.
|
|
563
|
+
*/
|
|
564
|
+
movingTo: '',
|
|
565
|
+
/** @type {string} A refusal shown inside the open input dialog. */
|
|
566
|
+
inputError: '',
|
|
567
|
+
/** @type {string[]} The input keys that refusal named. */
|
|
568
|
+
inputFieldErrors: [],
|
|
569
|
+
/** @type {string[]} The input keys the last refused move named. */
|
|
570
|
+
lastFieldErrors: [],
|
|
537
571
|
/** @type {string} Why the last move failed, or ''. */
|
|
538
572
|
moveError: '',
|
|
539
573
|
/**
|
|
@@ -766,6 +800,20 @@ export default {
|
|
|
766
800
|
return this.interactive && !this.busy
|
|
767
801
|
},
|
|
768
802
|
|
|
803
|
+
/**
|
|
804
|
+
* What the stage a move is running to says while it runs: the
|
|
805
|
+
* loading indicator's name and the live region's sentence.
|
|
806
|
+
*
|
|
807
|
+
* @return {string} The sentence, or '' when nothing is moving.
|
|
808
|
+
*/
|
|
809
|
+
movingMessage() {
|
|
810
|
+
if (!this.movingTo) {
|
|
811
|
+
return ''
|
|
812
|
+
}
|
|
813
|
+
const stage = this.stages.find((s) => s.id === this.movingTo)
|
|
814
|
+
return this.tr('Moving to {stage}', { stage: stage ? stage.label : this.movingTo })
|
|
815
|
+
},
|
|
816
|
+
|
|
769
817
|
/**
|
|
770
818
|
* The allowed moves by the stage they lead to, or null while they are
|
|
771
819
|
* unknown.
|
|
@@ -1225,13 +1273,44 @@ export default {
|
|
|
1225
1273
|
* @param {object} data The collected input values.
|
|
1226
1274
|
* @return {void}
|
|
1227
1275
|
*/
|
|
1228
|
-
onInputConfirm(data) {
|
|
1276
|
+
async onInputConfirm(data) {
|
|
1229
1277
|
const pending = this.pendingAction
|
|
1230
|
-
this.pendingAction = null
|
|
1231
1278
|
if (!pending || !pending.__request) {
|
|
1279
|
+
this.closeInputDialog()
|
|
1280
|
+
return
|
|
1281
|
+
}
|
|
1282
|
+
this.inputError = ''
|
|
1283
|
+
this.inputFieldErrors = []
|
|
1284
|
+
// THE DIALOG STAYS OPEN WHILE THE MOVE RUNS, with its confirm
|
|
1285
|
+
// button busy. Closing it first left the person looking at an
|
|
1286
|
+
// unchanged strip for as long as the transition took, which on a
|
|
1287
|
+
// live case was long enough to reload the page thinking it had
|
|
1288
|
+
// not been sent.
|
|
1289
|
+
const moved = await this.performMove(pending.__request, data)
|
|
1290
|
+
if (this.pendingAction !== pending) {
|
|
1291
|
+
// Closed while the move ran; nothing left to update.
|
|
1232
1292
|
return
|
|
1233
1293
|
}
|
|
1234
|
-
|
|
1294
|
+
if (moved) {
|
|
1295
|
+
this.closeInputDialog()
|
|
1296
|
+
return
|
|
1297
|
+
}
|
|
1298
|
+
// Refused. The sentence goes into the dialog the person is looking
|
|
1299
|
+
// at, with what they typed still in it.
|
|
1300
|
+
this.inputError = this.moveError
|
|
1301
|
+
this.inputFieldErrors = this.lastFieldErrors
|
|
1302
|
+
this.moveError = ''
|
|
1303
|
+
},
|
|
1304
|
+
|
|
1305
|
+
/**
|
|
1306
|
+
* Close the input dialog and forget the refusal it was showing.
|
|
1307
|
+
*
|
|
1308
|
+
* @return {void}
|
|
1309
|
+
*/
|
|
1310
|
+
closeInputDialog() {
|
|
1311
|
+
this.pendingAction = null
|
|
1312
|
+
this.inputError = ''
|
|
1313
|
+
this.inputFieldErrors = []
|
|
1235
1314
|
},
|
|
1236
1315
|
|
|
1237
1316
|
/**
|
|
@@ -1239,11 +1318,17 @@ export default {
|
|
|
1239
1318
|
*
|
|
1240
1319
|
* @param {{stage: {id: string, label: string}, action: string}} request The move.
|
|
1241
1320
|
* @param {object} [data] The collected transition inputs, when any.
|
|
1242
|
-
* @return {Promise<
|
|
1321
|
+
* @return {Promise<boolean>} Whether the move was made.
|
|
1243
1322
|
*/
|
|
1244
1323
|
async performMove(request, data) {
|
|
1245
1324
|
this.busy = true
|
|
1325
|
+
this.movingTo = request.stage.id
|
|
1246
1326
|
this.moveError = ''
|
|
1327
|
+
this.lastFieldErrors = []
|
|
1328
|
+
// Said as the move starts, not only when it lands: a screen reader
|
|
1329
|
+
// user otherwise hears nothing for as long as the server takes.
|
|
1330
|
+
this.statusMessage = this.movingMessage
|
|
1331
|
+
let moved = false
|
|
1247
1332
|
try {
|
|
1248
1333
|
if (this.lifecycleMode) {
|
|
1249
1334
|
await performTransition(this.recordId, request.action, data)
|
|
@@ -1270,11 +1355,16 @@ export default {
|
|
|
1270
1355
|
if (this.lifecycleMode) {
|
|
1271
1356
|
await this.loadActions()
|
|
1272
1357
|
}
|
|
1358
|
+
moved = true
|
|
1273
1359
|
} catch (error) {
|
|
1274
1360
|
this.moveError = transitionError(error, this.tr('The move could not be made'))
|
|
1361
|
+
this.lastFieldErrors = transitionFieldErrors(error)
|
|
1362
|
+
this.statusMessage = this.moveError
|
|
1275
1363
|
} finally {
|
|
1276
1364
|
this.busy = false
|
|
1365
|
+
this.movingTo = ''
|
|
1277
1366
|
}
|
|
1367
|
+
return moved
|
|
1278
1368
|
},
|
|
1279
1369
|
|
|
1280
1370
|
/**
|
|
@@ -1501,6 +1591,13 @@ export default {
|
|
|
1501
1591
|
padding: 8px 0;
|
|
1502
1592
|
}
|
|
1503
1593
|
|
|
1594
|
+
/* The stage a running move leads to: a small spinner before its label. */
|
|
1595
|
+
.cn-stages-widget__moving {
|
|
1596
|
+
display: inline-flex;
|
|
1597
|
+
vertical-align: middle;
|
|
1598
|
+
margin-inline-end: 4px;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1504
1601
|
.cn-stages-widget__notice {
|
|
1505
1602
|
margin: 0;
|
|
1506
1603
|
color: var(--color-text-maxcontrast);
|
|
@@ -1460,7 +1460,7 @@ export default {
|
|
|
1460
1460
|
* @return {string}
|
|
1461
1461
|
*/
|
|
1462
1462
|
formattedValue() {
|
|
1463
|
-
return formatMetricValue(this.displayValue, this.content.format, this.configCtx, this.objectCtx?.object || null)
|
|
1463
|
+
return formatMetricValue(this.displayValue, this.content.format, this.configCtx, this.objectCtx?.object || null, this.effectiveTranslate)
|
|
1464
1464
|
},
|
|
1465
1465
|
|
|
1466
1466
|
/**
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<template>
|
|
18
18
|
<div
|
|
19
19
|
class="cn-stats-block-widget"
|
|
20
|
-
:class="[iconClass, { 'cn-stats-block-widget--multi': hasEntries && !isRows && !isGrid, 'cn-stats-block-widget--rows': isRows, 'cn-stats-block-widget--grid': isGrid }]">
|
|
20
|
+
:class="[iconClass, { 'cn-stats-block-widget--multi': hasEntries && !isRows && !isGrid, 'cn-stats-block-widget--rows': isRows, 'cn-stats-block-widget--grid': isGrid, 'cn-stats-block-widget--tiles-box': isGrid && tiles === 'box' }]">
|
|
21
21
|
<!-- Grid layout: the `stats` (each a stat tile's content, so every
|
|
22
22
|
source kind a stat takes: ratio, elapsed, computed...) in `columns`
|
|
23
23
|
columns, inside this one card (the board's "Je week tot nu toe"
|
|
@@ -288,6 +288,21 @@ export default {
|
|
|
288
288
|
default: 2,
|
|
289
289
|
},
|
|
290
290
|
|
|
291
|
+
/**
|
|
292
|
+
* How the grid layout draws its tiles: `'box'` gives each tile a
|
|
293
|
+
* surface of its own (a grey ground, 10px radius, 14px 16px padding),
|
|
294
|
+
* as board DcRapportages draws its efficiency figures. Empty (the
|
|
295
|
+
* default) keeps the tiles as parts of the one card, without a box
|
|
296
|
+
* (board PqMijnWerk "Je week tot nu toe").
|
|
297
|
+
*
|
|
298
|
+
* @type {'box'|null}
|
|
299
|
+
* @spec openspec/changes/screens-stats-block-tile-box/specs/stats-block-grid/spec.md#requirement-a-stats-block-grid-can-box-its-tiles
|
|
300
|
+
*/
|
|
301
|
+
tiles: {
|
|
302
|
+
type: String,
|
|
303
|
+
default: null,
|
|
304
|
+
},
|
|
305
|
+
|
|
291
306
|
/**
|
|
292
307
|
* How the single-source value is shown: a style name (`'currency'`,
|
|
293
308
|
* `'number'`, `'percent'`, `'decimal'`, `'duration-hours'`) or
|
|
@@ -566,7 +581,7 @@ export default {
|
|
|
566
581
|
if (!fmt || this.loading) {
|
|
567
582
|
return null
|
|
568
583
|
}
|
|
569
|
-
return formatMetricValue(this.resolvedCount, fmt, this.configCtx, this.pageObject)
|
|
584
|
+
return formatMetricValue(this.resolvedCount, fmt, this.configCtx, this.pageObject, this.tl)
|
|
570
585
|
},
|
|
571
586
|
|
|
572
587
|
/**
|
|
@@ -598,7 +613,7 @@ export default {
|
|
|
598
613
|
return {
|
|
599
614
|
// The value as the entry's `format` says, money in the
|
|
600
615
|
// currency the entry names; null keeps the plain count.
|
|
601
|
-
formatted: fmt && resolved ? formatMetricValue(raw, fmt, this.configCtx, this.pageObject) : null,
|
|
616
|
+
formatted: fmt && resolved ? formatMetricValue(raw, fmt, this.configCtx, this.pageObject, this.tl) : null,
|
|
602
617
|
entry: entry || {},
|
|
603
618
|
key: `${(entry && entry.register) || ''}/${(entry && entry.schema) || ''}/${i}`,
|
|
604
619
|
count: resolved ? raw : 0,
|
|
@@ -961,7 +976,10 @@ export default {
|
|
|
961
976
|
color: var(--color-text-maxcontrast);
|
|
962
977
|
}
|
|
963
978
|
|
|
979
|
+
/* `width: 100%` plus this padding overflowed the card by 48px: the last
|
|
980
|
+
column ran past the card's right edge (visible once the tiles have a box). */
|
|
964
981
|
.cn-stats-block-widget--grid {
|
|
982
|
+
box-sizing: border-box;
|
|
965
983
|
padding: 4px 24px 20px;
|
|
966
984
|
}
|
|
967
985
|
|
|
@@ -977,6 +995,19 @@ export default {
|
|
|
977
995
|
min-width: 0;
|
|
978
996
|
}
|
|
979
997
|
|
|
998
|
+
/* `tiles: "box"`: each tile on a grey ground of its own (board DcRapportages,
|
|
999
|
+
"Vergaderingen en efficiëntie"), 12px apart. */
|
|
1000
|
+
.cn-stats-block-widget--tiles-box .cn-stats-block-widget__grid {
|
|
1001
|
+
gap: 12px;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
.cn-stats-block-widget--tiles-box .cn-stats-block-widget__grid-stat {
|
|
1005
|
+
box-sizing: border-box;
|
|
1006
|
+
padding: 14px 16px;
|
|
1007
|
+
border-radius: 10px;
|
|
1008
|
+
background: var(--color-background-hover);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
980
1011
|
.cn-stats-block-widget__rows {
|
|
981
1012
|
display: flex;
|
|
982
1013
|
flex-direction: column;
|
|
@@ -311,6 +311,12 @@ export default {
|
|
|
311
311
|
cnLook: { default: 'nextcloud' },
|
|
312
312
|
/** The tab list's name under the board look, provided by CnDetailPage (`config.tabsLabel`). */
|
|
313
313
|
cnDetailTabsLabel: { default: null },
|
|
314
|
+
/**
|
|
315
|
+
* The records the detail page's `visibilityContext` names, keyed
|
|
316
|
+
* by name (a case's case type), provided by CnDetailPage. A tab's
|
|
317
|
+
* `visibleWhen` reads them as `@context.<name>`. Null on any other page.
|
|
318
|
+
*/
|
|
319
|
+
cnDetailVisibilityRows: { default: null },
|
|
314
320
|
/**
|
|
315
321
|
* The host's translate (CnAppRoot provides `cnTranslate`), so a tab
|
|
316
322
|
* label written in the manifest reads in the user's language.
|
|
@@ -475,6 +481,29 @@ export default {
|
|
|
475
481
|
},
|
|
476
482
|
|
|
477
483
|
computed: {
|
|
484
|
+
/**
|
|
485
|
+
* What a tab's `visibleWhen` reads: the record, with the records the
|
|
486
|
+
* detail page's `visibilityContext` names under `@context.<name>`. Null while
|
|
487
|
+
* the record, or one of those records, is not there yet.
|
|
488
|
+
*
|
|
489
|
+
* @return {object|null}
|
|
490
|
+
* @spec openspec/changes/detail-widgets-visible-when/specs/detail-widgets-visible-when/spec.md#requirement-a-detail-page-can-load-records-a-condition-names
|
|
491
|
+
*/
|
|
492
|
+
conditionData() {
|
|
493
|
+
if (!this.objectData) {
|
|
494
|
+
return null
|
|
495
|
+
}
|
|
496
|
+
const holder = this.cnDetailVisibilityRows
|
|
497
|
+
if (!holder) {
|
|
498
|
+
return this.objectData
|
|
499
|
+
}
|
|
500
|
+
const rows = holder.value
|
|
501
|
+
if (!rows) {
|
|
502
|
+
return null
|
|
503
|
+
}
|
|
504
|
+
return { ...this.objectData, '@context': rows }
|
|
505
|
+
},
|
|
506
|
+
|
|
478
507
|
/**
|
|
479
508
|
* The open panel's own menu items, or an empty list.
|
|
480
509
|
*
|
|
@@ -859,10 +888,11 @@ export default {
|
|
|
859
888
|
return 'visible'
|
|
860
889
|
}
|
|
861
890
|
if (isLocallyDecidableVisibleWhen(cond)) {
|
|
862
|
-
|
|
891
|
+
const data = this.conditionData
|
|
892
|
+
if (!data) {
|
|
863
893
|
return 'pending'
|
|
864
894
|
}
|
|
865
|
-
return evaluateVisibleWhenLocal(cond,
|
|
895
|
+
return evaluateVisibleWhenLocal(cond, data) ? 'visible' : 'hidden'
|
|
866
896
|
}
|
|
867
897
|
const answer = this.sourceVisibility[index]
|
|
868
898
|
if (answer === undefined) {
|
|
@@ -883,7 +913,7 @@ export default {
|
|
|
883
913
|
async refreshSourceVisibility() {
|
|
884
914
|
const tabs = Array.isArray(this.content?.tabs) ? this.content.tabs : []
|
|
885
915
|
const seq = ++this.visibilitySeq
|
|
886
|
-
const ctx = { objectId: this.objectId, object: this.objectData || undefined }
|
|
916
|
+
const ctx = { objectId: this.objectId, object: this.conditionData || this.objectData || undefined }
|
|
887
917
|
await Promise.all(tabs.map(async (tab, index) => {
|
|
888
918
|
const cond = tab && typeof tab === 'object' ? tab.visibleWhen : null
|
|
889
919
|
if (!cond || isLocallyDecidableVisibleWhen(cond)) {
|
|
@@ -683,11 +683,27 @@ export default {
|
|
|
683
683
|
* deferred tour) go through here — so a centered step cannot be
|
|
684
684
|
* swallowed by either.
|
|
685
685
|
*
|
|
686
|
+
* A tour that declares `startOn` opens only on one of those route
|
|
687
|
+
* names, whatever its first step looks like.
|
|
688
|
+
*
|
|
686
689
|
* @param {object} tour The tour definition.
|
|
687
690
|
* @param {string} [routeName] The current route name.
|
|
688
691
|
* @return {boolean} True when the tour may open on this route.
|
|
692
|
+
* @spec openspec/changes/a-tour-starts-on-the-pages-it-names/specs/walkthrough/spec.md
|
|
689
693
|
*/
|
|
690
694
|
routeMatchesTour(tour, routeName) {
|
|
695
|
+
// A tour that names where it starts (`startOn`) opens only there.
|
|
696
|
+
// It wins over the centered-step rule below: a welcome card floats
|
|
697
|
+
// in the middle of the screen, but a user who opens a case from a
|
|
698
|
+
// mail link lands on that case, and the card then blocks the page
|
|
699
|
+
// they came for. Measured 13 October on dossiq: a staff user's first
|
|
700
|
+
// visit was a deep link to a Woo request, and "Welcome to Dossiq"
|
|
701
|
+
// opened over it. The tour waits in `_pendingAutoTour` and opens when
|
|
702
|
+
// the user reaches a named route.
|
|
703
|
+
const startOn = Array.isArray(tour && tour.startOn) ? tour.startOn.map(String) : []
|
|
704
|
+
if (startOn.length > 0) {
|
|
705
|
+
return !!routeName && startOn.includes(String(routeName))
|
|
706
|
+
}
|
|
691
707
|
const page = this.firstStepPage(tour)
|
|
692
708
|
if (!page) {
|
|
693
709
|
return true
|
package/src/css/form-field.css
CHANGED
|
@@ -183,3 +183,17 @@
|
|
|
183
183
|
.cn-look-board .cn-form-page__footer .cn-form-page__primary {
|
|
184
184
|
margin-inline-start: auto;
|
|
185
185
|
}
|
|
186
|
+
|
|
187
|
+
/* A Nextcloud text area or text field with its label inside the field hides
|
|
188
|
+
its placeholder until the field takes focus, so the label and the
|
|
189
|
+
placeholder are never drawn over each other. A theme that forces every
|
|
190
|
+
placeholder visible (thematiq's nldesign system sets
|
|
191
|
+
`::placeholder { opacity: 1 !important }`) drew both: the dossiq DqTaak
|
|
192
|
+
notes field read "Een notitie toevoegen" over "Wat heb je gevonden, wie heb
|
|
193
|
+
je gebeld?". Under the board look the placeholder stays hidden at rest, as
|
|
194
|
+
Nextcloud intends; a field with its label outside keeps its placeholder.
|
|
195
|
+
@spec openspec/changes/board-field-label-over-placeholder/specs/form-field-board-look/spec.md#requirement-a-field-label-and-its-placeholder-are-never-drawn-over-each-other */
|
|
196
|
+
.cn-look-board .textarea__input:not(:focus, .textarea__input--label-outside)::placeholder,
|
|
197
|
+
.cn-look-board .input-field:not(.input-field--label-outside) .input-field__input:not(:focus)::placeholder {
|
|
198
|
+
opacity: 0 !important;
|
|
199
|
+
}
|
package/src/css/form-widget.css
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
.cn-form-widget__textarea {
|
|
40
|
+
box-sizing: border-box;
|
|
40
41
|
width: 100%;
|
|
41
42
|
min-height: 80px;
|
|
42
43
|
padding: 8px;
|
|
@@ -58,3 +59,13 @@
|
|
|
58
59
|
color: var(--color-error);
|
|
59
60
|
margin: 0;
|
|
60
61
|
}
|
|
62
|
+
|
|
63
|
+
/* A select takes its field's width, however narrow the card. NcSelect is at
|
|
64
|
+
least 260px wide on its own, so in pipelinq's 234px "Nieuw contactmoment"
|
|
65
|
+
card at 1100 wide the channel and client selects ran 40px past the card's
|
|
66
|
+
edge. The client picker (CnResourceSelect) is an NcSelect too.
|
|
67
|
+
@spec openspec/changes/interaction-form-fits-and-reads-dutch/specs/interaction-form-widget/spec.md#requirement-the-interaction-form-fits-its-card-and-reads-in-the-readers-language */
|
|
68
|
+
.cn-form-widget .v-select.select {
|
|
69
|
+
min-width: 0;
|
|
70
|
+
width: 100%;
|
|
71
|
+
}
|