@conduction/nextcloud-vue 2.108.0 → 2.110.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 (161) hide show
  1. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
  2. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +115 -12
  3. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +36 -9
  5. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
  6. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue.js +10 -0
  7. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue.js.map +1 -0
  8. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue2.js +305 -0
  9. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue2.js.map +1 -0
  10. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue3.js +38 -0
  11. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue3.js.map +1 -0
  12. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +10 -0
  13. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js.map +1 -0
  14. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +398 -0
  15. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -0
  16. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js +155 -0
  17. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -0
  18. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue.js +9 -0
  19. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue.js.map +1 -0
  20. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue2.js +281 -0
  21. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue2.js.map +1 -0
  22. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue3.js +137 -0
  23. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue3.js.map +1 -0
  24. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue.js +9 -0
  25. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue.js.map +1 -0
  26. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue2.js +225 -0
  27. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue2.js.map +1 -0
  28. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue3.js +110 -0
  29. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue3.js.map +1 -0
  30. package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
  31. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +19 -3
  32. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  33. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  34. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -0
  35. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  36. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +513 -481
  37. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  38. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js +9 -0
  39. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js.map +1 -0
  40. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js +181 -0
  41. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js.map +1 -0
  42. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js +50 -0
  43. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js.map +1 -0
  44. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  45. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +102 -1
  46. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  47. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -6
  48. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  49. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue.js +9 -0
  50. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue.js.map +1 -0
  51. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue2.js +190 -0
  52. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue2.js.map +1 -0
  53. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue3.js +91 -0
  54. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue3.js.map +1 -0
  55. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue.js +9 -0
  56. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue.js.map +1 -0
  57. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue2.js +64 -0
  58. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue2.js.map +1 -0
  59. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue3.js +49 -0
  60. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue3.js.map +1 -0
  61. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue.js +9 -0
  62. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue.js.map +1 -0
  63. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue2.js +382 -0
  64. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue2.js.map +1 -0
  65. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue3.js +285 -0
  66. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue3.js.map +1 -0
  67. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js +9 -0
  68. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js.map +1 -0
  69. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js +277 -0
  70. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js.map +1 -0
  71. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js +183 -0
  72. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js.map +1 -0
  73. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +16 -14
  74. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  75. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +53 -22
  76. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  77. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue.js +9 -0
  78. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue.js.map +1 -0
  79. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue2.js +253 -0
  80. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue2.js.map +1 -0
  81. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue3.js +197 -0
  82. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue3.js.map +1 -0
  83. package/dist/esm/index.js +12 -0
  84. package/dist/esm/index.js.map +1 -1
  85. package/dist/esm/l10n/en.json.js +106 -1
  86. package/dist/esm/l10n/en.json.js.map +1 -1
  87. package/dist/esm/l10n/nl.json.js +105 -1
  88. package/dist/esm/l10n/nl.json.js.map +1 -1
  89. package/dist/esm/library-style.js +1 -1
  90. package/dist/esm/nextcloud-vue.css +510 -76
  91. package/dist/esm/utils/actionsDispatcher.js +11 -3
  92. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  93. package/dist/esm/utils/builtInModals.js +3 -1
  94. package/dist/esm/utils/builtInModals.js.map +1 -1
  95. package/dist/esm/utils/duplicateWarning.js +62 -0
  96. package/dist/esm/utils/duplicateWarning.js.map +1 -0
  97. package/dist/esm/utils/errors.js +4 -0
  98. package/dist/esm/utils/errors.js.map +1 -1
  99. package/dist/esm/utils/libraryWidgetKeys.js +4 -0
  100. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  101. package/dist/esm/utils/openDuplicateWarning.js +39 -0
  102. package/dist/esm/utils/openDuplicateWarning.js.map +1 -0
  103. package/dist/esm/utils/optionsFrom.js +69 -2
  104. package/dist/esm/utils/optionsFrom.js.map +1 -1
  105. package/dist/esm/utils/planTree.js +57 -0
  106. package/dist/esm/utils/planTree.js.map +1 -0
  107. package/dist/esm/utils/redactionSegments.js +53 -0
  108. package/dist/esm/utils/redactionSegments.js.map +1 -0
  109. package/dist/esm/utils/registerNcDashboardWidgets.js +61 -0
  110. package/dist/esm/utils/registerNcDashboardWidgets.js.map +1 -0
  111. package/dist/esm/utils/searchRefusal.js +62 -0
  112. package/dist/esm/utils/searchRefusal.js.map +1 -0
  113. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  114. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  115. package/dist/nextcloud-vue.cjs.js +17560 -12844
  116. package/dist/nextcloud-vue.cjs.js.map +2 -2
  117. package/dist/nextcloud-vue.css +510 -76
  118. package/l10n/en.json +106 -1
  119. package/l10n/nl.json +105 -1
  120. package/package.json +1 -1
  121. package/src/components/CnActionDialog/CnActionDialog.vue +134 -13
  122. package/src/components/CnAddressSearch/CnAddressSearch.vue +343 -0
  123. package/src/components/CnAddressSearch/index.js +12 -0
  124. package/src/components/CnAssignDialog/CnAssignDialog.vue +525 -0
  125. package/src/components/CnAssignDialog/index.js +12 -0
  126. package/src/components/CnDecisionListWidget/CnDecisionListWidget.vue +388 -0
  127. package/src/components/CnDecisionListWidget/index.js +12 -0
  128. package/src/components/CnDuplicateWarning/CnDuplicateWarning.vue +318 -0
  129. package/src/components/CnDuplicateWarning/index.js +12 -0
  130. package/src/components/CnFormDialog/CnFormDialog.vue +19 -3
  131. package/src/components/CnIndexPage/CnIndexPage.vue +52 -0
  132. package/src/components/CnNcDashboardList/CnNcDashboardList.vue +218 -0
  133. package/src/components/CnNcDashboardList/index.js +12 -0
  134. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +107 -6
  135. package/src/components/CnPlanWidget/CnPlanWidget.vue +246 -0
  136. package/src/components/CnPlanWidget/CnPlanWidgetRow.vue +117 -0
  137. package/src/components/CnPlanWidget/index.js +12 -0
  138. package/src/components/CnRedactionEditor/CnRedactionEditor.vue +655 -0
  139. package/src/components/CnRedactionEditor/index.js +13 -0
  140. package/src/components/CnRetentionWidget/CnRetentionWidget.vue +413 -0
  141. package/src/components/CnRetentionWidget/index.js +12 -0
  142. package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
  143. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +31 -0
  144. package/src/components/CnWorkQueue/CnWorkQueue.vue +463 -0
  145. package/src/components/CnWorkQueue/index.js +12 -0
  146. package/src/components/index.js +9 -0
  147. package/src/css/index-page.css +24 -0
  148. package/src/index.js +12 -0
  149. package/src/schemas/app-manifest-v2.schema.json +119 -3
  150. package/src/utils/actionsDispatcher.js +10 -2
  151. package/src/utils/builtInModals.js +3 -1
  152. package/src/utils/duplicateWarning.js +59 -0
  153. package/src/utils/errors.js +4 -0
  154. package/src/utils/libraryWidgetKeys.js +4 -0
  155. package/src/utils/openDuplicateWarning.js +35 -0
  156. package/src/utils/optionsFrom.js +68 -1
  157. package/src/utils/planTree.js +54 -0
  158. package/src/utils/redactionSegments.js +50 -0
  159. package/src/utils/registerNcDashboardWidgets.js +57 -0
  160. package/src/utils/searchRefusal.js +59 -0
  161. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -60,6 +60,7 @@ const BUILT_IN_WIDGET_KEYS = Object.freeze([
60
60
  'form-renderer',
61
61
  'map-viewer',
62
62
  'notice',
63
+ 'decision-list',
63
64
  'object-geo',
64
65
  'card-grid',
65
66
  'nav-card-grid',
@@ -135,10 +136,12 @@ const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
135
136
  'object-table',
136
137
  'party-actions',
137
138
  'people',
139
+ 'plan',
138
140
  'quicklinks',
139
141
  'radio-cards',
140
142
  'record-list',
141
143
  'related',
144
+ 'retention',
142
145
  'saved-view',
143
146
  'saved-views',
144
147
  'sections',
@@ -160,6 +163,7 @@ const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
160
163
  'video',
161
164
  'week-strip',
162
165
  'working-calendar',
166
+ 'work-queue',
163
167
  'workspace-filter',
164
168
  ]);
165
169
 
