UNPKG

@conform-to/dom

Version:

A set of opinionated helpers built on top of the Constraint Validation API

377 lines (364 loc) 12.7 kB
'use strict'; 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;