@atlaskit/editor-plugin-base 0.3.1 → 0.3.3

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @atlaskit/editor-plugin-base
2
2
 
3
+ ## 0.3.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 0.3.2
10
+
11
+ ### Patch Changes
12
+
13
+ - [#63346](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/63346) [`7ab6b24559c0`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/7ab6b24559c0) - Minor performance optimisation for scroll gutter.
14
+ - [#65031](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/65031) [`a00094111b5a`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/a00094111b5a) - ED-21609 Update adf-schema to 35.3.0
15
+ - Updated dependencies
16
+
3
17
  ## 0.3.1
4
18
 
5
19
  ### Patch Changes
@@ -11,6 +11,7 @@ var _adfSchema = require("@atlaskit/adf-schema");
11
11
  var _keymaps = require("@atlaskit/editor-common/keymaps");
12
12
  var _commands = require("@atlaskit/editor-prosemirror/commands");
13
13
  var _history = require("@atlaskit/editor-prosemirror/history");
14
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
14
15
  var _commands2 = require("./commands");
15
16
  var _contextIdentifier = _interopRequireWildcard(require("./pm-plugins/context-identifier"));
16
17
  var _disableSpellChecking = _interopRequireDefault(require("./pm-plugins/disable-spell-checking"));
@@ -19,6 +20,7 @@ var _frozenEditor = _interopRequireDefault(require("./pm-plugins/frozen-editor")
19
20
  var _inlineCursorTarget = _interopRequireDefault(require("./pm-plugins/inline-cursor-target"));
20
21
  var _newlinePreserveMarks = _interopRequireDefault(require("./pm-plugins/newline-preserve-marks"));
21
22
  var _scrollGutter = _interopRequireWildcard(require("./pm-plugins/scroll-gutter"));
23
+ var _pluginDeprecated = _interopRequireDefault(require("./pm-plugins/scroll-gutter/plugin-deprecated"));
22
24
  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); }
23
25
  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; }
24
26
  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; }
@@ -106,7 +108,7 @@ var basePlugin = function basePlugin(_ref) {
106
108
  plugins.push({
107
109
  name: 'scrollGutterPlugin',
108
110
  plugin: function plugin() {
109
- return (0, _scrollGutter.default)(options.allowScrollGutter);
111
+ return (0, _platformFeatureFlags.getBooleanFF)('platform.editor.scroll-gutter.reflow-optimisation') ? (0, _scrollGutter.default)(options.allowScrollGutter) : (0, _pluginDeprecated.default)(options.allowScrollGutter);
110
112
  }
111
113
  });
112
114
  }
@@ -7,6 +7,9 @@ exports.getKeyboardHeight = exports.default = void 0;
7
7
  var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
8
8
  var _utils = require("@atlaskit/editor-common/utils");
9
9
  var _pluginKey = require("./plugin-key");
