@sanity/form-builder
Version:
Sanity form builder
285 lines (278 loc) • 17.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Compositor = Compositor;
var _react = _interopRequireWildcard(require("react"));
var _portableTextEditor = require("@sanity/portable-text-editor");
var _types = require("@sanity/types");
var _ui = require("@sanity/ui");
var _components = require("@sanity/base/components");
var PathUtils = _interopRequireWildcard(require("@sanity/util/paths"));
var _ActivateOnFocus = _interopRequireDefault(require("../../components/ActivateOnFocus/ActivateOnFocus"));
var _BlockObject = require("./object/BlockObject");
var _InlineObject = require("./object/InlineObject");
var _EditObject = require("./object/EditObject");
var _text = require("./text");
var _Editor = require("./Editor");
var _Compositor = require("./Compositor.styles");
var _useObjectEditData = require("./hooks/useObjectEditData");
var _useScrollSelectionIntoView = require("./hooks/useScrollSelectionIntoView");
var _useObjectEditFormBuilderFocus = require("./hooks/useObjectEditFormBuilderFocus");
var _useObjectEditFormBuilderChange = require("./hooks/useObjectEditFormBuilderChange");
var _useHotKeys = require("./hooks/useHotKeys");
var _useScrollToFocusFromOutside = require("./hooks/useScrollToFocusFromOutside");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
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); }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
function _iterableToArrayLimit(arr, i) { var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"]; if (null != _i) { var _s, _e, _x, _r, _arr = [], _n = !0, _d = !1; try { if (_x = (_i = _i.call(arr)).next, 0 === i) { if (Object(_i) !== _i) return; _n = !1; } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0); } catch (err) { _d = !0, _e = err; } finally { try { if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return; } finally { if (_d) throw _e; } } return _arr; } }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
var ROOT_PATH = [];
var ACTIVATE_ON_FOCUS_MESSAGE = /*#__PURE__*/_react.default.createElement(_ui.Text, {
weight: "semibold"
}, "Click to activate");
function Compositor(props) {
var focusPath = props.focusPath,
hasFocus = props.hasFocus,
hotkeys = props.hotkeys,
isActive = props.isActive,
isFullscreen = props.isFullscreen,
markers = props.markers,
compareValue = props.compareValue,
onActivate = props.onActivate,
onChange = props.onChange,
onCopy = props.onCopy,
onFocus = props.onFocus,
onPaste = props.onPaste,
onToggleFullscreen = props.onToggleFullscreen,
presence = props.presence,
readOnly = props.readOnly,
renderBlockActions = props.renderBlockActions,
renderCustomMarkers = props.renderCustomMarkers,
value = props.value;
var editor = (0, _portableTextEditor.usePortableTextEditor)();
var _useState = (0, _react.useState)(null),
_useState2 = _slicedToArray(_useState, 2),
wrapperElement = _useState2[0],
setWrapperElement = _useState2[1];
var _useState3 = (0, _react.useState)(null),
_useState4 = _slicedToArray(_useState3, 2),
scrollElement = _useState4[0],
setScrollElement = _useState4[1];
var _useBoundaryElement = (0, _ui.useBoundaryElement)(),
boundaryElement = _useBoundaryElement.element;
// References to editor HTML elements that points to externally edited data (blocks, annotations, inline-objects)
var childEditorElementRef = (0, _react.useRef)();
var blockObjectElementRef = (0, _react.useRef)();
var inlineObjectElementRef = (0, _react.useRef)();
// Data about the current object inside the modal that is non-text (annotations, objects)
var objectEditData = (0, _useObjectEditData.useObjectEditData)(focusPath, {
block: blockObjectElementRef,
child: childEditorElementRef,
inline: inlineObjectElementRef
});
// Various focus handling hooks
var _useObjectEditFormBui = (0, _useObjectEditFormBuilderFocus.useObjectEditFormBuilderFocus)(onFocus),
onEditObjectFormBuilderFocus = _useObjectEditFormBui.onEditObjectFormBuilderFocus,
onEditObjectFormBuilderBlur = _useObjectEditFormBui.onEditObjectFormBuilderBlur,
onEditObjectClose = _useObjectEditFormBui.onEditObjectClose;
var _useObjectEditFormBui2 = (0, _useObjectEditFormBuilderChange.useObjectEditFormBuilderChange)(onChange),
onObjectEditFormBuilderChange = _useObjectEditFormBui2.onObjectEditFormBuilderChange;
// This is what PortableTextEditor will use to scroll the content into view when editing
// inside the editor
var scrollSelectionIntoView = (0, _useScrollSelectionIntoView.useScrollSelectionIntoView)(scrollElement);
// This will scroll to the content when focusPath is set from the outside
(0, _useScrollToFocusFromOutside.useScrollToFocusFromOutside)(hasFocus, focusPath, objectEditData, scrollElement);
var handleToggleFullscreen = (0, _react.useCallback)(() => {
onToggleFullscreen();
}, [onToggleFullscreen]);
var hotkeysWithFullscreenToggle = (0, _react.useMemo)(() => _objectSpread(_objectSpread({}, hotkeys), {}, {
custom: _objectSpread({
'mod+enter': onToggleFullscreen
}, (hotkeys === null || hotkeys === void 0 ? void 0 : hotkeys.custom) || {})
}), [hotkeys, onToggleFullscreen]);
var editorHotkeys = (0, _useHotKeys.useHotkeys)(hotkeysWithFullscreenToggle);
var editObjectKey = (0, _react.useMemo)(() => {
var last = objectEditData === null || objectEditData === void 0 ? void 0 : objectEditData.editorPath.slice(-1)[0];
if (last && (0, _types.isKeySegment)(last)) {
return last._key;
}
return null;
}, [objectEditData === null || objectEditData === void 0 ? void 0 : objectEditData.editorPath]);
var ptFeatures = (0, _react.useMemo)(() => _portableTextEditor.PortableTextEditor.getPortableTextFeatures(editor), [editor]);
var hasContent = !!value;
var initialSelection = (0, _react.useMemo)(() => focusPath && focusPath.length > 0 ? {
anchor: {
path: focusPath,
offset: 0
},
focus: {
path: focusPath,
offset: 0
}
} : null,
// eslint-disable-next-line react-hooks/exhaustive-deps
[] // Empty!
);
var renderBlock = (0, _react.useCallback)((block, blockType, attributes, defaultRender) => {
var isTextBlock = block._type === ptFeatures.types.block.name;
var blockRef = /*#__PURE__*/_react.default.createRef();
var blockMarkers = markers.filter(marker => (0, _types.isKeySegment)(marker.path[0]) && marker.path[0]._key === block._key);
if (isTextBlock) {
return /*#__PURE__*/_react.default.createElement(_text.TextBlock, {
attributes: attributes,
block: block,
compareValue: PathUtils.get(compareValue, [{
_key: block._key
}]),
blockRef: blockRef,
isFullscreen: isFullscreen,
markers: blockMarkers,
onChange: onChange,
readOnly: readOnly,
renderBlockActions: hasContent && renderBlockActions,
renderCustomMarkers: hasContent && renderCustomMarkers
}, defaultRender(block));
}
var useBlockObjectElementRef = block._key === editObjectKey;
return /*#__PURE__*/_react.default.createElement(_BlockObject.BlockObject, {
attributes: attributes,
block: block,
compareValue: PathUtils.get(compareValue, [{
_key: block._key
}]),
blockRef: blockRef,
editor: editor,
isFullscreen: isFullscreen,
markers: blockMarkers,
onChange: onChange,
onFocus: onFocus,
readOnly: readOnly,
ref: useBlockObjectElementRef ? blockObjectElementRef : undefined,
renderBlockActions: hasContent && renderBlockActions,
renderCustomMarkers: hasContent && renderCustomMarkers,
type: blockType
});
}, [compareValue, editObjectKey, editor, hasContent, isFullscreen, markers, onChange, onFocus, ptFeatures.types.block.name, readOnly, renderBlockActions, renderCustomMarkers]);
var renderChild = (0, _react.useCallback)((child, childType, attributes, defaultRender) => {
var isSpan = child._type === ptFeatures.types.span.name;
var useRefElm = child._key === editObjectKey;
if (isSpan) {
return /*#__PURE__*/_react.default.createElement("span", {
ref: useRefElm ? childEditorElementRef : undefined
}, defaultRender(child));
}
var childMarkers = markers.filter(marker => (0, _types.isKeySegment)(marker.path[2]) && marker.path[2]._key === child._key);
return /*#__PURE__*/_react.default.createElement(_InlineObject.InlineObject, {
attributes: attributes,
isEditing: !!editObjectKey,
markers: childMarkers,
onFocus: onFocus,
readOnly: readOnly,
ref: useRefElm ? inlineObjectElementRef : undefined,
renderCustomMarkers: renderCustomMarkers,
scrollElement: scrollElement,
type: childType,
value: child
});
}, [editObjectKey, markers, onFocus, ptFeatures.types.span.name, readOnly, renderCustomMarkers, scrollElement]);
var renderAnnotation = (0, _react.useCallback)((annotation, annotationType, attributes, defaultRender) => {
var annotationMarkers = markers.filter(marker => (0, _types.isKeySegment)(marker.path[2]) && marker.path[2]._key === annotation._key);
var hasError = annotationMarkers.filter(marker => marker.type === 'validation' && marker.level === 'error').length > 0;
var hasWarning = annotationMarkers.filter(marker => marker.type === 'validation' && marker.level === 'warning').length > 0;
return /*#__PURE__*/_react.default.createElement(_text.Annotation, {
attributes: attributes,
hasError: hasError,
hasWarning: hasWarning,
markers: annotationMarkers,
onFocus: onFocus,
readOnly: readOnly,
renderCustomMarkers: renderCustomMarkers,
scrollElement: scrollElement,
type: annotationType,
value: annotation
}, defaultRender());
}, [markers, onFocus, readOnly, renderCustomMarkers, scrollElement]);
var _useState5 = (0, _react.useState)(null),
_useState6 = _slicedToArray(_useState5, 2),
portalElement = _useState6[0],
setPortalElement = _useState6[1];
var editorNode = (0, _react.useMemo)(() => /*#__PURE__*/_react.default.createElement(_Editor.Editor, {
hotkeys: editorHotkeys,
initialSelection: initialSelection,
isActive: isActive,
isFullscreen: isFullscreen,
onFocus: onFocus,
onCopy: onCopy,
onPaste: onPaste,
onToggleFullscreen: handleToggleFullscreen,
readOnly: readOnly,
renderAnnotation: renderAnnotation,
renderBlock: renderBlock,
renderChild: renderChild,
setPortalElement: setPortalElement,
scrollElement: scrollElement,
scrollSelectionIntoView: scrollSelectionIntoView,
setScrollElement: setScrollElement
}),
// Keep only stable ones here!
[editorHotkeys, handleToggleFullscreen, initialSelection, isFullscreen, isActive, onCopy, onFocus, onPaste, readOnly, renderAnnotation, renderBlock, renderChild, scrollElement, scrollSelectionIntoView]);
var boundaryElm = isFullscreen ? scrollElement : boundaryElement;
var editObjectNode = (0, _react.useMemo)(() => /*#__PURE__*/_react.default.createElement(_ui.BoundaryElementProvider, {
element: boundaryElm
}, /*#__PURE__*/_react.default.createElement(_EditObject.EditObject, {
focusPath: focusPath,
objectEditData: objectEditData,
markers: markers // TODO: filter relevant?
,
onBlur: onEditObjectFormBuilderBlur,
onChange: onObjectEditFormBuilderChange,
onClose: onEditObjectClose,
onFocus: onEditObjectFormBuilderFocus,
readOnly: readOnly,
presence: presence,
scrollElement: boundaryElm,
value: value
})), [boundaryElm, focusPath, markers, objectEditData, onEditObjectClose, onEditObjectFormBuilderBlur, onEditObjectFormBuilderFocus, onObjectEditFormBuilderChange, presence, readOnly, value]);
var children = (0, _react.useMemo)(() => /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, editorNode, /*#__PURE__*/_react.default.createElement(_ui.BoundaryElementProvider, {
element: boundaryElm
}, editObjectNode)), [boundaryElm, editObjectNode, editorNode]);
var portal = (0, _ui.usePortal)();
var portalElements = (0, _react.useMemo)(() => ({
collapsed: wrapperElement,
default: portal.element,
editor: portalElement,
expanded: portal.element
}), [portal.element, portalElement, wrapperElement]);
return /*#__PURE__*/_react.default.createElement(_ui.PortalProvider, {
__unstable_elements: portalElements
}, /*#__PURE__*/_react.default.createElement(_ActivateOnFocus.default, {
message: ACTIVATE_ON_FOCUS_MESSAGE,
onActivate: onActivate,
isOverlayActive: !isActive
}, /*#__PURE__*/_react.default.createElement(_components.ChangeIndicatorWithProvidedFullPath, {
compareDeep: true,
value: value,
hasFocus: hasFocus && objectEditData === null,
path: ROOT_PATH
}, /*#__PURE__*/_react.default.createElement(_Compositor.Root, {
"data-focused": hasFocus ? '' : undefined,
"data-read-only": readOnly ? '' : undefined
}, /*#__PURE__*/_react.default.createElement("div", {
"data-wrapper": "",
ref: setWrapperElement
}, /*#__PURE__*/_react.default.createElement(_ui.Portal, {
__unstable_name: isFullscreen ? 'expanded' : 'collapsed'
}, isFullscreen ? /*#__PURE__*/_react.default.createElement(_Compositor.ExpandedLayer, null, children) : children)), /*#__PURE__*/_react.default.createElement("div", {
"data-border": ""
})))));
}