@@ -1 +1 @@
1
- {"version":3,"file":"libraryWidgetKeys.js","sources":["../../../src/utils/libraryWidgetKeys.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * libraryWidgetKeys — the one vocabulary of widget keys the LIBRARY itself\n * renders, and the only list `validateManifestV2` is allowed to consult.\n *\n * WHY THIS MODULE EXISTS\n * ----------------------\n * The validator used to carry its own hand-written array of \"library built-in\n * widget keys\", with a comment instructing whoever added a built-in to append\n * it \"in the same PR\". That instruction was followed for five keys and missed\n * for six, so the validator knew 11 keys while the library actually rendered\n * 45. Nothing failed when the two disagreed, because the only consumer is an\n * exemption: a key the validator does not recognise is treated as a CUSTOM\n * registry component, which makes a legitimate single-widget library dashboard\n * fail the ADR-036 Decision 1 anti-pattern rule with advice (\"declare it as\n * type:'custom'\") that is wrong for a library widget.\n *\n * A comment cannot enforce anything, so the array moved here and\n * `tests/utils/libraryWidgetKeys.spec.js` now binds it to the two registries\n * that actually resolve a key at runtime:\n *\n * 1. `BUILT_IN_WIDGETS` (components/CnWidgetGrid/builtInWidgets.js) — the v2\n * manifest widget set `CnWidgetGrid` resolves a `widgetKey` against.\n * 2. The dashboard widget catalog (`dashboardWidgetRegistry`), populated by\n * `registerDashboardWidgets.js`. `CnWidgetGrid` falls back to it (third in\n * the chain, after the consumer registry and `BUILT_IN_WIDGETS`), so a\n * catalog key renders on a v2 page exactly like a `BUILT_IN_WIDGETS` one.\n *\n * The test asserts SET EQUALITY in both directions against the live registries,\n * so adding a widget to either registry without touching this file fails CI,\n * and deleting one without removing it here fails too. The lists below are the\n * validator's view; the registries are the truth the test holds them to.\n *\n * THE CATALOG IS NOT LEGACY. It is a live half of the same vocabulary, and the\n * old array already conceded the point by exempting `chart` and `stats-block`,\n * which are catalog-only keys that `BUILT_IN_WIDGETS` has never held. What it\n * got wrong was doing that for two keys and not the other thirty-seven.\n * `widgetDispatch.js` unified the two resolution paths for the same reason: a\n * widget authored against one vocabulary was invisible to the other and said\n * nothing about why (101 of 236 detail widgets on hrmq).\n *\n * @module utils/libraryWidgetKeys\n */\n\nimport { WIDGET_TYPE_ALIASES } from './widgetTypeAliases.js'\n\n/**\n * Keys of `BUILT_IN_WIDGETS` — the v2 manifest widget set.\n *\n * Kept in the registry's own declaration order so a diff against\n * `builtInWidgets.js` reads straight down.\n *\n * @type {string[]} Frozen.\n */\nexport const BUILT_IN_WIDGET_KEYS = Object.freeze([\n\t'object-table',\n\t'form-renderer',\n\t'map-viewer',\n\t'notice',\n\t'object-geo',\n\t'card-grid',\n\t'nav-card-grid',\n\t'data',\n\t'metadata',\n\t'related',\n\t'integration',\n\t'banner',\n\t'audit-trail',\n\t'timeline',\n\t'header',\n\t'text',\n\t'divider',\n\t'saved-view-alert',\n\t'exports',\n])\n\n/**\n * Widget types the LIBRARY registers into the dashboard catalog at import time\n * (`registerDashboardWidgets.js` and the widget `index.js` modules it pulls in).\n *\n * Deliberately a static list rather than a read of `dashboardWidgetRegistry`:\n * that object is mutable and consumer-extensible, so deriving the exemption\n * from it at call time would let an app register its own full-page component in\n * the catalog and walk straight through the ADR-036 Decision 1 rule the\n * exemption exists to enforce. Only keys the library itself ships are exempt.\n *\n * Sorted, because unlike `BUILT_IN_WIDGETS` these registrations are spread over\n * ~30 files and have no meaningful declaration order.\n *\n * @type {string[]} Frozen.\n */\nexport const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([\n\t'audit-trail',\n\t'banner',\n\t'calendar',\n\t'chart',\n\t'checklist-run',\n\t'chips-field',\n\t'choice-picker',\n\t'container',\n\t'conversation',\n\t'countdown',\n\t'data',\n\t'delta',\n\t'device-frames',\n\t'divider',\n\t'document-review',\n\t'facts',\n\t'field-list',\n\t'files',\n\t'flow-runs',\n\t'gauge',\n\t'grouped-cards',\n\t'header',\n\t'image',\n\t'interaction-form',\n\t'kb-search',\n\t'label',\n\t'link',\n\t'links',\n\t'mail',\n\t'map',\n\t'markdown',\n\t'menu',\n\t'nc-widget',\n\t'next-step',\n\t'notepad',\n\t// `news` was here until the widget moved to LaunchPad, which owns the feed\n\t// fetching. An app that renders one now gets it from LaunchPad's registry.\n\t'object-geo',\n\t'object-list',\n\t'object-table',\n\t'party-actions',\n\t'people',\n\t'quicklinks',\n\t'radio-cards',\n\t'record-list',\n\t'related',\n\t'saved-view',\n\t'saved-views',\n\t'sections',\n\t'settings-form',\n\t'spend-analytics',\n\t'stacked-bar',\n\t'stages',\n\t'stat',\n\t'stats-block',\n\t'switch-rows',\n\t'table',\n\t'tabs',\n\t'tasks',\n\t'team-workload',\n\t'text',\n\t'tile',\n\t'timeline',\n\t'toggle-table',\n\t'video',\n\t'week-strip',\n\t'working-calendar',\n\t'workspace-filter',\n])\n\n/**\n * Expand a key set over `WIDGET_TYPE_ALIASES` so both spellings of an aliased\n * widget are present whenever either one is.\n *\n * `CnWidgetGrid` resolves a key and its canonical form (`object-table` ⇄\n * `table`, `map-viewer` ⇄ `map`), so both spellings render and both must be\n * exempt. Today each registry happens to hold one side of each pair, which is\n * exactly the accident that would silently stop being true when the next alias\n * lands.\n *\n * @param {Set<string>} keys The key set to expand, mutated in place.\n * @return {Set<string>} The same set.\n */\nfunction expandAliases(keys) {\n\tfor (const [alias, canonical] of Object.entries(WIDGET_TYPE_ALIASES)) {\n\t\tif (keys.has(alias) || keys.has(canonical)) {\n\t\t\tkeys.add(alias)\n\t\t\tkeys.add(canonical)\n\t\t}\n\t}\n\treturn keys\n}\n\n/**\n * Every widget key the library renders itself: both registries, plus both\n * spellings of every alias.\n *\n * A `widgetKey` in this list is served by a library `Cn*` SFC and does NOT\n * count as a custom registry component for the single-12×12-widget dashboard\n * rule (ADR-036 Decision 1).\n *\n * @type {string[]} Frozen.\n */\nexport const LIBRARY_WIDGET_KEYS = Object.freeze([\n\t...expandAliases(new Set([\n\t\t...BUILT_IN_WIDGET_KEYS,\n\t\t...DASHBOARD_CATALOG_WIDGET_KEYS,\n\t])),\n])\n"],"names":[],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC;AAClD,CAAC,cAAc;AACf,CAAC,eAAe;AAChB,CAAC,YAAY;AACb,CAAC,QAAQ;AACT,CAAC,YAAY;AACb,CAAC,WAAW;AACZ,CAAC,eAAe;AAChB,CAAC,MAAM;AACP,CAAC,UAAU;AACX,CAAC,SAAS;AACV,CAAC,aAAa;AACd,CAAC,QAAQ;AACT,CAAC,aAAa;AACd,CAAC,UAAU;AACX,CAAC,QAAQ;AACT,CAAC,MAAM;AACP,CAAC,SAAS;AACV,CAAC,kBAAkB;AACnB,CAAC,SAAS;AACV,CAAC,EAAC;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,6BAA6B,GAAG,MAAM,CAAC,MAAM,CAAC;AAC3D,CAAC,aAAa;AACd,CAAC,QAAQ;AACT,CAAC,UAAU;AACX,CAAC,OAAO;AACR,CAAC,eAAe;AAChB,CAAC,aAAa;AACd,CAAC,eAAe;AAChB,CAAC,WAAW;AACZ,CAAC,cAAc;AACf,CAAC,WAAW;AACZ,CAAC,MAAM;AACP,CAAC,OAAO;AACR,CAAC,eAAe;AAChB,CAAC,SAAS;AACV,CAAC,iBAAiB;AAClB,CAAC,OAAO;AACR,CAAC,YAAY;AACb,CAAC,OAAO;AACR,CAAC,WAAW;AACZ,CAAC,OAAO;AACR,CAAC,eAAe;AAChB,CAAC,QAAQ;AACT,CAAC,OAAO;AACR,CAAC,kBAAkB;AACnB,CAAC,WAAW;AACZ,CAAC,OAAO;AACR,CAAC,MAAM;AACP,CAAC,OAAO;AACR,CAAC,MAAM;AACP,CAAC,KAAK;AACN,CAAC,UAAU;AACX,CAAC,MAAM;AACP,CAAC,WAAW;AACZ,CAAC,WAAW;AACZ,CAAC,SAAS;AACV;AACA;AACA,CAAC,YAAY;AACb,CAAC,aAAa;AACd,CAAC,cAAc;AACf,CAAC,eAAe;AAChB,CAAC,QAAQ;AACT,CAAC,YAAY;AACb,CAAC,aAAa;AACd,CAAC,aAAa;AACd,CAAC,SAAS;AACV,CAAC,YAAY;AACb,CAAC,aAAa;AACd,CAAC,UAAU;AACX,CAAC,eAAe;AAChB,CAAC,iBAAiB;AAClB,CAAC,aAAa;AACd,CAAC,QAAQ;AACT,CAAC,MAAM;AACP,CAAC,aAAa;AACd,CAAC,aAAa;AACd,CAAC,OAAO;AACR,CAAC,MAAM;AACP,CAAC,OAAO;AACR,CAAC,eAAe;AAChB,CAAC,MAAM;AACP,CAAC,MAAM;AACP,CAAC,UAAU;AACX,CAAC,cAAc;AACf,CAAC,OAAO;AACR,CAAC,YAAY;AACb,CAAC,kBAAkB;AACnB,CAAC,kBAAkB;AACnB,CAAC,EAAC;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,aAAa,CAAC,IAAI,EAAE;AAC7B,CAAC,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE;AACvE,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE;AAC9C,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAC;AAClB,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAC;AACtB,GAAG;AACH,EAAE;AACF,CAAC,OAAO,IAAI;AACZ,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,mBAAmB,GAAG,MAAM,CAAC,MAAM,CAAC;AACjD,CAAC,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC;AAC1B,EAAE,GAAG,oBAAoB;AACzB,EAAE,GAAG,6BAA6B;AAClC,EAAE,CAAC,CAAC;AACJ,CAAC;;;;"}
1
+ {"version":3,"file":"libraryWidgetKeys.js","sources":["../../../src/utils/libraryWidgetKeys.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * libraryWidgetKeys — the one vocabulary of widget keys the LIBRARY itself\n * renders, and the only list `validateManifestV2` is allowed to consult.\n *\n * WHY THIS MODULE EXISTS\n * ----------------------\n * The validator used to carry its own hand-written array of \"library built-in\n * widget keys\", with a comment instructing whoever added a built-in to append\n * it \"in the same PR\". That instruction was followed for five keys and missed\n * for six, so the validator knew 11 keys while the library actually rendered\n * 45. Nothing failed when the two disagreed, because the only consumer is an\n * exemption: a key the validator does not recognise is treated as a CUSTOM\n * registry component, which makes a legitimate single-widget library dashboard\n * fail the ADR-036 Decision 1 anti-pattern rule with advice (\"declare it as\n * type:'custom'\") that is wrong for a library widget.\n *\n * A comment cannot enforce anything, so the array moved here and\n * `tests/utils/libraryWidgetKeys.spec.js` now binds it to the two registries\n * that actually resolve a key at runtime:\n *\n * 1. `BUILT_IN_WIDGETS` (components/CnWidgetGrid/builtInWidgets.js) — the v2\n * manifest widget set `CnWidgetGrid` resolves a `widgetKey` against.\n * 2. The dashboard widget catalog (`dashboardWidgetRegistry`), populated by\n * `registerDashboardWidgets.js`. `CnWidgetGrid` falls back to it (third in\n * the chain, after the consumer registry and `BUILT_IN_WIDGETS`), so a\n * catalog key renders on a v2 page exactly like a `BUILT_IN_WIDGETS` one.\n *\n * The test asserts SET EQUALITY in both directions against the live registries,\n * so adding a widget to either registry without touching this file fails CI,\n * and deleting one without removing it here fails too. The lists below are the\n * validator's view; the registries are the truth the test holds them to.\n *\n * THE CATALOG IS NOT LEGACY. It is a live half of the same vocabulary, and the\n * old array already conceded the point by exempting `chart` and `stats-block`,\n * which are catalog-only keys that `BUILT_IN_WIDGETS` has never held. What it\n * got wrong was doing that for two keys and not the other thirty-seven.\n * `widgetDispatch.js` unified the two resolution paths for the same reason: a\n * widget authored against one vocabulary was invisible to the other and said\n * nothing about why (101 of 236 detail widgets on hrmq).\n *\n * @module utils/libraryWidgetKeys\n */\n\nimport { WIDGET_TYPE_ALIASES } from './widgetTypeAliases.js'\n\n/**\n * Keys of `BUILT_IN_WIDGETS` — the v2 manifest widget set.\n *\n * Kept in the registry's own declaration order so a diff against\n * `builtInWidgets.js` reads straight down.\n *\n * @type {string[]} Frozen.\n */\nexport const BUILT_IN_WIDGET_KEYS = Object.freeze([\n\t'object-table',\n\t'form-renderer',\n\t'map-viewer',\n\t'notice',\n\t'decision-list',\n\t'object-geo',\n\t'card-grid',\n\t'nav-card-grid',\n\t'data',\n\t'metadata',\n\t'related',\n\t'integration',\n\t'banner',\n\t'audit-trail',\n\t'timeline',\n\t'header',\n\t'text',\n\t'divider',\n\t'saved-view-alert',\n\t'exports',\n])\n\n/**\n * Widget types the LIBRARY registers into the dashboard catalog at import time\n * (`registerDashboardWidgets.js` and the widget `index.js` modules it pulls in).\n *\n * Deliberately a static list rather than a read of `dashboardWidgetRegistry`:\n * that object is mutable and consumer-extensible, so deriving the exemption\n * from it at call time would let an app register its own full-page component in\n * the catalog and walk straight through the ADR-036 Decision 1 rule the\n * exemption exists to enforce. Only keys the library itself ships are exempt.\n *\n * Sorted, because unlike `BUILT_IN_WIDGETS` these registrations are spread over\n * ~30 files and have no meaningful declaration order.\n *\n * @type {string[]} Frozen.\n */\nexport const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([\n\t'audit-trail',\n\t'banner',\n\t'calendar',\n\t'chart',\n\t'checklist-run',\n\t'chips-field',\n\t'choice-picker',\n\t'container',\n\t'conversation',\n\t'countdown',\n\t'data',\n\t'delta',\n\t'device-frames',\n\t'divider',\n\t'document-review',\n\t'facts',\n\t'field-list',\n\t'files',\n\t'flow-runs',\n\t'gauge',\n\t'grouped-cards',\n\t'header',\n\t'image',\n\t'interaction-form',\n\t'kb-search',\n\t'label',\n\t'link',\n\t'links',\n\t'mail',\n\t'map',\n\t'markdown',\n\t'menu',\n\t'nc-widget',\n\t'next-step',\n\t'notepad',\n\t// `news` was here until the widget moved to LaunchPad, which owns the feed\n\t// fetching. An app that renders one now gets it from LaunchPad's registry.\n\t'object-geo',\n\t'object-list',\n\t'object-table',\n\t'party-actions',\n\t'people',\n\t'plan',\n\t'quicklinks',\n\t'radio-cards',\n\t'record-list',\n\t'related',\n\t'retention',\n\t'saved-view',\n\t'saved-views',\n\t'sections',\n\t'settings-form',\n\t'spend-analytics',\n\t'stacked-bar',\n\t'stages',\n\t'stat',\n\t'stats-block',\n\t'switch-rows',\n\t'table',\n\t'tabs',\n\t'tasks',\n\t'team-workload',\n\t'text',\n\t'tile',\n\t'timeline',\n\t'toggle-table',\n\t'video',\n\t'week-strip',\n\t'working-calendar',\n\t'work-queue',\n\t'workspace-filter',\n])\n\n/**\n * Expand a key set over `WIDGET_TYPE_ALIASES` so both spellings of an aliased\n * widget are present whenever either one is.\n *\n * `CnWidgetGrid` resolves a key and its canonical form (`object-table` ⇄\n * `table`, `map-viewer` ⇄ `map`), so both spellings render and both must be\n * exempt. Today each registry happens to hold one side of each pair, which is\n * exactly the accident that would silently stop being true when the next alias\n * lands.\n *\n * @param {Set<string>} keys The key set to expand, mutated in place.\n * @return {Set<string>} The same set.\n */\nfunction expandAliases(keys) {\n\tfor (const [alias, canonical] of Object.entries(WIDGET_TYPE_ALIASES)) {\n\t\tif (keys.has(alias) || keys.has(canonical)) {\n\t\t\tkeys.add(alias)\n\t\t\tkeys.add(canonical)\n\t\t}\n\t}\n\treturn keys\n}\n\n/**\n * Every widget key the library renders itself: both registries, plus both\n * spellings of every alias.\n *\n * A `widgetKey` in this list is served by a library `Cn*` SFC and does NOT\n * count as a custom registry component for the single-12×12-widget dashboard\n * rule (ADR-036 Decision 1).\n *\n * @type {string[]} Frozen.\n */\nexport const LIBRARY_WIDGET_KEYS = Object.freeze([\n\t...expandAliases(new Set([\n\t\t...BUILT_IN_WIDGET_KEYS,\n\t\t...DASHBOARD_CATALOG_WIDGET_KEYS,\n\t])),\n])\n"],"names":[],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC;AAClD,CAAC,cAAc;AACf,CAAC,eAAe;AAChB,CAAC,YAAY;AACb,CAAC,QAAQ;AACT,CAAC,eAAe;AAChB,CAAC,YAAY;AACb,CAAC,WAAW;AACZ,CAAC,eAAe;AAChB,CAAC,MAAM;AACP,CAAC,UAAU;AACX,CAAC,SAAS;AACV,CAAC,aAAa;AACd,CAAC,QAAQ;AACT,CAAC,aAAa;AACd,CAAC,UAAU;AACX,CAAC,QAAQ;AACT,CAAC,MAAM;AACP,CAAC,SAAS;AACV,CAAC,kBAAkB;AACnB,CAAC,SAAS;AACV,CAAC,EAAC;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,6BAA6B,GAAG,MAAM,CAAC,MAAM,CAAC;AAC3D,CAAC,aAAa;AACd,CAAC,QAAQ;AACT,CAAC,UAAU;AACX,CAAC,OAAO;AACR,CAAC,eAAe;AAChB,CAAC,aAAa;AACd,CAAC,eAAe;AAChB,CAAC,WAAW;AACZ,CAAC,cAAc;AACf,CAAC,WAAW;AACZ,CAAC,MAAM;AACP,CAAC,OAAO;AACR,CAAC,eAAe;AAChB,CAAC,SAAS;AACV,CAAC,iBAAiB;AAClB,CAAC,OAAO;AACR,CAAC,YAAY;AACb,CAAC,OAAO;AACR,CAAC,WAAW;AACZ,CAAC,OAAO;AACR,CAAC,eAAe;AAChB,CAAC,QAAQ;AACT,CAAC,OAAO;AACR,CAAC,kBAAkB;AACnB,CAAC,WAAW;AACZ,CAAC,OAAO;AACR,CAAC,MAAM;AACP,CAAC,OAAO;AACR,CAAC,MAAM;AACP,CAAC,KAAK;AACN,CAAC,UAAU;AACX,CAAC,MAAM;AACP,CAAC,WAAW;AACZ,CAAC,WAAW;AACZ,CAAC,SAAS;AACV;AACA;AACA,CAAC,YAAY;AACb,CAAC,aAAa;AACd,CAAC,cAAc;AACf,CAAC,eAAe;AAChB,CAAC,QAAQ;AACT,CAAC,MAAM;AACP,CAAC,YAAY;AACb,CAAC,aAAa;AACd,CAAC,aAAa;AACd,CAAC,SAAS;AACV,CAAC,WAAW;AACZ,CAAC,YAAY;AACb,CAAC,aAAa;AACd,CAAC,UAAU;AACX,CAAC,eAAe;AAChB,CAAC,iBAAiB;AAClB,CAAC,aAAa;AACd,CAAC,QAAQ;AACT,CAAC,MAAM;AACP,CAAC,aAAa;AACd,CAAC,aAAa;AACd,CAAC,OAAO;AACR,CAAC,MAAM;AACP,CAAC,OAAO;AACR,CAAC,eAAe;AAChB,CAAC,MAAM;AACP,CAAC,MAAM;AACP,CAAC,UAAU;AACX,CAAC,cAAc;AACf,CAAC,OAAO;AACR,CAAC,YAAY;AACb,CAAC,kBAAkB;AACnB,CAAC,YAAY;AACb,CAAC,kBAAkB;AACnB,CAAC,EAAC;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,aAAa,CAAC,IAAI,EAAE;AAC7B,CAAC,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE;AACvE,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE;AAC9C,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAC;AAClB,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAC;AACtB,GAAG;AACH,EAAE;AACF,CAAC,OAAO,IAAI;AACZ,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,mBAAmB,GAAG,MAAM,CAAC,MAAM,CAAC;AACjD,CAAC,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC;AAC1B,EAAE,GAAG,oBAAoB;AACzB,EAAE,GAAG,6BAA6B;AAClC,EAAE,CAAC,CAAC;AACJ,CAAC;;;;"}
@@ -0,0 +1,39 @@
1
+ import { createApp } from 'vue';
2
+ import __sfc_main from '../components/CnDuplicateWarning/CnDuplicateWarning.vue.js';
3
+
4
+ /**
5
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
6
+ * SPDX-License-Identifier: EUPL-1.2
7
+ *
8
+ * Open CnDuplicateWarning from code (a create override, a store action) and
9
+ * wait for the user's answer.
10
+ *
11
+ * @module utils/openDuplicateWarning
12
+ */
13
+
14
+
15
+ /**
16
+ * Mount the warning on the page and resolve with the decision: `{ reason,
17
+ * over }` when the user files anyway, null when they cancel.
18
+ *
19
+ * @spec openspec/changes/duplicate-warning/specs/duplicate-warning/spec.md#requirement-a-generic-duplicate-warning
20
+ * @param {object} props CnDuplicateWarning's props (`matches`, `records`, `policy`, `mayOverride`, ...).
21
+ * @return {Promise<{reason: string, over: Array<string>}|null>} The decision.
22
+ */
23
+ function openDuplicateWarning(props) {
24
+ return new Promise((resolve) => {
25
+ const host = document.createElement('div');
26
+ document.body.appendChild(host);
27
+ let app = null;
28
+ const finish = (decision) => {
29
+ app.unmount();
30
+ host.remove();
31
+ resolve(decision);
32
+ };
33
+ app = createApp(__sfc_main, { ...(props || {}), onClose: () => finish(null), onFile: (decision) => finish(decision) });
34
+ app.mount(host);
35
+ })
36
+ }
37
+
38
+ export { openDuplicateWarning };
39
+ //# sourceMappingURL=openDuplicateWarning.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openDuplicateWarning.js","sources":["../../../src/utils/openDuplicateWarning.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Open CnDuplicateWarning from code (a create override, a store action) and\n * wait for the user's answer.\n *\n * @module utils/openDuplicateWarning\n */\n\nimport { createApp } from 'vue'\nimport CnDuplicateWarning from '../components/CnDuplicateWarning/CnDuplicateWarning.vue'\n\n/**\n * Mount the warning on the page and resolve with the decision: `{ reason,\n * over }` when the user files anyway, null when they cancel.\n *\n * @spec openspec/changes/duplicate-warning/specs/duplicate-warning/spec.md#requirement-a-generic-duplicate-warning\n * @param {object} props CnDuplicateWarning's props (`matches`, `records`, `policy`, `mayOverride`, ...).\n * @return {Promise<{reason: string, over: Array<string>}|null>} The decision.\n */\nexport function openDuplicateWarning(props) {\n\treturn new Promise((resolve) => {\n\t\tconst host = document.createElement('div')\n\t\tdocument.body.appendChild(host)\n\t\tlet app = null\n\t\tconst finish = (decision) => {\n\t\t\tapp.unmount()\n\t\t\thost.remove()\n\t\t\tresolve(decision)\n\t\t}\n\t\tapp = createApp(CnDuplicateWarning, { ...(props || {}), onClose: () => finish(null), onFile: (decision) => finish(decision) })\n\t\tapp.mount(host)\n\t})\n}\n"],"names":["CnDuplicateWarning"],"mappings":";;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,oBAAoB,CAAC,KAAK,EAAE;AAC5C,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK;AACjC,EAAE,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,EAAC;AAC5C,EAAE,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAC;AACjC,EAAE,IAAI,GAAG,GAAG,KAAI;AAChB,EAAE,MAAM,MAAM,GAAG,CAAC,QAAQ,KAAK;AAC/B,GAAG,GAAG,CAAC,OAAO,GAAE;AAChB,GAAG,IAAI,CAAC,MAAM,GAAE;AAChB,GAAG,OAAO,CAAC,QAAQ,EAAC;AACpB,IAAG;AACH,EAAE,GAAG,GAAG,SAAS,CAACA,UAAkB,EAAE,EAAE,IAAI,KAAK,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAC;AAChI,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,EAAC;AACjB,EAAE,CAAC;AACH;;;;"}
@@ -114,7 +114,7 @@ async function loadOptionsFrom(from, ctx = {}) {
114
114
  return []
115
115
  }
116
116
  const data = await fetchEndpointSource(source, ctx, { force: true });
117
- const rows = Array.isArray(data) ? data : [];
117
+ const rows = narrowRows(Array.isArray(data) ? data : [], from);
118
118
  const seen = new Set();
119
119
  const out = [];
120
120
  for (const row of rows) {
@@ -132,6 +132,73 @@ async function loadOptionsFrom(from, ctx = {}) {
132
132
  return out
133
133
  }
134
134
 
135
+ /**
136
+ * The rows a source answers, narrowed: `where` keeps the rows whose fields
137
+ * equal its values (`{ type: 'string' }`), and `itemsPath` replaces each
138
+ * remaining row by the list at that path (the formats of one property type).
139
+ *
140
+ * @param {Array<unknown>} rows The rows.
141
+ * @param {{where?: object, itemsPath?: string}} from The `optionsFrom` block.
142
+ * @return {Array<unknown>} The rows the options are read from.
143
+ */
144
+ function narrowRows(rows, from) {
145
+ let out = rows;
146
+ if (isPlainObject(from.where)) {
147
+ out = out.filter((row) => row && typeof row === 'object' && Object.entries(from.where).every(([key, value]) => String(readPath(row, key)) === String(value)));
148
+ }
149
+ if (typeof from.itemsPath === 'string' && from.itemsPath !== '') {
150
+ out = out.flatMap((row) => {
151
+ const items = readPath(row, from.itemsPath);
152
+ return Array.isArray(items) ? items : []
153
+ });
154
+ }
155
+ return out
156
+ }
157
+
158
+ /**
159
+ * An `optionsFrom` block with every `@answer.<key>` string replaced by that
160
+ * form value, so a source can depend on another field (`where: { type:
161
+ * '@answer.propertyType' }`). A value the form does not hold becomes ''.
162
+ *
163
+ * @param {unknown} value The block, or a part of it.
164
+ * @param {object} formData The form values.
165
+ * @return {unknown} The block with the form values filled in.
166
+ */
167
+ function resolveFormTokens(value, formData) {
168
+ if (typeof value === 'string' && value.startsWith('@answer.')) {
169
+ const held = (formData || {})[value.slice('@answer.'.length)];
170
+ return held === undefined || held === null ? '' : held
171
+ }
172
+ if (Array.isArray(value)) {
173
+ return value.map((v) => resolveFormTokens(v, formData))
174
+ }
175
+ if (isPlainObject(value)) {
176
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, resolveFormTokens(v, formData)]))
177
+ }
178
+ return value
179
+ }
180
+
181
+ /**
182
+ * The form fields an `optionsFrom` block reads through `@answer.<key>`.
183
+ *
184
+ * @param {unknown} from The block.
185
+ * @return {Array<string>} The keys.
186
+ */
187
+ function formTokenKeys(from) {
188
+ const keys = new Set();
189
+ const walk = (v) => {
190
+ if (typeof v === 'string' && v.startsWith('@answer.')) {
191
+ keys.add(v.slice('@answer.'.length));
192
+ } else if (Array.isArray(v)) {
193
+ v.forEach(walk);
194
+ } else if (isPlainObject(v)) {
195
+ Object.values(v).forEach(walk);
196
+ }
197
+ };
198
+ walk(from);
199
+ return [...keys]
200
+ }
201
+
135
202
  /**
136
203
  * The options whose label holds the query, ignoring case. An empty query
137
204
  * keeps them all.
@@ -164,5 +231,5 @@ function fieldWithOptionsFrom(field) {
164
231
  return field.widget === widget ? field : { ...field, widget }
165
232
  }
166
233
 
167
- export { OPTIONS_FROM_LIMIT, fieldWithOptionsFrom, filterOptions, isOptionsFrom, loadOptionsFrom, optionFromRow, optionsFromSource };
234
+ export { OPTIONS_FROM_LIMIT, fieldWithOptionsFrom, filterOptions, formTokenKeys, isOptionsFrom, loadOptionsFrom, optionFromRow, optionsFromSource, resolveFormTokens };
168
235
  //# sourceMappingURL=optionsFrom.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"optionsFrom.js","sources":["../../../src/utils/optionsFrom.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Select options read from a source (`optionsFrom`), the shape a dashboard\n * page filter and a form field share:\n *\n * - `{ url, params?, responsePath? }`: rows from an app endpoint;\n * - `{ register, schema, filter? }`: OpenRegister objects of that schema;\n *\n * each with `valueField` (default `id`, else the object's uuid),\n * `labelField` (default `title`, else `name`, else the value) and an optional\n * `groupField` that groups the options, all dot-paths.\n *\n * @module utils/optionsFrom\n * @spec openspec/changes/form-field-options-from/specs/form-dialog/spec.md#requirement-a-select-field-can-read-its-options-from-a-source\n */\n\nimport { fetchEndpointSource } from '../composables/useEndpointSource.js'\nimport { readPath } from './readPath.js'\n\n/** How many objects a `{ register, schema }` source reads. */\nexport const OPTIONS_FROM_LIMIT = 500\n\nconst isPlainObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value)\n\n/**\n * Whether a value is a usable `optionsFrom` block.\n *\n * @param {unknown} from The block.\n * @return {boolean} True for an endpoint or a register and schema.\n */\nexport function isOptionsFrom(from) {\n\tif (!isPlainObject(from)) {\n\t\treturn false\n\t}\n\tconst endpoint = typeof from.url === 'string' && from.url !== ''\n\tconst objects = typeof from.register === 'string' && from.register !== '' && typeof from.schema === 'string' && from.schema !== ''\n\treturn endpoint || objects\n}\n\n/**\n * The endpoint source an `optionsFrom` block reads, in the `endpointSource`\n * shape `fetchEndpointSource` takes.\n *\n * @param {object} from The `optionsFrom` block.\n * @return {{url: string, params?: object, responsePath?: string}|null} The source, or null when unusable.\n */\nexport function optionsFromSource(from) {\n\tif (!isOptionsFrom(from)) {\n\t\treturn null\n\t}\n\tif (typeof from.url === 'string' && from.url !== '') {\n\t\treturn {\n\t\t\turl: from.url,\n\t\t\t...(isPlainObject(from.params) ? { params: from.params } : {}),\n\t\t\t...(typeof from.responsePath === 'string' && from.responsePath !== '' ? { responsePath: from.responsePath } : {}),\n\t\t}\n\t}\n\treturn {\n\t\turl: `/apps/openregister/api/objects/${encodeURIComponent(from.register)}/${encodeURIComponent(from.schema)}`,\n\t\tparams: { _limit: OPTIONS_FROM_LIMIT, ...(isPlainObject(from.filter) ? from.filter : {}) },\n\t\tresponsePath: 'results',\n\t}\n}\n\n/**\n * One row as an `{ id, label }` option, or null for a row without a value.\n * A plain string or number is its own value and label.\n *\n * @param {unknown} row The row.\n * @param {{valueField?: string, labelField?: string}} [from] The `optionsFrom` block.\n * @return {{id: (string|number), label: string}|null} The option.\n */\nexport function optionFromRow(row, from = {}) {\n\tif (row === null || row === undefined) {\n\t\treturn null\n\t}\n\tif (typeof row !== 'object') {\n\t\treturn row === '' ? null : { id: row, label: String(row) }\n\t}\n\tconst id = typeof from.valueField === 'string' && from.valueField !== ''\n\t\t? readPath(row, from.valueField)\n\t\t: (row.id ?? row['@self']?.id ?? row.uuid)\n\tif (id === null || id === undefined || id === '' || typeof id === 'object') {\n\t\treturn null\n\t}\n\tconst label = typeof from.labelField === 'string' && from.labelField !== ''\n\t\t? readPath(row, from.labelField)\n\t\t: (row.title || row.name)\n\tconst option = { id, label: label === null || label === undefined || label === '' ? String(id) : String(label) }\n\t// `groupField`: the option's group, which keeps options of one group together.\n\tif (typeof from.groupField === 'string' && from.groupField !== '') {\n\t\tconst group = readPath(row, from.groupField)\n\t\tif (group !== null && group !== undefined && group !== '') {\n\t\t\toption.group = String(group)\n\t\t}\n\t}\n\treturn option\n}\n\n/**\n * Read the options of an `optionsFrom` block. A failed or empty read gives\n * no options; the caller decides how to show that.\n *\n * @param {object} from The `optionsFrom` block.\n * @param {object} [ctx] The token context for the params (see `fetchEndpointSource`).\n * @return {Promise<Array<{id: (string|number), label: string}>>} The options, in the source's order.\n */\nexport async function loadOptionsFrom(from, ctx = {}) {\n\tconst source = optionsFromSource(from)\n\tif (!source) {\n\t\treturn []\n\t}\n\tconst data = await fetchEndpointSource(source, ctx, { force: true })\n\tconst rows = Array.isArray(data) ? data : []\n\tconst seen = new Set()\n\tconst out = []\n\tfor (const row of rows) {\n\t\tconst option = optionFromRow(row, from)\n\t\tif (option && !seen.has(String(option.id))) {\n\t\t\tseen.add(String(option.id))\n\t\t\tout.push(option)\n\t\t}\n\t}\n\tif (typeof from.groupField === 'string' && from.groupField !== '') {\n\t\t// Groups in the order they first appear, each option keeping its place within its group.\n\t\tconst order = [...new Set(out.map((o) => o.group || ''))]\n\t\treturn order.flatMap((group) => out.filter((o) => (o.group || '') === group))\n\t}\n\treturn out\n}\n\n/**\n * The options whose label holds the query, ignoring case. An empty query\n * keeps them all.\n *\n * @param {Array<{id: (string|number), label: string}>} options The options.\n * @param {string} [query] The search text.\n * @return {Array<{id: (string|number), label: string}>} The matching options.\n */\nexport function filterOptions(options, query = '') {\n\tconst q = typeof query === 'string' ? query.trim().toLowerCase() : ''\n\tif (q === '') {\n\t\treturn options\n\t}\n\treturn options.filter((o) => o.label.toLowerCase().includes(q) || String(o.id).toLowerCase().includes(q))\n}\n\n/**\n * A form field that reads its options (`optionsFrom`) drawn as a select: a\n * single select, or a multiselect for an array field. Any other field is\n * returned as it is.\n *\n * @param {object} field The field definition.\n * @return {object} The field.\n */\nexport function fieldWithOptionsFrom(field) {\n\tif (!field || !isOptionsFrom(field.optionsFrom)) {\n\t\treturn field\n\t}\n\tconst widget = field.type === 'array' ? 'multiselect' : 'select'\n\treturn field.widget === widget ? field : { ...field, widget }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"optionsFrom.js","sources":["../../../src/utils/optionsFrom.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Select options read from a source (`optionsFrom`), the shape a dashboard\n * page filter and a form field share:\n *\n * - `{ url, params?, responsePath? }`: rows from an app endpoint;\n * - `{ register, schema, filter? }`: OpenRegister objects of that schema;\n *\n * each with `valueField` (default `id`, else the object's uuid),\n * `labelField` (default `title`, else `name`, else the value) and an optional\n * `groupField` that groups the options, all dot-paths.\n *\n * @module utils/optionsFrom\n * @spec openspec/changes/form-field-options-from/specs/form-dialog/spec.md#requirement-a-select-field-can-read-its-options-from-a-source\n */\n\nimport { fetchEndpointSource } from '../composables/useEndpointSource.js'\nimport { readPath } from './readPath.js'\n\n/** How many objects a `{ register, schema }` source reads. */\nexport const OPTIONS_FROM_LIMIT = 500\n\nconst isPlainObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value)\n\n/**\n * Whether a value is a usable `optionsFrom` block.\n *\n * @param {unknown} from The block.\n * @return {boolean} True for an endpoint or a register and schema.\n */\nexport function isOptionsFrom(from) {\n\tif (!isPlainObject(from)) {\n\t\treturn false\n\t}\n\tconst endpoint = typeof from.url === 'string' && from.url !== ''\n\tconst objects = typeof from.register === 'string' && from.register !== '' && typeof from.schema === 'string' && from.schema !== ''\n\treturn endpoint || objects\n}\n\n/**\n * The endpoint source an `optionsFrom` block reads, in the `endpointSource`\n * shape `fetchEndpointSource` takes.\n *\n * @param {object} from The `optionsFrom` block.\n * @return {{url: string, params?: object, responsePath?: string}|null} The source, or null when unusable.\n */\nexport function optionsFromSource(from) {\n\tif (!isOptionsFrom(from)) {\n\t\treturn null\n\t}\n\tif (typeof from.url === 'string' && from.url !== '') {\n\t\treturn {\n\t\t\turl: from.url,\n\t\t\t...(isPlainObject(from.params) ? { params: from.params } : {}),\n\t\t\t...(typeof from.responsePath === 'string' && from.responsePath !== '' ? { responsePath: from.responsePath } : {}),\n\t\t}\n\t}\n\treturn {\n\t\turl: `/apps/openregister/api/objects/${encodeURIComponent(from.register)}/${encodeURIComponent(from.schema)}`,\n\t\tparams: { _limit: OPTIONS_FROM_LIMIT, ...(isPlainObject(from.filter) ? from.filter : {}) },\n\t\tresponsePath: 'results',\n\t}\n}\n\n/**\n * One row as an `{ id, label }` option, or null for a row without a value.\n * A plain string or number is its own value and label.\n *\n * @param {unknown} row The row.\n * @param {{valueField?: string, labelField?: string}} [from] The `optionsFrom` block.\n * @return {{id: (string|number), label: string}|null} The option.\n */\nexport function optionFromRow(row, from = {}) {\n\tif (row === null || row === undefined) {\n\t\treturn null\n\t}\n\tif (typeof row !== 'object') {\n\t\treturn row === '' ? null : { id: row, label: String(row) }\n\t}\n\tconst id = typeof from.valueField === 'string' && from.valueField !== ''\n\t\t? readPath(row, from.valueField)\n\t\t: (row.id ?? row['@self']?.id ?? row.uuid)\n\tif (id === null || id === undefined || id === '' || typeof id === 'object') {\n\t\treturn null\n\t}\n\tconst label = typeof from.labelField === 'string' && from.labelField !== ''\n\t\t? readPath(row, from.labelField)\n\t\t: (row.title || row.name)\n\tconst option = { id, label: label === null || label === undefined || label === '' ? String(id) : String(label) }\n\t// `groupField`: the option's group, which keeps options of one group together.\n\tif (typeof from.groupField === 'string' && from.groupField !== '') {\n\t\tconst group = readPath(row, from.groupField)\n\t\tif (group !== null && group !== undefined && group !== '') {\n\t\t\toption.group = String(group)\n\t\t}\n\t}\n\treturn option\n}\n\n/**\n * Read the options of an `optionsFrom` block. A failed or empty read gives\n * no options; the caller decides how to show that.\n *\n * @param {object} from The `optionsFrom` block.\n * @param {object} [ctx] The token context for the params (see `fetchEndpointSource`).\n * @return {Promise<Array<{id: (string|number), label: string}>>} The options, in the source's order.\n */\nexport async function loadOptionsFrom(from, ctx = {}) {\n\tconst source = optionsFromSource(from)\n\tif (!source) {\n\t\treturn []\n\t}\n\tconst data = await fetchEndpointSource(source, ctx, { force: true })\n\tconst rows = narrowRows(Array.isArray(data) ? data : [], from)\n\tconst seen = new Set()\n\tconst out = []\n\tfor (const row of rows) {\n\t\tconst option = optionFromRow(row, from)\n\t\tif (option && !seen.has(String(option.id))) {\n\t\t\tseen.add(String(option.id))\n\t\t\tout.push(option)\n\t\t}\n\t}\n\tif (typeof from.groupField === 'string' && from.groupField !== '') {\n\t\t// Groups in the order they first appear, each option keeping its place within its group.\n\t\tconst order = [...new Set(out.map((o) => o.group || ''))]\n\t\treturn order.flatMap((group) => out.filter((o) => (o.group || '') === group))\n\t}\n\treturn out\n}\n\n/**\n * The rows a source answers, narrowed: `where` keeps the rows whose fields\n * equal its values (`{ type: 'string' }`), and `itemsPath` replaces each\n * remaining row by the list at that path (the formats of one property type).\n *\n * @param {Array<unknown>} rows The rows.\n * @param {{where?: object, itemsPath?: string}} from The `optionsFrom` block.\n * @return {Array<unknown>} The rows the options are read from.\n */\nfunction narrowRows(rows, from) {\n\tlet out = rows\n\tif (isPlainObject(from.where)) {\n\t\tout = out.filter((row) => row && typeof row === 'object' && Object.entries(from.where).every(([key, value]) => String(readPath(row, key)) === String(value)))\n\t}\n\tif (typeof from.itemsPath === 'string' && from.itemsPath !== '') {\n\t\tout = out.flatMap((row) => {\n\t\t\tconst items = readPath(row, from.itemsPath)\n\t\t\treturn Array.isArray(items) ? items : []\n\t\t})\n\t}\n\treturn out\n}\n\n/**\n * An `optionsFrom` block with every `@answer.<key>` string replaced by that\n * form value, so a source can depend on another field (`where: { type:\n * '@answer.propertyType' }`). A value the form does not hold becomes ''.\n *\n * @param {unknown} value The block, or a part of it.\n * @param {object} formData The form values.\n * @return {unknown} The block with the form values filled in.\n */\nexport function resolveFormTokens(value, formData) {\n\tif (typeof value === 'string' && value.startsWith('@answer.')) {\n\t\tconst held = (formData || {})[value.slice('@answer.'.length)]\n\t\treturn held === undefined || held === null ? '' : held\n\t}\n\tif (Array.isArray(value)) {\n\t\treturn value.map((v) => resolveFormTokens(v, formData))\n\t}\n\tif (isPlainObject(value)) {\n\t\treturn Object.fromEntries(Object.entries(value).map(([k, v]) => [k, resolveFormTokens(v, formData)]))\n\t}\n\treturn value\n}\n\n/**\n * The form fields an `optionsFrom` block reads through `@answer.<key>`.\n *\n * @param {unknown} from The block.\n * @return {Array<string>} The keys.\n */\nexport function formTokenKeys(from) {\n\tconst keys = new Set()\n\tconst walk = (v) => {\n\t\tif (typeof v === 'string' && v.startsWith('@answer.')) {\n\t\t\tkeys.add(v.slice('@answer.'.length))\n\t\t} else if (Array.isArray(v)) {\n\t\t\tv.forEach(walk)\n\t\t} else if (isPlainObject(v)) {\n\t\t\tObject.values(v).forEach(walk)\n\t\t}\n\t}\n\twalk(from)\n\treturn [...keys]\n}\n\n/**\n * The options whose label holds the query, ignoring case. An empty query\n * keeps them all.\n *\n * @param {Array<{id: (string|number), label: string}>} options The options.\n * @param {string} [query] The search text.\n * @return {Array<{id: (string|number), label: string}>} The matching options.\n */\nexport function filterOptions(options, query = '') {\n\tconst q = typeof query === 'string' ? query.trim().toLowerCase() : ''\n\tif (q === '') {\n\t\treturn options\n\t}\n\treturn options.filter((o) => o.label.toLowerCase().includes(q) || String(o.id).toLowerCase().includes(q))\n}\n\n/**\n * A form field that reads its options (`optionsFrom`) drawn as a select: a\n * single select, or a multiselect for an array field. Any other field is\n * returned as it is.\n *\n * @param {object} field The field definition.\n * @return {object} The field.\n */\nexport function fieldWithOptionsFrom(field) {\n\tif (!field || !isOptionsFrom(field.optionsFrom)) {\n\t\treturn field\n\t}\n\tconst widget = field.type === 'array' ? 'multiselect' : 'select'\n\treturn field.widget === widget ? field : { ...field, widget }\n}\n"],"names":[],"mappings":";;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACY,MAAC,kBAAkB,GAAG,IAAG;AACrC;AACA,MAAM,aAAa,GAAG,CAAC,KAAK,KAAK,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,EAAC;AACrG;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,IAAI,EAAE;AACpC,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE;AAC3B,EAAE,OAAO,KAAK;AACd,EAAE;AACF,CAAC,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ,IAAI,IAAI,CAAC,GAAG,KAAK,GAAE;AACjE,CAAC,MAAM,OAAO,GAAG,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,IAAI,IAAI,CAAC,QAAQ,KAAK,EAAE,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,GAAE;AACnI,CAAC,OAAO,QAAQ,IAAI,OAAO;AAC3B,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,iBAAiB,CAAC,IAAI,EAAE;AACxC,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE;AAC3B,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,IAAI,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ,IAAI,IAAI,CAAC,GAAG,KAAK,EAAE,EAAE;AACtD,EAAE,OAAO;AACT,GAAG,GAAG,EAAE,IAAI,CAAC,GAAG;AAChB,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC;AACjE,GAAG,IAAI,OAAO,IAAI,CAAC,YAAY,KAAK,QAAQ,IAAI,IAAI,CAAC,YAAY,KAAK,EAAE,GAAG,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,GAAG,EAAE,CAAC;AACpH,GAAG;AACH,EAAE;AACF,CAAC,OAAO;AACR,EAAE,GAAG,EAAE,CAAC,+BAA+B,EAAE,kBAAkB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;AAC/G,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,IAAI,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,EAAE;AAC5F,EAAE,YAAY,EAAE,SAAS;AACzB,EAAE;AACF,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,GAAG,EAAE,IAAI,GAAG,EAAE,EAAE;AAC9C,CAAC,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,EAAE;AACxC,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE;AAC9B,EAAE,OAAO,GAAG,KAAK,EAAE,GAAG,IAAI,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE;AAC5D,EAAE;AACF,CAAC,MAAM,EAAE,GAAG,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,KAAK,EAAE;AACzE,IAAI,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC;AAClC,KAAK,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,GAAG,CAAC,IAAI,EAAC;AAC5C,CAAC,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE;AAC7E,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,KAAK,GAAG,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,KAAK,EAAE;AAC5E,IAAI,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC;AAClC,KAAK,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,IAAI,EAAC;AAC3B,CAAC,MAAM,MAAM,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAE;AACjH;AACA,CAAC,IAAI,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,KAAK,EAAE,EAAE;AACpE,EAAE,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,EAAC;AAC9C,EAAE,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE;AAC7D,GAAG,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,EAAC;AAC/B,GAAG;AACH,EAAE;AACF,CAAC,OAAO,MAAM;AACd,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,eAAe,eAAe,CAAC,IAAI,EAAE,GAAG,GAAG,EAAE,EAAE;AACtD,CAAC,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,EAAC;AACvC,CAAC,IAAI,CAAC,MAAM,EAAE;AACd,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAC;AACrE,CAAC,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,EAAE,EAAE,IAAI,EAAC;AAC/D,CAAC,MAAM,IAAI,GAAG,IAAI,GAAG,GAAE;AACvB,CAAC,MAAM,GAAG,GAAG,GAAE;AACf,CAAC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE;AACzB,EAAE,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,EAAE,IAAI,EAAC;AACzC,EAAE,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE;AAC9C,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,EAAC;AAC9B,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,EAAC;AACnB,GAAG;AACH,EAAE;AACF,CAAC,IAAI,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,KAAK,EAAE,EAAE;AACpE;AACA,EAAE,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,EAAC;AAC3D,EAAE,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,MAAM,KAAK,CAAC,CAAC;AAC/E,EAAE;AACF,CAAC,OAAO,GAAG;AACX,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE;AAChC,CAAC,IAAI,GAAG,GAAG,KAAI;AACf,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;AAChC,EAAE,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,MAAM,CAAC,KAAK,CAAC,CAAC,EAAC;AAC/J,EAAE;AACF,CAAC,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,IAAI,IAAI,CAAC,SAAS,KAAK,EAAE,EAAE;AAClE,EAAE,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,KAAK;AAC7B,GAAG,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,EAAC;AAC9C,GAAG,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,EAAE;AAC3C,GAAG,EAAC;AACJ,EAAE;AACF,CAAC,OAAO,GAAG;AACX,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,iBAAiB,CAAC,KAAK,EAAE,QAAQ,EAAE;AACnD,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE;AAChE,EAAE,MAAM,IAAI,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAC;AAC/D,EAAE,OAAO,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,GAAG,EAAE,GAAG,IAAI;AACxD,EAAE;AACF,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;AAC3B,EAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,iBAAiB,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AACzD,EAAE;AACF,CAAC,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE;AAC3B,EAAE,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;AACvG,EAAE;AACF,CAAC,OAAO,KAAK;AACb,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,IAAI,EAAE;AACpC,CAAC,MAAM,IAAI,GAAG,IAAI,GAAG,GAAE;AACvB,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK;AACrB,EAAE,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE;AACzD,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAC;AACvC,GAAG,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;AAC/B,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAC;AAClB,GAAG,MAAM,IAAI,aAAa,CAAC,CAAC,CAAC,EAAE;AAC/B,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAC;AACjC,GAAG;AACH,GAAE;AACF,CAAC,IAAI,CAAC,IAAI,EAAC;AACX,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;AACjB,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,OAAO,EAAE,KAAK,GAAG,EAAE,EAAE;AACnD,CAAC,MAAM,CAAC,GAAG,OAAO,KAAK,KAAK,QAAQ,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,GAAG,GAAE;AACtE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE;AACf,EAAE,OAAO,OAAO;AAChB,EAAE;AACF,CAAC,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC1G,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,oBAAoB,CAAC,KAAK,EAAE;AAC5C,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE;AAClD,EAAE,OAAO,KAAK;AACd,EAAE;AACF,CAAC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,KAAK,OAAO,GAAG,aAAa,GAAG,SAAQ;AACjE,CAAC,OAAO,KAAK,CAAC,MAAM,KAAK,MAAM,GAAG,KAAK,GAAG,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE;AAC9D;;;;"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * The plan rows of OpenRegister's case layer as a tree, and the moves a
6
+ * user may ask for on one item. The moves are advisory: OpenRegister decides.
7
+ *
8
+ * @module utils/planTree
9
+ */
10
+
11
+ /** Plan item states after which nothing more can happen. */
12
+ const TERMINAL_STATES = ['completed', 'terminated', 'disabled'];
13
+
14
+ /**
15
+ * The plan rows as a tree: stages with their items, each level by position.
16
+ *
17
+ * @param {Array<object>} items The plan rows (`id`, `parentItemId`, `position`).
18
+ * @return {Array<object>} The roots, each with `children`.
19
+ */
20
+ function planTree(items) {
21
+ const rows = Array.isArray(items) ? items : [];
22
+ const byId = new Map(rows.map((row) => [row.id, { ...row, children: [] }]));
23
+ const roots = [];
24
+ for (const row of rows) {
25
+ const node = byId.get(row.id);
26
+ const parent = row.parentItemId === null || row.parentItemId === undefined ? undefined : byId.get(row.parentItemId);
27
+ if (parent === undefined) {
28
+ roots.push(node);
29
+ } else {
30
+ parent.children.push(node);
31
+ }
32
+ }
33
+ const byPosition = (a, b) => (a.position ?? 0) - (b.position ?? 0);
34
+ for (const node of byId.values()) {
35
+ node.children.sort(byPosition);
36
+ }
37
+ return roots.sort(byPosition)
38
+ }
39
+
40
+ /**
41
+ * The target states a user may ask for. Advisory only: OpenRegister decides.
42
+ *
43
+ * @param {object} item The plan item.
44
+ * @return {string[]} The targets.
45
+ */
46
+ function offeredTransitions(item) {
47
+ if (!item || TERMINAL_STATES.includes(item.state)) {
48
+ return []
49
+ }
50
+ if (item.type === 'milestone') {
51
+ return item.state === 'available' ? ['completed', 'terminated'] : []
52
+ }
53
+ return item.state === 'active' ? ['completed', 'terminated'] : ['terminated']
54
+ }
55
+
56
+ export { TERMINAL_STATES, offeredTransitions, planTree };
57
+ //# sourceMappingURL=planTree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planTree.js","sources":["../../../src/utils/planTree.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * The plan rows of OpenRegister's case layer as a tree, and the moves a\n * user may ask for on one item. The moves are advisory: OpenRegister decides.\n *\n * @module utils/planTree\n */\n\n/** Plan item states after which nothing more can happen. */\nexport const TERMINAL_STATES = ['completed', 'terminated', 'disabled']\n\n/**\n * The plan rows as a tree: stages with their items, each level by position.\n *\n * @param {Array<object>} items The plan rows (`id`, `parentItemId`, `position`).\n * @return {Array<object>} The roots, each with `children`.\n */\nexport function planTree(items) {\n\tconst rows = Array.isArray(items) ? items : []\n\tconst byId = new Map(rows.map((row) => [row.id, { ...row, children: [] }]))\n\tconst roots = []\n\tfor (const row of rows) {\n\t\tconst node = byId.get(row.id)\n\t\tconst parent = row.parentItemId === null || row.parentItemId === undefined ? undefined : byId.get(row.parentItemId)\n\t\tif (parent === undefined) {\n\t\t\troots.push(node)\n\t\t} else {\n\t\t\tparent.children.push(node)\n\t\t}\n\t}\n\tconst byPosition = (a, b) => (a.position ?? 0) - (b.position ?? 0)\n\tfor (const node of byId.values()) {\n\t\tnode.children.sort(byPosition)\n\t}\n\treturn roots.sort(byPosition)\n}\n\n/**\n * The target states a user may ask for. Advisory only: OpenRegister decides.\n *\n * @param {object} item The plan item.\n * @return {string[]} The targets.\n */\nexport function offeredTransitions(item) {\n\tif (!item || TERMINAL_STATES.includes(item.state)) {\n\t\treturn []\n\t}\n\tif (item.type === 'milestone') {\n\t\treturn item.state === 'available' ? ['completed', 'terminated'] : []\n\t}\n\treturn item.state === 'active' ? ['completed', 'terminated'] : ['terminated']\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,eAAe,GAAG,CAAC,WAAW,EAAE,YAAY,EAAE,UAAU,EAAC;AACtE;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,QAAQ,CAAC,KAAK,EAAE;AAChC,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,GAAE;AAC/C,CAAC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC,EAAC;AAC5E,CAAC,MAAM,KAAK,GAAG,GAAE;AACjB,CAAC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE;AACzB,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAC;AAC/B,EAAE,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,IAAI,IAAI,GAAG,CAAC,YAAY,KAAK,SAAS,GAAG,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,YAAY,EAAC;AACrH,EAAE,IAAI,MAAM,KAAK,SAAS,EAAE;AAC5B,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAC;AACnB,GAAG,MAAM;AACT,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAC;AAC7B,GAAG;AACH,EAAE;AACF,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,IAAI,CAAC,EAAC;AACnE,CAAC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE;AACnC,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAC;AAChC,EAAE;AACF,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC;AAC9B,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,kBAAkB,CAAC,IAAI,EAAE;AACzC,CAAC,IAAI,CAAC,IAAI,IAAI,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;AACpD,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,EAAE;AAChC,EAAE,OAAO,IAAI,CAAC,KAAK,KAAK,WAAW,GAAG,CAAC,WAAW,EAAE,YAAY,CAAC,GAAG,EAAE;AACtE,EAAE;AACF,CAAC,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,GAAG,CAAC,WAAW,EAAE,YAAY,CAAC,GAAG,CAAC,YAAY,CAAC;AAC9E;;;;"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * A document text cut into plain and marked segments, for the redaction
6
+ * editor to draw the passages in place.
7
+ *
8
+ * @module utils/redactionSegments
9
+ */
10
+
11
+ /**
12
+ * The text as segments: plain ones and marked ones carrying the passage's
13
+ * verdict (`open` before it has one). Where passages overlap the longest wins,
14
+ * and every occurrence of a passage is marked.
15
+ *
16
+ * @param {string} text The document text.
17
+ * @param {Array<{value: string, verdict: (string|null)}>} passages The passages.
18
+ * @return {Array<{text: string, verdict: (string|null)}>} The segments.
19
+ */
20
+ function redactionSegments(text, passages) {
21
+ const source = String(text || '');
22
+ const ranges = [];
23
+ const ordered = (Array.isArray(passages) ? passages : [])
24
+ .filter((passage) => passage && String(passage.value || '') !== '')
25
+ .sort((left, right) => right.value.length - left.value.length);
26
+ for (const passage of ordered) {
27
+ let from = source.indexOf(passage.value);
28
+ while (from !== -1) {
29
+ const to = from + passage.value.length;
30
+ if (ranges.every((range) => to <= range.from || from >= range.to)) {
31
+ ranges.push({ from, to, verdict: passage.verdict || 'open' });
32
+ }
33
+ from = source.indexOf(passage.value, to);
34
+ }
35
+ }
36
+ ranges.sort((left, right) => left.from - right.from);
37
+ const segments = [];
38
+ let at = 0;
39
+ for (const range of ranges) {
40
+ if (range.from > at) {
41
+ segments.push({ text: source.slice(at, range.from), verdict: null });
42
+ }
43
+ segments.push({ text: source.slice(range.from, range.to), verdict: range.verdict });
44
+ at = range.to;
45
+ }
46
+ if (at < source.length) {
47
+ segments.push({ text: source.slice(at), verdict: null });
48
+ }
49
+ return segments
50
+ }
51
+
52
+ export { redactionSegments };
53
+ //# sourceMappingURL=redactionSegments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redactionSegments.js","sources":["../../../src/utils/redactionSegments.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * A document text cut into plain and marked segments, for the redaction\n * editor to draw the passages in place.\n *\n * @module utils/redactionSegments\n */\n\n/**\n * The text as segments: plain ones and marked ones carrying the passage's\n * verdict (`open` before it has one). Where passages overlap the longest wins,\n * and every occurrence of a passage is marked.\n *\n * @param {string} text The document text.\n * @param {Array<{value: string, verdict: (string|null)}>} passages The passages.\n * @return {Array<{text: string, verdict: (string|null)}>} The segments.\n */\nexport function redactionSegments(text, passages) {\n\tconst source = String(text || '')\n\tconst ranges = []\n\tconst ordered = (Array.isArray(passages) ? passages : [])\n\t\t.filter((passage) => passage && String(passage.value || '') !== '')\n\t\t.sort((left, right) => right.value.length - left.value.length)\n\tfor (const passage of ordered) {\n\t\tlet from = source.indexOf(passage.value)\n\t\twhile (from !== -1) {\n\t\t\tconst to = from + passage.value.length\n\t\t\tif (ranges.every((range) => to <= range.from || from >= range.to)) {\n\t\t\t\tranges.push({ from, to, verdict: passage.verdict || 'open' })\n\t\t\t}\n\t\t\tfrom = source.indexOf(passage.value, to)\n\t\t}\n\t}\n\tranges.sort((left, right) => left.from - right.from)\n\tconst segments = []\n\tlet at = 0\n\tfor (const range of ranges) {\n\t\tif (range.from > at) {\n\t\t\tsegments.push({ text: source.slice(at, range.from), verdict: null })\n\t\t}\n\t\tsegments.push({ text: source.slice(range.from, range.to), verdict: range.verdict })\n\t\tat = range.to\n\t}\n\tif (at < source.length) {\n\t\tsegments.push({ text: source.slice(at), verdict: null })\n\t}\n\treturn segments\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,iBAAiB,CAAC,IAAI,EAAE,QAAQ,EAAE;AAClD,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,EAAC;AAClC,CAAC,MAAM,MAAM,GAAG,GAAE;AAClB,CAAC,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,GAAG,EAAE;AACzD,GAAG,MAAM,CAAC,CAAC,OAAO,KAAK,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;AACrE,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,KAAK,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAC;AAChE,CAAC,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE;AAChC,EAAE,IAAI,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAC;AAC1C,EAAE,OAAO,IAAI,KAAK,CAAC,CAAC,EAAE;AACtB,GAAG,MAAM,EAAE,GAAG,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,OAAM;AACzC,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,KAAK,EAAE,IAAI,KAAK,CAAC,IAAI,IAAI,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE;AACtE,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,MAAM,EAAE,EAAC;AACjE,IAAI;AACJ,GAAG,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,EAAC;AAC3C,GAAG;AACH,EAAE;AACF,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,EAAC;AACrD,CAAC,MAAM,QAAQ,GAAG,GAAE;AACpB,CAAC,IAAI,EAAE,GAAG,EAAC;AACX,CAAC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE;AAC7B,EAAE,IAAI,KAAK,CAAC,IAAI,GAAG,EAAE,EAAE;AACvB,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAC;AACvE,GAAG;AACH,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,EAAC;AACrF,EAAE,EAAE,GAAG,KAAK,CAAC,GAAE;AACf,EAAE;AACF,CAAC,IAAI,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE;AACzB,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAC;AAC1D,EAAE;AACF,CAAC,OAAO,QAAQ;AAChB;;;;"}
@@ -0,0 +1,61 @@
1
+ import { translate } from '@nextcloud/l10n';
2
+ import { createApp } from 'vue';
3
+ import __sfc_main from '../components/CnNcDashboardList/CnNcDashboardList.vue.js';
4
+
5
+ /**
6
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
7
+ * SPDX-License-Identifier: EUPL-1.2
8
+ *
9
+ * Register an app's Nextcloud Dashboard widgets from configuration.
10
+ *
11
+ * @module utils/registerNcDashboardWidgets
12
+ */
13
+
14
+
15
+ /**
16
+ * Register each widget with `OCA.Dashboard.register(id, …)`. A widget mounts
17
+ * CnNcDashboardList with its `config`, or its own `component` with `props`
18
+ * and the widget's `title`, as its own Vue app with the app's translate provided as `cnTranslate`. The
19
+ * Dashboard host draws the widget's title, so the component must not.
20
+ *
21
+ * The widget ids are the ones the app's PHP `IWidget::getId()` returns; the
22
+ * server side stays the app's.
23
+ *
24
+ * @spec openspec/changes/nc-dashboard-widgets/specs/nc-dashboard-widgets/spec.md#requirement-dashboard-widgets-are-configuration
25
+ * @param {Array<{id: string, config?: object, component?: object, props?: object}>} widgets The widgets.
26
+ * @param {object} options Options.
27
+ * @param {string} options.appId The app id, for its translations.
28
+ * @param {(app: object) => void} [options.setup] Called on each widget's Vue app before mounting (to `use` a pinia, say).
29
+ * @param {object} [options.dashboard] The `OCA.Dashboard` registry (default `window.OCA.Dashboard`).
30
+ * @return {string[]} The ids registered; none when the Dashboard is absent.
31
+ */
32
+ function registerNcDashboardWidgets(widgets, options = {}) {
33
+ const dashboard = options.dashboard || (typeof window !== 'undefined' && window.OCA && window.OCA.Dashboard) || null;
34
+ if (!dashboard || typeof dashboard.register !== 'function' || !Array.isArray(widgets)) {
35
+ return []
36
+ }
37
+ const appId = options.appId || 'nextcloud-vue';
38
+ const cnTranslate = (key, vars) => translate(appId, key, vars);
39
+ const registered = [];
40
+ for (const widget of widgets) {
41
+ if (!widget || typeof widget.id !== 'string' || widget.id === '') {
42
+ continue
43
+ }
44
+ dashboard.register(widget.id, (el, context = {}) => {
45
+ const title = context.widget && context.widget.title ? context.widget.title : '';
46
+ const app = widget.component
47
+ ? createApp(widget.component, { ...(widget.props || {}), title })
48
+ : createApp(__sfc_main, { config: widget.config || {} });
49
+ app.provide('cnTranslate', cnTranslate);
50
+ if (typeof options.setup === 'function') {
51
+ options.setup(app);
52
+ }
53
+ app.mount(el);
54
+ });
55
+ registered.push(widget.id);
56
+ }
57
+ return registered
58
+ }
59
+
60
+ export { registerNcDashboardWidgets };
61
+ //# sourceMappingURL=registerNcDashboardWidgets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registerNcDashboardWidgets.js","sources":["../../../src/utils/registerNcDashboardWidgets.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Register an app's Nextcloud Dashboard widgets from configuration.\n *\n * @module utils/registerNcDashboardWidgets\n */\n\nimport { translate } from '@nextcloud/l10n'\nimport { createApp } from 'vue'\nimport CnNcDashboardList from '../components/CnNcDashboardList/CnNcDashboardList.vue'\n\n/**\n * Register each widget with `OCA.Dashboard.register(id, …)`. A widget mounts\n * CnNcDashboardList with its `config`, or its own `component` with `props`\n * and the widget's `title`, as its own Vue app with the app's translate provided as `cnTranslate`. The\n * Dashboard host draws the widget's title, so the component must not.\n *\n * The widget ids are the ones the app's PHP `IWidget::getId()` returns; the\n * server side stays the app's.\n *\n * @spec openspec/changes/nc-dashboard-widgets/specs/nc-dashboard-widgets/spec.md#requirement-dashboard-widgets-are-configuration\n * @param {Array<{id: string, config?: object, component?: object, props?: object}>} widgets The widgets.\n * @param {object} options Options.\n * @param {string} options.appId The app id, for its translations.\n * @param {(app: object) => void} [options.setup] Called on each widget's Vue app before mounting (to `use` a pinia, say).\n * @param {object} [options.dashboard] The `OCA.Dashboard` registry (default `window.OCA.Dashboard`).\n * @return {string[]} The ids registered; none when the Dashboard is absent.\n */\nexport function registerNcDashboardWidgets(widgets, options = {}) {\n\tconst dashboard = options.dashboard || (typeof window !== 'undefined' && window.OCA && window.OCA.Dashboard) || null\n\tif (!dashboard || typeof dashboard.register !== 'function' || !Array.isArray(widgets)) {\n\t\treturn []\n\t}\n\tconst appId = options.appId || 'nextcloud-vue'\n\tconst cnTranslate = (key, vars) => translate(appId, key, vars)\n\tconst registered = []\n\tfor (const widget of widgets) {\n\t\tif (!widget || typeof widget.id !== 'string' || widget.id === '') {\n\t\t\tcontinue\n\t\t}\n\t\tdashboard.register(widget.id, (el, context = {}) => {\n\t\t\tconst title = context.widget && context.widget.title ? context.widget.title : ''\n\t\t\tconst app = widget.component\n\t\t\t\t? createApp(widget.component, { ...(widget.props || {}), title })\n\t\t\t\t: createApp(CnNcDashboardList, { config: widget.config || {} })\n\t\t\tapp.provide('cnTranslate', cnTranslate)\n\t\t\tif (typeof options.setup === 'function') {\n\t\t\t\toptions.setup(app)\n\t\t\t}\n\t\t\tapp.mount(el)\n\t\t})\n\t\tregistered.push(widget.id)\n\t}\n\treturn registered\n}\n"],"names":["CnNcDashboardList"],"mappings":";;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,EAAE,EAAE;AAClE,CAAC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,KAAK,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,KAAI;AACrH,CAAC,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,CAAC,QAAQ,KAAK,UAAU,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;AACxF,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,gBAAe;AAC/C,CAAC,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,IAAI,KAAK,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAC;AAC/D,CAAC,MAAM,UAAU,GAAG,GAAE;AACtB,CAAC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE;AAC/B,EAAE,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ,IAAI,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE;AACpE,GAAG,QAAQ;AACX,GAAG;AACH,EAAE,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,OAAO,GAAG,EAAE,KAAK;AACtD,GAAG,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,GAAE;AACnF,GAAG,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS;AAC/B,MAAM,SAAS,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,IAAI,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC;AACrE,MAAM,SAAS,CAACA,UAAiB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,EAAC;AACnE,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,WAAW,EAAC;AAC1C,GAAG,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,UAAU,EAAE;AAC5C,IAAI,OAAO,CAAC,KAAK,CAAC,GAAG,EAAC;AACtB,IAAI;AACJ,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,EAAC;AAChB,GAAG,EAAC;AACJ,EAAE,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAC;AAC5B,EAAE;AACF,CAAC,OAAO,UAAU;AAClB;;;;"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * A refused search, said where it was typed. OpenRegister refuses a `_search`
6
+ * term it cannot parse (an unclosed bracket or quote) with `400 { error,
7
+ * position, term }` rather than running it as a literal, which would return
8
+ * nothing and read as an honest empty result.
9
+ *
10
+ * @module utils/searchRefusal
11
+ */
12
+
13
+ /** `<reason> at position <n>.`, the shape OpenRegister builds the message in. */
14
+ const POSITION_IN_MESSAGE = /\bat position (\d+)\.?\s*$/;
15
+
16
+ /**
17
+ * The 1-based character a refusal points at, or null.
18
+ *
19
+ * @param {object} error The ApiError.
20
+ * @return {number|null}
21
+ */
22
+ function positionOf(error) {
23
+ const carried = error.position ?? (error.details && typeof error.details === 'object' ? error.details.position : undefined);
24
+ if (Number.isInteger(carried) && carried > 0) {
25
+ return carried
26
+ }
27
+ const match = POSITION_IN_MESSAGE.exec(String(error.message || ''));
28
+ const parsed = match ? Number(match[1]) : 0;
29
+ return parsed > 0 ? parsed : null
30
+ }
31
+
32
+ /**
33
+ * A search refusal, or null when this failure is not one: not a 400, no term
34
+ * typed, or no position to point at (then it is a plain failed fetch).
35
+ *
36
+ * @spec openspec/changes/index-search-refusal/specs/index-search-refusal/spec.md#requirement-a-refused-search-is-said-where-it-was-typed
37
+ * @param {object|null} error The ApiError the store recorded.
38
+ * @param {string} term The term the user typed.
39
+ * @return {{message: string, position: number, term: string, before: string, at: string, after: string}|null}
40
+ */
41
+ function readSearchRefusal(error, term) {
42
+ const typed = typeof term === 'string' ? term : '';
43
+ if (!error || typed.trim() === '' || Number(error.status) !== 400) {
44
+ return null
45
+ }
46
+ const position = positionOf(error);
47
+ if (position === null) {
48
+ return null
49
+ }
50
+ const index = Math.min(position - 1, typed.length);
51
+ return {
52
+ message: String(error.message || ''),
53
+ position,
54
+ term: typed,
55
+ before: typed.slice(0, index),
56
+ at: typed.slice(index, index + 1),
57
+ after: typed.slice(index + 1),
58
+ }
59
+ }
60
+
61
+ export { readSearchRefusal };
62
+ //# sourceMappingURL=searchRefusal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"searchRefusal.js","sources":["../../../src/utils/searchRefusal.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * A refused search, said where it was typed. OpenRegister refuses a `_search`\n * term it cannot parse (an unclosed bracket or quote) with `400 { error,\n * position, term }` rather than running it as a literal, which would return\n * nothing and read as an honest empty result.\n *\n * @module utils/searchRefusal\n */\n\n/** `<reason> at position <n>.`, the shape OpenRegister builds the message in. */\nconst POSITION_IN_MESSAGE = /\\bat position (\\d+)\\.?\\s*$/\n\n/**\n * The 1-based character a refusal points at, or null.\n *\n * @param {object} error The ApiError.\n * @return {number|null}\n */\nfunction positionOf(error) {\n\tconst carried = error.position ?? (error.details && typeof error.details === 'object' ? error.details.position : undefined)\n\tif (Number.isInteger(carried) && carried > 0) {\n\t\treturn carried\n\t}\n\tconst match = POSITION_IN_MESSAGE.exec(String(error.message || ''))\n\tconst parsed = match ? Number(match[1]) : 0\n\treturn parsed > 0 ? parsed : null\n}\n\n/**\n * A search refusal, or null when this failure is not one: not a 400, no term\n * typed, or no position to point at (then it is a plain failed fetch).\n *\n * @spec openspec/changes/index-search-refusal/specs/index-search-refusal/spec.md#requirement-a-refused-search-is-said-where-it-was-typed\n * @param {object|null} error The ApiError the store recorded.\n * @param {string} term The term the user typed.\n * @return {{message: string, position: number, term: string, before: string, at: string, after: string}|null}\n */\nexport function readSearchRefusal(error, term) {\n\tconst typed = typeof term === 'string' ? term : ''\n\tif (!error || typed.trim() === '' || Number(error.status) !== 400) {\n\t\treturn null\n\t}\n\tconst position = positionOf(error)\n\tif (position === null) {\n\t\treturn null\n\t}\n\tconst index = Math.min(position - 1, typed.length)\n\treturn {\n\t\tmessage: String(error.message || ''),\n\t\tposition,\n\t\tterm: typed,\n\t\tbefore: typed.slice(0, index),\n\t\tat: typed.slice(index, index + 1),\n\t\tafter: typed.slice(index + 1),\n\t}\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM,mBAAmB,GAAG,6BAA4B;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,UAAU,CAAC,KAAK,EAAE;AAC3B,CAAC,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,KAAK,KAAK,CAAC,OAAO,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,GAAG,SAAS,EAAC;AAC5H,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC,EAAE;AAC/C,EAAE,OAAO,OAAO;AAChB,EAAE;AACF,CAAC,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,EAAC;AACpE,CAAC,MAAM,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAC;AAC5C,CAAC,OAAO,MAAM,GAAG,CAAC,GAAG,MAAM,GAAG,IAAI;AAClC,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,iBAAiB,CAAC,KAAK,EAAE,IAAI,EAAE;AAC/C,CAAC,MAAM,KAAK,GAAG,OAAO,IAAI,KAAK,QAAQ,GAAG,IAAI,GAAG,GAAE;AACnD,CAAC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,GAAG,EAAE;AACpE,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,EAAC;AACnC,CAAC,IAAI,QAAQ,KAAK,IAAI,EAAE;AACxB,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC,EAAE,KAAK,CAAC,MAAM,EAAC;AACnD,CAAC,OAAO;AACR,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC;AACtC,EAAE,QAAQ;AACV,EAAE,IAAI,EAAE,KAAK;AACb,EAAE,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC;AAC/B,EAAE,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC;AACnC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;AAC/B,EAAE;AACF;;;;"}