UNPKG

informed

Version:

A lightweight framework and utility for building powerful forms in React applications

339 lines (310 loc) 12.8 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _rollupPluginBabelHelpers = require('../_virtual/_rollupPluginBabelHelpers.js'); var React = require('react'); var useStateWithGetter = require('./useStateWithGetter.js'); var debug = require('../debug.js'); var Context = require('../Context.js'); var utils = require('../utils.js'); var ObjectMap = require('../ObjectMap.js'); var useFieldApi = require('./useFieldApi.js'); var useScope = require('./useScope.js'); var useFormApi = require('./useFormApi.js'); var useFieldSubscription = require('./useFieldSubscription.js'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var logger = debug.Debug('informed:useArrayField' + '\t'); var useArrayField = function useArrayField(_ref) { var userName = _ref.name, initialValue = _ref.initialValue, defaultValue = _ref.defaultValue, arrayFieldApiRef = _ref.arrayFieldApiRef; // Name might be scoped var name = useScope.useScope(userName); // Grab the form register context var formController = React.useContext(Context.FormControllerContext); // Hook onto the field api var fieldApi = useFieldApi.useFieldApi(name); // Gook onto the form api var formApi = useFormApi.useFormApi(); // For knowing if we are performing reset var resetRef = React.useRef(false); // Map will store all fields by name // Key => name // Val => fieldMetaRef // Why? so the array knows about all its field meta var _useState = React.useState(function () { return new Map(); }), _useState2 = _rollupPluginBabelHelpers.slicedToArray(_useState, 1), fieldsMap = _useState2[0]; var _useStateWithGetter = useStateWithGetter.useStateWithGetter(function () { // If we already have value i.e "saved" // use that ( it was not removed on purpose! ) if (formController.getValue(name)) { return formController.getValue(name); } return initialValue || formController.getInitialValue(name) || defaultValue || []; }), _useStateWithGetter2 = _rollupPluginBabelHelpers.slicedToArray(_useStateWithGetter, 3), initialValues = _useStateWithGetter2[0], setInitialValues = _useStateWithGetter2[1], getInitialValues = _useStateWithGetter2[2]; // TODO Need to use saved state to initialize ( after being re rendered ) var initialKeys = Array.isArray(initialValues) ? initialValues.map(function () { return utils.uuidv4(); }) : []; var _useStateWithGetter3 = useStateWithGetter.useStateWithGetter(initialKeys), _useStateWithGetter4 = _rollupPluginBabelHelpers.slicedToArray(_useStateWithGetter3, 3), keys = _useStateWithGetter4[0], setKeys = _useStateWithGetter4[1], getKeys = _useStateWithGetter4[2]; var _remove = function remove(i) { // Always get ref to latest keys var ks = getKeys(); // Notify form to expect removal on the last field formController.lockRemoval({ index: ks.length - 1, name: name }); // Remove the key var newKeys = ks.slice(0, i).concat(ks.slice(i + 1, ks.length)); setKeys(newKeys); // Remove the initial value ( user wanted to get rid of that input ) var initVals = getInitialValues(); var newInitialValues = initVals.slice(0, i).concat(initVals.slice(i + 1, initVals.length)); setInitialValues(newInitialValues); // We need to manually do a pull from the form state formController.pullOut("".concat(name, "[").concat(i, "]")); formApi.setDirt(name, []); formController.emitter.emit('field-value-set', name); }; var swap = function swap(a, b) { logger('Swapping', "".concat(name, "[").concat(a, "] and ").concat(name, "[").concat(b, "]")); formController.swap(name, a, b); // Always get ref to latest keys var ks = getKeys(); // Swap the keys var newKeys = _rollupPluginBabelHelpers.toConsumableArray(ks); if (ks[a] && ks[b]) { newKeys[a] = ks[b]; newKeys[b] = ks[a]; } else { // eslint-disable-next-line no-console console.warn("Attempted to swap ".concat(a, " with ").concat(b, " but one of them does not exist :(")); } setKeys(newKeys); }; var add = function add() { var amount = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; var ks = getKeys(); // if 'amount' is not defined, run the default behavior to add 1 field if (typeof amount !== 'number' || !Number(amount) || amount <= 0) { ks.push(utils.uuidv4()); } else { for (var i = 0; i < amount; i++) { ks.push(utils.uuidv4()); } } setKeys(_rollupPluginBabelHelpers.toConsumableArray(ks)); // If we added a new field we are no longer pristine formApi.setPristine(false); formApi.setDirt(name, []); formController.emitter.emit('field-value-set', name); }; var addWithInitialValue = function addWithInitialValue(initialValue) { var ks = getKeys(); ks.push(utils.uuidv4()); setKeys(_rollupPluginBabelHelpers.toConsumableArray(ks)); var newInitialValues = _rollupPluginBabelHelpers.toConsumableArray(getInitialValues()); newInitialValues[ks.length - 1] = initialValue; setInitialValues(newInitialValues); }; var insert = function insert(index, value) { logger("Inserting at index ".concat(index, " in ").concat(name, " with value"), value); // Use ObjectMap to insert the value into the form state formController.insert(name, index, value); // Always get ref to the latest keys var ks = getKeys(); // Insert a new key at the specified index var newKeys = _rollupPluginBabelHelpers.toConsumableArray(ks); newKeys.splice(index, 0, utils.uuidv4()); setKeys(newKeys); // Insert the initial value at the specified index var initVals = getInitialValues(); var newInitialValues = _rollupPluginBabelHelpers.toConsumableArray(initVals); newInitialValues.splice(index, 0, value); setInitialValues(newInitialValues); }; var initialValueRef = React.useRef(); initialValueRef.current = initialValue; var defaultValueRef = React.useRef(); defaultValueRef.current = defaultValue; var reset = function reset() { // First wipe the existing state // Array fields are unique.. because reset will create new keys every field will get unmounted // So, we can start by simply wiping out the keys below here ( same thing we do at form level reset ) // this will result in all fields performing their cleanup rutines logger("------------ ".concat(name, " Array Field Reset Start ------------")); // Performing reset so we set the flag resetRef.current = true; // Remove array field formController.remove(name); // Build new initial values var initVals = initialValueRef.current || formController.getInitialValue(name) || defaultValueRef.current || []; // Set our initial values back to what the user set at beginning setInitialValues(initVals); // Clear out keys ( we wait until all fields have deregistered before resetting ) setKeys([]); // ---vv Special case when there are no fields so we do it right away vv--- if (!fieldsMap.size && resetRef.current) { // V important we flag that we are done performing reset as all fields have deregistered resetRef.current = false; // For debug logging we show when complete logger("------------ ".concat(name, " Array Field Reset End ------------")); var _initVals = getInitialValues(); // Build a new set of keys because everything is new !!! var resetKeys = _initVals ? _initVals.map(function () { return utils.uuidv4(); }) : []; // Finally set that shit ! setKeys(resetKeys); } // ---^^ Special case when there are no fields so we do it right away ^^--- }; var clear = function clear() { formController.remove(name); setInitialValues([]); setKeys([]); }; // Create meta object var meta = { name: name, initialValue: initialValue, fieldApi: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, fieldApi), {}, { reset: reset }), arrayField: true }; var metaRef = React.useRef(meta); metaRef.current = meta; // Register as if its a field muahhahaha React.useEffect(function () { formController.register(name, metaRef); return function () { formController.deregister(name); }; }, [name]); var fields = keys.map(function (key, i) { var arrayFieldItemApi = { remove: function remove() { return _remove(i); } }; var arrayFieldItemState = { initialValue: initialValues && initialValues[i], key: key, name: "".concat(name, "[").concat(i, "]"), index: i, parent: name }; return { arrayFieldItemApi: arrayFieldItemApi, arrayFieldItemState: arrayFieldItemState }; }); var arrayFieldApi = React.useMemo(function () { return { add: add, remove: _remove, swap: swap, addWithInitialValue: addWithInitialValue, insert: insert, reset: reset, clear: clear }; }, []); if (arrayFieldApiRef) { arrayFieldApiRef.current = arrayFieldApi; } var arrayFieldState = { fields: fields, name: name // hidden }; // Wrap the updater to update array fields references var wrappedController = React.useMemo(function () { return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, formController), {}, { register: function register(n, m) { fieldsMap.set(n, m); formController.register(n, m); }, deregister: function deregister(n) { formController.deregister(n); // Remove from our map fieldsMap["delete"](n); // On last deregister we finally complete // console.log(`${name}-WTF1`, fieldsMap.size); // console.log(`${name}-WTF2`, resetRef.current); // NOTE: I originally tried to put the below logic inside of remove // However this cuases issues because deregister is called with the correct name where remove may have old name // Example [ 0, 1, 2 ] if we remove 1 then 2 moves to 1s place if (!fieldsMap.size && resetRef.current) { // V important we flag that we are done performing reset as all fields have deregistered resetRef.current = false; // For debug logging we show when complete logger("------------ ".concat(name, " Array Field Reset End ------------")); var initVals = getInitialValues(); // Build a new set of keys because everything is new !!! var resetKeys = initVals ? initVals.map(function () { return utils.uuidv4(); }) : []; // Finally set that shit ! setKeys(resetKeys); } }, getInitialValue: function getInitialValue(fieldName) { // If we are getting initial value and its for this field return that // Case1: // name = "friends" // fieldName = "friends[0].name" // // Case2: // name = "friends[0].siblings" // fieldName = "friends[0].siblings[0].name" // Use a regex to specifically target the last [0-9]+ and anything that follows var modifiedFieldName = fieldName.replace(/(\[[0-9]+\])[^[\]]*$/, ''); // Check if they match if (modifiedFieldName === name) { var path = fieldName.replace(name, ''); var v = ObjectMap.ObjectMap.get(getInitialValues(), path); logger("Getting initial value for ".concat(path, " which is ").concat(v)); return v; } return formController.getInitialValue(fieldName); } }); }, [name]); useFieldSubscription.useFieldSubscription('clear', [name], function (target) { logger("clear event triggered for ".concat(name, " because of ").concat(target)); clear(); }, false, // No scope ( lol ) because we are already scoped true // Flip order of target comparison ); var render = function render(children) { return /*#__PURE__*/React__default["default"].createElement(Context.FormControllerContext.Provider, { value: wrappedController }, /*#__PURE__*/React__default["default"].createElement(Context.ArrayFieldApiContext.Provider, { value: arrayFieldApi }, /*#__PURE__*/React__default["default"].createElement(Context.ArrayFieldStateContext.Provider, { value: arrayFieldState }, children))); }; return { render: render, arrayFieldState: arrayFieldState, arrayFieldApi: arrayFieldApi }; }; exports.useArrayField = useArrayField;