@atlaskit/renderer 133.14.2 → 133.15.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 (33) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/afm-cc/tsconfig.json +3 -0
  3. package/dist/cjs/messages.js +13 -1
  4. package/dist/cjs/react/nodes/heading.compiled.css +12 -0
  5. package/dist/cjs/react/nodes/heading.js +188 -48
  6. package/dist/cjs/ui/Renderer/RendererStyleContainer.js +8 -1
  7. package/dist/cjs/ui/Renderer/index.js +8 -2
  8. package/dist/cjs/ui/collapsible-headings-dom.js +138 -0
  9. package/dist/cjs/ui/collapsible-headings-section-model.js +38 -0
  10. package/dist/cjs/ui/collapsible-headings.js +232 -0
  11. package/dist/es2019/messages.js +12 -0
  12. package/dist/es2019/react/nodes/heading.compiled.css +12 -0
  13. package/dist/es2019/react/nodes/heading.js +142 -32
  14. package/dist/es2019/ui/Renderer/RendererStyleContainer.js +8 -0
  15. package/dist/es2019/ui/Renderer/index.js +8 -2
  16. package/dist/es2019/ui/collapsible-headings-dom.js +118 -0
  17. package/dist/es2019/ui/collapsible-headings-section-model.js +32 -0
  18. package/dist/es2019/ui/collapsible-headings.js +183 -0
  19. package/dist/esm/messages.js +12 -0
  20. package/dist/esm/react/nodes/heading.compiled.css +12 -0
  21. package/dist/esm/react/nodes/heading.js +188 -46
  22. package/dist/esm/ui/Renderer/RendererStyleContainer.js +8 -1
  23. package/dist/esm/ui/Renderer/index.js +8 -2
  24. package/dist/esm/ui/collapsible-headings-dom.js +129 -0
  25. package/dist/esm/ui/collapsible-headings-section-model.js +32 -0
  26. package/dist/esm/ui/collapsible-headings.js +221 -0
  27. package/dist/types/messages.d.ts +23 -21
  28. package/dist/types/react/nodes/heading.d.ts +3 -2
  29. package/dist/types/ui/collapsible-headings-dom.d.ts +14 -0
  30. package/dist/types/ui/collapsible-headings-section-model.d.ts +9 -0
  31. package/dist/types/ui/collapsible-headings.d.ts +26 -0
  32. package/dist/types/ui/renderer-props.d.ts +7 -0
  33. package/package.json +4 -3
