diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 94dc6a93d3..78b806c2a9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -88,8 +88,55 @@ jobs: semver_string: ${{ github.ref_name }} semver_pattern: '^release/(.*)$' - - name: update nextBump (release) + - name: Check version-policy version (release) if: startsWith(github.ref_name, 'release/') + id: release_version_policy + env: + RELEASE_VERSION: ${{ steps.semver_release.outputs.full }} + run: | + node <<'NODE' + const fs = require('fs'); + const policies = JSON.parse(fs.readFileSync('common/config/rush/version-policies.json', 'utf8')); + const policyVersion = policies[0] && policies[0].version; + const releaseVersion = process.env.RELEASE_VERSION; + + const parseVersion = version => String(version).split('-')[0].split('.').map(Number); + const compareVersion = (a, b) => { + const av = parseVersion(a); + const bv = parseVersion(b); + for (let i = 0; i < Math.max(av.length, bv.length); i++) { + const diff = (av[i] || 0) - (bv[i] || 0); + if (diff !== 0) { + return diff; + } + } + return 0; + }; + + if (!releaseVersion || !policyVersion) { + console.error('Missing release version or version-policy version.', { releaseVersion, policyVersion }); + process.exit(1); + } + + const result = compareVersion(releaseVersion, policyVersion); + if (result < 0) { + console.error( + `Release version ${releaseVersion} is lower than version-policy version ${policyVersion}.` + ); + process.exit(1); + } + + const shouldSkip = result === 0 ? 'true' : 'false'; + fs.appendFileSync(process.env.GITHUB_OUTPUT, `skip_next_bump=${shouldSkip}\n`); + console.log( + result === 0 + ? `Release version ${releaseVersion} equals version-policy version ${policyVersion}, skip nextBump update.` + : `Release version ${releaseVersion} is greater than version-policy version ${policyVersion}, update nextBump.` + ); + NODE + + - name: update nextBump (release) + if: startsWith(github.ref_name, 'release/') && steps.release_version_policy.outputs.skip_next_bump != 'true' uses: xile611/set-next-bump-of-rush@main with: release_version: ${{ steps.semver_release.outputs.full }} @@ -98,8 +145,8 @@ jobs: - name: Generate changelog blocks from changefiles (release) if: startsWith(github.ref_name, 'release/') env: - CHANGELOG_API_URL: ${{ secrets.VTABLE_CHANGELOG_API_URL }} - CHANGELOG_API_TOKEN: ${{ secrets.VTABLE_CHANGELOG_API_TOKEN }} + CHANGELOG_API_URL: ${{ secrets.VCHART_CHANGELOG_API_URL }} + CHANGELOG_API_TOKEN: ${{ secrets.VCHART_CHANGELOG_API_TOKEN }} RELEASE_VERSION: ${{ steps.semver_release.outputs.main }} run: | node <<'NODE' @@ -152,46 +199,25 @@ jobs: date: new Date().toISOString().slice(0, 10), changefiles, langs: ['en', 'zh'], - reuseHarmonyFromEn: true, template: 'default', }; const baseUrl = process.env.CHANGELOG_API_URL; const token = process.env.CHANGELOG_API_TOKEN; - function writeFallback() { - const date = payload.date; - const enLines = [ - `# v${version}`, - '', - date, - '', - '**🆕 New Features**', - '', - `- TODO: Fill in change details for v${version}.`, - '', - ]; - const zhLines = [ - `# v${version}`, - '', - date, - '', - '**🆕 新增功能**', - '', - `- TODO:补充 v${version} 的更新内容。`, - '', - ]; - fs.mkdirSync('.changelog', { recursive: true }); - fs.writeFileSync('.changelog/en.md', enLines.join('\n') + '\n', 'utf8'); - fs.writeFileSync('.changelog/zh.md', zhLines.join('\n') + '\n', 'utf8'); - fs.writeFileSync('.changelog/harmony.md', enLines.join('\n') + '\n', 'utf8'); - console.log('Wrote fallback changelog blocks for version', version); + function failChangelog(message) { + console.error(message); + process.exit(1); + } + + function validateBlock(block, lang) { + if (!block || /TODO/i.test(block)) { + throw new Error(`Invalid ${lang} changelog block: empty or contains TODO`); + } } if (!baseUrl || !token) { - console.log('CHANGELOG_API_URL or CHANGELOG_API_TOKEN not configured, using fallback template.'); - writeFallback(); - process.exit(0); + failChangelog('CHANGELOG_API_URL or CHANGELOG_API_TOKEN not configured.'); } const url = new URL('/api/changelog/vtable/generate', baseUrl); @@ -221,29 +247,26 @@ jobs: const parsed = JSON.parse(data || '{}'); const en = parsed.blocks && parsed.blocks.en; const zh = parsed.blocks && parsed.blocks.zh; - const harmony = parsed.harmony || en; if (!en || !zh) { throw new Error('Missing en/zh blocks in response'); } + validateBlock(String(en), 'en'); + validateBlock(String(zh), 'zh'); fs.mkdirSync('.changelog', { recursive: true }); fs.writeFileSync('.changelog/en.md', String(en).trimEnd() + '\n', 'utf8'); fs.writeFileSync('.changelog/zh.md', String(zh).trimEnd() + '\n', 'utf8'); - fs.writeFileSync('.changelog/harmony.md', String(harmony).trimEnd() + '\n', 'utf8'); console.log('Changelog blocks generated via API. traceId:', parsed.traceId || '(none)'); } catch (e) { - console.error('Failed to parse API response, fallback to template:', e.message || e); - writeFallback(); + failChangelog(`Failed to parse API response: ${e.message || e}`); } } else { - console.error('Changelog API returned non-2xx status:', res.statusCode, data); - writeFallback(); + failChangelog(`Changelog API returned non-2xx status: ${res.statusCode} ${data}`); } }); }); req.on('error', (err) => { - console.error('Changelog API request failed, fallback to template:', err.message || err); - writeFallback(); + failChangelog(`Changelog API request failed: ${err.message || err}`); }); req.write(body); @@ -252,6 +275,8 @@ jobs: - name: Prepend changelog blocks into release docs (release) if: startsWith(github.ref_name, 'release/') + env: + RELEASE_VERSION: ${{ steps.semver_release.outputs.main }} run: | set -euo pipefail mkdir -p docs/assets/changelog/en docs/assets/changelog/zh @@ -260,30 +285,24 @@ jobs: block_file=".changelog/${lang}.md" target_file="docs/assets/changelog/${lang}/release.md" if [ -f "$block_file" ]; then - if [ -f "$target_file" ]; then - cp "$target_file" "${target_file}.bak" - cat "$block_file" "${target_file}.bak" > "$target_file" - else - cp "$block_file" "$target_file" - fi + node -e ' + const fs = require("fs"); + const [blockFile, targetFile, version] = process.argv.slice(1); + const block = fs.readFileSync(blockFile, "utf8").trimEnd() + "\n\n"; + const oldContent = fs.existsSync(targetFile) ? fs.readFileSync(targetFile, "utf8") : ""; + const heading = `# v${version}`; + let contentWithoutSameVersion = oldContent; + if (oldContent.startsWith(`${heading}\n`) || oldContent.startsWith(`${heading}\r\n`)) { + const nextVersionIndex = oldContent.slice(1).search(/\n# v\d/); + contentWithoutSameVersion = nextVersionIndex >= 0 ? oldContent.slice(nextVersionIndex + 2) : ""; + } + fs.writeFileSync(targetFile, block + contentWithoutSameVersion.replace(/^\n+/, ""), "utf8"); + ' "$block_file" "$target_file" "$RELEASE_VERSION" else echo "Missing changelog block for $lang, skip." fi done - harmony_block=".changelog/harmony.md" - harmony_target="packages/harmony_vtable/library/CHANGELOG.md" - if [ -f "$harmony_block" ]; then - if [ -f "$harmony_target" ]; then - cp "$harmony_target" "${harmony_target}.bak" - cat "$harmony_block" "${harmony_target}.bak" > "$harmony_target" - else - cp "$harmony_block" "$harmony_target" - fi - else - echo "Missing harmony changelog block, skip." - fi - - name: Generate rush version (release) if: startsWith(github.ref_name, 'release/') run: node common/scripts/install-run-rush.js version --bump @@ -292,44 +311,57 @@ jobs: if: startsWith(github.ref_name, 'release/') run: node common/scripts/apply-release-version.js 'none' ${{ steps.semver_release.outputs.main }} - - name: Build vutils-extension && vtable - env: - NODE_OPTIONS: '--max_old_space_size=4096' - run: node common/scripts/install-run-rush.js build --to @visactor/vtable - - - name: Build vtable-extension - if: startsWith(github.ref_name, 'release/') || startsWith(github.ref_name, 'pre-release/') - env: - NODE_OPTIONS: '--max_old_space_size=4096' - run: node common/scripts/install-run-rush.js build --only @visactor/vtable-extension - - - name: Build react-vtable + - name: Build packages env: NODE_OPTIONS: '--max_old_space_size=4096' - run: node common/scripts/install-run-rush.js build --only @visactor/react-vtable + run: | + set -euo pipefail + package_names=() + while IFS= read -r package_name; do + [ -n "$package_name" ] && package_names+=("$package_name") + done < <(node <<'NODE' + const fs = require('fs'); + const path = require('path'); - - name: Build openinula-vtable - env: - NODE_OPTIONS: '--max_old_space_size=4096' - run: node common/scripts/install-run-rush.js build --only @visactor/openinula-vtable + const packagesDir = path.join(process.cwd(), 'packages'); + const packageNames = fs + .readdirSync(packagesDir, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => path.join(packagesDir, entry.name, 'package.json')) + .filter((packageJsonPath) => fs.existsSync(packageJsonPath)) + .map((packageJsonPath) => JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')).name) + .filter(Boolean) + .sort(); + + console.log(packageNames.join('\n')); + NODE + ) - - name: Build taro-vtable - env: - NODE_OPTIONS: '--max_old_space_size=4096' - run: node common/scripts/install-run-rush.js build --only @visactor/taro-vtable + build_args=() + for package_name in "${package_names[@]}"; do + build_args+=(--to "$package_name") + done - - name: Build lark-vtable - env: - NODE_OPTIONS: '--max_old_space_size=4096' - run: node common/scripts/install-run-rush.js build --only @visactor/lark-vtable + echo "Build packages: ${package_names[*]}" + node common/scripts/install-run-rush.js build "${build_args[@]}" - - name: Build wx-vtable + - name: Check npm version (release) + if: startsWith(github.ref_name, 'release/') + id: npm_version_release env: - NODE_OPTIONS: '--max_old_space_size=4096' - run: node common/scripts/install-run-rush.js build --only @visactor/wx-vtable + PACKAGE_VERSION: ${{ steps.semver_release.outputs.main }} + run: | + set -euo pipefail + if npm view "@visactor/vtable@$PACKAGE_VERSION" version --registry=https://registry.npmjs.org >/dev/null 2>&1; then + echo "Version $PACKAGE_VERSION already exists on npm, skip publish." + echo "skip_publish=true" >> "$GITHUB_OUTPUT" + else + echo "Version $PACKAGE_VERSION does not exist on npm, continue publish." + echo "skip_publish=false" >> "$GITHUB_OUTPUT" + fi - name: Publish to npm (release) - if: startsWith(github.ref_name, 'release/') + if: startsWith(github.ref_name, 'release/') && steps.npm_version_release.outputs.skip_publish != 'true' run: node common/scripts/install-run-rush.js publish --publish --include-all --tag latest - name: Update shrinkwrap @@ -349,6 +381,8 @@ jobs: if git diff --quiet; then echo 'No changes to commit for release branch.' else + rm -rf .changelog + find docs/assets/changelog -name '*.bak' -delete git add . git commit -m "build: release version ${{ steps.package_version_release.outputs.current_version }} [skip ci]" -n git push --no-verify origin ${{ github.ref_name }} diff --git a/common/changes/@visactor/vtable-plugins/fix-issue-5140-add-row-column-plugin-release_2026-05-25-17-05.json b/common/changes/@visactor/vtable-plugins/fix-issue-5140-add-row-column-plugin-release_2026-05-25-17-05.json new file mode 100644 index 0000000000..0bd03c6887 --- /dev/null +++ b/common/changes/@visactor/vtable-plugins/fix-issue-5140-add-row-column-plugin-release_2026-05-25-17-05.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable-plugins", + "comment": "Fix an issue where `AddRowColumnPlugin.release()` threw when only row or column controls were enabled (GitHub #5140)", + "type": "patch" + } + ], + "packageName": "@visactor/vtable-plugins", + "email": "892739385@qq.com" +} \ No newline at end of file diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index d99c74bbb1..0eb3ed1da4 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -19,34 +19,34 @@ importers: specifier: ^2.11.0 version: 2.57.0(vue@3.5.31(typescript@4.9.5)) '@visactor/openinula-vtable': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../packages/openinula-vtable '@visactor/react-vtable': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../packages/react-vtable '@visactor/vchart': specifier: 2.0.13-alpha.10 version: 2.0.13-alpha.10 '@visactor/vtable': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../packages/vtable '@visactor/vtable-editors': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../packages/vtable-editors '@visactor/vtable-export': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../packages/vtable-export '@visactor/vtable-gantt': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../packages/vtable-gantt '@visactor/vtable-search': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../packages/vtable-search '@visactor/vtable-sheet': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../packages/vtable-sheet '@visactor/vue-vtable': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../packages/vue-vtable '@visactor/vutils': specifier: ~1.0.17 @@ -140,7 +140,7 @@ importers: ../../packages/openinula-vtable: dependencies: '@visactor/vtable': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../vtable '@visactor/vutils': specifier: ~1.0.17 @@ -270,7 +270,7 @@ importers: ../../packages/react-vtable: dependencies: '@visactor/vtable': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../vtable '@visactor/vutils': specifier: ~1.0.17 @@ -445,7 +445,7 @@ importers: specifier: ~1.0.17 version: 1.0.23 '@visactor/vtable-editors': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../vtable-editors '@visactor/vutils': specifier: ~1.0.17 @@ -626,7 +626,7 @@ importers: ../../packages/vtable-calendar: dependencies: '@visactor/vtable': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../vtable '@visactor/vutils': specifier: ~1.0.17 @@ -849,7 +849,7 @@ importers: specifier: 2.0.7 version: 2.0.7 '@visactor/vtable': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../vtable '@visactor/vutils': specifier: ~1.0.17 @@ -1009,10 +1009,10 @@ importers: specifier: ~1.0.17 version: 1.0.23 '@visactor/vtable': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../vtable '@visactor/vtable-editors': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../vtable-editors '@visactor/vutils': specifier: ~1.0.17 @@ -1245,13 +1245,13 @@ importers: specifier: 2.0.13-alpha.10 version: 2.0.13-alpha.10 '@visactor/vtable': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../vtable '@visactor/vtable-editors': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../vtable-editors '@visactor/vtable-gantt': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../vtable-gantt '@vitejs/plugin-react': specifier: 3.1.0 @@ -1350,7 +1350,7 @@ importers: ../../packages/vtable-search: dependencies: '@visactor/vtable': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../vtable '@visactor/vutils': specifier: ~1.0.17 @@ -1504,13 +1504,13 @@ importers: specifier: ~1.0.17 version: 1.0.23 '@visactor/vtable': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../vtable '@visactor/vtable-editors': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../vtable-editors '@visactor/vtable-plugins': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../vtable-plugins '@visactor/vutils': specifier: ~1.0.17 @@ -1649,7 +1649,7 @@ importers: ../../packages/vue-vtable: dependencies: '@visactor/vtable': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../vtable '@visactor/vutils': specifier: ~1.0.17 @@ -1830,19 +1830,19 @@ importers: ../../tools/bugserver-trigger: dependencies: '@visactor/vtable': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../../packages/vtable '@visactor/vtable-editors': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../../packages/vtable-editors '@visactor/vtable-gantt': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../../packages/vtable-gantt '@visactor/vtable-plugins': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../../packages/vtable-plugins '@visactor/vtable-sheet': - specifier: workspace:* + specifier: workspace:1.26.2 version: link:../../packages/vtable-sheet devDependencies: '@internal/bundler': diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json index 8fa71ff8c3..b786c9e196 100644 --- a/common/config/rush/version-policies.json +++ b/common/config/rush/version-policies.json @@ -1 +1 @@ -[{"definitionName":"lockStepVersion","policyName":"vtableMain","version":"1.26.1","mainProject":"@visactor/vtable","nextBump":"patch"}] +[{"definitionName":"lockStepVersion","policyName":"vtableMain","version":"1.26.3","mainProject":"@visactor/vtable","nextBump":"patch"}] diff --git a/common/scripts/apply-release-version.js b/common/scripts/apply-release-version.js new file mode 100644 index 0000000000..a3c81ae866 --- /dev/null +++ b/common/scripts/apply-release-version.js @@ -0,0 +1,20 @@ +const writePrereleaseVersion = require('./set-prerelease-version'); +const checkAndUpdateNextBump = require('./version-policies'); +const parseVersion = require('./parse-version'); + + +function run() { + const preReleaseNameArg = process.argv.slice(2)[0]; + const preReleaseName = preReleaseNameArg === 'none' ? '' : preReleaseNameArg; + const nextVersionOrNextBump = process.argv.slice(2)[1]; + const buildName = process.argv.slice(2)[2]; + const nextBump = checkAndUpdateNextBump(nextVersionOrNextBump); + const parsedNextVersion = nextVersionOrNextBump ? parseVersion(nextVersionOrNextBump) : null; + const nextVersion = parsedNextVersion ? `${parsedNextVersion.major}.${parsedNextVersion.minor}.${parsedNextVersion.patch}`: null; + + console.log('[apply prerelease version]: ', preReleaseName, nextBump, nextVersion); + + writePrereleaseVersion(nextBump, preReleaseName, nextVersion, buildName); +} + +run() diff --git a/docs/assets/changelog/en/release.md b/docs/assets/changelog/en/release.md index 06bb9ad7f0..8b6c222b6a 100644 --- a/docs/assets/changelog/en/release.md +++ b/docs/assets/changelog/en/release.md @@ -1,3 +1,18 @@ +# v1.26.2 + +2026-06-16 + +**🐛 Bug Fixes** + +- **@visactor/vtable**: preserve sticky group position after expand or collapse +- **@visactor/vtable**: preserve hidden column order after filter updates +- **@visactor/vtable**: resolve the regression introduced by the #5137 fix that broke nested header dragging and exposed internal state +- **@visactor/vtable**: fix an issue where keyboard multi-selection with `Shift` plus arrow keys stopped expanding after the second cell [#5146](https://github.com/VisActor/VTable/issues/5146) +- **@visactor/vtable**: avoid selection gaps with frozen rows +- **@visactor/vtable**: fix unexpected scroll position changes when collapsing or expanding hierarchy nodes at the bottom of a pivot table + +[more detail about v1.26.2](https://github.com/VisActor/VTable/releases/tag/v1.26.2) + # v1.26.1 2026-05-11 diff --git a/docs/assets/changelog/zh/release.md b/docs/assets/changelog/zh/release.md index b7b455cfca..c945f2a94c 100644 --- a/docs/assets/changelog/zh/release.md +++ b/docs/assets/changelog/zh/release.md @@ -1,3 +1,18 @@ +# v1.26.2 + +2026-06-16 + +**🐛 功能修复** + +- **@visactor/vtable**: 修复分组展开或折叠后 sticky group 位置未正确保持的问题 +- **@visactor/vtable**: 修复筛选更新后隐藏列顺序被打乱的问题 +- **@visactor/vtable**: 修复 #5137 修复后引入的回归问题,避免嵌套表头拖拽异常并暴露内部状态 +- **@visactor/vtable**: 修复使用 `Shift` 配合方向键进行多选时,选区在第二个单元格后无法继续扩展的问题 [#5146](https://github.com/VisActor/VTable/issues/5146) +- **@visactor/vtable**: 修复冻结列场景下出现选区空隙的问题 +- **@visactor/vtable**: 修复透视表在底部折叠或展开层级节点时滚动位置异常的问题 + +[更多详情请查看 v1.26.2](https://github.com/VisActor/VTable/releases/tag/v1.26.2) + # v1.26.1 2026-05-11 diff --git a/docs/package.json b/docs/package.json index 42bdb73577..a22fef08fe 100644 --- a/docs/package.json +++ b/docs/package.json @@ -12,15 +12,15 @@ "dependencies": { "@arco-design/web-react": "2.66.12", "@arco-design/web-vue": "^2.11.0", - "@visactor/vtable": "workspace:*", - "@visactor/vtable-gantt": "workspace:*", - "@visactor/react-vtable": "workspace:*", - "@visactor/vue-vtable": "workspace:*", - "@visactor/openinula-vtable": "workspace:*", - "@visactor/vtable-editors": "workspace:*", - "@visactor/vtable-export": "workspace:*", - "@visactor/vtable-search": "workspace:*", - "@visactor/vtable-sheet": "workspace:*", + "@visactor/vtable": "workspace:1.26.2", + "@visactor/vtable-gantt": "workspace:1.26.2", + "@visactor/react-vtable": "workspace:1.26.2", + "@visactor/vue-vtable": "workspace:1.26.2", + "@visactor/openinula-vtable": "workspace:1.26.2", + "@visactor/vtable-editors": "workspace:1.26.2", + "@visactor/vtable-export": "workspace:1.26.2", + "@visactor/vtable-search": "workspace:1.26.2", + "@visactor/vtable-sheet": "workspace:1.26.2", "buble": "^0.20.0", "@visactor/vchart": "2.0.13-alpha.10", "markdown-it": "^13.0.0", @@ -54,4 +54,4 @@ "globby": "11.1.0", "chokidar": "^3.5.0" } -} \ No newline at end of file +} diff --git a/packages/openinula-vtable/package.json b/packages/openinula-vtable/package.json index 116be3c1b5..98f5067872 100644 --- a/packages/openinula-vtable/package.json +++ b/packages/openinula-vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/openinula-vtable", - "version": "1.26.1", + "version": "1.26.2", "description": "The openinula version of VTable", "keywords": [ "openinula", @@ -44,7 +44,7 @@ "access": "public" }, "dependencies": { - "@visactor/vtable": "workspace:*", + "@visactor/vtable": "workspace:1.26.2", "@visactor/vutils": "~1.0.17" }, "devDependencies": { @@ -91,5 +91,10 @@ }, "peerDependencies": { "openinula": "~0.1.2-SNAPSHOT" + }, + "repository": { + "type": "git", + "url": "https://github.com/VisActor/VTable.git", + "directory": "packages/openinula-vtable" } } diff --git a/packages/react-vtable/package.json b/packages/react-vtable/package.json index b24f079d74..a6575a643f 100644 --- a/packages/react-vtable/package.json +++ b/packages/react-vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/react-vtable", - "version": "1.26.1", + "version": "1.26.2", "description": "The react version of VTable", "keywords": [ "react", @@ -57,7 +57,7 @@ "react-dom": "^18.2.0 || ^19.0.0" }, "dependencies": { - "@visactor/vtable": "workspace:*", + "@visactor/vtable": "workspace:1.26.2", "@visactor/vutils": "~1.0.17", "react-is": "^18.2.0", "react-reconciler": "0.29.0" @@ -110,5 +110,10 @@ "@types/react-is": "^17.0.3", "@arco-design/web-react": "2.66.12", "@types/react-reconciler": "0.28.9" + }, + "repository": { + "type": "git", + "url": "https://github.com/VisActor/VTable.git", + "directory": "packages/react-vtable" } } diff --git a/packages/vtable-calendar/package.json b/packages/vtable-calendar/package.json index 82105b162b..e056ddf5fc 100644 --- a/packages/vtable-calendar/package.json +++ b/packages/vtable-calendar/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-calendar", - "version": "1.26.1", + "version": "1.26.2", "description": "The calendar component of VTable", "author": { "name": "VisActor", @@ -37,7 +37,7 @@ "access": "public" }, "dependencies": { - "@visactor/vtable": "workspace:*", + "@visactor/vtable": "workspace:1.26.2", "@visactor/vutils": "~1.0.17", "date-fns": "3.6.0" }, @@ -87,5 +87,10 @@ "axios": "^1.4.0", "@types/react-is": "^17.0.3", "rollup-plugin-node-resolve": "5.2.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/VisActor/VTable.git", + "directory": "packages/vtable-calendar" } } diff --git a/packages/vtable-editors/package.json b/packages/vtable-editors/package.json index b1180029af..987ec093a7 100644 --- a/packages/vtable-editors/package.json +++ b/packages/vtable-editors/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-editors", - "version": "1.26.1", + "version": "1.26.2", "description": "", "sideEffects": false, "main": "cjs/index.js", diff --git a/packages/vtable-export/package.json b/packages/vtable-export/package.json index 4633b2fe76..4e58ad8c70 100644 --- a/packages/vtable-export/package.json +++ b/packages/vtable-export/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-export", - "version": "1.26.1", + "version": "1.26.2", "description": "The export util of VTable", "author": { "name": "VisActor", @@ -37,7 +37,7 @@ "access": "public" }, "dependencies": { - "@visactor/vtable": "workspace:*", + "@visactor/vtable": "workspace:1.26.2", "@visactor/vutils": "~1.0.17", "file-saver": "2.0.5", "@types/file-saver": "2.0.7", @@ -90,5 +90,10 @@ "axios": "^1.4.0", "@types/react-is": "^17.0.3", "rollup-plugin-node-resolve": "5.2.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/VisActor/VTable.git", + "directory": "packages/vtable-export" } } diff --git a/packages/vtable-gantt/package.json b/packages/vtable-gantt/package.json index a11b7937c3..37174dbc62 100644 --- a/packages/vtable-gantt/package.json +++ b/packages/vtable-gantt/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-gantt", - "version": "1.26.1", + "version": "1.26.2", "description": "canvas table width high performance", "keywords": [ "vtable-gantt", @@ -50,8 +50,8 @@ "fix-memory-limit": "cross-env LIMIT=10240 increase-memory-limit" }, "dependencies": { - "@visactor/vtable": "workspace:*", - "@visactor/vtable-editors": "workspace:*", + "@visactor/vtable": "workspace:1.26.2", + "@visactor/vtable-editors": "workspace:1.26.2", "@visactor/vutils": "~1.0.17", "@visactor/vscale": "~1.0.17", "@visactor/vdataset": "~1.0.17", diff --git a/packages/vtable-plugins/__tests__/add-row-column-plugin.test.ts b/packages/vtable-plugins/__tests__/add-row-column-plugin.test.ts new file mode 100644 index 0000000000..9d9b2731b9 --- /dev/null +++ b/packages/vtable-plugins/__tests__/add-row-column-plugin.test.ts @@ -0,0 +1,47 @@ +// @ts-nocheck +import { ListTable } from '@visactor/vtable'; +import { AddRowColumnPlugin } from '../src/add-row-column'; +import { createDiv } from '../../vtable/__tests__/dom'; + +global.__VERSION__ = 'none'; + +describe('AddRowColumnPlugin release - issue #5140', () => { + const columns = [ + { field: 'id', title: 'ID', width: 120 }, + { field: 'name', title: 'Name', width: 160 } + ]; + const records = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' } + ]; + + function createTable(pluginOptions) { + const container = createDiv(); + container.style.position = 'relative'; + container.style.width = '600px'; + container.style.height = '400px'; + + return new ListTable({ + container, + columns, + records, + plugins: [new AddRowColumnPlugin(pluginOptions)] + }); + } + + afterEach(() => { + document.body.innerHTML = ''; + }); + + test('release should not throw when row controls are disabled', () => { + const table = createTable({ addRowEnable: false }); + + expect(() => table.release()).not.toThrow(); + }); + + test('release should not throw when column controls are disabled', () => { + const table = createTable({ addColumnEnable: false }); + + expect(() => table.release()).not.toThrow(); + }); +}); diff --git a/packages/vtable-plugins/__tests__/filter/filter-plugin.test.ts b/packages/vtable-plugins/__tests__/filter/filter-plugin.test.ts new file mode 100644 index 0000000000..71cb30bf8f --- /dev/null +++ b/packages/vtable-plugins/__tests__/filter/filter-plugin.test.ts @@ -0,0 +1,163 @@ +// @ts-nocheck +let subscribeCallback: ((state: any, action?: any) => void) | undefined; + +jest.mock('@visactor/vtable', () => ({ + TABLE_EVENT_TYPE: { + BEFORE_INIT: 'before_init', + BEFORE_UPDATE_OPTION: 'before_update_option', + ICON_CLICK: 'icon_click', + SCROLL: 'scroll', + CHANGE_CELL_VALUE: 'change_cell_value', + UPDATE_RECORD: 'update_record', + ADD_RECORD: 'add_record', + DELETE_RECORD: 'delete_record', + ADD_COLUMN: 'add_column', + DELETE_COLUMN: 'delete_column' + }, + TYPES: { + IconPosition: { + right: 'right' + } + } +})); + +jest.mock('../../src/filter/filter-engine', () => ({ + FilterEngine: jest.fn().mockImplementation(() => ({})) +})); + +jest.mock('../../src/filter/filter-state-manager', () => ({ + FilterStateManager: jest.fn().mockImplementation(() => ({ + subscribe: (cb: (state: any, action?: any) => void) => { + subscribeCallback = cb; + }, + getFilterState: () => undefined, + getActiveFilterFields: () => [], + reapplyCurrentFilters: jest.fn(), + dispatch: jest.fn() + })) +})); + +jest.mock('../../src/filter/filter-toolbar', () => ({ + FilterToolbar: jest.fn().mockImplementation(() => ({ + render: jest.fn(), + updateStyles: jest.fn(), + isVisible: false, + hide: jest.fn(), + show: jest.fn(), + adjustMenuPosition: jest.fn(), + valueFilter: { + syncSingleStateFromTableData: jest.fn() + } + })) +})); + +const { FilterPlugin } = require('../../src/filter/filter'); + +describe('FilterPlugin', () => { + beforeEach(() => { + subscribeCallback = undefined; + }); + + test('falls back to option columns before list table layout is ready', () => { + const optionColumns = [ + { field: 'a', title: 'A' }, + { field: 'b', title: 'B' } + ]; + const table = { + isListTable: () => true, + updateColumns: jest.fn(), + get columns() { + throw new TypeError("Cannot read properties of undefined (reading 'layoutMap')"); + } + }; + + const plugin = new FilterPlugin({}); + plugin.table = table; + + expect(() => + plugin.initFilterPlugin({ + options: { + columns: optionColumns + } + }) + ).not.toThrow(); + + expect(table.updateColumns).not.toHaveBeenCalled(); + }); + + test('uses current table column order when filter state updates', () => { + const staleColumns = [ + { field: 'a', title: 'A' }, + { field: 'b', title: 'B' }, + { field: 'c', title: 'C' } + ]; + const currentColumns = [ + { field: 'a', title: 'A' }, + { field: 'c', title: 'C' }, + { field: 'b', title: 'B' } + ]; + const table = { + isListTable: () => true, + updateColumns: jest.fn(), + get columns() { + return currentColumns; + } + }; + + const plugin = new FilterPlugin({}); + plugin.table = table; + plugin.initFilterPlugin({ + options: { + columns: staleColumns + } + }); + + subscribeCallback?.({}, { type: 'apply_filters' }); + + expect(table.updateColumns).toHaveBeenCalledWith(currentColumns, { + clearRowHeightCache: false + }); + }); + + test('uses synced options.columns when hidden columns exist', () => { + const syncedOptionColumns = [ + { field: 'id', title: 'ID' }, + { field: 'name', title: 'Name' }, + { field: 'gender', title: 'Gender', hide: true }, + { field: 'department', title: 'Department' }, + { field: 'city', title: 'City' }, + { field: 'status', title: 'Status' } + ]; + const table = { + isListTable: () => true, + options: { + columns: syncedOptionColumns + }, + updateColumns: jest.fn(), + get columns() { + return [ + { field: 'id', title: 'ID' }, + { field: 'name', title: 'Name' }, + { field: 'city', title: 'City' }, + { field: 'gender', title: 'Gender', hide: true }, + { field: 'department', title: 'Department' }, + { field: 'status', title: 'Status' } + ]; + } + }; + + const plugin = new FilterPlugin({}); + plugin.table = table; + plugin.initFilterPlugin({ + options: { + columns: syncedOptionColumns + } + }); + + subscribeCallback?.({}, { type: 'apply_filters' }); + + expect(table.updateColumns).toHaveBeenCalledWith(syncedOptionColumns, { + clearRowHeightCache: false + }); + }); +}); diff --git a/packages/vtable-plugins/demo/filter/issue-5137.ts b/packages/vtable-plugins/demo/filter/issue-5137.ts new file mode 100644 index 0000000000..aa5161fc7a --- /dev/null +++ b/packages/vtable-plugins/demo/filter/issue-5137.ts @@ -0,0 +1,221 @@ +import * as VTable from '@visactor/vtable'; +import { FilterPlugin } from '../../src/filter'; + +const CONTAINER_ID = 'vTable'; + +type RecordItem = { + id: number; + name: string; + gender: '男' | '女'; + city: string; + department: string; + status: '在职' | '请假' | '离职'; +}; + +function createRecords(): RecordItem[] { + return [ + { id: 1, name: '张三', gender: '男', city: '北京', department: '研发', status: '在职' }, + { id: 2, name: '李四', gender: '女', city: '上海', department: '销售', status: '请假' }, + { id: 3, name: '王五', gender: '男', city: '深圳', department: '研发', status: '在职' }, + { id: 4, name: '赵六', gender: '女', city: '杭州', department: '设计', status: '离职' }, + { id: 5, name: '钱七', gender: '男', city: '广州', department: '运营', status: '在职' }, + { id: 6, name: '孙八', gender: '女', city: '苏州', department: '销售', status: '请假' } + ]; +} + +function createColumns(): VTable.ColumnsDefine { + return [ + { field: 'id', title: 'ID', width: 80, sort: true }, + { field: 'name', title: '姓名', width: 120, sort: true }, + { field: 'gender', title: '性别', width: 100 }, + { field: 'city', title: '城市', width: 120 }, + { field: 'department', title: '部门', width: 120 }, + { field: 'status', title: '状态', width: 120 } + ]; +} + +function getOrderText(table: VTable.ListTable) { + const visibleFields: string[] = []; + for (let col = 0; col < table.colCount; col++) { + visibleFields.push(String(table.getHeaderField(col, 0))); + } + return visibleFields.join(' | '); +} + +function getOptionOrderText(table: VTable.ListTable) { + return (table.options.columns ?? []).map(col => `${String(col.field)}${col.hide ? '(hide)' : ''}`).join(' | '); +} + +function getVisibleHeaderColByField(table: VTable.ListTable, field: string) { + for (let col = 0; col < table.colCount; col++) { + if (table.getHeaderField(col, 0) === field) { + return col; + } + } + return -1; +} + +export function createTable() { + const container = document.getElementById(CONTAINER_ID); + if (!container) { + return; + } + + const panel = document.createElement('div'); + panel.style.position = 'fixed'; + panel.style.top = '12px'; + panel.style.right = '12px'; + panel.style.zIndex = '9999'; + panel.style.display = 'flex'; + panel.style.flexDirection = 'column'; + panel.style.gap = '8px'; + panel.style.width = '420px'; + panel.style.padding = '12px'; + panel.style.background = 'rgba(17,24,39,0.88)'; + panel.style.color = '#fff'; + panel.style.fontSize = '12px'; + panel.style.borderRadius = '8px'; + + const title = document.createElement('div'); + title.textContent = 'issue-5137 repro: 隐藏列 -> 拖拽排序 -> Filter 确认'; + title.style.fontWeight = 'bold'; + + const desc = document.createElement('div'); + desc.textContent = + '手动复现:1. 点击“隐藏性别列” 2. 直接拖拽“城市”列到“姓名”后面 3. 点击“状态”列的筛选图标并确认。也可用下面的一键脚本。'; + desc.style.lineHeight = '1.5'; + + const buttonRow = document.createElement('div'); + buttonRow.style.display = 'flex'; + buttonRow.style.flexWrap = 'wrap'; + buttonRow.style.gap = '8px'; + + const status = document.createElement('pre'); + status.style.margin = '0'; + status.style.whiteSpace = 'pre-wrap'; + status.style.lineHeight = '1.5'; + + const records = createRecords(); + const columns = createColumns(); + const filterPlugin = new FilterPlugin({ + defaultEnabled: true + }); + + const table = new VTable.ListTable({ + container, + records, + columns, + widthMode: 'standard', + dragHeaderMode: 'all', + plugins: [filterPlugin] + }); + + function updateStatus(label: string) { + status.textContent = `${label}\n` + `current: ${getOrderText(table)}\n` + `options: ${getOptionOrderText(table)}`; + } + + function hideGenderColumn() { + const nextColumns = table.columns.map(col => { + if (col.field === 'gender') { + return { + ...col, + hide: true + }; + } + return { ...col }; + }); + table.updateColumns(nextColumns, { clearRowHeightCache: false }); + updateStatus('已隐藏 `gender` 列'); + } + + function dragCityAfterDepartment() { + const cityIndex = getVisibleHeaderColByField(table, 'city'); + const departmentIndex = getVisibleHeaderColByField(table, 'department'); + if (cityIndex < 0 || departmentIndex < 0) { + updateStatus('未找到 `city` 或 `department` 列'); + return; + } + table.changeHeaderPosition({ + source: { col: cityIndex, row: 0 }, + target: { col: departmentIndex, row: 0 }, + movingColumnOrRow: 'column' + }); + updateStatus('已把 `city` 拖到 `department` 后面'); + } + + function applyStatusFilter() { + filterPlugin.applyFilterSnapshot({ + filters: [ + { + field: 'status', + type: 'byValue', + values: ['在职', '请假'], + enable: true + } + ] + }); + updateStatus('已执行一次 Filter 确认(status in 在职/请假)'); + } + + function runAllSteps() { + hideGenderColumn(); + dragCityAfterDepartment(); + applyStatusFilter(); + updateStatus('已完成完整复现链路'); + } + + function resetTable() { + table.updateOption({ + ...table.options, + records: createRecords(), + columns: createColumns(), + plugins: [filterPlugin] + }); + filterPlugin.applyFilterSnapshot({ filters: [] }); + updateStatus('已重置为初始状态'); + } + + function createButton(text: string, onClick: () => void) { + const button = document.createElement('button'); + button.textContent = text; + button.style.padding = '6px 10px'; + button.style.cursor = 'pointer'; + button.style.borderRadius = '4px'; + button.style.border = '1px solid rgba(255,255,255,0.2)'; + button.style.background = 'rgba(255,255,255,0.12)'; + button.style.color = '#fff'; + button.addEventListener('click', onClick); + return button; + } + + buttonRow.appendChild(createButton('隐藏性别列', hideGenderColumn)); + buttonRow.appendChild(createButton('拖拽 city 到 department 后', dragCityAfterDepartment)); + buttonRow.appendChild(createButton('一键复现', runAllSteps)); + buttonRow.appendChild(createButton('重置', resetTable)); + + panel.appendChild(title); + panel.appendChild(desc); + panel.appendChild(buttonRow); + panel.appendChild(status); + document.body.appendChild(panel); + + const demoWindow = window as Window & + typeof globalThis & { + tableInstance?: VTable.ListTable; + filterPlugin?: FilterPlugin; + issue5137?: Record; + }; + + demoWindow.tableInstance = table; + demoWindow.filterPlugin = filterPlugin; + demoWindow.issue5137 = { + hideGenderColumn, + dragCityAfterDepartment, + applyStatusFilter, + runAllSteps, + resetTable, + getOrderText: () => getOrderText(table) + }; + + updateStatus('初始化完成'); +} diff --git a/packages/vtable-plugins/demo/menu.ts b/packages/vtable-plugins/demo/menu.ts index a9718ec900..f5e19298ca 100644 --- a/packages/vtable-plugins/demo/menu.ts +++ b/packages/vtable-plugins/demo/menu.ts @@ -19,6 +19,10 @@ export const menus = [ path: 'filter', name: 'bug' }, + { + path: 'filter', + name: 'issue-5137' + }, { path: 'filter', name: 'value-filter' diff --git a/packages/vtable-plugins/jest.config.js b/packages/vtable-plugins/jest.config.js index 40c71a2d45..e3fb8ebf89 100644 --- a/packages/vtable-plugins/jest.config.js +++ b/packages/vtable-plugins/jest.config.js @@ -1,9 +1,14 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const path = require('path'); + const isCI = process.env.CI === 'true' || process.env.CI === '1'; module.exports = { preset: 'ts-jest', - testEnvironment: 'jsdom', - testMatch: ['/__tests__/**/*.test.(ts|js)'], + runner: 'jest-electron/runner', + testEnvironment: 'jest-electron/environment', + testRegex: '/__tests__(/.*)+\\.test\\.(js|ts)$', + testPathIgnorePatterns: ['/node_modules/', '/cjs/', '/es/', '/\\.rollup\\.cache/'], silent: false, verbose: true, globals: { @@ -13,16 +18,18 @@ module.exports = { }, tsconfig: { resolveJsonModule: true, - esModuleInterop: true + esModuleInterop: true, + paths: { + '@src/vrender': ['../vtable/src/vrender.ts'], + '@src/*': ['../vtable/src/*'] + } } }, __DEV__: true }, collectCoverage: true, coverageReporters: ['json-summary', 'lcov', 'text'], - collectCoverageFrom: [ - '/src/history/**/*.ts' - ], + collectCoverageFrom: ['/src/history/**/*.ts'], coverageThreshold: isCI ? { global: { @@ -33,6 +40,30 @@ module.exports = { } } : undefined, - moduleNameMapper: {}, + moduleNameMapper: { + 'd3-array': path.resolve( + __dirname, + '../../common/temp/node_modules/.pnpm/d3-array@3.2.3/node_modules/d3-array/dist/d3-array.min.js' + ), + 'd3-geo': path.resolve( + __dirname, + '../../common/temp/node_modules/.pnpm/d3-geo@1.12.1/node_modules/d3-geo/dist/d3-geo.min.js' + ), + 'd3-dsv': path.resolve( + __dirname, + '../../common/temp/node_modules/.pnpm/d3-dsv@3.0.1/node_modules/d3-dsv/dist/d3-dsv.min.js' + ), + 'd3-hexbin': path.resolve( + __dirname, + '../../common/temp/node_modules/.pnpm/d3-hexbin@0.2.2/node_modules/d3-hexbin/build/d3-hexbin.min.js' + ), + 'd3-hierarchy': path.resolve( + __dirname, + '../../common/temp/node_modules/.pnpm/d3-hierarchy@3.1.2/node_modules/d3-hierarchy/dist/d3-hierarchy.min.js' + ), + '^@visactor/vtable/es/(.*)$': '/../vtable/src/$1', + '@visactor/vtable': path.resolve(__dirname, '../vtable/src/index.ts'), + '@src/vrender': path.resolve(__dirname, '../vtable/src/vrender.ts') + }, setupFiles: ['./setup-mock.js'] }; diff --git a/packages/vtable-plugins/package.json b/packages/vtable-plugins/package.json index aa1760334b..3f30cdd6a4 100644 --- a/packages/vtable-plugins/package.json +++ b/packages/vtable-plugins/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-plugins", - "version": "1.26.1", + "version": "1.26.2", "description": "The search util of VTable", "author": { "name": "VisActor", @@ -61,9 +61,9 @@ "devDependencies": { "cross-env": "^7.0.3", "increase-memory-limit": "^1.0.7", - "@visactor/vtable": "workspace:*", - "@visactor/vtable-editors": "workspace:*", - "@visactor/vtable-gantt": "workspace:*", + "@visactor/vtable": "workspace:1.26.2", + "@visactor/vtable-editors": "workspace:1.26.2", + "@visactor/vtable-gantt": "workspace:1.26.2", "@visactor/vchart": "2.0.13-alpha.10", "@internal/bundler": "workspace:*", "@internal/eslint-config": "workspace:*", @@ -110,5 +110,10 @@ "rollup-plugin-node-resolve": "5.2.0", "@types/lodash": "4.14.182" }, - "packageManager": "pnpm@10.7.0" + "packageManager": "pnpm@10.7.0", + "repository": { + "type": "git", + "url": "https://github.com/VisActor/VTable.git", + "directory": "packages/vtable-plugins" + } } diff --git a/packages/vtable-plugins/src/add-row-column.ts b/packages/vtable-plugins/src/add-row-column.ts index 485608a80e..2ca92b178e 100644 --- a/packages/vtable-plugins/src/add-row-column.ts +++ b/packages/vtable-plugins/src/add-row-column.ts @@ -457,13 +457,13 @@ export class AddRowColumnPlugin implements pluginsDefinition.IVTablePlugin { } // #endregion release() { - this.leftDotForAddColumn.remove(); - this.rightDotForAddColumn.remove(); - this.addIconForAddColumn.remove(); - this.addLineForAddColumn.remove(); - this.topDotForAddRow.remove(); - this.bottomDotForAddRow.remove(); - this.addIconForAddRow.remove(); - this.addLineForAddRow.remove(); + this.leftDotForAddColumn?.remove(); + this.rightDotForAddColumn?.remove(); + this.addIconForAddColumn?.remove(); + this.addLineForAddColumn?.remove(); + this.topDotForAddRow?.remove(); + this.bottomDotForAddRow?.remove(); + this.addIconForAddRow?.remove(); + this.addLineForAddRow?.remove(); } } diff --git a/packages/vtable-plugins/src/filter/filter.ts b/packages/vtable-plugins/src/filter/filter.ts index 1c9ad2f53c..788d4e0a33 100644 --- a/packages/vtable-plugins/src/filter/filter.ts +++ b/packages/vtable-plugins/src/filter/filter.ts @@ -78,17 +78,20 @@ export class FilterPlugin implements pluginsDefinition.IVTablePlugin { this.filterEngine = new FilterEngine(this.pluginOptions); this.filterStateManager = new FilterStateManager(this.table, this.filterEngine); this.filterToolbar = new FilterToolbar(this.table, this.filterStateManager, this.pluginOptions); + // BEFORE_INIT 阶段 table.columns 可能还不可用,先缓存本次 options.columns 作为 getCurrentColumns 的回退值。 this.columns = eventArgs.options.columns; this.filterToolbar.render(document.body); - this.updateFilterIcons(this.columns); + this.updateFilterIcons(this.getCurrentColumns()); this.filterStateManager.subscribe((_: FilterState, action?: FilterAction) => { // 新增筛选配置时,不需要更新筛选图标以及表格 if (action?.type === FilterActionType.ADD_FILTER) { return; } - this.updateFilterIcons(this.columns); - (this.table as ListTable).updateColumns(this.columns, { + const currentColumns = this.getCurrentColumns(); + this.columns = currentColumns; + this.updateFilterIcons(currentColumns); + (this.table as ListTable).updateColumns(currentColumns, { clearRowHeightCache: false }); }); @@ -176,7 +179,9 @@ export class FilterPlugin implements pluginsDefinition.IVTablePlugin { // 更新筛选器UI样式 this.filterToolbar.updateStyles(this.pluginOptions.styles); // 更新icon - (this.table as ListTable).updateColumns(this.columns, { + const currentColumns = this.getCurrentColumns(); + this.columns = currentColumns; + (this.table as ListTable).updateColumns(currentColumns, { clearRowHeightCache: false }); } @@ -226,6 +231,21 @@ export class FilterPlugin implements pluginsDefinition.IVTablePlugin { this.updateFilterIcons(options.columns); } + private getCurrentColumns(): ColumnsDefine { + if (this.table?.isListTable?.()) { + const optionColumns = (this.table as ListTable).options?.columns; + if (optionColumns?.length) { + return optionColumns; + } + try { + return (this.table as ListTable).columns; + } catch (error) { + // BEFORE_INIT 阶段 ListTable 的 layoutMap 可能尚未建立,回退到最近一次缓存的 columns。 + } + } + return this.columns ?? []; + } + /** * 重新应用所有激活的筛选状态 * 在 updateOption 后调用,因为 updateOption 会全量更新表格 diff --git a/packages/vtable-plugins/src/global.d.ts b/packages/vtable-plugins/src/global.d.ts new file mode 100644 index 0000000000..fd30a9e5cf --- /dev/null +++ b/packages/vtable-plugins/src/global.d.ts @@ -0,0 +1,2 @@ +declare const __DEV__: boolean; +declare const __VERSION__: string; diff --git a/packages/vtable-search/package.json b/packages/vtable-search/package.json index 29621a1dee..a6c0241191 100644 --- a/packages/vtable-search/package.json +++ b/packages/vtable-search/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-search", - "version": "1.26.1", + "version": "1.26.2", "description": "The search util of VTable", "author": { "name": "VisActor", @@ -36,7 +36,7 @@ "access": "public" }, "dependencies": { - "@visactor/vtable": "workspace:*", + "@visactor/vtable": "workspace:1.26.2", "@visactor/vutils": "~1.0.17" }, "devDependencies": { @@ -86,5 +86,10 @@ "axios": "^1.4.0", "@types/react-is": "^17.0.3", "rollup-plugin-node-resolve": "5.2.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/VisActor/VTable.git", + "directory": "packages/vtable-search" } } diff --git a/packages/vtable-sheet/package.json b/packages/vtable-sheet/package.json index ff9043dff0..335a40feab 100644 --- a/packages/vtable-sheet/package.json +++ b/packages/vtable-sheet/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-sheet", - "version": "1.26.1", + "version": "1.26.2", "description": "Lightweight editable spreadsheet component based on VTable", "keywords": [ "vtable-sheet", @@ -50,9 +50,9 @@ "build:es": "bundle --clean -f es --ignorePostTasks" }, "dependencies": { - "@visactor/vtable": "workspace:*", - "@visactor/vtable-editors": "workspace:*", - "@visactor/vtable-plugins": "workspace:*", + "@visactor/vtable": "workspace:1.26.2", + "@visactor/vtable-editors": "workspace:1.26.2", + "@visactor/vtable-plugins": "workspace:1.26.2", "@visactor/vutils": "~1.0.17", "@visactor/vscale": "~1.0.17", "@visactor/vdataset": "~1.0.17", diff --git a/packages/vtable/CHANGELOG.json b/packages/vtable/CHANGELOG.json index 263601fb8f..2a6b5fc022 100644 --- a/packages/vtable/CHANGELOG.json +++ b/packages/vtable/CHANGELOG.json @@ -1,6 +1,64 @@ { "name": "@visactor/vtable", "entries": [ + { + "version": "1.26.3", + "tag": "@visactor/vtable_v1.26.3", + "date": "Tue, 16 Jun 2026 06:22:51 GMT", + "comments": { + "none": [ + { + "comment": "Merge pull request #5144 from VisActor/fix/issue-5027\n\nfix: avoid selection gap with frozen rows\n" + }, + { + "comment": "fix: preserve sticky group position after toggle\n\n" + }, + { + "comment": "fix: avoid selection gap with frozen rows\n\n" + }, + { + "comment": "fix: preserve hidden column order after filter update\n\n" + }, + { + "comment": "fix: resolve regression from #5137 fix which broke nested header drag and exposed internal state\n\n" + } + ], + "patch": [ + { + "comment": "Fix an issue where keyboard multi-selection with Shift plus arrow keys stopped expanding after the second cell (GitHub #5146)" + } + ] + } + }, + { + "version": "1.26.2", + "tag": "@visactor/vtable_v1.26.2", + "date": "Tue, 16 Jun 2026 01:39:41 GMT", + "comments": { + "none": [ + { + "comment": "Merge pull request #5144 from VisActor/fix/issue-5027\n\nfix: avoid selection gap with frozen rows\n" + }, + { + "comment": "fix: preserve sticky group position after toggle\n\n" + }, + { + "comment": "fix: avoid selection gap with frozen rows\n\n" + }, + { + "comment": "fix: preserve hidden column order after filter update\n\n" + }, + { + "comment": "fix: resolve regression from #5137 fix which broke nested header drag and exposed internal state\n\n" + } + ], + "patch": [ + { + "comment": "Fix an issue where keyboard multi-selection with Shift plus arrow keys stopped expanding after the second cell (GitHub #5146)" + } + ] + } + }, { "version": "1.26.1", "tag": "@visactor/vtable_v1.26.1", diff --git a/packages/vtable/CHANGELOG.md b/packages/vtable/CHANGELOG.md index 481eda0769..819bd210b7 100644 --- a/packages/vtable/CHANGELOG.md +++ b/packages/vtable/CHANGELOG.md @@ -1,6 +1,58 @@ # Change Log - @visactor/vtable -This log was last generated on Mon, 11 May 2026 08:51:13 GMT and should not be manually modified. +This log was last generated on Tue, 16 Jun 2026 06:22:51 GMT and should not be manually modified. + +## 1.26.3 +Tue, 16 Jun 2026 06:22:51 GMT + +### Patches + +- Fix an issue where keyboard multi-selection with Shift plus arrow keys stopped expanding after the second cell (GitHub #5146) + +### Updates + +- Merge pull request #5144 from VisActor/fix/issue-5027 + +fix: avoid selection gap with frozen rows + +- fix: preserve sticky group position after toggle + + +- fix: avoid selection gap with frozen rows + + +- fix: preserve hidden column order after filter update + + +- fix: resolve regression from #5137 fix which broke nested header drag and exposed internal state + + + +## 1.26.2 +Tue, 16 Jun 2026 01:39:41 GMT + +### Patches + +- Fix an issue where keyboard multi-selection with Shift plus arrow keys stopped expanding after the second cell (GitHub #5146) + +### Updates + +- Merge pull request #5144 from VisActor/fix/issue-5027 + +fix: avoid selection gap with frozen rows + +- fix: preserve sticky group position after toggle + + +- fix: avoid selection gap with frozen rows + + +- fix: preserve hidden column order after filter update + + +- fix: resolve regression from #5137 fix which broke nested header drag and exposed internal state + + ## 1.26.1 Mon, 11 May 2026 08:51:13 GMT diff --git a/packages/vtable/__tests__/columns/listTable-dragHeader.test.ts b/packages/vtable/__tests__/columns/listTable-dragHeader.test.ts index b366f90451..5d3bed929e 100644 --- a/packages/vtable/__tests__/columns/listTable-dragHeader.test.ts +++ b/packages/vtable/__tests__/columns/listTable-dragHeader.test.ts @@ -125,6 +125,84 @@ describe('listTable-cellType-function init test', () => { ]); listTable2.release(); }); + test('listTable changeHeaderPosition keeps options.columns in sync for follow-up updates', () => { + const containerDom2: HTMLElement = createDiv(); + containerDom2.style.position = 'relative'; + containerDom2.style.width = '1000px'; + containerDom2.style.height = '800px'; + const records2 = [ + { a: 1, b: 2, c: 3 }, + { a: 4, b: 5, c: 6 } + ]; + const columns2 = [ + { field: 'a', title: 'A' }, + { field: 'b', title: 'B' }, + { field: 'c', title: 'C' } + ]; + const listTable2 = new ListTable(containerDom2, { records: records2, columns: columns2, dragHeaderMode: 'all' }); + listTable2.changeHeaderPosition({ + source: { col: 1, row: 0 }, + target: { col: 2, row: 0 }, + movingColumnOrRow: 'column' + }); + expect(listTable2.options.columns).toEqual([ + { field: 'a', title: 'A' }, + { field: 'c', title: 'C' }, + { field: 'b', title: 'B' } + ]); + listTable2.updateOption({ + ...listTable2.options, + widthMode: 'standard' + }); + expect(listTable2.columns).toEqual([ + { field: 'a', title: 'A' }, + { field: 'c', title: 'C' }, + { field: 'b', title: 'B' } + ]); + listTable2.release(); + }); + test('listTable changeHeaderPosition keeps hidden-column order in sync for follow-up updates', () => { + const containerDom2: HTMLElement = createDiv(); + containerDom2.style.position = 'relative'; + containerDom2.style.width = '1000px'; + containerDom2.style.height = '800px'; + const records2 = [ + { id: 1, name: 'A', gender: 'M', city: 'BJ', department: 'RD', status: 'on' }, + { id: 2, name: 'B', gender: 'F', city: 'SH', department: 'Sales', status: 'off' } + ]; + const columns2 = [ + { field: 'id', title: 'ID' }, + { field: 'name', title: 'Name' }, + { field: 'gender', title: 'Gender', hide: true }, + { field: 'city', title: 'City' }, + { field: 'department', title: 'Department' }, + { field: 'status', title: 'Status' } + ]; + const listTable2 = new ListTable(containerDom2, { records: records2, columns: columns2, dragHeaderMode: 'all' }); + listTable2.changeHeaderPosition({ + source: { col: 2, row: 0 }, + target: { col: 3, row: 0 }, + movingColumnOrRow: 'column' + }); + expect(listTable2.options.columns).toEqual([ + { field: 'id', title: 'ID' }, + { field: 'name', title: 'Name' }, + { field: 'gender', title: 'Gender', hide: true }, + { field: 'department', title: 'Department' }, + { field: 'city', title: 'City' }, + { field: 'status', title: 'Status' } + ]); + listTable2.updateOption({ + ...listTable2.options, + widthMode: 'standard' + }); + expect(listTable2.getHeaderField(0, 0)).toBe('id'); + expect(listTable2.getHeaderField(1, 0)).toBe('name'); + expect(listTable2.getHeaderField(2, 0)).toBe('department'); + expect(listTable2.getHeaderField(3, 0)).toBe('city'); + expect(listTable2.getHeaderField(4, 0)).toBe('status'); + listTable2.release(); + }); test('listTable dragHeader interaction', () => { option.transpose = true; listTable.updateOption(option); diff --git a/packages/vtable/__tests__/options/listTable-api-with-frozen.test.ts b/packages/vtable/__tests__/options/listTable-api-with-frozen.test.ts index efc31f54cd..eb9077cf56 100644 --- a/packages/vtable/__tests__/options/listTable-api-with-frozen.test.ts +++ b/packages/vtable/__tests__/options/listTable-api-with-frozen.test.ts @@ -116,4 +116,30 @@ describe('listTable init test', () => { expect(listTable.cellIsInVisualView(3, 39)).toBe(true); expect(listTable.cellIsInVisualView(3, 38)).toBe(true); }); + + test('listTable body visible row range should ignore frozen rows offset duplication', () => { + const optionWithFrozenRows = { + ...option, + frozenRowCount: 5, + rightFrozenColCount: 0, + bottomFrozenRowCount: 0, + container: createDiv(), + records + }; + optionWithFrozenRows.container.style.position = 'relative'; + optionWithFrozenRows.container.style.width = '1000px'; + optionWithFrozenRows.container.style.height = '800px'; + + const frozenTable = new ListTable(optionWithFrozenRows); + frozenTable.scrollTop = 400; + + expect(frozenTable.getBodyVisibleRowRange()).toEqual({ + rowStart: 10, + rowEnd: 29 + }); + expect(frozenTable.getBodyVisibleCellRange()).toMatchObject({ + rowStart: 10, + rowEnd: 29 + }); + }); }); diff --git a/packages/vtable/__tests__/pivotTable-tree.test.ts b/packages/vtable/__tests__/pivotTable-tree.test.ts index 38531f7c22..2c4097d1d1 100644 --- a/packages/vtable/__tests__/pivotTable-tree.test.ts +++ b/packages/vtable/__tests__/pivotTable-tree.test.ts @@ -509,3 +509,154 @@ describe('pivotTableTree init test', () => { pivotTableTree.release(); }); }); + +describe('pivotTable grid-tree hierarchy scroll', () => { + test('keeps bottom scroll position after collapsing a visible bottom row tree node', () => { + const containerDom: HTMLElement = createDiv(); + containerDom.style.position = 'relative'; + containerDom.style.width = '800px'; + containerDom.style.height = '400px'; + + const rowTree = Array.from({ length: 80 }, (_, index) => ({ + dimensionKey: 'category', + value: index % 2 === 0 ? '餐饮' : `category-${index}`, + hierarchyState: 'expand', + children: [ + { + dimensionKey: 'subCategory', + value: `sub-${index}` + } + ] + })); + const columnTree = Array.from({ length: 20 }, (_, index) => ({ + value: `indicator-${index}`, + indicatorKey: `indicator-${index}` + })); + const indicators = columnTree.map(item => ({ + indicatorKey: item.indicatorKey, + caption: item.value, + width: 100 + })); + + const pivotTable = new PivotTable({ + container: containerDom, + records: [], + rowTree, + columnTree, + rows: [ + { + dimensionKey: 'category', + title: 'category', + width: 200 + }, + { + dimensionKey: 'subCategory', + title: 'subCategory', + width: 200 + } + ], + columns: [], + indicators, + defaultRowHeight: 40, + defaultHeaderRowHeight: 40, + rowHierarchyType: 'grid-tree', + columnHierarchyType: 'grid-tree', + widthMode: 'standard' + }); + + try { + const getMaxScrollTop = () => Math.max(0, pivotTable.getAllRowsHeight() - pivotTable.scenegraph.height); + + pivotTable.setScrollTop(Number.MAX_SAFE_INTEGER); + expect(pivotTable.scrollTop).toBe(getMaxScrollTop()); + + const visibleRowRange = pivotTable.getBodyVisibleRowRange(); + let targetRow = -1; + for (let row = visibleRowRange.rowStart; row <= visibleRowRange.rowEnd; row++) { + if (pivotTable.getCellValue(0, row) === '餐饮' && pivotTable.getHierarchyState(0, row) === 'expand') { + targetRow = row; + break; + } + } + expect(targetRow).toBeGreaterThanOrEqual(0); + + pivotTable.toggleHierarchyState(0, targetRow); + + expect(pivotTable.scrollTop).toBe(getMaxScrollTop()); + } finally { + pivotTable.release(); + } + }); + + test('does not scroll to bottom after expanding a row tree node when table was not scrollable', () => { + const containerDom: HTMLElement = createDiv(); + containerDom.style.position = 'relative'; + containerDom.style.width = '800px'; + containerDom.style.height = '400px'; + + const columnTree = Array.from({ length: 5 }, (_, index) => ({ + value: `indicator-${index}`, + indicatorKey: `indicator-${index}` + })); + const indicators = columnTree.map(item => ({ + indicatorKey: item.indicatorKey, + caption: item.value, + width: 100 + })); + + const pivotTable = new PivotTable({ + container: containerDom, + records: [], + rowTree: [ + { + dimensionKey: 'province', + value: '浙江省', + hierarchyState: 'collapse', + children: Array.from({ length: 20 }, (_, index) => ({ + dimensionKey: 'city', + value: `city-${index}` + })) + }, + { + dimensionKey: 'province', + value: '江苏省' + } + ], + columnTree, + rows: [ + { + dimensionKey: 'province', + title: 'province', + width: 200 + }, + { + dimensionKey: 'city', + title: 'city', + width: 200 + } + ], + columns: [], + indicators, + defaultRowHeight: 40, + defaultHeaderRowHeight: 40, + rowHierarchyType: 'grid-tree', + columnHierarchyType: 'grid-tree', + widthMode: 'standard' + }); + + try { + const getMaxScrollTop = () => Math.max(0, pivotTable.getAllRowsHeight() - pivotTable.scenegraph.height); + + expect(pivotTable.scrollTop).toBe(0); + expect(getMaxScrollTop()).toBe(0); + expect(pivotTable.getHierarchyState(0, 1)).toBe('collapse'); + + pivotTable.toggleHierarchyState(0, 1); + + expect(getMaxScrollTop()).toBeGreaterThan(0); + expect(pivotTable.scrollTop).toBe(0); + } finally { + pivotTable.release(); + } + }); +}); diff --git a/packages/vtable/examples/debug/issue-5027.ts b/packages/vtable/examples/debug/issue-5027.ts new file mode 100644 index 0000000000..f33e0f83ff --- /dev/null +++ b/packages/vtable/examples/debug/issue-5027.ts @@ -0,0 +1,81 @@ +import * as VTable from '../../src'; + +const CONTAINER_ID = 'vTable'; + +function createRecords(count: number) { + return Array.from({ length: count }, (_, index) => ({ + name: `John ${index + 1}`, + age: 18 + (index % 10), + gender: index % 2 === 0 ? 'male' : 'female', + hobby: index % 3 === 0 ? '🏀' : index % 3 === 1 ? '🎸' : '📚' + })); +} + +export function createTable() { + const container = document.getElementById(CONTAINER_ID); + if (!container) { + throw new Error('Cannot find VTable container'); + } + const option: VTable.ListTableConstructorOptions = { + container, + columns: [ + { + field: 'name', + title: 'name', + width: 180 + }, + { + field: 'age', + title: 'age', + width: 120 + }, + { + field: 'gender', + title: 'gender', + width: 140 + }, + { + field: 'hobby', + title: 'hobby', + width: 140 + } + ], + records: createRecords(2000), + frozenRowCount: 5, + heightMode: 'standard', + widthMode: 'standard', + select: { + headerSelectMode: 'inline', + highlightMode: 'column' + }, + theme: VTable.themes.DEFAULT.extends({ + scrollStyle: { + visible: 'always', + hoverOn: false + } + }) + }; + + const tableInstance = new VTable.ListTable(option); + + // Preselect the age column so the scrollbar drag issue can be reproduced immediately. + tableInstance.selectCol(1); + + const info = document.createElement('div'); + info.style.cssText = ['margin: 8px 0', 'font-size: 12px', 'line-height: 18px', 'color: #333'].join(';'); + info.innerText = 'issue-5027: drag the vertical scrollbar while the age column is selected and frozenRowCount is 5.'; + container.parentElement?.insertBefore(info, container); + + const w = window as unknown as { + tableInstance?: VTable.ListTable; + issue5027?: { + table: VTable.ListTable; + selectAgeColumn: () => void; + }; + }; + w.tableInstance = tableInstance; + w.issue5027 = { + table: tableInstance, + selectAgeColumn: () => tableInstance.selectCol(1) + }; +} diff --git a/packages/vtable/examples/debug/issue-5146.ts b/packages/vtable/examples/debug/issue-5146.ts new file mode 100644 index 0000000000..05cbb8d203 --- /dev/null +++ b/packages/vtable/examples/debug/issue-5146.ts @@ -0,0 +1,84 @@ +import * as VTable from '../../src'; + +const CONTAINER_ID = 'vTable'; +const TIP_ID = 'issue-5146-tip'; + +function createRecords(count: number) { + return Array.from({ length: count }, (_, index) => ({ + id: index + 1, + name: `name-${index + 1}`, + city: ['Beijing', 'Shanghai', 'Hangzhou', 'Shenzhen'][index % 4], + score: 60 + (index % 40), + email: `user${index + 1}@visactor.io` + })); +} + +function ensureTip() { + const existed = document.getElementById(TIP_ID); + if (existed) { + existed.remove(); + } + const tip = document.createElement('div'); + tip.id = TIP_ID; + tip.style.margin = '8px 0'; + tip.style.font = '14px/1.5 sans-serif'; + tip.textContent = [ + 'Issue #5146:', + 'click any body cell or use the preselected cell,', + 'then keep pressing Shift + Arrow keys to verify the selection range expands continuously.' + ].join(' '); + const container = document.getElementById(CONTAINER_ID); + container?.parentElement?.insertBefore(tip, container); +} + +export function createTable() { + ensureTip(); + + const option: VTable.ListTableConstructorOptions = { + container: document.getElementById(CONTAINER_ID), + records: createRecords(30), + columns: [ + { + field: 'id', + title: 'ID', + width: 80 + }, + { + field: 'name', + title: 'Name', + width: 180 + }, + { + field: 'city', + title: 'City', + width: 160 + }, + { + field: 'score', + title: 'Score', + width: 120 + }, + { + field: 'email', + title: 'Email', + width: 240 + } + ], + keyboardOptions: { + moveSelectedCellOnArrowKeys: true, + shiftMultiSelect: true + }, + select: { + headerSelectMode: 'cell' + }, + widthMode: 'standard', + defaultRowHeight: 36 + }; + + const table = new VTable.ListTable(option); + table.selectCell(1, 2); + table.getElement()?.focus(); + + const w = window as unknown as { tableInstance?: VTable.ListTable }; + w.tableInstance = table; +} diff --git a/packages/vtable/examples/debug/scroll-collapse-bottom.ts b/packages/vtable/examples/debug/scroll-collapse-bottom.ts new file mode 100644 index 0000000000..59a3a49e71 --- /dev/null +++ b/packages/vtable/examples/debug/scroll-collapse-bottom.ts @@ -0,0 +1,577 @@ +import * as VTable from '../../src'; + +export function createTable() { + const dom = document.querySelector('#vTable') as HTMLElement; + dom.style.width = '800px'; + dom.style.height = '400px'; + const PivotGrid = VTable.PivotTable; + const rowTree = [ + { + dimensionKey: '220524114340021', + value: '办公用品', + // hierarchyState: 'collapse', + children: [ + { + dimensionKey: '220524114340022', + value: '公司', + // hierarchyState: 'expand',//设置默认展开 + children: [ + { + dimensionKey: '220524114340023', + value: '一级', + children: [ + { + dimensionKey: '2205241143400232', + value: '一级' + }, + { + dimensionKey: '2205241143400232', + value: '二级' + }, + { + dimensionKey: '2205241143400232', + value: '三级' + } + ] + }, + { + dimensionKey: '220524114340023', + value: '二级', + children: [ + { + dimensionKey: '2205241143400232', + value: '一级' + }, + { + dimensionKey: '2205241143400232', + value: '二级' + }, + { + dimensionKey: '2205241143400232', + value: '三级' + } + ] + }, + { + dimensionKey: '220524114340023', + value: '三级' + } + ] + }, + { + dimensionKey: '220524114340022', + value: '消费者', + children: [ + { + dimensionKey: '220524114340023', + value: '一级1' + // hierarchyState: 'expand', + }, + { + dimensionKey: '220524114340023', + value: '二级1' + }, + { + dimensionKey: '220524114340023', + value: '三级1' + } + ] + }, + { + dimensionKey: '220524114340022', + value: '小型企业' + } + ] + }, + { + dimensionKey: '220524114340021', + //title: '220524114340021', + value: '家具', + children: [ + { + dimensionKey: '220524114340022', + value: '公司1' + // hierarchyState: 'expand', + }, + { + dimensionKey: '220524114340022', + value: '消费者1' + }, + { + dimensionKey: '220524114340022', + value: '小型企业1' + } + ] + }, + { + dimensionKey: '220524114340021', + //title: '220524114340021', + value: '餐饮', + children: [ + { + dimensionKey: '220524114340022', + value: '公司2' + // hierarchyState: 'expand', + }, + { + dimensionKey: '220524114340022', + value: '消费者2' + }, + { + dimensionKey: '220524114340022', + value: '小型企业2' + } + ] + }, + { + dimensionKey: '220524114340021', + //title: '220524114340021', + value: '技术', + children: [ + { + dimensionKey: '220524114340022', + value: '公司3' + // hierarchyState: 'expand', + }, + { + dimensionKey: '220524114340022', + value: '消费者3' + }, + { + dimensionKey: '220524114340022', + value: '小型企业3' + } + ] + } + ]; + const columnTree = [ + { + dimensionKey: '220524114340020', + value: '东北', + children: [ + { + dimensionKey: '220524114340031', + value: '黑龙江', + children: [ + { + indicatorKey: '220524114340013', + value: '销售额' + }, + { + indicatorKey: '220524114340014', + value: '利润' + } + ] + }, + { + dimensionKey: '220524114340031', + value: '吉林', + children: [ + { + indicatorKey: '220524114340013', + value: '销售额' + }, + { + indicatorKey: '220524114340014', + value: '利润' + } + ] + }, + { + dimensionKey: '220524114340031', + value: '辽宁', + children: [ + { + indicatorKey: '220524114340013', + value: '销售额' + }, + { + indicatorKey: '220524114340014', + value: '利润' + } + ] + } + ] + }, + { + dimensionKey: '220524114340020', + value: '华北', + children: [ + { + dimensionKey: '220524114340031', + value: '内蒙古', + children: [ + { + indicatorKey: '220524114340013', + value: '销售额' + }, + { + indicatorKey: '220524114340014', + value: '利润' + } + ] + }, + { + dimensionKey: '220524114340031', + value: '北京', + children: [ + { + indicatorKey: '220524114340013', + value: '销售额' + }, + { + indicatorKey: '220524114340014', + value: '利润' + } + ] + }, + { + dimensionKey: '220524114340031', + value: '天津', + children: [ + { + indicatorKey: '220524114340013', + value: '销售额' + }, + { + indicatorKey: '220524114340014', + value: '利润' + } + ] + } + ] + }, + { + dimensionKey: '220524114340020', + value: '中南', + children: [ + { + dimensionKey: '220524114340031', + value: '广东', + children: [ + { + indicatorKey: '220524114340013', + value: '销售额' + }, + { + indicatorKey: '220524114340014', + value: '利润' + } + ] + }, + { + dimensionKey: '220524114340031', + value: '广西', + children: [ + { + indicatorKey: '220524114340013', + value: '销售额' + }, + { + indicatorKey: '220524114340014', + value: '利润' + } + ] + }, + { + dimensionKey: '220524114340031', + value: '湖南', + children: [ + { + indicatorKey: '220524114340013', + value: '销售额' + }, + { + indicatorKey: '220524114340014', + value: '利润' + } + ] + } + ] + } + ]; + fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/test-demo-data/supermarket-flat.json') + .then(res => res.json()) + .then(data => { + const option = { + records: data, + menu: { + contextMenuItems: ['复制单元格内容', '查询详情'] + }, + rowTree: [ + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree, + ...rowTree + ], + columnTree: [ + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree, + ...columnTree + ], + rows: [ + { + dimensionKey: '220524114340021', + title: '类别-细分-邮寄方式', + headerFormat(value) { + return `${value}`; + }, + width: 200, + headerStyle: { + cursor: 'help', + textAlign: 'left', + borderColor: 'blue', + color: 'purple', + // textBaseline: 'top', + textStick: true, + bgColor: '#6cd26f' + } + }, + { + dimensionKey: '220524114340022', + title: '子类别', + headerStyle: { + textAlign: 'left', + color: 'blue', + bgColor: '#45b89f' + } + // headerType: 'MULTILINETEXT', + }, + { + dimensionKey: '220524114340023', + title: '邮寄方式', + headerStyle: { + textAlign: 'left', + color: 'white', + bgColor: '#6699ff' + } + // headerType: 'MULTILINETEXT', + } + ], + columns: [ + { + dimensionKey: '220524114340020', + title: '地区', + headerFormat(value) { + return `${value}地区`; + }, + headerStyle: { + textAlign: 'left', + borderColor: 'blue', + color: 'yellow', + textStick: true, + bgColor(arg) { + if ( + arg.cellHeaderPaths.colHeaderPaths && + 'value' in arg.cellHeaderPaths.colHeaderPaths[0] && + arg.cellHeaderPaths.colHeaderPaths[0].value === '东北' + ) { + return '#bd422a'; + } + if ( + arg.cellHeaderPaths.colHeaderPaths && + 'value' in arg.cellHeaderPaths.colHeaderPaths[0] && + arg.cellHeaderPaths.colHeaderPaths[0].value === '华北' + ) { + return '#ff9900'; + } + return 'gray'; + } + }, + // 指标菜单 + dropDownMenu: ['升序排序I', '降序排序I', '冻结列I'], + // corner菜单 + cornerDropDownMenu: ['升序排序C', '降序排序C', '冻结列C'], + drillDown: true + }, + { + dimensionKey: '220524114340031', + title: '省份' + } + ], + indicators: [ + { + indicatorKey: '220524114340013', + title: '销售额', + width: 'auto', + format(value, col, row, table) { + // if (rec.rowDimensions[0].value === '东北') return `${rec.dataValue}%`; + if (!value) { + return '--'; + } + return Math.floor(parseFloat(value)); + }, + headerStyle: { + color: 'red', + bgColor(arg) { + if ( + arg.cellHeaderPaths.colHeaderPaths && + 'value' in arg.cellHeaderPaths.colHeaderPaths[0] && + arg.cellHeaderPaths.colHeaderPaths[0].value === '东北' + ) { + return '#bd422a'; + } + if ( + arg.cellHeaderPaths.colHeaderPaths && + 'value' in arg.cellHeaderPaths.colHeaderPaths[0] && + arg.cellHeaderPaths.colHeaderPaths[0].value === '华北' + ) { + return '#ff9900'; + } + return 'gray'; + } + } + // headerType: 'MULTILINETEXT', + }, + { + indicatorKey: '220524114340014', + title: '利润', + format(value) { + // if (rec.rowDimensions[0].value === '东北') return `${rec.dataValue}%`; + if (!value) { + return '--'; + } + return Math.floor(parseFloat(value)); + }, + width: 'auto', + headerStyle: { + bgColor(arg) { + if ( + arg.cellHeaderPaths.colHeaderPaths && + 'value' in arg.cellHeaderPaths.colHeaderPaths[0] && + arg.cellHeaderPaths.colHeaderPaths[0].value === '东北' + ) { + return '#bd422a'; + } + if ( + arg.cellHeaderPaths.colHeaderPaths && + 'value' in arg.cellHeaderPaths.colHeaderPaths[0] && + arg.cellHeaderPaths.colHeaderPaths[0].value === '华北' + ) { + return '#ff9900'; + } + return 'gray'; + } + } + } + ], + corner: { + titleOnDimension: 'row', + headerStyle: { + textAlign: 'center', + borderColor: 'red', + color: 'red', + underline: true, + fontSize: 16, + fontWeight: 'bold', + fontFamily: 'sans-serif' + } + }, + // heightMode: 'autoHeight', + autoWrapText: true, + widthMode: 'standard', + columnHierarchyType: 'grid-tree', + rowHierarchyType: 'grid-tree', + rowExpandLevel: 2, + columnExpandLevel: 1, + rowHierarchyIndent: 20, + theme: VTable.themes.ARCO, + dragHeaderMode: 'all' + }; + + const instance = new PivotGrid(dom, option); + window.tableInstance = instance; + const { PIVOT_SORT_CLICK } = VTable.PivotTable.EVENT_TYPE; + instance.on(PIVOT_SORT_CLICK, e => { + const order = e.order === 'asc' ? 'desc' : e.order === 'desc' ? 'normal' : 'asc'; + instance.updatePivotSortState([{ dimensions: e.dimensionInfo, order }]); + }); + window.reproduceScrollCollapseBottom = () => { + instance.setScrollTop(Number.MAX_SAFE_INTEGER); + const before = { + scrollTop: instance.scrollTop, + maxScrollTop: Math.max(0, instance.getAllRowsHeight() - instance.scenegraph.height) + }; + const visibleRange = instance.getBodyVisibleRowRange(); + let targetRow = -1; + for (let row = visibleRange.rowStart; row <= visibleRange.rowEnd; row++) { + if (instance.getCellValue(0, row) === '餐饮' && instance.getHierarchyState(0, row) === 'expand') { + targetRow = row; + break; + } + } + if (targetRow >= 0) { + instance.toggleHierarchyState(0, targetRow); + } + const maxScrollTop = Math.max(0, instance.getAllRowsHeight() - instance.scenegraph.height); + const after = { + scrollTop: instance.scrollTop, + maxScrollTop, + diff: maxScrollTop - instance.scrollTop, + targetRow + }; + // eslint-disable-next-line no-console + console.log('scroll-collapse-bottom', { before, after }); + return { before, after }; + }; + }); +} diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index 9b1f6fa603..2b90485f98 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -33,6 +33,18 @@ export const menus = [ { path: 'debug', name: 'issue-5114' + }, + { + path: 'debug', + name: 'issue-5146' + }, + { + path: 'debug', + name: 'issue-5027' + }, + { + path: 'debug', + name: 'scroll-collapse-bottom' } ] }, diff --git a/packages/vtable/package.json b/packages/vtable/package.json index af18720687..142970aa2e 100644 --- a/packages/vtable/package.json +++ b/packages/vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable", - "version": "1.26.1", + "version": "1.26.2", "description": "canvas table width high performance", "keywords": [ "grid", @@ -52,7 +52,7 @@ "fix-memory-limit": "cross-env LIMIT=10240 increase-memory-limit" }, "dependencies": { - "@visactor/vtable-editors": "workspace:*", + "@visactor/vtable-editors": "workspace:1.26.2", "@visactor/vrender-core": "~1.0.41", "@visactor/vrender-kits": "~1.0.41", "@visactor/vrender-components": "~1.0.41", diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 1a79253a3f..070c74ee65 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -1056,10 +1056,32 @@ export class ListTable extends BaseTable implements ListTableAPI { } adjustHeightResizedRowMap(moveContext, this); } + this.syncColumnsStateFromLayoutMap(); return moveContext; } return null; } + private syncColumnsStateFromLayoutMap() { + const nextColumns = cloneDeepSpec(this.internalProps.columns, ['children']); + const cleanColumns = (columns: any[]) => { + columns.forEach(col => { + delete col.level; + delete col.size; + delete col.startInTotal; + delete col.startIndex; + delete col.id; + delete col.hierarchyState; + if (col.columns) { + cleanColumns(col.columns); + } + }); + }; + cleanColumns(nextColumns); + this.options.columns = nextColumns; + if (this.options.header) { + this.options.header = nextColumns; + } + } changeRecordOrder(sourceIndex: number, targetIndex: number) { if (this.transpose) { sourceIndex = this.getRecordShowIndexByCell(sourceIndex, 0); diff --git a/packages/vtable/src/PivotTable.ts b/packages/vtable/src/PivotTable.ts index f07f7583e1..bab1bc9290 100644 --- a/packages/vtable/src/PivotTable.ts +++ b/packages/vtable/src/PivotTable.ts @@ -1566,6 +1566,11 @@ export class PivotTable extends BaseTable implements PivotTableAPI { _refreshHierarchyState(col: number, row: number, recalculateColWidths: boolean = true, beforeUpdateCell?: Function) { const oldFrozenColCount = this.frozenColCount; const oldFrozenRowCount = this.frozenRowCount; + const sizeTolerance = this.options.customConfig?._disableColumnAndRowSizeRound ? 1 : 0; + const getMaxScrollTop = () => + Math.max(0, this.getAllRowsHeight() - (this.scenegraph?.height ?? this.tableNoFrameHeight) - sizeTolerance); + const oldMaxScrollTop = getMaxScrollTop(); + const isScrollToBottom = oldMaxScrollTop > 0 && this.scrollTop >= oldMaxScrollTop - 1; const visibleStartRow = this.getBodyVisibleRowRange().rowStart; this.internalProps._oldRowCount = this.rowCount; this.internalProps._oldColCount = this.colCount; @@ -1640,10 +1645,19 @@ export class PivotTable extends BaseTable implements PivotTableAPI { this.clearCellStyleCache(); this.scenegraph.createSceneGraph(); this.scrollToRow(visibleStartRow); + if (isScrollToBottom) { + this.clearCorrectTimer(); + this.setScrollTop(Number.MAX_SAFE_INTEGER); + } // this.renderWithRecreateCells(); } this.reactCustomLayout?.updateAllCustomCell(); + if (isScrollToBottom) { + this.clearCorrectTimer(); + this.setScrollTop(Number.MAX_SAFE_INTEGER); + } + if (checkHasChart) { // 检查更新节点状态后总宽高未撑满autoFill是否在起作用 if (this.autoFillWidth && !notFillWidth) { diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index 90b058be22..45c880566a 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -2503,17 +2503,19 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { /** 获取表格body部分的显示单元格范围 */ getBodyVisibleCellRange() { const { scrollTop, scrollLeft } = this; - const frozenRowsHeight = this.getFrozenRowsHeight(); const frozenColsContentWidth = this.getFrozenColsContentWidth(); const frozenColsOffset = this.getFrozenColsOffset(); const bottomFrozenRowsHeight = this.getBottomFrozenRowsHeight(); const rightFrozenColsWidth = this.getRightFrozenColsWidth(); // 计算非冻结 - const { row: rowStart } = this.getRowAt(scrollTop + frozenRowsHeight + 1); + const rowStart = Math.max(this.getTargetRowAt(scrollTop + 1)?.row ?? -1, this.frozenRowCount); const { col: colStart } = this.getColAt(scrollLeft + frozenColsContentWidth + 1); const rowEnd = this.getAllRowsHeight() > this.tableNoFrameHeight - ? this.getRowAt(scrollTop + this.tableNoFrameHeight - 1 - bottomFrozenRowsHeight).row + ? Math.max( + this.getTargetRowAt(scrollTop + this.tableNoFrameHeight - 1 - bottomFrozenRowsHeight)?.row ?? -1, + rowStart + ) : this.rowCount - 1; const colEnd = this.getAllColsWidth() > this.tableNoFrameWidth @@ -2532,13 +2534,16 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { */ getBodyVisibleRowRange(start_deltaY: number = 0, end_deltaY: number = 0) { const { scrollTop } = this; - const frozenRowsHeight = this.getFrozenRowsHeight(); const bottomFrozenRowsHeight = this.getBottomFrozenRowsHeight(); // 计算非冻结 - const { row: rowStart } = this.getRowAt(scrollTop + frozenRowsHeight + 1 + start_deltaY); + const rowStart = Math.max(this.getTargetRowAt(scrollTop + 1 + start_deltaY)?.row ?? -1, this.frozenRowCount); const rowEnd = this.getAllRowsHeight() > this.tableNoFrameHeight - ? this.getRowAt(scrollTop + this.tableNoFrameHeight - 1 - bottomFrozenRowsHeight + end_deltaY).row + ? Math.max( + this.getTargetRowAt(scrollTop + this.tableNoFrameHeight - 1 - bottomFrozenRowsHeight + end_deltaY)?.row ?? + -1, + rowStart + ) : this.rowCount - 1; if (rowEnd < 0) { return null; diff --git a/packages/vtable/src/event/event.ts b/packages/vtable/src/event/event.ts index 198f5325ae..bae7c2fd28 100644 --- a/packages/vtable/src/event/event.ts +++ b/packages/vtable/src/event/event.ts @@ -551,6 +551,9 @@ export class EventManager { cellIsHeaderCheck(eventArgsSet: SceneEvent, update?: boolean): boolean { const { eventArgs } = eventArgsSet; + if (!eventArgs) { + return false; + } const { col, row, target } = eventArgs; if (!this.table.isHeader(col, row)) { return false; diff --git a/packages/vtable/src/layout/simple-header-layout.ts b/packages/vtable/src/layout/simple-header-layout.ts index a175d9b5e3..6fc8918093 100644 --- a/packages/vtable/src/layout/simple-header-layout.ts +++ b/packages/vtable/src/layout/simple-header-layout.ts @@ -1311,10 +1311,16 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { } //#endregion // #region 将_columns的列定义调整位置 同调整_headerCellIds逻辑 - const sourceColumns = this._columns.splice( - sourceCellRange.start.col - this.leftRowSeriesNumberColumnCount, - sourceSize - ); + const sourceColIndex = sourceCellRange.start.col - this.leftRowSeriesNumberColumnCount; + const targetColIndex = + target.col >= source.col + ? targetCellRange.end.col - this.leftRowSeriesNumberColumnCount + : targetCellRange.start.col - this.leftRowSeriesNumberColumnCount; + + const sourceTotalIndex = (this._columns[sourceColIndex]?.define as any)?.startInTotal ?? sourceColIndex; + const targetTotalIndex = (this._columns[targetColIndex]?.define as any)?.startInTotal ?? targetColIndex; + + const sourceColumns = this._columns.splice(sourceColIndex, sourceSize); sourceColumns.unshift((targetIndex - this.leftRowSeriesNumberColumnCount) as any, 0 as any); Array.prototype.splice.apply(this._columns, sourceColumns); //#region 设置了maintainArrayDataOrder 需要调整columnTree中的field值 @@ -1327,11 +1333,7 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { //#endregion //#endregion // #region 对表头columnTree调整节点位置 - this.columnTree.movePosition( - sourceCellRange.start.row, - sourceCellRange.start.col - this.leftRowSeriesNumberColumnCount, - targetIndex - this.leftRowSeriesNumberColumnCount - ); + this.columnTree.movePosition(sourceCellRange.start.row, sourceTotalIndex, targetTotalIndex); //#region 设置了maintainArrayDataOrder 需要调整columnTree中的field值 if (this._table.options.dragOrder?.maintainArrayDataOrder) { //重新整理column中的field值 @@ -1381,15 +1383,21 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { Array.prototype.splice.apply(this._headerCellIds[row], sourceIds); } //将_columns的列定义调整位置 同调整_headerCellIds逻辑 - const sourceColumns = this._columns.splice(sourceCellRange.start.row, sourceSize); + const sourceRowIndex = sourceCellRange.start.row; + const targetRowIndex = target.row >= source.row ? targetCellRange.end.row : targetCellRange.start.row; + + const sourceTotalIndex = (this._columns[sourceRowIndex]?.define as any)?.startInTotal ?? sourceRowIndex; + const targetTotalIndex = (this._columns[targetRowIndex]?.define as any)?.startInTotal ?? targetRowIndex; + + const sourceColumns = this._columns.splice(sourceRowIndex, sourceSize); sourceColumns.unshift(targetIndex as any, 0 as any); Array.prototype.splice.apply(this._columns, sourceColumns); // 对表头columnTree调整节点位置 this.columnTree.movePosition( sourceCellRange.start.col - this.leftRowSeriesNumberColumnCount, - sourceCellRange.start.row, - targetIndex + (target.row > source.row ? sourceCellRange.end.row - sourceCellRange.start.row : 0) + sourceTotalIndex, + targetTotalIndex ); this.columnTree.reset(this.columnTree.tree.children); this._cellRangeMap = new Map(); diff --git a/packages/vtable/src/plugins/list-tree-stick-cell.ts b/packages/vtable/src/plugins/list-tree-stick-cell.ts index af05f37134..93c82918b1 100644 --- a/packages/vtable/src/plugins/list-tree-stick-cell.ts +++ b/packages/vtable/src/plugins/list-tree-stick-cell.ts @@ -6,6 +6,8 @@ import type { Graphic } from '@src/vrender'; import { createRect } from '@src/vrender'; import { Factory } from '../core/factory'; import { getTargetCell } from '../event/util'; +import { getIconAndPositionFromTarget } from '../scenegraph/utils/icon'; +import { IconFuncTypeEnum } from '../ts-types'; export interface IListTreeStickCellPlugin { new (table: ListTable): ListTreeStickCellPlugin; @@ -247,7 +249,18 @@ function prepareShadowRoot(table: ListTable) { const titleRows = table.listTreeStickCellPlugin.titleRows; const { shadowTarget } = e.pickParams; const cellGroup = getTargetCell(shadowTarget); - const { col, row } = cellGroup; + if (!cellGroup) { + return; + } + const iconInfo = getIconAndPositionFromTarget(shadowTarget); + const funcType = iconInfo?.icon?.attribute?.funcType; + if (funcType === IconFuncTypeEnum.collapse || funcType === IconFuncTypeEnum.expand) { + const stickCellTop = getStickCellTop(cellGroup, table); + table.toggleHierarchyState(cellGroup.col, cellGroup.row); + keepRowAtVisiblePosition(cellGroup.col, cellGroup.row, stickCellTop, table); + return; + } + const { row } = cellGroup; const rowIndex = titleRows.indexOf(row); // table.scrollToCell({ col, row: row - rowIndex }); scrollToRow(row - rowIndex, table); @@ -271,7 +284,18 @@ function prepareShadowRoot(table: ListTable) { const titleRows = table.listTreeStickCellPlugin.titleRows; const { shadowTarget } = e.pickParams; const cellGroup = getTargetCell(shadowTarget); - const { col, row } = cellGroup; + if (!cellGroup) { + return; + } + const iconInfo = getIconAndPositionFromTarget(shadowTarget); + const funcType = iconInfo?.icon?.attribute?.funcType; + if (funcType === IconFuncTypeEnum.collapse || funcType === IconFuncTypeEnum.expand) { + const stickCellTop = getStickCellTop(cellGroup, table); + table.toggleHierarchyState(cellGroup.col, cellGroup.row); + keepRowAtVisiblePosition(cellGroup.col, cellGroup.row, stickCellTop, table); + return; + } + const { row } = cellGroup; const rowIndex = titleRows.indexOf(row); // table.scrollToCell({ col, row: row - rowIndex }); scrollToRow(row - rowIndex, table); @@ -297,6 +321,21 @@ function scrollToRow(row: number, table: ListTable) { table.scenegraph.updateNextFrame(); } +function getStickCellTop(cellGroup: Group, table: ListTable) { + return table.getFrozenRowsHeight() + (cellGroup.attribute?.y ?? 0); +} + +function keepRowAtVisiblePosition(col: number, row: number, visibleTop: number, table: ListTable) { + const drawRange = table.getDrawRange(); + // Keep the clicked sticky group row in roughly the same viewport position after collapse/expand. + const targetScrollTop = Math.max( + 0, + Math.min(table.getCellRect(col, row).top - visibleTop, table.getAllRowsHeight() - drawRange.height) + ); + table.scrollTop = targetScrollTop; + table.scenegraph.updateNextFrame(); +} + export const registerListTreeStickCellPlugin = () => { Factory.registerComponent('listTreeStickCellPlugin', ListTreeStickCellPlugin); }; diff --git a/packages/vtable/src/state/select/update-position.ts b/packages/vtable/src/state/select/update-position.ts index 083ebd0aa1..02c93abf73 100644 --- a/packages/vtable/src/state/select/update-position.ts +++ b/packages/vtable/src/state/select/update-position.ts @@ -145,6 +145,9 @@ export function updateSelectPosition( if (!enableShiftSelectMode) { currentRange.end = currentRange.start; } + // Keep the focus cell in sync with the latest keyboard expansion target. + cellPos.col = col; + cellPos.row = row; scenegraph.deleteLastSelectedRangeComponents(); scenegraph.updateCellSelectBorder(currentRange); // } else if (isCtrl) { diff --git a/packages/vue-vtable/package.json b/packages/vue-vtable/package.json index 4bd2f9883b..db7b9ee66c 100644 --- a/packages/vue-vtable/package.json +++ b/packages/vue-vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vue-vtable", - "version": "1.26.1", + "version": "1.26.2", "description": "The vue version of VTable", "keywords": [ "vue", @@ -59,7 +59,7 @@ "access": "public" }, "dependencies": { - "@visactor/vtable": "workspace:*", + "@visactor/vtable": "workspace:1.26.2", "@visactor/vutils": "~1.0.17" }, "devDependencies": { @@ -107,5 +107,10 @@ "axios": "^1.4.0", "eslint-plugin-vue": "^9.26.0", "vue-eslint-parser": "^9.4.2" + }, + "repository": { + "type": "git", + "url": "https://github.com/VisActor/VTable.git", + "directory": "packages/vue-vtable" } } diff --git a/tools/bugserver-trigger/package.json b/tools/bugserver-trigger/package.json index c8ba84d000..256bc5f2b1 100644 --- a/tools/bugserver-trigger/package.json +++ b/tools/bugserver-trigger/package.json @@ -8,11 +8,11 @@ "ci": "ts-node --transpileOnly --skipProject ./scripts/trigger-test.ts" }, "dependencies": { - "@visactor/vtable": "workspace:*", - "@visactor/vtable-gantt": "workspace:*", - "@visactor/vtable-editors": "workspace:*", - "@visactor/vtable-plugins": "workspace:*", - "@visactor/vtable-sheet": "workspace:*" + "@visactor/vtable": "workspace:1.26.2", + "@visactor/vtable-gantt": "workspace:1.26.2", + "@visactor/vtable-editors": "workspace:1.26.2", + "@visactor/vtable-plugins": "workspace:1.26.2", + "@visactor/vtable-sheet": "workspace:1.26.2" }, "devDependencies": { "@rushstack/eslint-patch": "~1.1.4", @@ -28,4 +28,4 @@ "eslint": "~8.18.0", "cross-env": "^7.0.3" } -} \ No newline at end of file +}