@conduction/nextcloud-vue 2.92.0 → 2.93.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.
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +57 -4
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +44 -17
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +44 -2
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +140 -59
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +20 -6
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +3 -2
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +2 -36
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +5 -3
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +6 -4
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js +46 -0
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue3.js +6 -2
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +82 -9
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +104 -90
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +9 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js.map +1 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +355 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +462 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +21 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +402 -391
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIcon/semanticIcons.js +2 -2
- package/dist/esm/components/CnIcon/semanticIcons.js.map +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue.js +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js +22 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js +20 -14
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue.js +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js +2 -0
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js.map +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js +15 -5
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js.map +1 -1
- package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue.js +1 -1
- package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue2.js +8 -30
- package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue2.js.map +1 -1
- package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue3.js.map +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +9 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js.map +1 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js +164 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js +93 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -0
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +12 -0
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +24 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +24 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +887 -247
- package/dist/esm/utils/featureCatalog.js +135 -0
- package/dist/esm/utils/featureCatalog.js.map +1 -0
- package/dist/esm/utils/fillTemplate.js +74 -3
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/headerMeta.js +62 -12
- package/dist/esm/utils/headerMeta.js.map +1 -1
- package/dist/esm/utils/roadmapIssues.js +86 -0
- package/dist/esm/utils/roadmapIssues.js.map +1 -0
- package/dist/esm/utils/userDisplayName.js +47 -0
- package/dist/esm/utils/userDisplayName.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +14820 -12966
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +887 -247
- package/l10n/en.json +24 -1
- package/l10n/nl.json +24 -1
- package/package.json +1 -1
- package/src/components/CnAppRoot/CnAppRoot.md +2 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +66 -5
- package/src/components/CnChoiceCards/CnChoiceCards.md +27 -0
- package/src/components/CnChoiceCards/CnChoiceCards.vue +168 -3
- package/src/components/CnDashboardPage/CnDashboardPage.vue +45 -6
- package/src/components/CnDataTable/CnDataTable.vue +2 -36
- package/src/components/CnDetailPage/CnDetailPage.vue +5 -3
- package/src/components/CnFactsWidget/CnFactsWidget.vue +6 -4
- package/src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue +51 -1
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +98 -13
- package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +1053 -0
- package/src/components/CnFormDialog/CnFormDialog.vue +606 -562
- package/src/components/CnNcWidgetWidget/CnNcWidgetWidget.vue +23 -2
- package/src/components/CnNotificationPreferences/CnNotificationPreferences.vue +5 -0
- package/src/components/CnRoadmapTab/CnRoadmapTab.vue +6 -28
- package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +305 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +12 -0
- package/src/css/dialog.css +47 -0
- package/src/css/look-board-detail.css +8 -0
- package/src/css/look-board.css +33 -0
- package/src/schemas/app-manifest-v2.schema.json +9 -3
- package/src/utils/featureCatalog.js +132 -0
- package/src/utils/fillTemplate.js +73 -2
- package/src/utils/headerMeta.js +62 -12
- package/src/utils/roadmapIssues.js +82 -0
- package/src/utils/userDisplayName.js +54 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -91,586 +91,594 @@
|
|
|
91
91
|
only match on the label TEXT — which is the thing under test
|
|
92
92
|
the moment labels are translated, so the assertion passes by
|
|
93
93
|
construction in whichever language it was written in. -->
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}">
|
|
104
|
-
<!-- Board look: the label sits above the control, the error between
|
|
105
|
-
them; the control and the hint keep their place below. -->
|
|
106
|
-
<CnFormField
|
|
107
|
-
v-if="showsOuterLabel(field)"
|
|
108
|
-
:controlId="'cn-form-' + field.key"
|
|
109
|
-
:text="field.label"
|
|
110
|
-
:optional="!field.required"
|
|
111
|
-
:optionalLabel="optionalLabel"
|
|
112
|
-
:error="errors[field.key] || ''"
|
|
113
|
-
:errorId="errorIdFor(field)" />
|
|
114
|
-
<!-- @slot field-{key} Replace one auto-generated field with your own control. -->
|
|
115
|
-
<!-- @binding {object} field The field definition. -->
|
|
116
|
-
<!-- @binding {*} value The field's current value. -->
|
|
117
|
-
<!-- @binding {string} error The field's validation error, if any. -->
|
|
118
|
-
<!-- @binding {Function} update-field Write the value: `updateField(key, value)`. -->
|
|
119
|
-
<slot
|
|
120
|
-
v-if="$slots['field-' + field.key]"
|
|
121
|
-
:name="'field-' + field.key"
|
|
122
|
-
:field="field"
|
|
123
|
-
:value="formData[field.key]"
|
|
124
|
-
:error="errors[field.key]"
|
|
125
|
-
:updateField="updateField" />
|
|
126
|
-
|
|
127
|
-
<!-- referenceType (AD-18): render the integration's
|
|
128
|
-
single-entity widget instead of a plain input. -->
|
|
129
|
-
<component
|
|
130
|
-
:is="resolveReferenceWidget(field)"
|
|
131
|
-
v-else-if="resolveReferenceWidget(field)"
|
|
132
|
-
v-bind="referenceWidgetProps(field)"
|
|
133
|
-
@input="value => updateField(field.key, value)" />
|
|
134
|
-
|
|
135
|
-
<!-- referenceSemanticType (ADR-048): a field that declares a
|
|
136
|
-
canonical semantic-type URI whose provider schema is NOT
|
|
137
|
-
installed renders DISABLED with a mouse-over tooltip. When
|
|
138
|
-
a provider IS installed the field is transformed upstream
|
|
139
|
-
(applySemanticResolution → a `$ref` reference) so it flows
|
|
140
|
-
through the normal select branch below; only the
|
|
141
|
-
unresolved/loading case lands here. -->
|
|
94
|
+
<template v-for="field in visibleFields" :key="field.key">
|
|
95
|
+
<!-- A field whose `section` differs from the one before it opens a
|
|
96
|
+
new group under that heading, across both columns. -->
|
|
97
|
+
<h3
|
|
98
|
+
v-if="sectionStarts[field.key]"
|
|
99
|
+
class="cn-form-dialog__section"
|
|
100
|
+
data-testid="cn-form-dialog-section">
|
|
101
|
+
{{ sectionStarts[field.key] }}
|
|
102
|
+
</h3>
|
|
142
103
|
<div
|
|
143
|
-
|
|
144
|
-
class="cn-form-
|
|
145
|
-
:
|
|
146
|
-
|
|
147
|
-
:
|
|
148
|
-
:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
:
|
|
155
|
-
:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
:
|
|
169
|
-
:
|
|
170
|
-
:
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
104
|
+
:data-cn-field="field.key"
|
|
105
|
+
class="cn-form-dialog__field"
|
|
106
|
+
:class="{
|
|
107
|
+
'cn-form-dialog__field--wide': fieldSpansBothColumns(field),
|
|
108
|
+
'cn-form-field--half': isBoardLook && field.width === 'half',
|
|
109
|
+
'cn-form-field--invalid': isBoardLook && !!errors[field.key],
|
|
110
|
+
}">
|
|
111
|
+
<!-- Board look: the label sits above the control, the error between
|
|
112
|
+
them; the control and the hint keep their place below. -->
|
|
113
|
+
<CnFormField
|
|
114
|
+
v-if="showsOuterLabel(field)"
|
|
115
|
+
:controlId="'cn-form-' + field.key"
|
|
116
|
+
:text="field.label"
|
|
117
|
+
:optional="!field.required"
|
|
118
|
+
:optionalLabel="optionalLabel"
|
|
119
|
+
:error="errors[field.key] || ''"
|
|
120
|
+
:errorId="errorIdFor(field)" />
|
|
121
|
+
<!-- @slot field-{key} Replace one auto-generated field with your own control. -->
|
|
122
|
+
<!-- @binding {object} field The field definition. -->
|
|
123
|
+
<!-- @binding {*} value The field's current value. -->
|
|
124
|
+
<!-- @binding {string} error The field's validation error, if any. -->
|
|
125
|
+
<!-- @binding {Function} update-field Write the value: `updateField(key, value)`. -->
|
|
126
|
+
<slot
|
|
127
|
+
v-if="$slots['field-' + field.key]"
|
|
128
|
+
:name="'field-' + field.key"
|
|
129
|
+
:field="field"
|
|
130
|
+
:value="formData[field.key]"
|
|
131
|
+
:error="errors[field.key]"
|
|
132
|
+
:updateField="updateField" />
|
|
133
|
+
|
|
134
|
+
<!-- referenceType (AD-18): render the integration's
|
|
135
|
+
single-entity widget instead of a plain input. -->
|
|
136
|
+
<component
|
|
137
|
+
:is="resolveReferenceWidget(field)"
|
|
138
|
+
v-else-if="resolveReferenceWidget(field)"
|
|
139
|
+
v-bind="referenceWidgetProps(field)"
|
|
140
|
+
@input="value => updateField(field.key, value)" />
|
|
141
|
+
|
|
142
|
+
<!-- referenceSemanticType (ADR-048): a field that declares a
|
|
143
|
+
canonical semantic-type URI whose provider schema is NOT
|
|
144
|
+
installed renders DISABLED with a mouse-over tooltip. When
|
|
145
|
+
a provider IS installed the field is transformed upstream
|
|
146
|
+
(applySemanticResolution → a `$ref` reference) so it flows
|
|
147
|
+
through the normal select branch below; only the
|
|
148
|
+
unresolved/loading case lands here. -->
|
|
149
|
+
<div
|
|
150
|
+
v-else-if="field.referenceSemanticType && !isSemanticResolved(field)"
|
|
151
|
+
class="cn-form-dialog__semantic-unresolved"
|
|
152
|
+
:title="semanticUnavailableText(field)">
|
|
180
153
|
<NcTextField
|
|
181
154
|
:id="fieldControlId(field)"
|
|
182
155
|
:label="fieldLabelText(field)"
|
|
183
156
|
:labelOutside="isBoardLook"
|
|
184
157
|
v-bind="fieldAria(field)"
|
|
185
158
|
:modelValue="formData[field.key] != null ? String(formData[field.key]) : ''"
|
|
186
|
-
:
|
|
187
|
-
:
|
|
188
|
-
:
|
|
189
|
-
:placeholder="
|
|
190
|
-
|
|
191
|
-
<CnFieldHelper
|
|
192
|
-
:id="helpId(field)"
|
|
193
|
-
:text="field.description"
|
|
194
|
-
:more="field.descriptionLong"
|
|
195
|
-
:help="field.help"
|
|
196
|
-
:label="field.label"
|
|
197
|
-
:error="helperError(field)" />
|
|
198
|
-
</template>
|
|
159
|
+
:helperText="isSemanticLoading(field) ? '' : semanticUnavailableText(field)"
|
|
160
|
+
:disabled="true"
|
|
161
|
+
:loading="isSemanticLoading(field)"
|
|
162
|
+
:placeholder="field.description" />
|
|
163
|
+
</div>
|
|
199
164
|
|
|
200
|
-
<!--
|
|
201
|
-
|
|
165
|
+
<!-- Concept-scheme choice whose options could not be loaded: the
|
|
166
|
+
plain text input it had before, with a line saying so. -->
|
|
167
|
+
<div
|
|
168
|
+
v-else-if="field.codeList && codedFailed[field.key]"
|
|
169
|
+
class="cn-form-dialog__coded-fallback">
|
|
202
170
|
<NcTextField
|
|
203
171
|
:id="fieldControlId(field)"
|
|
204
172
|
:label="fieldLabelText(field)"
|
|
205
173
|
:labelOutside="isBoardLook"
|
|
206
174
|
v-bind="fieldAria(field)"
|
|
207
|
-
:modelValue="
|
|
208
|
-
:error="!!errors[field.key]"
|
|
209
|
-
type="number"
|
|
210
|
-
:disabled="field.readOnly"
|
|
211
|
-
:placeholder="fieldPlaceholder(field)"
|
|
212
|
-
@update:modelValue="value => updateField(field.key, value !== '' ? Number(value) : null)" />
|
|
213
|
-
<CnFieldHelper
|
|
214
|
-
:id="helpId(field)"
|
|
215
|
-
:text="field.description"
|
|
216
|
-
:more="field.descriptionLong"
|
|
217
|
-
:help="field.help"
|
|
218
|
-
:label="field.label"
|
|
219
|
-
:error="helperError(field)" />
|
|
220
|
-
</template>
|
|
221
|
-
|
|
222
|
-
<!-- Textarea -->
|
|
223
|
-
<div v-else-if="field.widget === 'textarea'" class="cn-form-dialog__textarea-wrapper">
|
|
224
|
-
<label v-if="!showsOuterLabel(field)" :for="'cn-form-' + field.key" class="cn-form-dialog__label">
|
|
225
|
-
{{ field.label }}{{ requiredSuffix(field) }}
|
|
226
|
-
</label>
|
|
227
|
-
<textarea
|
|
228
|
-
:id="'cn-form-' + field.key"
|
|
229
|
-
class="cn-form-dialog__textarea"
|
|
230
|
-
v-bind="fieldAria(field)"
|
|
231
|
-
:value="formData[field.key] || ''"
|
|
232
|
-
:disabled="field.readOnly"
|
|
233
|
-
:placeholder="fieldPlaceholder(field)"
|
|
234
|
-
rows="4"
|
|
235
|
-
@input="updateField(field.key, $event.target.value)" />
|
|
236
|
-
<CnFieldHelper
|
|
237
|
-
:id="helpId(field)"
|
|
238
|
-
:text="field.description"
|
|
239
|
-
:more="field.descriptionLong"
|
|
240
|
-
:help="field.help"
|
|
241
|
-
:label="field.label"
|
|
242
|
-
:error="helperError(field)" />
|
|
243
|
-
</div>
|
|
244
|
-
|
|
245
|
-
<!-- Object reference with inline create (`x-allow-create`):
|
|
246
|
-
a select-OR-create picker over the referenced schema.
|
|
247
|
-
Checked before the plain select branch so create-enabled
|
|
248
|
-
references get CnResourceSelect; plain references keep the
|
|
249
|
-
read-only NcSelect below. -->
|
|
250
|
-
<div
|
|
251
|
-
v-else-if="(isReferenceField(field) || isReferenceArrayField(field)) && field.allowCreate"
|
|
252
|
-
class="cn-form-dialog__select-wrapper">
|
|
253
|
-
<CnResourceSelect
|
|
254
|
-
:inputId="'cn-form-' + field.key"
|
|
255
|
-
:inputLabel="fieldLabelText(field)"
|
|
256
|
-
:labelOutside="isBoardLook"
|
|
257
|
-
:register="referenceRegister(field)"
|
|
258
|
-
:schema="String(field.reference.schema)"
|
|
259
|
-
:labelField="referenceLabelField(field)"
|
|
260
|
-
:multiple="!!field.reference.multiple"
|
|
261
|
-
:modelValue="referenceModelValue(field)"
|
|
262
|
-
:clearable="!field.required || !!field.reference.multiple"
|
|
263
|
-
:disabled="field.readOnly"
|
|
264
|
-
:preload="true"
|
|
265
|
-
:createHandler="term => openNestedCreate(field, term)"
|
|
266
|
-
@update:modelValue="value => onReferenceSelected(field, value)"
|
|
267
|
-
@create="obj => onReferenceCreated(field, obj)" />
|
|
268
|
-
<CnFieldHelper
|
|
269
|
-
:id="helpId(field)"
|
|
270
|
-
:text="field.description"
|
|
271
|
-
:more="field.descriptionLong"
|
|
272
|
-
:help="field.help"
|
|
273
|
-
:label="field.label"
|
|
274
|
-
:error="helperError(field)" />
|
|
275
|
-
</div>
|
|
276
|
-
|
|
277
|
-
<!-- Enum toggle (`widget: "switch"` on a 2-value enum):
|
|
278
|
-
renders a switch mapping off→enum[0], on→last enum value. -->
|
|
279
|
-
<div v-else-if="field.widget === 'switch'" class="cn-form-dialog__switch-wrapper">
|
|
280
|
-
<NcCheckboxRadioSwitch
|
|
281
|
-
:modelValue="isSwitchOn(field)"
|
|
282
|
-
:disabled="field.readOnly"
|
|
283
|
-
type="switch"
|
|
284
|
-
@update:modelValue="value => updateField(field.key, switchValueFor(field, value))">
|
|
285
|
-
{{ field.label }}{{ requiredSuffix(field) }}
|
|
286
|
-
</NcCheckboxRadioSwitch>
|
|
287
|
-
<CnFieldHelper
|
|
288
|
-
:id="helpId(field)"
|
|
289
|
-
:text="field.description"
|
|
290
|
-
:more="field.descriptionLong"
|
|
291
|
-
:help="field.help"
|
|
292
|
-
:label="field.label"
|
|
293
|
-
:error="helperError(field)" />
|
|
294
|
-
</div>
|
|
295
|
-
|
|
296
|
-
<!-- Select (enum / $ref object reference / single Nextcloud user, supports async function).
|
|
297
|
-
A single Nextcloud-user field (`widget: "user"`) renders here too:
|
|
298
|
-
`:user-select="isUserField(field)"` flips this NcSelect into NC's native
|
|
299
|
-
user picker, and the async load/search/select machinery (getEffective*,
|
|
300
|
-
onAsyncSearch) resolves real users — mirroring how `user-multiselect`
|
|
301
|
-
shares the multiselect branch below. -->
|
|
302
|
-
<div v-else-if="field.widget === 'select' || field.widget === 'user' || field.widget === 'group'" class="cn-form-dialog__select-wrapper">
|
|
303
|
-
<!-- A `widget: "user"` field renders NcSelectUsers, everything
|
|
304
|
-
else NcSelect. It used to be one NcSelect with
|
|
305
|
-
`:user-select="isUserField(field)"`, and @nextcloud/vue 9
|
|
306
|
-
REMOVED that prop, so a user field had silently become a
|
|
307
|
-
plain select. NcSelectUsers wraps NcSelect and merges the
|
|
308
|
-
rest of these bindings through, so the async load, search
|
|
309
|
-
and select machinery is unchanged either way. -->
|
|
310
|
-
<component
|
|
311
|
-
:is="isUserField(field) ? 'NcSelectUsers' : 'NcSelect'"
|
|
312
|
-
v-cn-select-aria="fieldAria(field)"
|
|
313
|
-
:inputId="'cn-form-' + field.key"
|
|
314
|
-
:inputLabel="fieldLabelText(field)"
|
|
315
|
-
:labelOutside="isBoardLook"
|
|
316
|
-
:options="getEffectiveOptions(field)"
|
|
317
|
-
:modelValue="getEffectiveSelectedOption(field)"
|
|
318
|
-
:clearable="!field.required"
|
|
319
|
-
:disabled="field.readOnly"
|
|
320
|
-
:loading="isFieldLoading(field)"
|
|
321
|
-
:filterable="!isAsyncEnum(field)"
|
|
322
|
-
@update:modelValue="onEffectiveSelectChange(field, $event)"
|
|
323
|
-
@search="isAsyncEnum(field) ? onAsyncSearch(field, $event) : undefined">
|
|
324
|
-
<template
|
|
325
|
-
v-if="$slots['field-' + field.key + '-option']"
|
|
326
|
-
#option="optionProps">
|
|
327
|
-
<!-- @slot field-{key}-option Render one dropdown option for a select, multiselect or tags field. -->
|
|
328
|
-
<!-- @binding {object} option The option being rendered (`{ id, label }`). -->
|
|
329
|
-
<slot :name="'field-' + field.key + '-option'" v-bind="optionProps" />
|
|
330
|
-
</template>
|
|
331
|
-
<!-- NcSelect's own option splits a label in two spans
|
|
332
|
-
(NcEllipsisedOption, keeping its last characters
|
|
333
|
-
visible), and in a narrow dropdown the halves broke
|
|
334
|
-
onto two lines mid-word: "Agricu lture". A plain
|
|
335
|
-
label wraps at spaces only. NcSelectUsers keeps its
|
|
336
|
-
avatar rendering. -->
|
|
337
|
-
<template
|
|
338
|
-
v-else-if="!isUserField(field)"
|
|
339
|
-
#option="optionProps">
|
|
340
|
-
<span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
|
|
341
|
-
</template>
|
|
342
|
-
<template
|
|
343
|
-
v-if="$slots['field-' + field.key + '-selected-option']"
|
|
344
|
-
#selected-option="optionProps">
|
|
345
|
-
<!-- @slot field-{key}-selected-option Render the chosen option of a select, multiselect or tags field. -->
|
|
346
|
-
<!-- @binding {object} option The selected option (`{ id, label }`). -->
|
|
347
|
-
<slot :name="'field-' + field.key + '-selected-option'" v-bind="optionProps" />
|
|
348
|
-
</template>
|
|
349
|
-
</component>
|
|
350
|
-
<CnFieldHelper
|
|
351
|
-
:id="helpId(field)"
|
|
352
|
-
:text="field.description"
|
|
353
|
-
:more="field.descriptionLong"
|
|
354
|
-
:help="field.help"
|
|
355
|
-
:label="field.label"
|
|
356
|
-
:error="helperError(field)" />
|
|
357
|
-
</div>
|
|
358
|
-
|
|
359
|
-
<!-- Multiselect (array enum items / $ref array / Nextcloud users, supports async function) -->
|
|
360
|
-
<div v-else-if="field.widget === 'multiselect' || field.widget === 'user-multiselect' || field.widget === 'group-multiselect'" class="cn-form-dialog__select-wrapper">
|
|
361
|
-
<NcSelect
|
|
362
|
-
v-cn-select-aria="fieldAria(field)"
|
|
363
|
-
:inputId="'cn-form-' + field.key"
|
|
364
|
-
:inputLabel="fieldLabelText(field)"
|
|
365
|
-
:labelOutside="isBoardLook"
|
|
366
|
-
:options="getEffectiveArrayOptions(field)"
|
|
367
|
-
:modelValue="getEffectiveSelectedArrayOptions(field)"
|
|
368
|
-
:multiple="true"
|
|
369
|
-
:keepOpen="true"
|
|
370
|
-
:clearable="true"
|
|
371
|
-
:disabled="field.readOnly"
|
|
372
|
-
:loading="isFieldLoading(field)"
|
|
373
|
-
:filterable="!isAsyncItemsEnum(field)"
|
|
374
|
-
@update:modelValue="onEffectiveMultiSelectChange(field, $event)"
|
|
375
|
-
@search="isAsyncItemsEnum(field) ? onAsyncSearch(field, $event) : undefined">
|
|
376
|
-
<template
|
|
377
|
-
v-if="$slots['field-' + field.key + '-option']"
|
|
378
|
-
#option="optionProps">
|
|
379
|
-
<slot :name="'field-' + field.key + '-option'" v-bind="optionProps" />
|
|
380
|
-
</template>
|
|
381
|
-
<template v-else #option="optionProps">
|
|
382
|
-
<span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
|
|
383
|
-
</template>
|
|
384
|
-
<template
|
|
385
|
-
v-if="$slots['field-' + field.key + '-selected-option']"
|
|
386
|
-
#selected-option="optionProps">
|
|
387
|
-
<slot :name="'field-' + field.key + '-selected-option'" v-bind="optionProps" />
|
|
388
|
-
</template>
|
|
389
|
-
<template v-else #selected-option="optionProps">
|
|
390
|
-
<span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
|
|
391
|
-
</template>
|
|
392
|
-
</NcSelect>
|
|
393
|
-
<CnFieldHelper
|
|
394
|
-
:id="helpId(field)"
|
|
395
|
-
:text="field.description"
|
|
396
|
-
:more="field.descriptionLong"
|
|
397
|
-
:help="field.help"
|
|
398
|
-
:label="field.label"
|
|
399
|
-
:error="helperError(field)" />
|
|
400
|
-
</div>
|
|
401
|
-
|
|
402
|
-
<!-- Tags (array, freeform, supports async suggestions) -->
|
|
403
|
-
<div v-else-if="field.widget === 'tags'" class="cn-form-dialog__select-wrapper">
|
|
404
|
-
<!-- TODO: restore `:options` to `asyncState[field.key]?.options` once on Vue 3 (buble doesn't support optional chaining) -->
|
|
405
|
-
<NcSelect
|
|
406
|
-
v-cn-select-aria="fieldAria(field)"
|
|
407
|
-
:inputId="'cn-form-' + field.key"
|
|
408
|
-
:inputLabel="fieldLabelText(field)"
|
|
409
|
-
:labelOutside="isBoardLook"
|
|
410
|
-
:modelValue="formData[field.key] || []"
|
|
411
|
-
:options="isFieldAsync(field) ? ((asyncState[field.key] && asyncState[field.key].options) || []) : []"
|
|
412
|
-
:multiple="true"
|
|
413
|
-
:keepOpen="true"
|
|
414
|
-
:taggable="true"
|
|
415
|
-
:clearable="true"
|
|
416
|
-
:disabled="field.readOnly"
|
|
417
|
-
:loading="isFieldLoading(field)"
|
|
418
|
-
:filterable="!isFieldAsync(field)"
|
|
419
|
-
@update:modelValue="updateField(field.key, $event)"
|
|
420
|
-
@search="isFieldAsync(field) ? onAsyncSearch(field, $event) : undefined">
|
|
421
|
-
<template
|
|
422
|
-
v-if="$slots['field-' + field.key + '-option']"
|
|
423
|
-
#option="optionProps">
|
|
424
|
-
<slot :name="'field-' + field.key + '-option'" v-bind="optionProps" />
|
|
425
|
-
</template>
|
|
426
|
-
<template v-else #option="optionProps">
|
|
427
|
-
<span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
|
|
428
|
-
</template>
|
|
429
|
-
<template
|
|
430
|
-
v-if="$slots['field-' + field.key + '-selected-option']"
|
|
431
|
-
#selected-option="optionProps">
|
|
432
|
-
<slot :name="'field-' + field.key + '-selected-option'" v-bind="optionProps" />
|
|
433
|
-
</template>
|
|
434
|
-
<template v-else #selected-option="optionProps">
|
|
435
|
-
<span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
|
|
436
|
-
</template>
|
|
437
|
-
</NcSelect>
|
|
438
|
-
<CnFieldHelper
|
|
439
|
-
:id="helpId(field)"
|
|
440
|
-
:text="field.description"
|
|
441
|
-
:more="field.descriptionLong"
|
|
442
|
-
:help="field.help"
|
|
443
|
-
:label="field.label"
|
|
444
|
-
:error="helperError(field)" />
|
|
445
|
-
</div>
|
|
446
|
-
|
|
447
|
-
<!-- Checkbox / Switch (boolean) -->
|
|
448
|
-
<div v-else-if="field.widget === 'checkbox'" class="cn-form-dialog__switch-wrapper">
|
|
449
|
-
<NcCheckboxRadioSwitch
|
|
450
|
-
:modelValue="!!formData[field.key]"
|
|
175
|
+
:modelValue="codedFallbackText(field)"
|
|
451
176
|
:disabled="field.readOnly"
|
|
452
|
-
|
|
453
|
-
@update:modelValue="
|
|
454
|
-
{{ field.label }}{{ requiredSuffix(field) }}
|
|
455
|
-
</NcCheckboxRadioSwitch>
|
|
456
|
-
<CnFieldHelper
|
|
457
|
-
:id="helpId(field)"
|
|
458
|
-
:text="field.description"
|
|
459
|
-
:more="field.descriptionLong"
|
|
460
|
-
:help="field.help"
|
|
461
|
-
:label="field.label"
|
|
462
|
-
:error="helperError(field)" />
|
|
463
|
-
</div>
|
|
464
|
-
|
|
465
|
-
<!-- Date / Datetime (NcTextField's type validator rejects
|
|
466
|
-
'date'/'datetime-local', so use NcDateTimePickerNative) -->
|
|
467
|
-
<div
|
|
468
|
-
v-else-if="field.widget === 'date' || field.widget === 'datetime'"
|
|
469
|
-
class="cn-form-dialog__select-wrapper">
|
|
470
|
-
<label v-if="!showsOuterLabel(field)" :for="'cn-form-' + field.key" class="cn-form-dialog__label">
|
|
471
|
-
{{ field.label }}{{ requiredSuffix(field) }}
|
|
472
|
-
</label>
|
|
473
|
-
<NcDateTimePickerNative
|
|
474
|
-
:id="'cn-form-' + field.key"
|
|
475
|
-
:type="field.widget === 'datetime' ? 'datetime-local' : 'date'"
|
|
476
|
-
:label="field.label"
|
|
477
|
-
:hideLabel="true"
|
|
478
|
-
:modelValue="dateValueFor(field)"
|
|
479
|
-
:disabled="field.readOnly"
|
|
480
|
-
@update:modelValue="date => onDateFieldInput(field, date)" />
|
|
481
|
-
<CnFieldHelper
|
|
482
|
-
:id="helpId(field)"
|
|
483
|
-
:text="field.description"
|
|
484
|
-
:more="field.descriptionLong"
|
|
485
|
-
:help="field.help"
|
|
486
|
-
:label="field.label"
|
|
487
|
-
:error="helperError(field)" />
|
|
488
|
-
</div>
|
|
489
|
-
|
|
490
|
-
<!-- JSON (type: 'object'|'array'|... with widget: 'json'): parses on input, stores parsed value in formData -->
|
|
491
|
-
<div v-else-if="field.widget === 'json'" class="cn-form-dialog__json-wrapper">
|
|
492
|
-
<label v-if="!showsOuterLabel(field)" :for="'cn-form-' + field.key" class="cn-form-dialog__label">
|
|
493
|
-
{{ field.label }}{{ requiredSuffix(field) }}
|
|
494
|
-
</label>
|
|
495
|
-
<CnJsonViewer
|
|
496
|
-
:value="jsonStringFor(field)"
|
|
497
|
-
language="json"
|
|
498
|
-
:readOnly="field.readOnly"
|
|
499
|
-
:errorText="jsonErrors[field.key] || ''"
|
|
500
|
-
@update:value="value => onJsonFieldInput(field, value)" />
|
|
501
|
-
<CnFieldHelper
|
|
502
|
-
:id="helpId(field)"
|
|
503
|
-
:text="field.description"
|
|
504
|
-
:more="field.descriptionLong"
|
|
505
|
-
:help="field.help"
|
|
506
|
-
:label="field.label"
|
|
507
|
-
:error="helperError(field)" />
|
|
177
|
+
:helperText="t('nextcloud-vue', 'The list of choices could not be loaded. Enter the value by hand.')"
|
|
178
|
+
@update:modelValue="onCodedFallbackInput(field, $event)" />
|
|
508
179
|
</div>
|
|
509
180
|
|
|
510
|
-
<!--
|
|
511
|
-
<div v-else-if="field.widget === 'code'" class="cn-form-dialog__json-wrapper">
|
|
512
|
-
<label v-if="!showsOuterLabel(field)" :for="'cn-form-' + field.key" class="cn-form-dialog__label">
|
|
513
|
-
{{ field.label }}{{ requiredSuffix(field) }}
|
|
514
|
-
</label>
|
|
515
|
-
<CnJsonViewer
|
|
516
|
-
:value="formData[field.key] != null ? String(formData[field.key]) : ''"
|
|
517
|
-
:language="field.language || 'auto'"
|
|
518
|
-
:readOnly="field.readOnly"
|
|
519
|
-
@update:value="value => updateField(field.key, value)" />
|
|
520
|
-
<CnFieldHelper
|
|
521
|
-
:id="helpId(field)"
|
|
522
|
-
:text="field.description"
|
|
523
|
-
:more="field.descriptionLong"
|
|
524
|
-
:help="field.help"
|
|
525
|
-
:label="field.label"
|
|
526
|
-
:error="helperError(field)" />
|
|
527
|
-
</div>
|
|
528
|
-
|
|
529
|
-
<!-- File (widget: 'file'): one file or several, small ones inline, big ones uploaded after save, optional camera. -->
|
|
530
|
-
<div v-else-if="field.widget === 'file'" class="cn-form-dialog__file-wrapper">
|
|
531
|
-
<CnFileField
|
|
532
|
-
:modelValue="formData[field.key]"
|
|
533
|
-
:label="fieldLabelText(field)"
|
|
534
|
-
:accept="field.file ? field.file.accept : ''"
|
|
535
|
-
:multiple="!!(field.file && field.file.multiple)"
|
|
536
|
-
:capture="field.file ? field.file.capture : ''"
|
|
537
|
-
v-bind="fileLimits(field)"
|
|
538
|
-
:disabled="field.readOnly"
|
|
539
|
-
:helperText="errors[field.key] || ''"
|
|
540
|
-
@update:modelValue="value => updateField(field.key, value)" />
|
|
541
|
-
<CnFieldHelper
|
|
542
|
-
:text="field.description"
|
|
543
|
-
:more="field.descriptionLong"
|
|
544
|
-
:help="field.help"
|
|
545
|
-
:label="field.label" />
|
|
546
|
-
</div>
|
|
547
|
-
|
|
548
|
-
<!-- Duration (widget: 'duration'): number + unit over an ISO 8601 string. -->
|
|
549
|
-
<div v-else-if="field.widget === 'duration'" class="cn-form-dialog__duration-wrapper">
|
|
550
|
-
<CnDurationField
|
|
551
|
-
:modelValue="formData[field.key] != null ? String(formData[field.key]) : null"
|
|
552
|
-
:inputLabel="fieldLabelText(field)"
|
|
553
|
-
:disabled="field.readOnly"
|
|
554
|
-
:error="!!errors[field.key]"
|
|
555
|
-
@update:modelValue="value => updateField(field.key, value)" />
|
|
556
|
-
<CnFieldHelper
|
|
557
|
-
:id="helpId(field)"
|
|
558
|
-
:text="field.description"
|
|
559
|
-
:more="field.descriptionLong"
|
|
560
|
-
:help="field.help"
|
|
561
|
-
:label="field.label"
|
|
562
|
-
:error="helperError(field)" />
|
|
563
|
-
</div>
|
|
564
|
-
|
|
565
|
-
<!-- Child records (widget: 'child-records'): another schema's records as an editable table, saved after the parent. -->
|
|
566
|
-
<div v-else-if="field.widget === 'child-records' && field.childRecords" class="cn-form-dialog__child-records-wrapper">
|
|
567
|
-
<CnChildRecordsField
|
|
568
|
-
:modelValue="Array.isArray(formData[field.key]) ? formData[field.key] : []"
|
|
569
|
-
:config="field.childRecords"
|
|
570
|
-
:register="register"
|
|
571
|
-
:parentId="item ? (item.id || item.uuid || '') : ''"
|
|
572
|
-
:inputLabel="fieldLabelText(field)"
|
|
573
|
-
:disabled="field.readOnly"
|
|
574
|
-
:error="errors[field.key] || ''"
|
|
575
|
-
@update:modelValue="value => updateField(field.key, value)"
|
|
576
|
-
@loaded="rows => { childOriginals[field.key] = rows }"
|
|
577
|
-
@validity="problems => { childProblems[field.key] = problems }" />
|
|
578
|
-
<CnFieldHelper
|
|
579
|
-
:text="field.description"
|
|
580
|
-
:more="field.descriptionLong"
|
|
581
|
-
:help="field.help"
|
|
582
|
-
:label="field.label" />
|
|
583
|
-
</div>
|
|
584
|
-
|
|
585
|
-
<!-- Sub-objects (widget: 'sub-objects'): an array of objects as an editable table. -->
|
|
586
|
-
<div v-else-if="field.widget === 'sub-objects' && field.items" class="cn-form-dialog__sub-objects-wrapper">
|
|
587
|
-
<CnSubObjectsField
|
|
588
|
-
:modelValue="Array.isArray(formData[field.key]) ? formData[field.key] : []"
|
|
589
|
-
:items="field.items"
|
|
590
|
-
:inputLabel="fieldLabelText(field)"
|
|
591
|
-
:disabled="field.readOnly"
|
|
592
|
-
:error="errors[field.key] || ''"
|
|
593
|
-
@update:modelValue="value => updateField(field.key, value)" />
|
|
594
|
-
<CnFieldHelper
|
|
595
|
-
:text="field.description"
|
|
596
|
-
:more="field.descriptionLong"
|
|
597
|
-
:help="field.help"
|
|
598
|
-
:label="field.label" />
|
|
599
|
-
</div>
|
|
600
|
-
|
|
601
|
-
<!-- Registry type-ahead (widget: 'property-source'): CnPropertySourceField;
|
|
602
|
-
a pick can fill empty sibling fields from config.fill. -->
|
|
603
|
-
<div v-else-if="field.widget === 'property-source' && field.propertySource" class="cn-form-dialog__property-source-wrapper">
|
|
604
|
-
<CnPropertySourceField
|
|
605
|
-
:provider="field.propertySource.provider"
|
|
606
|
-
:mode="field.propertySource.mode"
|
|
607
|
-
:inputId="'cn-form-' + field.key"
|
|
608
|
-
:inputLabel="fieldLabelText(field)"
|
|
609
|
-
:modelValue="formData[field.key] != null ? String(formData[field.key]) : ''"
|
|
610
|
-
:disabled="field.readOnly"
|
|
611
|
-
:error="!!errors[field.key]"
|
|
612
|
-
@update:modelValue="value => updateField(field.key, value)"
|
|
613
|
-
@resolved="payload => onPropertySourceResolved(field, payload)" />
|
|
614
|
-
<CnFieldHelper
|
|
615
|
-
:id="helpId(field)"
|
|
616
|
-
:text="field.description"
|
|
617
|
-
:more="field.descriptionLong"
|
|
618
|
-
:help="field.help"
|
|
619
|
-
:label="field.label"
|
|
620
|
-
:error="helperError(field)" />
|
|
621
|
-
</div>
|
|
622
|
-
|
|
623
|
-
<!-- Icon (widget: 'icon'): renders CnIconBrowser, forwarding the field's icon config.
|
|
624
|
-
`searchable` is gone — the browser always searches. -->
|
|
625
|
-
<div v-else-if="field.widget === 'icon'" class="cn-form-dialog__icon-wrapper">
|
|
626
|
-
<label v-if="!showsOuterLabel(field)" :for="'cn-form-' + field.key" class="cn-form-dialog__label">
|
|
627
|
-
{{ field.label }}{{ requiredSuffix(field) }}
|
|
628
|
-
</label>
|
|
629
|
-
<CnIconBrowser
|
|
630
|
-
:value="formData[field.key] != null ? String(formData[field.key]) : null"
|
|
631
|
-
:sources="field.iconSources || ['mdi']"
|
|
632
|
-
:catalogues="field.catalogues || {}"
|
|
633
|
-
:allowCustomSvg="!!field.allowCustomSvg"
|
|
634
|
-
:clearable="!field.required"
|
|
635
|
-
@input="value => updateField(field.key, value)" />
|
|
636
|
-
<CnFieldHelper
|
|
637
|
-
:id="helpId(field)"
|
|
638
|
-
:text="field.description"
|
|
639
|
-
:more="field.descriptionLong"
|
|
640
|
-
:help="field.help"
|
|
641
|
-
:label="field.label"
|
|
642
|
-
:error="helperError(field)" />
|
|
643
|
-
</div>
|
|
644
|
-
|
|
645
|
-
<!-- Fallback: text input -->
|
|
181
|
+
<!-- Auto-generated field -->
|
|
646
182
|
<template v-else>
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
183
|
+
<!-- Text / Email / URL. The helper line lives outside the
|
|
184
|
+
input (rather than on `helper-text`) so an over-long
|
|
185
|
+
description can hang its info popover off it. -->
|
|
186
|
+
<template v-if="field.widget === 'text' || field.widget === 'email' || field.widget === 'url'">
|
|
187
|
+
<NcTextField
|
|
188
|
+
:id="fieldControlId(field)"
|
|
189
|
+
:label="fieldLabelText(field)"
|
|
190
|
+
:labelOutside="isBoardLook"
|
|
191
|
+
v-bind="fieldAria(field)"
|
|
192
|
+
:modelValue="formData[field.key] != null ? String(formData[field.key]) : ''"
|
|
193
|
+
:error="!!errors[field.key]"
|
|
194
|
+
:type="field.widget === 'email' ? 'email' : field.widget === 'url' ? 'url' : 'text'"
|
|
195
|
+
:disabled="field.readOnly"
|
|
196
|
+
:placeholder="fieldPlaceholder(field)"
|
|
197
|
+
@update:modelValue="value => updateField(field.key, value)" />
|
|
198
|
+
<CnFieldHelper
|
|
199
|
+
:id="helpId(field)"
|
|
200
|
+
:text="field.description"
|
|
201
|
+
:more="field.descriptionLong"
|
|
202
|
+
:help="field.help"
|
|
203
|
+
:label="field.label"
|
|
204
|
+
:error="helperError(field)" />
|
|
205
|
+
</template>
|
|
206
|
+
|
|
207
|
+
<!-- Number -->
|
|
208
|
+
<template v-else-if="field.widget === 'number'">
|
|
209
|
+
<NcTextField
|
|
210
|
+
:id="fieldControlId(field)"
|
|
211
|
+
:label="fieldLabelText(field)"
|
|
212
|
+
:labelOutside="isBoardLook"
|
|
213
|
+
v-bind="fieldAria(field)"
|
|
214
|
+
:modelValue="formData[field.key] != null ? String(formData[field.key]) : ''"
|
|
215
|
+
:error="!!errors[field.key]"
|
|
216
|
+
type="number"
|
|
217
|
+
:disabled="field.readOnly"
|
|
218
|
+
:placeholder="fieldPlaceholder(field)"
|
|
219
|
+
@update:modelValue="value => updateField(field.key, value !== '' ? Number(value) : null)" />
|
|
220
|
+
<CnFieldHelper
|
|
221
|
+
:id="helpId(field)"
|
|
222
|
+
:text="field.description"
|
|
223
|
+
:more="field.descriptionLong"
|
|
224
|
+
:help="field.help"
|
|
225
|
+
:label="field.label"
|
|
226
|
+
:error="helperError(field)" />
|
|
227
|
+
</template>
|
|
228
|
+
|
|
229
|
+
<!-- Textarea -->
|
|
230
|
+
<div v-else-if="field.widget === 'textarea'" class="cn-form-dialog__textarea-wrapper">
|
|
231
|
+
<label v-if="!showsOuterLabel(field)" :for="'cn-form-' + field.key" class="cn-form-dialog__label">
|
|
232
|
+
{{ field.label }}{{ requiredSuffix(field) }}
|
|
233
|
+
</label>
|
|
234
|
+
<textarea
|
|
235
|
+
:id="'cn-form-' + field.key"
|
|
236
|
+
class="cn-form-dialog__textarea"
|
|
237
|
+
v-bind="fieldAria(field)"
|
|
238
|
+
:value="formData[field.key] || ''"
|
|
239
|
+
:disabled="field.readOnly"
|
|
240
|
+
:placeholder="fieldPlaceholder(field)"
|
|
241
|
+
rows="4"
|
|
242
|
+
@input="updateField(field.key, $event.target.value)" />
|
|
243
|
+
<CnFieldHelper
|
|
244
|
+
:id="helpId(field)"
|
|
245
|
+
:text="field.description"
|
|
246
|
+
:more="field.descriptionLong"
|
|
247
|
+
:help="field.help"
|
|
248
|
+
:label="field.label"
|
|
249
|
+
:error="helperError(field)" />
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<!-- Object reference with inline create (`x-allow-create`):
|
|
253
|
+
a select-OR-create picker over the referenced schema.
|
|
254
|
+
Checked before the plain select branch so create-enabled
|
|
255
|
+
references get CnResourceSelect; plain references keep the
|
|
256
|
+
read-only NcSelect below. -->
|
|
257
|
+
<div
|
|
258
|
+
v-else-if="(isReferenceField(field) || isReferenceArrayField(field)) && field.allowCreate"
|
|
259
|
+
class="cn-form-dialog__select-wrapper">
|
|
260
|
+
<CnResourceSelect
|
|
261
|
+
:inputId="'cn-form-' + field.key"
|
|
262
|
+
:inputLabel="fieldLabelText(field)"
|
|
263
|
+
:labelOutside="isBoardLook"
|
|
264
|
+
:register="referenceRegister(field)"
|
|
265
|
+
:schema="String(field.reference.schema)"
|
|
266
|
+
:labelField="referenceLabelField(field)"
|
|
267
|
+
:multiple="!!field.reference.multiple"
|
|
268
|
+
:modelValue="referenceModelValue(field)"
|
|
269
|
+
:clearable="!field.required || !!field.reference.multiple"
|
|
270
|
+
:disabled="field.readOnly"
|
|
271
|
+
:preload="true"
|
|
272
|
+
:createHandler="term => openNestedCreate(field, term)"
|
|
273
|
+
@update:modelValue="value => onReferenceSelected(field, value)"
|
|
274
|
+
@create="obj => onReferenceCreated(field, obj)" />
|
|
275
|
+
<CnFieldHelper
|
|
276
|
+
:id="helpId(field)"
|
|
277
|
+
:text="field.description"
|
|
278
|
+
:more="field.descriptionLong"
|
|
279
|
+
:help="field.help"
|
|
280
|
+
:label="field.label"
|
|
281
|
+
:error="helperError(field)" />
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
<!-- Enum toggle (`widget: "switch"` on a 2-value enum):
|
|
285
|
+
renders a switch mapping off→enum[0], on→last enum value. -->
|
|
286
|
+
<div v-else-if="field.widget === 'switch'" class="cn-form-dialog__switch-wrapper">
|
|
287
|
+
<NcCheckboxRadioSwitch
|
|
288
|
+
:modelValue="isSwitchOn(field)"
|
|
289
|
+
:disabled="field.readOnly"
|
|
290
|
+
type="switch"
|
|
291
|
+
@update:modelValue="value => updateField(field.key, switchValueFor(field, value))">
|
|
292
|
+
{{ field.label }}{{ requiredSuffix(field) }}
|
|
293
|
+
</NcCheckboxRadioSwitch>
|
|
294
|
+
<CnFieldHelper
|
|
295
|
+
:id="helpId(field)"
|
|
296
|
+
:text="field.description"
|
|
297
|
+
:more="field.descriptionLong"
|
|
298
|
+
:help="field.help"
|
|
299
|
+
:label="field.label"
|
|
300
|
+
:error="helperError(field)" />
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
<!-- Select (enum / $ref object reference / single Nextcloud user, supports async function).
|
|
304
|
+
A single Nextcloud-user field (`widget: "user"`) renders here too:
|
|
305
|
+
`:user-select="isUserField(field)"` flips this NcSelect into NC's native
|
|
306
|
+
user picker, and the async load/search/select machinery (getEffective*,
|
|
307
|
+
onAsyncSearch) resolves real users — mirroring how `user-multiselect`
|
|
308
|
+
shares the multiselect branch below. -->
|
|
309
|
+
<div v-else-if="field.widget === 'select' || field.widget === 'user' || field.widget === 'group'" class="cn-form-dialog__select-wrapper">
|
|
310
|
+
<!-- A `widget: "user"` field renders NcSelectUsers, everything
|
|
311
|
+
else NcSelect. It used to be one NcSelect with
|
|
312
|
+
`:user-select="isUserField(field)"`, and @nextcloud/vue 9
|
|
313
|
+
REMOVED that prop, so a user field had silently become a
|
|
314
|
+
plain select. NcSelectUsers wraps NcSelect and merges the
|
|
315
|
+
rest of these bindings through, so the async load, search
|
|
316
|
+
and select machinery is unchanged either way. -->
|
|
317
|
+
<component
|
|
318
|
+
:is="isUserField(field) ? 'NcSelectUsers' : 'NcSelect'"
|
|
319
|
+
v-cn-select-aria="fieldAria(field)"
|
|
320
|
+
:inputId="'cn-form-' + field.key"
|
|
321
|
+
:inputLabel="fieldLabelText(field)"
|
|
322
|
+
:labelOutside="isBoardLook"
|
|
323
|
+
:options="getEffectiveOptions(field)"
|
|
324
|
+
:modelValue="getEffectiveSelectedOption(field)"
|
|
325
|
+
:clearable="!field.required"
|
|
326
|
+
:disabled="field.readOnly"
|
|
327
|
+
:loading="isFieldLoading(field)"
|
|
328
|
+
:filterable="!isAsyncEnum(field)"
|
|
329
|
+
@update:modelValue="onEffectiveSelectChange(field, $event)"
|
|
330
|
+
@search="isAsyncEnum(field) ? onAsyncSearch(field, $event) : undefined">
|
|
331
|
+
<template
|
|
332
|
+
v-if="$slots['field-' + field.key + '-option']"
|
|
333
|
+
#option="optionProps">
|
|
334
|
+
<!-- @slot field-{key}-option Render one dropdown option for a select, multiselect or tags field. -->
|
|
335
|
+
<!-- @binding {object} option The option being rendered (`{ id, label }`). -->
|
|
336
|
+
<slot :name="'field-' + field.key + '-option'" v-bind="optionProps" />
|
|
337
|
+
</template>
|
|
338
|
+
<!-- NcSelect's own option splits a label in two spans
|
|
339
|
+
(NcEllipsisedOption, keeping its last characters
|
|
340
|
+
visible), and in a narrow dropdown the halves broke
|
|
341
|
+
onto two lines mid-word: "Agricu lture". A plain
|
|
342
|
+
label wraps at spaces only. NcSelectUsers keeps its
|
|
343
|
+
avatar rendering. -->
|
|
344
|
+
<template
|
|
345
|
+
v-else-if="!isUserField(field)"
|
|
346
|
+
#option="optionProps">
|
|
347
|
+
<span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
|
|
348
|
+
</template>
|
|
349
|
+
<template
|
|
350
|
+
v-if="$slots['field-' + field.key + '-selected-option']"
|
|
351
|
+
#selected-option="optionProps">
|
|
352
|
+
<!-- @slot field-{key}-selected-option Render the chosen option of a select, multiselect or tags field. -->
|
|
353
|
+
<!-- @binding {object} option The selected option (`{ id, label }`). -->
|
|
354
|
+
<slot :name="'field-' + field.key + '-selected-option'" v-bind="optionProps" />
|
|
355
|
+
</template>
|
|
356
|
+
</component>
|
|
357
|
+
<CnFieldHelper
|
|
358
|
+
:id="helpId(field)"
|
|
359
|
+
:text="field.description"
|
|
360
|
+
:more="field.descriptionLong"
|
|
361
|
+
:help="field.help"
|
|
362
|
+
:label="field.label"
|
|
363
|
+
:error="helperError(field)" />
|
|
364
|
+
</div>
|
|
365
|
+
|
|
366
|
+
<!-- Multiselect (array enum items / $ref array / Nextcloud users, supports async function) -->
|
|
367
|
+
<div v-else-if="field.widget === 'multiselect' || field.widget === 'user-multiselect' || field.widget === 'group-multiselect'" class="cn-form-dialog__select-wrapper">
|
|
368
|
+
<NcSelect
|
|
369
|
+
v-cn-select-aria="fieldAria(field)"
|
|
370
|
+
:inputId="'cn-form-' + field.key"
|
|
371
|
+
:inputLabel="fieldLabelText(field)"
|
|
372
|
+
:labelOutside="isBoardLook"
|
|
373
|
+
:options="getEffectiveArrayOptions(field)"
|
|
374
|
+
:modelValue="getEffectiveSelectedArrayOptions(field)"
|
|
375
|
+
:multiple="true"
|
|
376
|
+
:keepOpen="true"
|
|
377
|
+
:clearable="true"
|
|
378
|
+
:disabled="field.readOnly"
|
|
379
|
+
:loading="isFieldLoading(field)"
|
|
380
|
+
:filterable="!isAsyncItemsEnum(field)"
|
|
381
|
+
@update:modelValue="onEffectiveMultiSelectChange(field, $event)"
|
|
382
|
+
@search="isAsyncItemsEnum(field) ? onAsyncSearch(field, $event) : undefined">
|
|
383
|
+
<template
|
|
384
|
+
v-if="$slots['field-' + field.key + '-option']"
|
|
385
|
+
#option="optionProps">
|
|
386
|
+
<slot :name="'field-' + field.key + '-option'" v-bind="optionProps" />
|
|
387
|
+
</template>
|
|
388
|
+
<template v-else #option="optionProps">
|
|
389
|
+
<span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
|
|
390
|
+
</template>
|
|
391
|
+
<template
|
|
392
|
+
v-if="$slots['field-' + field.key + '-selected-option']"
|
|
393
|
+
#selected-option="optionProps">
|
|
394
|
+
<slot :name="'field-' + field.key + '-selected-option'" v-bind="optionProps" />
|
|
395
|
+
</template>
|
|
396
|
+
<template v-else #selected-option="optionProps">
|
|
397
|
+
<span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
|
|
398
|
+
</template>
|
|
399
|
+
</NcSelect>
|
|
400
|
+
<CnFieldHelper
|
|
401
|
+
:id="helpId(field)"
|
|
402
|
+
:text="field.description"
|
|
403
|
+
:more="field.descriptionLong"
|
|
404
|
+
:help="field.help"
|
|
405
|
+
:label="field.label"
|
|
406
|
+
:error="helperError(field)" />
|
|
407
|
+
</div>
|
|
408
|
+
|
|
409
|
+
<!-- Tags (array, freeform, supports async suggestions) -->
|
|
410
|
+
<div v-else-if="field.widget === 'tags'" class="cn-form-dialog__select-wrapper">
|
|
411
|
+
<!-- TODO: restore `:options` to `asyncState[field.key]?.options` once on Vue 3 (buble doesn't support optional chaining) -->
|
|
412
|
+
<NcSelect
|
|
413
|
+
v-cn-select-aria="fieldAria(field)"
|
|
414
|
+
:inputId="'cn-form-' + field.key"
|
|
415
|
+
:inputLabel="fieldLabelText(field)"
|
|
416
|
+
:labelOutside="isBoardLook"
|
|
417
|
+
:modelValue="formData[field.key] || []"
|
|
418
|
+
:options="isFieldAsync(field) ? ((asyncState[field.key] && asyncState[field.key].options) || []) : []"
|
|
419
|
+
:multiple="true"
|
|
420
|
+
:keepOpen="true"
|
|
421
|
+
:taggable="true"
|
|
422
|
+
:clearable="true"
|
|
423
|
+
:disabled="field.readOnly"
|
|
424
|
+
:loading="isFieldLoading(field)"
|
|
425
|
+
:filterable="!isFieldAsync(field)"
|
|
426
|
+
@update:modelValue="updateField(field.key, $event)"
|
|
427
|
+
@search="isFieldAsync(field) ? onAsyncSearch(field, $event) : undefined">
|
|
428
|
+
<template
|
|
429
|
+
v-if="$slots['field-' + field.key + '-option']"
|
|
430
|
+
#option="optionProps">
|
|
431
|
+
<slot :name="'field-' + field.key + '-option'" v-bind="optionProps" />
|
|
432
|
+
</template>
|
|
433
|
+
<template v-else #option="optionProps">
|
|
434
|
+
<span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
|
|
435
|
+
</template>
|
|
436
|
+
<template
|
|
437
|
+
v-if="$slots['field-' + field.key + '-selected-option']"
|
|
438
|
+
#selected-option="optionProps">
|
|
439
|
+
<slot :name="'field-' + field.key + '-selected-option'" v-bind="optionProps" />
|
|
440
|
+
</template>
|
|
441
|
+
<template v-else #selected-option="optionProps">
|
|
442
|
+
<span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
|
|
443
|
+
</template>
|
|
444
|
+
</NcSelect>
|
|
445
|
+
<CnFieldHelper
|
|
446
|
+
:id="helpId(field)"
|
|
447
|
+
:text="field.description"
|
|
448
|
+
:more="field.descriptionLong"
|
|
449
|
+
:help="field.help"
|
|
450
|
+
:label="field.label"
|
|
451
|
+
:error="helperError(field)" />
|
|
452
|
+
</div>
|
|
453
|
+
|
|
454
|
+
<!-- Checkbox / Switch (boolean) -->
|
|
455
|
+
<div v-else-if="field.widget === 'checkbox'" class="cn-form-dialog__switch-wrapper">
|
|
456
|
+
<NcCheckboxRadioSwitch
|
|
457
|
+
:modelValue="!!formData[field.key]"
|
|
458
|
+
:disabled="field.readOnly"
|
|
459
|
+
type="switch"
|
|
460
|
+
@update:modelValue="value => updateField(field.key, value)">
|
|
461
|
+
{{ field.label }}{{ requiredSuffix(field) }}
|
|
462
|
+
</NcCheckboxRadioSwitch>
|
|
463
|
+
<CnFieldHelper
|
|
464
|
+
:id="helpId(field)"
|
|
465
|
+
:text="field.description"
|
|
466
|
+
:more="field.descriptionLong"
|
|
467
|
+
:help="field.help"
|
|
468
|
+
:label="field.label"
|
|
469
|
+
:error="helperError(field)" />
|
|
470
|
+
</div>
|
|
471
|
+
|
|
472
|
+
<!-- Date / Datetime (NcTextField's type validator rejects
|
|
473
|
+
'date'/'datetime-local', so use NcDateTimePickerNative) -->
|
|
474
|
+
<div
|
|
475
|
+
v-else-if="field.widget === 'date' || field.widget === 'datetime'"
|
|
476
|
+
class="cn-form-dialog__select-wrapper">
|
|
477
|
+
<label v-if="!showsOuterLabel(field)" :for="'cn-form-' + field.key" class="cn-form-dialog__label">
|
|
478
|
+
{{ field.label }}{{ requiredSuffix(field) }}
|
|
479
|
+
</label>
|
|
480
|
+
<NcDateTimePickerNative
|
|
481
|
+
:id="'cn-form-' + field.key"
|
|
482
|
+
:type="field.widget === 'datetime' ? 'datetime-local' : 'date'"
|
|
483
|
+
:label="field.label"
|
|
484
|
+
:hideLabel="true"
|
|
485
|
+
:modelValue="dateValueFor(field)"
|
|
486
|
+
:disabled="field.readOnly"
|
|
487
|
+
@update:modelValue="date => onDateFieldInput(field, date)" />
|
|
488
|
+
<CnFieldHelper
|
|
489
|
+
:id="helpId(field)"
|
|
490
|
+
:text="field.description"
|
|
491
|
+
:more="field.descriptionLong"
|
|
492
|
+
:help="field.help"
|
|
493
|
+
:label="field.label"
|
|
494
|
+
:error="helperError(field)" />
|
|
495
|
+
</div>
|
|
496
|
+
|
|
497
|
+
<!-- JSON (type: 'object'|'array'|... with widget: 'json'): parses on input, stores parsed value in formData -->
|
|
498
|
+
<div v-else-if="field.widget === 'json'" class="cn-form-dialog__json-wrapper">
|
|
499
|
+
<label v-if="!showsOuterLabel(field)" :for="'cn-form-' + field.key" class="cn-form-dialog__label">
|
|
500
|
+
{{ field.label }}{{ requiredSuffix(field) }}
|
|
501
|
+
</label>
|
|
502
|
+
<CnJsonViewer
|
|
503
|
+
:value="jsonStringFor(field)"
|
|
504
|
+
language="json"
|
|
505
|
+
:readOnly="field.readOnly"
|
|
506
|
+
:errorText="jsonErrors[field.key] || ''"
|
|
507
|
+
@update:value="value => onJsonFieldInput(field, value)" />
|
|
508
|
+
<CnFieldHelper
|
|
509
|
+
:id="helpId(field)"
|
|
510
|
+
:text="field.description"
|
|
511
|
+
:more="field.descriptionLong"
|
|
512
|
+
:help="field.help"
|
|
513
|
+
:label="field.label"
|
|
514
|
+
:error="helperError(field)" />
|
|
515
|
+
</div>
|
|
516
|
+
|
|
517
|
+
<!-- Code (freeform editor, stored as raw string; optional `field.language` chooses highlighting) -->
|
|
518
|
+
<div v-else-if="field.widget === 'code'" class="cn-form-dialog__json-wrapper">
|
|
519
|
+
<label v-if="!showsOuterLabel(field)" :for="'cn-form-' + field.key" class="cn-form-dialog__label">
|
|
520
|
+
{{ field.label }}{{ requiredSuffix(field) }}
|
|
521
|
+
</label>
|
|
522
|
+
<CnJsonViewer
|
|
523
|
+
:value="formData[field.key] != null ? String(formData[field.key]) : ''"
|
|
524
|
+
:language="field.language || 'auto'"
|
|
525
|
+
:readOnly="field.readOnly"
|
|
526
|
+
@update:value="value => updateField(field.key, value)" />
|
|
527
|
+
<CnFieldHelper
|
|
528
|
+
:id="helpId(field)"
|
|
529
|
+
:text="field.description"
|
|
530
|
+
:more="field.descriptionLong"
|
|
531
|
+
:help="field.help"
|
|
532
|
+
:label="field.label"
|
|
533
|
+
:error="helperError(field)" />
|
|
534
|
+
</div>
|
|
535
|
+
|
|
536
|
+
<!-- File (widget: 'file'): one file or several, small ones inline, big ones uploaded after save, optional camera. -->
|
|
537
|
+
<div v-else-if="field.widget === 'file'" class="cn-form-dialog__file-wrapper">
|
|
538
|
+
<CnFileField
|
|
539
|
+
:modelValue="formData[field.key]"
|
|
540
|
+
:label="fieldLabelText(field)"
|
|
541
|
+
:accept="field.file ? field.file.accept : ''"
|
|
542
|
+
:multiple="!!(field.file && field.file.multiple)"
|
|
543
|
+
:capture="field.file ? field.file.capture : ''"
|
|
544
|
+
v-bind="fileLimits(field)"
|
|
545
|
+
:disabled="field.readOnly"
|
|
546
|
+
:helperText="errors[field.key] || ''"
|
|
547
|
+
@update:modelValue="value => updateField(field.key, value)" />
|
|
548
|
+
<CnFieldHelper
|
|
549
|
+
:text="field.description"
|
|
550
|
+
:more="field.descriptionLong"
|
|
551
|
+
:help="field.help"
|
|
552
|
+
:label="field.label" />
|
|
553
|
+
</div>
|
|
554
|
+
|
|
555
|
+
<!-- Duration (widget: 'duration'): number + unit over an ISO 8601 string. -->
|
|
556
|
+
<div v-else-if="field.widget === 'duration'" class="cn-form-dialog__duration-wrapper">
|
|
557
|
+
<CnDurationField
|
|
558
|
+
:modelValue="formData[field.key] != null ? String(formData[field.key]) : null"
|
|
559
|
+
:inputLabel="fieldLabelText(field)"
|
|
560
|
+
:disabled="field.readOnly"
|
|
561
|
+
:error="!!errors[field.key]"
|
|
562
|
+
@update:modelValue="value => updateField(field.key, value)" />
|
|
563
|
+
<CnFieldHelper
|
|
564
|
+
:id="helpId(field)"
|
|
565
|
+
:text="field.description"
|
|
566
|
+
:more="field.descriptionLong"
|
|
567
|
+
:help="field.help"
|
|
568
|
+
:label="field.label"
|
|
569
|
+
:error="helperError(field)" />
|
|
570
|
+
</div>
|
|
571
|
+
|
|
572
|
+
<!-- Child records (widget: 'child-records'): another schema's records as an editable table, saved after the parent. -->
|
|
573
|
+
<div v-else-if="field.widget === 'child-records' && field.childRecords" class="cn-form-dialog__child-records-wrapper">
|
|
574
|
+
<CnChildRecordsField
|
|
575
|
+
:modelValue="Array.isArray(formData[field.key]) ? formData[field.key] : []"
|
|
576
|
+
:config="field.childRecords"
|
|
577
|
+
:register="register"
|
|
578
|
+
:parentId="item ? (item.id || item.uuid || '') : ''"
|
|
579
|
+
:inputLabel="fieldLabelText(field)"
|
|
580
|
+
:disabled="field.readOnly"
|
|
581
|
+
:error="errors[field.key] || ''"
|
|
582
|
+
@update:modelValue="value => updateField(field.key, value)"
|
|
583
|
+
@loaded="rows => { childOriginals[field.key] = rows }"
|
|
584
|
+
@validity="problems => { childProblems[field.key] = problems }" />
|
|
585
|
+
<CnFieldHelper
|
|
586
|
+
:text="field.description"
|
|
587
|
+
:more="field.descriptionLong"
|
|
588
|
+
:help="field.help"
|
|
589
|
+
:label="field.label" />
|
|
590
|
+
</div>
|
|
591
|
+
|
|
592
|
+
<!-- Sub-objects (widget: 'sub-objects'): an array of objects as an editable table. -->
|
|
593
|
+
<div v-else-if="field.widget === 'sub-objects' && field.items" class="cn-form-dialog__sub-objects-wrapper">
|
|
594
|
+
<CnSubObjectsField
|
|
595
|
+
:modelValue="Array.isArray(formData[field.key]) ? formData[field.key] : []"
|
|
596
|
+
:items="field.items"
|
|
597
|
+
:inputLabel="fieldLabelText(field)"
|
|
598
|
+
:disabled="field.readOnly"
|
|
599
|
+
:error="errors[field.key] || ''"
|
|
600
|
+
@update:modelValue="value => updateField(field.key, value)" />
|
|
601
|
+
<CnFieldHelper
|
|
602
|
+
:text="field.description"
|
|
603
|
+
:more="field.descriptionLong"
|
|
604
|
+
:help="field.help"
|
|
605
|
+
:label="field.label" />
|
|
606
|
+
</div>
|
|
607
|
+
|
|
608
|
+
<!-- Registry type-ahead (widget: 'property-source'): CnPropertySourceField;
|
|
609
|
+
a pick can fill empty sibling fields from config.fill. -->
|
|
610
|
+
<div v-else-if="field.widget === 'property-source' && field.propertySource" class="cn-form-dialog__property-source-wrapper">
|
|
611
|
+
<CnPropertySourceField
|
|
612
|
+
:provider="field.propertySource.provider"
|
|
613
|
+
:mode="field.propertySource.mode"
|
|
614
|
+
:inputId="'cn-form-' + field.key"
|
|
615
|
+
:inputLabel="fieldLabelText(field)"
|
|
616
|
+
:modelValue="formData[field.key] != null ? String(formData[field.key]) : ''"
|
|
617
|
+
:disabled="field.readOnly"
|
|
618
|
+
:error="!!errors[field.key]"
|
|
619
|
+
@update:modelValue="value => updateField(field.key, value)"
|
|
620
|
+
@resolved="payload => onPropertySourceResolved(field, payload)" />
|
|
621
|
+
<CnFieldHelper
|
|
622
|
+
:id="helpId(field)"
|
|
623
|
+
:text="field.description"
|
|
624
|
+
:more="field.descriptionLong"
|
|
625
|
+
:help="field.help"
|
|
626
|
+
:label="field.label"
|
|
627
|
+
:error="helperError(field)" />
|
|
628
|
+
</div>
|
|
629
|
+
|
|
630
|
+
<!-- Icon (widget: 'icon'): renders CnIconBrowser, forwarding the field's icon config.
|
|
631
|
+
`searchable` is gone — the browser always searches. -->
|
|
632
|
+
<div v-else-if="field.widget === 'icon'" class="cn-form-dialog__icon-wrapper">
|
|
633
|
+
<label v-if="!showsOuterLabel(field)" :for="'cn-form-' + field.key" class="cn-form-dialog__label">
|
|
634
|
+
{{ field.label }}{{ requiredSuffix(field) }}
|
|
635
|
+
</label>
|
|
636
|
+
<CnIconBrowser
|
|
637
|
+
:value="formData[field.key] != null ? String(formData[field.key]) : null"
|
|
638
|
+
:sources="field.iconSources || ['mdi']"
|
|
639
|
+
:catalogues="field.catalogues || {}"
|
|
640
|
+
:allowCustomSvg="!!field.allowCustomSvg"
|
|
641
|
+
:clearable="!field.required"
|
|
642
|
+
@input="value => updateField(field.key, value)" />
|
|
643
|
+
<CnFieldHelper
|
|
644
|
+
:id="helpId(field)"
|
|
645
|
+
:text="field.description"
|
|
646
|
+
:more="field.descriptionLong"
|
|
647
|
+
:help="field.help"
|
|
648
|
+
:label="field.label"
|
|
649
|
+
:error="helperError(field)" />
|
|
650
|
+
</div>
|
|
651
|
+
|
|
652
|
+
<!-- Fallback: text input -->
|
|
653
|
+
<template v-else>
|
|
654
|
+
<NcTextField
|
|
655
|
+
:id="fieldControlId(field)"
|
|
656
|
+
:label="fieldLabelText(field)"
|
|
657
|
+
:labelOutside="isBoardLook"
|
|
658
|
+
v-bind="fieldAria(field)"
|
|
659
|
+
:modelValue="formData[field.key] != null ? String(formData[field.key]) : ''"
|
|
660
|
+
:error="!!errors[field.key]"
|
|
661
|
+
:disabled="field.readOnly"
|
|
662
|
+
:placeholder="fieldPlaceholder(field)"
|
|
663
|
+
@update:modelValue="value => updateField(field.key, value)" />
|
|
664
|
+
<CnFieldHelper
|
|
665
|
+
:id="helpId(field)"
|
|
666
|
+
:text="field.description"
|
|
667
|
+
:more="field.descriptionLong"
|
|
668
|
+
:help="field.help"
|
|
669
|
+
:label="field.label"
|
|
670
|
+
:error="helperError(field)" />
|
|
671
|
+
</template>
|
|
672
|
+
<small
|
|
673
|
+
v-if="assignedFrom[field.key]"
|
|
674
|
+
class="cn-form-dialog__assigned"
|
|
675
|
+
aria-live="polite"
|
|
676
|
+
data-testid="cn-form-dialog-assigned">
|
|
677
|
+
{{ t('nextcloud-vue', 'Filled in from {field}', { field: assignedFrom[field.key] }) }}
|
|
678
|
+
</small>
|
|
664
679
|
</template>
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
class="cn-form-dialog__assigned"
|
|
668
|
-
aria-live="polite"
|
|
669
|
-
data-testid="cn-form-dialog-assigned">
|
|
670
|
-
{{ t('nextcloud-vue', 'Filled in from {field}', { field: assignedFrom[field.key] }) }}
|
|
671
|
-
</small>
|
|
672
|
-
</template>
|
|
673
|
-
</div>
|
|
680
|
+
</div>
|
|
681
|
+
</template>
|
|
674
682
|
|
|
675
683
|
<!-- The extra questions a chosen case type / product line asks
|
|
676
684
|
are fetched, so say they are coming. Silence here reads as
|
|
@@ -1748,6 +1756,27 @@ export default {
|
|
|
1748
1756
|
.map((field) => this.applyCodePicker(field))
|
|
1749
1757
|
},
|
|
1750
1758
|
|
|
1759
|
+
/**
|
|
1760
|
+
* The section headings, keyed by the field that opens each: a field
|
|
1761
|
+
* whose `section` (set through `fieldOverrides`) differs from the
|
|
1762
|
+
* visible field before it starts a group. Sections follow the field
|
|
1763
|
+
* order; the heading goes through the app's translate function.
|
|
1764
|
+
*
|
|
1765
|
+
* @return {Object<string, string>} Field key to heading.
|
|
1766
|
+
*/
|
|
1767
|
+
sectionStarts() {
|
|
1768
|
+
const starts = {}
|
|
1769
|
+
let current = ''
|
|
1770
|
+
for (const field of this.visibleFields) {
|
|
1771
|
+
const section = typeof field.section === 'string' ? field.section : ''
|
|
1772
|
+
if (section !== '' && section !== current) {
|
|
1773
|
+
starts[field.key] = typeof this.cnTranslate === 'function' ? this.cnTranslate(section) : section
|
|
1774
|
+
}
|
|
1775
|
+
current = section
|
|
1776
|
+
}
|
|
1777
|
+
return starts
|
|
1778
|
+
},
|
|
1779
|
+
|
|
1751
1780
|
/**
|
|
1752
1781
|
* The schema properties that hold the record's tenant, which the form
|
|
1753
1782
|
* hides (see `isTenantProperty`) and fills itself: from the record
|
|
@@ -4945,6 +4974,21 @@ export default {
|
|
|
4945
4974
|
from pushing its column past its share: a grid item's default `auto`
|
|
4946
4975
|
minimum is its content, so without it one long option label widens the
|
|
4947
4976
|
whole column and the two stop being equal. */
|
|
4977
|
+
.cn-form-dialog__section {
|
|
4978
|
+
margin: 12px 0 0;
|
|
4979
|
+
padding-top: 12px;
|
|
4980
|
+
border-top: 1px solid var(--color-border);
|
|
4981
|
+
font-size: 15px;
|
|
4982
|
+
font-weight: 700;
|
|
4983
|
+
}
|
|
4984
|
+
|
|
4985
|
+
/* The first heading opens the form: no rule above it. */
|
|
4986
|
+
.cn-form-dialog__form .cn-form-dialog__section:first-of-type {
|
|
4987
|
+
margin-top: 0;
|
|
4988
|
+
padding-top: 0;
|
|
4989
|
+
border-top: 0;
|
|
4990
|
+
}
|
|
4991
|
+
|
|
4948
4992
|
.cn-form-dialog__form--two-column {
|
|
4949
4993
|
display: grid;
|
|
4950
4994
|
grid-template-columns: 1fr 1fr;
|