@conduction/nextcloud-vue 2.117.0 → 2.118.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +2 -1
  2. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +6 -6
  5. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  6. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +2 -1
  7. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
  8. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  9. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +18 -0
  10. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  11. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +14 -8
  12. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  13. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  14. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +11 -1
  15. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  16. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  17. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +9 -4
  18. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  19. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  20. package/dist/esm/components/CnMailTab/CnMailTab.vue2.js +2 -1
  21. package/dist/esm/components/CnMailTab/CnMailTab.vue2.js.map +1 -1
  22. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  23. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +71 -0
  24. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  25. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +25 -9
  26. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  27. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  28. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +11 -2
  29. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  30. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  31. package/dist/esm/components/CnSearchPage/CnSearchPage.vue.js +1 -1
  32. package/dist/esm/components/CnSearchPage/CnSearchPage.vue2.js +161 -5
  33. package/dist/esm/components/CnSearchPage/CnSearchPage.vue2.js.map +1 -1
  34. package/dist/esm/components/CnSearchPage/CnSearchPage.vue3.js +9 -9
  35. package/dist/esm/components/CnSearchPage/CnSearchPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  37. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +35 -5
  38. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  39. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  40. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue.js +9 -0
  41. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue.js.map +1 -0
  42. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js +435 -0
  43. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js.map +1 -0
  44. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js +300 -0
  45. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js.map +1 -0
  46. package/dist/esm/composables/useEndpointSource.js +1 -36
  47. package/dist/esm/composables/useEndpointSource.js.map +1 -1
  48. package/dist/esm/dialogs/CnTransitionInputDialog.vue.js +1 -1
  49. package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js +57 -336
  50. package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js.map +1 -1
  51. package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js +18 -155
  52. package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js.map +1 -1
  53. package/dist/esm/index.js +2 -0
  54. package/dist/esm/index.js.map +1 -1
  55. package/dist/esm/l10n/en.json.js +2 -1
  56. package/dist/esm/l10n/en.json.js.map +1 -1
  57. package/dist/esm/l10n/nl.json.js +2 -1
  58. package/dist/esm/l10n/nl.json.js.map +1 -1
  59. package/dist/esm/library-style.js +1 -1
  60. package/dist/esm/nextcloud-vue.css +308 -229
  61. package/dist/esm/utils/actionsDispatcher.js +3 -2
  62. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  63. package/dist/esm/utils/interpolateUrlTokens.js +43 -0
  64. package/dist/esm/utils/interpolateUrlTokens.js.map +1 -0
  65. package/dist/esm/utils/labelsByField.js +72 -0
  66. package/dist/esm/utils/labelsByField.js.map +1 -0
  67. package/dist/esm/utils/listCreateAction.js +91 -0
  68. package/dist/esm/utils/listCreateAction.js.map +1 -0
  69. package/dist/esm/utils/transitionInputs.js +493 -0
  70. package/dist/esm/utils/transitionInputs.js.map +1 -0
  71. package/dist/esm/utils/visibleWhen.js +9 -2
  72. package/dist/esm/utils/visibleWhen.js.map +1 -1
  73. package/dist/nextcloud-vue.cjs.js +6690 -5404
  74. package/dist/nextcloud-vue.cjs.js.map +2 -2
  75. package/dist/nextcloud-vue.css +308 -229
  76. package/l10n/en.json +2 -1
  77. package/l10n/nl.json +2 -1
  78. package/package.json +1 -1
  79. package/src/components/CnActionsBar/CnActionsBar.vue +6 -6
  80. package/src/components/CnDashboardPage/CnDashboardPage.vue +27 -5
  81. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +11 -1
  82. package/src/components/CnIndexPage/CnIndexPage.vue +9 -4
  83. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +86 -0
  84. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +11 -2
  85. package/src/components/CnSearchPage/CnSearchPage.vue +170 -14
  86. package/src/components/CnTabsWidget/CnTabsWidget.vue +35 -5
  87. package/src/components/CnTransitionInputFields/CnTransitionInputFields.vue +750 -0
  88. package/src/components/CnTransitionInputFields/index.js +11 -0
  89. package/src/components/index.js +1 -0
  90. package/src/composables/useEndpointSource.js +3 -36
  91. package/src/dialogs/CnTransitionInputDialog.vue +73 -465
  92. package/src/index.js +2 -0
  93. package/src/utils/interpolateUrlTokens.js +40 -0
  94. package/src/utils/labelsByField.js +69 -0
  95. package/src/utils/listCreateAction.js +87 -0
  96. package/src/utils/transitionInputs.js +489 -0
  97. package/src/utils/visibleWhen.js +9 -2
