1111 class =" question__item__pseudoInput" />
1212 <input
1313 ref =" input"
14+ v-model =" localText"
1415 :aria-label =" ariaLabel"
1516 :placeholder =" placeholder"
16- :value =" answer.text"
1717 class =" question__input"
1818 :class =" { 'question__input--shifted': !isDropdown }"
1919 :maxlength =" maxOptionLength"
2020 type =" text"
2121 dir =" auto"
2222 @input =" debounceOnInput"
2323 @keydown.delete =" deleteEntry"
24- @keydown.enter.prevent =" focusNextInput "
24+ @keydown.enter.prevent =" onEnter "
2525 @compositionstart =" onCompositionStart"
2626 @compositionend =" onCompositionEnd" />
2727
6464 </template >
6565 </NcButton >
6666 </div >
67+ <div v-else class =" option__actions" >
68+ <NcButton
69+ :aria-label =" t('forms', 'Add a new answer option')"
70+ variant =" tertiary"
71+ :disabled =" isIMEComposing || !canCreateLocalAnswer"
72+ @click =" createLocalAnswer" >
73+ <template #icon >
74+ <IconPlus :size =" 20" />
75+ </template >
76+ </NcButton >
77+ </div >
6778 </li >
6879</template >
6980
@@ -98,6 +109,7 @@ export default {
98109 IconCheckboxBlankOutline,
99110 IconDelete,
100111 IconDragIndicator,
112+ IconPlus,
101113 IconRadioboxBlank,
102114 NcActions,
103115 NcActionButton,
@@ -140,10 +152,18 @@ export default {
140152 queue: null ,
141153 debounceOnInput: null ,
142154 isIMEComposing: false ,
155+ localText: this .answer ? .text ?? ' ' ,
143156 }
144157 },
145158
146159 computed: {
160+ canCreateLocalAnswer () {
161+ if (this .answer .local ) {
162+ return !! this .localText ? .trim ()
163+ }
164+ return !! this .answer .text ? .trim ()
165+ },
166+
147167 ariaLabel () {
148168 if (this .answer .local ) {
149169 return t (' forms' , ' Add a new answer option' )
@@ -169,6 +189,17 @@ export default {
169189 },
170190 },
171191
192+ watch: {
193+ // Keep localText in sync when the parent replaces/updates the answer prop
194+ answer: {
195+ handler (newVal ) {
196+ this .localText = newVal? .text ?? ' '
197+ },
198+
199+ deep: true ,
200+ },
201+ },
202+
172203 created () {
173204 this .queue = new PQueue ({ concurrency: 1 })
174205
@@ -196,34 +227,72 @@ export default {
196227 * @param {InputEvent} event The input event that triggered adding a new entry
197228 */
198229 async onInput ({ target, isComposing }) {
230+ if (this .answer .local ) {
231+ this .localText = target .value
232+ return
233+ }
234+
199235 if (! isComposing && ! this .isIMEComposing && target .value !== ' ' ) {
200236 // clone answer
201237 const answer = Object .assign ({}, this .answer )
202238 answer .text = this .$refs .input .value
203239
204- if (this .answer .local ) {
205- // Dispatched for creation. Marked as synced
206- this .$set (this .answer , ' local' , false )
207- const newAnswer = await this .createAnswer (answer)
240+ await this .updateAnswer (answer)
241+
242+ // Forward changes, but use current answer.text to avoid erasing
243+ // any in-between changes while updating the answer
244+ answer .text = this .$refs .input .value
245+ this .$emit (' update:answer' , this .index , answer)
246+ }
247+ },
208248
209- // Forward changes, but use current answer.text to avoid erasing
210- // any in-between changes while creating the answer
211- newAnswer .text = this .$refs .input .value
249+ /**
250+ * Handle Enter key: create local answer or move focus
251+ *
252+ * @param {KeyboardEvent} e the keydown event
253+ */
254+ onEnter (e ) {
255+ if (this .answer .local ) {
256+ this .createLocalAnswer (e)
257+ return
258+ }
259+ this .focusNextInput (e)
260+ },
212261
213- this .$emit (' create-answer' , this .index , newAnswer)
214- } else {
215- await this .updateAnswer (answer)
262+ /**
263+ * Create a new local answer option from the current input
264+ *
265+ * @param {Event} e the triggering event
266+ */
267+ async createLocalAnswer (e ) {
268+ if (this .isIMEComposing || e? .isComposing ) {
269+ return
270+ }
216271
217- // Forward changes, but use current answer.text to avoid erasing
218- // any in-between changes while updating the answer
219- answer .text = this .$refs .input .value
220- this .$emit (' update:answer' , this .index , answer)
221- }
272+ const value = this .localText ?? ' '
273+ if (! value .trim ()) {
274+ return
222275 }
276+
277+ const answer = { ... this .answer }
278+ answer .text = value
279+
280+ // Dispatched for creation. Marked as synced
281+ this .$set (this .answer , ' local' , false )
282+ const newAnswer = await this .createAnswer (answer)
283+
284+ // Forward changes, but use current answer.text to avoid erasing
285+ // any in-between changes while creating the answer
286+ newAnswer .text = this .$refs .input .value
287+ this .localText = ' '
288+
289+ this .$emit (' create-answer' , this .index , newAnswer)
223290 },
224291
225292 /**
226293 * Request a new answer
294+ *
295+ * @param {Event} e the triggering event
227296 */
228297 focusNextInput (e ) {
229298 if (this .isIMEComposing || e? .isComposing ) {
0 commit comments