@conform-to/dom
Version:
A set of opinionated helpers built on top of the Constraint Validation API
377 lines (364 loc) • 12.7 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js');
var dom = require('./dom.js');
var formdata = require('./formdata.js');
var util = require('./util.js');
/**
* The name to be used when submitting a form control
*/
var INTENT = '__intent__';
/**
* The name to be used when submitting a state
*/
var STATE = '__state__';
function getSubmissionContext(body) {
var intent = body.get(INTENT);
var state = body.get(STATE);
util.invariant((typeof intent === 'string' || intent === null) && (typeof state === 'string' || state === null), "The input name \"".concat(INTENT, "\" and \"").concat(STATE, "\" are reserved by Conform. Please use another name for your input."));
var context = {
payload: {},
fields: new Set(),
intent: getIntent(intent)
};
if (state) {
context.state = JSON.parse(state);
}
var _loop = function _loop(next) {
if (name === INTENT || name === STATE) {
return 1; // continue
}
context.fields.add(name);
formdata.setPathValue(context.payload, name, prev => {
if (!prev) {
return next;
} else if (Array.isArray(prev)) {
return prev.concat(next);
} else {
return [prev, next];
}
}, {
silent: true
});
};
for (var [name, next] of body.entries()) {
if (_loop(next)) continue;
}
return context;
}
function parse(payload, options) {
var context = getSubmissionContext(payload);
var intent = context.intent;
if (intent) {
switch (intent.type) {
case 'update':
{
var name = formdata.appendPath(intent.payload.name, intent.payload.index);
var _value = intent.payload.value;
if (typeof intent.payload.value !== 'undefined') {
if (name) {
formdata.setPathValue(context.payload, name, () => _value);
} else {
context.payload = _value;
}
}
break;
}
case 'reset':
{
var _name = formdata.appendPath(intent.payload.name, intent.payload.index);
if (_name) {
formdata.setPathValue(context.payload, _name, () => undefined);
} else {
context.payload = {};
}
break;
}
case 'insert':
case 'remove':
case 'reorder':
{
setListValue(context.payload, intent);
break;
}
}
}
var result = options.resolve(context.payload, intent);
var mergeResolveResult = resolved => createSubmission(_rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, context), {}, {
value: resolved.value,
error: resolved.error
}));
if (result instanceof Promise) {
return result.then(mergeResolveResult);
}
return mergeResolveResult(result);
}
function createSubmission(context) {
if (context.intent || context.error !== undefined) {
return {
status: !context.intent ? 'error' : undefined,
payload: context.payload,
error: typeof context.error !== 'undefined' ? context.error : {},
reply(options) {
return replySubmission(context, options);
}
};
}
return {
status: 'success',
payload: context.payload,
value: context.value,
reply(options) {
return replySubmission(context, options);
}
};
}
function replySubmission(context) {
var _context$intent, _context$intent$paylo, _options$formErrors, _ref;
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if ('resetForm' in options && options.resetForm || ((_context$intent = context.intent) === null || _context$intent === void 0 ? void 0 : _context$intent.type) === 'reset' && ((_context$intent$paylo = context.intent.payload.name) !== null && _context$intent$paylo !== void 0 ? _context$intent$paylo : '') === '') {
return {
initialValue: null
};
}
if ('hideFields' in options && options.hideFields) {
for (var name of options.hideFields) {
var _value2 = formdata.getPathValue(context.payload, name);
if (typeof _value2 !== 'undefined') {
formdata.setPathValue(context.payload, name, () => undefined);
}
}
}
var extraError = 'formErrors' in options || 'fieldErrors' in options ? normalize(_rollupPluginBabelHelpers.objectSpread2({
'': (_options$formErrors = options.formErrors) !== null && _options$formErrors !== void 0 ? _options$formErrors : null
}, options.fieldErrors)) : null;
var error = context.error || extraError ? _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, context.error), extraError) : undefined;
var initialValue = (_ref = normalize(context.payload,
// We can't serialize the file and send it back from the server, but we can preserve it in the client
typeof document !== 'undefined'
// We need the file on the client because it's treated as the form value
// But we will exclude the File type for now as it's only used by the internal
// form state and we will remove the need to preserve the file on the client soon
)) !== null && _ref !== void 0 ? _ref : {};
return {
status: context.intent ? undefined : error ? 'error' : 'success',
intent: context.intent ? context.intent : undefined,
initialValue,
error,
state: context.state,
fields: Array.from(context.fields)
};
}
function getIntent(serializedIntent) {
if (!serializedIntent) {
return null;
}
var control = JSON.parse(serializedIntent);
if (typeof control.type !== 'string' || typeof control.payload === 'undefined') {
throw new Error('Unknown form control intent');
}
return control;
}
function serializeIntent(intent) {
switch (intent.type) {
case 'insert':
return JSON.stringify({
type: intent.type,
payload: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, intent.payload), {}, {
defaultValue: serialize(intent.payload.defaultValue)
})
});
case 'update':
return JSON.stringify({
type: intent.type,
payload: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, intent.payload), {}, {
value: serialize(intent.payload.value)
})
});
default:
return JSON.stringify(intent);
}
}
function updateList(list, intent) {
var _intent$payload$index;
util.invariant(Array.isArray(list), "Failed to update list. The value is not an array.");
switch (intent.type) {
case 'insert':
list.splice((_intent$payload$index = intent.payload.index) !== null && _intent$payload$index !== void 0 ? _intent$payload$index : list.length, 0, intent.payload.defaultValue);
break;
case 'remove':
list.splice(intent.payload.index, 1);
break;
case 'reorder':
list.splice(intent.payload.to, 0, ...list.splice(intent.payload.from, 1));
break;
default:
throw new Error('Unknown list intent received');
}
}
function setListValue(data, intent) {
formdata.setPathValue(data, intent.payload.name, value => {
var list = value !== null && value !== void 0 ? value : [];
updateList(list, intent);
return list;
});
}
/**
* A placeholder symbol for the root value of a nested object
*/
var root = Symbol.for('root');
function setState(state, name, valueFn) {
// The keys are sorted in desc so that the root value is handled last
var keys = Object.keys(state).sort((prev, next) => next.localeCompare(prev));
var target = {};
var _loop2 = function _loop2() {
var value = state[_key];
if (formdata.isPathPrefix(_key, name) && _key !== name) {
formdata.setPathValue(target, _key, currentValue => {
if (typeof currentValue === 'undefined') {
return value;
}
// As the key should be unique, if currentValue is already defined,
// it must be either an object or an array
// @ts-expect-error
currentValue[root] = value;
return currentValue;
});
// Remove the value from the data
delete state[_key];
}
};
for (var _key of keys) {
_loop2();
}
var result = valueFn(formdata.getPathValue(target, name));
Object.assign(state, flatten(result, {
resolve(data) {
if (util.isPlainObject(data) || Array.isArray(data)) {
var _data$root;
// @ts-expect-error
return (_data$root = data[root]) !== null && _data$root !== void 0 ? _data$root : null;
}
return data;
},
prefix: name
}));
}
function setListState(state, intent, getDefaultValue) {
setState(state, intent.payload.name, value => {
var list = value !== null && value !== void 0 ? value : [];
switch (intent.type) {
case 'insert':
updateList(list, {
type: intent.type,
payload: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, intent.payload), {}, {
defaultValue: getDefaultValue === null || getDefaultValue === void 0 ? void 0 : getDefaultValue(intent.payload.defaultValue)
})
});
break;
default:
updateList(list, intent);
break;
}
return list;
});
}
function serialize(defaultValue) {
if (util.isPlainObject(defaultValue)) {
// @ts-expect-error FIXME
return Object.entries(defaultValue).reduce((result, _ref2) => {
var [key, value] = _ref2;
result[key] = serialize(value);
return result;
}, {});
} else if (Array.isArray(defaultValue)) {
// @ts-expect-error FIXME
return defaultValue.map(serialize);
} else if (defaultValue instanceof Date) {
// @ts-expect-error FIXME
return defaultValue.toISOString();
} else if (typeof defaultValue === 'boolean') {
// @ts-expect-error FIXME
return defaultValue ? 'on' : undefined;
} else if (typeof defaultValue === 'number' || typeof defaultValue === 'bigint') {
// @ts-expect-error FIXME
return defaultValue.toString();
} else {
// @ts-expect-error FIXME
return defaultValue !== null && defaultValue !== void 0 ? defaultValue : undefined;
}
}
/**
* Normalize value by removing empty object or array, empty string and null values
*/
function normalize(value) {
var acceptFile = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
if (util.isPlainObject(value)) {
var obj = Object.keys(value).sort().reduce((result, key) => {
var data = normalize(value[key], acceptFile);
if (typeof data !== 'undefined') {
result[key] = data;
}
return result;
}, {});
if (Object.keys(obj).length === 0) {
return;
}
return obj;
}
if (Array.isArray(value)) {
if (value.length === 0) {
return undefined;
}
return value.map(item => normalize(item, acceptFile));
}
if (typeof value === 'string' && value === '' || value === null || dom.isGlobalInstance(value, 'File') && (!acceptFile || value.size === 0)) {
return;
}
return value;
}
/**
* Flatten a tree into a dictionary
*/
function flatten(data) {
var _options$resolve;
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var result = {};
var resolve = (_options$resolve = options.resolve) !== null && _options$resolve !== void 0 ? _options$resolve : data => data;
function process(data, prefix) {
var value = normalize(resolve(data));
if (typeof value !== 'undefined') {
result[prefix] = value;
}
if (Array.isArray(data)) {
for (var i = 0; i < data.length; i++) {
process(data[i], "".concat(prefix, "[").concat(i, "]"));
}
} else if (util.isPlainObject(data)) {
for (var [_key2, _value3] of Object.entries(data)) {
process(_value3, prefix ? "".concat(prefix, ".").concat(_key2) : _key2);
}
}
}
if (data) {
var _options$prefix;
process(data, (_options$prefix = options.prefix) !== null && _options$prefix !== void 0 ? _options$prefix : '');
}
return result;
}
exports.INTENT = INTENT;
exports.STATE = STATE;
exports.createSubmission = createSubmission;
exports.flatten = flatten;
exports.getIntent = getIntent;
exports.getSubmissionContext = getSubmissionContext;
exports.normalize = normalize;
exports.parse = parse;
exports.replySubmission = replySubmission;
exports.root = root;
exports.serialize = serialize;
exports.serializeIntent = serializeIntent;
exports.setListState = setListState;
exports.setListValue = setListValue;
exports.setState = setState;
exports.updateList = updateList;