import React, { Children, cloneElement, forwardRef, ForwardRefExoticComponent, ForwardRefRenderFunction, ReactElement, ReactNode, useContext, useEffect, useMemo, useRef, useState, } from 'react'; import { cacheFoldStore } from './utils/const'; import classNames from 'classnames'; import Context from './common/context'; import { debounceCalPageClassNames, debounceCalPageClassNames2, getGapVal, isValidGap, wrapUnit, } from './utils'; import Block from './block'; import Row from './row'; import Col from './col'; import Cell from './cell'; import { CnLayoutSectionProps, LayoutContextProps, TypeMark } from './types'; import isNil from 'lodash/isNil'; import isString from 'lodash/isString'; import { CnPageColumnChange, CnSectionCalculateLayout, onEvent, } from './utils/event-name'; type ISection = ForwardRefExoticComponent & TypeMark; /** * 获取计算后的子节点 * @param children * @param numberOfColumns * @param maxNumberOfColumns */ function getValidChildren( children: ReactNode, numberOfColumns: number, maxNumberOfColumns: number, ) { const newChildren = wrapBlock(children, maxNumberOfColumns); return calBlockSpan(newChildren as ReactElement[], numberOfColumns); } /** * 为非 Block 节点,包裹 Block 子元素 * @param children * @param maxNumberOfColumns * @return ReactNode[] */ function wrapBlock(children: ReactNode, maxNumberOfColumns: number) { let tmp: ReactNode[] = []; const ret: ReactNode[] = []; const validChildList = Children.toArray(children).filter( (child) => !isNil(child), ); validChildList.forEach((child: any, index) => { if (child?.type === Block || child?.type?.typeMark === 'Block') { if (tmp.length > 0) { ret.push( {tmp} , ); tmp = []; } ret.push(child); if (tmp.length > 0) { ret.push( {[...tmp]} , ); tmp = []; } } else { tmp.push(child); } if (index === validChildList.length - 1 && tmp.length > 0) { ret.push( {[...tmp]} , ); tmp = []; } }); return ret; } /** * 为 Block 自动调整列宽 * @param children * @param numberOfColumns */ function calBlockSpan( originalChildren: ReactElement[], numberOfColumns: number, ) { const hiddenChildrenList = []; const children = originalChildren.filter((child, index) => { const { _context, _nodeId } = child?.props || {}; if (_context && _nodeId) { const hidden = _context?.$?.(_nodeId)?.props?.hidden || cacheFoldStore[_nodeId]; if (hidden) { hiddenChildrenList.push([index, child]); } return hidden !== true; } return true; }); let ret: ReactElement[] = []; let stack: ReactElement[] = []; let counter = 0; const len = children.length; for (let i = 0; i < len; i++) { const child = children[i]; const span: number = +child.props?.span; // MODIFY 读取 span 属性 // const span: number = +child.props?._layout?.span; if (span < numberOfColumns) { if (span + counter <= numberOfColumns) { stack.push(child); counter += span; } else { ret = [...ret, ...adjustColWidth(stack, counter, numberOfColumns)]; stack = [child]; counter = span; } } else { if (stack.length > 0) { ret = [...ret, ...adjustColWidth(stack, counter, numberOfColumns)]; stack = []; counter = 0; } ret = [ ...ret, ...adjustColWidth([child], numberOfColumns, numberOfColumns), ]; } if (i === len - 1 && stack.length > 0) { ret = [...ret, ...adjustColWidth(stack, counter, numberOfColumns)]; } } if (hiddenChildrenList.length > 0) { hiddenChildrenList.forEach((item) => { ret.splice(item[0], 0, item[1]); }); } return ret; } /** * 重算列宽 * @param blockNodes * @param totalSpan * @param maxColNum */ function adjustColWidth( blockNodes: ReactElement[], totalSpan: number, maxColNum: number, ) { return blockNodes.map((item) => { const { span } = item.props; return cloneElement(item, { ...item.props, span: Math.round((span / totalSpan) * maxColNum), }); // MODIFY 设置 span 属性 // const span = +item.props?._layout?.span; // return cloneElement(item, { // ...item.props, // _layout: { // ...item.props?._layout, // span: Math.round((span / totalSpan) * maxColNum), // } // }); }); } /** * @description 为容器绑定resize事件(默认监听window的resize事件,OnePage模式下监听距离当前Section最近的OnePage容器) * @param container * @returns */ function addResizeListener(container?: HTMLElement | null) { if ( document.body.getElementsByClassName('cn-one-page-layout-col')?.length > 0 ) { const targetNode = container?.closest('.cn-one-page-layout-col'); if (targetNode && ResizeObserver) { const ro = new ResizeObserver(() => { debounceCalPageClassNames2({ container: targetNode }); }); ro.observe(targetNode); return; } } debounceCalPageClassNames(); window.addEventListener('resize', debounceCalPageClassNames2); } /** * 章节 * @param props * @param ref */ const Section: ForwardRefRenderFunction< HTMLDivElement, CnLayoutSectionProps > = (props, ref) => { const { className, children, title, titleAlign, extra, gap: blockGapProp, noPadding, ...others } = props; const containerRef = useRef(null); const { prefix, blockGap: blockGapContext, breakPoint: { numberOfColumns }, maxNumberOfColumns, } = useContext(Context); const [updateFlag, setUpdateFlag] = useState(Date.now()); const [realNumberOfColumns, setRealNumberOfColumns] = useState(numberOfColumns); useEffect(() => { setRealNumberOfColumns(numberOfColumns); }, [numberOfColumns]); useEffect(() => { onEvent(CnPageColumnChange, (payload) => { if (typeof payload?.numberOfColumns === 'number') { setRealNumberOfColumns(payload.numberOfColumns); } }); onEvent(CnSectionCalculateLayout, (payload) => { const { _nodeId } = payload || {}; const hasChildren = Children.toArray(children).find((item) => { //@ts-ignore return item?.props?._nodeId === _nodeId; }); if (hasChildren) { setUpdateFlag(Date.now()); } }); addResizeListener(containerRef?.current); }, []); const clsPrefix = `${prefix}section`; const hasHead = title || extra; // classNames const sectionCls = classNames(clsPrefix, className); const blockWrapperCls = `${clsPrefix}-block-wrapper`; const innerContainerWithHead = classNames(`${clsPrefix}-inner--with-head`, { [`${clsPrefix}-no-padding`]: noPadding, }); const innerContainerWithoutHead = classNames( `${clsPrefix}-inner-without-head`, ); // 此处定义的是 blockGap const gap = getGapVal(blockGapContext, blockGapProp); const blockWrapperStyle = useMemo(() => { return { ...(isValidGap(gap) ? { gridColumnGap: wrapUnit(gap), gridRowGap: wrapUnit(gap), } : null), }; }, [gap]); const newChildren = useMemo( () => getValidChildren(children, realNumberOfColumns, maxNumberOfColumns), [children, realNumberOfColumns, maxNumberOfColumns, updateFlag], ); // 带有 title&extra if (hasHead) { return (
{isString(title) ? {title} : title} {extra ? ( {isString(extra) ? {extra} : extra} ) : null}
{newChildren}
); } return (
{newChildren}
); }; const RefSection: ISection = forwardRef(Section); RefSection.displayName = 'Section'; RefSection.typeMark = 'Section'; export default RefSection;