naive-ui
Version: 
A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast
258 lines • 9.56 kB
JavaScript
import { cloneVNode, computed, defineComponent, h, mergeProps, onMounted, provide, ref, toRef, vShow } from 'vue';
import { useBreakpoints, useMemo } from 'vooks';
import { VResizeObserver } from 'vueuc';
import { beforeNextFrameOnce, parseResponsivePropValue, pxfy } from 'seemly';
import { defaultBreakpoints } from "../../config-provider/src/config.mjs";
import { useConfig } from "../../_mixins/index.mjs";
import { flatten, getSlot, isBrowser, isNodeVShowFalse } from "../../_utils/index.mjs";
import { defaultSpan, gridInjectionKey } from "./config.mjs";
const defaultCols = 24;
const SSR_ATTR_NAME = '__ssr__';
export const gridProps = {
  layoutShiftDisabled: Boolean,
  responsive: {
    type: [String, Boolean],
    default: 'self'
  },
  cols: {
    type: [Number, String],
    default: defaultCols
  },
  itemResponsive: Boolean,
  collapsed: Boolean,
  // may create grid rows < collapsedRows since a item may take all the row
  collapsedRows: {
    type: Number,
    default: 1
  },
  itemStyle: [Object, String],
  xGap: {
    type: [Number, String],
    default: 0
  },
  yGap: {
    type: [Number, String],
    default: 0
  }
};
export default defineComponent({
  name: 'Grid',
  inheritAttrs: false,
  props: gridProps,
  setup(props) {
    const {
      mergedClsPrefixRef,
      mergedBreakpointsRef
    } = useConfig(props);
    const numRegex = /^\d+$/;
    const widthRef = ref(undefined);
    const breakpointsRef = useBreakpoints((mergedBreakpointsRef === null || mergedBreakpointsRef === void 0 ? void 0 : mergedBreakpointsRef.value) || defaultBreakpoints);
    const isResponsiveRef = useMemo(() => {
      if (props.itemResponsive) return true;
      if (!numRegex.test(props.cols.toString())) return true;
      if (!numRegex.test(props.xGap.toString())) return true;
      if (!numRegex.test(props.yGap.toString())) return true;
      return false;
    });
    const responsiveQueryRef = computed(() => {
      if (!isResponsiveRef.value) return undefined;
      return props.responsive === 'self' ? widthRef.value : breakpointsRef.value;
    });
    const responsiveColsRef = useMemo(() => {
      var _a;
      return (_a = Number(parseResponsivePropValue(props.cols.toString(), responsiveQueryRef.value))) !== null && _a !== void 0 ? _a : defaultCols;
    });
    const responsiveXGapRef = useMemo(() => parseResponsivePropValue(props.xGap.toString(), responsiveQueryRef.value));
    const responsiveYGapRef = useMemo(() => parseResponsivePropValue(props.yGap.toString(), responsiveQueryRef.value));
    const handleResize = entry => {
      widthRef.value = entry.contentRect.width;
    };
    const handleResizeRaf = entry => {
      beforeNextFrameOnce(handleResize, entry);
    };
    const overflowRef = ref(false);
    const handleResizeRef = computed(() => {
      if (props.responsive === 'self') {
        return handleResizeRaf;
      }
      return undefined;
    });
    // for SSR, fix bug https://github.com/tusen-ai/naive-ui/issues/2462
    const isSsrRef = ref(false);
    const contentElRef = ref();
    onMounted(() => {
      const {
        value: contentEl
      } = contentElRef;
      if (contentEl) {
        if (contentEl.hasAttribute(SSR_ATTR_NAME)) {
          contentEl.removeAttribute(SSR_ATTR_NAME);
          isSsrRef.value = true;
        }
      }
    });
    provide(gridInjectionKey, {
      layoutShiftDisabledRef: toRef(props, 'layoutShiftDisabled'),
      isSsrRef,
      itemStyleRef: toRef(props, 'itemStyle'),
      xGapRef: responsiveXGapRef,
      overflowRef
    });
    return {
      isSsr: !isBrowser,
      contentEl: contentElRef,
      mergedClsPrefix: mergedClsPrefixRef,
      style: computed(() => {
        if (props.layoutShiftDisabled) {
          return {
            width: '100%',
            display: 'grid',
            gridTemplateColumns: `repeat(${props.cols}, minmax(0, 1fr))`,
            columnGap: pxfy(props.xGap),
            rowGap: pxfy(props.yGap)
          };
        }
        return {
          width: '100%',
          display: 'grid',
          gridTemplateColumns: `repeat(${responsiveColsRef.value}, minmax(0, 1fr))`,
          columnGap: pxfy(responsiveXGapRef.value),
          rowGap: pxfy(responsiveYGapRef.value)
        };
      }),
      isResponsive: isResponsiveRef,
      responsiveQuery: responsiveQueryRef,
      responsiveCols: responsiveColsRef,
      handleResize: handleResizeRef,
      overflow: overflowRef
    };
  },
  render() {
    if (this.layoutShiftDisabled) {
      return h('div', mergeProps({
        ref: 'contentEl',
        class: `${this.mergedClsPrefix}-grid`,
        style: this.style
      }, this.$attrs), this.$slots);
    }
    const renderContent = () => {
      var _a, _b, _c, _d, _e, _f, _g;
      this.overflow = false;
      // render will be called twice when mounted, I can't figure out why
      // 2 jobs will be pushed into job queues with same id, and then be flushed
      const rawChildren = flatten(getSlot(this));
      const childrenAndRawSpan = [];
      const {
        collapsed,
        collapsedRows,
        responsiveCols,
        responsiveQuery
      } = this;
      rawChildren.forEach(child => {
        var _a, _b, _c, _d, _e;
        if (((_a = child === null || child === void 0 ? void 0 : child.type) === null || _a === void 0 ? void 0 : _a.__GRID_ITEM__) !== true) return;
        if (isNodeVShowFalse(child)) {
          const clonedNode = cloneVNode(child);
          if (clonedNode.props) {
            clonedNode.props.privateShow = false;
          } else {
            clonedNode.props = {
              privateShow: false
            };
          }
          childrenAndRawSpan.push({
            child: clonedNode,
            rawChildSpan: 0
          });
          return;
        }
        // We don't want v-show to control display, so we need to stripe it
        // here, nor it may mess child's style
        child.dirs = ((_b = child.dirs) === null || _b === void 0 ? void 0 : _b.filter(({
          dir
        }) => dir !== vShow)) || null;
        if (((_c = child.dirs) === null || _c === void 0 ? void 0 : _c.length) === 0) {
          child.dirs = null;
        }
        const clonedChild = cloneVNode(child);
        const rawChildSpan = Number((_e = parseResponsivePropValue((_d = clonedChild.props) === null || _d === void 0 ? void 0 : _d.span, responsiveQuery)) !== null && _e !== void 0 ? _e : defaultSpan);
        if (rawChildSpan === 0) return;
        childrenAndRawSpan.push({
          child: clonedChild,
          rawChildSpan
        });
      });
      let suffixSpan = 0;
      const maybeSuffixNode = (_a = childrenAndRawSpan[childrenAndRawSpan.length - 1]) === null || _a === void 0 ? void 0 : _a.child;
      if (maybeSuffixNode === null || maybeSuffixNode === void 0 ? void 0 : maybeSuffixNode.props) {
        const suffixPropValue = (_b = maybeSuffixNode.props) === null || _b === void 0 ? void 0 : _b.suffix;
        if (suffixPropValue !== undefined && suffixPropValue !== false) {
          suffixSpan = Number((_d = parseResponsivePropValue((_c = maybeSuffixNode.props) === null || _c === void 0 ? void 0 : _c.span, responsiveQuery)) !== null && _d !== void 0 ? _d : defaultSpan);
          maybeSuffixNode.props.privateSpan = suffixSpan;
          maybeSuffixNode.props.privateColStart = responsiveCols + 1 - suffixSpan;
          maybeSuffixNode.props.privateShow = (_e = maybeSuffixNode.props.privateShow) !== null && _e !== void 0 ? _e : true;
        }
      }
      let spanCounter = 0;
      let done = false;
      for (const {
        child,
        rawChildSpan
      } of childrenAndRawSpan) {
        if (done) {
          this.overflow = true;
        }
        if (!done) {
          const childOffset = Number((_g = parseResponsivePropValue((_f = child.props) === null || _f === void 0 ? void 0 : _f.offset, responsiveQuery)) !== null && _g !== void 0 ? _g : 0);
          // it could be 0 sometimes (v-show = false)
          const childSpan = Math.min(rawChildSpan + childOffset, responsiveCols);
          if (!child.props) {
            child.props = {
              privateSpan: childSpan,
              privateOffset: childOffset
            };
          } else {
            child.props.privateSpan = childSpan;
            child.props.privateOffset = childOffset;
          }
          if (collapsed) {
            const remainder = spanCounter % responsiveCols;
            if (childSpan + remainder > responsiveCols) {
              spanCounter += responsiveCols - remainder;
            }
            if (childSpan + spanCounter + suffixSpan > collapsedRows * responsiveCols) {
              done = true;
            } else {
              spanCounter += childSpan;
            }
          }
        }
        if (done) {
          if (child.props) {
            // suffix node's privateShow may be true
            if (child.props.privateShow !== true) {
              child.props.privateShow = false;
            }
          } else {
            child.props = {
              privateShow: false
            };
          }
        }
      }
      return h('div', mergeProps({
        ref: 'contentEl',
        class: `${this.mergedClsPrefix}-grid`,
        style: this.style,
        [SSR_ATTR_NAME]: this.isSsr || undefined
      }, this.$attrs), childrenAndRawSpan.map(({
        child
      }) => child));
    };
    return this.isResponsive && this.responsive === 'self' ? h(VResizeObserver, {
      onResize: this.handleResize
    }, {
      default: renderContent
    }) : renderContent();
  }
});