@@ -0,0 +1,232 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _typeof = require("@babel/runtime/helpers/typeof");
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.CollapsibleHeadingsProvider = CollapsibleHeadingsProvider;
9
+ exports.useCollapsibleHeading = useCollapsibleHeading;
10
+ exports.useCollapsibleHeadingsController = useCollapsibleHeadingsController;
11
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
12
+ var _react = _interopRequireWildcard(require("react"));
13
+ var _bindEventListener = require("bind-event-listener");
14
+ var _collapsibleHeadingsDom = require("./collapsible-headings-dom");
15
+ var _collapsibleHeadingsSectionModel = require("./collapsible-headings-section-model");
16
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
17
+ var CollapsibleHeadingsContext = /*#__PURE__*/_react.default.createContext(null);
18
+ var emptyCollapsedHeadings = new Set();
19
+ function supportsHiddenUntilFound(rendererRef) {
20
+ var _rendererRef$current;
21
+ var body = (_rendererRef$current = rendererRef.current) === null || _rendererRef$current === void 0 ? void 0 : _rendererRef$current.ownerDocument.body;
22
+ return body ? 'onbeforematch' in body : false;
23
+ }
24
+
25
+ /** Provides collapse state and synchronizes hidden top-level renderer content. */
26
+ function CollapsibleHeadingsProvider(_ref) {
27
+ var children = _ref.children,
28
+ isEnabled = _ref.isEnabled,
29
+ pmDocument = _ref.pmDocument,
30
+ rendererRef = _ref.rendererRef;
31
+ var _useState = (0, _react.useState)(false),
32
+ _useState2 = (0, _slicedToArray2.default)(_useState, 2),
33
+ isBrowserFindSupported = _useState2[0],
34
+ setIsBrowserFindSupported = _useState2[1];
35
+ var _useState3 = (0, _react.useState)({
36
+ pmDocument: pmDocument,
37
+ positions: new Set()
38
+ }),
39
+ _useState4 = (0, _slicedToArray2.default)(_useState3, 2),
40
+ state = _useState4[0],
41
+ setState = _useState4[1];
42
+ var originalHiddenAttributes = _react.default.useRef(new WeakMap()).current;
43
+ var sections = (0, _react.useMemo)(function () {
44
+ return (0, _collapsibleHeadingsSectionModel.buildTopLevelHeadingSections)(pmDocument);
45
+ }, [pmDocument]);
46
+ var collapsibleSectionPositions = (0, _react.useMemo)(function () {
47
+ return new Set(sections.filter(function (section) {
48
+ return section.contentFrom < section.to;
49
+ }).map(function (section) {
50
+ return section.headingPos;
51
+ }));
52
+ }, [sections]);
53
+ var collapsedHeadings = state.pmDocument === pmDocument ? state.positions : emptyCollapsedHeadings;
54
+ var isActive = isEnabled && isBrowserFindSupported && Boolean(pmDocument);
55
+ (0, _react.useEffect)(function () {
56
+ setIsBrowserFindSupported(isEnabled && supportsHiddenUntilFound(rendererRef));
57
+ }, [isEnabled, rendererRef]);
58
+ var updateCollapsedHeadings = (0, _react.useCallback)(function (update) {
59
+ setState(function (currentState) {
60
+ var positions = new Set(currentState.pmDocument === pmDocument ? currentState.positions : emptyCollapsedHeadings);
61
+ if (!update(positions)) {
62
+ return currentState.pmDocument === pmDocument ? currentState : {
63
+ pmDocument: pmDocument,
64
+ positions: positions
65
+ };
66
+ }
67
+ return {
68
+ pmDocument: pmDocument,
69
+ positions: positions
70
+ };
71
+ });
72
+ }, [pmDocument]);
73
+ var collapse = (0, _react.useCallback)(function (headingPos) {
74
+ if (!collapsibleSectionPositions.has(headingPos)) {
75
+ return;
76
+ }
77
+ updateCollapsedHeadings(function (positions) {
78
+ if (positions.has(headingPos)) {
79
+ return false;
80
+ }
81
+ positions.add(headingPos);
82
+ return true;
83
+ });
84
+ }, [collapsibleSectionPositions, updateCollapsedHeadings]);
85
+ var expand = (0, _react.useCallback)(function (headingPos) {
86
+ updateCollapsedHeadings(function (positions) {
87
+ return positions.delete(headingPos);
88
+ });
89
+ }, [updateCollapsedHeadings]);
90
+ var expandMany = (0, _react.useCallback)(function (headingPositions) {
91
+ var positionsToExpand = new Set(headingPositions);
92
+ updateCollapsedHeadings(function (positions) {
93
+ var didChange = false;
94
+ positionsToExpand.forEach(function (headingPos) {
95
+ didChange = positions.delete(headingPos) || didChange;
96
+ });
97
+ return didChange;
98
+ });
99
+ }, [updateCollapsedHeadings]);
100
+ var expandContainingPosition = (0, _react.useCallback)(function (position) {
101
+ expandMany(sections.filter(function (section) {
102
+ return position >= section.contentFrom && position < section.to;
103
+ }).map(function (section) {
104
+ return section.headingPos;
105
+ }));
106
+ }, [expandMany, sections]);
107
+ var toggle = (0, _react.useCallback)(function (headingPos) {
108
+ if (!collapsibleSectionPositions.has(headingPos)) {
109
+ return;
110
+ }
111
+ updateCollapsedHeadings(function (positions) {
112
+ if (positions.has(headingPos)) {
113
+ positions.delete(headingPos);
114
+ } else {
115
+ positions.add(headingPos);
116
+ }
117
+ return true;
118
+ });
119
+ }, [collapsibleSectionPositions, updateCollapsedHeadings]);
120
+ var contextValue = (0, _react.useMemo)(function () {
121
+ return isActive ? {
122
+ canCollapse: function canCollapse(headingPos) {
123
+ return collapsibleSectionPositions.has(headingPos);
124
+ },
125
+ collapse: collapse,
126
+ expand: expand,
127
+ expandContainingPosition: expandContainingPosition,
128
+ expandMany: expandMany,
129
+ isCollapsed: function isCollapsed(headingPos) {
130
+ return collapsedHeadings.has(headingPos);
131
+ },
132
+ toggle: toggle
133
+ } : null;
134
+ }, [collapse, collapsedHeadings, collapsibleSectionPositions, expand, expandContainingPosition, expandMany, isActive, toggle]);
135
+ (0, _react.useEffect)(function () {
136
+ var rendererRoot = rendererRef.current;
137
+ if (!rendererRoot) {
138
+ return;
139
+ }
140
+ var positions = isActive ? collapsedHeadings : emptyCollapsedHeadings;
141
+ var sync = function sync() {
142
+ return (0, _collapsibleHeadingsDom.reconcileCollapsedHeadingContent)({
143
+ collapsedHeadings: positions,
144
+ originalHiddenAttributes: originalHiddenAttributes,
145
+ rendererRoot: rendererRoot,
146
+ sections: sections
147
+ });
148
+ };
149
+ sync();
150
+ if (!isActive || positions.size === 0) {
151
+ return;
152
+ }
153
+ var rendererDocuments = (0, _collapsibleHeadingsDom.getOwnedRendererDocuments)(rendererRoot);
154
+ var rendererDocumentSet = new Set(rendererDocuments);
155
+ var handleBeforeMatch = function handleBeforeMatch(event) {
156
+ var _rendererRoot$ownerDo;
157
+ var target = event.target;
158
+ var HTMLElementConstructor = (_rendererRoot$ownerDo = rendererRoot.ownerDocument.defaultView) === null || _rendererRoot$ownerDo === void 0 ? void 0 : _rendererRoot$ownerDo.HTMLElement;
159
+ if (!HTMLElementConstructor || !(target instanceof HTMLElementConstructor)) {
160
+ return;
161
+ }
162
+ var collapsedContent = target.closest("[".concat(_collapsibleHeadingsDom.COLLAPSED_CONTENT_OWNERS_ATTRIBUTE, "]"));
163
+ if (!collapsedContent) {
164
+ return;
165
+ }
166
+ var rendererDocument = collapsedContent.closest('.ak-renderer-document');
167
+ if (!rendererDocument || !rendererDocumentSet.has(rendererDocument)) {
168
+ return;
169
+ }
170
+ expandMany((0, _collapsibleHeadingsDom.getCollapsedContentOwners)(collapsedContent));
171
+ };
172
+ var unbindBeforeMatchListeners = rendererDocuments.map(function (rendererDocument) {
173
+ return (0, _bindEventListener.bind)(rendererDocument, {
174
+ type: 'beforematch',
175
+ listener: handleBeforeMatch,
176
+ options: {
177
+ capture: true
178
+ }
179
+ });
180
+ });
181
+ var observer = typeof MutationObserver === 'undefined' ? undefined : new MutationObserver(sync);
182
+ rendererDocuments.forEach(function (rendererDocument) {
183
+ observer === null || observer === void 0 || observer.observe(rendererDocument, {
184
+ childList: true
185
+ });
186
+ });
187
+ return function () {
188
+ observer === null || observer === void 0 || observer.disconnect();
189
+ unbindBeforeMatchListeners.forEach(function (unbindBeforeMatch) {
190
+ return unbindBeforeMatch();
191
+ });
192
+ };
193
+ }, [collapsedHeadings, expandMany, isActive, originalHiddenAttributes, rendererRef, sections]);
194
+ (0, _react.useEffect)(function () {
195
+ return function () {
196
+ var rendererRoot = rendererRef.current;
197
+ if (rendererRoot) {
198
+ (0, _collapsibleHeadingsDom.reconcileCollapsedHeadingContent)({
199
+ collapsedHeadings: emptyCollapsedHeadings,
200
+ originalHiddenAttributes: originalHiddenAttributes,
201
+ rendererRoot: rendererRoot,
202
+ sections: []
203
+ });
204
+ }
205
+ };
206
+ }, [originalHiddenAttributes, rendererRef]);
207
+ return /*#__PURE__*/_react.default.createElement(CollapsibleHeadingsContext.Provider, {
208
+ value: contextValue
209
+ }, children);
210
+ }
211
+
212
+ /** Returns collapse state for a top-level heading when the provider is enabled. */
213
+ function useCollapsibleHeading(startPos, isTopLevel) {
214
+ var context = _react.default.useContext(CollapsibleHeadingsContext);
215
+ var contextToggle = context === null || context === void 0 ? void 0 : context.toggle;
216
+ var isCollapsible = Boolean(context && isTopLevel && context.canCollapse(startPos));
217
+ var isCollapsed = Boolean(isCollapsible && (context === null || context === void 0 ? void 0 : context.isCollapsed(startPos)));
218
+ var toggle = (0, _react.useCallback)(function () {
219
+ return contextToggle === null || contextToggle === void 0 ? void 0 : contextToggle(startPos);
220
+ }, [contextToggle, startPos]);
221
+ return (0, _react.useMemo)(function () {
222
+ return isCollapsible ? {
223
+ isCollapsed: isCollapsed,
224
+ toggle: toggle
225
+ } : null;
226
+ }, [isCollapsed, isCollapsible, toggle]);
227
+ }
228
+
229
+ /** Returns the collapse controller for integrations such as renderer Find. */
230
+ function useCollapsibleHeadingsController() {
231
+ return _react.default.useContext(CollapsibleHeadingsContext);
232
+ }
@@ -2,6 +2,18 @@
2
2
  // Entry file in package.json
