From da8d2aeaef4abb2e920c9b5b8ea14621d7ef87d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?w=C5=AB=20y=C4=81ng?= Date: Tue, 24 Sep 2024 22:01:33 +0800 Subject: [PATCH] chore: release tdesign-vue@1.10.1-naruto (#3338) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: replace type alias path to relative path (#3297) * fix(Select): fix keyboard event conflict with self-defined components (#3303) * chore: optimize (#3304) * fix(Select): support checkAll option when using filterable API (#3295) * perf(select): 全选常驻 * perf(select): 选择器搜索时支持全选选项常驻 * chore(select): 支持搜索词保留 * feat(menu): support multiple head menu (#3296) * feat(menu): support multiple head menu * feat(menu): lint multiple head menu code * fix(menu): update snap files * fix(menu): fix code style * chore: complete classprefix docs (#3306) * fix(input): fix status default value (#3307) * chore(deps-dev): bump jsdom from 20.0.3 to 25.0.0 (#3300) * chore(deps-dev): bump jsdom from 20.0.3 to 25.0.0 Bumps [jsdom](https://github.com/jsdom/jsdom) from 20.0.3 to 25.0.0. - [Release notes](https://github.com/jsdom/jsdom/releases) - [Changelog](https://github.com/jsdom/jsdom/blob/main/Changelog.md) - [Commits](https://github.com/jsdom/jsdom/compare/20.0.3...25.0.0) --- updated-dependencies: - dependency-name: jsdom dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * chore: update snapshot --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] * fix(upload): uploadPastedFiles =false 时第一次上传文件后会报错,导致整个vue 响应式丢失 (#3308) * fix(TimePicker): fixed only support hh:mm format * fix(TimePicker): disabled position only is start * fix(Upload): fixed vue error on uploadPastedFiles is false * feat(tag): support title api (#3309) * feat(tag): support title api * fix: test case * fix: test case * chore: fix test * chore: update snapshot --------- Co-authored-by: github-actions[bot] * feat(form): support form id and button form API (#3310) * feat: date-picker and time-picker support readonly API (#3311) * fix(TimePicker): fixed only support hh:mm format * fix(TimePicker): disabled position only is start * fix(Upload): fixed vue error on uploadPastedFiles is false * feat(DatePicker): support readonly * feat(TimePicker): support readonly * chore: update snapshot * docs: add readonly in api --------- Co-authored-by: github-actions[bot] * chore: update pr-comment-ci (#3312) * chore: update pr-comment-ci * chore: add generate:coverage-badge * feat(site): component Internationalization (#3316) * feat(site): component Internationalization * chore: remove`data` defined * docs(icon): fix installation instructions (#3318) * [Bug Fix]:修复表格开启虚拟滚动和loading 后,分页和表格内容顺序错乱的问题 (#3319) * fix(TimePicker): fixed only support hh:mm format * fix(TimePicker): disabled position only is start * fix(Upload): fixed vue error on uploadPastedFiles is false * docs: add readonly in api * fix(Table): virtual scroll pagination content order error * fix(tree-select): keys should support deep keys (#3313) * fix(TimePicker): fixed only support hh:mm format * fix(TimePicker): disabled position only is start * fix(Upload): fixed vue error on uploadPastedFiles is false * fix(Treeselect): suport deep keys like entiry.label * chore: change lodash import * fix(Slider): fix change-end emit and support empty array as initial value (#3320) * fix(TimePicker): fixed only support hh:mm format * fix(TimePicker): disabled position only is start * fix(Upload): fixed vue error on uploadPastedFiles is false * docs: add readonly in api * fix(Slider): change end event not emit * fix(Slider): range mode not use on value is empty array * feat(TimeRangePicker): auto open others panel, auto set start end value order (#3327) * fix(TimePicker): fixed only support hh:mm format * fix(TimePicker): disabled position only is start * fix(Upload): fixed vue error on uploadPastedFiles is false * docs: add readonly in api * feat(TimeRangePicker): auto open others panel, auto set start end value order * fix(TreeSelect): mulitple set true has empty selection tag (#3328) * fix(TimePicker): fixed only support hh:mm format * fix(TimePicker): disabled position only is start * fix(Upload): fixed vue error on uploadPastedFiles is false * docs: add readonly in api * fix(TreeSelect): mulitple set true has empty selection tag * chore: update test snap * style: code style fixed * fix(tree-select): fix multiple select click parent node callback (#3330) * feat: checkboxGroup/select/List support change component tag name (#3322) * feat(table): add class to identify expanded and folded row (#3331) * feat(table): add class to identify expanded and folded row when expandRowKeys is defined * chore: update snapshot --------- Co-authored-by: github-actions[bot] * feat(cascader): support scroll to first selected node (#3335) * feat(cascader): support scroll to first select node * chore: fix lint * feat(i18n): add ru_RU and it_IT (#3334) * fix(DatePicker): fix boundary week format bug (#3336) * docs: fix datepicker week firstdayofweek usage demo * fix(DatePicker): fix week calculate bug * fat(upload): uploader trigger context add disabled (#3321) * fix(TimePicker): fixed only support hh:mm format * fix(TimePicker): disabled position only is start * fix(Upload): fixed vue error on uploadPastedFiles is false * docs: add readonly in api * feat(Upload): trigger context add disabled * fix(TimePicker): use 12 hours, disable hour not add 12, check meridiem only support en (#3326) * fix(TimePicker): fixed only support hh:mm format * fix(TimePicker): disabled position only is start * fix(Upload): fixed vue error on uploadPastedFiles is false * docs: add readonly in api * fix(TimePicker): use 12 hours, disable hour not add 12, check meridiem only support en * fix(cascader): fix filterable parent click bug (#3333) * feat(Rate): add clearable prop (#3332) * fix(TimePicker): fixed only support hh:mm format * fix(TimePicker): disabled position only is start * fix(Upload): fixed vue error on uploadPastedFiles is false * docs: add readonly in api * feat(Rate): add allowClear Prop * chore: update test snap * chore: change prop name clearable * chore: clearable result should be 0 * chore: clearable result should be 0 --------- Co-authored-by: wū yāng * fix(SelectInput): overlay width not update on width change (#3325) * fix(TimePicker): fixed only support hh:mm format * fix(TimePicker): disabled position only is start * fix(Upload): fixed vue error on uploadPastedFiles is false * docs: add readonly in api * fix(SelectInput): overlay width not update on width change * chore: release 1.10.1 (#3337) * chore: release 1.10.1 * chore: fix cascader change bug * chore: changelog's changes --------- Co-authored-by: github-actions[bot] * chore: release 1.10.1-naruto --------- Signed-off-by: dependabot[bot] Co-authored-by: 企鹅岛岛主 <76833996+zhengchengshi@users.noreply.github.com> Co-authored-by: setcy Co-authored-by: liweijie0812 <674416404@qq.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: myron Co-authored-by: 阿菜 Cai <1064425721@qq.com> Co-authored-by: 阿菜 Cai --- .github/workflows/pr-comment-ci.yml | 270 +++- CHANGELOG.md | 37 + package.json | 5 +- site/src/App.vue | 18 +- src/_common | 2 +- src/button/button.en-US.md | 1 + src/button/button.md | 1 + src/button/props.ts | 5 + src/button/type.ts | 4 + src/cascader/cascader.tsx | 25 +- src/cascader/core/effect.ts | 17 +- src/checkbox/__tests__/index.test.jsx | 14 +- src/checkbox/group.tsx | 4 +- src/config-provider/config-provider.en-US.md | 29 +- src/config-provider/config-provider.md | 31 +- src/date-picker/DatePicker.tsx | 3 +- src/date-picker/_example-composition/week.vue | 19 +- src/date-picker/_example/week.vue | 24 +- src/date-picker/date-picker.en-US.md | 1 + src/date-picker/date-picker.md | 1 + src/date-picker/date-range-picker-props.ts | 5 + src/date-picker/hooks/useRange.ts | 2 +- src/date-picker/props.ts | 5 + src/date-picker/type.ts | 8 + src/form/form.en-US.md | 1 + src/form/form.md | 1 + src/form/props.ts | 5 + src/form/type.ts | 4 + src/icon/icon.en-US.md | 6 +- src/icon/icon.md | 2 +- src/input/input.en-US.md | 2 +- src/input/input.md | 2 +- src/input/props.ts | 5 +- src/input/type.ts | 3 +- src/list/hooks/useListItem.ts | 5 +- src/locale/it_IT.ts | 3 + src/locale/ru_RU.ts | 3 + src/menu/_example-composition/multi-head.vue | 160 ++ src/menu/_example/multi-head.vue | 170 +++ src/menu/head-menu.tsx | 14 +- src/menu/submenu.tsx | 7 +- src/menu/v-menu.ts | 12 +- src/popup/popup.tsx | 5 +- src/range-input/range-input-popup.tsx | 2 +- src/rate/__tests__/index.test.jsx | 19 + src/rate/_example-composition/clear.vue | 14 + src/rate/_example/clear.vue | 18 + src/rate/props.ts | 2 + src/rate/rate.en-US.md | 1 + src/rate/rate.md | 1 + src/rate/rate.tsx | 8 +- src/rate/type.ts | 5 + src/select-input/select-input.tsx | 2 +- src/select-input/useMultiple.tsx | 5 +- src/select-input/useOverlayInnerStyle.ts | 3 + src/select-input/useSingle.tsx | 5 +- src/select/hooks/useSelectOptions.ts | 10 +- src/select/select-panel.tsx | 1 + src/select/select.tsx | 12 +- src/slider/slider.tsx | 6 +- src/table/base-table.tsx | 3 + src/table/hooks/useClassName.ts | 2 + src/table/hooks/useRowExpand.tsx | 10 + src/table/primary-table.tsx | 9 +- src/table/tr.tsx | 7 +- src/table/type.ts | 1 + src/tabs/tabs.tsx | 7 +- src/tag/__tests__/vitest-tag.test.jsx | 99 +- src/tag/props.ts | 10 +- src/tag/tag.en-US.md | 6 +- src/tag/tag.md | 12 +- src/tag/tag.tsx | 27 +- src/tag/type.ts | 8 +- src/time-picker/panel/single-panel.tsx | 33 +- src/time-picker/props.ts | 5 + src/time-picker/time-picker.en-US.md | 1 + src/time-picker/time-picker.md | 1 + src/time-picker/time-picker.tsx | 1 + src/time-picker/time-range-picker-props.ts | 5 + src/time-picker/time-range-picker.tsx | 44 +- src/time-picker/type.ts | 8 + .../__snapshots__/index.test.jsx.snap | 35 +- src/tree-select/tree-select.tsx | 5 +- src/tree-select/useTreeSelect.ts | 47 +- src/tree-select/utils.ts | 19 +- src/tree/hooks/useRenderLabel.tsx | 2 +- src/upload/type.ts | 1 + src/upload/upload.en-US.md | 2 +- src/upload/upload.md | 2 +- src/upload/upload.tsx | 14 +- src/utils/helper.ts | 9 + test/snap/__snapshots__/csr.test.js.snap | 1297 ++++++++++++++++- test/snap/__snapshots__/ssr.test.js.snap | 8 +- 93 files changed, 2588 insertions(+), 207 deletions(-) create mode 100644 src/locale/it_IT.ts create mode 100644 src/locale/ru_RU.ts create mode 100644 src/menu/_example-composition/multi-head.vue create mode 100644 src/menu/_example/multi-head.vue create mode 100644 src/rate/_example-composition/clear.vue create mode 100644 src/rate/_example/clear.vue diff --git a/.github/workflows/pr-comment-ci.yml b/.github/workflows/pr-comment-ci.yml index bcca5e167..88cb71c0d 100644 --- a/.github/workflows/pr-comment-ci.yml +++ b/.github/workflows/pr-comment-ci.yml @@ -3,6 +3,8 @@ name: PR_COMMENT_CI on: issue_comment: types: [created] +env: + HUSKY: '0' jobs: check: @@ -11,29 +13,32 @@ jobs: next_action: ${{ steps.get-action.outputs.next_action }} if: ${{ github.event.issue.pull_request }} steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - uses: actions/github-script@v7 - id: get-action - with: + - uses: actions/checkout@v4 + with: + repository: Tencent/tdesign + sparse-checkout: | + .github/.pr-comment-ci-whitelist + sparse-checkout-cone-mode: false + + - uses: actions/github-script@v7 + id: get-action + with: script: | const user = context.payload.comment.user.login - core.debug(`user: ${user}`) + core.info(`user: ${user}`) - const fs = require('fs') - const CODEOWNERS = fs.readFileSync('.github/CODEOWNERS', 'utf8') - core.debug(`CODEOWNERS: ${CODEOWNERS}`) + const fs = require('fs'); + const whitelist = fs.readFileSync('.github/.pr-comment-ci-whitelist', 'utf8'); - let isReviewer = false; - CODEOWNERS.match(/@\w+/g).forEach((owner) => { - if (owner === `@${user}`) { - isReviewer = true + let isWhitelist = false; + whitelist.split('\n').forEach((owner) => { + if (owner === user) { + isWhitelist = true; } - }) + }); let next_action = '' - if (isReviewer) { + if (isWhitelist) { const body = context.payload.comment.body core.info(`body: ${body}`) if (body.startsWith('/update-common')) { @@ -41,13 +46,25 @@ jobs: } if (body.startsWith('/update-snapshot')) { next_action='update-snapshot' - } + } + if (body.startsWith('/update-coverage')) { + next_action='update-coverage' + } + + if(next_action){ + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'rocket', + }) + } } else { core.warning('You are not collaborator'); } core.info(`next_action: ${next_action}`) core.setOutput('next_action', next_action) - + update-common: needs: check runs-on: ubuntu-latest @@ -55,20 +72,74 @@ jobs: steps: - uses: actions/checkout@v4 with: + fetch-depth: 0 token: ${{ secrets.PERSONAL_TOKEN }} + - name: gh checkout pr env: GH_TOKEN: ${{ secrets.PERSONAL_TOKEN }} - run: gh pr checkout ${{ github.event.issue.number }} --recurse-submodules - - run: git submodule update --remote --merge - - name: Commit Common + run: gh pr checkout ${{ github.event.issue.number }} + + - name: git config run: | - git add . git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" - git commit -m "chore: update common" - git push - + + - name: submodule init and sync remote + run: | + git submodule update --init --remote + + - name: Commit common + run: | + git status + working_tree_clean=$(git status | grep -c 'nothing to commit, working tree clean') || true + if [ "$working_tree_clean" -eq "0" ]; then + git add . + git commit -m "chore: update common" + fi + git status + + - name: merge develop + run: | + git merge develop --no-commit || true + + - name: check conflicts + run: | + git status + conflict_count=$(git status | grep -c 'both modified:') || true + working_tree_clean=$(git status | grep -c 'nothing to commit, working tree clean') || true + common_conflict=$(git status | grep 'both modified:' | grep -c '_common') || true + conflicts_sum=$((common_conflict)) + echo "conflict_count: $conflict_count" + echo "working_tree_clean: $working_tree_clean" + echo "common_conflict: $common_conflict" + echo "conflicts_sum: $conflicts_sum" + + if [ "$working_tree_clean" -eq "1" ]; then + echo "nothing to commit, working tree clean" + exit 0 + fi + + if [ "$conflict_count" -gt "0" ]&&[ "$conflicts_sum" -eq "0" ]; then + echo "Unknown conflict " + git status + exit 1 + fi + + if [ "$common_conflict" -eq "1" ];then + git checkout --ours src/_common + git add src/_common + echo "resolve conflict _common" + fi + + git status + git commit -am "chore: merge develop" + + - name: git push + run: | + git status + git push || true + update-snapshot: needs: check runs-on: ubuntu-latest @@ -76,20 +147,163 @@ jobs: steps: - uses: actions/checkout@v4 with: + fetch-depth: 0 token: ${{ secrets.PERSONAL_TOKEN }} + - name: gh checkout pr env: GH_TOKEN: ${{ secrets.PERSONAL_TOKEN }} run: gh pr checkout ${{ github.event.issue.number }} --recurse-submodules + + - name: bot commtent + id: bot-comment + uses: actions/github-script@v7 + with: + script: | + const url = `${context.serverUrl}//${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}` + const urlLink = `[Open](${url})` + const { data: comment } = await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `⏳ 正在运行快照更新。。。 CI: ${urlLink}` + }) + return comment.id + + - name: git config + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + + - name: merge develop + run: | + git merge develop --no-commit || true + + - name: check conflicts + run: | + git status + conflict_count=$(git status | grep -c 'both modified:') || true + working_tree_clean=$(git status | grep -c 'nothing to commit, working tree clean') || true + csr_snap_conflict=$(git status | grep 'both modified:' | grep -c 'csr.test.jsx.snap') || true + ssr_snap_conflict=$(git status | grep 'both modified:' | grep -c 'ssr.test.jsx.snap') || true + common_conflict=$(git status | grep 'both modified:' | grep -c '_common') || true + conflicts_sum=$((csr_snap_conflict + ssr_snap_conflict + common_conflict)) + echo "conflict_count: $conflict_count" + echo "working_tree_clean: $working_tree_clean" + echo "csr_snap_conflict: $csr_snap_conflict" + echo "ssr_snap_conflict: $ssr_snap_conflict" + echo "common_conflict: $common_conflict" + echo "conflicts_sum: $conflicts_sum" + + if [ "$working_tree_clean" -eq "1" ]; then + echo "nothing to commit, working tree clean" + exit 0 + fi + + if [ "$conflict_count" -gt "0" ]&&[ "$conflicts_sum" -eq "0" ]; then + echo "Unknown conflict " + git status + exit 1 + fi + + if [ "$csr_snap_conflict" -eq "1" ];then + git checkout --theirs test/snap/__snapshots__/csr.test.jsx.snap + git add test/snap/__snapshots__/csr.test.jsx.snap + echo "resolve conflict csr.test.jsx.snap" + fi + + if [ "$ssr_snap_conflict" -eq "1" ];then + git checkout --theirs test/snap/__snapshots__/ssr.test.jsx.snap + git add test/snap/__snapshots__/ssr.test.jsx.snap + echo "resolve conflict ssr.test.jsx.snap" + fi + + if [ "$common_conflict" -eq "1" ];then + git checkout --theirs src/_common + git add src/_common + echo "resolve conflict _common" + fi + + git status + git commit -am "chore: merge develop" + - uses: actions/setup-node@v4 with: node-version: 18 + - run: npm install + - run: npm run test:update + - name: Commit Snapshot run: | - git add . + git status + working_tree_clean=$(git status | grep -c 'nothing to commit, working tree clean') || true + if [ "$working_tree_clean" -eq "0" ]; then + git add . + git commit -m "chore: update snapshot" + fi + git status + + - name: git push + run: | + git status + git push || true + + update-coverage: + needs: check + runs-on: ubuntu-latest + if: ${{ needs.check.outputs.next_action == 'update-coverage' }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.PERSONAL_TOKEN }} + + - name: gh checkout pr + env: + GH_TOKEN: ${{ secrets.PERSONAL_TOKEN }} + run: gh pr checkout ${{ github.event.issue.number }} --recurse-submodules + + - name: git config + run: | git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" - git commit -m "chore: update snapshot" - git push + + - name: bot commtent + id: bot-comment + uses: actions/github-script@v7 + with: + script: | + const url = `${context.serverUrl}//${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}` + const urlLink = `[Open](${url})` + const { data: comment } = await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `⏳ 正在运行 coverage badge 更新。。。 CI: ${urlLink}` + }) + return comment.id + + - uses: actions/setup-node@v4 + with: + node-version: 18 + + - run: npm install + + - run: npm run generate:coverage-badge + + - name: commit coverage badge + run: | + git status + working_tree_clean=$(git status | grep -c 'nothing to commit, working tree clean') || true + if [ "$working_tree_clean" -eq "0" ]; then + git add . + git commit -m "chore: update coverage badge" + fi + git status + + - name: git push + run: | + git status + git push || true diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f7ff7553..daf0e4ba3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,43 @@ toc: false docClass: timeline --- +## 🌈 1.10.1 `2024-09-24` + +### 🚀 Features + +- `Button`: 新增`form` API,原生的 form 属性,支持用于通过 form 属性触发对应 id 的 form 的表单事件 @uyarn ([#3310](https://github.com/Tencent/tdesign-vue/pull/3310)) +- `Cascader`: 支持在打开菜单时滚动到首个已选项所在节点的能力 @uyarn ([#3335](https://github.com/Tencent/tdesign-vue/pull/3335)) +- `DatePicker`: 支持`readonly`属性 @myronliu347 ([#3311](https://github.com/Tencent/tdesign-vue/pull/3311)) +- `Form`: 新增`id` API,表单原生的 id 属性,支持用于配合非表单内的按钮通过 form 属性来触发表单事件 @uyarn ([#3310](https://github.com/Tencent/tdesign-vue/pull/3310)) +- `Menu`: `expandType`为`normal`时,支持三级子菜单的展示 @setcy ([#3296](https://github.com/Tencent/tdesign-vue/pull/3296)) +- `Select`: 支持在过滤情况下使用`checkAll`配置全选的功能 @zhengchengshi ([#3295](https://github.com/Tencent/tdesign-vue/pull/3295)) +- `Table`: 可展开收起场景下新增 `t-table__row--expanded` 和 `t-table__row--folded` 用于区分展开和收起的行 @uyarn ([#3331](https://github.com/Tencent/tdesign-vue/pull/3331)) +- `Tag`: 新增 `title` API 控制鼠标悬停显示的文本 @liweijie0812 ([#3309](https://github.com/Tencent/tdesign-vue/pull/3309)) +- `TimePicker`: 支持`readonly`属性 @myronliu347 ([#3311](https://github.com/Tencent/tdesign-vue/pull/3311)) +- `TimeRangePicker`: 当结束时间大于开始时间时,自动调整时间范围的顺序 @myronliu347 ([#3327](https://github.com/Tencent/tdesign-vue/pull/3327)) +- `TreeSelect`: 修改多选状态下默认点击父节点选项的行为为选中父节点,如果需要点击展开的交互效果,请配置`treeProps.expandOnClickNode` @uyarn ([#3330](https://github.com/Tencent/tdesign-vue/pull/3330)) +- `Rate`: 新增支持`clearable` API,用于清空评分 @myronliu347 ([#3332](https://github.com/Tencent/tdesign-vue/pull/3332)) + +### 🐞 Bug Fixes + +- `Cascader`: 修复过滤条件下选择父节点导致样式异常的问题 @uyarn ([#3333](https://github.com/Tencent/tdesign-vue/pull/3333)) +- `DatePicker`: 修复周选择器下,年份边界日期返回格式错误的问题 @uyarn ([#3336](https://github.com/Tencent/tdesign-vue/pull/3336)) +- `Select`: 修复下拉面板存在自定义节点且存在回车等操作时与组件自身键盘事件冲突的问题 @uyarn ([#3303](https://github.com/Tencent/tdesign-vue/pull/3303)) +- `SelectInput`: 修复动态变化输入框宽度的情况下,下拉菜单宽度没有动态跟随变化的问题 @myronliu347 ([#3325](https://github.com/Tencent/tdesign-vue/pull/3325)) +- `Slider`: 修复`change-end`事件回到没有正确`emit`的问题 @myronliu347 ([#3320](https://github.com/Tencent/tdesign-vue/pull/3320)) +- `Table`: 修复表格开启虚拟滚动和 `loading` 后,分页和表格内容顺序错乱的问题 @myronliu347 ([#3319](https://github.com/Tencent/tdesign-vue/pull/3319)) +- `TimePicker`: 修复 12 小时制切换在 `dayjs` 切换中文情况下失效的问题 @myronliu347 ([#3326](https://github.com/Tencent/tdesign-vue/pull/3326)) +- `TreeSelect`: 修复无法支持深层的 `keys` 设置的问题 @myronliu347 ([#3313](https://github.com/Tencent/tdesign-vue/pull/3313)) +- `Upload`: 修复 `uploadPastedFiles = false` 时第一次上传文件后报错导致响应式丢失的问题 @myronliu347 ([#3308](https://github.com/Tencent/tdesign-vue/pull/3308)) + +### 🚧 Others + +- `DatePicker`: 优化周选择器配合`firstDayOfWeek`使用的问题,详情请查看示例代码 @uyarn ([#3336](https://github.com/Tencent/tdesign-vue/pull/3336)) +- `Dialog`: 优化非模态模式下的展示样式 @RSS1102 ([common#1945](https://github.com/Tencent/tdesign-common/pull/1945)) +- `Popup`: 修复文档内容错误 @novlan1 ([common#1941](https://github.com/Tencent/tdesign-common/pull/1941)) +- `i18n`: 新增俄语和意大利语的语言配置支持 @liweijie0812 ([#3334](https://github.com/Tencent/tdesign-vue/pull/3334)) + + ## 🌈 1.10.0 `2024-08-29` ### 🚀 Features - `Empty`: 新增空状态组件 `Empty`,用于空状态时的占位提示 @HaixingOoO ([#3287](https://github.com/Tencent/tdesign-vue/pull/3287)) diff --git a/package.json b/package.json index 90ce357ff..336711a91 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "tdesign-vue", "purename": "tdesign", - "version": "1.10.0-naruto", + "version": "1.10.1-naruto", "description": "tdesign-vue", "title": "tdesign-vue", "keywords": [ @@ -63,6 +63,7 @@ "lint": "npm run lint:tsc && eslint --ext .vue,.js,.ts,.tsx ./ --max-warnings 0", "lint:tsc": "tsc --emitDeclarationOnly", "generate:usage": "node script/generate-usage/index.js", + "generate:coverage-badge": "npm run test:coverage && node script/test/generate-coverage.js", "test": "npm run test:unit && npm run test:snap", "test:unit": "vitest run --config vitest.config.js", "test:update": "vitest run -u && npm run test:snap-update", @@ -156,7 +157,7 @@ "gray-matter": "^4.0.3", "husky": "^7.0.4", "hyphenate": "^0.2.4", - "jsdom": "^20.0.1", + "jsdom": "^25.0.0", "less": "^4.1.2", "lint-staged": "^12.3.7", "mockdate": "^3.0.2", diff --git a/site/src/App.vue b/site/src/App.vue index ca3f2ba4a..e04eacd4d 100644 --- a/site/src/App.vue +++ b/site/src/App.vue @@ -1,10 +1,24 @@ + + diff --git a/src/menu/_example/multi-head.vue b/src/menu/_example/multi-head.vue new file mode 100644 index 000000000..12299ade7 --- /dev/null +++ b/src/menu/_example/multi-head.vue @@ -0,0 +1,170 @@ + + + + diff --git a/src/menu/head-menu.tsx b/src/menu/head-menu.tsx index 7e90a0f4c..8fc307fb7 100644 --- a/src/menu/head-menu.tsx +++ b/src/menu/head-menu.tsx @@ -7,6 +7,7 @@ import { TdMenuInterface, TdOpenType } from './const'; import { Tabs, TabPanel } from '../tabs'; import { renderContent, renderTNodeJSX } from '../utils/render-tnode'; import VMenu from './v-menu'; +import type { VMenuData } from './v-menu'; import { usePrefixClass } from '../hooks/useConfig'; export default defineComponent({ @@ -131,14 +132,19 @@ export default defineComponent({ }; }, methods: { - renderNormalSubmenu() { - if (this.submenu.length === 0) return null; + renderNormalSubmenu(node: VMenuData[], depth: number) { + if (node.length === 0) return null; + return (
    { {this.submenu.map((item) => ( - + + {item.children && item.children.length > 0 + ? this.renderNormalSubmenu(item.children, depth + 1) + : null} + ))} } @@ -159,7 +165,7 @@ export default defineComponent({
      {renderContent(this, 'default', 'content')}
    {operations &&
    {operations}
    } - {this.mode === 'normal' && this.renderNormalSubmenu()} + {this.mode === 'normal' && this.renderNormalSubmenu(this.submenu, 1)} ); }, diff --git a/src/menu/submenu.tsx b/src/menu/submenu.tsx index a0aa1a017..80095b8c6 100644 --- a/src/menu/submenu.tsx +++ b/src/menu/submenu.tsx @@ -1,4 +1,5 @@ import { + h, defineComponent, computed, inject, @@ -204,7 +205,11 @@ export default defineComponent({ }); onMounted(() => { - menu?.vMenu?.add({ value: props.value, parent: submenu?.value }); + menu?.vMenu?.add({ + value: props.value, + parent: submenu?.value, + vnode: isFunction(props.title) ? [props.title(h)] : props.title, + }); const instance = getCurrentInstance().proxy; let node = instance.$parent; diff --git a/src/menu/v-menu.ts b/src/menu/v-menu.ts index f4c5eab24..999800e8b 100644 --- a/src/menu/v-menu.ts +++ b/src/menu/v-menu.ts @@ -3,7 +3,7 @@ import { VNodeChildren } from 'vue'; type MenuValue = string | number; type MenuNode = MenuValue | VMenuData; -interface VMenuData { +export interface VMenuData { value: MenuValue; parent?: MenuNode; children: VMenuData[]; @@ -57,7 +57,9 @@ export default class VMenu { constructor(options: Record) { const root: VMenuData = { - value: null, parent: null, children: [], + value: null, + parent: null, + children: [], }; this.data = root; this.isMutex = options?.isMutex; @@ -114,9 +116,9 @@ export default class VMenu { } const sameParentNodes = getTreeSameParentNodes(this.data, val); - const sameLevelSubmenuValues = new Set(sameParentNodes - .filter((node) => node.children?.length > 0 && node.value !== val) - .map((child) => child.value)); + const sameLevelSubmenuValues = new Set( + sameParentNodes.filter((node) => node.children?.length > 0 && node.value !== val).map((child) => child.value), + ); this.expandValues.forEach((val) => { const isHit = sameLevelSubmenuValues.has(val); diff --git a/src/popup/popup.tsx b/src/popup/popup.tsx index 69c09ae37..161e16b9d 100644 --- a/src/popup/popup.tsx +++ b/src/popup/popup.tsx @@ -107,8 +107,10 @@ export default mixins(classPrefixMixins).extend({ } }); } + this.$nextTick(() => { this.popupMounted(); + this.updateOverlayInnerStyle(); }); } else { this.preventClosing(false); @@ -211,7 +213,7 @@ export default mixins(classPrefixMixins).extend({ // popup弹出第一次初始化暴露事件 popupMounted() { - // 用于select定位事件 + // 用于下拉组件定位事件 const overlayEl = this.$refs?.overlay as HTMLElement; if (overlayEl) { this.updateScrollTop?.(overlayEl); @@ -442,6 +444,7 @@ export default mixins(classPrefixMixins).extend({ onResize={() => { if (visible) { this.updatePopper(); + this.updateOverlayInnerStyle(); } }} parent={this} diff --git a/src/range-input/range-input-popup.tsx b/src/range-input/range-input-popup.tsx index 18114b17b..861ef6542 100644 --- a/src/range-input/range-input-popup.tsx +++ b/src/range-input/range-input-popup.tsx @@ -45,7 +45,7 @@ export default defineComponent({ visible={this.popupVisible || innerPopupVisible} {...{ props: { - disabled: this.disabled, + disabled: this.disabled || this.readonly, overlayInnerStyle: tOverlayInnerStyle, onVisibleChange: onInnerPopupVisibleChange, ...(this.popupProps as Object), diff --git a/src/rate/__tests__/index.test.jsx b/src/rate/__tests__/index.test.jsx index f38573869..795337025 100644 --- a/src/rate/__tests__/index.test.jsx +++ b/src/rate/__tests__/index.test.jsx @@ -30,6 +30,25 @@ describe('Rate', () => { expect(wrapper.find('.t-rate__item--half').exists()).toBeTruthy(); }); + it('clear', async () => { + const wrapper = mount({ + data() { + return { + value: 4, + }; + }, + render() { + return (this.value = v)} clearable />; + }, + }); + + const items = wrapper.findAll('.t-rate__item--full'); + expect(items.length).toBe(4); + + items.at(3).trigger('click'); + await wrapper.vm.$nextTick(); + expect(wrapper.vm.value).toBe(0); + }); it('custom icon', () => { const wrapper = mount({ render() { diff --git a/src/rate/_example-composition/clear.vue b/src/rate/_example-composition/clear.vue new file mode 100644 index 000000000..4216f4688 --- /dev/null +++ b/src/rate/_example-composition/clear.vue @@ -0,0 +1,14 @@ + + diff --git a/src/rate/_example/clear.vue b/src/rate/_example/clear.vue new file mode 100644 index 000000000..265131567 --- /dev/null +++ b/src/rate/_example/clear.vue @@ -0,0 +1,18 @@ + + diff --git a/src/rate/props.ts b/src/rate/props.ts index ecb63c667..db3a7fff2 100644 --- a/src/rate/props.ts +++ b/src/rate/props.ts @@ -10,6 +10,8 @@ import { PropType } from 'vue'; export default { /** 是否允许半选 */ allowHalf: Boolean, + /** 是否允许清除 */ + clearable: Boolean, /** 评分图标的颜色,样式中默认为 #ED7B2F。一个值表示设置选中高亮的五角星颜色,示例:[选中颜色]。数组则表示分别设置 选中高亮的五角星颜色 和 未选中暗灰的五角星颜色,[选中颜色,未选中颜色]。示例:['#ED7B2F', '#E3E6EB'] */ color: { type: [String, Array] as PropType, diff --git a/src/rate/rate.en-US.md b/src/rate/rate.en-US.md index 8e87cf758..0d9916b48 100644 --- a/src/rate/rate.en-US.md +++ b/src/rate/rate.en-US.md @@ -7,6 +7,7 @@ name | type | default | description | required -- | -- | -- | -- | -- allowHalf | Boolean | false | \- | N +clearable | Boolean | false | \- | N color | String / Array | '#ED7B2F' | Typescript:`string \| Array` | N count | Number | 5 | \- | N disabled | Boolean | undefined | \- | N diff --git a/src/rate/rate.md b/src/rate/rate.md index 89a465e9e..4a733cf74 100644 --- a/src/rate/rate.md +++ b/src/rate/rate.md @@ -7,6 +7,7 @@ 名称 | 类型 | 默认值 | 描述 | 必传 -- | -- | -- | -- | -- allowHalf | Boolean | false | 是否允许半选 | N +clearable | Boolean | false | 是否允许清除 | N color | String / Array | '#ED7B2F' | 评分图标的颜色,样式中默认为 #ED7B2F。一个值表示设置选中高亮的五角星颜色,示例:[选中颜色]。数组则表示分别设置 选中高亮的五角星颜色 和 未选中暗灰的五角星颜色,[选中颜色,未选中颜色]。示例:['#ED7B2F', '#E3E6EB']。TS 类型:`string \| Array` | N count | Number | 5 | 评分的数量 | N disabled | Boolean | undefined | 是否禁用评分 | N diff --git a/src/rate/rate.tsx b/src/rate/rate.tsx index 6e4c37e8a..f6ffe4a2e 100644 --- a/src/rate/rate.tsx +++ b/src/rate/rate.tsx @@ -55,7 +55,13 @@ export default defineComponent({ const clickHandler = (event: MouseEvent, index: number) => { if (props.disabled) return; - setStarValue(getStarValue(event, index)); + const value = getStarValue(event, index); + if (props.clearable && value === starValue.value) { + hoverValue.value = undefined; + setStarValue(0); + } else { + setStarValue(value); + } }; const getStarCls = (index: number) => { diff --git a/src/rate/type.ts b/src/rate/type.ts index 3a0e12aef..13cc32dfb 100644 --- a/src/rate/type.ts +++ b/src/rate/type.ts @@ -12,6 +12,11 @@ export interface TdRateProps { * @default false */ allowHalf?: boolean; + /** + * 是否允许清除 + * @default false + */ + clearable?: boolean; /** * 评分图标的颜色,样式中默认为 #ED7B2F。一个值表示设置选中高亮的五角星颜色,示例:[选中颜色]。数组则表示分别设置 选中高亮的五角星颜色 和 未选中暗灰的五角星颜色,[选中颜色,未选中颜色]。示例:['#ED7B2F', '#E3E6EB'] * @default '#ED7B2F' diff --git a/src/select-input/select-input.tsx b/src/select-input/select-input.tsx index 64e252c23..979dfa6a3 100644 --- a/src/select-input/select-input.tsx +++ b/src/select-input/select-input.tsx @@ -129,7 +129,7 @@ export default defineComponent({ scopedSlots={{ ...this.$scopedSlots, content: this.$scopedSlots.panel }} hideEmptyPopup={true} key={this.multiple ? 'multiple' : 'single'} - disabled={this.disabled} + disabled={this.disabled || this.readonly} on={{ 'visible-change': this.onInnerPopupVisibleChange, }} diff --git a/src/select-input/useMultiple.tsx b/src/select-input/useMultiple.tsx index c6bf0f62d..c6d029d5c 100644 --- a/src/select-input/useMultiple.tsx +++ b/src/select-input/useMultiple.tsx @@ -2,6 +2,7 @@ import Vue, { SetupContext, computed, ref, toRefs, } from 'vue'; import isObject from 'lodash/isObject'; +import lodashGet from 'lodash/get'; import { TdSelectInputProps, SelectInputKeys } from './type'; import { SelectInputCommonProperties } from './interface'; import TagInput, { TagInputValue, TagInputProps } from '../tag-input'; @@ -36,9 +37,9 @@ export default function useMultiple(props: TdSelectInputProps, context: SetupCon const iKeys = computed(() => ({ ...DEFAULT_KEYS, ...props.keys })); const tags = computed(() => { if (!(props.value instanceof Array)) { - return isObject(props.value) ? [props.value[iKeys.value.label]] : [props.value]; + return isObject(props.value) ? [lodashGet(props.value, iKeys.value.label)] : [props.value]; } - return props.value.map((item) => (isObject(item) ? item[iKeys.value.label] : item)); + return props.value.map((item) => (isObject(item) ? lodashGet(item, iKeys.value.label) : item)); }); const tPlaceholder = computed(() => (!tags.value || !tags.value.length ? props.placeholder : '')); diff --git a/src/select-input/useOverlayInnerStyle.ts b/src/select-input/useOverlayInnerStyle.ts index 283458f73..ce83865c4 100644 --- a/src/select-input/useOverlayInnerStyle.ts +++ b/src/select-input/useOverlayInnerStyle.ts @@ -29,6 +29,9 @@ export default function useOverlayInnerStyle( const matchWidthFunc = (triggerElement: HTMLElement, popupElement: HTMLElement) => { // 避免因滚动条出现文本省略,预留宽度 8 const SCROLLBAR_WIDTH = popupElement.scrollHeight > popupElement.offsetHeight ? 8 : 0; + // 先重置掉设置才能获取到内部原始真实宽度 + // eslint-disable-next-line no-param-reassign + popupElement.style.width = ''; const width = popupElement.offsetWidth + SCROLLBAR_WIDTH >= triggerElement.offsetWidth ? popupElement.offsetWidth : triggerElement.offsetWidth; diff --git a/src/select-input/useSingle.tsx b/src/select-input/useSingle.tsx index 93c9bfaeb..aac9b35e0 100644 --- a/src/select-input/useSingle.tsx +++ b/src/select-input/useSingle.tsx @@ -4,6 +4,7 @@ import { import isObject from 'lodash/isObject'; import pick from 'lodash/pick'; +import lodashGet from 'lodash/get'; import { SelectInputCommonProperties } from './interface'; import { TdSelectInputProps } from './type'; import Input, { InputProps, StrInputProps } from '../input'; @@ -32,7 +33,7 @@ const DEFAULT_KEYS = { function getInputValue(value: TdSelectInputProps['value'], keys: TdSelectInputProps['keys']) { const iKeys = keys || DEFAULT_KEYS; - return isObject(value) ? value[iKeys.label] : value; + return isObject(value) ? lodashGet(value, iKeys.label) : value; } export default function useSingle(props: TdSelectInputProps, context: SetupContext) { @@ -106,7 +107,7 @@ export default function useSingle(props: TdSelectInputProps, context: SetupConte label: prefixContent.length ? () => prefixContent : undefined, autoWidth: props.autoWidth, autofocus: props.autofocus, - readonly: !props.allowInput, + readonly: !props.allowInput || props.readonly, placeholder: singleValueDisplay ? '' : props.placeholder, suffixIcon: !props.disabled && props.loading ? () => : props.suffixIcon, showClearIconOnEmpty: Boolean(!props.disabled && props.clearable && (inputValue.value || displayedValue)), diff --git a/src/select/hooks/useSelectOptions.ts b/src/select/hooks/useSelectOptions.ts index 0ced8b622..242925318 100644 --- a/src/select/hooks/useSelectOptions.ts +++ b/src/select/hooks/useSelectOptions.ts @@ -7,6 +7,9 @@ import { ref, Ref, computed, onBeforeUpdate, ComponentInternalInstance, watch, VNode, } from 'vue'; import get from 'lodash/get'; +import { getVNodeComponentName, getVueComponentName } from '../../utils/helper'; +import Option from '../option'; +import OptionGroup from '../optionGroup'; import { TdSelectProps, TdOptionProps, SelectOptionGroup, SelectValue, } from '../type'; @@ -60,8 +63,9 @@ export default function useSelectOptions( innerSlotRecord = instance.$slots.default; // 处理 slots 中 t-option 与 t-option-group const currentSlots = instance.$slots.default || []; - currentSlots.forEach((child: any) => { - if (child.componentOptions?.tag === 't-option') { + currentSlots.forEach((child: VNode) => { + const componentName = getVNodeComponentName(child); + if (componentName && componentName === getVueComponentName(Option)) { // 独立选项 innerOptions.push({ // 单独处理 style 和 class 参数的透传 @@ -73,7 +77,7 @@ export default function useSelectOptions( index: dynamicIndex, } as TdOptionProps); dynamicIndex += 1; - } else if (child.componentOptions?.tag === 't-option-group') { + } else if (componentName && componentName === getVueComponentName(OptionGroup)) { // 分组选项 const groupOption = { group: (child.componentOptions.propsData as TdOptionProps)?.label, diff --git a/src/select/select-panel.tsx b/src/select/select-panel.tsx index a07e225bc..86cd73204 100644 --- a/src/select/select-panel.tsx +++ b/src/select/select-panel.tsx @@ -92,6 +92,7 @@ export default defineComponent({ if (isFunction(props.filter)) { return props.filter(`${props.inputValue}`, option); } + if ((option as TdOptionProps)?.checkAll === true) return true; return option.label?.toLowerCase?.().indexOf(`${props.inputValue}`.toLowerCase()) > -1; }; diff --git a/src/select/select.tsx b/src/select/select.tsx index cda6c6228..c9642090f 100644 --- a/src/select/select.tsx +++ b/src/select/select.tsx @@ -12,7 +12,7 @@ import useVModel from '../hooks/useVModel'; import { useTNodeJSX } from '../hooks/tnode'; import { useConfig, usePrefixClass } from '../config-provider/useConfig'; import { - TdSelectProps, SelectValue, TdOptionProps, SelectValueChangeTrigger, + TdSelectProps, SelectValue, TdOptionProps, SelectValueChangeTrigger, SelectOption, } from './type'; import props from './props'; import TLoading from '../loading'; @@ -296,14 +296,22 @@ export default defineComponent({ // 全选点击回调,t-option 的事件调用到这里处理 const handleCheckAllClick = (e: MouseEvent | KeyboardEvent) => { + const filterMethods = (option: SelectOption) => { + if (isFunction(props.filter)) { + return props.filter(`${tInputValue.value}`, option); + } + return option.label?.toLowerCase?.().indexOf(`${tInputValue.value}`.toLowerCase()) > -1; + }; setInnerValue( isAllOptionsChecked.value ? [] : getAllSelectableOption(optionsList.value) + .filter(filterMethods) .map((option) => option.value) .slice(0, max.value || Infinity), { e, trigger: isAllOptionsChecked.value ? 'uncheck' : 'check' }, ); + !reserveKeyword?.value && setTInputValue(''); }; const handleCreate = () => { @@ -453,7 +461,7 @@ export default defineComponent({ }; if (displayOptionsLength === 0) return; const preventKeys = ['ArrowDown', 'ArrowUp', 'Enter', 'Escape', 'Tab']; - if (preventKeys.includes(e.code)) { + if (preventKeys.includes(e.code) && selectInputRef.value?.$el.contains(e.target)) { e.preventDefault(); } switch (e.code) { diff --git a/src/slider/slider.tsx b/src/slider/slider.tsx index 8866fc489..6274dec7d 100644 --- a/src/slider/slider.tsx +++ b/src/slider/slider.tsx @@ -228,8 +228,8 @@ export default mixins(classPrefixMixins).extend({ const { min, max, value } = this; if (this.range) { if (Array.isArray(value)) { - this.firstValue = Math.max(min || 0, value[0]); - this.secondValue = Math.min(max || 100, value[1]); + this.firstValue = Math.max(min || 0, value[0] ?? 0); + this.secondValue = Math.min(max || 100, value[1] ?? 0); } else { this.firstValue = min || 0; this.secondValue = max || 100; @@ -329,7 +329,7 @@ export default mixins(classPrefixMixins).extend({ }, emitChangeEnd() { const changeEndValue = this.range ? [this.firstValue, this.secondValue] : this.firstValue; - emitEvent>(this, 'changeEnd', changeEndValue); + emitEvent>(this, 'change-end', changeEndValue); }, getStopStyle(position: number) { return this.vertical ? { top: `calc(${100 - position}% - 1px)` } : { left: `${position}%` }; diff --git a/src/table/base-table.tsx b/src/table/base-table.tsx index 3624ba91f..782d364fc 100644 --- a/src/table/base-table.tsx +++ b/src/table/base-table.tsx @@ -574,6 +574,7 @@ export default defineComponent({ const tableContent = (
    this.tableRef : undefined} @@ -657,6 +659,7 @@ export default defineComponent({ {/* 右侧滚动条分隔线 */} {this.showRightDivider && (
    props.columns?.[0]?.fixed === 'left'); + const getExpandedRowClass = (params: RowClassNameParams) => { + // 如果没有配置展开行,则不需要增加展开收起相关的类名 + if (!showExpandedRow.value) return null; + const { row, rowKey } = params; + const currentRowKey = get(row, rowKey || 'id'); + return tableExpandClasses[tExpandedRowKeys.value?.includes(currentRowKey) ? 'rowExpanded' : 'rowFolded']; + }; + const onToggleExpand = (e: MouseEvent | KeyboardEvent, row: TableRowData) => { props.expandOnRowClick && e.stopPropagation(); const currentId = get(row, props.rowKey || 'id'); @@ -117,5 +126,6 @@ export default function useRowExpand(props: TdPrimaryTableProps, context: SetupC getExpandColumn, renderExpandedRow, onInnerExpandRowClick, + getExpandedRowClass, }; } diff --git a/src/table/primary-table.tsx b/src/table/primary-table.tsx index 675fbf52c..233810e7d 100644 --- a/src/table/primary-table.tsx +++ b/src/table/primary-table.tsx @@ -87,7 +87,12 @@ export default defineComponent({ const { tDisplayColumns, renderColumnController } = useColumnController(props, context); // 展开/收起行功能 const { - showExpandedRow, showExpandIconColumn, getExpandColumn, renderExpandedRow, onInnerExpandRowClick, + showExpandedRow, + showExpandIconColumn, + getExpandColumn, + renderExpandedRow, + onInnerExpandRowClick, + getExpandedRowClass, } = useRowExpand(props, context); // 排序功能 const { renderSortIcon } = useSorter(props, context); @@ -148,7 +153,7 @@ export default defineComponent({ // 如果想给 TR 添加类名,请在这里补充,不要透传更多额外 Props 到 BaseTable const tRowClassNames = computed(() => { - const tClassNames = [props.rowClassName, selectedRowClassNames.value]; + const tClassNames = [props.rowClassName, selectedRowClassNames.value, getExpandedRowClass]; return tClassNames.filter((v) => v); }); diff --git a/src/table/tr.tsx b/src/table/tr.tsx index 4663a028f..4ce9866db 100644 --- a/src/table/tr.tsx +++ b/src/table/tr.tsx @@ -180,7 +180,12 @@ export default defineComponent({ const classes = computed(() => { const customClasses = formatRowClassNames( props.rowClassName, - { row: props.row, rowIndex: props.rowIndex, type: 'body' }, + { + row: props.row, + rowKey: props.rowKey, + rowIndex: props.rowIndex, + type: 'body', + }, props.rowKey || 'id', ); return [trStyles.value?.classes, customClasses]; diff --git a/src/table/type.ts b/src/table/type.ts index 378515b69..c4924e76d 100644 --- a/src/table/type.ts +++ b/src/table/type.ts @@ -1035,6 +1035,7 @@ export type TableRowAttributes = export interface RowClassNameParams { row: T; rowIndex: number; + rowKey?: string; type?: 'body' | 'foot'; } diff --git a/src/tabs/tabs.tsx b/src/tabs/tabs.tsx index af81ad1b1..54092198a 100644 --- a/src/tabs/tabs.tsx +++ b/src/tabs/tabs.tsx @@ -1,5 +1,5 @@ import Vue, { VNode, VueConstructor } from 'vue'; -import kebabCase from 'lodash/kebabCase'; +import { getVNodeComponentName, getVueComponentName } from '../utils/helper'; import props from './props'; import TTabPanel from './tab-panel'; import TTabNav from './tab-nav'; @@ -68,9 +68,10 @@ export default mixins(Vue as VueConstructor, classPrefixMixi return; } const newPanels = this.listPanels + .filter((child) => getVNodeComponentName(child) === getVueComponentName(TTabPanel)) .map((panel: VNode) => panel.componentInstance as InstanceType) - .filter(Boolean) - .filter((child) => kebabCase(child?.$vnode?.tag).endsWith('t-tab-panel')); // 不可用classPrefix替换 此处是判断组件tag + .filter(Boolean); + const isUnchanged = () => newPanels.length === this.panels.length && this.panels.every((panel, index) => panel === newPanels[index]); if (isUnchanged() && !force) return; this.panels = newPanels; diff --git a/src/tag/__tests__/vitest-tag.test.jsx b/src/tag/__tests__/vitest-tag.test.jsx index 3d5c7a5c6..13b56f9c7 100644 --- a/src/tag/__tests__/vitest-tag.test.jsx +++ b/src/tag/__tests__/vitest-tag.test.jsx @@ -33,6 +33,56 @@ describe('Tag Component', () => { expect(wrapper2.find('.t-tag__icon-close').exists()).toBeTruthy(); }); + it('props.color is equal to #ff0000', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + const domWrapper = wrapper.findComponent(Tag); + expect(domWrapper.element.style.backgroundColor).toBe('rgb(255, 0, 0)'); + }); + it('props.color expect variant=dark', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + const domWrapper = wrapper.findComponent(Tag); + expect(domWrapper.element.style.backgroundColor).toBe('rgb(255, 0, 0)'); + expect(domWrapper.element.style.color).toBe('white'); + }); + it('props.color expect variant=light', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + const domWrapper = wrapper.findComponent(Tag); + expect(domWrapper.element.style.color).toBe('rgb(255, 0, 0)'); + expect(domWrapper.element.style.backgroundColor).toBe('rgba(255, 0, 0, 0.1)'); + }); + it('props.color expect variant=outline', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + const domWrapper = wrapper.findComponent(Tag); + expect(domWrapper.element.style.borderColor).toBe('#ff0000'); + expect(domWrapper.element.style.color).toBe('rgb(255, 0, 0)'); + }); + it('props.color expect variant=light-outline', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + const domWrapper = wrapper.findComponent(Tag); + expect(domWrapper.element.style.borderColor).toBe('#ff0000'); + expect(domWrapper.element.style.color).toBe('rgb(255, 0, 0)'); + }); + it('props.content works fine', () => { const wrapper = mount({ render() { @@ -106,9 +156,19 @@ describe('Tag Component', () => { return ; }, }); - const domWrapper1 = wrapper.find('.t-tag--text'); - expect(domWrapper1.element.style.maxWidth).toBe('150px'); - expect(domWrapper1.attributes('title')).toBe('This is a long long long long long tag'); + const domWrapper = wrapper.find('.t-tag--text'); + expect(domWrapper.attributes('title')).toBe('This is a long long long long long tag'); + expect(domWrapper.element.style.maxWidth).toBe('150px'); + }); + it('props.maxWidth is equal to 150', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + const domWrapper = wrapper.find('.t-tag--text'); + expect(domWrapper.attributes('title')).toBe('This is a long long long long long tag'); + expect(domWrapper.element.style.maxWidth).toBe('150px'); }); const shapeClassNameList = [{ 't-tag--square': false }, 't-tag--round', 't-tag--mark']; @@ -156,6 +216,39 @@ describe('Tag Component', () => { }); }); + it('props.title is equal to This is a long tag', () => { + const wrapper = mount({ + render() { + return ( + + ); + }, + }); + const domWrapper = wrapper.find('.t-tag--text'); + expect(domWrapper.element.style.maxWidth).toBe('150px'); + expect(domWrapper.attributes('title')).toBe('This is a long tag'); + }); + it('props.title is equal to ', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + const domWrapper = wrapper.find('.t-tag--text'); + expect(domWrapper.element.style.maxWidth).toBe('150px'); + expect(domWrapper.attributes('title')).toBeUndefined(); + }); + it('props.title is equal to undefined', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + const domWrapper = wrapper.find('.t-tag--text'); + expect(domWrapper.element.style.maxWidth).toBe('150px'); + expect(domWrapper.attributes('title')).toBeUndefined(); + }); + ['dark', 'light', 'outline', 'light-outline'].forEach((item) => { it(`props.variant is equal to ${item}`, () => { const wrapper = mount({ diff --git a/src/tag/props.ts b/src/tag/props.ts index 68922af95..56508b65b 100644 --- a/src/tag/props.ts +++ b/src/tag/props.ts @@ -11,7 +11,10 @@ export default { /** 标签是否可关闭 */ closable: Boolean, /** 自定义标签颜色 */ - color: String, + color: { + type: String, + default: '', + }, /** 组件子元素 */ content: { type: [String, Function] as PropType, @@ -58,6 +61,11 @@ export default { return ['default', 'primary', 'warning', 'danger', 'success'].includes(val); }, }, + /** 标签标题,在标签hover时展示,默认为标签内容 */ + title: { + type: String, + default: '', + }, /** 标签风格变体 */ variant: { type: String as PropType, diff --git a/src/tag/tag.en-US.md b/src/tag/tag.en-US.md index fdeded2c8..f7aed69bc 100644 --- a/src/tag/tag.en-US.md +++ b/src/tag/tag.en-US.md @@ -1,12 +1,13 @@ :: BASE_DOC :: ## API + ### Tag Props name | type | default | description | required -- | -- | -- | -- | -- closable | Boolean | false | \- | N -color | String | '' | custom color | N +color | String | - | self-defined tag color | N content | String / Slot / Function | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N default | String / Slot / Function | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N disabled | Boolean | false | \- | N @@ -15,6 +16,7 @@ maxWidth | String / Number | - | \- | N shape | String | square | options: square/round/mark | N size | String | medium | options: small/medium/large。Typescript:`SizeEnum`。[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N theme | String | default | options: default/primary/warning/danger/success | N +title | String | - | title of tag | N variant | String | dark | options: dark/light/outline/light-outline | N onClick | Function | | Typescript:`(context: { e: MouseEvent }) => void`
    | N onClose | Function | | Typescript:`(context: { e: MouseEvent }) => void`
    | N @@ -26,6 +28,7 @@ name | params | description click | `(context: { e: MouseEvent })` | \- close | `(context: { e: MouseEvent })` | \- + ### CheckTag Props name | type | default | description | required @@ -49,6 +52,7 @@ name | params | description change | `(checked: boolean, context: CheckTagChangeContext)` | [see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/tag/type.ts)。
    `interface CheckTagChangeContext { e: MouseEvent \| KeyboardEvent; value: string \| number }`
    click | `(context: { e: MouseEvent })` | \- + ### CheckTagGroup Props name | type | default | description | required diff --git a/src/tag/tag.md b/src/tag/tag.md index f16c47e9c..6f3fa7b31 100644 --- a/src/tag/tag.md +++ b/src/tag/tag.md @@ -5,12 +5,13 @@ {{ custom-color }} ## API + ### Tag Props -名称 | 类型 | 默认值 | 说明 | 必传 +名称 | 类型 | 默认值 | 描述 | 必传 -- | -- | -- | -- | -- closable | Boolean | false | 标签是否可关闭 | N -color | String | '' | 颜色 | N +color | String | - | 自定义标签颜色 | N content | String / Slot / Function | - | 组件子元素。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N default | String / Slot / Function | - | 组件子元素,同 `content`。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N disabled | Boolean | false | 标签禁用态,失效标签不能触发事件。默认风格(theme=default)才有禁用态 | N @@ -19,6 +20,7 @@ maxWidth | String / Number | - | 标签最大宽度,宽度超出后会出现 shape | String | square | 标签类型,有三种:方形、圆角方形、标记型。可选项:square/round/mark | N size | String | medium | 标签尺寸。可选项:small/medium/large。TS 类型:`SizeEnum`。[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N theme | String | default | 组件风格,用于描述组件不同的应用场景。可选项:default/primary/warning/danger/success | N +title | String | - | 标签标题,在标签hover时展示,默认为标签内容 | N variant | String | dark | 标签风格变体。可选项:dark/light/outline/light-outline | N onClick | Function | | TS 类型:`(context: { e: MouseEvent }) => void`
    点击时触发 | N onClose | Function | | TS 类型:`(context: { e: MouseEvent }) => void`
    如果关闭按钮存在,点击关闭按钮时触发 | N @@ -30,9 +32,10 @@ onClose | Function | | TS 类型:`(context: { e: MouseEvent }) => void`
    click | `(context: { e: MouseEvent })` | 点击时触发 close | `(context: { e: MouseEvent })` | 如果关闭按钮存在,点击关闭按钮时触发 + ### CheckTag Props -名称 | 类型 | 默认值 | 说明 | 必传 +名称 | 类型 | 默认值 | 描述 | 必传 -- | -- | -- | -- | -- checked | Boolean | - | 标签选中的状态,默认风格(theme=default)才有选中态。支持语法糖 `v-model` | N defaultChecked | Boolean | - | 标签选中的状态,默认风格(theme=default)才有选中态。非受控属性 | N @@ -53,9 +56,10 @@ onClick | Function | | TS 类型:`(context: { e: MouseEvent }) => void`
    change | `(checked: boolean, context: CheckTagChangeContext)` | 状态切换时触发。[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/tag/type.ts)。
    `interface CheckTagChangeContext { e: MouseEvent \| KeyboardEvent; value: string \| number }`
    click | `(context: { e: MouseEvent })` | 点击标签时触发 + ### CheckTagGroup Props -名称 | 类型 | 默认值 | 说明 | 必传 +名称 | 类型 | 默认值 | 描述 | 必传 -- | -- | -- | -- | -- checkedProps | Object | - | 透传标签选中态属性。TS 类型:`TdTagProps` | N multiple | Boolean | false | 是否支持选中多个标签 | N diff --git a/src/tag/tag.tsx b/src/tag/tag.tsx index e1ff37d86..9d2a3a5c8 100644 --- a/src/tag/tag.tsx +++ b/src/tag/tag.tsx @@ -2,6 +2,7 @@ import Vue from 'vue'; import { CloseIcon as TdCloseIcon } from 'tdesign-icons-vue'; import { ScopedSlotReturnValue } from 'vue/types/vnode'; import tinycolor from 'tinycolor2'; +import isString from 'lodash/isString'; import props from './props'; import mixins from '../utils/mixins'; import getConfigReceiverMixins, { TagConfig, getGlobalIconMixins } from '../config-provider/config-receiver'; @@ -98,6 +99,22 @@ export default mixins(getConfigReceiverMixins('tag'), getGlobalI } return style; }, + renderTitle(tagContent: string) { + if (!this.maxWidth) { + return undefined; + } + + const vProps = (this.$vnode.componentOptions.propsData as TdTagProps) || {}; + if (Reflect.has(vProps, 'title')) { + return vProps.title || undefined; + } + + if (tagContent) { + return tagContent; + } + + return undefined; + }, }, render() { @@ -106,19 +123,13 @@ export default mixins(getConfigReceiverMixins('tag'), getGlobalI // 标签内容 const tagContent: TNodeReturnValue = renderContent(this, 'default', 'content'); - const title = typeof tagContent === 'string' ? tagContent : ''; - const titleAttribute = title && this.maxWidth ? { title } : undefined; + const title = this.renderTitle(isString(tagContent) ? tagContent : ''); // 图标 const icon = renderTNodeJSX(this, 'icon'); return (
    {icon} - + {tagContent} {!this.disabled ? closeIcon : undefined} diff --git a/src/tag/type.ts b/src/tag/type.ts index fd2fe32bb..f0b0481e7 100644 --- a/src/tag/type.ts +++ b/src/tag/type.ts @@ -13,7 +13,8 @@ export interface TdTagProps { */ closable?: boolean; /** - * 自定义颜色 + * 自定义标签颜色 + * @default '' */ color?: string; /** @@ -52,6 +53,11 @@ export interface TdTagProps { * @default default */ theme?: 'default' | 'primary' | 'warning' | 'danger' | 'success'; + /** + * 标签标题,在标签hover时展示,默认为标签内容 + * @default '' + */ + title?: string; /** * 标签风格变体 * @default dark diff --git a/src/time-picker/panel/single-panel.tsx b/src/time-picker/panel/single-panel.tsx index 208b140f0..70d881167 100644 --- a/src/time-picker/panel/single-panel.tsx +++ b/src/time-picker/panel/single-panel.tsx @@ -106,8 +106,15 @@ export default defineComponent({ dayjsValue.value.minute(), dayjsValue.value.second(), ]; - params[colIdx] = Number(el); - return !props.disableTime?.(...params, { partial: position.value || 'start' })?.[col]?.includes(Number(el)); + + let n = Number(el); + + if (col === EPickerCols.hour && TWELVE_HOUR_FORMAT.test(props.format) && dayjsValue.value.hour() >= 12) { + n += 12; + } + + params[colIdx] = n; + return !props.disableTime?.(...params, { partial: position.value || 'start' })?.[col]?.includes(n); } return true; }; @@ -135,10 +142,14 @@ export default defineComponent({ dayjsValue.value.second(), dayjsValue.value.millisecond(), ]; - params[colIdx] = Number(t); - return !props - .disableTime?.(...params, { partial: position.value || 'start' }) - ?.[col]?.includes(Number(t)); + let n = Number(t); + + if (col === EPickerCols.hour && TWELVE_HOUR_FORMAT.test(props.format) && dayjsValue.value.hour() >= 12) { + n += 12; + } + + params[colIdx] = n; + return !props.disableTime?.(...params, { partial: position.value || 'start' })?.[col]?.includes(n); }) : colList; } @@ -247,11 +258,7 @@ export default defineComponent({ const handleTimeItemClick = (col: EPickerCols, el: string | number, idx: number, e: MouseEvent) => { if (!timeItemCanUsed(col, el)) return; if (timeArr.includes(col)) { - if ( - col === EPickerCols.hour - && dayjsValue.value.format('a') === PM - && cols.value.includes(EPickerCols.meridiem) - ) { + if (col === EPickerCols.hour && dayjsValue.value.hour() >= 12 && cols.value.includes(EPickerCols.meridiem)) { // eslint-disable-next-line no-param-reassign el = Number(el) + 12; } @@ -277,7 +284,7 @@ export default defineComponent({ // 如果没有设置大于1的steps或设置了大于1的step 正常处理滚动 scrollToTime( col, - timeArr.includes(col) ? dayjsValue.value[col]?.() : dayjsValue.value.format('a'), + timeArr.includes(col) ? dayjsValue.value[col]?.() : dayjsValue.value.locale('en').format('a'), idx, behavior, ); @@ -294,7 +301,7 @@ export default defineComponent({ const isCurrent = (col: EPickerCols, colItem: string | number) => { let colVal: number; if (col === EPickerCols.meridiem) { - const currentMeridiem = dayjsValue.value.format('a'); + const currentMeridiem = dayjsValue.value.locale('en').format('a'); return currentMeridiem === colItem; } colVal = dayjsValue.value[col]?.(); diff --git a/src/time-picker/props.ts b/src/time-picker/props.ts index c3d459c8b..69b7f3769 100644 --- a/src/time-picker/props.ts +++ b/src/time-picker/props.ts @@ -23,6 +23,11 @@ export default { type: Boolean, default: undefined, }, + /** 只读状态,值为真会隐藏输入框,且无法打开下拉框 */ + readonly: { + type: Boolean, + default: undefined, + }, /** 用于格式化时间,[详细文档](https://day.js.org/docs/en/display/format) */ format: { type: String, diff --git a/src/time-picker/time-picker.en-US.md b/src/time-picker/time-picker.en-US.md index 3c9145352..0591185e9 100644 --- a/src/time-picker/time-picker.en-US.md +++ b/src/time-picker/time-picker.en-US.md @@ -17,6 +17,7 @@ inputProps | Object | - | Typescript:`InputProps`,[Input API Documents](./in placeholder | String | undefined | \- | N popupProps | Object | - | Typescript:`PopupProps`,[Popup API Documents](./popup?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/time-picker/type.ts) | N presets | Object | - | Typescript:`PresetTime` `interface PresetTime { [presetName: string]: TimePickerValue \| (() => TimePickerValue) }`。[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/time-picker/type.ts) | N +readonly | Boolean | false | \- | N size | String | medium | options: small/medium/large | N status | String | default | options: default/success/warning/error | N steps | Array | [1, 1, 1] | Typescript:`Array` | N diff --git a/src/time-picker/time-picker.md b/src/time-picker/time-picker.md index 592d08ed2..600daccc1 100644 --- a/src/time-picker/time-picker.md +++ b/src/time-picker/time-picker.md @@ -17,6 +17,7 @@ inputProps | Object | - | 透传给输入框(Input)组件的参数。TS 类 placeholder | String | undefined | 占位符 | N popupProps | Object | - | 透传给 popup 组件的参数。TS 类型:`PopupProps`,[Popup API Documents](./popup?tab=api)。[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/time-picker/type.ts) | N presets | Object | - | 预设快捷时间选择,示例:`{ '前一小时': '11:00:00' }`。TS 类型:`PresetTime` `interface PresetTime { [presetName: string]: TimePickerValue \| (() => TimePickerValue) }`。[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/time-picker/type.ts) | N +readonly | Boolean | false | 只读状态,值为真会隐藏输入框,且无法打开下拉框 | N size | String | medium | 尺寸。可选项:small/medium/large | N status | String | default | 输入框状态。可选项:default/success/warning/error | N steps | Array | [1, 1, 1] | 时间间隔步数,数组排列 [小时, 分钟, 秒],示例:[2, 1, 1] 或者 ['2', '1', '1']。TS 类型:`Array` | N diff --git a/src/time-picker/time-picker.tsx b/src/time-picker/time-picker.tsx index 64274c43c..de7bc42aa 100755 --- a/src/time-picker/time-picker.tsx +++ b/src/time-picker/time-picker.tsx @@ -135,6 +135,7 @@ export default defineComponent({ onInputChange: this.handleInputChange, borderless: this.borderless, disabled: this.isDisabled, + readonly: this.readonly, clearable: this.clearable, allowInput: this.allowInput, class: this.inputClasses, diff --git a/src/time-picker/time-range-picker-props.ts b/src/time-picker/time-range-picker-props.ts index 7f17043f6..939beaaf6 100644 --- a/src/time-picker/time-range-picker-props.ts +++ b/src/time-picker/time-range-picker-props.ts @@ -23,6 +23,11 @@ export default { type: [Boolean, Array] as PropType, default: undefined, }, + /** 只读状态,值为真会隐藏输入框,且无法打开下拉框 */ + readonly: { + type: Boolean, + default: undefined, + }, /** 用于格式化时间,[详细文档](https://day.js.org/docs/en/display/format) */ format: { type: String, diff --git a/src/time-picker/time-range-picker.tsx b/src/time-picker/time-range-picker.tsx index 4bca55b69..9f1619153 100644 --- a/src/time-picker/time-range-picker.tsx +++ b/src/time-picker/time-range-picker.tsx @@ -42,6 +42,7 @@ export default defineComponent({ const currentPanelIdx = ref(undefined); const currentValue = ref>(TIME_PICKER_EMPTY); const isShowPanel = ref(false); + let openedPanels: number[] = []; const inputClasses = computed(() => [ `${componentName.value}__group`, @@ -72,6 +73,7 @@ export default defineComponent({ const handleClick = ({ position }: { position: 'first' | 'second' }) => { currentPanelIdx.value = position === 'first' ? 0 : 1; + openedPanels.push(currentPanelIdx.value); }; const handleTimeChange = (newValue: string) => { @@ -82,6 +84,18 @@ export default defineComponent({ } }; + const setCurrentValue2InnerValue = () => { + const [startTime, endTime] = currentValue.value; + const startDayjs = dayjs(startTime, props.format); + const endDayjs = dayjs(endTime, props.format); + + if (startDayjs.isAfter(endDayjs, 'second')) { + setInnerValue([currentValue.value[1], currentValue.value[0]]); + } else { + setInnerValue([currentValue.value[0], currentValue.value[1]]); + } + }; + const handleInputBlur = (value: TimeRangeValue, { e }: { e: FocusEvent }) => { if (props.allowInput) { const isValidTime = validateInputValue(currentValue.value[currentPanelIdx.value], format.value); @@ -90,6 +104,7 @@ export default defineComponent({ currentPanelIdx.value === 0 ? (currentValue.value = [formattedVal, currentValue.value[1] ?? formattedVal]) : (currentValue.value = [currentValue.value[0] ?? formattedVal, formattedVal]); + setCurrentValue2InnerValue(); } } props.onBlur?.({ value, e }); @@ -105,12 +120,29 @@ export default defineComponent({ }; const handleClickConfirm = () => { - const isValidTime = !currentValue.value.find((v) => !validateInputValue(v, format.value)); - if (isValidTime) setInnerValue(currentValue.value); + if (openedPanels.length < 2) { + currentPanelIdx.value = currentPanelIdx.value === 1 ? 0 : 1; + openedPanels.push(currentPanelIdx.value); + return; + } + + const noValidIndex = currentValue.value.findIndex((v) => !validateInputValue(v, format.value)); + if (noValidIndex !== -1) { + currentPanelIdx.value = noValidIndex; + openedPanels.push(currentPanelIdx.value); + return; + } + + setCurrentValue2InnerValue(); isShowPanel.value = false; }; const handleFocus = (value: TimeRangeValue, { e, position }: { e: FocusEvent; position: RangeInputPosition }) => { + const panelIndex = position === 'first' ? 0 : 1; + if (isShowPanel.value && currentPanelIdx.value !== panelIndex) { + currentPanelIdx.value = panelIndex; + openedPanels.push(currentPanelIdx.value); + } props.onFocus?.({ value, e, position: position === 'first' ? 'start' : 'end' }); ctx.emit('focus', { value, e, position: position === 'first' ? 'start' : 'end' }); }; @@ -133,7 +165,10 @@ export default defineComponent({ () => isShowPanel.value, () => { currentValue.value = isShowPanel.value ? innerValue.value ?? TIME_PICKER_EMPTY : TIME_PICKER_EMPTY; - if (!isShowPanel.value) currentPanelIdx.value = undefined; + if (!isShowPanel.value) { + currentPanelIdx.value = undefined; + openedPanels = []; + } }, ); return { @@ -167,6 +202,7 @@ export default defineComponent({ props: { onInputChange: this.handleInputChange, disabled: this.isDisabled, + readonly: this.readonly, popupVisible: this.isShowPanel, inputValue: this.isShowPanel ? this.currentValue : this.innerValue ?? TIME_PICKER_EMPTY, popupProps: { @@ -191,7 +227,7 @@ export default defineComponent({ onClick: this.handleClick, onFocus: this.handleFocus, onBlur: this.handleInputBlur, - readonly: !this.allowInput, + readonly: this.readonly || !this.allowInput, activeIndex: this.currentPanelIdx, ...this.rangeInputProps, }, diff --git a/src/time-picker/type.ts b/src/time-picker/type.ts index 0ff7ae6c3..3417b0773 100644 --- a/src/time-picker/type.ts +++ b/src/time-picker/type.ts @@ -39,6 +39,10 @@ export interface TdTimePickerProps { * 是否禁用组件 */ disabled?: boolean; + /** + * 只读状态,值为真会隐藏输入框,且无法打开下拉框 + */ + readonly?: boolean; /** * 用于格式化时间,[详细文档](https://day.js.org/docs/en/display/format) * @default HH:mm:ss @@ -153,6 +157,10 @@ export interface TdTimeRangePickerProps { * 是否禁用组件,值为数组表示可分别控制开始日期和结束日期是否禁用 */ disabled?: boolean | Array; + /** + * 只读状态,值为真会隐藏输入框,且无法打开下拉框 + */ + readonly?: boolean; /** * 用于格式化时间,[详细文档](https://day.js.org/docs/en/display/format) * @default HH:mm:ss diff --git a/src/tree-select/__tests__/__snapshots__/index.test.jsx.snap b/src/tree-select/__tests__/__snapshots__/index.test.jsx.snap index 499c5bb68..86be716a8 100644 --- a/src/tree-select/__tests__/__snapshots__/index.test.jsx.snap +++ b/src/tree-select/__tests__/__snapshots__/index.test.jsx.snap @@ -465,49 +465,30 @@ exports[`TreeSelect > :props > :minCollapsedNum 1`] = ` exports[`TreeSelect > :props > :multiple 1`] = `
    -
    - - - - -
    -
    + /> + > + 请选择 + diff --git a/src/tree-select/tree-select.tsx b/src/tree-select/tree-select.tsx index d4d16d96b..21768e494 100644 --- a/src/tree-select/tree-select.tsx +++ b/src/tree-select/tree-select.tsx @@ -1,6 +1,7 @@ import { defineComponent, computed } from 'vue'; import isArray from 'lodash/isArray'; import isFunction from 'lodash/isFunction'; +import lodashGet from 'lodash/get'; import Tree, { TreeProps } from '../tree'; import props from './props'; import SelectInput, { SelectInputProps } from '../select-input'; @@ -105,7 +106,7 @@ export default defineComponent({ onLoad: this.treeNodeLoad, onChange: this.treeNodeChange, onActive: this.treeNodeActive, - expandOnClickNode: true, + expandOnClickNode: false, empty: () => this.renderDefaultTNode('empty', { defaultNode:
    {this.global.empty}
    , }), @@ -182,7 +183,7 @@ export default defineComponent({ ? { value: this.nodeInfo as TreeOptionData[], onClose: (index: number) => { - const value = this.nodeInfo.map((node: TreeOptionData) => node.value); + const value = this.nodeInfo.map((node: TreeOptionData) => lodashGet(node, this.tKeys.value)); this.tagChange(value, { trigger: 'tag-remove', index, diff --git a/src/tree-select/useTreeSelect.ts b/src/tree-select/useTreeSelect.ts index eee20d475..816d40efb 100644 --- a/src/tree-select/useTreeSelect.ts +++ b/src/tree-select/useTreeSelect.ts @@ -2,6 +2,8 @@ import { SetupContext, ref, computed, toRefs, watch, } from 'vue'; import isFunction from 'lodash/isFunction'; +import lodashGet from 'lodash/get'; +import lodashSet from 'lodash/set'; import { RemoveOptions, TdTreeSelectProps, TreeSelectValue } from './type'; import { TreeProps, TreeInstanceFunctions, TreeNodeValue } from '../tree'; import { usePrefixClass, useConfig } from '../hooks/useConfig'; @@ -10,7 +12,7 @@ import { TreeOptionData, TreeKeysType } from '../common'; import useVModel from '../hooks/useVModel'; import useDefaultValue from '../hooks/useDefaultValue'; import { SelectInputProps } from '../select-input'; -import { getNodeDataByValue } from './utils'; +import { getNodeDataByValue, normalizeArray } from './utils'; const DEFAULT_KEYS = { label: 'label', @@ -57,7 +59,7 @@ export default function useTreeSelect(props: TdTreeSelectProps, context: SetupCo const tKeys = computed(() => ({ ...DEFAULT_KEYS, ...props.treeProps?.keys, ...props.keys })); const inputPlaceholder = computed(() => { - let label = nodeInfo.value?.[tKeys.value.label]; + let label = nodeInfo.value ? lodashGet(nodeInfo.value, tKeys.value.label) : undefined; if (typeof label === 'number') label = String(label); return (innerVisible.value && label) || props.placeholder || global.value.placeholder; }); @@ -89,35 +91,33 @@ export default function useTreeSelect(props: TdTreeSelectProps, context: SetupCo // single tree-select use nodeInfo to compute singleActivated const singleActivated = computed(() => { if (props.multiple || !nodeInfo.value) return []; - if (nodeInfo.value instanceof Array) return [nodeInfo.value[0]?.[tKeys.value.value]]; - return [nodeInfo.value[tKeys.value.value]]; + if (nodeInfo.value instanceof Array) return [nodeInfo.value[0] ? lodashGet(nodeInfo.value[0], tKeys.value.value) : undefined]; + return [lodashGet(nodeInfo.value, tKeys.value.value)]; }); // multiple tree-select: use nodeInfo to compute multipleChecked, because nodeInfo also decided by treeSelectValue const multipleChecked = computed((): Array => { if (!props.multiple || !nodeInfo.value) return []; if (nodeInfo.value instanceof Array) { - return nodeInfo.value.map((item: TreeOptionData) => item[tKeys.value.value]); + return nodeInfo.value.map((item: TreeOptionData) => lodashGet(item, tKeys.value.value)); } - return [nodeInfo.value[tKeys.value.value]]; + return [lodashGet(nodeInfo.value, tKeys.value.value)]; }); // multiple tree select node info list function getMultipleNodeInfo(): TreeOptionData[] { const { value } = treeSelectValue; - const list = Array.isArray(value) ? value : [value]; + const list = normalizeArray(value); if (treeRef.value) { return list.map((item) => { const finalValue = typeof item === 'object' ? item.value : item; - return ( - treeRef.value.getItem(finalValue)?.data || { - [tKeys.value.label]: finalValue, - [tKeys.value.value]: finalValue, - } - ); + const obj = {}; + lodashSet(obj, tKeys.value.label, finalValue); + lodashSet(obj, tKeys.value.value, finalValue); + return treeRef.value.getItem(finalValue)?.data || obj; }); } - const onlyValues = list.map((item) => (typeof item === 'object' ? item[tKeys.value.value] : item)); + const onlyValues = list.map((item) => (typeof item === 'object' ? lodashGet(item, tKeys.value.value) : item)); return getNodeDataByValue(onlyValues, props.data, tKeys.value); } @@ -130,12 +130,10 @@ export default function useTreeSelect(props: TdTreeSelectProps, context: SetupCo const oneValue = Array.isArray(value) ? value[0] : value; const finalValue = typeof oneValue === 'object' ? oneValue.value : oneValue; if (treeRef.value) { - return ( - treeRef.value.getItem(finalValue)?.data || { - [tKeys.value.label]: finalValue, - [tKeys.value.value]: finalValue, - } - ); + const obj = {}; + lodashSet(obj, tKeys.value.label, finalValue); + lodashSet(obj, tKeys.value.value, finalValue); + return treeRef.value.getItem(finalValue)?.data || obj; } return getNodeDataByValue([finalValue], props.data, tKeys.value)[0]; } @@ -174,7 +172,7 @@ export default function useTreeSelect(props: TdTreeSelectProps, context: SetupCo // only for single tree select const treeNodeActive: TreeProps['onActive'] = (value, ctx) => { if (props.multiple) return; - if (treeSelectValue.value === ctx.node.data[tKeys.value.value]) { + if (treeSelectValue.value === lodashGet(ctx.node.data, tKeys.value.value)) { return; } const onlyLeafNode = Boolean( @@ -183,6 +181,7 @@ export default function useTreeSelect(props: TdTreeSelectProps, context: SetupCo && Array.isArray(ctx.node?.data?.children) && ctx.node?.data?.children?.length, ); + let current: TreeSelectValue = value; const nodeValue = Array.isArray(value) ? value[0] : value; current = isObjectValue.value ? treeRef.value.getItem(nodeValue) : nodeValue; @@ -196,9 +195,9 @@ export default function useTreeSelect(props: TdTreeSelectProps, context: SetupCo innerInputValue.value && setInnerInputValue('', { trigger: 'change', e: ctx.e }); }; - // label could be used as TNode, then use text to filter + // label could be used as TdNode, then use text to filter const defaultFilterFunction: TreeProps['filter'] = (node) => { - const label = node.data[tKeys.value.label]; + const label = lodashGet(node.data, tKeys.value.label); const searchLabel = isFunction(label) ? node.data.text : label || node.data.text; return searchLabel?.indexOf(innerInputValue.value) >= 0; }; @@ -238,7 +237,7 @@ export default function useTreeSelect(props: TdTreeSelectProps, context: SetupCo if (!fitTrigger) return; // handle remove event const current = treeSelectValue.value[index]; - const currentDeleteValue = typeof current === 'object' ? current[tKeys.value.value] : current; + const currentDeleteValue = typeof current === 'object' ? lodashGet(current, tKeys.value.value) : current; const currentNode = treeRef.value?.getItem(currentDeleteValue); const data = currentNode ? currentNode.data : getNodeDataByValue([currentDeleteValue], props.data, tKeys.value)[0]; if (fitTrigger) { diff --git a/src/tree-select/utils.ts b/src/tree-select/utils.ts index 566127a6f..fe59984b4 100644 --- a/src/tree-select/utils.ts +++ b/src/tree-select/utils.ts @@ -1,4 +1,8 @@ +import isNil from 'lodash/isNil'; +import lodashGet from 'lodash/get'; +import lodashSet from 'lodash/set'; import { TreeOptionData, TreeKeysType } from '../common'; +import type { TreeSelectValue } from './type'; export function getNodeDataByValue( values: Array, @@ -13,7 +17,7 @@ export function getNodeDataByValue( ) => { for (let i = 0, len = data.length; i < len; i++) { const item = data[i]; - const index = values.findIndex((val) => item[keys.value] === val); + const index = values.findIndex((val) => lodashGet(item, keys.value) === val); if (index !== -1) { // results.push(item); results.set(values[index], item); @@ -34,7 +38,10 @@ export function getNodeDataByValue( if (values.length && results.size < values.length) { values.forEach((value) => { if (!results.get(value)) { - results.set(value, { [keys.label]: value, [keys.value]: value }); + const obj = {}; + lodashSet(obj, keys.label, value); + lodashSet(obj, keys.value, value); + results.set(value, obj); } }); } @@ -42,4 +49,12 @@ export function getNodeDataByValue( return values.map((value) => results.get(value)); } +export function normalizeArray(value: TreeSelectValue) { + if (isNil(value)) { + return []; + } + + return Array.isArray(value) ? value : [value]; +} + export default {}; diff --git a/src/tree/hooks/useRenderLabel.tsx b/src/tree/hooks/useRenderLabel.tsx index 3efff0365..c765411d0 100644 --- a/src/tree/hooks/useRenderLabel.tsx +++ b/src/tree/hooks/useRenderLabel.tsx @@ -72,7 +72,7 @@ export default function useRenderLabel(state: TypeTreeItemState) { name={String(node.value)} onChange={handleChange} stopLabelTrigger={shouldStopLabelTrigger.value} - ignore="expand,active" + ignore={treeProps.expandOnClickNode ? 'active' : 'expand,active'} props={itemCheckProps} > {labelNode} diff --git a/src/upload/type.ts b/src/upload/type.ts index be27ad599..18bcea4b0 100644 --- a/src/upload/type.ts +++ b/src/upload/type.ts @@ -383,6 +383,7 @@ export type SizeUnit = SizeUnitArray[number]; export interface TriggerContext { dragActive?: boolean; + disabled?: boolean; files: UploadFile[]; } diff --git a/src/upload/upload.en-US.md b/src/upload/upload.en-US.md index 9b87cee59..927b05c0d 100644 --- a/src/upload/upload.en-US.md +++ b/src/upload/upload.en-US.md @@ -43,7 +43,7 @@ sizeLimit | Number / Object | - | files size limit。Typescript:`number \| Siz status | String | - | tips status。options: default/success/warning/error | N theme | String | file | different upload UI styles。options: custom/file/file-input/file-flow/image/image-flow | N tips | String / Slot / Function | - | tips text below upload component, define it's color with `status`。Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N -trigger | Slot / Function | - | trigger elements UI。Typescript:`TNode` `interface TriggerContext { dragActive?: boolean; files: UploadFile[] }`。[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts)。[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/upload/type.ts) | N +trigger | Slot / Function | - | trigger elements UI。Typescript:`TNode` `interface TriggerContext { dragActive?: boolean; disabled?: boolean; files: UploadFile[] }`。[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts)。[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/upload/type.ts) | N triggerButtonProps | Object | - | trigger button props, it can be used to change color/size/href/... of the trigger button。Typescript:`ButtonProps`,[Button API Documents](./button?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/upload/type.ts) | N uploadAllFilesInOneRequest | Boolean | false | uploading all files in one request | N uploadButton | Object / Slot / Function | - | upload button props, which showed on `autoUpload=false` and multiple files/images upload。Typescript:`null \| ButtonProps \| TNode<{ disabled: boolean; uploading: boolean; uploadFiles: () => void; uploadText: string }>`。[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N diff --git a/src/upload/upload.md b/src/upload/upload.md index 1a2e492e6..0c902cabd 100644 --- a/src/upload/upload.md +++ b/src/upload/upload.md @@ -43,7 +43,7 @@ sizeLimit | Number / Object | - | 图片文件大小限制,默认单位 KB。 status | String | - | 文件上传提示文本状态。可选项:default/success/warning/error | N theme | String | file | 组件风格。custom 表示完全自定义风格;file 表示默认文件上传风格;file-input 表示输入框形式的文件上传;file-flow 表示文件批量上传;image 表示默认图片上传风格;image-flow 表示图片批量上传。可选项:custom/file/file-input/file-flow/image/image-flow | N tips | String / Slot / Function | - | 组件下方文本提示,可以使用 `status` 定义文本。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N -trigger | Slot / Function | - | 触发上传的元素,`files` 指本次显示的全部文件。TS 类型:`TNode` `interface TriggerContext { dragActive?: boolean; files: UploadFile[] }`。[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts)。[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/upload/type.ts) | N +trigger | Slot / Function | - | 触发上传的元素,`files` 指本次显示的全部文件。TS 类型:`TNode` `interface TriggerContext { dragActive?: boolean; disabled?: boolean; files: UploadFile[] }`。[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts)。[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/upload/type.ts) | N triggerButtonProps | Object | - | 透传选择按钮全部属性。TS 类型:`ButtonProps`,[Button API Documents](./button?tab=api)。[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/upload/type.ts) | N uploadAllFilesInOneRequest | Boolean | false | 是否在同一个请求中上传全部文件,默认一个请求上传一个文件。多文件上传时有效 | N uploadButton | Object / Slot / Function | - | 批量文件/图片上传,`autoUpload=false` 场景下,透传“点击上传”按钮属性。TS 类型:`null \| ButtonProps \| TNode<{ disabled: boolean; uploading: boolean; uploadFiles: () => void; uploadText: string }>`。[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N diff --git a/src/upload/upload.tsx b/src/upload/upload.tsx index 34438cb10..7df545c5c 100644 --- a/src/upload/upload.tsx +++ b/src/upload/upload.tsx @@ -109,11 +109,17 @@ export default defineComponent({ toUploadFiles.value = files; uploadData.uploadFiles(); }; + const handlePasteFileChange = (e: ClipboardEvent) => { + if (props.uploadPastedFiles) { + onPasteFileChange(e); + } + }; return { ...uploadData, onUploadPaste, onPasteFileChange, + handlePasteFileChange, commonDisplayFileProps, dragProps, uploadClasses, @@ -148,7 +154,11 @@ export default defineComponent({ }; return ( renderContent(this, 'default', 'trigger', { - params: { dragActive: false, files: this.uploadValue }, + params: { + dragActive: false, + files: this.uploadValue, + disabled: this.tDisabled, + }, }) || getDefaultTrigger() ); }, @@ -264,7 +274,7 @@ export default defineComponent({ render() { return ( -
    +
    csr test ./src/color-picker/_example/panel.vue 1`] >
    csr test ./src/color-picker/_example/panel.vue 1`]
    csr test ./src/color-picker/_example/status-readonl >
    csr test ./src/color-picker/_example/swatch-color.v >
    csr test ./src/color-picker/_example/swatch-color.v >
    csr test ./src/config-provider/_example/table.vue 1 class="t-table__body" > csr test ./src/config-provider/_example/table.vue 1 csr test ./src/config-provider/_example/table.vue 1 csr test ./src/form/_example/layout.vue 1`] = `
    csr test ./src/form/_example/layout.vue 1`] = `
    csr test ./src/menu/_example/head-menu-tile.vue 1`]
    `; +exports[`csr snapshot test > csr test ./src/menu/_example/multi-head.vue 1`] = ` +
    +
    +
    + +
      +
    • +
      + + 菜单1 + +
      +
        +
      • +
        + 子菜单1-1 +
        +
          +
        • + + 子菜单1-1-1 + +
        • +
        • + + 子菜单1-1-2 + +
        • +
        • + + 子菜单1-1-3 + +
        • +
        +
      • +
      • +
        + 子菜单1-2 +
        +
          +
        • + + 子菜单1-2-1 + +
        • +
        • + + 子菜单1-2-2 + +
        • +
        • + + 子菜单1-2-3 + +
        • +
        +
      • +
      • +
        + 子菜单1-3 +
        +
          +
        • + + 子菜单1-3-1 + +
        • +
        • + + 子菜单1-3-2 + +
        • +
        • + + 子菜单1-3-3 + +
        • +
        +
      • +
      +
    • +
    • +
      + + 菜单2 + +
      +
        +
      • +
        + 子菜单2-1 +
        +
          +
        • + + 子菜单2-1-1 + +
        • +
        • + + 子菜单2-1-2 + +
        • +
        • + + 子菜单2-1-3 + +
        • +
        +
      • +
      • +
        + 子菜单2-2 +
        +
          +
        • + + 子菜单2-2-1 + +
        • +
        • + + 子菜单2-2-2 + +
        • +
        • + + 子菜单2-2-3 + +
        • +
        +
      • +
      • +
        + 子菜单2-3 +
        +
          +
        • + + 子菜单2-3-1 + +
        • +
        • + + 子菜单2-3-2 + +
        • +
        • + + 子菜单2-3-3 + +
        • +
        +
      • +
      +
    • +
    +
    + + + + +
    +
    +
    +
    +
    + +
      +
    • +
      + + 菜单1 + +
      +
        +
      • +
        + 子菜单1-1 +
        +
          +
        • + + 子菜单1-1-1 + +
        • +
        • + + 子菜单1-1-2 + +
        • +
        • + + 子菜单1-1-3 + +
        • +
        +
      • +
      • +
        + 子菜单1-2 +
        +
          +
        • + + 子菜单1-2-1 + +
        • +
        • + + 子菜单1-2-2 + +
        • +
        • + + 子菜单1-2-3 + +
        • +
        +
      • +
      • +
        + 子菜单1-3 +
        +
          +
        • + + 子菜单1-3-1 + +
        • +
        • + + 子菜单1-3-2 + +
        • +
        • + + 子菜单1-3-3 + +
        • +
        +
      • +
      +
    • +
    • +
      + + 菜单2 + +
      +
        +
      • +
        + 子菜单2-1 +
        +
          +
        • + + 子菜单2-1-1 + +
        • +
        • + + 子菜单2-1-2 + +
        • +
        • + + 子菜单2-1-3 + +
        • +
        +
      • +
      • +
        + 子菜单2-2 +
        +
          +
        • + + 子菜单2-2-1 + +
        • +
        • + + 子菜单2-2-2 + +
        • +
        • + + 子菜单2-2-3 + +
        • +
        +
      • +
      • +
        + 子菜单2-3 +
        +
          +
        • + + 子菜单2-3-1 + +
        • +
        • + + 子菜单2-3-2 + +
        • +
        • + + 子菜单2-3-3 + +
        • +
        +
      • +
      +
    • +
    +
    +
    + + + + +
    +
    +
    +
    +
    +`; + exports[`csr snapshot test > csr test ./src/menu/_example/multi-side.vue 1`] = `
    csr test ./src/rate/_example/base.vue 1`] = `
    `; +exports[`csr snapshot test > csr test ./src/rate/_example/clear.vue 1`] = ` +
    +
    +

    + clearable: true +

    +
    +
    +
    +
      +
    • +
      +
      + + + +
      +
      + + + +
      +
      +
    • +
    • +
      +
      + + + +
      +
      + + + +
      +
      +
    • +
    • +
      +
      + + + +
      +
      + + + +
      +
      +
    • +
    • +
      +
      + + + +
      +
      + + + +
      +
      +
    • +
    • +
      +
      + + + +
      +
      + + + +
      +
      +
    • +
    +
    +
    +
    +

    + clearable: false +

    +
    +
    +
    +
      +
    • +
      +
      + + + +
      +
      + + + +
      +
      +
    • +
    • +
      +
      + + + +
      +
      + + + +
      +
      +
    • +
    • +
      +
      + + + +
      +
      + + + +
      +
      +
    • +
    • +
      +
      + + + +
      +
      + + + +
      +
      +
    • +
    • +
      +
      + + + +
      +
      + + + +
      +
      +
    • +
    +
    +
    +
    +`; + exports[`csr snapshot test > csr test ./src/rate/_example/custom.vue 1`] = `
    csr test ./src/tag/_example/long-text.vue 1`] = `
    默认超八个字超长文本标签超长省略文本标签 diff --git a/test/snap/__snapshots__/ssr.test.js.snap b/test/snap/__snapshots__/ssr.test.js.snap index 5e53bf549..b24d049df 100644 --- a/test/snap/__snapshots__/ssr.test.js.snap +++ b/test/snap/__snapshots__/ssr.test.js.snap @@ -304,7 +304,7 @@ exports[`ssr snapshot test > renders ./src/config-provider/_example/pagination.v exports[`ssr snapshot test > renders ./src/config-provider/_example/popconfirm.vue correctly 1`] = `"
    "`; -exports[`ssr snapshot test > renders ./src/config-provider/_example/table.vue correctly 1`] = `"
    Type
    Platform
    Property


    Type
    Platform
    Property
    ArrayVue(PC)A
    StringReact(PC)B
    ObjectMiniprogramC


    "`; +exports[`ssr snapshot test > renders ./src/config-provider/_example/table.vue correctly 1`] = `"
    Type
    Platform
    Property


    Type
    Platform
    Property
    ArrayVue(PC)A
    StringReact(PC)B
    ObjectMiniprogramC


    "`; exports[`ssr snapshot test > renders ./src/date-picker/_example/base.vue correctly 1`] = `"
    "`; @@ -678,6 +678,8 @@ exports[`ssr snapshot test > renders ./src/menu/_example/head-menu-mode-tile.vue exports[`ssr snapshot test > renders ./src/menu/_example/head-menu-tile.vue correctly 1`] = `"
    \\"logo\\"
    • 菜单1
      • 子菜单1-1
      • 子菜单1-2
      • 子菜单1-3
    • 菜单2
      • 子菜单2-1
      • 子菜单2-2
      • 子菜单2-3
    • 菜单3
      • 子菜单3-1
      • 子菜单3-2
      • 子菜单3-3
    • 菜单4
    "`; +exports[`ssr snapshot test > renders ./src/menu/_example/multi-head.vue correctly 1`] = `"
    \\"logo\\"
    • 菜单1
      • 子菜单1-1
        • 子菜单1-1-1
        • 子菜单1-1-2
        • 子菜单1-1-3
      • 子菜单1-2
        • 子菜单1-2-1
        • 子菜单1-2-2
        • 子菜单1-2-3
      • 子菜单1-3
        • 子菜单1-3-1
        • 子菜单1-3-2
        • 子菜单1-3-3
    • 菜单2
      • 子菜单2-1
        • 子菜单2-1-1
        • 子菜单2-1-2
        • 子菜单2-1-3
      • 子菜单2-2
        • 子菜单2-2-1
        • 子菜单2-2-2
        • 子菜单2-2-3
      • 子菜单2-3
        • 子菜单2-3-1
        • 子菜单2-3-2
        • 子菜单2-3-3
    \\"logo\\"
    • 菜单1
      • 子菜单1-1
        • 子菜单1-1-1
        • 子菜单1-1-2
        • 子菜单1-1-3
      • 子菜单1-2
        • 子菜单1-2-1
        • 子菜单1-2-2
        • 子菜单1-2-3
      • 子菜单1-3
        • 子菜单1-3-1
        • 子菜单1-3-2
        • 子菜单1-3-3
    • 菜单2
      • 子菜单2-1
        • 子菜单2-1-1
        • 子菜单2-1-2
        • 子菜单2-1-3
      • 子菜单2-2
        • 子菜单2-2-1
        • 子菜单2-2-2
        • 子菜单2-2-3
      • 子菜单2-3
        • 子菜单2-3-1
        • 子菜单2-3-2
        • 子菜单2-3-3
    "`; + exports[`ssr snapshot test > renders ./src/menu/_example/multi-side.vue correctly 1`] = `"
    • 消息区
    • 个人中心
    • 视频区
    • 资源编辑
    • 仪表盘
    • 资源列表
    • 根目录
    • 调度平台
    • 精准监控
    • 消息区
    • 个人中心
    • 视频区
    • 资源编辑
    "`; exports[`ssr snapshot test > renders ./src/menu/_example/multiple.vue correctly 1`] = `"
    \\"logo\\"
    • 菜单1
    • 菜单2
    \\"logo\\"
    • 菜单1
    • 菜单2
    "`; @@ -823,6 +825,8 @@ exports[`ssr snapshot test > renders ./src/range-input/_example/status.vue corre exports[`ssr snapshot test > renders ./src/rate/_example/base.vue correctly 1`] = `"
    "`; +exports[`ssr snapshot test > renders ./src/rate/_example/clear.vue correctly 1`] = `"

    clearable: true

    clearable: false

    "`; + exports[`ssr snapshot test > renders ./src/rate/_example/custom.vue correctly 1`] = `"
    "`; exports[`ssr snapshot test > renders ./src/rate/_example/icon.vue correctly 1`] = `"
    "`; @@ -1117,7 +1121,7 @@ exports[`ssr snapshot test > renders ./src/tag/_example/disabled.vue correctly 1 exports[`ssr snapshot test > renders ./src/tag/_example/icon.vue correctly 1`] = `"
    函数图标
    插槽图标
    "`; -exports[`ssr snapshot test > renders ./src/tag/_example/long-text.vue correctly 1`] = `"
    默认超八个字超长文本标签超长省略文本标签
    "`; +exports[`ssr snapshot test > renders ./src/tag/_example/long-text.vue correctly 1`] = `"
    默认超八个字超长文本标签超长省略文本标签
    "`; exports[`ssr snapshot test > renders ./src/tag/_example/plain.vue correctly 1`] = `"
    标签一
    标签二
    标签三
    标签四
    标签五
    "`;