@conduction/nextcloud-vue 2.119.0 → 2.121.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/dist/esm/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue.js +9 -0
  2. package/dist/esm/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue.js.map +1 -0
  3. package/dist/esm/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue2.js +226 -0
  4. package/dist/esm/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue2.js.map +1 -0
  5. package/dist/esm/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue3.js +211 -0
  6. package/dist/esm/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue3.js.map +1 -0
  7. package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue.js +9 -0
  8. package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue.js.map +1 -0
  9. package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue2.js +64 -0
  10. package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue2.js.map +1 -0
  11. package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue3.js +89 -0
  12. package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue3.js.map +1 -0
  13. package/dist/esm/components/CnIcon/semanticIcons.js +2 -2
  14. package/dist/esm/components/CnIcon/semanticIcons.js.map +1 -1
  15. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +53 -1
  16. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  17. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +546 -523
  18. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  19. package/dist/esm/components/CnIndexPage/useNamedSource.js +9 -0
  20. package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
  21. package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue.js +9 -0
  22. package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue.js.map +1 -0
  23. package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue2.js +257 -0
  24. package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue2.js.map +1 -0
  25. package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue3.js +266 -0
  26. package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue3.js.map +1 -0
  27. package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue.js +1 -1
  28. package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue2.js +14 -7
  29. package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue2.js.map +1 -1
  30. package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue3.js.map +1 -1
  31. package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue.js +9 -0
  32. package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue.js.map +1 -0
  33. package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue2.js +278 -0
  34. package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue2.js.map +1 -0
  35. package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue3.js +128 -0
  36. package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue3.js.map +1 -0
  37. package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue.js +9 -0
  38. package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue.js.map +1 -0
  39. package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue2.js +214 -0
  40. package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue2.js.map +1 -0
  41. package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue3.js +125 -0
  42. package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue3.js.map +1 -0
  43. package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue.js +9 -0
  44. package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue.js.map +1 -0
  45. package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue2.js +163 -0
  46. package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue2.js.map +1 -0
  47. package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue3.js +131 -0
  48. package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue3.js.map +1 -0
  49. package/dist/esm/components/CnSchemaRulesWidget/schemaRules.js +127 -0
  50. package/dist/esm/components/CnSchemaRulesWidget/schemaRules.js.map +1 -0
  51. package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue.js +9 -0
  52. package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue.js.map +1 -0
  53. package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue2.js +82 -0
  54. package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue2.js.map +1 -0
  55. package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue3.js +61 -0
  56. package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue3.js.map +1 -0
  57. package/dist/esm/components/CnSearchIndexStatus/searchIndexStatus.js +60 -0
  58. package/dist/esm/components/CnSearchIndexStatus/searchIndexStatus.js.map +1 -0
  59. package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue.js +10 -0
  60. package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue.js.map +1 -0
  61. package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue2.js +226 -0
  62. package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue2.js.map +1 -0
  63. package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue3.js +126 -0
  64. package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue3.js.map +1 -0
  65. package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue.js +10 -0
  66. package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue.js.map +1 -0
  67. package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue2.js +944 -0
  68. package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue2.js.map +1 -0
  69. package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue3.js +276 -0
  70. package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue3.js.map +1 -0
  71. package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue.js +9 -0
  72. package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue.js.map +1 -0
  73. package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue2.js +354 -0
  74. package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue2.js.map +1 -0
  75. package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue3.js +392 -0
  76. package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue3.js.map +1 -0
  77. package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue.js +9 -0
  78. package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue.js.map +1 -0
  79. package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue2.js +180 -0
  80. package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue2.js.map +1 -0
  81. package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue3.js +208 -0
  82. package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue3.js.map +1 -0
  83. package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue.js +9 -0
  84. package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue.js.map +1 -0
  85. package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue2.js +244 -0
  86. package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue2.js.map +1 -0
  87. package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue3.js +76 -0
  88. package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue3.js.map +1 -0
  89. package/dist/esm/components/CnStateMachineEditor/stateMachineFields.js +290 -0
  90. package/dist/esm/components/CnStateMachineEditor/stateMachineFields.js.map +1 -0
  91. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue.js +9 -0
  92. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue.js.map +1 -0
  93. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js +448 -0
  94. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js.map +1 -0
  95. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js +303 -0
  96. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js.map +1 -0
  97. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +1 -1
  98. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +19 -2
  99. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -1
  100. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js +73 -62
  101. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -1
  102. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +6 -0
  103. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  104. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +42 -32
  105. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  106. package/dist/esm/composables/indexSources.js +116 -16
  107. package/dist/esm/composables/indexSources.js.map +1 -1
  108. package/dist/esm/index.js +9 -0
  109. package/dist/esm/index.js.map +1 -1
  110. package/dist/esm/l10n/en.json.js +207 -1
  111. package/dist/esm/l10n/en.json.js.map +1 -1
  112. package/dist/esm/l10n/nl.json.js +207 -1
  113. package/dist/esm/l10n/nl.json.js.map +1 -1
  114. package/dist/esm/library-style.js +1 -1
  115. package/dist/esm/nextcloud-vue.css +951 -16
  116. package/dist/esm/utils/aiFeatures.js +245 -0
  117. package/dist/esm/utils/aiFeatures.js.map +1 -0
  118. package/dist/esm/utils/aiSuggestionTracker.js +182 -0
  119. package/dist/esm/utils/aiSuggestionTracker.js.map +1 -0
  120. package/dist/esm/utils/libraryWidgetKeys.js +2 -0
  121. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  122. package/dist/esm/utils/objectPermissions.js +552 -0
  123. package/dist/esm/utils/objectPermissions.js.map +1 -0
  124. package/dist/esm/utils/objectTranslations.js +180 -0
  125. package/dist/esm/utils/objectTranslations.js.map +1 -0
  126. package/dist/esm/utils/stateMachineValidation.js +254 -0
  127. package/dist/esm/utils/stateMachineValidation.js.map +1 -0
  128. package/dist/esm/utils/widgetDispatch.js +3 -1
  129. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  130. package/dist/nextcloud-vue.cjs.js +23038 -14567
  131. package/dist/nextcloud-vue.cjs.js.map +2 -2
  132. package/dist/nextcloud-vue.css +951 -16
  133. package/l10n/en.json +207 -1
  134. package/l10n/nl.json +207 -1
  135. package/package.json +1 -1
  136. package/src/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue +416 -0
  137. package/src/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue +136 -0
  138. package/src/components/CnAiFeaturesWidget/__tests__/CnAiFeaturesWidget.spec.js +213 -0
  139. package/src/components/CnAiFeaturesWidget/index.js +14 -0
  140. package/src/components/CnIndexPage/CnIndexPage.vue +74 -3
  141. package/src/components/CnIndexPage/useNamedSource.js +9 -0
  142. package/src/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue +499 -0
  143. package/src/components/CnObjectPermissionsTab/__tests__/CnObjectPermissionsTab.spec.js +186 -0
  144. package/src/components/CnObjectPermissionsTab/index.js +13 -0
  145. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +8 -1
  146. package/src/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue +424 -0
  147. package/src/components/CnObjectTranslationsWidget/__tests__/CnObjectTranslationsWidget.spec.js +189 -0
  148. package/src/components/CnObjectTranslationsWidget/index.js +13 -0
  149. package/src/components/CnReadinessChecklist/CnReadinessChecklist.vue +335 -0
  150. package/src/components/CnReadinessChecklist/index.js +9 -0
  151. package/src/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue +300 -0
  152. package/src/components/CnSchemaRulesWidget/index.js +10 -0
  153. package/src/components/CnSchemaRulesWidget/schemaRules.js +123 -0
  154. package/src/components/CnSearchIndexStatus/CnSearchIndexStatus.vue +160 -0
  155. package/src/components/CnSearchIndexStatus/index.js +10 -0
  156. package/src/components/CnSearchIndexStatus/searchIndexStatus.js +56 -0
  157. package/src/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue +328 -0
  158. package/src/components/CnShippedConfigurationSection/index.js +9 -0
  159. package/src/components/CnStateMachineEditor/CnStateMachineEditor.vue +1332 -0
  160. package/src/components/CnStateMachineEditor/CnStateMachineFields.vue +791 -0
  161. package/src/components/CnStateMachineEditor/CnStateMachineNode.vue +442 -0
  162. package/src/components/CnStateMachineEditor/CnStateMachinePanel.vue +326 -0
  163. package/src/components/CnStateMachineEditor/index.js +8 -0
  164. package/src/components/CnStateMachineEditor/stateMachineFields.js +287 -0
  165. package/src/components/CnTimelineWidget/CnTimelineManage.vue +827 -0
  166. package/src/components/CnTimelineWidget/CnTimelineWidget.vue +29 -3
  167. package/src/components/CnWidgetGrid/builtInWidgets.js +6 -0
  168. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +10 -0
  169. package/src/components/index.js +8 -0
  170. package/src/composables/indexSources.js +116 -16
  171. package/src/css/index-page.css +7 -0
  172. package/src/index.js +9 -0
  173. package/src/utils/aiFeatures.js +241 -0
  174. package/src/utils/aiSuggestionTracker.js +178 -0
  175. package/src/utils/libraryWidgetKeys.js +2 -0
  176. package/src/utils/objectPermissions.js +548 -0
  177. package/src/utils/objectTranslations.js +176 -0
  178. package/src/utils/stateMachineValidation.js +250 -0
  179. package/src/utils/widgetDispatch.js +3 -1
