@atlaskit/editor-plugin-block-controls 3.15.10 → 3.15.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cjs/pm-plugins/decorations-drag-handle.js +29 -11
  3. package/dist/cjs/pm-plugins/decorations-quick-insert-button.js +51 -9
  4. package/dist/cjs/pm-plugins/quick-insert-calculate-position.js +70 -0
  5. package/dist/cjs/pm-plugins/vanilla-quick-insert.js +190 -0
  6. package/dist/cjs/pm-plugins/vanilla-tooltip.js +179 -0
  7. package/dist/cjs/ui/global-styles.js +86 -1
  8. package/dist/es2019/pm-plugins/decorations-drag-handle.js +29 -11
  9. package/dist/es2019/pm-plugins/decorations-quick-insert-button.js +51 -9
  10. package/dist/es2019/pm-plugins/quick-insert-calculate-position.js +64 -0
  11. package/dist/es2019/pm-plugins/vanilla-quick-insert.js +189 -0
  12. package/dist/es2019/pm-plugins/vanilla-tooltip.js +147 -0
  13. package/dist/es2019/ui/global-styles.js +84 -1
  14. package/dist/esm/pm-plugins/decorations-drag-handle.js +29 -11
  15. package/dist/esm/pm-plugins/decorations-quick-insert-button.js +51 -9
  16. package/dist/esm/pm-plugins/quick-insert-calculate-position.js +64 -0
  17. package/dist/esm/pm-plugins/vanilla-quick-insert.js +183 -0
  18. package/dist/esm/pm-plugins/vanilla-tooltip.js +172 -0
  19. package/dist/esm/ui/global-styles.js +86 -1
  20. package/dist/types/pm-plugins/quick-insert-calculate-position.d.ts +12 -0
  21. package/dist/types/pm-plugins/vanilla-quick-insert.d.ts +21 -0
  22. package/dist/types/pm-plugins/vanilla-tooltip.d.ts +27 -0
  23. package/dist/types-ts4.5/pm-plugins/quick-insert-calculate-position.d.ts +12 -0
  24. package/dist/types-ts4.5/pm-plugins/vanilla-quick-insert.d.ts +21 -0
  25. package/dist/types-ts4.5/pm-plugins/vanilla-tooltip.d.ts +27 -0
  26. package/package.json +5 -4
@@ -7,9 +7,11 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.GlobalStylesWrapper = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
9
  var _react = require("@emotion/react");
10
+ var _styles = require("@atlaskit/editor-common/styles");
10
11
  var _whitespace = require("@atlaskit/editor-common/whitespace");
11
12
  var _editorSharedStyles = require("@atlaskit/editor-shared-styles");
12
13
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
14
+ var _constants = require("@atlaskit/theme/constants");
13
15
  var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
14
16
  var _consts = require("./consts");
