@sanity/form-builder
Version:
Sanity form builder
427 lines (422 loc) • 18.9 kB
JavaScript
"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: []
});