@elastic/eui
Version:
Elastic UI Component Library
103 lines (95 loc) • 4.43 kB
JavaScript
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { useEffect } from 'react';
/**
* Clipboard text cleaning logic
*/
// Special visually hidden unicode characters that we use to manually clean content
// and force our own newlines/horizontal tabs
import { jsx as ___EmotionJSX } from "@emotion/react";
export var CHARS = {
NEWLINE: '↵',
TAB: '↦',
// Use multiple characters to reduce the chances of consumers also using these characters
TABULAR_CONTENT_BOUND: '𐘂𐘂',
NO_COPY_BOUND: '✄𐘗'
};
// This regex finds all content between two bounds
export var noCopyBoundsRegex = new RegExp("".concat(CHARS.NO_COPY_BOUND, "[^").concat(CHARS.NO_COPY_BOUND, "]*").concat(CHARS.NO_COPY_BOUND), 'gs');
var hasCharsToReplace = function hasCharsToReplace(text) {
for (var _i = 0, _Object$values = Object.values(CHARS); _i < _Object$values.length; _i++) {
var char = _Object$values[_i];
if (text.indexOf(char) >= 0) return true;
}
return false;
};
// Strip all existing newlines and replace our special hidden characters
// with the desired spacing needed to paste cleanly into a spreadsheet
export var onTabularCopy = function onTabularCopy(event) {
var _window$getSelection;
if (!event.clipboardData) return;
var selectedText = (_window$getSelection = window.getSelection()) === null || _window$getSelection === void 0 ? void 0 : _window$getSelection.toString();
if (!selectedText || !hasCharsToReplace(selectedText)) return;
var amendedText = selectedText.split(CHARS.TABULAR_CONTENT_BOUND).map(function (text) {
return hasCharsToReplace(text) ? text.replace(/\r?\n/g, '') // remove all other newlines generated by content or block display
.replaceAll(CHARS.NEWLINE, '\n') // insert newline for each table/grid row
.replace(/\t/g, '') // remove tabs generated by content or automatically by <td> elements
.replaceAll(CHARS.TAB, "\t") // insert horizontal tab for each table/grid cell
.replace(noCopyBoundsRegex, '') // remove text that should not be copied (e.g. screen reader instructions)
: text;
}).join('');
event.clipboardData.setData('text/plain', amendedText);
event.preventDefault();
};
/**
* JSX utils for rendering the hidden marker characters
*/
var VisuallyHide = function VisuallyHide(_ref) {
var children = _ref.children,
_ref$type = _ref.type,
type = _ref$type === void 0 ? 'true' : _ref$type;
return (
// Hides the characters to both sighted user and screen readers
// Sadly, we can't use `hidden` as that hides the chars from the clipboard as well
___EmotionJSX("span", {
className: "euiScreenReaderOnly",
"aria-hidden": true,
"data-tabular-copy-marker": type
}, children)
);
};
export var tabularCopyMarkers = {
hiddenTab: ___EmotionJSX(VisuallyHide, {
type: "tab"
}, CHARS.TAB),
hiddenNewline: ___EmotionJSX(VisuallyHide, {
type: "newline"
}, CHARS.NEWLINE),
hiddenWrapperBoundary: ___EmotionJSX(VisuallyHide, {
type: "boundary"
}, CHARS.TABULAR_CONTENT_BOUND),
hiddenNoCopyBoundary: ___EmotionJSX(VisuallyHide, {
type: "no-copy"
}, CHARS.NO_COPY_BOUND)
};
/**
* Wrapper setup around table/grid tabular content we want to override/clean up on copy
*/
export var OverrideCopiedTabularContent = function OverrideCopiedTabularContent(_ref2) {
var children = _ref2.children;
useEffect(function () {
// Chrome and webkit browsers work perfectly when passing `onTabularCopy` to a React
// `onCopy` prop, but sadly Firefox does not if copying more than just the table/grid
// (e.g. Ctrl+A). So we have to set up a global window event listener
window.document.addEventListener('copy', onTabularCopy);
// Note: Since onCopy is static, we don't have to worry about duplicate
// event listeners - it's automatically handled by the browser. See:
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Multiple_identical_event_listeners
}, []);
return ___EmotionJSX(React.Fragment, null, tabularCopyMarkers.hiddenWrapperBoundary, children, tabularCopyMarkers.hiddenWrapperBoundary);
};