@atlaskit/editor-plugin-block-controls
Version:
Block controls plugin for @atlaskit/editor-core
192 lines (191 loc) • 11.1 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
import { useLayoutEffect, useRef, useState } from 'react';
import { bind } from 'bind-event-listener';
import { getDocument } from '@atlaskit/browser-apis';
import { isCSSAnchorSupported } from '@atlaskit/editor-common/styles/native-anchor';
import { getNodeTypeWithLevel } from '../pm-plugins/decorations-common';
import { getNodeContentElement } from '../ui/utils/get-node-content-element';
import { getAbsoluteSurfacePlacement, getLeftGutterSurfacePlacement, getSurfacePlacement, toMeasuredSurfaceWrapperPlacement } from '../ui/utils/get-surface-placement';
var EMPTY_PLACEMENTS = new Map();
export var getSurfaceKey = function getSurfaceKey(side, descriptor) {
return "".concat(side, ":").concat(descriptor.anchorName);
};
/**
* Measures surface placements from DOM geometry when the browser cannot position surfaces with CSS
* anchors. Returns an empty map and binds nothing when anchors are supported. Both sides share one
* set of listeners, and measuring reruns only when a side's descriptor list changes, which the
* descriptor resolver keeps stable across unrelated updates.
*/
export var useMeasuredSurfacePlacements = function useMeasuredSurfacePlacements(editorView, left, right) {
var _useState = useState(EMPTY_PLACEMENTS),
_useState2 = _slicedToArray(_useState, 2),
placements = _useState2[0],
setPlacements = _useState2[1];
var animationFrameRef = useRef(undefined);
useLayoutEffect(function () {
var _getDocument;
// Anchor support is fixed for the session, so anchored surfaces never set measured state.
if (isCSSAnchorSupported()) {
return;
}
var descriptors = [].concat(_toConsumableArray((left !== null && left !== void 0 ? left : []).map(function (descriptor) {
return {
descriptor: descriptor,
side: 'left'
};
})), _toConsumableArray((right !== null && right !== void 0 ? right : []).map(function (descriptor) {
return {
descriptor: descriptor,
side: 'right'
};
})));
if (descriptors.length === 0) {
// Clear only when there is something to clear, so an empty map does not commit a render.
setPlacements(function (previous) {
return previous.size > 0 ? EMPTY_PLACEMENTS : previous;
});
return;
}
var measurementTargets = descriptors.flatMap(function (_ref) {
var descriptor = _ref.descriptor,
side = _ref.side;
var blockControlsContext = descriptor.blockControlsContext;
var targetNodeElement = editorView.nodeDOM(blockControlsContext.targetNode.pos);
var targetElementRoot = targetNodeElement instanceof HTMLElement ? targetNodeElement : targetNodeElement === null || targetNodeElement === void 0 ? void 0 : targetNodeElement.parentElement;
if (!targetElementRoot) {
return [];
}
return [{
descriptor: descriptor,
side: side,
targetElement: getNodeContentElement(targetElementRoot, blockControlsContext.targetNode.type.name),
targetElementRoot: targetElementRoot
}];
});
var offsetParent = editorView.dom.offsetParent;
var isDocumentOffsetParent = !offsetParent || offsetParent === ((_getDocument = getDocument()) === null || _getDocument === void 0 ? void 0 : _getDocument.body);
var scrollingOffsetParent = offsetParent instanceof HTMLElement && !isDocumentOffsetParent ? offsetParent : undefined;
var updatePositions = function updatePositions() {
var _scrollingOffsetParen, _scrollingOffsetParen2;
animationFrameRef.current = undefined;
var offsetParentRect = offsetParent === null || offsetParent === void 0 ? void 0 : offsetParent.getBoundingClientRect();
var offsetParentScrollTop = (_scrollingOffsetParen = scrollingOffsetParent === null || scrollingOffsetParent === void 0 ? void 0 : scrollingOffsetParent.scrollTop) !== null && _scrollingOffsetParen !== void 0 ? _scrollingOffsetParen : window.scrollY;
var offsetParentScrollLeft = (_scrollingOffsetParen2 = scrollingOffsetParent === null || scrollingOffsetParent === void 0 ? void 0 : scrollingOffsetParent.scrollLeft) !== null && _scrollingOffsetParen2 !== void 0 ? _scrollingOffsetParen2 : window.scrollX;
var nextPlacements = new Map();
var _iterator = _createForOfIteratorHelper(measurementTargets),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var _targetElementRoot$ge, _offsetParentRect$lef;
var _step$value = _step.value,
descriptor = _step$value.descriptor,
side = _step$value.side,
targetElement = _step$value.targetElement,
targetElementRoot = _step$value.targetElementRoot;
if (!targetElementRoot.isConnected || !targetElement.isConnected) {
continue;
}
var blockControlsContext = descriptor.blockControlsContext,
isTopLevel = descriptor.isTopLevel;
var options = {
layout: (_targetElementRoot$ge = targetElementRoot.getAttribute('layout')) !== null && _targetElementRoot$ge !== void 0 ? _targetElementRoot$ge : '',
nodeRect: targetElement.getBoundingClientRect(),
nodeType: blockControlsContext.targetNode.type.name,
nodeTypeWithLevel: getNodeTypeWithLevel(blockControlsContext.targetNode.node),
parentNodeType: blockControlsContext.targetNode.parentType === 'doc' ? undefined : blockControlsContext.targetNode.parentType
};
var placement = side === 'left' && isTopLevel ? getLeftGutterSurfacePlacement(_objectSpread(_objectSpread({}, options), {}, {
leftGutterBoundary: (_offsetParentRect$lef = offsetParentRect === null || offsetParentRect === void 0 ? void 0 : offsetParentRect.left) !== null && _offsetParentRect$lef !== void 0 ? _offsetParentRect$lef : 0
})) : getSurfacePlacement(_objectSpread(_objectSpread({}, options), {}, {
side: side
}));
nextPlacements.set(getSurfaceKey(side, descriptor), toMeasuredSurfaceWrapperPlacement(getAbsoluteSurfacePlacement({
offsetParentRect: offsetParentRect,
offsetParentScrollLeft: offsetParentScrollLeft,
offsetParentScrollTop: offsetParentScrollTop,
placement: placement
})));
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
setPlacements(nextPlacements);
};
var schedulePositionUpdate = function schedulePositionUpdate() {
if (animationFrameRef.current === undefined) {
animationFrameRef.current = requestAnimationFrame(updatePositions);
}
};
updatePositions();
var resizeObserver = new ResizeObserver(schedulePositionUpdate);
var observedElements = new Set([editorView.dom]);
var _iterator2 = _createForOfIteratorHelper(measurementTargets),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var targetElement = _step2.value.targetElement;
observedElements.add(targetElement);
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
if (offsetParent instanceof HTMLElement) {
observedElements.add(offsetParent);
}
observedElements.forEach(function (element) {
return resizeObserver.observe(element);
});
var unbindWindowResize = bind(window, {
type: 'resize',
listener: schedulePositionUpdate
});
var unbindWindowScroll = bind(window, {
type: 'scroll',
listener: schedulePositionUpdate,
options: {
capture: true,
passive: true
}
});
var visualViewport = window.visualViewport;
var unbindVisualViewportResize = visualViewport ? bind(visualViewport, {
type: 'resize',
listener: schedulePositionUpdate
}) : undefined;
var unbindVisualViewportScroll = visualViewport ? bind(visualViewport, {
type: 'scroll',
listener: schedulePositionUpdate
}) : undefined;
var unbindTransitionEnd = bind(editorView.dom, {
type: 'transitionend',
listener: schedulePositionUpdate,
options: {
capture: true
}
});
return function () {
if (animationFrameRef.current !== undefined) {
cancelAnimationFrame(animationFrameRef.current);
animationFrameRef.current = undefined;
}
resizeObserver.disconnect();
unbindWindowResize();
unbindWindowScroll();
unbindVisualViewportResize === null || unbindVisualViewportResize === void 0 || unbindVisualViewportResize();
unbindVisualViewportScroll === null || unbindVisualViewportScroll === void 0 || unbindVisualViewportScroll();
unbindTransitionEnd();
};
}, [editorView, left, right]);
return placements;
};