1
0

index.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import React from 'react';
  2. import { cloneDeepWith, set, get } from 'lodash';
  3. import warning from '@douyinfe/semi-foundation/utils/warning';
  4. import { isHTMLElement } from '@douyinfe/semi-foundation/utils/dom';
  5. import semiGlobal from "./semi-global";
  6. /**
  7. * stop propagation
  8. *
  9. * @param {React.MouseEvent<HTMLElement>} e React mouse event object
  10. * @param {boolean} noImmediate Skip stopping immediate propagation
  11. */
  12. export function stopPropagation(e: React.MouseEvent | React.FocusEvent<HTMLElement>, noImmediate?: boolean) {
  13. if (e && typeof e.stopPropagation === 'function') {
  14. e.stopPropagation();
  15. }
  16. if (!noImmediate && e.nativeEvent && typeof e.nativeEvent.stopImmediatePropagation === 'function') {
  17. e.nativeEvent.stopImmediatePropagation();
  18. }
  19. }
  20. /**
  21. * use in Table, Form, Navigation
  22. *
  23. * skip clone function and react element
  24. */
  25. export function cloneDeep<T>(value: T): T;
  26. export function cloneDeep<T>(value: T, customizer: (value: any) => any): any;
  27. export function cloneDeep(value: any, customizer?: (value: any) => any) {
  28. return cloneDeepWith(value, v => {
  29. if (typeof customizer === 'function') {
  30. return customizer(v);
  31. }
  32. if (typeof v === 'function' || React.isValidElement(v)) {
  33. return v;
  34. }
  35. if (Object.prototype.toString.call(v) === '[object Error]') {
  36. return v;
  37. }
  38. // it is tricky
  39. // when array length beyond max length, array.length will be 0
  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. try {
  49. warning(
  50. get(process, 'env.NODE_ENV') !== 'production',
  51. `[Semi] You may use an out-of-bounds array. In some cases, your program may not behave as expected.
  52. The maximum length of an array is 4294967295.
  53. Please check whether the array subscript in your data exceeds the maximum value of the JS array subscript`
  54. );
  55. } catch (e) {
  56. }
  57. return newArray;
  58. } else {
  59. return undefined;
  60. }
  61. }
  62. return undefined;
  63. });
  64. }
  65. export interface RegisterMediaQueryOption {
  66. match?: (e: MediaQueryList | MediaQueryListEvent) => void;
  67. unmatch?: (e: MediaQueryList | MediaQueryListEvent) => void;
  68. callInInit?: boolean
  69. }
  70. /**
  71. * register matchFn and unMatchFn callback while media query
  72. * @param {string} media media string
  73. * @param {object} param param object
  74. * @returns function
  75. */
  76. export const registerMediaQuery = (media: string, { match, unmatch, callInInit = true }: RegisterMediaQueryOption): () => void => {
  77. if (typeof window !== 'undefined') {
  78. const mediaQueryList = window.matchMedia(media);
  79. function handlerMediaChange(e: MediaQueryList | MediaQueryListEvent): void {
  80. if (e.matches) {
  81. match && match(e);
  82. } else {
  83. unmatch && unmatch(e);
  84. }
  85. }
  86. callInInit && handlerMediaChange(mediaQueryList);
  87. if (Object.prototype.hasOwnProperty.call(mediaQueryList, 'addEventListener')) {
  88. mediaQueryList.addEventListener('change', handlerMediaChange);
  89. return (): void => mediaQueryList.removeEventListener('change', handlerMediaChange);
  90. }
  91. mediaQueryList.addListener(handlerMediaChange);
  92. return (): void => mediaQueryList.removeListener(handlerMediaChange);
  93. }
  94. return () => undefined;
  95. };
  96. /**
  97. * Determine whether the incoming element is a built-in icon
  98. * @param icon 元素
  99. * @returns boolean
  100. */
  101. export const isSemiIcon = (icon: any): boolean => React.isValidElement(icon) && get(icon.type, 'elementType') === 'Icon';
  102. export function getActiveElement(): HTMLElement | null {
  103. return document ? document.activeElement as HTMLElement : null;
  104. }
  105. export function isNodeContainsFocus(node: HTMLElement) {
  106. const activeElement = getActiveElement();
  107. return activeElement === node || node.contains(activeElement);
  108. }
  109. export function getFocusableElements(node: HTMLElement) {
  110. if (!isHTMLElement(node)) {
  111. return [];
  112. }
  113. const focusableSelectorsList = [
  114. "input:not([disabled]):not([tabindex='-1'])",
  115. "textarea:not([disabled]):not([tabindex='-1'])",
  116. "button:not([disabled]):not([tabindex='-1'])",
  117. "a[href]:not([tabindex='-1'])",
  118. "select:not([disabled]):not([tabindex='-1'])",
  119. "area[href]:not([tabindex='-1'])",
  120. "iframe:not([tabindex='-1'])",
  121. "object:not([tabindex='-1'])",
  122. "*[tabindex]:not([tabindex='-1'])",
  123. "*[contenteditable]:not([tabindex='-1'])",
  124. ];
  125. const focusableSelectorsStr = focusableSelectorsList.join(',');
  126. // we are not filtered elements which are invisible
  127. const focusableElements = Array.from(node.querySelectorAll<HTMLElement>(focusableSelectorsStr));
  128. return focusableElements;
  129. }
  130. export async function runAfterTicks(func: (...args: any) => any, numberOfTicks: number) {
  131. if (numberOfTicks===0) {
  132. await func();
  133. return;
  134. } else {
  135. await new Promise<void>(resolve=>{
  136. setTimeout(async ()=>{
  137. await runAfterTicks(func, numberOfTicks-1);
  138. resolve();
  139. }, 0);
  140. });
  141. return;
  142. }
  143. }
  144. export function getScrollbarWidth() {
  145. if (globalThis && Object.prototype.toString.call(globalThis) === '[object Window]') {
  146. return window.innerWidth - document.documentElement.clientWidth;
  147. }
  148. return 0;
  149. }
  150. export function getDefaultPropsFromGlobalConfig(componentName: string, semiDefaultProps: any = {}) {
  151. const getFromGlobalConfig = ()=> semiGlobal?.config?.overrideDefaultProps?.[componentName] || {};
  152. return new Proxy({
  153. ...semiDefaultProps,
  154. }, {
  155. get(target, key, receiver) {
  156. const defaultPropsFromGlobal = getFromGlobalConfig();
  157. if (key in defaultPropsFromGlobal) {
  158. return defaultPropsFromGlobal[key];
  159. }
  160. return Reflect.get(target, key, receiver);
  161. },
  162. set(target, key, value, receiver) {
  163. return Reflect.set(target, key, value, receiver);
  164. },
  165. ownKeys() {
  166. const defaultPropsFromGlobal = getFromGlobalConfig();
  167. return Array.from(new Set([...Reflect.ownKeys(semiDefaultProps), ...Object.keys(defaultPropsFromGlobal)]));
  168. },
  169. getOwnPropertyDescriptor(target, key) {
  170. const defaultPropsFromGlobal = getFromGlobalConfig();
  171. if (key in defaultPropsFromGlobal) {
  172. return Reflect.getOwnPropertyDescriptor(defaultPropsFromGlobal, key);
  173. } else {
  174. return Reflect.getOwnPropertyDescriptor(target, key);
  175. }
  176. }
  177. });
  178. }