From 6da530d0c5f53d283ddfaa4b3e510ca11c9bf83e Mon Sep 17 00:00:00 2001 From: Wenjun Xu <906626481@qq.com> Date: Thu, 18 Jan 2024 18:15:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=88=E5=B9=B6=20master=20=E5=88=B0?= =?UTF-8?q?=20next=20(#2493)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 🤖 更新 changelog 文件 (#2186) * fix: 复制时移除空控制符 (#2204) fix: 移除空控制符 Co-authored-by: 沫君 <chuxiao.lcx@alibaba-inc.com> * fix: 系统拦截快捷键后多选交互异常 (#2191) * fix: 系统拦截快捷键后多选交互异常 * test: 增加测试用例 --------- Co-authored-by: 沫君 <chuxiao.lcx@alibaba-inc.com> * fix: 修复 meta name 同名时,hoverFocus 出错的问题 (#2187) * docs: 更新自定义datacell示例 (#2208) Co-authored-by: 沫君 <chuxiao.lcx@alibaba-inc.com> * feat: 行列头和数值为空时,不渲染表格框架 (#2207) * feat: 当用户还未配置行列头和数值时,不渲染 * feat: 当用户还未配置行列头和数值时,不渲染表格框架 * test: 添加行列头和数值为空时,不渲染表格框架的单测 --------- Co-authored-by: zishang <lyl275911@antgroup.com> * fix: 修复总计小计被意外 format 的问题 (#2209) * fix(core): only do compatibility of shift + scroll on Windows (#2206) * fix(core): only do compatibility of shit + scroll on Windows * fix(core): only do compatibility of shit + scroll on Windows - add test case --------- Co-authored-by: stone <stone-lyl@users.noreply.github.com> * chore: 🤖 更新 changelog 文件 (#2213) * fix(core): can not set field height or width when the field name is surrounded by square brackets (#2212) Co-authored-by: Jinke Li <a1231236677287@163.com> * fix: 明细表存在横向滚动条时多列头文本不居中 close #2199 (#2200) Co-authored-by: Jinke Li <a1231236677287@163.com> * feat: 在行列头配置为空时,趋势分析表不渲染框架 (#2216) * feat: 当用户还未配置行列头和数值时,不渲染 * feat: 当用户还未配置行列头和数值时,不渲染表格框架 * feat: 当 dataCfg 配置为空时,不展示空框架,兼容趋势分析表 * test: 添加当 dataCfg 配置为空时,不展示空框架,兼容趋势分析表单测 * test: 添加趋势分析表中自定义 cornerExtraFieldText 相关单测 --------- Co-authored-by: zishang <lyl275911@antgroup.com> * fix(core): can reset tooltip.renderTooltip in setOptions (#2210) Co-authored-by: Jinke Li <a1231236677287@163.com> * fix: 修复树状模式下,总计小计格式化问题 (#2219) * fix: 修复树状模式下,总计小计格式化问题 * fix: 修改 active border 绘制顺序 * test: 单测修复 * fix: 修复 dataCell linkField 不能点击的问题 (#2227) * chore: 🤖 更新 changelog 文件 (#2221) * chore: 🤖 更新 changelog 文件 (#2228) * fix(core): highlight the column header cell and row header when data cell clicked (#2211) Co-authored-by: Jinke Li <a1231236677287@163.com> Co-authored-by: 刘嘉一 <lcx.seima@gmail.com> * docs: type InteractionStateTheme (#2234) * fix(interaction): 修复列头隐藏后展开 icon 显示异常 close #2194 (#2224) Co-authored-by: 卿珂 <lijinke.ljk@antgroup.com> * feat: 表格滚动后触发hover (#2235) * feat: trigger hover after scroll * chore: rename property and add note * test: should trigger mousemove after scroll * chore: ci pass * feat: add option to control hover after scroll * test: set hover after scroll option to true * docs: hover after scroll - description in interaction chapter - one demo * chore: config button in react playground * chore: update screenshot link --------- Co-authored-by: 刘嘉一 <lcx.seima@gmail.com> * fix(react): 更新react分页器定义 (#2238) * fix(react): correct `showPagination` type * docs: correct `showPagination` type * chore: correct `showPagination` type - update `showPagination` type in react playground - update correct callback in s2 site demo --------- Co-authored-by: 刘嘉一 <lcx.seima@gmail.com> * feat: support separate config of `hoverHighlight` (#2226) * chore: 🤖 更新 changelog 文件 (#2245) * docs: update DATA_CELL_SELECT_MOVE event (#2248) * fix: 交叉表圈选表头复制内容不正确 (#2254) * fix(core): 使用客户端的位置来计算调整大小的偏移量 (#2273) * fix(core): 删除范围选择中的错误拦截器 (#2263) * fix(core): 悬停角头单元格后显示图标 (#2261) Co-authored-by: Jinke Li <a1231236677287@163.com> * docs: 更新 FAQ 和 文档示例 (#2276) Co-authored-by: 卿珂 <lijinke.ljk@antgroup.com> * feat: 为 react 编辑组件添加 onDataCellEditEnd 事件 (#2247) * fix(core): 文本区域高度不应超过视口高度 (#2265) Co-authored-by: Jinke Li <a1231236677287@163.com> * fix(core): 调用 `setOptions` 设置 `brushSelection` (#2257) * docs: 完善 tooltip 文档 (#2281) Co-authored-by: 卿珂 <lijinke.ljk@antgroup.com> * chore: 🤖 更新 changelog 文件 (#2282) * fix(table-sheet): 修复明细表获取到错误的实际渲染内容高度 (#2290) Co-authored-by: 卿珂 <lijinke.ljk@antgroup.com> * fix: 点击列头后hover拦截消失 (#2288) Co-authored-by: 沫君 <chuxiao.lcx@antgroup.com> * chore: 🤖 更新 changelog 文件 (#2293) * perf(react): 防止不必要的重新渲染 (#2250) * chore: `setDataCfg` 函数类型优化 (#2286) * fix: 修复复制整行时错位 (#2278) * fix: 修复复制整行时错位 * fix: 修复选行复制时的排序问题 --------- Co-authored-by: owen.wjh <owen.wjh@alibaba-inc.com> * fix: condition.mapping 返回值与文档不符合,允许返回undefined与null (#2320) Co-authored-by: hekunyu <hekunyu.latte@bytedance.com> * chore: 🤖 更新 changelog 文件 (#2306) * fix(scroll): 修复移动端快速滚动时控制台报错 close #2266 (#2302) fix(scroll): 修复移动端滚动时控制台报错 close #2266 Co-authored-by: 卿珂 <lijinke.ljk@antgroup.com> * fix(scroll): 修复调用 scrollWithAnimation 后 unmount 表格导致频繁报错 (#2317) * fix(scroll): 修复调用 scrollWithAnimation 后 unmount 表格导致频繁报错 * fix(scroll): 修复调用 scrollWithAnimation 后 unmount 表格导致频繁报错 - 添加单测 --------- Co-authored-by: 晓峦 <luanwendi.lwd@alibaba-inc.com> Co-authored-by: Jinke Li <a1231236677287@163.com> * feat: 趋势分析表无波动的字体不用红绿色显示 (#2339) * feat: 趋势分析表无波动的字体不用红绿色显示 * refactor: 判0逻辑合并到isZeroOrEmptyValue --------- Co-authored-by: huiyu.zjt <huiyu.zjt@antgroup.com> * fix: 优化 handleDimensionValueFilter 复杂度 (#2325) * feat: 交叉表行头叶子节点支持斑马纹风格 (#2332) * feat: 行头叶子节点支持crossBackgroundColor * feat: 行头叶子节点支持crossBackgroundColor * refactor: 函数命名getCrossBackgroundColor、测试用例使用createPivotSheet --------- Co-authored-by: huiyu.zjt <huiyu.zjt@antgroup.com> * fix(table-facet): 修复过滤多列时,删除一列过滤将同时清空后续过滤列 (#2323) * docs: 完善交互主题配置 (#2344) * chore: 🤖 更新 changelog 文件 (#2345) * feat: 对比值无波动时也显示灰色 (#2351) * feat: 趋势分析表无波动的字体不用红绿色显示 * refactor: 判0逻辑合并到isZeroOrEmptyValue * feat: 对比值与原始值相同时也使用灰色 * fix: 单测断言修复 --------- Co-authored-by: huiyu.zjt <huiyu.zjt@antgroup.com> * feat: 小计/总计功能,支持按维度分组汇总 (#2346) * feat(Api): 添加 totalDimensionGroup/subTotalDimensionGroup api,以及一些临时的开发函数 * feat(Hierarchy): 总计小计结点下添加Hierarchy * feat(Render): getMultipleMap 实现,计算总计小计下的布局信息 * feat(Render): 按维度分组的小计总计下表头位置的调整和渲染 * feat(DataSet): 存在维度分组时的汇总值获取 * feat(DataSet): 存在维度分组时的汇总值获取 * feat(DataSet): 存在维度分组时的汇总值获取 * feat: 补充注释 * feat: 单测快照更新,添加isTotalRoot属性 * fix: 有多个 Value 时不允许隐藏度量列 * fix: 有多个 Value 时不允许隐藏度量列 * fix: 删除了一个莫名其妙的函数 * test: 按维度分组汇总能力单测 * docs: 按维度分组汇总能力文档 * test: 更新,多度量指标不允许隐藏指标头 * docs: 图片示例 * test: 更新 snap 数据文件 * chore: 版本号更新 * chore: 版本号更新 * chore: 版本号更新 * chore: 版本号更新 * chore: 版本号更新 * chore: 版本号更新 * chore: 版本号更新 * Merge remote-tracking branch 'origin/Juze_TotalsDimGroup' into Juze_TotalsDimGroup * test: 更新快照 * chore: 删除开发测试文件 * fix: 汇总指标节点也是汇总节点 * chore: 删除无用文件 * fix: isTotalRoot 替换 isTotals * fix: isTotalRoot 替换 isTotals * fix: isTotalRoot 替换 isTotals * test: 更新 React 包快照 * fix: 修复树状模式下总计节点的指标节点没有被格式化数据 * refactor: 修改代码风格和编码规范 * test: 修改代码风格和编码规范 * test: 修改代码风格和编码规范 * test: 修改代码风格和编码规范 * chore: 更改 interface 命名规范 --------- Co-authored-by: JuZe <niuwenze.nwz@antgroup.com> Co-authored-by: Wenjun Xu <906626481@qq.com> Co-authored-by: Jinke Li <a1231236677287@163.com> * chore: 🤖 更新 changelog 文件 (#2356) * fix(layout): 修复隐藏结点时对父节点的布局计算错误 close #2355 (#2360) * fix(layout): 修复隐藏结点时对父节点的布局计算错误 close #2355 * chore: 增加废弃标记 --------- Co-authored-by: JuZe <niuwenze.nwz@antgroup.com> * docs: 修复 headerActionIcons 错误文档 close #2362 (#2363) * fix(table-sheet): 明细表数据为空时错误的展示一行空数据 close #2255 (#2357) * fix(table-sheet): 明细表数据为空时错误的展示一行空数据 close #2255 * test: 修复测试 * test: 修复测试 * fix: 重命名 --------- Co-authored-by: Wenjun Xu <906626481@qq.com> * fix: 列头绘制多列文本时错误的使用了数值单元格的样式 close #2359 (#2364) * chore: 🤖 更新 changelog 文件 (#2369) * fix: 修复分组汇总时,按汇总排序获取排序数据为空 (#2370) * fix: 修复指标不在最后一级配置与分组汇总同时使用时,获取数据为空 * fix: 修复指标不在最后一级配置与分组汇总同时使用时,获取数据为空 * test: 增加分组汇总时按汇总排序单测 * test: 增加分组汇总时按汇总排序单测 * chore: 优化代码可读,getMultiData 使用对象传参 * chore: 优化代码可读,getMultiData 使用对象传参 * chore: 优化代码可读,getMultiData 使用对象传参 * chore: getMultiData 使用传参格式改回 * chore: 改名为 includeTotalData 参数名以及文档 * docs: 加个空格 --------- Co-authored-by: JuZe <niuwenze.nwz@antgroup.com> * fix: 修复树状角头,当有存在icon时,内容与box宽度恰好相等,出现换行 close #2389 (#2390) * fix: 树状角头,当有两个icon时,内容与box宽度恰好相等,出现换行 * fix: 树状角头,当有两个icon时,内容与box宽度恰好相等,出现换行 close:2389 * fix: 树状角头,当有两个icon时,内容与box宽度恰好相等,出现换行 close:2389 * chore: 增加注释 --------- Co-authored-by: JuZe <niuwenze.nwz@antgroup.com> * fix(interaction): 修复拖动水平滚动条后单元格选中状态被重置 close #2376 (#2380) * fix(layout): 修复在紧凑模式列头宽度未按文本自适应 close #2385 (#2392) * fix(layout): 修复在紧凑模式列头宽度未按文本自适应 close #2385 * chore: 还原配置 * chore: 不按 issue 模版提交自动关闭 (#2393) * docs: 新增自定义明细表单元格 & 自定义 mini 图文档 (#2394) * chore: 🤖 更新 changelog 文件 (#2395) * docs: 更新官网 S2 版本 & 优化文档 (#2397) * docs: 更新官网 S2 版本 & 优化文档 * chore: 移动位置 * fix: 修复趋势分析表自定义列头 tooltip 后错误的使用行头的 tooltip (#2399) * feat: 支持透视表明细表单元格虚线&分割线虚线(#2400) (#2401) * feat: 支持透视表明细表单元格虚线&分割线虚线(#2400) * docs: 修改contributing.md * docs: 补充缺失的 fontStyle 和 fontVariant 文档 (#2407) * chore: 🤖 更新 changelog 文件 (#2408) * fix(interaction): 修复行列头圈选后滑出可视范围后, 错误的选择了数值单元格 close #2340 (#2411) * fix(interaction): 修复行列头圈选后滑出可视范围后, 错误的选择了数值单元格 close #2340 * fix: brush selected * fix(copy): 修复刷选复制行列头时, 数值单元格未格式化 & 存在省略号时未复制原始值 (#2410) fix(copy): 修复刷选复制行列头时, 数值单元格未格式化 * fix: 修复交叉模式下 行序号位置不正确&总计行未添加行序号 (#2412) fix: fix row series number position and height bug Co-authored-by: wuding.why <wuding.why@alibaba-inc.com> * refactor: 抽取明细表冻结行列逻辑,交叉表冻结首行逻辑复用 (#2415) * refactor: extract freeze public logic * fix: lint error fixed * fix: follow variable naming conventions --------- Co-authored-by: wuding.why <wuding.why@alibaba-inc.com> * fix: 修复隐藏列总计时行总计也被隐藏问题 (#2417) * fix: 修复隐藏列总计时行总计也被隐藏了 * fix: add fix bug comment * fix: add test case * fix: optimize test case --------- Co-authored-by: wuding.why <wuding.why@alibaba-inc.com> * chore: update dumi-theme-antv 0.4.0 (#2422) * chore: update dumi-theme-antv 0.4.0 * chore: update dumi-theme-antv 0.4.0 * docs: buttons unify and image position (#2425) * docs: image position change (#2426) * docs: 更新官网主题 (#2427) * docs: 更新官网主题 * chore: update reviewer * docs: 更新官网新闻公告 (#2428) * ci: 部分 GitHub Action CI 脚本升级 (#2434) * fix: 修复维度缺失部分维值时, 行维值以及对应的数值展示错误 (#2436) * fix: 修复缺失维度所生成的 query 多了 empty extra value 字段 (#2444) * fix: 修复中英文标点符号 (#2442) * fix: 修复中英文标点符号 * test: 单测修复 * chore: 🤖 更新 changelog 文件 (#2445) * feat: 交叉表支持冻结首行能力 (#2416) * feat: 交叉表支持冻结首行 * feat: 调整冻结实现逻辑 & CR遗留问题 & 行头部分重构实现 * fix: 修复 CR 系列反馈的问题 * fix: 修复二轮 CR 反馈的问题 --------- Co-authored-by: wuding.why <wuding.why@alibaba-inc.com> * docs: 润色文档细节 & 修复一些错误的文档示例地址 (#2448) * fix(interaction): 修复数值单元格取消选中 & 点击空白处取消选中时没触发 GLOBAL_SELECTED 事件 (#2449) * fix(interaction): 修复数值单元格取消选中 & 点击空白处取消选中时没触发 GLOBAL_SELECTED 事件 close #2447 * test: 修复单测 * refactor: frozenRowCell 重命名为 rowCell (#2450) * refactor: frozenRowCell 重命名为 rowCell * chore: 类型修改 * fix: 修复行头滚动刷选获取到错误的实例 * fix: 修复透视表数据为空时,行列交叉单元格缺失的问题 (#2452) * fix: 区分 GuiIcon 报错类型 close #2345 (#2451) * fix(table-sheet): 修复明细表排序后开启行列冻结, 冻结行展示错误 close #2388 (#2453) * fix(table-sheet): 修复明细表排序后开启行列冻结, 冻结行展示错误 close #2388 * test: 增加明细表 theme 测试 * test: 修复过滤条件单测 * fix: seriesNumberHeader 不应该使用 custom row cell (#2459) * fix(table-sheet): 修复明细表 tooltip 展示了错误的汇总数据的问题 (#2457) * chore: 🤖 更新 changelog 文件 (#2461) * fix(tooltip): 修复字段描述为英文时展示不全 (#2466) * fix(tooltip): 修复字段描述为英文时展示不全 * test: 删除无用快照 * test: 跳过测试 * fix: 修复多列文本单元格 hover 时报错 (#2472) * feat: 在自动发布前, 自动注入新版本到打包代码中 (#2477) * fix: 修复初始化渲染未完成时headNode NPE问题 (#2479) fix: 修复初始化渲染时headNode NPE问题 Co-authored-by: wuding.why <wuding.why@alibaba-inc.com> * feat(perf): 优化 dataset 数据结构转化,以及交互过程中 layout性能 (#2476) * fix: 修复行头字段为空字符串时, getDimensionValues 获取数据错误的问题 (#2482) * chore: 🤖 更新 changelog 文件 (#2481) Co-authored-by: Wenjun Xu <906626481@qq.com> * WIP * chore: 合并单测和文档 * chore: 解决冲突 * chore: 解决冲突 * test: 修复测试 * test: 修复测试 * fix: dataset 层面代码合并 * fix: facet 层面代码合并 * fix: 处理 build hierarchy 合并问题 * fix: 处理 frozen 代码问题 * fix: 处理 row cell 冻结时,滚动问题 * fix: 移除多余的冻结宽度计算 * fix: 处理 row cell 冻结单元格 resize area * fix: 处理 table data cell 冻结单元格 resize area * fix: 处理 table data cell 冻结单元格 clip 缺失 * fix: 处理 grid group 线段尺寸问题 * fix: i处理 frozen group shadow * fix: 修复分页时, rowHeader 裁剪问题 * fix: node 增加 isFrozen 属性 * fix: 处理 resize line 偏移问题 * fix: 修复冻结时, grid group 绘制尺寸问题 * test: 单测修复 * test: 总计小计分组单测修复 * test: dataset 单测修复 * test: hideValue 用例修复 * test: 尺寸问题单测修复 * refactor: remove calculateGridRowNodesWidth * fix: 修复 panelScrollGroup children 层级问题 * refactor: rename test file * test: 修复交互测试 * test: 修复交互测试 * fix: 修复 merge cell border 绘制问题 * test: table-data-set 单测修复 * test: sort 单测修复 * test: 修复 table content height 问题 * fix: 修复 panelScrollGroup clip 问题 * fix: 修复 panelScrollGroup clip 问题 * test: 修复导出和隐藏列头测试 * test: 完善导出和复制测试 * test: 更新快照 * test: 修复 pivot frozen row 单测 * test: 修复 custom-cell-style 单测 * test: 修复 corner 单测 * test: 修复 spread-sheet-facet 单测 * test: 修复 issue bugs 单测 * test: 单测修复 * fix: 处理 corner cell icon 位置问题 * refactor: 处理 empty text 代码 * test: 单测修复 * test: 修复列头隐藏单测 * test: 单测修复, row 带序号时 clip 修复 * test: data-cell 单测修复 * test: 修复 table-facet 错误 * fix: 修复紧凑模式下宽度计算错误 * test: 修复列头隐藏单测 * test: 修复滚动相关单测 * test: theme 单测修复 * style: lint fix * test: 修复 pivot-facet 单测 * test: 修复剩余单测 * fix: 修复明细表序号列对齐 * fix: 修复所有类型问题 * test: 修复 s2-react 下所有 单测 * fix: 修复组件层问题 * fix(table-sheet): 修复明细表配置自定义行高后展示异常 close #2501 (#2521) * fix: 增加树状模式自定义宽度的容错 (#2519) --------- Co-authored-by: Jinke Li <a1231236677287@163.com> Co-authored-by: 刘嘉一 <lcx.seima@gmail.com> Co-authored-by: 沫君 <chuxiao.lcx@alibaba-inc.com> Co-authored-by: stone <stone-lyl@users.noreply.github.com> Co-authored-by: zishang <lyl275911@antgroup.com> Co-authored-by: Tyler Roi <iherewithmyheart@gmail.com> Co-authored-by: Bran <1055025755@qq.com> Co-authored-by: 卿珂 <lijinke.ljk@antgroup.com> Co-authored-by: 沫君 <chuxiao.lcx@antgroup.com> Co-authored-by: serializedowen <wjh199455@gmail.com> Co-authored-by: owen.wjh <owen.wjh@alibaba-inc.com> Co-authored-by: Hemisu <hekunyu@me.com> Co-authored-by: hekunyu <hekunyu.latte@bytedance.com> Co-authored-by: LUUUAN <48037246+LUUUAN@users.noreply.github.com> Co-authored-by: 晓峦 <luanwendi.lwd@alibaba-inc.com> Co-authored-by: huiyu.zjt <Alexzjt@users.noreply.github.com> Co-authored-by: huiyu.zjt <huiyu.zjt@antgroup.com> Co-authored-by: NoobNot <56724970+NoobNotN@users.noreply.github.com> Co-authored-by: JuZe <niuwenze.nwz@antgroup.com> Co-authored-by: Aimer <44153856+aimerthyr@users.noreply.github.com> Co-authored-by: wuhaiyang <wuhaiyang@andthink.cn> Co-authored-by: wuding.why <wuding.why@alibaba-inc.com> Co-authored-by: Frank William <65594180+ai-qing-hai@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/bug-report.md | 2 +- .github/ISSUE_TEMPLATE/feature-request.md | 33 + .github/workflows/auto-release.yml | 8 +- .github/workflows/codeql-analysis.yml | 10 +- .github/workflows/compressed-size.yml | 6 +- .github/workflows/disscustion.yml | 4 +- .github/workflows/issue-labeled.yml | 24 +- .github/workflows/issue-opend.yml | 6 +- .github/workflows/lint.yml | 11 +- .github/workflows/pr-auto-assign-reviewer.yml | 2 +- .github/workflows/prerelease-notify.yml | 2 +- .github/workflows/release-notify.yml | 4 +- .github/workflows/release-success.yml | 17 +- .github/workflows/site-build-notify.yml | 6 +- .github/workflows/sync-notify.yml | 2 +- .github/workflows/test.yml | 9 +- .releaserc.base.js | 16 +- CONTRIBUTING.md | 2 +- LICENSE | 2 +- README.en-US.md | 9 +- README.md | 9 +- package.json | 4 +- packages/s2-core/CHANGELOG.md | 39 +- packages/s2-core/README.md | 5 +- .../__snapshots__/issue-2359-spec.ts.snap | 67 + .../bugs/__snapshots__/issue-565-spec.ts.snap | 13 + .../s2-core/__tests__/bugs/issue-1191-spec.ts | 22 +- .../s2-core/__tests__/bugs/issue-1201-spec.ts | 46 +- .../s2-core/__tests__/bugs/issue-1561-spec.ts | 17 +- .../s2-core/__tests__/bugs/issue-1587-spec.ts | 3 +- .../s2-core/__tests__/bugs/issue-1624-spec.ts | 51 + .../s2-core/__tests__/bugs/issue-1715-spec.ts | 8 +- .../s2-core/__tests__/bugs/issue-1781-spec.ts | 21 +- .../s2-core/__tests__/bugs/issue-2164-spec.ts | 7 +- .../s2-core/__tests__/bugs/issue-2195-spec.ts | 74 + .../s2-core/__tests__/bugs/issue-2199-spec.ts | 41 + .../s2-core/__tests__/bugs/issue-2322-spec.ts | 50 + .../s2-core/__tests__/bugs/issue-2340-spec.ts | 80 + .../s2-core/__tests__/bugs/issue-2359-spec.ts | 66 + .../s2-core/__tests__/bugs/issue-2385-spec.ts | 62 + .../s2-core/__tests__/bugs/issue-2501-spec.ts | 63 + .../s2-core/__tests__/bugs/issue-446-spec.ts | 6 +- .../s2-core/__tests__/bugs/issue-565-spec.ts | 22 +- .../s2-core/__tests__/bugs/issue-725-spec.ts | 29 +- .../__tests__/data/data-custom-trees.ts | 22 + .../__tests__/data/data-issue-2199.json | 126 ++ .../__tests__/data/data-issue-2322.json | 132 ++ .../__tests__/data/data-issue-2385.json | 138 ++ .../__tests__/data/data-issue-292.json | 7 +- .../__tests__/data/data-issue-725.json | 5 - .../s2-core/__tests__/data/sort-advanced.ts | 1 + .../__tests__/data/total-group-data.ts | 201 ++ .../__snapshots__/corner-spec.ts.snap | 76 +- .../custom-cell-style-spec.ts.snap | 166 +- .../__snapshots__/custom-grid-spec.ts.snap | 42 +- .../custom-table-col-spec.ts.snap | 26 +- .../__snapshots__/hidden-columns-spec.ts.snap | 30 + .../multi-line-text-spec.ts.snap | 652 +++---- .../spread-sheet-resize-spec.ts.snap | 21 + .../__snapshots__/theme-spec.ts.snap | 801 ++++++-- .../__tests__/spreadsheet/corner-spec.ts | 22 +- .../spreadsheet/custom-cell-style-spec.ts | 31 +- .../__tests__/spreadsheet/custom-grid-spec.ts | 9 +- .../__tests__/spreadsheet/custom-tree-spec.ts | 13 +- .../spreadsheet/empty-dataset-spec.ts | 69 + .../spreadsheet/empty-string-values-spec.ts | 124 ++ .../spreadsheet/header-action-icons-spec.ts | 12 +- .../spreadsheet/hidden-columns-spec.ts | 158 +- ...interaction-brush-selection-scroll-spec.ts | 3 +- .../spreadsheet/miss-dimension-values-spec.ts | 269 +++ .../__tests__/spreadsheet/row-link-spec.ts | 21 +- .../__tests__/spreadsheet/scroll-spec.ts | 195 +- .../spreadsheet/sort-by-order-spec.ts | 8 +- .../spread-sheet-facet-layout-api-spec.ts | 50 +- .../spreadsheet/spread-sheet-resize-spec.ts | 40 +- .../spread-sheet-series-number-spec.ts | 24 +- .../spreadsheet/spread-sheet-totals-spec.ts | 35 +- .../spread-sheet-tree-mode-spec.ts | 43 +- .../__tests__/spreadsheet/table-sheet-spec.ts | 19 +- .../__tests__/spreadsheet/theme-spec.ts | 34 +- .../__tests__/spreadsheet/total-group-spec.ts | 332 ++++ .../__tests__/unit/cell/col-cell-spec.ts | 32 +- .../__tests__/unit/cell/corner-cell-spec.ts | 1 - .../unit/cell/custom-tree-corner-cell-spec.ts | 17 +- .../__tests__/unit/cell/data-cell-spec.ts | 48 +- .../__tests__/unit/cell/header-cell-spec.ts | 64 + .../__tests__/unit/cell/row-cell-spec.ts | 71 +- .../__tests__/unit/common/i18n/index-spec.ts | 6 +- .../unit/data-process/pivot-spec.tsx | 83 +- .../pivot-data-set-total-spec.ts.snap | 133 ++ .../__snapshots__/table-data-set-spec.ts.snap | 1191 ++++++++++++ .../data-set/custom-tree-data-set-spec.ts | 41 +- .../data-set/pivot-data-set-row-value-spec.ts | 123 +- .../unit/data-set/pivot-data-set-spec.ts | 100 +- .../data-set/pivot-data-set-total-spec.ts | 505 +++-- .../unit/data-set/table-data-set-spec.ts | 109 +- .../__snapshots__/table-dataset-spec.ts.snap | 459 +++++ .../unit/dataset/table-dataset-spec.ts | 139 +- .../__snapshots__/table-facet-spec.ts.snap | 145 ++ ...cornerBBox-spec.ts => corner-bbox-spec.ts} | 2 +- .../{panelBBox-spec.ts => panel-bbox-spec.ts} | 2 +- .../unit/facet/header/frozen-row-spec.ts | 51 + .../unit/facet/layout/col-node-width-spec.ts | 12 +- .../unit/facet/layout/row-node-width-spec.ts | 6 +- .../__tests__/unit/facet/pivot-facet-spec.ts | 127 +- .../__tests__/unit/facet/table-facet-spec.ts | 194 +- packages/s2-core/__tests__/unit/facet/util.ts | 19 +- .../click/data-cell-click-spec.ts | 88 +- .../click/row-column-click-spec.ts | 45 +- .../base-interaction/hover-spec.ts | 44 +- .../base-brush-selection-spec.ts | 8 + .../col-brush-selection-spec.ts | 36 +- .../data-brush-selection-spec.ts | 60 +- .../row-brush-selection-spec.ts | 34 +- .../data-cell-multi-selection-spec.ts | 18 +- .../unit/interaction/event-controller-spec.ts | 14 +- .../unit/interaction/range-selection-spec.ts | 35 + .../__tests__/unit/interaction/root-spec.ts | 57 +- .../interaction/row-column-resize-spec.ts | 10 + .../unit/sheet-type/pivot-sheet-spec.ts | 101 +- .../unit/sheet-type/table-sheet-spec.ts | 17 +- .../__snapshots__/sort-action-spec.tsx.snap | 22 + .../unit/utils/data-set-operate-spec.tsx | 18 +- .../unit/utils/dataset/pivot-data-set-spec.ts | 803 ++++++-- .../export/__snapshots__/copy-spec.ts.snap | 162 ++ .../__snapshots__/export-pivot-spec.ts.snap | 13 + .../export/__snapshots__/export-spec.ts.snap | 360 ++++ .../__tests__/unit/utils/export/copy-spec.ts | 297 ++- .../unit/utils/export/export-pivot-spec.ts | 96 +- .../unit/utils/export/export-spec.ts | 473 +++++ .../unit/utils/export/export-table-spec.ts | 15 +- .../export/{index-spec.ts => utils-spec.ts} | 4 +- .../__tests__/unit/utils/facet-spec.ts | 20 - .../unit/utils/g-mini-charts-spec.ts | 7 +- .../__tests__/unit/utils/hide-columns-spec.ts | 425 +++++ .../utils/interaction/hover-event-spec.ts | 4 +- .../unit/utils/interaction/resize-spec.ts | 2 +- .../interaction/state-controller-spec.ts | 4 +- .../__tests__/unit/utils/merge-cell-spec.ts | 9 +- .../__tests__/unit/utils/sort-action-spec.tsx | 78 +- .../s2-core/__tests__/unit/utils/text-spec.ts | 69 + .../__tests__/unit/utils/tooltip-spec.ts | 157 +- packages/s2-core/__tests__/util/helpers.ts | 126 +- packages/s2-core/__tests__/util/index.ts | 50 +- packages/s2-core/package.json | 10 +- packages/s2-core/src/cell/base-cell.ts | 62 +- packages/s2-core/src/cell/col-cell.ts | 102 +- packages/s2-core/src/cell/corner-cell.ts | 110 +- packages/s2-core/src/cell/data-cell.ts | 63 +- packages/s2-core/src/cell/header-cell.ts | 40 +- packages/s2-core/src/cell/index.ts | 4 +- packages/s2-core/src/cell/merged-cell.ts | 70 +- packages/s2-core/src/cell/row-cell.ts | 50 +- packages/s2-core/src/cell/table-col-cell.ts | 51 +- packages/s2-core/src/cell/table-data-cell.ts | 37 +- .../src/cell/table-series-number-cell.ts | 2 +- packages/s2-core/src/common/constant/basic.ts | 31 +- packages/s2-core/src/common/constant/copy.ts | 1 + packages/s2-core/src/common/constant/field.ts | 12 + packages/s2-core/src/common/constant/index.ts | 2 + .../src/common/constant/interaction.ts | 1 + packages/s2-core/src/common/constant/node.ts | 1 - .../s2-core/src/common/constant/options.ts | 12 +- packages/s2-core/src/common/constant/query.ts | 6 + packages/s2-core/src/common/constant/total.ts | 23 - packages/s2-core/src/common/i18n/en_US.ts | 3 +- packages/s2-core/src/common/i18n/zh_CN.ts | 3 +- packages/s2-core/src/common/icons/gui-icon.ts | 21 +- .../s2-core/src/common/interface/basic.ts | 28 +- .../s2-core/src/common/interface/emitter.ts | 18 +- .../interface/export.ts} | 4 +- .../s2-core/src/common/interface/index.ts | 2 + .../src/common/interface/interaction.ts | 37 +- .../s2-core/src/common/interface/resize.ts | 2 + .../src/common/interface/s2DataConfig.ts | 10 +- .../s2-core/src/common/interface/s2Options.ts | 14 +- .../s2-core/src/common/interface/store.ts | 2 +- .../s2-core/src/common/interface/style.ts | 2 +- .../s2-core/src/common/interface/theme.ts | 5 + .../s2-core/src/common/interface/tooltip.ts | 4 +- .../s2-core/src/common/interface/utils.ts | 3 + .../s2-core/src/data-set/base-data-set.ts | 42 +- packages/s2-core/src/data-set/cell-data.ts | 38 +- .../data-set/custom-grid-pivot-data-set.ts | 2 +- .../data-set/custom-tree-pivot-data-set.ts | 19 +- packages/s2-core/src/data-set/index.ts | 8 +- packages/s2-core/src/data-set/interface.ts | 98 +- .../s2-core/src/data-set/pivot-data-set.ts | 453 +++-- .../s2-core/src/data-set/table-data-set.ts | 111 +- .../README-adjustTotalNodesCoordinate.md | 57 + packages/s2-core/src/facet/base-facet.ts | 229 ++- .../facet/bbox/{baseBBox.ts => base-bbox.ts} | 0 .../bbox/{cornerBBox.ts => corner-bbox.ts} | 9 +- .../bbox/{panelBBox.ts => panel-bbox.ts} | 11 +- packages/s2-core/src/facet/frozen-facet.ts | 775 ++++++++ packages/s2-core/src/facet/header/col.ts | 31 +- packages/s2-core/src/facet/header/corner.ts | 10 +- packages/s2-core/src/facet/header/frame.ts | 4 +- packages/s2-core/src/facet/header/index.ts | 1 + packages/s2-core/src/facet/header/row.ts | 155 +- .../s2-core/src/facet/header/series-number.ts | 9 +- .../s2-core/src/facet/header/table-col.ts | 85 +- packages/s2-core/src/facet/header/util.ts | 2 + .../src/facet/layout/build-gird-hierarchy.ts | 185 +- .../facet/layout/build-row-tree-hierarchy.ts | 54 +- .../s2-core/src/facet/layout/interface.ts | 17 +- .../s2-core/src/facet/layout/layout-hooks.ts | 8 +- packages/s2-core/src/facet/layout/node.ts | 7 + .../s2-core/src/facet/layout/total-class.ts | 22 +- packages/s2-core/src/facet/pivot-facet.ts | 538 +++--- packages/s2-core/src/facet/table-facet.ts | 923 +-------- packages/s2-core/src/facet/utils.ts | 113 +- packages/s2-core/src/group/grid-group.ts | 42 +- .../s2-core/src/group/panel-scroll-group.ts | 9 +- .../base-interaction/click/data-cell-click.ts | 60 +- .../click/row-column-click.ts | 31 +- .../base-interaction/click/row-text-click.ts | 9 +- .../src/interaction/base-interaction/hover.ts | 37 +- .../brush-selection/base-brush-selection.ts | 24 +- .../brush-selection/col-brush-selection.ts | 8 +- .../data-cell-brush-selection.ts | 7 +- .../brush-selection/row-brush-selection.ts | 16 +- .../interaction/data-cell-multi-selection.ts | 15 + .../src/interaction/event-controller.ts | 81 +- .../src/interaction/range-selection.ts | 26 +- packages/s2-core/src/interaction/root.ts | 82 +- .../src/interaction/row-column-resize.ts | 32 +- .../src/interaction/selected-cell-move.ts | 9 +- .../s2-core/src/sheet-type/pivot-sheet.ts | 12 +- .../s2-core/src/sheet-type/spread-sheet.ts | 36 +- packages/s2-core/src/theme/index.ts | 48 +- packages/s2-core/src/utils/cell/cell.ts | 12 +- .../s2-core/src/utils/data-set-operate.ts | 145 +- .../src/utils/dataset/pivot-data-set.ts | 542 ++++-- .../utils/export/copy/base-data-cell-copy.ts | 4 +- .../s2-core/src/utils/export/copy/common.ts | 2 +- .../s2-core/src/utils/export/copy/core.ts | 7 +- .../s2-core/src/utils/export/copy/index.ts | 2 +- .../utils/export/copy/pivot-data-cell-copy.ts | 5 +- .../utils/export/copy/pivot-header-copy.ts | 2 +- .../src/utils/export/copy/table-copy.ts | 2 +- packages/s2-core/src/utils/export/index.ts | 130 +- packages/s2-core/src/utils/export/utils.ts | 137 ++ packages/s2-core/src/utils/hide-columns.ts | 11 +- packages/s2-core/src/utils/index.ts | 4 +- .../src/utils/interaction/merge-cell.ts | 78 +- .../src/utils/interaction/select-event.ts | 4 + .../s2-core/src/utils/layout/add-totals.ts | 14 +- packages/s2-core/src/utils/layout/frozen.ts | 31 - .../src/utils/layout/generate-header-nodes.ts | 56 +- .../s2-core/src/utils/layout/generate-id.ts | 10 +- .../layout/get-dims-condition-by-node.ts | 10 +- .../src/utils/layout/whether-leaf-by-level.ts | 18 + packages/s2-core/src/utils/math.ts | 5 + packages/s2-core/src/utils/merge.ts | 6 +- .../s2-core/src/utils/number-calculate.ts | 6 +- packages/s2-core/src/utils/sort-action.ts | 88 +- packages/s2-core/src/utils/text.ts | 41 +- packages/s2-core/src/utils/theme.ts | 2 +- packages/s2-core/src/utils/tooltip.ts | 24 +- packages/s2-react/CHANGELOG.md | 90 +- packages/s2-react/README.md | 9 +- .../__tests__/spreadsheet/drill-down-spec.tsx | 21 +- .../spreadsheet/filter-sheet-spec.tsx | 177 +- .../__tests__/spreadsheet/pagination-spec.tsx | 42 +- .../__snapshots__/index-spec.tsx.snap | 30 + .../custom-tooltip/index-spec.tsx | 24 + .../sheets/strategy-sheet/index-spec.tsx | 111 +- .../tooltip/__snapshots__/index-spec.tsx.snap | 8 +- .../unit/components/tooltip/index-spec.tsx | 15 +- .../__tests__/unit/hooks/useEvents-spec.ts | 6 +- packages/s2-react/__tests__/util/helpers.ts | 3 +- packages/s2-react/package.json | 10 +- .../components/GridAnalysisSheet.tsx | 3 +- packages/s2-react/playground/config.tsx | 44 +- packages/s2-react/playground/drill-down.tsx | 7 +- packages/s2-react/playground/index.html | 3 +- packages/s2-react/playground/index.tsx | 86 +- packages/s2-react/playground/utils.ts | 3 + .../src/components/advanced-sort/index.tsx | 10 +- .../s2-react/src/components/export/index.tsx | 8 +- .../src/components/export/strategy-copy.ts | 16 +- .../custom-cell/edit-cell/index.tsx | 38 +- .../sheets/editable-sheet/drag-copy/index.tsx | 5 +- .../sheets/editable-sheet/index.tsx | 5 +- .../sheets/strategy-sheet/custom-data-set.ts | 19 +- .../custom-tooltip/data-cell-tooltip.tsx | 19 +- .../strategy-sheet/custom-tooltip/index.less | 4 + .../custom-tooltip/row-cell-tooltip.tsx | 5 +- .../sheets/strategy-sheet/index.tsx | 9 +- .../components/switcher/dimension/index.tsx | 25 + .../tooltip/components/description.tsx | 3 +- .../tooltip/components/head-info.tsx | 7 +- .../components/tooltip/components/summary.tsx | 3 +- packages/s2-react/src/hooks/useEvents.ts | 4 +- .../s2-shared/__tests__/utils/options-spec.ts | 6 +- packages/s2-shared/src/constant/i18n/en_US.ts | 2 +- packages/s2-shared/src/constant/i18n/zh_CN.ts | 2 +- packages/s2-shared/src/interface.ts | 28 +- packages/s2-shared/src/utils/resize.ts | 5 +- packages/s2-vue/CHANGELOG.md | 8 +- packages/s2-vue/package.json | 10 +- packages/s2-vue/playground/App.vue | 32 +- pnpm-lock.yaml | 1654 +++++++++++++++-- s2-site/.dumirc.ts | 83 +- .../docs/api/basic-class/base-data-set.en.md | 53 +- .../docs/api/basic-class/base-data-set.zh.md | 50 +- .../docs/api/basic-class/spreadsheet.zh.md | 2 +- s2-site/docs/api/components/drill-down.zh.md | 2 +- .../docs/api/components/sheet-component.zh.md | 11 +- s2-site/docs/api/general/S2Event.zh.md | 1 + s2-site/docs/api/general/S2Options.zh.md | 4 +- s2-site/docs/api/general/S2Theme.en.md | 2 + s2-site/docs/api/general/S2Theme.zh.md | 43 +- s2-site/docs/api/graphic.zh.md | 2 + s2-site/docs/common/contact-us.en.md | 6 +- s2-site/docs/common/contact-us.zh.md | 3 - s2-site/docs/common/development.zh.md | 10 +- s2-site/docs/common/export.zh.md | 16 +- s2-site/docs/common/header-action-icon.zh.md | 6 +- s2-site/docs/common/icon.zh.md | 10 +- s2-site/docs/common/interaction.zh.md | 2 +- s2-site/docs/common/packages.zh.md | 15 + s2-site/docs/common/totals.zh.md | 2 + .../docs/manual/advanced/custom/hook.zh.md | 10 +- .../manual/advanced/data-process/pivot.zh.md | 233 +-- .../docs/manual/advanced/get-cell-data.zh.md | 24 +- .../manual/advanced/interaction/basic.en.md | 14 +- .../manual/advanced/interaction/basic.zh.md | 19 +- .../manual/basic/analysis/copy-export.zh.md | 81 +- .../manual/basic/analysis/drill-down.en.md | 8 +- .../manual/basic/analysis/drill-down.zh.md | 21 +- .../manual/basic/analysis/editable-mode.zh.md | 26 +- .../basic/analysis/mobile-component.zh.md | 2 +- .../manual/basic/analysis/pagination.en.md | 11 + .../manual/basic/analysis/pagination.zh.md | 15 +- .../docs/manual/basic/analysis/strategy.zh.md | 31 +- .../docs/manual/basic/analysis/switcher.zh.md | 11 +- .../manual/basic/sheet-type/pivot-mode.zh.md | 74 +- .../manual/basic/sheet-type/table-mode.zh.md | 11 +- s2-site/docs/manual/basic/sort/advanced.zh.md | 48 +- s2-site/docs/manual/basic/theme.zh.md | 12 +- s2-site/docs/manual/basic/tooltip.zh.md | 21 + s2-site/docs/manual/basic/totals.zh.md | 110 +- s2-site/docs/manual/faq.zh.md | 109 +- s2-site/docs/manual/getting-started.zh.md | 48 +- s2-site/docs/manual/introduction.zh.md | 7 +- s2-site/examples/analysis/get-data/API.en.md | 6 + s2-site/examples/analysis/get-data/API.zh.md | 6 + .../analysis/get-data/demo/get-cell-data.ts | 91 + .../analysis/get-data/demo/get-multi-data.ts | 93 + .../examples/analysis/get-data/demo/meta.json | 24 + .../examples/analysis/get-data/index.en.md | 5 + .../examples/analysis/get-data/index.zh.md | 6 + .../examples/analysis/sort/demo/advanced.tsx | 2 +- .../analysis/sort/demo/custom-list.ts | 2 +- .../analysis/sort/demo/custom-measure.ts | 2 +- .../analysis/sort/demo/custom-method.ts | 2 +- .../analysis/sort/demo/custom-sort-func.ts | 2 +- .../analysis/sort/demo/group-sort.tsx | 2 +- .../analysis/totals/demo/calculate.ts | 2 +- .../examples/analysis/totals/demo/custom.ts | 2 +- .../totals/demo/dimension-group-col.ts | 68 + .../totals/demo/dimension-group-row.ts | 67 + .../examples/analysis/totals/demo/meta.json | 16 + .../analysis/totals/demo/multiple-values.ts | 8 +- s2-site/examples/basic/pivot/demo/grid.ts | 3 + s2-site/examples/basic/pivot/demo/tree.ts | 12 + s2-site/examples/basic/table/demo/table.ts | 1 + s2-site/examples/case/art/demo/lost-text.tsx | 298 +++ s2-site/examples/case/art/demo/meta.json | 10 +- .../comparison/demo/measure-comparison.tsx | 4 +- .../custom/custom-cell/demo/corner-header.ts | 5 +- .../custom-cell/demo/custom-specified-cell.ts | 95 +- .../custom-cell/demo/custom-table-cell.ts | 139 ++ .../custom-cell/demo/data-cell-placeholder.ts | 42 + .../custom/custom-cell/demo/data-cell.ts | 6 +- .../custom/custom-cell/demo/meta.json | 26 +- .../custom/custom-cell/demo/mini-chart.ts | 178 ++ .../custom/custom-cell/demo/totals-cell.ts | 2 +- .../custom-layout/demo/custom-coordinate.ts | 22 + .../demo/custom-data-position.ts | 22 + .../demo/custom-layout-arrange.ts | 22 + .../demo/custom-layout-hierarchy.ts | 22 + .../custom-layout/demo/custom-value-order.ts | 22 + .../custom/custom-tree/demo/custom-tree.ts | 2 +- .../advanced/demo/custom-tree-link-jump.ts | 2 +- .../advanced/demo/frozen-pivot-grid.ts | 24 + .../advanced/demo/frozen-pivot-tree.ts | 19 + .../interaction/advanced/demo/meta.json | 16 + .../advanced/demo/scroll-speed-ratio.ts | 2 +- .../basic/demo/frozen-row-header.ts | 38 + .../basic/demo/hover-after-scroll.ts | 31 + .../examples/interaction/basic/demo/hover.ts | 7 + .../examples/interaction/basic/demo/meta.json | 28 +- .../interaction/basic/demo/state-theme.ts | 118 ++ .../custom/demo/double-click-hide-columns.ts | 2 +- .../layout/adaptive/demo/react-adaptive.tsx | 12 +- .../examples/layout/basic/demo/adaptive.ts | 21 + .../examples/layout/basic/demo/colAdaptive.ts | 1 + s2-site/examples/layout/basic/demo/compact.ts | 18 +- s2-site/examples/layout/basic/demo/meta.json | 10 +- .../layout/custom/demo/custom-table-size.ts | 2 +- .../layout/custom/demo/hide-columns.ts | 2 +- .../examples/layout/custom/demo/hide-value.ts | 2 +- .../custom/demo/only-show-row-header.ts | 2 +- .../{drill-dwon => drill-down}/API.en.md | 0 .../{drill-dwon => drill-down}/API.zh.md | 0 .../demo/basic-panel.tsx | 0 .../demo/for-pivot.tsx | 0 .../{drill-dwon => drill-down}/demo/meta.json | 0 .../{drill-dwon => drill-down}/index.en.md | 0 .../{drill-dwon => drill-down}/index.zh.md | 0 .../react-component/pagination/demo/table.tsx | 8 +- .../react-component/sheet/demo/editable.tsx | 5 + .../sheet/demo/strategy-mini-chart.tsx | 5 + .../switcher/demo/pivot-header.tsx | 2 +- .../switcher/demo/pivot-with-children.tsx | 2 +- .../react-component/switcher/demo/pivot.tsx | 2 +- .../demo/custom-click-show-tooltip.tsx | 49 +- .../tooltip/demo/custom-content.tsx | 51 +- .../tooltip/demo/custom-description.tsx | 2 +- .../demo/custom-hover-show-tooltip.tsx | 26 + .../theme/custom/demo/custom-palette.ts | 2 +- .../theme/custom/demo/custom-schema.ts | 2 +- .../demo/custom-transparent-background.ts | 2 +- .../examples/theme/default/demo/colorful.ts | 2 +- .../examples/theme/default/demo/default.ts | 2 +- s2-site/examples/theme/default/demo/gray.ts | 2 +- s2-site/playground/dataset/mock-dataset.json | 374 +--- s2-site/playground/sheet-component/config.ts | 8 +- s2-site/public/site.css | 4 + scripts/add-version.js | 42 + 433 files changed, 19219 insertions(+), 6201 deletions(-) create mode 100644 packages/s2-core/__tests__/bugs/__snapshots__/issue-2359-spec.ts.snap create mode 100644 packages/s2-core/__tests__/bugs/__snapshots__/issue-565-spec.ts.snap create mode 100644 packages/s2-core/__tests__/bugs/issue-1624-spec.ts create mode 100644 packages/s2-core/__tests__/bugs/issue-2195-spec.ts create mode 100644 packages/s2-core/__tests__/bugs/issue-2199-spec.ts create mode 100644 packages/s2-core/__tests__/bugs/issue-2322-spec.ts create mode 100644 packages/s2-core/__tests__/bugs/issue-2340-spec.ts create mode 100644 packages/s2-core/__tests__/bugs/issue-2359-spec.ts create mode 100644 packages/s2-core/__tests__/bugs/issue-2385-spec.ts create mode 100644 packages/s2-core/__tests__/bugs/issue-2501-spec.ts create mode 100644 packages/s2-core/__tests__/data/data-custom-trees.ts create mode 100644 packages/s2-core/__tests__/data/data-issue-2199.json create mode 100644 packages/s2-core/__tests__/data/data-issue-2322.json create mode 100644 packages/s2-core/__tests__/data/data-issue-2385.json create mode 100644 packages/s2-core/__tests__/data/total-group-data.ts create mode 100644 packages/s2-core/__tests__/spreadsheet/__snapshots__/hidden-columns-spec.ts.snap create mode 100644 packages/s2-core/__tests__/spreadsheet/__snapshots__/spread-sheet-resize-spec.ts.snap create mode 100644 packages/s2-core/__tests__/spreadsheet/empty-dataset-spec.ts create mode 100644 packages/s2-core/__tests__/spreadsheet/empty-string-values-spec.ts create mode 100644 packages/s2-core/__tests__/spreadsheet/miss-dimension-values-spec.ts create mode 100644 packages/s2-core/__tests__/spreadsheet/total-group-spec.ts create mode 100644 packages/s2-core/__tests__/unit/data-set/__snapshots__/pivot-data-set-total-spec.ts.snap create mode 100644 packages/s2-core/__tests__/unit/data-set/__snapshots__/table-data-set-spec.ts.snap create mode 100644 packages/s2-core/__tests__/unit/dataset/__snapshots__/table-dataset-spec.ts.snap create mode 100644 packages/s2-core/__tests__/unit/facet/__snapshots__/table-facet-spec.ts.snap rename packages/s2-core/__tests__/unit/facet/bbox/{cornerBBox-spec.ts => corner-bbox-spec.ts} (98%) rename packages/s2-core/__tests__/unit/facet/bbox/{panelBBox-spec.ts => panel-bbox-spec.ts} (98%) create mode 100644 packages/s2-core/__tests__/unit/facet/header/frozen-row-spec.ts create mode 100644 packages/s2-core/__tests__/unit/utils/__snapshots__/sort-action-spec.tsx.snap create mode 100644 packages/s2-core/__tests__/unit/utils/export/__snapshots__/copy-spec.ts.snap create mode 100644 packages/s2-core/__tests__/unit/utils/export/__snapshots__/export-pivot-spec.ts.snap create mode 100644 packages/s2-core/__tests__/unit/utils/export/__snapshots__/export-spec.ts.snap create mode 100644 packages/s2-core/__tests__/unit/utils/export/export-spec.ts rename packages/s2-core/__tests__/unit/utils/export/{index-spec.ts => utils-spec.ts} (94%) create mode 100644 packages/s2-core/src/common/constant/field.ts create mode 100644 packages/s2-core/src/common/constant/query.ts delete mode 100644 packages/s2-core/src/common/constant/total.ts rename packages/s2-core/src/{utils/export/interface.ts => common/interface/export.ts} (94%) create mode 100644 packages/s2-core/src/common/interface/utils.ts create mode 100644 packages/s2-core/src/facet/README-adjustTotalNodesCoordinate.md rename packages/s2-core/src/facet/bbox/{baseBBox.ts => base-bbox.ts} (100%) rename packages/s2-core/src/facet/bbox/{cornerBBox.ts => corner-bbox.ts} (93%) rename packages/s2-core/src/facet/bbox/{panelBBox.ts => panel-bbox.ts} (85%) create mode 100644 packages/s2-core/src/facet/frozen-facet.ts create mode 100644 packages/s2-core/src/utils/export/utils.ts create mode 100644 packages/s2-core/src/utils/layout/whether-leaf-by-level.ts create mode 100644 packages/s2-core/src/utils/math.ts create mode 100644 s2-site/examples/analysis/get-data/API.en.md create mode 100644 s2-site/examples/analysis/get-data/API.zh.md create mode 100644 s2-site/examples/analysis/get-data/demo/get-cell-data.ts create mode 100644 s2-site/examples/analysis/get-data/demo/get-multi-data.ts create mode 100644 s2-site/examples/analysis/get-data/demo/meta.json create mode 100644 s2-site/examples/analysis/get-data/index.en.md create mode 100644 s2-site/examples/analysis/get-data/index.zh.md create mode 100644 s2-site/examples/analysis/totals/demo/dimension-group-col.ts create mode 100644 s2-site/examples/analysis/totals/demo/dimension-group-row.ts create mode 100644 s2-site/examples/case/art/demo/lost-text.tsx create mode 100644 s2-site/examples/custom/custom-cell/demo/custom-table-cell.ts create mode 100644 s2-site/examples/custom/custom-cell/demo/data-cell-placeholder.ts create mode 100644 s2-site/examples/custom/custom-cell/demo/mini-chart.ts create mode 100644 s2-site/examples/interaction/advanced/demo/frozen-pivot-grid.ts create mode 100644 s2-site/examples/interaction/advanced/demo/frozen-pivot-tree.ts create mode 100644 s2-site/examples/interaction/basic/demo/frozen-row-header.ts create mode 100644 s2-site/examples/interaction/basic/demo/hover-after-scroll.ts create mode 100644 s2-site/examples/interaction/basic/demo/state-theme.ts create mode 100644 s2-site/examples/layout/basic/demo/adaptive.ts rename s2-site/examples/react-component/{drill-dwon => drill-down}/API.en.md (100%) rename s2-site/examples/react-component/{drill-dwon => drill-down}/API.zh.md (100%) rename s2-site/examples/react-component/{drill-dwon => drill-down}/demo/basic-panel.tsx (100%) rename s2-site/examples/react-component/{drill-dwon => drill-down}/demo/for-pivot.tsx (100%) rename s2-site/examples/react-component/{drill-dwon => drill-down}/demo/meta.json (100%) rename s2-site/examples/react-component/{drill-dwon => drill-down}/index.en.md (100%) rename s2-site/examples/react-component/{drill-dwon => drill-down}/index.zh.md (100%) create mode 100644 scripts/add-version.js diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 14a0fcaa81..916dadda53 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -49,7 +49,7 @@ eg. <!-- Required! --> <!-- eg. `s2Options` and `s2DataCfg`, or `<SheetComponent {...} />` --> -<!-- 请粘贴你的核心代码片段,包括但不限于 `报错信息`, `s2Options` 等,请不要粘贴你自己的业务代码,请注意使用 markdown code 标签 --> +<!-- 请粘贴你的核心代码片段 (文本形式,而不是图片),包括但不限于 `报错信息`, `s2Options` 等,请不要粘贴你自己的业务代码,请注意使用 markdown code 标签 --> <!-- 如果你使用官网的在线示例,编辑代码后复现了 Bug,请粘贴你的代码,而不是直接贴一个在线示例的链接,没有任何意义,它不会保存你刚写的代码 --> ### 🔗 Reproduce Link diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index bc72b546fd..518d3e947a 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -8,6 +8,39 @@ labels: 🙏feature request <!-- ⚠️ Please fill in the template strictly, otherwise it will be closed directly (请严格按照模板填写,否则直接关闭) --> <!-- ⚠️ 请严格按照模板填写,否则直接关闭 --> +### 🏷 Version + +<!-- Required! --> +<!-- 请填写你正在使用的版本 --> +<!-- 请不要写 🙅🏻♀️🚫 `latest`, `1.x` --> +<!-- +eg. + +| Package | Version | +| -------------- | ------- | +| @antv/s2 | 1.2.0 | +| @antv/s2-react | 1.3.3 | +| @antv/s2-vue | - | + +--> + +| Package | Version | +| -------------- | ------- | +| @antv/s2 | | +| @antv/s2-react | | +| @antv/s2-vue | | + +### Sheet Type + +<!-- Required! --> +<!-- 请填写你具体使用的表格类型 --> + +- [ ] PivotSheet +- [ ] TableSheet +- [ ] GridAnalysisSheet +- [ ] StrategySheet +- [ ] EditableSheet + ### 🖋 Description <!-- Required! --> diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index 6e3945cfa1..1f7d825c29 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -16,7 +16,7 @@ jobs: node-version: [20] # semantic-release 需要 >= 16 的 Node.js 环境 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Python 3.11 和 node-gyp 有兼容问题, 导致无法安装依赖 # https://github.com/slint-ui/slint/commit/a9c48e33502fdebc36c5aa2f4f516c2218424679#diff-944291df2c9c06359d37cc8833d182d705c9e8c3108e7cfe132d61a06e9133dd @@ -29,7 +29,7 @@ jobs: version: 8 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -44,7 +44,7 @@ jobs: { "msgtype": "link", "link": { - "title": "🚀 开始自动发布 🚀", + "title": "🚑 开始自动发布 (${{ github.head_ref || github.ref_name }}) 🚑", "text": "🔗 请点击链接查看详情", "messageUrl": "https://github.com/antvis/S2/actions/workflows/auto-release.yml", "picUrl": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*gt5-RZDjt3IAAAAAAAAAAAAADmJ7AQ/original" @@ -77,7 +77,7 @@ jobs: { "msgtype": "link", "link": { - "title": "🚨 自动发布失败 🚨", + "title": "🚨 自动发布失败 (${{ github.head_ref || github.ref_name }})", "text": "🔗 请点击链接查看具体原因, 及时修复, 尝试点击右上角 [Re-run all jobs] 重试, 或手动发布 🚑", "messageUrl": "https://github.com/antvis/S2/actions/workflows/auto-release.yml", "picUrl": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*PRSkSqsE_vYAAAAAAAAAAAAADmJ7AQ/original" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 4a4f797a5a..ee18ab6b2b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -9,17 +9,21 @@ # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # -name: "CodeQL" +name: 🏨 CodeQL on: push: - branches: [ master ] + branches: [ master, alpha, beta, next ] pull_request: # The branches below must be a subset of the branches above - branches: [ master ] + branches: [ master, alpha, beta, next ] schedule: - cron: '38 0 * * 2' +concurrency: + group: ${{github.workflow}}-${{github.event_name}}-${{github.ref}} + cancel-in-progress: true + jobs: analyze: name: Analyze diff --git a/.github/workflows/compressed-size.yml b/.github/workflows/compressed-size.yml index 9092245ce9..c25c7f9a23 100644 --- a/.github/workflows/compressed-size.yml +++ b/.github/workflows/compressed-size.yml @@ -4,6 +4,10 @@ on: pull_request: types: [opened, synchronize] +concurrency: + group: ${{github.workflow}}-${{github.event_name}}-${{github.ref}} + cancel-in-progress: true + jobs: compressed-size: @@ -11,7 +15,7 @@ jobs: steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Python 3.11 和 node-gyp 有兼容问题, 导致无法安装依赖 # https://github.com/slint-ui/slint/commit/a9c48e33502fdebc36c5aa2f4f516c2218424679#diff-944291df2c9c06359d37cc8833d182d705c9e8c3108e7cfe132d61a06e9133dd diff --git a/.github/workflows/disscustion.yml b/.github/workflows/disscustion.yml index 4e91b508d2..f863c2fd91 100644 --- a/.github/workflows/disscustion.yml +++ b/.github/workflows/disscustion.yml @@ -1,5 +1,5 @@ -name: Discussions +name: 💬 Discussions on: discussion: @@ -21,6 +21,6 @@ jobs: "title": "📢 用户: ${{ github.event.discussion.user.login }} 创建了讨论:(${{ github.event.discussion.title }})", "text": "👀 点击查看", "messageUrl": "${{ github.event.discussion.html_url }}", - "picUrl": "https://gw.alipayobjects.com/zos/antfincdn/ISzgBCtgR/2c5c4aaa-4f40-46f7-8f6b-427fa9ff07bb.png" + "picUrl": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*1PTTQLk3j5AAAAAAAAAAAAAADmJ7AQ/original" } } diff --git a/.github/workflows/issue-labeled.yml b/.github/workflows/issue-labeled.yml index 4eb12f78cc..31f9629bdf 100644 --- a/.github/workflows/issue-labeled.yml +++ b/.github/workflows/issue-labeled.yml @@ -105,7 +105,7 @@ jobs: body: | 你好 @${{ github.event.issue.user.login }},经过我们的反复讨论, 你的需求现已被采纳, 我们会排期开发, 但人力资源有限, 短期内无法支持, 请关注后续发布日志。当然, 如果能贡献 PR 帮助我们改进, 不胜感激! - Hello, @${{ github.vent.issue.user.login }}, your feature request has been accepted after our repeated discussion. We will schedule the development. However, it could not be supported in the short term since limited time, please pay attention to the follow-up release logs. Of course, looking forward for your PR! + Hello, @${{ github.event.issue.user.login }}, your feature request has been accepted after our repeated discussion. We will schedule the development. However, it could not be supported in the short term since limited time, please pay attention to the follow-up release logs. Of course, looking forward for your PR! - name: Rejected if: github.event.label.name == '❌ won''t support' @@ -118,3 +118,25 @@ jobs: 你好 @${{ github.event.issue.user.login }},经过我们的反复讨论, 你的需求过于定制化,不适合直接添加到 S2 中, S2 作为开源框架,只会进行通用能力的增强和自定义接口的开放。你可以通过 S2 提供的自定义能力自行实现,感谢你的理解。 Hello, @${{ github.event.issue.user.login }}, After our repeated discussions, your needs are too customized and not suitable for implementing directly to S2. As an open source framework, S2 will only enhance general capabilities and open custom interfaces. You can implement it yourself through the customization capabilities provided by S2, thank you for your understanding. + + - name: Supported or fixed in next + if: github.event.label.name == '✨ supported or fixed in next' + uses: actions-cool/issues-helper@main + with: + actions: 'create-comment' + token: ${{ secrets.GITHUB_TOKEN }} + issue-number: ${{ github.event.issue.number }} + body: | + 你好 @${{ github.event.issue.user.login }},该功能或缺陷已经在 `2.0 next 版本` 中支持或修复,`next` 版本目前处于内测中, 感谢你的支持与理解。 + + 如有任何 `2.0 版本` 问题,请前往[讨论区](https://github.com/antvis/S2/discussions/1933),正式版预计年底发布 (文档施工中 🚧), 抢先试用: + + ```bash + yarn add @antv/s2@next + yarn add @antv/s2-react@next + yarn add @antv/s2-vue@next + ``` + + Hello, @${{ github.event.issue.user.login }}, This feature or flaw has been supported or fixed in `2.0 next version`, `next` version is currently in private beta, thank you for your support and understanding. + + Any `2.0` version issues, please go to [discussion](https://github.com/antvis/S2/discussions/1933), which released the official version is expected to the end (document 🚧) during construction, the first trial: diff --git a/.github/workflows/issue-opend.yml b/.github/workflows/issue-opend.yml index 1cfb08c64c..ec973f8af4 100644 --- a/.github/workflows/issue-opend.yml +++ b/.github/workflows/issue-opend.yml @@ -20,9 +20,9 @@ jobs: Hello, @${{ github.event.issue.user.login }}, please edit your issue title. a concise issue title will save everyone time. please do not leave the title as the body or empty. - # 如果是 bug 的 issue, 但是基本的版本号,表格类型, 描述都没有, 直接关闭, 不多BB. - - name: check bug report issue body - if: contains(github.event.issue.title, '🐛') == true && contains(github.event.issue.body, 'Version') == false && contains(github.event.issue.body, 'Sheet Type') == false && contains(github.event.issue.body, 'Description') == false + # 如果 issue 的提交者无视模版, 连基本的版本号,表格类型, 描述都没有, 直接自动关闭, 不多BB. + - name: check issue body + if: contains(github.event.issue.body, 'Version') == false && contains(github.event.issue.body, 'Sheet Type') == false && contains(github.event.issue.body, 'Description') == false uses: actions-cool/issues-helper@main with: actions: 'create-comment,add-labels,close-issue' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2f2d4322d0..c43309deac 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -name: lint +name: 👨🔬 lint on: [pull_request] @@ -6,7 +6,6 @@ concurrency: group: ${{github.workflow}}-${{github.event_name}}-${{github.ref}} cancel-in-progress: true - jobs: lint: runs-on: macos-latest @@ -16,7 +15,7 @@ jobs: node-version: [20] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Python 3.11 和 node-gyp 有兼容问题, 导致无法安装依赖 # https://github.com/slint-ui/slint/commit/a9c48e33502fdebc36c5aa2f4f516c2218424679#diff-944291df2c9c06359d37cc8833d182d705c9e8c3108e7cfe132d61a06e9133dd @@ -29,7 +28,7 @@ jobs: version: 8 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -43,8 +42,8 @@ jobs: - name: Build run: pnpm build - - name: Bundle size - run: pnpm bundle:size + - name: Bundle size limit + run: pnpm build:size-limit env: CI: true BUNDLESIZE_GITHUB_TOKEN: ${{ secrets.BUNDLESIZE_GITHUB_TOKEN }} diff --git a/.github/workflows/pr-auto-assign-reviewer.yml b/.github/workflows/pr-auto-assign-reviewer.yml index 9f74e609f9..cc1f6cec4e 100644 --- a/.github/workflows/pr-auto-assign-reviewer.yml +++ b/.github/workflows/pr-auto-assign-reviewer.yml @@ -21,5 +21,5 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} pr-emoji: '+1, rocket' - reviewers: 'serializedowen,lcx-seima,lijinke666,wjgogogo,stone-lyl,GaoFuhong' + reviewers: 'lijinke666,wjgogogo,wuhaiyang' review-creator: false diff --git a/.github/workflows/prerelease-notify.yml b/.github/workflows/prerelease-notify.yml index 12d063a8d8..84126f1d97 100644 --- a/.github/workflows/prerelease-notify.yml +++ b/.github/workflows/prerelease-notify.yml @@ -23,7 +23,7 @@ jobs: ${{ secrets.DING_TALK_ACCESS_TOKEN }} ${{ secrets.DING_TALK_GROUP_TOKEN }} notify_title: '🎉 {release_tag} 发布 🎉' - notify_body: '## { title } <hr /> ![preview](https://gw.alipayobjects.com/zos/antfincdn/ISzgBCtgR/2c5c4aaa-4f40-46f7-8f6b-427fa9ff07bb.png) <hr /> { body } <hr />' + notify_body: '## { title } <hr /> ![preview](https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*1PTTQLk3j5AAAAAAAAAAAAAADmJ7AQ/original) <hr /> { body } <hr />' notify_footer: '> 该版本为测试版, 请谨慎使用, 前往 [**AntV/S2 Releases**]({ release_url }) 查看完整更新日志.' at_all: false enable_prerelease: true diff --git a/.github/workflows/release-notify.yml b/.github/workflows/release-notify.yml index 36452302a0..b4d892176f 100644 --- a/.github/workflows/release-notify.yml +++ b/.github/workflows/release-notify.yml @@ -18,8 +18,8 @@ jobs: ${{ secrets.DING_TALK_ACCESS_TOKEN }} ${{ secrets.DING_TALK_GROUP_TOKEN }} ${{ secrets.DING_TALK_PUBLIC_TOKEN }} - notify_title: '🎉 S2 新版本发布啦 🎉' - notify_body: '## { title } <hr /> ![preview](https://gw.alipayobjects.com/zos/antfincdn/ISzgBCtgR/2c5c4aaa-4f40-46f7-8f6b-427fa9ff07bb.png) <hr /> 看看有哪些更新吧 <hr />' + notify_title: '🎉 AntV/S2 新版本发布啦 🎉' + notify_body: '## { title } <hr /> ![preview](https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*1PTTQLk3j5AAAAAAAAAAAAAADmJ7AQ/original) <hr /> 看看有哪些更新吧 <hr />' notify_footer: '> 前往 [**AntV/S2 Releases**](https://github.com/antvis/S2/releases/latest) 查看完整更新日志.' at_all: false enable_prerelease: false diff --git a/.github/workflows/release-success.yml b/.github/workflows/release-success.yml index 164f782554..6fefaea70c 100644 --- a/.github/workflows/release-success.yml +++ b/.github/workflows/release-success.yml @@ -16,8 +16,19 @@ jobs: defaults: run: working-directory: s2-site + + strategy: + matrix: + node-version: [18] + steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'yarn' - name: Git bootstrap run: | @@ -56,7 +67,7 @@ jobs: { "msgtype": "link", "link": { - "title": "📢 开始自动部署旧版官网(https://s2.antv.vision) 📢 ", + "title": "📢 开始自动部署旧版官网 (https://s2.antv.vision) 📢 ", "text": "🔗 请点击链接查看详情", "messageUrl": "https://github.com/antvis/S2/actions/workflows/release-success.yml", "picUrl": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*gt5-RZDjt3IAAAAAAAAAAAAADmJ7AQ/original" @@ -68,7 +79,7 @@ jobs: with: node-version: 16 cache: 'yarn' - + # 安装官网依赖 - name: Install Dependencies run: yarn diff --git a/.github/workflows/site-build-notify.yml b/.github/workflows/site-build-notify.yml index 9022cbc5f6..d34d32097c 100644 --- a/.github/workflows/site-build-notify.yml +++ b/.github/workflows/site-build-notify.yml @@ -16,10 +16,10 @@ jobs: { "msgtype": "link", "link": { - "title": "✅ 旧官网(https://s2.antv.vision) 部署成功", - "text": "点击访问 https://s2.antv.vision/", + "title": "✅ 旧官网 (https://s2.antv.vision) 部署成功", + "text": "点击访问", "messageUrl": "https://s2.antv.vision/", - "picUrl": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*Eel8Rp5jlAkAAAAAAAAAAAAADmJ7AQ/original" + "picUrl": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*1PTTQLk3j5AAAAAAAAAAAAAADmJ7AQ/original" } } diff --git a/.github/workflows/sync-notify.yml b/.github/workflows/sync-notify.yml index afde29a503..227d218ae5 100644 --- a/.github/workflows/sync-notify.yml +++ b/.github/workflows/sync-notify.yml @@ -23,7 +23,7 @@ jobs: "title": "✅ 同步 changelog 成功", "text": "📢 请发布值班合并该 PR 后, 访问官网查看是否有异常", "messageUrl": "https://s2.antv.antgroup.com", - "picUrl": "https://gw.alipayobjects.com/zos/antfincdn/ISzgBCtgR/2c5c4aaa-4f40-46f7-8f6b-427fa9ff07bb.png" + "picUrl": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*1PTTQLk3j5AAAAAAAAAAAAAADmJ7AQ/original" } } diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3fb24312f1..7a6b58f018 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: test +name: 💯 test on: [pull_request] @@ -18,7 +18,7 @@ jobs: node-version: [20] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Python 3.11 和 node-gyp 有兼容问题, 导致无法安装依赖 # https://github.com/slint-ui/slint/commit/a9c48e33502fdebc36c5aa2f4f516c2218424679#diff-944291df2c9c06359d37cc8833d182d705c9e8c3108e7cfe132d61a06e9133dd @@ -31,7 +31,7 @@ jobs: version: 8 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -44,9 +44,10 @@ jobs: pnpm test:ci-coverage - name: Upload test coverage - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: false verbose: true - name: Workflow failed alert diff --git a/.releaserc.base.js b/.releaserc.base.js index 29d4c0ef0e..544d514383 100644 --- a/.releaserc.base.js +++ b/.releaserc.base.js @@ -1,11 +1,13 @@ +const path = require('path'); + module.exports = { + extends: 'semantic-release-monorepo', branches: [ 'latest', { name: 'beta', channel: 'beta', prerelease: true }, { name: 'alpha', channel: 'alpha', prerelease: true }, { name: 'next', channel: 'next', prerelease: true }, ], - extends: 'semantic-release-monorepo', plugins: [ [ '@semantic-release/commit-analyzer', @@ -26,11 +28,19 @@ module.exports = { '@semantic-release/npm', [ '@semantic-release/git', - { + { message: 'chore(release): 🤖 ${nextRelease.gitTag} [skip ci]', - }, + }, ], '@semantic-release/github', + [ + '@semantic-release/exec', + { + prepareCmd: + `node ${path.resolve(__dirname, './scripts/add-version.js')} ` + + '${nextRelease.gitTag}', + }, + ], ], preset: 'angular', }; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b7b5f275ed..5f81433362 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,7 +38,7 @@ 2. 安装依赖:`pnpm install` 3. 提交你的改动,commit 请遵守 [AngularJS Git Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#heading=h.uyo6cb12dt6w) 4. 如果你的改动是修复 bug, 还可以在提交信息后面加上 `close #issue 号`, 这样可以在 pr 合并后,可以自动关闭对应的 issue, 比如 `fix: render bug close #123` -5. 确保加上了对应的单元测试和文档 (如有必要) +5. 确保加上了对应的单元测试和文档 (如果有 `Snapshot` UI 快照 (.snap 文件)更新, 可以运行 `yarn core:test -- -u` 和 `yarn react:test -- -u` 自动更新, 并一起提交上来, 请勿手动编辑) 6. 所有 Lint 和 Test 检查通过后,并且 review 通过,我们会合并你的 pr. ![preview](https://gw.alipayobjects.com/zos/antfincdn/ssOxFrycD/86339514-5f9a-4101-8690-e47c97cd8af5.png) diff --git a/LICENSE b/LICENSE index 6fc25ee611..a63e9f8b5b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2021 AntV +Copyright (c) 2021-present AntV Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.en-US.md b/README.en-US.md index 1b93f73601..e2902b21bc 100644 --- a/README.en-US.md +++ b/README.en-US.md @@ -68,9 +68,9 @@ demo components and expansion capabilities, it allows developers to use it quick ## 📦 Installation ```bash -$ npm install @antv/s2 -# yarn add @antv/s2 -# pnpm install @antv/s2 +$ npm install @antv/s2 --save +# yarn add @antv/s2 --save +# pnpm install @antv/s2 --save ``` ## 🔨 Getting Started @@ -229,9 +229,6 @@ pnpm site:start <a> <img width="300" height="auto" alt="S2" src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*2VvTSZmI4vYAAAAAAAAAAAAADmJ7AQ/original"> </a> - <a> - <img width="300" height="auto" alt="S2" src="https://gw.alipayobjects.com/zos/antfincdn/v4TlwgORE/qq_qr_code.JPG"> - </a> </p> ## 👬 Contributors diff --git a/README.md b/README.md index 5b32f0d71a..1210bbc8bf 100644 --- a/README.md +++ b/README.md @@ -64,9 +64,9 @@ S2 是 AntV 在多维交叉分析表格领域的解决方案,完全基于数 ## 📦 安装 ```bash -$ npm install @antv/s2 -# yarn add @antv/s2 -# pnpm install @antv/s2 +$ npm install @antv/s2 --save +# yarn add @antv/s2 --save +# pnpm install @antv/s2 --save ``` ## 🔨 使用 @@ -223,9 +223,6 @@ pnpm site:start <a> <img width="300" height="auto" alt="DingTalk" src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*2VvTSZmI4vYAAAAAAAAAAAAADmJ7AQ/original"> </a> - <a> - <img width="300" height="auto" alt="qq" src="https://gw.alipayobjects.com/zos/antfincdn/v4TlwgORE/qq_qr_code.JPG"> - </a> </p> ## 👬 Contributors diff --git a/package.json b/package.json index 8fcdb05722..0b20ab3a44 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "@rollup/plugin-typescript": "^11.1.5", "@rushstack/eslint-patch": "^1.5.1", "@semantic-release/changelog": "^6.0.3", + "@semantic-release/exec": "^6.0.3", "@semantic-release/git": "^10.0.1", "@swc/core": "^1.3.95", "@swc/jest": "^0.2.29", @@ -164,7 +165,8 @@ "typescript": "^5.2.2", "vite": "^4.5.0", "vite-plugin-imp": "^2.4.0", - "vue-jest": "^5.0.0-alpha.10" + "vue-jest": "^5.0.0-alpha.10", + "size-limit": "^11.0.0" }, "license": "MIT", "repository": { diff --git a/packages/s2-core/CHANGELOG.md b/packages/s2-core/CHANGELOG.md index ee669635bd..d2bec1e76c 100644 --- a/packages/s2-core/CHANGELOG.md +++ b/packages/s2-core/CHANGELOG.md @@ -1,22 +1,19 @@ # [@antv/s2-v2.0.0-next.10](https://github.com/antvis/S2/compare/@antv/s2-v2.0.0-next.9...@antv/s2-v2.0.0-next.10) (2023-12-12) - ### Features * 支持在单元格内渲染 G2 图表 ([#2437](https://github.com/antvis/S2/issues/2437)) ([497f941](https://github.com/antvis/S2/commit/497f9414b89fce01b60db9b6c2eb4292ffe69c1d)) # [@antv/s2-v2.0.0-next.9](https://github.com/antvis/S2/compare/@antv/s2-v2.0.0-next.8...@antv/s2-v2.0.0-next.9) (2023-11-22) - ### Features * headerActionIcons 支持细粒度配置 & 修复异步渲染导致无法获取实例的问题 ([#2301](https://github.com/antvis/S2/issues/2301)) ([b2d6f1f](https://github.com/antvis/S2/commit/b2d6f1fb04d3fa73129669fc7d2dec84943252db)) * **layout:** 单元格支持渲染多行文本 ([#2383](https://github.com/antvis/S2/issues/2383)) ([e3b919a](https://github.com/antvis/S2/commit/e3b919a4f37d600a0f516944edf4eed8b2c0174d)) * 支持 antd v5 ([#2413](https://github.com/antvis/S2/issues/2413)) ([299c7bf](https://github.com/antvis/S2/commit/299c7bfe2e86838153273c92dd6d2b72917cfdea)) -* 支持 React 18 (兼容 React 16/17) ([#2373](https://github.com/antvis/S2/issues/2373)) ([25ce9b0](https://github.com/antvis/S2/commit/25ce9b0ccc3e609d8add09b3209f6f981dc1dc4e)) +* 支持 React 18 (兼容 React 16/17) ([#2373](https://github.com/antvis/S2/issues/2373)) ([25ce9b0](https://github.com/antvis/S2/commit/25ce9b0ccc3e609d8add09b3209f6f981dc1dc4e)) * 支持自定义 G 5.0 插件和配置 ([#2423](https://github.com/antvis/S2/issues/2423)) ([cc6c47f](https://github.com/antvis/S2/commit/cc6c47fd0927125bbc378fe6914becfcbe1b0acd)) - ### BREAKING CHANGES * 移除 devicePixelRatio 和 supportsCSSTransform @@ -75,13 +72,13 @@ ### Features * 使用 requestIdleCallback 处理数据大量导出的情况 ([#2272](https://github.com/antvis/S2/issues/2272)) ([42a5551](https://github.com/antvis/S2/commit/42a55516dd369d9ab5579b52fbc9900b0ad81858)) -* 同步复制支持自定义transformer ([#2201](https://github.com/antvis/S2/issues/2201)) ([9003767](https://github.com/antvis/S2/commit/9003767d584248b9d122f299326fd14753961883)) +* 同步复制支持自定义 transformer ([#2201](https://github.com/antvis/S2/issues/2201)) ([9003767](https://github.com/antvis/S2/commit/9003767d584248b9d122f299326fd14753961883)) * 增加暗黑主题 ([#2130](https://github.com/antvis/S2/issues/2130)) ([51dbdcf](https://github.com/antvis/S2/commit/51dbdcf564b387a3fd1809a71016f3a91eebde38)) * 完善复制和导出在格式化后,总计、小计对应数值没有格式化的问题 ([#2237](https://github.com/antvis/S2/issues/2237)) ([abc0dbb](https://github.com/antvis/S2/commit/abc0dbb1544d9a4ef133e6a2c7d2d09ac8f35b48)) * 文本和图标的条件格式支持主题配置 ([#2267](https://github.com/antvis/S2/issues/2267)) ([c332c68](https://github.com/antvis/S2/commit/c332c687dfb7be1d07b79b44934f78c1947cc466)) * 条件格式 mapping 增加第三个参数获取单元格实例 ([#2242](https://github.com/antvis/S2/issues/2242)) ([aae427d](https://github.com/antvis/S2/commit/aae427dfe6a87cae577ce2449fd6058d358971f9)) * 行列头兼容 condition icon 和 action icons ([#2161](https://github.com/antvis/S2/issues/2161)) ([1df4286](https://github.com/antvis/S2/commit/1df42860f6a12d3cb182ba7633c4984a04e62890)) -* 适配g5.0异步渲染 ([#2251](https://github.com/antvis/S2/issues/2251)) ([069d03d](https://github.com/antvis/S2/commit/069d03d299429c2ffab3e20d56ecd6bb30119ffd)) +* 适配 g5.0 异步渲染 ([#2251](https://github.com/antvis/S2/issues/2251)) ([069d03d](https://github.com/antvis/S2/commit/069d03d299429c2ffab3e20d56ecd6bb30119ffd)) # [@antv/s2-v2.0.0-next.7](https://github.com/antvis/S2/compare/@antv/s2-v2.0.0-next.6...@antv/s2-v2.0.0-next.7) (2023-04-28) @@ -95,25 +92,25 @@ * **tooltip:** 修复特定配置下点击 tooltip 内容后 tooltip 关闭 close [#2170](https://github.com/antvis/S2/issues/2170) ([#2172](https://github.com/antvis/S2/issues/2172)) ([6219e57](https://github.com/antvis/S2/commit/6219e579364cfb7ac3a8b3db4ae01c5672d7f2d4)) * 修复 cornerText 配置对树状模式的适配 ([#2167](https://github.com/antvis/S2/issues/2167)) ([e9efcea](https://github.com/antvis/S2/commit/e9efcea944f5d0793d4a1250362e6b6f6b492c52)) * 修复总计小计 linkField 样式问题 ([#2169](https://github.com/antvis/S2/issues/2169)) ([4450278](https://github.com/antvis/S2/commit/4450278d82888c117e5bd9d31874b88ecdb33d99)) -* 修改DataCell类 drawLinkFieldShape 方法名为 drawLinkFieldShapeOwn ([d5e14b2](https://github.com/antvis/S2/commit/d5e14b25abba5bfaf74dddb17d9f5b44c74bc29b)) +* 修改 DataCell 类 drawLinkFieldShape 方法名为 drawLinkFieldShapeOwn ([d5e14b2](https://github.com/antvis/S2/commit/d5e14b25abba5bfaf74dddb17d9f5b44c74bc29b)) * 多指标行头总计节点宽度计算错误 ([#2165](https://github.com/antvis/S2/issues/2165)) ([08ef330](https://github.com/antvis/S2/commit/08ef330a02a1fbf11f49090f4fd7f5d2b0cc1093)) -* 微应用环境识别mouseEvent失效 ([bddbe34](https://github.com/antvis/S2/commit/bddbe34104355ac0087bc9f72377889a8f444d7a)), closes [#2162](https://github.com/antvis/S2/issues/2162) +* 微应用环境识别 mouseEvent 失效 ([bddbe34](https://github.com/antvis/S2/commit/bddbe34104355ac0087bc9f72377889a8f444d7a)), closes [#2162](https://github.com/antvis/S2/issues/2162) * 统一风格、删除冗余代码 ([7b4ef0e](https://github.com/antvis/S2/commit/7b4ef0edf72e059b427c54e6ea881c4c8e347aed)) * 行头过宽且不冻结时滚动条渲染错误 ([#2173](https://github.com/antvis/S2/issues/2173)) ([ab79ea0](https://github.com/antvis/S2/commit/ab79ea0664046bc6479a717d7b3b0ee7efe05b31)) -* 避免s2实例被污染 ([8c44a85](https://github.com/antvis/S2/commit/8c44a85a678eadaab3fb2a66b5b02a123f74c9bb)) +* 避免 s2 实例被污染 ([8c44a85](https://github.com/antvis/S2/commit/8c44a85a678eadaab3fb2a66b5b02a123f74c9bb)) ### Features -* icon支持更新name与fill ([#2138](https://github.com/antvis/S2/issues/2138)) ([d000aea](https://github.com/antvis/S2/commit/d000aeac332676cfa15d9986ec7f4be948c565d0)) +* icon 支持更新 name 与 fill ([#2138](https://github.com/antvis/S2/issues/2138)) ([d000aea](https://github.com/antvis/S2/commit/d000aeac332676cfa15d9986ec7f4be948c565d0)) * **interaction:** 点击角头后支持选中所对应那一列的行头 close [#2073](https://github.com/antvis/S2/issues/2073) ([#2081](https://github.com/antvis/S2/issues/2081)) ([ad2b5d8](https://github.com/antvis/S2/commit/ad2b5d87edf4c529d7c9a5e1348e893e14547ef3)) * **interaction:** 行头支持滚动刷选 ([#2087](https://github.com/antvis/S2/issues/2087)) ([65c3f3b](https://github.com/antvis/S2/commit/65c3f3b6a37709c0fa684b0f5717d3b349251e48)) -* 修改文档、添加用例演示、修改方法名drawLinkFieldShapLogic -> drawLinkField ([7f2bd69](https://github.com/antvis/S2/commit/7f2bd690bd703b8e4d678c03b9fc79db30848ca3)) +* 修改文档、添加用例演示、修改方法名 drawLinkFieldShapLogic -> drawLinkField ([7f2bd69](https://github.com/antvis/S2/commit/7f2bd690bd703b8e4d678c03b9fc79db30848ca3)) * 在 shape 中添加文本的原始值 ([#2109](https://github.com/antvis/S2/issues/2109)) ([4d81e72](https://github.com/antvis/S2/commit/4d81e72440d797fd7a06179794c342f009fc39c3)) -* 增加dataCell 下划线测试用例及demo ([a5efe17](https://github.com/antvis/S2/commit/a5efe17bda06cc8eba633cbea9c56ceb8b8c703e)) -* 增加自定义merged-cell ([534cc15](https://github.com/antvis/S2/commit/534cc15da9f766f95be3c622e65e45d8796ff020)) -* 复制支持自定义transformer ([#2090](https://github.com/antvis/S2/issues/2090)) ([250eecd](https://github.com/antvis/S2/commit/250eecd32ed4f48b95ed7c4e480fa3c75d4bb5d7)) +* 增加 dataCell 下划线测试用例及 demo ([a5efe17](https://github.com/antvis/S2/commit/a5efe17bda06cc8eba633cbea9c56ceb8b8c703e)) +* 增加自定义 merged-cell ([534cc15](https://github.com/antvis/S2/commit/534cc15da9f766f95be3c622e65e45d8796ff020)) +* 复制支持自定义 transformer ([#2090](https://github.com/antvis/S2/issues/2090)) ([250eecd](https://github.com/antvis/S2/commit/250eecd32ed4f48b95ed7c4e480fa3c75d4bb5d7)) * 提取跳转链接下划线 公共逻辑 到 BaseCell 类 ([34dbbb3](https://github.com/antvis/S2/commit/34dbbb3bdf028cb96508dcead724d9ac9bcc1ab9)) -* 数据单元格DataCell类中增加链接跳转渲染 ([bb5a964](https://github.com/antvis/S2/commit/bb5a964787a80843515b4d552adb3fdb59393e3d)) +* 数据单元格 DataCell 类中增加链接跳转渲染 ([bb5a964](https://github.com/antvis/S2/commit/bb5a964787a80843515b4d552adb3fdb59393e3d)) # [@antv/s2-v2.0.0-next.6](https://github.com/antvis/S2/compare/@antv/s2-v2.0.0-next.5...@antv/s2-v2.0.0-next.6) (2023-04-23) @@ -300,7 +297,6 @@ ### Bug Fixes -* 下钻后 meta.childField 不正确 ([#1788](https://github.com/antvis/S2/issues/1788)) ([1c61dd4](https://github.com/antvis/S2/commit/1c61dd4081c9d3fed6f276b0546865914040b07a)) * 重构绘制盒模型,修复边框偏移问题 ([#1854](https://github.com/antvis/S2/issues/1854)) ([f7e0858](https://github.com/antvis/S2/commit/f7e0858a937ea557532a7fff948e9af3b6a1fdff)) # [@antv/s2-v1.35.0](https://github.com/antvis/S2/compare/@antv/s2-v1.34.1...@antv/s2-v1.35.0) (2022-11-21) @@ -315,11 +311,11 @@ # [@antv/s2-v1.35.0](https://github.com/antvis/S2/compare/@antv/s2-v1.34.1...@antv/s2-v1.35.0) (2022-11-21) -### Features +======= -* 明细表支持多级表头 ([#1921](https://github.com/antvis/S2/issues/1921)) ([47cdbdc](https://github.com/antvis/S2/commit/47cdbdccafbd7f19a05550a483a42aac11a93778)), closes [#1687](https://github.com/antvis/S2/issues/1687) [#1801](https://github.com/antvis/S2/issues/1801) +# [@antv/s2-v1.35.1](https://github.com/antvis/S2/compare/@antv/s2-v1.35.0...@antv/s2-v1.35.1) (2022-11-28) -# [@antv/s2-v1.34.1](https://github.com/antvis/S2/compare/@antv/s2-v1.34.0...@antv/s2-v1.34.1) (2022-11-18) +### Bug Fixes ### Bug Fixes @@ -487,11 +483,6 @@ * 复制支持 html 格式 ([#1647](https://github.com/antvis/S2/issues/1647)) ([3ea6349](https://github.com/antvis/S2/commit/3ea634970a162d869cf12dad7aa754bebafd30f3)) * 支持 resize 最右侧 column ([#1611](https://github.com/antvis/S2/issues/1611)) ([f63bfa2](https://github.com/antvis/S2/commit/f63bfa2a0e95c8c42c064d0e2e56ce9550ac50c6)) -# [@antv/s2-v1.24.0](https://github.com/antvis/S2/compare/@antv/s2-v1.23.0...@antv/s2-v1.24.0) (2022-07-22) - -### Bug Fixes - -* **layout:** 修复 Firefox 浏览器部分 icon 渲染失败 close [#1571](https://github.com/antvis/S2/issues/1571) ([#1599](https://github.com/antvis/S2/issues/1599)) ([6b76c4e](https://github.com/antvis/S2/commit/6b76c4e2c80b88eeb63d7adfc6b48da7d0b3ea4c)) * **strategysheet:** 修复单元格宽度拖拽变小后子弹图宽度计算错误 ([#1584](https://github.com/antvis/S2/issues/1584)) ([99b8593](https://github.com/antvis/S2/commit/99b859392c7151d5700bf1c505a02f795b9a3f80)) * **strategysheet:** 修复子弹图进度小于 1% 时显示错误的问题 ([#1563](https://github.com/antvis/S2/issues/1563)) ([936ca6a](https://github.com/antvis/S2/commit/936ca6a3a7bf40ddc0ff1a0271c3a5ffb1091dcf)) * **strategysheet:** 修复子弹图颜色显示错误 & 百分比精度问题 ([#1588](https://github.com/antvis/S2/issues/1588)) ([c4bb48c](https://github.com/antvis/S2/commit/c4bb48cbe128b47e3574af903142934fd7452846)) diff --git a/packages/s2-core/README.md b/packages/s2-core/README.md index 80a489c909..c04f9fec8c 100644 --- a/packages/s2-core/README.md +++ b/packages/s2-core/README.md @@ -143,7 +143,7 @@ const s2DataConfig = { ```ts const s2Options = { width: 600, - height: 600, + height: 600 } ``` @@ -219,9 +219,6 @@ pnpm site:start <a> <img width="300" height="auto" alt="DingTalk" src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*2VvTSZmI4vYAAAAAAAAAAAAADmJ7AQ/original"> </a> - <a> - <img width="300" height="auto" alt="qq" src="https://gw.alipayobjects.com/zos/antfincdn/v4TlwgORE/qq_qr_code.JPG"> - </a> </p> ## 👬 Contributors diff --git a/packages/s2-core/__tests__/bugs/__snapshots__/issue-2359-spec.ts.snap b/packages/s2-core/__tests__/bugs/__snapshots__/issue-2359-spec.ts.snap new file mode 100644 index 0000000000..076a151e3f --- /dev/null +++ b/packages/s2-core/__tests__/bugs/__snapshots__/issue-2359-spec.ts.snap @@ -0,0 +1,67 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Table Sheet Custom Multiple Values Tests should use current cell text theme 1`] = ` +Array [ + Array [ + Object { + "fill": "#000000", + "fontSize": 12, + }, + ], + Array [ + Object { + "fill": "red", + "fontSize": 20, + }, + Object { + "fill": "red", + "fontSize": 20, + }, + Object { + "fill": "red", + "fontSize": 20, + }, + ], +] +`; + +exports[`Table Sheet Custom Multiple Values Tests should use current cell text theme 2`] = ` +Array [ + Array [ + Object { + "fill": "#000000", + "fontSize": 12, + }, + ], + Array [ + Object { + "fill": "#000000", + "fontSize": 12, + }, + ], + Array [ + Object { + "fill": "#000000", + "fontSize": 12, + }, + ], + Array [ + Object { + "fill": "green", + "fontSize": 30, + }, + ], + Array [ + Object { + "fill": "green", + "fontSize": 30, + }, + ], + Array [ + Object { + "fill": "green", + "fontSize": 30, + }, + ], +] +`; diff --git a/packages/s2-core/__tests__/bugs/__snapshots__/issue-565-spec.ts.snap b/packages/s2-core/__tests__/bugs/__snapshots__/issue-565-spec.ts.snap new file mode 100644 index 0000000000..72fd2fd2d2 --- /dev/null +++ b/packages/s2-core/__tests__/bugs/__snapshots__/issue-565-spec.ts.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Export data in pivot tree mode should export correct col header in pivot tree mode 1`] = ` +" col0 col0-0 col0-0 + col1 col1-0 col1-1 + col2 col2-0 col2-0 +row0 row1 row2 number number +row0 +row0 row1-0 +row0 row1-0 row2-0 3 +row0 row1-1 +row0 row1-1 row2-0 2 4" +`; diff --git a/packages/s2-core/__tests__/bugs/issue-1191-spec.ts b/packages/s2-core/__tests__/bugs/issue-1191-spec.ts index f533ac3630..696f9e0ca1 100644 --- a/packages/s2-core/__tests__/bugs/issue-1191-spec.ts +++ b/packages/s2-core/__tests__/bugs/issue-1191-spec.ts @@ -49,7 +49,7 @@ const dataCfg: S2DataConfig = { { field: 'price', name: '价格', - formatter: (v) => `price:${v}`, + formatter: (v) => `price: ${v}`, }, ], }; @@ -74,21 +74,21 @@ describe('Link Field Tests', () => { test('province row cell should use link field style', () => { // 浙江省对应 cell - const province = s2.facet.rowHeader?.children[0]; + const province = s2.facet + .getRowCells() + .find((cell) => cell.getMeta().value === '浙江'); - // @ts-ignore - expect(province.textShape.attr('fill')).toEqual('red'); - // @ts-ignore - expect(province.linkFieldShape).toBeDefined(); + expect(province?.getTextShape().attr('fill')).toEqual('red'); + expect(province?.getLinkFieldShape()).toBeDefined(); }); test('city row cell should not use link field style', () => { // 义乌对应 cell - const city = s2.facet.rowHeader?.children[1]; + const city = s2.facet + .getRowCells() + .find((cell) => cell.getMeta().value === '义乌'); - // @ts-ignore - expect(city.textShape.attr('fill')).not.toEqual('red'); - // @ts-ignore - expect(city.linkFieldShape).not.toBeDefined(); + expect(city?.getTextShape().attr('fill')).not.toEqual('red'); + expect(city?.getLinkFieldShape()).not.toBeDefined(); }); }); diff --git a/packages/s2-core/__tests__/bugs/issue-1201-spec.ts b/packages/s2-core/__tests__/bugs/issue-1201-spec.ts index f04d0d57d5..2af1b5b5bb 100644 --- a/packages/s2-core/__tests__/bugs/issue-1201-spec.ts +++ b/packages/s2-core/__tests__/bugs/issue-1201-spec.ts @@ -4,11 +4,12 @@ * https://github.com/antvis/S2/issues/1201 * fillOpacity */ -import { getContainer } from '../util/helpers'; +import type { S2Options } from '../../src'; import * as mockDataConfig from '../data/data-issue-292.json'; +import { getContainer } from '../util/helpers'; import { PivotSheet } from '@/sheet-type'; -const s2Options = { +const s2Options: S2Options = { width: 800, height: 600, }; @@ -17,22 +18,20 @@ describe('background color opacity test', () => { test('should set background color opacity correctly', async () => { const s2 = new PivotSheet(getContainer(), mockDataConfig, s2Options); - s2.setThemeCfg({ - theme: { - cornerCell: { - cell: { - backgroundColorOpacity: 0.1, - }, + s2.setTheme({ + cornerCell: { + cell: { + backgroundColorOpacity: 0.1, }, - rowCell: { - cell: { - backgroundColorOpacity: 0.2, - }, + }, + rowCell: { + cell: { + backgroundColorOpacity: 0.2, }, - colCell: { - cell: { - backgroundColorOpacity: 0.3, - }, + }, + colCell: { + cell: { + backgroundColorOpacity: 0.3, }, }, }); @@ -40,21 +39,18 @@ describe('background color opacity test', () => { await s2.render(); // corner cell - const cornerCell = s2.facet.cornerHeader.children[0]; + const cornerCell = s2.facet.getCornerCells()[0]; - // @ts-ignore - expect(cornerCell.backgroundShape.attr('fillOpacity')).toEqual(0.1); + expect(cornerCell.getBackgroundShape().style.fillOpacity).toEqual(0.1); // row cell - const rowCell = s2.facet.rowHeader!.children[0]; + const rowCell = s2.facet.getRowCells()[0]; - // @ts-ignore - expect(rowCell.backgroundShape.attr('fillOpacity')).toEqual(0.2); + expect(rowCell.getBackgroundShape().style.fillOpacity).toEqual(0.2); // col cell - const colCell = s2.facet.columnHeader.children[0].children[0]; + const colCell = s2.facet.getColCells()[0]; - // @ts-ignore - expect(colCell.backgroundShape.attr('fillOpacity')).toEqual(0.3); + expect(colCell.getBackgroundShape().style.fillOpacity).toEqual(0.3); }); }); diff --git a/packages/s2-core/__tests__/bugs/issue-1561-spec.ts b/packages/s2-core/__tests__/bugs/issue-1561-spec.ts index dadd3db4a6..058eeb32c2 100644 --- a/packages/s2-core/__tests__/bugs/issue-1561-spec.ts +++ b/packages/s2-core/__tests__/bugs/issue-1561-spec.ts @@ -23,22 +23,23 @@ describe('Grid Border Tests', () => { const panelScrollGroup = s2.facet.panelGroup.children[0]; const gridGroup = (panelScrollGroup as any).gridGroup as Group; - const originalLeftBorderBbox = (gridGroup.children[0] as Group).getBBox(); + const originalLeftBorderBBox = (gridGroup.children[0] as Group).getBBox(); s2.facet.updateScrollOffset({ offsetX: { value: 100, animate: false } }); s2.facet.updateScrollOffset({ offsetX: { value: 200, animate: false } }); s2.facet.updateScrollOffset({ offsetX: { value: 300, animate: false } }); s2.facet.updateScrollOffset({ offsetX: { value: 0, animate: false } }); - const newLeftBorderBbox = (gridGroup.children[0] as Group).getBBox(); + + const newLeftBorderBBbox = (gridGroup.children[0] as Group).getBBox(); const widthRatio = - newLeftBorderBbox.right - - newLeftBorderBbox.left - - (originalLeftBorderBbox.right - originalLeftBorderBbox.left); + newLeftBorderBBbox.right - + newLeftBorderBBbox.left - + (originalLeftBorderBBox.right - originalLeftBorderBBox.left); const heightRatio = - newLeftBorderBbox.bottom - - newLeftBorderBbox.top - - (originalLeftBorderBbox.bottom - originalLeftBorderBbox.top); + newLeftBorderBBbox.bottom - + newLeftBorderBBbox.top - + (originalLeftBorderBBox.bottom - originalLeftBorderBBox.top); // g绘制时,会将坐标1变成0.5,来达到真正绘制1px的效果,因此宽高不一定完全相同,会有1px的差值 expect(widthRatio).toBeLessThanOrEqual(1); diff --git a/packages/s2-core/__tests__/bugs/issue-1587-spec.ts b/packages/s2-core/__tests__/bugs/issue-1587-spec.ts index bb5ff56da7..c39d5c3903 100644 --- a/packages/s2-core/__tests__/bugs/issue-1587-spec.ts +++ b/packages/s2-core/__tests__/bugs/issue-1587-spec.ts @@ -11,9 +11,8 @@ import { PivotSheet } from '@/sheet-type'; const s2Options: S2Options = { width: 800, height: 600, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error pagination: { + current: 1, pageSize: 2, }, }; diff --git a/packages/s2-core/__tests__/bugs/issue-1624-spec.ts b/packages/s2-core/__tests__/bugs/issue-1624-spec.ts new file mode 100644 index 0000000000..9ce404a029 --- /dev/null +++ b/packages/s2-core/__tests__/bugs/issue-1624-spec.ts @@ -0,0 +1,51 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/** + * @description spec for issue #1624 + * https://github.com/antvis/S2/issues/1624 + */ + +import * as mockDataConfig from '../data/simple-data.json'; +import { getContainer, sleep } from '../util/helpers'; +import { S2Event, type S2Options } from '@/index'; +import { PivotSheet } from '@/sheet-type'; + +const s2Options: S2Options = { + width: 800, + height: 600, + hdAdapter: false, +}; + +describe('Data Cell Border Tests', () => { + const borderWidth = 4; + + test('should draw correct data cell border when hover focus', async () => { + const s2 = new PivotSheet(getContainer(), mockDataConfig, s2Options); + + s2.setTheme({ + dataCell: { + cell: { + verticalBorderWidth: borderWidth, + horizontalBorderWidth: borderWidth, + }, + }, + }); + await s2.render(); + + const dataCell = s2.facet.getDataCells()[0]; + + s2.emit(S2Event.DATA_CELL_HOVER, { + target: dataCell, + } as any); + + await sleep(40); + + const meta = dataCell.getBBoxByType(); + const borderBBox = dataCell + .getStateShapes() + .get('interactiveBorderShape')! + .getBBox(); + + expect(meta.width).toBeGreaterThanOrEqual(borderBBox.width + borderWidth); + expect(meta.height).toBeGreaterThanOrEqual(borderBBox.height + borderWidth); + }); +}); diff --git a/packages/s2-core/__tests__/bugs/issue-1715-spec.ts b/packages/s2-core/__tests__/bugs/issue-1715-spec.ts index 682d3f62d6..4eaf9d183b 100644 --- a/packages/s2-core/__tests__/bugs/issue-1715-spec.ts +++ b/packages/s2-core/__tests__/bugs/issue-1715-spec.ts @@ -4,6 +4,7 @@ * https://github.com/antvis/S2/issues/1715 * https://github.com/antvis/S2/issues/2049 */ + import { getContainer } from '../util/helpers'; import * as mockDataConfig from '../data/mock-dataset.json'; import type { S2DataConfig, S2Options, SpreadSheet } from '../../src'; @@ -81,9 +82,10 @@ describe('Multi Values GrandTotal Height Test', () => { const grandTotalsNode = s2.facet .getColNodes() - .find((node) => node.isGrandTotals)!; + .find((node) => node.isGrandTotals && node.isTotalRoot); - expect(s2.facet.getLayoutResult().colsHierarchy.height).toBe(60); - expect(grandTotalsNode.height).toEqual(30); + // 有多个 Value 时不允许隐藏度量列 + expect(s2.facet.getLayoutResult().colsHierarchy.height).toBe(90); + expect(grandTotalsNode!.height).toEqual(60); }); }); diff --git a/packages/s2-core/__tests__/bugs/issue-1781-spec.ts b/packages/s2-core/__tests__/bugs/issue-1781-spec.ts index 30dee7a17b..c8a357c5b3 100644 --- a/packages/s2-core/__tests__/bugs/issue-1781-spec.ts +++ b/packages/s2-core/__tests__/bugs/issue-1781-spec.ts @@ -4,12 +4,13 @@ * https://github.com/antvis/S2/issues/1781 */ +import type { FederatedPointerEvent } from '@antv/g'; +import * as mockDataConfig from '../data/simple-table-data.json'; import { createFederatedMouseEvent, getContainer, sleep, } from '../util/helpers'; -import * as mockDataConfig from '../data/simple-table-data.json'; import { OriginEventType, S2Event, @@ -46,27 +47,29 @@ describe('Hover Focus Tests', () => { await sleep(3000); // 浙江省份信息 - const provinceCell = s2.facet.panelScrollGroup.getChildByIndex(7); + const provinceCell = s2.facet.getDataCells()[7]; // 义乌城市信息 - const cityCell = s2.facet.panelScrollGroup.getChildByIndex(10); + const cityCell = s2.facet.getDataCells()[10]; const event = createFederatedMouseEvent(s2, OriginEventType.POINTER_MOVE); event.target = provinceCell; - // @ts-ignore - s2.emit(S2Event.DATA_CELL_HOVER, event); + s2.emit(S2Event.DATA_CELL_HOVER, event as FederatedPointerEvent); expect( - // @ts-ignore - provinceCell.stateShapes + provinceCell + .getStateShapes() .get('interactiveBorderShape') ?.attr('visibility'), ).toEqual('visible'); + expect( - // @ts-ignore - cityCell.stateShapes.get('interactiveBorderShape')?.attr('visibility'), + cityCell + .getStateShapes() + .get('interactiveBorderShape') + ?.attr('visibility'), ).toEqual('hidden'); }); }); diff --git a/packages/s2-core/__tests__/bugs/issue-2164-spec.ts b/packages/s2-core/__tests__/bugs/issue-2164-spec.ts index 394da2dc35..a9dde4113f 100644 --- a/packages/s2-core/__tests__/bugs/issue-2164-spec.ts +++ b/packages/s2-core/__tests__/bugs/issue-2164-spec.ts @@ -6,14 +6,14 @@ import { getContainer } from '../util/helpers'; import * as mockDataConfig from '../data/simple-data.json'; -import type { S2Options } from '@/index'; +import { LayoutWidthType, type S2Options } from '@/index'; import { PivotSheet } from '@/sheet-type'; const s2Options: S2Options = { width: 400, height: 400, style: { - layoutWidthType: 'compact', + layoutWidthType: LayoutWidthType.Compact, }, totals: { row: { @@ -32,7 +32,8 @@ describe('Grand Total Row Node Tests', () => { ...mockDataConfig, fields: { ...mockDataConfig.fields, - valueInCols: false, // 指标放行头 + // 指标放行头 + valueInCols: false, }, }, s2Options, diff --git a/packages/s2-core/__tests__/bugs/issue-2195-spec.ts b/packages/s2-core/__tests__/bugs/issue-2195-spec.ts new file mode 100644 index 0000000000..ed8c1e1d7f --- /dev/null +++ b/packages/s2-core/__tests__/bugs/issue-2195-spec.ts @@ -0,0 +1,74 @@ +/** + * 字段代码中有方括号,无法拖拽调整该字段所在列的宽度 + * 描述应当调整为:字段代码中有方括号,无法使用 `xxxByField` 配置调整该字段的布局 + * @description spec for issue #2195 + * https://github.com/antvis/S2/issues/2195 + */ + +import { getContainer } from '../util/helpers'; +import type { S2DataConfig, S2Options } from '@/index'; +import { PivotSheet } from '@/sheet-type'; + +const modifiedMockDataConfig: S2DataConfig = { + fields: { + rows: ['province', '[city]'], + columns: ['type'], + values: ['price', 'cost'], + valueInCols: true, + }, + data: [ + { + province: '浙江', + '[city]': '义乌', + type: '笔', + price: 1, + cost: 2, + }, + { + province: '浙江', + '[city]': '义乌', + type: '笔', + price: 1, + cost: 2, + }, + { + province: '浙江', + '[city]': '杭州', + type: '笔', + price: 1, + cost: 2, + }, + ], +}; + +const s2Options: S2Options = { + width: 400, + height: 400, + style: { + rowCell: { + widthByField: { + province: 300, + '[city]': 123, + }, + }, + }, +}; + +describe('Field surrounded by square brackets Tests', () => { + test('should render correctly when use field surrounded by square brackets', async () => { + const s2 = new PivotSheet( + getContainer(), + modifiedMockDataConfig, + s2Options, + ); + + await s2.render(); + + s2.facet + .getLayoutResult() + .rowNodes.filter((node) => node.field === '[city]') + .forEach((node) => { + expect(node.width).toEqual(123); + }); + }); +}); diff --git a/packages/s2-core/__tests__/bugs/issue-2199-spec.ts b/packages/s2-core/__tests__/bugs/issue-2199-spec.ts new file mode 100644 index 0000000000..fa82bb1813 --- /dev/null +++ b/packages/s2-core/__tests__/bugs/issue-2199-spec.ts @@ -0,0 +1,41 @@ +/** + * @description spec for issue #2199 + * https://github.com/antvis/S2/issues/2199 + * 明细表: 当有冻结列 + 列分组的情况下, 会出现列头文本不居中现象 + */ +import { getContainer } from 'tests/util/helpers'; +import dataCfg from '../data/data-issue-2199.json'; +import { TableSheet } from '@/sheet-type'; +import type { S2Options } from '@/common/interface'; + +const s2Options: S2Options = { + width: 300, + height: 480, + showSeriesNumber: true, + frozen: { + colCount: 1, + }, +}; + +describe('ColCell Text Center Tests', () => { + test('should draw text centered in cell', async () => { + const s2 = new TableSheet(getContainer(), dataCfg, s2Options); + + await s2.render(); + + s2.facet.updateScrollOffset({ + offsetX: { + value: 500, + animate: false, + }, + }); + + const node = s2.facet.getColNodes(0).slice(-1)?.[0]; + const cell = node?.belongsCell; + const { width: nodeWidth, x: nodeX } = node; + const { width: textWidth, x: textXActual } = cell!.getBBoxByType(); + const textXCalc = nodeX + (nodeWidth - textWidth) / 2; + + expect(textXCalc).toBeCloseTo(textXActual); + }); +}); diff --git a/packages/s2-core/__tests__/bugs/issue-2322-spec.ts b/packages/s2-core/__tests__/bugs/issue-2322-spec.ts new file mode 100644 index 0000000000..3b5a8874dd --- /dev/null +++ b/packages/s2-core/__tests__/bugs/issue-2322-spec.ts @@ -0,0 +1,50 @@ +/** + * @description spec for issue #2322 + * https://github.com/antvis/S2/issues/2322 + * 明细表: 多列筛选后清空其中一列筛选,导致其他筛选也清空 + */ +import { getContainer, sleep } from 'tests/util/helpers'; +import dataCfg from '../data/data-issue-2322.json'; +import { S2Event } from '@/common'; +import type { S2Options } from '@/common/interface'; +import { TableSheet } from '@/sheet-type'; + +const s2Options: S2Options = { + width: 800, + height: 480, + showSeriesNumber: true, + frozen: { + colCount: 1, + }, +}; + +describe('Table Sheet Filter Test', () => { + test('should filter correctly when part of the filter is cleaned', async () => { + const s2 = new TableSheet(getContainer(), dataCfg, s2Options); + + await s2.render(); + + // 为两个不同的列设定过滤 + s2.emit(S2Event.RANGE_FILTER, { + filterKey: 'province', + filteredValues: ['吉林'], + }); + s2.emit(S2Event.RANGE_FILTER, { + filterKey: 'city', + filteredValues: ['杭州'], + }); + + // 删除一列过滤 + s2.emit(S2Event.RANGE_FILTER, { + filterKey: 'province', + filteredValues: [], + }); + + await sleep(200); + + // 应过滤掉 city = 杭州 的值,共4行 + expect(s2.dataSet.getDisplayDataSet()).toHaveLength( + s2.dataSet.originData.length - 4, + ); + }); +}); diff --git a/packages/s2-core/__tests__/bugs/issue-2340-spec.ts b/packages/s2-core/__tests__/bugs/issue-2340-spec.ts new file mode 100644 index 0000000000..33f194d379 --- /dev/null +++ b/packages/s2-core/__tests__/bugs/issue-2340-spec.ts @@ -0,0 +1,80 @@ +/** + * @description spec for issue #2340 + * https://github.com/antvis/S2/issues/2340 + */ +import { + CellType, + InteractionStateName, + getCellMeta, + type S2CellType, + type S2Options, +} from '../../src'; +import { createPivotSheet, sleep } from '../util/helpers'; + +const s2Options: S2Options = { + width: 800, + height: 600, + style: { + dataCell: { + width: 200, + height: 200, + }, + }, +}; + +describe('Header Brush Selection Tests', () => { + test.each([CellType.COL_CELL, CellType.ROW_CELL])( + 'should not trigger data cell selected when header selected and scroll out of viewport', + async (cellType) => { + const s2 = createPivotSheet(s2Options, { useSimpleData: false }); + + await s2.render(); + + const isRow = cellType === CellType.ROW_CELL; + const targetCells = isRow + ? s2.facet.getRowCells() + : s2.facet.getColCells(); + + const cells = [ + targetCells.find((cell) => { + const meta = cell.getMeta(); + + return meta.isLeaf; + }), + ] as S2CellType[]; + + s2.interaction.changeState({ + cells: cells.map(getCellMeta), + stateName: InteractionStateName.BRUSH_SELECTED, + }); + + await sleep(500); + + const offsetKey = isRow ? 'offsetY' : 'offsetX'; + + // 将圈选的单元格滑出可视范围 + s2.facet.updateScrollOffset({ + [offsetKey]: { value: 300 }, + }); + + await sleep(500); + + // 还原 + s2.facet.updateScrollOffset({ + [offsetKey]: { value: 0 }, + }); + + expect(s2.interaction.getActiveCells()).toHaveLength(1); + expect(s2.interaction.getCurrentStateName()).toEqual( + InteractionStateName.BRUSH_SELECTED, + ); + + // 交互过的不应该有 dataCell (未触发过列头多选) + s2.interaction.getInteractedCells().forEach((cell) => { + expect(cell.cellType).toEqual( + isRow ? CellType.ROW_CELL : CellType.COL_CELL, + ); + }); + }, + ); +}); diff --git a/packages/s2-core/__tests__/bugs/issue-2359-spec.ts b/packages/s2-core/__tests__/bugs/issue-2359-spec.ts new file mode 100644 index 0000000000..2d4ad61d5e --- /dev/null +++ b/packages/s2-core/__tests__/bugs/issue-2359-spec.ts @@ -0,0 +1,66 @@ +/** + * @description spec for issue #2359 + * https://github.com/antvis/S2/issues/2359 + * 明细表: 自定义列头误用 dataCell 样式 + */ +import { pick } from 'lodash'; +import { createTableSheet } from 'tests/util/helpers'; +import { TableColCell, drawObjectText } from '../../src'; +import type { S2CellType, S2Options } from '@/common/interface'; + +class TestColCell extends TableColCell { + drawTextShape() { + drawObjectText(this, { + values: [['A', 'B', 'C']], + }); + } +} + +const s2Options: S2Options = { + width: 300, + height: 480, + showSeriesNumber: true, + colCell: (...args) => new TestColCell(...args), +}; + +describe('Table Sheet Custom Multiple Values Tests', () => { + test('should use current cell text theme', async () => { + const s2 = createTableSheet(s2Options); + + s2.setTheme({ + colCell: { + measureText: { + fontSize: 12, + }, + bolderText: { + fontSize: 14, + }, + text: { + fontSize: 20, + fill: 'red', + }, + }, + dataCell: { + text: { + fontSize: 30, + fill: 'green', + }, + }, + }); + await s2.render(); + + const mapTheme = (cell: S2CellType) => { + return cell + .getTextShapes() + .map((shape) => pick(shape.attributes, ['fill', 'fontSize'])); + }; + + const colCellTexts = s2.facet.getColCells().map(mapTheme); + + const dataCellTexts = s2.facet.getDataCells().map(mapTheme); + + expect(colCellTexts).toMatchSnapshot(); + + expect(dataCellTexts).toMatchSnapshot(); + }); +}); diff --git a/packages/s2-core/__tests__/bugs/issue-2385-spec.ts b/packages/s2-core/__tests__/bugs/issue-2385-spec.ts new file mode 100644 index 0000000000..592524fa35 --- /dev/null +++ b/packages/s2-core/__tests__/bugs/issue-2385-spec.ts @@ -0,0 +1,62 @@ +/** + * @description spec for issue #2385 + * https://github.com/antvis/S2/issues/2385 + */ +import { LayoutWidthType, type S2Options } from '../../src'; +import * as mockDataConfig from '../data/data-issue-2385.json'; +import { getContainer } from '../util/helpers'; +import { PivotSheet, TableSheet } from '@/sheet-type'; + +const s2Options: S2Options = { + width: 800, + height: 600, + style: { + dataCell: { + width: 200, + }, + layoutWidthType: LayoutWidthType.Compact, + }, +}; + +describe('Compare Layout Tests', () => { + test('should get max col width for pivot sheet', async () => { + const s2 = new PivotSheet(getContainer(), mockDataConfig, s2Options); + + s2.setTheme({ + dataCell: { + text: { + fontSize: 20, + }, + }, + }); + await s2.render(); + + const colLeafNodes = s2.facet.getColLeafNodes(); + + expect(Math.floor(colLeafNodes[0].width)).toBeCloseTo(189); + expect(Math.floor(colLeafNodes[1].width)).toEqual(90); + }); + + test('should get max col width for table sheet', async () => { + const s2 = new TableSheet(getContainer(), mockDataConfig, s2Options); + + s2.setDataCfg({ + fields: { + columns: ['price'], + }, + }); + s2.setTheme({ + dataCell: { + text: { + fontSize: 20, + }, + }, + }); + + await s2.render(); + + const colLeafNodes = s2.facet.getColLeafNodes(); + + expect(Math.floor(colLeafNodes[0].width)).toBeCloseTo(182); + }); +}); diff --git a/packages/s2-core/__tests__/bugs/issue-2501-spec.ts b/packages/s2-core/__tests__/bugs/issue-2501-spec.ts new file mode 100644 index 0000000000..143c76f1ad --- /dev/null +++ b/packages/s2-core/__tests__/bugs/issue-2501-spec.ts @@ -0,0 +1,63 @@ +/** + * @description spec for issue #2501 + * https://github.com/antvis/S2/issues/2501 + */ + +import type { TableFacet } from '../../src/facet'; +import * as mockDataConfig from '../data/simple-table-data.json'; +import { getContainer } from '../util/helpers'; +import type { SpreadSheet, S2DataConfig, S2Options } from '@/index'; +import { TableSheet } from '@/sheet-type'; + +const s2DataConfig: S2DataConfig = { + ...mockDataConfig, +}; + +const s2Options: S2Options = { + width: 800, + height: 400, + style: { + rowCell: { + heightByField: { + '0': 100, + '1': 150, + }, + }, + }, +}; + +describe('Table Sheet Row Offsets Tests', () => { + let s2: SpreadSheet; + + beforeEach(() => { + s2 = new TableSheet(getContainer(), s2DataConfig, s2Options); + + s2.render(); + }); + + test('should get correctly row offset data', () => { + expect((s2.facet as TableFacet).rowOffsets).toMatchInlineSnapshot(` + Array [ + 0, + 100, + 250, + 280, + ] + `); + }); + + test('should get correctly data cell offset for heightByField', () => { + const { getCellOffsetY, getTotalHeight } = s2.facet.getViewCellHeights(); + + expect(getCellOffsetY(0)).toEqual(0); + expect(getCellOffsetY(1)).toEqual(100); + expect(getCellOffsetY(2)).toEqual(250); + expect(getTotalHeight()).toEqual(280); + }); + + test('should get correctly row layout for heightByField', () => { + const { getTotalLength } = s2.facet.getViewCellHeights(); + + expect(getTotalLength()).toEqual(3); + }); +}); diff --git a/packages/s2-core/__tests__/bugs/issue-446-spec.ts b/packages/s2-core/__tests__/bugs/issue-446-spec.ts index 385d726105..5d8f283250 100644 --- a/packages/s2-core/__tests__/bugs/issue-446-spec.ts +++ b/packages/s2-core/__tests__/bugs/issue-446-spec.ts @@ -7,7 +7,7 @@ import { getContainer } from '../util/helpers'; import * as mockDataConfig from '../data/data-issue-446.json'; import { TableSheet } from '@/sheet-type'; -import { copyData } from '@/utils'; +import { asyncGetAllPlainData } from '@/utils'; const s2Options = { width: 800, @@ -20,7 +20,7 @@ describe('export', () => { const s2 = new TableSheet(getContainer(), mockDataConfig, s2Options); await s2.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: '\t', formatOptions: true, @@ -43,7 +43,7 @@ describe('export', () => { }); await s2.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: '\t', }); diff --git a/packages/s2-core/__tests__/bugs/issue-565-spec.ts b/packages/s2-core/__tests__/bugs/issue-565-spec.ts index d86f5100eb..2705af69a9 100644 --- a/packages/s2-core/__tests__/bugs/issue-565-spec.ts +++ b/packages/s2-core/__tests__/bugs/issue-565-spec.ts @@ -6,13 +6,14 @@ */ import * as mockDataConfig from 'tests/data/data-issue-565.json'; import { getContainer } from 'tests/util/helpers'; +import type { S2Options } from '../../src'; import { PivotSheet } from '@/sheet-type'; -import { copyData } from '@/utils'; +import { asyncGetAllPlainData } from '@/utils'; -const s2Options = { +const s2Options: S2Options = { width: 800, height: 600, - hierarchyType: 'tree' as const, + hierarchyType: 'tree', }; describe('Export data in pivot tree mode', () => { @@ -20,7 +21,8 @@ describe('Export data in pivot tree mode', () => { const s2 = new PivotSheet(getContainer(), mockDataConfig, s2Options); await s2.render(); - const data = copyData({ + + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: '\t', }); @@ -32,16 +34,6 @@ describe('Export data in pivot tree mode', () => { expect(rows[1].split('\t')[0]).toEqual(''); expect(rows[7].split('\t')[0]).toEqual('row0'); expect(rows[8].split('\t')[0]).toEqual('row0'); - expect(data).toMatchInlineSnapshot(` - " col0 col0-0 col0-0 - col1 col1-0 col1-1 - col2 col2-0 col2-0 - row0 row1 row2 number number - row0 - row0 row1-0 - row0 row1-0 row2-0 3 - row0 row1-1 - row0 row1-1 row2-0 2 4" - `); + expect(data).toMatchSnapshot(); }); }); diff --git a/packages/s2-core/__tests__/bugs/issue-725-spec.ts b/packages/s2-core/__tests__/bugs/issue-725-spec.ts index 6d222d4230..ad503c135b 100644 --- a/packages/s2-core/__tests__/bugs/issue-725-spec.ts +++ b/packages/s2-core/__tests__/bugs/issue-725-spec.ts @@ -3,15 +3,13 @@ * https://github.com/antvis/S2/issues/725 * Wrong multi measure render * Wrong group sort - * */ import * as mockDataConfig from 'tests/data/data-issue-725.json'; import { assembleDataCfg } from '../util'; import type { S2DataConfig } from '@/common/interface'; -import { PivotSheet } from '@/sheet-type'; import { PivotDataSet } from '@/data-set'; -import { getDimensionsWithoutPathPre } from '@/utils/dataset/pivot-data-set'; +import { PivotSheet } from '@/sheet-type'; jest.mock('@/sheet-type'); @@ -39,18 +37,17 @@ describe('Group Sort When Have Same Child Measure', () => { }); test('should get correct group sort', () => { - expect( - getDimensionsWithoutPathPre(dataSet.getDimensionValues('type')), - ).toEqual(['办公用品', '家具产品', '家具产品', '办公用品']); - expect( - getDimensionsWithoutPathPre( - dataSet.getDimensionValues('type', { city: '白山' }), - ), - ).toEqual(['办公用品', '家具产品']); - expect( - getDimensionsWithoutPathPre( - dataSet.getDimensionValues('type', { city: '抚顺' }), - ), - ).toEqual(['家具产品', '办公用品']); + expect(dataSet.getDimensionValues('type')).toEqual([ + '办公用品', + '家具产品', + ]); + expect(dataSet.getDimensionValues('type', { city: '白山' })).toEqual([ + '办公用品', + '家具产品', + ]); + expect(dataSet.getDimensionValues('type', { city: '抚顺' })).toEqual([ + '家具产品', + '办公用品', + ]); }); }); diff --git a/packages/s2-core/__tests__/data/data-custom-trees.ts b/packages/s2-core/__tests__/data/data-custom-trees.ts new file mode 100644 index 0000000000..7e5a8832a2 --- /dev/null +++ b/packages/s2-core/__tests__/data/data-custom-trees.ts @@ -0,0 +1,22 @@ +export const dataCustomTrees = [ + { + type: '家具', + sub_type: '桌子', + 'measure-a': 1, + 'measure-b': 2, + 'measure-c': 3, + 'measure-d': 4, + 'measure-e': 5, + 'measure-f': 6, + }, + { + type: '家具', + sub_type: '椅子', + 'measure-a': 11, + 'measure-b': 22, + 'measure-c': 33, + 'measure-d': 44, + 'measure-e': 55, + 'measure-f': 66, + }, +]; diff --git a/packages/s2-core/__tests__/data/data-issue-2199.json b/packages/s2-core/__tests__/data/data-issue-2199.json new file mode 100644 index 0000000000..bb93e0401f --- /dev/null +++ b/packages/s2-core/__tests__/data/data-issue-2199.json @@ -0,0 +1,126 @@ +{ + "fields": { + "columns": [ + { + "field": "area", + "title": "位置", + "children": [ + { "field": "province", "title": "省份" }, + { "field": "city", "title": "城市" } + ] + }, + { + "field": "type", + "title": "商品类别" + }, + { + "field": "money", + "title": "金额", + "children": [ + { + "field": "price", + "title": "价格" + } + ] + } + ] + }, + "data": [ + { + "province": "浙江", + "city": "杭州", + "type": "笔", + "price": 1 + }, + { + "province": "浙江", + "city": "杭州", + "type": "纸张", + "price": 2 + }, + { + "province": "浙江", + "city": "舟山", + "type": "笔", + "price": 17 + }, + { + "province": "浙江", + "city": "舟山", + "type": "纸张", + "price": 6 + }, + { + "province": "吉林", + "city": "长春", + "type": "笔", + "price": 8 + }, + { + "province": "吉林", + "city": "白山", + "type": "笔", + "price": 12 + }, + { + "province": "吉林", + "city": "长春", + "type": "纸张", + "price": 3 + }, + { + "province": "吉林", + "city": "白山", + "type": "纸张", + "price": 25 + }, + { + "province": "浙江", + "city": "杭州", + "type": "笔", + "price": 20 + }, + { + "province": "浙江", + "city": "杭州", + "type": "纸张", + "price": 10 + }, + { + "province": "浙江", + "city": "舟山", + "type": "笔", + "price": 15 + }, + { + "province": "浙江", + "city": "舟山", + "type": "纸张", + "price": 2 + }, + { + "province": "吉林", + "city": "长春", + "type": "笔", + "price": 15 + }, + { + "province": "吉林", + "city": "白山", + "type": "笔", + "price": 30 + }, + { + "province": "吉林", + "city": "长春", + "type": "纸张", + "price": 40 + }, + { + "province": "吉林", + "city": "白山", + "type": "纸张", + "price": 50 + } + ] +} diff --git a/packages/s2-core/__tests__/data/data-issue-2322.json b/packages/s2-core/__tests__/data/data-issue-2322.json new file mode 100644 index 0000000000..9dde6f3fc4 --- /dev/null +++ b/packages/s2-core/__tests__/data/data-issue-2322.json @@ -0,0 +1,132 @@ +{ + "fields": { + "columns": [ + { + "field": "area", + "title": "区域", + "children": [ + { + "field": "province", + "title": "省份" + }, + { + "field": "city", + "title": "城市" + } + ] + }, + { + "field": "type", + "title": "商品类别" + }, + { + "field": "money", + "title": "金额", + "children": [ + { + "field": "price", + "title": "价格" + } + ] + } + ] + }, + "data": [ + { + "province": "浙江", + "city": "杭州", + "type": "笔", + "price": 1 + }, + { + "province": "浙江", + "city": "杭州", + "type": "纸张", + "price": 2 + }, + { + "province": "浙江", + "city": "舟山", + "type": "笔", + "price": 17 + }, + { + "province": "浙江", + "city": "舟山", + "type": "纸张", + "price": 6 + }, + { + "province": "吉林", + "city": "长春", + "type": "笔", + "price": 8 + }, + { + "province": "吉林", + "city": "白山", + "type": "笔", + "price": 12 + }, + { + "province": "吉林", + "city": "长春", + "type": "纸张", + "price": 3 + }, + { + "province": "吉林", + "city": "白山", + "type": "纸张", + "price": 25 + }, + { + "province": "浙江", + "city": "杭州", + "type": "笔", + "price": 20 + }, + { + "province": "浙江", + "city": "杭州", + "type": "纸张", + "price": 10 + }, + { + "province": "浙江", + "city": "舟山", + "type": "笔", + "price": 15 + }, + { + "province": "浙江", + "city": "舟山", + "type": "纸张", + "price": 2 + }, + { + "province": "吉林", + "city": "长春", + "type": "笔", + "price": 15 + }, + { + "province": "吉林", + "city": "白山", + "type": "笔", + "price": 30 + }, + { + "province": "吉林", + "city": "长春", + "type": "纸张", + "price": 40 + }, + { + "province": "吉林", + "city": "白山", + "type": "纸张", + "price": 50 + } + ] +} diff --git a/packages/s2-core/__tests__/data/data-issue-2385.json b/packages/s2-core/__tests__/data/data-issue-2385.json new file mode 100644 index 0000000000..d6f20caac5 --- /dev/null +++ b/packages/s2-core/__tests__/data/data-issue-2385.json @@ -0,0 +1,138 @@ +{ + "data": [ + { + "province": "浙江", + "city": "杭州", + "type": "笔", + "price": "11111111111111111", + "cost": "33.333" + }, + { + "province": "浙江", + "city": "杭州", + "type": "纸张", + "price": "2", + "cost": "1.5" + }, + { + "province": "浙江", + "city": "舟山", + "type": "笔", + "price": "2", + "cost": "1.5" + }, + { + "province": "浙江", + "city": "舟山", + "type": "纸张", + "price": "666.333", + "cost": "0.2" + }, + { + "province": "吉林", + "city": "长春", + "type": "笔", + "price": "3", + "cost": "2" + }, + { + "province": "吉林", + "city": "长春", + "type": "纸张", + "price": "2", + "cost": "1" + }, + { + "province": "吉林", + "city": "白山", + "type": "笔", + "price": "4", + "cost": "3" + }, + { + "province": "吉林", + "city": "白山", + "type": "纸张", + "price": "1", + "cost": "33.333" + }, + { + "price": "15.5", + "cost": "10.2" + }, + { + "province": "浙江", + "price": "5.5", + "cost": "3.7" + }, + { + "province": "浙江", + "city": "杭州", + "price": "3", + "cost": "2" + }, + { + "province": "浙江", + "city": "舟山", + "price": "2.5", + "cost": "1.7" + }, + { + "province": "吉林", + "price": "10", + "cost": "6.5" + }, + { + "province": "吉林", + "city": "长春", + "price": "5", + "cost": "3" + }, + { + "province": "吉林", + "city": "白山", + "price": "5", + "cost": "3.5" + }, + { + "type": "笔", + "price": "10", + "cost": "7" + }, + { + "type": "笔", + "province": "浙江", + "price": "3", + "cost": "2" + }, + { + "type": "笔", + "province": "吉林", + "price": "7", + "cost": "5" + }, + { + "type": "纸张", + "price": "5.5", + "cost": "3.2" + }, + { + "type": "纸张", + "province": "浙江", + "price": "2.5", + "cost": "1.7" + }, + { + "type": "纸张", + "province": "吉林", + "price": "3", + "cost": "1.5" + } + ], + "fields": { + "rows": ["province", "city"], + "columns": ["type"], + "values": ["price"], + "valueInCols": true + } +} diff --git a/packages/s2-core/__tests__/data/data-issue-292.json b/packages/s2-core/__tests__/data/data-issue-292.json index 8a387e8391..98ccfbf083 100644 --- a/packages/s2-core/__tests__/data/data-issue-292.json +++ b/packages/s2-core/__tests__/data/data-issue-292.json @@ -10,12 +10,7 @@ "province": "浙江", "city": "义乌", "type": "笔", - "cost": "2" - }, - { - "province": "浙江", - "city": "义乌", - "type": "笔", + "cost": "2", "price": "8" }, { diff --git a/packages/s2-core/__tests__/data/data-issue-725.json b/packages/s2-core/__tests__/data/data-issue-725.json index ad4d2a36dc..5510e53fd0 100644 --- a/packages/s2-core/__tests__/data/data-issue-725.json +++ b/packages/s2-core/__tests__/data/data-issue-725.json @@ -19,11 +19,6 @@ } ], "data": [ - { - "city": "白山", - "type": "办公用品", - "price": "" - }, { "city": "白山", "type": "办公用品", diff --git a/packages/s2-core/__tests__/data/sort-advanced.ts b/packages/s2-core/__tests__/data/sort-advanced.ts index bd067d3b47..e6068afe62 100644 --- a/packages/s2-core/__tests__/data/sort-advanced.ts +++ b/packages/s2-core/__tests__/data/sort-advanced.ts @@ -183,4 +183,5 @@ export const sortData = { price: '3', }, ], + totalData: [], }; diff --git a/packages/s2-core/__tests__/data/total-group-data.ts b/packages/s2-core/__tests__/data/total-group-data.ts new file mode 100644 index 0000000000..ec1c1a7e3c --- /dev/null +++ b/packages/s2-core/__tests__/data/total-group-data.ts @@ -0,0 +1,201 @@ +import { Aggregation, type S2DataConfig, type S2Options } from '@/common'; + +export const s2Options: S2Options = { + width: 800, + height: 600, + // 配置行小计总计显示,且按维度分组(列小计总计同理) + totals: { + row: { + showGrandTotals: true, + showSubTotals: true, + reverseGrandTotalsLayout: true, + reverseSubTotalsLayout: true, + subTotalsDimensions: ['province'], + calcGrandTotals: { + aggregation: Aggregation.SUM, + }, + calcSubTotals: { + aggregation: Aggregation.SUM, + }, + grandTotalsGroupDimensions: ['type'], + subTotalsGroupDimensions: ['type'], + }, + col: { + showGrandTotals: true, + showSubTotals: true, + reverseGrandTotalsLayout: true, + reverseSubTotalsLayout: true, + calcGrandTotals: { + aggregation: Aggregation.SUM, + }, + calcSubTotals: { + aggregation: Aggregation.SUM, + }, + grandTotalsGroupDimensions: ['sub_type'], + }, + }, +}; + +export const dataCfg: S2DataConfig = { + fields: { + rows: ['province', 'city', 'type'], + columns: ['sub_type'], + values: ['price', 'cost'], + }, + meta: [ + { + field: 'province', + name: '省份', + }, + { + field: 'city', + name: '城市', + }, + { + field: 'type', + name: '商品类别', + }, + { + field: 'sub_type', + name: '商品子类别', + }, + { + field: 'price', + name: '价格', + }, + { + field: 'cost', + name: '成本', + }, + ], + data: [ + { + price: 100, + cost: 100, + province: '浙江省', + city: '杭州市', + type: '家具', + sub_type: '桌子', + }, + { + price: 100, + cost: 100, + province: '浙江省', + city: '杭州市', + type: '家具', + sub_type: '沙发', + }, + { + price: 100, + cost: 100, + province: '浙江省', + city: '杭州市', + type: '办公用品', + sub_type: '笔', + }, + + { + price: 200, + cost: 200, + province: '浙江省', + city: '舟山市', + type: '家具', + sub_type: '桌子', + }, + + { + price: 200, + cost: 200, + province: '浙江省', + city: '舟山市', + type: '家具', + sub_type: '沙发', + }, + + { + price: 200, + cost: 200, + province: '浙江省', + city: '舟山市', + type: '办公用品', + sub_type: '笔', + }, + + { + price: 200, + cost: 200, + province: '浙江省', + city: '舟山市', + type: '办公用品', + sub_type: '纸张', + }, + { + price: 300, + cost: 300, + province: '四川省', + city: '成都市', + type: '家具', + sub_type: '桌子', + }, + { + price: 400, + cost: 400, + province: '四川省', + city: '绵阳市', + type: '家具', + sub_type: '桌子', + }, + + { + price: 300, + cost: 300, + province: '四川省', + city: '成都市', + type: '家具', + sub_type: '沙发', + }, + { + price: 400, + cost: 400, + province: '四川省', + city: '绵阳市', + type: '家具', + sub_type: '沙发', + }, + + { + price: 300, + cost: 300, + province: '四川省', + city: '成都市', + type: '办公用品', + sub_type: '笔', + }, + { + price: 400, + cost: 400, + province: '四川省', + city: '绵阳市', + type: '办公用品', + sub_type: '笔', + }, + + { + price: 300, + cost: 300, + province: '四川省', + city: '成都市', + type: '办公用品', + sub_type: '纸张', + }, + { + price: 400, + cost: 400, + province: '四川省', + city: '绵阳市', + type: '办公用品', + sub_type: '纸张', + }, + ], + totalData: [], +}; diff --git a/packages/s2-core/__tests__/spreadsheet/__snapshots__/corner-spec.ts.snap b/packages/s2-core/__tests__/spreadsheet/__snapshots__/corner-spec.ts.snap index e906a49ea1..8bb717bbfc 100644 --- a/packages/s2-core/__tests__/spreadsheet/__snapshots__/corner-spec.ts.snap +++ b/packages/s2-core/__tests__/spreadsheet/__snapshots__/corner-spec.ts.snap @@ -11,7 +11,7 @@ Array [ "field": "province", "height": 30, "hierarchy": undefined, - "id": "", + "id": "province", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -19,6 +19,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -28,7 +29,7 @@ Array [ "seriesNumberWidth": undefined, "spreadsheet": Anything, "value": "province", - "width": 99, + "width": 99.33, "x": 0, "y": 0, }, @@ -41,7 +42,7 @@ Array [ "field": "city", "height": 30, "hierarchy": undefined, - "id": "", + "id": "city", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -49,6 +50,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -58,8 +60,8 @@ Array [ "seriesNumberWidth": undefined, "spreadsheet": Anything, "value": "city", - "width": 99, - "x": 99, + "width": 99.33, + "x": 99.33, "y": 0, }, ] @@ -84,6 +86,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -106,7 +109,7 @@ Array [ "field": "province", "height": 30, "hierarchy": undefined, - "id": "", + "id": "province", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -114,6 +117,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -136,7 +140,7 @@ Array [ "field": "city", "height": 30, "hierarchy": undefined, - "id": "", + "id": "city", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -144,6 +148,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -171,7 +176,7 @@ Array [ "field": "", "height": 30, "hierarchy": undefined, - "id": "", + "id": "province/city/数值", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -179,6 +184,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -188,7 +194,7 @@ Array [ "seriesNumberWidth": 0, "spreadsheet": Anything, "value": "province/city/数值", - "width": 145, + "width": 145.36, "x": 0, "y": 0, }, @@ -214,6 +220,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -236,7 +243,7 @@ Array [ "field": "", "height": 30, "hierarchy": undefined, - "id": "", + "id": "province/city/数值", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -244,6 +251,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -253,7 +261,7 @@ Array [ "seriesNumberWidth": 80, "spreadsheet": Anything, "value": "province/city/数值", - "width": 145, + "width": 145.36, "x": 80, "y": 0, }, @@ -271,7 +279,7 @@ Array [ "field": "province", "height": 30, "hierarchy": undefined, - "id": "", + "id": "province", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -279,6 +287,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -301,7 +310,7 @@ Array [ "field": "city", "height": 30, "hierarchy": undefined, - "id": "", + "id": "city", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -309,6 +318,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -344,6 +354,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -366,7 +377,7 @@ Array [ "field": "province", "height": 30, "hierarchy": undefined, - "id": "", + "id": "province", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -374,6 +385,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -396,7 +408,7 @@ Array [ "field": "city", "height": 30, "hierarchy": undefined, - "id": "", + "id": "city", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -404,6 +416,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -431,7 +444,7 @@ Array [ "field": "", "height": 30, "hierarchy": undefined, - "id": "", + "id": "province/city", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -439,6 +452,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -474,6 +488,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -496,7 +511,7 @@ Array [ "field": "", "height": 30, "hierarchy": undefined, - "id": "", + "id": "province/city", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -504,6 +519,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -531,7 +547,7 @@ Array [ "field": "province", "height": 30, "hierarchy": undefined, - "id": "", + "id": "province", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -539,6 +555,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -548,7 +565,7 @@ Array [ "seriesNumberWidth": undefined, "spreadsheet": Anything, "value": "province", - "width": 99, + "width": 99.33, "x": 0, "y": 0, }, @@ -561,7 +578,7 @@ Array [ "field": "city", "height": 30, "hierarchy": undefined, - "id": "", + "id": "city", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -569,6 +586,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -578,8 +596,8 @@ Array [ "seriesNumberWidth": undefined, "spreadsheet": Anything, "value": "city", - "width": 99, - "x": 99, + "width": 99.33, + "x": 99.33, "y": 0, }, ] @@ -604,6 +622,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -626,7 +645,7 @@ Array [ "field": "province", "height": 30, "hierarchy": undefined, - "id": "", + "id": "province", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -634,6 +653,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -656,7 +676,7 @@ Array [ "field": "city", "height": 30, "hierarchy": undefined, - "id": "", + "id": "city", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -664,6 +684,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -691,7 +712,7 @@ Array [ "field": "", "height": 30, "hierarchy": undefined, - "id": "", + "id": "province/city", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -699,6 +720,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -734,6 +756,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, @@ -756,7 +779,7 @@ Array [ "field": "", "height": 30, "hierarchy": undefined, - "id": "", + "id": "province/city", "inCollapseNode": undefined, "isCollapsed": undefined, "isGrandTotals": undefined, @@ -764,6 +787,7 @@ Array [ "isPivotMode": true, "isSubTotals": undefined, "isTotalMeasure": undefined, + "isTotalRoot": undefined, "isTotals": undefined, "level": undefined, "padding": 0, diff --git a/packages/s2-core/__tests__/spreadsheet/__snapshots__/custom-cell-style-spec.ts.snap b/packages/s2-core/__tests__/spreadsheet/__snapshots__/custom-cell-style-spec.ts.snap index 2baf34699e..14478ee83c 100644 --- a/packages/s2-core/__tests__/spreadsheet/__snapshots__/custom-cell-style-spec.ts.snap +++ b/packages/s2-core/__tests__/spreadsheet/__snapshots__/custom-cell-style-spec.ts.snap @@ -45,17 +45,17 @@ Array [ Object { "height": 30, "id": "root[&]笔", - "width": 300, + "width": 299, }, Object { "height": 30, "id": "root[&]笔[&]price", - "width": 150, + "width": 149.5, }, Object { "height": 30, "id": "root[&]笔[&]cost", - "width": 150, + "width": 149.5, }, ] `; @@ -65,17 +65,17 @@ Array [ Object { "height": 60, "id": "root[&]浙江", - "width": 149, + "width": 149.5, }, Object { "height": 30, "id": "root[&]浙江[&]义乌", - "width": 149, + "width": 149.5, }, Object { "height": 30, "id": "root[&]浙江[&]杭州", - "width": 149, + "width": 149.5, }, ] `; @@ -140,7 +140,7 @@ Array [ Object { "height": 66, "id": "root[&]浙江", - "width": 149, + "width": 149.5, }, Object { "height": 33, @@ -160,17 +160,17 @@ Array [ Object { "height": 330, "id": "root[&]浙江", - "width": 149, + "width": 149.5, }, Object { "height": 30, "id": "root[&]浙江[&]义乌", - "width": 149, + "width": 149.5, }, Object { "height": 300, "id": "root[&]浙江[&]杭州", - "width": 149, + "width": 149.5, }, ] `; @@ -180,17 +180,17 @@ Array [ Object { "height": 60, "id": "root[&]浙江", - "width": 149, + "width": 149.5, }, Object { "height": 30, "id": "root[&]浙江[&]义乌", - "width": 149, + "width": 149.5, }, Object { "height": 30, "id": "root[&]浙江[&]杭州", - "width": 149, + "width": 149.5, }, ] `; @@ -200,17 +200,17 @@ Array [ Object { "height": 30, "id": "root[&]笔", - "width": 300, + "width": 299, }, Object { "height": 30, "id": "root[&]笔[&]price", - "width": 150, + "width": 149.5, }, Object { "height": 30, "id": "root[&]笔[&]cost", - "width": 150, + "width": 149.5, }, ] `; @@ -220,22 +220,22 @@ Array [ Object { "height": 30, "id": "root[&]浙江[&]义乌-root[&]笔[&]price", - "width": 150, + "width": 149.5, }, Object { "height": 30, "id": "root[&]浙江[&]杭州-root[&]笔[&]price", - "width": 150, + "width": 149.5, }, Object { "height": 30, "id": "root[&]浙江[&]义乌-root[&]笔[&]cost", - "width": 150, + "width": 149.5, }, Object { "height": 30, "id": "root[&]浙江[&]杭州-root[&]笔[&]cost", - "width": 150, + "width": 149.5, }, ] `; @@ -247,12 +247,12 @@ Array [ Object { "height": 30, "id": "root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "root[&]子类别", - "width": 299, + "width": 299.5, }, ] `; @@ -262,132 +262,132 @@ Array [ Object { "height": 30, "id": "0-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "1-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "2-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "3-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "4-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "5-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "6-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "7-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "8-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "9-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "10-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "11-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "12-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "0-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "1-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "2-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "3-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "4-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "5-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "6-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "7-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "8-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "9-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "10-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "11-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "12-root[&]子类别", - "width": 299, + "width": 299.5, }, ] `; @@ -427,7 +427,7 @@ Array [ Object { "height": 60, "id": "root[&]类型", - "width": 119, + "width": 119.8, }, Object { "height": 30, @@ -482,72 +482,72 @@ Array [ Object { "height": 60, "id": "0-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 60, "id": "1-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 60, "id": "2-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 60, "id": "3-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 60, "id": "4-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 60, "id": "5-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 60, "id": "6-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 60, "id": "0-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 60, "id": "1-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 60, "id": "2-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 60, "id": "3-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 60, "id": "4-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 60, "id": "5-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 60, "id": "6-root[&]子类别", - "width": 299, + "width": 299.5, }, ] `; @@ -557,102 +557,102 @@ Array [ Object { "height": 40, "id": "0-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "1-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 100, "id": "2-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "3-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "4-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "5-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "6-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "7-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "8-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "9-root[&]类别", - "width": 299, + "width": 299.5, }, Object { "height": 40, "id": "0-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "1-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 100, "id": "2-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "3-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "4-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "5-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "6-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "7-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "8-root[&]子类别", - "width": 299, + "width": 299.5, }, Object { "height": 30, "id": "9-root[&]子类别", - "width": 299, + "width": 299.5, }, ] `; diff --git a/packages/s2-core/__tests__/spreadsheet/__snapshots__/custom-grid-spec.ts.snap b/packages/s2-core/__tests__/spreadsheet/__snapshots__/custom-grid-spec.ts.snap index 4e93984e3d..63872b5a1c 100644 --- a/packages/s2-core/__tests__/spreadsheet/__snapshots__/custom-grid-spec.ts.snap +++ b/packages/s2-core/__tests__/spreadsheet/__snapshots__/custom-grid-spec.ts.snap @@ -253,7 +253,7 @@ Array [ "field": "a-1", "height": 30, "value": "自定义节点 a-1", - "width": 300, + "width": 299.01, "x": 0, "y": 0, }, @@ -262,7 +262,7 @@ Array [ "field": "a-1-1", "height": 30, "value": "自定义节点 a-1-1", - "width": 200, + "width": 199.34, "x": 0, "y": 30, }, @@ -271,7 +271,7 @@ Array [ "field": "measure-1", "height": 30, "value": "指标1", - "width": 100, + "width": 99.67, "x": 0, "y": 60, }, @@ -280,8 +280,8 @@ Array [ "field": "measure-2", "height": 30, "value": "指标2", - "width": 100, - "x": 100, + "width": 99.67, + "x": 99.67, "y": 60, }, Object { @@ -289,8 +289,8 @@ Array [ "field": "a-1-2", "height": 60, "value": "自定义节点 a-1-2", - "width": 100, - "x": 200, + "width": 99.67, + "x": 199.34, "y": 30, }, Object { @@ -298,8 +298,8 @@ Array [ "field": "a-2", "height": 90, "value": "自定义节点 a-2", - "width": 100, - "x": 300, + "width": 99.67, + "x": 299.01, "y": 0, }, ] @@ -309,19 +309,19 @@ exports[`SpreadSheet Custom Grid Tests Custom Row Grid Tests should calc correct Array [ Object { "field": "measure-1", - "width": 119, + "width": 119.6, }, Object { "field": "measure-2", - "width": 119, + "width": 119.6, }, Object { "field": "a-1-2", - "width": 159, + "width": 159.6, }, Object { "field": "a-2", - "width": 278, + "width": 279.2, }, ] `; @@ -330,11 +330,11 @@ exports[`SpreadSheet Custom Grid Tests Custom Row Grid Tests should calc correct Array [ Object { "field": "sub_type", - "width": 160, + "width": 159.4, }, Object { "field": "sub_type", - "width": 160, + "width": 159.4, }, ] `; @@ -435,37 +435,37 @@ Array [ "description": "a-1 描述", "height": 90, "value": "自定义节点 a-1", - "width": 119, + "width": 119.6, }, Object { "description": "a-1-1 描述", "height": 60, "value": "自定义节点 a-1-1", - "width": 119, + "width": 119.6, }, Object { "description": "指标1描述", "height": 30, "value": "指标1", - "width": 119, + "width": 119.6, }, Object { "description": "指标2描述", "height": 30, "value": "指标2", - "width": 119, + "width": 119.6, }, Object { "description": "a-1-2 描述", "height": 30, "value": "自定义节点 a-1-2", - "width": 238, + "width": 239.2, }, Object { "description": "a-2 描述", "height": 30, "value": "自定义节点 a-2", - "width": 357, + "width": 358.79999999999995, }, ] `; diff --git a/packages/s2-core/__tests__/spreadsheet/__snapshots__/custom-table-col-spec.ts.snap b/packages/s2-core/__tests__/spreadsheet/__snapshots__/custom-table-col-spec.ts.snap index e607dd4387..e7310e8255 100644 --- a/packages/s2-core/__tests__/spreadsheet/__snapshots__/custom-table-col-spec.ts.snap +++ b/packages/s2-core/__tests__/spreadsheet/__snapshots__/custom-table-col-spec.ts.snap @@ -169,43 +169,43 @@ Array [ "description": undefined, "height": 30, "value": "地区", - "width": 238, + "width": 239.6, }, Object { "description": undefined, "height": 30, "value": "省份", - "width": 119, + "width": 119.8, }, Object { "description": undefined, "height": 30, "value": "城市", - "width": 119, + "width": 119.8, }, Object { "description": undefined, "height": 60, "value": "类型", - "width": 119, + "width": 119.8, }, Object { "description": undefined, "height": 30, "value": "金额", - "width": 238, + "width": 239.6, }, Object { "description": "价格描述", "height": 30, "value": "价格", - "width": 119, + "width": 119.8, }, Object { "description": undefined, "height": 30, "value": "数量", - "width": 119, + "width": 119.8, }, ] `; @@ -216,37 +216,37 @@ Array [ "description": "a-1 描述", "height": 30, "value": "自定义节点 a-1", - "width": 447, + "width": 449.25, }, Object { "description": "a-1-1 描述", "height": 30, "value": "自定义节点 a-1-1", - "width": 298, + "width": 299.5, }, Object { "description": "指标1描述", "height": 30, "value": "指标1", - "width": 149, + "width": 149.75, }, Object { "description": "指标2描述", "height": 30, "value": "指标2", - "width": 149, + "width": 149.75, }, Object { "description": "a-1-2 描述", "height": 60, "value": "自定义节点 a-1-2", - "width": 149, + "width": 149.75, }, Object { "description": "a-2 描述", "height": 90, "value": "自定义节点 a-2", - "width": 149, + "width": 149.75, }, ] `; diff --git a/packages/s2-core/__tests__/spreadsheet/__snapshots__/hidden-columns-spec.ts.snap b/packages/s2-core/__tests__/spreadsheet/__snapshots__/hidden-columns-spec.ts.snap new file mode 100644 index 0000000000..90c2f63ff0 --- /dev/null +++ b/packages/s2-core/__tests__/spreadsheet/__snapshots__/hidden-columns-spec.ts.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SpreadSheet Hidden Columns Tests PivotSheet Multiple Values Tests should render correct row corner after hide measure node 1`] = ` +Array [ + Object { + "height": 30, + "width": 119.6, + "x": 0, + "y": 60, + }, + Object { + "height": 30, + "width": 119.6, + "x": 119.6, + "y": 60, + }, + Object { + "height": 30, + "width": 239.2, + "x": 0, + "y": 0, + }, + Object { + "height": 30, + "width": 239.2, + "x": 0, + "y": 30, + }, +] +`; diff --git a/packages/s2-core/__tests__/spreadsheet/__snapshots__/multi-line-text-spec.ts.snap b/packages/s2-core/__tests__/spreadsheet/__snapshots__/multi-line-text-spec.ts.snap index 5b04f4dac4..f9206280f8 100644 --- a/packages/s2-core/__tests__/spreadsheet/__snapshots__/multi-line-text-spec.ts.snap +++ b/packages/s2-core/__tests__/spreadsheet/__snapshots__/multi-line-text-spec.ts.snap @@ -2428,12 +2428,12 @@ Array [ "width": 96, }, Object { - "actualText": "城我是省略号", + "actualText": "城市城@@@", "actualTextHeight": 16, - "actualTextWidth": 73, + "actualTextWidth": 72, "height": 30, "multiLineActualTexts": Array [ - "城我是省略号", + "城市城@@@", ], "originalText": "城市城市城市城市城市城市城市城市城市城市城市城市", "width": 96, @@ -2977,7 +2977,7 @@ Array [ "省份", ], "originalText": "省份", - "width": 103, + "width": 103.8, }, Object { "actualText": "城市城市城市城市城市城市城市城市城市城市...", @@ -2990,7 +2990,7 @@ Array [ "城市城市城市...", ], "originalText": "城市城市城市城市城市城市城市城市城市城市城市城市", - "width": 103, + "width": 103.8, }, Object { "actualText": "类别类别类别类别类别类别类别类别类别类别", @@ -3003,7 +3003,7 @@ Array [ "类别类别类别", ], "originalText": "类别类别类别类别类别类别类别类别类别类别", - "width": 103, + "width": 103.8, }, Object { "actualText": "子类别", @@ -3014,7 +3014,7 @@ Array [ "子类别", ], "originalText": "子类别", - "width": 103, + "width": 103.8, }, Object { "actualText": "数量数量数量数量数量数量数量数量数量数量...", @@ -3027,7 +3027,7 @@ Array [ "数量数量数量...", ], "originalText": "数量数量数量数量数量数量数量数量数量数量数量", - "width": 103, + "width": 103.8, }, ] `; @@ -3177,7 +3177,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省浙江省浙江省浙江省浙江省浙江省浙江...", @@ -3190,7 +3190,7 @@ Array [ "省浙江省浙江...", ], "originalText": "浙江省浙江省浙江省浙江省浙江省浙江省浙江省浙江省浙江省浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -3201,7 +3201,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -3212,7 +3212,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -3223,7 +3223,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -3234,7 +3234,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -3245,7 +3245,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -3256,7 +3256,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -3267,7 +3267,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -3278,7 +3278,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -3289,7 +3289,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -3300,7 +3300,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "绍兴市", @@ -3311,7 +3311,7 @@ Array [ "绍兴市", ], "originalText": "绍兴市", - "width": 103, + "width": 103.8, }, Object { "actualText": "杭州市杭州市杭州市杭州市杭州市杭州市杭州...", @@ -3324,7 +3324,7 @@ Array [ "市杭州市杭州...", ], "originalText": "杭州市杭州市杭州市杭州市杭州市杭州市杭州市杭州市杭州市杭州市", - "width": 103, + "width": 103.8, }, Object { "actualText": "绍兴市", @@ -3335,7 +3335,7 @@ Array [ "绍兴市", ], "originalText": "绍兴市", - "width": 103, + "width": 103.8, }, Object { "actualText": "宁波市", @@ -3346,7 +3346,7 @@ Array [ "宁波市", ], "originalText": "宁波市", - "width": 103, + "width": 103.8, }, Object { "actualText": "舟山市", @@ -3357,7 +3357,7 @@ Array [ "舟山市", ], "originalText": "舟山市", - "width": 103, + "width": 103.8, }, Object { "actualText": "杭州市", @@ -3368,7 +3368,7 @@ Array [ "杭州市", ], "originalText": "杭州市", - "width": 103, + "width": 103.8, }, Object { "actualText": "绍兴市", @@ -3379,7 +3379,7 @@ Array [ "绍兴市", ], "originalText": "绍兴市", - "width": 103, + "width": 103.8, }, Object { "actualText": "宁波市", @@ -3390,7 +3390,7 @@ Array [ "宁波市", ], "originalText": "宁波市", - "width": 103, + "width": 103.8, }, Object { "actualText": "舟山市", @@ -3401,7 +3401,7 @@ Array [ "舟山市", ], "originalText": "舟山市", - "width": 103, + "width": 103.8, }, Object { "actualText": "杭州市", @@ -3412,7 +3412,7 @@ Array [ "杭州市", ], "originalText": "杭州市", - "width": 103, + "width": 103.8, }, Object { "actualText": "绍兴市", @@ -3423,7 +3423,7 @@ Array [ "绍兴市", ], "originalText": "绍兴市", - "width": 103, + "width": 103.8, }, Object { "actualText": "宁波市", @@ -3434,7 +3434,7 @@ Array [ "宁波市", ], "originalText": "宁波市", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -3445,7 +3445,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具家具家具家具家具家具家具家具家具家具...", @@ -3458,7 +3458,7 @@ Array [ "家具家具家具...", ], "originalText": "家具家具家具家具家具家具家具家具家具家具家具家具家具家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -3469,7 +3469,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -3480,7 +3480,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -3491,7 +3491,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -3502,7 +3502,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -3513,7 +3513,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -3524,7 +3524,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -3535,7 +3535,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -3546,7 +3546,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -3557,7 +3557,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -3568,7 +3568,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子", @@ -3579,7 +3579,7 @@ Array [ "桌子", ], "originalText": "桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子...", @@ -3592,7 +3592,7 @@ Array [ "桌子桌子桌子...", ], "originalText": "桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子", @@ -3603,7 +3603,7 @@ Array [ "桌子", ], "originalText": "桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子", @@ -3614,7 +3614,7 @@ Array [ "桌子", ], "originalText": "桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子", @@ -3625,7 +3625,7 @@ Array [ "桌子", ], "originalText": "桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "沙发", @@ -3636,7 +3636,7 @@ Array [ "沙发", ], "originalText": "沙发", - "width": 103, + "width": 103.8, }, Object { "actualText": "沙发", @@ -3647,7 +3647,7 @@ Array [ "沙发", ], "originalText": "沙发", - "width": 103, + "width": 103.8, }, Object { "actualText": "沙发", @@ -3658,7 +3658,7 @@ Array [ "沙发", ], "originalText": "沙发", - "width": 103, + "width": 103.8, }, Object { "actualText": "沙发", @@ -3669,7 +3669,7 @@ Array [ "沙发", ], "originalText": "沙发", - "width": 103, + "width": 103.8, }, Object { "actualText": "笔", @@ -3680,7 +3680,7 @@ Array [ "笔", ], "originalText": "笔", - "width": 103, + "width": 103.8, }, Object { "actualText": "笔", @@ -3691,7 +3691,7 @@ Array [ "笔", ], "originalText": "笔", - "width": 103, + "width": 103.8, }, Object { "actualText": "笔", @@ -3702,7 +3702,7 @@ Array [ "笔", ], "originalText": "笔", - "width": 103, + "width": 103.8, }, Object { "actualText": "236723672361111", @@ -3710,11 +3710,11 @@ Array [ "actualTextWidth": 100, "height": 30, "multiLineActualTexts": Array [ - "236723672361", - "111", + "2367236723611", + "11", ], "originalText": "236723672361111", - "width": 103, + "width": 103.8, }, Object { "actualText": "7789778977897789778977897789", @@ -3722,12 +3722,12 @@ Array [ "actualTextWidth": 189, "height": 30, "multiLineActualTexts": Array [ - "778977897789", - "778977897789", - "7789", + "7789778977897", + "7897789778977", + "89", ], "originalText": "7789778977897789778977897789", - "width": 103, + "width": 103.8, }, Object { "actualText": "236723672361111", @@ -3735,11 +3735,11 @@ Array [ "actualTextWidth": 100, "height": 30, "multiLineActualTexts": Array [ - "236723672361", - "111", + "2367236723611", + "11", ], "originalText": "236723672361111", - "width": 103, + "width": 103.8, }, Object { "actualText": "3877", @@ -3750,7 +3750,7 @@ Array [ "3877", ], "originalText": "3877", - "width": 103, + "width": 103.8, }, Object { "actualText": "4342", @@ -3761,7 +3761,7 @@ Array [ "4342", ], "originalText": "4342", - "width": 103, + "width": 103.8, }, Object { "actualText": "5343", @@ -3772,7 +3772,7 @@ Array [ "5343", ], "originalText": "5343", - "width": 103, + "width": 103.8, }, Object { "actualText": "632", @@ -3783,7 +3783,7 @@ Array [ "632", ], "originalText": "632", - "width": 103, + "width": 103.8, }, Object { "actualText": "7234", @@ -3794,7 +3794,7 @@ Array [ "7234", ], "originalText": "7234", - "width": 103, + "width": 103.8, }, Object { "actualText": "834", @@ -3805,7 +3805,7 @@ Array [ "834", ], "originalText": "834", - "width": 103, + "width": 103.8, }, Object { "actualText": "945", @@ -3816,7 +3816,7 @@ Array [ "945", ], "originalText": "945", - "width": 103, + "width": 103.8, }, Object { "actualText": "1304", @@ -3827,7 +3827,7 @@ Array [ "1304", ], "originalText": "1304", - "width": 103, + "width": 103.8, }, Object { "actualText": "1145", @@ -3838,7 +3838,7 @@ Array [ "1145", ], "originalText": "1145", - "width": 103, + "width": 103.8, }, ] `; @@ -4018,7 +4018,7 @@ Array [ "省份", ], "originalText": "省份", - "width": 103, + "width": 103.8, }, Object { "actualText": "城市城市城市城市城市城市城...", @@ -4030,7 +4030,7 @@ Array [ "市城市城市城...", ], "originalText": "城市城市城市城市城市城市城市城市城市城市城市城市", - "width": 103, + "width": 103.8, }, Object { "actualText": "类别类别类别类别类别类别类...", @@ -4042,7 +4042,7 @@ Array [ "别类别类别类...", ], "originalText": "类别类别类别类别类别类别类别类别类别类别", - "width": 103, + "width": 103.8, }, Object { "actualText": "子类别", @@ -4053,7 +4053,7 @@ Array [ "子类别", ], "originalText": "子类别", - "width": 103, + "width": 103.8, }, Object { "actualText": "数量数量数量数量数量数量数...", @@ -4065,7 +4065,7 @@ Array [ "量数量数量数...", ], "originalText": "数量数量数量数量数量数量数量数量数量数量数量", - "width": 103, + "width": 103.8, }, ] `; @@ -4215,7 +4215,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省浙江省浙江省浙江省浙...", @@ -4227,7 +4227,7 @@ Array [ "江省浙江省浙...", ], "originalText": "浙江省浙江省浙江省浙江省浙江省浙江省浙江省浙江省浙江省浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -4238,7 +4238,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -4249,7 +4249,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -4260,7 +4260,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -4271,7 +4271,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -4282,7 +4282,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -4293,7 +4293,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -4304,7 +4304,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -4315,7 +4315,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -4326,7 +4326,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -4337,7 +4337,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "绍兴市", @@ -4348,7 +4348,7 @@ Array [ "绍兴市", ], "originalText": "绍兴市", - "width": 103, + "width": 103.8, }, Object { "actualText": "杭州市杭州市杭州市杭州市杭...", @@ -4360,7 +4360,7 @@ Array [ "州市杭州市杭...", ], "originalText": "杭州市杭州市杭州市杭州市杭州市杭州市杭州市杭州市杭州市杭州市", - "width": 103, + "width": 103.8, }, Object { "actualText": "绍兴市", @@ -4371,7 +4371,7 @@ Array [ "绍兴市", ], "originalText": "绍兴市", - "width": 103, + "width": 103.8, }, Object { "actualText": "宁波市", @@ -4382,7 +4382,7 @@ Array [ "宁波市", ], "originalText": "宁波市", - "width": 103, + "width": 103.8, }, Object { "actualText": "舟山市", @@ -4393,7 +4393,7 @@ Array [ "舟山市", ], "originalText": "舟山市", - "width": 103, + "width": 103.8, }, Object { "actualText": "杭州市", @@ -4404,7 +4404,7 @@ Array [ "杭州市", ], "originalText": "杭州市", - "width": 103, + "width": 103.8, }, Object { "actualText": "绍兴市", @@ -4415,7 +4415,7 @@ Array [ "绍兴市", ], "originalText": "绍兴市", - "width": 103, + "width": 103.8, }, Object { "actualText": "宁波市", @@ -4426,7 +4426,7 @@ Array [ "宁波市", ], "originalText": "宁波市", - "width": 103, + "width": 103.8, }, Object { "actualText": "舟山市", @@ -4437,7 +4437,7 @@ Array [ "舟山市", ], "originalText": "舟山市", - "width": 103, + "width": 103.8, }, Object { "actualText": "杭州市", @@ -4448,7 +4448,7 @@ Array [ "杭州市", ], "originalText": "杭州市", - "width": 103, + "width": 103.8, }, Object { "actualText": "绍兴市", @@ -4459,7 +4459,7 @@ Array [ "绍兴市", ], "originalText": "绍兴市", - "width": 103, + "width": 103.8, }, Object { "actualText": "宁波市", @@ -4470,7 +4470,7 @@ Array [ "宁波市", ], "originalText": "宁波市", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -4481,7 +4481,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具家具家具家具家具家具家...", @@ -4493,7 +4493,7 @@ Array [ "具家具家具家...", ], "originalText": "家具家具家具家具家具家具家具家具家具家具家具家具家具家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -4504,7 +4504,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -4515,7 +4515,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -4526,7 +4526,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -4537,7 +4537,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -4548,7 +4548,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -4559,7 +4559,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -4570,7 +4570,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -4581,7 +4581,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -4592,7 +4592,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -4603,7 +4603,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子", @@ -4614,7 +4614,7 @@ Array [ "桌子", ], "originalText": "桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子桌子桌子桌子桌子桌子桌...", @@ -4626,7 +4626,7 @@ Array [ "子桌子桌子桌...", ], "originalText": "桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子", @@ -4637,7 +4637,7 @@ Array [ "桌子", ], "originalText": "桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子", @@ -4648,7 +4648,7 @@ Array [ "桌子", ], "originalText": "桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子", @@ -4659,7 +4659,7 @@ Array [ "桌子", ], "originalText": "桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "沙发", @@ -4670,7 +4670,7 @@ Array [ "沙发", ], "originalText": "沙发", - "width": 103, + "width": 103.8, }, Object { "actualText": "沙发", @@ -4681,7 +4681,7 @@ Array [ "沙发", ], "originalText": "沙发", - "width": 103, + "width": 103.8, }, Object { "actualText": "沙发", @@ -4692,7 +4692,7 @@ Array [ "沙发", ], "originalText": "沙发", - "width": 103, + "width": 103.8, }, Object { "actualText": "沙发", @@ -4703,7 +4703,7 @@ Array [ "沙发", ], "originalText": "沙发", - "width": 103, + "width": 103.8, }, Object { "actualText": "笔", @@ -4714,7 +4714,7 @@ Array [ "笔", ], "originalText": "笔", - "width": 103, + "width": 103.8, }, Object { "actualText": "笔", @@ -4725,7 +4725,7 @@ Array [ "笔", ], "originalText": "笔", - "width": 103, + "width": 103.8, }, Object { "actualText": "笔", @@ -4736,7 +4736,7 @@ Array [ "笔", ], "originalText": "笔", - "width": 103, + "width": 103.8, }, Object { "actualText": "236723672361111", @@ -4744,23 +4744,23 @@ Array [ "actualTextWidth": 100, "height": 30, "multiLineActualTexts": Array [ - "236723672361", - "111", + "2367236723611", + "11", ], "originalText": "236723672361111", - "width": 103, + "width": 103.8, }, Object { - "actualText": "77897789778977897789778...", + "actualText": "778977897789778977897789...", "actualTextHeight": 30, - "actualTextWidth": 165, + "actualTextWidth": 172, "height": 30, "multiLineActualTexts": Array [ - "778977897789", - "77897789778...", + "7789778977897", + "78977897789...", ], "originalText": "7789778977897789778977897789", - "width": 103, + "width": 103.8, }, Object { "actualText": "236723672361111", @@ -4768,11 +4768,11 @@ Array [ "actualTextWidth": 100, "height": 30, "multiLineActualTexts": Array [ - "236723672361", - "111", + "2367236723611", + "11", ], "originalText": "236723672361111", - "width": 103, + "width": 103.8, }, Object { "actualText": "3877", @@ -4783,7 +4783,7 @@ Array [ "3877", ], "originalText": "3877", - "width": 103, + "width": 103.8, }, Object { "actualText": "4342", @@ -4794,7 +4794,7 @@ Array [ "4342", ], "originalText": "4342", - "width": 103, + "width": 103.8, }, Object { "actualText": "5343", @@ -4805,7 +4805,7 @@ Array [ "5343", ], "originalText": "5343", - "width": 103, + "width": 103.8, }, Object { "actualText": "632", @@ -4816,7 +4816,7 @@ Array [ "632", ], "originalText": "632", - "width": 103, + "width": 103.8, }, Object { "actualText": "7234", @@ -4827,7 +4827,7 @@ Array [ "7234", ], "originalText": "7234", - "width": 103, + "width": 103.8, }, Object { "actualText": "834", @@ -4838,7 +4838,7 @@ Array [ "834", ], "originalText": "834", - "width": 103, + "width": 103.8, }, Object { "actualText": "945", @@ -4849,7 +4849,7 @@ Array [ "945", ], "originalText": "945", - "width": 103, + "width": 103.8, }, Object { "actualText": "1304", @@ -4860,7 +4860,7 @@ Array [ "1304", ], "originalText": "1304", - "width": 103, + "width": 103.8, }, Object { "actualText": "1145", @@ -4871,7 +4871,7 @@ Array [ "1145", ], "originalText": "1145", - "width": 103, + "width": 103.8, }, ] `; @@ -5062,7 +5062,7 @@ Array [ "省份", ], "originalText": "省份", - "width": 103, + "width": 103.8, }, Object { "actualText": "城市城市城市...", @@ -5073,7 +5073,7 @@ Array [ "城市城市城市...", ], "originalText": "城市城市城市城市城市城市城市城市城市城市城市城市", - "width": 103, + "width": 103.8, }, Object { "actualText": "类别类别类别...", @@ -5084,7 +5084,7 @@ Array [ "类别类别类别...", ], "originalText": "类别类别类别类别类别类别类别类别类别类别", - "width": 103, + "width": 103.8, }, Object { "actualText": "子类别", @@ -5095,7 +5095,7 @@ Array [ "子类别", ], "originalText": "子类别", - "width": 103, + "width": 103.8, }, Object { "actualText": "数量数量数量...", @@ -5106,7 +5106,7 @@ Array [ "数量数量数量...", ], "originalText": "数量数量数量数量数量数量数量数量数量数量数量", - "width": 103, + "width": 103.8, }, ] `; @@ -5267,7 +5267,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省浙江省...", @@ -5278,7 +5278,7 @@ Array [ "浙江省浙江省...", ], "originalText": "浙江省浙江省浙江省浙江省浙江省浙江省浙江省浙江省浙江省浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -5289,7 +5289,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -5300,7 +5300,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -5311,7 +5311,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -5322,7 +5322,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -5333,7 +5333,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -5344,7 +5344,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -5355,7 +5355,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -5366,7 +5366,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -5377,7 +5377,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -5388,7 +5388,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -5399,7 +5399,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "绍兴市", @@ -5410,7 +5410,7 @@ Array [ "绍兴市", ], "originalText": "绍兴市", - "width": 103, + "width": 103.8, }, Object { "actualText": "杭州市杭州市...", @@ -5421,7 +5421,7 @@ Array [ "杭州市杭州市...", ], "originalText": "杭州市杭州市杭州市杭州市杭州市杭州市杭州市杭州市杭州市杭州市", - "width": 103, + "width": 103.8, }, Object { "actualText": "绍兴市", @@ -5432,7 +5432,7 @@ Array [ "绍兴市", ], "originalText": "绍兴市", - "width": 103, + "width": 103.8, }, Object { "actualText": "宁波市", @@ -5443,7 +5443,7 @@ Array [ "宁波市", ], "originalText": "宁波市", - "width": 103, + "width": 103.8, }, Object { "actualText": "舟山市", @@ -5454,7 +5454,7 @@ Array [ "舟山市", ], "originalText": "舟山市", - "width": 103, + "width": 103.8, }, Object { "actualText": "杭州市", @@ -5465,7 +5465,7 @@ Array [ "杭州市", ], "originalText": "杭州市", - "width": 103, + "width": 103.8, }, Object { "actualText": "绍兴市", @@ -5476,7 +5476,7 @@ Array [ "绍兴市", ], "originalText": "绍兴市", - "width": 103, + "width": 103.8, }, Object { "actualText": "宁波市", @@ -5487,7 +5487,7 @@ Array [ "宁波市", ], "originalText": "宁波市", - "width": 103, + "width": 103.8, }, Object { "actualText": "舟山市", @@ -5498,7 +5498,7 @@ Array [ "舟山市", ], "originalText": "舟山市", - "width": 103, + "width": 103.8, }, Object { "actualText": "杭州市", @@ -5509,7 +5509,7 @@ Array [ "杭州市", ], "originalText": "杭州市", - "width": 103, + "width": 103.8, }, Object { "actualText": "绍兴市", @@ -5520,7 +5520,7 @@ Array [ "绍兴市", ], "originalText": "绍兴市", - "width": 103, + "width": 103.8, }, Object { "actualText": "宁波市", @@ -5531,7 +5531,7 @@ Array [ "宁波市", ], "originalText": "宁波市", - "width": 103, + "width": 103.8, }, Object { "actualText": "舟山市", @@ -5542,7 +5542,7 @@ Array [ "舟山市", ], "originalText": "舟山市", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -5553,7 +5553,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具家具家具...", @@ -5564,7 +5564,7 @@ Array [ "家具家具家具...", ], "originalText": "家具家具家具家具家具家具家具家具家具家具家具家具家具家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -5575,7 +5575,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -5586,7 +5586,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -5597,7 +5597,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -5608,7 +5608,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -5619,7 +5619,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -5630,7 +5630,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -5641,7 +5641,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -5652,7 +5652,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -5663,7 +5663,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -5674,7 +5674,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -5685,7 +5685,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子", @@ -5696,7 +5696,7 @@ Array [ "桌子", ], "originalText": "桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子桌子桌子...", @@ -5707,7 +5707,7 @@ Array [ "桌子桌子桌子...", ], "originalText": "桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子", @@ -5718,7 +5718,7 @@ Array [ "桌子", ], "originalText": "桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子", @@ -5729,7 +5729,7 @@ Array [ "桌子", ], "originalText": "桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子", @@ -5740,7 +5740,7 @@ Array [ "桌子", ], "originalText": "桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "沙发", @@ -5751,7 +5751,7 @@ Array [ "沙发", ], "originalText": "沙发", - "width": 103, + "width": 103.8, }, Object { "actualText": "沙发", @@ -5762,7 +5762,7 @@ Array [ "沙发", ], "originalText": "沙发", - "width": 103, + "width": 103.8, }, Object { "actualText": "沙发", @@ -5773,7 +5773,7 @@ Array [ "沙发", ], "originalText": "沙发", - "width": 103, + "width": 103.8, }, Object { "actualText": "沙发", @@ -5784,7 +5784,7 @@ Array [ "沙发", ], "originalText": "沙发", - "width": 103, + "width": 103.8, }, Object { "actualText": "笔", @@ -5795,7 +5795,7 @@ Array [ "笔", ], "originalText": "笔", - "width": 103, + "width": 103.8, }, Object { "actualText": "笔", @@ -5806,7 +5806,7 @@ Array [ "笔", ], "originalText": "笔", - "width": 103, + "width": 103.8, }, Object { "actualText": "笔", @@ -5817,7 +5817,7 @@ Array [ "笔", ], "originalText": "笔", - "width": 103, + "width": 103.8, }, Object { "actualText": "笔", @@ -5828,7 +5828,7 @@ Array [ "笔", ], "originalText": "笔", - "width": 103, + "width": 103.8, }, Object { "actualText": "23672367236...", @@ -5839,7 +5839,7 @@ Array [ "23672367236...", ], "originalText": "236723672361111", - "width": 103, + "width": 103.8, }, Object { "actualText": "77897789778...", @@ -5850,7 +5850,7 @@ Array [ "77897789778...", ], "originalText": "7789778977897789778977897789", - "width": 103, + "width": 103.8, }, Object { "actualText": "23672367236...", @@ -5861,7 +5861,7 @@ Array [ "23672367236...", ], "originalText": "236723672361111", - "width": 103, + "width": 103.8, }, Object { "actualText": "3877", @@ -5872,7 +5872,7 @@ Array [ "3877", ], "originalText": "3877", - "width": 103, + "width": 103.8, }, Object { "actualText": "4342", @@ -5883,7 +5883,7 @@ Array [ "4342", ], "originalText": "4342", - "width": 103, + "width": 103.8, }, Object { "actualText": "5343", @@ -5894,7 +5894,7 @@ Array [ "5343", ], "originalText": "5343", - "width": 103, + "width": 103.8, }, Object { "actualText": "632", @@ -5905,7 +5905,7 @@ Array [ "632", ], "originalText": "632", - "width": 103, + "width": 103.8, }, Object { "actualText": "7234", @@ -5916,7 +5916,7 @@ Array [ "7234", ], "originalText": "7234", - "width": 103, + "width": 103.8, }, Object { "actualText": "834", @@ -5927,7 +5927,7 @@ Array [ "834", ], "originalText": "834", - "width": 103, + "width": 103.8, }, Object { "actualText": "945", @@ -5938,7 +5938,7 @@ Array [ "945", ], "originalText": "945", - "width": 103, + "width": 103.8, }, Object { "actualText": "1304", @@ -5949,7 +5949,7 @@ Array [ "1304", ], "originalText": "1304", - "width": 103, + "width": 103.8, }, Object { "actualText": "1145", @@ -5960,7 +5960,7 @@ Array [ "1145", ], "originalText": "1145", - "width": 103, + "width": 103.8, }, Object { "actualText": "1432", @@ -5971,7 +5971,7 @@ Array [ "1432", ], "originalText": "1432", - "width": 103, + "width": 103.8, }, ] `; @@ -6217,7 +6217,7 @@ Array [ "省份", ], "originalText": "省份", - "width": 103, + "width": 103.8, }, Object { "actualText": "城市城市城市城市城市城市城...", @@ -6229,7 +6229,7 @@ Array [ "市城市城市城...", ], "originalText": "城市城市城市城市城市城市城市城市城市城市城市城市", - "width": 103, + "width": 103.8, }, Object { "actualText": "类别类别类别类别类别类别类...", @@ -6241,7 +6241,7 @@ Array [ "别类别类别类...", ], "originalText": "类别类别类别类别类别类别类别类别类别类别", - "width": 103, + "width": 103.8, }, Object { "actualText": "子类别", @@ -6252,7 +6252,7 @@ Array [ "子类别", ], "originalText": "子类别", - "width": 103, + "width": 103.8, }, Object { "actualText": "数量数量数量数量数量数量数...", @@ -6264,7 +6264,7 @@ Array [ "量数量数量数...", ], "originalText": "数量数量数量数量数量数量数量数量数量数量数量", - "width": 103, + "width": 103.8, }, ] `; @@ -6480,7 +6480,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省浙江省浙江省浙江省浙...", @@ -6492,7 +6492,7 @@ Array [ "江省浙江省浙...", ], "originalText": "浙江省浙江省浙江省浙江省浙江省浙江省浙江省浙江省浙江省浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -6503,7 +6503,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -6514,7 +6514,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -6525,7 +6525,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -6536,7 +6536,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -6547,7 +6547,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -6558,7 +6558,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -6569,7 +6569,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -6580,7 +6580,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -6591,7 +6591,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -6602,7 +6602,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -6613,7 +6613,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -6624,7 +6624,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -6635,7 +6635,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -6646,7 +6646,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "浙江省", @@ -6657,7 +6657,7 @@ Array [ "浙江省", ], "originalText": "浙江省", - "width": 103, + "width": 103.8, }, Object { "actualText": "四川省", @@ -6668,7 +6668,7 @@ Array [ "四川省", ], "originalText": "四川省", - "width": 103, + "width": 103.8, }, Object { "actualText": "绍兴市", @@ -6679,7 +6679,7 @@ Array [ "绍兴市", ], "originalText": "绍兴市", - "width": 103, + "width": 103.8, }, Object { "actualText": "杭州市杭州市杭州市杭州市杭...", @@ -6691,7 +6691,7 @@ Array [ "州市杭州市杭...", ], "originalText": "杭州市杭州市杭州市杭州市杭州市杭州市杭州市杭州市杭州市杭州市", - "width": 103, + "width": 103.8, }, Object { "actualText": "绍兴市", @@ -6702,7 +6702,7 @@ Array [ "绍兴市", ], "originalText": "绍兴市", - "width": 103, + "width": 103.8, }, Object { "actualText": "宁波市", @@ -6713,7 +6713,7 @@ Array [ "宁波市", ], "originalText": "宁波市", - "width": 103, + "width": 103.8, }, Object { "actualText": "舟山市", @@ -6724,7 +6724,7 @@ Array [ "舟山市", ], "originalText": "舟山市", - "width": 103, + "width": 103.8, }, Object { "actualText": "杭州市", @@ -6735,7 +6735,7 @@ Array [ "杭州市", ], "originalText": "杭州市", - "width": 103, + "width": 103.8, }, Object { "actualText": "绍兴市", @@ -6746,7 +6746,7 @@ Array [ "绍兴市", ], "originalText": "绍兴市", - "width": 103, + "width": 103.8, }, Object { "actualText": "宁波市", @@ -6757,7 +6757,7 @@ Array [ "宁波市", ], "originalText": "宁波市", - "width": 103, + "width": 103.8, }, Object { "actualText": "舟山市", @@ -6768,7 +6768,7 @@ Array [ "舟山市", ], "originalText": "舟山市", - "width": 103, + "width": 103.8, }, Object { "actualText": "杭州市", @@ -6779,7 +6779,7 @@ Array [ "杭州市", ], "originalText": "杭州市", - "width": 103, + "width": 103.8, }, Object { "actualText": "绍兴市", @@ -6790,7 +6790,7 @@ Array [ "绍兴市", ], "originalText": "绍兴市", - "width": 103, + "width": 103.8, }, Object { "actualText": "宁波市", @@ -6801,7 +6801,7 @@ Array [ "宁波市", ], "originalText": "宁波市", - "width": 103, + "width": 103.8, }, Object { "actualText": "舟山市", @@ -6812,7 +6812,7 @@ Array [ "舟山市", ], "originalText": "舟山市", - "width": 103, + "width": 103.8, }, Object { "actualText": "杭州市", @@ -6823,7 +6823,7 @@ Array [ "杭州市", ], "originalText": "杭州市", - "width": 103, + "width": 103.8, }, Object { "actualText": "绍兴市", @@ -6834,7 +6834,7 @@ Array [ "绍兴市", ], "originalText": "绍兴市", - "width": 103, + "width": 103.8, }, Object { "actualText": "宁波市", @@ -6845,7 +6845,7 @@ Array [ "宁波市", ], "originalText": "宁波市", - "width": 103, + "width": 103.8, }, Object { "actualText": "舟山市", @@ -6856,7 +6856,7 @@ Array [ "舟山市", ], "originalText": "舟山市", - "width": 103, + "width": 103.8, }, Object { "actualText": "成都市", @@ -6867,7 +6867,7 @@ Array [ "成都市", ], "originalText": "成都市", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -6878,7 +6878,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具家具家具家具家具家具家...", @@ -6890,7 +6890,7 @@ Array [ "具家具家具家...", ], "originalText": "家具家具家具家具家具家具家具家具家具家具家具家具家具家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -6901,7 +6901,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -6912,7 +6912,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -6923,7 +6923,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -6934,7 +6934,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -6945,7 +6945,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -6956,7 +6956,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -6967,7 +6967,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -6978,7 +6978,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -6989,7 +6989,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -7000,7 +7000,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -7011,7 +7011,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -7022,7 +7022,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -7033,7 +7033,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -7044,7 +7044,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "办公用品", @@ -7055,7 +7055,7 @@ Array [ "办公用品", ], "originalText": "办公用品", - "width": 103, + "width": 103.8, }, Object { "actualText": "家具", @@ -7066,7 +7066,7 @@ Array [ "家具", ], "originalText": "家具", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子", @@ -7077,7 +7077,7 @@ Array [ "桌子", ], "originalText": "桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子桌子桌子桌子桌子桌子桌...", @@ -7089,7 +7089,7 @@ Array [ "子桌子桌子桌...", ], "originalText": "桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子", @@ -7100,7 +7100,7 @@ Array [ "桌子", ], "originalText": "桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子", @@ -7111,7 +7111,7 @@ Array [ "桌子", ], "originalText": "桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子", @@ -7122,7 +7122,7 @@ Array [ "桌子", ], "originalText": "桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "沙发", @@ -7133,7 +7133,7 @@ Array [ "沙发", ], "originalText": "沙发", - "width": 103, + "width": 103.8, }, Object { "actualText": "沙发", @@ -7144,7 +7144,7 @@ Array [ "沙发", ], "originalText": "沙发", - "width": 103, + "width": 103.8, }, Object { "actualText": "沙发", @@ -7155,7 +7155,7 @@ Array [ "沙发", ], "originalText": "沙发", - "width": 103, + "width": 103.8, }, Object { "actualText": "沙发", @@ -7166,7 +7166,7 @@ Array [ "沙发", ], "originalText": "沙发", - "width": 103, + "width": 103.8, }, Object { "actualText": "笔", @@ -7177,7 +7177,7 @@ Array [ "笔", ], "originalText": "笔", - "width": 103, + "width": 103.8, }, Object { "actualText": "笔", @@ -7188,7 +7188,7 @@ Array [ "笔", ], "originalText": "笔", - "width": 103, + "width": 103.8, }, Object { "actualText": "笔", @@ -7199,7 +7199,7 @@ Array [ "笔", ], "originalText": "笔", - "width": 103, + "width": 103.8, }, Object { "actualText": "笔", @@ -7210,7 +7210,7 @@ Array [ "笔", ], "originalText": "笔", - "width": 103, + "width": 103.8, }, Object { "actualText": "纸张", @@ -7221,7 +7221,7 @@ Array [ "纸张", ], "originalText": "纸张", - "width": 103, + "width": 103.8, }, Object { "actualText": "纸张", @@ -7232,7 +7232,7 @@ Array [ "纸张", ], "originalText": "纸张", - "width": 103, + "width": 103.8, }, Object { "actualText": "纸张", @@ -7243,7 +7243,7 @@ Array [ "纸张", ], "originalText": "纸张", - "width": 103, + "width": 103.8, }, Object { "actualText": "纸张", @@ -7254,7 +7254,7 @@ Array [ "纸张", ], "originalText": "纸张", - "width": 103, + "width": 103.8, }, Object { "actualText": "桌子", @@ -7265,7 +7265,7 @@ Array [ "桌子", ], "originalText": "桌子", - "width": 103, + "width": 103.8, }, Object { "actualText": "236723672361111", @@ -7273,23 +7273,23 @@ Array [ "actualTextWidth": 100, "height": 20, "multiLineActualTexts": Array [ - "236723672361", - "111", + "2367236723611", + "11", ], "originalText": "236723672361111", - "width": 103, + "width": 103.8, }, Object { - "actualText": "77897789778977897789778...", + "actualText": "778977897789778977897789...", "actualTextHeight": 30, - "actualTextWidth": 165, + "actualTextWidth": 172, "height": 20, "multiLineActualTexts": Array [ - "778977897789", - "77897789778...", + "7789778977897", + "78977897789...", ], "originalText": "7789778977897789778977897789", - "width": 103, + "width": 103.8, }, Object { "actualText": "236723672361111", @@ -7297,11 +7297,11 @@ Array [ "actualTextWidth": 100, "height": 20, "multiLineActualTexts": Array [ - "236723672361", - "111", + "2367236723611", + "11", ], "originalText": "236723672361111", - "width": 103, + "width": 103.8, }, Object { "actualText": "3877", @@ -7312,7 +7312,7 @@ Array [ "3877", ], "originalText": "3877", - "width": 103, + "width": 103.8, }, Object { "actualText": "4342", @@ -7323,7 +7323,7 @@ Array [ "4342", ], "originalText": "4342", - "width": 103, + "width": 103.8, }, Object { "actualText": "5343", @@ -7334,7 +7334,7 @@ Array [ "5343", ], "originalText": "5343", - "width": 103, + "width": 103.8, }, Object { "actualText": "632", @@ -7345,7 +7345,7 @@ Array [ "632", ], "originalText": "632", - "width": 103, + "width": 103.8, }, Object { "actualText": "7234", @@ -7356,7 +7356,7 @@ Array [ "7234", ], "originalText": "7234", - "width": 103, + "width": 103.8, }, Object { "actualText": "834", @@ -7367,7 +7367,7 @@ Array [ "834", ], "originalText": "834", - "width": 103, + "width": 103.8, }, Object { "actualText": "945", @@ -7378,7 +7378,7 @@ Array [ "945", ], "originalText": "945", - "width": 103, + "width": 103.8, }, Object { "actualText": "1304", @@ -7389,7 +7389,7 @@ Array [ "1304", ], "originalText": "1304", - "width": 103, + "width": 103.8, }, Object { "actualText": "1145", @@ -7400,7 +7400,7 @@ Array [ "1145", ], "originalText": "1145", - "width": 103, + "width": 103.8, }, Object { "actualText": "1432", @@ -7411,7 +7411,7 @@ Array [ "1432", ], "originalText": "1432", - "width": 103, + "width": 103.8, }, Object { "actualText": "1343", @@ -7422,7 +7422,7 @@ Array [ "1343", ], "originalText": "1343", - "width": 103, + "width": 103.8, }, Object { "actualText": "1354", @@ -7433,7 +7433,7 @@ Array [ "1354", ], "originalText": "1354", - "width": 103, + "width": 103.8, }, Object { "actualText": "1523", @@ -7444,7 +7444,7 @@ Array [ "1523", ], "originalText": "1523", - "width": 103, + "width": 103.8, }, Object { "actualText": "1634", @@ -7455,7 +7455,7 @@ Array [ "1634", ], "originalText": "1634", - "width": 103, + "width": 103.8, }, Object { "actualText": "1723", @@ -7466,7 +7466,7 @@ Array [ "1723", ], "originalText": "1723", - "width": 103, + "width": 103.8, }, ] `; @@ -7493,7 +7493,7 @@ Array [ "省份", ], "originalText": "省份", - "width": 103, + "width": 103.8, }, Object { "actualText": "城市城市城市城市城市城市城...", @@ -7505,7 +7505,7 @@ Array [ "市城市城市城...", ], "originalText": "城市城市城市城市城市城市城市城市城市城市城市城市", - "width": 103, + "width": 103.8, }, Object { "actualText": "类别类别类别类别类别类别类...", @@ -7517,7 +7517,7 @@ Array [ "别类别类别类...", ], "originalText": "类别类别类别类别类别类别类别类别类别类别", - "width": 103, + "width": 103.8, }, Object { "actualText": "子类别", @@ -7528,7 +7528,7 @@ Array [ "子类别", ], "originalText": "子类别", - "width": 103, + "width": 103.8, }, Object { "actualText": "数量数量数量数量数量数量数...", @@ -7540,7 +7540,7 @@ Array [ "量数量数量数...", ], "originalText": "数量数量数量数量数量数量数量数量数量数量数量", - "width": 103, + "width": 103.8, }, ] `; diff --git a/packages/s2-core/__tests__/spreadsheet/__snapshots__/spread-sheet-resize-spec.ts.snap b/packages/s2-core/__tests__/spreadsheet/__snapshots__/spread-sheet-resize-spec.ts.snap new file mode 100644 index 0000000000..fc3bf7f405 --- /dev/null +++ b/packages/s2-core/__tests__/spreadsheet/__snapshots__/spread-sheet-resize-spec.ts.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SpreadSheet Resize Active Tests should render correctly layout when tree row width is invalid number 1`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]浙江", + "width": 120, + }, + Object { + "height": 30, + "id": "root[&]浙江[&]义乌", + "width": 120, + }, + Object { + "height": 30, + "id": "root[&]浙江[&]杭州", + "width": 120, + }, +] +`; diff --git a/packages/s2-core/__tests__/spreadsheet/__snapshots__/theme-spec.ts.snap b/packages/s2-core/__tests__/spreadsheet/__snapshots__/theme-spec.ts.snap index 309bd54147..81c158e752 100644 --- a/packages/s2-core/__tests__/spreadsheet/__snapshots__/theme-spec.ts.snap +++ b/packages/s2-core/__tests__/spreadsheet/__snapshots__/theme-spec.ts.snap @@ -22,6 +22,7 @@ Object { "cell": Object { "backgroundColor": "#3471F9", "backgroundColorOpacity": 1, + "borderDash": Array [], "horizontalBorderColor": "#5286FA", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, @@ -106,7 +107,7 @@ Object { "fontWeight": 700, "maxLines": 1, "opacity": 1, - "textAlign": "left", + "textAlign": "right", "textBaseline": "middle", "textOverflow": "ellipsis", "wordWrap": true, @@ -114,6 +115,7 @@ Object { "cell": Object { "backgroundColor": "#3471F9", "backgroundColorOpacity": 1, + "borderDash": Array [], "horizontalBorderColor": "#5286FA", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, @@ -142,7 +144,7 @@ Object { "fontWeight": 700, "maxLines": 1, "opacity": 1, - "textAlign": "right", + "textAlign": "left", "textBaseline": "middle", "textOverflow": "ellipsis", "wordWrap": true, @@ -448,6 +450,7 @@ Object { "cell": Object { "backgroundColor": "#F5F8FF", "backgroundColorOpacity": 1, + "borderDash": Array [], "horizontalBorderColor": "#E1EAFE", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, @@ -549,6 +552,7 @@ Object { "trackColor": "rgba(0,0,0,0.01)", }, "splitLine": Object { + "borderDash": Array [], "horizontalBorderColor": "#3471F9", "horizontalBorderColorOpacity": 0.2, "horizontalBorderWidth": 2, @@ -587,6 +591,7 @@ Object { "cell": Object { "backgroundColor": "#133aad", "backgroundColorOpacity": 1, + "borderDash": Array [], "horizontalBorderColor": "#0647b1", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, @@ -671,7 +676,7 @@ Object { "fontWeight": 700, "maxLines": 1, "opacity": 1, - "textAlign": "left", + "textAlign": "right", "textBaseline": "middle", "textOverflow": "ellipsis", "wordWrap": true, @@ -679,6 +684,7 @@ Object { "cell": Object { "backgroundColor": "#133aad", "backgroundColorOpacity": 1, + "borderDash": Array [], "horizontalBorderColor": "#0647b1", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, @@ -707,7 +713,7 @@ Object { "fontWeight": 700, "maxLines": 1, "opacity": 1, - "textAlign": "right", + "textAlign": "left", "textBaseline": "middle", "textOverflow": "ellipsis", "wordWrap": true, @@ -1013,6 +1019,7 @@ Object { "cell": Object { "backgroundColor": "#151a27", "backgroundColorOpacity": 1, + "borderDash": Array [], "horizontalBorderColor": "#1e2436", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, @@ -1114,6 +1121,7 @@ Object { "trackColor": "rgba(0,0,0,0.01)", }, "splitLine": Object { + "borderDash": Array [], "horizontalBorderColor": "#7899ff", "horizontalBorderColorOpacity": 0.2, "horizontalBorderWidth": 2, @@ -1152,6 +1160,7 @@ Object { "cell": Object { "backgroundColor": "#E0E9FD", "backgroundColorOpacity": 1, + "borderDash": Array [], "horizontalBorderColor": "#CCDBFC", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, @@ -1236,7 +1245,7 @@ Object { "fontWeight": 700, "maxLines": 1, "opacity": 1, - "textAlign": "left", + "textAlign": "right", "textBaseline": "middle", "textOverflow": "ellipsis", "wordWrap": true, @@ -1244,6 +1253,7 @@ Object { "cell": Object { "backgroundColor": "#E0E9FD", "backgroundColorOpacity": 1, + "borderDash": Array [], "horizontalBorderColor": "#CCDBFC", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, @@ -1272,7 +1282,7 @@ Object { "fontWeight": 700, "maxLines": 1, "opacity": 1, - "textAlign": "right", + "textAlign": "left", "textBaseline": "middle", "textOverflow": "ellipsis", "wordWrap": true, @@ -1578,6 +1588,7 @@ Object { "cell": Object { "backgroundColor": "#F5F8FE", "backgroundColorOpacity": 1, + "borderDash": Array [], "horizontalBorderColor": "#E0E9FD", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, @@ -1679,6 +1690,7 @@ Object { "trackColor": "rgba(0,0,0,0.01)", }, "splitLine": Object { + "borderDash": Array [], "horizontalBorderColor": "#326EF4", "horizontalBorderColorOpacity": 0.2, "horizontalBorderWidth": 2, @@ -1695,7 +1707,7 @@ Object { } `; -exports[`SpreadSheet Theme Tests Theme Default Value Tests should get default theme 2`] = ` +exports[`SpreadSheet Theme Tests Theme Default Value Tests should get gray theme 1`] = ` Object { "background": Object { "color": "#FFFFFF", @@ -1715,9 +1727,10 @@ Object { "wordWrap": true, }, "cell": Object { - "backgroundColor": "#E0E9FD", + "backgroundColor": "#F0F2F4", "backgroundColorOpacity": 1, - "horizontalBorderColor": "#CCDBFC", + "borderDash": Array [], + "horizontalBorderColor": "#E7E9ED", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, "interactionState": Object { @@ -1726,7 +1739,7 @@ Object { "backgroundOpacity": 1, }, "hover": Object { - "backgroundColor": "#CCDBFC", + "backgroundColor": "#E7E9ED", "backgroundOpacity": 0.6, }, "prepareSelect": Object { @@ -1739,7 +1752,7 @@ Object { "backgroundOpacity": 1, }, "selected": Object { - "backgroundColor": "#CCDBFC", + "backgroundColor": "#E7E9ED", "backgroundOpacity": 0.6, }, "unselected": Object { @@ -1754,7 +1767,7 @@ Object { "right": 8, "top": 4, }, - "verticalBorderColor": "#CCDBFC", + "verticalBorderColor": "#E7E9ED", "verticalBorderColorOpacity": 1, "verticalBorderWidth": 1, }, @@ -1801,15 +1814,16 @@ Object { "fontWeight": 700, "maxLines": 1, "opacity": 1, - "textAlign": "left", + "textAlign": "right", "textBaseline": "middle", "textOverflow": "ellipsis", "wordWrap": true, }, "cell": Object { - "backgroundColor": "#E0E9FD", + "backgroundColor": "#F0F2F4", "backgroundColorOpacity": 1, - "horizontalBorderColor": "#CCDBFC", + "borderDash": Array [], + "horizontalBorderColor": "#E7E9ED", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, "padding": Object { @@ -1818,7 +1832,7 @@ Object { "right": 8, "top": 4, }, - "verticalBorderColor": "#CCDBFC", + "verticalBorderColor": "#E7E9ED", "verticalBorderColorOpacity": 1, "verticalBorderWidth": 1, }, @@ -1837,7 +1851,7 @@ Object { "fontWeight": 700, "maxLines": 1, "opacity": 1, - "textAlign": "right", + "textAlign": "left", "textBaseline": "middle", "textOverflow": "ellipsis", "wordWrap": true, @@ -1849,7 +1863,7 @@ Object { "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", "fontSize": 12, "fontWeight": 700, - "linkTextFill": "#326EF4", + "linkTextFill": "#565C64", "maxLines": 1, "opacity": 1, "textAlign": "right", @@ -1860,8 +1874,8 @@ Object { "cell": Object { "backgroundColor": "#FFFFFF", "backgroundColorOpacity": 1, - "crossBackgroundColor": "#F5F8FE", - "horizontalBorderColor": "#E0E9FD", + "crossBackgroundColor": "#FAFBFB", + "horizontalBorderColor": "#F0F2F4", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, "interactionState": Object { @@ -1870,11 +1884,11 @@ Object { "backgroundOpacity": 1, }, "hover": Object { - "backgroundColor": "#E0E9FD", + "backgroundColor": "#F0F2F4", "backgroundOpacity": 0.6, }, "hoverFocus": Object { - "backgroundColor": "#E0E9FD", + "backgroundColor": "#F0F2F4", "backgroundOpacity": 0.6, "borderColor": "#000000", "borderOpacity": 1, @@ -1890,7 +1904,7 @@ Object { "backgroundOpacity": 1, }, "selected": Object { - "backgroundColor": "#E0E9FD", + "backgroundColor": "#F0F2F4", "backgroundOpacity": 0.6, }, "unselected": Object { @@ -1905,7 +1919,7 @@ Object { "right": 8, "top": 8, }, - "verticalBorderColor": "#E0E9FD", + "verticalBorderColor": "#F0F2F4", "verticalBorderColorOpacity": 1, "verticalBorderWidth": 1, }, @@ -1919,7 +1933,7 @@ Object { }, "miniChart": Object { "bar": Object { - "fill": "#326EF4", + "fill": "#565C64", "intervalPadding": 4, "opacity": 1, }, @@ -1943,17 +1957,17 @@ Object { }, }, "interval": Object { - "fill": "#326EF4", + "fill": "#9DA7B6", "height": 12, }, "line": Object { "linkLine": Object { - "fill": "#326EF4", + "fill": "#565C64", "opacity": 0.6, "size": 1.5, }, "point": Object { - "fill": "#326EF4", + "fill": "#565C64", "opacity": 1, "size": 2.2, }, @@ -1964,7 +1978,7 @@ Object { "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", "fontSize": 12, "fontWeight": "normal", - "linkTextFill": "#326EF4", + "linkTextFill": "#565C64", "maxLines": 1, "opacity": 1, "textAlign": "right", @@ -1979,7 +1993,7 @@ Object { "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", "fontSize": 12, "fontWeight": 700, - "linkTextFill": "#326EF4", + "linkTextFill": "#565C64", "maxLines": 1, "opacity": 1, "textAlign": "right", @@ -1990,8 +2004,8 @@ Object { "cell": Object { "backgroundColor": "#FFFFFF", "backgroundColorOpacity": 1, - "crossBackgroundColor": "#F5F8FE", - "horizontalBorderColor": "#E0E9FD", + "crossBackgroundColor": "#FAFBFB", + "horizontalBorderColor": "#F0F2F4", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, "interactionState": Object { @@ -2000,11 +2014,11 @@ Object { "backgroundOpacity": 1, }, "hover": Object { - "backgroundColor": "#E0E9FD", + "backgroundColor": "#F0F2F4", "backgroundOpacity": 0.6, }, "hoverFocus": Object { - "backgroundColor": "#E0E9FD", + "backgroundColor": "#F0F2F4", "backgroundOpacity": 0.6, "borderColor": "#000000", "borderOpacity": 1, @@ -2020,7 +2034,7 @@ Object { "backgroundOpacity": 1, }, "selected": Object { - "backgroundColor": "#E0E9FD", + "backgroundColor": "#F0F2F4", "backgroundOpacity": 0.6, }, "unselected": Object { @@ -2035,7 +2049,7 @@ Object { "right": 8, "top": 8, }, - "verticalBorderColor": "#E0E9FD", + "verticalBorderColor": "#F0F2F4", "verticalBorderColorOpacity": 1, "verticalBorderWidth": 1, }, @@ -2049,7 +2063,7 @@ Object { }, "miniChart": Object { "bar": Object { - "fill": "#326EF4", + "fill": "#565C64", "intervalPadding": 4, "opacity": 1, }, @@ -2073,17 +2087,17 @@ Object { }, }, "interval": Object { - "fill": "#326EF4", + "fill": "#9DA7B6", "height": 12, }, "line": Object { "linkLine": Object { - "fill": "#326EF4", + "fill": "#565C64", "opacity": 0.6, "size": 1.5, }, "point": Object { - "fill": "#326EF4", + "fill": "#565C64", "opacity": 1, "size": 2.2, }, @@ -2094,7 +2108,7 @@ Object { "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", "fontSize": 12, "fontWeight": "normal", - "linkTextFill": "#326EF4", + "linkTextFill": "#565C64", "maxLines": 1, "opacity": 1, "textAlign": "right", @@ -2104,13 +2118,13 @@ Object { }, }, "prepareSelectMask": Object { - "backgroundColor": "#234DAB", + "backgroundColor": "#6E757F", "backgroundOpacity": 0.3, }, "resizeArea": Object { - "background": "#326EF4", + "background": "#9DA7B6", "backgroundOpacity": 0, - "guideLineColor": "#326EF4", + "guideLineColor": "#9DA7B6", "guideLineDash": Array [ 3, 3, @@ -2118,7 +2132,7 @@ Object { "guideLineDisableColor": "rgba(0,0,0,0.25)", "interactionState": Object { "hover": Object { - "backgroundColor": "#326EF4", + "backgroundColor": "#9DA7B6", "backgroundOpacity": 1, }, }, @@ -2132,7 +2146,7 @@ Object { "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", "fontSize": 12, "fontWeight": 700, - "linkTextFill": "#326EF4", + "linkTextFill": "#565C64", "maxLines": 1, "opacity": 1, "textAlign": "left", @@ -2141,9 +2155,10 @@ Object { "wordWrap": true, }, "cell": Object { - "backgroundColor": "#F5F8FE", + "backgroundColor": "#FAFBFB", "backgroundColorOpacity": 1, - "horizontalBorderColor": "#E0E9FD", + "borderDash": Array [], + "horizontalBorderColor": "#F0F2F4", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, "interactionState": Object { @@ -2152,7 +2167,7 @@ Object { "backgroundOpacity": 1, }, "hover": Object { - "backgroundColor": "#E0E9FD", + "backgroundColor": "#F0F2F4", "backgroundOpacity": 0.6, }, "prepareSelect": Object { @@ -2165,7 +2180,7 @@ Object { "backgroundOpacity": 1, }, "selected": Object { - "backgroundColor": "#E0E9FD", + "backgroundColor": "#F0F2F4", "backgroundOpacity": 0.6, }, "unselected": Object { @@ -2180,7 +2195,7 @@ Object { "right": 8, "top": 4, }, - "verticalBorderColor": "#E0E9FD", + "verticalBorderColor": "#F0F2F4", "verticalBorderColorOpacity": 1, "verticalBorderWidth": 1, }, @@ -2197,7 +2212,7 @@ Object { "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", "fontSize": 12, "fontWeight": "normal", - "linkTextFill": "#326EF4", + "linkTextFill": "#565C64", "maxLines": 1, "opacity": 1, "textAlign": "left", @@ -2211,7 +2226,7 @@ Object { "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", "fontSize": 12, "fontWeight": "normal", - "linkTextFill": "#326EF4", + "linkTextFill": "#565C64", "maxLines": 1, "opacity": 1, "textAlign": "center", @@ -2224,7 +2239,7 @@ Object { "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", "fontSize": 12, "fontWeight": "normal", - "linkTextFill": "#326EF4", + "linkTextFill": "#565C64", "maxLines": 1, "opacity": 1, "textAlign": "left", @@ -2244,7 +2259,8 @@ Object { "trackColor": "rgba(0,0,0,0.01)", }, "splitLine": Object { - "horizontalBorderColor": "#326EF4", + "borderDash": Array [], + "horizontalBorderColor": "#BAC1CC", "horizontalBorderColorOpacity": 0.2, "horizontalBorderWidth": 2, "shadowColors": Object { @@ -2253,14 +2269,14 @@ Object { }, "shadowWidth": 8, "showShadow": true, - "verticalBorderColor": "#326EF4", + "verticalBorderColor": "#BAC1CC", "verticalBorderColorOpacity": 0.25, "verticalBorderWidth": 2, }, } `; -exports[`SpreadSheet Theme Tests Theme Default Value Tests should get gray theme 1`] = ` +exports[`SpreadSheet Theme Tests Theme Default Value Tests should get pivot sheet default theme 1`] = ` Object { "background": Object { "color": "#FFFFFF", @@ -2280,9 +2296,10 @@ Object { "wordWrap": true, }, "cell": Object { - "backgroundColor": "#F0F2F4", + "backgroundColor": "#E0E9FD", "backgroundColorOpacity": 1, - "horizontalBorderColor": "#E7E9ED", + "borderDash": Array [], + "horizontalBorderColor": "#CCDBFC", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, "interactionState": Object { @@ -2291,7 +2308,7 @@ Object { "backgroundOpacity": 1, }, "hover": Object { - "backgroundColor": "#E7E9ED", + "backgroundColor": "#CCDBFC", "backgroundOpacity": 0.6, }, "prepareSelect": Object { @@ -2304,7 +2321,7 @@ Object { "backgroundOpacity": 1, }, "selected": Object { - "backgroundColor": "#E7E9ED", + "backgroundColor": "#CCDBFC", "backgroundOpacity": 0.6, }, "unselected": Object { @@ -2319,7 +2336,7 @@ Object { "right": 8, "top": 4, }, - "verticalBorderColor": "#E7E9ED", + "verticalBorderColor": "#CCDBFC", "verticalBorderColorOpacity": 1, "verticalBorderWidth": 1, }, @@ -2366,15 +2383,16 @@ Object { "fontWeight": 700, "maxLines": 1, "opacity": 1, - "textAlign": "left", + "textAlign": "right", "textBaseline": "middle", "textOverflow": "ellipsis", "wordWrap": true, }, "cell": Object { - "backgroundColor": "#F0F2F4", + "backgroundColor": "#E0E9FD", "backgroundColorOpacity": 1, - "horizontalBorderColor": "#E7E9ED", + "borderDash": Array [], + "horizontalBorderColor": "#CCDBFC", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, "padding": Object { @@ -2383,7 +2401,7 @@ Object { "right": 8, "top": 4, }, - "verticalBorderColor": "#E7E9ED", + "verticalBorderColor": "#CCDBFC", "verticalBorderColorOpacity": 1, "verticalBorderWidth": 1, }, @@ -2402,7 +2420,7 @@ Object { "fontWeight": 700, "maxLines": 1, "opacity": 1, - "textAlign": "right", + "textAlign": "left", "textBaseline": "middle", "textOverflow": "ellipsis", "wordWrap": true, @@ -2414,7 +2432,7 @@ Object { "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", "fontSize": 12, "fontWeight": 700, - "linkTextFill": "#565C64", + "linkTextFill": "#326EF4", "maxLines": 1, "opacity": 1, "textAlign": "right", @@ -2425,8 +2443,8 @@ Object { "cell": Object { "backgroundColor": "#FFFFFF", "backgroundColorOpacity": 1, - "crossBackgroundColor": "#FAFBFB", - "horizontalBorderColor": "#F0F2F4", + "crossBackgroundColor": "#F5F8FE", + "horizontalBorderColor": "#E0E9FD", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, "interactionState": Object { @@ -2435,11 +2453,11 @@ Object { "backgroundOpacity": 1, }, "hover": Object { - "backgroundColor": "#F0F2F4", + "backgroundColor": "#E0E9FD", "backgroundOpacity": 0.6, }, "hoverFocus": Object { - "backgroundColor": "#F0F2F4", + "backgroundColor": "#E0E9FD", "backgroundOpacity": 0.6, "borderColor": "#000000", "borderOpacity": 1, @@ -2455,7 +2473,7 @@ Object { "backgroundOpacity": 1, }, "selected": Object { - "backgroundColor": "#F0F2F4", + "backgroundColor": "#E0E9FD", "backgroundOpacity": 0.6, }, "unselected": Object { @@ -2470,7 +2488,7 @@ Object { "right": 8, "top": 8, }, - "verticalBorderColor": "#F0F2F4", + "verticalBorderColor": "#E0E9FD", "verticalBorderColorOpacity": 1, "verticalBorderWidth": 1, }, @@ -2484,7 +2502,7 @@ Object { }, "miniChart": Object { "bar": Object { - "fill": "#565C64", + "fill": "#326EF4", "intervalPadding": 4, "opacity": 1, }, @@ -2508,17 +2526,17 @@ Object { }, }, "interval": Object { - "fill": "#9DA7B6", + "fill": "#326EF4", "height": 12, }, "line": Object { "linkLine": Object { - "fill": "#565C64", + "fill": "#326EF4", "opacity": 0.6, "size": 1.5, }, "point": Object { - "fill": "#565C64", + "fill": "#326EF4", "opacity": 1, "size": 2.2, }, @@ -2529,7 +2547,7 @@ Object { "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", "fontSize": 12, "fontWeight": "normal", - "linkTextFill": "#565C64", + "linkTextFill": "#326EF4", "maxLines": 1, "opacity": 1, "textAlign": "right", @@ -2544,7 +2562,7 @@ Object { "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", "fontSize": 12, "fontWeight": 700, - "linkTextFill": "#565C64", + "linkTextFill": "#326EF4", "maxLines": 1, "opacity": 1, "textAlign": "right", @@ -2555,8 +2573,8 @@ Object { "cell": Object { "backgroundColor": "#FFFFFF", "backgroundColorOpacity": 1, - "crossBackgroundColor": "#FAFBFB", - "horizontalBorderColor": "#F0F2F4", + "crossBackgroundColor": "#F5F8FE", + "horizontalBorderColor": "#E0E9FD", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, "interactionState": Object { @@ -2565,11 +2583,11 @@ Object { "backgroundOpacity": 1, }, "hover": Object { - "backgroundColor": "#F0F2F4", + "backgroundColor": "#E0E9FD", "backgroundOpacity": 0.6, }, "hoverFocus": Object { - "backgroundColor": "#F0F2F4", + "backgroundColor": "#E0E9FD", "backgroundOpacity": 0.6, "borderColor": "#000000", "borderOpacity": 1, @@ -2585,7 +2603,7 @@ Object { "backgroundOpacity": 1, }, "selected": Object { - "backgroundColor": "#F0F2F4", + "backgroundColor": "#E0E9FD", "backgroundOpacity": 0.6, }, "unselected": Object { @@ -2600,7 +2618,7 @@ Object { "right": 8, "top": 8, }, - "verticalBorderColor": "#F0F2F4", + "verticalBorderColor": "#E0E9FD", "verticalBorderColorOpacity": 1, "verticalBorderWidth": 1, }, @@ -2614,7 +2632,7 @@ Object { }, "miniChart": Object { "bar": Object { - "fill": "#565C64", + "fill": "#326EF4", "intervalPadding": 4, "opacity": 1, }, @@ -2638,17 +2656,17 @@ Object { }, }, "interval": Object { - "fill": "#9DA7B6", + "fill": "#326EF4", "height": 12, }, "line": Object { "linkLine": Object { - "fill": "#565C64", + "fill": "#326EF4", "opacity": 0.6, "size": 1.5, }, "point": Object { - "fill": "#565C64", + "fill": "#326EF4", "opacity": 1, "size": 2.2, }, @@ -2659,7 +2677,7 @@ Object { "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", "fontSize": 12, "fontWeight": "normal", - "linkTextFill": "#565C64", + "linkTextFill": "#326EF4", "maxLines": 1, "opacity": 1, "textAlign": "right", @@ -2669,13 +2687,13 @@ Object { }, }, "prepareSelectMask": Object { - "backgroundColor": "#6E757F", + "backgroundColor": "#234DAB", "backgroundOpacity": 0.3, }, "resizeArea": Object { - "background": "#9DA7B6", + "background": "#326EF4", "backgroundOpacity": 0, - "guideLineColor": "#9DA7B6", + "guideLineColor": "#326EF4", "guideLineDash": Array [ 3, 3, @@ -2683,7 +2701,7 @@ Object { "guideLineDisableColor": "rgba(0,0,0,0.25)", "interactionState": Object { "hover": Object { - "backgroundColor": "#9DA7B6", + "backgroundColor": "#326EF4", "backgroundOpacity": 1, }, }, @@ -2697,7 +2715,7 @@ Object { "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", "fontSize": 12, "fontWeight": 700, - "linkTextFill": "#565C64", + "linkTextFill": "#326EF4", "maxLines": 1, "opacity": 1, "textAlign": "left", @@ -2706,9 +2724,10 @@ Object { "wordWrap": true, }, "cell": Object { - "backgroundColor": "#FAFBFB", + "backgroundColor": "#F5F8FE", "backgroundColorOpacity": 1, - "horizontalBorderColor": "#F0F2F4", + "borderDash": Array [], + "horizontalBorderColor": "#E0E9FD", "horizontalBorderColorOpacity": 1, "horizontalBorderWidth": 1, "interactionState": Object { @@ -2717,7 +2736,7 @@ Object { "backgroundOpacity": 1, }, "hover": Object { - "backgroundColor": "#F0F2F4", + "backgroundColor": "#E0E9FD", "backgroundOpacity": 0.6, }, "prepareSelect": Object { @@ -2730,7 +2749,7 @@ Object { "backgroundOpacity": 1, }, "selected": Object { - "backgroundColor": "#F0F2F4", + "backgroundColor": "#E0E9FD", "backgroundOpacity": 0.6, }, "unselected": Object { @@ -2745,7 +2764,7 @@ Object { "right": 8, "top": 4, }, - "verticalBorderColor": "#F0F2F4", + "verticalBorderColor": "#E0E9FD", "verticalBorderColorOpacity": 1, "verticalBorderWidth": 1, }, @@ -2762,7 +2781,7 @@ Object { "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", "fontSize": 12, "fontWeight": "normal", - "linkTextFill": "#565C64", + "linkTextFill": "#326EF4", "maxLines": 1, "opacity": 1, "textAlign": "left", @@ -2776,7 +2795,7 @@ Object { "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", "fontSize": 12, "fontWeight": "normal", - "linkTextFill": "#565C64", + "linkTextFill": "#326EF4", "maxLines": 1, "opacity": 1, "textAlign": "center", @@ -2789,7 +2808,7 @@ Object { "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", "fontSize": 12, "fontWeight": "normal", - "linkTextFill": "#565C64", + "linkTextFill": "#326EF4", "maxLines": 1, "opacity": 1, "textAlign": "left", @@ -2809,7 +2828,8 @@ Object { "trackColor": "rgba(0,0,0,0.01)", }, "splitLine": Object { - "horizontalBorderColor": "#BAC1CC", + "borderDash": Array [], + "horizontalBorderColor": "#326EF4", "horizontalBorderColorOpacity": 0.2, "horizontalBorderWidth": 2, "shadowColors": Object { @@ -2818,7 +2838,576 @@ Object { }, "shadowWidth": 8, "showShadow": true, - "verticalBorderColor": "#BAC1CC", + "verticalBorderColor": "#326EF4", + "verticalBorderColorOpacity": 0.25, + "verticalBorderWidth": 2, + }, +} +`; + +exports[`SpreadSheet Theme Tests Theme Default Value Tests should get table sheet theme 1`] = ` +Object { + "background": Object { + "color": "#FFFFFF", + "opacity": 1, + }, + "colCell": Object { + "bolderText": Object { + "fill": "#000000", + "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", + "fontSize": 12, + "fontWeight": 700, + "maxLines": 1, + "opacity": 1, + "textAlign": "center", + "textBaseline": "middle", + "textOverflow": "ellipsis", + "wordWrap": true, + }, + "cell": Object { + "backgroundColor": "#E0E9FD", + "backgroundColorOpacity": 1, + "borderDash": Array [], + "horizontalBorderColor": "#CCDBFC", + "horizontalBorderColorOpacity": 1, + "horizontalBorderWidth": 1, + "interactionState": Object { + "highlight": Object { + "backgroundColor": "#87B5FF", + "backgroundOpacity": 1, + }, + "hover": Object { + "backgroundColor": "#CCDBFC", + "backgroundOpacity": 0.6, + }, + "prepareSelect": Object { + "borderColor": "#000000", + "borderOpacity": 1, + "borderWidth": 1, + }, + "searchResult": Object { + "backgroundColor": "#F0F7FF", + "backgroundOpacity": 1, + }, + "selected": Object { + "backgroundColor": "#CCDBFC", + "backgroundOpacity": 0.6, + }, + "unselected": Object { + "backgroundOpacity": 0.3, + "opacity": 0.3, + "textOpacity": 0.3, + }, + }, + "padding": Object { + "bottom": 4, + "left": 8, + "right": 8, + "top": 4, + }, + "verticalBorderColor": "#CCDBFC", + "verticalBorderColorOpacity": 1, + "verticalBorderWidth": 1, + }, + "icon": Object { + "fill": "#000000", + "margin": Object { + "bottom": 6, + "left": 4, + "right": 4, + "top": 6, + }, + "size": 10, + }, + "measureText": Object { + "fill": "#000000", + "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", + "fontSize": 12, + "fontWeight": "normal", + "maxLines": 1, + "opacity": 1, + "textAlign": "right", + "textBaseline": "middle", + "textOverflow": "ellipsis", + "wordWrap": true, + }, + "text": Object { + "fill": "#000000", + "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", + "fontSize": 12, + "fontWeight": "normal", + "maxLines": 1, + "opacity": 1, + "textAlign": "center", + "textBaseline": "middle", + "textOverflow": "ellipsis", + "wordWrap": true, + }, + }, + "cornerCell": Object { + "bolderText": Object { + "fill": "#000000", + "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", + "fontSize": 12, + "fontWeight": 700, + "maxLines": 1, + "opacity": 1, + "textAlign": "center", + "textBaseline": "middle", + "textOverflow": "ellipsis", + "wordWrap": true, + }, + "cell": Object { + "backgroundColor": "#E0E9FD", + "backgroundColorOpacity": 1, + "borderDash": Array [], + "horizontalBorderColor": "#CCDBFC", + "horizontalBorderColorOpacity": 1, + "horizontalBorderWidth": 1, + "padding": Object { + "bottom": 4, + "left": 8, + "right": 8, + "top": 4, + }, + "verticalBorderColor": "#CCDBFC", + "verticalBorderColorOpacity": 1, + "verticalBorderWidth": 1, + }, + "icon": Object { + "fill": "#000000", + "margin": Object { + "left": 4, + "right": 4, + }, + "size": 10, + }, + "text": Object { + "fill": "#000000", + "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", + "fontSize": 12, + "fontWeight": 700, + "maxLines": 1, + "opacity": 1, + "textAlign": "center", + "textBaseline": "middle", + "textOverflow": "ellipsis", + "wordWrap": true, + }, + }, + "dataCell": Object { + "bolderText": Object { + "fill": "#000000", + "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", + "fontSize": 12, + "fontWeight": 700, + "linkTextFill": "#326EF4", + "maxLines": 1, + "opacity": 1, + "textAlign": "right", + "textBaseline": "middle", + "textOverflow": "ellipsis", + "wordWrap": true, + }, + "cell": Object { + "backgroundColor": "#FFFFFF", + "backgroundColorOpacity": 1, + "crossBackgroundColor": "#F5F8FE", + "horizontalBorderColor": "#E0E9FD", + "horizontalBorderColorOpacity": 1, + "horizontalBorderWidth": 1, + "interactionState": Object { + "highlight": Object { + "backgroundColor": "#87B5FF", + "backgroundOpacity": 1, + }, + "hover": Object { + "backgroundColor": "#E0E9FD", + "backgroundOpacity": 0.6, + }, + "hoverFocus": Object { + "backgroundColor": "#E0E9FD", + "backgroundOpacity": 0.6, + "borderColor": "#000000", + "borderOpacity": 1, + "borderWidth": 1, + }, + "prepareSelect": Object { + "borderColor": "#000000", + "borderOpacity": 1, + "borderWidth": 1, + }, + "searchResult": Object { + "backgroundColor": "#F0F7FF", + "backgroundOpacity": 1, + }, + "selected": Object { + "backgroundColor": "#E0E9FD", + "backgroundOpacity": 0.6, + }, + "unselected": Object { + "backgroundOpacity": 0.3, + "opacity": 0.3, + "textOpacity": 0.3, + }, + }, + "padding": Object { + "bottom": 8, + "left": 8, + "right": 8, + "top": 8, + }, + "verticalBorderColor": "#E0E9FD", + "verticalBorderColorOpacity": 1, + "verticalBorderWidth": 1, + }, + "icon": Object { + "fill": "#000000", + "margin": Object { + "left": 4, + "right": 4, + }, + "size": 10, + }, + "miniChart": Object { + "bar": Object { + "fill": "#326EF4", + "intervalPadding": 4, + "opacity": 1, + }, + "bullet": Object { + "backgroundColor": "#E9E9E9", + "comparativeMeasure": Object { + "fill": "#000000", + "height": 12, + "opacity": 0.25, + "width": 1, + }, + "progressBar": Object { + "height": 10, + "innerHeight": 6, + "widthPercent": 0.6, + }, + "rangeColors": Object { + "bad": "#FF4D4F", + "good": "#29A294", + "satisfactory": "#FAAD14", + }, + }, + "interval": Object { + "fill": "#326EF4", + "height": 12, + }, + "line": Object { + "linkLine": Object { + "fill": "#326EF4", + "opacity": 0.6, + "size": 1.5, + }, + "point": Object { + "fill": "#326EF4", + "opacity": 1, + "size": 2.2, + }, + }, + }, + "text": Object { + "fill": "#000000", + "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", + "fontSize": 12, + "fontWeight": "normal", + "linkTextFill": "#326EF4", + "maxLines": 1, + "opacity": 1, + "textAlign": "right", + "textBaseline": "middle", + "textOverflow": "ellipsis", + "wordWrap": true, + }, + }, + "mergedCell": Object { + "bolderText": Object { + "fill": "#000000", + "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", + "fontSize": 12, + "fontWeight": 700, + "linkTextFill": "#326EF4", + "maxLines": 1, + "opacity": 1, + "textAlign": "right", + "textBaseline": "middle", + "textOverflow": "ellipsis", + "wordWrap": true, + }, + "cell": Object { + "backgroundColor": "#FFFFFF", + "backgroundColorOpacity": 1, + "crossBackgroundColor": "#F5F8FE", + "horizontalBorderColor": "#E0E9FD", + "horizontalBorderColorOpacity": 1, + "horizontalBorderWidth": 1, + "interactionState": Object { + "highlight": Object { + "backgroundColor": "#87B5FF", + "backgroundOpacity": 1, + }, + "hover": Object { + "backgroundColor": "#E0E9FD", + "backgroundOpacity": 0.6, + }, + "hoverFocus": Object { + "backgroundColor": "#E0E9FD", + "backgroundOpacity": 0.6, + "borderColor": "#000000", + "borderOpacity": 1, + "borderWidth": 1, + }, + "prepareSelect": Object { + "borderColor": "#000000", + "borderOpacity": 1, + "borderWidth": 1, + }, + "searchResult": Object { + "backgroundColor": "#F0F7FF", + "backgroundOpacity": 1, + }, + "selected": Object { + "backgroundColor": "#E0E9FD", + "backgroundOpacity": 0.6, + }, + "unselected": Object { + "backgroundOpacity": 0.3, + "opacity": 0.3, + "textOpacity": 0.3, + }, + }, + "padding": Object { + "bottom": 8, + "left": 8, + "right": 8, + "top": 8, + }, + "verticalBorderColor": "#E0E9FD", + "verticalBorderColorOpacity": 1, + "verticalBorderWidth": 1, + }, + "icon": Object { + "fill": "#000000", + "margin": Object { + "left": 4, + "right": 4, + }, + "size": 10, + }, + "miniChart": Object { + "bar": Object { + "fill": "#326EF4", + "intervalPadding": 4, + "opacity": 1, + }, + "bullet": Object { + "backgroundColor": "#E9E9E9", + "comparativeMeasure": Object { + "fill": "#000000", + "height": 12, + "opacity": 0.25, + "width": 1, + }, + "progressBar": Object { + "height": 10, + "innerHeight": 6, + "widthPercent": 0.6, + }, + "rangeColors": Object { + "bad": "#FF4D4F", + "good": "#29A294", + "satisfactory": "#FAAD14", + }, + }, + "interval": Object { + "fill": "#326EF4", + "height": 12, + }, + "line": Object { + "linkLine": Object { + "fill": "#326EF4", + "opacity": 0.6, + "size": 1.5, + }, + "point": Object { + "fill": "#326EF4", + "opacity": 1, + "size": 2.2, + }, + }, + }, + "text": Object { + "fill": "#000000", + "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", + "fontSize": 12, + "fontWeight": "normal", + "linkTextFill": "#326EF4", + "maxLines": 1, + "opacity": 1, + "textAlign": "right", + "textBaseline": "middle", + "textOverflow": "ellipsis", + "wordWrap": true, + }, + }, + "prepareSelectMask": Object { + "backgroundColor": "#234DAB", + "backgroundOpacity": 0.3, + }, + "resizeArea": Object { + "background": "#326EF4", + "backgroundOpacity": 0, + "guideLineColor": "#326EF4", + "guideLineDash": Array [ + 3, + 3, + ], + "guideLineDisableColor": "rgba(0,0,0,0.25)", + "interactionState": Object { + "hover": Object { + "backgroundColor": "#326EF4", + "backgroundOpacity": 1, + }, + }, + "minCellHeight": 40, + "minCellWidth": 42, + "size": 3, + }, + "rowCell": Object { + "bolderText": Object { + "fill": "#000000", + "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", + "fontSize": 12, + "fontWeight": 700, + "linkTextFill": "#326EF4", + "maxLines": 1, + "opacity": 1, + "textAlign": "center", + "textBaseline": "middle", + "textOverflow": "ellipsis", + "wordWrap": true, + }, + "cell": Object { + "backgroundColor": "#F5F8FE", + "backgroundColorOpacity": 1, + "borderDash": Array [], + "horizontalBorderColor": "#E0E9FD", + "horizontalBorderColorOpacity": 1, + "horizontalBorderWidth": 1, + "interactionState": Object { + "highlight": Object { + "backgroundColor": "#87B5FF", + "backgroundOpacity": 1, + }, + "hover": Object { + "backgroundColor": "#E0E9FD", + "backgroundOpacity": 0.6, + }, + "prepareSelect": Object { + "borderColor": "#000000", + "borderOpacity": 1, + "borderWidth": 1, + }, + "searchResult": Object { + "backgroundColor": "#F0F7FF", + "backgroundOpacity": 1, + }, + "selected": Object { + "backgroundColor": "#E0E9FD", + "backgroundOpacity": 0.6, + }, + "unselected": Object { + "backgroundOpacity": 0.3, + "opacity": 0.3, + "textOpacity": 0.3, + }, + }, + "padding": Object { + "bottom": 4, + "left": 8, + "right": 8, + "top": 4, + }, + "verticalBorderColor": "#E0E9FD", + "verticalBorderColorOpacity": 1, + "verticalBorderWidth": 1, + }, + "icon": Object { + "fill": "#000000", + "margin": Object { + "left": 4, + "right": 4, + }, + "size": 10, + }, + "measureText": Object { + "fill": "#000000", + "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", + "fontSize": 12, + "fontWeight": "normal", + "linkTextFill": "#326EF4", + "maxLines": 1, + "opacity": 1, + "textAlign": "center", + "textBaseline": "middle", + "textOverflow": "ellipsis", + "wordWrap": true, + }, + "seriesNumberWidth": 80, + "seriesText": Object { + "fill": "#000000", + "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", + "fontSize": 12, + "fontWeight": "normal", + "linkTextFill": "#326EF4", + "maxLines": 1, + "opacity": 1, + "textAlign": "center", + "textBaseline": "middle", + "textOverflow": "ellipsis", + "wordWrap": true, + }, + "text": Object { + "fill": "#000000", + "fontFamily": "Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif", + "fontSize": 12, + "fontWeight": "normal", + "linkTextFill": "#326EF4", + "maxLines": 1, + "opacity": 1, + "textAlign": "center", + "textBaseline": "middle", + "textOverflow": "ellipsis", + "wordWrap": true, + }, + }, + "scrollBar": Object { + "hoverSize": 8, + "lineCap": "round", + "size": 6, + "thumbColor": "rgba(0,0,0,0.15)", + "thumbHorizontalMinSize": 32, + "thumbHoverColor": "rgba(0,0,0,0.25)", + "thumbVerticalMinSize": 32, + "trackColor": "rgba(0,0,0,0.01)", + }, + "splitLine": Object { + "borderDash": Array [], + "horizontalBorderColor": "#326EF4", + "horizontalBorderColorOpacity": 0.2, + "horizontalBorderWidth": 2, + "shadowColors": Object { + "left": "rgba(0,0,0,0.1)", + "right": "rgba(0,0,0,0)", + }, + "shadowWidth": 8, + "showShadow": true, + "verticalBorderColor": "#326EF4", "verticalBorderColorOpacity": 0.25, "verticalBorderWidth": 2, }, diff --git a/packages/s2-core/__tests__/spreadsheet/corner-spec.ts b/packages/s2-core/__tests__/spreadsheet/corner-spec.ts index 0336cbc086..4acec56dde 100644 --- a/packages/s2-core/__tests__/spreadsheet/corner-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/corner-spec.ts @@ -49,6 +49,7 @@ describe('PivotSheet Corner Tests', () => { fields: { ...simpleDataConfig.fields, columns: [], + values: ['price'], }, }); s2.setOptions({ @@ -96,6 +97,15 @@ describe('PivotSheet Corner Tests', () => { }, }, }); + + s2.setDataCfg({ + ...simpleDataConfig, + fields: { + ...simpleDataConfig.fields, + values: ['price'], + }, + }); + await s2.render(); const cornerNodes = s2.facet.getCornerNodes(); @@ -315,11 +325,9 @@ describe('PivotSheet Corner Tests', () => { expect(cornerNode.value).toEqual(cornerText); - const cell = s2.facet.cornerHeader.children[0]; + const cornerCell = s2.facet.getCornerCells()[0]; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect(cell.actualText).toEqual(cornerText); + expect(cornerCell.getActualText()).toEqual(cornerText); }); test('should get custom corner extra text when hierarchy type is tree', async () => { @@ -343,10 +351,8 @@ describe('PivotSheet Corner Tests', () => { expect(cornerNode.value).toEqual(cornerExtraFieldText); - const cell = s2.facet.cornerHeader.children[2]; + const cornerCell = s2.facet.getCornerCells()[2]; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect(cell.actualText).toEqual(cornerExtraFieldText); + expect(cornerCell.getActualText()).toEqual(cornerExtraFieldText); }); }); diff --git a/packages/s2-core/__tests__/spreadsheet/custom-cell-style-spec.ts b/packages/s2-core/__tests__/spreadsheet/custom-cell-style-spec.ts index e8a04a4caf..fad262d910 100644 --- a/packages/s2-core/__tests__/spreadsheet/custom-cell-style-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/custom-cell-style-spec.ts @@ -4,12 +4,13 @@ import { getContainer, } from 'tests/util/helpers'; import * as dataConfig from 'tests/data/mock-dataset.json'; +import * as simpleDataConfig from 'tests/data/simple-data.json'; import { customColSimpleColumns } from '../data/custom-table-col-fields'; import { EXTRA_FIELD, type S2DataConfig } from '@/common'; import type { ViewMeta } from '@/common/interface/basic'; import type { Node } from '@/facet/layout/node'; import type { S2Options } from '@/common/interface'; -import { TableSheet, type SpreadSheet } from '@/sheet-type'; +import { TableSheet, type SpreadSheet, PivotSheet } from '@/sheet-type'; describe('SpreadSheet Custom Cell Style Tests', () => { let s2: SpreadSheet; @@ -37,7 +38,7 @@ describe('SpreadSheet Custom Cell Style Tests', () => { }); afterEach(() => { - s2.destroy(); + // s2.destroy(); }); test('should render default cell style', () => { @@ -386,17 +387,27 @@ describe('SpreadSheet Custom Cell Style Tests', () => { }); test('should get custom col cell style if measure column hidden', async () => { - const sheet = createPivotSheet({ - ...s2Options, - style: { - colCell: { - hideValue: true, - widthByField: { - 'root[&]笔': 100, + const sheet = new PivotSheet( + getContainer(), + { + ...simpleDataConfig, + fields: { + ...simpleDataConfig.fields, + values: ['price'], + }, + }, + { + ...s2Options, + style: { + colCell: { + hideValue: true, + widthByField: { + 'root[&]笔': 100, + }, }, }, }, - }); + ); await sheet.render(); diff --git a/packages/s2-core/__tests__/spreadsheet/custom-grid-spec.ts b/packages/s2-core/__tests__/spreadsheet/custom-grid-spec.ts index 5c4a40292f..05c7d307a8 100644 --- a/packages/s2-core/__tests__/spreadsheet/custom-grid-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/custom-grid-spec.ts @@ -3,7 +3,6 @@ import { CustomGridData } from 'tests/data/data-custom-grid'; import { getContainer } from 'tests/util/helpers'; import { pick } from 'lodash'; import { waitForRender } from 'tests/util'; -import type { HeaderCell } from '../../src/cell/header-cell'; import { KEY_GROUP_COL_RESIZE_AREA } from '../../src/common/constant'; import { CustomGridPivotDataSet } from '../../src/data-set/custom-grid-pivot-data-set'; import { @@ -186,9 +185,7 @@ describe('SpreadSheet Custom Grid Tests', () => { ); test('should render custom format corner text', () => { - const cornerCellLabels = ( - s2.facet.cornerHeader.children as HeaderCell[] - ).map((cell: HeaderCell) => { + const cornerCellLabels = s2.facet.getCornerCells().map((cell) => { const value = cell.getActualText(); const meta = cell.getMeta(); @@ -363,9 +360,7 @@ describe('SpreadSheet Custom Grid Tests', () => { ); test('should render custom format corner text', () => { - const cornerCellLabels = ( - s2.facet.cornerHeader.children as HeaderCell[] - ).map((cell: HeaderCell) => { + const cornerCellLabels = s2.facet.getCornerCells().map((cell) => { const value = cell.getActualText(); const meta = cell.getMeta(); diff --git a/packages/s2-core/__tests__/spreadsheet/custom-tree-spec.ts b/packages/s2-core/__tests__/spreadsheet/custom-tree-spec.ts index 8c5ecdd755..854a168503 100644 --- a/packages/s2-core/__tests__/spreadsheet/custom-tree-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/custom-tree-spec.ts @@ -27,11 +27,9 @@ const s2Options: S2Options = { describe('SpreadSheet Custom Tree Tests', () => { let s2: SpreadSheet; - const getCornerCellLabels = () => - (s2.facet as any) - .getCornerHeader() - .getChildren() - .map((cell: HeaderCell) => cell.getActualText()); + const getCornerCellLabels = () => { + return s2.facet.getCornerCells().map((cell) => cell.getActualText()); + }; const mapRowNodes = (spreadsheet: SpreadSheet) => spreadsheet.facet.getRowLeafNodes().map((node) => { @@ -202,10 +200,7 @@ describe('SpreadSheet Custom Tree Tests', () => { await s2.render(); - const cornerCellLabels = (s2.facet as any) - .getCornerHeader() - .getChildren() - .map((cell: HeaderCell) => cell.getActualText()); + const cornerCellLabels = getCornerCellLabels(); expect(cornerCellLabels).toEqual(['测试', '类型']); }); diff --git a/packages/s2-core/__tests__/spreadsheet/empty-dataset-spec.ts b/packages/s2-core/__tests__/spreadsheet/empty-dataset-spec.ts new file mode 100644 index 0000000000..9f70f1426c --- /dev/null +++ b/packages/s2-core/__tests__/spreadsheet/empty-dataset-spec.ts @@ -0,0 +1,69 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import { getContainer } from 'tests/util/helpers'; +import type { S2DataConfig } from '../../src'; +import { PivotSheet, TableSheet } from '@/sheet-type'; +import type { S2Options } from '@/common/interface/s2Options'; + +const s2Options: S2Options = { + width: 400, + height: 400, + hierarchyType: 'grid', +}; + +describe('Empty Dataset Structure Tests', () => { + test('should generate placeholder for pivot mode with single dimension', async () => { + const container = getContainer(); + + const s2DataCfg: S2DataConfig = { + fields: { + rows: ['province', 'city'], + columns: ['type'], + values: ['price'], + valueInCols: true, + }, + data: [], + }; + const s2 = new PivotSheet(container, s2DataCfg, s2Options); + + await s2.render(); + + // @ts-ignore + expect(s2.facet.panelScrollGroupIndexes).toEqual([0, 0, 0, 0]); + }); + + test('should generate placeholder for pivot mode with two dimensions', async () => { + const container = getContainer(); + + const s2DataCfg: S2DataConfig = { + fields: { + rows: ['province', 'city'], + columns: ['type'], + values: ['price', 'cost'], + valueInCols: true, + }, + data: [], + }; + + const s2 = new PivotSheet(container, s2DataCfg, s2Options); + + await s2.render(); + // @ts-ignore + expect(s2.facet.panelScrollGroupIndexes).toEqual([0, 1, 0, 0]); + }); + + test(`shouldn't generate placeholder for table mode`, async () => { + const container = getContainer(); + + const s2DataCfg: S2DataConfig = { + fields: { + columns: ['province', 'city', 'type', 'price', 'cost'], + }, + data: [], + }; + const s2 = new TableSheet(container, s2DataCfg, s2Options); + + await s2.render(); + // @ts-ignore + expect(s2.facet.panelScrollGroupIndexes).toEqual([]); + }); +}); diff --git a/packages/s2-core/__tests__/spreadsheet/empty-string-values-spec.ts b/packages/s2-core/__tests__/spreadsheet/empty-string-values-spec.ts new file mode 100644 index 0000000000..ea9933c33e --- /dev/null +++ b/packages/s2-core/__tests__/spreadsheet/empty-string-values-spec.ts @@ -0,0 +1,124 @@ +import { getContainer } from 'tests/util/helpers'; +import { LayoutWidthType, type S2DataConfig, type S2Options } from '@/common'; +import { PivotSheet, SpreadSheet } from '@/sheet-type'; + +const s2Options: S2Options = { + debug: true, + width: 600, + height: 400, + hierarchyType: 'grid', + style: { + layoutWidthType: LayoutWidthType.Adaptive, + dataCell: { + height: 30, + }, + }, +}; + +const testDataCfg: S2DataConfig = { + meta: [ + { + field: 'first', + name: '一级维度', + }, + { + field: 'second', + name: '二级维度', + }, + { + field: 'number', + name: '数值', + }, + ], + fields: { + rows: ['first', 'second'], + columns: [], + values: ['number'], + valueInCols: true, + }, + data: [ + { + first: '', + second: '维值1', + number: 100, + }, + { + first: '', + second: '维值2', + number: 200, + }, + { + first: null, + second: '维值3', + number: 300, + }, + { + first: null, + second: '维值4', + number: 400, + }, + { + first: '非空维度', + second: '维值5', + number: 500, + }, + { + first: '非空维度', + second: '维值6', + number: 600, + }, + ], +}; + +describe('Empty String Values Tests', () => { + let s2: SpreadSheet; + + beforeEach(() => { + s2 = new PivotSheet(getContainer(), testDataCfg, s2Options); + s2.render(); + }); + + test('should get correctly first dimension values', () => { + const values = s2.dataSet.getDimensionValues('first'); + + expect(values).toEqual(['', 'null', '非空维度']); + }); + + test('should get correctly second dimension values', () => { + const values = s2.dataSet.getDimensionValues('second'); + + expect(values).toEqual([ + '维值1', + '维值2', + '维值3', + '维值4', + '维值5', + '维值6', + ]); + }); + + test('should get correctly second dimension values by specific query', () => { + let values = s2.dataSet.getDimensionValues('second', { first: '' }); + + expect(values).toEqual(['维值1', '维值2']); + + values = s2.dataSet.getDimensionValues('second', { first: 'null' }); + expect(values).toEqual(['维值3', '维值4']); + + values = s2.dataSet.getDimensionValues('second', { first: '非空维度' }); + expect(values).toEqual(['维值5', '维值6']); + }); + + test('should get correctly layout result', () => { + const nodes = s2.facet.getLayoutResult().rowLeafNodes; + + expect(nodes.map((node) => node.id)).toEqual([ + 'root[&][&]维值1', + 'root[&][&]维值2', + 'root[&]null[&]维值3', + 'root[&]null[&]维值4', + 'root[&]非空维度[&]维值5', + 'root[&]非空维度[&]维值6', + ]); + }); +}); diff --git a/packages/s2-core/__tests__/spreadsheet/header-action-icons-spec.ts b/packages/s2-core/__tests__/spreadsheet/header-action-icons-spec.ts index 993e89ef0d..4a9390d360 100644 --- a/packages/s2-core/__tests__/spreadsheet/header-action-icons-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/header-action-icons-spec.ts @@ -183,21 +183,21 @@ describe('HeaderActionIcons Tests', () => { ], Array [ Object { - "x": 158, + "x": 158.5, "y": 9.5, }, Object { - "x": 200, + "x": 200.5, "y": 9.5, }, ], Array [ Object { - "x": 158, + "x": 158.5, "y": 39.5, }, Object { - "x": 200, + "x": 200.5, "y": 39.5, }, ], @@ -281,13 +281,13 @@ describe('HeaderActionIcons Tests', () => { }, Object { "cfg": Object { - "x": 186, + "x": 186.5, "y": 9.5, }, }, Object { "cfg": Object { - "x": 186, + "x": 186.5, "y": 39.5, }, }, diff --git a/packages/s2-core/__tests__/spreadsheet/hidden-columns-spec.ts b/packages/s2-core/__tests__/spreadsheet/hidden-columns-spec.ts index ac6c7760e1..19cc8de525 100644 --- a/packages/s2-core/__tests__/spreadsheet/hidden-columns-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/hidden-columns-spec.ts @@ -3,11 +3,11 @@ import * as mockDataConfig from 'tests/data/mock-dataset.json'; import * as mockPivotDataConfig from 'tests/data/simple-data.json'; import * as mockTableDataConfig from 'tests/data/simple-table-data.json'; import { waitForRender } from 'tests/util'; -import { getContainer } from 'tests/util/helpers'; +import { createPivotSheet, getContainer } from 'tests/util/helpers'; import { customColGridSimpleFields } from '../data/custom-grid-simple-fields'; import { customColMultipleColumns } from '../data/custom-table-col-fields'; import { PivotSheet, TableSheet } from '@/sheet-type'; -import type { HiddenColumnsInfo, S2Options } from '@/common'; +import type { HiddenColumnsInfo, S2DataConfig, S2Options } from '@/common'; const s2Options: S2Options = { width: 400, @@ -213,7 +213,7 @@ describe('SpreadSheet Hidden Columns Tests', () => { let pivotSheet: PivotSheet; - const pivotDataCfg = { + const pivotDataCfg: S2DataConfig = { ...mockPivotDataConfig, fields: { rows: ['province'], @@ -229,7 +229,7 @@ describe('SpreadSheet Hidden Columns Tests', () => { }); afterEach(() => { - pivotSheet.destroy(); + // pivotSheet.destroy(); }); test('should get init column node', () => { @@ -297,7 +297,7 @@ describe('SpreadSheet Hidden Columns Tests', () => { ); }); - test('should not rerender after hidden empty column fields if disable force render', () => { + test('should not rerender after hidden empty column fields if disable force render', async () => { const defaultHiddenColumnsDetail = [ null, ] as unknown as HiddenColumnsInfo[]; @@ -308,7 +308,7 @@ describe('SpreadSheet Hidden Columns Tests', () => { .spyOn(pivotSheet, 'render') .mockImplementationOnce(async () => {}); - pivotSheet.interaction.hideColumns([], false); + await pivotSheet.interaction.hideColumns([], false); const hiddenColumnsDetail = pivotSheet.store.get('hiddenColumnsDetail'); @@ -316,7 +316,7 @@ describe('SpreadSheet Hidden Columns Tests', () => { expect(hiddenColumnsDetail).toEqual(defaultHiddenColumnsDetail); }); - test('should rerender after hidden empty column fields if enable force render', () => { + test('should rerender after hidden empty column fields if enable force render', async () => { const defaultHiddenColumnsDetail = [ null, ] as unknown as HiddenColumnsInfo[]; @@ -327,7 +327,7 @@ describe('SpreadSheet Hidden Columns Tests', () => { .spyOn(pivotSheet, 'render') .mockImplementationOnce(async () => {}); - pivotSheet.interaction.hideColumns([], true); + await pivotSheet.interaction.hideColumns([], true); const hiddenColumnsDetail = pivotSheet.store.get('hiddenColumnsDetail'); @@ -402,13 +402,8 @@ describe('SpreadSheet Hidden Columns Tests', () => { .getColNodes() .find((node) => node.isGrandTotals)!; - const rootNode = pivotSheet.facet - .getColNodes() - .find((node) => node.id === 'root[&]笔')!; - - const parentNode = pivotSheet.facet - .getColNodes() - .find((node) => node.id === 'root[&]笔[&]义乌')!; + const rootNode = pivotSheet.facet.getColNodeById('root[&]笔')!; + const parentNode = pivotSheet.facet.getColNodeById('root[&]笔[&]义乌')!; const hiddenColumnsInfo = pivotSheet.store.get('hiddenColumnsDetail')[0]; @@ -420,6 +415,67 @@ describe('SpreadSheet Hidden Columns Tests', () => { expect(parentNode.hiddenChildNodeInfo).toEqual(hiddenColumnsInfo); }); + // https://github.com/antvis/S2/issues/2355 + test('should render correctly x and width after hide columns when there is only one value for the higher-level dimension.', async () => { + const nodeId = 'root[&]笔[&]义乌[&]price'; + + pivotSheet.setOptions({ + style: { + colCell: { + width: 100, + }, + }, + }); + const data = pivotSheet.dataCfg.data.map((i) => ({ ...i, cost: 0 })); + + pivotSheet.setDataCfg({ + data, + fields: { + values: ['cost', 'price'], + }, + }); + await pivotSheet.render(); + + await pivotSheet.interaction.hideColumns([nodeId]); + + const rootNode = pivotSheet.facet.getColNodeById('root[&]笔'); + + expect(rootNode!.width).toEqual(300); + expect(rootNode!.x).toEqual(0); + }); + + // https://github.com/antvis/S2/issues/2194 + test('should render correctly when always hidden last column', async () => { + const sheet = createPivotSheet( + { + interaction: { + hiddenColumnFields: [], + }, + }, + { useSimpleData: false }, + ); + + await sheet.render(); + + // 模拟一列一列的手动隐藏最后一列 + const colIds = [ + 'root[&]办公用品[&]纸张[&]数量', + 'root[&]办公用品[&]笔[&]数量', + 'root[&]家具[&]沙发[&]数量', + ]; + + await Promise.all( + colIds.map(async (field) => { + await sheet.interaction.hideColumns([field]); + }), + ); + + const leafNodes = sheet.facet.getColLeafNodes(); + + expect(leafNodes).toHaveLength(1); + expect(leafNodes[0].id).toEqual('root[&]家具[&]桌子[&]数量'); + }); + test('should hide columns for multiple columns', async () => { const hiddenColumns = [ 'root[&]自定义节点 a-1[&]自定义节点 a-1-1[&]指标1', @@ -512,9 +568,7 @@ describe('SpreadSheet Hidden Columns Tests', () => { await sheet.interaction.hideColumns([id]); }); - const totalsSiblingNode = sheet.facet - .getColNodes() - .find((node) => node.id === 'root[&]家具')!; + const totalsSiblingNode = sheet.facet.getColNodeById('root[&]家具')!; expect(totalsSiblingNode.x).toEqual(x); expect(totalsSiblingNode.width).toEqual(width); @@ -526,8 +580,8 @@ describe('SpreadSheet Hidden Columns Tests', () => { test('should hide measure node', async () => { const nodeIds = [ - 'root[&]家具[&]桌子[&]number', - 'root[&]办公用品[&]笔[&]number', + 'root[&]家具[&]桌子[&]数量', + 'root[&]办公用品[&]笔[&]数量', ]; await waitForRender(sheet, async () => { @@ -545,8 +599,8 @@ describe('SpreadSheet Hidden Columns Tests', () => { const nodeIds = [ 'root[&]总计', 'root[&]家具[&]小计', - 'root[&]家具[&]桌子[&]number', - 'root[&]办公用品[&]笔[&]number', + 'root[&]家具[&]桌子[&]数量', + 'root[&]办公用品[&]笔[&]数量', ]; await waitForRender(sheet, async () => { @@ -564,39 +618,12 @@ describe('SpreadSheet Hidden Columns Tests', () => { expect(colsHierarchy.sampleNodeForLastLevel?.height).toStrictEqual(30); expect(colsHierarchy.sampleNodeForLastLevel?.y).toStrictEqual(60); expect(colsHierarchy.height).toStrictEqual(90); - expect(colCornerNodesMeta).toMatchInlineSnapshot(` - Array [ - Object { - "height": 30, - "width": 119, - "x": 0, - "y": 60, - }, - Object { - "height": 30, - "width": 119, - "x": 119, - "y": 60, - }, - Object { - "height": 30, - "width": 238, - "x": 0, - "y": 0, - }, - Object { - "height": 30, - "width": 238, - "x": 0, - "y": 30, - }, - ] - `); + expect(colCornerNodesMeta).toMatchSnapshot(); }); // https://github.com/antvis/S2/issues/1721 - test('should hide grand totals node1', async () => { - const nodeId = 'root[&]总计[&]sub_type'; + test('should hide grand totals node', async () => { + const nodeId = 'root[&]总计[&]子类别'; sheet.setDataCfg({ ...mockDataConfig, @@ -607,16 +634,37 @@ describe('SpreadSheet Hidden Columns Tests', () => { valueInCols: true, }, }); + await sheet.render(); - await waitForRender(sheet, async () => { - await sheet.interaction.hideColumns([nodeId]); - }); + await sheet.interaction.hideColumns([nodeId]); const leafNodes = sheet.facet.getColLeafNodes(); expect(leafNodes.some((node) => node.id === nodeId)).toBeFalsy(); expect(leafNodes).toHaveLength(5); }); + + test.each(['grid', 'tree'] as S2Options['hierarchyType'][])( + 'hiding the column totals should not hide the row totals for %s mode', + async (hierarchyType) => { + sheet.setOptions({ hierarchyType }); + await sheet.render(); + + const nodeId = 'root[&]总计'; + const preRowNodes = sheet.facet.getRowNodes(); + const preColumnNodes = sheet.facet.getColNodes(); + + await waitForRender(sheet, async () => { + await sheet.interaction.hideColumns([nodeId]); + }); + + expect(sheet.facet.getRowNodes()[0].id).toBe(nodeId); + expect(sheet.facet.getRowNodes().length).toBe(preRowNodes.length); + expect(sheet.facet.getColNodes().length).toBe( + preColumnNodes.length - 1, + ); + }, + ); }); }); }); diff --git a/packages/s2-core/__tests__/spreadsheet/interaction-brush-selection-scroll-spec.ts b/packages/s2-core/__tests__/spreadsheet/interaction-brush-selection-scroll-spec.ts index a1a8739ac9..a579f830b5 100644 --- a/packages/s2-core/__tests__/spreadsheet/interaction-brush-selection-scroll-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/interaction-brush-selection-scroll-spec.ts @@ -15,6 +15,7 @@ import { TableSheet, SpreadSheet, type S2CellType, + LayoutWidthType, } from '@/index'; const data = getMockData( @@ -55,7 +56,7 @@ const options: S2Options = { showSeriesNumber: true, placeholder: '', style: { - layoutWidthType: 'compact', + layoutWidthType: LayoutWidthType.Compact, dataCell: { height: 32, }, diff --git a/packages/s2-core/__tests__/spreadsheet/miss-dimension-values-spec.ts b/packages/s2-core/__tests__/spreadsheet/miss-dimension-values-spec.ts new file mode 100644 index 0000000000..7fd84b7ba9 --- /dev/null +++ b/packages/s2-core/__tests__/spreadsheet/miss-dimension-values-spec.ts @@ -0,0 +1,269 @@ +import { getContainer } from 'tests/util/helpers'; +import { + EMPTY_FIELD_VALUE, + LayoutWidthType, + ORIGIN_FIELD, + type S2DataConfig, + type S2Options, +} from '@/common'; +import { PivotSheet, SpreadSheet } from '@/sheet-type'; + +const s2Options: S2Options = { + debug: true, + width: 600, + height: 400, + hierarchyType: 'grid', + totals: { + row: { + showGrandTotals: false, + showSubTotals: { + always: false, + }, + reverseGrandTotalsLayout: false, + reverseSubTotalsLayout: false, + subTotalsDimensions: ['first', 'second'], + }, + col: { + showGrandTotals: false, + showSubTotals: false, + reverseGrandTotalsLayout: false, + reverseSubTotalsLayout: false, + subTotalsDimensions: [], + }, + }, + style: { + layoutWidthType: LayoutWidthType.Adaptive, + dataCell: { + height: 30, + }, + }, + showDefaultHeaderActionIcon: false, +}; + +const testDataCfg: S2DataConfig = { + meta: [ + { + field: 'first', + name: '一级维度', + }, + { + field: 'second', + name: '二级维度', + }, + { + field: 'third', + name: '三级维度', + }, + { + field: 'number', + name: '数值', + }, + ], + fields: { + rows: ['first', 'second', 'third'], + columns: [], + values: ['number'], + valueInCols: true, + }, + data: [ + { + first: '总计', + number: 1732771, + }, + { + first: '维值-1', + second: '维值-2', + third: '维度-3', + number: 172245, + }, + { + first: '维值-1', + second: '维值-2', + third: '维度-3', + number: 12222, + }, + { + first: '维值-1', + second: '维值-3', + third: '维值-3', + number: 11111, + }, + { + first: '维值-1', + second: '维值-3', + third: '维度-3', + number: 11111, + }, + { + first: '维值-1', + number: 456, + }, + { + first: '测试-1', + second: '测试-2', + third: '维度-3', + number: 12, + }, + { + first: '测试-1', + second: '测试-2', + third: '维度-3', + number: 4444567, + }, + { + first: '测试-1', + second: '测试-3', + number: 111233, + }, + { + first: '测试-1', + second: '测试-3', + number: 785222, + }, + { + first: '测试-1', + second: '测试-4', + third: '维度-3', + number: 6455644, + }, + { + first: '测试-1', + second: '测试-4', + number: 289898, + }, + { + first: '测试-1', + second: '测试-5', + number: 2222, + }, + { + first: '测试-1', + second: '测试-5', + third: '维度-3', + number: 1111, + }, + { + first: '测试-1', + number: 125555, + }, + { + first: '测试-6', + second: '测试-x', + number: 409090, + }, + { + first: '测试-6', + second: '测试-x', + number: 111111, + }, + { + first: '测试-6', + second: '测试-7', + number: 5555, + }, + { + first: '测试-6', + second: '测试-7', + number: 67878, + }, + { + first: '测试-6', + second: '测试-8', + number: 53445.464, + }, + { + first: '测试-6', + second: '测试-8', + number: 456.464, + }, + { + first: '测试-6', + number: 123.416, + }, + ], +}; + +describe('Miss Dimension Values Tests', () => { + let s2: SpreadSheet; + + beforeEach(async () => { + s2 = new PivotSheet(getContainer(), testDataCfg, s2Options); + await s2.render(); + }); + + test('should get correctly empty dimension values', () => { + const emptyDimensionValueNode = s2.facet.getRowNodes()[0].children[0]; + + expect(emptyDimensionValueNode.value).toEqual(EMPTY_FIELD_VALUE); + expect(emptyDimensionValueNode.id).toEqual( + `root[&]总计[&]${EMPTY_FIELD_VALUE}`, + ); + expect(emptyDimensionValueNode.belongsCell!.getActualText()).toEqual('-'); + }); + + test('should get correctly empty dimension values and use custom placeholder text', async () => { + const placeholder = '*'; + + s2.setOptions({ + placeholder, + }); + + await s2.render(false); + + const emptyDimensionValueNode = s2.facet.getRowNodes()[0].children[0]; + + expect(emptyDimensionValueNode.belongsCell!.getActualText()).toEqual( + placeholder, + ); + }); + + test('should generate correct query for empty node', () => { + const emptyDimensionValueNode1 = s2.facet.getRowNodes()[0]; + + expect(emptyDimensionValueNode1.query).toEqual({ + first: '总计', + }); + + const emptyDimensionValueNode2 = s2.facet.getRowNodes()[1]; + + expect(emptyDimensionValueNode2.query).toEqual({ + first: '总计', + }); + }); + + test('should get correctly dimension data and ignore empty dimension value', () => { + const emptyDimensionValueNode = s2.facet.getRowNodes()[0].children[0]; + + const data = s2.dataSet.getCellMultiData({ + query: emptyDimensionValueNode.query!, + }); + const dimensionValues = s2.dataSet.getDimensionValues( + emptyDimensionValueNode.field, + ); + const emptyDimensionDataCell = s2.facet.getDataCells()[0]; + + expect(emptyDimensionValueNode.query).toEqual( + emptyDimensionValueNode.parent!.query, + ); + expect(emptyDimensionDataCell.getMeta().fieldValue).toEqual(1732771); + expect(data[0][ORIGIN_FIELD]).toMatchInlineSnapshot(` + Object { + "first": "总计", + "number": 1732771, + } + `); + expect(dimensionValues).toMatchInlineSnapshot(` + Array [ + "维值-2", + "维值-3", + "测试-2", + "测试-3", + "测试-4", + "测试-5", + "测试-x", + "测试-7", + "测试-8", + ] + `); + }); +}); diff --git a/packages/s2-core/__tests__/spreadsheet/row-link-spec.ts b/packages/s2-core/__tests__/spreadsheet/row-link-spec.ts index 41d7d61a62..18dd45ec8e 100644 --- a/packages/s2-core/__tests__/spreadsheet/row-link-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/row-link-spec.ts @@ -1,7 +1,7 @@ import { getContainer } from 'tests/util/helpers'; import { noop } from 'lodash'; import { PivotSheet } from '@/sheet-type'; -import { S2Event, type S2Options } from '@/common'; +import { S2Event, type S2DataConfig, type S2Options } from '@/common'; const s2Options: S2Options = { width: 400, @@ -19,7 +19,7 @@ const s2Options: S2Options = { }, }; -const s2DataCfg = { +const s2DataCfg: S2DataConfig = { fields: { rows: ['province', 'city'], columns: ['type'], @@ -32,14 +32,7 @@ const s2DataCfg = { city: '义乌1', type: '笔', price: 1, - cost: 9, - }, - { - province: '浙江', - city: '义乌1', - type: '笔', - price: 10, - cost: 99, + cost: 2, }, { province: '浙江', @@ -76,7 +69,7 @@ describe('Row Text Link Tests', () => { let s2: PivotSheet; const linkFieldJump = jest.fn(); - beforeAll(async () => { + beforeEach(async () => { container = getContainer(); s2 = new PivotSheet(container, s2DataCfg, s2Options); await s2.render(); @@ -122,15 +115,15 @@ describe('Row Text Link Tests', () => { }, } as any); - expect(linkFieldJump).toHaveBeenCalledWith({ + expect(linkFieldJump).toHaveBeenLastCalledWith({ field: 'city', cellData: rowNode, record: { province: '浙江', city: '义乌1', type: '笔', - price: 10, - cost: 99, + price: 1, + cost: 2, rowIndex: 1, }, }); diff --git a/packages/s2-core/__tests__/spreadsheet/scroll-spec.ts b/packages/s2-core/__tests__/spreadsheet/scroll-spec.ts index 34a09bb472..33439bc201 100644 --- a/packages/s2-core/__tests__/spreadsheet/scroll-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/scroll-spec.ts @@ -1,7 +1,8 @@ /* eslint-disable jest/no-conditional-expect */ import * as mockDataConfig from 'tests/data/simple-data.json'; import { createMockCellInfo, getContainer, sleep } from 'tests/util/helpers'; -import { ScrollType } from '../../src/ui/scrollbar'; +import { get } from 'lodash'; +import { ScrollBar, ScrollType } from '../../src/ui/scrollbar'; import type { CellScrollPosition } from './../../src/common/interface/scroll'; import { PivotSheet, SpreadSheet } from '@/sheet-type'; import type { @@ -12,6 +13,7 @@ import type { import { InteractionStateName, InterceptType, + LayoutWidthType, OriginEventType, S2Event, ScrollbarPositionType, @@ -381,6 +383,62 @@ describe('Scroll Tests', () => { }, ); + // https://github.com/antvis/S2/issues/2222 + test.each([ + { + type: 'horizontal', + offset: { + scrollX: 20, + scrollY: 0, + }, + }, + { + type: 'vertical', + offset: { + scrollX: 0, + scrollY: 20, + }, + }, + ])( + 'should trigger hover cells when hover cells after scroll by %o', + async ({ offset }) => { + s2.setOptions({ + interaction: { + hoverAfterScroll: true, + }, + }); + + s2.facet.cornerBBox.maxY = -9999; + s2.facet.panelBBox.minX = -9999; + s2.facet.panelBBox.minY = -9999; + + const bbox = s2.getCanvasElement().getBoundingClientRect(); + const mousemoveEvent = new MouseEvent(OriginEventType.POINTER_MOVE, { + clientX: bbox.left + 100, + clientY: bbox.top + 100, + }); + + canvas.dispatchEvent(mousemoveEvent); + + s2.container.emit = jest.fn(); + + const wheelEvent = new WheelEvent('wheel', { + deltaX: offset.scrollX, + deltaY: offset.scrollY, + }); + + canvas.dispatchEvent(wheelEvent); + + // wait requestAnimationFrame and debounce + await sleep(1000); + + expect(s2.container.emit).toHaveBeenCalledWith( + OriginEventType.POINTER_MOVE, + expect.any(Object), + ); + }, + ); + test('should not trigger scroll event on passive renders', async () => { const sheet = new PivotSheet(getContainer(), mockDataConfig, { ...s2Options, @@ -403,7 +461,7 @@ describe('Scroll Tests', () => { scrollbarPosition: ScrollbarPositionType.CONTENT, }, style: { - layoutWidthType: 'compact', + layoutWidthType: LayoutWidthType.Compact, }, }); s2.changeSheetSize(100, 1000); // 横向滚动条 @@ -414,7 +472,8 @@ describe('Scroll Tests', () => { s2.changeSheetSize(1000, 150); // 纵向滚动条 await s2.render(false); - expect(s2.facet.vScrollBar.getBBox().x).toBe(195); + + expect(Math.floor(s2.facet.vScrollBar.getBBox().x)).toEqual(195); s2.setOptions({ interaction: { @@ -455,11 +514,11 @@ describe('Scroll Tests', () => { const scrollBar = s2.facet[name]; - const positon = scrollBar['getCoordinatesWithBBoxExtraPadding'](); + const position = scrollBar['getCoordinatesWithBBoxExtraPadding'](); expect( Math.round(scrollBar.thumbShape.getBBox()[key] as number), - ).toStrictEqual(Math.round(positon.end - positon.start)); + ).toStrictEqual(Math.round(position.end - position.start)); }, ); @@ -492,7 +551,7 @@ describe('Scroll Tests', () => { rowHeader: true, }, style: { - layoutWidthType: 'compact', + layoutWidthType: LayoutWidthType.Compact, rowCell: { width: 200, }, @@ -639,13 +698,13 @@ describe('Scroll Tests', () => { ).toBeFalsy(); }); - test('should scroll horizontally when shift key is held', async () => { + test('should scroll horizontally when shift key is held on Windows', async () => { s2.setOptions({ frozen: { rowHeader: true, }, style: { - layoutWidthType: 'compact', + layoutWidthType: LayoutWidthType.Compact, rowCell: { width: 200, }, @@ -676,11 +735,39 @@ describe('Scroll Tests', () => { shiftKey: true, }); + Object.defineProperty(window.navigator, 'userAgent', { + value: 'Windows', + configurable: true, + writable: true, + }); + canvas.dispatchEvent(wheelEvent); + await sleep(200); + expect(onScroll).toHaveBeenCalled(); + }); - await sleep(1000); + test('should not scroll horizontally when shift key is held on macOS', async () => { + const onScroll = jest.fn((...args) => { + expect(args[0].rowHeaderScrollX).toBeGreaterThan(0); + expect(args[0].scrollX).toBe(0); + expect(args[0].scrollY).toBe(0); + }); - expect(onScroll).toHaveBeenCalled(); + const wheelEvent = new WheelEvent('wheel', { + deltaX: 0, + deltaY: 20, + shiftKey: true, + }); + + Object.defineProperty(window.navigator, 'userAgent', { + value: 'Mac OS', + configurable: true, + writable: true, + }); + + canvas.dispatchEvent(wheelEvent); + await sleep(200); + expect(onScroll).not.toHaveBeenCalled(); }); it('should not change init body overscrollBehavior style when render and destroyed', async () => { @@ -766,4 +853,92 @@ describe('Scroll Tests', () => { expect(errorSpy).not.toHaveBeenCalled(); }); + + // https://github.com/antvis/S2/issues/2316 + test('should not throw infinite error if spreadsheet is unmounted during scrolling', async () => { + s2.setOptions({ + style: { + rowCell: { + width: 200, + }, + dataCell: { + width: 30, + }, + }, + }); + + await s2.render(false); + + const errorSpy = jest + .spyOn(console, 'error') + .mockImplementationOnce(() => {}); + + // 滚动时 unmount 表格实例 + s2.facet.scrollWithAnimation( + { + offsetX: { + value: 10, + animate: true, + }, + offsetY: { + value: 10, + animate: true, + }, + }, + 200, + ); + s2.destroy(); + + await sleep(500); + + expect(errorSpy).toHaveBeenCalledTimes(0); + }); + + // https://github.com/antvis/S2/issues/2376 + test.each(['hScrollBar', 'hRowScrollBar'])( + 'should not reset interaction state after %s scrollbar thumb or track clicked', + (scrollbarName) => { + const isMatchElementSpy = jest + .spyOn(s2.interaction.eventController, 'isMatchElement') + .mockImplementation(() => true); + + const reset = jest.fn(); + + const scrollbar = get(s2.facet, scrollbarName) as ScrollBar; + const colCell = s2.facet.getColLeafCells()[0]!; + + s2.on(S2Event.GLOBAL_RESET, reset); + s2.interaction.selectHeaderCell({ + cell: colCell, + }); + + expect(s2.interaction.isSelectedState()).toBeTruthy(); + + const { maxX, maxY } = s2.facet?.panelBBox || {}; + const { x, y } = canvas.getBoundingClientRect() || {}; + + // 滚动条 + window.dispatchEvent( + new MouseEvent('click', { + clientX: x + scrollbar.position.x, + // 在滚动条内点击 + clientY: y + scrollbar.position.y + scrollbar!.theme!.size! - 2, + } as MouseEventInit), + ); + + // 滑动轨道 + window.dispatchEvent( + new MouseEvent('click', { + // 右下角滑道点击 + clientX: x + maxX - 2, + clientY: y + maxY + 2, + } as MouseEventInit), + ); + + expect(s2.interaction.isSelectedState()).toBeTruthy(); + expect(reset).not.toHaveBeenCalled(); + + isMatchElementSpy.mockClear(); + }, + ); }); diff --git a/packages/s2-core/__tests__/spreadsheet/sort-by-order-spec.ts b/packages/s2-core/__tests__/spreadsheet/sort-by-order-spec.ts index 33a238ffdc..ab07de4ac3 100644 --- a/packages/s2-core/__tests__/spreadsheet/sort-by-order-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/sort-by-order-spec.ts @@ -73,12 +73,12 @@ describe('Manual Sort Tests', () => { }, { sortFieldId: 'type2', - sortBy: ['整体访问', '小程序访问', '支付宝访问'], + sortBy: ['支付宝访问', '整体访问', '小程序访问'], }, ], }; - beforeAll(async () => { + beforeEach(async () => { const container = getContainer(); s2 = new PivotSheet(container, mockDataCfg, s2Options); @@ -94,12 +94,12 @@ describe('Manual Sort Tests', () => { s2.dataSet.getDimensionValues('type2', { type1: '整体访问', }), - ).toEqual(['整体访问', '小程序访问', '支付宝访问']); + ).toEqual(['支付宝访问', '整体访问', '小程序访问']); expect( s2.dataSet.getDimensionValues('type2', { type1: '小程序访问', }), - ).toEqual(['小程序访问', '整体访问', '支付宝访问']); + ).toEqual(['支付宝访问', '整体访问', '小程序访问']); expect( s2.dataSet.getDimensionValues('type2', { type1: '支付宝访问', diff --git a/packages/s2-core/__tests__/spreadsheet/spread-sheet-facet-layout-api-spec.ts b/packages/s2-core/__tests__/spreadsheet/spread-sheet-facet-layout-api-spec.ts index d711058a39..da32d09082 100644 --- a/packages/s2-core/__tests__/spreadsheet/spread-sheet-facet-layout-api-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/spread-sheet-facet-layout-api-spec.ts @@ -35,17 +35,17 @@ describe('Facet Layout API Tests', () => { Array [ Object { "field": "province", - "id": "", + "id": "province", "value": "province", }, Object { "field": "city", - "id": "", + "id": "city", "value": "city", }, Object { "field": "type", - "id": "", + "id": "type", "value": "type", }, Object { @@ -107,17 +107,17 @@ describe('Facet Layout API Tests', () => { }, Object { "field": "province", - "id": "", + "id": "province", "value": "province", }, Object { "field": "city", - "id": "", + "id": "city", "value": "city", }, Object { "field": "type", - "id": "", + "id": "type", "value": "type", }, Object { @@ -164,17 +164,17 @@ describe('Facet Layout API Tests', () => { Array [ Object { "field": "province", - "id": "", + "id": "province", "value": "province", }, Object { "field": "city", - "id": "", + "id": "city", "value": "city", }, Object { "field": "type", - "id": "", + "id": "type", "value": "type", }, Object { @@ -226,17 +226,17 @@ describe('Facet Layout API Tests', () => { Array [ Object { "field": "province", - "id": "", + "id": "province", "value": "province", }, Object { "field": "city", - "id": "", + "id": "city", "value": "city", }, Object { "field": "type", - "id": "", + "id": "type", "value": "type", }, Object { @@ -314,7 +314,7 @@ describe('Facet Layout API Tests', () => { Array [ Object { "field": "type", - "id": "", + "id": "type", "value": "type", }, Object { @@ -353,17 +353,17 @@ describe('Facet Layout API Tests', () => { Array [ Object { "field": "province", - "id": "", + "id": "province", "value": "province", }, Object { "field": "city", - "id": "", + "id": "city", "value": "city", }, Object { "field": "type", - "id": "", + "id": "type", "value": "type", }, ] @@ -385,17 +385,17 @@ describe('Facet Layout API Tests', () => { }, Object { "field": "province", - "id": "", + "id": "province", "value": "province", }, Object { "field": "city", - "id": "", + "id": "city", "value": "city", }, Object { "field": "type", - "id": "", + "id": "type", "value": "type", }, ] @@ -407,17 +407,17 @@ describe('Facet Layout API Tests', () => { Array [ Object { "field": "province", - "id": "", + "id": "province", "value": "province", }, Object { "field": "city", - "id": "", + "id": "city", "value": "city", }, Object { "field": "type", - "id": "", + "id": "type", "value": "type", }, ] @@ -439,17 +439,17 @@ describe('Facet Layout API Tests', () => { }, Object { "field": "province", - "id": "", + "id": "province", "value": "province", }, Object { "field": "city", - "id": "", + "id": "city", "value": "city", }, Object { "field": "type", - "id": "", + "id": "type", "value": "type", }, ] diff --git a/packages/s2-core/__tests__/spreadsheet/spread-sheet-resize-spec.ts b/packages/s2-core/__tests__/spreadsheet/spread-sheet-resize-spec.ts index 198c0513c3..4c2abe2f06 100644 --- a/packages/s2-core/__tests__/spreadsheet/spread-sheet-resize-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/spread-sheet-resize-spec.ts @@ -1,15 +1,15 @@ import * as mockDataConfig from 'tests/data/simple-data.json'; import { getContainer } from 'tests/util/helpers'; import type { Group } from '@antv/g'; -import { PivotSheet } from '@/sheet-type'; import { - KEY_GROUP_COL_RESIZE_AREA, - KEY_GROUP_CORNER_RESIZE_AREA, - KEY_GROUP_ROW_RESIZE_AREA, type S2Options, -} from '@/common'; + PivotSheet, + KEY_GROUP_ROW_RESIZE_AREA, + KEY_GROUP_CORNER_RESIZE_AREA, + KEY_GROUP_COL_RESIZE_AREA, +} from '../../src'; -async function renderSheet(options: S2Options) { +async function renderSheet(options?: S2Options) { const s2 = new PivotSheet(getContainer(), mockDataConfig, { height: 150, ...options, @@ -22,6 +22,7 @@ async function renderSheet(options: S2Options) { }, }, }); + await s2.render(); return s2; @@ -191,4 +192,31 @@ describe('SpreadSheet Resize Active Tests', () => { expect(group.getElementById(KEY_GROUP_ROW_RESIZE_AREA)).toBeNull(); expect(group.getElementById(KEY_GROUP_CORNER_RESIZE_AREA)).toBeNull(); }); + + test('should render correctly layout when tree row width is invalid number', async () => { + const s2 = await renderSheet(); + + s2.setOptions({ + hierarchyType: 'tree', + style: { + rowCell: { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + width: '@', + }, + }, + }); + + await s2.render(false); + + const nodes = s2.facet.getRowNodes().map((node) => { + return { + id: node.id, + width: node.width, + height: node.height, + }; + }); + + expect(nodes).toMatchSnapshot(); + }); }); diff --git a/packages/s2-core/__tests__/spreadsheet/spread-sheet-series-number-spec.ts b/packages/s2-core/__tests__/spreadsheet/spread-sheet-series-number-spec.ts index 6944013201..0573dc8232 100644 --- a/packages/s2-core/__tests__/spreadsheet/spread-sheet-series-number-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/spread-sheet-series-number-spec.ts @@ -1,8 +1,6 @@ import * as mockDataConfig from 'tests/data/simple-data.json'; import { getContainer } from 'tests/util/helpers'; -import { PivotSheet } from '@/sheet-type'; -import type { S2Options } from '@/common'; -import type { SeriesNumberCell } from '@/cell'; +import { type S2Options, PivotSheet } from '../../src'; const s2Options: S2Options = { width: 400, @@ -23,14 +21,10 @@ describe('SpreadSheet Series Number Tests', () => { await s2.render(); - const seriesNumberHeader = s2.facet.seriesNumberHeader; + const seriesNumberCell = s2.facet.getSeriesNumberCells(); - expect(seriesNumberHeader?.children).toHaveLength(1); - - const seriesNum1 = seriesNumberHeader?.children[0] as SeriesNumberCell; - - // @ts-ignore - expect(seriesNum1.meta.height).toEqual(60); + expect(seriesNumberCell).toHaveLength(1); + expect(seriesNumberCell[0].getMeta().height).toEqual(60); }); test("series number should contain root parent and it's all children in tree mode", async () => { @@ -41,13 +35,9 @@ describe('SpreadSheet Series Number Tests', () => { await s2.render(); - const seriesNumberHeader = s2.facet.seriesNumberHeader; - - expect(seriesNumberHeader?.children).toHaveLength(1); - - const seriesNum1 = seriesNumberHeader?.children[0] as SeriesNumberCell; + const seriesNumberCell = s2.facet.getSeriesNumberCells(); - // @ts-ignore - expect(seriesNum1.meta.height).toEqual(90); + expect(seriesNumberCell).toHaveLength(1); + expect(seriesNumberCell[0].getMeta().height).toEqual(90); }); }); diff --git a/packages/s2-core/__tests__/spreadsheet/spread-sheet-totals-spec.ts b/packages/s2-core/__tests__/spreadsheet/spread-sheet-totals-spec.ts index eeb3b48ac8..666aff715c 100644 --- a/packages/s2-core/__tests__/spreadsheet/spread-sheet-totals-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/spread-sheet-totals-spec.ts @@ -1,8 +1,7 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import { get, merge } from 'lodash'; +import { merge } from 'lodash'; import { assembleDataCfg, assembleOptions, TOTALS_OPTIONS } from 'tests/util'; import { getContainer } from 'tests/util/helpers'; -import { DataCell } from '@/cell'; import type { RawData, S2DataConfig, S2Options } from '@/common'; import type { Node } from '@/facet/layout/node'; import { PivotSheet } from '@/sheet-type'; @@ -147,30 +146,22 @@ describe('Spreadsheet Totals Tests', () => { }); await spreadsheet.render(); - const grandTotal = spreadsheet.facet.panelScrollGroup.children.find( - (child) => - child instanceof DataCell && get(child, 'meta.rowId') === 'root[&]总计', - ) as DataCell; + const grandTotal = spreadsheet.facet + .getDataCells() + .find((cell) => cell.getMeta().rowId === 'root[&]总计')!; - // @ts-ignore - expect(grandTotal.textShape.attr('text')).toEqual('26193'); + expect(grandTotal.getTextShape().attr('text')).toEqual('26193'); - const rowSubtotal1 = spreadsheet.facet.panelScrollGroup.children.find( - (child) => - child instanceof DataCell && - get(child, 'meta.rowId') === 'root[&]浙江省', - ) as DataCell; + const rowSubtotal1 = spreadsheet.facet + .getDataCells() + .find((cell) => cell.getMeta().rowId === 'root[&]浙江省')!; - // @ts-ignore - expect(rowSubtotal1.textShape).toBeUndefined(); + expect(rowSubtotal1.getTextShape()).toBeUndefined(); - const rowSubtotal2 = spreadsheet.facet.panelScrollGroup.children.find( - (child) => - child instanceof DataCell && - get(child, 'meta.rowId') === 'root[&]浙江省', - ) as DataCell; + const rowSubtotal2 = spreadsheet.facet + .getDataCells() + .find((cell) => cell.getMeta().rowId === 'root[&]四川省')!; - // @ts-ignore - expect(rowSubtotal2.textShape).toBeUndefined(); + expect(rowSubtotal2.getTextShape()).toBeUndefined(); }); }); diff --git a/packages/s2-core/__tests__/spreadsheet/spread-sheet-tree-mode-spec.ts b/packages/s2-core/__tests__/spreadsheet/spread-sheet-tree-mode-spec.ts index ae94d02794..2861bae844 100644 --- a/packages/s2-core/__tests__/spreadsheet/spread-sheet-tree-mode-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/spread-sheet-tree-mode-spec.ts @@ -1,5 +1,6 @@ import * as mockDataConfig from 'tests/data/simple-data.json'; import { createPivotSheet, getContainer } from 'tests/util/helpers'; +import { PivotSheet } from '../../src'; import type { S2DataConfig, S2Options } from '@/common'; const s2Options: S2Options = { @@ -11,11 +12,11 @@ const s2Options: S2Options = { describe('SpreadSheet Tree Mode Tests', () => { let container: HTMLElement; - beforeAll(() => { + beforeEach(() => { container = getContainer(); }); - afterAll(() => { + afterEach(() => { container?.remove(); }); @@ -51,5 +52,43 @@ describe('SpreadSheet Tree Mode Tests', () => { rowsHierarchyWidth, ); }); + + // https://github.com/antvis/S2/issues/2389 + test('the corner should only have one line with action icon', async () => { + // 行头维度更改为较长的 name + const newDataCfg: S2DataConfig = { + ...mockDataConfig, + meta: [ + { + field: 'province', + name: '省1234567890份', + }, + { + field: 'city', + name: '城1234567890市', + }, + ], + }; + + // 添加 icon + const newS2Options: S2Options = { + ...s2Options, + headerActionIcons: [ + { + icons: ['SortDownSelected'], + belongsCell: 'cornerCell', + }, + ], + }; + const s2 = new PivotSheet(container, newDataCfg, newS2Options); + + await s2.render(); + + // 检查文本是否只有一行 + const cornerCell = s2.facet.getCornerCells()[0]; + + expect(cornerCell.getTextShapes()).toHaveLength(1); + expect(cornerCell.isMultiLineText()).toBeFalsy(); + }); }); }); diff --git a/packages/s2-core/__tests__/spreadsheet/table-sheet-spec.ts b/packages/s2-core/__tests__/spreadsheet/table-sheet-spec.ts index c9ae16ea22..19c04800dc 100644 --- a/packages/s2-core/__tests__/spreadsheet/table-sheet-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/table-sheet-spec.ts @@ -1,20 +1,19 @@ -import { last } from 'lodash'; import { getContainer, getMockData, sleep } from 'tests/util/helpers'; import { - ColCell, DeviceType, ResizeType, TableSheet, type RawData, type S2DataConfig, type S2Options, + LayoutWidthType, } from '@/index'; const data = getMockData( '../../../s2-react/__tests__/data/tableau-supermarket.csv', ) as RawData[]; -const columns = [ +const columns: string[] = [ 'order_id', 'order_date', 'ship_date', @@ -75,7 +74,7 @@ const options: S2Options = { showSeriesNumber: true, placeholder: '', style: { - layoutWidthType: 'compact', + layoutWidthType: LayoutWidthType.Compact, dataCell: { height: 32, }, @@ -193,12 +192,11 @@ describe('TableSheet normal spec', () => { await s2.render(); - const getLastColCell = () => - last(s2.facet.getColNodes())!.belongsCell as ColCell; - const preColWidth = getLastColCell().getMeta().width; - await sleep(30); + let columnNodes = s2.facet.getColNodes(); + + const startCellWidth = columnNodes[columnNodes.length - 1].width; const { x, width, top } = s2.getCanvasElement().getBoundingClientRect(); s2.getCanvasElement().dispatchEvent( @@ -232,9 +230,10 @@ describe('TableSheet normal spec', () => { await sleep(300); - const currentColWidth = getLastColCell().getMeta().width; + columnNodes = s2.facet.getColNodes(); + const endCellWidth = columnNodes[columnNodes.length - 1].width; - expect(currentColWidth).toBeGreaterThanOrEqual(resizeLength + preColWidth); + expect(Math.floor(endCellWidth - startCellWidth)).toBe(140); }); test('should render link shape', async () => { diff --git a/packages/s2-core/__tests__/spreadsheet/theme-spec.ts b/packages/s2-core/__tests__/spreadsheet/theme-spec.ts index 950807e4d7..2a9e180e62 100644 --- a/packages/s2-core/__tests__/spreadsheet/theme-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/theme-spec.ts @@ -1,7 +1,5 @@ /* eslint-disable jest/expect-expect */ -import { Text, type Group } from '@antv/g'; -import { createPivotSheet } from 'tests/util/helpers'; -import type { RowCell } from '@/cell'; +import { createPivotSheet, createTableSheet } from 'tests/util/helpers'; import { CellType, EXTRA_COLUMN_FIELD, @@ -62,11 +60,17 @@ describe('SpreadSheet Theme Tests', () => { expect(s2.getThemeName()).toEqual('dark'); }); - test('should get default theme', () => { + test('should get pivot sheet default theme', () => { expect(s2.theme).toMatchSnapshot(); expect(s2.theme).toEqual(s2.getTheme()); }); + test('should get table sheet theme', () => { + const tableSheet = createTableSheet(null); + + expect(tableSheet.theme).toMatchSnapshot(); + }); + test.each(['dark', 'gray', 'colorful', 'default'] as ThemeName[])( 'should get %s theme', (name) => { @@ -219,7 +223,7 @@ describe('SpreadSheet Theme Tests', () => { s2.setThemeCfg(getRowCellThemeCfg(align)); await s2.render(); - const rowCell = s2.facet.rowHeader!.children[0] as RowCell; + const rowCell = s2.facet.getRowCells()[0]; const rowCellWidth = rowCell.getMeta().width; const actionIcon = rowCell.getActionIcons()[0]; @@ -463,9 +467,6 @@ describe('SpreadSheet Theme Tests', () => { }); describe('Series Cell Tests', () => { - const getTextShape = (group: Group) => - group.children.find((child) => child instanceof Text) as Text; - test.each(['top', 'middle', 'bottom'] as TextBaseline[])( 'should render %s text align for column nodes', async (textBaseline) => { @@ -488,14 +489,19 @@ describe('SpreadSheet Theme Tests', () => { await s2.render(); - const rowCell = s2.facet.rowHeader!.children[0] as Group; // 浙江省 - const textOfRowCell = getTextShape(rowCell); + // 浙江省 + const rowCell = s2.facet.getRowCells()[0]; + const rowCellTextShape = rowCell.getTextShape(); - const seriesCell = s2.facet.seriesNumberHeader!.children[0] as Group; // 序号1 - const textOfSeriesCell = getTextShape(seriesCell); + // 序号1 + const seriesCell = s2.facet.getSeriesNumberCells()[0]; + const seriesCellTextShape = seriesCell.getTextShape(); - expect(textOfRowCell?.attr('textBaseline')).toEqual(textBaseline); - expect(textOfSeriesCell?.attr('textBaseline')).toEqual(textBaseline); + expect(rowCellTextShape?.attr('textBaseline')).toEqual(textBaseline); + expect(seriesCellTextShape?.attr('textBaseline')).toEqual(textBaseline); + expect(rowCellTextShape.attr('y')).toEqual( + seriesCellTextShape.attr('y'), + ); }, ); }); diff --git a/packages/s2-core/__tests__/spreadsheet/total-group-spec.ts b/packages/s2-core/__tests__/spreadsheet/total-group-spec.ts new file mode 100644 index 0000000000..4505813244 --- /dev/null +++ b/packages/s2-core/__tests__/spreadsheet/total-group-spec.ts @@ -0,0 +1,332 @@ +import { getContainer } from 'tests/util/helpers'; +import { map } from 'lodash'; +import { s2Options, dataCfg } from '../data/total-group-data'; +import { CellData } from '../../src'; +import type { PivotFacet } from '../../src/facet'; +import { PivotSheet } from '@/sheet-type'; +import { type S2Options, ORIGIN_FIELD } from '@/common'; + +describe('Total Group Dimension Test', () => { + let container: HTMLDivElement; + + let s2: PivotSheet; + + beforeEach(() => { + container = getContainer(); + }); + + afterEach(() => { + // s2?.destroy(); + }); + + test(`should get correct layout with row total group dimension 'type'`, async () => { + s2 = new PivotSheet(container, dataCfg, s2Options as S2Options); + await s2.render(); + + const facet = s2.facet as PivotFacet; + const { rowLeafNodes } = facet.getLayoutResult(); + + expect(map(rowLeafNodes, 'id')).toMatchInlineSnapshot(` + Array [ + "root[&]总计[&]家具", + "root[&]总计[&]办公用品", + "root[&]浙江省[&]小计[&]家具", + "root[&]浙江省[&]小计[&]办公用品", + "root[&]浙江省[&]杭州市[&]家具", + "root[&]浙江省[&]杭州市[&]办公用品", + "root[&]浙江省[&]舟山市[&]家具", + "root[&]浙江省[&]舟山市[&]办公用品", + "root[&]四川省[&]小计[&]家具", + "root[&]四川省[&]小计[&]办公用品", + "root[&]四川省[&]成都市[&]家具", + "root[&]四川省[&]成都市[&]办公用品", + "root[&]四川省[&]绵阳市[&]家具", + "root[&]四川省[&]绵阳市[&]办公用品", + ] + `); + + expect((facet.getCellMeta(0, 0)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + type: '家具', + price: 2000, + }); + expect((facet.getCellMeta(1, 1)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + type: '办公用品', + cost: 1900, + }); + }); + + test(`should get correct layout with row total group dimension 'city'`, async () => { + const newS2Options = { + ...s2Options, + totals: { + ...s2Options.totals, + row: { + ...s2Options.totals!.row!, + grandTotalsGroupDimensions: ['city'], + }, + }, + }; + + s2 = new PivotSheet(container, dataCfg, newS2Options as S2Options); + await s2.render(); + + const facet = s2.facet; + const { rowLeafNodes } = facet.getLayoutResult(); + + expect(map(rowLeafNodes, 'id')).toMatchInlineSnapshot(` + Array [ + "root[&]总计[&]杭州市", + "root[&]总计[&]舟山市", + "root[&]总计[&]成都市", + "root[&]总计[&]绵阳市", + "root[&]浙江省[&]小计[&]家具", + "root[&]浙江省[&]小计[&]办公用品", + "root[&]浙江省[&]杭州市[&]家具", + "root[&]浙江省[&]杭州市[&]办公用品", + "root[&]浙江省[&]舟山市[&]家具", + "root[&]浙江省[&]舟山市[&]办公用品", + "root[&]四川省[&]小计[&]家具", + "root[&]四川省[&]小计[&]办公用品", + "root[&]四川省[&]成都市[&]家具", + "root[&]四川省[&]成都市[&]办公用品", + "root[&]四川省[&]绵阳市[&]家具", + "root[&]四川省[&]绵阳市[&]办公用品", + ] + `); + + expect((facet.getCellMeta(0, 0)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + city: '杭州市', + price: 300, + }); + + expect((facet.getCellMeta(1, 0)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + city: '舟山市', + price: 800, + }); + + expect((facet.getCellMeta(2, 0)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + city: '成都市', + price: 1200, + }); + + expect((facet.getCellMeta(3, 0)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + city: '绵阳市', + price: 1600, + }); + }); + + test(`should get correct layout with row sub group dimension 'type'`, async () => { + const newS2Options = { + ...s2Options, + totals: { + ...s2Options.totals, + row: { + ...s2Options.totals!.row, + // 总计分组下,city 城市维度会出现分组 + grandTotalsGroupDimensions: ['city'], + subTotalsGroupDimensions: ['type'], + }, + }, + }; + + const newDataCfg = { + ...dataCfg, + fields: { + ...dataCfg.fields, + rows: ['province', 'city', 'type'], + columns: ['sub_type'], + values: ['price', 'cost'], + }, + }; + + s2 = new PivotSheet(container, newDataCfg, newS2Options as S2Options); + await s2.render(); + + const facet = s2.facet; + const { rowLeafNodes } = facet.getLayoutResult(); + + expect(rowLeafNodes[4].id).toEqual('root[&]浙江省[&]小计[&]家具'); + expect(rowLeafNodes[5].id).toEqual('root[&]浙江省[&]小计[&]办公用品'); + + expect((facet.getCellMeta(4, 0)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + province: '浙江省', + price: 600, + type: '家具', + }); + + expect((facet.getCellMeta(5, 0)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + province: '浙江省', + type: '办公用品', + price: 500, + }); + }); + + test(`should get correct layout with col total group dimension 'type'`, async () => { + const newS2Options = { + ...s2Options, + totals: { + ...s2Options.totals, + col: { + ...s2Options.totals!.col, + grandTotalsGroupDimensions: ['type'], + }, + }, + }; + + const newDataCfg = { + ...dataCfg, + fields: { + ...dataCfg.fields, + rows: ['province'], + columns: ['city', 'type', 'sub_type'], + values: ['price', 'cost'], + }, + }; + + s2 = new PivotSheet(container, newDataCfg, newS2Options as S2Options); + await s2.render(); + + const facet = s2.facet; + + expect((facet.getCellMeta(0, 0)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + type: '家具', + price: 2000, + }); + + expect((facet.getCellMeta(0, 2)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + type: '办公用品', + price: 1900, + }); + }); + + test(`should get correct layout with col total group dimension 'sub_type'`, async () => { + const newS2Options = { + ...s2Options, + totals: { + ...s2Options.totals, + col: { + ...s2Options.totals!.col, + grandTotalsGroupDimensions: ['sub_type'], + }, + }, + }; + + const newDataCfg = { + ...dataCfg, + fields: { + ...dataCfg.fields, + rows: ['province'], + columns: ['city', 'type', 'sub_type'], + values: ['price', 'cost'], + }, + }; + + s2 = new PivotSheet(container, newDataCfg, newS2Options as S2Options); + await s2.render(); + + const facet = s2.facet; + + expect((facet.getCellMeta(0, 0)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + sub_type: '桌子', + price: 1000, + }); + + expect((facet.getCellMeta(0, 2)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + sub_type: '沙发', + price: 1000, + }); + + expect((facet.getCellMeta(0, 4)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + sub_type: '笔', + price: 1000, + }); + + expect((facet.getCellMeta(0, 6)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + sub_type: '纸张', + price: 900, + }); + }); + + test(`should get correct layout with col sub total group dimension 'sub_type'`, async () => { + const newS2Options = { + ...s2Options, + totals: { + ...s2Options.totals, + col: { + ...s2Options.totals!.col, + grandTotalsGroupDimensions: [], + subTotalsDimensions: ['city'], + subTotalsGroupDimensions: ['sub_type'], + }, + }, + }; + + const newDataCfg = { + ...dataCfg, + fields: { + ...dataCfg.fields, + rows: ['province'], + columns: ['city', 'type', 'sub_type'], + values: ['price', 'cost'], + }, + }; + + s2 = new PivotSheet(container, newDataCfg, newS2Options as S2Options); + await s2.render(); + + const facet = s2.facet; + + expect((facet.getCellMeta(0, 2)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + city: '杭州市', + sub_type: '桌子', + price: 100, + }); + + expect((facet.getCellMeta(0, 4)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + city: '杭州市', + sub_type: '沙发', + price: 100, + }); + + expect((facet.getCellMeta(0, 6)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + city: '杭州市', + sub_type: '笔', + price: 100, + }); + }); + + test(`should get correct layout with giving total data`, async () => { + const newDataCfg = { + ...dataCfg, + data: dataCfg.data.concat([ + { + type: '家具', + price: 6666, + cost: 6666, + }, + { + type: '办公用品', + price: 9999, + cost: 9999, + }, + ]), + }; + + s2 = new PivotSheet(container, newDataCfg, s2Options as S2Options); + await s2.render(); + + const facet = s2.facet; + + expect((facet.getCellMeta(0, 0)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + type: '家具', + price: 6666, + cost: 6666, + }); + + expect((facet.getCellMeta(1, 0)!.data as CellData)[ORIGIN_FIELD]).toEqual({ + type: '办公用品', + price: 9999, + cost: 9999, + }); + }); +}); diff --git a/packages/s2-core/__tests__/unit/cell/col-cell-spec.ts b/packages/s2-core/__tests__/unit/cell/col-cell-spec.ts index 9331a781d0..66be515b2d 100644 --- a/packages/s2-core/__tests__/unit/cell/col-cell-spec.ts +++ b/packages/s2-core/__tests__/unit/cell/col-cell-spec.ts @@ -1,13 +1,13 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import { get, set } from 'lodash'; +import { set } from 'lodash'; import { createFakeSpreadSheet, createPivotSheet } from 'tests/util/helpers'; import type { ColHeaderConfig } from '../../../src/facet/header'; import { getContainer } from './../../util/helpers'; -import type { Node } from '@/facet/layout/node'; -import { PivotDataSet } from '@/data-set'; -import { SpreadSheet, PivotSheet } from '@/sheet-type'; -import { EXTRA_FIELD, type Formatter, type TextAlign } from '@/common'; import { ColCell } from '@/cell'; +import { EXTRA_FIELD, type Formatter, type TextAlign } from '@/common'; +import { PivotDataSet } from '@/data-set'; +import type { Node } from '@/facet/layout/node'; +import { PivotSheet, SpreadSheet } from '@/sheet-type'; const MockPivotSheet = PivotSheet as unknown as jest.Mock<PivotSheet>; const MockPivotDataSet = PivotDataSet as unknown as jest.Mock<PivotDataSet>; @@ -101,9 +101,7 @@ describe('Col Cell Tests', () => { const colCell = new ColCell(node, s2, headerConfig); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect(colCell.textShape.attr('text')).toEqual('test'); + expect(colCell.getTextShape().attr('text')).toEqual('test'); }); }); @@ -125,7 +123,7 @@ describe('Col Cell Tests', () => { test('should draw right condition text shape', async () => { await s2.render(); - const colCell = s2.facet.columnHeader.children[0].children[1] as ColCell; + const colCell = s2.facet.getColCells()[1]; expect(colCell.getTextShape().parsedStyle.fill).toBeColor('#5083F5'); }); @@ -150,10 +148,13 @@ describe('Col Cell Tests', () => { }); await s2.render(); - const colCell = s2.facet.columnHeader.children[0].children[0]; + const colCell = s2.facet.getColCells()[0]; - expect(get(colCell, 'conditionIconShape.cfg.name')).toEqual('CellUp'); - expect(get(colCell, 'conditionIconShape.cfg.fill')).toEqual('red'); + // @ts-ignore + expect(colCell.rightIconPosition).toEqual({ + x: 152, + y: 10.5, + }); }); test('should draw right condition background shape', async () => { @@ -171,10 +172,11 @@ describe('Col Cell Tests', () => { ], }, }); + await s2.render(); - const colCell = s2.facet.columnHeader.children[0].children[1]; + const colCell = s2.facet.getColCells()[1]; - expect(get(colCell, 'backgroundShape.parsedStyle.fill')).toBeColor( + expect(colCell.getBackgroundShape().parsedStyle.fill).toBeColor( '#F7B46F', ); }); @@ -198,7 +200,7 @@ describe('Col Cell Tests', () => { }); await s2.render(); - const colCell = s2.facet.columnHeader.children[0].children[0] as ColCell; + const colCell = s2.facet.getColCells()[0]; const { fill, fontSize, fontWeight } = colCell.getTextShape().attributes; expect(fill).toEqual('red'); diff --git a/packages/s2-core/__tests__/unit/cell/corner-cell-spec.ts b/packages/s2-core/__tests__/unit/cell/corner-cell-spec.ts index 76455c2c5e..1cd114bdcd 100644 --- a/packages/s2-core/__tests__/unit/cell/corner-cell-spec.ts +++ b/packages/s2-core/__tests__/unit/cell/corner-cell-spec.ts @@ -26,7 +26,6 @@ describe('Corner Cell Tests', () => { }); const drawTextShapeSpy = jest - // @ts-ignore .spyOn(cornerCell, 'drawTextShape') .mockImplementationOnce(() => true); diff --git a/packages/s2-core/__tests__/unit/cell/custom-tree-corner-cell-spec.ts b/packages/s2-core/__tests__/unit/cell/custom-tree-corner-cell-spec.ts index 5a4da7c8b1..68d3041791 100644 --- a/packages/s2-core/__tests__/unit/cell/custom-tree-corner-cell-spec.ts +++ b/packages/s2-core/__tests__/unit/cell/custom-tree-corner-cell-spec.ts @@ -4,10 +4,9 @@ import { customTreeNodes } from 'tests/data/custom-tree-nodes'; import { CustomTreeData } from 'tests/data/data-custom-tree'; import { getContainer } from 'tests/util/helpers'; -import { get } from 'lodash'; import type { S2DataConfig } from '@/common/interface'; import { PivotSheet, SpreadSheet } from '@/sheet-type'; -import { CornerCell, type S2Options } from '@/index'; +import { type S2Options } from '@/index'; describe('test for corner text', () => { const values: string[] = [ @@ -44,21 +43,19 @@ describe('test for corner text', () => { }); test('get correct default corner text when the corner label is empty', () => { - const cornerCells = mockSheet.facet.cornerHeader.children; + const cornerCells = mockSheet.facet.getCornerCells(); - expect(get(cornerCells[0], 'actualText')).toEqual('自定义节点A/指标E/数值'); - expect(get(cornerCells[1], 'actualText')).toEqual('type'); + expect(cornerCells[0].getActualText()).toEqual('自定义节点A/指标E/数值'); + expect(cornerCells[1].getActualText()).toEqual('type'); }); test('get correct default corner text when set the cornerText.', async () => { mockSheet.setOptions({ ...options, cornerText: 'test' }); await mockSheet.render(); - const cornerCells = mockSheet.facet.cornerHeader.children.filter( - (v) => v instanceof CornerCell, - ); + const cornerCells = mockSheet.facet.getCornerCells(); - expect(get(cornerCells[0], 'actualText')).toEqual('test'); - expect(get(cornerCells[1], 'actualText')).toEqual('type'); + expect(cornerCells[0].getActualText()).toEqual('test'); + expect(cornerCells[1].getActualText()).toEqual('type'); }); }); diff --git a/packages/s2-core/__tests__/unit/cell/data-cell-spec.ts b/packages/s2-core/__tests__/unit/cell/data-cell-spec.ts index 096ccf74bf..6ce23c12d3 100644 --- a/packages/s2-core/__tests__/unit/cell/data-cell-spec.ts +++ b/packages/s2-core/__tests__/unit/cell/data-cell-spec.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import type { Rect } from '@antv/g'; -import { find, get } from 'lodash'; +import { find, get, keys } from 'lodash'; import { createPivotSheet, createTableSheet } from 'tests/util/helpers'; import { DataCell } from '@/cell'; import type { TextAlign } from '@/common'; @@ -13,11 +13,11 @@ import { type S2CellType, type ViewMeta, } from '@/common'; -import { EXTRA_FIELD, VALUE_FIELD } from '@/common/constant/basic'; import { DEFAULT_FONT_COLOR, REVERSE_FONT_COLOR, } from '@/common/constant/condition'; +import { EXTRA_FIELD, VALUE_FIELD } from '@/common/constant/field'; import { PivotDataSet } from '@/data-set'; import type { PivotFacet } from '@/facet'; import { PivotSheet, SpreadSheet } from '@/sheet-type'; @@ -28,11 +28,11 @@ const MockPivotDataSet = PivotDataSet as unknown as jest.Mock<PivotDataSet>; const findDataCell = ( s2: SpreadSheet, valueField: 'price' | 'cost' | 'number', -) => - s2.facet.panelGroup.children[0].find<DataCell>( - (item) => - item instanceof DataCell && item.getMeta().valueField === valueField, - ); +) => { + return s2.facet + .getDataCells() + .find((item) => item.getMeta().valueField === valueField); +}; describe('Data Cell Tests', () => { const meta = { @@ -57,7 +57,7 @@ describe('Data Cell Tests', () => { }); test.each([ - ['left', 311], + ['left', 312], ['center', 375], ['right', 438], ] as const)( @@ -75,14 +75,13 @@ describe('Data Cell Tests', () => { }, }, }); + await s2.render(); - const panelBBoxInstance = s2.facet.panelGroup.children[0]; - const dataCell = panelBBoxInstance.children.find( - (item) => item instanceof DataCell, - ) as DataCell; - const { left: minX, right: maxX } = - dataCell['linkFieldShape'].getBBox(); + const dataCell = s2.facet.getDataCells()[0]; + const { left: minX, right: maxX } = dataCell + .getLinkFieldShape() + .getBBox(); // 宽度相当 const linkLength = maxX - minX; @@ -100,7 +99,7 @@ describe('Data Cell Tests', () => { }); describe('Data Cell Formatter & Method Tests', () => { - beforeEach(() => { + beforeEach(async () => { const container = document.createElement('div'); s2 = new MockPivotSheet(container); @@ -110,7 +109,11 @@ describe('Data Cell Tests', () => { s2.facet = { getRowLeafNodes: () => [], + getRowLeafNodeByIndex: jest.fn(), + getCells: () => [], } as unknown as PivotFacet; + + await s2.render(); }); test('should pass complete data into formatter', () => { @@ -207,7 +210,7 @@ describe('Data Cell Tests', () => { fill: 'red', }); - beforeEach(() => { + beforeEach(async () => { const container = document.createElement('div'); s2 = new MockPivotSheet(container); @@ -217,7 +220,11 @@ describe('Data Cell Tests', () => { s2.facet = { getRowLeafNodes: () => [], + getRowLeafNodeByIndex: jest.fn(), + getCells: () => [], } as unknown as PivotFacet; + + await s2.render(); }); test("shouldn't init when width or height is not positive", () => { @@ -543,11 +550,12 @@ describe('Data Cell Tests', () => { field: 'cost', mapping(value, dataInfo) { const originData = s2.dataSet.originData; - const resultData = find(originData, dataInfo); + const resultData = find(originData, (item) => + keys(item).every((key) => item[key] === dataInfo[key]), + ); - expect(resultData).toEqual(dataInfo); - // @ts-ignore - expect(value).toEqual(resultData.cost); + expect(value).toEqual(resultData?.['cost']); + expect(value).toEqual(dataInfo['cost']); return { fill: '#fffae6', diff --git a/packages/s2-core/__tests__/unit/cell/header-cell-spec.ts b/packages/s2-core/__tests__/unit/cell/header-cell-spec.ts index a86817ee4f..08e98afa71 100644 --- a/packages/s2-core/__tests__/unit/cell/header-cell-spec.ts +++ b/packages/s2-core/__tests__/unit/cell/header-cell-spec.ts @@ -49,6 +49,70 @@ describe('header cell formatter test', () => { expect(rowCell.getFieldValue()).toBe('杭州1'); }); + test('should not format pivot col and row total cell', () => { + const colNode = new Node({ + id: `root[&]总计`, + field: '', + value: '总计', + parent: root, + isTotalRoot: true, + }); + const rowNode = new Node({ + id: `root[&]杭州[&]小计`, + field: '', + value: '小计', + parent: root, + isTotalRoot: true, + }); + + const formatter: Formatter = (value) => { + return `${value}1`; + }; + + jest.spyOn(s2.dataSet, 'getFieldFormatter').mockReturnValue(formatter); + + const colCell = new ColCell(colNode, s2); + const rowCell = new RowCell(rowNode, s2); + + expect(colCell.getFieldValue()).toBe('总计'); + expect(rowCell.getFieldValue()).toBe('小计'); + }); + + test('should not format pivot row grand total cell in tree mode', () => { + const rowGrandTotalNode = new Node({ + id: `root[&]总计`, + field: '', + value: '总计', + parent: root, + isTotals: true, + isGrandTotals: true, + isTotalRoot: true, + }); + + const rowSubTotalNode = new Node({ + id: `root[&]杭州`, + field: '', + value: '杭州', + parent: root, + isTotals: true, + isSubTotals: true, + isGrandTotals: false, + }); + + const formatter: Formatter = (value) => { + return `${value}1`; + }; + + jest.spyOn(s2.dataSet, 'getFieldFormatter').mockReturnValue(formatter); + jest.spyOn(s2, 'isHierarchyTreeType').mockReturnValue(true); + + const grandTotalCell = new ColCell(rowGrandTotalNode, s2); + const subTotalCell = new RowCell(rowSubTotalNode, s2); + + expect(grandTotalCell.getFieldValue()).toBe('总计'); + expect(subTotalCell.getFieldValue()).toBe('杭州1'); + }); + test('pivot corner cell not formatter', () => { const formatter: Formatter = (value) => `${value}1`; diff --git a/packages/s2-core/__tests__/unit/cell/row-cell-spec.ts b/packages/s2-core/__tests__/unit/cell/row-cell-spec.ts index c53885a731..e60a3c0a09 100644 --- a/packages/s2-core/__tests__/unit/cell/row-cell-spec.ts +++ b/packages/s2-core/__tests__/unit/cell/row-cell-spec.ts @@ -1,8 +1,6 @@ -import { get } from 'lodash'; import { createPivotSheet } from 'tests/util/helpers'; -import type { RowCell } from '@antv/s2'; -import type { SpreadSheet } from '@/sheet-type'; import type { TextAlign } from '@/common'; +import type { SpreadSheet } from '@/sheet-type'; describe('Row Cell Tests', () => { describe('Link Shape Tests', () => { @@ -15,8 +13,8 @@ describe('Row Cell Tests', () => { test.each([ ['left', 21], - ['center', 75], - ['right', 129], + ['center', 75.25], + ['right', 129.5], ] as [TextAlign, number][])( 'should align link shape with text by %o', async (textAlign, textCenterX) => { @@ -34,7 +32,7 @@ describe('Row Cell Tests', () => { }); await s2.render(); - const provinceCell = s2.facet.rowHeader!.children[0] as RowCell; + const provinceCell = s2.facet.getRowCells()[0]; const { left: minX, right: maxX } = provinceCell .getLinkFieldShape() .getBBox(); @@ -72,9 +70,9 @@ describe('Row Cell Tests', () => { test('should draw right condition text shape', async () => { await s2.render(); - const rowCell = s2.facet.rowHeader!.children[1] as RowCell; + const rowCell = s2.facet.getRowCells()[1]; - expect(rowCell.getTextShape().parsedStyle.fill).toBeColor('#5083F5'); + expect(rowCell.getTextShape().style.fill).toEqual('#5083F5'); }); test('should draw right condition icon shape', async () => { @@ -93,11 +91,12 @@ describe('Row Cell Tests', () => { ], }, }); + await s2.render(); - const rowCell = s2.facet.rowHeader!.children[1]; + const rowCell = s2.facet.getRowCells()[1]; - expect(get(rowCell, 'conditionIconShape.cfg.name')).toEqual('CellUp'); - expect(get(rowCell, 'conditionIconShape.cfg.fill')).toEqual('red'); + // @ts-ignore + expect(rowCell.rightIconPosition).toEqual({ x: 186.5, y: 9.5 }); }); test('should draw right condition background shape', async () => { @@ -115,12 +114,11 @@ describe('Row Cell Tests', () => { ], }, }); + await s2.render(); - const rowCell = s2.facet.rowHeader!.children[0]; + const rowCell = s2.facet.getRowCells()[0]; - expect(get(rowCell, 'backgroundShape.parsedStyle.fill')).toBeColor( - '#F7B46F', - ); + expect(rowCell.getBackgroundShape().style.fill).toEqual('#F7B46F'); }); test('should render text by text theme', async () => { @@ -140,9 +138,10 @@ describe('Row Cell Tests', () => { ], }, }); + await s2.render(); - const rowCell = s2.facet.rowHeader!.children[1] as RowCell; + const rowCell = s2.facet.getRowCells()[1]; const { fill, fontSize, fontWeight } = rowCell.getTextShape().attributes; expect(fill).toEqual('red'); @@ -150,4 +149,44 @@ describe('Row Cell Tests', () => { expect(fontWeight).toEqual(800); }); }); + + describe('Cross Background Color Tests', () => { + const s2 = createPivotSheet({ + width: 800, + height: 600, + }); + + const crossColor = '#FFFFFF'; + const defaultColor = '#F5F8FF'; + const cellColorConfig = { + crossBackgroundColor: crossColor, + backgroundColor: defaultColor, + }; + + s2.setTheme({ + rowCell: { + cell: cellColorConfig, + }, + dataCell: { + cell: cellColorConfig, + }, + }); + + test('should draw right condition background shape', async () => { + await s2.render(); + + const rowCell0 = s2.facet.getRowCells()[0]; + const rowCell1 = s2.facet.getRowCells()[1]; + const rowCell2 = s2.facet.getRowCells()[2]; + + expect(rowCell0.getActualText()).toEqual('浙江'); + expect(rowCell0.getBackgroundShape().style.fill).toEqual(defaultColor); + + expect(rowCell1.getActualText()).toEqual('义乌'); + expect(rowCell1.getBackgroundShape().style.fill).toEqual(crossColor); + + expect(rowCell2.getActualText()).toEqual('杭州'); + expect(rowCell2.getBackgroundShape().style.fill).toEqual(defaultColor); + }); + }); }); diff --git a/packages/s2-core/__tests__/unit/common/i18n/index-spec.ts b/packages/s2-core/__tests__/unit/common/i18n/index-spec.ts index bad4ef936d..f4849f4766 100644 --- a/packages/s2-core/__tests__/unit/common/i18n/index-spec.ts +++ b/packages/s2-core/__tests__/unit/common/i18n/index-spec.ts @@ -19,7 +19,7 @@ describe('I18n Test', () => { setLang('en_US'); expect(i18n('小计')).toEqual('Total'); expect(i18n('总计')).toEqual('Total'); - expect(i18n('总和')).toEqual('SUM'); + expect(i18n('总和')).toEqual('(SUM)'); expect(i18n('项')).toEqual('items'); expect(i18n('已选择')).toEqual('selected'); expect(i18n('序号')).toEqual('Index'); @@ -27,13 +27,14 @@ describe('I18n Test', () => { expect(i18n('数值')).toEqual('Measure'); expect(i18n('共计')).toEqual('Total'); expect(i18n('条')).toEqual(''); + expect(i18n(',')).toEqual(', '); }); test('should show Chinese text when set lang to zh', () => { setLang('zh_CN'); expect(i18n('小计')).toEqual('小计'); expect(i18n('总计')).toEqual('总计'); - expect(i18n('总和')).toEqual('总和'); + expect(i18n('总和')).toEqual('(总和)'); expect(i18n('项')).toEqual('项'); expect(i18n('已选择')).toEqual('已选择'); expect(i18n('序号')).toEqual('序号'); @@ -41,5 +42,6 @@ describe('I18n Test', () => { expect(i18n('数值')).toEqual('数值'); expect(i18n('共计')).toEqual('共计'); expect(i18n('条')).toEqual('条'); + expect(i18n(',')).toEqual(','); }); }); diff --git a/packages/s2-core/__tests__/unit/data-process/pivot-spec.tsx b/packages/s2-core/__tests__/unit/data-process/pivot-spec.tsx index c07d3637b5..c7d5d0493c 100644 --- a/packages/s2-core/__tests__/unit/data-process/pivot-spec.tsx +++ b/packages/s2-core/__tests__/unit/data-process/pivot-spec.tsx @@ -7,6 +7,7 @@ import { assembleDataCfg, assembleOptions } from '../../util'; import { getContainer } from '../../util/helpers'; import { data } from '../../data/mock-dataset.json'; import type { ViewMeta } from '../../../src/common'; +import type { CellData } from '../../../src'; import { VALUE_FIELD } from '@/common/constant'; import type { PivotDataSet } from '@/data-set/pivot-data-set'; import { PivotSheet, SpreadSheet } from '@/sheet-type'; @@ -49,48 +50,59 @@ describe('Pivot Table Core Data Process', () => { }); test('should get correct indexes data', () => { + const prefix = 'province[&]city[&]type[&]sub_type'; + const ds = s2.dataSet as PivotDataSet; const indexesData = ds.indexesData; - expect(flattenDeep(indexesData).filter(Boolean)).toHaveLength( + expect(flattenDeep(indexesData[prefix]).filter(Boolean)).toHaveLength( data.length, ); - expect(get(indexesData, '1.1.1.1')).toEqual({ + // 左上角 + expect(get(indexesData, [prefix, 1, 1, 1, 1, 1])).toEqual({ province: '浙江省', city: '杭州市', type: '家具', sub_type: '桌子', number: 7789, - }); // 左上角 - expect(get(indexesData, '1.1.2.2')).toEqual({ + }); + + // 右上角 + expect(get(indexesData, [prefix, 1, 1, 2, 2, 1])).toEqual({ province: '浙江省', city: '杭州市', type: '办公用品', sub_type: '纸张', number: 1343, - }); // 右上角 - expect(get(indexesData, '2.4.1.1')).toEqual({ + }); + + // 左下角 + expect(get(indexesData, [prefix, 2, 4, 1, 1, 1])).toEqual({ province: '四川省', city: '乐山市', type: '家具', sub_type: '桌子', number: 2330, - }); // 左下角 - expect(get(indexesData, '2.4.2.2')).toEqual({ + }); + + // 右下角 + expect(get(indexesData, [prefix, 2, 4, 2, 2, 1])).toEqual({ province: '四川省', city: '乐山市', type: '办公用品', sub_type: '纸张', number: 352, - }); // 右下角 - expect(get(indexesData, '1.4.2.1')).toEqual({ + }); + + // 中间 + expect(get(indexesData, [prefix, 1, 4, 2, 1, 1])).toEqual({ province: '浙江省', city: '舟山市', type: '办公用品', sub_type: '笔', number: 1432, - }); // 中间 + }); }); }); @@ -140,6 +152,7 @@ describe('Pivot Table Core Data Process', () => { '南充市', '乐山市', ]); + // 父子关系正确 const leavesNodes = rowsHierarchy.getLeaves(); const firstLeafNode = leavesNodes[0]; @@ -166,26 +179,26 @@ describe('Pivot Table Core Data Process', () => { // 节点正确 expect(colsHierarchy.getIndexNodes()).toHaveLength(4); - expect(colsHierarchy.getNodes()).toHaveLength(10); // 价格在列头 家具[&]桌子[&]number + expect(colsHierarchy.getNodes()).toHaveLength(10); // 价格在列头 家具[&]桌子[&]数量 // 叶子节点正确 expect(colsHierarchy.getLeaves().map((node) => node.value)).toEqual([ - 'number', - 'number', - 'number', - 'number', + '数量', + '数量', + '数量', + '数量', ]); // 层级正确 expect(colsHierarchy.getNodes().map((node) => node.value)).toEqual([ '家具', '桌子', - 'number', + '数量', '沙发', - 'number', + '数量', '办公用品', '笔', - 'number', + '数量', '纸张', - 'number', + '数量', ]); expect(colsHierarchy.getNodes(0).map((node) => node.value)).toEqual([ '家具', @@ -198,16 +211,17 @@ describe('Pivot Table Core Data Process', () => { '纸张', ]); expect(colsHierarchy.getNodes(2).map((node) => node.value)).toEqual([ - 'number', - 'number', - 'number', - 'number', + '数量', + '数量', + '数量', + '数量', ]); + // 父子关系正确 const leavesNodes = colsHierarchy.getLeaves(); const firstLeafNode = leavesNodes[0]; - expect(firstLeafNode.value).toEqual('number'); + expect(firstLeafNode.value).toEqual('数量'); expect(firstLeafNode.parent!.value).toEqual('桌子'); expect(firstLeafNode.parent!.parent?.value).toEqual('家具'); expect( @@ -215,7 +229,7 @@ describe('Pivot Table Core Data Process', () => { ).toEqual(['桌子', '沙发']); const lastLeafNode = leavesNodes[leavesNodes.length - 1]; - expect(lastLeafNode.value).toEqual('number'); + expect(lastLeafNode.value).toEqual('数量'); expect(lastLeafNode.parent!.value).toEqual('纸张'); expect(lastLeafNode.parent!.parent?.value).toEqual('办公用品'); expect( @@ -228,25 +242,25 @@ describe('Pivot Table Core Data Process', () => { test('should calc correct row & cell width', () => { const { rowLeafNodes, colLeafNodes } = s2.facet.getLayoutResult(); - expect(rowLeafNodes[0].width).toEqual(99); - expect(colLeafNodes[0].width).toEqual(100); + expect(rowLeafNodes[0].width).toEqual(99.66); + expect(colLeafNodes[0].width).toEqual(99.67); }); test('should calc correct row node size and coordinate', () => { const { dataCell } = s2.options.style!; const { rowsHierarchy, rowLeafNodes } = s2.facet.getLayoutResult(); // all sample width. - expect(rowsHierarchy.sampleNodesForAllLevels[0]?.width).toEqual(99); - expect(rowsHierarchy.sampleNodesForAllLevels[1]?.width).toEqual(99); + expect(rowsHierarchy.sampleNodesForAllLevels[0]?.width).toEqual(99.66); + expect(rowsHierarchy.sampleNodesForAllLevels[1]?.width).toEqual(99.66); // all width expect(uniq(rowsHierarchy.getNodes().map((node) => node.width))).toEqual([ - 99, + 99.66, ]); // leaf node rowLeafNodes.forEach((node, index) => { expect(node.height).toEqual(dataCell?.height!); expect(node.y).toEqual(node.height * index); - expect(node.x).toEqual(99); + expect(node.x).toEqual(99.66); }); // level = 0 const provinceNodes = rowsHierarchy.getNodes(0); @@ -284,7 +298,7 @@ describe('Pivot Table Core Data Process', () => { colLeafNodes.forEach((node, index) => { const width = Math.floor(node.width); - expect(width).toEqual(100); + expect(width).toEqual(99); expect(node.x).toEqual(node.width * index); expect(node.y).toEqual(node.level * (colCell!.height as number)); }); @@ -316,7 +330,8 @@ describe('Pivot Table Core Data Process', () => { describe('4、Calculate data cell info', () => { test('should get correct data value', () => { const { getCellMeta } = s2.facet; - const getData = (meta: ViewMeta | null) => meta?.data?.[VALUE_FIELD]; + const getData = (meta: ViewMeta | null) => + (meta?.data as CellData)?.[VALUE_FIELD]; // 左上角 expect(getData(getCellMeta(0, 0))).toBe(7789); diff --git a/packages/s2-core/__tests__/unit/data-set/__snapshots__/pivot-data-set-total-spec.ts.snap b/packages/s2-core/__tests__/unit/data-set/__snapshots__/pivot-data-set-total-spec.ts.snap new file mode 100644 index 0000000000..fe05152534 --- /dev/null +++ b/packages/s2-core/__tests__/unit/data-set/__snapshots__/pivot-data-set-total-spec.ts.snap @@ -0,0 +1,133 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Pivot Dataset Total Test test for total with dimension group get correct MultiData when query need to be processed 1`] = ` +Array [ + CellData { + "extraField": "number", + "raw": Object { + "city": "杭州市", + "number": 7789, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + }, + CellData { + "extraField": "number", + "raw": Object { + "city": "绍兴市", + "number": 2367, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + }, + CellData { + "extraField": "number", + "raw": Object { + "city": "宁波市", + "number": 3877, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + }, + CellData { + "extraField": "number", + "raw": Object { + "city": "舟山市", + "number": 4342, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + }, +] +`; + +exports[`Pivot Dataset Total Test test for total with dimension group get correct MultiData when query need to be processed 2`] = `Array []`; + +exports[`Pivot Dataset Total Test test for total with dimension group get correct MultiData when query need to be processed 3`] = ` +Array [ + CellData { + "extraField": "number", + "raw": Object { + "city": "杭州市", + "number": 7789, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + }, + CellData { + "extraField": "number", + "raw": Object { + "city": "绍兴市", + "number": 2367, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + }, + CellData { + "extraField": "number", + "raw": Object { + "city": "宁波市", + "number": 3877, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + }, + CellData { + "extraField": "number", + "raw": Object { + "city": "舟山市", + "number": 4342, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + }, + CellData { + "extraField": "number", + "raw": Object { + "city": "成都市", + "number": 1723, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + }, + CellData { + "extraField": "number", + "raw": Object { + "city": "绵阳市", + "number": 1822, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + }, + CellData { + "extraField": "number", + "raw": Object { + "city": "南充市", + "number": 1943, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + }, + CellData { + "extraField": "number", + "raw": Object { + "city": "乐山市", + "number": 2330, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + }, +] +`; diff --git a/packages/s2-core/__tests__/unit/data-set/__snapshots__/table-data-set-spec.ts.snap b/packages/s2-core/__tests__/unit/data-set/__snapshots__/table-data-set-spec.ts.snap new file mode 100644 index 0000000000..13feac731e --- /dev/null +++ b/packages/s2-core/__tests__/unit/data-set/__snapshots__/table-data-set-spec.ts.snap @@ -0,0 +1,1191 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Table Mode Dataset Test test for query data #getMultiData by empty query 1`] = ` +Array [ + Object { + "city": "杭州市", + "number": 7789, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "绍兴市", + "number": 2367, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "宁波市", + "number": 3877, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "舟山市", + "number": 4342, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "杭州市", + "number": 5343, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "绍兴市", + "number": 632, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "宁波市", + "number": 7234, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "舟山市", + "number": 834, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "杭州市", + "number": 945, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "绍兴市", + "number": 1304, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "宁波市", + "number": 1145, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "舟山市", + "number": 1432, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "杭州市", + "number": 1343, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "绍兴市", + "number": 1354, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "宁波市", + "number": 1523, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "舟山市", + "number": 1634, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "成都市", + "number": 1723, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "绵阳市", + "number": 1822, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "南充市", + "number": 1943, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "乐山市", + "number": 2330, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "成都市", + "number": 2451, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "绵阳市", + "number": 2244, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "南充市", + "number": 2333, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "乐山市", + "number": 2445, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "成都市", + "number": 2335, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "绵阳市", + "number": 245, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "南充市", + "number": 2457, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "乐山市", + "number": 2458, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "成都市", + "number": 4004, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "绵阳市", + "number": 3077, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "南充市", + "number": 3551, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "乐山市", + "number": 352, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "number": 26193, + "sub_type": "桌子", + "type": "家具", + }, + Object { + "number": 49709, + "type": "家具", + }, + Object { + "number": 23516, + "sub_type": "沙发", + "type": "家具", + }, + Object { + "number": 29159, + "type": "办公用品", + }, + Object { + "number": 12321, + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "number": 16838, + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "number": 18375, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "number": 14043, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "number": 4826, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "number": 5854, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "number": 7818, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "number": 9473, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "number": 7495, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "number": 10984, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "杭州市", + "number": 13132, + "province": "浙江省", + "type": "家具", + }, + Object { + "city": "杭州市", + "number": 2288, + "province": "浙江省", + "type": "办公用品", + }, + Object { + "city": "杭州市", + "number": 15420, + "province": "浙江省", + }, + Object { + "city": "绍兴市", + "number": 2999, + "province": "浙江省", + "type": "家具", + }, + Object { + "city": "绍兴市", + "number": 2658, + "province": "浙江省", + "type": "办公用品", + }, + Object { + "city": "绍兴市", + "number": 5657, + "province": "浙江省", + }, + Object { + "city": "宁波市", + "number": 11111, + "province": "浙江省", + "type": "家具", + }, + Object { + "city": "宁波市", + "number": 2668, + "province": "浙江省", + "type": "办公用品", + }, + Object { + "city": "宁波市", + "number": 13779, + "province": "浙江省", + }, + Object { + "city": "舟山市", + "number": 5176, + "province": "浙江省", + "type": "家具", + }, + Object { + "city": "舟山市", + "number": 3066, + "province": "浙江省", + "type": "办公用品", + }, + Object { + "city": "舟山市", + "number": 8242, + "province": "浙江省", + }, + Object { + "city": "成都市", + "number": 4174, + "province": "四川省", + "type": "家具", + }, + Object { + "city": "成都市", + "number": 6339, + "province": "四川省", + "type": "办公用品", + }, + Object { + "city": "成都市", + "number": 10513, + "province": "四川省", + }, + Object { + "city": "绵阳市", + "number": 4066, + "province": "四川省", + "type": "家具", + }, + Object { + "city": "绵阳市", + "number": 3322, + "province": "四川省", + "type": "办公用品", + }, + Object { + "city": "绵阳市", + "number": 7388, + "province": "四川省", + }, + Object { + "city": "南充市", + "number": 4276, + "province": "四川省", + "type": "家具", + }, + Object { + "city": "南充市", + "number": 6008, + "province": "四川省", + "type": "办公用品", + }, + Object { + "city": "南充市", + "number": 10284, + "province": "四川省", + }, + Object { + "city": "乐山市", + "number": 4775, + "province": "四川省", + "type": "家具", + }, + Object { + "city": "乐山市", + "number": 2810, + "province": "四川省", + "type": "办公用品", + }, + Object { + "city": "乐山市", + "number": 7585, + "province": "四川省", + }, + Object { + "number": 32418, + "province": "浙江省", + "type": "家具", + }, + Object { + "number": 10680, + "province": "浙江省", + "type": "办公用品", + }, + Object { + "number": 43098, + "province": "浙江省", + }, + Object { + "number": 17291, + "province": "四川省", + "type": "家具", + }, + Object { + "number": 18479, + "province": "四川省", + "type": "办公用品", + }, + Object { + "number": 35770, + "province": "四川省", + }, + Object { + "number": 78868, + }, +] +`; + +exports[`Table Mode Dataset Test test for query data #getMultiData by field query 1`] = ` +Array [ + "杭州市", + "绍兴市", + "宁波市", + "舟山市", + "杭州市", + "绍兴市", + "宁波市", + "舟山市", + "杭州市", + "绍兴市", + "宁波市", + "舟山市", + "杭州市", + "绍兴市", + "宁波市", + "舟山市", + "成都市", + "绵阳市", + "南充市", + "乐山市", + "成都市", + "绵阳市", + "南充市", + "乐山市", + "成都市", + "绵阳市", + "南充市", + "乐山市", + "成都市", + "绵阳市", + "南充市", + "乐山市", + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + "杭州市", + "杭州市", + "杭州市", + "绍兴市", + "绍兴市", + "绍兴市", + "宁波市", + "宁波市", + "宁波市", + "舟山市", + "舟山市", + "舟山市", + "成都市", + "成都市", + "成都市", + "绵阳市", + "绵阳市", + "绵阳市", + "南充市", + "南充市", + "南充市", + "乐山市", + "乐山市", + "乐山市", + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, +] +`; + +exports[`Table Mode Dataset Test test for query data #getMultiData by field query 2`] = ` +Array [ + 7789, + 2367, + 3877, + 4342, + 5343, + 632, + 7234, + 834, + 945, + 1304, + 1145, + 1432, + 1343, + 1354, + 1523, + 1634, + 1723, + 1822, + 1943, + 2330, + 2451, + 2244, + 2333, + 2445, + 2335, + 245, + 2457, + 2458, + 4004, + 3077, + 3551, + 352, + 26193, + 49709, + 23516, + 29159, + 12321, + 16838, + 18375, + 14043, + 4826, + 5854, + 7818, + 9473, + 7495, + 10984, + 13132, + 2288, + 15420, + 2999, + 2658, + 5657, + 11111, + 2668, + 13779, + 5176, + 3066, + 8242, + 4174, + 6339, + 10513, + 4066, + 3322, + 7388, + 4276, + 6008, + 10284, + 4775, + 2810, + 7585, + 32418, + 10680, + 43098, + 17291, + 18479, + 35770, + 78868, +] +`; + +exports[`Table Mode Dataset Test test for query data #getMultiData by field query 3`] = ` +Array [ + "桌子", + "桌子", + "桌子", + "桌子", + "沙发", + "沙发", + "沙发", + "沙发", + "笔", + "笔", + "笔", + "笔", + "纸张", + "纸张", + "纸张", + "纸张", + "桌子", + "桌子", + "桌子", + "桌子", + "沙发", + "沙发", + "沙发", + "沙发", + "笔", + "笔", + "笔", + "笔", + "纸张", + "纸张", + "纸张", + "纸张", + "桌子", + undefined, + "沙发", + undefined, + "笔", + "纸张", + "桌子", + "沙发", + "笔", + "纸张", + "桌子", + "沙发", + "笔", + "纸张", + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, +] +`; + +exports[`Table Mode Dataset Test test for query data #getMultiData by rowIndex query 2`] = ` +Array [ + Object { + "city": "杭州市", + "number": 7789, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "绍兴市", + "number": 2367, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "宁波市", + "number": 3877, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "舟山市", + "number": 4342, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "杭州市", + "number": 5343, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "绍兴市", + "number": 632, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "宁波市", + "number": 7234, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "舟山市", + "number": 834, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "杭州市", + "number": 945, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "绍兴市", + "number": 1304, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "宁波市", + "number": 1145, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "舟山市", + "number": 1432, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "杭州市", + "number": 1343, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "绍兴市", + "number": 1354, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "宁波市", + "number": 1523, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "舟山市", + "number": 1634, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "成都市", + "number": 1723, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "绵阳市", + "number": 1822, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "南充市", + "number": 1943, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "乐山市", + "number": 2330, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "成都市", + "number": 2451, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "绵阳市", + "number": 2244, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "南充市", + "number": 2333, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "乐山市", + "number": 2445, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "成都市", + "number": 2335, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "绵阳市", + "number": 245, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "南充市", + "number": 2457, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "乐山市", + "number": 2458, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "成都市", + "number": 4004, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "绵阳市", + "number": 3077, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "南充市", + "number": 3551, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "乐山市", + "number": 352, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "number": 26193, + "sub_type": "桌子", + "type": "家具", + }, + Object { + "number": 49709, + "type": "家具", + }, + Object { + "number": 23516, + "sub_type": "沙发", + "type": "家具", + }, + Object { + "number": 29159, + "type": "办公用品", + }, + Object { + "number": 12321, + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "number": 16838, + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "number": 18375, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "number": 14043, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "number": 4826, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "number": 5854, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "number": 7818, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "number": 9473, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "number": 7495, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "number": 10984, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "杭州市", + "number": 13132, + "province": "浙江省", + "type": "家具", + }, + Object { + "city": "杭州市", + "number": 2288, + "province": "浙江省", + "type": "办公用品", + }, + Object { + "city": "杭州市", + "number": 15420, + "province": "浙江省", + }, + Object { + "city": "绍兴市", + "number": 2999, + "province": "浙江省", + "type": "家具", + }, + Object { + "city": "绍兴市", + "number": 2658, + "province": "浙江省", + "type": "办公用品", + }, + Object { + "city": "绍兴市", + "number": 5657, + "province": "浙江省", + }, + Object { + "city": "宁波市", + "number": 11111, + "province": "浙江省", + "type": "家具", + }, + Object { + "city": "宁波市", + "number": 2668, + "province": "浙江省", + "type": "办公用品", + }, + Object { + "city": "宁波市", + "number": 13779, + "province": "浙江省", + }, + Object { + "city": "舟山市", + "number": 5176, + "province": "浙江省", + "type": "家具", + }, + Object { + "city": "舟山市", + "number": 3066, + "province": "浙江省", + "type": "办公用品", + }, + Object { + "city": "舟山市", + "number": 8242, + "province": "浙江省", + }, + Object { + "city": "成都市", + "number": 4174, + "province": "四川省", + "type": "家具", + }, + Object { + "city": "成都市", + "number": 6339, + "province": "四川省", + "type": "办公用品", + }, + Object { + "city": "成都市", + "number": 10513, + "province": "四川省", + }, + Object { + "city": "绵阳市", + "number": 4066, + "province": "四川省", + "type": "家具", + }, + Object { + "city": "绵阳市", + "number": 3322, + "province": "四川省", + "type": "办公用品", + }, + Object { + "city": "绵阳市", + "number": 7388, + "province": "四川省", + }, + Object { + "city": "南充市", + "number": 4276, + "province": "四川省", + "type": "家具", + }, + Object { + "city": "南充市", + "number": 6008, + "province": "四川省", + "type": "办公用品", + }, + Object { + "city": "南充市", + "number": 10284, + "province": "四川省", + }, + Object { + "city": "乐山市", + "number": 4775, + "province": "四川省", + "type": "家具", + }, + Object { + "city": "乐山市", + "number": 2810, + "province": "四川省", + "type": "办公用品", + }, + Object { + "city": "乐山市", + "number": 7585, + "province": "四川省", + }, + Object { + "number": 32418, + "province": "浙江省", + "type": "家具", + }, + Object { + "number": 10680, + "province": "浙江省", + "type": "办公用品", + }, + Object { + "number": 43098, + "province": "浙江省", + }, + Object { + "number": 17291, + "province": "四川省", + "type": "家具", + }, + Object { + "number": 18479, + "province": "四川省", + "type": "办公用品", + }, + Object { + "number": 35770, + "province": "四川省", + }, + Object { + "number": 78868, + }, +] +`; diff --git a/packages/s2-core/__tests__/unit/data-set/custom-tree-data-set-spec.ts b/packages/s2-core/__tests__/unit/data-set/custom-tree-data-set-spec.ts index d4fc7554d9..34b2cc9a8c 100644 --- a/packages/s2-core/__tests__/unit/data-set/custom-tree-data-set-spec.ts +++ b/packages/s2-core/__tests__/unit/data-set/custom-tree-data-set-spec.ts @@ -5,7 +5,7 @@ import { get } from 'lodash'; import { customTreeNodes } from 'tests/data/custom-tree-nodes'; import { CustomTreeData } from 'tests/data/data-custom-tree'; import type { S2DataConfig } from '@/common/interface'; -import { EXTRA_FIELD } from '@/common/constant'; +import { EXTRA_FIELD, ORIGIN_FIELD } from '@/common/constant'; import { PivotSheet } from '@/sheet-type'; import { CustomTreePivotDataSet } from '@/data-set/custom-tree-pivot-data-set'; @@ -50,7 +50,7 @@ describe('Custom Tree Dataset Test', () => { test('should get empty row pivot meta', () => { const rowPivotMeta = dataSet.rowPivotMeta; - expect([...rowPivotMeta.keys()]).toEqual([]); + expect([...rowPivotMeta.keys()]).toEqual(values); }); test('should get correct col pivot meta', () => { @@ -66,9 +66,10 @@ describe('Custom Tree Dataset Test', () => { }); test('should get correct indexesData', () => { + const prefix = 'type[&]sub_type'; const indexesData = dataSet.indexesData; - expect(get(indexesData, '1.1')).toEqual({ + expect(get(indexesData, [prefix, 1, 1, 1])).toEqual({ type: '家具', sub_type: '桌子', 'measure-a': 1, @@ -78,7 +79,7 @@ describe('Custom Tree Dataset Test', () => { 'measure-e': 5, 'measure-f': 6, }); - expect(get(indexesData, '1.2')).toEqual({ + expect(get(indexesData, [prefix, 1, 1, 2])).toEqual({ type: '家具', sub_type: '椅子', 'measure-a': 11, @@ -94,27 +95,23 @@ describe('Custom Tree Dataset Test', () => { describe('test for query data', () => { test('getCellData function', () => { expect( - dataSet - .getCellData({ - query: { - type: '家具', - sub_type: '桌子', - [EXTRA_FIELD]: 'measure-a', - }, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + type: '家具', + sub_type: '桌子', + [EXTRA_FIELD]: 'measure-a', + }, + })?.[ORIGIN_FIELD], ).toContainEntries([['measure-a', 1]]); expect( - dataSet - .getCellData({ - query: { - type: '家具', - sub_type: '椅子', - [EXTRA_FIELD]: 'measure-e', - }, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + type: '家具', + sub_type: '椅子', + [EXTRA_FIELD]: 'measure-e', + }, + })?.[ORIGIN_FIELD], ).toContainEntries([['measure-e', 55]]); }); }); diff --git a/packages/s2-core/__tests__/unit/data-set/pivot-data-set-row-value-spec.ts b/packages/s2-core/__tests__/unit/data-set/pivot-data-set-row-value-spec.ts index 0c08e25cd5..40ba2a4ccf 100644 --- a/packages/s2-core/__tests__/unit/data-set/pivot-data-set-row-value-spec.ts +++ b/packages/s2-core/__tests__/unit/data-set/pivot-data-set-row-value-spec.ts @@ -4,7 +4,7 @@ import { get, keys } from 'lodash'; import { data } from 'tests/data/mock-dataset.json'; import { assembleDataCfg } from '../../util'; -import { EXTRA_FIELD } from '@/common/constant'; +import { EXTRA_FIELD, VALUE_FIELD } from '@/common/constant'; import type { S2DataConfig } from '@/common/interface'; import { PivotSheet } from '@/sheet-type'; import { PivotDataSet } from '@/data-set/pivot-data-set'; @@ -95,23 +95,24 @@ describe('Pivot Mode Test When Value In Row', () => { }); test('should get correct indexesData', () => { + const prefix = 'province[&]city[&]type[&]sub_type'; const indexesData = dataSet.indexesData; - expect(get(indexesData, '1.1.1.1')).toEqual({ + expect(get(indexesData, [prefix, 1, 1, 1, 1, 1])).toEqual({ province: '浙江省', city: '杭州市', type: '家具', sub_type: '桌子', number: 7789, }); - expect(get(indexesData, '1.2.2.1')).toEqual({ + expect(get(indexesData, [prefix, 1, 2, 1, 1, 2])).toEqual({ province: '浙江省', city: '绍兴市', - type: '办公用品', - sub_type: '笔', - number: 1304, + type: '家具', + sub_type: '沙发', + number: 632, }); - expect(get(indexesData, '2.1.1.2')).toEqual({ + expect(get(indexesData, [prefix, 2, 1, 1, 1, 2])).toEqual({ province: '四川省', city: '成都市', type: '家具', @@ -126,6 +127,7 @@ describe('Pivot Mode Test When Value In Row', () => { expect([...keys(sortedDimensionValues)]).toEqual([ 'province', 'city', + EXTRA_FIELD, 'type', 'sub_type', ]); @@ -133,8 +135,98 @@ describe('Pivot Mode Test When Value In Row', () => { getDimensionsWithoutPathPre(sortedDimensionValues['province']), ).toEqual(['浙江省', '四川省']); expect( - getDimensionsWithoutPathPre(sortedDimensionValues['city']), - ).toEqual([ + getDimensionsWithoutPathPre(sortedDimensionValues['sub_type']), + ).toEqual(['桌子', '沙发', '笔', '纸张']); + }); + }); + + describe('test for query data', () => { + test('getCellData function', () => { + expect( + dataSet.getCellData({ + query: { + province: '浙江省', + city: '杭州市', + type: '家具', + sub_type: '桌子', + [EXTRA_FIELD]: 'number', + }, + })?.[VALUE_FIELD], + ).toEqual(7789); + + expect( + dataSet.getCellData({ + query: { + province: '四川省', + city: '乐山市', + type: '办公用品', + sub_type: '纸张', + [EXTRA_FIELD]: 'number', + }, + })?.[VALUE_FIELD], + ).toEqual(352); + }); + + test('getMultiData function', () => { + const specialQuery = { + province: '浙江省', + city: '杭州市', + type: '家具', + sub_type: '桌子', + [EXTRA_FIELD]: 'number', + }; + + expect(dataSet.getCellMultiData({ query: specialQuery })).toHaveLength(1); + expect( + dataSet.getCellMultiData({ query: specialQuery })[0]?.[VALUE_FIELD], + ).toEqual(7789); + + expect( + dataSet.getCellMultiData({ + query: { + province: '浙江省', + type: '家具', + sub_type: '桌子', + [EXTRA_FIELD]: 'number', + }, + }), + ).toHaveLength(4); + + expect( + dataSet.getCellMultiData({ + query: { + type: '家具', + sub_type: '桌子', + [EXTRA_FIELD]: 'number', + }, + }), + ).toHaveLength(8); + + expect( + dataSet.getCellMultiData({ + query: { + type: '家具', + [EXTRA_FIELD]: 'number', + }, + }), + ).toHaveLength(16); + + expect( + dataSet.getCellMultiData({ + query: { + [EXTRA_FIELD]: 'number', + }, + }), + ).toHaveLength(32); + }); + + test('getDimensionValues function', () => { + // without query + expect(dataSet.getDimensionValues('province')).toEqual([ + '浙江省', + '四川省', + ]); + expect(dataSet.getDimensionValues('city')).toEqual([ '杭州市', '绍兴市', '宁波市', @@ -144,6 +236,19 @@ describe('Pivot Mode Test When Value In Row', () => { '南充市', '乐山市', ]); + expect(dataSet.getDimensionValues('type')).toEqual(['家具', '办公用品']); + expect(dataSet.getDimensionValues('sub_type')).toEqual([ + '桌子', + '沙发', + '笔', + '纸张', + ]); + + expect(dataSet.getDimensionValues('empty')).toEqual([]); + + const sortedDimensionValues = dataSet.sortedDimensionValues; + + // with query expect( getDimensionsWithoutPathPre(sortedDimensionValues['type']), ).toEqual(['家具', '办公用品']); diff --git a/packages/s2-core/__tests__/unit/data-set/pivot-data-set-spec.ts b/packages/s2-core/__tests__/unit/data-set/pivot-data-set-spec.ts index 929f98ca26..0e24c8451e 100644 --- a/packages/s2-core/__tests__/unit/data-set/pivot-data-set-spec.ts +++ b/packages/s2-core/__tests__/unit/data-set/pivot-data-set-spec.ts @@ -14,7 +14,7 @@ import type { SortMethod, CustomHeaderField, } from '@/common/interface'; -import { EXTRA_FIELD, TOTAL_VALUE } from '@/common/constant'; +import { EXTRA_FIELD, ORIGIN_FIELD, TOTAL_VALUE } from '@/common/constant'; import type { S2DataConfig } from '@/common/interface'; import { PivotSheet } from '@/sheet-type'; import { PivotDataSet } from '@/data-set/pivot-data-set'; @@ -105,22 +105,23 @@ describe('Pivot Dataset Test', () => { test('should get correct indexesData', () => { const indexesData = dataSet.indexesData; + const prefix = 'province[&]city[&]type[&]sub_type'; - expect(get(indexesData, '1.1.1.1')).toEqual({ + expect(get(indexesData, [prefix, 1, 1, 1, 1, 1])).toEqual({ province: '浙江省', city: '杭州市', type: '家具', sub_type: '桌子', number: 7789, }); - expect(get(indexesData, '1.2.2.1')).toEqual({ + expect(get(indexesData, [prefix, 1, 2, 2, 1, 1])).toEqual({ province: '浙江省', city: '绍兴市', type: '办公用品', sub_type: '笔', number: 1304, }); - expect(get(indexesData, '2.1.1.2')).toEqual({ + expect(get(indexesData, [prefix, 2, 1, 1, 2, 1])).toEqual({ province: '四川省', city: '成都市', type: '家具', @@ -137,6 +138,7 @@ describe('Pivot Dataset Test', () => { 'city', 'type', 'sub_type', + EXTRA_FIELD, ]); expect( getDimensionsWithoutPathPre(sortedDimensionValues['province']), @@ -160,6 +162,10 @@ describe('Pivot Dataset Test', () => { getDimensionsWithoutPathPre(sortedDimensionValues['sub_type']), ).toEqual(['桌子', '沙发', '笔', '纸张']); }); + + test('should get correctly empty dataset result', () => { + expect(dataSet.isEmpty()).toBeFalsy(); + }); }); describe('test for query data', () => { @@ -175,7 +181,7 @@ describe('Pivot Dataset Test', () => { isTotals: true, }); - expect(cell1!.getOrigin()).toContainEntries([['number', 7789]]); + expect(cell1?.[ORIGIN_FIELD]).toContainEntries([['number', 7789]]); const cell2 = dataSet.getCellData({ query: { @@ -188,7 +194,7 @@ describe('Pivot Dataset Test', () => { isTotals: true, }); - expect(cell2!.getOrigin()).toContainEntries([['number', 352]]); + expect(cell2?.[ORIGIN_FIELD]).toContainEntries([['number', 352]]); }); describe('getCellMultiData function', () => { @@ -208,89 +214,9 @@ describe('Pivot Dataset Test', () => { 1, ); expect( - dataSet.getCellMultiData({ query: specialQuery })[0].getOrigin(), + dataSet.getCellMultiData({ query: specialQuery })[0]?.[ORIGIN_FIELD], ).toContainEntries([['number', 7789]]); }); - - test('should get all detail data when child dimension is not specified', () => { - expect( - dataSet.getCellMultiData({ - query: { - province: '浙江省', - type: '家具', - sub_type: '桌子', - [EXTRA_FIELD]: 'number', - }, - totals: { - row: { totalDimensions: false }, - column: { totalDimensions: false }, - }, - }), - ).toHaveLength(4); - - expect( - dataSet.getCellMultiData({ - query: { - type: '家具', - sub_type: '桌子', - [EXTRA_FIELD]: 'number', - }, - totals: { - row: { totalDimensions: false }, - column: { totalDimensions: false }, - }, - }), - ).toHaveLength(8); - - expect( - dataSet.getCellMultiData({ - query: { - type: '家具', - [EXTRA_FIELD]: 'number', - }, - totals: { - row: { totalDimensions: false }, - column: { totalDimensions: false }, - }, - }), - ).toHaveLength(16); - - expect( - dataSet.getCellMultiData({ - query: { - [EXTRA_FIELD]: 'number', - }, - totals: { - row: { totalDimensions: false }, - column: { totalDimensions: false }, - }, - }), - ).toHaveLength(32); - }); - - test('should only query grand total data', () => { - expect( - dataSet.getCellMultiData({ - query: { [EXTRA_FIELD]: 'number' }, - totals: { - row: { grandTotalOnly: true }, - column: { grandTotalOnly: true }, - }, - }), - ).toHaveLength(1); - }); - - test('should query all grand total and sub total data in columns for all cities', () => { - expect( - dataSet.getCellMultiData({ - query: { [EXTRA_FIELD]: 'number' }, - totals: { - row: { totalDimensions: false }, - column: { grandTotalOnly: false, subTotalOnly: true }, - }, - }), - ).toHaveLength(24); - }); }); test('getDimensionValues function', () => { diff --git a/packages/s2-core/__tests__/unit/data-set/pivot-data-set-total-spec.ts b/packages/s2-core/__tests__/unit/data-set/pivot-data-set-total-spec.ts index f6261ede2a..cd8f4a4f03 100644 --- a/packages/s2-core/__tests__/unit/data-set/pivot-data-set-total-spec.ts +++ b/packages/s2-core/__tests__/unit/data-set/pivot-data-set-total-spec.ts @@ -2,19 +2,25 @@ * pivot mode base data-set test. */ import { get, keys } from 'lodash'; -import * as multiDataCfg from 'tests/data/simple-data.json'; import * as mockData from 'tests/data/mock-dataset.json'; -import { assembleDataCfg, TOTALS_OPTIONS } from '../../util'; +import * as multiDataCfg from 'tests/data/simple-data.json'; import type { Query } from '../../../src/data-set/interface'; -import { EXTRA_FIELD, TOTAL_VALUE, VALUE_FIELD } from '@/common/constant'; +import { TOTALS_OPTIONS, assembleDataCfg } from '../../util'; +import { + EXTRA_FIELD, + ORIGIN_FIELD, + QueryDataType, + TOTAL_VALUE, + VALUE_FIELD, +} from '@/common/constant'; import { - type S2DataConfig, Aggregation, type CalcTotals, + type S2DataConfig, } from '@/common/interface'; -import { PivotSheet } from '@/sheet-type'; -import { PivotDataSet } from '@/data-set/pivot-data-set'; import { Store } from '@/common/store'; +import { PivotDataSet } from '@/data-set/pivot-data-set'; +import { PivotSheet } from '@/sheet-type'; import { getDimensionsWithoutPathPre } from '@/utils/dataset/pivot-data-set'; jest.mock('@/sheet-type'); @@ -78,6 +84,12 @@ describe('Pivot Dataset Total Test', () => { TOTAL_VALUE, ]); + expect([...colPivotMeta.get('家具')!.children.keys()]).toEqual([ + '桌子', + '沙发', + TOTAL_VALUE, + ]); + expect([...colPivotMeta.get('办公用品')!.children.keys()]).toEqual([ '笔', '纸张', @@ -88,24 +100,32 @@ describe('Pivot Dataset Total Test', () => { test('should get correct indexesData', () => { const indexesData = dataSet.indexesData; - expect(get(indexesData, '1.1.0.0')).toEqual({ + expect( + get(indexesData, ['province[&]city[&]type[&]sub_type', 1, 1, 0, 0, 1]), + ).toEqual({ province: '浙江省', city: '杭州市', number: 15420, }); - expect(get(indexesData, '1.1.2.0')).toEqual({ + expect( + get(indexesData, ['province[&]city[&]type[&]sub_type', 1, 1, 2, 0, 1]), + ).toEqual({ province: '浙江省', city: '杭州市', type: '办公用品', number: 2288, }); - expect(get(indexesData, '2.0.2.0')).toEqual({ + expect( + get(indexesData, ['province[&]city[&]type[&]sub_type', 2, 0, 2, 0, 1]), + ).toEqual({ province: '四川省', type: '办公用品', number: 18479, }); - expect(get(indexesData, '0.0.0.0')).toEqual({ + expect( + get(indexesData, ['province[&]city[&]type[&]sub_type', 0, 0, 0, 0, 1]), + ).toEqual({ number: 78868, }); }); @@ -118,10 +138,12 @@ describe('Pivot Dataset Total Test', () => { 'city', 'type', 'sub_type', + EXTRA_FIELD, ]); expect( getDimensionsWithoutPathPre(sortedDimensionValues['province']), ).toEqual(['浙江省', '四川省', TOTAL_VALUE]); + expect( getDimensionsWithoutPathPre(sortedDimensionValues['city']), ).toEqual([ @@ -140,6 +162,9 @@ describe('Pivot Dataset Total Test', () => { expect( getDimensionsWithoutPathPre(sortedDimensionValues['type']), ).toEqual(['家具', '办公用品', TOTAL_VALUE]); + expect( + getDimensionsWithoutPathPre(sortedDimensionValues['type']), + ).toEqual(['家具', '办公用品', TOTAL_VALUE]); expect( getDimensionsWithoutPathPre(sortedDimensionValues['sub_type']), ).toEqual([ @@ -151,80 +176,79 @@ describe('Pivot Dataset Total Test', () => { TOTAL_VALUE, TOTAL_VALUE, ]); + expect( + getDimensionsWithoutPathPre(sortedDimensionValues[EXTRA_FIELD]), + ).toEqual([ + 'number', + 'number', + 'number', + 'number', + 'number', + 'number', + 'number', + ]); }); }); describe('test for query data', () => { test('getCellData function', () => { expect( - dataSet - .getCellData({ - query: { - province: '浙江省', - type: '家具', - sub_type: '桌子', - [EXTRA_FIELD]: 'number', - }, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + province: '浙江省', + type: '家具', + sub_type: '桌子', + [EXTRA_FIELD]: 'number', + }, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 18375]]); expect( - dataSet - .getCellData({ - query: { - type: '家具', - sub_type: '桌子', - [EXTRA_FIELD]: 'number', - }, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + type: '家具', + sub_type: '桌子', + [EXTRA_FIELD]: 'number', + }, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 26193]]); expect( - dataSet - .getCellData({ - query: { - province: '浙江省', - city: '杭州市', - type: '家具', - [EXTRA_FIELD]: 'number', - }, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + province: '浙江省', + city: '杭州市', + type: '家具', + [EXTRA_FIELD]: 'number', + }, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 13132]]); expect( - dataSet - .getCellData({ - query: { - province: '浙江省', - city: '杭州市', - [EXTRA_FIELD]: 'number', - }, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + province: '浙江省', + city: '杭州市', + [EXTRA_FIELD]: 'number', + }, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 15420]]); expect( - dataSet - .getCellData({ - query: { - type: '家具', - [EXTRA_FIELD]: 'number', - }, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + type: '家具', + [EXTRA_FIELD]: 'number', + }, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 49709]]); expect( - dataSet - .getCellData({ - query: { - [EXTRA_FIELD]: 'number', - }, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + [EXTRA_FIELD]: 'number', + }, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 78868]]); }); @@ -268,80 +292,68 @@ describe('Pivot Dataset Total Test', () => { }); test('should get correct total cell data when calculated by aggregation', () => { expect( - dataSet - .getCellData({ - query: { - province: '浙江省', - type: '家具', - sub_type: '桌子', - [EXTRA_FIELD]: 'number', - }, - isTotals: true, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + province: '浙江省', + type: '家具', + sub_type: '桌子', + [EXTRA_FIELD]: 'number', + }, + isTotals: true, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 18375]]); expect( - dataSet - .getCellData({ - query: { - type: '家具', - sub_type: '桌子', - [EXTRA_FIELD]: 'number', - }, - isTotals: true, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + type: '家具', + sub_type: '桌子', + [EXTRA_FIELD]: 'number', + }, + isTotals: true, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 26193]]); expect( - dataSet - .getCellData({ - query: { - province: '浙江省', - city: '杭州市', - type: '家具', - [EXTRA_FIELD]: 'number', - }, - isTotals: true, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + province: '浙江省', + city: '杭州市', + type: '家具', + [EXTRA_FIELD]: 'number', + }, + isTotals: true, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 13132]]); expect( - dataSet - .getCellData({ - query: { - province: '浙江省', - city: '杭州市', - [EXTRA_FIELD]: 'number', - }, - isTotals: true, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + province: '浙江省', + city: '杭州市', + [EXTRA_FIELD]: 'number', + }, + isTotals: true, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 15420]]); expect( - dataSet - .getCellData({ - query: { - type: '家具', - [EXTRA_FIELD]: 'number', - }, - isTotals: true, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + type: '家具', + [EXTRA_FIELD]: 'number', + }, + isTotals: true, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 49709]]); expect( - dataSet - .getCellData({ - query: { - [EXTRA_FIELD]: 'number', - }, - isTotals: true, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + [EXTRA_FIELD]: 'number', + }, + isTotals: true, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 78868]]); }); @@ -389,28 +401,24 @@ describe('Pivot Dataset Total Test', () => { }); test('should get correct total cell data when calculated by aggregation and multi values', () => { expect( - dataSet - .getCellData({ - query: { - province: '浙江', - type: '笔', - [EXTRA_FIELD]: 'price', - }, - isTotals: true, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + province: '浙江', + type: '笔', + [EXTRA_FIELD]: 'price', + }, + isTotals: true, + })?.[ORIGIN_FIELD], ).toContainEntries([['price', 2]]); expect( - dataSet - .getCellData({ - query: { - province: '浙江', - type: '笔', - [EXTRA_FIELD]: 'cost', - }, - isTotals: true, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + province: '浙江', + type: '笔', + [EXTRA_FIELD]: 'cost', + }, + isTotals: true, + })?.[ORIGIN_FIELD], ).toContainEntries([['cost', 4]]); }); }); @@ -473,82 +481,126 @@ describe('Pivot Dataset Total Test', () => { dataSet = new PivotDataSet(mockSheet); dataSet.setDataCfg(dataCfg); }); + + test('should get correct total cell data when totals calculated by calcFunc and Existential dimension grouping', () => { + const totalStatus = { + isRowTotal: true, + isColTotal: true, + isRowSubTotal: true, + isColSubTotal: true, + }; + + expect( + dataSet.getCellData({ + query: { + province: '浙江省', + sub_type: '桌子', + [EXTRA_FIELD]: 'number', + }, + isTotals: true, + totalStatus, + })?.[VALUE_FIELD], + ).toEqual(18375); + + expect( + dataSet.getCellData({ + query: { + province: '浙江省', + [EXTRA_FIELD]: 'number', + }, + totalStatus, + isTotals: true, + })?.[VALUE_FIELD], + ).toEqual(43098); + + expect( + dataSet.getCellData({ + query: { + sub_type: '桌子', + [EXTRA_FIELD]: 'number', + }, + totalStatus, + isTotals: true, + })?.[VALUE_FIELD], + ).toEqual(26193); + + expect( + dataSet.getCellData({ + query: { + province: '浙江省', + type: '家具', + [EXTRA_FIELD]: 'number', + }, + isTotals: true, + totalStatus, + })?.[VALUE_FIELD], + ).toEqual(32418); + }); + test('should get correct total cell data when totals calculated by calcFunc', () => { expect( - dataSet - .getCellData({ - query: { - province: '浙江省', - type: '家具', - sub_type: '桌子', - [EXTRA_FIELD]: 'number', - }, - isTotals: true, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + province: '浙江省', + type: '家具', + sub_type: '桌子', + [EXTRA_FIELD]: 'number', + }, + isTotals: true, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 18375]]); expect( - dataSet - .getCellData({ - query: { - type: '家具', - sub_type: '桌子', - [EXTRA_FIELD]: 'number', - }, - isTotals: true, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + type: '家具', + sub_type: '桌子', + [EXTRA_FIELD]: 'number', + }, + isTotals: true, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 52386]]); expect( - dataSet - .getCellData({ - query: { - province: '浙江省', - city: '杭州市', - type: '家具', - [EXTRA_FIELD]: 'number', - }, - isTotals: true, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + province: '浙江省', + city: '杭州市', + type: '家具', + [EXTRA_FIELD]: 'number', + }, + isTotals: true, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 26264]]); expect( - dataSet - .getCellData({ - query: { - province: '浙江省', - city: '杭州市', - [EXTRA_FIELD]: 'number', - }, - isTotals: true, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + province: '浙江省', + city: '杭州市', + [EXTRA_FIELD]: 'number', + }, + isTotals: true, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 15420]]); expect( - dataSet - .getCellData({ - query: { - type: '家具', - [EXTRA_FIELD]: 'number', - }, - isTotals: true, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + type: '家具', + [EXTRA_FIELD]: 'number', + }, + isTotals: true, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 99418]]); expect( - dataSet - .getCellData({ - query: { - [EXTRA_FIELD]: 'number', - }, - isTotals: true, - })! - .getOrigin(), + dataSet.getCellData({ + query: { + [EXTRA_FIELD]: 'number', + }, + isTotals: true, + })?.[ORIGIN_FIELD], ).toContainEntries([['number', 78868]]); }); }); @@ -564,7 +616,7 @@ describe('Pivot Dataset Total Test', () => { expect(dataSet.getCellMultiData({ query: specialQuery })).toHaveLength(1); expect( - dataSet.getCellMultiData({ query: specialQuery })[0].getOrigin(), + dataSet.getCellMultiData({ query: specialQuery })[0]?.[ORIGIN_FIELD], ).toContainEntries([['number', 7789]]); expect( dataSet.getCellMultiData({ @@ -721,4 +773,53 @@ describe('Pivot Dataset Total Test', () => { expect(isColSubTotal4).toBeTrue(); }); }); + + describe('test for total with dimension group', () => { + beforeEach(() => { + MockPivotSheet.mockClear(); + const mockSheet = new MockPivotSheet(); + + mockSheet.store = new Store(); + mockSheet.isValueInCols = () => true; + dataSet = new PivotDataSet(mockSheet); + + dataCfg = assembleDataCfg({ + meta: [], + fields: { + rows: ['province', 'city', 'type', 'sub_type'], + columns: [], + }, + }); + dataSet.setDataCfg(dataCfg); + }); + + test('get correct MultiData when query need to be processed', () => { + expect( + dataSet.getCellMultiData({ + query: { + province: '浙江省', + sub_type: '桌子', + }, + queryType: QueryDataType.DetailOnly, + }), + ).toMatchSnapshot(); + expect( + dataSet.getCellMultiData({ + query: { + province: '浙江省', + sub_type: '杭州市', + }, + queryType: QueryDataType.DetailOnly, + }), + ).toMatchSnapshot(); + expect( + dataSet.getCellMultiData({ + query: { + sub_type: '桌子', + }, + queryType: QueryDataType.DetailOnly, + }), + ).toMatchSnapshot(); + }); + }); }); diff --git a/packages/s2-core/__tests__/unit/data-set/table-data-set-spec.ts b/packages/s2-core/__tests__/unit/data-set/table-data-set-spec.ts index d3f6ab1f30..a96e5d399c 100644 --- a/packages/s2-core/__tests__/unit/data-set/table-data-set-spec.ts +++ b/packages/s2-core/__tests__/unit/data-set/table-data-set-spec.ts @@ -3,7 +3,7 @@ */ import { assembleDataCfg } from 'tests/util'; import type { S2DataConfig } from '@/common/interface'; -import { TableSheet } from '@/sheet-type'; +import { SpreadSheet, TableSheet } from '@/sheet-type'; import { TableDataSet } from '@/data-set/table-data-set'; jest.mock('@/sheet-type'); @@ -11,7 +11,9 @@ jest.mock('@/facet/layout/node'); const MockTableSheet = TableSheet as any as jest.Mock<TableSheet>; describe('Table Mode Dataset Test', () => { + let s2: SpreadSheet; let dataSet: TableDataSet; + const mockNumberFormatter = jest.fn().mockReturnValue('number'); const mockSubTypeFormatter = jest.fn().mockReturnValue('sub_type'); const mockTypeFormatter = jest.fn().mockReturnValue('type'); @@ -59,12 +61,14 @@ describe('Table Mode Dataset Test', () => { beforeEach(() => { MockTableSheet.mockClear(); - dataSet = new TableDataSet(new MockTableSheet()); + + s2 = new MockTableSheet(); + dataSet = new TableDataSet(s2); dataSet.setDataCfg(dataCfg); }); - afterAll(() => {}); + afterEach(() => {}); describe('meta config test', () => { test.each` @@ -100,10 +104,14 @@ describe('Table Mode Dataset Test', () => { test('should get correct meta data', () => { expect(dataSet.meta).toEqual(expect.objectContaining([])); }); + + test('should get correctly empty dataset result', () => { + expect(dataSet.isEmpty()).toBeFalsy(); + }); }); describe('test for query data', () => { - test('getCellData function', () => { + test('#getCellData', () => { expect( dataSet.getCellData({ query: { rowIndex: 0 }, @@ -120,7 +128,7 @@ describe('Table Mode Dataset Test', () => { dataSet.getCellData({ query: { rowIndex: 0, - col: 'city', + field: 'city', }, }), ).toEqual('杭州市'); @@ -129,7 +137,7 @@ describe('Table Mode Dataset Test', () => { dataSet.getCellData({ query: { rowIndex: 2, - col: 'number', + field: 'number', }, }), ).toEqual(3877); @@ -138,10 +146,97 @@ describe('Table Mode Dataset Test', () => { dataSet.getCellData({ query: { rowIndex: 5, - col: 'sub_type', + field: 'sub_type', }, }), ).toEqual('沙发'); }); + + test('#getMultiData by empty query', () => { + expect(dataSet.getCellMultiData({})).toMatchSnapshot(); + }); + + test('#getMultiData by rowIndex query', () => { + expect( + dataSet.getCellMultiData({ + query: { + rowIndex: 0, + }, + }), + ).toMatchInlineSnapshot(` + Array [ + Object { + "city": "杭州市", + "number": 7789, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + ] + `); + + expect( + dataSet.getCellMultiData({ + query: { + rowIndex: -1, + }, + }), + ).toMatchSnapshot(); + }); + + test('#getMultiData by field query', () => { + expect( + dataSet.getCellMultiData({ + query: { + field: 'city', + }, + }), + ).toMatchSnapshot(); + + expect( + dataSet.getCellMultiData({ + query: { + field: 'number', + }, + }), + ).toMatchSnapshot(); + + expect( + dataSet.getCellMultiData({ + query: { + field: 'sub_type', + }, + }), + ).toMatchSnapshot(); + }); + + test('#getMultiData by field and rowIndex query', () => { + expect( + dataSet.getCellMultiData({ + query: { + field: 'city', + rowIndex: 0, + }, + }), + ).toEqual(['杭州市']); + + expect( + dataSet.getCellMultiData({ + query: { + field: 'number', + rowIndex: 2, + }, + }), + ).toEqual([3877]); + + expect( + dataSet.getCellMultiData({ + query: { + field: 'sub_type', + rowIndex: 3, + }, + }), + ).toEqual(['桌子']); + }); }); }); diff --git a/packages/s2-core/__tests__/unit/dataset/__snapshots__/table-dataset-spec.ts.snap b/packages/s2-core/__tests__/unit/dataset/__snapshots__/table-dataset-spec.ts.snap new file mode 100644 index 0000000000..b37ddaca28 --- /dev/null +++ b/packages/s2-core/__tests__/unit/dataset/__snapshots__/table-dataset-spec.ts.snap @@ -0,0 +1,459 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Table Mode Dataset Test test for sort and filter should asc sort by number field 1`] = ` +Array [ + Object { + "city": "绵阳市", + "number": 245, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "乐山市", + "number": 352, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "绍兴市", + "number": 632, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "舟山市", + "number": 834, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "杭州市", + "number": 945, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "宁波市", + "number": 1145, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "绍兴市", + "number": 1304, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "杭州市", + "number": 1343, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "绍兴市", + "number": 1354, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "舟山市", + "number": 1432, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "宁波市", + "number": 1523, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "舟山市", + "number": 1634, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "成都市", + "number": 1723, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "绵阳市", + "number": 1822, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "南充市", + "number": 1943, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "绵阳市", + "number": 2244, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "乐山市", + "number": 2330, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "南充市", + "number": 2333, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "成都市", + "number": 2335, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "绍兴市", + "number": 2367, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "乐山市", + "number": 2445, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "成都市", + "number": 2451, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "南充市", + "number": 2457, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "乐山市", + "number": 2458, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "绵阳市", + "number": 3077, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "南充市", + "number": 3551, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "宁波市", + "number": 3877, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "成都市", + "number": 4004, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "舟山市", + "number": 4342, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "杭州市", + "number": 5343, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "宁波市", + "number": 7234, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "杭州市", + "number": 7789, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, +] +`; + +exports[`Table Mode Dataset Test test for sort and filter should desc sort by number field 1`] = ` +Array [ + Object { + "city": "杭州市", + "number": 7789, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "宁波市", + "number": 7234, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "杭州市", + "number": 5343, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "舟山市", + "number": 4342, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "成都市", + "number": 4004, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "宁波市", + "number": 3877, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "南充市", + "number": 3551, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "绵阳市", + "number": 3077, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "乐山市", + "number": 2458, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "南充市", + "number": 2457, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "成都市", + "number": 2451, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "乐山市", + "number": 2445, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "绍兴市", + "number": 2367, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "成都市", + "number": 2335, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "南充市", + "number": 2333, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "乐山市", + "number": 2330, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "绵阳市", + "number": 2244, + "province": "四川省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "南充市", + "number": 1943, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "绵阳市", + "number": 1822, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "成都市", + "number": 1723, + "province": "四川省", + "sub_type": "桌子", + "type": "家具", + }, + Object { + "city": "舟山市", + "number": 1634, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "宁波市", + "number": 1523, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "舟山市", + "number": 1432, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "绍兴市", + "number": 1354, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "杭州市", + "number": 1343, + "province": "浙江省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "绍兴市", + "number": 1304, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "宁波市", + "number": 1145, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "杭州市", + "number": 945, + "province": "浙江省", + "sub_type": "笔", + "type": "办公用品", + }, + Object { + "city": "舟山市", + "number": 834, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "绍兴市", + "number": 632, + "province": "浙江省", + "sub_type": "沙发", + "type": "家具", + }, + Object { + "city": "乐山市", + "number": 352, + "province": "四川省", + "sub_type": "纸张", + "type": "办公用品", + }, + Object { + "city": "绵阳市", + "number": 245, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + }, +] +`; diff --git a/packages/s2-core/__tests__/unit/dataset/table-dataset-spec.ts b/packages/s2-core/__tests__/unit/dataset/table-dataset-spec.ts index 8089f50f50..07cc181f83 100644 --- a/packages/s2-core/__tests__/unit/dataset/table-dataset-spec.ts +++ b/packages/s2-core/__tests__/unit/dataset/table-dataset-spec.ts @@ -1,12 +1,12 @@ /** * table mode data-set test. */ -import { orderBy, uniq } from 'lodash'; +import { first, last, orderBy, uniq } from 'lodash'; import { data } from 'tests/data/mock-dataset.json'; import { assembleDataCfg } from '../../util'; import type { S2DataConfig, SortParam } from '@/common/interface'; -import { TableSheet } from '@/sheet-type'; import { TableDataSet } from '@/data-set/table-data-set'; +import { TableSheet } from '@/sheet-type'; jest.mock('@/sheet-type'); jest.mock('@/facet/layout/node'); @@ -15,6 +15,7 @@ const MockTableSheet = TableSheet as any as jest.Mock<TableSheet>; describe('Table Mode Dataset Test', () => { let dataSet: TableDataSet; + const dataCfg: S2DataConfig = { ...assembleDataCfg({}), meta: [], @@ -36,6 +37,10 @@ describe('Table Mode Dataset Test', () => { dataSet.setDataCfg(dataCfg); }); + afterEach(() => { + MockTableSheet.mockClear(); + }); + describe('test base dataset structure', () => { test('should get correct field data', () => { expect(dataSet.fields.rows).toEqual(undefined); @@ -60,7 +65,7 @@ describe('Table Mode Dataset Test', () => { dataSet.getCellData({ query: { rowIndex: 0, - col: 'city', + field: 'city', }, }), ).toEqual('杭州市'); @@ -69,7 +74,7 @@ describe('Table Mode Dataset Test', () => { dataSet.getCellData({ query: { rowIndex: 2, - col: 'number', + field: 'number', }, }), ).toEqual(3877); @@ -78,7 +83,7 @@ describe('Table Mode Dataset Test', () => { dataSet.getCellData({ query: { rowIndex: 5, - col: 'sub_type', + field: 'sub_type', }, }), ).toEqual('沙发'); @@ -97,7 +102,7 @@ describe('Table Mode Dataset Test', () => { expect( emptyDataSet.getCellData({ query: { - col: 'sub_type', + field: 'sub_type', rowIndex: 0, }, }), @@ -120,11 +125,12 @@ describe('Table Mode Dataset Test', () => { dataSet.getCellData({ query: { rowIndex: 0, - col: 'city', + field: 'city', }, }), ).toEqual('成都市'); }); + it('should getCellData with customFilter', () => { dataSet.setDataCfg({ ...dataCfg, @@ -139,7 +145,7 @@ describe('Table Mode Dataset Test', () => { dataSet.getCellData({ query: { rowIndex: 0, - col: 'city', + field: 'city', }, }), ).toEqual('杭州市'); @@ -161,7 +167,7 @@ describe('Table Mode Dataset Test', () => { dataSet.getCellData({ query: { rowIndex: 0, - col: 'city', + field: 'city', }, }), ).toEqual('成都市'); @@ -181,7 +187,7 @@ describe('Table Mode Dataset Test', () => { dataSet.getCellData({ query: { rowIndex: 0, - col: 'number', + field: 'number', }, }), ).toEqual(245); @@ -292,5 +298,118 @@ describe('Table Mode Dataset Test', () => { dataSet.handleDimensionValuesSort(); expect([...result, ...rest]).toStrictEqual(dataSet.getDisplayDataSet()); }); + + it('should asc sort by number field', () => { + const sortFieldId = 'number'; + + dataSet.sortParams = [ + { + sortFieldId, + sortMethod: 'asc', + }, + ]; + + dataSet.handleDimensionValuesSort(); + + expect(dataSet.getDisplayDataSet()).toHaveLength(32); + expect(dataSet.getDisplayDataSet()).toMatchSnapshot(); + }); + + it('should desc sort by number field', () => { + const sortFieldId = 'number'; + + dataSet.sortParams = [ + { + sortFieldId, + sortMethod: 'desc', + }, + ]; + + dataSet.handleDimensionValuesSort(); + + expect(dataSet.getDisplayDataSet()).toHaveLength(32); + expect(dataSet.getDisplayDataSet()).toMatchSnapshot(); + }); + + // https://github.com/antvis/S2/issues/2388 + it('should frozen correctly desc sorted data', () => { + const sortFieldId = 'number'; + + Object.defineProperty(dataSet.spreadsheet, 'options', { + value: { + frozenRowCount: 1, + frozenTrailingRowCount: 1, + }, + }); + + dataSet.sortParams = [ + { + sortFieldId, + sortMethod: 'desc', + }, + ]; + + dataSet.handleDimensionValuesSort(); + + expect(dataSet.getDisplayDataSet()).toHaveLength(32); + expect(first(dataSet.getDisplayDataSet())).toMatchInlineSnapshot(` + Object { + "city": "杭州市", + "number": 7789, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + } + `); + expect(last(dataSet.getDisplayDataSet())).toMatchInlineSnapshot(` + Object { + "city": "绵阳市", + "number": 245, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + } + `); + }); + + it('should frozen correctly asc sorted data', () => { + const sortFieldId = 'number'; + + Object.defineProperty(dataSet.spreadsheet, 'options', { + value: { + frozenRowCount: 1, + frozenTrailingRowCount: 1, + }, + }); + + dataSet.sortParams = [ + { + sortFieldId, + sortMethod: 'asc', + }, + ]; + + dataSet.handleDimensionValuesSort(); + + expect(dataSet.getDisplayDataSet()).toHaveLength(32); + expect(first(dataSet.getDisplayDataSet())).toMatchInlineSnapshot(` + Object { + "city": "绵阳市", + "number": 245, + "province": "四川省", + "sub_type": "笔", + "type": "办公用品", + } + `); + expect(last(dataSet.getDisplayDataSet())).toMatchInlineSnapshot(` + Object { + "city": "杭州市", + "number": 7789, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + } + `); + }); }); }); diff --git a/packages/s2-core/__tests__/unit/facet/__snapshots__/table-facet-spec.ts.snap b/packages/s2-core/__tests__/unit/facet/__snapshots__/table-facet-spec.ts.snap new file mode 100644 index 0000000000..df5e1dc0ba --- /dev/null +++ b/packages/s2-core/__tests__/unit/facet/__snapshots__/table-facet-spec.ts.snap @@ -0,0 +1,145 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Table Mode Facet Test With Adaptive Layout should get correct col layout col hierarchy coordinate with adaptive layout 1`] = ` +Array [ + Object { + "height": 30, + "width": 119.8, + "x": 0, + "y": 0, + }, + Object { + "height": 30, + "width": 119.8, + "x": 119.8, + "y": 0, + }, + Object { + "height": 30, + "width": 119.8, + "x": 239.6, + "y": 0, + }, + Object { + "height": 30, + "width": 119.8, + "x": 359.4, + "y": 0, + }, + Object { + "height": 30, + "width": 119.8, + "x": 479.2, + "y": 0, + }, +] +`; + +exports[`Table Mode Facet Test With Adaptive Layout should get correct col layout with seriesNumber col hierarchy coordinate with adaptive layout with seriesNumber 1`] = ` +Array [ + Object { + "height": 30, + "width": 103.8, + "x": 80, + "y": 0, + }, + Object { + "height": 30, + "width": 103.8, + "x": 183.8, + "y": 0, + }, + Object { + "height": 30, + "width": 103.8, + "x": 287.6, + "y": 0, + }, + Object { + "height": 30, + "width": 103.8, + "x": 391.40000000000003, + "y": 0, + }, + Object { + "height": 30, + "width": 103.8, + "x": 495.20000000000005, + "y": 0, + }, +] +`; + +exports[`Table Mode Facet With Column Grouping Frozen Test should get correct frozenInfo 1`] = ` +Object { + "frozenCol": Object { + "range": Array [ + 0, + 0, + ], + "width": 199.66, + "x": 0, + }, + "frozenRow": Object { + "height": 60, + "range": Array [ + 0, + 1, + ], + "y": 0, + }, + "frozenTrailingCol": Object { + "range": Array [ + 2, + 2, + ], + "width": 199.66, + "x": 399.34000000000003, + }, + "frozenTrailingRow": Object { + "height": 60, + "range": Array [ + 30, + 31, + ], + "y": 502, + }, +} +`; + +exports[`Table Mode Facet With Frozen Test should get correct frozenInfo 1`] = ` +Object { + "frozenCol": Object { + "range": Array [ + 0, + 1, + ], + "width": 239.6, + "x": 0, + }, + "frozenRow": Object { + "height": 60, + "range": Array [ + 0, + 1, + ], + "y": 0, + }, + "frozenTrailingCol": Object { + "range": Array [ + 3, + 4, + ], + "width": 239.60000000000002, + "x": 359.4, + }, + "frozenTrailingRow": Object { + "height": 60, + "range": Array [ + 30, + 31, + ], + "y": 502, + }, +} +`; diff --git a/packages/s2-core/__tests__/unit/facet/bbox/cornerBBox-spec.ts b/packages/s2-core/__tests__/unit/facet/bbox/corner-bbox-spec.ts similarity index 98% rename from packages/s2-core/__tests__/unit/facet/bbox/cornerBBox-spec.ts rename to packages/s2-core/__tests__/unit/facet/bbox/corner-bbox-spec.ts index 6381e67460..229ef9cde7 100644 --- a/packages/s2-core/__tests__/unit/facet/bbox/cornerBBox-spec.ts +++ b/packages/s2-core/__tests__/unit/facet/bbox/corner-bbox-spec.ts @@ -1,5 +1,5 @@ import type { BaseFacet } from '@/facet/base-facet'; -import { CornerBBox } from '@/facet/bbox/cornerBBox'; +import { CornerBBox } from '@/facet/bbox/corner-bbox'; describe('cornerBBox test', () => { let mockFacet: BaseFacet; diff --git a/packages/s2-core/__tests__/unit/facet/bbox/panelBBox-spec.ts b/packages/s2-core/__tests__/unit/facet/bbox/panel-bbox-spec.ts similarity index 98% rename from packages/s2-core/__tests__/unit/facet/bbox/panelBBox-spec.ts rename to packages/s2-core/__tests__/unit/facet/bbox/panel-bbox-spec.ts index f758ee9ed0..e6635f0a66 100644 --- a/packages/s2-core/__tests__/unit/facet/bbox/panelBBox-spec.ts +++ b/packages/s2-core/__tests__/unit/facet/bbox/panel-bbox-spec.ts @@ -1,6 +1,6 @@ import type { S2Options, ThemeCfg } from '@/common'; import type { BaseFacet } from '@/facet/base-facet'; -import { PanelBBox } from '@/facet/bbox/panelBBox'; +import { PanelBBox } from '@/facet/bbox/panel-bbox'; describe('PanelBBox Tests', () => { const layoutResult = { diff --git a/packages/s2-core/__tests__/unit/facet/header/frozen-row-spec.ts b/packages/s2-core/__tests__/unit/facet/header/frozen-row-spec.ts new file mode 100644 index 0000000000..43e038b1b0 --- /dev/null +++ b/packages/s2-core/__tests__/unit/facet/header/frozen-row-spec.ts @@ -0,0 +1,51 @@ +import { createPivotSheet } from 'tests/util/helpers'; +import { get } from 'lodash'; +import type { HierarchyType, RowHeader } from '../../../../src'; +import type { FrozenFacet } from '../../../../src/facet/frozen-facet'; +import { DEFAULT_OPTIONS, FrozenGroupType } from '@/common'; +import { RowCell } from '@/cell'; + +const s2 = createPivotSheet( + { + ...DEFAULT_OPTIONS, + frozen: { + firstRow: true, + }, + totals: { row: { showGrandTotals: true, reverseGrandTotalsLayout: true } }, + showSeriesNumber: false, + }, + { useSimpleData: false }, +); + +describe('Pivot Frozen Row Header Test', () => { + test.each(['grid', 'tree'] as HierarchyType[])( + 'frozen row header group api', + async (hierarchyType) => { + s2.setOptions({ hierarchyType }); + await s2.render(); + + const rowHeader = s2.facet.rowHeader as RowHeader; + + expect(rowHeader).toBeTruthy(); + expect(rowHeader.frozenRowGroup).toBeTruthy(); + expect(rowHeader.scrollGroup).toBeTruthy(); + + expect(rowHeader.frozenRowGroup.children).toHaveLength(1); + const frozenRowCell = rowHeader.frozenRowGroup.children[0]; + + expect(frozenRowCell instanceof RowCell).toBeTrue(); + expect(get(frozenRowCell, 'meta.height')).toEqual(30); + + expect(rowHeader.scrollGroup.getChildren()).toHaveLength(10); + const scrollCell = rowHeader.scrollGroup.getChildren()[0]; + + expect(scrollCell instanceof RowCell).toBeTrue(); + expect(get(frozenRowCell, 'meta.height')).toEqual(30); + + expect( + (s2.facet as FrozenFacet).frozenGroupInfo[FrozenGroupType.FROZEN_ROW] + .height, + ).toBe(30); + }, + ); +}); diff --git a/packages/s2-core/__tests__/unit/facet/layout/col-node-width-spec.ts b/packages/s2-core/__tests__/unit/facet/layout/col-node-width-spec.ts index 271b8c12de..b6d1e7fc98 100644 --- a/packages/s2-core/__tests__/unit/facet/layout/col-node-width-spec.ts +++ b/packages/s2-core/__tests__/unit/facet/layout/col-node-width-spec.ts @@ -2,7 +2,7 @@ import * as mockDataConfig from 'tests/data/simple-data.json'; import * as mockTableDataConfig from 'tests/data/simple-table-data.json'; import { getContainer } from 'tests/util/helpers'; import { PivotSheet, TableSheet } from '@/sheet-type'; -import type { S2DataConfig, S2Options } from '@/common'; +import { LayoutWidthType, type S2DataConfig, type S2Options } from '@/common'; const s2options: S2Options = { width: 800, @@ -35,7 +35,7 @@ describe('Col width Test', () => { }); test('get correct width in layoutWidthType adaptive mode', () => { - expect(s2.facet.getColLeafNodes()[0].width).toBe(200); + expect(s2.facet.getColLeafNodes()[0].width).toBe(199.5); }); test('get correct width in layoutWidthType adaptive mode when enable series number', async () => { @@ -44,7 +44,7 @@ describe('Col width Test', () => { }); await s2.render(); - expect(s2.facet.getColLeafNodes()[0].width).toBe(180); + expect(s2.facet.getColLeafNodes()[0].width).toBe(179.5); }); test('get correct width in layoutWidthType adaptive tree mode', async () => { @@ -69,7 +69,7 @@ describe('Col width Test', () => { test('get correct width in layoutWidthType compact mode', async () => { s2.setOptions({ style: { - layoutWidthType: 'compact', + layoutWidthType: LayoutWidthType.Compact, }, }); await s2.render(); @@ -92,7 +92,7 @@ describe('Col width Test', () => { }); s2.setOptions({ style: { - layoutWidthType: 'compact', + layoutWidthType: LayoutWidthType.Compact, }, }); await s2.render(); @@ -126,7 +126,7 @@ describe('Col width Test', () => { test('get correct width in layoutWidthType compact mode', async () => { s2.setOptions({ style: { - layoutWidthType: 'compact', + layoutWidthType: LayoutWidthType.Compact, }, }); await s2.render(); diff --git a/packages/s2-core/__tests__/unit/facet/layout/row-node-width-spec.ts b/packages/s2-core/__tests__/unit/facet/layout/row-node-width-spec.ts index dc208af2d9..a2c2141155 100644 --- a/packages/s2-core/__tests__/unit/facet/layout/row-node-width-spec.ts +++ b/packages/s2-core/__tests__/unit/facet/layout/row-node-width-spec.ts @@ -1,7 +1,7 @@ import * as mockDataConfig from 'tests/data/data-issue-372.json'; import { getContainer } from 'tests/util/helpers'; import { PivotSheet } from '@/sheet-type'; -import type { S2Options } from '@/common'; +import { LayoutWidthType, type S2Options } from '@/common'; const s2options: S2Options = { width: 800, @@ -25,7 +25,7 @@ describe('Row width Test in grid mode', () => { test('get the correct custom width of row nodes when the layoutWidthType equals colAdaptive', async () => { s2.setOptions({ style: { - layoutWidthType: 'compact', + layoutWidthType: LayoutWidthType.Compact, rowCell: { width: 50 }, }, }); @@ -39,7 +39,7 @@ describe('Row width Test in grid mode', () => { test('get the correct custom width of row nodes when the layoutWidthType equals compact', async () => { s2.setOptions({ style: { - layoutWidthType: 'compact', + layoutWidthType: LayoutWidthType.Compact, rowCell: { width: 20 }, }, }); diff --git a/packages/s2-core/__tests__/unit/facet/pivot-facet-spec.ts b/packages/s2-core/__tests__/unit/facet/pivot-facet-spec.ts index 83d37f218b..e24c1868fe 100644 --- a/packages/s2-core/__tests__/unit/facet/pivot-facet-spec.ts +++ b/packages/s2-core/__tests__/unit/facet/pivot-facet-spec.ts @@ -2,24 +2,29 @@ * pivot mode pivot test. */ import { Canvas, Group, Rect, type CanvasConfig } from '@antv/g'; -import { assembleDataCfg, assembleOptions } from 'tests/util'; -import { size, find } from 'lodash'; import { Renderer } from '@antv/g-canvas'; +import { find, size } from 'lodash'; +import { assembleDataCfg, assembleOptions } from 'tests/util'; +import { FrozenGroupType } from '../../../src'; +import { createFakeSpreadSheet } from '../../util/helpers'; import { getMockPivotMeta } from './util'; -import { Node } from '@/facet/layout/node'; -import { DEFAULT_TREE_ROW_CELL_WIDTH } from '@/common/constant/options'; -import type { PanelScrollGroup } from '@/group/panel-scroll-group'; -import { SpreadSheet } from '@/sheet-type'; -import { PivotDataSet } from '@/data-set/pivot-data-set'; -import { PivotFacet } from '@/facet/pivot-facet'; import { CornerCell, DataCell } from '@/cell'; +import { + DEFAULT_OPTIONS, + DEFAULT_STYLE, + DEFAULT_TREE_ROW_CELL_WIDTH, +} from '@/common/constant/options'; +import type { ViewMeta } from '@/common/interface/basic'; import { Store } from '@/common/store'; -import { getTheme } from '@/theme'; -import { DEFAULT_OPTIONS, DEFAULT_STYLE } from '@/common/constant/options'; +import type { CellData } from '@/data-set/cell-data'; +import { PivotDataSet } from '@/data-set/pivot-data-set'; import { ColHeader, CornerHeader, Frame, RowHeader } from '@/facet/header'; -import type { ViewMeta } from '@/common/interface/basic'; +import { Node } from '@/facet/layout/node'; +import { PivotFacet } from '@/facet/pivot-facet'; +import type { PanelScrollGroup } from '@/group/panel-scroll-group'; import { RootInteraction } from '@/interaction/root'; -import type { CellData } from '@/data-set/cell-data'; +import { SpreadSheet } from '@/sheet-type'; +import { getTheme } from '@/theme'; jest.mock('@/interaction/root'); @@ -49,7 +54,9 @@ jest.mock('@/sheet-type', () => { return { dataCfg: assembleDataCfg(), - options: assembleOptions(), + options: assembleOptions({ + dataCell: (viewMeta) => new DataCell(viewMeta, viewMeta.spreadsheet), + }), container, theme: getTheme({}), store: new Store(), @@ -70,11 +77,20 @@ jest.mock('@/sheet-type', () => { layoutResult: { rowLeafNodes: [], }, + getLayoutResult: () => ({ rowLeafNodes: [], colLeafNodes: [] }), getRowLeafNodes: () => [], getRowNodes: () => [], getColNodes: () => [], getHiddenColumnsInfo: jest.fn(), getCellMeta: jest.fn(), + getRowLeafNodeByIndex: () => [], + frozenGroupInfo: { + [FrozenGroupType.FROZEN_ROW]: {}, + [FrozenGroupType.FROZEN_COL]: {}, + [FrozenGroupType.FROZEN_TRAILING_ROW]: {}, + [FrozenGroupType.FROZEN_TRAILING_COL]: {}, + }, + cornerBBox: {}, }, getCanvasElement: () => container.getContextService().getDomElement() as HTMLCanvasElement, @@ -103,12 +119,17 @@ jest.mock('@/data-set/pivot-data-set', () => { sortedDimensionValues, moreThanOneValue: jest.fn(), getField: jest.fn(), + transformIndexesData: actualPivotDataSet.prototype.transformIndexesData, + getExistValuesByDataItem: + actualPivotDataSet.prototype.getExistValuesByDataItem, getFieldFormatter: actualDataSet.prototype.getFieldFormatter, getFieldMeta: (field: string, meta: ViewMeta) => find(meta, { field }), getFieldName: actualPivotDataSet.prototype.getFieldName, getCellData: actualPivotDataSet.prototype.getCellData, getCellMultiData: jest.fn(), getDimensionValues: actualPivotDataSet.prototype.getDimensionValues, + getFieldsAndPivotMetaByField: + actualPivotDataSet.prototype.getFieldsAndPivotMetaByField, }; }), }; @@ -130,13 +151,16 @@ describe('Pivot Mode Facet Test', () => { s2.options = assembleOptions({ dataCell: (viewMeta) => new DataCell(viewMeta, s2), }); + const facet = new PivotFacet(s2); - const facet: PivotFacet = new PivotFacet(s2); - - beforeAll(async () => { + beforeEach(async () => { await s2.container.ready; }); + afterEach(() => { + facet.destroy(); + }); + describe('should get correct hierarchy', () => { const { dataCell, colCell } = s2.options.style!; const { rowsHierarchy, colsHierarchy, colLeafNodes } = @@ -155,9 +179,9 @@ describe('Pivot Mode Facet Test', () => { expect(rowsHierarchy.getNodes(0)).toHaveLength(2); rowsHierarchy.getLeaves().forEach((node, index) => { - expect(node.width).toBe(99); + expect(Math.floor(node.width)).toBeCloseTo(99); expect(node.height).toBe(dataCell!.height!); - expect(node.x).toBe(99 * node.level); + expect(Math.floor(node.x)).toBeCloseTo(99 * node.level); expect(node.y).toBe(node.height * index); }); @@ -181,9 +205,9 @@ describe('Pivot Mode Facet Test', () => { expect(colsHierarchy.getNodes(0)).toHaveLength(2); colsHierarchy.getLeaves().forEach((node, index) => { - expect(node.width).toBe(width); + expect(Math.ceil(node.width)).toBeCloseTo(width); expect(node.height).toBe(colCell!.height); - expect(node.x).toBe(width * index); + expect(Math.ceil(node.x)).toBe(width * index); expect(node.y).toBe(node.height * node.level); }); @@ -219,22 +243,18 @@ describe('Pivot Mode Facet Test', () => { }); describe('should get correct result when tree mode', () => { - s2.isHierarchyTreeType = jest.fn().mockReturnValue(true); - s2.options = assembleOptions({ - hierarchyType: 'tree', - }); - // 小于 DEFAULT_TREE_ROW_WIDTH - const spy = jest.spyOn(s2, 'measureTextWidth').mockReturnValue(30); - - s2.dataSet = new MockPivotDataSet(s2); - const treeFacet = new PivotFacet(s2); - const { rowsHierarchy } = treeFacet.getLayoutResult(); + test('row hierarchy when tree mode', () => { + s2.isHierarchyTreeType = jest.fn().mockReturnValue(true); + s2.options = assembleOptions({ + hierarchyType: 'tree', + }); + // 小于 DEFAULT_TREE_ROW_WIDTH + const spy = jest.spyOn(s2, 'measureTextWidth').mockReturnValue(30); - afterAll(() => { - spy.mockRestore(); - }); + s2.dataSet = new MockPivotDataSet(s2); + const treeFacet = new PivotFacet(s2); + const { rowsHierarchy } = treeFacet.getLayoutResult(); - test('row hierarchy when tree mode', () => { const { dataCell, rowCell } = s2.options.style!; expect(rowsHierarchy.getLeaves()).toHaveLength(8); @@ -248,31 +268,33 @@ describe('Pivot Mode Facet Test', () => { expect(node.x).toBe(0); expect(node.y).toBe(node.height * index); }); + + spy.mockRestore(); }); }); describe('should get correct layer after render', () => { - beforeAll(() => { + beforeEach(() => { facet.render(); }); - afterAll(() => { + afterEach(() => { facet.destroy(); }); test('get header after render', () => { const { rowHeader, cornerHeader, columnHeader, centerFrame } = facet; - expect(rowHeader instanceof RowHeader).toBeTrue(); - expect(rowHeader!.children).toHaveLength(10); + expect(rowHeader).toBeInstanceOf(RowHeader); + expect(rowHeader!.children[0].children).toHaveLength(10); expect(rowHeader!.parsedStyle.visibility).not.toEqual('hidden'); - expect(cornerHeader instanceof CornerHeader).toBeTrue(); - expect(cornerHeader.children.length).toBe(3); + expect(cornerHeader).toBeInstanceOf(CornerHeader); + expect(cornerHeader.children).toHaveLength(2); expect(cornerHeader.parsedStyle.visibility).not.toEqual('hidden'); - expect(columnHeader instanceof ColHeader).toBeTrue(); - expect(centerFrame instanceof Frame).toBeTrue(); + expect(columnHeader).toBeInstanceOf(ColHeader); + expect(centerFrame).toBeInstanceOf(Frame); }); test('get background after render', () => { @@ -287,24 +309,13 @@ describe('Pivot Mode Facet Test', () => { }); describe('should get correct result when enable series number', () => { - const mockDataSet = new MockPivotDataSet(s2); + test('render correct corner header', () => { + const s2 = createFakeSpreadSheet(); - s2.options = assembleOptions({ - showSeriesNumber: true, - dataCell: (fct) => new DataCell(fct, s2), - }); - s2.dataSet = mockDataSet; - const seriesNumberFacet = new PivotFacet(s2); + s2.dataSet = new MockPivotDataSet(s2); + const seriesNumberFacet = new PivotFacet(s2); - beforeAll(() => { seriesNumberFacet.render(); - }); - - afterAll(() => { - seriesNumberFacet.destroy(); - }); - - test('render correct corner header', () => { const { cornerHeader } = seriesNumberFacet; expect(cornerHeader instanceof CornerHeader).toBeTrue(); @@ -320,7 +331,7 @@ describe('Pivot Mode Facet Test', () => { }); it.each(['updateScrollOffset', 'scrollWithAnimation', 'scrollImmediately'])( - 'should not throw "Cannot read property \'value\' of undefined" error if called with single offset config', + 'should not throw "Cannot read property \'value\' of undefined" error if called with single offset config by %s', (method) => { const onlyOffsetYFn = () => { // @ts-ignore diff --git a/packages/s2-core/__tests__/unit/facet/table-facet-spec.ts b/packages/s2-core/__tests__/unit/facet/table-facet-spec.ts index 7a8f38966f..c5b621d547 100644 --- a/packages/s2-core/__tests__/unit/facet/table-facet-spec.ts +++ b/packages/s2-core/__tests__/unit/facet/table-facet-spec.ts @@ -4,15 +4,17 @@ import { Canvas, Group, type CanvasConfig } from '@antv/g'; import { Renderer } from '@antv/g-canvas'; import { assembleDataCfg, assembleOptions } from 'tests/util'; +import { pick } from 'lodash'; import { data } from '../../data/mock-dataset.json'; -import { ROOT_NODE_ID } from '@/common/constant'; +import { createFakeSpreadSheet } from '../../util/helpers'; +import { LayoutWidthType, ROOT_NODE_ID } from '@/common/constant'; import { Store } from '@/common/store'; import { TableDataSet } from '@/data-set/table-data-set'; import { TableFacet } from '@/facet/table-facet'; import { getFrozenLeafNodesCount } from '@/facet/utils'; import { - customMerge, Node, + customMerge, type HiddenColumnsInfo, type S2DataConfig, type S2Options, @@ -20,6 +22,10 @@ import { import { SpreadSheet } from '@/sheet-type'; import { getTheme } from '@/theme'; +const actualDataSet = jest.requireActual( + '@/data-set/base-data-set', +).BaseDataSet; + jest.mock('@/sheet-type', () => { return { SpreadSheet: jest.fn().mockImplementation(() => { @@ -54,6 +60,9 @@ jest.mock('@/sheet-type', () => { getHiddenColumnsInfo: jest.fn(), getColNodeHeight: jest.fn(), }, + dataSet: { + isEmpty: jest.fn(), + }, isHierarchyTreeType: jest.fn(), getCanvasElement: () => container.getContextService().getDomElement() as HTMLCanvasElement, @@ -89,8 +98,10 @@ jest.mock('@/data-set/table-data-set', () => { getFieldName: jest.fn(), getDimensionValues: jest.fn(), getDisplayDataSet: jest.fn(() => data), - getFieldFormatter: jest.fn(), getCellData: () => 1, + getFieldMeta: jest.fn(), + getFieldFormatter: actualDataSet.prototype.getFieldFormatter, + isEmpty: jest.fn(), }; }), }; @@ -118,6 +129,8 @@ const createMockTableFacet = ( ), }); s2.dataSet = new MockTableDataSet(s2); + // @ts-ignore + s2.dataSet.getField = jest.fn(); s2.dataSet.fields = s2.dataCfg.fields; const facet = new TableFacet(s2); @@ -156,38 +169,73 @@ describe('Table Mode Facet Test', () => { expect(facet.getColLeafNodes()[0].value).toEqual(seriesNumberText); }); + + describe('should get none layer when dataCfg.fields is empty', () => { + const spreadsheet = createFakeSpreadSheet({ + s2DataConfig: { + fields: { + rows: [], + columns: [], + values: [], + }, + }, + }); + const mockDataSet = new MockTableDataSet(spreadsheet); + + spreadsheet.dataSet = mockDataSet; + + const newFacet: TableFacet = new TableFacet(spreadsheet); + + beforeEach(() => { + newFacet.render(); + }); + + afterEach(() => { + newFacet.destroy(); + }); + + test('can not get header after render in table sheet', () => { + const { rowHeader, cornerHeader, columnHeader, centerFrame } = newFacet; + + expect(rowHeader).toBeFalsy(); + expect(cornerHeader).toBeFalsy(); + expect(columnHeader).toBeFalsy(); + expect(centerFrame).toBeFalsy(); + }); + + test('can not get series number after render in table sheet', () => { + const { backgroundGroup } = newFacet; + const rect = backgroundGroup.children[0]; + + expect(rect).toBeFalsy(); + }); + }); }); describe('Table Mode Facet Test With Adaptive Layout', () => { describe('should get correct col layout', () => { - const { facet, s2 } = createMockTableFacet({ - showSeriesNumber: false, - }); - const { colCell } = s2.options.style!; - test('col hierarchy coordinate with adaptive layout', () => { - const adaptiveWith = 119; - - facet.getColLeafNodes().forEach((node, index) => { - expect(node.y).toBe(0); - expect(node.x).toBe(index * adaptiveWith); - expect(Math.round(node.width)).toBe(adaptiveWith); - expect(node.height).toBe(colCell!.height); + const { facet } = createMockTableFacet({ + showSeriesNumber: false, }); + + expect( + facet + .getColLeafNodes() + .map((node) => pick(node, ['x', 'y', 'width', 'height'])), + ).toMatchSnapshot(); }); }); describe('should get correct col layout with seriesNumber', () => { - const { facet, s2 } = createMockTableFacet({ - showSeriesNumber: true, - }); - const { colCell } = s2.options.style!; - test('col hierarchy coordinate with adaptive layout with seriesNumber', () => { + const { facet, s2 } = createMockTableFacet({ + showSeriesNumber: true, + }); + const { colCell } = s2.options.style!; const colLeafNodes = facet.getColLeafNodes(); const seriesNumberWidth = facet.getSeriesNumberWidth(); - const adaptiveWith = 103; const seriesNumberNode = colLeafNodes[0]; @@ -196,12 +244,11 @@ describe('Table Mode Facet Test With Adaptive Layout', () => { expect(seriesNumberNode.width).toBe(seriesNumberWidth); expect(seriesNumberNode.height).toBe(colCell!.height); - colLeafNodes.slice(1).forEach((node, index) => { - expect(node.y).toBe(0); - expect(node.x).toBe(index * adaptiveWith + seriesNumberWidth); - expect(node.width).toBe(adaptiveWith); - expect(node.height).toBe(colCell!.height); - }); + expect( + colLeafNodes + .slice(1) + .map((node) => pick(node, ['x', 'y', 'width', 'height'])), + ).toMatchSnapshot(); }); }); }); @@ -233,7 +280,7 @@ describe('Table Mode Facet Test With Compact Layout', () => { }, undefined, (spreadsheet) => { - spreadsheet.getLayoutWidthType = () => 'compact'; + spreadsheet.getLayoutWidthType = () => LayoutWidthType.Compact; spreadsheet.measureTextWidth = mockMeasureFunc as unknown as SpreadSheet['measureTextWidth']; spreadsheet.measureTextWidthRoughly = mockMeasureFunc; @@ -280,7 +327,7 @@ describe('Table Mode Facet Test With Compact Layout', () => { }, undefined, (spreadsheet) => { - spreadsheet.getLayoutWidthType = () => 'compact'; + spreadsheet.getLayoutWidthType = () => LayoutWidthType.Compact; spreadsheet.measureTextWidth = mockMeasureFunc as unknown as SpreadSheet['measureTextWidth']; spreadsheet.measureTextWidthRoughly = mockMeasureFunc; @@ -318,38 +365,7 @@ describe('Table Mode Facet With Frozen Test', () => { test('should get correct frozenInfo', () => { facet.calculateFrozenGroupInfo(); - expect(facet.frozenGroupInfo).toMatchInlineSnapshot(` - Object { - "frozenCol": Object { - "range": Array [ - 0, - 1, - ], - "width": 238, - }, - "frozenRow": Object { - "height": 60, - "range": Array [ - 0, - 1, - ], - }, - "frozenTrailingCol": Object { - "range": Array [ - 3, - 4, - ], - "width": 238, - }, - "frozenTrailingRow": Object { - "height": 60, - "range": Array [ - 30, - 31, - ], - }, - } - `); + expect(facet.frozenGroupInfo).toMatchSnapshot(); }); test('should get correct xy indexes with frozen', () => { @@ -378,8 +394,8 @@ describe('Table Mode Facet With Frozen Test', () => { .getColLeafNodes() .slice(-colCount) .reverse() - .map((node) => node.x), - ).toEqual([481, 362]); + .map((node) => Math.floor(node.x)), + ).toEqual([479, 359]); }); test('should get correct cell layout with frozenTrailingCol', () => { @@ -390,8 +406,8 @@ describe('Table Mode Facet With Frozen Test', () => { .getColLeafNodes() .slice(-trailingColCount!) .reverse() - .map((node) => node.x), - ).toEqual([481, 362]); + .map((node) => Math.floor(node.x)), + ).toEqual([479, 359]); }); test('should get correct cell layout with frozenTrailingRow', () => { @@ -643,38 +659,7 @@ describe('Table Mode Facet With Column Grouping Frozen Test', () => { test('should get correct frozenInfo', () => { facet.calculateFrozenGroupInfo(); - expect(facet.frozenGroupInfo).toMatchInlineSnapshot(` - Object { - "frozenCol": Object { - "range": Array [ - 0, - 0, - ], - "width": 199, - }, - "frozenRow": Object { - "height": 60, - "range": Array [ - 0, - 1, - ], - }, - "frozenTrailingCol": Object { - "range": Array [ - 2, - 2, - ], - "width": 199, - }, - "frozenTrailingRow": Object { - "height": 60, - "range": Array [ - 30, - 31, - ], - }, - } - `); + expect(facet.frozenGroupInfo).toMatchSnapshot(); }); test('should get correct col layout with frozen col', () => { @@ -704,12 +689,8 @@ describe('Table Mode Facet With Column Grouping Frozen Test', () => { .getColLeafNodes() .slice(-trailingColCount) .reverse() - .map((node) => node.x), - ).toMatchInlineSnapshot(` - Array [ - 401, - ] - `); + .map((node) => Math.floor(node.x)), + ).toEqual([399]); }); test('should get correct cell layout with frozenTrailingRow', () => { @@ -722,12 +703,7 @@ describe('Table Mode Facet With Column Grouping Frozen Test', () => { .slice(-trailingRowCount!) .reverse() .map((_, idx) => getCellMeta(displayData.length - 1 - idx, 1)!.y), - ).toMatchInlineSnapshot(` - Array [ - 532, - 502, - ] - `); + ).toEqual([532, 502]); }); test('should get correct viewCellHeights result', () => { diff --git a/packages/s2-core/__tests__/unit/facet/util.ts b/packages/s2-core/__tests__/unit/facet/util.ts index 8df105b473..45aed17f8c 100644 --- a/packages/s2-core/__tests__/unit/facet/util.ts +++ b/packages/s2-core/__tests__/unit/facet/util.ts @@ -1,6 +1,8 @@ import { assembleDataCfg } from 'tests/util'; -import type { RawData } from '../../../src/common'; -import { transformIndexesData } from '@/utils/dataset/pivot-data-set'; +import { + getExistValues, + transformIndexesData, +} from '@/utils/dataset/pivot-data-set'; /** * 获取 Mock 数据 @@ -9,17 +11,20 @@ export function getMockPivotMeta() { const sortedDimensionValues = {}; const rawRowPivotMeta = new Map(); const rawColPivotMeta = new Map(); - const rawIndexesData: RawData[][] | RawData[] = []; + const { fields, data } = assembleDataCfg(); + const rawIndexesData = {}; return transformIndexesData({ - rows: fields.rows, - columns: fields.columns, - values: fields.values, - originData: data, + rows: fields.rows as string[], + columns: fields.columns as string[], + values: fields.values as string[], + data, indexesData: rawIndexesData, sortedDimensionValues, rowPivotMeta: rawRowPivotMeta, colPivotMeta: rawColPivotMeta, + valueInCols: true, + getExistValuesByDataItem: getExistValues, }); } diff --git a/packages/s2-core/__tests__/unit/interaction/base-interaction/click/data-cell-click-spec.ts b/packages/s2-core/__tests__/unit/interaction/base-interaction/click/data-cell-click-spec.ts index e8fdcc89f3..a8823d2063 100644 --- a/packages/s2-core/__tests__/unit/interaction/base-interaction/click/data-cell-click-spec.ts +++ b/packages/s2-core/__tests__/unit/interaction/base-interaction/click/data-cell-click-spec.ts @@ -3,8 +3,6 @@ import { createMockCellInfo, sleep, } from 'tests/util/helpers'; -import type { GEvent } from '@/index'; -import type { SpreadSheet } from '@/sheet-type'; import { HOVER_FOCUS_DURATION, InteractionName, @@ -12,7 +10,10 @@ import { InterceptType, S2Event, } from '@/common/constant'; +import type { InteractionCellHighlightOptions } from '@/common/interface'; import { CustomRect } from '@/engine'; +import type { GEvent } from '@/index'; +import type { SpreadSheet } from '@/sheet-type'; jest.mock('@/interaction/event-controller'); @@ -84,9 +85,30 @@ describe('Interaction Data Cell Click Tests', () => { s2.emit(S2Event.DATA_CELL_CLICK, { stopPropagation() {}, } as unknown as GEvent); + expect(selected).toHaveBeenCalledWith([mockCellInfo.mockCell]); }); + // https://github.com/antvis/S2/issues/2447 + test('should emit cell selected event when cell unselected', () => { + jest + .spyOn(s2.interaction, 'isSelectedCell') + .mockImplementationOnce(() => true); + + const selected = jest.fn(); + + s2.on(S2Event.GLOBAL_SELECTED, selected); + + s2.emit(S2Event.DATA_CELL_CLICK, { + stopPropagation() {}, + originalEvent: { + detail: 1, + }, + } as unknown as GEvent); + + expect(selected).toHaveBeenCalledWith([]); + }); + test('should emit link field jump event when link field text click and not show tooltip', () => { const linkFieldJump = jest.fn(); @@ -172,4 +194,66 @@ describe('Interaction Data Cell Click Tests', () => { expect(s2.interaction.isHoverFocusState()).toBeFalsy(); expect(clearHoverTimerSpy).toHaveBeenCalledTimes(2); }); + + test('should highlight the column header cell and row header cell when data cell clicked', () => { + const headerCellId0 = 'header-0'; + const headerCellId1 = 'header-1'; + const columnNode: Node[] = [ + { + belongsCell: { + getMeta: () => ({ + id: headerCellId0, + colIndex: -1, + rowIndex: -1, + }), + } as any, + id: headerCellId0, + } as unknown as Node, + { + belongsCell: { + getMeta: () => ({ + id: headerCellId1, + colIndex: -1, + rowIndex: -1, + }), + } as any, + id: headerCellId1, + } as unknown as Node, + ]; + + s2.facet.getColNodes = jest.fn(() => columnNode) as any; + s2.facet.getRowNodes = jest.fn(() => []); + + const firstDataCellInfo = createMockCellInfo( + `${headerCellId0}[&]first-data-cell`, + ); + + s2.getCell = () => firstDataCellInfo.mockCell as any; + + s2.setOptions({ + interaction: { + selectedCellHighlight: { + colHeader: true, + rowHeader: true, + } as InteractionCellHighlightOptions, + }, + }); + + s2.interaction.updateCells = jest.fn(); + s2.facet.getColCells = jest.fn(); + s2.facet.getRowCells = jest.fn(); + + s2.emit(S2Event.DATA_CELL_CLICK, { + stopPropagation() {}, + } as unknown as GEvent); + + expect(s2.interaction.getState()).toEqual({ + cells: [firstDataCellInfo.mockCellMeta], + stateName: InteractionStateName.SELECTED, + onUpdateCells: expect.any(Function), + }); + expect(s2.interaction.updateCells).toHaveBeenCalled(); + expect(s2.facet.getColCells).toHaveBeenCalled(); + expect(s2.facet.getRowCells).toHaveBeenCalled(); + }); }); diff --git a/packages/s2-core/__tests__/unit/interaction/base-interaction/click/row-column-click-spec.ts b/packages/s2-core/__tests__/unit/interaction/base-interaction/click/row-column-click-spec.ts index b911a822af..52bcd2b273 100644 --- a/packages/s2-core/__tests__/unit/interaction/base-interaction/click/row-column-click-spec.ts +++ b/packages/s2-core/__tests__/unit/interaction/base-interaction/click/row-column-click-spec.ts @@ -9,7 +9,12 @@ import type { ViewMeta, } from '@/common/interface'; import type { SpreadSheet } from '@/sheet-type'; -import { InteractionStateName, S2Event } from '@/common/constant'; +import { + InteractionKeyboardKey, + InteractionStateName, + InterceptType, + S2Event, +} from '@/common/constant'; import type { Node } from '@/facet/layout/node'; import { CustomRect } from '@/engine'; @@ -85,6 +90,42 @@ describe('Interaction Row & Column Cell Click Tests', () => { expect(rowColumnClick.bindEvents).toBeDefined(); }); + test.each([InteractionKeyboardKey.META, InteractionKeyboardKey.CONTROL])( + 'should add click intercept when %s keydown', + (key) => { + s2.emit(S2Event.GLOBAL_KEYBOARD_DOWN, { + key, + } as KeyboardEvent); + + expect(s2.interaction.hasIntercepts([InterceptType.CLICK])).toBeTruthy(); + }, + ); + + test.each([InteractionKeyboardKey.META, InteractionKeyboardKey.CONTROL])( + 'should remove click intercept when %s keyup', + (key) => { + s2.interaction.addIntercepts([InterceptType.CLICK]); + s2.emit(S2Event.GLOBAL_KEYBOARD_UP, { + key, + } as KeyboardEvent); + + expect(s2.interaction.hasIntercepts([InterceptType.CLICK])).toBeFalsy(); + }, + ); + + test.each([InteractionKeyboardKey.META, InteractionKeyboardKey.CONTROL])( + 'should remove click intercept when %s released', + () => { + Object.defineProperty(rowColumnClick, 'isMultiSelection', { + value: true, + }); + s2.interaction.addIntercepts([InterceptType.CLICK]); + s2.emit(S2Event.GLOBAL_MOUSE_MOVE, {} as MouseEvent); + + expect(s2.interaction.hasIntercepts([InterceptType.CLICK])).toBeFalsy(); + }, + ); + // https://github.com/antvis/S2/issues/1243 test.each([S2Event.ROW_CELL_CLICK, S2Event.COL_CELL_CLICK])( 'should selected cell when %s cell clicked', @@ -108,6 +149,7 @@ describe('Interaction Row & Column Cell Click Tests', () => { }); expect(s2.showTooltipWithInfo).toHaveBeenCalled(); expect(selected).toHaveBeenCalled(); + expect(s2.interaction.hasIntercepts([InterceptType.HOVER])).toBeTrue(); isSelectedCellSpy.mockRestore(); }, @@ -139,6 +181,7 @@ describe('Interaction Row & Column Cell Click Tests', () => { expect(s2.interaction.getState().cells).toEqual([]); expect(s2.showTooltipWithInfo).toHaveBeenCalled(); expect(selected).toHaveBeenCalled(); + expect(s2.interaction.hasIntercepts([InterceptType.HOVER])).toBeFalse(); getInteractedCellsSpy.mockRestore(); }, diff --git a/packages/s2-core/__tests__/unit/interaction/base-interaction/hover-spec.ts b/packages/s2-core/__tests__/unit/interaction/base-interaction/hover-spec.ts index 42bfa5ec4d..53a5c36dc8 100644 --- a/packages/s2-core/__tests__/unit/interaction/base-interaction/hover-spec.ts +++ b/packages/s2-core/__tests__/unit/interaction/base-interaction/hover-spec.ts @@ -32,6 +32,7 @@ describe('Interaction Hover Tests', () => { const mockTooltipParams = [ [{ value: undefined, valueField: undefined }], { + enableFormat: true, hideSummary: true, isTotals: undefined, onlyShowCellText: true, @@ -77,7 +78,7 @@ describe('Interaction Hover Tests', () => { mockCellUpdate.mockReset(); }); - afterAll(() => { + afterEach(() => { mockCellUpdate.mockRestore(); }); @@ -86,6 +87,15 @@ describe('Interaction Hover Tests', () => { }); test('should trigger data cell hover', async () => { + const interactionGetHoverHighlightSpy = jest + .spyOn(s2.interaction, 'getHoverHighlight') + .mockImplementationOnce(() => ({ + rowHeader: true, + colHeader: true, + currentRow: true, + currentCol: true, + })); + s2.emit(S2Event.DATA_CELL_HOVER, { target: {} } as GEvent); expect(s2.interaction.getState()).toEqual({ cells: [mockCellMeta], @@ -99,6 +109,37 @@ describe('Interaction Hover Tests', () => { stateName: InteractionStateName.HOVER_FOCUS, }); expect(s2.showTooltipWithInfo).toHaveBeenCalled(); + expect(interactionGetHoverHighlightSpy).toHaveBeenCalled(); + }); + + test('should trigger data cell hover depend on separate config', async () => { + s2.facet.getColCells = jest.fn(); + s2.facet.getRowCells = jest.fn(); + + s2.setOptions({ + interaction: { + hoverHighlight: { + colHeader: true, + rowHeader: false, + }, + }, + }); + + s2.emit(S2Event.DATA_CELL_HOVER, { target: {} } as GEvent); + expect(s2.interaction.getState()).toEqual({ + cells: [mockCellMeta], + stateName: InteractionStateName.HOVER, + }); + + await sleep(1000); + + expect(s2.interaction.getState()).toEqual({ + cells: [mockCellMeta], + stateName: InteractionStateName.HOVER_FOCUS, + }); + + expect(s2.facet.getColCells).toHaveBeenCalled(); + expect(s2.facet.getRowCells).not.toHaveBeenCalled(); }); test('should not trigger data cell hover when hover cell not change', () => { @@ -283,6 +324,7 @@ describe('Interaction Hover Tests', () => { await sleep(HOVER_FOCUS_DURATION + 200); expect(s2.showTooltipWithInfo).toHaveBeenCalled(); + expect(s2.hideTooltip).toHaveBeenCalled(); }, ); diff --git a/packages/s2-core/__tests__/unit/interaction/brush-selection/base-brush-selection-spec.ts b/packages/s2-core/__tests__/unit/interaction/brush-selection/base-brush-selection-spec.ts index f1b5b92ee5..2ca03ad8d0 100644 --- a/packages/s2-core/__tests__/unit/interaction/brush-selection/base-brush-selection-spec.ts +++ b/packages/s2-core/__tests__/unit/interaction/brush-selection/base-brush-selection-spec.ts @@ -59,7 +59,15 @@ describe('Interaction Base Cell Brush Selection Tests', () => { s2 = new PivotSheet(getContainer(), null as unknown as S2DataConfig, null); await s2.render(); + mockRootInteraction = new MockRootInteraction(s2); + mockRootInteraction.getBrushSelection = () => { + return { + dataCell: true, + rowCell: true, + colCell: true, + }; + }; s2.getCell = jest.fn(() => startBrushDataCell) as any; s2.facet.foregroundGroup = new Group(); s2.showTooltipWithInfo = jest.fn(); diff --git a/packages/s2-core/__tests__/unit/interaction/brush-selection/col-brush-selection-spec.ts b/packages/s2-core/__tests__/unit/interaction/brush-selection/col-brush-selection-spec.ts index fae5b8fdfd..99b88feadd 100644 --- a/packages/s2-core/__tests__/unit/interaction/brush-selection/col-brush-selection-spec.ts +++ b/packages/s2-core/__tests__/unit/interaction/brush-selection/col-brush-selection-spec.ts @@ -68,11 +68,19 @@ describe('Interaction Col Cell Brush Selection Tests', () => { }, }, }); + await s2.render(); + s2.showTooltipWithInfo = jest.fn(); mockRootInteraction = new MockRootInteraction(s2); s2.getCell = jest.fn(() => startBrushColCell) as any; + mockRootInteraction.getBrushSelection = () => { + return { + dataCell: true, + rowCell: true, + colCell: true, + }; + }; s2.interaction = mockRootInteraction; - await s2.render(); brushSelectionInstance = new ColCellBrushSelection(s2); brushSelectionInstance.brushSelectionStage = @@ -203,4 +211,30 @@ describe('Interaction Col Cell Brush Selection Tests', () => { expect(selectedFn).toHaveBeenCalledTimes(1); expect(brushSelectionFn).toHaveBeenCalledTimes(1); }); + + test('should not emit brush secletion event', () => { + mockRootInteraction.getBrushSelection = () => ({ + dataCell: true, + rowCell: true, + colCell: false, + }); + + const brushSelectionFn = jest.fn(); + + s2.on(S2Event.COL_CELL_BRUSH_SELECTION, brushSelectionFn); + + // ================== mouse down ================== + emitEvent(S2Event.COL_CELL_MOUSE_DOWN, { x: 200, y: 0 }); + + // ================== mouse move ================== + emitEvent(S2Event.COL_CELL_MOUSE_MOVE, { + clientX: 600, + clientY: 90, + }); + + // ================== mouse up ================== + emitEvent(S2Event.GLOBAL_MOUSE_UP, {}); + // emit event + expect(brushSelectionFn).toHaveBeenCalledTimes(0); + }); }); diff --git a/packages/s2-core/__tests__/unit/interaction/brush-selection/data-brush-selection-spec.ts b/packages/s2-core/__tests__/unit/interaction/brush-selection/data-brush-selection-spec.ts index 4aed4c4dd2..e088960cf2 100644 --- a/packages/s2-core/__tests__/unit/interaction/brush-selection/data-brush-selection-spec.ts +++ b/packages/s2-core/__tests__/unit/interaction/brush-selection/data-brush-selection-spec.ts @@ -115,6 +115,8 @@ describe('Interaction Data Cell Brush Selection Tests', () => { null as unknown as S2DataConfig, null as unknown as S2Options, ); + await s2.render(); + mockRootInteraction = new MockRootInteraction(s2); s2.getCell = jest.fn(() => startBrushDataCell) as any; s2.showTooltipWithInfo = jest.fn(); @@ -126,8 +128,14 @@ describe('Interaction Data Cell Brush Selection Tests', () => { currentCol: false, }; }; + mockRootInteraction.getBrushSelection = () => { + return { + dataCell: true, + rowCell: true, + colCell: true, + }; + }; s2.interaction = mockRootInteraction; - await s2.render(); s2.facet.getDataCells = () => panelGroupAllDataCells; s2.facet.getLayoutResult = () => ({ @@ -462,15 +470,23 @@ describe('Interaction Data Cell Brush Selection Tests', () => { (facet as TableFacet).frozenGroupInfo = { [FrozenGroupType.FROZEN_COL]: { width: 100, + x: 0, + range: [], }, [FrozenGroupType.FROZEN_TRAILING_COL]: { width: 100, + x: 0, + range: [], }, [FrozenGroupType.FROZEN_ROW]: { height: 0, + y: 0, + range: [], }, [FrozenGroupType.FROZEN_TRAILING_ROW]: { height: 0, + y: 0, + range: [], }, }; @@ -488,16 +504,24 @@ describe('Interaction Data Cell Brush Selection Tests', () => { (facet as TableFacet).frozenGroupInfo = { [FrozenGroupType.FROZEN_COL]: { + x: 0, width: 0, + range: [], }, [FrozenGroupType.FROZEN_TRAILING_COL]: { + x: 0, width: 0, + range: [], }, [FrozenGroupType.FROZEN_ROW]: { + y: 0, + range: [], height: 100, }, [FrozenGroupType.FROZEN_TRAILING_ROW]: { height: 100, + y: 0, + range: [], }, }; expect(getScrollOffsetForRow(7, ScrollDirection.SCROLL_UP, s2)).toBe(600); @@ -519,15 +543,23 @@ describe('Interaction Data Cell Brush Selection Tests', () => { (s2.facet as TableFacet).frozenGroupInfo = { [FrozenGroupType.FROZEN_COL]: { + width: 0, + x: 0, range: [0, 1], }, [FrozenGroupType.FROZEN_TRAILING_COL]: { + width: 0, + x: 0, range: [8, 9], }, [FrozenGroupType.FROZEN_ROW]: { + y: 0, + height: 0, range: [0, 1], }, [FrozenGroupType.FROZEN_TRAILING_ROW]: { + y: 0, + height: 0, range: [8, 9], }, }; @@ -541,4 +573,30 @@ describe('Interaction Data Cell Brush Selection Tests', () => { expect(validateXIndex(8)).toBe(null); expect(validateXIndex(7)).toBe(7); }); + + test('should not emit brush selection event', () => { + mockRootInteraction.getBrushSelection = () => ({ + dataCell: false, + rowCell: true, + colCell: true, + }); + + const brushSelectionFn = jest.fn(); + + s2.on(S2Event.DATA_CELL_BRUSH_SELECTION, brushSelectionFn); + + // ================== mouse down ================== + emitEvent(S2Event.DATA_CELL_MOUSE_DOWN, { x: 10, y: 20 }); + + // ================== mouse move ================== + emitGlobalEvent(S2Event.GLOBAL_MOUSE_MOVE, { + clientX: 100, + clientY: 200, + }); + + // ================== mouse up ================== + emitEvent(S2Event.GLOBAL_MOUSE_UP, {}); + // emit event + expect(brushSelectionFn).toHaveBeenCalledTimes(0); + }); }); diff --git a/packages/s2-core/__tests__/unit/interaction/brush-selection/row-brush-selection-spec.ts b/packages/s2-core/__tests__/unit/interaction/brush-selection/row-brush-selection-spec.ts index 797ba740a1..d36a16a44b 100644 --- a/packages/s2-core/__tests__/unit/interaction/brush-selection/row-brush-selection-spec.ts +++ b/packages/s2-core/__tests__/unit/interaction/brush-selection/row-brush-selection-spec.ts @@ -68,11 +68,19 @@ describe('Interaction Row Cell Brush Selection Tests', () => { }, }); + await s2.render(); + s2.showTooltipWithInfo = jest.fn(); s2.getCell = jest.fn(() => startBrushRowCell) as any; mockRootInteraction = new MockRootInteraction(s2); + mockRootInteraction.getBrushSelection = () => { + return { + dataCell: true, + rowCell: true, + colCell: true, + }; + }; s2.interaction = mockRootInteraction; - await s2.render(); brushSelectionInstance = new RowCellBrushSelection(s2); brushSelectionInstance.brushSelectionStage = @@ -250,4 +258,28 @@ describe('Interaction Row Cell Brush Selection Tests', () => { // get brush range selected cells expect(brushSelectionInstance.brushRangeCells.length).toEqual(8); }); + + test('should not emit brush secletion event', () => { + mockRootInteraction.getBrushSelection = () => ({ + dataCell: true, + rowCell: false, + colCell: true, + }); + + const brushSelectionFn = jest.fn(); + + s2.on(S2Event.ROW_CELL_BRUSH_SELECTION, brushSelectionFn); + + // ================== mouse down ================== + emitEvent(S2Event.ROW_CELL_MOUSE_DOWN, { x: 10, y: 90 }); + + s2.getCell = jest.fn(() => endBrushRowCell) as any; + // ================== mouse move ================== + emitEvent(S2Event.GLOBAL_MOUSE_MOVE, { clientX: 180, clientY: 400 }); + + // ================== mouse up ================== + emitEvent(S2Event.GLOBAL_MOUSE_UP, {}); + // emit event + expect(brushSelectionFn).toHaveBeenCalledTimes(0); + }); }); diff --git a/packages/s2-core/__tests__/unit/interaction/data-cell-multi-selection-spec.ts b/packages/s2-core/__tests__/unit/interaction/data-cell-multi-selection-spec.ts index b546e9ca74..31ce8676bf 100644 --- a/packages/s2-core/__tests__/unit/interaction/data-cell-multi-selection-spec.ts +++ b/packages/s2-core/__tests__/unit/interaction/data-cell-multi-selection-spec.ts @@ -40,7 +40,7 @@ describe('Interaction Data Cell Multi Selection Tests', () => { }); test.each([InteractionKeyboardKey.META, InteractionKeyboardKey.CONTROL])( - 'should add click intercept when keydown', + 'should add click intercept when %s keydown', (key) => { s2.emit(S2Event.GLOBAL_KEYBOARD_DOWN, { key, @@ -51,8 +51,9 @@ describe('Interaction Data Cell Multi Selection Tests', () => { ); test.each([InteractionKeyboardKey.META, InteractionKeyboardKey.CONTROL])( - 'should remove click intercept when keyup', + 'should remove click intercept when %s keyup', (key) => { + s2.interaction.addIntercepts([InterceptType.CLICK]); s2.emit(S2Event.GLOBAL_KEYBOARD_UP, { key, } as KeyboardEvent); @@ -61,6 +62,19 @@ describe('Interaction Data Cell Multi Selection Tests', () => { }, ); + test.each([InteractionKeyboardKey.META, InteractionKeyboardKey.CONTROL])( + 'should remove click intercept when %s released', + () => { + Object.defineProperty(dataCellMultiSelection, 'isMultiSelection', { + value: true, + }); + s2.interaction.addIntercepts([InterceptType.CLICK]); + s2.emit(S2Event.GLOBAL_MOUSE_MOVE, {} as MouseEvent); + + expect(s2.interaction.hasIntercepts([InterceptType.CLICK])).toBeFalsy(); + }, + ); + test.each([InteractionKeyboardKey.META, InteractionKeyboardKey.CONTROL])( 'should select multiple data cell', (key) => { diff --git a/packages/s2-core/__tests__/unit/interaction/event-controller-spec.ts b/packages/s2-core/__tests__/unit/interaction/event-controller-spec.ts index ba67172c53..0eb3b690ac 100644 --- a/packages/s2-core/__tests__/unit/interaction/event-controller-spec.ts +++ b/packages/s2-core/__tests__/unit/interaction/event-controller-spec.ts @@ -125,12 +125,11 @@ describe('Interaction Event Controller Tests', () => { renderer: new Renderer() as unknown as CanvasConfig['renderer'], }); spreadsheet.facet = { + ...spreadsheet.facet, panelBBox: { maxX: s2Options.width, maxY: s2Options.height, } as BBox, - getDataCells: jest.fn(), - getCells: jest.fn(), } as unknown as BaseFacet; spreadsheet.interaction = new RootInteraction( spreadsheet as unknown as SpreadSheet, @@ -549,14 +548,18 @@ describe('Interaction Event Controller Tests', () => { test('should reset if current mouse inside the canvas container, but outside the panel facet', () => { spreadsheet.facet = { + ...spreadsheet.facet, panelBBox: { maxX: 100, maxY: 100, } as BBox, } as BaseFacet; + + const selected = jest.fn(); const reset = jest.fn(); spreadsheet.on(S2Event.GLOBAL_RESET, reset); + spreadsheet.on(S2Event.GLOBAL_SELECTED, selected); const pointInCanvas = spreadsheet.container.viewport2Client({ x: 120, @@ -571,25 +574,31 @@ describe('Interaction Event Controller Tests', () => { } as MouseEventInit), ); + expect(selected).toHaveBeenCalledWith([]); expect(reset).toHaveBeenCalled(); expect(spreadsheet.interaction.reset).toHaveBeenCalled(); }); test('should reset if press ecs', () => { spreadsheet.facet = { + ...spreadsheet.facet, panelBBox: { maxX: 100, maxY: 100, } as BBox, } as BaseFacet; + + const selected = jest.fn(); const reset = jest.fn(); spreadsheet.on(S2Event.GLOBAL_RESET, reset); + spreadsheet.on(S2Event.GLOBAL_SELECTED, selected); window.dispatchEvent( new KeyboardEvent('keydown', { key: InteractionKeyboardKey.ESC }), ); + expect(selected).toHaveBeenCalledWith([]); expect(reset).toHaveBeenCalled(); expect(spreadsheet.interaction.reset).toHaveBeenCalled(); }); @@ -664,6 +673,7 @@ describe('Interaction Event Controller Tests', () => { test('should disable reset if autoResetSheetStyle set to false', () => { spreadsheet.facet = { + ...spreadsheet.facet, panelBBox: { maxX: 100, maxY: 100, diff --git a/packages/s2-core/__tests__/unit/interaction/range-selection-spec.ts b/packages/s2-core/__tests__/unit/interaction/range-selection-spec.ts index 89ad7f4935..20da43616f 100644 --- a/packages/s2-core/__tests__/unit/interaction/range-selection-spec.ts +++ b/packages/s2-core/__tests__/unit/interaction/range-selection-spec.ts @@ -10,10 +10,12 @@ import { S2Event, } from '@/common/constant'; import { RangeSelection } from '@/interaction/range-selection'; +import { getCellMeta } from '@/utils'; jest.mock('@/utils/tooltip'); jest.mock('@/interaction/event-controller'); jest.mock('@/interaction/base-interaction/click/data-cell-click'); +jest.mock('@/interaction/base-interaction/click/row-column-click'); describe('Interaction Range Selection Tests', () => { let rangeSelection: RangeSelection; @@ -47,6 +49,7 @@ describe('Interaction Range Selection Tests', () => { }); test('should remove click intercept when shift keyup', () => { + s2.interaction.addIntercepts([InterceptType.CLICK]); s2.emit(S2Event.GLOBAL_KEYBOARD_UP, { key: InteractionKeyboardKey.SHIFT, } as KeyboardEvent); @@ -54,6 +57,16 @@ describe('Interaction Range Selection Tests', () => { expect(s2.interaction.hasIntercepts([InterceptType.CLICK])).toBeFalsy(); }); + test('should remove click intercept when shift released', () => { + Object.defineProperty(rangeSelection, 'isRangeSelection', { + value: true, + }); + s2.interaction.addIntercepts([InterceptType.CLICK]); + s2.emit(S2Event.GLOBAL_MOUSE_MOVE, {} as MouseEvent); + + expect(s2.interaction.hasIntercepts([InterceptType.CLICK])).toBeFalsy(); + }); + test('should set last clicked cell', () => { s2.interaction.changeState({ cells: [], @@ -70,6 +83,28 @@ describe('Interaction Range Selection Tests', () => { expect(s2.store.get('lastClickedCell')).toEqual(mockCell00.mockCell); }); + test('should remove hover intercepts when col cell unselected', () => { + const mockCell00 = createMockCellInfo('3-3', { rowIndex: 3, colIndex: 3 }); + + s2.getCell = () => mockCell00.mockCell as any; + + s2.interaction.addIntercepts([InterceptType.HOVER]); + + // 有选中时,不应清理 hover 拦截 + s2.interaction.getCells = () => [getCellMeta(mockCell00.mockCell)]; + s2.emit(S2Event.COL_CELL_CLICK, { + stopPropagation() {}, + } as unknown as GEvent); + expect(s2.interaction.hasIntercepts([InterceptType.HOVER])).toBeTrue(); + + // 无选中时,应清理拦截 + s2.interaction.getCells = () => []; + s2.emit(S2Event.COL_CELL_CLICK, { + stopPropagation() {}, + } as unknown as GEvent); + expect(s2.interaction.hasIntercepts([InterceptType.HOVER])).toBeFalse(); + }); + // should use data cell click interaction for single cell select test('should not hide tooltip and change single data cell state', () => { s2.store.set('lastClickedCell', null); diff --git a/packages/s2-core/__tests__/unit/interaction/root-spec.ts b/packages/s2-core/__tests__/unit/interaction/root-spec.ts index e35b3123e0..683b673589 100644 --- a/packages/s2-core/__tests__/unit/interaction/root-spec.ts +++ b/packages/s2-core/__tests__/unit/interaction/root-spec.ts @@ -1,13 +1,15 @@ import type { Canvas } from '@antv/g'; -import { createMockCellInfo, sleep } from 'tests/util/helpers'; import { get } from 'lodash'; +import { createMockCellInfo, sleep } from 'tests/util/helpers'; import type { PivotFacet } from '../../../src/facet'; import { Store } from '@/common/store'; import { BaseEvent, CellType, + ColCellBrushSelection, CornerCellClick, DataCell, + DataCellBrushSelection, DataCellClick, DataCellMultiSelection, GuiIcon, @@ -17,17 +19,15 @@ import { InterceptType, MergedCell, MergedCellClick, + Node, RangeSelection, + RowCellBrushSelection, RowColumnClick, RowColumnResize, RowTextClick, - type S2Options, SelectedCellMove, SpreadSheet, - Node, - DataCellBrushSelection, - ColCellBrushSelection, - RowCellBrushSelection, + type S2Options, } from '@/index'; import { RootInteraction } from '@/interaction/root'; import { mergeCell, unmergeCell } from '@/utils/interaction/merge-cell'; @@ -68,7 +68,7 @@ describe('RootInteraction Tests', () => { }, }) as unknown as DataCell; - beforeAll(() => { + beforeEach(() => { MockSpreadSheet.mockClear(); panelGroupAllDataCells = Array.from<DataCell>({ length: 10 }).map( (_, idx) => getMockCell(idx), @@ -100,6 +100,10 @@ describe('RootInteraction Tests', () => { mockSpreadSheetInstance.interaction = rootInteraction; }); + afterEach(() => { + rootInteraction.destroy(); + }); + test('should get default interaction state', () => { expect(rootInteraction.getState()).toEqual({ cells: [], @@ -174,9 +178,9 @@ describe('RootInteraction Tests', () => { // https://github.com/antvis/S2/issues/1243 test('should multi selected header cells', () => { - const isEqualStateNameSpy = jest + jest .spyOn(rootInteraction, 'isEqualStateName') - .mockImplementation(() => false); + .mockImplementationOnce(() => false); const mockCellA = createMockCellInfo('test-A').mockCell; const mockCellB = createMockCellInfo('test-B').mockCell; @@ -187,10 +191,7 @@ describe('RootInteraction Tests', () => { isMultiSelection: true, }); - expect(rootInteraction.getState().cells).toEqual([ - getCellMeta(mockCell), - getCellMeta(mockCellA), - ]); + expect(rootInteraction.getState().cells).toEqual([getCellMeta(mockCellA)]); // 选中 cellB rootInteraction.selectHeaderCell({ @@ -199,7 +200,6 @@ describe('RootInteraction Tests', () => { }); expect(rootInteraction.getState().cells).toEqual([ - getCellMeta(mockCell), getCellMeta(mockCellA), getCellMeta(mockCellB), ]); @@ -211,12 +211,7 @@ describe('RootInteraction Tests', () => { }); // 取消选中 - expect(rootInteraction.getState().cells).toEqual([ - getCellMeta(mockCell), - getCellMeta(mockCellA), - ]); - - isEqualStateNameSpy.mockRestore(); + expect(rootInteraction.getState().cells).toEqual([getCellMeta(mockCellA)]); }); test('should call merge cells', () => { @@ -515,7 +510,9 @@ describe('RootInteraction Tests', () => { InterceptType.CLICK, ]), ).toBeTruthy(); + rootInteraction.removeIntercepts([InterceptType.CLICK]); + expect(rootInteraction.hasIntercepts([InterceptType.CLICK])).toBeFalsy(); expect(rootInteraction.hasIntercepts([InterceptType.HOVER])).toBeTruthy(); }); @@ -671,16 +668,20 @@ describe('RootInteraction Tests', () => { }); }); - test('should reset interaction when visibilitychange', () => { - rootInteraction = new RootInteraction(mockSpreadSheetInstance); - mockSpreadSheetInstance.interaction = rootInteraction; - rootInteraction.interactions.forEach((interaction) => { - interaction.reset = jest.fn(); - }); + // eslint-disable-next-line jest/no-disabled-tests + test.skip('should reset interaction when visibilitychange', () => { + const resetSpyList = [...rootInteraction.interactions.values()].map( + (interaction) => { + return jest + .spyOn(interaction, 'reset') + .mockImplementationOnce(() => {}); + }, + ); + window.dispatchEvent(new Event('visibilitychange')); - rootInteraction.interactions.forEach((interaction) => { - expect(interaction.reset).toHaveBeenCalled(); + resetSpyList.forEach((resetSpy) => { + expect(resetSpy).toHaveBeenCalledTimes(1); }); }); }); diff --git a/packages/s2-core/__tests__/unit/interaction/row-column-resize-spec.ts b/packages/s2-core/__tests__/unit/interaction/row-column-resize-spec.ts index 26b133a534..8953d35524 100644 --- a/packages/s2-core/__tests__/unit/interaction/row-column-resize-spec.ts +++ b/packages/s2-core/__tests__/unit/interaction/row-column-resize-spec.ts @@ -226,6 +226,8 @@ describe('Interaction Row Column Resize Tests', () => { { offsetX: 10, offsetY: 20, + clientX: 10, + clientY: 20, }, resizeInfo, ); @@ -233,6 +235,7 @@ describe('Interaction Row Column Resize Tests', () => { expect(s2.store.get('resized')).toBeFalsy(); expect(rowColumnResizeInstance.resizeStartPosition).toStrictEqual({ offsetX: 10, + clientX: 10, }); expect(getStartGuideLine().attr('path')).toStrictEqual([ ['M', 3.5, 2], @@ -356,12 +359,15 @@ describe('Interaction Row Column Resize Tests', () => { { offsetX: 10, offsetY: 20, + clientX: 10, + clientY: 20, }, resizeInfo, ); expect(rowColumnResizeInstance.resizeStartPosition).toStrictEqual({ offsetY: 20, + clientY: 20, }); expect(getStartGuideLine().attr('path')).toStrictEqual([ ['M', 2, 3.5], @@ -750,6 +756,8 @@ describe('Interaction Row Column Resize Tests', () => { { offsetX: 10, offsetY: 20, + clientX: 10, + clientY: 20, }, resizeInfo, ); @@ -759,6 +767,8 @@ describe('Interaction Row Column Resize Tests', () => { { offsetX: 20, offsetY: 20, + clientX: 20, + clientY: 20, }, resizeInfo, ); diff --git a/packages/s2-core/__tests__/unit/sheet-type/pivot-sheet-spec.ts b/packages/s2-core/__tests__/unit/sheet-type/pivot-sheet-spec.ts index 35f4efca83..1a62ea9e86 100644 --- a/packages/s2-core/__tests__/unit/sheet-type/pivot-sheet-spec.ts +++ b/packages/s2-core/__tests__/unit/sheet-type/pivot-sheet-spec.ts @@ -1,6 +1,6 @@ // eslint-disable-next-line max-classes-per-file import { Canvas, CanvasEvent } from '@antv/g'; -import { cloneDeep, get, last } from 'lodash'; +import { cloneDeep, last } from 'lodash'; import dataCfg from 'tests/data/simple-data.json'; import { waitForRender } from 'tests/util'; import { createPivotSheet, getContainer, sleep } from 'tests/util/helpers'; @@ -33,7 +33,6 @@ import { type S2Options, type TooltipShowOptions, } from '@/common'; -import type { CornerCell } from '@/cell/corner-cell'; jest.mock('@/utils/hide-columns'); @@ -67,14 +66,15 @@ describe('PivotSheet Tests', () => { let container: HTMLDivElement; - beforeAll(async () => { + beforeEach(async () => { setLang('zh_CN'); + container = getContainer(); s2 = new PivotSheet(container, dataCfg, s2Options); await s2.render(); }); - afterAll(() => { + afterEach(() => { container?.remove(); s2?.destroy(); }); @@ -130,7 +130,7 @@ describe('PivotSheet Tests', () => { s2.tooltip.destroy(); // remove container - expect(s2.tooltip.container).toBe(null); + expect(s2.tooltip.container).toBeFalsy(); // reset position expect(s2.tooltip.position).toEqual({ x: 0, @@ -478,6 +478,53 @@ describe('PivotSheet Tests', () => { expect(s2.options.showSeriesNumber).toBeTruthy(); }); + test('should init new tooltip', () => { + const tooltipDestroySpy = jest + .spyOn(s2.tooltip, 'destroy') + .mockImplementationOnce(() => {}); + + class CustomTooltip extends BaseTooltip {} + + s2.setOptions({ + tooltip: { + render: (spreadsheet) => new CustomTooltip(spreadsheet), + }, + }); + + expect(tooltipDestroySpy).toHaveBeenCalled(); + expect(s2.tooltip).toBeInstanceOf(CustomTooltip); + }); + + test('should refresh brush selection info', () => { + s2.setOptions({ + interaction: { + brushSelection: true, + }, + }); + + expect(s2.interaction.getBrushSelection()).toStrictEqual({ + dataCell: true, + rowCell: true, + colCell: true, + }); + + s2.setOptions({ + interaction: { + brushSelection: { + dataCell: true, + rowCell: false, + colCell: false, + }, + }, + }); + + expect(s2.interaction.getBrushSelection()).toStrictEqual({ + dataCell: true, + rowCell: false, + colCell: false, + }); + }); + test('should render sheet', async () => { const facetRenderSpy = jest .spyOn(s2, 'buildFacet' as any) @@ -610,7 +657,7 @@ describe('PivotSheet Tests', () => { expect(s2.facet.foregroundGroup.children).toHaveLength(9); // panel scroll group - expect(s2.facet.panelGroup.children).toHaveLength(1); + expect(s2.facet.panelGroup.children).toHaveLength(7); expect( s2.facet.panelGroup.getElementsByName(KEY_GROUP_PANEL_SCROLL), ).toHaveLength(1); @@ -726,7 +773,7 @@ describe('PivotSheet Tests', () => { const clearDrillDownDataSpy = jest .spyOn(s2.dataSet, 'clearDrillDownData' as any) - .mockImplementation(() => {}); + .mockImplementation(() => true); s2.clearDrillDownData(); @@ -742,6 +789,22 @@ describe('PivotSheet Tests', () => { renderSpy.mockRestore(); }); + test(`shouldn't rerender without drill down data`, () => { + const renderSpy = jest + .spyOn(s2, 'render') + .mockImplementationOnce(() => Promise.resolve()); + + const clearDrillDownDataSpy = jest + .spyOn(s2.dataSet, 'clearDrillDownData' as any) + .mockImplementation(() => false); + + s2.clearDrillDownData(); + + expect(clearDrillDownDataSpy).toHaveBeenCalledTimes(1); + // rerender + expect(renderSpy).toHaveBeenCalledTimes(0); + }); + test('should get extra field text', async () => { const pivotSheet = new PivotSheet( container, @@ -754,11 +817,9 @@ describe('PivotSheet Tests', () => { ); await pivotSheet.render(); - const extraField = last( - pivotSheet.facet.cornerHeader.children, - ) as CornerCell; + const extraField = last(pivotSheet.facet.getCornerCells()); - expect(get(extraField, 'actualText')).toEqual('数值'); + expect(extraField?.getActualText()).toEqual('数值'); }); // https://github.com/antvis/S2/issues/1212 @@ -780,11 +841,9 @@ describe('PivotSheet Tests', () => { await pivotSheet.render(); - const extraField = last( - pivotSheet.facet.cornerHeader.children, - ) as CornerCell; + const extraField = last(pivotSheet.facet.getCornerCells()); - expect(get(extraField, 'actualText')).toEqual(cornerExtraFieldText); + expect(extraField?.getActualText()).toEqual(cornerExtraFieldText); }); describe('Tree Collapse Tests', () => { @@ -994,7 +1053,7 @@ describe('PivotSheet Tests', () => { { query: undefined, sortByMeasure: nodeMeta.value, - sortFieldId: 'field', + sortFieldId: 'city', sortMethod: 'asc', }, ]); @@ -1010,7 +1069,7 @@ describe('PivotSheet Tests', () => { { query: undefined, sortByMeasure: nodeMeta.value, - sortFieldId: 'field', + sortFieldId: 'city', sortMethod: 'desc', }, ]); @@ -1042,7 +1101,7 @@ describe('PivotSheet Tests', () => { { query: { $$extra$$: 'price', type: '笔' }, sortByMeasure: 'price', - sortFieldId: 'field', + sortFieldId: 'city', sortMethod: 'asc', }, ]); @@ -1063,7 +1122,11 @@ describe('PivotSheet Tests', () => { s2.store.set('test', 111); // restore mock... - (s2.tooltip.show as jest.Mock).mockRestore(); + const tooltipShowSpy = jest + .spyOn(s2.tooltip, 'show') + .mockImplementationOnce(() => {}); + + tooltipShowSpy.mockRestore(); s2.showTooltip({ position: { x: 10, diff --git a/packages/s2-core/__tests__/unit/sheet-type/table-sheet-spec.ts b/packages/s2-core/__tests__/unit/sheet-type/table-sheet-spec.ts index ed8e336f0b..f3ca32b9dc 100644 --- a/packages/s2-core/__tests__/unit/sheet-type/table-sheet-spec.ts +++ b/packages/s2-core/__tests__/unit/sheet-type/table-sheet-spec.ts @@ -23,16 +23,16 @@ describe('TableSheet Tests', () => { let container: HTMLDivElement; - beforeAll(async () => { + beforeEach(async () => { container = getContainer(); s2 = new TableSheet(container, dataCfg, s2Options); await s2.render(); s2.store.set('sortMethodMap', null); }); - afterAll(() => { - container?.remove(); - s2?.destroy(); + afterEach(() => { + // container?.remove(); + // s2?.destroy(); }); describe('TableSheet Sort Tests', () => { @@ -95,15 +95,10 @@ describe('TableSheet Tests', () => { s2.groupSortByMethod('desc', node); expect(s2.store.get('sortMethodMap')).toEqual({ - city: 'asc', cost: 'desc', }); expect(s2.getMenuDefaultSelectedKeys(node.id)).toEqual(['desc']); expect(s2.dataCfg.sortParams).toEqual([ - { - sortFieldId: 'city', - sortMethod: 'asc', - }, { sortFieldId: 'cost', sortMethod: 'desc', @@ -267,4 +262,8 @@ describe('TableSheet Tests', () => { expect(sheet.facet).toBeInstanceOf(TableFacet); expect(mockRender).toHaveBeenCalledTimes(1); }); + + test('should get content height', () => { + expect(s2.getContentHeight()).toEqual(120); + }); }); diff --git a/packages/s2-core/__tests__/unit/utils/__snapshots__/sort-action-spec.tsx.snap b/packages/s2-core/__tests__/unit/utils/__snapshots__/sort-action-spec.tsx.snap new file mode 100644 index 0000000000..831f4bd7ae --- /dev/null +++ b/packages/s2-core/__tests__/unit/utils/__snapshots__/sort-action-spec.tsx.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`total group dimension sort test should sort by col total with group 1`] = ` +Array [ + CellData { + "extraField": "price", + "raw": Object { + "city": "杭州", + "price": "666", + "type": "笔", + }, + }, + CellData { + "extraField": "price", + "raw": Object { + "city": "杭州", + "price": "999", + "type": "纸张", + }, + }, +] +`; diff --git a/packages/s2-core/__tests__/unit/utils/data-set-operate-spec.tsx b/packages/s2-core/__tests__/unit/utils/data-set-operate-spec.tsx index 5fd2938768..70efb9d846 100644 --- a/packages/s2-core/__tests__/unit/utils/data-set-operate-spec.tsx +++ b/packages/s2-core/__tests__/unit/utils/data-set-operate-spec.tsx @@ -1,11 +1,11 @@ import { set } from 'lodash'; +import { flattenIndexesData } from '../../../src/utils/dataset/pivot-data-set'; +import { QueryDataType, type FlattingIndexesData } from '../../../src'; +import { Aggregation } from '@/common/interface'; import { - getListBySorted, getAggregationAndCalcFuncByQuery, - flattenIndexesData, + getListBySorted, } from '@/utils/data-set-operate'; -import { Aggregation, type FlattingIndexesData } from '@/common/interface'; -import { DataSelectType } from '@/common/constant/total'; describe('Data Set Operate Test', () => { const data: FlattingIndexesData = []; @@ -27,18 +27,12 @@ describe('Data Set Operate Test', () => { }); test('flatten out all data with all select type', () => { - expect(flattenIndexesData(data, DataSelectType.All)).toBeArrayOfSize(6); - }); - - test('flatten out total data with total only type', () => { - expect( - flattenIndexesData(data, DataSelectType.TotalOnly), - ).toBeArrayOfSize(2); + expect(flattenIndexesData(data, QueryDataType.All)).toBeArrayOfSize(6); }); test('flatten out detail data with detail only type', () => { expect( - flattenIndexesData(data, DataSelectType.DetailOnly), + flattenIndexesData(data, QueryDataType.DetailOnly), ).toBeArrayOfSize(4); }); }); diff --git a/packages/s2-core/__tests__/unit/utils/dataset/pivot-data-set-spec.ts b/packages/s2-core/__tests__/unit/utils/dataset/pivot-data-set-spec.ts index 029c730360..418a6753cf 100644 --- a/packages/s2-core/__tests__/unit/utils/dataset/pivot-data-set-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/dataset/pivot-data-set-spec.ts @@ -1,212 +1,643 @@ -import { assembleDataCfg } from 'tests/util'; -import { get } from 'lodash'; -import { data } from 'tests/data/mock-dataset.json'; +import { MULTI_VALUE } from '@/common/constant/field'; +import type { PivotMeta, SortedDimensionValues } from '@/data-set/interface'; import { - deleteMetaById, - transformIndexesData, - transformDimensionsValues, - getDataPath, - getDimensionsWithoutPathPre, - getDimensionsWithParentPath, + existDimensionTotalGroup, + flattenDimensionValues, } from '@/utils/dataset/pivot-data-set'; -import type { S2DataConfig } from '@/common/interface'; -import { CellData } from '@/data-set/cell-data'; -describe('PivotDataSet util test', () => { - const dataCfg: S2DataConfig = assembleDataCfg({ - data, - meta: [], - }); +describe('pivot-data-set utils test', () => { + let fields: string[]; + let sortedDimensionValues: SortedDimensionValues; + let pivotMeta: PivotMeta; + + beforeEach(() => { + fields = ['province', 'city', 'type', 'subType']; - test('for deleteMetaById function', () => { - const childrenMeta = { - level: 0, - children: new Map(), - childField: 'country', + sortedDimensionValues = { + province: ['浙江省', '四川省', '$$total$$'], + city: [ + '$$total$$[&]$$total$$', + '四川省[&]$$total$$', + '四川省[&]成[&]都[&]市', + '四川省[&]绵阳市', + '浙江省[&]$$total$$', + '浙江省[&]杭州市', + '浙江省[&]舟山市', + ], + type: [ + '$$total$$[&]$$total$$[&]$$total$$', + '四川省[&]$$total$$[&]$$total$$', + '四川省[&]成[&]都[&]市[&]$$total$$', + '四川省[&]成[&]都[&]市[&]家具', + '四川省[&]成[&]都[&]市[&]办公用品', + '四川省[&]绵阳市[&]$$total$$', + '四川省[&]绵阳市[&]家具', + '四川省[&]绵阳市[&]办公用品', + '浙江省[&]$$total$$[&]$$total$$', + '浙江省[&]杭州市[&]$$total$$', + '浙江省[&]杭州市[&]家具', + '浙江省[&]杭州市[&]办公用品', + '浙江省[&]舟山市[&]$$total$$', + '浙江省[&]舟山市[&]家具', + '浙江省[&]舟山市[&]办公用品', + ], + + subType: [ + '$$total$$[&]$$total$$[&]$$total$$[&]$$total$$', + '四川省[&]$$total$$[&]$$total$$[&]$$total$$', + '四川省[&]成[&]都[&]市[&]$$total$$[&]$$total$$', + '四川省[&]成[&]都[&]市[&]家具[&]$$total$$', + '四川省[&]成[&]都[&]市[&]家具[&]桌子', + '四川省[&]成[&]都[&]市[&]家具[&]沙发', + '四川省[&]成[&]都[&]市[&]办公用品[&]$$total$$', + '四川省[&]成[&]都[&]市[&]办公用品[&]笔', + '四川省[&]成[&]都[&]市[&]办公用品[&]纸张', + '四川省[&]绵阳市[&]$$total$$[&]$$total$$', + '四川省[&]绵阳市[&]家具[&]$$total$$', + '四川省[&]绵阳市[&]家具[&]桌子', + '四川省[&]绵阳市[&]家具[&]沙发', + '四川省[&]绵阳市[&]办公用品[&]$$total$$', + '四川省[&]绵阳市[&]办公用品[&]笔', + '四川省[&]绵阳市[&]办公用品[&]纸张', + '浙江省[&]$$total$$[&]$$total$$[&]$$total$$', + '浙江省[&]杭州市[&]$$total$$[&]$$total$$', + '浙江省[&]杭州市[&]家具[&]$$total$$', + '浙江省[&]杭州市[&]家具[&]桌子', + '浙江省[&]杭州市[&]家具[&]沙发', + '浙江省[&]杭州市[&]办公用品[&]$$total$$', + '浙江省[&]杭州市[&]办公用品[&]笔', + '浙江省[&]杭州市[&]办公用品[&]纸张', + '浙江省[&]舟山市[&]$$total$$[&]$$total$$', + '浙江省[&]舟山市[&]家具[&]$$total$$', + '浙江省[&]舟山市[&]家具[&]桌子', + '浙江省[&]舟山市[&]家具[&]沙发', + '浙江省[&]舟山市[&]办公用品[&]$$total$$', + '浙江省[&]舟山市[&]办公用品[&]笔', + '浙江省[&]舟山市[&]办公用品[&]纸张', + ], }; - const meta = new Map().set('浙江省', { - level: 0, - children: new Map().set('杭州市', childrenMeta), - childField: 'city', - }); - deleteMetaById(meta, 'root[&]浙江省'); - const result = meta.get('浙江省'); + pivotMeta = new Map([ + [ + '四川省', + { + childField: 'city', + level: 1, + id: '四川省', + dimensions: ['四川省'], + children: new Map([ + [ + '成[&]都[&]市', + { + childField: 'type', + level: 1, + id: '四川省[&]成[&]都[&]市', + dimensions: ['四川省', '成[&]都[&]市'], + children: new Map([ + [ + '家具', + { + childField: 'subType', + level: 1, + id: '四川省[&]成[&]都[&]市[&]家具', + dimensions: ['四川省', '成[&]都[&]市', '家具'], + children: new Map([ + [ + '桌子', + { + childFiled: null, + id: '四川省[&]成[&]都[&]市[&]家具[&]桌子', + dimensions: [ + '四川省', + '成[&]都[&]市', + '家具', + '桌子', + ], - expect(result.childField).toBeUndefined(); - expect(result.children).toBeEmpty(); + level: 1, + children: new Map(), + }, + ], + [ + '沙发', + { + childFiled: null, + level: 2, + id: '四川省[&]成[&]都[&]市[&]家具[&]沙发', + dimensions: [ + '四川省', + '成[&]都[&]市', + '家具', + '沙发', + ], + children: new Map(), + }, + ], + ]), + }, + ], + [ + '办公用品', + { + childField: 'subType', + level: 2, + id: '四川省[&]成[&]都[&]市[&]办公用品', + dimensions: ['四川省', '成[&]都[&]市', '办公用品'], + children: new Map([ + [ + '笔', + { + childFiled: null, + level: 1, + id: '四川省[&]成[&]都[&]市[&]办公用品[&]笔', + dimensions: [ + '四川省', + '成[&]都[&]市', + '办公用品', + '笔', + ], + children: new Map(), + }, + ], + [ + '纸张', + { + childFiled: null, + level: 2, + id: '四川省[&]成[&]都[&]市[&]办公用品[&]纸张', + dimensions: [ + '四川省', + '成[&]都[&]市', + '办公用品', + '纸张', + ], + children: new Map(), + }, + ], + ]), + }, + ], + ]), + }, + ], + [ + '绵阳市', + { + childField: 'type', + level: 2, + id: '四川省[&]绵阳市', + dimensions: ['四川省', '绵阳市'], + children: new Map([ + [ + '家具', + { + childField: 'subType', + level: 1, + id: '四川省[&]绵阳市[&]家具', + dimensions: ['四川省', '绵阳市', '家具'], + children: new Map([ + [ + '桌子', + { + childFiled: null, + level: 1, + id: '四川省[&]绵阳市[&]家具[&]桌子', + dimensions: ['四川省', '绵阳市', '家具', '桌子'], + children: new Map(), + }, + ], + [ + '沙发', + { + childFiled: null, + level: 2, + id: '四川省[&]绵阳市[&]家具[&]沙发', + dimensions: ['四川省', '绵阳市', '家具', '沙发'], + children: new Map(), + }, + ], + ]), + }, + ], + [ + '办公用品', + { + childField: 'subType', + level: 2, + id: '四川省[&]绵阳市[&]办公用品', + dimensions: ['四川省', '绵阳市', '办公用品'], + children: new Map([ + [ + '笔', + { + childFiled: null, + level: 1, + id: '四川省[&]绵阳市[&]办公用品[&]笔', + dimensions: ['四川省', '绵阳市', '办公用品', '笔'], + children: new Map(), + }, + ], + [ + '纸张', + { + childFiled: null, + level: 2, + id: '四川省[&]绵阳市[&]办公用品[&]纸张', + dimensions: [ + '四川省', + '绵阳市', + '办公用品', + '纸张', + ], + children: new Map(), + }, + ], + ]), + }, + ], + ]), + }, + ], + ]), + }, + ], + [ + '浙江省', + { + childField: 'city', + level: 2, + id: '浙江省', + dimensions: ['浙江省'], + children: new Map([ + [ + '杭州市', + { + childField: 'type', + level: 1, + id: '浙江省[&]杭州市', + dimensions: ['浙江省', '杭州市'], + children: new Map([ + [ + '家具', + { + childField: 'subType', + level: 1, + id: '浙江省[&]杭州市[&]家具', + dimensions: ['浙江省', '杭州市', '家具'], + children: new Map([ + [ + '桌子', + { + childFiled: null, + level: 1, + id: '浙江省[&]杭州市[&]家具[&]桌子', + dimensions: ['浙江省', '杭州市', '家具', '桌子'], + children: new Map(), + }, + ], + [ + '沙发', + { + childFiled: null, + level: 2, + id: '浙江省[&]杭州市[&]家具[&]沙发', + dimensions: ['浙江省', '杭州市', '家具', '沙发'], + children: new Map(), + }, + ], + ]), + }, + ], + [ + '办公用品', + { + childField: 'subType', + level: 2, + id: '浙江省[&]杭州市[&]办公用品', + dimensions: ['浙江省', '杭州市'], + children: new Map([ + [ + '笔', + { + childFiled: null, + level: 1, + id: '浙江省[&]杭州市[&]办公用品[&]笔', + dimensions: ['浙江省', '杭州市', '办公用品', '笔'], + children: new Map(), + }, + ], + [ + '纸张', + { + childFiled: null, + level: 2, + id: '浙江省[&]杭州市[&]办公用品[&]纸张', + dimensions: [ + '浙江省', + '杭州市', + '办公用品', + '纸张', + ], + children: new Map(), + }, + ], + ]), + }, + ], + ]), + }, + ], + [ + '舟山市', + { + childField: 'type', + level: 2, + id: '浙江省[&]舟山市', + dimensions: ['浙江省', '舟山市'], + children: new Map([ + [ + '家具', + { + childField: 'subType', + level: 1, + id: '浙江省[&]舟山市[&]家具', + dimensions: ['浙江省', '舟山市', '家具'], + children: new Map([ + [ + '桌子', + { + childFiled: null, + level: 1, + id: '浙江省[&]舟山市[&]家具[&]桌子', + dimensions: ['浙江省', '舟山市', '家具', '桌子'], + children: new Map(), + }, + ], + [ + '沙发', + { + childFiled: null, + level: 2, + id: '浙江省[&]舟山市[&]家具[&]沙发', + dimensions: ['浙江省', '舟山市', '家具', '沙发'], + children: new Map(), + }, + ], + ]), + }, + ], + [ + '办公用品', + { + childField: 'subType', + level: 2, + id: '浙江省[&]舟山市[&]办公用品', + dimensions: ['浙江省', '舟山市', '办公用品'], + children: new Map([ + [ + '笔', + { + childFiled: null, + level: 1, + id: '浙江省[&]舟山市[&]办公用品[&]笔', + dimensions: ['浙江省', '舟山市', '办公用品', '笔'], + children: new Map(), + }, + ], + [ + '纸张', + { + childFiled: null, + level: 2, + id: '浙江省[&]舟山市[&]办公用品[&]纸张', + dimensions: [ + '浙江省', + '舟山市', + '办公用品', + '纸张', + ], + children: new Map(), + }, + ], + ]), + }, + ], + ]), + }, + ], + ]), + }, + ], + ]) as unknown as PivotMeta; }); - test('for transformIndexesData function', () => { - const { rows, columns, values } = dataCfg.fields; - const sortedDimensionValues = {}; - const rowPivotMeta = new Map(); - const colPivotMeta = new Map(); - const result = transformIndexesData({ - rows, - values, - columns: columns as string[], - originData: dataCfg.data, - indexesData: [], - sortedDimensionValues, - rowPivotMeta, - colPivotMeta, - }); - - expect(result.indexesData).toHaveLength(3); - expect(result.paths).toHaveLength(32); - expect(get(result.indexesData, result.paths[0])).toEqual({ - city: '杭州市', - number: 7789, - province: '浙江省', - sub_type: '桌子', - type: '家具', - }); - expect(result.colPivotMeta?.has('家具')).toBeTrue(); - expect(result.rowPivotMeta?.has('浙江省')).toBeTrue(); + test(`should return false if doesn't exist total group`, () => { expect( - getDimensionsWithoutPathPre(result.sortedDimensionValues['province']), - ).toEqual(['浙江省', '四川省']); - }); + existDimensionTotalGroup(['家具', '纸张', MULTI_VALUE, MULTI_VALUE]), + ).toBeFalse(); - test('for transformDimensionsValues function', () => { - const rows = ['province', 'city']; - const data = { - city: '杭州市', - number: 7789, - province: '浙江省', - sub_type: '桌子', - type: '家具', - }; - const result = transformDimensionsValues(data, rows); + expect( + existDimensionTotalGroup([ + MULTI_VALUE, + MULTI_VALUE, + MULTI_VALUE, + MULTI_VALUE, + ]), + ).toBeFalse(); - expect(result).toEqual(['浙江省', '杭州市']); + expect( + existDimensionTotalGroup(['四川省', '成[&]都[&]市', '办公用品', '纸张']), + ).toBeFalse(); }); - test('for return type of transformDimensionsValues function', () => { - const rows = ['row0', 'row1']; - const data = { - row0: 0, - number: 7789, - row1: 1, - sub_type: '桌子', - type: '家具', - }; - const result = transformDimensionsValues(data, rows); + test('should return true if exist total group', () => { + expect( + existDimensionTotalGroup(['四川省', MULTI_VALUE, '家具', MULTI_VALUE]), + ).toBeTrue(); - expect(result).toEqual(['0', '1']); - }); + expect( + existDimensionTotalGroup([MULTI_VALUE, MULTI_VALUE, '家具', MULTI_VALUE]), + ).toBeTrue(); - test('for getDataPath function', () => { - const rowDimensionValues = ['浙江省', '杭州市']; - const colDimensionValues = ['家具', '桌子']; - const rows = ['province', 'city']; - const columns = ['type', 'sub_type']; - const values = ['value']; - const rowPivotMeta = new Map(); - const colPivotMeta = new Map(); - - const result = getDataPath({ - rowDimensionValues, - colDimensionValues, - rowPivotMeta, - colPivotMeta, - shouldCreateOrUpdate: true, - rows, - columns, - values, - }); - - expect(result).toEqual([1, 1, 1, 1]); + expect( + existDimensionTotalGroup([MULTI_VALUE, MULTI_VALUE, MULTI_VALUE, '纸张']), + ).toBeTrue(); }); - test('for getDataPath function when not createIfNotExist and without rows or columns', () => { - const rowDimensionValues = ['浙江省', '杭州市']; - const colDimensionValues = ['家具', '桌子']; - const rowPivotMeta = new Map(); - const colPivotMeta = new Map(); - - getDataPath({ - rowDimensionValues, - colDimensionValues, - rowPivotMeta, - colPivotMeta, - }); - expect(rowPivotMeta.size).toEqual(0); - expect(colPivotMeta.size).toEqual(0); - }); + test(`should return flatten dimension values if doesn't exist total group`, () => { + expect( + flattenDimensionValues({ + fields, + pivotMeta, + sortedDimensionValues, + dimensionValues: ['四川省', '成[&]都[&]市', '办公用品', '纸张'], + }), + ).toEqual([['四川省', '成[&]都[&]市', '办公用品', '纸张']]); - test('for getDataPath function when createIfNotExist and without rows or columns', () => { - const rowDimensionValues = ['浙江省', '杭州市']; - const colDimensionValues = ['家具', '桌子']; - const rowPivotMeta = new Map(); - const colPivotMeta = new Map(); - - getDataPath({ - rowDimensionValues, - colDimensionValues, - rowPivotMeta, - colPivotMeta, - shouldCreateOrUpdate: true, - }); - expect(rowPivotMeta.get(rowDimensionValues[0]).childField).toBeUndefined(); - expect(colPivotMeta.get(colDimensionValues[0]).childField).toBeUndefined(); + expect( + flattenDimensionValues({ + fields, + pivotMeta, + sortedDimensionValues, + dimensionValues: ['四川省', '成[&]都[&]市', MULTI_VALUE, MULTI_VALUE], + }), + ).toEqual([['四川省', '成[&]都[&]市', MULTI_VALUE, MULTI_VALUE]]); }); - test('for getDataPath function when createIfNotExist and with rows or columns', () => { - const rowDimensionValues = ['浙江省', '杭州市']; - const colDimensionValues = ['家具', '桌子']; - const rows = ['province', 'city']; - const columns = ['type', 'sub_type']; - const rowPivotMeta = new Map(); - const colPivotMeta = new Map(); - - getDataPath({ - rowDimensionValues, - colDimensionValues, - rowPivotMeta, - colPivotMeta, - shouldCreateOrUpdate: true, - rows, - columns, - }); - expect(rowPivotMeta.get(rowDimensionValues[0]).childField).toEqual('city'); - expect(colPivotMeta.get(colDimensionValues[0]).childField).toEqual( - 'sub_type', - ); - }); + test(`should return flatten dimension values if exist total group`, () => { + expect( + flattenDimensionValues({ + fields, + pivotMeta, + sortedDimensionValues, + dimensionValues: [MULTI_VALUE, '成[&]都[&]市', MULTI_VALUE, '纸张'], + }), + ).toMatchInlineSnapshot(` + Array [ + Array [ + "四川省", + "成[&]都[&]市", + "办公用品", + "纸张", + ], + ] + `); - test('for getDimensionsWithoutPathPre function', () => { - const dimensions = ['芜湖市[&]家具[&]椅子', '芜湖市[&]家具', '芜湖市']; + expect( + flattenDimensionValues({ + fields, + pivotMeta, + sortedDimensionValues, + dimensionValues: [ + MULTI_VALUE, + '成[&]都[&]市', + MULTI_VALUE, + MULTI_VALUE, + ], + }), + ).toMatchInlineSnapshot(` + Array [ + Array [ + "四川省", + "成[&]都[&]市", + "家具", + "桌子", + ], + Array [ + "四川省", + "成[&]都[&]市", + "家具", + "沙发", + ], + Array [ + "四川省", + "成[&]都[&]市", + "办公用品", + "笔", + ], + Array [ + "四川省", + "成[&]都[&]市", + "办公用品", + "纸张", + ], + ] + `); - expect(getDimensionsWithoutPathPre(dimensions)).toEqual([ - '椅子', - '家具', - '芜湖市', - ]); - }); + expect( + flattenDimensionValues({ + fields, + pivotMeta, + sortedDimensionValues, + dimensionValues: [MULTI_VALUE, MULTI_VALUE, '办公用品', MULTI_VALUE], + }), + ).toMatchInlineSnapshot(` + Array [ + Array [ + "四川省", + "成[&]都[&]市", + "办公用品", + "笔", + ], + Array [ + "四川省", + "成[&]都[&]市", + "办公用品", + "纸张", + ], + Array [ + "四川省", + "绵阳市", + "办公用品", + "笔", + ], + Array [ + "四川省", + "绵阳市", + "办公用品", + "纸张", + ], + Array [ + "浙江省", + "杭州市", + "办公用品", + "笔", + ], + Array [ + "浙江省", + "杭州市", + "办公用品", + "纸张", + ], + Array [ + "浙江省", + "舟山市", + "办公用品", + "笔", + ], + Array [ + "浙江省", + "舟山市", + "办公用品", + "纸张", + ], + ] + `); - test('for getDimensionsWithParentPath function', () => { - const field = 'city'; - const defaultDimensions = ['province', 'city']; - const dimensions = [ - new CellData( - { - province: '辽宁省', - city: '芜湖市', - category: '家具', - subCategory: '椅子', - price: '', - }, - 'price', - ), - ]; - const result = getDimensionsWithParentPath( - field, - defaultDimensions, - dimensions, - ); - - expect(result).toEqual(['辽宁省[&]芜湖市']); + expect( + flattenDimensionValues({ + fields, + pivotMeta, + sortedDimensionValues, + dimensionValues: ['四川省', MULTI_VALUE, '办公用品', MULTI_VALUE], + }), + ).toMatchInlineSnapshot(` + Array [ + Array [ + "四川省", + "成[&]都[&]市", + "办公用品", + "笔", + ], + Array [ + "四川省", + "成[&]都[&]市", + "办公用品", + "纸张", + ], + Array [ + "四川省", + "绵阳市", + "办公用品", + "笔", + ], + Array [ + "四川省", + "绵阳市", + "办公用品", + "纸张", + ], + ] + `); }); }); diff --git a/packages/s2-core/__tests__/unit/utils/export/__snapshots__/copy-spec.ts.snap b/packages/s2-core/__tests__/unit/utils/export/__snapshots__/copy-spec.ts.snap new file mode 100644 index 0000000000..c7c3058c62 --- /dev/null +++ b/packages/s2-core/__tests__/unit/utils/export/__snapshots__/copy-spec.ts.snap @@ -0,0 +1,162 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`List Table Core Data Process should copy all data 1`] = ` +"1 浙江省 杭州市 家具 ### 问题摘要 +- **会话地址**: 7789 +2 浙江省 绍兴市 家具 桌子 2367 +3 浙江省 宁波市 家具 桌子 3877 +4 浙江省 舟山市 家具 桌子 4342 +5 浙江省 杭州市 家具 沙发 5343 +6 浙江省 绍兴市 家具 沙发 632 +7 浙江省 宁波市 家具 沙发 7234 +8 浙江省 舟山市 家具 沙发 834 +9 浙江省 杭州市 办公用品 笔 945 +10 浙江省 绍兴市 办公用品 笔 1304 +11 浙江省 宁波市 办公用品 笔 1145 +12 浙江省 舟山市 办公用品 笔 1432 +13 浙江省 杭州市 办公用品 纸张 1343 +14 浙江省 绍兴市 办公用品 纸张 1354 +15 浙江省 宁波市 办公用品 纸张 1523 +16 浙江省 舟山市 办公用品 纸张 1634 +17 四川省 成都市 家具 桌子 1723 +18 四川省 绵阳市 家具 桌子 1822 +19 四川省 南充市 家具 桌子 1943 +20 四川省 乐山市 家具 桌子 2330 +21 四川省 成都市 家具 沙发 2451 +22 四川省 绵阳市 家具 沙发 2244 +23 四川省 南充市 家具 沙发 2333 +24 四川省 乐山市 家具 沙发 2445 +25 四川省 成都市 办公用品 笔 2335 +26 四川省 绵阳市 办公用品 笔 245 +27 四川省 南充市 办公用品 笔 2457 +28 四川省 乐山市 办公用品 笔 2458 +29 四川省 成都市 办公用品 纸张 4004 +30 四川省 绵阳市 办公用品 纸张 3077 +31 四川省 南充市 办公用品 纸张 3551 +32 四川省 乐山市 办公用品 纸张 352" +`; + +exports[`List Table Core Data Process should copy correct data when selected diagonal cells 1`] = ` +"浙江省 + + + + + + + + 宁波市" +`; + +exports[`Pivot Table getBrushHeaderCopyable should copy all col data in grid mode for custom field meta 1`] = ` +Array [ + Object { + "content": "家具 家具 办公用品 办公用品 +桌子 沙发 笔 纸张 +数量 数量 数量 数量", + "type": "text/plain", + }, + Object { + "content": "<meta charset=\\"utf-8\\"><table><tbody><tr><td>家具</td><td>家具</td><td>办公用品</td><td>办公用品</td></tr><tr><td>桌子</td><td>沙发</td><td>笔</td><td>纸张</td></tr><tr><td>数量</td><td>数量</td><td>数量</td><td>数量</td></tr></tbody></table>", + "type": "text/html", + }, +] +`; + +exports[`Pivot Table getBrushHeaderCopyable should copy all original row data in grid mode if contains text ellipses 1`] = ` +Array [ + Object { + "content": "浙江省 杭州市 +浙江省 绍兴市 +浙江省 宁波市 +浙江省 舟山市 +四川省 成都市 +四川省 绵阳市 +四川省 南充市 +四川省 乐山市", + "type": "text/plain", + }, + Object { + "content": "<meta charset=\\"utf-8\\"><table><tbody><tr><td>浙江省</td><td>杭州市</td></tr><tr><td>浙江省</td><td>绍兴市</td></tr><tr><td>浙江省</td><td>宁波市</td></tr><tr><td>浙江省</td><td>舟山市</td></tr><tr><td>四川省</td><td>成都市</td></tr><tr><td>四川省</td><td>绵阳市</td></tr><tr><td>四川省</td><td>南充市</td></tr><tr><td>四川省</td><td>乐山市</td></tr></tbody></table>", + "type": "text/html", + }, +] +`; + +exports[`Pivot Table getBrushHeaderCopyable should copy all row data in grid mode with formatter 1`] = ` +"浙江省 杭州市 数值 +浙江省 绍兴市 数值 +浙江省 宁波市 数值 +浙江省 舟山市 数值 +四川省 成都市 数值 +四川省 绵阳市 数值 +四川省 南充市 数值 +四川省 乐山市 数值" +`; + +exports[`Pivot Table getBrushHeaderCopyable should copy col total data in grid mode 1`] = ` +Array [ + Object { + "content": "家具 家具 家具 办公用品 办公用品 +桌子 沙发 小计 笔 纸张 +number number 小计 number number", + "type": "text/plain", + }, + Object { + "content": "<meta charset=\\"utf-8\\"><table><tbody><tr><td>家具</td><td>家具</td><td>家具</td><td>办公用品</td><td>办公用品</td></tr><tr><td>桌子</td><td>沙发</td><td>小计</td><td>笔</td><td>纸张</td></tr><tr><td>number</td><td>number</td><td>小计</td><td>number</td><td>number</td></tr></tbody></table>", + "type": "text/html", + }, +] +`; + +exports[`Tree Table Core Data Process should copy normal data with header for custom field formatter if enable copyWithFormat 1`] = ` +Array [ + Object { + "content": " 家具 家具 家具 办公用品 办公用品 + 桌子 沙发 小计 笔 纸张 + 数量 数量 数量 数量 +浙江省 18375-@ 14043-@ 32418-@ 4826-@ 5854-@ +浙江省 杭州市 7789-@ 5343-@ 13132-@ 945-@ 1343-@ +浙江省 绍兴市 2367-@ 632-@ 2999-@ 1304-@ 1354-@ +浙江省 宁波市 3877-@ 7234-@ 11111-@ 1145-@ 1523-@ +浙江省 舟山市 4342-@ 834-@ 5176-@ 1432-@ 1634-@ +四川省 7818-@ 9473-@ 17291-@ 7495-@ 10984-@ +四川省 成都市 1723-@ 2451-@ 4174-@ 2335-@ 4004-@ +四川省 绵阳市 1822-@ 2244-@ 4066-@ 245-@ 3077-@ +四川省 南充市 1943-@ 2333-@ 4276-@ 2457-@ 3551-@ +四川省 乐山市 2330-@ 2445-@ 4775-@ 2458-@ 352-@ +总计 26193-@ 23516-@ 49709-@ 12321-@ 16838-@", + "type": "text/plain", + }, + Object { + "content": "<meta charset=\\"utf-8\\"><table><tbody><tr><td></td><td></td><td>家具</td><td>家具</td><td>家具</td><td>办公用品</td><td>办公用品</td></tr><tr><td></td><td></td><td>桌子</td><td>沙发</td><td>小计</td><td>笔</td><td>纸张</td></tr><tr><td></td><td></td><td>数量</td><td>数量</td><td></td><td>数量</td><td>数量</td></tr><tr><td>浙江省</td><td></td><td>18375-@</td><td>14043-@</td><td>32418-@</td><td>4826-@</td><td>5854-@</td></tr><tr><td>浙江省</td><td>杭州市</td><td>7789-@</td><td>5343-@</td><td>13132-@</td><td>945-@</td><td>1343-@</td></tr><tr><td>浙江省</td><td>绍兴市</td><td>2367-@</td><td>632-@</td><td>2999-@</td><td>1304-@</td><td>1354-@</td></tr><tr><td>浙江省</td><td>宁波市</td><td>3877-@</td><td>7234-@</td><td>11111-@</td><td>1145-@</td><td>1523-@</td></tr><tr><td>浙江省</td><td>舟山市</td><td>4342-@</td><td>834-@</td><td>5176-@</td><td>1432-@</td><td>1634-@</td></tr><tr><td>四川省</td><td></td><td>7818-@</td><td>9473-@</td><td>17291-@</td><td>7495-@</td><td>10984-@</td></tr><tr><td>四川省</td><td>成都市</td><td>1723-@</td><td>2451-@</td><td>4174-@</td><td>2335-@</td><td>4004-@</td></tr><tr><td>四川省</td><td>绵阳市</td><td>1822-@</td><td>2244-@</td><td>4066-@</td><td>245-@</td><td>3077-@</td></tr><tr><td>四川省</td><td>南充市</td><td>1943-@</td><td>2333-@</td><td>4276-@</td><td>2457-@</td><td>3551-@</td></tr><tr><td>四川省</td><td>乐山市</td><td>2330-@</td><td>2445-@</td><td>4775-@</td><td>2458-@</td><td>352-@</td></tr><tr><td>总计</td><td></td><td>26193-@</td><td>23516-@</td><td>49709-@</td><td>12321-@</td><td>16838-@</td></tr></tbody></table>", + "type": "text/html", + }, +] +`; + +exports[`Tree Table Core Data Process should copy normal data with header for custom field name 1`] = ` +Array [ + Object { + "content": " 家具 家具 家具 办公用品 办公用品 + 桌子 沙发 小计 笔 纸张 + 数量 数量 数量 数量 +浙江省 18375 14043 32418 4826 5854 +浙江省 杭州市 7789 5343 13132 945 1343 +浙江省 绍兴市 2367 632 2999 1304 1354 +浙江省 宁波市 3877 7234 11111 1145 1523 +浙江省 舟山市 4342 834 5176 1432 1634 +四川省 7818 9473 17291 7495 10984 +四川省 成都市 1723 2451 4174 2335 4004 +四川省 绵阳市 1822 2244 4066 245 3077 +四川省 南充市 1943 2333 4276 2457 3551 +四川省 乐山市 2330 2445 4775 2458 352 +总计 26193 23516 49709 12321 16838", + "type": "text/plain", + }, + Object { + "content": "<meta charset=\\"utf-8\\"><table><tbody><tr><td></td><td></td><td>家具</td><td>家具</td><td>家具</td><td>办公用品</td><td>办公用品</td></tr><tr><td></td><td></td><td>桌子</td><td>沙发</td><td>小计</td><td>笔</td><td>纸张</td></tr><tr><td></td><td></td><td>数量</td><td>数量</td><td></td><td>数量</td><td>数量</td></tr><tr><td>浙江省</td><td></td><td>18375</td><td>14043</td><td>32418</td><td>4826</td><td>5854</td></tr><tr><td>浙江省</td><td>杭州市</td><td>7789</td><td>5343</td><td>13132</td><td>945</td><td>1343</td></tr><tr><td>浙江省</td><td>绍兴市</td><td>2367</td><td>632</td><td>2999</td><td>1304</td><td>1354</td></tr><tr><td>浙江省</td><td>宁波市</td><td>3877</td><td>7234</td><td>11111</td><td>1145</td><td>1523</td></tr><tr><td>浙江省</td><td>舟山市</td><td>4342</td><td>834</td><td>5176</td><td>1432</td><td>1634</td></tr><tr><td>四川省</td><td></td><td>7818</td><td>9473</td><td>17291</td><td>7495</td><td>10984</td></tr><tr><td>四川省</td><td>成都市</td><td>1723</td><td>2451</td><td>4174</td><td>2335</td><td>4004</td></tr><tr><td>四川省</td><td>绵阳市</td><td>1822</td><td>2244</td><td>4066</td><td>245</td><td>3077</td></tr><tr><td>四川省</td><td>南充市</td><td>1943</td><td>2333</td><td>4276</td><td>2457</td><td>3551</td></tr><tr><td>四川省</td><td>乐山市</td><td>2330</td><td>2445</td><td>4775</td><td>2458</td><td>352</td></tr><tr><td>总计</td><td></td><td>26193</td><td>23516</td><td>49709</td><td>12321</td><td>16838</td></tr></tbody></table>", + "type": "text/html", + }, +] +`; diff --git a/packages/s2-core/__tests__/unit/utils/export/__snapshots__/export-pivot-spec.ts.snap b/packages/s2-core/__tests__/unit/utils/export/__snapshots__/export-pivot-spec.ts.snap new file mode 100644 index 0000000000..7bbda655b9 --- /dev/null +++ b/packages/s2-core/__tests__/unit/utils/export/__snapshots__/export-pivot-spec.ts.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PivotSheet Export Test should export correct data when data is incomplete 1`] = ` +" province 浙江省 浙江省 浙江省 浙江省 四川省 四川省 四川省 四川省 + city 杭州市 绍兴市 宁波市 舟山市 成都市 绵阳市 南充市 乐山市 +type sub_type number number number number number number number number +家具 +家具 桌子 2367 3877 4342 1723 1822 1943 2330 +家具 沙发 632 7234 834 2451 2244 2333 2445 +办公用品 +办公用品 笔 +办公用品 纸张 1354 1523 1634 4004 3077 3551 352" +`; diff --git a/packages/s2-core/__tests__/unit/utils/export/__snapshots__/export-spec.ts.snap b/packages/s2-core/__tests__/unit/utils/export/__snapshots__/export-spec.ts.snap new file mode 100644 index 0000000000..d4c258e782 --- /dev/null +++ b/packages/s2-core/__tests__/unit/utils/export/__snapshots__/export-spec.ts.snap @@ -0,0 +1,360 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PivotSheet Export Test should export correct $$extra$$ field name 1`] = ` +Array [ + "type", + "sub_type", + "数值", + "数值", + "数值", + "数值", + "数值", + "数值", + "数值", + "数值 +", +] +`; + +exports[`TableSheet Export Test should export correct data with no series number 1`] = ` +Array [ + "province city type sub_type number +", + "浙江省 杭州市 家具 桌子 7789 +", + "浙江省 绍兴市 家具 桌子 2367 +", + "浙江省 宁波市 家具 桌子 3877 +", + "浙江省 舟山市 家具 桌子 4342 +", + "浙江省 杭州市 家具 沙发 5343 +", + "浙江省 绍兴市 家具 沙发 632 +", + "浙江省 宁波市 家具 沙发 7234 +", + "浙江省 舟山市 家具 沙发 834 +", + "浙江省 杭州市 办公用品 笔 945 +", + "浙江省 绍兴市 办公用品 笔 1304 +", + "浙江省 宁波市 办公用品 笔 1145 +", + "浙江省 舟山市 办公用品 笔 1432 +", + "浙江省 杭州市 办公用品 纸张 1343 +", + "浙江省 绍兴市 办公用品 纸张 1354 +", + "浙江省 宁波市 办公用品 纸张 1523 +", + "浙江省 舟山市 办公用品 纸张 1634 +", + "四川省 成都市 家具 桌子 1723 +", + "四川省 绵阳市 家具 桌子 1822 +", + "四川省 南充市 家具 桌子 1943 +", + "四川省 乐山市 家具 桌子 2330 +", + "四川省 成都市 家具 沙发 2451 +", + "四川省 绵阳市 家具 沙发 2244 +", + "四川省 南充市 家具 沙发 2333 +", + "四川省 乐山市 家具 沙发 2445 +", + "四川省 成都市 办公用品 笔 2335 +", + "四川省 绵阳市 办公用品 笔 245 +", + "四川省 南充市 办公用品 笔 2457 +", + "四川省 乐山市 办公用品 笔 2458 +", + "四川省 成都市 办公用品 纸张 4004 +", + "四川省 绵阳市 办公用品 纸张 3077 +", + "四川省 南充市 办公用品 纸张 3551 +", + "四川省 乐山市 办公用品 纸张 352 +", + " 家具 桌子 26193 +", + " 家具 49709 +", + " 家具 沙发 23516 +", + " 办公用品 29159 +", + " 办公用品 笔 12321 +", + " 办公用品 纸张 16838 +", + "浙江省 家具 桌子 18375 +", + "浙江省 家具 沙发 14043 +", + "浙江省 办公用品 笔 4826 +", + "浙江省 办公用品 纸张 5854 +", + "四川省 家具 桌子 7818 +", + "四川省 家具 沙发 9473 +", + "四川省 办公用品 笔 7495 +", + "四川省 办公用品 纸张 10984 +", + "浙江省 杭州市 家具 13132 +", + "浙江省 杭州市 办公用品 2288 +", + "浙江省 杭州市 15420 +", + "浙江省 绍兴市 家具 2999 +", + "浙江省 绍兴市 办公用品 2658 +", + "浙江省 绍兴市 5657 +", + "浙江省 宁波市 家具 11111 +", + "浙江省 宁波市 办公用品 2668 +", + "浙江省 宁波市 13779 +", + "浙江省 舟山市 家具 5176 +", + "浙江省 舟山市 办公用品 3066 +", + "浙江省 舟山市 8242 +", + "四川省 成都市 家具 4174 +", + "四川省 成都市 办公用品 6339 +", + "四川省 成都市 10513 +", + "四川省 绵阳市 家具 4066 +", + "四川省 绵阳市 办公用品 3322 +", + "四川省 绵阳市 7388 +", + "四川省 南充市 家具 4276 +", + "四川省 南充市 办公用品 6008 +", + "四川省 南充市 10284 +", + "四川省 乐山市 家具 4775 +", + "四川省 乐山市 办公用品 2810 +", + "四川省 乐山市 7585 +", + "浙江省 家具 32418 +", + "浙江省 办公用品 10680 +", + "浙江省 43098 +", + "四川省 家具 17291 +", + "四川省 办公用品 18479 +", + "四川省 35770 +", + " 78868", +] +`; + +exports[`TableSheet Export Test should export correct data with no series number 2`] = ` +Array [ + "province", + "city", + "type", + "sub_type", + "number +", +] +`; + +exports[`TableSheet Export Test should export correct data with series number 1`] = ` +Array [ + "序号 province city 产品类型 sub_type number +", + "1 浙江省 杭州市 家具 桌子 7789 +", + "2 浙江省 绍兴市 家具 桌子 2367 +", + "3 浙江省 宁波市 家具 桌子 3877 +", + "4 浙江省 舟山市 家具 桌子 4342 +", + "5 浙江省 杭州市 家具 沙发 5343 +", + "6 浙江省 绍兴市 家具 沙发 632 +", + "7 浙江省 宁波市 家具 沙发 7234 +", + "8 浙江省 舟山市 家具 沙发 834 +", + "9 浙江省 杭州市 办公用品 笔 945 +", + "10 浙江省 绍兴市 办公用品 笔 1304 +", + "11 浙江省 宁波市 办公用品 笔 1145 +", + "12 浙江省 舟山市 办公用品 笔 1432 +", + "13 浙江省 杭州市 办公用品 纸张 1343 +", + "14 浙江省 绍兴市 办公用品 纸张 1354 +", + "15 浙江省 宁波市 办公用品 纸张 1523 +", + "16 浙江省 舟山市 办公用品 纸张 1634 +", + "17 四川省 成都市 家具 桌子 1723 +", + "18 四川省 绵阳市 家具 桌子 1822 +", + "19 四川省 南充市 家具 桌子 1943 +", + "20 四川省 乐山市 家具 桌子 2330 +", + "21 四川省 成都市 家具 沙发 2451 +", + "22 四川省 绵阳市 家具 沙发 2244 +", + "23 四川省 南充市 家具 沙发 2333 +", + "24 四川省 乐山市 家具 沙发 2445 +", + "25 四川省 成都市 办公用品 笔 2335 +", + "26 四川省 绵阳市 办公用品 笔 245 +", + "27 四川省 南充市 办公用品 笔 2457 +", + "28 四川省 乐山市 办公用品 笔 2458 +", + "29 四川省 成都市 办公用品 纸张 4004 +", + "30 四川省 绵阳市 办公用品 纸张 3077 +", + "31 四川省 南充市 办公用品 纸张 3551 +", + "32 四川省 乐山市 办公用品 纸张 352 +", + "33 家具 桌子 26193 +", + "34 家具 49709 +", + "35 家具 沙发 23516 +", + "36 办公用品 29159 +", + "37 办公用品 笔 12321 +", + "38 办公用品 纸张 16838 +", + "39 浙江省 家具 桌子 18375 +", + "40 浙江省 家具 沙发 14043 +", + "41 浙江省 办公用品 笔 4826 +", + "42 浙江省 办公用品 纸张 5854 +", + "43 四川省 家具 桌子 7818 +", + "44 四川省 家具 沙发 9473 +", + "45 四川省 办公用品 笔 7495 +", + "46 四川省 办公用品 纸张 10984 +", + "47 浙江省 杭州市 家具 13132 +", + "48 浙江省 杭州市 办公用品 2288 +", + "49 浙江省 杭州市 15420 +", + "50 浙江省 绍兴市 家具 2999 +", + "51 浙江省 绍兴市 办公用品 2658 +", + "52 浙江省 绍兴市 5657 +", + "53 浙江省 宁波市 家具 11111 +", + "54 浙江省 宁波市 办公用品 2668 +", + "55 浙江省 宁波市 13779 +", + "56 浙江省 舟山市 家具 5176 +", + "57 浙江省 舟山市 办公用品 3066 +", + "58 浙江省 舟山市 8242 +", + "59 四川省 成都市 家具 4174 +", + "60 四川省 成都市 办公用品 6339 +", + "61 四川省 成都市 10513 +", + "62 四川省 绵阳市 家具 4066 +", + "63 四川省 绵阳市 办公用品 3322 +", + "64 四川省 绵阳市 7388 +", + "65 四川省 南充市 家具 4276 +", + "66 四川省 南充市 办公用品 6008 +", + "67 四川省 南充市 10284 +", + "68 四川省 乐山市 家具 4775 +", + "69 四川省 乐山市 办公用品 2810 +", + "70 四川省 乐山市 7585 +", + "71 浙江省 家具 32418 +", + "72 浙江省 办公用品 10680 +", + "73 浙江省 43098 +", + "74 四川省 家具 17291 +", + "75 四川省 办公用品 18479 +", + "76 四川省 35770 +", + "77 78868", +] +`; + +exports[`TableSheet Export Test should export correct data with series number 2`] = ` +Array [ + "序号", + "province", + "city", + "产品类型", + "sub_type", + "number +", +] +`; diff --git a/packages/s2-core/__tests__/unit/utils/export/copy-spec.ts b/packages/s2-core/__tests__/unit/utils/export/copy-spec.ts index 4687639bf2..086fa5ec1f 100644 --- a/packages/s2-core/__tests__/unit/utils/export/copy-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/export/copy-spec.ts @@ -1,24 +1,24 @@ import { map } from 'lodash'; import { data as originalData, totalData } from 'tests/data/mock-dataset.json'; import { + TOTALS_OPTIONS, assembleDataCfg, assembleOptions, - TOTALS_OPTIONS, waitForRender, } from 'tests/util'; import { getContainer } from 'tests/util/helpers'; -import type { Meta, S2DataConfig } from '@/common/interface'; -import { Aggregation } from '@/common/interface'; -import { TableDataCell, TableSeriesNumberCell } from '@/cell'; +import type { S2DataConfig } from '../../../../src/common'; +import { TableSeriesNumberCell } from '@/cell'; import { NewLine, NewTab, S2Event } from '@/common/constant'; import { - CellType, InteractionStateName, SortMethodType, } from '@/common/constant/interaction'; -import { PivotSheet, TableSheet } from '@/sheet-type'; +import type { Meta } from '@/common/interface'; +import { Aggregation } from '@/common/interface'; +import { PivotSheet, SpreadSheet, TableSheet } from '@/sheet-type'; import { getSelectedData } from '@/utils/export/copy'; -import { CopyMIMEType } from '@/utils/export/interface'; +import { CopyMIMEType } from '@/common/interface/export'; import { convertString } from '@/utils/export/method'; import { getCellMeta } from '@/utils/interaction/select-event'; @@ -32,7 +32,7 @@ const testData = originalData.map((item, i) => { return { ...item }; }); -const getCopyPlainContent = (sheet: TableSheet | PivotSheet): string => { +const getCopyPlainContent = (sheet: SpreadSheet): string => { const data = getSelectedData(sheet); return data[0].content; @@ -53,8 +53,14 @@ describe('List Table Core Data Process', () => { }), assembleOptions({ showSeriesNumber: true, + interaction: { + selectedCellHighlight: { + currentRow: true, + }, + }, }), ); + await s2.render(); }); @@ -74,7 +80,7 @@ describe('List Table Core Data Process', () => { it('should copy normal data', () => { const cell = s2.facet .getDataCells() - .find((cell) => !(cell instanceof TableSeriesNumberCell))!; + .find((cell) => cell.getMeta().valueField === 'province')!; s2.interaction.changeState({ cells: [getCellMeta(cell)], @@ -94,14 +100,13 @@ describe('List Table Core Data Process', () => { }); it('should copy row data', () => { - const cell = s2.facet - .getCells() - .filter((cell) => cell instanceof TableSeriesNumberCell)[3]; + const cell = s2.facet.getSeriesNumberCells()[3]; s2.interaction.changeState({ cells: [getCellMeta(cell)], stateName: InteractionStateName.SELECTED, }); + expect(getCopyPlainContent(s2)).toMatchInlineSnapshot( `"1 浙江省 舟山市 家具 桌子 4342"`, ); @@ -113,6 +118,7 @@ describe('List Table Core Data Process', () => { stateName: InteractionStateName.ALL_SELECTED, }); + expect(getCopyPlainContent(s2)).toMatchSnapshot(); expect(getCopyPlainContent(s2).split(NewLine).length).toBe(33); expect(getCopyPlainContent(s2).split(NewLine)[2]).toMatchInlineSnapshot( `"2 浙江省 绍兴市 家具 桌子 2367"`, @@ -130,12 +136,13 @@ describe('List Table Core Data Process', () => { const cell = s2.facet .getDataCells() - .find((cell) => !(cell instanceof TableSeriesNumberCell))!; + .find((cell) => cell.getMeta().valueField === 'province')!; s2.interaction.changeState({ cells: [getCellMeta(cell)], stateName: InteractionStateName.SELECTED, }); + expect(getCopyPlainContent(s2)).toEqual('province\r\n浙江省'); }); @@ -159,53 +166,46 @@ describe('List Table Core Data Process', () => { await sheet.render(); const cell = s2.facet - .getCells() - .filter( - (cell) => - cell.cellType === CellType.DATA_CELL && - !(cell instanceof TableSeriesNumberCell), - )[0]; + .getDataCells() + .find((cell) => cell.getMeta().valueField === 'province')!; sheet.interaction.changeState({ cells: [getCellMeta(cell)], stateName: InteractionStateName.SELECTED, }); + expect(getCopyPlainContent(sheet)).toEqual('浙江省元'); }); // https://github.com/antvis/S2/issues/1770 it('should copy format data with row header selected', async () => { - const sheet = new TableSheet( - getContainer(), - assembleDataCfg({ - meta: [{ field: 'province', formatter: (v) => `${v}_formatted` }], - fields: { - columns: ['province', 'city', 'type', 'sub_type', 'number'], - }, - }), - assembleOptions({ - interaction: { - enableCopy: true, - copyWithFormat: true, - }, - showSeriesNumber: false, - }), - ); + s2.setDataCfg({ + meta: [{ field: 'province', formatter: (v) => `${v}_formatted` }], + fields: { + columns: ['province', 'city', 'type', 'sub_type', 'number'], + }, + }); - await sheet.render(); + s2.setOptions({ + interaction: { + enableCopy: true, + copyWithFormat: true, + }, + showSeriesNumber: false, + }); - const cell = sheet.facet - .getCells() - .filter((cell) => cell instanceof TableDataCell)[0]; + await s2.render(); - sheet.interaction.changeState({ - cells: [getCellMeta(cell)], + const dataCell = s2.facet.getDataCells()[0]; + + s2.interaction.changeState({ + cells: [getCellMeta(dataCell)], stateName: InteractionStateName.SELECTED, }); - const data = getCopyPlainContent(sheet); + const data = getCopyPlainContent(s2); - expect(data).toBe('浙江省_formatted'); + expect(data).toEqual('浙江省_formatted'); }); // https://github.com/antvis/S2/issues/1770 @@ -231,7 +231,7 @@ describe('List Table Core Data Process', () => { const cell = sheet.facet.getColCells()[1]; - await sheet.interaction.changeState({ + sheet.interaction.changeState({ cells: [getCellMeta(cell)], stateName: InteractionStateName.SELECTED, }); @@ -253,17 +253,7 @@ describe('List Table Core Data Process', () => { const dataContent = getCopyPlainContent(s2); - expect(dataContent).toMatchInlineSnapshot(` - "浙江省 - - - - - - - - 宁波市" - `); + expect(dataContent).toMatchSnapshot(); }); it('should copy correct data with data filtered', async () => { @@ -281,9 +271,7 @@ describe('List Table Core Data Process', () => { }); }); - const cell = s2.facet - .getCells() - .filter((cell) => cell instanceof TableSeriesNumberCell)[3]; + const cell = s2.facet.getSeriesNumberCells()[3]; s2.interaction.changeState({ cells: [getCellMeta(cell)], @@ -296,7 +284,9 @@ describe('List Table Core Data Process', () => { s2.interaction.changeState({ stateName: InteractionStateName.ALL_SELECTED, }); + expect(getCopyPlainContent(s2).split('\n').length).toEqual(16); + // clear filter condition s2.emit(S2Event.RANGE_FILTER, { filterKey: 'province', @@ -314,9 +304,7 @@ describe('List Table Core Data Process', () => { ]); }); - const cell = s2.facet - .getCells() - .filter((cell) => cell instanceof TableSeriesNumberCell)[1]; + const cell = s2.facet.getSeriesNumberCells()[1]; s2.interaction.changeState({ cells: [getCellMeta(cell)], @@ -325,13 +313,15 @@ describe('List Table Core Data Process', () => { const data = getCopyPlainContent(s2); expect(data).toBe('1 浙江省 宁波市 家具 沙发 7234'); + s2.interaction.changeState({ stateName: InteractionStateName.ALL_SELECTED, }); + expect(getCopyPlainContent(s2).split('\n').length).toEqual(33); }); - it('should copy correct data with \n data', async () => { + it('should copy correct data with "\n" data', async () => { const newLineText = `1 2`; const sheet = new TableSheet( @@ -356,12 +346,8 @@ describe('List Table Core Data Process', () => { await sheet.render(); const cell = sheet.facet - .getCells() - .filter( - (cell) => - cell.cellType === CellType.DATA_CELL && - !(cell instanceof TableSeriesNumberCell), - )[20]; + .getDataCells() + .filter((cell) => !(cell instanceof TableSeriesNumberCell))[20]; sheet.interaction.changeState({ cells: [getCellMeta(cell)], @@ -409,6 +395,7 @@ describe('List Table Core Data Process', () => { it('should copy row data when select data row cell', async () => { s2.setOptions({ + showSeriesNumber: false, interaction: { selectedCellHighlight: { currentRow: true, @@ -425,15 +412,13 @@ describe('List Table Core Data Process', () => { stateName: InteractionStateName.SELECTED, }); - expect(getCopyPlainContent(s2)).toMatchInlineSnapshot(` - "1 浙江省 杭州市 家具 ### 问题摘要 - - **会话地址**: 7789" - `); - expect(getCopyPlainContent(s2).split(NewTab).length).toBe(6); + expect(getCopyPlainContent(s2)).toEqual('浙江省'); + expect(getCopyPlainContent(s2).split(NewTab).length).toBe(1); }); it('should support custom copy matrix transformer', async () => { s2.setOptions({ + showSeriesNumber: false, interaction: { customTransformer: () => { return { @@ -1348,9 +1333,55 @@ describe('Tree Table Core Data Process', () => { 总计 26193 23516 49709 12321 16838" `); }); + + it('should copy normal data with header for custom field name', async () => { + s2.setOptions({ + interaction: { + copyWithHeader: true, + }, + }); + s2.setDataCfg({ + meta: [ + { + field: 'number', + name: '数量', + }, + ], + }); + await s2.render(); + + setSelectedVisibleCell(); + + expect(getSelectedData(s2)).toMatchSnapshot(); + }); + + it('should copy normal data with header for custom field formatter if enable copyWithFormat', async () => { + s2.setOptions({ + interaction: { + copyWithHeader: true, + copyWithFormat: true, + }, + }); + s2.setDataCfg({ + meta: [ + { + field: 'number', + name: '数量', + formatter: (value) => `${value}-@`, + }, + ], + }); + await s2.render(); + + setSelectedVisibleCell(); + + expect(getSelectedData(s2)).toMatchSnapshot(); + }); }); describe('Pivot Table getBrushHeaderCopyable', () => { + let s2: SpreadSheet; + const dataCfg = assembleDataCfg({ meta: [], fields: { @@ -1359,6 +1390,7 @@ describe('Pivot Table getBrushHeaderCopyable', () => { values: ['number'], }, }); + const options = assembleOptions({ hierarchyType: 'grid', interaction: { @@ -1367,9 +1399,8 @@ describe('Pivot Table getBrushHeaderCopyable', () => { }, }); - const s2 = new PivotSheet(getContainer(), dataCfg, options); - beforeEach(async () => { + s2 = new PivotSheet(getContainer(), dataCfg, options); await s2.render(); }); @@ -1396,6 +1427,56 @@ describe('Pivot Table getBrushHeaderCopyable', () => { `); }); + test('should copy all row data in grid mode with formatter', async () => { + s2.setDataCfg({ + fields: { + valueInCols: false, + }, + meta: [ + { + field: 'number', + name: '数值', + }, + ], + }); + await s2.render(); + + const cells = s2.facet.getRowCells(); + + s2.interaction.changeState({ + cells: map(cells, getCellMeta), + stateName: InteractionStateName.SELECTED, + onUpdateCells: (root) => { + root.updateCells(cells); + }, + }); + + expect(getCopyPlainContent(s2)).toMatchSnapshot(); + }); + + test('should copy all original row data in grid mode if contains text ellipses', () => { + s2.setOptions({ + style: { + rowCell: { + // 展示省略号 + width: 10, + }, + }, + }); + + const cells = s2.facet.getRowCells(); + + s2.interaction.changeState({ + cells: map(cells, getCellMeta), + stateName: InteractionStateName.SELECTED, + onUpdateCells: (root) => { + root.updateCells(s2.facet.getRowCells()); + }, + }); + + expect(getSelectedData(s2)).toMatchSnapshot(); + }); + test('should copy all col data in grid mode', () => { const cells = s2.facet.getColCells(); @@ -1406,8 +1487,8 @@ describe('Pivot Table getBrushHeaderCopyable', () => { root.updateCells(cells); }, }); - // 列头高度 + // 列头高度 expect(getCopyPlainContent(s2)).toMatchInlineSnapshot(` "家具 家具 办公用品 办公用品 桌子 沙发 笔 纸张 @@ -1415,6 +1496,59 @@ describe('Pivot Table getBrushHeaderCopyable', () => { `); }); + test('should copy all col data in grid mode with formatter', async () => { + s2.setDataCfg({ + meta: [ + { + field: 'number', + name: '数值', + }, + ], + }); + await s2.render(); + + const cells = s2.facet.getColCells(); + + s2.interaction.changeState({ + cells: map(cells, getCellMeta), + stateName: InteractionStateName.SELECTED, + onUpdateCells: (root) => { + root.updateCells(cells); + }, + }); + + expect(getCopyPlainContent(s2)).toMatchInlineSnapshot(` + "家具 家具 办公用品 办公用品 + 桌子 沙发 笔 纸张 + 数值 数值 数值 数值" + `); + }); + + test('should copy all col data in grid mode for custom field meta', async () => { + s2.setDataCfg({ + meta: [ + { + field: 'number', + name: '数量', + }, + ], + }); + + await s2.render(); + + const cells = s2.facet.getColCells(); + + s2.interaction.changeState({ + cells: map(cells, getCellMeta), + stateName: InteractionStateName.SELECTED, + onUpdateCells: (root) => { + root.updateCells(cells); + }, + }); + + expect(getSelectedData(s2)).toMatchSnapshot(); + }); + test('should copy selection row data in grid mode', async () => { const sheet = new PivotSheet( getContainer(), @@ -1523,6 +1657,7 @@ describe('Pivot Table getBrushHeaderCopyable', () => { root.updateCells(sheet.facet.getColCells()); }, }); + expect(getCopyPlainContent(sheet)).toMatchInlineSnapshot(` "浙江省 浙江省 杭州市 绍兴市" @@ -1598,14 +1733,6 @@ describe('Pivot Table getBrushHeaderCopyable', () => { const copyableList = getSelectedData(sheet); - expect(copyableList[0].content).toMatchInlineSnapshot(` - "家具 家具 家具 办公用品 办公用品 - 桌子 沙发 小计 笔 纸张 - number number 小计 number number" - `); - - expect(copyableList[1].content).toMatchInlineSnapshot( - `"<meta charset=\\"utf-8\\"><table><tbody><tr><td>家具</td><td>家具</td><td>家具</td><td>办公用品</td><td>办公用品</td></tr><tr><td>桌子</td><td>沙发</td><td>小计</td><td>笔</td><td>纸张</td></tr><tr><td>number</td><td>number</td><td>小计</td><td>number</td><td>number</td></tr></tbody></table>"`, - ); + expect(copyableList).toMatchSnapshot(); }); }); diff --git a/packages/s2-core/__tests__/unit/utils/export/export-pivot-spec.ts b/packages/s2-core/__tests__/unit/utils/export/export-pivot-spec.ts index 969537bb3c..2a46ca4d72 100644 --- a/packages/s2-core/__tests__/unit/utils/export/export-pivot-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/export/export-pivot-spec.ts @@ -1,15 +1,9 @@ -import { - asyncGetAllPlainData, - copyData, - NewLine, - NewTab, - PivotSheet, -} from '@antv/s2'; -import { getContainer } from 'tests/util/helpers'; -import { assembleDataCfg, assembleOptions } from 'tests/util'; +import { NewLine, NewTab, PivotSheet, asyncGetAllPlainData } from '@antv/s2'; import { map, omit } from 'lodash'; import { data as originData } from 'tests/data/mock-dataset.json'; -import { CopyMIMEType } from '@/utils/export/interface'; +import { assembleDataCfg, assembleOptions } from 'tests/util'; +import { getContainer } from 'tests/util/helpers'; +import { CopyMIMEType } from '@/common/interface/export'; describe('PivotSheet Export Test', () => { let pivotSheet: PivotSheet; @@ -50,7 +44,7 @@ describe('PivotSheet Export Test', () => { } await s2.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: NewTab, }); @@ -93,7 +87,7 @@ describe('PivotSheet Export Test', () => { } await s2.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: s2, }); @@ -131,7 +125,7 @@ describe('PivotSheet Export Test', () => { ); await s2.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: NewTab, formatOptions: true, @@ -171,7 +165,7 @@ describe('PivotSheet Export Test', () => { ); await s2.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: NewTab, formatOptions: true, @@ -218,7 +212,7 @@ describe('PivotSheet Export Test', () => { ); await s2.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: NewTab, }); @@ -258,7 +252,7 @@ describe('PivotSheet Export Test', () => { ); await s2.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: NewTab, }); @@ -298,7 +292,7 @@ describe('PivotSheet Export Test', () => { ); await s2.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: NewTab, }); @@ -341,7 +335,7 @@ describe('PivotSheet Export Test', () => { ); await s2.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: NewTab, formatOptions: { isFormatHeader: true }, @@ -377,7 +371,7 @@ describe('PivotSheet Export Test', () => { await s2.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: NewTab, formatOptions: { isFormatData: true }, @@ -427,7 +421,35 @@ describe('PivotSheet Export Test', () => { ); await s2.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ + sheetInstance: s2, + split: NewTab, + }); + + expect(data).toMatchSnapshot(); + }); + + it('should export correct data when series number', async () => { + const s2 = new PivotSheet( + getContainer(), + assembleDataCfg({ + meta: [], + fields: { + valueInCols: true, + columns: ['province', 'city'], + rows: ['type', 'sub_type'], + values: ['number'], + }, + }), + assembleOptions({ + hierarchyType: 'grid', + showSeriesNumber: true, + interaction: { enableCopy: true, copyWithHeader: true }, + }), + ); + + await s2.render(); + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: NewTab, }); @@ -436,20 +458,23 @@ describe('PivotSheet Export Test', () => { " province 浙江省 浙江省 浙江省 浙江省 四川省 四川省 四川省 四川省 city 杭州市 绍兴市 宁波市 舟山市 成都市 绵阳市 南充市 乐山市 type sub_type number number number number number number number number - 家具 - 家具 桌子 2367 3877 4342 1723 1822 1943 2330 - 家具 沙发 632 7234 834 2451 2244 2333 2445 - 办公用品 - 办公用品 笔 - 办公用品 纸张 1354 1523 1634 4004 3077 3551 352" + 家具 桌子 7789 2367 3877 4342 1723 1822 1943 2330 + 家具 沙发 5343 632 7234 834 2451 2244 2333 2445 + 办公用品 笔 945 1304 1145 1432 2335 245 2457 2458 + 办公用品 纸张 1343 1354 1523 1634 4004 3077 3551 352" `); + + const rows = data.split(NewLine); + + expect(rows[0].split(NewTab)[1]).toEqual('province'); + expect(rows[1].split(NewTab)[1]).toEqual('city'); }); - it('should export correct data when series number', async () => { + it('should export correct data with formatter', async () => { const s2 = new PivotSheet( getContainer(), assembleDataCfg({ - meta: [], + meta: [{ field: 'number', name: '数值' }], fields: { valueInCols: true, columns: ['province', 'city'], @@ -459,13 +484,12 @@ describe('PivotSheet Export Test', () => { }), assembleOptions({ hierarchyType: 'grid', - showSeriesNumber: true, interaction: { enableCopy: true, copyWithHeader: true }, }), ); await s2.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: NewTab, }); @@ -473,7 +497,7 @@ describe('PivotSheet Export Test', () => { expect(data).toMatchInlineSnapshot(` " province 浙江省 浙江省 浙江省 浙江省 四川省 四川省 四川省 四川省 city 杭州市 绍兴市 宁波市 舟山市 成都市 绵阳市 南充市 乐山市 - type sub_type number number number number number number number number + type sub_type 数值 数值 数值 数值 数值 数值 数值 数值 家具 桌子 7789 2367 3877 4342 1723 1822 1943 2330 家具 沙发 5343 632 7234 834 2451 2244 2333 2445 办公用品 笔 945 1304 1145 1432 2335 245 2457 2458 @@ -497,7 +521,7 @@ describe('PivotSheet Export Test', () => { await s2.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: NewTab, customTransformer: () => { @@ -513,11 +537,13 @@ describe('PivotSheet Export Test', () => { }); // https://github.com/antvis/S2/issues/2236 - it('should export correct data When the split separator is configured', () => { - const data = copyData({ + it('should export correct data When the split separator is configured', async () => { + const data = await asyncGetAllPlainData({ sheetInstance: pivotSheet, split: ',', - formatOptions: { isFormatHeader: true }, + formatOptions: { + isFormatHeader: true, + }, }); expect(data).toMatchInlineSnapshot(` diff --git a/packages/s2-core/__tests__/unit/utils/export/export-spec.ts b/packages/s2-core/__tests__/unit/utils/export/export-spec.ts new file mode 100644 index 0000000000..00f03a3b42 --- /dev/null +++ b/packages/s2-core/__tests__/unit/utils/export/export-spec.ts @@ -0,0 +1,473 @@ +import { asyncGetAllPlainData } from '../../../../src/utils'; +import { assembleDataCfg, assembleOptions } from '../../../util'; +import { getContainer } from '../../../util/helpers'; +import { PivotSheet, TableSheet } from '@/sheet-type'; + +describe('TableSheet Export Test', () => { + it('should export correct data with series number', async () => { + const s2 = new TableSheet( + getContainer(), + assembleDataCfg({ + meta: [ + { + field: 'type', + name: '产品类型', + formatter: (type) => (type ? `${type}产品` : ''), + }, + ], + fields: { + columns: ['province', 'city', 'type', 'sub_type', 'number'], + }, + }), + assembleOptions({ + showSeriesNumber: true, + }), + ); + + await s2.render(); + const data = await asyncGetAllPlainData({ + sheetInstance: s2, + split: '\t', + formatOptions: { + isFormatHeader: true, + }, + }); + + const rows = data.split('\n'); + const headers = rows[0].split('\t'); + + expect(rows).toHaveLength(78); + expect(rows).toMatchSnapshot(); + + // 6列数据 包括序列号 + rows.forEach((row) => { + expect(row.split('\t')).toHaveLength(6); + }); + + expect(headers).toMatchSnapshot(); + }); + + it('should export correct data with no series number', async () => { + const s2 = new TableSheet( + getContainer(), + assembleDataCfg({ + meta: [], + fields: { + columns: ['province', 'city', 'type', 'sub_type', 'number'], + }, + }), + assembleOptions({ + showSeriesNumber: false, + }), + ); + + await s2.render(); + const data = await asyncGetAllPlainData({ + sheetInstance: s2, + split: '\t', + }); + const rows = data.split('\n'); + const headers = rows[0].split('\t'); + + expect(rows).toHaveLength(78); + expect(rows).toMatchSnapshot(); + + // 5列数据 不包括序列号 + rows.forEach((e) => { + expect(e.split('\t')).toHaveLength(5); + }); + expect(headers).toMatchSnapshot(); + }); +}); + +describe('PivotSheet Export Test', () => { + it('should export correct data in grid mode', async () => { + const s2 = new PivotSheet( + getContainer(), + assembleDataCfg({ + meta: [], + fields: { + valueInCols: true, + columns: ['province', 'city', 'type', 'sub_type', 'number'], + }, + }), + assembleOptions({ hierarchyType: 'grid' }), + ); + + await s2.render(); + const data = await asyncGetAllPlainData({ + sheetInstance: s2, + split: '\t', + }); + const rows = data.split('\n'); + + expect(rows).toHaveLength(14); + expect(rows[0].split('\t')[1]).toEqual('province'); + expect(rows[1].split('\t')[1]).toEqual('city'); + + rows.forEach((e) => { + expect(e.split('\t')).toHaveLength(34); + }); + }); + + it('should export correct data in tree mode', async () => { + const s2 = new PivotSheet( + getContainer(), + assembleDataCfg({ + meta: [], + fields: { + valueInCols: true, + columns: ['province', 'city', 'type', 'sub_type', 'number'], + }, + }), + assembleOptions({ + hierarchyType: 'tree', + }), + ); + + await s2.render(); + const data = await asyncGetAllPlainData({ + sheetInstance: s2, + split: '\t', + }); + const rows = data.split('\n'); + + expect(rows).toHaveLength(16); + expect(rows[0].split('\t')[1]).toEqual('province'); + expect(rows[1].split('\t')[1]).toEqual('city'); + + rows.forEach((e) => { + expect(e.split('\t')).toHaveLength(34); + }); + }); + + // 因为导出的数据单测,很难看出问题,所以提供图片 + 代码的模式查看: + // https://gw.alipayobjects.com/zos/antfincdn/AU83KF1Sq/6fb3f3e6-0064-4ef8-a5c3-b1333fb59adf.png + it('should export correct data in tree mode and row collapseAll is true', async () => { + const s2 = new PivotSheet( + getContainer(), + assembleDataCfg(), + assembleOptions({ + hierarchyType: 'tree', + style: { + rowCell: { + collapseAll: true, + }, + }, + }), + ); + + await s2.render(); + const data = await asyncGetAllPlainData({ + sheetInstance: s2, + split: '\t', + }); + const rows = data.split('\n'); + + expect(rows).toHaveLength(5); + expect(rows[0].split('\t').length).toEqual(5); + expect(rows[0].split('\t')[0]).toEqual('类别'); + expect(rows[0].split('\t')[1]).toEqual('家具'); + expect(rows[1].split('\t')[0]).toEqual('子类别'); + expect(rows[1].split('\t')[1]).toEqual('桌子'); + expect(rows[2].split('\t')[0]).toEqual('省份'); + expect(rows[2].split('\t')[1]).toEqual('数量'); + }); + + // https://gw.alipayobjects.com/zos/antfincdn/PyrWwocNf/56d0914b-159a-4293-8615-6c1308bf4b3a.png + it('should export correct data in tree mode and hierarchyCollapse is false', async () => { + const s2 = new PivotSheet( + getContainer(), + assembleDataCfg(), + assembleOptions({ + hierarchyType: 'tree', + style: { + rowCell: { + collapseAll: false, + }, + }, + }), + ); + + await s2.render(); + const data = await asyncGetAllPlainData({ + sheetInstance: s2, + split: '\t', + }); + const rows = data.split('\n'); + + expect(rows).toHaveLength(13); + expect(rows[0].split('\t').length).toEqual(6); + expect(rows[0].split('\t')[1]).toEqual('类别'); + expect(rows[0].split('\t')[2]).toEqual('家具'); + expect(rows[1].split('\t')[1]).toEqual('子类别'); + expect(rows[1].split('\t')[2]).toEqual('桌子'); + expect(rows[2].split('\t')[0]).toEqual('省份'); + expect(rows[2].split('\t')[1]).toEqual('城市'); + expect(rows[2].split('\t')[2]).toEqual('数量'); + }); + + it('should export correct data in grid mode with valueInCols is false', async () => { + const s2 = new PivotSheet( + getContainer(), + assembleDataCfg({ + meta: [], + fields: { + valueInCols: false, + columns: ['province', 'city', 'type', 'sub_type', 'number'], + }, + }), + assembleOptions({ + hierarchyType: 'grid', + }), + ); + + await s2.render(); + const data = await asyncGetAllPlainData({ + sheetInstance: s2, + split: '\t', + }); + const rows = data.split('\n'); + + expect(rows).toHaveLength(13); + rows.forEach((e) => { + expect(e.split('\t')).toHaveLength(35); + }); + }); + + it('should export correct data in grid mode with totals in col', async () => { + const s2 = new PivotSheet( + getContainer(), + assembleDataCfg({ + fields: { + valueInCols: true, + columns: ['province', 'city', 'type', 'sub_type', 'number'], + }, + }), + assembleOptions({ + hierarchyType: 'grid', + totals: { + row: { + showGrandTotals: true, + showSubTotals: true, + subTotalsDimensions: ['province'], + }, + col: { + showGrandTotals: true, + showSubTotals: true, + subTotalsDimensions: ['type'], + }, + }, + }), + ); + + await s2.render(); + const data = await asyncGetAllPlainData({ + sheetInstance: s2, + split: '\t', + }); + const rows = data.split('\n'); + + expect(rows).toHaveLength(17); + rows.forEach((e) => { + expect(e.split('\t')).toHaveLength(53); + }); + }); + + it('should export correct data in grid mode with grouped totals in col', async () => { + const s2 = new PivotSheet( + getContainer(), + assembleDataCfg({ + fields: { + valueInCols: true, + columns: ['province', 'city', 'type', 'sub_type', 'number'], + }, + }), + assembleOptions({ + hierarchyType: 'grid', + totals: { + row: { + showGrandTotals: true, + showSubTotals: true, + subTotalsDimensions: ['province'], + }, + col: { + grandTotalsGroupDimensions: ['city', 'type'], + subTotalsGroupDimensions: ['sub_type'], + showGrandTotals: true, + showSubTotals: true, + subTotalsDimensions: ['type'], + }, + }, + }), + ); + + await s2.render(); + const data = await asyncGetAllPlainData({ + sheetInstance: s2, + split: '\t', + }); + const rows = data.split('\n'); + + expect(rows).toHaveLength(17); + rows.forEach((e) => { + expect(e.split('\t')).toHaveLength(60); + }); + }); + + it('should export correct data in grid mode with grouped totals in row', async () => { + const s2 = new PivotSheet( + getContainer(), + assembleDataCfg({ + fields: { + valueInCols: false, + columns: ['province', 'city', 'type', 'sub_type', 'number'], + }, + }), + assembleOptions({ + hierarchyType: 'grid', + totals: { + row: { + showGrandTotals: true, + showSubTotals: true, + subTotalsDimensions: ['province'], + }, + col: { + grandTotalsGroupDimensions: ['city', 'sub_type', 'province'], + subTotalsGroupDimensions: ['sub_type'], + showGrandTotals: true, + showSubTotals: true, + subTotalsDimensions: ['type'], + }, + }, + }), + ); + + await s2.render(); + const data = await asyncGetAllPlainData({ + sheetInstance: s2, + split: '\t', + }); + const rows = data.split('\n'); + + expect(rows).toHaveLength(16); + rows.forEach((e) => { + expect(e.split('\t')).toHaveLength(63); + }); + }); + it('should export correct data in grid mode with totals in row', async () => { + const s2 = new PivotSheet( + getContainer(), + assembleDataCfg({ + fields: { + valueInCols: false, + columns: ['province', 'city', 'type', 'sub_type', 'number'], + }, + }), + assembleOptions({ + hierarchyType: 'grid', + totals: { + row: { + showGrandTotals: true, + showSubTotals: true, + subTotalsDimensions: ['province'], + }, + col: { + showGrandTotals: true, + showSubTotals: true, + subTotalsDimensions: ['type'], + }, + }, + }), + ); + + await s2.render(); + const data = await asyncGetAllPlainData({ + sheetInstance: s2, + split: '\t', + }); + const rows = data.split('\n'); + + expect(rows).toHaveLength(16); + rows.forEach((e) => { + expect(e.split('\t')).toHaveLength(54); + }); + }); + + it('should export correct data when isFormat: {isFormatHeader: true}', async () => { + const s2 = new PivotSheet( + getContainer(), + assembleDataCfg({ + meta: [ + { + field: 'province', + formatter: (value) => { + return `${value}-province`; + }, + }, + { + field: 'type', + formatter: (value) => { + return `${value}-type`; + }, + }, + ], + fields: { + valueInCols: true, + columns: ['province', 'city'], + rows: ['type', 'sub_type'], + values: ['number'], + }, + }), + assembleOptions({}), + ); + + await s2.render(); + const data = await asyncGetAllPlainData({ + sheetInstance: s2, + split: '\t', + formatOptions: { + isFormatHeader: true, + }, + }); + const rows = data.split('\n'); + + expect(rows).toHaveLength(7); + expect(rows[0].split('\t')[1]).toEqual('province'); + expect(rows[0].split('\t')[2]).toEqual('浙江省-province'); + expect(rows[1].split('\t')[1]).toEqual('city'); + expect(rows[3].split('\t')[0]).toEqual('家具-type'); + }); + + it('should export correct $$extra$$ field name', async () => { + const s2 = new PivotSheet( + getContainer(), + assembleDataCfg({ + meta: [ + { + field: 'number', + name: '数值', + }, + ], + fields: { + valueInCols: true, + columns: ['province', 'city'], + rows: ['type', 'sub_type'], + values: ['number'], + }, + }), + assembleOptions({}), + ); + + await s2.render(); + const data = await asyncGetAllPlainData({ + sheetInstance: s2, + split: '\t', + formatOptions: false, + }); + const rows = data.split('\n'); + const headers = rows[2].split('\t'); + + expect(headers).toMatchSnapshot(); + }); +}); diff --git a/packages/s2-core/__tests__/unit/utils/export/export-table-spec.ts b/packages/s2-core/__tests__/unit/utils/export/export-table-spec.ts index fca32be09c..726ebc946d 100644 --- a/packages/s2-core/__tests__/unit/utils/export/export-table-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/export/export-table-spec.ts @@ -3,9 +3,9 @@ import { data as originData } from 'tests/data/mock-dataset.json'; import { assembleDataCfg, assembleOptions } from '../../../util'; import { getContainer } from '../../../util/helpers'; import { TableSheet } from '@/sheet-type'; -import { asyncGetAllPlainData, copyData } from '@/utils'; +import { asyncGetAllPlainData } from '@/utils'; import { NewTab, NewLine } from '@/common'; -import { CopyMIMEType } from '@/utils/export/interface'; +import { CopyMIMEType } from '@/common/interface/export'; describe('TableSheet Export Test', () => { it('should export correct data with series number', async () => { @@ -61,7 +61,7 @@ describe('TableSheet Export Test', () => { ]); } - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: NewTab, formatOptions: { @@ -99,7 +99,7 @@ describe('TableSheet Export Test', () => { ); await s2.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: NewTab, }); @@ -140,7 +140,7 @@ describe('TableSheet Export Test', () => { ); await s2.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: NewTab, formatOptions: true, @@ -155,6 +155,7 @@ describe('TableSheet Export Test', () => { 浙江省-province 家具-type 沙发 5343" `); }); + it('should support custom export matrix transformer', async () => { const s2 = new TableSheet( getContainer(), @@ -170,7 +171,7 @@ describe('TableSheet Export Test', () => { ); await s2.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: s2, split: NewTab, formatOptions: true, @@ -200,7 +201,7 @@ describe('TableSheet Export Test', () => { ); await tableSheet.render(); - const data = copyData({ + const data = await asyncGetAllPlainData({ sheetInstance: tableSheet, split: ',', }); diff --git a/packages/s2-core/__tests__/unit/utils/export/index-spec.ts b/packages/s2-core/__tests__/unit/utils/export/utils-spec.ts similarity index 94% rename from packages/s2-core/__tests__/unit/utils/export/index-spec.ts rename to packages/s2-core/__tests__/unit/utils/export/utils-spec.ts index acf738bb79..547081bb92 100644 --- a/packages/s2-core/__tests__/unit/utils/export/index-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/export/utils-spec.ts @@ -1,6 +1,6 @@ -import { copyToClipboard } from '@/utils/export'; +import { copyToClipboard } from '@/utils/export/utils'; -describe('Copy Tests', () => { +describe('Export & Copy Utils Tests', () => { test('should async copy text to clipboard', async () => { const text = '222'; diff --git a/packages/s2-core/__tests__/unit/utils/facet-spec.ts b/packages/s2-core/__tests__/unit/utils/facet-spec.ts index 8267cdcb2b..b44bff8a28 100644 --- a/packages/s2-core/__tests__/unit/utils/facet-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/facet-spec.ts @@ -1,30 +1,10 @@ import { - getSubTotalNodeWidthOrHeightByLevel, getIndexRangeWithOffsets, getAdjustedRowScrollX, getAdjustedScrollOffset, } from '@/utils/facet'; describe('Facet util test', () => { - test('should get correct width of subTotal node', () => { - const sampleNodesForAllLevels = [ - { - id: 'root[&]测试', - value: '测试', - isSubTotals: true, - width: 20, - level: 0, - }, - ]; - - expect( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - getSubTotalNodeWidthOrHeightByLevel(sampleNodesForAllLevels, -1, 'width'), - ).toEqual(20); - expect(getSubTotalNodeWidthOrHeightByLevel([], -1, 'width')).toEqual(0); - }); - test('should get correct index range for given offsets', () => { const offsets = [0, 30, 60, 90, 120, 150, 160, 170, 190]; diff --git a/packages/s2-core/__tests__/unit/utils/g-mini-charts-spec.ts b/packages/s2-core/__tests__/unit/utils/g-mini-charts-spec.ts index c560ca9177..5e88113ef6 100644 --- a/packages/s2-core/__tests__/unit/utils/g-mini-charts-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/g-mini-charts-spec.ts @@ -3,7 +3,7 @@ import { getContainer } from 'tests/util/helpers'; import { forEach, map } from 'lodash'; import { data } from 'tests/data/mock-dataset.json'; import type { RangeColors } from '../../../src/common/interface/theme'; -import { PivotSheet } from '@/sheet-type'; +import { PivotSheet, SpreadSheet } from '@/sheet-type'; import { CellType, MiniChartTypes, type S2CellType } from '@/common'; import { getBulletRangeColor, @@ -312,6 +312,7 @@ describe('MiniCharts Utils Tests', () => { }); describe('drawInterval Test', () => { + let s2: SpreadSheet; const dataCfg = assembleDataCfg({ meta: [], fields: { @@ -324,6 +325,7 @@ describe('drawInterval Test', () => { const horizontalBorderWidth = getTheme({})?.dataCell?.cell?.horizontalBorderWidth ?? 1; + const options = assembleOptions({ style: { dataCell: { @@ -334,9 +336,8 @@ describe('drawInterval Test', () => { conditions: {}, }); - const s2 = new PivotSheet(getContainer(), dataCfg, options); - beforeEach(async () => { + s2 = new PivotSheet(getContainer(), dataCfg, options); await s2.render(); }); diff --git a/packages/s2-core/__tests__/unit/utils/hide-columns-spec.ts b/packages/s2-core/__tests__/unit/utils/hide-columns-spec.ts index fd08727dc0..03c02f07c8 100644 --- a/packages/s2-core/__tests__/unit/utils/hide-columns-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/hide-columns-spec.ts @@ -56,6 +56,10 @@ describe('Hide Columns Tests', () => { mockSpreadSheetInstance.render = jest.fn(); mockSpreadSheetInstance.interaction = { reset: jest.fn(), + isSelectedState: jest.fn(), + intercepts: new Set(), + getActiveCells: jest.fn(() => []), + clearHoverTimer: jest.fn(), } as unknown as RootInteraction; mockSpreadSheetInstance.isTableMode = () => true; mockSpreadSheetInstance.isPivotMode = () => false; @@ -344,6 +348,427 @@ describe('Hide Columns Tests', () => { ]); }); + // https://github.com/antvis/S2/issues/2194 + test('should hidden group columns for fields (5 => 2)', async () => { + for (const field of ['5', '4', '3', '2']) { + // eslint-disable-next-line no-await-in-loop + await hideColumnsByThunkGroup(mockSpreadSheetInstance, [field]); + } + + expect(mockSpreadSheetInstance.store.get('hiddenColumnsDetail')) + .toMatchInlineSnapshot(` + Array [ + Object { + "displaySiblingNode": Object { + "next": null, + "prev": Object { + "colIndex": 4, + "field": "4", + "id": "id-4", + }, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 5, + "field": "5", + "id": "id-5", + }, + ], + }, + Object { + "displaySiblingNode": Object { + "next": null, + "prev": Object { + "colIndex": 3, + "field": "3", + "id": "id-3", + }, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 4, + "field": "4", + "id": "id-4", + }, + ], + }, + Object { + "displaySiblingNode": Object { + "next": null, + "prev": Object { + "colIndex": 2, + "field": "2", + "id": "id-2", + }, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 3, + "field": "3", + "id": "id-3", + }, + ], + }, + Object { + "displaySiblingNode": Object { + "next": null, + "prev": Object { + "colIndex": 1, + "field": "1", + "id": "id-1", + }, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 2, + "field": "2", + "id": "id-2", + }, + ], + }, + ] + `); + }); + + test('should hidden group columns for fields (3 => 5)', async () => { + await hideColumnsByThunkGroup(mockSpreadSheetInstance, ['3']); + await hideColumnsByThunkGroup(mockSpreadSheetInstance, ['5']); + + expect(mockSpreadSheetInstance.store.get('hiddenColumnsDetail')) + .toMatchInlineSnapshot(` + Array [ + Object { + "displaySiblingNode": Object { + "next": Object { + "colIndex": 4, + "field": "4", + "id": "id-4", + }, + "prev": Object { + "colIndex": 2, + "field": "2", + "id": "id-2", + }, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 3, + "field": "3", + "id": "id-3", + }, + ], + }, + Object { + "displaySiblingNode": Object { + "next": null, + "prev": Object { + "colIndex": 4, + "field": "4", + "id": "id-4", + }, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 5, + "field": "5", + "id": "id-5", + }, + ], + }, + ] + `); + }); + + test('should get empty next sibling nodes when always hidden last column for fields (1 => 2)', async () => { + await hideColumnsByThunkGroup(mockSpreadSheetInstance, ['1']); + await hideColumnsByThunkGroup(mockSpreadSheetInstance, ['2']); + + expect(mockSpreadSheetInstance.store.get('hiddenColumnsDetail')) + .toMatchInlineSnapshot(` + Array [ + Object { + "displaySiblingNode": Object { + "next": Object { + "colIndex": 2, + "field": "2", + "id": "id-2", + }, + "prev": null, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 1, + "field": "1", + "id": "id-1", + }, + ], + }, + Object { + "displaySiblingNode": Object { + "next": Object { + "colIndex": 3, + "field": "3", + "id": "id-3", + }, + "prev": null, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 2, + "field": "2", + "id": "id-2", + }, + ], + }, + ] + `); + }); + + test('should get empty next sibling nodes when always hidden last column for fields (1 => 3)', async () => { + await hideColumnsByThunkGroup(mockSpreadSheetInstance, ['1']); + await hideColumnsByThunkGroup(mockSpreadSheetInstance, ['3']); + + expect(mockSpreadSheetInstance.store.get('hiddenColumnsDetail')) + .toMatchInlineSnapshot(` + Array [ + Object { + "displaySiblingNode": Object { + "next": Object { + "colIndex": 2, + "field": "2", + "id": "id-2", + }, + "prev": null, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 1, + "field": "1", + "id": "id-1", + }, + ], + }, + Object { + "displaySiblingNode": Object { + "next": Object { + "colIndex": 4, + "field": "4", + "id": "id-4", + }, + "prev": Object { + "colIndex": 2, + "field": "2", + "id": "id-2", + }, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 3, + "field": "3", + "id": "id-3", + }, + ], + }, + ] + `); + }); + + test('should get empty next sibling nodes when always hidden last column for fields (5 => 4)', async () => { + await hideColumnsByThunkGroup(mockSpreadSheetInstance, ['5']); + await hideColumnsByThunkGroup(mockSpreadSheetInstance, ['4']); + + expect(mockSpreadSheetInstance.store.get('hiddenColumnsDetail')) + .toMatchInlineSnapshot(` + Array [ + Object { + "displaySiblingNode": Object { + "next": null, + "prev": Object { + "colIndex": 4, + "field": "4", + "id": "id-4", + }, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 5, + "field": "5", + "id": "id-5", + }, + ], + }, + Object { + "displaySiblingNode": Object { + "next": null, + "prev": Object { + "colIndex": 3, + "field": "3", + "id": "id-3", + }, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 4, + "field": "4", + "id": "id-4", + }, + ], + }, + ] + `); + }); + + test('should get correctly sibling nodes when hidden first and last column for fields (1 => 5)', async () => { + await hideColumnsByThunkGroup(mockSpreadSheetInstance, ['1']); + await hideColumnsByThunkGroup(mockSpreadSheetInstance, ['5']); + + expect(mockSpreadSheetInstance.store.get('hiddenColumnsDetail')) + .toMatchInlineSnapshot(` + Array [ + Object { + "displaySiblingNode": Object { + "next": Object { + "colIndex": 2, + "field": "2", + "id": "id-2", + }, + "prev": null, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 1, + "field": "1", + "id": "id-1", + }, + ], + }, + Object { + "displaySiblingNode": Object { + "next": null, + "prev": Object { + "colIndex": 4, + "field": "4", + "id": "id-4", + }, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 5, + "field": "5", + "id": "id-5", + }, + ], + }, + ] + `); + }); + + test('should get correctly sibling nodes when hidden odd columns for fields (2 => 4)', async () => { + await hideColumnsByThunkGroup(mockSpreadSheetInstance, ['2']); + await hideColumnsByThunkGroup(mockSpreadSheetInstance, ['4']); + + expect(mockSpreadSheetInstance.store.get('hiddenColumnsDetail')) + .toMatchInlineSnapshot(` + Array [ + Object { + "displaySiblingNode": Object { + "next": Object { + "colIndex": 3, + "field": "3", + "id": "id-3", + }, + "prev": Object { + "colIndex": 1, + "field": "1", + "id": "id-1", + }, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 2, + "field": "2", + "id": "id-2", + }, + ], + }, + Object { + "displaySiblingNode": Object { + "next": Object { + "colIndex": 5, + "field": "5", + "id": "id-5", + }, + "prev": Object { + "colIndex": 3, + "field": "3", + "id": "id-3", + }, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 4, + "field": "4", + "id": "id-4", + }, + ], + }, + ] + `); + }); + + test('should get correctly sibling nodes when hidden near columns for fields (2 => 3)', async () => { + await hideColumnsByThunkGroup(mockSpreadSheetInstance, ['2']); + await hideColumnsByThunkGroup(mockSpreadSheetInstance, ['3']); + + expect(mockSpreadSheetInstance.store.get('hiddenColumnsDetail')) + .toMatchInlineSnapshot(` + Array [ + Object { + "displaySiblingNode": Object { + "next": Object { + "colIndex": 3, + "field": "3", + "id": "id-3", + }, + "prev": Object { + "colIndex": 1, + "field": "1", + "id": "id-1", + }, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 2, + "field": "2", + "id": "id-2", + }, + ], + }, + Object { + "displaySiblingNode": Object { + "next": Object { + "colIndex": 4, + "field": "4", + "id": "id-4", + }, + "prev": Object { + "colIndex": 1, + "field": "1", + "id": "id-1", + }, + }, + "hideColumnNodes": Array [ + Object { + "colIndex": 3, + "field": "3", + "id": "id-3", + }, + ], + }, + ] + `); + }); + test('should skip hidden group columns if hidden column fields not change', async () => { await hideColumnsByThunkGroup(mockSpreadSheetInstance, []); diff --git a/packages/s2-core/__tests__/unit/utils/interaction/hover-event-spec.ts b/packages/s2-core/__tests__/unit/utils/interaction/hover-event-spec.ts index 391d77684a..589b1c90d4 100644 --- a/packages/s2-core/__tests__/unit/utils/interaction/hover-event-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/interaction/hover-event-spec.ts @@ -27,7 +27,7 @@ jest.mock('@/cell', () => { }); describe('Hover Event Utils Tests', () => { - describe('getActiveHoverRowColCells test', () => { + describe('#getActiveHoverRowColCells', () => { test('should return correct result for getActiveHoverRowColCells', () => { const cells = [ new ColCell({} as unknown as Node, {} as unknown as SpreadSheet), @@ -45,7 +45,7 @@ describe('Hover Event Utils Tests', () => { }); }); - describe('updateAllColHeaderCellState test', () => { + describe('#updateAllColHeaderCellState', () => { test('should return correct result for updateAllColHeaderCellState', () => { const cells = [ new ColCell({} as unknown as Node, {} as unknown as SpreadSheet), diff --git a/packages/s2-core/__tests__/unit/utils/interaction/resize-spec.ts b/packages/s2-core/__tests__/unit/utils/interaction/resize-spec.ts index 24059c0f90..8da6764df0 100644 --- a/packages/s2-core/__tests__/unit/utils/interaction/resize-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/interaction/resize-spec.ts @@ -33,7 +33,7 @@ describe('Resize Utils Tests', () => { let s2: SpreadSheet; - beforeAll(() => { + beforeEach(() => { MockSpreadSheet.mockClear(); s2 = new MockSpreadSheet(); diff --git a/packages/s2-core/__tests__/unit/utils/interaction/state-controller-spec.ts b/packages/s2-core/__tests__/unit/utils/interaction/state-controller-spec.ts index b7f16f6d51..93faee639f 100644 --- a/packages/s2-core/__tests__/unit/utils/interaction/state-controller-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/interaction/state-controller-spec.ts @@ -1,10 +1,10 @@ -import { getCellMeta } from '@/utils/interaction/select-event'; -import type { RowCell } from '@/cell/row-cell'; +import type { RowCell } from '../../../../src/cell'; import { CellType, InteractionStateName } from '@/common/constant/interaction'; import type { S2Options } from '@/common/interface'; import { Store } from '@/common/store'; import { RootInteraction } from '@/interaction/root'; import { SpreadSheet } from '@/sheet-type'; +import { getCellMeta } from '@/utils/interaction/select-event'; import { clearState, setState } from '@/utils/interaction/state-controller'; jest.mock('@/sheet-type'); diff --git a/packages/s2-core/__tests__/unit/utils/merge-cell-spec.ts b/packages/s2-core/__tests__/unit/utils/merge-cell-spec.ts index e33ef59157..c5c70a4069 100644 --- a/packages/s2-core/__tests__/unit/utils/merge-cell-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/merge-cell-spec.ts @@ -20,12 +20,11 @@ import { import type { RootInteraction } from '@/interaction/root'; import type { MergedCellInfo, - S2CellType, TempMergedCell, ViewMeta, } from '@/common/interface'; import type { BaseFacet } from '@/facet'; -import type { MergedCell } from '@/cell'; +import type { DataCell, MergedCell } from '@/cell'; jest.mock('@/sheet-type'); @@ -35,7 +34,7 @@ describe('Merge Cells Test', () => { let mockOneCellEdges: [number, number][][] = []; let mockTwoCellEdges: [number, number][][] = []; let mockMergeCellInfo: MergedCellInfo[] = []; - let mockAllVisibleCells: S2CellType[] = []; + let mockAllVisibleCells: DataCell[] = []; beforeEach(() => { mockInstance = new MockSpreadSheet(); @@ -112,7 +111,7 @@ describe('Merge Cells Test', () => { mockAllVisibleCells = [ { getMeta: jest.fn().mockReturnValue(mockMergeCellInfo[2]) }, { getMeta: jest.fn().mockReturnValue(mockMergeCellInfo[3]) }, - ] as unknown as S2CellType[]; + ] as unknown as DataCell[]; }); test('should get none active cells info', () => { @@ -204,7 +203,7 @@ describe('Merge Cells Test', () => { }, ]; - expect(getPolygonPoints(mockCells as unknown as S2CellType[])).toEqual( + expect(getPolygonPoints(mockCells as unknown as DataCell[])).toEqual( mockResult, ); }); diff --git a/packages/s2-core/__tests__/unit/utils/sort-action-spec.tsx b/packages/s2-core/__tests__/unit/utils/sort-action-spec.tsx index 3f0c6bc0e5..1031a7f419 100644 --- a/packages/s2-core/__tests__/unit/utils/sort-action-spec.tsx +++ b/packages/s2-core/__tests__/unit/utils/sort-action-spec.tsx @@ -14,7 +14,7 @@ import { type S2DataConfig, VALUE_FIELD, } from '@/common'; -import { PivotSheet } from '@/sheet-type'; +import { PivotSheet, SpreadSheet } from '@/sheet-type'; import { PivotDataSet, type SortActionParams } from '@/data-set'; import { CellData } from '@/data-set/cell-data'; @@ -509,6 +509,10 @@ describe('GetSortByMeasureValues Total Fallback Tests', () => { sheet.dataSet = dataSet; }); + afterEach(() => { + sheet.destroy(); + }); + test('should sort by col total', () => { // 根据列(类别)的总和排序 const sortParam: SortParam = { @@ -531,7 +535,6 @@ describe('GetSortByMeasureValues Total Fallback Tests', () => { { price: 41.5, type: '纸张', - [EXTRA_FIELD]: 'price', }, 'price', ), @@ -539,7 +542,6 @@ describe('GetSortByMeasureValues Total Fallback Tests', () => { { price: 37, type: '笔', - [EXTRA_FIELD]: 'price', }, 'price', ), @@ -567,7 +569,6 @@ describe('GetSortByMeasureValues Total Fallback Tests', () => { { price: 33, province: '吉林', - [EXTRA_FIELD]: 'price', }, 'price', ), @@ -575,7 +576,6 @@ describe('GetSortByMeasureValues Total Fallback Tests', () => { { price: 45.5, province: '浙江', - [EXTRA_FIELD]: 'price', }, 'price', ), @@ -675,7 +675,6 @@ describe('GetSortByMeasureValues Total Fallback Tests', () => { province: '浙江', city: '杭州', price: 3, - [EXTRA_FIELD]: 'price', }, 'price', ), @@ -684,7 +683,6 @@ describe('GetSortByMeasureValues Total Fallback Tests', () => { province: '浙江', city: '舟山', price: 42.5, - [EXTRA_FIELD]: 'price', }, 'price', ), @@ -693,7 +691,6 @@ describe('GetSortByMeasureValues Total Fallback Tests', () => { province: '吉林', city: '长春', price: 13, - [EXTRA_FIELD]: 'price', }, 'price', ), @@ -702,7 +699,6 @@ describe('GetSortByMeasureValues Total Fallback Tests', () => { province: '吉林', city: '白山', price: 20, - [EXTRA_FIELD]: 'price', }, 'price', ), @@ -772,3 +768,67 @@ describe('GetSortByMeasureValues Total Fallback Tests', () => { ]); }); }); + +describe('total group dimension sort test', () => { + let sheet: SpreadSheet; + + beforeEach(() => { + const currentOptions = { + totals: { + col: { + grandTotalsGroupDimensions: ['city'], + showGrandTotals: true, + }, + }, + } as S2Options; + + const dataConfig = { + ...sortData, + data: [ + ...sortData.data, + { + city: '杭州', + type: '纸张', + price: '999', + }, + { + city: '杭州', + type: '笔', + price: '666', + }, + ], + fields: { + rows: ['type'], + columns: ['province', 'city'], + values: ['price'], + }, + }; + + sheet = new PivotSheet(getContainer(), dataConfig, currentOptions); + sheet.render(); + }); + + afterEach(() => { + sheet.destroy(); + }); + test('should sort by col total with group', () => { + // 根据列(类别)的总和排序 + const sortParam: SortParam = { + sortFieldId: 'type', + sortByMeasure: TOTAL_VALUE, + sortMethod: 'desc', + query: { + [EXTRA_FIELD]: 'price', + city: '杭州', + }, + }; + + const params: SortActionParams = { + dataSet: sheet.dataSet as PivotDataSet, + sortParam, + }; + const measureValues = getSortByMeasureValues(params); + + expect(measureValues).toMatchSnapshot(); + }); +}); diff --git a/packages/s2-core/__tests__/unit/utils/text-spec.ts b/packages/s2-core/__tests__/unit/utils/text-spec.ts index 10cf637e1a..4829de77ab 100644 --- a/packages/s2-core/__tests__/unit/utils/text-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/text-spec.ts @@ -8,6 +8,8 @@ import { getCellWidth, getEmptyPlaceholder, getContentAreaForMultiData, + isZeroOrEmptyValue, + isUnchangedValue, } from '@/utils/text'; const isHD = window.devicePixelRatio >= 2; @@ -274,3 +276,70 @@ describe('Text Utils Tests', () => { ]); }); }); + +describe('isZeroOrEmptyValue', () => { + test('should return true for zero values', () => { + expect(isZeroOrEmptyValue('0.00%')).toBe(true); + expect(isZeroOrEmptyValue('-0.00%')).toBe(true); + expect(isZeroOrEmptyValue('0.0万亿')).toBe(true); + expect(isZeroOrEmptyValue('-0.0万亿')).toBe(true); + expect(isZeroOrEmptyValue('0.00万')).toBe(true); + expect(isZeroOrEmptyValue('-0.00万')).toBe(true); + expect(isZeroOrEmptyValue('0')).toBe(true); + expect(isZeroOrEmptyValue('-0')).toBe(true); + expect(isZeroOrEmptyValue(0)).toBe(true); + expect(isZeroOrEmptyValue(-0)).toBe(true); + }); + + test('should return false for non-zero values', () => { + expect(isZeroOrEmptyValue('0.5%')).toBe(false); + expect(isZeroOrEmptyValue('-0.5%')).toBe(false); + expect(isZeroOrEmptyValue('0.01万亿')).toBe(false); + expect(isZeroOrEmptyValue('-0.01万亿')).toBe(false); + expect(isZeroOrEmptyValue('1')).toBe(false); + expect(isZeroOrEmptyValue('-1')).toBe(false); + expect(isZeroOrEmptyValue(0.1)).toBe(false); + expect(isZeroOrEmptyValue(-0.1)).toBe(false); + }); + + test('should return true for non-numeric values', () => { + expect(isZeroOrEmptyValue('abc')).toBe(true); + expect(isZeroOrEmptyValue('')).toBe(true); + expect(isZeroOrEmptyValue(null as any)).toBe(true); + expect(isZeroOrEmptyValue(undefined as any)).toBe(true); + }); +}); + +describe('isUnchangedValue', () => { + test('should return true for zero values', () => { + expect(isUnchangedValue(0, 123)).toBeTruthy(); + expect(isUnchangedValue('0', 'abc')).toBeTruthy(); + }); + + test('should return true for empty values', () => { + expect(isUnchangedValue('', 'abc')).toBeTruthy(); + expect(isUnchangedValue(null as any, 123)).toBeTruthy(); + expect(isUnchangedValue(undefined as any, 123)).toBeTruthy(); + }); + + test('should return true for unchanged values', () => { + expect(isUnchangedValue('test', 'test')).toBeTruthy(); + expect(isUnchangedValue(123, 123)).toBeTruthy(); + }); + + test('should return true for numberless changed values', () => { + expect(isUnchangedValue('test', 'abc')).toBeTruthy(); + }); + + test('should return false for numeric changed values', () => { + expect(isUnchangedValue(123, 456)).toBeFalsy(); + }); + + test('should return true for negative zero', () => { + expect(isUnchangedValue(-0, 123)).toBeTruthy(); + }); + + test('should return false for negative values', () => { + expect(isUnchangedValue(-123, 123)).toBeFalsy(); + }); +}); diff --git a/packages/s2-core/__tests__/unit/utils/tooltip-spec.ts b/packages/s2-core/__tests__/unit/utils/tooltip-spec.ts index d0a348b6a5..00cbc509af 100644 --- a/packages/s2-core/__tests__/unit/utils/tooltip-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/tooltip-spec.ts @@ -2,9 +2,9 @@ import { createFakeSpreadSheet, createMockCellInfo, createPivotSheet, + createTableSheet, getContainer, } from 'tests/util/helpers'; - import { omit } from 'lodash'; import * as dataConfig from 'tests/data/mock-dataset.json'; import { CellData } from '@/data-set/cell-data'; @@ -62,7 +62,7 @@ describe('Tooltip Utils Tests', () => { height: 1000, }; - beforeAll(() => { + beforeEach(() => { s2 = createFakeSpreadSheet(); tooltipContainer = { getBoundingClientRect: () => @@ -873,6 +873,159 @@ describe('Tooltip Utils Tests', () => { }, ); }); + + describe('Tooltip Get Data Tests For TableSheet', () => { + beforeEach(() => { + s2 = createTableSheet( + { showSeriesNumber: true }, + { useSimpleData: false }, + ); + s2.render(); + }); + + afterEach(() => { + s2.destroy(); + }); + + test('should get correctly summaries of selected col cell', () => { + const typeColCell = s2.facet.getColLeafNodes()[1].belongsCell!; + const subTypeColCell = s2.facet.getColLeafNodes()[2].belongsCell!; + + expect(getMockTooltipData(typeColCell)).toMatchInlineSnapshot(` + Object { + "description": "类别说明。。", + "details": null, + "headInfo": null, + "infos": undefined, + "interpretation": undefined, + "name": null, + "summaries": Array [ + Object { + "name": "", + "selectedData": Array [ + "家具", + "家具", + "家具", + "家具", + "家具", + "家具", + "家具", + "家具", + "办公用品", + "办公用品", + "办公用品", + "办公用品", + "办公用品", + "办公用品", + "办公用品", + "办公用品", + "家具", + "家具", + "家具", + "家具", + "家具", + "家具", + "家具", + "家具", + "办公用品", + "办公用品", + "办公用品", + "办公用品", + "办公用品", + "办公用品", + "办公用品", + "办公用品", + ], + "value": "", + }, + ], + "tips": undefined, + } + `); + expect(getMockTooltipData(subTypeColCell)).toMatchInlineSnapshot(` + Object { + "description": "子类别说明。。", + "details": null, + "headInfo": null, + "infos": undefined, + "interpretation": undefined, + "name": null, + "summaries": Array [ + Object { + "name": "", + "selectedData": Array [ + "桌子", + "桌子", + "桌子", + "桌子", + "沙发", + "沙发", + "沙发", + "沙发", + "笔", + "笔", + "笔", + "笔", + "纸张", + "纸张", + "纸张", + "纸张", + "桌子", + "桌子", + "桌子", + "桌子", + "沙发", + "沙发", + "沙发", + "沙发", + "笔", + "笔", + "笔", + "笔", + "纸张", + "纸张", + "纸张", + "纸张", + ], + "value": "", + }, + ], + "tips": undefined, + } + `); + }); + + test('should get correctly summaries of selected series number cell', () => { + const seriesCell = s2.facet.getDataCells()[0]; + + expect(getMockTooltipData(seriesCell)).toMatchInlineSnapshot(` + Object { + "description": undefined, + "details": null, + "headInfo": null, + "infos": undefined, + "interpretation": undefined, + "name": null, + "summaries": Array [ + Object { + "name": "", + "selectedData": Array [ + Object { + "city": "杭州市", + "number": 7789, + "province": "浙江省", + "sub_type": "桌子", + "type": "家具", + }, + ], + "value": "", + }, + ], + "tips": undefined, + } + `); + }); + }); }); test('should set container style', () => { diff --git a/packages/s2-core/__tests__/util/helpers.ts b/packages/s2-core/__tests__/util/helpers.ts index 35e9226cda..596827ab0f 100644 --- a/packages/s2-core/__tests__/util/helpers.ts +++ b/packages/s2-core/__tests__/util/helpers.ts @@ -7,21 +7,31 @@ import { FederatedMouseEvent, FederatedPointerEvent, type CanvasConfig, + Group, } from '@antv/g'; import { omit } from 'lodash'; import * as simpleDataConfig from 'tests/data/simple-data.json'; import * as dataConfig from 'tests/data/mock-dataset.json'; import { Renderer } from '@antv/g-canvas'; -import type { BaseDataSet, Node } from '../../src'; +import { getTheme, type BaseDataSet, type Node, Hierarchy } from '../../src'; + +import { assembleOptions, assembleDataCfg } from '.'; import { RootInteraction } from '@/interaction/root'; import { Store } from '@/common/store'; -import type { S2CellType, S2Options, ViewMeta } from '@/common/interface'; +import type { + InternalFullyTheme, + LayoutResult, + S2CellType, + S2DataConfig, + S2Options, + ViewMeta, +} from '@/common/interface'; import { PivotSheet, SpreadSheet, TableSheet } from '@/sheet-type'; import type { BaseTooltip } from '@/ui/tooltip'; import { customMerge } from '@/utils/merge'; -import { DEFAULT_OPTIONS } from '@/common/constant'; +import { DEFAULT_OPTIONS, FrozenGroupType } from '@/common/constant'; import type { BaseFacet } from '@/facet'; -import type { PanelBBox } from '@/facet/bbox/panelBBox'; +import type { PanelBBox } from '@/facet/bbox/panel-bbox'; export const parseCSV = (csv: string, header?: string[]) => { const DELIMITER = ','; @@ -53,7 +63,11 @@ export const sleep = async (timeout = 0) => { }); }; -export const createFakeSpreadSheet = () => { +export const createFakeSpreadSheet = (config?: { + s2Options?: Partial<S2Options>; + s2DataConfig?: Partial<S2DataConfig>; +}) => { + const { s2Options = {}, s2DataConfig = {} } = config || {}; const container = getContainer(); class FakeSpreadSheet extends EE { @@ -66,20 +80,15 @@ export const createFakeSpreadSheet = () => { const s2 = new FakeSpreadSheet() as unknown as SpreadSheet; - s2.options = { - ...DEFAULT_OPTIONS, - hdAdapter: false, - }; - s2.dataCfg = { - meta: [], - data: [], - fields: { - rows: [], - columns: [], - values: [], + s2.options = assembleOptions( + { + ...DEFAULT_OPTIONS, + hdAdapter: false, }, - sortParams: [], - }; + s2Options, + ); + + s2.dataCfg = assembleDataCfg({ sortParams: [] }, s2DataConfig); s2.container = new Canvas({ width: DEFAULT_OPTIONS.width!, height: DEFAULT_OPTIONS.height!, @@ -91,41 +100,52 @@ export const createFakeSpreadSheet = () => { getCellMultiData() { return []; }, + getField: jest.fn(), } as unknown as any; + + const layoutResult: LayoutResult = { + rowLeafNodes: [], + colLeafNodes: [], + rowNodes: [], + colNodes: [], + colsHierarchy: new Hierarchy(), + rowsHierarchy: new Hierarchy(), + }; + s2.facet = { panelBBox: { maxX: s2.options.width, maxY: s2.options.height, } as PanelBBox, - panelGroup: { - getChildren() { - return []; - }, - }, - foregroundGroup: { - getChildren() { - return []; - }, - }, - layoutResult: { - getCellMeta: jest.fn(), - rowLeafNodes: [], - colLeafNodes: [], - rowNodes: [], - colNodes: [], - }, + panelGroup: s2.container.appendChild(new Group()), + foregroundGroup: s2.container.appendChild(new Group()), + backgroundGroup: s2.container.appendChild(new Group()), + layoutResult, + getLayoutResult: () => layoutResult, getCellMeta: jest.fn(), getCellById: jest.fn(), - getCellChildrenNodes: jest.fn(), - getCells: jest.fn(), - getColCells: jest.fn(), - getRowCells: jest.fn(), - getDataCells: jest.fn(), - getRowNodes: jest.fn(), - getRowLeafNodes: jest.fn(), - getColNodes: jest.fn(), - getColLeafNodes: jest.fn(), - getInitColLeafNodes: jest.fn(), + getCellChildrenNodes: () => [], + getCells: () => [], + getColCells: () => [], + getRowCells: () => [], + getDataCells: () => [], + getRowNodes: () => [], + getRowLeafNodes: () => [], + getColNodes: () => [], + getColLeafNodes: () => [], + getInitColLeafNodes: () => [], + getHeaderCells: () => [], + getHiddenColumnsInfo: jest.fn(), + getCellAdaptiveHeight: jest.fn(), + getRowLeafNodeByIndex: jest.fn(), + getColLeafNodeByIndex: jest.fn(), + frozenGroupInfo: { + [FrozenGroupType.FROZEN_ROW]: {}, + [FrozenGroupType.FROZEN_COL]: {}, + [FrozenGroupType.FROZEN_TRAILING_ROW]: {}, + [FrozenGroupType.FROZEN_TRAILING_COL]: {}, + }, + cornerBBox: {}, } as unknown as BaseFacet; s2.container.render = jest.fn(); s2.store = new Store(); @@ -148,11 +168,25 @@ export const createFakeSpreadSheet = () => { s2.getCell = jest.fn(); s2.isHierarchyTreeType = jest.fn(); s2.facet.getRowNodes = jest.fn().mockReturnValue([]); + s2.facet.getCells = jest.fn().mockReturnValue([]); s2.getCanvasElement = () => s2.container.getContextService().getDomElement() as HTMLCanvasElement; s2.isCustomHeaderFields = jest.fn(() => false); s2.isCustomRowFields = jest.fn(() => false); s2.isCustomColumnFields = jest.fn(() => false); + s2.isValueInCols = jest.fn(); + s2.isCustomHeaderFields = jest.fn(); + s2.isCustomColumnFields = jest.fn(); + s2.isCustomRowFields = jest.fn(); + s2.getTotalsConfig = jest.fn(); + s2.getLayoutWidthType = jest.fn(); + s2.enableFrozenHeaders = jest.fn(); + s2.measureTextWidth = jest.fn(); + s2.isFrozenRowHeader = jest.fn(); + s2.theme = getTheme({ + name: 'default', + spreadsheet: s2, + }) as InternalFullyTheme; const interaction = new RootInteraction(s2 as unknown as SpreadSheet); @@ -275,7 +309,7 @@ export const createFederatedMouseEvent = ( }; export const createTableSheet = ( - s2Options: S2Options, + s2Options: S2Options | null, { useSimpleData } = { useSimpleData: true }, ) => new TableSheet( diff --git a/packages/s2-core/__tests__/util/index.ts b/packages/s2-core/__tests__/util/index.ts index ef6dfcd39d..195f07e5d6 100644 --- a/packages/s2-core/__tests__/util/index.ts +++ b/packages/s2-core/__tests__/util/index.ts @@ -1,36 +1,38 @@ -import { data, totalData, meta } from 'tests/data/mock-dataset.json'; +import { data, meta, totalData } from 'tests/data/mock-dataset.json'; import { + DEFAULT_DATA_CONFIG, DEFAULT_OPTIONS, + S2Event, + SpreadSheet, type S2DataConfig, type S2Options, - DEFAULT_DATA_CONFIG, - SpreadSheet, - S2Event, } from '@/index'; import { customMerge } from '@/utils'; -export const assembleOptions = (...options: Partial<S2Options>[]) => - customMerge<S2Options>( - DEFAULT_OPTIONS, - { debug: false, width: 600, height: 600 }, - ...options, - ); +export const assembleOptions = (...options: Partial<S2Options>[]) => { + const s2Options: S2Options = { + debug: false, + width: 600, + height: 600, + }; + + return customMerge<S2Options>(DEFAULT_OPTIONS, s2Options, ...options); +}; -export const assembleDataCfg = (...dataCfg: Partial<S2DataConfig>[]) => - customMerge<S2DataConfig>( - DEFAULT_DATA_CONFIG, - { - fields: { - rows: ['province', 'city'], - columns: ['type', 'sub_type'], - values: ['number'], - valueInCols: true, - }, - meta, - data: data.concat(totalData as any), +export const assembleDataCfg = (...dataCfg: Partial<S2DataConfig>[]) => { + const s2DataCfg: S2DataConfig = { + fields: { + rows: ['province', 'city'], + columns: ['type', 'sub_type'], + values: ['number'], + valueInCols: true, }, - ...dataCfg, - ); + meta, + data: data.concat(totalData as any), + }; + + return customMerge<S2DataConfig>(DEFAULT_DATA_CONFIG, s2DataCfg, ...dataCfg); +}; export const TOTALS_OPTIONS: S2Options['totals'] = { row: { diff --git a/packages/s2-core/package.json b/packages/s2-core/package.json index 44d1402cb0..2c47ca6b39 100644 --- a/packages/s2-core/package.json +++ b/packages/s2-core/package.json @@ -46,9 +46,10 @@ "build:umd": "cross-env FORMAT=umd rollup -c rollup.config.mjs", "build:analysis": "cross-env FORMAT=esm ANALYSIS=true rollup -c rollup.config.mjs", "build:dts": "run-s dts:*", + "build:size-limit": "size-limit", + "build:size-limit-json": "yarn build:size-limit --json", "dts:build": "tsc -p tsconfig.declaration.json", "dts:extract": "cross-env LIB=s2-core node ../../scripts/dts.js", - "bundle:size": "bundlesize", "watch": "rimraf esm && pnpm build:esm -w", "test:live": "node ./scripts/test-live.mjs", "sync-event": "node ./scripts/sync-event.mjs", @@ -85,14 +86,15 @@ "*.css", "dist/*" ], - "bundlesize": [ + "size-limit": [ { "path": "./dist/index.min.js", - "maxSize": "300 kB" + "import": "{ createComponent }", + "limit": "200 kB" }, { "path": "./dist/style.min.css", - "maxSize": "5 kB" + "limit": "5 kB" } ], "publishConfig": { diff --git a/packages/s2-core/src/cell/base-cell.ts b/packages/s2-core/src/cell/base-cell.ts index e94f7f4b23..435531a4cc 100644 --- a/packages/s2-core/src/cell/base-cell.ts +++ b/packages/s2-core/src/cell/base-cell.ts @@ -156,6 +156,12 @@ export abstract class BaseCell<T extends SimpleBBox> extends Group { condition: Condition, ): ConditionMappingResult | undefined | null; + protected abstract getBackgroundColor(): { + backgroundColor: string | undefined; + backgroundColorOpacity: number | undefined; + intelligentReverseTextColor: boolean; + }; + public constructor( meta: T, spreadsheet: SpreadSheet, @@ -300,6 +306,14 @@ export abstract class BaseCell<T extends SimpleBBox> extends Group { return this.linkFieldShape; } + public getBackgroundShape() { + return this.backgroundShape; + } + + public getStateShapes() { + return this.stateShapes; + } + protected getResizeAreaStyle(): ResizeArea { return this.getStyle('resizeArea') as ResizeArea; } @@ -353,7 +367,7 @@ export abstract class BaseCell<T extends SimpleBBox> extends Group { } /** - * 绘制hover悬停,刷选的外框 + * 绘制 hover 悬停,刷选的外框 */ protected drawInteractiveBorderShape() { this.stateShapes.set( @@ -361,27 +375,11 @@ export abstract class BaseCell<T extends SimpleBBox> extends Group { renderRect(this, { ...this.getBBoxByType(CellClipBox.PADDING_BOX), visibility: 'hidden', + pointerEvents: 'none', }), ); } - protected abstract getBackgroundColor(): { - backgroundColor: string | undefined; - backgroundColorOpacity: number | undefined; - intelligentReverseTextColor: boolean; - }; - - protected drawBackgroundShape() { - const { backgroundColor, backgroundColorOpacity } = - this.getBackgroundColor(); - - this.backgroundShape = renderRect(this, { - ...this.getBBoxByType(), - fill: backgroundColor, - fillOpacity: backgroundColorOpacity, - }); - } - /** * 交互使用的背景色 */ @@ -391,10 +389,22 @@ export abstract class BaseCell<T extends SimpleBBox> extends Group { renderRect(this, { ...this.getBBoxByType(), visibility: 'hidden', + pointerEvents: 'none', }), ); } + protected drawBackgroundShape() { + const { backgroundColor, backgroundColorOpacity } = + this.getBackgroundColor(); + + this.backgroundShape = renderRect(this, { + ...this.getBBoxByType(), + fill: backgroundColor, + fillOpacity: backgroundColorOpacity, + }); + } + public renderTextShape( style: TextStyleProps, options?: RenderTextShapeOptions, @@ -750,4 +760,20 @@ export abstract class BaseCell<T extends SimpleBBox> extends Group { return getIconTotalWidth(this.groupedIcons[position], iconStyle); } + + protected getCrossBackgroundColor(rowIndex: number) { + const { crossBackgroundColor, backgroundColorOpacity } = + this.getStyle().cell; + + if (crossBackgroundColor && rowIndex % 2 === 0) { + // 隔行颜色的配置 + // 偶数行展示灰色背景,因为index是从0开始的 + return { backgroundColorOpacity, backgroundColor: crossBackgroundColor }; + } + + return { + backgroundColorOpacity, + backgroundColor: this.getStyle().cell.backgroundColor, + }; + } } diff --git a/packages/s2-core/src/cell/col-cell.ts b/packages/s2-core/src/cell/col-cell.ts index 60236dc167..ae843dac63 100644 --- a/packages/s2-core/src/cell/col-cell.ts +++ b/packages/s2-core/src/cell/col-cell.ts @@ -99,11 +99,41 @@ export class ColCell extends HeaderCell<ColHeaderConfig> { return false; } - protected getTextPosition(): PointLike { - const { isLeaf } = this.meta; + /** + * 计算文本位置时候需要,留给后代根据情况(固定列)覆盖 + * @param viewport + * @returns viewport + */ + protected handleViewport(): AreaRange { + /** + * p(x, y) + * +----------------------+ x + * | +---------------> + * | viewport | |ColCell | + * | |-|---------+ + * +--------------------|-+ + * | + * y | + * v + * + * 将 viewport 坐标(p)映射到 col header 的坐标体系中,简化计算逻辑 + * + */ const { width, cornerWidth = 0, scrollX = 0 } = this.getHeaderConfig(); const scrollContainsRowHeader = !this.spreadsheet.isFrozenRowHeader(); + + const viewport: AreaRange = { + start: scrollX - (scrollContainsRowHeader ? cornerWidth : 0), + size: width + (scrollContainsRowHeader ? cornerWidth : 0), + }; + + return viewport; + } + + protected getTextPosition(): PointLike { + const { isLeaf } = this.meta; + const textStyle = this.getTextStyle(); const contentBox = this.getBBoxByType(CellClipBox.CONTENT_BOX); const iconStyle = this.getIconStyle()!; @@ -137,26 +167,7 @@ export class ColCell extends HeaderCell<ColHeaderConfig> { return { x: textX, y: textY }; } - /** - * p(x, y) - * +----------------------+ x - * | +---------------> - * | viewport | |ColCell | - * | |-|---------+ - * +--------------------|-+ - * | - * y | - * v - * - * 将 viewport 坐标(p)映射到 col header 的坐标体系中,简化计算逻辑 - * - */ - const viewport: AreaRange = { - start: scrollX - (scrollContainsRowHeader ? cornerWidth : 0), - size: width + (scrollContainsRowHeader ? cornerWidth : 0), - }; - - this.handleViewport(viewport); + const viewport = this.handleViewport(); const { cell, icon } = this.getStyle()!; const { textAlign, textBaseline } = this.getTextStyle(); @@ -270,7 +281,7 @@ export class ColCell extends HeaderCell<ColHeaderConfig> { style: { ...attrs.style, x: 0, - y: y + height - resizeStyle.size! / 2, + y: y + height - resizeStyle.size!, width: resizeAreaWidth, }, }, @@ -369,7 +380,7 @@ export class ColCell extends HeaderCell<ColHeaderConfig> { { style: { ...attrs.style, - x: offsetX + width - resizeStyle.size! / 2, + x: offsetX + width - resizeStyle.size!, y: offsetY, height, }, @@ -435,11 +446,22 @@ export class ColCell extends HeaderCell<ColHeaderConfig> { } this.addExpandColumnSplitLine(); - this.addExpandColumnIcon(); + this.addExpandColumnIcons(); } - protected addExpandColumnIcon() { - const iconConfig = this.getExpandColumnIconConfig(); + protected addExpandColumnIcons() { + const isLastColumn = this.isLastColumn(); + + this.addExpandColumnIcon(isLastColumn); + + // 如果当前节点的兄弟节点 (前/后) 都被隐藏了, 隐藏后当前节点变为最后一个节点, 需要渲染两个展开按钮, 一个展开[前], 一个展开[后] + if (this.isAllDisplaySiblingNodeHidden() && isLastColumn) { + this.addExpandColumnIcon(false); + } + } + + private addExpandColumnIcon(isLastColumn: boolean) { + const iconConfig = this.getExpandColumnIconConfig(isLastColumn); const icon = renderIcon(this, { ...iconConfig, name: 'ExpandColIcon', @@ -452,12 +474,12 @@ export class ColCell extends HeaderCell<ColHeaderConfig> { } // 在隐藏的下一个兄弟节点的起始坐标显示隐藏提示线和展开按钮, 如果是尾元素, 则显示在前一个兄弟节点的结束坐标 - protected getExpandColumnIconConfig() { + protected getExpandColumnIconConfig(isLastColumn: boolean) { const { size = 0 } = this.getExpandIconTheme(); const { x, y, width, height } = this.getBBoxByType(); const baseIconX = x - size; - const iconX = this.isLastColumn() ? baseIconX + width : baseIconX; + const iconX = isLastColumn ? baseIconX + width : baseIconX; const iconY = y + height / 2 - size / 2; return { @@ -472,12 +494,20 @@ export class ColCell extends HeaderCell<ColHeaderConfig> { return isLastColumnAfterHidden(this.spreadsheet, this.meta.id); } - /** - * 计算文本位置时候需要,留给后代根据情况(固定列)覆盖 - * @param viewport - * @returns viewport - */ - protected handleViewport(viewport: AreaRange): AreaRange { - return viewport; + protected isAllDisplaySiblingNodeHidden() { + const { id } = this.meta; + const lastHiddenColumnDetail = this.spreadsheet.store.get( + 'hiddenColumnsDetail', + [], + ); + + const isPrevSiblingNodeHidden = lastHiddenColumnDetail.find( + ({ displaySiblingNode }) => displaySiblingNode?.next?.id === id, + ); + const isNextSiblingNodeHidden = lastHiddenColumnDetail.find( + ({ displaySiblingNode }) => displaySiblingNode?.prev?.id === id, + ); + + return isNextSiblingNodeHidden && isPrevSiblingNodeHidden; } } diff --git a/packages/s2-core/src/cell/corner-cell.ts b/packages/s2-core/src/cell/corner-cell.ts index dd61264bab..2d484d095b 100644 --- a/packages/s2-core/src/cell/corner-cell.ts +++ b/packages/s2-core/src/cell/corner-cell.ts @@ -7,7 +7,7 @@ import { ResizeDirectionType, S2Event, } from '../common/constant'; -import type { FormatResult, TextTheme } from '../common/interface'; +import type { FormatResult } from '../common/interface'; import { CellBorderPosition, CellClipBox } from '../common/interface'; import { CornerNodeType } from '../common/interface/node'; import { CustomRect } from '../engine'; @@ -15,6 +15,7 @@ import type { CornerHeaderConfig } from '../facet/header/interface'; import { getHorizontalTextIconPosition, getVerticalIconPosition, + getVerticalTextPosition, } from '../utils/cell/cell'; import { formattedFieldValue } from '../utils/cell/header-cell'; import { renderTreeIcon } from '../utils/g-renders'; @@ -40,8 +41,6 @@ export class CornerCell extends HeaderCell<CornerHeaderConfig> { return [CellBorderPosition.TOP, CellBorderPosition.LEFT]; } - public update() {} - protected initCell() { super.initCell(); this.resetTextAndConditionIconShapes(); @@ -51,53 +50,7 @@ export class CornerCell extends HeaderCell<CornerHeaderConfig> { this.drawActionAndConditionIcons(); this.drawBorders(); this.drawResizeArea(); - } - - public drawTextShape() { - const { x, y, height, width } = this.getBBoxByType(CellClipBox.CONTENT_BOX); - const textStyle = this.getTextStyle(); - const cornerText = this.getFieldValue(); - const maxWidth = this.getMaxTextWidth(); - - const { textX, leftIconX, rightIconX } = getHorizontalTextIconPosition({ - bbox: { - x: x + this.getTreeIconWidth(), - y, - width: width - this.getTreeIconWidth(), - height, - }, - textAlign: textStyle.textAlign!, - textWidth: this.getActualTextWidth(), - groupedIcons: this.groupedIcons, - iconStyle: this.getIconStyle()!, - }); - - const textY = y + height / 2; - - this.renderTextShape({ - ...textStyle, - x: textX, - y: textY, - text: cornerText, - wordWrapWidth: maxWidth, - }); - - const { size = 0 } = this.getStyle()!.icon!; - const iconY = getVerticalIconPosition( - size, - y + height / 2, - size, - textStyle.textBaseline!, - ); - - this.leftIconPosition = { - x: leftIconX, - y: iconY, - }; - this.rightIconPosition = { - x: rightIconX, - y: iconY, - }; + this.update(); } /** @@ -228,7 +181,7 @@ export class CornerCell extends HeaderCell<CornerHeaderConfig> { { style: { ...attrs.style, - x: offsetX + width - resizeStyle.size! / 2, + x: offsetX + width - resizeStyle.size!, y: offsetY, height: this.isLastRowCornerCell() ? headerHeight : height, }, @@ -249,20 +202,6 @@ export class CornerCell extends HeaderCell<CornerHeaderConfig> { return this.showTreeIcon() ? size! + margin!.right! : 0; } - protected getTextStyle(): TextTheme { - const { text, bolderText } = this.getStyle()!; - const cornerTextStyle = this.isBolderText() ? text : bolderText; - - const textStyle = - this.getContainConditionMappingResultTextStyle(cornerTextStyle); - - return { - ...textStyle, - // 角头因为要折行,所以在都是按照 middle 来计算,这里写死,不然用户配置了 baseline,会导致计算错误 - textBaseline: 'middle', - }; - } - protected getMaxTextWidth(): number { const { width } = this.getBBoxByType(CellClipBox.CONTENT_BOX); @@ -272,9 +211,46 @@ export class CornerCell extends HeaderCell<CornerHeaderConfig> { } protected getTextPosition(): PointLike { + const contentBox = this.getBBoxByType(CellClipBox.CONTENT_BOX); + const { x, y, height, width } = contentBox; + + const textStyle = this.getTextStyle(); + + const { textX, leftIconX, rightIconX } = getHorizontalTextIconPosition({ + bbox: { + x: x + this.getTreeIconWidth(), + y, + width: width - this.getTreeIconWidth(), + height, + }, + textAlign: textStyle.textAlign!, + textWidth: this.getActualTextWidth(), + groupedIcons: this.groupedIcons, + iconStyle: this.getIconStyle()!, + }); + + const textY = getVerticalTextPosition(contentBox, textStyle.textBaseline!); + + const { size = 0 } = this.getStyle()!.icon!; + const iconY = getVerticalIconPosition( + size, + textY, + textStyle.fontSize!, + textStyle.textBaseline!, + ); + + this.leftIconPosition = { + x: leftIconX, + y: iconY, + }; + this.rightIconPosition = { + x: rightIconX, + y: iconY, + }; + return { - x: 0, - y: 0, + x: textX, + y: textY, }; } diff --git a/packages/s2-core/src/cell/data-cell.ts b/packages/s2-core/src/cell/data-cell.ts index 76a086c80c..a81850c762 100644 --- a/packages/s2-core/src/cell/data-cell.ts +++ b/packages/s2-core/src/cell/data-cell.ts @@ -28,6 +28,7 @@ import type { MultiData, TextTheme, ViewMeta, + ViewMetaData, ViewMetaIndexType, } from '../common/interface'; import { @@ -37,7 +38,7 @@ import { type IconCondition, type InteractionStateTheme, } from '../common/interface'; -import { getFieldValueOfViewMetaData } from '../data-set/cell-data'; +import { CellData } from '../data-set/cell-data'; import { getHorizontalTextIconPosition, getVerticalIconPosition, @@ -67,6 +68,12 @@ import type { RawData } from './../common/interface/s2DataConfig'; * 3、left rect area is interval(in left) and text(in right) */ export class DataCell extends BaseCell<ViewMeta> { + /** + * 用于 merge cell 中用于绘制 border 的位置信息 + * @see packages/s2-core/src/facet/base-facet.ts L1319 + */ + position: [rowIndex: number, colIndex: number]; + // condition icon 坐标 iconPosition: PointLike; @@ -180,15 +187,18 @@ export class DataCell extends BaseCell<ViewMeta> { return; } - if (this.spreadsheet.options.interaction?.hoverHighlight) { + const { currentRow, currentCol } = + this.spreadsheet.interaction.getHoverHighlight(); + + if (currentRow || currentCol) { // 如果当前是hover,要绘制出十字交叉的行列样式 const currentColIndex = this.meta.colIndex; const currentRowIndex = this.meta.rowIndex; // 当视图内的 cell 行列 index 与 hover 的 cell 一致,绘制hover的十字样式 if ( - currentColIndex === currentHoverCell?.colIndex || - currentRowIndex === currentHoverCell?.rowIndex + (currentCol && currentColIndex === currentHoverCell?.colIndex) || + (currentRow && currentRowIndex === currentHoverCell?.rowIndex) ) { this.updateByState(InteractionStateName.HOVER); } else { @@ -259,18 +269,18 @@ export class DataCell extends BaseCell<ViewMeta> { this.generateIconConfig(); this.drawBackgroundShape(); this.drawInteractiveBgShape(); + if (!this.shouldHideRowSubtotalData()) { this.drawConditionIntervalShape(); } - this.drawInteractiveBorderShape(); - if (!this.shouldHideRowSubtotalData()) { this.drawTextShape(); this.drawConditionIconShapes(); } this.drawBorders(); + this.drawInteractiveBorderShape(); this.update(); } @@ -308,17 +318,24 @@ export class DataCell extends BaseCell<ViewMeta> { } protected shouldHideRowSubtotalData() { + const { rowId, rowIndex } = this.meta; + // 如果该格子是被下钻的格子,下钻格子本身来说是明细格子,因为下钻变成了小计格子,是应该展示的 + const drillDownIdPathMap = this.spreadsheet.store.get('drillDownIdPathMap'); + + if (drillDownIdPathMap?.has(rowId!)) { + return false; + } + const { row = {} } = this.spreadsheet.options.totals ?? {}; - const { rowIndex } = this.meta; - const node = this.spreadsheet.facet.getRowLeafNodes()[rowIndex]; + const node = this.spreadsheet.facet?.getRowLeafNodeByIndex(rowIndex); const isRowSubTotal = !node?.isGrandTotals && node?.isTotals; - /* + /** * 在树状结构时,如果单元格本身是行小计,但是行小计配置又未开启时 - * 不过能否查到实际的数据,都不应该展示 + * 不管能否查到实际的数据,都不应该展示 */ return ( - this.spreadsheet.options.hierarchyType === 'tree' && + this.spreadsheet.isHierarchyTreeType() && !row.showSubTotals && isRowSubTotal ); @@ -329,7 +346,7 @@ export class DataCell extends BaseCell<ViewMeta> { return { value: null, - /* + /** * 这里使用默认的placeholder,而不是空字符串,是为了防止后续使用用户自定义的placeholder * 比如用户自定义 placeholder 为 0, 那行小计也会显示0,也很有迷惑性,显示 - 更为合理 */ @@ -392,18 +409,12 @@ export class DataCell extends BaseCell<ViewMeta> { } public getBackgroundColor() { - const cellStyle = this.getStyle()?.cell; - const { crossBackgroundColor, backgroundColorOpacity } = cellStyle!; - - let backgroundColor = cellStyle!.backgroundColor; - - if (crossBackgroundColor && this.meta.rowIndex % 2 === 0) { - /* - * 隔行颜色的配置 - * 偶数行展示灰色背景,因为index是从0开始的 - */ - backgroundColor = crossBackgroundColor; - } + const backgroundColorByCross = this.getCrossBackgroundColor( + this.meta.rowIndex, + ); + const backgroundColor = backgroundColorByCross.backgroundColor; + const backgroundColorOpacity = + backgroundColorByCross.backgroundColorOpacity; if (this.shouldHideRowSubtotalData()) { return { @@ -419,7 +430,7 @@ export class DataCell extends BaseCell<ViewMeta> { ); } - // dataCell根据state 改变当前样式, + // dataCell 根据 state 改变当前样式, protected changeRowColSelectState(indexType: ViewMetaIndexType) { const { interaction } = this.spreadsheet; const currentIndex = get(this.meta, indexType); @@ -481,7 +492,7 @@ export class DataCell extends BaseCell<ViewMeta> { ? this.spreadsheet.dataSet.getCellData({ query: { rowIndex: this.meta.rowIndex }, }) - : getFieldValueOfViewMetaData(this.meta.data); + : CellData.getFieldValue(this.meta.data as ViewMetaData); return condition?.mapping(value, rowDataInfo as RawData, this); } diff --git a/packages/s2-core/src/cell/header-cell.ts b/packages/s2-core/src/cell/header-cell.ts index f2039de51b..69a5dee2a5 100644 --- a/packages/s2-core/src/cell/header-cell.ts +++ b/packages/s2-core/src/cell/header-cell.ts @@ -82,7 +82,7 @@ export abstract class HeaderCell< return super.shouldInit() && !this.isShallowRender(); } - protected handleRestOptions(...[headerConfig]: [T]) { + protected handleRestOptions(...[headerConfig]: [T, unknown]) { this.headerConfig = { ...headerConfig }; const { value, query } = this.meta; @@ -139,13 +139,32 @@ export abstract class HeaderCell< } protected getFormattedFieldValue(): FormatResult { - const { value, field } = this.meta; + const { isTotalRoot, isGrandTotals, value } = this.meta; - const formatter = this.spreadsheet.dataSet.getFieldFormatter(field); - // TODO: formatter 简化成两个参数 formatter(value, this,meta) - const formattedValue = formatter! - ? formatter(value, undefined, this.meta) - : value; + const formatter = this.spreadsheet.dataSet.getFieldFormatter( + this.meta.field, + ); + + /** + * 如果是 table mode,列头不需要被格式化 + * 树状模式下,小计是父维度本身,需要被格式化,此时只有总计才不需要被格式化 + * 平铺模式下,总计/小计 文字单元格,不需要被格式化 + * 自定义树模式下,没有总计小计概念,isTotals 均为 false, 所以不受影响 + */ + let shouldFormat = true; + + if (this.spreadsheet.isTableMode()) { + shouldFormat = false; + } else if (this.spreadsheet.isHierarchyTreeType()) { + shouldFormat = !(isGrandTotals && isTotalRoot); + } else { + shouldFormat = !isTotalRoot; + } + + const formattedValue = + shouldFormat && formatter + ? formatter(value, undefined, this.meta) + : value; return { formattedValue, @@ -415,7 +434,11 @@ export abstract class HeaderCell< public update() { const { interaction } = this.spreadsheet; const stateInfo = interaction?.getState(); - const cells = interaction?.getCells([CellType.COL_CELL, CellType.ROW_CELL]); + const cells = interaction?.getCells([ + CellType.CORNER_CELL, + CellType.COL_CELL, + CellType.ROW_CELL, + ]); if (!first(cells)) { return; @@ -423,6 +446,7 @@ export abstract class HeaderCell< switch (stateInfo?.stateName) { case InteractionStateName.SELECTED: + case InteractionStateName.BRUSH_SELECTED: this.handleSelect(cells, stateInfo?.nodes); break; case InteractionStateName.HOVER_FOCUS: diff --git a/packages/s2-core/src/cell/index.ts b/packages/s2-core/src/cell/index.ts index c6ca335870..80545190fd 100644 --- a/packages/s2-core/src/cell/index.ts +++ b/packages/s2-core/src/cell/index.ts @@ -4,11 +4,11 @@ import { CornerCell } from './corner-cell'; import { DataCell } from './data-cell'; import { HeaderCell } from './header-cell'; import { MergedCell } from './merged-cell'; -import { RowCell } from './row-cell'; import { TableColCell } from './table-col-cell'; import { TableCornerCell } from './table-corner-cell'; import { TableDataCell } from './table-data-cell'; import { TableSeriesNumberCell } from './table-series-number-cell'; +import { RowCell } from './row-cell'; import { SeriesNumberCell } from './series-number-cell'; export { @@ -16,7 +16,6 @@ export { TableColCell, TableSeriesNumberCell, TableDataCell, - SeriesNumberCell, RowCell, ColCell, DataCell, @@ -24,4 +23,5 @@ export { CornerCell, BaseCell, HeaderCell, + SeriesNumberCell, }; diff --git a/packages/s2-core/src/cell/merged-cell.ts b/packages/s2-core/src/cell/merged-cell.ts index 064fef3a09..b3d84810b2 100644 --- a/packages/s2-core/src/cell/merged-cell.ts +++ b/packages/s2-core/src/cell/merged-cell.ts @@ -1,18 +1,21 @@ import { isEmpty, isObject } from 'lodash'; import { CellType } from '../common/constant'; -import type { ViewMeta } from '../common/interface'; -import type { S2CellType } from '../common/interface/interaction'; +import { CellBorderPosition, type ViewMeta } from '../common/interface'; import type { SpreadSheet } from '../sheet-type'; -import { renderPolygon } from '../utils/g-renders'; -import { getPolygonPoints } from '../utils/interaction/merge-cell'; +import { renderLine, renderPolygon } from '../utils/g-renders'; +import { + getPolygonPoints, + getRightAndBottomCells, +} from '../utils/interaction/merge-cell'; import { drawObjectText } from '../utils/text'; +import { getBorderPositionAndStyle } from '../utils'; import { DataCell } from './data-cell'; /** * Cell for panelGroup area */ export class MergedCell extends DataCell { - public cells: S2CellType[]; + public cells: DataCell[]; public get cellType() { return CellType.MERGED_CELL; @@ -20,13 +23,13 @@ export class MergedCell extends DataCell { public constructor( spreadsheet: SpreadSheet, - cells: S2CellType[], + cells: DataCell[], meta?: ViewMeta, ) { super(meta!, spreadsheet, cells); } - handleRestOptions(...[cells]: [S2CellType[]]) { + handleRestOptions(...[cells]: [DataCell[]]) { this.cells = cells; } @@ -35,9 +38,9 @@ export class MergedCell extends DataCell { protected initCell() { this.resetTextAndConditionIconShapes(); // TODO:1、交互态扩展; 2、合并后的单元格文字布局及文字内容(目前参考Excel合并后只保留第一个单元格子的数据) - this.conditions = this.spreadsheet.options.conditions!; this.drawBackgroundShape(); this.drawTextShape(); + this.drawBorders(); } /** @@ -49,7 +52,6 @@ export class MergedCell extends DataCell { this.backgroundShape = renderPolygon(this, { points: allPoints, - stroke: cellTheme!.horizontalBorderColor, fill: cellTheme!.backgroundColor, }); } @@ -65,4 +67,54 @@ export class MergedCell extends DataCell { super.drawTextShape(); } } + + override drawBorders(): void { + const { right, bottom, bottomRightCornerCell } = getRightAndBottomCells( + this.cells, + ); + + right.forEach((cell) => { + const { position, style } = getBorderPositionAndStyle( + CellBorderPosition.RIGHT, + cell.getBBoxByType(), + cell.getStyle()?.cell!, + ); + + renderLine(this, { ...position, ...style }); + }); + + bottom.forEach((cell) => { + const { position, style } = getBorderPositionAndStyle( + CellBorderPosition.BOTTOM, + cell.getBBoxByType(), + cell.getStyle()?.cell!, + ); + + renderLine(this, { ...position, ...style }); + }); + + bottomRightCornerCell.forEach((cell) => { + const { x, y, width, height } = cell.getBBoxByType(); + const { + horizontalBorderWidth = 0, + verticalBorderWidth = 0, + verticalBorderColor, + } = cell.getStyle()?.cell!; + + const x1 = x + width - verticalBorderWidth / 2; + const x2 = x1; + const y1 = y + height - horizontalBorderWidth; + const y2 = y + height; + + renderLine(this, { + x1, + x2, + y1, + y2, + lineWidth: verticalBorderWidth, + stroke: verticalBorderColor, + strokeOpacity: verticalBorderWidth, + }); + }); + } } diff --git a/packages/s2-core/src/cell/row-cell.ts b/packages/s2-core/src/cell/row-cell.ts index bb8d1bb688..2fcf24e1a4 100644 --- a/packages/s2-core/src/cell/row-cell.ts +++ b/packages/s2-core/src/cell/row-cell.ts @@ -1,7 +1,8 @@ import type { PointLike } from '@antv/g'; -import { find, get } from 'lodash'; +import { find, get, merge } from 'lodash'; import { CellType, + FrozenGroupType, KEY_GROUP_ROW_RESIZE_AREA, ResizeAreaEffect, ResizeDirectionType, @@ -26,6 +27,7 @@ import { getResizeAreaAttrs, } from '../utils/interaction/resize'; import { isMobile } from '../utils/is-mobile'; +import type { FrozenFacet } from '../facet/frozen-facet'; import type { SimpleBBox } from './../engine/interface'; import { adjustTextIconPositionWhileScrolling } from './../utils/cell/text-scrolling'; import { shouldAddResizeArea } from './../utils/interaction/resize'; @@ -64,6 +66,16 @@ export class RowCell extends HeaderCell<RowHeaderConfig> { this.update(); } + public getBackgroundColor() { + const { backgroundColor, backgroundColorOpacity } = + this.getCrossBackgroundColor(this.meta.rowIndex); + + return merge( + { backgroundColor, backgroundColorOpacity }, + this.getBackgroundConditionFill(), + ); + } + protected showTreeIcon() { return this.spreadsheet.isHierarchyTreeType() && !this.meta.isLeaf; } @@ -232,6 +244,7 @@ export class RowCell extends HeaderCell<RowHeaderConfig> { viewportHeight: headerHeight, scrollX = 0, scrollY = 0, + spreadsheet, } = this.getHeaderConfig(); const resizeAreaBBox: SimpleBBox = { @@ -241,25 +254,30 @@ export class RowCell extends HeaderCell<RowHeaderConfig> { height: resizeStyle.size!, }; + const isFrozen = this.getMeta().isFrozen; + + const frozenRowGroupHeight = (spreadsheet.facet as FrozenFacet) + .frozenGroupInfo[FrozenGroupType.FROZEN_ROW]?.height; + const resizeClipAreaBBox: SimpleBBox = { x: 0, - y: 0, + y: frozenRowGroupHeight, width: headerWidth, height: headerHeight, }; if ( + !isFrozen && !shouldAddResizeArea(resizeAreaBBox, resizeClipAreaBBox, { scrollX, scrollY, - }) || - !position + }) ) { return; } const offsetX = position.x + x - scrollX; - const offsetY = position.y + y - scrollY; + const offsetY = position.y + y - (isFrozen ? 0 : scrollY); const resizeAreaWidth = this.spreadsheet.isFrozenRowHeader() ? headerWidth - position.x - (x - scrollX) @@ -282,7 +300,7 @@ export class RowCell extends HeaderCell<RowHeaderConfig> { style: { ...attrs.style, x: offsetX, - y: offsetY + height - resizeStyle.size! / 2, + y: offsetY + height - resizeStyle.size!, width: resizeAreaWidth, }, }, @@ -355,16 +373,26 @@ export class RowCell extends HeaderCell<RowHeaderConfig> { }; } + protected handleViewport() { + const { scrollY, viewportHeight, spreadsheet } = this.getHeaderConfig(); + + const frozenRowGroupHeight = (spreadsheet.facet as FrozenFacet) + .frozenGroupInfo[FrozenGroupType.FROZEN_ROW].height; + + const viewport: AreaRange = { + start: this.getMeta().isFrozen ? 0 : scrollY! + frozenRowGroupHeight, + size: viewportHeight - frozenRowGroupHeight, + }; + + return viewport; + } + protected getTextPosition(): PointLike { - const { scrollY, viewportHeight } = this.getHeaderConfig(); const textArea = this.getTextArea(); const textStyle = this.getTextStyle(); const { cell, icon: iconStyle } = this.getStyle(); - const viewport: AreaRange = { - start: scrollY!, - size: viewportHeight, - }; + const viewport = this.handleViewport(); const { textStart } = adjustTextIconPositionWhileScrolling( viewport, diff --git a/packages/s2-core/src/cell/table-col-cell.ts b/packages/s2-core/src/cell/table-col-cell.ts index 8f8875f506..11cc2c1d99 100644 --- a/packages/s2-core/src/cell/table-col-cell.ts +++ b/packages/s2-core/src/cell/table-col-cell.ts @@ -1,6 +1,7 @@ import { find } from 'lodash'; import { ColCell } from '../cell/col-cell'; import { + FrozenGroupType, HORIZONTAL_RESIZE_AREA_KEY_PRE, KEY_GROUP_FROZEN_COL_RESIZE_AREA, } from '../common/constant'; @@ -8,15 +9,14 @@ import type { FormatResult } from '../common/interface'; import type { AreaRange } from '../common/interface/scroll'; import type { SimpleBBox } from '../engine'; import type { BaseHeaderConfig } from '../facet/header'; -import { getNodeRoot, isFrozenCol, isFrozenTrailingCol } from '../facet/utils'; import { formattedFieldValue } from '../utils/cell/header-cell'; import { renderRect } from '../utils/g-renders'; import { getOrCreateResizeAreaGroupById, shouldAddResizeArea, } from '../utils/interaction/resize'; -import { getFrozenColWidth } from '../utils/layout/frozen'; import { getSortTypeIcon } from '../utils/sort-action'; +import type { FrozenFacet } from '../facet/frozen-facet'; export class TableColCell extends ColCell { protected handleRestOptions(...[headerConfig]: [BaseHeaderConfig]) { @@ -33,18 +33,6 @@ export class TableColCell extends ColCell { }; } - protected isFrozenCell() { - const { colCount = 0, trailingColCount = 0 } = - this.spreadsheet.options.frozen!; - const colNodes = this.spreadsheet.facet?.getColNodes(0); - const { colIndex } = getNodeRoot(this.meta); - - return ( - isFrozenCol(colIndex, colCount) || - isFrozenTrailingCol(colIndex, trailingColCount, colNodes?.length) - ); - } - protected getFormattedFieldValue(): FormatResult { return formattedFieldValue( this.meta, @@ -53,7 +41,7 @@ export class TableColCell extends ColCell { } protected shouldAddVerticalResizeArea() { - if (this.isFrozenCell()) { + if (this.getMeta().isFrozen) { return true; } @@ -62,6 +50,7 @@ export class TableColCell extends ColCell { scrollY, width: headerWidth, height: headerHeight, + spreadsheet, } = this.getHeaderConfig(); const { x, y, width, height } = this.getBBoxByType(); const resizeStyle = this.getResizeAreaStyle(); @@ -73,17 +62,15 @@ export class TableColCell extends ColCell { height, }; - const frozenWidth = getFrozenColWidth( - this.spreadsheet.facet.getColLeafNodes(), - this.spreadsheet.options.frozen!, - ); + const frozenGroupInfo = (spreadsheet.facet as FrozenFacet).frozenGroupInfo; + const colWidth = frozenGroupInfo[FrozenGroupType.FROZEN_COL].width; + const trailingColWidth = + frozenGroupInfo[FrozenGroupType.FROZEN_TRAILING_COL].width; + const resizeClipAreaBBox: SimpleBBox = { - x: frozenWidth.frozenColWidth, + x: colWidth, y: 0, - width: - headerWidth - - frozenWidth.frozenColWidth - - frozenWidth.frozenTrailingColWidth, + width: headerWidth - colWidth - trailingColWidth, height: headerHeight, }; @@ -97,7 +84,7 @@ export class TableColCell extends ColCell { const { x, y } = this.meta; const { scrollX = 0, position } = this.getHeaderConfig(); - if (this.isFrozenCell()) { + if (this.getMeta().isFrozen) { return { x: position?.x + x, y: position?.y + y, @@ -111,9 +98,7 @@ export class TableColCell extends ColCell { } protected getColResizeArea() { - const isFrozenCell = this.isFrozenCell(); - - if (!isFrozenCell) { + if (!this.getMeta().isFrozen) { return super.getColResizeArea(); } @@ -142,16 +127,20 @@ export class TableColCell extends ColCell { } protected drawBackgroundShape() { - const { backgroundColor } = this.getStyle()!.cell!; + const { backgroundColor, backgroundColorOpacity } = + this.getStyle()!.cell! || {}; this.backgroundShape = renderRect(this, { ...this.getBBoxByType(), fill: backgroundColor, + fillOpacity: backgroundColorOpacity, }); } - protected handleViewport(viewport: AreaRange): AreaRange { - if (this.isFrozenCell()) { + protected handleViewport(): AreaRange { + const viewport = super.handleViewport(); + + if (this.getMeta().isFrozen) { viewport.start = 0; } diff --git a/packages/s2-core/src/cell/table-data-cell.ts b/packages/s2-core/src/cell/table-data-cell.ts index 2f73f83cc9..13fa924d50 100644 --- a/packages/s2-core/src/cell/table-data-cell.ts +++ b/packages/s2-core/src/cell/table-data-cell.ts @@ -1,6 +1,7 @@ import { Frame } from '../facet/header/frame'; import { DataCell } from '../cell/data-cell'; import { + FrozenGroupType, KEY_GROUP_FROZEN_ROW_RESIZE_AREA, KEY_GROUP_ROW_RESIZE_AREA, ResizeAreaEffect, @@ -13,8 +14,10 @@ import { import { getOrCreateResizeAreaGroupById, getResizeAreaAttrs, + shouldAddResizeArea, } from '../utils/interaction/resize'; -import { CustomRect } from '../engine'; +import { CustomRect, type SimpleBBox } from '../engine'; +import type { FrozenFacet } from '../facet/frozen-facet'; import { BaseCell } from './base-cell'; export class TableDataCell extends DataCell { @@ -32,7 +35,7 @@ export class TableDataCell extends DataCell { protected shouldDrawResizeArea() { // 每一行直绘制一条贯穿式 resize 热区 - const id = String(this.meta.rowIndex); + const id = `${this.meta.rowIndex}`; const resizeArea = getOrCreateResizeAreaGroupById( this.spreadsheet, @@ -95,6 +98,36 @@ export class TableDataCell extends DataCell { let offsetY = y + headerHeight + Frame.getHorizontalBorderWidth(this.spreadsheet); + const frozenGroupInfo = (this.spreadsheet.facet as FrozenFacet) + .frozenGroupInfo; + const rowHeight = frozenGroupInfo[FrozenGroupType.FROZEN_ROW].height; + const rowTrailingHeight = + frozenGroupInfo[FrozenGroupType.FROZEN_TRAILING_ROW].height; + + const resizeAreaBBox: SimpleBBox = { + x: 0, + y: y + height - resizeStyle.size!, + width: headerWidth, + height: resizeStyle.size!, + }; + const resizeClipAreaBBox: SimpleBBox = { + x: 0, + y: rowHeight, + width: headerWidth, + height: + this.spreadsheet.facet.panelBBox.height - rowHeight - rowTrailingHeight, + }; + + if ( + !isFrozen && + !shouldAddResizeArea(resizeAreaBBox, resizeClipAreaBBox, { + scrollX: 0, + scrollY, + }) + ) { + return; + } + if (!isFrozen) { offsetY -= scrollY + paginationSy; } diff --git a/packages/s2-core/src/cell/table-series-number-cell.ts b/packages/s2-core/src/cell/table-series-number-cell.ts index 8464f6500c..d5a0faef26 100644 --- a/packages/s2-core/src/cell/table-series-number-cell.ts +++ b/packages/s2-core/src/cell/table-series-number-cell.ts @@ -6,7 +6,7 @@ export class TableSeriesNumberCell extends TableDataCell { public get cellType() { /* * 在行列冻结并且开启序号时 - * 那么在如果行头冻结2列,并且CellTypes设置成以前的RowCell时,【FrozenRowGroup的分割线】和【左上角和左下角的边框样式】样式会混乱 + * 如果行头冻结 2 列,并且 CellTypes 设置成以前的 RowCell 时,【 FrozenRowGroup 的分割线】和【左上角和左下角的边框样式】样式会混乱 * 因此下层在选择到序号时,需要将 cellType 修改为 RowCell, 保证交互逻辑统一: * packages/s2-core/src/utils/interaction/select-event.ts -> getCellMeta */ diff --git a/packages/s2-core/src/common/constant/basic.ts b/packages/s2-core/src/common/constant/basic.ts index afdf8dd69b..680fb04a8b 100644 --- a/packages/s2-core/src/common/constant/basic.ts +++ b/packages/s2-core/src/common/constant/basic.ts @@ -1,33 +1,21 @@ import { i18n } from '../i18n'; -export const VALUE_FIELD = '$$value$$'; -export const EXTRA_FIELD = '$$extra$$'; -export const EXTRA_COLUMN_FIELD = '$$extra_column$$'; - -export const SERIES_NUMBER_FIELD = '$$series_number$$'; - -export const TOTAL_VALUE = '$$total$$'; -export const MULTI_VALUE = '$$multi$$'; - -export const NULL_SYMBOL_ID = '$$null$$'; -export const UNDEFINED_SYMBOL_ID = '$$undefined$$'; - +// 约定这个 z-index 为 0 的 container 作为基准 export const BACK_GROUND_GROUP_CONTAINER_Z_INDEX = 0; -/* - * foregroundGroup 上的 children 层叠顺序 - * 约定这个 z-index 为 0 的 container 作为基准 - */ +// foregroundGroup 上的 children 层叠顺序 export const FRONT_GROUND_GROUP_CONTAINER_Z_INDEX = 3; -export const FRONT_GROUND_GROUP_COL_SCROLL_Z_INDEX = 3; -export const FRONT_GROUND_GROUP_COL_FROZEN_Z_INDEX = 4; +export const FRONT_GROUND_GROUP_SCROLL_Z_INDEX = 3; +export const FRONT_GROUND_GROUP_FROZEN_Z_INDEX = 4; export const FRONT_GROUND_GROUP_RESIZE_AREA_Z_INDEX = 5; export const FRONT_GROUND_GROUP_BRUSH_SELECTION_Z_INDEX = 5; // panelGroup 上的 children 层叠顺序 export const PANEL_GROUP_GROUP_CONTAINER_Z_INDEX = 1; export const PANEL_GROUP_SCROLL_GROUP_Z_INDEX = 1; -export const PANEL_GROUP_FROZEN_GROUP_Z_INDEX = 2; +export const PANEL_GRID_GROUP_Z_INDEX = 2; +export const PANEL_MERGE_GROUP_Z_INDEX = 3; +export const PANEL_GROUP_FROZEN_GROUP_Z_INDEX = 4; // group's key export const KEY_GROUP_BACK_GROUND = 'backGroundGroup'; @@ -52,6 +40,8 @@ export const KEY_GROUP_COL_SCROLL = 'colScrollGroup'; export const KEY_GROUP_COL_FROZEN = 'colFrozenGroup'; export const KEY_GROUP_COL_FROZEN_TRAILING = 'colFrozenTrailingGroup'; export const KEY_GROUP_GRID_GROUP = 'gridGroup'; +export const KEY_GROUP_ROW_SCROLL = 'rowScrollGroup'; +export const KEY_GROUP_ROW_HEADER_FROZEN = 'rowHeaderFrozenGroup'; export const HORIZONTAL_RESIZE_AREA_KEY_PRE = 'horizontal-resize-area-'; @@ -73,9 +63,6 @@ export enum MiniChartTypes { Bullet = 'bullet', } -// 线条 linecap 样式 -export const SQUARE_LINE_CAP = 'square'; - export const getDefaultSeriesNumberText = (defaultText?: string) => defaultText ?? i18n('序号'); diff --git a/packages/s2-core/src/common/constant/copy.ts b/packages/s2-core/src/common/constant/copy.ts index 8ba009648e..d3818f24a8 100644 --- a/packages/s2-core/src/common/constant/copy.ts +++ b/packages/s2-core/src/common/constant/copy.ts @@ -5,6 +5,7 @@ export enum CopyType { } export const NewLine = '\r\n'; + export const NewTab = '\t'; // 每次异步渲染数据的阈值 diff --git a/packages/s2-core/src/common/constant/field.ts b/packages/s2-core/src/common/constant/field.ts new file mode 100644 index 0000000000..6a677e5b88 --- /dev/null +++ b/packages/s2-core/src/common/constant/field.ts @@ -0,0 +1,12 @@ +// 值字段的 id 是固定的! +export const VALUE_FIELD = '$$value$$'; +export const EXTRA_FIELD = '$$extra$$'; +export const ORIGIN_FIELD = '$$origin$$'; +export const EXTRA_COLUMN_FIELD = '$$extra_column$$'; +export const TOTAL_VALUE = '$$total$$'; +export const MULTI_VALUE = '$$multi$$'; +export const SERIES_NUMBER_FIELD = '$$series_number$$'; +export const EMPTY_FIELD_VALUE = '$$empty_field_value$$'; +export const EMPTY_EXTRA_FIELD_PLACEHOLDER = '$$empty_extra_placeholder$$'; +export const NULL_SYMBOL_ID = '$$null$$'; +export const UNDEFINED_SYMBOL_ID = '$$undefined$$'; diff --git a/packages/s2-core/src/common/constant/index.ts b/packages/s2-core/src/common/constant/index.ts index 8bb7b84fac..c9e8259d6d 100644 --- a/packages/s2-core/src/common/constant/index.ts +++ b/packages/s2-core/src/common/constant/index.ts @@ -1,3 +1,4 @@ +export * from './field'; export * from './events'; export * from './basic'; export * from './classnames'; @@ -12,3 +13,4 @@ export * from './resize'; export * from './copy'; export * from './pagination'; export * from './node'; +export * from './query'; diff --git a/packages/s2-core/src/common/constant/interaction.ts b/packages/s2-core/src/common/constant/interaction.ts index 0d775a572c..c8f7f8a641 100644 --- a/packages/s2-core/src/common/constant/interaction.ts +++ b/packages/s2-core/src/common/constant/interaction.ts @@ -17,6 +17,7 @@ export enum InteractionName { export enum InteractionStateName { ALL_SELECTED = 'allSelected', SELECTED = 'selected', + BRUSH_SELECTED = 'brushSelected', UNSELECTED = 'unselected', HOVER = 'hover', HOVER_FOCUS = 'hoverFocus', diff --git a/packages/s2-core/src/common/constant/node.ts b/packages/s2-core/src/common/constant/node.ts index 9f0023a63f..d24907d1ca 100644 --- a/packages/s2-core/src/common/constant/node.ts +++ b/packages/s2-core/src/common/constant/node.ts @@ -1,4 +1,3 @@ export const ROOT_NODE_ID = 'root'; - export const NODE_ID_SEPARATOR = '[&]'; export const ROOT_BEGINNING_REGEX = /^root\[&\]*/; diff --git a/packages/s2-core/src/common/constant/options.ts b/packages/s2-core/src/common/constant/options.ts index 7481fe55c4..2dc5fa57b3 100644 --- a/packages/s2-core/src/common/constant/options.ts +++ b/packages/s2-core/src/common/constant/options.ts @@ -10,7 +10,13 @@ import { EMPTY_PLACEHOLDER } from './basic'; export const MIN_DEVICE_PIXEL_RATIO = 1; -export enum LayoutWidthTypes { +/** + * 布局类型: + * adaptive: 行列等宽,均分整个 canvas 画布宽度 + * colAdaptive:列等宽,行头紧凑布局,列等分画布宽度减去行头宽度的剩余宽度 + * compact:行列紧凑布局,指标维度少的时候无法布满整个画布 + */ +export enum LayoutWidthType { Adaptive = 'adaptive', ColAdaptive = 'colAdaptive', Compact = 'compact', @@ -21,7 +27,7 @@ export const SPLIT_LINE_WIDTH = 1; export const DEFAULT_TREE_ROW_CELL_WIDTH = 120; export const DEFAULT_STYLE: S2Style = { - layoutWidthType: LayoutWidthTypes.Adaptive, + layoutWidthType: LayoutWidthType.Adaptive, rowCell: { showTreeLeafNodeAlignDot: false, widthByField: null, @@ -112,7 +118,7 @@ export const DEFAULT_MOBILE_OPTIONS: S2Options = { width: mobileWidth - 40, height: 380, style: { - layoutWidthType: LayoutWidthTypes.ColAdaptive, + layoutWidthType: LayoutWidthType.ColAdaptive, }, interaction: { hoverHighlight: false, diff --git a/packages/s2-core/src/common/constant/query.ts b/packages/s2-core/src/common/constant/query.ts new file mode 100644 index 0000000000..7f642628c3 --- /dev/null +++ b/packages/s2-core/src/common/constant/query.ts @@ -0,0 +1,6 @@ +export enum QueryDataType { + /* 获取所有的数据 */ + All = 'all', + /* 只需要明细数据 */ + DetailOnly = 'detailOnly', +} diff --git a/packages/s2-core/src/common/constant/total.ts b/packages/s2-core/src/common/constant/total.ts deleted file mode 100644 index 128ebdb566..0000000000 --- a/packages/s2-core/src/common/constant/total.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { TotalSelectionsOfMultiData } from '../../data-set/interface'; - -export const DEFAULT_TOTAL_SELECTIONS: TotalSelectionsOfMultiData = { - row: { - grandTotalOnly: false, - subTotalOnly: false, - totalDimensions: true, - }, - column: { - grandTotalOnly: false, - subTotalOnly: false, - totalDimensions: true, - }, -}; - -export enum DataSelectType { - // 获取所有的数据 - All = 'all', - // 只需要明细数据 - DetailOnly = 'detailOny', - // 只需要总计/小计数据 - TotalOnly = 'totalOnly', -} diff --git a/packages/s2-core/src/common/i18n/en_US.ts b/packages/s2-core/src/common/i18n/en_US.ts index 88e8e2429a..fb88221d5c 100644 --- a/packages/s2-core/src/common/i18n/en_US.ts +++ b/packages/s2-core/src/common/i18n/en_US.ts @@ -1,7 +1,7 @@ export const EN_US = { 小计: 'Total', 总计: 'Total', - 总和: 'SUM', + 总和: '(SUM)', 项: 'items', 已选择: 'selected', 序号: 'Index', @@ -15,4 +15,5 @@ export const EN_US = { 升序: 'ASC', 降序: 'DESC', 不排序: 'No order', + ',': ', ', }; diff --git a/packages/s2-core/src/common/i18n/zh_CN.ts b/packages/s2-core/src/common/i18n/zh_CN.ts index 6e677d4684..45913deb78 100644 --- a/packages/s2-core/src/common/i18n/zh_CN.ts +++ b/packages/s2-core/src/common/i18n/zh_CN.ts @@ -1,7 +1,7 @@ export const ZH_CN = { 小计: '小计', 总计: '总计', - 总和: '总和', + 总和: '(总和)', 项: '项', 已选择: '已选择', 序号: '序号', @@ -16,4 +16,5 @@ export const ZH_CN = { 降序: '降序', 组内降序: '组内降序', 不排序: '不排序', + ',': ',', }; diff --git a/packages/s2-core/src/common/icons/gui-icon.ts b/packages/s2-core/src/common/icons/gui-icon.ts index 5c12f6f101..6812659e49 100644 --- a/packages/s2-core/src/common/icons/gui-icon.ts +++ b/packages/s2-core/src/common/icons/gui-icon.ts @@ -1,9 +1,10 @@ /** - * @Description: 请严格要求 svg 的 viewBox,若设计产出的 svg 不是此规格,请叫其修改为 '0 0 1024 1024' + * @description: 请严格要求 svg 的 viewBox,若设计产出的 svg 不是此规格,请叫其修改为 '0 0 1024 1024' */ import { Group, type ImageStyleProps } from '@antv/g'; -import { omit, clone } from 'lodash'; +import { clone, omit } from 'lodash'; import { CustomImage } from '../../engine'; +import { DebuggerUtil } from '../debug'; import { getIcon } from './factory'; const STYLE_PLACEHOLDER = '<svg'; @@ -139,17 +140,27 @@ export class GuiIcon extends Group { } else { this.getImage(name, cacheKey, fill) .then((value: HTMLImageElement) => { - // 加载完成后,当前 Cell 可能已经销毁了 + // 异步加载完成后,当前 Cell 可能已经销毁了 if (this.destroyed) { + DebuggerUtil.getInstance().logger(`GuiIcon ${name} destroyed.`); + return; } image.attr('img', value); this.appendChild(image); }) - .catch((event: Event) => { + .catch((event: string | Event) => { + // 如果是 TypeError, 则是 G 底层渲染有问题, 其他场景才报加载异常的错误 + if (event instanceof TypeError) { + // eslint-disable-next-line no-console + console.warn(`GuiIcon ${name} destroyed:`, event); + + return; + } + // eslint-disable-next-line no-console - console.error(`GuiIcon ${name} load failed`, event); + console.error(`GuiIcon ${name} load failed:`, event); }); } } diff --git a/packages/s2-core/src/common/interface/basic.ts b/packages/s2-core/src/common/interface/basic.ts index 65b1d3ac64..5480d9511b 100644 --- a/packages/s2-core/src/common/interface/basic.ts +++ b/packages/s2-core/src/common/interface/basic.ts @@ -6,6 +6,7 @@ import type { IconPosition, RawData, ResizeInfo, + SimpleData, } from '../../common/interface'; import type { FrameConfig } from '../../common/interface/frame'; import type { Query } from '../../data-set'; @@ -27,7 +28,7 @@ export type { GetCellMeta, LayoutResult } from './facet'; */ export type Formatter = ( v: unknown, - data?: ViewMetaData | ViewMetaData[], + data?: SimpleData | ViewMetaData | ViewMetaData[], meta?: Node | ViewMeta, ) => string; @@ -68,14 +69,6 @@ export enum CellClipBox { CONTENT_BOX = 'contentBox', } -/** - * 布局类型: - * adaptive: 行列等宽,均分整个 canvas 画布宽度 - * colAdaptive:列等宽,行头紧凑布局,列等分画布宽度减去行头宽度的剩余宽度 - * compact:行列紧凑布局,指标维度少的时候无法布满整个画布 - */ -export type LayoutWidthType = 'adaptive' | 'colAdaptive' | 'compact'; - export interface Meta { /** * 字段 id @@ -179,6 +172,15 @@ export interface Total { */ subTotalsDimensions?: string[]; + /** + * 总计分组 + */ + grandTotalsGroupDimensions?: string[]; + /** + * 小计分组 + */ + subTotalsGroupDimensions?: string[]; + /** * 总计布局位置,默认是下或右 */ @@ -221,7 +223,7 @@ export interface Sort { sortByMeasure?: string; /** 筛选条件,缩小排序范围 */ - query?: Record<string, any>; + query?: Query; /** 组内排序用来显示icon */ type?: string; @@ -380,7 +382,7 @@ export type DataCellCallback = (viewMeta: ViewMeta) => DataCell; export type MergedCellCallback = ( spreadsheet: SpreadSheet, - cells: S2CellType[], + cells: DataCell[], meta?: ViewMeta, ) => MergedCell; @@ -399,7 +401,7 @@ export interface MergedCellInfo { } export type TempMergedCell = { - cells: S2CellType[]; + cells: DataCell[]; viewMeta: ViewMeta; }; @@ -418,7 +420,7 @@ export interface ViewMeta { // cell's height height: number; // cell origin data raws(multiple data) - data: ViewMetaData; + data: ViewMetaData | SimpleData | undefined; // cell' row index (in rowLeafNodes) rowIndex: number; // cell' col index (in colLeafNodes) diff --git a/packages/s2-core/src/common/interface/emitter.ts b/packages/s2-core/src/common/interface/emitter.ts index 9d22472eee..7b4319c519 100644 --- a/packages/s2-core/src/common/interface/emitter.ts +++ b/packages/s2-core/src/common/interface/emitter.ts @@ -1,23 +1,29 @@ import type { FederatedPointerEvent as CanvasEvent } from '@antv/g'; -import type { SpreadSheet } from '../../sheet-type'; -import type { DataCell } from '../../cell/data-cell'; -import type { RowCell } from '../../cell/row-cell'; +import type { + CornerCell, + MergedCell, + RowCell, + SeriesNumberCell, +} from '../../cell'; import type { ColCell } from '../../cell/col-cell'; +import type { DataCell } from '../../cell/data-cell'; import type { S2Event } from '../../common/constant'; import type { CellMeta, CellScrollPosition, Data, + FilterParam, HiddenColumnsInfo, LayoutResult, RowCellCollapsedParams, S2CellType, + S2Style, + SortParams, } from '../../common/interface'; -import type { FilterParam, SortParams, S2Style } from '../../common/interface'; import type { RawData } from '../../common/interface/s2DataConfig'; -import type { CopyableList } from '../../utils/export/interface'; import type { Node } from '../../facet/layout/node'; -import type { CornerCell, MergedCell, SeriesNumberCell } from '../../cell'; +import type { SpreadSheet } from '../../sheet-type'; +import type { CopyableList } from './export'; import type { ResizeInfo } from './resize'; type CanvasEventHandler = (event: CanvasEvent) => void; diff --git a/packages/s2-core/src/utils/export/interface.ts b/packages/s2-core/src/common/interface/export.ts similarity index 94% rename from packages/s2-core/src/utils/export/interface.ts rename to packages/s2-core/src/common/interface/export.ts index 328702343e..4480aab47f 100644 --- a/packages/s2-core/src/utils/export/interface.ts +++ b/packages/s2-core/src/common/interface/export.ts @@ -1,6 +1,6 @@ import type { SpreadSheet } from '../../sheet-type'; -import type { DataItem, CellMeta } from '../../common'; -import { EXTRA_FIELD } from '../../common'; +import type { DataItem, CellMeta } from '..'; +import { EXTRA_FIELD } from '..'; export type MatrixPlainTransformer = ( data: DataItem[][], diff --git a/packages/s2-core/src/common/interface/index.ts b/packages/s2-core/src/common/interface/index.ts index 8183cf5f81..610f33fbff 100644 --- a/packages/s2-core/src/common/interface/index.ts +++ b/packages/s2-core/src/common/interface/index.ts @@ -17,3 +17,5 @@ export * from './facet'; export * from './style'; export * from './collapse'; export * from './text'; +export * from './utils'; +export * from './export'; diff --git a/packages/s2-core/src/common/interface/interaction.ts b/packages/s2-core/src/common/interface/interaction.ts index af373c1665..c2024be3d6 100644 --- a/packages/s2-core/src/common/interface/interaction.ts +++ b/packages/s2-core/src/common/interface/interaction.ts @@ -1,10 +1,3 @@ -import type { SimpleBBox } from '../../engine'; -import type { - InteractionStateName, - CellType, - InterceptType, - ScrollbarPositionType, -} from '../constant'; import type { BaseCell, ColCell, @@ -16,14 +9,21 @@ import type { TableSeriesNumberCell, } from '../../cell'; import type { HeaderCell } from '../../cell/header-cell'; +import type { SeriesNumberCell } from '../../cell/series-number-cell'; +import type { SimpleBBox } from '../../engine'; import type { Node } from '../../facet/layout/node'; +import type { RootInteraction } from '../../interaction'; import type { BaseEvent } from '../../interaction/base-event'; import type { SpreadSheet } from '../../sheet-type'; -import type { RootInteraction } from '../../interaction'; -import type { SeriesNumberCell } from '../../cell/series-number-cell'; -import type { Transformer } from '../../utils/export/interface'; -import type { ResizeInteractionOptions } from './resize'; +import type { + CellType, + InteractionStateName, + InterceptType, + ScrollbarPositionType, +} from '../constant'; +import type { Transformer } from './export'; import type { ViewMeta } from './basic'; +import type { ResizeInteractionOptions } from './resize'; export type S2CellType<T extends SimpleBBox = ViewMeta> = | DataCell @@ -136,7 +136,7 @@ export interface HoverFocusOptions { duration?: number; } -export interface BrushSelection { +export interface BrushSelectionOptions { dataCell?: boolean; rowCell?: boolean; colCell?: boolean; @@ -163,7 +163,7 @@ export interface InteractionOptions { /** * 十字器高亮效果 */ - hoverHighlight?: boolean; + hoverHighlight?: InteractionCellHighlightOptions | boolean; /** * 悬停聚焦, 800ms 后会显示其对应 tooltip, 可以自定义 duration @@ -215,7 +215,7 @@ export interface InteractionOptions { /** * 刷选 */ - brushSelection?: BrushSelection | boolean; + brushSelection?: BrushSelectionOptions | boolean; /** * 多选 Command/Ctrl + click @@ -246,7 +246,7 @@ export interface InteractionOptions { /** * 选中单元格高亮联动 (高亮所对应行头/列头, 高亮当前行/当前列) */ - selectedCellHighlight?: boolean | InteractionCellSelectedHighlightOptions; + selectedCellHighlight?: boolean | InteractionCellHighlightOptions; /** * 滚动到边界的行为 @@ -255,6 +255,11 @@ export interface InteractionOptions { */ overscrollBehavior?: 'auto' | 'none' | 'contain' | null; + /** + * 表格滚动后是否触发 hover + */ + hoverAfterScroll?: boolean; + /** * 自定义交互 * @see https://s2.antv.antgroup.com/manual/advanced/interaction/custom @@ -262,7 +267,7 @@ export interface InteractionOptions { customInteractions?: CustomInteraction[]; } -export interface InteractionCellSelectedHighlightOptions { +export interface InteractionCellHighlightOptions { /** 高亮行头 */ rowHeader?: boolean; /** 高亮列头 */ diff --git a/packages/s2-core/src/common/interface/resize.ts b/packages/s2-core/src/common/interface/resize.ts index 0b04116ae5..f51ff2fe03 100644 --- a/packages/s2-core/src/common/interface/resize.ts +++ b/packages/s2-core/src/common/interface/resize.ts @@ -35,6 +35,8 @@ export interface ResizeGuideLinePosition { export interface ResizePosition { offsetX?: number; offsetY?: number; + clientX?: number; + clientY?: number; } export interface ResizeDetail { diff --git a/packages/s2-core/src/common/interface/s2DataConfig.ts b/packages/s2-core/src/common/interface/s2DataConfig.ts index 3ace8790ce..ce21a19d7a 100644 --- a/packages/s2-core/src/common/interface/s2DataConfig.ts +++ b/packages/s2-core/src/common/interface/s2DataConfig.ts @@ -1,8 +1,4 @@ -import type { - EXTRA_FIELD, - MiniChartTypes, - VALUE_FIELD, -} from '../constant/basic'; +import type { EXTRA_FIELD, MiniChartTypes, VALUE_FIELD } from '../constant'; import type { Fields, FilterParam, Meta, SortParams } from './basic'; export interface BaseChartData { @@ -88,7 +84,7 @@ export type ExtraData = { [VALUE_FIELD]: string | DataItem; }; -export type Data = (RawData & ExtraData) | undefined | null; +export type Data = RawData & ExtraData; export interface CustomTreeNode { /** @@ -136,5 +132,3 @@ export interface S2DataConfig { filterParams?: FilterParam[]; [key: string]: unknown; } - -export type FlattingIndexesData = RawData[][] | RawData[] | RawData; diff --git a/packages/s2-core/src/common/interface/s2Options.ts b/packages/s2-core/src/common/interface/s2Options.ts index 8531a8ce5c..53c2e92a72 100644 --- a/packages/s2-core/src/common/interface/s2Options.ts +++ b/packages/s2-core/src/common/interface/s2Options.ts @@ -123,11 +123,6 @@ export interface S2BasicOptions< */ placeholder?: ((meta: Record<string, any>) => string) | string; - // /** - // * 自定义 DPR, 默认 "window.devicePixelRatio" - // */ - // devicePixelRatio?: number; - /** * 设备类型: pc / mobile */ @@ -257,6 +252,11 @@ export interface S2PivotSheetFrozenOptions { * 当值为 boolean 时,true 对应冻结最大区域为 0.5, false 对应 0 */ rowHeader?: boolean | number; + + /** + * 是否冻结首行 (适用于总计置于顶部, 树状模式等场景) + */ + firstRow?: boolean; } export interface S2TableSheetFrozenOptions { @@ -281,11 +281,13 @@ export interface S2TableSheetFrozenOptions { trailingColCount?: number; } +export type HierarchyType = 'grid' | 'tree'; + export interface S2PivotSheetOptions { /** * 行头布局类型, grid: 平铺网格 | tree: 树状结构 */ - hierarchyType?: 'grid' | 'tree'; + hierarchyType?: HierarchyType; /** * 小计/总计配置 diff --git a/packages/s2-core/src/common/interface/store.ts b/packages/s2-core/src/common/interface/store.ts index 275ae28764..5ee5bcdfef 100644 --- a/packages/s2-core/src/common/interface/store.ts +++ b/packages/s2-core/src/common/interface/store.ts @@ -79,7 +79,7 @@ export interface StoreKey { /** * 原始数据配置 */ - originalDataCfg: S2DataConfig; + originalDataCfg: Partial<S2DataConfig>; /** * 可视区域包裹盒模型 diff --git a/packages/s2-core/src/common/interface/style.ts b/packages/s2-core/src/common/interface/style.ts index 4532fa7a2d..f3cce47eea 100644 --- a/packages/s2-core/src/common/interface/style.ts +++ b/packages/s2-core/src/common/interface/style.ts @@ -1,5 +1,5 @@ import type { Node } from '../../facet/layout/node'; -import type { LayoutWidthType } from './basic'; +import type { LayoutWidthType } from '../constant'; export type CellCustomSize = | null diff --git a/packages/s2-core/src/common/interface/theme.ts b/packages/s2-core/src/common/interface/theme.ts index e9fd0691b2..06bccdb352 100644 --- a/packages/s2-core/src/common/interface/theme.ts +++ b/packages/s2-core/src/common/interface/theme.ts @@ -167,6 +167,9 @@ export interface CellTheme { /** 交互态 */ interactionState?: InteractionState; + + /** 单元格边线虚线 */ + borderDash?: LineStyleProps['lineDash']; } export interface IconTheme { @@ -268,6 +271,8 @@ export interface SplitLine { /** 线性变化右侧颜色 */ right: string; }; + /** 分割线虚线 */ + borderDash?: LineStyleProps['lineDash']; } export interface DefaultCellTheme extends GridAnalysisCellTheme { /** 粗体文本样式 */ diff --git a/packages/s2-core/src/common/interface/tooltip.ts b/packages/s2-core/src/common/interface/tooltip.ts index 2b8f8db802..5d3ef1c20e 100644 --- a/packages/s2-core/src/common/interface/tooltip.ts +++ b/packages/s2-core/src/common/interface/tooltip.ts @@ -1,10 +1,10 @@ import type { FederatedPointerEvent as CanvasEvent } from '@antv/g'; import type * as CSS from 'csstype'; -import type { Data, Point, S2CellType } from '../../common/interface'; +import type { Point, S2CellType, ViewMetaData } from '../../common/interface'; import type { SpreadSheet } from '../../sheet-type'; import type { BaseTooltip } from '../../ui/tooltip'; -export type TooltipDataItem = Data; +export type TooltipDataItem = ViewMetaData; export interface TooltipOperatorMenuInfo { key: string; diff --git a/packages/s2-core/src/common/interface/utils.ts b/packages/s2-core/src/common/interface/utils.ts new file mode 100644 index 0000000000..503aba346b --- /dev/null +++ b/packages/s2-core/src/common/interface/utils.ts @@ -0,0 +1,3 @@ +export type PickEssential<O> = { + [K in keyof O as Pick<Partial<O>, K> extends Pick<O, K> ? never : K]: O[K]; +}; diff --git a/packages/s2-core/src/data-set/base-data-set.ts b/packages/s2-core/src/data-set/base-data-set.ts index 7f78355960..42292cbe0c 100644 --- a/packages/s2-core/src/data-set/base-data-set.ts +++ b/packages/s2-core/src/data-set/base-data-set.ts @@ -3,6 +3,7 @@ import { find, get, identity, + isEmpty, isNil, isString, map, @@ -10,7 +11,7 @@ import { memoize, min, } from 'lodash'; -import type { CellMeta, CustomHeaderField, RowData } from '../common'; +import type { CellMeta, CustomHeaderField, ViewMeta } from '../common'; import { CellType } from '../common'; import type { Fields, @@ -20,8 +21,8 @@ import type { RawData, S2CellType, S2DataConfig, + SimpleData, SortParams, - ViewMeta, ViewMetaData, } from '../common/interface'; import type { ValueRange } from '../common/interface/condition'; @@ -32,6 +33,7 @@ import { setValueRangeState, } from '../utils/condition/state-controller'; import { generateExtraFieldMeta } from '../utils/dataset/pivot-data-set'; +import type { Indexes } from '../utils/indexes'; import type { GetCellDataParams, Query } from './interface'; import type { GetCellMultiDataParams } from './index'; @@ -52,9 +54,9 @@ export abstract class BaseDataSet { public originData: RawData[]; /** - * 二维索引数据 + * 索引数据 */ - public indexesData: RawData[][] | RawData[]; + public indexesData: Record<string, RawData[][] | RawData[]>; /** * 高级排序, 组内排序 @@ -69,8 +71,11 @@ export abstract class BaseDataSet { /** * 表格实例 */ - protected spreadsheet: SpreadSheet; + public spreadsheet: SpreadSheet; + /** + * 展示数据 + */ protected displayData: RawData[]; public constructor(spreadsheet: SpreadSheet) { @@ -207,16 +212,13 @@ export abstract class BaseDataSet { this.sortParams = sortParams; this.filterParams = filterParams; this.displayData = this.originData; - this.indexesData = []; + this.indexesData = {}; } /** * 添加 (角头/数值虚拟字段) 格式化信息 */ - public getFieldMetaWithExtraField( - meta: Meta[] = [], - defaultExtraFieldText: string, - ): Meta[] { + public processMeta(meta: Meta[] = [], defaultExtraFieldText: string): Meta[] { const newMeta: Meta[] = [ ...meta, generateExtraFieldMeta( @@ -233,6 +235,15 @@ export abstract class BaseDataSet { return this.displayData; } + public isEmpty() { + return isEmpty(this.getDisplayDataSet()); + } + + // https://github.com/antvis/S2/issues/2255 + public getEmptyViewIndexes(): Indexes { + return [] as unknown as Indexes; + } + public getValueRangeByField(field: string): ValueRange { const cacheRange = getValueRangeState(this.spreadsheet, field); @@ -292,11 +303,13 @@ export abstract class BaseDataSet { */ public abstract getCellData( params: GetCellDataParams, - ): ViewMetaData | undefined; + ): ViewMetaData | SimpleData | undefined; /** * 获取批量的单元格数据 * 如果 query 为空, 则返回全量数据 + * @description 默认获取符合 query 的所有数据,包括小计总计等汇总数据; + * 如果只希望获取明细数据,请使用 { queryType: QueryDataType.DetailOnly } */ public abstract getCellMultiData( params: GetCellMultiDataParams, @@ -310,8 +323,9 @@ export abstract class BaseDataSet { } /** - * get a row cells data including cell - * @param cellMeta + * 查询当前整行数据 */ - public abstract getRowData(cellMeta: CellMeta): RowData; + public abstract getRowData( + cellMeta: CellMeta | ViewMeta | Node, + ): ViewMetaData[] | ViewMetaData; } diff --git a/packages/s2-core/src/data-set/cell-data.ts b/packages/s2-core/src/data-set/cell-data.ts index cc4b3aa010..c112ad9738 100644 --- a/packages/s2-core/src/data-set/cell-data.ts +++ b/packages/s2-core/src/data-set/cell-data.ts @@ -1,7 +1,7 @@ /* eslint-disable no-empty-function */ import type { ViewMetaData } from '../common/interface/basic'; -import { EXTRA_FIELD, VALUE_FIELD } from '../common/constant/basic'; import type { RawData } from '../common/interface/s2DataConfig'; +import { ORIGIN_FIELD, EXTRA_FIELD, VALUE_FIELD } from '../common/constant'; export class CellData { constructor( @@ -9,20 +9,24 @@ export class CellData { private extraField: string, ) {} - static getCellDataList(raw: RawData, extraFields: string[]) { - return extraFields.map((field) => new CellData(raw, field)); + static getCellData(raw: RawData, extraField: string) { + return new CellData(raw, extraField); } - getOrigin() { - return this.raw; + static getCellDataList(raw: RawData, extraFields: string[]) { + return extraFields.map((field) => CellData.getCellData(raw, field)); } - getValueByField(field: string) { - if (field === VALUE_FIELD || field === EXTRA_FIELD) { - return this[field]; + static getFieldValue(data: ViewMetaData, field: string = '') { + if (data instanceof CellData) { + return field ? data.getValueByField(field) : data[ORIGIN_FIELD]; } - return this.raw?.[field]; + return data?.[field]; + } + + get [ORIGIN_FIELD]() { + return this.raw; } get [EXTRA_FIELD]() { @@ -30,14 +34,14 @@ export class CellData { } get [VALUE_FIELD]() { - return this.raw?.[this.extraField]; + return this.raw[this.extraField]; } -} -export const getFieldValueOfViewMetaData = (data: ViewMetaData, field = '') => { - if (data instanceof CellData) { - return field ? data.getValueByField(field) : data.getOrigin(); - } + getValueByField(field: string) { + if (field === VALUE_FIELD || field === EXTRA_FIELD) { + return this[field]; + } - return data?.[field]; -}; + return this.raw[field]; + } +} diff --git a/packages/s2-core/src/data-set/custom-grid-pivot-data-set.ts b/packages/s2-core/src/data-set/custom-grid-pivot-data-set.ts index 719b2827ed..a35feb5445 100644 --- a/packages/s2-core/src/data-set/custom-grid-pivot-data-set.ts +++ b/packages/s2-core/src/data-set/custom-grid-pivot-data-set.ts @@ -8,7 +8,7 @@ export class CustomGridPivotDataSet extends CustomTreePivotDataSet { const rows = valueInCols ? [EXTRA_FIELD] : [...(dataCfg.fields.rows || []), EXTRA_FIELD]; - const meta = this.getFieldMetaWithExtraField(dataCfg.meta!, i18n('数值')); + const meta = this.processMeta(dataCfg.meta!, i18n('数值')); return { ...dataCfg, diff --git a/packages/s2-core/src/data-set/custom-tree-pivot-data-set.ts b/packages/s2-core/src/data-set/custom-tree-pivot-data-set.ts index c5b02d6173..4fea6af241 100644 --- a/packages/s2-core/src/data-set/custom-tree-pivot-data-set.ts +++ b/packages/s2-core/src/data-set/custom-tree-pivot-data-set.ts @@ -1,9 +1,10 @@ -import { get } from 'lodash'; +import { get, type PropertyPath } from 'lodash'; import { EXTRA_FIELD } from '../common/constant'; -import type { Meta, S2DataConfig } from '../common/interface'; import { i18n } from '../common/i18n'; +import type { Meta, S2DataConfig } from '../common/interface'; import { getDataPath, + getDataPathPrefix, transformDimensionsValues, } from '../utils/dataset/pivot-data-set'; import { CellData } from './cell-data'; @@ -12,7 +13,7 @@ import { PivotDataSet } from './pivot-data-set'; export class CustomTreePivotDataSet extends PivotDataSet { getCellData(params: GetCellMultiDataParams) { - const { query } = params; + const { query = {} } = params || {}; const { columns, rows } = this.fields; const rowDimensionValues = transformDimensionsValues( query, @@ -27,12 +28,15 @@ export class CustomTreePivotDataSet extends PivotDataSet { colDimensionValues, rowPivotMeta: this.rowPivotMeta, colPivotMeta: this.colPivotMeta, + rowFields: rows as string[], + colFields: columns as string[], + prefix: getDataPathPrefix(rows as string[], columns as string[]), }); - const rawData = get(this.indexesData, path); + const rawData = get(this.indexesData, path as PropertyPath); if (rawData) { - return new CellData(rawData, query[EXTRA_FIELD]); + return CellData.getCellData(rawData, query[EXTRA_FIELD]); } } @@ -45,10 +49,7 @@ export class CustomTreePivotDataSet extends PivotDataSet { */ const updatedDataCfg = super.processDataCfg(dataCfg); - const newMeta: Meta[] = this.getFieldMetaWithExtraField( - dataCfg.meta, - i18n('指标'), - ); + const newMeta: Meta[] = this.processMeta(dataCfg.meta, i18n('指标')); return { ...updatedDataCfg, diff --git a/packages/s2-core/src/data-set/index.ts b/packages/s2-core/src/data-set/index.ts index 8d658cf4fa..f0ffc95172 100644 --- a/packages/s2-core/src/data-set/index.ts +++ b/packages/s2-core/src/data-set/index.ts @@ -1,17 +1,17 @@ import { BaseDataSet } from './base-data-set'; +import { CustomGridPivotDataSet } from './custom-grid-pivot-data-set'; +import { CustomTreePivotDataSet } from './custom-tree-pivot-data-set'; import { PivotDataSet } from './pivot-data-set'; import { TableDataSet } from './table-data-set'; -import { CustomTreePivotDataSet } from './custom-tree-pivot-data-set'; -import { CustomGridPivotDataSet } from './custom-grid-pivot-data-set'; export { CellData } from './cell-data'; export { BaseDataSet, + CustomGridPivotDataSet, + CustomTreePivotDataSet, PivotDataSet, TableDataSet, - CustomTreePivotDataSet, - CustomGridPivotDataSet, }; export * from './interface'; diff --git a/packages/s2-core/src/data-set/interface.ts b/packages/s2-core/src/data-set/interface.ts index 3ba7fa5fa1..af8de99a3f 100644 --- a/packages/s2-core/src/data-set/interface.ts +++ b/packages/s2-core/src/data-set/interface.ts @@ -1,26 +1,20 @@ -import type { BaseFields, SortParam } from '../common/interface'; +import type { QueryDataType } from '../common'; +import type { RawData, SortParam } from '../common/interface'; import type { Node } from '../facet/layout/node'; import type { CellData } from './cell-data'; import type { PivotDataSet } from './pivot-data-set'; export type Query = Record<string, any>; -export type TotalSelection = { - grandTotalOnly?: boolean; - subTotalOnly?: boolean; - totalDimensions?: boolean | string[]; -}; - -export type TotalSelectionsOfMultiData = { - row?: TotalSelection; - column?: TotalSelection; -}; - export type PivotMetaValue = { - // field level index + // 当前维度结合父级维度生成的完整 id 信息 + id: string; + // 当前维度结合父级维度生成的完整 dimensions 信息,主要是预防 field 数据本身出现 [&] 导致维度信息识别不正确 + dimensions: string[]; + // 当前维度 + value: string; level: number; children: PivotMeta; - // field name childField?: string; }; @@ -28,32 +22,33 @@ export type PivotMeta = Map<string, PivotMetaValue>; export type SortedDimensionValues = Record<string, string[]>; +export interface OnFirstCreateParams { + careRepeated?: boolean; + // 维度 id,如 city + dimension: string; + // 完整维度信息:'四川省[&]成都市' + dimensionPath: string; +} + +export type DataPath = (number | string | undefined)[]; + export type DataPathParams = { rowDimensionValues: string[]; colDimensionValues: string[]; - shouldCreateOrUpdate?: boolean; + rowPivotMeta: PivotMeta; + colPivotMeta: PivotMeta; + rowFields: string[]; + colFields: string[]; + // first create data path + isFirstCreate?: boolean; // callback when pivot map create node - onCreate?: (params: { - // 维度 id,如 city - dimension: string; - // 维度数组 ['四川省', '成都市'] - dimensionPath: string[]; - }) => void; - rowPivotMeta?: PivotMeta; - colPivotMeta?: PivotMeta; -} & BaseFields; - -export type DataPath = (number | string)[]; + onFirstCreate?: (params: OnFirstCreateParams) => void; + prefix?: string; +}; export interface GetCellDataParams { - /** - * 查询条件 - */ - query: Query; - - /** - * 是否是汇总节点 - */ + // search query + query?: Query; isTotals?: boolean; /** @@ -65,19 +60,35 @@ export interface GetCellDataParams { * 是否是行头 */ isRow?: boolean; + // use with isTotals + totalStatus?: TotalStatus; +} + +export interface CheckAccordQueryParams { + // item of sortedDimensionValues,es: "浙江省[&]杭州市[&]家具[&]桌子" + dimensionValues: string; + query: Query; + // rows or columns + dimensions: string[]; + field: string; +} + +export interface TotalStatus { + isRowTotal: boolean; + isRowSubTotal: boolean; + isColTotal: boolean; + isColSubTotal: boolean; } export interface GetCellMultiDataParams { /** * 查询条件 */ - query: Query; - + query?: Query; /** - * 汇总 + * 查询类型 */ - totals?: TotalSelectionsOfMultiData; - + queryType?: QueryDataType; /** * 下钻 */ @@ -92,3 +103,12 @@ export interface SortActionParams { sortByValues?: string[]; isSortByMeasure?: boolean; } + +export interface SortPivotMetaParams { + pivotMeta: PivotMeta; + dimensions: string[]; + sortedDimensionValues: string[]; + sortFieldId: string; +} + +export type FlattingIndexesData = RawData[][] | RawData[] | RawData; diff --git a/packages/s2-core/src/data-set/pivot-data-set.ts b/packages/s2-core/src/data-set/pivot-data-set.ts index 7d43da9bcd..721f64f720 100644 --- a/packages/s2-core/src/data-set/pivot-data-set.ts +++ b/packages/s2-core/src/data-set/pivot-data-set.ts @@ -10,57 +10,58 @@ import { get, has, includes, + indexOf, isArray, isEmpty, isNumber, - isUndefined, map, + some, uniq, unset, + type PropertyPath, + omit, } from 'lodash'; -import type { CellMeta } from '../common'; +import { + QueryDataType, + type CellMeta, + type CustomHeaderFields, + type Data, +} from '../common'; import { EXTRA_FIELD, MULTI_VALUE, - NODE_ID_SEPARATOR, TOTAL_VALUE, VALUE_FIELD, } from '../common/constant'; -import { DataSelectType } from '../common/constant/total'; -import { DebuggerUtil, DEBUG_TRANSFORM_DATA } from '../common/debug'; +import { DEBUG_TRANSFORM_DATA, DebuggerUtil } from '../common/debug'; import { i18n } from '../common/i18n'; import type { - CustomHeaderFields, - FlattingIndexesData, Formatter, Meta, PartDrillDownDataCache, PartDrillDownFieldInLevel, RawData, - RowData, S2DataConfig, - TotalsStatus, ViewMeta, } from '../common/interface'; import { Node } from '../facet/layout/node'; -import { - filterTotal, - flattenIndexesData, - getAggregationAndCalcFuncByQuery, - getListBySorted, - getTotalSelection, -} from '../utils/data-set-operate'; +import { getAggregationAndCalcFuncByQuery } from '../utils/data-set-operate'; import { deleteMetaById, filterExtraDimension, + flattenIndexesData, getDataPath, - getDimensionsWithoutPathPre, - shouldQueryMultiData, + getDataPathPrefix, + getExistValues, + getFlattenDimensionValues, + getSatisfiedPivotMetaValues, + isMultiValue, transformDimensionsValues, transformIndexesData, + type TransformResult, } from '../utils/dataset/pivot-data-set'; import { calcActionByType } from '../utils/number-calculate'; -import { handleSortAction } from '../utils/sort-action'; +import { getSortedPivotMeta, handleSortAction } from '../utils/sort-action'; import { BaseDataSet } from './base-data-set'; import { CellData } from './cell-data'; import type { @@ -69,8 +70,7 @@ import type { PivotMeta, Query, SortedDimensionValues, - TotalSelection, - TotalSelectionsOfMultiData, + TotalStatus, } from './interface'; export class PivotDataSet extends BaseDataSet { @@ -83,6 +83,10 @@ export class PivotDataSet extends BaseDataSet { // sorted dimension values public sortedDimensionValues: SortedDimensionValues; + getExistValuesByDataItem(data: RawData, values: string[]) { + return getExistValues(data, values); + } + /** * When data related config changed, we need * 1、re-process config @@ -92,27 +96,43 @@ export class PivotDataSet extends BaseDataSet { */ public setDataCfg(dataCfg: S2DataConfig) { super.setDataCfg(dataCfg); + const { rows } = this.fields; + this.sortedDimensionValues = {}; this.rowPivotMeta = new Map(); this.colPivotMeta = new Map(); + this.transformIndexesData(this.originData, rows as string[]); + this.handleDimensionValuesSort(); + } + + public transformIndexesData( + data: RawData[], + rows: string[], + ): TransformResult { + const { columns, values, valueInCols } = this.fields; + + let result!: TransformResult; DebuggerUtil.getInstance().debugCallback(DEBUG_TRANSFORM_DATA, () => { - const { rows, columns, values } = this.fields; - const { indexesData } = transformIndexesData({ - rows, - columns, - values, - originData: this.originData, + result = transformIndexesData({ + rows: rows as string[], + columns: columns as string[], + values: values!, + valueInCols: valueInCols!, + data, indexesData: this.indexesData, sortedDimensionValues: this.sortedDimensionValues, rowPivotMeta: this.rowPivotMeta, colPivotMeta: this.colPivotMeta, + getExistValuesByDataItem: this.getExistValuesByDataItem, }); - - this.indexesData = indexesData; + this.indexesData = result.indexesData; + this.rowPivotMeta = result.rowPivotMeta; + this.colPivotMeta = result.colPivotMeta; + this.sortedDimensionValues = result.sortedDimensionValues; }); - this.handleDimensionValuesSort(); + return result; } /** @@ -126,7 +146,6 @@ export class PivotDataSet extends BaseDataSet { drillDownData: RawData[], rowNode: Node, ) { - const { columns, values } = this.fields; const currentRowFields = Node.getFieldPath(rowNode, true); const nextRowFields = [...currentRowFields, extraRowField]; const store = this.spreadsheet.store; @@ -138,33 +157,16 @@ export class PivotDataSet extends BaseDataSet { if (idPathMap.has(rowNodeId)) { // the current node has a drill-down field, clean it forEach(idPathMap.get(rowNodeId), (path) => { - unset(this.indexesData, path); + unset(this.indexesData, path as unknown as PropertyPath); }); deleteMetaById(this.rowPivotMeta, rowNodeId); } // 3、转换数据 - const { - paths: drillDownDataPaths, - indexesData, - rowPivotMeta, - colPivotMeta, - sortedDimensionValues, - } = transformIndexesData({ - rows: nextRowFields, - columns, - values, - originData: drillDownData, - indexesData: this.indexesData, - sortedDimensionValues: this.sortedDimensionValues, - rowPivotMeta: this.rowPivotMeta, - colPivotMeta: this.colPivotMeta, - }); - - this.indexesData = indexesData; - this.rowPivotMeta = rowPivotMeta!; - this.colPivotMeta = colPivotMeta!; - this.sortedDimensionValues = sortedDimensionValues; + const { paths: drillDownDataPaths } = this.transformIndexesData( + drillDownData, + nextRowFields, + ); /* * 4、record data paths by nodeId @@ -184,7 +186,7 @@ export class PivotDataSet extends BaseDataSet { const idPathMap = store.get('drillDownIdPathMap'); if (!idPathMap) { - return; + return false; } const drillDownDataCache = store.get( @@ -198,12 +200,13 @@ export class PivotDataSet extends BaseDataSet { if (currentIdPathMap) { forEach(currentIdPathMap, (path) => { - unset(this.indexesData, path); + unset(this.indexesData, path as unknown as PropertyPath); }); } // 2. 删除 rowPivotMeta 当前下钻层级对应 meta 信息 deleteMetaById(this.rowPivotMeta, rowNodeId); + // 3. 删除下钻缓存路径 idPathMap.delete(rowNodeId); @@ -245,6 +248,8 @@ export class PivotDataSet extends BaseDataSet { } store.set('drillDownIdPathMap', idPathMap); + + return true; } /** @@ -271,9 +276,33 @@ export class PivotDataSet extends BaseDataSet { }); this.sortedDimensionValues[sortFieldId] = result; + this.handlePivotMetaSort(sortFieldId, result); }); }; + protected handlePivotMetaSort( + sortFieldId: string, + sortedDimensionValues: string[], + ) { + const { rows, columns } = this.fields; + + if (includes(rows, sortFieldId)) { + this.rowPivotMeta = getSortedPivotMeta({ + pivotMeta: this.rowPivotMeta, + dimensions: rows as string[], + sortFieldId, + sortedDimensionValues, + }); + } else if (includes(columns, sortFieldId)) { + this.colPivotMeta = getSortedPivotMeta({ + pivotMeta: this.colPivotMeta, + dimensions: columns as string[], + sortFieldId, + sortedDimensionValues, + }); + } + } + public processDataCfg(dataCfg: S2DataConfig): S2DataConfig { const { data, meta = [], fields, sortParams = [] } = dataCfg; const { @@ -297,7 +326,7 @@ export class PivotDataSet extends BaseDataSet { : uniq([...rows, EXTRA_FIELD]); } - const newMeta: Meta[] = this.getFieldMetaWithExtraField(meta, i18n('数值')); + const newMeta: Meta[] = this.processMeta(meta, i18n('数值')); return { data, @@ -312,75 +341,68 @@ export class PivotDataSet extends BaseDataSet { }; } - public getDimensionValues(field: string, query?: Query): string[] { + protected getFieldsAndPivotMetaByField(field: string) { const { rows = [], columns = [] } = this.fields || {}; - let meta: PivotMeta = new Map(); - let dimensions: CustomHeaderFields = []; - - if (includes(rows, field)) { - meta = this.rowPivotMeta; - dimensions = rows; - } else if (includes(columns, field)) { - meta = this.colPivotMeta; - dimensions = columns as string[]; - } - if (!isEmpty(query)) { - let sortedMeta: string[] = []; - const dimensionValuePath = []; - - for (const dimension of dimensions) { - const value: string = get(query, dimension as string); + if (rows.includes(field)) { + return { + dimensions: rows as string[], + pivotMeta: this.rowPivotMeta, + }; + } - dimensionValuePath.push(`${value}`); - const cacheKey = dimensionValuePath.join(`${NODE_ID_SEPARATOR}`); + if (columns.includes(field)) { + return { + dimensions: columns as string[], + pivotMeta: this.colPivotMeta, + }; + } - if (meta.has(value) && !isUndefined(value)) { - const childField = meta.get(value)?.childField; + return {}; + } - meta = meta.get(value)!.children; - if ( - find(this.sortParams, (item) => item.sortFieldId === childField) && - this.sortedDimensionValues[childField!] - ) { - const dimensionValues = this.sortedDimensionValues[ - childField! - ]?.filter((item) => item?.includes(cacheKey)); + public getDimensionValues(field: string, query: Query = {}): string[] { + const { pivotMeta, dimensions } = this.getFieldsAndPivotMetaByField(field); - sortedMeta = getDimensionsWithoutPathPre([...dimensionValues]); - } else { - sortedMeta = [...meta.keys()]; - } - } - } - if (isEmpty(sortedMeta)) { - return []; - } - - return filterTotal(getListBySorted([...meta.keys()], sortedMeta)); + if (!pivotMeta || !dimensions) { + return []; } - if (this.sortedDimensionValues[field]) { - return filterTotal( - getDimensionsWithoutPathPre([...this.sortedDimensionValues[field]]), - ); - } + const dimensionValues = transformDimensionsValues( + query, + dimensions, + MULTI_VALUE, + ); + + const values = getSatisfiedPivotMetaValues({ + pivotMeta, + dimensionValues, + fields: dimensions, + fieldIdx: indexOf(dimensions, field), + queryType: QueryDataType.DetailOnly, + sortedDimensionValues: this.sortedDimensionValues, + }); - return filterTotal([...meta.keys()]); + return uniq(values.map((v) => v.value)); } - getTotalValue(query: Query) { + getTotalValue(query: Query, totalStatus?: TotalStatus) { + const effectiveStatus = some(totalStatus); + const status = effectiveStatus ? totalStatus! : this.getTotalStatus(query); const { aggregation, calcFunc } = getAggregationAndCalcFuncByQuery( - this.getTotalStatus(query) as TotalsStatus, - this.spreadsheet.options?.totals!, + status, + this.spreadsheet.options?.totals, ) || {}; const calcAction = calcActionByType[aggregation!]; // 前端计算汇总值 if (calcAction || !!calcFunc) { - const data = this.getCellMultiData({ query }); + const data = this.getCellMultiData({ + query, + queryType: QueryDataType.DetailOnly, + }); let totalValue: number | null = null; if (calcFunc) { @@ -389,18 +411,18 @@ export class PivotDataSet extends BaseDataSet { totalValue = calcAction(data, VALUE_FIELD)!; } - return new CellData( - { ...query, [query[EXTRA_FIELD]]: totalValue }, + return CellData.getCellData( + { ...omit(query, [EXTRA_FIELD]), [query[EXTRA_FIELD]]: totalValue }, query[EXTRA_FIELD], ); } } public getCellData(params: GetCellDataParams) { - const { query = {}, rowNode, isTotals = false } = params || {}; + const { query = {}, rowNode, isTotals = false, totalStatus } = params || {}; const { rows: originRows, columns } = this.fields; - let rows = originRows; + let rows = originRows as string[]; const drillDownIdPathMap = this.spreadsheet?.store.get('drillDownIdPathMap'); @@ -412,15 +434,13 @@ export class PivotDataSet extends BaseDataSet { (parentPath) => rowNode?.id.startsWith(parentPath), ); - // 如果是下钻结点,小计行维度在 originRows 中并不存在 - if (!isTotals || isDrillDown) { - rows = Node.getFieldPath(rowNode!, isDrillDown) ?? originRows; + // 如果是下钻结点,行维度在 originRows 中并不存在 + if (rowNode && isDrillDown) { + rows = + Node.getFieldPath(rowNode, isDrillDown) ?? (originRows as string[]); } - const rowDimensionValues = transformDimensionsValues( - query, - rows as string[], - ); + const rowDimensionValues = transformDimensionsValues(query, rows); const colDimensionValues = transformDimensionsValues( query, columns as string[], @@ -430,17 +450,20 @@ export class PivotDataSet extends BaseDataSet { colDimensionValues, rowPivotMeta: this.rowPivotMeta, colPivotMeta: this.colPivotMeta, + rowFields: rows as string[], + colFields: columns as string[], + prefix: getDataPathPrefix(rows as string[], columns as string[]), }); - const rawData = get(this.indexesData, path); + const rawData = get(this.indexesData, path as PropertyPath); if (rawData) { // 如果已经有数据则取已有数据 - return new CellData(rawData, query[EXTRA_FIELD]); + return CellData.getCellData(rawData, query[EXTRA_FIELD]); } if (isTotals) { - return this.getTotalValue(query); + return this.getTotalValue(query, totalStatus); } } @@ -450,7 +473,7 @@ export class PivotDataSet extends BaseDataSet { if (isSubTotal) { const firstDimension = find(dimensions, (item) => !has(query, item)); - return firstDimension && firstDimension !== first(dimensions); + return !!(firstDimension && firstDimension !== first(dimensions)); } return every(dimensions, (item) => !has(query, item)); @@ -464,135 +487,111 @@ export class PivotDataSet extends BaseDataSet { }; }; - protected getMultiDataQueryPath(query: Query, drillDownFields?: string[]) { - const { rows = [], columns = [] } = this.fields; - const totalRows = !isEmpty(drillDownFields) - ? uniq(rows.concat(drillDownFields!)) - : rows; + protected getQueryExtraFields(query: Query): string[] { + const { values = [] } = this.fields; + const extra = query[EXTRA_FIELD]; + + if (extra) { + return includes(values, extra) ? [extra] : []; + } + + return values; + } + + public getCellMultiData(params: GetCellMultiDataParams) { + const { + query = {}, + queryType = QueryDataType.All, + drillDownFields = [], + } = params || {}; + + if (isEmpty(query)) { + // 如果查询的 query 为空,这样的场景其实没有意义,如果用户想获取全量数据,可以直接从 data 中获取 + // eslint-disable-next-line no-console + console.warn( + `query: ${query} shouldn't be empty, you can get all data from dataCfg if you're intended.\n you should use { EXTRA_FIELD: xxx} as least if you want query all specific value data`, + ); + } + + const { rows, columns } = this.fields; + const totalRows: string[] = !isEmpty(drillDownFields) + ? (rows as string[]).concat(drillDownFields!) + : (rows as string[]); const rowDimensionValues = transformDimensionsValues( query, - totalRows as string[], + totalRows, MULTI_VALUE, ); - const colDimensionValues = transformDimensionsValues( query, columns as string[], MULTI_VALUE, ); - const path: (string | number)[] = getDataPath({ + const { rowQueries, colQueries } = getFlattenDimensionValues({ rowDimensionValues, colDimensionValues, rowPivotMeta: this.rowPivotMeta, colPivotMeta: this.colPivotMeta, + rowFields: totalRows, + colFields: columns as string[], + sortedDimensionValues: this.sortedDimensionValues, + queryType, }); - return { path, rows: totalRows, columns }; - } - - protected getQueryExtraFields(query: Query) { - const { values } = this.fields; - - return query?.[EXTRA_FIELD] ? [query[EXTRA_FIELD]] : values; - } - - protected getTotalSelectionByDimensions( - rows: string[], - columns: string[], - totals?: TotalSelectionsOfMultiData, - ) { - const getTotalSelectTypes = ( - dimensions: string[], - totalSelection?: TotalSelection, - ) => { - const { grandTotalOnly, subTotalOnly, totalDimensions } = - totalSelection || ({} as TotalSelection); - - return filterExtraDimension(dimensions).map((dimension, idx) => { - let type = DataSelectType.DetailOnly; - - if ( - totalDimensions === true || - includes(totalDimensions as string[], dimension) - ) { - type = DataSelectType.All; - } + const prefix = getDataPathPrefix(totalRows, columns as string[]); + const all: RawData[] = []; + + for (const rowQuery of rowQueries) { + for (const colQuery of colQueries) { + const path = getDataPath({ + rowDimensionValues: rowQuery, + colDimensionValues: colQuery, + rowPivotMeta: this.rowPivotMeta, + colPivotMeta: this.colPivotMeta, + rowFields: totalRows, + colFields: columns as string[], + prefix, + }); - // 如果当前可以选择总计/小计数据,则进一步根据 grandTotalOnly 以及 subTotalOnly 收缩范围 - if ( - type === DataSelectType.All && - ((idx === 0 && grandTotalOnly) || (idx !== 0 && subTotalOnly)) - ) { - type = DataSelectType.TotalOnly; + let hadMultiField = false; + let result: any = this.indexesData; + + for (let i = 0; i < path.length; i++) { + const current = path[i]; + + if (hadMultiField) { + if (isMultiValue(current)) { + result = flattenIndexesData(result, queryType); + } else { + result = compact( + map(result, (item) => item?.[current as string | number]), + ); + } + } else if (isMultiValue(current)) { + hadMultiField = true; + result = compact([result]); + i--; + } else { + result = result?.[current as string | number]; + } } - - return type; - }); - }; - - totals = getTotalSelection(totals); - - const rowSelectTypes = getTotalSelectTypes(rows, totals?.row); - const columnSelectTypes = getTotalSelectTypes(columns, totals?.column); - - return rowSelectTypes.concat(columnSelectTypes); - } - - public getCellMultiData(params: GetCellMultiDataParams) { - const { query, totals, drillDownFields } = params; - const { path, rows, columns } = this.getMultiDataQueryPath( - query, - drillDownFields, - ); - - const selectTypes = this.getTotalSelectionByDimensions( - rows as string[], - columns as string[], - totals, - ); - - let hadMultiField = false; - let result: FlattingIndexesData = this.indexesData; - - /* - * TODO: 原本的展开逻辑在有下钻的情况下,应该是有问题的, - * 因为下钻数据是放在原本的明细数据里面, 在对象里面添加了 1,2,3 这样的属性值来储存下钻数据 - * 由于对下钻整体的逻辑还不是很清楚,这里只是对原来的逻辑进行效率优化 - * 后续如果需要进行下钻优化,这里也需要同时处理 - */ - for (let i = 0; i < path.length; i++) { - const current = path[i]; - - if (hadMultiField) { - if (shouldQueryMultiData(current)) { - result = flattenIndexesData( - result, - selectTypes[i], - ) as FlattingIndexesData; - } else { - result = map(result!, (item) => get(item, current)); + // 如果每一个维度都是被指定好的,那么最终获取的数据就是单个的 + if (isArray(result)) { + all.push(...(result as Data[])); + } else if (result) { + all.push(result); } - } else if (shouldQueryMultiData(current)) { - hadMultiField = true; - result = [result] as FlattingIndexesData; - i--; - } else { - result = get(result, current); } } - // 如果每一个维度都是被指定好的,那么最终获取的数据就是单个的 - if (!isArray(result)) { - result = [result]; - } - - const extraFields = this.getQueryExtraFields(query)!; + const extraFields = this.getQueryExtraFields(query); - return flatMap(compact(result as RawData[]), (item) => - CellData.getCellDataList(item, extraFields), - ); + // 多个 extra field 有时对应的同一个对象,需要进行去重 + return flatMap(uniq(all), (item) => { + return item ? CellData.getCellDataList(item, extraFields) : []; + }); } public getFieldFormatter(field: string, cellMeta?: ViewMeta): Formatter { @@ -644,7 +643,7 @@ export class PivotDataSet extends BaseDataSet { return isNumber(customValueOrder); } - public getRowData(cellMeta: CellMeta): RowData { + public getRowData(cellMeta: CellMeta) { return this.getCellMultiData({ query: cellMeta.rowQuery! }); } } diff --git a/packages/s2-core/src/data-set/table-data-set.ts b/packages/s2-core/src/data-set/table-data-set.ts index 7c3bcd4569..72e7d233e4 100644 --- a/packages/s2-core/src/data-set/table-data-set.ts +++ b/packages/s2-core/src/data-set/table-data-set.ts @@ -1,10 +1,14 @@ -import { each, orderBy, filter, includes, isFunction, isObject } from 'lodash'; -import { isAscSort, isDescSort } from '..'; -import type { S2DataConfig, RawData, Data } from '../common/interface'; +import { each, filter, hasIn, isFunction, isObject, orderBy } from 'lodash'; import type { CellMeta } from '../common'; -import type { RowData } from '../common/interface/basic'; -import type { GetCellMultiDataParams, Query } from './interface'; +import type { + Data, + RawData, + S2DataConfig, + SimpleData, +} from '../common/interface'; +import { isAscSort, isDescSort } from '../utils/sort-action'; import { BaseDataSet } from './base-data-set'; +import type { GetCellMultiDataParams } from './interface'; export class TableDataSet extends BaseDataSet { public processDataCfg(dataCfg: S2DataConfig): S2DataConfig { @@ -21,15 +25,13 @@ export class TableDataSet extends BaseDataSet { * 返回顶部冻结行 * @returns */ - protected getStartRows() { - const { rowCount } = this.spreadsheet.options.frozen!; + protected getStartFrozenRows(displayData: RawData[]): RawData[] { + const { rowCount } = this.spreadsheet.options.frozen! || {}; if (!rowCount) { return []; } - const { displayData } = this; - return displayData.slice(0, rowCount); } @@ -37,48 +39,44 @@ export class TableDataSet extends BaseDataSet { * 返回底部冻结行 * @returns */ - protected getEndRows() { - const { trailingRowCount } = this.spreadsheet.options.frozen!; + protected getEndFrozenRows(displayData: RawData[]): RawData[] { + const { trailingRowCount } = this.spreadsheet.options.frozen! || {}; // 没有冻结行时返回空数组 if (!trailingRowCount) { return []; } - const { displayData } = this; - return displayData.slice(-trailingRowCount); } - /** - * 返回可移动的非冻结行 - * @returns - */ - protected getMovableRows(): RawData[] { - const { trailingRowCount, rowCount } = this.spreadsheet.options.frozen!; + protected getDisplayData(displayData: RawData[]): RawData[] { + const startFrozenRows = this.getStartFrozenRows(displayData); + const endFrozenRows = this.getEndFrozenRows(displayData); - return this.displayData.slice( - rowCount || 0, - -trailingRowCount! || undefined, + const data = displayData.slice( + startFrozenRows.length || 0, + -endFrozenRows.length || undefined, ); + + return [...startFrozenRows, ...data, ...endFrozenRows]; } handleDimensionValueFilter = () => { each(this.filterParams, ({ filterKey, filteredValues, customFilter }) => { - const defaultFilterFunc = (row: Query) => - !includes(filteredValues, row[filterKey]); - - this.displayData = [ - ...this.getStartRows(), - ...filter(this.getMovableRows(), (row) => { - if (customFilter) { - return customFilter(row) && defaultFilterFunc(row); - } + const filteredValuesSet = new Set(filteredValues); + const defaultFilterFunc = (row: RawData) => + !filteredValuesSet.has(row[filterKey]); - return defaultFilterFunc(row); - }), - ...this.getEndRows(), - ]; + const filteredData = filter(this.displayData, (row) => { + if (customFilter) { + return customFilter(row) && defaultFilterFunc(row); + } + + return defaultFilterFunc(row); + }); + + this.displayData = this.getDisplayData(filteredData); }); }; @@ -92,7 +90,7 @@ export class TableDataSet extends BaseDataSet { return; } - let data = this.getMovableRows(); + let data = this.displayData; const restData: RawData[] = []; @@ -151,11 +149,7 @@ export class TableDataSet extends BaseDataSet { } // For frozen options - this.displayData = [ - ...this.getStartRows(), - ...sortedData, - ...this.getEndRows(), - ]; + this.displayData = this.getDisplayData(sortedData); }); }; @@ -163,25 +157,44 @@ export class TableDataSet extends BaseDataSet { return []; } - public getCellData({ query }: GetCellMultiDataParams): Data { + public getCellData({ query = {} }: GetCellMultiDataParams = {}): + | Data + | SimpleData + | undefined { if (this.displayData.length === 0 && query['rowIndex'] === 0) { - return; + return undefined; } const rowData = this.displayData[query['rowIndex']]; - if (!('col' in query) || !isObject(rowData)) { + if (!hasIn(query, 'field') || !isObject(rowData)) { return rowData as Data; } - return rowData[query['col']] as unknown as Data; + return rowData[query['field']] as SimpleData; } - public getCellMultiData(): Data[] { - return this.displayData as Data[]; + public getCellMultiData({ query = {} }: GetCellMultiDataParams = {}): Data[] { + if (!query) { + return this.displayData as Data[]; + } + + const rowData = this.displayData[query['rowIndex']] + ? [this.displayData[query['rowIndex']]] + : this.displayData; + + if (!hasIn(query, 'field')) { + return rowData as Data[]; + } + + return rowData.map((item) => item[query['field']]) as Data[]; } - public getRowData(cellMeta: CellMeta): RowData { - return this.getCellData({ query: { rowIndex: cellMeta.rowIndex } }); + public getRowData(cell: CellMeta) { + return this.getCellData({ + query: { + rowIndex: cell.rowIndex, + }, + }) as Data; } } diff --git a/packages/s2-core/src/facet/README-adjustTotalNodesCoordinate.md b/packages/s2-core/src/facet/README-adjustTotalNodesCoordinate.md new file mode 100644 index 0000000000..3998b5fcf6 --- /dev/null +++ b/packages/s2-core/src/facet/README-adjustTotalNodesCoordinate.md @@ -0,0 +1,57 @@ +## 小计总计节点布局逻辑 + +1. 计算一个 multipleMap ,表示每个 level 的汇总节点实际占据一个单元格 +2. 遍历所有汇总单元格,根据 multipleMap 中的值,计算宽或高为合并多个单元格 + +🌰 例子: + +rows: [ 省份, 城市, 类别, 子类别 ] + +Total - row - totalDimensionGroup: [ 城市, 类别 ] + +Total - row - subTotalDimensionGroup: [ 子类别 ] + +<img src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*6kU_SqAmKMkAAAAAAAAAAAAADmJ7AQ/original" width="600" alt="row" /> + +### multipleMap 如何计算 - getMultipleMap 函数 + +行总计 multipleMap: + +1. 初始化一个长度与 rows 相同的数组 [ 1, 1, 1 ,1 ] +2. 从后往前判断 totalDimensionGroup.includes(field) + - 第一个处理子类别,不做处理 + - 第二个处理 类别,判断为 false,将类别数值向前移动[ 1, 2, 0, 1] + - 第三个判断 城市,判断为 false,将城市数值向前移动[ 3, 0, 0, 1] + - 第四个 省份,首个维度不做遍历 +3. multipleMap 结果为 [ 3, 0, 0, 1] + +行小计 multipleMap: + +1. 初始化一个长度与 rows 相同的数组 [ 1, 1, 1 ,1 ] +2. 从后往前判断 subTotalDimensionGroup.includes(field) + - 第一个处理子类别,判断为 true,不做处理 + - 第二个处理 类别,判断为 false,将类别数值向前移动[ 1, 1, 2, 0] + - 第三个判断 城市,判断为 flase,不做处理 + - 第四个 省份,首个维度不做遍历 +3. multipleMap 结果为 [ 1, 1, 2, 0] + +### multipleMap 如何使用 - adjustTotalNodesCoordinate 函数 + +总计的 multipleMap = [ 1 , 1 ,2 ,0 ] + +multipleMap 表示: + +- row 的第一第二个维度占据一个单元格 +- 第三个维度合并两个单元格 +- 第四个维度没有节点 + +小计的 multipleMap = [ 3,0,0,1 ] + +multipleMap 表示: + +- 小计结点,row的第一个维度合并三个单元格 +- 小计结点的最后一个维度占据一个单元格 + +当小计节点出现在第二个维度,则合并量为‘最近的上一个不为0的值’ - level 差 + +即实际该小计节点实际的 multipleMap 为 [1,2,0,1] diff --git a/packages/s2-core/src/facet/base-facet.ts b/packages/s2-core/src/facet/base-facet.ts index 1cd3eeec29..8375b25398 100644 --- a/packages/s2-core/src/facet/base-facet.ts +++ b/packages/s2-core/src/facet/base-facet.ts @@ -47,6 +47,7 @@ import { KEY_GROUP_PANEL_SCROLL, KEY_GROUP_ROW_INDEX_RESIZE_AREA, KEY_GROUP_ROW_RESIZE_AREA, + OriginEventType, PANEL_GROUP_GROUP_CONTAINER_Z_INDEX, PANEL_GROUP_SCROLL_GROUP_Z_INDEX, S2Event, @@ -54,9 +55,9 @@ import { } from '../common/constant'; import { DEFAULT_PAGE_INDEX } from '../common/constant/pagination'; import { - DebuggerUtil, DEBUG_HEADER_LAYOUT, DEBUG_VIEW_RENDER, + DebuggerUtil, } from '../common/debug'; import type { AdjustLeafNodesParams, @@ -83,22 +84,24 @@ import { getAdjustedRowScrollX, getAdjustedScrollOffset } from '../utils/facet'; import { getAllChildCells } from '../utils/get-all-child-cells'; import { getColsForGrid, getRowsForGrid } from '../utils/grid'; import { diffPanelIndexes, type PanelIndexes } from '../utils/indexes'; -import { isMobile } from '../utils/is-mobile'; -import { CornerBBox } from './bbox/cornerBBox'; -import { PanelBBox } from './bbox/panelBBox'; +import { isMobile, isWindows } from '../utils/is-mobile'; +import { floor } from '../utils/math'; +import { CornerBBox } from './bbox/corner-bbox'; +import { PanelBBox } from './bbox/panel-bbox'; import { ColHeader, CornerHeader, Frame, RowHeader, SeriesNumberHeader, + type RowHeaderConfig, } from './header'; import type { Hierarchy } from './layout/hierarchy'; import type { ViewCellHeights } from './layout/interface'; import { Node } from './layout/node'; import { WheelEvent as MobileWheel } from './mobile/wheelEvent'; import { - calculateInViewIndexes, + areAllFieldsEmpty, getCellRange, optimizeScrollXY, translateGroup, @@ -141,7 +144,7 @@ export abstract class BaseFacet { * 当前布局节点信息 * @description 内部消费, 外部调用请使用 facet.getLayoutResult() */ - private layoutResult: LayoutResult; + protected layoutResult: LayoutResult; public viewCellWidths: number[]; @@ -171,8 +174,17 @@ export abstract class BaseFacet { protected abstract doLayout(): LayoutResult; + protected abstract clip(scrollX: number, scrollY: number): void; + + public abstract calculateXYIndexes( + scrollX: number, + scrollY: number, + ): PanelIndexes; + public abstract getViewCellHeights(): ViewCellHeights; + public abstract addDataCell(cell: DataCell): void; + public abstract getCellMeta( rowIndex: number, colIndex: number, @@ -212,7 +224,6 @@ export abstract class BaseFacet { protected initGroups() { const container = this.spreadsheet.container; - // the main three layer groups this.backgroundGroup = container.appendChild( new Group({ name: KEY_GROUP_BACK_GROUND, @@ -221,6 +232,7 @@ export abstract class BaseFacet { ); this.initPanelGroups(); + this.foregroundGroup = container.appendChild( new Group({ name: KEY_GROUP_FORE_GROUND, @@ -356,11 +368,25 @@ export abstract class BaseFacet { colsHierarchy.sampleNodesForAllLevels = compact( sampleMaxHeightNodesForAllLevels, ); + colsHierarchy.sampleNodesForAllLevels.forEach((levelSampleNode) => { levelSampleNode.height = this.getColNodeHeight( levelSampleNode, colsHierarchy, ); + if (levelSampleNode.level === 0) { + levelSampleNode.y = 0; + } else { + const preLevelSample = colsHierarchy.sampleNodesForAllLevels[ + levelSampleNode.level - 1 + ] ?? { + y: 0, + height: 0, + }; + + levelSampleNode.y = preLevelSample.y + preLevelSample.height; + } + colsHierarchy.height += levelSampleNode.height; }); } @@ -410,6 +436,7 @@ export abstract class BaseFacet { const { deltaX, deltaY, x, y } = ev; // The coordinates of mobile and pc are three times different + // TODO: 手指快速往上滚动时, deltaY 有时会为负数, 导致向下滚动时然后回弹, 看起来就像表格在抖动, 需要判断滚动方向, next 版本未复现 this.onWheel({ ...originEvent, deltaX, @@ -429,6 +456,10 @@ export abstract class BaseFacet { * Start render, call from outside */ public render() { + if (areAllFieldsEmpty(this.spreadsheet.dataCfg.fields)) { + return; + } + this.adjustScrollOffset(); this.renderHeaders(); this.renderScrollBars(); @@ -528,7 +559,7 @@ export abstract class BaseFacet { const offset = get(scrollOffset, key); if (!isUndefined(offset)) { - this.spreadsheet.store.set(key, Math.floor(offset)); + this.spreadsheet.store.set(key, floor(offset)); } }); }; @@ -566,7 +597,7 @@ export abstract class BaseFacet { const { current = DEFAULT_PAGE_INDEX, pageSize } = pagination; const total = this.viewCellHeights.getTotalLength(); - const pageCount = Math.floor((total - 1) / pageSize) + 1; + const pageCount = floor((total - 1) / pageSize) + 1; this.spreadsheet.emit(S2Event.LAYOUT_PAGINATION, { pageSize, @@ -602,35 +633,6 @@ export abstract class BaseFacet { this.viewCellHeights = this.getViewCellHeights(); }; - /** - * The purpose of this rewrite is to take into account that when rowHeader supports scrollbars - *the panel viewable area must vary with the horizontal distance of the scroll - * @param scrollX - * @param scrollY - * @public - */ - public calculateXYIndexes(scrollX: number, scrollY: number): PanelIndexes { - const { viewportHeight: height, viewportWidth: width } = this.panelBBox; - - const indexes = calculateInViewIndexes({ - scrollX, - scrollY, - widths: this.viewCellWidths, - heights: this.viewCellHeights, - viewport: { - width, - height, - x: 0, - y: 0, - }, - rowRemainWidth: this.getRealScrollX(this.cornerBBox.width), - }); - - return { - center: indexes, - }; - } - getRealScrollX = (scrollX: number, hRowScroll = 0) => this.spreadsheet.isFrozenRowHeader() ? hRowScroll : scrollX; @@ -710,19 +712,25 @@ export abstract class BaseFacet { ); this.timer = timer((elapsed) => { - const ratio = Math.min(elapsed / duration, 1); - const [scrollX, scrollY, rowHeaderScrollX] = interpolate(ratio); - - this.setScrollOffset({ - rowHeaderScrollX, - scrollX, - scrollY, - }); - this.startScroll(); + try { + const ratio = Math.min(elapsed / duration, 1); + const [scrollX, scrollY, rowHeaderScrollX] = interpolate(ratio); + + this.setScrollOffset({ + rowHeaderScrollX, + scrollX, + scrollY, + }); + this.startScroll(); - if (elapsed > duration) { + if (elapsed > duration) { + this.timer.stop(); + cb?.(); + } + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); this.timer.stop(); - cb?.(); } }); }; @@ -829,7 +837,7 @@ export abstract class BaseFacet { ScrollType.ScrollChange, ({ offset }: ScrollChangeParams) => { const newOffset = this.getValidScrollBarOffset(offset, maxOffset); - const rowHeaderScrollX = Math.floor(newOffset); + const rowHeaderScrollX = floor(newOffset); this.setScrollOffset({ rowHeaderScrollX }); @@ -871,7 +879,7 @@ export abstract class BaseFacet { clamp(offset, 0, maxOffset); renderHScrollBar = (width: number, realWidth: number, scrollX: number) => { - if (Math.floor(width) < Math.floor(realWidth)) { + if (floor(width) < floor(realWidth)) { const halfScrollSize = this.scrollBarSize / 2; const { maxY } = this.getScrollbarPosition(); const isScrollContainsRowHeader = !this.spreadsheet.isFrozenRowHeader(); @@ -1158,9 +1166,16 @@ export abstract class BaseFacet { }; private stopScrollChaining = (event: WheelEvent) => { - event?.preventDefault?.(); + if (event?.cancelable) { + event?.preventDefault?.(); + } + // 移动端的 prevent 存在于 originalEvent上 - (event as unknown as GraphEvent)?.originalEvent?.preventDefault?.(); + const mobileEvent = (event as unknown as GraphEvent)?.originalEvent; + + if (mobileEvent?.cancelable) { + mobileEvent?.preventDefault?.(); + } }; onWheel = (event: WheelEvent) => { @@ -1168,8 +1183,9 @@ export abstract class BaseFacet { let { deltaX, deltaY, offsetX, offsetY } = event; const { shiftKey } = event; - // 按住shift时,固定为水平方向滚动 - if (shiftKey) { + // Windows 环境,按住 shift 时,固定为水平方向滚动,macOS 环境默认有该行为 + // see https://github.com/antvis/S2/issues/2198 + if (shiftKey && isWindows()) { offsetX = offsetX - deltaX + deltaY; deltaX = deltaY; offsetY -= deltaY; @@ -1265,26 +1281,21 @@ export abstract class BaseFacet { scrollY, KEY_GROUP_ROW_INDEX_RESIZE_AREA, ); - this.cornerHeader.onCorScroll( + this.cornerHeader?.onCorScroll( this.getRealScrollX(scrollX, hRowScroll), KEY_GROUP_CORNER_RESIZE_AREA, ); - this.centerFrame.onChangeShadowVisibility( + this.centerFrame?.onChangeShadowVisibility( scrollX, this.getRealWidth() - this.panelBBox.width, ); - this.centerFrame.onBorderScroll(this.getRealScrollX(scrollX)); - this.columnHeader.onColScroll(scrollX, KEY_GROUP_COL_RESIZE_AREA); + this.centerFrame?.onBorderScroll(this.getCenterFrameScrollX(scrollX)); + this.columnHeader?.onColScroll(scrollX, KEY_GROUP_COL_RESIZE_AREA); } - addDataCell = (cell: DataCell) => { - this.panelScrollGroup?.appendChild(cell); - - setTimeout(() => { - this.spreadsheet.emit(S2Event.DATA_CELL_RENDER, cell); - this.spreadsheet.emit(S2Event.LAYOUT_CELL_RENDER, cell); - }, 100); - }; + protected getCenterFrameScrollX(scrollX: number) { + return this.getRealScrollX(scrollX); + } realDataCellRender = (scrollX: number, scrollY: number) => { const indexes = this.calculateXYIndexes(scrollX, scrollY); @@ -1305,6 +1316,11 @@ export abstract class BaseFacet { if (viewMeta) { const cell = this.spreadsheet.options.dataCell?.(viewMeta)!; + if (!cell) { + return; + } + + cell.position = [j, i]; // mark cell for removing cell.name = `${i}-${j}`; this.addDataCell(cell); @@ -1431,20 +1447,24 @@ export abstract class BaseFacet { this.foregroundGroup.appendChild(this.centerFrame); } + protected getRowHeaderCfg(): RowHeaderConfig { + const { y, viewportHeight, viewportWidth, height } = this.panelBBox; + const seriesNumberWidth = this.getSeriesNumberWidth(); + + return { + width: this.cornerBBox.width, + height, + viewportWidth, + viewportHeight, + position: { x: seriesNumberWidth, y }, + nodes: this.layoutResult.rowNodes, + spreadsheet: this.spreadsheet, + }; + } + protected getRowHeader(): RowHeader | null { if (!this.rowHeader) { - const { y, viewportHeight, viewportWidth, height } = this.panelBBox; - const seriesNumberWidth = this.getSeriesNumberWidth(); - - return new RowHeader({ - width: this.cornerBBox.width, - height, - viewportWidth, - viewportHeight, - position: { x: seriesNumberWidth, y }, - nodes: this.layoutResult.rowNodes, - spreadsheet: this.spreadsheet, - }); + return new RowHeader(this.getRowHeaderCfg()); } return this.rowHeader; @@ -1524,8 +1544,12 @@ export abstract class BaseFacet { protected getGridInfo = () => { const [colMin, colMax, rowMin, rowMax] = this.preCellIndexes!.center; - const cols = getColsForGrid(colMin, colMax, this.layoutResult.colLeafNodes); - const rows = getRowsForGrid(rowMin, rowMax, this.viewCellHeights); + const cols = getColsForGrid( + colMin!, + colMax!, + this.layoutResult.colLeafNodes, + ); + const rows = getRowsForGrid(rowMin!, rowMax!, this.viewCellHeights); return { cols, @@ -1550,12 +1574,7 @@ export abstract class BaseFacet { scrollY: originalScrollY, rowHeaderScrollX, } = this.getScrollOffset(); - const defaultScrollY = originalScrollY + this.getPaginationScrollY(); - const scrollY = getAdjustedScrollOffset( - defaultScrollY, - this.viewCellHeights.getTotalHeight(), - this.panelBBox.viewportHeight, - ); + const scrollY = originalScrollY + this.getPaginationScrollY(); this.spreadsheet.hideTooltip(); this.spreadsheet.interaction.clearHoverTimer(); @@ -1563,6 +1582,9 @@ export abstract class BaseFacet { this.realDataCellRender(scrollX, scrollY); this.updatePanelScrollGroup(); this.translateRelatedGroups(scrollX, scrollY, rowHeaderScrollX); + + this.clip(scrollX, scrollY); + if (!skipScrollEvent) { this.emitScrollEvent({ scrollX, scrollY, rowHeaderScrollX }); } @@ -1575,11 +1597,31 @@ export abstract class BaseFacet { } protected onAfterScroll = debounce(() => { - const { interaction } = this.spreadsheet; + const { interaction, container } = this.spreadsheet; // 如果是选中单元格状态, 则继续保留 hover 拦截, 避免滚动后 hover 清空已选单元格 if (!interaction.isSelectedState()) { - this.spreadsheet.interaction.removeIntercepts([InterceptType.HOVER]); + interaction.removeIntercepts([InterceptType.HOVER]); + + if (interaction.getHoverAfterScroll()) { + // https://github.com/antvis/S2/issues/2222 + const canvasMousemoveEvent = + interaction.eventController.canvasMousemoveEvent; + + if (canvasMousemoveEvent) { + const { x, y } = canvasMousemoveEvent; + const shape = container.document.elementFromPointSync(x, y); + + if (shape) { + container.emit(OriginEventType.POINTER_MOVE, { + ...canvasMousemoveEvent, + shape, + target: shape, + timestamp: performance.now(), + }); + } + } + } } }, 300); @@ -1607,8 +1649,9 @@ export abstract class BaseFacet { return; } - return hiddenColumnsDetail.find((detail) => - detail.hideColumnNodes.some((node) => node.id === columnNode.id), + return hiddenColumnsDetail.find( + (detail) => + detail?.hideColumnNodes?.some((node) => node.id === columnNode.id), ); } @@ -1732,6 +1775,10 @@ export abstract class BaseFacet { return colNodes.filter((node) => node.level === level); } + public getTopLevelColNodes() { + return this.getColNodes(0); + } + /** * 根据 id 获取指定列头节点 * @example facet.getColNodeById('root[&]节点1[&]数值') diff --git a/packages/s2-core/src/facet/bbox/baseBBox.ts b/packages/s2-core/src/facet/bbox/base-bbox.ts similarity index 100% rename from packages/s2-core/src/facet/bbox/baseBBox.ts rename to packages/s2-core/src/facet/bbox/base-bbox.ts diff --git a/packages/s2-core/src/facet/bbox/cornerBBox.ts b/packages/s2-core/src/facet/bbox/corner-bbox.ts similarity index 93% rename from packages/s2-core/src/facet/bbox/cornerBBox.ts rename to packages/s2-core/src/facet/bbox/corner-bbox.ts index e7ca126e02..6aebf3f4b8 100644 --- a/packages/s2-core/src/facet/bbox/cornerBBox.ts +++ b/packages/s2-core/src/facet/bbox/corner-bbox.ts @@ -1,6 +1,7 @@ import { clamp, isBoolean } from 'lodash'; import { DEFAULT_CORNER_MAX_WIDTH_RATIO } from '../../common/constant'; -import { BaseBBox } from './baseBBox'; +import { floor } from '../../utils/math'; +import { BaseBBox } from './base-bbox'; export class CornerBBox extends BaseBBox { calculateBBox() { @@ -26,7 +27,7 @@ export class CornerBBox extends BaseBBox { return colCell?.height; } - return Math.floor(colsHierarchy.height); + return floor(colsHierarchy.height); } private getCornerBBoxHeight() { @@ -38,7 +39,7 @@ export class CornerBBox extends BaseBBox { private getCornerBBoxWidth() { const { rowsHierarchy } = this.layoutResult; - this.originalWidth = Math.floor( + this.originalWidth = floor( rowsHierarchy.width + this.facet.getSeriesNumberWidth(), ); @@ -87,6 +88,6 @@ export class CornerBBox extends BaseBBox { clippedWidth = maxCornerBBoxWidth; } - return Math.floor(clippedWidth); + return floor(clippedWidth); } } diff --git a/packages/s2-core/src/facet/bbox/panelBBox.ts b/packages/s2-core/src/facet/bbox/panel-bbox.ts similarity index 85% rename from packages/s2-core/src/facet/bbox/panelBBox.ts rename to packages/s2-core/src/facet/bbox/panel-bbox.ts index aa27d03aad..3cea077be9 100644 --- a/packages/s2-core/src/facet/bbox/panelBBox.ts +++ b/packages/s2-core/src/facet/bbox/panel-bbox.ts @@ -1,5 +1,6 @@ +import { floor } from '../../utils/math'; import { Frame } from '../header/frame'; -import { BaseBBox } from './baseBBox'; +import { BaseBBox } from './base-bbox'; export class PanelBBox extends BaseBBox { calculateBBox() { @@ -8,8 +9,8 @@ export class PanelBBox extends BaseBBox { const { cornerBBox } = this.facet; const cornerPosition = { - x: Math.floor(cornerBBox.maxX), - y: Math.floor(cornerBBox.maxY), + x: floor(cornerBBox.maxX), + y: floor(cornerBBox.maxY), }; // splitLine 也应该占位,panelBBox = canvasBBox - cornerBBox - splitLineBBox @@ -30,10 +31,10 @@ export class PanelBBox extends BaseBBox { this.width = panelWidth; this.height = panelHeight; this.viewportHeight = Math.abs( - Math.floor(Math.min(panelHeight, this.originalHeight)), + floor(Math.min(panelHeight, this.originalHeight)), ); this.viewportWidth = Math.abs( - Math.floor(Math.min(panelWidth, this.originalWidth)), + floor(Math.min(panelWidth, this.originalWidth)), ); this.maxX = this.x + this.viewportWidth; this.maxY = this.y + this.viewportHeight; diff --git a/packages/s2-core/src/facet/frozen-facet.ts b/packages/s2-core/src/facet/frozen-facet.ts new file mode 100644 index 0000000000..5045e4b778 --- /dev/null +++ b/packages/s2-core/src/facet/frozen-facet.ts @@ -0,0 +1,775 @@ +import { Group, Rect, type LineStyleProps } from '@antv/g'; +import { last } from 'lodash'; +import type { DataCell } from '../cell'; +import { + FRONT_GROUND_GROUP_FROZEN_Z_INDEX, + FrozenCellGroupMap, + FrozenGroupType, + KEY_GROUP_FROZEN_SPLIT_LINE, + KEY_GROUP_PANEL_FROZEN_BOTTOM, + KEY_GROUP_PANEL_FROZEN_COL, + KEY_GROUP_PANEL_FROZEN_ROW, + KEY_GROUP_PANEL_FROZEN_TOP, + KEY_GROUP_PANEL_FROZEN_TRAILING_COL, + KEY_GROUP_PANEL_FROZEN_TRAILING_ROW, + PANEL_GROUP_FROZEN_GROUP_Z_INDEX, + S2Event, + SPLIT_LINE_WIDTH, +} from '../common/constant'; +import type { SimpleBBox } from '../engine'; +import { FrozenGroup } from '../group/frozen-group'; +import { getValidFrozenOptions, renderLine } from '../utils'; +import { + getColsForGrid, + getFrozenRowsForGrid, + getRowsForGrid, +} from '../utils/grid'; +import type { Indexes, PanelIndexes } from '../utils/indexes'; +import { floor } from '../utils/math'; +import { BaseFacet } from './base-facet'; +import { Frame } from './header/frame'; +import { Node } from './layout/node'; +import { + calculateFrozenCornerCells, + calculateInViewIndexes, + getFrozenDataCellType, + getFrozenLeafNodesCount, + splitInViewIndexesWithFrozen, + translateGroup, +} from './utils'; + +/** + * Defines the row freeze abstract standard interface + */ +export abstract class FrozenFacet extends BaseFacet { + public rowOffsets: number[]; + + public frozenGroupInfo = { + [FrozenGroupType.FROZEN_COL]: { + width: 0, + x: 0, + range: [] as number[], + }, + [FrozenGroupType.FROZEN_TRAILING_COL]: { + width: 0, + x: 0, + range: [] as number[], + }, + [FrozenGroupType.FROZEN_ROW]: { + height: 0, + y: 0, + range: [] as number[], + }, + [FrozenGroupType.FROZEN_TRAILING_ROW]: { + height: 0, + y: 0, + range: [] as number[], + }, + } satisfies Record< + FrozenGroupType, + { + width?: number; + height?: number; + x?: number; + y?: number; + range: number[]; + } + >; + + public panelScrollGroupIndexes: Indexes = [0, 0, 0, 0]; + + protected override initPanelGroups(): void { + super.initPanelGroups(); + [ + this.frozenRowGroup, + this.frozenColGroup, + this.frozenTrailingRowGroup, + this.frozenTrailingColGroup, + this.frozenTopGroup, + this.frozenBottomGroup, + ] = [ + KEY_GROUP_PANEL_FROZEN_ROW, + KEY_GROUP_PANEL_FROZEN_COL, + KEY_GROUP_PANEL_FROZEN_TRAILING_ROW, + KEY_GROUP_PANEL_FROZEN_TRAILING_COL, + KEY_GROUP_PANEL_FROZEN_TOP, + KEY_GROUP_PANEL_FROZEN_BOTTOM, + ].map((name) => { + const frozenGroup = new FrozenGroup({ + name, + zIndex: PANEL_GROUP_FROZEN_GROUP_Z_INDEX, + s2: this.spreadsheet, + }); + + this.panelGroup.appendChild(frozenGroup); + + return frozenGroup; + }); + } + + protected getFrozenOptions() { + const colLength = this.getColLeafNodes().length; + const cellRange = this.getCellRange(); + + return getValidFrozenOptions( + this.spreadsheet.options.frozen!, + colLength, + cellRange.end - cellRange.start + 1, + ); + } + + public calculateFrozenGroupInfo() { + const { + colCount = 0, + rowCount = 0, + trailingColCount = 0, + trailingRowCount = 0, + } = this.getFrozenOptions(); + + const topLevelColNodes = this.getTopLevelColNodes(); + const viewCellHeights = this.viewCellHeights; + const cellRange = this.getCellRange(); + const { frozenCol, frozenTrailingCol, frozenRow, frozenTrailingRow } = + this.frozenGroupInfo; + + if (colCount > 0) { + frozenCol.width = + topLevelColNodes[colCount - 1].x + topLevelColNodes[colCount - 1].width; + frozenCol.x = 0; + frozenCol.range = [0, colCount - 1]; + } + + if (rowCount > 0) { + frozenRow.height = + viewCellHeights.getCellOffsetY(cellRange.start + rowCount) - + viewCellHeights.getCellOffsetY(cellRange.start); + frozenRow.y = 0; + frozenRow.range = [cellRange.start, cellRange.start + rowCount - 1]; + } + + if (trailingColCount > 0) { + frozenTrailingCol.width = + topLevelColNodes[topLevelColNodes.length - 1].x - + topLevelColNodes[topLevelColNodes.length - trailingColCount].x + + topLevelColNodes[topLevelColNodes.length - 1].width; + frozenTrailingCol.x = this.panelBBox.width - frozenTrailingCol.width; + frozenTrailingCol.range = [ + topLevelColNodes.length - trailingColCount, + topLevelColNodes.length - 1, + ]; + } + + if (trailingRowCount > 0) { + frozenTrailingRow.height = + viewCellHeights.getCellOffsetY(cellRange.end + 1) - + viewCellHeights.getCellOffsetY(cellRange.end + 1 - trailingRowCount); + frozenTrailingRow.y = this.panelBBox.height - frozenTrailingRow.height; + frozenTrailingRow.range = [ + cellRange.end - trailingRowCount + 1, + cellRange.end, + ]; + } + } + + protected getFinalViewport() { + const { viewportHeight: height, viewportWidth: width } = this.panelBBox; + + const { + colCount = 0, + rowCount = 0, + trailingColCount = 0, + trailingRowCount = 0, + } = this.getFrozenOptions(); + + const finalViewport: SimpleBBox = { + width, + height, + x: 0, + y: 0, + }; + + if (colCount > 0 || trailingColCount > 0) { + const { frozenTrailingCol, frozenCol } = this.frozenGroupInfo; + + finalViewport.width -= frozenTrailingCol.width! + frozenCol.width!; + finalViewport.x += frozenCol.width!; + } + + if (rowCount > 0 || trailingRowCount > 0) { + const { frozenRow, frozenTrailingRow } = this.frozenGroupInfo; + + // canvas 高度小于 row height 和 trailingRow height 的时候 height 为 0 + if ( + finalViewport.height < + frozenRow.height! + frozenTrailingRow.height! + ) { + finalViewport.height = 0; + finalViewport.y = 0; + } else { + finalViewport.height -= frozenRow.height! + frozenTrailingRow.height!; + finalViewport.y += frozenRow.height!; + } + } + + return finalViewport; + } + + public calculateXYIndexes(scrollX: number, scrollY: number): PanelIndexes { + const colLength = this.getColLeafNodes().length; + const cellRange = this.getCellRange(); + + const { + colCount = 0, + rowCount = 0, + trailingColCount = 0, + trailingRowCount = 0, + } = this.getFrozenOptions(); + + const finalViewport: SimpleBBox = this.getFinalViewport(); + + const indexes = + this.spreadsheet.isTableMode() && this.spreadsheet.dataSet?.isEmpty?.() + ? this.spreadsheet.dataSet.getEmptyViewIndexes() + : calculateInViewIndexes({ + scrollX, + scrollY, + widths: this.viewCellWidths, + heights: this.viewCellHeights, + viewport: finalViewport, + rowRemainWidth: this.getRealScrollX(this.cornerBBox.width), + }); + + this.panelScrollGroupIndexes = indexes; + + const { colCount: realColCount, trailingColCount: realTrailingColCount } = + this.getRealFrozenColumns(colCount, trailingColCount); + + return splitInViewIndexesWithFrozen( + indexes, + { + colCount: realColCount, + trailingColCount: realTrailingColCount, + rowCount, + trailingRowCount, + }, + colLength, + cellRange, + ); + } + + addDataCell = (cell: DataCell) => { + const { + rowCount = 0, + colCount = 0, + trailingRowCount = 0, + trailingColCount = 0, + } = this.getFrozenOptions(); + + const colLength = this.getColNodes().length; + const cellRange = this.getCellRange(); + const { colCount: realColCount, trailingColCount: realTrailingColCount } = + this.getRealFrozenColumns(colCount, trailingColCount); + + const frozenCellType = getFrozenDataCellType( + cell.getMeta(), + { + rowCount, + trailingRowCount, + colCount: realColCount, + trailingColCount: realTrailingColCount, + }, + colLength, + cellRange, + ); + + const groupName = FrozenCellGroupMap[frozenCellType]; + + if (groupName) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const group = this[groupName] as Group; + + group.appendChild(cell); + } + + setTimeout(() => { + this.spreadsheet.emit(S2Event.DATA_CELL_RENDER, cell); + this.spreadsheet.emit(S2Event.LAYOUT_CELL_RENDER, cell); + }, 100); + }; + + addFrozenCell = (colIndex: number, rowIndex: number, group: Group) => { + const viewMeta = this.getCellMeta(rowIndex, colIndex); + + if (viewMeta) { + viewMeta.isFrozenCorner = true; + const cell = this.spreadsheet.options.dataCell?.(viewMeta)!; + + group.appendChild(cell); + } + }; + + protected updateFrozenGroupGrid(): void { + [ + FrozenGroupType.FROZEN_COL, + FrozenGroupType.FROZEN_ROW, + FrozenGroupType.FROZEN_TRAILING_COL, + FrozenGroupType.FROZEN_TRAILING_ROW, + ].forEach((key) => { + if (!this.frozenGroupInfo[key].range) { + return; + } + + let cols: number[] = []; + let rows: number[] = []; + + if (key.toLowerCase().includes('row')) { + const [rowMin, rowMax] = this.frozenGroupInfo[key].range || []; + + cols = this.gridInfo.cols; + rows = getRowsForGrid(rowMin, rowMax, this.viewCellHeights); + + if (key === FrozenGroupType.FROZEN_TRAILING_ROW) { + const top = + this.frozenGroupInfo[FrozenGroupType.FROZEN_TRAILING_ROW].y; + + rows = getFrozenRowsForGrid( + rowMin, + rowMax, + top, + this.viewCellHeights, + ); + } + } else { + const [colMin, colMax] = this.frozenGroupInfo[key].range || []; + const nodes = this.getTopLevelColNodes(); + + cols = getColsForGrid(colMin, colMax, nodes); + rows = this.gridInfo.rows; + } + + this[`${key}Group`].updateGrid( + { + cols, + rows, + }, + `${key}Group`, + ); + }); + } + + public updatePanelScrollGroup(): void { + super.updatePanelScrollGroup(); + this.updateFrozenGroupGrid(); + } + + protected translateRelatedGroups( + scrollX: number, + scrollY: number, + hRowScroll: number, + ) { + super.translateRelatedGroups(scrollX, scrollY, hRowScroll); + this.translateFrozenGroups(); + this.updateRowResizeArea(); + this.renderFrozenGroupSplitLine(scrollX, scrollY); + } + + protected translateFrozenGroups = () => { + const { scrollY, scrollX } = this.getScrollOffset(); + const paginationScrollY = this.getPaginationScrollY(); + + const { x, y } = this.panelBBox; + + translateGroup(this.frozenTopGroup, x, y - paginationScrollY); + translateGroup(this.frozenBottomGroup, x, y); + + translateGroup(this.frozenRowGroup, x - scrollX, y - paginationScrollY); + translateGroup(this.frozenTrailingRowGroup, x - scrollX, y); + + translateGroup(this.frozenColGroup, x, y - scrollY - paginationScrollY); + translateGroup( + this.frozenTrailingColGroup, + x, + y - scrollY - paginationScrollY, + ); + }; + + protected updateRowResizeArea() {} + + // eslint-disable-next-line max-lines-per-function + protected renderFrozenGroupSplitLine = (scrollX: number, scrollY: number) => { + const { + width: panelWidth, + height: panelHeight, + viewportWidth, + viewportHeight, + x: panelBBoxStartX, + y: panelBBoxStartY, + } = this.panelBBox; + + const topLevelColNodes = this.getTopLevelColNodes(); + const cellRange = this.getCellRange(); + const { + rowCount: frozenRowCount = 0, + colCount: frozenColCount = 0, + trailingColCount: frozenTrailingColCount = 0, + trailingRowCount: frozenTrailingRowCount = 0, + } = this.getFrozenOptions(); + + // 在分页条件下需要额外处理 Y 轴滚动值 + const relativeScrollY = Math.floor(scrollY - this.getPaginationScrollY()); + + // scroll boundary + const maxScrollX = Math.max(0, last(this.viewCellWidths)! - viewportWidth); + const maxScrollY = Math.max( + 0, + this.viewCellHeights.getCellOffsetY(cellRange.end + 1) - + this.viewCellHeights.getCellOffsetY(cellRange.start) - + viewportHeight, + ); + + // remove previous split line group + this.foregroundGroup.getElementById(KEY_GROUP_FROZEN_SPLIT_LINE)?.remove(); + + const { splitLine } = this.spreadsheet.theme; + const splitLineGroup = this.foregroundGroup.appendChild( + new Group({ + id: KEY_GROUP_FROZEN_SPLIT_LINE, + style: { + zIndex: FRONT_GROUND_GROUP_FROZEN_Z_INDEX, + }, + }), + ); + + const verticalBorderStyle: Partial<LineStyleProps> = { + lineWidth: SPLIT_LINE_WIDTH, + stroke: splitLine?.verticalBorderColor, + opacity: splitLine?.verticalBorderColorOpacity, + }; + + const horizontalBorderStyle: Partial<LineStyleProps> = { + lineWidth: SPLIT_LINE_WIDTH, + stroke: splitLine?.horizontalBorderColor, + opacity: splitLine?.horizontalBorderColorOpacity, + }; + + const frameVerticalBorderWidth = Frame.getVerticalBorderWidth( + this.spreadsheet, + ); + + if (frozenColCount > 0) { + const x = topLevelColNodes.reduce((prev, item, idx) => { + if (idx < frozenColCount) { + return prev + item.width; + } + + return prev; + }, 0); + + const height = + (frozenTrailingRowCount > 0 ? panelHeight : viewportHeight) + + panelBBoxStartY; + + renderLine(splitLineGroup, { + ...verticalBorderStyle, + x1: x + panelBBoxStartX, + x2: x + panelBBoxStartX, + y1: 0, + y2: height, + }); + + if (splitLine?.showShadow && scrollX > 0) { + splitLineGroup.appendChild( + new Rect({ + style: { + x: x + panelBBoxStartX, + y: 0, + width: splitLine?.shadowWidth!, + height, + fill: this.getShadowFill(0), + }, + }), + ); + } + } + + if (frozenRowCount > 0) { + const y = + panelBBoxStartY + + this.getTotalHeightForRange( + cellRange.start, + cellRange.start + frozenRowCount - 1, + ); + const width = frozenTrailingColCount > 0 ? panelWidth : viewportWidth; + + renderLine(splitLineGroup, { + ...horizontalBorderStyle, + x1: 0, + x2: width + frameVerticalBorderWidth, + y1: y, + y2: y, + }); + + if (splitLine?.showShadow && relativeScrollY > 0) { + splitLineGroup.appendChild( + new Rect({ + style: { + x: 0, + y, + width: width + frameVerticalBorderWidth, + height: splitLine?.shadowWidth!, + fill: this.getShadowFill(90), + }, + }), + ); + } + } + + if (frozenTrailingColCount > 0) { + const { x } = + topLevelColNodes[topLevelColNodes.length - frozenTrailingColCount]; + const height = + (frozenTrailingRowCount ? panelHeight : viewportHeight) + + panelBBoxStartY; + + renderLine(splitLineGroup, { + ...verticalBorderStyle, + x1: x + panelBBoxStartX, + x2: x + panelBBoxStartX, + y1: 0, + y2: height, + }); + + if (splitLine?.showShadow && floor(scrollX) < floor(maxScrollX)) { + splitLineGroup.appendChild( + new Rect({ + style: { + x: x + panelBBoxStartX - splitLine.shadowWidth!, + y: 0, + width: splitLine.shadowWidth!, + height, + fill: this.getShadowFill(180), + }, + }), + ); + } + } + + if (frozenTrailingRowCount > 0) { + const y = + this.panelBBox.maxY - + this.getTotalHeightForRange( + cellRange.end - frozenTrailingRowCount + 1, + cellRange.end, + ); + const width = frozenTrailingColCount > 0 ? panelWidth : viewportWidth; + + renderLine(splitLineGroup, { + ...horizontalBorderStyle, + x1: 0, + x2: width + frameVerticalBorderWidth, + y1: y, + y2: y, + }); + + if (splitLine?.showShadow && relativeScrollY < floor(maxScrollY)) { + splitLineGroup.appendChild( + new Rect({ + style: { + x: 0, + y: y - splitLine.shadowWidth!, + width: width + frameVerticalBorderWidth, + height: splitLine.shadowWidth!, + fill: this.getShadowFill(270), + }, + }), + ); + } + } + }; + + protected init(): void { + super.init(); + } + + public render(): void { + this.calculateFrozenGroupInfo(); + this.renderFrozenPanelCornerGroup(); + super.render(); + } + + protected override getCenterFrameScrollX(scrollX: number): number { + if (this.getFrozenOptions().colCount! > 0) { + return 0; + } + + return super.getCenterFrameScrollX(scrollX); + } + + protected renderFrozenPanelCornerGroup = () => { + const topLevelNodes = this.getTopLevelColNodes(); + const cellRange = this.getCellRange(); + + const { + rowCount: frozenRowCount = 0, + colCount: frozenColCount = 0, + trailingRowCount: frozenTrailingRowCount = 0, + trailingColCount: frozenTrailingColCount = 0, + } = this.getFrozenOptions(); + + const { colCount, trailingColCount } = getFrozenLeafNodesCount( + topLevelNodes, + frozenColCount, + frozenTrailingColCount, + ); + + const result = calculateFrozenCornerCells( + { + rowCount: frozenRowCount, + colCount, + trailingRowCount: frozenTrailingRowCount, + trailingColCount, + }, + this.getColLeafNodes().length, + cellRange, + ); + + Object.keys(result).forEach((key) => { + const cells = result[key]; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const group = this[FrozenCellGroupMap[key]] as Group; + + if (group) { + cells.forEach((cell) => { + this.addFrozenCell(cell.x, cell.y, group); + }); + } + }); + }; + + getRealFrozenColumns = ( + colCount: number, + trailingColCount: number, + ): { colCount: number; trailingColCount: number } => { + if (colCount || trailingColCount) { + const nodes = this.getTopLevelColNodes(); + + return getFrozenLeafNodesCount(nodes, colCount, trailingColCount); + } + + return { + colCount, + trailingColCount, + }; + }; + + public getCellHeightByRowIndex(rowIndex: number) { + if (this.rowOffsets) { + return this.getRowCellHeight({ id: String(rowIndex) } as Node); + } + + return this.getDefaultCellHeight(); + } + + public getTotalHeightForRange = (start: number, end: number) => { + if (start < 0 || end < 0) { + return 0; + } + + if (this.rowOffsets) { + return this.rowOffsets[end + 1] - this.rowOffsets[start]; + } + + let totalHeight = 0; + + for (let index = start; index < end + 1; index++) { + const height = this.getDefaultCellHeight(); + + totalHeight += height; + } + + return totalHeight; + }; + + protected getDefaultCellHeight(): number { + return this.getRowCellHeight(null as unknown as Node); + } + + protected getShadowFill = (angle: number) => { + const { splitLine } = this.spreadsheet.theme; + + return `l (${angle}) 0:${splitLine?.shadowColors?.left} 1:${splitLine?.shadowColors?.right}`; + }; + + protected clip() { + const { frozenGroupInfo, spreadsheet } = this; + + const isFrozenRowHeader = spreadsheet.isFrozenRowHeader(); + + const frozenColGroupWidth = + frozenGroupInfo[FrozenGroupType.FROZEN_COL].width; + const frozenTrailingColWidth = + frozenGroupInfo[FrozenGroupType.FROZEN_TRAILING_COL].width; + const frozenRowGroupHeight = + frozenGroupInfo[FrozenGroupType.FROZEN_ROW].height; + const frozenTrailingRowHeight = + frozenGroupInfo[FrozenGroupType.FROZEN_TRAILING_ROW].height; + + const panelScrollGroupClipX = + (isFrozenRowHeader ? this.panelBBox.x : 0) + frozenColGroupWidth; + const panelScrollGroupClipY = this.panelBBox.y + frozenRowGroupHeight; + const panelScrollGroupClipWidth = + this.panelBBox.width - + frozenColGroupWidth - + frozenTrailingColWidth + + (isFrozenRowHeader ? 0 : this.panelBBox.x); + const panelScrollGroupClipHeight = + this.panelBBox.height - frozenRowGroupHeight - frozenTrailingRowHeight; + + this.panelScrollGroup.style.clipPath = new Rect({ + style: { + x: panelScrollGroupClipX, + y: panelScrollGroupClipY, + width: panelScrollGroupClipWidth, + height: panelScrollGroupClipHeight, + }, + }); + + this.frozenColGroup.style.clipPath = new Rect({ + style: { + x: this.panelBBox.x, + y: panelScrollGroupClipY, + width: frozenColGroupWidth, + height: panelScrollGroupClipHeight, + }, + }); + + this.frozenTrailingColGroup.style.clipPath = new Rect({ + style: { + x: this.panelBBox.x + this.panelBBox.width - frozenTrailingColWidth, + y: panelScrollGroupClipY, + width: frozenTrailingColWidth, + height: panelScrollGroupClipHeight, + }, + }); + + this.frozenRowGroup.style.clipPath = new Rect({ + style: { + x: panelScrollGroupClipX, + y: this.panelBBox.y, + width: panelScrollGroupClipWidth, + height: frozenRowGroupHeight, + }, + }); + + this.frozenTrailingRowGroup.style.clipPath = new Rect({ + style: { + x: panelScrollGroupClipX, + y: this.panelBBox.y + this.panelBBox.height - frozenTrailingRowHeight, + width: panelScrollGroupClipWidth, + height: frozenTrailingRowHeight, + }, + }); + } +} diff --git a/packages/s2-core/src/facet/header/col.ts b/packages/s2-core/src/facet/header/col.ts index 6f5f2c9ab5..3fd284c6a5 100644 --- a/packages/s2-core/src/facet/header/col.ts +++ b/packages/s2-core/src/facet/header/col.ts @@ -1,8 +1,8 @@ -import { Group, Rect, type DisplayObject } from '@antv/g'; +import { Group, Rect } from '@antv/g'; import { each } from 'lodash'; import { ColCell } from '../../cell/col-cell'; import { - FRONT_GROUND_GROUP_COL_SCROLL_Z_INDEX, + FRONT_GROUND_GROUP_SCROLL_Z_INDEX, KEY_GROUP_COL_SCROLL, S2Event, } from '../../common/constant'; @@ -17,21 +17,20 @@ import type { ColHeaderConfig } from './interface'; export class ColHeader extends BaseHeader<ColHeaderConfig> { protected scrollGroup: Group; - protected background: DisplayObject; - constructor(config: ColHeaderConfig) { super(config); - this.initScrollGroup(); } protected getCellInstance(node: Node) { + const headerConfig = this.getHeaderConfig(); + const { spreadsheet } = this.getHeaderConfig(); const { colCell } = spreadsheet.options; return ( - colCell?.(node, spreadsheet, this.headerConfig) || - new ColCell(node, spreadsheet, this.headerConfig) + colCell?.(node, spreadsheet, headerConfig) || + new ColCell(node, spreadsheet, headerConfig) ); } @@ -39,7 +38,7 @@ export class ColHeader extends BaseHeader<ColHeaderConfig> { this.scrollGroup = this.appendChild( new Group({ name: KEY_GROUP_COL_SCROLL, - style: { zIndex: FRONT_GROUND_GROUP_COL_SCROLL_Z_INDEX }, + style: { zIndex: FRONT_GROUND_GROUP_SCROLL_Z_INDEX }, }), ); } @@ -58,13 +57,14 @@ export class ColHeader extends BaseHeader<ColHeaderConfig> { } protected clip() { - const { height, spreadsheet } = this.getHeaderConfig(); + const { height, width, spreadsheet, position } = this.getHeaderConfig(); + const isFrozenRowHeader = spreadsheet.isFrozenRowHeader(); this.scrollGroup.style.clipPath = new Rect({ style: { - x: 0, - y: 0, - width: spreadsheet.options.width!, + x: isFrozenRowHeader ? position.x : 0, + y: isFrozenRowHeader ? position.y : 0, + width: isFrozenRowHeader ? width : position.x + width, height, }, }); @@ -72,7 +72,6 @@ export class ColHeader extends BaseHeader<ColHeaderConfig> { public clear() { this.scrollGroup?.removeChildren(); - this.background?.remove(); } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -101,12 +100,14 @@ export class ColHeader extends BaseHeader<ColHeaderConfig> { each(nodes, (node) => { if (this.isColCellInRect(node)) { + const group = this.getCellGroup(node); + + node.isFrozen = group !== this.scrollGroup; + const cell = this.getCellInstance(node); node.belongsCell = cell; - const group = this.getCellGroup(node); - group?.appendChild(cell); spreadsheet.emit(S2Event.COL_CELL_RENDER, cell as ColCell); spreadsheet.emit(S2Event.LAYOUT_CELL_RENDER, cell); diff --git a/packages/s2-core/src/facet/header/corner.ts b/packages/s2-core/src/facet/header/corner.ts index 0b7cb4f848..c076d5b3d6 100644 --- a/packages/s2-core/src/facet/header/corner.ts +++ b/packages/s2-core/src/facet/header/corner.ts @@ -3,8 +3,8 @@ import { includes } from 'lodash'; import { CornerCell } from '../../cell/corner-cell'; import type { S2CellType } from '../../common/interface'; import { CornerNodeType } from '../../common/interface/node'; -import type { CornerBBox } from '../bbox/cornerBBox'; -import type { PanelBBox } from '../bbox/panelBBox'; +import type { CornerBBox } from '../bbox/corner-bbox'; +import type { PanelBBox } from '../bbox/panel-bbox'; import { Node } from '../layout/node'; import { translateGroupX } from '../utils'; import { S2Event } from '../../common'; @@ -150,7 +150,7 @@ export class CornerHeader extends BaseHeader<CornerHeaderConfig> { if (spreadsheet.isHierarchyTreeType()) { const cornerText = this.getTreeCornerText(options); const cornerNode: Node = new Node({ - id: '', + id: cornerText, field: '', value: cornerText, }); @@ -178,7 +178,7 @@ export class CornerHeader extends BaseHeader<CornerHeaderConfig> { const value = spreadsheet.dataSet.getFieldName(field); const cornerNode: Node = new Node({ - id: '', + id: field, field, value, }); @@ -206,7 +206,7 @@ export class CornerHeader extends BaseHeader<CornerHeaderConfig> { const value = spreadsheet.dataSet.getFieldName(field); const cNode = new Node({ - id: '', + id: field, field, value, }); diff --git a/packages/s2-core/src/facet/header/frame.ts b/packages/s2-core/src/facet/header/frame.ts index f1458677b4..2f09fcafab 100644 --- a/packages/s2-core/src/facet/header/frame.ts +++ b/packages/s2-core/src/facet/header/frame.ts @@ -3,6 +3,7 @@ import { renderLine } from '.././../utils/g-renders'; import type { FrameConfig } from '../../common/interface'; import { translateGroup } from '../utils'; import type { SpreadSheet } from '../../sheet-type/spread-sheet'; +import { floor } from '../../utils/math'; export class Frame extends Group { declare cfg: FrameConfig; @@ -72,8 +73,7 @@ export class Frame extends Group { public onChangeShadowVisibility(scrollX: number, maxScrollX: number) { this.cfg.showViewportLeftShadow = scrollX > 0; // baseFacet#renderHScrollBar render condition - this.cfg.showViewportRightShadow = - Math.floor(scrollX) < Math.floor(maxScrollX); + this.cfg.showViewportRightShadow = floor(scrollX) < floor(maxScrollX); this.render(); } diff --git a/packages/s2-core/src/facet/header/index.ts b/packages/s2-core/src/facet/header/index.ts index be55354e98..3001a3417d 100644 --- a/packages/s2-core/src/facet/header/index.ts +++ b/packages/s2-core/src/facet/header/index.ts @@ -3,4 +3,5 @@ export { CornerHeader } from './corner'; export { Frame } from './frame'; export { RowHeader } from './row'; export { SeriesNumberHeader } from './series-number'; + export * from './interface'; diff --git a/packages/s2-core/src/facet/header/row.ts b/packages/s2-core/src/facet/header/row.ts index dafbd5eaf2..0d08e50847 100644 --- a/packages/s2-core/src/facet/header/row.ts +++ b/packages/s2-core/src/facet/header/row.ts @@ -1,9 +1,17 @@ -import { Rect } from '@antv/g'; -import { each, isEmpty } from 'lodash'; +import { Group, Rect } from '@antv/g'; +import { each } from 'lodash'; import { RowCell } from '../../cell'; import type { Node } from '../layout/node'; -import { translateGroup } from '../utils'; -import { S2Event } from '../../common'; +import { getFrozenRowCfgPivot, translateGroup } from '../utils'; +import { + FRONT_GROUND_GROUP_FROZEN_Z_INDEX, + FRONT_GROUND_GROUP_SCROLL_Z_INDEX, + FrozenGroupType, + KEY_GROUP_ROW_HEADER_FROZEN, + KEY_GROUP_ROW_SCROLL, + S2Event, +} from '../../common'; +import type { FrozenFacet } from '../frozen-facet'; import { BaseHeader } from './base'; import type { RowHeaderConfig } from './interface'; @@ -11,12 +19,34 @@ import type { RowHeaderConfig } from './interface'; * Row Header for SpreadSheet */ export class RowHeader extends BaseHeader<RowHeaderConfig> { + public scrollGroup: Group; + + public frozenRowGroup: Group; + constructor(config: RowHeaderConfig) { super(config); + this.initGroups(); } - protected getCellInstance(node: Node): RowCell { + private initGroups() { + this.scrollGroup = this.appendChild( + new Group({ + name: KEY_GROUP_ROW_SCROLL, + style: { zIndex: FRONT_GROUND_GROUP_SCROLL_Z_INDEX }, + }), + ); + + this.frozenRowGroup = this.appendChild( + new Group({ + name: KEY_GROUP_ROW_HEADER_FROZEN, + style: { zIndex: FRONT_GROUND_GROUP_FROZEN_Z_INDEX }, + }), + ); + } + + public getCellInstance(node: Node): RowCell { const headerConfig = this.getHeaderConfig(); + const { spreadsheet } = headerConfig; const { rowCell } = spreadsheet.options; @@ -26,53 +56,68 @@ export class RowHeader extends BaseHeader<RowHeaderConfig> { ); } - protected layout() { + // row'cell only show when visible + protected isRowCellInRect(node: Node): boolean { const { - nodes, - spreadsheet, width, viewportHeight, + position, scrollY = 0, scrollX = 0, - position, } = this.getHeaderConfig(); - const rowCell = spreadsheet?.options?.rowCell; - // row'cell only show when visible - const rowCellInRect = (node: Node): boolean => { - return ( - // bottom - viewportHeight + scrollY > node.y && - // top - scrollY < node.y + node.height && - // left - width - position.x + scrollX > node.x && - // right - scrollX - position.x < node.x + node.width - ); - }; + if (this.isFrozenRow(node)) { + return true; + } + + return ( + // bottom + viewportHeight + scrollY > node.y && + // top + scrollY < node.y + node.height && + // left + width - position.x + scrollX > node.x && + // right + scrollX - position.x < node.x + node.width + ); + } + + public isFrozenRow(node: Node): boolean { + const { spreadsheet } = this.headerConfig; + const { facet } = spreadsheet; + const { rowCount = 0 } = getFrozenRowCfgPivot( + spreadsheet.options, + facet.getRowNodes(), + ); + return rowCount > 0 && node.rowIndex >= 0 && node.rowIndex < rowCount; + } + + protected getCellGroup(item: Node): Group { + if (this.isFrozenRow(item)) { + return this.frozenRowGroup; + } + + return this.scrollGroup; + } + + protected layout() { + const { nodes, spreadsheet } = this.getHeaderConfig(); + + // row'cell only show when visible each(nodes, (node) => { - if (rowCellInRect(node) && node.height !== 0) { - let cell: RowCell | null = null; + if (this.isRowCellInRect(node) && node.height !== 0) { + const group = this.getCellGroup(node); - // 首先由外部控制UI展示 - if (rowCell) { - cell = rowCell(node, spreadsheet, this.headerConfig); - } + node.isFrozen = group !== this.scrollGroup; - // 如果外部没处理,就用默认的 - if (isEmpty(cell) && spreadsheet.isPivotMode()) { - cell = new RowCell(node, spreadsheet, this.headerConfig); - } + const cell = this.getCellInstance(node); node.belongsCell = cell; - if (cell) { - this.appendChild(cell); - spreadsheet.emit(S2Event.ROW_CELL_RENDER, cell); - spreadsheet.emit(S2Event.LAYOUT_CELL_RENDER, cell); - } + group.appendChild(cell); + spreadsheet.emit(S2Event.ROW_CELL_RENDER, cell); + spreadsheet.emit(S2Event.LAYOUT_CELL_RENDER, cell); } }); } @@ -80,20 +125,40 @@ export class RowHeader extends BaseHeader<RowHeaderConfig> { protected offset() { const { scrollX = 0, scrollY = 0, position } = this.getHeaderConfig(); - // 向右多移动的 seriesNumberWidth 是序号的宽度 - translateGroup(this, position.x - scrollX, position.y - scrollY); + const translateX = position.x - scrollX; + + translateGroup(this.scrollGroup, translateX, position.y - scrollY); + translateGroup(this.frozenRowGroup, translateX, position.y); } protected clip(): void { - const { width, height, viewportHeight } = this.getHeaderConfig(); + const { width, viewportHeight, position, spreadsheet } = + this.getHeaderConfig(); - this.style.clipPath = new Rect({ + const frozenRowGroupHeight = (spreadsheet.facet as FrozenFacet) + .frozenGroupInfo[FrozenGroupType.FROZEN_ROW].height; + + this.scrollGroup.style.clipPath = new Rect({ + style: { + x: spreadsheet.facet.cornerBBox.x, + y: position.y + frozenRowGroupHeight, + width, + height: viewportHeight, + }, + }); + + this.frozenRowGroup.style.clipPath = new Rect({ style: { - x: 0, - y: 0, + x: spreadsheet.facet.cornerBBox.x, + y: position.y, width, - height: height + viewportHeight, + height: frozenRowGroupHeight, }, }); } + + public clear() { + this.scrollGroup?.removeChildren(); + this.frozenRowGroup?.removeChildren(); + } } diff --git a/packages/s2-core/src/facet/header/series-number.ts b/packages/s2-core/src/facet/header/series-number.ts index 768ee4a14e..388017f368 100644 --- a/packages/s2-core/src/facet/header/series-number.ts +++ b/packages/s2-core/src/facet/header/series-number.ts @@ -2,7 +2,7 @@ import { Rect } from '@antv/g'; import { each } from 'lodash'; import { SeriesNumberCell } from '../../cell/series-number-cell'; import type { SpreadSheet } from '../../sheet-type/index'; -import type { PanelBBox } from '../bbox/panelBBox'; +import type { PanelBBox } from '../bbox/panel-bbox'; import type { Hierarchy } from '../layout/hierarchy'; import type { Node } from '../layout/node'; import { translateGroup } from '../utils'; @@ -65,12 +65,13 @@ export class SeriesNumberHeader extends BaseHeader<BaseHeaderConfig> { } public clip(): void { - const { width, height, viewportHeight } = this.getHeaderConfig(); + const { width, height, viewportHeight, position, spreadsheet } = + this.getHeaderConfig(); this.style.clipPath = new Rect({ style: { - x: 0, - y: 0, + x: spreadsheet.facet.cornerBBox.x, + y: position.y, width, height: height + viewportHeight, }, diff --git a/packages/s2-core/src/facet/header/table-col.ts b/packages/s2-core/src/facet/header/table-col.ts index 8df8a0d84d..c21ef890f0 100644 --- a/packages/s2-core/src/facet/header/table-col.ts +++ b/packages/s2-core/src/facet/header/table-col.ts @@ -1,14 +1,14 @@ import { Group, Rect, type RectStyleProps } from '@antv/g'; import { TableColCell, TableCornerCell } from '../../cell'; import { - FRONT_GROUND_GROUP_COL_FROZEN_Z_INDEX, + FRONT_GROUND_GROUP_FROZEN_Z_INDEX, + FrozenGroupType, KEY_GROUP_COL_FROZEN, KEY_GROUP_COL_FROZEN_TRAILING, KEY_GROUP_FROZEN_COL_RESIZE_AREA, SERIES_NUMBER_FIELD, } from '../../common/constant'; import type { SpreadSheet } from '../../sheet-type'; -import { getFrozenColWidth } from '../../utils/layout/frozen'; import type { Node } from '../layout/node'; import { getFrozenLeafNodesCount, @@ -17,6 +17,7 @@ import { isFrozenTrailingCol, translateGroupX, } from '../utils'; +import type { FrozenFacet } from '../frozen-facet'; import { ColHeader } from './col'; import type { ColHeaderConfig } from './interface'; @@ -30,7 +31,6 @@ export class TableColHeader extends ColHeader { constructor(config: ColHeaderConfig) { super(config); - this.initFrozenColGroups(); } @@ -63,7 +63,7 @@ export class TableColHeader extends ColHeader { this.frozenColGroup = this.appendChild( new Group({ name: KEY_GROUP_COL_FROZEN, - style: { zIndex: FRONT_GROUND_GROUP_COL_FROZEN_Z_INDEX }, + style: { zIndex: FRONT_GROUND_GROUP_FROZEN_Z_INDEX }, }), ); } @@ -72,30 +72,14 @@ export class TableColHeader extends ColHeader { this.frozenTrailingColGroup = this.appendChild( new Group({ name: KEY_GROUP_COL_FROZEN_TRAILING, - style: { zIndex: FRONT_GROUND_GROUP_COL_FROZEN_Z_INDEX }, + style: { zIndex: FRONT_GROUND_GROUP_FROZEN_Z_INDEX }, }), ); } } - protected isFrozenCell(meta: Node) { - const { spreadsheet } = this.getHeaderConfig(); - const { - colCount: frozenColCount = 0, - trailingColCount: frozenTrailingColCount = 0, - } = spreadsheet.options.frozen!; - const { colIndex } = meta; - const colLeafNodes = spreadsheet.facet.getColLeafNodes(); - - return ( - isFrozenCol(colIndex, frozenColCount) || - isFrozenTrailingCol(colIndex, frozenTrailingColCount, colLeafNodes.length) - ); - } - public clear() { super.clear(); - this.frozenTrailingColGroup?.removeChildren(); this.frozenColGroup?.removeChildren(); @@ -116,37 +100,23 @@ export class TableColHeader extends ColHeader { const leftLeafNode = getLeftLeafNode(node); const topLevelNodes = spreadsheet.facet.getColNodes(0); - const { - colCount: frozenColCount, - trailingColCount: frozenTrailingColCount, - } = getFrozenLeafNodesCount(topLevelNodes, colCount, trailingColCount); - return { - leftLeafNodeColIndex: leftLeafNode.colIndex, - frozenColCount, - frozenTrailingColCount, colLength: topLevelNodes.length, + leftLeafNodeColIndex: leftLeafNode.colIndex, + ...getFrozenLeafNodesCount(topLevelNodes, colCount, trailingColCount), }; } protected getCellGroup(node: Node): Group { - const { - leftLeafNodeColIndex, - frozenColCount, - frozenTrailingColCount, - colLength, - } = this.getColFrozenOptionsByNode(node); + const { colLength, leftLeafNodeColIndex, colCount, trailingColCount } = + this.getColFrozenOptionsByNode(node); - if (isFrozenCol(leftLeafNodeColIndex, frozenColCount)) { + if (isFrozenCol(leftLeafNodeColIndex, colCount)) { return this.frozenColGroup; } if ( - isFrozenTrailingCol( - leftLeafNodeColIndex, - frozenTrailingColCount, - colLength, - ) + isFrozenTrailingCol(leftLeafNodeColIndex, trailingColCount, colLength) ) { return this.frozenTrailingColGroup; } @@ -155,20 +125,12 @@ export class TableColHeader extends ColHeader { } protected isColCellInRect(node: Node): boolean { - const { - leftLeafNodeColIndex, - frozenColCount, - frozenTrailingColCount, - colLength, - } = this.getColFrozenOptionsByNode(node); + const { leftLeafNodeColIndex, colLength, colCount, trailingColCount } = + this.getColFrozenOptionsByNode(node); if ( - isFrozenCol(leftLeafNodeColIndex, frozenColCount) || - isFrozenTrailingCol( - leftLeafNodeColIndex, - frozenTrailingColCount, - colLength, - ) + isFrozenCol(leftLeafNodeColIndex, colCount) || + isFrozenTrailingCol(leftLeafNodeColIndex, trailingColCount, colLength) ) { return true; } @@ -177,17 +139,16 @@ export class TableColHeader extends ColHeader { } public getScrollGroupClipBBox = (): RectStyleProps => { - const { width, height, spreadsheet } = this.getHeaderConfig(); - const topLevelNodes = spreadsheet.facet.getColNodes(0); - const { frozenColWidth, frozenTrailingColWidth } = getFrozenColWidth( - topLevelNodes, - spreadsheet.options.frozen!, - ); - const scrollGroupWidth = width - frozenColWidth - frozenTrailingColWidth; + const { width, height, spreadsheet, position } = this.getHeaderConfig(); + const frozenGroupInfo = (spreadsheet.facet as FrozenFacet).frozenGroupInfo; + const colWidth = frozenGroupInfo[FrozenGroupType.FROZEN_COL].width; + const trailingColWidth = + frozenGroupInfo[FrozenGroupType.FROZEN_TRAILING_COL].width; + const scrollGroupWidth = width - colWidth - trailingColWidth; return { - x: frozenColWidth, - y: 0, + x: position.x + colWidth, + y: position.y, width: scrollGroupWidth, height, }; diff --git a/packages/s2-core/src/facet/header/util.ts b/packages/s2-core/src/facet/header/util.ts index a268178d2c..8da2016ef2 100644 --- a/packages/s2-core/src/facet/header/util.ts +++ b/packages/s2-core/src/facet/header/util.ts @@ -40,6 +40,8 @@ export const getSeriesNumberNodes = ( sNode.height = isHierarchyTreeType ? node.getTotalHeightForTreeHierarchy() : node.height; + sNode.isLeaf = true; + sNode.spreadsheet = spreadsheet; return sNode; }); diff --git a/packages/s2-core/src/facet/layout/build-gird-hierarchy.ts b/packages/s2-core/src/facet/layout/build-gird-hierarchy.ts index 42cd6c35db..162b839078 100644 --- a/packages/s2-core/src/facet/layout/build-gird-hierarchy.ts +++ b/packages/s2-core/src/facet/layout/build-gird-hierarchy.ts @@ -1,37 +1,18 @@ -import { isEmpty, isUndefined } from 'lodash'; -import { EXTRA_FIELD } from '../../common/constant'; -import type { SpreadSheet } from '../../sheet-type'; +import { isEmpty } from 'lodash'; +import { EMPTY_FIELD_VALUE, EXTRA_FIELD } from '../../common/constant'; import { addTotals } from '../../utils/layout/add-totals'; import { generateHeaderNodes } from '../../utils/layout/generate-header-nodes'; import { getDimsCondition } from '../../utils/layout/get-dims-condition-by-node'; +import { whetherLeafByLevel } from '../../utils/layout/whether-leaf-by-level'; import type { FieldValue, GridHeaderParams } from '../layout/interface'; import { layoutArrange } from '../layout/layout-hooks'; import { TotalMeasure } from '../layout/total-measure'; +import { filterOutDetail } from '../../utils/data-set-operate'; +import { TotalClass } from './total-class'; -const hideValueColumn = ( - spreadsheet: SpreadSheet, - fieldValues: FieldValue[], - field: string, -) => { - const hideMeasure = spreadsheet.options.style?.colCell?.hideValue ?? false; - const { valueInCols } = spreadsheet.dataSet.fields; - - for (const value of fieldValues) { - if (hideMeasure && valueInCols && field === EXTRA_FIELD) { - fieldValues.splice(fieldValues.indexOf(value), 1); - } - } -}; - -/** - * Build grid hierarchy in rows or columns - * - * @param params - */ -export const buildGridHierarchy = (params: GridHeaderParams) => { +const buildTotalGridHierarchy = (params: GridHeaderParams) => { const { addTotalMeasureInTotal, - addMeasureInTotalQuery, parentNode, currentField, fields, @@ -40,73 +21,127 @@ export const buildGridHierarchy = (params: GridHeaderParams) => { } = params; const index = fields.indexOf(currentField); - - const { values = [] } = spreadsheet.dataSet.fields; + const dataSet = spreadsheet.dataSet; + const { values = [] } = dataSet.fields; const fieldValues: FieldValue[] = []; - let query: Record<string, string> = {}; - - if (parentNode.isTotals) { - // add total measures - if (addTotalMeasureInTotal) { - query = getDimsCondition(parentNode.parent!, true); - // add total measures - fieldValues.push(...values.map((v) => new TotalMeasure(v))); + let query: Record<string, unknown> = {}; + const totalsConfig = spreadsheet.getTotalsConfig(currentField); + const dimensionGroup = parentNode.isGrandTotals + ? totalsConfig.grandTotalsGroupDimensions + : totalsConfig.subTotalsGroupDimensions; + + if (dimensionGroup?.includes(currentField)) { + query = getDimsCondition(parentNode); + const dimValues = dataSet.getDimensionValues(currentField, query); + + fieldValues.push( + ...(dimValues || []).map( + (value) => + new TotalClass({ + label: value, + isSubTotals: parentNode.isSubTotals!, + isGrandTotals: parentNode.isGrandTotals!, + isTotalRoot: false, + }), + ), + ); + if (isEmpty(fieldValues) && currentField) { + fieldValues.push(EMPTY_FIELD_VALUE); } + } else if (addTotalMeasureInTotal && currentField === EXTRA_FIELD) { + // add total measures + query = getDimsCondition(parentNode); + fieldValues.push(...values.map((v) => new TotalMeasure(v))); + } else if (whetherLeafByLevel({ spreadsheet, level: index, fields })) { + // 如果最后一级没有分组维度,则将上一个结点设为叶子节点 + parentNode.isLeaf = true; + hierarchy.pushIndexNode(parentNode); + parentNode.rowIndex = hierarchy.getIndexNodes().length - 1; + + return; } else { - // field(dimension)'s all values - query = getDimsCondition(parentNode, true); + // 如果是空维度,则跳转到下一级 level + buildTotalGridHierarchy({ ...params, currentField: fields[index + 1] }); - const dimValues = spreadsheet.dataSet.getDimensionValues( - currentField, - query, - ); + return; + } - const arrangedValues = layoutArrange( - spreadsheet, - dimValues, - parentNode, - currentField, - ); + const displayFieldValues = filterOutDetail(fieldValues as string[]); - fieldValues.push(...(arrangedValues || [])); + generateHeaderNodes({ + ...params, + fieldValues: displayFieldValues, + level: index, + parentNode, + query, + }); +}; - // add skeleton for empty data +const buildNormalGridHierarchy = (params: GridHeaderParams) => { + const { parentNode, currentField, fields, spreadsheet } = params; + const dataSet = spreadsheet.dataSet; + const { values = [] } = dataSet.fields; - const fieldName = spreadsheet.dataSet.getFieldName(currentField); + const index = fields.indexOf(currentField); - if (isEmpty(fieldValues)) { - if (currentField === EXTRA_FIELD) { - fieldValues.push(...(values || [])); - } else { - fieldValues.push(fieldName); - } - } + const fieldValues: FieldValue[] = []; - // hide value in columns - hideValueColumn(spreadsheet, fieldValues, currentField); - // add totals if needed - addTotals({ - currentField, - lastField: fields[index - 1], - isFirstField: index === 0, - fieldValues, - spreadsheet, - }); - } + let query: Record<string, unknown> = {}; - const displayFieldValues = fieldValues.filter((value) => !isUndefined(value)); + // field(dimension)'s all values + query = getDimsCondition(parentNode, true); - generateHeaderNodes({ + const dimValues = dataSet.getDimensionValues(currentField, query); + + const arrangedValues = layoutArrange( spreadsheet, + dimValues, + parentNode, currentField, - fields, + ); + + fieldValues.push(...(arrangedValues || [])); + + // add skeleton for empty data + + if (isEmpty(fieldValues) && currentField) { + if (currentField === EXTRA_FIELD) { + fieldValues.push(...values); + } else { + fieldValues.push(EMPTY_FIELD_VALUE); + } + } + + // add totals if needed + addTotals({ + currentField, + lastField: fields[index - 1], + isFirstField: index === 0, + fieldValues, + spreadsheet, + }); + + const displayFieldValues = filterOutDetail(fieldValues as string[]); + + generateHeaderNodes({ + ...params, fieldValues: displayFieldValues, - hierarchy, - parentNode, level: index, + parentNode, query, - addMeasureInTotalQuery, - addTotalMeasureInTotal, }); }; + +/** + * Build grid hierarchy in rows or columns + * + * @param params + */ +export const buildGridHierarchy = (params: GridHeaderParams) => { + if (params.parentNode.isTotals) { + buildTotalGridHierarchy(params); + } else { + buildNormalGridHierarchy(params); + } +}; diff --git a/packages/s2-core/src/facet/layout/build-row-tree-hierarchy.ts b/packages/s2-core/src/facet/layout/build-row-tree-hierarchy.ts index a779f5d286..5b1c6aa21d 100644 --- a/packages/s2-core/src/facet/layout/build-row-tree-hierarchy.ts +++ b/packages/s2-core/src/facet/layout/build-row-tree-hierarchy.ts @@ -1,8 +1,7 @@ import { isNumber } from 'lodash'; -import { i18n, NODE_ID_SEPARATOR, ROOT_NODE_ID } from '../../common'; -import type { PivotDataSet } from '../../data-set'; +import { i18n } from '../../common'; import type { SpreadSheet } from '../../sheet-type'; -import { filterTotal, getListBySorted } from '../../utils/data-set-operate'; +import { filterOutDetail } from '../../utils/data-set-operate'; import { generateId } from '../../utils/layout/generate-id'; import type { FieldValue, TreeHeaderParams } from '../layout/interface'; import { layoutArrange, layoutHierarchy } from '../layout/layout-hooks'; @@ -25,13 +24,16 @@ const addTotals = ( const func = totalsConfig.reverseGrandTotalsLayout ? 'unshift' : 'push'; fieldValues[func]( - new TotalClass(totalsConfig.grandTotalsLabel!, false, true), + new TotalClass({ + label: totalsConfig.grandTotalsLabel!, + isGrandTotals: true, + isSubTotals: false, + isTotalRoot: false, + }), ); } }; -const NODE_ID_PREFIX_LEN = (ROOT_NODE_ID + NODE_ID_SEPARATOR).length; - /** * Only row header has tree hierarchy, in this scene: * 1、value in rows is not work => valueInCols is ineffective @@ -39,40 +41,12 @@ const NODE_ID_PREFIX_LEN = (ROOT_NODE_ID + NODE_ID_SEPARATOR).length; * @param params */ export const buildRowTreeHierarchy = (params: TreeHeaderParams) => { - const { - parentNode, - currentField = '', - level, - hierarchy, - pivotMeta, - spreadsheet, - } = params; - const { collapseFields, collapseAll, expandDepth } = - spreadsheet.options.style?.rowCell!; + const { spreadsheet, parentNode, currentField, level, hierarchy, pivotMeta } = + params; const { query, id: parentId } = parentNode; - const isDrillDownItem = spreadsheet.dataCfg.fields.rows?.length! <= level; - const sortedDimensionValues = - (spreadsheet.dataSet as PivotDataSet)?.sortedDimensionValues?.[ - currentField - ] || []; - - const unsortedDimValues = filterTotal(Array.from(pivotMeta.keys())); - const dimValues = getListBySorted( - unsortedDimValues, - sortedDimensionValues, - (dimVal) => { - /* - * 根据父节点 id,修改 unsortedDimValues 里用于比较的值,使其格式与 sortedDimensionValues 排序值一致 - * unsortedDimValues:['成都', '绵阳'] - * sortedDimensionValues: ['四川[&]成都'] - */ - if (ROOT_NODE_ID === parentId) { - return dimVal; - } - - return generateId(parentId, dimVal).slice(NODE_ID_PREFIX_LEN); - }, - ); + const isDrillDownItem = spreadsheet.dataCfg?.fields?.rows?.length! <= level; + + const dimValues = filterOutDetail(Array.from(pivotMeta.keys())); let fieldValues: FieldValue[] = layoutArrange( spreadsheet, @@ -119,6 +93,8 @@ export const buildRowTreeHierarchy = (params: TreeHeaderParams) => { const nodeId = generateId(parentId, value); + const { collapseFields, collapseAll, expandDepth } = + spreadsheet.options.style?.rowCell!; /* * 行头收起/展开配置优先级:collapseFields -> expandDepth -> collapseAll * 优先从读取 collapseFields 中的特定 node 的值 diff --git a/packages/s2-core/src/facet/layout/interface.ts b/packages/s2-core/src/facet/layout/interface.ts index 2ad8638826..c190d00327 100644 --- a/packages/s2-core/src/facet/layout/interface.ts +++ b/packages/s2-core/src/facet/layout/interface.ts @@ -39,15 +39,8 @@ export interface TotalParams { spreadsheet: SpreadSheet; } -export interface HeaderNodesParams { - spreadsheet: SpreadSheet; - currentField: string; - fields: string[]; +export interface HeaderNodesParams extends GridHeaderParams { fieldValues: FieldValue[]; - addTotalMeasureInTotal: boolean; - addMeasureInTotalQuery: boolean; - hierarchy: Hierarchy; - parentNode: Node; level: number; query: Record<string, any>; } @@ -67,7 +60,7 @@ export interface TreeHeaderParams { spreadsheet: SpreadSheet; parentNode: Node; hierarchy: Hierarchy; - currentField: string | undefined; + currentField: string; level: number; pivotMeta: PivotMeta; } @@ -101,3 +94,9 @@ export interface CustomTreeHeaderParams { hierarchy: Hierarchy; tree: CustomTreeNode[]; } + +export interface WhetherLeafParams { + spreadsheet: SpreadSheet; + fields: string[]; + level: number; +} diff --git a/packages/s2-core/src/facet/layout/layout-hooks.ts b/packages/s2-core/src/facet/layout/layout-hooks.ts index 21675b6fb1..ccb0220352 100644 --- a/packages/s2-core/src/facet/layout/layout-hooks.ts +++ b/packages/s2-core/src/facet/layout/layout-hooks.ts @@ -36,7 +36,13 @@ export const layoutHierarchy = ( const hiddenColumnNode = spreadsheet?.facet?.getHiddenColumnsInfo(currentNode); - if (hiddenColumnNode) { + if ( + hiddenColumnNode && + // fix: Only hiding the column headers is supported to prevent the row subtotals from being hidden when the IDs of the row totals and column totals are the same. + spreadsheet?.dataSet?.fields?.columns?.find( + (field) => field === currentNode?.field, + ) + ) { return false; } diff --git a/packages/s2-core/src/facet/layout/node.ts b/packages/s2-core/src/facet/layout/node.ts index 94343bcda0..359902d0e6 100644 --- a/packages/s2-core/src/facet/layout/node.ts +++ b/packages/s2-core/src/facet/layout/node.ts @@ -29,6 +29,7 @@ export interface BaseNodeConfig { isSubTotals?: boolean; isCollapsed?: boolean | null; isGrandTotals?: boolean; + isTotalRoot?: boolean; hierarchy?: Hierarchy; isPivotMode?: boolean; seriesNumberWidth?: number; @@ -130,8 +131,12 @@ export class Node { public isSubTotals?: boolean; + public isTotalRoot?: boolean; + public hiddenChildNodeInfo?: HiddenColumnsInfo | null; + public isFrozen?: boolean; + public extra?: { description?: string; isCustomNode?: boolean; @@ -152,6 +157,7 @@ export class Node { isGrandTotals, isSubTotals, isCollapsed, + isTotalRoot, hierarchy, isPivotMode, seriesNumberWidth, @@ -183,6 +189,7 @@ export class Node { this.isGrandTotals = isGrandTotals; this.isSubTotals = isSubTotals; this.belongsCell = belongsCell; + this.isTotalRoot = isTotalRoot; this.extra = extra; } diff --git a/packages/s2-core/src/facet/layout/total-class.ts b/packages/s2-core/src/facet/layout/total-class.ts index 51df2d35c1..719687895f 100644 --- a/packages/s2-core/src/facet/layout/total-class.ts +++ b/packages/s2-core/src/facet/layout/total-class.ts @@ -1,6 +1,16 @@ /** * Class to mark '小计' & '总计' */ + +export interface TotalClassConfig { + label: string; + // 是否属于小计汇总格 + isSubTotals: boolean; + // 是否属于总计汇总格 + isGrandTotals: boolean; + // 是否是”小计“、”总计“单元格本身 + isTotalRoot?: boolean; +} export class TotalClass { public label: string; @@ -8,13 +18,15 @@ export class TotalClass { public isGrandTotals: boolean; - public constructor( - label: string, - isSubTotals = false, - isGrandTotals = false, - ) { + // 是否为 小计/总计 根结点,即 value = “小计”,单元格,此类结点不参与 query + public isTotalRoot: boolean; + + public constructor(params: TotalClassConfig) { + const { label, isSubTotals, isGrandTotals, isTotalRoot = false } = params; + this.label = label; this.isSubTotals = isSubTotals; this.isGrandTotals = isGrandTotals; + this.isTotalRoot = isTotalRoot; } } diff --git a/packages/s2-core/src/facet/pivot-facet.ts b/packages/s2-core/src/facet/pivot-facet.ts index 59253edf41..6c9a2f7a32 100644 --- a/packages/s2-core/src/facet/pivot-facet.ts +++ b/packages/s2-core/src/facet/pivot-facet.ts @@ -1,11 +1,10 @@ +import { Group, Rect, type LineStyleProps } from '@antv/g'; import { filter, - find, forEach, get, isArray, isEmpty, - isNil, isNumber, keys, last, @@ -19,37 +18,36 @@ import { import { ColCell, RowCell, SeriesNumberCell } from '../cell'; import { DEFAULT_TREE_ROW_CELL_WIDTH, + FRONT_GROUND_GROUP_FROZEN_Z_INDEX, + FrozenGroupType, + KEY_GROUP_FROZEN_SPLIT_LINE, LAYOUT_SAMPLE_COUNT, + SPLIT_LINE_WIDTH, type IconTheme, type MultiData, type ViewMeta, } from '../common'; -import { EXTRA_FIELD, LayoutWidthTypes, VALUE_FIELD } from '../common/constant'; +import { EXTRA_FIELD, LayoutWidthType, VALUE_FIELD } from '../common/constant'; import { CellType } from '../common/constant/interaction'; import { DebuggerUtil } from '../common/debug'; import type { LayoutResult, SimpleData } from '../common/interface'; import type { PivotDataSet } from '../data-set/pivot-data-set'; -import type { SpreadSheet } from '../sheet-type'; -import { safeJsonParse } from '../utils'; +import { renderLine, safeJsonParse } from '../utils'; import { getDataCellId } from '../utils/cell/data-cell'; import { getActionIconConfig } from '../utils/cell/header-cell'; -import { - getIndexRangeWithOffsets, - getSubTotalNodeWidthOrHeightByLevel, -} from '../utils/facet'; +import { getIndexRangeWithOffsets } from '../utils/facet'; +import { getRowsForGrid } from '../utils/grid'; +import { floor } from '../utils/math'; import { getCellWidth } from '../utils/text'; -import { BaseFacet } from './base-facet'; +import { FrozenFacet } from './frozen-facet'; import { Frame } from './header'; import { buildHeaderHierarchy } from './layout/build-header-hierarchy'; import type { Hierarchy } from './layout/hierarchy'; import { layoutCoordinate } from './layout/layout-hooks'; import { Node } from './layout/node'; +import { getFrozenRowCfgPivot } from './utils'; -export class PivotFacet extends BaseFacet { - public constructor(spreadsheet: SpreadSheet) { - super(spreadsheet); - } - +export class PivotFacet extends FrozenFacet { get rowCellTheme() { return this.spreadsheet.theme.rowCell!.cell; } @@ -217,7 +215,9 @@ export class PivotFacet extends BaseFacet { const colNodeHeight = this.getColNodeHeight(currentNode, colsHierarchy); currentNode.height = - currentNode.isGrandTotals && currentNode.isLeaf + currentNode.isGrandTotals && + !currentNode.isTotalMeasure && + currentNode.isLeaf ? colsHierarchy.height : colNodeHeight; @@ -232,21 +232,105 @@ export class PivotFacet extends BaseFacet { this.autoCalculateColNodeWidthAndX(colLeafNodes); if (!isEmpty(this.spreadsheet.options.totals?.col)) { - this.adjustGrandTotalNodesCoordinate(colsHierarchy); - this.adjustSubTotalNodesCoordinate(colsHierarchy); + this.adjustTotalNodesCoordinate({ + hierarchy: colsHierarchy, + isRowHeader: false, + isSubTotal: true, + }); + this.adjustTotalNodesCoordinate({ + hierarchy: colsHierarchy, + isRowHeader: false, + isSubTotal: false, + }); + } + } + + // please read README-adjustTotalNodesCoordinate.md to understand this function + private getMultipleMap( + hierarchy: Hierarchy, + isRowHeader?: boolean, + isSubTotal?: boolean, + ) { + const { maxLevel } = hierarchy; + const dataSet = this.spreadsheet.dataSet; + const { totals } = this.spreadsheet.options; + const moreThanOneValue = dataSet.moreThanOneValue(); + const { rows, columns } = dataSet.fields; + const fields = isRowHeader ? rows : columns; + const totalConfig = isRowHeader ? totals!.row : totals!.col; + const dimensionGroup = isSubTotal + ? totalConfig?.subTotalsGroupDimensions || [] + : totalConfig?.grandTotalsGroupDimensions || []; + const multipleMap: number[] = Array.from({ length: maxLevel + 1 }, () => 1); + + for (let level = maxLevel; level > 0; level--) { + const currentField = fields![level] as string; + // 若不符合【分组维度包含此维度】或【者指标维度下非单指标维度】,此表头单元格为空,将宽高合并到上级单元格 + const existValueField = currentField === EXTRA_FIELD && moreThanOneValue; + + if (!(dimensionGroup.includes(currentField) || existValueField)) { + multipleMap[level - 1] += multipleMap[level]; + multipleMap[level] = 0; + } } + + return multipleMap; + } + + // please read README-adjustTotalNodesCoordinate.md to understand this function + private adjustTotalNodesCoordinate(params: { + hierarchy: Hierarchy; + isRowHeader?: boolean; + isSubTotal?: boolean; + }) { + const { hierarchy, isRowHeader, isSubTotal } = params; + const multipleMap = this.getMultipleMap(hierarchy, isRowHeader, isSubTotal); + const totalNodes = filter(hierarchy.getNodes(), (node: Node) => + isSubTotal ? node.isSubTotals : node.isGrandTotals, + ) as Node[]; + const key = isRowHeader ? 'width' : 'height'; + + forEach(totalNodes, (node: Node) => { + let multiple = multipleMap[node.level]; + + // 小计根节点若为 0,则改为最近上级倍数 - level 差 + if (!multiple && isSubTotal) { + let lowerLevelIndex = 1; + + while (multiple < 1) { + multiple = + multipleMap[node.level - lowerLevelIndex] - lowerLevelIndex; + lowerLevelIndex++; + } + } + + let res = 0; + + for (let i = 0; i < multiple; i++) { + res += get( + hierarchy.sampleNodesForAllLevels?.find( + (sampleNode) => sampleNode.level === node.level + i, + ), + [key], + 0, + ); + } + node[key] = res; + }); } /** - * Auto Auto Auto column no-leaf node's width and x coordinate + * Auto column no-leaf node's width and x coordinate * @param colLeafNodes */ private autoCalculateColNodeWidthAndX(colLeafNodes: Node[]) { let prevColParent: Node | null = null; + let i = 0; + const leafNodes = colLeafNodes.slice(0); - while (leafNodes.length) { - const node = leafNodes.shift(); + while (i < leafNodes.length) { + const node = leafNodes[i++]; const parentNode = node?.parent; if (prevColParent !== parentNode && parentNode) { @@ -269,14 +353,14 @@ export class PivotFacet extends BaseFacet { } private calculateColLeafNodesWidth( - col: Node, + colNode: Node, colLeafNodes: Node[], rowLeafNodes: Node[], rowHeaderWidth: number, ): number { const { colCell } = this.spreadsheet.options.style!; - const cellDraggedWidth = this.getColCellDraggedWidth(col); + const cellDraggedWidth = this.getColCellDraggedWidth(colNode); // 1. 拖拽后的宽度优先级最高 if (isNumber(cellDraggedWidth)) { @@ -284,91 +368,15 @@ export class PivotFacet extends BaseFacet { } // 2. 其次是自定义, 返回 null 则使用默认宽度 - const cellCustomWidth = this.getCellCustomSize(col, colCell?.width!); + const cellCustomWidth = this.getCellCustomSize(colNode, colCell?.width!); if (isNumber(cellCustomWidth)) { return cellCustomWidth; } // 3. 紧凑布局 - if (this.spreadsheet.getLayoutWidthType() === LayoutWidthTypes.Compact) { - const { - bolderText: colCellTextStyle, - cell: colCellStyle, - icon: colIconStyle, - } = this.spreadsheet.theme.colCell!; - - // leaf node rough width - const cellFormatter = this.spreadsheet.dataSet.getFieldFormatter( - col.field, - ); - const leafNodeLabel = cellFormatter?.(col.value) ?? col.value; - const iconWidth = this.getExpectedCellIconWidth( - CellType.COL_CELL, - this.spreadsheet.isValueInCols() && - this.spreadsheet.options.showDefaultHeaderActionIcon!, - colIconStyle!, - ); - const leafNodeRoughWidth = - this.spreadsheet.measureTextWidthRoughly(leafNodeLabel) + iconWidth; - - // 采样 50 个 label,逐个计算找出最长的 label - let maxDataLabel = ''; - let maxDataLabelWidth = 0; - - for (let index = 0; index < LAYOUT_SAMPLE_COUNT; index++) { - const rowNode = rowLeafNodes[index]; - - if (rowNode) { - const cellData = ( - this.spreadsheet.dataSet as PivotDataSet - ).getCellData({ - query: { ...col.query, ...rowNode.query }, - rowNode, - isTotals: - col.isTotals || - col.isTotalMeasure || - rowNode.isTotals || - rowNode.isTotalMeasure, - }); - - if (cellData) { - // 总小计格子不一定有数据 - const valueData = cellData?.[VALUE_FIELD]; - const formattedValue = - this.spreadsheet.dataSet.getFieldFormatter( - cellData[EXTRA_FIELD], - )?.(valueData) ?? valueData; - const cellLabel = `${formattedValue}`; - const cellLabelWidth = - this.spreadsheet.measureTextWidthRoughly(cellLabel); - - if (cellLabelWidth > maxDataLabelWidth) { - maxDataLabel = cellLabel; - maxDataLabelWidth = cellLabelWidth; - } - } - } - } - - // compare result - const isLeafNodeWidthLonger = leafNodeRoughWidth > maxDataLabelWidth; - const maxLabel = isLeafNodeWidthLonger ? leafNodeLabel : maxDataLabel; - const appendedWidth = isLeafNodeWidthLonger ? iconWidth : 0; - - DebuggerUtil.getInstance().logger( - 'Max Label In Col:', - col.field, - maxLabel, - ); - - return ( - this.spreadsheet.measureTextWidth(maxLabel, colCellTextStyle) + - colCellStyle!.padding!.left! + - colCellStyle!.padding!.right! + - colCellStyle!.verticalBorderWidth! * 2 + - appendedWidth - ); + if (this.spreadsheet.getLayoutWidthType() === LayoutWidthType.Compact) { + return this.getCompactGridColNodeWidth(colNode, rowLeafNodes); } /** @@ -376,7 +384,7 @@ export class PivotFacet extends BaseFacet { * 4.1 树状自定义 */ if (this.spreadsheet.isHierarchyTreeType()) { - return this.getAdaptTreeColWidth(col, colLeafNodes, rowLeafNodes); + return this.getAdaptTreeColWidth(colNode, colLeafNodes, rowLeafNodes); } // 4.2 网格自定义 @@ -531,8 +539,16 @@ export class PivotFacet extends BaseFacet { }); this.autoCalculateRowNodeHeightAndY(rowLeafNodes); if (!isEmpty(this.spreadsheet.options.totals?.row)) { - this.adjustGrandTotalNodesCoordinate(rowsHierarchy, true); - this.adjustSubTotalNodesCoordinate(rowsHierarchy, true); + this.adjustTotalNodesCoordinate({ + hierarchy: rowsHierarchy, + isRowHeader: true, + isSubTotal: false, + }); + this.adjustTotalNodesCoordinate({ + hierarchy: rowsHierarchy, + isRowHeader: true, + isSubTotal: true, + }); } } } @@ -544,10 +560,11 @@ export class PivotFacet extends BaseFacet { private autoCalculateRowNodeHeightAndY(rowLeafNodes: Node[]) { // 3、in grid type, all no-leaf node's height, y are auto calculated let prevRowParent = null; + let i = 0; const leafNodes = rowLeafNodes.slice(0); - while (leafNodes.length) { - const node = leafNodes.shift(); + while (i < leafNodes.length) { + const node = leafNodes[i++]; const parent = node?.parent; if (prevRowParent !== parent && parent) { @@ -563,116 +580,6 @@ export class PivotFacet extends BaseFacet { } } - /** - * @description adjust the coordinate of total nodes and their children - * @param hierarchy Hierarchy - * @param isRowHeader boolean - */ - private adjustGrandTotalNodesCoordinate( - hierarchy: Hierarchy, - isRowHeader?: boolean, - ) { - const moreThanOneValue = this.spreadsheet.dataSet.moreThanOneValue(); - const { maxLevel } = hierarchy; - const grandTotalNode = find( - hierarchy.getNodes(0), - (node: Node) => node.isGrandTotals, - ); - - if (!(grandTotalNode instanceof Node)) { - return; - } - - const grandTotalChildren = grandTotalNode.children; - - // 总计节点层级 (有且有两级) - if (isRowHeader) { - // 填充行总单元格宽度 - grandTotalNode.width = hierarchy.width; - // 调整其叶子节点位置和宽度 - forEach(grandTotalChildren, (node: Node) => { - const maxLevelNode = hierarchy.getNodes(maxLevel)[0]; - - node.x = maxLevelNode.x; - node.width = maxLevelNode.width; - }); - } else if (maxLevel > 1 || (maxLevel <= 1 && !moreThanOneValue)) { - /* - * 只有当列头总层级大于1级或列头为1级单指标时总计格高度才需要填充 - * 填充列总计单元格高度 - */ - const grandTotalChildrenHeight = grandTotalChildren?.[0]?.height ?? 0; - - grandTotalNode.height = hierarchy.height - grandTotalChildrenHeight; - // 调整其叶子节点位置, 以非小计行为准 - const positionY = - find(hierarchy.getNodes(maxLevel), (node: Node) => !node.isTotalMeasure) - ?.y || 0; - - forEach(grandTotalChildren, (node: Node) => { - node.y = positionY; - }); - } - } - - /** - * @description adust the coordinate of subTotal nodes when there is just one value - * @param hierarchy Hierarchy - * @param isRowHeader boolean - */ - private adjustSubTotalNodesCoordinate( - hierarchy: Hierarchy, - isRowHeader?: boolean, - ) { - const subTotalNodes = hierarchy - .getNodes() - .filter((node) => node.isSubTotals); - - if (isEmpty(subTotalNodes)) { - return; - } - - const { maxLevel } = hierarchy; - - forEach(subTotalNodes, (subTotalNode: Node) => { - const subTotalChildNode = subTotalNode.children; - - if (isRowHeader) { - // 填充行总单元格宽度 - subTotalNode.width = getSubTotalNodeWidthOrHeightByLevel( - hierarchy.sampleNodesForAllLevels, - subTotalNode.level, - 'width', - ); - - // 调整其叶子节点位置 - forEach(subTotalChildNode, (node: Node) => { - node.x = hierarchy.getNodes(maxLevel)[0].x; - }); - } else { - // 填充列总单元格高度 - const totalHeight = getSubTotalNodeWidthOrHeightByLevel( - hierarchy.sampleNodesForAllLevels, - subTotalNode.level, - 'height', - ); - const subTotalNodeChildrenHeight = subTotalChildNode?.[0]?.height ?? 0; - - subTotalNode.height = totalHeight - subTotalNodeChildrenHeight; - // 调整其叶子节点位置, 以非小计行为准 - const positionY = - find( - hierarchy.getNodes(maxLevel), - (node: Node) => !node.isTotalMeasure, - )?.y || 0; - - forEach(subTotalChildNode, (node: Node) => { - node.y = positionY; - }); - } - }); - } - /** * 计算 grid 模式下 node 宽度 * @param node @@ -693,9 +600,9 @@ export class PivotFacet extends BaseFacet { return cellCustomWidth; } - if (this.spreadsheet.getLayoutWidthType() !== LayoutWidthTypes.Adaptive) { + if (this.spreadsheet.getLayoutWidthType() !== LayoutWidthType.Adaptive) { // compact or colAdaptive - return this.getCompactGridRowWidth(node); + return this.getCompactGridRowNodeWidth(node); } // adaptive @@ -727,7 +634,7 @@ export class PivotFacet extends BaseFacet { return Math.max( getCellWidth(dataCell!, this.getColLabelLength(col, rowLeafNodes)), - Math.floor((availableWidth - rowHeaderWidth) / colSize), + floor((availableWidth - rowHeaderWidth) / colSize), ); } @@ -798,15 +705,12 @@ export class PivotFacet extends BaseFacet { const colSize = Math.max(1, rowHeaderColSize + colHeaderColSize); if (!rowHeaderWidth) { - return Math.max( - getCellWidth(dataCell!), - Math.floor(availableWidth / colSize), - ); + return Math.max(getCellWidth(dataCell!), floor(availableWidth / colSize)); } return Math.max( getCellWidth(dataCell!), - Math.floor((availableWidth - rowHeaderWidth) / colHeaderColSize), + floor((availableWidth - rowHeaderWidth) / colHeaderColSize), ); } @@ -821,7 +725,7 @@ export class PivotFacet extends BaseFacet { // 1. 用户拖拽或手动指定的行头宽度优先级最高 const customRowWidth = this.getCellCustomSize(null, rowCell?.width!); - if (!isNil(customRowWidth)) { + if (isNumber(customRowWidth)) { return customRowWidth; } @@ -857,7 +761,7 @@ export class PivotFacet extends BaseFacet { * @param node 目标节点 * @returns 宽度 */ - private getCompactGridRowWidth(node: Node): number { + private getCompactGridRowNodeWidth(node: Node): number { const { bolderText: rowTextStyle, icon: rowIconStyle, @@ -916,6 +820,94 @@ export class PivotFacet extends BaseFacet { return Math.max(rowNodeWidth, fieldNameNodeWidth); } + private getCompactGridColNodeWidth(colNode: Node, rowLeafNodes: Node[]) { + const { + bolderText: colCellTextStyle, + cell: colCellStyle, + icon: colIconStyle, + } = this.spreadsheet.theme.colCell!; + const { text: dataCellTextStyle } = this.spreadsheet.theme.dataCell; + + // leaf node rough width + const cellFormatter = this.spreadsheet.dataSet.getFieldFormatter( + colNode.field, + ); + const leafNodeLabel = cellFormatter?.(colNode.value) ?? colNode.value; + const iconWidth = this.getExpectedCellIconWidth( + CellType.COL_CELL, + this.spreadsheet.isValueInCols() && + this.spreadsheet.options.showDefaultHeaderActionIcon!, + colIconStyle!, + ); + const leafNodeRoughWidth = + this.spreadsheet.measureTextWidthRoughly(leafNodeLabel) + iconWidth; + + // 采样 50 个 label,逐个计算找出最长的 label + let maxDataLabel = ''; + let maxDataLabelWidth = 0; + + for (let index = 0; index < LAYOUT_SAMPLE_COUNT; index++) { + const rowNode = rowLeafNodes[index]; + + if (rowNode) { + const cellData = (this.spreadsheet.dataSet as PivotDataSet).getCellData( + { + query: { ...colNode.query, ...rowNode.query }, + rowNode, + isTotals: + colNode.isTotals || + colNode.isTotalMeasure || + rowNode.isTotals || + rowNode.isTotalMeasure, + }, + ); + + if (cellData) { + // 总小计格子不一定有数据 + const valueData = cellData?.[VALUE_FIELD]; + const formattedValue = + this.spreadsheet.dataSet.getFieldFormatter(cellData[EXTRA_FIELD])?.( + valueData, + ) ?? valueData; + const cellLabel = `${formattedValue}`; + const cellLabelWidth = + this.spreadsheet.measureTextWidthRoughly(cellLabel); + + if (cellLabelWidth > maxDataLabelWidth) { + maxDataLabel = cellLabel; + maxDataLabelWidth = cellLabelWidth; + } + } + } + } + + // compare result + const isLeafNodeWidthLonger = leafNodeRoughWidth > maxDataLabelWidth; + const maxLabel = isLeafNodeWidthLonger ? leafNodeLabel : maxDataLabel; + const appendedWidth = isLeafNodeWidthLonger ? iconWidth : 0; + + DebuggerUtil.getInstance().logger( + 'Max Label In Col:', + colNode.field, + maxLabel, + maxDataLabelWidth, + ); + + // 取列头/数值字体最大的文本宽度 https://github.com/antvis/S2/issues/2385 + const maxTextWidth = this.spreadsheet.measureTextWidth(maxLabel, { + ...colCellTextStyle, + fontSize: Math.max(dataCellTextStyle.fontSize, colCellTextStyle.fontSize), + }); + + return ( + maxTextWidth + + colCellStyle!.padding!.left! + + colCellStyle!.padding!.right! + + colCellStyle!.verticalBorderWidth! * 2 + + appendedWidth + ); + } + public getViewCellHeights() { const rowLeafNodes = this.getRowLeafNodes(); @@ -951,4 +943,96 @@ export class PivotFacet extends BaseFacet { (element: SeriesNumberCell) => element instanceof SeriesNumberCell, ) as unknown[] as SeriesNumberCell[]; } + + protected updateFrozenGroupGrid(): void { + [FrozenGroupType.FROZEN_ROW].forEach((key) => { + if (!this.frozenGroupInfo[key].range) { + return; + } + + let cols: number[] = []; + let rows: number[] = []; + + if (key.toLowerCase().includes('row')) { + const [rowMin, rowMax] = this.frozenGroupInfo[key].range || []; + + cols = this.gridInfo.cols; + rows = getRowsForGrid(rowMin, rowMax, this.viewCellHeights); + } + + this[`${key}Group`].updateGrid( + { + cols, + rows, + }, + `${key}Group`, + ); + }); + } + + protected getFrozenOptions() { + return getFrozenRowCfgPivot( + this.spreadsheet.options, + this.layoutResult.rowNodes, + ); + } + + public enableFrozenFirstRow(): boolean { + return !!this.getFrozenOptions().rowCount; + } + + protected renderFrozenGroupSplitLine = (scrollX: number, scrollY: number) => { + this.foregroundGroup.getElementById(KEY_GROUP_FROZEN_SPLIT_LINE)?.remove(); + if (this.enableFrozenFirstRow()) { + // 在分页条件下需要额外处理 Y 轴滚动值 + const relativeScrollY = floor(scrollY - this.getPaginationScrollY()); + const splitLineGroup = this.foregroundGroup.appendChild( + new Group({ + id: KEY_GROUP_FROZEN_SPLIT_LINE, + style: { + zIndex: FRONT_GROUND_GROUP_FROZEN_Z_INDEX, + }, + }), + ); + + const { splitLine } = this.spreadsheet.theme; + + const horizontalBorderStyle: Partial<LineStyleProps> = { + lineWidth: SPLIT_LINE_WIDTH, + stroke: splitLine?.horizontalBorderColor, + opacity: splitLine?.horizontalBorderColorOpacity, + }; + + const cellRange = this.getCellRange(); + const y = + this.panelBBox.y + + this.getTotalHeightForRange(cellRange.start, cellRange.start); + const width = + this.cornerBBox.width + + Frame.getVerticalBorderWidth(this.spreadsheet) + + this.panelBBox.viewportWidth; + + renderLine(splitLineGroup, { + ...horizontalBorderStyle, + x1: 0, + x2: width, + y1: y, + y2: y, + }); + + if (splitLine!.showShadow && relativeScrollY > 0) { + splitLineGroup.appendChild( + new Rect({ + style: { + x: 0, + y, + width, + height: splitLine?.shadowWidth!, + fill: this.getShadowFill(90), + }, + }), + ); + } + } + }; } diff --git a/packages/s2-core/src/facet/table-facet.ts b/packages/s2-core/src/facet/table-facet.ts index 5cba6012e0..9393666d63 100644 --- a/packages/s2-core/src/facet/table-facet.ts +++ b/packages/s2-core/src/facet/table-facet.ts @@ -1,131 +1,70 @@ -import { Group, Rect, type LineStyleProps } from '@antv/g'; +import { Group } from '@antv/g'; import { isBoolean, isNumber, keys, last, maxBy, set } from 'lodash'; import { TableColCell, TableDataCell, TableSeriesNumberCell } from '../cell'; import { - FRONT_GROUND_GROUP_COL_FROZEN_Z_INDEX, KEY_GROUP_FROZEN_ROW_RESIZE_AREA, - KEY_GROUP_FROZEN_SPLIT_LINE, - KEY_GROUP_PANEL_FROZEN_BOTTOM, - KEY_GROUP_PANEL_FROZEN_COL, - KEY_GROUP_PANEL_FROZEN_ROW, - KEY_GROUP_PANEL_FROZEN_TOP, - KEY_GROUP_PANEL_FROZEN_TRAILING_COL, - KEY_GROUP_PANEL_FROZEN_TRAILING_ROW, KEY_GROUP_ROW_RESIZE_AREA, - LayoutWidthTypes, - PANEL_GROUP_FROZEN_GROUP_Z_INDEX, + LayoutWidthType, S2Event, SERIES_NUMBER_FIELD, - SPLIT_LINE_WIDTH, } from '../common/constant'; -import { FrozenCellGroupMap, FrozenGroupType } from '../common/constant/frozen'; import { DebuggerUtil } from '../common/debug'; import type { FilterParam, LayoutResult, ResizeInteractionOptions, - S2CellType, SortParams, TableSortParam, ViewMeta, - ViewMetaData, } from '../common/interface'; import type { TableDataSet } from '../data-set'; -import type { SimpleBBox } from '../engine'; -import { FrozenGroup } from '../group/frozen-group'; import type { SpreadSheet } from '../sheet-type'; import { getDataCellId } from '../utils/cell/data-cell'; import { getOccupiedWidthForTableCol } from '../utils/cell/table-col-cell'; import { getIndexRangeWithOffsets } from '../utils/facet'; -import { renderLine } from '../utils/g-renders'; import { getAllChildCells } from '../utils/get-all-child-cells'; -import { - getColsForGrid, - getFrozenRowsForGrid, - getRowsForGrid, -} from '../utils/grid'; -import type { Indexes, PanelIndexes } from '../utils/indexes'; import { getValidFrozenOptions } from '../utils/layout/frozen'; -import { BaseFacet } from './base-facet'; -import { CornerBBox } from './bbox/cornerBBox'; -import { Frame, type SeriesNumberHeader } from './header'; -import type { ColHeader } from './header/col'; -import { TableColHeader } from './header/table-col'; +import { floor } from '../utils/math'; +import { CornerBBox } from './bbox/corner-bbox'; +import { FrozenFacet } from './frozen-facet'; +import { ColHeader, Frame } from './header'; import { buildHeaderHierarchy } from './layout/build-header-hierarchy'; import { Hierarchy } from './layout/hierarchy'; import { layoutCoordinate } from './layout/layout-hooks'; import { Node } from './layout/node'; -import { - calculateFrozenCornerCells, - calculateInViewIndexes, - getFrozenDataCellType, - getFrozenLeafNodesCount, - isFrozenTrailingRow, - splitInViewIndexesWithFrozen, - translateGroup, -} from './utils'; - -export class TableFacet extends BaseFacet { - public declare rowOffsets: number[]; - - public frozenGroupInfo: Record< - FrozenGroupType, - { - width?: number; - height?: number; - range?: number[]; - } - > = { - [FrozenGroupType.FROZEN_COL]: { - width: 0, - }, - [FrozenGroupType.FROZEN_ROW]: { - height: 0, - }, - [FrozenGroupType.FROZEN_TRAILING_ROW]: { - height: 0, - }, - [FrozenGroupType.FROZEN_TRAILING_COL]: { - width: 0, - }, - }; - - public panelScrollGroupIndexes: Indexes = [] as unknown as Indexes; +import { getFrozenLeafNodesCount, isFrozenTrailingRow } from './utils'; +import { TableColHeader } from './header/table-col'; +export class TableFacet extends FrozenFacet { public constructor(spreadsheet: SpreadSheet) { super(spreadsheet); - this.spreadsheet.on(S2Event.RANGE_SORT, this.onSortHandler); this.spreadsheet.on(S2Event.RANGE_FILTER, this.onFilterHandler); } - protected override initPanelGroups(): void { - super.initPanelGroups(); - [ - this.frozenRowGroup, - this.frozenColGroup, - this.frozenTrailingRowGroup, - this.frozenTrailingColGroup, - this.frozenTopGroup, - this.frozenBottomGroup, - ] = [ - KEY_GROUP_PANEL_FROZEN_ROW, - KEY_GROUP_PANEL_FROZEN_COL, - KEY_GROUP_PANEL_FROZEN_TRAILING_ROW, - KEY_GROUP_PANEL_FROZEN_TRAILING_COL, - KEY_GROUP_PANEL_FROZEN_TOP, - KEY_GROUP_PANEL_FROZEN_BOTTOM, - ].map((name) => { - const frozenGroup = new FrozenGroup({ - name, - zIndex: PANEL_GROUP_FROZEN_GROUP_Z_INDEX, - s2: this.spreadsheet, - }); + public init() { + this.initRowOffsets(); + super.init(); + } - this.panelGroup.appendChild(frozenGroup); + protected initRowOffsets() { + const heightByField = + this.spreadsheet.options.style?.rowCell?.heightByField; - return frozenGroup; - }); + if (keys(heightByField!).length) { + const data = this.spreadsheet.dataSet.getDisplayDataSet(); + + this.rowOffsets = [0]; + let lastOffset = 0; + + data.forEach((_, rowIndex) => { + const currentHeight = this.getCellHeightByRowIndex(rowIndex); + const currentOffset = lastOffset + currentHeight; + + this.rowOffsets.push(currentOffset); + lastOffset = currentOffset; + }); + } } private onSortHandler = (sortParams: SortParams) => { @@ -188,7 +127,7 @@ export class TableFacet extends BaseFacet { if (oldIndex !== -1) { if (unFilter) { // remove filter params on current key if passed an empty filterValues field - oldConfig.splice(oldIndex); + oldConfig.splice(oldIndex, 1); } else { // if filter with same key already exists, replace it oldConfig[oldIndex] = params; @@ -210,27 +149,15 @@ export class TableFacet extends BaseFacet { return this.spreadsheet.theme.dataCell?.cell; } - override clearAllGroup() { - super.clearAllGroup(); - this.frozenRowGroup.removeChildren(); - this.frozenColGroup.removeChildren(); - this.frozenTrailingRowGroup.removeChildren(); - this.frozenTrailingColGroup.removeChildren(); - this.frozenTopGroup.removeChildren(); - this.frozenBottomGroup.removeChildren(); - } - public destroy(): void { super.destroy(); - const s2 = this.spreadsheet; - - s2.off(S2Event.RANGE_SORT, this.onSortHandler); - s2.off(S2Event.RANGE_FILTER, this.onFilterHandler); + this.spreadsheet.off(S2Event.RANGE_SORT, this.onSortHandler); + this.spreadsheet.off(S2Event.RANGE_FILTER, this.onFilterHandler); } protected calculateCornerBBox() { const { colsHierarchy } = this.getLayoutResult(); - const height = Math.floor(colsHierarchy.height); + const height = floor(colsHierarchy.height); this.cornerBBox = new CornerBBox(this); @@ -278,19 +205,18 @@ export class TableFacet extends BaseFacet { const { showSeriesNumber } = this.spreadsheet.options; const cellHeight = this.getCellHeightByRowIndex(rowIndex); const cellRange = this.getCellRange(); - const { trailingRowCount: frozenTrailingRowCount = 0 } = - getValidFrozenOptions( - this.spreadsheet.options.frozen!, - colLeafNodes.length, - cellRange.end - cellRange.start + 1, - ); + const { trailingRowCount = 0 } = getValidFrozenOptions( + this.spreadsheet.options.frozen!, + colLeafNodes.length, + cellRange.end - cellRange.start + 1, + ); - let data: ViewMetaData | number; + let data; const x = colNode.x; let y = this.viewCellHeights.getCellOffsetY(rowIndex); - if (isFrozenTrailingRow(rowIndex, cellRange.end, frozenTrailingRowCount)) { + if (isFrozenTrailingRow(rowIndex, cellRange.end, trailingRowCount)) { y = this.panelBBox.height - this.getTotalHeightForRange(rowIndex, cellRange.end); @@ -301,7 +227,7 @@ export class TableFacet extends BaseFacet { } else { data = this.spreadsheet.dataSet.getCellData({ query: { - col: colNode.field, + field: colNode.field, rowIndex, }, }); @@ -331,7 +257,7 @@ export class TableFacet extends BaseFacet { const { dataCell } = this.spreadsheet.options.style!; const { showSeriesNumber } = this.spreadsheet.options; - if (this.spreadsheet.getLayoutWidthType() !== LayoutWidthTypes.Compact) { + if (this.spreadsheet.getLayoutWidthType() !== LayoutWidthType.Compact) { const seriesNumberWidth = this.getSeriesNumberWidth(); const colHeaderColSize = colLeafNodes.length - (showSeriesNumber ? 1 : 0); const canvasW = @@ -339,16 +265,23 @@ export class TableFacet extends BaseFacet { seriesNumberWidth - Frame.getVerticalBorderWidth(this.spreadsheet); - // TODO: 向下取整, 导致单元格未撑满 canvas, 在冻结情况下会有问题, 代冻结重构后解决 + // TODO: 向下取整, 导致单元格未撑满 canvas, 在冻结情况下会有问题, 待冻结重构后解决 return Math.max( dataCell?.width!, - Math.floor(canvasW / Math.max(1, colHeaderColSize)), + floor(canvasW / Math.max(1, colHeaderColSize)), ); } return dataCell?.width ?? 0; } + public getContentHeight(): number { + const { getTotalHeight } = this.getViewCellHeights(); + const { colsHierarchy } = this.layoutResult; + + return getTotalHeight() + colsHierarchy.height; + } + protected getColNodeHeight(colNode: Node, colsHierarchy: Hierarchy) { const colCell = new TableColCell(colNode, this.spreadsheet, { shallowRender: true, @@ -397,19 +330,20 @@ export class TableFacet extends BaseFacet { } const topLevelNodes = colsHierarchy.getNodes(0); - const { trailingColCount: frozenTrailingColCount = 0 } = - getValidFrozenOptions( - this.spreadsheet.options.frozen!, - topLevelNodes.length, - ); + const { trailingColCount = 0 } = getValidFrozenOptions( + this.spreadsheet.options.frozen!, + topLevelNodes.length, + ); preLeafNode = Node.blankNode(); - const { width } = this.getCanvasSize(); + const width = + this.getCanvasSize().width - + Frame.getVerticalBorderWidth(this.spreadsheet); - if (frozenTrailingColCount > 0) { + if (trailingColCount > 0) { const { trailingColCount: realFrozenTrailingColCount } = - getFrozenLeafNodesCount(topLevelNodes, 0, frozenTrailingColCount); + getFrozenLeafNodesCount(topLevelNodes, 0, trailingColCount); const leafNodes = allNodes.filter((node) => node.isLeaf); for (let i = 1; i <= realFrozenTrailingColCount; i++) { @@ -482,13 +416,13 @@ export class TableFacet extends BaseFacet { let colWidth: number; - if (layoutWidthType === LayoutWidthTypes.Compact) { - const datas = dataSet.getDisplayDataSet(); + if (layoutWidthType === LayoutWidthType.Compact) { + const data = dataSet.getDisplayDataSet(); const formatter = dataSet.getFieldFormatter(colNode.field); // 采样前50,找出表身最长的数据 const maxLabel = maxBy( - datas + data ?.slice(0, 50) .map( (data) => @@ -536,41 +470,7 @@ export class TableFacet extends BaseFacet { return colWidth; } - protected getDefaultCellHeight(): number { - return this.getRowCellHeight(null as unknown as Node); - } - - public getCellHeightByRowIndex(rowIndex: number) { - if (this.rowOffsets) { - return this.getRowCellHeight({ id: String(rowIndex) } as Node); - } - - return this.getDefaultCellHeight(); - } - - protected initRowOffsets() { - const heightByField = - this.spreadsheet.options.style?.rowCell?.heightByField; - - if (keys(heightByField!).length) { - const data = this.spreadsheet.dataSet.getDisplayDataSet(); - - this.rowOffsets = [0]; - let lastOffset = 0; - - data.forEach((_, rowIndex) => { - const currentHeight = this.getCellHeightByRowIndex(rowIndex); - const currentOffset = lastOffset + currentHeight; - - this.rowOffsets.push(currentOffset); - lastOffset = currentOffset; - }); - } - } - public getViewCellHeights() { - this.initRowOffsets(); - const defaultCellHeight = this.getDefaultCellHeight(); return { @@ -594,13 +494,7 @@ export class TableFacet extends BaseFacet { return this.rowOffsets[offset]; } - let totalOffset = 0; - - for (let index = 0; index < offset; index++) { - totalOffset += defaultCellHeight; - } - - return totalOffset; + return offset * defaultCellHeight; }, getTotalLength: () => this.spreadsheet.dataSet.getDisplayDataSet().length, @@ -614,12 +508,12 @@ export class TableFacet extends BaseFacet { ); } - const yMin = Math.floor(minHeight / defaultCellHeight); + const yMin = floor(minHeight / defaultCellHeight, 0); // 防止数组index溢出导致报错 const yMax = maxHeight % defaultCellHeight === 0 ? maxHeight / defaultCellHeight - 1 - : Math.floor(maxHeight / defaultCellHeight); + : floor(maxHeight / defaultCellHeight, 0); return { start: Math.max(0, yMin), @@ -629,383 +523,6 @@ export class TableFacet extends BaseFacet { }; } - protected translateFrozenGroups = () => { - const { scrollY, scrollX } = this.getScrollOffset(); - const paginationScrollY = this.getPaginationScrollY(); - - const { x, y } = this.panelBBox; - - translateGroup(this.frozenTopGroup, x, y - paginationScrollY); - translateGroup(this.frozenBottomGroup, x, y); - - translateGroup(this.frozenRowGroup, x - scrollX, y - paginationScrollY); - translateGroup(this.frozenTrailingRowGroup, x - scrollX, y); - - translateGroup(this.frozenColGroup, x, y - scrollY - paginationScrollY); - translateGroup( - this.frozenTrailingColGroup, - x, - y - scrollY - paginationScrollY, - ); - }; - - public getTotalHeightForRange = (start: number, end: number) => { - if (start < 0 || end < 0) { - return 0; - } - - if (this.rowOffsets) { - return this.rowOffsets[end + 1] - this.rowOffsets[start]; - } - - let totalHeight = 0; - - for (let index = start; index < end + 1; index++) { - const height = this.getDefaultCellHeight(); - - totalHeight += height; - } - - return totalHeight; - }; - - private getShadowFill = (angle: number) => { - const { splitLine } = this.spreadsheet.theme; - - return `l (${angle}) 0:${splitLine?.shadowColors?.left} 1:${splitLine?.shadowColors?.right}`; - }; - - // eslint-disable-next-line max-lines-per-function - protected renderFrozenGroupSplitLine = (scrollX: number, scrollY: number) => { - const { - width: panelWidth, - height: panelHeight, - viewportWidth, - viewportHeight, - x: panelBBoxStartX, - y: panelBBoxStartY, - } = this.panelBBox; - - const topLevelColNodes = this.getTopLevelColNodes(); - const cellRange = this.getCellRange(); - const dataLength = cellRange.end - cellRange.start; - const { - rowCount: frozenRowCount = 0, - colCount: frozenColCount = 0, - trailingColCount: frozenTrailingColCount = 0, - trailingRowCount: frozenTrailingRowCount = 0, - } = getValidFrozenOptions( - this.spreadsheet.options.frozen!, - topLevelColNodes.length, - dataLength, - ); - - // 在分页条件下需要额外处理 Y 轴滚动值 - const relativeScrollY = Math.floor(scrollY - this.getPaginationScrollY()); - - // scroll boundary - const maxScrollX = Math.max(0, last(this.viewCellWidths)! - viewportWidth); - const maxScrollY = Math.max( - 0, - this.viewCellHeights.getCellOffsetY(cellRange.end + 1) - - this.viewCellHeights.getCellOffsetY(cellRange.start) - - viewportHeight, - ); - - // remove previous split line group - this.foregroundGroup.getElementById(KEY_GROUP_FROZEN_SPLIT_LINE)?.remove(); - - const { splitLine } = this.spreadsheet.theme; - const splitLineGroup = this.foregroundGroup.appendChild( - new Group({ - id: KEY_GROUP_FROZEN_SPLIT_LINE, - style: { - zIndex: FRONT_GROUND_GROUP_COL_FROZEN_Z_INDEX, - }, - }), - ); - - const verticalBorderStyle: Partial<LineStyleProps> = { - lineWidth: SPLIT_LINE_WIDTH, - stroke: splitLine?.verticalBorderColor, - opacity: splitLine?.verticalBorderColorOpacity, - }; - - const horizontalBorderStyle: Partial<LineStyleProps> = { - lineWidth: SPLIT_LINE_WIDTH, - stroke: splitLine?.horizontalBorderColor, - opacity: splitLine?.horizontalBorderColorOpacity, - }; - - const frameVerticalBorderWidth = Frame.getVerticalBorderWidth( - this.spreadsheet, - ); - - if (frozenColCount > 0) { - const x = topLevelColNodes.reduce((prev, item, idx) => { - if (idx < frozenColCount) { - return prev + item.width; - } - - return prev; - }, 0); - - const height = frozenTrailingRowCount > 0 ? panelHeight : viewportHeight; - - renderLine(splitLineGroup, { - ...verticalBorderStyle, - x1: x + panelBBoxStartX, - x2: x + panelBBoxStartX, - y1: panelBBoxStartY, - y2: panelBBoxStartY + height, - }); - - if (splitLine?.showShadow && scrollX > 0) { - splitLineGroup.appendChild( - new Rect({ - style: { - x: x + panelBBoxStartX, - y: panelBBoxStartY, - width: splitLine?.shadowWidth!, - height, - fill: this.getShadowFill(0), - }, - }), - ); - } - } - - if (frozenRowCount > 0) { - const y = - panelBBoxStartY + - this.getTotalHeightForRange( - cellRange.start, - cellRange.start + frozenRowCount - 1, - ); - const width = frozenTrailingColCount > 0 ? panelWidth : viewportWidth; - - renderLine(splitLineGroup, { - ...horizontalBorderStyle, - x1: 0, - x2: width + frameVerticalBorderWidth, - y1: y, - y2: y, - }); - - if (splitLine?.showShadow && relativeScrollY > 0) { - splitLineGroup.appendChild( - new Rect({ - style: { - x: 0, - y, - width: width + frameVerticalBorderWidth, - height: splitLine?.shadowWidth!, - fill: this.getShadowFill(90), - }, - }), - ); - } - } - - if (frozenTrailingColCount > 0) { - const { x } = - topLevelColNodes[topLevelColNodes.length - frozenTrailingColCount]; - const height = frozenTrailingRowCount ? panelHeight : viewportHeight; - - renderLine(splitLineGroup, { - ...verticalBorderStyle, - x1: x, - x2: x, - y1: panelBBoxStartY, - y2: panelBBoxStartY + height, - }); - - if ( - splitLine?.showShadow && - Math.floor(scrollX) < Math.floor(maxScrollX) - ) { - splitLineGroup.appendChild( - new Rect({ - style: { - x: x - splitLine.shadowWidth!, - y: panelBBoxStartY, - width: splitLine.shadowWidth!, - height, - fill: this.getShadowFill(180), - }, - }), - ); - } - } - - if (frozenTrailingRowCount > 0) { - const y = - this.panelBBox.maxY - - this.getTotalHeightForRange( - cellRange.end - frozenTrailingRowCount + 1, - cellRange.end, - ); - const width = frozenTrailingColCount > 0 ? panelWidth : viewportWidth; - - renderLine(splitLineGroup, { - ...horizontalBorderStyle, - x1: 0, - x2: width + frameVerticalBorderWidth, - y1: y, - y2: y, - }); - - if (splitLine?.showShadow && relativeScrollY < Math.floor(maxScrollY)) { - splitLineGroup.appendChild( - new Rect({ - style: { - x: 0, - y: y - splitLine.shadowWidth!, - width: width + frameVerticalBorderWidth, - height: splitLine.shadowWidth!, - fill: this.getShadowFill(270), - }, - }), - ); - } - } - }; - - protected renderFrozenPanelCornerGroup = () => { - const topLevelNodes = this.getTopLevelColNodes(); - const cellRange = this.getCellRange(); - - const { - rowCount: frozenRowCount = 0, - colCount: frozenColCount = 0, - trailingRowCount: frozenTrailingRowCount = 0, - trailingColCount: frozenTrailingColCount = 0, - } = getValidFrozenOptions( - this.spreadsheet.options.frozen!, - topLevelNodes.length, - cellRange.end - cellRange.start + 1, - ); - - const { colCount, trailingColCount } = getFrozenLeafNodesCount( - topLevelNodes, - frozenColCount, - frozenTrailingColCount, - ); - - const result = calculateFrozenCornerCells( - { - rowCount: frozenRowCount, - colCount, - trailingRowCount: frozenTrailingRowCount, - trailingColCount, - }, - this.getColLeafNodes().length, - cellRange, - ); - - Object.keys(result).forEach((key) => { - const cells = result[key]; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const group = this[FrozenCellGroupMap[key]] as Group; - - if (group) { - cells.forEach((cell) => { - this.addFrozenCell(cell.x, cell.y, group); - }); - } - }); - }; - - addFrozenCell = (colIndex: number, rowIndex: number, group: Group) => { - const viewMeta = this.getCellMeta(rowIndex, colIndex); - - if (viewMeta) { - viewMeta.isFrozenCorner = true; - const cell = this.spreadsheet.options.dataCell?.(viewMeta)!; - - group.appendChild(cell); - } - }; - - getRealFrozenColumns = ( - frozenColCount: number, - frozenTrailingColCount: number, - ): { colCount: number; trailingColCount: number } => { - if (frozenColCount || frozenTrailingColCount) { - const nodes = this.getTopLevelColNodes(); - - return getFrozenLeafNodesCount( - nodes, - frozenColCount, - frozenTrailingColCount, - ); - } - - return { - colCount: frozenColCount, - trailingColCount: frozenTrailingColCount, - }; - }; - - addDataCell = (cell: S2CellType<ViewMeta>) => { - const { - rowCount: frozenRowCount = 0, - colCount: frozenColCount = 0, - trailingRowCount: frozenTrailingRowCount = 0, - trailingColCount: frozenTrailingColCount = 0, - } = this.spreadsheet.options.frozen!; - - const colLength = this.getColNodes().length; - const cellRange = this.getCellRange(); - const { colCount, trailingColCount } = this.getRealFrozenColumns( - frozenColCount, - frozenTrailingColCount, - ); - - const frozenCellType = getFrozenDataCellType( - cell.getMeta(), - { - rowCount: frozenRowCount, - trailingRowCount: frozenTrailingRowCount, - colCount, - trailingColCount, - }, - colLength, - cellRange, - ); - - const groupName = FrozenCellGroupMap[frozenCellType]; - - if (groupName) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const group = this[groupName] as Group; - - group.appendChild(cell); - } - }; - - protected getColHeader(): ColHeader { - if (!this.columnHeader) { - const { x, width, viewportHeight, viewportWidth } = this.panelBBox; - - return new TableColHeader({ - width, - height: this.cornerBBox.height, - viewportWidth, - viewportHeight, - cornerWidth: this.cornerBBox.width, - position: { x, y: 0 }, - nodes: this.getColNodes(), - sortParam: this.spreadsheet.store.get('sortParam'), - spreadsheet: this.spreadsheet, - }); - } - - return this.columnHeader; - } - protected updateRowResizeArea() { const { resize } = this.spreadsheet.options.interaction!; @@ -1042,299 +559,32 @@ export class TableFacet extends BaseFacet { }); } - public render() { - this.calculateFrozenGroupInfo(); - this.renderFrozenPanelCornerGroup(); - super.render(); - } - - private getFrozenOptions = () => { - const colLength = this.getColLeafNodes().length; - const cellRange = this.getCellRange(); - - return getValidFrozenOptions( - this.spreadsheet.options.frozen!, - colLength, - cellRange.end - cellRange.start + 1, - ); - }; - - public calculateFrozenGroupInfo() { - const { - colCount: frozenColCount = 0, - rowCount: frozenRowCount = 0, - trailingColCount: frozenTrailingColCount = 0, - trailingRowCount: frozenTrailingRowCount = 0, - } = this.getFrozenOptions(); - - const topLevelColNodes = this.getTopLevelColNodes(); - const viewCellHeights = this.viewCellHeights; - const cellRange = this.getCellRange(); - const { frozenCol, frozenTrailingCol, frozenRow, frozenTrailingRow } = - this.frozenGroupInfo; - - if (frozenColCount > 0) { - frozenCol.width = - topLevelColNodes[frozenColCount - 1].x + - topLevelColNodes[frozenColCount - 1].width - - 0; - frozenCol.range = [0, frozenColCount - 1]; - } - - if (frozenRowCount > 0) { - frozenRow.height = - viewCellHeights.getCellOffsetY(cellRange.start + frozenRowCount) - - viewCellHeights.getCellOffsetY(cellRange.start); - frozenRow.range = [cellRange.start, cellRange.start + frozenRowCount - 1]; - } - - if (frozenTrailingColCount > 0) { - frozenTrailingCol.width = - topLevelColNodes[topLevelColNodes.length - 1].x - - topLevelColNodes[topLevelColNodes.length - frozenTrailingColCount].x + - topLevelColNodes[topLevelColNodes.length - 1].width; - frozenTrailingCol.range = [ - topLevelColNodes.length - frozenTrailingColCount, - topLevelColNodes.length - 1, - ]; - } - - if (frozenTrailingRowCount > 0) { - frozenTrailingRow.height = - viewCellHeights.getCellOffsetY(cellRange.end + 1) - - viewCellHeights.getCellOffsetY( - cellRange.end + 1 - frozenTrailingRowCount, - ); - frozenTrailingRow.range = [ - cellRange.end - frozenTrailingRowCount + 1, - cellRange.end, - ]; - } - } - protected getRowHeader() { return null; } - protected getSeriesNumberHeader(): SeriesNumberHeader | null { - return null; - } - - protected translateRelatedGroups( - scrollX: number, - scrollY: number, - hRowScroll: number, - ) { - super.translateRelatedGroups(scrollX, scrollY, hRowScroll); - this.translateFrozenGroups(); - this.updateRowResizeArea(); - this.renderFrozenGroupSplitLine(scrollX, scrollY); - } - - public calculateXYIndexes(scrollX: number, scrollY: number): PanelIndexes { - const colLength = this.getColLeafNodes().length; - const cellRange = this.getCellRange(); - - const { viewportHeight: height, viewportWidth: width } = this.panelBBox; - - const { - colCount: frozenColCount = 0, - rowCount: frozenRowCount = 0, - trailingColCount: frozenTrailingColCount = 0, - trailingRowCount: frozenTrailingRowCount = 0, - } = this.getFrozenOptions(); - - const finalViewport: SimpleBBox = { - width, - height, - x: 0, - y: 0, - }; - - if (frozenTrailingColCount > 0 || frozenColCount > 0) { - const { frozenTrailingCol, frozenCol } = this.frozenGroupInfo; - - finalViewport.width -= frozenTrailingCol.width! + frozenCol.width!; - finalViewport.x += frozenCol.width!; - } - - if (frozenTrailingRowCount > 0 || frozenRowCount > 0) { - const { frozenRow, frozenTrailingRow } = this.frozenGroupInfo; - - // canvas 高度小于 row height 和 trailingRow height 的时候 height 为 0 - if ( - finalViewport.height < - frozenRow.height! + frozenTrailingRow.height! - ) { - finalViewport.height = 0; - finalViewport.y = 0; - } else { - finalViewport.height -= frozenRow.height! + frozenTrailingRow.height!; - finalViewport.y += frozenRow.height!; - } - } - - const indexes = calculateInViewIndexes({ - scrollX, - scrollY, - widths: this.viewCellWidths, - heights: this.viewCellHeights, - viewport: finalViewport, - rowRemainWidth: this.getRealScrollX(this.cornerBBox.width), - }); - - this.panelScrollGroupIndexes = indexes; - - const { colCount, trailingColCount } = this.getRealFrozenColumns( - frozenColCount, - frozenTrailingColCount, - ); - - return splitInViewIndexesWithFrozen( - indexes, - { - colCount, - rowCount: frozenRowCount, - trailingColCount, - trailingRowCount: frozenTrailingRowCount, - }, - colLength, - cellRange, - ); - } - - // 对 panelScrollGroup 以及四个方向的 frozenGroup 做 Clip,避免有透明度时冻结分组和滚动分组展示重叠 - protected clip(scrollX: number, scrollY: number) { - const paginationScrollY = this.getPaginationScrollY(); - const { - frozenRowGroup, - frozenColGroup, - frozenTrailingColGroup, - frozenTrailingRowGroup, - } = this; - const frozenColGroupWidth = frozenColGroup.getBBox().width; - const frozenRowGroupHeight = frozenRowGroup.getBBox().height; - const frozenTrailingColBBox = frozenTrailingColGroup.getBBox(); - const frozenTrailingRowGroupHeight = - frozenTrailingRowGroup.getBBox().height; - const panelScrollGroupWidth = - this.panelBBox.width - - frozenColGroupWidth - - frozenTrailingColGroup.getBBox().width; - const panelScrollGroupHeight = - this.panelBBox.height - - frozenRowGroupHeight - - frozenTrailingRowGroupHeight; - - frozenRowGroup.style.clipPath = new Rect({ - style: { - x: scrollX + frozenColGroupWidth, - y: paginationScrollY, - width: panelScrollGroupWidth, - height: frozenRowGroupHeight, - }, - }); - - frozenTrailingRowGroup.style.clipPath = new Rect({ - style: { - x: scrollX + frozenColGroupWidth, - y: frozenTrailingRowGroup.getBBox().top, - width: panelScrollGroupWidth, - height: frozenTrailingRowGroupHeight, - }, - }); - - const colClipArea = { - y: scrollY + frozenRowGroupHeight, - height: panelScrollGroupHeight, - }; - - frozenColGroup.style.clipPath = new Rect({ - style: { - ...colClipArea, - x: 0, - width: frozenColGroupWidth, - }, - }); - - frozenTrailingColGroup.style.clipPath = new Rect({ - style: { - ...colClipArea, - x: frozenTrailingColBBox.left, - width: frozenTrailingColBBox.width, - }, - }); - - const rowResizeGroup = this.foregroundGroup.getElementById<Group>( - KEY_GROUP_ROW_RESIZE_AREA, - ); + protected getColHeader(): ColHeader { + if (!this.columnHeader) { + const { x, width, viewportHeight, viewportWidth } = this.panelBBox; - if (rowResizeGroup) { - rowResizeGroup.style.clipPath = new Rect({ - style: { - x: 0, - y: this.panelBBox.y + frozenRowGroupHeight, - width: - this.panelBBox.width + - Frame.getVerticalBorderWidth(this.spreadsheet), - height: panelScrollGroupHeight, - }, + return new TableColHeader({ + width, + height: this.cornerBBox.height, + viewportWidth, + viewportHeight, + cornerWidth: this.cornerBBox.width, + position: { x, y: 0 }, + nodes: this.getColNodes(), + sortParam: this.spreadsheet.store.get('sortParam'), + spreadsheet: this.spreadsheet, }); } - } - public getTopLevelColNodes() { - return this.getColNodes(0); + return this.columnHeader; } - public updatePanelScrollGroup() { - super.updatePanelScrollGroup(); - - [ - FrozenGroupType.FROZEN_COL, - FrozenGroupType.FROZEN_ROW, - FrozenGroupType.FROZEN_TRAILING_COL, - FrozenGroupType.FROZEN_TRAILING_ROW, - ].forEach((key) => { - if (!this.frozenGroupInfo[key].range) { - return; - } - - let cols: number[] = []; - let rows: number[] = []; - - if (key.toLowerCase().includes('row')) { - const [rowMin, rowMax] = this.frozenGroupInfo[key].range || []; - - cols = this.gridInfo.cols; - rows = getRowsForGrid(rowMin, rowMax, this.viewCellHeights); - - if (key === FrozenGroupType.FROZEN_TRAILING_ROW) { - const { top } = this.frozenTrailingRowGroup.getBBox(); - - rows = getFrozenRowsForGrid( - rowMin, - rowMax, - Math.ceil(top), - this.viewCellHeights, - ); - } - } else { - const [colMin, colMax] = this.frozenGroupInfo[key].range || []; - const nodes = this.getTopLevelColNodes(); - - cols = getColsForGrid(colMin, colMax, nodes); - rows = this.gridInfo.rows; - } - - this[`${key}Group`].updateGrid( - { - cols, - rows, - }, - `${key}Group`, - ); - }); + protected getSeriesNumberHeader() { + return null; } /** @@ -1342,9 +592,8 @@ export class TableFacet extends BaseFacet { * @description 明细表序号单元格是基于 DataCell 实现 */ public getSeriesNumberCells(): TableSeriesNumberCell[] { - // @ts-ignore - return this.getDataCells().filter( - (cell) => cell instanceof TableSeriesNumberCell, - ); + return this.getDataCells().filter((cell) => { + return cell.getMeta().valueField === SERIES_NUMBER_FIELD; + }) as TableSeriesNumberCell[]; } } diff --git a/packages/s2-core/src/facet/utils.ts b/packages/s2-core/src/facet/utils.ts index 20cbd631bb..d17a8c33e3 100644 --- a/packages/s2-core/src/facet/utils.ts +++ b/packages/s2-core/src/facet/utils.ts @@ -1,16 +1,19 @@ import type { Group } from '@antv/g'; -import { findIndex, isNil } from 'lodash'; +import { findIndex, isEmpty, isNil } from 'lodash'; import type { FrozenCellIndex } from '../common/constant/frozen'; import { FrozenCellType } from '../common/constant/frozen'; import { DEFAULT_PAGE_INDEX } from '../common/constant/pagination'; import type { CustomHeaderFields, + Fields, Pagination, + S2Options, + S2PivotSheetFrozenOptions, S2TableSheetFrozenOptions, ScrollSpeedRatio, } from '../common/interface'; -import type { SimpleBBox } from '../engine'; import type { Indexes } from '../utils/indexes'; +import type { SimpleBBox } from '../engine'; import type { ViewCellHeights } from './layout/interface'; import type { Node } from './layout/node'; @@ -272,54 +275,50 @@ export const splitInViewIndexesWithFrozen = ( }, ) => { const { - colCount: frozenColCount = 0, - rowCount: frozenRowCount = 0, - trailingColCount: frozenTrailingColCount = 0, - trailingRowCount: frozenTrailingRowCount = 0, + colCount = 0, + rowCount = 0, + trailingColCount = 0, + trailingRowCount = 0, } = frozenOptions; const centerIndexes: Indexes = [...indexes]; // Cut off frozen cells from centerIndexes - if (isFrozenCol(centerIndexes[0], frozenColCount)) { - centerIndexes[0] = frozenColCount; + if (isFrozenCol(centerIndexes[0], colCount)) { + centerIndexes[0] = colCount; } - if ( - isFrozenTrailingCol(centerIndexes[1], frozenTrailingColCount, colLength) - ) { - centerIndexes[1] = colLength - frozenTrailingColCount - 1; + if (isFrozenTrailingCol(centerIndexes[1], trailingColCount, colLength)) { + centerIndexes[1] = colLength - trailingColCount - 1; } - if (isFrozenRow(centerIndexes[2], cellRange.start, frozenRowCount)) { - centerIndexes[2] = cellRange.start + frozenRowCount; + if (isFrozenRow(centerIndexes[2], cellRange.start, rowCount)) { + centerIndexes[2] = cellRange.start + rowCount; } - if ( - isFrozenTrailingRow(centerIndexes[3], cellRange.end, frozenTrailingRowCount) - ) { - centerIndexes[3] = cellRange.end - frozenTrailingRowCount; + if (isFrozenTrailingRow(centerIndexes[3], cellRange.end, trailingRowCount)) { + centerIndexes[3] = cellRange.end - trailingRowCount; } // Calculate indexes for four frozen groups const frozenRowIndexes: Indexes = [...centerIndexes]; frozenRowIndexes[2] = cellRange.start; - frozenRowIndexes[3] = cellRange.start + frozenRowCount - 1; + frozenRowIndexes[3] = cellRange.start + rowCount - 1; const frozenColIndexes: Indexes = [...centerIndexes]; frozenColIndexes[0] = 0; - frozenColIndexes[1] = frozenColCount - 1; + frozenColIndexes[1] = colCount - 1; const frozenTrailingRowIndexes: Indexes = [...centerIndexes]; - frozenTrailingRowIndexes[2] = cellRange.end + 1 - frozenTrailingRowCount; + frozenTrailingRowIndexes[2] = cellRange.end + 1 - trailingRowCount; frozenTrailingRowIndexes[3] = cellRange.end; const frozenTrailingColIndexes: Indexes = [...centerIndexes]; - frozenTrailingColIndexes[0] = colLength - frozenTrailingColCount; + frozenTrailingColIndexes[0] = colLength - trailingColCount; frozenTrailingColIndexes[1] = colLength - 1; return { @@ -355,17 +354,15 @@ export const getCellRange = ( /** * 给定一个一层的 node 数组以及左右固定列的数量,计算出实际固定列(叶子节点)的数量 * @param nodes - * @param frozenColCount - * @param frozenTrailingColCount + * @param colCount + * @param trailingColCount * @returns {colCount, trailingColCount} */ export const getFrozenLeafNodesCount = ( nodes: Node[], - frozenColCount: number, - frozenTrailingColCount: number, + colCount: number, + trailingColCount: number, ): { colCount: number; trailingColCount: number } => { - let colCount = frozenColCount; - let trailingColCount = frozenTrailingColCount; const getLeafNodesCount = (node: Node) => { if (node.isLeaf) { return 1; @@ -382,17 +379,17 @@ export const getFrozenLeafNodesCount = ( return 0; }; - if (frozenColCount) { - colCount = nodes.slice(0, frozenColCount).reduce((count, node) => { + if (colCount) { + colCount = nodes.slice(0, colCount).reduce((count, node) => { count += getLeafNodesCount(node); return count; }, 0); } - if (frozenTrailingColCount) { + if (trailingColCount) { trailingColCount = nodes - .slice(nodes.length - frozenTrailingColCount) + .slice(nodes.length - trailingColCount) .reduce((count, node) => { count += getLeafNodesCount(node); @@ -473,3 +470,55 @@ export const getLeftLeafNode = (node: Node): Node => { return firstNode.isLeaf ? firstNode : getLeftLeafNode(firstNode); }; +/** + * fields 的 rows、columns、values 值都为空时,返回 true + * @param {Fields} fields + * @return {boolean} + */ +export const areAllFieldsEmpty = (fields: Fields) => { + return ( + isEmpty(fields.rows) && isEmpty(fields.columns) && isEmpty(fields.values) + ); +}; + +/** + * get frozen options pivot-sheet (business limit) + * @param options + * @returns + */ +export const getFrozenRowCfgPivot = ( + options: S2Options, + rowNodes: Node[], +): S2PivotSheetFrozenOptions & + S2TableSheetFrozenOptions & { + rowHeight: number; + } => { + /** + * series number cell 可以自定义布局,和 row cell 不一定是 1 对 1 的关系 + * showSeriesNumber 暂时禁用 首行冻结 + * */ + const { pagination, frozen, hierarchyType, showSeriesNumber } = options; + + const enablePagination = pagination && pagination.pageSize; + let firstRow = false; + const headNode = rowNodes?.[0]; + + if (!enablePagination && !showSeriesNumber && frozen?.firstRow) { + const treeMode = hierarchyType === 'tree'; + + // tree mode + // first node no children: entire row + firstRow = treeMode || headNode?.children?.length === 0; + } + + const effectiveFrozenFirstRow = firstRow && !!headNode; + + return { + rowCount: effectiveFrozenFirstRow ? 1 : 0, + colCount: 0, + trailingColCount: 0, + trailingRowCount: 0, + firstRow, + rowHeight: effectiveFrozenFirstRow ? headNode.height : 0, + }; +}; diff --git a/packages/s2-core/src/group/grid-group.ts b/packages/s2-core/src/group/grid-group.ts index 8080a190a3..9c166c0361 100644 --- a/packages/s2-core/src/group/grid-group.ts +++ b/packages/s2-core/src/group/grid-group.ts @@ -1,5 +1,9 @@ import { Group } from '@antv/g'; -import { KEY_GROUP_GRID_GROUP, SQUARE_LINE_CAP } from '../common/constant'; +import { last } from 'lodash'; +import { + KEY_GROUP_GRID_GROUP, + PANEL_GRID_GROUP_Z_INDEX, +} from '../common/constant'; import type { GridInfo } from '../common/interface'; import type { GridGroupConstructorParameters } from '../common/interface/group'; import type { SpreadSheet } from '../sheet-type/spread-sheet'; @@ -26,44 +30,39 @@ export class GridGroup extends Group { }; public updateGrid = (gridInfo: GridInfo, id = KEY_GROUP_GRID_GROUP) => { - const bbox = this.getBBox(); - const { theme } = this.s2; - - const style = theme.dataCell!.cell; - if (!this.gridGroup || !this.getElementById(id)) { this.gridGroup = this.appendChild( new Group({ id, + style: { + zIndex: PANEL_GRID_GROUP_Z_INDEX, + }, }), ); + } else { + this.gridGroup.removeChildren(); } - this.gridGroup.removeChildren(); + const width = last(gridInfo.cols) ?? 0; + const height = last(gridInfo.rows) ?? 0; + const { theme } = this.s2; + + const style = theme.dataCell!.cell; const verticalBorderWidth = style?.verticalBorderWidth; this.gridInfo = gridInfo; - - /* - * line 在绘制时,包围盒计算有点问题,会带入lineWidth - * 比如传入的 x1=0, x2=10, lineWidth=20 - * 最后line得出来的包围盒 minX=-10, maxX=20,会将lineWidth/2纳入计算中 - * 最后就会导致更新过程中,GridGroup的包围盒不断被放大 - * 因此在传入时,将这部分坐标减去,并结合lineCap将这部分绘制出来,达到内容区域绘制不变,包围盒计算正确的目的 - */ const halfVerticalBorderWidthBorderWidth = verticalBorderWidth! / 2; this.gridInfo.cols.forEach((x) => { renderLine(this.gridGroup, { x1: x - halfVerticalBorderWidthBorderWidth, x2: x - halfVerticalBorderWidthBorderWidth, - y1: halfVerticalBorderWidthBorderWidth, - y2: Math.floor(bbox.height - halfVerticalBorderWidthBorderWidth), + y1: 0, + y2: height, stroke: style!.verticalBorderColor, strokeOpacity: style!.verticalBorderColorOpacity, lineWidth: verticalBorderWidth, - lineCap: SQUARE_LINE_CAP, }); }); @@ -72,17 +71,14 @@ export class GridGroup extends Group { this.gridInfo.rows.forEach((y) => { renderLine(this.gridGroup, { - x1: halfHorizontalBorderWidth, - x2: Math.floor(bbox.width - halfHorizontalBorderWidth), + x1: 0, + x2: width, y1: y - halfHorizontalBorderWidth, y2: y - halfHorizontalBorderWidth, stroke: style!.horizontalBorderColor, strokeOpacity: style!.horizontalBorderColorOpacity, lineWidth: horizontalBorderWidth, - lineCap: SQUARE_LINE_CAP, }); }); - - this.gridGroup.toFront(); }; } diff --git a/packages/s2-core/src/group/panel-scroll-group.ts b/packages/s2-core/src/group/panel-scroll-group.ts index 315355e3b7..226bcc3505 100644 --- a/packages/s2-core/src/group/panel-scroll-group.ts +++ b/packages/s2-core/src/group/panel-scroll-group.ts @@ -4,7 +4,10 @@ import type { GridGroupConstructorParameters } from '../common/interface/group'; import { updateMergedCells } from '../utils/interaction/merge-cell'; import { S2Event } from '../common'; import type { MergedCell } from './../cell/merged-cell'; -import { KEY_GROUP_MERGED_CELLS } from './../common/constant/basic'; +import { + KEY_GROUP_MERGED_CELLS, + PANEL_MERGE_GROUP_Z_INDEX, +} from './../common/constant/basic'; import { GridGroup } from './grid-group'; export class PanelScrollGroup extends GridGroup { @@ -27,6 +30,9 @@ export class PanelScrollGroup extends GridGroup { this.mergedCellsGroup = this.appendChild( new Group({ id: KEY_GROUP_MERGED_CELLS, + style: { + zIndex: PANEL_MERGE_GROUP_Z_INDEX, + }, }), ); } @@ -34,7 +40,6 @@ export class PanelScrollGroup extends GridGroup { updateMergedCells() { this.initMergedCellsGroup(); updateMergedCells(this.s2, this.mergedCellsGroup); - this.mergedCellsGroup.toFront(); } addMergeCell(mergedCell: MergedCell) { diff --git a/packages/s2-core/src/interaction/base-interaction/click/data-cell-click.ts b/packages/s2-core/src/interaction/base-interaction/click/data-cell-click.ts index a316e2e1ca..ca459b8069 100644 --- a/packages/s2-core/src/interaction/base-interaction/click/data-cell-click.ts +++ b/packages/s2-core/src/interaction/base-interaction/click/data-cell-click.ts @@ -1,4 +1,5 @@ import type { FederatedPointerEvent as CanvasEvent } from '@antv/g'; +import { forEach } from 'lodash'; import type { DataCell } from '../../../cell/data-cell'; import { InteractionStateName, @@ -9,41 +10,30 @@ import type { TooltipData, TooltipOperatorOptions, ViewMeta, + ViewMetaData, } from '../../../common/interface'; import { getCellMeta, afterSelectDataCells, + getRowCellForSelectedCell, } from '../../../utils/interaction/select-event'; import { getTooltipOptions, getTooltipVisibleOperator, } from '../../../utils/tooltip'; import { BaseEvent, type BaseEventImplement } from '../../base-event'; +import { updateAllColHeaderCellState } from '../../../utils/interaction'; export class DataCellClick extends BaseEvent implements BaseEventImplement { - private clickTimer: number; - - private clickCount = 0; - public bindEvents() { this.bindDataCellClick(); } - private countClick() { - window.clearTimeout(this.clickTimer); - this.clickTimer = window.setTimeout(() => { - this.clickCount = 0; - }, 200); - this.clickCount++; - } - private bindDataCellClick() { this.spreadsheet.on(S2Event.DATA_CELL_CLICK, (event: CanvasEvent) => { - this.countClick(); - event.stopPropagation(); - const { interaction } = this.spreadsheet; + const { interaction, facet } = this.spreadsheet; interaction.clearHoverTimer(); @@ -67,12 +57,15 @@ export class DataCellClick extends BaseEvent implements BaseEventImplement { interaction.addIntercepts([InterceptType.HOVER]); if (interaction.isSelectedCell(cell)) { - /** - * 双击时不触发选择态 reset - * g5.0 mouseup 底层监听的是 pointerup,detail 为 0,需自行判断是否双击 - */ - if (this.clickCount <= 1) { + // https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail,使用 detail 属性来判断是否是双击,双击时不触发选择态 reset + if ((event.originalEvent as UIEvent)?.detail === 1) { interaction.reset(); + + // https://github.com/antvis/S2/issues/2447 + this.spreadsheet.emit( + S2Event.GLOBAL_SELECTED, + interaction.getActiveCells(), + ); } return; @@ -85,6 +78,31 @@ export class DataCellClick extends BaseEvent implements BaseEventImplement { }); this.spreadsheet.emit(S2Event.GLOBAL_SELECTED, [cell]); this.showTooltip(event, meta); + + // 点击单元格,高亮对应的行头、列头 + const { rowId, colId, spreadsheet } = meta; + const { colHeader, rowHeader } = interaction.getSelectedCellHighlight(); + + if (colHeader) { + updateAllColHeaderCellState( + colId, + facet.getColCells(), + InteractionStateName.SELECTED, + ); + } + + if (rowHeader) { + if (rowId) { + const allRowHeaderCells = getRowCellForSelectedCell( + meta, + spreadsheet, + ); + + forEach(allRowHeaderCells, (rowCell) => { + rowCell.updateByState(InteractionStateName.SELECTED); + }); + } + } }); } @@ -111,7 +129,7 @@ export class DataCellClick extends BaseEvent implements BaseEventImplement { const onlyShowCellText = this.spreadsheet.isTableMode(); const cellData = onlyShowCellText ? ({ - ...currentCellMeta, + ...(currentCellMeta as ViewMetaData), value: value || fieldValue, valueField: field || valueField, } as TooltipData) diff --git a/packages/s2-core/src/interaction/base-interaction/click/row-column-click.ts b/packages/s2-core/src/interaction/base-interaction/click/row-column-click.ts index 1a7be05a58..8874c90730 100644 --- a/packages/s2-core/src/interaction/base-interaction/click/row-column-click.ts +++ b/packages/s2-core/src/interaction/base-interaction/click/row-column-click.ts @@ -1,10 +1,10 @@ import type { FederatedPointerEvent as CanvasEvent } from '@antv/g'; -import { difference } from 'lodash'; +import { difference, findLast } from 'lodash'; import { CellType, + getTooltipOperatorHiddenColumnsMenu, InterceptType, S2Event, - getTooltipOperatorHiddenColumnsMenu, } from '../../../common/constant'; import type { TooltipBaseOperatorMenuItem, @@ -20,7 +20,10 @@ import { hideColumnsByThunkGroup, isEqualDisplaySiblingNodeId, } from '../../../utils/hide-columns'; -import { isMultiSelectionKey } from '../../../utils/interaction/select-event'; +import { + isMouseEventWithMeta, + isMultiSelectionKey, +} from '../../../utils/interaction/select-event'; import { getTooltipOptions, getTooltipVisibleOperator, @@ -37,6 +40,12 @@ export class RowColumnClick extends BaseEvent implements BaseEventImplement { this.bindColCellClick(); this.bindRowCellClick(); this.bindTableColExpand(); + this.bindMouseMove(); + } + + public reset() { + this.isMultiSelection = false; + this.spreadsheet.interaction.removeIntercepts([InterceptType.CLICK]); } private bindKeyboardDown() { @@ -53,8 +62,16 @@ export class RowColumnClick extends BaseEvent implements BaseEventImplement { private bindKeyboardUp() { this.spreadsheet.on(S2Event.GLOBAL_KEYBOARD_UP, (event: KeyboardEvent) => { if (isMultiSelectionKey(event)) { - this.isMultiSelection = false; - this.spreadsheet.interaction.removeIntercepts([InterceptType.CLICK]); + this.reset(); + } + }); + } + + private bindMouseMove() { + this.spreadsheet.on(S2Event.GLOBAL_MOUSE_MOVE, (event) => { + // 当快捷键被系统拦截后,按需补充调用一次 reset + if (this.isMultiSelection && !isMouseEventWithMeta(event)) { + this.reset(); } }); } @@ -198,8 +215,10 @@ export class RowColumnClick extends BaseEvent implements BaseEventImplement { 'hiddenColumnsDetail', [], ); + + // 当前单元格的前/后节点都被隐藏时, 会出现两个展开按钮, 优先展开靠右的 const { hideColumnNodes = [] } = - lastHiddenColumnsDetail.find(({ displaySiblingNode }) => + findLast(lastHiddenColumnsDetail, ({ displaySiblingNode }) => isEqualDisplaySiblingNodeId(displaySiblingNode, node.id), ) || {}; diff --git a/packages/s2-core/src/interaction/base-interaction/click/row-text-click.ts b/packages/s2-core/src/interaction/base-interaction/click/row-text-click.ts index 68ec6275ec..8b080cf2a9 100644 --- a/packages/s2-core/src/interaction/base-interaction/click/row-text-click.ts +++ b/packages/s2-core/src/interaction/base-interaction/click/row-text-click.ts @@ -1,7 +1,7 @@ import type { FederatedPointerEvent as CanvasEvent } from '@antv/g'; import { InterceptType, S2Event } from '../../../common/constant'; import type { RawData } from '../../../common/interface'; -import { getFieldValueOfViewMetaData } from '../../../data-set/cell-data'; +import { CellData } from '../../../data-set/cell-data'; import type { Node } from '../../../facet/layout/node'; import { BaseEvent, type BaseEventImplement } from '../../base-event'; import type { Data } from '../../../common/interface'; @@ -41,14 +41,9 @@ export class RowTextClick extends BaseEvent implements BaseEventImplement { const data = this.spreadsheet.dataSet.getCellMultiData({ query: leafNode?.query!, - totals: { - row: { - totalDimensions: true, - }, - }, })[0]; - const originalData = getFieldValueOfViewMetaData(data) as RawData; + const originalData = CellData.getFieldValue(data) as RawData; return { ...originalData, diff --git a/packages/s2-core/src/interaction/base-interaction/hover.ts b/packages/s2-core/src/interaction/base-interaction/hover.ts index ed8221937f..81f07fdf16 100644 --- a/packages/s2-core/src/interaction/base-interaction/hover.ts +++ b/packages/s2-core/src/interaction/base-interaction/hover.ts @@ -32,15 +32,24 @@ export class HoverEvent extends BaseEvent implements BaseEventImplement { public updateRowColCells(meta: ViewMeta) { const { rowId, colId } = meta; - const { facet } = this.spreadsheet; + const { facet, interaction } = this.spreadsheet; updateAllColHeaderCellState( colId, facet.getColCells(), InteractionStateName.HOVER, ); + const { rowHeader, colHeader } = interaction.getHoverHighlight(); - if (rowId) { + if (colHeader) { + updateAllColHeaderCellState( + colId, + facet.getColCells(), + InteractionStateName.HOVER, + ); + } + + if (rowHeader && rowId) { // update rowHeader cells const allRowHeaderCells = getActiveHoverRowColCells( rowId, @@ -88,8 +97,12 @@ export class HoverEvent extends BaseEvent implements BaseEventImplement { }; if (interactionOptions?.hoverHighlight) { - // highlight all the row and column cells which the cell belongs to - this.updateRowColCells(meta); + const { rowHeader, colHeader } = interaction.getHoverHighlight(); + + if (rowHeader || colHeader) { + // highlight all the row and column cells which the cell belongs to + this.updateRowColCells(meta); + } } const data = this.getCellData(meta, onlyShowCellText); @@ -155,7 +168,7 @@ export class HoverEvent extends BaseEvent implements BaseEventImplement { isTotals: meta.isTotals, hideSummary: true, onlyShowCellText: true, - enableFormat: this.spreadsheet.isPivotMode(), + enableFormat: true, }; const data = this.getCellData(meta, options.onlyShowCellText); @@ -213,11 +226,13 @@ export class HoverEvent extends BaseEvent implements BaseEventImplement { stateName: InteractionStateName.HOVER, }); - this.showEllipsisTooltip(event, cell); - if (interactionOptions?.hoverHighlight) { - // highlight all the row and column cells which the cell belongs to - this.updateRowColCells(meta); + const { rowHeader, colHeader } = interaction.getHoverHighlight(); + + if (rowHeader || colHeader) { + // highlight all the row and column cells which the cell belongs to + this.updateRowColCells(meta); + } } if (interactionOptions?.hoverFocus) { @@ -240,9 +255,7 @@ export class HoverEvent extends BaseEvent implements BaseEventImplement { public bindCornerCellHover() { this.spreadsheet.on(S2Event.CORNER_CELL_HOVER, (event: CanvasEvent) => { - const cell = this.spreadsheet.getCell(event.target); - - this.showEllipsisTooltip(event, cell); + this.handleHeaderHover(event); }); } } diff --git a/packages/s2-core/src/interaction/brush-selection/base-brush-selection.ts b/packages/s2-core/src/interaction/brush-selection/base-brush-selection.ts index 52979f5252..16d0a3f3d3 100644 --- a/packages/s2-core/src/interaction/brush-selection/base-brush-selection.ts +++ b/packages/s2-core/src/interaction/brush-selection/base-brush-selection.ts @@ -1,8 +1,8 @@ import { - type FederatedPointerEvent as CanvasEvent, + Rect, type DisplayObject, + type FederatedPointerEvent as CanvasEvent, type PointLike, - Rect, } from '@antv/g'; import { cloneDeep, isEmpty, isNil, map, throttle } from 'lodash'; import { ColCell, DataCell, RowCell } from '../../cell'; @@ -24,7 +24,6 @@ import type { BrushRange, OffsetConfig, OnUpdateCells, - Point, S2CellType, ViewMeta, } from '../../common/interface'; @@ -111,7 +110,7 @@ export class BaseBrushSelection } // 默认是 Data cell 的绘制区 - protected isPointInCanvas(point: Point): boolean { + protected isPointInCanvas(point: PointLike): boolean { const { height, width } = this.spreadsheet.facet.getCanvasSize(); const { minX, minY } = this.spreadsheet.facet.panelBBox; @@ -144,7 +143,10 @@ export class BaseBrushSelection this.mouseMoveDistanceFromCanvas = Math.abs(deltaVal); }; - public formatBrushPointForScroll = (delta: Point, isRowHeader = false) => { + public formatBrushPointForScroll = ( + delta: PointLike, + isRowHeader = false, + ) => { const { x, y } = delta; const { facet } = this.spreadsheet; const { minX, maxX } = isRowHeader ? facet.cornerBBox : facet.panelBBox; @@ -205,7 +207,7 @@ export class BaseBrushSelection let min = 0; const frozenRowRange = frozenInfo?.frozenRow?.range; - if (frozenRowRange) { + if (frozenRowRange?.[1]) { min = frozenRowRange[1] + 1; } @@ -216,7 +218,7 @@ export class BaseBrushSelection let max = facet.getCellRange().end; const frozenTrailingRowRange = frozenInfo?.frozenTrailingRow?.range; - if (frozenTrailingRowRange) { + if (frozenTrailingRowRange?.[0]) { max = frozenTrailingRowRange[0] - 1; } @@ -234,7 +236,7 @@ export class BaseBrushSelection let min = 0; const frozenColRange = frozenInfo?.frozenCol?.range; - if (frozenColRange) { + if (frozenColRange?.[1]) { min = frozenColRange[1] + 1; } @@ -245,7 +247,7 @@ export class BaseBrushSelection let max = facet.getColLeafNodes().length - 1; const frozenTrailingColRange = frozenInfo?.frozenTrailingCol?.range; - if (frozenTrailingColRange) { + if (frozenTrailingColRange?.[0]) { max = frozenTrailingColRange[0] - 1; } @@ -765,7 +767,7 @@ export class BaseBrushSelection } }; - public autoBrushScroll(point: Point, isRowHeader = false) { + public autoBrushScroll(point: PointLike, isRowHeader = false) { this.clearAutoScroll(); if (!this.isPointInCanvas(point)) { @@ -813,7 +815,7 @@ export class BaseBrushSelection protected updateSelectedCells() {} - protected getPrepareSelectMaskPosition(brushRange: BrushRange): Point { + protected getPrepareSelectMaskPosition(brushRange: BrushRange): PointLike { return { x: brushRange.start.x, y: brushRange.start.y, diff --git a/packages/s2-core/src/interaction/brush-selection/col-brush-selection.ts b/packages/s2-core/src/interaction/brush-selection/col-brush-selection.ts index 0d8f865ed7..3892a326c5 100644 --- a/packages/s2-core/src/interaction/brush-selection/col-brush-selection.ts +++ b/packages/s2-core/src/interaction/brush-selection/col-brush-selection.ts @@ -26,6 +26,10 @@ export class ColCellBrushSelection extends BaseBrushSelection { protected bindMouseDown() { this.spreadsheet.on(S2Event.COL_CELL_MOUSE_DOWN, (event) => { + if (!this.spreadsheet.interaction.getBrushSelection().colCell) { + return; + } + super.mouseDown(event); }); } @@ -96,16 +100,16 @@ export class ColCellBrushSelection extends BaseBrushSelection { ); } - // 最终刷选的cell + // 最终刷选的 cell protected updateSelectedCells() { const { interaction, facet } = this.spreadsheet; interaction.changeState({ cells: map(this.brushRangeCells, getCellMeta), - stateName: InteractionStateName.SELECTED, onUpdateCells: (root) => { root.updateCells(facet.getColCells()); }, + stateName: InteractionStateName.BRUSH_SELECTED, }); this.spreadsheet.emit( diff --git a/packages/s2-core/src/interaction/brush-selection/data-cell-brush-selection.ts b/packages/s2-core/src/interaction/brush-selection/data-cell-brush-selection.ts index 9b8d7adc98..d3ff5b802a 100644 --- a/packages/s2-core/src/interaction/brush-selection/data-cell-brush-selection.ts +++ b/packages/s2-core/src/interaction/brush-selection/data-cell-brush-selection.ts @@ -22,6 +22,10 @@ export class DataCellBrushSelection extends BaseBrushSelection { protected bindMouseDown() { this.spreadsheet.on(S2Event.DATA_CELL_MOUSE_DOWN, (event) => { + if (!this.spreadsheet.interaction.getBrushSelection().dataCell) { + return; + } + super.mouseDown(event); this.resetScrollDelta(); }); @@ -99,13 +103,14 @@ export class DataCellBrushSelection extends BaseBrushSelection { return metas; }; - // 最终刷选的cell + // 最终刷选的 cell protected updateSelectedCells() { const brushRange = this.getBrushRange(); const selectedCellMetas = this.getSelectedCellMetas(brushRange); this.spreadsheet.interaction.changeState({ cells: selectedCellMetas, + // TODO: 怕上层有直接消费 stateName, 暂时保留, 2.0 版本改成 InteractionStateName.BRUSH_SELECTED stateName: InteractionStateName.SELECTED, onUpdateCells: afterSelectDataCells, }); diff --git a/packages/s2-core/src/interaction/brush-selection/row-brush-selection.ts b/packages/s2-core/src/interaction/brush-selection/row-brush-selection.ts index 226cba7768..14b30eabe1 100644 --- a/packages/s2-core/src/interaction/brush-selection/row-brush-selection.ts +++ b/packages/s2-core/src/interaction/brush-selection/row-brush-selection.ts @@ -1,6 +1,5 @@ import { isNil, last, map } from 'lodash'; import { RowCell } from '../../cell'; - import { InterceptType, S2Event } from '../../common/constant'; import { InteractionBrushSelectionStage, @@ -13,9 +12,9 @@ import type { Point, ViewMeta, } from '../../common/interface'; +import type { BBox } from '../../engine'; import type { Node } from '../../facet/layout/node'; import { getCellMeta } from '../../utils/interaction/select-event'; -import type { BBox } from '../../engine'; import { BaseBrushSelection } from './base-brush-selection'; export class RowCellBrushSelection extends BaseBrushSelection { @@ -25,6 +24,10 @@ export class RowCellBrushSelection extends BaseBrushSelection { protected bindMouseDown() { this.spreadsheet.on(S2Event.ROW_CELL_MOUSE_DOWN, (event) => { + if (!this.spreadsheet.interaction.getBrushSelection().rowCell) { + return; + } + super.mouseDown(event); }); } @@ -102,7 +105,7 @@ export class RowCellBrushSelection extends BaseBrushSelection { this.spreadsheet.interaction.changeState({ cells: selectedCellMetas, - stateName: InteractionStateName.SELECTED, + stateName: InteractionStateName.BRUSH_SELECTED, onUpdateCells: this.onUpdateCells, }); @@ -125,16 +128,15 @@ export class RowCellBrushSelection extends BaseBrushSelection { return this.spreadsheet.facet.getRowNodes().filter(this.isInBrushRange); }; - private getScrollBrushRangeCells(nodes: Node[]) { + private getScrollBrushRangeCells(nodes: Node[]): RowCell[] { return nodes.map((node) => { - const visibleCell = this.getVisibleBrushRangeCells(node.id); + const visibleCell = this.getVisibleBrushRangeCells(node.id) as RowCell; if (visibleCell) { return visibleCell; } - // TODO: 先暂时不考虑自定义单元格的情况, next 分支把这些单元格 (包括自定义单元格) 都放在了 s2.options.rowCell 里 - return new RowCell(node, this.spreadsheet); + return this.spreadsheet.facet.rowHeader!.getCellInstance(node); }); } diff --git a/packages/s2-core/src/interaction/data-cell-multi-selection.ts b/packages/s2-core/src/interaction/data-cell-multi-selection.ts index b2640e2127..a7f03e197c 100644 --- a/packages/s2-core/src/interaction/data-cell-multi-selection.ts +++ b/packages/s2-core/src/interaction/data-cell-multi-selection.ts @@ -11,6 +11,7 @@ import type { CellMeta, S2CellType, ViewMeta } from '../common/interface'; import { getCellMeta, isMultiSelectionKey, + isMouseEventWithMeta, } from '../utils/interaction/select-event'; import { getCellsTooltipData } from '../utils/tooltip'; import { afterSelectDataCells } from '../utils/interaction/select-event'; @@ -26,6 +27,7 @@ export class DataCellMultiSelection this.bindKeyboardDown(); this.bindDataCellClick(); this.bindKeyboardUp(); + this.bindMouseMove(); } public reset() { @@ -53,6 +55,15 @@ export class DataCellMultiSelection }); } + private bindMouseMove() { + this.spreadsheet.on(S2Event.GLOBAL_MOUSE_MOVE, (event) => { + // 当快捷键被系统拦截后,按需补充调用一次 reset + if (this.isMultiSelection && !isMouseEventWithMeta(event)) { + this.reset(); + } + }); + } + private getSelectedCells(cell: S2CellType<ViewMeta>) { const id = cell.getMeta().id; const { interaction } = this.spreadsheet; @@ -85,6 +96,10 @@ export class DataCellMultiSelection if (isEmpty(selectedCells)) { interaction.clearState(); this.spreadsheet.hideTooltip(); + this.spreadsheet.emit( + S2Event.GLOBAL_SELECTED, + interaction.getActiveCells(), + ); return; } diff --git a/packages/s2-core/src/interaction/event-controller.ts b/packages/s2-core/src/interaction/event-controller.ts index 6482d607b4..83c6c34029 100644 --- a/packages/s2-core/src/interaction/event-controller.ts +++ b/packages/s2-core/src/interaction/event-controller.ts @@ -5,7 +5,6 @@ import { type Group, } from '@antv/g'; import { each, get, hasIn, isEmpty, isNil } from 'lodash'; -import { CustomImage } from '../engine'; import { GuiIcon } from '../common'; import { CellType, @@ -17,6 +16,7 @@ import { SHAPE_STYLE_MAP, } from '../common/constant'; import type { EmitterType, ResizeInfo } from '../common/interface'; +import { CustomImage } from '../engine'; import type { SpreadSheet } from '../sheet-type'; import { getSelectedData } from '../utils/export/copy'; import { keyEqualTo } from '../utils/export/method'; @@ -55,6 +55,8 @@ export class EventController { public isCanvasEffect = false; + public canvasMousemoveEvent: CanvasEvent; + constructor(spreadsheet: SpreadSheet) { this.spreadsheet = spreadsheet; this.bindEvents(); @@ -146,7 +148,7 @@ export class EventController { return; } - /* + /** * 全局有 mouseUp 和 click 事件, 当刷选完成后会同时触发, 当选中单元格后, 会同时触发 click 对应的 reset 事件 * 所以如果是 刷选过程中 引起的 click(mousedown + mouseup) 事件, 则不需要重置 */ @@ -175,8 +177,12 @@ export class EventController { return; } + interaction.reset(); this.spreadsheet.emit(S2Event.GLOBAL_RESET, event); - interaction?.reset(); + this.spreadsheet.emit( + S2Event.GLOBAL_SELECTED, + interaction.getActiveCells(), + ); } private isMouseEvent(event: Event): event is MouseEvent { @@ -184,6 +190,36 @@ export class EventController { return hasIn(event, 'clientX') && hasIn(event, 'clientY'); } + public isMatchElement(event: MouseEvent) { + const canvas = this.spreadsheet.getCanvasElement(); + const { target } = event; + + return ( + target === canvas || + target instanceof DisplayObject || + target instanceof Canvas + ); + } + + public isMatchPoint(event: MouseEvent) { + /** + * 这里不能使用 bounding rect 的 width 和 height, 高清适配后 canvas 实际宽高会变 + * 比如实际 400 * 300 => hd (800 * 600) + * 从视觉来看, 虽然点击了空白处, 但其实还是处于 放大后的 canvas 区域, 所以还需要额外判断一下坐标 + */ + const canvas = this.spreadsheet.getCanvasElement(); + const { width, height } = this.getContainerRect(); + const { x, y } = canvas.getBoundingClientRect() || {}; + const { clientX, clientY } = event; + + return ( + clientX <= x + width && + clientX >= x && + clientY <= y + height && + clientY >= y + ); + } + private isMouseOnTheCanvasContainer(event: Event) { if (this.isMouseEvent(event)) { const canvas = this.spreadsheet.getCanvasElement(); @@ -192,38 +228,27 @@ export class EventController { return false; } - const { x, y } = canvas.getBoundingClientRect() || {}; - - /* - * 这里不能使用 bounding rect 的 width 和 height, 高清适配后 canvas 实际宽高会变 - * 比如实际 400 * 300 => hd (800 * 600) - * 从视觉来看, 虽然点击了空白处, 但其实还是处于 放大后的 canvas 区域, 所以还需要额外判断一下坐标 - */ - const { width, height } = this.getContainerRect(); - - const { target: eventTarget, clientX, clientY } = event; - - return ( - (eventTarget === canvas || - eventTarget instanceof DisplayObject || - eventTarget instanceof Canvas) && - clientX <= x + width && - clientX >= x && - clientY <= y + height && - clientY >= y - ); + return this.isMatchElement(event) && this.isMatchPoint(event); } return false; } private getContainerRect() { - const { maxX, maxY } = this.spreadsheet.facet?.panelBBox || {}; - const { width, height } = this.spreadsheet.options; + const { facet, options } = this.spreadsheet; + const scrollBar = facet?.hRowScrollBar || facet?.hScrollBar; + const { maxX, maxY } = facet?.panelBBox || {}; + const { width = 0, height = 0 } = options; + + /** + * https://github.com/antvis/S2/issues/2376 + * 横向的滚动条在表格外 (Canvas 内), 点击滚动条(含滑道区域) 不应该重置交互 + */ + const trackHeight = scrollBar?.theme?.size || 0; return { - width: Math.min(width!, maxX), - height: Math.min(height!, maxY), + width: Math.min(width, maxX), + height: Math.min(height, maxY + trackHeight), }; } @@ -357,6 +382,8 @@ export class EventController { }; private onCanvasMousemove = (event: CanvasEvent) => { + this.canvasMousemoveEvent = event; + if (this.isResizeArea(event)) { this.activeResizeArea(event); this.spreadsheet.emit( diff --git a/packages/s2-core/src/interaction/range-selection.ts b/packages/s2-core/src/interaction/range-selection.ts index aa44170782..4aecb085cb 100644 --- a/packages/s2-core/src/interaction/range-selection.ts +++ b/packages/s2-core/src/interaction/range-selection.ts @@ -1,5 +1,5 @@ -import type { FederatedPointerEvent as Event } from '@antv/g'; -import { inRange, isNil, range } from 'lodash'; +import { inRange, isEmpty, isNil, range } from 'lodash'; +import type { FederatedPointerEvent } from '@antv/g'; import { DataCell } from '../cell'; import { CellType, @@ -22,6 +22,7 @@ export class RangeSelection extends BaseEvent implements BaseEventImplement { this.bindDataCellClick(); this.bindColCellClick(); this.bindKeyboardUp(); + this.bindMouseMove(); } public reset() { @@ -49,21 +50,30 @@ export class RangeSelection extends BaseEvent implements BaseEventImplement { }); } + private bindMouseMove() { + // 当快捷键被系统拦截后,按需补充调用一次 reset + this.spreadsheet.on(S2Event.GLOBAL_MOUSE_MOVE, (event) => { + if (this.isRangeSelection && !event.shiftKey) { + this.reset(); + } + }); + } + private bindColCellClick() { if (this.spreadsheet.isTableMode()) { // series-number click - this.spreadsheet.on(S2Event.ROW_CELL_CLICK, (event: Event) => { + this.spreadsheet.on(S2Event.ROW_CELL_CLICK, (event) => { this.handleColClick(event); }); } - this.spreadsheet.on(S2Event.COL_CELL_CLICK, (event: Event) => { + this.spreadsheet.on(S2Event.COL_CELL_CLICK, (event) => { this.handleColClick(event); }); } private bindDataCellClick() { - this.spreadsheet.on(S2Event.DATA_CELL_CLICK, (event: Event) => { + this.spreadsheet.on(S2Event.DATA_CELL_CLICK, (event) => { event.stopPropagation(); const cell = this.spreadsheet.getCell(event.target) as DataCell; const meta = cell.getMeta(); @@ -123,7 +133,7 @@ export class RangeSelection extends BaseEvent implements BaseEventImplement { }); } - private handleColClick = (event: Event) => { + private handleColClick = (event: FederatedPointerEvent) => { event.stopPropagation(); const { interaction, facet } = this.spreadsheet; const cell = this.spreadsheet.getCell(event.target); @@ -183,6 +193,10 @@ export class RangeSelection extends BaseEvent implements BaseEventImplement { stateName: InteractionStateName.SELECTED, }); } else { + if (isEmpty(interaction.getCells())) { + interaction.removeIntercepts([InterceptType.HOVER]); + } + this.spreadsheet.store.set('lastClickedCell', cell); } diff --git a/packages/s2-core/src/interaction/root.ts b/packages/s2-core/src/interaction/root.ts index 7fa9e23ef8..7c34578467 100644 --- a/packages/s2-core/src/interaction/root.ts +++ b/packages/s2-core/src/interaction/root.ts @@ -2,23 +2,23 @@ import { concat, find, forEach, isBoolean, isEmpty, isNil, map } from 'lodash'; import type { MergedCell } from '../cell'; import { CellType, + INTERACTION_STATE_INFO_KEY, InteractionName, InteractionStateName, - INTERACTION_STATE_INFO_KEY, InterceptType, S2Event, } from '../common/constant'; import type { - BrushSelection, + BrushSelectionOptions, BrushSelectionInfo, CellMeta, CustomInteraction, + InteractionCellHighlightOptions, InteractionStateInfo, Intercept, MergedCellInfo, S2CellType, SelectHeaderCellInfo, - InteractionCellSelectedHighlightOptions, } from '../common/interface'; import type { Node } from '../facet/layout/node'; import type { SpreadSheet } from '../sheet-type'; @@ -36,14 +36,14 @@ import { } from './base-interaction/click'; import { CornerCellClick } from './base-interaction/click/corner-cell-click'; import { HoverEvent } from './base-interaction/hover'; -import { EventController } from './event-controller'; -import { RangeSelection } from './range-selection'; -import { SelectedCellMove } from './selected-cell-move'; -import { DataCellBrushSelection } from './brush-selection/data-cell-brush-selection'; import { ColCellBrushSelection } from './brush-selection/col-brush-selection'; +import { DataCellBrushSelection } from './brush-selection/data-cell-brush-selection'; import { RowCellBrushSelection } from './brush-selection/row-brush-selection'; import { DataCellMultiSelection } from './data-cell-multi-selection'; +import { EventController } from './event-controller'; +import { RangeSelection } from './range-selection'; import { RowColumnResize } from './row-column-resize'; +import { SelectedCellMove } from './selected-cell-move'; export class RootInteraction { public spreadsheet: SpreadSheet; @@ -146,7 +146,10 @@ export class RootInteraction { } public isSelectedState() { - return this.isStateOf(InteractionStateName.SELECTED); + return ( + this.isStateOf(InteractionStateName.SELECTED) || + this.isStateOf(InteractionStateName.BRUSH_SELECTED) + ); } public isAllSelectedState() { @@ -184,7 +187,7 @@ export class RootInteraction { // 获取 cells 中在可视区域内的实例列表 public getActiveCells(): S2CellType[] { const ids = this.getCells().map((item) => item.id); - const allCells = this.spreadsheet.facet.getCells(); + const allCells = this.spreadsheet.facet?.getCells(); // 这里的顺序要以 ids 中的顺序为准,代表点击 cell 的顺序 return map(ids, (id) => @@ -325,7 +328,7 @@ export class RootInteraction { } private getBrushSelectionInfo( - brushSelection?: boolean | BrushSelection, + brushSelection?: boolean | BrushSelectionOptions, ): BrushSelectionInfo { if (isBoolean(brushSelection)) { return { @@ -542,7 +545,7 @@ export class RootInteraction { return this.hoverTimer; } - public getSelectedCellHighlight(): InteractionCellSelectedHighlightOptions { + public getSelectedCellHighlight(): InteractionCellHighlightOptions { const { selectedCellHighlight } = this.spreadsheet.options.interaction!; if (isBoolean(selectedCellHighlight)) { @@ -559,7 +562,7 @@ export class RootInteraction { colHeader = false, currentRow = false, currentCol = false, - } = (selectedCellHighlight as unknown as InteractionCellSelectedHighlightOptions) ?? + } = (selectedCellHighlight as unknown as InteractionCellHighlightOptions) ?? {}; return { @@ -569,4 +572,59 @@ export class RootInteraction { currentCol, }; } + + public getHoverAfterScroll(): boolean { + return this.spreadsheet.options.interaction!.hoverAfterScroll!; + } + + public getHoverHighlight(): InteractionCellHighlightOptions { + const { hoverHighlight } = this.spreadsheet.options.interaction!; + + if (isBoolean(hoverHighlight)) { + return { + rowHeader: hoverHighlight, + colHeader: hoverHighlight, + currentRow: hoverHighlight, + currentCol: hoverHighlight, + }; + } + + const { + rowHeader = false, + colHeader = false, + currentRow = false, + currentCol = false, + } = hoverHighlight ?? ({} as InteractionCellHighlightOptions); + + return { + rowHeader, + colHeader, + currentRow, + currentCol, + }; + } + + public getBrushSelection(): BrushSelectionOptions { + const { brushSelection } = this.spreadsheet.options.interaction!; + + if (isBoolean(brushSelection)) { + return { + dataCell: brushSelection, + rowCell: brushSelection, + colCell: brushSelection, + }; + } + + const { + dataCell = false, + rowCell = false, + colCell = false, + } = brushSelection ?? ({} as BrushSelectionOptions); + + return { + dataCell, + rowCell, + colCell, + }; + } } diff --git a/packages/s2-core/src/interaction/row-column-resize.ts b/packages/s2-core/src/interaction/row-column-resize.ts index a73e598085..f2145bbc64 100644 --- a/packages/s2-core/src/interaction/row-column-resize.ts +++ b/packages/s2-core/src/interaction/row-column-resize.ts @@ -1,4 +1,10 @@ -import { Group, type DisplayObject, type PathStyleProps, Path } from '@antv/g'; +import { + FederatedPointerEvent, + Group, + Path, + type DisplayObject, + type PathStyleProps, +} from '@antv/g'; import { clone, isEmpty, throttle } from 'lodash'; import type { ResizeInteractionOptions, @@ -23,6 +29,7 @@ import type { ResizePosition, } from '../common/interface/resize'; import { CustomRect } from '../engine'; +import { floor } from '../utils/math'; import { BaseEvent, type BaseEventImplement } from './base-interaction'; export class RowColumnResize extends BaseEvent implements BaseEventImplement { @@ -124,10 +131,7 @@ export class RowColumnResize extends BaseEvent implements BaseEventImplement { } private updateResizeGuideLinePosition( - offset: { - x: number; - y: number; - }, + event: FederatedPointerEvent, resizeInfo: ResizeInfo, ) { const resizeShapes = this.getResizeShapes(); @@ -160,7 +164,8 @@ export class RowColumnResize extends BaseEvent implements BaseEventImplement { ['M', offsetX + width - halfSize, offsetY], ['L', offsetX + width - halfSize, guideLineMaxHeight], ]); - this.resizeStartPosition.offsetX = offset.x; + this.resizeStartPosition.offsetX = event.offsetX; + this.resizeStartPosition.clientX = event.clientX; return; } @@ -173,7 +178,8 @@ export class RowColumnResize extends BaseEvent implements BaseEventImplement { ['M', offsetX, offsetY + height - halfSize], ['L', guideLineMaxWidth, offsetY + height - halfSize], ]); - this.resizeStartPosition.offsetY = offset.y; + this.resizeStartPosition.offsetY = event.offsetY; + this.resizeStartPosition.clientY = event.clientY; } private bindMouseDown() { @@ -192,13 +198,7 @@ export class RowColumnResize extends BaseEvent implements BaseEventImplement { this.spreadsheet.interaction.addIntercepts([InterceptType.RESIZE]); this.setResizeTarget(shape); this.showResizeGroup(); - this.updateResizeGuideLinePosition( - { - x: event.offsetX, - y: event.offsetY, - }, - resizeInfo, - ); + this.updateResizeGuideLinePosition(event, resizeInfo); }); } @@ -574,14 +574,14 @@ export class RowColumnResize extends BaseEvent implements BaseEventImplement { ); const { start, end } = this.getResizeGuideLinePosition(); - const resizedWidth = Math.floor( + const resizedWidth = floor( end.x - start.x + (defaultResizeInfo.type === ResizeDirectionType.Horizontal ? defaultResizeInfo.size : 0), ); - const resizedHeight = Math.floor( + const resizedHeight = floor( end.y - start.y + (defaultResizeInfo.type === ResizeDirectionType.Vertical diff --git a/packages/s2-core/src/interaction/selected-cell-move.ts b/packages/s2-core/src/interaction/selected-cell-move.ts index 71f7eed0fe..6f9d956406 100644 --- a/packages/s2-core/src/interaction/selected-cell-move.ts +++ b/packages/s2-core/src/interaction/selected-cell-move.ts @@ -6,6 +6,7 @@ import { calculateInViewIndexes } from '../facet/utils'; import type { SpreadSheet } from '../sheet-type'; import { getDataCellId } from '../utils'; import { getRangeIndex, selectCells } from '../utils/interaction/select-event'; +import { floor } from '../utils/math'; import { BaseEvent, type BaseEventImplement } from './base-interaction'; const SelectedCellMoveMap = [ @@ -265,22 +266,22 @@ export class SelectedCellMove extends BaseEvent implements BaseEventImplement { const { viewportHeight: height, viewportWidth: width } = facet.panelBBox; const splitLineStyle = get(spreadsheet, 'theme.splitLine'); const frozenColWidth = frozenColGroup - ? Math.floor( + ? floor( frozenColGroup.getBBox().width - splitLineStyle.verticalBorderWidth / 2, ) : 0; const frozenTrailingColWidth = frozenTrailingColGroup - ? Math.floor(frozenTrailingColGroup.getBBox().width) + ? floor(frozenTrailingColGroup.getBBox().width) : 0; const frozenRowHeight = frozenRowGroup - ? Math.floor( + ? floor( frozenRowGroup.getBBox().height - splitLineStyle.horizontalBorderWidth / 2, ) : 0; const frozenTrailingRowHeight = frozenTrailingRowGroup - ? Math.floor(frozenTrailingRowGroup.getBBox().height) + ? floor(frozenTrailingRowGroup.getBBox().height) : 0; const indexes = calculateInViewIndexes({ diff --git a/packages/s2-core/src/sheet-type/pivot-sheet.ts b/packages/s2-core/src/sheet-type/pivot-sheet.ts index 7783c8a422..2cce6f7bd2 100644 --- a/packages/s2-core/src/sheet-type/pivot-sheet.ts +++ b/packages/s2-core/src/sheet-type/pivot-sheet.ts @@ -37,6 +37,13 @@ export class PivotSheet extends SpreadSheet { return false; } + public getContentHeight() { + return this.facet.getContentHeight(); + } + + /** + * Check if is pivot mode + */ public isPivotMode(): boolean { return true; } @@ -65,8 +72,9 @@ export class PivotSheet extends SpreadSheet { public clearDrillDownData(rowNodeId?: string, preventRender?: boolean) { if (this.dataSet instanceof PivotDataSet) { - this.dataSet.clearDrillDownData(rowNodeId); - if (!preventRender) { + const cleaned = this.dataSet.clearDrillDownData(rowNodeId); + + if (cleaned && !preventRender) { // 重置当前交互 this.interaction.reset(); this.render(false); diff --git a/packages/s2-core/src/sheet-type/spread-sheet.ts b/packages/s2-core/src/sheet-type/spread-sheet.ts index ac13d77501..1585c1e20d 100644 --- a/packages/s2-core/src/sheet-type/spread-sheet.ts +++ b/packages/s2-core/src/sheet-type/spread-sheet.ts @@ -23,6 +23,7 @@ import { import { BaseCell } from '../cell'; import { InterceptType, + LayoutWidthType, S2Event, getTooltipOperatorSortMenus, getTooltipOperatorTableSortMenus, @@ -38,7 +39,6 @@ import type { Fields, InteractionOptions, InternalFullyTheme, - LayoutWidthType, OffsetConfig, Pagination, S2CellType, @@ -249,10 +249,12 @@ export abstract class SpreadSheet extends EE { } private initInteraction() { + this.interaction?.destroy?.(); this.interaction = new RootInteraction(this); } private initTooltip() { + this.tooltip?.destroy?.(); this.tooltip = this.renderTooltip(); if (!(this.tooltip instanceof BaseTooltip)) { // eslint-disable-next-line no-console @@ -357,9 +359,14 @@ export abstract class SpreadSheet extends EE { * Group sort params kept in {@see store} and * Priority: group sort > advanced sort * @param dataCfg - * @param reset reset: true, 直接使用用户传入的 DataCfg ,不再与上次数据进行合并 + * @param reset 是否使用传入的 dataCfg 重置已保存的 dataCfg + * + * @example setDataCfg(dataCfg, true) 直接使用传入的 DataCfg,不再与上次数据进行合并 */ - public setDataCfg(dataCfg: S2DataConfig, reset?: boolean) { + public setDataCfg<T extends boolean = false>( + dataCfg: T extends true ? S2DataConfig : Partial<S2DataConfig>, + reset?: T, + ) { this.store.set('originalDataCfg', dataCfg); if (reset) { this.dataCfg = getSafetyDataConfig(dataCfg); @@ -373,12 +380,17 @@ export abstract class SpreadSheet extends EE { public setOptions(options: Partial<S2Options>, reset?: boolean) { this.hideTooltip(); + if (reset) { this.options = getSafetyOptions(options); } else { this.options = customMerge(this.options, options); } + if (reset || options.tooltip?.render) { + this.initTooltip(); + } + this.registerIcons(); } @@ -572,6 +584,10 @@ export abstract class SpreadSheet extends EE { ); } + protected isCellType(cell?: CellEventTarget) { + return cell instanceof BaseCell; + } + // 获取当前 cell 实例 public getCell<T extends S2CellType = S2CellType>( target: CellEventTarget, @@ -580,8 +596,8 @@ export abstract class SpreadSheet extends EE { // 一直索引到 g 顶层的 canvas 来检查是否在指定的cell中 while (parent && !(parent instanceof Canvas)) { - if (parent instanceof BaseCell) { - // 在单元格中,返回 true + if (this.isCellType(parent)) { + // 在单元格中,返回true return parent as T; } @@ -618,12 +634,12 @@ export abstract class SpreadSheet extends EE { : false; return { + grandTotalsLabel: i18n('总计'), + subTotalsLabel: i18n('小计'), + grandTotalsGroupDimensions: [], + subTotalsGroupDimensions: [], + ...totalConfig, showSubTotals, - showGrandTotals: totalConfig.showGrandTotals, - reverseGrandTotalsLayout: totalConfig.reverseGrandTotalsLayout, - reverseSubTotalsLayout: totalConfig.reverseSubTotalsLayout, - grandTotalsLabel: totalConfig.grandTotalsLabel || i18n('总计'), - subTotalsLabel: totalConfig.subTotalsLabel || i18n('小计'), }; } diff --git a/packages/s2-core/src/theme/index.ts b/packages/s2-core/src/theme/index.ts index 31ded99ace..84ce9d1088 100644 --- a/packages/s2-core/src/theme/index.ts +++ b/packages/s2-core/src/theme/index.ts @@ -1,5 +1,9 @@ /* eslint-disable max-lines-per-function */ -import { FONT_FAMILY, INTERVAL_BAR_HEIGHT } from '../common/constant'; +import { + FONT_FAMILY, + INTERVAL_BAR_HEIGHT, + LayoutWidthType, +} from '../common/constant'; import type { DefaultCellTheme, S2Theme, @@ -24,20 +28,27 @@ export const getTheme = ( } = themeCfg?.palette || getPalette(themeCfg?.name); const isTable = themeCfg?.spreadsheet?.isTableMode(); + const isCompactMode = + themeCfg?.spreadsheet?.getLayoutWidthType() === LayoutWidthType.Compact; const boldTextDefaultFontWeight = isWindows() ? 'bold' : 700; - const getHeaderCellTextOverflow = (): TextTheme => ({ - wordWrap: true, - maxLines: 1, - textOverflow: 'ellipsis', - }); + const getHeaderCellTextOverflow = (): TextTheme => { + return { + wordWrap: true, + maxLines: 1, + textOverflow: 'ellipsis', + }; + }; - const getDataCellTextOverflow = (): TextTheme => ({ - wordWrap: true, - // 数值单元格不建议文字换行, 通常是展示数值, 会有歧义 (明细表除外, 自行覆盖主题配置) - maxLines: 1, - textOverflow: 'ellipsis', - }); + const getDataCellTextOverflow = (): TextTheme => { + return { + // 紧凑模式下文本内容自适应, 不显示省略号 + wordWrap: !isCompactMode, + // 数值单元格不建议文字换行, 通常是展示数值, 会有歧义 (明细表除外, 自行覆盖主题配置) + maxLines: 1, + textOverflow: isCompactMode ? '' : 'ellipsis', + }; + }; const getDataCell = (): DefaultCellTheme => ({ bolderText: { @@ -187,7 +198,7 @@ export const getTheme = ( return { // ------------- Headers ------------------- cornerCell: { - bolderText: { + text: { fontFamily: FONT_FAMILY, fontSize: 12, fontWeight: boldTextDefaultFontWeight, @@ -197,13 +208,13 @@ export const getTheme = ( textBaseline: 'middle', ...getHeaderCellTextOverflow(), }, - text: { + bolderText: { fontFamily: FONT_FAMILY, fontSize: 12, fontWeight: boldTextDefaultFontWeight, fill: basicColors[0], opacity: 1, - textAlign: 'right', + textAlign: isTable ? 'center' : 'right', textBaseline: 'middle', ...getHeaderCellTextOverflow(), }, @@ -219,6 +230,8 @@ export const getTheme = ( // ----------- border width -------------- horizontalBorderWidth: 1, verticalBorderWidth: 1, + // -------------- border dash ----------------- + borderDash: [], // -------------- layout ----------------- padding: { top: 4, @@ -296,6 +309,8 @@ export const getTheme = ( // ----------- bottom border width -------------- horizontalBorderWidth: 1, verticalBorderWidth: 1, + // -------------- border dash ----------------- + borderDash: [], // -------------- layout ----------------- padding: { top: 4, @@ -395,6 +410,8 @@ export const getTheme = ( // ----------- border width -------------- horizontalBorderWidth: 1, verticalBorderWidth: 1, + // -------------- border dash ----------------- + borderDash: [], // -------------- layout ----------------- padding: { top: 4, @@ -498,6 +515,7 @@ export const getTheme = ( left: 'rgba(0,0,0,0.1)', right: 'rgba(0,0,0,0)', }, + borderDash: [], }, // ------------- prepareSelectMask ----------------- prepareSelectMask: { diff --git a/packages/s2-core/src/utils/cell/cell.ts b/packages/s2-core/src/utils/cell/cell.ts index 171de172bb..8b04d6a7f9 100644 --- a/packages/s2-core/src/utils/cell/cell.ts +++ b/packages/s2-core/src/utils/cell/cell.ts @@ -1,3 +1,5 @@ +import type { LineStyleProps } from '@antv/g'; +import { isEmpty } from 'lodash'; import type { SimpleBBox } from '../../engine'; import { CellClipBox, @@ -195,9 +197,15 @@ export const getBorderPositionAndStyle = ( verticalBorderWidth = 0, verticalBorderColor, verticalBorderColorOpacity, + borderDash, } = style; - const borderStyle = [ + // TODO: 如果是空数组 G 底层绘制会报错 + const lineDash: LineStyleProps['lineDash'] = isEmpty(borderDash) + ? '' + : borderDash; + + const borderStyle: Partial<LineStyleProps> = [ CellBorderPosition.TOP, CellBorderPosition.BOTTOM, ].includes(position) @@ -205,11 +213,13 @@ export const getBorderPositionAndStyle = ( lineWidth: horizontalBorderWidth, stroke: horizontalBorderColor, strokeOpacity: horizontalBorderColorOpacity, + lineDash, } : { lineWidth: verticalBorderWidth, stroke: verticalBorderColor, strokeOpacity: verticalBorderColorOpacity, + lineDash, }; let x1 = 0; diff --git a/packages/s2-core/src/utils/data-set-operate.ts b/packages/s2-core/src/utils/data-set-operate.ts index 7717d76fad..753dbe2f4a 100644 --- a/packages/s2-core/src/utils/data-set-operate.ts +++ b/packages/s2-core/src/utils/data-set-operate.ts @@ -1,27 +1,16 @@ -import { every, flatMap, get, has, isArray, keys } from 'lodash'; +import { isArray, flattenDeep } from 'lodash'; import { - DataSelectType, - DEFAULT_TOTAL_SELECTIONS, -} from '../common/constant/total'; -import { TOTAL_VALUE } from '../common/constant/basic'; -import type { - RawData, - Totals, - TotalsStatus, - FlattingIndexesData, - CustomHeaderFields, - CalcTotals, -} from '../common/interface'; -import type { TotalSelectionsOfMultiData } from '../data-set/interface'; -import { customMerge } from './merge'; -import { filterExtraDimension } from './dataset/pivot-data-set'; + EMPTY_EXTRA_FIELD_PLACEHOLDER, + TOTAL_VALUE, +} from '../common/constant/field'; +import type { CalcTotals, Totals, TotalsStatus } from '../common/interface'; export const getListBySorted = ( list: string[], sorted: string[], mapValue?: (val: string) => string, -) => - list.sort((a, b) => { +) => { + return list.sort((a, b) => { if (mapValue) { a = mapValue(a); b = mapValue(b); @@ -44,37 +33,34 @@ export const getListBySorted = ( return ia - ib; }); +}; -export const filterTotal = (values: string[] = []) => - values.filter((v) => v !== TOTAL_VALUE); - -export const getFieldKeysByDimensionValues = ( - dimensionValues: string[] | undefined[], - dimensions: CustomHeaderFields, +export const filterOutDetail = (values: string[] = []) => { + return values.filter( + (v) => v !== TOTAL_VALUE && v !== EMPTY_EXTRA_FIELD_PLACEHOLDER, + ); +}; +export const customFlattenDeep = ( + data: Record<any, any>[] | Record<any, any>, ) => { - const result: string[] = []; - - dimensionValues?.forEach((item, index) => { - if (item === undefined) { - if (dimensions[index]) { - result.push(dimensions[index] as string); - } - } - }); + if (!isArray(data)) { + return [data]; + } - return result; + return flattenDeep(data); }; /** * arr1包含arr2,将arr2排到最后 * */ -export const sortByItems = (arr1: string[], arr2: string[]) => - arr1?.filter((item) => !arr2?.includes(item))?.concat(arr2); +export const sortByItems = (arr1: string[], arr2: string[]) => { + return arr1?.filter((item) => !arr2?.includes(item))?.concat(arr2); +}; export function getAggregationAndCalcFuncByQuery( totalsStatus: TotalsStatus, - totalsOptions?: Totals, + totalsOptions?: Totals | null, ) { const { isRowTotal, isRowSubTotal, isColTotal, isColSubTotal } = totalsStatus; const { row, col } = totalsOptions || {}; @@ -104,88 +90,3 @@ export function getAggregationAndCalcFuncByQuery( getCalcTotals(rowCalcSubTotals, isRowSubTotal) ); } - -/** - * 判断是普通单元格数据还是总计或小计 - * @param ids - * @param data - * @returns - */ -export function isTotalData(ids: string[], data: RawData): boolean { - return !every(filterExtraDimension(ids), (id) => has(data, id)); -} - -export function getTotalSelection(totals = {} as TotalSelectionsOfMultiData) { - return customMerge<TotalSelectionsOfMultiData>( - DEFAULT_TOTAL_SELECTIONS, - totals, - ); -} - -export function flattenIndexesData( - data: FlattingIndexesData, - selectType: DataSelectType = DataSelectType.All, -) { - if (!isArray(data)) { - return [data]; - } - - return flatMap(data, (dimensionData) => { - if (!isArray(dimensionData)) { - return [dimensionData]; - } - - // 数组的第 0 项是总计/小计专位,从第 1 项开始是明细数据 - const startIdx = selectType === DataSelectType.DetailOnly ? 1 : 0; - const length = selectType === DataSelectType.TotalOnly ? 1 : undefined; - - return dimensionData.slice(startIdx, length).filter(Boolean); - }); -} - -export const flatten = (data: Record<any, any>[] | Record<any, any>) => { - const result = []; - - if (Array.isArray(data)) { - // 总计小计在数组里面,以 undefine作为key, 直接forEach的话会漏掉总计小计 - const containsTotal = 'undefined' in data; - const itemLength = data.length + (containsTotal ? 1 : 0); - - let i = 0; - - while (i < itemLength) { - // eslint-disable-next-line dot-notation - const current = - i === data.length ? data['undefined' as unknown as number] : data[i]; - - i++; - - if (current && 'undefined' in current) { - keys(current).forEach((ki) => { - result.push(current[ki]); - }); - } else if (Array.isArray(current)) { - result.push(...current); - } else { - result.push(current); - } - } - } else { - result.push(data); - } - - return result; -}; - -export const flattenDeep = (data: Record<any, any>[] | Record<any, any>) => - keys(data)?.reduce((pre, next) => { - const item = get(data, next); - - if (Array.isArray(item)) { - pre = pre.concat(flattenDeep(item)); - } else { - pre?.push(item as unknown as never); - } - - return pre; - }, []); diff --git a/packages/s2-core/src/utils/dataset/pivot-data-set.ts b/packages/s2-core/src/utils/dataset/pivot-data-set.ts index f81c264476..013c4654df 100644 --- a/packages/s2-core/src/utils/dataset/pivot-data-set.ts +++ b/packages/s2-core/src/utils/dataset/pivot-data-set.ts @@ -1,52 +1,129 @@ -import { forEach, has, intersection, last, set, find, get } from 'lodash'; -import type { CellData } from '../../data-set/cell-data'; import { + compact, + find, + flatMap, + forEach, + get, + indexOf, + intersection, + isArray, + isEmpty, + isNull, + last, + set, + sortBy, +} from 'lodash'; +import { + EMPTY_EXTRA_FIELD_PLACEHOLDER, EXTRA_FIELD, + MULTI_VALUE, NODE_ID_SEPARATOR, + QueryDataType, ROOT_NODE_ID, TOTAL_VALUE, - MULTI_VALUE, } from '../../common/constant'; -import type { BaseFields, RawData } from '../../common/interface'; +import type { Meta } from '../../common/interface/basic'; +import type { PickEssential } from '../../common/interface/utils'; +import type { CellData } from '../../data-set/cell-data'; import type { DataPath, DataPathParams, + FlattingIndexesData, + OnFirstCreateParams, PivotMeta, + PivotMetaValue, SortedDimensionValues, + TotalStatus, } from '../../data-set/interface'; -import type { Meta } from '../../common/interface/basic'; +import type { Node } from '../../facet/layout/node'; +import type { RawData } from '../../common'; export function filterExtraDimension(dimensions: string[] = []) { return dimensions.filter((d) => d !== EXTRA_FIELD); } -export function transformDimensionsValues( - record: RawData, - dimensions: string[], - placeholder: string = TOTAL_VALUE, -): string[] { - return filterExtraDimension(dimensions).map((dimension) => - !has(record, dimension) ? placeholder : String(record[dimension]), - ); -} - -export function shouldQueryMultiData(pathValue: string | number) { +export function isMultiValue(pathValue: string | number | undefined) { return pathValue === MULTI_VALUE; } /** - * Get dimensions without path pre - * dimensions: ['辽宁省[&]芜湖市[&]家具[&]椅子'] - * return ['椅子'] + * Transform from origin single data to correct dimension values + * data: { + * price: 16, + * province: '辽宁省', + * city: '芜湖市', + * category: '家具', + * subCategory: '椅子', + * } + * dimensions: [province, city] + * return [辽宁省, 芜湖市] * + * @param record * @param dimensions */ -export function getDimensionsWithoutPathPre(dimensions: string[]) { - return dimensions.map((item) => { - const splitArr = item?.split(NODE_ID_SEPARATOR); - return splitArr?.[splitArr?.length! - 1] || item; - }); +export function transformDimensionsValues( + record: RawData = {}, + dimensions: string[] = [], + placeholder = TOTAL_VALUE, +): string[] { + return dimensions.reduce((res: string[], dimension: string) => { + const value = record[dimension]; + + if (!(dimension in record)) { + res.push(placeholder); + } else { + res.push(String(value)); + } + + return res; + }, []); +} + +export function getExistValues(data: RawData, values: string[]) { + const result = values.filter((v) => v in data); + + if (isEmpty(result)) { + result.push(EMPTY_EXTRA_FIELD_PLACEHOLDER); + } + + return result; +} + +export function transformDimensionsValuesWithExtraFields( + record: RawData = {}, + dimensions: string[] = [], + values: string[] | null, +) { + const result = []; + + function transform(data: RawData, fields: string[], valueField?: string) { + return fields.reduce((res: string[], dimension: string) => { + const value = data[dimension]; + + if (!(dimension in data)) { + if (dimension === EXTRA_FIELD && valueField) { + res.push(valueField); + } else { + res.push(TOTAL_VALUE); + } + } else { + res.push(String(value)); + } + + return res; + }, []); + } + + if (values) { + values.forEach((value) => { + result.push(transform(record, dimensions, value)); + }); + } else { + result.push(transform(record, dimensions)); + } + + return result; } /** @@ -85,6 +162,13 @@ export function getDimensionsWithParentPath( ?.filter(Boolean); } +export function getDataPathPrefix(rowFields: string[], colFields: string[]) { + return rowFields + .concat(colFields) + .filter((i) => i !== EXTRA_FIELD) + .join(NODE_ID_SEPARATOR); +} + /** * Transform a single data to path * { @@ -106,73 +190,75 @@ export function getDimensionsWithParentPath( */ export function getDataPath(params: DataPathParams): DataPath { const { - rows, - columns, - values, rowDimensionValues, colDimensionValues, - shouldCreateOrUpdate, - onCreate, + isFirstCreate, + onFirstCreate, + rowFields, + colFields, rowPivotMeta, colPivotMeta, + prefix = '', } = params; - // TODO: extract as a layout hook - const appendValues = () => { - const map = new Map(); - - values?.forEach((v, idx) => { - map.set(v, { - level: idx, - children: new Map(), - }); - }); - - return map; - }; - - /* - * 根据行、列维度值生成对应的 path路径,始终将总计小计置于第 0 位,明细数据从第 1 位开始,有两个情况: - * 如果是汇总格子: path = [0,0,0,0] path中会存在 0的值(这里在indexesData里面会映射) - * 如果是明细格子: path = [1,1,1] 数字均不为0 - */ + // 根据行、列维度值生成对应的 path 路径,始终将总计小计置于第 0 位,明细数据从第 1 位开始,有两个情况: + // 如果是汇总格子: path = [0, 0, 0, 0] path 中会存在 0 的值 + // 如果是明细格子: path = [1, 1, 1] 数字均不为 0 const getPath = ( dimensions: string[], dimensionValues: string[], pivotMeta: PivotMeta, - ): DataPath => { + careRepeated: boolean, + ) => { let currentMeta = pivotMeta; const path: DataPath = []; for (let i = 0; i < dimensionValues.length; i++) { const value = dimensionValues[i]; - const isTotal = value === TOTAL_VALUE; - if (shouldCreateOrUpdate && !currentMeta.has(value)) { + if (isFirstCreate && currentMeta && !currentMeta?.has(value)) { + const currentDimensions = dimensionValues + .slice(0, i + 1) + .map((it) => String(it)); + const id = currentDimensions.join(NODE_ID_SEPARATOR); + + const isTotal = value === TOTAL_VALUE; + + let level; + + if (isTotal) { + level = 0; + } else if (currentMeta.has(TOTAL_VALUE)) { + level = currentMeta.size; + } else { + level = currentMeta.size + 1; + } + currentMeta.set(value, { - level: isTotal ? 0 : currentMeta.size + 1, - childField: dimensions?.[i + 1], - children: - dimensions?.[i + 1] === EXTRA_FIELD ? appendValues() : new Map(), + id, + dimensions: currentDimensions, + value, + level, + children: new Map(), }); - onCreate?.({ + onFirstCreate?.({ dimension: dimensions?.[i], - dimensionPath: dimensionValues.slice(0, i + 1), + dimensionPath: id, + careRepeated, }); } - const meta = currentMeta.get(value); + const meta = currentMeta?.get(value); - // 只出现在 getCellMultiData 中, 使用特殊的 value 指明当前复合选择 - if (value === MULTI_VALUE) { - path.push(value); - } else { - path.push(meta?.level!); - } + path.push(isMultiValue(value) ? value : meta?.level); if (meta) { - if (shouldCreateOrUpdate && meta.childField !== dimensions?.[i + 1]) { - meta.childField = dimensions?.[i + 1]; + const childDimension = dimensions?.[i + 1]; + + if (isFirstCreate && meta.childField !== childDimension) { + // mark the child field + // NOTE: should take more care when reset meta.childField to undefined, the meta info is shared with brother nodes. + meta.childField = childDimension; } currentMeta = meta?.children; @@ -182,38 +268,47 @@ export function getDataPath(params: DataPathParams): DataPath { return path; }; - const rowPath = getPath(rows as string[], rowDimensionValues, rowPivotMeta!); - const colPath = getPath( - columns as string[], - colDimensionValues, - colPivotMeta!, - ); - const result = rowPath.concat(...colPath); + const rowPath = getPath(rowFields, rowDimensionValues, rowPivotMeta, false); + const colPath = getPath(colFields, colDimensionValues, colPivotMeta, true); - return result; + return [prefix, ...rowPath, ...colPath]; } - -interface Param extends BaseFields { - originData: RawData[]; - indexesData: RawData[][] | RawData[]; +interface Param { + rows: string[]; + columns: string[]; + values: string[]; + valueInCols: boolean; + data: RawData[]; + indexesData: Record<string, RawData[][] | RawData[]>; sortedDimensionValues: SortedDimensionValues; rowPivotMeta?: PivotMeta; colPivotMeta?: PivotMeta; + getExistValuesByDataItem?: (data: RawData, values: string[]) => string[]; +} + +export interface TransformResult { + paths: DataPath[]; + indexesData: Record<string, RawData[][] | RawData[]>; + rowPivotMeta: PivotMeta; + colPivotMeta: PivotMeta; + sortedDimensionValues: SortedDimensionValues; } /** - * 转换原始数据为多维数组数据 + * 转换原始数据为二维数组数据 */ -export function transformIndexesData(params: Param) { +export function transformIndexesData(params: Param): TransformResult { const { rows, columns, values, - originData = [], - indexesData = [], + valueInCols, + data = [], + indexesData = {}, sortedDimensionValues, rowPivotMeta, colPivotMeta, + getExistValuesByDataItem, } = params; const paths: DataPath[] = []; @@ -229,64 +324,68 @@ export function transformIndexesData(params: Param) { const onFirstCreate = ({ dimension, dimensionPath, - }: { - // 维度 id,如 city - dimension: string; - // 维度数组 ['四川省', '成都市'] - dimensionPath: string[]; - }) => { - if (repeatedDimensionSet.has(dimension)) { - /* - * 当行、列都配置了同一维度字段时,因为 getDataPath 先处理行、再处理列 - * 所有重复字段的维度值无需再加入到 sortedDimensionValues - */ + careRepeated = true, + }: OnFirstCreateParams) => { + if (careRepeated && repeatedDimensionSet.has(dimension)) { + // 当行、列都配置了同一维度字段时,因为 getDataPath 先处理行、再处理列 + // 所有重复字段的维度值无需再加入到 sortedDimensionValues return; } ( sortedDimensionValues[dimension] || (sortedDimensionValues[dimension] = []) - ).push( - // 拼接维度路径 [1, undefined] => ['1', 'undefined'] => '1[&]undefined - dimensionPath.map((it) => `${it}`).join(NODE_ID_SEPARATOR), - ); + ).push(dimensionPath); }; - originData.forEach((data) => { + const prefix = getDataPathPrefix(rows, columns as string[]); + + data.forEach((item: RawData) => { // 空数据没有意义,直接跳过 - if (!data) { + if (!item || isEmpty(item)) { return; } - const rowDimensionValues = transformDimensionsValues( - data, - rows as string[], - ); - const colDimensionValues = transformDimensionsValues( - data, - columns as string[], - ); - const path = getDataPath({ - rowDimensionValues, - colDimensionValues, - rowPivotMeta, - colPivotMeta, - shouldCreateOrUpdate: true, - onCreate: onFirstCreate, + const existValues = getExistValuesByDataItem + ? getExistValuesByDataItem(item, values) + : getExistValues(item, values); + + const multiRowDimensionValues = transformDimensionsValuesWithExtraFields( + item, rows, + valueInCols ? null : existValues, + ); + const multiColDimensionValues = transformDimensionsValuesWithExtraFields( + item, columns, - values, - }); + valueInCols ? existValues : null, + ); + + for (const rowDimensionValues of multiRowDimensionValues) { + for (const colDimensionValues of multiColDimensionValues) { + const path = getDataPath({ + rowDimensionValues, + colDimensionValues, + rowPivotMeta: rowPivotMeta!, + colPivotMeta: colPivotMeta!, + rowFields: rows, + colFields: columns, + isFirstCreate: true, + onFirstCreate, + prefix, + }); - paths.push(path); - set(indexesData, path, data); + paths.push(path); + set(indexesData, path as (string | number)[], item); + } + } }); return { paths, indexesData, - rowPivotMeta, - colPivotMeta, + rowPivotMeta: rowPivotMeta!, + colPivotMeta: colPivotMeta!, sortedDimensionValues, }; } @@ -340,3 +439,206 @@ export function generateExtraFieldMeta( return extraFieldMeta; } + +export function getHeaderTotalStatus(row: Node, col: Node): TotalStatus { + return { + isRowTotal: row.isGrandTotals!, + isRowSubTotal: row.isSubTotals!, + isColTotal: col.isGrandTotals!, + isColSubTotal: col.isSubTotals!, + }; +} + +/** + * 检查 getMultiData 时,传入的 query 是否是包含总计、小计分组的场景 + * MULTI_VALUE 后面再出现具体的名字,就表明是分组场景 + * 以 rows: [province, city] 为例 + * 如果是: [四川, MULTI_VALUE] => 代表获取四川下面的所有 city + * 如果是: [MULTI_VALUE, 成都] => 这种结果就是所有成都的小计分组 + * 每个 province 下面的 city 都不一样的 + * 就算换成 [province, sex] => [MULTI_VALUE, 女] 这样的形式,去获取所有 province 下的女性,但是每个 province 下的女性的 index 也可能不同 + * 需要将其拓展成多个结构 => [MULTI_VALUE, 女] => [[四川,女], [北京,女], ....] => [[1,1],[2,1],[3,2]....] + */ +export function existDimensionTotalGroup(path: string[]) { + let multiIdx = null; + + for (let i = 0; i < path.length; i++) { + const element = path[i]; + + if (isMultiValue(element)) { + multiIdx = i; + } else if (!isNull(multiIdx) && multiIdx < i) { + return true; + } + } + + return false; +} + +export function getSatisfiedPivotMetaValues(params: { + pivotMeta: PivotMeta; + dimensionValues: string[]; + fieldIdx: number; + queryType: QueryDataType; + fields: string[]; + sortedDimensionValues: SortedDimensionValues; +}) { + const { + pivotMeta, + dimensionValues, + fieldIdx, + queryType, + fields, + sortedDimensionValues, + } = params; + const rootContainer = { + children: pivotMeta, + } as PivotMetaValue; + + let metaValueList = [rootContainer]; + + function flattenMetaValue(list: PivotMetaValue[], field: string) { + const allValues = flatMap(list, (metaValue) => { + const values = [...metaValue.children.values()]; + + return values.filter( + (v) => + v.value !== EMPTY_EXTRA_FIELD_PLACEHOLDER && + (queryType === QueryDataType.All ? true : v.value !== TOTAL_VALUE), + ); + }); + + if (list.length > 1) { + // 从不同父维度中获取的子维度需要再排一次,比如province => city 按照字母倒序,那么在获取了所有 province 的 city 后需要再排一次 + const sortedDimensionValue = sortedDimensionValues[field] ?? []; + + return sortBy(allValues, (item) => + indexOf(sortedDimensionValue, item.id), + ); + } + + return allValues; + } + + for (let i = 0; i <= fieldIdx; i++) { + const dimensionValue = dimensionValues[i]; + const field = fields[i]; + + if (isMultiValue(dimensionValue)) { + metaValueList = flattenMetaValue(metaValueList, field); + } else { + metaValueList = metaValueList + .map((v) => v.children.get(dimensionValue)!) + .filter(Boolean); + } + } + + return metaValueList; +} + +export function flattenDimensionValues(params: { + dimensionValues: string[]; + pivotMeta: PivotMeta; + fields: string[]; + sortedDimensionValues: SortedDimensionValues; + queryType?: QueryDataType; +}) { + const { + dimensionValues, + pivotMeta, + fields, + sortedDimensionValues, + queryType = QueryDataType.All, + } = params; + + if (!existDimensionTotalGroup(dimensionValues)) { + return [dimensionValues]; + } + + const metaValues = getSatisfiedPivotMetaValues({ + pivotMeta, + dimensionValues, + fieldIdx: dimensionValues.length - 1, + queryType, + fields, + sortedDimensionValues, + }); + + return metaValues.map((v) => v.dimensions); +} + +export function getFlattenDimensionValues( + params: PickEssential<DataPathParams> & { + sortedDimensionValues: SortedDimensionValues; + queryType: QueryDataType; + }, +) { + const { + rowFields, + rowDimensionValues, + rowPivotMeta, + colFields, + colDimensionValues, + colPivotMeta, + queryType, + sortedDimensionValues, + } = params; + const rowQueries = flattenDimensionValues({ + dimensionValues: rowDimensionValues, + pivotMeta: rowPivotMeta, + fields: rowFields, + sortedDimensionValues, + queryType, + }); + const colQueries = flattenDimensionValues({ + dimensionValues: colDimensionValues, + pivotMeta: colPivotMeta, + fields: colFields, + sortedDimensionValues, + queryType, + }); + + return { + rowQueries, + colQueries, + }; +} + +export function flattenIndexesData( + data: FlattingIndexesData, + queryType: QueryDataType, +): FlattingIndexesData { + if (!data) { + return []; + } + + if (!isArray(data)) { + return compact([data]); + } + + return flatMap(data, (dimensionData: RawData[]) => { + if (!isArray(dimensionData)) { + return compact([dimensionData]) as RawData[]; + } + + // 数组的第 0 项是总计/小计专位,从第 1 项开始是明细数据 + const startIdx = queryType === QueryDataType.DetailOnly ? 1 : 0; + + return compact(dimensionData.slice(startIdx)); + }) as unknown as FlattingIndexesData; +} + +/** + * Get dimensions without path pre + * dimensions: ['辽宁省[&]芜湖市[&]家具[&]椅子'] + * return ['椅子'] + * + * @param dimensions + */ +export function getDimensionsWithoutPathPre(dimensions: string[]) { + return dimensions.map((item) => { + const splitArr = item?.split(NODE_ID_SEPARATOR); + + return splitArr[splitArr?.length - 1] ?? item; + }); +} diff --git a/packages/s2-core/src/utils/export/copy/base-data-cell-copy.ts b/packages/s2-core/src/utils/export/copy/base-data-cell-copy.ts index 3d7497032a..769500f8e3 100644 --- a/packages/s2-core/src/utils/export/copy/base-data-cell-copy.ts +++ b/packages/s2-core/src/utils/export/copy/base-data-cell-copy.ts @@ -4,9 +4,9 @@ import type { CopyablePlain, CopyAndExportUnifyConfig, SheetCopyConstructorParams, -} from '../interface'; +} from '../../../common/interface/export'; import { type DataItem, NewTab } from '../../../common'; -import { CopyMIMEType } from '../interface'; +import { CopyMIMEType } from '../../../common/interface/export'; import { unifyConfig } from './common'; // BaseDataCellCopy class diff --git a/packages/s2-core/src/utils/export/copy/common.ts b/packages/s2-core/src/utils/export/copy/common.ts index dacbc57b90..d6976f6664 100644 --- a/packages/s2-core/src/utils/export/copy/common.ts +++ b/packages/s2-core/src/utils/export/copy/common.ts @@ -12,7 +12,7 @@ import { type MatrixPlainTransformer, type MatrixHTMLTransformer, CopyMIMEType, -} from '../interface'; +} from '../../../common/interface/export'; import type { BaseDataSet } from './../../../data-set/base-data-set'; // 把 string[][] 矩阵转换成 CopyablePlain diff --git a/packages/s2-core/src/utils/export/copy/core.ts b/packages/s2-core/src/utils/export/copy/core.ts index d06016f8fe..561355b51f 100644 --- a/packages/s2-core/src/utils/export/copy/core.ts +++ b/packages/s2-core/src/utils/export/copy/core.ts @@ -7,14 +7,14 @@ import { type S2CellType, } from '../../../common'; import type { SpreadSheet } from '../../../sheet-type'; -import { copyToClipboard } from '../index'; +import { copyToClipboard } from '../utils'; import type { ColCell, RowCell } from '../../../cell'; import { getSelectedCols, getSelectedRows } from '../method'; import { type CopyableList, type CopyAllDataParams, CopyMIMEType, -} from '../interface'; +} from '../../../common/interface/export'; import { getBrushHeaderCopyable } from './pivot-header-copy'; import { processSelectedAllPivot, @@ -42,9 +42,8 @@ export const getHeaderNodeFromMeta = ( /** * 返回选中数据单元格生成的二维数组( CellMeta[][]) * @param { CellMeta[] } cells - * @return { CellMeta[][] } */ -const getSelectedCellsMeta = (cells: CellMeta[]) => { +const getSelectedCellsMeta = (cells: CellMeta[]): CellMeta[][] => { if (!cells?.length) { return []; } diff --git a/packages/s2-core/src/utils/export/copy/index.ts b/packages/s2-core/src/utils/export/copy/index.ts index 3cc9bd16be..123563161b 100644 --- a/packages/s2-core/src/utils/export/copy/index.ts +++ b/packages/s2-core/src/utils/export/copy/index.ts @@ -1,4 +1,4 @@ -import type { SheetCopyConstructorParams } from '../interface'; +import type { SheetCopyConstructorParams } from '../../../common/interface/export'; import { getSelectedData } from './core'; import { PivotDataCellCopy } from './pivot-data-cell-copy'; diff --git a/packages/s2-core/src/utils/export/copy/pivot-data-cell-copy.ts b/packages/s2-core/src/utils/export/copy/pivot-data-cell-copy.ts index 1df16a9f85..8c8471dbce 100644 --- a/packages/s2-core/src/utils/export/copy/pivot-data-cell-copy.ts +++ b/packages/s2-core/src/utils/export/copy/pivot-data-cell-copy.ts @@ -15,7 +15,7 @@ import type { CopyableList, MeasureQuery, SheetCopyConstructorParams, -} from '../interface'; +} from '../../../common/interface/export'; import { convertString, getColNodeFieldFromNode, @@ -23,6 +23,7 @@ import { getSelectedCols, getSelectedRows, } from '../method'; +import type { CellData } from '../../../data-set'; import type { BaseDataSet } from './../../../data-set/base-data-set'; import { BaseDataCellCopy } from './base-data-cell-copy'; import { @@ -231,7 +232,7 @@ export class PivotDataCellCopy extends BaseDataCellCopy { dataSet, ); - const fieldValue = cellData?.[VALUE_FIELD]; + const fieldValue = (cellData as CellData)?.[VALUE_FIELD]; const isChartData = isPlainObject( (fieldValue as MultiData<MiniChartData>)?.values, ); diff --git a/packages/s2-core/src/utils/export/copy/pivot-header-copy.ts b/packages/s2-core/src/utils/export/copy/pivot-header-copy.ts index 999938f8a9..273f1a3184 100644 --- a/packages/s2-core/src/utils/export/copy/pivot-header-copy.ts +++ b/packages/s2-core/src/utils/export/copy/pivot-header-copy.ts @@ -1,6 +1,6 @@ import { filter, isEmpty, map, max, repeat, zip } from 'lodash'; import type { ColCell, RowCell } from '../../../cell'; -import type { CopyableList } from '../interface'; +import type { CopyableList } from '../../../common/interface/export'; import { getAllLevels, getHeaderList } from '../method'; import { CellType, NODE_ID_SEPARATOR } from '../../../common'; import { matrixHtmlTransformer, matrixPlainTextTransformer } from './common'; diff --git a/packages/s2-core/src/utils/export/copy/table-copy.ts b/packages/s2-core/src/utils/export/copy/table-copy.ts index 90d3f280e1..92117589f8 100644 --- a/packages/s2-core/src/utils/export/copy/table-copy.ts +++ b/packages/s2-core/src/utils/export/copy/table-copy.ts @@ -12,7 +12,7 @@ import type { CopyableList, CopyAllDataParams, SheetCopyConstructorParams, -} from '../interface'; +} from '../../../common/interface/export'; import { convertString, getColNodeFieldFromNode, diff --git a/packages/s2-core/src/utils/export/index.ts b/packages/s2-core/src/utils/export/index.ts index bcb3b7787f..57837de931 100644 --- a/packages/s2-core/src/utils/export/index.ts +++ b/packages/s2-core/src/utils/export/index.ts @@ -1,128 +1,12 @@ -import { concat, get } from 'lodash'; -import { - CopyMIMEType, - type Copyable, - type CopyableItem, - type FormatOptions, - type CopyableList, - type CopyAllDataParams, -} from './interface'; -import { processAllSelected, processAllSelectedAsync } from './copy/core'; -import { getNodeFormatData, assembleMatrix, getMaxRowLen } from './copy/common'; +import type { + CopyableList, + FormatOptions, +} from '../../common/interface/export'; +import { assembleMatrix, getMaxRowLen, getNodeFormatData } from './copy/common'; import { getHeaderList } from './method'; -export const copyToClipboardByExecCommand = (data: Copyable): Promise<void> => - new Promise((resolve, reject) => { - let content: string; - - if (Array.isArray(data)) { - content = get( - data.filter((item) => item.type === CopyMIMEType.PLAIN), - '[0].content', - '', - ) as string; - } else { - content = data.content || ''; - } - - const textarea = document.createElement('textarea'); - - textarea.value = content; - document.body.appendChild(textarea); - // 开启 preventScroll, 防止页面有滚动条时触发滚动 - textarea.focus({ preventScroll: true }); - textarea.select(); - - const success = document.execCommand('copy'); - - document.body.removeChild(textarea); - - if (success) { - resolve(); - } else { - reject(); - } - }); - -export const copyToClipboardByClipboard = (data: Copyable): Promise<void> => - navigator.clipboard - .write([ - new ClipboardItem( - concat(data).reduce((prev, copyable: CopyableItem) => { - const { type, content } = copyable; - - return { - ...prev, - [type]: new Blob([content], { type }), - }; - }, {}), - ), - ]) - .catch(() => copyToClipboardByExecCommand(data)); - -export const copyToClipboard = ( - data: Copyable | string, - sync = false, -): Promise<void> => { - let copyableItem: Copyable; - - if (typeof data === 'string') { - copyableItem = { - content: data, - type: CopyMIMEType.PLAIN, - }; - } else { - copyableItem = data; - } - - if (!navigator.clipboard || !window.ClipboardItem || sync) { - return copyToClipboardByExecCommand(copyableItem); - } - - return copyToClipboardByClipboard(copyableItem); -}; - -export const download = (str: string, fileName: string) => { - try { - const link = document.createElement('a'); - - link.download = `${fileName}.csv`; - // Avoid errors in Chinese encoding. - const dataBlob = new Blob([`\ufeff${str}`], { - type: 'text/csv;charset=utf-8', - }); - - link.href = URL.createObjectURL(dataBlob); - link.click(); - URL.revokeObjectURL(link.href); - } catch (e) { - // eslint-disable-next-line no-console - console.error(e); - } -}; - -/** - * Copy data - * @param sheetInstance - * @param split - * @param formatOptions 是否格式化数据 - * @param customTransformer - * @param isAsyncExport 是否异步导出 - * @deprecated 后续将废弃方法,将使用 asyncGetAllPlainData - */ -// TODO: 改名 -export const copyData = (params: CopyAllDataParams) => { - const result = processAllSelected(params); - - return result[0].content; -}; - -export const asyncGetAllPlainData = async (params: CopyAllDataParams) => { - const result = await processAllSelectedAsync(params); - - return result[0].content; -}; - export type { CopyableList, FormatOptions }; export { assembleMatrix, getMaxRowLen, getNodeFormatData }; export { getHeaderList }; +export * from './utils'; +export * from './copy'; diff --git a/packages/s2-core/src/utils/export/utils.ts b/packages/s2-core/src/utils/export/utils.ts new file mode 100644 index 0000000000..d1f1eb2745 --- /dev/null +++ b/packages/s2-core/src/utils/export/utils.ts @@ -0,0 +1,137 @@ +import { concat, get } from 'lodash'; +import { + CopyMIMEType, + type CopyAllDataParams, + type Copyable, + type CopyableItem, +} from '../../common/interface/export'; +import { processAllSelected, processAllSelectedAsync } from './copy/core'; + +/** + * 同步复制 + */ +export const copyToClipboardByExecCommand = (data: Copyable): Promise<void> => + new Promise((resolve, reject) => { + let content: string; + + if (Array.isArray(data)) { + content = get( + data.filter((item) => item.type === CopyMIMEType.PLAIN), + '[0].content', + '', + ) as string; + } else { + content = data.content || ''; + } + + const textarea = document.createElement('textarea'); + + textarea.value = content; + document.body.appendChild(textarea); + // 开启 preventScroll, 防止页面有滚动条时触发滚动 + textarea.focus({ preventScroll: true }); + textarea.select(); + + const success = document.execCommand('copy'); + + document.body.removeChild(textarea); + + if (success) { + resolve(); + } else { + reject(); + } + }); + +/** + * 异步复制 + */ +export const copyToClipboardByClipboard = (data: Copyable): Promise<void> => + navigator.clipboard + .write([ + new ClipboardItem( + concat(data).reduce((prev, copyable: CopyableItem) => { + const { type, content } = copyable; + // eslint-disable-next-line no-control-regex + const contentToCopy = content.replace(/\x00/g, ''); + + return { + ...prev, + [type]: new Blob([contentToCopy], { type }), + }; + }, {}), + ), + ]) + .catch(() => copyToClipboardByExecCommand(data)); + +/** + * @name 复制数据 + * @param data 数据源 + * @param sync 同步复制 + */ +export const copyToClipboard = ( + data: Copyable | string, + sync = false, +): Promise<void> => { + let copyableItem: Copyable; + + if (typeof data === 'string') { + copyableItem = { + content: data, + type: CopyMIMEType.PLAIN, + }; + } else { + copyableItem = data; + } + + if (!navigator.clipboard || !window.ClipboardItem || sync) { + return copyToClipboardByExecCommand(copyableItem); + } + + return copyToClipboardByClipboard(copyableItem); +}; + +/** + * @name 导出数据 + * @param dataString 数据源 (字符串) + * @param fileName 文件名 (格式为 csv) + */ +export const download = (dataString: string, fileName: string) => { + try { + const link = document.createElement('a'); + + link.download = `${fileName}.csv`; + // Avoid errors in Chinese encoding. + const dataBlob = new Blob([`\ufeff${dataString}`], { + type: 'text/csv;charset=utf-8', + }); + + link.href = URL.createObjectURL(dataBlob); + link.click(); + URL.revokeObjectURL(link.href); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } +}; + +/** + * 同步获取文本数据 + * @param params CopyAllDataParams + * @deprecated 后续将废弃方法,将使用 asyncGetAllPlainData + */ + +export const getAllPlainData = (params: CopyAllDataParams) => { + const result = processAllSelected(params); + + return result[0].content; +}; + +/** + * @name 异步获取文本数据 + */ +export const asyncGetAllPlainData = async (params: CopyAllDataParams) => { + const result = await processAllSelectedAsync(params); + + return result[0].content; +}; diff --git a/packages/s2-core/src/utils/hide-columns.ts b/packages/s2-core/src/utils/hide-columns.ts index 1167af1ae5..c0198f0298 100644 --- a/packages/s2-core/src/utils/hide-columns.ts +++ b/packages/s2-core/src/utils/hide-columns.ts @@ -154,9 +154,18 @@ export const hideColumns = async ( ...lastHiddenColumnFields, ]); + const isAllNearToHiddenColumnNodes = getHiddenColumnNodes( + spreadsheet, + hiddenColumnFields, + ).every((node, i, nodes) => { + const nextNode = nodes[i + 1]; + + return !nextNode || Math.abs(node.colIndex - nextNode.colIndex) === 1; + }); + const displaySiblingNode = getHiddenColumnDisplaySiblingNode( spreadsheet, - selectedColumnFields, + isAllNearToHiddenColumnNodes ? hiddenColumnFields : selectedColumnFields, ); const currentHiddenColumnsInfo: HiddenColumnsInfo = { diff --git a/packages/s2-core/src/utils/index.ts b/packages/s2-core/src/utils/index.ts index def125c3d1..bde0ca1822 100644 --- a/packages/s2-core/src/utils/index.ts +++ b/packages/s2-core/src/utils/index.ts @@ -5,8 +5,7 @@ export * from './layout'; export * from './text'; export * from './color'; export * from './theme'; -export * from './export/index'; -export * from './export/copy'; +export * from './export'; export * from './interaction'; export * from './g-renders'; export * from './g-mini-charts'; @@ -18,3 +17,4 @@ export * from './cell/cell'; export * from './cell/data-cell'; export * from './sort-action'; export * from './inject-css-text'; +export * from './math'; diff --git a/packages/s2-core/src/utils/interaction/merge-cell.ts b/packages/s2-core/src/utils/interaction/merge-cell.ts index a1fb8f6e77..3b2ee286d3 100644 --- a/packages/s2-core/src/utils/interaction/merge-cell.ts +++ b/packages/s2-core/src/utils/interaction/merge-cell.ts @@ -4,6 +4,7 @@ import { filter, find, forEach, + includes, isEmpty, isEqual, map, @@ -12,12 +13,12 @@ import { MergedCell } from '../../cell/merged-cell'; import { CellType } from '../../common/constant'; import type { MergedCellInfo, - S2CellType, TempMergedCell, ViewMeta, MergedCellCallback, } from '../../common/interface'; import type { SpreadSheet } from '../../sheet-type'; +import type { DataCell } from '../../cell'; /** * according to the coordinates of the starting point of the rectangle, @@ -86,7 +87,7 @@ export const getNextEdge = ( * return all the points of the polygon * @param cells the collection of information of cells which needed be merged */ -export const getPolygonPoints = (cells: S2CellType[]) => { +export const getPolygonPoints = (cells: DataCell[]) => { let allEdges: [number, number][][] = []; cells.forEach((cell) => { @@ -111,6 +112,53 @@ export const getPolygonPoints = (cells: S2CellType[]) => { return allPoints; }; +export const getRightAndBottomCells = (cells: DataCell[]) => { + const right: DataCell[] = []; + const bottom: DataCell[] = []; + const bottomRightCornerCell: DataCell[] = []; + + cells.forEach((cell) => { + const [row, col] = cell.position; + + if ( + !find( + cells, + (temp) => temp.position[0] === row + 1 && temp.position[1] === col, + ) + ) { + bottom.push(cell); + } + + if ( + !find( + cells, + (temp) => temp.position[1] === col + 1 && temp.position[0] === row, + ) + ) { + right.push(cell); + } + }); + + // 在绘制了 right border 后,如果它上面的 cell 也是 merge cell 中的,且无需绘制 right 时,需要单独为其位置 bottomRight corner 的 border,反正连线会断 + right.forEach((cell) => { + const [row, col] = cell.position; + const top = find( + cells, + (temp) => temp.position[0] === row - 1 && temp.position[1] === col, + ); + + if (top && !includes(right, top)) { + bottomRightCornerCell.push(top); + } + }); + + return { + bottom, + right, + bottomRightCornerCell, + }; +}; + /** * get cells on the outside of visible area through mergeCellInfo * @param invisibleCellInfo @@ -120,7 +168,7 @@ export const getInvisibleInfo = ( invisibleCellInfo: MergedCellInfo[], sheet: SpreadSheet, ) => { - const cells: S2CellType[] = []; + const cells: DataCell[] = []; let viewMeta: ViewMeta | undefined; forEach(invisibleCellInfo, (cellInfo) => { @@ -148,14 +196,14 @@ export const getInvisibleInfo = ( */ export const getVisibleInfo = ( cellsInfos: MergedCellInfo[], - allVisibleCells: S2CellType[], + allVisibleCells: DataCell[], ) => { - const cells: S2CellType[] = []; + const cells: DataCell[] = []; const invisibleCellInfo: MergedCellInfo[] = []; let cellsMeta: ViewMeta | Node | undefined; forEach(cellsInfos, (cellInfo: MergedCellInfo) => { - const findCell = find(allVisibleCells, (cell: S2CellType) => { + const findCell = find(allVisibleCells, (cell: DataCell) => { const meta = cell?.getMeta?.(); if ( @@ -164,7 +212,7 @@ export const getVisibleInfo = ( ) { return cell; } - }) as S2CellType; + }) as DataCell; if (findCell) { cells.push(findCell); @@ -186,7 +234,7 @@ export const getVisibleInfo = ( * @param sheet */ export const getTempMergedCell = ( - allVisibleCells: S2CellType[], + allVisibleCells: DataCell[], sheet?: SpreadSheet, cellsInfos: MergedCellInfo[] = [], ): TempMergedCell => { @@ -195,7 +243,7 @@ export const getTempMergedCell = ( allVisibleCells, ); let viewMeta: ViewMeta | Node | undefined = cellsMeta; - let mergedAllCells: S2CellType[] = cells; + let mergedAllCells: DataCell[] = cells; // some cells are invisible and some cells are visible const isPartiallyVisible = invisibleCellInfo?.length > 0 && @@ -247,21 +295,21 @@ export const getActiveCellsInfo = (sheet: SpreadSheet) => { /** * 创建 merged cell 实例 - * @param spreasheet 表格实例 + * @param spreadsheet 表格实例 * @param cells 待合并的单元格 * @param meta 元信息 * @returns */ export const getMergedCellInstance: MergedCellCallback = ( - spreasheet, + spreadsheet, cells, meta, ) => { - if (spreasheet.options.mergedCell) { - return spreasheet.options.mergedCell(spreasheet, cells, meta); + if (spreadsheet.options.mergedCell) { + return spreadsheet.options.mergedCell(spreadsheet, cells, meta); } - return new MergedCell(spreasheet, cells, meta); + return new MergedCell(spreadsheet, cells, meta); }; /** @@ -315,7 +363,7 @@ export const removeUnmergedCellsInfo = ( removeMergedCell: MergedCell, mergedCellsInfo: MergedCellInfo[][], ): MergedCellInfo[][] => { - const removeCellInfo = map(removeMergedCell.cells, (cell: S2CellType) => { + const removeCellInfo = map(removeMergedCell.cells, (cell: DataCell) => { return { colIndex: cell.getMeta().colIndex, rowIndex: cell.getMeta().rowIndex, diff --git a/packages/s2-core/src/utils/interaction/select-event.ts b/packages/s2-core/src/utils/interaction/select-event.ts index 99b62bbcf9..b35da0dbb8 100644 --- a/packages/s2-core/src/utils/interaction/select-event.ts +++ b/packages/s2-core/src/utils/interaction/select-event.ts @@ -30,6 +30,10 @@ export const isMultiSelectionKey = (e: KeyboardEvent) => e.key as InteractionKeyboardKey, ); +export const isMouseEventWithMeta = (e: MouseEvent) => { + return e.ctrlKey || e.metaKey; +}; + export const getCellMeta = (cell: S2CellType): CellMeta => { const meta = cell.getMeta(); const { id, colIndex, rowIndex, rowQuery } = meta || {}; diff --git a/packages/s2-core/src/utils/layout/add-totals.ts b/packages/s2-core/src/utils/layout/add-totals.ts index e268699275..48e47b238c 100644 --- a/packages/s2-core/src/utils/layout/add-totals.ts +++ b/packages/s2-core/src/utils/layout/add-totals.ts @@ -16,7 +16,12 @@ export const addTotals = (params: TotalParams) => { // check to see if grand total is added if (totalsConfig?.showGrandTotals) { action = totalsConfig.reverseGrandTotalsLayout ? 'unshift' : 'push'; - totalValue = new TotalClass(totalsConfig.grandTotalsLabel!, false, true); + totalValue = new TotalClass({ + label: totalsConfig.grandTotalsLabel!, + isSubTotals: false, + isGrandTotals: true, + isTotalRoot: true, + }); } } else if ( /** @@ -30,7 +35,12 @@ export const addTotals = (params: TotalParams) => { currentField !== EXTRA_FIELD ) { action = totalsConfig.reverseSubTotalsLayout ? 'unshift' : 'push'; - totalValue = new TotalClass(totalsConfig.subTotalsLabel!, true); + totalValue = new TotalClass({ + label: totalsConfig.subTotalsLabel!, + isSubTotals: true, + isGrandTotals: false, + isTotalRoot: true, + }); } if (action) { diff --git a/packages/s2-core/src/utils/layout/frozen.ts b/packages/s2-core/src/utils/layout/frozen.ts index 87de79e883..e43919bf03 100644 --- a/packages/s2-core/src/utils/layout/frozen.ts +++ b/packages/s2-core/src/utils/layout/frozen.ts @@ -1,5 +1,4 @@ import type { S2TableSheetFrozenOptions } from '../../common/interface'; -import type { Node } from '../../facet/layout/node'; export const getValidFrozenOptions = ( defaultFrozenOptions: S2TableSheetFrozenOptions, @@ -37,33 +36,3 @@ export const getValidFrozenOptions = ( return frozenOptions; }; - -export const getFrozenColWidth = ( - colLeafNodes: Node[], - options: S2TableSheetFrozenOptions, -) => { - const result = { - frozenColWidth: 0, - frozenTrailingColWidth: 0, - }; - - if (!options.colCount && !options.trailingColCount) { - return result; - } - - const { - colCount: frozenColCount = 0, - trailingColCount: frozenTrailingColCount = 0, - } = getValidFrozenOptions(options, colLeafNodes.length); - - for (let i = 0; i < frozenColCount; i++) { - result.frozenColWidth += colLeafNodes[i].width; - } - - for (let i = 0; i < frozenTrailingColCount; i++) { - result.frozenTrailingColWidth += - colLeafNodes[colLeafNodes.length - 1 - i].width; - } - - return result; -}; diff --git a/packages/s2-core/src/utils/layout/generate-header-nodes.ts b/packages/s2-core/src/utils/layout/generate-header-nodes.ts index b6aab33910..87ebd7281a 100644 --- a/packages/s2-core/src/utils/layout/generate-header-nodes.ts +++ b/packages/s2-core/src/utils/layout/generate-header-nodes.ts @@ -1,5 +1,4 @@ -import { includes } from 'lodash'; -import { EXTRA_FIELD } from '../../common/constant'; +import { EMPTY_FIELD_VALUE, EXTRA_FIELD } from '../../common/constant'; import { i18n } from '../../common/i18n'; import { buildGridHierarchy } from '../../facet/layout/build-gird-hierarchy'; import type { HeaderNodesParams } from '../../facet/layout/interface'; @@ -8,6 +7,7 @@ import { Node } from '../../facet/layout/node'; import { TotalClass } from '../../facet/layout/total-class'; import { TotalMeasure } from '../../facet/layout/total-measure'; import { generateId } from '../../utils/layout/generate-id'; +import { whetherLeafByLevel } from './whether-leaf-by-level'; // eslint-disable-next-line max-lines-per-function export const generateHeaderNodes = (params: HeaderNodesParams) => { @@ -23,16 +23,16 @@ export const generateHeaderNodes = (params: HeaderNodesParams) => { addTotalMeasureInTotal, spreadsheet, } = params; - const { colCell } = spreadsheet.options.style!; for (const [index, fieldValue] of fieldValues.entries()) { const isTotals = fieldValue instanceof TotalClass; const isTotalMeasure = fieldValue instanceof TotalMeasure; let value: string; - let nodeQuery; + let nodeQuery: Record<string, unknown>; let isLeaf = false; let isGrandTotals = false; let isSubTotals = false; + let isTotalRoot = false; let adjustedField = currentField; if (isTotals) { @@ -40,27 +40,29 @@ export const generateHeaderNodes = (params: HeaderNodesParams) => { isGrandTotals = totalClass.isGrandTotals; isSubTotals = totalClass.isSubTotals; + isTotalRoot = totalClass.isTotalRoot; value = i18n((fieldValue as TotalClass).label); - if (addMeasureInTotalQuery) { - // root[&]四川[&]总计 => {province: '四川', EXTRA_FIELD: 'price'} - nodeQuery = { - ...query, - [EXTRA_FIELD]: spreadsheet?.dataSet?.fields?.values?.[0], - }; - isLeaf = true; + if (isTotalRoot) { + nodeQuery = query; } else { // root[&]四川[&]总计 => {province: '四川'} - nodeQuery = query; - if (!addTotalMeasureInTotal) { - isLeaf = true; - } + nodeQuery = { ...query, [currentField]: value }; + } + + if (addMeasureInTotalQuery) { + // root[&]四川[&]总计 => {province: '四川', EXTRA_FIELD: 'price'} + nodeQuery[EXTRA_FIELD] = spreadsheet?.dataSet?.fields.values![0]; } + + isLeaf = whetherLeafByLevel({ spreadsheet, level, fields }); } else if (isTotalMeasure) { value = i18n((fieldValue as TotalMeasure).label); // root[&]四川[&]总计[&]price => {province: '四川',EXTRA_FIELD: 'price' } nodeQuery = { ...query, [EXTRA_FIELD]: value }; adjustedField = EXTRA_FIELD; - isLeaf = true; + isGrandTotals = parentNode.isGrandTotals!; + isSubTotals = parentNode.isSubTotals!; + isLeaf = whetherLeafByLevel({ spreadsheet, level, fields }); } else if (spreadsheet.isTableMode()) { value = fieldValue; adjustedField = fields[index]; @@ -69,16 +71,18 @@ export const generateHeaderNodes = (params: HeaderNodesParams) => { } else { value = fieldValue; // root[&]四川[&]成都 => {province: '四川', city: '成都' } - nodeQuery = { ...query, [currentField]: value }; - const isValueInCols = spreadsheet.dataCfg.fields?.valueInCols ?? true; - const isHideValue = - colCell?.hideValue && isValueInCols && includes(fields, EXTRA_FIELD); - const extraSize = isHideValue ? 2 : 1; + // 子维度的维值为空时, 使用父级节点的 query, 避免查询不到数据 + nodeQuery = + value === EMPTY_FIELD_VALUE + ? { ...query } + : { ...query, [currentField]: value }; - isLeaf = level === fields.length - extraSize; + isLeaf = whetherLeafByLevel({ spreadsheet, level, fields }); } - const nodeId = generateId(parentNode.id, value); + // 优先找序列字段对应的格式化名称, 找不到则是普通维值 + const formattedValue = spreadsheet.dataSet.getFieldName(value) || value; + const nodeId = generateId(parentNode.id, formattedValue); if (!nodeId) { return; @@ -89,15 +93,16 @@ export const generateHeaderNodes = (params: HeaderNodesParams) => { // create new header nodes const node = new Node({ id: nodeId, - value, + value: formattedValue, level, field: adjustedField, parent: parentNode, - isTotals, + isTotals: isTotals || isTotalMeasure, isGrandTotals, isSubTotals, isTotalMeasure, isCollapsed, + isTotalRoot, hierarchy, query: nodeQuery, spreadsheet, @@ -115,6 +120,7 @@ export const generateHeaderNodes = (params: HeaderNodesParams) => { const hiddenColumnsInfo = spreadsheet?.facet?.getHiddenColumnsInfo(node); if (hiddenColumnsInfo && parentNode) { + // hiddenChildNodeInfo 属性在 S2 中没有用到,但是没删怕外部有使用,已标记为废弃 parentNode.hiddenChildNodeInfo = hiddenColumnsInfo; } diff --git a/packages/s2-core/src/utils/layout/generate-id.ts b/packages/s2-core/src/utils/layout/generate-id.ts index f13150849d..c510086e9e 100644 --- a/packages/s2-core/src/utils/layout/generate-id.ts +++ b/packages/s2-core/src/utils/layout/generate-id.ts @@ -10,8 +10,8 @@ import { * Row and column header node id generator. */ -export const generateId = (...ids: string[]): string => - ids +export const generateId = (...ids: string[]): string => { + return ids .map((value) => { if (isUndefined(value)) { return UNDEFINED_SYMBOL_ID; @@ -24,9 +24,10 @@ export const generateId = (...ids: string[]): string => return String(value); }) .join(NODE_ID_SEPARATOR); +}; -export const resolveId = (id = '') => - id +export const resolveId = (id = '') => { + return id .split(NODE_ID_SEPARATOR) .reduce<(string | null | undefined)[]>((result, current) => { if (current === ROOT_NODE_ID) { @@ -43,3 +44,4 @@ export const resolveId = (id = '') => return result; }, []); +}; diff --git a/packages/s2-core/src/utils/layout/get-dims-condition-by-node.ts b/packages/s2-core/src/utils/layout/get-dims-condition-by-node.ts index 67d20b26b0..f02ce4278b 100644 --- a/packages/s2-core/src/utils/layout/get-dims-condition-by-node.ts +++ b/packages/s2-core/src/utils/layout/get-dims-condition-by-node.ts @@ -1,19 +1,21 @@ +import type { Query } from '../../data-set'; +import { EMPTY_FIELD_VALUE } from '../../common/constant'; import type { Node } from '../../facet/layout/node'; export function getDimsCondition(parent: Node, force?: boolean) { - const cond: Record<string, string> = {}; - let p = parent; + const cond: Query = {}; + let p: Node | undefined = parent; while (p && p.field) { /** * 当为表格布局时,小计行的内容是“小计”不需要作为筛选条件 * 当为树状布局时,force可以强行指定小计行,即父类目作为筛选条件 */ - if (!p.isTotals || force) { + if ((!p.isTotalRoot || force) && p.value !== EMPTY_FIELD_VALUE) { cond[p.field] = p.value; } - p = p.parent!; + p = p.parent; } return cond; diff --git a/packages/s2-core/src/utils/layout/whether-leaf-by-level.ts b/packages/s2-core/src/utils/layout/whether-leaf-by-level.ts new file mode 100644 index 0000000000..7a10280d4b --- /dev/null +++ b/packages/s2-core/src/utils/layout/whether-leaf-by-level.ts @@ -0,0 +1,18 @@ +import { includes } from 'lodash'; +import type { WhetherLeafParams } from '../../facet/layout/interface'; +import { EXTRA_FIELD } from '../../common'; + +export const whetherLeafByLevel = (params: WhetherLeafParams) => { + const { spreadsheet, level, fields } = params; + const { options, dataSet } = spreadsheet; + const moreThanOneValue = dataSet.moreThanOneValue(); + const isValueInCols = spreadsheet.dataCfg.fields?.valueInCols ?? true; + const isHideMeasure = + options?.style?.colCell?.hideValue && + isValueInCols && + !moreThanOneValue && + includes(fields, EXTRA_FIELD); + const extraSize = isHideMeasure ? 2 : 1; + + return level === fields.length - extraSize; +}; diff --git a/packages/s2-core/src/utils/math.ts b/packages/s2-core/src/utils/math.ts new file mode 100644 index 0000000000..45d7aeaf4b --- /dev/null +++ b/packages/s2-core/src/utils/math.ts @@ -0,0 +1,5 @@ +import { floor as innerFloor } from 'lodash'; + +export function floor(value: number, precision = 2) { + return innerFloor(value, precision); +} diff --git a/packages/s2-core/src/utils/merge.ts b/packages/s2-core/src/utils/merge.ts index 0551769d98..3bb4346e32 100644 --- a/packages/s2-core/src/utils/merge.ts +++ b/packages/s2-core/src/utils/merge.ts @@ -1,11 +1,11 @@ -import { isArray, isEmpty, mergeWith, uniq, isEqual, isString } from 'lodash'; +import { isArray, isEmpty, isEqual, isString, mergeWith, uniq } from 'lodash'; import { DEFAULT_DATA_CONFIG } from '../common/constant/dataConfig'; import { DEFAULT_OPTIONS } from '../common/constant/options'; import type { + CustomHeaderFields, + Fields, S2DataConfig, S2Options, - Fields, - CustomHeaderFields, } from '../common/interface'; export const customMerge = <T = unknown>(...objects: unknown[]): T => { diff --git a/packages/s2-core/src/utils/number-calculate.ts b/packages/s2-core/src/utils/number-calculate.ts index 9892202cee..7fbb662273 100644 --- a/packages/s2-core/src/utils/number-calculate.ts +++ b/packages/s2-core/src/utils/number-calculate.ts @@ -1,5 +1,5 @@ import Decimal from 'decimal.js'; -import { getFieldValueOfViewMetaData } from '../data-set/cell-data'; +import { CellData } from '../data-set/cell-data'; import { Aggregation, type ViewMetaData } from '../common/interface'; export const isNotNumber = (value: unknown) => { @@ -38,9 +38,7 @@ const processFieldValues = ( } return data.reduce<Array<Decimal>>((resultArr, item) => { - const fieldValue = getFieldValueOfViewMetaData(item, field) as - | string - | number; + const fieldValue = CellData.getFieldValue(item, field) as string | number; const notNumber = isNotNumber(fieldValue); diff --git a/packages/s2-core/src/utils/sort-action.ts b/packages/s2-core/src/utils/sort-action.ts index 128bd508e3..2d48cf58ef 100644 --- a/packages/s2-core/src/utils/sort-action.ts +++ b/packages/s2-core/src/utils/sort-action.ts @@ -1,13 +1,14 @@ import { compact, - concat, endsWith, + flatMap, includes, indexOf, isEmpty, isNil, keys, map, + sortBy, split, toUpper, uniq, @@ -15,18 +16,21 @@ import { import { EXTRA_FIELD, NODE_ID_SEPARATOR, + ORIGIN_FIELD, + QueryDataType, TOTAL_VALUE, } from '../common/constant'; import type { Fields, SortMethod, SortParam } from '../common/interface'; import type { PivotDataSet, Query } from '../data-set'; import type { CellData } from '../data-set/cell-data'; -import type { SortActionParams } from '../data-set/interface'; +import type { + PivotMeta, + PivotMetaValue, + SortActionParams, + SortPivotMetaParams, +} from '../data-set/interface'; import { getLeafColumnsWithKey } from '../facet/utils'; -import { - getListBySorted, - isTotalData, - sortByItems, -} from '../utils/data-set-operate'; +import { getListBySorted, sortByItems } from '../utils/data-set-operate'; import { filterExtraDimension, getDimensionsWithParentPath, @@ -284,26 +288,28 @@ export const getSortByMeasureValues = ( const { dataSet, sortParam, originValues } = params; const { fields } = dataSet!; const { sortByMeasure, query, sortFieldId } = sortParam!; - // 按 query 查出所有数据 - const dataList = dataSet!.getCellMultiData({ query: query! }); - const columns = getLeafColumnsWithKey(fields.columns); + + if (sortByMeasure !== TOTAL_VALUE) { + const dataList = dataSet!.getCellMultiData({ + query, + queryType: QueryDataType.DetailOnly, + }); + + return dataList; + } /** * 按明细数据 * 需要过滤查询出的总/小计“汇总数据” */ - if (sortByMeasure !== TOTAL_VALUE) { - const rowColFields = concat(fields.rows, columns) as string[]; - - return dataList.filter( - (dataItem) => - /* - * 过滤出包含所有行列维度的数据 - * 若缺失任意 field,则是汇总数据,需要过滤掉 - */ - !isTotalData(rowColFields, dataItem.getOrigin()), - ); - } + + const dataList = dataSet!.getCellMultiData({ + query, + queryType: QueryDataType.All, + }); + + // 按 query 查出所有数据 + const columns = getLeafColumnsWithKey(fields.columns); /** * 按汇总值进行排序 @@ -314,11 +320,11 @@ export const getSortByMeasureValues = ( const isSortFieldInRow = includes(fields.rows, sortFieldId); // 排序字段所在一侧的全部字段 const sortFields = filterExtraDimension( - (isSortFieldInRow ? fields.rows : fields.columns) as string[], + (isSortFieldInRow ? fields.rows : columns) as string[], ); // 与排序交叉的另一侧全部字段 const oppositeFields = filterExtraDimension( - (isSortFieldInRow ? fields.columns : fields.rows) as string[], + (isSortFieldInRow ? columns : fields.rows) as string[], ); const fieldAfterSortField = sortFields[sortFields.indexOf(sortFieldId) + 1]; @@ -328,7 +334,7 @@ export const getSortByMeasureValues = ( ); const totalDataList = dataList.filter((dataItem) => { - const dataItemKeys = new Set(keys(dataItem.getOrigin())); + const dataItemKeys = new Set(keys(dataItem[ORIGIN_FIELD])); if (!dataItemKeys.has(sortFieldId)) { /* @@ -418,3 +424,35 @@ export const getSortTypeIcon = ( return 'SortDown'; } }; + +/** + * 对 pivot meta 中的内容进行排序,返回新的 sorted pivot meta + */ +export const getSortedPivotMeta = (params: SortPivotMetaParams) => { + const { pivotMeta, dimensions, sortedDimensionValues, sortFieldId } = params; + const rootContainer = { + children: pivotMeta, + } as PivotMetaValue; + let metaValueList = [rootContainer]; + + for (const dimension of dimensions) { + if (dimension !== sortFieldId) { + metaValueList = flatMap(metaValueList, (metaValue) => { + return [...metaValue.children.values()]; + }); + } else { + metaValueList.forEach((metaValue) => { + const values = [...metaValue.children.values()]; + + const entities = sortBy(values, (value) => { + return indexOf(sortedDimensionValues, value.id); + }).map((value) => [value.value, value] as [string, PivotMetaValue]); + + metaValue.children = new Map(entities) as PivotMeta; + }); + break; + } + } + + return rootContainer.children; +}; diff --git a/packages/s2-core/src/utils/text.ts b/packages/s2-core/src/utils/text.ts index 9e22337e56..bb0756e413 100644 --- a/packages/s2-core/src/utils/text.ts +++ b/packages/s2-core/src/utils/text.ts @@ -15,6 +15,7 @@ import type { ColCell } from '../cell'; import { CellType, ELLIPSIS_SYMBOL, + EMPTY_FIELD_VALUE, EMPTY_PLACEHOLDER, } from '../common/constant'; import { @@ -47,10 +48,11 @@ export const getDisplayText = ( text: string | number | null | undefined, placeholder?: string, ) => { - const empty = placeholder ?? EMPTY_PLACEHOLDER; + const emptyPlaceholder = placeholder ?? EMPTY_PLACEHOLDER; + // 对应维度缺少维度数据时, 会使用 EMPTY_FIELD_VALUE 填充, 实际渲染时统一转成 "-" + const isEmptyText = isNil(text) || text === '' || text === EMPTY_FIELD_VALUE; - // [null, undefined, ''] will return empty - return isNil(text) || text === '' ? empty : `${text}`; + return isEmptyText ? emptyPlaceholder : `${text}`; }; /** @@ -268,7 +270,7 @@ export const getEllipsisText = ({ * Two cases needed to be considered since the derived value could be number or string. * @param value */ -export const isUpDataValue = (value: number | string): boolean => { +export const isUpDataValue = (value: SimpleData): boolean => { if (isNumber(value)) { return value >= 0; } @@ -276,6 +278,32 @@ export const isUpDataValue = (value: number | string): boolean => { return !!value && !trim(value).startsWith('-'); }; +/** + * Determines whether the data is actually equal to 0 or empty or nil + * example: "0.00%" => true + * @param value + */ +export const isZeroOrEmptyValue = (value: SimpleData): boolean => { + return ( + isNil(value) || + value === '' || + Number(String(value).replace(/[^0-9.]+/g, '')) === 0 + ); +}; + +/** + * Determines whether the data is actually equal to 0 or empty or nil or equals to compareValue + * example: "0.00%" => true + * @param value + * @param compareValue + */ +export const isUnchangedValue = ( + value: SimpleData, + compareValue: SimpleData, +): boolean => { + return isZeroOrEmptyValue(value) || value === compareValue; +}; + /** * 根据单元格对齐方式计算文本的 x 坐标 * @param x 单元格的 x 坐标 @@ -311,9 +339,8 @@ const calX = ( const getDrawStyle = (cell: S2CellType) => { const { isTotals } = cell.getMeta(); const isMeasureField = (cell as ColCell).isMeasureField?.(); - const cellStyle: InternalFullyCellTheme = cell.getStyle( - isMeasureField ? CellType.COL_CELL : CellType.DATA_CELL, - ); + + const cellStyle = cell.getStyle(cell.cellType || CellType.DATA_CELL); let textStyle: TextTheme | undefined; diff --git a/packages/s2-core/src/utils/theme.ts b/packages/s2-core/src/utils/theme.ts index bbbeb61f91..106c48c5e6 100644 --- a/packages/s2-core/src/utils/theme.ts +++ b/packages/s2-core/src/utils/theme.ts @@ -1,6 +1,6 @@ import { PALETTE_MAP, STYLE_ELEMENT_ID } from '../common/constant'; import type { Palette, ThemeName } from '../common/interface/theme'; -import DarkVars from '../styles/theme/dark.less'; +import DarkVars from '../styles/theme/dark.less?inline'; import { injectCssText } from './inject-css-text'; /** diff --git a/packages/s2-core/src/utils/tooltip.ts b/packages/s2-core/src/utils/tooltip.ts index f457d8c998..77806b6b41 100644 --- a/packages/s2-core/src/utils/tooltip.ts +++ b/packages/s2-core/src/utils/tooltip.ts @@ -62,7 +62,7 @@ import type { TooltipPosition, TooltipSummaryOptions, } from '../common/interface/tooltip'; -import { getFieldValueOfViewMetaData } from '../data-set/cell-data'; +import { CellData } from '../data-set/cell-data'; import type { Node as S2Node } from '../facet/layout/node'; import { getLeafColumnsWithKey } from '../facet/utils'; import type { SpreadSheet } from '../sheet-type'; @@ -212,7 +212,7 @@ export const getListItem = ( const formatter = getFieldFormatter(spreadsheet, field); // 非数值类型的 data 不展示 (趋势分析表/迷你图/G2 图表),上层通过自定义 tooltip 的方式去自行定制 - const dataValue = getFieldValueOfViewMetaData(data, field); + const dataValue = CellData.getFieldValue(data, field); const displayDataValue = isObject(dataValue) ? null : dataValue; const value = formatter( @@ -234,7 +234,7 @@ export const getFieldList = ( const currentFields = filter( concat([], fields), (field) => - field !== EXTRA_FIELD && getFieldValueOfViewMetaData(activeData, field), + field !== EXTRA_FIELD && CellData.getFieldValue(activeData, field), ); return map(currentFields, (field: string) => @@ -489,11 +489,15 @@ export const getSummaries = (params: SummaryParam): TooltipSummaryOptions[] => { const isTableMode = spreadsheet.isTableMode(); if (isTableMode && options?.onlyShowCellText) { - const selectedCellsData = spreadsheet.dataSet.getCellMultiData({ - query: {}, - }); - - return [{ selectedData: selectedCellsData as Data[], name: '', value: '' }]; + const meta = targetCell?.getMeta(); + // 如果是列头, 获取当前列所有数据, 其他则获取当前整行 (1条数据) + const selectedCellsData = ( + meta?.field + ? spreadsheet.dataSet.getCellMultiData({ query: { field: meta.field } }) + : [spreadsheet.dataSet.getRowData(meta as ViewMeta)] + ) as ViewMetaData[]; + + return [{ selectedData: selectedCellsData, name: '', value: '' }]; } // 拿到选择的所有 dataCell的数据 @@ -579,8 +583,8 @@ export const getTooltipData = (params: TooltipDataParam): TooltipData => { } else if (options.onlyShowCellText) { // 行列头hover & 明细表所有hover - const value = getFieldValueOfViewMetaData(firstCellInfo, 'value') as string; - const valueField = getFieldValueOfViewMetaData( + const value = CellData.getFieldValue(firstCellInfo, 'value') as string; + const valueField = CellData.getFieldValue( firstCellInfo, 'valueField', ) as string; diff --git a/packages/s2-react/CHANGELOG.md b/packages/s2-react/CHANGELOG.md index edda629d83..04d4ca6dd4 100644 --- a/packages/s2-react/CHANGELOG.md +++ b/packages/s2-react/CHANGELOG.md @@ -1,10 +1,61 @@ # [@antv/s2-react-v2.0.0-next.9](https://github.com/antvis/S2/compare/@antv/s2-react-v2.0.0-next.8...@antv/s2-react-v2.0.0-next.9) (2023-12-12) +* **table-sheet:** 修复明细表 tooltip 展示了错误的汇总数据的问题 ([#2457](https://github.com/antvis/S2/issues/2457)) ([51bc110](https://github.com/antvis/S2/commit/51bc1105d8c52bd46cc89dfe2698b0fe42745c69)) +* **table-sheet:** 修复明细表排序后开启行列冻结,冻结行展示错误 close [#2388](https://github.com/antvis/S2/issues/2388) ([#2453](https://github.com/antvis/S2/issues/2453)) ([741e27a](https://github.com/antvis/S2/commit/741e27aab78b4b415d5f9e49760b401c93a84ca9)) + +### Features + +* 交叉表支持冻结首行能力 ([#2416](https://github.com/antvis/S2/issues/2416)) ([b81b795](https://github.com/antvis/S2/commit/b81b7957b9e8b8e1fbac9ebc6cacdf45a14e5412)) + +# [@antv/s2-react-v1.44.3](https://github.com/antvis/S2/compare/@antv/s2-react-v1.44.2...@antv/s2-react-v1.44.3) (2023-12-01) ### Bug Fixes -* 修复 React 18 StrictMode 同时挂载两个表格的问题 ([#2432](https://github.com/antvis/S2/issues/2432)) ([7c4da43](https://github.com/antvis/S2/commit/7c4da435976076e5babe21012524694986286210)) +* **copy:** 修复刷选复制行列头时,数值单元格未格式化 & 存在省略号时未复制原始值 ([#2410](https://github.com/antvis/S2/issues/2410)) ([708fde4](https://github.com/antvis/S2/commit/708fde479bb48b941445b3adaf1f56cf5cb6b301)) +* 修复中英文标点符号 ([#2442](https://github.com/antvis/S2/issues/2442)) ([17a2d00](https://github.com/antvis/S2/commit/17a2d00f13ff1db4cc8236176b2a26c5212a2dbd)) + +# [@antv/s2-react-v1.44.2](https://github.com/antvis/S2/compare/@antv/s2-react-v1.44.1...@antv/s2-react-v1.44.2) (2023-11-10) + +### Bug Fixes + +* 修复趋势分析表自定义列头 tooltip 后错误的使用行头的 tooltip ([#2399](https://github.com/antvis/S2/issues/2399)) ([0310c2f](https://github.com/antvis/S2/commit/0310c2f7f6ea054a79f0fc71b972ee1d3dd1c649)) + +# [@antv/s2-react-v1.44.1](https://github.com/antvis/S2/compare/@antv/s2-react-v1.44.0...@antv/s2-react-v1.44.1) (2023-10-27) + +### Bug Fixes + +* **interaction:** 修复拖动水平滚动条后单元格选中状态被重置 close [#2376](https://github.com/antvis/S2/issues/2376) ([#2380](https://github.com/antvis/S2/issues/2380)) ([b2e9700](https://github.com/antvis/S2/commit/b2e97008122f5320342fd069a08f6e821a5c9ad6)) +* **layout:** 修复在紧凑模式列头宽度未按文本自适应 close [#2385](https://github.com/antvis/S2/issues/2385) ([#2392](https://github.com/antvis/S2/issues/2392)) ([2edd99c](https://github.com/antvis/S2/commit/2edd99c367116bad661a02893a303311787eb647)) + +# [@antv/s2-react-v1.44.0](https://github.com/antvis/S2/compare/@antv/s2-react-v1.43.0...@antv/s2-react-v1.44.0) (2023-09-22) + +### Features + +* 对比值无波动时也显示灰色 ([#2351](https://github.com/antvis/S2/issues/2351)) ([12f2d02](https://github.com/antvis/S2/commit/12f2d0268d447ec99a1227ffedd5ed266d93e86b)) +* 小计/总计功能,支持按维度分组汇总 ([#2346](https://github.com/antvis/S2/issues/2346)) ([20e608f](https://github.com/antvis/S2/commit/20e608f2447e9ffdb135d98e4cc7f39f1cfb308d)) + +# [@antv/s2-react-v1.43.0](https://github.com/antvis/S2/compare/@antv/s2-react-v1.42.1...@antv/s2-react-v1.43.0) (2023-09-09) + +### Bug Fixes + +* **interaction:** 修复行头滚动刷选范围判断错误 ([8b080af](https://github.com/antvis/S2/commit/8b080afccdd4bebc157e0d569d36b2b04175a522)) +### Features + +* 对比值无波动时也显示灰色 ([#2351](https://github.com/antvis/S2/issues/2351)) ([12f2d02](https://github.com/antvis/S2/commit/12f2d0268d447ec99a1227ffedd5ed266d93e86b)) + +# [@antv/s2-react-v1.42.2-alpha.1](https://github.com/antvis/S2/compare/@antv/s2-react-v1.42.1...@antv/s2-react-v1.42.2-alpha.1) (2023-09-07) + +### Bug Fixes + +* **interaction:** 修复行头滚动刷选范围判断错误 ([8b080af](https://github.com/antvis/S2/commit/8b080afccdd4bebc157e0d569d36b2b04175a522)) +* **scroll:** 修复移动端快速滚动时控制台报错 close [#2266](https://github.com/antvis/S2/issues/2266) ([#2302](https://github.com/antvis/S2/issues/2302)) ([4ccc03d](https://github.com/antvis/S2/commit/4ccc03d50ef6622774a8c9e3599c988d2a7e126e)) + +# [@antv/s2-react-v1.37.1-alpha.2](https://github.com/antvis/S2/compare/@antv/s2-react-v1.37.1-alpha.1...@antv/s2-react-v1.37.1-alpha.2) (2023-03-08) + +### Bug Fixes + +* 修复 React 18 StrictMode 同时挂载两个表格的问题 ([#2432](https://github.com/antvis/S2/issues/2432)) ([7c4da43](https://github.com/antvis/S2/commit/7c4da435976076e5babe21012524694986286210)) ### Features @@ -12,16 +63,14 @@ # [@antv/s2-react-v2.0.0-next.8](https://github.com/antvis/S2/compare/@antv/s2-react-v2.0.0-next.7...@antv/s2-react-v2.0.0-next.8) (2023-11-22) - ### Features * headerActionIcons 支持细粒度配置 & 修复异步渲染导致无法获取实例的问题 ([#2301](https://github.com/antvis/S2/issues/2301)) ([b2d6f1f](https://github.com/antvis/S2/commit/b2d6f1fb04d3fa73129669fc7d2dec84943252db)) * **layout:** 单元格支持渲染多行文本 ([#2383](https://github.com/antvis/S2/issues/2383)) ([e3b919a](https://github.com/antvis/S2/commit/e3b919a4f37d600a0f516944edf4eed8b2c0174d)) * 支持 antd v5 ([#2413](https://github.com/antvis/S2/issues/2413)) ([299c7bf](https://github.com/antvis/S2/commit/299c7bfe2e86838153273c92dd6d2b72917cfdea)) -* 支持 React 18 (兼容 React 16/17) ([#2373](https://github.com/antvis/S2/issues/2373)) ([25ce9b0](https://github.com/antvis/S2/commit/25ce9b0ccc3e609d8add09b3209f6f981dc1dc4e)) +* 支持 React 18 (兼容 React 16/17) ([#2373](https://github.com/antvis/S2/issues/2373)) ([25ce9b0](https://github.com/antvis/S2/commit/25ce9b0ccc3e609d8add09b3209f6f981dc1dc4e)) * 支持自定义 G 5.0 插件和配置 ([#2423](https://github.com/antvis/S2/issues/2423)) ([cc6c47f](https://github.com/antvis/S2/commit/cc6c47fd0927125bbc378fe6914becfcbe1b0acd)) - ### BREAKING CHANGES * 移除 devicePixelRatio 和 supportsCSSTransform @@ -75,11 +124,11 @@ ### Features * 使用 requestIdleCallback 处理数据大量导出的情况 ([#2272](https://github.com/antvis/S2/issues/2272)) ([42a5551](https://github.com/antvis/S2/commit/42a55516dd369d9ab5579b52fbc9900b0ad81858)) -* 同步复制支持自定义transformer ([#2201](https://github.com/antvis/S2/issues/2201)) ([9003767](https://github.com/antvis/S2/commit/9003767d584248b9d122f299326fd14753961883)) +* 同步复制支持自定义 transformer ([#2201](https://github.com/antvis/S2/issues/2201)) ([9003767](https://github.com/antvis/S2/commit/9003767d584248b9d122f299326fd14753961883)) * 增加暗黑主题 ([#2130](https://github.com/antvis/S2/issues/2130)) ([51dbdcf](https://github.com/antvis/S2/commit/51dbdcf564b387a3fd1809a71016f3a91eebde38)) * 文本和图标的条件格式支持主题配置 ([#2267](https://github.com/antvis/S2/issues/2267)) ([c332c68](https://github.com/antvis/S2/commit/c332c687dfb7be1d07b79b44934f78c1947cc466)) * 行列头兼容 condition icon 和 action icons ([#2161](https://github.com/antvis/S2/issues/2161)) ([1df4286](https://github.com/antvis/S2/commit/1df42860f6a12d3cb182ba7633c4984a04e62890)) -* 适配g5.0异步渲染 ([#2251](https://github.com/antvis/S2/issues/2251)) ([069d03d](https://github.com/antvis/S2/commit/069d03d299429c2ffab3e20d56ecd6bb30119ffd)) +* 适配 g5.0 异步渲染 ([#2251](https://github.com/antvis/S2/issues/2251)) ([069d03d](https://github.com/antvis/S2/commit/069d03d299429c2ffab3e20d56ecd6bb30119ffd)) ### Performance Improvements @@ -97,10 +146,10 @@ * **interaction:** 点击角头后支持选中所对应那一列的行头 close [#2073](https://github.com/antvis/S2/issues/2073) ([#2081](https://github.com/antvis/S2/issues/2081)) ([ad2b5d8](https://github.com/antvis/S2/commit/ad2b5d87edf4c529d7c9a5e1348e893e14547ef3)) * **interaction:** 行头支持滚动刷选 ([#2087](https://github.com/antvis/S2/issues/2087)) ([65c3f3b](https://github.com/antvis/S2/commit/65c3f3b6a37709c0fa684b0f5717d3b349251e48)) -* 修改文档、添加用例演示、修改方法名drawLinkFieldShapLogic -> drawLinkField ([7f2bd69](https://github.com/antvis/S2/commit/7f2bd690bd703b8e4d678c03b9fc79db30848ca3)) -* 增加dataCell 下划线测试用例及demo ([a5efe17](https://github.com/antvis/S2/commit/a5efe17bda06cc8eba633cbea9c56ceb8b8c703e)) +* 修改文档、添加用例演示、修改方法名 drawLinkFieldShapLogic -> drawLinkField ([7f2bd69](https://github.com/antvis/S2/commit/7f2bd690bd703b8e4d678c03b9fc79db30848ca3)) +* 增加 dataCell 下划线测试用例及 demo ([a5efe17](https://github.com/antvis/S2/commit/a5efe17bda06cc8eba633cbea9c56ceb8b8c703e)) * 提取跳转链接下划线 公共逻辑 到 BaseCell 类 ([34dbbb3](https://github.com/antvis/S2/commit/34dbbb3bdf028cb96508dcead724d9ac9bcc1ab9)) -* 移除switcher按钮important样式 ([#2139](https://github.com/antvis/S2/issues/2139)) ([d4f9197](https://github.com/antvis/S2/commit/d4f9197f1c3d1b3cfc2bdfb8d375413a2daa79c3)) +* 移除 switcher 按钮 important 样式 ([#2139](https://github.com/antvis/S2/issues/2139)) ([d4f9197](https://github.com/antvis/S2/commit/d4f9197f1c3d1b3cfc2bdfb8d375413a2daa79c3)) # [@antv/s2-react-v2.0.0-next.5](https://github.com/antvis/S2/compare/@antv/s2-react-v2.0.0-next.4...@antv/s2-react-v2.0.0-next.5) (2023-04-23) @@ -112,8 +161,8 @@ ### Bug Fixes -* **layout:** 修复存在列总计但不存在列小计时, 隐藏其兄弟节点后单元格坐标偏移 close [#1993](https://github.com/antvis/S2/issues/1993) ([#2047](https://github.com/antvis/S2/issues/2047)) ([2ae663e](https://github.com/antvis/S2/commit/2ae663e1c46a3c8cb04b79d357fc033314f4cf77)) -* **layout:** 修复存在多列头多数值且数值置于行头时,列总计单元格高度不对 close [#1715](https://github.com/antvis/S2/issues/1715) [#2049](https://github.com/antvis/S2/issues/2049) ([#2051](https://github.com/antvis/S2/issues/2051)) ([a415f46](https://github.com/antvis/S2/commit/a415f465e8fa355a5b68d556f6fa645e3a72b5b7)) +* **layout:** 修复存在列总计但不存在列小计时,隐藏其兄弟节点后单元格坐标偏移 close [#1993](https://github.com/antvis/S2/issues/1993) ([#2047](https://github.com/antvis/S2/issues/2047)) ([2ae663e](https://github.com/antvis/S2/commit/2ae663e1c46a3c8cb04b79d357fc033314f4cf77)) +* **layout:** 修复存在多列头多数值且数值置于行头时,列总计单元格高度不对 close [#1715](https://github.com/antvis/S2/issues/1715) [#2049](https://github.com/antvis/S2/issues/2049) ([#2051](https://github.com/antvis/S2/issues/2051)) ([a415f46](https://github.com/antvis/S2/commit/a415f465e8fa355a5b68d556f6fa645e3a72b5b7)) * **layout:** 修复无列头时行头对应的角头不显示 close [#1929](https://github.com/antvis/S2/issues/1929) ([#2026](https://github.com/antvis/S2/issues/2026)) ([c073578](https://github.com/antvis/S2/commit/c073578dc008ef83a2877041830be18f827c7341)) ### Features @@ -125,8 +174,8 @@ ### Bug Fixes -* **layout:** 修复存在列总计但不存在列小计时, 隐藏其兄弟节点后单元格坐标偏移 close [#1993](https://github.com/antvis/S2/issues/1993) ([#2047](https://github.com/antvis/S2/issues/2047)) ([2ae663e](https://github.com/antvis/S2/commit/2ae663e1c46a3c8cb04b79d357fc033314f4cf77)) -* **layout:** 修复存在多列头多数值且数值置于行头时,列总计单元格高度不对 close [#1715](https://github.com/antvis/S2/issues/1715) [#2049](https://github.com/antvis/S2/issues/2049) ([#2051](https://github.com/antvis/S2/issues/2051)) ([a415f46](https://github.com/antvis/S2/commit/a415f465e8fa355a5b68d556f6fa645e3a72b5b7)) +* **layout:** 修复存在列总计但不存在列小计时,隐藏其兄弟节点后单元格坐标偏移 close [#1993](https://github.com/antvis/S2/issues/1993) ([#2047](https://github.com/antvis/S2/issues/2047)) ([2ae663e](https://github.com/antvis/S2/commit/2ae663e1c46a3c8cb04b79d357fc033314f4cf77)) +* **layout:** 修复存在多列头多数值且数值置于行头时,列总计单元格高度不对 close [#1715](https://github.com/antvis/S2/issues/1715) [#2049](https://github.com/antvis/S2/issues/2049) ([#2051](https://github.com/antvis/S2/issues/2051)) ([a415f46](https://github.com/antvis/S2/commit/a415f465e8fa355a5b68d556f6fa645e3a72b5b7)) * **layout:** 修复无列头时行头对应的角头不显示 close [#1929](https://github.com/antvis/S2/issues/1929) ([#2026](https://github.com/antvis/S2/issues/2026)) ([c073578](https://github.com/antvis/S2/commit/c073578dc008ef83a2877041830be18f827c7341)) ### Features @@ -137,7 +186,7 @@ ### Bug Fixes -* 列头label存在数组,复制导出列头层级补齐错误 ([#1990](https://github.com/antvis/S2/issues/1990)) ([ec62409](https://github.com/antvis/S2/commit/ec62409b688c5dd5e39a93f5b292d909496ed830)) +* 列头 label 存在数组,复制导出列头层级补齐错误 ([#1990](https://github.com/antvis/S2/issues/1990)) ([ec62409](https://github.com/antvis/S2/commit/ec62409b688c5dd5e39a93f5b292d909496ed830)) ### Features @@ -148,7 +197,7 @@ ### Bug Fixes * **interaction:** 修复自定义列头时无法调整第一列的叶子节点高度 close [#1979](https://github.com/antvis/S2/issues/1979) ([#2038](https://github.com/antvis/S2/issues/2038)) ([a632ab1](https://github.com/antvis/S2/commit/a632ab19193b19ab80f456ab3ce19740dce0e52b)) -* 列头label存在数组,复制导出列头层级补齐错误 ([#1990](https://github.com/antvis/S2/issues/1990)) ([ec62409](https://github.com/antvis/S2/commit/ec62409b688c5dd5e39a93f5b292d909496ed830)) +* 列头 label 存在数组,复制导出列头层级补齐错误 ([#1990](https://github.com/antvis/S2/issues/1990)) ([ec62409](https://github.com/antvis/S2/commit/ec62409b688c5dd5e39a93f5b292d909496ed830)) ### Code Refactoring @@ -159,11 +208,11 @@ * selected cell highlight ([#1878](https://github.com/antvis/S2/issues/1878)) ([3e11a37](https://github.com/antvis/S2/commit/3e11a37bf94f758379ba2819ec5d8b3251708814)) * 单元格宽高配置增强 close [#1895](https://github.com/antvis/S2/issues/1895) ([#1981](https://github.com/antvis/S2/issues/1981)) ([ec6736f](https://github.com/antvis/S2/commit/ec6736f108801e1129c4d3fd29d13d1fbff2a1d2)) * 折叠展开重构 & 简化行头 tree 相关配置 ([#2030](https://github.com/antvis/S2/issues/2030)) ([0f3ea3b](https://github.com/antvis/S2/commit/0f3ea3b5c668137bc2fcb53bd186a41b34140e25)) -* 暴露afterRealCellRender,这样能够更灵活的使用datacell ([#1970](https://github.com/antvis/S2/issues/1970)) ([66c5ab9](https://github.com/antvis/S2/commit/66c5ab9992c51b475be8acaf9a198d49f3114a49)) +* 暴露 afterRealCellRender,这样能够更灵活的使用 datacell ([#1970](https://github.com/antvis/S2/issues/1970)) ([66c5ab9](https://github.com/antvis/S2/commit/66c5ab9992c51b475be8acaf9a198d49f3114a49)) ### BREAKING CHANGES -* s2Options.tooltip 和 s2Options.style API 命名更改, 移除 trend 操作项 +* s2Options.tooltip 和 s2Options.style API 命名更改,移除 trend 操作项 * refactor: tree 相关配置移动到 rowCell 下 @@ -226,13 +275,6 @@ * 2.0-next * 2.0 * <https://github.com/antvis/S2/discussions/1933> -======= - -# [@antv/s2-react-v1.32.0](https://github.com/antvis/S2/compare/@antv/s2-react-v1.31.0...@antv/s2-react-v1.32.0) (2022-11-21) - -### Features - -* 明细表支持多级表头 ([#1921](https://github.com/antvis/S2/issues/1921)) ([47cdbdc](https://github.com/antvis/S2/commit/47cdbdccafbd7f19a05550a483a42aac11a93778)), closes [#1687](https://github.com/antvis/S2/issues/1687) [#1801](https://github.com/antvis/S2/issues/1801) ### Features diff --git a/packages/s2-react/README.md b/packages/s2-react/README.md index 18aa74b5e1..8881ab1bc7 100644 --- a/packages/s2-react/README.md +++ b/packages/s2-react/README.md @@ -106,7 +106,7 @@ const s2DataConfig = { ```ts const s2Options = { width: 600, - height: 480, + height: 480 } ``` @@ -118,18 +118,17 @@ const s2Options = { <div id="container"></div> ``` -```ts +```tsx +import ReactDOM from 'react-dom' import { SheetComponent } from '@antv/s2-react'; import '@antv/s2-react/dist/style.min.css'; -const container = document.getElementById('container'); - ReactDOM.render( <SheetComponent dataCfg={s2DataConfig} options={s2Options} />, - document.getElementById('container'), + document.getElementById('container') ); ``` diff --git a/packages/s2-react/__tests__/spreadsheet/drill-down-spec.tsx b/packages/s2-react/__tests__/spreadsheet/drill-down-spec.tsx index b50db9179b..bc3f5ffe52 100644 --- a/packages/s2-react/__tests__/spreadsheet/drill-down-spec.tsx +++ b/packages/s2-react/__tests__/spreadsheet/drill-down-spec.tsx @@ -1,4 +1,4 @@ -import { customMerge, GuiIcon, Node, RowCell, SpreadSheet } from '@antv/s2'; +import { customMerge, Node, SpreadSheet } from '@antv/s2'; import { waitFor } from '@testing-library/react'; import { get, noop } from 'lodash'; import React from 'react'; @@ -32,18 +32,13 @@ const partDrillDownParams: SheetComponentsProps['partDrillDown'] = { }), }; -const findDrillDownIcon = (instance: SpreadSheet) => { - const rowHeaderActionIcons = get( - (instance.facet.rowHeader?.children as RowCell[]).find( - (item) => item.getActualText() === '杭州', - ), - 'actionIcons', - [], - ); - - return rowHeaderActionIcons.find( - (icon: GuiIcon) => get(icon, 'cfg.name') === 'DrillDownIcon', - ); +const findDrillDownIcon = (s2: SpreadSheet) => { + const rowHeaderActionIcons = s2.facet + ?.getRowCells() + ?.find((cell) => cell.getActualText() === '杭州') + ?.getActionIcons(); + + return rowHeaderActionIcons?.find((icon) => icon.name === 'DrillDownIcon'); }; describe('Spread Sheet Drill Down Tests', () => { diff --git a/packages/s2-react/__tests__/spreadsheet/filter-sheet-spec.tsx b/packages/s2-react/__tests__/spreadsheet/filter-sheet-spec.tsx index 80dad59db9..d92a210ab7 100644 --- a/packages/s2-react/__tests__/spreadsheet/filter-sheet-spec.tsx +++ b/packages/s2-react/__tests__/spreadsheet/filter-sheet-spec.tsx @@ -1,43 +1,22 @@ /* eslint-disable no-console */ -import { - DeviceType, - S2Event, - SpreadSheet, - TableSheet, - type S2DataConfig, - type S2MountContainer, - type S2Options, -} from '@antv/s2'; +import { DeviceType, S2Event, SpreadSheet, type S2DataConfig } from '@antv/s2'; import { Button, Space } from 'antd'; import React from 'react'; -import { act } from 'react-dom/test-utils'; -import { getMockData, renderComponent, sleep } from '../util/helpers'; +import { waitFor } from '@testing-library/react'; +import type { Root } from 'react-dom/client'; import { - SheetComponent, - type SheetComponentOptions, - type SheetComponentsProps, -} from '@/components'; + getContainer, + getMockData, + renderComponent, + sleep, +} from '../util/helpers'; +import { SheetComponent, type SheetComponentOptions } from '@/components'; const data = getMockData('../data/tableau-supermarket.csv'); -let spreadSheet: SpreadSheet; - -const onMounted = - (ref: React.MutableRefObject<SpreadSheet | undefined>) => - ( - dom: S2MountContainer, - dataCfg: S2DataConfig, - options: SheetComponentsProps['options'], - ) => { - const s2 = new TableSheet(dom, dataCfg, options as S2Options); - - ref.current = s2; - spreadSheet = s2; - - return s2; - }; +let s2: SpreadSheet; -const columns = [ +const columns: string[] = [ 'order_id', 'order_date', 'ship_date', @@ -55,9 +34,9 @@ const columns = [ 'count', 'discount', 'profit', -] as const; +]; -const meta = [ +const meta: S2DataConfig['meta'] = [ { field: 'count', name: '销售个数', @@ -75,7 +54,7 @@ function MainLayout() { }, meta, data, - } as unknown as S2DataConfig; + }; const options: SheetComponentOptions = { width: 800, @@ -84,6 +63,7 @@ function MainLayout() { device: DeviceType.PC, interaction: { enableCopy: true, + linkFields: ['order_id', 'customer_name'], }, style: { dataCell: { @@ -102,7 +82,7 @@ function MainLayout() { }, }; - const s2Ref = React.useRef<SpreadSheet | undefined>(undefined); + const s2Ref = React.useRef<SpreadSheet | null>(null); return ( <Space direction="vertical"> @@ -116,7 +96,6 @@ function MainLayout() { > Filter </Button> - <Button onClick={() => { s2Ref.current?.emit(S2Event.RANGE_FILTER, { @@ -128,85 +107,121 @@ function MainLayout() { Reset </Button> <SheetComponent + ref={s2Ref} dataCfg={dataCfg} adaptive={false} options={options} sheetType={'table'} - spreadsheet={onMounted(s2Ref)} + onMounted={(spreadsheet) => { + s2 = spreadsheet; + }} /> </Space> ); } describe('table sheet filter spec', () => { - renderComponent(<MainLayout />); + let container: HTMLDivElement; + let unmount: Root['unmount']; - test('filter customer_type values', async () => { - spreadSheet.emit(S2Event.RANGE_FILTER, { - filterKey: 'customer_type', - filteredValues: ['消费者'], - }); + const filterKey = 'customer_type'; + const filteredValue = '消费者'; - await sleep(50); + beforeEach(() => { + container = getContainer(); - expect(spreadSheet.facet.getCellRange()).toStrictEqual({ - end: 467, - start: 0, - }); + unmount = renderComponent(<MainLayout />); }); - test('reset filter params on customer_type', async () => { - spreadSheet.emit(S2Event.RANGE_FILTER, { - filterKey: 'customer_type', - filteredValues: ['消费者'], - }); + afterEach(() => { + container?.remove(); + unmount?.(); + }); + + test('filter customer_type values', async () => { + await waitFor(() => { + s2.emit(S2Event.RANGE_FILTER, { + filterKey, + filteredValues: [filteredValue], + }); - spreadSheet.emit(S2Event.RANGE_FILTER, { - filterKey: 'customer_type', - filteredValues: [], + expect(s2.facet.getCellRange()).toStrictEqual({ + end: 465, + start: 0, + }); + expect(s2.dataSet.getDisplayDataSet()).toHaveLength(466); + expect( + s2.dataSet + .getDisplayDataSet() + .some((item) => item['customer_type'] === filteredValue), + ).toBeFalsy(); }); + }); - await sleep(50); + test('reset filter params on customer_type', async () => { + await waitFor(() => { + s2.emit(S2Event.RANGE_FILTER, { + filterKey, + filteredValues: [filteredValue], + }); - expect(spreadSheet.facet.getCellRange()).toStrictEqual({ - end: 999, - start: 0, + s2.emit(S2Event.RANGE_FILTER, { + filterKey, + filteredValues: [], + }); + + expect(s2.facet.getCellRange()).toStrictEqual({ + end: 999, + start: 0, + }); + expect(s2.dataSet.getDisplayDataSet()).toHaveLength(1000); }); }); test('filtered event fired with new data', async () => { - let dataLength = 0; - - spreadSheet.on(S2Event.RANGE_FILTERED, (data) => { - dataLength = data.length; - }); + await waitFor(async () => { + let dataLength = 0; + + s2.on(S2Event.RANGE_FILTERED, (data) => { + dataLength = data.length; + expect(data.length).toStrictEqual(466); + expect(s2.dataSet.getDisplayDataSet()).toHaveLength(466); + expect( + s2.dataSet + .getDisplayDataSet() + .some((item) => item['customer_type'] === filteredValue), + ).toBeFalsy(); + }); - spreadSheet.emit(S2Event.RANGE_FILTER, { - filterKey: 'customer_type', - filteredValues: ['消费者'], - }); + s2.emit(S2Event.RANGE_FILTER, { + filterKey, + filteredValues: [filteredValue], + }); - await sleep(50); + await sleep(50); - expect(dataLength).toStrictEqual(468); + expect(dataLength).toStrictEqual(466); + }); }); test('falsy/nullish data should not be filtered with irrelevant filter params', async () => { - let dataLength = 0; + await waitFor(async () => { + let dataLength = 0; - spreadSheet.on(S2Event.RANGE_FILTERED, (data) => { - dataLength = data.length; - }); + s2.on(S2Event.RANGE_FILTERED, (data) => { + dataLength = data.length; + expect(data.length).toStrictEqual(1000); + expect(s2.dataSet.getDisplayDataSet()).toHaveLength(1000); + }); - act(() => { - spreadSheet.emit(S2Event.RANGE_FILTER, { + s2.emit(S2Event.RANGE_FILTER, { filterKey: 'express_type', filteredValues: ['消费者'], }); - }); - await sleep(50); + await sleep(200); - expect(dataLength).toStrictEqual(468); + expect(dataLength).toStrictEqual(1000); + }); }); }); diff --git a/packages/s2-react/__tests__/spreadsheet/pagination-spec.tsx b/packages/s2-react/__tests__/spreadsheet/pagination-spec.tsx index 34ea40d934..3517077d08 100644 --- a/packages/s2-react/__tests__/spreadsheet/pagination-spec.tsx +++ b/packages/s2-react/__tests__/spreadsheet/pagination-spec.tsx @@ -1,7 +1,14 @@ -import { SpreadSheet, setLang, type LangType, type Pagination } from '@antv/s2'; +import { + SpreadSheet, + setLang, + type LangType, + type Pagination, + type S2DataConfig, +} from '@antv/s2'; import { waitFor } from '@testing-library/react'; import React from 'react'; import type { Root } from 'react-dom/client'; +import { pivotSheetDataCfg } from '../../playground/config'; import { SheetComponent, type SheetComponentsProps } from '../../src'; import * as mockDataConfig from '../data/simple-data.json'; import { renderComponent } from '../util/helpers'; @@ -16,6 +23,8 @@ const s2Options: SheetComponentsProps['options'] = { hierarchyType: 'grid', }; +let s2: SpreadSheet; + describe('Pagination Tests', () => { let unmount: Root['unmount']; @@ -45,7 +54,7 @@ describe('Pagination Tests', () => { unmount = renderComponent( <SheetComponent options={s2Options} - dataCfg={mockDataConfig as any} + dataCfg={mockDataConfig as S2DataConfig} showPagination onMounted={(instance) => { spreadsheet = instance; @@ -79,7 +88,7 @@ describe('Pagination Tests', () => { showQuickJumper: true, } as Pagination, }} - dataCfg={mockDataConfig as any} + dataCfg={mockDataConfig as S2DataConfig} showPagination onMounted={(instance) => { spreadsheet = instance; @@ -97,4 +106,31 @@ describe('Pagination Tests', () => { ).toBeFalsy(); }); }); + + test('should row header cell render text position based on the actual cell height when pagination is show', async () => { + renderComponent( + <SheetComponent + options={{ + ...s2Options, + pagination: { + ...s2Options.pagination, + current: 1, + pageSize: 1, + }, + height: 400, + }} + dataCfg={pivotSheetDataCfg} + onMounted={(instance) => { + s2 = instance; + }} + showPagination + />, + ); + + await waitFor(() => { + const rowCell = s2.facet.getRowCells()[0]; + + expect(rowCell.getTextShape().parsedStyle.y).toBe(15); + }); + }); }); diff --git a/packages/s2-react/__tests__/unit/components/sheets/strategy-sheet/__snapshots__/index-spec.tsx.snap b/packages/s2-react/__tests__/unit/components/sheets/strategy-sheet/__snapshots__/index-spec.tsx.snap index cb8da06994..38c5b643e0 100644 --- a/packages/s2-react/__tests__/unit/components/sheets/strategy-sheet/__snapshots__/index-spec.tsx.snap +++ b/packages/s2-react/__tests__/unit/components/sheets/strategy-sheet/__snapshots__/index-spec.tsx.snap @@ -15,6 +15,36 @@ exports[`<StrategySheet/> Tests StrategySheet Export Tests should export correct 指标E 自定义节点D " `; +exports[`<StrategySheet/> Tests StrategySheet Export Tests should export correct data for custom corner text 1`] = ` +" 日期 2022-09 2022-10 2022-11 2021年净增完成度 趋势 2022 + 指标 数值 环比 同比 数值 环比 数值 环比 同比 净增完成度 趋势 数值 环比 +自定义节点A - +自定义节点A 指标A 377 3877 4324 42% - - 377 +自定义节点A 指标A 指标B 377 324 377 324 -0.02 - - 377 324 +自定义节点A 指标A 自定义节点B +自定义节点A 指标A 指标C 324 377 0 - - 324 +自定义节点A 指标A 指标D 377 324 377 324 0.02 - - 377 324 +自定义节点A 自定义节点E +指标E 377 324 0.02 - - +指标E 自定义节点C +指标E 自定义节点D " +`; + +exports[`<StrategySheet/> Tests StrategySheet Export Tests should export correct data for default corner text 1`] = ` +" 日期 2022-09 2022-10 2022-11 2021年净增完成度 趋势 2022 + 指标 数值 环比 同比 数值 环比 数值 环比 同比 净增完成度 趋势 数值 环比 +自定义节点A - +自定义节点A 指标A 377 3877 4324 42% - - 377 +自定义节点A 指标A 指标B 377 324 377 324 -0.02 - - 377 324 +自定义节点A 指标A 自定义节点B +自定义节点A 指标A 指标C 324 377 0 - - 324 +自定义节点A 指标A 指标D 377 324 377 324 0.02 - - 377 324 +自定义节点A 自定义节点E +指标E 377 324 0.02 - - +指标E 自定义节点C +指标E 自定义节点D " +`; + exports[`<StrategySheet/> Tests StrategySheet Export Tests should export correct data for empty cell 1`] = ` " 日期 2022-09 2022-10 2022-11 2021年净增完成度 趋势 2022 指标 数值 环比 同比 数值 环比 数值 环比 同比 净增完成度 趋势 数值 环比 diff --git a/packages/s2-react/__tests__/unit/components/sheets/strategy-sheet/custom-tooltip/index-spec.tsx b/packages/s2-react/__tests__/unit/components/sheets/strategy-sheet/custom-tooltip/index-spec.tsx index c45a84b423..f25ae6607a 100644 --- a/packages/s2-react/__tests__/unit/components/sheets/strategy-sheet/custom-tooltip/index-spec.tsx +++ b/packages/s2-react/__tests__/unit/components/sheets/strategy-sheet/custom-tooltip/index-spec.tsx @@ -72,4 +72,28 @@ describe('StrategySheet Tooltip Tests', () => { expect(screen.getAllByText('customDerivedValue')).toHaveLength(3); expect(screen.getAllByText('customDerivedValue')).toMatchSnapshot(); }); + + // cli 的方式得到的结果是错, 基于 test:live 就是对的, 不知道为啥 + test.skip('should render overflow wrap description for row tooltip', () => { + const description = `test_`.repeat(40); + const mockDescCellInfo = createMockCellInfo('test', { + extra: { + description, + }, + }); + + const { container } = render( + <StrategySheetRowCellTooltip + cell={mockDescCellInfo.mockCell} + label="test row label" + />, + ); + + const { width, height } = container! + .querySelector('.s2-strategy-sheet-tooltip-description-text')! + .getBoundingClientRect(); + + expect(Math.floor(width)).toBeCloseTo(937); + expect(Math.floor(height)).toBeCloseTo(37); + }); }); diff --git a/packages/s2-react/__tests__/unit/components/sheets/strategy-sheet/index-spec.tsx b/packages/s2-react/__tests__/unit/components/sheets/strategy-sheet/index-spec.tsx index 3c81121e27..b63fbc5ebf 100644 --- a/packages/s2-react/__tests__/unit/components/sheets/strategy-sheet/index-spec.tsx +++ b/packages/s2-react/__tests__/unit/components/sheets/strategy-sheet/index-spec.tsx @@ -1,29 +1,29 @@ /* eslint-disable max-classes-per-file */ import { CellType, - customMerge, - getCellMeta, + CornerNodeType, InteractionStateName, + RowCell, SpreadSheet, - type S2DataConfig, + customMerge, + getCellMeta, type GEvent, - RowCell, + type S2DataConfig, } from '@antv/s2'; -import React from 'react'; -import { get } from 'lodash'; import { waitFor } from '@testing-library/react'; -import { getContainer, renderComponent } from '../../../../util/helpers'; +import React from 'react'; import { - StrategySheetDataConfig, StrategyOptions, + StrategySheetDataConfig, } from '../../../../data/strategy-data'; +import { getContainer, renderComponent } from '../../../../util/helpers'; +import { strategyCopy } from '@/components/export/strategy-copy'; import { SheetComponent, StrategySheetColCell, StrategySheetDataCell, type SheetComponentOptions, } from '@/components'; -import { strategyCopy } from '@/components/export/strategy-copy'; describe('<StrategySheet/> Tests', () => { let s2: SpreadSheet; @@ -132,6 +132,28 @@ describe('<StrategySheet/> Tests', () => { }, ); + test('should get current cell custom tooltip content', () => { + renderStrategySheet({ + tooltip: { + enable: true, + rowCell: { + content: () => <div>{CellType.ROW_CELL}</div>, + }, + dataCell: { + content: () => <div>{CellType.DATA_CELL}</div>, + }, + }, + }); + + jest.spyOn(s2, 'getCellType').mockReturnValueOnce(CellType.COL_CELL); + + s2.showTooltipWithInfo({} as GEvent, []); + + [CellType.ROW_CELL, CellType.DATA_CELL].forEach((content) => { + expect(s2.tooltip.container!.innerText).not.toEqual(content); + }); + }); + test('should render correctly KPI bullet column measure text', async () => { renderStrategySheet( { @@ -167,6 +189,37 @@ describe('<StrategySheet/> Tests', () => { }); }); + test('should get custom corner extra field text', async () => { + const cornerExtraFieldText = '自定义'; + const s2DataCfg: Partial<S2DataConfig> = { + fields: { + ...StrategySheetDataConfig.fields, + valueInCols: false, + }, + }; + + const s2Options: SheetComponentOptions = { + cornerExtraFieldText, + }; + + renderStrategySheet(s2Options, { + ...StrategySheetDataConfig, + ...s2DataCfg, + }); + + await waitFor(() => { + const cornerNode = s2.facet + .getCornerNodes() + .find((node) => node.cornerType === CornerNodeType.Row); + + const textList = s2.facet.getCornerNodes().map((node) => node.value); + const cornerText = `自定义节点A/指标E/${cornerExtraFieldText}`; + + expect(textList).toEqual([cornerText, '日期']); + expect(cornerNode!.value).toEqual(cornerText); + }); + }); + test('should format corner date field', async () => { renderStrategySheet( { @@ -185,11 +238,11 @@ describe('<StrategySheet/> Tests', () => { ); await waitFor(() => { - const textList = s2.facet.cornerHeader.children.map((element) => - get(element, 'actualText'), - ); + const cornerTextList = s2.facet + .getCornerCells() + .map((cell) => cell.getActualText()); - expect(textList).toEqual(['自定义节点A/指标E/指标', '日期']); + expect(cornerTextList).toEqual(['自定义节点A/指标E/数值', '日期']); }); }); @@ -212,11 +265,11 @@ describe('<StrategySheet/> Tests', () => { ); await waitFor(() => { - const textList = s2.facet.cornerHeader.children.map((element) => - get(element, 'actualText'), - ); + const cornerTextList = s2.facet + .getCornerCells() + .map((cell) => cell.getActualText()); - expect(textList).toEqual(['测试', '日期']); + expect(cornerTextList).toEqual(['测试', '日期']); }); }); @@ -265,6 +318,30 @@ describe('<StrategySheet/> Tests', () => { }); }); + test('should export correct data for default corner text', async () => { + await waitFor(() => { + s2.setOptions({ + cornerText: undefined, + }); + + const result = strategyCopy(s2, '\t', true); + + expect(result).toMatchSnapshot(); + }); + }); + + test('should export correct data for custom corner text', async () => { + await waitFor(() => { + s2.setOptions({ + cornerText: '自定义', + }); + + const result = strategyCopy(s2, '\t', true); + + expect(result).toMatchSnapshot(); + }); + }); + test('should export correct data for multi different cycle compare data', async () => { await waitFor(() => { /* diff --git a/packages/s2-react/__tests__/unit/components/tooltip/__snapshots__/index-spec.tsx.snap b/packages/s2-react/__tests__/unit/components/tooltip/__snapshots__/index-spec.tsx.snap index 7958b81bef..36062f9a4a 100644 --- a/packages/s2-react/__tests__/unit/components/tooltip/__snapshots__/index-spec.tsx.snap +++ b/packages/s2-react/__tests__/unit/components/tooltip/__snapshots__/index-spec.tsx.snap @@ -28,7 +28,7 @@ exports[`Tooltip Common Components Tests render TooltipHead 1`] = ` <div class="antv-s2-tooltip-head-info-list" > - 一二线城市,有信用卡 / 学生 / 20岁以下 + 一二线城市,有信用卡/学生/20岁以下 </div> </DocumentFragment> `; @@ -54,7 +54,7 @@ exports[`Tooltip Common Components Tests render TooltipSummary 1`] = ` <span class="antv-s2-tooltip-summary-key" > - A人群 (总和) + A人群(总和) </span> <span class="antv-s2-tooltip-summary-val antv-s2-tooltip-bold" @@ -68,7 +68,7 @@ exports[`Tooltip Common Components Tests render TooltipSummary 1`] = ` <span class="antv-s2-tooltip-summary-key" > - B人群 (总和) + B人群(总和) </span> <span class="antv-s2-tooltip-summary-val antv-s2-tooltip-bold" @@ -82,7 +82,7 @@ exports[`Tooltip Common Components Tests render TooltipSummary 1`] = ` <span class="antv-s2-tooltip-summary-key" > - 差值 (总和) + 差值(总和) </span> <span class="antv-s2-tooltip-summary-val antv-s2-tooltip-bold" diff --git a/packages/s2-react/__tests__/unit/components/tooltip/index-spec.tsx b/packages/s2-react/__tests__/unit/components/tooltip/index-spec.tsx index 4fca6a6589..480ad69e72 100644 --- a/packages/s2-react/__tests__/unit/components/tooltip/index-spec.tsx +++ b/packages/s2-react/__tests__/unit/components/tooltip/index-spec.tsx @@ -5,6 +5,8 @@ import { getTooltipOperatorSortMenus, type S2CellType, type TooltipOperatorMenuItem, + type TooltipDetailListItem, + type TooltipSummaryOptions, } from '@antv/s2'; import { render, screen, waitFor } from '@testing-library/react'; import React from 'react'; @@ -158,7 +160,7 @@ describe('Tooltip Common Components Tests', () => { }); test('render TooltipDetail', () => { - const list = [ + const list: TooltipDetailListItem[] = [ { name: '20岁以下', value: '20.5%', @@ -173,8 +175,11 @@ describe('Tooltip Common Components Tests', () => { }); test('render TooltipHead', () => { - const cols = [{ name: '所在城市', value: '一二线城市' }]; - const rows = [ + const cols: TooltipDetailListItem[] = [ + { name: '所在城市', value: '一二线城市' }, + ]; + + const rows: TooltipDetailListItem[] = [ { name: '类别', value: '有信用卡' }, { name: '职业', value: '学生' }, { name: '年龄分布', value: '20岁以下' }, @@ -185,11 +190,11 @@ describe('Tooltip Common Components Tests', () => { ); expect(asFragment()).toMatchSnapshot(); - expect(getByText('一二线城市,有信用卡 / 学生 / 20岁以下')).toBeTruthy(); + expect(getByText('一二线城市,有信用卡/学生/20岁以下')).toBeTruthy(); }); test('render TooltipSummary', () => { - const summaries = [ + const summaries: TooltipSummaryOptions[] = [ { name: 'A人群', selectedData: Array(30), value: '495.48 %' }, { name: 'B人群', selectedData: Array(30), value: '494.52%' }, { name: '差值', selectedData: Array(30), value: '+381%' }, diff --git a/packages/s2-react/__tests__/unit/hooks/useEvents-spec.ts b/packages/s2-react/__tests__/unit/hooks/useEvents-spec.ts index 9ab2db6c48..d39a4ddd3c 100644 --- a/packages/s2-react/__tests__/unit/hooks/useEvents-spec.ts +++ b/packages/s2-react/__tests__/unit/hooks/useEvents-spec.ts @@ -1,14 +1,14 @@ -import { renderHook, act } from '@testing-library/react-hooks'; import { + GEvent, PivotSheet, S2Event, SpreadSheet, - GEvent, type S2DataConfig, type S2Options, } from '@antv/s2'; -import { createMockCellInfo, getContainer } from 'tests/util/helpers'; +import { act, renderHook } from '@testing-library/react-hooks'; import * as mockDataConfig from 'tests/data/simple-data.json'; +import { createMockCellInfo, getContainer } from 'tests/util/helpers'; import type { SheetComponentsProps } from '../../../src/components'; import { useCellEvent, useEvents, useS2Event } from '@/hooks'; diff --git a/packages/s2-react/__tests__/util/helpers.ts b/packages/s2-react/__tests__/util/helpers.ts index cc03b16f16..2dc7385da4 100644 --- a/packages/s2-react/__tests__/util/helpers.ts +++ b/packages/s2-react/__tests__/util/helpers.ts @@ -45,7 +45,7 @@ export function getMockSheetInstance(Sheet: typeof SpreadSheet = PivotSheet) { export const createMockCellInfo = ( cellId: string, - { colIndex = 0, rowIndex = 0 } = {}, + { colIndex = 0, rowIndex = 0, extra = {} } = {}, ) => { const mockCellViewMeta: Partial<ViewMeta> = { id: cellId, @@ -75,6 +75,7 @@ export const createMockCellInfo = ( getFieldName: jest.fn(), }, } as unknown as SpreadSheet, + extra, }; const mockCellMeta = omit(mockCellViewMeta, 'update'); const mockCell = { diff --git a/packages/s2-react/package.json b/packages/s2-react/package.json index c0729d5a72..c14e701d09 100644 --- a/packages/s2-react/package.json +++ b/packages/s2-react/package.json @@ -44,10 +44,11 @@ "build:umd": "cross-env FORMAT=umd vite build", "build:analysis": "cross-env FORMAT=es ANALYSIS=true vite build", "build:dts": "run-s dts:*", + "build:size-limit": "size-limit", + "build:size-limit-json": "pnpm build:size-limit --json", "watch": "rimraf esm && pnpm build:esm -w", "dts:build": "tsc -p tsconfig.declaration.json", "dts:extract": "cross-env LIB=s2-react node ../../scripts/dts.js", - "bundle:size": "bundlesize", "test": "jest --passWithNoTests", "test:coverage": "pnpm test -- --coverage", "test:ci": "pnpm test -- --maxWorkers=3", @@ -93,14 +94,15 @@ "react-dom": "^18.2.0", "vite-plugin-svgr": "^2.2.2" }, - "bundlesize": [ + "size-limit": [ { "path": "./dist/index.min.js", - "maxSize": "650 kB" + "import": "{ createComponent }", + "limit": "70 kB" }, { "path": "./dist/style.min.css", - "maxSize": "205 kB" + "limit": "5 kB" } ], "publishConfig": { diff --git a/packages/s2-react/playground/components/GridAnalysisSheet.tsx b/packages/s2-react/playground/components/GridAnalysisSheet.tsx index 654029aaee..173240e931 100644 --- a/packages/s2-react/playground/components/GridAnalysisSheet.tsx +++ b/packages/s2-react/playground/components/GridAnalysisSheet.tsx @@ -1,5 +1,6 @@ import { isUpDataValue, SpreadSheet } from '@antv/s2'; import React from 'react'; +import { LayoutWidthType } from '@antv/s2'; import { SheetComponent, type SheetComponentOptions, @@ -15,7 +16,7 @@ export const mockGridAnalysisOptions: SheetComponentOptions = { selectedCellsSpotlight: true, }, style: { - layoutWidthType: 'colAdaptive', + layoutWidthType: LayoutWidthType.ColAdaptive, rowCell: { width: 80, height: 100, diff --git a/packages/s2-react/playground/config.tsx b/packages/s2-react/playground/config.tsx index 54006e552d..38bf9ddfb7 100644 --- a/packages/s2-react/playground/config.tsx +++ b/packages/s2-react/playground/config.tsx @@ -7,6 +7,8 @@ import { type CustomTreeNode, type S2DataConfig, type S2TableSheetFrozenOptions, + customMerge, + type ThemeCfg, } from '@antv/s2'; import { getBaseSheetComponentOptions } from '@antv/s2-shared'; import type { SliderSingleProps } from 'antd'; @@ -74,6 +76,39 @@ export const pivotSheetDataCfg: S2DataConfig = { export const pivotSheetMultiLineTextDataCfg = PivotSheetMultiLineTextDataCfg; +export const pivotSheetDataCfgForCompactMode = customMerge<S2DataConfig>( + pivotSheetDataCfg, + { + data: [ + ...pivotSheetDataCfg.data, + { + province: '浙江', + city: '杭州', + type: '笔', + price: '11111111', + }, + { + province: '浙江', + city: '杭州', + type: '纸张', + price: '2', + }, + { + province: '浙江', + city: '舟山', + type: '笔', + price: '2', + }, + { + province: '浙江', + city: '舟山', + type: '纸张', + price: '133.333', + }, + ], + }, +); + export const s2ConditionsOptions: SheetComponentOptions['conditions'] = { text: [ { @@ -292,6 +327,8 @@ export const s2Options: SheetComponentOptions = { interaction: { enableCopy: true, copyWithFormat: true, + copyWithHeader: true, + hoverAfterScroll: true, // 防止 mac 触摸板横向滚动触发浏览器返回, 和移动端下拉刷新 overscrollBehavior: 'none', brushSelection: { @@ -300,7 +337,7 @@ export const s2Options: SheetComponentOptions = { rowCell: true, }, resize: { - rowResizeType: ResizeType.CURRENT, + rowResizeType: ResizeType.ALL, colResizeType: ResizeType.CURRENT, }, }, @@ -346,5 +383,10 @@ export const sliderOptions: SliderSingleProps = { }, }; +export const s2ThemeConfig: ThemeCfg = { + name: 'default', + theme: {}, +}; + export const defaultOptions = getBaseSheetComponentOptions<SheetComponentOptions>(s2Options); diff --git a/packages/s2-react/playground/drill-down.tsx b/packages/s2-react/playground/drill-down.tsx index bcdc8c02ad..88b87355f3 100644 --- a/packages/s2-react/playground/drill-down.tsx +++ b/packages/s2-react/playground/drill-down.tsx @@ -1,4 +1,4 @@ -import type { PivotDataSet, RawData } from '@antv/s2'; +import { ORIGIN_FIELD, type PivotDataSet, type RawData } from '@antv/s2'; import type { PartDrillDownInfo } from '@antv/s2-shared'; import { forEach, random } from 'lodash'; import React from 'react'; @@ -29,15 +29,12 @@ export const partDrillDown: PartDrillDown = { fetchData: (meta, drillFields) => new Promise<PartDrillDownInfo>((resolve) => { // 弹窗 -> 选择 -> 请求数据 - const preDrillDownfield = - meta.spreadsheet.store.get('drillDownNode')?.field; const dataSet = meta.spreadsheet.dataSet as PivotDataSet; const field = drillFields[0]; const rowData = dataSet .getCellMultiData({ query: meta.query!, - drillDownFields: [preDrillDownfield], }) .filter( (item) => @@ -47,7 +44,7 @@ export const partDrillDown: PartDrillDown = { const drillDownData: RawData[] = []; forEach(rowData, (data) => { - const { number, sub_type: subType, type } = data.getOrigin(); + const { number, sub_type: subType, type } = data[ORIGIN_FIELD]; const number0 = random(50, number as number); const number1 = (number as number) - number0; const dataItem0: RawData = { diff --git a/packages/s2-react/playground/index.html b/packages/s2-react/playground/index.html index 13576bda2a..901bfef23c 100644 --- a/packages/s2-react/playground/index.html +++ b/packages/s2-react/playground/index.html @@ -8,6 +8,5 @@ <body> <div id="root"></div> <script type="module" src="./index.tsx"></script> - </body> -</html> \ No newline at end of file +</html> diff --git a/packages/s2-react/playground/index.tsx b/packages/s2-react/playground/index.tsx index f1b4a295b5..722ce26b66 100644 --- a/packages/s2-react/playground/index.tsx +++ b/packages/s2-react/playground/index.tsx @@ -12,7 +12,7 @@ import { getPalette, type CustomHeaderFields, type HeaderActionIconProps, - type InteractionCellSelectedHighlightOptions, + type InteractionCellHighlightOptions, type InteractionOptions, type S2DataConfig, type TargetCellInfo, @@ -48,22 +48,25 @@ import reactPkg from '../package.json'; import type { SheetComponentOptions } from '../src'; import { SheetComponent } from '../src'; import { ConfigProvider } from '../src/components/config-provider'; +import { ChartSheet } from './components/ChartSheet'; import { CustomGrid } from './components/CustomGrid'; import { CustomTree } from './components/CustomTree'; import { EditableSheet } from './components/EditableSheet'; import { GridAnalysisSheet } from './components/GridAnalysisSheet'; +import { LinkGroup } from './components/LinkGroup'; import { MobileSheetComponent } from './components/Mobile'; import { PluginsSheet } from './components/Plugins'; import { ResizeConfig } from './components/ResizeConfig'; import { StrategySheet } from './components/StrategySheet'; -import { LinkGroup } from './components/LinkGroup'; import { TableSheetFrozenOptions, defaultOptions, pivotSheetDataCfg, + pivotSheetDataCfgForCompactMode, pivotSheetMultiLineTextDataCfg, s2ConditionsOptions, s2Options, + s2ThemeConfig, sliderOptions, tableSheetDataCfg, tableSheetMultipleColumns, @@ -71,12 +74,18 @@ import { } from './config'; import { PlaygroundContext } from './context/playground.context'; import { partDrillDown } from './drill-down'; -import { onSheetMounted } from './utils'; -import { ChartSheet } from './components/ChartSheet'; import './index.less'; type TableSheetColumnType = 'single' | 'multiple'; +const onSheetMounted = (s2: SpreadSheet) => { + console.log('onSheetMounted: ', s2); + // @ts-ignore + window.s2 = s2; + // @ts-ignore + window.g_instances = [s2.container]; +}; + const CustomTooltip = () => ( <div> 自定义 Tooltip <div>1</div> @@ -99,9 +108,7 @@ function MainLayout() { ); const [showPagination, setShowPagination] = React.useState(false); const [showTotals, setShowTotals] = React.useState(false); - const [themeCfg, setThemeCfg] = React.useState<ThemeCfg>({ - name: 'default', - }); + const [themeCfg, setThemeCfg] = React.useState<ThemeCfg>(s2ThemeConfig); const [themeColor, setThemeColor] = React.useState<string>('#FFF'); const [showCustomTooltip, setShowCustomTooltip] = React.useState(false); const [adaptive, setAdaptive] = React.useState<Adaptive>(false); @@ -269,10 +276,30 @@ function MainLayout() { clearInterval(scrollTimer.current!); }); + useUpdateEffect(() => { + switch (options!.style!.layoutWidthType) { + case 'compact': + updateOptions({ + style: { + dataCell: { + width: 200, + }, + }, + }); + setDataCfg(pivotSheetDataCfgForCompactMode); + break; + + default: + updateOptions({ + style: DEFAULT_STYLE, + }); + setDataCfg(pivotSheetDataCfg); + } + }, [options.style!.layoutWidthType]); + // ================== Config ======================== const mergedOptions: SheetComponentOptions = customMerge( - {}, { pagination: showPagination && { pageSize: 10, @@ -416,7 +443,9 @@ function MainLayout() { <Tooltip title="布局类型"> <Radio.Group onChange={onLayoutWidthTypeChange} - defaultValue="adaptive" + defaultValue={ + options?.style?.layoutWidthType + } > <Radio.Button value="adaptive"> 行列等宽 @@ -541,6 +570,30 @@ function MainLayout() { }} disabled={sheetType === 'table'} /> + <Tooltip title="使用场景: 1. 开启总计, 且置于顶部, 2. 树状模式(关闭序号)"> + <Switch + checkedChildren="冻结首行开" + unCheckedChildren="冻结首行关" + defaultChecked={ + mergedOptions.frozen?.firstRow + } + onChange={(checked) => { + updateOptions({ + frozen: { + firstRow: checked, + }, + }); + }} + disabled={ + sheetType === 'table' || + (mergedOptions.hierarchyType === 'grid' && + (!mergedOptions?.totals?.row + ?.showGrandTotals || + !mergedOptions?.totals?.row + ?.reverseGrandTotalsLayout)) + } + /> + </Tooltip> <Tooltip title="透视表有效"> <Switch checkedChildren="冻结行头开" @@ -1182,8 +1235,7 @@ function MainLayout() { onChange={(type) => { let selectedCellHighlight: | boolean - | InteractionCellSelectedHighlightOptions = - false; + | InteractionCellHighlightOptions = false; const oldIdx = type.findIndex( (typeItem: any) => isBoolean(typeItem), ); @@ -1232,9 +1284,9 @@ function MainLayout() { <Switch checkedChildren="hover十字器开" unCheckedChildren="hover十字器关" - checked={ - mergedOptions?.interaction?.hoverHighlight - } + checked={Boolean( + mergedOptions?.interaction?.hoverHighlight, + )} onChange={(checked) => { updateOptions({ interaction: { @@ -1463,7 +1515,11 @@ function MainLayout() { { key: 'editable', label: '编辑表', - children: <EditableSheet />, + children: ( + <EditableSheet + onDataCellEditEnd={logHandler('onDataCellEditEnd')} + /> + ), }, { key: 'mobile', diff --git a/packages/s2-react/playground/utils.ts b/packages/s2-react/playground/utils.ts index b331180c4c..c638769034 100644 --- a/packages/s2-react/playground/utils.ts +++ b/packages/s2-react/playground/utils.ts @@ -1,5 +1,7 @@ +/* eslint-disable no-restricted-imports */ /* eslint-disable no-console */ import type { SpreadSheet } from '@antv/s2'; +import _ from 'lodash'; export const onSheetMounted = (s2: SpreadSheet) => { console.log('onSheetMounted: ', s2); @@ -7,4 +9,5 @@ export const onSheetMounted = (s2: SpreadSheet) => { window.s2 = s2; // @ts-ignore window.g_instances = [s2.container]; + window._ = _; }; diff --git a/packages/s2-react/src/components/advanced-sort/index.tsx b/packages/s2-react/src/components/advanced-sort/index.tsx index 8253fa0781..a0e63ceafb 100644 --- a/packages/s2-react/src/components/advanced-sort/index.tsx +++ b/packages/s2-react/src/components/advanced-sort/index.tsx @@ -5,6 +5,7 @@ import { type SortParam, SpreadSheet, TOTAL_VALUE, + EXTRA_FIELD, } from '@antv/s2'; import { Button, Cascader, Form, Layout, Modal, Radio, Select } from 'antd'; import cx from 'classnames'; @@ -124,10 +125,7 @@ export const AdvancedSort: React.FC<AdvancedSortProps> = ({ ); }; - const handleCustomSort = ( - dimension: Dimension, - splitOrders: string[] = [], - ) => { + const handleCustomSort = (dimension: Dimension, splitOrders?: string[]) => { handleCustom(); setCurrentDimension(dimension); if (splitOrders) { @@ -194,7 +192,7 @@ export const AdvancedSort: React.FC<AdvancedSortProps> = ({ current.sortMethod = sortMethod; current.query = { - $$extra$$: rule[1], + [EXTRA_FIELD]: rule[1], }; } else if (rule[0] === 'sortBy') { current.sortBy = currentSortBy; @@ -328,7 +326,7 @@ export const AdvancedSort: React.FC<AdvancedSortProps> = ({ } = item || {}; return ( - <Form.Item name={field} key={field}> + <Form.Item key={field}> <Form.Item name={[field, 'name']} initialValue={name} noStyle> <Select className={`${ADVANCED_SORT_PRE_CLS}-select`} diff --git a/packages/s2-react/src/components/export/index.tsx b/packages/s2-react/src/components/export/index.tsx index 90f52c08ad..88018e5960 100644 --- a/packages/s2-react/src/components/export/index.tsx +++ b/packages/s2-react/src/components/export/index.tsx @@ -6,7 +6,7 @@ import { asyncGetAllPlainData, copyToClipboard, download, - copyData as getSheetData, + getAllPlainData, i18n, } from '@antv/s2'; import { Dropdown, message, type DropDownProps, Button } from 'antd'; @@ -64,7 +64,7 @@ export const Export: React.FC<ExportProps> = React.memo((props) => { split: NewTab, formatOptions: isFormat, }) - : getSheetData({ + : getAllPlainData({ sheetInstance: sheet, split: NewTab, formatOptions: isFormat, @@ -82,9 +82,9 @@ export const Export: React.FC<ExportProps> = React.memo((props) => { }; const downloadData = async (isFormat: boolean) => { - const data = await getSheetData({ + const data = await asyncGetAllPlainData({ sheetInstance: sheet, - split: ',', + split: NewTab, formatOptions: isFormat, }); diff --git a/packages/s2-react/src/components/export/strategy-copy.ts b/packages/s2-react/src/components/export/strategy-copy.ts index 8ffa04c522..110f259de5 100644 --- a/packages/s2-react/src/components/export/strategy-copy.ts +++ b/packages/s2-react/src/components/export/strategy-copy.ts @@ -1,18 +1,18 @@ import { - type CopyableList, - type SheetCopyConstructorParams, - type FormatOptions, - type Node, - type ViewMeta, CornerNodeType, + NewTab, PivotDataCellCopy, SpreadSheet, - NewTab, - safeJsonParse, assembleMatrix, + getHeaderList, getMaxRowLen, getNodeFormatData, - getHeaderList, + safeJsonParse, + type CopyableList, + type FormatOptions, + type Node, + type SheetCopyConstructorParams, + type ViewMeta, } from '@antv/s2'; import { flatten, diff --git a/packages/s2-react/src/components/sheets/editable-sheet/custom-cell/edit-cell/index.tsx b/packages/s2-react/src/components/sheets/editable-sheet/custom-cell/edit-cell/index.tsx index 9d2c87f34f..458bf6a316 100644 --- a/packages/s2-react/src/components/sheets/editable-sheet/custom-cell/edit-cell/index.tsx +++ b/packages/s2-react/src/components/sheets/editable-sheet/custom-cell/edit-cell/index.tsx @@ -1,23 +1,24 @@ -import React, { - memo, - useRef, - useState, - useEffect, - useMemo, - useCallback, -} from 'react'; -import { Input } from 'antd'; import { + GEvent, S2Event, S2_PREFIX_CLS, SpreadSheet, - GEvent, type DataItem, type S2CellType, + type ViewMeta, } from '@antv/s2'; -import { isNil, pick } from 'lodash'; -import { useS2Event } from '../../../../../hooks'; +import { Input } from 'antd'; +import { isNil, merge, pick } from 'lodash'; +import React, { + memo, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { useSpreadSheetInstance } from '../../../../../context/SpreadSheetContext'; +import { useS2Event } from '../../../../../hooks'; import { invokeComponent, type InvokeComponentProps, @@ -35,6 +36,7 @@ export interface CustomProps { type onChangeProps = { onChange?: (val: any[]) => void; + onDataCellEditEnd?: (meta: ViewMeta) => void; trigger?: number; CustomComponent?: React.FunctionComponent<CustomProps>; }; @@ -46,7 +48,7 @@ function EditCellComponent( ) { const { params, resolver } = props; const s2 = useSpreadSheetInstance(); - const { cell, onChange, CustomComponent } = params; + const { cell, onChange, onDataCellEditEnd, CustomComponent } = params; const { left, top, width, height } = useMemo(() => { const rect = s2?.getCanvasElement().getBoundingClientRect(); @@ -118,6 +120,16 @@ function EditCellComponent( s2.dataSet.originData[rowIndex][valueField] = inputVal; s2.render(true); + const meta = merge(cell.getMeta(), { + fieldValue: inputVal, + valueField, + data: { + [valueField]: inputVal, + }, + }) as ViewMeta; + + onDataCellEditEnd?.(meta); + if (onChange) { onChange(s2.dataSet.originData); } diff --git a/packages/s2-react/src/components/sheets/editable-sheet/drag-copy/index.tsx b/packages/s2-react/src/components/sheets/editable-sheet/drag-copy/index.tsx index 439aa3b8b8..c60435acc5 100644 --- a/packages/s2-react/src/components/sheets/editable-sheet/drag-copy/index.tsx +++ b/packages/s2-react/src/components/sheets/editable-sheet/drag-copy/index.tsx @@ -1,11 +1,10 @@ -import React, { useState, useEffect, useCallback } from 'react'; -import { DataCell, S2Event, S2_PREFIX_CLS, GEvent } from '@antv/s2'; import type { ScrollOffset } from '@antv/s2'; +import { DataCell, GEvent, S2Event, S2_PREFIX_CLS } from '@antv/s2'; import { isEqual, pick } from 'lodash'; +import React, { useCallback, useEffect, useState } from 'react'; import { useS2Event } from '../../../../hooks'; import { useSpreadSheetInstance } from '../../../../context/SpreadSheetContext'; import { DragCopyMask } from './drag-copy-mask'; - import './drag-copy-point.less'; export function DragCopyPoint() { diff --git a/packages/s2-react/src/components/sheets/editable-sheet/index.tsx b/packages/s2-react/src/components/sheets/editable-sheet/index.tsx index 4f10bf7cb2..dd79d7c357 100644 --- a/packages/s2-react/src/components/sheets/editable-sheet/index.tsx +++ b/packages/s2-react/src/components/sheets/editable-sheet/index.tsx @@ -10,7 +10,10 @@ export const EditableSheet: React.FC<SheetComponentsProps> = React.memo( return ( <BaseSheet {...props} sheetType={'table'}> - <EditCell onChange={onChange} /> + <EditCell + onChange={onChange} + onDataCellEditEnd={props.onDataCellEditEnd} + /> <DragCopyPoint /> </BaseSheet> ); diff --git a/packages/s2-react/src/components/sheets/strategy-sheet/custom-data-set.ts b/packages/s2-react/src/components/sheets/strategy-sheet/custom-data-set.ts index 81301d5f15..5d30c20294 100644 --- a/packages/s2-react/src/components/sheets/strategy-sheet/custom-data-set.ts +++ b/packages/s2-react/src/components/sheets/strategy-sheet/custom-data-set.ts @@ -2,17 +2,34 @@ import { CustomTreePivotDataSet, type S2DataConfig, EXTRA_FIELD, + EMPTY_EXTRA_FIELD_PLACEHOLDER, + type RawData, + type Meta, + i18n, } from '@antv/s2'; -import { size } from 'lodash'; +import { isEmpty, isObject, keys, size } from 'lodash'; export class StrategySheetDataSet extends CustomTreePivotDataSet { + getExistValuesByDataItem(data: RawData) { + const result = keys(data).filter((key) => isObject(data[key])); + + if (isEmpty(result)) { + result.push(EMPTY_EXTRA_FIELD_PLACEHOLDER); + } + + return result; + } + processDataCfg(dataCfg: S2DataConfig): S2DataConfig { const updatedDataCfg = super.processDataCfg(dataCfg); // 多指标数值挂行头,单指标挂列头 const valueInCols = size(updatedDataCfg?.fields?.values) <= 1; + const newMeta: Meta[] = this.processMeta(dataCfg.meta, i18n('数值')); + return { ...updatedDataCfg, + meta: newMeta, fields: { ...updatedDataCfg.fields, rows: [...(dataCfg.fields.rows || []), EXTRA_FIELD], diff --git a/packages/s2-react/src/components/sheets/strategy-sheet/custom-tooltip/data-cell-tooltip.tsx b/packages/s2-react/src/components/sheets/strategy-sheet/custom-tooltip/data-cell-tooltip.tsx index a99ed6596c..6ed0ca85ff 100644 --- a/packages/s2-react/src/components/sheets/strategy-sheet/custom-tooltip/data-cell-tooltip.tsx +++ b/packages/s2-react/src/components/sheets/strategy-sheet/custom-tooltip/data-cell-tooltip.tsx @@ -5,6 +5,7 @@ import { type MultiData, type SimpleData, type ViewMeta, + isUnchangedValue, } from '@antv/s2'; import cls from 'classnames'; import { first, get, isEmpty, isFunction, isNil } from 'lodash'; @@ -70,9 +71,12 @@ export const StrategySheetDataCellTooltip: React.FC<CustomTooltipProps> = ({ <div className={tooltipCls('divider')} /> <ul className={tooltipCls('derived-values')}> {(derivedValues as SimpleData[]).map((derivedValue, i) => { - const isNormal = isNil(derivedValue) || derivedValue === ''; - const isUp = isUpDataValue(derivedValue as string); - const isDown = !isNormal && !isUp; + const isUnchanged = isUnchangedValue( + derivedValue, + value as SimpleData, + ); + const isUp = !isUnchanged && isUpDataValue(derivedValue); + const isDown = !isUnchanged && !isUp; const originalDerivedValue = derivedOriginalValues[ i ] as SimpleData; @@ -88,7 +92,7 @@ export const StrategySheetDataCellTooltip: React.FC<CustomTooltipProps> = ({ 'derived-value-trend-down': isDown, })} > - {!isNormal && ( + {!isUnchanged && ( <span className="derived-value-trend-icon"></span> )} {renderDerivedValue?.( @@ -109,7 +113,12 @@ export const StrategySheetDataCellTooltip: React.FC<CustomTooltipProps> = ({ )} {rowDescription && ( <div className={tooltipCls('description')}> - {i18n('说明')}: {rowDescription} + <span className={tooltipCls('description-label')}> + {i18n('说明')} + </span> + <span className={tooltipCls('description-text')}> + {rowDescription} + </span> </div> )} </div> diff --git a/packages/s2-react/src/components/sheets/strategy-sheet/custom-tooltip/index.less b/packages/s2-react/src/components/sheets/strategy-sheet/custom-tooltip/index.less index c194294ca2..16e2b8db0a 100644 --- a/packages/s2-react/src/components/sheets/strategy-sheet/custom-tooltip/index.less +++ b/packages/s2-react/src/components/sheets/strategy-sheet/custom-tooltip/index.less @@ -19,6 +19,10 @@ margin: 10px -12px; } + & &-description { + overflow-wrap: break-word; + } + // tooltip for row cell // ------------------------------------------------------ &&-row &-value { diff --git a/packages/s2-react/src/components/sheets/strategy-sheet/custom-tooltip/row-cell-tooltip.tsx b/packages/s2-react/src/components/sheets/strategy-sheet/custom-tooltip/row-cell-tooltip.tsx index b955721510..d395f9a451 100644 --- a/packages/s2-react/src/components/sheets/strategy-sheet/custom-tooltip/row-cell-tooltip.tsx +++ b/packages/s2-react/src/components/sheets/strategy-sheet/custom-tooltip/row-cell-tooltip.tsx @@ -22,7 +22,10 @@ export const StrategySheetRowCellTooltip: React.FC<CustomTooltipProps> = ({ <div className={tooltipCls('value')}>{rowName}</div> {description && ( <div className={tooltipCls('description')}> - {i18n('说明')}: {description} + <span className={tooltipCls('description-label')}> + {i18n('说明')} + </span> + <span className={tooltipCls('description-text')}>{description}</span> </div> )} </div> diff --git a/packages/s2-react/src/components/sheets/strategy-sheet/index.tsx b/packages/s2-react/src/components/sheets/strategy-sheet/index.tsx index 6c95c25b37..8d21ffcad7 100644 --- a/packages/s2-react/src/components/sheets/strategy-sheet/index.tsx +++ b/packages/s2-react/src/components/sheets/strategy-sheet/index.tsx @@ -1,12 +1,7 @@ -import { - customMerge, - Node, - SpreadSheet, - type ColHeaderConfig, - type ViewMeta, -} from '@antv/s2'; +import { type ViewMeta } from '@antv/s2'; import { isEmpty, size } from 'lodash'; import React from 'react'; +import { customMerge, Node, SpreadSheet, type ColHeaderConfig } from '@antv/s2'; import { BaseSheet } from '../base-sheet'; import type { SheetComponentOptions, SheetComponentsProps } from '../interface'; import { StrategySheetColCell, StrategySheetDataCell } from './custom-cell'; diff --git a/packages/s2-react/src/components/switcher/dimension/index.tsx b/packages/s2-react/src/components/switcher/dimension/index.tsx index 6b074e0a35..4498732517 100644 --- a/packages/s2-react/src/components/switcher/dimension/index.tsx +++ b/packages/s2-react/src/components/switcher/dimension/index.tsx @@ -20,6 +20,25 @@ type DimensionProps = SwitcherField & crossRows?: boolean; }; +/** + * 解决 react 18 with strict mode 下的拖动报错问题 + * https://github.com/atlassian/react-beautiful-dnd/issues/2399#issuecomment-1175638194 + */ +const useAfterAnimationFrame = () => { + const [enabled, setEnabled] = React.useState(false); + + React.useEffect(() => { + const animation = requestAnimationFrame(() => setEnabled(true)); + + return () => { + cancelAnimationFrame(animation); + setEnabled(false); + }; + }, []); + + return enabled; +}; + export const Dimension: React.FC<DimensionProps> = React.memo((props) => { const { fieldType, @@ -37,6 +56,12 @@ export const Dimension: React.FC<DimensionProps> = React.memo((props) => { const [expandChildren, setExpandChildren] = React.useState(true); + const enabled = useAfterAnimationFrame(); + + if (!enabled) { + return null; + } + const onUpdateExpand = (event: CheckboxChangeEvent) => { setExpandChildren(event.target.checked); }; diff --git a/packages/s2-react/src/components/tooltip/components/description.tsx b/packages/s2-react/src/components/tooltip/components/description.tsx index 4dd7b3d818..27a0fd7296 100644 --- a/packages/s2-react/src/components/tooltip/components/description.tsx +++ b/packages/s2-react/src/components/tooltip/components/description.tsx @@ -10,7 +10,8 @@ export const TooltipDescription: React.FC<TooltipDescriptionProps> = React.memo( <> {description && ( <div className={`${TOOLTIP_PREFIX_CLS}-description`}> - {i18n('说明')}:{description} + {i18n('说明')} + {description} </div> )} </> diff --git a/packages/s2-react/src/components/tooltip/components/head-info.tsx b/packages/s2-react/src/components/tooltip/components/head-info.tsx index 316492ab4e..92818f9a4a 100644 --- a/packages/s2-react/src/components/tooltip/components/head-info.tsx +++ b/packages/s2-react/src/components/tooltip/components/head-info.tsx @@ -3,6 +3,7 @@ import { type TooltipDetailListItem, type TooltipHeadInfo, TOOLTIP_PREFIX_CLS, + i18n, } from '@antv/s2'; export const TooltipHead: React.FC<TooltipHeadInfo> = React.memo((props) => { @@ -10,9 +11,9 @@ export const TooltipHead: React.FC<TooltipHeadInfo> = React.memo((props) => { return ( <div className={`${TOOLTIP_PREFIX_CLS}-head-info-list`}> - {cols.map((item: TooltipDetailListItem) => item.value)?.join(` / `)} - {cols.length > 0 && rows.length > 0 && ','} - {rows.map((item: TooltipDetailListItem) => item.value)?.join(` / `)} + {cols.map((item: TooltipDetailListItem) => item.value)?.join('/')} + {cols.length > 0 && rows.length > 0 && i18n(',')} + {rows.map((item: TooltipDetailListItem) => item.value)?.join('/')} </div> ); }); diff --git a/packages/s2-react/src/components/tooltip/components/summary.tsx b/packages/s2-react/src/components/tooltip/components/summary.tsx index 9f8aae7eee..9afc88b258 100644 --- a/packages/s2-react/src/components/tooltip/components/summary.tsx +++ b/packages/s2-react/src/components/tooltip/components/summary.tsx @@ -46,7 +46,8 @@ export const TooltipSummary: React.FC<TooltipSummaryProps> = React.memo( > {name ? ( <span className={`${TOOLTIP_PREFIX_CLS}-summary-key`}> - {name} ({i18n('总和')}) + {name} + {i18n('总和')} </span> ) : ( <span className={`${TOOLTIP_PREFIX_CLS}-summary-key`}> diff --git a/packages/s2-react/src/hooks/useEvents.ts b/packages/s2-react/src/hooks/useEvents.ts index efa5e8056a..185a44ae8d 100644 --- a/packages/s2-react/src/hooks/useEvents.ts +++ b/packages/s2-react/src/hooks/useEvents.ts @@ -1,10 +1,10 @@ /* eslint-disable max-lines-per-function */ import { - type EmitterType, - getBaseCellData, GEvent, S2Event, SpreadSheet, + getBaseCellData, + type EmitterType, type TargetCellInfo, } from '@antv/s2'; import React from 'react'; diff --git a/packages/s2-shared/__tests__/utils/options-spec.ts b/packages/s2-shared/__tests__/utils/options-spec.ts index 3eb9076492..4b3315e387 100644 --- a/packages/s2-shared/__tests__/utils/options-spec.ts +++ b/packages/s2-shared/__tests__/utils/options-spec.ts @@ -1,7 +1,7 @@ import { DEFAULT_MOBILE_OPTIONS, DeviceType, - LayoutWidthTypes, + LayoutWidthType, type S2Options, } from '@antv/s2'; import { pick } from 'lodash'; @@ -166,9 +166,7 @@ describe('Options Tests', () => { "rangeSelection": false, } `); - expect(options.style?.layoutWidthType).toEqual( - LayoutWidthTypes.ColAdaptive, - ); + expect(options.style?.layoutWidthType).toEqual(LayoutWidthType.ColAdaptive); expect(firstLevelOptions).toEqual({ height: 380, device: DeviceType.MOBILE, diff --git a/packages/s2-shared/src/constant/i18n/en_US.ts b/packages/s2-shared/src/constant/i18n/en_US.ts index 5137f8ca23..91e3386c9d 100644 --- a/packages/s2-shared/src/constant/i18n/en_US.ts +++ b/packages/s2-shared/src/constant/i18n/en_US.ts @@ -30,7 +30,7 @@ export const EN_US: Record<string, string> = { '按以下规则进行排序(优先级由低到高)': 'Order according to the following rules (from low to high priority)', 按: 'By', - 说明: 'Description', + 说明: 'Description: ', // export 复制原始数据: 'Copy raw data', diff --git a/packages/s2-shared/src/constant/i18n/zh_CN.ts b/packages/s2-shared/src/constant/i18n/zh_CN.ts index 4763cb138b..681ae4bd17 100644 --- a/packages/s2-shared/src/constant/i18n/zh_CN.ts +++ b/packages/s2-shared/src/constant/i18n/zh_CN.ts @@ -30,7 +30,7 @@ export const ZH_CN: Record<string, string> = { '按以下规则进行排序(优先级由低到高)': '按以下规则进行排序(优先级由低到高)', 按: '按', - 说明: '说明', + 说明: '说明:', // export 复制原始数据: '复制原始数据', diff --git a/packages/s2-shared/src/interface.ts b/packages/s2-shared/src/interface.ts index a5c343847d..3704945285 100644 --- a/packages/s2-shared/src/interface.ts +++ b/packages/s2-shared/src/interface.ts @@ -2,6 +2,7 @@ import type { BaseTooltipOperatorMenuOptions, CellScrollPosition, ColCell, + CopyableList, CornerCell, DataCell, GEvent, @@ -27,6 +28,7 @@ import type { ThemeCfg, TooltipContentType, TooltipOperatorOptions, + ViewMeta, ViewMetaData, } from '@antv/s2'; @@ -56,11 +58,27 @@ export type LayoutPaginationParams = { total: number; current: number; }; +type _ShowPagination = + | boolean + | { + onShowSizeChange?: (pageSize: number) => void; + onChange?: (current: number) => void; + }; + +type ShowPagination<OverrideShowPagination, Options> = + OverrideShowPagination extends true + ? Options extends { + pagination?: { onShowSizeChange?: unknown; onChange?: unknown }; + } + ? boolean | Options['pagination'] + : _ShowPagination + : _ShowPagination; export interface BaseSheetComponentProps< PartialDrillDown = PartDrillDown, Header = unknown, Options = S2Options<TooltipContentType, Pagination>, + OverrideShowPagination = false, > { sheetType?: SheetType; spreadsheet?: ( @@ -73,12 +91,7 @@ export interface BaseSheetComponentProps< loading?: boolean; partDrillDown?: PartialDrillDown; adaptive?: Adaptive; - showPagination?: - | boolean - | { - onShowSizeChange?: (pageSize: number) => void; - onChange?: (current: number) => void; - }; + showPagination?: ShowPagination<OverrideShowPagination, Options>; themeCfg?: ThemeCfg; header?: Header; /** 底表 render callback */ @@ -124,6 +137,7 @@ export interface BaseSheetComponentProps< onDataCellBrushSelection?: (brushRangeDataCells: DataCell[]) => void; onDataCellSelectMove?: (metaList: ViewMetaData[]) => void; onDataCellRender?: (cell: DataCell) => void; + onDataCellEditEnd?: (meta: ViewMeta) => void; // ============== Corner Cell ==================== onCornerCellHover?: (data: TargetCellInfo) => void; @@ -192,7 +206,7 @@ export interface BaseSheetComponentProps< // ============== Global ==================== onKeyBoardDown?: (event: KeyboardEvent) => void; onKeyBoardUp?: (event: KeyboardEvent) => void; - onCopied?: (copyData: string) => void; + onCopied?: (data: CopyableList) => void; onActionIconHover?: (event: GEvent) => void; onActionIconHoverOff?: (event: GEvent) => void; onActionIconClick?: (event: GEvent) => void; diff --git a/packages/s2-shared/src/utils/resize.ts b/packages/s2-shared/src/utils/resize.ts index 1f43f118ac..a6cb36f85d 100644 --- a/packages/s2-shared/src/utils/resize.ts +++ b/packages/s2-shared/src/utils/resize.ts @@ -1,4 +1,5 @@ import { debounce, isBoolean } from 'lodash'; +import { floor } from '@antv/s2'; import { RESIZE_RENDER_DELAY } from '../constant/resize'; import type { Adaptive, ResizeEffectParams } from '../interface'; @@ -47,11 +48,11 @@ export const createResizeObserver = (params: ResizeEffectParams) => { container; const width = adaptiveWidth - ? Math.floor(containerWidth ?? s2.options.width) + ? floor(containerWidth ?? s2.options.width) : s2.options.width; // 去除 header 和 page 后才是 sheet 真正的高度 const height = adaptiveHeight - ? Math.floor(containerHeight ?? s2.options.height) + ? floor(containerHeight ?? s2.options.height) : s2.options.height; if (!adaptiveWidth && !adaptiveHeight) { diff --git a/packages/s2-vue/CHANGELOG.md b/packages/s2-vue/CHANGELOG.md index 5f3ae56d18..f479ba58e9 100644 --- a/packages/s2-vue/CHANGELOG.md +++ b/packages/s2-vue/CHANGELOG.md @@ -1,18 +1,15 @@ # [@antv/s2-vue-v2.0.0-next.9](https://github.com/antvis/S2/compare/@antv/s2-vue-v2.0.0-next.8...@antv/s2-vue-v2.0.0-next.9) (2023-12-12) - ### Features * 支持在单元格内渲染 G2 图表 ([#2437](https://github.com/antvis/S2/issues/2437)) ([497f941](https://github.com/antvis/S2/commit/497f9414b89fce01b60db9b6c2eb4292ffe69c1d)) # [@antv/s2-vue-v2.0.0-next.8](https://github.com/antvis/S2/compare/@antv/s2-vue-v2.0.0-next.7...@antv/s2-vue-v2.0.0-next.8) (2023-11-22) - ### Features * 支持 antd v5 ([#2413](https://github.com/antvis/S2/issues/2413)) ([299c7bf](https://github.com/antvis/S2/commit/299c7bfe2e86838153273c92dd6d2b72917cfdea)) -* 支持 React 18 (兼容 React 16/17) ([#2373](https://github.com/antvis/S2/issues/2373)) ([25ce9b0](https://github.com/antvis/S2/commit/25ce9b0ccc3e609d8add09b3209f6f981dc1dc4e)) - +* 支持 React 18 (兼容 React 16/17) ([#2373](https://github.com/antvis/S2/issues/2373)) ([25ce9b0](https://github.com/antvis/S2/commit/25ce9b0ccc3e609d8add09b3209f6f981dc1dc4e)) ### BREAKING CHANGES @@ -59,10 +56,9 @@ # [@antv/s2-vue-v2.0.0-next.6](https://github.com/antvis/S2/compare/@antv/s2-vue-v2.0.0-next.5...@antv/s2-vue-v2.0.0-next.6) (2023-04-23) - ### Bug Fixes -* 修复 vue playground 启动失败 ([#2104](https://github.com/antvis/S2/issues/2104)) ([a62974d](https://github.com/antvis/S2/commit/a62974d88e1e3f6d3dcc16daf936a95236e9d6d3)) +* 修复 vue playground 启动失败 ([#2104](https://github.com/antvis/S2/issues/2104)) ([a62974d](https://github.com/antvis/S2/commit/a62974d88e1e3f6d3dcc16daf936a95236e9d6d3)) # [@antv/s2-vue-v2.0.0-next.5](https://github.com/antvis/S2/compare/@antv/s2-vue-v2.0.0-next.4...@antv/s2-vue-v2.0.0-next.5) (2023-02-22) diff --git a/packages/s2-vue/package.json b/packages/s2-vue/package.json index 61deef090f..d5254c15e7 100644 --- a/packages/s2-vue/package.json +++ b/packages/s2-vue/package.json @@ -45,9 +45,10 @@ "build:umd": "cross-env FORMAT=umd vite build", "build:analysis": "cross-env FORMAT=es ANALYSIS=true vite build", "build:dts": "run-s dts:*", + "build:size-limit": "size-limit", + "build:size-limit-json": "yarn build:size-limit --json", "dts:build": "vue-tsc -p tsconfig.declaration.json", "dts:extract": "cross-env LIB=s2-vue node ../../scripts/dts.js", - "bundle:size": "bundlesize", "test": "jest --passWithNoTests", "test:coverage": "pnpm test -- --coverage", "test:ci": "pnpm test -- --maxWorkers=3", @@ -75,14 +76,15 @@ "vue": "^3.3.4", "vue-tsc": "^1.8.19" }, - "bundlesize": [ + "size-limit": [ { "path": "./dist/index.min.js", - "maxSize": "650 kB" + "import": "{ createComponent }", + "limit": "20 kB" }, { "path": "./dist/style.min.css", - "maxSize": "205 kB" + "limit": "5 kB" } ], "publishConfig": { diff --git a/packages/s2-vue/playground/App.vue b/packages/s2-vue/playground/App.vue index 25f913d111..dcd6ffd98f 100644 --- a/packages/s2-vue/playground/App.vue +++ b/packages/s2-vue/playground/App.vue @@ -1,12 +1,6 @@ <script lang="ts"> /* eslint-disable no-console */ -import { - CellType, - type Data, - type RawData, - type S2DataConfig, - type S2Options, -} from '@antv/s2'; +import { CellType, RawData, type S2DataConfig, type S2Options } from '@antv/s2'; import type { PartDrillDown, PartDrillDownInfo, @@ -540,28 +534,20 @@ const partDrillDown: PartDrillDown = { fetchData: (meta, drillFields) => new Promise<PartDrillDownInfo>((resolve) => { // 弹窗 -> 选择 -> 请求数据 - const preDrillDownfield = - meta.spreadsheet.store.get('drillDownNode')?.field; const dataSet = meta.spreadsheet.dataSet; const field = drillFields[0]; - const rowData = ( - dataSet.getCellMultiData({ - query: meta?.query!, - drillDownFields: [preDrillDownfield], - }) as Data[] - ).filter( - (item) => - item!['sub_type'] && item!['type'] && item![preDrillDownfield], - ); + const rowData = dataSet + .getCellMultiData({ query: meta.query! }) + .filter((item) => item?.['sub_type'] && item?.['type']) as RawData[]; console.log(rowData); const drillDownData: RawData[] = []; - forEach(rowData, (data: any) => { - const { number: num, sub_type: subType, type } = data; - const number0 = random(50, !!num); - const number1 = (num as number) - number0; - const dataItem0: RawData = { + forEach(rowData, (data) => { + const { number, sub_type: subType, type } = data; + const number0 = random(50, number as number); + const number1 = Number(number!) - number0; + const dataItem0 = { ...meta.query, number: number0, sub_type: subType, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 499653def3..bc757a06f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,6 +47,9 @@ importers: '@semantic-release/changelog': specifier: ^6.0.3 version: 6.0.3(semantic-release@19.0.5) + '@semantic-release/exec': + specifier: ^6.0.3 + version: 6.0.3(semantic-release@19.0.5) '@semantic-release/git': specifier: ^10.0.1 version: 10.0.1(semantic-release@19.0.5) @@ -227,6 +230,9 @@ importers: semver: specifier: ^7.5.4 version: 7.5.4 + size-limit: + specifier: ^11.0.0 + version: 11.0.1 stylelint: specifier: ^15.11.0 version: 15.11.0(typescript@5.2.2) @@ -390,7 +396,7 @@ importers: version: 18.2.0(react@18.2.0) vite-plugin-svgr: specifier: ^2.2.2 - version: 2.4.0(rollup@4.1.5)(vite@4.5.0) + version: 2.4.0 packages/s2-shared: devDependencies: @@ -436,16 +442,16 @@ importers: version: 3.3.4 vue-tsc: specifier: ^1.8.19 - version: 1.8.19(typescript@5.2.2) + version: 1.8.19 s2-site: dependencies: '@ant-design/icons': specifier: ^5.2.6 - version: 5.2.6(react-dom@18.2.0)(react@18.2.0) + version: 5.2.6 '@antv/dumi-theme-antv': specifier: ^0.4.4 - version: 0.4.4(@babel/core@7.23.2)(dumi@2.2.15) + version: 0.4.4(dumi@2.2.15) '@antv/g-canvas': specifier: ^1.11.25 version: 1.11.25 @@ -466,13 +472,13 @@ importers: version: link:../packages/s2-react antd: specifier: ^5.12.2 - version: 5.12.2(react-dom@18.2.0)(react@18.2.0) + version: 5.12.2 copy-to-clipboard: specifier: ^3.3.3 version: 3.3.3 dumi: specifier: ^2.2.15 - version: 2.2.15(@babel/core@7.23.2)(@types/node@20.8.9)(eslint@8.52.0)(jest@26.6.3)(postcss-less@6.0.0)(prettier@3.0.3)(rollup@4.1.5)(stylelint@15.11.0)(typescript@5.2.2) + version: 2.2.15 gh-pages: specifier: ^6.1.0 version: 6.1.0 @@ -481,7 +487,7 @@ importers: version: 4.17.21 react-color: specifier: ^2.19.3 - version: 2.19.3(react@18.2.0) + version: 2.19.3 devDependencies: '@google-cloud/translate': specifier: ^7.0.3 @@ -534,6 +540,7 @@ packages: /@aashutoshrathi/word-wrap@1.2.6: resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} engines: {node: '>=0.10.0'} + dev: true /@algolia/autocomplete-core@1.7.2: resolution: {integrity: sha512-eclwUDC6qfApNnEfu1uWcL/rudQsn59tjEoUYZYE2JSXZrHLRjBUGMxiCoknobU2Pva8ejb0eRxpIYDtVVqdsw==} @@ -662,6 +669,21 @@ packages: dependencies: '@ctrl/tinycolor': 3.6.1 + /@ant-design/cssinjs@1.18.1: + resolution: {integrity: sha512-1JURAPrsjK1GwpqByTq3bJ7nF7lbMKDZpehqeR2n8/IR5O58/W1U4VcOeaw5ZyTHri3tEMcom7dyP2tvxpW54g==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + dependencies: + '@babel/runtime': 7.23.5 + '@emotion/hash': 0.8.0 + '@emotion/unitless': 0.7.5 + classnames: 2.3.2 + csstype: 3.1.2 + rc-util: 5.38.1 + stylis: 4.1.3 + dev: false + /@ant-design/cssinjs@1.18.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-1JURAPrsjK1GwpqByTq3bJ7nF7lbMKDZpehqeR2n8/IR5O58/W1U4VcOeaw5ZyTHri3tEMcom7dyP2tvxpW54g==} peerDependencies: @@ -677,6 +699,7 @@ packages: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) stylis: 4.1.3 + dev: true /@ant-design/icons-svg@4.3.1: resolution: {integrity: sha512-4QBZg8ccyC6LPIRii7A0bZUk3+lEDCLnhB+FVsflGdcWPPmV+j3fire4AwwoqHV/BibgvBmR9ZIo4s867smv+g==} @@ -708,6 +731,20 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@ant-design/icons@5.2.6: + resolution: {integrity: sha512-4wn0WShF43TrggskBJPRqCD0fcHbzTYjnaoskdiJrVHg86yxoZ8ZUqsXvyn4WUqehRiFKnaclOhqk9w4Ui2KVw==} + engines: {node: '>=8'} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + dependencies: + '@ant-design/colors': 7.0.0 + '@ant-design/icons-svg': 4.3.1 + '@babel/runtime': 7.23.2 + classnames: 2.3.2 + rc-util: 5.38.1 + dev: false + /@ant-design/icons@5.2.6(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-4wn0WShF43TrggskBJPRqCD0fcHbzTYjnaoskdiJrVHg86yxoZ8ZUqsXvyn4WUqehRiFKnaclOhqk9w4Ui2KVw==} engines: {node: '>=8'} @@ -722,6 +759,19 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true + + /@ant-design/react-slick@1.0.2: + resolution: {integrity: sha512-Wj8onxL/T8KQLFFiCA4t8eIRGpRR+UPgOdac2sYzonv+i0n3kXHmvHLLiOYL655DQx2Umii9Y9nNgL7ssu5haQ==} + peerDependencies: + react: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + json2mq: 0.2.0 + resize-observer-polyfill: 1.5.1 + throttle-debounce: 5.0.0 + dev: false /@ant-design/react-slick@1.0.2(react@18.2.0): resolution: {integrity: sha512-Wj8onxL/T8KQLFFiCA4t8eIRGpRR+UPgOdac2sYzonv+i0n3kXHmvHLLiOYL655DQx2Umii9Y9nNgL7ssu5haQ==} @@ -761,13 +811,13 @@ packages: '@antv/util': 2.0.17 gl-matrix: 3.4.3 - /@antv/dumi-theme-antv@0.4.4(@babel/core@7.23.2)(dumi@2.2.15): + /@antv/dumi-theme-antv@0.4.4(dumi@2.2.15): resolution: {integrity: sha512-/5eB2bn9pZZmORcYFmQQ8xjgD22/W879E3XfHKvgYDNkYlxNUMmOknxiNbV5Pdf0s8oUgLEerKnSWqdkQc6Tvg==} peerDependencies: dumi: ^2.0.0 dependencies: '@ant-design/icons': 4.8.1(react-dom@18.2.0)(react@18.2.0) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.23.2) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6 '@babel/standalone': 7.20.12 '@docsearch/css': 3.3.1 '@docsearch/react': 3.3.1(react-dom@18.2.0)(react@18.2.0) @@ -778,7 +828,7 @@ packages: codesandbox: 2.2.3 d3-dsv: 3.0.1 docsearch.js: 2.6.3 - dumi: 2.2.15(@babel/core@7.23.2)(@types/node@20.8.9)(eslint@8.52.0)(jest@26.6.3)(postcss-less@6.0.0)(prettier@3.0.3)(rollup@4.1.5)(stylelint@15.11.0)(typescript@5.2.2) + dumi: 2.2.15 front-matter: 4.0.2 fs-extra: 10.1.0 glob: 8.1.0 @@ -1050,7 +1100,7 @@ packages: transitivePeerDependencies: - supports-color - /@babel/eslint-parser@7.22.15(@babel/core@7.23.2)(eslint@8.52.0): + /@babel/eslint-parser@7.22.15(@babel/core@7.23.2): resolution: {integrity: sha512-yc8OOBIQk1EcRrpizuARSQS0TWAcOMpEJ1aafhNznaeYkeL+OhqnDObGFylB8ka8VFF/sZc+S4RzHyO+3LjQxg==} engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} peerDependencies: @@ -1059,7 +1109,6 @@ packages: dependencies: '@babel/core': 7.23.2 '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 - eslint: 8.52.0 eslint-visitor-keys: 2.1.0 semver: 6.3.1 dev: false @@ -1116,13 +1165,12 @@ packages: semver: 6.3.1 dev: true - /@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.23.2): + /@babel/helper-create-regexp-features-plugin@7.22.15: resolution: {integrity: sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.2 '@babel/helper-annotate-as-pure': 7.22.5 regexpu-core: 5.3.2 semver: 6.3.1 @@ -1158,6 +1206,19 @@ packages: dependencies: '@babel/types': 7.23.0 + /@babel/helper-module-transforms@7.23.0: + resolution: {integrity: sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-simple-access': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/helper-validator-identifier': 7.22.20 + dev: false + /@babel/helper-module-transforms@7.23.0(@babel/core@7.23.2): resolution: {integrity: sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==} engines: {node: '>=6.9.0'} @@ -1263,6 +1324,14 @@ packages: dependencies: '@babel/types': 7.23.0 + /@babel/plugin-syntax-async-generators@7.8.4: + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/helper-plugin-utils': 7.22.5 + dev: false + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.2): resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: @@ -1270,6 +1339,15 @@ packages: dependencies: '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-bigint@7.8.3: + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.2): resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} @@ -1278,6 +1356,15 @@ packages: dependencies: '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-class-properties@7.12.13: + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.2): resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} @@ -1286,6 +1373,15 @@ packages: dependencies: '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-import-meta@7.10.4: + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.2): resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} @@ -1294,6 +1390,15 @@ packages: dependencies: '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-json-strings@7.8.3: + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.2): resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} @@ -1302,6 +1407,7 @@ packages: dependencies: '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 + dev: true /@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.23.2): resolution: {integrity: sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==} @@ -1313,6 +1419,14 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-logical-assignment-operators@7.10.4: + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/helper-plugin-utils': 7.22.5 + dev: false + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.2): resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: @@ -1320,6 +1434,15 @@ packages: dependencies: '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3: + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.2): resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} @@ -1328,6 +1451,15 @@ packages: dependencies: '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-numeric-separator@7.10.4: + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.2): resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} @@ -1336,6 +1468,15 @@ packages: dependencies: '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-object-rest-spread@7.8.3: + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.2): resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} @@ -1344,6 +1485,15 @@ packages: dependencies: '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-optional-catch-binding@7.8.3: + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.2): resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} @@ -1352,6 +1502,15 @@ packages: dependencies: '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-optional-chaining@7.8.3: + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.2): resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} @@ -1360,6 +1519,16 @@ packages: dependencies: '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-top-level-await@7.14.5: + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.2): resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} @@ -1369,6 +1538,7 @@ packages: dependencies: '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 + dev: true /@babel/plugin-syntax-typescript@7.22.5(@babel/core@7.23.2): resolution: {integrity: sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==} @@ -1380,14 +1550,13 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.23.2): + /@babel/plugin-syntax-unicode-sets-regex@7.18.6: resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.2 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.2) + '@babel/helper-create-regexp-features-plugin': 7.22.15 '@babel/helper-plugin-utils': 7.22.5 dev: false @@ -1403,6 +1572,17 @@ packages: '@babel/helper-simple-access': 7.22.5 dev: true + /@babel/plugin-transform-modules-commonjs@7.23.0: + resolution: {integrity: sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/helper-module-transforms': 7.23.0 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-simple-access': 7.22.5 + dev: false + /@babel/plugin-transform-modules-commonjs@7.23.0(@babel/core@7.23.2): resolution: {integrity: sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==} engines: {node: '>=6.9.0'} @@ -1413,6 +1593,7 @@ packages: '@babel/helper-module-transforms': 7.23.0(@babel/core@7.23.2) '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-simple-access': 7.22.5 + dev: true /@babel/plugin-transform-react-jsx-self@7.22.5(@babel/core@7.23.2): resolution: {integrity: sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==} @@ -1526,6 +1707,7 @@ packages: /@bcoe/v8-coverage@0.2.3: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + dev: true /@bloomberg/record-tuple-polyfill@0.0.4: resolution: {integrity: sha512-h0OYmPR3A5Dfbetra/GzxBAzQk8sH7LhRkRUTdagX6nrtlUgJGYCTv4bBK33jsTQw9HDd8PE2x1Ma+iRKEDUsw==} @@ -1538,6 +1720,7 @@ packages: dependencies: exec-sh: 0.3.6 minimist: 1.2.8 + dev: true /@colors/colors@1.5.0: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} @@ -1717,10 +1900,12 @@ packages: '@csstools/css-tokenizer': ^2.2.1 dependencies: '@csstools/css-tokenizer': 2.2.1 + dev: true /@csstools/css-tokenizer@2.2.1: resolution: {integrity: sha512-Zmsf2f/CaEPWEVgw29odOj+WEVoiJy9s9NOv5GgNY9mZ1CZ7394By6wONrONrTsnNDv6F9hR02nvFihrGVGHBg==} engines: {node: ^14 || ^16 || >=18} + dev: true /@csstools/media-query-list-parser@2.1.5(@csstools/css-parser-algorithms@2.3.2)(@csstools/css-tokenizer@2.2.1): resolution: {integrity: sha512-IxVBdYzR8pYe89JiyXQuYk4aVVoCPhMJkz6ElRwlVysjwURTsTk/bmY/z4FfeRE+CRBMlykPwXEVUg8lThv7AQ==} @@ -1731,6 +1916,7 @@ packages: dependencies: '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) '@csstools/css-tokenizer': 2.2.1 + dev: true /@csstools/postcss-color-function@1.1.1(postcss@8.4.31): resolution: {integrity: sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==} @@ -1853,6 +2039,7 @@ packages: postcss-selector-parser: ^6.0.13 dependencies: postcss-selector-parser: 6.0.13 + dev: true /@ctrl/tinycolor@3.5.0: resolution: {integrity: sha512-tlJpwF40DEQcfR/QF+wNMVyGMaO9FQp6Z1Wahj4Gk3CJQYHwA2xVG7iKDFdW6zuxZY9XWOpGcfNCTsX4McOsOg==} @@ -2297,6 +2484,15 @@ packages: requiresBuild: true optional: true + /@eslint-community/eslint-utils@4.4.0: + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint-visitor-keys: 3.4.3 + dev: false + /@eslint-community/eslint-utils@4.4.0(eslint@8.52.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2305,6 +2501,7 @@ packages: dependencies: eslint: 8.52.0 eslint-visitor-keys: 3.4.3 + dev: true /@eslint-community/regexpp@4.9.1: resolution: {integrity: sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==} @@ -2325,10 +2522,12 @@ packages: strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color + dev: true /@eslint/js@8.52.0: resolution: {integrity: sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true /@floating-ui/core@0.6.2: resolution: {integrity: sha512-jktYRmZwmau63adUG3GKOAVCofBXkk55S/zQ94XOorAHhwqFIOFAy1rSp2N0Wp6/tGbe9V3u/ExlGZypyY17rg==} @@ -2418,7 +2617,7 @@ packages: tslib: 2.6.2 dev: false - /@formatjs/intl@2.9.9(typescript@5.2.2): + /@formatjs/intl@2.9.9: resolution: {integrity: sha512-JI3CNgL2Zdg5lv9ncT2sYKqbAj2RGrCbdzaCckIxMPxn4QuHuOVvYUGmBAXVusBmfG/0sxLmMrnwnBioz+QKdA==} peerDependencies: typescript: '5' @@ -2433,7 +2632,6 @@ packages: '@formatjs/intl-listformat': 7.5.3 intl-messageformat: 10.5.8 tslib: 2.6.2 - typescript: 5.2.2 dev: false /@google-cloud/common@4.0.3: @@ -2508,13 +2706,16 @@ packages: minimatch: 3.1.2 transitivePeerDependencies: - supports-color + dev: true /@humanwhocodes/module-importer@1.0.1: resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} + dev: true /@humanwhocodes/object-schema@2.0.1: resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} + dev: true /@iconify/types@2.0.0: resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -2533,12 +2734,19 @@ packages: - supports-color dev: false + /@icons/material@0.2.4: + resolution: {integrity: sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==} + peerDependencies: + react: '*' + dev: false + /@icons/material@0.2.4(react@18.2.0): resolution: {integrity: sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==} peerDependencies: react: '*' dependencies: react: 18.2.0 + dev: true /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} @@ -2585,6 +2793,7 @@ packages: jest-message-util: 26.6.2 jest-util: 26.6.2 slash: 3.0.0 + dev: true /@jest/core@26.6.3: resolution: {integrity: sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==} @@ -2624,6 +2833,7 @@ packages: - supports-color - ts-node - utf-8-validate + dev: true /@jest/create-cache-key-function@27.5.1: resolution: {integrity: sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ==} @@ -2652,6 +2862,7 @@ packages: '@jest/types': 26.6.2 '@types/node': 20.8.9 jest-mock: 26.6.2 + dev: true /@jest/fake-timers@24.9.0: resolution: {integrity: sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==} @@ -2674,6 +2885,7 @@ packages: jest-message-util: 26.6.2 jest-mock: 26.6.2 jest-util: 26.6.2 + dev: true /@jest/globals@26.6.2: resolution: {integrity: sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==} @@ -2682,6 +2894,7 @@ packages: '@jest/environment': 26.6.2 '@jest/types': 26.6.2 expect: 26.6.2 + dev: true /@jest/reporters@26.6.2: resolution: {integrity: sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==} @@ -2715,6 +2928,7 @@ packages: node-notifier: 8.0.2 transitivePeerDependencies: - supports-color + dev: true /@jest/schemas@29.6.3: resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} @@ -2739,6 +2953,7 @@ packages: callsites: 3.1.0 graceful-fs: 4.2.10 source-map: 0.6.1 + dev: true /@jest/test-result@24.9.0: resolution: {integrity: sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==} @@ -2757,6 +2972,7 @@ packages: '@jest/types': 26.6.2 '@types/istanbul-lib-coverage': 2.0.4 collect-v8-coverage: 1.0.1 + dev: true /@jest/test-sequencer@24.9.0: resolution: {integrity: sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A==} @@ -2787,6 +3003,7 @@ packages: - supports-color - ts-node - utf-8-validate + dev: true /@jest/transform@24.9.0: resolution: {integrity: sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==} @@ -2833,6 +3050,7 @@ packages: write-file-atomic: 3.0.3 transitivePeerDependencies: - supports-color + dev: true /@jest/transform@29.7.0: resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} @@ -2875,6 +3093,7 @@ packages: '@types/node': 20.8.9 '@types/yargs': 15.0.15 chalk: 4.1.2 + dev: true /@jest/types@27.5.1: resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==} @@ -2948,6 +3167,17 @@ packages: dependencies: call-bind: 1.0.2 + /@loadable/component@5.15.2: + resolution: {integrity: sha512-ryFAZOX5P2vFkUdzaAtTG88IGnr9qxSdvLRvJySXcUA4B4xVWurUNADu3AnKPksxOZajljqTrDEDcYjeL4lvLw==} + engines: {node: '>=8'} + peerDependencies: + react: '>=16.3.0' + dependencies: + '@babel/runtime': 7.23.5 + hoist-non-react-statics: 3.3.2 + react-is: 16.13.1 + dev: false + /@loadable/component@5.15.2(react@18.1.0): resolution: {integrity: sha512-ryFAZOX5P2vFkUdzaAtTG88IGnr9qxSdvLRvJySXcUA4B4xVWurUNADu3AnKPksxOZajljqTrDEDcYjeL4lvLw==} engines: {node: '>=8'} @@ -3259,6 +3489,18 @@ packages: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} dev: true + /@rc-component/color-picker@1.4.1: + resolution: {integrity: sha512-vh5EWqnsayZa/JwUznqDaPJz39jznx/YDbyBuVJntv735tKXKwEUZZb2jYEldOg+NKWZwtALjGMrNeGBmqFoEw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + '@ctrl/tinycolor': 3.6.1 + classnames: 2.3.2 + rc-util: 5.38.1 + dev: false + /@rc-component/color-picker@1.4.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-vh5EWqnsayZa/JwUznqDaPJz39jznx/YDbyBuVJntv735tKXKwEUZZb2jYEldOg+NKWZwtALjGMrNeGBmqFoEw==} peerDependencies: @@ -3271,6 +3513,17 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true + + /@rc-component/context@1.4.0: + resolution: {integrity: sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + rc-util: 5.38.1 + dev: false /@rc-component/context@1.4.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==} @@ -3282,6 +3535,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /@rc-component/mini-decimal@1.1.0: resolution: {integrity: sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==} @@ -3289,6 +3543,18 @@ packages: dependencies: '@babel/runtime': 7.23.5 + /@rc-component/mutate-observer@1.1.0: + resolution: {integrity: sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-util: 5.38.1 + dev: false + /@rc-component/mutate-observer@1.1.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==} engines: {node: '>=8.x'} @@ -3301,8 +3567,9 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true - /@rc-component/portal@1.1.2(react-dom@18.2.0)(react@18.2.0): + /@rc-component/portal@1.1.2: resolution: {integrity: sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==} engines: {node: '>=8.x'} peerDependencies: @@ -3311,25 +3578,67 @@ packages: dependencies: '@babel/runtime': 7.23.5 classnames: 2.3.2 - rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + rc-util: 5.38.1 + dev: false - /@rc-component/tour@1.11.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-c9Lw3/oVinj5D64Rsp8aDLOXcgdViE+hq7bj0Qoo8fTuQEh9sSpUw5OZcum943JkjeIE4hLcc5FD4a5ANtMJ4w==} + /@rc-component/portal@1.1.2(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==} engines: {node: '>=8.x'} peerDependencies: react: '>=16.9.0' react-dom: '>=16.9.0' dependencies: '@babel/runtime': 7.23.5 - '@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0) - '@rc-component/trigger': 1.18.2(react-dom@18.2.0)(react@18.2.0) classnames: 2.3.2 rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + /@rc-component/tour@1.11.1: + resolution: {integrity: sha512-c9Lw3/oVinj5D64Rsp8aDLOXcgdViE+hq7bj0Qoo8fTuQEh9sSpUw5OZcum943JkjeIE4hLcc5FD4a5ANtMJ4w==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + '@rc-component/portal': 1.1.2 + '@rc-component/trigger': 1.18.2 + classnames: 2.3.2 + rc-util: 5.38.1 + dev: false + + /@rc-component/tour@1.11.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-c9Lw3/oVinj5D64Rsp8aDLOXcgdViE+hq7bj0Qoo8fTuQEh9sSpUw5OZcum943JkjeIE4hLcc5FD4a5ANtMJ4w==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + '@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0) + '@rc-component/trigger': 1.18.2(react-dom@18.2.0)(react@18.2.0) + classnames: 2.3.2 + rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + + /@rc-component/trigger@1.18.2: + resolution: {integrity: sha512-jRLYgFgjLEPq3MvS87fIhcfuywFSRDaDrYw1FLku7Cm4esszvzTbA0JBsyacAyLrK9rF3TiHFcvoEDMzoD3CTA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + '@rc-component/portal': 1.1.2 + classnames: 2.3.2 + rc-motion: 2.9.0 + rc-resize-observer: 1.4.0 + rc-util: 5.38.1 + dev: false + /@rc-component/trigger@1.18.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-jRLYgFgjLEPq3MvS87fIhcfuywFSRDaDrYw1FLku7Cm4esszvzTbA0JBsyacAyLrK9rF3TiHFcvoEDMzoD3CTA==} engines: {node: '>=8.x'} @@ -3345,6 +3654,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /@remix-run/router@1.2.1: resolution: {integrity: sha512-XiY0IsyHR+DXYS5vBxpoBe/8veTeoRpMHP+vDosLZxL5bnpetzI0igkxkLZS235ldLzyfkxF+2divEwWHP3vMQ==} @@ -3457,6 +3767,20 @@ packages: picomatch: 2.3.1 dev: true + /@rollup/pluginutils@5.0.5: + resolution: {integrity: sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.0 + estree-walker: 2.0.2 + picomatch: 2.3.1 + dev: true + /@rollup/pluginutils@5.0.5(rollup@4.1.5): resolution: {integrity: sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==} engines: {node: '>=14.0.0'} @@ -3477,6 +3801,7 @@ packages: cpu: [arm] os: [android] requiresBuild: true + dev: true optional: true /@rollup/rollup-android-arm64@4.1.5: @@ -3484,6 +3809,7 @@ packages: cpu: [arm64] os: [android] requiresBuild: true + dev: true optional: true /@rollup/rollup-darwin-arm64@4.1.5: @@ -3491,6 +3817,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: true optional: true /@rollup/rollup-darwin-x64@4.1.5: @@ -3498,6 +3825,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-arm-gnueabihf@4.1.5: @@ -3505,6 +3833,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-arm64-gnu@4.1.5: @@ -3513,6 +3842,7 @@ packages: os: [linux] libc: [glibc] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-arm64-musl@4.1.5: @@ -3521,6 +3851,7 @@ packages: os: [linux] libc: [musl] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-x64-gnu@4.1.5: @@ -3529,6 +3860,7 @@ packages: os: [linux] libc: [glibc] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-x64-musl@4.1.5: @@ -3537,6 +3869,7 @@ packages: os: [linux] libc: [musl] requiresBuild: true + dev: true optional: true /@rollup/rollup-win32-arm64-msvc@4.1.5: @@ -3544,6 +3877,7 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true + dev: true optional: true /@rollup/rollup-win32-ia32-msvc@4.1.5: @@ -3551,6 +3885,7 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true + dev: true optional: true /@rollup/rollup-win32-x64-msvc@4.1.5: @@ -3558,6 +3893,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: true optional: true /@rushstack/eslint-patch@1.5.1: @@ -3641,6 +3977,23 @@ packages: engines: {node: '>=14.17'} dev: true + /@semantic-release/exec@6.0.3(semantic-release@19.0.5): + resolution: {integrity: sha512-bxAq8vLOw76aV89vxxICecEa8jfaWwYITw6X74zzlO0mc/Bgieqx9kBRz9z96pHectiTAtsCwsQcUyLYWnp3VQ==} + engines: {node: '>=14.17'} + peerDependencies: + semantic-release: '>=18.0.0' + dependencies: + '@semantic-release/error': 3.0.0 + aggregate-error: 3.1.0 + debug: 4.3.4 + execa: 5.1.1 + lodash: 4.17.21 + parse-json: 5.2.0 + semantic-release: 19.0.5 + transitivePeerDependencies: + - supports-color + dev: true + /@semantic-release/git@10.0.1(semantic-release@19.0.5): resolution: {integrity: sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==} engines: {node: '>=14.17'} @@ -3748,15 +4101,22 @@ packages: engines: {node: '>=6'} dev: true + /@sindresorhus/merge-streams@1.0.0: + resolution: {integrity: sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==} + engines: {node: '>=18'} + dev: true + /@sinonjs/commons@1.8.6: resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==} dependencies: type-detect: 4.0.8 + dev: true /@sinonjs/fake-timers@6.0.1: resolution: {integrity: sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==} dependencies: '@sinonjs/commons': 1.8.6 + dev: true /@sketch-hq/sketch-file-format-ts@6.5.0: resolution: {integrity: sha512-shaGl4ttFDpHjYBoMaZpciOtsi/lKvJ3VfcBYk6+PjjbFs6H5GxPAyhbiSqy3Vmx30aos284pd88QzD3rE6iag==} @@ -3797,7 +4157,7 @@ packages: dependencies: '@babel/core': 7.23.2 postcss: 8.4.31 - postcss-syntax: 0.36.2(postcss-less@6.0.0)(postcss@8.4.31) + postcss-syntax: 0.36.2(postcss@8.4.31) transitivePeerDependencies: - supports-color dev: false @@ -4257,6 +4617,7 @@ packages: /@tootallnate/once@1.1.2: resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} engines: {node: '>= 6'} + dev: true /@tootallnate/once@2.0.0: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} @@ -4467,6 +4828,7 @@ packages: /@types/minimist@1.2.2: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} + dev: true /@types/ms@0.7.31: resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} @@ -4492,6 +4854,7 @@ packages: /@types/normalize-package-data@2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} + dev: true /@types/parse-json@4.0.0: resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} @@ -4501,6 +4864,7 @@ packages: /@types/prettier@2.7.2: resolution: {integrity: sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==} + dev: true /@types/prop-types@15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} @@ -4590,6 +4954,7 @@ packages: /@types/stack-utils@2.0.1: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} + dev: true /@types/strip-bom@3.0.0: resolution: {integrity: sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==} @@ -4623,6 +4988,7 @@ packages: resolution: {integrity: sha512-IziEYMU9XoVj8hWg7k+UJrXALkGFjWJhn5QFEv9q4p+v40oZhSuC135M38st8XPjICL7Ey4TV64ferBGUoJhBg==} dependencies: '@types/yargs-parser': 21.0.0 + dev: true /@types/yargs@16.0.5: resolution: {integrity: sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==} @@ -4635,7 +5001,7 @@ packages: '@types/yargs-parser': 21.0.0 dev: false - /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.52.0)(typescript@5.2.2): + /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0): resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -4647,18 +5013,16 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.9.1 - '@typescript-eslint/parser': 5.62.0(eslint@8.52.0)(typescript@5.2.2) + '@typescript-eslint/parser': 5.62.0 '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/type-utils': 5.62.0(eslint@8.52.0)(typescript@5.2.2) - '@typescript-eslint/utils': 5.62.0(eslint@8.52.0)(typescript@5.2.2) + '@typescript-eslint/type-utils': 5.62.0 + '@typescript-eslint/utils': 5.62.0 debug: 4.3.4 - eslint: 8.52.0 graphemer: 1.4.0 ignore: 5.2.4 natural-compare-lite: 1.4.0 semver: 7.5.4 - tsutils: 3.21.0(typescript@5.2.2) - typescript: 5.2.2 + tsutils: 3.21.0 transitivePeerDependencies: - supports-color dev: false @@ -4692,7 +5056,7 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@5.62.0(eslint@8.52.0)(typescript@5.2.2): + /@typescript-eslint/parser@5.62.0: resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -4704,10 +5068,8 @@ packages: dependencies: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.2.2) + '@typescript-eslint/typescript-estree': 5.62.0 debug: 4.3.4 - eslint: 8.52.0 - typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: false @@ -4757,7 +5119,7 @@ packages: '@typescript-eslint/visitor-keys': 6.9.1 dev: true - /@typescript-eslint/type-utils@5.62.0(eslint@8.52.0)(typescript@5.2.2): + /@typescript-eslint/type-utils@5.62.0: resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -4767,12 +5129,10 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.2.2) - '@typescript-eslint/utils': 5.62.0(eslint@8.52.0)(typescript@5.2.2) + '@typescript-eslint/typescript-estree': 5.62.0 + '@typescript-eslint/utils': 5.62.0 debug: 4.3.4 - eslint: 8.52.0 - tsutils: 3.21.0(typescript@5.2.2) - typescript: 5.2.2 + tsutils: 3.21.0 transitivePeerDependencies: - supports-color dev: false @@ -4833,7 +5193,7 @@ packages: - supports-color dev: true - /@typescript-eslint/typescript-estree@5.62.0(typescript@5.2.2): + /@typescript-eslint/typescript-estree@5.62.0: resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -4848,8 +5208,7 @@ packages: globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 - tsutils: 3.21.0(typescript@5.2.2) - typescript: 5.2.2 + tsutils: 3.21.0 transitivePeerDependencies: - supports-color dev: false @@ -4895,19 +5254,18 @@ packages: - typescript dev: true - /@typescript-eslint/utils@5.62.0(eslint@8.52.0)(typescript@5.2.2): + /@typescript-eslint/utils@5.62.0: resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.52.0) + '@eslint-community/eslint-utils': 4.4.0 '@types/json-schema': 7.0.13 '@types/semver': 7.5.3 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.2.2) - eslint: 8.52.0 + '@typescript-eslint/typescript-estree': 5.62.0 eslint-scope: 5.1.1 semver: 7.5.4 transitivePeerDependencies: @@ -5004,7 +5362,7 @@ packages: - supports-color dev: false - /@umijs/bundler-vite@4.0.88(@types/node@20.8.9)(postcss@8.4.31)(rollup@4.1.5)(sass@1.69.5): + /@umijs/bundler-vite@4.0.88(postcss@8.4.31)(sass@1.69.5): resolution: {integrity: sha512-qiToAgLwmPWz1t8l82qEPyFXlZvnRKWJW0WG6vuxQNikDMKCWhk18gZaWwMNhqQvAx/oZ37vFCmR8CDoNOMI/A==} hasBin: true dependencies: @@ -5015,9 +5373,9 @@ packages: core-js: 3.28.0 less: 4.1.3 postcss-preset-env: 7.5.0(postcss@8.4.31) - rollup-plugin-visualizer: 5.9.0(rollup@4.1.5) + rollup-plugin-visualizer: 5.9.0 systemjs: 6.14.2 - vite: 4.3.1(@types/node@20.8.9)(less@4.1.3)(sass@1.69.5) + vite: 4.3.1(less@4.1.3)(sass@1.69.5) transitivePeerDependencies: - '@types/node' - postcss @@ -5029,7 +5387,7 @@ packages: - terser dev: false - /@umijs/bundler-webpack@4.0.88(typescript@5.2.2): + /@umijs/bundler-webpack@4.0.88: resolution: {integrity: sha512-9ivKa6pREdgLhni9Zl+HhdqehizLOw+U8Md31l8pcQFq8KHrmlYj0X4iGNMaT7OVWJtZznY0wH4lfLNKSd+RXA==} hasBin: true dependencies: @@ -5046,7 +5404,7 @@ packages: cors: 2.8.5 css-loader: 6.7.1 es5-imcompatible-versions: 0.1.88 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.2.2) + fork-ts-checker-webpack-plugin: 8.0.0 jest-worker: 29.4.3 lightningcss: 1.19.0 node-libs-browser: 2.2.1 @@ -5190,21 +5548,21 @@ packages: query-string: 6.14.1 dev: false - /@umijs/lint@4.0.88(eslint@8.52.0)(jest@26.6.3)(postcss-less@6.0.0)(stylelint@15.11.0)(typescript@5.2.2): + /@umijs/lint@4.0.88: resolution: {integrity: sha512-vDLhfsA0zrAR2NFr/hna75kr1uTYSG032eCg89yBvC7fDn0jxIcXQXEHriZCsgge1puWw0bscnAWR8fx9rdmfA==} dependencies: '@babel/core': 7.23.2 - '@babel/eslint-parser': 7.22.15(@babel/core@7.23.2)(eslint@8.52.0) + '@babel/eslint-parser': 7.22.15(@babel/core@7.23.2) '@stylelint/postcss-css-in-js': 0.38.0(postcss-syntax@0.36.2)(postcss@8.4.31) - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.52.0)(typescript@5.2.2) - '@typescript-eslint/parser': 5.62.0(eslint@8.52.0)(typescript@5.2.2) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0) + '@typescript-eslint/parser': 5.62.0 '@umijs/babel-preset-umi': 4.0.88 - eslint-plugin-jest: 27.2.3(@typescript-eslint/eslint-plugin@5.62.0)(eslint@8.52.0)(jest@26.6.3)(typescript@5.2.2) - eslint-plugin-react: 7.33.2(eslint@8.52.0) - eslint-plugin-react-hooks: 4.6.0(eslint@8.52.0) + eslint-plugin-jest: 27.2.3(@typescript-eslint/eslint-plugin@5.62.0) + eslint-plugin-react: 7.33.2 + eslint-plugin-react-hooks: 4.6.0 postcss: 8.4.31 - postcss-syntax: 0.36.2(postcss-less@6.0.0)(postcss@8.4.31) - stylelint-config-standard: 25.0.0(stylelint@15.11.0) + postcss-syntax: 0.36.2(postcss@8.4.31) + stylelint-config-standard: 25.0.0 transitivePeerDependencies: - eslint - jest @@ -5236,7 +5594,7 @@ packages: tsx: 3.14.0 dev: false - /@umijs/preset-umi@4.0.88(@types/node@20.8.9)(rollup@4.1.5)(sass@1.69.5)(typescript@5.2.2): + /@umijs/preset-umi@4.0.88(sass@1.69.5): resolution: {integrity: sha512-q5emmxWjOrA8ZNTNaNcUEU/FxM/DDnpjGK8aYIce/ih/suzo+K2csWDAkQCBYgNPg4Ro1Juv2oUgyOt3TVcUXA==} dependencies: '@iconify/utils': 2.1.1 @@ -5245,8 +5603,8 @@ packages: '@umijs/babel-preset-umi': 4.0.88 '@umijs/bundler-esbuild': 4.0.88 '@umijs/bundler-utils': 4.0.88 - '@umijs/bundler-vite': 4.0.88(@types/node@20.8.9)(postcss@8.4.31)(rollup@4.1.5)(sass@1.69.5) - '@umijs/bundler-webpack': 4.0.88(typescript@5.2.2) + '@umijs/bundler-vite': 4.0.88(postcss@8.4.31)(sass@1.69.5) + '@umijs/bundler-webpack': 4.0.88 '@umijs/core': 4.0.88 '@umijs/did-you-know': 1.0.3 '@umijs/es-module-parser': 0.0.7 @@ -5331,6 +5689,19 @@ packages: source-map: 0.7.4 dev: false + /@umijs/renderer-react@4.0.88: + resolution: {integrity: sha512-5zU4+PLO9+D8nl/WikGE6QjYvztKWSLGp+N33TPL0BrrJOSTI/ffH/iwsPxiFG/ABX4oz3SShUyIhyChSnP/WQ==} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + dependencies: + '@babel/runtime': 7.23.2 + '@loadable/component': 5.15.2 + history: 5.3.0 + react-helmet-async: 1.3.0 + react-router-dom: 6.3.0 + dev: false + /@umijs/renderer-react@4.0.88(react-dom@18.1.0)(react@18.1.0): resolution: {integrity: sha512-5zU4+PLO9+D8nl/WikGE6QjYvztKWSLGp+N33TPL0BrrJOSTI/ffH/iwsPxiFG/ABX4oz3SShUyIhyChSnP/WQ==} peerDependencies: @@ -5358,14 +5729,14 @@ packages: - supports-color dev: false - /@umijs/test@4.0.88(@babel/core@7.23.2): + /@umijs/test@4.0.88: resolution: {integrity: sha512-3v021VhU/aQc3Jk421z3ys1Q5S/JJlIzhJYYds+O1gb3esgeaGIS6OyDA1xmCaqVGgJeYOq408AN43/hCR9rtw==} dependencies: - '@babel/plugin-transform-modules-commonjs': 7.23.0(@babel/core@7.23.2) + '@babel/plugin-transform-modules-commonjs': 7.23.0 '@jest/types': 27.5.1 '@umijs/bundler-utils': 4.0.88 '@umijs/utils': 4.0.88 - babel-jest: 29.7.0(@babel/core@7.23.2) + babel-jest: 29.7.0 esbuild: 0.17.19 identity-obj-proxy: 3.0.0 isomorphic-unfetch: 4.0.2 @@ -5391,6 +5762,7 @@ packages: /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + dev: true /@vitejs/plugin-react@4.0.0(vite@4.3.1): resolution: {integrity: sha512-HX0XzMjL3hhOYm+0s95pb0Z7F8O81G7joUHgfDd/9J/ZZf5k4xX6QAMFkKsHFxaHlf6X7GD7+XuaZ66ULiJuhQ==} @@ -5402,7 +5774,7 @@ packages: '@babel/plugin-transform-react-jsx-self': 7.22.5(@babel/core@7.23.2) '@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.23.2) react-refresh: 0.14.0 - vite: 4.3.1(@types/node@20.8.9)(less@4.1.3)(sass@1.69.5) + vite: 4.3.1(less@4.1.3)(sass@1.69.5) transitivePeerDependencies: - supports-color dev: false @@ -5558,7 +5930,7 @@ packages: - supports-color dev: true - /@vue/language-core@1.8.19(typescript@5.2.2): + /@vue/language-core@1.8.19: resolution: {integrity: sha512-nt3dodGs97UM6fnxeQBazO50yYCKBK53waFWB3qMbLmR6eL3aUryZgQtZoBe1pye17Wl8fs9HysV3si6xMgndQ==} peerDependencies: typescript: '*' @@ -5573,7 +5945,6 @@ packages: '@vue/shared': 3.3.4 minimatch: 9.0.3 muggle-string: 0.3.1 - typescript: 5.2.2 vue-template-compiler: 2.7.14 dev: true @@ -5634,11 +6005,11 @@ packages: resolution: {integrity: sha512-CPuIReonid9+zOG/CGTT05FXrPYATEqoDGNrEaqS4hwcw5BUNM2FguC0mOwJD4Jr16UpRVl9N0pY3P+srIbqmg==} dev: true - /@vue/typescript@1.8.19(typescript@5.2.2): + /@vue/typescript@1.8.19: resolution: {integrity: sha512-k/SHeeQROUgqsxyHQ8Cs3Zz5TnX57p7BcBDVYR2E0c61QL2DJ2G8CsaBremmNGuGE6o1R5D50IHIxFmroMz8iw==} dependencies: '@volar/typescript': 1.10.4 - '@vue/language-core': 1.8.19(typescript@5.2.2) + '@vue/language-core': 1.8.19 transitivePeerDependencies: - typescript dev: true @@ -5706,6 +6077,7 @@ packages: /abab@2.0.6: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + dev: true /abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} @@ -5729,6 +6101,7 @@ packages: dependencies: acorn: 7.4.1 acorn-walk: 7.2.0 + dev: true /acorn-jsx@5.3.2(acorn@8.10.0): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -5736,6 +6109,7 @@ packages: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: acorn: 8.10.0 + dev: true /acorn-walk@6.2.0: resolution: {integrity: sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==} @@ -5745,6 +6119,7 @@ packages: /acorn-walk@7.2.0: resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} engines: {node: '>=0.4.0'} + dev: true /acorn@5.7.4: resolution: {integrity: sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==} @@ -5762,6 +6137,7 @@ packages: resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} engines: {node: '>=0.4.0'} hasBin: true + dev: true /acorn@8.10.0: resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} @@ -5782,6 +6158,7 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color + dev: true /agent-base@7.1.0: resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} @@ -5857,6 +6234,7 @@ packages: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 uri-js: 4.4.1 + dev: true /algoliasearch@3.35.1: resolution: {integrity: sha512-K4yKVhaHkXfJ/xcUnil04xiSrB8B8yHZoFEhWNpXg23eiCnqvTZw1tn/SqvdsANlYHLJlKl0qi3I/Q2Sqo7LwQ==} @@ -5932,6 +6310,7 @@ packages: engines: {node: '>=8'} dependencies: type-fest: 0.21.3 + dev: true /ansi-escapes@5.0.0: resolution: {integrity: sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==} @@ -6083,6 +6462,66 @@ packages: scroll-into-view-if-needed: 2.2.31 dev: false + /antd@5.12.2: + resolution: {integrity: sha512-lJ4pdBRboN2Tl5hXTqgxUjGIercB2YIK7Z8fVDushgrJ55RhywJjvcQVnBmuxcSPuWEF2Yu8SNBVAnaV4EqzWA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@ant-design/colors': 7.0.0 + '@ant-design/cssinjs': 1.18.1 + '@ant-design/icons': 5.2.6 + '@ant-design/react-slick': 1.0.2 + '@babel/runtime': 7.23.5 + '@ctrl/tinycolor': 3.6.1 + '@rc-component/color-picker': 1.4.1 + '@rc-component/mutate-observer': 1.1.0 + '@rc-component/tour': 1.11.1 + '@rc-component/trigger': 1.18.2 + classnames: 2.3.2 + copy-to-clipboard: 3.3.3 + dayjs: 1.11.7 + qrcode.react: 3.1.0 + rc-cascader: 3.20.0 + rc-checkbox: 3.1.0 + rc-collapse: 3.7.2 + rc-dialog: 9.3.4 + rc-drawer: 6.5.2 + rc-dropdown: 4.1.0 + rc-field-form: 1.41.0 + rc-image: 7.5.1 + rc-input: 1.3.6 + rc-input-number: 8.4.0 + rc-mentions: 2.9.1 + rc-menu: 9.12.4 + rc-motion: 2.9.0 + rc-notification: 5.3.0 + rc-pagination: 4.0.3 + rc-picker: 3.14.6(dayjs@1.11.7) + rc-progress: 3.5.1 + rc-rate: 2.12.0 + rc-resize-observer: 1.4.0 + rc-segmented: 2.2.2 + rc-select: 14.10.0 + rc-slider: 10.5.0 + rc-steps: 6.0.1 + rc-switch: 4.1.0 + rc-table: 7.36.0 + rc-tabs: 12.14.1 + rc-textarea: 1.5.3 + rc-tooltip: 6.1.2 + rc-tree: 5.8.2 + rc-tree-select: 5.15.0 + rc-upload: 4.3.5 + rc-util: 5.38.1 + scroll-into-view-if-needed: 3.1.0 + throttle-debounce: 5.0.0 + transitivePeerDependencies: + - date-fns + - luxon + - moment + dev: false + /antd@5.12.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-lJ4pdBRboN2Tl5hXTqgxUjGIercB2YIK7Z8fVDushgrJ55RhywJjvcQVnBmuxcSPuWEF2Yu8SNBVAnaV4EqzWA==} peerDependencies: @@ -6143,6 +6582,7 @@ packages: - date-fns - luxon - moment + dev: true /anymatch@1.3.2: resolution: {integrity: sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==} @@ -6158,6 +6598,7 @@ packages: normalize-path: 2.1.1 transitivePeerDependencies: - supports-color + dev: true /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} @@ -6222,14 +6663,17 @@ packages: /arr-diff@4.0.0: resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==} engines: {node: '>=0.10.0'} + dev: true /arr-flatten@1.1.0: resolution: {integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==} engines: {node: '>=0.10.0'} + dev: true /arr-union@3.1.0: resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==} engines: {node: '>=0.10.0'} + dev: true /array-buffer-byte-length@1.0.0: resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} @@ -6291,6 +6735,7 @@ packages: /array-unique@0.3.2: resolution: {integrity: sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==} engines: {node: '>=0.10.0'} + dev: true /array.prototype.findlastindex@1.2.3: resolution: {integrity: sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==} @@ -6366,6 +6811,7 @@ packages: /arrify@1.0.1: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} + dev: true /arrify@2.0.1: resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} @@ -6400,6 +6846,7 @@ packages: /assign-symbols@1.0.0: resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} engines: {node: '>=0.10.0'} + dev: true /ast-types-flow@0.0.7: resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==} @@ -6408,6 +6855,7 @@ packages: /astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} + dev: true /astring@1.8.4: resolution: {integrity: sha512-97a+l2LBU3Op3bBQEff79i/E4jMD2ZLFD8rHx9B6mXyB2uQwhJQYfiDqUwtfjF4QA1F2qs//N6Cw8LetMbQjcw==} @@ -6565,18 +7013,18 @@ packages: slash: 3.0.0 transitivePeerDependencies: - supports-color + dev: true - /babel-jest@29.7.0(@babel/core@7.23.2): + /babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.8.0 dependencies: - '@babel/core': 7.23.2 '@jest/transform': 29.7.0 '@types/babel__core': 7.20.2 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.23.2) + babel-preset-jest: 29.6.3 chalk: 4.1.2 graceful-fs: 4.2.10 slash: 3.0.0 @@ -6629,6 +7077,7 @@ packages: '@babel/types': 7.23.0 '@types/babel__core': 7.20.2 '@types/babel__traverse': 7.18.3 + dev: true /babel-plugin-jest-hoist@29.6.3: resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} @@ -6640,6 +7089,25 @@ packages: '@types/babel__traverse': 7.18.3 dev: false + /babel-preset-current-node-syntax@1.0.1: + resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/plugin-syntax-async-generators': 7.8.4 + '@babel/plugin-syntax-bigint': 7.8.3 + '@babel/plugin-syntax-class-properties': 7.12.13 + '@babel/plugin-syntax-import-meta': 7.10.4 + '@babel/plugin-syntax-json-strings': 7.8.3 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3 + '@babel/plugin-syntax-numeric-separator': 7.10.4 + '@babel/plugin-syntax-object-rest-spread': 7.8.3 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3 + '@babel/plugin-syntax-optional-chaining': 7.8.3 + '@babel/plugin-syntax-top-level-await': 7.14.5 + dev: false + /babel-preset-current-node-syntax@1.0.1(@babel/core@7.23.2): resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} peerDependencies: @@ -6658,6 +7126,7 @@ packages: '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.2) '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.2) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.2) + dev: true /babel-preset-jest@24.9.0(@babel/core@7.23.2): resolution: {integrity: sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==} @@ -6679,16 +7148,16 @@ packages: '@babel/core': 7.23.2 babel-plugin-jest-hoist: 26.6.2 babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.2) + dev: true - /babel-preset-jest@29.6.3(@babel/core@7.23.2): + /babel-preset-jest@29.6.3: resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.2 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.2) + babel-preset-current-node-syntax: 1.0.1 dev: false /babel-runtime@6.26.0: @@ -6706,6 +7175,7 @@ packages: /balanced-match@2.0.0: resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} + dev: true /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -6721,6 +7191,7 @@ packages: isobject: 3.0.1 mixin-deep: 1.3.2 pascalcase: 0.1.1 + dev: true /bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} @@ -6751,7 +7222,6 @@ packages: /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} - dev: false /binaryextensions@2.3.0: resolution: {integrity: sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==} @@ -6868,6 +7338,7 @@ packages: to-regex: 3.0.2 transitivePeerDependencies: - supports-color + dev: true /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} @@ -6889,6 +7360,7 @@ packages: /browser-process-hrtime@1.0.0: resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} + dev: true /browser-resolve@1.11.3: resolution: {integrity: sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==} @@ -7055,6 +7527,11 @@ packages: - debug dev: true + /bytes-iec@3.1.1: + resolution: {integrity: sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA==} + engines: {node: '>= 0.8'} + dev: true + /bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -7109,6 +7586,7 @@ packages: to-object-path: 0.3.0 union-value: 1.0.1 unset-value: 1.0.0 + dev: true /cacheable-request@6.1.0: resolution: {integrity: sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==} @@ -7176,6 +7654,7 @@ packages: map-obj: 4.3.0 quick-lru: 5.1.1 type-fest: 1.4.0 + dev: true /camelcase@1.2.1: resolution: {integrity: sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g==} @@ -7211,6 +7690,7 @@ packages: engines: {node: 6.* || 8.* || >= 10.*} dependencies: rsvp: 4.8.5 + dev: true /capture-stack-trace@1.0.2: resolution: {integrity: sha512-X/WM2UQs6VMHUtjUDnZTRI+i1crWteJySFzr9UpGoQa4WQffXVTTXuekjl7TjZRlcF2XfjgITT0HxZ9RnxeT0w==} @@ -7292,6 +7772,7 @@ packages: /char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} + dev: true /character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -7339,7 +7820,6 @@ packages: readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.3 - dev: false /chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -7354,6 +7834,7 @@ packages: /ci-info@2.0.0: resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + dev: true /ci-info@3.7.1: resolution: {integrity: sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==} @@ -7369,6 +7850,7 @@ packages: /cjs-module-lexer@0.6.0: resolution: {integrity: sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==} + dev: true /class-utils@0.3.6: resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==} @@ -7378,6 +7860,7 @@ packages: define-property: 0.2.5 isobject: 3.0.1 static-extend: 0.1.2 + dev: true /classnames@2.3.2: resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==} @@ -7496,6 +7979,7 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 6.2.0 + dev: true /cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} @@ -7527,6 +8011,7 @@ packages: /co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + dev: true /coa@2.0.2: resolution: {integrity: sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==} @@ -7585,6 +8070,7 @@ packages: /collect-v8-coverage@1.0.1: resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==} + dev: true /collection-visit@1.0.0: resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==} @@ -7592,6 +8078,7 @@ packages: dependencies: map-visit: 1.0.0 object-visit: 1.0.1 + dev: true /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -7625,6 +8112,7 @@ packages: /colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + dev: true /colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -7693,6 +8181,7 @@ packages: /component-emitter@1.3.0: resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} + dev: true /compute-scroll-into-view@1.0.20: resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} @@ -7844,6 +8333,7 @@ packages: /convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + dev: true /convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -7867,6 +8357,7 @@ packages: /copy-descriptor@0.1.1: resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==} engines: {node: '>=0.10.0'} + dev: true /copy-to-clipboard@3.3.3: resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} @@ -7956,6 +8447,7 @@ packages: parse-json: 5.2.0 path-type: 4.0.0 typescript: 5.2.2 + dev: true /cpx@1.5.0: resolution: {integrity: sha512-jHTjZhsbg9xWgsP2vuNW2jnnzBX+p4T+vNI9Lbjzs1n4KhOfa22bQppiFYLsWQKd8TzmL5aSP/Me3yfsCwXbDA==} @@ -8035,6 +8527,7 @@ packages: semver: 5.7.1 shebang-command: 1.2.0 which: 1.3.1 + dev: true /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} @@ -8099,6 +8592,7 @@ packages: /css-functions-list@3.2.1: resolution: {integrity: sha512-Nj5YcaGgBtuUmn1D7oHqPW0c9iui7xsTsj5lIX8ZgevdfhmjFfKB3r8moHJtNJnctnYXJyYX5I1pp90HM4TPgQ==} engines: {node: '>=12 || >=16'} + dev: true /css-has-pseudo@3.0.4(postcss@8.4.31): resolution: {integrity: sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==} @@ -8186,6 +8680,7 @@ packages: dependencies: mdn-data: 2.0.30 source-map-js: 1.0.2 + dev: true /css-what@3.4.2: resolution: {integrity: sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==} @@ -8289,9 +8784,11 @@ packages: /cssom@0.3.8: resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} + dev: true /cssom@0.4.4: resolution: {integrity: sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==} + dev: true /cssstyle@1.4.0: resolution: {integrity: sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==} @@ -8304,6 +8801,7 @@ packages: engines: {node: '>=8'} dependencies: cssom: 0.3.8 + dev: true /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} @@ -8451,6 +8949,7 @@ packages: abab: 2.0.6 whatwg-mimetype: 2.3.0 whatwg-url: 8.7.0 + dev: true /datauri@3.0.0: resolution: {integrity: sha512-NeDFuUPV1YCpCn8MUIcDk1QnuyenUHs7f4Q5P0n9FFA0neKFrfEH9esR+YMW95BplbYfdmjbs0Pl/ZGAaM2QHQ==} @@ -8525,6 +9024,7 @@ packages: dependencies: decamelize: 1.2.0 map-obj: 1.0.1 + dev: true /decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} @@ -8533,6 +9033,7 @@ packages: /decamelize@5.0.1: resolution: {integrity: sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==} engines: {node: '>=10'} + dev: true /decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} @@ -8599,6 +9100,7 @@ packages: /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true /deep-rename-keys@0.2.1: resolution: {integrity: sha512-RHd9ABw4Fvk+gYDWqwOftG849x0bYOySl/RgX0tLI9i27ZIeSO91mLZJEp7oPHOMFqHvpgu21YptmDt0FYD/0A==} @@ -8611,6 +9113,7 @@ packages: /deepmerge@4.2.2: resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} engines: {node: '>=0.10.0'} + dev: true /deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} @@ -8679,12 +9182,14 @@ packages: engines: {node: '>=0.10.0'} dependencies: is-descriptor: 0.1.6 + dev: true /define-property@1.0.0: resolution: {integrity: sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==} engines: {node: '>=0.10.0'} dependencies: is-descriptor: 1.0.2 + dev: true /define-property@2.0.2: resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==} @@ -8692,6 +9197,7 @@ packages: dependencies: is-descriptor: 1.0.2 isobject: 3.0.1 + dev: true /defined@1.0.1: resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} @@ -8751,6 +9257,7 @@ packages: /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} + dev: true /detect-newline@4.0.1: resolution: {integrity: sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==} @@ -8769,6 +9276,7 @@ packages: /diff-sequences@26.6.2: resolution: {integrity: sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==} engines: {node: '>= 10.14.2'} + dev: true /diff-sequences@27.5.1: resolution: {integrity: sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==} @@ -8819,6 +9327,7 @@ packages: engines: {node: '>=6.0.0'} dependencies: esutils: 2.0.3 + dev: true /dom-accessibility-api@0.5.15: resolution: {integrity: sha512-8o+oVqLQZoruQPYy3uAAQtc6YbtSiRq5aPJBhJ82YTJRHvI6ofhYAkC81WmjFTnfUbqg6T3aCglIpU9p/5e7Cw==} @@ -8886,6 +9395,7 @@ packages: engines: {node: '>=8'} dependencies: webidl-conversions: 5.0.0 + dev: true /domhandler@4.3.1: resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} @@ -8961,7 +9471,7 @@ packages: resolution: {integrity: sha512-a/Y5lf0G6gwsEQ9hop/n03CcjmHsGBk384Cz/AEX6mRYrfSpUx/lQvP9HLoXkCzScl9PL1sSmLPnMkgaXDCZLA==} dev: false - /dumi@2.2.15(@babel/core@7.23.2)(@types/node@20.8.9)(eslint@8.52.0)(jest@26.6.3)(postcss-less@6.0.0)(prettier@3.0.3)(rollup@4.1.5)(stylelint@15.11.0)(typescript@5.2.2): + /dumi@2.2.15: resolution: {integrity: sha512-O6bUZvqYictU1p5kuwD26Vrn4g5HuMaw41GBhjEEG0rjAveNvnQdgJBJS6YarNCTTLJ9fUZ4t86aBt31MUCTHw==} hasBin: true peerDependencies: @@ -9007,12 +9517,12 @@ packages: prism-themes: 1.9.0 prismjs: 1.29.0 raw-loader: 4.0.2 - rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) - rc-tabs: 12.14.1(react-dom@18.2.0)(react@18.2.0) - rc-tree: 5.8.2(react-dom@18.2.0)(react@18.2.0) + rc-motion: 2.9.0 + rc-tabs: 12.14.1 + rc-tree: 5.8.2 react-copy-to-clipboard: 5.1.0 react-error-boundary: 4.0.11 - react-intl: 6.5.5(typescript@5.2.2) + react-intl: 6.5.5 rehype-autolink-headings: 6.1.1 rehype-remove-comments: 5.0.0 rehype-stringify: 9.0.3 @@ -9023,7 +9533,7 @@ packages: remark-rehype: 10.1.0 sass: 1.69.5 sitemap: 7.1.1 - umi: 4.0.88(@babel/core@7.23.2)(@types/node@20.8.9)(eslint@8.52.0)(jest@26.6.3)(postcss-less@6.0.0)(prettier@3.0.3)(rollup@4.1.5)(sass@1.69.5)(stylelint@15.11.0)(typescript@5.2.2) + umi: 4.0.88(sass@1.69.5) unified: 10.1.2 unist-util-visit: 4.1.2 unist-util-visit-parents: 5.1.3 @@ -9165,6 +9675,7 @@ packages: /emittery@0.7.2: resolution: {integrity: sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==} engines: {node: '>=10'} + dev: true /emoji-regex@10.2.1: resolution: {integrity: sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==} @@ -9732,6 +10243,7 @@ packages: /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + dev: true /escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} @@ -9761,6 +10273,7 @@ packages: optionator: 0.8.3 optionalDependencies: source-map: 0.6.1 + dev: true /eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.29.0)(eslint@8.52.0): resolution: {integrity: sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==} @@ -9869,7 +10382,7 @@ packages: - supports-color dev: true - /eslint-plugin-jest@27.2.3(@typescript-eslint/eslint-plugin@5.62.0)(eslint@8.52.0)(jest@26.6.3)(typescript@5.2.2): + /eslint-plugin-jest@27.2.3(@typescript-eslint/eslint-plugin@5.62.0): resolution: {integrity: sha512-sRLlSCpICzWuje66Gl9zvdF6mwD5X86I4u55hJyFBsxYOsBCmT5+kSUjf+fkFWVMMgpzNEupjW8WzUqi83hJAQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -9882,10 +10395,8 @@ packages: jest: optional: true dependencies: - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.52.0)(typescript@5.2.2) - '@typescript-eslint/utils': 5.62.0(eslint@8.52.0)(typescript@5.2.2) - eslint: 8.52.0 - jest: 26.6.3 + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0) + '@typescript-eslint/utils': 5.62.0 transitivePeerDependencies: - supports-color - typescript @@ -9980,6 +10491,13 @@ packages: synckit: 0.8.5 dev: true + /eslint-plugin-react-hooks@4.6.0: + resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + dev: false + /eslint-plugin-react-hooks@4.6.0(eslint@8.52.0): resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} engines: {node: '>=10'} @@ -9987,6 +10505,31 @@ packages: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 dependencies: eslint: 8.52.0 + dev: true + + /eslint-plugin-react@7.33.2: + resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + dependencies: + array-includes: 3.1.6 + array.prototype.flatmap: 1.3.1 + array.prototype.tosorted: 1.1.1 + doctrine: 2.1.0 + es-iterator-helpers: 1.0.15 + estraverse: 5.3.0 + jsx-ast-utils: 3.3.3 + minimatch: 3.1.2 + object.entries: 1.1.6 + object.fromentries: 2.0.6 + object.hasown: 1.1.2 + object.values: 1.1.6 + prop-types: 15.8.1 + resolve: 2.0.0-next.4 + semver: 6.3.1 + string.prototype.matchall: 4.0.8 + dev: false /eslint-plugin-react@7.33.2(eslint@8.52.0): resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} @@ -10011,6 +10554,7 @@ packages: resolve: 2.0.0-next.4 semver: 6.3.1 string.prototype.matchall: 4.0.8 + dev: true /eslint-plugin-vue@9.17.0(eslint@8.52.0): resolution: {integrity: sha512-r7Bp79pxQk9I5XDP0k2dpUC7Ots3OSWgvGZNu3BxmKK6Zg7NgVtcOB6OCna5Kb9oQwJPl5hq183WD0SY5tZtIQ==} @@ -10043,6 +10587,7 @@ packages: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 + dev: true /eslint-utils@3.0.0(eslint@8.52.0): resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} @@ -10107,6 +10652,7 @@ packages: text-table: 0.2.0 transitivePeerDependencies: - supports-color + dev: true /espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} @@ -10115,6 +10661,7 @@ packages: acorn: 8.10.0 acorn-jsx: 5.3.2(acorn@8.10.0) eslint-visitor-keys: 3.4.3 + dev: true /esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} @@ -10126,6 +10673,7 @@ packages: engines: {node: '>=0.10'} dependencies: estraverse: 5.3.0 + dev: true /esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} @@ -10212,6 +10760,7 @@ packages: /exec-sh@0.3.6: resolution: {integrity: sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==} + dev: true /execa@0.7.0: resolution: {integrity: sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==} @@ -10250,6 +10799,7 @@ packages: p-finally: 1.0.0 signal-exit: 3.0.7 strip-eof: 1.0.0 + dev: true /execa@4.1.0: resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} @@ -10264,6 +10814,7 @@ packages: onetime: 5.1.2 signal-exit: 3.0.7 strip-final-newline: 2.0.0 + dev: true /execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} @@ -10311,6 +10862,7 @@ packages: /exit@0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} + dev: true /expand-brackets@0.1.5: resolution: {integrity: sha512-hxx03P2dJxss6ceIeri9cmYOT4SRs3Zk3afZwWpOsRqLqprhTR8u++SlC+sFGsQr7WGFPdMF7Gjc1njDLDK6UA==} @@ -10332,6 +10884,7 @@ packages: to-regex: 3.0.2 transitivePeerDependencies: - supports-color + dev: true /expand-range@1.8.2: resolution: {integrity: sha512-AFASGfIlnIbkKPQwX1yHaDjFvh/1gyKJODme52V6IORh69uEYgZp0o9C+qsIGNVEiuuhQU0CSSl++Rlegg1qvA==} @@ -10376,6 +10929,7 @@ packages: jest-matcher-utils: 26.6.2 jest-message-util: 26.6.2 jest-regex-util: 26.0.0 + dev: true /extend-shallow@2.0.1: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} @@ -10389,6 +10943,7 @@ packages: dependencies: assign-symbols: 1.0.0 is-extendable: 1.0.1 + dev: true /extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -10422,6 +10977,7 @@ packages: to-regex: 3.0.2 transitivePeerDependencies: - supports-color + dev: true /extract-from-css@0.4.4: resolution: {integrity: sha512-41qWGBdtKp9U7sgBxAQ7vonYqSXzgW/SiAYzq4tdWSVhAShvpVCH1nyvPQgjse6EdgbW7Y7ERdT3674/lKr65A==} @@ -10474,11 +11030,23 @@ packages: merge2: 1.4.1 micromatch: 4.0.5 - /fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + /fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} /fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true /fast-loops@1.1.3: resolution: {integrity: sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g==} @@ -10500,6 +11068,7 @@ packages: /fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} engines: {node: '>= 4.9.1'} + dev: true /fastest-stable-stringify@2.0.2: resolution: {integrity: sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==} @@ -10564,12 +11133,14 @@ packages: engines: {node: ^10.12.0 || >=12.0.0} dependencies: flat-cache: 3.0.4 + dev: true /file-entry-cache@7.0.1: resolution: {integrity: sha512-uLfFktPmRetVCbHe5UPuekWrQ6hENufnA46qEGbfACkK5drjTTdQYUragRgMjHldcbYG+nslUerqMPjbBSHXjQ==} engines: {node: '>=12.0.0'} dependencies: flat-cache: 3.1.1 + dev: true /file-name@0.1.0: resolution: {integrity: sha512-Q8SskhjF4eUk/xoQkmubwLkoHwOTv6Jj/WGtOVLKkZ0vvM+LipkSXugkn1F/+mjWXU32AXLZB3qaz0arUzgtRw==} @@ -10632,6 +11203,7 @@ packages: is-number: 3.0.0 repeat-string: 1.6.1 to-regex-range: 2.1.1 + dev: true /fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} @@ -10712,6 +11284,7 @@ packages: dependencies: flatted: 3.2.7 rimraf: 3.0.2 + dev: true /flat-cache@3.1.1: resolution: {integrity: sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==} @@ -10720,12 +11293,15 @@ packages: flatted: 3.2.9 keyv: 4.5.4 rimraf: 3.0.2 + dev: true /flatted@3.2.7: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + dev: true /flatted@3.2.9: resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} + dev: true /flru@1.0.2: resolution: {integrity: sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==} @@ -10774,6 +11350,7 @@ packages: /for-in@1.0.2: resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} engines: {node: '>=0.10.0'} + dev: true /for-own@0.1.5: resolution: {integrity: sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==} @@ -10797,7 +11374,7 @@ packages: /forever-agent@0.6.1: resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} - /fork-ts-checker-webpack-plugin@8.0.0(typescript@5.2.2): + /fork-ts-checker-webpack-plugin@8.0.0: resolution: {integrity: sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==} engines: {node: '>=12.13.0', yarn: '>=1.0.0'} peerDependencies: @@ -10816,7 +11393,6 @@ packages: schema-utils: 3.1.1 semver: 7.5.4 tapable: 2.2.1 - typescript: 5.2.2 dev: false /form-data@2.3.3: @@ -10834,6 +11410,7 @@ packages: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 + dev: true /format@0.2.2: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} @@ -10856,6 +11433,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: map-cache: 0.2.2 + dev: true /from2@2.3.0: resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} @@ -11082,12 +11660,14 @@ packages: engines: {node: '>=6'} dependencies: pump: 3.0.0 + dev: true /get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} dependencies: pump: 3.0.0 + dev: true /get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} @@ -11250,6 +11830,7 @@ packages: engines: {node: '>=10.13.0'} dependencies: is-glob: 4.0.3 + dev: true /glob2base@0.0.12: resolution: {integrity: sha512-ZyqlgowMbfj2NPjxaZZ/EtsXlOch28FRXgMd64vqZWk1bT9+wvSRLYD1om9M7QfQru51zJPAT17qXm4/zd+9QA==} @@ -11323,6 +11904,7 @@ packages: engines: {node: '>=6'} dependencies: global-prefix: 3.0.0 + dev: true /global-prefix@0.1.5: resolution: {integrity: sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==} @@ -11341,6 +11923,7 @@ packages: ini: 1.3.8 kind-of: 6.0.3 which: 1.3.1 + dev: true /global-tunnel-ng@2.7.1: resolution: {integrity: sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==} @@ -11370,6 +11953,7 @@ packages: engines: {node: '>=8'} dependencies: type-fest: 0.20.2 + dev: true /globalthis@1.0.3: resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} @@ -11399,6 +11983,18 @@ packages: slash: 4.0.0 dev: false + /globby@14.0.0: + resolution: {integrity: sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==} + engines: {node: '>=18'} + dependencies: + '@sindresorhus/merge-streams': 1.0.0 + fast-glob: 3.3.2 + ignore: 5.2.4 + path-type: 5.0.0 + slash: 5.1.0 + unicorn-magic: 0.1.0 + dev: true + /globby@6.1.0: resolution: {integrity: sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==} engines: {node: '>=0.10.0'} @@ -11411,6 +12007,7 @@ packages: /globjoin@0.1.4: resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} + dev: true /google-auth-library@8.7.0: resolution: {integrity: sha512-1M0NG5VDIvJZEnstHbRdckLZESoJwguinwN8Dhae0j2ZKIQFIV63zxm6Fo6nM4xkgqUr2bbMtV5Dgo+Hy6oo0Q==} @@ -11514,6 +12111,7 @@ packages: /growly@1.3.0: resolution: {integrity: sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==} requiresBuild: true + dev: true optional: true /gtoken@6.1.2: @@ -11571,6 +12169,7 @@ packages: /hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} + dev: true /harmony-reflect@1.6.2: resolution: {integrity: sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==} @@ -11636,6 +12235,7 @@ packages: get-value: 2.0.6 has-values: 1.0.0 isobject: 3.0.1 + dev: true /has-values@0.1.4: resolution: {integrity: sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==} @@ -11647,6 +12247,7 @@ packages: dependencies: is-number: 3.0.0 kind-of: 4.0.0 + dev: true /has@1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} @@ -11942,6 +12543,7 @@ packages: engines: {node: '>=10'} dependencies: lru-cache: 6.0.0 + dev: true /hosted-git-info@6.1.1: resolution: {integrity: sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==} @@ -11974,6 +12576,7 @@ packages: engines: {node: '>=10'} dependencies: whatwg-encoding: 1.0.5 + dev: true /html-entities@2.3.3: resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==} @@ -11981,6 +12584,7 @@ packages: /html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + dev: true /html-minifier-terser@6.1.0: resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} @@ -12004,6 +12608,7 @@ packages: /html-tags@3.3.1: resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} engines: {node: '>=8'} + dev: true /html-to-text@9.0.5: resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} @@ -12099,6 +12704,7 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color + dev: true /http-proxy-agent@5.0.0: resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} @@ -12151,6 +12757,7 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color + dev: true /https-proxy-agent@7.0.2: resolution: {integrity: sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==} @@ -12165,6 +12772,7 @@ packages: /human-signals@1.1.1: resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} engines: {node: '>=8.12.0'} + dev: true /human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} @@ -12319,6 +12927,7 @@ packages: /import-lazy@4.0.0: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} engines: {node: '>=8'} + dev: true /import-local@3.1.0: resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} @@ -12327,6 +12936,7 @@ packages: dependencies: pkg-dir: 4.2.0 resolve-cwd: 3.0.0 + dev: true /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} @@ -12489,12 +13099,14 @@ packages: engines: {node: '>=0.10.0'} dependencies: kind-of: 3.2.2 + dev: true /is-accessor-descriptor@1.0.0: resolution: {integrity: sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==} engines: {node: '>=0.10.0'} dependencies: kind-of: 6.0.3 + dev: true /is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -12562,7 +13174,6 @@ packages: engines: {node: '>=8'} dependencies: binary-extensions: 2.2.0 - dev: false /is-boolean-object@1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} @@ -12601,6 +13212,7 @@ packages: hasBin: true dependencies: ci-info: 2.0.0 + dev: true /is-core-module@2.11.0: resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} @@ -12623,12 +13235,14 @@ packages: engines: {node: '>=0.10.0'} dependencies: kind-of: 3.2.2 + dev: true /is-data-descriptor@1.0.0: resolution: {integrity: sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==} engines: {node: '>=0.10.0'} dependencies: kind-of: 6.0.3 + dev: true /is-date-object@1.0.5: resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} @@ -12646,6 +13260,7 @@ packages: is-accessor-descriptor: 0.1.6 is-data-descriptor: 0.1.4 kind-of: 5.1.0 + dev: true /is-descriptor@1.0.2: resolution: {integrity: sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==} @@ -12654,6 +13269,7 @@ packages: is-accessor-descriptor: 1.0.0 is-data-descriptor: 1.0.0 kind-of: 6.0.3 + dev: true /is-directory@0.3.1: resolution: {integrity: sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==} @@ -12718,6 +13334,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: is-plain-object: 2.0.4 + dev: true /is-extglob@1.0.0: resolution: {integrity: sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==} @@ -12756,6 +13373,7 @@ packages: /is-generator-fn@2.1.0: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} engines: {node: '>=6'} + dev: true /is-generator-function@1.0.10: resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} @@ -12845,6 +13463,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: kind-of: 3.2.2 + dev: true /is-number@4.0.0: resolution: {integrity: sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==} @@ -12880,10 +13499,12 @@ packages: /is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} + dev: true /is-plain-obj@1.1.0: resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} engines: {node: '>=0.10.0'} + dev: true /is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} @@ -12903,6 +13524,7 @@ packages: /is-plain-object@5.0.0: resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} engines: {node: '>=0.10.0'} + dev: true /is-posix-bracket@0.1.1: resolution: {integrity: sha512-Yu68oeXJ7LeWNmZ3Zov/xg/oDBnBK2RNxwYY1ilNJX+tKKZqgPK+qOn/Gs9jEu66KDY9Netf5XLKNGzas/vPfQ==} @@ -12911,6 +13533,7 @@ packages: /is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + dev: true /is-primitive@2.0.0: resolution: {integrity: sha512-N3w1tFaRfk3UrPfqeRyD+GYDASU3W5VinKhlORy8EWVf/sIdDL9GAcew85XmktCfH+ngG7SRXEVDoO18WMdB/Q==} @@ -13044,6 +13667,7 @@ packages: /is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} + dev: true /is-wsl@1.1.0: resolution: {integrity: sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==} @@ -13134,6 +13758,7 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color + dev: true /istanbul-lib-instrument@5.2.1: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} @@ -13154,6 +13779,7 @@ packages: istanbul-lib-coverage: 3.2.0 make-dir: 3.1.0 supports-color: 7.2.0 + dev: true /istanbul-lib-source-maps@4.0.1: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} @@ -13164,6 +13790,7 @@ packages: source-map: 0.6.1 transitivePeerDependencies: - supports-color + dev: true /istanbul-reports@3.1.5: resolution: {integrity: sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==} @@ -13171,6 +13798,7 @@ packages: dependencies: html-escaper: 2.0.2 istanbul-lib-report: 3.0.0 + dev: true /istextorbinary@2.6.0: resolution: {integrity: sha512-+XRlFseT8B3L9KyjxxLjfXSLMuErKDsd8DBNrsaxoViABMEZlOSCstwmw0qpoFX3+U6yWU1yhLudAe6/lETGGA==} @@ -13211,6 +13839,7 @@ packages: '@jest/types': 26.6.2 execa: 4.1.0 throat: 5.0.0 + dev: true /jest-cli@26.6.3: resolution: {integrity: sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==} @@ -13236,6 +13865,7 @@ packages: - supports-color - ts-node - utf-8-validate + dev: true /jest-config@24.9.0: resolution: {integrity: sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==} @@ -13296,6 +13926,7 @@ packages: - canvas - supports-color - utf-8-validate + dev: true /jest-diff@24.9.0: resolution: {integrity: sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==} @@ -13315,6 +13946,7 @@ packages: diff-sequences: 26.6.2 jest-get-type: 26.3.0 pretty-format: 26.6.2 + dev: true /jest-diff@27.5.1: resolution: {integrity: sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==} @@ -13338,6 +13970,7 @@ packages: engines: {node: '>= 10.14.2'} dependencies: detect-newline: 3.1.0 + dev: true /jest-each@24.9.0: resolution: {integrity: sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==} @@ -13361,6 +13994,7 @@ packages: jest-get-type: 26.3.0 jest-util: 26.6.2 pretty-format: 26.6.2 + dev: true /jest-electron@0.1.12(jest@26.6.3): resolution: {integrity: sha512-10Hjr1kpyWz5cj9Xs/Xfb8yvF1LZPVmyEMHu/A/VhgvIV5yiP9uD8FHNc4HAfEnZHM+wvpR5YfveBTUAiNfwnA==} @@ -13416,6 +14050,7 @@ packages: - canvas - supports-color - utf-8-validate + dev: true /jest-environment-node@24.9.0: resolution: {integrity: sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==} @@ -13440,6 +14075,7 @@ packages: '@types/node': 20.8.9 jest-mock: 26.6.2 jest-util: 26.6.2 + dev: true /jest-extended@0.11.5: resolution: {integrity: sha512-3RsdFpLWKScpsLD6hJuyr/tV5iFOrw7v6YjA3tPdda9sJwoHwcMROws5gwiIZfcwhHlJRwFJB2OUvGmF3evV/Q==} @@ -13463,6 +14099,7 @@ packages: /jest-get-type@26.3.0: resolution: {integrity: sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==} engines: {node: '>= 10.14.2'} + dev: true /jest-get-type@27.5.1: resolution: {integrity: sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==} @@ -13511,6 +14148,7 @@ packages: fsevents: 2.3.3 transitivePeerDependencies: - supports-color + dev: true /jest-haste-map@29.7.0: resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} @@ -13585,6 +14223,7 @@ packages: - supports-color - ts-node - utf-8-validate + dev: true /jest-leak-detector@24.9.0: resolution: {integrity: sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA==} @@ -13600,6 +14239,7 @@ packages: dependencies: jest-get-type: 26.3.0 pretty-format: 26.6.2 + dev: true /jest-less-loader@0.2.0(less@4.2.0): resolution: {integrity: sha512-1UVS+j6O42jzhQG6baGNZZUMWtMNk+ZDQ3FYmk7xThfwcbvWbfOqFJ/n3e9gJI4A8yJTUz+5oEgPQkKNae4yCA==} @@ -13635,6 +14275,7 @@ packages: jest-diff: 26.6.2 jest-get-type: 26.3.0 pretty-format: 26.6.2 + dev: true /jest-matcher-utils@27.5.1: resolution: {integrity: sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==} @@ -13675,6 +14316,7 @@ packages: pretty-format: 26.6.2 slash: 3.0.0 stack-utils: 2.0.6 + dev: true /jest-mock@24.9.0: resolution: {integrity: sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==} @@ -13689,6 +14331,7 @@ packages: dependencies: '@jest/types': 26.6.2 '@types/node': 20.8.9 + dev: true /jest-pnp-resolver@1.2.3(jest-resolve@24.9.0): resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} @@ -13712,6 +14355,7 @@ packages: optional: true dependencies: jest-resolve: 26.6.2 + dev: true /jest-raw-loader@1.0.1: resolution: {integrity: sha512-g9oaAjeC4/rIJk1Wd3RxVbOfMizowM7LSjEJqa4R9qDX0OjQNABXOhH+GaznUp+DjTGVPi2vPPbQXyX87DOnYg==} @@ -13725,6 +14369,7 @@ packages: /jest-regex-util@26.0.0: resolution: {integrity: sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==} engines: {node: '>= 10.14.2'} + dev: true /jest-regex-util@29.6.3: resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} @@ -13740,6 +14385,7 @@ packages: jest-snapshot: 26.6.2 transitivePeerDependencies: - supports-color + dev: true /jest-resolve@24.9.0: resolution: {integrity: sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ==} @@ -13764,6 +14410,7 @@ packages: read-pkg-up: 7.0.1 resolve: 1.22.8 slash: 3.0.0 + dev: true /jest-runner@24.9.0: resolution: {integrity: sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==} @@ -13824,6 +14471,7 @@ packages: - supports-color - ts-node - utf-8-validate + dev: true /jest-runtime@24.9.0: resolution: {integrity: sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==} @@ -13897,6 +14545,7 @@ packages: - supports-color - ts-node - utf-8-validate + dev: true /jest-serializer@24.9.0: resolution: {integrity: sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==} @@ -13909,6 +14558,7 @@ packages: dependencies: '@types/node': 20.8.9 graceful-fs: 4.2.10 + dev: true /jest-snapshot@24.9.0: resolution: {integrity: sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew==} @@ -13953,6 +14603,7 @@ packages: semver: 7.5.4 transitivePeerDependencies: - supports-color + dev: true /jest-url-loader@0.1.0: resolution: {integrity: sha512-XbYYGUuejpKe7bnAwIcNqy3lBFn2Ml7dtBCnZk0qOgAOBgsU4/M0hcq5mgW+l9gRjT6QAH/2eCtRN135Y6pcKw==} @@ -13990,6 +14641,7 @@ packages: graceful-fs: 4.2.10 is-ci: 2.0.0 micromatch: 4.0.5 + dev: true /jest-util@29.7.0: resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} @@ -14025,6 +14677,7 @@ packages: jest-get-type: 26.3.0 leven: 3.1.0 pretty-format: 26.6.2 + dev: true /jest-watcher@26.6.2: resolution: {integrity: sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==} @@ -14037,6 +14690,7 @@ packages: chalk: 4.1.2 jest-util: 26.6.2 string-length: 4.0.2 + dev: true /jest-worker@24.9.0: resolution: {integrity: sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==} @@ -14053,6 +14707,7 @@ packages: '@types/node': 20.8.9 merge-stream: 2.0.0 supports-color: 7.2.0 + dev: true /jest-worker@29.4.3: resolution: {integrity: sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==} @@ -14088,6 +14743,7 @@ packages: - supports-color - ts-node - utf-8-validate + dev: true /jiti@1.21.0: resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} @@ -14234,6 +14890,7 @@ packages: - bufferutil - supports-color - utf-8-validate + dev: true /jsesc@0.5.0: resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} @@ -14257,6 +14914,7 @@ packages: /json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + dev: true /json-parse-better-errors@1.0.2: resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} @@ -14269,12 +14927,14 @@ packages: /json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + dev: true /json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} /json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true /json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} @@ -14371,6 +15031,7 @@ packages: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} dependencies: json-buffer: 3.0.1 + dev: true /kind-of@3.2.2: resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} @@ -14383,14 +15044,17 @@ packages: engines: {node: '>=0.10.0'} dependencies: is-buffer: 1.1.6 + dev: true /kind-of@5.1.0: resolution: {integrity: sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==} engines: {node: '>=0.10.0'} + dev: true /kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} + dev: true /klaw@3.0.0: resolution: {integrity: sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==} @@ -14401,6 +15065,7 @@ packages: /kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} + dev: true /kleur@4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} @@ -14408,6 +15073,7 @@ packages: /known-css-properties@0.29.0: resolution: {integrity: sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==} + dev: true /kolorist@1.6.0: resolution: {integrity: sha512-dLkz37Ab97HWMx9KTes3Tbi3D1ln9fCAy2zr2YVExJasDRPGRaKcoE4fycWNtnCAJfjFqe0cnY+f8KT2JePEXQ==} @@ -14492,6 +15158,7 @@ packages: /leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} + dev: true /levn@0.3.0: resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} @@ -14499,6 +15166,7 @@ packages: dependencies: prelude-ls: 1.1.2 type-check: 0.3.2 + dev: true /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} @@ -14506,6 +15174,7 @@ packages: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 + dev: true /lightningcss-darwin-arm64@1.19.0: resolution: {integrity: sha512-wIJmFtYX0rXHsXHSr4+sC5clwblEMji7HHQ4Ub1/CznVRxtCFha6JIt5JZaNf8vQrfdZnBxLLC6R8pC818jXqg==} @@ -14604,6 +15273,11 @@ packages: engines: {node: '>=10'} dev: true + /lilconfig@3.0.0: + resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==} + engines: {node: '>=14'} + dev: true + /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -14764,6 +15438,7 @@ packages: /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true /lodash.mergewith@4.6.2: resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} @@ -14787,6 +15462,7 @@ packages: /lodash.truncate@4.4.2: resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + dev: true /lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} @@ -14957,20 +15633,24 @@ packages: /map-cache@0.2.2: resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} engines: {node: '>=0.10.0'} + dev: true /map-obj@1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'} + dev: true /map-obj@4.3.0: resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} engines: {node: '>=8'} + dev: true /map-visit@1.0.0: resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==} engines: {node: '>=0.10.0'} dependencies: object-visit: 1.0.1 + dev: true /markdown-it-anchor@8.6.6(@types/markdown-it@12.2.3)(markdown-it@12.3.2): resolution: {integrity: sha512-jRW30YGywD2ESXDc+l17AiritL0uVaSnWsb26f+68qaW9zgbIIr1f4v2Nsvc0+s0Z2N3uX6t/yAw7BwCQ1wMsA==} @@ -15075,6 +15755,7 @@ packages: /mathml-tag-names@2.1.3: resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} + dev: true /md5.js@1.3.5: resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} @@ -15261,6 +15942,7 @@ packages: /mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + dev: true /mdn-data@2.0.4: resolution: {integrity: sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==} @@ -15302,6 +15984,7 @@ packages: trim-newlines: 4.1.1 type-fest: 1.4.0 yargs-parser: 20.2.9 + dev: true /meow@12.1.1: resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} @@ -15610,6 +16293,7 @@ packages: to-regex: 3.0.2 transitivePeerDependencies: - supports-color + dev: true /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} @@ -15693,6 +16377,7 @@ packages: /min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} + dev: true /minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -15734,6 +16419,7 @@ packages: arrify: 1.0.1 is-plain-obj: 1.1.0 kind-of: 6.0.3 + dev: true /minimist@1.2.7: resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} @@ -15783,6 +16469,7 @@ packages: dependencies: for-in: 1.0.2 is-extendable: 1.0.1 + dev: true /mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -15914,11 +16601,18 @@ packages: to-regex: 3.0.2 transitivePeerDependencies: - supports-color + dev: true /nanopop@2.2.0: resolution: {integrity: sha512-E9JaHcxh3ere8/BEZHAcnuD10RluTSPyTToBvoFWS9/7DcCx6gyKjbn7M7Bx7E1veCxCuY1iO6h4+gdAf1j73Q==} dev: true + /nanospinner@1.1.0: + resolution: {integrity: sha512-yFvNYMig4AthKYfHFl1sLj7B2nkHL4lzdig4osvl9/LdGbXwrdFRoqBS98gsEsOakr0yH+r5NZ/1Y9gdVB8trA==} + dependencies: + picocolors: 1.0.0 + dev: true + /napi-build-utils@1.0.2: resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} dev: true @@ -15929,6 +16623,7 @@ packages: /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true /needle@3.2.0: resolution: {integrity: sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==} @@ -15953,6 +16648,7 @@ packages: /nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} + dev: true /no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -16058,6 +16754,7 @@ packages: shellwords: 0.1.1 uuid: 8.3.2 which: 2.0.2 + dev: true optional: true /node-releases@2.0.13: @@ -16098,12 +16795,14 @@ packages: is-core-module: 2.13.0 semver: 7.5.4 validate-npm-package-license: 3.0.4 + dev: true /normalize-path@2.1.1: resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} engines: {node: '>=0.10.0'} dependencies: remove-trailing-separator: 1.1.0 + dev: true /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -16295,6 +16994,7 @@ packages: /nwsapi@2.2.2: resolution: {integrity: sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==} + dev: true /oauth-sign@0.9.0: resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} @@ -16310,6 +17010,7 @@ packages: copy-descriptor: 0.1.1 define-property: 0.2.5 kind-of: 3.2.2 + dev: true /object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} @@ -16338,6 +17039,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: isobject: 3.0.1 + dev: true /object.assign@4.1.4: resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} @@ -16420,6 +17122,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: isobject: 3.0.1 + dev: true /object.values@1.1.6: resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==} @@ -16512,6 +17215,7 @@ packages: prelude-ls: 1.1.2 type-check: 0.3.2 word-wrap: 1.2.3 + dev: true /optionator@0.9.3: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} @@ -16523,6 +17227,7 @@ packages: levn: 0.4.1 prelude-ls: 1.2.1 type-check: 0.4.0 + dev: true /ora@1.4.0: resolution: {integrity: sha512-iMK1DOQxzzh2MBlVsU42G80mnrvUhqsMh74phHtDlrcTZPK0pH6o7l7DRshK+0YsxDyEuaOkziVdvM3T0QTzpw==} @@ -16592,6 +17297,7 @@ packages: /p-each-series@2.2.0: resolution: {integrity: sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==} engines: {node: '>=8'} + dev: true /p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} @@ -16867,6 +17573,7 @@ packages: /pascalcase@0.1.1: resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==} engines: {node: '>=0.10.0'} + dev: true /path-browserify@0.0.1: resolution: {integrity: sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==} @@ -16932,6 +17639,11 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + /path-type@5.0.0: + resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} + engines: {node: '>=12'} + dev: true + /pbkdf2@3.1.2: resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} engines: {node: '>=0.12'} @@ -17083,6 +17795,7 @@ packages: /posix-character-classes@0.1.1: resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==} engines: {node: '>=0.10.0'} + dev: true /postcss-attribute-case-insensitive@5.0.2(postcss@8.4.31): resolution: {integrity: sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==} @@ -17346,6 +18059,7 @@ packages: postcss: ^8.3.5 dependencies: postcss: 8.4.31 + dev: true /postcss-load-config@3.1.4(postcss@8.4.31): resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} @@ -17756,6 +18470,7 @@ packages: /postcss-resolve-nested-selector@0.1.1: resolution: {integrity: sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==} + dev: true /postcss-safe-parser@6.0.0(postcss@8.4.31): resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} @@ -17764,6 +18479,7 @@ packages: postcss: ^8.3.3 dependencies: postcss: 8.4.31 + dev: true /postcss-selector-not@5.0.0(postcss@8.4.31): resolution: {integrity: sha512-/2K3A4TCP9orP4TNS7u3tGdRFVKqz/E6pX3aGnriPG0jU78of8wsUcqE4QAhWEU0d+WnMSF93Ah3F//vUtK+iQ==} @@ -17799,7 +18515,7 @@ packages: svgo: 2.8.0 dev: true - /postcss-syntax@0.36.2(postcss-less@6.0.0)(postcss@8.4.31): + /postcss-syntax@0.36.2(postcss@8.4.31): resolution: {integrity: sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==} peerDependencies: postcss: '>=5.0.0' @@ -17821,7 +18537,6 @@ packages: optional: true dependencies: postcss: 8.4.31 - postcss-less: 6.0.0(postcss@8.4.31) dev: false /postcss-unique-selectors@5.1.1(postcss@8.4.31): @@ -17870,10 +18585,12 @@ packages: /prelude-ls@1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} engines: {node: '>= 0.8.0'} + dev: true /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + dev: true /prepend-http@1.0.4: resolution: {integrity: sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==} @@ -17897,7 +18614,7 @@ packages: fast-diff: 1.2.0 dev: true - /prettier-plugin-organize-imports@3.2.4(prettier@3.0.3)(typescript@5.2.2): + /prettier-plugin-organize-imports@3.2.4: resolution: {integrity: sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog==} peerDependencies: '@volar/vue-language-plugin-pug': ^1.0.4 @@ -17909,12 +18626,9 @@ packages: optional: true '@volar/vue-typescript': optional: true - dependencies: - prettier: 3.0.3 - typescript: 5.2.2 dev: false - /prettier-plugin-packagejson@2.4.3(prettier@3.0.3): + /prettier-plugin-packagejson@2.4.3: resolution: {integrity: sha512-kPeeviJiwy0BgOSk7No8NmzzXfW4R9FYWni6ziA5zc1kGVVrKnBzMZdu2TUhI+I7h8/5Htt3vARYOk7KKJTTNQ==} peerDependencies: prettier: '>= 1.16.0' @@ -17922,7 +18636,6 @@ packages: prettier: optional: true dependencies: - prettier: 3.0.3 sort-package-json: 2.4.1 synckit: 0.8.5 dev: false @@ -17937,6 +18650,7 @@ packages: resolution: {integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==} engines: {node: '>=14'} hasBin: true + dev: true /pretty-error@4.0.0: resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==} @@ -17970,6 +18684,7 @@ packages: ansi-regex: 5.0.1 ansi-styles: 4.3.0 react-is: 17.0.2 + dev: true /pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} @@ -18048,6 +18763,7 @@ packages: dependencies: kleur: 3.0.3 sisteransi: 1.0.5 + dev: true /prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -18156,6 +18872,7 @@ packages: dependencies: end-of-stream: 1.4.4 once: 1.4.0 + dev: true /pumpify@1.5.1: resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==} @@ -18177,12 +18894,19 @@ packages: resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + /qrcode.react@3.1.0: + resolution: {integrity: sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dev: false + /qrcode.react@3.1.0(react@18.2.0): resolution: {integrity: sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: react: 18.2.0 + dev: true /qs@6.11.2: resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==} @@ -18212,6 +18936,7 @@ packages: /querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + dev: true /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -18234,6 +18959,7 @@ packages: /quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} + dev: true /quickselect@2.0.0: resolution: {integrity: sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==} @@ -18301,6 +19027,20 @@ packages: resize-observer-polyfill: 1.5.1 dev: false + /rc-cascader@3.20.0: + resolution: {integrity: sha512-lkT9EEwOcYdjZ/jvhLoXGzprK1sijT3/Tp4BLxQQcHDZkkOzzwYQC9HgmKoJz0K7CukMfgvO9KqHeBdgE+pELw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + array-tree-filter: 2.1.0 + classnames: 2.3.2 + rc-select: 14.10.0 + rc-tree: 5.8.2 + rc-util: 5.38.1 + dev: false + /rc-cascader@3.20.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-lkT9EEwOcYdjZ/jvhLoXGzprK1sijT3/Tp4BLxQQcHDZkkOzzwYQC9HgmKoJz0K7CukMfgvO9KqHeBdgE+pELw==} peerDependencies: @@ -18315,6 +19055,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-cascader@3.7.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-SFtGpwmYN7RaWEAGTS4Rkc62ZV/qmQGg/tajr/7mfIkleuu8ro9Hlk6J+aA0x1YS4zlaZBtTcSaXM01QMiEV/A==} @@ -18345,6 +19086,17 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-checkbox@3.1.0: + resolution: {integrity: sha512-PAwpJFnBa3Ei+5pyqMMXdcKYKNBMS+TvSDiLdDnARnMJHC8ESxwPfm4Ao1gJiKtWLdmGfigascnCpwrHFgoOBQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-util: 5.38.1 + dev: false + /rc-checkbox@3.1.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-PAwpJFnBa3Ei+5pyqMMXdcKYKNBMS+TvSDiLdDnARnMJHC8ESxwPfm4Ao1gJiKtWLdmGfigascnCpwrHFgoOBQ==} peerDependencies: @@ -18356,6 +19108,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-collapse@3.4.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-jpTwLgJzkhAgp2Wpi3xmbTbbYExg6fkptL67Uu5LCRVEj6wqmy0DHTjjeynsjOLsppHGHu41t1ELntZ0lEvS/Q==} @@ -18372,6 +19125,18 @@ packages: shallowequal: 1.1.0 dev: false + /rc-collapse@3.7.2: + resolution: {integrity: sha512-ZRw6ipDyOnfLFySxAiCMdbHtb5ePAsB9mT17PA6y1mRD/W6KHRaZeb5qK/X9xDV1CqgyxMpzw0VdS74PCcUk4A==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-motion: 2.9.0 + rc-util: 5.38.1 + dev: false + /rc-collapse@3.7.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-ZRw6ipDyOnfLFySxAiCMdbHtb5ePAsB9mT17PA6y1mRD/W6KHRaZeb5qK/X9xDV1CqgyxMpzw0VdS74PCcUk4A==} peerDependencies: @@ -18384,6 +19149,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-dialog@9.0.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-s3U+24xWUuB6Bn2Lk/Qt6rufy+uT+QvWkiFhNBcO9APLxcFFczWamaq7x9h8SCuhfc1nHcW4y8NbMsnAjNnWyg==} @@ -18400,6 +19166,19 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-dialog@9.3.4: + resolution: {integrity: sha512-975X3018GhR+EjZFbxA2Z57SX5rnu0G0/OxFgMMvZK4/hQWEm3MHaNvP4wXpxYDoJsp+xUvVW+GB9CMMCm81jA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + '@rc-component/portal': 1.1.2 + classnames: 2.3.2 + rc-motion: 2.9.0 + rc-util: 5.38.1 + dev: false + /rc-dialog@9.3.4(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-975X3018GhR+EjZFbxA2Z57SX5rnu0G0/OxFgMMvZK4/hQWEm3MHaNvP4wXpxYDoJsp+xUvVW+GB9CMMCm81jA==} peerDependencies: @@ -18413,6 +19192,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-drawer@4.4.3(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-FYztwRs3uXnFOIf1hLvFxIQP9MiZJA+0w+Os8dfDh/90X7z/HqP/Yg+noLCIeHEbKln1Tqelv8ymCAN24zPcfQ==} @@ -18442,6 +19222,19 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-drawer@6.5.2: + resolution: {integrity: sha512-QckxAnQNdhh4vtmKN0ZwDf3iakO83W9eZcSKWYYTDv4qcD2fHhRAZJJ/OE6v2ZlQ2kSqCJX5gYssF4HJFvsEPQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + '@rc-component/portal': 1.1.2 + classnames: 2.3.2 + rc-motion: 2.9.0 + rc-util: 5.38.1 + dev: false + /rc-drawer@6.5.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-QckxAnQNdhh4vtmKN0ZwDf3iakO83W9eZcSKWYYTDv4qcD2fHhRAZJJ/OE6v2ZlQ2kSqCJX5gYssF4HJFvsEPQ==} peerDependencies: @@ -18455,6 +19248,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-dropdown@4.0.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-OdpXuOcme1rm45cR0Jzgfl1otzmU4vuBVb+etXM8vcaULGokAKVpKlw8p6xzspG7jGd/XxShvq+N3VNEfk/l5g==} @@ -18470,6 +19264,18 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-dropdown@4.1.0: + resolution: {integrity: sha512-VZjMunpBdlVzYpEdJSaV7WM7O0jf8uyDjirxXLZRNZ+tAC+NzD3PXPEtliFwGzVwBBdCmGuSqiS9DWcOLxQ9tw==} + peerDependencies: + react: '>=16.11.0' + react-dom: '>=16.11.0' + dependencies: + '@babel/runtime': 7.23.5 + '@rc-component/trigger': 1.18.2 + classnames: 2.3.2 + rc-util: 5.38.1 + dev: false + /rc-dropdown@4.1.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-VZjMunpBdlVzYpEdJSaV7WM7O0jf8uyDjirxXLZRNZ+tAC+NzD3PXPEtliFwGzVwBBdCmGuSqiS9DWcOLxQ9tw==} peerDependencies: @@ -18482,6 +19288,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-field-form@1.34.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-BdciU5C7dBO51/9ZKcMvK2f8zaaO12Lt1eBhlAo8nNv+6htlNcgY9DAkUlZ7gfyWjnCc1Oo4hHIXau1m6tLw1A==} @@ -18497,6 +19304,18 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-field-form@1.41.0: + resolution: {integrity: sha512-k9AS0wmxfJfusWDP/YXWTpteDNaQ4isJx9UKxx4/e8Dub4spFeZ54/EuN2sYrMRID/+hUznPgVZeg+Gf7XSYCw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + async-validator: 4.2.5 + rc-util: 5.38.1 + dev: false + /rc-field-form@1.41.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-k9AS0wmxfJfusWDP/YXWTpteDNaQ4isJx9UKxx4/e8Dub4spFeZ54/EuN2sYrMRID/+hUznPgVZeg+Gf7XSYCw==} engines: {node: '>=8.x'} @@ -18509,6 +19328,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-footer@0.6.8(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-JBZ+xcb6kkex8XnBd4VHw1ZxjV6kmcwUumSHaIFdka2qzMCo7Klcy4sI6G0XtUpG/vtpislQCc+S9Bc+NLHYMg==} @@ -18538,6 +19358,20 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-image@7.5.1: + resolution: {integrity: sha512-Z9loECh92SQp0nSipc0MBuf5+yVC05H/pzC+Nf8xw1BKDFUJzUeehYBjaWlxly8VGBZJcTHYri61Fz9ng1G3Ag==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + '@rc-component/portal': 1.1.2 + classnames: 2.3.2 + rc-dialog: 9.3.4 + rc-motion: 2.9.0 + rc-util: 5.38.1 + dev: false + /rc-image@7.5.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-Z9loECh92SQp0nSipc0MBuf5+yVC05H/pzC+Nf8xw1BKDFUJzUeehYBjaWlxly8VGBZJcTHYri61Fz9ng1G3Ag==} peerDependencies: @@ -18552,6 +19386,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-input-number@7.3.11(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-aMWPEjFeles6PQnMqP5eWpxzsvHm9rh1jQOWXExUEIxhX62Fyl/ptifLHOn17+waDG1T/YUb6flfJbvwRhHrbA==} @@ -18566,6 +19401,19 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-input-number@8.4.0: + resolution: {integrity: sha512-B6rziPOLRmeP7kcS5qbdC5hXvvDHYKV4vUxmahevYx2E6crS2bRi0xLDjhJ0E1HtOWo8rTmaE2EBJAkTCZOLdA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + '@rc-component/mini-decimal': 1.1.0 + classnames: 2.3.2 + rc-input: 1.3.6 + rc-util: 5.38.1 + dev: false + /rc-input-number@8.4.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-B6rziPOLRmeP7kcS5qbdC5hXvvDHYKV4vUxmahevYx2E6crS2bRi0xLDjhJ0E1HtOWo8rTmaE2EBJAkTCZOLdA==} peerDependencies: @@ -18579,6 +19427,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-input@0.1.4(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-FqDdNz+fV2dKNgfXzcSLKvC+jEs1709t7nD+WdfjrdSaOcefpgc7BUJYadc3usaING+b7ediMTfKxuJBsEFbXA==} @@ -18593,6 +19442,17 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-input@1.3.6: + resolution: {integrity: sha512-/HjTaKi8/Ts4zNbYaB5oWCquxFyFQO4Co1MnMgoCeGJlpe7k8Eir2HN0a0F9IHDmmo+GYiGgPpz7w/d/krzsJA==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-util: 5.38.1 + dev: false + /rc-input@1.3.6(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-/HjTaKi8/Ts4zNbYaB5oWCquxFyFQO4Co1MnMgoCeGJlpe7k8Eir2HN0a0F9IHDmmo+GYiGgPpz7w/d/krzsJA==} peerDependencies: @@ -18604,6 +19464,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-mentions@1.13.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-FCkaWw6JQygtOz0+Vxz/M/NWqrWHB9LwqlY2RtcuFqWJNFK9njijOOzTSsBGANliGufVUzx/xuPHmZPBV0+Hgw==} @@ -18621,6 +19482,21 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-mentions@2.9.1: + resolution: {integrity: sha512-cZuElWr/5Ws0PXx1uxobxfYh4mqUw2FitfabR62YnWgm+WAfDyXZXqZg5DxXW+M1cgVvntrQgDDd9LrihrXzew==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + '@rc-component/trigger': 1.18.2 + classnames: 2.3.2 + rc-input: 1.3.6 + rc-menu: 9.12.4 + rc-textarea: 1.5.3 + rc-util: 5.38.1 + dev: false + /rc-mentions@2.9.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-cZuElWr/5Ws0PXx1uxobxfYh4mqUw2FitfabR62YnWgm+WAfDyXZXqZg5DxXW+M1cgVvntrQgDDd9LrihrXzew==} peerDependencies: @@ -18636,6 +19512,21 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true + + /rc-menu@9.12.4: + resolution: {integrity: sha512-t2NcvPLV1mFJzw4F21ojOoRVofK2rWhpKPx69q2raUsiHPDP6DDevsBILEYdsIegqBeSXoWs2bf6CueBKg3BFg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + '@rc-component/trigger': 1.18.2 + classnames: 2.3.2 + rc-motion: 2.9.0 + rc-overflow: 1.3.2 + rc-util: 5.38.1 + dev: false /rc-menu@9.12.4(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-t2NcvPLV1mFJzw4F21ojOoRVofK2rWhpKPx69q2raUsiHPDP6DDevsBILEYdsIegqBeSXoWs2bf6CueBKg3BFg==} @@ -18651,6 +19542,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-menu@9.8.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-179weouypfjWJSRvvoo/vPy+StojsMzK2XC5jRNhL1ryt/N/8wAFESte8K6jZJkNp9DHDLFTe+dCGmikKpiFuA==} @@ -18669,6 +19561,17 @@ packages: shallowequal: 1.1.0 dev: false + /rc-motion@2.9.0: + resolution: {integrity: sha512-XIU2+xLkdIr1/h6ohPZXyPBMvOmuyFZQ/T0xnawz+Rh+gh4FINcnZmMT5UTIj6hgI0VLDjTaPeRd+smJeSPqiQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-util: 5.38.1 + dev: false + /rc-motion@2.9.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-XIU2+xLkdIr1/h6ohPZXyPBMvOmuyFZQ/T0xnawz+Rh+gh4FINcnZmMT5UTIj6hgI0VLDjTaPeRd+smJeSPqiQ==} peerDependencies: @@ -18696,6 +19599,19 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-notification@5.3.0: + resolution: {integrity: sha512-WCf0uCOkZ3HGfF0p1H4Sgt7aWfipxORWTPp7o6prA3vxwtWhtug3GfpYls1pnBp4WA+j8vGIi5c2/hQRpGzPcQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-motion: 2.9.0 + rc-util: 5.38.1 + dev: false + /rc-notification@5.3.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-WCf0uCOkZ3HGfF0p1H4Sgt7aWfipxORWTPp7o6prA3vxwtWhtug3GfpYls1pnBp4WA+j8vGIi5c2/hQRpGzPcQ==} engines: {node: '>=8.x'} @@ -18709,6 +19625,19 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true + + /rc-overflow@1.3.2: + resolution: {integrity: sha512-nsUm78jkYAoPygDAcGZeC2VwIg/IBGSodtOY3pMof4W3M9qRJgqaDYm03ZayHlde3I6ipliAxbN0RUcGf5KOzw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-resize-observer: 1.4.0 + rc-util: 5.38.1 + dev: false /rc-overflow@1.3.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-nsUm78jkYAoPygDAcGZeC2VwIg/IBGSodtOY3pMof4W3M9qRJgqaDYm03ZayHlde3I6ipliAxbN0RUcGf5KOzw==} @@ -18735,6 +19664,17 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-pagination@4.0.3: + resolution: {integrity: sha512-s1MNwyU83AgycikYckRdpmkN+4ZZuul0E0YdDp7dMgcjg/d2fak767ZIbLP4Q5YPPla7NDorfVFTvGQAPj6jXA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-util: 5.38.1 + dev: false + /rc-pagination@4.0.3(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-s1MNwyU83AgycikYckRdpmkN+4ZZuul0E0YdDp7dMgcjg/d2fak767ZIbLP4Q5YPPla7NDorfVFTvGQAPj6jXA==} peerDependencies: @@ -18746,6 +19686,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-picker@2.7.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-oZH6FZ3j4iuBxHB4NvQ6ABRsS2If/Kpty1YFFsji7/aej6ruGmfM7WnJWQ88AoPfpJ++ya5z+nVEA8yCRYGKyw==} @@ -18766,6 +19707,33 @@ packages: shallowequal: 1.1.0 dev: false + /rc-picker@3.14.6(dayjs@1.11.7): + resolution: {integrity: sha512-AdKKW0AqMwZsKvIpwUWDUnpuGKZVrbxVTZTNjcO+pViGkjC1EBcjMgxVe8tomOEaIHJL5Gd13vS8Rr3zzxWmag==} + engines: {node: '>=8.x'} + peerDependencies: + date-fns: '>= 2.x' + dayjs: '>= 1.x' + luxon: '>= 3.x' + moment: '>= 2.x' + react: '>=16.9.0' + react-dom: '>=16.9.0' + peerDependenciesMeta: + date-fns: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + dependencies: + '@babel/runtime': 7.23.5 + '@rc-component/trigger': 1.18.2 + classnames: 2.3.2 + dayjs: 1.11.7 + rc-util: 5.38.1 + dev: false + /rc-picker@3.14.6(dayjs@1.11.7)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-AdKKW0AqMwZsKvIpwUWDUnpuGKZVrbxVTZTNjcO+pViGkjC1EBcjMgxVe8tomOEaIHJL5Gd13vS8Rr3zzxWmag==} engines: {node: '>=8.x'} @@ -18793,6 +19761,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-progress@3.4.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-eAFDHXlk8aWpoXl0llrenPMt9qKHQXphxcVsnKs0FHC6eCSk1ebJtyaVjJUzKe0233ogiLDeEFK1Uihz3s67hw==} @@ -18807,6 +19776,17 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-progress@3.5.1: + resolution: {integrity: sha512-V6Amx6SbLRwPin/oD+k1vbPrO8+9Qf8zW1T8A7o83HdNafEVvAxPV5YsgtKFP+Ud5HghLj33zKOcEHrcrUGkfw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-util: 5.38.1 + dev: false + /rc-progress@3.5.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-V6Amx6SbLRwPin/oD+k1vbPrO8+9Qf8zW1T8A7o83HdNafEVvAxPV5YsgtKFP+Ud5HghLj33zKOcEHrcrUGkfw==} peerDependencies: @@ -18818,6 +19798,19 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true + + /rc-rate@2.12.0: + resolution: {integrity: sha512-g092v5iZCdVzbjdn28FzvWebK2IutoVoiTeqoLTj9WM7SjA/gOJIw5/JFZMRyJYYVe1jLAU2UhAfstIpCNRozg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-util: 5.38.1 + dev: false /rc-rate@2.12.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-g092v5iZCdVzbjdn28FzvWebK2IutoVoiTeqoLTj9WM7SjA/gOJIw5/JFZMRyJYYVe1jLAU2UhAfstIpCNRozg==} @@ -18831,6 +19824,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-rate@2.9.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-SaiZFyN8pe0Fgphv8t3+kidlej+cq/EALkAJAc3A0w0XcPaH2L1aggM8bhe1u6GAGuQNAoFvTLjw4qLPGRKV5g==} @@ -18846,6 +19840,18 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-resize-observer@1.4.0: + resolution: {integrity: sha512-PnMVyRid9JLxFavTjeDXEXo65HCRqbmLBw9xX9gfC4BZiSzbLXKzW3jPz+J0P71pLbD5tBMTT+mkstV5gD0c9Q==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-util: 5.38.1 + resize-observer-polyfill: 1.5.1 + dev: false + /rc-resize-observer@1.4.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-PnMVyRid9JLxFavTjeDXEXo65HCRqbmLBw9xX9gfC4BZiSzbLXKzW3jPz+J0P71pLbD5tBMTT+mkstV5gD0c9Q==} peerDependencies: @@ -18873,6 +19879,18 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-segmented@2.2.2: + resolution: {integrity: sha512-Mq52M96QdHMsNdE/042ibT5vkcGcD5jxKp7HgPC2SRofpia99P5fkfHy1pEaajLMF/kj0+2Lkq1UZRvqzo9mSA==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-motion: 2.9.0 + rc-util: 5.38.1 + dev: false + /rc-segmented@2.2.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-Mq52M96QdHMsNdE/042ibT5vkcGcD5jxKp7HgPC2SRofpia99P5fkfHy1pEaajLMF/kj0+2Lkq1UZRvqzo9mSA==} peerDependencies: @@ -18885,6 +19903,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-select@14.1.18(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-4JgY3oG2Yz68ECMUSCON7mtxuJvCSj+LJpHEg/AONaaVBxIIrmI/ZTuMJkyojall/X50YdBe5oMKqHHPNiPzEg==} @@ -18904,6 +19923,22 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-select@14.10.0: + resolution: {integrity: sha512-TsIJTYafTTapCA32LLNpx/AD6ntepR1TG8jEVx35NiAAWCPymhUfuca8kRcUNd3WIGVMDcMKn9kkphoxEz+6Ag==} + engines: {node: '>=8.x'} + peerDependencies: + react: '*' + react-dom: '*' + dependencies: + '@babel/runtime': 7.23.5 + '@rc-component/trigger': 1.18.2 + classnames: 2.3.2 + rc-motion: 2.9.0 + rc-overflow: 1.3.2 + rc-util: 5.38.1 + rc-virtual-list: 3.11.3 + dev: false + /rc-select@14.10.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-TsIJTYafTTapCA32LLNpx/AD6ntepR1TG8jEVx35NiAAWCPymhUfuca8kRcUNd3WIGVMDcMKn9kkphoxEz+6Ag==} engines: {node: '>=8.x'} @@ -18920,6 +19955,7 @@ packages: rc-virtual-list: 3.11.3(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-slider@10.0.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-igTKF3zBet7oS/3yNiIlmU8KnZ45npmrmHlUUio8PNbIhzMcsh+oE/r2UD42Y6YD2D/s+kzCQkzQrPD6RY435Q==} @@ -18936,6 +19972,18 @@ packages: shallowequal: 1.1.0 dev: false + /rc-slider@10.5.0: + resolution: {integrity: sha512-xiYght50cvoODZYI43v3Ylsqiw14+D7ELsgzR40boDZaya1HFa1Etnv9MDkQE8X/UrXAffwv2AcNAhslgYuDTw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-util: 5.38.1 + dev: false + /rc-slider@10.5.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-xiYght50cvoODZYI43v3Ylsqiw14+D7ELsgzR40boDZaya1HFa1Etnv9MDkQE8X/UrXAffwv2AcNAhslgYuDTw==} engines: {node: '>=8.x'} @@ -18948,6 +19996,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-steps@5.0.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-9TgRvnVYirdhbV0C3syJFj9EhCRqoJAsxt4i1rED5o8/ZcSv5TLIYyo4H8MCjLPvbe2R+oBAm/IYBEtC+OS1Rw==} @@ -18963,6 +20012,18 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-steps@6.0.1: + resolution: {integrity: sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-util: 5.38.1 + dev: false + /rc-steps@6.0.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==} engines: {node: '>=8.x'} @@ -18975,6 +20036,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-switch@3.2.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-+gUJClsZZzvAHGy1vZfnwySxj+MjLlGRyXKXScrtCTcmiYNPzxDFOxdQ/3pK1Kt/0POvwJ/6ALOR8gwdXGhs+A==} @@ -18989,6 +20051,17 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-switch@4.1.0: + resolution: {integrity: sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-util: 5.38.1 + dev: false + /rc-switch@4.1.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==} peerDependencies: @@ -19000,21 +20073,37 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true + + /rc-table@7.26.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-0cD8e6S+DTGAt5nBZQIPFYEaIukn17sfa5uFL98faHlH/whZzD8ii3dbFL4wmUDEL4BLybhYop+QUfZJ4CPvNQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + shallowequal: 1.1.0 + dev: false - /rc-table@7.26.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-0cD8e6S+DTGAt5nBZQIPFYEaIukn17sfa5uFL98faHlH/whZzD8ii3dbFL4wmUDEL4BLybhYop+QUfZJ4CPvNQ==} + /rc-table@7.36.0: + resolution: {integrity: sha512-3xVcdCC5OLeOOhaCg+5Lps2oPreM/GWXmUXWTSX4p6vF7F76ABM4dfPpMJ9Dnf5yGRyh+8pe7FRyhRVnWw2H/w==} engines: {node: '>=8.x'} peerDependencies: react: '>=16.9.0' react-dom: '>=16.9.0' dependencies: '@babel/runtime': 7.23.5 + '@rc-component/context': 1.4.0 classnames: 2.3.2 - rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - shallowequal: 1.1.0 + rc-resize-observer: 1.4.0 + rc-util: 5.38.1 + rc-virtual-list: 3.11.3 dev: false /rc-table@7.36.0(react-dom@18.2.0)(react@18.2.0): @@ -19032,6 +20121,23 @@ packages: rc-virtual-list: 3.11.3(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true + + /rc-tabs@12.14.1: + resolution: {integrity: sha512-1xlE7JQNYxD5RwBsM7jf2xSdUrkmTSDFLFEm2gqAgnsRlOGydEzXXNAVTOT6QcgM1G/gCm+AgG+FYPUGb4Hs4g==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-dropdown: 4.1.0 + rc-menu: 9.12.4 + rc-motion: 2.9.0 + rc-resize-observer: 1.4.0 + rc-util: 5.38.1 + dev: false /rc-tabs@12.14.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-1xlE7JQNYxD5RwBsM7jf2xSdUrkmTSDFLFEm2gqAgnsRlOGydEzXXNAVTOT6QcgM1G/gCm+AgG+FYPUGb4Hs4g==} @@ -19049,6 +20155,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-tabs@12.5.6(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-aArXHzxK7YICxe+622CZ8FlO5coMi8P7E6tXpseCPKm1gdTjUt0LrQK1/AxcrRXZXG3K4QqhlKmET0+cX5DQaQ==} @@ -19083,6 +20190,19 @@ packages: shallowequal: 1.1.0 dev: false + /rc-textarea@1.5.3: + resolution: {integrity: sha512-oH682ghHx++stFNYrosPRBfwsypywrTXpaD0/5Z8MPkUOnyOQUaY9ueL9tMu6BP1LfsuYQ1VLpg5OtshViLNgA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-input: 1.3.6 + rc-resize-observer: 1.4.0 + rc-util: 5.38.1 + dev: false + /rc-textarea@1.5.3(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-oH682ghHx++stFNYrosPRBfwsypywrTXpaD0/5Z8MPkUOnyOQUaY9ueL9tMu6BP1LfsuYQ1VLpg5OtshViLNgA==} peerDependencies: @@ -19096,6 +20216,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-tooltip@5.2.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-jtQzU/18S6EI3lhSGoDYhPqNpWajMtS5VV/ld1LwyfrDByQpYmw/LW6U7oFXXLukjfDHQ7Ju705A82PRNFWYhg==} @@ -19110,6 +20231,17 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-tooltip@6.1.2: + resolution: {integrity: sha512-89zwvybvCxGJu3+gGF8w5AXd4HHk6hIN7K0vZbkzjilVaEAIWPqc1fcyeUeP71n3VCcw7pTL9LyFupFbrx8gHw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + '@rc-component/trigger': 1.18.2 + classnames: 2.3.2 + dev: false + /rc-tooltip@6.1.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-89zwvybvCxGJu3+gGF8w5AXd4HHk6hIN7K0vZbkzjilVaEAIWPqc1fcyeUeP71n3VCcw7pTL9LyFupFbrx8gHw==} peerDependencies: @@ -19121,6 +20253,20 @@ packages: classnames: 2.3.2 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true + + /rc-tree-select@5.15.0: + resolution: {integrity: sha512-YJHfdO6azFnR0/JuNBZLDptGE4/RGfVeHAafUIYcm2T3RBkL1O8aVqiHvwIyLzdK59ry0NLrByd+3TkfpRM+9Q==} + peerDependencies: + react: '*' + react-dom: '*' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-select: 14.10.0 + rc-tree: 5.8.2 + rc-util: 5.38.1 + dev: false /rc-tree-select@5.15.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-YJHfdO6azFnR0/JuNBZLDptGE4/RGfVeHAafUIYcm2T3RBkL1O8aVqiHvwIyLzdK59ry0NLrByd+3TkfpRM+9Q==} @@ -19135,6 +20281,7 @@ packages: rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-tree-select@5.5.5(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-k2av7jF6tW9bIO4mQhaVdV4kJ1c54oxV3/hHVU+oD251Gb5JN+m1RbJFTMf1o0rAFqkvto33rxMdpafaGKQRJw==} @@ -19167,6 +20314,20 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-tree@5.8.2: + resolution: {integrity: sha512-xH/fcgLHWTLmrSuNphU8XAqV7CdaOQgm4KywlLGNoTMhDAcNR3GVNP6cZzb0GrKmIZ9yae+QLot/cAgUdPRMzg==} + engines: {node: '>=10.x'} + peerDependencies: + react: '*' + react-dom: '*' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-motion: 2.9.0 + rc-util: 5.38.1 + rc-virtual-list: 3.11.3 + dev: false + /rc-tree@5.8.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-xH/fcgLHWTLmrSuNphU8XAqV7CdaOQgm4KywlLGNoTMhDAcNR3GVNP6cZzb0GrKmIZ9yae+QLot/cAgUdPRMzg==} engines: {node: '>=10.x'} @@ -19181,6 +20342,7 @@ packages: rc-virtual-list: 3.11.3(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /rc-trigger@5.3.4(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-mQv+vas0TwKcjAO2izNPkqR4j86OemLRmvL2nOzdP9OWNWA1ivoTt5hzFqYNW9zACwmTezRiN8bttrC7cZzYSw==} @@ -19198,6 +20360,17 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /rc-upload@4.3.5: + resolution: {integrity: sha512-EHlKJbhkgFSQHliTj9v/2K5aEuFwfUQgZARzD7AmAPOneZEPiCNF3n6PEWIuqz9h7oq6FuXgdR67sC5BWFxJbA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-util: 5.38.1 + dev: false + /rc-upload@4.3.5(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-EHlKJbhkgFSQHliTj9v/2K5aEuFwfUQgZARzD7AmAPOneZEPiCNF3n6PEWIuqz9h7oq6FuXgdR67sC5BWFxJbA==} peerDependencies: @@ -19210,6 +20383,16 @@ packages: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + /rc-util@5.38.1: + resolution: {integrity: sha512-e4ZMs7q9XqwTuhIK7zBIVFltUtMSjphuPPQXHoHlzRzNdOwUxDejo0Zls5HYaJfRKNURcsS/ceKVULlhjBrxng==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.23.5 + react-is: 18.2.0 + dev: false + /rc-util@5.38.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-e4ZMs7q9XqwTuhIK7zBIVFltUtMSjphuPPQXHoHlzRzNdOwUxDejo0Zls5HYaJfRKNURcsS/ceKVULlhjBrxng==} peerDependencies: @@ -19221,6 +20404,19 @@ packages: react-dom: 18.2.0(react@18.2.0) react-is: 18.2.0 + /rc-virtual-list@3.11.3: + resolution: {integrity: sha512-tu5UtrMk/AXonHwHxUogdXAWynaXsrx1i6dsgg+lOo/KJSF8oBAcprh1z5J3xgnPJD5hXxTL58F8s8onokdt0Q==} + engines: {node: '>=8.x'} + peerDependencies: + react: '*' + react-dom: '*' + dependencies: + '@babel/runtime': 7.23.5 + classnames: 2.3.2 + rc-resize-observer: 1.4.0 + rc-util: 5.38.1 + dev: false + /rc-virtual-list@3.11.3(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-tu5UtrMk/AXonHwHxUogdXAWynaXsrx1i6dsgg+lOo/KJSF8oBAcprh1z5J3xgnPJD5hXxTL58F8s8onokdt0Q==} engines: {node: '>=8.x'} @@ -19263,6 +20459,20 @@ packages: - react-native dev: false + /react-color@2.19.3: + resolution: {integrity: sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==} + peerDependencies: + react: '*' + dependencies: + '@icons/material': 0.2.4 + lodash: 4.17.21 + lodash-es: 4.17.21 + material-colors: 1.2.6 + prop-types: 15.8.1 + reactcss: 1.2.3 + tinycolor2: 1.6.0 + dev: false + /react-color@2.19.3(react@18.2.0): resolution: {integrity: sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==} peerDependencies: @@ -19276,6 +20486,7 @@ packages: react: 18.2.0 reactcss: 1.2.3(react@18.2.0) tinycolor2: 1.6.0 + dev: true /react-copy-to-clipboard@5.1.0: resolution: {integrity: sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==} @@ -19336,6 +20547,19 @@ packages: prop-types: 15.8.1 dev: false + /react-helmet-async@1.3.0: + resolution: {integrity: sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==} + peerDependencies: + react: ^16.6.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@babel/runtime': 7.23.5 + invariant: 2.2.4 + prop-types: 15.8.1 + react-fast-compare: 3.2.0 + shallowequal: 1.1.0 + dev: false + /react-helmet-async@1.3.0(react-dom@18.1.0)(react@18.1.0): resolution: {integrity: sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==} peerDependencies: @@ -19363,7 +20587,7 @@ packages: react-side-effect: 2.1.2(react@18.2.0) dev: false - /react-intl@6.5.5(typescript@5.2.2): + /react-intl@6.5.5: resolution: {integrity: sha512-cI5UKvBh4tc1zxLIziHBYGMX3dhYWDEFlvUDVN6NfT2i96zTXz/zH2AmM8+2waqgOhwkFUzd+7kK1G9q7fiC2g==} peerDependencies: react: ^16.6.0 || 17 || 18 @@ -19374,7 +20598,7 @@ packages: dependencies: '@formatjs/ecma402-abstract': 1.18.0 '@formatjs/icu-messageformat-parser': 2.7.3 - '@formatjs/intl': 2.9.9(typescript@5.2.2) + '@formatjs/intl': 2.9.9 '@formatjs/intl-displaynames': 6.6.4 '@formatjs/intl-listformat': 7.5.3 '@types/hoist-non-react-statics': 3.3.1 @@ -19382,7 +20606,6 @@ packages: hoist-non-react-statics: 3.3.2 intl-messageformat: 10.5.8 tslib: 2.6.2 - typescript: 5.2.2 dev: false /react-is@16.13.1: @@ -19428,6 +20651,16 @@ packages: resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} engines: {node: '>=0.10.0'} + /react-router-dom@6.3.0: + resolution: {integrity: sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + dependencies: + history: 5.3.0 + react-router: 6.3.0 + dev: false + /react-router-dom@6.3.0(react-dom@18.1.0)(react@18.1.0): resolution: {integrity: sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==} peerDependencies: @@ -19453,6 +20686,14 @@ packages: react-router: 6.6.1(react@18.2.0) dev: false + /react-router@6.3.0: + resolution: {integrity: sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==} + peerDependencies: + react: '>=16.8' + dependencies: + history: 5.3.0 + dev: false + /react-router@6.3.0(react@18.1.0): resolution: {integrity: sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==} peerDependencies: @@ -19561,6 +20802,14 @@ packages: dependencies: loose-envify: 1.4.0 + /reactcss@1.2.3: + resolution: {integrity: sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==} + peerDependencies: + react: '*' + dependencies: + lodash: 4.17.21 + dev: false + /reactcss@1.2.3(react@18.2.0): resolution: {integrity: sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==} peerDependencies: @@ -19568,6 +20817,7 @@ packages: dependencies: lodash: 4.17.21 react: 18.2.0 + dev: true /read-pkg-up@4.0.0: resolution: {integrity: sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==} @@ -19584,6 +20834,7 @@ packages: find-up: 4.1.0 read-pkg: 5.2.0 type-fest: 0.8.1 + dev: true /read-pkg-up@8.0.0: resolution: {integrity: sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==} @@ -19592,6 +20843,7 @@ packages: find-up: 5.0.0 read-pkg: 6.0.0 type-fest: 1.4.0 + dev: true /read-pkg@3.0.0: resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} @@ -19610,6 +20862,7 @@ packages: normalize-package-data: 2.5.0 parse-json: 5.2.0 type-fest: 0.6.0 + dev: true /read-pkg@6.0.0: resolution: {integrity: sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==} @@ -19619,6 +20872,7 @@ packages: normalize-package-data: 3.0.3 parse-json: 5.2.0 type-fest: 1.4.0 + dev: true /readable-stream@2.3.7: resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} @@ -19655,7 +20909,6 @@ packages: engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 - dev: false /reading-time@1.5.0: resolution: {integrity: sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==} @@ -19687,6 +20940,7 @@ packages: dependencies: indent-string: 5.0.0 strip-indent: 4.0.0 + dev: true /redeyed@2.1.1: resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} @@ -19751,6 +21005,7 @@ packages: dependencies: extend-shallow: 3.0.2 safe-regex: 1.1.0 + dev: true /regexp.prototype.flags@1.4.3: resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} @@ -19972,6 +21227,7 @@ packages: /remove-trailing-separator@1.1.0: resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==} + dev: true /rename-keys@1.2.0: resolution: {integrity: sha512-U7XpAktpbSgHTRSNRrjKSrjYkZKuhUukfoBlXWXUExCAqhzh1TU3BDRAfJmarcl5voKS+pbKU9MvyLWKZ4UEEg==} @@ -19991,6 +21247,7 @@ packages: /repeat-element@1.1.4: resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==} engines: {node: '>=0.10.0'} + dev: true /repeat-string@1.6.1: resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} @@ -20052,12 +21309,15 @@ packages: /require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + dev: true /require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + dev: true /requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + dev: true /requizzle@0.2.4: resolution: {integrity: sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==} @@ -20073,6 +21333,7 @@ packages: engines: {node: '>=8'} dependencies: resolve-from: 5.0.0 + dev: true /resolve-dir@0.1.1: resolution: {integrity: sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==} @@ -20109,6 +21370,7 @@ packages: /resolve-url@0.2.1: resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} deprecated: https://github.com/lydell/resolve-url#deprecated + dev: true /resolve@1.1.7: resolution: {integrity: sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==} @@ -20179,6 +21441,7 @@ packages: /ret@0.1.15: resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} engines: {node: '>=0.12'} + dev: true /retry-request@5.0.2: resolution: {integrity: sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==} @@ -20220,6 +21483,7 @@ packages: hasBin: true dependencies: glob: 7.2.3 + dev: true /rimraf@5.0.5: resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} @@ -20297,7 +21561,7 @@ packages: typescript: 5.2.2 dev: true - /rollup-plugin-visualizer@5.9.0(rollup@4.1.5): + /rollup-plugin-visualizer@5.9.0: resolution: {integrity: sha512-bbDOv47+Bw4C/cgs0czZqfm8L82xOZssk4ayZjG40y9zbXclNk7YikrZTDao6p7+HDiGxrN0b65SgZiVm9k1Cg==} engines: {node: '>=14'} hasBin: true @@ -20309,7 +21573,6 @@ packages: dependencies: open: 8.4.0 picomatch: 2.3.1 - rollup: 4.1.5 source-map: 0.7.4 yargs: 17.7.2 dev: false @@ -20378,6 +21641,7 @@ packages: '@rollup/rollup-win32-ia32-msvc': 4.1.5 '@rollup/rollup-win32-x64-msvc': 4.1.5 fsevents: 2.3.3 + dev: true /roughjs@4.6.6: resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} @@ -20390,6 +21654,7 @@ packages: /rsvp@4.8.5: resolution: {integrity: sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==} engines: {node: 6.* || >= 7.*} + dev: true /rtl-css-js@1.16.1: resolution: {integrity: sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==} @@ -20491,6 +21756,7 @@ packages: resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==} dependencies: ret: 0.1.15 + dev: true /safe-stable-stringify@2.4.2: resolution: {integrity: sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==} @@ -20517,6 +21783,7 @@ packages: walker: 1.0.8 transitivePeerDependencies: - supports-color + dev: true /sass@1.69.5: resolution: {integrity: sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==} @@ -20536,6 +21803,7 @@ packages: engines: {node: '>=10'} dependencies: xmlchars: 2.2.0 + dev: true /scheduler@0.22.0: resolution: {integrity: sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==} @@ -20702,6 +21970,7 @@ packages: /set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + dev: true /set-function-name@2.0.1: resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} @@ -20724,6 +21993,7 @@ packages: is-extendable: 0.1.1 is-plain-object: 2.0.4 split-string: 3.1.0 + dev: true /setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} @@ -20776,6 +22046,7 @@ packages: /shellwords@0.1.1: resolution: {integrity: sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==} requiresBuild: true + dev: true optional: true /shortid@2.2.16: @@ -20798,6 +22069,7 @@ packages: /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + dev: true /signale@1.4.0: resolution: {integrity: sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==} @@ -20827,6 +22099,7 @@ packages: /sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + dev: true /sitemap@7.1.1: resolution: {integrity: sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==} @@ -20839,6 +22112,19 @@ packages: sax: 1.2.4 dev: false + /size-limit@11.0.1: + resolution: {integrity: sha512-6L80ocVspWPrhIRg8kPl41VypqTGH8/lu9e6TJiSJpkNLtOR2h/EEqdAO/wNJOv/sUVtjX+lVEWrzBpItGP+gQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + bytes-iec: 3.1.1 + chokidar: 3.5.3 + globby: 14.0.0 + lilconfig: 3.0.0 + nanospinner: 1.1.0 + picocolors: 1.0.0 + dev: true + /size-sensor@1.0.1: resolution: {integrity: sha512-QTy7MnuugCFXIedXRpUSk9gUnyNiaxIdxGfUjr8xxXOqIB3QvBUYP9+b51oCg2C4dnhaeNk/h57TxjbvoJrJUA==} dev: false @@ -20856,6 +22142,11 @@ packages: resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} engines: {node: '>=12'} + /slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} + dev: true + /slice-ansi@4.0.0: resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} engines: {node: '>=10'} @@ -20863,6 +22154,7 @@ packages: ansi-styles: 4.3.0 astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 + dev: true /slice-ansi@5.0.0: resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} @@ -20894,12 +22186,14 @@ packages: define-property: 1.0.0 isobject: 3.0.1 snapdragon-util: 3.0.1 + dev: true /snapdragon-util@3.0.1: resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==} engines: {node: '>=0.10.0'} dependencies: kind-of: 3.2.2 + dev: true /snapdragon@0.8.2: resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==} @@ -20915,6 +22209,7 @@ packages: use: 3.1.1 transitivePeerDependencies: - supports-color + dev: true /socks-proxy-agent@3.0.1: resolution: {integrity: sha512-ZwEDymm204mTzvdqyUqOdovVr2YRd2NYskrYrF2LXyZ9qDiMAoFESGK8CRphiO7rtbo2Y757k2Nia3x2hGtalA==} @@ -20967,6 +22262,7 @@ packages: resolve-url: 0.2.1 source-map-url: 0.4.1 urix: 0.1.0 + dev: true /source-map-resolve@0.6.0: resolution: {integrity: sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==} @@ -20990,6 +22286,7 @@ packages: /source-map-url@0.4.1: resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==} deprecated: See https://github.com/lydell/source-map-url#deprecated + dev: true /source-map@0.1.32: resolution: {integrity: sha512-htQyLrrRLkQ87Zfrir4/yN+vAUd6DNjVayEjTSHXu29AYQJw57I4/xEL/M6p6E/woPNJwvZt6rVlzc7gFEJccQ==} @@ -21083,6 +22380,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: extend-shallow: 3.0.2 + dev: true /split2@1.0.0: resolution: {integrity: sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==} @@ -21163,6 +22461,7 @@ packages: engines: {node: '>=10'} dependencies: escape-string-regexp: 2.0.0 + dev: true /stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} @@ -21193,6 +22492,7 @@ packages: dependencies: define-property: 0.2.5 object-copy: 0.1.0 + dev: true /stdin-discarder@0.1.0: resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} @@ -21269,6 +22569,7 @@ packages: dependencies: char-regex: 1.0.2 strip-ansi: 6.0.1 + dev: true /string-width@1.0.2: resolution: {integrity: sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==} @@ -21435,6 +22736,7 @@ packages: /strip-bom@4.0.0: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} engines: {node: '>=8'} + dev: true /strip-eof@1.0.0: resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} @@ -21460,6 +22762,7 @@ packages: engines: {node: '>=12'} dependencies: min-indent: 1.0.1 + dev: true /strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} @@ -21468,6 +22771,7 @@ packages: /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + dev: true /strip-outer@1.0.1: resolution: {integrity: sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==} @@ -21485,6 +22789,7 @@ packages: /style-search@0.1.0: resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==} + dev: true /style-to-object@0.3.0: resolution: {integrity: sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==} @@ -21527,21 +22832,18 @@ packages: stylelint: 15.11.0(typescript@5.2.2) dev: true - /stylelint-config-recommended@7.0.0(stylelint@15.11.0): + /stylelint-config-recommended@7.0.0: resolution: {integrity: sha512-yGn84Bf/q41J4luis1AZ95gj0EQwRX8lWmGmBwkwBNSkpGSpl66XcPTulxGa/Z91aPoNGuIGBmFkcM1MejMo9Q==} peerDependencies: stylelint: ^14.4.0 - dependencies: - stylelint: 15.11.0(typescript@5.2.2) dev: false - /stylelint-config-standard@25.0.0(stylelint@15.11.0): + /stylelint-config-standard@25.0.0: resolution: {integrity: sha512-21HnP3VSpaT1wFjFvv9VjvOGDtAviv47uTp3uFmzcN+3Lt+RYRv6oAplLaV51Kf792JSxJ6svCJh/G18E9VnCA==} peerDependencies: stylelint: ^14.4.0 dependencies: - stylelint: 15.11.0(typescript@5.2.2) - stylelint-config-recommended: 7.0.0(stylelint@15.11.0) + stylelint-config-recommended: 7.0.0 dev: false /stylelint-config-standard@34.0.0(stylelint@15.11.0): @@ -21602,6 +22904,7 @@ packages: transitivePeerDependencies: - supports-color - typescript + dev: true /stylis@4.1.3: resolution: {integrity: sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==} @@ -21663,6 +22966,7 @@ packages: dependencies: has-flag: 4.0.0 supports-color: 7.2.0 + dev: true /supports-hyperlinks@3.0.0: resolution: {integrity: sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==} @@ -21670,6 +22974,7 @@ packages: dependencies: has-flag: 4.0.0 supports-color: 7.2.0 + dev: true /supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} @@ -21688,6 +22993,7 @@ packages: /svg-tags@1.0.0: resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} + dev: true /svgo-browser@1.3.8: resolution: {integrity: sha512-yOgDNIcewFZN3+jXdWeh/rQzbWJjCq1dTHphLz2r4T4AfTm+nqCxZ5B89v9bjQbFKA/s/k7TUc7J90+pP2HTyw==} @@ -21731,6 +23037,7 @@ packages: /symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + dev: true /synckit@0.8.5: resolution: {integrity: sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==} @@ -21752,6 +23059,7 @@ packages: slice-ansi: 4.0.0 string-width: 4.2.3 strip-ansi: 6.0.1 + dev: true /taffydb@2.6.2: resolution: {integrity: sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==} @@ -21868,6 +23176,7 @@ packages: dependencies: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 + dev: true /terser@5.21.0: resolution: {integrity: sha512-WtnFKrxu9kaoXuiZFSGrcAvvBqAdmKx0SFNmVNYdJamMu9yyN3I/QF0FbH4QcqJQ+y1CJnzxGIKH0cSj+FGYRw==} @@ -21909,6 +23218,7 @@ packages: /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true /textextensions@2.6.0: resolution: {integrity: sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ==} @@ -21927,6 +23237,7 @@ packages: /throat@5.0.0: resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} + dev: true /throttle-debounce@3.0.1: resolution: {integrity: sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==} @@ -22012,6 +23323,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: kind-of: 3.2.2 + dev: true /to-readable-stream@1.0.0: resolution: {integrity: sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==} @@ -22024,6 +23336,7 @@ packages: dependencies: is-number: 3.0.0 repeat-string: 1.6.1 + dev: true /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} @@ -22039,6 +23352,7 @@ packages: extend-shallow: 3.0.2 regex-not: 1.0.2 safe-regex: 1.1.0 + dev: true /toggle-selection@1.0.6: resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} @@ -22058,6 +23372,7 @@ packages: punycode: 2.1.1 universalify: 0.2.0 url-parse: 1.5.10 + dev: true /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -22074,6 +23389,7 @@ packages: engines: {node: '>=8'} dependencies: punycode: 2.1.1 + dev: true /transformation-matrix@2.15.0: resolution: {integrity: sha512-HN3kCvvH4ug3Xm/ycOfCFQOOktg5htxlC4Ih1Z7Wb6BMtQho+q+irOdGo10ARRKpqkRBXgBzQFw/AVmR0oIf0g==} @@ -22099,6 +23415,7 @@ packages: /trim-newlines@4.1.1: resolution: {integrity: sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==} engines: {node: '>=12'} + dev: true /trim-repeated@1.0.0: resolution: {integrity: sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==} @@ -22158,6 +23475,15 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + /tsutils@3.21.0: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + dev: false + /tsutils@3.21.0(typescript@5.2.2): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} @@ -22166,6 +23492,7 @@ packages: dependencies: tslib: 1.14.1 typescript: 5.2.2 + dev: true /tsx@3.14.0: resolution: {integrity: sha512-xHtFaKtHxM9LOklMmJdI3BEnQq/D5F73Of2E1GDrITi9sgoVkvIsrQUTY1G8FlmGtA+awCI4EBlTRRYxkL2sRg==} @@ -22202,16 +23529,19 @@ packages: engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.1.2 + dev: true /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 + dev: true /type-detect@4.0.8: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} + dev: true /type-fest@0.13.1: resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} @@ -22233,22 +23563,27 @@ packages: /type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} + dev: true /type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} + dev: true /type-fest@0.6.0: resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} engines: {node: '>=8'} + dev: true /type-fest@0.8.1: resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} engines: {node: '>=8'} + dev: true /type-fest@1.4.0: resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} engines: {node: '>=10'} + dev: true /type-fest@3.13.1: resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} @@ -22293,6 +23628,7 @@ packages: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} dependencies: is-typedarray: 1.0.0 + dev: true /typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} @@ -22313,6 +23649,7 @@ packages: resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} engines: {node: '>=14.17'} hasBin: true + dev: true /uc.micro@1.0.6: resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} @@ -22340,23 +23677,23 @@ packages: requiresBuild: true optional: true - /umi@4.0.88(@babel/core@7.23.2)(@types/node@20.8.9)(eslint@8.52.0)(jest@26.6.3)(postcss-less@6.0.0)(prettier@3.0.3)(rollup@4.1.5)(sass@1.69.5)(stylelint@15.11.0)(typescript@5.2.2): + /umi@4.0.88(sass@1.69.5): resolution: {integrity: sha512-gNgBMbLuZwj5c2uroFmtgT4tJO2JFKfIxJaad3oFa0cOzJMSx6CnaG3Z2/+pU2eAy9EMBvbtY/iAmkr3ZNEAWg==} engines: {node: '>=14'} hasBin: true dependencies: '@babel/runtime': 7.23.2 '@umijs/bundler-utils': 4.0.88 - '@umijs/bundler-webpack': 4.0.88(typescript@5.2.2) + '@umijs/bundler-webpack': 4.0.88 '@umijs/core': 4.0.88 - '@umijs/lint': 4.0.88(eslint@8.52.0)(jest@26.6.3)(postcss-less@6.0.0)(stylelint@15.11.0)(typescript@5.2.2) - '@umijs/preset-umi': 4.0.88(@types/node@20.8.9)(rollup@4.1.5)(sass@1.69.5)(typescript@5.2.2) - '@umijs/renderer-react': 4.0.88(react-dom@18.1.0)(react@18.1.0) + '@umijs/lint': 4.0.88 + '@umijs/preset-umi': 4.0.88(sass@1.69.5) + '@umijs/renderer-react': 4.0.88 '@umijs/server': 4.0.88 - '@umijs/test': 4.0.88(@babel/core@7.23.2) + '@umijs/test': 4.0.88 '@umijs/utils': 4.0.88 - prettier-plugin-organize-imports: 3.2.4(prettier@3.0.3)(typescript@5.2.2) - prettier-plugin-packagejson: 2.4.3(prettier@3.0.3) + prettier-plugin-organize-imports: 3.2.4 + prettier-plugin-packagejson: 2.4.3 transitivePeerDependencies: - '@babel/core' - '@types/node' @@ -22432,6 +23769,11 @@ packages: engines: {node: '>=4'} dev: false + /unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + dev: true + /unified@10.1.2: resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} dependencies: @@ -22451,6 +23793,7 @@ packages: get-value: 2.0.6 is-extendable: 0.1.1 set-value: 2.0.1 + dev: true /unique-filename@1.1.1: resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==} @@ -22553,6 +23896,7 @@ packages: /universalify@0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} + dev: true /universalify@2.0.0: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} @@ -22576,6 +23920,7 @@ packages: dependencies: has-value: 0.3.1 isobject: 3.0.1 + dev: true /untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} @@ -22624,6 +23969,7 @@ packages: /urix@0.1.0: resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==} deprecated: Please see https://github.com/lydell/urix#deprecated + dev: true /url-join@4.0.1: resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} @@ -22648,6 +23994,7 @@ packages: dependencies: querystringify: 2.2.0 requires-port: 1.0.0 + dev: true /url@0.11.3: resolution: {integrity: sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==} @@ -22679,6 +24026,7 @@ packages: /use@3.1.1: resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==} engines: {node: '>=0.10.0'} + dev: true /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -22754,6 +24102,7 @@ packages: '@types/istanbul-lib-coverage': 2.0.4 convert-source-map: 1.9.0 source-map: 0.7.4 + dev: true /validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -22846,14 +24195,13 @@ packages: - supports-color dev: true - /vite-plugin-svgr@2.4.0(rollup@4.1.5)(vite@4.5.0): + /vite-plugin-svgr@2.4.0: resolution: {integrity: sha512-q+mJJol6ThvqkkJvvVFEndI4EaKIjSI0I3jNFgSoC9fXAz1M7kYTVUin8fhUsFojFDKZ9VHKtX6NXNaOLpbsHA==} peerDependencies: vite: ^2.6.0 || 3 || 4 dependencies: - '@rollup/pluginutils': 5.0.5(rollup@4.1.5) + '@rollup/pluginutils': 5.0.5 '@svgr/core': 6.5.1 - vite: 4.5.0(@types/node@20.8.9)(less@4.2.0) transitivePeerDependencies: - rollup - supports-color @@ -22866,7 +24214,7 @@ packages: svgo: 2.8.0 dev: true - /vite@4.3.1(@types/node@20.8.9)(less@4.1.3)(sass@1.69.5): + /vite@4.3.1(less@4.1.3)(sass@1.69.5): resolution: {integrity: sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -22891,7 +24239,6 @@ packages: terser: optional: true dependencies: - '@types/node': 20.8.9 esbuild: 0.17.19 less: 4.1.3 postcss: 8.4.31 @@ -23012,16 +24359,15 @@ packages: he: 1.2.0 dev: true - /vue-tsc@1.8.19(typescript@5.2.2): + /vue-tsc@1.8.19: resolution: {integrity: sha512-tacMQLQ0CXAfbhRycCL5sWIy1qujXaIEtP1hIQpzHWOUuICbtTj9gJyFf91PvzG5KCNIkA5Eg7k2Fmgt28l5DQ==} hasBin: true peerDependencies: typescript: '*' dependencies: - '@vue/language-core': 1.8.19(typescript@5.2.2) - '@vue/typescript': 1.8.19(typescript@5.2.2) + '@vue/language-core': 1.8.19 + '@vue/typescript': 1.8.19 semver: 7.5.4 - typescript: 5.2.2 dev: true /vue-types@3.0.2(vue@3.3.4): @@ -23048,12 +24394,14 @@ packages: deprecated: Use your platform's native performance.now() and performance.timeOrigin. dependencies: browser-process-hrtime: 1.0.0 + dev: true /w3c-xmlserializer@2.0.0: resolution: {integrity: sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==} engines: {node: '>=10'} dependencies: xml-name-validator: 3.0.0 + dev: true /walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -23097,18 +24445,22 @@ packages: /webidl-conversions@5.0.0: resolution: {integrity: sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==} engines: {node: '>=8'} + dev: true /webidl-conversions@6.1.0: resolution: {integrity: sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==} engines: {node: '>=10.4'} + dev: true /whatwg-encoding@1.0.5: resolution: {integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==} dependencies: iconv-lite: 0.4.24 + dev: true /whatwg-mimetype@2.3.0: resolution: {integrity: sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==} + dev: true /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -23140,6 +24492,7 @@ packages: lodash: 4.17.21 tr46: 2.1.0 webidl-conversions: 6.1.0 + dev: true /which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -23177,6 +24530,7 @@ packages: /which-module@2.0.0: resolution: {integrity: sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==} + dev: true /which-pm-runs@1.1.0: resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} @@ -23237,6 +24591,7 @@ packages: /word-wrap@1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} engines: {node: '>=0.10.0'} + dev: true /wordwrap@0.0.2: resolution: {integrity: sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==} @@ -23262,6 +24617,7 @@ packages: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 + dev: true /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} @@ -23306,6 +24662,7 @@ packages: is-typedarray: 1.0.0 signal-exit: 3.0.7 typedarray-to-buffer: 3.1.5 + dev: true /write-file-atomic@4.0.2: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} @@ -23321,6 +24678,7 @@ packages: dependencies: imurmurhash: 0.1.4 signal-exit: 4.1.0 + dev: true /ws@5.2.3: resolution: {integrity: sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA==} @@ -23347,6 +24705,7 @@ packages: optional: true utf-8-validate: optional: true + dev: true /xdg-basedir@3.0.0: resolution: {integrity: sha512-1Dly4xqlulvPD3fZUQJLY+FUIeqN3N2MM3uqe4rCJftAvOjFa3jFGfctOgluGx4ahPbUCsZkmJILiP0Vi4T6lQ==} @@ -23361,6 +24720,7 @@ packages: /xml-name-validator@3.0.0: resolution: {integrity: sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==} + dev: true /xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} @@ -23376,6 +24736,7 @@ packages: /xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + dev: true /xmlcreate@2.0.4: resolution: {integrity: sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==} @@ -23427,10 +24788,12 @@ packages: dependencies: camelcase: 5.3.1 decamelize: 1.2.0 + dev: true /yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} + dev: true /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} @@ -23466,6 +24829,7 @@ packages: which-module: 2.0.0 y18n: 4.0.3 yargs-parser: 18.1.3 + dev: true /yargs@16.2.0: resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} diff --git a/s2-site/.dumirc.ts b/s2-site/.dumirc.ts index 3f4fdc2e4f..077f3b9416 100644 --- a/s2-site/.dumirc.ts +++ b/s2-site/.dumirc.ts @@ -6,7 +6,7 @@ export default defineConfig({ { id: 'zh', name: '中文' }, { id: 'en', name: 'English' }, ], - title: 'S2', // 网站header标题 + title: 'S2', // 网站 header 标题 favicons: [ 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*7svFR6wkPMoAAAAAAAAAAAAADmJ7AQ/original', ], // 网站 favicon @@ -20,6 +20,7 @@ export default defineConfig({ description: '多维交叉分析表格', defaultLanguage: 'zh', // 默认语言 isAntVSite: false, // 是否是 AntV 的大官网 + footerTheme: 'light', // 白色 底部主题 siteUrl: 'https://antv.antgroup.com', // 官网地址 githubUrl: repository.url, // GitHub 地址 showSearch: true, // 是否显示搜索框 @@ -276,61 +277,69 @@ export default defineConfig({ }, /** 首页技术栈介绍 */ detail: { + engine: { + zh: 'S2', + en: 'S2', + }, title: { - zh: 'S2 多维交叉分析表格', - en: 'S2 Multi Cross Analysis Table', + zh: 'S2·多维交叉分析表格', + en: 'S2·Multi Cross Analysis Table', }, description: { zh: 'S2 是多维交叉分析领域的表格解决方案,数据驱动视图,提供底层核心库、基础组件库、业务场景库,具备自由扩展的能力,让开发者既能开箱即用,也能基于自身场景自由发挥。', en: 'S2 is a table solution in the field of multidimensional cross analysis. It is data-driven view, provides the underlying core library, basic component library and business scenario library, and has the ability of free expansion, allowing developers to use it out of the box and freely play based on their own scenarios.', }, image: - 'https://gw.alipayobjects.com/zos/bmw-prod/1aa91199-b986-4553-a425-6baa18c3a9bd.svg', + 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*1PTTQLk3j5AAAAAAAAAAAAAADmJ7AQ/original', + imageStyle: { + marginLeft: '70px', + marginTop: '90px', + }, buttons: [ { text: { - zh: '图表示例', - en: 'Examples', + zh: '开始使用', + en: 'Getting Started', }, - link: `/examples`, - type: 'primary', + link: `/manual/getting-started`, }, { text: { - zh: '开始使用', - en: 'Getting Started', + zh: '图表示例', + en: 'Examples', }, - link: `/manual/getting-started`, + link: `/examples`, + type: 'primary', }, ], }, /** 新闻公告,优先选择配置的,如果没有配置则使用远程的! */ - news: [ - { - type: { - zh: '论坛', - en: 'Forum', - }, - title: { - zh: 'AntV 芒种日 图新物:GraphInsight 发布', - en: 'AntV New Product Launch: GraphInsight', - }, - date: '2022.06.06', - link: 'https://github.com/antvis/GraphInsight', - }, - { - type: { - zh: '论坛', - en: 'Forum', - }, - title: { - zh: 'SEE Conf 2022 支付宝体验科技大会', - en: 'Alipay Experience Technology Conference', - }, - date: '2022.01.08', - link: 'https://seeconf.antfin.com/', - }, - ], + // news: [ + // { + // type: { + // zh: '论坛', + // en: 'Forum', + // }, + // title: { + // zh: 'AntV 芒种日 图新物:GraphInsight 发布', + // en: 'AntV New Product Launch: GraphInsight', + // }, + // date: '2022.06.06', + // link: 'https://github.com/antvis/GraphInsight', + // }, + // { + // type: { + // zh: '论坛', + // en: 'Forum', + // }, + // title: { + // zh: 'SEE Conf 2022 支付宝体验科技大会', + // en: 'Alipay Experience Technology Conference', + // }, + // date: '2022.01.08', + // link: 'https://seeconf.antfin.com/', + // }, + // ], /** 首页特性介绍 */ features: [ { diff --git a/s2-site/docs/api/basic-class/base-data-set.en.md b/s2-site/docs/api/basic-class/base-data-set.en.md index e11b9062e8..eb53db279c 100644 --- a/s2-site/docs/api/basic-class/base-data-set.en.md +++ b/s2-site/docs/api/basic-class/base-data-set.en.md @@ -10,25 +10,26 @@ set. [details](https://github.com/antvis/S2/blob/master/packages/s2-core/src/dat s2.dataSet.getFieldName('type') ``` -| parameter | illustrate | type | Version | -| ------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- | -| fields | field information | () => [Fields](/docs/api/general/S2DataConfig#fields) | | -| meta | Field meta information, including field name, formatting, etc. | () => [Meta\[\]](/docs/api/general/S2DataConfig#meta) | | -| originData | Raw data | () => [DataType\[\]](#datatype) | | -| totalData | summary data | () => [DataType\[\]](#datatype) | | -| indexesData | multidimensional index data | () => [DataType\[\]](#datatype) | | -| sortParams | sort configuration | () => [SortParams](/docs/api/general/S2DataConfig#sortparams) | | -| spreadsheet | Form instance | () => [SpreadSheet](/docs/api/basic-class/spreadsheet) | | -| getFieldMeta | Get field metadata information | (field: string, meta?: [Meta\[\]](/docs/api/general/S2DataConfig#meta) ) => [Meta](/docs/api/general/S2DataConfig#meta) | | -| getFieldName | get field name | `() => string` | | -| getFieldFormatter | Get the field formatting function | `() => (v: string) => unknown` | | -| getFieldDescription | Get field description | `() => string` | | -| setDataCfg | Set data configuration | (dataCfg: [S2DataConfig](/docs/api/general/S2DataConfig) , reset?: boolean) => void | The `reset` parameter needs to be used in `@antv/s2-v1.34.0` version | -| getDisplayDataSet | Get the currently displayed dataset | () => [DataType\[\]](#datatype) | | -| getDimensionValues | get dimension value | (filed: string, query?: [DataType](#datatype) ) => string\[] | | -| getCellData | Get a single cell data | (params: [CellDataParams](#celldataparams) ) => [DataType\[\]](#datatype) | | -| getCellMultiData | Get bulk cell data | (query: [DataType](#datatype) , isTotals?: boolean, isRow?: boolean, drillDownFields?: string\[]) => [DataType\[\]](#datatype) | | -| moreThanOneValue | Is there more than 1 value | () => [ViewMeta](#viewmeta) | | +| parameter | illustrate | type | Version | +| ----------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| fields | field information | () => [Fields](/docs/api/general/S2DataConfig#fields) | | +| meta | Field meta information, including field name, formatting, etc. | () => [Meta\[\]](/docs/api/general/S2DataConfig#meta) | | +| originData | Raw data | () => [DataType\[\]](#datatype) | | +| totalData | summary data | () => [DataType\[\]](#datatype) | | +| indexesData | multidimensional index data | () => [DataType\[\]](#datatype) | | +| sortParams | sort configuration | () => [SortParams](/docs/api/general/S2DataConfig#sortparams) | | +| spreadsheet | Form example | () => [SpreadSheet](/docs/api/basic-class/spreadsheet) | | +| getFieldMeta | Get field metadata information | (field: string, meta?: [Meta\[\]](/docs/api/general/S2DataConfig#meta) ) => [Meta](/docs/api/general/S2DataConfig#meta) | | +| getFieldName | get field name | `() => string` | | +| getFieldFormatter | Get the field formatting function | `() => (v: string) => unknown` | | +| getFieldDescription | Get field description | `() => string` | | +| setDataCfg | Set data configuration | `<T extends boolean = false>(dataCfg: T extends true ?` [`S2DataConfig`](/docs/api/general/S2DataConfig) `: Partial<`[`S2DataConfig`](/docs/api/general/S2DataConfig)`>, reset?: T) => void` | The `reset` parameter needs to be used in `@antv/s2-v1.34.0` version | +| getDisplayDataSet | Get the currently displayed dataset | () => [DataType\[\]](#datatype) | | +| getDimensionValues | get dimension value | (filed: string, query?: [DataType](#datatype) ) => string\[] | | +| getCellData | Get a single cell data | (params: [CellDataParams](#celldataparams) ) => [DataType\[\]](#datatype) | | +| getMultiData | Get bulk cell data | (query: [DataType](#datatype),params?: [MultiDataParams](#multidataparams)) => [DataType[]](#datatype) | | +| <strike>getMultiData</strike>(deprecated) | Get bulk cell data | (query: [DataType](#datatype) , isTotals?: boolean, isRow?: boolean, drillDownFields?: string\[], includeTotalData:boolean) => [DataType\[\]](#datatype) | | +| moreThanOneValue | Is there more than 1 value | () => [ViewMeta](#viewmeta) | | ### DataType @@ -50,4 +51,16 @@ interface CellDataParams { } ``` -`markdown:docs/common/custom/customTreeNode.en.md` +### MultiDataParams + +```ts +interface MultiDataParams { + drillDownFields?: string[]; // drill down dimensions + queryType?: QueryDataType; // query type, get all data by default +} + +enum QueryDataType { + All = 'all', // get all data, include total data + DetailOnly = 'detailOnly', // only get detail data +} +``` diff --git a/s2-site/docs/api/basic-class/base-data-set.zh.md b/s2-site/docs/api/basic-class/base-data-set.zh.md index add7e3c3c0..3135f7493d 100644 --- a/s2-site/docs/api/basic-class/base-data-set.zh.md +++ b/s2-site/docs/api/basic-class/base-data-set.zh.md @@ -9,25 +9,27 @@ order: 5 s2.dataSet.getFieldName('type') ``` -| 参数 | 说明 | 类型 | 版本 | -| ------------------- | ---------------------------------- | ------------------------------------------------------------ | ------------------------------------------- | -| fields | 字段信息 | () => [Fields](/docs/api/general/S2DataConfig#fields) | | -| meta | 字段元信息,包含有字段名、格式化等 | () => [Meta[]](/docs/api/general/S2DataConfig#meta) | | -| originData | 原始数据 | () => [DataType[]](#datatype) | | -| totalData | 汇总数据 | () => [DataType[]](#datatype) | | -| indexesData | 多维索引数据 | () => [DataType[]](#datatype) | | -| sortParams | 排序配置 | () => [SortParams](/docs/api/general/S2DataConfig#sortparams) | | -| spreadsheet | 表格实例 | () => [SpreadSheet](/docs/api/basic-class/spreadsheet) | | -| getFieldMeta | 获取字段元数据信息 | (field: string, meta?: [Meta[]](/docs/api/general/S2DataConfig#meta)) => [Meta](/docs/api/general/S2DataConfig#meta) | | -| getFieldName | 获取字段名 | `() => string` | | -| getFieldFormatter | 获取字段格式化函数 | `() => (v: string) => unknown` | | -| getFieldDescription | 获取字段描述 | `() => string` | | -| setDataCfg | 设置数据配置 | (dataCfg: [S2DataConfig](/docs/api/general/S2DataConfig), reset?: boolean) => void | `reset` 参数需在 `@antv/s2-v1.34.0`版本使用 | -| getDisplayDataSet | 获取当前显示的数据集 | () => [DataType[]](#datatype) | | -| getDimensionValues | 获取维值 | (filed: string, query?: [DataType](#datatype) ) => string[] | | -| getCellData | 获取单个的单元格数据 | (params: [GetCellDataParams](#getcelldataparams)) => [DataType[]](#datatype) | | -| getCellMultiData | 获取批量的单元格数据 | (params: [GetCellMultiDataParams](#getcellmultidataparams)) => [DataType[]](#datatype) | | -| moreThanOneValue | 是否超过 1 个数值 | () => [ViewMeta](#viewmeta) | | +| 参数 | 说明 | 类型 | 版本 | +| -------------------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| fields | 字段信息 | () => [Fields](/docs/api/general/S2DataConfig#fields) | | +| meta | 字段元信息,包含有字段名、格式化等 | () => [Meta[]](/docs/api/general/S2DataConfig#meta) | | +| originData | 原始数据 | () => [DataType[]](#datatype) | | +| totalData | 汇总数据 | () => [DataType[]](#datatype) | | +| indexesData | 多维索引数据 | () => [DataType[]](#datatype) | | +| sortParams | 排序配置 | () => [SortParams](/docs/api/general/S2DataConfig#sortparams) | | +| spreadsheet | 表格实例 | () => [SpreadSheet](/docs/api/basic-class/spreadsheet) | | +| getFieldMeta | 获取字段元数据信息 | (field: string, meta?: [Meta[]](/docs/api/general/S2DataConfig#meta)) => [Meta](/docs/api/general/S2DataConfig#meta) | | +| getFieldName | 获取字段名 | `() => string` | | +| getFieldFormatter | 获取字段格式化函数 | `() => (v: string) => unknown` | | +| getFieldDescription | 获取字段描述 | `() => string` | | +| setDataCfg | 设置数据配置 | `<T extends boolean = false>(dataCfg: T extends true ?` [`S2DataConfig`](/docs/api/general/S2DataConfig) `: Partial<`[`S2DataConfig`](/docs/api/general/S2DataConfig)`>, reset?: T) => void` | `reset` 参数需在 `@antv/s2-v1.34.0`版本使用 | +| getDisplayDataSet | 获取当前显示的数据集 | () => [DataType[]](#datatype) | | +| getDimensionValues | 获取维值 | (filed: string, query?: [DataType](#datatype) ) => string[] | | +| getCellData | 获取单个的单元格数据 | (params: [CellDataParams](#celldataparams)) => [DataType[]](#datatype) | | +| getMultiData | 获取批量的单元格数据 | (query: [DataType](#datatype),params?: [MultiDataParams](#multidataparams)) => [DataType[]](#datatype) | | +| <strike>getMultiData<strike> (已废弃) | 获取批量的单元格数据 | (query: [DataType](#datatype), isTotals?: boolean, isRow?: boolean, drillDownFields?: string[], includeTotalData:boolean) => [DataType[]](#datatype) | | +| moreThanOneValue | 是否超过 1 个数值 | () => [ViewMeta](#viewmeta) | | +| isEmpty | 是否为空数据集 | () => `boolean` | `@antv/s2-v1.51.1` | ### DataType @@ -97,6 +99,16 @@ interface GetCellMultiDataParams { * 下钻 */ drillDownFields?: string[]; + + /** + * 获取数据的类型,默认获取所有的数据 + */ + queryType?: QueryDataType; +} + +enum QueryDataType { + All = 'all', // 获取所有的数据 + DetailOnly = 'detailOnly', // 只需要明细数据 } ``` diff --git a/s2-site/docs/api/basic-class/spreadsheet.zh.md b/s2-site/docs/api/basic-class/spreadsheet.zh.md index b5e6bbfc7b..d9f00dd965 100644 --- a/s2-site/docs/api/basic-class/spreadsheet.zh.md +++ b/s2-site/docs/api/basic-class/spreadsheet.zh.md @@ -38,7 +38,7 @@ s2.isPivotMode() | hideTooltip | 隐藏 tooltip | `() => void` | | | destroyTooltip | 销毁 tooltip | `() => void` | | | registerIcons | 注册 自定义 svg 图标 (根据 `options.customSVGIcons`) | `() => void` | | -| setDataCfg | 更新数据配置 | (dataCfg: [S2DataConfig](/docs/api/general/S2DataConfig), reset?: boolean ) => void | `reset` 参数需在 `@antv/s2-v1.34.0`版本使用 | +| setDataCfg | 更新数据配置 | `<T extends boolean = false>(dataCfg: T extends true ?` [`S2DataConfig`](/docs/api/general/S2DataConfig) `: Partial<`[`S2DataConfig`](/docs/api/general/S2DataConfig)`>, reset?: T) => void` | `reset` 参数需在 `@antv/s2-v1.34.0`版本使用 | | setOptions | 更新表格配置 | (options: [S2Options](/docs/api/general/S2Options), reset?: boolean) => void | `reset` 参数需在 `@antv/s2-v1.34.0`版本使用 | | render | 重新渲染表格,如果 `reloadData` = true, 则会重新计算数据,`reBuildDataSet` = true, 重新构建数据集,`reBuildHiddenColumnsDetail` = true 重新构建隐藏列信息 | `(reloadData?: boolean, { reBuildDataSet?: boolean; reBuildHiddenColumnsDetail?: boolean }) => Promise<void>` | | | destroy | 销毁表格 | `() => void` | | diff --git a/s2-site/docs/api/components/drill-down.zh.md b/s2-site/docs/api/components/drill-down.zh.md index 2f557d7ff6..d703bf7604 100644 --- a/s2-site/docs/api/components/drill-down.zh.md +++ b/s2-site/docs/api/components/drill-down.zh.md @@ -19,7 +19,7 @@ const s2Options = { /> ``` -📊 查看 [React 版下钻 demo](/examples/react-component/drill-dwon#for-pivot) +📊 查看 [React 版下钻 demo](/examples/react-component/drill-down#for-pivot) ## Vue 下钻组件 diff --git a/s2-site/docs/api/components/sheet-component.zh.md b/s2-site/docs/api/components/sheet-component.zh.md index c7e1ff872e..4bf596bf52 100644 --- a/s2-site/docs/api/components/sheet-component.zh.md +++ b/s2-site/docs/api/components/sheet-component.zh.md @@ -19,7 +19,7 @@ order: 0 | options | 透视表属性配置项 | [SheetComponentOptions](#sheetcomponentoptions) | | ✓ | | partDrillDown | 维度下钻相关属性 | [PartDrillDown](/docs/api/components/drill-down) | | | | adaptive | 是否根据窗口大小自适应 | `boolean | { width?: boolean, height?: boolean, getContainer: () => HTMLElement }` | `false` | | -| showPagination | 是否显示默认分页<br>(只有在 `options` 配置过 `pagination` 属性才会生效) | `boolean` \| \{ <br>onShowSizeChange?: (pageSize: number) => void,<br>onChange?: (current: number) => void <br>} | `false` | | +| showPagination | 是否显示默认分页<br>(只有在 `options` 配置过 `pagination` 属性才会生效) | `boolean` \| \{ <br>onShowSizeChange?: (current:number, pageSize: number) => void,<br>onChange?: (current:number, pageSize: number) => void <br>} | `false` | | | themeCfg | 自定义透视表主题样式 | [ThemeCfg](/docs/api/general/S2Theme) | | | | loading | 控制表格的加载状态 | `boolean` | | | | header | 表头配置项 | [HeaderCfgProps](/docs/api/components/header) | | | @@ -49,6 +49,7 @@ order: 0 | onDataCellMouseMove | 数值单元格鼠标移动事件 | (data: [TargetCellInfo](#targetcellinfo)) => void | | | | onDataCellBrushSelection | 数值单元格刷选事件 | ( dataCells: [DataCell](/docs/api/basic-class/base-cell)[] ) => void | | | | onDataCellSelectMove | 数值单元格键盘方向键移动事件 | (metas: CellMeta[]) => void | | | +| onDataCellEditEnd | 数值单元格编辑完成(暂只支持编辑表) | (meta: [ViewMeta](/docs/api/basic-class/node)) => void | | | | onCornerCellHover | 角头鼠标悬停事件 | (data: [TargetCellInfo](#targetcellinfo)) => void | | | | onCornerCellClick | 角头鼠标单击事件 | (data: [TargetCellInfo](#targetcellinfo)) => void | | | | onCornerCellDoubleClick | 角头鼠标双击事件 | (data: [TargetCellInfo](#targetcellinfo)) => void | | | @@ -86,7 +87,7 @@ order: 0 | onLayoutResizeMouseMove | resize 热区鼠标移动事件 | ( event: `MouseEvent`, resizeInfo?: [ResizeInfo](#resizeinfo)) => void; | | | | onKeyBoardDown | 键盘按下事件 | (event: KeyboardEvent) => void | | | | onKeyBoardUp | 键盘松开事件 | (event: KeyboardEvent) => void | | | -| onCopied | 复制事件 | (copyData: string) => void | | | +| onCopied | 复制事件 | (data: CopyableList) => void | | | | onActionIconHover | 行头操作 icon 悬停事件 | (event: CanvasEvent) => void | | | | onActionIconClick | 行头操作 icon 点击事件 | (event: CanvasEvent) => void | | | | onContextMenu | 右键单元格单击事件 | (data: [TargetCellInfo](#targetcellinfo)) => void | | | @@ -96,7 +97,7 @@ order: 0 | onReset | 交互状态重置事件 | (event: KeyboardEvent) => void | | | | onLinkFieldJump | 链接字段跳转事件 (cellData: @antv/s2 1.37.0 新增) | (data: { key: string; cellData: [Node](/docs/api/basic-class/node); record: [Data](/docs/api/general/S2DataConfig#data) }) => void | | | | onScroll | 单元格滚动事件 (含行头和数值单元格) | ({position: [CellScrollPosition](#cellscrollposition)} ) => void; | | | -| onColCellBrushSelection | 批量选中刷选范围内的列头单元格,刷选过程中,显示刷选范围提示蒙层,刷选完成后,弹出 tooltip, 展示被刷选单元格信息(仅支持透视表) | (cells: [ColCell](/docs/api/basic-class/base-cell)[]) => void; | | | +| onColCellBrushSelection | 批量选中刷选范围内的列头单元格,刷选过程中,显示刷选范围提示蒙层,刷选完成后,弹出 tooltip, 展示被刷选单元格信息 | (cells: [ColCell](/docs/api/basic-class/base-cell)[]) => void; | | | | onRowCellBrushSelection | 批量选中刷选范围内的行头单元格,刷选过程中,显示刷选范围提示蒙层,刷选完成后,弹出 tooltip, 展示被刷选单元格信息(仅支持透视表) | (cells: [RowCell](/docs/api/basic-class/base-cell)[]) => void; | | | ## SheetComponentOptions @@ -206,7 +207,7 @@ type SheetComponentOptions = S2Options< | layoutResizeMouseMove | resize 热区鼠标移动事件 | ( event:`MouseEvent`, resizeInfo?: [ResizeInfo](#resizeinfo)) => void; | | | | keyBoardDown | 键盘按下事件 | (event: KeyboardEvent) => void | | | | keyBoardUp | 键盘松开事件 | (event: KeyboardEvent) => void | | | -| copied | 复制事件 | (copyData: string) => void | | | +| copied | 复制事件 | (data: CopyableList) => void | | | | actionIconHover | 行头操作 icon 悬停事件 | (event: CanvasEvent) => void | | | | actionIconClick | 行头操作 icon 点击事件 | (event: CanvasEvent) => void | | | | contextMenu | 右键单元格单击事件 | (data: [TargetCellInfo](#targetcellinfo)) => void | | | @@ -216,7 +217,7 @@ type SheetComponentOptions = S2Options< | reset | 交互状态重置事件 | (event: KeyboardEvent) => void | | | | linkFieldJump | 链接字段跳转事件 (cellData: @antv/s2 1.37.0 新增) | (data: { key: string; cellData: [Node](/docs/api/basic-class/node); record: [Data](/docs/api/general/S2DataConfig#data) }) => void | | | | scroll | 单元格滚动事件 (含行头和数值单元格) | ({position: [CellScrollPosition](#cellscrollposition)} ) => void; | | | -| colCellBrushSelection | 批量选中刷选范围内的列头单元格,刷选过程中,显示刷选范围提示蒙层,刷选完成后,弹出 tooltip, 展示被刷选单元格信息(仅支持透视表) | (cells: ColCell[]) => void; | | | +| colCellBrushSelection | 批量选中刷选范围内的列头单元格,刷选过程中,显示刷选范围提示蒙层,刷选完成后,弹出 tooltip, 展示被刷选单元格信息 | (cells: ColCell[]) => void; | | | | rowCellBrushSelection | 批量选中刷选范围内的行头单元格,刷选过程中,显示刷选范围提示蒙层,刷选完成后,弹出 tooltip, 展示被刷选单元格信息(仅支持透视表) | (cells: RowCell[]) => void; | | | ## SheetComponentOptions diff --git a/s2-site/docs/api/general/S2Event.zh.md b/s2-site/docs/api/general/S2Event.zh.md index 7cb09b1a4c..b4aa8577b7 100644 --- a/s2-site/docs/api/general/S2Event.zh.md +++ b/s2-site/docs/api/general/S2Event.zh.md @@ -60,6 +60,7 @@ s2.on(S2Event.ROW_CELL_CLICK, (event) => { | 鼠标移动 | `S2Event.DATA_CELL_MOUSE_MOVE` | 数值单元格鼠标移动 | | 鼠标松开 | `S2Event.DATA_CELL_MOUSE_UP` | 数值单元格鼠标松开 | | 刷选 | `S2Event.DATA_CELL_BRUSH_SELECTION` | 数值单元格刷选 | +| 键盘方向键移动 | `S2Event.DATA_CELL_SELECT_MOVE` | 数值单元格键盘方向键移动 | | 单元格渲染 | `S2Event.DATA_CELL_RENDER` | 数值单元格布局渲染完成事件 | ### 角头单元格 (CornerCell) diff --git a/s2-site/docs/api/general/S2Options.zh.md b/s2-site/docs/api/general/S2Options.zh.md index f82cfb273e..d1aa655143 100644 --- a/s2-site/docs/api/general/S2Options.zh.md +++ b/s2-site/docs/api/general/S2Options.zh.md @@ -13,8 +13,8 @@ const s2Options = { } ``` -| 参数 | 类型 | 必选 | 默认值 | 功能描述 | -| -- | --- | -- | -- | --- | +| 参数 | 类型 | 必选 | 默认值 | 功能描述 | 版本 | +| -- | --- | -- | -- | --- | --- | | width | `number` | | 600 | 表格宽度 | | height | `number` | | 480 | 表格高度 | | debug | `boolean` | |`false` | 是否开启调试模式 | diff --git a/s2-site/docs/api/general/S2Theme.en.md b/s2-site/docs/api/general/S2Theme.en.md index 5f94f84953..d1cc837840 100644 --- a/s2-site/docs/api/general/S2Theme.en.md +++ b/s2-site/docs/api/general/S2Theme.en.md @@ -196,6 +196,8 @@ Function description: interactive general theme | backgroundOpacity | background transparency | `number` | | | | borderColor | Edge fill color | `string` | | | | borderWidth | Edge Width | `number` | | | +| borderOpacity | border transparency | `number` | | | +| textOpacity | text transparency | `number` | | | | opacity | overall transparency | `number` | | | #### Margin|Padding diff --git a/s2-site/docs/api/general/S2Theme.zh.md b/s2-site/docs/api/general/S2Theme.zh.md index c6b05c8169..8617aa683c 100644 --- a/s2-site/docs/api/general/S2Theme.zh.md +++ b/s2-site/docs/api/general/S2Theme.zh.md @@ -136,6 +136,7 @@ s2.setTheme({ | showShadow | 分割线是否显示外阴影(行列冻结情况下) | `boolean` | `true` | | | shadowWidth | 阴影宽度 | `number` | 10 | | | shadowColors | `left` : 线性变化左侧颜色 <br> `right` : 线性变化右侧颜色 | `{left: string,` <br> `right: string}` | `{left: 'rgba(0,0,0,0.1)',`<br>`right: 'rgba(0,0,0,0)'}` | | +| borderDash | 分割线虚线 | `number \| string \| (string \| number)[]` | `[]` | | #### TextTheme @@ -149,7 +150,9 @@ s2.setTheme({ | textBaseline | 绘制文本时的基线 | `top \| middle \| bottom` | - | | | fontFamily | 字体 | `string` | `Roboto, PingFangSC,` <br> `BlinkMacSystemFont,` <br> `Microsoft YaHei,` <br> `Arial, sans-serif` | | | fontSize | 字体大小 | `number` | - | | -| fontWeight | number <br> string: `normal` <br> `bold` <br> `bolder` <br> `lighter` 字体粗细 | `number \| string` | 粗体文本:Mobile:`520` PC: `bold` <br> 普通文本:`normal` | | +| fontWeight | number <br> string: `normal` <br> `bold` <br> `bolder` <br> `lighter` 字体粗细 | `number` \| `string` | 粗体文本:Mobile:`520` PC: `bold` <br> 普通文本:`normal` | | +| fontStyle | 字体样式 | `normal \| italic \| oblique` | `normal` | +| fontVariant | 字体变体 | `normal \| small-caps \| string` | `normal` | | fill | 字体颜色 | `string` | - | | | linkTextFill | 链接文本颜色 | `string` | - | | | opacity | 字体透明度 | `number` | 1 | | @@ -176,6 +179,8 @@ s2.setTheme({ | verticalBorderWidth | 单元格垂直边线宽度 | `number` | - | | | padding | 单元格内边距 | [Padding](#margin--padding) | - | | | interactionState | 单元格交互态 | [InteractionStateTheme](#interactionstatetheme) | - | | +| interactionState | 单元格交互态 ([查看默认配置](https://github.com/antvis/S2/blob/master/packages/s2-core/src/theme/index.ts#L66-L107)) ([示例](/zh/examples/interaction/basic#state-theme)) | Record<[InteractionStateName](#interactionstatename), [InteractionStateTheme](#interactionstatetheme)> | - | | +| borderDash | 单元格边线虚线 | `number \| string \| (string \| number)[]` | `[]` | | #### IconTheme @@ -189,18 +194,54 @@ s2.setTheme({ | size | icon 大小 | `number` | - | | | margin | 单元格外边距 | [Margin](#margin--padding) | - | | +#### InteractionStateName + +> 示例 + +```ts +s2.setTheme({ + dataCell: { + cell: { + interactionState: { + hoverFocus: {}, + selected: {}, + prepareSelect: {} + } + } + } +}) +``` + +| 状态名 | 说明 | 类型 | 默认值 | 必选 | +| ----------------- | ---------- | -------- | ------ | ---- | +| hover | 悬停 | [InteractionStateTheme](#interactionstatetheme) | | | +| hoverFocus | 悬停聚焦 | [InteractionStateTheme](#interactionstatetheme) | | | +| selected | 选中 | [InteractionStateTheme](#interactionstatetheme)| | | +| unselected | 未选中 | [InteractionStateTheme](#interactionstatetheme) | | | +| searchResult | 搜索结果 | [InteractionStateTheme](#interactionstatetheme) | | | +| highlight | 高亮 | [InteractionStateTheme](#interactionstatetheme) | | | +| prepareSelect | 预选中 | [InteractionStateTheme](#interactionstatetheme) | | | + #### InteractionStateTheme <description> **optional** _object_ </description> 功能描述:交互通用主题 +```ts +type InteractionState = { + [K in InteractionStateName]?: InteractionStateTheme; +}; +``` + | 参数 | 说明 | 类型 | 默认值 | 必选 | | ----------------- | ---------- | -------- | ------ | ---- | | backgroundColor | 背景填充色 | `string` | | | | backgroundOpacity | 背景透明度 | `number` | | | | borderColor | 边线填充色 | `string` | | | | borderWidth | 边线宽度 | `number` | | | +| borderOpacity | 边线透明度 | `number` | | | +| textOpacity | 文本透明度 | `number` | | | | opacity | 整体透明度 | `number` | | | #### Margin | Padding diff --git a/s2-site/docs/api/graphic.zh.md b/s2-site/docs/api/graphic.zh.md index 7c92aff545..8fb20a7e90 100644 --- a/s2-site/docs/api/graphic.zh.md +++ b/s2-site/docs/api/graphic.zh.md @@ -47,6 +47,8 @@ S2 使用 [AntV/G](https://g.antv.vision/zh/docs/guide/introduce) 作为绘图 | fontSize | `number` | 文字大小 | | fontFamily | `string` | 文字字体 | | fontWeight | `number` | 字体粗细 | +| fontStyle | `normal \| italic \| oblique` | 字体样式 | +| fontVariant | `normal \| small-caps \| string` | 字体变体 | | lineHeight | `number` | 文字的行高 | | textAlign | `center` \| `left` \| `right` \| `start` \| `end` | 设置文本内容的对齐方式 | | textBaseline | `top` \| `middle` \| `bottom` \| `alphabetic` \| `hanging` | 设置在绘制文本时使用的当前文本基线| diff --git a/s2-site/docs/common/contact-us.en.md b/s2-site/docs/common/contact-us.en.md index 71d2b7cc26..397c40a4ad 100644 --- a/s2-site/docs/common/contact-us.en.md +++ b/s2-site/docs/common/contact-us.en.md @@ -3,4 +3,8 @@ title: Contact Us order: 5 --- -<p><a><img width="300" height="auto" alt="DingTalk" src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*2VvTSZmI4vYAAAAAAAAAAAAADmJ7AQ/original"> </a><a><img width="300" height="auto" alt="qq" src="https://gw.alipayobjects.com/zos/antfincdn/v4TlwgORE/qq_qr_code.JPG"></a></p> +<p> + <a> + <img width="300" height="auto" alt="DingTalk" src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*2VvTSZmI4vYAAAAAAAAAAAAADmJ7AQ/original"> + </a> +</p> diff --git a/s2-site/docs/common/contact-us.zh.md b/s2-site/docs/common/contact-us.zh.md index 59ae6f1dba..6281d0dc71 100644 --- a/s2-site/docs/common/contact-us.zh.md +++ b/s2-site/docs/common/contact-us.zh.md @@ -7,7 +7,4 @@ order: 5 <a> <img width="300" height="auto" alt="DingTalk" src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*2VvTSZmI4vYAAAAAAAAAAAAADmJ7AQ/original"> </a> - <a> - <img width="300" height="auto" alt="qq" src="https://gw.alipayobjects.com/zos/antfincdn/v4TlwgORE/qq_qr_code.JPG"> - </a> </p> diff --git a/s2-site/docs/common/development.zh.md b/s2-site/docs/common/development.zh.md index b284351730..68e7f8d96c 100644 --- a/s2-site/docs/common/development.zh.md +++ b/s2-site/docs/common/development.zh.md @@ -1,4 +1,12 @@ -跃跃欲试想贡献?[查看贡献指南](https://s2.antv.antgroup.com/manual/contribution) +跃跃欲试想贡献?[查看贡献指南](https://s2.antv.antgroup.com/manual/contribution) , 欢迎 [Pull Request](https://github.com/antvis/S2/pulls),或给我们 [报告 Bug](https://github.com/antvis/S2/issues/new?assignees=&labels=&projects=&template=bug-report.md&title=%F0%9F%90%9B). + +> 强烈建议花一点你的宝贵时间阅读: + +- [《提 Issue 前必读》](https://github.com/antvis/S2/issues/1904) +- [《如何向开源项目提交无法解答的问题》](https://zhuanlan.zhihu.com/p/25795393) +- [《如何有效地报告 Bug》](https://www.chiark.greenend.org.uk/~sgtatham/bugs-cn.html) +- [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way) +- [《如何向开源社区提问题》](https://github.com/seajs/seajs/issues/545) > S2 使用 pnpm 作为包管理器 diff --git a/s2-site/docs/common/export.zh.md b/s2-site/docs/common/export.zh.md index 152f818a2a..8757d38d58 100644 --- a/s2-site/docs/common/export.zh.md +++ b/s2-site/docs/common/export.zh.md @@ -7,10 +7,10 @@ order: 8 组件层的复制,导出等功能,基于核心层 `@antv/s2` 透出的一系列工具方法封装,可以根据实际业务,基于工具方法自行封装 -```tsx -import { copyData, copyToClipboard, download } from '@antv/s2' +```ts +import { copyData, copyToClipboard, download, registerTransformer, CopyMIMEType } from '@antv/s2' -// 拿到复制数据 +// 拿到复制数据 (选中的单元格) const data = copyData(spreadsheet, '\t', false) // 复制数据到剪贴板 @@ -23,17 +23,15 @@ copyToClipboard(data) console.log('复制失败') }) -// 导出数据 +// 导出数据 (filename.csv) download(data, 'filename') - -// 自定义导出类型 +// 自定义复制导出转换 (复制到 word、语雀等场景会成为一个空表格) registerTransformer(CopyMIMEType.HTML, (matrix) => { return `<td></td>` }) const data = copyData(spreadsheet, '\t', false) -// 复制到word、语雀等场景会成为一个空表格 ``` @@ -46,7 +44,7 @@ const data = copyData(spreadsheet, '\t', false) | spreadsheet | s2 实例 | [SpreadSheet](/docs/api/basic-class/spreadsheet) | | ✓ | | split | 分隔符 | `string` | | ✓ | | formatOptions | 是否格式化,可以分别对数据单元格和行列头进行格式化,传布尔值会同时对单元格和行列头生效。 | <code> boolean \| { isFormatHeader?: boolean, isFormatData?: boolean} </code> | `false` | | -| customTransformer | 导出时支持自定义(transformer)数据导出格式化方法 | <code> (transformer: Transformer) => Partial<Transformer> </code> | `transformer` | | +| customTransformer | 导出时支持自定义 (transformer) 数据导出格式化方法 | <code> (transformer: Transformer) => Partial<Transformer> </code> | `transformer` | | | isAsyncExport | 是否异步导出 | boolean | false | | ### copyToClipboard @@ -74,5 +72,5 @@ export interface Transformer { | 参数 | 说明 | 类型 | 默认值 | 必选 | | --- | --- |--------------------------|-----| --- | -| type | 复制内容的MIMEType | `CopyMIMEType` | | ✓ | +| type | 复制内容的 MIMEType | `CopyMIMEType` | | ✓ | | transformer | 处理函数 | `MatrixHTMLTransformer | MatrixPlainTransformer` | | ✓ | diff --git a/s2-site/docs/common/header-action-icon.zh.md b/s2-site/docs/common/header-action-icon.zh.md index 3d42bc3d60..8894107073 100644 --- a/s2-site/docs/common/header-action-icon.zh.md +++ b/s2-site/docs/common/header-action-icon.zh.md @@ -23,9 +23,9 @@ | 参数 | 功能描述 | 类型 | 默认值 | 必选 | | --- | --- | --- | --- | --- | -| iconName | 当前点击的 icon 名称 | string | | ✓ | -| meta |当前 cell 的 meta 信息| Node | | ✓ | -| event |当前点击事件信息| Event |false| ✓ | +| iconName | 当前 icon 名称 | string | | ✓ | +| meta |当前 cell 的 meta 信息| [Node](/api/basic-class/node) | | ✓ | +| event |当前点击事件信息| Event | false | ✓ | ## CustomSVGIcon diff --git a/s2-site/docs/common/icon.zh.md b/s2-site/docs/common/icon.zh.md index 58a8b63450..9838273b7a 100644 --- a/s2-site/docs/common/icon.zh.md +++ b/s2-site/docs/common/icon.zh.md @@ -7,12 +7,12 @@ order: 3 | icon 名称 | icon 图标 | 功能描述 | icon 名称 | icon 图标 | 功能描述 | | ------------- | --------------------- | ---------- | ---------------- | ----------- | ------------------ | -| CellDown | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632471683806-41687600-9f55-49f7-8210-25c438b8152e.png" height=30> | 同环比下降 | ExpandColIcon | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632472462583-40f32d2a-0a26-4e4f-8ebf-39603c3b8939.png" height=30> | 明细表隐藏展开 | +| CellDown | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632471683806-41687600-9f55-49f7-8210-25c438b8152e.png" height=30> | 同环比下降 | ExpandColIcon | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632472462583-40f32d2a-0a26-4e4f-8ebf-39603c3b8939.png" height=30> | 展开列头 | | CellUp | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632471716079-9bc714c4-0b4e-4176-a2b9-d620251d30d6.png" height=30> | 同环比上升 | Plus | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632475581023-4a53ecff-942c-45ff-8dc5-1c5b08e7b157.png" height=30> | 树状表格展开 | | GlobalAsc | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632471780679-5a7ee62d-73be-4713-945d-6b03f2786e8d.png" height=30> | 全局升序 | Minus | <img alt="icon" src="https://gw.alipayobjects.com/zos/antfincdn/dKGwptOOB9/34d9064e-eaee-4160-ad84-a08f4ef1fee4.png" height=30> | 树状表格收起 | -| GlobalDesc | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632471882478-bdbe6981-ce4b-4082-b6ad-f13577329147.png" height=30> | 全局降序 | SortDown | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632473030451-4aed635f-d192-470b-91e6-5bfed9fac595.png" height=30> | 明细表降序 | -| GroupAsc | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632471962652-722d8fec-9bee-4a85-9cc1-ac4f51f483c6.png" height=30> | 组内升序 | SortDownSelected | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632472951651-80c2949e-7b03-4a64-a283-1c4e37fc5e60.png" height=30> | 明细表降序选择状态 | -| GroupDesc | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632472173126-d751f07a-10c4-44fb-a916-362f2ba611e6.png" height=30> | 组内降序 | SortUp | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632473083059-12d7b39e-1a59-4584-b2f6-4608ee9e04fb.png" height=30> | 明细表升序 | -| Trend | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632473312620-593aeff4-c618-4b2e-bc26-136a751efff9.png" height=30> | 趋势图 | SortUpSelected | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632473154460-1a7c66bc-7f3f-4c46-a6e1-a586d566b94c.png" height=30> | 明细表升序选择状态 | +| GlobalDesc | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632471882478-bdbe6981-ce4b-4082-b6ad-f13577329147.png" height=30> | 全局降序 | SortDown | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632473030451-4aed635f-d192-470b-91e6-5bfed9fac595.png" height=30> | 降序 | +| GroupAsc | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632471962652-722d8fec-9bee-4a85-9cc1-ac4f51f483c6.png" height=30> | 组内升序 | SortDownSelected | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632472951651-80c2949e-7b03-4a64-a283-1c4e37fc5e60.png" height=30> | 降序选中状态 | +| GroupDesc | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632472173126-d751f07a-10c4-44fb-a916-362f2ba611e6.png" height=30> | 组内降序 | SortUp | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632473083059-12d7b39e-1a59-4584-b2f6-4608ee9e04fb.png" height=30> | 升序 | +| Trend | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632473312620-593aeff4-c618-4b2e-bc26-136a751efff9.png" height=30> | 趋势图 | SortUpSelected | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632473154460-1a7c66bc-7f3f-4c46-a6e1-a586d566b94c.png" height=30> | 升序选中状态 | | ArrowUp | <img alt="icon" src="https://gw.alipayobjects.com/zos/antfincdn/g9lTlN2xG/84042923-69b2-4ccc-89b4-1b2b5aa45d68.png" height=30> | 指标上升 |ArrowDown | <img alt="icon" src="https://gw.alipayobjects.com/zos/antfincdn/OjQEFxclz/c7f5cce0-16e4-4522-987a-ae21ab9f24fa.png" height=30> | 指标下降 | | DrillDownIcon | <img alt="icon" src="https://intranetproxy.alipay.com/skylark/lark/0/2021/png/315626/1632473411428-4959bde8-ead3-4c81-921d-26035bee21ae.png" height=30> | 下钻 | | | | diff --git a/s2-site/docs/common/interaction.zh.md b/s2-site/docs/common/interaction.zh.md index 27ccfd9fcf..8f47a35f1b 100644 --- a/s2-site/docs/common/interaction.zh.md +++ b/s2-site/docs/common/interaction.zh.md @@ -15,7 +15,7 @@ order: 5 | enableCopy | 是否允许复制 | `boolean` | `false` | | | copyWithHeader | 复制数据是否带表头信息 | `boolean` | `false` | | | copyWithFormat | 是否使用 field format 格式复制 | `boolean` | `false` | | -| customTransformer | 复制时支持自定义(transformer)数据格式化方法 | <code> (transformer: [Transformer](/docs/api/components/export#transformer)) => Partial<Transformer> </code> | `transformer` | | +| customTransformer | 复制时支持自定义 (transformer) 数据格式化方法 | <code> (transformer: [Transformer](/docs/api/components/export#transformer)) => Partial<Transformer> </code> | `transformer` | | | customInteractions | 自定义交互 [详情](/docs/manual/advanced/interaction/custom) | [CustomInteraction[]](#custominteraction) | | | | scrollSpeedRatio | 用于控制滚动速率,分水平和垂直两个方向,默认为 1 | [ScrollSpeedRatio](#scrollspeedratio) | | | | autoResetSheetStyle | 用于控制点击表格外区域和按下 esc 键时是否重置交互状态 | `boolean` | `true` | | diff --git a/s2-site/docs/common/packages.zh.md b/s2-site/docs/common/packages.zh.md index 3b0767f32b..01f71f584a 100644 --- a/s2-site/docs/common/packages.zh.md +++ b/s2-site/docs/common/packages.zh.md @@ -3,8 +3,23 @@ title: Packages order: 5 --- +- `@antv/s2`: 基于 `Canvas` 和 [AntV/G](https://g.antv.vision/zh/docs/guide/introduce) 开发,提供基本的表格展示/交互等能力。 +- `@antv/s2-react`: 基于 `React` 和 `@antv/s2` 封装,提供配套的分析组件,配置项和 `@antv/s2` 通用。 +- `@antv/s2-vue`: 基于 `Vue3` 和 `@antv/s2` 封装,配置项和 `@antv/s2` 通用,如果你想在 `Vue2` 中使用,请使用 `@antv/s2`。 + +也就是说 `@antv/s2` 和**框架无关**,你也可以在 `Vue`, `Angular` 等框架中直接使用。 + | 版本号 | 稳定版 | 测试版 | 预览版 | 先行版 | 包大小 | 下载量 | | -------- | ------ | --------- | ---------- | ---------- | ---------- | ------ | | [@antv/s2](https://github.com/antvis/S2/tree/master/packages/s2-core) | ![latest](https://img.shields.io/npm/v/@antv/s2/latest.svg) | ![beta](https://img.shields.io/npm/v/@antv/s2/beta.svg) | ![alpha](https://img.shields.io/npm/v/@antv/s2/alpha.svg) | ![next](https://img.shields.io/npm/v/@antv/s2/next.svg) | ![size](https://img.badgesize.io/https:/unpkg.com/@antv/s2@latest/dist/index.min.js?label=gzip%20size&compression=gzip) | ![download](https://img.shields.io/npm/dm/@antv/s2.svg) | | [@antv/s2-react](https://github.com/antvis/S2/tree/master/packages/s2-react) | ![latest](https://img.shields.io/npm/v/@antv/s2-react/latest.svg) | ![beta](https://img.shields.io/npm/v/@antv/s2-react/beta.svg) | ![alpha](https://img.shields.io/npm/v/@antv/s2-react/alpha.svg) | ![next](https://img.shields.io/npm/v/@antv/s2-react/next.svg)| ![size](https://img.badgesize.io/https:/unpkg.com/@antv/s2-react@latest/dist/index.min.js?label=gzip%20size&compression=gzip) | ![download](https://img.shields.io/npm/dm/@antv/s2-react.svg) | | [@antv/s2-vue](https://github.com/antvis/S2/tree/master/packages/s2-vue) | ![latest](https://img.shields.io/npm/v/@antv/s2-vue/latest.svg) | ![beta](https://img.shields.io/npm/v/@antv/s2-vue/beta.svg) | ![alpha](https://img.shields.io/npm/v/@antv/s2-vue/alpha.svg) | ![next](https://img.shields.io/npm/v/@antv/s2-vue/next.svg) | ![size](https://img.badgesize.io/https:/unpkg.com/@antv/s2-vue@latest/dist/index.min.js?label=gzip%20size&compression=gzip) | ![download](https://img.shields.io/npm/dm/@antv/s2-vue.svg) | + +:::info{title='如何获取新版本发布通知?'} + +- 订阅:[https://github.com/antvis/S2/releases.atom](https://github.com/antvis/S2/releases.atom) 来获得新版本发布的通知。 +- 加入钉钉交流群,新版本发布后,会通过🤖 群机器人推送。 +- `Watch` [S2 代码仓库](https://github.com/antvis/S2), 选择 `Custom - Releases` 来获取消息推送。 + +![preview](https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*NKYFSKFV_scAAAAAAAAAAAAADmJ7AQ/original) +::: diff --git a/s2-site/docs/common/totals.zh.md b/s2-site/docs/common/totals.zh.md index ea6876cdae..2d92fae22d 100644 --- a/s2-site/docs/common/totals.zh.md +++ b/s2-site/docs/common/totals.zh.md @@ -27,6 +27,8 @@ object **必选**,_default:null_ 功能描述: 小计总计配置 | subLabel | 小计别名 | `string` | | | | calcGrandTotals | 自定义计算总计 | [CalcTotals](#calctotals) | | | | calcSubTotals | 自定义计算小计 | [CalcTotals](#calctotals) | | | +| totalsGroupDimensions | 总计的分组维度 |`string[]` | | | +| subTotalsGroupDimensions | 小计的分组维度 | `string[]` | | | ## CalcTotals diff --git a/s2-site/docs/manual/advanced/custom/hook.zh.md b/s2-site/docs/manual/advanced/custom/hook.zh.md index a395b2e60c..b527c4ce92 100644 --- a/s2-site/docs/manual/advanced/custom/hook.zh.md +++ b/s2-site/docs/manual/advanced/custom/hook.zh.md @@ -31,23 +31,23 @@ order: 1 ## dataCell -改变数据单元格的默认实现,需要继承自 [dataCell](https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/data-cell.ts),覆盖某些方法,比如字体样式、背景样式等。[例子](/examples/custom/custom-cell#data-cell) +修改数据单元格的默认实现,需要继承自 [DataCell](https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/data-cell.ts) (明细表对应 [TableDataCell](https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/table-data-cell.ts)),复写某些方法,比如字体样式、背景样式等。[例子](/examples/custom/custom-cell#data-cell) ## rowCell -改变行头单元格的默认实现,需要继承自 [rowCell](https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/row-cell.ts),覆盖某些方法,比如字体样式、背景样式等。[例子](/examples/custom/custom-cell#row-cell) +修改行头单元格的默认实现,需要继承自 [RowCell](https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/row-cell.ts),复写某些方法,比如字体样式、背景样式等。[例子](/examples/custom/custom-cell#row-cell) ## colCell -改变列头单元格的默认实现,需要继承自 [colCell](https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/col-cell.ts),覆盖某些方法,比如字体样式、背景样式等。[例子](/examples/custom/custom-cell#col-cell) +修改列头单元格的默认实现,需要继承自 [ColCell](https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/col-cell.ts)(明细表对应 [TableColCell](https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/table-col-cell.ts)),复写某些方法,比如字体样式、背景样式等。[例子](/examples/custom/custom-cell#col-cell) ## cornerCell -改变角头单元格的默认实现,需要继承自 [cornerCell](https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/corner-cell.ts),覆盖某些方法,比如字体样式、背景样式等。[例子](/examples/custom/custom-cell#corner-cell) +修改角头单元格的默认实现,需要继承自 [TableCornerCell](https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/corner-cell.ts)(明细表对应 [TableCornerCell](https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/table-corner-cell.ts)),复写某些方法,比如字体样式、背景样式等。[例子](/examples/custom/custom-cell#corner-cell) ## cornerHeader -改变角头的默认实现,需要继承自 [Group](https://g.antv.vision/zh/docs/api/group),覆盖某些方法,比如渲染内容更换等。[例子](/examples/custom/custom-cell#corner-cell) +修改角头的默认实现,需要继承自 [Group](https://g.antv.vision/zh/docs/api/group),复写某些方法,比如渲染内容更换等。[例子](/examples/custom/custom-cell#corner-header) ## frame diff --git a/s2-site/docs/manual/advanced/data-process/pivot.zh.md b/s2-site/docs/manual/advanced/data-process/pivot.zh.md index f41430eea3..febb01385a 100644 --- a/s2-site/docs/manual/advanced/data-process/pivot.zh.md +++ b/s2-site/docs/manual/advanced/data-process/pivot.zh.md @@ -5,7 +5,7 @@ order: 1 本文会介绍透视表的数据流处理过程,让读者更直观的了解 `S2` 内部数据逻辑。 -数据处理流程是:`原始数据 -> 生成多维数组 -> 生成层级结构 -> 获取数据` ,接下来我们会逐一讲解,目标是实现下图透视表: +数据处理流程是:`原始数据 -> 生成 indexesData 多维数据 -> 生成层级结构 -> 获取数据` ,接下来我们会逐一讲解,目标是实现下图透视表: <img src="https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*J2fuRIJnQdgAAAAAAAAAAAAAARQnAQ" alt="s2-data-process-demo" width="600" /> @@ -58,196 +58,145 @@ const options = { /> ``` -## 生成多维数组 +## 生成 indexesData 多维数据 -首先,处理第三条数据,提取当前明细数据在初始配置条件下的行、列维度结果。 +首先,处理数据,提取当前明细数据在初始配置条件下的行、列维度结果。 ```ts -// 第四条数据 +// 以第四条数据为例 // { "price": 4,"province": "浙江省","city": "绍兴市","type": "家具","sub_type": "沙发" } -const rowDimensionValues = transformDimensionsValue(currentData, ['province', 'city']); // 结果是 ['浙江省', '绍兴市'] -const colDimensionValues = transformDimensionsValue(currentData, ['type', 'sub_type']); // 结果是 ['家具', '沙发'] +const rowDimensionValues = transformDimensionsValues(currentData, ['province', 'city']); // 结果是 ['浙江省', '绍兴市'] +const colDimensionValues = transformDimensionsValues(currentData, ['type', 'sub_type']); // 结果是 ['家具', '沙发'] ``` 然后,根据数据的行列维度结果和初始配置条件,我们可以获取到当前明细数据的路径(即在行树结构和列树结构的坐标索引) ```ts -const rowPath = getPath(rowDimensionValues); // 结果是 [0, 1]; 因为浙江下面有杭州和绍兴,所以绍兴坐标为 1,下同。 -const colPath = getPath(colDimensionValues); // 结果是 [0, 1]; -const dataPath = rowPath.concat(...colPath); // 结果是 [0, 1, 0, 1]; - -lodash.set(indexesData, dataPath, currentData); // [0, 1, 0, 1] 是 { "price": 4,"province": "浙江省","city": "绍兴市","type": "家具","sub_type": "沙发" } +// 以第四条数据为例 +const prefix = 'province[&]city[&]type[&]sub_type'; +// 第 0 位 始终是小计、总计的专属位,明细数据都是从第 1 位开始 +const rowPath = getDataPath(rowDimensionValues); // 结果是 [1, 2]; +const colPath = getDataPath(colDimensionValues); // 结果是 [1, 2]; +const dataPath =[prefix, ...rowPath.concat(...colPath)] ; // 结果是 ['province[&]city[&]type[&]sub_type', 1, 2, 1, 2]; +const indexesData={}; +lodash.set(indexesData, dataPath, currentData); ``` -最后,按照上述流程,遍历所有数据,得到最终的多维数组,结果是: +最后,按照上述流程,遍历所有数据,得到最终的 indexesData,结果是: ```ts -[ - [ +{ + "province[&]city[&]type[&]sub_type": [ + null, [ + null, [ - [{ - "price": 1, - "province": "浙江省", - "city": "杭州市", - "type": "家具", - "sub_type": "桌子", - "$$extra$$": "price", - "$$value$$": 1 - }], - [{ - "price": 3, - "province": "浙江省", - "city": "杭州市", - "type": "家具", - "sub_type": "沙发", - "$$extra$$": "price", - "$$value$$": 3 - }] - ] - ], - [ + null, + [ + null, + [ + null, + { + "price": 1, + "province": "浙江省", + "city": "杭州市", + "type": "家具", + "sub_type": "桌子" + } + ], + [ + null, + { + "price": 3, + "province": "浙江省", + "city": "杭州市", + "type": "家具", + "sub_type": "沙发" + } + ] + ] + ], [ - [{ - "price": 2, - "province": "浙江省", - "city": "绍兴市", - "type": "家具", - "sub_type": "桌子", - "$$extra$$": "price", - "$$value$$": 2 - }], - [{ - "price": 4, - "province": "浙江省", - "city": "绍兴市", - "type": "家具", - "sub_type": "沙发", - "$$extra$$": "price", - "$$value$$": 4 - }] + null, + [ + null, + [ + null, + { + "price": 2, + "province": "浙江省", + "city": "绍兴市", + "type": "家具", + "sub_type": "桌子" + } + ], + [ + null, + { + "price": 4, + "province": "浙江省", + "city": "绍兴市", + "type": "家具", + "sub_type": "沙发" + } + ] + ] ] ] ] -] +} ``` ## 生成层级结构 接下来是按照数据结构,分别生成行列的树状结构。我们知道,存储明细数据的 Meta 结构一般有三种:扁平数组、图、树,对于表场景查询频率非常高,透视表本身的展现形式也表达了一种树形结构,因此我们选择了构建树形结构来实现 Meta。 -下面,我们以行树结构为例,讲解 `S2` 中层级结构的构造过程。 +下面,我们以行树结构为例,生成的 Map 结构是:<br/>![rowPivotMeta](https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*BScNTbO2TrIAAAAAAAAAAAAADmJ7AQ/original) -首先,拿到某条数据的行维度枚举值: +## 获取数据 -```ts -// 第四条数据 -// { "price": 4,"province": "浙江省","city": "绍兴市","type": "家具","sub_type": "沙发" } -const rowDimensionValues = transformDimensionsValue(currentData, ['province', 'city']); // 结果是 ['浙江省', '绍兴市'] -``` +### 获取单个数据 -然后,遍历此条数据的行维度枚举: +当渲染透视表数据单元格时,需要获取对应的展示内容(数据)。举个例子,需要右下角单元格数据时,代码如下: ```ts -let currentMeta = this.rowPivotMeta; // 存储行树形结构 Map. -for (let i = 0; i < rowDimensionValues.length; i++) { // 遍历 ['浙江省', '绍兴市']; - if (isFirstCreate) { - currentMeta.set(rowDimensionValues[i], { // currentMeta = - level: currentMeta.size, - children: new Map(), - }); - } - const meta = this.rowPivotMeta.get(value); - currentMeta = meta?.children; -} +const data = getCellData({ + query: { province: '浙江省', city: '绍兴市', type: '家具', sub_type: '沙发', $$extra$$: 'price' } +}); ``` -第一次循环 `['浙江省', '绍兴市']` 时,`currentMeta` 的结果是: +实现过程是,先拿到行、列维度枚举值: ```ts -Map(1) { - [[entries]] => [{ - '浙江省' => { key: '浙江省', value: { children: Map(0)}}) - }] -} +const rowDimensionValues = transformDimensionsValues(query, ['province', 'city']); // ['浙江省', '绍兴市'] +const colDimensionValues = transformDimensionsValues(query, ['type', 'sub_type', '$$extra$$']); // ['家具', '沙发', 'price'] ``` -第二次循环的结果是: - -```ts -Map(1) { - [[Entries]] => [{ - '浙江省' => { key: '浙江省', value: { - children: Map(1) { - [[Entries]] => [{ - '绍兴市' => { key: '绍兴市', value: { children: Map(0)}} - }] - } - }} - }] -} -``` - -当遍历一条明细数据后,变成 `浙江省 => 绍兴市` 这样层级结构。当遍历完所有明细数据后,最终的行层级结构如下: +然后通过枚举值获取数据查询路径,并从前面生成的多维数组中拿到具体数据。 ```ts -Map(1) { - [[Entries]] => [{ - '浙江省' => { key: '浙江省', value: { - childField: 'city', - children: Map(2) { - [[Entries]] => [{ - '杭州市' => { key: '杭州市', value: { children: Map(0)}} - }, { - '绍兴市' => { key: '绍兴市', value: { children: Map(0)}} - }] - } - }} - }] -} +const path = getDataPath({ rowDimensionValues, colDimensionValues }); +const rowData = lodash.get(indexesData, path); ``` -列的最终层级结构如下: +在拿到数据后,我们需要为原始数据增加 `$$extra$$` 等信息,用于标识所选择的具体是哪个维度,因此,内部使用了 Proxy 对 rowData 进行包裹,对其行为进行增强。 ```ts -Map(1) { - [[Entries]] => [{ - '家具' => { key: '家具', value: { - childField: 'sub_type', - children: Map(2) { - [[Entries]] => [{ - '桌子' => { key: '桌子', value: { children: Map(0)}} - }, { - '沙发' => { key: '沙发', value: { children: Map(0)}} - }] - } - }} - }] -} -``` +const data=new Proxy(rowData,handler); -## 获取数据 - -当渲染透视表数据单元格时,需要获取对应的展示内容(数据)。举个例子,需要右下角单元格数据时,代码如下: - -```ts -const data = getCellData({ - query: { province: '浙江省', city: '绍兴市', type: '家具', sub_type: '沙发', $$extra$$: 'price' } -}); +console.log({...data}); //{ "price": 4,"province": "浙江省","city": "绍兴市","type": "家具","sub_type": "沙发", "$$extra$$": "price", "$$value$$": 4 } ``` -实现过程是,先拿到行、列维度枚举值: +### 获取多个数据 -```ts -const rowDimensionValues = getQueryDimValues(['province', 'city'], query); // ['浙江省', '绍兴市'] -const colDimensionValues = getQueryDimValues(['type', 'sub_type', '$$extra$$'], query); // ['家具', '沙发', 'price'] -``` +如果想获取多个单元格数据,其大致流程和获取单个数据一致;获取多个单元格数据允许有维度缺失,会返回多个符合 query 的数据。 -然后通过枚举值获取数据查询路径,并从前面生成的多维数组中拿到具体数据。 +比如获取*浙江省*下的所有信息示例代码如下: ```ts -const path = getDataPath({ rowDimensionValues, colDimensionValues }); // [0, 1, 0, 1, 0] -const data = lodash.get(indexesData, path); +const dataList = getMultiData({ + query: { province: '浙江省', $$extra$$: 'price' } +}); ``` 总结下,获取数据是通过查询条件,构造当前查询条件对应的数据路径,然后从多维数组中直接拿取。 diff --git a/s2-site/docs/manual/advanced/get-cell-data.zh.md b/s2-site/docs/manual/advanced/get-cell-data.zh.md index c55d25d67b..f77a923696 100644 --- a/s2-site/docs/manual/advanced/get-cell-data.zh.md +++ b/s2-site/docs/manual/advanced/get-cell-data.zh.md @@ -196,9 +196,9 @@ s2.on(S2Event.DATA_CELL_CLICK, (event) => { ```ts // 找到 "舟山市" 对应的行头单元格节点 -const rowCellNode = s2.facet.getRowCellNodes().find((node) => node.id === 'root[&]浙江省[&]舟山市') +const rowCellNode = s2.facet.getRowCellNodes().find((node) => node.id === 'root[&] 浙江省 [&] 舟山市') // 找到 "办公用品" 下 "纸张" 对应的 "数量"列头单元格节点 -const colCellNode = s2.facet.getColCellNodes().find((node) => node.id === 'root[&]办公用品[&]纸张[&]number') +const colCellNode = s2.facet.getColCellNodes().find((node) => node.id === 'root[&] 办公用品 [&] 纸张 [&]number') const data = s2.dataSet.getCellMultiData({ query: { @@ -208,17 +208,15 @@ const data = s2.dataSet.getCellMultiData({ }) /** - [ - { - "number": 1634, - "province": "浙江省", - "city": "舟山市", - "type": "办公用品", - "sub_type": "纸张", - "$$extra$$": "number", - "$$value$$": 1634 - } - ] + { + "number": 1634, + "province": "浙江省", + "city": "舟山市", + "type": "办公用品", + "sub_type": "纸张", + "$$extra$$": "number", + "$$value$$": 1634 + } */ ``` diff --git a/s2-site/docs/manual/advanced/interaction/basic.en.md b/s2-site/docs/manual/advanced/interaction/basic.en.md index afb6672918..e50116f862 100644 --- a/s2-site/docs/manual/advanced/interaction/basic.en.md +++ b/s2-site/docs/manual/advanced/interaction/basic.en.md @@ -187,18 +187,18 @@ When the mouse selects a cell or brushes a selected cell, the row and column hea // 当 selectedCellHighlight 为 boolean 时 const s2Options = { interaction: { - selectedCellHighlight: true // 默认 false, 当 selectedCellsSpotlight 为 true 时,会高亮 rowHeader 和 colHeader (兼容未拓展类型前的设计) + selectedCellHighlight: true // default is false } }; -// 同时还可以分别配置 selectedCellHighlight 中 header 和 cells 的高亮 -const S2Options = { +// You can also configure the highlighting of header and cells in selectedCellHighlight separately +const s2Options = { interaction: { selectedCellHighlight: { - rowHeader: true, // 选中单元格时,高亮行头 - colHeader: true, // 选中单元格时,高亮列头 - rowCells: false, // 选中单元格时,高亮当前行 - colCells: false, // 选中单元格时,高亮当前列 + rowHeader: true, // Highlight row header when cell is selected + colHeader: true, // Highlight column header when cells are selected + currentRow: false, // Highlight the current row when a cell is selected + currentCol: false, // Highlight the current column when a cell is selected }, }, }; diff --git a/s2-site/docs/manual/advanced/interaction/basic.zh.md b/s2-site/docs/manual/advanced/interaction/basic.zh.md index 8ab17de892..b23f25b4f8 100644 --- a/s2-site/docs/manual/advanced/interaction/basic.zh.md +++ b/s2-site/docs/manual/advanced/interaction/basic.zh.md @@ -27,7 +27,7 @@ order: 0 | 行/列头手动调整宽高 | `S2Event.LAYOUT_RESIZE` | 鼠标悬浮在行/列头单元格边缘,出现指示条和光标,按住鼠标左键拖动,调整宽高 | | 刷选 | `S2Event.DATA_CELL_BRUSH_SELECTION` `S2Event.GLOBAL_SELECTED` | 批量选中刷选范围内的数值单元格,刷选过程中,显示刷选范围提示蒙层,刷选完成后,弹出 tooltip, 展示被刷选单元格信息和数量 | | 行头刷选 | `S2Event.ROW_CELL_BRUSH_SELECTION` `S2Event.GLOBAL_SELECTED` | 批量选中刷选范围内的行头单元格,刷选过程中,显示刷选范围提示蒙层,刷选完成后,弹出 tooltip, 展示被刷选单元格信息(仅支持透视表) | -| 列头刷选 | `S2Event.COL_CELL_BRUSH_SELECTION` `S2Event.GLOBAL_SELECTED` | 批量选中刷选范围内的列头单元格,刷选过程中,显示刷选范围提示蒙层,刷选完成后,弹出 tooltip, 展示被刷选单元格信息(仅支持透视表) | +| 列头刷选 | `S2Event.COL_CELL_BRUSH_SELECTION` `S2Event.GLOBAL_SELECTED` | 批量选中刷选范围内的列头单元格,刷选过程中,显示刷选范围提示蒙层,刷选完成后,弹出 tooltip, 展示被刷选单元格信息 | | 区间快捷多选 | `S2Event.GLOBAL_SELECTED` | 单选单元格 (start), 然后按住 `Shift` 再次选中一个单元格 (end), 选中两个单元格区间所有单元格 | | 悬停 | `S2Event.GLOBAL_HOVER` | 鼠标悬停时,对应单元格高亮展示,如果是数值单元格,则默认 [十字高亮](/docs/manual/advanced/interaction/basic#行列联动高亮),可设置 `hoverHighlight: false` 关闭 | | 复制 | `S2Event.GLOBAL_COPIED` | 复制选中的单元格数据 | @@ -174,6 +174,13 @@ const s2Options = { const s2Options = { interaction: { hoverHighlight: false // 默认 true + // 等同于 + // hoverHighlight: { + // rowHeader = false, // 高亮悬停格子所在行头 + // colHeader = false, // 高亮悬停格子所在列头 + // currentRow = false, // 高亮悬停格子所在行 + // currentCol = false, // 高亮悬停格子所在列 + // }, } }; ``` @@ -199,8 +206,8 @@ const s2Options = { selectedCellHighlight: { rowHeader: true, // 选中单元格时,高亮行头 colHeader: true, // 选中单元格时,高亮列头 - rowCells: false, // 选中单元格时,高亮当前行 - colCells: false, // 选中单元格时,高亮当前列 + currentRow: false, // 选中单元格时,高亮当前行 + currentCol: false, // 选中单元格时,高亮当前列 }, }, }; @@ -451,6 +458,12 @@ const s2Options = { }; ``` +## 调整交互主题 + +<Playground path='interaction/basic/demo/state-theme.ts' rid='container' height='300'></playground> + +可以通过 [主题配置](https://s2.antv.antgroup.com/api/general/s2-theme#interactionstatename) 对应的 [交互主题](https://s2.antv.antgroup.com/api/general/s2-theme#interactionstatename), 调整选中/悬停/圈选等交互主题。 + ## 调用 API `S2` 内置了一些交互相关的 `API`,统一挂载在 `s2.interaction` 命名空间下,你可以在拿到 [SpreadSheet 实例](/docs/api/basic-class/spreadsheet) 后调用它们来实现你的效果,比如 `选中所有单元格`, `获取列头单元格` 等常用方法,具体请查看 [Interaction 实例类](/docs/api/basic-class/interaction) diff --git a/s2-site/docs/manual/basic/analysis/copy-export.zh.md b/s2-site/docs/manual/basic/analysis/copy-export.zh.md index ed3d130023..68b53b19a3 100644 --- a/s2-site/docs/manual/basic/analysis/copy-export.zh.md +++ b/s2-site/docs/manual/basic/analysis/copy-export.zh.md @@ -5,11 +5,11 @@ order: 11 ## 简介 -复制与导出的内容都可以直接放入 Excel 中进行展示,S2 已经完成了格式上的兼容。 +复制与导出的内容都可以直接放入 `Excel` 中进行展示,S2 已经完成了格式上的兼容。 ### 复制 -此功能可快速将表格内容复制到剪切板 +此功能可快速将表格内容复制到剪切板中, `@antv/s2` 核心层提供了基础的复制功能,可配置 `enableCopy` 开启 #### 全量复制 @@ -26,41 +26,54 @@ S2 的导出组件,分别提供了原始数据的复制和格式化后数据 #### 局部复制 -使用快捷键 `command/ctrl + c` 即可复制选中区域(局部复制) +通过 [内置交互](/manual/advanced/interaction/basic) (单选/多选/圈选), 使用快捷键 `Command/Ctrl + C` 即可复制选中区域(局部复制) ```ts const s2Options = { interaction: { // 是否开启复制 enableCopy: true, + // 复制格式化后的数据 (s2DataConfig.meta 中配置的 formatter) + copyWithFormat: false, + // 复制数值时是否携带所对应的行列头维值 + copyWithHeader: true, // 圈选复制前,需要开启圈选功能 brushSelection: { - dataCell: true, // 默认开启 - rowCell: true, - colCell: true, + dataCell: true, // 圈选数值单元格 (默认开启) + rowCell: true, // 圈选行头单元格 + colCell: true, // 圈选列头单元格 } } }; ``` -- 复制到 Excel +##### 复制粘贴到 Excel + +<img alt="excelCopy" src="https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*LzTYTpFosccAAAAAAAAAAAAAARQnAQ" width="600"/> + +<br/> + +##### 复制粘贴到富文本编辑器中 (带 `HTML` 格式) + +<img alt="HTMLCopy" src="https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*DuHCSbpv_XkAAAAAAAAAAAAAARQnAQ" width="600"/> + +<br/> -<img alt="excelCopy" src="https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*LzTYTpFosccAAAAAAAAAAAAAARQnAQ" width="600"> +##### 复制行头单元格 -- 复制带 HTML 格式 +<img alt="CopyCol" src="https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*_NukQpysLC8AAAAAAAAAAAAAARQnAQ" width="600"/> -<img alt="HTMLCopy" src="https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*DuHCSbpv_XkAAAAAAAAAAAAAARQnAQ" width="600"> +<br/> -- 复制行头内容 +##### 复制列头单元格 -<img alt="CopyCol" src="https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*_NukQpysLC8AAAAAAAAAAAAAARQnAQ" width="600"> +<img alt="CopyRow" src="https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*ncuAQaL4AvAAAAAAAAAAAAAAARQnAQ" width="600"/> -- 复制列头内容 +<br/> -<img alt="CopyRow" src="https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*ncuAQaL4AvAAAAAAAAAAAAAAARQnAQ" width="600"> +##### 带表头复制 -- 带表头复制 -**copyWithHeader**: 复制数据是否带表头信息,默认为 `false` +**copyWithHeader**: 复制数据时是否携带相对应**行列表头**信息,默认为 `false` ```ts const s2Options = { @@ -71,13 +84,43 @@ const s2Options = { }; ``` -<img alt="copyWithHeader" src="https://gw.alipayobjects.com/zos/antfincdn/wSBjSYKSM/3eee7bc2-7f8e-4dd9-8836-52a978d9718a.png" width="600"> +<img alt="copyWithHeader" src="https://gw.alipayobjects.com/zos/antfincdn/wSBjSYKSM/3eee7bc2-7f8e-4dd9-8836-52a978d9718a.png" width="600" /> -### 导出 +<br/> + +##### 复制格式化后的数据 -`@antv/s2-react` 组件层提供了导出功能 +**copyWithFormat**: 当 `S2DataConfig` 的 `meta` 中配置了 [自定义格式函数时](/api/general/s2-data-config#meta), 是否按照 `formatter` 复制数据 ```ts +const s2DataConfig = { + fields: { ... } + meta: [ + { + field: 'city', + name: '城市', + formatter: (value) => `${value}-xx` + } + ] +} + +const s2Options = { + interaction: { + enableCopy: true, + copyWithFormat: true, + } +}; +``` + +### 导出 + +`@antv/s2-react` 组件层提供了开箱即用的导出功能 + +:::info{title='使用 `@antv/s2` 如何导出?'} +`@antv/s2` 内置了一系列工具函数,[见下方文档](#原始导出方法) +::: + +```tsx import { SheetComponent } from '@antv/s2-react' <SheetComponent diff --git a/s2-site/docs/manual/basic/analysis/drill-down.en.md b/s2-site/docs/manual/basic/analysis/drill-down.en.md index fded7be227..6b69b06161 100644 --- a/s2-site/docs/manual/basic/analysis/drill-down.en.md +++ b/s2-site/docs/manual/basic/analysis/drill-down.en.md @@ -29,7 +29,7 @@ const&nbsp;sex&nbsp;=&nbsp;[&nbsp;'男',&nbsp;'女' const&nbsp;PartDrillDown&nbsp;=&nbsp;{ &nbsp;&nbsp;drillConfig:&nbsp;{ -&nbsp;&nbsp;&nbsp;&nbsp;dataSet:&nbsp;[&nbsp;//&nbsp;下钻数据源配置 +&nbsp;&nbsp;&nbsp;&nbsp;dataSet:&nbsp;[&nbsp;//&nbsp; 下钻数据源配置 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;name:&nbsp;'客户性别', &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value:&nbsp;'sex', @@ -38,7 +38,7 @@ const&nbsp;PartDrillDown&nbsp;=&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;], &nbsp;&nbsp;}, -&nbsp;&nbsp;//&nbsp;点击下钻后的回调 +&nbsp;&nbsp;//&nbsp; 点击下钻后的回调 &nbsp;&nbsp;fetchData:&nbsp;(meta,&nbsp;drillFields)&nbsp;=> &nbsp;&nbsp;&nbsp;&nbsp;new&nbsp;Promise((resolve)&nbsp;=>&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;dataSet&nbsp;=&nbsp;meta.spreadsheet.dataSet; @@ -71,8 +71,8 @@ const&nbsp;PartDrillDown&nbsp;=&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resolve({ -&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;drillField:&nbsp;field,&nbsp;//&nbsp;下钻维度&nbsp;value&nbsp;值 -&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;drillData:&nbsp;drillDownData,&nbsp;//&nbsp;下钻数据 +&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;drillField:&nbsp;field,&nbsp;//&nbsp; 下钻维度&nbsp;value&nbsp; 值 +&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;drillData:&nbsp;drillDownData,&nbsp;//&nbsp; 下钻数据 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}); &nbsp;&nbsp;&nbsp;&nbsp;}), }; diff --git a/s2-site/docs/manual/basic/analysis/drill-down.zh.md b/s2-site/docs/manual/basic/analysis/drill-down.zh.md index 5ee3ec96e4..e135baed43 100644 --- a/s2-site/docs/manual/basic/analysis/drill-down.zh.md +++ b/s2-site/docs/manual/basic/analysis/drill-down.zh.md @@ -88,24 +88,25 @@ const PartDrillDown = { ```jsx import React from 'react'; -import ReactDOM from 'react-dom'; import { SheetComponent } from '@antv/s2-react'; +import '@antv/s2-react/dist/style.min.css'; const s2Options = { hierarchyType: 'tree', // 树形结构 }; -ReactDOM.render( - <SheetComponent - dataCfg={s2DataConfig} - options={s2Options} - partDrillDown={PartDrillDown} - />, - document.getElementById('container'), -); +const App = () => { + return ( + <SheetComponent + dataCfg={s2DataConfig} + options={s2Options} + partDrillDown={PartDrillDown} + /> + ) +} ``` -<Playground path='react-component/drill-dwon/demo/for-pivot.tsx' rid='container'></playground> +<Playground path='react-component/drill-down/demo/for-pivot.tsx' rid='container'></playground> ## 使用场景 diff --git a/s2-site/docs/manual/basic/analysis/editable-mode.zh.md b/s2-site/docs/manual/basic/analysis/editable-mode.zh.md index f368ffc1ca..813be1d588 100644 --- a/s2-site/docs/manual/basic/analysis/editable-mode.zh.md +++ b/s2-site/docs/manual/basic/analysis/editable-mode.zh.md @@ -3,21 +3,25 @@ title: 编辑表 order: 3 --- -## 明细表简介 +## 简介 -编辑表是 `S2` 明细表的衍生形态之一。在提供完整的明细表的分析功能之外,还支持对数据的修改操作。 +编辑表是 `S2` 明细表的衍生形态之一,基于 `React` 版本的明细表封装,在提供完整的明细表的分析功能之外,还支持对数据的修改操作。 <img alt="editable-mode" src="https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*9RoBT5FIJG0AAAAAAAAAAAAAARQnAQ" width="600"> ## 使用 +:::warning{title="注意"} +编辑表的原理本质上是在 `Canvas` 表格上增加一个 `div` 蒙层,来实现对数据的编辑,如果想在 `@antv/s2` 和 `@antv/s2-vue` 中使用,请自行参考 [React 版本的实现](https://github.com/antvis/S2/blob/b81b7957b9e8b8e1fbac9ebc6cacdf45a14e5412/packages/s2-react/src/components/sheets/editable-sheet/index.tsx#L7) 进行封装。 +::: + +<Playground path='react-component/sheet/demo/editable' rid='container'></playground> + ```html <div id="container"></div> ``` -### React 组件方式 - -```typescript +```tsx import React from "react"; import ReactDOM from "react-dom"; import { SheetComponent } from '@antv/s2-react'; @@ -160,17 +164,19 @@ const s2Options = { // 4, 渲染 ReactDOM.render( <SheetComponent - sheetType="editable" // 此处指定sheetType为editable + sheetType="editable" // 此处指定 sheetType 为 editable dataCfg={s2DataCfg} options={s2Options} + onDataCellEditEnd={(meta) => { + console.log('onDataCellEditEnd', meta); + }} />, document.getElementById('container') ); ``` -## 特性 +## 效果 -效果如图: -<img src="https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*9RoBT5FIJG0AAAAAAAAAAAAAARQnAQ" width="600" alt="preview" /> +[查看示例](/examples/react-component/sheet#editable) -[playground 地址](/examples/react-component/sheet#editable) +<img src="https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*9RoBT5FIJG0AAAAAAAAAAAAAARQnAQ" width="600" alt="preview" /> diff --git a/s2-site/docs/manual/basic/analysis/mobile-component.zh.md b/s2-site/docs/manual/basic/analysis/mobile-component.zh.md index a6a60e5ddf..7d48213eca 100644 --- a/s2-site/docs/manual/basic/analysis/mobile-component.zh.md +++ b/s2-site/docs/manual/basic/analysis/mobile-component.zh.md @@ -44,7 +44,7 @@ export const DEFAULT_MOBILE_OPTIONS: Readonly<S2Options> = { width: mobileWidth - 40, height: 380, style: { - layoutWidthType: LayoutWidthTypes.ColAdaptive, + layoutWidthType: LayoutWidthType.ColAdaptive, }, interaction: { hoverHighlight: false, diff --git a/s2-site/docs/manual/basic/analysis/pagination.en.md b/s2-site/docs/manual/basic/analysis/pagination.en.md index c46b9eeaaa..586a20553c 100644 --- a/s2-site/docs/manual/basic/analysis/pagination.en.md +++ b/s2-site/docs/manual/basic/analysis/pagination.en.md @@ -35,6 +35,17 @@ items to use it out of the box. |-----|-----|-----|-----|-----| | showPagination | Whether to display the default pagination<br> (only if the `pagination` attribute is configured in `options`, it will take effect) | `boolean` | `{ <br>onShowSizeChange?: (pageSize: number) => void,<br> >onChange?: (current: number) => void <br>}` | `false` | +📢 It should be noted that in the @antv/s2-react version, the type of `showPagination` is: + +```ts +type ShowPagination = + | boolean + | { + onShowSizeChange?: (current: number, pageSize: number) => void, + onChange?: (current: number, pageSize: number) => void + } +``` + ### React version > The [Ant Design](https://ant.design/components/pagination-cn/) Pagination paging component is used, which diff --git a/s2-site/docs/manual/basic/analysis/pagination.zh.md b/s2-site/docs/manual/basic/analysis/pagination.zh.md index 091f487aac..ce3c076544 100644 --- a/s2-site/docs/manual/basic/analysis/pagination.zh.md +++ b/s2-site/docs/manual/basic/analysis/pagination.zh.md @@ -29,8 +29,19 @@ S2 内置提供了分页能力。本质上是前端分页,点击下一页滚 ``` | 参数 | 说明 | 类型 | 默认值 | 必选 | -|-----|-----|-----|-----|-----| -| showPagination | 是否显示默认分页<br>(只有在 `options` 配置过 `pagination` 属性才会生效) | `boolean` | `{ <br>onShowSizeChange?: (pageSize: number) => void,<br>onChange?: (current: number) => void <br>}` | `false` | +| -- | -- | -- | -- | --- | +| showPagination | 是否显示默认分页<br>(只有在 `options` 配置过 `pagination` 属性才会生效) | `boolean` \| \{ <br>onShowSizeChange?: (pageSize: number) => void,<br>onChange?: (current: number) => void <br>} | `false` | | + +📢 需要特别注意的是:在 @antv/s2-react 版本中,`showPagination` 的类型是: + +```ts +type ShowPagination = + | boolean + | { + onShowSizeChange?: (current: number, pageSize: number) => void, + onChange?: (current: number, pageSize: number) => void + } +``` ### React 版 diff --git a/s2-site/docs/manual/basic/analysis/strategy.zh.md b/s2-site/docs/manual/basic/analysis/strategy.zh.md index 7d577bf8b6..0373d33f85 100644 --- a/s2-site/docs/manual/basic/analysis/strategy.zh.md +++ b/s2-site/docs/manual/basic/analysis/strategy.zh.md @@ -28,7 +28,7 @@ order: 9 <details> <summary>查看详情</summary> -```js +```ts const s2Options = { width: 600, height: 480, @@ -71,21 +71,20 @@ const s2Options = { </details> -```ts +```tsx import React from "react"; -import ReactDOM from "react-dom"; import { SheetComponent } from "@antv/s2-react"; import '@antv/s2-react/dist/style.min.css'; -ReactDOM.render( - <SheetComponent - sheetType="strategy" - dataCfg={s2DataConfig} - options={s2Options} - />, - document.getElementById('container'), -); - +const App = () => { + return ( + <SheetComponent + dataCfg={s2DataCfg} + options={s2Options} + sheetType="strategy" + /> + ) +} ``` <Playground path='react-component/sheet/demo/strategy.tsx' rid='container'></playground> @@ -238,7 +237,13 @@ const s2Options = { <Playground path='react-component/sheet/demo/strategy-mini-chart.tsx' rid='container2'></playground> -配置如下: +### 在普通透视表中使用 + +如果不依赖 `React`, 想在 `@antv/s2` 普通的透视表中使用 mini 图,可以参考这个 [示例](/zh/examples/custom/custom-cell/#mini-chart) + +<Playground path='custom/custom-cell/demo/mini-chart.ts' rid='container3'></playground> + +### API <embed src="@/docs/common/mini-chart.zh.md"></embed> diff --git a/s2-site/docs/manual/basic/analysis/switcher.zh.md b/s2-site/docs/manual/basic/analysis/switcher.zh.md index a4d604f42a..9a4e505757 100644 --- a/s2-site/docs/manual/basic/analysis/switcher.zh.md +++ b/s2-site/docs/manual/basic/analysis/switcher.zh.md @@ -32,18 +32,17 @@ const switcherFields = { ```js import React from "react"; -import ReactDOM from "react-dom"; import { Switcher } from "@antv/s2-react"; const onSubmit = (result) => { console.log("result:", result); }; -ReactDOM.render( - <Switcher {...switcherFields} onSubmit={onSubmit} />, - document.getElementById("container") -); - +const App = () => { + return ( + <Switcher {...switcherFields} onSubmit={onSubmit} /> + ) +} ``` <Playground path='react-component/switcher/demo/pure-switcher.tsx' rid='container'></playground> diff --git a/s2-site/docs/manual/basic/sheet-type/pivot-mode.zh.md b/s2-site/docs/manual/basic/sheet-type/pivot-mode.zh.md index cc6e38d1dc..581bb302c6 100644 --- a/s2-site/docs/manual/basic/sheet-type/pivot-mode.zh.md +++ b/s2-site/docs/manual/basic/sheet-type/pivot-mode.zh.md @@ -83,7 +83,7 @@ ReactDOM.render( 📊 查看 [React 版本透视表示例](/examples/react-component/sheet#pivot) 和 [API 文档](/api/components/sheet-component)。 -### 类方式 +### PivotSheet 类方式 如果不打算依赖 `React`,可以在上面第三步之后直接调用: @@ -96,3 +96,75 @@ s2.render(); ``` 📊 查看 [类方式透视表示例](/examples/basic/pivot#grid) 和 [API 文档](/api/general/s2options)。 + +## 特性 + +### 展示形态 + +默认支持 [平铺模式](/zh/examples/basic/pivot/#grid) 和 [树状模式](/zh/examples/basic/pivot/#tree) 两种展示形态。 + +### 数据汇总 + +支持 [小计/总计](/manual/basic/totals) 的透视能力。 + +### 冻结行头 + +当行头固定时,行头会有一个独立的可滚动区域,如果关闭冻结行头,则滚动区域为整个表格。 + +<Playground path='interaction/basic/demo/frozen-row-header.ts' rid='frozen-row-header' height='300'></playground> + +<br/> + +```ts +const s2Options = { + frozenRowHeader: false, // 默认开启 +} +``` + +<img src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*kk0ETbbbnOsAAAAAAAAAAAAADmJ7AQ/original" width="600" alt="preview"> + +### 冻结首行 <Badge type="success">@antv/s2@^1.53.0 新增</Badge> + +:::info{title="注意"} + +目前仅提供**冻结首行**能力,和 [明细表行列冻结](https://s2.antv.antgroup.com/manual/basic/sheet-type/table-mode#%E8%A1%8C%E5%88%97%E5%86%BB%E7%BB%93) 不同,透视表由于带有分组的特性,布局比较复杂,考虑到交互合理性,目前有如下限制: + +- 首行不存在子节点(适用于总计置于顶部,只有单个维值,树状模式等场景)。 +- 分页场景暂不支持。 + +`s2Options` 中配置 `frozenFirstRow` 开启首行冻结能力 + +::: + +#### 平铺模式 + +```ts +const s2Options = { + frozenFirstRow: true, + hierarchyType: 'grid', + // 需要开启行总计 & 总计行置于顶部 + totals: { + row: { + showGrandTotals: true, + reverseLayout: true, + }, + }, +} +``` + +<Playground path='interaction/advanced/demo/frozen-pivot-grid.ts' rid='container-grid' height='300'></playground> + +<br/> + +#### 树状模式 + +```ts +const s2Options = { + frozenFirstRow: true, + hierarchyType: 'tree', +} +``` + +<Playground path='interaction/advanced/demo/frozen-pivot-tree.ts' rid='container-tree' height='300'></playground> + +<br/> diff --git a/s2-site/docs/manual/basic/sheet-type/table-mode.zh.md b/s2-site/docs/manual/basic/sheet-type/table-mode.zh.md index ef69c7c391..4d88a0b4d7 100644 --- a/s2-site/docs/manual/basic/sheet-type/table-mode.zh.md +++ b/s2-site/docs/manual/basic/sheet-type/table-mode.zh.md @@ -101,11 +101,12 @@ s2.render(); ### 序号 -在 `s2Options` 中传入 `showSeriesNumber` 即可展示内置的序号。[查看 demo](/examples/basic/table#table) +在 `s2Options` 中传入 `showSeriesNumber` 即可展示内置的序号,可以自定义序号列标题。[查看 demo](/examples/basic/table#table) ```ts const s2Options = { - showSeriesNumber: true + showSeriesNumber: true, + seriesNumberText: '自定义序号标题' // 默认 "序号" } ``` @@ -113,6 +114,10 @@ const s2Options = { 行列冻结让特定行列在滚动时保持固定,从而一直保持在视口范围内,提供信息的对照和参考。[查看 demo](/examples/interaction/basic#frozen) +<Playground path='interaction/basic/demo/frozen.ts' rid='container' height='300'></playground> + +<br/> + 行列冻结通过在 `s2Options` 中传入这些属性控制: ```ts @@ -129,5 +134,3 @@ const s2Options = { 效果如图: <img src="https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*tZkOSqYWVFQAAAAAAAAAAAAAARQnAQ" width="600" alt="preview" /> - -<Playground path='interaction/basic/demo/frozen.ts' rid='container' height='300'></playground> diff --git a/s2-site/docs/manual/basic/sort/advanced.zh.md b/s2-site/docs/manual/basic/sort/advanced.zh.md index 1f32f98f4d..69e9130be0 100644 --- a/s2-site/docs/manual/basic/sort/advanced.zh.md +++ b/s2-site/docs/manual/basic/sort/advanced.zh.md @@ -11,15 +11,14 @@ order: 1 使用 `@antv/s2-react` 的 `SheetComponent` 组件 ,并给 `header` 配置 `advancedSortCfg` ,配置具体信息可查看 [AdvancedSortCfgProps](/docs/api/components/advanced-sort#advancedsortcfgprops) -```ts -import React, { useState } from 'react'; -import ReactDOM from 'react-dom'; +```tsx +import React from 'react'; import { SortParams } from '@antv/s2'; import { SheetComponent } from '@antv/s2-react'; import '@antv/s2-react/dist/style.min.css'; -const AdvancedSortDemo = () => { - const [dataCfg, setDataCfg] = useState(s2DataConfig); +export const AdvancedSortDemo = () => { + const [dataCfg, setDataCfg] = React.useState(s2DataConfig); return ( <SheetComponent @@ -39,34 +38,39 @@ const AdvancedSortDemo = () => { /> ); }; - -ReactDOM.render(<AdvancedSortDemo />, document.getElementById('container')); - ``` ## 配置 ### 显示 -```ts -advancedSortCfg: { - open: true, -} +```tsx +<SheetComponent + header={{ + advancedSortCfg: { + open: true, + }, + }} +/> ``` -<img src="https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*E4dxS6EpfHEAAAAAAAAAAAAAARQnAQ" width = "600" alt="row" /> +<img src="https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*E4dxS6EpfHEAAAAAAAAAAAAAARQnAQ" width="600" alt="row" /> ### 提交 通过 `onSortConfirm` 函数透出所选规则数据 `ruleValues` 和处理成表可直接用的数据 `sortParams` ```ts -advancedSortCfg: { - open: true, - onSortConfirm: (ruleValues: RuleValue[], sortParams: SortParams) => { - console.log(ruleValues, sortParams) - }, -}, +<SheetComponent + header={{ + advancedSortCfg: { + open: true, + onSortConfirm: (ruleValues: RuleValue[], sortParams: SortParams) => { + console.log(ruleValues, sortParams) + } + }, + }} +/> ``` @@ -79,7 +83,7 @@ advancedSortCfg: { | 参数 | 说明 | 类型 | 默认值 | 必选 | | --------------- | ------------------ | ---------------------- | ------ | ---- | | className | class 类名称 | `string` | - | | -| icon | 排序按钮图标 | `React.ReactNode` | - | | +| icon | 排序按钮图标 | `ReactNode` | - | | | text | 排序按钮名称 | `ReactNode` | - | | | ruleText | 规则描述 | `string` | - | | @@ -107,12 +111,12 @@ advancedSortCfg: { 支持自定义规则配置列表,不配置默认为:`首字母、手动排序、其他字段` ->注意:如果这里自定义,则需在 onSortConfirm 中通过 ruleValues 自定义 sortParams +> 注意:如果这里自定义,则需在 onSortConfirm 中通过 ruleValues 自定义 sortParams | 属性 | 类型 | 必选 | 默认值 | 功能描述 | | ------- | ------------------------------------------ | --- | ----- | --------- | | label | `string` | | ✓ | 规则名称 | -| value | `'sortMethod' | 'sortBy' | 'sortByMeasure'` | ✓ | | 规则值 | +| value | `'sortMethod' \| 'sortBy' \| 'sortByMeasure'` | ✓ | | 规则值 | | children | `RuleOption[]` | | ✓ | 规则子列表 | <img src="https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*V2PWTItVICQAAAAAAAAAAAAAARQnAQ" width = "600" alt="row" /> diff --git a/s2-site/docs/manual/basic/theme.zh.md b/s2-site/docs/manual/basic/theme.zh.md index e0953d51b0..16788f5ef6 100644 --- a/s2-site/docs/manual/basic/theme.zh.md +++ b/s2-site/docs/manual/basic/theme.zh.md @@ -106,7 +106,7 @@ S2 内置 3 套主题效果: 📊 查看更多 [主题示例](/examples/theme/default#default)。 -### 自定义 schema +### 自定义 Schema 如果内置的主题不满意你的要求,那么你可以通过自定义 `schema` 的方式重写特定的配置。 @@ -128,6 +128,8 @@ s2.render(false); <Playground path="theme/custom/demo/custom-schema.ts" rid='custom-schema'></playground> +<br/> + #### 自定义单元格对齐方式 [查看详情](https://s2.antv.antgroup.com/manual/advanced/custom/cell-align) 和 [完整 API](/api/general/s2theme#s2theme) @@ -173,6 +175,14 @@ s2.setTheme({ }); ``` +#### 自定义交互样式 + +[查看文档](/manual/advanced/interaction/basic#%E8%B0%83%E6%95%B4%E4%BA%A4%E4%BA%92%E4%B8%BB%E9%A2%98) [查看示例](/zh/examples/interaction/basic#state-theme) + +<Playground path='interaction/basic/demo/state-theme.ts' rid='state-theme' height='300'></playground> + +<br/> + ### 自定义色板 自定义 `schema` 虽然灵活,但是心智负担比较重,需要对 `schema` 的结构有比较详细的了解。因此我们还提供了自定义色板功能,此时你需要为 `setThemeCfg` 配置`palette`对象。[查看完整色板配置](/docs/api/general/S2Theme#palette): diff --git a/s2-site/docs/manual/basic/tooltip.zh.md b/s2-site/docs/manual/basic/tooltip.zh.md index 5106e4b71d..5c0e751db7 100644 --- a/s2-site/docs/manual/basic/tooltip.zh.md +++ b/s2-site/docs/manual/basic/tooltip.zh.md @@ -311,6 +311,27 @@ const s2Options = { }; ``` +通过实例方法调用同理,[查看更多配置](/api/basic-class/base-tooltip#tooltipshowoptions) + +```ts +s2.showTooltip({ + options: { + operator: { + menus: [ + { + key: 'custom-a', + text: '操作 1', + icon: 'Trend', + onClick: (cell) => { + console.log('操作 1 点击', cell); + }, + } + ], + }, + } +}) +``` + <br/> <Playground path='react-component/tooltip/demo/custom-operation.tsx' rid='container-custom-operations' height='300'></playground> diff --git a/s2-site/docs/manual/basic/totals.zh.md b/s2-site/docs/manual/basic/totals.zh.md index de1c0c1be4..54ce2a3c0c 100644 --- a/s2-site/docs/manual/basic/totals.zh.md +++ b/s2-site/docs/manual/basic/totals.zh.md @@ -5,7 +5,7 @@ order: 5 ## 简介 -小计总计属于表的透视功能,可以给行和列分别配置小计总计。 +小计总计属于表的透视功能,可以给行头和列头分别配置小计总计。 ### 小计 @@ -15,13 +15,17 @@ order: 5 平铺模式下,给当前维度额外增加一行/列 -<img src="https://gw.alipayobjects.com/zos/antfincdn/sK5Rx1%26Sp/c4dcee0c-af4b-4be6-b665-c810eec78101.png" width = "600" alt="row" /> +<img src="https://gw.alipayobjects.com/zos/antfincdn/sK5Rx1%26Sp/c4dcee0c-af4b-4be6-b665-c810eec78101.png" width="600" alt="row" /> + +<br/> #### 形式二:挂靠节点 树状模式下,挂靠到当前节点所在行/列中 -<img src="https://gw.alipayobjects.com/zos/antfincdn/Ljeww3JNa/543f1a66-51e3-4134-a2ec-83fd6a64f7d9.png" width = "600" alt="row" /> +<img src="https://gw.alipayobjects.com/zos/antfincdn/Ljeww3JNa/543f1a66-51e3-4134-a2ec-83fd6a64f7d9.png" width="600" alt="row" /> + +<br/> ### 总计 @@ -33,19 +37,43 @@ order: 5 <img src="https://gw.alipayobjects.com/zos/antfincdn/9GwQ67LQ%26/c11b6f7b-ff0a-4ce3-89e7-1eccb95719a3.png" width="600" alt="row" /> +<br/> + 树状: -<img src="https://gw.alipayobjects.com/zos/antfincdn/MRc64qzqf/d77ae378-4512-45a8-b2e0-9fb7e4a19c45.png" width="600" alt="row" /> +<img src="https://gw.alipayobjects.com/zos/antfincdn/MRc64qzqf/d77ae378-4512-45a8-b2e0-9fb7e4a19c45.png" width="600" alt="row" /> + +<br/> #### 2. 多度量值 平铺: -<img src="https://gw.alipayobjects.com/zos/antfincdn/bPhcUuHCi/6cd43952-58fb-469a-b4bb-fdd142bf3317.png" width="600" alt="row" /> +<img src="https://gw.alipayobjects.com/zos/antfincdn/bPhcUuHCi/6cd43952-58fb-469a-b4bb-fdd142bf3317.png" width="600" alt="row" /> + +<br/> 树状: -<img src="https://gw.alipayobjects.com/zos/antfincdn/GekvQBQAw/8dde8830-e496-458c-b05e-bcd4f3e4bc0c.png" width="600" alt="row" /> +<img src="https://gw.alipayobjects.com/zos/antfincdn/GekvQBQAw/8dde8830-e496-458c-b05e-bcd4f3e4bc0c.png" width="600" alt="row" /> + +<br/> + +### 分组汇总 + +按维度进行 `小计/总计` 的汇总计算,用于进行某一维度的数据对比分析等。 + +#### 行总计/行小计分组 + +<Playground path='analysis/totals/demo/dimension-group-row.ts' rid='pivot-total-group-row' height='400'></playground> + +<br/> + +#### 列总计/列小计分组 + +<Playground path='analysis/totals/demo/dimension-group-col.ts' rid='pivot-total-group-col' height='400'></playground> + +<br/> ## 使用 @@ -59,8 +87,8 @@ object **必选**,_default:null_ 功能描述: 小计总计配置 | 参数 | 说明 | 类型 | 默认值 | 必选 | | ---- | ------ | --------------------------------------------- | ------ | ---- | -| row | 列总计 | [Total](/docs/api/general/S2Options#total) | {} | | -| col | 行总计 | [Total](/docs/api/general/S2Options#total) | {} | | +| row | 列总计 | [Total](/docs/api/general/S2Options#total) | - | | +| col | 行总计 | [Total](/docs/api/general/S2Options#total) | - | | #### Total @@ -87,6 +115,8 @@ const s2Options = { reverseGrandTotalsLayout: true, reverseSubTotalsLayout: true, subTotalsDimensions: ['province'], + totalsGroupDimensions: ['city'], + subTotalsGroupDimensions: ['type', 'sub_type'], }, col: { showGrandTotals: true, @@ -103,36 +133,36 @@ const s2Options = { #### 1. 数据传入 -数据根据行/列位置以及 key 值传入,维度 key 值没有包含所有行、列的 key,举例如下: +数据根据行/列位置以及 `key` 值传入,维度 `key` 值没有包含所有行、列的 `key`,举例如下: ```typescript [ - // 总计/总计 - { - price: '15.5', - }, - // 浙江/总计 - { - province: '浙江', - price: '5.5', - }, - // 浙江-杭州/总计 - { - province: '浙江', - city: '杭州', - price: '3', - }, - // 总计/笔 - { - type: '笔', - price: '10', - }, - // 浙江-小计/笔 - { - province: "浙江", - type: "笔", - price: "3" - }, + // 总计/总计 + { + price: '15.5', + }, + // 浙江/总计 + { + province: '浙江', + price: '5.5', + }, + // 浙江-杭州/总计 + { + province: '浙江', + city: '杭州', + price: '3', + }, + // 总计/笔 + { + type: '笔', + price: '10', + }, + // 浙江-小计/笔 + { + province: "浙江", + type: "笔", + price: "3" + }, ] ``` @@ -154,16 +184,16 @@ const s2DataConfig = { }, ], ... -}; +} ``` #### 2. 计算出数据 可以给 `totals` 下的 `row` 、 `col` 分别配置属性 `calcGrandTotals` 、 `calcSubTotals` 来实现计算汇总数据 -##### 1. 配置聚合方式 +##### 2.1. 配置聚合方式 -通过配置 `aggregation` 来实现,聚合方式目前支持 `SUM` (求和)、 `MIN` (最小值)、 `MAX` (最大值)和 `AVG` (算术平均) 。 +通过配置 `aggregation` 来实现,聚合方式目前支持 `SUM` (求和)、 `MIN` (最小值)、 `MAX` (最大值)和 `AVG` (算术平均)。 [查看示例](https://s2.antv.antgroup.com/zh/examples/analysis/totals/#calculate) ```ts const s2Options = { @@ -198,9 +228,9 @@ const s2Options = { }; ``` -##### 2. 配置自定义方法 +<br/> -通过配置 `calcFunc: (query: Record<string, any>, arr: Record<string, any>[]) => number` 来实现 +##### 2.2. 配置自定义方法 ```ts const s2Options = { diff --git a/s2-site/docs/manual/faq.zh.md b/s2-site/docs/manual/faq.zh.md index e398b58424..0b82587814 100644 --- a/s2-site/docs/manual/faq.zh.md +++ b/s2-site/docs/manual/faq.zh.md @@ -4,7 +4,7 @@ order: 8 --- :::warning{title="一些建议"} -**在提出问题前,请确保你已经仔细阅读了一遍文档,查看了相关图表示例,并且已经查看了常见问题。** +**在提出问题前,请确保你已经仔细阅读了一遍文档,查看了相关图表示例,并且已经查看了常见问题和 Issues。** ::: ## 1. 使用问题 @@ -135,10 +135,70 @@ s2.render(false) 请查看 [这篇文章](/docs/manual/advanced/get-cell-data) -### 为什么 tooltip 在 `@antv/s2` 中不显示,在 `@antv/s2-react` `@antv/s2-vue` 中可以正常显示? +### 为什么 Tooltip 在 `@antv/s2` 中不显示,在 `@antv/s2-react` `@antv/s2-vue` 中可以正常显示? 请查看 [Tooltip 注意事项](/docs/manual/basic/tooltip#%E7%AE%80%E4%BB%8B) +### 如何在点击或悬停单元格的时候自定义 Tooltip? + +请查看相关文档和示例 + +- [Tooltip 自定义教程](https://s2.antv.antgroup.com/manual/basic/tooltip#%E8%87%AA%E5%AE%9A%E4%B9%89) +- [自定义点击显示 Tooltip](/examples/react-component/tooltip/#custom-click-show-tooltip) +- [自定义悬停显示 Tooltip](/examples/react-component/tooltip/#custom-hover-show-tooltip) + +### 如何在 Tooltip 里自定义操作项? + +- 方式 1: 默认 tooltip 内容不变,通过 [自定义操作项](https://s2.antv.antgroup.com/zh/examples/react-component/tooltip/#custom-operation), 在内容上方增加自定义操作菜单。 +- 方式 2: 通过 [自定义 Tooltip 内容](https://s2.antv.antgroup.com/zh/examples/react-component/tooltip/#custom-content), 完全自定义组件内容。 + +### React 组件,自定义显示 tooltip 后,内容未更新怎么回事? + +当手动调用实例方法 `s2.showTooltip` 时,如果内容是一个 React 自定义组件,且组件内容未更新时,可以尝试声明 `forceRender` 强制更新组件内容 + +```ts +s2.showTooltip({ + ..., + content: <YourComponent props={"A"}/> +}) + +s2.showTooltip({ + ..., + content: <YourComponent props={"B"} /> + options: { + forceRender: true + } +}) +``` + +相关 issue: <https://github.com/antvis/S2/issues/1716> + +### 使用 React 组件,Tooltip 莫名其妙被隐藏,不展示了? + +```tsx +<SheetComponent options={options} dataCfg={dataCfg}/> +``` + +- `场景 1`: 当组件重新渲染,或者配置项更新后,组件会 [更新 S2 底表的配置](https://github.com/antvis/S2/blob/master/packages/s2-react/src/hooks/useSpreadSheet.ts#L111), 会触发 [隐藏 Tooltip 的逻辑](https://github.com/antvis/S2/blob/master/packages/s2-core/src/sheet-type/spread-sheet.ts#L381), 请检查并尽量避免你的`上层组件更新`, 或者`配置项的引用被改变` 所导致的 `SheetComponent` 无意义的重渲染。 + +- `场景 2`: S2 默认点击非表格区域,会隐藏 tooltip, 还原交互状态,请确保你自己的业务逻辑有无相应的 `click` 事件,看是否有被冒泡影响,尝试阻止冒泡 + +```ts +event.stopPropagation() +``` + +- `场景 3`: 手动调用 `s2.showTooltip` 展示 tooltip 后,点击内部的某个元素后,再次展示第二个 tooltip, 这个时候 tooltip 被隐藏,和场景 2 类似,请给 `click` 事件增加冒泡处理。 + +```ts +// 菜单 1-1 => click + +s2.showTooltip({ ... }) + +// 菜单 1-1 => click +event.stopPropagation() +s2.showTooltip({ ... }) +``` + ### 如何在 Vue 中自定义 Tooltip? 可直接使用 S2 的 Vue3 版本 `@antv/s2-vue`, 或查看 [在 Vue3 中自定义](/docs/manual/basic/tooltip/#在-vue3-中自定义) @@ -165,9 +225,38 @@ s2.render(false) 请查看 [使用文档](/docs/manual/advanced/custom/cell-size#%E8%B0%83%E6%95%B4%E5%88%97%E5%A4%B4%E5%8D%95%E5%85%83%E6%A0%BC%E5%AE%BD%E9%AB%98) 和 [示例](/examples/gallery#category-%E8%87%AA%E5%AE%9A%E4%B9%89%E8%A1%8C%E5%88%97%E5%AE%BD%E9%AB%98) +### 如何关闭 hover 单元格出现的黑色边框? + +![preview](https://gw.alipayobjects.com/zos/antfincdn/nDIO0OG8fv/4ff6613f-fad3-4ea6-9473-0161509f692c.png) + +边框属于 `聚焦 (hoverFocus)` 交互状态的一种,可通过 [主题配置 - 交互通用主题](https://s2.antv.antgroup.com/api/general/s2-theme#interactionstatename) 关闭。 + +```ts +s2.setTheme({ + dataCell: { + cell: { + interactionState: { + hoverFocus: { + // 边框设置为透明 + borderColor: 'transparent' + // 或者边框透明度设置为 0 + // borderOpacity: 0 + } + } + } + } +}) +``` + +### 如何修改选中,悬停,刷选等单元格交互主题配置? + +请 [查看文档](/manual/advanced/interaction/basic#%E8%B0%83%E6%95%B4%E4%BA%A4%E4%BA%92%E4%B8%BB%E9%A2%98) 和 [示例](/zh/examples/interaction/basic#state-theme) + ### S2 支持对表格进行编辑吗? -请查看 [编辑模式示例](/examples/case/data-preview#excel) +请查看 [编辑模式示例](/examples/case/data-preview#excel) 和 [编辑表示例](https://s2.antv.antgroup.com/examples/react-component/sheet/#editable) + +目前只有 React 版本 `@antv/s2-react` 支持编辑表格,其他版本暂不支持,需参考 [源码](https://github.com/antvis/S2/blob/2d85d5739f5a3a52e92df699a935df93aa2a6a73/packages/s2-react/src/components/sheets/editable-sheet/index.tsx#L10) 自行实现 ### 如何注册 `AntV/G` 渲染引擎的插件? @@ -188,17 +277,7 @@ const s2Options = { } ``` -### S2 有对应的 `Vue` 或者 `Angular` 版本吗? - -目前,S2 由三个包构成 - -- `@antv/s2`: 基于 `canvas` 和 [AntV/G](https://g.antv.vision/zh/docs/guide/introduce) 开发,提供基本的表格展示/交互等能力 -- `@antv/s2-react`: 基于 `@antv/s2` 封装,提供配套的分析组件 -- `@antv/s2-vue`: 基于 `Vue3` 和 `@antv/s2` 封装,提供配套的分析组件 - -也就是说 `@antv/s2` 和**框架无关**,你可以在 `Vue`, `Angular` 等框架中使用。 - -以下是版本概览: +### S2 有对应的 `Vue` 或者 `Angular` 版本吗?如何获取新版本发布通知? <embed src="@/docs/common/packages.zh.md"></embed> @@ -257,6 +336,8 @@ const s2Options = { ### 有讨论群吗? +交流群不提供任何答疑,有任何问题请直接提交 [Issue](https://github.com/antvis/S2/issues/new/choose) 或者 [Discussion](https://github.com/antvis/S2/discussions/new?category=q-a), 当然,也期待你的 [Pull request](https://github.com/antvis/S2/pulls). + <embed src="@/docs/common/contact-us.zh.md"></embed> ## 2. 错误和警告 diff --git a/s2-site/docs/manual/getting-started.zh.md b/s2-site/docs/manual/getting-started.zh.md index 03f0b4bf31..baee9bc136 100644 --- a/s2-site/docs/manual/getting-started.zh.md +++ b/s2-site/docs/manual/getting-started.zh.md @@ -2,26 +2,30 @@ title: 快速上手 order: 1 --- + ## 📦 安装 -### npm | yarn 安装 +### 使用 npm 或 yarn 或 pnpm 安装 ```bash # npm -$ npm install @antv/s2 +$ npm install @antv/s2 --save # yarn -$ yarn add @antv/s2 +$ yarn add @antv/s2 --save + +# pnpm +$ pnpm install @antv/s2 --save ``` ### 使用 React 或 Vue3 版本 ```bash # React -$ yarn add @antv/s2 @antv/s2-react +$ yarn add @antv/s2 @antv/s2-react --save # Vue3 -$ yarn add @antv/s2 @antv/s2-vue +$ yarn add @antv/s2 @antv/s2-vue --save ``` ### 浏览器引入(不推荐) @@ -34,6 +38,8 @@ $ yarn add @antv/s2 @antv/s2-vue 创建 `S2` 表格有三种方式,基础类版本 `(s2-core)` 和 基于 `core` 层 封装的 `React` 和 `Vue3` 版本 +### 版本 + <embed src="@/docs/common/packages.zh.md"></embed> ### 基础类 @@ -188,18 +194,17 @@ run(); ### `React` 版本 -`S2` 提供了开箱即用的 `React` 版本 [表格组件](/examples/gallery#category-表格组件), 还有丰富的配套 [分析组件](/examples/gallery#category-Tooltip), 帮助开发者快速满足业务看数分析需求。 +`S2` 提供了开箱即用的 `React` 版本 [表格组件](examples/gallery#category-表格组件) +, 还有丰富的配套 [分析组件](/examples/gallery#category-Tooltip), 帮助开发者快速满足业务看数分析需求。 #### 表格组件使用 -```ts +```tsx import React from 'react'; import ReactDOM from 'react-dom'; import { SheetComponent } from '@antv/s2-react'; import '@antv/s2-react/dist/style.min.css'; -const container = document.getElementById('container'); - ReactDOM.render( <SheetComponent dataCfg={s2DataConfig} @@ -210,12 +215,11 @@ ReactDOM.render( ``` -#### 注意事项 - -`React` 版本的 `分析组件` 如:`高级排序`, `导出`, `下钻`, `Tooltip` 等组件基于 `antd` 组件库开发,如需使用,需要额外安装,并引入对应样式 +:::warning{title='注意事项'} +`React` 版本的 `分析组件` 如:`高级排序`, `导出`, `下钻`, `Tooltip` 等组件基于 `antd` 组件库开发,如需使用,需要额外安装,并引入对应样式。 -```ts -yarn add antd @ant-design/icons +```bash +yarn add antd @ant-design/icons --save ``` 📊 查看 [React 版本透视表 demo](/examples/react-component/sheet#pivot)。 @@ -228,8 +232,6 @@ import ReactDOM from 'react-dom'; import { MobileSheet } from '@antv/s2-react'; import '@antv/s2-react/dist/style.min.css'; -const container = document.getElementById('container'); - ReactDOM.render( <MobileSheet dataCfg={s2DataConfig} @@ -292,20 +294,26 @@ createApp(App).mount('#app'); ``` -#### 注意事项 +:::warning{title='注意事项'} -`Vue3` 版本的 `分析组件` 如:`高级排序`, `导出`, `下钻`, `Tooltip` 等组件基于 `ant-design-vue` 组件库开发,如需使用,需要额外安装,并引入对应样式 +`Vue3` 版本的 `分析组件` 如:`高级排序`, `导出`, `下钻`, `Tooltip` 等组件基于 `ant-design-vue` 组件库开发,如需使用,需要额外安装,并引入对应样式。 -```ts -yarn add ant-design-vue +```bash +yarn add ant-design-vue --save ``` +::: + ```ts import "@antv/s2-vue/dist/style.min.css"; ``` 📊 查看 [Vue3 版本透视表 demo](https://codesandbox.io/s/s2-vue-hwg64q)。 +## TypeScript + +`S2` 使用 `TypeScript` 开发,提供完整的类型定义文件,配合 `VS Code` 等编辑器可以获得良好的类型提示。 + ## ⌨️ 本地开发 <embed src="@/docs/common/development.zh.md"></embed> diff --git a/s2-site/docs/manual/introduction.zh.md b/s2-site/docs/manual/introduction.zh.md index 9b982f76fb..2fd3af8cc9 100644 --- a/s2-site/docs/manual/introduction.zh.md +++ b/s2-site/docs/manual/introduction.zh.md @@ -27,7 +27,7 @@ redirect_from: ## ❓ 什么是 S2 -[S2](https://github.com/antvis/s2) 是一个面向可视分析领域的数据驱动的表可视化引擎。"S" 取自于 "SpreadSheet" 的两个 "S","2" 代表了透视表中的行列两个维度。旨在提供美观、易用、高性能、易扩展的多维表格。 +[S2](https://github.com/antvis/s2) 是一个面向可视分析领域的数据驱动的表可视化引擎。`S` 取自于 `SpreadSheet` 的两个 `S`,`2` 代表了透视表中的行列两个维度。旨在提供美观、易用、高性能、易扩展的多维表格。 ![demos](https://gw.alipayobjects.com/zos/antfincdn/6R5Koawk9L/huaban%2525202.png) @@ -42,8 +42,9 @@ redirect_from: ## 📦 安装 ```bash -npm install @antv/s2 -# yarn add @antv/s2 +npm install @antv/s2 --save +# yarn add @antv/s2 --save +# pnpm install @antv/s2 --save ``` ## 🔨 使用 diff --git a/s2-site/examples/analysis/get-data/API.en.md b/s2-site/examples/analysis/get-data/API.en.md new file mode 100644 index 0000000000..b5bc01dac0 --- /dev/null +++ b/s2-site/examples/analysis/get-data/API.en.md @@ -0,0 +1,6 @@ +--- +title: API +order: 4 +--- + +<embed src="@/docs/api/basic-class/base-data-set.en.md"></embed> diff --git a/s2-site/examples/analysis/get-data/API.zh.md b/s2-site/examples/analysis/get-data/API.zh.md new file mode 100644 index 0000000000..fb2a104754 --- /dev/null +++ b/s2-site/examples/analysis/get-data/API.zh.md @@ -0,0 +1,6 @@ +--- +title: API +order: 4 +--- + +<embed src="@/docs/api/basic-class/base-data-set.zh.md"></embed> diff --git a/s2-site/examples/analysis/get-data/demo/get-cell-data.ts b/s2-site/examples/analysis/get-data/demo/get-cell-data.ts new file mode 100644 index 0000000000..a052fad192 --- /dev/null +++ b/s2-site/examples/analysis/get-data/demo/get-cell-data.ts @@ -0,0 +1,91 @@ +import { PivotSheet, EXTRA_FIELD } from '@antv/s2'; + +fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/4347c2dd-6554-451b-9d44-15b04e5de657.json', +) + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const s2DataConfig = { + fields: { + rows: ['province', 'city'], + columns: ['type'], + values: ['price'], + }, + meta: [ + { + field: 'province', + name: '省份', + }, + { + field: 'city', + name: '城市', + }, + { + field: 'type', + name: '商品类别', + }, + { + field: 'price', + name: '价格', + }, + ], + data, + }; + + const s2Options = { + width: 600, + height: 480, + selectedCellsSpotlight: true, + hoverHighlight: true, + tooltip: { + showTooltip: true, + }, + interaction: { + enableCopy: true, + }, + // 配置小计总计显示 + totals: { + row: { + showGrandTotals: true, + showSubTotals: true, + reverseLayout: true, + reverseSubLayout: true, + subTotalsDimensions: ['province'], + }, + col: { + showGrandTotals: true, + showSubTotals: true, + reverseLayout: true, + reverseSubLayout: true, + subTotalsDimensions: ['type'], + }, + }, + }; + const s2 = new PivotSheet(container, s2DataConfig, s2Options); + s2.render(); + + // 获取明细单元格 + const cellData = s2.dataSet.getCellData({ + query: { + province: '浙江', + city: '杭州', + type: '笔', + [EXTRA_FIELD]: 'price', + }, + }); + + console.log('单个数据', cellData); + + // 获取小计数据 + const subTotalData = s2.dataSet.getCellData({ + query: { + province: '浙江', + type: '笔', + [EXTRA_FIELD]: 'price', + }, + isTotals: true, + }); + + console.log('小计数据', subTotalData); + }); diff --git a/s2-site/examples/analysis/get-data/demo/get-multi-data.ts b/s2-site/examples/analysis/get-data/demo/get-multi-data.ts new file mode 100644 index 0000000000..59c9eeb8ee --- /dev/null +++ b/s2-site/examples/analysis/get-data/demo/get-multi-data.ts @@ -0,0 +1,93 @@ +import { PivotSheet, EXTRA_FIELD, QueryDataType } from '@antv/s2'; + +fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/4347c2dd-6554-451b-9d44-15b04e5de657.json', +) + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const s2DataConfig = { + fields: { + rows: ['province', 'city'], + columns: ['type'], + values: ['price'], + }, + meta: [ + { + field: 'province', + name: '省份', + }, + { + field: 'city', + name: '城市', + }, + { + field: 'type', + name: '商品类别', + }, + { + field: 'price', + name: '价格', + }, + ], + data, + }; + + const s2Options = { + width: 600, + height: 480, + selectedCellsSpotlight: true, + hoverHighlight: true, + tooltip: { + showTooltip: true, + }, + interaction: { + enableCopy: true, + }, + // 配置小计总计显示 + totals: { + row: { + showGrandTotals: true, + showSubTotals: true, + reverseLayout: true, + reverseSubLayout: true, + subTotalsDimensions: ['province'], + }, + col: { + showGrandTotals: true, + showSubTotals: true, + reverseLayout: true, + reverseSubLayout: true, + subTotalsDimensions: ['type'], + }, + }, + }; + const s2 = new PivotSheet(container, s2DataConfig, s2Options); + s2.render(); + + // 获取所有浙江下的数据 + const all = s2.dataSet.getMultiData( + { + province: '浙江', + [EXTRA_FIELD]: 'price', + }, + { + queryType: QueryDataType.All, + }, + ); + + console.log('所有数据', all); + + // 获取所有浙江下的明细数据 + const detail = s2.dataSet.getMultiData( + { + province: '浙江', + [EXTRA_FIELD]: 'price', + }, + { + queryType: QueryDataType.DetailOnly, + }, + ); + + console.log('所有明细数据', detail); + }); diff --git a/s2-site/examples/analysis/get-data/demo/meta.json b/s2-site/examples/analysis/get-data/demo/meta.json new file mode 100644 index 0000000000..0902e6e7ca --- /dev/null +++ b/s2-site/examples/analysis/get-data/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "获取单元格数据", + "en": "Get Cell Data" + }, + "demos": [ + { + "filename": "get-cell-data.ts", + "title": { + "zh": "获取单个单元格数据", + "en": "Get Single Cell Data" + }, + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/S4FZba2BY7/8e25e21c-28d3-4572-8585-f0b7435ab7c8.png" + }, + { + "filename": "get-multi-data.ts", + "title": { + "zh": "获取多个单元格数据", + "en": "Get Multi Cell Data" + }, + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/S4FZba2BY7/8e25e21c-28d3-4572-8585-f0b7435ab7c8.png" + } + ] +} diff --git a/s2-site/examples/analysis/get-data/index.en.md b/s2-site/examples/analysis/get-data/index.en.md new file mode 100644 index 0000000000..f8d73bc12d --- /dev/null +++ b/s2-site/examples/analysis/get-data/index.en.md @@ -0,0 +1,5 @@ +--- +title: Get Cell Data +order: 3 +--- + diff --git a/s2-site/examples/analysis/get-data/index.zh.md b/s2-site/examples/analysis/get-data/index.zh.md new file mode 100644 index 0000000000..70016e1895 --- /dev/null +++ b/s2-site/examples/analysis/get-data/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 获取单元格数据 +order: 3 +--- + + diff --git a/s2-site/examples/analysis/sort/demo/advanced.tsx b/s2-site/examples/analysis/sort/demo/advanced.tsx index d9bee18123..f5d4c0d878 100644 --- a/s2-site/examples/analysis/sort/demo/advanced.tsx +++ b/s2-site/examples/analysis/sort/demo/advanced.tsx @@ -6,7 +6,7 @@ import 'antd/es/cascader/style/index.css'; import '@antv/s2-react/dist/style.min.css'; fetch( - 'https://gw.alipayobjects.com/os/bmw-prod/21ffc284-50a2-4a30-8bb0-b2f9ac4a8fbc.json', + 'https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json', ) .then((res) => res.json()) .then((data) => { diff --git a/s2-site/examples/analysis/sort/demo/custom-list.ts b/s2-site/examples/analysis/sort/demo/custom-list.ts index 67ea91caa4..d20e26d016 100644 --- a/s2-site/examples/analysis/sort/demo/custom-list.ts +++ b/s2-site/examples/analysis/sort/demo/custom-list.ts @@ -1,6 +1,6 @@ import { PivotSheet } from '@antv/s2'; -fetch('https://assets.antv.antgroup.com/s2/basic.json') +fetch('https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json') .then((res) => res.json()) .then((data) => { const container = document.getElementById('container'); diff --git a/s2-site/examples/analysis/sort/demo/custom-measure.ts b/s2-site/examples/analysis/sort/demo/custom-measure.ts index 4508d5c7ea..5627965ae1 100644 --- a/s2-site/examples/analysis/sort/demo/custom-measure.ts +++ b/s2-site/examples/analysis/sort/demo/custom-measure.ts @@ -1,6 +1,6 @@ import { PivotSheet, EXTRA_FIELD } from '@antv/s2'; -fetch('https://assets.antv.antgroup.com/s2/basic.json') +fetch('https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json') .then((res) => res.json()) .then((data) => { const container = document.getElementById('container'); diff --git a/s2-site/examples/analysis/sort/demo/custom-method.ts b/s2-site/examples/analysis/sort/demo/custom-method.ts index f4a3c3d22f..93444f0aec 100644 --- a/s2-site/examples/analysis/sort/demo/custom-method.ts +++ b/s2-site/examples/analysis/sort/demo/custom-method.ts @@ -1,6 +1,6 @@ import { PivotSheet } from '@antv/s2'; -fetch('https://assets.antv.antgroup.com/s2/basic.json') +fetch('https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json') .then((res) => res.json()) .then((data) => { const container = document.getElementById('container'); diff --git a/s2-site/examples/analysis/sort/demo/custom-sort-func.ts b/s2-site/examples/analysis/sort/demo/custom-sort-func.ts index fd46142a81..2f14efb975 100644 --- a/s2-site/examples/analysis/sort/demo/custom-sort-func.ts +++ b/s2-site/examples/analysis/sort/demo/custom-sort-func.ts @@ -1,6 +1,6 @@ import { PivotSheet, EXTRA_FIELD } from '@antv/s2'; -fetch('https://assets.antv.antgroup.com/s2/basic.json') +fetch('https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json') .then((res) => res.json()) .then((data) => { const container = document.getElementById('container'); diff --git a/s2-site/examples/analysis/sort/demo/group-sort.tsx b/s2-site/examples/analysis/sort/demo/group-sort.tsx index bd2ec1d21a..2c10da871b 100644 --- a/s2-site/examples/analysis/sort/demo/group-sort.tsx +++ b/s2-site/examples/analysis/sort/demo/group-sort.tsx @@ -4,7 +4,7 @@ import { SheetComponent } from '@antv/s2-react'; import '@antv/s2-react/dist/style.min.css'; fetch( - 'https://gw.alipayobjects.com/os/bmw-prod/21ffc284-50a2-4a30-8bb0-b2f9ac4a8fbc.json', + 'https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json', ) .then((res) => res.json()) .then((data) => { diff --git a/s2-site/examples/analysis/totals/demo/calculate.ts b/s2-site/examples/analysis/totals/demo/calculate.ts index f76e0b1495..c1570a2038 100644 --- a/s2-site/examples/analysis/totals/demo/calculate.ts +++ b/s2-site/examples/analysis/totals/demo/calculate.ts @@ -1,6 +1,6 @@ import { PivotSheet } from '@antv/s2'; -fetch('https://assets.antv.antgroup.com/s2/basic.json') +fetch('https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json001215413-dev-S09001736318/s2/basic.json') .then((res) => res.json()) .then((data) => { const container = document.getElementById('container'); diff --git a/s2-site/examples/analysis/totals/demo/custom.ts b/s2-site/examples/analysis/totals/demo/custom.ts index b65ab5ace4..2c96eadffe 100644 --- a/s2-site/examples/analysis/totals/demo/custom.ts +++ b/s2-site/examples/analysis/totals/demo/custom.ts @@ -1,6 +1,6 @@ import { PivotSheet, EXTRA_FIELD } from '@antv/s2'; -fetch('https://assets.antv.antgroup.com/s2/basic.json') +fetch('https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json') .then((res) => res.json()) .then((data) => { const container = document.getElementById('container'); diff --git a/s2-site/examples/analysis/totals/demo/dimension-group-col.ts b/s2-site/examples/analysis/totals/demo/dimension-group-col.ts new file mode 100644 index 0000000000..d0b992238a --- /dev/null +++ b/s2-site/examples/analysis/totals/demo/dimension-group-col.ts @@ -0,0 +1,68 @@ +import { PivotSheet } from '@antv/s2'; + +fetch('https://render.alipay.com/p/yuyan/180020010001215413/s2/total-group.json') + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const s2DataConfig = { + fields: { + rows: [], + columns: ['province', 'city', 'type'], + values: ['price' ,'cost'], + valueInCols: false, + }, + meta: [ + { + field: 'province', + name: '省份', + }, + { + field: 'city', + name: '城市', + }, + { + field: 'type', + name: '商品类别', + }, + { + field: 'price', + name: '价格', + }, + { + field: 'cost', + name: '成本', + }, + ], + data, + }; + + const s2Options = { + width: 600, + height: 480, + // 配置行小计总计显示,且按维度分组(列小计总计同理) + totals: { + col: { + showGrandTotals: true, + showSubTotals: true, + reverseLayout: true, + reverseSubLayout: true, + subTotalsDimensions: ['province'], + calcTotals: { + // 设置总计汇总计算方式为求和 + aggregation: 'SUM', + }, + calcSubTotals: { + // 设置小计汇总计算方式为求和 + aggregation: 'SUM', + }, + // 总计分组下,city 城市维度会出现分组 + totalsGroupDimensions: ['city'], + // 小计维度下,type 类别维度下会出现分组 + subTotalsGroupDimensions: ['type'], + }, + }, + }; + const s2 = new PivotSheet(container, s2DataConfig, s2Options); + + s2.render(); + }); diff --git a/s2-site/examples/analysis/totals/demo/dimension-group-row.ts b/s2-site/examples/analysis/totals/demo/dimension-group-row.ts new file mode 100644 index 0000000000..ec9aca8d99 --- /dev/null +++ b/s2-site/examples/analysis/totals/demo/dimension-group-row.ts @@ -0,0 +1,67 @@ +import { PivotSheet } from '@antv/s2'; + +fetch('https://render.alipay.com/p/yuyan/180020010001215413/s2/total-group.json') + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const s2DataConfig = { + fields: { + rows: ['province', 'city', 'type'], + columns: [], + values: ['price' ,'cost'], + }, + meta: [ + { + field: 'province', + name: '省份', + }, + { + field: 'city', + name: '城市', + }, + { + field: 'type', + name: '商品类别', + }, + { + field: 'price', + name: '价格', + }, + { + field: 'cost', + name: '成本', + }, + ], + data, + }; + + const s2Options = { + width: 600, + height: 480, + // 配置行小计总计显示,且按维度分组(列小计总计同理) + totals: { + row: { + showGrandTotals: true, + showSubTotals: true, + reverseLayout: true, + reverseSubLayout: true, + subTotalsDimensions: ['province'], + calcTotals: { + // 设置总计汇总计算方式为求和 + aggregation: 'SUM', + }, + calcSubTotals: { + // 设置小计汇总计算方式为求和 + aggregation: 'SUM', + }, + // 总计分组下,city 城市维度会出现分组 + totalsGroupDimensions: ['city'], + // 小计维度下,type 类别维度下会出现分组 + subTotalsGroupDimensions: ['type'], + }, + }, + }; + const s2 = new PivotSheet(container, s2DataConfig, s2Options); + + s2.render(); + }); diff --git a/s2-site/examples/analysis/totals/demo/meta.json b/s2-site/examples/analysis/totals/demo/meta.json index d751e58992..12ee0b59ab 100644 --- a/s2-site/examples/analysis/totals/demo/meta.json +++ b/s2-site/examples/analysis/totals/demo/meta.json @@ -20,6 +20,22 @@ }, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/3SuoQrkTsR/5657d02f-8e7f-4fbc-8159-7a0e8f772462.png" }, + { + "filename": "dimension-group-row.ts", + "title": { + "zh": "行总计小计按维度分组", + "en": "Total Of Rows Grouped By Dimension" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*1SDsRpTA_kQAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "dimension-group-col.ts", + "title": { + "zh": "列总计小计按维度分组", + "en": "Total Of Columns Grouped By Dimension" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*5PTqQpXXCcsAAAAAAAAAAAAADmJ7AQ/original" + }, { "filename": "tree.ts", "title": { diff --git a/s2-site/examples/analysis/totals/demo/multiple-values.ts b/s2-site/examples/analysis/totals/demo/multiple-values.ts index 3162452b82..e11b87e378 100644 --- a/s2-site/examples/analysis/totals/demo/multiple-values.ts +++ b/s2-site/examples/analysis/totals/demo/multiple-values.ts @@ -1,7 +1,7 @@ import { PivotSheet } from '@antv/s2'; fetch( - 'https://gw.alipayobjects.com/os/bmw-prod/6eede6eb-8021-4da8-bb12-67891a5705b7.json', + 'https://render.alipay.com/p/yuyan/180020010001215413/s2/total-group.json', ) .then((res) => res.json()) .then((data) => { @@ -34,9 +34,15 @@ fetch( name: '成本', }, ], + // 从 @antv/s2 1.53.0-alpha 开始,如果是多度量的场景,我们期望同一个数据里就包含了多个 values 属性,即: + // [{province: "四川", city: "成都", type: "商品", price: 100, cost: 80}] + // 而不是: + // [{province: "四川", city: "成都", type: "商品", price: 100}, {province: "四川", city: "成都", type: "商品", price: 100}] data, }; + + const s2Options = { width: 600, height: 480, diff --git a/s2-site/examples/basic/pivot/demo/grid.ts b/s2-site/examples/basic/pivot/demo/grid.ts index bd3947c201..0dfa6ca497 100644 --- a/s2-site/examples/basic/pivot/demo/grid.ts +++ b/s2-site/examples/basic/pivot/demo/grid.ts @@ -10,7 +10,10 @@ fetch( const s2Options = { width: 600, height: 480, + // 冻结行头 + // frozenRowHeader: true }; + const s2 = new PivotSheet(container, dataCfg, s2Options); s2.render(); diff --git a/s2-site/examples/basic/pivot/demo/tree.ts b/s2-site/examples/basic/pivot/demo/tree.ts index c0d893c038..361567ca9d 100644 --- a/s2-site/examples/basic/pivot/demo/tree.ts +++ b/s2-site/examples/basic/pivot/demo/tree.ts @@ -11,6 +11,18 @@ fetch( width: 600, height: 480, hierarchyType: 'tree', + style: { + // 折叠全部 + // hierarchyCollapse: true, + + // 折叠浙江省下面所有的城市 + collapsedRows: { + 'root[&]浙江省': true, + }, + }, + + // 冻结行头 + // frozenRowHeader: true }; const s2 = new PivotSheet(container, dataCfg, s2Options); diff --git a/s2-site/examples/basic/table/demo/table.ts b/s2-site/examples/basic/table/demo/table.ts index 1333265372..54e41c1510 100644 --- a/s2-site/examples/basic/table/demo/table.ts +++ b/s2-site/examples/basic/table/demo/table.ts @@ -37,6 +37,7 @@ fetch('https://assets.antv.antgroup.com/s2/basic-table-mode.json') width: 600, height: 480, showSeriesNumber: true, + // seriesNumberText: '自定义序号标题', }; const s2 = new TableSheet(container, s2DataConfig, s2Options); diff --git a/s2-site/examples/case/art/demo/lost-text.tsx b/s2-site/examples/case/art/demo/lost-text.tsx new file mode 100644 index 0000000000..5f23880720 --- /dev/null +++ b/s2-site/examples/case/art/demo/lost-text.tsx @@ -0,0 +1,298 @@ +/* eslint-disable max-classes-per-file */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { SheetComponent } from '@antv/s2-react'; +import { Tag } from 'antd'; +import { + BaseEvent, + CellType, + getTheme, + InterceptType, + S2Event, + CornerCell, +} from '@antv/s2'; + +import '@antv/s2-react/dist/style.min.css'; + +const Theme = { + rowCell: { + text: { + opacity: 0, + }, + bolderText: { + opacity: 0, + }, + measureText: { + opacity: 0, + }, + }, + colCell: { + text: { + opacity: 0, + }, + bolderText: { + opacity: 0, + }, + measureText: { + opacity: 0, + }, + }, + dataCell: { + text: { + opacity: 0, + }, + }, +}; + +class CustomCornerCell extends CornerCell { + drawBackgroundShape() { + this.addShape('rect', { + attrs: { + ...this.getCellArea(), + fill: '#E0E9FD', + }, + }); + } + + getCornerText() { + return '👍🏻'; + } +} + +class CustomInteraction extends BaseEvent { + timer = null; + + count = 0; + + changeCell(cellType: CellType) { + this.count++; + + const defaultTheme = getTheme(null)?.[cellType]; + + this.spreadsheet.setTheme({ + [cellType]: defaultTheme, + }); + this.spreadsheet.render(false); + + if (this.count >= 3) { + clearInterval(this.timer); + this.showSuccessTips(); + } + } + + resetCell() { + this.count = 0; + this.spreadsheet.setTheme(Theme); + this.spreadsheet.render(false); + } + + showSuccessTips() { + const rect = this.spreadsheet.getCanvasElement().getBoundingClientRect(); + + this.spreadsheet.showTooltip({ + position: { + x: rect.width / 2 + rect.left, + y: rect.height / 2 + rect.top, + }, + content: ( + <div + style={{ + padding: 20, + textAlign: 'center', + }} + > + <h3>💐 通关啦 💐</h3> + <p> + S2 + 多维交叉分析表格是多维交叉分析领域的表格解决方案,数据驱动视图,提供底层核心库、基础组件库、业务场景库,具备自由扩展的能力,让开发者既能开箱即用,也能基于自身场景自由发挥。 + </p> + <p> + <a href="https://s2.antv.antgroup.com" target="__blank"> + 前往官网 https://s2.antv.antgroup.com/ + </a> + </p> + </div> + ), + }); + this.spreadsheet.interaction.addIntercepts([InterceptType.HOVER]); + } + + bindEvents() { + // 角头: 一键三连 + this.addCornerCellInteraction(); + // 行头: 多选全部偶数行 + this.addRowCellInteraction(); + // 列头: 调整列宽/刷选全部 + this.addColCellInteraction(); + // 数值: 键盘方向键移动端选中单元格到右下角 + this.addDataCellInteraction(); + } + + addCornerCellInteraction() { + const countMap: Record<number, CellType> = { + 0: CellType.ROW_CELL, + 1: CellType.COL_CELL, + 2: CellType.DATA_CELL, + }; + + this.spreadsheet.on(S2Event.CORNER_CELL_MOUSE_DOWN, () => { + clearInterval(this.timer); + this.resetCell(); + + this.timer = setInterval(() => { + this.changeCell(countMap[this.count]); + }, 1000); + }); + + this.spreadsheet.on(S2Event.CORNER_CELL_MOUSE_UP, () => { + clearInterval(this.timer); + + if (this.count < 3) { + this.resetCell(); + } + }); + } + + addDataCellInteraction() { + this.spreadsheet.on(S2Event.DATA_CELL_SELECT_MOVE, (cells) => { + const { colIndex, rowIndex } = cells[0]; + + const isLastCell = colIndex === 3 && rowIndex === 7; + + if (isLastCell) { + this.changeCell(CellTypes.DATA_CELL); + } + }); + } + + addColCellInteraction() { + this.spreadsheet.on(S2Event.LAYOUT_RESIZE_COL_WIDTH, ({ info }) => { + const rules = [6, 66, 666]; + + if (rules.includes(info.resizedWidth)) { + this.changeCell(CellTypes.COL_CELL); + } + }); + + this.spreadsheet.on(S2Event.COL_CELL_BRUSH_SELECTION, (colCells) => { + const isAllSelected = + colCells.length === this.spreadsheet.getColumnNodes().length; + + if (isAllSelected) { + this.changeCell(CellTypes.COL_CELL); + } + }); + } + + addRowCellInteraction() { + this.spreadsheet.on(S2Event.GLOBAL_SELECTED, (cells) => { + const selectedOddRowCells = cells.filter((cell) => { + const meta = cell.getMeta(); + + return cell.cellType === CellTypes.ROW_CELL && meta.rowIndex % 2 !== 0; + }); + + const isAllOddRowCellsSelected = selectedOddRowCells.length === 4; + + if (isAllOddRowCellsSelected) { + this.changeCell(CellTypes.ROW_CELL); + } + }); + } +} + +export const s2Options = { + debug: true, + width: 600, + height: 400, + showSeriesNumber: false, + showDefaultHeaderActionIcon: false, + interaction: { + enableCopy: true, + // 防止 mac 触摸板横向滚动触发浏览器返回, 和移动端下拉刷新 + overscrollBehavior: 'none', + brushSelection: { + data: true, + col: true, + row: true, + }, + hoverFocus: false, + hoverHighlight: false, + customInteractions: [ + { + key: 'CustomInteraction', + interaction: CustomInteraction, + }, + ], + }, + tooltip: { + showTooltip: false, + }, + hierarchyType: 'grid', + style: { + rowCfg: { + width: 100, + }, + cellCfg: { + width: 50, + height: 30, + }, + }, + cornerCell: (...args) => new CustomCornerCell(...args), +}; + +fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/2a5dbbc8-d0a7-4d02-b7c9-34f6ca63cff6.json', +) + .then((res) => res.json()) + .then((dataCfg) => { + ReactDOM.render( + <SheetComponent + dataCfg={dataCfg} + options={s2Options} + themeCfg={{ theme: Theme }} + header={{ + description: ( + <> + <h4> + <span>单元格的文字都消失了, 想办法让文字全部显示出来.</span> + <a + href="https://codesandbox.io/s/brave-pine-kki1xp?file=/src/index.tsx" + target="__blank" + > + 查看代码 + </a> + </h4> + <ul> + <li> + <Tag> + 列头可以 "调整" 成三个尺码: s (6px) M (66px) L (666px) + </Tag> + </li> + <li> + <Tag>列头10个单元格可以 "圈" 在一起</Tag> + </li> + <li> + <Tag>行头多选, 让它显示斑马纹</Tag> + </li> + <li> + <Tag>有一个数值单元格喜欢待在角落 ↑ ↓ ← →</Tag> + </li> + <li> + 搞不定, 试试看看 + <a + href="https://s2.antv.antgroup.com/manual/advanced/interaction/basic" + target="__blank" + > + 基础交互 + </a> + 章节或试试 <Tag>长按一键三连</Tag> + </li> + </ul> + </> + ), + }} + />, + document.getElementById('container'), + ); + }); diff --git a/s2-site/examples/case/art/demo/meta.json b/s2-site/examples/case/art/demo/meta.json index 4472e98d6c..77f7574aa4 100644 --- a/s2-site/examples/case/art/demo/meta.json +++ b/s2-site/examples/case/art/demo/meta.json @@ -15,10 +15,18 @@ { "filename": "time-spend-abstract.tsx", "title": { - "zh": "想如何度过一天(抽象版)", + "zh": "想如何度过一天 (抽象版)", "en": "How to spend the day" }, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/lN8USZvMEV/f6b5e06f-5def-4db8-a9c8-826e347d0fa1.png" + }, + { + "filename": "lost-text.tsx", + "title": { + "zh": "消失的文字 (交互小游戏)", + "en": "Lost text" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*3WDCR7PZY6MAAAAAAAAAAAAADmJ7AQ/original" } ] } diff --git a/s2-site/examples/case/comparison/demo/measure-comparison.tsx b/s2-site/examples/case/comparison/demo/measure-comparison.tsx index eae238e3ed..a4e86ae871 100644 --- a/s2-site/examples/case/comparison/demo/measure-comparison.tsx +++ b/s2-site/examples/case/comparison/demo/measure-comparison.tsx @@ -435,7 +435,9 @@ class CustomCornelCell extends CornerCell { drawBorderShape() {} } -fetch('https://assets.antv.antgroup.com/s2/index-comparison.json') +fetch( + 'https://render.alipay.com/p/yuyan/180020010001215413/s2/index-comparison.json', +) .then((res) => res.json()) .then((data) => { const s2DataConfig = { diff --git a/s2-site/examples/custom/custom-cell/demo/corner-header.ts b/s2-site/examples/custom/custom-cell/demo/corner-header.ts index 1b40471866..4d65f8ea4f 100644 --- a/s2-site/examples/custom/custom-cell/demo/corner-header.ts +++ b/s2-site/examples/custom/custom-cell/demo/corner-header.ts @@ -8,13 +8,17 @@ import { get } from 'lodash'; */ class CustomCornerHeader extends Group { node; + backgroundShape; + textShape; + constructor(node) { super({}); this.node = node; this.initCornerHeader(); } + initCornerHeader() { this.initBg(); this.initText(); @@ -77,6 +81,5 @@ fetch( }; const s2 = new PivotSheet(container, s2DataConfig, s2Options); - // 使用 s2.render(); }); diff --git a/s2-site/examples/custom/custom-cell/demo/custom-specified-cell.ts b/s2-site/examples/custom/custom-cell/demo/custom-specified-cell.ts index 13c641ae06..ee24a885be 100644 --- a/s2-site/examples/custom/custom-cell/demo/custom-specified-cell.ts +++ b/s2-site/examples/custom/custom-cell/demo/custom-specified-cell.ts @@ -1,8 +1,8 @@ /* eslint-disable max-classes-per-file */ -import { PivotSheet, DataCell, ColCell } from '@antv/s2'; +import { PivotSheet, DataCell, ColCell, CornerCell, RowCell } from '@antv/s2'; /** - * 自定义 DataCell,给特定单元格设置背景色, 文字大小, 颜色 + * 自定义 DataCell,通过复写基类方法, 给特定单元格设置背景色, 文字大小, 颜色等... * 查看更多方法 https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/data-cell.ts */ class CustomDataCell extends DataCell { @@ -62,7 +62,7 @@ class CustomDataCell extends DataCell { } /** - * 自定义 ColCell, 给特定单元格设置文字大小, 颜色 + * 自定义 ColCell, 通过复写基类方法, 给特定单元格设置文字大小, 颜色等... * 查看更多方法 https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/col-cell.ts */ class CustomColCell extends ColCell { @@ -70,7 +70,7 @@ class CustomColCell extends ColCell { const defaultTextStyle = super.getTextStyle(); // 指定列 - if (this.meta.rowIndex % 2 === 0) { + if (this.meta.colIndex % 2 === 0) { return { ...defaultTextStyle, fontSize: 16, @@ -102,6 +102,81 @@ class CustomColCell extends ColCell { } } +/** + * 自定义 CornerCell, 通过复写基类方法, 给特定单元格设置文字大小, 颜色等... + * 查看更多方法 https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/corner-cell.ts + */ +class CustomCornerCell extends CornerCell { + getBackgroundColor() { + // 特定数据 + if (this.meta.field === 'province') { + return { + backgroundColor: 'red', + backgroundColorOpacity: 0.2, + }; + } + + return super.getBackgroundColor(); + } + + getTextStyle() { + const defaultTextStyle = super.getTextStyle(); + + if (this.meta.field === 'type') { + return { + ...defaultTextStyle, + fill: '#06a', + fontSize: 20, + fontWeight: 200, + }; + } + + return super.getTextStyle(); + } +} + +/** + * 自定义 RowCell, 通过复写基类方法, 给特定单元格设置文字大小, 颜色等... + * 查看更多方法 https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/row-cell.ts + */ +class CustomRowCell extends RowCell { + getBackgroundColor() { + // 特定数据 + if (this.meta.field === 'province') { + return { + backgroundColor: 'red', + backgroundColorOpacity: 0.2, + }; + } + + return super.getBackgroundColor(); + } + + getTextStyle() { + const defaultTextStyle = super.getTextStyle(); + + if (this.meta.field === 'type') { + return { + ...defaultTextStyle, + fill: '#06a', + fontSize: 20, + fontWeight: 200, + }; + } + + if (this.meta.rowIndex >= 1) { + return { + ...defaultTextStyle, + fill: '#dcdcdc', + fontSize: 20, + fontWeight: 700, + }; + } + + return super.getTextStyle(); + } +} + fetch( 'https://gw.alipayobjects.com/os/bmw-prod/cd9814d0-6dfa-42a6-8455-5a6bd0ff93ca.json', ) @@ -120,16 +195,20 @@ fetch( const s2Options = { width: 600, height: 480, - dataCell: (viewMeta) => { - return new CustomDataCell(viewMeta, viewMeta?.spreadsheet); + cornerCell: (node, spreadsheet, headerConfig) => { + return new CustomCornerCell(node, spreadsheet, headerConfig); }, - // rowCell 同理, 请参考示例 colCell: (node, spreadsheet, headerConfig) => { return new CustomColCell(node, spreadsheet, headerConfig); }, + rowCell: (node, spreadsheet, headerConfig) => { + return new CustomRowCell(node, spreadsheet, headerConfig); + }, + dataCell: (viewMeta) => { + return new CustomDataCell(viewMeta, viewMeta?.spreadsheet); + }, }; const s2 = new PivotSheet(container, s2DataConfig, s2Options); - // 使用 s2.render(); }); diff --git a/s2-site/examples/custom/custom-cell/demo/custom-table-cell.ts b/s2-site/examples/custom/custom-cell/demo/custom-table-cell.ts new file mode 100644 index 0000000000..7a3e822182 --- /dev/null +++ b/s2-site/examples/custom/custom-cell/demo/custom-table-cell.ts @@ -0,0 +1,139 @@ +/* eslint-disable max-classes-per-file */ +import { TableColCell, TableDataCell, TableSheet } from '@antv/s2'; + +/** + * 自定义 DataCell,通过复写基类方法, 给特定单元格设置背景色, 文字大小, 颜色等... + * 查看更多方法 https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/table-data-cell.ts + */ +class CustomDataCell extends TableDataCell { + getBackgroundColor() { + // 特定数据 + if (this.meta.fieldValue >= 6000) { + return { + backgroundColor: 'red', + backgroundColorOpacity: 0.2, + }; + } + + return super.getBackgroundColor(); + } + + getTextStyle() { + const defaultTextStyle = super.getTextStyle(); + // 序号 + + if (this.meta.colIndex === 0) { + return { + ...defaultTextStyle, + fontWeight: 600, + textAlign: 'center', + }; + } + + // 指定列 + if (this.meta.rowIndex % 2 === 0 && this.meta.colIndex > 0) { + return { + ...defaultTextStyle, + fontSize: 16, + fill: '#396', + textAlign: 'left', + }; + } + + // 指定数据 + if (this.meta.fieldValue >= 600 || this.meta.fieldValue === '沙发') { + return { + ...defaultTextStyle, + fontSize: 14, + fontWeight: 700, + fill: '#f63', + textAlign: 'center', + }; + } + + // 指定单元格 + if (this.meta.id === '7-root[&]省份') { + return { + ...defaultTextStyle, + fontSize: 12, + fontWeight: 200, + fill: '#dcdcdc', + opacity: 0.9, + textAlign: 'right', + }; + } + + // 使用默认处理 + return super.getTextStyle(); + } +} + +/** + * 自定义 ColCell, 通过复写基类方法, 给特定单元格设置文字大小, 颜色等... + * 查看更多方法 https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/table-col-cell.ts + */ +class CustomColCell extends TableColCell { + getTextStyle() { + const defaultTextStyle = super.getTextStyle(); + + // 指定列 + if (this.meta.colIndex % 2 === 0) { + return { + ...defaultTextStyle, + fontSize: 16, + fill: '#396', + textAlign: 'left', + }; + } + + // 指定层级 + if (this.meta.level >= 0) { + return { + ...defaultTextStyle, + fill: 'pink', + textAlign: 'center', + }; + } + + // 指定文本 + if (this.meta.label === '子类别') { + return { + ...defaultTextStyle, + fontSize: 22, + textAlign: 'right', + }; + } + + // 使用默认处理 + return super.getTextStyle(); + } +} + +fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/cd9814d0-6dfa-42a6-8455-5a6bd0ff93ca.json', +) + .then((res) => res.json()) + .then((res) => { + const container = document.getElementById('container'); + const s2DataConfig = { + fields: { + columns: ['province', 'city', 'type', 'sub_type', 'number'], + }, + meta: res.meta, + data: res.data, + }; + const s2Options = { + width: 600, + height: 480, + showSeriesNumber: true, + colCell: (node, spreadsheet, headerConfig) => { + return new CustomColCell(node, spreadsheet, headerConfig); + }, + dataCell: (viewMeta) => { + return new CustomDataCell(viewMeta, viewMeta?.spreadsheet); + }, + }; + const s2 = new TableSheet(container, s2DataConfig, s2Options); + + s2.render(); + }); diff --git a/s2-site/examples/custom/custom-cell/demo/data-cell-placeholder.ts b/s2-site/examples/custom/custom-cell/demo/data-cell-placeholder.ts new file mode 100644 index 0000000000..b61659a5d8 --- /dev/null +++ b/s2-site/examples/custom/custom-cell/demo/data-cell-placeholder.ts @@ -0,0 +1,42 @@ +import { PivotSheet, CornerCell } from '@antv/s2'; + +fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/cd9814d0-6dfa-42a6-8455-5a6bd0ff93ca.json', +) + .then((res) => res.json()) + .then((res) => { + const container = document.getElementById('container'); + const s2DataConfig = { + fields: { + rows: ['province', 'city'], + columns: ['type', 'sub_type'], + values: ['number'], + }, + meta: res.meta, + data: res.data.map((item, i) => { + return { + ...item, + number: i < 5 ? item.number : null, + }; + }), + }; + const s2Options = { + width: 600, + height: 480, + // 默认 "-" + // placeholder: '', + placeholder: (cell) => { + // 或者根据当前单元格动态设置 + console.log('cell: ', cell); + if (cell.cellType === 'dataCell') { + return '*****'; + } + + // 返回 null, 使用默认值 ("-") + return null; + }, + }; + const s2 = new PivotSheet(container, s2DataConfig, s2Options); + + s2.render(); + }); diff --git a/s2-site/examples/custom/custom-cell/demo/data-cell.ts b/s2-site/examples/custom/custom-cell/demo/data-cell.ts index 8b9ccff792..41a6eaa656 100644 --- a/s2-site/examples/custom/custom-cell/demo/data-cell.ts +++ b/s2-site/examples/custom/custom-cell/demo/data-cell.ts @@ -1,4 +1,4 @@ -import { PivotSheet, DataCell } from '@antv/s2'; +import { DataCell, PivotSheet } from '@antv/s2'; /** * 自定义 DataCell,给数值单元格添加背景图 @@ -21,7 +21,7 @@ fetch( ) .then((res) => res.json()) .then((res) => { - const container = document.getElementById('container'); + const container = document.getElementById('container')!; const s2DataConfig = { fields: { rows: ['province', 'city'], @@ -31,6 +31,7 @@ fetch( meta: res.meta, data: res.data, }; + const s2Options = { width: 600, height: 480, @@ -43,6 +44,5 @@ fetch( }; const s2 = new PivotSheet(container, s2DataConfig, s2Options); - // 使用 s2.render(); }); diff --git a/s2-site/examples/custom/custom-cell/demo/meta.json b/s2-site/examples/custom/custom-cell/demo/meta.json index 2e38f0597b..42b1f3d65f 100644 --- a/s2-site/examples/custom/custom-cell/demo/meta.json +++ b/s2-site/examples/custom/custom-cell/demo/meta.json @@ -66,7 +66,7 @@ "zh": "自定义特定单元格", "en": "custom specified cell" }, - "screenshot": "https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*KJqYQKjxIwIAAAAAAAAAAAAAARQnAQ" + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*GSh4R6qdC5IAAAAAAAAAAAAADmJ7AQ/original" }, { "filename": "custom-merged-cell.ts", @@ -75,6 +75,30 @@ "en": "custom merged cell" }, "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*B7mTTZYFjv8AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "custom-table-cell.ts", + "title": { + "zh": "自定义明细表单元格", + "en": "custom table cell" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*-uLETb4gK9AAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "mini-chart.ts", + "title": { + "zh": "自定义 mini 图", + "en": "custom mini chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*apnIT4KXP3YAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "data-cell-placeholder.ts", + "title": { + "zh": "自定义空数据单元格占位符", + "en": "custom cell placeholder" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*B-VMT7kCWL0AAAAAAAAAAAAADmJ7AQ/original" } ] } diff --git a/s2-site/examples/custom/custom-cell/demo/mini-chart.ts b/s2-site/examples/custom/custom-cell/demo/mini-chart.ts new file mode 100644 index 0000000000..ee007fa585 --- /dev/null +++ b/s2-site/examples/custom/custom-cell/demo/mini-chart.ts @@ -0,0 +1,178 @@ +import { PivotSheet, DataCell, drawObjectText } from '@antv/s2'; +import { isArray, isObject } from 'lodash'; + +/** + * 自定义 DataCell,使用 drawObjectText 绘制简易的 mini 图 + * 查看更多方法 https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/data-cell.ts + */ +class CustomDataCell extends DataCell { + // 当数值为对象时,完全接管绘制, 使用内置的 `drawObjectText` 根据不同的数据结构 (见下方) 绘制不同的图形 + drawTextShape() { + const { fieldValue } = this.getMeta(); + + if (isObject(fieldValue) || isArray(fieldValue)) { + drawObjectText(this); + return; + } + + super.drawTextShape(); + } +} + +fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/cd9814d0-6dfa-42a6-8455-5a6bd0ff93ca.json', +) + .then((res) => res.json()) + .then((res) => { + const container = document.getElementById('container')!; + const s2DataConfig = { + fields: { + rows: ['province', 'city'], + columns: ['type', 'sub_type'], + values: ['number'], + }, + meta: res.meta, + data: [ + // 用于绘制 mini 图的数据, 数据结构请查阅: https://s2.antv.antgroup.com/manual/basic/analysis/strategy#%E9%85%8D%E7%BD%AE-mini-%E5%9B%BE + { + province: '海南省', + city: '三亚市', + type: '家具', + sub_type: '桌子', + number: { + // 折线图 + values: { + type: 'line', + data: [ + { + year: '2017', + value: -368, + }, + { + year: '2018', + value: 368, + }, + { + year: '2019', + value: 368, + }, + { + year: '2020', + value: 368, + }, + { + year: '2021', + value: 268, + }, + { + year: '2022', + value: 168, + }, + ], + encode: { x: 'year', y: 'value' }, + }, + }, + }, + { + province: '海南省', + city: '三亚市', + type: '家具', + sub_type: '沙发', + number: { + // 柱状图 + values: { + type: 'bar', + data: [ + { + year: '2017', + value: -368, + }, + { + year: '2018', + value: 328, + }, + { + year: '2019', + value: 38, + }, + { + year: '2020', + value: 168, + }, + { + year: '2021', + value: 268, + }, + { + year: '2022', + value: 368, + }, + ], + encode: { x: 'year', y: 'value' }, + }, + }, + }, + { + province: '海南省', + city: '三亚市', + type: '办公用品', + sub_type: '笔', + number: { + // 多列文本 + values: [ + [3877, -4324, '42%'], + [3877, 4324, '-42%'], + ], + }, + }, + { + province: '海南省', + city: '三亚市', + type: '办公用品', + sub_type: '纸张', + number: { + // 子弹图 + values: { + measure: 0.3, + target: 0.76, + }, + }, + }, + ...res.data, + ], + }; + + const s2Options = { + width: 1000, + height: 680, + style: { + cellCfg: { + height: 40, + }, + }, + conditions: { + text: [ + { + field: 'number', + mapping: (value, cellInfo) => { + const { meta, colIndex } = cellInfo || {}; + if (colIndex === 0 || !value || !meta?.fieldValue) { + return { + fill: '#000', + }; + } + return { + fill: value > 0 ? '#FF4D4F' : '#29A294', + }; + }, + }, + ], + }, + dataCell: (viewMeta) => { + return new CustomDataCell(viewMeta, viewMeta?.spreadsheet); + }, + }; + const s2 = new PivotSheet(container, s2DataConfig, s2Options); + + s2.render(); + }); diff --git a/s2-site/examples/custom/custom-cell/demo/totals-cell.ts b/s2-site/examples/custom/custom-cell/demo/totals-cell.ts index 6115363d6a..99cced08ea 100644 --- a/s2-site/examples/custom/custom-cell/demo/totals-cell.ts +++ b/s2-site/examples/custom/custom-cell/demo/totals-cell.ts @@ -1,7 +1,7 @@ import { PivotSheet, RowCell, renderRect } from '@antv/s2'; /** - * 继承 RowCell, 单独修改小计/总计的背景色和文字颜色 + * 继承 RowCell, 单独修改行小计/行总计的背景色和文字颜色 (继承 ColCell, 列小计/列总计 同理) * 查看更多方法 https://github.com/antvis/S2/blob/master/packages/s2-core/src/cell/row-cell.ts */ class CustomTotalsRowCell extends RowCell { diff --git a/s2-site/examples/custom/custom-layout/demo/custom-coordinate.ts b/s2-site/examples/custom/custom-layout/demo/custom-coordinate.ts index 89b159b6bc..dd646652bc 100644 --- a/s2-site/examples/custom/custom-layout/demo/custom-coordinate.ts +++ b/s2-site/examples/custom/custom-layout/demo/custom-coordinate.ts @@ -13,6 +13,28 @@ fetch( values: ['number'], }, data: res.data, + meta: [ + { + field: 'number', + name: '数量', + }, + { + field: 'province', + name: '省份', + }, + { + field: 'city', + name: '城市', + }, + { + field: 'type', + name: '类别', + }, + { + field: 'sub_type', + name: '子类别', + }, + ], }; const s2Options: S2Options = { diff --git a/s2-site/examples/custom/custom-layout/demo/custom-data-position.ts b/s2-site/examples/custom/custom-layout/demo/custom-data-position.ts index 89f699f766..9be34b1bb0 100644 --- a/s2-site/examples/custom/custom-layout/demo/custom-data-position.ts +++ b/s2-site/examples/custom/custom-layout/demo/custom-data-position.ts @@ -19,6 +19,28 @@ fetch( values: ['number'], }, data: res.data, + meta: [ + { + field: 'number', + name: '数量', + }, + { + field: 'province', + name: '省份', + }, + { + field: 'city', + name: '城市', + }, + { + field: 'type', + name: '类别', + }, + { + field: 'sub_type', + name: '子类别', + }, + ], }; const s2Options: S2Options = { diff --git a/s2-site/examples/custom/custom-layout/demo/custom-layout-arrange.ts b/s2-site/examples/custom/custom-layout/demo/custom-layout-arrange.ts index 00674ecd75..28959d0dc0 100644 --- a/s2-site/examples/custom/custom-layout/demo/custom-layout-arrange.ts +++ b/s2-site/examples/custom/custom-layout/demo/custom-layout-arrange.ts @@ -13,6 +13,28 @@ fetch( values: ['number'], }, data: res.data, + meta: [ + { + field: 'number', + name: '数量', + }, + { + field: 'province', + name: '省份', + }, + { + field: 'city', + name: '城市', + }, + { + field: 'type', + name: '类别', + }, + { + field: 'sub_type', + name: '子类别', + }, + ], }; const s2Options = { diff --git a/s2-site/examples/custom/custom-layout/demo/custom-layout-hierarchy.ts b/s2-site/examples/custom/custom-layout/demo/custom-layout-hierarchy.ts index c3264b7ef5..e9a5c8ab19 100644 --- a/s2-site/examples/custom/custom-layout/demo/custom-layout-hierarchy.ts +++ b/s2-site/examples/custom/custom-layout/demo/custom-layout-hierarchy.ts @@ -45,6 +45,28 @@ fetch( }, ], ], + meta: [ + { + field: 'number', + name: '数量', + }, + { + field: 'province', + name: '省份', + }, + { + field: 'city', + name: '城市', + }, + { + field: 'type', + name: '类别', + }, + { + field: 'sub_type', + name: '子类别', + }, + ], }; const s2Options = { diff --git a/s2-site/examples/custom/custom-layout/demo/custom-value-order.ts b/s2-site/examples/custom/custom-layout/demo/custom-value-order.ts index ba02eedb84..1bd014dc1c 100644 --- a/s2-site/examples/custom/custom-layout/demo/custom-value-order.ts +++ b/s2-site/examples/custom/custom-layout/demo/custom-value-order.ts @@ -16,6 +16,28 @@ fetch( customValueOrder: 1, }, data: res.data, + meta: [ + { + field: 'number', + name: '数量', + }, + { + field: 'province', + name: '省份', + }, + { + field: 'city', + name: '城市', + }, + { + field: 'type', + name: '类别', + }, + { + field: 'sub_type', + name: '子类别', + }, + ], }; const s2Options = { diff --git a/s2-site/examples/custom/custom-tree/demo/custom-tree.ts b/s2-site/examples/custom/custom-tree/demo/custom-tree.ts index cf0762ed11..5086bfe421 100644 --- a/s2-site/examples/custom/custom-tree/demo/custom-tree.ts +++ b/s2-site/examples/custom/custom-tree/demo/custom-tree.ts @@ -1,7 +1,7 @@ import { PivotSheet, S2DataConfig, S2Options } from '@antv/s2'; fetch( - 'https://gw.alipayobjects.com/os/bmw-prod/af54ea12-01d7-4696-a51c-c4d5e4ede28e.json', + 'https://render.alipay.com/p/yuyan/180020010001215413/s2/custom-tree.json', ) .then((res) => res.json()) .then((res) => { diff --git a/s2-site/examples/interaction/advanced/demo/custom-tree-link-jump.ts b/s2-site/examples/interaction/advanced/demo/custom-tree-link-jump.ts index 357099ed67..d869262b5d 100644 --- a/s2-site/examples/interaction/advanced/demo/custom-tree-link-jump.ts +++ b/s2-site/examples/interaction/advanced/demo/custom-tree-link-jump.ts @@ -1,7 +1,7 @@ import { S2Event, PivotSheet } from '@antv/s2'; fetch( - 'https://gw.alipayobjects.com/os/bmw-prod/af54ea12-01d7-4696-a51c-c4d5e4ede28e.json', + 'https://render.alipay.com/p/yuyan/180020010001215413/s2/custom-tree.json', ) .then((res) => res.json()) .then((res) => { diff --git a/s2-site/examples/interaction/advanced/demo/frozen-pivot-grid.ts b/s2-site/examples/interaction/advanced/demo/frozen-pivot-grid.ts new file mode 100644 index 0000000000..4c44b20818 --- /dev/null +++ b/s2-site/examples/interaction/advanced/demo/frozen-pivot-grid.ts @@ -0,0 +1,24 @@ +import { PivotSheet } from '@antv/s2'; + +fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/2a5dbbc8-d0a7-4d02-b7c9-34f6ca63cff6.json', +) + .then((res) => res.json()) + .then((dataCfg) => { + const container = document.getElementById('container'); + + const s2Options = { + width: 600, + height: 300, + frozenFirstRow: true, + totals: { + row: { + showGrandTotals: true, + reverseLayout: true, + }, + }, + }; + const s2 = new PivotSheet(container, dataCfg, s2Options); + + s2.render(); + }); diff --git a/s2-site/examples/interaction/advanced/demo/frozen-pivot-tree.ts b/s2-site/examples/interaction/advanced/demo/frozen-pivot-tree.ts new file mode 100644 index 0000000000..2f3b6cc674 --- /dev/null +++ b/s2-site/examples/interaction/advanced/demo/frozen-pivot-tree.ts @@ -0,0 +1,19 @@ +import { PivotSheet } from '@antv/s2'; + +fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/2a5dbbc8-d0a7-4d02-b7c9-34f6ca63cff6.json', +) + .then((res) => res.json()) + .then((dataCfg) => { + const container = document.getElementById('container'); + + const s2Options = { + width: 600, + height: 300, + hierarchyType: 'tree', + frozenFirstRow: true, + }; + const s2 = new PivotSheet(container, dataCfg, s2Options); + + s2.render(); + }); diff --git a/s2-site/examples/interaction/advanced/demo/meta.json b/s2-site/examples/interaction/advanced/demo/meta.json index d68eaec408..bdc80aaa85 100644 --- a/s2-site/examples/interaction/advanced/demo/meta.json +++ b/s2-site/examples/interaction/advanced/demo/meta.json @@ -107,6 +107,22 @@ "en": "Reaching the boundary of a scrolling area" }, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/JRAt1kb93/Kapture%2525202022-06-06%252520at%25252011.28.43.gif" + }, + { + "filename": "frozen-pivot-grid.ts", + "title": { + "zh": "透视表 - 平铺模式冻结首行", + "en": "pivot mode freezes head rows" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ge0_S5iMB-wAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "frozen-pivot-tree.ts", + "title": { + "zh": "透视表 - 树状模式冻结首行", + "en": "tree mode freezes head rows" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ncdCT7NB2I0AAAAAAAAAAAAADmJ7AQ/original" } ] } diff --git a/s2-site/examples/interaction/advanced/demo/scroll-speed-ratio.ts b/s2-site/examples/interaction/advanced/demo/scroll-speed-ratio.ts index c835361025..f0e1fa9679 100644 --- a/s2-site/examples/interaction/advanced/demo/scroll-speed-ratio.ts +++ b/s2-site/examples/interaction/advanced/demo/scroll-speed-ratio.ts @@ -30,7 +30,7 @@ function createSlider(s2) { document.querySelector('#container > canvas').before(slider); } -fetch('https://assets.antv.antgroup.com/s2/basic.json') +fetch('https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json') .then((res) => res.json()) .then((data) => { const container = document.getElementById('container'); diff --git a/s2-site/examples/interaction/basic/demo/frozen-row-header.ts b/s2-site/examples/interaction/basic/demo/frozen-row-header.ts new file mode 100644 index 0000000000..e51fb43708 --- /dev/null +++ b/s2-site/examples/interaction/basic/demo/frozen-row-header.ts @@ -0,0 +1,38 @@ +import { PivotSheet, S2Event, S2Options } from '@antv/s2'; + +fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/2a5dbbc8-d0a7-4d02-b7c9-34f6ca63cff6.json', +) + .then((res) => res.json()) + .then((dataCfg) => { + const container = document.getElementById('container'); + + const s2Options: S2Options = { + width: 600, + height: 480, + hierarchyType: 'tree', // 'tree' | 'grid' + // 默认开启行头冻结, 关闭后滚动区域为整个表格 + frozenRowHeader: true, + style: { + rowCfg: { + treeRowsWidth: 400, + width: 200, + }, + colCfg: { + width: 200, + }, + }, + }; + + const s2 = new PivotSheet(container, dataCfg, s2Options); + + s2.on(S2Event.GLOBAL_SCROLL, (e) => { + console.log('scroll', e); + }); + + s2.on(S2Event.ROW_CELL_SCROLL, (e) => { + console.log('row cell scroll', e); + }); + + s2.render(); + }); diff --git a/s2-site/examples/interaction/basic/demo/hover-after-scroll.ts b/s2-site/examples/interaction/basic/demo/hover-after-scroll.ts new file mode 100644 index 0000000000..2478449bcd --- /dev/null +++ b/s2-site/examples/interaction/basic/demo/hover-after-scroll.ts @@ -0,0 +1,31 @@ +import { PivotSheet, S2Options } from '@antv/s2'; + +fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/2a5dbbc8-d0a7-4d02-b7c9-34f6ca63cff6.json', +) + .then((res) => res.json()) + .then((dataCfg) => { + const container = document.getElementById('container'); + + const s2Options: S2Options = { + width: 600, + height: 480, + style: { + cellCfg: { + height: 100, + }, + }, + interaction: { + // 悬停高亮 + hoverHighlight: true, + // 滚动后自动触发悬停状态 + hoverAfterScroll: true, + }, + tooltip: { + showTooltip: true, + }, + }; + const s2 = new PivotSheet(container, dataCfg, s2Options); + + s2.render(); + }); diff --git a/s2-site/examples/interaction/basic/demo/hover.ts b/s2-site/examples/interaction/basic/demo/hover.ts index 88a4795ef8..761c274f9b 100644 --- a/s2-site/examples/interaction/basic/demo/hover.ts +++ b/s2-site/examples/interaction/basic/demo/hover.ts @@ -13,6 +13,13 @@ fetch( interaction: { // 悬停高亮 hoverHighlight: true, + // 等同于 + // hoverHighlight: { + // rowHeader = true, // 高亮悬停格子所在行头 + // colHeader = true, // 高亮悬停格子所在列头 + // currentRow = true, // 高亮悬停格子所在行 + // currentCol = true, // 高亮悬停格子所在列 + // }, }, tooltip: { enable: true, diff --git a/s2-site/examples/interaction/basic/demo/meta.json b/s2-site/examples/interaction/basic/demo/meta.json index 292ec118a9..3be5a6e3d1 100644 --- a/s2-site/examples/interaction/basic/demo/meta.json +++ b/s2-site/examples/interaction/basic/demo/meta.json @@ -63,11 +63,19 @@ { "filename": "frozen.ts", "title": { - "zh": "行列冻结", - "en": "Freeze Rows And Cols" + "zh": "明细表 - 行列冻结", + "en": "TableSheet - Frozen Rows And Cols" }, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/%24a16EsOCR8/frozeb.gif" }, + { + "filename": "frozen-row-header.ts", + "title": { + "zh": "透视表 - 行头冻结", + "en": "PivotSheet - Frozen Row Header" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*kk0ETbbbnOsAAAAAAAAAAAAADmJ7AQ/original" + }, { "filename": "auto-reset-sheet-style.ts", "title": { @@ -75,6 +83,22 @@ "en": "Disable auto reset sheet style" }, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/Lb0%26u6LtAu/reset.gif" + }, + { + "filename": "hover-after-scroll.ts", + "title": { + "zh": "滚动后自动触发悬停状态", + "en": "Trigger hover after scroll" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*NbG7SakRzXUAAAAAAAAAAAAADmJ7AQ/original.gif" + }, + { + "filename": "state-theme.ts", + "title": { + "zh": "交互主题配置", + "en": "Interaction state theme" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*6zM5TpfV7qIAAAAAAAAAAAAADmJ7AQ/original" } ] } diff --git a/s2-site/examples/interaction/basic/demo/state-theme.ts b/s2-site/examples/interaction/basic/demo/state-theme.ts new file mode 100644 index 0000000000..3d43c15ac0 --- /dev/null +++ b/s2-site/examples/interaction/basic/demo/state-theme.ts @@ -0,0 +1,118 @@ +import { PivotSheet, S2Options } from '@antv/s2'; + +fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/2a5dbbc8-d0a7-4d02-b7c9-34f6ca63cff6.json', +) + .then((res) => res.json()) + .then((dataCfg) => { + const container = document.getElementById('container'); + + const s2Options: S2Options = { + width: 600, + height: 480, + interaction: { + hoverHighlight: true, + selectedCellHighlight: true, + selectedCellsSpotlight: true, + multiSelection: true, + selectedCellMove: true, + rangeSelection: true, + brushSelection: { + row: true, + col: true, + data: true, + }, + }, + tooltip: { + showTooltip: true, + }, + style: { + rowCfg: { + width: 100, + }, + cellCfg: { + width: 120, + }, + }, + }; + + const s2 = new PivotSheet(container, dataCfg, s2Options); + + s2.setTheme({ + // 刷选 (预选中遮罩框) + prepareSelectMask: { + backgroundColor: '#ccc', + }, + // 宽高调整 (热区, 参考线) + resizeArea: { + // 热区大小, 背景色 + size: 4, + background: 'rgba(0,0,0, 0.5)', + backgroundOpacity: 0, + + // 参考线 + guideLineColor: 'pink', + guideLineDisableColor: 'yellow', + guideLineDash: [4, 4], + }, + // 滚动条 + scrollBar: { + trackColor: 'rgba(0,0,0,0.01)', + thumbHoverColor: 'rgba(0,0,0,0.25)', + thumbColor: 'rgba(0,0,0,0.15)', + // 滑块最小宽度 + thumbHorizontalMinSize: 32, + thumbVerticalMinSize: 32, + size: 6, + hoverSize: 10, + lineCap: 'round', + }, + // 数值单元格 (其他单元格同理) + dataCell: { + cell: { + interactionState: { + // 悬停聚焦: 关闭悬停单元格时出现的 "黑色边框" + hoverFocus: { + // 边框设置为透明 + borderColor: 'transparent', + // 或者边框透明度设置为 0 + // borderOpacity: 0 + }, + // 十字悬停 + hover: { + backgroundOpacity: 0.2, + borderColor: 'transparent', + borderOpacity: 1, + }, + // 选中背景色/边框 + selected: { + backgroundColor: 'pink', + borderWidth: 3, + borderColor: '#dcdcdc', + borderOpacity: 1, + }, + // 未选中背景色/边框 + unselected: { + backgroundOpacity: 0.5, + textOpacity: 0.1, + opacity: 0.1, + }, + // 高亮效果 + highlight: { + textOpacity: 0.2, + backgroundColor: '#f63', + borderColor: '#f63', + borderOpacity: 1, + }, + // 预选 (刷选) + prepareSelect: { + borderColor: '#396', + borderOpacity: 1, + }, + }, + }, + }, + }); + + s2.render(); + }); diff --git a/s2-site/examples/interaction/custom/demo/double-click-hide-columns.ts b/s2-site/examples/interaction/custom/demo/double-click-hide-columns.ts index 2e9a9d8087..dbcc4ea574 100644 --- a/s2-site/examples/interaction/custom/demo/double-click-hide-columns.ts +++ b/s2-site/examples/interaction/custom/demo/double-click-hide-columns.ts @@ -34,7 +34,7 @@ class ContextMenuInteraction extends BaseEvent { } fetch( - 'https://gw.alipayobjects.com/os/bmw-prod/4eff53f3-f952-4b77-8862-4b6ecbd31667.json', + 'https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json', ) .then((res) => res.json()) .then((data) => { diff --git a/s2-site/examples/layout/adaptive/demo/react-adaptive.tsx b/s2-site/examples/layout/adaptive/demo/react-adaptive.tsx index f89d279ec9..8133f86da9 100644 --- a/s2-site/examples/layout/adaptive/demo/react-adaptive.tsx +++ b/s2-site/examples/layout/adaptive/demo/react-adaptive.tsx @@ -1,7 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { SheetComponent } from '@antv/s2-react'; -import { concat, debounce, forEach, map } from 'lodash'; fetch( 'https://gw.alipayobjects.com/os/bmw-prod/2a5dbbc8-d0a7-4d02-b7c9-34f6ca63cff6.json', @@ -13,13 +12,16 @@ fetch( height: 480, }; - ReactDOM.render( - <SheetComponent dataCfg={dataCfg} options={s2Options} adaptive={{ + <SheetComponent + dataCfg={dataCfg} + options={s2Options} + adaptive={{ width: true, height: false, - getContainer: () => document.getElementById('container') - }} />, + getContainer: () => document.getElementById('container'), + }} + />, document.getElementById('container'), ); }); diff --git a/s2-site/examples/layout/basic/demo/adaptive.ts b/s2-site/examples/layout/basic/demo/adaptive.ts new file mode 100644 index 0000000000..e63fdb9be5 --- /dev/null +++ b/s2-site/examples/layout/basic/demo/adaptive.ts @@ -0,0 +1,21 @@ +import { PivotSheet } from '@antv/s2'; + +fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/2a5dbbc8-d0a7-4d02-b7c9-34f6ca63cff6.json', +) + .then((res) => res.json()) + .then((dataCfg) => { + const container = document.getElementById('container'); + + const s2Options = { + width: 600, + height: 480, + style: { + // 了解更多: https://s2.antv.antgroup.com/api/general/s2-options#style + layoutWidthType: 'adaptive', + }, + }; + const s2 = new PivotSheet(container, dataCfg, s2Options); + + s2.render(); + }); diff --git a/s2-site/examples/layout/basic/demo/colAdaptive.ts b/s2-site/examples/layout/basic/demo/colAdaptive.ts index a8c54becc5..6c37ca45da 100644 --- a/s2-site/examples/layout/basic/demo/colAdaptive.ts +++ b/s2-site/examples/layout/basic/demo/colAdaptive.ts @@ -11,6 +11,7 @@ fetch( width: 600, height: 480, style: { + // 了解更多: https://s2.antv.antgroup.com/api/general/s2-options#style layoutWidthType: 'colAdaptive', }, }; diff --git a/s2-site/examples/layout/basic/demo/compact.ts b/s2-site/examples/layout/basic/demo/compact.ts index bf1b9ce198..408058793c 100644 --- a/s2-site/examples/layout/basic/demo/compact.ts +++ b/s2-site/examples/layout/basic/demo/compact.ts @@ -5,16 +5,32 @@ fetch( ) .then((res) => res.json()) .then((dataCfg) => { + // 增加几条长度不一致的 mock 数据 + dataCfg.data.at(0).number = 11111111; + dataCfg.data.at(6).number = 7777; + dataCfg.data.at(-1).number = 666666; + const container = document.getElementById('container'); const s2Options = { - width: 400, + width: 600, height: 480, style: { + // 了解更多: https://s2.antv.antgroup.com/api/general/s2-options#style layoutWidthType: 'compact', }, }; + const s2 = new PivotSheet(container, dataCfg, s2Options); + // 紧凑模式下, 列头宽度为实际内容宽度 (取当前列最大值, 采样每一列前 50 条数据) + s2.setTheme({ + dataCell: { + text: { + fontSize: 16, + }, + }, + }); + s2.render(); }); diff --git a/s2-site/examples/layout/basic/demo/meta.json b/s2-site/examples/layout/basic/demo/meta.json index be75879383..7ac8996af1 100644 --- a/s2-site/examples/layout/basic/demo/meta.json +++ b/s2-site/examples/layout/basic/demo/meta.json @@ -4,13 +4,21 @@ "en": "Pivot" }, "demos": [ + { + "filename": "adaptive.ts", + "title": { + "zh": "行列等宽布局 (默认)", + "en": "Adaptive (Default)" + }, + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/cEkDC%26g%24xj/df31b66d-4a76-4e69-be5a-f467af2d337d.png" + }, { "filename": "compact.ts", "title": { "zh": "紧凑布局", "en": "Compact" }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/yzBFZaMMLH/55a2d580-1121-468e-9d0b-25f3e9ae25df.png" + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*NN_lTpAqhQkAAAAAAAAAAAAADmJ7AQ/original" }, { "filename": "colAdaptive.ts", diff --git a/s2-site/examples/layout/custom/demo/custom-table-size.ts b/s2-site/examples/layout/custom/demo/custom-table-size.ts index 60e53ebb31..e633941010 100644 --- a/s2-site/examples/layout/custom/demo/custom-table-size.ts +++ b/s2-site/examples/layout/custom/demo/custom-table-size.ts @@ -1,6 +1,6 @@ import { S2DataConfig, S2Options, TableSheet } from '@antv/s2'; -fetch('https://assets.antv.antgroup.com/s2/basic.json') +fetch('https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json') .then((res) => res.json()) .then((data) => { // 详情请查看: https://s2.antv.antgroup.com/zh/docs/manual/advanced/custom/cell-size diff --git a/s2-site/examples/layout/custom/demo/hide-columns.ts b/s2-site/examples/layout/custom/demo/hide-columns.ts index ebc9c99c20..813dff20f9 100644 --- a/s2-site/examples/layout/custom/demo/hide-columns.ts +++ b/s2-site/examples/layout/custom/demo/hide-columns.ts @@ -1,6 +1,6 @@ import { TableSheet } from '@antv/s2'; -fetch('https://assets.antv.antgroup.com/s2/basic.json') +fetch('https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json') .then((res) => res.json()) .then((data) => { const container = document.getElementById('container'); diff --git a/s2-site/examples/layout/custom/demo/hide-value.ts b/s2-site/examples/layout/custom/demo/hide-value.ts index c0d82a1d68..0439fb10bf 100644 --- a/s2-site/examples/layout/custom/demo/hide-value.ts +++ b/s2-site/examples/layout/custom/demo/hide-value.ts @@ -1,7 +1,7 @@ import { S2DataConfig, S2Options } from '@antv/s2'; import { PivotSheet } from '@antv/s2'; -fetch('https://assets.antv.antgroup.com/s2/basic.json') +fetch('https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json') .then((res) => res.json()) .then((data) => { const container = document.getElementById('container'); diff --git a/s2-site/examples/layout/custom/demo/only-show-row-header.ts b/s2-site/examples/layout/custom/demo/only-show-row-header.ts index 0f0386fd29..f7736f8f8b 100644 --- a/s2-site/examples/layout/custom/demo/only-show-row-header.ts +++ b/s2-site/examples/layout/custom/demo/only-show-row-header.ts @@ -1,7 +1,7 @@ import { S2DataConfig, S2Options } from '@antv/s2'; import { PivotSheet } from '@antv/s2'; -fetch('https://assets.antv.antgroup.com/s2/basic.json') +fetch('https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json001215413-dev-S09001736318/s2/basic.json') .then((res) => res.json()) .then((data) => { const container = document.getElementById('container'); diff --git a/s2-site/examples/react-component/drill-dwon/API.en.md b/s2-site/examples/react-component/drill-down/API.en.md similarity index 100% rename from s2-site/examples/react-component/drill-dwon/API.en.md rename to s2-site/examples/react-component/drill-down/API.en.md diff --git a/s2-site/examples/react-component/drill-dwon/API.zh.md b/s2-site/examples/react-component/drill-down/API.zh.md similarity index 100% rename from s2-site/examples/react-component/drill-dwon/API.zh.md rename to s2-site/examples/react-component/drill-down/API.zh.md diff --git a/s2-site/examples/react-component/drill-dwon/demo/basic-panel.tsx b/s2-site/examples/react-component/drill-down/demo/basic-panel.tsx similarity index 100% rename from s2-site/examples/react-component/drill-dwon/demo/basic-panel.tsx rename to s2-site/examples/react-component/drill-down/demo/basic-panel.tsx diff --git a/s2-site/examples/react-component/drill-dwon/demo/for-pivot.tsx b/s2-site/examples/react-component/drill-down/demo/for-pivot.tsx similarity index 100% rename from s2-site/examples/react-component/drill-dwon/demo/for-pivot.tsx rename to s2-site/examples/react-component/drill-down/demo/for-pivot.tsx diff --git a/s2-site/examples/react-component/drill-dwon/demo/meta.json b/s2-site/examples/react-component/drill-down/demo/meta.json similarity index 100% rename from s2-site/examples/react-component/drill-dwon/demo/meta.json rename to s2-site/examples/react-component/drill-down/demo/meta.json diff --git a/s2-site/examples/react-component/drill-dwon/index.en.md b/s2-site/examples/react-component/drill-down/index.en.md similarity index 100% rename from s2-site/examples/react-component/drill-dwon/index.en.md rename to s2-site/examples/react-component/drill-down/index.en.md diff --git a/s2-site/examples/react-component/drill-dwon/index.zh.md b/s2-site/examples/react-component/drill-down/index.zh.md similarity index 100% rename from s2-site/examples/react-component/drill-dwon/index.zh.md rename to s2-site/examples/react-component/drill-down/index.zh.md diff --git a/s2-site/examples/react-component/pagination/demo/table.tsx b/s2-site/examples/react-component/pagination/demo/table.tsx index 26ad84e4b5..35d0c51028 100644 --- a/s2-site/examples/react-component/pagination/demo/table.tsx +++ b/s2-site/examples/react-component/pagination/demo/table.tsx @@ -50,11 +50,11 @@ fetch('https://assets.antv.antgroup.com/s2/basic-table-mode.json') options={s2Options} sheetType="table" showPagination={{ - onChange: (current) => { - console.log(current); + onChange: (current, pageSize) => { + console.log(current, pageSize); }, - onShowSizeChange: (pageSize) => { - console.log(pageSize); + onShowSizeChange: (current, pageSize) => { + console.log(current, pageSize); }, }} />, diff --git a/s2-site/examples/react-component/sheet/demo/editable.tsx b/s2-site/examples/react-component/sheet/demo/editable.tsx index 387614cb0a..c47d623562 100644 --- a/s2-site/examples/react-component/sheet/demo/editable.tsx +++ b/s2-site/examples/react-component/sheet/demo/editable.tsx @@ -47,11 +47,16 @@ fetch('https://assets.antv.antgroup.com/s2/basic-table-mode.json') }, }; + const onDataCellEditEnd = (meta) => { + console.log('onDataCellEditEnd', meta); + }; + ReactDOM.render( <SheetComponent dataCfg={s2DataConfig} options={s2Options} sheetType="editable" + onDataCellEditEnd={onDataCellEditEnd} />, document.getElementById('container'), ); diff --git a/s2-site/examples/react-component/sheet/demo/strategy-mini-chart.tsx b/s2-site/examples/react-component/sheet/demo/strategy-mini-chart.tsx index e779069b83..6036db5ba3 100644 --- a/s2-site/examples/react-component/sheet/demo/strategy-mini-chart.tsx +++ b/s2-site/examples/react-component/sheet/demo/strategy-mini-chart.tsx @@ -3,6 +3,11 @@ import ReactDOM from 'react-dom'; import { SheetComponent, SheetComponentOptions } from '@antv/s2-react'; import '@antv/s2-react/dist/style.min.css'; +/** + * 该示例为 React 版本的趋势分析表 + * 如何在普通图表中使用, 请查看: https://s2.antv.antgroup.com/zh/examples/custom/custom-cell#mini-chart + */ + fetch( 'https://gw.alipayobjects.com/os/bmw-prod/b942d973-7364-4fad-a10a-369426a61376.json', ) diff --git a/s2-site/examples/react-component/switcher/demo/pivot-header.tsx b/s2-site/examples/react-component/switcher/demo/pivot-header.tsx index 24fddced08..5a7143b7b2 100644 --- a/s2-site/examples/react-component/switcher/demo/pivot-header.tsx +++ b/s2-site/examples/react-component/switcher/demo/pivot-header.tsx @@ -5,7 +5,7 @@ import insertCss from 'insert-css'; import '@antv/s2-react/dist/style.min.css'; fetch( - 'https://gw.alipayobjects.com/os/bmw-prod/6eede6eb-8021-4da8-bb12-67891a5705b7.json', + 'https://render.alipay.com/p/yuyan/180020010001215413/s2/total-group.json', ) .then((res) => res.json()) .then((data) => { diff --git a/s2-site/examples/react-component/switcher/demo/pivot-with-children.tsx b/s2-site/examples/react-component/switcher/demo/pivot-with-children.tsx index 28148b39ba..7aa330585f 100644 --- a/s2-site/examples/react-component/switcher/demo/pivot-with-children.tsx +++ b/s2-site/examples/react-component/switcher/demo/pivot-with-children.tsx @@ -5,7 +5,7 @@ import insertCss from 'insert-css'; import '@antv/s2-react/dist/style.min.css'; fetch( - 'https://gw.alipayobjects.com/os/bmw-prod/0c913e28-7806-41b2-a046-df3c1586712c.json', + 'https://render.alipay.com/p/yuyan/180020010001215413/s2/pivot-switcher-with-chidlren.json', ) .then((res) => res.json()) .then((data) => { diff --git a/s2-site/examples/react-component/switcher/demo/pivot.tsx b/s2-site/examples/react-component/switcher/demo/pivot.tsx index e87431b98d..9c8a675b09 100644 --- a/s2-site/examples/react-component/switcher/demo/pivot.tsx +++ b/s2-site/examples/react-component/switcher/demo/pivot.tsx @@ -5,7 +5,7 @@ import insertCss from 'insert-css'; import '@antv/s2-react/dist/style.min.css'; fetch( - 'https://gw.alipayobjects.com/os/bmw-prod/6eede6eb-8021-4da8-bb12-67891a5705b7.json', + 'https://render.alipay.com/p/yuyan/180020010001215413/s2/total-group.json', ) .then((res) => res.json()) .then((data) => { diff --git a/s2-site/examples/react-component/tooltip/demo/custom-click-show-tooltip.tsx b/s2-site/examples/react-component/tooltip/demo/custom-click-show-tooltip.tsx index fd9e10673d..a3c3d49041 100644 --- a/s2-site/examples/react-component/tooltip/demo/custom-click-show-tooltip.tsx +++ b/s2-site/examples/react-component/tooltip/demo/custom-click-show-tooltip.tsx @@ -4,6 +4,8 @@ import { SheetComponent, SheetComponentOptions } from '@antv/s2-react'; import '@antv/s2-react/dist/style.min.css'; import { TargetCellInfo } from '@antv/s2'; +const CustomColCellTooltip = () => <div>col cell</div>; + fetch( 'https://gw.alipayobjects.com/os/bmw-prod/2a5dbbc8-d0a7-4d02-b7c9-34f6ca63cff6.json', ) @@ -20,24 +22,49 @@ fetch( }, }; - const CustomTooltip = () => <div>demo</div>; - - const onColCellClick = (cellInfo: TargetCellInfo) => { - if (!cellInfo?.viewMeta) { + const onColCellClick = ({ viewMeta, event }) => { + if (!viewMeta) { return; } - const { spreadsheet, id } = cellInfo.viewMeta; + + const { spreadsheet, id } = viewMeta; + + // 点击列头的 [家具] 试试 if (id === 'root[&]家具') { const position = { - x: cellInfo.event.clientX, - y: cellInfo.event.clientY, + x: event.clientX, + y: event.clientY, }; - spreadsheet.tooltip.show({ + + // 查看更多配置项: https://s2.antv.antgroup.com/api/basic-class/base-tooltip#tooltipshowoptions + spreadsheet.showTooltip({ position, - content: <CustomTooltip />, + content: <CustomColCellTooltip />, + options: { + operator: { + menus: [ + { + key: 'custom-a', + text: '操作1', + icon: 'Trend', + onClick: (cell) => { + console.log('操作1点击', cell); + }, + children: [ + { + key: 'custom-a-a', + text: '操作 1-1', + icon: 'Trend', + onClick: (cell) => { + console.log('操作 1-1 点击', cell); + }, + }, + ], + }, + ], + }, + }, }); - } else { - spreadsheet.hideTooltip(); } }; diff --git a/s2-site/examples/react-component/tooltip/demo/custom-content.tsx b/s2-site/examples/react-component/tooltip/demo/custom-content.tsx index 3c1c7edc3f..69637b494e 100644 --- a/s2-site/examples/react-component/tooltip/demo/custom-content.tsx +++ b/s2-site/examples/react-component/tooltip/demo/custom-content.tsx @@ -1,28 +1,57 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; import { SheetComponent, SheetComponentOptions } from '@antv/s2-react'; -import insertCss from 'insert-css'; import '@antv/s2-react/dist/style.min.css'; +import { Button } from 'antd'; +import insertCss from 'insert-css'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const CustomTooltip = <div className="tooltip-custom-component">content</div>; + +const RowCellTooltip = ({ title }) => ( + <div className="tooltip-custom-component">{title}</div> +); + +const ColCellTooltip = () => { + const [open, setOpen] = React.useState(false); + + return ( + <div className="tooltip-custom-component"> + <Button + onClick={() => { + setOpen(!open); + }} + > + 切换 + </Button> + <span style={{ marginLeft: 4 }}> + {open ? 'colTooltip1' : 'colTooltip2'} + </span> + </div> + ); +}; fetch( 'https://gw.alipayobjects.com/os/bmw-prod/2a5dbbc8-d0a7-4d02-b7c9-34f6ca63cff6.json', ) .then((res) => res.json()) .then((dataCfg) => { - const CustomTooltip = ( - <div className="tooltip-custom-component">content</div> - ); - const RowCellTooltip = ( - <div className="tooltip-custom-component">rowCellTooltip</div> - ); - const s2Options: SheetComponentOptions = { width: 600, height: 480, tooltip: { content: CustomTooltip, rowCell: { - content: RowCellTooltip, + content: (cell, defaultTooltipShowOptions) => { + console.log('当前单元格:', cell); + console.log('默认 tooltip 详细信息:', defaultTooltipShowOptions); + + const meta = cell.getMeta(); + + return <RowCellTooltip title={meta.label} />; + }, + }, + colCell: { + content: <ColCellTooltip />, }, }, }; diff --git a/s2-site/examples/react-component/tooltip/demo/custom-description.tsx b/s2-site/examples/react-component/tooltip/demo/custom-description.tsx index 3d75cf5ddb..e2ff699ce3 100644 --- a/s2-site/examples/react-component/tooltip/demo/custom-description.tsx +++ b/s2-site/examples/react-component/tooltip/demo/custom-description.tsx @@ -4,7 +4,7 @@ import { SheetComponent, SheetComponentOptions } from '@antv/s2-react'; import '@antv/s2-react/dist/style.min.css'; import { S2DataConfig } from '@antv/s2'; -fetch('https://assets.antv.antgroup.com/s2/basic.json') +fetch('https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json') .then((res) => res.json()) .then((data) => { const dataCfg: S2DataConfig = { diff --git a/s2-site/examples/react-component/tooltip/demo/custom-hover-show-tooltip.tsx b/s2-site/examples/react-component/tooltip/demo/custom-hover-show-tooltip.tsx index 8f8d3d994f..69de5ed312 100644 --- a/s2-site/examples/react-component/tooltip/demo/custom-hover-show-tooltip.tsx +++ b/s2-site/examples/react-component/tooltip/demo/custom-hover-show-tooltip.tsx @@ -25,12 +25,38 @@ fetch( const CustomDataCellTooltip = () => <div>data cell tooltip</div>; const onColCellHover = ({ event, viewMeta }) => { + // 查看更多配置项: https://s2.antv.antgroup.com/api/basic-class/base-tooltip#tooltipshowoptions viewMeta.spreadsheet.tooltip.show({ position: { x: event.clientX, y: event.clientY, }, content: <CustomColCellTooltip />, + // 自定义操作项 + options: { + operator: { + menus: [ + { + key: 'custom-a', + text: '操作1', + icon: 'Trend', + onClick: (cell) => { + console.log('操作1点击', cell); + }, + children: [ + { + key: 'custom-a-a', + text: '操作 1-1', + icon: 'Trend', + onClick: (cell) => { + console.log('操作 1-1 点击', cell); + }, + }, + ], + }, + ], + }, + }, }); }; diff --git a/s2-site/examples/theme/custom/demo/custom-palette.ts b/s2-site/examples/theme/custom/demo/custom-palette.ts index 249bd9a7e4..671451ec7b 100644 --- a/s2-site/examples/theme/custom/demo/custom-palette.ts +++ b/s2-site/examples/theme/custom/demo/custom-palette.ts @@ -1,7 +1,7 @@ import { PivotSheet } from '@antv/s2'; fetch( - 'https://gw.alipayobjects.com/os/bmw-prod/4eff53f3-f952-4b77-8862-4b6ecbd31667.json', + 'https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json', ) .then((res) => res.json()) .then((data) => { diff --git a/s2-site/examples/theme/custom/demo/custom-schema.ts b/s2-site/examples/theme/custom/demo/custom-schema.ts index 135cf95452..63cdc94c27 100644 --- a/s2-site/examples/theme/custom/demo/custom-schema.ts +++ b/s2-site/examples/theme/custom/demo/custom-schema.ts @@ -1,7 +1,7 @@ import { TableSheet } from '@antv/s2'; fetch( - 'https://gw.alipayobjects.com/os/bmw-prod/4eff53f3-f952-4b77-8862-4b6ecbd31667.json', + 'https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json', ) .then((res) => res.json()) .then((data) => { diff --git a/s2-site/examples/theme/custom/demo/custom-transparent-background.ts b/s2-site/examples/theme/custom/demo/custom-transparent-background.ts index c68bf393d7..1e7ad85005 100644 --- a/s2-site/examples/theme/custom/demo/custom-transparent-background.ts +++ b/s2-site/examples/theme/custom/demo/custom-transparent-background.ts @@ -1,7 +1,7 @@ import { PivotSheet } from '@antv/s2'; fetch( - 'https://gw.alipayobjects.com/os/bmw-prod/4eff53f3-f952-4b77-8862-4b6ecbd31667.json', + 'https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json', ) .then((res) => res.json()) .then((data) => { diff --git a/s2-site/examples/theme/default/demo/colorful.ts b/s2-site/examples/theme/default/demo/colorful.ts index eded49591f..616dd23cec 100644 --- a/s2-site/examples/theme/default/demo/colorful.ts +++ b/s2-site/examples/theme/default/demo/colorful.ts @@ -1,7 +1,7 @@ import { PivotSheet } from '@antv/s2'; fetch( - 'https://gw.alipayobjects.com/os/bmw-prod/4eff53f3-f952-4b77-8862-4b6ecbd31667.json', + 'https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json', ) .then((res) => res.json()) .then((data) => { diff --git a/s2-site/examples/theme/default/demo/default.ts b/s2-site/examples/theme/default/demo/default.ts index ef3218b288..45b4e137c3 100644 --- a/s2-site/examples/theme/default/demo/default.ts +++ b/s2-site/examples/theme/default/demo/default.ts @@ -1,7 +1,7 @@ import { PivotSheet } from '@antv/s2'; fetch( - 'https://gw.alipayobjects.com/os/bmw-prod/4eff53f3-f952-4b77-8862-4b6ecbd31667.json', + 'https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json', ) .then((res) => res.json()) .then((data) => { diff --git a/s2-site/examples/theme/default/demo/gray.ts b/s2-site/examples/theme/default/demo/gray.ts index fb123ab6bb..9b4f697f0b 100644 --- a/s2-site/examples/theme/default/demo/gray.ts +++ b/s2-site/examples/theme/default/demo/gray.ts @@ -1,7 +1,7 @@ import { PivotSheet } from '@antv/s2'; fetch( - 'https://gw.alipayobjects.com/os/bmw-prod/4eff53f3-f952-4b77-8862-4b6ecbd31667.json', + 'https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json', ) .then((res) => res.json()) .then((data) => { diff --git a/s2-site/playground/dataset/mock-dataset.json b/s2-site/playground/dataset/mock-dataset.json index ca28ad467a..0b9c979497 100644 --- a/s2-site/playground/dataset/mock-dataset.json +++ b/s2-site/playground/dataset/mock-dataset.json @@ -299,487 +299,289 @@ { "number": 26193, "type": "家具", - "sub_type": "桌子" + "sub_type": "桌子", + "price": 2020 }, { "number": 49709, - "type": "家具" + "type": "家具", + "price": 17920 }, { "number": 23516, "type": "家具", - "sub_type": "沙发" + "sub_type": "沙发", + "price": 15900 }, { "number": 29159, - "type": "办公用品" + "type": "办公用品", + "price": 48.4 }, { "number": 12321, "type": "办公用品", - "sub_type": "笔" + "sub_type": "笔", + "price": 43 }, { "number": 16838, "type": "办公用品", - "sub_type": "纸张" + "sub_type": "纸张", + "price": 5.4 }, { "number": 18375, "province": "浙江省", "type": "家具", - "sub_type": "桌子" + "sub_type": "桌子", + "price": 1140 }, { "number": 14043, "province": "浙江省", "type": "家具", - "sub_type": "沙发" + "sub_type": "沙发", + "price": 7850 }, { "number": 4826, "province": "浙江省", "type": "办公用品", - "sub_type": "笔" + "sub_type": "笔", + "price": 22 }, { "number": 5854, "province": "浙江省", "type": "办公用品", - "sub_type": "纸张" + "sub_type": "纸张", + "price": 3 }, { "number": 7818, "province": "四川省", "type": "家具", - "sub_type": "桌子" + "sub_type": "桌子", + "price": 880 }, { "number": 9473, "province": "四川省", "type": "家具", - "sub_type": "沙发" + "sub_type": "沙发", + "price": 8050 }, { "number": 7495, "province": "四川省", "type": "办公用品", - "sub_type": "笔" + "sub_type": "笔", + "price": 21 }, { "number": 10984, "province": "四川省", "type": "办公用品", - "sub_type": "纸张" + "sub_type": "纸张", + "price": 2.4 }, { "number": 13132, "province": "浙江省", "city": "杭州市", - "type": "家具" + "type": "家具", + "price": 2400 }, { "number": 2288, "province": "浙江省", "city": "杭州市", - "type": "办公用品" + "type": "办公用品", + "price": 6 }, { "number": 15420, "province": "浙江省", - "city": "杭州市" + "city": "杭州市", + "price": 2406 }, { "number": 2999, "province": "浙江省", "city": "绍兴市", - "type": "家具" + "type": "家具", + "price": 2220 }, { "number": 2658, "province": "浙江省", "city": "绍兴市", - "type": "办公用品" + "type": "办公用品", + "price": 6.5 }, { "number": 5657, "province": "浙江省", - "city": "绍兴市" + "city": "绍兴市", + "price": 2226.5 }, { "number": 11111, "province": "浙江省", "city": "宁波市", - "type": "家具" + "type": "家具", + "price": 2060 }, { "number": 2668, "province": "浙江省", "city": "宁波市", - "type": "办公用品" + "type": "办公用品", + "price": 7.8 }, { "number": 13779, "province": "浙江省", - "city": "宁波市" + "city": "宁波市", + "price": 2067.8 }, { "number": 5176, "province": "浙江省", "city": "舟山市", - "type": "家具" + "type": "家具", + "price": 2310 }, { "number": 3066, "province": "浙江省", "city": "舟山市", - "type": "办公用品" + "type": "办公用品", + "price": 4.7 }, { "number": 8242, "province": "浙江省", - "city": "舟山市" + "city": "舟山市", + "price": 2314.7 }, { "number": 4174, "province": "四川省", "city": "成都市", - "type": "家具" + "type": "家具", + "price": 2340 }, { "number": 6339, "province": "四川省", "city": "成都市", - "type": "办公用品" + "type": "办公用品", + "price": 8.8 }, { "number": 10513, "province": "四川省", - "city": "成都市" + "city": "成都市", + "price": 2348.8 }, { "number": 4066, "province": "四川省", "city": "绵阳市", - "type": "家具" + "type": "家具", + "price": 2230 }, { "number": 3322, "province": "四川省", "city": "绵阳市", - "type": "办公用品" + "type": "办公用品", + "price": 5.5 }, { "number": 7388, "province": "四川省", - "city": "绵阳市" + "city": "绵阳市", + "price": 2235.5 }, { "number": 4276, "province": "四川省", "city": "南充市", - "type": "家具" + "type": "家具", + "price": 2250 }, { "number": 6008, "province": "四川省", "city": "南充市", - "type": "办公用品" + "type": "办公用品", + "price": 3.5 }, { "number": 10284, "province": "四川省", - "city": "南充市" + "city": "南充市", + "price": 2253.5 }, { "number": 4775, "province": "四川省", "city": "乐山市", - "type": "家具" + "type": "家具", + "price": 2110 }, { "number": 2810, "province": "四川省", "city": "乐山市", - "type": "办公用品" + "type": "办公用品", + "price": 5.6 }, { "number": 7585, "province": "四川省", - "city": "乐山市" + "city": "乐山市", + "price": 2115.6 }, { "number": 32418, "province": "浙江省", - "type": "家具" - }, - { - "number": 10680, - "province": "浙江省", - "type": "办公用品" - }, - { - "number": 43098, - "province": "浙江省" - }, - { - "number": 17291, - "province": "四川省", - "type": "家具" - }, - { - "number": 18479, - "province": "四川省", - "type": "办公用品" - }, - { - "number": 35770, - "province": "四川省" - }, - { - "number": 78868 - }, - { - "price": 2020, - "type": "家具", - "sub_type": "桌子" - }, - { - "price": 17920, - "type": "家具" - }, - { - "price": 15900, - "type": "家具", - "sub_type": "沙发" - }, - { - "price": 48.4, - "type": "办公用品" - }, - { - "price": 43, - "type": "办公用品", - "sub_type": "笔" - }, - { - "price": 5.4, - "type": "办公用品", - "sub_type": "纸张" - }, - { - "price": 1140, - "province": "浙江省", "type": "家具", - "sub_type": "桌子" + "price": 8990 }, { - "price": 7850, - "province": "浙江省", - "type": "家具", - "sub_type": "沙发" - }, - { - "price": 22, + "number": 10680, "province": "浙江省", "type": "办公用品", - "sub_type": "笔" + "price": 25 }, { - "price": 3, + "number": 43098, "province": "浙江省", - "type": "办公用品", - "sub_type": "纸张" + "price": 9015 }, { - "price": 880, - "province": "四川省", - "type": "家具", - "sub_type": "桌子" - }, - { - "price": 8050, + "number": 17291, "province": "四川省", "type": "家具", - "sub_type": "沙发" - }, - { - "price": 21, - "province": "四川省", - "type": "办公用品", - "sub_type": "笔" + "price": 8930 }, { - "price": 2.4, + "number": 18479, "province": "四川省", "type": "办公用品", - "sub_type": "纸张" - }, - { - "price": 2400, - "province": "浙江省", - "city": "杭州市", - "type": "家具" - }, - { - "price": 6, - "province": "浙江省", - "city": "杭州市", - "type": "办公用品" - }, - { - "price": 2406, - "province": "浙江省", - "city": "杭州市" + "price": 23.4 }, { - "price": 2220, - "province": "浙江省", - "city": "绍兴市", - "type": "家具" - }, - { - "price": 6.5, - "province": "浙江省", - "city": "绍兴市", - "type": "办公用品" - }, - { - "price": 2226.5, - "province": "浙江省", - "city": "绍兴市" - }, - { - "price": 2060, - "province": "浙江省", - "city": "宁波市", - "type": "家具" - }, - { - "price": 7.8, - "province": "浙江省", - "city": "宁波市", - "type": "办公用品" - }, - { - "price": 2067.8, - "province": "浙江省", - "city": "宁波市" - }, - { - "price": 2310, - "province": "浙江省", - "city": "舟山市", - "type": "家具" - }, - { - "price": 4.7, - "province": "浙江省", - "city": "舟山市", - "type": "办公用品" - }, - { - "price": 2314.7, - "province": "浙江省", - "city": "舟山市" - }, - { - "price": 2340, - "province": "四川省", - "city": "成都市", - "type": "家具" - }, - { - "price": 8.8, - "province": "四川省", - "city": "成都市", - "type": "办公用品" - }, - { - "price": 2348.8, - "province": "四川省", - "city": "成都市" - }, - { - "price": 2230, - "province": "四川省", - "city": "绵阳市", - "type": "家具" - }, - { - "price": 5.5, - "province": "四川省", - "city": "绵阳市", - "type": "办公用品" - }, - { - "price": 2235.5, - "province": "四川省", - "city": "绵阳市" - }, - { - "price": 2250, - "province": "四川省", - "city": "南充市", - "type": "家具" - }, - { - "price": 3.5, - "province": "四川省", - "city": "南充市", - "type": "办公用品" - }, - { - "price": 2253.5, - "province": "四川省", - "city": "南充市" - }, - { - "price": 2110, - "province": "四川省", - "city": "乐山市", - "type": "家具" - }, - { - "price": 5.6, - "province": "四川省", - "city": "乐山市", - "type": "办公用品" - }, - { - "price": 2115.6, - "province": "四川省", - "city": "乐山市" - }, - { - "price": 8990, - "province": "浙江省", - "type": "家具" - }, - { - "price": 25, - "province": "浙江省", - "type": "办公用品" - }, - { - "price": 9015, - "province": "浙江省" - }, - { - "price": 8930, - "province": "四川省", - "type": "家具" - }, - { - "price": 23.4, + "number": 35770, "province": "四川省", - "type": "办公用品" - }, - { - "price": 8953.4, - "province": "四川省" + "price": 8953.4 }, { + "number": 78868, "price": 17968.4 } ], diff --git a/s2-site/playground/sheet-component/config.ts b/s2-site/playground/sheet-component/config.ts index a7806bc943..7e5055f09e 100644 --- a/s2-site/playground/sheet-component/config.ts +++ b/s2-site/playground/sheet-component/config.ts @@ -5,17 +5,17 @@ import { meta, fields, rowSubTotalsDimensions, - colSubTotalsDimensions + colSubTotalsDimensions, } from '../dataset/mock-dataset.json'; export const sheetDataCfg: S2DataConfig = { data, totalData, meta, - fields + fields, }; export const subTotalsDimensions = { rowSubTotalsDimensions, - colSubTotalsDimensions -} + colSubTotalsDimensions, +}; diff --git a/s2-site/public/site.css b/s2-site/public/site.css index 9c282610b2..bbe2cc2b58 100644 --- a/s2-site/public/site.css +++ b/s2-site/public/site.css @@ -18,6 +18,10 @@ margin: 20px 0; } +.dumi-default-table { + margin: 0; +} + img[alt='preview'] { margin: 10px 0; } diff --git a/scripts/add-version.js b/scripts/add-version.js new file mode 100644 index 0000000000..33958b907b --- /dev/null +++ b/scripts/add-version.js @@ -0,0 +1,42 @@ +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +const nextVersion = process.argv[2]; + +const packageEntry = process.cwd(); + +const packageName = path.basename(packageEntry); +const srcEntry = path.resolve(packageEntry, './src/index.ts'); + +function generateNextVersion() { + const versionCode = `\nexport const version = '${nextVersion}';\n`; + fs.writeFileSync(srcEntry, versionCode, { encoding: 'utf8', flag: 'a+' }); +} + +function build() { + // 直接运行 yarn build 要报错,用一下蠢办法 + execSync(`yarn clean`, { + stdio: 'inherit', + }); + + execSync(`yarn build:umd & yarn build:cjs & yarn build:esm`, { + stdio: 'inherit', + }); + + execSync(`yarn dts:build && yarn dts:extract`, { + stdio: 'inherit', + }); +} + +function restoreVersionChange() { + execSync(`git restore ${srcEntry}`, { stdio: 'inherit' }); +} + +console.log(`🔖 ${packageName} 添加 nextVersion: ${nextVersion}\n`); + +generateNextVersion(); +build(); +restoreVersionChange(); + +console.log(`✅ ${packageName} nextVersion(${nextVersion}) 添加成功 \n`);