@conduction/nextcloud-vue 2.89.0 → 2.89.2

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.
@@ -38141,17 +38141,17 @@ button.cn-object-calendar__overflow[data-v-e1c69ee2] {
38141
38141
  }
38142
38142
 
38143
38143
 
38144
- .cn-dashboard-grid[data-v-a91998fc] {
38144
+ .cn-dashboard-grid[data-v-a76b6fc6] {
38145
38145
  width: 100%;
38146
38146
  min-height: 200px;
38147
38147
  }
38148
- .grid-stack[data-v-a91998fc] {
38148
+ .grid-stack[data-v-a76b6fc6] {
38149
38149
  background: transparent;
38150
38150
  }
38151
38151
 
38152
38152
  /* Screen-reader-only text: the keyboard key map and the live region. Kept in
38153
38153
  the layout (not `display: none`) so assistive tech still reads it. */
38154
- .cn-dashboard-grid__sr-only[data-v-a91998fc] {
38154
+ .cn-dashboard-grid__sr-only[data-v-a76b6fc6] {
38155
38155
  position: absolute;
38156
38156
  width: 1px;
38157
38157
  height: 1px;
@@ -38166,7 +38166,7 @@ button.cn-object-calendar__overflow[data-v-e1c69ee2] {
38166
38166
  /* Grid items become tab stops in edit mode — give them a visible focus ring
38167
38167
  that satisfies WCAG 2.1 SC 2.4.7 without relying on the widget's own
38168
38168
  styling. `outline-offset` keeps the ring outside the rounded card. */
38169
- .grid-stack-item[data-v-a91998fc]:focus-visible {
38169
+ .grid-stack-item[data-v-a76b6fc6]:focus-visible {
38170
38170
  outline: 2px solid var(--color-primary-element, #0082c9);
38171
38171
  outline-offset: 2px;
38172
38172
  border-radius: var(--border-radius-container-large, 16px);
@@ -38178,19 +38178,19 @@ button.cn-object-calendar__overflow[data-v-e1c69ee2] {
38178
38178
  cell never shrank: "Waiting for me" stayed a tall card around one row. In
38179
38179
  such a cell the child takes its own height. A cell without the key keeps
38180
38180
  the filling child. */
38181
- .cn-dashboard-grid__item--fit[data-v-a91998fc] > .grid-stack-item-content > * {
38181
+ .cn-dashboard-grid__item--fit[data-v-a76b6fc6] > .grid-stack-item-content > * {
38182
38182
  height: auto;
38183
38183
  }
38184
- [data-v-a91998fc] .grid-stack-item-content {
38184
+ [data-v-a76b6fc6] .grid-stack-item-content {
38185
38185
  background: var(--color-main-background);
38186
38186
  border: none;
38187
38187
  box-shadow: none;
38188
38188
  overflow: hidden;
38189
38189
  }
38190
- [data-v-a91998fc] .grid-stack-item-content:has(.cn-widget-wrapper--borderless) {
38190
+ [data-v-a76b6fc6] .grid-stack-item-content:has(.cn-widget-wrapper--borderless) {
38191
38191
  background: transparent;
38192
38192
  }
38193
- [data-v-a91998fc] .grid-stack-placeholder > .placeholder-content {
38193
+ [data-v-a76b6fc6] .grid-stack-placeholder > .placeholder-content {
38194
38194
  background: var(--color-primary-element-light);
38195
38195
  border: 2px dashed var(--color-primary-element);
38196
38196
  border-radius: var(--border-radius-large);
@@ -65,8 +65,11 @@ function readGridGeometry(items) {
65
65
  * full-width card under the stat tiles landed below the lists.
66
66
  *
67
67
  * @spec openspec/changes/dashboard-growth-keeps-order/specs/dashboard-grid/spec.md#requirement-a-growing-cell-keeps-the-order-of-the-cards-under-it
68
- * @param {Array<{x: number, y: number, w: number, h: number}>} nodes The grid's items (the growing one among them).
69
- * @param {{x: number, y: number, w: number, h: number}} node The growing item.
68
+ * The growing item is never among the items it moves, also when `nodes`
69
+ * holds another reference to it (a proxy of the same node, matched by `el`).
70
+ *
71
+ * @param {Array<{x: number, y: number, w: number, h: number, el: (HTMLElement|undefined)}>} nodes The grid's items (the growing one among them).
72
+ * @param {{x: number, y: number, w: number, h: number, el: (HTMLElement|undefined)}} node The growing item.
70
73
  * @param {number} h Its new height, in grid rows.
71
74
  * @return {Array<{node: object, y: number}>} The items to move and their new row, bottom-most first.
72
75
  */
@@ -81,7 +84,8 @@ function planGrowth(nodes, node, h) {
81
84
  while (queue.length > 0) {
82
85
  const rect = queue.shift();
83
86
  for (const other of nodes) {
84
- if (other === node || moved.has(other) || !overlaps(rect, other)) {
87
+ const self = other === node || (node.el && other.el === node.el);
88
+ if (self || moved.has(other) || !overlaps(rect, other)) {
85
89
  continue
86
90
  }
87
91
  const next = { x: other.x, y: other.y + delta, w: other.w, h: other.h };
@@ -1 +1 @@
1
- {"version":3,"file":"gridStack.js","sources":["../../../src/utils/gridStack.js"],"sourcesContent":["/**\n * Thin GridStack bootstrap shared by the drag/resize grids (CnDashboardGrid and\n * CnWidgetGrid's editable body slot). Keeps the GridStack option shape in one\n * place so the two engines stay consistent.\n *\n * @module utils/gridStack\n */\n\nimport { GridStack } from 'gridstack'\n// `gridstack` is a peerDependency, not bundled — see the matching comment in\n// CnDashboardGrid.vue. The consumer must install `gridstack` themselves and\n// import its stylesheet (`gridstack/dist/gridstack.min.css`) from that same\n// copy; nc-vue ships no GridStack CSS of its own so the JS driving the grid\n// and the CSS sizing it can never come from two different versions.\n\n/**\n * Initialise a GridStack instance on a `.grid-stack` element.\n *\n * @param {HTMLElement} el The `.grid-stack` container element.\n * @param {object} opts Options.\n * @param {number} opts.columns Column count.\n * @param {boolean} opts.editable Whether drag + resize are enabled.\n * @param {number} [opts.cellHeight] Cell height in px (default 80).\n * @param {number} [opts.margin] Grid margin in px (default 12).\n * @return {import('gridstack').GridStack} The GridStack instance.\n */\nexport function initGridStack(el, opts) {\n\tconst { columns, editable, cellHeight = 80, margin = 12 } = opts\n\treturn GridStack.init({\n\t\tcolumn: columns,\n\t\tcellHeight,\n\t\tmargin,\n\t\tfloat: true,\n\t\tanimate: true,\n\t\tdisableDrag: !editable,\n\t\tdisableResize: !editable,\n\t\tremovable: false,\n\t}, el)\n}\n\n/**\n * Read the geometry GridStack reports for a changed set of items into a plain\n * map keyed by node id.\n *\n * @param {Array<{id: (string|number), x: number, y: number, w: number, h: number}>} items\n * The items GridStack passes to its `change` handler.\n * @return {Map<string, {gridX: number, gridY: number, gridWidth: number, gridHeight: number}>}\n */\nexport function readGridGeometry(items) {\n\tconst map = new Map()\n\tfor (const it of items || []) {\n\t\tmap.set(String(it.id), { gridX: it.x, gridY: it.y, gridWidth: it.w, gridHeight: it.h })\n\t}\n\treturn map\n}\n\n/**\n * Where the items under a growing cell go so they keep their order: every item\n * the growth would overlap moves down by the growth, and so does every item\n * that move would overlap in turn. Items beside the cell stay put.\n *\n * GridStack resolves the same collision itself, but not in order: when\n * pipelinq's size-to-content banner grew from two rows to three, the\n * full-width card under the stat tiles landed below the lists.\n *\n * @spec openspec/changes/dashboard-growth-keeps-order/specs/dashboard-grid/spec.md#requirement-a-growing-cell-keeps-the-order-of-the-cards-under-it\n * @param {Array<{x: number, y: number, w: number, h: number}>} nodes The grid's items (the growing one among them).\n * @param {{x: number, y: number, w: number, h: number}} node The growing item.\n * @param {number} h Its new height, in grid rows.\n * @return {Array<{node: object, y: number}>} The items to move and their new row, bottom-most first.\n */\nexport function planGrowth(nodes, node, h) {\n\tconst delta = h - node.h\n\tif (!(delta > 0)) {\n\t\treturn []\n\t}\n\tconst moved = new Map()\n\tconst overlaps = (a, b) => a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h\n\tconst queue = [{ x: node.x, y: node.y, w: node.w, h }]\n\twhile (queue.length > 0) {\n\t\tconst rect = queue.shift()\n\t\tfor (const other of nodes) {\n\t\t\tif (other === node || moved.has(other) || !overlaps(rect, other)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconst next = { x: other.x, y: other.y + delta, w: other.w, h: other.h }\n\t\t\tmoved.set(other, next.y)\n\t\t\tqueue.push(next)\n\t\t}\n\t}\n\treturn [...moved.entries()]\n\t\t.map(([item, y]) => ({ node: item, y }))\n\t\t.sort((a, b) => b.y - a.y)\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gridStack.js","sources":["../../../src/utils/gridStack.js"],"sourcesContent":["/**\n * Thin GridStack bootstrap shared by the drag/resize grids (CnDashboardGrid and\n * CnWidgetGrid's editable body slot). Keeps the GridStack option shape in one\n * place so the two engines stay consistent.\n *\n * @module utils/gridStack\n */\n\nimport { GridStack } from 'gridstack'\n// `gridstack` is a peerDependency, not bundled — see the matching comment in\n// CnDashboardGrid.vue. The consumer must install `gridstack` themselves and\n// import its stylesheet (`gridstack/dist/gridstack.min.css`) from that same\n// copy; nc-vue ships no GridStack CSS of its own so the JS driving the grid\n// and the CSS sizing it can never come from two different versions.\n\n/**\n * Initialise a GridStack instance on a `.grid-stack` element.\n *\n * @param {HTMLElement} el The `.grid-stack` container element.\n * @param {object} opts Options.\n * @param {number} opts.columns Column count.\n * @param {boolean} opts.editable Whether drag + resize are enabled.\n * @param {number} [opts.cellHeight] Cell height in px (default 80).\n * @param {number} [opts.margin] Grid margin in px (default 12).\n * @return {import('gridstack').GridStack} The GridStack instance.\n */\nexport function initGridStack(el, opts) {\n\tconst { columns, editable, cellHeight = 80, margin = 12 } = opts\n\treturn GridStack.init({\n\t\tcolumn: columns,\n\t\tcellHeight,\n\t\tmargin,\n\t\tfloat: true,\n\t\tanimate: true,\n\t\tdisableDrag: !editable,\n\t\tdisableResize: !editable,\n\t\tremovable: false,\n\t}, el)\n}\n\n/**\n * Read the geometry GridStack reports for a changed set of items into a plain\n * map keyed by node id.\n *\n * @param {Array<{id: (string|number), x: number, y: number, w: number, h: number}>} items\n * The items GridStack passes to its `change` handler.\n * @return {Map<string, {gridX: number, gridY: number, gridWidth: number, gridHeight: number}>}\n */\nexport function readGridGeometry(items) {\n\tconst map = new Map()\n\tfor (const it of items || []) {\n\t\tmap.set(String(it.id), { gridX: it.x, gridY: it.y, gridWidth: it.w, gridHeight: it.h })\n\t}\n\treturn map\n}\n\n/**\n * Where the items under a growing cell go so they keep their order: every item\n * the growth would overlap moves down by the growth, and so does every item\n * that move would overlap in turn. Items beside the cell stay put.\n *\n * GridStack resolves the same collision itself, but not in order: when\n * pipelinq's size-to-content banner grew from two rows to three, the\n * full-width card under the stat tiles landed below the lists.\n *\n * @spec openspec/changes/dashboard-growth-keeps-order/specs/dashboard-grid/spec.md#requirement-a-growing-cell-keeps-the-order-of-the-cards-under-it\n * The growing item is never among the items it moves, also when `nodes`\n * holds another reference to it (a proxy of the same node, matched by `el`).\n *\n * @param {Array<{x: number, y: number, w: number, h: number, el: (HTMLElement|undefined)}>} nodes The grid's items (the growing one among them).\n * @param {{x: number, y: number, w: number, h: number, el: (HTMLElement|undefined)}} node The growing item.\n * @param {number} h Its new height, in grid rows.\n * @return {Array<{node: object, y: number}>} The items to move and their new row, bottom-most first.\n */\nexport function planGrowth(nodes, node, h) {\n\tconst delta = h - node.h\n\tif (!(delta > 0)) {\n\t\treturn []\n\t}\n\tconst moved = new Map()\n\tconst overlaps = (a, b) => a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h\n\tconst queue = [{ x: node.x, y: node.y, w: node.w, h }]\n\twhile (queue.length > 0) {\n\t\tconst rect = queue.shift()\n\t\tfor (const other of nodes) {\n\t\t\tconst self = other === node || (node.el && other.el === node.el)\n\t\t\tif (self || moved.has(other) || !overlaps(rect, other)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconst next = { x: other.x, y: other.y + delta, w: other.w, h: other.h }\n\t\t\tmoved.set(other, next.y)\n\t\t\tqueue.push(next)\n\t\t}\n\t}\n\treturn [...moved.entries()]\n\t\t.map(([item, y]) => ({ node: item, y }))\n\t\t.sort((a, b) => b.y - a.y)\n}\n"],"names":[],"mappings":"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"}
@@ -199632,6 +199632,11 @@ var script$3e = {
199632
199632
  cnTranslate: { default: () => (key) => key },
199633
199633
  },
199634
199634
 
199635
+ // A dashboard binds the widget's `content` keys as attributes too. Falling
199636
+ // through, `submitLabel` would reach CnFormWidgetBase and win over the
199637
+ // translated label the widget passes, so attributes go through rootAttrs.
199638
+ inheritAttrs: false,
199639
+
199635
199640
  props: {
199636
199641
  /**
199637
199642
  * Persisted configuration blob (see component description for the shape).
@@ -199667,6 +199672,19 @@ var script$3e = {
199667
199672
  },
199668
199673
 
199669
199674
  computed: {
199675
+ /**
199676
+ * The attributes for the root form: everything the widget was given,
199677
+ * minus the names CnFormWidgetBase takes as props, which the widget
199678
+ * sets itself.
199679
+ *
199680
+ * @spec openspec/changes/interaction-form-fits-and-reads-dutch/specs/interaction-form-widget/spec.md#requirement-the-interaction-form-fits-its-card-and-reads-in-the-readers-language
199681
+ * @return {object} The attributes to bind.
199682
+ */
199683
+ rootAttrs() {
199684
+ const own = new Set(Object.keys(__sfc_main$3f.props || {}));
199685
+ return Object.fromEntries(Object.entries(this.$attrs).filter(([key]) => !own.has(vue$1.camelize(key))))
199686
+ },
199687
+
199670
199688
  register() {
199671
199689
  return this.content.register || 'pipelinq'
199672
199690
  },
@@ -200034,7 +200052,7 @@ function render$3e(_ctx, _cache, $props, $setup, $data, $options) {
200034
200052
  const _component_NcButton = vue$1.resolveComponent("NcButton");
200035
200053
  const _component_CnFormWidgetBase = vue$1.resolveComponent("CnFormWidgetBase");
200036
200054
 
200037
- return (vue$1.openBlock(), vue$1.createBlock(_component_CnFormWidgetBase, {
200055
+ return (vue$1.openBlock(), vue$1.createBlock(_component_CnFormWidgetBase, vue$1.mergeProps($options.rootAttrs, {
200038
200056
  blockClass: "cn-interaction-form-widget",
200039
200057
  fields: $options.formFields,
200040
200058
  model: $data.form,
@@ -200046,7 +200064,7 @@ function render$3e(_ctx, _cache, $props, $setup, $data, $options) {
200046
200064
  errorMessage: $data.errorMessage,
200047
200065
  "onUpdate:field": $options.onFieldUpdate,
200048
200066
  onSubmit: $options.onRegister
200049
- }, {
200067
+ }), {
200050
200068
  "field-client": vue$1.withCtx(() => [
200051
200069
  vue$1.createVNode(_component_CnResourceSelect, {
200052
200070
  register: $options.register,
@@ -200079,7 +200097,7 @@ function render$3e(_ctx, _cache, $props, $setup, $data, $options) {
200079
200097
  : vue$1.createCommentVNode("v-if", true)
200080
200098
  ]),
200081
200099
  _: 1 /* STABLE */
200082
- }, 8 /* PROPS */, ["fields", "model", "errors", "canSubmit", "submitting", "submitLabel", "submittingLabel", "errorMessage", "onUpdate:field", "onSubmit"]))
200100
+ }, 16 /* FULL_PROPS */, ["fields", "model", "errors", "canSubmit", "submitting", "submitLabel", "submittingLabel", "errorMessage", "onUpdate:field", "onSubmit"]))
200083
200101
  }
200084
200102
 
200085
200103
  script$3e.render = render$3e;
@@ -306984,8 +307002,11 @@ function readGridGeometry(items) {
306984
307002
  * full-width card under the stat tiles landed below the lists.
306985
307003
  *
306986
307004
  * @spec openspec/changes/dashboard-growth-keeps-order/specs/dashboard-grid/spec.md#requirement-a-growing-cell-keeps-the-order-of-the-cards-under-it
306987
- * @param {Array<{x: number, y: number, w: number, h: number}>} nodes The grid's items (the growing one among them).
306988
- * @param {{x: number, y: number, w: number, h: number}} node The growing item.
307005
+ * The growing item is never among the items it moves, also when `nodes`
307006
+ * holds another reference to it (a proxy of the same node, matched by `el`).
307007
+ *
307008
+ * @param {Array<{x: number, y: number, w: number, h: number, el: (HTMLElement|undefined)}>} nodes The grid's items (the growing one among them).
307009
+ * @param {{x: number, y: number, w: number, h: number, el: (HTMLElement|undefined)}} node The growing item.
306989
307010
  * @param {number} h Its new height, in grid rows.
306990
307011
  * @return {Array<{node: object, y: number}>} The items to move and their new row, bottom-most first.
306991
307012
  */
@@ -307000,7 +307021,8 @@ function planGrowth(nodes, node, h) {
307000
307021
  while (queue.length > 0) {
307001
307022
  const rect = queue.shift();
307002
307023
  for (const other of nodes) {
307003
- if (other === node || moved.has(other) || !overlaps(rect, other)) {
307024
+ const self = other === node || (node.el && other.el === node.el);
307025
+ if (self || moved.has(other) || !overlaps(rect, other)) {
307004
307026
  continue
307005
307027
  }
307006
307028
  const next = { x: other.x, y: other.y + delta, w: other.w, h: other.h };
@@ -307624,16 +307646,27 @@ var script$1t = {
307624
307646
  * (`itemsInRowOrder`). A cell that shrinks is left to GridStack as
307625
307647
  * before.
307626
307648
  *
307649
+ * The grid is read raw: through Vue's proxy `engine.nodes` holds proxies
307650
+ * while `el.gridstackNode` is the plain node, so the growing cell would
307651
+ * not be recognised among the nodes and would be moved down by its own
307652
+ * growth. Moving a cell makes GridStack measure it again, so a cell the
307653
+ * wrapper is already growing goes straight to GridStack.
307654
+ *
307627
307655
  * @spec openspec/changes/dashboard-growth-keeps-order/specs/dashboard-grid/spec.md#requirement-a-growing-cell-keeps-the-order-of-the-cards-under-it
307628
307656
  * @return {void}
307629
307657
  */
307630
307658
  keepOrderOnGrowth() {
307631
- const grid = this.grid;
307659
+ const grid = vue$1.toRaw(this.grid);
307632
307660
  if (!grid || typeof grid.resizeToContent !== 'function' || typeof grid.update !== 'function') {
307633
307661
  return
307634
307662
  }
307635
307663
  const resize = grid.resizeToContent.bind(grid);
307664
+ const growing = new Set();
307636
307665
  grid.resizeToContent = (el) => {
307666
+ if (growing.has(el)) {
307667
+ resize(el);
307668
+ return
307669
+ }
307637
307670
  const node = el && el.gridstackNode;
307638
307671
  const nodes = grid.engine && Array.isArray(grid.engine.nodes) ? grid.engine.nodes : null;
307639
307672
  const rows = node && nodes ? this.contentRows(el, node) : null;
@@ -307644,15 +307677,20 @@ var script$1t = {
307644
307677
  }
307645
307678
  // Inside a batch the moves are not packed until the cell has grown.
307646
307679
  const batching = Boolean(grid.engine.batchMode);
307647
- if (!batching) {
307648
- grid.batchUpdate();
307649
- }
307650
- for (const step of plan) {
307651
- grid.update(step.node.el, { y: step.y });
307652
- }
307653
- resize(el);
307654
- if (!batching) {
307655
- grid.batchUpdate(false);
307680
+ growing.add(el);
307681
+ try {
307682
+ if (!batching) {
307683
+ grid.batchUpdate();
307684
+ }
307685
+ for (const step of plan) {
307686
+ grid.update(step.node.el, { y: step.y });
307687
+ }
307688
+ resize(el);
307689
+ if (!batching) {
307690
+ grid.batchUpdate(false);
307691
+ }
307692
+ } finally {
307693
+ growing.delete(el);
307656
307694
  }
307657
307695
  };
307658
307696
  },
@@ -308255,7 +308293,7 @@ function render$1t(_ctx, _cache, $props, $setup, $data, $options) {
308255
308293
  }
308256
308294
 
308257
308295
  script$1t.render = render$1t;
308258
- script$1t.__scopeId = "data-v-a91998fc";
308296
+ script$1t.__scopeId = "data-v-a76b6fc6";
308259
308297
  const __sfc_main$1t = script$1t;
308260
308298
 
308261
308299
  /**
@@ -358535,6 +358573,7 @@ var script$h = {
358535
358573
  // write lands; this warns the user while there are unsaved/in-flight
358536
358574
  // changes. Registered before the early-return so it always installs.
358537
358575
  window.addEventListener('beforeunload', this.onBeforeUnload);
358576
+ window.addEventListener(OBJECTS_CHANGED_EVENT, this.onObjectsChanged);
358538
358577
 
358539
358578
  // Opt-out fast-path: empty `requiresApps` already initialised
358540
358579
  // `capabilitiesLoading` to `false` in data(); skip the check.
@@ -358580,6 +358619,7 @@ var script$h = {
358580
358619
  this.removeDiagnostics = null;
358581
358620
  }
358582
358621
  window.removeEventListener('beforeunload', this.onBeforeUnload);
358622
+ window.removeEventListener(OBJECTS_CHANGED_EVENT, this.onObjectsChanged);
358583
358623
  this.cnScopedTheme.teardown(this.appId);
358584
358624
  // Scope the side effect to the shell's lifetime. Layers already written
358585
358625
  // stay on screen (see `uninstallModalStack`), so a dialog outliving the
@@ -359426,13 +359466,17 @@ var script$h = {
359426
359466
  * response `total` written to `cnMenuItemCounts[item.id]`. The
359427
359467
  * request goes straight through axios rather than the object store,
359428
359468
  * so the index page's whole-schema total in the store is left alone.
359429
- * Failures leave the badge unrendered.
359469
+ * Failures leave the badge unrendered. Given a changed register and
359470
+ * schema, only the entries counting that list are read again.
359430
359471
  *
359431
359472
  * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-menu-entry-counts-a-filtered-list
359473
+ * @param {{register?: string|null, schema?: string|null}|null} [changed] The list that changed; null reads every count.
359432
359474
  * @return {void}
359433
359475
  * @private
359434
359476
  */
359435
- _hydrateMenuItemCounts() {
359477
+ _hydrateMenuItemCounts(changed = null) {
359478
+ const register = changed?.register ?? null;
359479
+ const schema = changed?.schema ?? null;
359436
359480
  const collect = (items) => {
359437
359481
  const out = [];
359438
359482
  for (const item of items ?? []) {
@@ -359447,10 +359491,27 @@ var script$h = {
359447
359491
  return out
359448
359492
  };
359449
359493
  for (const item of collect(this.manifest?.menu)) {
359450
- this._fetchMenuItemCount(item);
359494
+ if ((register === null || item.count.register === register) && (schema === null || item.count.schema === schema)) {
359495
+ this._fetchMenuItemCount(item);
359496
+ }
359451
359497
  }
359452
359498
  },
359453
359499
 
359500
+ /**
359501
+ * Objects changed outside the list showing them (`cn:objects-changed`,
359502
+ * see `dispatchObjectsChanged`): read the filtered menu counts of that
359503
+ * register and schema again, so a badge follows a claim, a close or a
359504
+ * bulk action instead of keeping the number it had at mount. A signal
359505
+ * without a register or schema reads every filtered count again.
359506
+ *
359507
+ * @spec openspec/changes/menu-counts-follow-changed-objects/specs/layout-components/spec.md#requirement-a-filtered-menu-count-follows-changed-objects
359508
+ * @param {CustomEvent} event The signal, with `detail: { register, schema, id }`.
359509
+ * @return {void}
359510
+ */
359511
+ onObjectsChanged(event) {
359512
+ this._hydrateMenuItemCounts(event?.detail ?? null);
359513
+ },
359514
+
359454
359515
  /**
359455
359516
  * One filtered-count request for a menu entry; see
359456
359517
  * `_hydrateMenuItemCounts`.
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/dist/nextcloud-vue.cjs.js.map
3
- size: 40761222 bytes
4
- sha256: 74c8351e2250accf05102ce32e61fbb7e7f3c5c7ace613b0d9df9cee7b9249ae
3
+ size: 40768932 bytes
4
+ sha256: 5eaf232e7473c7af37c95cfc8b0839550069002fa540192d0ea4b88131fbc06d
@@ -38141,17 +38141,17 @@ button.cn-object-calendar__overflow[data-v-e1c69ee2] {
38141
38141
  }
38142
38142
 
38143
38143
 
38144
- .cn-dashboard-grid[data-v-a91998fc] {
38144
+ .cn-dashboard-grid[data-v-a76b6fc6] {
38145
38145
  width: 100%;
38146
38146
  min-height: 200px;
38147
38147
  }
38148
- .grid-stack[data-v-a91998fc] {
38148
+ .grid-stack[data-v-a76b6fc6] {
38149
38149
  background: transparent;
38150
38150
  }
38151
38151
 
38152
38152
  /* Screen-reader-only text: the keyboard key map and the live region. Kept in
38153
38153
  the layout (not `display: none`) so assistive tech still reads it. */
38154
- .cn-dashboard-grid__sr-only[data-v-a91998fc] {
38154
+ .cn-dashboard-grid__sr-only[data-v-a76b6fc6] {
38155
38155
  position: absolute;
38156
38156
  width: 1px;
38157
38157
  height: 1px;
@@ -38166,7 +38166,7 @@ button.cn-object-calendar__overflow[data-v-e1c69ee2] {
38166
38166
  /* Grid items become tab stops in edit mode — give them a visible focus ring
38167
38167
  that satisfies WCAG 2.1 SC 2.4.7 without relying on the widget's own
38168
38168
  styling. `outline-offset` keeps the ring outside the rounded card. */
38169
- .grid-stack-item[data-v-a91998fc]:focus-visible {
38169
+ .grid-stack-item[data-v-a76b6fc6]:focus-visible {
38170
38170
  outline: 2px solid var(--color-primary-element, #0082c9);
38171
38171
  outline-offset: 2px;
38172
38172
  border-radius: var(--border-radius-container-large, 16px);
@@ -38178,19 +38178,19 @@ button.cn-object-calendar__overflow[data-v-e1c69ee2] {
38178
38178
  cell never shrank: "Waiting for me" stayed a tall card around one row. In
38179
38179
  such a cell the child takes its own height. A cell without the key keeps
38180
38180
  the filling child. */
38181
- .cn-dashboard-grid__item--fit[data-v-a91998fc] > .grid-stack-item-content > * {
38181
+ .cn-dashboard-grid__item--fit[data-v-a76b6fc6] > .grid-stack-item-content > * {
38182
38182
  height: auto;
38183
38183
  }
38184
- [data-v-a91998fc] .grid-stack-item-content {
38184
+ [data-v-a76b6fc6] .grid-stack-item-content {
38185
38185
  background: var(--color-main-background);
38186
38186
  border: none;
38187
38187
  box-shadow: none;
38188
38188
  overflow: hidden;
38189
38189
  }
38190
- [data-v-a91998fc] .grid-stack-item-content:has(.cn-widget-wrapper--borderless) {
38190
+ [data-v-a76b6fc6] .grid-stack-item-content:has(.cn-widget-wrapper--borderless) {
38191
38191
  background: transparent;
38192
38192
  }
38193
- [data-v-a91998fc] .grid-stack-placeholder > .placeholder-content {
38193
+ [data-v-a76b6fc6] .grid-stack-placeholder > .placeholder-content {
38194
38194
  background: var(--color-primary-element-light);
38195
38195
  border: 2px dashed var(--color-primary-element);
38196
38196
  border-radius: var(--border-radius-large);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.89.0",
3
+ "version": "2.89.2",
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": {
@@ -740,6 +740,7 @@ import { DEFAULT_FORGE, resolveForge } from '../../utils/forge.js'
740
740
  import { BUILT_IN_KB_PROVIDERS } from '../../utils/kbSearchProviders.js'
741
741
  import { createManifestTranslate } from '../../utils/manifestTranslate.js'
742
742
  import { installModalStack, uninstallModalStack } from '../../utils/modalStack.js'
743
+ import { OBJECTS_CHANGED_EVENT } from '../../utils/objectSignals.js'
743
744
  import { resolveBrand } from '../../utils/resolveBrand.js'
744
745
  import { resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
745
746
  import { passesContextPredicates } from '../../utils/visibleIfContext.js'
@@ -3221,6 +3222,7 @@ export default {
3221
3222
  // write lands; this warns the user while there are unsaved/in-flight
3222
3223
  // changes. Registered before the early-return so it always installs.
3223
3224
  window.addEventListener('beforeunload', this.onBeforeUnload)
3225
+ window.addEventListener(OBJECTS_CHANGED_EVENT, this.onObjectsChanged)
3224
3226
 
3225
3227
  // Opt-out fast-path: empty `requiresApps` already initialised
3226
3228
  // `capabilitiesLoading` to `false` in data(); skip the check.
@@ -3266,6 +3268,7 @@ export default {
3266
3268
  this.removeDiagnostics = null
3267
3269
  }
3268
3270
  window.removeEventListener('beforeunload', this.onBeforeUnload)
3271
+ window.removeEventListener(OBJECTS_CHANGED_EVENT, this.onObjectsChanged)
3269
3272
  this.cnScopedTheme.teardown(this.appId)
3270
3273
  // Scope the side effect to the shell's lifetime. Layers already written
3271
3274
  // stay on screen (see `uninstallModalStack`), so a dialog outliving the
@@ -4112,13 +4115,17 @@ export default {
4112
4115
  * response `total` written to `cnMenuItemCounts[item.id]`. The
4113
4116
  * request goes straight through axios rather than the object store,
4114
4117
  * so the index page's whole-schema total in the store is left alone.
4115
- * Failures leave the badge unrendered.
4118
+ * Failures leave the badge unrendered. Given a changed register and
4119
+ * schema, only the entries counting that list are read again.
4116
4120
  *
4117
4121
  * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-menu-entry-counts-a-filtered-list
4122
+ * @param {{register?: string|null, schema?: string|null}|null} [changed] The list that changed; null reads every count.
4118
4123
  * @return {void}
4119
4124
  * @private
4120
4125
  */
4121
- _hydrateMenuItemCounts() {
4126
+ _hydrateMenuItemCounts(changed = null) {
4127
+ const register = changed?.register ?? null
4128
+ const schema = changed?.schema ?? null
4122
4129
  const collect = (items) => {
4123
4130
  const out = []
4124
4131
  for (const item of items ?? []) {
@@ -4133,10 +4140,27 @@ export default {
4133
4140
  return out
4134
4141
  }
4135
4142
  for (const item of collect(this.manifest?.menu)) {
4136
- this._fetchMenuItemCount(item)
4143
+ if ((register === null || item.count.register === register) && (schema === null || item.count.schema === schema)) {
4144
+ this._fetchMenuItemCount(item)
4145
+ }
4137
4146
  }
4138
4147
  },
4139
4148
 
4149
+ /**
4150
+ * Objects changed outside the list showing them (`cn:objects-changed`,
4151
+ * see `dispatchObjectsChanged`): read the filtered menu counts of that
4152
+ * register and schema again, so a badge follows a claim, a close or a
4153
+ * bulk action instead of keeping the number it had at mount. A signal
4154
+ * without a register or schema reads every filtered count again.
4155
+ *
4156
+ * @spec openspec/changes/menu-counts-follow-changed-objects/specs/layout-components/spec.md#requirement-a-filtered-menu-count-follows-changed-objects
4157
+ * @param {CustomEvent} event The signal, with `detail: { register, schema, id }`.
4158
+ * @return {void}
4159
+ */
4160
+ onObjectsChanged(event) {
4161
+ this._hydrateMenuItemCounts(event?.detail ?? null)
4162
+ },
4163
+
4140
4164
  /**
4141
4165
  * One filtered-count request for a menu entry; see
4142
4166
  * `_hydrateMenuItemCounts`.
@@ -71,6 +71,7 @@
71
71
  <script>
72
72
  import { translate as t } from '@nextcloud/l10n'
73
73
  import { GridStack } from 'gridstack'
74
+ import { toRaw } from 'vue'
74
75
  import { planGrowth } from '../../utils/gridStack.js'
75
76
  // `gridstack` is a peerDependency (see package.json), not bundled — this
76
77
  // component's JS and the consumer's GridStack stylesheet MUST come from the
@@ -683,16 +684,27 @@ export default {
683
684
  * (`itemsInRowOrder`). A cell that shrinks is left to GridStack as
684
685
  * before.
685
686
  *
687
+ * The grid is read raw: through Vue's proxy `engine.nodes` holds proxies
688
+ * while `el.gridstackNode` is the plain node, so the growing cell would
689
+ * not be recognised among the nodes and would be moved down by its own
690
+ * growth. Moving a cell makes GridStack measure it again, so a cell the
691
+ * wrapper is already growing goes straight to GridStack.
692
+ *
686
693
  * @spec openspec/changes/dashboard-growth-keeps-order/specs/dashboard-grid/spec.md#requirement-a-growing-cell-keeps-the-order-of-the-cards-under-it
687
694
  * @return {void}
688
695
  */
689
696
  keepOrderOnGrowth() {
690
- const grid = this.grid
697
+ const grid = toRaw(this.grid)
691
698
  if (!grid || typeof grid.resizeToContent !== 'function' || typeof grid.update !== 'function') {
692
699
  return
693
700
  }
694
701
  const resize = grid.resizeToContent.bind(grid)
702
+ const growing = new Set()
695
703
  grid.resizeToContent = (el) => {
704
+ if (growing.has(el)) {
705
+ resize(el)
706
+ return
707
+ }
696
708
  const node = el && el.gridstackNode
697
709
  const nodes = grid.engine && Array.isArray(grid.engine.nodes) ? grid.engine.nodes : null
698
710
  const rows = node && nodes ? this.contentRows(el, node) : null
@@ -703,15 +715,20 @@ export default {
703
715
  }
704
716
  // Inside a batch the moves are not packed until the cell has grown.
705
717
  const batching = Boolean(grid.engine.batchMode)
706
- if (!batching) {
707
- grid.batchUpdate()
708
- }
709
- for (const step of plan) {
710
- grid.update(step.node.el, { y: step.y })
711
- }
712
- resize(el)
713
- if (!batching) {
714
- grid.batchUpdate(false)
718
+ growing.add(el)
719
+ try {
720
+ if (!batching) {
721
+ grid.batchUpdate()
722
+ }
723
+ for (const step of plan) {
724
+ grid.update(step.node.el, { y: step.y })
725
+ }
726
+ resize(el)
727
+ if (!batching) {
728
+ grid.batchUpdate(false)
729
+ }
730
+ } finally {
731
+ growing.delete(el)
715
732
  }
716
733
  }
717
734
  },
@@ -4,6 +4,7 @@
4
4
  -->
5
5
  <template>
6
6
  <CnFormWidgetBase
7
+ v-bind="rootAttrs"
7
8
  blockClass="cn-interaction-form-widget"
8
9
  :fields="formFields"
9
10
  :model="form"
@@ -51,6 +52,7 @@
51
52
  <script>
52
53
  import { translate as t } from '@nextcloud/l10n'
53
54
  import { NcButton } from '@nextcloud/vue'
55
+ import { camelize } from 'vue'
54
56
  import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
55
57
  import CnFormWidgetBase from '../CnFormWidgetBase/CnFormWidgetBase.vue'
56
58
  import CnResourceSelect from '../CnResourceSelect/CnResourceSelect.vue'
@@ -125,6 +127,11 @@ export default {
125
127
  cnTranslate: { default: () => (key) => key },
126
128
  },
127
129
 
130
+ // A dashboard binds the widget's `content` keys as attributes too. Falling
131
+ // through, `submitLabel` would reach CnFormWidgetBase and win over the
132
+ // translated label the widget passes, so attributes go through rootAttrs.
133
+ inheritAttrs: false,
134
+
128
135
  props: {
129
136
  /**
130
137
  * Persisted configuration blob (see component description for the shape).
@@ -160,6 +167,19 @@ export default {
160
167
  },
161
168
 
162
169
  computed: {
170
+ /**
171
+ * The attributes for the root form: everything the widget was given,
172
+ * minus the names CnFormWidgetBase takes as props, which the widget
173
+ * sets itself.
174
+ *
175
+ * @spec openspec/changes/interaction-form-fits-and-reads-dutch/specs/interaction-form-widget/spec.md#requirement-the-interaction-form-fits-its-card-and-reads-in-the-readers-language
176
+ * @return {object} The attributes to bind.
177
+ */
178
+ rootAttrs() {
179
+ const own = new Set(Object.keys(CnFormWidgetBase.props || {}))
180
+ return Object.fromEntries(Object.entries(this.$attrs).filter(([key]) => !own.has(camelize(key))))
181
+ },
182
+
163
183
  register() {
164
184
  return this.content.register || 'pipelinq'
165
185
  },
@@ -64,8 +64,11 @@ export function readGridGeometry(items) {
64
64
  * full-width card under the stat tiles landed below the lists.
65
65
  *
66
66
  * @spec openspec/changes/dashboard-growth-keeps-order/specs/dashboard-grid/spec.md#requirement-a-growing-cell-keeps-the-order-of-the-cards-under-it
67
- * @param {Array<{x: number, y: number, w: number, h: number}>} nodes The grid's items (the growing one among them).
68
- * @param {{x: number, y: number, w: number, h: number}} node The growing item.
67
+ * The growing item is never among the items it moves, also when `nodes`
68
+ * holds another reference to it (a proxy of the same node, matched by `el`).
69
+ *
70
+ * @param {Array<{x: number, y: number, w: number, h: number, el: (HTMLElement|undefined)}>} nodes The grid's items (the growing one among them).
71
+ * @param {{x: number, y: number, w: number, h: number, el: (HTMLElement|undefined)}} node The growing item.
69
72
  * @param {number} h Its new height, in grid rows.
70
73
  * @return {Array<{node: object, y: number}>} The items to move and their new row, bottom-most first.
71
74
  */
@@ -80,7 +83,8 @@ export function planGrowth(nodes, node, h) {
80
83
  while (queue.length > 0) {
81
84
  const rect = queue.shift()
82
85
  for (const other of nodes) {
83
- if (other === node || moved.has(other) || !overlaps(rect, other)) {
86
+ const self = other === node || (node.el && other.el === node.el)
87
+ if (self || moved.has(other) || !overlaps(rect, other)) {
84
88
  continue
85
89
  }
86
90
  const next = { x: other.x, y: other.y + delta, w: other.w, h: other.h }