@conduction/nextcloud-vue 2.56.0-beta.31 → 2.56.0-beta.32

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 (178) hide show
  1. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +55 -4
  2. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  3. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +51 -15
  4. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  5. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +13 -2
  6. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  7. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +63 -52
  8. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  9. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js +10 -0
  10. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js.map +1 -0
  11. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js +252 -0
  12. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js.map +1 -0
  13. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js +140 -0
  14. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js.map +1 -0
  15. package/dist/esm/components/CnChipsFieldWidget/index.js +26 -0
  16. package/dist/esm/components/CnChipsFieldWidget/index.js.map +1 -0
  17. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js +10 -0
  18. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js.map +1 -0
  19. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js +354 -0
  20. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js.map +1 -0
  21. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js +172 -0
  22. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js.map +1 -0
  23. package/dist/esm/components/CnChoicePickerWidget/index.js +27 -0
  24. package/dist/esm/components/CnChoicePickerWidget/index.js.map +1 -0
  25. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +25 -2
  26. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -1
  28. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  29. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  30. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +3 -2
  31. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  32. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +29 -0
  33. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  34. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +20 -3
  35. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +7 -1
  37. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  38. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  39. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +77 -3
  40. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
  41. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +282 -69
  42. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
  43. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +0 -1
  44. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  45. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  46. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +65 -2
  47. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  48. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -2
  49. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  50. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +10 -0
  51. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js.map +1 -0
  52. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +174 -0
  53. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -0
  54. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +75 -0
  55. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -0
  56. package/dist/esm/components/CnRadioCardsWidget/index.js +28 -0
  57. package/dist/esm/components/CnRadioCardsWidget/index.js.map +1 -0
  58. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +18 -1
  59. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  60. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +34 -25
  61. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  62. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js +10 -0
  63. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js.map +1 -0
  64. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js +222 -0
  65. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js.map +1 -0
  66. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js +64 -0
  67. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js.map +1 -0
  68. package/dist/esm/components/CnSavedViewsWidget/index.js +30 -0
  69. package/dist/esm/components/CnSavedViewsWidget/index.js.map +1 -0
  70. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
  71. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
  72. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +10 -0
  73. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js.map +1 -0
  74. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +347 -0
  75. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -0
  76. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +184 -0
  77. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -0
  78. package/dist/esm/components/CnSettingsFormWidget/index.js +27 -0
  79. package/dist/esm/components/CnSettingsFormWidget/index.js.map +1 -0
  80. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +87 -6
  81. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  82. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  83. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +10 -0
  84. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js.map +1 -0
  85. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +159 -0
  86. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -0
  87. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js +60 -0
  88. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -0
  89. package/dist/esm/components/CnSwitchRowsWidget/index.js +26 -0
  90. package/dist/esm/components/CnSwitchRowsWidget/index.js.map +1 -0
  91. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +47 -1
  92. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  93. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +16 -14
  94. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  95. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +80 -1
  96. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  97. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +75 -32
  98. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  99. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +10 -0
  100. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js.map +1 -0
  101. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +269 -0
  102. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -0
  103. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js +106 -0
  104. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -0
  105. package/dist/esm/components/CnToggleTableWidget/index.js +27 -0
  106. package/dist/esm/components/CnToggleTableWidget/index.js.map +1 -0
  107. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
  108. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  109. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
  110. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +4 -2
  111. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
  112. package/dist/esm/index.js +7 -0
  113. package/dist/esm/index.js.map +1 -1
  114. package/dist/esm/l10n/en.json.js +1 -0
  115. package/dist/esm/l10n/en.json.js.map +1 -1
  116. package/dist/esm/l10n/nl.json.js +1 -0
  117. package/dist/esm/l10n/nl.json.js.map +1 -1
  118. package/dist/esm/nextcloud-vue.css +843 -4
  119. package/dist/esm/utils/contentHeaderLink.js +51 -0
  120. package/dist/esm/utils/contentHeaderLink.js.map +1 -0
  121. package/dist/esm/utils/fetchFilterCounts.js +40 -5
  122. package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
  123. package/dist/esm/utils/initials.js +32 -0
  124. package/dist/esm/utils/initials.js.map +1 -0
  125. package/dist/esm/utils/libraryWidgetKeys.js +7 -0
  126. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  127. package/dist/esm/utils/objectWrite.js +59 -0
  128. package/dist/esm/utils/objectWrite.js.map +1 -0
  129. package/dist/esm/utils/widgetDispatch.js +1 -1
  130. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  131. package/dist/nextcloud-vue.cjs.js +10510 -6830
  132. package/dist/nextcloud-vue.cjs.js.map +2 -2
  133. package/dist/nextcloud-vue.css +843 -4
  134. package/l10n/en.json +1 -0
  135. package/l10n/nl.json +1 -0
  136. package/package.json +1 -1
  137. package/src/components/CnBannerWidget/CnBannerWidget.vue +93 -13
  138. package/src/components/CnCellRenderer/CnCellRenderer.md +2 -0
  139. package/src/components/CnCellRenderer/CnCellRenderer.vue +43 -3
  140. package/src/components/CnChipsFieldWidget/CnChipsFieldWidget.vue +433 -0
  141. package/src/components/CnChipsFieldWidget/index.js +25 -0
  142. package/src/components/CnChoicePickerWidget/CnChoicePickerWidget.vue +556 -0
  143. package/src/components/CnChoicePickerWidget/index.js +26 -0
  144. package/src/components/CnDashboardPage/CnDashboardPage.vue +38 -2
  145. package/src/components/CnDataTable/CnDataTable.vue +2 -1
  146. package/src/components/CnDetailPage/CnDetailPage.vue +45 -2
  147. package/src/components/CnFactsWidget/CnFactsWidget.vue +18 -3
  148. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +265 -4
  149. package/src/components/CnIndexPage/CnIndexPage.vue +0 -1
  150. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +69 -2
  151. package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +313 -0
  152. package/src/components/CnRadioCardsWidget/index.js +27 -0
  153. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +43 -1
  154. package/src/components/CnSavedViewsWidget/CnSavedViewsWidget.vue +296 -0
  155. package/src/components/CnSavedViewsWidget/index.js +29 -0
  156. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +13 -0
  157. package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +555 -0
  158. package/src/components/CnSettingsFormWidget/index.js +26 -0
  159. package/src/components/CnStatWidget/CnStatWidget.vue +87 -6
  160. package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +226 -0
  161. package/src/components/CnSwitchRowsWidget/index.js +25 -0
  162. package/src/components/CnTabsWidget/CnTabsWidget.vue +82 -3
  163. package/src/components/CnTasksWidget/CnTasksWidget.vue +175 -15
  164. package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +386 -0
  165. package/src/components/CnToggleTableWidget/index.js +26 -0
  166. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
  167. package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +28 -1
  168. package/src/components/index.js +7 -0
  169. package/src/css/look-board-detail.css +36 -2
  170. package/src/css/look-board-index.css +23 -0
  171. package/src/css/look-board.css +72 -0
  172. package/src/index.js +7 -0
  173. package/src/utils/contentHeaderLink.js +48 -0
  174. package/src/utils/fetchFilterCounts.js +39 -4
  175. package/src/utils/initials.js +29 -0
  176. package/src/utils/libraryWidgetKeys.js +7 -0
  177. package/src/utils/objectWrite.js +55 -0
  178. package/src/utils/widgetDispatch.js +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"CnWorkspaceFilterWidget.vue3.js","sources":["../../../../src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue?vue&type=template&id=f0d5c1c6&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-workspace-filter-widget\"\n\t\t:class=\"{\n\t\t\t'cn-workspace-filter-widget--start': isStart,\n\t\t\t'cn-workspace-filter-widget--chips': style === 'chips',\n\t\t\t'cn-workspace-filter-widget--bar': isBar,\n\t\t\t'cn-workspace-filter-widget--inline': content.inline === true,\n\t\t\t'cn-workspace-filter-widget--end': content.align === 'end',\n\t\t}\"\n\t\tdata-testid=\"cn-workspace-filter-widget\">\n\t\t<!-- The start card (`layout: \"start\"`): a title and one line of\n\t\t guidance beside the control, as the \"Contact starten\" card. -->\n\t\t<div v-if=\"isStart && (title || description)\" class=\"cn-workspace-filter-widget__text\">\n\t\t\t<h3 v-if=\"title\" class=\"cn-workspace-filter-widget__title\">\n\t\t\t\t{{ title }}\n\t\t\t</h3>\n\t\t\t<p v-if=\"description\" class=\"cn-workspace-filter-widget__description\">\n\t\t\t\t{{ description }}\n\t\t\t</p>\n\t\t</div>\n\n\t\t<!-- A bar (`layout: \"bar\"`): its `controls` side by side in one row,\n\t\t each a workspace filter of its own (chips, a switch, a select). -->\n\t\t<template v-if=\"isBar\">\n\t\t\t<CnWorkspaceFilterWidget\n\t\t\t\tv-for=\"(control, index) in barControls\"\n\t\t\t\t:key=\"index\"\n\t\t\t\t:content=\"control\"\n\t\t\t\t@change=\"$emit('change', $event)\" />\n\t\t</template>\n\n\t\t<p v-if=\"!isBar && label && style !== 'chips' && style !== 'toggle'\" class=\"cn-workspace-filter-widget__label\">\n\t\t\t{{ label }}\n\t\t</p>\n\n\t\t<!-- An object picker (`objectSource`): searches OpenRegister on the\n\t\t server, so it reaches every object, not a fetched page. -->\n\t\t<CnResourceSelect\n\t\t\tv-if=\"!isBar && objectSource\"\n\t\t\tclass=\"cn-workspace-filter-widget__control\"\n\t\t\t:register=\"objectSource.register\"\n\t\t\t:schema=\"objectSource.schema\"\n\t\t\t:labelField=\"objectSource.labelField || 'title'\"\n\t\t\t:modelValue=\"selected === undefined || selected === null ? '' : String(selected)\"\n\t\t\t:placeholder=\"tr(objectSource.placeholder || '')\"\n\t\t\t:ariaLabelCombobox=\"tr(objectSource.ariaLabel || '') || title || label || writeKey\"\n\t\t\t:preload=\"objectSource.preload !== false\"\n\t\t\t:clearable=\"Boolean(content.allLabel)\"\n\t\t\t:allowCreate=\"false\"\n\t\t\tdata-testid=\"cn-workspace-filter-object\"\n\t\t\t@update:modelValue=\"applySelection($event || '')\" />\n\n\t\t<div v-else-if=\"loading\" class=\"cn-workspace-filter-widget__status\">\n\t\t\t{{ loadingLabel }}\n\t\t</div>\n\n\t\t<!-- Chips style (screens-page-lenses): one compact segmented group,\n\t\t a count after each label, arrow keys move the choice. -->\n\t\t<CnSegmentedControl\n\t\t\tv-else-if=\"style === 'chips'\"\n\t\t\tclass=\"cn-workspace-filter-widget__chips\"\n\t\t\tsize=\"compact\"\n\t\t\t:options=\"chipOptions\"\n\t\t\t:modelValue=\"selected === undefined || selected === null ? null : String(selected)\"\n\t\t\t:ariaLabel=\"label || writeKey\"\n\t\t\tdata-testid=\"cn-workspace-filter-chips\"\n\t\t\t@update:modelValue=\"applySelection\" />\n\n\t\t<!-- Toggle style (screens-page-controls): a switch that writes\n\t\t `onValue` / `offValue` (true / false) to the key. -->\n\t\t<NcCheckboxRadioSwitch\n\t\t\tv-else-if=\"style === 'toggle'\"\n\t\t\tclass=\"cn-workspace-filter-widget__toggle\"\n\t\t\ttype=\"switch\"\n\t\t\t:modelValue=\"toggleOn\"\n\t\t\tdata-testid=\"cn-workspace-filter-toggle\"\n\t\t\t@update:modelValue=\"onToggle\">\n\t\t\t{{ tr(label) }}\n\t\t</NcCheckboxRadioSwitch>\n\n\t\t<!-- Select style -->\n\t\t<NcSelect\n\t\t\tv-else-if=\"style === 'select'\"\n\t\t\t:modelValue=\"selectedOption\"\n\t\t\t:options=\"displayOptions\"\n\t\t\t:clearable=\"false\"\n\t\t\t:inputLabel=\"label || writeKey\"\n\t\t\tlabel=\"label\"\n\t\t\tdata-testid=\"cn-workspace-filter-select\"\n\t\t\t@update:modelValue=\"onSelect\" />\n\n\t\t<!-- Radio-list style (default) -->\n\t\t<ul v-else-if=\"!isBar\"\n\t\t\tclass=\"cn-workspace-filter-widget__list\"\n\t\t\trole=\"radiogroup\"\n\t\t\t:aria-label=\"label || writeKey\">\n\t\t\t<li\n\t\t\t\tv-for=\"option in displayOptions\"\n\t\t\t\t:key=\"String(option.value)\"\n\t\t\t\tclass=\"cn-workspace-filter-widget__item\">\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\ttype=\"radio\"\n\t\t\t\t\t:name=\"radioGroupName\"\n\t\t\t\t\t:modelValue=\"isActive(option) ? String(option.value) : ''\"\n\t\t\t\t\t:value=\"String(option.value)\"\n\t\t\t\t\t:data-testid=\"`cn-workspace-filter-option-${option.value}`\"\n\t\t\t\t\t@update:modelValue=\"onRadioPick(option)\">\n\t\t\t\t\t<span class=\"cn-workspace-filter-widget__option-label\">{{ option.label }}</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tv-if=\"showCounts && option.count !== undefined && option.count !== null\"\n\t\t\t\t\t\tclass=\"cn-workspace-filter-widget__count\">{{ option.count }}</span>\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t</li>\n\t\t</ul>\n\n\t\t<component\n\t\t\t:is=\"startLink.to ? 'router-link' : 'a'\"\n\t\t\tv-if=\"startLink\"\n\t\t\t:to=\"startLink.to || undefined\"\n\t\t\t:href=\"startLink.href || undefined\"\n\t\t\tclass=\"cn-workspace-filter-widget__link\"\n\t\t\tdata-testid=\"cn-workspace-filter-link\">\n\t\t\t{{ startLink.label }}\n\t\t</component>\n\t</div>\n</template>\n\n<script>\nimport { subscribe, unsubscribe } from '@nextcloud/event-bus'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcCheckboxRadioSwitch, NcSelect } from '@nextcloud/vue'\nimport CnResourceSelect from '../CnResourceSelect/CnResourceSelect.vue'\nimport CnSegmentedControl from '../CnSegmentedControl/CnSegmentedControl.vue'\nimport { fetchEndpointSource } from '../../composables/useEndpointSource.js'\nimport { fetchListTotal } from '../../utils/fetchFilterCounts.js'\nimport { resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\n\n/**\n * CnWorkspaceFilterWidget — a dashboard choice-list that writes the selected\n * value into the page workspace context so sibling widgets refetch (#91\n * Wave 3, the `workspace-filter` widget).\n *\n * Options come from ONE of three sources (checked in order):\n * 1. static `options[]` — each `{ value, label, count? }`.\n * 2. an OpenRegister `source` — `{ register, schema, groupBy, filter? }`,\n * fetched from OR's `/grouped` facet so each distinct value becomes an\n * option with its object `count`.\n * 3. an `endpointSource` — `{ url, params?, responsePath? }` returning an\n * array of `{ value, label, count? }` (or `{ id, name }` / bare strings,\n * normalised).\n *\n * The chosen value is written to the workspace context key named by `writes`\n * (`\"@workspace.<key>\"` or a bare `\"<key>\"`). CnDashboardPage provides\n * `cnWorkspaceContext`; every sibling widget whose declarative source\n * interpolates `@workspace.<key>` re-resolves its tokens and refetches when\n * the selection changes — the pipelinq WerkplekQueueFilter contract.\n *\n * An optional leading \"All\" option (`allLabel`) clears the key (writes the\n * empty string), so an optional `@workspace.<key>?` token drops and siblings\n * show the unfiltered set.\n *\n * ```js\n * content: {\n * label: 'Queue',\n * writes: '@workspace.queue',\n * style: 'radio', // 'radio' (default) | 'select' | 'chips'\n * allLabel: 'All',\n * showCounts: true,\n * source: { register: 'pipelinq', schema: 'werkitem', groupBy: 'queue' },\n * }\n * ```\n */\nexport default {\n\tname: 'CnWorkspaceFilterWidget',\n\n\tcomponents: { CnResourceSelect, CnSegmentedControl, NcSelect, NcCheckboxRadioSwitch },\n\n\tinject: {\n\t\t/**\n\t\t * Page-level workspace context. CnDashboardPage provides a `ref({})`;\n\t\t * Vue 2.7's Options-API inject AUTO-UNWRAPS it, so this is the plain\n\t\t * reactive bag (the raw-ref `{ value }` shape is also handled on write).\n\t\t * Null on pages without one (the widget then just emits `@change`).\n\t\t */\n\t\tcnWorkspaceContext: { default: null },\n\t\t/** Host translate function, for the start card's texts. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted configuration blob.\n\t\t *\n\t\t * Start card keys (abstract-start-card): `layout: \"start\"` draws\n\t\t * `title` and `description` beside the control, and `link`\n\t\t * (`{ label, route? | href? }`) after it. `objectSource`\n\t\t * (`{ register, schema, labelField?, placeholder?, ariaLabel?, preload? }`)\n\t\t * makes the control an OpenRegister object picker that searches on the\n\t\t * server and writes the picked object's id; without a selection and\n\t\t * without `allLabel` it selects the first object.\n\t\t *\n\t\t * Chips (screens-page-lenses): `style: \"chips\"` draws the options as one\n\t\t * compact segmented group with a count after each label. An option's\n\t\t * `countFrom` (`{ register, schema, filter? }`, or a list of them that\n\t\t * are added up) counts that list; a static `count` is used otherwise.\n\t\t * `urlKey` keeps the choice in the address (`?<urlKey>=<value>`), and\n\t\t * the address wins over `default` on load.\n\t\t *\n\t\t * Page controls (screens-page-controls): `style: \"toggle\"` is a switch\n\t\t * that writes `onValue` / `offValue` (true / false); `layout: \"bar\"`\n\t\t * with `controls` draws several controls side by side in one row, and\n\t\t * `align: \"end\"` pushes a control to the right edge.\n\t\t *\n\t\t * @type {{label?: string, controls?: Array<object>, onValue?: unknown, offValue?: unknown, align?: string, urlKey?: string, writes?: string, style?: string, allLabel?: string, showCounts?: boolean, options?: Array, source?: object, endpointSource?: object, objectSource?: object, default?: (string|number), layout?: string, title?: string, description?: string, link?: object}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\n\temits: ['change'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Fetched options (from source / endpointSource); static options use content.options directly. */\n\t\t\tfetchedOptions: null,\n\t\t\t/** True while options are being fetched. */\n\t\t\tloading: false,\n\t\t\t/** The currently-selected value (mirrors the workspace key). */\n\t\t\tselected: undefined,\n\t\t\t/** Counted totals of the options that ask (`countFrom`), by option position. */\n\t\t\toptionCounts: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Section label. */\n\t\tlabel() {\n\t\t\treturn this.content.label || ''\n\t\t},\n\n\t\t/** Whether the widget draws the start card. */\n\t\tisStart() {\n\t\t\treturn this.content.layout === 'start'\n\t\t},\n\n\t\t/** The start card's title, translated. */\n\t\ttitle() {\n\t\t\treturn this.tr(this.content.title || '')\n\t\t},\n\n\t\t/** The start card's guidance line, translated. */\n\t\tdescription() {\n\t\t\treturn this.tr(this.content.description || '')\n\t\t},\n\n\t\t/**\n\t\t * The link after the control, or null.\n\t\t *\n\t\t * @return {{label: string, to?: object, href?: string}|null}\n\t\t */\n\t\tstartLink() {\n\t\t\tconst link = this.content.link\n\t\t\tif (!link || typeof link !== 'object' || !link.label) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (link.route && typeof link.route === 'object' && link.route.name) {\n\t\t\t\treturn { label: this.tr(link.label), to: link.route }\n\t\t\t}\n\t\t\tif (typeof link.href === 'string' && link.href !== '') {\n\t\t\t\treturn { label: this.tr(link.label), href: link.href }\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/**\n\t\t * The object picker declaration, or null.\n\t\t *\n\t\t * @return {{register: string, schema: string, labelField?: string}|null}\n\t\t */\n\t\tobjectSource() {\n\t\t\tconst src = this.content.objectSource\n\t\t\treturn src && typeof src === 'object' && src.register && src.schema ? src : null\n\t\t},\n\n\t\t/** Choice style: 'chips', 'select' or 'radio' (default). */\n\t\tstyle() {\n\t\t\treturn ['select', 'chips', 'toggle'].includes(this.content.style) ? this.content.style : 'radio'\n\t\t},\n\n\t\t/**\n\t\t * The chips: each option with its label translated and its count,\n\t\t * the static one or the counted one.\n\t\t *\n\t\t * @return {Array<{value: string, label: string, count?: number}>}\n\t\t * @spec openspec/changes/screens-page-lenses/specs/page-lenses/spec.md#requirement-a-workspace-filter-can-draw-chips-with-counts\n\t\t */\n\t\tchipOptions() {\n\t\t\treturn this.displayOptions.map((option, index) => {\n\t\t\t\tconst counted = this.optionCounts[index]\n\t\t\t\tconst count = counted !== undefined && counted !== null ? counted : option.count\n\t\t\t\tconst chip = { value: String(option.value), label: this.tr(option.label) }\n\t\t\t\tif (this.showCounts && Number.isFinite(count)) {\n\t\t\t\t\tchip.count = count\n\t\t\t\t}\n\t\t\t\treturn chip\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Whether the widget is a bar of controls (`layout: \"bar\"`).\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/screens-page-controls/specs/page-controls/spec.md#requirement-a-bar-holds-the-pages-controls-in-one-row\n\t\t */\n\t\tisBar() {\n\t\t\treturn this.content.layout === 'bar' && Array.isArray(this.content.controls)\n\t\t},\n\n\t\t/**\n\t\t * The bar's controls, each an inline workspace filter of its own.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tbarControls() {\n\t\t\treturn this.content.controls\n\t\t\t\t.filter((control) => control && typeof control === 'object')\n\t\t\t\t.map((control) => ({ ...control, inline: true }))\n\t\t},\n\n\t\t/** @return {unknown} The value a switch writes when on. */\n\t\tonValue() {\n\t\t\treturn this.content.onValue !== undefined ? this.content.onValue : true\n\t\t},\n\n\t\t/** @return {unknown} The value a switch writes when off. */\n\t\toffValue() {\n\t\t\treturn this.content.offValue !== undefined ? this.content.offValue : false\n\t\t},\n\n\t\t/** @return {boolean} Whether the switch is on. */\n\t\ttoggleOn() {\n\t\t\treturn this.selected !== undefined && String(this.selected) === String(this.onValue)\n\t\t},\n\n\t\t/**\n\t\t * The address key that carries the choice (`urlKey`), or ''.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\turlKey() {\n\t\t\treturn typeof this.content.urlKey === 'string' ? this.content.urlKey : ''\n\t\t},\n\n\t\t/** Whether to render per-option counts. */\n\t\tshowCounts() {\n\t\t\treturn this.content.showCounts !== false\n\t\t},\n\n\t\t/** The bare workspace key the selection writes (strips a `@workspace.` / `@page.` prefix). */\n\t\twriteKey() {\n\t\t\tconst w = this.content.writes || ''\n\t\t\treturn String(w).replace(/^@(workspace|page)\\./, '')\n\t\t},\n\n\t\t/** A stable radiogroup name so the radios are mutually exclusive. */\n\t\tradioGroupName() {\n\t\t\treturn `cn-wsf-${this.writeKey || 'filter'}`\n\t\t},\n\n\t\t/** The option list actually rendered — the optional \"All\" entry prepended. */\n\t\tdisplayOptions() {\n\t\t\tconst base = this.normalisedOptions\n\t\t\tif (this.content.allLabel) {\n\t\t\t\treturn [{ value: '', label: this.content.allLabel }, ...base]\n\t\t\t}\n\t\t\treturn base\n\t\t},\n\n\t\t/** Normalised `{ value, label, count? }` options from whichever source is configured. */\n\t\tnormalisedOptions() {\n\t\t\tconst raw = this.fetchedOptions !== null\n\t\t\t\t? this.fetchedOptions\n\t\t\t\t: (Array.isArray(this.content.options) ? this.content.options : [])\n\t\t\treturn raw.map((o) => this.normaliseOption(o)).filter((o) => o.value !== undefined && o.value !== null)\n\t\t},\n\n\t\t/** The selected option object (for NcSelect). */\n\t\tselectedOption() {\n\t\t\treturn this.displayOptions.find((o) => String(o.value) === String(this.selected)) || null\n\t\t},\n\n\t\t/**\n\t\t * The reactive workspace value for this key — watched so an external\n\t\t * write (another widget) syncs the local selection.\n\t\t *\n\t\t * @return {unknown}\n\t\t */\n\t\tworkspaceValue() {\n\t\t\treturn this.readWorkspace()\n\t\t},\n\n\t\tloadingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Loading…')\n\t\t},\n\t},\n\n\twatch: {\n\t\t/** Re-fetch options when a dynamic source changes. */\n\t\tcontent: {\n\t\t\tdeep: true,\n\t\t\thandler() {\n\t\t\t\tthis.loadOptions()\n\t\t\t},\n\t\t},\n\n\t\t/**\n\t\t * Track external workspace changes (another widget writing the same key).\n\t\t *\n\t\t * @param {unknown} val The new workspace value for this key.\n\t\t */\n\t\tworkspaceValue(val) {\n\t\t\tif (val !== undefined) {\n\t\t\t\tthis.selected = val\n\t\t\t}\n\t\t},\n\t},\n\n\tcreated() {\n\t\t// Seed the selection: existing workspace value → content.default → the\n\t\t// \"All\" clear option (empty) when present, else the first option once\n\t\t// loaded (handled in loadOptions).\n\t\tconst fromAddress = this.readAddress()\n\t\tconst existing = this.readWorkspace()\n\t\tif (fromAddress !== undefined) {\n\t\t\tthis.selected = fromAddress\n\t\t\tthis.writeWorkspace(fromAddress)\n\t\t} else if (existing !== undefined) {\n\t\t\tthis.selected = existing\n\t\t} else if (this.content.default !== undefined) {\n\t\t\tthis.selected = this.content.default\n\t\t\tthis.writeWorkspace(this.content.default)\n\t\t} else if (this.style === 'toggle') {\n\t\t\t// A switch starts off, and says so, so a condition on its key holds.\n\t\t\tthis.selected = this.offValue\n\t\t\tthis.writeWorkspace(this.offValue)\n\t\t}\n\t\tthis.loadOptions()\n\t},\n\n\tmounted() {\n\t\tthis.onPageRefresh = () => this.loadCounts()\n\t\tsubscribe('cn:page:refresh', this.onPageRefresh)\n\t\tthis.loadCounts()\n\t},\n\n\tbeforeUnmount() {\n\t\tunsubscribe('cn:page:refresh', this.onPageRefresh)\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The choice named in the address (`?<urlKey>=…`), when it is one of\n\t\t * the options; undefined otherwise.\n\t\t *\n\t\t * @return {string|undefined}\n\t\t * @spec openspec/changes/screens-page-lenses/specs/page-lenses/spec.md#requirement-the-choice-is-in-the-address\n\t\t */\n\t\treadAddress() {\n\t\t\tif (!this.urlKey || !this.$route || !this.$route.query) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tconst raw = this.$route.query[this.urlKey]\n\t\t\tif (this.style === 'toggle') {\n\t\t\t\tif (raw === undefined) {\n\t\t\t\t\treturn undefined\n\t\t\t\t}\n\t\t\t\treturn [this.onValue, this.offValue].find((value) => String(value) === String(raw))\n\t\t\t}\n\t\t\tconst known = this.displayOptions.some((option) => String(option.value) === String(raw))\n\t\t\treturn known ? String(raw) : undefined\n\t\t},\n\n\t\t/**\n\t\t * Write the choice to the address, replacing the history entry and\n\t\t * keeping the other query keys.\n\t\t *\n\t\t * @param {unknown} value The chosen value.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/screens-page-lenses/specs/page-lenses/spec.md#requirement-the-choice-is-in-the-address\n\t\t */\n\t\twriteAddress(value) {\n\t\t\tif (!this.urlKey || !this.$router || !this.$route) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst query = this.$route.query || {}\n\t\t\tif (String(query[this.urlKey]) === String(value)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst result = this.$router.replace({ query: { ...query, [this.urlKey]: String(value) } })\n\t\t\tif (result && typeof result.catch === 'function') {\n\t\t\t\tresult.catch(() => {})\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Count the lists behind the options that ask (`countFrom`: a list\n\t\t * `{ register, schema, filter? }`, or several that are added up). One\n\t\t * list request each; an option whose count failed shows none.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/screens-page-lenses/specs/page-lenses/spec.md#requirement-a-workspace-filter-can-draw-chips-with-counts\n\t\t */\n\t\tasync loadCounts() {\n\t\t\tconst options = this.displayOptions\n\t\t\tconst seq = (this.countSeq = (this.countSeq || 0) + 1)\n\t\t\tconst ctx = { workspace: this.readWorkspaceBag() }\n\t\t\tawait Promise.all(options.map(async (option, index) => {\n\t\t\t\tconst raw = (Array.isArray(this.content.options) ? this.content.options : [])\n\t\t\t\t\t.find((candidate) => candidate && typeof candidate === 'object' && String(candidate.value ?? candidate.id) === String(option.value))\n\t\t\t\tconst from = raw ? raw.countFrom : null\n\t\t\t\tconst lists = (Array.isArray(from) ? from : [from]).filter((list) => list && typeof list === 'object' && list.register && list.schema)\n\t\t\t\tif (lists.length === 0) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tlet total\n\t\t\t\ttry {\n\t\t\t\t\tconst parts = await Promise.all(lists.map((list) => fetchListTotal(list.register, list.schema, list.filter || {}, ctx)))\n\t\t\t\t\tif (parts.some((part) => part === null)) {\n\t\t\t\t\t\tthrow new Error('no total')\n\t\t\t\t\t}\n\t\t\t\t\ttotal = parts.reduce((sum, part) => sum + part, 0)\n\t\t\t\t} catch {\n\t\t\t\t\ttotal = null\n\t\t\t\t}\n\t\t\t\tif (seq === this.countSeq) {\n\t\t\t\t\tthis.optionCounts = { ...this.optionCounts, [index]: total }\n\t\t\t\t}\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Normalise a raw option into `{ value, label, count? }`. Accepts\n\t\t * `{ value, label, count }`, `{ id, name }`, `{ key, count }` (OR\n\t\t * facet), or a bare string/number.\n\t\t *\n\t\t * @param {unknown} o The raw option.\n\t\t * @return {{value: unknown, label: string, count?: number}}\n\t\t */\n\t\tnormaliseOption(o) {\n\t\t\tif (o === null || o === undefined) {\n\t\t\t\treturn { value: undefined, label: '' }\n\t\t\t}\n\t\t\tif (typeof o !== 'object') {\n\t\t\t\treturn { value: o, label: String(o) }\n\t\t\t}\n\t\t\tconst value = o.value !== undefined ? o.value : (o.id !== undefined ? o.id : o.key)\n\t\t\tconst label = o.label !== undefined ? o.label : (o.name !== undefined ? o.name : String(value))\n\t\t\tconst out = { value, label: String(label) }\n\t\t\tif (o.count !== undefined) {\n\t\t\t\tout.count = o.count\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Load options for the active source (static content.options need no\n\t\t * fetch). Seeds the first option as the selection when nothing is set\n\t\t * and no \"All\" clear option exists.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadOptions() {\n\t\t\tif (this.objectSource) {\n\t\t\t\tawait this.seedFirstObject()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.content.endpointSource && this.content.endpointSource.url) {\n\t\t\t\tawait this.loadFromEndpoint()\n\t\t\t} else if (this.content.source && this.content.source.schema) {\n\t\t\t\tawait this.loadFromOrSource()\n\t\t\t} else {\n\t\t\t\tthis.fetchedOptions = null\n\t\t\t}\n\t\t\t// Default selection: when nothing is selected and there's no \"All\"\n\t\t\t// clear affordance, adopt the first option so siblings have a value.\n\t\t\tif ((this.selected === undefined || this.selected === '') && !this.content.allLabel) {\n\t\t\t\tconst first = this.normalisedOptions[0]\n\t\t\t\tif (first && this.readWorkspace() === undefined && this.content.default === undefined) {\n\t\t\t\t\tthis.selected = first.value\n\t\t\t\t\tthis.writeWorkspace(first.value)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Translate a manifest string through the host app.\n\t\t *\n\t\t * @param {string} text The text.\n\t\t * @return {string}\n\t\t */\n\t\ttr(text) {\n\t\t\tif (typeof text !== 'string' || text === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\t/**\n\t\t * The object picker's default: with nothing selected, no `default` and\n\t\t * no `allLabel`, select the first object the source lists, so the page\n\t\t * always has one in focus. Fail-safe: a failed read selects nothing.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync seedFirstObject() {\n\t\t\tif ((this.selected !== undefined && this.selected !== '') || this.content.allLabel || this.content.default !== undefined) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst src = this.objectSource\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/objects/{register}/{schema}', { register: src.register, schema: src.schema })\n\t\t\t\tconst res = await axios.get(url, { params: { _limit: 1 } })\n\t\t\t\tconst data = res && res.data\n\t\t\t\tconst rows = Array.isArray(data) ? data : ((data && data.results) || [])\n\t\t\t\tconst first = rows[0]\n\t\t\t\tconst id = first && (first.id || (first['@self'] && first['@self'].id))\n\t\t\t\tif (id && (this.selected === undefined || this.selected === '') && this.readWorkspace() === undefined) {\n\t\t\t\t\tthis.selected = String(id)\n\t\t\t\t\tthis.writeWorkspace(String(id))\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Nothing selected: the siblings wait for a pick.\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Fetch options from an `endpointSource` (array of options at\n\t\t * responsePath). Fail-safe: a failed fetch leaves an empty list.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadFromEndpoint() {\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst payload = await fetchEndpointSource(this.content.endpointSource, {\n\t\t\t\t\tworkspace: this.readWorkspaceBag(),\n\t\t\t\t})\n\t\t\t\tthis.fetchedOptions = Array.isArray(payload) ? payload : []\n\t\t\t} catch {\n\t\t\t\tthis.fetchedOptions = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Fetch options from an OpenRegister `/grouped` facet — each distinct\n\t\t * `groupBy` value becomes an option with its object count. Fail-safe.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadFromOrSource() {\n\t\t\tconst src = this.content.source\n\t\t\tthis.loading = true\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(\n\t\t\t\t\t'/apps/openregister/api/objects/aggregations/{register}/{schema}/grouped',\n\t\t\t\t\t{ register: src.register, schema: src.schema },\n\t\t\t\t)\n\t\t\t\tconst params = { groupBy: src.groupBy, metric: 'count' }\n\t\t\t\tconst filter = resolveFilterTokens(src.filter || {}, { workspace: this.readWorkspaceBag() })\n\t\t\t\tfor (const [k, v] of Object.entries(filter)) {\n\t\t\t\t\tif (v !== '' && v !== null && v !== undefined) {\n\t\t\t\t\t\tparams[`filter[${k}]`] = v\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst res = await axios.get(url, { params })\n\t\t\t\tconst groups = (res && res.data && res.data.groups) || []\n\t\t\t\tthis.fetchedOptions = groups\n\t\t\t\t\t.filter((g) => g.key !== null && g.key !== undefined && g.key !== '')\n\t\t\t\t\t.map((g) => ({ value: g.key, label: String(g.key), count: Number(g.value) || 0 }))\n\t\t\t} catch {\n\t\t\t\tthis.fetchedOptions = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Whether an option is the active selection.\n\t\t *\n\t\t * @param {object} option The option.\n\t\t * @return {boolean}\n\t\t */\n\t\tisActive(option) {\n\t\t\treturn String(option.value) === String(this.selected ?? '')\n\t\t},\n\n\t\t/**\n\t\t * Radio-list pick handler.\n\t\t *\n\t\t * @param {object} option The picked option.\n\t\t * @return {void}\n\t\t */\n\t\tonRadioPick(option) {\n\t\t\tthis.applySelection(option.value)\n\t\t},\n\n\t\t/**\n\t\t * Switch handler: write `onValue` or `offValue`.\n\t\t *\n\t\t * @param {boolean} on Whether the switch is on.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/screens-page-controls/specs/page-controls/spec.md#requirement-a-toggle-and-a-select-are-page-controls\n\t\t */\n\t\tonToggle(on) {\n\t\t\tthis.applySelection(on ? this.onValue : this.offValue)\n\t\t},\n\n\t\t/**\n\t\t * NcSelect input handler.\n\t\t *\n\t\t * @param {object|null} option The picked option object.\n\t\t * @return {void}\n\t\t */\n\t\tonSelect(option) {\n\t\t\tthis.applySelection(option ? option.value : '')\n\t\t},\n\n\t\t/**\n\t\t * Apply a new selection: mirror it locally, write it into the workspace\n\t\t * context (so sibling widgets refetch), and emit `@change`.\n\t\t *\n\t\t * @param {unknown} value The new value.\n\t\t * @return {void}\n\t\t */\n\t\tapplySelection(value) {\n\t\t\tthis.selected = value\n\t\t\tthis.writeWorkspace(value)\n\t\t\tthis.writeAddress(value)\n\t\t\t/**\n\t\t\t * @event change Emitted when the selection changes. Payload: `{ key, value }`.\n\t\t\t */\n\t\t\tthis.$emit('change', { key: this.writeKey, value })\n\t\t},\n\n\t\t/**\n\t\t * The unwrapped workspace bag (or `{}`).\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\treadWorkspaceBag() {\n\t\t\tconst holder = this.cnWorkspaceContext\n\t\t\tif (!holder || typeof holder !== 'object') {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\treturn ('value' in holder) ? (holder.value || {}) : holder\n\t\t},\n\n\t\t/**\n\t\t * Read the current workspace value for this widget's key (undefined\n\t\t * when unset).\n\t\t *\n\t\t * @return {unknown}\n\t\t */\n\t\treadWorkspace() {\n\t\t\tif (!this.writeKey) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tconst bag = this.readWorkspaceBag()\n\t\t\treturn bag[this.writeKey]\n\t\t},\n\n\t\t/**\n\t\t * Write a value into the reactive workspace bag under this widget's key\n\t\t * (the CnInteractionFormWidget write pattern — replace-in-place for the\n\t\t * unwrapped bag, `.value` for the raw-ref shape).\n\t\t *\n\t\t * @param {unknown} value The value to write.\n\t\t * @return {void}\n\t\t */\n\t\twriteWorkspace(value) {\n\t\t\tif (!this.writeKey) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst holder = this.cnWorkspaceContext\n\t\t\tif (!holder || typeof holder !== 'object') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif ('value' in holder) {\n\t\t\t\tholder.value = { ...(holder.value || {}), [this.writeKey]: value }\n\t\t\t\treturn\n\t\t\t}\n\t\t\tholder[this.writeKey] = value\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-workspace-filter-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\twidth: 100%;\n}\n\n.cn-workspace-filter-widget__label {\n\tmargin: 0;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-workspace-filter-widget__status {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-workspace-filter-widget__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n/* The chips: the compact segmented group, left, in the card the grid draws. */\n.cn-workspace-filter-widget--chips {\n\tflex-direction: row;\n\tflex-wrap: wrap;\n\talign-items: center;\n}\n\n/* A bar: its controls in one row, an `align: \"end\"` control pushed right. */\n.cn-workspace-filter-widget--bar {\n\tflex-direction: row;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 12px 16px;\n}\n\n/* An inline control sits in a bar: label left of the control, width to its content. */\n.cn-workspace-filter-widget--inline {\n\tflex-direction: row;\n\talign-items: center;\n\tgap: 8px;\n\twidth: auto;\n}\n\n.cn-workspace-filter-widget--inline .cn-workspace-filter-widget__label {\n\tfont-weight: 400;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-workspace-filter-widget--end {\n\tmargin-inline-start: auto;\n}\n\n.cn-workspace-filter-widget--end ~ .cn-workspace-filter-widget--end {\n\tmargin-inline-start: 0;\n}\n\n.cn-workspace-filter-widget__toggle {\n\tfont-weight: 600;\n}\n\n/* The start card: text left, control (and link) right, as PqDashboard's \"Contact starten\". */\n.cn-workspace-filter-widget--start {\n\tflex-direction: row;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 16px 24px;\n}\n\n.cn-workspace-filter-widget__text {\n\tflex: 1 1 320px;\n\tmin-width: 0;\n}\n\n.cn-workspace-filter-widget__title {\n\tmargin: 0 0 4px;\n\tfont-size: 17px;\n\tfont-weight: 700;\n\tcolor: var(--color-main-text);\n}\n\n.cn-workspace-filter-widget__description {\n\tmargin: 0;\n\tmax-width: 420px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-workspace-filter-widget--start .cn-workspace-filter-widget__control,\n.cn-workspace-filter-widget--start > .v-select {\n\tflex: 0 1 480px;\n\tmin-width: 240px;\n}\n\n.cn-workspace-filter-widget__link {\n\tfont-weight: 600;\n\tcolor: var(--color-primary-element);\n\ttext-decoration: underline;\n\twhite-space: nowrap;\n}\n\n.cn-workspace-filter-widget__count {\n\tmargin-inline-start: 6px;\n\tpadding: 0 6px;\n\tborder-radius: 999px;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n}\n</style>\n"],"names":["_createElementBlock","_openBlock","_toDisplayString","_Fragment","_createBlock","_createTextVNode","_renderList","_createElementVNode"],"mappings":"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"}