@@ -25,112 +25,19 @@
25
25
  data-testid="cn-transition-input-error">
26
26
  {{ error }}
27
27
  </p>
28
- <div v-for="field in fields"
29
- :key="field.key"
30
- class="cn-transition-input__field"
31
- :class="{ 'cn-transition-input__field--refused': isRefused(field.key) }"
32
- :data-testid="`cn-transition-input-${field.key}`">
33
- <!-- An input that declares its choices is picked from them. -->
34
- <NcSelect v-if="field.widget === 'select'"
35
- :modelValue="selectedOption(field)"
36
- :options="field.options"
37
- label="label"
38
- :inputLabel="requiredLabel(field)"
39
- :clearable="!field.required"
40
- :data-testid="`cn-transition-input-select-${field.key}`"
41
- @update:modelValue="setValue(field.key, $event && $event.value !== undefined ? $event.value : '')" />
42
-
43
- <!-- An input that points at another record is picked, never typed. -->
44
- <div v-else-if="field.widget === 'reference'" class="cn-transition-input__reference">
45
- <label class="cn-transition-input__label">{{ requiredLabel(field) }}</label>
46
- <CnResourceSelect
47
- :register="field.referenceRegister"
48
- :schema="String(field.reference.schema)"
49
- :labelField="(field.picker && field.picker.labelField) || field.reference.labelField || 'name'"
50
- :filter="(field.picker && field.picker.filter) || {}"
51
- :exclude="excludedIds(field)"
52
- :allowCreate="false"
53
- :preload="true"
54
- :inputLabel="requiredLabel(field)"
55
- :modelValue="String(values[field.key] ?? '')"
56
- :data-testid="`cn-transition-input-picker-${field.key}`"
57
- @update:modelValue="setValue(field.key, $event)" />
58
- </div>
59
-
60
- <!-- An object input: its sub-properties, one field at a time. -->
61
- <fieldset v-else-if="field.widget === 'object'" class="cn-transition-input__object">
62
- <legend>{{ requiredLabel(field) }}</legend>
63
- <div v-for="sub in field.subFields"
64
- :key="sub.key"
65
- class="cn-transition-input__subfield"
66
- :data-testid="`cn-transition-input-${field.key}-${sub.key}`">
67
- <NcTextArea v-if="sub.widget === 'textarea'"
68
- :modelValue="String(subValue(field.key, sub.key) ?? '')"
69
- :label="subLabel(sub)"
70
- rows="3"
71
- @update:modelValue="setSubValue(field.key, sub.key, $event)" />
72
- <NcCheckboxRadioSwitch v-else-if="sub.widget === 'checkbox'"
73
- :modelValue="subValue(field.key, sub.key) === true"
74
- type="switch"
75
- @update:modelValue="setSubValue(field.key, sub.key, $event === true)">
76
- {{ subLabel(sub) }}
77
- </NcCheckboxRadioSwitch>
78
- <NcTextField v-else
79
- :modelValue="String(subValue(field.key, sub.key) ?? '')"
80
- :type="sub.widget === 'number' ? 'number' : 'text'"
81
- :label="subLabel(sub)"
82
- @update:modelValue="setSubValue(field.key, sub.key, $event)" />
83
- <p v-if="isSubRefused(field.key, sub.key)"
84
- class="cn-transition-input__field-error"
85
- :data-testid="`cn-transition-input-error-${field.key}.${sub.key}`">
86
- {{ t('nextcloud-vue', 'This value was not accepted.') }}
87
- </p>
88
- </div>
89
- </fieldset>
90
-
91
- <NcCheckboxRadioSwitch v-else-if="field.widget === 'checkbox'"
92
- :modelValue="values[field.key] === true"
93
- type="switch"
94
- @update:modelValue="setValue(field.key, $event === true)">
95
- {{ requiredLabel(field) }}
96
- </NcCheckboxRadioSwitch>
97
-
98
- <NcTextArea v-else-if="field.widget === 'textarea'"
99
- :modelValue="String(values[field.key] ?? '')"
100
- :label="requiredLabel(field)"
101
- :helperText="field.description || ''"
102
- rows="4"
103
- @update:modelValue="setValue(field.key, $event)" />
104
-
105
- <NcTextField v-else-if="field.widget === 'number'"
106
- :modelValue="String(values[field.key] ?? '')"
107
- type="number"
108
- :label="requiredLabel(field)"
109
- :helperText="field.description || ''"
110
- @update:modelValue="setValue(field.key, $event)" />
111
-
112
- <NcTextField v-else
113
- :modelValue="String(values[field.key] ?? '')"
114
- :label="requiredLabel(field)"
115
- :helperText="field.description || ''"
116
- @update:modelValue="setValue(field.key, $event)" />
117
-
118
- <p v-if="isRefused(field.key)"
119
- class="cn-transition-input__field-error"
120
- :data-testid="`cn-transition-input-error-${field.key}`">
121
- {{ refusalFor(field.key) }}
122
- </p>
123
- </div>
124
-
125
- <!-- A key the refusal names that this dialog never offered is not a
126
- field anybody can correct here. Saying so beats saying nothing:
127
- the request sent something the transition does not accept. -->
128
- <p v-for="key in unofferedRefusals"
129
- :key="key"
130
- class="cn-transition-input__field-error"
131
- :data-testid="`cn-transition-input-error-${key}`">
132
- {{ undeclaredMessage(key) }}
133
- </p>
28
+ <CnTransitionInputFields
29
+ :inputs="transition.inputs || []"
30
+ :modelValue="values"
31
+ :schema="schema"
32
+ :currentObject="currentObject"
33
+ :register="register"
34
+ :context="context"
35
+ :fieldErrors="fieldErrors"
36
+ :disabled="busy"
37
+ :columns="transition.columns === 2 ? 2 : 1"
38
+ :summary="transition.summary || null"
39
+ :notice="transition.notice || null"
40
+ @update:modelValue="values = $event" />
134
41
  </div>
