@conform-to/react
Version:
Conform view adapter for react
306 lines (292 loc) • 12.8 kB
JavaScript
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 applyIntent(submission, options) {
var _options$handlers, _handler$validatePayl, _handler$validatePayl2;
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 : actionHandlers;
var handler = handlers[intent.type];
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)) {
return handler.onApply(submission.payload, intent.payload);
}
return submission.payload;
}
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.getPathSegments(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 actionHandlers = {
reset: {
onApply() {
return null;
}
},
validate: {
validatePayload(name) {
return util.isOptional(name, util.isString);
},
onUpdate(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.getPathSegments(name);
var touchedFields = util.appendUniqueItem(state.touchedFields, name);
for (var field of submission.fields) {
// Add all child fields to the touched fields too
if (future.getRelativePath(field, basePath) !== null) {
touchedFields = util.appendUniqueItem(touchedFields, field);
}
}
// We couldn't find out all the fields from the FormData, e.g. unchecked checkboxes.
// or fieldsets without any fields, but we can at least include missing
// required fields based on the form error
if (name === '' && error) {
for (var _name of Object.keys(error.fieldErrors)) {
touchedFields = util.appendUniqueItem(touchedFields, _name);
}
}
return util.merge(state, {
touchedFields
});
}
},
update: {
validatePayload(options) {
return future.isPlainObject(options) && util.isOptional(options.name, util.isString) && util.isOptional(options.index, util.isNumber) && !util.isUndefined(options.value);
},
onApply(value, options) {
return util.updateValueAtPath(value, future.appendPathSegment(options.name, options.index), options.value);
},
onUpdate(state, _ref2) {
var {
type,
submission,
intent
} = _ref2;
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.appendPathSegment(intent.payload.name, intent.payload.index);
// Remove all child keys
listKeys = name === '' ? {} : updateListKeys(state.listKeys, name);
}
var basePath = future.getPathSegments(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: {
validatePayload(options) {
return future.isPlainObject(options) && util.isString(options.name) && util.isOptional(options.index, util.isNumber);
},
onApply(value, options) {
var _options$index;
var list = Array.from(util.getArrayAtPath(value, options.name));
insertItem(list, options.defaultValue, (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : list.length);
return util.updateValueAtPath(value, options.name, list);
},
onUpdate(state$1, _ref3) {
var _intent$payload$index;
var {
type,
submission,
intent
} = _ref3;
var currentValue = submission.payload;
var list = util.getArrayAtPath(currentValue, intent.payload.name);
var index = (_intent$payload$index = intent.payload.index) !== null && _intent$payload$index !== void 0 ? _intent$payload$index : list.length;
var updateListIndex = util.createPathIndexUpdater(intent.payload.name, currentIndex => index <= currentIndex ? 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$inten;
var listKeys = 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, currentValue, intent.payload.name));
insertItem(listKeys, util.generateUniqueKey(), index);
keys = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, updateListKeys(state$1.listKeys, future.appendPathSegment(intent.payload.name, index), updateListIndex)), {}, {
// Update existing list keys
[intent.payload.name]: listKeys
});
}
return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, state$1), {}, {
listKeys: keys,
touchedFields
});
}
},
remove: {
validatePayload(options) {
return future.isPlainObject(options) && util.isString(options.name) && util.isNumber(options.index);
},
onApply(value, options) {
var list = Array.from(util.getArrayAtPath(value, options.name));
removeItem(list, options.index);
return util.updateValueAtPath(value, options.name, list);
},
onUpdate(state$1, _ref4) {
var {
type,
submission,
intent
} = _ref4;
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 = 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$inten2;
var listKeys = 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(listKeys, intent.payload.index);
keys = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, updateListKeys(state$1.listKeys, future.appendPathSegment(intent.payload.name, intent.payload.index), updateListIndex)), {}, {
// Update existing list keys
[intent.payload.name]: listKeys
});
}
return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, state$1), {}, {
listKeys: keys,
touchedFields
});
}
},
reorder: {
validatePayload(options) {
return future.isPlainObject(options) && util.isString(options.name) && util.isNumber(options.from) && util.isNumber(options.to);
},
onApply(value, options) {
var list = Array.from(util.getArrayAtPath(value, options.name));
reorderItems(list, options.from, options.to);
return util.updateValueAtPath(value, options.name, list);
},
onUpdate(state$1, _ref5) {
var {
type,
submission,
intent
} = _ref5;
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.appendPathSegment(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.actionHandlers = actionHandlers;
exports.applyIntent = applyIntent;
exports.deserializeIntent = deserializeIntent;
exports.insertItem = insertItem;
exports.removeItem = removeItem;
exports.reorderItems = reorderItems;
exports.serializeIntent = serializeIntent;
exports.updateListKeys = updateListKeys;
;