1
+ {"version":3,"file":"CnWorkspaceFilterWidget.vue3.js","sources":["../../../../src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue?vue&type=template&id=f0d5c1c6&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-workspace-filter-widget\"\n\t\t:class=\"{\n\t\t\t'cn-workspace-filter-widget--start': isStart,\n\t\t\t'cn-workspace-filter-widget--chips': style === 'chips',\n\t\t\t'cn-workspace-filter-widget--bar': isBar,\n\t\t\t'cn-workspace-filter-widget--inline': content.inline === true,\n\t\t\t'cn-workspace-filter-widget--end': content.align === 'end',\n\t\t}\"\n\t\tdata-testid=\"cn-workspace-filter-widget\">\n\t\t<!-- The start card (`layout: \"start\"`): a title and one line of\n\t\t guidance beside the control, as the \"Contact starten\" card. -->\n\t\t<div v-if=\"isStart && (title || description)\" class=\"cn-workspace-filter-widget__text\">\n\t\t\t<h3 v-if=\"title\" class=\"cn-workspace-filter-widget__title\">\n\t\t\t\t{{ title }}\n\t\t\t</h3>\n\t\t\t<p v-if=\"description\" class=\"cn-workspace-filter-widget__description\">\n\t\t\t\t{{ description }}\n\t\t\t</p>\n\t\t</div>\n\n\t\t<!-- A bar (`layout: \"bar\"`): its `controls` side by side in one row,\n\t\t each a workspace filter of its own (chips, a switch, a select). -->\n\t\t<template v-if=\"isBar\">\n\t\t\t<CnWorkspaceFilterWidget\n\t\t\t\tv-for=\"(control, index) in barControls\"\n\t\t\t\t:key=\"index\"\n\t\t\t\t:content=\"control\"\n\t\t\t\t@change=\"$emit('change', $event)\" />\n\t\t</template>\n\n\t\t<p v-if=\"!isBar && label && style !== 'chips' && style !== 'toggle'\" class=\"cn-workspace-filter-widget__label\">\n\t\t\t{{ label }}\n\t\t</p>\n\n\t\t<!-- An object picker (`objectSource`): searches OpenRegister on the\n\t\t server, so it reaches every object, not a fetched page. -->\n\t\t<CnResourceSelect\n\t\t\tv-if=\"!isBar && objectSource\"\n\t\t\tclass=\"cn-workspace-filter-widget__control\"\n\t\t\t:register=\"objectSource.register\"\n\t\t\t:schema=\"objectSource.schema\"\n\t\t\t:labelField=\"objectSource.labelField || 'title'\"\n\t\t\t:modelValue=\"selected === undefined || selected === null ? '' : String(selected)\"\n\t\t\t:placeholder=\"tr(objectSource.placeholder || '')\"\n\t\t\t:ariaLabelCombobox=\"tr(objectSource.ariaLabel || '') || title || label || writeKey\"\n\t\t\t:preload=\"objectSource.preload !== false\"\n\t\t\t:clearable=\"Boolean(content.allLabel)\"\n\t\t\t:allowCreate=\"false\"\n\t\t\tdata-testid=\"cn-workspace-filter-object\"\n\t\t\t@update:modelValue=\"applySelection($event || '')\" />\n\n\t\t<div v-else-if=\"loading\" class=\"cn-workspace-filter-widget__status\">\n\t\t\t{{ loadingLabel }}\n\t\t</div>\n\n\t\t<!-- Chips style (screens-page-lenses): one compact segmented group,\n\t\t a count after each label, arrow keys move the choice. -->\n\t\t<CnSegmentedControl\n\t\t\tv-else-if=\"style === 'chips'\"\n\t\t\tclass=\"cn-workspace-filter-widget__chips\"\n\t\t\tsize=\"compact\"\n\t\t\t:options=\"chipOptions\"\n\t\t\t:modelValue=\"selected === undefined || selected === null ? null : String(selected)\"\n\t\t\t:ariaLabel=\"label || writeKey\"\n\t\t\tdata-testid=\"cn-workspace-filter-chips\"\n\t\t\t@update:modelValue=\"applySelection\" />\n\n\t\t<!-- Toggle style (screens-page-controls): a switch that writes\n\t\t `onValue` / `offValue` (true / false) to the key. -->\n\t\t<NcCheckboxRadioSwitch\n\t\t\tv-else-if=\"style === 'toggle'\"\n\t\t\tclass=\"cn-workspace-filter-widget__toggle\"\n\t\t\ttype=\"switch\"\n\t\t\t:modelValue=\"toggleOn\"\n\t\t\tdata-testid=\"cn-workspace-filter-toggle\"\n\t\t\t@update:modelValue=\"onToggle\">\n\t\t\t{{ tr(label) }}\n\t\t</NcCheckboxRadioSwitch>\n\n\t\t<!-- Select style -->\n\t\t<NcSelect\n\t\t\tv-else-if=\"style === 'select'\"\n\t\t\t:modelValue=\"selectedOption\"\n\t\t\t:options=\"displayOptions\"\n\t\t\t:clearable=\"false\"\n\t\t\t:inputLabel=\"content.inline ? '' : (label || writeKey)\"\n\t\t\t:labelOutside=\"content.inline === true\"\n\t\t\t:ariaLabelCombobox=\"label || writeKey\"\n\t\t\tlabel=\"label\"\n\t\t\tdata-testid=\"cn-workspace-filter-select\"\n\t\t\t@update:modelValue=\"onSelect\" />\n\n\t\t<!-- Radio-list style (default) -->\n\t\t<ul v-else-if=\"!isBar\"\n\t\t\tclass=\"cn-workspace-filter-widget__list\"\n\t\t\trole=\"radiogroup\"\n\t\t\t:aria-label=\"label || writeKey\">\n\t\t\t<li\n\t\t\t\tv-for=\"option in displayOptions\"\n\t\t\t\t:key=\"String(option.value)\"\n\t\t\t\tclass=\"cn-workspace-filter-widget__item\">\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\ttype=\"radio\"\n\t\t\t\t\t:name=\"radioGroupName\"\n\t\t\t\t\t:modelValue=\"isActive(option) ? String(option.value) : ''\"\n\t\t\t\t\t:value=\"String(option.value)\"\n\t\t\t\t\t:data-testid=\"`cn-workspace-filter-option-${option.value}`\"\n\t\t\t\t\t@update:modelValue=\"onRadioPick(option)\">\n\t\t\t\t\t<span class=\"cn-workspace-filter-widget__option-label\">{{ option.label }}</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tv-if=\"showCounts && option.count !== undefined && option.count !== null\"\n\t\t\t\t\t\tclass=\"cn-workspace-filter-widget__count\">{{ option.count }}</span>\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t</li>\n\t\t</ul>\n\n\t\t<component\n\t\t\t:is=\"startLink.to ? 'router-link' : 'a'\"\n\t\t\tv-if=\"startLink\"\n\t\t\t:to=\"startLink.to || undefined\"\n\t\t\t:href=\"startLink.href || undefined\"\n\t\t\tclass=\"cn-workspace-filter-widget__link\"\n\t\t\tdata-testid=\"cn-workspace-filter-link\">\n\t\t\t{{ startLink.label }}\n\t\t</component>\n\t</div>\n</template>\n\n<script>\nimport { subscribe, unsubscribe } from '@nextcloud/event-bus'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcCheckboxRadioSwitch, NcSelect } from '@nextcloud/vue'\nimport CnResourceSelect from '../CnResourceSelect/CnResourceSelect.vue'\nimport CnSegmentedControl from '../CnSegmentedControl/CnSegmentedControl.vue'\nimport { fetchEndpointSource } from '../../composables/useEndpointSource.js'\nimport { fetchListTotal } from '../../utils/fetchFilterCounts.js'\nimport { resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\n\n/**\n * CnWorkspaceFilterWidget — a dashboard choice-list that writes the selected\n * value into the page workspace context so sibling widgets refetch (#91\n * Wave 3, the `workspace-filter` widget).\n *\n * Options come from ONE of three sources (checked in order):\n * 1. static `options[]` — each `{ value, label, count? }`.\n * 2. an OpenRegister `source` — `{ register, schema, groupBy, filter? }`,\n * fetched from OR's `/grouped` facet so each distinct value becomes an\n * option with its object `count`.\n * 3. an `endpointSource` — `{ url, params?, responsePath? }` returning an\n * array of `{ value, label, count? }` (or `{ id, name }` / bare strings,\n * normalised).\n *\n * The chosen value is written to the workspace context key named by `writes`\n * (`\"@workspace.<key>\"` or a bare `\"<key>\"`). CnDashboardPage provides\n * `cnWorkspaceContext`; every sibling widget whose declarative source\n * interpolates `@workspace.<key>` re-resolves its tokens and refetches when\n * the selection changes — the pipelinq WerkplekQueueFilter contract.\n *\n * An optional leading \"All\" option (`allLabel`) clears the key (writes the\n * empty string), so an optional `@workspace.<key>?` token drops and siblings\n * show the unfiltered set.\n *\n * ```js\n * content: {\n * label: 'Queue',\n * writes: '@workspace.queue',\n * style: 'radio', // 'radio' (default) | 'select' | 'chips'\n * allLabel: 'All',\n * showCounts: true,\n * source: { register: 'pipelinq', schema: 'werkitem', groupBy: 'queue' },\n * }\n * ```\n */\nexport default {\n\tname: 'CnWorkspaceFilterWidget',\n\n\tcomponents: { CnResourceSelect, CnSegmentedControl, NcSelect, NcCheckboxRadioSwitch },\n\n\tinject: {\n\t\t/**\n\t\t * Page-level workspace context. CnDashboardPage provides a `ref({})`;\n\t\t * Vue 2.7's Options-API inject AUTO-UNWRAPS it, so this is the plain\n\t\t * reactive bag (the raw-ref `{ value }` shape is also handled on write).\n\t\t * Null on pages without one (the widget then just emits `@change`).\n\t\t */\n\t\tcnWorkspaceContext: { default: null },\n\t\t/** Host translate function, for the start card's texts. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted configuration blob.\n\t\t *\n\t\t * Start card keys (abstract-start-card): `layout: \"start\"` draws\n\t\t * `title` and `description` beside the control, and `link`\n\t\t * (`{ label, route? | href? }`) after it. `objectSource`\n\t\t * (`{ register, schema, labelField?, placeholder?, ariaLabel?, preload? }`)\n\t\t * makes the control an OpenRegister object picker that searches on the\n\t\t * server and writes the picked object's id; without a selection and\n\t\t * without `allLabel` it selects the first object.\n\t\t *\n\t\t * Chips (screens-page-lenses): `style: \"chips\"` draws the options as one\n\t\t * compact segmented group with a count after each label. An option's\n\t\t * `countFrom` (`{ register, schema, filter? }`, or a list of them that\n\t\t * are added up) counts that list; a static `count` is used otherwise.\n\t\t * `urlKey` keeps the choice in the address (`?<urlKey>=<value>`), and\n\t\t * the address wins over `default` on load.\n\t\t *\n\t\t * Page controls (screens-page-controls): `style: \"toggle\"` is a switch\n\t\t * that writes `onValue` / `offValue` (true / false); `layout: \"bar\"`\n\t\t * with `controls` draws several controls side by side in one row, and\n\t\t * `align: \"end\"` pushes a control to the right edge.\n\t\t *\n\t\t * @type {{label?: string, controls?: Array<object>, onValue?: unknown, offValue?: unknown, align?: string, urlKey?: string, writes?: string, style?: string, allLabel?: string, showCounts?: boolean, options?: Array, source?: object, endpointSource?: object, objectSource?: object, default?: (string|number), layout?: string, title?: string, description?: string, link?: object}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\n\temits: ['change'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Fetched options (from source / endpointSource); static options use content.options directly. */\n\t\t\tfetchedOptions: null,\n\t\t\t/** True while options are being fetched. */\n\t\t\tloading: false,\n\t\t\t/** The currently-selected value (mirrors the workspace key). */\n\t\t\tselected: undefined,\n\t\t\t/** Counted totals of the options that ask (`countFrom`), by option position. */\n\t\t\toptionCounts: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Section label. */\n\t\tlabel() {\n\t\t\treturn this.content.label || ''\n\t\t},\n\n\t\t/** Whether the widget draws the start card. */\n\t\tisStart() {\n\t\t\treturn this.content.layout === 'start'\n\t\t},\n\n\t\t/** The start card's title, translated. */\n\t\ttitle() {\n\t\t\treturn this.tr(this.content.title || '')\n\t\t},\n\n\t\t/** The start card's guidance line, translated. */\n\t\tdescription() {\n\t\t\treturn this.tr(this.content.description || '')\n\t\t},\n\n\t\t/**\n\t\t * The link after the control, or null.\n\t\t *\n\t\t * @return {{label: string, to?: object, href?: string}|null}\n\t\t */\n\t\tstartLink() {\n\t\t\tconst link = this.content.link\n\t\t\tif (!link || typeof link !== 'object' || !link.label) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (link.route && typeof link.route === 'object' && link.route.name) {\n\t\t\t\treturn { label: this.tr(link.label), to: link.route }\n\t\t\t}\n\t\t\tif (typeof link.href === 'string' && link.href !== '') {\n\t\t\t\treturn { label: this.tr(link.label), href: link.href }\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/**\n\t\t * The object picker declaration, or null.\n\t\t *\n\t\t * @return {{register: string, schema: string, labelField?: string}|null}\n\t\t */\n\t\tobjectSource() {\n\t\t\tconst src = this.content.objectSource\n\t\t\treturn src && typeof src === 'object' && src.register && src.schema ? src : null\n\t\t},\n\n\t\t/** Choice style: 'chips', 'select' or 'radio' (default). */\n\t\tstyle() {\n\t\t\treturn ['select', 'chips', 'toggle'].includes(this.content.style) ? this.content.style : 'radio'\n\t\t},\n\n\t\t/**\n\t\t * The chips: each option with its label translated and its count,\n\t\t * the static one or the counted one.\n\t\t *\n\t\t * @return {Array<{value: string, label: string, count?: number}>}\n\t\t * @spec openspec/changes/screens-page-lenses/specs/page-lenses/spec.md#requirement-a-workspace-filter-can-draw-chips-with-counts\n\t\t */\n\t\tchipOptions() {\n\t\t\treturn this.displayOptions.map((option, index) => {\n\t\t\t\tconst counted = this.optionCounts[index]\n\t\t\t\tconst count = counted !== undefined && counted !== null ? counted : option.count\n\t\t\t\tconst chip = { value: String(option.value), label: this.tr(option.label) }\n\t\t\t\tif (this.showCounts && Number.isFinite(count)) {\n\t\t\t\t\tchip.count = count\n\t\t\t\t}\n\t\t\t\treturn chip\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Whether the widget is a bar of controls (`layout: \"bar\"`).\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/screens-page-controls/specs/page-controls/spec.md#requirement-a-bar-holds-the-pages-controls-in-one-row\n\t\t */\n\t\tisBar() {\n\t\t\treturn this.content.layout === 'bar' && Array.isArray(this.content.controls)\n\t\t},\n\n\t\t/**\n\t\t * The bar's controls, each an inline workspace filter of its own.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tbarControls() {\n\t\t\treturn this.content.controls\n\t\t\t\t.filter((control) => control && typeof control === 'object')\n\t\t\t\t.map((control) => ({ ...control, inline: true }))\n\t\t},\n\n\t\t/** @return {unknown} The value a switch writes when on. */\n\t\tonValue() {\n\t\t\treturn this.content.onValue !== undefined ? this.content.onValue : true\n\t\t},\n\n\t\t/** @return {unknown} The value a switch writes when off. */\n\t\toffValue() {\n\t\t\treturn this.content.offValue !== undefined ? this.content.offValue : false\n\t\t},\n\n\t\t/** @return {boolean} Whether the switch is on. */\n\t\ttoggleOn() {\n\t\t\treturn this.selected !== undefined && String(this.selected) === String(this.onValue)\n\t\t},\n\n\t\t/**\n\t\t * The address key that carries the choice (`urlKey`), or ''.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\turlKey() {\n\t\t\treturn typeof this.content.urlKey === 'string' ? this.content.urlKey : ''\n\t\t},\n\n\t\t/** Whether to render per-option counts. */\n\t\tshowCounts() {\n\t\t\treturn this.content.showCounts !== false\n\t\t},\n\n\t\t/** The bare workspace key the selection writes (strips a `@workspace.` / `@page.` prefix). */\n\t\twriteKey() {\n\t\t\tconst w = this.content.writes || ''\n\t\t\treturn String(w).replace(/^@(workspace|page)\\./, '')\n\t\t},\n\n\t\t/** A stable radiogroup name so the radios are mutually exclusive. */\n\t\tradioGroupName() {\n\t\t\treturn `cn-wsf-${this.writeKey || 'filter'}`\n\t\t},\n\n\t\t/** The option list actually rendered — the optional \"All\" entry prepended. */\n\t\tdisplayOptions() {\n\t\t\tconst base = this.normalisedOptions\n\t\t\tif (this.content.allLabel) {\n\t\t\t\treturn [{ value: '', label: this.content.allLabel }, ...base]\n\t\t\t}\n\t\t\treturn base\n\t\t},\n\n\t\t/** Normalised `{ value, label, count? }` options from whichever source is configured. */\n\t\tnormalisedOptions() {\n\t\t\tconst raw = this.fetchedOptions !== null\n\t\t\t\t? this.fetchedOptions\n\t\t\t\t: (Array.isArray(this.content.options) ? this.content.options : [])\n\t\t\treturn raw.map((o) => this.normaliseOption(o)).filter((o) => o.value !== undefined && o.value !== null)\n\t\t},\n\n\t\t/** The selected option object (for NcSelect). */\n\t\tselectedOption() {\n\t\t\treturn this.displayOptions.find((o) => String(o.value) === String(this.selected)) || null\n\t\t},\n\n\t\t/**\n\t\t * The reactive workspace value for this key — watched so an external\n\t\t * write (another widget) syncs the local selection.\n\t\t *\n\t\t * @return {unknown}\n\t\t */\n\t\tworkspaceValue() {\n\t\t\treturn this.readWorkspace()\n\t\t},\n\n\t\tloadingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Loading…')\n\t\t},\n\t},\n\n\twatch: {\n\t\t/** Re-fetch options when a dynamic source changes. */\n\t\tcontent: {\n\t\t\tdeep: true,\n\t\t\thandler() {\n\t\t\t\tthis.loadOptions()\n\t\t\t},\n\t\t},\n\n\t\t/**\n\t\t * Track external workspace changes (another widget writing the same key).\n\t\t *\n\t\t * @param {unknown} val The new workspace value for this key.\n\t\t */\n\t\tworkspaceValue(val) {\n\t\t\tif (val !== undefined) {\n\t\t\t\tthis.selected = val\n\t\t\t}\n\t\t},\n\t},\n\n\tcreated() {\n\t\t// Seed the selection: existing workspace value → content.default → the\n\t\t// \"All\" clear option (empty) when present, else the first option once\n\t\t// loaded (handled in loadOptions).\n\t\tconst fromAddress = this.readAddress()\n\t\tconst existing = this.readWorkspace()\n\t\tif (fromAddress !== undefined) {\n\t\t\tthis.selected = fromAddress\n\t\t\tthis.writeWorkspace(fromAddress)\n\t\t} else if (existing !== undefined) {\n\t\t\tthis.selected = existing\n\t\t} else if (this.content.default !== undefined) {\n\t\t\tthis.selected = this.content.default\n\t\t\tthis.writeWorkspace(this.content.default)\n\t\t} else if (this.style === 'toggle') {\n\t\t\t// A switch starts off, and says so, so a condition on its key holds.\n\t\t\tthis.selected = this.offValue\n\t\t\tthis.writeWorkspace(this.offValue)\n\t\t}\n\t\tthis.loadOptions()\n\t},\n\n\tmounted() {\n\t\tthis.onPageRefresh = () => this.loadCounts()\n\t\tsubscribe('cn:page:refresh', this.onPageRefresh)\n\t\tthis.loadCounts()\n\t},\n\n\tbeforeUnmount() {\n\t\tunsubscribe('cn:page:refresh', this.onPageRefresh)\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The choice named in the address (`?<urlKey>=…`), when it is one of\n\t\t * the options; undefined otherwise.\n\t\t *\n\t\t * @return {string|undefined}\n\t\t * @spec openspec/changes/screens-page-lenses/specs/page-lenses/spec.md#requirement-the-choice-is-in-the-address\n\t\t */\n\t\treadAddress() {\n\t\t\tif (!this.urlKey || !this.$route || !this.$route.query) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tconst raw = this.$route.query[this.urlKey]\n\t\t\tif (this.style === 'toggle') {\n\t\t\t\tif (raw === undefined) {\n\t\t\t\t\treturn undefined\n\t\t\t\t}\n\t\t\t\treturn [this.onValue, this.offValue].find((value) => String(value) === String(raw))\n\t\t\t}\n\t\t\tconst known = this.displayOptions.some((option) => String(option.value) === String(raw))\n\t\t\treturn known ? String(raw) : undefined\n\t\t},\n\n\t\t/**\n\t\t * Write the choice to the address, replacing the history entry and\n\t\t * keeping the other query keys.\n\t\t *\n\t\t * @param {unknown} value The chosen value.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/screens-page-lenses/specs/page-lenses/spec.md#requirement-the-choice-is-in-the-address\n\t\t */\n\t\twriteAddress(value) {\n\t\t\tif (!this.urlKey || !this.$router || !this.$route) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst query = this.$route.query || {}\n\t\t\tif (String(query[this.urlKey]) === String(value)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst result = this.$router.replace({ query: { ...query, [this.urlKey]: String(value) } })\n\t\t\tif (result && typeof result.catch === 'function') {\n\t\t\t\tresult.catch(() => {})\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Count the lists behind the options that ask (`countFrom`: a list\n\t\t * `{ register, schema, filter? }`, or several that are added up). One\n\t\t * list request each; an option whose count failed shows none.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/screens-page-lenses/specs/page-lenses/spec.md#requirement-a-workspace-filter-can-draw-chips-with-counts\n\t\t */\n\t\tasync loadCounts() {\n\t\t\tconst options = this.displayOptions\n\t\t\tconst seq = (this.countSeq = (this.countSeq || 0) + 1)\n\t\t\tconst ctx = { workspace: this.readWorkspaceBag() }\n\t\t\tawait Promise.all(options.map(async (option, index) => {\n\t\t\t\tconst raw = (Array.isArray(this.content.options) ? this.content.options : [])\n\t\t\t\t\t.find((candidate) => candidate && typeof candidate === 'object' && String(candidate.value ?? candidate.id) === String(option.value))\n\t\t\t\tconst from = raw ? raw.countFrom : null\n\t\t\t\tconst lists = (Array.isArray(from) ? from : [from]).filter((list) => list && typeof list === 'object' && list.register && list.schema)\n\t\t\t\tif (lists.length === 0) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tlet total\n\t\t\t\ttry {\n\t\t\t\t\tconst parts = await Promise.all(lists.map((list) => fetchListTotal(list.register, list.schema, list.filter || {}, ctx)))\n\t\t\t\t\tif (parts.some((part) => part === null)) {\n\t\t\t\t\t\tthrow new Error('no total')\n\t\t\t\t\t}\n\t\t\t\t\ttotal = parts.reduce((sum, part) => sum + part, 0)\n\t\t\t\t} catch {\n\t\t\t\t\ttotal = null\n\t\t\t\t}\n\t\t\t\tif (seq === this.countSeq) {\n\t\t\t\t\tthis.optionCounts = { ...this.optionCounts, [index]: total }\n\t\t\t\t}\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Normalise a raw option into `{ value, label, count? }`. Accepts\n\t\t * `{ value, label, count }`, `{ id, name }`, `{ key, count }` (OR\n\t\t * facet), or a bare string/number.\n\t\t *\n\t\t * @param {unknown} o The raw option.\n\t\t * @return {{value: unknown, label: string, count?: number}}\n\t\t */\n\t\tnormaliseOption(o) {\n\t\t\tif (o === null || o === undefined) {\n\t\t\t\treturn { value: undefined, label: '' }\n\t\t\t}\n\t\t\tif (typeof o !== 'object') {\n\t\t\t\treturn { value: o, label: String(o) }\n\t\t\t}\n\t\t\tconst value = o.value !== undefined ? o.value : (o.id !== undefined ? o.id : o.key)\n\t\t\tconst label = o.label !== undefined ? o.label : (o.name !== undefined ? o.name : String(value))\n\t\t\tconst out = { value, label: String(label) }\n\t\t\tif (o.count !== undefined) {\n\t\t\t\tout.count = o.count\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Load options for the active source (static content.options need no\n\t\t * fetch). Seeds the first option as the selection when nothing is set\n\t\t * and no \"All\" clear option exists.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadOptions() {\n\t\t\tif (this.objectSource) {\n\t\t\t\tawait this.seedFirstObject()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.content.endpointSource && this.content.endpointSource.url) {\n\t\t\t\tawait this.loadFromEndpoint()\n\t\t\t} else if (this.content.source && this.content.source.schema) {\n\t\t\t\tawait this.loadFromOrSource()\n\t\t\t} else {\n\t\t\t\tthis.fetchedOptions = null\n\t\t\t}\n\t\t\t// Default selection: when nothing is selected and there's no \"All\"\n\t\t\t// clear affordance, adopt the first option so siblings have a value.\n\t\t\tif ((this.selected === undefined || this.selected === '') && !this.content.allLabel) {\n\t\t\t\tconst first = this.normalisedOptions[0]\n\t\t\t\tif (first && this.readWorkspace() === undefined && this.content.default === undefined) {\n\t\t\t\t\tthis.selected = first.value\n\t\t\t\t\tthis.writeWorkspace(first.value)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Translate a manifest string through the host app.\n\t\t *\n\t\t * @param {string} text The text.\n\t\t * @return {string}\n\t\t */\n\t\ttr(text) {\n\t\t\tif (typeof text !== 'string' || text === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\t/**\n\t\t * The object picker's default: with nothing selected, no `default` and\n\t\t * no `allLabel`, select the first object the source lists, so the page\n\t\t * always has one in focus. Fail-safe: a failed read selects nothing.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync seedFirstObject() {\n\t\t\tif ((this.selected !== undefined && this.selected !== '') || this.content.allLabel || this.content.default !== undefined) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst src = this.objectSource\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/objects/{register}/{schema}', { register: src.register, schema: src.schema })\n\t\t\t\tconst res = await axios.get(url, { params: { _limit: 1 } })\n\t\t\t\tconst data = res && res.data\n\t\t\t\tconst rows = Array.isArray(data) ? data : ((data && data.results) || [])\n\t\t\t\tconst first = rows[0]\n\t\t\t\tconst id = first && (first.id || (first['@self'] && first['@self'].id))\n\t\t\t\tif (id && (this.selected === undefined || this.selected === '') && this.readWorkspace() === undefined) {\n\t\t\t\t\tthis.selected = String(id)\n\t\t\t\t\tthis.writeWorkspace(String(id))\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Nothing selected: the siblings wait for a pick.\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Fetch options from an `endpointSource` (array of options at\n\t\t * responsePath). Fail-safe: a failed fetch leaves an empty list.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadFromEndpoint() {\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst payload = await fetchEndpointSource(this.content.endpointSource, {\n\t\t\t\t\tworkspace: this.readWorkspaceBag(),\n\t\t\t\t})\n\t\t\t\tthis.fetchedOptions = Array.isArray(payload) ? payload : []\n\t\t\t} catch {\n\t\t\t\tthis.fetchedOptions = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Fetch options from an OpenRegister `/grouped` facet — each distinct\n\t\t * `groupBy` value becomes an option with its object count. Fail-safe.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadFromOrSource() {\n\t\t\tconst src = this.content.source\n\t\t\tthis.loading = true\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(\n\t\t\t\t\t'/apps/openregister/api/objects/aggregations/{register}/{schema}/grouped',\n\t\t\t\t\t{ register: src.register, schema: src.schema },\n\t\t\t\t)\n\t\t\t\tconst params = { groupBy: src.groupBy, metric: 'count' }\n\t\t\t\tconst filter = resolveFilterTokens(src.filter || {}, { workspace: this.readWorkspaceBag() })\n\t\t\t\tfor (const [k, v] of Object.entries(filter)) {\n\t\t\t\t\tif (v !== '' && v !== null && v !== undefined) {\n\t\t\t\t\t\tparams[`filter[${k}]`] = v\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst res = await axios.get(url, { params })\n\t\t\t\tconst groups = (res && res.data && res.data.groups) || []\n\t\t\t\tthis.fetchedOptions = groups\n\t\t\t\t\t.filter((g) => g.key !== null && g.key !== undefined && g.key !== '')\n\t\t\t\t\t.map((g) => ({ value: g.key, label: String(g.key), count: Number(g.value) || 0 }))\n\t\t\t} catch {\n\t\t\t\tthis.fetchedOptions = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Whether an option is the active selection.\n\t\t *\n\t\t * @param {object} option The option.\n\t\t * @return {boolean}\n\t\t */\n\t\tisActive(option) {\n\t\t\treturn String(option.value) === String(this.selected ?? '')\n\t\t},\n\n\t\t/**\n\t\t * Radio-list pick handler.\n\t\t *\n\t\t * @param {object} option The picked option.\n\t\t * @return {void}\n\t\t */\n\t\tonRadioPick(option) {\n\t\t\tthis.applySelection(option.value)\n\t\t},\n\n\t\t/**\n\t\t * Switch handler: write `onValue` or `offValue`.\n\t\t *\n\t\t * @param {boolean} on Whether the switch is on.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/screens-page-controls/specs/page-controls/spec.md#requirement-a-toggle-and-a-select-are-page-controls\n\t\t */\n\t\tonToggle(on) {\n\t\t\tthis.applySelection(on ? this.onValue : this.offValue)\n\t\t},\n\n\t\t/**\n\t\t * NcSelect input handler.\n\t\t *\n\t\t * @param {object|null} option The picked option object.\n\t\t * @return {void}\n\t\t */\n\t\tonSelect(option) {\n\t\t\tthis.applySelection(option ? option.value : '')\n\t\t},\n\n\t\t/**\n\t\t * Apply a new selection: mirror it locally, write it into the workspace\n\t\t * context (so sibling widgets refetch), and emit `@change`.\n\t\t *\n\t\t * @param {unknown} value The new value.\n\t\t * @return {void}\n\t\t */\n\t\tapplySelection(value) {\n\t\t\tthis.selected = value\n\t\t\tthis.writeWorkspace(value)\n\t\t\tthis.writeAddress(value)\n\t\t\t/**\n\t\t\t * @event change Emitted when the selection changes. Payload: `{ key, value }`.\n\t\t\t */\n\t\t\tthis.$emit('change', { key: this.writeKey, value })\n\t\t},\n\n\t\t/**\n\t\t * The unwrapped workspace bag (or `{}`).\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\treadWorkspaceBag() {\n\t\t\tconst holder = this.cnWorkspaceContext\n\t\t\tif (!holder || typeof holder !== 'object') {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\treturn ('value' in holder) ? (holder.value || {}) : holder\n\t\t},\n\n\t\t/**\n\t\t * Read the current workspace value for this widget's key (undefined\n\t\t * when unset).\n\t\t *\n\t\t * @return {unknown}\n\t\t */\n\t\treadWorkspace() {\n\t\t\tif (!this.writeKey) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tconst bag = this.readWorkspaceBag()\n\t\t\treturn bag[this.writeKey]\n\t\t},\n\n\t\t/**\n\t\t * Write a value into the reactive workspace bag under this widget's key\n\t\t * (the CnInteractionFormWidget write pattern — replace-in-place for the\n\t\t * unwrapped bag, `.value` for the raw-ref shape).\n\t\t *\n\t\t * @param {unknown} value The value to write.\n\t\t * @return {void}\n\t\t */\n\t\twriteWorkspace(value) {\n\t\t\tif (!this.writeKey) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst holder = this.cnWorkspaceContext\n\t\t\tif (!holder || typeof holder !== 'object') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif ('value' in holder) {\n\t\t\t\tholder.value = { ...(holder.value || {}), [this.writeKey]: value }\n\t\t\t\treturn\n\t\t\t}\n\t\t\tholder[this.writeKey] = value\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-workspace-filter-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\twidth: 100%;\n}\n\n.cn-workspace-filter-widget__label {\n\tmargin: 0;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-workspace-filter-widget__status {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-workspace-filter-widget__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n/* The chips: the compact segmented group, left, in the card the grid draws. */\n.cn-workspace-filter-widget--chips {\n\tflex-direction: row;\n\tflex-wrap: wrap;\n\talign-items: center;\n}\n\n/* A bar: its controls in one row, an `align: \"end\"` control pushed right. */\n.cn-workspace-filter-widget--bar {\n\tflex-direction: row;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 12px 16px;\n}\n\n/* An inline control sits in a bar: label left of the control, width to its content. */\n.cn-workspace-filter-widget--inline {\n\tflex-direction: row;\n\talign-items: center;\n\tgap: 8px;\n\twidth: auto;\n}\n\n.cn-workspace-filter-widget--inline .cn-workspace-filter-widget__label {\n\tfont-weight: 400;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-workspace-filter-widget--end {\n\tmargin-inline-start: auto;\n}\n\n.cn-workspace-filter-widget--end ~ .cn-workspace-filter-widget--end {\n\tmargin-inline-start: 0;\n}\n\n.cn-workspace-filter-widget__toggle {\n\tfont-weight: 600;\n}\n\n/* The start card: text left, control (and link) right, as PqDashboard's \"Contact starten\". */\n.cn-workspace-filter-widget--start {\n\tflex-direction: row;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 16px 24px;\n}\n\n.cn-workspace-filter-widget__text {\n\tflex: 1 1 320px;\n\tmin-width: 0;\n}\n\n.cn-workspace-filter-widget__title {\n\tmargin: 0 0 4px;\n\tfont-size: 17px;\n\tfont-weight: 700;\n\tcolor: var(--color-main-text);\n}\n\n.cn-workspace-filter-widget__description {\n\tmargin: 0;\n\tmax-width: 420px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-workspace-filter-widget--start .cn-workspace-filter-widget__control,\n.cn-workspace-filter-widget--start > .v-select {\n\tflex: 0 1 480px;\n\tmin-width: 240px;\n}\n\n.cn-workspace-filter-widget__link {\n\tfont-weight: 600;\n\tcolor: var(--color-primary-element);\n\ttext-decoration: underline;\n\twhite-space: nowrap;\n}\n\n.cn-workspace-filter-widget__count {\n\tmargin-inline-start: 6px;\n\tpadding: 0 6px;\n\tborder-radius: 999px;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n}\n\n/* On a phone the bar's controls take a row each (the chips already wrap), and\n a switch and a select are 44px touch targets. */\n@media (max-width: 640px) {\n\t.cn-workspace-filter-widget--bar > .cn-workspace-filter-widget--inline {\n\t\tflex: 1 1 100%;\n\t}\n\n\t.cn-workspace-filter-widget--bar > .cn-workspace-filter-widget--end {\n\t\tmargin-inline-start: 0;\n\t}\n\n\t.cn-workspace-filter-widget--inline .cn-workspace-filter-widget__toggle {\n\t\tmin-height: 44px;\n\t}\n\n\t.cn-workspace-filter-widget--inline :deep(.v-select) {\n\t\tflex: 1 1 auto;\n\t\tmin-width: 0;\n\t}\n\n\t.cn-workspace-filter-widget--inline :deep(.vs__dropdown-toggle) {\n\t\tmin-height: 44px;\n\t}\n}\n</style>\n"],"names":["_createElementBlock","_openBlock","_toDisplayString","_Fragment","_createBlock","_createTextVNode","_renderList","_createElementVNode"],"mappings":"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"}
package/dist/esm/index.js CHANGED
@@ -77,7 +77,9 @@ export { default as CnChartWidget } from './components/CnChartWidget/CnChartWidg
77
77
  export { default as CnChartWidgetForm } from './components/CnChartWidgetForm/CnChartWidgetForm.vue.js';
