@conduction/nextcloud-vue 2.89.2 → 2.91.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.
Files changed (52) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +13 -0
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +3 -1
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  6. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  7. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +18 -0
  8. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  9. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -2
  10. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  11. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  12. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +15 -1
  13. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  14. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +1 -1
  15. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  16. package/dist/esm/components/CnDialog/CnDialog.vue2.js +1 -1
  17. package/dist/esm/components/CnDialog/CnDialog.vue2.js.map +1 -1
  18. package/dist/esm/components/CnDialog/CnDialog.vue3.js.map +1 -1
  19. package/dist/esm/components/CnDialogHeader/CnDialogHeader.vue2.js +1 -1
  20. package/dist/esm/components/CnDialogHeader/CnDialogHeader.vue2.js.map +1 -1
  21. package/dist/esm/components/CnDialogHeader/CnDialogHeader.vue3.js.map +1 -1
  22. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
  23. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +16 -1
  24. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
  25. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +1 -1
  26. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
  27. package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue.js +1 -1
  28. package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue2.js +6 -3
  29. package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue2.js.map +1 -1
  30. package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue3.js.map +1 -1
  31. package/dist/esm/index.js +2 -0
  32. package/dist/esm/index.js.map +1 -1
  33. package/dist/esm/library-style.js +1 -1
  34. package/dist/esm/nextcloud-vue.css +74 -74
  35. package/dist/esm/utils/declaredProps.js +106 -0
  36. package/dist/esm/utils/declaredProps.js.map +1 -0
  37. package/dist/nextcloud-vue.cjs.js +185 -20
  38. package/dist/nextcloud-vue.cjs.js.map +2 -2
  39. package/dist/nextcloud-vue.css +74 -74
  40. package/package.json +1 -1
  41. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +14 -1
  42. package/src/components/CnDashboardPage/CnDashboardPage.vue +20 -2
  43. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +16 -2
  44. package/src/components/CnDialog/CnDialog.vue +1 -1
  45. package/src/components/CnDialog/index.js +7 -0
  46. package/src/components/CnDialogHeader/CnDialogHeader.vue +1 -1
  47. package/src/components/CnDialogHeader/index.js +7 -0
  48. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +17 -2
  49. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +6 -3
  50. package/src/components/index.js +2 -0
  51. package/src/index.js +2 -0
  52. package/src/utils/declaredProps.js +115 -0
@@ -34,7 +34,7 @@
34
34
  :is="entry.renderer"
35
35
  :widgetId="entry.id"
36
36
  :content="entry.content"
37
- v-bind="entry.content" />
37
+ v-bind="declaredContent(entry)" />
38
38
  </CnWidgetWrapper>
39
39
  </div>
40
40
  </component>
@@ -43,6 +43,7 @@
43
43
  <script>
44
44
  import CnKpiGrid from '../CnKpiGrid/CnKpiGrid.vue'
45
45
  import CnWidgetWrapper from '../CnWidgetWrapper/CnWidgetWrapper.vue'
46
+ import { pickDeclaredProps } from '../../utils/declaredProps.js'
46
47
  import { canonicalWidgetType } from '../../utils/widgetTypeAliases.js'
47
48
  import { BUILT_IN_WIDGETS } from '../CnWidgetGrid/builtInWidgets.js'
48
49
  import { getWidgetTypeEntry } from '../CnWidgetGrid/dashboardWidgetRegistry.js'
