@atlaskit/renderer
Version:
Renderer component
233 lines (228 loc) • 9.72 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireDefault(require("react"));
var _interactionMetrics = require("@atlaskit/react-ufo/interaction-metrics");
var _analytics = require("@atlaskit/editor-common/analytics");
var _visuallyHidden = _interopRequireDefault(require("@atlaskit/visually-hidden"));
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
var _analyticsContext = _interopRequireDefault(require("../../analytics/analyticsContext"));
var _clipboard = require("../utils/clipboard");
var _headingAnchor = _interopRequireDefault(require("./heading-anchor"));
var _react2 = require("@emotion/react");
/**
* @jsxRuntime classic
* @jsx jsx
* @jsxFrag
*/
// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
var RENDERER_HEADING_WRAPPER = 'renderer-heading-wrapper';
var getCurrentUrlWithHash = function getCurrentUrlWithHash() {
var hash = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var url = new URL(window.location.href);
url.search = ''; // clear any query params so that the page will correctly scroll to the anchor
url.hash = encodeURIComponent(hash);
return url.href;
};
function hasRightAlignmentMark(marks) {
if (!marks || !marks.length) {
return false;
}
return marks.some(function (mark) {
return mark.type.name === 'alignment' && mark.attrs.align === 'end';
});
}
var wrapperStyles = (0, _react2.css)({
// Important: do NOT use flex here.
// With flex + baseline alignment, the anchor aligns to the *first line* of a multi-line heading,
// which visually places it at the top-right. We want the anchor to sit immediately after the
// last character of the heading (i.e. after the final wrapped line), so we use normal inline flow.
display: 'block'
});
function WrappedHeadingAnchor(_ref) {
var enableNestedHeaderLinks = _ref.enableNestedHeaderLinks,
level = _ref.level,
headingId = _ref.headingId,
hideFromScreenReader = _ref.hideFromScreenReader;
return (0, _react2.jsx)(_analyticsContext.default.Consumer, null, function (_ref2) {
var fireAnalyticsEvent = _ref2.fireAnalyticsEvent;
return (0, _react2.jsx)(_headingAnchor.default, {
enableNestedHeaderLinks: enableNestedHeaderLinks,
level: level
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onCopyText: function onCopyText() {
fireAnalyticsEvent({
action: _analytics.ACTION.CLICKED,
actionSubject: _analytics.ACTION_SUBJECT.BUTTON,
actionSubjectId: _analytics.ACTION_SUBJECT_ID.HEADING_ANCHOR_LINK,
eventType: _analytics.EVENT_TYPE.UI
});
return (0, _clipboard.copyTextToClipboard)(getCurrentUrlWithHash(headingId));
},
hideFromScreenReader: hideFromScreenReader,
headingId: headingId
});
});
}
/**
* Old heading structure (before a11y fix):
* - headning anchor is rendered INSIDE the heading element
* - A duplicate anchor is rendered in VisuallyHidden for screen readers
* - The visible button has hideFromScreenReader={true}
*
*/
function HeadingWithDuplicateAnchor(props) {
var headingId = props.headingId,
dataAttributes = props.dataAttributes,
allowHeadingAnchorLinks = props.allowHeadingAnchorLinks,
marks = props.marks,
invisible = props.invisible,
localId = props.localId,
asInline = props.asInline;
var HX = "h".concat(props.level);
var mouseEntered = _react.default.useRef(false);
var showAnchorLink = !!props.showAnchorLink;
var isRightAligned = hasRightAlignmentMark(marks);
var enableNestedHeaderLinks = allowHeadingAnchorLinks && allowHeadingAnchorLinks.allowNestedHeaderLinks;
var headingIdToUse = invisible ? undefined : headingId;
var mouseEnterHandler = function mouseEnterHandler() {
if (showAnchorLink && !mouseEntered.current) {
// Abort TTVC calculation when the mouse hovers over heading. Hovering over
// heading render heading anchor and inline comment buttons. These user-induced
// DOM changes are valid reasons to abort the TTVC calculation.
(0, _interactionMetrics.abortAll)('new_interaction');
mouseEntered.current = true;
}
};
return (0, _react2.jsx)(_react.default.Fragment, null, (0, _react2.jsx)(HX, {
id: headingIdToUse,
"data-local-id": localId,
"data-renderer-start-pos": dataAttributes['data-renderer-start-pos'],
"data-as-inline": asInline,
onMouseEnter: mouseEnterHandler,
tabIndex: (0, _expValEquals.expValEquals)('confluence_toc_nav_a11y', 'isEnabled', true) ? -1 : undefined
}, (0, _react2.jsx)(_react.default.Fragment, null, showAnchorLink && headingId && isRightAligned && (0, _react2.jsx)(WrappedHeadingAnchor, {
level: props.level,
enableNestedHeaderLinks: enableNestedHeaderLinks,
headingId: headingId,
hideFromScreenReader: true
}), props.children, showAnchorLink && headingId && !isRightAligned && (0, _react2.jsx)(WrappedHeadingAnchor, {
level: props.level,
enableNestedHeaderLinks: enableNestedHeaderLinks,
headingId: headingId,
hideFromScreenReader: true
}))), (0, _react2.jsx)(_visuallyHidden.default, {
testId: "visually-hidden-heading-anchor"
}, showAnchorLink && headingId && (0, _react2.jsx)(WrappedHeadingAnchor, {
level: props.level,
enableNestedHeaderLinks: enableNestedHeaderLinks,
headingId: headingId
})));
}
/**
* New heading structure (a11y fix):
* - Heading anchor is rendered OUTSIDE the heading element in a .renderer-heading-wrapper div
* - Uses data-level attribute for CSS styling
* - Better accessibility: heading contains only text, button is a sibling
*/
function HeadingWithWrapper(props) {
var headingId = props.headingId,
dataAttributes = props.dataAttributes,
allowHeadingAnchorLinks = props.allowHeadingAnchorLinks,
marks = props.marks,
invisible = props.invisible,
localId = props.localId,
asInline = props.asInline;
var HX = "h".concat(props.level);
var mouseEntered = _react.default.useRef(false);
var showAnchorLink = !!props.showAnchorLink;
var isRightAligned = hasRightAlignmentMark(marks);
var enableNestedHeaderLinks = allowHeadingAnchorLinks && allowHeadingAnchorLinks.allowNestedHeaderLinks;
var headingIdToUse = invisible ? undefined : headingId;
var mouseEnterHandler = function mouseEnterHandler() {
if (showAnchorLink && !mouseEntered.current) {
// Abort TTVC calculation when the mouse hovers over heading. Hovering over
// heading render heading anchor and inline comment buttons. These user-induced
// DOM changes are valid reasons to abort the TTVC calculation.
(0, _interactionMetrics.abortAll)('new_interaction');
mouseEntered.current = true;
}
};
return (0, _react2.jsx)("div", {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
className: RENDERER_HEADING_WRAPPER,
"data-testid": RENDERER_HEADING_WRAPPER,
"data-level": props.level,
css: wrapperStyles
}, showAnchorLink && headingId && isRightAligned && (0, _react2.jsx)(WrappedHeadingAnchor, {
level: props.level,
enableNestedHeaderLinks: enableNestedHeaderLinks,
headingId: headingId,
hideFromScreenReader: false
}), (0, _react2.jsx)(HX, {
id: headingIdToUse,
"data-local-id": localId,
"data-renderer-start-pos": dataAttributes['data-renderer-start-pos'],
"data-as-inline": asInline,
onMouseEnter: mouseEnterHandler,
tabIndex: (0, _expValEquals.expValEquals)('confluence_toc_nav_a11y', 'isEnabled', true) ? -1 : undefined
}, props.children), showAnchorLink && headingId && !isRightAligned && (0, _react2.jsx)(WrappedHeadingAnchor, {
level: props.level,
enableNestedHeaderLinks: enableNestedHeaderLinks,
headingId: headingId,
hideFromScreenReader: false
}));
}
/**
* Gated Heading component:
* - When platform_editor_copy_link_a11y_inconsistency_fix experiment is enabled,
* returns HeadingWithWrapper (new a11y-improved structure)
* - Otherwise returns HeadingWithDuplicateAnchor (old structure)
*/
function Heading(_ref3) {
var allowHeadingAnchorLinks = _ref3.allowHeadingAnchorLinks,
children = _ref3.children,
dataAttributes = _ref3.dataAttributes,
headingId = _ref3.headingId,
invisible = _ref3.invisible,
level = _ref3.level,
localId = _ref3.localId,
marks = _ref3.marks,
nodeType = _ref3.nodeType,
showAnchorLink = _ref3.showAnchorLink,
serializer = _ref3.serializer,
asInline = _ref3.asInline;
if ((0, _expValEquals.expValEquals)('platform_editor_copy_link_a11y_inconsistency_fix', 'isEnabled', true)) {
return (0, _react2.jsx)(HeadingWithWrapper, {
allowHeadingAnchorLinks: allowHeadingAnchorLinks,
dataAttributes: dataAttributes,
headingId: headingId,
invisible: invisible,
level: level,
localId: localId,
marks: marks,
nodeType: nodeType,
serializer: serializer,
showAnchorLink: showAnchorLink,
asInline: asInline
}, children);
}
return (0, _react2.jsx)(HeadingWithDuplicateAnchor, {
allowHeadingAnchorLinks: allowHeadingAnchorLinks,
dataAttributes: dataAttributes,
headingId: headingId,
invisible: invisible,
level: level,
localId: localId,
marks: marks,
nodeType: nodeType,
serializer: serializer,
showAnchorLink: showAnchorLink,
asInline: asInline
}, children);
}
var _default = exports.default = Heading;