78
78
  export { default as CnChatPage } from './components/CnChatPage/CnChatPage.vue.js';
79
79
  export { default as CnChildRecordsField } from './components/CnChildRecordsField/CnChildRecordsField.vue.js';
80
+ export { default as CnChipsFieldWidget } from './components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js';
80
81
  export { default as CnChoiceCards } from './components/CnChoiceCards/CnChoiceCards.vue.js';
82
+ export { default as CnChoicePickerWidget } from './components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js';
81
83
  export { default as CnColorPicker } from './components/CnColorPicker/CnColorPicker.vue.js';
82
84
  export { default as CnCommandPalette } from './components/CnCommandPalette/CnCommandPalette.vue.js';
83
85
  export { default as CnConfigurationCard } from './components/CnConfigurationCard/CnConfigurationCard.vue.js';
@@ -262,6 +264,7 @@ export { default as CnPropertyValueCell } from './components/CnAdvancedFormDialo
262
264
  export { default as CnQuickFilterBar } from './components/CnQuickFilterBar/CnQuickFilterBar.vue.js';
263
265
  export { default as CnQuicklinksWidget } from './components/CnQuicklinksWidget/CnQuicklinksWidget.vue.js';
264
266
  export { default as CnQuicklinksWidgetForm } from './components/CnQuicklinksWidgetForm/CnQuicklinksWidgetForm.vue.js';
