diff --git a/components/_util/copy-to-clipboard/index.ts b/components/_util/copy-to-clipboard/index.ts new file mode 100644 index 000000000..93b3815d5 --- /dev/null +++ b/components/_util/copy-to-clipboard/index.ts @@ -0,0 +1,121 @@ +import deselectCurrent from './toggle-selection'; + +interface Options { + debug?: boolean; + message?: string; + format?: string; // MIME type + onCopy?: (clipboardData: object) => void; +} + +const clipboardToIE11Formatting = { + 'text/plain': 'Text', + 'text/html': 'Url', + default: 'Text', +}; + +const defaultMessage = 'Copy to clipboard: #{key}, Enter'; + +function format(message: string) { + const copyKey = (/mac os x/i.test(navigator.userAgent) ? '⌘' : 'Ctrl') + '+C'; + return message.replace(/#{\s*key\s*}/g, copyKey); +} + +function copy(text: string, options?: Options): boolean { + let debug, + message, + reselectPrevious, + range, + selection, + mark, + success = false; + if (!options) { + options = {}; + } + debug = options.debug || false; + try { + reselectPrevious = deselectCurrent(); + + range = document.createRange(); + selection = document.getSelection(); + + mark = document.createElement('span'); + mark.textContent = text; + // reset user styles for span element + mark.style.all = 'unset'; + // prevents scrolling to the end of the page + mark.style.position = 'fixed'; + mark.style.top = 0; + mark.style.clip = 'rect(0, 0, 0, 0)'; + // used to preserve spaces and line breaks + mark.style.whiteSpace = 'pre'; + // do not inherit user-select (it may be `none`) + mark.style.webkitUserSelect = 'text'; + mark.style.MozUserSelect = 'text'; + mark.style.msUserSelect = 'text'; + mark.style.userSelect = 'text'; + mark.addEventListener('copy', function(e) { + e.stopPropagation(); + if (options.format) { + e.preventDefault(); + if (typeof e.clipboardData === 'undefined') { + // IE 11 + debug && console.warn('unable to use e.clipboardData'); + debug && console.warn('trying IE specific stuff'); + (window as any).clipboardData.clearData(); + const format = + clipboardToIE11Formatting[options.format] || clipboardToIE11Formatting['default']; + (window as any).clipboardData.setData(format, text); + } else { + // all other browsers + e.clipboardData.clearData(); + e.clipboardData.setData(options.format, text); + } + } + if (options.onCopy) { + e.preventDefault(); + options.onCopy(e.clipboardData); + } + }); + + document.body.appendChild(mark); + + range.selectNodeContents(mark); + selection.addRange(range); + + const successful = document.execCommand('copy'); + if (!successful) { + throw new Error('copy command was unsuccessful'); + } + success = true; + } catch (err) { + debug && console.error('unable to copy using execCommand: ', err); + debug && console.warn('trying IE specific stuff'); + try { + (window as any).clipboardData.setData(options.format || 'text', text); + options.onCopy && options.onCopy((window as any).clipboardData); + success = true; + } catch (err) { + debug && console.error('unable to copy using clipboardData: ', err); + debug && console.error('falling back to prompt'); + message = format('message' in options ? options.message : defaultMessage); + window.prompt(message, text); + } + } finally { + if (selection) { + if (typeof selection.removeRange == 'function') { + selection.removeRange(range); + } else { + selection.removeAllRanges(); + } + } + + if (mark) { + document.body.removeChild(mark); + } + reselectPrevious(); + } + + return success; +} + +export default copy; diff --git a/components/_util/copy-to-clipboard/toggle-selection.ts b/components/_util/copy-to-clipboard/toggle-selection.ts new file mode 100644 index 000000000..d6ece1612 --- /dev/null +++ b/components/_util/copy-to-clipboard/toggle-selection.ts @@ -0,0 +1,41 @@ +// copy from https://github.com/sudodoki/toggle-selection +// refactor to esm +const deselectCurrent = (): (() => void) => { + const selection = document.getSelection(); + if (!selection.rangeCount) { + return function() {}; + } + let active = document.activeElement as any; + + const ranges = []; + for (let i = 0; i < selection.rangeCount; i++) { + ranges.push(selection.getRangeAt(i)); + } + + switch ( + active.tagName.toUpperCase() // .toUpperCase handles XHTML + ) { + case 'INPUT': + case 'TEXTAREA': + active.blur(); + break; + + default: + active = null; + break; + } + + selection.removeAllRanges(); + return function() { + selection.type === 'Caret' && selection.removeAllRanges(); + + if (!selection.rangeCount) { + ranges.forEach(function(range) { + selection.addRange(range); + }); + } + + active && active.focus(); + }; +}; +export default deselectCurrent; diff --git a/components/typography/Base.tsx b/components/typography/Base.tsx index 6d7a9046e..3976c065c 100644 --- a/components/typography/Base.tsx +++ b/components/typography/Base.tsx @@ -10,7 +10,7 @@ import PropTypes from '../_util/vue-types'; import Typography, { TypographyProps } from './Typography'; import ResizeObserver from '../vc-resize-observer'; import Tooltip from '../tooltip'; -import copy from 'copy-to-clipboard'; +import copy from '../_util/copy-to-clipboard'; import { defaultConfigProvider } from '../config-provider'; import CheckOutlined from '@ant-design/icons-vue/CheckOutlined'; import CopyOutlined from '@ant-design/icons-vue/CopyOutlined'; @@ -40,7 +40,7 @@ export type BaseType = 'secondary' | 'success' | 'warning' | 'danger'; const isLineClampSupport = isStyleSupport('webkitLineClamp'); const isTextOverflowSupport = isStyleSupport('textOverflow'); -function toArray(value) { +function toArray(value: any) { let ret = value; if (value === undefined) { ret = []; @@ -67,13 +67,14 @@ interface EditConfig { autoSize?: boolean | AutoSizeType; } -interface EllipsisConfig { +export interface EllipsisConfig { rows?: number; expandable?: boolean; suffix?: string; symbol?: VNodeTypes; onExpand?: EventHandlerNonNull; onEllipsis?: (ellipsis: boolean) => void; + tooltip?: VNodeTypes; } export interface BlockProps extends TypographyProps { diff --git a/components/typography/Paragraph.tsx b/components/typography/Paragraph.tsx index 4f9992686..b5f254b76 100644 --- a/components/typography/Paragraph.tsx +++ b/components/typography/Paragraph.tsx @@ -1,15 +1,17 @@ import { FunctionalComponent } from 'vue'; import Base, { BlockProps } from './Base'; -const Paragraph: FunctionalComponent = (props, { slots }) => { +const Paragraph: FunctionalComponent = (props, { slots, attrs }) => { const paragraphProps = { ...props, component: 'div', + ...attrs, }; return {slots.default?.()}; }; Paragraph.displayName = 'ATypographyParagraph'; +Paragraph.inheritAttrs = false; export default Paragraph; diff --git a/components/typography/Text.tsx b/components/typography/Text.tsx index 2cc2c5663..d714c52f2 100644 --- a/components/typography/Text.tsx +++ b/components/typography/Text.tsx @@ -1,27 +1,35 @@ import { FunctionalComponent } from 'vue'; +import omit from 'omit.js'; import warning from '../_util/warning'; -import Base, { BlockProps } from './Base'; +import Base, { BlockProps, EllipsisConfig } from './Base'; export interface TextProps extends BlockProps { - ellipsis: boolean; + ellipsis?: boolean | Omit; } -const Text: FunctionalComponent = (props, { slots }) => { +const Text: FunctionalComponent = (props, { slots, attrs }) => { const { ellipsis } = props; warning( - typeof ellipsis !== 'object', + typeof ellipsis !== 'object' || + !ellipsis || + (!('expandable' in ellipsis) && !('rows' in ellipsis)), 'Typography.Text', - '`ellipsis` is only support boolean value.', + '`ellipsis` do not support `expandable` or `rows` props.', ); const textProps = { ...props, - ellipsis: !!ellipsis, + ellipsis: + ellipsis && typeof ellipsis === 'object' + ? omit(ellipsis as any, ['expandable', 'rows']) + : ellipsis, component: 'span', + ...attrs, }; return {slots.default?.()}; }; Text.displayName = 'ATypographyText'; +Text.inheritAttrs = false; export default Text; diff --git a/components/typography/Title.tsx b/components/typography/Title.tsx index d90bb13f9..c2f9d9db6 100644 --- a/components/typography/Title.tsx +++ b/components/typography/Title.tsx @@ -7,7 +7,7 @@ const TITLE_ELE_LIST = tupleNum(1, 2, 3, 4, 5); export type TitleProps = Omit; -const Title: FunctionalComponent = (props, { slots }) => { +const Title: FunctionalComponent = (props, { slots, attrs }) => { const { level = 1, ...restProps } = props; let component: string; if (TITLE_ELE_LIST.indexOf(level) !== -1) { @@ -20,11 +20,13 @@ const Title: FunctionalComponent = (props, { slots }) => { const titleProps = { ...restProps, component, + attrs, }; return {slots.default?.()}; }; Title.displayName = 'ATypographyTitle'; +Title.inheritAttrs = false; export default Title; diff --git a/package.json b/package.json index 585d6968e..51d77eca9 100644 --- a/package.json +++ b/package.json @@ -210,7 +210,6 @@ "@simonwep/pickr": "~1.8.0", "array-tree-filter": "^2.1.0", "async-validator": "^3.3.0", - "copy-to-clipboard": "^3.3.1", "dom-align": "^1.10.4", "dom-scroll-into-view": "^2.0.0", "is-mobile": "^2.2.1",