Browse Source

fix(grid): fix grid margin when gutter (#896)

* chore: add useInject and useProvide

* fix(grid): fix grid margin when gutter

* test(grid): run grid test
HaiWei Lian 4 years ago
parent
commit
0f08529dc6

+ 24 - 4
src/packages/__VUE/grid/__test__/__snapshots__/grid.spec.ts.snap

@@ -10,22 +10,42 @@ exports[`should render default slot correctly 1`] = `
 
 exports[`should render gutter correctly 1`] = `
 "<view class=\\"nut-grid\\" style=\\"padding-left: 20px;\\">
-  <view class=\\"nut-grid-item\\" style=\\"flex-basis: 25%; padding-right: 20px; margin-bottom: 20px;\\">
+  <view class=\\"nut-grid-item\\" style=\\"flex-basis: 25%; padding-right: 20px;\\">
     <view class=\\"nut-grid-item__content nut-grid-item__content--border nut-grid-item__content--surround nut-grid-item__content--center\\"><i class=\\"nutui-iconfont nut-icon nut-icon-\\" style=\\"font-size: 28px; width: 28px; height: 28px;\\" src=\\"\\"></i>
       <view class=\\"nut-grid-item__text\\"></view>
     </view>
   </view>
-  <view class=\\"nut-grid-item\\" style=\\"flex-basis: 25%; padding-right: 20px; margin-bottom: 20px;\\">
+  <view class=\\"nut-grid-item\\" style=\\"flex-basis: 25%; padding-right: 20px;\\">
     <view class=\\"nut-grid-item__content nut-grid-item__content--border nut-grid-item__content--surround nut-grid-item__content--center\\"><i class=\\"nutui-iconfont nut-icon nut-icon-\\" style=\\"font-size: 28px; width: 28px; height: 28px;\\" src=\\"\\"></i>
       <view class=\\"nut-grid-item__text\\"></view>
     </view>
   </view>
-  <view class=\\"nut-grid-item\\" style=\\"flex-basis: 25%; padding-right: 20px; margin-bottom: 20px;\\">
+  <view class=\\"nut-grid-item\\" style=\\"flex-basis: 25%; padding-right: 20px;\\">
     <view class=\\"nut-grid-item__content nut-grid-item__content--border nut-grid-item__content--surround nut-grid-item__content--center\\"><i class=\\"nutui-iconfont nut-icon nut-icon-\\" style=\\"font-size: 28px; width: 28px; height: 28px;\\" src=\\"\\"></i>
       <view class=\\"nut-grid-item__text\\"></view>
     </view>
   </view>
-  <view class=\\"nut-grid-item\\" style=\\"flex-basis: 25%; padding-right: 20px; margin-bottom: 20px;\\">
+  <view class=\\"nut-grid-item\\" style=\\"flex-basis: 25%; padding-right: 20px;\\">
+    <view class=\\"nut-grid-item__content nut-grid-item__content--border nut-grid-item__content--surround nut-grid-item__content--center\\"><i class=\\"nutui-iconfont nut-icon nut-icon-\\" style=\\"font-size: 28px; width: 28px; height: 28px;\\" src=\\"\\"></i>
+      <view class=\\"nut-grid-item__text\\"></view>
+    </view>
+  </view>
+  <view class=\\"nut-grid-item\\" style=\\"flex-basis: 25%; padding-right: 20px; margin-top: 20px;\\">
+    <view class=\\"nut-grid-item__content nut-grid-item__content--border nut-grid-item__content--surround nut-grid-item__content--center\\"><i class=\\"nutui-iconfont nut-icon nut-icon-\\" style=\\"font-size: 28px; width: 28px; height: 28px;\\" src=\\"\\"></i>
+      <view class=\\"nut-grid-item__text\\"></view>
+    </view>
+  </view>
+  <view class=\\"nut-grid-item\\" style=\\"flex-basis: 25%; padding-right: 20px; margin-top: 20px;\\">
+    <view class=\\"nut-grid-item__content nut-grid-item__content--border nut-grid-item__content--surround nut-grid-item__content--center\\"><i class=\\"nutui-iconfont nut-icon nut-icon-\\" style=\\"font-size: 28px; width: 28px; height: 28px;\\" src=\\"\\"></i>
+      <view class=\\"nut-grid-item__text\\"></view>
+    </view>
+  </view>
+  <view class=\\"nut-grid-item\\" style=\\"flex-basis: 25%; padding-right: 20px; margin-top: 20px;\\">
+    <view class=\\"nut-grid-item__content nut-grid-item__content--border nut-grid-item__content--surround nut-grid-item__content--center\\"><i class=\\"nutui-iconfont nut-icon nut-icon-\\" style=\\"font-size: 28px; width: 28px; height: 28px;\\" src=\\"\\"></i>
+      <view class=\\"nut-grid-item__text\\"></view>
+    </view>
+  </view>
+  <view class=\\"nut-grid-item\\" style=\\"flex-basis: 25%; padding-right: 20px; margin-top: 20px;\\">
     <view class=\\"nut-grid-item__content nut-grid-item__content--border nut-grid-item__content--surround nut-grid-item__content--center\\"><i class=\\"nutui-iconfont nut-icon nut-icon-\\" style=\\"font-size: 28px; width: 28px; height: 28px;\\" src=\\"\\"></i>
       <view class=\\"nut-grid-item__text\\"></view>
     </view>

+ 1 - 1
src/packages/__VUE/grid/__test__/grid.spec.ts

@@ -40,7 +40,7 @@ test('should render gutter correctly', () => {
       gutter: 20
     },
     slots: {
-      default: [GridItem, GridItem, GridItem, GridItem]
+      default: [GridItem, GridItem, GridItem, GridItem, GridItem, GridItem, GridItem, GridItem]
     }
   });
 