267
+ export { default as CnRadioCardsWidget } from './components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js';
265
268
  export { default as CnRecordListWidget } from './components/CnRecordListWidget/CnRecordListWidget.vue.js';
266
269
  export { default as CnReferencePreview } from './components/CnReferencePreview/CnReferencePreview.vue.js';
267
270
  export { default as CnRegisterMapping } from './components/CnRegisterMapping/CnRegisterMapping.vue.js';
@@ -280,6 +283,7 @@ export { default as CnSavedViewPresentationDialog } from './components/CnSavedVi
280
283
  export { default as CnSavedViewsControl } from './components/CnSavedViewsControl/CnSavedViewsControl.vue.js';
281
284
  export { default as CnSavedViewShareDialog } from './components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue.js';
282
285
  export { default as CnSavedViewShareFields } from './components/CnSavedViewShareFields/CnSavedViewShareFields.vue.js';
286
+ export { default as CnSavedViewsWidget } from './components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js';
283
287
  export { default as CnSavedViewWidget } from './components/CnSavedViewWidget/CnSavedViewWidget.vue.js';
284
288
  export { default as CnSavedViewWidgetForm } from './components/CnSavedViewWidgetForm/CnSavedViewWidgetForm.vue.js';
285
289
  export { default as CnSaveViewDialog } from './components/CnSaveViewDialog/CnSaveViewDialog.vue.js';
