@conduction/nextcloud-vue 2.56.0-beta.45 → 2.56.0-beta.47
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 +10 -39
- 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 +39 -10
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +418 -399
- 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/CnLinkCardsPage/CnLinkCardsPage.vue.js +1 -1
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js +156 -3
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js +168 -52
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue.js +9 -0
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue.js.map +1 -0
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue2.js +83 -0
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue2.js.map +1 -0
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue3.js +119 -0
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue3.js.map +1 -0
- 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/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +33 -2
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTextWidget/CnTextWidget.vue.js +1 -1
- package/dist/esm/components/CnTextWidget/CnTextWidget.vue2.js +63 -4
- package/dist/esm/components/CnTextWidget/CnTextWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTextWidget/CnTextWidget.vue3.js +19 -2
- package/dist/esm/components/CnTextWidget/CnTextWidget.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 +45 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +44 -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 +1182 -328
- package/dist/esm/utils/boardDate.js +35 -1
- package/dist/esm/utils/boardDate.js.map +1 -1
- package/dist/esm/utils/featureCatalog.js +135 -0
- package/dist/esm/utils/featureCatalog.js.map +1 -0
- package/dist/esm/utils/fillTemplate.js +84 -4
- 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 +15618 -13097
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1182 -328
- package/l10n/en.json +45 -1
- package/l10n/nl.json +44 -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 +10 -39
- 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.md +1 -0
- package/src/components/CnFormDialog/CnFormDialog.vue +619 -562
- package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +338 -5
- package/src/components/CnModuleCard/CnModuleCard.vue +258 -0
- 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/CnTabsWidget/CnTabsWidget.vue +33 -2
- package/src/components/CnTextWidget/CnTextWidget.vue +89 -4
- 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 +67 -4
- package/src/utils/boardDate.js +34 -0
- package/src/utils/featureCatalog.js +132 -0
- package/src/utils/fillTemplate.js +83 -3
- 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)"
|
|
175
|
+
:modelValue="codedFallbackText(field)"
|
|
282
176
|
:disabled="field.readOnly"
|
|
283
|
-
|
|
284
|
-
@update:modelValue="
|
|
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]"
|
|
451
|
-
:disabled="field.readOnly"
|
|
452
|
-
type="switch"
|
|
453
|
-
@update:modelValue="value => updateField(field.key, value)">
|
|
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)" />
|
|
508
|
-
</div>
|
|
509
|
-
|
|
510
|
-
<!-- Code (freeform editor, stored as raw string; optional `field.language` chooses highlighting) -->
|
|
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)" />
|
|
177
|
+
:helperText="t('nextcloud-vue', 'The list of choices could not be loaded. Enter the value by hand.')"
|
|
178
|
+
@update:modelValue="onCodedFallbackInput(field, $event)" />
|
|
527
179
|
</div>
|
|
528
180
|
|
|
529
|
-
<!--
|
|
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
|
|
@@ -758,6 +766,11 @@
|
|
|
758
766
|
@click="executeConfirm">
|
|
759
767
|
<template #icon>
|
|
760
768
|
<NcLoadingIcon v-if="loading" :size="20" />
|
|
769
|
+
<CnIcon
|
|
770
|
+
v-else-if="confirmIcon"
|
|
771
|
+
:name="confirmIcon"
|
|
772
|
+
:size="20"
|
|
773
|
+
data-testid="cn-form-dialog-confirm-icon" />
|
|
761
774
|
<Plus v-else-if="isCreateMode" :size="20" />
|
|
762
775
|
<ContentSaveOutline v-else :size="20" />
|
|
763
776
|
</template>
|
|
@@ -781,6 +794,7 @@ import CnDurationField from '../CnDurationField/CnDurationField.vue'
|
|
|
781
794
|
import CnFieldHelper from '../CnFieldHelper/CnFieldHelper.vue'
|
|
782
795
|
import CnFileField from '../CnFileField/CnFileField.vue'
|
|
783
796
|
import CnFormField from '../CnFormField/CnFormField.vue'
|
|
797
|
+
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
784
798
|
import CnIconBrowser from '../CnIconBrowser/CnIconBrowser.vue'
|
|
785
799
|
import CnJsonViewer from '../CnJsonViewer/CnJsonViewer.vue'
|
|
786
800
|
import CnPropertySourceField from '../CnPropertySourceField/CnPropertySourceField.vue'
|
|
@@ -1023,6 +1037,7 @@ export default {
|
|
|
1023
1037
|
CnSubObjectsField,
|
|
1024
1038
|
CnChildRecordsField,
|
|
1025
1039
|
CnDialog,
|
|
1040
|
+
CnIcon,
|
|
1026
1041
|
CnFormField,
|
|
1027
1042
|
NcButton,
|
|
1028
1043
|
NcNoteCard,
|
|
@@ -1303,6 +1318,12 @@ export default {
|
|
|
1303
1318
|
default: '',
|
|
1304
1319
|
},
|
|
1305
1320
|
|
|
1321
|
+
/** MDI icon name on the Confirm button (`SwapHorizontal`, `Archive`, `Send`, `Gavel`). Defaults to Plus when creating, else a save icon. */
|
|
1322
|
+
confirmIcon: {
|
|
1323
|
+
type: String,
|
|
1324
|
+
default: '',
|
|
1325
|
+
},
|
|
1326
|
+
|
|
1306
1327
|
/** Keep the Confirm button disabled regardless of the form state, for a host that knows the form cannot be completed. */
|
|
1307
1328
|
confirmDisabled: {
|
|
1308
1329
|
type: Boolean,
|
|
@@ -1735,6 +1756,27 @@ export default {
|
|
|
1735
1756
|
.map((field) => this.applyCodePicker(field))
|
|
1736
1757
|
},
|
|
1737
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
|
+
|
|
1738
1780
|
/**
|
|
1739
1781
|
* The schema properties that hold the record's tenant, which the form
|
|
1740
1782
|
* hides (see `isTenantProperty`) and fills itself: from the record
|
|
@@ -4932,6 +4974,21 @@ export default {
|
|
|
4932
4974
|
from pushing its column past its share: a grid item's default `auto`
|
|
4933
4975
|
minimum is its content, so without it one long option label widens the
|
|
4934
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
|
+
|
|
4935
4992
|
.cn-form-dialog__form--two-column {
|
|
4936
4993
|
display: grid;
|
|
4937
4994
|
grid-template-columns: 1fr 1fr;
|