@conduction/nextcloud-vue 2.100.0 → 2.102.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/CnActionsBar/CnActionsBar.vue2.js +22 -0
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +8 -6
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +29 -4
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +9 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +22 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +26 -16
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +70 -6
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +105 -74
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js +9 -0
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js +139 -0
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js +76 -0
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +160 -2
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +206 -94
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesReview.js +61 -0
- package/dist/esm/components/CnFilesBrowser/filesReview.js.map +1 -0
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js +9 -0
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js +279 -0
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js +122 -0
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnGroupedCardsWidget/index.js +29 -0
- package/dist/esm/components/CnGroupedCardsWidget/index.js.map +1 -0
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +320 -74
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +323 -213
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js +30 -3
- package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +15 -0
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue.js +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js +25 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +14 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +14 -6
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +10 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +6 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +91 -4
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +63 -38
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue.js +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +106 -4
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +70 -25
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js +9 -0
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js +175 -0
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js +77 -0
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +35 -2
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +4 -0
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +2 -23
- 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/composables/indexSources.js +176 -20
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/composables/useEndpointSource.js +28 -6
- package/dist/esm/composables/useEndpointSource.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 +41 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +39 -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 +939 -376
- package/dist/esm/utils/builtInFormatters.js +24 -4
- package/dist/esm/utils/builtInFormatters.js.map +1 -1
- package/dist/esm/utils/languageMapText.js +50 -0
- package/dist/esm/utils/languageMapText.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +3 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/mapList.js +71 -0
- package/dist/esm/utils/mapList.js.map +1 -0
- package/dist/esm/utils/rowClassRules.js +43 -0
- package/dist/esm/utils/rowClassRules.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/visibleWhen.js +19 -2
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +9681 -7136
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +939 -376
- package/l10n/en.json +41 -1
- package/l10n/nl.json +39 -1
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.md +1 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +31 -4
- package/src/components/CnChartWidget/CnChartWidget.md +3 -2
- package/src/components/CnChartWidget/CnChartWidget.vue +42 -4
- package/src/components/CnChoiceCards/CnChoiceCards.md +39 -0
- package/src/components/CnChoiceCards/CnChoiceCards.vue +93 -16
- package/src/components/CnDashboardPage/CnDashboardPage.vue +110 -21
- package/src/components/CnExportsWidget/CnExportsWidget.vue +233 -0
- package/src/components/CnExportsWidget/index.js +7 -0
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +324 -0
- package/src/components/CnFilesBrowser/filesReview.js +57 -0
- package/src/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue +433 -0
- package/src/components/CnGroupedCardsWidget/index.js +28 -0
- package/src/components/CnIndexPage/CnIndexPage.md +5 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +313 -9
- package/src/components/CnIndexPage/useNamedSource.js +30 -3
- package/src/components/CnMapWidget/CnMapWidget.vue +37 -0
- package/src/components/CnMenuWidget/CnMenuWidget.vue +25 -1
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +29 -1
- package/src/components/CnObjectSidebar/CnFilesTab.vue +15 -0
- package/src/components/CnOrderedListField/CnOrderedListField.vue +151 -5
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +1 -0
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +159 -5
- package/src/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue +272 -0
- package/src/components/CnSavedViewAlertWidget/index.js +7 -0
- package/src/components/CnSetupWizard/CnSetupWizard.vue +35 -2
- package/src/components/CnWidgetGrid/builtInWidgets.js +4 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +2 -23
- package/src/components/index.js +3 -0
- package/src/composables/indexSources.js +175 -19
- package/src/composables/useEndpointSource.js +28 -6
- package/src/css/index-page.css +117 -0
- package/src/css/look-board-index.css +10 -0
- package/src/css/look-board.css +44 -0
- package/src/index.js +1 -0
- package/src/schemas/app-manifest-v2.schema.json +85 -3
- package/src/utils/builtInFormatters.js +24 -4
- package/src/utils/languageMapText.js +46 -0
- package/src/utils/libraryWidgetKeys.js +3 -0
- package/src/utils/mapList.js +67 -0
- package/src/utils/rowClassRules.js +39 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/visibleWhen.js +19 -2
|
@@ -239,6 +239,11 @@ export default {
|
|
|
239
239
|
CnMenuItemIcon,
|
|
240
240
|
},
|
|
241
241
|
|
|
242
|
+
inject: {
|
|
243
|
+
/** Host translate function provided by CnAppRoot, for the item labels. */
|
|
244
|
+
cnTranslate: { default: () => (key) => key },
|
|
245
|
+
},
|
|
246
|
+
|
|
242
247
|
props: {
|
|
243
248
|
/**
|
|
244
249
|
* Persisted widget content `{items, style, orientation, showIcons,
|
|
@@ -288,7 +293,26 @@ export default {
|
|
|
288
293
|
*/
|
|
289
294
|
items() {
|
|
290
295
|
const arr = this.content?.items
|
|
291
|
-
|
|
296
|
+
if (!Array.isArray(arr)) {
|
|
297
|
+
return []
|
|
298
|
+
}
|
|
299
|
+
// Labels are manifest strings: they go through the app's translate,
|
|
300
|
+
// children included, so "Case types" reads "Zaaktypen" (DqCatalogus).
|
|
301
|
+
const translate = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key
|
|
302
|
+
const translated = (list) => list.map((item) => {
|
|
303
|
+
if (!item || typeof item !== 'object') {
|
|
304
|
+
return item
|
|
305
|
+
}
|
|
306
|
+
const out = { ...item }
|
|
307
|
+
if (typeof item.label === 'string' && item.label !== '') {
|
|
308
|
+
out.label = translate(item.label)
|
|
309
|
+
}
|
|
310
|
+
if (Array.isArray(item.children)) {
|
|
311
|
+
out.children = translated(item.children)
|
|
312
|
+
}
|
|
313
|
+
return out
|
|
314
|
+
})
|
|
315
|
+
return translated(arr)
|
|
292
316
|
},
|
|
293
317
|
|
|
294
318
|
/**
|
|
@@ -233,6 +233,15 @@
|
|
|
233
233
|
{{ moreLabel }}
|
|
234
234
|
</p>
|
|
235
235
|
</template>
|
|
236
|
+
<!-- `content.footnote`: a line under the table ("Awb 4:17 en 4:18"),
|
|
237
|
+
through the host translate. Not while the list waits for its
|
|
238
|
+
context or failed to load. -->
|
|
239
|
+
<p
|
|
240
|
+
v-if="resolvedFootnote && !waitingForContext && !error"
|
|
241
|
+
class="cn-object-list-widget__footnote"
|
|
242
|
+
data-testid="cn-object-list-widget-footnote">
|
|
243
|
+
{{ resolvedFootnote }}
|
|
244
|
+
</p>
|
|
236
245
|
<!-- Create affordance (ADR-062): every collection carries its Add at
|
|
237
246
|
the bottom of the widget; the host card's Actions menu calls the
|
|
238
247
|
same openCreate() through the public method. Suppressed while the
|
|
@@ -430,8 +439,10 @@ export default {
|
|
|
430
439
|
* `hideHeader: true` drops the table's column header row (CnDataTable's
|
|
431
440
|
* `hideHeader`), for a short list that reads as rows, not as a table.
|
|
432
441
|
*
|
|
442
|
+
* `footnote` is a line under the table, through the host translate.
|
|
443
|
+
*
|
|
433
444
|
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-small-opt-ins-from-the-decidiq-and-learniq-lanes
|
|
434
|
-
* @type {{register?: string, schema?: string, filter?: object, sort?: {field?: string, dir?: string}, limit?: number, extend?: Array<string>, columns?: Array, rowActions?: Array<object>, dropZone?: object, upload?: boolean, groupBy?: string, groupLabel?: string, groupLabelResolve?: {register: string, schema: string, labelField?: string}, selectable?: boolean, bulkActions?: Array<object>, sortable?: boolean, facet?: {field: string, label?: string}, rowRoute?: string, prompt?: string, emptyText?: string, viewAllRoute?: string, viewAllQuery?: object, hideHeader?: boolean}}
|
|
445
|
+
* @type {{register?: string, schema?: string, filter?: object, sort?: {field?: string, dir?: string}, limit?: number, extend?: Array<string>, columns?: Array, rowActions?: Array<object>, dropZone?: object, upload?: boolean, groupBy?: string, groupLabel?: string, groupLabelResolve?: {register: string, schema: string, labelField?: string}, selectable?: boolean, bulkActions?: Array<object>, sortable?: boolean, facet?: {field: string, label?: string}, rowRoute?: string, prompt?: string, emptyText?: string, footnote?: string, viewAllRoute?: string, viewAllQuery?: object, hideHeader?: boolean}}
|
|
435
446
|
*/
|
|
436
447
|
content: {
|
|
437
448
|
type: Object,
|
|
@@ -918,6 +929,17 @@ export default {
|
|
|
918
929
|
return this.emptyText ? fn(this.emptyText) : this.emptyText
|
|
919
930
|
},
|
|
920
931
|
|
|
932
|
+
/**
|
|
933
|
+
* `content.footnote` run through the host translate function, or ''.
|
|
934
|
+
*
|
|
935
|
+
* @return {string}
|
|
936
|
+
*/
|
|
937
|
+
resolvedFootnote() {
|
|
938
|
+
const note = typeof this.content.footnote === 'string' ? this.content.footnote : ''
|
|
939
|
+
const fn = typeof this.cnTranslate === 'function' ? this.cnTranslate : (k) => k
|
|
940
|
+
return note ? fn(note) : ''
|
|
941
|
+
},
|
|
942
|
+
|
|
921
943
|
/**
|
|
922
944
|
* The rows actually rendered: capped to what fits the host grid cell
|
|
923
945
|
* (ADR-062 — content adapts to the cell, never a nested scrollbar).
|
|
@@ -2027,6 +2049,12 @@ export default {
|
|
|
2027
2049
|
padding: 4px;
|
|
2028
2050
|
}
|
|
2029
2051
|
|
|
2052
|
+
.cn-object-list-widget__footnote {
|
|
2053
|
+
color: var(--color-text-maxcontrast);
|
|
2054
|
+
font-size: 13px;
|
|
2055
|
+
margin: 8px 0 0;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2030
2058
|
/* Footer Add — same pattern as the integration leaves' footer action
|
|
2031
2059
|
("Open in Calendar"): full-width, centered, divider-topped, pinned to
|
|
2032
2060
|
the card bottom. */
|
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
:dropHint="dropHint"
|
|
16
16
|
:rowActions="rowActions"
|
|
17
17
|
:newActions="newActions"
|
|
18
|
+
:headerActions="headerActions"
|
|
19
|
+
:rowButton="rowButton"
|
|
20
|
+
:reviewSummary="reviewSummary"
|
|
21
|
+
:footnote="footnote"
|
|
22
|
+
:nameSecondary="nameSecondary"
|
|
18
23
|
:linkedItems="linkedItems"
|
|
19
24
|
:columns="columns"
|
|
20
25
|
:rowData="rowDataSource"
|
|
@@ -420,6 +425,16 @@ export default {
|
|
|
420
425
|
* @type {string[]}
|
|
421
426
|
*/
|
|
422
427
|
facets: { type: Array, default: () => [] },
|
|
428
|
+
/** Forwarded to CnFilesBrowser: the host's own buttons in the bar. */
|
|
429
|
+
headerActions: { type: Array, default: () => [] },
|
|
430
|
+
/** Forwarded to CnFilesBrowser: one visible button on every file row. */
|
|
431
|
+
rowButton: { type: Object, default: null },
|
|
432
|
+
/** Forwarded to CnFilesBrowser: the review summary above the list. */
|
|
433
|
+
reviewSummary: { type: Object, default: null },
|
|
434
|
+
/** Forwarded to CnFilesBrowser: the host's own line under the list. */
|
|
435
|
+
footnote: { type: String, default: '' },
|
|
436
|
+
/** Forwarded to CnFilesBrowser: the second line under each file's name. */
|
|
437
|
+
nameSecondary: { type: String, default: '' },
|
|
423
438
|
/** A dotted path to the list of records in the `rowDataUrl` answer; empty reads the answer itself. */
|
|
424
439
|
rowDataPath: { type: String, default: '' },
|
|
425
440
|
/** The record field that holds the Nextcloud file id. */
|
|
@@ -17,7 +17,30 @@
|
|
|
17
17
|
v-for="(row, index) in rows"
|
|
18
18
|
:key="row.value"
|
|
19
19
|
class="cn-ordered-list-field__row"
|
|
20
|
-
:
|
|
20
|
+
:class="{
|
|
21
|
+
'cn-ordered-list-field__row--dragging': dragIndex === index,
|
|
22
|
+
'cn-ordered-list-field__row--over': dropIndex === index && dragIndex !== index,
|
|
23
|
+
}"
|
|
24
|
+
:data-testid="`cn-ordered-list-row-${row.value}`"
|
|
25
|
+
@dragover.prevent="onDragOver(index)"
|
|
26
|
+
@drop.prevent="onDrop(index)">
|
|
27
|
+
<!-- A drag handle (DqPersoonlijkeInstellingen). It is a button: a
|
|
28
|
+
keyboard user moves the row with the arrow keys on it, and the
|
|
29
|
+
up and down buttons stay for anyone who neither drags nor uses
|
|
30
|
+
arrow keys. -->
|
|
31
|
+
<button
|
|
32
|
+
v-if="!disabled"
|
|
33
|
+
type="button"
|
|
34
|
+
class="cn-ordered-list-field__handle"
|
|
35
|
+
draggable="true"
|
|
36
|
+
:aria-label="t('nextcloud-vue', 'Reorder {item}: drag, or press the up and down arrow keys', { item: row.label })"
|
|
37
|
+
:data-testid="`cn-ordered-list-handle-${row.value}`"
|
|
38
|
+
@dragstart="onDragStart($event, index)"
|
|
39
|
+
@dragend="onDragEnd"
|
|
40
|
+
@keydown.up.prevent="move(index, index - 1, 'handle')"
|
|
41
|
+
@keydown.down.prevent="move(index, index + 1, 'handle')">
|
|
42
|
+
<DragVertical :size="20" />
|
|
43
|
+
</button>
|
|
21
44
|
<span class="cn-ordered-list-field__position" aria-hidden="true">{{ index + 1 }}</span>
|
|
22
45
|
<span v-if="row.icon" class="cn-ordered-list-field__tile" aria-hidden="true">
|
|
23
46
|
<CnIcon :name="row.icon" :size="18" />
|
|
@@ -104,6 +127,7 @@ import { NcSelect } from '@nextcloud/vue'
|
|
|
104
127
|
import ChevronDown from 'vue-material-design-icons/ChevronDown.vue'
|
|
105
128
|
import ChevronUp from 'vue-material-design-icons/ChevronUp.vue'
|
|
106
129
|
import Close from 'vue-material-design-icons/Close.vue'
|
|
130
|
+
import DragVertical from 'vue-material-design-icons/DragVertical.vue'
|
|
107
131
|
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
108
132
|
|
|
109
133
|
let orderedListUid = 0
|
|
@@ -134,7 +158,7 @@ let orderedListUid = 0
|
|
|
134
158
|
export default {
|
|
135
159
|
name: 'CnOrderedListField',
|
|
136
160
|
|
|
137
|
-
components: { ChevronDown, ChevronUp, Close, CnIcon, NcSelect },
|
|
161
|
+
components: { ChevronDown, ChevronUp, Close, CnIcon, DragVertical, NcSelect },
|
|
138
162
|
|
|
139
163
|
props: {
|
|
140
164
|
/** The values in their order. */
|
|
@@ -205,6 +229,12 @@ export default {
|
|
|
205
229
|
return {
|
|
206
230
|
labelId: `cn-ordered-list-label-${orderedListUid}`,
|
|
207
231
|
announcement: '',
|
|
232
|
+
/** The value whose handle takes focus after the next render, or null. */
|
|
233
|
+
pendingHandleFocus: null,
|
|
234
|
+
/** The index of the row being dragged, or -1. */
|
|
235
|
+
dragIndex: -1,
|
|
236
|
+
/** The index of the row the drag is over, or -1. */
|
|
237
|
+
dropIndex: -1,
|
|
208
238
|
}
|
|
209
239
|
},
|
|
210
240
|
|
|
@@ -244,6 +274,20 @@ export default {
|
|
|
244
274
|
},
|
|
245
275
|
},
|
|
246
276
|
|
|
277
|
+
watch: {
|
|
278
|
+
// The parent's new order arrives as new rows; the moved row's handle
|
|
279
|
+
// lost focus when its row moved in the DOM, so it takes it back.
|
|
280
|
+
rows: {
|
|
281
|
+
flush: 'post',
|
|
282
|
+
handler() {
|
|
283
|
+
if (this.pendingHandleFocus !== null) {
|
|
284
|
+
this.focusPendingHandle()
|
|
285
|
+
this.pendingHandleFocus = null
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
|
|
247
291
|
methods: {
|
|
248
292
|
t,
|
|
249
293
|
|
|
@@ -270,13 +314,15 @@ export default {
|
|
|
270
314
|
},
|
|
271
315
|
|
|
272
316
|
/**
|
|
273
|
-
* Move a row to another place and keep focus on the pressed
|
|
274
|
-
* where it still makes sense
|
|
317
|
+
* Move a row to another place and keep focus on the pressed control
|
|
318
|
+
* where it still makes sense: the row's handle after an arrow key on
|
|
319
|
+
* it, else the up or down button.
|
|
275
320
|
*
|
|
276
321
|
* @param {number} from Its index.
|
|
277
322
|
* @param {number} to The index it moves to.
|
|
323
|
+
* @param {string} [via] `handle` when the move came from the handle.
|
|
278
324
|
*/
|
|
279
|
-
move(from, to) {
|
|
325
|
+
move(from, to, via = '') {
|
|
280
326
|
if (to < 0 || to >= this.rows.length) {
|
|
281
327
|
return
|
|
282
328
|
}
|
|
@@ -286,7 +332,16 @@ export default {
|
|
|
286
332
|
const row = this.rows[from]
|
|
287
333
|
this.emitValue(values)
|
|
288
334
|
this.announcement = t('nextcloud-vue', '{item} moved to position {position} of {total}', { item: row.label, position: to + 1, total: values.length })
|
|
335
|
+
if (via === 'handle') {
|
|
336
|
+
// Re-focused once the parent hands the new order back: moving the
|
|
337
|
+
// row in the DOM takes focus off its handle.
|
|
338
|
+
this.pendingHandleFocus = row.value
|
|
339
|
+
}
|
|
289
340
|
this.$nextTick(() => {
|
|
341
|
+
if (via === 'handle') {
|
|
342
|
+
this.focusPendingHandle()
|
|
343
|
+
return
|
|
344
|
+
}
|
|
290
345
|
const direction = to < from ? 'up' : 'down'
|
|
291
346
|
const atEdge = (direction === 'up' && to === 0) || (direction === 'down' && to === values.length - 1)
|
|
292
347
|
const target = this.$el.querySelector(`[data-testid="cn-ordered-list-${atEdge ? (direction === 'up' ? 'down' : 'up') : direction}-${row.value}"]`)
|
|
@@ -296,6 +351,61 @@ export default {
|
|
|
296
351
|
})
|
|
297
352
|
},
|
|
298
353
|
|
|
354
|
+
/** Put focus back on the handle of the row just moved by its handle. */
|
|
355
|
+
focusPendingHandle() {
|
|
356
|
+
if (this.pendingHandleFocus === null) {
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
const handle = this.$el.querySelector(`[data-testid="cn-ordered-list-handle-${this.pendingHandleFocus}"]`)
|
|
360
|
+
if (handle) {
|
|
361
|
+
handle.focus()
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Start dragging a row by its handle.
|
|
367
|
+
*
|
|
368
|
+
* @param {DragEvent} event The drag event.
|
|
369
|
+
* @param {number} index The row's index.
|
|
370
|
+
*/
|
|
371
|
+
onDragStart(event, index) {
|
|
372
|
+
this.dragIndex = index
|
|
373
|
+
if (event && event.dataTransfer) {
|
|
374
|
+
event.dataTransfer.effectAllowed = 'move'
|
|
375
|
+
event.dataTransfer.setData('text/plain', String(this.rows[index].value))
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Mark the row a drag is over as where the row will land.
|
|
381
|
+
*
|
|
382
|
+
* @param {number} index The row's index.
|
|
383
|
+
*/
|
|
384
|
+
onDragOver(index) {
|
|
385
|
+
if (this.dragIndex !== -1) {
|
|
386
|
+
this.dropIndex = index
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Drop the dragged row in the place of the row it is over.
|
|
392
|
+
*
|
|
393
|
+
* @param {number} index The row's index.
|
|
394
|
+
*/
|
|
395
|
+
onDrop(index) {
|
|
396
|
+
const from = this.dragIndex
|
|
397
|
+
this.onDragEnd()
|
|
398
|
+
if (from !== -1 && from !== index) {
|
|
399
|
+
this.move(from, index, 'handle')
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
|
|
403
|
+
/** End a drag, dropped or not. */
|
|
404
|
+
onDragEnd() {
|
|
405
|
+
this.dragIndex = -1
|
|
406
|
+
this.dropIndex = -1
|
|
407
|
+
},
|
|
408
|
+
|
|
299
409
|
/**
|
|
300
410
|
* Remove a row.
|
|
301
411
|
*
|
|
@@ -371,6 +481,42 @@ export default {
|
|
|
371
481
|
border-top: 1px solid var(--color-border);
|
|
372
482
|
}
|
|
373
483
|
|
|
484
|
+
.cn-ordered-list-field__row--dragging {
|
|
485
|
+
opacity: 0.5;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/* Where a dragged row will land: a primary line on the row's top edge. */
|
|
489
|
+
.cn-ordered-list-field__row--over {
|
|
490
|
+
box-shadow: inset 0 2px 0 var(--color-primary-element);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.cn-ordered-list-field__handle {
|
|
494
|
+
display: inline-flex;
|
|
495
|
+
flex: none;
|
|
496
|
+
align-items: center;
|
|
497
|
+
justify-content: center;
|
|
498
|
+
width: 28px;
|
|
499
|
+
min-width: 28px;
|
|
500
|
+
height: 34px;
|
|
501
|
+
min-height: 34px;
|
|
502
|
+
margin: 0 -6px 0 -8px;
|
|
503
|
+
padding: 0;
|
|
504
|
+
border: 0;
|
|
505
|
+
border-radius: var(--border-radius-element, 8px);
|
|
506
|
+
background: transparent;
|
|
507
|
+
color: var(--color-text-maxcontrast);
|
|
508
|
+
cursor: grab;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.cn-ordered-list-field__handle:active {
|
|
512
|
+
cursor: grabbing;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.cn-ordered-list-field__handle:focus-visible {
|
|
516
|
+
outline: 2px solid var(--color-primary-element);
|
|
517
|
+
outline-offset: 1px;
|
|
518
|
+
}
|
|
519
|
+
|
|
374
520
|
.cn-ordered-list-field__position {
|
|
375
521
|
flex: none;
|
|
376
522
|
min-width: 18px;
|
|
@@ -59,6 +59,7 @@ In `manifest.json` (the usual entry-point — CnIndexPage mounts the bar for you
|
|
|
59
59
|
- `selectLabel` (String, default `'Filter'`) — accessible label / placeholder for the dropdown control.
|
|
60
60
|
- `placeholder` (String) — placeholder text for the dropdown (falls back to `selectLabel`).
|
|
61
61
|
- `maxVisible` (Number, default `0`) — chips mode only: how many pills render inline before the rest move behind one more chip, a `⋯` pill that opens a small panel of the hidden lenses (also as chips). `0` renders every tab. The visible set is the first `maxVisible` entries of `tabs`, so their declared order decides what stays in the strip; an active hidden tab moves its label and fill onto that chip. Manifest: `config.quickFilterMaxVisible`.
|
|
62
|
+
- `fit` (Boolean, default `false`) — chips mode only: show as many chips as the bar's own width holds and move the rest behind the overflow chip, measured again when the width, the tabs or the counts change. The bar takes the room its parent gives it; CnIndexPage's `quickFilterPlacement: "toolbar"` gives it what the Filter button and the view switch leave on their row. With `maxVisible` too, the smaller of the two wins.
|
|
62
63
|
- `counts` (Object or Array, default `null`): how many records each tab matches, keyed by tab index. A tab with a number shows it after its label. CnIndexPage fills this for tabs that set `showCount`.
|
|
63
64
|
|
|
64
65
|
### Events
|
|
@@ -24,8 +24,28 @@
|
|
|
24
24
|
</div>
|
|
25
25
|
<!-- Chips mode (default): the clickable tab strip. -->
|
|
26
26
|
<div v-else
|
|
27
|
+
ref="chipsRoot"
|
|
27
28
|
class="cn-quick-filter-bar"
|
|
28
|
-
:class="{ 'cn-quick-filter-bar--inline': inline }">
|
|
29
|
+
:class="{ 'cn-quick-filter-bar--inline': inline, 'cn-quick-filter-bar--fit': fit }">
|
|
30
|
+
<!-- With `fit`: an invisible copy of every chip and the overflow chip,
|
|
31
|
+
measured to work out how many chips the bar's width holds. -->
|
|
32
|
+
<div v-if="fit"
|
|
33
|
+
ref="measure"
|
|
34
|
+
class="cn-quick-filter-bar__measure"
|
|
35
|
+
aria-hidden="true">
|
|
36
|
+
<span v-for="entry in entries"
|
|
37
|
+
:key="'m:' + entry.tab.label + ':' + entry.index"
|
|
38
|
+
class="cn-quick-filter-bar__tab"
|
|
39
|
+
:class="{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }"
|
|
40
|
+
data-measure="chip">
|
|
41
|
+
<span class="cn-quick-filter-bar__label">{{ tl(entry.tab.label) }}</span>
|
|
42
|
+
<span v-if="countOf(entry) !== null" class="cn-quick-filter-bar__count">{{ ' ' + countOf(entry) }}</span>
|
|
43
|
+
</span>
|
|
44
|
+
<span class="cn-quick-filter-bar__tab cn-quick-filter-bar__more" data-measure="more">
|
|
45
|
+
<span v-if="overflowLabel" class="cn-quick-filter-bar__label">{{ overflowLabel }}</span>
|
|
46
|
+
<DotsHorizontal :size="16" class="cn-quick-filter-bar__icon" />
|
|
47
|
+
</span>
|
|
48
|
+
</div>
|
|
29
49
|
<!-- The tablist holds ONLY tabs: the overflow chip is a sibling, not a
|
|
30
50
|
child, because a role="tablist" may not contain a menu button. -->
|
|
31
51
|
<!-- Board look: the tabs are chips, pressed buttons in a group, not a
|
|
@@ -266,6 +286,19 @@ export default {
|
|
|
266
286
|
type: [Object, Array],
|
|
267
287
|
default: null,
|
|
268
288
|
},
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Chips mode only: show as many chips as the bar's own width holds and
|
|
292
|
+
* move the rest into the overflow chip, measured again whenever the
|
|
293
|
+
* width, the tabs or the counts change. The bar takes the room it is
|
|
294
|
+
* given (CnIndexPage's `quickFilterPlacement: "toolbar"` gives it what
|
|
295
|
+
* the Filter button and the view switch leave on their row). With
|
|
296
|
+
* `maxVisible` too, the smaller of the two wins.
|
|
297
|
+
*/
|
|
298
|
+
fit: {
|
|
299
|
+
type: Boolean,
|
|
300
|
+
default: false,
|
|
301
|
+
},
|
|
269
302
|
},
|
|
270
303
|
|
|
271
304
|
emits: ['update:active-index', 'update:selected-indices'],
|
|
@@ -274,6 +307,8 @@ export default {
|
|
|
274
307
|
return {
|
|
275
308
|
/** Whether the overflow chip's panel is open. */
|
|
276
309
|
moreOpen: false,
|
|
310
|
+
/** With `fit`: how many chips the width holds; null before the first measure. */
|
|
311
|
+
fitCount: null,
|
|
277
312
|
}
|
|
278
313
|
},
|
|
279
314
|
|
|
@@ -303,10 +338,24 @@ export default {
|
|
|
303
338
|
* @return {Array<{tab: object, index: number}>}
|
|
304
339
|
*/
|
|
305
340
|
visibleEntries() {
|
|
306
|
-
|
|
341
|
+
const limit = this.effectiveMax
|
|
342
|
+
if (!limit || this.tabs.length <= limit) {
|
|
307
343
|
return this.entries
|
|
308
344
|
}
|
|
309
|
-
return this.entries.slice(0,
|
|
345
|
+
return this.entries.slice(0, limit)
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* How many chips render: `maxVisible`, and with `fit` no more than the
|
|
350
|
+
* width holds; 0 for no limit.
|
|
351
|
+
*
|
|
352
|
+
* @return {number}
|
|
353
|
+
*/
|
|
354
|
+
effectiveMax() {
|
|
355
|
+
if (!this.fit || this.fitCount === null) {
|
|
356
|
+
return this.maxVisible
|
|
357
|
+
}
|
|
358
|
+
return this.maxVisible ? Math.min(this.maxVisible, this.fitCount) : this.fitCount
|
|
310
359
|
},
|
|
311
360
|
|
|
312
361
|
/**
|
|
@@ -316,10 +365,11 @@ export default {
|
|
|
316
365
|
* @return {Array<{tab: object, index: number}>}
|
|
317
366
|
*/
|
|
318
367
|
overflowEntries() {
|
|
319
|
-
|
|
368
|
+
const limit = this.effectiveMax
|
|
369
|
+
if (!limit || this.tabs.length <= limit) {
|
|
320
370
|
return []
|
|
321
371
|
}
|
|
322
|
-
return this.entries.slice(
|
|
372
|
+
return this.entries.slice(limit)
|
|
323
373
|
},
|
|
324
374
|
|
|
325
375
|
/**
|
|
@@ -399,7 +449,79 @@ export default {
|
|
|
399
449
|
},
|
|
400
450
|
},
|
|
401
451
|
|
|
452
|
+
watch: {
|
|
453
|
+
tabs() {
|
|
454
|
+
this.scheduleFit()
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
counts: {
|
|
458
|
+
deep: true,
|
|
459
|
+
handler() {
|
|
460
|
+
this.scheduleFit()
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
},
|
|
464
|
+
|
|
465
|
+
mounted() {
|
|
466
|
+
if (this.fit && typeof ResizeObserver === 'function' && this.$refs.chipsRoot) {
|
|
467
|
+
this.fitObserver = new ResizeObserver(() => this.measureFit())
|
|
468
|
+
this.fitObserver.observe(this.$refs.chipsRoot)
|
|
469
|
+
}
|
|
470
|
+
this.scheduleFit()
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
beforeUnmount() {
|
|
474
|
+
if (this.fitObserver) {
|
|
475
|
+
this.fitObserver.disconnect()
|
|
476
|
+
this.fitObserver = null
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
|
|
402
480
|
methods: {
|
|
481
|
+
/** Measure the fit after the next render. */
|
|
482
|
+
scheduleFit() {
|
|
483
|
+
if (this.fit) {
|
|
484
|
+
this.$nextTick(() => this.measureFit())
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Work out how many chips the bar's width holds, from the invisible
|
|
490
|
+
* copy: every chip that fits, and when not all do, room left for the
|
|
491
|
+
* overflow chip. At least one chip always shows.
|
|
492
|
+
*/
|
|
493
|
+
measureFit() {
|
|
494
|
+
const box = this.$refs.measure
|
|
495
|
+
const root = this.$refs.chipsRoot
|
|
496
|
+
if (!this.fit || !box || !root) {
|
|
497
|
+
return
|
|
498
|
+
}
|
|
499
|
+
const available = root.clientWidth
|
|
500
|
+
const chips = [...box.querySelectorAll('[data-measure="chip"]')].map((el) => el.offsetWidth)
|
|
501
|
+
const more = box.querySelector('[data-measure="more"]')
|
|
502
|
+
const moreWidth = more ? more.offsetWidth : 0
|
|
503
|
+
const gap = parseFloat(getComputedStyle(box).columnGap) || 8
|
|
504
|
+
if (available <= 0 || chips.length === 0) {
|
|
505
|
+
this.fitCount = null
|
|
506
|
+
return
|
|
507
|
+
}
|
|
508
|
+
const total = chips.reduce((sum, width) => sum + width, 0) + gap * (chips.length - 1)
|
|
509
|
+
if (total <= available) {
|
|
510
|
+
this.fitCount = null
|
|
511
|
+
return
|
|
512
|
+
}
|
|
513
|
+
let used = moreWidth
|
|
514
|
+
let count = 0
|
|
515
|
+
for (const width of chips) {
|
|
516
|
+
if (used + gap + width > available) {
|
|
517
|
+
break
|
|
518
|
+
}
|
|
519
|
+
used += gap + width
|
|
520
|
+
count++
|
|
521
|
+
}
|
|
522
|
+
this.fitCount = Math.max(1, count)
|
|
523
|
+
},
|
|
524
|
+
|
|
403
525
|
/**
|
|
404
526
|
* A manifest string through the host's label lookup (`cnTranslate`), so a key
|
|
405
527
|
* such as "Mine" reads in the user's language.
|
|
@@ -531,6 +653,38 @@ export default {
|
|
|
531
653
|
/* Inline variant — bare strip for embedding inside another bar (e.g. the
|
|
532
654
|
CnActionsBar #filters slot), so it reads as a control beside the view toggle
|
|
533
655
|
rather than a separate bordered row. */
|
|
656
|
+
/* With `fit`: one line, as wide as it is given; the chips it cannot hold
|
|
657
|
+
are in the overflow chip. The measuring copy takes no room and is never
|
|
658
|
+
seen or read. */
|
|
659
|
+
.cn-quick-filter-bar--fit {
|
|
660
|
+
position: relative;
|
|
661
|
+
flex-wrap: nowrap;
|
|
662
|
+
min-width: 0;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.cn-quick-filter-bar--fit .cn-quick-filter-bar__tabs {
|
|
666
|
+
flex-wrap: nowrap;
|
|
667
|
+
min-width: 0;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.cn-quick-filter-bar__measure {
|
|
671
|
+
position: absolute;
|
|
672
|
+
inset-block-start: 0;
|
|
673
|
+
inset-inline-start: 0;
|
|
674
|
+
display: flex;
|
|
675
|
+
gap: 8px;
|
|
676
|
+
width: 0;
|
|
677
|
+
height: 0;
|
|
678
|
+
overflow: hidden;
|
|
679
|
+
visibility: hidden;
|
|
680
|
+
pointer-events: none;
|
|
681
|
+
white-space: nowrap;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.cn-quick-filter-bar__measure > * {
|
|
685
|
+
flex: none;
|
|
686
|
+
}
|
|
687
|
+
|
|
534
688
|
.cn-quick-filter-bar--inline {
|
|
535
689
|
padding: 0;
|
|
536
690
|
border-bottom: none;
|