util.tsx 5.0 KB

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