@conduction/nextcloud-vue 2.90.0 → 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 (37) hide show
  1. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  2. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +15 -1
  3. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  4. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +1 -1
  5. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  6. package/dist/esm/components/CnDialog/CnDialog.vue2.js +1 -1
  7. package/dist/esm/components/CnDialog/CnDialog.vue2.js.map +1 -1
  8. package/dist/esm/components/CnDialog/CnDialog.vue3.js.map +1 -1
  9. package/dist/esm/components/CnDialogHeader/CnDialogHeader.vue2.js +1 -1
  10. package/dist/esm/components/CnDialogHeader/CnDialogHeader.vue2.js.map +1 -1
  11. package/dist/esm/components/CnDialogHeader/CnDialogHeader.vue3.js.map +1 -1
  12. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
  13. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +16 -1
  14. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
  15. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +1 -1
  16. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
  17. package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue.js +1 -1
  18. package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue2.js +6 -3
  19. package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue2.js.map +1 -1
  20. package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue3.js.map +1 -1
  21. package/dist/esm/index.js +2 -0
  22. package/dist/esm/index.js.map +1 -1
  23. package/dist/esm/library-style.js +1 -1
  24. package/dist/esm/nextcloud-vue.css +11 -11
  25. package/dist/nextcloud-vue.cjs.js +153 -122
  26. package/dist/nextcloud-vue.cjs.js.map +2 -2
  27. package/dist/nextcloud-vue.css +11 -11
  28. package/package.json +1 -1
  29. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +16 -2
  30. package/src/components/CnDialog/CnDialog.vue +1 -1
  31. package/src/components/CnDialog/index.js +7 -0
  32. package/src/components/CnDialogHeader/CnDialogHeader.vue +1 -1
  33. package/src/components/CnDialogHeader/index.js +7 -0
  34. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +17 -2
  35. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +6 -3
  36. package/src/components/index.js +2 -0
  37. package/src/index.js +2 -0
@@ -25077,7 +25077,7 @@ button.cn-stepper__step[data-v-776581d4] {
25077
25077
  }
25078
25078
 
25079
25079
 