135
42
 
136
43
  <template #actions>
@@ -159,11 +66,16 @@
159
66
 
160
67
  <script>
161
68
  import { translate as t } from '@nextcloud/l10n'
162
- import { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcSelect, NcTextArea, NcTextField } from '@nextcloud/vue'
69
+ import { NcButton, NcLoadingIcon } from '@nextcloud/vue'
163
70
  import CnDialog from '../components/CnDialog/CnDialog.vue'
164
- import CnResourceSelect from '../components/CnResourceSelect/CnResourceSelect.vue'
71
+ import CnTransitionInputFields from '../components/CnTransitionInputFields/CnTransitionInputFields.vue'
165
72
  import { dialogBoardMixin } from '../mixins/dialogBoard.js'
166
- import { fieldsFromSchema } from '../utils/schema.js'
73
+ import {
74
+ initialInputValues,
75
+ inputFieldsComplete,
76
+ resolveInputFields,
77
+ submittedValue,
78
+ } from '../utils/transitionInputs.js'
167
79
 
168
80
  /**
169
81
  * CnTransitionInputDialog — collects a lifecycle transition's declared
@@ -172,25 +84,27 @@ import { fieldsFromSchema } from '../utils/schema.js'
172
84
  * A schema's `x-openregister-lifecycle.transitions.<action>` block may declare
173
85
  * `inputs: [{ field, required }]`; the transition endpoint then accepts
174
86
  * `{ action, data: { <field>: <value> } }`. This dialog renders one input per
175
- * declared field, disables the confirm button until every `required: true`
176
- * field is filled, and emits the collected values — the parent
177
- * (`CnLifecycleActions`) performs the actual POST. Cancel emits `close`
87
+ * declared field, disables the confirm button until every shown
88
+ * `required: true` field is filled, and emits the collected values — the
89
+ * parent (`CnLifecycleActions`) performs the actual POST. Cancel emits `close`
178
90
  * without any confirm, so no request is made.
179
91
  *
180
- * Field rendering resolves each declared field against the object's JSON
181
- * Schema via `fieldsFromSchema()` when a `schema` is given: the label comes
182
- * from the property `title`, booleans render as a switch, numbers as a number
183
- * field, and long text (`maxLength > 255` or a `textarea`/`markdown` format)
184
- * as a textarea. Anything else — including a field the schema does not
185
- * declare — falls back to a plain labelled text input.
92
+ * The inputs are drawn by `CnTransitionInputFields`, which a host can also
93
+ * use on its own to open an act's inputs in place. Field rendering resolves
94
+ * each declared field against the object's JSON Schema via
95
+ * `fieldsFromSchema()` when a `schema` is given: the label comes from the
96
+ * property `title`, booleans render as a switch, numbers as a number field,
97
+ * and long text (`maxLength > 255` or a `textarea`/`markdown` format) as a
98
+ * textarea. Anything else — including a field the schema does not declare —
99
+ * falls back to a plain labelled text input.
186
100
  *
187
101
  * An input may also say how it is asked for, which matters when the choices
188
102
  * depend on the record rather than on its schema: `label` names the field,
189
- * and `options: [{ value, label }]` turns it into a picker that shows each
190
- * `label` and sends the chosen `value`. An input that declares neither is
191
- * rendered from the schema as before. Deliberately minimal:
192
- * text, textarea, number and checkbox cover transition inputs; this is not a
193
- * form engine (use `CnFormDialog` for full object forms).
103
+ * `options: [{ value, label }]` or `optionsSource` (a path into `context`)
104
+ * turn it into a picker, and `widget` names `text`, `textarea`, `number`,
105
+ * `date` or `template` outright. `other`, `helperFrom`, `preview` and
106
+ * `default` are described in `utils/transitionInputs.js`. The transition may
107
+ * carry `columns`, `summary` and `notice` for the layout around the inputs.
194
108
  *
195
109
  * Lives in its own file under `src/dialogs/` per the modal-isolation rule.
196
110
  *
@@ -207,14 +121,10 @@ export default {
207
121
  name: 'CnTransitionInputDialog',
208
122
 
209
123
  components: {
210
- CnResourceSelect,
211
124
  CnDialog,
125
+ CnTransitionInputFields,
212
126
  NcButton,
213
127
  NcLoadingIcon,
214
- NcTextField,
215
- NcTextArea,
216
- NcCheckboxRadioSwitch,
217
- NcSelect,
218
128
  },
219
129
 
220
130
  mixins: [dialogBoardMixin],
@@ -224,12 +134,14 @@ export default {
224
134
  * The transition being applied. `inputs` is the declared input list from
225
135
  * `x-openregister-lifecycle.transitions.<action>.inputs` (server-derived
226
136
  * or config-declared); `label` doubles as dialog title + confirm label.
137
+ * `columns`, `summary` and `notice` lay out the inputs (see
138
+ * `CnTransitionInputFields`).
227
139
  *
228
140
  * An input may carry a `label` for its field and `options` to pick
229
141
  * from, each `{ value, label }`; the picker shows the label and sends
230
142
  * the value.
231
143
  *
232
- * @type {{action?: string, label?: string, inputs?: Array<{field: string, required?: boolean, label?: string, options?: Array<{value: string, label?: string}>}>}}
144
+ * @type {{action?: string, label?: string, columns?: number, summary?: object, notice?: object, inputs?: Array<{field: string, required?: boolean, label?: string, widget?: string, options?: Array<{value: string, label?: string}>, optionsSource?: string}>}}
233
145
  */
