UNPKG

@sanity/form-builder

Version:
285 lines (278 loc) • 17.2 kB
"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": "" }))))); }