@@ -287,6 +291,7 @@ export { default as CnSchemaFormDialog } from './components/CnSchemaFormDialog/C
287
291
  export { default as CnSearchPage } from './components/CnSearchPage/CnSearchPage.vue.js';
288
292
  export { default as CnSectionsWidget } from './components/CnSectionsWidget/CnSectionsWidget.vue.js';
289
293
  export { default as CnSettingsCard } from './components/CnSettingsCard/CnSettingsCard.vue.js';
294
+ export { default as CnSettingsFormWidget } from './components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js';
290
295
  export { default as CnSettingsPage } from './components/CnSettingsPage/CnSettingsPage.vue.js';
291
296
  export { default as CnSettingsSection } from './components/CnSettingsSection/CnSettingsSection.vue.js';
292
297
  export { default as CnSetupWizard } from './components/CnSetupWizard/CnSetupWizard.vue.js';
@@ -306,6 +311,7 @@ export { default as CnStructuredDocReview } from './components/CnStructuredDocRe
306
311
  export { default as CnSubObjectsField } from './components/CnSubObjectsField/CnSubObjectsField.vue.js';
307
312
  export { default as CnSummaryAggregates } from './components/CnSummaryAggregates/CnSummaryAggregates.vue.js';
308
313
  export { default as CnSupportDialog } from './components/CnSupportDialog/CnSupportDialog.vue.js';