3
3
 
4
4
  import { defineMessages } from 'react-intl';
5
+ export const collapsibleHeadingMessages = defineMessages({
6
+ collapseSection: {
7
+ id: 'fabric.editor.collapsibleHeading.collapseSection.ai-non-final',
8
+ defaultMessage: 'Collapse section',
9
+ description: 'Accessible label and tooltip for the button beside an expanded renderer heading. Activating the button hides the content in that heading section.'
10
+ },
11
+ expandSection: {
12
+ id: 'fabric.editor.collapsibleHeading.expandSection.ai-non-final',
13
+ defaultMessage: 'Expand section',
14
+ description: 'Accessible label and tooltip for the button beside a collapsed renderer heading. Activating the button reveals the content in that heading section.'
15
+ }
16
+ });
5
17
  export const headingAnchorLinkMessages = defineMessages({
6
18
  copyHeadingLinkToClipboard: {
7
19
  id: 'fabric.editor.headingLink.copyAnchorLink',
@@ -0,0 +1,12 @@
1
+ ._152tidpf{inset-block-start:0}
2
+ ._1e02idpf{inset-inline-start:0}
3
+ ._1e0c1ule{display:block}
4
+ ._1pbykb7n{z-index:1}
5
+ ._ahbqukr8{margin-inline-start:var(--ds-space-negative-400,-2pc)}
6
+ ._bozgxy5q{padding-inline-start:var(--ds-space-400,2pc)}
7
+ ._kqswh2mm{position:relative}
8
+ ._kqswstnw{position:absolute}
9
+ ._lcxvglyw{pointer-events:none}
10
+ ._tzy4idpf{opacity:0}
11
+ @media (hover:none){._tza3kb7n{opacity:1}._12yg1wug{pointer-events:auto}}
12
+ @media (pointer:coarse){._xrqrkb7n{opacity:1}._l4c51wug{pointer-events:auto}}
@@ -1,18 +1,22 @@
1
- /**
2
- * @jsxRuntime classic
3
- * @jsx jsx
4
- */
1
+ /* heading.tsx generated by @compiled/babel-plugin v0.40.0 */
2
+ import "./heading.compiled.css";
3
+ import { ax, ix } from "@compiled/react/runtime";
5
4
  import React from 'react';
5
+ import { useIntl } from 'react-intl';
6
+ import { IconButton } from '@atlaskit/button/new';
7
+ import ChevronDownIcon from '@atlaskit/icon/core/chevron-down';
8
+ import ChevronRightIcon from '@atlaskit/icon/core/chevron-right';
6
9
  import { abortAll } from '@atlaskit/react-ufo/interaction-metrics';
7
10
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
8
11
  import VisuallyHidden from '@atlaskit/visually-hidden/visually-hidden';
9
12
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
10
13
  import AnalyticsContext from '../../analytics/analyticsContext';
11
14
  import { copyTextToClipboard } from '../utils/clipboard';
15
+ import { collapsibleHeadingMessages } from '../../messages';
16
+ import { useCollapsibleHeading } from '../../ui/collapsible-headings';
12
17
  import HeadingAnchor from './heading-anchor';
13
- // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
14
- import { css, jsx } from '@emotion/react';
15
18
  const RENDERER_HEADING_WRAPPER = 'renderer-heading-wrapper';
19
+ const RENDERER_COLLAPSIBLE_HEADING_WRAPPER = 'renderer-collapsible-heading-wrapper';
16
20
  const getCurrentUrlWithHash = (hash = '') => {
17
21
  const url = new URL(window.location.href);
18
22
  url.search = ''; // clear any query params so that the page will correctly scroll to the anchor
@@ -25,13 +29,25 @@ function hasRightAlignmentMark(marks) {
25
29
  }
26
30
  return marks.some(mark => mark.type.name === 'alignment' && mark.attrs.align === 'end');
27
31
  }
28
- const wrapperStyles = css({
29
- // Important: do NOT use flex here.
30
- // With flex + baseline alignment, the anchor aligns to the *first line* of a multi-line heading,
31
- // which visually places it at the top-right. We want the anchor to sit immediately after the
32
- // last character of the heading (i.e. after the final wrapped line), so we use normal inline flow.
33
- display: 'block'
32
+ const wrapperStyles = null;
33
+ const collapsibleHeadingWrapperStyles = null;
34
+ const collapsibleHeadingButtonStyles = null;
35
+ const collapsibleHeadingButtonHiddenStyles = null;
36
+ const ChevronDownSmallIcon = () => /*#__PURE__*/React.createElement(ChevronDownIcon, {
37
+ label: "",
38
+ size: "small"
34
39
  });
40
+ const ChevronRightSmallIcon = () => /*#__PURE__*/React.createElement(ChevronRightIcon, {
41
+ label: "",
42
+ size: "small"
43
+ });
44
+ const hasFocusVisible = target => {
45
+ try {
46
+ return target.matches(':focus-visible');
47
+ } catch {
48
+ return true;
49
+ }
50
+ };
35
51
  const getHeadingAnchorLinksConfig = allowHeadingAnchorLinks => typeof allowHeadingAnchorLinks === 'object' ? allowHeadingAnchorLinks : undefined;
36
52
  function WrappedHeadingAnchor({
37
53
  enableNestedHeaderLinks,
@@ -40,9 +56,9 @@ function WrappedHeadingAnchor({
40
56
  headingId,
41
57
  hideFromScreenReader
42
58
  }) {
43
- return jsx(AnalyticsContext.Consumer, null, ({
59
+ return /*#__PURE__*/React.createElement(AnalyticsContext.Consumer, null, ({
44
60
  fireAnalyticsEvent
45
- }) => jsx(HeadingAnchor, {
61
+ }) => /*#__PURE__*/React.createElement(HeadingAnchor, {
46
62
  enableNestedHeaderLinks: enableNestedHeaderLinks,
47
63
  level: level
48
64
  // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
@@ -61,6 +77,73 @@ function WrappedHeadingAnchor({
61
77
  headingId: headingId
62
78
  }));
63
79
  }
80
+ function CollapsibleHeadingButton({
81
+ collapsibleHeading,
82
+ isHeadingHovered
83
+ }) {
84
+ const intl = useIntl();
85
+ const [isFocused, setIsFocused] = React.useState(false);
86
+ const {
87
+ isCollapsed,
88
+ toggle
89
+ } = collapsibleHeading;
90
+ const isVisible = isCollapsed || isFocused || isHeadingHovered;
91
+ const handleClick = React.useCallback(event => {
92
+ event.stopPropagation();
93
+ toggle();
94
+ }, [toggle]);
95
+ const handleFocus = React.useCallback(event => {
96
+ setIsFocused(hasFocusVisible(event.target));
97
+ }, []);
98
+ const handleBlur = React.useCallback(() => setIsFocused(false), []);
99
+ const handleKeyDown = React.useCallback(() => setIsFocused(true), []);
100
+ return /*#__PURE__*/React.createElement("span", {
101
+ className: ax(["_kqswstnw _152tidpf _1e02idpf _1pbykb7n", !isVisible && "_tzy4idpf _lcxvglyw _tza3kb7n _12yg1wug _xrqrkb7n _l4c51wug"])
102
+ }, /*#__PURE__*/React.createElement(IconButton, {
103
+ appearance: "subtle",
104
+ spacing: "compact",
105
+ icon: isCollapsed ? ChevronRightSmallIcon : ChevronDownSmallIcon,
106
+ label: intl.formatMessage(isCollapsed ? collapsibleHeadingMessages.expandSection : collapsibleHeadingMessages.collapseSection),
107
+ "aria-expanded": !isCollapsed,
108
+ isTooltipDisabled: false,
109
+ testId: "collapsible-heading-button",
110
+ onClick: handleClick,
111
+ onFocus: handleFocus,
112
+ onBlur: handleBlur,
113
+ onKeyDown: handleKeyDown
114
+ }));
115
+ }
116
+ function CollapsibleHeadingContainer({
117
+ children,
118
+ collapsibleHeading,
119
+ headingLevel
120
+ }) {
121
+ const mouseEntered = React.useRef(false);
122
+ const [isHovered, setIsHovered] = React.useState(false);
123
+ const [isFocused, setIsFocused] = React.useState(false);
124
+ const mouseEnterHandler = () => {
125
+ if (!mouseEntered.current) {
126
+ abortAll('new_interaction');
127
+ mouseEntered.current = true;
128
+ }
129
+ };
130
+ return /*#__PURE__*/React.createElement("div", {
131
+ "data-testid": RENDERER_COLLAPSIBLE_HEADING_WRAPPER,
132
+ "data-level": headingLevel,
133
+ onMouseEnter: () => {
134
+ mouseEnterHandler();
135
+ setIsHovered(true);
136
+ },
137
+ onMouseLeave: () => setIsHovered(false),
138
+ onFocus: event => setIsFocused(hasFocusVisible(event.target)),
139
+ onBlur: () => setIsFocused(false),
140
+ className: ax(["_ahbqukr8 _bozgxy5q _kqswh2mm"])
141
+ }, /*#__PURE__*/React.createElement(CollapsibleHeadingButton, {
142
+ collapsibleHeading: collapsibleHeading,
143
+ isHeadingHovered: isHovered || isFocused
144
+ }), children);
145
+ }
146
+
64
147
  /**
65
148
  * Old heading structure (before a11y fix):
66
149
  * - headning anchor is rendered INSIDE the heading element
@@ -95,28 +178,28 @@ function HeadingWithDuplicateAnchor(props) {
95
178
  mouseEntered.current = true;
96
179
  }
97
180
  };
98
- return jsx(React.Fragment, null, jsx(HX, {
181
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(HX, {
99
182
  id: headingIdToUse,
100
183
  "data-local-id": localId,
101
184
  "data-renderer-start-pos": dataAttributes['data-renderer-start-pos'],
102
185
  "data-as-inline": asInline,
103
186
  onMouseEnter: mouseEnterHandler,
104
187
  tabIndex: -1
105
- }, jsx(React.Fragment, null, showAnchorLink && headingId && isRightAligned && jsx(WrappedHeadingAnchor, {
188
+ }, /*#__PURE__*/React.createElement(React.Fragment, null, showAnchorLink && headingId && isRightAligned && /*#__PURE__*/React.createElement(WrappedHeadingAnchor, {
106
189
  level: props.level,
107
190
  enableNestedHeaderLinks: enableNestedHeaderLinks,
108
191
  getHeadingLink: getHeadingLink,
109
192
  headingId: headingId,
110
193
  hideFromScreenReader: true
111
- }), props.children, showAnchorLink && headingId && !isRightAligned && jsx(WrappedHeadingAnchor, {
194
+ }), props.children, showAnchorLink && headingId && !isRightAligned && /*#__PURE__*/React.createElement(WrappedHeadingAnchor, {
112
195
  level: props.level,
113
196
  enableNestedHeaderLinks: enableNestedHeaderLinks,
114
197
  getHeadingLink: getHeadingLink,
115
198
  headingId: headingId,
116
199
  hideFromScreenReader: true
117
- }))), jsx(VisuallyHidden, {
200
+ }))), /*#__PURE__*/React.createElement(VisuallyHidden, {
118
201
  testId: "visually-hidden-heading-anchor"
119
- }, showAnchorLink && headingId && jsx(WrappedHeadingAnchor, {
202
+ }, showAnchorLink && headingId && /*#__PURE__*/React.createElement(WrappedHeadingAnchor, {
120
203
  level: props.level,
121
204
  enableNestedHeaderLinks: enableNestedHeaderLinks,
122
205
  getHeadingLink: getHeadingLink,
@@ -138,10 +221,13 @@ function HeadingWithWrapper(props) {
138
221
  marks,
139
222
  invisible,
140
223
  localId,
141
- asInline
224
+ asInline,
225
+ collapsibleHeading
142
226
  } = props;
143
227
  const HX = `h${props.level}`;
144
228
  const mouseEntered = React.useRef(false);
229
+ const [isHovered, setIsHovered] = React.useState(false);
230
+ const [isFocused, setIsFocused] = React.useState(false);
145
231
  const showAnchorLink = !!props.showAnchorLink;
146
232
  const isRightAligned = hasRightAlignmentMark(marks);
147
233
  const headingAnchorLinksConfig = getHeadingAnchorLinksConfig(allowHeadingAnchorLinks);
@@ -149,7 +235,7 @@ function HeadingWithWrapper(props) {
149
235
  const getHeadingLink = headingAnchorLinksConfig === null || headingAnchorLinksConfig === void 0 ? void 0 : headingAnchorLinksConfig.getHeadingLink;
150
236
  const headingIdToUse = invisible ? undefined : headingId;
151
237
  const mouseEnterHandler = () => {
152
- if (showAnchorLink && !mouseEntered.current) {
238
+ if ((showAnchorLink || collapsibleHeading) && !mouseEntered.current) {
153
239
  // Abort TTVC calculation when the mouse hovers over heading. Hovering over
154
240
  // heading render heading anchor and inline comment buttons. These user-induced
155
241
  // DOM changes are valid reasons to abort the TTVC calculation.
@@ -157,26 +243,39 @@ function HeadingWithWrapper(props) {
157
243
  mouseEntered.current = true;
158
244
  }
159
245
  };
160
- return jsx("div", {
246
+ const handleWrapperMouseEnter = () => {
247
+ mouseEnterHandler();
248
+ setIsHovered(true);
249
+ };
250
+ const handleWrapperMouseLeave = () => setIsHovered(false);
251
+ const handleWrapperFocus = event => setIsFocused(hasFocusVisible(event.target));
252
+ const handleWrapperBlur = () => setIsFocused(false);
253
+ return /*#__PURE__*/React.createElement("div", {
161
254
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
162
- className: RENDERER_HEADING_WRAPPER,
255
+ className: ax(["_1e0c1ule", collapsibleHeading && "_ahbqukr8 _bozgxy5q _kqswh2mm", RENDERER_HEADING_WRAPPER]),
163
256
  "data-testid": RENDERER_HEADING_WRAPPER,
164
257
  "data-level": props.level,
165
- css: wrapperStyles
166
- }, showAnchorLink && headingId && isRightAligned && jsx(WrappedHeadingAnchor, {
258
+ onMouseEnter: collapsibleHeading ? handleWrapperMouseEnter : undefined,
259
+ onMouseLeave: collapsibleHeading ? handleWrapperMouseLeave : undefined,
260
+ onFocus: collapsibleHeading ? handleWrapperFocus : undefined,
261
+ onBlur: collapsibleHeading ? handleWrapperBlur : undefined
262
+ }, collapsibleHeading && /*#__PURE__*/React.createElement(CollapsibleHeadingButton, {
263
+ collapsibleHeading: collapsibleHeading,
264
+ isHeadingHovered: isHovered || isFocused
265
+ }), showAnchorLink && headingId && isRightAligned && /*#__PURE__*/React.createElement(WrappedHeadingAnchor, {
167
266
  level: props.level,
168
267
  enableNestedHeaderLinks: enableNestedHeaderLinks,
169
268
  getHeadingLink: getHeadingLink,
170
269
  headingId: headingId,
171
270
  hideFromScreenReader: false
172
- }), jsx(HX, {
271
+ }), /*#__PURE__*/React.createElement(HX, {
173
272
  id: headingIdToUse,
174
273
  "data-local-id": localId,
175
274
  "data-renderer-start-pos": dataAttributes['data-renderer-start-pos'],
176
275
  "data-as-inline": asInline,
177
- onMouseEnter: mouseEnterHandler,
276
+ onMouseEnter: collapsibleHeading ? undefined : mouseEnterHandler,
178
277
  tabIndex: -1
179
- }, props.children), showAnchorLink && headingId && !isRightAligned && jsx(WrappedHeadingAnchor, {
278
+ }, props.children), showAnchorLink && headingId && !isRightAligned && /*#__PURE__*/React.createElement(WrappedHeadingAnchor, {
180
279
  level: props.level,
181
280
  enableNestedHeaderLinks: enableNestedHeaderLinks,
182
281
  getHeadingLink: getHeadingLink,
@@ -189,7 +288,8 @@ function HeadingWithWrapper(props) {
189
288
  * Gated Heading component:
190
289
  * - When platform_editor_copy_link_a11y_inconsistency_fix experiment is enabled,
191
290
  * returns HeadingWithWrapper (new a11y-improved structure)
192
- * - Otherwise returns HeadingWithDuplicateAnchor (old structure)
291
+ * - Otherwise preserves HeadingWithDuplicateAnchor (old structure), adding only the collapse
292
+ * container when the top-level heading is collapsible
193
293
  */
194
294
  function Heading({
195
295
  allowHeadingAnchorLinks,
@@ -201,12 +301,14 @@ function Heading({
201
301
  localId,
202
302
  marks,
203
303
  nodeType,
304
+ path,
204
305
  showAnchorLink,
205
306
  serializer,
206
307
  asInline
207
308
  }) {
309
+ const collapsibleHeading = useCollapsibleHeading(dataAttributes['data-renderer-start-pos'], (path === null || path === void 0 ? void 0 : path.length) === 0);
208
310
  if (expValEquals('platform_editor_copy_link_a11y_inconsistency_fix', 'isEnabled', true)) {
209
- return jsx(HeadingWithWrapper, {
311
+ return /*#__PURE__*/React.createElement(HeadingWithWrapper, {
210
312
  allowHeadingAnchorLinks: allowHeadingAnchorLinks,
211
313
  dataAttributes: dataAttributes,
212
314
  headingId: headingId,
@@ -217,10 +319,11 @@ function Heading({
217
319
  nodeType: nodeType,
218
320
  serializer: serializer,
219
321
  showAnchorLink: showAnchorLink,
220
- asInline: asInline
322
+ asInline: asInline,
323
+ collapsibleHeading: collapsibleHeading
221
324
  }, children);
222
325
  }
223
- return jsx(HeadingWithDuplicateAnchor, {
326
+ const headingWithDuplicateAnchor = /*#__PURE__*/React.createElement(HeadingWithDuplicateAnchor, {
224
327
  allowHeadingAnchorLinks: allowHeadingAnchorLinks,
225
328
  dataAttributes: dataAttributes,
226
329
  headingId: headingId,
@@ -233,5 +336,12 @@ function Heading({
233
336
  showAnchorLink: showAnchorLink,
234
337
  asInline: asInline
235
338
  }, children);
339
+ if (collapsibleHeading) {
340
+ return /*#__PURE__*/React.createElement(CollapsibleHeadingContainer, {
341
+ collapsibleHeading: collapsibleHeading,
342
+ headingLevel: level
343
+ }, headingWithDuplicateAnchor);
344
+ }
345
+ return headingWithDuplicateAnchor;
236
346
  }
237
347
  export default Heading;
@@ -28,6 +28,7 @@ import { EmojiSharedCssClassName, defaultEmojiHeight, defaultDenseEmojiHeight, d
28
28
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
29
29
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
30
30
  import { BodiedSyncBlockSharedCssClassName, SyncBlockSharedCssClassName } from '@atlaskit/editor-common/sync-block';
31
+ import { COLLAPSED_CONTENT_OWNERS_ATTRIBUTE } from '../collapsible-headings-dom';
31
32
  const wrappedMediaBreakoutPoint = 410;
32
33
  const TELEPOINTER_ID = 'ai-streaming-telepointer';
33
34
  const tableShadowWidth = 32;
@@ -86,6 +87,13 @@ const baseStyles = css({
86
87
  clear: 'both'
87
88
  },
88
89
  [`.${RendererCssClassName.DOCUMENT}`]: {
90
+ [`> [${COLLAPSED_CONTENT_OWNERS_ATTRIBUTE}][hidden='until-found']`]: {
91
+ display: 'block',
92
+ minBlockSize: 0,
93
+ marginBlock: 0,
94
+ paddingBlock: 0,
95
+ borderBlockWidth: 0
96
+ },
89
97
  // p, h3, and action items
90
98
  [`.${INLINE_IMAGE_WRAPPER_CLASS_NAME}`]: {
91
99
  height: '22px',
@@ -36,6 +36,7 @@ import { RendererContext as ActionsContext, RendererActionsContext } from '../Re
36
36
  import { Provider as SmartCardStorageProvider } from '../SmartCardStorage';
37
37
  import { ActiveHeaderIdProvider } from '../active-header-id-provider';
38
38
  import { AnnotationsPositionContext, AnnotationsWrapper } from '../annotations';
39
+ import { CollapsibleHeadingsProvider } from '../collapsible-headings';
39
40
  import { ErrorBoundary } from './ErrorBoundary';
40
41
  import { BreakoutSSRInlineScript } from './breakout-ssr';
41
42
  import { isInteractiveElement } from './click-to-edit';
@@ -58,7 +59,7 @@ export const DEGRADED_SEVERITY_THRESHOLD = 3000;
58
59
  const TABLE_INFO_TIMEOUT = 10000;
59
60
  const RENDER_EVENT_SAMPLE_RATE = 0.1;
60
61
  const packageName = "@atlaskit/renderer";
61
- const packageVersion = "133.14.1";
62
+ const packageVersion = "133.14.2";
62
63
  const setAsQueryContainerStyles = css({
63
64
  containerName: 'ak-renderer-wrapper',
64
65
  containerType: 'inline-size'
@@ -457,6 +458,7 @@ export const RendererFunctionalComponent = props => {
457
458
  ...createRendererContext(props.featureFlags, props.isTopLevelRenderer),
458
459
  timeZone: props.timeZone
459
460
  }), [props.featureFlags, props.isTopLevelRenderer, createRendererContext, props.timeZone]);
461
+ const isCollapsibleHeadingsEnabled = props.allowCollapsibleHeadings === true && ['full-page', 'full-width', 'max'].includes(props.appearance || '') && rendererContext.isTopLevelRenderer && expValEquals('platform_renderer_collapsible_headings', 'isEnabled', true);
460
462
  useScrollToBlock(editorRef, props.document, props.scrollToBlock);
461
463
  try {
462
464
  var _rendererContext$feat, _props$media;
@@ -514,7 +516,11 @@ export const RendererFunctionalComponent = props => {
514
516
  doc: pmDoc,
515
517
  schema: schema,
516
518
  onAnalyticsEvent: fireAnalyticsEvent
517
- }, result)))))));
519
+ }, jsx(CollapsibleHeadingsProvider, {
520
+ pmDocument: pmDoc,
521
+ rendererRef: editorRef,
522
+ isEnabled: isCollapsibleHeadingsEnabled
523
+ }, result))))))));
518
524
  const rendererResult = props.truncated ? jsx(TruncatedWrapper, {
519
525
  height: props.maxHeight,
520
526
  fadeHeight: props.fadeOutHeight