@@ -0,0 +1,791 @@
1
+ <!--
2
+ - SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>
3
+ - SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+ <template>
6
+ <div class="cn-sm-fields" :class="{ 'cn-sm-fields--row': row }">
7
+ <template v-for="(field, fieldIndex) in fields" :key="field.key || `${field.type}-${fieldIndex}`">
8
+ <!-- One line of fields side by side. -->
9
+ <CnStateMachineFields
10
+ v-if="field.type === 'row'"
11
+ :fields="field.fields"
12
+ :target="target"
13
+ :path="path"
14
+ :disabled="isDisabled(field)"
15
+ row />
16
+
17
+ <!-- A group: collapsible, switchable, or a nested object. -->
18
+ <div v-else-if="field.type === 'group'" class="cn-sm-group" :class="{ 'cn-sm-group--section': field.section }">
19
+ <button
20
+ v-if="field.collapsible"
21
+ type="button"
22
+ class="cn-sm-group__toggle"
23
+ :aria-expanded="isOpen(field) ? 'true' : 'false'"
24
+ :aria-controls="idFor(field, 'body')"
25
+ @click="toggleOpen(field)">
26
+ <span>{{ panel.tr(field.label) }}</span>
27
+ <ChevronDown v-if="isOpen(field)" :size="18" />
28
+ <ChevronRight v-else :size="18" />
29
+ </button>
30
+ <h4 v-else-if="field.label && !field.optional" class="cn-sm-group__heading">
31
+ {{ panel.tr(field.label) }}
32
+ </h4>
33
+
34
+ <p v-if="field.lockable && panel.locked && field.lockedNotice" class="cn-sm-group__notice" role="note">
35
+ {{ panel.tr(field.lockedNotice) }}
36
+ </p>
37
+
38
+ <div v-if="!field.collapsible || isOpen(field)" :id="idFor(field, 'body')" class="cn-sm-group__body">
39
+ <div v-if="field.optional" class="cn-sm-field cn-sm-field--check">
40
+ <input
41
+ :id="idFor(field, 'enabled')"
42
+ type="checkbox"
43
+ :checked="panel.isEnabled(childPath(field))"
44
+ :disabled="isDisabled(field)"
45
+ @change="panel.setEnabled(childPath(field), $event.target.checked)">
46
+ <label :for="idFor(field, 'enabled')">{{ panel.tr(field.toggleLabel || field.label) }}</label>
47
+ </div>
48
+ <CnStateMachineFields
49
+ v-if="!field.optional || panel.isEnabled(childPath(field))"
50
+ :class="{ 'cn-sm-fields--boxed': field.optional }"
51
+ :fields="field.fields"
52
+ :target="field.key ? target[field.key] : target"
53
+ :path="childPath(field)"
54
+ :disabled="isDisabled(field)" />
55
+ </div>
56
+ </div>
57
+
58
+ <!-- A list of strings or of objects with one edited text. -->
59
+ <fieldset v-else-if="field.type === 'list'" class="cn-sm-list" :class="{ 'cn-sm-list--section': field.section }">
60
+ <legend class="cn-sm-field__label" :class="{ 'cn-sm-list__legend--section': field.section }">
61
+ {{ panel.tr(field.label) }}
62
+ </legend>
63
+ <div
64
+ v-for="(item, index) in target[field.key]"
65
+ :key="itemKey(field, item, index)"
66
+ class="cn-sm-list__item"
67
+ :draggable="field.reorderable && !isDisabled(field) ? 'true' : 'false'"
68
+ @dragstart="onItemDragStart(field, index, $event)"
69
+ @dragover.prevent
70
+ @drop.stop.prevent="onItemDrop(field, index)">
71
+ <span v-if="field.reorderable" class="cn-sm-list__handle" aria-hidden="true">
72
+ <DragVertical :size="16" />
73
+ </span>
74
+ <input
75
+ type="text"
76
+ class="cn-sm-field__input"
77
+ :value="itemText(field, item)"
78
+ :placeholder="field.itemLabel ? panel.tr(field.itemLabel) : ''"
79
+ :aria-label="itemAriaLabel(field, index)"
80
+ :disabled="isDisabled(field)"
81
+ @input="setItemText(field, index, $event.target.value)">
82
+ <template v-if="field.reorderable">
83
+ <NcButton
84
+ variant="tertiary"
85
+ :disabled="isDisabled(field) || index === 0"
86
+ :aria-label="moveLabel(field, index, 'up')"
87
+ @click="move(field, index, index - 1)">
88
+ <template #icon>
89
+ <ArrowUp :size="16" />
90
+ </template>
91
+ </NcButton>
92
+ <NcButton
93
+ variant="tertiary"
94
+ :disabled="isDisabled(field) || index === target[field.key].length - 1"
95
+ :aria-label="moveLabel(field, index, 'down')"
96
+ @click="move(field, index, index + 1)">
97
+ <template #icon>
98
+ <ArrowDown :size="16" />
99
+ </template>
100
+ </NcButton>
101
+ </template>
102
+ <NcButton
103
+ variant="tertiary"
104
+ :disabled="isDisabled(field)"
105
+ :aria-label="removeLabel(field, index)"
106
+ @click="removeItem(field, index)">
107
+ <template #icon>
108
+ <Close :size="16" />
109
+ </template>
110
+ </NcButton>
111
+ </div>
112
+ <NcButton variant="secondary" :disabled="isDisabled(field)" @click="addItem(field)">
113
+ {{ panel.tr(field.addLabel) || t('nextcloud-vue', 'Add') }}
114
+ </NcButton>
115
+ </fieldset>
116
+
117
+ <!-- A list of objects whose fields depend on their type. -->
118
+ <fieldset v-else-if="field.type === 'typed-list'" class="cn-sm-list" :class="{ 'cn-sm-list--section': field.section }">
119
+ <legend class="cn-sm-field__label" :class="{ 'cn-sm-list__legend--section': field.section }">
120
+ {{ panel.tr(field.label) }}
121
+ </legend>
122
+ <div
123
+ v-for="(item, index) in target[field.key]"
124
+ :key="itemKey(field, item, index)"
125
+ class="cn-sm-list__typed">
126
+ <div class="cn-sm-field">
127
+ <label :for="idFor(field, `type-${index}`)" class="cn-sm-field__label">
128
+ {{ field.typeLabel ? panel.tr(field.typeLabel) : t('nextcloud-vue', 'Type') }}
129
+ </label>
130
+ <select
131
+ :id="idFor(field, `type-${index}`)"
132
+ class="cn-sm-field__input"
133
+ :value="item[typeKey(field)]"
134
+ :disabled="isDisabled(field)"
135
+ @change="panel.set(item, typeKey(field), $event.target.value)">
136
+ <option
137
+ v-for="type in typesFor(field, item)"
138
+ :key="type.value"
139
+ :value="type.value">
140
+ {{ panel.tr(type.label) }}
141
+ </option>
142
+ </select>
143
+ </div>
144
+ <CnStateMachineFields
145
+ :fields="fieldsOfType(field, item)"
146
+ :target="item"
147
+ :path="`${childPath(field)}.${index}`"
148
+ :disabled="isDisabled(field)" />
149
+ <NcButton
150
+ variant="tertiary"
151
+ class="cn-sm-list__remove"
152
+ :disabled="isDisabled(field)"
153
+ :aria-label="panel.tr(field.removeLabel) || t('nextcloud-vue', 'Remove')"
154
+ @click="removeItem(field, index)">
155
+ <template #icon>
156
+ <Close :size="16" />
157
+ </template>
158
+ </NcButton>
159
+ </div>
160
+ <NcButton variant="secondary" :disabled="isDisabled(field)" @click="addItem(field)">
161
+ {{ panel.tr(field.addLabel) || t('nextcloud-vue', 'Add') }}
162
+ </NcButton>
163
+ </fieldset>
164
+
165
+ <!-- Several values picked from a set of options. -->
166
+ <fieldset v-else-if="field.type === 'checkboxes'" class="cn-sm-list">
167
+ <legend class="cn-sm-field__label">
168
+ {{ panel.tr(field.label) }}
169
+ </legend>
170
+ <div v-for="(option, index) in panel.optionsOf(field)" :key="String(option.value)" class="cn-sm-field cn-sm-field--check">
171
+ <input
172
+ :id="idFor(field, `option-${index}`)"
173
+ type="checkbox"
174
+ :checked="(target[field.key] || []).includes(option.value)"
175
+ :disabled="isDisabled(field)"
176
+ @change="toggleOption(field, option.value)">
177
+ <label :for="idFor(field, `option-${index}`)">{{ option.label }}</label>
178
+ </div>
179
+ </fieldset>
180
+
181
+ <!-- A single check box. -->
182
+ <div v-else-if="field.type === 'checkbox'" class="cn-sm-field cn-sm-field--check">
183
+ <input
184
+ :id="idFor(field)"
185
+ type="checkbox"
186
+ :checked="!!target[field.key]"
187
+ :disabled="isDisabled(field)"
188
+ @change="panel.set(target, field.key, $event.target.checked)">
189
+ <label :for="idFor(field)">{{ panel.tr(field.label) }}</label>
190
+ </div>
191
+
192
+ <!-- One value from a set of options. -->
193
+ <div v-else-if="field.type === 'select'" class="cn-sm-field" :class="widthClass(field)">
194
+ <label :for="idFor(field)" class="cn-sm-field__label" :class="{ 'cn-sm-visually-hidden': field.hideLabel }">
195
+ {{ panel.tr(field.label) }}
196
+ </label>
197
+ <select
198
+ :id="idFor(field)"
199
+ class="cn-sm-field__input"
200
+ :disabled="isDisabled(field)"
201
+ @change="onSelect(field, $event.target.value)">
202
+ <option
203
+ v-for="(option, index) in selectOptions(field)"
204
+ :key="index"
205
+ :value="String(index)"
206
+ :selected="sameValue(option.value, target[field.key])">
207
+ {{ option.label }}
208
+ </option>
209
+ </select>
210
+ </div>
211
+
212
+ <!-- Free text, a longer text, or a number. -->
213
+ <div v-else class="cn-sm-field" :class="widthClass(field)">
214
+ <label :for="idFor(field)" class="cn-sm-field__label" :class="{ 'cn-sm-visually-hidden': field.hideLabel }">
215
+ {{ panel.tr(field.label) }}
216
+ </label>
217
+ <textarea
218
+ v-if="field.type === 'textarea'"
219
+ :id="idFor(field)"
220
+ class="cn-sm-field__input"
221
+ :rows="field.rows || 3"
222
+ :value="target[field.key] ?? ''"
223
+ :placeholder="field.placeholder ? panel.tr(field.placeholder) : ''"
224
+ :aria-describedby="field.hint ? idFor(field, 'hint') : undefined"
225
+ :disabled="isDisabled(field)"
226
+ @input="panel.set(target, field.key, $event.target.value)" />
227
+ <input
228
+ v-else
229
+ :id="idFor(field)"
230
+ class="cn-sm-field__input"
231
+ :type="field.type === 'number' ? 'number' : (field.inputType || 'text')"
232
+ :min="field.min"
233
+ :max="field.max"
234
+ :value="target[field.key] ?? ''"
235
+ :placeholder="field.placeholder ? panel.tr(field.placeholder) : ''"
236
+ :aria-describedby="field.hint ? idFor(field, 'hint') : undefined"
237
+ :disabled="isDisabled(field)"
238
+ @input="onInput(field, $event.target.value)">
239
+ <p v-if="field.hint" :id="idFor(field, 'hint')" class="cn-sm-field__hint">
240
+ {{ panel.tr(field.hint) }}
241
+ </p>
242
+ </div>
243
+ </template>
244
+ </div>
245
+ </template>
246
+
247
+ <script>
248
+ import { translate as t } from '@nextcloud/l10n'
249
+ import { NcButton } from '@nextcloud/vue'
250
+ import ArrowDown from 'vue-material-design-icons/ArrowDown.vue'
251
+ import ArrowUp from 'vue-material-design-icons/ArrowUp.vue'
252
+ import ChevronDown from 'vue-material-design-icons/ChevronDown.vue'
253
+ import ChevronRight from 'vue-material-design-icons/ChevronRight.vue'
254
+ import Close from 'vue-material-design-icons/Close.vue'
255
+ import DragVertical from 'vue-material-design-icons/DragVertical.vue'
256
+ import { newListItem } from './stateMachineFields.js'
257
+
258
+ /**
259
+ * Renders field descriptors against one object of a property panel's copy.
260
+ *
261
+ * Every write goes through the panel's injected `set`, so the panel owns the
262
+ * copy and hears each change. Recursive for groups, rows and typed lists.
263
+ */
264
+ export default {
265
+ name: 'CnStateMachineFields',
266
+
267
+ components: { NcButton, ArrowDown, ArrowUp, ChevronDown, ChevronRight, Close, DragVertical },
268
+
269
+ inject: ['cnStateMachinePanel'],
270
+
271
+ props: {
272
+ /** The field descriptors to render. */
273
+ fields: {
274
+ type: Array,
275
+ default: () => [],
276
+ },
277
+
278
+ /** The object of the panel's copy these fields edit. */
279
+ target: {
280
+ type: Object,
281
+ required: true,
282
+ },
283
+
284
+ /** Where `target` sits in the copy, for ids and optional-group state. */
285
+ path: {
286
+ type: String,
287
+ default: '',
288
+ },
289
+
290
+ /** Disable every control. */
291
+ disabled: {
292
+ type: Boolean,
293
+ default: false,
294
+ },
295
+
296
+ /** Lay the fields out on one line. */
297
+ row: {
298
+ type: Boolean,
299
+ default: false,
300
+ },
301
+ },
302
+
303
+ data() {
304
+ return {
305
+ open: {},
306
+ dragIndex: null,
307
+ dragKey: null,
308
+ }
309
+ },
310
+
311
+ computed: {
312
+ /**
313
+ * @return {object} The owning panel's API.
314
+ */
315
+ panel() {
316
+ return this.cnStateMachinePanel
317
+ },
318
+ },
319
+
320
+ methods: {
321
+ t,
322
+
323
+ /**
324
+ * @param {object} field A descriptor.
325
+ * @return {boolean} Whether its controls are disabled.
326
+ */
327
+ isDisabled(field) {
328
+ return this.disabled || (field.lockable === true && this.panel.locked === true)
329
+ },
330
+
331
+ /**
332
+ * @param {object} field A descriptor.
333
+ * @return {string} The path of the object a group or list edits.
334
+ */
335
+ childPath(field) {
336
+ if (!field.key) {
337
+ return this.path
338
+ }
339
+ return this.path ? `${this.path}.${field.key}` : field.key
340
+ },
341
+
342
+ /**
343
+ * @param {object} field A descriptor.
344
+ * @param {string} [suffix] A part name.
345
+ * @return {string} A DOM id unique within the editor.
346
+ */
347
+ idFor(field, suffix = '') {
348
+ const raw = [this.panel.uid, this.childPath(field) || field.type, suffix].filter(Boolean).join('-')
349
+ return raw.replace(/[^A-Za-z0-9_-]/g, '-')
350
+ },
351
+
352
+ /**
353
+ * @param {object} field A descriptor.
354
+ * @return {string|null} The width modifier class.
355
+ */
356
+ widthClass(field) {
357
+ return field.width === 'narrow' ? 'cn-sm-field--narrow' : null
358
+ },
359
+
360
+ /**
361
+ * @param {object} field A collapsible group.
362
+ * @return {boolean} Whether it is open.
363
+ */
364
+ isOpen(field) {
365
+ const key = this.childPath(field) || field.label
366
+ return this.open[key] ?? field.open === true
367
+ },
368
+
369
+ /**
370
+ * @param {object} field A collapsible group.
371
+ * @return {void}
372
+ */
373
+ toggleOpen(field) {
374
+ const key = this.childPath(field) || field.label
375
+ this.open = { ...this.open, [key]: !this.isOpen(field) }
376
+ },
377
+
378
+ /**
379
+ * @param {object} field A text or number descriptor.
380
+ * @param {string} raw The input's value.
381
+ * @return {void}
382
+ */
383
+ onInput(field, raw) {
384
+ if (field.type === 'number') {
385
+ this.panel.set(this.target, field.key, raw === '' ? null : Number(raw))
386
+ return
387
+ }
388
+ this.panel.set(this.target, field.key, raw)
389
+ },
390
+
391
+ /**
392
+ * @param {object} field A select descriptor.
393
+ * @return {Array<object>} Its options, the empty option first.
394
+ */
395
+ selectOptions(field) {
396
+ const options = this.panel.optionsOf(field)
397
+ if (field.emptyOption) {
398
+ return [{ value: field.emptyOption.value ?? null, label: this.panel.tr(field.emptyOption.label || '') }, ...options]
399
+ }
400
+ return options
401
+ },
402
+
403
+ /**
404
+ * @param {unknown} a One value.
405
+ * @param {unknown} b Another.
406
+ * @return {boolean} Whether they are the same option, treating null and undefined alike.
407
+ */
408
+ sameValue(a, b) {
409
+ return (a ?? null) === (b ?? null)
410
+ },
411
+
412
+ /**
413
+ * @param {object} field A select descriptor.
414
+ * @param {string} index The chosen option's index.
415
+ * @return {void}
416
+ */
417
+ onSelect(field, index) {
418
+ const option = this.selectOptions(field)[Number(index)]
419
+ this.panel.set(this.target, field.key, option ? option.value : null)
420
+ },
421
+
422
+ /**
423
+ * @param {object} field A checkboxes descriptor.
424
+ * @param {unknown} value The option toggled.
425
+ * @return {void}
426
+ */
427
+ toggleOption(field, value) {
428
+ const current = [...(this.target[field.key] || [])]
429
+ const at = current.indexOf(value)
430
+ if (at >= 0) {
431
+ current.splice(at, 1)
432
+ } else {
433
+ current.push(value)
434
+ }
435
+ this.panel.set(this.target, field.key, current)
436
+ },
437
+
438
+ /**
439
+ * @param {object} field A list descriptor.
440
+ * @param {unknown} item An entry.
441
+ * @param {number} index Its index.
442
+ * @return {string} A stable key for the entry.
443
+ */
444
+ itemKey(field, item, index) {
445
+ if (field.idKey && item && typeof item === 'object' && item[field.idKey] !== undefined) {
446
+ return String(item[field.idKey])
447
+ }
448
+ return `${field.key}-${index}`
449
+ },
450
+
451
+ /**
452
+ * @param {object} field A list descriptor.
453
+ * @param {unknown} item An entry.
454
+ * @return {string} The text the entry's input shows.
455
+ */
456
+ itemText(field, item) {
457
+ if (field.itemType === 'object') {
458
+ return (item && item[field.itemKey || 'label']) ?? ''
459
+ }
460
+ return item ?? ''
461
+ },
462
+
463
+ /**
464
+ * @param {object} field A list descriptor.
465
+ * @param {number} index The entry.
466
+ * @param {string} value The new text.
467
+ * @return {void}
468
+ */
469
+ setItemText(field, index, value) {
470
+ const list = this.target[field.key]
471
+ if (field.itemType === 'object') {
472
+ this.panel.set(list[index], field.itemKey || 'label', value)
473
+ return
474
+ }
475
+ this.panel.set(list, index, value)
476
+ },
477
+
478
+ /**
479
+ * @param {object} field A list descriptor.
480
+ * @param {number} index The entry.
481
+ * @return {string} The entry input's accessible name.
482
+ */
483
+ itemAriaLabel(field, index) {
484
+ const label = this.panel.tr(field.itemLabel || field.label)
485
+ return this.target[field.key].length > 1 ? `${label} ${index + 1}` : label
486
+ },
487
+
488
+ /**
489
+ * @param {object} field A list descriptor.
490
+ * @param {number} index The entry.
491
+ * @return {string} The remove button's accessible name.
492
+ */
493
+ removeLabel(field, index) {
494
+ const label = this.panel.tr(field.removeLabel) || t('nextcloud-vue', 'Remove')
495
+ return this.target[field.key].length > 1 ? `${label} ${index + 1}` : label
496
+ },
497
+
498
+ /**
499
+ * @param {object} field A list descriptor.
500
+ * @param {number} index The entry.
501
+ * @param {string} direction `up` or `down`.
502
+ * @return {string} The move button's accessible name.
503
+ */
504
+ moveLabel(field, index, direction) {
505
+ const label = direction === 'up' ? t('nextcloud-vue', 'Move up') : t('nextcloud-vue', 'Move down')
506
+ return `${label}: ${this.itemText(field, this.target[field.key][index]) || index + 1}`
507
+ },
508
+
509
+ /**
510
+ * @param {object} field A list descriptor.
511
+ * @return {void}
512
+ */
513
+ addItem(field) {
514
+ this.panel.push(this.target[field.key], newListItem(field))
515
+ },
516
+
517
+ /**
518
+ * @param {object} field A list descriptor.
519
+ * @param {number} index The entry.
520
+ * @return {void}
521
+ */
522
+ removeItem(field, index) {
523
+ this.panel.splice(this.target[field.key], index)
524
+ },
525
+
526
+ /**
527
+ * @param {object} field A list descriptor.
528
+ * @param {number} from The entry's index.
529
+ * @param {number} to Its new index.
530
+ * @return {void}
531
+ */
532
+ move(field, from, to) {
533
+ this.panel.move(this.target[field.key], from, to)
534
+ },
535
+
536
+ /**
537
+ * @param {object} field A list descriptor.
538
+ * @param {number} index The dragged entry.
539
+ * @param {DragEvent} event The drag event.
540
+ * @return {void}
541
+ */
542
+ onItemDragStart(field, index, event) {
543
+ if (!field.reorderable || this.isDisabled(field)) {
544
+ return
545
+ }
546
+ this.dragIndex = index
547
+ this.dragKey = field.key
548
+ if (event.dataTransfer) {
549
+ event.dataTransfer.effectAllowed = 'move'
550
+ event.dataTransfer.setData('text/plain', String(index))
551
+ }
552
+ },
553
+
554
+ /**
555
+ * @param {object} field A list descriptor.
556
+ * @param {number} index The entry dropped on.
557
+ * @return {void}
558
+ */
559
+ onItemDrop(field, index) {
560
+ if (this.dragKey === field.key && this.dragIndex !== null) {
561
+ this.move(field, this.dragIndex, index)
562
+ }
563
+ this.dragIndex = null
564
+ this.dragKey = null
565
+ },
566
+
567
+ /**
568
+ * @param {object} field A typed-list descriptor.
569
+ * @return {string} The key holding an entry's type.
570
+ */
571
+ typeKey(field) {
572
+ return field.typeKey || 'type'
573
+ },
574
+
575
+ /**
576
+ * The types an entry can be set to: the offered ones, plus its own when
577
+ * that is one no longer offered, so a stored entry still shows what it is.
578
+ *
579
+ * @param {object} field A typed-list descriptor.
580
+ * @param {object} item The entry.
581
+ * @return {Array<object>} The types.
582
+ */
583
+ typesFor(field, item) {
584
+ const own = item[this.typeKey(field)]
585
+ return (field.types || []).filter((type) => !type.hidden || type.value === own)
586
+ },
587
+
588
+ /**
589
+ * @param {object} field A typed-list descriptor.
590
+ * @param {object} item The entry.
591
+ * @return {Array<object>} The fields of the entry's type.
592
+ */
593
+ fieldsOfType(field, item) {
594
+ const type = (field.types || []).find((candidate) => candidate.value === item[this.typeKey(field)])
595
+ return type && Array.isArray(type.fields) ? type.fields : []
596
+ },
597
+ },
598
+ }
599
+ </script>
600
+
601
+ <style scoped>
602
+ .cn-sm-fields {
603
+ display: flex;
604
+ flex-direction: column;
605
+ gap: 12px;
606
+ min-width: 0;
607
+ }
608
+
609
+ .cn-sm-fields--row {
610
+ flex-direction: row;
611
+ align-items: flex-end;
612
+ gap: 8px;
613
+ }
614
+
615
+ .cn-sm-fields--row > * {
616
+ flex: 1 1 0;
617
+ min-width: 0;
618
+ }
619
+
620
+ .cn-sm-fields--row > .cn-sm-field--narrow {
621
+ flex: 0 0 88px;
622
+ }
623
+
624
+ .cn-sm-fields--boxed {
625
+ padding: 8px;
626
+ border-radius: var(--border-radius, 8px);
627
+ background: var(--color-background-dark, #ededed);
628
+ }
629
+
630
+ .cn-sm-field {
631
+ display: flex;
632
+ flex-direction: column;
633
+ gap: 4px;
634
+ min-width: 0;
635
+ }
636
+
637
+ .cn-sm-field--check {
638
+ flex-direction: row;
639
+ align-items: center;
640
+ gap: 8px;
641
+ }
642
+
643
+ .cn-sm-field__label,
644
+ .cn-sm-field > label {
645
+ font-size: 14px;
646
+ font-weight: 600;
647
+ color: var(--color-main-text, #222);
648
+ }
649
+
650
+ .cn-sm-field--check > label {
651
+ font-weight: 400;
652
+ }
653
+
654
+ .cn-sm-field__input {
655
+ width: 100%;
656
+ min-height: 36px;
657
+ box-sizing: border-box;
658
+ padding: 6px 8px;
659
+ border: 1px solid var(--color-border-maxcontrast, #7f7f7f);
660
+ border-radius: var(--border-radius-element, 8px);
661
+ background: var(--color-main-background, #fff);
662
+ color: var(--color-main-text, #222);
663
+ font: inherit;
664
+ }
665
+
666
+ textarea.cn-sm-field__input {
667
+ resize: vertical;
668
+ }
669
+
670
+ .cn-sm-field__input:focus-visible {
671
+ outline: 2px solid var(--color-primary-element, #0082c9);
672
+ outline-offset: 1px;
673
+ }
674
+
675
+ .cn-sm-field__hint {
676
+ margin: 0;
677
+ font-size: 13px;
678
+ color: var(--color-text-maxcontrast, #6b6b6b);
679
+ }
680
+
681
+ .cn-sm-list {
682
+ display: flex;
683
+ flex-direction: column;
684
+ gap: 6px;
685
+ min-width: 0;
686
+ margin: 0;
687
+ padding: 0;
688
+ border: 0;
689
+ }
690
+
691
+ .cn-sm-list--section,
692
+ .cn-sm-group--section {
693
+ padding-top: 12px;
694
+ border-top: 1px solid var(--color-border, #d0d0d0);
695
+ }
696
+
697
+ .cn-sm-list legend {
698
+ padding: 0;
699
+ margin-bottom: 6px;
700
+ }
701
+
702
+ .cn-sm-list__legend--section,
703
+ .cn-sm-group__heading {
704
+ margin: 0 0 6px;
705
+ font-size: 12px;
706
+ font-weight: 600;
707
+ letter-spacing: 0.04em;
708
+ text-transform: uppercase;
709
+ color: var(--color-text-maxcontrast, #6b6b6b);
710
+ }
711
+
712
+ .cn-sm-list__item {
713
+ display: flex;
714
+ align-items: center;
715
+ gap: 4px;
716
+ }
717
+
718
+ .cn-sm-list__handle {
719
+ display: inline-flex;
720
+ cursor: grab;
721
+ color: var(--color-text-maxcontrast, #6b6b6b);
722
+ }
723
+
724
+ .cn-sm-list__typed {
725
+ position: relative;
726
+ display: flex;
727
+ flex-direction: column;
728
+ gap: 6px;
729
+ padding: 8px 44px 8px 8px;
730
+ border-radius: var(--border-radius, 8px);
731
+ background: var(--color-background-dark, #ededed);
732
+ }
733
+
734
+ .cn-sm-list__remove {
735
+ position: absolute;
736
+ top: 4px;
737
+ inset-inline-end: 4px;
738
+ }
739
+
740
+ .cn-sm-group {
741
+ display: flex;
742
+ flex-direction: column;
743
+ gap: 8px;
744
+ }
745
+
746
+ .cn-sm-group__toggle {
747
+ display: flex;
748
+ align-items: center;
749
+ justify-content: space-between;
750
+ width: 100%;
751
+ min-height: 34px;
752
+ padding: 4px 0;
753
+ border: 0;
754
+ background: none;
755
+ color: var(--color-text-maxcontrast, #6b6b6b);
756
+ font-size: 12px;
757
+ font-weight: 600;
758
+ letter-spacing: 0.04em;
759
+ text-transform: uppercase;
760
+ cursor: pointer;
761
+ }
762
+
763
+ .cn-sm-group__toggle:focus-visible {
764
+ outline: 2px solid var(--color-primary-element, #0082c9);
765
+ outline-offset: 2px;
766
+ }
767
+
768
+ .cn-sm-group__notice {
769
+ margin: 0;
770
+ padding: 8px;
771
+ border-inline-start: 4px solid var(--color-warning, #c28900);
772
+ border-radius: var(--border-radius, 8px);
773
+ background: var(--color-background-dark, #ededed);
774
+ font-size: 13px;
775
+ }
776
+
777
+ .cn-sm-group__body {
778
+ display: flex;
779
+ flex-direction: column;
780
+ gap: 8px;
781
+ }
782
+
783
+ .cn-sm-visually-hidden {
784
+ position: absolute;
785
+ width: 1px;
786
+ height: 1px;
787
+ overflow: hidden;
788
+ clip-path: inset(50%);
789
+ white-space: nowrap;
790
+ }
791
+ </style>