@conform-to/react 1.16.0 → 1.17.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.
@@ -1,5 +1,5 @@
1
1
  import { objectSpread2 as _objectSpread2 } from '../_virtual/_rollupPluginBabelHelpers.mjs';
2
- import { getPathSegments, getRelativePath, isPlainObject, appendPathSegment } from '@conform-to/dom/future';
2
+ import { getPathSegments, getRelativePath, isPlainObject, appendPathSegment, getValueAtPath } from '@conform-to/dom/future';
3
3
  import { isOptional, isUndefined, isNullable, appendUniqueItem, merge, updateValueAtPath, isString, getArrayAtPath, createPathIndexUpdater, compactMap, generateUniqueKey, isNumber, transformKeys } from './util.mjs';
4
4
  import { getDefaultListKey } from './state.mjs';
5
5
 
@@ -42,19 +42,35 @@ function deserializeIntent(value) {
42
42
  * Applies intent transformation to submission payload.
43
43
  * Returns modified payload or null for reset intent.
44
44
  */
45
- function applyIntent(submission, options) {
46
- var _options$handlers, _handler$validatePayl, _handler$validatePayl2;
45
+ function resolveIntent(submission, options) {
46
+ var _options$handlers, _handler$validate, _handler$validate2;
47
47
  if (!submission.intent) {
48
48
  return submission.payload;
49
49
  }
50
50
  var intent = deserializeIntent(submission.intent);
51
- var handlers = (_options$handlers = options === null || options === void 0 ? void 0 : options.handlers) !== null && _options$handlers !== void 0 ? _options$handlers : actionHandlers;
51
+ var handlers = (_options$handlers = options === null || options === void 0 ? void 0 : options.handlers) !== null && _options$handlers !== void 0 ? _options$handlers : intentHandlers;
52
52
  var handler = handlers[intent.type];
53
- if (handler && handler.onApply && ((_handler$validatePayl = (_handler$validatePayl2 = handler.validatePayload) === null || _handler$validatePayl2 === void 0 ? void 0 : _handler$validatePayl2.call(handler, intent.payload)) !== null && _handler$validatePayl !== void 0 ? _handler$validatePayl : true)) {
54
- return handler.onApply(submission.payload, intent.payload);
53
+ if (handler !== null && handler !== void 0 && handler.resolve && ((_handler$validate = (_handler$validate2 = handler.validate) === null || _handler$validate2 === void 0 ? void 0 : _handler$validate2.call(handler, intent.payload)) !== null && _handler$validate !== void 0 ? _handler$validate : true)) {
54
+ return handler.resolve(submission.payload, intent.payload);
55
55
  }
56
56
  return submission.payload;
57
57
  }
58
+
59
+ /**
60
+ * Resolves an intent after validation by calling the handler's onResolve.
61
+ * Mutates the result with updated value/error and returns whether the intent was cancelled.
62
+ */
63
+ function applyIntent(result, intent, options) {
64
+ if (intent) {
65
+ var _options$handlers2, _handler$validate3, _handler$validate4;
66
+ var handlers = (_options$handlers2 = options === null || options === void 0 ? void 0 : options.handlers) !== null && _options$handlers2 !== void 0 ? _options$handlers2 : intentHandlers;
67
+ var handler = handlers[intent.type];
68
+ if (handler !== null && handler !== void 0 && handler.apply && ((_handler$validate3 = (_handler$validate4 = handler.validate) === null || _handler$validate4 === void 0 ? void 0 : _handler$validate4.call(handler, intent.payload)) !== null && _handler$validate3 !== void 0 ? _handler$validate3 : true)) {
69
+ return handler.apply(result, intent.payload);
70
+ }
71
+ }
72
+ return result;
73
+ }
58
74
  function insertItem(list, item, index) {
59
75
  list.splice(index, 0, item);
60
76
  }
@@ -86,36 +102,34 @@ function updateListKeys() {
86
102
  * - update: updates specific field values
87
103
  * - insert/remove/reorder: manages array field operations
88
104
  */
89
- var actionHandlers = {
105
+ var intentHandlers = {
90
106
  reset: {
91
- validatePayload(options) {
107
+ validate(options) {
92
108
  return isOptional(options, isPlainObject) && (isUndefined(options === null || options === void 0 ? void 0 : options.defaultValue) || isNullable(options === null || options === void 0 ? void 0 : options.defaultValue, isPlainObject));
93
109
  },
94
- onApply(_, options) {
110
+ resolve(_, options) {
95
111
  if ((options === null || options === void 0 ? void 0 : options.defaultValue) === null) {
96
112
  return {};
97
113
  }
98
114
  return options === null || options === void 0 ? void 0 : options.defaultValue;
99
115
  },
100
- onUpdate(_, _ref) {
101
- var {
102
- targetValue,
103
- ctx
104
- } = _ref;
105
- return ctx.reset(targetValue);
116
+ apply(result) {
117
+ return _objectSpread2(_objectSpread2({}, result), {}, {
118
+ reset: true
119
+ });
106
120
  }
107
121
  },
108
122
  validate: {
109
- validatePayload(name) {
123
+ validate(name) {
110
124
  return isOptional(name, isString);
111
125
  },
112
- onUpdate(state, _ref2) {
126
+ update(state, _ref) {
113
127
  var _intent$payload;
114
128
  var {
115
129
  submission,
116
130
  intent,
117
131
  error
118
- } = _ref2;
132
+ } = _ref;
119
133
  var name = (_intent$payload = intent.payload) !== null && _intent$payload !== void 0 ? _intent$payload : '';
120
134
  var basePath = getPathSegments(name);
121
135
  var allFields = error ?
@@ -134,20 +148,20 @@ var actionHandlers = {
134
148
  }
135
149
  },
136
150
  update: {
137
- validatePayload(options) {
151
+ validate(options) {
138
152
  return isPlainObject(options) && isOptional(options.name, isString) && isOptional(options.index, isNumber) && !isUndefined(options.value);
139
153
  },
140
- onApply(value, options) {
154
+ resolve(value, options) {
141
155
  var _options$value;
142
156
  var name = appendPathSegment(options.name, options.index);
143
157
  return updateValueAtPath(value, name, (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : name === '' ? {} : null);
144
158
  },
145
- onUpdate(state, _ref3) {
159
+ update(state, _ref2) {
146
160
  var {
147
161
  type,
148
162
  submission,
149
163
  intent
150
- } = _ref3;
164
+ } = _ref2;
151
165
  if (type === 'server') {
152
166
  return state;
153
167
  }
@@ -174,63 +188,141 @@ var actionHandlers = {
174
188
  }
175
189
  },
176
190
  insert: {
177
- validatePayload(options) {
178
- return isPlainObject(options) && isString(options.name) && isOptional(options.index, isNumber);
191
+ validate(options) {
192
+ return isPlainObject(options) && isString(options.name) && isOptional(options.index, isNumber) && isOptional(options.from, isString) && isOptional(options.onInvalid, mode => mode === 'revert');
179
193
  },
180
- onApply(value, options) {
194
+ resolve(value, options) {
181
195
  var _options$index;
182
- var list = Array.from(getArrayAtPath(value, options.name));
183
- insertItem(list, options.defaultValue, (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : list.length);
184
- return updateValueAtPath(value, options.name, list);
196
+ var result = value;
197
+ var itemValue = options.defaultValue;
198
+ if (options.from !== undefined) {
199
+ itemValue = getValueAtPath(result, options.from);
200
+ result = updateValueAtPath(result, options.from, '');
201
+ }
202
+ var list = Array.from(getArrayAtPath(result, options.name));
203
+ insertItem(list, itemValue, (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : list.length);
204
+ return updateValueAtPath(result, options.name, list);
185
205
  },
186
- onUpdate(state, _ref4) {
206
+ apply(result, options) {
207
+ var _result$error;
208
+ // Warn if validation result is not yet available
209
+ if (typeof result.error === 'undefined' && (options.onInvalid || options.from)) {
210
+ // eslint-disable-next-line no-console
211
+ console.warn('intent.insert() with `onInvalid` or `from` requires the validation result to be available synchronously. ' + 'These options are ignored because the error is not yet known.');
212
+ return result;
213
+ }
214
+ var arrayErrors = (_result$error = result.error) === null || _result$error === void 0 ? void 0 : _result$error.fieldErrors[options.name];
215
+ if (options.onInvalid === 'revert' && arrayErrors !== null && arrayErrors !== void 0 && arrayErrors.length) {
216
+ return _objectSpread2(_objectSpread2({}, result), {}, {
217
+ targetValue: undefined
218
+ });
219
+ }
220
+ if (options.from !== undefined) {
221
+ var _options$index2, _result$error2;
222
+ var index = (_options$index2 = options.index) !== null && _options$index2 !== void 0 ? _options$index2 : getArrayAtPath(result.submission.payload, options.name).length;
223
+ var insertedItemPath = appendPathSegment(options.name, index);
224
+ var insertedItemErrors = (_result$error2 = result.error) === null || _result$error2 === void 0 ? void 0 : _result$error2.fieldErrors[insertedItemPath];
225
+ if (insertedItemErrors !== null && insertedItemErrors !== void 0 && insertedItemErrors.length) {
226
+ var _result$error$fieldEr, _result$error3, _result$error$formErr, _result$error4, _result$error5;
227
+ var fromErrors = (_result$error$fieldEr = (_result$error3 = result.error) === null || _result$error3 === void 0 ? void 0 : _result$error3.fieldErrors[options.from]) !== null && _result$error$fieldEr !== void 0 ? _result$error$fieldEr : [];
228
+ return _objectSpread2(_objectSpread2({}, result), {}, {
229
+ targetValue: undefined,
230
+ error: {
231
+ formErrors: (_result$error$formErr = (_result$error4 = result.error) === null || _result$error4 === void 0 ? void 0 : _result$error4.formErrors) !== null && _result$error$formErr !== void 0 ? _result$error$formErr : [],
232
+ fieldErrors: _objectSpread2(_objectSpread2({}, (_result$error5 = result.error) === null || _result$error5 === void 0 ? void 0 : _result$error5.fieldErrors), {}, {
233
+ [options.from]: [...fromErrors, ...insertedItemErrors],
234
+ [insertedItemPath]: []
235
+ })
236
+ }
237
+ });
238
+ }
239
+ }
240
+ return result;
241
+ },
242
+ update(state, _ref3) {
187
243
  var _intent$payload$index;
188
244
  var {
189
245
  type,
190
246
  submission,
191
- intent
192
- } = _ref4;
247
+ intent,
248
+ ctx
249
+ } = _ref3;
193
250
  if (type === 'server') {
194
251
  return state;
195
252
  }
196
- var currentValue = submission.payload;
197
- var list = getArrayAtPath(currentValue, intent.payload.name);
198
- var index = (_intent$payload$index = intent.payload.index) !== null && _intent$payload$index !== void 0 ? _intent$payload$index : list.length;
253
+ var from = intent.payload.from;
254
+ var index = (_intent$payload$index = intent.payload.index) !== null && _intent$payload$index !== void 0 ? _intent$payload$index : getArrayAtPath(submission.payload, intent.payload.name).length;
199
255
  var updateListIndex = createPathIndexUpdater(intent.payload.name, currentIndex => index <= currentIndex ? currentIndex + 1 : currentIndex);
200
- var touchedFields = appendUniqueItem(compactMap(state.touchedFields, updateListIndex), intent.payload.name);
201
- var keys = state.listKeys;
256
+ var touchedFields = state.touchedFields;
257
+ var listKeys = state.listKeys;
258
+ if (!ctx.cancelled) {
259
+ touchedFields = compactMap(state.touchedFields, updateListIndex);
202
260
 
203
- // Update the keys only for client updates to avoid double updates if there is no client validation
204
- if (type === 'client') {
205
- var _state$listKeys$inten;
206
- var listKeys = Array.from((_state$listKeys$inten = state.listKeys[intent.payload.name]) !== null && _state$listKeys$inten !== void 0 ? _state$listKeys$inten : getDefaultListKey(state.resetKey, currentValue, intent.payload.name));
207
- insertItem(listKeys, generateUniqueKey(), index);
208
- keys = _objectSpread2(_objectSpread2({}, updateListKeys(state.listKeys, appendPathSegment(intent.payload.name, index), updateListIndex)), {}, {
209
- // Update existing list keys
210
- [intent.payload.name]: listKeys
211
- });
261
+ // Update the keys only for client updates to avoid double updates if there is no client validation
262
+ if (type === 'client') {
263
+ var _state$listKeys$inten;
264
+ var selectedListKeys = Array.from((_state$listKeys$inten = state.listKeys[intent.payload.name]) !== null && _state$listKeys$inten !== void 0 ? _state$listKeys$inten : getDefaultListKey(state.resetKey, submission.payload, intent.payload.name));
265
+ insertItem(selectedListKeys, generateUniqueKey(), index);
266
+ listKeys = _objectSpread2(_objectSpread2({}, updateListKeys(state.listKeys, appendPathSegment(intent.payload.name, index), updateListIndex)), {}, {
267
+ // Update existing list keys
268
+ [intent.payload.name]: selectedListKeys
269
+ });
270
+ }
271
+ }
272
+ touchedFields = appendUniqueItem(touchedFields, intent.payload.name);
273
+ if (from !== undefined) {
274
+ touchedFields = appendUniqueItem(touchedFields, from);
212
275
  }
213
276
  return _objectSpread2(_objectSpread2({}, state), {}, {
214
- listKeys: keys,
277
+ listKeys,
215
278
  touchedFields
216
279
  });
217
280
  }
218
281
  },
219
282
  remove: {
220
- validatePayload(options) {
221
- return isPlainObject(options) && isString(options.name) && isNumber(options.index);
283
+ validate(options) {
284
+ return isPlainObject(options) && isString(options.name) && isNumber(options.index) && isOptional(options.onInvalid, v => v === 'revert' || v === 'insert');
222
285
  },
223
- onApply(value, options) {
286
+ resolve(value, options) {
224
287
  var list = Array.from(getArrayAtPath(value, options.name));
225
288
  removeItem(list, options.index);
226
289
  return updateValueAtPath(value, options.name, list);
227
290
  },
228
- onUpdate(state, _ref5) {
291
+ apply(result, options) {
292
+ var _result$error6;
293
+ // Warn if validation result is not yet available
294
+ if (typeof result.error === 'undefined' && options.onInvalid) {
295
+ if (process.env.NODE_ENV !== 'production') {
296
+ // eslint-disable-next-line no-console
297
+ console.warn('intent.remove() with `onInvalid` requires the validation result to be available synchronously. ' + 'This option is ignored because the error is not yet known.');
298
+ }
299
+ return result;
300
+ }
301
+ if (result.targetValue && (_result$error6 = result.error) !== null && _result$error6 !== void 0 && _result$error6.fieldErrors[options.name]) {
302
+ switch (options.onInvalid) {
303
+ case 'revert':
304
+ return _objectSpread2(_objectSpread2({}, result), {}, {
305
+ targetValue: undefined
306
+ });
307
+ case 'insert':
308
+ {
309
+ var list = Array.from(getArrayAtPath(result.targetValue, options.name));
310
+ insertItem(list, options.defaultValue, list.length);
311
+ return _objectSpread2(_objectSpread2({}, result), {}, {
312
+ targetValue: updateValueAtPath(result.targetValue, options.name, list)
313
+ });
314
+ }
315
+ }
316
+ }
317
+ return result;
318
+ },
319
+ update(state, _ref4) {
229
320
  var {
230
321
  type,
231
322
  submission,
232
- intent
233
- } = _ref5;
323
+ intent,
324
+ ctx
325
+ } = _ref4;
234
326
  if (type === 'server') {
235
327
  return state;
236
328
  }
@@ -241,40 +333,54 @@ var actionHandlers = {
241
333
  }
242
334
  return intent.payload.index < currentIndex ? currentIndex - 1 : currentIndex;
243
335
  });
244
- var touchedFields = appendUniqueItem(compactMap(state.touchedFields, updateListIndex), intent.payload.name);
245
- var keys = state.listKeys;
336
+ var touchedFields = state.touchedFields;
337
+ var listKeys = state.listKeys;
246
338
 
247
- // Update the keys only for client updates to avoid double updates if there is no client validation
248
- if (type === 'client') {
249
- var _state$listKeys$inten2;
250
- var listKeys = Array.from((_state$listKeys$inten2 = state.listKeys[intent.payload.name]) !== null && _state$listKeys$inten2 !== void 0 ? _state$listKeys$inten2 : getDefaultListKey(state.resetKey, currentValue, intent.payload.name));
251
- removeItem(listKeys, intent.payload.index);
252
- keys = _objectSpread2(_objectSpread2({}, updateListKeys(state.listKeys, appendPathSegment(intent.payload.name, intent.payload.index), updateListIndex)), {}, {
253
- // Update existing list keys
254
- [intent.payload.name]: listKeys
255
- });
339
+ // If onInvalid is 'insert', we still remove the item and then insert a new item at the end
340
+ if (!ctx.cancelled || intent.payload.onInvalid === 'insert') {
341
+ touchedFields = compactMap(touchedFields, updateListIndex);
342
+
343
+ // Update the keys only for client updates to avoid double updates if there is no client validation
344
+ if (type === 'client') {
345
+ var _state$listKeys$inten2;
346
+ var selectedListKeys = Array.from((_state$listKeys$inten2 = state.listKeys[intent.payload.name]) !== null && _state$listKeys$inten2 !== void 0 ? _state$listKeys$inten2 : getDefaultListKey(state.resetKey, currentValue, intent.payload.name));
347
+ removeItem(selectedListKeys, intent.payload.index);
348
+ listKeys = _objectSpread2(_objectSpread2({}, updateListKeys(state.listKeys, appendPathSegment(intent.payload.name, intent.payload.index), updateListIndex)), {}, {
349
+ // Update existing list keys
350
+ [intent.payload.name]: selectedListKeys
351
+ });
352
+ if (ctx.cancelled) {
353
+ var index = selectedListKeys.length;
354
+ insertItem(selectedListKeys, generateUniqueKey(), index);
355
+ listKeys = _objectSpread2(_objectSpread2({}, updateListKeys(state.listKeys, appendPathSegment(intent.payload.name, index), updateListIndex)), {}, {
356
+ // Update existing list keys
357
+ [intent.payload.name]: selectedListKeys
358
+ });
359
+ }
360
+ }
256
361
  }
362
+ touchedFields = appendUniqueItem(touchedFields, intent.payload.name);
257
363
  return _objectSpread2(_objectSpread2({}, state), {}, {
258
- listKeys: keys,
364
+ listKeys: listKeys,
259
365
  touchedFields
260
366
  });
261
367
  }
262
368
  },
263
369
  reorder: {
264
- validatePayload(options) {
370
+ validate(options) {
265
371
  return isPlainObject(options) && isString(options.name) && isNumber(options.from) && isNumber(options.to);
266
372
  },
267
- onApply(value, options) {
373
+ resolve(value, options) {
268
374
  var list = Array.from(getArrayAtPath(value, options.name));
269
375
  reorderItems(list, options.from, options.to);
270
376
  return updateValueAtPath(value, options.name, list);
271
377
  },
272
- onUpdate(state, _ref6) {
378
+ update(state, _ref5) {
273
379
  var {
274
380
  type,
275
381
  submission,
276
382
  intent
277
- } = _ref6;
383
+ } = _ref5;
278
384
  if (type === 'server') {
279
385
  return state;
280
386
  }
@@ -312,4 +418,4 @@ var actionHandlers = {
312
418
  }
313
419
  };
314
420
 
315
- export { actionHandlers, applyIntent, deserializeIntent, insertItem, removeItem, reorderItems, serializeIntent, updateListKeys };
421
+ export { applyIntent, deserializeIntent, insertItem, intentHandlers, removeItem, reorderItems, resolveIntent, serializeIntent, updateListKeys };
@@ -1,5 +1,5 @@
1
1
  import { type FieldName, type ValidationAttributes, type Serialize } from '@conform-to/dom/future';
2
- import type { FieldMetadata, Fieldset, FormContext, FormMetadata, FormState, FormAction, UnknownIntent, ActionHandler, BaseFieldMetadata, BaseFormMetadata, DefineConditionalField } from './types';
2
+ import type { FieldMetadata, Fieldset, FormContext, FormMetadata, FormState, FormAction, UnknownIntent, IntentHandler, BaseFieldMetadata, BaseFormMetadata, DefineConditionalField } from './types';
3
3
  export declare function initializeState<ErrorShape>(options?: {
4
4
  defaultValue?: Record<string, unknown> | null | undefined;
5
5
  resetKey?: string | undefined;
@@ -11,7 +11,8 @@ export declare function initializeState<ErrorShape>(options?: {
11
11
  * - Initialize: set initial server value
12
12
  */
13
13
  export declare function updateState<ErrorShape>(state: FormState<ErrorShape>, action: FormAction<ErrorShape, UnknownIntent | null, {
14
- handlers: Record<string, ActionHandler>;
14
+ handlers: Record<string, IntentHandler>;
15
+ cancelled: boolean;
15
16
  reset: (defaultValue?: Record<string, unknown> | null | undefined) => FormState<ErrorShape>;
16
17
  }>): FormState<ErrorShape>;
17
18
  /**
@@ -57,12 +57,13 @@ function updateState(state, action) {
57
57
  type: 'validate'
58
58
  };
59
59
  var handler = (_action$ctx$handlers = action.ctx.handlers) === null || _action$ctx$handlers === void 0 ? void 0 : _action$ctx$handlers[intent.type];
60
- if (typeof (handler === null || handler === void 0 ? void 0 : handler.onUpdate) === 'function') {
61
- var _handler$validatePayl, _handler$validatePayl2;
62
- if ((_handler$validatePayl = (_handler$validatePayl2 = handler.validatePayload) === null || _handler$validatePayl2 === void 0 ? void 0 : _handler$validatePayl2.call(handler, intent.payload)) !== null && _handler$validatePayl !== void 0 ? _handler$validatePayl : true) {
63
- return handler.onUpdate(state, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, action), {}, {
60
+ if (typeof (handler === null || handler === void 0 ? void 0 : handler.update) === 'function') {
61
+ var _handler$validate, _handler$validate2;
62
+ if ((_handler$validate = (_handler$validate2 = handler.validate) === null || _handler$validate2 === void 0 ? void 0 : _handler$validate2.call(handler, intent.payload)) !== null && _handler$validate !== void 0 ? _handler$validate : true) {
63
+ return handler.update(state, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, action), {}, {
64
64
  ctx: {
65
- reset: action.ctx.reset
65
+ reset: action.ctx.reset,
66
+ cancelled: action.ctx.cancelled
66
67
  },
67
68
  intent: {
68
69
  type: intent.type,
@@ -328,6 +329,7 @@ function getField(context, options) {
328
329
  max: constraint === null || constraint === void 0 ? void 0 : constraint.max,
329
330
  step: constraint === null || constraint === void 0 ? void 0 : constraint.step,
330
331
  multiple: constraint === null || constraint === void 0 ? void 0 : constraint.multiple,
332
+ accept: constraint === null || constraint === void 0 ? void 0 : constraint.accept,
331
333
  get defaultValue() {
332
334
  return getDefaultValue(context, name, serialize);
333
335
  },
@@ -53,12 +53,13 @@ function updateState(state, action) {
53
53
  type: 'validate'
54
54
  };
55
55
  var handler = (_action$ctx$handlers = action.ctx.handlers) === null || _action$ctx$handlers === void 0 ? void 0 : _action$ctx$handlers[intent.type];
56
- if (typeof (handler === null || handler === void 0 ? void 0 : handler.onUpdate) === 'function') {
57
- var _handler$validatePayl, _handler$validatePayl2;
58
- if ((_handler$validatePayl = (_handler$validatePayl2 = handler.validatePayload) === null || _handler$validatePayl2 === void 0 ? void 0 : _handler$validatePayl2.call(handler, intent.payload)) !== null && _handler$validatePayl !== void 0 ? _handler$validatePayl : true) {
59
- return handler.onUpdate(state, _objectSpread2(_objectSpread2({}, action), {}, {
56
+ if (typeof (handler === null || handler === void 0 ? void 0 : handler.update) === 'function') {
57
+ var _handler$validate, _handler$validate2;
58
+ if ((_handler$validate = (_handler$validate2 = handler.validate) === null || _handler$validate2 === void 0 ? void 0 : _handler$validate2.call(handler, intent.payload)) !== null && _handler$validate !== void 0 ? _handler$validate : true) {
59
+ return handler.update(state, _objectSpread2(_objectSpread2({}, action), {}, {
60
60
  ctx: {
61
- reset: action.ctx.reset
61
+ reset: action.ctx.reset,
62
+ cancelled: action.ctx.cancelled
62
63
  },
63
64
  intent: {
64
65
  type: intent.type,
@@ -324,6 +325,7 @@ function getField(context, options) {
324
325
  max: constraint === null || constraint === void 0 ? void 0 : constraint.max,
325
326
  step: constraint === null || constraint === void 0 ? void 0 : constraint.step,
326
327
  multiple: constraint === null || constraint === void 0 ? void 0 : constraint.multiple,
328
+ accept: constraint === null || constraint === void 0 ? void 0 : constraint.accept,
327
329
  get defaultValue() {
328
330
  return getDefaultValue(context, name, serialize$1);
329
331
  },
@@ -422,7 +422,7 @@ export interface IntentDispatcher<FormShape extends Record<string, any> = Record
422
422
  /**
423
423
  * Insert a new item into an array field.
424
424
  */
425
- insert<FieldShape extends Array<any>>(options: {
425
+ insert<FieldShape extends Array<any> | null | undefined>(options: {
426
426
  /**
427
427
  * The name of the array field to insert into.
428
428
  */
@@ -435,20 +435,45 @@ export interface IntentDispatcher<FormShape extends Record<string, any> = Record
435
435
  /**
436
436
  * The default value for the new item.
437
437
  */
438
- defaultValue?: FieldShape extends Array<infer ItemShape> ? DefaultValue<ItemShape> : never;
438
+ defaultValue?: NonNullable<FieldShape> extends Array<infer ItemShape> ? DefaultValue<ItemShape> : never;
439
+ /**
440
+ * The name of a field to read the value from.
441
+ * When specified, the value is read from this field, validated,
442
+ * and if valid, inserted into the array and the source field is cleared.
443
+ * If validation fails, the error is shown on the source field instead.
444
+ * Requires the validation error to be available synchronously.
445
+ */
446
+ from?: string;
447
+ /**
448
+ * What to do when the insert causes a validation error on the array.
449
+ * - 'revert': Don't insert, keep original array state.
450
+ * Requires the validation error to be available synchronously.
451
+ */
452
+ onInvalid?: 'revert';
439
453
  }): void;
440
454
  /**
441
455
  * Remove an item from an array field.
442
456
  */
443
- remove(options: {
457
+ remove<FieldShape extends Array<any> | null | undefined>(options: {
444
458
  /**
445
459
  * The name of the array field to remove from.
446
460
  */
447
- name: FieldName<Array<any>>;
461
+ name: FieldName<FieldShape>;
448
462
  /**
449
463
  * The index of the item to remove.
450
464
  */
451
465
  index: number;
466
+ /**
467
+ * What to do when the remove causes a validation error on the array.
468
+ * - 'revert': Don't remove, keep original item as-is.
469
+ * - 'insert': Remove the item but insert a new blank item at the end.
470
+ * Requires the validation error to be available synchronously.
471
+ */
472
+ onInvalid?: 'revert' | 'insert';
473
+ /**
474
+ * The default value for the new item when onInvalid is 'insert'.
475
+ */
476
+ defaultValue?: NonNullable<FieldShape> extends Array<infer ItemShape> ? DefaultValue<ItemShape> : never;
452
477
  }): void;
453
478
  /**
454
479
  * Reorder items in an array field.
@@ -465,14 +490,16 @@ export type FormIntent<Dispatcher extends IntentDispatcher = IntentDispatcher> =
465
490
  payload: Args extends [infer Payload] ? Payload : undefined;
466
491
  } : never;
467
492
  }[keyof Dispatcher];
468
- export type ActionHandler<Signature extends (payload: any) => void = (payload: any) => void> = {
469
- validatePayload?(...args: UnknownArgs<Parameters<Signature>>): boolean;
470
- onApply?(value: Record<string, FormValue>, ...args: Parameters<Signature>): Record<string, FormValue> | undefined;
471
- onUpdate?<ErrorShape extends BaseErrorShape>(state: FormState<ErrorShape>, action: FormAction<ErrorShape, {
493
+ export type IntentHandler<Signature extends (payload: any) => void = (payload: any) => void> = {
494
+ validate?(...args: UnknownArgs<Parameters<Signature>>): boolean;
495
+ resolve?(value: Record<string, FormValue>, ...args: Parameters<Signature>): Record<string, FormValue> | undefined;
496
+ apply?<ErrorShape extends BaseErrorShape>(result: SubmissionResult<ErrorShape>, ...args: Parameters<Signature>): SubmissionResult<ErrorShape>;
497
+ update?<ErrorShape extends BaseErrorShape>(state: FormState<ErrorShape>, action: FormAction<ErrorShape, {
472
498
  type: string;
473
499
  payload: Signature extends (payload: infer Payload) => void ? Payload : undefined;
474
500
  }, {
475
501
  reset: (defaultValue?: Record<string, unknown> | null) => FormState<ErrorShape>;
502
+ cancelled?: boolean;
476
503
  }>): FormState<ErrorShape>;
477
504
  };
478
505
  type BaseCombine<T, K extends PropertyKey = T extends unknown ? keyof T : never> = T extends unknown ? T & Partial<Record<Exclude<K, keyof T>, never>> : never;
package/dist/helpers.d.ts CHANGED
@@ -34,6 +34,7 @@ type InputProps = Pretty<FormControlProps & {
34
34
  step?: string | number;
35
35
  pattern?: string;
36
36
  multiple?: boolean;
37
+ accept?: string;
37
38
  value?: string;
38
39
  defaultChecked?: boolean;
39
40
  defaultValue?: string;
package/dist/helpers.js CHANGED
@@ -109,7 +109,8 @@ function getInputProps(metadata, options) {
109
109
  max: metadata.max,
110
110
  step: metadata.step,
111
111
  pattern: metadata.pattern,
112
- multiple: metadata.multiple
112
+ multiple: metadata.multiple,
113
+ accept: metadata.accept
113
114
  });
114
115
  if (typeof options.value === 'undefined' || options.value) {
115
116
  if (options.type === 'checkbox' || options.type === 'radio') {
package/dist/helpers.mjs CHANGED
@@ -105,7 +105,8 @@ function getInputProps(metadata, options) {
105
105
  max: metadata.max,
106
106
  step: metadata.step,
107
107
  pattern: metadata.pattern,
108
- multiple: metadata.multiple
108
+ multiple: metadata.multiple,
109
+ accept: metadata.accept
109
110
  });
110
111
  if (typeof options.value === 'undefined' || options.value) {
111
112
  if (options.type === 'checkbox' || options.type === 'radio') {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Conform view adapter for react",
4
4
  "homepage": "https://conform.guide",
5
5
  "license": "MIT",
6
- "version": "1.16.0",
6
+ "version": "1.17.0",
7
7
  "main": "./dist/index.js",
8
8
  "module": "./dist/index.mjs",
9
9
  "types": "./dist/index.d.ts",
@@ -41,7 +41,7 @@
41
41
  "url": "https://github.com/edmundhung/conform/issues"
42
42
  },
43
43
  "dependencies": {
44
- "@conform-to/dom": "1.16.0"
44
+ "@conform-to/dom": "1.17.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@babel/core": "^7.17.8",