234
146
  transition: {
235
147
  type: Object,
@@ -270,6 +182,16 @@ export default {
270
182
  default: '',
271
183
  },
272
184
 
185
+ /**
186
+ * What an input's `optionsSource` and the summary paths read.
187
+ *
188
+ * @type {object}
189
+ */
190
+ context: {
191
+ type: Object,
192
+ default: () => ({}),
193
+ },
194
+
273
195
  /**
274
196
  * The sentence a refused transition answered with. Present means the
275
197
  * dialog stayed open BECAUSE the attempt was refused, with everything
@@ -311,66 +233,18 @@ export default {
311
233
  data() {
312
234
  return {
313
235
  /** @type {{[key: string]: unknown}} Collected input values, keyed by declared field name. */
314
- values: this.initialValues(),
236
+ values: initialInputValues(this.transition?.inputs, this.schema, this.currentObject),
315
237
  }
316
238
  },
317
239
 
318
240
  computed: {
319
- /** The declared inputs, normalised to `{ field, required }`. */
320
- inputs() {
321
- const declared = Array.isArray(this.transition.inputs) ? this.transition.inputs : []
322
- return declared
323
- .filter((input) => input && typeof input.field === 'string' && input.field !== '')
324
- .map((input) => ({
325
- field: input.field,
326
- required: input.required === true,
327
- picker: input.picker && typeof input.picker === 'object' ? input.picker : null,
328
- fields: Array.isArray(input.fields) ? input.fields.filter((f) => typeof f === 'string') : null,
329
- label: typeof input.label === 'string' && input.label.trim() !== '' ? input.label.trim() : null,
330
- options: this.declaredOptions(input.options),
331
- }))
332
- },
333
-
334
- /**
335
- * One renderable field descriptor per declared input. Schema-declared
336
- * fields come from `fieldsFromSchema()` (label from the property title,
337
- * widget from the type/format); the widget is then clamped to the minimal
338
- * set this dialog renders (text / textarea / number / checkbox). Fields
339
- * the schema does not declare fall back to a plain text input.
340
- *
341
- * An input that declares `options` is a picker whatever the schema
342
- * says, because its choices are the ones this transition offers on
343
- * this record. A declared `label` names the field over the schema's.
344
- */
241
+ /** One field descriptor per declared input (see `resolveInputFields`). */
345
242
  fields() {
346
- const keys = this.inputs.map((input) => input.field)
347
- const resolved = fieldsFromSchema(this.schema, { include: keys, includeReadOnly: true })
348
- return this.inputs.map((input) => {
349
- const field = resolved.find((f) => f.key === input.field)
350
- if (input.options) {
351
- return {
352
- key: input.field,
353
- label: input.label || (field && field.label) || input.field,
354
- description: (field && field.description) || '',
355
- widget: 'select',
356
- options: input.options,
357
- required: input.required,
358
- }
359
- }
360
- const prop = (this.schema && this.schema.properties && this.schema.properties[input.field]) || null
361
- if (prop && prop.type === 'object' && prop.properties && typeof prop.properties === 'object') {
362
- return this.objectField(input, prop)
363
- }
364
- if (!field) {
365
- return { key: input.field, label: input.label || input.field, description: '', widget: 'text', required: input.required }
366
- }
367
- if (field.reference && field.reference.multiple !== true) {
368
- const register = field.reference.register || this.register
369
- if (register !== '') {
370
- return { ...field, label: input.label || field.label, widget: 'reference', referenceRegister: register, picker: input.picker, required: input.required }
371
- }
372
- }
373
- return { ...field, label: input.label || field.label, widget: this.clampWidget(field.widget), required: input.required }
243
+ return resolveInputFields(this.transition.inputs, {
244
+ schema: this.schema,
245
+ register: this.register,
246
+ context: this.context,
247
+ values: this.values,
374
248
  })
375
249
  },
376
250
 
@@ -384,167 +258,27 @@ export default {
384
258
  return this.transition.label || t('nextcloud-vue', 'Confirm')
385
259
  },
386
260
 
387
- /** True when every `required: true` input holds a non-empty value. */
261
+ /** True when every shown `required: true` input holds a value. */
388
262
  canConfirm() {
389
- if (this.busy === true) {
390
- return false
391
- }
392
- return this.fields.every((field) => (field.widget === 'object'
393
- ? field.subFields.every((sub) => !sub.required || this.isSubFilled(field.key, sub))
394
- : (!field.required || this.isFilled(field))))
395
- },
396
-
397
- /** The refused keys this dialog offers no field for. */
398
- unofferedRefusals() {
399
- const offered = this.fields.map((field) => field.key)
400
- return this.refusedKeys.filter((key) => !offered.includes(key))
401
- },
402
-
403
- /** The refusal's field keys, normalised to strings. */
404
- refusedKeys() {
405
- if (!Array.isArray(this.fieldErrors)) {
406
- return []
407
- }
408
- return this.fieldErrors.filter((key) => typeof key === 'string' && key !== '')
263
+ return this.busy !== true && inputFieldsComplete(this.fields, this.values)
409
264
  },
410
265
  },
411
266
 
412
267
  methods: {
413
- // Exposed to the template for the static button labels — the same
414
- // `methods: { t }` pattern the other dialogs in this folder use.
415
268
  t,
416
269
 
417
270
  /**
418
- * Whether the refusal named this field.
419
- *
420
- * @param {string} key The field key.
421
- * @return {boolean}
422
- */
423
- isRefused(key) {
424
- return this.refusedKeys.includes(key)
425
- },
426
-
427
- /**
428
- * What to say beside a refused field.
429
- *
430
- * The KIND is decided here rather than read out of the refusal's
431
- * sentence. A field this dialog offers and the person left empty is a
432
- * missing required input; one it offers that carries a value was
433
- * refused for what is in it. Parsing the server's prose to tell those
434
- * apart would break the first time the sentence is reworded, and a
435
- * reworded sentence is not a contract change.
436
- *
437
- * @param {string} key The field key.
438
- * @return {string}
439
- */
440
- refusalFor(key) {
441
- const field = this.fields.find((candidate) => candidate.key === key)
442
- if (field && !this.isFilled(field)) {
443
- return t('nextcloud-vue', 'This field is required.')
444
- }
445
- return t('nextcloud-vue', 'This value was not accepted.')
446
- },
447
-
448
- /**
449
- * What to say about a refused key this dialog does not offer.
450
- *
451
- * @param {string} key The field key.
452
- * @return {string}
453
- */
454
- undeclaredMessage(key) {
455
- return t('nextcloud-vue', 'This action does not accept the field "{field}".', { field: key })
456
- },
457
-
458
- /**
459
- * The descriptor of an object input: its sub-properties (narrowed to the
460
- * input's `fields` when it names them), each with its own widget.
461
- * A sub-field is required when the input names it in `fields` and the
462
- * sub-schema lists it as required, or when the whole input is required
463
- * and it is the only one asked.
464
- *
465
- * @param {object} input The normalised input.
466
- * @param {object} prop The object property's schema.
467
- * @return {object} The field descriptor.
468
- */
469
- objectField(input, prop) {
470
- const subs = fieldsFromSchema(prop, { includeReadOnly: true, ...(input.fields ? { include: input.fields } : {}) })
471
- const required = Array.isArray(prop.required) ? prop.required : []
472
- const only = subs.length === 1
473
- return {
474
- key: input.field,
475
- label: prop.title || input.field,
476
- description: prop.description || '',
477
- widget: 'object',
478
- required: input.required,
479
- subFields: subs.map((sub) => ({
480
- ...sub,
481
- widget: this.clampWidget(sub.widget),
482
- required: (input.fields ? required.includes(sub.key) : sub.required === true) || (input.required && only),
483
- })),
484
- }
485
- },
486
-
487
- /**
488
- * The choices an input declares, as `[{ value, label }]`, or null when
489
- * it declares none. An option without a value is dropped, because it
490
- * cannot be sent; one without a label shows its value.
491
- *
492
- * @param {unknown} options The input's declared `options`.
493
- * @return {Array<{value: string, label: string}>|null}
494
- */
495
- declaredOptions(options) {
496
- if (!Array.isArray(options)) {
497
- return null
498
- }
499
- const usable = options
500
- .filter((option) => option && typeof option === 'object'
501
- && (typeof option.value === 'string' || typeof option.value === 'number')
502
- && String(option.value).trim() !== '')
503
- .map((option) => ({
504
- value: String(option.value),
505
- label: typeof option.label === 'string' && option.label.trim() !== '' ? option.label.trim() : String(option.value),
506
- }))
507
- return usable.length > 0 ? usable : null
508
- },
509
-
510
- /**
511
- * The option a picker field currently holds, or null.
512
- *
513
- * @param {object} field The select field descriptor.
514
- * @return {{value: string, label: string}|null}
515
- */
516
- selectedOption(field) {
517
- const value = this.values[field.key]
518
- return field.options.find((option) => option.value === String(value ?? '')) || null
519
- },
520
-
521
- /**
522
- * Ids a reference picker must not offer: the current record, when the
523
- * input's `picker.excludeSelf` says so.
524
- *
525
- * @param {object} field The reference field descriptor.
526
- * @return {string[]} The ids to leave out.
527
- */
528
- excludedIds(field) {
529
- if (!field.picker || field.picker.excludeSelf !== true || !this.currentObject) {
530
- return []
531
- }
532
- const self = this.currentObject
533
- return [self.id, self.uuid, self['@self'] && self['@self'].id].filter((id) => id !== undefined && id !== null).map(String)
534
- },
535
-
536
- /**
537
- * Label of a sub-field, with the required marker.
271
+ * Store one input's value.
538
272
  *
539
- * @param {object} sub The sub-field descriptor.
540
- * @return {string}
273
+ * @param {string} key The declared field name.
274
+ * @param {unknown} value The new value.
541
275
  */
542
- subLabel(sub) {
543
- return sub.required ? `${sub.label} *` : sub.label
276
+ setValue(key, value) {
277
+ this.values = { ...this.values, [key]: value }
544
278
  },
545
279
 
546
280
  /**
547
- * A typed sub-value.
281
+ * A typed sub-value of an object input.
548
282
  *
549
283
  * @param {string} key The input key.
550
284
  * @param {string} sub The sub-field key.
@@ -567,110 +301,6 @@ export default {
567
301
  this.setValue(key, { ...group, [sub]: value })
568
302
  },
569
303
 
570
- /**
571
- * Whether a required sub-field holds a value.
572
- *
573
- * @param {string} key The input key.
574
- * @param {object} sub The sub-field descriptor.
575
- * @return {boolean}
576
- */
577
- isSubFilled(key, sub) {
578
- const value = this.subValue(key, sub.key)
579
- return sub.widget === 'checkbox' ? value === true : String(value ?? '').trim() !== ''
580
- },
581
-
582
- /**
583
- * Whether a refusal named `<input>.<sub>`.
584
- *
585
- * @param {string} key The input key.
586
- * @param {string} sub The sub-field key.
587
- * @return {boolean}
588
- */
589
- isSubRefused(key, sub) {
590
- return this.refusedKeys.includes(`${key}.${sub}`)
591
- },
592
-
593
- /** Seed each declared input from its schema default (booleans start false). */
594
- initialValues() {
595
- const values = {}
596
- const declared = Array.isArray(this.transition?.inputs) ? this.transition.inputs : []
597
- const properties = (this.schema && this.schema.properties) || {}
598
- for (const input of declared) {
599
- if (!input || typeof input.field !== 'string' || input.field === '') {
600
- continue
601
- }
602
- const prop = properties[input.field] || {}
603
- if (prop.type === 'object' && prop.properties && typeof prop.properties === 'object') {
604
- // An object input starts from the record's present value, so
605
- // the sub-fields it asks for show what is there.
606
- const present = this.currentObject ? this.currentObject[input.field] : null
607
- values[input.field] = present && typeof present === 'object' ? { ...present } : {}
608
- continue
609
- }
610
- values[input.field] = prop.default !== undefined
611
- ? prop.default
612
- : (prop.type === 'boolean' ? false : '')
613
- }
614
- return values
615
- },
616
-
617
- /**
618
- * Clamp a `fieldsFromSchema()` widget to the minimal set this dialog
619
- * renders. Anything richer (select, date, user picker, …) degrades to a
620
- * plain text input rather than pulling a form engine into the dialog.
621
- *
622
- * @param {string} widget The resolved widget name.
623
- * @return {'text'|'textarea'|'number'|'checkbox'}
624
- */
625
- clampWidget(widget) {
626
- if (widget === 'checkbox' || widget === 'switch') {
627
- return 'checkbox'
628
- }
629
- if (widget === 'textarea') {
630
- return 'textarea'
631
- }
632
- if (widget === 'number') {
633
- return 'number'
634
- }
635
- return 'text'
636
- },
637
-
638
- /**
639
- * The field's visible label, with a `*` marker on required inputs so the
640
- * confirm-gating is visually explained.
641
- *
642
- * @param {object} field The field descriptor.
643
- * @return {string}
644
- */
645
- requiredLabel(field) {
646
- return field.required ? `${field.label} *` : field.label
647
- },
648
-
649
- /**
650
- * Store one input's value.
651
- *
652
- * @param {string} key The declared field name.
653
- * @param {unknown} value The new value.
654
- */
655
- setValue(key, value) {
656
- this.values = { ...this.values, [key]: value }
657
- },
658
-
659
- /**
660
- * Whether a required field counts as filled: a checked switch for
661
- * booleans, a non-blank string otherwise.
662
- *
663
- * @param {object} field The field descriptor.
664
- * @return {boolean}
665
- */
666
- isFilled(field) {
667
- const value = this.values[field.key]
668
- if (field.widget === 'checkbox') {
669
- return value === true
670
- }
671
- return String(value ?? '').trim() !== ''
672
- },
673
-
674
304
  /**
675
305
  * The object an object input sends: the record's current value with the
676
306
  * asked sub-fields merged over it, so a sub-field that was not asked for
@@ -698,18 +328,21 @@ export default {
698
328
  return base
699
329
  },
700
330
 
701
- /** Confirm: emit exactly the declared keys (numbers cast) and let the parent POST. */
331
+ /** Confirm: emit the asked keys (numbers cast, "Other" as '') and let the parent POST. */
702
332
  onConfirm() {
703
333
  if (!this.canConfirm) {
704
334
  return
705
335
  }
706
336
  const data = {}
707
337
  for (const field of this.fields) {
338
+ if (field.hidden || field.widget === 'template') {
339
+ continue
340
+ }
708
341
  if (field.widget === 'object') {
709
342
  data[field.key] = this.mergedObject(field)
710
343
  continue
711
344
  }
712
- let value = this.values[field.key]
345
+ let value = submittedValue(this.values[field.key])
713
346
  if (field.widget === 'number' && String(value ?? '').trim() !== '') {
714
347
  const parsed = Number(value)
715
348
  if (!Number.isNaN(parsed)) {
@@ -720,8 +353,8 @@ export default {
720
353
  }
721
354
  /**
722
355
  * @event confirm Emitted when the user confirms with all required
723
- * inputs filled. Payload holds exactly the declared input keys; the
724
- * parent POSTs `{ action, data }`.
356
+ * inputs filled. Payload holds the declared input keys that were
357
+ * asked; the parent POSTs `{ action, data }`.
725
358
  * @type {{[key: string]: unknown}}
726
359
  */
727
360
  this.$emit('confirm', data)
@@ -742,29 +375,4 @@ export default {
742
375
  margin: 0;
743
376
  color: var(--color-error-text, var(--color-error));
744
377
  }
745
-
746
- .cn-transition-input__field-error {
747
- margin: 4px 0 0;
748
- font-size: 0.9em;
749
- color: var(--color-error-text, var(--color-error));
750
- }
751
-
752
- .cn-transition-input__object {
753
- border: 1px solid var(--color-border);
754
- border-radius: var(--border-radius);
755
- padding: 8px 12px;
756
- display: flex;
757
- flex-direction: column;
758
- gap: 8px;
759
- }
760
-
761
- .cn-transition-input__label {
762
- display: block;
763
- margin-bottom: 4px;
764
- }
765
-
766
- .cn-transition-input__field--refused {
767
- border-inline-start: 2px solid var(--color-error);
768
- padding-inline-start: 8px;
769
- }
770
378
  </style>
package/src/index.js CHANGED
@@ -350,6 +350,7 @@ export {
350
350
  CnTimelineView,
351
351
  CnTimelineWidget,
352
352
  CnToggleTableWidget,
353
+ CnTransitionInputFields,
353
354
  CnTranslatedBadge,
354
355
  CnTreeView,
355
356
  CnUnreadMarker,
@@ -579,6 +580,7 @@ export { currentTheme, useCurrentTheme } from './composables/useCurrentTheme.js'
579
580
  export { ROADMAP_LABEL_BLOCKLIST } from './utils/roadmapLabelBlocklist.js'
580
581
  // The words a person reads for a notification rule key (never the raw key).
581
582
  export { humaniseRuleKey, notificationRuleLabel } from './utils/notificationRuleLabel.js'
583
+ export { readTransitionInputs } from './utils/transitionInputs.js'
582
584
  // An identifier-shaped key made readable (`shortDescription` → "Short description").
583
585
  export { humaniseKey } from './utils/humaniseKey.js'
584
586