UNPKG

@sanity/form-builder

Version:
427 lines (422 loc) • 18.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ArrayInput = void 0; exports.createProtoValue = createProtoValue; var _isPlainObject2 = _interopRequireDefault(require("lodash/isPlainObject")); var _types = require("@sanity/types"); var _paths = require("@sanity/util/paths"); var _components = require("@sanity/base/components"); var _ui = require("@sanity/ui"); var _react = _interopRequireDefault(require("react")); var _operators = require("rxjs/operators"); var _content = require("@sanity/util/content"); var _PatchEvent = require("../../../PatchEvent"); var _Alert = require("../../../components/Alert"); var _Details = require("../../../components/Details"); var _list = require("../common/list"); var _empty = require("../../../utils/empty"); var _applyPatch = require("../../../patch/applyPatch"); var _common = require("../../common"); var _item = require("./item"); var _uploadTarget = require("./uploadTarget/uploadTarget"); var _helpers = require("./item/helpers"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); } function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } var UploadTargetFieldsetMemo; function getUploadTargetFieldset() { if (!UploadTargetFieldsetMemo) { UploadTargetFieldsetMemo = (0, _uploadTarget.uploadTarget)(_components.FormFieldSet); } return UploadTargetFieldsetMemo; } function createProtoValue(type) { if (!(0, _types.isObjectSchemaType)(type)) { throw new Error("Invalid item type: \"".concat(type.type, "\". Default array input can only contain objects (for now)")); } var _key = (0, _content.randomKey)(12); return type.name === 'object' ? { _key } : { _type: type.name, _key }; } class ArrayInput extends _react.default.Component { constructor() { super(...arguments); _defineProperty(this, "_focusArea", null); _defineProperty(this, "toast", null); _defineProperty(this, "uploadSubscriptions", {}); _defineProperty(this, "state", { isResolvingInitialValue: false }); _defineProperty(this, "insert", (item, position, path) => { var onChange = this.props.onChange; onChange(_PatchEvent.PatchEvent.from((0, _PatchEvent.setIfMissing)([]), (0, _PatchEvent.insert)([item], position, path))); }); _defineProperty(this, "handlePrepend", value => { this.handleInsert({ item: value, position: 'before', path: [0] }); }); _defineProperty(this, "handleAppend", value => { this.handleInsert({ item: value, position: 'after', path: [-1] }); }); _defineProperty(this, "handleInsert", event => { var _this$props = this.props, resolveInitialValue = _this$props.resolveInitialValue, onFocus = _this$props.onFocus; this.setState({ isResolvingInitialValue: true }); var memberType = this.getMemberTypeOfItem(event.item); var resolvedInitialValue = (0, _helpers.isEmpty)(event.item) && resolveInitialValue ? resolveInitialValue(memberType, event.item) : Promise.resolve({}); resolvedInitialValue.then(initial => _objectSpread(_objectSpread({}, event.item), initial)).then(value => { this.insert(value, event.position, event.path); }, error => { var _this$toast; (_this$toast = this.toast) === null || _this$toast === void 0 ? void 0 : _this$toast.push({ title: "Could not resolve initial value", description: "Unable to resolve initial value for type: ".concat(memberType.name, ": ").concat(error.message, "."), status: 'error' }); this.insert(event.item, event.position, event.path); }).finally(() => { this.setState({ isResolvingInitialValue: false }); if (event.edit === false) { onFocus([{ _key: event.item._key }]); } else { this.openItem(event.item._key); } }); }); _defineProperty(this, "handleRemoveItem", item => { this.removeItem(item); }); _defineProperty(this, "handleFocus", event => { // We want to handle focus when the array input *itself* element receives // focus, not when a child element receives focus, but React has decided // to let focus bubble, so this workaround is needed // Background: https://github.com/facebook/react/issues/6410#issuecomment-671915381 if (event.currentTarget === event.target && event.currentTarget === this._focusArea) { this.props.onFocus([]); } }); _defineProperty(this, "openItem", key => { this.props.onFocus([{ _key: key }, _paths.FOCUS_TERMINATOR]); }); _defineProperty(this, "handleItemChange", (event, item) => { var _this$props2 = this.props, onChange = _this$props2.onChange, value = _this$props2.value; var memberType = this.getMemberTypeOfItem(item); if (!memberType) { // eslint-disable-next-line no-console console.log('Could not find member type of item ', item); return; } var key = item._key || (0, _content.randomKey)(12); onChange(event.prefixAll({ _key: key }).prepend(item._key ? [] : (0, _PatchEvent.set)(key, [value.indexOf(item), '_key']))); }); _defineProperty(this, "handleSortEnd", event => { var _this$props3 = this.props, value = _this$props3.value, onChange = _this$props3.onChange; var item = value[event.oldIndex]; var refItem = value[event.newIndex]; if (!item._key || !refItem._key) { // eslint-disable-next-line no-console console.error('Neither the item you are moving nor the item you are moving to have a key. Cannot continue.'); return; } if (event.oldIndex === event.newIndex || item._key === refItem._key) { return; } onChange(_PatchEvent.PatchEvent.from((0, _PatchEvent.unset)([{ _key: item._key }]), (0, _PatchEvent.insert)([item], event.oldIndex > event.newIndex ? 'before' : 'after', [{ _key: refItem._key }]))); }); _defineProperty(this, "setFocusArea", el => { this._focusArea = el; }); _defineProperty(this, "handleFixMissingKeys", () => { var _this$props4 = this.props, onChange = _this$props4.onChange, value = _this$props4.value; var patches = value.map((val, i) => (0, _PatchEvent.setIfMissing)((0, _content.randomKey)(), [i, '_key'])); onChange(_PatchEvent.PatchEvent.from(...patches)); }); _defineProperty(this, "setToast", toast => { this.toast = toast; }); _defineProperty(this, "handleRemoveNonObjectValues", () => { var _this$props5 = this.props, onChange = _this$props5.onChange, value = _this$props5.value; var nonObjects = value.reduce((acc, val, i) => (0, _isPlainObject2.default)(val) ? acc : acc.concat(i), []).reverse(); var patches = nonObjects.map(index => (0, _PatchEvent.unset)([index])); onChange(_PatchEvent.PatchEvent.from(...patches)); }); _defineProperty(this, "handleUpload", _ref => { var file = _ref.file, type = _ref.type, uploader = _ref.uploader; var onChange = this.props.onChange; var item = createProtoValue(type); var key = item._key; this.insert(item, 'after', [-1]); var events$ = uploader.upload(file, type).pipe((0, _operators.map)(uploadEvent => _PatchEvent.PatchEvent.from(uploadEvent.patches || []).prefixAll({ _key: key }))); this.uploadSubscriptions = _objectSpread(_objectSpread({}, this.uploadSubscriptions), {}, { [key]: events$.subscribe(onChange) }); }); _defineProperty(this, "handleFocusItem", item => { this.openItem(item._key); }); } removeItem(item) { var _this$props6 = this.props, onChange = _this$props6.onChange, onFocus = _this$props6.onFocus, value = _this$props6.value; // create a patch for removing the item var patch = _PatchEvent.PatchEvent.from((0, _PatchEvent.unset)((0, _types.isKeySegment)(item) ? [{ _key: item._key }] : [value.indexOf(item)])); // apply the patch to the current value var result = (0, _applyPatch.applyAll)(value || [], patch.patches); // if the result is an empty array if (Array.isArray(result) && !result.length) { // then unset the value onChange(_PatchEvent.PatchEvent.from((0, _PatchEvent.unset)())); } else { // otherwise apply the patch onChange(patch); } if (item._key in this.uploadSubscriptions) { this.uploadSubscriptions[item._key].unsubscribe(); } // move focus to the nearest sibling var idx = value.indexOf(item); var nearestSibling = value[idx + 1] || value[idx - 1]; // if there's no siblings we want to focus the input itself onFocus(nearestSibling ? [{ _key: nearestSibling._key }] : []); } getMemberTypeOfItem(item) { var type = this.props.type; var itemTypeName = (0, _content.resolveTypeName)(item); return type.of.find(memberType => memberType.name === itemTypeName); } focus() { if (this._focusArea) { this._focusArea.focus(); } } render() { var _this$props7 = this.props, type = _this$props7.type, _this$props7$level = _this$props7.level, level = _this$props7$level === void 0 ? 1 : _this$props7$level, markers = _this$props7.markers, readOnly = _this$props7.readOnly, onChange = _this$props7.onChange, _this$props7$value = _this$props7.value, value = _this$props7$value === void 0 ? [] : _this$props7$value, presence = _this$props7.presence, focusPath = _this$props7.focusPath, onBlur = _this$props7.onBlur, resolveUploader = _this$props7.resolveUploader, onFocus = _this$props7.onFocus, compareValue = _this$props7.compareValue, filterField = _this$props7.filterField, ReferenceItemComponent = _this$props7.ReferenceItemComponent, ArrayFunctionsImpl = _this$props7.ArrayFunctionsImpl, _this$props7$ArrayIte = _this$props7.ArrayItemImpl, ArrayItemImpl = _this$props7$ArrayIte === void 0 ? _item.ArrayItem : _this$props7$ArrayIte; var isResolvingInitialValue = this.state.isResolvingInitialValue; var hasNonObjectValues = (value || []).some(item => !(0, _isPlainObject2.default)(item)); if (hasNonObjectValues) { return /*#__PURE__*/_react.default.createElement(_components.FormFieldSet, { title: type.title, description: type.description, level: level - 1, tabIndex: 0, onFocus: this.handleFocus, ref: this.setFocusArea, __unstable_markers: markers }, /*#__PURE__*/_react.default.createElement(_Alert.Alert, { status: "error", suffix: /*#__PURE__*/_react.default.createElement(_ui.Stack, { padding: 2 }, /*#__PURE__*/_react.default.createElement(_ui.Button, { onClick: this.handleRemoveNonObjectValues, text: "Remove non-object values", tone: "critical" })), title: /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "Invalid list values") }, /*#__PURE__*/_react.default.createElement(_ui.Text, { as: "p", muted: true, size: 1 }, "Some items in this list are not objects. This must be fixed in order to edit the list."), /*#__PURE__*/_react.default.createElement(_Details.Details, { marginTop: 4, open: __DEV__, title: /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "Developer info") }, /*#__PURE__*/_react.default.createElement(_ui.Stack, { space: 3 }, /*#__PURE__*/_react.default.createElement(_ui.Text, { as: "p", muted: true, size: 1 }, "This usually happens when items are created using an API client, or when a custom input component has added invalid data to the list."))))); } var options = type.options || {}; var hasMissingKeys = value.some(item => !item._key); var isSortable = options.sortable !== false && !hasMissingKeys; var isGrid = options.layout === 'grid'; var fieldPresence = presence.filter(item => item.path.length === 0); var UploadTargetFieldset = getUploadTargetFieldset(); return /*#__PURE__*/_react.default.createElement(UploadTargetFieldset, { __unstable_changeIndicator: false, tabIndex: 0, title: type.title, description: type.description, onFocus: this.handleFocus, onBlur: onBlur, level: level - 1, __unstable_presence: fieldPresence.length > 0 ? fieldPresence : _empty.EMPTY_ARRAY, __unstable_markers: markers, disabled: readOnly, ref: this.setFocusArea, resolveUploader: resolveUploader, types: type.of, onUpload: this.handleUpload }, /*#__PURE__*/_react.default.createElement(_components.ImperativeToast, { ref: this.setToast }), /*#__PURE__*/_react.default.createElement(_ui.Stack, { space: 3 }, hasMissingKeys && /*#__PURE__*/_react.default.createElement(_Alert.Alert, { status: "warning", suffix: /*#__PURE__*/_react.default.createElement(_ui.Stack, { padding: 2 }, /*#__PURE__*/_react.default.createElement(_ui.Button, { onClick: this.handleFixMissingKeys, text: "Add missing keys", tone: "caution" })), title: /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "Missing keys") }, /*#__PURE__*/_react.default.createElement(_ui.Text, { as: "p", muted: true, size: 1 }, "Some items in the list are missing their keys. This must be fixed in order to edit the list."), /*#__PURE__*/_react.default.createElement(_Details.Details, { marginTop: 4, open: __DEV__, title: /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "Developer info") }, /*#__PURE__*/_react.default.createElement(_ui.Stack, { space: 3 }, /*#__PURE__*/_react.default.createElement(_ui.Text, { as: "p", muted: true, size: 1 }, "This usually happens when items are created using an API client, and the", ' ', /*#__PURE__*/_react.default.createElement("code", null, "_key"), " property has not been included."), /*#__PURE__*/_react.default.createElement(_ui.Text, { as: "p", muted: true, size: 1 }, "The value of the ", /*#__PURE__*/_react.default.createElement("code", null, "_key"), " property must be a unique string.")))), /*#__PURE__*/_react.default.createElement(_ui.Stack, { "data-ui": "ArrayInput__content", space: 3 }, ((value === null || value === void 0 ? void 0 : value.length) > 0 || isResolvingInitialValue) && /*#__PURE__*/_react.default.createElement(_ui.Card, { border: true, radius: 1, paddingY: isGrid ? 2 : 1, paddingX: isGrid ? 2 : undefined }, /*#__PURE__*/_react.default.createElement(_list.List, { onSortEnd: this.handleSortEnd, isSortable: isSortable, isGrid: isGrid }, value.map((item, index) => { var _this$getMemberTypeOf; return /*#__PURE__*/_react.default.createElement(_list.Item, { key: item._key || index, isSortable: isSortable, isGrid: isGrid, index: index }, /*#__PURE__*/_react.default.createElement(_common.ConditionalReadOnlyField, { readOnly: readOnly || ((_this$getMemberTypeOf = this.getMemberTypeOfItem(item)) === null || _this$getMemberTypeOf === void 0 ? void 0 : _this$getMemberTypeOf.readOnly), value: item, parent: value }, /*#__PURE__*/_react.default.createElement(ArrayItemImpl, { compareValue: compareValue, filterField: filterField, focusPath: focusPath, itemKey: item._key, index: index, markers: markers, ReferenceItemComponent: ReferenceItemComponent, onBlur: onBlur, onChange: this.handleItemChange, onFocus: onFocus, onRemove: this.handleRemoveItem, onInsert: this.handleInsert, presence: presence, readOnly: readOnly || hasMissingKeys, type: type, value: item }))); }), isResolvingInitialValue && /*#__PURE__*/_react.default.createElement(_list.Item, { isGrid: isGrid, index: -1 }, /*#__PURE__*/_react.default.createElement(_ui.Card, { radius: 1, padding: 1 }, /*#__PURE__*/_react.default.createElement(_ui.Flex, { align: "center", justify: "center", padding: 3 }, /*#__PURE__*/_react.default.createElement(_ui.Box, { marginX: 3 }, /*#__PURE__*/_react.default.createElement(_ui.Spinner, { muted: true })), /*#__PURE__*/_react.default.createElement(_ui.Text, null, "Resolving initial value\u2026")))))), /*#__PURE__*/_react.default.createElement(ArrayFunctionsImpl, { type: type, value: value, readOnly: readOnly, onAppendItem: this.handleAppend, onPrependItem: this.handlePrepend, onFocusItem: this.handleFocusItem, onCreateValue: createProtoValue, onChange: onChange })))); } } exports.ArrayInput = ArrayInput; _defineProperty(ArrayInput, "defaultProps", { focusPath: [] });