15
17
  /**
@@ -125,6 +127,89 @@ var globalStyles = function globalStyles() {
125
127
  }
126
128
  });
127
129
  };
130
+ var quickInsertStyles = function quickInsertStyles() {
131
+ return (0, _react.css)({
132
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
133
+ '.blocks-quick-insert-button': {
134
+ backgroundColor: 'transparent',
135
+ top: "var(--top-override,8px)",
136
+ position: 'sticky',
137
+ boxSizing: 'border-box',
138
+ display: 'flex',
139
+ flexDirection: 'column',
140
+ justifyContent: 'center',
141
+ alignItems: 'center',
142
+ height: "var(--ds-space-300, 24px)",
143
+ width: "var(--ds-space-300, 24px)",
144
+ border: 'none',
145
+ borderRadius: '50%',
146
+ zIndex: _constants.layers.card(),
147
+ outline: 'none',
148
+ cursor: 'pointer',
149
+ color: "var(--ds-icon-subtle, #626F86)"
150
+ },
151
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
152
+ '[data-blocks-quick-insert-container]:has(~ [data-prosemirror-node-name="table"] .pm-table-with-controls tr.sticky) &': {
153
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
154
+ '--top-override': "".concat(_styles.tableControlsSpacing, "px")
155
+ },
156
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
157
+ '[data-prosemirror-mark-name="breakout"]:has([data-blocks-quick-insert-container]):has(~ [data-prosemirror-node-name="table"] .pm-table-with-controls tr.sticky) &': {
158
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
159
+ '--top-override': "".concat(_styles.tableControlsSpacing, "px")
160
+ },
161
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
162
+ '.blocks-quick-insert-button:hover': {
163
+ backgroundColor: "var(--ds-background-neutral-subtle-hovered, #091E420F)"
164
+ },
165
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
166
+ '.blocks-quick-insert-button:active': {
167
+ backgroundColor: "var(--ds-background-neutral-subtle-pressed, #091E4224)"
168
+ },
169
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
170
+ '.blocks-quick-insert-button:focus': {
171
+ outline: "2px solid ".concat("var(--ds-border-focused, #388BFF)")
172
+ },
173
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
174
+ '.blocks-quick-insert-visible-container': {
175
+ transition: 'opacity 0.1s ease-in-out, visibility 0.1s ease-in-out',
176
+ opacity: 1,
177
+ visibility: 'visible'
178
+ },
179
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
180
+ '.blocks-quick-insert-invisible-container': {
181
+ transition: 'opacity 0.1s ease-in-out, visibility 0.1s ease-in-out',
182
+ opacity: 0,
183
+ visibility: 'hidden'
184
+ },
185
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
186
+ '.blocks-quick-insert-tooltip': {
187
+ zIndex: _constants.layers.tooltip(),
188
+ borderRadius: "var(--ds-border-radius, 4px)",
189
+ padding: "var(--ds-space-050, 4px)".concat(" 0"),
190
+ boxSizing: 'border-box',
191
+ maxWidth: '240px',
192
+ backgroundColor: "var(--ds-background-neutral-bold, #44546F)",
193
+ color: "var(--ds-text-inverse, #FFFFFF)",
194
+ font: "var(--ds-font-body-UNSAFE_small, normal 400 12px/16px ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)",
195
+ insetBlockStart: "var(--ds-space-0, 0px)",
196
+ insetInlineStart: "var(--ds-space-0, 0px)",
197
+ overflowWrap: 'break-word',
198
+ paddingBlockEnd: "var(--ds-space-025, 2px)",
199
+ paddingBlockStart: "var(--ds-space-025, 2px)",
200
+ paddingInlineEnd: "var(--ds-space-075, 6px)",
201
+ paddingInlineStart: "var(--ds-space-075, 6px)",
202
+ wordWrap: 'break-word',
203
+ pointerEvents: 'none',
204
+ userSelect: 'none',
205
+ // Based on: platform/packages/design-system/motion/src/entering/keyframes-motion.tsx
206
+ transition: 'opacity .1s ease-in-out, transform .1s ease-in-out, visibility .1s ease-in-out',
207
+ '@media (prefers-reduced-motion: reduce)': {
208
+ transition: 'none'
209
+ }
210
+ }
211
+ });
212
+ };
128
213
  var topLevelNodeMarginStyles = (0, _react.css)({
129
214
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors -- Ignored via go/DSP-18766
130
215
  '.ProseMirror': {
@@ -201,6 +286,6 @@ var blockCardWithoutLayout = (0, _react.css)({
201
286
  });
202
287
  var GlobalStylesWrapper = exports.GlobalStylesWrapper = function GlobalStylesWrapper() {
203
288
  return (0, _react.jsx)(_react.Global, {
204
- styles: [globalStyles(), globalDnDStyle, extendedHoverZone(), (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1') && (0, _platformFeatureFlags.fg)('platform_editor_controls_widget_visibility') ? undefined : withInlineNodeStyle, withDeleteLinesStyleFix, withMediaSingleStyleFix, legacyBreakoutWideLayoutStyle, headingWithIndentationInLayoutStyleFix, (0, _experiments.editorExperiment)('advanced_layouts', true) ? blockCardWithoutLayout : undefined, withDividerInPanelStyleFix, withFormatInLayoutStyleFix, (0, _platformFeatureFlags.fg)('platform_editor_fix_safari_cursor_hidden_empty') ? withRelativePosStyle : withRelativePosStyleLegacy, topLevelNodeMarginStyles, withAnchorNameZindexStyle]
289
+ styles: [globalStyles(), globalDnDStyle, extendedHoverZone(), (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1') && (0, _platformFeatureFlags.fg)('platform_editor_controls_widget_visibility') ? undefined : withInlineNodeStyle, (0, _experiments.editorExperiment)('platform_editor_block_control_optimise_render', true) ? quickInsertStyles : undefined, withDeleteLinesStyleFix, withMediaSingleStyleFix, legacyBreakoutWideLayoutStyle, headingWithIndentationInLayoutStyleFix, (0, _experiments.editorExperiment)('advanced_layouts', true) ? blockCardWithoutLayout : undefined, withDividerInPanelStyleFix, withFormatInLayoutStyleFix, (0, _platformFeatureFlags.fg)('platform_editor_fix_safari_cursor_hidden_empty') ? withRelativePosStyle : withRelativePosStyleLegacy, topLevelNodeMarginStyles, withAnchorNameZindexStyle]
205
290
  });
206
291
  };
@@ -28,6 +28,34 @@ export const dragHandleDecoration = (api, formatMessage, pos, anchorName, nodeTy
28
28
  }
29
29
  let unbind;
30
30
  const key = uuid();
31
+ const widgetSpec = editorExperiment('platform_editor_breakout_resizing', true) ? {
32
+ side: -1,
33
+ type: TYPE_HANDLE_DEC,
34
+ testid: `${TYPE_HANDLE_DEC}-${uuid()}`,
35
+ /**
36
+ * sigh - `marks` influences the position that the widget is drawn (as described on the `side` property).
37
+ * Leaving this 'undefined' causes the widget to be wrapped in the mark before this position which creates
38
+ * weird stacking context issues. Providing an empty array causes the widget to correctly render before
39
+ * this exact position at the top of the DOM.
40
+ */
41
+ marks: [],
42
+ destroy: node => {
43
+ unbind && unbind();
44
+ if (editorExperiment('platform_editor_block_control_optimise_render', true) && node instanceof HTMLElement) {
45
+ ReactDOM.unmountComponentAtNode(node);
46
+ }
47
+ }
48
+ } : {
49
+ side: -1,
50
+ type: TYPE_HANDLE_DEC,
51
+ testid: `${TYPE_HANDLE_DEC}-${uuid()}`,
52
+ destroy: node => {
53
+ unbind && unbind();
54
+ if (editorExperiment('platform_editor_block_control_optimise_render', true) && node instanceof HTMLElement) {
55
+ ReactDOM.unmountComponentAtNode(node);
56
+ }
57
+ }
58
+ };
31
59
  return Decoration.widget(pos, (view, getPosUnsafe) => {
32
60
  const element = document.createElement('span');
33
61
  // inline decoration causes focus issues when refocusing Editor into first line
@@ -117,15 +145,5 @@ export const dragHandleDecoration = (api, formatMessage, pos, anchorName, nodeTy
117
145
  }), element);
