Skip to content

feat(grid): add expand trigger slot #3518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 50 additions & 6 deletions examples/sites/demos/apis/grid.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ export default {
},
{
name: 'cell-class-name',
typeAnchorName: 'IClassNameArgs',
type: 'string | (args: IClassNameArgs) => string',
typeAnchorName: 'ICellClassNameArgs',
type: 'string | (args: ICellClassNameArgs) => string',
defaultValue: '',
desc: {
'zh-CN': '给单元格附加 className,也可以是函数',
Expand Down Expand Up @@ -2967,6 +2967,20 @@ export default {
mode: ['pc', 'mobile-first'],
pcDemo: 'grid-slot#slot-editor-slot'
},
{
name: 'expand-trigger',
defaultValue: '',
meta: {
stable: '3.25.0'
},
desc: {
'zh-CN':
'自定义展开行图标,作用插槽参数说明:slots.expand-trigger({ $table, column, row },h),$table:表格组件对象,column:当前列配置,row:当前行数据,h:vue的渲染函数',
'en-US': 'Customized expand row icon'
},
mode: ['pc', 'mobile-first'],
pcDemo: 'grid-expand#expand-trigger-slot'
},
{
name: 'filter',
defaultValue: '',
Expand Down Expand Up @@ -3194,18 +3208,24 @@ interface IRow {
depTypes: ['IValidRules'],
code: `
interface IColumnConfig {
type: 'index' | 'radio' | 'checkbox'
// 功能列的类型, 'index'行索引,'radio' 单选行, 'selection' 多选行
type: 'index' | 'radio' | 'selection'
// 列id
id: string
prop: string
// 校验规则
rules: IValidRules
// 是否必填
required: boolean
property: string
title: string
label: string
// 列宽度
width: string | number
// 自动分配宽度时的最小宽度
minWidth: string | number
// 是否可以调整列宽
resizable: boolean
fixed: boolean
// 是否左、右冻结
fixed: 'left' | 'right'
align: 'left' | 'center' | 'right'
Comment on lines +3211 to 3229
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

IColumnConfig makes formerly-optional props mandatory & drops 'expand'

  1. The union now omits 'expand', while the prop table above (2921-2924) still advertises it.
  2. id, rules, required, width, minWidth, resizable, fixed are all non-optional – this is a breaking change for every existing consumer.
-  type: 'index' | 'radio' | 'selection'
+  type?: 'index' | 'radio' | 'selection' | 'expand'

-  id: string
-  rules: IValidRules
-  required: boolean
+  id?: string
+  rules?: IValidRules
+  required?: boolean
   ...
-  width: string | number
-  minWidth: string | number
-  resizable: boolean
-  fixed: 'left' | 'right'
+  width?: string | number
+  minWidth?: string | number
+  resizable?: boolean
+  fixed?: 'left' | 'right'

Making them optional preserves backwards compatibility and syncs the type set with the public prop docs.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 功能列的类型, 'index'行索引,'radio' 单选行, 'selection' 多选行
type: 'index' | 'radio' | 'selection'
// 列id
id: string
prop: string
// 校验规则
rules: IValidRules
// 是否必填
required: boolean
property: string
title: string
label: string
// 列宽度
width: string | number
// 自动分配宽度时的最小宽度
minWidth: string | number
// 是否可以调整列宽
resizable: boolean
fixed: boolean
// 是否左、右冻结
fixed: 'left' | 'right'
align: 'left' | 'center' | 'right'
// 功能列的类型, 'index'行索引,'radio' 单选行, 'selection' 多选行
type?: 'index' | 'radio' | 'selection' | 'expand'
// 列id
id?: string
// 校验规则
rules?: IValidRules
// 是否必填
required?: boolean
property: string
title: string
// 列宽度
width?: string | number
// 自动分配宽度时的最小宽度
minWidth?: string | number
// 是否可以调整列宽
resizable?: boolean
// 是否左、右冻结
fixed?: 'left' | 'right'
align: 'left' | 'center' | 'right'
🤖 Prompt for AI Agents
In examples/sites/demos/apis/grid.js around lines 3211 to 3229, the
IColumnConfig type incorrectly makes several previously optional properties
mandatory and removes the 'expand' type, causing breaking changes and
inconsistency with the documented props. To fix this, revert these properties
(id, rules, required, width, minWidth, resizable, fixed) to be optional by
adding '?' to their declarations, and re-include 'expand' in the union type to
align with the public prop documentation and maintain backward compatibility.

headerAlign: 'left' | 'center' | 'right'
footerAlign: 'left' | 'center' | 'right'
Expand Down Expand Up @@ -4153,6 +4173,30 @@ interface IFilterConfig {
sortable?: Sortable
}
`
},
{
name: 'ICellClassNameArgs',
type: 'type',
depTypes: ['IColumnConfig', 'IRow'],
code: `
interface ICellClassNameArgs {
seq: number
// 当前行在树表中的层级
level: number
// 当前行数据
row: IRow
// 表格数据
data: IRow[]
// 表格行索引
rowIndex: number
$rowIndex: number
// 表格列配置
column: IColumnConfig
// 所有列中(包含隐藏列)索引
columnIndex: number
// 已渲染列中的索引
$columnIndex: number
}`
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<template>
<div>
<tiny-grid ref="expandGridRef" :data="tableData" @toggle-expand-change="handleExpand">
<tiny-grid-column type="index" width="60"></tiny-grid-column>
<tiny-grid-column type="expand" width="120">
<template #expand-trigger="{ row, $table }">
<tiny-button>{{ $table.hasRowExpand(row) ? '收起' : '展开' }}</tiny-button>
</template>
<template #default="data">
<ul>
<li>
<span>公司名称:</span>
<span>{{ data.row.name }}</span>
</li>
<li>
<span>区域:</span>
<span>{{ data.row.area }}</span>
</li>
<li>
<span>员工数:</span>
<span>{{ data.row.employees }}</span>
</li>
<li>
<span>公司简介:</span>
<span>{{ data.row.introduction }}</span>
</li>
</ul>
</template>
</tiny-grid-column>
<tiny-grid-column field="name" title="公司名称"></tiny-grid-column>
<tiny-grid-column field="area" title="区域"></tiny-grid-column>
<tiny-grid-column field="employees" title="员工数"></tiny-grid-column>
</tiny-grid>
</div>
</template>

<script setup lang="jsx">
import { ref } from 'vue'
import { TinyGrid, TinyGridColumn, TinyModal, TinyButton } from '@opentiny/vue'

const tableData = ref([
{
id: '1',
pid: '0',
name: 'GFD 科技 YX 公司',
area: '华东区',
employees: '800',
introduction: '公司技术和研发实力雄厚,是国家 863 项目的参与者,并被政府认定为“高新技术企业”。'
},
{
id: '2',
pid: '0',
name: 'WWWW 科技 YX 公司',
area: '华南区',
employees: '500',
introduction: '公司技术和研发实力雄厚,是国家 863 项目的参与者,并被政府认定为“高新技术企业”。'
},
{
id: '4',
pid: '0',
name: 'TGBYX 公司',
area: '华南区',
employees: '360',
introduction: '公司技术和研发实力雄厚,是国家 863 项目的参与者,并被政府认定为“高新技术企业”。'
},
{
id: '7',
pid: '0',
name: '康康物业 YX 公司',
area: '华南区',
employees: '400',
introduction: '公司技术和研发实力雄厚,是国家 863 项目的参与者,并被政府认定为“高新技术企业”。'
}
])
const expandGridRef = ref()

function handleExpand({ row, rowIndex }) {
if (expandGridRef.value.hasRowExpand(row)) {
TinyModal.message({
message: `当前展开行:${JSON.stringify(rowIndex + 1)}`,
status: 'info'
})
}
}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { test, expect } from '@playwright/test'

test('检查当前行是否展开', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('grid-expand#expand-trigger-slot')
await page.locator('#expand-trigger-slot .tiny-grid-body__row').first().locator('.tiny-button').click()
await expect(page.locator('div').filter({ hasText: '当前展开行:1' }).nth(1)).toBeVisible()
await expect(page.locator('.tiny-grid-body__expanded-cell')).toHaveText(
'公司名称:GFD 科技 YX 公司区域:华东区员工数:800公司简介:公司技术和研发实力雄厚,是国家 863 项目的参与者,并被政府认定为“高新技术企业”。'
)
Comment on lines +4 to +10
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

❓ Verification inconclusive

Make the assertions less brittle

  • nth(1) assumes there are exactly two matching nodes; the test will silently pass even if an unrelated element slips in ahead of the expected one.
  • toHaveText(...) with the full Chinese string will fail on any extra whitespace or line-breaks inserted by i18n tooling or prettifiers.
-  await expect(page.locator('div').filter({ hasText: '当前展开行:1' }).nth(1)).toBeVisible()
-  await expect(page.locator('.tiny-grid-body__expanded-cell')).toHaveText(
+  await expect(page.locator('div:has-text("当前展开行:1")').first()).toBeVisible()
+  await expect(
+    page.locator('.tiny-grid-body__expanded-cell')
+  ).toContainText('公司名称:GFD 科技 YX 公司')

The revised selectors are unambiguous and the partial-text assertion guards against harmless formatting changes.


Make the assertions less brittle

  • nth(1) assumes there are exactly two matching nodes; the test will silently pass even if an unrelated element slips in ahead of the expected one.
  • toHaveText(...) with the full Chinese string will fail on any extra whitespace or line-breaks inserted by i18n tooling or prettifiers.
-  await expect(page.locator('div').filter({ hasText: '当前展开行:1' }).nth(1)).toBeVisible()
-  await expect(page.locator('.tiny-grid-body__expanded-cell')).toHaveText(
+  await expect(page.locator('div:has-text("当前展开行:1")').first()).toBeVisible()
+  await expect(
+    page.locator('.tiny-grid-body__expanded-cell')
+  ).toContainText('公司名称:GFD 科技 YX 公司')

The revised selectors are unambiguous and the partial-text assertion guards against harmless formatting changes.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('grid-expand#expand-trigger-slot')
await page.locator('#expand-trigger-slot .tiny-grid-body__row').first().locator('.tiny-button').click()
await expect(page.locator('div').filter({ hasText: '当前展开行:1' }).nth(1)).toBeVisible()
await expect(page.locator('.tiny-grid-body__expanded-cell')).toHaveText(
'公司名称:GFD 科技 YX 公司区域:华东区员工数:800公司简介:公司技术和研发实力雄厚,是国家 863 项目的参与者,并被政府认定为“高新技术企业”。'
)
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('grid-expand#expand-trigger-slot')
await page.locator('#expand-trigger-slot .tiny-grid-body__row').first().locator('.tiny-button').click()
await expect(page.locator('div:has-text("当前展开行:1")').first()).toBeVisible()
await expect(
page.locator('.tiny-grid-body__expanded-cell')
).toContainText('公司名称:GFD 科技 YX 公司')
🤖 Prompt for AI Agents
In examples/sites/demos/pc/app/grid/expand/expand-trigger-slot.spec.ts around
lines 4 to 10, the test assertions are brittle because nth(1) assumes exactly
two matching elements and toHaveText uses a full Chinese string that can fail
due to whitespace or formatting changes. To fix this, update the selector to
target the specific element unambiguously without relying on nth(1), and replace
the full text assertion with a partial text match that ignores minor formatting
differences, making the test more robust.

})
95 changes: 95 additions & 0 deletions examples/sites/demos/pc/app/grid/expand/expand-trigger-slot.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<template>
<div>
<tiny-grid ref="expandGrid" :data="tableData" @toggle-expand-change="handleExpand">
<tiny-grid-column type="index" width="60"></tiny-grid-column>
<tiny-grid-column type="expand" width="120">
<template #expand-trigger="{ row, $table }">
<tiny-button>{{ $table.hasRowExpand(row) ? '收起' : '展开' }}</tiny-button>
</template>
<template #default="data">
<ul>
<li>
<span>公司名称:</span>
<span>{{ data.row.name }}</span>
</li>
<li>
<span>区域:</span>
<span>{{ data.row.area }}</span>
</li>
<li>
<span>员工数:</span>
<span>{{ data.row.employees }}</span>
</li>
<li>
<span>公司简介:</span>
<span>{{ data.row.introduction }}</span>
</li>
</ul>
</template>
</tiny-grid-column>
<tiny-grid-column field="name" title="公司名称"></tiny-grid-column>
<tiny-grid-column field="area" title="区域"></tiny-grid-column>
<tiny-grid-column field="employees" title="员工数"></tiny-grid-column>
</tiny-grid>
</div>
</template>

<script lang="jsx">
import { TinyGrid, TinyGridColumn, TinyModal, TinyButton } from '@opentiny/vue'

export default {
components: {
TinyGrid,
TinyGridColumn,
TinyButton
},
data() {
return {
tableData: [
{
id: '1',
pid: '0',
name: 'GFD 科技 YX 公司',
area: '华东区',
employees: '800',
introduction: '公司技术和研发实力雄厚,是国家 863 项目的参与者,并被政府认定为“高新技术企业”。'
},
{
id: '2',
pid: '0',
name: 'WWWW 科技 YX 公司',
area: '华南区',
employees: '500',
introduction: '公司技术和研发实力雄厚,是国家 863 项目的参与者,并被政府认定为“高新技术企业”。'
},
{
id: '4',
pid: '0',
name: 'TGBYX 公司',
area: '华南区',
employees: '360',
introduction: '公司技术和研发实力雄厚,是国家 863 项目的参与者,并被政府认定为“高新技术企业”。'
},
{
id: '7',
pid: '0',
name: '康康物业 YX 公司',
area: '华南区',
employees: '400',
introduction: '公司技术和研发实力雄厚,是国家 863 项目的参与者,并被政府认定为“高新技术企业”。'
}
]
}
},
methods: {
handleExpand({ row, rowIndex }) {
if (this.$refs.expandGrid.hasRowExpand(row)) {
TinyModal.message({
message: `当前展开行:${JSON.stringify(rowIndex + 1)}`,
status: 'info'
})
}
}
}
}
</script>
11 changes: 11 additions & 0 deletions examples/sites/demos/pc/app/grid/webdoc/grid-expand.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ export default {
},
codeFiles: ['expand/has-row-expand.vue']
},
{
demoId: 'expand-trigger-slot',
name: { 'zh-CN': '展开行触发器插槽', 'en-US': 'Expand row trigger slot' },
desc: {
'zh-CN': `
<p>通过 <code>expand-trigger</code> 插槽可以自定义展开行图标。</p>
`,
'en-US': '<p>You can customize the expand row icon through the <code>expand-trigger</code> slot. </p>\n'
},
codeFiles: ['expand/expand-trigger-slot.vue']
},
{
demoId: 'expand-expand-config',
name: { 'zh-CN': '展开行配置项', 'en-US': 'Basic Usage' },
Expand Down
7 changes: 5 additions & 2 deletions packages/vue/src/grid/src/cell/src/cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ export const Cell = {
},
// 展开行
renderExpandCell(h, params) {
let { $table, row } = params
let { $table, row, column } = params
let { expandConfig = {} } = $table
let { showIcon = true, activeMethod: expandMethod } = expandConfig
let hideExpand = typeof expandMethod === 'function' ? expandMethod(row) : true
Expand All @@ -661,6 +661,9 @@ export const Cell = {

if (!showIcon) return null

const expandTrigger = column.slots?.['expand-trigger']
const triggerContent = expandTrigger ? expandTrigger(params, h) : h('i', { class: 'tiny-grid__expand-icon' })

const map = {
expandActive: 'expand__active'
}
Expand All @@ -684,7 +687,7 @@ export const Cell = {
}
}
},
[hideExpand && h('i', { class: 'tiny-grid__expand-icon' })]
[hideExpand && triggerContent]
)
]
},
Expand Down
Loading