UNPKG

@blocklet/ui-react

Version:

Some useful front-end web components that can be used in Blocklets.

162 lines (142 loc) 5.09 kB
export const mapRecursive = (array, fn, childrenKey = 'children') => { return array.map((item) => { if (Array.isArray(item[childrenKey])) { return fn({ ...item, [childrenKey]: mapRecursive(item[childrenKey], fn, childrenKey), }); } return fn(item); }); }; // 展平有层级结构的 array export const flatRecursive = (array, childrenKey = 'children') => { const result = []; mapRecursive(array, (item) => result.push(item), childrenKey); return result; }; // 对有层级结构的 array 元素计数 export const countRecursive = (array, childrenKey = 'children') => { let counter = 0; mapRecursive(array, () => counter++, childrenKey); return counter; }; // 对有层级结构的 array 进行 filter 处理 // 因为是 DFS 遍历, 可以借助 context.filteredChildren 在过滤/保留子结的同时保持父子结构 (即使父结点不满足筛选条件) export const filterRecursive = (array, predicate, childrenKey = 'children') => { return array .map((item) => ({ ...item })) .filter((item) => { const children = item[childrenKey]; if (Array.isArray(children)) { const filtered = filterRecursive(children, predicate, childrenKey); item[childrenKey] = filtered?.length ? filtered : undefined; } const context = { filteredChildren: item[childrenKey], isLeaf: !children?.length }; return predicate(item, context); }); }; // "http://", "https://" 2 种情况 export const isUrl = (str) => { return /^https?:\/\//.test(str); }; // 链接是否是 mailto 协议 export const isMailProtocol = (str) => { return /^mailto:/i.test(str.trim()); }; /** * @description 检测是否是 Iconify 格式的字符串 * @deprecated */ export const isIconifyString = (str) => { return /^[\w-]+:[\w-]+$/.test(str); }; /** * 检测 path 是否匹配当前 location, path 只考虑 "/" 开头的相对路径 */ export const matchPath = (path) => { if (!path || !path?.startsWith('/')) { return false; } const ensureTrailingSlash = (str) => (str.endsWith('/') ? str : `${str}/`); const pathname = ensureTrailingSlash(window.location.pathname); const normalizedPath = ensureTrailingSlash(new URL(path, window.location.origin).pathname); return pathname.startsWith(normalizedPath); }; /** * 从一组 paths 中, 找到匹配当前 location 的 path, 返回序号 */ export const matchPaths = (paths = []) => { const matched = paths.map((item, index) => ({ path: item, index })).filter((item) => matchPath(item.path)); if (!matched?.length) { return -1; } // 多个 path 都匹配时, 取一个最具体 (最长的) path const mostSpecific = matched.slice(1).reduce((prev, cur) => { return prev.path.length >= cur.path.length ? prev : cur; }, matched[0]); return mostSpecific.index; }; /** 导航列表分列 */ export const splitNavColumns = (items, options = {}) => { const { columns = 1, breakInside = false, groupHeight = 48, itemHeight = 24, childrenKey = 'items' } = options; // 分组数 const groups = items.length; // 高度预估 const totalHeight = items.reduce((height, group) => { return height + groupHeight + (group[childrenKey]?.length || 0) * itemHeight; }, 0); const targetHeight = Math.ceil(totalHeight / columns); // 使用贪心策略进行分列 const result = [[]]; let currentColumn = 0; let currentHeight = 0; // 允许的高度偏差范围(有利于得到高度相差不大的列) const heightVariance = targetHeight * 0.2; // 是否应该分列 const shouldBreakColumn = (nextHeight) => { return ( currentHeight > targetHeight - heightVariance && currentColumn < columns - 1 && currentHeight + nextHeight > targetHeight + heightVariance ); }; items.forEach((group) => { // 当前分组的预估高度 const groupTotalHeight = groupHeight + (group[childrenKey]?.length || 0) * itemHeight; // 允许截断分组,可以在任何子项处换列,尽可能利用当前列的剩余空间 if (breakInside && shouldBreakColumn(groupHeight)) { currentColumn++; currentHeight = 0; result[currentColumn] = []; } // 不允许截断分组,只能在分组边界换列,优先分列,列不够用再考虑充分利用剩余空间 if (!breakInside && currentHeight > 0 && (groups <= columns || shouldBreakColumn(groupTotalHeight))) { currentColumn++; currentHeight = 0; result[currentColumn] = []; } // 添加分组标题 result[currentColumn].push({ ...group, group: true, }); currentHeight += groupHeight; // 添加子项 if (group[childrenKey]) { group[childrenKey].forEach((child) => { if (breakInside && shouldBreakColumn(itemHeight)) { currentColumn++; currentHeight = 0; result[currentColumn] = []; } result[currentColumn].push({ ...child, group: false, }); currentHeight += itemHeight; }); } }); return result; };