UNPKG

@conform-to/react

Version:

Conform view adapter for react

446 lines (431 loc) 20.6 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _rollupPluginBabelHelpers = require('../_virtual/_rollupPluginBabelHelpers.js'); var future = require('@conform-to/dom/future'); var util = require('./util.js'); var state = require('./state.js'); /** * Serializes intent to string format: "type" or "type(payload)". */ function serializeIntent(intent) { if (!intent.payload) { return intent.type; } return "".concat(intent.type, "(").concat(JSON.stringify(intent.payload), ")"); } /** * Parses serialized intent string back to intent object. */ function deserializeIntent(value) { var type = value; var payload; var serializedPayload; var openParenIndex = value.indexOf('('); if (openParenIndex > 0 && value[value.length - 1] === ')') { type = value.slice(0, openParenIndex); serializedPayload = value.slice(openParenIndex + 1, -1); } if (serializedPayload) { try { payload = JSON.parse(serializedPayload); } catch (_unused) { // Ignore the error } } return { type, payload }; } /** * Applies intent transformation to submission payload. * Returns modified payload or null for reset intent. */ function resolveIntent(submission, options) { var _options$handlers, _handler$validate, _handler$validate2; if (!submission.intent) { return submission.payload; } var intent = deserializeIntent(submission.intent); var handlers = (_options$handlers = options === null || options === void 0 ? void 0 : options.handlers) !== null && _options$handlers !== void 0 ? _options$handlers : intentHandlers; var handler = handlers[intent.type]; 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)) { return handler.resolve(submission.payload, intent.payload); } return submission.payload; } /** * Resolves an intent after validation by calling the handler's onResolve. * Mutates the result with updated value/error and returns whether the intent was cancelled. */ function applyIntent(result, intent, options) { if (intent) { var _options$handlers2, _handler$validate3, _handler$validate4; var handlers = (_options$handlers2 = options === null || options === void 0 ? void 0 : options.handlers) !== null && _options$handlers2 !== void 0 ? _options$handlers2 : intentHandlers; var handler = handlers[intent.type]; 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)) { return handler.apply(result, intent.payload); } } return result; } function insertItem(list, item, index) { list.splice(index, 0, item); } function removeItem(list, index) { list.splice(index, 1); } function reorderItems(list, fromIndex, toIndex) { list.splice(toIndex, 0, ...list.splice(fromIndex, 1)); } /** * Updates list keys by removing child keys and optionally transforming remaining keys. */ function updateListKeys() { var keys = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var keyToBeRemoved = arguments.length > 1 ? arguments[1] : undefined; var updateKey = arguments.length > 2 ? arguments[2] : undefined; var basePath = future.parsePath(keyToBeRemoved); return util.transformKeys(keys, field => { var _updateKey; return future.getRelativePath(field, basePath) !== null ? null : (_updateKey = updateKey === null || updateKey === void 0 ? void 0 : updateKey(field)) !== null && _updateKey !== void 0 ? _updateKey : field; }); } /** * Built-in action handlers for form intents: * - reset: clears form data * - validate: marks fields as touched for validation display * - update: updates specific field values * - insert/remove/reorder: manages array field operations */ var intentHandlers = { reset: { validate(options) { return util.isOptional(options, future.isPlainObject) && (util.isUndefined(options === null || options === void 0 ? void 0 : options.defaultValue) || util.isNullable(options === null || options === void 0 ? void 0 : options.defaultValue, future.isPlainObject)); }, resolve(_, options) { if ((options === null || options === void 0 ? void 0 : options.defaultValue) === null) { return {}; } return options === null || options === void 0 ? void 0 : options.defaultValue; }, apply(result) { return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, result), {}, { reset: true }); } }, validate: { validate(name) { return util.isOptional(name, util.isString); }, update(state, _ref) { var _intent$payload; var { submission, intent, error } = _ref; var name = (_intent$payload = intent.payload) !== null && _intent$payload !== void 0 ? _intent$payload : ''; var basePath = future.parsePath(name); var allFields = error ? // Consider fields / fieldset with errors as touched too submission.fields.concat(Object.keys(error.fieldErrors)) : submission.fields; var touchedFields = util.appendUniqueItem(state.touchedFields, name); for (var field of allFields) { // Add all child fields to the touched fields too if (future.getRelativePath(field, basePath) !== null) { touchedFields = util.appendUniqueItem(touchedFields, field); } } return util.merge(state, { touchedFields }); } }, update: { validate(options) { return future.isPlainObject(options) && util.isOptional(options.name, util.isString) && util.isOptional(options.index, util.isNumber) && !util.isUndefined(options.value); }, resolve(value, options) { var _options$value; var name = future.appendPath(options.name, options.index); return util.updatePathValue(value, name, (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : name === '' ? {} : null); }, update(state, _ref2) { var { type, submission, intent } = _ref2; if (type === 'server') { return state; } var listKeys = state.listKeys; // Update the keys only for client updates to avoid double updates if there is no client validation if (type === 'client') { // TODO: Do we really need to update the keys here? var name = future.appendPath(intent.payload.name, intent.payload.index); // Remove all child keys listKeys = name === '' ? {} : updateListKeys(state.listKeys, name); } var basePath = future.parsePath(intent.payload.name); var touchedFields = state.touchedFields; for (var field of submission.fields) { if (basePath.length === 0 || future.getRelativePath(field, basePath) !== null) { touchedFields = util.appendUniqueItem(touchedFields, field); } } return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, state), {}, { listKeys, touchedFields }); } }, insert: { validate(options) { return future.isPlainObject(options) && util.isString(options.name) && util.isOptional(options.index, util.isNumber) && util.isOptional(options.from, util.isString) && util.isOptional(options.onInvalid, mode => mode === 'revert'); }, resolve(value, options) { var _options$index; var result = value; var itemValue = options.defaultValue; if (options.from !== undefined) { itemValue = future.getPathValue(result, options.from); result = util.updatePathValue(result, options.from, ''); } var list = Array.from(util.getPathArray(result, options.name)); insertItem(list, itemValue, (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : list.length); return util.updatePathValue(result, options.name, list); }, apply(result, options) { var _result$error; // Warn if validation result is not yet available if (typeof result.error === 'undefined' && (options.onInvalid || options.from)) { // eslint-disable-next-line no-console 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.'); return result; } var listError = (_result$error = result.error) === null || _result$error === void 0 ? void 0 : _result$error.fieldErrors[options.name]; if (options.onInvalid === 'revert' && listError != null) { return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, result), {}, { targetValue: undefined }); } if (options.from !== undefined) { var _options$index2, _result$error2, _result$error3; var index = (_options$index2 = options.index) !== null && _options$index2 !== void 0 ? _options$index2 : util.getPathArray(result.submission.payload, options.name).length; var insertedItemPath = future.appendPath(options.name, index); var insertedItemError = (_result$error2 = result.error) === null || _result$error2 === void 0 ? void 0 : _result$error2.fieldErrors[insertedItemPath]; var fromFieldError = (_result$error3 = result.error) === null || _result$error3 === void 0 ? void 0 : _result$error3.fieldErrors[options.from]; if (fromFieldError != null) { var _result$error$formErr, _result$error4, _result$error5; return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, result), {}, { targetValue: undefined, error: { 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 : null, fieldErrors: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, (_result$error5 = result.error) === null || _result$error5 === void 0 ? void 0 : _result$error5.fieldErrors), {}, { [insertedItemPath]: null }) } }); } if (insertedItemError != null) { var _result$error$formErr2, _result$error6, _result$error7; return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, result), {}, { targetValue: undefined, error: { formErrors: (_result$error$formErr2 = (_result$error6 = result.error) === null || _result$error6 === void 0 ? void 0 : _result$error6.formErrors) !== null && _result$error$formErr2 !== void 0 ? _result$error$formErr2 : null, fieldErrors: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, (_result$error7 = result.error) === null || _result$error7 === void 0 ? void 0 : _result$error7.fieldErrors), {}, { [options.from]: insertedItemError, [insertedItemPath]: null }) } }); } } return result; }, update(state$1, _ref3) { var _intent$payload$index; var { type, submission, intent, ctx } = _ref3; if (type === 'server') { return state$1; } var from = intent.payload.from; var index = (_intent$payload$index = intent.payload.index) !== null && _intent$payload$index !== void 0 ? _intent$payload$index : util.getPathArray(submission.payload, intent.payload.name).length; var updateListIndex = util.createPathIndexUpdater(intent.payload.name, currentIndex => index <= currentIndex ? currentIndex + 1 : currentIndex); var touchedFields = state$1.touchedFields; var listKeys = state$1.listKeys; if (!ctx.cancelled) { touchedFields = util.compactMap(state$1.touchedFields, updateListIndex); // Update the keys only for client updates to avoid double updates if there is no client validation if (type === 'client') { var _state$listKeys$inten; var selectedListKeys = Array.from((_state$listKeys$inten = state$1.listKeys[intent.payload.name]) !== null && _state$listKeys$inten !== void 0 ? _state$listKeys$inten : state.getDefaultListKey(state$1.resetKey, submission.payload, intent.payload.name)); insertItem(selectedListKeys, util.generateUniqueKey(), index); listKeys = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, updateListKeys(state$1.listKeys, future.appendPath(intent.payload.name, index), updateListIndex)), {}, { // Update existing list keys [intent.payload.name]: selectedListKeys }); } } touchedFields = util.appendUniqueItem(touchedFields, intent.payload.name); if (from !== undefined) { touchedFields = util.appendUniqueItem(touchedFields, from); } return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, state$1), {}, { listKeys, touchedFields }); } }, remove: { validate(options) { return future.isPlainObject(options) && util.isString(options.name) && util.isNumber(options.index) && util.isOptional(options.onInvalid, v => v === 'revert' || v === 'insert'); }, resolve(value, options) { var list = Array.from(util.getPathArray(value, options.name)); removeItem(list, options.index); return util.updatePathValue(value, options.name, list); }, apply(result, options) { var _result$error8; // Warn if validation result is not yet available if (typeof result.error === 'undefined' && options.onInvalid) { if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line no-console 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.'); } return result; } if (result.targetValue && (_result$error8 = result.error) !== null && _result$error8 !== void 0 && _result$error8.fieldErrors[options.name]) { switch (options.onInvalid) { case 'revert': return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, result), {}, { targetValue: undefined }); case 'insert': { var list = Array.from(util.getPathArray(result.targetValue, options.name)); insertItem(list, options.defaultValue, list.length); return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, result), {}, { targetValue: util.updatePathValue(result.targetValue, options.name, list) }); } } } return result; }, update(state$1, _ref4) { var { type, submission, intent, ctx } = _ref4; if (type === 'server') { return state$1; } var currentValue = submission.payload; var updateListIndex = util.createPathIndexUpdater(intent.payload.name, currentIndex => { if (intent.payload.index === currentIndex) { return null; } return intent.payload.index < currentIndex ? currentIndex - 1 : currentIndex; }); var touchedFields = state$1.touchedFields; var listKeys = state$1.listKeys; // If onInvalid is 'insert', we still remove the item and then insert a new item at the end if (!ctx.cancelled || intent.payload.onInvalid === 'insert') { touchedFields = util.compactMap(touchedFields, updateListIndex); // Update the keys only for client updates to avoid double updates if there is no client validation if (type === 'client') { var _state$listKeys$inten2; var selectedListKeys = Array.from((_state$listKeys$inten2 = state$1.listKeys[intent.payload.name]) !== null && _state$listKeys$inten2 !== void 0 ? _state$listKeys$inten2 : state.getDefaultListKey(state$1.resetKey, currentValue, intent.payload.name)); removeItem(selectedListKeys, intent.payload.index); listKeys = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, updateListKeys(state$1.listKeys, future.appendPath(intent.payload.name, intent.payload.index), updateListIndex)), {}, { // Update existing list keys [intent.payload.name]: selectedListKeys }); if (ctx.cancelled) { var index = selectedListKeys.length; insertItem(selectedListKeys, util.generateUniqueKey(), index); listKeys = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, updateListKeys(state$1.listKeys, future.appendPath(intent.payload.name, index), updateListIndex)), {}, { // Update existing list keys [intent.payload.name]: selectedListKeys }); } } } touchedFields = util.appendUniqueItem(touchedFields, intent.payload.name); return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, state$1), {}, { listKeys: listKeys, touchedFields }); } }, reorder: { validate(options) { return future.isPlainObject(options) && util.isString(options.name) && util.isNumber(options.from) && util.isNumber(options.to); }, resolve(value, options) { var list = Array.from(util.getPathArray(value, options.name)); reorderItems(list, options.from, options.to); return util.updatePathValue(value, options.name, list); }, update(state$1, _ref5) { var { type, submission, intent } = _ref5; if (type === 'server') { return state$1; } var currentValue = submission.payload; var updateListIndex = util.createPathIndexUpdater(intent.payload.name, currentIndex => { if (intent.payload.from === intent.payload.to) { return currentIndex; } if (currentIndex === intent.payload.from) { return intent.payload.to; } if (intent.payload.from < intent.payload.to) { return currentIndex > intent.payload.from && currentIndex <= intent.payload.to ? currentIndex - 1 : currentIndex; } return currentIndex >= intent.payload.to && currentIndex < intent.payload.from ? currentIndex + 1 : currentIndex; }); var touchedFields = util.appendUniqueItem(util.compactMap(state$1.touchedFields, updateListIndex), intent.payload.name); var keys = state$1.listKeys; // Update the keys only for client updates to avoid double updates if there is no client validation if (type === 'client') { var _state$listKeys$inten3; var listKeys = Array.from((_state$listKeys$inten3 = state$1.listKeys[intent.payload.name]) !== null && _state$listKeys$inten3 !== void 0 ? _state$listKeys$inten3 : state.getDefaultListKey(state$1.resetKey, currentValue, intent.payload.name)); reorderItems(listKeys, intent.payload.from, intent.payload.to); keys = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, updateListKeys(state$1.listKeys, future.appendPath(intent.payload.name, intent.payload.from), updateListIndex)), {}, { // Update existing list keys [intent.payload.name]: listKeys }); } return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, state$1), {}, { listKeys: keys, touchedFields }); } } }; exports.applyIntent = applyIntent; exports.deserializeIntent = deserializeIntent; exports.insertItem = insertItem; exports.intentHandlers = intentHandlers; exports.removeItem = removeItem; exports.reorderItems = reorderItems; exports.resolveIntent = resolveIntent; exports.serializeIntent = serializeIntent; exports.updateListKeys = updateListKeys;