118
146
  }
119
147
  return element;
120
- }, {
121
- side: -1,
122
- type: TYPE_HANDLE_DEC,
123
- testid: `${TYPE_HANDLE_DEC}-${uuid()}`,
124
- destroy: node => {
125
- unbind && unbind();
126
- if (editorExperiment('platform_editor_block_control_optimise_render', true) && node instanceof HTMLElement) {
127
- ReactDOM.unmountComponentAtNode(node);
128
- }
129
- }
130
- });
148
+ }, widgetSpec);
131
149
  };
@@ -2,18 +2,68 @@ import { createElement } from 'react';
2
2
  import uuid from 'uuid';
3
3
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
4
4
  import { fg } from '@atlaskit/platform-feature-flags';
5
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
5
6
  import { QuickInsertWithVisibility, TypeAheadControl } from '../ui/quick-insert-button';
7
+ import { createVanillaButton } from './vanilla-quick-insert';
6
8
  const TYPE_QUICK_INSERT = 'INSERT_BUTTON';
7
9
  export const findQuickInsertInsertButtonDecoration = (decorations, from, to) => {
8
10
  return decorations.find(from, to, spec => spec.type === TYPE_QUICK_INSERT);
9
11
  };
10
12
  export const quickInsertButtonDecoration = (api, formatMessage, rootPos, anchorName, nodeType, nodeViewPortalProviderAPI, rootAnchorName, rootNodeType, anchorRectCache) => {
11
13
  const key = uuid();
14
+ const cleanupCallbacks = [];
15
+ const widgetSpec = editorExperiment('platform_editor_breakout_resizing', true) ? {
16
+ side: -2,
17
+ type: TYPE_QUICK_INSERT,
18
+ /**
19
+ * sigh - `marks` influences the position that the widget is drawn (as described on the `side` property).
20
+ * Leaving this 'undefined' causes the widget to be wrapped in the mark before this position which creates
21
+ * weird stacking context issues. Providing an empty array causes the widget to correctly render before
22
+ * this exact position at the top of the DOM.
23
+ */
24
+ marks: [],
25
+ destroy: _ => {
26
+ if (fg('platform_editor_fix_widget_destroy')) {
27
+ nodeViewPortalProviderAPI.remove(key);
28
+ }
29
+ cleanupCallbacks.forEach(cb => {
30
+ cb();
31
+ });
32
+ }
33
+ } : {
34
+ side: -2,
35
+ type: TYPE_QUICK_INSERT,
36
+ destroy: _ => {
37
+ if (fg('platform_editor_fix_widget_destroy')) {
38
+ nodeViewPortalProviderAPI.remove(key);
39
+ }
40
+ cleanupCallbacks.forEach(cb => {
41
+ cb();
42
+ });
43
+ }
44
+ };
12
45
  return Decoration.widget(rootPos, (view, getPos) => {
13
46
  const element = document.createElement('span');
14
47
  element.contentEditable = 'false';
15
48
  element.setAttribute('data-blocks-quick-insert-container', 'true');
16
49
  element.setAttribute('data-testid', 'block-ctrl-quick-insert-button');
50
+ if (editorExperiment('platform_editor_block_control_optimise_render', true, {
51
+ exposure: true
52
+ })) {
53
+ const vanillaElement = createVanillaButton({
54
+ formatMessage,
55
+ api,
56
+ view,
57
+ getPos,
58
+ cleanupCallbacks,
59
+ rootAnchorName: rootAnchorName !== null && rootAnchorName !== void 0 ? rootAnchorName : nodeType,
60
+ anchorName,
61
+ rootNodeType: rootNodeType !== null && rootNodeType !== void 0 ? rootNodeType : nodeType,
62
+ anchorRectCache
63
+ });
64
+ element.appendChild(vanillaElement);
65
+ return element;
66
+ }
17
67
 
18
68
  // all changes already under experiment
19
69
  if (fg('platform_editor_controls_widget_visibility')) {
@@ -42,13 +92,5 @@ export const quickInsertButtonDecoration = (api, formatMessage, rootPos, anchorN
42
92
  }), element, key);
43
93
  }
44
94
  return element;
45
- }, {
46
- side: -2,
47
- type: TYPE_QUICK_INSERT,
48
- destroy: _ => {
49
- if (fg('platform_editor_fix_widget_destroy')) {
50
- nodeViewPortalProviderAPI.remove(key);
51
- }
52
- }
53
- });
95
+ }, widgetSpec);
54
96
  };