25080
- .cn-detail-page__catalog-action[data-v-e7ff148e] {
25080
+ .cn-detail-page__catalog-action[data-v-47b3ec9e] {
25081
25081
  align-items: center;
25082
25082
  background: var(--color-primary-element-light);
25083
25083
  border: 0;
@@ -25093,19 +25093,19 @@ button.cn-stepper__step[data-v-776581d4] {
25093
25093
  padding: 0 14px;
25094
25094
  white-space: nowrap;
25095
25095
  }
25096
- .cn-detail-page__catalog-note[data-v-e7ff148e] {
25096
+ .cn-detail-page__catalog-note[data-v-47b3ec9e] {
25097
25097
  color: var(--color-text-maxcontrast);
25098
25098
  font-size: 14px;
25099
25099
  white-space: nowrap;
25100
25100
  }
25101
- .cn-detail-widget-host[data-v-e7ff148e],
25102
- .cn-detail-widget-host[data-v-e7ff148e] > * {
25101
+ .cn-detail-widget-host[data-v-47b3ec9e],
25102
+ .cn-detail-widget-host[data-v-47b3ec9e] > * {
25103
25103
  height: 100%;
25104
25104
  }
25105
25105
 
25106
25106
  /* A bare host is a tab panel's body: it fills the panel and lets the panel
25107
25107
  scroll, rather than growing a second scroll region of its own. */
25108
- .cn-detail-widget-host--bare[data-v-e7ff148e] {
25108
+ .cn-detail-widget-host--bare[data-v-47b3ec9e] {
25109
25109
  min-height: 0;
25110
25110
  }
25111
25111
 
@@ -36057,14 +36057,14 @@ button.cn-object-calendar__overflow[data-v-e1c69ee2] {
36057
36057
  }
36058
36058
 
36059
36059
 
36060
- .cn-index-page-widgets--stat-row[data-v-5e716bba] {
36060
+ .cn-index-page-widgets--stat-row[data-v-319129cb] {
36061
36061
  margin-bottom: var(--cn-board-section-gap, 20px);
36062
36062
  }
36063
- .cn-index-page-widgets__cell[data-v-5e716bba] {
36063
+ .cn-index-page-widgets__cell[data-v-319129cb] {
36064
36064
  display: flex;
36065
36065
  min-width: 0;
36066
36066
  }
36067
- .cn-index-page-widgets__cell[data-v-5e716bba] > * {
36067
+ .cn-index-page-widgets__cell[data-v-319129cb] > * {
36068
36068
  flex: 1 1 auto;
36069
36069
  min-width: 0;
36070
36070
  }
@@ -36072,7 +36072,7 @@ button.cn-object-calendar__overflow[data-v-e1c69ee2] {
36072
36072
  /* The side panel column (DqTeamwachtrij: 300px, 16px between cards). Its
36073
36073
  place beside the toolbar and the list is set by CnIndexPage
36074
36074
  (`cn-index-page--with-side-panel`, index-page.css). */
36075
- .cn-index-page-widgets--side-panel[data-v-5e716bba] {
36075
+ .cn-index-page-widgets--side-panel[data-v-319129cb] {
36076
36076
  display: flex;
36077
36077
  flex-direction: column;
36078
36078
  gap: 16px;
@@ -40772,10 +40772,10 @@ a.cn-integration-card__title[data-v-093a874d]:hover {
40772
40772
  * let the strip scroll horizontally once they no longer fit. `flex-basis: 0`
40773
40773
  * + equal grow keeps every tab the same width whether the row fits or scrolls.
40774
40774
  * Apps with longer labels can widen the floor via --cn-sidebar-tab-min-width. */
40775
- [data-v-8b218df0] .app-sidebar-tabs__nav {
40775
+ [data-v-09402703] .app-sidebar-tabs__nav {
40776
40776
  overflow-x: auto;
40777
40777
  }
40778
- [data-v-8b218df0] .app-sidebar-tabs__nav .app-sidebar-tabs__tab {
40778
+ [data-v-09402703] .app-sidebar-tabs__nav .app-sidebar-tabs__tab {
40779
40779
  flex-grow: 1;
40780
40780
  flex-shrink: 0;
40781
40781
  flex-basis: 0;
@@ -26166,7 +26166,7 @@ script$6Y.__scopeId = "data-v-52ba5fea";
26166
26166
  const __sfc_main$6S = script$6Y;
26167
26167
 
26168
26168
  /**
26169
- * CnDialogHeader (internal) - the board dialog header: eyebrow, 20px title,
26169
+ * CnDialogHeader - the board dialog header: eyebrow, 20px title,
26170
26170
  * subtitle and a round 36px close button.
26171
26171
  *
26172
26172
  * The close button emits `close` and is disabled while `noClose` is set, so a
@@ -26293,7 +26293,7 @@ function resolveDialogWidth(width, defaultWidth) {
26293
26293
  }
26294
26294
 
26295
26295
  /**
26296
- * CnDialog (internal) - the one place where a `Cn*` dialog turns into the
26296
+ * CnDialog - the one place where a `Cn*` dialog turns into the
26297
26297
  * board dialog (screens-dialog-parity).
26298
26298
  *
26299
26299
  * In the Nextcloud look it is `NcDialog` with the caller's `size`, unchanged.
@@ -152257,6 +152257,108 @@ script$4K.render = render$4K;
152257
152257
  script$4K.__scopeId = "data-v-f0af265a";
152258
152258
  const __sfc_main$4E = script$4K;
152259
152259
 
152260
+ /**
152261
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
152262
+ * SPDX-License-Identifier: EUPL-1.2
152263
+ */
152264
+
152265
+
152266
+ const warned$2 = new Set();
152267
+
152268
+ /**
152269
+ * Add the prop names one component options object declares, following its
152270
+ * `extends` and `mixins`.
152271
+ *
152272
+ * @param {object} options A component options object.
152273
+ * @param {Set<string>} names The set to add camelCase prop names to.
152274
+ * @param {number} depth Recursion guard.
152275
+ */
152276
+ function collect(options, names, depth) {
152277
+ if (!options || typeof options !== 'object' || depth > 8) {
152278
+ return
152279
+ }
152280
+ const props = options.props;
152281
+ if (Array.isArray(props)) {
152282
+ props.forEach((name) => names.add(vue$1.camelize(String(name))));
152283
+ } else if (props && typeof props === 'object') {
152284
+ Object.keys(props).forEach((name) => names.add(vue$1.camelize(name)));
152285
+ }
152286
+ collect(options.extends, names, depth + 1);
152287
+ if (Array.isArray(options.mixins)) {
152288
+ options.mixins.forEach((mixin) => collect(mixin, names, depth + 1));
152289
+ }
152290
+ }
152291
+
152292
+ /**
152293
+ * The prop names a component declares, or `null` when it takes every
152294
+ * attribute itself and so must keep receiving all of them:
152295
+ * - `inheritAttrs: false`: the component forwards `$attrs` on purpose;
152296
+ * - a functional component without `props`: its attrs are its props;
152297
+ * - an async component that has not resolved yet.
152298
+ *
152299
+ * @spec openspec/changes/dashboard-declared-widget-props/specs/dashboard-page/spec.md#requirement-a-dashboard-widget-receives-only-the-content-keys-it-declares
152300
+ * @param {object|((...args: unknown[]) => unknown)|null} component A component definition.
152301
+ * @return {Set<string>|null} camelCase prop names, or null for "all".
152302
+ */
152303
+ function declaredPropNames(component) {
152304
+ if (!component) {
152305
+ return null
152306
+ }
152307
+ if (typeof component === 'function') {
152308
+ if (!component.props) {
152309
+ return null
152310
+ }
152311
+ const names = new Set();
152312
+ collect({ props: component.props }, names, 0);
152313
+ return names
152314
+ }
152315
+ if (typeof component !== 'object') {
152316
+ return null
152317
+ }
152318
+ if (component.__asyncLoader) {
152319
+ return component.__asyncResolved ? declaredPropNames(component.__asyncResolved) : null
152320
+ }
152321
+ const chain = [component, component.extends, ...(component.mixins || [])];
152322
+ if (chain.some((options) => options && options.inheritAttrs === false)) {
152323
+ return null
152324
+ }
152325
+ const names = new Set();
152326
+ collect(component, names, 0);
152327
+ return names
152328
+ }
152329
+
152330
+ /**
152331
+ * Keep the entries of `bindings` whose key `component` declares as a prop.
152332
+ * A component for which {@link declaredPropNames} returns null gets every
152333
+ * entry. A dropped key is warned about once outside production when the
152334
+ * component has no `content` prop either, so the key reaches nothing.
152335
+ *
152336
+ * @spec openspec/changes/dashboard-declared-widget-props/specs/dashboard-page/spec.md#requirement-a-dashboard-widget-receives-only-the-content-keys-it-declares
152337
+ * @param {object|((...args: unknown[]) => unknown)|null} component The component that will receive the bindings.
152338
+ * @param {object} bindings The candidate bindings.
152339
+ * @param {string} label Names the component in the warning (for example the widget type).
152340
+ * @return {object} The bindings the component declares.
152341
+ */
152342
+ function pickDeclaredProps(component, bindings, label) {
152343
+ const declared = declaredPropNames(component);
152344
+ if (!declared || !bindings || typeof bindings !== 'object') {
152345
+ return bindings
152346
+ }
152347
+ const out = {};
152348
+ for (const [key, value] of Object.entries(bindings)) {
152349
+ if (declared.has(vue$1.camelize(key))) {
152350
+ out[key] = value;
152351
+ continue
152352
+ }
152353
+ if (process.env.NODE_ENV !== 'production' && !declared.has('content') && !warned$2.has(`${label}\u0000${key}`)) {
152354
+ warned$2.add(`${label}\u0000${key}`);
152355
+ // eslint-disable-next-line no-console
152356
+ console.warn(`[nextcloud-vue] content key "${key}" is not passed to widget "${label}": it declares neither that prop nor \`content\`.`);
152357
+ }
152358
+ }
152359
+ return out
152360
+ }
152361
+
152260
152362
  // SPDX-FileCopyrightText: 2026 Conduction B.V.
152261
152363
  // SPDX-License-Identifier: EUPL-1.2
152262
152364
 
@@ -158624,7 +158726,7 @@ const DEFAULT_COMPONENT_MAP = Object.freeze({
158624
158726
 
158625
158727
  const KNOWN_TYPES = ['boolean', 'number', 'password', 'string', 'enum', 'json', 'file'];
158626
158728
 
158627
- const warned$2 = new Set();
158729
+ const warned$1 = new Set();
158628
158730
 
158629
158731
  /**
158630
158732
  * Coerce an enum field's options into the `{ label, value }` shape
@@ -158783,8 +158885,8 @@ function cnRenderFormField({ field, value, onInput, t, error, componentMap } = {
158783
158885
  // per process. It used to be taken unconditionally and silently,
158784
158886
  // because the CommonJS lookup that fed `NcTextArea` could never
158785
158887
  // resolve from an ESM build.
158786
- if (!map['string-textarea'] && !warned$2.has('__no-nc-textarea')) {
158787
- warned$2.add('__no-nc-textarea');
158888
+ if (!map['string-textarea'] && !warned$1.has('__no-nc-textarea')) {
158889
+ warned$1.add('__no-nc-textarea');
158788
158890
  // eslint-disable-next-line no-console
158789
158891
  console.warn('[cnRenderFormField] NcTextArea is unavailable from @nextcloud/vue; '
158790
158892
  + 'falling back to a bare <textarea> for `widget: "textarea"` fields. '
@@ -158824,8 +158926,8 @@ function cnRenderFormField({ field, value, onInput, t, error, componentMap } = {
158824
158926
  }
158825
158927
  } else if (!KNOWN_TYPES.includes(field.type)) {
158826
158928
  // Unknown type — warn ONCE per type and fall back to NcTextField.
158827
- if (!warned$2.has(field.type)) {
158828
- warned$2.add(field.type);
158929
+ if (!warned$1.has(field.type)) {
158930
+ warned$1.add(field.type);
158829
158931
  // eslint-disable-next-line no-console
158830
158932
  console.warn(`[cnRenderFormField] Unknown field.type "${field.type}" for field "${field.key}". Falling back to NcTextField. Known types: ${KNOWN_TYPES.join(', ')}.`);
158831
158933
  }
@@ -163778,7 +163880,20 @@ var script$4p = {
163778
163880
  if (title) {
163779
163881
  base.title = title;
163780
163882
  }
163781
- return { ...base, ...this.content }
163883
+ return { ...base, ...this.declaredContent }
163884
+ },
163885
+
163886
+ /**
163887
+ * The content keys the renderer declares as props. An undeclared key
163888
+ * would fall through as an attribute onto the renderer's root and could
163889
+ * override a value the widget computes there; the whole content still
163890
+ * arrives as `content`.
163891
+ *
163892
+ * @return {object} The declared content keys.
163893
+ * @spec openspec/changes/declared-props-detail-index-sidebar/specs/widget-hosts/spec.md#requirement-a-widget-host-passes-only-the-keys-a-widget-declares
163894
+ */
163895
+ declaredContent() {
163896
+ return pickDeclaredProps(this.renderer, this.content, this.widget?.type || '')
163782
163897
  },
163783
163898
 
163784
163899
  /**
@@ -164268,7 +164383,7 @@ function render$4p(_ctx, _cache, $props, $setup, $data, $options) {
164268
164383
  class: "cn-detail-page__card-fit"
164269
164384
  }, vue$1.createSlots({
164270
164385
  default: vue$1.withCtx(() => [
164271
- (vue$1.openBlock(), vue$1.createBlock(vue$1.resolveDynamicComponent($options.renderer), vue$1.mergeProps({ content: $options.content }, $options.content), null, 16 /* FULL_PROPS */, ["content"]))
164386
+ (vue$1.openBlock(), vue$1.createBlock(vue$1.resolveDynamicComponent($options.renderer), vue$1.mergeProps({ content: $options.content }, $options.declaredContent), null, 16 /* FULL_PROPS */, ["content"]))
164272
164387
  ]),
164273
164388
  _: 2 /* DYNAMIC */
164274
164389
  }, [
@@ -164295,7 +164410,7 @@ function render$4p(_ctx, _cache, $props, $setup, $data, $options) {
164295
164410
  }
164296
164411
 
164297
164412
  script$4p.render = render$4p;
164298
- script$4p.__scopeId = "data-v-e7ff148e";
164413
+ script$4p.__scopeId = "data-v-47b3ec9e";
164299
164414
  const __sfc_main$4j = script$4p;
164300
164415
 
164301
164416
  /**
@@ -257068,7 +257183,7 @@ const __sfc_main$2l = script$2l;
257068
257183
  * @spec openspec/changes/environment-banner/tasks.md#task-2
257069
257184
  */
257070
257185
 
257071
- let warned$1 = false;
257186
+ let warned = false;
257072
257187
 
257073
257188
  /**
257074
257189
  * @param {object} options Options.
@@ -257094,8 +257209,8 @@ function useEnvironment({ environment, tenant, apiBase = '/apps/openregister/api
257094
257209
  const value = response && response.data && (response.data.environment ?? (response.data.results && response.data.results.environment));
257095
257210
  fetched.value = { ...fetched.value, [uuid]: typeof value === 'string' ? value : '' };
257096
257211
  } catch (error) {
257097
- if (!warned$1) {
257098
- warned$1 = true;
257212
+ if (!warned) {
257213
+ warned = true;
257099
257214
  // eslint-disable-next-line no-console
257100
257215
  console.warn('[useEnvironment] The organisation could not be read, so no environment is shown.', error);
257101
257216
  }
@@ -265292,7 +265407,7 @@ var script$25 = {
265292
265407
  * The renderable entries, in the listed order.
265293
265408
  *
265294
265409
  * @spec openspec/changes/screens-index-stat-row/specs/index-page/spec.md#requirement-an-index-page-can-draw-a-stat-row-above-the-list
265295
- * @return {Array<{id: string, title: string, content: object, headerLink: (object|null), renderer: object}>}
265410
+ * @return {Array<{id: string, type: string, title: string, content: object, headerLink: (object|null), renderer: object}>}
265296
265411
  */
265297
265412
  entries() {
265298
265413
  const list = Array.isArray(this.widgets) ? this.widgets : [];
@@ -265312,6 +265427,7 @@ var script$25 = {
265312
265427
  const translate = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key;
265313
265428
  out.push({
265314
265429
  id: def.id,
265430
+ type: def.type,
265315
265431
  title: typeof def.title === 'string' && def.title !== '' ? translate(def.title) : '',
265316
265432
  content: (def.content && typeof def.content === 'object') ? def.content : {},
265317
265433
  headerLink: (def.headerLink && typeof def.headerLink === 'object') ? def.headerLink : null,
@@ -265323,6 +265439,19 @@ var script$25 = {
265323
265439
  },
265324
265440
 
265325
265441
  methods: {
265442
+ /**
265443
+ * The content keys an entry's renderer declares as props. An undeclared
265444
+ * key would fall through onto the renderer's root; the whole content
265445
+ * still arrives as `content`.
265446
+ *
265447
+ * @param {{ renderer: object, content: object, type?: string, id: string }} entry A resolved entry.
265448
+ * @return {object} The declared content keys.
265449
+ * @spec openspec/changes/declared-props-detail-index-sidebar/specs/widget-hosts/spec.md#requirement-a-widget-host-passes-only-the-keys-a-widget-declares
265450
+ */
265451
+ declaredContent(entry) {
265452
+ return pickDeclaredProps(entry.renderer, entry.content, entry.type || entry.id)
265453
+ },
265454
+
265326
265455
  /**
265327
265456
  * Resolve a widget type: the consumer registry, then the dashboard
265328
265457
  * catalog (the type as written, then its alias), then the built-ins.
@@ -265376,7 +265505,7 @@ function render$25(_ctx, _cache, $props, $setup, $data, $options) {
265376
265505
  (vue$1.openBlock(), vue$1.createBlock(vue$1.resolveDynamicComponent(entry.renderer), vue$1.mergeProps({
265377
265506
  widgetId: entry.id,
265378
265507
  content: entry.content
265379
- }, { ref_for: true }, entry.content), null, 16 /* FULL_PROPS */, ["widgetId", "content"]))
265508
+ }, { ref_for: true }, $options.declaredContent(entry)), null, 16 /* FULL_PROPS */, ["widgetId", "content"]))
265380
265509
  ]),
265381
265510
  _: 2 /* DYNAMIC */
265382
265511
  }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["widgetId", "title", "showTitle", "headerLink", "flush"])
@@ -265389,7 +265518,7 @@ function render$25(_ctx, _cache, $props, $setup, $data, $options) {
265389
265518
  }
265390
265519
 
265391
265520
  script$25.render = render$25;
265392
- script$25.__scopeId = "data-v-5e716bba";
265521
+ script$25.__scopeId = "data-v-319129cb";
265393
265522
  const __sfc_main$25 = script$25;
265394
265523
 
265395
265524
  /**
@@ -318411,108 +318540,6 @@ script$1j.render = render$1j;
318411
318540
  script$1j.__scopeId = "data-v-a78d1ec4";
318412
318541
  const __sfc_main$1j = script$1j;
318413
318542
 
318414
- /**
318415
- * SPDX-FileCopyrightText: 2026 Conduction B.V.
318416
- * SPDX-License-Identifier: EUPL-1.2
318417
- */
318418
-
318419
-
318420
- const warned = new Set();
318421
-
318422
- /**
318423
- * Add the prop names one component options object declares, following its
318424
- * `extends` and `mixins`.
318425
- *
318426
- * @param {object} options A component options object.
318427
- * @param {Set<string>} names The set to add camelCase prop names to.
318428
- * @param {number} depth Recursion guard.
318429
- */
318430
- function collect(options, names, depth) {
318431
- if (!options || typeof options !== 'object' || depth > 8) {
318432
- return
318433
- }
318434
- const props = options.props;
318435
- if (Array.isArray(props)) {
318436
- props.forEach((name) => names.add(vue$1.camelize(String(name))));
318437
- } else if (props && typeof props === 'object') {
318438
- Object.keys(props).forEach((name) => names.add(vue$1.camelize(name)));
318439
- }
318440
- collect(options.extends, names, depth + 1);
318441
- if (Array.isArray(options.mixins)) {
318442
- options.mixins.forEach((mixin) => collect(mixin, names, depth + 1));
318443
- }
318444
- }
318445
-
318446
- /**
318447
- * The prop names a component declares, or `null` when it takes every
318448
- * attribute itself and so must keep receiving all of them:
318449
- * - `inheritAttrs: false`: the component forwards `$attrs` on purpose;
318450
- * - a functional component without `props`: its attrs are its props;
318451
- * - an async component that has not resolved yet.
318452
- *
318453
- * @spec openspec/changes/dashboard-declared-widget-props/specs/dashboard-page/spec.md#requirement-a-dashboard-widget-receives-only-the-content-keys-it-declares
318454
- * @param {object|((...args: unknown[]) => unknown)|null} component A component definition.
318455
- * @return {Set<string>|null} camelCase prop names, or null for "all".
318456
- */
318457
- function declaredPropNames(component) {
318458
- if (!component) {
318459
- return null
318460
- }
318461
- if (typeof component === 'function') {
318462
- if (!component.props) {
318463
- return null
318464
- }
318465
- const names = new Set();
318466
- collect({ props: component.props }, names, 0);
318467
- return names
318468
- }
318469
- if (typeof component !== 'object') {
318470
- return null
318471
- }
318472
- if (component.__asyncLoader) {
318473
- return component.__asyncResolved ? declaredPropNames(component.__asyncResolved) : null
318474
- }
318475
- const chain = [component, component.extends, ...(component.mixins || [])];
318476
- if (chain.some((options) => options && options.inheritAttrs === false)) {
318477
- return null
318478
- }
318479
- const names = new Set();
318480
- collect(component, names, 0);
318481
- return names
318482
- }
318483
-
318484
- /**
318485
- * Keep the entries of `bindings` whose key `component` declares as a prop.
318486
- * A component for which {@link declaredPropNames} returns null gets every
318487
- * entry. A dropped key is warned about once outside production when the
318488
- * component has no `content` prop either, so the key reaches nothing.
318489
- *
318490
- * @spec openspec/changes/dashboard-declared-widget-props/specs/dashboard-page/spec.md#requirement-a-dashboard-widget-receives-only-the-content-keys-it-declares
318491
- * @param {object|((...args: unknown[]) => unknown)|null} component The component that will receive the bindings.
318492
- * @param {object} bindings The candidate bindings.
318493
- * @param {string} label Names the component in the warning (for example the widget type).
318494
- * @return {object} The bindings the component declares.
318495
- */
318496
- function pickDeclaredProps(component, bindings, label) {
318497
- const declared = declaredPropNames(component);
318498
- if (!declared || !bindings || typeof bindings !== 'object') {
318499
- return bindings
318500
- }
318501
- const out = {};
318502
- for (const [key, value] of Object.entries(bindings)) {
318503
- if (declared.has(vue$1.camelize(key))) {
318504
- out[key] = value;
318505
- continue
318506
- }
318507
- if (process.env.NODE_ENV !== 'production' && !declared.has('content') && !warned.has(`${label}\u0000${key}`)) {
318508
- warned.add(`${label}\u0000${key}`);
318509
- // eslint-disable-next-line no-console
318510
- console.warn(`[nextcloud-vue] content key "${key}" is not passed to widget "${label}": it declares neither that prop nor \`content\`.`);
318511
- }
318512
- }
318513
- return out
318514
- }
318515
-
318516
318543
  /** Surfaces understood by the pluggable integration registry (AD-19). */
318517
318544
  const INTEGRATION_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'];
318518
318545
 
@@ -335035,9 +335062,11 @@ var script$N = {
335035
335062
  * widgets get the shared coordinate context plus the loaded object
335036
335063
  * (`objectData`); the prop-driven `data` built-in also gets the schema
335037
335064
  * OBJECT bound to its `schema` prop (the other tabs use the `schema`
335038
- * slug string, so the override is scoped to `type: 'data'`). A widget's
335039
- * own `props` win on overlap.
335065
+ * slug string, so the override is scoped to `type: 'data'`). Of a
335066
+ * widget's own `props`, the ones its component declares win on overlap;
335067
+ * an undeclared one would only fall through onto the component's root.
335040
335068
  *
335069
+ * @spec openspec/changes/declared-props-detail-index-sidebar/specs/widget-hosts/spec.md#requirement-a-widget-host-passes-only-the-keys-a-widget-declares
335041
335070
  * @param {{ type: string, props?: object }} w Widget spec
335042
335071
  * @return {object} Props to v-bind onto the resolved widget component
335043
335072
  */
@@ -335050,7 +335079,7 @@ var script$N = {
335050
335079
  // a String `schema`.
335051
335080
  bindings.schema = this.objectSchema;
335052
335081
  }
335053
- return { ...bindings, ...(w.props || {}) }
335082
+ return { ...bindings, ...pickDeclaredProps(this.resolveWidgetComponent(w.type), w.props || {}, w.type || '') }
335054
335083
  },
335055
335084
 
335056
335085
  /**
@@ -335372,7 +335401,7 @@ function render$N(_ctx, _cache, $props, $setup, $data, $options) {
335372
335401
  }
335373
335402
 
335374
335403
  script$N.render = render$N;
335375
- script$N.__scopeId = "data-v-8b218df0";
335404
+ script$N.__scopeId = "data-v-09402703";
335376
335405
  const __sfc_main$N = script$N;
335377
335406
 
335378
335407
  /**
@@ -397557,6 +397586,8 @@ exports.CnDetailCard = __sfc_main$6E;
397557
397586
  exports.CnDetailGrid = __sfc_main$4J;
397558
397587
  exports.CnDetailPage = __sfc_main$1m;
397559
397588
  exports.CnDetailWidgetHost = __sfc_main$4j;
397589
+ exports.CnDialog = __sfc_main$6Q;
397590
+ exports.CnDialogHeader = __sfc_main$6R;
397560
397591
  exports.CnDividerWidget = __sfc_main$4B;
397561
397592
  exports.CnDividerWidgetForm = __sfc_main$42;
397562
397593
  exports.CnDocumentReviewList = __sfc_main$76;
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/dist/nextcloud-vue.cjs.js.map
3
- size: 40779638 bytes
4
- sha256: b171c6d90fa9da1ac7b3e057618c0102feb5c4a930845adf71bda7617ccd3073
3
+ size: 40783800 bytes
4
+ sha256: 38ecb05b1a9c62bd250ca390394a3597748055d8c7dc1363ad11aa8069730ec1
@@ -25077,7 +25077,7 @@ button.cn-stepper__step[data-v-776581d4] {
25077
25077
  }
25078
25078
 
25079
25079
 
25080
- .cn-detail-page__catalog-action[data-v-e7ff148e] {
25080
+ .cn-detail-page__catalog-action[data-v-47b3ec9e] {
25081
25081
  align-items: center;
25082
25082
  background: var(--color-primary-element-light);
25083
25083
  border: 0;
@@ -25093,19 +25093,19 @@ button.cn-stepper__step[data-v-776581d4] {
25093
25093
  padding: 0 14px;
25094
25094
  white-space: nowrap;
25095
25095
  }
25096
- .cn-detail-page__catalog-note[data-v-e7ff148e] {
25096
+ .cn-detail-page__catalog-note[data-v-47b3ec9e] {
25097
25097
  color: var(--color-text-maxcontrast);
25098
25098
  font-size: 14px;
25099
25099
  white-space: nowrap;
25100
25100
  }
25101
- .cn-detail-widget-host[data-v-e7ff148e],
25102
- .cn-detail-widget-host[data-v-e7ff148e] > * {
25101
+ .cn-detail-widget-host[data-v-47b3ec9e],
25102
+ .cn-detail-widget-host[data-v-47b3ec9e] > * {
25103
25103
  height: 100%;
25104
25104
  }
25105
25105
 
25106
25106
  /* A bare host is a tab panel's body: it fills the panel and lets the panel
25107
25107
  scroll, rather than growing a second scroll region of its own. */
25108
- .cn-detail-widget-host--bare[data-v-e7ff148e] {
25108
+ .cn-detail-widget-host--bare[data-v-47b3ec9e] {
25109
25109
  min-height: 0;
25110
25110
  }
25111
25111
 
@@ -36057,14 +36057,14 @@ button.cn-object-calendar__overflow[data-v-e1c69ee2] {
36057
36057
  }
36058
36058
 
36059
36059
 
36060
- .cn-index-page-widgets--stat-row[data-v-5e716bba] {
36060
+ .cn-index-page-widgets--stat-row[data-v-319129cb] {
36061
36061
  margin-bottom: var(--cn-board-section-gap, 20px);
36062
36062
  }
36063
- .cn-index-page-widgets__cell[data-v-5e716bba] {
36063
+ .cn-index-page-widgets__cell[data-v-319129cb] {
36064
36064
  display: flex;
36065
36065
  min-width: 0;
36066
36066
  }
36067
- .cn-index-page-widgets__cell[data-v-5e716bba] > * {
36067
+ .cn-index-page-widgets__cell[data-v-319129cb] > * {
36068
36068
  flex: 1 1 auto;
36069
36069
  min-width: 0;
36070
36070
  }
@@ -36072,7 +36072,7 @@ button.cn-object-calendar__overflow[data-v-e1c69ee2] {
36072
36072
  /* The side panel column (DqTeamwachtrij: 300px, 16px between cards). Its
36073
36073
  place beside the toolbar and the list is set by CnIndexPage
36074
36074
  (`cn-index-page--with-side-panel`, index-page.css). */
36075
- .cn-index-page-widgets--side-panel[data-v-5e716bba] {
36075
+ .cn-index-page-widgets--side-panel[data-v-319129cb] {
36076
36076
  display: flex;
36077
36077
  flex-direction: column;
36078
36078
  gap: 16px;
@@ -40772,10 +40772,10 @@ a.cn-integration-card__title[data-v-093a874d]:hover {
40772
40772
  * let the strip scroll horizontally once they no longer fit. `flex-basis: 0`
40773
40773
  * + equal grow keeps every tab the same width whether the row fits or scrolls.
40774
40774
  * Apps with longer labels can widen the floor via --cn-sidebar-tab-min-width. */
40775
- [data-v-8b218df0] .app-sidebar-tabs__nav {
40775
+ [data-v-09402703] .app-sidebar-tabs__nav {
40776
40776
  overflow-x: auto;
40777
40777
  }
40778
- [data-v-8b218df0] .app-sidebar-tabs__nav .app-sidebar-tabs__tab {
40778
+ [data-v-09402703] .app-sidebar-tabs__nav .app-sidebar-tabs__tab {
40779
40779
  flex-grow: 1;
40780
40780
  flex-shrink: 0;
40781
40781
  flex-basis: 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.90.0",
3
+ "version": "2.91.0",
4
4
  "description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
5
5
  "homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
6
6
  "bugs": {
@@ -236,7 +236,7 @@
236
236
  <template v-if="widget.icon" #title-icon>
237
237
  <CnIcon :name="widget.icon" :size="20" />
238
238
  </template>
239
- <component :is="renderer" :content="content" v-bind="content" />
239
+ <component :is="renderer" :content="content" v-bind="declaredContent" />
240
240
  </CnWidgetWrapper>
241
241
 
242
242
  <!-- Every other content-driven catalog widget, and every bare-mode
@@ -276,6 +276,7 @@ import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry
276
276
  import { normalizeLook } from '../../composables/useLook.js'
277
277
  import { isAppInstalled } from '../../utils/appInstalled.js'
278
278
  import { widgetHeaderLink } from '../../utils/contentHeaderLink.js'
279
+ import { pickDeclaredProps } from '../../utils/declaredProps.js'
279
280
  import { unwrapAppConfig } from '../../utils/formatMetric.js'
280
281
  import { isActivityWidget } from '../../utils/headerMeta.js'
281
282
  import { PANEL_ACTION_SINK } from '../../utils/panelActions.js'
@@ -997,7 +998,20 @@ export default {
997
998
  if (title) {
998
999
  base.title = title
999
1000
  }
1000
- return { ...base, ...this.content }
1001
+ return { ...base, ...this.declaredContent }
1002
+ },
1003
+
1004
+ /**
1005
+ * The content keys the renderer declares as props. An undeclared key
1006
+ * would fall through as an attribute onto the renderer's root and could
1007
+ * override a value the widget computes there; the whole content still
1008
+ * arrives as `content`.
1009
+ *
1010
+ * @return {object} The declared content keys.
1011
+ * @spec openspec/changes/declared-props-detail-index-sidebar/specs/widget-hosts/spec.md#requirement-a-widget-host-passes-only-the-keys-a-widget-declares
1012
+ */
1013
+ declaredContent() {
1014
+ return pickDeclaredProps(this.renderer, this.content, this.widget?.type || '')
1001
1015
  },
1002
1016
 
1003
1017
  /**
@@ -32,7 +32,7 @@ import { useLook } from '../../composables/useLook.js'
32
32
  import { resolveDialogWidth } from '../../utils/dialogWidths.js'
33
33
 
34
34
  /**
35
- * CnDialog (internal) - the one place where a `Cn*` dialog turns into the
35
+ * CnDialog - the one place where a `Cn*` dialog turns into the
36
36
  * board dialog (screens-dialog-parity).
37
37
  *
38
38
  * In the Nextcloud look it is `NcDialog` with the caller's `size`, unchanged.
@@ -0,0 +1,7 @@
1
+ // SPDX-FileCopyrightText: 2026 Conduction B.V.
2
+ // SPDX-License-Identifier: EUPL-1.2
3
+
4
+ import CnDialog from './CnDialog.vue'
5
+
6
+ export default CnDialog
7
+ export { CnDialog }
@@ -45,7 +45,7 @@ import { NcButton } from '@nextcloud/vue'
45
45
  import Close from 'vue-material-design-icons/Close.vue'
46
46
 
47
47
  /**
48
- * CnDialogHeader (internal) - the board dialog header: eyebrow, 20px title,
48
+ * CnDialogHeader - the board dialog header: eyebrow, 20px title,
49
49
  * subtitle and a round 36px close button.
50
50
  *
51
51
  * The close button emits `close` and is disabled while `noClose` is set, so a
@@ -0,0 +1,7 @@
1
+ // SPDX-FileCopyrightText: 2026 Conduction B.V.
2
+ // SPDX-License-Identifier: EUPL-1.2
3
+
4
+ import CnDialogHeader from './CnDialogHeader.vue'
5
+
6
+ export default CnDialogHeader
7
+ export { CnDialogHeader }