+ 3 - 2
src/packages/__VUE/grid/common.ts

@@ -1,7 +1,8 @@
-import { h, provide, computed } from 'vue';
+import { h, computed } from 'vue';
 import type { PropType, CSSProperties, ExtractPropTypes, SetupContext, RenderFunction } from 'vue';
 import { createComponent } from '../../utils/create';
 import { pxCheck } from '../../utils/pxCheck';
+import { useProvide } from '../../utils/useRelation/useProvide';
 
 const { componentName } = createComponent('grid');
 
@@ -65,7 +66,7 @@ export type GridProps = ExtractPropTypes<typeof gridProps>;
 export const component = {
   props: gridProps,
   setup(props: GridProps, { slots }: SetupContext): RenderFunction {
-    provide(GRID_KEY, props);
+    useProvide(GRID_KEY, `${componentName}-item`)({ props });
 
     const rootClass = computed(() => {
       const prefixCls = componentName;

+ 9 - 5
src/packages/__VUE/griditem/index.taro.vue

@@ -16,9 +16,10 @@
 </template>
 
 <script lang="ts">
-import { inject, computed, CSSProperties } from 'vue';
+import { computed, CSSProperties } from 'vue';
 import { createComponent } from '../../utils/create';
 import { pxCheck } from '../../utils/pxCheck';
+import { useInject } from '../../utils/useRelation/useInject';
 import { GRID_KEY, GridProps } from '../grid/common';
 const { create, componentName } = createComponent('grid-item');
 
@@ -52,7 +53,10 @@ export default create({
   },
   emits: ['click'],
   setup(props, { emit }) {
-    const parent = inject(GRID_KEY) as Required<GridProps>;
+    const Parent = useInject<{ props: Required<GridProps> }>(GRID_KEY);
+    if (!Parent.parent) return;
+    const index = Parent.index;
+    const parent = Parent.parent.props;
 
     // root
     const rootClass = computed(() => {
@@ -71,9 +75,9 @@ export default create({
         style.paddingTop = `${100 / +parent.columnNum}%`;
       } else if (parent.gutter) {
         style.paddingRight = pxCheck(parent.gutter);
-        // TODO: 上边缘间隔处理 index >= columnNum
-        // style.marginTop = pxCheck(parent.gutter);
-        style.marginBottom = pxCheck(parent.gutter);
+        if (index.value >= parent.columnNum) {
+          style.marginTop = pxCheck(parent.gutter);
+        }
       }
 
       return style;

+ 9 - 5
src/packages/__VUE/griditem/index.vue

@@ -16,10 +16,11 @@
 </template>
 
 <script lang="ts">
-import { inject, computed, CSSProperties } from 'vue';
+import { computed, CSSProperties } from 'vue';
 import { useRouter } from 'vue-router';
 import { createComponent } from '../../utils/create';
 import { pxCheck } from '../../utils/pxCheck';
+import { useInject } from '../../utils/useRelation/useInject';
 import { GRID_KEY, GridProps } from '../grid/common';
 const { create, componentName } = createComponent('grid-item');
 
@@ -53,7 +54,10 @@ export default create({
   },
   emits: ['click'],
   setup(props, { emit }) {
-    const parent = inject(GRID_KEY) as Required<GridProps>;
+    const Parent = useInject<{ props: Required<GridProps> }>(GRID_KEY);
+    if (!Parent.parent) return;
+    const index = Parent.index;
+    const parent = Parent.parent.props;
 
     // root
     const rootClass = computed(() => {
@@ -72,9 +76,9 @@ export default create({
         style.paddingTop = `${100 / +parent.columnNum}%`;
       } else if (parent.gutter) {
         style.paddingRight = pxCheck(parent.gutter);
-        // TODO: 上边缘间隔处理 index >= columnNum
-        // style.marginTop = pxCheck(parent.gutter);
-        style.marginBottom = pxCheck(parent.gutter);
+        if (index.value >= parent.columnNum) {
+          style.marginTop = pxCheck(parent.gutter);
+        }
       }
 
       return style;

+ 32 - 0
src/packages/utils/useRelation/useInject.ts

@@ -0,0 +1,32 @@
+import { ref, inject, computed, onUnmounted, getCurrentInstance } from 'vue';
+import type { InjectionKey, ComponentInternalInstance } from 'vue';
+
+type ParentProvide<T> = T & {
+  add(child: ComponentInternalInstance): void;
+  remove(child: ComponentInternalInstance): void;
+  internalChildren: ComponentInternalInstance[];
+};
+
+export function useInject<T>(key: InjectionKey<ParentProvide<T>>) {
+  const parent = inject(key, null);
+
+  if (parent) {
+    const instance = getCurrentInstance()!;
+    const { add, remove, internalChildren } = parent;
+
+    add(instance);
+    onUnmounted(() => remove(instance));
+
+    const index = computed(() => internalChildren.indexOf(instance));
+
+    return {
+      parent,
+      index
+    };
+  }
+
+  return {
+    parent: null,
+    index: ref(-1)
+  };
+}

+ 80 - 0
src/packages/utils/useRelation/useProvide.ts

@@ -0,0 +1,80 @@
+import { isVNode, provide, markRaw, shallowReactive, getCurrentInstance } from 'vue';
+import type { VNode, InjectionKey, ConcreteComponent, VNodeNormalizedChildren, ComponentInternalInstance } from 'vue';
+
+export function flattenVNodes(children: VNodeNormalizedChildren, childName?: string) {
+  const result: VNode[] = [];
+
+  const traverse = (children: VNodeNormalizedChildren) => {
+    if (!Array.isArray(children)) return;
+    children.forEach((child) => {
+      if (!isVNode(child)) return;
+
+      if (childName) {
+        if (child.type && (child.type as ConcreteComponent).name === childName) {
+          result.push(child);
+          return;
+        }
+      } else {
+        result.push(child);
+      }
+
+      if (child.component?.subTree) {
+        traverse(child.component.subTree.children);
+      }
+
+      if (child.children) {
+        traverse(child.children);
+      }
+    });
+  };
+
+  traverse(children);
+
+  return result;
+}
+
+export function sortChildren(
+  parent: ComponentInternalInstance,
+  internalChildren: ComponentInternalInstance[],
+  childName?: string
+) {
+  const vnodes = flattenVNodes(parent.subTree.children, childName);
+  internalChildren.sort((a, b) => {
+    return vnodes.indexOf(a.vnode) - vnodes.indexOf(b.vnode);
+  });
+}
+
+// 如果指定组件名称,则只查找此组件并且查到后结束。也就是不关心此组件下的内容,在大部分场景下节省查找消耗。
+export function useProvide<ProvideValue>(key: InjectionKey<ProvideValue>, childName?: string) {
+  const internalChildren: ComponentInternalInstance[] = shallowReactive([]);
+  const parent = getCurrentInstance()!;
+
+  const add = (child: ComponentInternalInstance) => {
+    if (!child.proxy) return;
+    internalChildren.push(markRaw(child));
+    sortChildren(parent, internalChildren, childName);
+  };
+
+  const remove = (child: ComponentInternalInstance) => {
+    internalChildren.splice(internalChildren.indexOf(markRaw(child)), 1);
+  };
+
+  const extend = Object.assign;
+  return (value?: ProvideValue) => {
+    provide(
+      key,
+      extend(
+        {
+          add,
+          remove,
+          internalChildren
+        },
+        value
+      )
+    );
+
+    return {
+      internalChildren
+    };
+  };
+}