@conduction/nextcloud-vue 2.89.2 → 2.90.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +13 -0
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +3 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +18 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -2
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +63 -63
- package/dist/esm/utils/declaredProps.js +106 -0
- package/dist/esm/utils/declaredProps.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +150 -16
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +63 -63
- package/package.json +1 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +14 -1
- package/src/components/CnDashboardPage/CnDashboardPage.vue +20 -2
- package/src/utils/declaredProps.js +115 -0
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
:active="tab.id === activeTabId">
|
|
77
77
|
<div class="cn-admin-settings-shell__panel" :data-tab="tab.id">
|
|
78
78
|
<!-- @slot tab-<id> The panel of the tab with that `id` in `tabs`. -->
|
|
79
|
-
<slot :name="`tab-${tab.id}`" />
|
|
79
|
+
<slot v-if="!lazyTabs || tab.id === activeTabId" :name="`tab-${tab.id}`" />
|
|
80
80
|
</div>
|
|
81
81
|
</CnTab>
|
|
82
82
|
</CnTabs>
|
|
@@ -491,6 +491,19 @@ export default {
|
|
|
491
491
|
default: '',
|
|
492
492
|
},
|
|
493
493
|
|
|
494
|
+
/**
|
|
495
|
+
* Mount a tab's `tab-<id>` slot only while that tab is open, and
|
|
496
|
+
* unmount it when another tab opens, so each visit mounts it afresh
|
|
497
|
+
* and the page does not load every tab at once. Off (the default),
|
|
498
|
+
* every panel stays mounted and only the open one shows.
|
|
499
|
+
*
|
|
500
|
+
* @spec openspec/changes/admin-settings-lazy-tabs/specs/settings-board-look/spec.md#requirement-the-admin-shell-can-mount-only-the-open-tab
|
|
501
|
+
*/
|
|
502
|
+
lazyTabs: {
|
|
503
|
+
type: Boolean,
|
|
504
|
+
default: false,
|
|
505
|
+
},
|
|
506
|
+
|
|
494
507
|
/**
|
|
495
508
|
* How the `tabs` strip is drawn, as CnTabs' `variant`: `line`, the
|
|
496
509
|
* folder tabs (the default), or `underline`, plain labels on a rule
|
|
@@ -421,7 +421,7 @@
|
|
|
421
421
|
:is="registryRenderer(item)"
|
|
422
422
|
:widgetId="item.widgetId"
|
|
423
423
|
:content="registryWidgetBindings(item)"
|
|
424
|
-
v-bind="{ ...
|
|
424
|
+
v-bind="{ ...registryWidgetProps(item), ...containerBindings(item) }"
|
|
425
425
|
@header="onWidgetHeader(item, $event)" />
|
|
426
426
|
</CnWidgetWrapper>
|
|
427
427
|
<CnWidgetWrapper
|
|
@@ -882,7 +882,7 @@
|
|
|
882
882
|
:is="registryRenderer(item)"
|
|
883
883
|
:widgetId="item.widgetId"
|
|
884
884
|
:content="registryWidgetBindings(item)"
|
|
885
|
-
v-bind="{ ...
|
|
885
|
+
v-bind="{ ...registryWidgetProps(item), ...containerBindings(item), ...cellChromeBindings(item) }"
|
|
886
886
|
@header="onWidgetHeader(item, $event)" />
|
|
887
887
|
</CnWidgetWrapper>
|
|
888
888
|
</template>
|
|
@@ -984,6 +984,7 @@ import { pageViews } from '../../mixins/pageViews.js'
|
|
|
984
984
|
import { mergeUserLayout, resolveUserLayoutApi } from '../../store/plugins/dashboardLayouts.js'
|
|
985
985
|
import { isAppInstalled } from '../../utils/appInstalled.js'
|
|
986
986
|
import { contentHeaderLink } from '../../utils/contentHeaderLink.js'
|
|
987
|
+
import { pickDeclaredProps } from '../../utils/declaredProps.js'
|
|
987
988
|
import { userLocale } from '../../utils/userLocale.js'
|
|
988
989
|
import { compareVisibleWhen, evaluateWorkspaceCondition, isWorkspaceCondition, readVisibleWhenValue } from '../../utils/visibleWhen.js'
|
|
989
990
|
import { canonicalWidgetType } from '../../utils/widgetTypeAliases.js'
|
|
@@ -3642,6 +3643,23 @@ export default {
|
|
|
3642
3643
|
return outcome ? { ...content, conditionOutcome: outcome } : content
|
|
3643
3644
|
},
|
|
3644
3645
|
|
|
3646
|
+
/**
|
|
3647
|
+
* The content keys a registry widget receives as props: only the ones
|
|
3648
|
+
* its renderer declares. An undeclared key would otherwise fall through
|
|
3649
|
+
* as an attribute onto the renderer's root component and could override
|
|
3650
|
+
* a value the widget computes for it. The whole content still arrives
|
|
3651
|
+
* as `content`. A renderer that forwards its own attributes
|
|
3652
|
+
* (`inheritAttrs: false`) keeps receiving every key.
|
|
3653
|
+
*
|
|
3654
|
+
* @param {object} item Layout item.
|
|
3655
|
+
* @return {object} The bindings the renderer declares.
|
|
3656
|
+
* @spec openspec/changes/dashboard-declared-widget-props/specs/dashboard-page/spec.md#requirement-a-dashboard-widget-receives-only-the-content-keys-it-declares
|
|
3657
|
+
*/
|
|
3658
|
+
registryWidgetProps(item) {
|
|
3659
|
+
const def = this.getWidgetDef(item.widgetId)
|
|
3660
|
+
return pickDeclaredProps(this.registryRenderer(item), this.registryWidgetBindings(item), def?.type || item.widgetId)
|
|
3661
|
+
},
|
|
3662
|
+
|
|
3645
3663
|
/**
|
|
3646
3664
|
* What a container widget (registry `container`, such as `tabs`) needs
|
|
3647
3665
|
* beside its content: the page's widget definitions, so its tabs find
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* `process.env.NODE_ENV` is substituted by every consuming app's bundler at
|
|
8
|
+
* build time; see NcSelectTags.js.
|
|
9
|
+
*/
|
|
10
|
+
/* global process */
|
|
11
|
+
|
|
12
|
+
import { camelize } from 'vue'
|
|
13
|
+
|
|
14
|
+
const warned = new Set()
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Add the prop names one component options object declares, following its
|
|
18
|
+
* `extends` and `mixins`.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} options A component options object.
|
|
21
|
+
* @param {Set<string>} names The set to add camelCase prop names to.
|
|
22
|
+
* @param {number} depth Recursion guard.
|
|
23
|
+
*/
|
|
24
|
+
function collect(options, names, depth) {
|
|
25
|
+
if (!options || typeof options !== 'object' || depth > 8) {
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
const props = options.props
|
|
29
|
+
if (Array.isArray(props)) {
|
|
30
|
+
props.forEach((name) => names.add(camelize(String(name))))
|
|
31
|
+
} else if (props && typeof props === 'object') {
|
|
32
|
+
Object.keys(props).forEach((name) => names.add(camelize(name)))
|
|
33
|
+
}
|
|
34
|
+
collect(options.extends, names, depth + 1)
|
|
35
|
+
if (Array.isArray(options.mixins)) {
|
|
36
|
+
options.mixins.forEach((mixin) => collect(mixin, names, depth + 1))
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The prop names a component declares, or `null` when it takes every
|
|
42
|
+
* attribute itself and so must keep receiving all of them:
|
|
43
|
+
* - `inheritAttrs: false`: the component forwards `$attrs` on purpose;
|
|
44
|
+
* - a functional component without `props`: its attrs are its props;
|
|
45
|
+
* - an async component that has not resolved yet.
|
|
46
|
+
*
|
|
47
|
+
* @spec openspec/changes/dashboard-declared-widget-props/specs/dashboard-page/spec.md#requirement-a-dashboard-widget-receives-only-the-content-keys-it-declares
|
|
48
|
+
* @param {object|((...args: unknown[]) => unknown)|null} component A component definition.
|
|
49
|
+
* @return {Set<string>|null} camelCase prop names, or null for "all".
|
|
50
|
+
*/
|
|
51
|
+
export function declaredPropNames(component) {
|
|
52
|
+
if (!component) {
|
|
53
|
+
return null
|
|
54
|
+
}
|
|
55
|
+
if (typeof component === 'function') {
|
|
56
|
+
if (!component.props) {
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
const names = new Set()
|
|
60
|
+
collect({ props: component.props }, names, 0)
|
|
61
|
+
return names
|
|
62
|
+
}
|
|
63
|
+
if (typeof component !== 'object') {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
if (component.__asyncLoader) {
|
|
67
|
+
return component.__asyncResolved ? declaredPropNames(component.__asyncResolved) : null
|
|
68
|
+
}
|
|
69
|
+
const chain = [component, component.extends, ...(component.mixins || [])]
|
|
70
|
+
if (chain.some((options) => options && options.inheritAttrs === false)) {
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
const names = new Set()
|
|
74
|
+
collect(component, names, 0)
|
|
75
|
+
return names
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Keep the entries of `bindings` whose key `component` declares as a prop.
|
|
80
|
+
* A component for which {@link declaredPropNames} returns null gets every
|
|
81
|
+
* entry. A dropped key is warned about once outside production when the
|
|
82
|
+
* component has no `content` prop either, so the key reaches nothing.
|
|
83
|
+
*
|
|
84
|
+
* @spec openspec/changes/dashboard-declared-widget-props/specs/dashboard-page/spec.md#requirement-a-dashboard-widget-receives-only-the-content-keys-it-declares
|
|
85
|
+
* @param {object|((...args: unknown[]) => unknown)|null} component The component that will receive the bindings.
|
|
86
|
+
* @param {object} bindings The candidate bindings.
|
|
87
|
+
* @param {string} label Names the component in the warning (for example the widget type).
|
|
88
|
+
* @return {object} The bindings the component declares.
|
|
89
|
+
*/
|
|
90
|
+
export function pickDeclaredProps(component, bindings, label) {
|
|
91
|
+
const declared = declaredPropNames(component)
|
|
92
|
+
if (!declared || !bindings || typeof bindings !== 'object') {
|
|
93
|
+
return bindings
|
|
94
|
+
}
|
|
95
|
+
const out = {}
|
|
96
|
+
for (const [key, value] of Object.entries(bindings)) {
|
|
97
|
+
if (declared.has(camelize(key))) {
|
|
98
|
+
out[key] = value
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
if (process.env.NODE_ENV !== 'production' && !declared.has('content') && !warned.has(`${label}\u0000${key}`)) {
|
|
102
|
+
warned.add(`${label}\u0000${key}`)
|
|
103
|
+
// eslint-disable-next-line no-console
|
|
104
|
+
console.warn(`[nextcloud-vue] content key "${key}" is not passed to widget "${label}": it declares neither that prop nor \`content\`.`)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return out
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Forget which keys were warned about. For tests.
|
|
112
|
+
*/
|
|
113
|
+
export function resetDeclaredPropWarnings() {
|
|
114
|
+
warned.clear()
|
|
115
|
+
}
|