@sanity/form-builder
Version:
Sanity form builder
295 lines (292 loc) • 13 kB
JavaScript
"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;