sfc.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. /**
  2. * Create a basic component with common options
  3. */
  4. import '../../locale';
  5. import { camelize } from '..';
  6. import { SlotsMixin } from '../../mixins/slots';
  7. import Vue, {
  8. VNode,
  9. VueConstructor,
  10. ComponentOptions,
  11. CreateElement,
  12. RenderContext
  13. } from 'vue';
  14. import { InjectOptions, PropsDefinition } from 'vue/types/options';
  15. export type ScopedSlot<Props = any> = (props?: Props) => VNode[] | VNode | undefined;
  16. export type DefaultSlots = {
  17. default?: ScopedSlot;
  18. };
  19. export type ScopedSlots = DefaultSlots & {
  20. [key: string]: ScopedSlot | undefined;
  21. };
  22. export type ModelOptions = {
  23. prop?: string;
  24. event?: string;
  25. };
  26. export interface VantComponentOptions extends ComponentOptions<Vue> {
  27. functional?: boolean;
  28. install?: (Vue: VueConstructor) => void;
  29. }
  30. export type DefaultProps = Record<string, any>;
  31. export type FunctionComponent<
  32. Props = DefaultProps,
  33. PropDefs = PropsDefinition<Props>
  34. > = {
  35. (
  36. h: CreateElement,
  37. props: Props,
  38. slots: ScopedSlots,
  39. context: RenderContext<Props>
  40. ): VNode | undefined;
  41. props?: PropDefs;
  42. model?: ModelOptions;
  43. inject?: InjectOptions;
  44. };
  45. export type TsxBaseProps<Slots> = {
  46. key: string | number;
  47. // hack for jsx prop spread
  48. props: any;
  49. class: any;
  50. style: string | object[] | object;
  51. scopedSlots: Slots;
  52. };
  53. export type TsxComponent<Props, Events, Slots> = (
  54. props: Partial<Props & Events & TsxBaseProps<Slots>>
  55. ) => VNode;
  56. const arrayProp = {
  57. type: Array,
  58. default: () => []
  59. };
  60. const numberProp = {
  61. type: Number,
  62. default: 0
  63. };
  64. function defaultProps(props: any) {
  65. Object.keys(props).forEach(key => {
  66. if (props[key] === Array) {
  67. props[key] = arrayProp;
  68. } else if (props[key] === Number) {
  69. props[key] = numberProp;
  70. }
  71. });
  72. }
  73. function install(this: ComponentOptions<Vue>, Vue: VueConstructor) {
  74. const { name } = this;
  75. Vue.component(name as string, this);
  76. Vue.component(camelize(`-${name}`), this);
  77. }
  78. // unify slots & scopedSlots
  79. export function unifySlots(context: RenderContext) {
  80. // use data.scopedSlots in lower Vue version
  81. const scopedSlots = context.scopedSlots || context.data.scopedSlots || {};
  82. const slots = context.slots();
  83. Object.keys(slots).forEach(key => {
  84. if (!scopedSlots[key]) {
  85. scopedSlots[key] = () => slots[key];
  86. }
  87. });
  88. return scopedSlots;
  89. }
  90. // should be removed after Vue 3
  91. function transformFunctionComponent(
  92. pure: FunctionComponent
  93. ): VantComponentOptions {
  94. return {
  95. functional: true,
  96. props: pure.props,
  97. model: pure.model,
  98. render: (h, context): any =>
  99. pure(h, context.props, unifySlots(context), context)
  100. };
  101. }
  102. export const useSFC = (name: string) => <Props = DefaultProps, Events = {}, Slots = {}>(
  103. sfc: VantComponentOptions | FunctionComponent
  104. ): TsxComponent<Props, Events, Slots> => {
  105. if (typeof sfc === 'function') {
  106. sfc = transformFunctionComponent(sfc);
  107. }
  108. if (!sfc.functional) {
  109. sfc.mixins = sfc.mixins || [];
  110. sfc.mixins.push(SlotsMixin);
  111. }
  112. if (sfc.props) {
  113. defaultProps(sfc.props);
  114. }
  115. sfc.name = name;
  116. sfc.install = install;
  117. return sfc as TsxComponent<Props, Events, Slots>;
  118. };