@atlaskit/editor-common 119.16.4 → 120.0.0

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 (46) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/codemods/117.0.0-upgrade-react-peer-dependencies.ts +6 -0
  3. package/dist/cjs/media-insert/rank.js +11 -0
  4. package/dist/cjs/monitoring/error.js +1 -1
  5. package/dist/cjs/node-anchor/limited-mode-document-thresholds.js +41 -3
  6. package/dist/cjs/node-anchor/node-anchor-provider.js +5 -46
  7. package/dist/cjs/node-anchor/should-enable-limited-mode.js +52 -0
  8. package/dist/cjs/quick-insert/ElementBrowserQuickInsertMenuItem.js +128 -0
  9. package/dist/cjs/quick-insert/QuickInsertMenuItem.js +24 -45
  10. package/dist/cjs/quick-insert/TypeAheadQuickInsertMenuItem.js +56 -0
  11. package/dist/cjs/quick-insert/quickInsertMenuItemUtils.js +22 -0
  12. package/dist/cjs/ui/DropList/index.js +1 -1
  13. package/dist/es2019/media-insert/rank.js +7 -0
  14. package/dist/es2019/monitoring/error.js +1 -1
  15. package/dist/es2019/node-anchor/limited-mode-document-thresholds.js +39 -2
  16. package/dist/es2019/node-anchor/node-anchor-provider.js +5 -48
  17. package/dist/es2019/node-anchor/should-enable-limited-mode.js +47 -0
  18. package/dist/es2019/quick-insert/ElementBrowserQuickInsertMenuItem.js +123 -0
  19. package/dist/es2019/quick-insert/QuickInsertMenuItem.js +24 -38
  20. package/dist/es2019/quick-insert/TypeAheadQuickInsertMenuItem.js +51 -0
  21. package/dist/es2019/quick-insert/quickInsertMenuItemUtils.js +13 -0
  22. package/dist/es2019/ui/DropList/index.js +1 -1
  23. package/dist/esm/media-insert/rank.js +4 -0
  24. package/dist/esm/monitoring/error.js +1 -1
  25. package/dist/esm/node-anchor/limited-mode-document-thresholds.js +41 -2
  26. package/dist/esm/node-anchor/node-anchor-provider.js +5 -46
  27. package/dist/esm/node-anchor/should-enable-limited-mode.js +46 -0
  28. package/dist/esm/quick-insert/ElementBrowserQuickInsertMenuItem.js +121 -0
  29. package/dist/esm/quick-insert/QuickInsertMenuItem.js +24 -43
  30. package/dist/esm/quick-insert/TypeAheadQuickInsertMenuItem.js +49 -0
  31. package/dist/esm/quick-insert/quickInsertMenuItemUtils.js +16 -0
  32. package/dist/esm/ui/DropList/index.js +1 -1
  33. package/dist/types/media-insert/rank.d.ts +3 -0
  34. package/dist/types/node-anchor/limited-mode-document-thresholds.d.ts +29 -2
  35. package/dist/types/node-anchor/should-enable-limited-mode.d.ts +21 -0
  36. package/dist/types/quick-insert/ElementBrowserQuickInsertMenuItem.d.ts +3 -0
  37. package/dist/types/quick-insert/QuickInsertContext.d.ts +1 -0
  38. package/dist/types/quick-insert/QuickInsertMenuItem.d.ts +8 -8
  39. package/dist/types/quick-insert/TypeAheadQuickInsertMenuItem.d.ts +3 -0
  40. package/dist/types/quick-insert/quickInsertMenuItemUtils.d.ts +2 -0
  41. package/media-insert/rank/package.json +10 -0
  42. package/package.json +2 -2
  43. package/should-enable-limited-mode/package.json +10 -0
  44. /package/dist/cjs/quick-insert/{QuickInsertMenuItem.compiled.css → TypeAheadQuickInsertMenuItem.compiled.css} +0 -0
  45. /package/dist/es2019/quick-insert/{QuickInsertMenuItem.compiled.css → TypeAheadQuickInsertMenuItem.compiled.css} +0 -0
  46. /package/dist/esm/quick-insert/{QuickInsertMenuItem.compiled.css → TypeAheadQuickInsertMenuItem.compiled.css} +0 -0