@@ -0,0 +1,64 @@
1
+ import { rootElementGap, topPositionAdjustment, QUICK_INSERT_DIMENSIONS, QUICK_INSERT_LEFT_OFFSET } from '../ui/consts';
2
+ import { refreshAnchorName } from '../ui/utils/anchor-name';
3
+ import { getControlBottomCSSValue, getControlHeightCSSValue, getNodeHeight, getTopPosition, shouldBeSticky } from './utils/drag-handle-positions';
4
+ import { getLeftPositionForRootElement } from './utils/widget-positions';
5
+
6
+ // Adapted from `src/ui/drag-handle.tsx` as positioning logic is similar
7
+ // CHANGES - added an offset so quick insert button can be positioned beside drag handle
8
+ // CHANGES - removed `editorExperiment('nested-dnd', true)` check and rootNodeType calculation
9
+ // CHANGES - replace anchorName with rootAnchorName
10
+ // CHANGES - `removed editorExperiment('advanced_layouts', true) && isLayoutColumn` checks as quick insert button will not be positioned for layout column
11
+ export const calculatePosition = ({
12
+ rootAnchorName,
13
+ anchorName,
14
+ view,
15
+ getPos,
16
+ rootNodeType,
17
+ macroInteractionUpdates,
18
+ anchorRectCache
19
+ }) => {
20
+ const supportsAnchor = CSS.supports('top', `anchor(${rootAnchorName} start)`) && CSS.supports('left', `anchor(${rootAnchorName} start)`);
21
+ const safeAnchorName = refreshAnchorName({
22
+ getPos,
23
+ view,
24
+ anchorName: rootAnchorName
25
+ });
26
+ const dom = view.dom.querySelector(`[data-drag-handler-anchor-name="${safeAnchorName}"]`);
27
+ const hasResizer = rootNodeType === 'table' || rootNodeType === 'mediaSingle';
28
+ const isExtension = rootNodeType === 'extension' || rootNodeType === 'bodiedExtension';
29
+ const isBlockCard = rootNodeType === 'blockCard';
30
+ const isEmbedCard = rootNodeType === 'embedCard';
31
+ const isMacroInteractionUpdates = macroInteractionUpdates && isExtension;
32
+ let innerContainer = null;
33
+ if (dom) {
34
+ if (isEmbedCard) {
35
+ innerContainer = dom.querySelector('.rich-media-item');
36
+ } else if (hasResizer) {
37
+ innerContainer = dom.querySelector('.resizer-item');
38
+ } else if (isExtension) {
39
+ innerContainer = dom.querySelector('.extension-container[data-layout]');
40
+ } else if (isBlockCard) {
41
+ //specific to datasource blockCard
42
+ innerContainer = dom.querySelector('.datasourceView-content-inner-wrap');
43
+ }
44
+ }
45
+ const isEdgeCase = (hasResizer || isExtension || isEmbedCard || isBlockCard) && innerContainer;
46
+ const isSticky = shouldBeSticky(rootNodeType);
47
+ const bottom = getControlBottomCSSValue(safeAnchorName || anchorName, isSticky, true);
48
+ if (supportsAnchor) {
49
+ return {
50
+ left: isEdgeCase ? `calc(anchor(${safeAnchorName} start) + ${getLeftPositionForRootElement(dom, rootNodeType, QUICK_INSERT_DIMENSIONS, innerContainer, isMacroInteractionUpdates)} + -${QUICK_INSERT_LEFT_OFFSET}px)` : `calc(anchor(${safeAnchorName} start) - ${QUICK_INSERT_DIMENSIONS.width}px - ${rootElementGap(rootNodeType)}px + -${QUICK_INSERT_LEFT_OFFSET}px)`,
51
+ top: `calc(anchor(${safeAnchorName} start) + ${topPositionAdjustment(rootNodeType)}px)`,
52
+ ...bottom
53
+ };
54
+ }
55
+
56
+ // expensive, calls offsetHeight
57
+ const nodeHeight = getNodeHeight(dom, safeAnchorName || anchorName, anchorRectCache) || 0;
58
+ const height = getControlHeightCSSValue(nodeHeight, isSticky, true, "var(--ds-space-300, 24px)");
59
+ return {
60
+ left: isEdgeCase ? `calc(${(dom === null || dom === void 0 ? void 0 : dom.offsetLeft) || 0}px + ${getLeftPositionForRootElement(dom, rootNodeType, QUICK_INSERT_DIMENSIONS, innerContainer, isMacroInteractionUpdates)} + -${QUICK_INSERT_LEFT_OFFSET}px)` : `calc(${getLeftPositionForRootElement(dom, rootNodeType, QUICK_INSERT_DIMENSIONS, innerContainer, isMacroInteractionUpdates)} + -${QUICK_INSERT_LEFT_OFFSET}px)`,
61
+ top: getTopPosition(dom, rootNodeType),
62
+ ...height
63
+ };
64
+ };
@@ -0,0 +1,189 @@
1
+ import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
2
+ import { blockControlsMessages as messages } from '@atlaskit/editor-common/messages';
3
+ import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
4
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
5
+ import { findParentNode, findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
6
+ import { CellSelection } from '@atlaskit/editor-tables/cell-selection';
7
+ import { isInTextSelection, isNestedNodeSelected, isNonEditableBlock, isSelectionInNode } from '../ui/utils/document-checks';
8
+ import { createNewLine } from '../ui/utils/editor-commands';
9
+ import { calculatePosition } from './quick-insert-calculate-position';
10
+ import { VanillaTooltip } from './vanilla-tooltip';
11
+ // Based on platform/packages/design-system/icon/svgs/utility/add.svg
12
+ const plusButtonDOM = ['http://www.w3.org/2000/svg svg', {
13
+ width: '12',
14
+ height: '12',
15
+ fill: 'none',
16
+ viewBox: '0 0 12 12',
17
+ style: 'pointer-events: none;'
18
+ }, ['http://www.w3.org/2000/svg path', {
19
+ fill: 'currentcolor',
20
+ 'fill-rule': 'evenodd',
21
+ d: 'M5.25 6.75V11h1.5V6.75H11v-1.5H6.75V1h-1.5v4.25H1v1.5z',
22
+ 'clip-rule': 'evenodd',
23
+ style: 'pointer-events: none;'
24
+ }]];
25
+ const vanillaQuickInsert = ({
26
+ view,
27
+ getPos,
28
+ rootNodeType,
29
+ anchorRectCache,
30
+ anchorName,
31
+ rootAnchorName,
32
+ api
33
+ }) => {
34
+ var _api$featureFlags$sha, _api$featureFlags, _api$featureFlags$sha2;
35
+ return ['div', {
36
+ style: convertToInlineCss({
37
+ position: 'absolute',
38
+ ...calculatePosition({
39
+ rootAnchorName,
40
+ anchorName,
41
+ view,
42
+ getPos,
43
+ rootNodeType: rootNodeType,
44
+ macroInteractionUpdates: (_api$featureFlags$sha = api === null || api === void 0 ? void 0 : (_api$featureFlags = api.featureFlags) === null || _api$featureFlags === void 0 ? void 0 : (_api$featureFlags$sha2 = _api$featureFlags.sharedState.currentState()) === null || _api$featureFlags$sha2 === void 0 ? void 0 : _api$featureFlags$sha2.macroInteractionUpdates) !== null && _api$featureFlags$sha !== void 0 ? _api$featureFlags$sha : false,
45
+ anchorRectCache
46
+ })
47
+ })
48
+ }, ['button', {
49
+ class: 'blocks-quick-insert-button',
50
+ 'data-testid': 'editor-quick-insert-button'
51
+ }, plusButtonDOM]];
52
+ };
53
+
54
+ /**
55
+ * Create a Node which contains the quick insert button
56
+ */
57
+ export const createVanillaButton = props => {
58
+ var _props$api$typeAhead, _props$api$typeAhead$, _props$api$blockContr, _props$api$blockContr2, _props$api$typeAhead2, _props$api$blockContr3;
59
+ const {
60
+ dom
61
+ } = DOMSerializer.renderSpec(document, vanillaQuickInsert(props));
62
+ if (dom instanceof HTMLElement) {
63
+ const button = dom.querySelector('button[data-testid="editor-quick-insert-button"]');
64
+ if (button instanceof HTMLButtonElement) {
65
+ button.onclick = () => handleQuickInsert(props);
66
+ const tooltip = new VanillaTooltip(button, props.formatMessage(messages.insert), 'quick-insert-button-tooltip');
67
+ props.cleanupCallbacks.push(() => {
68
+ tooltip.destroy();
69
+ });
70
+ }
71
+ }
72
+
73
+ // Dynamically control the visibility of the node
74
+ let isTypeAheadOpen = (_props$api$typeAhead = props.api.typeAhead) === null || _props$api$typeAhead === void 0 ? void 0 : (_props$api$typeAhead$ = _props$api$typeAhead.sharedState.currentState()) === null || _props$api$typeAhead$ === void 0 ? void 0 : _props$api$typeAhead$.isOpen;
75
+ let isEditing = (_props$api$blockContr = props.api.blockControls) === null || _props$api$blockContr === void 0 ? void 0 : (_props$api$blockContr2 = _props$api$blockContr.sharedState.currentState()) === null || _props$api$blockContr2 === void 0 ? void 0 : _props$api$blockContr2.isEditing;
76
+ const changeDOMVisibility = () => {
77
+ if (!(dom instanceof HTMLElement)) {
78
+ return;
79
+ }
80
+ if (isTypeAheadOpen || isEditing) {
81
+ dom.classList.add('blocks-quick-insert-invisible-container');
82
+ dom.classList.remove('blocks-quick-insert-visible-container');
83
+ } else {
84
+ dom.classList.add('blocks-quick-insert-visible-container');
85
+ dom.classList.remove('blocks-quick-insert-invisible-container');
86
+ }
87
+ };
88
+ changeDOMVisibility();
89
+ props.cleanupCallbacks.push((_props$api$typeAhead2 = props.api.typeAhead) === null || _props$api$typeAhead2 === void 0 ? void 0 : _props$api$typeAhead2.sharedState.onChange(({
90
+ nextSharedState
91
+ }) => {
92
+ isTypeAheadOpen = nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.isOpen;
93
+ changeDOMVisibility();
94
+ }));
95
+ props.cleanupCallbacks.push((_props$api$blockContr3 = props.api.blockControls) === null || _props$api$blockContr3 === void 0 ? void 0 : _props$api$blockContr3.sharedState.onChange(({
96
+ nextSharedState
97
+ }) => {
98
+ isEditing = nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.isEditing;
99
+ changeDOMVisibility();
100
+ }));
101
+ return dom;
102
+ };
103
+ const TEXT_PARENT_TYPES = ['paragraph', 'heading', 'blockquote', 'taskItem', 'decisionItem'];
104
+ const handleQuickInsert = ({
105
+ api,
106
+ view,
107
+ getPos
108
+ }) => {
109
+ var _api$quickInsert;
110
+ // if the selection is not within the node this decoration is rendered at
111
+ // then insert a newline and trigger quick insert
112
+ const start = getPos();
113
+ if (start !== undefined) {
114
+ // if the selection is not within the node this decoration is rendered at
115
+ // or the node is non-editable, then insert a newline and trigger quick insert
116
+ const isSelectionInsideNode = isSelectionInNode(start, view);
117
+ if (!isSelectionInsideNode || isNonEditableBlock(start, view)) {
118
+ api.core.actions.execute(createNewLine(start));
119
+ }
120
+ const {
121
+ codeBlock
122
+ } = view.state.schema.nodes;
123
+ const {
124
+ selection
125
+ } = view.state;
126
+ const codeBlockParentNode = findParentNodeOfType(codeBlock)(selection);
127
+ if (codeBlockParentNode) {
128
+ // Slash command is not meant to be triggered inside code block, hence always insert slash in a new line following
129
+ api.core.actions.execute(createNewLine(codeBlockParentNode.pos));
130
+ } else if (isSelectionInsideNode) {
131
+ // text or element with be deselected and the / added immediately after the paragraph
132
+ // unless the selection is empty
133
+ const currentSelection = view.state.selection;
134
+ if (isInTextSelection(view) && currentSelection.from !== currentSelection.to) {
135
+ const currentParagraphNode = findParentNode(node => TEXT_PARENT_TYPES.includes(node.type.name))(currentSelection);
136
+ if (currentParagraphNode) {
137
+ const newPos =
138
+ //if the current selection is selected from right to left, then set the selection to the start of the paragraph
139
+ currentSelection.anchor === currentSelection.to ? currentParagraphNode.pos : currentParagraphNode.pos + currentParagraphNode.node.nodeSize - 1;
140
+ api.core.actions.execute(({
141
+ tr
142
+ }) => {
143
+ tr.setSelection(TextSelection.create(view.state.selection.$from.doc, newPos));
144
+ return tr;
145
+ });
146
+ }
147
+ }
148
+ if (isNestedNodeSelected(view)) {
149
+ // if the nested selected node is non-editable, then insert a newline below the selected node
150
+ if (isNonEditableBlock(view.state.selection.from, view)) {
151
+ api.core.actions.execute(createNewLine(view.state.selection.from));
152
+ } else {
153
+ // otherwise need to force the selection to be at the start of the node, because
154
+ // prosemirror is keeping it as NodeSelection for nested nodes. Do this to keep it
155
+ // consistent NodeSelection for root level nodes.
156
+ api.core.actions.execute(({
157
+ tr
158
+ }) => {
159
+ createNewLine(view.state.selection.from)({
160
+ tr
161
+ });
162
+ tr.setSelection(TextSelection.create(tr.doc, view.state.selection.from));
163
+ return tr;
164
+ });
165
+ }
166
+ }
167
+ if (currentSelection instanceof CellSelection) {
168
+ // find the last inline position in the selection
169
+ const lastInlinePosition = TextSelection.near(view.state.selection.$to, -1);
170
+ lastInlinePosition && api.core.actions.execute(({
171
+ tr
172
+ }) => {
173
+ if (!(lastInlinePosition instanceof TextSelection)) {
174
+ // this will create a new line after the node
175
+ createNewLine(lastInlinePosition.from)({
176
+ tr
177
+ });
178
+ // this will find the next valid text position after the node
179
+ tr.setSelection(TextSelection.create(tr.doc, lastInlinePosition.to));
180
+ } else {
181
+ tr.setSelection(lastInlinePosition);
182
+ }
183
+ return tr;
184
+ });
185
+ }
186
+ }
187
+ }
188
+ (_api$quickInsert = api.quickInsert) === null || _api$quickInsert === void 0 ? void 0 : _api$quickInsert.actions.openTypeAhead('blockControl', true);
189
+ };
@@ -0,0 +1,147 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import { createPopper } from '@popperjs/core';
3
+ import { bind } from 'bind-event-listener';
4
+ const startingOffset = {
5
+ name: 'offset',
6
+ options: {
7
+ offset: [0, 4]
8
+ }
9
+ };
10
+ const endingOffset = {
11
+ name: 'offset',
12
+ options: {
13
+ offset: [0, 8]
14
+ }
15
+ };
16
+
17
+ /**
18
+ * A tooltip component similar to "@atlaskit/tooltip" but built for vanilla scenarios
19
+ *
20
+ * Uses Popover API for accessibility + stacking context: https://developer.mozilla.org/en-US/docs/Web/API/Popover_API
21
+ * Uses popperJS for positioning
22
+ *
23
+ * @warning Still experimental. One day we can likely want to move this to a common package.
24
+ */
25
+ export class VanillaTooltip {
26
+ constructor(button, content,
27
+ /**
28
+ * Id associated to the tooltip - must be unique.
29
+ */
30
+ id, timeout = 300) {
31
+ _defineProperty(this, "listeners", []);
32
+ _defineProperty(this, "shouldHidePopover", false);
33
+ _defineProperty(this, "isDisplayed", false);
34
+ this.button = button;
35
+ this.timeout = timeout;
36
+ const tooltip = document.createElement('span');
37
+ tooltip.role = 'tooltip';
38
+ tooltip.popover = 'hint';
39
+ // Warning: Currently this is used for styling - only works in the block controls package
40
+ tooltip.className = 'blocks-quick-insert-tooltip';
41
+ tooltip.id = id;
42
+ tooltip.textContent = content;
43
+ this.tooltip = tooltip;
44
+
45
+ // Button preparation
46
+ button.appendChild(tooltip);
47
+ // Prepare the button to have the popover target and accessibility properties
48
+ button.setAttribute('popovertarget', tooltip.id);
49
+ button.setAttribute('aria-describedby', tooltip.id);
50
+ const showEvents = ['mouseenter', 'focus'];
51
+ const hideEvents = ['mouseleave', 'blur'];
52
+ showEvents.forEach(event => {
53
+ this.listeners.push(bind(button, {
54
+ type: event,
55
+ listener: () => this.show()
56
+ }));
57
+ });
58
+ hideEvents.forEach(event => {
59
+ this.listeners.push(bind(button, {
60
+ type: event,
61
+ listener: () => this.hide()
62
+ }));
63
+ });
64
+ this.listeners.push(bind(window, {
65
+ type: 'keydown',
66
+ listener: e => {
67
+ if (e.key === 'Escape') {
68
+ this.hide(true);
69
+ }
70
+ }
71
+ }));
72
+
73
+ // Hide the tooltip if the hide transition has completed
74
+ this.tooltip.ontransitionend = () => {
75
+ if (this.shouldHidePopover) {
76
+ this.tooltip.hidePopover();
77
+ }
78
+ };
79
+ }
80
+ createPopperInstance() {
81
+ this.popperInstance = createPopper(this.button, this.tooltip, {
82
+ placement: 'top',
83
+ modifiers: [startingOffset]
84
+ });
85
+ }
86
+ destroy() {
87
+ var _this$popperInstance;
88
+ (_this$popperInstance = this.popperInstance) === null || _this$popperInstance === void 0 ? void 0 : _this$popperInstance.destroy();
89
+ this.listeners.forEach(listener => {
90
+ listener();
91
+ });
92
+ }
93
+ hide(immediate = false) {
94
+ clearTimeout(this.currentTimeoutId);
95
+ this.shouldHidePopover = true;
96
+ // Disable the event listeners
97
+ this.currentTimeoutId = setTimeout(() => {
98
+ var _this$popperInstance2;
99
+ (_this$popperInstance2 = this.popperInstance) === null || _this$popperInstance2 === void 0 ? void 0 : _this$popperInstance2.setOptions(options => ({
100
+ ...options,
101
+ modifiers: [startingOffset, {
102
+ name: 'eventListeners',
103
+ enabled: false
104
+ }]
105
+ }));
106
+ this.tooltip.style.opacity = '0';
107
+ this.isDisplayed = false;
108
+ // If transition animations are disabled immediately hide the popover
109
+ if (this.tooltip.style.transition === 'none') {
110
+ this.tooltip.hidePopover();
111
+ }
112
+ }, immediate ? 0 : this.timeout);
113
+ }
114
+ show() {
115
+ if (this.isDisplayed) {
116
+ return;
117
+ }
118
+ clearTimeout(this.currentTimeoutId);
119
+ this.shouldHidePopover = false;
120
+
121
+ // Make the tooltip visible - but hide until
122
+ this.tooltip.style.visibility = 'hidden';
123
+ this.tooltip.showPopover();
124
+
125
+ // Update its position
126
+ if (!this.popperInstance) {
127
+ this.createPopperInstance();
128
+ } else {
129
+ this.popperInstance.update();
130
+ }
131
+
132
+ // Enable the event listeners
133
+ this.currentTimeoutId = setTimeout(() => {
134
+ var _this$popperInstance3;
135
+ this.tooltip.style.opacity = '1';
136
+ this.tooltip.style.visibility = 'visible';
137
+ (_this$popperInstance3 = this.popperInstance) === null || _this$popperInstance3 === void 0 ? void 0 : _this$popperInstance3.setOptions(options => ({
138
+ ...options,
139
+ modifiers: [endingOffset, {
140
+ name: 'eventListeners',
141
+ enabled: true
142
+ }]
143
+ }));
144
+ this.isDisplayed = true;
145
+ }, this.timeout);
146
+ }
147
+ }