UNPKG

@sanity/form-builder

Version:
295 lines (292 loc) • 13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ArrayOfPrimitivesInput = void 0; var _get2 = _interopRequireDefault(require("lodash/get")); var _react = _interopRequireDefault(require("react")); var _paths = require("@sanity/util/paths"); var _ui = require("@sanity/ui"); var _components = require("@sanity/base/components"); var _content = require("@sanity/util/content"); var _PatchEvent = require("../../../PatchEvent"); var _list = require("../common/list"); var _common = require("../../common"); var _getEmptyValue = _interopRequireDefault(require("./getEmptyValue")); var _ItemRow = require("./ItemRow"); var _nearestIndex = require("./utils/nearestIndex"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 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 NO_MARKERS = []; function move(arr, from, to) { var copy = arr.slice(); var val = copy[from]; copy.splice(from, 1); copy.splice(to, 0, val); return copy; } /** * @param index index to insert item after. An index of -1 will prepend the item * @param arr the array to insert the item into * @param item the item to insert * * @example * Inserts "hello" at the beginning * ```js * insertAfter(-1, ["one", "two"], "hello") * // => ["hello", "one", "two"] * ``` */ function insertAfter(index, arr, item) { var copy = arr.slice(); copy.splice(index + 1, 0, item); return copy; } class ArrayOfPrimitivesInput extends _react.default.PureComponent { constructor() { super(...arguments); _defineProperty(this, "_element", null); _defineProperty(this, "_lastAddedIndex", -1); _defineProperty(this, "handleAppend", itemValue => { var _this$props = this.props, _this$props$value = _this$props.value, value = _this$props$value === void 0 ? [] : _this$props$value, onFocus = _this$props.onFocus; this.set(value.concat(itemValue)); onFocus([value.length]); }); _defineProperty(this, "handlePrepend", itemValue => { var _this$props2 = this.props, _this$props2$value = _this$props2.value, value = _this$props2$value === void 0 ? [] : _this$props2$value, onFocus = _this$props2.onFocus; this.set([itemValue].concat(value)); onFocus([value.length]); }); _defineProperty(this, "handleRemoveItem", index => { this.removeAt(index); }); _defineProperty(this, "handleInsert", (pos, index, item) => { var _this$props3 = this.props, _this$props3$value = _this$props3.value, value = _this$props3$value === void 0 ? [] : _this$props3$value, onFocus = _this$props3.onFocus; var insertIndex = index + (pos === 'before' ? -1 : 0); this.set(insertAfter(insertIndex, value, item)); onFocus([insertIndex + 1]); }); _defineProperty(this, "handleItemChange", event => { this._lastAddedIndex = -1; this.props.onChange(event); }); _defineProperty(this, "handleItemEnterKey", index => { var _this$props$type; var firstType = (_this$props$type = this.props.type) === null || _this$props$type === void 0 ? void 0 : _this$props$type.of[0]; if (firstType) { this.insertAfter(index, firstType); this._lastAddedIndex = index + 1; } }); _defineProperty(this, "handleItemEscapeKey", index => { var value = this.props.value; if (index === this._lastAddedIndex && value[index] === '') { this.removeAt(index); } }); _defineProperty(this, "handleSortEnd", event => { var _this$props4 = this.props, value = _this$props4.value, onFocus = _this$props4.onFocus; var oldIndex = event.oldIndex, newIndex = event.newIndex; this.set(move(value, oldIndex, newIndex)); onFocus([newIndex]); }); _defineProperty(this, "setElement", el => { this._element = el; }); _defineProperty(this, "handleFocusRoot", 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._element) { this.props.onFocus([]); } }); _defineProperty(this, "handleFocusItem", (item, index) => { this.props.onFocus([index]); }); } set(nextValue) { this._lastAddedIndex = -1; var patch = nextValue.length === 0 ? (0, _PatchEvent.unset)() : (0, _PatchEvent.set)(nextValue); this.props.onChange(_PatchEvent.PatchEvent.from(patch)); } removeAt(index) { var _this$props$value2 = this.props.value, value = _this$props$value2 === void 0 ? [] : _this$props$value2; this.set(value.filter((_, i) => i !== index)); this.props.onFocus([Math.max(0, index - 1)]); } insertAfter(index, type) { var _this$props5 = this.props, _this$props5$value = _this$props5.value, value = _this$props5$value === void 0 ? [] : _this$props5$value, onFocus = _this$props5.onFocus; var emptyValue = (0, _getEmptyValue.default)(type); if (emptyValue === undefined) { throw new Error("Cannot create empty primitive value from ".concat(type.name)); } this.set(insertAfter(index, value, emptyValue)); onFocus([index + 1]); } getMemberType(typeName) { var type = this.props.type; return type === null || type === void 0 ? void 0 : type.of.find(memberType => memberType.name === typeName || memberType.jsonType === typeName); } focus() { if (this._element) { this._element.focus(); } } getSnapshotBeforeUpdate(prevProps, prevState) { var _prevProps$focusPath = prevProps.focusPath, prevFocusPath = _prevProps$focusPath === void 0 ? [] : _prevProps$focusPath, _prevProps$value = prevProps.value, prevValue = _prevProps$value === void 0 ? [] : _prevProps$value; var _this$props6 = this.props, _this$props6$focusPat = _this$props6.focusPath, focusPath = _this$props6$focusPat === void 0 ? [] : _this$props6$focusPat, _this$props6$value = _this$props6.value, value = _this$props6$value === void 0 ? [] : _this$props6$value; if (prevFocusPath[0] === focusPath[0] && prevValue.length !== value.length) { var _selection$focusNode; // the length of the array has changed, but the focus path has not, which may happen if someone inserts or removes a new item above the one currently in focus var focusIndex = focusPath[0]; var selection = window.getSelection(); if (!(selection.focusNode instanceof HTMLElement)) { return null; } var input = (_selection$focusNode = selection.focusNode) === null || _selection$focusNode === void 0 ? void 0 : _selection$focusNode.querySelector('input,textarea'); return input instanceof HTMLInputElement ? { prevFocusedIndex: focusIndex, restoreSelection: { text: selection.toString(), start: input.selectionStart, end: input.selectionEnd, value: input.value } } : {}; } return null; } componentDidUpdate(prevProps, prevState, snapshot) { if (snapshot !== null && snapshot !== void 0 && snapshot.restoreSelection && prevProps.value) { var prevFocusedValue = prevProps.value[snapshot.prevFocusedIndex]; var nearestIndex = (0, _nearestIndex.nearestIndexOf)(this.props.value || [], snapshot.prevFocusedIndex, prevFocusedValue); if (nearestIndex === -1) { return; } var newInput = this._element.querySelector("[data-item-index='".concat(nearestIndex, "'] input,textarea")); if (newInput instanceof HTMLInputElement) { newInput.focus(); try { newInput.setSelectionRange(snapshot.restoreSelection.start, snapshot.restoreSelection.end); } catch (_unused) { // not all inputs supports selection (e.g. <input type="number" />) } } this.props.onFocus([nearestIndex]); } } render() { var _this$props7 = this.props, type = _this$props7.type, value = _this$props7.value, _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, onFocus = _this$props7.onFocus, presence = _this$props7.presence, compareValue = _this$props7.compareValue, focusPath = _this$props7.focusPath, ArrayFunctionsImpl = _this$props7.ArrayFunctionsImpl, onBlur = _this$props7.onBlur; var isSortable = !readOnly && (0, _get2.default)(type, 'options.sortable') !== false; return /*#__PURE__*/_react.default.createElement(_components.FormFieldSet, { title: type === null || type === void 0 ? void 0 : type.title, description: type === null || type === void 0 ? void 0 : type.description, level: level - 1, tabIndex: 0, onFocus: this.handleFocusRoot, ref: this.setElement, __unstable_presence: presence.filter(item => item.path[0] === '$' || item.path.length === 0), __unstable_changeIndicator: false, __unstable_markers: markers }, /*#__PURE__*/_react.default.createElement(_ui.Stack, { space: 3 }, /*#__PURE__*/_react.default.createElement(_ui.Stack, { space: 1 }, value && value.length > 0 && /*#__PURE__*/_react.default.createElement(_ui.Card, { padding: 1, border: true }, /*#__PURE__*/_react.default.createElement(_list.List, { onSortEnd: this.handleSortEnd, isSortable: isSortable }, value.map((item, index) => { var _this$getMemberType; var filteredMarkers = markers.filter(marker => (0, _paths.startsWith)([index], marker.path)); var childPresence = presence.filter(pItem => (0, _paths.startsWith)([index], pItem.path)); var memberType = this.getMemberType((0, _content.resolveTypeName)(item)); // Best effort attempt to make a stable key for each item in the array // Since items may be reordered and change at any time, there's no way to reliably address each item uniquely // This is a "best effort"-attempt at making sure we don't re-use internal state for item inputs // when items gets added or removed to the array var key = "".concat((memberType === null || memberType === void 0 ? void 0 : memberType.name) || 'invalid-type', "-").concat(String(index)); return /*#__PURE__*/_react.default.createElement(_list.Item, { key: key, index: index, "data-item-index": index, isSortable: isSortable }, /*#__PURE__*/_react.default.createElement(_common.ConditionalReadOnlyField, { readOnly: readOnly || ((_this$getMemberType = this.getMemberType((0, _content.resolveTypeName)(item))) === null || _this$getMemberType === void 0 ? void 0 : _this$getMemberType.readOnly), value: item, parent: value }, /*#__PURE__*/_react.default.createElement(_ItemRow.ItemRow, { level: level + 1, index: index, value: item, compareValue: compareValue, readOnly: readOnly, markers: filteredMarkers.length === 0 ? NO_MARKERS : filteredMarkers, isSortable: isSortable, type: memberType, focusPath: focusPath, onFocus: onFocus, onBlur: onBlur, insertableTypes: type.of, onEnterKey: this.handleItemEnterKey, onEscapeKey: this.handleItemEscapeKey, onChange: this.handleItemChange, onInsert: this.handleInsert, onRemove: this.handleRemoveItem, presence: childPresence }))); })))), /*#__PURE__*/_react.default.createElement(ArrayFunctionsImpl, { type: type, value: value, readOnly: readOnly, onAppendItem: this.handleAppend, onPrependItem: this.handlePrepend, onFocusItem: this.handleFocusItem, onCreateValue: _getEmptyValue.default, onChange: onChange }))); } } exports.ArrayOfPrimitivesInput = ArrayOfPrimitivesInput;