@@ -1,7 +1,7 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import { isEmptyDocument } from '../utils';
3
3
  import { DynamicBitArray } from './dynamic-bit-array';
4
- import { LIMITED_MODE_DEFAULT_DOC_SIZE_THRESHOLD, LIMITED_MODE_DEFAULT_NODE_COUNT_THRESHOLD } from './limited-mode-document-thresholds';
4
+ import { shouldEnableLimitedModeForDocument } from './should-enable-limited-mode';
5
5
  export class NodeAnchorProvider {
6
6
  constructor(limitedMode = false, emptyDoc = false) {
7
7
  _defineProperty(this, "cache", new WeakMap());
@@ -67,54 +67,11 @@ export class NodeAnchorProvider {
67
67
  const nodeIdProviderMap = new WeakMap();
68
68
 
69
69
  /**
70
- * Determines whether limited mode should be enabled.
71
- * This logic mirrors the limited mode plugin implementation, but lives here to avoid a circular dependency.
72
- * If it changes, update the matching logic in `editor-plugin-limited-mode/src/pm-plugins/main.ts`.
73
- *
74
- * Limited mode is activated when ANY of the following conditions are met:
75
- * 1. Document size exceeds `LIMITED_MODE_DEFAULT_DOC_SIZE_THRESHOLD` — checked first as O(1)
76
- * 2. Node count exceeds `LIMITED_MODE_DEFAULT_NODE_COUNT_THRESHOLD`
77
- * 3. Document contains a legacy-content macro (LCM)
78
- *
79
- * Performance optimisations:
80
- * - Doc size is checked first (O(1)) - if it exceeds threshold, we skip traversal entirely.
81
- * - If we find an LCM during traversal, we exit early since limited mode will be enabled.
70
+ * Determines whether limited mode should be enabled for the given editor view.
71
+ * Delegates to the shared decision in `should-enable-limited-mode.ts` so the provider and the
72
+ * limited-mode plugin can never drift.
82
73
  */
83
- const isLimitedModeEnabled = editorView => {
84
- const doc = editorView.state.doc;
85
- const nodeCountThreshold = LIMITED_MODE_DEFAULT_NODE_COUNT_THRESHOLD;
86
- const docSizeThreshold = LIMITED_MODE_DEFAULT_DOC_SIZE_THRESHOLD;
87
-
88
- // Early exit: doc size exceeds threshold - O(1), no traversal needed
89
- if (doc.nodeSize > docSizeThreshold) {
90
- return true;
91
- }
92
-
93
- // Single traversal for node count and LCM detection
94
- let nodeCount = 0;
95
- let hasLcm = false;
96
- doc.descendants(node => {
97
- var _node$attrs;
98
- nodeCount += 1;
99
- if (((_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.extensionKey) === 'legacy-content') {
100
- hasLcm = true;
101
-
102
- // Early exit: LCM found — limited mode will be enabled
103
- return false;
104
- }
105
- });
106
-
107
- // LCM condition takes precedence (if we early exited traversal, this is why)
108
- if (hasLcm) {
109
- return true;
110
- }
111
-
112
- // Check node count threshold
113
- if (nodeCount > nodeCountThreshold) {
114
- return true;
115
- }
116
- return false;
117
- };
74
+ const isLimitedModeEnabled = editorView => shouldEnableLimitedModeForDocument(editorView.state.doc);
118
75
 
119
76
  // Get the NodeIdProvider for a specific EditorView instance.
120
77
  // This allows access to the node ids anywhere.
@@ -0,0 +1,47 @@
1
+ import { getLimitedModeThresholds } from './limited-mode-document-thresholds';
2
+ const LEGACY_CONTENT_EXTENSION_KEY = 'legacy-content';
3
+
4
+ /**
5
+ * Single source of truth for the limited (performance) mode decision.
6
+ *
7
+ * Shared by the limited-mode plugin (`editor-plugin-limited-mode/src/pm-plugins/main.ts`) and the
8
+ * node-anchor provider (`node-anchor-provider.ts`) so the logic can no longer drift between two
9
+ * hand-synced copies. It lives in `editor-common` to avoid a circular dependency (the plugin already
10
+ * depends on `editor-common`; `editor-common` must not depend on the plugin).
11
+ *
12
+ * Limited mode is enabled when ANY of the following is true:
13
+ * 1. `doc.nodeSize` exceeds the doc-size threshold (checked first, O(1))
14
+ * 2. the document contains a legacy-content macro (LCM)
15
+ * 3. node count exceeds the node-count threshold
16
+ *
17
+ * The thresholds come from `getLimitedModeThresholds()`, which is where the
18
+ * `platform_editor_limited_threshold_tweaks` cohort is resolved — see
19
+ * `limited-mode-document-thresholds.ts` for the values and the data behind them.
20
+ *
21
+ * Performance: doc size is checked first so an oversized document skips the traversal entirely.
22
+ */
23
+ export const shouldEnableLimitedModeForDocument = doc => {
24
+ const {
25
+ docSizeThreshold,
26
+ nodeCountThreshold
27
+ } = getLimitedModeThresholds();
28
+ if (doc.nodeSize > docSizeThreshold) {
29
+ return true;
30
+ }
31
+
32
+ // Single traversal for node count and LCM detection.
33
+ let nodeCount = 0;
34
+ let hasLcm = false;
35
+ doc.descendants(node => {
36
+ var _node$attrs;
37
+ nodeCount += 1;
38
+ if (((_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.extensionKey) === LEGACY_CONTENT_EXTENSION_KEY) {
39
+ hasLcm = true;
40
+
41
+ // Early exit: LCM found — limited mode will be enabled
42
+ return false;
43
+ }
44
+ return true;
45
+ });
46
+ return hasLcm || nodeCount > nodeCountThreshold;
47
+ };
@@ -0,0 +1,123 @@
1
+ import React from 'react';
2
+
3
+ // eslint-disable-next-line @atlaskit/design-system/no-emotion-primitives -- Compiled primitives do not provide Pressable.
4
+ import { Box, Inline, Pressable, Stack, Text, xcss } from '@atlaskit/primitives';
5
+ import { useQuickInsertMenuItemSelection } from './quickInsertMenuItemUtils';
6
+ import { useQuickInsertContext } from './useQuickInsertContext';
7
+ const itemStyles = xcss({
8
+ alignItems: 'start',
9
+ backgroundColor: 'color.background.neutral.subtle',
10
+ borderColor: 'color.border',
11
+ borderRadius: 'radius.medium',
12
+ borderStyle: 'solid',
13
+ borderWidth: 'border.width',
14
+ color: 'color.text',
15
+ cursor: 'pointer',
16
+ display: 'flex',
17
+ gap: 'space.150',
18
+ padding: 'space.150',
19
+ textAlign: 'start',
20
+ width: '100%'
21
+ });
22
+ const selectedItemStyles = xcss({
23
+ alignItems: 'start',
24
+ backgroundColor: 'color.background.selected',
25
+ borderColor: 'color.border.selected',
26
+ borderRadius: 'radius.medium',
27
+ borderStyle: 'solid',
28
+ borderWidth: 'border.width',
29
+ color: 'color.text',
30
+ cursor: 'pointer',
31
+ display: 'flex',
32
+ gap: 'space.150',
33
+ padding: 'space.150',
34
+ textAlign: 'start',
35
+ width: '100%'
36
+ });
37
+ const disabledItemStyles = xcss({
38
+ alignItems: 'start',
39
+ backgroundColor: 'color.background.neutral.subtle',
40
+ borderColor: 'color.border',
41
+ borderRadius: 'radius.medium',
42
+ borderStyle: 'solid',
43
+ borderWidth: 'border.width',
44
+ color: 'color.text.disabled',
45
+ cursor: 'not-allowed',
46
+ display: 'flex',
47
+ gap: 'space.150',
48
+ padding: 'space.150',
49
+ textAlign: 'start',
50
+ width: '100%'
51
+ });
52
+ const iconStyles = xcss({
53
+ alignItems: 'center',
54
+ backgroundColor: 'elevation.surface.overlay',
55
+ borderColor: 'color.border',
56
+ borderRadius: 'radius.medium',
57
+ borderStyle: 'solid',
58
+ borderWidth: 'border.width',
59
+ display: 'flex',
60
+ flexShrink: 0,
61
+ height: '40px',
62
+ justifyContent: 'center',
63
+ overflow: 'hidden',
64
+ width: '40px'
65
+ });
66
+ const descriptionStyles = xcss({
67
+ color: 'color.text.subtle'
68
+ });
69
+ const shortcutStyles = xcss({
70
+ backgroundColor: 'color.background.neutral',
71
+ borderRadius: 'radius.small',
72
+ color: 'color.text.subtle',
73
+ font: 'font.body.small',
74
+ paddingBlock: 'space.025',
75
+ paddingInline: 'space.050'
76
+ });
77
+ export const ElementBrowserQuickInsertMenuItem = ({
78
+ ariaLabel,
79
+ description,
80
+ iconBefore,
81
+ isDisabled,
82
+ onSelect,
83
+ shortcut,
84
+ shouldWrapIcon = true,
85
+ title
86
+ }) => {
87
+ const {
88
+ item
89
+ } = useQuickInsertContext();
90
+ const {
91
+ id,
92
+ isSelected
93
+ } = item !== null && item !== void 0 ? item : {
94
+ id: undefined,
95
+ isSelected: false
96
+ };
97
+ const handleClick = useQuickInsertMenuItemSelection(onSelect);
98
+ return /*#__PURE__*/React.createElement(Pressable, {
99
+ "aria-label": ariaLabel,
100
+ "aria-selected": isSelected,
101
+ id: id,
102
+ isDisabled: isDisabled,
103
+ onClick: handleClick,
104
+ role: "option",
105
+ xcss: isDisabled ? disabledItemStyles : isSelected ? selectedItemStyles : itemStyles
106
+ }, iconBefore && shouldWrapIcon ? /*#__PURE__*/React.createElement(Box, {
107
+ xcss: iconStyles
108
+ }, iconBefore) : iconBefore, /*#__PURE__*/React.createElement(Stack, {
109
+ space: "space.050",
110
+ grow: "fill"
111
+ }, /*#__PURE__*/React.createElement(Inline, {
112
+ alignBlock: "center",
113
+ spread: "space-between",
114
+ space: "space.100"
115
+ }, /*#__PURE__*/React.createElement(Text, {
116
+ weight: "medium"
117
+ }, title), shortcut && /*#__PURE__*/React.createElement(Box, {
118
+ as: "span",
119
+ xcss: shortcutStyles
120
+ }, shortcut)), description && /*#__PURE__*/React.createElement(Box, {
121
+ xcss: descriptionStyles
122
+ }, /*#__PURE__*/React.createElement(Text, null, description))));
123
+ };
@@ -1,51 +1,37 @@
1
- /* QuickInsertMenuItem.tsx generated by @compiled/babel-plugin v2.0.0 */
2
- import "./QuickInsertMenuItem.compiled.css";
3
- import { ax, ix } from "@compiled/react/runtime";
4
- import React, { useCallback } from 'react';
5
- import ButtonItem from '@atlaskit/menu/button-item';
6
- import { Box, Inline, Text } from '@atlaskit/primitives/compiled';
1
+ import React from 'react';
2
+ import { ElementBrowserQuickInsertMenuItem } from './ElementBrowserQuickInsertMenuItem';
3
+ import { TypeAheadQuickInsertMenuItem } from './TypeAheadQuickInsertMenuItem';
7
4
  import { useQuickInsertContext } from './useQuickInsertContext';
8
- const styles = {
9
- icon: "_2rko1qi0 _1reo15vq _18m915vq _1h6d1l7x _1dqonqa1 _189ee4h9 _4cvr1h6o _bfhk1bhr _1e0c1txw _4t3izwfg _1bah1h6o _1bsbzwfg",
10
- shortcut: "_2rko12b0 _11c8wadc _1rjcv77o _18zr1b66 _bfhki8nm _syazazsu"
11
- };
12
5
  export const QuickInsertMenuItem = ({
13
6
  ariaLabel,
7
+ description,
14
8
  iconBefore,
15
9
  isDisabled,
16
10
  onSelect,
17
11
  shortcut,
18
- shouldWrapIcon = true,
12
+ shouldWrapIcon,
19
13
  title
20
14
  }) => {
21
- var _item$isSelected;
22
15
  const {
23
- editorView,
24
- item,
25
- select
16
+ surface
26
17
  } = useQuickInsertContext();
27
- const isSelected = (_item$isSelected = item === null || item === void 0 ? void 0 : item.isSelected) !== null && _item$isSelected !== void 0 ? _item$isSelected : false;
28
- const handleClick = useCallback(() => select(selectionContext => onSelect({
29
- editorView,
30
- insert: selectionContext.insert,
31
- source: selectionContext.source
32
- })), [editorView, onSelect, select]);
33
- return /*#__PURE__*/React.createElement(ButtonItem, {
34
- "aria-label": ariaLabel,
35
- "aria-selected": isSelected,
36
- iconBefore: iconBefore && shouldWrapIcon ? /*#__PURE__*/React.createElement(Box, {
37
- xcss: styles.icon
38
- }, iconBefore) : iconBefore,
39
- id: item === null || item === void 0 ? void 0 : item.id,
18
+ return surface === 'element-browser' ? /*#__PURE__*/React.createElement(ElementBrowserQuickInsertMenuItem, {
19
+ ariaLabel: ariaLabel,
20
+ description: description,
21
+ iconBefore: iconBefore,
40
22
  isDisabled: isDisabled,
41
- isSelected: isSelected,
42
- onClick: handleClick,
43
- role: "option"
44
- }, /*#__PURE__*/React.createElement(Inline, {
45
- alignBlock: "center",
46
- spread: "space-between"
47
- }, /*#__PURE__*/React.createElement(Text, null, title), shortcut && /*#__PURE__*/React.createElement(Box, {
48
- as: "span",
49
- xcss: styles.shortcut
50
- }, shortcut)));
23
+ onSelect: onSelect,
24
+ shortcut: shortcut,
25
+ shouldWrapIcon: shouldWrapIcon,
26
+ title: title
27
+ }) : /*#__PURE__*/React.createElement(TypeAheadQuickInsertMenuItem, {
28
+ ariaLabel: ariaLabel,
29
+ description: description,
30
+ iconBefore: iconBefore,
31
+ isDisabled: isDisabled,
32
+ onSelect: onSelect,
33
+ shortcut: shortcut,
34
+ shouldWrapIcon: shouldWrapIcon,
35
+ title: title
36
+ });
51
37
  };
@@ -0,0 +1,51 @@
1
+ /* TypeAheadQuickInsertMenuItem.tsx generated by @compiled/babel-plugin v2.0.0 */
2
+ import "./TypeAheadQuickInsertMenuItem.compiled.css";
3
+ import { ax, ix } from "@compiled/react/runtime";
4
+ import React from 'react';
5
+ import ButtonItem from '@atlaskit/menu/button-item';
6
+ import { Box, Inline, Text } from '@atlaskit/primitives/compiled';
7
+ import { useQuickInsertMenuItemSelection } from './quickInsertMenuItemUtils';
8
+ import { useQuickInsertContext } from './useQuickInsertContext';
9
+ const styles = {
10
+ icon: "_2rko1qi0 _1reo15vq _18m915vq _1h6d1l7x _1dqonqa1 _189ee4h9 _4cvr1h6o _bfhk1bhr _1e0c1txw _4t3izwfg _1bah1h6o _1bsbzwfg",
11
+ shortcut: "_2rko12b0 _11c8wadc _1rjcv77o _18zr1b66 _bfhki8nm _syazazsu"
12
+ };
13
+ export const TypeAheadQuickInsertMenuItem = ({
14
+ ariaLabel,
15
+ iconBefore,
16
+ isDisabled,
17
+ onSelect,
18
+ shortcut,
19
+ shouldWrapIcon = true,
20
+ title
21
+ }) => {
22
+ const {
23
+ item
24
+ } = useQuickInsertContext();
25
+ const {
26
+ id,
27
+ isSelected
28
+ } = item !== null && item !== void 0 ? item : {
29
+ id: undefined,
30
+ isSelected: false
31
+ };
32
+ const handleClick = useQuickInsertMenuItemSelection(onSelect);
33
+ return /*#__PURE__*/React.createElement(ButtonItem, {
34
+ "aria-label": ariaLabel,
35
+ "aria-selected": isSelected,
36
+ iconBefore: iconBefore && shouldWrapIcon ? /*#__PURE__*/React.createElement(Box, {
37
+ xcss: styles.icon
38
+ }, iconBefore) : iconBefore,
39
+ id: id,
40
+ isDisabled: isDisabled,
41
+ isSelected: isSelected,
42
+ onClick: handleClick,
43
+ role: "option"
44
+ }, /*#__PURE__*/React.createElement(Inline, {
45
+ alignBlock: "center",
46
+ spread: "space-between"
47
+ }, /*#__PURE__*/React.createElement(Text, null, title), shortcut && /*#__PURE__*/React.createElement(Box, {
48
+ as: "span",
49
+ xcss: styles.shortcut
50
+ }, shortcut)));
51
+ };
@@ -0,0 +1,13 @@
1
+ import { useCallback } from 'react';
2
+ import { useQuickInsertContext } from './useQuickInsertContext';
3
+ export const useQuickInsertMenuItemSelection = onSelect => {
4
+ const {
5
+ editorView,
6
+ select
7
+ } = useQuickInsertContext();
8
+ return useCallback(() => select(selectionContext => onSelect({
9
+ editorView,
10
+ insert: selectionContext.insert,
11
+ source: selectionContext.source
12
+ })), [editorView, onSelect, select]);
13
+ };
@@ -14,7 +14,7 @@ import withAnalyticsEvents from '@atlaskit/analytics-next/withAnalyticsEvents';
14
14
  import { fg } from '@atlaskit/platform-feature-flags/fg';
15
15
  import Layer from '../Layer';
16
16
  const packageName = "@atlaskit/editor-common";
17
- const packageVersion = "119.16.3";
17
+ const packageVersion = "119.17.0";
18
18
  const halfFocusRing = 1;
19
19
  const dropOffset = '0, 8';
20
20
  const fadeIn = keyframes({
@@ -0,0 +1,4 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import { MEDIA_INSERT_TAB } from '../analytics/types/enums';
3
+ export var DEFAULT_MEDIA_INSERT_TAB_RANK = 0;
4
+ export var MEDIA_INSERT_TAB_RANK = _defineProperty(_defineProperty(_defineProperty({}, MEDIA_INSERT_TAB.UPLOAD, 100), MEDIA_INSERT_TAB.LINK, 200), MEDIA_INSERT_TAB.CREATE, 300);
@@ -20,7 +20,7 @@ var NETWORK_FAILURE_REGEX = /^network failure/i;
20
20
  var RESIZE_OBSERVER_LOOP_REGEX = /ResizeObserver loop completed with undelivered notifications/;
21
21
  var SENTRY_DSN = 'https://0b10c8e02fb44d8796c047b102c9bee8@o55978.ingest.sentry.io/4505129224110080';
22
22
  var packageName = 'editor-common'; // Sentry doesn't accept '/' in its releases https://docs.sentry.io/platforms/javascript/configuration/releases/
23
- var packageVersion = "119.16.3";
23
+ var packageVersion = "119.17.0";
24
24
  var sanitiseSentryEvents = function sanitiseSentryEvents(data, _hint) {
25
25
  // Remove URL as it has UGC
26
26
  // Ignored via go/ees007
@@ -1,9 +1,48 @@
1
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
2
+
1
3
  /** Fixed document thresholds for limited (performance) mode (previously Statsig-driven). */
2
4
  export var LIMITED_MODE_DEFAULT_NODE_COUNT_THRESHOLD = 5000;
3
5
 
4
6
  // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
5
7
  export var LIMITED_MODE_DEFAULT_DOC_SIZE_THRESHOLD = 30000;
6
8
 
7
- /** When true, any legacy-content macro triggers limited mode regardless of size/count. */
9
+ /**
10
+ * Treatment node-count threshold. Production node-count percentiles across all documents are
11
+ * p90 ≈ 206, p95 ≈ 576, p99 ≈ 2,438 — today's 5,000 sits beyond p99 and fires on only ~0.36% of
12
+ * documents. 2,500 is p99 rounded, i.e. limited mode becomes a guard on the largest ~1% of pages.
13
+ *
14
+ * Deliberately NOT lower: the worst-5%-by-INP cohort has a median document of ~25 nodes, so its
15
+ * slowness is device/extension/collab-bound and structurally invisible. No node-count value catches
16
+ * it without enabling limited mode for nearly everyone (300 nodes ⇒ 8% of all sessions for 34%
17
+ * recall). Chasing that cohort needs a runtime input-latency trigger, not a bigger document guard.
18
+ */
19
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
20
+ export var LIMITED_MODE_TWEAKED_NODE_COUNT_THRESHOLD = 2438;
21
+
22
+ /**
23
+ * Treatment `nodeSize` threshold — a backstop for pathologically huge documents, not a routine
24
+ * trigger. Input latency really does degrade at extreme document sizes: the 100k+ `nodeSize` bucket
25
+ * sits at p90 113.5ms / p99 899ms, against p90 25.6ms in the 6k–11k bucket. But that curve is smooth
26
+ * and only becomes severe far past the body of the distribution (production `nodeSize` p99 is
27
+ * ~91.8k), so this is set well beyond p99 rather than at the control's 30,000 — which fires around
28
+ * p95 and so degrades a large population of plain long-text pages that type perfectly well.
29
+ */
30
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
31
+ export var LIMITED_MODE_TWEAKED_DOC_SIZE_THRESHOLD = 750000;
32
+
33
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
34
+
35
+ /**
36
+ * The document thresholds currently in force. This is the only place the cohort is resolved — the
37
+ * limited-mode decision itself is identical either way, only these two numbers change.
38
+ */
8
39
  // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
9
- export var LIMITED_MODE_INCLUDE_LEGACY_CONTENT_IN_THRESHOLD = true;
40
+ export var getLimitedModeThresholds = function getLimitedModeThresholds() {
41
+ return isExperimentEnabled('platform_editor_limited_threshold_tweaks') ? {
42
+ docSizeThreshold: LIMITED_MODE_TWEAKED_DOC_SIZE_THRESHOLD,
43
+ nodeCountThreshold: LIMITED_MODE_TWEAKED_NODE_COUNT_THRESHOLD
44
+ } : {
45
+ docSizeThreshold: LIMITED_MODE_DEFAULT_DOC_SIZE_THRESHOLD,
46
+ nodeCountThreshold: LIMITED_MODE_DEFAULT_NODE_COUNT_THRESHOLD
47
+ };
48
+ };
@@ -3,7 +3,7 @@ import _createClass from "@babel/runtime/helpers/createClass";
3
3
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
4
4
  import { isEmptyDocument } from '../utils';
5
5
  import { DynamicBitArray } from './dynamic-bit-array';
6
- import { LIMITED_MODE_DEFAULT_DOC_SIZE_THRESHOLD, LIMITED_MODE_DEFAULT_NODE_COUNT_THRESHOLD } from './limited-mode-document-thresholds';
6
+ import { shouldEnableLimitedModeForDocument } from './should-enable-limited-mode';
7
7
  export var NodeAnchorProvider = /*#__PURE__*/function () {
8
8
  function NodeAnchorProvider() {
9
9
  var limitedMode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
@@ -87,53 +87,12 @@ export var NodeAnchorProvider = /*#__PURE__*/function () {
87
87
  var nodeIdProviderMap = new WeakMap();
88
88
 
89
89
  /**
90
- * Determines whether limited mode should be enabled.
91
- * This logic mirrors the limited mode plugin implementation, but lives here to avoid a circular dependency.
92
- * If it changes, update the matching logic in `editor-plugin-limited-mode/src/pm-plugins/main.ts`.
93
- *
94
- * Limited mode is activated when ANY of the following conditions are met:
95
- * 1. Document size exceeds `LIMITED_MODE_DEFAULT_DOC_SIZE_THRESHOLD` — checked first as O(1)
96
- * 2. Node count exceeds `LIMITED_MODE_DEFAULT_NODE_COUNT_THRESHOLD`
97
- * 3. Document contains a legacy-content macro (LCM)
98
- *
99
- * Performance optimisations:
100
- * - Doc size is checked first (O(1)) - if it exceeds threshold, we skip traversal entirely.
101
- * - If we find an LCM during traversal, we exit early since limited mode will be enabled.
90
+ * Determines whether limited mode should be enabled for the given editor view.
91
+ * Delegates to the shared decision in `should-enable-limited-mode.ts` so the provider and the
92
+ * limited-mode plugin can never drift.
102
93
  */
103
94
  var isLimitedModeEnabled = function isLimitedModeEnabled(editorView) {
104
- var doc = editorView.state.doc;
105
- var nodeCountThreshold = LIMITED_MODE_DEFAULT_NODE_COUNT_THRESHOLD;
106
- var docSizeThreshold = LIMITED_MODE_DEFAULT_DOC_SIZE_THRESHOLD;
107
-
108
- // Early exit: doc size exceeds threshold - O(1), no traversal needed
109
- if (doc.nodeSize > docSizeThreshold) {
110
- return true;
111
- }
112
-
113
- // Single traversal for node count and LCM detection
114
- var nodeCount = 0;
115
- var hasLcm = false;
116
- doc.descendants(function (node) {
117
- var _node$attrs;
118
- nodeCount += 1;
119
- if (((_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.extensionKey) === 'legacy-content') {
120
- hasLcm = true;
121
-
122
- // Early exit: LCM found — limited mode will be enabled
123
- return false;
124
- }
125
- });
126
-
127
- // LCM condition takes precedence (if we early exited traversal, this is why)
128
- if (hasLcm) {
129
- return true;
130
- }
131
-
132
- // Check node count threshold
133
- if (nodeCount > nodeCountThreshold) {
134
- return true;
135
- }
136
- return false;
95
+ return shouldEnableLimitedModeForDocument(editorView.state.doc);
137
96
  };
138
97
 
139
98
  // Get the NodeIdProvider for a specific EditorView instance.
@@ -0,0 +1,46 @@
1
+ import { getLimitedModeThresholds } from './limited-mode-document-thresholds';
2
+ var LEGACY_CONTENT_EXTENSION_KEY = 'legacy-content';
3
+
4
+ /**
5
+ * Single source of truth for the limited (performance) mode decision.
6
+ *
7
+ * Shared by the limited-mode plugin (`editor-plugin-limited-mode/src/pm-plugins/main.ts`) and the
8
+ * node-anchor provider (`node-anchor-provider.ts`) so the logic can no longer drift between two
9
+ * hand-synced copies. It lives in `editor-common` to avoid a circular dependency (the plugin already
10
+ * depends on `editor-common`; `editor-common` must not depend on the plugin).
11
+ *
12
+ * Limited mode is enabled when ANY of the following is true:
13
+ * 1. `doc.nodeSize` exceeds the doc-size threshold (checked first, O(1))
14
+ * 2. the document contains a legacy-content macro (LCM)
15
+ * 3. node count exceeds the node-count threshold
16
+ *
17
+ * The thresholds come from `getLimitedModeThresholds()`, which is where the
18
+ * `platform_editor_limited_threshold_tweaks` cohort is resolved — see
19
+ * `limited-mode-document-thresholds.ts` for the values and the data behind them.
20
+ *
21
+ * Performance: doc size is checked first so an oversized document skips the traversal entirely.
22
+ */
23
+ export var shouldEnableLimitedModeForDocument = function shouldEnableLimitedModeForDocument(doc) {
24
+ var _getLimitedModeThresh = getLimitedModeThresholds(),
25
+ docSizeThreshold = _getLimitedModeThresh.docSizeThreshold,
26
+ nodeCountThreshold = _getLimitedModeThresh.nodeCountThreshold;
27
+ if (doc.nodeSize > docSizeThreshold) {
28
+ return true;
29
+ }
30
+
31
+ // Single traversal for node count and LCM detection.
32
+ var nodeCount = 0;
33
+ var hasLcm = false;
34
+ doc.descendants(function (node) {
35
+ var _node$attrs;
36
+ nodeCount += 1;
37
+ if (((_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.extensionKey) === LEGACY_CONTENT_EXTENSION_KEY) {
38
+ hasLcm = true;
39
+
40
+ // Early exit: LCM found — limited mode will be enabled
41
+ return false;
42
+ }
43
+ return true;
44
+ });
45
+ return hasLcm || nodeCount > nodeCountThreshold;
46
+ };