@@ -89,7 +90,7 @@ export default {
89
90
  * The renderable entries, in the listed order.
90
91
  *
91
92
  * @spec openspec/changes/screens-index-stat-row/specs/index-page/spec.md#requirement-an-index-page-can-draw-a-stat-row-above-the-list
92
- * @return {Array<{id: string, title: string, content: object, headerLink: (object|null), renderer: object}>}
93
+ * @return {Array<{id: string, type: string, title: string, content: object, headerLink: (object|null), renderer: object}>}
93
94
  */
94
95
  entries() {
95
96
  const list = Array.isArray(this.widgets) ? this.widgets : []
@@ -109,6 +110,7 @@ export default {
109
110
  const translate = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key
110
111
  out.push({
111
112
  id: def.id,
113
+ type: def.type,
112
114
  title: typeof def.title === 'string' && def.title !== '' ? translate(def.title) : '',
113
115
  content: (def.content && typeof def.content === 'object') ? def.content : {},
114
116
  headerLink: (def.headerLink && typeof def.headerLink === 'object') ? def.headerLink : null,
@@ -120,6 +122,19 @@ export default {
120
122
  },
121
123
 
122
124
  methods: {
125
+ /**
126
+ * The content keys an entry's renderer declares as props. An undeclared
127
+ * key would fall through onto the renderer's root; the whole content
128
+ * still arrives as `content`.
129
+ *
130
+ * @param {{ renderer: object, content: object, type?: string, id: string }} entry A resolved entry.
131
+ * @return {object} The declared content keys.
132
+ * @spec openspec/changes/declared-props-detail-index-sidebar/specs/widget-hosts/spec.md#requirement-a-widget-host-passes-only-the-keys-a-widget-declares
133
+ */
134
+ declaredContent(entry) {
135
+ return pickDeclaredProps(entry.renderer, entry.content, entry.type || entry.id)
136
+ },
137
+
123
138
  /**
124
139
  * Resolve a widget type: the consumer registry, then the dashboard
125
140
  * catalog (the type as written, then its alias), then the built-ins.
@@ -229,6 +229,7 @@ import CnTagsTab from './CnTagsTab.vue'
229
229
  import CnTasksTab from './CnTasksTab.vue'
230
230
  import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'
231
231
  import { useObjectSubscription } from '../../composables/useObjectSubscription.js'
232
+ import { pickDeclaredProps } from '../../utils/declaredProps.js'
232
233
  import { labelLang } from '../../utils/manifestTranslate.js'
233
234
  import { CnIcon } from '../CnIcon/index.js'
234
235
  import { CnLeafMountHost } from '../CnLeafMountHost/index.js'
@@ -965,9 +966,11 @@ export default {
965
966
  * widgets get the shared coordinate context plus the loaded object
966
967
  * (`objectData`); the prop-driven `data` built-in also gets the schema
967
968
  * OBJECT bound to its `schema` prop (the other tabs use the `schema`
968
- * slug string, so the override is scoped to `type: 'data'`). A widget's
969
- * own `props` win on overlap.
969
+ * slug string, so the override is scoped to `type: 'data'`). Of a
970
+ * widget's own `props`, the ones its component declares win on overlap;
971
+ * an undeclared one would only fall through onto the component's root.
970
972
  *
973
+ * @spec openspec/changes/declared-props-detail-index-sidebar/specs/widget-hosts/spec.md#requirement-a-widget-host-passes-only-the-keys-a-widget-declares
971
974
  * @param {{ type: string, props?: object }} w Widget spec
972
975
  * @return {object} Props to v-bind onto the resolved widget component
973
976
  */
@@ -980,7 +983,7 @@ export default {
980
983
  // a String `schema`.
981
984
  bindings.schema = this.objectSchema
982
985
  }
983
- return { ...bindings, ...(w.props || {}) }
986
+ return { ...bindings, ...pickDeclaredProps(this.resolveWidgetComponent(w.type), w.props || {}, w.type || '') }
984
987
  },
985
988
 
986
989
  /**
@@ -43,6 +43,8 @@ export { CnDeleteDialog } from './CnDeleteDialog/index.js'
43
43
  export { CnCopyDialog } from './CnCopyDialog/index.js'
44
44
  export { CnFieldHelper } from './CnFieldHelper/index.js'
45
45
  export { CnFormDialog } from './CnFormDialog/index.js'
46
+ export { CnDialog } from './CnDialog/index.js'
47
+ export { CnDialogHeader } from './CnDialogHeader/index.js'
46
48
  export { CnFormBuilder } from './CnFormBuilder/index.js'
47
49
  export { CnAdvancedFormDialog, CnMetadataTab, CnPropertiesTab, CnPropertyValueCell } from './CnAdvancedFormDialog/index.js'
48
50
  export { CnMassDeleteDialog } from './CnMassDeleteDialog/index.js'
package/src/index.js CHANGED
@@ -99,6 +99,8 @@ export {
99
99
  CnDetailGrid,
100
100
  CnDetailPage,
101
101
  CnDetailWidgetHost,
102
+ CnDialog,
103
+ CnDialogHeader,
102
104
  CnDividerWidget,
103
105
  CnDividerWidgetForm,
104
106
  CnDocumentReviewList,
@@ -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
+ }