grommet
Version:
focus on the essential experience
308 lines (293 loc) • 12.2 kB
JavaScript
var _excluded = ["children", "footer", "onDone", "pad", "updateOn"];
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
import React, { useMemo, useCallback, useContext, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import { Box } from '../Box';
import { Button } from '../Button';
import { Footer } from '../Footer';
import { Form } from '../Form';
import { DataContext } from '../../contexts/DataContext';
import { DataFormContext } from '../../contexts/DataFormContext';
import { MessageContext } from '../../contexts/MessageContext';
import { useDebounce } from '../../utils/use-debounce';
import { useThemeValue } from '../../utils/useThemeValue';
var MaxForm = styled(Form).withConfig({
displayName: "DataForm__MaxForm",
componentId: "sc-v64e1r-0"
})(["max-width:100%;", ""], function (props) {
return props.fill && 'max-height: 100%;';
});
// We convert the view structure to something more flat to work better
// with the Form inputs. These keys are how we flatten the Form value object
// from the view object.
export var formSearchKey = '_search';
export var formSortKey = '_sort';
export var formRangeKey = '_range';
export var formStepKey = '_step';
export var formPageKey = '_page';
export var formColumnsKey = '_columns';
export var formGroupByKey = '_groupBy';
export var formViewNameKey = '_view';
var viewFormKeyMap = {
search: formSearchKey,
sort: formSortKey,
step: formStepKey,
page: formPageKey,
columns: formColumnsKey,
groupBy: formGroupByKey,
view: formViewNameKey
};
// flatten nested objects.
// For example: { a: { b: v, c: z } } -> { 'a.b': v, 'a.c': z }
var _flatten = function flatten(formValue, options) {
var result = JSON.parse(JSON.stringify(formValue));
Object.keys(result).forEach(function (i) {
// We check the type of the i using
// typeof() function and recursively
// call the function again
// ignore _range situations
if (typeof result[i] === 'object' && !Array.isArray(result[i]) && (options != null && options.full || !result[i][formRangeKey])) {
var temp = _flatten(result[i], options);
Object.keys(temp).forEach(function (j) {
// Store temp in result
// ignore empty arrays
if (!Array.isArray(temp[j]) || Array.isArray(temp[j]) && temp[j].length) result[i + "." + j] = temp[j];
});
delete result[i];
}
});
return result;
};
// unflatten nested objects. For example: { 'a.b': v } -> { a: { b: v } }
var unflatten = function unflatten(formValue) {
var result = JSON.parse(JSON.stringify(formValue));
var specialKeys = Object.values(viewFormKeyMap);
Object.keys(result).filter(function (k) {
return !specialKeys.includes(k);
}).forEach(function (k) {
var parts = k.split('.');
var val = result[k];
delete result[k];
var parent = result;
while (parts.length > 1) {
var sub = parts.shift();
if (!parent[sub]) parent[sub] = {};
parent = parent[sub];
}
parent[parts.shift()] = val;
});
return result;
};
// converts from the external view format to the internal Form value format
var viewToFormValue = function viewToFormValue(view) {
var result = _extends({}, (view == null ? void 0 : view.properties) || {});
// convert { min: , max: } range to [min, max] for RangeSelector
Object.keys(result).forEach(function (key) {
var _result$key, _result$key2;
if (typeof ((_result$key = result[key]) == null ? void 0 : _result$key.min) === 'number' || typeof ((_result$key2 = result[key]) == null ? void 0 : _result$key2.max) === 'number') {
var _result$key3;
result[key] = (_result$key3 = {}, _result$key3[formRangeKey] = [result[key].min, result[key].max], _result$key3);
}
});
// convert formal view keys to their form '_' prefixed counterparts
Object.keys(viewFormKeyMap).forEach(function (key) {
if (view != null && view[key]) result[viewFormKeyMap[key]] = view[key];
});
// always have some blank search text
if (!result[formSearchKey]) result[formSearchKey] = '';
if (view != null && view.sort) result[formSortKey] = view.sort;
if (view != null && view.name) result[formViewNameKey] = view.name;
if (view != null && view.columns) result[formColumnsKey] = view.columns;
if (view != null && view.groupBy) result[formGroupByKey] = view.groupBy;
return unflatten(result);
};
// converts from the internal Form value format to the external view format
var formValueToView = function formValueToView(value, views) {
var result = {};
// if the user chose a view, use that
if (value[formViewNameKey]) result = JSON.parse(JSON.stringify(views.find(function (v) {
return v.name === value[formViewNameKey];
})));
var valueCopy = _extends({}, value);
Object.keys(viewFormKeyMap).forEach(function (key) {
if (valueCopy[viewFormKeyMap[key]]) {
result[key] = valueCopy[viewFormKeyMap[key]];
}
delete valueCopy[viewFormKeyMap[key]];
});
// flatten any nested objects
var flatValue = _flatten(valueCopy);
result.properties = _extends({}, result.properties || {}, flatValue);
// convert any ranges
Object.keys(result.properties).forEach(function (key) {
if (result.properties[key][formRangeKey]) {
result.properties[key] = {
min: result.properties[key][formRangeKey][0],
max: result.properties[key][formRangeKey][1]
};
}
});
return result;
};
// remove any empty arrays of property values by deleting the key for
// that property in the view properties
var clearEmpty = function clearEmpty(formValue, pendingReset) {
var value = formValue;
Object.keys(value).forEach(function (k) {
if (Array.isArray(value[k]) && value[k].length === 0) delete value[k];
// special case for when range selector returns to its min/max
// flat format with full: true needed to match filter name structure
// { a: b: { _range: [0, 100] } } ==> a.b._range: [0, 100]
if (typeof value[k] === 'object' && !Array.isArray(value[k])) {
var _pendingReset$current;
var filterName = k + "." + Object.keys(_flatten(value[k], {
full: true
}))[0];
if (pendingReset != null && (_pendingReset$current = pendingReset.current) != null && _pendingReset$current.has(filterName)) {
delete value[k];
pendingReset.current["delete"](filterName);
}
}
});
return value;
};
// if paging, when anything other than the page changes, reset the page to 1
var resetPage = function resetPage(nextFormValue, prevFormValue) {
if (prevFormValue[formPageKey] && prevFormValue[formPageKey] > 1)
// eslint-disable-next-line no-param-reassign
nextFormValue[formPageKey] = 1;
};
// function shared by onSubmit and onChange to coordinate view
// name changes
var normalizeValue = function normalizeValue(nextValue, prevValue, views, pendingReset) {
if (nextValue[formViewNameKey] && nextValue[formViewNameKey] !== prevValue[formViewNameKey]) {
// view name changed, reset view contents from named view
return viewToFormValue(views.find(function (v) {
return v.name === nextValue[formViewNameKey];
}));
}
// something else changed
// clear empty properties
var result = clearEmpty(nextValue, pendingReset);
// if we have a view and something changed, clear the view
if (result[formViewNameKey]) {
var view = views.find(function (v) {
return v.name === result[formViewNameKey];
});
var viewValue = viewToFormValue(view);
clearEmpty(viewValue, pendingReset);
if (Object.keys(viewValue).length !== Object.keys(result).length) {
delete result[formViewNameKey];
} else if (Object.keys(viewValue).some(function (k) {
return (
// allow mismatch between empty and set strings
viewValue[k] && result[k] && JSON.stringify(result[k]) !== JSON.stringify(viewValue[k])
);
})) {
delete result[formViewNameKey];
}
}
return result;
};
// 300ms was chosen empirically as a reasonable default
var DEBOUNCE_TIMEOUT = 300;
export var DataForm = function DataForm(_ref) {
var children = _ref.children,
footer = _ref.footer,
onDone = _ref.onDone,
pad = _ref.pad,
_ref$updateOn = _ref.updateOn,
updateOn = _ref$updateOn === void 0 ? 'submit' : _ref$updateOn,
rest = _objectWithoutPropertiesLoose(_ref, _excluded);
var _useContext = useContext(DataContext),
messages = _useContext.messages,
onView = _useContext.onView,
view = _useContext.view,
views = _useContext.views;
var _useContext2 = useContext(MessageContext),
format = _useContext2.format;
var _useState = useState(viewToFormValue(view)),
formValue = _useState[0],
setFormValue = _useState[1];
// special case for range selectors which always have a value.
// when value returns to its min/max, remove it from view
// like other properties
var pendingReset = useRef(new Set());
var contextValue = useMemo(function () {
return {
inDataForm: true,
pendingReset: pendingReset
};
}, []);
var debounce = useDebounce(DEBOUNCE_TIMEOUT);
var _useThemeValue = useThemeValue(),
theme = _useThemeValue.theme;
var onSubmit = useCallback(function (_ref2) {
var value = _ref2.value;
var nextValue = normalizeValue(value, formValue, views, pendingReset);
resetPage(nextValue, formValue);
setFormValue(nextValue);
onView(formValueToView(nextValue, views));
if (onDone) onDone();
}, [formValue, onDone, onView, views]);
var onChange = useCallback(function (value, _ref3) {
var touched = _ref3.touched;
var nextValue = normalizeValue(value, formValue, views, pendingReset);
resetPage(nextValue, formValue);
setFormValue(nextValue);
if (updateOn === 'change') {
// debounce search
if (touched[formSearchKey]) {
debounce(function () {
return function () {
return onView(formValueToView(nextValue, views));
};
});
} else {
onView(formValueToView(nextValue, views));
}
}
}, [debounce, formValue, onView, updateOn, views]);
useEffect(function () {
return setFormValue(viewToFormValue(view));
}, [view]);
var content = children;
if (footer !== false && updateOn === 'submit' || pad) {
var _theme$dataFilters, _theme$dataFilters2;
content = /*#__PURE__*/React.createElement(Box, {
fill: "vertical"
}, /*#__PURE__*/React.createElement(Box, {
flex: true,
overflow: "auto",
pad: {
horizontal: pad,
top: pad
}
}, /*#__PURE__*/React.createElement(Box, {
flex: false
}, content)), footer !== false && updateOn === 'submit' && /*#__PURE__*/React.createElement(Footer, {
flex: false,
pad: {
horizontal: pad,
bottom: pad
},
margin: (_theme$dataFilters = theme.dataFilters) == null || (_theme$dataFilters = _theme$dataFilters.footer) == null || (_theme$dataFilters = _theme$dataFilters.actions) == null ? void 0 : _theme$dataFilters.margin,
gap: (_theme$dataFilters2 = theme.dataFilters) == null || (_theme$dataFilters2 = _theme$dataFilters2.footer) == null || (_theme$dataFilters2 = _theme$dataFilters2.actions) == null ? void 0 : _theme$dataFilters2.gap
}, /*#__PURE__*/React.createElement(Button, {
label: format({
id: 'dataForm.submit',
messages: messages == null ? void 0 : messages.dataForm
}),
type: "submit",
primary: true
})));
}
return /*#__PURE__*/React.createElement(MaxForm, _extends({}, rest, {
value: formValue,
onSubmit: updateOn === 'submit' ? onSubmit : undefined,
onChange: onChange
}), /*#__PURE__*/React.createElement(DataFormContext.Provider, {
value: contextValue
}, content));
};