index.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /* eslint-disable max-len */
  2. /* argus-disable unPkgSensitiveInfo */
  3. import React from 'react';
  4. import { cloneDeepWith, set, get } from 'lodash';
  5. import warning from '@douyinfe/semi-foundation/utils/warning';
  6. import { findAll } from '@douyinfe/semi-foundation/utils/getHighlight';
  7. import { isHTMLElement } from '@douyinfe/semi-foundation/utils/dom';
  8. /**
  9. * stop propagation
  10. *
  11. * @param {React.MouseEvent<HTMLElement>} e React mouse event object
  12. * @param {boolean} noImmediate Skip stopping immediate propagation
  13. */
  14. export function stopPropagation(e: React.MouseEvent, noImmediate?: boolean) {
  15. if (e && typeof e.stopPropagation === 'function') {
  16. e.stopPropagation();
  17. }
  18. if (!noImmediate && e.nativeEvent && typeof e.nativeEvent.stopImmediatePropagation === 'function') {
  19. e.nativeEvent.stopImmediatePropagation();
  20. }
  21. }
  22. /**
  23. *
  24. * @param {any} value
  25. * @param {Function} customizer
  26. * @returns {any}
  27. * use in Table, Form, Navigation
  28. */
  29. export function cloneDeep(value: any, customizer?: (value: any) => void) {
  30. return cloneDeepWith(value, v => {
  31. if (typeof customizer === 'function') {
  32. return customizer(v);
  33. }
  34. if (typeof v === 'function' || React.isValidElement(v)) {
  35. return v;
  36. }
  37. if (Object.prototype.toString.call(v) === '[object Error]') {
  38. return v;
  39. }
  40. if (Array.isArray(v) && v.length === 0) {
  41. const keys: string[] = Object.keys(v);
  42. if (keys.length) {
  43. const newArray: any[] = [];
  44. keys.forEach(key => {
  45. set(newArray, key, v[key]);
  46. });
  47. // internal-issues:887
  48. warning(
  49. get(process, 'env.NODE_ENV') !== 'production',
  50. `[Semi] You may use an out-of-bounds array. In some cases, your program may not behave as expected.
  51. The maximum length of an array is 4294967295.
  52. Please check whether the array subscript in your data exceeds the maximum value of the JS array subscript`
  53. );
  54. return newArray;
  55. } else {
  56. return undefined;
  57. }
  58. }
  59. return undefined;
  60. });
  61. }
  62. /**
  63. * [getHighLightTextHTML description]
  64. *
  65. * @param {string} sourceString [source content text]
  66. * @param {Array<string>} searchWords [keywords to be highlighted]
  67. * @param {object} option
  68. * @param {true} option.highlightTag [The tag wrapped by the highlighted content, mark is used by default]
  69. * @param {true} option.highlightClassName
  70. * @param {true} option.highlightStyle
  71. * @param {boolean} option.caseSensitive
  72. *
  73. * @return {Array<object>}
  74. */
  75. export const getHighLightTextHTML = ({
  76. sourceString = '',
  77. searchWords = [],
  78. option = { autoEscape: true, caseSensitive: false }
  79. }: GetHighLightTextHTMLProps) => {
  80. const chunks: HighLightTextHTMLChunk[] = findAll({ sourceString, searchWords, ...option });
  81. const markEle = option.highlightTag || 'mark';
  82. const highlightClassName = option.highlightClassName || '';
  83. const highlightStyle = option.highlightStyle || {};
  84. return chunks.map((chunk: HighLightTextHTMLChunk, index: number) => {
  85. const { end, start, highlight } = chunk;
  86. const text = sourceString.substr(start, end - start);
  87. if (highlight) {
  88. return React.createElement(
  89. markEle,
  90. {
  91. style: highlightStyle,
  92. className: highlightClassName,
  93. key: text + index
  94. },
  95. text
  96. );
  97. } else {
  98. return text;
  99. }
  100. });
  101. };
  102. export interface RegisterMediaQueryOption {
  103. match?: (e: MediaQueryList | MediaQueryListEvent) => void;
  104. unmatch?: (e: MediaQueryList | MediaQueryListEvent) => void;
  105. callInInit?: boolean;
  106. }
  107. /**
  108. * register matchFn and unMatchFn callback while media query
  109. * @param {string} media media string
  110. * @param {object} param param object
  111. * @returns function
  112. */
  113. export const registerMediaQuery = (media: string, { match, unmatch, callInInit = true }: RegisterMediaQueryOption): () => void => {
  114. if (typeof window !== 'undefined') {
  115. const mediaQueryList = window.matchMedia(media);
  116. function handlerMediaChange(e: MediaQueryList | MediaQueryListEvent): void {
  117. if (e.matches) {
  118. match && match(e);
  119. } else {
  120. unmatch && unmatch(e);
  121. }
  122. }
  123. callInInit && handlerMediaChange(mediaQueryList);
  124. if (Object.prototype.hasOwnProperty.call(mediaQueryList, 'addEventListener')) {
  125. mediaQueryList.addEventListener('change', handlerMediaChange);
  126. return (): void => mediaQueryList.removeEventListener('change', handlerMediaChange);
  127. }
  128. mediaQueryList.addListener(handlerMediaChange);
  129. return (): void => mediaQueryList.removeListener(handlerMediaChange);
  130. }
  131. return () => undefined;
  132. };
  133. export interface GetHighLightTextHTMLProps {
  134. sourceString?: string;
  135. searchWords?: any[];
  136. option: HighLightTextHTMLOption;
  137. }
  138. export interface HighLightTextHTMLOption {
  139. highlightTag?: string;
  140. highlightClassName?: string;
  141. highlightStyle?: Record<string, any>;
  142. caseSensitive: boolean;
  143. autoEscape: boolean;
  144. }
  145. export interface HighLightTextHTMLChunk {
  146. start?: number;
  147. end?: number;
  148. highlight?: any;
  149. }
  150. /**
  151. * Determine whether the incoming element is a built-in icon
  152. * @param icon 元素
  153. * @returns boolean
  154. */
  155. export const isSemiIcon = (icon: any): boolean => React.isValidElement(icon) && get(icon.type, 'elementType') === 'Icon';
  156. export function getActiveElement(): HTMLElement | null {
  157. return document ? document.activeElement as HTMLElement : null;
  158. }
  159. export function isNodeContainsFocus(node: HTMLElement) {
  160. const activeElement = getActiveElement();
  161. return activeElement === node || node.contains(activeElement);
  162. }
  163. export function getFocusableElements(node: HTMLElement) {
  164. if (!isHTMLElement(node)) {
  165. return [];
  166. }
  167. const focusableSelectorsList = [
  168. "input:not([disabled]):not([tabindex='-1'])",
  169. "textarea:not([disabled]):not([tabindex='-1'])",
  170. "button:not([disabled]):not([tabindex='-1'])",
  171. "a[href]:not([tabindex='-1'])",
  172. "select:not([disabled]):not([tabindex='-1'])",
  173. "area[href]:not([tabindex='-1'])",
  174. "iframe:not([tabindex='-1'])",
  175. "object:not([tabindex='-1'])",
  176. "*[tabindex]:not([tabindex='-1'])",
  177. "*[contenteditable]:not([tabindex='-1'])",
  178. ];
  179. const focusableSelectorsStr = focusableSelectorsList.join(',');
  180. // we are not filtered elements which are invisible
  181. const focusableElements = Array.from(node.querySelectorAll<HTMLElement>(focusableSelectorsStr));
  182. return focusableElements;
  183. }