10
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, 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 normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
11
+ 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); }
12
+ 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; }
10
13
  var getKeyboardHeight = exports.getKeyboardHeight = function getKeyboardHeight(state) {
11
14
  if (state) {
12
15
  var scrollGutterPluginState = (0, _pluginKey.getScrollGutterPluginState)(state);
@@ -205,17 +208,78 @@ var _default = exports.default = function _default() {
205
208
  scrollElement = getScrollElement(view);
206
209
  var editorElement = view.dom;
207
210
  var editorParentElement = editorElement.parentElement;
211
+ var viewportElementHeight = 0;
212
+ var contentElementHeight = 0;
213
+ var addAndObserveGutter = function addAndObserveGutter() {
214
+ if (!gutter.isMounted()) {
215
+ gutter.observe(scrollElement);
216
+ gutter.addGutter(editorParentElement);
217
+ }
218
+ };
219
+ var toggleGutter = function toggleGutter(viewPortSize, contentSize) {
220
+ if (pluginOptions.persistScrollGutter) {
221
+ if (!(0, _utils.isEmptyDocument)(view.state.doc)) {
222
+ addAndObserveGutter();
223
+ } else {
224
+ gutter.removeGutter();
225
+ }
226
+ return;
227
+ }
228
+
229
+ // Add or remove the gutter based on whether the content is about to exceed the viewport height.
230
+ // We do this to ensure there is sufficient white space below the last content node in order to
231
+ // see any UI control elements which may sit beneath it.
232
+ var gutterThresholdHeight = viewPortSize - gutterSize;
233
+ if (contentSize >= gutterThresholdHeight) {
234
+ addAndObserveGutter();
235
+ } else {
236
+ if (gutter.isMounted()) {
237
+ gutter.removeGutter();
238
+ }
239
+ }
240
+ };
241
+ var observerCallback = function observerCallback(entries) {
242
+ var _iterator = _createForOfIteratorHelper(entries),
243
+ _step;
244
+ try {
245
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
246
+ var entry = _step.value;
247
+ var elementHeight = Math.round(entry.contentRect.height);
248
+ if (entry.target === scrollElement) {
249
+ var _getKeyboardHeight;
250
+ var keyboardHeight = (_getKeyboardHeight = getKeyboardHeight(view === null || view === void 0 ? void 0 : view.state)) !== null && _getKeyboardHeight !== void 0 ? _getKeyboardHeight : 0;
251
+ viewportElementHeight = elementHeight - keyboardHeight;
252
+ } else if (entry.target === editorParentElement) {
253
+ contentElementHeight = elementHeight - (gutter.isMounted() ? gutterSize : 0);
254
+ }
255
+ }
256
+ } catch (err) {
257
+ _iterator.e(err);
258
+ } finally {
259
+ _iterator.f();
260
+ }
261
+ toggleGutter(viewportElementHeight, contentElementHeight);
262
+ };
263
+
264
+ // Subscribe to updates of the height of the viewport and content elements
265
+ // We toggle the value when it is set here or when PM updates to ensure we're using
266
+ // the latest.
267
+ var resizeObserver = new ResizeObserver(observerCallback);
268
+ if (scrollElement && editorParentElement) {
269
+ resizeObserver.observe(scrollElement);
270
+ resizeObserver.observe(editorParentElement);
271
+ }
208
272
  return {
209
273
  destroy: function destroy() {
210
274
  // Remove if it's mounted
211
275
  gutter.destroy();
212
276
  scrollElement = editorParentElement = editorElement = null;
277
+ resizeObserver.disconnect();
213
278
  },
214
279
  /**
215
280
  * Toggle the Scroll Gutter Element
216
281
  */
217
282
  update: function update(view, prevState) {
218
- var _getKeyboardHeight;
219
283
  if (!scrollElement || !editorParentElement) {
220
284
  return;
221
285
  }
@@ -224,37 +288,7 @@ var _default = exports.default = function _default() {
224
288
  // No need to recheck if the selected node hasn't changed.
225
289
  return;
226
290
  }
227
- var gutterMounted = gutter.isMounted();
228
- var addAndObserveGutter = function addAndObserveGutter() {
229
- if (!gutterMounted) {
230
- gutter.observe(scrollElement);
231
- gutter.addGutter(editorParentElement);
232
- }
233
- };
234
- if (pluginOptions.persistScrollGutter) {
235
- if (!(0, _utils.isEmptyDocument)(state.doc)) {
236
- addAndObserveGutter();
237
- } else {
238
- gutter.removeGutter();
239
- }
240
- return;
241
- }
242
- // Determine whether we need to consider Keyboard Height
243
- var keyboardHeight = (_getKeyboardHeight = getKeyboardHeight(state)) !== null && _getKeyboardHeight !== void 0 ? _getKeyboardHeight : 0;
244
- var viewportHeight = scrollElement.offsetHeight - keyboardHeight;
245
- var contentHeight = editorParentElement.offsetHeight - (gutterMounted ? gutterSize : 0);
246
-
247
- // Add or remove the gutter based on whether the content is about to exceed the viewport height.
248
- // We do this to ensure there is sufficient white space below the last content node in order to
249
- // see any UI control elements which may sit beneath it.
250
- var gutterThresholdHeight = viewportHeight - gutterSize;
251
- if (contentHeight >= gutterThresholdHeight) {
252
- addAndObserveGutter();
253
- } else {
254
- if (gutterMounted) {
255
- gutter.removeGutter();
256
- }
257
- }
291
+ toggleGutter(viewportElementHeight, contentElementHeight);
258
292
  }
259
293
  };
260
294
  })
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getKeyboardHeight = exports.default = void 0;
7
+ var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
8
+ var _utils = require("@atlaskit/editor-common/utils");
9
+ var _pluginKey = require("./plugin-key");
10
+ var getKeyboardHeight = exports.getKeyboardHeight = function getKeyboardHeight(state) {
11
+ if (state) {
12
+ var scrollGutterPluginState = (0, _pluginKey.getScrollGutterPluginState)(state);
13
+ return scrollGutterPluginState ? scrollGutterPluginState.keyboardHeight : undefined;
14
+ }
15
+ };
16
+ var MIN_TAP_SIZE_IN_PX = 40;
17
+ function supportsIntersectionObserver() {
18
+ if (typeof window !== 'undefined' && 'IntersectionObserver' in window && 'IntersectionObserverEntry' in window && 'intersectionRatio' in
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ window.IntersectionObserverEntry.prototype) {
21
+ return true;
22
+ }
23
+ return false;
24
+ }
25
+ function listenForGutterVisibilityChanges(scrollElement, gutterIsVisible) {
26
+ if (supportsIntersectionObserver()) {
27
+ var observer = new IntersectionObserver(function (entries, _) {
28
+ entries.forEach(function (entry) {
29
+ gutterIsVisible(entry.intersectionRatio > 0);
30
+ });
31
+ }, {
32
+ root: scrollElement,
33
+ rootMargin: '0px',
34
+ threshold: 0
35
+ });
36
+ return observer;
37
+ }
38
+ return undefined;
39
+ }
40
+
41
+ /**
42
+ * Create a gutter element that can be added or removed from the DOM.
43
+ */
44
+ function createGutter(gutterSize) {
45
+ var gutter = document.createElement('div');
46
+ gutter.style.paddingBottom = "".concat(gutterSize, "px");
47
+ gutter.id = _utils.GUTTER_SELECTOR.substr(1);
48
+ var initialized = false;
49
+ var mounted = false;
50
+ var currentParent = null;
51
+ var observer;
52
+ var isVisible = false;
53
+ return {
54
+ addGutter: function addGutter(parent) {
55
+ if (parent) {
56
+ currentParent = parent;
57
+ parent.appendChild(gutter);
58
+ mounted = true;
59
+ if (observer) {
60
+ observer.observe(gutter);
61
+ }
62
+ }
63
+ },
64
+ removeGutter: function removeGutter() {
65
+ if (currentParent && mounted) {
66
+ mounted = false;
67
+ currentParent.removeChild(gutter);
68
+ if (observer) {
69
+ observer.unobserve(gutter);
70
+ }
71
+ }
72
+ },
73
+ element: function element() {
74
+ return gutter;
75
+ },
76
+ isMounted: function isMounted() {
77
+ return mounted;
78
+ },
79
+ visible: function visible() {
80
+ // If we know whether it's visible we can avoid expensive calculations
81
+ if (observer) {
82
+ return isVisible;
83
+ }
84
+ // Fallback for legacy browsers assumes it's visible (if mounted)
85
+ return mounted;
86
+ },
87
+ observe: function observe(scrollElement) {
88
+ if (!initialized) {
89
+ initialized = true;
90
+ observer = listenForGutterVisibilityChanges(scrollElement, function (visible) {
91
+ return isVisible = visible;
92
+ });
93
+ }
94
+ },
95
+ destroy: function destroy() {
96
+ if (observer) {
97
+ observer.disconnect();
98
+ }
99
+ observer = undefined;
100
+ this.removeGutter();
101
+ initialized = mounted = false;
102
+ }
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Get caret top position given the current selection,
108
+ * use start container position as fallback
109
+ */
110
+ function getCaretTopPosition() {
111
+ var windowSelection = window.getSelection();
112
+ if (windowSelection && windowSelection.rangeCount > 0) {
113
+ var range = windowSelection.getRangeAt(0);
114
+ if (range) {
115
+ var clientRects = range.getClientRects();
116
+ // Return client rects
117
+ if (clientRects && clientRects.length > 0) {
118
+ return clientRects[0].top;
119
+ }
120
+
121
+ // Return container top
122
+ var container = range.startContainer;
123
+ if (container && container.getBoundingClientRect) {
124
+ return container.getBoundingClientRect().top;
125
+ }
126
+ }
127
+ }
128
+ return;
129
+ }
130
+ function scrollToGutterElement(scrollContainer, gutterElement, gutterSize) {
131
+ var viewportHeight = scrollContainer.offsetHeight;
132
+ var viewportOffsetY = scrollContainer.getBoundingClientRect().top;
133
+ var caretTopPosition = getCaretTopPosition();
134
+ if (!caretTopPosition) {
135
+ return false;
136
+ }
137
+ var caretTopFromContainer = caretTopPosition - viewportOffsetY;
138
+ var gutterThresholdTop = viewportHeight - gutterSize - MIN_TAP_SIZE_IN_PX * 2;
139
+ if (caretTopFromContainer < gutterThresholdTop) {
140
+ return false;
141
+ }
142
+
143
+ // Clamp the scroll position to above the scroll gutter element.
144
+ gutterElement.scrollIntoView(false);
145
+
146
+ // Mark scrolling as handled so that other plugin's don't override our position.
147
+ return true;
148
+ }
149
+ var _default = exports.default = function _default() {
150
+ var pluginOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
151
+ var getScrollElement = pluginOptions.getScrollElement,
152
+ allowCustomScrollHandler = pluginOptions.allowCustomScrollHandler,
153
+ _pluginOptions$gutter = pluginOptions.gutterSize,
154
+ gutterSize = _pluginOptions$gutter === void 0 ? _utils.GUTTER_SIZE_IN_PX : _pluginOptions$gutter;
155
+ if (!getScrollElement) {
156
+ return undefined;
157
+ }
158
+ var gutter = createGutter(gutterSize);
159
+ var scrollElement = null; // | undefined;
160
+
161
+ return new _safePlugin.SafePlugin({
162
+ key: _pluginKey.scrollGutterPluginKey,
163
+ state: {
164
+ init: function init() {
165
+ return {};
166
+ },
167
+ apply: function apply(tr, pluginState) {
168
+ if (tr.getMeta(_pluginKey.scrollGutterPluginKey)) {
169
+ return tr.getMeta(_pluginKey.scrollGutterPluginKey);
170
+ }
171
+ return pluginState;
172
+ }
173
+ },
174
+ props: {
175
+ // Determines the distance (in pixels) between the cursor and the end of the visible viewport at which point,
176
+ // when scrolling the cursor into view, scrolling takes place.
177
+ // Defaults to 0: https://prosemirror.net/docs/ref/#view.EditorProps.scrollThreshold
178
+ scrollThreshold: gutterSize / 2,
179
+ // Determines the extra space (in pixels) that is left above or below the cursor when it is scrolled into view.
180
+ // Defaults to 5: https://prosemirror.net/docs/ref/#view.EditorProps.scrollMargin
181
+ scrollMargin: gutterSize,
182
+ // Called when the view, after updating its state, tries to scroll the selection into view
183
+ // https://prosemirror.net/docs/ref/#view.EditorProps.handleScrollToSelection
184
+ handleScrollToSelection: function handleScrollToSelection() {
185
+ if (allowCustomScrollHandler === false) {
186
+ return false;
187
+ }
188
+ if (!gutter.isMounted() || !gutter.visible() || !scrollElement) {
189
+ // Avoid scrolling until applicable
190
+ return false;
191
+ }
192
+ return scrollToGutterElement(scrollElement, gutter.element(), gutterSize);
193
+ }
194
+ },
195
+ view: function (_view) {
196
+ function view(_x) {
197
+ return _view.apply(this, arguments);
198
+ }
199
+ view.toString = function () {
200
+ return _view.toString();
201
+ };
202
+ return view;
203
+ }(function (view) {
204
+ // Store references to avoid lookups on successive checks.
205
+ scrollElement = getScrollElement(view);
206
+ var editorElement = view.dom;
207
+ var editorParentElement = editorElement.parentElement;
208
+ return {
209
+ destroy: function destroy() {
210
+ // Remove if it's mounted
211
+ gutter.destroy();
212
+ scrollElement = editorParentElement = editorElement = null;
213
+ },
214
+ /**
215
+ * Toggle the Scroll Gutter Element
216
+ */
217
+ update: function update(view, prevState) {
218
+ var _getKeyboardHeight;
219
+ if (!scrollElement || !editorParentElement) {
220
+ return;
221
+ }
222
+ var state = view.state;
223
+ if (prevState.selection === state.selection) {
224
+ // No need to recheck if the selected node hasn't changed.
225
+ return;
226
+ }
227
+ var gutterMounted = gutter.isMounted();
228
+ var addAndObserveGutter = function addAndObserveGutter() {
229
+ if (!gutterMounted) {
230
+ gutter.observe(scrollElement);
231
+ gutter.addGutter(editorParentElement);
232
+ }
233
+ };
234
+ if (pluginOptions.persistScrollGutter) {
235
+ if (!(0, _utils.isEmptyDocument)(state.doc)) {
236
+ addAndObserveGutter();
237
+ } else {
238
+ gutter.removeGutter();
239
+ }
240
+ return;
241
+ }
242
+ // Determine whether we need to consider Keyboard Height
243
+ var keyboardHeight = (_getKeyboardHeight = getKeyboardHeight(state)) !== null && _getKeyboardHeight !== void 0 ? _getKeyboardHeight : 0;
244
+ var viewportHeight = scrollElement.offsetHeight - keyboardHeight;
245
+ var contentHeight = editorParentElement.offsetHeight - (gutterMounted ? gutterSize : 0);
246
+
247
+ // Add or remove the gutter based on whether the content is about to exceed the viewport height.
248
+ // We do this to ensure there is sufficient white space below the last content node in order to
249
+ // see any UI control elements which may sit beneath it.
250
+ var gutterThresholdHeight = viewportHeight - gutterSize;
251
+ if (contentHeight >= gutterThresholdHeight) {
252
+ addAndObserveGutter();
253
+ } else {
254
+ if (gutterMounted) {
255
+ gutter.removeGutter();
256
+ }
257
+ }
258
+ }
259
+ };
260
+ })
261
+ });
262
+ };
@@ -2,6 +2,7 @@ import { doc, paragraph, text } from '@atlaskit/adf-schema';
2
2
  import { keymap } from '@atlaskit/editor-common/keymaps';
3
3
  import { baseKeymap } from '@atlaskit/editor-prosemirror/commands';
4
4
  import { history } from '@atlaskit/editor-prosemirror/history';
5
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
5
6
  import { setKeyboardHeight } from './commands';
6
7
  import contextIdentifierPlugin, { getContextIdentifier } from './pm-plugins/context-identifier';
7
8
  import disableSpellcheckingPlugin from './pm-plugins/disable-spell-checking';
@@ -10,6 +11,7 @@ import frozenEditor from './pm-plugins/frozen-editor';
10
11
  import inlineCursorTargetPlugin from './pm-plugins/inline-cursor-target';
11
12
  import newlinePreserveMarksPlugin from './pm-plugins/newline-preserve-marks';
12
13
  import scrollGutter, { getKeyboardHeight } from './pm-plugins/scroll-gutter';
14
+ import deprecatedScrollGutter from './pm-plugins/scroll-gutter/plugin-deprecated';
13
15
  const basePlugin = ({
14
16
  config: options,
15
17
  api
@@ -83,7 +85,7 @@ const basePlugin = ({
83
85
  if (options && options.allowScrollGutter) {
84
86
  plugins.push({
85
87
  name: 'scrollGutterPlugin',
86
- plugin: () => scrollGutter(options.allowScrollGutter)
88
+ plugin: () => getBooleanFF('platform.editor.scroll-gutter.reflow-optimisation') ? scrollGutter(options.allowScrollGutter) : deprecatedScrollGutter(options.allowScrollGutter)
87
89
  });
88
90
  }
89
91
  plugins.push({
@@ -187,17 +187,69 @@ export default ((pluginOptions = {}) => {
187
187
  scrollElement = getScrollElement(view);
188
188
  let editorElement = view.dom;
189
189
  let editorParentElement = editorElement.parentElement;
190
+ let viewportElementHeight = 0;
191
+ let contentElementHeight = 0;
192
+ const addAndObserveGutter = () => {
193
+ if (!gutter.isMounted()) {
194
+ gutter.observe(scrollElement);
195
+ gutter.addGutter(editorParentElement);
196
+ }
197
+ };
198
+ const toggleGutter = (viewPortSize, contentSize) => {
199
+ if (pluginOptions.persistScrollGutter) {
200
+ if (!isEmptyDocument(view.state.doc)) {
201
+ addAndObserveGutter();
202
+ } else {
203
+ gutter.removeGutter();
204
+ }
205
+ return;
206
+ }
207
+
208
+ // Add or remove the gutter based on whether the content is about to exceed the viewport height.
209
+ // We do this to ensure there is sufficient white space below the last content node in order to
210
+ // see any UI control elements which may sit beneath it.
211
+ const gutterThresholdHeight = viewPortSize - gutterSize;
212
+ if (contentSize >= gutterThresholdHeight) {
213
+ addAndObserveGutter();
214
+ } else {
215
+ if (gutter.isMounted()) {
216
+ gutter.removeGutter();
217
+ }
218
+ }
219
+ };
220
+ const observerCallback = entries => {
221
+ for (const entry of entries) {
222
+ const elementHeight = Math.round(entry.contentRect.height);
223
+ if (entry.target === scrollElement) {
224
+ var _getKeyboardHeight;
225
+ const keyboardHeight = (_getKeyboardHeight = getKeyboardHeight(view === null || view === void 0 ? void 0 : view.state)) !== null && _getKeyboardHeight !== void 0 ? _getKeyboardHeight : 0;
226
+ viewportElementHeight = elementHeight - keyboardHeight;
227
+ } else if (entry.target === editorParentElement) {
228
+ contentElementHeight = elementHeight - (gutter.isMounted() ? gutterSize : 0);
229
+ }
230
+ }
231
+ toggleGutter(viewportElementHeight, contentElementHeight);
232
+ };
233
+
234
+ // Subscribe to updates of the height of the viewport and content elements
235
+ // We toggle the value when it is set here or when PM updates to ensure we're using
236
+ // the latest.
237
+ const resizeObserver = new ResizeObserver(observerCallback);
238
+ if (scrollElement && editorParentElement) {
239
+ resizeObserver.observe(scrollElement);
240
+ resizeObserver.observe(editorParentElement);
241
+ }
190
242
  return {
191
243
  destroy() {
192
244
  // Remove if it's mounted
193
245
  gutter.destroy();
194
246
  scrollElement = editorParentElement = editorElement = null;
247
+ resizeObserver.disconnect();
195
248
  },
196
249
  /**
197
250
  * Toggle the Scroll Gutter Element
198
251
  */
199
252
  update(view, prevState) {
200
- var _getKeyboardHeight;
201
253
  if (!scrollElement || !editorParentElement) {
202
254
  return;
203
255
  }
@@ -206,37 +258,7 @@ export default ((pluginOptions = {}) => {
206
258
  // No need to recheck if the selected node hasn't changed.
207
259
  return;
208
260
  }
209
- const gutterMounted = gutter.isMounted();
210
- const addAndObserveGutter = () => {
211
- if (!gutterMounted) {
212
- gutter.observe(scrollElement);
213
- gutter.addGutter(editorParentElement);
214
- }
215
- };
216
- if (pluginOptions.persistScrollGutter) {
217
- if (!isEmptyDocument(state.doc)) {
218
- addAndObserveGutter();
219
- } else {
220
- gutter.removeGutter();
221
- }
222
- return;
223
- }
224
- // Determine whether we need to consider Keyboard Height
225
- const keyboardHeight = (_getKeyboardHeight = getKeyboardHeight(state)) !== null && _getKeyboardHeight !== void 0 ? _getKeyboardHeight : 0;
226
- const viewportHeight = scrollElement.offsetHeight - keyboardHeight;
227
- const contentHeight = editorParentElement.offsetHeight - (gutterMounted ? gutterSize : 0);
228
-
229
- // Add or remove the gutter based on whether the content is about to exceed the viewport height.
230
- // We do this to ensure there is sufficient white space below the last content node in order to
231
- // see any UI control elements which may sit beneath it.
232
- const gutterThresholdHeight = viewportHeight - gutterSize;
233
- if (contentHeight >= gutterThresholdHeight) {
234
- addAndObserveGutter();
235
- } else {
236
- if (gutterMounted) {
237
- gutter.removeGutter();
238
- }
239
- }
261
+ toggleGutter(viewportElementHeight, contentElementHeight);
240
262
  }
241
263
  };
242
264
  }
@@ -0,0 +1,244 @@
1
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import { GUTTER_SELECTOR, GUTTER_SIZE_IN_PX, isEmptyDocument } from '@atlaskit/editor-common/utils';
3
+ import { getScrollGutterPluginState, scrollGutterPluginKey } from './plugin-key';
4
+ export const getKeyboardHeight = state => {
5
+ if (state) {
6
+ const scrollGutterPluginState = getScrollGutterPluginState(state);
7
+ return scrollGutterPluginState ? scrollGutterPluginState.keyboardHeight : undefined;
8
+ }
9
+ };
10
+ const MIN_TAP_SIZE_IN_PX = 40;
11
+ function supportsIntersectionObserver() {
12
+ if (typeof window !== 'undefined' && 'IntersectionObserver' in window && 'IntersectionObserverEntry' in window && 'intersectionRatio' in
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
+ window.IntersectionObserverEntry.prototype) {
15
+ return true;
16
+ }
17
+ return false;
18
+ }
19
+ function listenForGutterVisibilityChanges(scrollElement, gutterIsVisible) {
20
+ if (supportsIntersectionObserver()) {
21
+ const observer = new IntersectionObserver((entries, _) => {
22
+ entries.forEach(entry => {
23
+ gutterIsVisible(entry.intersectionRatio > 0);
24
+ });
25
+ }, {
26
+ root: scrollElement,
27
+ rootMargin: '0px',
28
+ threshold: 0
29
+ });
30
+ return observer;
31
+ }
32
+ return undefined;
33
+ }
34
+
35
+ /**
36
+ * Create a gutter element that can be added or removed from the DOM.
37
+ */
38
+ function createGutter(gutterSize) {
39
+ const gutter = document.createElement('div');
40
+ gutter.style.paddingBottom = `${gutterSize}px`;
41
+ gutter.id = GUTTER_SELECTOR.substr(1);
42
+ let initialized = false;
43
+ let mounted = false;
44
+ let currentParent = null;
45
+ let observer;
46
+ let isVisible = false;
47
+ return {
48
+ addGutter(parent) {
49
+ if (parent) {
50
+ currentParent = parent;
51
+ parent.appendChild(gutter);
52
+ mounted = true;
53
+ if (observer) {
54
+ observer.observe(gutter);
55
+ }
56
+ }
57
+ },
58
+ removeGutter() {
59
+ if (currentParent && mounted) {
60
+ mounted = false;
61
+ currentParent.removeChild(gutter);
62
+ if (observer) {
63
+ observer.unobserve(gutter);
64
+ }
65
+ }
66
+ },
67
+ element() {
68
+ return gutter;
69
+ },
70
+ isMounted() {
71
+ return mounted;
72
+ },
73
+ visible() {
74
+ // If we know whether it's visible we can avoid expensive calculations
75
+ if (observer) {
76
+ return isVisible;
77
+ }
78
+ // Fallback for legacy browsers assumes it's visible (if mounted)
79
+ return mounted;
80
+ },
81
+ observe(scrollElement) {
82
+ if (!initialized) {
83
+ initialized = true;
84
+ observer = listenForGutterVisibilityChanges(scrollElement, visible => isVisible = visible);
85
+ }
86
+ },
87
+ destroy() {
88
+ if (observer) {
89
+ observer.disconnect();
90
+ }
91
+ observer = undefined;
92
+ this.removeGutter();
93
+ initialized = mounted = false;
94
+ }
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Get caret top position given the current selection,
100
+ * use start container position as fallback
101
+ */
102
+ function getCaretTopPosition() {
103
+ const windowSelection = window.getSelection();
104
+ if (windowSelection && windowSelection.rangeCount > 0) {
105
+ const range = windowSelection.getRangeAt(0);
106
+ if (range) {
107
+ const clientRects = range.getClientRects();
108
+ // Return client rects
109
+ if (clientRects && clientRects.length > 0) {
110
+ return clientRects[0].top;
111
+ }
112
+
113
+ // Return container top
114
+ const container = range.startContainer;
115
+ if (container && container.getBoundingClientRect) {
116
+ return container.getBoundingClientRect().top;
117
+ }
118
+ }
119
+ }
120
+ return;
121
+ }
122
+ function scrollToGutterElement(scrollContainer, gutterElement, gutterSize) {
123
+ const viewportHeight = scrollContainer.offsetHeight;
124
+ const viewportOffsetY = scrollContainer.getBoundingClientRect().top;
125
+ const caretTopPosition = getCaretTopPosition();
126
+ if (!caretTopPosition) {
127
+ return false;
128
+ }
129
+ const caretTopFromContainer = caretTopPosition - viewportOffsetY;
130
+ const gutterThresholdTop = viewportHeight - gutterSize - MIN_TAP_SIZE_IN_PX * 2;
131
+ if (caretTopFromContainer < gutterThresholdTop) {
132
+ return false;
133
+ }
134
+
135
+ // Clamp the scroll position to above the scroll gutter element.
136
+ gutterElement.scrollIntoView(false);
137
+
138
+ // Mark scrolling as handled so that other plugin's don't override our position.
139
+ return true;
140
+ }
141
+ export default ((pluginOptions = {}) => {
142
+ const {
143
+ getScrollElement,
144
+ allowCustomScrollHandler,
145
+ gutterSize = GUTTER_SIZE_IN_PX
146
+ } = pluginOptions;
147
+ if (!getScrollElement) {
148
+ return undefined;
149
+ }
150
+ const gutter = createGutter(gutterSize);
151
+ let scrollElement = null; // | undefined;
152
+
153
+ return new SafePlugin({
154
+ key: scrollGutterPluginKey,
155
+ state: {
156
+ init: () => ({}),
157
+ apply: (tr, pluginState) => {
158
+ if (tr.getMeta(scrollGutterPluginKey)) {
159
+ return tr.getMeta(scrollGutterPluginKey);
160
+ }
161
+ return pluginState;
162
+ }
163
+ },
164
+ props: {
165
+ // Determines the distance (in pixels) between the cursor and the end of the visible viewport at which point,
166
+ // when scrolling the cursor into view, scrolling takes place.
167
+ // Defaults to 0: https://prosemirror.net/docs/ref/#view.EditorProps.scrollThreshold
168
+ scrollThreshold: gutterSize / 2,
169
+ // Determines the extra space (in pixels) that is left above or below the cursor when it is scrolled into view.
170
+ // Defaults to 5: https://prosemirror.net/docs/ref/#view.EditorProps.scrollMargin
171
+ scrollMargin: gutterSize,
172
+ // Called when the view, after updating its state, tries to scroll the selection into view
173
+ // https://prosemirror.net/docs/ref/#view.EditorProps.handleScrollToSelection
174
+ handleScrollToSelection: () => {
175
+ if (allowCustomScrollHandler === false) {
176
+ return false;
177
+ }
178
+ if (!gutter.isMounted() || !gutter.visible() || !scrollElement) {
179
+ // Avoid scrolling until applicable
180
+ return false;
181
+ }
182
+ return scrollToGutterElement(scrollElement, gutter.element(), gutterSize);
183
+ }
184
+ },
185
+ view(view) {
186
+ // Store references to avoid lookups on successive checks.
187
+ scrollElement = getScrollElement(view);
188
+ let editorElement = view.dom;
189
+ let editorParentElement = editorElement.parentElement;
190
+ return {
191
+ destroy() {
192
+ // Remove if it's mounted
193
+ gutter.destroy();
194
+ scrollElement = editorParentElement = editorElement = null;
195
+ },
196
+ /**
197
+ * Toggle the Scroll Gutter Element
198
+ */
199
+ update(view, prevState) {
200
+ var _getKeyboardHeight;
201
+ if (!scrollElement || !editorParentElement) {
202
+ return;
203
+ }
204
+ const state = view.state;
205
+ if (prevState.selection === state.selection) {
206
+ // No need to recheck if the selected node hasn't changed.
207
+ return;
208
+ }
209
+ const gutterMounted = gutter.isMounted();
210
+ const addAndObserveGutter = () => {
211
+ if (!gutterMounted) {
212
+ gutter.observe(scrollElement);
213
+ gutter.addGutter(editorParentElement);
214
+ }
215
+ };
216
+ if (pluginOptions.persistScrollGutter) {
217
+ if (!isEmptyDocument(state.doc)) {
218
+ addAndObserveGutter();
219
+ } else {
220
+ gutter.removeGutter();
221
+ }
222
+ return;
223
+ }
224
+ // Determine whether we need to consider Keyboard Height
225
+ const keyboardHeight = (_getKeyboardHeight = getKeyboardHeight(state)) !== null && _getKeyboardHeight !== void 0 ? _getKeyboardHeight : 0;
226
+ const viewportHeight = scrollElement.offsetHeight - keyboardHeight;
227
+ const contentHeight = editorParentElement.offsetHeight - (gutterMounted ? gutterSize : 0);
228
+
229
+ // Add or remove the gutter based on whether the content is about to exceed the viewport height.
230
+ // We do this to ensure there is sufficient white space below the last content node in order to
231
+ // see any UI control elements which may sit beneath it.
232
+ const gutterThresholdHeight = viewportHeight - gutterSize;
233
+ if (contentHeight >= gutterThresholdHeight) {
234
+ addAndObserveGutter();
235
+ } else {
236
+ if (gutterMounted) {
237
+ gutter.removeGutter();
238
+ }
239
+ }
240
+ }
241
+ };
242
+ }
243
+ });
244
+ });
@@ -5,6 +5,7 @@ import { doc, paragraph, text } from '@atlaskit/adf-schema';
5
5
  import { keymap } from '@atlaskit/editor-common/keymaps';
6
6
  import { baseKeymap } from '@atlaskit/editor-prosemirror/commands';
7
7
  import { history } from '@atlaskit/editor-prosemirror/history';
8
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
8
9
  import { setKeyboardHeight } from './commands';
9
10
  import contextIdentifierPlugin, { getContextIdentifier } from './pm-plugins/context-identifier';
10
11
  import disableSpellcheckingPlugin from './pm-plugins/disable-spell-checking';
@@ -13,6 +14,7 @@ import frozenEditor from './pm-plugins/frozen-editor';
13
14
  import inlineCursorTargetPlugin from './pm-plugins/inline-cursor-target';
14
15
  import newlinePreserveMarksPlugin from './pm-plugins/newline-preserve-marks';
15
16
  import scrollGutter, { getKeyboardHeight } from './pm-plugins/scroll-gutter';
17
+ import deprecatedScrollGutter from './pm-plugins/scroll-gutter/plugin-deprecated';
16
18
  var basePlugin = function basePlugin(_ref) {
17
19
  var _api$featureFlags;
18
20
  var options = _ref.config,
@@ -96,7 +98,7 @@ var basePlugin = function basePlugin(_ref) {
96
98
  plugins.push({
97
99
  name: 'scrollGutterPlugin',
98
100
  plugin: function plugin() {
99
- return scrollGutter(options.allowScrollGutter);
101
+ return getBooleanFF('platform.editor.scroll-gutter.reflow-optimisation') ? scrollGutter(options.allowScrollGutter) : deprecatedScrollGutter(options.allowScrollGutter);
100
102
  }
101
103
  });
102
104
  }
@@ -1,3 +1,6 @@
1
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, 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 normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
2
+ 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); }
3
+ 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; }
1
4
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
5
  import { GUTTER_SELECTOR, GUTTER_SIZE_IN_PX, isEmptyDocument } from '@atlaskit/editor-common/utils';
3
6
  import { getScrollGutterPluginState, scrollGutterPluginKey } from './plugin-key';
@@ -199,17 +202,78 @@ export default (function () {
199
202
  scrollElement = getScrollElement(view);
200
203
  var editorElement = view.dom;
201
204
  var editorParentElement = editorElement.parentElement;
205
+ var viewportElementHeight = 0;
206
+ var contentElementHeight = 0;
207
+ var addAndObserveGutter = function addAndObserveGutter() {
208
+ if (!gutter.isMounted()) {
209
+ gutter.observe(scrollElement);
210
+ gutter.addGutter(editorParentElement);
211
+ }
212
+ };
213
+ var toggleGutter = function toggleGutter(viewPortSize, contentSize) {
214
+ if (pluginOptions.persistScrollGutter) {
215
+ if (!isEmptyDocument(view.state.doc)) {
216
+ addAndObserveGutter();
217
+ } else {
218
+ gutter.removeGutter();
219
+ }
220
+ return;
221
+ }
222
+
223
+ // Add or remove the gutter based on whether the content is about to exceed the viewport height.
224
+ // We do this to ensure there is sufficient white space below the last content node in order to
225
+ // see any UI control elements which may sit beneath it.
226
+ var gutterThresholdHeight = viewPortSize - gutterSize;
227
+ if (contentSize >= gutterThresholdHeight) {
228
+ addAndObserveGutter();
229
+ } else {
230
+ if (gutter.isMounted()) {
231
+ gutter.removeGutter();
232
+ }
233
+ }
234
+ };
235
+ var observerCallback = function observerCallback(entries) {
236
+ var _iterator = _createForOfIteratorHelper(entries),
237
+ _step;
238
+ try {
239
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
240
+ var entry = _step.value;
241
+ var elementHeight = Math.round(entry.contentRect.height);
242
+ if (entry.target === scrollElement) {
243
+ var _getKeyboardHeight;
244
+ var keyboardHeight = (_getKeyboardHeight = getKeyboardHeight(view === null || view === void 0 ? void 0 : view.state)) !== null && _getKeyboardHeight !== void 0 ? _getKeyboardHeight : 0;
245
+ viewportElementHeight = elementHeight - keyboardHeight;
246
+ } else if (entry.target === editorParentElement) {
247
+ contentElementHeight = elementHeight - (gutter.isMounted() ? gutterSize : 0);
248
+ }
249
+ }
250
+ } catch (err) {
251
+ _iterator.e(err);
252
+ } finally {
253
+ _iterator.f();
254
+ }
255
+ toggleGutter(viewportElementHeight, contentElementHeight);
256
+ };
257
+
258
+ // Subscribe to updates of the height of the viewport and content elements
259
+ // We toggle the value when it is set here or when PM updates to ensure we're using
260
+ // the latest.
261
+ var resizeObserver = new ResizeObserver(observerCallback);
262
+ if (scrollElement && editorParentElement) {
263
+ resizeObserver.observe(scrollElement);
264
+ resizeObserver.observe(editorParentElement);
265
+ }
202
266
  return {
203
267
  destroy: function destroy() {
204
268
  // Remove if it's mounted
205
269
  gutter.destroy();
206
270
  scrollElement = editorParentElement = editorElement = null;
271
+ resizeObserver.disconnect();
207
272
  },
208
273
  /**
209
274
  * Toggle the Scroll Gutter Element
210
275
  */
211
276
  update: function update(view, prevState) {
212
- var _getKeyboardHeight;
213
277
  if (!scrollElement || !editorParentElement) {
214
278
  return;
215
279
  }
@@ -218,37 +282,7 @@ export default (function () {
218
282
  // No need to recheck if the selected node hasn't changed.
219
283
  return;
220
284
  }
221
- var gutterMounted = gutter.isMounted();
222
- var addAndObserveGutter = function addAndObserveGutter() {
223
- if (!gutterMounted) {
224
- gutter.observe(scrollElement);
225
- gutter.addGutter(editorParentElement);
226
- }
227
- };
228
- if (pluginOptions.persistScrollGutter) {
229
- if (!isEmptyDocument(state.doc)) {
230
- addAndObserveGutter();
231
- } else {
232
- gutter.removeGutter();
233
- }
234
- return;
235
- }
236
- // Determine whether we need to consider Keyboard Height
237
- var keyboardHeight = (_getKeyboardHeight = getKeyboardHeight(state)) !== null && _getKeyboardHeight !== void 0 ? _getKeyboardHeight : 0;
238
- var viewportHeight = scrollElement.offsetHeight - keyboardHeight;
239
- var contentHeight = editorParentElement.offsetHeight - (gutterMounted ? gutterSize : 0);
240
-
241
- // Add or remove the gutter based on whether the content is about to exceed the viewport height.
242
- // We do this to ensure there is sufficient white space below the last content node in order to
243
- // see any UI control elements which may sit beneath it.
244
- var gutterThresholdHeight = viewportHeight - gutterSize;
245
- if (contentHeight >= gutterThresholdHeight) {
246
- addAndObserveGutter();
247
- } else {
248
- if (gutterMounted) {
249
- gutter.removeGutter();
250
- }
251
- }
285
+ toggleGutter(viewportElementHeight, contentElementHeight);
252
286
  }
253
287
  };
254
288
  })
@@ -0,0 +1,256 @@
1
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import { GUTTER_SELECTOR, GUTTER_SIZE_IN_PX, isEmptyDocument } from '@atlaskit/editor-common/utils';
3
+ import { getScrollGutterPluginState, scrollGutterPluginKey } from './plugin-key';
4
+ export var getKeyboardHeight = function getKeyboardHeight(state) {
5
+ if (state) {
6
+ var scrollGutterPluginState = getScrollGutterPluginState(state);
7
+ return scrollGutterPluginState ? scrollGutterPluginState.keyboardHeight : undefined;
8
+ }
9
+ };
10
+ var MIN_TAP_SIZE_IN_PX = 40;
11
+ function supportsIntersectionObserver() {
12
+ if (typeof window !== 'undefined' && 'IntersectionObserver' in window && 'IntersectionObserverEntry' in window && 'intersectionRatio' in
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
+ window.IntersectionObserverEntry.prototype) {
15
+ return true;
16
+ }
17
+ return false;
18
+ }
19
+ function listenForGutterVisibilityChanges(scrollElement, gutterIsVisible) {
20
+ if (supportsIntersectionObserver()) {
21
+ var observer = new IntersectionObserver(function (entries, _) {
22
+ entries.forEach(function (entry) {
23
+ gutterIsVisible(entry.intersectionRatio > 0);
24
+ });
25
+ }, {
26
+ root: scrollElement,
27
+ rootMargin: '0px',
28
+ threshold: 0
29
+ });
30
+ return observer;
31
+ }
32
+ return undefined;
33
+ }
34
+
35
+ /**
36
+ * Create a gutter element that can be added or removed from the DOM.
37
+ */
38
+ function createGutter(gutterSize) {
39
+ var gutter = document.createElement('div');
40
+ gutter.style.paddingBottom = "".concat(gutterSize, "px");
41
+ gutter.id = GUTTER_SELECTOR.substr(1);
42
+ var initialized = false;
43
+ var mounted = false;
44
+ var currentParent = null;
45
+ var observer;
46
+ var isVisible = false;
47
+ return {
48
+ addGutter: function addGutter(parent) {
49
+ if (parent) {
50
+ currentParent = parent;
51
+ parent.appendChild(gutter);
52
+ mounted = true;
53
+ if (observer) {
54
+ observer.observe(gutter);
55
+ }
56
+ }
57
+ },
58
+ removeGutter: function removeGutter() {
59
+ if (currentParent && mounted) {
60
+ mounted = false;
61
+ currentParent.removeChild(gutter);
62
+ if (observer) {
63
+ observer.unobserve(gutter);
64
+ }
65
+ }
66
+ },
67
+ element: function element() {
68
+ return gutter;
69
+ },
70
+ isMounted: function isMounted() {
71
+ return mounted;
72
+ },
73
+ visible: function visible() {
74
+ // If we know whether it's visible we can avoid expensive calculations
75
+ if (observer) {
76
+ return isVisible;
77
+ }
78
+ // Fallback for legacy browsers assumes it's visible (if mounted)
79
+ return mounted;
80
+ },
81
+ observe: function observe(scrollElement) {
82
+ if (!initialized) {
83
+ initialized = true;
84
+ observer = listenForGutterVisibilityChanges(scrollElement, function (visible) {
85
+ return isVisible = visible;
86
+ });
87
+ }
88
+ },
89
+ destroy: function destroy() {
90
+ if (observer) {
91
+ observer.disconnect();
92
+ }
93
+ observer = undefined;
94
+ this.removeGutter();
95
+ initialized = mounted = false;
96
+ }
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Get caret top position given the current selection,
102
+ * use start container position as fallback
103
+ */
104
+ function getCaretTopPosition() {
105
+ var windowSelection = window.getSelection();
106
+ if (windowSelection && windowSelection.rangeCount > 0) {
107
+ var range = windowSelection.getRangeAt(0);
108
+ if (range) {
109
+ var clientRects = range.getClientRects();
110
+ // Return client rects
111
+ if (clientRects && clientRects.length > 0) {
112
+ return clientRects[0].top;
113
+ }
114
+
115
+ // Return container top
116
+ var container = range.startContainer;
117
+ if (container && container.getBoundingClientRect) {
118
+ return container.getBoundingClientRect().top;
119
+ }
120
+ }
121
+ }
122
+ return;
123
+ }
124
+ function scrollToGutterElement(scrollContainer, gutterElement, gutterSize) {
125
+ var viewportHeight = scrollContainer.offsetHeight;
126
+ var viewportOffsetY = scrollContainer.getBoundingClientRect().top;
127
+ var caretTopPosition = getCaretTopPosition();
128
+ if (!caretTopPosition) {
129
+ return false;
130
+ }
131
+ var caretTopFromContainer = caretTopPosition - viewportOffsetY;
132
+ var gutterThresholdTop = viewportHeight - gutterSize - MIN_TAP_SIZE_IN_PX * 2;
133
+ if (caretTopFromContainer < gutterThresholdTop) {
134
+ return false;
135
+ }
136
+
137
+ // Clamp the scroll position to above the scroll gutter element.
138
+ gutterElement.scrollIntoView(false);
139
+
140
+ // Mark scrolling as handled so that other plugin's don't override our position.
141
+ return true;
142
+ }
143
+ export default (function () {
144
+ var pluginOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
145
+ var getScrollElement = pluginOptions.getScrollElement,
146
+ allowCustomScrollHandler = pluginOptions.allowCustomScrollHandler,
147
+ _pluginOptions$gutter = pluginOptions.gutterSize,
148
+ gutterSize = _pluginOptions$gutter === void 0 ? GUTTER_SIZE_IN_PX : _pluginOptions$gutter;
149
+ if (!getScrollElement) {
150
+ return undefined;
151
+ }
152
+ var gutter = createGutter(gutterSize);
153
+ var scrollElement = null; // | undefined;
154
+
155
+ return new SafePlugin({
156
+ key: scrollGutterPluginKey,
157
+ state: {
158
+ init: function init() {
159
+ return {};
160
+ },
161
+ apply: function apply(tr, pluginState) {
162
+ if (tr.getMeta(scrollGutterPluginKey)) {
163
+ return tr.getMeta(scrollGutterPluginKey);
164
+ }
165
+ return pluginState;
166
+ }
167
+ },
168
+ props: {
169
+ // Determines the distance (in pixels) between the cursor and the end of the visible viewport at which point,
170
+ // when scrolling the cursor into view, scrolling takes place.
171
+ // Defaults to 0: https://prosemirror.net/docs/ref/#view.EditorProps.scrollThreshold
172
+ scrollThreshold: gutterSize / 2,
173
+ // Determines the extra space (in pixels) that is left above or below the cursor when it is scrolled into view.
174
+ // Defaults to 5: https://prosemirror.net/docs/ref/#view.EditorProps.scrollMargin
175
+ scrollMargin: gutterSize,
176
+ // Called when the view, after updating its state, tries to scroll the selection into view
177
+ // https://prosemirror.net/docs/ref/#view.EditorProps.handleScrollToSelection
178
+ handleScrollToSelection: function handleScrollToSelection() {
179
+ if (allowCustomScrollHandler === false) {
180
+ return false;
181
+ }
182
+ if (!gutter.isMounted() || !gutter.visible() || !scrollElement) {
183
+ // Avoid scrolling until applicable
184
+ return false;
185
+ }
186
+ return scrollToGutterElement(scrollElement, gutter.element(), gutterSize);
187
+ }
188
+ },
189
+ view: function (_view) {
190
+ function view(_x) {
191
+ return _view.apply(this, arguments);
192
+ }
193
+ view.toString = function () {
194
+ return _view.toString();
195
+ };
196
+ return view;
197
+ }(function (view) {
198
+ // Store references to avoid lookups on successive checks.
199
+ scrollElement = getScrollElement(view);
200
+ var editorElement = view.dom;
201
+ var editorParentElement = editorElement.parentElement;
202
+ return {
203
+ destroy: function destroy() {
204
+ // Remove if it's mounted
205
+ gutter.destroy();
206
+ scrollElement = editorParentElement = editorElement = null;
207
+ },
208
+ /**
209
+ * Toggle the Scroll Gutter Element
210
+ */
211
+ update: function update(view, prevState) {
212
+ var _getKeyboardHeight;
213
+ if (!scrollElement || !editorParentElement) {
214
+ return;
215
+ }
216
+ var state = view.state;
217
+ if (prevState.selection === state.selection) {
218
+ // No need to recheck if the selected node hasn't changed.
219
+ return;
220
+ }
221
+ var gutterMounted = gutter.isMounted();
222
+ var addAndObserveGutter = function addAndObserveGutter() {
223
+ if (!gutterMounted) {
224
+ gutter.observe(scrollElement);
225
+ gutter.addGutter(editorParentElement);
226
+ }
227
+ };
228
+ if (pluginOptions.persistScrollGutter) {
229
+ if (!isEmptyDocument(state.doc)) {
230
+ addAndObserveGutter();
231
+ } else {
232
+ gutter.removeGutter();
233
+ }
234
+ return;
235
+ }
236
+ // Determine whether we need to consider Keyboard Height
237
+ var keyboardHeight = (_getKeyboardHeight = getKeyboardHeight(state)) !== null && _getKeyboardHeight !== void 0 ? _getKeyboardHeight : 0;
238
+ var viewportHeight = scrollElement.offsetHeight - keyboardHeight;
239
+ var contentHeight = editorParentElement.offsetHeight - (gutterMounted ? gutterSize : 0);
240
+
241
+ // Add or remove the gutter based on whether the content is about to exceed the viewport height.
242
+ // We do this to ensure there is sufficient white space below the last content node in order to
243
+ // see any UI control elements which may sit beneath it.
244
+ var gutterThresholdHeight = viewportHeight - gutterSize;
245
+ if (contentHeight >= gutterThresholdHeight) {
246
+ addAndObserveGutter();
247
+ } else {
248
+ if (gutterMounted) {
249
+ gutter.removeGutter();
250
+ }
251
+ }
252
+ }
253
+ };
254
+ })
255
+ });
256
+ });
@@ -0,0 +1,22 @@
1
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import type { EditorState } from '@atlaskit/editor-prosemirror/state';
3
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
4
+ export declare const getKeyboardHeight: (state?: EditorState) => number | undefined;
5
+ export type ScrollGutterPluginOptions = {
6
+ /** Element the page uses for scrolling */
7
+ getScrollElement?: (view: EditorView) => HTMLElement | null;
8
+ /**
9
+ * Whether to allow custom functionality to scroll to gutter element in
10
+ * plugin's handleScrollToSelection function
11
+ * Default is true
12
+ */
13
+ allowCustomScrollHandler?: boolean;
14
+ /**
15
+ * Persist scroll gutter when the mobile appearance is COMPACT
16
+ * Default is false
17
+ */
18
+ persistScrollGutter?: boolean;
19
+ gutterSize?: number;
20
+ };
21
+ declare const _default: (pluginOptions?: ScrollGutterPluginOptions) => SafePlugin<{}> | undefined;
22
+ export default _default;
@@ -0,0 +1,22 @@
1
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import type { EditorState } from '@atlaskit/editor-prosemirror/state';
3
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
4
+ export declare const getKeyboardHeight: (state?: EditorState) => number | undefined;
5
+ export type ScrollGutterPluginOptions = {
6
+ /** Element the page uses for scrolling */
7
+ getScrollElement?: (view: EditorView) => HTMLElement | null;
8
+ /**
9
+ * Whether to allow custom functionality to scroll to gutter element in
10
+ * plugin's handleScrollToSelection function
11
+ * Default is true
12
+ */
13
+ allowCustomScrollHandler?: boolean;
14
+ /**
15
+ * Persist scroll gutter when the mobile appearance is COMPACT
16
+ * Default is false
17
+ */
18
+ persistScrollGutter?: boolean;
19
+ gutterSize?: number;
20
+ };
21
+ declare const _default: (pluginOptions?: ScrollGutterPluginOptions) => SafePlugin<{}> | undefined;
22
+ export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-base",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Base plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -13,7 +13,7 @@
13
13
  "releaseModel": "continuous",
14
14
  "runReact18": false
15
15
  },
16
- "repository": "https://bitbucket.org/atlassian/atlassian-frontend",
16
+ "repository": "https://bitbucket.org/atlassian/atlassian-frontend-mirror",
17
17
  "main": "dist/cjs/index.js",
18
18
  "module": "dist/esm/index.js",
19
19
  "module:es2019": "dist/es2019/index.js",
@@ -32,10 +32,11 @@
32
32
  ".": "./src/index.ts"
33
33
  },
34
34
  "dependencies": {
35
- "@atlaskit/adf-schema": "^35.2.0",
36
- "@atlaskit/editor-common": "^76.30.0",
35
+ "@atlaskit/adf-schema": "^35.3.0",
36
+ "@atlaskit/editor-common": "^77.0.0",
37
37
  "@atlaskit/editor-plugin-feature-flags": "^1.0.1",
38
38
  "@atlaskit/editor-prosemirror": "1.1.0",
39
+ "@atlaskit/platform-feature-flags": "^0.2.0",
39
40
  "@babel/runtime": "^7.0.0"
40
41
  },
41
42
  "devDependencies": {
@@ -43,12 +44,11 @@
43
44
  "@atlaskit/editor-plugin-content-insertion": "^0.1.0",
44
45
  "@atlaskit/editor-plugin-emoji": "^1.0.0",
45
46
  "@atlaskit/editor-plugin-guideline": "^0.5.0",
46
- "@atlaskit/editor-plugin-selection": "^0.1.0",
47
- "@atlaskit/editor-plugin-table": "^5.7.0",
48
- "@atlaskit/editor-plugin-type-ahead": "^0.8.0",
47
+ "@atlaskit/editor-plugin-selection": "^0.2.0",
48
+ "@atlaskit/editor-plugin-table": "^6.1.0",
49
+ "@atlaskit/editor-plugin-type-ahead": "^0.9.0",
49
50
  "@atlaskit/editor-plugin-width": "^0.2.0",
50
51
  "@atlaskit/util-data-test": "^17.8.0",
51
- "@atlaskit/webdriver-runner": "*",
52
52
  "@atlassian/atlassian-frontend-prettier-config-1.0.0": "npm:@atlassian/atlassian-frontend-prettier-config@1.0.0",
53
53
  "@testing-library/react": "^12.1.5",
54
54
  "raf-stub": "^2.0.1"
@@ -85,5 +85,15 @@
85
85
  ]
86
86
  }
87
87
  },
88
- "prettier": "@atlassian/atlassian-frontend-prettier-config-1.0.0"
88
+ "prettier": "@atlassian/atlassian-frontend-prettier-config-1.0.0",
89
+ "platform-feature-flags": {
90
+ "platform.editor.scroll-gutter.reflow-optimisation": {
91
+ "type": "boolean"
92
+ }
93
+ },
94
+ "stricter": {
95
+ "no-unused-dependencies": {
96
+ "checkDevDependencies": true
97
+ }
98
+ }
89
99
  }
package/tsconfig.app.json CHANGED
@@ -37,6 +37,9 @@
37
37
  },
38
38
  {
39
39
  "path": "../editor-plugin-feature-flags/tsconfig.app.json"
40
+ },
41
+ {
42
+ "path": "../../platform/feature-flags/tsconfig.app.json"
40
43
  }
41
44
  ]
42
45
  }