@conduction/nextcloud-vue 2.132.0 → 2.133.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 (86) hide show
  1. package/dist/esm/components/CnActionButtons/CnActionButtons.vue.js +1 -1
  2. package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js +14 -0
  3. package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js +5 -4
  5. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
  6. package/dist/esm/components/CnConditionListEditor/CnConditionListEditor.vue.js +9 -0
  7. package/dist/esm/components/CnConditionListEditor/CnConditionListEditor.vue.js.map +1 -0
  8. package/dist/esm/components/CnConditionListEditor/CnConditionListEditor.vue2.js +237 -0
  9. package/dist/esm/components/CnConditionListEditor/CnConditionListEditor.vue2.js.map +1 -0
  10. package/dist/esm/components/CnConditionListEditor/CnConditionListEditor.vue3.js +103 -0
  11. package/dist/esm/components/CnConditionListEditor/CnConditionListEditor.vue3.js.map +1 -0
  12. package/dist/esm/components/CnConditionListEditor/conditionOperators.js +121 -0
  13. package/dist/esm/components/CnConditionListEditor/conditionOperators.js.map +1 -0
  14. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +12 -0
  15. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  16. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +1 -1
  17. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  18. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +8 -1
  19. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  20. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  21. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue.js +1 -1
  22. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js +46 -7
  23. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js.map +1 -1
  24. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js +1 -1
  25. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js.map +1 -1
  26. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue.js +1 -1
  27. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js +59 -3
  28. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js.map +1 -1
  29. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js +197 -127
  30. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js.map +1 -1
  31. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  32. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +42 -2
  33. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  34. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  35. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  36. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +1 -1
  37. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  38. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  39. package/dist/esm/index.js +2 -0
  40. package/dist/esm/index.js.map +1 -1
  41. package/dist/esm/l10n/en.json.js +17 -1
  42. package/dist/esm/l10n/en.json.js.map +1 -1
  43. package/dist/esm/l10n/nl.json.js +17 -1
  44. package/dist/esm/l10n/nl.json.js.map +1 -1
  45. package/dist/esm/library-style.js +1 -1
  46. package/dist/esm/nextcloud-vue.css +277 -166
  47. package/dist/esm/store/plugins/liveUpdates.js +13 -4
  48. package/dist/esm/store/plugins/liveUpdates.js.map +1 -1
  49. package/dist/esm/store/useObjectStore.js +28 -5
  50. package/dist/esm/store/useObjectStore.js.map +1 -1
  51. package/dist/esm/utils/detailActionModel.js +6 -0
  52. package/dist/esm/utils/detailActionModel.js.map +1 -1
  53. package/dist/esm/utils/listLenses.js +20 -1
  54. package/dist/esm/utils/listLenses.js.map +1 -1
  55. package/dist/esm/utils/objectRows.js +59 -0
  56. package/dist/esm/utils/objectRows.js.map +1 -0
  57. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  58. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  59. package/dist/nextcloud-vue.cjs.js +5961 -5127
  60. package/dist/nextcloud-vue.cjs.js.map +2 -2
  61. package/dist/nextcloud-vue.css +277 -166
  62. package/l10n/en.json +17 -1
  63. package/l10n/nl.json +17 -1
  64. package/package.json +1 -1
  65. package/src/components/CnActionButtons/CnActionButtons.vue +15 -0
  66. package/src/components/CnConditionListEditor/CnConditionListEditor.vue +346 -0
  67. package/src/components/CnConditionListEditor/conditionOperators.js +126 -0
  68. package/src/components/CnConditionListEditor/index.js +17 -0
  69. package/src/components/CnDetailPage/CnDetailPage.md +1 -0
  70. package/src/components/CnDetailPage/CnDetailPage.vue +13 -1
  71. package/src/components/CnIndexPage/CnIndexPage.vue +8 -1
  72. package/src/components/CnNcWidgetWidget/CnNcWidgetWidget.vue +47 -8
  73. package/src/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue +139 -2
  74. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +42 -2
  75. package/src/components/CnTabsWidget/CnTabsWidget.vue +1 -1
  76. package/src/components/index.js +1 -0
  77. package/src/css/look-board-index.css +6 -0
  78. package/src/css/look-board.css +35 -7
  79. package/src/index.js +2 -0
  80. package/src/schemas/app-manifest-v2.schema.json +17 -1
  81. package/src/store/plugins/liveUpdates.js +13 -4
  82. package/src/store/useObjectStore.js +28 -5
  83. package/src/utils/detailActionModel.js +6 -0
  84. package/src/utils/listLenses.js +20 -1
  85. package/src/utils/objectRows.js +55 -0
  86. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"CnActionButtons.vue3.js","sources":["../../../../src/components/CnActionButtons/CnActionButtons.vue?vue&type=template&id=7319dc1e&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div\n\t\tclass=\"cn-action-buttons\"\n\t\t:class=\"{ 'cn-action-buttons--menu': display === 'menu' }\"\n\t\tdata-testid=\"cn-action-buttons\">\n\t\t<!-- `display: \"menu\"` keeps every dialog below but renders no buttons of\n\t\t its own: the host draws the entries as NcActionButtons inside an\n\t\t overflow menu instead, driven by the `entries` event. NcActions\n\t\t keeps only NcAction* vnodes out of its default slot, so this\n\t\t component cannot be placed in the menu itself.\n\t\t That mode is enforced in `barActions` / `overflowActions`, which\n\t\t both answer empty for it — so `inline` only means anything while\n\t\t this component is the one drawing. -->\n\t\t<template v-for=\"entry in barActions\">\n\t\t\t<!-- Toggle: a stateful two-way state button (GET on mount, write on\n\t\t\t click, optimistic + revert). Rendered inline, never dispatched. -->\n\t\t\t<NcButton\n\t\t\t\tv-if=\"entry.type === 'toggle'\"\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\t:variant=\"toggleState[entry.id] ? 'primary' : 'secondary'\"\n\t\t\t\t:disabled=\"Boolean(togglePending[entry.id])\"\n\t\t\t\t:data-testid=\"`cn-action-toggle-${entry.id}`\"\n\t\t\t\t:aria-pressed=\"String(Boolean(toggleState[entry.id]))\"\n\t\t\t\t@click=\"onToggleClick(entry)\">\n\t\t\t\t<template v-if=\"entry.icon\" #icon>\n\t\t\t\t\t<CnIcon v-if=\"isMdiIconName(entry.icon)\" :name=\"entry.icon\" :size=\"20\" />\n\t\t\t\t\t<span v-else :class=\"entry.icon\" />\n\t\t\t\t</template>\n\t\t\t\t{{ tr(toggleState[entry.id] ? (entry.labelOn || entry.label) : (entry.labelOff || entry.label)) }}\n\t\t\t</NcButton>\n\t\t\t<!-- An entry that goes to a URL is a LINK, not a button that calls\n\t\t\t the browser. NcButton renders an anchor when given `href`, so it\n\t\t\t keeps the cluster's styling while the browser supplies the\n\t\t\t pointer, the focus ring, middle-click and \"open in new tab\" —\n\t\t\t none of which a click handler can offer. -->\n\t\t\t<NcButton\n\t\t\t\tv-else-if=\"entry.href\"\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\t:variant=\"entry.variant || 'secondary'\"\n\t\t\t\t:href=\"entry.href\"\n\t\t\t\t:target=\"linkTargetOf(entry)\"\n\t\t\t\t:data-testid=\"`cn-action-${entry.id}`\"\n\t\t\t\t@click=\"onLinkClick(entry, $event)\">\n\t\t\t\t<template v-if=\"entry.icon\" #icon>\n\t\t\t\t\t<CnIcon v-if=\"isMdiIconName(entry.icon)\" :name=\"entry.icon\" :size=\"20\" />\n\t\t\t\t\t<span v-else :class=\"entry.icon\" />\n\t\t\t\t</template>\n\t\t\t\t{{ tr(entry.label) }}\n\t\t\t</NcButton>\n\t\t\t<!-- Everything else: a plain action button routed through the shared\n\t\t\t dispatcher (api-call / open-form / navigate / open-modal / refresh),\n\t\t\t confirm-gated first when the action asks for it. -->\n\t\t\t<!-- `variant`, NOT `type`. NcButton carries both: `type` is the\n\t\t\t native button type (default \"button\") and `variant` is the\n\t\t\t styling. Binding the descriptor's variant to `type` set an\n\t\t\t invalid native type and left `variant` at its \"secondary\"\n\t\t\t default, so `variant: \"primary\"` / `\"error\"` had never once\n\t\t\t rendered — silently, since an unknown native type is ignored. -->\n\t\t\t<NcButton\n\t\t\t\tv-else\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\t:variant=\"entry.variant || 'secondary'\"\n\t\t\t\t:disabled=\"Boolean(actionPending[entry.id])\"\n\t\t\t\t:data-testid=\"`cn-action-${entry.id}`\"\n\t\t\t\t@click=\"onActionClick(entry)\">\n\t\t\t\t<template v-if=\"entry.icon\" #icon>\n\t\t\t\t\t<CnIcon v-if=\"isMdiIconName(entry.icon)\" :name=\"entry.icon\" :size=\"20\" />\n\t\t\t\t\t<span v-else :class=\"entry.icon\" />\n\t\t\t\t</template>\n\t\t\t\t{{ tr(entry.label) }}\n\t\t\t</NcButton>\n\t\t\t<!-- Sub-actions. Its OWN chevron rather than entries folded into the\n\t\t\t overflow below, because NcActions cannot nest — and a grouped\n\t\t\t dropdown is the point of declaring children (a version list\n\t\t\t flattened into the page's one menu grows without bound). The\n\t\t\t parent's own button above still renders alongside this chevron —\n\t\t\t a parent meant only to group children needs a real `handler` (or\n\t\t\t `href`/other type) of its own, or its button dispatches nothing. -->\n\t\t\t<NcActions\n\t\t\t\tv-if=\"hasChildren(entry)\"\n\t\t\t\t:key=\"`${entry.id}-children`\"\n\t\t\t\t:menuName=\"childMenuName(entry)\"\n\t\t\t\t:data-testid=\"`cn-action-children-${entry.id}`\">\n\t\t\t\t<template v-for=\"child in visibleChildren(entry)\">\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\tv-if=\"child.href\"\n\t\t\t\t\t\t:key=\"`${child.id}-link`\"\n\t\t\t\t\t\t:href=\"child.href\"\n\t\t\t\t\t\t:target=\"linkTargetOf(child)\"\n\t\t\t\t\t\t:data-testid=\"`cn-action-${child.id}`\"\n\t\t\t\t\t\t@click=\"onLinkClick(child, $event)\">\n\t\t\t\t\t\t<template v-if=\"child.icon\" #icon>\n\t\t\t\t\t\t\t<CnIcon v-if=\"isMdiIconName(child.icon)\" :name=\"child.icon\" :size=\"20\" />\n\t\t\t\t\t\t\t<span v-else :class=\"child.icon\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ tr(child.label) }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tv-else\n\t\t\t\t\t\t:key=\"child.id\"\n\t\t\t\t\t\t:disabled=\"Boolean(actionPending[child.id])\"\n\t\t\t\t\t\t:data-testid=\"`cn-action-${child.id}`\"\n\t\t\t\t\t\t@click=\"onActionClick(child)\">\n\t\t\t\t\t\t<template v-if=\"child.icon\" #icon>\n\t\t\t\t\t\t\t<CnIcon v-if=\"isMdiIconName(child.icon)\" :name=\"child.icon\" :size=\"20\" />\n\t\t\t\t\t\t\t<span v-else :class=\"child.icon\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ tr(child.label) }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcActions>\n\t\t</template>\n\n\t\t<!-- Overflow. Only ever rendered when `inline` is set, so a consumer\n\t\t that never asked for a limit keeps every action as a button. -->\n\t\t<NcActions\n\t\t\tv-if=\"overflowActions.length > 0\"\n\t\t\t:menuName=\"overflowOnly ? undefined : overflowMenuName\"\n\t\t\t:aria-label=\"overflowOnly ? overflowTriggerLabel : undefined\"\n\t\t\t:forceMenu=\"true\"\n\t\t\tdata-testid=\"cn-action-buttons-overflow\">\n\t\t\t<template v-for=\"entry in overflowActions\">\n\t\t\t\t<NcActionLink\n\t\t\t\t\tv-if=\"entry.href\"\n\t\t\t\t\t:key=\"`${entry.id}-link`\"\n\t\t\t\t\t:href=\"entry.href\"\n\t\t\t\t\t:target=\"linkTargetOf(entry)\"\n\t\t\t\t\t:data-testid=\"`cn-action-${entry.id}`\"\n\t\t\t\t\t@click=\"onLinkClick(entry, $event)\">\n\t\t\t\t\t<template v-if=\"entry.icon\" #icon>\n\t\t\t\t\t\t<CnIcon v-if=\"isMdiIconName(entry.icon)\" :name=\"entry.icon\" :size=\"20\" />\n\t\t\t\t\t\t<span v-else :class=\"entry.icon\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ tr(entry.label) }}\n\t\t\t\t</NcActionLink>\n\t\t\t\t<NcActionButton\n\t\t\t\t\tv-else\n\t\t\t\t\t:key=\"entry.id\"\n\t\t\t\t\t:disabled=\"Boolean(actionPending[entry.id])\"\n\t\t\t\t\t:data-testid=\"`cn-action-${entry.id}`\"\n\t\t\t\t\t@click=\"onActionClick(entry)\">\n\t\t\t\t\t<template v-if=\"entry.icon\" #icon>\n\t\t\t\t\t\t<CnIcon v-if=\"isMdiIconName(entry.icon)\" :name=\"entry.icon\" :size=\"20\" />\n\t\t\t\t\t\t<span v-else :class=\"entry.icon\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ tr(entry.label) }}\n\t\t\t\t</NcActionButton>\n\t\t\t</template>\n\t\t</NcActions>\n\n\t\t<!-- Confirm gate for a confirm:true action (reuses CnConfirmDialog). -->\n\t\t<CnConfirmDialog\n\t\t\tv-if=\"confirmEntry\"\n\t\t\tref=\"confirmDialog\"\n\t\t\t:dialogTitle=\"tr(confirmEntry.confirmTitle || confirmEntry.label)\"\n\t\t\t:message=\"tr(confirmEntry.confirmMessage) || defaultConfirmMessage\"\n\t\t\t:variant=\"confirmEntry.variant === 'error' ? 'error' : 'primary'\"\n\t\t\t@confirm=\"onConfirmProceed\"\n\t\t\t@close=\"confirmEntry = null\" />\n\n\t\t<!-- Schema-driven create dialog for an open-form action. The plain\n\t\t form is the default: the properties/JSON table is a power-user\n\t\t surface, and a header button that says \"New case\" is aimed at\n\t\t someone filing one, not at someone inspecting the schema. Set\n\t\t `advanced: true` on the action to get the table back. -->\n\t\t<CnFormDialog\n\t\t\tv-if=\"formEntry && formSchema && !formEntry.advanced\"\n\t\t\tref=\"formDialog\"\n\t\t\t:schema=\"formSchema\"\n\t\t\t:item=\"formItem\"\n\t\t\t:register=\"formRegister\"\n\t\t\t:initialData=\"formInitialValues || {}\"\n\t\t\t:dialogTitle=\"tr(formEntry.formTitle) || ''\"\n\t\t\t:eyebrow=\"tr(formEntry.formEyebrow) || ''\"\n\t\t\t:subtitle=\"tr(formEntry.formSubtitle) || ''\"\n\t\t\t:confirmLabel=\"tr(formEntry.formConfirmLabel) || ''\"\n\t\t\t:includeFields=\"formEntry.includeFields || null\"\n\t\t\t:excludeFields=\"formEntry.excludeFields || []\"\n\t\t\t:fieldOverrides=\"formEntry.fieldOverrides || {}\"\n\t\t\t:size=\"formEntry.size || 'normal'\"\n\t\t\t:columns=\"formEntry.columns || 1\"\n\t\t\t@confirm=\"onFormConfirm\"\n\t\t\t@close=\"closeForm\" />\n\t\t<CnAdvancedFormDialog\n\t\t\tv-if=\"formEntry && formSchema && formEntry.advanced\"\n\t\t\tref=\"formDialog\"\n\t\t\t:schema=\"formSchema\"\n\t\t\t:item=\"null\"\n\t\t\t:initialValues=\"formInitialValues\"\n\t\t\t:confirmLabel=\"tr(formEntry.formConfirmLabel) || ''\"\n\t\t\t@confirm=\"onFormConfirm\"\n\t\t\t@close=\"closeForm\" />\n\n\t\t<!-- run-node's config dialog (manifest-run-node-action): only mounted\n\t\t when the target node declared at least one field AND that\n\t\t describe call succeeded — an empty/no form runs immediately with\n\t\t no dialog at all (openRunNode below), and a describe FAILURE also\n\t\t skips the dialog (toasts instead) rather than showing an empty,\n\t\t confusing one. -->\n\t\t<CnRunNodeDialog\n\t\t\tv-if=\"runNodeEntry && runNodeFields.length\"\n\t\t\t:title=\"tr(runNodeEntry.formTitle) || tr(runNodeEntry.label)\"\n\t\t\t:fields=\"runNodeFields\"\n\t\t\t:loading=\"Boolean(actionPending[runNodeEntry.id])\"\n\t\t\t:translate=\"effectiveTranslate\"\n\t\t\t@confirm=\"onRunNodeConfirm\"\n\t\t\t@close=\"closeRunNode\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcActions, NcButton } from '@nextcloud/vue'\nimport { inject } from 'vue'\nimport CnConfirmDialog from '../../dialogs/CnConfirmDialog.vue'\nimport CnRunNodeDialog from '../../dialogs/CnRunNodeDialog.vue'\nimport { fetchEndpointSource } from '../../composables/useEndpointSource.js'\nimport { useObjectStore } from '../../store/useObjectStore.js'\nimport { actionLink, buildOnSuccessRoute, dispatchAction, interpolateActionTarget, isExternalActionTarget, postRunNode, resolveCreateOverrideHandler, resolveObjectOpType } from '../../utils/actionsDispatcher.js'\nimport { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'\nimport { answersByArrayKey, mergeValueArray, usesArrayValues, valueRecordsFor } from '../../utils/dynamicProperties.js'\nimport { followLinkClick } from '../../utils/linkNavigation.js'\nimport { resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\nimport { evaluateVisibleWhen } from '../../utils/visibleWhen.js'\nimport { CnAdvancedFormDialog } from '../CnAdvancedFormDialog/index.js'\nimport { CnFormDialog } from '../CnFormDialog/index.js'\nimport { CnIcon } from '../CnIcon/index.js'\n\n/**\n * CnActionButtons — declarative header-actions surface (#91 Wave 3).\n *\n * Renders a page's `headerActions[]` as buttons and owns the two action\n * behaviours that need STATE or a DIALOG the one-shot `dispatchAction`\n * can't provide on its own:\n *\n * - **`open-form`** — mounts the shared `CnAdvancedFormDialog` for the\n * action's `schema` (fetched through the object store), saves on\n * confirm, toasts, refreshes the page, and optionally navigates to\n * `onSuccessRoute`. Two optional keys cover the schemas a bare create\n * cannot satisfy: `props` seeds fixed field values into the create form\n * (so one schema can back several buttons, each fixing its own\n * discriminator), and `createOverride` names a registry handler that owns\n * the persist instead of `objectStore.saveObject` (for a schema requiring\n * a server-minted field the form cannot supply). `includeFields` /\n * `excludeFields` / `fieldOverrides` narrow what the button asks for, which\n * is how one schema serves both a full editor and a quick-create button.\n * `advanced: true` swaps in the properties/JSON table for a power user.\n * With `endpoint: { url, method?, loadUrl?, responsePath? }` and\n * `formProperties` (JSON-schema properties) it edits a record that is not a\n * register object instead: it reads the record from the endpoint, shows the\n * declared fields filled in, and sends them back with PATCH (or PUT). With\n * `method: \"POST\"` it creates one: an empty form, posted to the endpoint.\n * - **`toggle`** — a two-way state button: `GET`s `stateSource` on mount,\n * renders `labelOn` / `labelOff`, and on click `writes` the flipped\n * value OPTIMISTICALLY, reverting on failure.\n * - **`run-node`** (manifest-run-node-action) — GETs the target flow node's\n * describe endpoint (OpenRegister's `or-flow-run-node`); a node with\n * declared fields mounts `CnRunNodeDialog` (its own small field renderer —\n * see that component's docblock for why it is not `CnFlowNodeEditModal`'s),\n * a node with none runs immediately. Either way `postRunNode()`\n * (`actionsDispatcher.js`) makes the actual call, subject-token-resolved\n * exactly like `agent`.\n *\n * Every other type (`api-call`, `navigate`, `open-modal`, `open-page`,\n * `refresh`, `handler`) routes through the shared `dispatchAction` — with\n * a `CnConfirmDialog` gate first when the action sets `confirm: true`\n * (the object-op precedent: the dispatcher runs AFTER confirmation).\n *\n * Each action may carry a `visibleWhen` predicate (the shared banner\n * shape — `{ endpoint | source | field, op, value }`) evaluated against\n * the page / object context; a hidden action simply doesn't render.\n *\n * Placement: CnDashboardPage / CnDetailPage / CnPageRenderer mount this in\n * their header-actions area, passing the page's `headerActions[]`. The\n * object context (`{ objectId, object, register, schema }`) and the page\n * workspace / app-config bags are injected so tokens + local `visibleWhen`\n * resolve against the current record and page state.\n *\n * ```json\n * \"headerActions\": [\n * { \"id\": \"new-lead\", \"label\": \"New lead\", \"type\": \"open-form\",\n * \"register\": \"crm\", \"schema\": \"lead\", \"onSuccessRoute\": \"Leads\", \"variant\": \"primary\" },\n * { \"id\": \"approve\", \"label\": \"Approve\", \"type\": \"api-call\",\n * \"url\": \"/apps/shillinq/api/payment-runs/@objectId/approve\", \"confirm\": true,\n * \"visibleWhen\": { \"field\": \"state\", \"op\": \"eq\", \"value\": \"pending\" } },\n * { \"id\": \"toggle-open\", \"type\": \"toggle\", \"labelOn\": \"Open\", \"labelOff\": \"Closed\",\n * \"stateSource\": { \"url\": \"/apps/pipelinq/api/werkplek/@objectId/state\", \"responsePath\": \"open\" },\n * \"field\": \"open\", \"writeUrl\": \"/apps/pipelinq/api/werkplek/@objectId/state\", \"method\": \"PUT\" }\n * ]\n * ```\n */\nexport default {\n\tname: 'CnActionButtons',\n\n\tcomponents: { NcActions, NcActionButton, NcActionLink, NcButton, CnIcon, CnConfirmDialog, CnFormDialog, CnAdvancedFormDialog, CnRunNodeDialog },\n\n\tinject: {\n\t\t/** Detail-page object context (`{ objectId, object, register, schema }`). */\n\t\tcnObjectContext: { default: null },\n\t\t/** v2 slot-grid detail context holder (CnPageRenderer). */\n\t\tcnDetailObjectContext: { default: null },\n\t\t/** Page-level workspace context bag (reactive). */\n\t\tcnWorkspaceContext: { default: () => ({}) },\n\t\t/** Page-level app config bag (reactive). */\n\t\tcnAppConfig: { default: () => ({}) },\n\t\t/** Pre-bound dispatchAction from CnPageRenderer (router/registry/handlers/openModal wired). */\n\t\tcnDispatchAction: { default: null },\n\t\t/**\n\t\t * Host translate function provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). The\n\t\t * manifest-authored action labels, confirm copy and api-call toasts\n\t\t * are run through it. Defaults to an identity function so an\n\t\t * untranslated key renders as itself.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t\t/** v2 component registry, for resolving an `open-form` createOverride. */\n\t\tcnRegistry: { default: () => ({}) },\n\t\t/** Legacy customComponents map, same resolution fallback. */\n\t\tcnCustomComponents: { default: () => ({}) },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The page's declarative header actions. Each entry is a typed action\n\t\t * (`open-form` | `toggle` | `api-call` | `navigate` | `open-modal` |\n\t\t * `refresh` | `handler`) plus `id` / `label` and an optional\n\t\t * `visibleWhen` predicate, `icon`, `variant`, and `confirm`.\n\t\t *\n\t\t * An entry carrying `href` (with optional `target`) renders as a real\n\t\t * link instead — an anchor in the bar, an NcActionLink in a menu — and\n\t\t * is never dispatched. Use it for anything that ends in a URL, so the\n\t\t * browser keeps middle-click, \"open in new tab\" and the link semantics\n\t\t * assistive tech announces.\n\t\t *\n\t\t * `href` is used as given, so it must be a finished URL. A manifest\n\t\t * `headerActions[]` entry may carry `href` too: the page (CnDetailPage,\n\t\t * CnDashboardPage) resolves an instance path through `generateUrl()`\n\t\t * and sets `target` before the entry reaches this component.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\tactions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * How many collapsible actions stay as buttons; the rest fall into a\n\t\t * single `···` overflow menu.\n\t\t *\n\t\t * `0` (the default) renders EVERY action as a button, which is what\n\t\t * every existing consumer already gets — the overflow only ever\n\t\t * appears once a host asks for a limit.\n\t\t *\n\t\t * A `variant: \"primary\"` action, an action carrying `children`, and a\n\t\t * `toggle` are never collapsed and never consume one of the N slots:\n\t\t * NcActions paints all of its inline actions with one shared variant\n\t\t * (so a lone primary among them is not expressible), it cannot nest\n\t\t * another NcActions, and a toggle's state lives in its button.\n\t\t *\n\t\t * @type {number}\n\t\t */\n\t\tinline: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/**\n\t\t * Name for the overflow menu. Empty falls back to a translated\n\t\t * \"Actions\".\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\toverflowLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Every collapsible action in the `···` menu, none as a button: the\n\t\t * trigger is icon only and named by `overflowAriaLabel` (a card's\n\t\t * \"Actions for Volledigheid toetsen\").\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\toverflowOnly: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * The accessible name of the icon-only trigger under `overflowOnly`.\n\t\t * Empty falls back to the overflow label.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\toverflowAriaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Explicit Vue Router instance for `navigate` / `open-page` /\n\t\t * `onSuccessRoute`. Falls back to `this.$router`. Only needed for\n\t\t * standalone mounts outside a router tree.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\trouter: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Where the actions are drawn. `buttons` (the default) puts one\n\t\t * NcButton per action in the host's header. `menu` draws none and\n\t\t * emits `entries` instead, so the host can render them inside an\n\t\t * overflow menu while this component keeps owning the dialogs.\n\t\t *\n\t\t * @type {'buttons'|'menu'}\n\t\t */\n\t\tdisplay: {\n\t\t\ttype: String,\n\t\t\tdefault: 'buttons',\n\t\t\tvalidator: (v) => ['buttons', 'menu'].includes(v),\n\t\t},\n\t},\n\n\temits: ['created', 'entries'],\n\n\tsetup() {\n\t\t// Read the two detail-surface injects once so the object token context\n\t\t// resolves identically to the endpoint-bound widgets (Wave 3).\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst detailCtxRaw = inject('cnDetailObjectContext', null)\n\t\tconst workspaceRaw = inject('cnWorkspaceContext', null)\n\t\tconst appConfigRaw = inject('cnAppConfig', null)\n\t\treturn { objectCtxRaw, detailCtxRaw, workspaceRaw, appConfigRaw }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Evaluated visibleWhen outcome per action id (true when no predicate). */\n\t\t\tvisibility: {},\n\t\t\t/** Current boolean state per toggle action id. */\n\t\t\ttoggleState: {},\n\t\t\t/** In-flight flag per toggle action id (disables the button). */\n\t\t\ttogglePending: {},\n\t\t\t/** In-flight flag per non-toggle action id. */\n\t\t\tactionPending: {},\n\t\t\t/** The action awaiting confirmation (null = no dialog). */\n\t\t\tconfirmEntry: null,\n\t\t\t/** The open-form action currently showing its dialog (null = closed). */\n\t\t\tformEntry: null,\n\t\t\t/** The fetched schema object for the open-form dialog (null until loaded). */\n\t\t\tformSchema: null,\n\t\t\t/** The record an endpoint form edits (null for a create form). */\n\t\t\tformItem: null,\n\t\t\t/** The run-node action currently in flight / showing its dialog (null = closed). */\n\t\t\trunNodeEntry: null,\n\t\t\t/** The target node's declared config-form fields, from the describe GET. */\n\t\t\trunNodeFields: [],\n\t\t\t/** Default confirm-dialog message. */\n\t\t\tdefaultConfirmMessage: t('nextcloud-vue', 'Are you sure you want to continue?'),\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective translate function: the injected `cnTranslate` (the host\n\t\t * app's bound `t()`), identity by default.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key\n\t\t},\n\n\t\t/**\n\t\t * Create-form seed values for the open `open-form` action. An action\n\t\t * declares them as `props`, which is how a single schema backs several\n\t\t * buttons — \"New request\" and \"New complaint\" both open the `ticket`\n\t\t * form, each fixing its own `ticketType`.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tformInitialValues() {\n\t\t\tconst props = this.formEntry && this.formEntry.props\n\t\t\tif (!props || typeof props !== 'object' || Array.isArray(props)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Seed values go through the SAME token grammar as filters, so an\n\t\t\t// action on a detail page can stamp the record it belongs to:\n\t\t\t// `{ \"domainObjectRef\": \"@objectId\" }`. Without this the literal\n\t\t\t// string \"@objectId\" is saved, and a foreign key pointing at nothing\n\t\t\t// is a defect that only shows up in whatever reads it later.\n\t\t\treturn resolveFilterTokens(props, this.tokenCtx)\n\t\t},\n\n\t\t/**\n\t\t * The register the open dialog reads and writes against. The plain\n\t\t * form needs it to resolve `$ref` pickers into real dropdowns; without\n\t\t * one a reference field renders as an empty select and the action is\n\t\t * unusable for exactly the schemas that need it most.\n\t\t *\n\t\t * @return {string} The register slug, '' when none is resolvable.\n\t\t */\n\t\tformRegister() {\n\t\t\treturn (this.formEntry && this.formEntry.register) || this.objectCtx.register || ''\n\t\t},\n\n\t\t/**\n\t\t * The actions whose visibleWhen evaluated true (or carry no predicate).\n\t\t *\n\t\t * A `navigate` action whose target leaves the app is turned into a link\n\t\t * entry here, so it takes the `href` branch above instead of being\n\t\t * dispatched. An absolute URL is not a route: pushing one at the router\n\t\t * matched nothing and landed on the fallback page, carrying the URL's\n\t\t * own query string with it. The action's `target` (the URL) is replaced\n\t\t * by the anchor's `target`, since the two fields share a name and the\n\t\t * URL now lives in `href`. An author's explicit `href` still wins.\n\t\t *\n\t\t * An in-app `navigate` or `open-page` becomes a link too (see\n\t\t * `toLinkEntry`), keeping its own `target` so a fallback dispatch works.\n\t\t */\n\t\tvisibleActions() {\n\t\t\treturn (this.actions || [])\n\t\t\t\t.filter((a) => a && a.id && this.visibility[a.id] !== false)\n\t\t\t\t.map((a) => this.toLinkEntry(a))\n\t\t},\n\n\t\t/**\n\t\t * The actions rendered in the bar itself: every non-collapsible one,\n\t\t * plus the first `inline` collapsible ones. Declaration order is kept,\n\t\t * so promoting an action never reshuffles the row.\n\t\t *\n\t\t * Empty in `display: \"menu\"` — the host draws the entries itself there,\n\t\t * so this component renders no buttons and has nothing to cap.\n\t\t *\n\t\t * @return {Array<object>} Actions to render as buttons / chevrons.\n\t\t */\n\t\tbarActions() {\n\t\t\tif (this.display === 'menu') {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tif (this.overflowOnly) {\n\t\t\t\treturn this.visibleActions.filter((a) => !this.isCollapsible(a))\n\t\t\t}\n\t\t\tif (!(this.inline > 0)) {\n\t\t\t\treturn this.visibleActions\n\t\t\t}\n\t\t\tconst promoted = new Set(this.visibleActions\n\t\t\t\t.filter((a) => this.isCollapsible(a))\n\t\t\t\t.slice(0, this.inline)\n\t\t\t\t.map((a) => a.id))\n\t\t\treturn this.visibleActions.filter((a) => !this.isCollapsible(a) || promoted.has(a.id))\n\t\t},\n\n\t\t/**\n\t\t * The actions that did not fit, for the `···` menu. Always empty while\n\t\t * `inline` is 0, and in `display: \"menu\"` where the host owns the menu.\n\t\t *\n\t\t * @return {Array<object>} Actions to render as menu items.\n\t\t */\n\t\toverflowActions() {\n\t\t\tif (this.display === 'menu' || !(this.inline > 0 || this.overflowOnly)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst shown = new Set(this.barActions.map((a) => a.id))\n\t\t\treturn this.visibleActions.filter((a) => !shown.has(a.id))\n\t\t},\n\n\t\t/**\n\t\t * Name for the overflow trigger.\n\t\t *\n\t\t * @return {string} The label.\n\t\t */\n\t\toverflowMenuName() {\n\t\t\treturn this.tr(this.overflowLabel) || t('nextcloud-vue', 'Actions')\n\t\t},\n\n\t\t/**\n\t\t * Name of the icon-only trigger under `overflowOnly`.\n\t\t *\n\t\t * @return {string} The label.\n\t\t */\n\t\toverflowTriggerLabel() {\n\t\t\treturn this.overflowAriaLabel || this.overflowMenuName\n\t\t},\n\n\t\t/**\n\t\t * The visible actions flattened for a host that draws them as menu\n\t\t * items. Everything the item needs to render is resolved here — the\n\t\t * translated label a toggle currently shows, its pressed state, its\n\t\t * pending flag, and which of the two icon shapes applies — so the host\n\t\t * template stays a plain v-for and needs none of this component's\n\t\t * state. `run()` is pre-bound, the way CnIndexPage pre-binds a header\n\t\t * action's handler for the index bar, so the host dispatches without\n\t\t * reaching back through a ref.\n\t\t *\n\t\t * @return {Array<object>} One menu-ready descriptor per visible action.\n\t\t */\n\t\tmenuEntries() {\n\t\t\treturn this.visibleActions.map((entry) => {\n\t\t\t\tconst isToggle = entry.type === 'toggle'\n\t\t\t\tconst on = Boolean(this.toggleState[entry.id])\n\t\t\t\tconst label = isToggle\n\t\t\t\t\t? (on ? (entry.labelOn || entry.label) : (entry.labelOff || entry.label))\n\t\t\t\t\t: entry.label\n\t\t\t\treturn {\n\t\t\t\t\tid: entry.id,\n\t\t\t\t\tlabel: this.tr(label),\n\t\t\t\t\t// An mdi name goes to CnIcon; a legacy `icon-*` class goes\n\t\t\t\t\t// on a bare span. Deciding it here keeps isMdiIconName from\n\t\t\t\t\t// having to be duplicated into every host.\n\t\t\t\t\ticonName: this.isMdiIconName(entry.icon) ? entry.icon : '',\n\t\t\t\t\ticonClass: (entry.icon && !this.isMdiIconName(entry.icon)) ? entry.icon : '',\n\t\t\t\t\tdisabled: Boolean(isToggle ? this.togglePending[entry.id] : this.actionPending[entry.id]),\n\t\t\t\t\t// null, not false: a non-toggle carries no pressed state at\n\t\t\t\t\t// all, and `aria-pressed=\"false\"` would announce every\n\t\t\t\t\t// action as an un-pressed toggle button.\n\t\t\t\t\tpressed: isToggle ? on : null,\n\t\t\t\t\ttestid: isToggle ? `cn-action-toggle-${entry.id}` : `cn-action-${entry.id}`,\n\t\t\t\t\t// A link entry, for a host to render as an anchor rather\n\t\t\t\t\t// than a button with a click handler. Named `linkTarget`\n\t\t\t\t\t// because an action's own `target` is its destination. An in-app\n\t\t\t\t\t// link (`linkTo`) is left to `run()`: a host anchor has no router.\n\t\t\t\t\thref: (!entry.linkTo && entry.href) || '',\n\t\t\t\t\tlinkTarget: (!entry.linkTo && entry.href) ? (entry.target || '_blank') : '',\n\t\t\t\t\trun: () => (isToggle ? this.onToggleClick(entry) : this.onActionClick(entry)),\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/** Unwrapped page workspace bag. */\n\t\tworkspaceCtx() {\n\t\t\tconst c = this.workspaceRaw\n\t\t\tconst v = (c && typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t\treturn (v && typeof v === 'object') ? v : {}\n\t\t},\n\n\t\t/** Unwrapped app-config bag. */\n\t\tconfigCtx() {\n\t\t\tconst c = this.appConfigRaw\n\t\t\tconst v = (c && typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t\treturn (v && typeof v === 'object') ? v : {}\n\t\t},\n\n\t\t/** The merged object token context (both detail-surface injects). */\n\t\tobjectCtx() {\n\t\t\treturn resolveObjectTokenContext(this.objectCtxRaw, this.detailCtxRaw) || {}\n\t\t},\n\n\t\t/** The full token context for url/param interpolation + local visibleWhen. */\n\t\ttokenCtx() {\n\t\t\treturn {\n\t\t\t\t...this.objectCtx,\n\t\t\t\tworkspace: this.workspaceCtx,\n\t\t\t\tconfig: this.configCtx,\n\t\t\t}\n\t\t},\n\n\t\t/** The effective router (explicit prop wins, else this.$router). */\n\t\teffectiveRouter() {\n\t\t\treturn this.router || this.$router || null\n\t\t},\n\t},\n\n\twatch: {\n\t\tactions: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.evaluateVisibility()\n\t\t\t\tthis.initToggles()\n\t\t\t},\n\t\t},\n\n\t\t// Re-evaluate local (object-context) visibleWhen when the record loads.\n\t\tobjectCtx: {\n\t\t\tdeep: true,\n\t\t\thandler() { this.evaluateVisibility() },\n\t\t},\n\n\t\t// Push the menu-ready descriptors at a `display: \"menu\"` host. It fires\n\t\t// immediately so the host has the list on first paint, and again on\n\t\t// every change of visibility, toggle state or pending flag — the three\n\t\t// things that would otherwise leave a stale item in an open menu.\n\t\tmenuEntries: {\n\t\t\timmediate: true,\n\t\t\thandler(entries) {\n\t\t\t\tif (this.display === 'menu') {\n\t\t\t\t\t/**\n\t\t\t\t\t * @event entries Emitted in `display: \"menu\"` only, whenever the visible actions, their toggle state or their pending flags change. Payload: one menu-ready descriptor per visible action, each carrying `id`, `label`, `iconName`, `iconClass`, `disabled`, `pressed`, `testid` and a pre-bound `run()`.\n\t\t\t\t\t */\n\t\t\t\t\tthis.$emit('entries', entries)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Heuristic MDI-name check (an `icon-`-prefixed string is a CSS class).\n\t\t *\n\t\t * @param {string} name The icon string.\n\t\t * @return {boolean} True when it should render via CnIcon.\n\t\t */\n\t\tisMdiIconName(name) {\n\t\t\treturn typeof name === 'string' && name !== '' && !name.startsWith('icon-')\n\t\t},\n\n\t\t/**\n\t\t * Evaluate every action's `visibleWhen` against the current context.\n\t\t * Fail-safe (a broken predicate hides its action); actions without a\n\t\t * predicate are always visible.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync evaluateVisibility() {\n\t\t\t// Children are walked too: a sub-action whose predicate was never\n\t\t\t// evaluated would read as visible and render regardless.\n\t\t\tconst flat = []\n\t\t\tfor (const action of this.actions || []) {\n\t\t\t\tif (!action || !action.id) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tflat.push(action)\n\t\t\t\tif (Array.isArray(action.children)) {\n\t\t\t\t\tflat.push(...action.children.filter((c) => c && c.id))\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const action of flat) {\n\t\t\t\tif (!action.visibleWhen) {\n\t\t\t\t\tthis.visibility[action.id] = true\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst ok = await evaluateVisibleWhen(action.visibleWhen, this.tokenCtx)\n\t\t\t\tthis.visibility[action.id] = ok\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Seed each toggle action's state from its `stateSource` endpoint\n\t\t * (fetched through the shared endpoint engine so `@objectId` etc.\n\t\t * resolve). No-op for non-toggle actions.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync initToggles() {\n\t\t\tfor (const action of this.actions || []) {\n\t\t\t\tif (!action || action.type !== 'toggle' || !action.id) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tthis.toggleState[action.id] = false\n\t\t\t\tif (!action.stateSource || !action.stateSource.url) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst payload = await fetchEndpointSource(action.stateSource, this.tokenCtx)\n\t\t\t\t\tconst value = action.field ? this.readField(payload, action.field) : payload\n\t\t\t\t\tthis.toggleState[action.id] = Boolean(value)\n\t\t\t\t} catch {\n\t\t\t\t\t// Leave the default (off); a failed state read never breaks the bar.\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Whether an action may be pushed into the overflow menu. See the\n\t\t * `inline` prop for why these three shapes never can be.\n\t\t *\n\t\t * @param {object} entry The action descriptor.\n\t\t * @return {boolean} True when it can collapse.\n\t\t */\n\t\tisCollapsible(entry) {\n\t\t\treturn !this.hasChildren(entry)\n\t\t\t\t&& entry.variant !== 'primary'\n\t\t\t\t&& entry.type !== 'toggle'\n\t\t},\n\n\t\t/**\n\t\t * Whether an action declares sub-actions worth a chevron.\n\t\t *\n\t\t * @param {object} entry The action descriptor.\n\t\t * @return {boolean} True when it has at least one visible child.\n\t\t */\n\t\thasChildren(entry) {\n\t\t\treturn this.visibleChildren(entry).length > 0\n\t\t},\n\n\t\t/**\n\t\t * An action's sub-actions, minus any hidden by `visibleWhen`.\n\t\t *\n\t\t * @param {object} entry The action descriptor.\n\t\t * @return {Array<object>} The renderable children.\n\t\t */\n\t\tvisibleChildren(entry) {\n\t\t\tif (!Array.isArray(entry?.children)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn entry.children\n\t\t\t\t.filter((c) => c && c.id && this.visibility[c.id] !== false)\n\t\t\t\t.map((c) => this.toLinkEntry(c))\n\t\t},\n\n\t\t/**\n\t\t * Give a navigating action the `href` it renders as. An external\n\t\t * `navigate` target becomes the href with a `_blank` anchor target. An\n\t\t * in-app `navigate` / `open-page` gets the router-resolved href plus\n\t\t * `linkTo`, the location a plain click routes to; one with `confirm`,\n\t\t * an `onSelect` function, or an unresolvable route stays a button.\n\t\t *\n\t\t * @param {object} a The action descriptor.\n\t\t * @return {object} The descriptor, possibly with `href` / `linkTo` added.\n\t\t */\n\t\ttoLinkEntry(a) {\n\t\t\tif (a.href || (a.type !== 'navigate' && a.type !== 'open-page')) {\n\t\t\t\treturn a\n\t\t\t}\n\t\t\tlet entry = a\n\t\t\tif (a.type === 'navigate') {\n\t\t\t\t// Interpolated BEFORE the external test and before it becomes an\n\t\t\t\t// href, so the anchor carries the record's id rather than the token.\n\t\t\t\tconst target = interpolateActionTarget(a.target, this.tokenCtx)\n\t\t\t\tif (isExternalActionTarget(target)) {\n\t\t\t\t\treturn { ...a, href: target, target: '_blank' }\n\t\t\t\t}\n\t\t\t\tentry = target === a.target ? a : { ...a, target }\n\t\t\t}\n\t\t\tif (typeof entry.onSelect === 'function') {\n\t\t\t\treturn entry\n\t\t\t}\n\t\t\tconst link = actionLink(entry, { router: this.effectiveRouter })\n\t\t\treturn link ? { ...entry, href: link.href, linkTo: link.to } : entry\n\t\t},\n\n\t\t/**\n\t\t * The anchor `target` of a link entry. An in-app link opens in place;\n\t\t * its own `target` is the destination, not an anchor target.\n\t\t *\n\t\t * @param {object} entry The link entry.\n\t\t * @return {string|undefined} The anchor target.\n\t\t */\n\t\tlinkTargetOf(entry) {\n\t\t\treturn entry.linkTo ? undefined : (entry.target || undefined)\n\t\t},\n\n\t\t/**\n\t\t * Click on a link entry: a plain click on an in-app link routes in\n\t\t * place; a URL or a modified click is left to the browser.\n\t\t *\n\t\t * @param {object} entry The link entry.\n\t\t * @param {MouseEvent} event The click event.\n\t\t * @return {void}\n\t\t */\n\t\tonLinkClick(entry, event) {\n\t\t\tif (entry.linkTo) {\n\t\t\t\tfollowLinkClick(event, entry.linkTo, this.effectiveRouter)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Name for a sub-action chevron. `childrenLabel` when set, else the\n\t\t * parent's own label, so the dropdown is never announced unlabelled.\n\t\t *\n\t\t * @param {object} entry The action descriptor.\n\t\t * @return {string} The menu name.\n\t\t */\n\t\tchildMenuName(entry) {\n\t\t\treturn this.tr(entry.childrenLabel) || this.tr(entry.label) || ''\n\t\t},\n\n\t\t/**\n\t\t * Read a dot-path off a payload (the toggle state field).\n\t\t *\n\t\t * @param {unknown} data The payload.\n\t\t * @param {string} field The dot-path.\n\t\t * @return {unknown} The value at the path.\n\t\t */\n\t\treadField(data, field) {\n\t\t\tif (!field) {\n\t\t\t\treturn data\n\t\t\t}\n\t\t\treturn String(field).split('.').reduce((o, k) => (o === null || o === undefined ? o : o[k]), data)\n\t\t},\n\n\t\t/**\n\t\t * Toggle click: flip the state OPTIMISTICALLY, write it, and revert on\n\t\t * failure. Success/error toast via the api-call dispatch path (which\n\t\t * also refreshes the page unless `refresh: false`).\n\t\t *\n\t\t * @param {object} entry The toggle action.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onToggleClick(entry) {\n\t\t\tif (this.togglePending[entry.id]) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst previous = Boolean(this.toggleState[entry.id])\n\t\t\tconst next = !previous\n\t\t\tthis.toggleState[entry.id] = next\n\t\t\tthis.togglePending[entry.id] = true\n\t\t\tconst writeParams = { ...(entry.params || {}) }\n\t\t\tif (entry.field) {\n\t\t\t\twriteParams[entry.field] = next\n\t\t\t}\n\t\t\tconst result = await this.dispatch({\n\t\t\t\ttype: 'api-call',\n\t\t\t\turl: entry.writeUrl,\n\t\t\t\tmethod: entry.method || 'PUT',\n\t\t\t\tparams: writeParams,\n\t\t\t\tsuccessMessage: entry.successMessage,\n\t\t\t\terrorMessage: entry.errorMessage,\n\t\t\t\trefresh: entry.refresh,\n\t\t\t})\n\t\t\tif (!result || result.ok === false) {\n\t\t\t\t// Revert the optimistic flip.\n\t\t\t\tthis.toggleState[entry.id] = previous\n\t\t\t}\n\t\t\tthis.togglePending[entry.id] = false\n\t\t},\n\n\t\t/**\n\t\t * Non-toggle action click: gate on `confirm` first, else run.\n\t\t *\n\t\t * @param {object} entry The action.\n\t\t * @return {void}\n\t\t */\n\t\tonActionClick(entry) {\n\t\t\tif (entry.confirm === true) {\n\t\t\t\tthis.confirmEntry = entry\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.runAction(entry)\n\t\t},\n\n\t\t/**\n\t\t * The confirm dialog's proceed handler: run the pending action, then\n\t\t * report the outcome back to the dialog's result phase.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onConfirmProceed() {\n\t\t\tconst entry = this.confirmEntry\n\t\t\tif (!entry) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst result = await this.runAction(entry)\n\t\t\tconst dialog = this.$refs.confirmDialog\n\t\t\tif (dialog && typeof dialog.setResult === 'function') {\n\t\t\t\tconst ok = result === undefined || (result && result.ok !== false)\n\t\t\t\tdialog.setResult(ok ? { success: true } : { error: t('nextcloud-vue', 'Action failed.') })\n\t\t\t} else {\n\t\t\t\tthis.confirmEntry = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Run an action (post-confirmation). `open-form` opens the schema\n\t\t * dialog; everything else dispatches. Returns the dispatch result so\n\t\t * the confirm dialog can report success/failure.\n\t\t *\n\t\t * @param {object} entry The action.\n\t\t * @return {Promise<unknown>} The dispatch result (undefined for open-form).\n\t\t */\n\t\tasync runAction(entry) {\n\t\t\t// A function on the descriptor, for a PARENT COMPONENT composing\n\t\t\t// entries in JS (CnDetailPage folds its own Edit in this way).\n\t\t\t// Manifest JSON cannot carry one, so the declarative path is\n\t\t\t// untouched — and it runs after the confirm gate, like every\n\t\t\t// dispatched type.\n\t\t\tif (typeof entry.onSelect === 'function') {\n\t\t\t\tentry.onSelect(entry)\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tif (entry.type === 'open-form') {\n\t\t\t\tawait this.openForm(entry)\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tif (entry.type === 'run-node') {\n\t\t\t\tawait this.openRunNode(entry)\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tthis.actionPending[entry.id] = true\n\t\t\tconst result = await this.dispatch(entry)\n\t\t\tthis.actionPending[entry.id] = false\n\t\t\treturn result\n\t\t},\n\n\t\t/**\n\t\t * Resolve a manifest-authored UI string through the host translate\n\t\t * function. A pure pass-through: an empty value, or a catalogue that\n\t\t * lacks the key, returns the input unchanged.\n\t\t *\n\t\t * @param {string} [value] The English source string.\n\t\t * @return {string|undefined} The translated (or source) string.\n\t\t */\n\t\ttr(value) {\n\t\t\treturn value ? this.effectiveTranslate(value) : value\n\t\t},\n\n\t\t/**\n\t\t * Route an action through the shared dispatcher — the pre-bound\n\t\t * `cnDispatchAction` (router/registry/handlers/openModal already\n\t\t * wired) when available, else a direct call with a minimal context.\n\t\t * The token context and the host translate function are always\n\t\t * merged in (the latter localises `api-call` success/error toasts).\n\t\t *\n\t\t * @param {object} action The action to dispatch.\n\t\t * @return {Promise<unknown>} The dispatch result.\n\t\t */\n\t\tdispatch(action) {\n\t\t\tconst extra = { tokenCtx: this.tokenCtx, translate: this.effectiveTranslate }\n\t\t\tif (typeof this.cnDispatchAction === 'function') {\n\t\t\t\treturn this.cnDispatchAction(action, extra)\n\t\t\t}\n\t\t\treturn dispatchAction(action, {\n\t\t\t\trouter: this.effectiveRouter,\n\t\t\t\topenForm: (a) => this.openForm(a),\n\t\t\t\topenRunNode: (a) => this.openRunNode(a),\n\t\t\t\t...extra,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Open the schema-driven create dialog for an `open-form` action:\n\t\t * fetch the schema object through the shared object store, then mount\n\t\t * CnAdvancedFormDialog. A failed schema load toasts and no-ops.\n\t\t *\n\t\t * @param {object} entry The open-form action (`register`, `schema`).\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync openForm(entry) {\n\t\t\tthis.formEntry = entry\n\t\t\tthis.formSchema = null\n\t\t\tthis.formItem = null\n\t\t\tif (entry.endpoint && typeof entry.endpoint.url === 'string' && entry.endpoint.url !== '') {\n\t\t\t\tawait this.openEndpointForm(entry)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst register = entry.register || this.objectCtx.register || ''\n\t\t\tconst schema = entry.schema || ''\n\t\t\tif (!schema) {\n\t\t\t\tthis.formEntry = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst store = useObjectStore()\n\t\t\t\tconst type = resolveObjectOpType(store, { register, schema })\n\t\t\t\tthis.formSchema = await store.fetchSchema(type)\n\t\t\t} catch {\n\t\t\t\tthis.formSchema = null\n\t\t\t}\n\t\t\tif (!this.formSchema) {\n\t\t\t\tconst { showError } = await import('@nextcloud/dialogs')\n\t\t\t\tif (typeof showError === 'function') {\n\t\t\t\t\tshowError(t('nextcloud-vue', 'Could not open the form.'))\n\t\t\t\t}\n\t\t\t\tthis.formEntry = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Open an `open-form` action with an `endpoint` as an edit form over a\n\t\t * record that is not a register object (an OpenRegister organisation):\n\t\t * the fields are the action's `formProperties`, the values are read from\n\t\t * `endpoint.loadUrl` (else `endpoint.url`) at `endpoint.responsePath`,\n\t\t * and the save sends the changed fields to `endpoint.url`. A failed\n\t\t * read toasts and opens nothing.\n\t\t *\n\t\t * @param {object} entry The open-form action with `endpoint` and `formProperties`.\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/open-form-endpoint/specs/open-form-endpoint/spec.md#requirement-an-open-form-action-can-edit-a-record-through-an-endpoint\n\t\t */\n\t\tasync openEndpointForm(entry) {\n\t\t\tconst properties = entry.formProperties && typeof entry.formProperties === 'object' && !Array.isArray(entry.formProperties) ? entry.formProperties : {}\n\t\t\tif (String(entry.endpoint.method || '').toUpperCase() === 'POST') {\n\t\t\t\tthis.formSchema = { type: 'object', properties }\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst loadUrl = entry.endpoint.loadUrl || entry.endpoint.url\n\t\t\tconst record = await fetchEndpointSource({ url: loadUrl, responsePath: entry.endpoint.responsePath || '' }, this.tokenCtx, { force: true })\n\t\t\t\t.catch(() => null)\n\t\t\tif (!record || typeof record !== 'object') {\n\t\t\t\tconst { showError } = await import('@nextcloud/dialogs')\n\t\t\t\tif (typeof showError === 'function') {\n\t\t\t\t\tshowError(t('nextcloud-vue', 'Could not open the form.'))\n\t\t\t\t}\n\t\t\t\tthis.formEntry = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.formItem = record\n\t\t\tthis.formSchema = { type: 'object', properties }\n\t\t},\n\n\t\t/**\n\t\t * Save an endpoint form: send the declared fields to `endpoint.url`\n\t\t * with `endpoint.method` (`PATCH` by default, or `PUT`), then toast and\n\t\t * refresh the page as an object save does.\n\t\t *\n\t\t * @param {object} entry The open-form action.\n\t\t * @param {object} formData The form's values.\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/open-form-endpoint/specs/open-form-endpoint/spec.md#requirement-an-open-form-action-can-edit-a-record-through-an-endpoint\n\t\t */\n\t\tasync saveEndpointForm(entry, formData) {\n\t\t\tconst dialog = this.$refs.formDialog\n\t\t\tconst keys = Object.keys(this.formSchema?.properties || {})\n\t\t\tconst body = Object.fromEntries(keys.filter((key) => key in formData).map((key) => [key, formData[key]]))\n\t\t\tconst verb = String(entry.endpoint.method || 'PATCH').toUpperCase()\n\t\t\tconst method = verb === 'PUT' ? 'put' : (verb === 'POST' ? 'post' : 'patch')\n\t\t\ttry {\n\t\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\t\timport('@nextcloud/router'),\n\t\t\t\t])\n\t\t\t\tconst url = interpolateActionTarget(entry.endpoint.url, this.tokenCtx)\n\t\t\t\tconst response = await axios[method](/^https?:\\/\\//i.test(url) ? url : generateUrl(url), body)\n\t\t\t\tif (dialog && typeof dialog.setResult === 'function') {\n\t\t\t\t\tdialog.setResult({ success: true })\n\t\t\t\t}\n\t\t\t\tconst { showSuccess } = await import('@nextcloud/dialogs')\n\t\t\t\tif (typeof showSuccess === 'function') {\n\t\t\t\t\tshowSuccess(this.tr(entry.successMessage) || t('nextcloud-vue', 'Saved.'))\n\t\t\t\t}\n\t\t\t\tconst { emit } = await import('@nextcloud/event-bus')\n\t\t\t\temit('cn:page:refresh', {})\n\t\t\t\tthis.$emit('created', response && response.data)\n\t\t\t} catch (error) {\n\t\t\t\tconst serverMessage = error && error.response && error.response.data\n\t\t\t\t\t&& (error.response.data.error || error.response.data.message)\n\t\t\t\tif (dialog && typeof dialog.setResult === 'function') {\n\t\t\t\t\tdialog.setResult({ error: serverMessage || this.tr(entry.errorMessage) || t('nextcloud-vue', 'Save failed.') })\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Open (or skip) the `run-node` config dialog: GET the target node's\n\t\t * describe endpoint (`or-flow-run-node`'s `GET .../{flowId}/{nodeId}/run`),\n\t\t * and either mount `CnRunNodeDialog` with its declared fields, or —\n\t\t * for a node with none — run immediately with an empty config. A\n\t\t * failed describe call toasts and does neither (RN-4: this resolves\n\t\t * as soon as the decision is made, never waiting on the person).\n\t\t *\n\t\t * @param {object} entry The run-node action (`flowId`, `nodeId`, ...).\n\t\t * @return {Promise<void>}\n\t\t *\n\t\t * @spec openspec/changes/manifest-run-node-action/specs/manifest-run-node-action/spec.md#requirement-the-nodes-own-config-form-drives-the-dialog-not-a-new-token\n\t\t */\n\t\tasync openRunNode(entry) {\n\t\t\tconst flowId = entry.flowId\n\t\t\tconst nodeId = entry.nodeId\n\t\t\tif (!flowId || !nodeId) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn('[CnActionButtons] run-node requires flowId and nodeId.', entry)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// The try assigns it and the catch returns, so an initialiser here\n\t\t\t// would never be read.\n\t\t\tlet fields\n\t\t\ttry {\n\t\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\t\timport('@nextcloud/router'),\n\t\t\t\t])\n\t\t\t\tconst url = generateUrl(`/apps/openregister/api/flows/${encodeURIComponent(flowId)}/nodes/${encodeURIComponent(nodeId)}/run`)\n\t\t\t\tconst response = await axios.get(url)\n\t\t\t\tfields = Array.isArray(response.data && response.data.configForm) ? response.data.configForm : []\n\t\t\t} catch (error) {\n\t\t\t\tconst { showError } = await import('@nextcloud/dialogs')\n\t\t\t\tconst serverMessage = error && error.response && error.response.data\n\t\t\t\t\t&& (error.response.data.error || error.response.data.message)\n\t\t\t\tif (typeof showError === 'function') {\n\t\t\t\t\tshowError(serverMessage || t('nextcloud-vue', 'Could not open this action.'))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (!fields.length) {\n\t\t\t\t// Empty (or undeclared) form: the \"empty form is itself\n\t\t\t\t// information\" rule flow-node-config-forms already established\n\t\t\t\t// for the editor — run immediately, no dialog.\n\t\t\t\tthis.actionPending[entry.id] = true\n\t\t\t\tawait this.runNodePost(entry, {})\n\t\t\t\tthis.actionPending[entry.id] = false\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.runNodeFields = fields\n\t\t\tthis.runNodeEntry = entry\n\t\t},\n\n\t\t/**\n\t\t * The run-node dialog's confirm: POST the collected config, close the\n\t\t * dialog either way (success or failure both end the interaction —\n\t\t * a failure already toasted its own message).\n\t\t *\n\t\t * @param {object} config The dialog's collected field values.\n\t\t * @return {Promise<void>}\n\t\t *\n\t\t * @spec openspec/changes/manifest-run-node-action/specs/manifest-run-node-action/spec.md#requirement-a-run-node-action-invokes-one-flow-node-against-the-page-object\n\t\t */\n\t\tasync onRunNodeConfirm(config) {\n\t\t\tconst entry = this.runNodeEntry\n\t\t\tif (!entry) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.actionPending[entry.id] = true\n\t\t\tawait this.runNodePost(entry, config)\n\t\t\tthis.actionPending[entry.id] = false\n\t\t\tthis.closeRunNode()\n\t\t},\n\n\t\t/**\n\t\t * POST the run-node call through the shared dispatcher's `postRunNode`\n\t\t * (subject/register/schema resolution, fail-closed, toast + refresh —\n\t\t * see its own docblock in actionsDispatcher.js).\n\t\t *\n\t\t * @param {object} entry The run-node action.\n\t\t * @param {object} config The node's collected config.\n\t\t * @return {Promise<{ok: boolean}>}\n\t\t */\n\t\trunNodePost(entry, config) {\n\t\t\treturn postRunNode(entry, { tokenCtx: this.tokenCtx, translate: this.effectiveTranslate }, config)\n\t\t},\n\n\t\t/** Close the run-node dialog without running anything. */\n\t\tcloseRunNode() {\n\t\t\tthis.runNodeEntry = null\n\t\t\tthis.runNodeFields = []\n\t\t},\n\n\t\t/**\n\t\t * The create dialog's confirm: save the new object through the shared\n\t\t * object store, report the result phase, toast, refresh the page, and\n\t\t * navigate to `onSuccessRoute` when set — merging the saved object's id\n\t\t * into the route params (via `buildOnSuccessRoute`) so the navigation\n\t\t * can deep-link to the created object's detail page.\n\t\t *\n\t\t * @param {object} formData The dialog's form payload.\n\t\t * @param {object|null} [dynamic] The answers to the schema's data-driven questions (`{ answers, declarations }`), when it has any.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onFormConfirm(formData, dynamic = null) {\n\t\t\tconst entry = this.formEntry\n\t\t\tif (entry && entry.endpoint) {\n\t\t\t\tawait this.saveEndpointForm(entry, formData)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst dialog = this.$refs.formDialog\n\t\t\tconst register = this.formRegister\n\t\t\tconst schema = (entry && entry.schema) || ''\n\t\t\ttry {\n\t\t\t\tconst store = useObjectStore()\n\t\t\t\tconst type = resolveObjectOpType(store, { register, schema })\n\t\t\t\t// A schema can require a field the form cannot supply — a\n\t\t\t\t// server-minted foreign key, say — in which case a straight\n\t\t\t\t// saveObject 400s. `createOverride` names a registry handler\n\t\t\t\t// that owns the persist instead (the CnIndexPage precedent).\n\t\t\t\tconst override = resolveCreateOverrideHandler(\n\t\t\t\t\tentry && entry.createOverride,\n\t\t\t\t\tthis.cnRegistry,\n\t\t\t\t\tthis.cnCustomComponents,\n\t\t\t\t)\n\t\t\t\tconst payload = { ...formData }\n\t\t\t\t// Array-mode declarations fold their answers into the parent's\n\t\t\t\t// own payload, so the whole record saves in ONE write. The\n\t\t\t\t// child-record mode below cannot do that (the rows need the\n\t\t\t\t// parent's id), which is why it writes afterwards and can leave\n\t\t\t\t// answers behind if that second write fails.\n\t\t\t\tthis.foldArrayAnswers(payload, dynamic)\n\t\t\t\tconst saved = override\n\t\t\t\t\t? await override(payload, { register, schema, type })\n\t\t\t\t\t: await store.saveObject(type, payload)\n\t\t\t\tif (!saved) {\n\t\t\t\t\tthrow new Error('save rejected')\n\t\t\t\t}\n\t\t\t\tawait this.saveDynamicAnswers(store, register, dynamic, saved)\n\t\t\t\tif (dialog && typeof dialog.setResult === 'function') {\n\t\t\t\t\tdialog.setResult({ success: true })\n\t\t\t\t}\n\t\t\t\tconst { showSuccess } = await import('@nextcloud/dialogs')\n\t\t\t\tif (typeof showSuccess === 'function') {\n\t\t\t\t\tshowSuccess((entry && entry.successMessage) || t('nextcloud-vue', 'Saved.'))\n\t\t\t\t}\n\t\t\t\tconst { emit } = await import('@nextcloud/event-bus')\n\t\t\t\temit('cn:page:refresh', {})\n\t\t\t\t/**\n\t\t\t\t * @event created Emitted after an open-form action saves. Payload: the created object.\n\t\t\t\t */\n\t\t\t\tthis.$emit('created', saved)\n\t\t\t\tif (entry && entry.onSuccessRoute && this.effectiveRouter) {\n\t\t\t\t\tconst location = buildOnSuccessRoute(entry.onSuccessRoute, saved)\n\t\t\t\t\tif (location) {\n\t\t\t\t\t\tthis.effectiveRouter.push(location).catch(() => {})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tif (dialog && typeof dialog.setResult === 'function') {\n\t\t\t\t\tdialog.setResult({ error: (e && e.message) || t('nextcloud-vue', 'Save failed.') })\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Write the answers to a schema's data-driven questions, once the\n\t\t * object they belong to exists.\n\t\t *\n\t\t * This runs AFTER the parent save and deliberately does not roll it\n\t\t * back on failure: a case that exists without one of its custom values\n\t\t * is recoverable by editing it, whereas discarding a case the user\n\t\t * believes they filed is not. A failure here surfaces as the dialog's\n\t\t * error so it is never silent.\n\t\t *\n\t\t * @param {object} store The object store.\n\t\t * @param {string} register The register the object was saved in.\n\t\t * @param {object|null} dynamic The `{ answers, declarations }` payload.\n\t\t * @param {object} saved The saved parent object.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync saveDynamicAnswers(store, register, dynamic, saved) {\n\t\t\tif (!dynamic || !Array.isArray(dynamic.answers) || dynamic.answers.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst objectId = saved && (saved.id || saved.uuid)\n\t\t\tif (!objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst declarations = dynamic.declarations || []\n\t\t\tfor (const { key, config } of declarations) {\n\t\t\t\tconst values = config && config.values\n\t\t\t\tif (!values || !values.schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\t// Already written as part of the parent payload.\n\t\t\t\tif (usesArrayValues(config)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\t// An answer belongs to exactly one declaration. With a single\n\t\t\t\t// declaration every answer carries its key anyway; the filter\n\t\t\t\t// only matters once a schema has two, where writing an answer\n\t\t\t\t// to both value schemas would invent a row nobody entered.\n\t\t\t\tconst mine = declarations.length === 1\n\t\t\t\t\t? dynamic.answers\n\t\t\t\t\t: dynamic.answers.filter((a) => a.declarationKey === key)\n\t\t\t\tconst rows = valueRecordsFor(mine, config, objectId)\n\t\t\t\tif (rows.length === 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst valueRegister = values.register || register\n\t\t\t\tconst type = resolveObjectOpType(store, { register: valueRegister, schema: values.schema })\n\t\t\t\tfor (const row of rows) {\n\t\t\t\t\tawait store.saveObject(type, row)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Fold array-mode dynamic answers into the parent payload.\n\t\t *\n\t\t * @param {object} payload The parent object payload, mutated in place.\n\t\t * @param {object|null} dynamic The `{ answers, declarations }` payload.\n\t\t * @return {void}\n\t\t */\n\t\tfoldArrayAnswers(payload, dynamic) {\n\t\t\tif (!dynamic || !Array.isArray(dynamic.answers) || dynamic.answers.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst declarations = dynamic.declarations || []\n\t\t\tfor (const { key, config } of declarations) {\n\t\t\t\tif (!usesArrayValues(config)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst mine = declarations.length === 1\n\t\t\t\t\t? dynamic.answers\n\t\t\t\t\t: dynamic.answers.filter((a) => a.declarationKey === key)\n\t\t\t\t// Each answer goes to the array its definition is routed to\n\t\t\t\t// (`values.arrayKeyBy`), merged over what that array holds.\n\t\t\t\tfor (const [arrayKey, subset] of Object.entries(answersByArrayKey(mine, config, dynamic.definitions || []))) {\n\t\t\t\t\tconst entries = mergeValueArray(payload[arrayKey], subset, config, dynamic.definitions || [])\n\t\t\t\t\tif (entries.length || Array.isArray(payload[arrayKey])) {\n\t\t\t\t\t\tpayload[arrayKey] = entries\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Close the create dialog.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tcloseForm() {\n\t\t\tthis.formEntry = null\n\t\t\tthis.formSchema = null\n\t\t\tthis.formItem = null\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-action-buttons {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 8px;\n\tflex-wrap: wrap;\n}\n\n/* In menu mode the only children are dialogs, so the wrapper must not be a\n box: an empty inline-flex child still eats one of the header's flex gaps\n and pushes the Actions menu away from the Edit button beside it. */\n.cn-action-buttons--menu {\n\tdisplay: contents;\n}\n</style>\n"],"names":["_normalizeClass","_createElementBlock","_Fragment","_renderList","_createBlock","_createTextVNode","_toDisplayString"],"mappings":"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"}