314
+ export { default as CnSwitchRowsWidget } from './components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js';
309
315
  export { default as CnTab } from './components/CnTabs/CnTab.vue.js';
310
316
  export { default as CnTabbedFormDialog } from './components/CnTabbedFormDialog/CnTabbedFormDialog.vue.js';
311
317
  export { default as CnTableWidget } from './components/CnTableWidget/CnTableWidget.vue.js';
@@ -326,6 +332,7 @@ export { default as CnTileWidget } from './components/CnTileWidget/CnTileWidget.
326
332
  export { default as CnTimelineStages } from './components/CnTimelineStages/CnTimelineStages.vue.js';
327
333
  export { default as CnTimelineView } from './components/CnTimelineView/CnTimelineView.vue.js';
328
334
  export { default as CnTimelineWidget } from './components/CnTimelineWidget/CnTimelineWidget.vue.js';
335
+ export { default as CnToggleTableWidget } from './components/CnToggleTableWidget/CnToggleTableWidget.vue.js';
329
336
  export { default as CnTranslatedBadge } from './components/CnTranslatedBadge/CnTranslatedBadge.vue.js';
330
337
  export { default as CnTreeView } from './components/CnTreeView/CnTreeView.vue.js';
331
338
  export { default as CnUnreadMarker } from './components/CnUnreadMarker/CnUnreadMarker.vue.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -53,6 +53,7 @@ var translations = {
53
53
  "Could not update the view \"{name}\"": "Could not update the view \"{name}\"",
54
54
  Unread: "Unread",
55
55
  "Mark as unread": "Mark as unread",
56
+ "Open sidebar": "Open sidebar",
56
57
  "Could not mark this record as unread.": "Could not mark this record as unread.",
57
58
  "Add to favourites": "Add to favourites",
58
59
  "Remove from favourites": "Remove from favourites",
@@ -1 +1 @@
1
- {"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -53,6 +53,7 @@ var translations = {
53
53
  "Could not update the view \"{name}\"": "Weergave \"{name}\" kon niet worden bijgewerkt",
54
54
  Unread: "Ongelezen",
55
55
  "Mark as unread": "Markeren als ongelezen",
56
+ "Open sidebar": "Zijbalk openen",
56
57
  "Could not mark this record as unread.": "Dit dossier kon niet als ongelezen worden gemarkeerd.",
57
58
  "Add to favourites": "Aan favorieten toevoegen",
58
59
  "Remove from favourites": "Uit favorieten verwijderen",
@@ -1 +1 @@
1
- {"version":3,"file":"nl.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"nl.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}