@atlaskit/renderer 126.4.0 → 126.5.1

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,24 @@
1
1
  # @atlaskit/renderer
2
2
 
3
+ ## 126.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`1c91357f0c2e8`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/1c91357f0c2e8) -
8
+ Confcloud-83420: Fixed edge cases, unskipped tests
9
+
10
+ ## 126.5.0
11
+
12
+ ### Minor Changes
13
+
14
+ - [`d88e2cfa7371b`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/d88e2cfa7371b) -
15
+ [ux] fix copy heading link button a11y behaviours by only having one button, that is outside the
16
+ heading element
17
+
18
+ ### Patch Changes
19
+
20
+ - Updated dependencies
21
+
3
22
  ## 126.4.0
4
23
 
5
24
  ### Minor Changes
@@ -14,6 +14,16 @@ var headingAnchorLinkMessages = exports.headingAnchorLinkMessages = (0, _reactIn
14
14
  defaultMessage: 'Copy link to heading',
15
15
  description: 'Copy heading link to clipboard'
16
16
  },
17
+ copyLinkToClipboard: {
18
+ id: 'fabric.editor.headingLink.copyAnchorLinkTo',
19
+ defaultMessage: 'Copy link to',
20
+ description: 'Copy heading link to clipboard. Will be used as part of a 2-part a11y label ("Copy link to", "{heading text}")'
21
+ },
22
+ copyHeadingLinkLabelledBy: {
23
+ id: 'fabric.editor.headingLink.copyAnchorLinkLabelledBy',
24
+ defaultMessage: '{copyLink} {heading}',
25
+ description: 'The order in which to read the parts of the aria-labelledby for the copy heading link button depending on the grammar of the language. {copyLink} will be replaced with the "Copy link to" text and {heading} will be replaced with the actual heading text'
26
+ },
17
27
  copiedHeadingLinkToClipboard: {
18
28
  id: 'fabric.editor.headingLink.copied',
19
29
  defaultMessage: 'Copied!',
@@ -19,6 +19,7 @@ var _react = _interopRequireDefault(require("react"));
19
19
  var _react2 = require("@emotion/react");
20
20
  var _reactIntlNext = require("react-intl-next");
21
21
  var _link = _interopRequireDefault(require("@atlaskit/icon/core/link"));
22
+ var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
22
23
  var _tooltip = _interopRequireDefault(require("@atlaskit/tooltip"));
23
24
  var _messages = require("../../messages");
24
25
  var _excluded = ["children"];
@@ -51,13 +52,10 @@ var copyAnchorButtonStyles = (0, _react2.css)({
51
52
  // Ignored via go/ees005
52
53
  // eslint-disable-next-line @repo/internal/react/no-class-components
53
54
  var HeadingAnchor = /*#__PURE__*/function (_React$PureComponent) {
54
- function HeadingAnchor() {
55
+ function HeadingAnchor(props) {
55
56
  var _this;
56
57
  (0, _classCallCheck2.default)(this, HeadingAnchor);
57
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
58
- args[_key] = arguments[_key];
59
- }
60
- _this = _callSuper(this, HeadingAnchor, [].concat(args));
58
+ _this = _callSuper(this, HeadingAnchor, [props]);
61
59
  (0, _defineProperty2.default)(_this, "state", {
62
60
  tooltipMessage: '',
63
61
  isClicked: false
@@ -104,30 +102,38 @@ var HeadingAnchor = /*#__PURE__*/function (_React$PureComponent) {
104
102
  };
105
103
  }());
106
104
  (0, _defineProperty2.default)(_this, "resetMessage", function () {
107
- _this.setTooltipState(_messages.headingAnchorLinkMessages.copyHeadingLinkToClipboard);
105
+ var tooltip = (0, _expValEquals.expValEquals)('platform_editor_copy_link_a11y_inconsistency_fix', 'isEnabled', true) ? _messages.headingAnchorLinkMessages.copyLinkToClipboard : _messages.headingAnchorLinkMessages.copyHeadingLinkToClipboard;
106
+ _this.setTooltipState(tooltip);
108
107
  });
109
108
  (0, _defineProperty2.default)(_this, "renderAnchorButton", function () {
110
109
  var _this$props = _this.props,
111
110
  _this$props$hideFromS = _this$props.hideFromScreenReader,
112
111
  hideFromScreenReader = _this$props$hideFromS === void 0 ? false : _this$props$hideFromS,
113
112
  headingId = _this$props.headingId;
113
+ var labelledBy = (0, _expValEquals.expValEquals)('platform_editor_copy_link_a11y_inconsistency_fix', 'isEnabled', true) ? _this.props.intl.formatMessage(_messages.headingAnchorLinkMessages.copyHeadingLinkLabelledBy, {
114
+ copyLink: _this.copyLinkId,
115
+ heading: headingId
116
+ }) : headingId;
117
+ var tabIndex = (0, _expValEquals.expValEquals)('platform_editor_copy_link_a11y_inconsistency_fix', 'isEnabled', true) ? 0 : hideFromScreenReader ? undefined : -1;
114
118
  return (0, _react2.jsx)("button", {
115
119
  "data-testid": "anchor-button",
120
+ id: _this.copyLinkId,
116
121
  css: copyAnchorButtonStyles
117
122
  // eslint-disable-next-line @atlassian/a11y/mouse-events-have-key-events
118
123
  ,
119
124
  onMouseLeave: _this.resetMessage,
120
125
  onClick: _this.copyToClipboard,
121
126
  "aria-hidden": hideFromScreenReader,
122
- tabIndex: hideFromScreenReader ? undefined : -1,
127
+ tabIndex: tabIndex,
123
128
  "aria-label": hideFromScreenReader ? undefined : _this.state.tooltipMessage,
124
- "aria-labelledby": hideFromScreenReader ? undefined : headingId,
129
+ "aria-labelledby": hideFromScreenReader ? undefined : labelledBy,
125
130
  type: "button"
126
131
  }, (0, _react2.jsx)(_link.default, {
127
132
  label: _this.getCopyAriaLabel(),
128
133
  color: _this.state.isClicked ? "var(--ds-icon-selected, #1868DB)" : "var(--ds-icon-subtle, #505258)"
129
134
  }));
130
135
  });
136
+ _this.copyLinkId = (0, _expValEquals.expValEquals)('platform_editor_copy_link_a11y_inconsistency_fix', 'isEnabled', true) ? crypto.randomUUID() : undefined;
131
137
  return _this;
132
138
  }
133
139
  (0, _inherits2.default)(HeadingAnchor, _React$PureComponent);
@@ -6,12 +6,23 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.default = void 0;
8
8
  var _react = _interopRequireDefault(require("react"));
9
- var _visuallyHidden = _interopRequireDefault(require("@atlaskit/visually-hidden"));
10
9
  var _interactionMetrics = require("@atlaskit/react-ufo/interaction-metrics");
11
10
  var _analytics = require("@atlaskit/editor-common/analytics");
11
+ var _visuallyHidden = _interopRequireDefault(require("@atlaskit/visually-hidden"));
12
+ var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
12
13
  var _analyticsContext = _interopRequireDefault(require("../../analytics/analyticsContext"));
13
14
  var _clipboard = require("../utils/clipboard");
14
15
  var _headingAnchor = _interopRequireDefault(require("./heading-anchor"));
16
+ var _react2 = require("@emotion/react");
17
+ /**
18
+ * @jsxRuntime classic
19
+ * @jsx jsx
20
+ * @jsxFrag
21
+ */
22
+
23
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
24
+
25
+ var RENDERER_HEADING_WRAPPER = 'renderer-heading-wrapper';
15
26
  var getCurrentUrlWithHash = function getCurrentUrlWithHash() {
16
27
  var hash = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
17
28
  var url = new URL(window.location.href);
@@ -27,14 +38,21 @@ function hasRightAlignmentMark(marks) {
27
38
  return mark.type.name === 'alignment' && mark.attrs.align === 'end';
28
39
  });
29
40
  }
41
+ var wrapperStyles = (0, _react2.css)({
42
+ // Important: do NOT use flex here.
43
+ // With flex + baseline alignment, the anchor aligns to the *first line* of a multi-line heading,
44
+ // which visually places it at the top-right. We want the anchor to sit immediately after the
45
+ // last character of the heading (i.e. after the final wrapped line), so we use normal inline flow.
46
+ display: 'block'
47
+ });
30
48
  function WrappedHeadingAnchor(_ref) {
31
49
  var enableNestedHeaderLinks = _ref.enableNestedHeaderLinks,
32
50
  level = _ref.level,
33
51
  headingId = _ref.headingId,
34
52
  hideFromScreenReader = _ref.hideFromScreenReader;
35
- return /*#__PURE__*/_react.default.createElement(_analyticsContext.default.Consumer, null, function (_ref2) {
53
+ return (0, _react2.jsx)(_analyticsContext.default.Consumer, null, function (_ref2) {
36
54
  var fireAnalyticsEvent = _ref2.fireAnalyticsEvent;
37
- return /*#__PURE__*/_react.default.createElement(_headingAnchor.default, {
55
+ return (0, _react2.jsx)(_headingAnchor.default, {
38
56
  enableNestedHeaderLinks: enableNestedHeaderLinks,
39
57
  level: level,
40
58
  onCopyText: function onCopyText() {
@@ -51,7 +69,14 @@ function WrappedHeadingAnchor(_ref) {
51
69
  });
52
70
  });
53
71
  }
54
- function Heading(props) {
72
+ /**
73
+ * Old heading structure (before a11y fix):
74
+ * - headning anchor is rendered INSIDE the heading element
75
+ * - A duplicate anchor is rendered in VisuallyHidden for screen readers
76
+ * - The visible button has hideFromScreenReader={true}
77
+ *
78
+ */
79
+ function HeadingWithDuplicateAnchor(props) {
55
80
  var headingId = props.headingId,
56
81
  dataAttributes = props.dataAttributes,
57
82
  allowHeadingAnchorLinks = props.allowHeadingAnchorLinks,
@@ -74,28 +99,131 @@ function Heading(props) {
74
99
  mouseEntered.current = true;
75
100
  }
76
101
  };
77
- return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(HX, {
102
+ return (0, _react2.jsx)(_react.default.Fragment, null, (0, _react2.jsx)(HX, {
78
103
  id: headingIdToUse,
79
104
  "data-local-id": localId,
80
105
  "data-renderer-start-pos": dataAttributes['data-renderer-start-pos'],
81
106
  "data-as-inline": asInline,
82
107
  onMouseEnter: mouseEnterHandler
83
- }, /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, showAnchorLink && headingId && isRightAligned && /*#__PURE__*/_react.default.createElement(WrappedHeadingAnchor, {
108
+ }, (0, _react2.jsx)(_react.default.Fragment, null, showAnchorLink && headingId && isRightAligned && (0, _react2.jsx)(WrappedHeadingAnchor, {
84
109
  level: props.level,
85
110
  enableNestedHeaderLinks: enableNestedHeaderLinks,
86
111
  headingId: headingId,
87
112
  hideFromScreenReader: true
88
- }), props.children, showAnchorLink && headingId && !isRightAligned && /*#__PURE__*/_react.default.createElement(WrappedHeadingAnchor, {
113
+ }), props.children, showAnchorLink && headingId && !isRightAligned && (0, _react2.jsx)(WrappedHeadingAnchor, {
89
114
  level: props.level,
90
115
  enableNestedHeaderLinks: enableNestedHeaderLinks,
91
116
  headingId: headingId,
92
117
  hideFromScreenReader: true
93
- }))), /*#__PURE__*/_react.default.createElement(_visuallyHidden.default, {
118
+ }))), (0, _react2.jsx)(_visuallyHidden.default, {
94
119
  testId: "visually-hidden-heading-anchor"
95
- }, showAnchorLink && headingId && /*#__PURE__*/_react.default.createElement(WrappedHeadingAnchor, {
120
+ }, showAnchorLink && headingId && (0, _react2.jsx)(WrappedHeadingAnchor, {
96
121
  level: props.level,
97
122
  enableNestedHeaderLinks: enableNestedHeaderLinks,
98
123
  headingId: headingId
99
124
  })));
100
125
  }
126
+
127
+ /**
128
+ * New heading structure (a11y fix):
129
+ * - Heading anchor is rendered OUTSIDE the heading element in a .renderer-heading-wrapper div
130
+ * - Uses data-level attribute for CSS styling
131
+ * - Better accessibility: heading contains only text, button is a sibling
132
+ */
133
+ function HeadingWithWrapper(props) {
134
+ var headingId = props.headingId,
135
+ dataAttributes = props.dataAttributes,
136
+ allowHeadingAnchorLinks = props.allowHeadingAnchorLinks,
137
+ marks = props.marks,
138
+ invisible = props.invisible,
139
+ localId = props.localId,
140
+ asInline = props.asInline;
141
+ var HX = "h".concat(props.level);
142
+ var mouseEntered = _react.default.useRef(false);
143
+ var showAnchorLink = !!props.showAnchorLink;
144
+ var isRightAligned = hasRightAlignmentMark(marks);
145
+ var enableNestedHeaderLinks = allowHeadingAnchorLinks && allowHeadingAnchorLinks.allowNestedHeaderLinks;
146
+ var headingIdToUse = invisible ? undefined : headingId;
147
+ var mouseEnterHandler = function mouseEnterHandler() {
148
+ if (showAnchorLink && !mouseEntered.current) {
149
+ // Abort TTVC calculation when the mouse hovers over heading. Hovering over
150
+ // heading render heading anchor and inline comment buttons. These user-induced
151
+ // DOM changes are valid reasons to abort the TTVC calculation.
152
+ (0, _interactionMetrics.abortAll)('new_interaction');
153
+ mouseEntered.current = true;
154
+ }
155
+ };
156
+ return (0, _react2.jsx)("div", {
157
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
158
+ className: RENDERER_HEADING_WRAPPER,
159
+ "data-testid": RENDERER_HEADING_WRAPPER,
160
+ "data-level": props.level,
161
+ css: wrapperStyles
162
+ }, showAnchorLink && headingId && isRightAligned && (0, _react2.jsx)(WrappedHeadingAnchor, {
163
+ level: props.level,
164
+ enableNestedHeaderLinks: enableNestedHeaderLinks,
165
+ headingId: headingId,
166
+ hideFromScreenReader: false
167
+ }), (0, _react2.jsx)(HX, {
168
+ id: headingIdToUse,
169
+ "data-local-id": localId,
170
+ "data-renderer-start-pos": dataAttributes['data-renderer-start-pos'],
171
+ "data-as-inline": asInline,
172
+ onMouseEnter: mouseEnterHandler
173
+ }, props.children), showAnchorLink && headingId && !isRightAligned && (0, _react2.jsx)(WrappedHeadingAnchor, {
174
+ level: props.level,
175
+ enableNestedHeaderLinks: enableNestedHeaderLinks,
176
+ headingId: headingId,
177
+ hideFromScreenReader: false
178
+ }));
179
+ }
180
+
181
+ /**
182
+ * Gated Heading component:
183
+ * - When platform_editor_copy_link_a11y_inconsistency_fix experiment is enabled,
184
+ * returns HeadingWithWrapper (new a11y-improved structure)
185
+ * - Otherwise returns HeadingWithDuplicateAnchor (old structure)
186
+ */
187
+ function Heading(_ref3) {
188
+ var allowHeadingAnchorLinks = _ref3.allowHeadingAnchorLinks,
189
+ children = _ref3.children,
190
+ dataAttributes = _ref3.dataAttributes,
191
+ headingId = _ref3.headingId,
192
+ invisible = _ref3.invisible,
193
+ level = _ref3.level,
194
+ localId = _ref3.localId,
195
+ marks = _ref3.marks,
196
+ nodeType = _ref3.nodeType,
197
+ showAnchorLink = _ref3.showAnchorLink,
198
+ serializer = _ref3.serializer,
199
+ asInline = _ref3.asInline;
200
+ if ((0, _expValEquals.expValEquals)('platform_editor_copy_link_a11y_inconsistency_fix', 'isEnabled', true)) {
201
+ return (0, _react2.jsx)(HeadingWithWrapper, {
202
+ allowHeadingAnchorLinks: allowHeadingAnchorLinks,
203
+ dataAttributes: dataAttributes,
204
+ headingId: headingId,
205
+ invisible: invisible,
206
+ level: level,
207
+ localId: localId,
208
+ marks: marks,
209
+ nodeType: nodeType,
210
+ serializer: serializer,
211
+ showAnchorLink: showAnchorLink,
212
+ asInline: asInline
213
+ }, children);
214
+ }
215
+ return (0, _react2.jsx)(HeadingWithDuplicateAnchor, {
216
+ allowHeadingAnchorLinks: allowHeadingAnchorLinks,
217
+ dataAttributes: dataAttributes,
218
+ headingId: headingId,
219
+ invisible: invisible,
220
+ level: level,
221
+ localId: localId,
222
+ marks: marks,
223
+ nodeType: nodeType,
224
+ serializer: serializer,
225
+ showAnchorLink: showAnchorLink,
226
+ asInline: asInline
227
+ }, children);
228
+ }
101
229
  var _default = exports.default = Heading;