1
+ {"version":3,"file":"CnActionButtons.vue3.js","sources":["../../../../src/components/CnActionButtons/CnActionButtons.vue?vue&type=template&id=57a55d70&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div\n\t\tclass=\"cn-action-buttons\"\n\t\t:class=\"{ 'cn-action-buttons--menu': display === 'menu' }\"\n\t\tdata-testid=\"cn-action-buttons\">\n\t\t<!-- `display: \"menu\"` keeps every dialog below but renders no buttons of\n\t\t its own: the host draws the entries as NcActionButtons inside an\n\t\t overflow menu instead, driven by the `entries` event. NcActions\n\t\t keeps only NcAction* vnodes out of its default slot, so this\n\t\t component cannot be placed in the menu itself.\n\t\t That mode is enforced in `barActions` / `overflowActions`, which\n\t\t both answer empty for it — so `inline` only means anything while\n\t\t this component is the one drawing. -->\n\t\t<template v-for=\"entry in barActions\">\n\t\t\t<!-- Toggle: a stateful two-way state button (GET on mount, write on\n\t\t\t click, optimistic + revert). Rendered inline, never dispatched. -->\n\t\t\t<NcButton\n\t\t\t\tv-if=\"entry.type === 'toggle'\"\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\t:variant=\"toggleState[entry.id] ? 'primary' : 'secondary'\"\n\t\t\t\t:disabled=\"Boolean(togglePending[entry.id])\"\n\t\t\t\t:data-testid=\"`cn-action-toggle-${entry.id}`\"\n\t\t\t\t:aria-pressed=\"String(Boolean(toggleState[entry.id]))\"\n\t\t\t\t@click=\"onToggleClick(entry)\">\n\t\t\t\t<template v-if=\"entry.icon\" #icon>\n\t\t\t\t\t<CnIcon v-if=\"isMdiIconName(entry.icon)\" :name=\"entry.icon\" :size=\"20\" />\n\t\t\t\t\t<span v-else :class=\"entry.icon\" />\n\t\t\t\t</template>\n\t\t\t\t{{ tr(toggleState[entry.id] ? (entry.labelOn || entry.label) : (entry.labelOff || entry.label)) }}\n\t\t\t</NcButton>\n\t\t\t<!-- An entry that goes to a URL is a LINK, not a button that calls\n\t\t\t the browser. NcButton renders an anchor when given `href`, so it\n\t\t\t keeps the cluster's styling while the browser supplies the\n\t\t\t pointer, the focus ring, middle-click and \"open in new tab\" —\n\t\t\t none of which a click handler can offer. -->\n\t\t\t<NcButton\n\t\t\t\tv-else-if=\"entry.href\"\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\t:variant=\"entry.variant || 'secondary'\"\n\t\t\t\t:href=\"entry.href\"\n\t\t\t\t:target=\"linkTargetOf(entry)\"\n\t\t\t\t:data-testid=\"`cn-action-${entry.id}`\"\n\t\t\t\t@click=\"onLinkClick(entry, $event)\">\n\t\t\t\t<template v-if=\"entry.icon\" #icon>\n\t\t\t\t\t<CnIcon v-if=\"isMdiIconName(entry.icon)\" :name=\"entry.icon\" :size=\"20\" />\n\t\t\t\t\t<span v-else :class=\"entry.icon\" />\n\t\t\t\t</template>\n\t\t\t\t{{ tr(entry.label) }}\n\t\t\t</NcButton>\n\t\t\t<!-- Everything else: a plain action button routed through the shared\n\t\t\t dispatcher (api-call / open-form / navigate / open-modal / refresh),\n\t\t\t confirm-gated first when the action asks for it. -->\n\t\t\t<!-- `variant`, NOT `type`. NcButton carries both: `type` is the\n\t\t\t native button type (default \"button\") and `variant` is the\n\t\t\t styling. Binding the descriptor's variant to `type` set an\n\t\t\t invalid native type and left `variant` at its \"secondary\"\n\t\t\t default, so `variant: \"primary\"` / `\"error\"` had never once\n\t\t\t rendered — silently, since an unknown native type is ignored. -->\n\t\t\t<NcButton\n\t\t\t\tv-else\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\t:variant=\"entry.variant || 'secondary'\"\n\t\t\t\t:disabled=\"Boolean(actionPending[entry.id])\"\n\t\t\t\t:data-testid=\"`cn-action-${entry.id}`\"\n\t\t\t\t@click=\"onActionClick(entry)\">\n\t\t\t\t<template v-if=\"entry.icon\" #icon>\n\t\t\t\t\t<CnIcon v-if=\"isMdiIconName(entry.icon)\" :name=\"entry.icon\" :size=\"20\" />\n\t\t\t\t\t<span v-else :class=\"entry.icon\" />\n\t\t\t\t</template>\n\t\t\t\t{{ tr(entry.label) }}\n\t\t\t</NcButton>\n\t\t\t<!-- Sub-actions. Its OWN chevron rather than entries folded into the\n\t\t\t overflow below, because NcActions cannot nest — and a grouped\n\t\t\t dropdown is the point of declaring children (a version list\n\t\t\t flattened into the page's one menu grows without bound). The\n\t\t\t parent's own button above still renders alongside this chevron —\n\t\t\t a parent meant only to group children needs a real `handler` (or\n\t\t\t `href`/other type) of its own, or its button dispatches nothing. -->\n\t\t\t<NcActions\n\t\t\t\tv-if=\"hasChildren(entry)\"\n\t\t\t\t:key=\"`${entry.id}-children`\"\n\t\t\t\t:menuName=\"childMenuName(entry)\"\n\t\t\t\t:data-testid=\"`cn-action-children-${entry.id}`\">\n\t\t\t\t<template v-for=\"child in visibleChildren(entry)\">\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\tv-if=\"child.href\"\n\t\t\t\t\t\t:key=\"`${child.id}-link`\"\n\t\t\t\t\t\t:href=\"child.href\"\n\t\t\t\t\t\t:target=\"linkTargetOf(child)\"\n\t\t\t\t\t\t:data-testid=\"`cn-action-${child.id}`\"\n\t\t\t\t\t\t@click=\"onLinkClick(child, $event)\">\n\t\t\t\t\t\t<template v-if=\"child.icon\" #icon>\n\t\t\t\t\t\t\t<CnIcon v-if=\"isMdiIconName(child.icon)\" :name=\"child.icon\" :size=\"20\" />\n\t\t\t\t\t\t\t<span v-else :class=\"child.icon\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ tr(child.label) }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tv-else\n\t\t\t\t\t\t:key=\"child.id\"\n\t\t\t\t\t\t:disabled=\"Boolean(actionPending[child.id])\"\n\t\t\t\t\t\t:data-testid=\"`cn-action-${child.id}`\"\n\t\t\t\t\t\t@click=\"onActionClick(child)\">\n\t\t\t\t\t\t<template v-if=\"child.icon\" #icon>\n\t\t\t\t\t\t\t<CnIcon v-if=\"isMdiIconName(child.icon)\" :name=\"child.icon\" :size=\"20\" />\n\t\t\t\t\t\t\t<span v-else :class=\"child.icon\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ tr(child.label) }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcActions>\n\t\t</template>\n\n\t\t<!-- Overflow. Only ever rendered when `inline` is set, so a consumer\n\t\t that never asked for a limit keeps every action as a button. -->\n\t\t<NcActions\n\t\t\tv-if=\"overflowActions.length > 0\"\n\t\t\t:menuName=\"overflowOnly ? undefined : overflowMenuName\"\n\t\t\t:aria-label=\"overflowOnly ? overflowTriggerLabel : undefined\"\n\t\t\t:forceMenu=\"true\"\n\t\t\tdata-testid=\"cn-action-buttons-overflow\">\n\t\t\t<template v-for=\"entry in overflowActions\">\n\t\t\t\t<NcActionLink\n\t\t\t\t\tv-if=\"entry.href\"\n\t\t\t\t\t:key=\"`${entry.id}-link`\"\n\t\t\t\t\t:href=\"entry.href\"\n\t\t\t\t\t:target=\"linkTargetOf(entry)\"\n\t\t\t\t\t:data-testid=\"`cn-action-${entry.id}`\"\n\t\t\t\t\t@click=\"onLinkClick(entry, $event)\">\n\t\t\t\t\t<template v-if=\"entry.icon\" #icon>\n\t\t\t\t\t\t<CnIcon v-if=\"isMdiIconName(entry.icon)\" :name=\"entry.icon\" :size=\"20\" />\n\t\t\t\t\t\t<span v-else :class=\"entry.icon\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ tr(entry.label) }}\n\t\t\t\t</NcActionLink>\n\t\t\t\t<NcActionButton\n\t\t\t\t\tv-else\n\t\t\t\t\t:key=\"entry.id\"\n\t\t\t\t\t:disabled=\"Boolean(actionPending[entry.id])\"\n\t\t\t\t\t:data-testid=\"`cn-action-${entry.id}`\"\n\t\t\t\t\t@click=\"onActionClick(entry)\">\n\t\t\t\t\t<template v-if=\"entry.icon\" #icon>\n\t\t\t\t\t\t<CnIcon v-if=\"isMdiIconName(entry.icon)\" :name=\"entry.icon\" :size=\"20\" />\n\t\t\t\t\t\t<span v-else :class=\"entry.icon\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ tr(entry.label) }}\n\t\t\t\t</NcActionButton>\n\t\t\t</template>\n\t\t</NcActions>\n\n\t\t<!-- Confirm gate for a confirm:true action (reuses CnConfirmDialog). -->\n\t\t<CnConfirmDialog\n\t\t\tv-if=\"confirmEntry\"\n\t\t\tref=\"confirmDialog\"\n\t\t\t:dialogTitle=\"tr(confirmEntry.confirmTitle || confirmEntry.label)\"\n\t\t\t:message=\"tr(confirmEntry.confirmMessage) || defaultConfirmMessage\"\n\t\t\t:variant=\"confirmEntry.variant === 'error' ? 'error' : 'primary'\"\n\t\t\tv-bind=\"confirmButtonProps\"\n\t\t\t@confirm=\"onConfirmProceed\"\n\t\t\t@close=\"confirmEntry = null\" />\n\n\t\t<!-- Schema-driven create dialog for an open-form action. The plain\n\t\t form is the default: the properties/JSON table is a power-user\n\t\t surface, and a header button that says \"New case\" is aimed at\n\t\t someone filing one, not at someone inspecting the schema. Set\n\t\t `advanced: true` on the action to get the table back. -->\n\t\t<CnFormDialog\n\t\t\tv-if=\"formEntry && formSchema && !formEntry.advanced\"\n\t\t\tref=\"formDialog\"\n\t\t\t:schema=\"formSchema\"\n\t\t\t:item=\"formItem\"\n\t\t\t:register=\"formRegister\"\n\t\t\t:initialData=\"formInitialValues || {}\"\n\t\t\t:dialogTitle=\"tr(formEntry.formTitle) || ''\"\n\t\t\t:eyebrow=\"tr(formEntry.formEyebrow) || ''\"\n\t\t\t:subtitle=\"tr(formEntry.formSubtitle) || ''\"\n\t\t\t:confirmLabel=\"tr(formEntry.formConfirmLabel) || ''\"\n\t\t\t:includeFields=\"formEntry.includeFields || null\"\n\t\t\t:excludeFields=\"formEntry.excludeFields || []\"\n\t\t\t:fieldOverrides=\"formEntry.fieldOverrides || {}\"\n\t\t\t:size=\"formEntry.size || 'normal'\"\n\t\t\t:columns=\"formEntry.columns || 1\"\n\t\t\t@confirm=\"onFormConfirm\"\n\t\t\t@close=\"closeForm\" />\n\t\t<CnAdvancedFormDialog\n\t\t\tv-if=\"formEntry && formSchema && formEntry.advanced\"\n\t\t\tref=\"formDialog\"\n\t\t\t:schema=\"formSchema\"\n\t\t\t:item=\"null\"\n\t\t\t:initialValues=\"formInitialValues\"\n\t\t\t:confirmLabel=\"tr(formEntry.formConfirmLabel) || ''\"\n\t\t\t@confirm=\"onFormConfirm\"\n\t\t\t@close=\"closeForm\" />\n\n\t\t<!-- run-node's config dialog (manifest-run-node-action): only mounted\n\t\t when the target node declared at least one field AND that\n\t\t describe call succeeded — an empty/no form runs immediately with\n\t\t no dialog at all (openRunNode below), and a describe FAILURE also\n\t\t skips the dialog (toasts instead) rather than showing an empty,\n\t\t confusing one. -->\n\t\t<CnRunNodeDialog\n\t\t\tv-if=\"runNodeEntry && runNodeFields.length\"\n\t\t\t:title=\"tr(runNodeEntry.formTitle) || tr(runNodeEntry.label)\"\n\t\t\t:fields=\"runNodeFields\"\n\t\t\t:loading=\"Boolean(actionPending[runNodeEntry.id])\"\n\t\t\t:translate=\"effectiveTranslate\"\n\t\t\t@confirm=\"onRunNodeConfirm\"\n\t\t\t@close=\"closeRunNode\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcActions, NcButton } from '@nextcloud/vue'\nimport { inject } from 'vue'\nimport CnConfirmDialog from '../../dialogs/CnConfirmDialog.vue'\nimport CnRunNodeDialog from '../../dialogs/CnRunNodeDialog.vue'\nimport { fetchEndpointSource } from '../../composables/useEndpointSource.js'\nimport { useObjectStore } from '../../store/useObjectStore.js'\nimport { actionLink, buildOnSuccessRoute, dispatchAction, interpolateActionTarget, isExternalActionTarget, postRunNode, resolveCreateOverrideHandler, resolveObjectOpType } from '../../utils/actionsDispatcher.js'\nimport { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'\nimport { answersByArrayKey, mergeValueArray, usesArrayValues, valueRecordsFor } from '../../utils/dynamicProperties.js'\nimport { followLinkClick } from '../../utils/linkNavigation.js'\nimport { resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\nimport { evaluateVisibleWhen } from '../../utils/visibleWhen.js'\nimport { CnAdvancedFormDialog } from '../CnAdvancedFormDialog/index.js'\nimport { CnFormDialog } from '../CnFormDialog/index.js'\nimport { CnIcon } from '../CnIcon/index.js'\n\n/**\n * CnActionButtons — declarative header-actions surface (#91 Wave 3).\n *\n * Renders a page's `headerActions[]` as buttons and owns the two action\n * behaviours that need STATE or a DIALOG the one-shot `dispatchAction`\n * can't provide on its own:\n *\n * - **`open-form`** — mounts the shared `CnAdvancedFormDialog` for the\n * action's `schema` (fetched through the object store), saves on\n * confirm, toasts, refreshes the page, and optionally navigates to\n * `onSuccessRoute`. Two optional keys cover the schemas a bare create\n * cannot satisfy: `props` seeds fixed field values into the create form\n * (so one schema can back several buttons, each fixing its own\n * discriminator), and `createOverride` names a registry handler that owns\n * the persist instead of `objectStore.saveObject` (for a schema requiring\n * a server-minted field the form cannot supply). `includeFields` /\n * `excludeFields` / `fieldOverrides` narrow what the button asks for, which\n * is how one schema serves both a full editor and a quick-create button.\n * `advanced: true` swaps in the properties/JSON table for a power user.\n * With `endpoint: { url, method?, loadUrl?, responsePath? }` and\n * `formProperties` (JSON-schema properties) it edits a record that is not a\n * register object instead: it reads the record from the endpoint, shows the\n * declared fields filled in, and sends them back with PATCH (or PUT). With\n * `method: \"POST\"` it creates one: an empty form, posted to the endpoint.\n * - **`toggle`** — a two-way state button: `GET`s `stateSource` on mount,\n * renders `labelOn` / `labelOff`, and on click `writes` the flipped\n * value OPTIMISTICALLY, reverting on failure.\n * - **`run-node`** (manifest-run-node-action) — GETs the target flow node's\n * describe endpoint (OpenRegister's `or-flow-run-node`); a node with\n * declared fields mounts `CnRunNodeDialog` (its own small field renderer —\n * see that component's docblock for why it is not `CnFlowNodeEditModal`'s),\n * a node with none runs immediately. Either way `postRunNode()`\n * (`actionsDispatcher.js`) makes the actual call, subject-token-resolved\n * exactly like `agent`.\n *\n * Every other type (`api-call`, `navigate`, `open-modal`, `open-page`,\n * `refresh`, `handler`) routes through the shared `dispatchAction` — with\n * a `CnConfirmDialog` gate first when the action sets `confirm: true`\n * (the object-op precedent: the dispatcher runs AFTER confirmation).\n *\n * Each action may carry a `visibleWhen` predicate (the shared banner\n * shape — `{ endpoint | source | field, op, value }`) evaluated against\n * the page / object context; a hidden action simply doesn't render.\n *\n * Placement: CnDashboardPage / CnDetailPage / CnPageRenderer mount this in\n * their header-actions area, passing the page's `headerActions[]`. The\n * object context (`{ objectId, object, register, schema }`) and the page\n * workspace / app-config bags are injected so tokens + local `visibleWhen`\n * resolve against the current record and page state.\n *\n * ```json\n * \"headerActions\": [\n * { \"id\": \"new-lead\", \"label\": \"New lead\", \"type\": \"open-form\",\n * \"register\": \"crm\", \"schema\": \"lead\", \"onSuccessRoute\": \"Leads\", \"variant\": \"primary\" },\n * { \"id\": \"approve\", \"label\": \"Approve\", \"type\": \"api-call\",\n * \"url\": \"/apps/shillinq/api/payment-runs/@objectId/approve\", \"confirm\": true,\n * \"visibleWhen\": { \"field\": \"state\", \"op\": \"eq\", \"value\": \"pending\" } },\n * { \"id\": \"toggle-open\", \"type\": \"toggle\", \"labelOn\": \"Open\", \"labelOff\": \"Closed\",\n * \"stateSource\": { \"url\": \"/apps/pipelinq/api/werkplek/@objectId/state\", \"responsePath\": \"open\" },\n * \"field\": \"open\", \"writeUrl\": \"/apps/pipelinq/api/werkplek/@objectId/state\", \"method\": \"PUT\" }\n * ]\n * ```\n */\nexport default {\n\tname: 'CnActionButtons',\n\n\tcomponents: { NcActions, NcActionButton, NcActionLink, NcButton, CnIcon, CnConfirmDialog, CnFormDialog, CnAdvancedFormDialog, CnRunNodeDialog },\n\n\tinject: {\n\t\t/** Detail-page object context (`{ objectId, object, register, schema }`). */\n\t\tcnObjectContext: { default: null },\n\t\t/** v2 slot-grid detail context holder (CnPageRenderer). */\n\t\tcnDetailObjectContext: { default: null },\n\t\t/** Page-level workspace context bag (reactive). */\n\t\tcnWorkspaceContext: { default: () => ({}) },\n\t\t/** Page-level app config bag (reactive). */\n\t\tcnAppConfig: { default: () => ({}) },\n\t\t/** Pre-bound dispatchAction from CnPageRenderer (router/registry/handlers/openModal wired). */\n\t\tcnDispatchAction: { default: null },\n\t\t/**\n\t\t * Host translate function provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). The\n\t\t * manifest-authored action labels, confirm copy and api-call toasts\n\t\t * are run through it. Defaults to an identity function so an\n\t\t * untranslated key renders as itself.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t\t/** v2 component registry, for resolving an `open-form` createOverride. */\n\t\tcnRegistry: { default: () => ({}) },\n\t\t/** Legacy customComponents map, same resolution fallback. */\n\t\tcnCustomComponents: { default: () => ({}) },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The page's declarative header actions. Each entry is a typed action\n\t\t * (`open-form` | `toggle` | `api-call` | `navigate` | `open-modal` |\n\t\t * `refresh` | `handler`) plus `id` / `label` and an optional\n\t\t * `visibleWhen` predicate, `icon`, `variant`, and `confirm`.\n\t\t *\n\t\t * An entry carrying `href` (with optional `target`) renders as a real\n\t\t * link instead — an anchor in the bar, an NcActionLink in a menu — and\n\t\t * is never dispatched. Use it for anything that ends in a URL, so the\n\t\t * browser keeps middle-click, \"open in new tab\" and the link semantics\n\t\t * assistive tech announces.\n\t\t *\n\t\t * `href` is used as given, so it must be a finished URL. A manifest\n\t\t * `headerActions[]` entry may carry `href` too: the page (CnDetailPage,\n\t\t * CnDashboardPage) resolves an instance path through `generateUrl()`\n\t\t * and sets `target` before the entry reaches this component.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\tactions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * How many collapsible actions stay as buttons; the rest fall into a\n\t\t * single `···` overflow menu.\n\t\t *\n\t\t * `0` (the default) renders EVERY action as a button, which is what\n\t\t * every existing consumer already gets — the overflow only ever\n\t\t * appears once a host asks for a limit.\n\t\t *\n\t\t * A `variant: \"primary\"` action, an action carrying `children`, and a\n\t\t * `toggle` are never collapsed and never consume one of the N slots:\n\t\t * NcActions paints all of its inline actions with one shared variant\n\t\t * (so a lone primary among them is not expressible), it cannot nest\n\t\t * another NcActions, and a toggle's state lives in its button.\n\t\t *\n\t\t * @type {number}\n\t\t */\n\t\tinline: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/**\n\t\t * Name for the overflow menu. Empty falls back to a translated\n\t\t * \"Actions\".\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\toverflowLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Every collapsible action in the `···` menu, none as a button: the\n\t\t * trigger is icon only and named by `overflowAriaLabel` (a card's\n\t\t * \"Actions for Volledigheid toetsen\").\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\toverflowOnly: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * The accessible name of the icon-only trigger under `overflowOnly`.\n\t\t * Empty falls back to the overflow label.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\toverflowAriaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Explicit Vue Router instance for `navigate` / `open-page` /\n\t\t * `onSuccessRoute`. Falls back to `this.$router`. Only needed for\n\t\t * standalone mounts outside a router tree.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\trouter: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Where the actions are drawn. `buttons` (the default) puts one\n\t\t * NcButton per action in the host's header. `menu` draws none and\n\t\t * emits `entries` instead, so the host can render them inside an\n\t\t * overflow menu while this component keeps owning the dialogs.\n\t\t *\n\t\t * @type {'buttons'|'menu'}\n\t\t */\n\t\tdisplay: {\n\t\t\ttype: String,\n\t\t\tdefault: 'buttons',\n\t\t\tvalidator: (v) => ['buttons', 'menu'].includes(v),\n\t\t},\n\t},\n\n\temits: ['created', 'entries'],\n\n\tsetup() {\n\t\t// Read the two detail-surface injects once so the object token context\n\t\t// resolves identically to the endpoint-bound widgets (Wave 3).\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst detailCtxRaw = inject('cnDetailObjectContext', null)\n\t\tconst workspaceRaw = inject('cnWorkspaceContext', null)\n\t\tconst appConfigRaw = inject('cnAppConfig', null)\n\t\treturn { objectCtxRaw, detailCtxRaw, workspaceRaw, appConfigRaw }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Evaluated visibleWhen outcome per action id (true when no predicate). */\n\t\t\tvisibility: {},\n\t\t\t/** Current boolean state per toggle action id. */\n\t\t\ttoggleState: {},\n\t\t\t/** In-flight flag per toggle action id (disables the button). */\n\t\t\ttogglePending: {},\n\t\t\t/** In-flight flag per non-toggle action id. */\n\t\t\tactionPending: {},\n\t\t\t/** The action awaiting confirmation (null = no dialog). */\n\t\t\tconfirmEntry: null,\n\t\t\t/** The open-form action currently showing its dialog (null = closed). */\n\t\t\tformEntry: null,\n\t\t\t/** The fetched schema object for the open-form dialog (null until loaded). */\n\t\t\tformSchema: null,\n\t\t\t/** The record an endpoint form edits (null for a create form). */\n\t\t\tformItem: null,\n\t\t\t/** The run-node action currently in flight / showing its dialog (null = closed). */\n\t\t\trunNodeEntry: null,\n\t\t\t/** The target node's declared config-form fields, from the describe GET. */\n\t\t\trunNodeFields: [],\n\t\t\t/** Default confirm-dialog message. */\n\t\t\tdefaultConfirmMessage: t('nextcloud-vue', 'Are you sure you want to continue?'),\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The confirm button's words and icon from the action\n\t\t * (`confirmLabel`, `confirmIcon`); none set leaves the dialog's own.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tconfirmButtonProps() {\n\t\t\tconst entry = this.confirmEntry || {}\n\t\t\treturn {\n\t\t\t\t...(typeof entry.confirmLabel === 'string' && entry.confirmLabel !== '' ? { confirmLabel: this.tr(entry.confirmLabel) } : {}),\n\t\t\t\t...(typeof entry.confirmIcon === 'string' && entry.confirmIcon !== '' ? { confirmIcon: entry.confirmIcon } : {}),\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Effective translate function: the injected `cnTranslate` (the host\n\t\t * app's bound `t()`), identity by default.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key\n\t\t},\n\n\t\t/**\n\t\t * Create-form seed values for the open `open-form` action. An action\n\t\t * declares them as `props`, which is how a single schema backs several\n\t\t * buttons — \"New request\" and \"New complaint\" both open the `ticket`\n\t\t * form, each fixing its own `ticketType`.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tformInitialValues() {\n\t\t\tconst props = this.formEntry && this.formEntry.props\n\t\t\tif (!props || typeof props !== 'object' || Array.isArray(props)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Seed values go through the SAME token grammar as filters, so an\n\t\t\t// action on a detail page can stamp the record it belongs to:\n\t\t\t// `{ \"domainObjectRef\": \"@objectId\" }`. Without this the literal\n\t\t\t// string \"@objectId\" is saved, and a foreign key pointing at nothing\n\t\t\t// is a defect that only shows up in whatever reads it later.\n\t\t\treturn resolveFilterTokens(props, this.tokenCtx)\n\t\t},\n\n\t\t/**\n\t\t * The register the open dialog reads and writes against. The plain\n\t\t * form needs it to resolve `$ref` pickers into real dropdowns; without\n\t\t * one a reference field renders as an empty select and the action is\n\t\t * unusable for exactly the schemas that need it most.\n\t\t *\n\t\t * @return {string} The register slug, '' when none is resolvable.\n\t\t */\n\t\tformRegister() {\n\t\t\treturn (this.formEntry && this.formEntry.register) || this.objectCtx.register || ''\n\t\t},\n\n\t\t/**\n\t\t * The actions whose visibleWhen evaluated true (or carry no predicate).\n\t\t *\n\t\t * A `navigate` action whose target leaves the app is turned into a link\n\t\t * entry here, so it takes the `href` branch above instead of being\n\t\t * dispatched. An absolute URL is not a route: pushing one at the router\n\t\t * matched nothing and landed on the fallback page, carrying the URL's\n\t\t * own query string with it. The action's `target` (the URL) is replaced\n\t\t * by the anchor's `target`, since the two fields share a name and the\n\t\t * URL now lives in `href`. An author's explicit `href` still wins.\n\t\t *\n\t\t * An in-app `navigate` or `open-page` becomes a link too (see\n\t\t * `toLinkEntry`), keeping its own `target` so a fallback dispatch works.\n\t\t */\n\t\tvisibleActions() {\n\t\t\treturn (this.actions || [])\n\t\t\t\t.filter((a) => a && a.id && this.visibility[a.id] !== false)\n\t\t\t\t.map((a) => this.toLinkEntry(a))\n\t\t},\n\n\t\t/**\n\t\t * The actions rendered in the bar itself: every non-collapsible one,\n\t\t * plus the first `inline` collapsible ones. Declaration order is kept,\n\t\t * so promoting an action never reshuffles the row.\n\t\t *\n\t\t * Empty in `display: \"menu\"` — the host draws the entries itself there,\n\t\t * so this component renders no buttons and has nothing to cap.\n\t\t *\n\t\t * @return {Array<object>} Actions to render as buttons / chevrons.\n\t\t */\n\t\tbarActions() {\n\t\t\tif (this.display === 'menu') {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tif (this.overflowOnly) {\n\t\t\t\treturn this.visibleActions.filter((a) => !this.isCollapsible(a))\n\t\t\t}\n\t\t\tif (!(this.inline > 0)) {\n\t\t\t\treturn this.visibleActions\n\t\t\t}\n\t\t\tconst promoted = new Set(this.visibleActions\n\t\t\t\t.filter((a) => this.isCollapsible(a))\n\t\t\t\t.slice(0, this.inline)\n\t\t\t\t.map((a) => a.id))\n\t\t\treturn this.visibleActions.filter((a) => !this.isCollapsible(a) || promoted.has(a.id))\n\t\t},\n\n\t\t/**\n\t\t * The actions that did not fit, for the `···` menu. Always empty while\n\t\t * `inline` is 0, and in `display: \"menu\"` where the host owns the menu.\n\t\t *\n\t\t * @return {Array<object>} Actions to render as menu items.\n\t\t */\n\t\toverflowActions() {\n\t\t\tif (this.display === 'menu' || !(this.inline > 0 || this.overflowOnly)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst shown = new Set(this.barActions.map((a) => a.id))\n\t\t\treturn this.visibleActions.filter((a) => !shown.has(a.id))\n\t\t},\n\n\t\t/**\n\t\t * Name for the overflow trigger.\n\t\t *\n\t\t * @return {string} The label.\n\t\t */\n\t\toverflowMenuName() {\n\t\t\treturn this.tr(this.overflowLabel) || t('nextcloud-vue', 'Actions')\n\t\t},\n\n\t\t/**\n\t\t * Name of the icon-only trigger under `overflowOnly`.\n\t\t *\n\t\t * @return {string} The label.\n\t\t */\n\t\toverflowTriggerLabel() {\n\t\t\treturn this.overflowAriaLabel || this.overflowMenuName\n\t\t},\n\n\t\t/**\n\t\t * The visible actions flattened for a host that draws them as menu\n\t\t * items. Everything the item needs to render is resolved here — the\n\t\t * translated label a toggle currently shows, its pressed state, its\n\t\t * pending flag, and which of the two icon shapes applies — so the host\n\t\t * template stays a plain v-for and needs none of this component's\n\t\t * state. `run()` is pre-bound, the way CnIndexPage pre-binds a header\n\t\t * action's handler for the index bar, so the host dispatches without\n\t\t * reaching back through a ref.\n\t\t *\n\t\t * @return {Array<object>} One menu-ready descriptor per visible action.\n\t\t */\n\t\tmenuEntries() {\n\t\t\treturn this.visibleActions.map((entry) => {\n\t\t\t\tconst isToggle = entry.type === 'toggle'\n\t\t\t\tconst on = Boolean(this.toggleState[entry.id])\n\t\t\t\tconst label = isToggle\n\t\t\t\t\t? (on ? (entry.labelOn || entry.label) : (entry.labelOff || entry.label))\n\t\t\t\t\t: entry.label\n\t\t\t\treturn {\n\t\t\t\t\tid: entry.id,\n\t\t\t\t\tlabel: this.tr(label),\n\t\t\t\t\t// An mdi name goes to CnIcon; a legacy `icon-*` class goes\n\t\t\t\t\t// on a bare span. Deciding it here keeps isMdiIconName from\n\t\t\t\t\t// having to be duplicated into every host.\n\t\t\t\t\ticonName: this.isMdiIconName(entry.icon) ? entry.icon : '',\n\t\t\t\t\ticonClass: (entry.icon && !this.isMdiIconName(entry.icon)) ? entry.icon : '',\n\t\t\t\t\tdisabled: Boolean(isToggle ? this.togglePending[entry.id] : this.actionPending[entry.id]),\n\t\t\t\t\t// null, not false: a non-toggle carries no pressed state at\n\t\t\t\t\t// all, and `aria-pressed=\"false\"` would announce every\n\t\t\t\t\t// action as an un-pressed toggle button.\n\t\t\t\t\tpressed: isToggle ? on : null,\n\t\t\t\t\ttestid: isToggle ? `cn-action-toggle-${entry.id}` : `cn-action-${entry.id}`,\n\t\t\t\t\t// A link entry, for a host to render as an anchor rather\n\t\t\t\t\t// than a button with a click handler. Named `linkTarget`\n\t\t\t\t\t// because an action's own `target` is its destination. An in-app\n\t\t\t\t\t// link (`linkTo`) is left to `run()`: a host anchor has no router.\n\t\t\t\t\thref: (!entry.linkTo && entry.href) || '',\n\t\t\t\t\tlinkTarget: (!entry.linkTo && entry.href) ? (entry.target || '_blank') : '',\n\t\t\t\t\trun: () => (isToggle ? this.onToggleClick(entry) : this.onActionClick(entry)),\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/** Unwrapped page workspace bag. */\n\t\tworkspaceCtx() {\n\t\t\tconst c = this.workspaceRaw\n\t\t\tconst v = (c && typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t\treturn (v && typeof v === 'object') ? v : {}\n\t\t},\n\n\t\t/** Unwrapped app-config bag. */\n\t\tconfigCtx() {\n\t\t\tconst c = this.appConfigRaw\n\t\t\tconst v = (c && typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t\treturn (v && typeof v === 'object') ? v : {}\n\t\t},\n\n\t\t/** The merged object token context (both detail-surface injects). */\n\t\tobjectCtx() {\n\t\t\treturn resolveObjectTokenContext(this.objectCtxRaw, this.detailCtxRaw) || {}\n\t\t},\n\n\t\t/** The full token context for url/param interpolation + local visibleWhen. */\n\t\ttokenCtx() {\n\t\t\treturn {\n\t\t\t\t...this.objectCtx,\n\t\t\t\tworkspace: this.workspaceCtx,\n\t\t\t\tconfig: this.configCtx,\n\t\t\t}\n\t\t},\n\n\t\t/** The effective router (explicit prop wins, else this.$router). */\n\t\teffectiveRouter() {\n\t\t\treturn this.router || this.$router || null\n\t\t},\n\t},\n\n\twatch: {\n\t\tactions: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.evaluateVisibility()\n\t\t\t\tthis.initToggles()\n\t\t\t},\n\t\t},\n\n\t\t// Re-evaluate local (object-context) visibleWhen when the record loads.\n\t\tobjectCtx: {\n\t\t\tdeep: true,\n\t\t\thandler() { this.evaluateVisibility() },\n\t\t},\n\n\t\t// Push the menu-ready descriptors at a `display: \"menu\"` host. It fires\n\t\t// immediately so the host has the list on first paint, and again on\n\t\t// every change of visibility, toggle state or pending flag — the three\n\t\t// things that would otherwise leave a stale item in an open menu.\n\t\tmenuEntries: {\n\t\t\timmediate: true,\n\t\t\thandler(entries) {\n\t\t\t\tif (this.display === 'menu') {\n\t\t\t\t\t/**\n\t\t\t\t\t * @event entries Emitted in `display: \"menu\"` only, whenever the visible actions, their toggle state or their pending flags change. Payload: one menu-ready descriptor per visible action, each carrying `id`, `label`, `iconName`, `iconClass`, `disabled`, `pressed`, `testid` and a pre-bound `run()`.\n\t\t\t\t\t */\n\t\t\t\t\tthis.$emit('entries', entries)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Heuristic MDI-name check (an `icon-`-prefixed string is a CSS class).\n\t\t *\n\t\t * @param {string} name The icon string.\n\t\t * @return {boolean} True when it should render via CnIcon.\n\t\t */\n\t\tisMdiIconName(name) {\n\t\t\treturn typeof name === 'string' && name !== '' && !name.startsWith('icon-')\n\t\t},\n\n\t\t/**\n\t\t * Evaluate every action's `visibleWhen` against the current context.\n\t\t * Fail-safe (a broken predicate hides its action); actions without a\n\t\t * predicate are always visible.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync evaluateVisibility() {\n\t\t\t// Children are walked too: a sub-action whose predicate was never\n\t\t\t// evaluated would read as visible and render regardless.\n\t\t\tconst flat = []\n\t\t\tfor (const action of this.actions || []) {\n\t\t\t\tif (!action || !action.id) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tflat.push(action)\n\t\t\t\tif (Array.isArray(action.children)) {\n\t\t\t\t\tflat.push(...action.children.filter((c) => c && c.id))\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const action of flat) {\n\t\t\t\tif (!action.visibleWhen) {\n\t\t\t\t\tthis.visibility[action.id] = true\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst ok = await evaluateVisibleWhen(action.visibleWhen, this.tokenCtx)\n\t\t\t\tthis.visibility[action.id] = ok\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Seed each toggle action's state from its `stateSource` endpoint\n\t\t * (fetched through the shared endpoint engine so `@objectId` etc.\n\t\t * resolve). No-op for non-toggle actions.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync initToggles() {\n\t\t\tfor (const action of this.actions || []) {\n\t\t\t\tif (!action || action.type !== 'toggle' || !action.id) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tthis.toggleState[action.id] = false\n\t\t\t\tif (!action.stateSource || !action.stateSource.url) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst payload = await fetchEndpointSource(action.stateSource, this.tokenCtx)\n\t\t\t\t\tconst value = action.field ? this.readField(payload, action.field) : payload\n\t\t\t\t\tthis.toggleState[action.id] = Boolean(value)\n\t\t\t\t} catch {\n\t\t\t\t\t// Leave the default (off); a failed state read never breaks the bar.\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Whether an action may be pushed into the overflow menu. See the\n\t\t * `inline` prop for why these three shapes never can be.\n\t\t *\n\t\t * @param {object} entry The action descriptor.\n\t\t * @return {boolean} True when it can collapse.\n\t\t */\n\t\tisCollapsible(entry) {\n\t\t\treturn !this.hasChildren(entry)\n\t\t\t\t&& entry.variant !== 'primary'\n\t\t\t\t&& entry.type !== 'toggle'\n\t\t},\n\n\t\t/**\n\t\t * Whether an action declares sub-actions worth a chevron.\n\t\t *\n\t\t * @param {object} entry The action descriptor.\n\t\t * @return {boolean} True when it has at least one visible child.\n\t\t */\n\t\thasChildren(entry) {\n\t\t\treturn this.visibleChildren(entry).length > 0\n\t\t},\n\n\t\t/**\n\t\t * An action's sub-actions, minus any hidden by `visibleWhen`.\n\t\t *\n\t\t * @param {object} entry The action descriptor.\n\t\t * @return {Array<object>} The renderable children.\n\t\t */\n\t\tvisibleChildren(entry) {\n\t\t\tif (!Array.isArray(entry?.children)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn entry.children\n\t\t\t\t.filter((c) => c && c.id && this.visibility[c.id] !== false)\n\t\t\t\t.map((c) => this.toLinkEntry(c))\n\t\t},\n\n\t\t/**\n\t\t * Give a navigating action the `href` it renders as. An external\n\t\t * `navigate` target becomes the href with a `_blank` anchor target. An\n\t\t * in-app `navigate` / `open-page` gets the router-resolved href plus\n\t\t * `linkTo`, the location a plain click routes to; one with `confirm`,\n\t\t * an `onSelect` function, or an unresolvable route stays a button.\n\t\t *\n\t\t * @param {object} a The action descriptor.\n\t\t * @return {object} The descriptor, possibly with `href` / `linkTo` added.\n\t\t */\n\t\ttoLinkEntry(a) {\n\t\t\tif (a.href || (a.type !== 'navigate' && a.type !== 'open-page')) {\n\t\t\t\treturn a\n\t\t\t}\n\t\t\tlet entry = a\n\t\t\tif (a.type === 'navigate') {\n\t\t\t\t// Interpolated BEFORE the external test and before it becomes an\n\t\t\t\t// href, so the anchor carries the record's id rather than the token.\n\t\t\t\tconst target = interpolateActionTarget(a.target, this.tokenCtx)\n\t\t\t\tif (isExternalActionTarget(target)) {\n\t\t\t\t\treturn { ...a, href: target, target: '_blank' }\n\t\t\t\t}\n\t\t\t\tentry = target === a.target ? a : { ...a, target }\n\t\t\t}\n\t\t\tif (typeof entry.onSelect === 'function') {\n\t\t\t\treturn entry\n\t\t\t}\n\t\t\tconst link = actionLink(entry, { router: this.effectiveRouter })\n\t\t\treturn link ? { ...entry, href: link.href, linkTo: link.to } : entry\n\t\t},\n\n\t\t/**\n\t\t * The anchor `target` of a link entry. An in-app link opens in place;\n\t\t * its own `target` is the destination, not an anchor target.\n\t\t *\n\t\t * @param {object} entry The link entry.\n\t\t * @return {string|undefined} The anchor target.\n\t\t */\n\t\tlinkTargetOf(entry) {\n\t\t\treturn entry.linkTo ? undefined : (entry.target || undefined)\n\t\t},\n\n\t\t/**\n\t\t * Click on a link entry: a plain click on an in-app link routes in\n\t\t * place; a URL or a modified click is left to the browser.\n\t\t *\n\t\t * @param {object} entry The link entry.\n\t\t * @param {MouseEvent} event The click event.\n\t\t * @return {void}\n\t\t */\n\t\tonLinkClick(entry, event) {\n\t\t\tif (entry.linkTo) {\n\t\t\t\tfollowLinkClick(event, entry.linkTo, this.effectiveRouter)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Name for a sub-action chevron. `childrenLabel` when set, else the\n\t\t * parent's own label, so the dropdown is never announced unlabelled.\n\t\t *\n\t\t * @param {object} entry The action descriptor.\n\t\t * @return {string} The menu name.\n\t\t */\n\t\tchildMenuName(entry) {\n\t\t\treturn this.tr(entry.childrenLabel) || this.tr(entry.label) || ''\n\t\t},\n\n\t\t/**\n\t\t * Read a dot-path off a payload (the toggle state field).\n\t\t *\n\t\t * @param {unknown} data The payload.\n\t\t * @param {string} field The dot-path.\n\t\t * @return {unknown} The value at the path.\n\t\t */\n\t\treadField(data, field) {\n\t\t\tif (!field) {\n\t\t\t\treturn data\n\t\t\t}\n\t\t\treturn String(field).split('.').reduce((o, k) => (o === null || o === undefined ? o : o[k]), data)\n\t\t},\n\n\t\t/**\n\t\t * Toggle click: flip the state OPTIMISTICALLY, write it, and revert on\n\t\t * failure. Success/error toast via the api-call dispatch path (which\n\t\t * also refreshes the page unless `refresh: false`).\n\t\t *\n\t\t * @param {object} entry The toggle action.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onToggleClick(entry) {\n\t\t\tif (this.togglePending[entry.id]) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst previous = Boolean(this.toggleState[entry.id])\n\t\t\tconst next = !previous\n\t\t\tthis.toggleState[entry.id] = next\n\t\t\tthis.togglePending[entry.id] = true\n\t\t\tconst writeParams = { ...(entry.params || {}) }\n\t\t\tif (entry.field) {\n\t\t\t\twriteParams[entry.field] = next\n\t\t\t}\n\t\t\tconst result = await this.dispatch({\n\t\t\t\ttype: 'api-call',\n\t\t\t\turl: entry.writeUrl,\n\t\t\t\tmethod: entry.method || 'PUT',\n\t\t\t\tparams: writeParams,\n\t\t\t\tsuccessMessage: entry.successMessage,\n\t\t\t\terrorMessage: entry.errorMessage,\n\t\t\t\trefresh: entry.refresh,\n\t\t\t})\n\t\t\tif (!result || result.ok === false) {\n\t\t\t\t// Revert the optimistic flip.\n\t\t\t\tthis.toggleState[entry.id] = previous\n\t\t\t}\n\t\t\tthis.togglePending[entry.id] = false\n\t\t},\n\n\t\t/**\n\t\t * Non-toggle action click: gate on `confirm` first, else run.\n\t\t *\n\t\t * @param {object} entry The action.\n\t\t * @return {void}\n\t\t */\n\t\tonActionClick(entry) {\n\t\t\tif (entry.confirm === true) {\n\t\t\t\tthis.confirmEntry = entry\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.runAction(entry)\n\t\t},\n\n\t\t/**\n\t\t * The confirm dialog's proceed handler: run the pending action, then\n\t\t * report the outcome back to the dialog's result phase.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onConfirmProceed() {\n\t\t\tconst entry = this.confirmEntry\n\t\t\tif (!entry) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst result = await this.runAction(entry)\n\t\t\tconst dialog = this.$refs.confirmDialog\n\t\t\tif (dialog && typeof dialog.setResult === 'function') {\n\t\t\t\tconst ok = result === undefined || (result && result.ok !== false)\n\t\t\t\tdialog.setResult(ok ? { success: true } : { error: t('nextcloud-vue', 'Action failed.') })\n\t\t\t} else {\n\t\t\t\tthis.confirmEntry = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Run an action (post-confirmation). `open-form` opens the schema\n\t\t * dialog; everything else dispatches. Returns the dispatch result so\n\t\t * the confirm dialog can report success/failure.\n\t\t *\n\t\t * @param {object} entry The action.\n\t\t * @return {Promise<unknown>} The dispatch result (undefined for open-form).\n\t\t */\n\t\tasync runAction(entry) {\n\t\t\t// A function on the descriptor, for a PARENT COMPONENT composing\n\t\t\t// entries in JS (CnDetailPage folds its own Edit in this way).\n\t\t\t// Manifest JSON cannot carry one, so the declarative path is\n\t\t\t// untouched — and it runs after the confirm gate, like every\n\t\t\t// dispatched type.\n\t\t\tif (typeof entry.onSelect === 'function') {\n\t\t\t\tentry.onSelect(entry)\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tif (entry.type === 'open-form') {\n\t\t\t\tawait this.openForm(entry)\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tif (entry.type === 'run-node') {\n\t\t\t\tawait this.openRunNode(entry)\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tthis.actionPending[entry.id] = true\n\t\t\tconst result = await this.dispatch(entry)\n\t\t\tthis.actionPending[entry.id] = false\n\t\t\treturn result\n\t\t},\n\n\t\t/**\n\t\t * Resolve a manifest-authored UI string through the host translate\n\t\t * function. A pure pass-through: an empty value, or a catalogue that\n\t\t * lacks the key, returns the input unchanged.\n\t\t *\n\t\t * @param {string} [value] The English source string.\n\t\t * @return {string|undefined} The translated (or source) string.\n\t\t */\n\t\ttr(value) {\n\t\t\treturn value ? this.effectiveTranslate(value) : value\n\t\t},\n\n\t\t/**\n\t\t * Route an action through the shared dispatcher — the pre-bound\n\t\t * `cnDispatchAction` (router/registry/handlers/openModal already\n\t\t * wired) when available, else a direct call with a minimal context.\n\t\t * The token context and the host translate function are always\n\t\t * merged in (the latter localises `api-call` success/error toasts).\n\t\t *\n\t\t * @param {object} action The action to dispatch.\n\t\t * @return {Promise<unknown>} The dispatch result.\n\t\t */\n\t\tdispatch(action) {\n\t\t\tconst extra = { tokenCtx: this.tokenCtx, translate: this.effectiveTranslate }\n\t\t\tif (typeof this.cnDispatchAction === 'function') {\n\t\t\t\treturn this.cnDispatchAction(action, extra)\n\t\t\t}\n\t\t\treturn dispatchAction(action, {\n\t\t\t\trouter: this.effectiveRouter,\n\t\t\t\topenForm: (a) => this.openForm(a),\n\t\t\t\topenRunNode: (a) => this.openRunNode(a),\n\t\t\t\t...extra,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Open the schema-driven create dialog for an `open-form` action:\n\t\t * fetch the schema object through the shared object store, then mount\n\t\t * CnAdvancedFormDialog. A failed schema load toasts and no-ops.\n\t\t *\n\t\t * @param {object} entry The open-form action (`register`, `schema`).\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync openForm(entry) {\n\t\t\tthis.formEntry = entry\n\t\t\tthis.formSchema = null\n\t\t\tthis.formItem = null\n\t\t\tif (entry.endpoint && typeof entry.endpoint.url === 'string' && entry.endpoint.url !== '') {\n\t\t\t\tawait this.openEndpointForm(entry)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst register = entry.register || this.objectCtx.register || ''\n\t\t\tconst schema = entry.schema || ''\n\t\t\tif (!schema) {\n\t\t\t\tthis.formEntry = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst store = useObjectStore()\n\t\t\t\tconst type = resolveObjectOpType(store, { register, schema })\n\t\t\t\tthis.formSchema = await store.fetchSchema(type)\n\t\t\t} catch {\n\t\t\t\tthis.formSchema = null\n\t\t\t}\n\t\t\tif (!this.formSchema) {\n\t\t\t\tconst { showError } = await import('@nextcloud/dialogs')\n\t\t\t\tif (typeof showError === 'function') {\n\t\t\t\t\tshowError(t('nextcloud-vue', 'Could not open the form.'))\n\t\t\t\t}\n\t\t\t\tthis.formEntry = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Open an `open-form` action with an `endpoint` as an edit form over a\n\t\t * record that is not a register object (an OpenRegister organisation):\n\t\t * the fields are the action's `formProperties`, the values are read from\n\t\t * `endpoint.loadUrl` (else `endpoint.url`) at `endpoint.responsePath`,\n\t\t * and the save sends the changed fields to `endpoint.url`. A failed\n\t\t * read toasts and opens nothing.\n\t\t *\n\t\t * @param {object} entry The open-form action with `endpoint` and `formProperties`.\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/open-form-endpoint/specs/open-form-endpoint/spec.md#requirement-an-open-form-action-can-edit-a-record-through-an-endpoint\n\t\t */\n\t\tasync openEndpointForm(entry) {\n\t\t\tconst properties = entry.formProperties && typeof entry.formProperties === 'object' && !Array.isArray(entry.formProperties) ? entry.formProperties : {}\n\t\t\tif (String(entry.endpoint.method || '').toUpperCase() === 'POST') {\n\t\t\t\tthis.formSchema = { type: 'object', properties }\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst loadUrl = entry.endpoint.loadUrl || entry.endpoint.url\n\t\t\tconst record = await fetchEndpointSource({ url: loadUrl, responsePath: entry.endpoint.responsePath || '' }, this.tokenCtx, { force: true })\n\t\t\t\t.catch(() => null)\n\t\t\tif (!record || typeof record !== 'object') {\n\t\t\t\tconst { showError } = await import('@nextcloud/dialogs')\n\t\t\t\tif (typeof showError === 'function') {\n\t\t\t\t\tshowError(t('nextcloud-vue', 'Could not open the form.'))\n\t\t\t\t}\n\t\t\t\tthis.formEntry = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.formItem = record\n\t\t\tthis.formSchema = { type: 'object', properties }\n\t\t},\n\n\t\t/**\n\t\t * Save an endpoint form: send the declared fields to `endpoint.url`\n\t\t * with `endpoint.method` (`PATCH` by default, or `PUT`), then toast and\n\t\t * refresh the page as an object save does.\n\t\t *\n\t\t * @param {object} entry The open-form action.\n\t\t * @param {object} formData The form's values.\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/open-form-endpoint/specs/open-form-endpoint/spec.md#requirement-an-open-form-action-can-edit-a-record-through-an-endpoint\n\t\t */\n\t\tasync saveEndpointForm(entry, formData) {\n\t\t\tconst dialog = this.$refs.formDialog\n\t\t\tconst keys = Object.keys(this.formSchema?.properties || {})\n\t\t\tconst body = Object.fromEntries(keys.filter((key) => key in formData).map((key) => [key, formData[key]]))\n\t\t\tconst verb = String(entry.endpoint.method || 'PATCH').toUpperCase()\n\t\t\tconst method = verb === 'PUT' ? 'put' : (verb === 'POST' ? 'post' : 'patch')\n\t\t\ttry {\n\t\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\t\timport('@nextcloud/router'),\n\t\t\t\t])\n\t\t\t\tconst url = interpolateActionTarget(entry.endpoint.url, this.tokenCtx)\n\t\t\t\tconst response = await axios[method](/^https?:\\/\\//i.test(url) ? url : generateUrl(url), body)\n\t\t\t\tif (dialog && typeof dialog.setResult === 'function') {\n\t\t\t\t\tdialog.setResult({ success: true })\n\t\t\t\t}\n\t\t\t\tconst { showSuccess } = await import('@nextcloud/dialogs')\n\t\t\t\tif (typeof showSuccess === 'function') {\n\t\t\t\t\tshowSuccess(this.tr(entry.successMessage) || t('nextcloud-vue', 'Saved.'))\n\t\t\t\t}\n\t\t\t\tconst { emit } = await import('@nextcloud/event-bus')\n\t\t\t\temit('cn:page:refresh', {})\n\t\t\t\tthis.$emit('created', response && response.data)\n\t\t\t} catch (error) {\n\t\t\t\tconst serverMessage = error && error.response && error.response.data\n\t\t\t\t\t&& (error.response.data.error || error.response.data.message)\n\t\t\t\tif (dialog && typeof dialog.setResult === 'function') {\n\t\t\t\t\tdialog.setResult({ error: serverMessage || this.tr(entry.errorMessage) || t('nextcloud-vue', 'Save failed.') })\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Open (or skip) the `run-node` config dialog: GET the target node's\n\t\t * describe endpoint (`or-flow-run-node`'s `GET .../{flowId}/{nodeId}/run`),\n\t\t * and either mount `CnRunNodeDialog` with its declared fields, or —\n\t\t * for a node with none — run immediately with an empty config. A\n\t\t * failed describe call toasts and does neither (RN-4: this resolves\n\t\t * as soon as the decision is made, never waiting on the person).\n\t\t *\n\t\t * @param {object} entry The run-node action (`flowId`, `nodeId`, ...).\n\t\t * @return {Promise<void>}\n\t\t *\n\t\t * @spec openspec/changes/manifest-run-node-action/specs/manifest-run-node-action/spec.md#requirement-the-nodes-own-config-form-drives-the-dialog-not-a-new-token\n\t\t */\n\t\tasync openRunNode(entry) {\n\t\t\tconst flowId = entry.flowId\n\t\t\tconst nodeId = entry.nodeId\n\t\t\tif (!flowId || !nodeId) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn('[CnActionButtons] run-node requires flowId and nodeId.', entry)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// The try assigns it and the catch returns, so an initialiser here\n\t\t\t// would never be read.\n\t\t\tlet fields\n\t\t\ttry {\n\t\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\t\timport('@nextcloud/router'),\n\t\t\t\t])\n\t\t\t\tconst url = generateUrl(`/apps/openregister/api/flows/${encodeURIComponent(flowId)}/nodes/${encodeURIComponent(nodeId)}/run`)\n\t\t\t\tconst response = await axios.get(url)\n\t\t\t\tfields = Array.isArray(response.data && response.data.configForm) ? response.data.configForm : []\n\t\t\t} catch (error) {\n\t\t\t\tconst { showError } = await import('@nextcloud/dialogs')\n\t\t\t\tconst serverMessage = error && error.response && error.response.data\n\t\t\t\t\t&& (error.response.data.error || error.response.data.message)\n\t\t\t\tif (typeof showError === 'function') {\n\t\t\t\t\tshowError(serverMessage || t('nextcloud-vue', 'Could not open this action.'))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (!fields.length) {\n\t\t\t\t// Empty (or undeclared) form: the \"empty form is itself\n\t\t\t\t// information\" rule flow-node-config-forms already established\n\t\t\t\t// for the editor — run immediately, no dialog.\n\t\t\t\tthis.actionPending[entry.id] = true\n\t\t\t\tawait this.runNodePost(entry, {})\n\t\t\t\tthis.actionPending[entry.id] = false\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.runNodeFields = fields\n\t\t\tthis.runNodeEntry = entry\n\t\t},\n\n\t\t/**\n\t\t * The run-node dialog's confirm: POST the collected config, close the\n\t\t * dialog either way (success or failure both end the interaction —\n\t\t * a failure already toasted its own message).\n\t\t *\n\t\t * @param {object} config The dialog's collected field values.\n\t\t * @return {Promise<void>}\n\t\t *\n\t\t * @spec openspec/changes/manifest-run-node-action/specs/manifest-run-node-action/spec.md#requirement-a-run-node-action-invokes-one-flow-node-against-the-page-object\n\t\t */\n\t\tasync onRunNodeConfirm(config) {\n\t\t\tconst entry = this.runNodeEntry\n\t\t\tif (!entry) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.actionPending[entry.id] = true\n\t\t\tawait this.runNodePost(entry, config)\n\t\t\tthis.actionPending[entry.id] = false\n\t\t\tthis.closeRunNode()\n\t\t},\n\n\t\t/**\n\t\t * POST the run-node call through the shared dispatcher's `postRunNode`\n\t\t * (subject/register/schema resolution, fail-closed, toast + refresh —\n\t\t * see its own docblock in actionsDispatcher.js).\n\t\t *\n\t\t * @param {object} entry The run-node action.\n\t\t * @param {object} config The node's collected config.\n\t\t * @return {Promise<{ok: boolean}>}\n\t\t */\n\t\trunNodePost(entry, config) {\n\t\t\treturn postRunNode(entry, { tokenCtx: this.tokenCtx, translate: this.effectiveTranslate }, config)\n\t\t},\n\n\t\t/** Close the run-node dialog without running anything. */\n\t\tcloseRunNode() {\n\t\t\tthis.runNodeEntry = null\n\t\t\tthis.runNodeFields = []\n\t\t},\n\n\t\t/**\n\t\t * The create dialog's confirm: save the new object through the shared\n\t\t * object store, report the result phase, toast, refresh the page, and\n\t\t * navigate to `onSuccessRoute` when set — merging the saved object's id\n\t\t * into the route params (via `buildOnSuccessRoute`) so the navigation\n\t\t * can deep-link to the created object's detail page.\n\t\t *\n\t\t * @param {object} formData The dialog's form payload.\n\t\t * @param {object|null} [dynamic] The answers to the schema's data-driven questions (`{ answers, declarations }`), when it has any.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onFormConfirm(formData, dynamic = null) {\n\t\t\tconst entry = this.formEntry\n\t\t\tif (entry && entry.endpoint) {\n\t\t\t\tawait this.saveEndpointForm(entry, formData)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst dialog = this.$refs.formDialog\n\t\t\tconst register = this.formRegister\n\t\t\tconst schema = (entry && entry.schema) || ''\n\t\t\ttry {\n\t\t\t\tconst store = useObjectStore()\n\t\t\t\tconst type = resolveObjectOpType(store, { register, schema })\n\t\t\t\t// A schema can require a field the form cannot supply — a\n\t\t\t\t// server-minted foreign key, say — in which case a straight\n\t\t\t\t// saveObject 400s. `createOverride` names a registry handler\n\t\t\t\t// that owns the persist instead (the CnIndexPage precedent).\n\t\t\t\tconst override = resolveCreateOverrideHandler(\n\t\t\t\t\tentry && entry.createOverride,\n\t\t\t\t\tthis.cnRegistry,\n\t\t\t\t\tthis.cnCustomComponents,\n\t\t\t\t)\n\t\t\t\tconst payload = { ...formData }\n\t\t\t\t// Array-mode declarations fold their answers into the parent's\n\t\t\t\t// own payload, so the whole record saves in ONE write. The\n\t\t\t\t// child-record mode below cannot do that (the rows need the\n\t\t\t\t// parent's id), which is why it writes afterwards and can leave\n\t\t\t\t// answers behind if that second write fails.\n\t\t\t\tthis.foldArrayAnswers(payload, dynamic)\n\t\t\t\tconst saved = override\n\t\t\t\t\t? await override(payload, { register, schema, type })\n\t\t\t\t\t: await store.saveObject(type, payload)\n\t\t\t\tif (!saved) {\n\t\t\t\t\tthrow new Error('save rejected')\n\t\t\t\t}\n\t\t\t\tawait this.saveDynamicAnswers(store, register, dynamic, saved)\n\t\t\t\tif (dialog && typeof dialog.setResult === 'function') {\n\t\t\t\t\tdialog.setResult({ success: true })\n\t\t\t\t}\n\t\t\t\tconst { showSuccess } = await import('@nextcloud/dialogs')\n\t\t\t\tif (typeof showSuccess === 'function') {\n\t\t\t\t\tshowSuccess((entry && entry.successMessage) || t('nextcloud-vue', 'Saved.'))\n\t\t\t\t}\n\t\t\t\tconst { emit } = await import('@nextcloud/event-bus')\n\t\t\t\temit('cn:page:refresh', {})\n\t\t\t\t/**\n\t\t\t\t * @event created Emitted after an open-form action saves. Payload: the created object.\n\t\t\t\t */\n\t\t\t\tthis.$emit('created', saved)\n\t\t\t\tif (entry && entry.onSuccessRoute && this.effectiveRouter) {\n\t\t\t\t\tconst location = buildOnSuccessRoute(entry.onSuccessRoute, saved)\n\t\t\t\t\tif (location) {\n\t\t\t\t\t\tthis.effectiveRouter.push(location).catch(() => {})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tif (dialog && typeof dialog.setResult === 'function') {\n\t\t\t\t\tdialog.setResult({ error: (e && e.message) || t('nextcloud-vue', 'Save failed.') })\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Write the answers to a schema's data-driven questions, once the\n\t\t * object they belong to exists.\n\t\t *\n\t\t * This runs AFTER the parent save and deliberately does not roll it\n\t\t * back on failure: a case that exists without one of its custom values\n\t\t * is recoverable by editing it, whereas discarding a case the user\n\t\t * believes they filed is not. A failure here surfaces as the dialog's\n\t\t * error so it is never silent.\n\t\t *\n\t\t * @param {object} store The object store.\n\t\t * @param {string} register The register the object was saved in.\n\t\t * @param {object|null} dynamic The `{ answers, declarations }` payload.\n\t\t * @param {object} saved The saved parent object.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync saveDynamicAnswers(store, register, dynamic, saved) {\n\t\t\tif (!dynamic || !Array.isArray(dynamic.answers) || dynamic.answers.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst objectId = saved && (saved.id || saved.uuid)\n\t\t\tif (!objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst declarations = dynamic.declarations || []\n\t\t\tfor (const { key, config } of declarations) {\n\t\t\t\tconst values = config && config.values\n\t\t\t\tif (!values || !values.schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\t// Already written as part of the parent payload.\n\t\t\t\tif (usesArrayValues(config)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\t// An answer belongs to exactly one declaration. With a single\n\t\t\t\t// declaration every answer carries its key anyway; the filter\n\t\t\t\t// only matters once a schema has two, where writing an answer\n\t\t\t\t// to both value schemas would invent a row nobody entered.\n\t\t\t\tconst mine = declarations.length === 1\n\t\t\t\t\t? dynamic.answers\n\t\t\t\t\t: dynamic.answers.filter((a) => a.declarationKey === key)\n\t\t\t\tconst rows = valueRecordsFor(mine, config, objectId)\n\t\t\t\tif (rows.length === 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst valueRegister = values.register || register\n\t\t\t\tconst type = resolveObjectOpType(store, { register: valueRegister, schema: values.schema })\n\t\t\t\tfor (const row of rows) {\n\t\t\t\t\tawait store.saveObject(type, row)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Fold array-mode dynamic answers into the parent payload.\n\t\t *\n\t\t * @param {object} payload The parent object payload, mutated in place.\n\t\t * @param {object|null} dynamic The `{ answers, declarations }` payload.\n\t\t * @return {void}\n\t\t */\n\t\tfoldArrayAnswers(payload, dynamic) {\n\t\t\tif (!dynamic || !Array.isArray(dynamic.answers) || dynamic.answers.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst declarations = dynamic.declarations || []\n\t\t\tfor (const { key, config } of declarations) {\n\t\t\t\tif (!usesArrayValues(config)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst mine = declarations.length === 1\n\t\t\t\t\t? dynamic.answers\n\t\t\t\t\t: dynamic.answers.filter((a) => a.declarationKey === key)\n\t\t\t\t// Each answer goes to the array its definition is routed to\n\t\t\t\t// (`values.arrayKeyBy`), merged over what that array holds.\n\t\t\t\tfor (const [arrayKey, subset] of Object.entries(answersByArrayKey(mine, config, dynamic.definitions || []))) {\n\t\t\t\t\tconst entries = mergeValueArray(payload[arrayKey], subset, config, dynamic.definitions || [])\n\t\t\t\t\tif (entries.length || Array.isArray(payload[arrayKey])) {\n\t\t\t\t\t\tpayload[arrayKey] = entries\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Close the create dialog.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tcloseForm() {\n\t\t\tthis.formEntry = null\n\t\t\tthis.formSchema = null\n\t\t\tthis.formItem = null\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-action-buttons {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 8px;\n\tflex-wrap: wrap;\n}\n\n/* In menu mode the only children are dialogs, so the wrapper must not be a\n box: an empty inline-flex child still eats one of the header's flex gaps\n and pushes the Actions menu away from the Edit button beside it. */\n.cn-action-buttons--menu {\n\tdisplay: contents;\n}\n</style>\n"],"names":["_normalizeClass","_createElementBlock","_Fragment","_renderList","_createBlock","_createTextVNode","_toDisplayString"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import script from './CnConditionListEditor.vue2.js';
2
+ import { render } from './CnConditionListEditor.vue3.js';
3
+
4
+ script.render = render;
5
+ script.__scopeId = "data-v-f00a4cf2";
6
+ const __sfc_main = script;
7
+
8
+ export { __sfc_main as default };
9
+ //# sourceMappingURL=CnConditionListEditor.vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnConditionListEditor.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
@@ -0,0 +1,237 @@
1
+ import { translate } from '@nextcloud/l10n';
2
+ import { NcButton, NcSelect, NcTextField } from '@nextcloud/vue';
3
+ import Close from 'vue-material-design-icons/Close.vue';
4
+ import Plus from 'vue-material-design-icons/Plus.vue';
5
+ import { operatorsForType, operatorLabel, operatorTakesValue } from './conditionOperators.js';
6
+
7
+ /**
8
+ * CnConditionListEditor - the conditions of a rule as rows over an object's
9
+ * own fields: field, operator, value. All rows hold together. The operators
10
+ * follow the field's type (see `operatorsForType`), "is filled in" needs no
11
+ * value, and the value is a choice for a choice or yes/no field.
12
+ *
13
+ * v-model on `{ field, operator, value }[]`, in both dialects (`value`/`input`
14
+ * and `modelValue`/`update:modelValue`). Read a condition back as words with
15
+ * `conditionSentence` from `./conditionOperators.js`.
16
+ */
17
+ var script = {
18
+ name: 'CnConditionListEditor',
19
+
20
+ components: { NcButton, NcSelect, NcTextField, Close, Plus },
21
+
22
+ props: {
23
+ /**
24
+ * The conditions.
25
+ *
26
+ * @type {Array<{field: string, operator: string, value?: unknown}>}
27
+ */
28
+ value: { type: Array, default: () => [] },
29
+ /**
30
+ * The same value under Vue 3's v-model name; wins over `value` when passed.
31
+ *
32
+ * @type {Array}
33
+ */
34
+ modelValue: { type: Array, default: undefined },
35
+ /**
36
+ * The fields a condition may name: `{ id, label, type, options?, format? }`.
37
+ * `type` is number, integer, date, boolean, presence or anything else (text);
38
+ * `options` (`[{ id, label }]`) make the value a choice.
39
+ *
40
+ * @type {Array<{id: string, label: string, type?: string, options?: Array}>}
41
+ */
42
+ fields: { type: Array, default: () => [] },
43
+ /**
44
+ * The legend above the rows, already translated.
45
+ *
46
+ * @type {string}
47
+ */
48
+ label: { type: String, default: '' },
49
+ /**
50
+ * What an empty list says, already translated.
51
+ *
52
+ * @type {string}
53
+ */
54
+ emptyText: { type: String, default: '' },
55
+ },
56
+
57
+ emits: ['input', 'update:modelValue'],
58
+
59
+ data() {
60
+ return {
61
+ rows: this.copy(this.modelValue !== undefined ? this.modelValue : this.value),
62
+ }
63
+ },
64
+
65
+ computed: {
66
+ /**
67
+ * The field choices.
68
+ *
69
+ * @return {Array<{id: string, label: string}>}
70
+ */
71
+ fieldOptions() {
72
+ return this.fields.map((f) => ({ id: f.id, label: f.label || f.id }))
73
+ },
74
+ },
75
+
76
+ watch: {
77
+ value(next) {
78
+ if (this.modelValue === undefined) {
79
+ this.rows = this.copy(next);
80
+ }
81
+ },
82
+
83
+ modelValue(next) {
84
+ if (next !== undefined) {
85
+ this.rows = this.copy(next);
86
+ }
87
+ },
88
+ },
89
+
90
+ methods: {
91
+ t: translate,
92
+
93
+ /**
94
+ * A detached copy of the bound conditions.
95
+ *
96
+ * @param {Array} list The conditions.
97
+ * @return {Array} The copy.
98
+ */
99
+ copy(list) {
100
+ return (Array.isArray(list) ? list : []).map((row) => ({ ...row }))
101
+ },
102
+
103
+ /**
104
+ * The field with this id.
105
+ *
106
+ * @param {string} id The field id.
107
+ * @return {object|undefined}
108
+ */
109
+ fieldOf(id) {
110
+ return this.fields.find((f) => f.id === id)
111
+ },
112
+
113
+ fieldOption(id) {
114
+ return this.fieldOptions.find((o) => o.id === id) || null
115
+ },
116
+
117
+ operatorOptions(fieldId) {
118
+ return operatorsForType(this.fieldOf(fieldId)?.type).map((id) => ({ id, label: operatorLabel(id) }))
119
+ },
120
+
121
+ operatorOption(operator) {
122
+ return operator ? { id: operator, label: operatorLabel(operator) } : null
123
+ },
124
+
125
+ takesValue(operator) {
126
+ return operatorTakesValue(operator)
127
+ },
128
+
129
+ /**
130
+ * The value choices of a field, or null when its value is typed.
131
+ *
132
+ * @param {string} fieldId The field id.
133
+ * @return {Array<{id: string, label: string}>|null}
134
+ */
135
+ valueOptions(fieldId) {
136
+ const field = this.fieldOf(fieldId);
137
+ if (field?.type === 'boolean') {
138
+ return [
139
+ { id: true, label: translate('nextcloud-vue', 'Yes') },
140
+ { id: false, label: translate('nextcloud-vue', 'No') },
141
+ ]
142
+ }
143
+ if (Array.isArray(field?.options) && field.options.length > 0) {
144
+ return field.options.map((o) => (typeof o === 'object'
145
+ ? { id: o.id ?? o.value, label: o.label ?? String(o.id ?? o.value) }
146
+ : { id: o, label: String(o) }))
147
+ }
148
+ return null
149
+ },
150
+
151
+ valueOption(row) {
152
+ return (this.valueOptions(row.field) || []).find((o) => String(o.id) === String(row.value)) || null
153
+ },
154
+
155
+ inputType(fieldId) {
156
+ const type = this.fieldOf(fieldId)?.type;
157
+ if (type === 'number' || type === 'integer') {
158
+ return 'number'
159
+ }
160
+ return type === 'date' ? 'date' : 'text'
161
+ },
162
+
163
+ /**
164
+ * A typed value as stored: a number for a number field.
165
+ *
166
+ * @param {string} fieldId The field id.
167
+ * @param {string} raw What was typed.
168
+ * @return {string|number}
169
+ */
170
+ typedValue(fieldId, raw) {
171
+ const type = this.fieldOf(fieldId)?.type;
172
+ if ((type === 'number' || type === 'integer') && raw !== '' && !Number.isNaN(Number(raw))) {
173
+ return Number(raw)
174
+ }
175
+ return raw
176
+ },
177
+
178
+ setField(i, option) {
179
+ const field = this.fieldOf(option?.id);
180
+ const operators = operatorsForType(field?.type);
181
+ this.rows[i] = { field: option?.id || '', operator: operators[0], value: field?.type === 'boolean' ? true : '' };
182
+ this.emitRows();
183
+ },
184
+
185
+ setOperator(i, option) {
186
+ const row = { ...this.rows[i], operator: option?.id || 'eq' };
187
+ if (!operatorTakesValue(row.operator)) {
188
+ delete row.value;
189
+ } else if (row.value === undefined) {
190
+ row.value = '';
191
+ }
192
+ this.rows[i] = row;
193
+ this.emitRows();
194
+ },
195
+
196
+ setValue(i, value) {
197
+ this.rows[i] = { ...this.rows[i], value };
198
+ this.emitRows();
199
+ },
200
+
201
+ add() {
202
+ this.rows.push({ field: '', operator: 'eq', value: '' });
203
+ this.emitRows();
204
+ },
205
+
206
+ remove(i) {
207
+ this.rows.splice(i, 1);
208
+ this.emitRows();
209
+ },
210
+
211
+ /**
212
+ * Tell the consumer, in both v-model dialects.
213
+ *
214
+ * @return {void}
215
+ */
216
+ emitRows() {
217
+ const next = this.copy(this.rows);
218
+ /**
219
+ * The conditions changed.
220
+ *
221
+ * @event input
222
+ * @type {Array}
223
+ */
224
+ this.$emit('input', next);
225
+ /**
226
+ * The conditions changed (Vue 3 v-model).
227
+ *
228
+ * @event update:modelValue
229
+ * @type {Array}
230
+ */
231
+ this.$emit('update:modelValue', next);
232
+ },
233
+ },
234
+ };
235
+
236
+ export { script as default };
237
+ //# sourceMappingURL=CnConditionListEditor.vue2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnConditionListEditor.vue2.js","sources":["../../../../src/components/CnConditionListEditor/CnConditionListEditor.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<fieldset class=\"cn-condition-list\">\n\t\t<legend v-if=\"label\" class=\"cn-condition-list__label\">\n\t\t\t{{ label }}\n\t\t</legend>\n\t\t<p v-if=\"rows.length === 0\" class=\"cn-condition-list__empty\">\n\t\t\t{{ emptyText || t('nextcloud-vue', 'No conditions: the rule always applies.') }}\n\t\t</p>\n\t\t<div\n\t\t\tv-for=\"(row, i) in rows\"\n\t\t\t:key=\"i\"\n\t\t\tclass=\"cn-condition-list__row\">\n\t\t\t<NcSelect\n\t\t\t\tclass=\"cn-condition-list__field\"\n\t\t\t\t:modelValue=\"fieldOption(row.field)\"\n\t\t\t\t:options=\"fieldOptions\"\n\t\t\t\tlabel=\"label\"\n\t\t\t\t:inputLabel=\"t('nextcloud-vue', 'Field')\"\n\t\t\t\t:clearable=\"false\"\n\t\t\t\t@update:modelValue=\"setField(i, $event)\" />\n\t\t\t<NcSelect\n\t\t\t\tclass=\"cn-condition-list__operator\"\n\t\t\t\t:modelValue=\"operatorOption(row.operator)\"\n\t\t\t\t:options=\"operatorOptions(row.field)\"\n\t\t\t\tlabel=\"label\"\n\t\t\t\t:inputLabel=\"t('nextcloud-vue', 'Operator')\"\n\t\t\t\t:clearable=\"false\"\n\t\t\t\t:disabled=\"!row.field\"\n\t\t\t\t@update:modelValue=\"setOperator(i, $event)\" />\n\t\t\t<template v-if=\"takesValue(row.operator)\">\n\t\t\t\t<NcSelect\n\t\t\t\t\tv-if=\"valueOptions(row.field)\"\n\t\t\t\t\tclass=\"cn-condition-list__value\"\n\t\t\t\t\t:modelValue=\"valueOption(row)\"\n\t\t\t\t\t:options=\"valueOptions(row.field)\"\n\t\t\t\t\tlabel=\"label\"\n\t\t\t\t\t:inputLabel=\"t('nextcloud-vue', 'Value')\"\n\t\t\t\t\t:clearable=\"false\"\n\t\t\t\t\t@update:modelValue=\"setValue(i, $event ? $event.id : '')\" />\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else\n\t\t\t\t\tclass=\"cn-condition-list__value\"\n\t\t\t\t\t:type=\"inputType(row.field)\"\n\t\t\t\t\t:modelValue=\"row.value === undefined || row.value === null ? '' : String(row.value)\"\n\t\t\t\t\t:label=\"t('nextcloud-vue', 'Value')\"\n\t\t\t\t\t@update:modelValue=\"setValue(i, typedValue(row.field, $event))\" />\n\t\t\t</template>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Remove condition')\"\n\t\t\t\t@click=\"remove(i)\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Close :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<NcButton variant=\"tertiary\" :disabled=\"fields.length === 0\" @click=\"add\">\n\t\t\t<template #icon>\n\t\t\t\t<Plus :size=\"18\" />\n\t\t\t</template>\n\t\t\t{{ t('nextcloud-vue', 'Add condition') }}\n\t\t</NcButton>\n\t</fieldset>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcSelect, NcTextField } from '@nextcloud/vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport { operatorLabel, operatorsForType, operatorTakesValue } from './conditionOperators.js'\n\n/**\n * CnConditionListEditor - the conditions of a rule as rows over an object's\n * own fields: field, operator, value. All rows hold together. The operators\n * follow the field's type (see `operatorsForType`), \"is filled in\" needs no\n * value, and the value is a choice for a choice or yes/no field.\n *\n * v-model on `{ field, operator, value }[]`, in both dialects (`value`/`input`\n * and `modelValue`/`update:modelValue`). Read a condition back as words with\n * `conditionSentence` from `./conditionOperators.js`.\n */\nexport default {\n\tname: 'CnConditionListEditor',\n\n\tcomponents: { NcButton, NcSelect, NcTextField, Close, Plus },\n\n\tprops: {\n\t\t/**\n\t\t * The conditions.\n\t\t *\n\t\t * @type {Array<{field: string, operator: string, value?: unknown}>}\n\t\t */\n\t\tvalue: { type: Array, default: () => [] },\n\t\t/**\n\t\t * The same value under Vue 3's v-model name; wins over `value` when passed.\n\t\t *\n\t\t * @type {Array}\n\t\t */\n\t\tmodelValue: { type: Array, default: undefined },\n\t\t/**\n\t\t * The fields a condition may name: `{ id, label, type, options?, format? }`.\n\t\t * `type` is number, integer, date, boolean, presence or anything else (text);\n\t\t * `options` (`[{ id, label }]`) make the value a choice.\n\t\t *\n\t\t * @type {Array<{id: string, label: string, type?: string, options?: Array}>}\n\t\t */\n\t\tfields: { type: Array, default: () => [] },\n\t\t/**\n\t\t * The legend above the rows, already translated.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tlabel: { type: String, default: '' },\n\t\t/**\n\t\t * What an empty list says, already translated.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\temptyText: { type: String, default: '' },\n\t},\n\n\temits: ['input', 'update:modelValue'],\n\n\tdata() {\n\t\treturn {\n\t\t\trows: this.copy(this.modelValue !== undefined ? this.modelValue : this.value),\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The field choices.\n\t\t *\n\t\t * @return {Array<{id: string, label: string}>}\n\t\t */\n\t\tfieldOptions() {\n\t\t\treturn this.fields.map((f) => ({ id: f.id, label: f.label || f.id }))\n\t\t},\n\t},\n\n\twatch: {\n\t\tvalue(next) {\n\t\t\tif (this.modelValue === undefined) {\n\t\t\t\tthis.rows = this.copy(next)\n\t\t\t}\n\t\t},\n\n\t\tmodelValue(next) {\n\t\t\tif (next !== undefined) {\n\t\t\t\tthis.rows = this.copy(next)\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * A detached copy of the bound conditions.\n\t\t *\n\t\t * @param {Array} list The conditions.\n\t\t * @return {Array} The copy.\n\t\t */\n\t\tcopy(list) {\n\t\t\treturn (Array.isArray(list) ? list : []).map((row) => ({ ...row }))\n\t\t},\n\n\t\t/**\n\t\t * The field with this id.\n\t\t *\n\t\t * @param {string} id The field id.\n\t\t * @return {object|undefined}\n\t\t */\n\t\tfieldOf(id) {\n\t\t\treturn this.fields.find((f) => f.id === id)\n\t\t},\n\n\t\tfieldOption(id) {\n\t\t\treturn this.fieldOptions.find((o) => o.id === id) || null\n\t\t},\n\n\t\toperatorOptions(fieldId) {\n\t\t\treturn operatorsForType(this.fieldOf(fieldId)?.type).map((id) => ({ id, label: operatorLabel(id) }))\n\t\t},\n\n\t\toperatorOption(operator) {\n\t\t\treturn operator ? { id: operator, label: operatorLabel(operator) } : null\n\t\t},\n\n\t\ttakesValue(operator) {\n\t\t\treturn operatorTakesValue(operator)\n\t\t},\n\n\t\t/**\n\t\t * The value choices of a field, or null when its value is typed.\n\t\t *\n\t\t * @param {string} fieldId The field id.\n\t\t * @return {Array<{id: string, label: string}>|null}\n\t\t */\n\t\tvalueOptions(fieldId) {\n\t\t\tconst field = this.fieldOf(fieldId)\n\t\t\tif (field?.type === 'boolean') {\n\t\t\t\treturn [\n\t\t\t\t\t{ id: true, label: t('nextcloud-vue', 'Yes') },\n\t\t\t\t\t{ id: false, label: t('nextcloud-vue', 'No') },\n\t\t\t\t]\n\t\t\t}\n\t\t\tif (Array.isArray(field?.options) && field.options.length > 0) {\n\t\t\t\treturn field.options.map((o) => (typeof o === 'object'\n\t\t\t\t\t? { id: o.id ?? o.value, label: o.label ?? String(o.id ?? o.value) }\n\t\t\t\t\t: { id: o, label: String(o) }))\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\tvalueOption(row) {\n\t\t\treturn (this.valueOptions(row.field) || []).find((o) => String(o.id) === String(row.value)) || null\n\t\t},\n\n\t\tinputType(fieldId) {\n\t\t\tconst type = this.fieldOf(fieldId)?.type\n\t\t\tif (type === 'number' || type === 'integer') {\n\t\t\t\treturn 'number'\n\t\t\t}\n\t\t\treturn type === 'date' ? 'date' : 'text'\n\t\t},\n\n\t\t/**\n\t\t * A typed value as stored: a number for a number field.\n\t\t *\n\t\t * @param {string} fieldId The field id.\n\t\t * @param {string} raw What was typed.\n\t\t * @return {string|number}\n\t\t */\n\t\ttypedValue(fieldId, raw) {\n\t\t\tconst type = this.fieldOf(fieldId)?.type\n\t\t\tif ((type === 'number' || type === 'integer') && raw !== '' && !Number.isNaN(Number(raw))) {\n\t\t\t\treturn Number(raw)\n\t\t\t}\n\t\t\treturn raw\n\t\t},\n\n\t\tsetField(i, option) {\n\t\t\tconst field = this.fieldOf(option?.id)\n\t\t\tconst operators = operatorsForType(field?.type)\n\t\t\tthis.rows[i] = { field: option?.id || '', operator: operators[0], value: field?.type === 'boolean' ? true : '' }\n\t\t\tthis.emitRows()\n\t\t},\n\n\t\tsetOperator(i, option) {\n\t\t\tconst row = { ...this.rows[i], operator: option?.id || 'eq' }\n\t\t\tif (!operatorTakesValue(row.operator)) {\n\t\t\t\tdelete row.value\n\t\t\t} else if (row.value === undefined) {\n\t\t\t\trow.value = ''\n\t\t\t}\n\t\t\tthis.rows[i] = row\n\t\t\tthis.emitRows()\n\t\t},\n\n\t\tsetValue(i, value) {\n\t\t\tthis.rows[i] = { ...this.rows[i], value }\n\t\t\tthis.emitRows()\n\t\t},\n\n\t\tadd() {\n\t\t\tthis.rows.push({ field: '', operator: 'eq', value: '' })\n\t\t\tthis.emitRows()\n\t\t},\n\n\t\tremove(i) {\n\t\t\tthis.rows.splice(i, 1)\n\t\t\tthis.emitRows()\n\t\t},\n\n\t\t/**\n\t\t * Tell the consumer, in both v-model dialects.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\temitRows() {\n\t\t\tconst next = this.copy(this.rows)\n\t\t\t/**\n\t\t\t * The conditions changed.\n\t\t\t *\n\t\t\t * @event input\n\t\t\t * @type {Array}\n\t\t\t */\n\t\t\tthis.$emit('input', next)\n\t\t\t/**\n\t\t\t * The conditions changed (Vue 3 v-model).\n\t\t\t *\n\t\t\t * @event update:modelValue\n\t\t\t * @type {Array}\n\t\t\t */\n\t\t\tthis.$emit('update:modelValue', next)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-condition-list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tborder: 0;\n\tmargin: 0;\n\tpadding: 0;\n\tmin-width: 0;\n}\n\n.cn-condition-list__label {\n\tfont-weight: 600;\n\tpadding: 0;\n\tmargin-bottom: 4px;\n}\n\n.cn-condition-list__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tmargin: 0;\n}\n\n.cn-condition-list__row {\n\tdisplay: grid;\n\tgrid-template-columns: minmax(0, 2fr) minmax(0, 1.4fr) minmax(0, 1.6fr) auto;\n\tgap: 8px;\n\talign-items: end;\n}\n\n.cn-condition-list__row > :nth-child(3):last-child {\n\tgrid-column: 4;\n}\n\n@media (max-width: 640px) {\n\t.cn-condition-list__row {\n\t\tgrid-template-columns: minmax(0, 1fr) auto;\n\t}\n}\n</style>\n"],"names":["t"],"mappings":"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"}