util.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import type { VNodeChild } from 'vue';
  2. import type { App } from 'vue';
  3. export function withInstall(...components: any[]) {
  4. components.forEach((comp) => {
  5. comp.install = (app: App) => {
  6. app.component(comp.displayName || comp.name, comp);
  7. };
  8. });
  9. }
  10. export function convertToUnit(
  11. str: string | number | null | undefined,
  12. unit = 'px'
  13. ): string | undefined {
  14. if (str == null || str === '') {
  15. return undefined;
  16. } else if (isNaN(+str!)) {
  17. return String(str);
  18. } else {
  19. return `${Number(str)}${unit}`;
  20. }
  21. }
  22. /**
  23. * Camelize a hyphen-delimited string.
  24. */
  25. const camelizeRE = /-(\w)/g;
  26. export const camelize = (str: string): string => {
  27. return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''));
  28. };
  29. export function wrapInArray<T>(v: T | T[] | null | undefined): T[] {
  30. return v != null ? (Array.isArray(v) ? v : [v]) : [];
  31. }
  32. const pattern = {
  33. styleList: /;(?![^(]*\))/g,
  34. styleProp: /:(.*)/,
  35. } as const;
  36. function parseStyle(style: string) {
  37. const styleMap: Recordable = {};
  38. for (const s of style.split(pattern.styleList)) {
  39. let [key, val] = s.split(pattern.styleProp);
  40. key = key.trim();
  41. if (!key) {
  42. continue;
  43. }
  44. // May be undefined if the `key: value` pair is incomplete.
  45. if (typeof val === 'string') {
  46. val = val.trim();
  47. }
  48. styleMap[camelize(key)] = val;
  49. }
  50. return styleMap;
  51. }
  52. /**
  53. * Intelligently merges data for createElement.
  54. * Merges arguments left to right, preferring the right argument.
  55. * Returns new VNodeData object.
  56. */
  57. export function mergeData(...vNodeData: VNodeChild[]): VNodeChild;
  58. export function mergeData(...args: any[]): VNodeChild {
  59. const mergeTarget: any = {};
  60. let i: number = args.length;
  61. let prop: string;
  62. // Allow for variadic argument length.
  63. while (i--) {
  64. // Iterate through the data properties and execute merge strategies
  65. // Object.keys eliminates need for hasOwnProperty call
  66. for (prop of Object.keys(args[i])) {
  67. switch (prop) {
  68. // Array merge strategy (array concatenation)
  69. case 'class':
  70. case 'directives':
  71. if (args[i][prop]) {
  72. mergeTarget[prop] = mergeClasses(mergeTarget[prop], args[i][prop]);
  73. }
  74. break;
  75. case 'style':
  76. if (args[i][prop]) {
  77. mergeTarget[prop] = mergeStyles(mergeTarget[prop], args[i][prop]);
  78. }
  79. break;
  80. // Space delimited string concatenation strategy
  81. case 'staticClass':
  82. if (!args[i][prop]) {
  83. break;
  84. }
  85. if (mergeTarget[prop] === undefined) {
  86. mergeTarget[prop] = '';
  87. }
  88. if (mergeTarget[prop]) {
  89. // Not an empty string, so concatenate
  90. mergeTarget[prop] += ' ';
  91. }
  92. mergeTarget[prop] += args[i][prop].trim();
  93. break;
  94. // Object, the properties of which to merge via array merge strategy (array concatenation).
  95. // Callback merge strategy merges callbacks to the beginning of the array,
  96. // so that the last defined callback will be invoked first.
  97. // This is done since to mimic how Object.assign merging
  98. // uses the last given value to assign.
  99. case 'on':
  100. case 'nativeOn':
  101. if (args[i][prop]) {
  102. mergeTarget[prop] = mergeListeners(mergeTarget[prop], args[i][prop]);
  103. }
  104. break;
  105. // Object merge strategy
  106. case 'attrs':
  107. case 'props':
  108. case 'domProps':
  109. case 'scopedSlots':
  110. case 'staticStyle':
  111. case 'hook':
  112. case 'transition':
  113. if (!args[i][prop]) {
  114. break;
  115. }
  116. if (!mergeTarget[prop]) {
  117. mergeTarget[prop] = {};
  118. }
  119. mergeTarget[prop] = { ...args[i][prop], ...mergeTarget[prop] };
  120. break;
  121. // Reassignment strategy (no merge)
  122. default:
  123. // slot, key, ref, tag, show, keepAlive
  124. if (!mergeTarget[prop]) {
  125. mergeTarget[prop] = args[i][prop];
  126. }
  127. }
  128. }
  129. }
  130. return mergeTarget;
  131. }
  132. export function mergeStyles(
  133. target: undefined | string | object[] | object,
  134. source: undefined | string | object[] | object
  135. ) {
  136. if (!target) return source;
  137. if (!source) return target;
  138. target = wrapInArray(typeof target === 'string' ? parseStyle(target) : target);
  139. return (target as object[]).concat(typeof source === 'string' ? parseStyle(source) : source);
  140. }
  141. export function mergeClasses(target: any, source: any) {
  142. if (!source) return target;
  143. if (!target) return source;
  144. return target ? wrapInArray(target).concat(source) : source;
  145. }
  146. export function mergeListeners(
  147. target: Indexable<Function | Function[]> | undefined,
  148. source: Indexable<Function | Function[]> | undefined
  149. ) {
  150. if (!target) return source;
  151. if (!source) return target;
  152. let event: string;
  153. for (event of Object.keys(source)) {
  154. // Concat function to array of functions if callback present.
  155. if (target[event]) {
  156. // Insert current iteration data in beginning of merged array.
  157. target[event] = wrapInArray(target[event]);
  158. (target[event] as Function[]).push(...wrapInArray(source[event]));
  159. } else {
  160. // Straight assign.
  161. target[event] = source[event];
  162. }
  163. }
  164. return target;
  165. }