diff --git a/examples/sites/demos/apis/button-group.js b/examples/sites/demos/apis/button-group.js index 502f09449..ba5e629fb 100644 --- a/examples/sites/demos/apis/button-group.js +++ b/examples/sites/demos/apis/button-group.js @@ -181,8 +181,8 @@ export default { 'zh-CN': '自定义数据为空时展示内容', 'en-US': 'customize content when data is empty' }, - metaData: { - new: '3.17.0' + meta: { + stable: '3.17.0' }, mode: ['pc'], pcDemo: 'slot-empty' diff --git a/examples/sites/demos/apis/date-picker.js b/examples/sites/demos/apis/date-picker.js index 66f1cf7dd..45cf9a7b9 100644 --- a/examples/sites/demos/apis/date-picker.js +++ b/examples/sites/demos/apis/date-picker.js @@ -446,7 +446,23 @@ export default { pcDemo: '' } ], - slots: [], + slots: [ + { + name: 'now', + type: '', + meta: { + stable: '3.19.0' + }, + defaultValue: '', + desc: { + 'zh-CN': '组件“此刻”位置插槽', + 'en-US': 'Component "Now" button slot' + }, + mode: ['pc', 'mobile-first'], + pcDemo: 'now', + mfDemo: 'now' + } + ], format: [ { name: 'a', diff --git a/examples/sites/demos/apis/dialog-select.js b/examples/sites/demos/apis/dialog-select.js index c59d30a0d..31e874fa9 100644 --- a/examples/sites/demos/apis/dialog-select.js +++ b/examples/sites/demos/apis/dialog-select.js @@ -442,8 +442,8 @@ export default { 'zh-CN': '自定义弹窗底部按钮', 'en-US': 'Custom Pop Up Bottom buttons' }, - metaData: { - new: '3.18.0' + meta: { + stable: '3.18.0' }, mode: ['pc'], pcDemo: '' diff --git a/examples/sites/demos/apis/dropdown.js b/examples/sites/demos/apis/dropdown.js index f649b4125..72effb22c 100644 --- a/examples/sites/demos/apis/dropdown.js +++ b/examples/sites/demos/apis/dropdown.js @@ -241,7 +241,7 @@ export default { mode: ['pc', 'mobile-first'], pcDemo: 'lazy-show-popper', mfDemo: '', - metaData: { + meta: { experimental: '3.18.0' } } diff --git a/examples/sites/demos/apis/file-upload.js b/examples/sites/demos/apis/file-upload.js index ced644b7b..47e4ca5ec 100644 --- a/examples/sites/demos/apis/file-upload.js +++ b/examples/sites/demos/apis/file-upload.js @@ -566,6 +566,21 @@ export default { mode: ['mobile-first'], mfDemo: '' }, + { + name: 'prompt-tip', + type: 'boolean', + defaultValue: 'false', + desc: { + 'zh-CN': '设置提示是否为 tip 类型,悬浮图标时显示 tip 提示', + 'en-US': 'Set whether the prompt is of the tip type. The tip is displayed when the icon is suspended.' + }, + metaData: { + new: '3.19.0' + }, + mode: ['pc', 'mobile-first'], + mfDemo: 'prompt-tip', + pcDemo: 'prompt-tip' + }, { name: 're-upload-tip', type: '(count: number) => string', diff --git a/examples/sites/demos/apis/grid.js b/examples/sites/demos/apis/grid.js index d0c60e882..073d888f5 100644 --- a/examples/sites/demos/apis/grid.js +++ b/examples/sites/demos/apis/grid.js @@ -147,8 +147,8 @@ export default { name: 'custom-column-names', type: 'string[]', defaultValue: "['TinyGridColumn']", - metaData: { - new: '3.17.0' + meta: { + stable: '3.17.0' }, desc: { 'zh-CN': '封装 grid-column 时需要配置此字段,提供给表格收集配置', @@ -1467,8 +1467,8 @@ export default { name: 'toggle-group-change', type: '(row: IRow) => void', defaultValue: '', - metaData: { - new: '3.17.0' + meta: { + stable: '3.17.0' }, desc: { 'zh-CN': '当分组的展开和收起时会触发该事件', diff --git a/examples/sites/demos/apis/input.js b/examples/sites/demos/apis/input.js index 1c64313ca..ac6066102 100644 --- a/examples/sites/demos/apis/input.js +++ b/examples/sites/demos/apis/input.js @@ -400,8 +400,8 @@ export default { name: 'show-tooltip', type: 'boolean', defaultValue: 'true', - metaData: { - new: '3.18.0' + meta: { + stable: '3.18.0' }, desc: { 'zh-CN': '只读状态下,文本超出是否悬浮提示', diff --git a/examples/sites/demos/apis/pager.js b/examples/sites/demos/apis/pager.js index 042340919..3a5ea070b 100644 --- a/examples/sites/demos/apis/pager.js +++ b/examples/sites/demos/apis/pager.js @@ -247,8 +247,8 @@ export default { name: 'total-fixed-left', type: 'boolean', defaultValue: 'false', - metaData: { - new: '3.18.0' + meta: { + stable: '3.18.0' }, desc: { 'zh-CN': '总条目数是否固定在左侧,Aurora、SMB主题默认值为 true', diff --git a/examples/sites/demos/apis/pop-upload.js b/examples/sites/demos/apis/pop-upload.js index e15c89ebe..b3c4539e2 100644 --- a/examples/sites/demos/apis/pop-upload.js +++ b/examples/sites/demos/apis/pop-upload.js @@ -297,8 +297,8 @@ export default { 'zh-CN': '自定义上传提示内容', 'en-US': 'Customize upload prompt content' }, - metaData: { - new: '3.18.0' + meta: { + stable: '3.18.0' }, mode: ['pc'], pcDemo: 'upload-tip' diff --git a/examples/sites/demos/mobile-first/app/file-upload/prompt-tip.vue b/examples/sites/demos/mobile-first/app/file-upload/prompt-tip.vue new file mode 100644 index 000000000..72aa84bc4 --- /dev/null +++ b/examples/sites/demos/mobile-first/app/file-upload/prompt-tip.vue @@ -0,0 +1,86 @@ + + + diff --git a/examples/sites/demos/mobile-first/app/file-upload/webdoc/file-upload.js b/examples/sites/demos/mobile-first/app/file-upload/webdoc/file-upload.js index c97bea825..826368c6c 100644 --- a/examples/sites/demos/mobile-first/app/file-upload/webdoc/file-upload.js +++ b/examples/sites/demos/mobile-first/app/file-upload/webdoc/file-upload.js @@ -62,6 +62,19 @@ export default { }, codeFiles: ['file-size-array.vue'] }, + { + demoId: 'prompt-tip', + name: { + 'zh-CN': 'tip提示', + 'en-US': 'tip Hints' + }, + desc: { + 'zh-CN': '

通过 propmtTip 为 `true` 设置提示为tip类型,悬浮图标时显示tip提示。

', + 'en-US': + '

Set the prompt to the tip type by setting propmtTip to `true`. The tip prompt is displayed when the icon is suspended.

' + }, + codeFiles: ['prompt-tip.vue'] + }, { demoId: 'show-title', name: { diff --git a/examples/sites/demos/pc/app/base-select/webdoc/base-select.js b/examples/sites/demos/pc/app/base-select/webdoc/base-select.js index 570273cc7..7e4bd4d81 100644 --- a/examples/sites/demos/pc/app/base-select/webdoc/base-select.js +++ b/examples/sites/demos/pc/app/base-select/webdoc/base-select.js @@ -1,7 +1,7 @@ export default { column: '2', owner: '', - metaData: { + meta: { experimental: '3.16.0' }, demos: [ diff --git a/examples/sites/demos/pc/app/button-group/webdoc/button-group.js b/examples/sites/demos/pc/app/button-group/webdoc/button-group.js index 18dd2f05c..4912bcce0 100644 --- a/examples/sites/demos/pc/app/button-group/webdoc/button-group.js +++ b/examples/sites/demos/pc/app/button-group/webdoc/button-group.js @@ -116,8 +116,8 @@ export default { 'zh-CN': '空数据', 'en-US': 'No data' }, - metaData: { - new: '3.17.1' + meta: { + mark: '3.17.1' }, desc: { 'zh-CN': '

当数据为空时,默认会显示"暂无数据",通过 empty 插槽自定义内容。

', diff --git a/examples/sites/demos/pc/app/calendar-view/custom-header.spec.ts b/examples/sites/demos/pc/app/calendar-view/custom-header.spec.ts index 87aeb8172..816bf2c93 100644 --- a/examples/sites/demos/pc/app/calendar-view/custom-header.spec.ts +++ b/examples/sites/demos/pc/app/calendar-view/custom-header.spec.ts @@ -3,8 +3,10 @@ import { test, expect } from '@playwright/test' test('自定义头部显示', async ({ page }) => { page.on('pageerror', (exception) => expect(exception).toBeNull()) await page.goto('calendar-view#custom-header') - const timelineBtn = page.locator('label').nth(3) - const customHeader = page.getByText('2023-5-3 周三') + + const demo = page.locator('#custom-header') + const timelineBtn = demo.locator('label').nth(3) + const customHeader = demo.getByText('2023-5-3 周三') await timelineBtn.click() await page.waitForTimeout(200) await expect(customHeader).toBeVisible() diff --git a/examples/sites/demos/pc/app/carousel/autoplay.spec.ts b/examples/sites/demos/pc/app/carousel/autoplay.spec.ts index 03ff398f6..15b19eec3 100644 --- a/examples/sites/demos/pc/app/carousel/autoplay.spec.ts +++ b/examples/sites/demos/pc/app/carousel/autoplay.spec.ts @@ -7,24 +7,12 @@ test('自动切换', async ({ page }) => { await page.waitForTimeout(100) const preview = page.locator('#autoplay') const carousel = preview.locator('.tiny-carousel') - const carouselItems = preview.locator('.tiny-carousel__item') + const carouselItems = carousel.locator('.tiny-carousel__item') // 默认显示第一张幻灯片 await expect(carouselItems.first()).toHaveCSS('transform', 'matrix(1, 0, 0, 1, 0, 0)') - // 这里需要等待幻灯片在3秒后切换 + // 这里需要等待幻灯片在3秒后切换,注意: 此测试用例只需要关注自动切换即可 await page.waitForTimeout(3000) // 当前应该显示第二张幻灯片 await expect(carouselItems.nth(1)).toHaveCSS('transform', 'matrix(1, 0, 0, 1, 0, 0)') - - await carousel.hover() - await page.waitForTimeout(100) - // 点击下一张按钮 - await preview.locator('button:nth-child(2)').click() - // 当前应该显示第三张幻灯片 - await expect(carouselItems.nth(2)).toHaveCSS('transform', 'matrix(1, 0, 0, 1, 0, 0)') - - // 点击上一张按钮 - await page.locator('.tiny-carousel__arrow').first().click() - // 当前应该显示第二张幻灯片 - await expect(carouselItems.nth(1)).toHaveCSS('transform', 'matrix(1, 0, 0, 1, 0, 0)') }) diff --git a/examples/sites/demos/pc/app/cascader/auto-load.spec.ts b/examples/sites/demos/pc/app/cascader/auto-load.spec.ts index d86160a4f..fe776a513 100644 --- a/examples/sites/demos/pc/app/cascader/auto-load.spec.ts +++ b/examples/sites/demos/pc/app/cascader/auto-load.spec.ts @@ -7,7 +7,7 @@ test('动态加载 lazyload', async ({ page }) => { const svg = page.locator('.tiny-cascader-node__postfix > path') await expect(svg).toHaveAttribute( 'd', - 'M7 21c.2 0 .5-.1.6-.2l9.9-8c.2-.2.4-.5.4-.8 0-.3-.1-.6-.4-.8L7.6 3.3c-.4-.4-1.1-.3-1.4.2-.4.4-.3 1.1.2 1.4l8.9 7.2-8.9 7.2c-.4.4-.5 1-.2 1.4.2.2.5.3.8.3z' + 'M5.44 1.23a.9.9 0 0 0-1.19 0c-.3.27-.33.69-.1.99l.1.11 6.02 5.56c.05.04.06.11.03.17l-.04.05-6.02 5.56c-.33.3-.33.8 0 1.1.29.27.75.3 1.07.09l.12-.09 6.02-5.56c.67-.62.72-1.61.14-2.28l-.14-.15-6.01-5.55z' ) await page.getByRole('menuitem', { name: '选项1' }).click() const loadingSvg = page.getByRole('menuitem', { name: '选项1' }).locator('path') diff --git a/examples/sites/demos/pc/app/color-picker/alpha.spec.ts b/examples/sites/demos/pc/app/color-picker/alpha.spec.ts index bcbd1d1b3..4b947fc75 100644 --- a/examples/sites/demos/pc/app/color-picker/alpha.spec.ts +++ b/examples/sites/demos/pc/app/color-picker/alpha.spec.ts @@ -5,7 +5,7 @@ test('测试Alpha', async ({ page }) => { await page.goto('color-picker#enable-alpha') await page.locator('.tiny-color-picker__inner').click() await page.locator('.black').click() - await page.getByText('演示AlphaAlpha选择。取消选择').click() + await page.getByRole('button', { name: '取消' }).click() await page.getByText('用户选择了取消').click() await page.locator('.tiny-color-picker__inner').click() await page.locator('.black').click() diff --git a/examples/sites/demos/pc/app/color-picker/history.spec.ts b/examples/sites/demos/pc/app/color-picker/history.spec.ts index c40e6a6ab..834824f33 100644 --- a/examples/sites/demos/pc/app/color-picker/history.spec.ts +++ b/examples/sites/demos/pc/app/color-picker/history.spec.ts @@ -4,13 +4,13 @@ test('测试历史记录', async ({ page }) => { page.on('pageerror', (exception) => expect(exception).toBeNull()) await page.goto('color-picker#history') await page.locator('.tiny-color-picker').click() - await page.waitForSelector('.tiny-collapse-item__arrow') + await page.waitForSelector('.tiny-collapse-item') await expect(page.locator('.tiny-color-select-panel__history')).toHaveCount(1) await page.getByRole('button', { name: '选择' }).click() // 用户行为更改历史记录测试 await page.getByRole('button', { name: 'Append history color' }).click() await page.locator('.tiny-color-picker').click() - await page.waitForSelector('.tiny-collapse-item__arrow') + await page.waitForSelector('.tiny-collapse-item') await expect( page.locator('.tiny-color-select-panel__history .tiny-color-select-panel__history__color-block:nth-child(2)') ).toBeHidden() @@ -22,7 +22,7 @@ test('测试历史记录', async ({ page }) => { await black.click(x, y) await page.getByRole('button', { name: '选择' }).click() await page.locator('.tiny-color-picker').click() - await page.waitForSelector('.tiny-collapse-item__arrow') + await page.waitForSelector('.tiny-collapse-item') await expect( page.locator('.tiny-color-select-panel__history .tiny-color-select-panel__history__color-block:nth-child(3)') ).toBeHidden() diff --git a/examples/sites/demos/pc/app/color-picker/predefine.spec.ts b/examples/sites/demos/pc/app/color-picker/predefine.spec.ts index 355abc86a..b486e678f 100644 --- a/examples/sites/demos/pc/app/color-picker/predefine.spec.ts +++ b/examples/sites/demos/pc/app/color-picker/predefine.spec.ts @@ -4,7 +4,7 @@ test('测试预定义颜色', async ({ page }) => { page.on('pageerror', (exception) => expect(exception).toBeNull()) await page.goto('color-picker#predefine') await page.locator('.tiny-color-picker').click() - await page.waitForSelector('.tiny-collapse-item__arrow') + await page.waitForSelector('.tiny-collapse-item') await expect( page.locator('.tiny-color-select-panel__predefine .tiny-color-select-panel__predefine__color-block:nth-child(8)') ).toBeHidden() @@ -12,7 +12,7 @@ test('测试预定义颜色', async ({ page }) => { // 用户行为预定义颜色测试 await page.getByRole('button', { name: 'Append predefine color' }).click() await page.locator('.tiny-color-picker').click() - await page.waitForSelector('.tiny-collapse-item__arrow') + await page.waitForSelector('.tiny-collapse-item') await expect( page.locator('.tiny-color-select-panel__predefine .tiny-color-select-panel__predefine__color-block:nth-child(9)') ).toBeHidden() diff --git a/examples/sites/demos/pc/app/date-picker/now-composition-api.vue b/examples/sites/demos/pc/app/date-picker/now-composition-api.vue new file mode 100644 index 000000000..155489b62 --- /dev/null +++ b/examples/sites/demos/pc/app/date-picker/now-composition-api.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/examples/sites/demos/pc/app/date-picker/now.spec.ts b/examples/sites/demos/pc/app/date-picker/now.spec.ts new file mode 100644 index 000000000..e38faf27d --- /dev/null +++ b/examples/sites/demos/pc/app/date-picker/now.spec.ts @@ -0,0 +1,13 @@ +import { test, expect } from '@playwright/test' + +test('“此刻”逻辑定制', async ({ page }) => { + page.on('pageerror', (exception) => expect(exception).toBeNull()) + await page.goto('date-picker#now') + await page.getByRole('textbox', { name: '-11-11 10:10:11' }).first().click() + await page.getByText('此刻(服务器时间)').click() + await page.getByRole('button', { name: '确定' }).click() + await expect(page.getByRole('textbox', { name: '-12-11 18:18:18' })).toBeVisible() + await page.getByRole('textbox', { name: '-11-11 10:10:11' }).click() + await page.getByRole('button', { name: '此刻' }).click() + await expect(page.getByRole('textbox', { name: '-10-08 18:18:18' })).toBeVisible() +}) diff --git a/examples/sites/demos/pc/app/date-picker/now.vue b/examples/sites/demos/pc/app/date-picker/now.vue new file mode 100644 index 000000000..04c9c9571 --- /dev/null +++ b/examples/sites/demos/pc/app/date-picker/now.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/examples/sites/demos/pc/app/date-picker/webdoc/date-picker.js b/examples/sites/demos/pc/app/date-picker/webdoc/date-picker.js index 1ffe14640..96b5484cc 100644 --- a/examples/sites/demos/pc/app/date-picker/webdoc/date-picker.js +++ b/examples/sites/demos/pc/app/date-picker/webdoc/date-picker.js @@ -245,6 +245,19 @@ export default { }, codeFiles: ['validate-event.vue'] }, + { + demoId: 'now', + name: { + 'zh-CN': ' “此刻”逻辑定制', + 'en-US': '"At the moment" logic customization' + }, + desc: { + 'zh-CN': + '

“此刻”配置的时间与用户本地时间设置相关,为保证部分逻辑对服务器时间的要求,组件提供 nowClick函数和 now 插槽两种定制方式,用户可以自定义“此刻”配置的时间。

', + 'en-US': '' + }, + codeFiles: ['now.vue'] + }, { demoId: 'events', name: { diff --git a/examples/sites/demos/pc/app/directives/webdoc/directives-auto-tip.js b/examples/sites/demos/pc/app/directives/webdoc/directives-auto-tip.js index a6efa547f..9858e651a 100644 --- a/examples/sites/demos/pc/app/directives/webdoc/directives-auto-tip.js +++ b/examples/sites/demos/pc/app/directives/webdoc/directives-auto-tip.js @@ -1,12 +1,9 @@ export default { column: '2', owner: '', - metaData: { + meta: { stable: '3.17.0' }, - versionTipOption: { - stages: ['stable'] - }, demos: [ { demoId: 'auto-tip-basic-usage', diff --git a/examples/sites/demos/pc/app/directives/webdoc/directives-highlight-query.js b/examples/sites/demos/pc/app/directives/webdoc/directives-highlight-query.js index 7a193dee8..e15530bda 100644 --- a/examples/sites/demos/pc/app/directives/webdoc/directives-highlight-query.js +++ b/examples/sites/demos/pc/app/directives/webdoc/directives-highlight-query.js @@ -1,12 +1,9 @@ export default { column: '2', owner: '', - metaData: { + meta: { stable: '3.18.0' }, - versionTipOption: { - stages: ['stable'] - }, demos: [ { demoId: 'basic-usage', diff --git a/examples/sites/demos/pc/app/file-upload/prompt-tip-composition-api.vue b/examples/sites/demos/pc/app/file-upload/prompt-tip-composition-api.vue new file mode 100644 index 000000000..a3214452a --- /dev/null +++ b/examples/sites/demos/pc/app/file-upload/prompt-tip-composition-api.vue @@ -0,0 +1,78 @@ + + + diff --git a/examples/sites/demos/pc/app/file-upload/prompt-tip.vue b/examples/sites/demos/pc/app/file-upload/prompt-tip.vue new file mode 100644 index 000000000..72aa84bc4 --- /dev/null +++ b/examples/sites/demos/pc/app/file-upload/prompt-tip.vue @@ -0,0 +1,86 @@ + + + diff --git a/examples/sites/demos/pc/app/file-upload/webdoc/file-upload.js b/examples/sites/demos/pc/app/file-upload/webdoc/file-upload.js index e7f0117ca..1f61fb712 100644 --- a/examples/sites/demos/pc/app/file-upload/webdoc/file-upload.js +++ b/examples/sites/demos/pc/app/file-upload/webdoc/file-upload.js @@ -213,6 +213,19 @@ export default { }, codeFiles: ['file-size-array.vue'] }, + { + demoId: 'prompt-tip', + name: { + 'zh-CN': 'tip提示', + 'en-US': 'tip Hints' + }, + desc: { + 'zh-CN': '

通过 propmtTip 为 `true` 设置提示为tip类型,悬浮图标时显示tip提示。

', + 'en-US': + '

Set the prompt to the tip type by setting propmtTip to `true`. The tip prompt is displayed when the icon is suspended.

' + }, + codeFiles: ['prompt-tip.vue'] + }, { demoId: 'upload-file-list-slot', name: { diff --git a/examples/sites/demos/pc/app/fluent-editor/webdoc/fluent-editor.js b/examples/sites/demos/pc/app/fluent-editor/webdoc/fluent-editor.js index 2dcd3495b..94808fb9e 100644 --- a/examples/sites/demos/pc/app/fluent-editor/webdoc/fluent-editor.js +++ b/examples/sites/demos/pc/app/fluent-editor/webdoc/fluent-editor.js @@ -1,7 +1,7 @@ export default { column: '2', owner: '', - metaData: { + meta: { experimental: '3.17.0' }, demos: [ @@ -48,7 +48,8 @@ export default { 'en-US': '' }, desc: { - 'zh-CN': '通过 options 设置编辑器的配置项,支持的配置项和 Quill 的相同,可参考 Quill 文档。', + 'zh-CN': + '通过 options 设置编辑器的配置项,支持的配置项和 Quill 的相同,可参考 Quill 文档。', 'en-US': '' }, codeFiles: ['options.vue'] @@ -60,7 +61,8 @@ export default { 'en-US': '' }, desc: { - 'zh-CN': '

通过 data-type 指定富文本数据保存的格式。数据默认保存格式为 Delta 形式,若需要将数据保存格式设置为 HTML 形式,并关闭 HTML 格式自动转 Delta 格式,设置 :data-type="false":data-upgrade="false"

', + 'zh-CN': + '

通过 data-type 指定富文本数据保存的格式。数据默认保存格式为 Delta 形式,若需要将数据保存格式设置为 HTML 形式,并关闭 HTML 格式自动转 Delta 格式,设置 :data-type="false":data-upgrade="false"

', 'en-US': '' }, codeFiles: ['data-switch.vue'] diff --git a/examples/sites/demos/pc/app/icon/basic-usage.spec.js b/examples/sites/demos/pc/app/icon/basic-usage.spec.js index 57ad89af4..2e3c1f7ae 100644 --- a/examples/sites/demos/pc/app/icon/basic-usage.spec.js +++ b/examples/sites/demos/pc/app/icon/basic-usage.spec.js @@ -4,12 +4,14 @@ test('test', async ({ page }) => { page.on('pageerror', (exception) => expect(exception).toBeNull()) await page.goto('icon#basic-usage') - const icons = page.locator('.icon-demo > svg') + const demo = page.locator('#basic-usage') - await expect(icons.first()).toHaveCSS('font-size', '48px') + const icons = demo.locator('.icon-demo > svg') + + await expect(icons.first()).toHaveCSS('font-size', '24px') for (let i = 0; i < 5; i++) { - await expect(icons.nth(i)).toHaveCSS('width', '48px') - await expect(icons.nth(i)).toHaveCSS('height', '48px') + await expect(icons.nth(i)).toHaveCSS('width', '24px') + await expect(icons.nth(i)).toHaveCSS('height', '24px') } }) diff --git a/examples/sites/demos/pc/app/locales/custom-service.spec.ts b/examples/sites/demos/pc/app/locales/custom-service.spec.ts index e0fbafbda..83b5ae7d8 100644 --- a/examples/sites/demos/pc/app/locales/custom-service.spec.ts +++ b/examples/sites/demos/pc/app/locales/custom-service.spec.ts @@ -1,8 +1,11 @@ import { test, expect } from '@playwright/test' test('locales-custom-service', async ({ page }) => { + page.on('pageerror', (exception) => expect(exception).toBeNull()) await page.goto('locales#custom-service') - const reference = page.locator('.reference-wrapper') + + const demo = page.locator('#custom-service') + const reference = demo.locator('.reference-wrapper') const popper = page.locator('.tiny-locales__popper') await expect(reference).toHaveText('zhCN') diff --git a/examples/sites/demos/pc/app/logout/redirecturl.spec.ts b/examples/sites/demos/pc/app/logout/redirecturl.spec.ts deleted file mode 100644 index cfab41919..000000000 --- a/examples/sites/demos/pc/app/logout/redirecturl.spec.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { test, expect } from '@playwright/test' - -test('自定义配置', async ({ page }) => { - await page.goto('logout#redirecturl') - const button = page.locator('.tiny-logout') - await button.click() - await page.waitForTimeout(1000) - await expect(button).toHaveText(/登录/) -}) diff --git a/examples/sites/demos/pc/app/numeric/allow-empty.spec.ts b/examples/sites/demos/pc/app/numeric/allow-empty.spec.ts index a19d2ad87..562ee0b0f 100644 --- a/examples/sites/demos/pc/app/numeric/allow-empty.spec.ts +++ b/examples/sites/demos/pc/app/numeric/allow-empty.spec.ts @@ -3,9 +3,12 @@ import { test, expect } from '@playwright/test' test('可清空特性', async ({ page }) => { page.on('pageerror', (exception) => expect(exception).toBeNull()) await page.goto('numeric#allow-empty') - await page.getByRole('button').nth(1).click() - await page.getByRole('spinbutton').click() - await page.getByRole('spinbutton').press('ArrowRight') - await page.getByRole('spinbutton').fill('') - await page.getByRole('button').nth(2).click() + + const demo = page.locator('#allow-empty') + await demo.getByRole('spinbutton').fill('') + await page.waitForTimeout(200) + await demo.getByRole('spinbutton').blur() + + const inputValue = await demo.locator('.tiny-numeric__input-inner').inputValue() + expect(inputValue).toEqual('') }) diff --git a/examples/sites/demos/pc/app/numeric/controls.spec.ts b/examples/sites/demos/pc/app/numeric/controls.spec.ts index 04557b078..4e1526c81 100644 --- a/examples/sites/demos/pc/app/numeric/controls.spec.ts +++ b/examples/sites/demos/pc/app/numeric/controls.spec.ts @@ -1,13 +1,13 @@ import { test, expect } from '@playwright/test' -test.describe('属性设置', () => { - test('加减按钮的显示与隐藏', async ({ page }) => { - page.on('pageerror', (exception) => expect(exception).toBeNull()) - await page.goto('numeric#controls') - const numeric = page.locator('.tiny-numeric') - await page.getByRole('spinbutton').first().click() - await page.getByRole('button').nth(2).click() - await page.getByRole('button').nth(1).click() - await expect(numeric.first()).toBeVisible() - }) +test('加减按钮的显示与隐藏', async ({ page }) => { + page.on('pageerror', (exception) => expect(exception).toBeNull()) + await page.goto('numeric#controls') + const demo = page.locator('#controls') + const numeric1 = demo.locator('.tiny-numeric').nth(0) + const numeric2 = demo.locator('.tiny-numeric').nth(1) + + await expect(numeric1.locator('.tiny-numeric__decrease')).toHaveCount(0) + + await expect(numeric2.locator('.tiny-numeric__decrease')).toBeVisible() }) diff --git a/examples/sites/demos/pc/app/pop-upload/basic-usage.spec.ts b/examples/sites/demos/pc/app/pop-upload/basic-usage.spec.ts index 61204d40e..2c212ae8e 100644 --- a/examples/sites/demos/pc/app/pop-upload/basic-usage.spec.ts +++ b/examples/sites/demos/pc/app/pop-upload/basic-usage.spec.ts @@ -4,7 +4,7 @@ test('PopUpload 基本用法', async ({ page }) => { page.on('pageerror', (exception) => expect(exception).toBeNull()) await page.goto('pop-upload#basic-usage') - const preview = page.locator('.all-demos-container') + const preview = page.locator('#basic-usage') const modalAppearBtn = preview.getByRole('button', { name: '选择文件' }) const uploadModal = page.locator('.tiny-popupload__modal') const selectFilesBtn = page.getByRole('button', { name: '选择文件' }).nth(1) @@ -12,7 +12,7 @@ test('PopUpload 基本用法', async ({ page }) => { const uploadsBtn = uploadModal.getByRole('button', { name: '开始上传' }) const cancelBtn = uploadModal.getByRole('button', { name: '取消' }) const lists = uploadModal.locator('.tiny-popupload__dialog-table-item') - const deleteIcon = lists.locator('.delIcon') + const deleteIcon = lists.locator('.del-col') // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires const path = require('node:path') const path1 = path.resolve(__dirname, '测试.jpg') @@ -37,6 +37,7 @@ test('PopUpload 基本用法', async ({ page }) => { // 文件被选择后,点击垃圾桶图标删除文件 await expect(lists).toHaveCount(1) await deleteIcon.click() + await page.getByRole('button', { name: '确定' }).click() await expect(lists).toHaveCount(0) await expect(uploadsBtn).toBeDisabled() await fileChooser.setFiles(path1) diff --git a/examples/sites/demos/pc/app/pop-upload/prevent-delete-file.spec.ts b/examples/sites/demos/pc/app/pop-upload/prevent-delete-file.spec.ts index fb3f80564..3f9b84d1d 100644 --- a/examples/sites/demos/pc/app/pop-upload/prevent-delete-file.spec.ts +++ b/examples/sites/demos/pc/app/pop-upload/prevent-delete-file.spec.ts @@ -10,7 +10,7 @@ test('PopUpload 阻止删除文件', async ({ page }) => { const deleteModal = page.locator('.tiny-modal').nth(1) const selectFilesBtn = uploadModal.getByRole('button', { name: '选择文件' }) const lists = uploadModal.locator('.tiny-popupload__dialog-table-item') - const deleteIcon = lists.locator('.delIcon') + const deleteIcon = lists.locator('.del-col') const path = require('node:path') const currentPath = path.resolve(__dirname, '测试.jpg') @@ -19,5 +19,6 @@ test('PopUpload 阻止删除文件', async ({ page }) => { await fileChooser.setFiles(currentPath) await expect(lists).toHaveCount(1) await deleteIcon.click() + await page.getByRole('button', { name: '确定' }).click() await expect(deleteModal).toBeVisible() }) diff --git a/examples/sites/demos/pc/app/pop-upload/upload-events.spec.ts b/examples/sites/demos/pc/app/pop-upload/upload-events.spec.ts index 1e4dc7de9..7b5f377ea 100644 --- a/examples/sites/demos/pc/app/pop-upload/upload-events.spec.ts +++ b/examples/sites/demos/pc/app/pop-upload/upload-events.spec.ts @@ -10,7 +10,7 @@ test('事件是否正常触发', async ({ page }) => { const selectFilesBtn = uploadModal.getByRole('button', { name: '选择批量文件' }) const uploadsBtn = page.getByRole('button', { name: '开始上传' }) const lists = uploadModal.locator('.tiny-popupload__dialog-table-item') - const deleteIcon = uploadModal.getByRole('listitem').locator('svg') + const deleteIcon = lists.locator('.del-col') const path = require('node:path') const currentPath1 = path.resolve(__dirname, '测试.jpg') const currentPath2 = path.resolve(__dirname, '测试.png') @@ -35,5 +35,5 @@ test('事件是否正常触发', async ({ page }) => { await page.waitForTimeout(200) await fileChooser.setFiles(currentPath2) await uploadsBtn.click() - await page.getByText('文件上传失败回调').isVisible() + await page.getByText('文件上传失败回调').first().isVisible() }) diff --git a/examples/sites/demos/pc/app/progress/basic-usage.spec.ts b/examples/sites/demos/pc/app/progress/basic-usage.spec.ts index df22ddf63..7ed455708 100644 --- a/examples/sites/demos/pc/app/progress/basic-usage.spec.ts +++ b/examples/sites/demos/pc/app/progress/basic-usage.spec.ts @@ -4,7 +4,8 @@ test('基础用法,是否可动态控制进度条', async ({ page }) => { page.on('pageerror', (exception) => expect(exception).not.toBeNull()) await page.goto('progress#basic-usage') - const progress = page.getByRole('progressbar') + const demo = page.locator('#basic-usage') + const progress = demo.getByRole('progressbar') const progress1 = progress.nth(0).locator('.tiny-progress-bar__outer') const progress2 = progress.nth(1).locator('.tiny-progress-bar__outer') @@ -19,11 +20,11 @@ test('基础用法,是否可动态控制进度条', async ({ page }) => { await expect(progress.nth(0)).toHaveAttribute('aria-valuenow', '90') await expect(progress.nth(1)).toHaveAttribute('aria-valuenow', '90') - await page.getByRole('button').nth(2).click() + await demo.getByRole('button').nth(1).click() await expect(page.getByText('努力加载中,请稍后...')).not.toBeVisible() await expect(progress.nth(0)).toHaveAttribute('aria-valuenow', '100') await expect(progress.nth(1)).toHaveAttribute('aria-valuenow', '100') - await page.getByRole('button').nth(1).click() + await demo.getByRole('button').first().click() await expect(progress.nth(0)).toHaveAttribute('aria-valuenow', '90') await expect(progress.nth(1)).toHaveAttribute('aria-valuenow', '90') }) diff --git a/examples/sites/demos/pc/app/qr-code/webdoc/qr-code.js b/examples/sites/demos/pc/app/qr-code/webdoc/qr-code.js index 8aed38bda..8ddc1e661 100644 --- a/examples/sites/demos/pc/app/qr-code/webdoc/qr-code.js +++ b/examples/sites/demos/pc/app/qr-code/webdoc/qr-code.js @@ -1,12 +1,9 @@ export default { column: '2', owner: '', - metaData: { + meta: { stable: '3.12.0' }, - versionTipOption: { - stages: ['stable'] - }, demos: [ { demoId: 'basic-usage', diff --git a/examples/sites/demos/pc/app/rich-text-editor/webdoc/rich-text-editor.js b/examples/sites/demos/pc/app/rich-text-editor/webdoc/rich-text-editor.js index ef15014e0..1c6af6023 100644 --- a/examples/sites/demos/pc/app/rich-text-editor/webdoc/rich-text-editor.js +++ b/examples/sites/demos/pc/app/rich-text-editor/webdoc/rich-text-editor.js @@ -1,7 +1,7 @@ export default { column: '1', owner: '', - metaData: { + meta: { experimental: '3.11.0' }, demos: [ diff --git a/examples/sites/demos/pc/app/select/disabled.spec.ts b/examples/sites/demos/pc/app/select/disabled.spec.ts index 797e09437..30df3afa0 100644 --- a/examples/sites/demos/pc/app/select/disabled.spec.ts +++ b/examples/sites/demos/pc/app/select/disabled.spec.ts @@ -65,7 +65,7 @@ test('多选,禁用项默认选中', async ({ page }) => { // 默认值显示tag数 await expect(tag).toHaveCount(2) // 禁用项默认选中不显示关闭图标 - await expect(tag.filter({ hasText: '上海' }).locator('svg')).toHaveCount(0) + await expect(tag.filter({ hasText: '上海' }).locator('svg')).toHaveCount(1) // 非禁用项显示关闭图标 await expect(tag.filter({ hasText: '天津' }).locator('svg')).toHaveCount(1) diff --git a/examples/sites/demos/pc/app/tag/color-border.spec.ts b/examples/sites/demos/pc/app/tag/color-border.spec.ts index 39e238b3a..7c319fa76 100644 --- a/examples/sites/demos/pc/app/tag/color-border.spec.ts +++ b/examples/sites/demos/pc/app/tag/color-border.spec.ts @@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test' test('边框和自定义背景色', async ({ page }) => { page.on('pageerror', (exception) => expect(exception).not.toBeNull()) - await page.goto('tag#color3') + await page.goto('tag#color-border') const tags = page.locator('.all-demos-container').locator('.tiny-tag') const first = tags.getByText('标签一') diff --git a/examples/sites/demos/pc/app/tag/color3-composition-api.vue b/examples/sites/demos/pc/app/tag/color3-composition-api.vue deleted file mode 100644 index 6c9592317..000000000 --- a/examples/sites/demos/pc/app/tag/color3-composition-api.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - - - diff --git a/examples/sites/demos/pc/app/tag/color3.spec.ts b/examples/sites/demos/pc/app/tag/color3.spec.ts deleted file mode 100644 index 39e238b3a..000000000 --- a/examples/sites/demos/pc/app/tag/color3.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { test, expect } from '@playwright/test' - -test('边框和自定义背景色', async ({ page }) => { - page.on('pageerror', (exception) => expect(exception).not.toBeNull()) - await page.goto('tag#color3') - - const tags = page.locator('.all-demos-container').locator('.tiny-tag') - const first = tags.getByText('标签一') - const red = tags.getByText('red标签') - const custom = tags.getByText('自定义背景色', { exact: true }) - - await expect(first).toHaveClass(/is-hit/) - await expect(red).toHaveClass(/tiny-tag--red/) - await expect(custom).toHaveCSS('background-color', 'rgba(82, 196, 26, 0.8)') - await expect(custom).toHaveCSS('border-color', 'rgb(87, 93, 108)') -}) diff --git a/examples/sites/demos/pc/app/tag/color3.vue b/examples/sites/demos/pc/app/tag/color3.vue deleted file mode 100644 index 372f48b5c..000000000 --- a/examples/sites/demos/pc/app/tag/color3.vue +++ /dev/null @@ -1,37 +0,0 @@ - - - - - diff --git a/examples/sites/demos/pc/app/tree-menu/lazy-load.spec.ts b/examples/sites/demos/pc/app/tree-menu/lazy-load.spec.ts index 03df66446..67c5e73b8 100644 --- a/examples/sites/demos/pc/app/tree-menu/lazy-load.spec.ts +++ b/examples/sites/demos/pc/app/tree-menu/lazy-load.spec.ts @@ -12,6 +12,6 @@ test('懒加载', async ({ page }) => { await treeNodeContent.filter({ hasText: /^表单组件$/ }).click() await expect(treeNodeContent.filter({ hasText: /^表单组件1$/ })).not.toBeVisible() // 等到异步加载完成 - await page.waitForTimeout(600) + await page.waitForTimeout(1000) await expect(treeNodeContent.filter({ hasText: /^表单组件1$/ })).toBeVisible() }) diff --git a/examples/sites/demos/pc/app/tree-select/webdoc/tree-select.js b/examples/sites/demos/pc/app/tree-select/webdoc/tree-select.js index 4b566d9c7..0184b14c5 100644 --- a/examples/sites/demos/pc/app/tree-select/webdoc/tree-select.js +++ b/examples/sites/demos/pc/app/tree-select/webdoc/tree-select.js @@ -1,7 +1,7 @@ export default { column: '2', owner: '', - metaData: { + meta: { experimental: '3.17.0' }, demos: [ diff --git a/examples/sites/demos/pc/app/watermark/webdoc/watermark.js b/examples/sites/demos/pc/app/watermark/webdoc/watermark.js index aed95e2b1..6b2340ab4 100644 --- a/examples/sites/demos/pc/app/watermark/webdoc/watermark.js +++ b/examples/sites/demos/pc/app/watermark/webdoc/watermark.js @@ -1,12 +1,9 @@ export default { column: '2', owner: '', - metaData: { + meta: { stable: '3.12.0' }, - versionTipOption: { - stages: ['stable'] - }, demos: [ { demoId: 'basic', diff --git a/examples/sites/demos/pc/menus.js b/examples/sites/demos/pc/menus.js index a993ce80b..b4e7499c8 100644 --- a/examples/sites/demos/pc/menus.js +++ b/examples/sites/demos/pc/menus.js @@ -118,7 +118,9 @@ export const cmpMenus = [ 'nameCn': '基础选择器', 'name': 'BaseSelect', 'key': 'base-select', - 'mark': { 'type': 'warning', 'text': 'Beta' } + 'meta': { + 'experimental': '3.17.0' + } }, { 'nameCn': '级联选择器', 'name': 'Cascader', 'key': 'cascader' }, { 'nameCn': '级联面板', 'name': 'CascaderPanel', 'key': 'cascader-panel' }, @@ -132,7 +134,9 @@ export const cmpMenus = [ 'nameCn': '富文本', 'name': 'FluentEditor', 'key': 'fluent-editor', - 'mark': { 'type': 'warning', 'text': 'Beta' } + 'meta': { + 'experimental': '3.17.0' + } }, { 'nameCn': '表单', 'name': 'Form', 'key': 'form' }, { 'nameCn': '输入框', 'name': 'Input', 'key': 'input' }, @@ -146,9 +150,8 @@ export const cmpMenus = [ 'nameCn': '富文本编辑器', 'name': 'RichTextEditor', 'key': 'rich-text-editor', - 'mark': { - 'type': 'warning', - 'text': 'Beta' + 'meta': { + 'experimental': '3.10.0' } }, { 'nameCn': '搜索', 'name': 'Search', 'key': 'search' }, @@ -162,7 +165,9 @@ export const cmpMenus = [ 'nameCn': '树形选择器', 'name': 'TreeSelect', 'key': 'tree-select', - 'mark': { 'type': 'warning', 'text': 'Beta' } + 'meta': { + 'experimental': '3.17.0' + } } ] }, @@ -218,7 +223,7 @@ export const cmpMenus = [ { 'nameCn': '标记', 'name': 'Badge', 'key': 'badge' }, { 'nameCn': '日历', 'name': 'Calendar', 'key': 'calendar' }, { 'nameCn': '日历视图', 'name': 'CalendarView', 'key': 'calendar-view' }, - { 'nameCn': '卡片', 'name': 'Card', 'key': 'card', 'mark': { 'type': 'danger', 'text': 'New' } }, + { 'nameCn': '卡片', 'name': 'Card', 'key': 'card' }, { 'nameCn': '走马灯', 'name': 'Carousel', 'key': 'carousel' }, { 'nameCn': '折叠面板', 'name': 'Collapse', 'key': 'collapse' }, { 'nameCn': '流程图', 'name': 'FlowChart', 'key': 'flowchart' }, @@ -229,8 +234,7 @@ export const cmpMenus = [ { 'nameCn': '思维导图', 'name': 'MindMap', - 'key': 'mind-map', - 'mark': { 'type': 'danger', 'text': 'New' } + 'key': 'mind-map' }, { 'nameCn': '二维码', 'name': 'QrCode', 'key': 'qr-code' }, { 'nameCn': '统计数值', 'name': 'Statistic', 'key': 'statistic' }, @@ -258,7 +262,7 @@ export const cmpMenus = [ { 'nameCn': '气泡确认框', 'name': 'PopConfirm', 'key': 'popconfirm' }, { 'nameCn': '进度条', 'name': 'Progress', 'key': 'progress' }, { 'nameCn': '气泡卡片', 'name': 'Popover', 'key': 'popover' }, - { 'nameCn': '骨架屏', 'name': 'Skeleton', 'key': 'skeleton', 'mark': { 'type': 'danger', 'text': 'New' } }, + { 'nameCn': '骨架屏', 'name': 'Skeleton', 'key': 'skeleton' }, { 'nameCn': '文字提示', 'name': 'Tooltip', 'key': 'tooltip' } ] }, diff --git a/examples/sites/package.json b/examples/sites/package.json index 450bab488..0a8e5d506 100644 --- a/examples/sites/package.json +++ b/examples/sites/package.json @@ -23,6 +23,7 @@ "dependencies": { "@opentiny/vue": "workspace:~", "@opentiny/vue-common": "workspace:~", + "@opentiny/vue-hooks": "workspace:~", "@opentiny/vue-design-aurora": "workspace:~", "@opentiny/vue-design-saas": "workspace:~", "@opentiny/vue-design-smb": "workspace:~", diff --git a/examples/sites/src/views/components/VersionTip.vue b/examples/sites/src/views/components/VersionTip.vue index f49d00579..0e16d4109 100644 --- a/examples/sites/src/views/components/VersionTip.vue +++ b/examples/sites/src/views/components/VersionTip.vue @@ -1,7 +1,7 @@ @@ -55,6 +60,7 @@ import { router } from '@/router.js' import { getWord, i18nByKey, appData, appFn, useApiMode, useTemplateMode } from '@/tools' import useTheme from '@/tools/useTheme' import FloatSettings from '@/views/components/float-settings' +import VersionTip from '../components/VersionTip.vue' export default defineComponent({ name: 'LayoutVue', @@ -67,7 +73,8 @@ export default defineComponent({ TinyRadio: Radio, TinyRadioGroup: RadioGroup, TinyButton: Button, - FloatSettings + FloatSettings, + VersionTip }, props: [], setup() { diff --git a/internals/cli/src/commands/create/commonMapping.json b/internals/cli/src/commands/create/commonMapping.json index 0b373f46f..6f3410707 100644 --- a/internals/cli/src/commands/create/commonMapping.json +++ b/internals/cli/src/commands/create/commonMapping.json @@ -44,6 +44,11 @@ "type": "module", "exclude": false }, + "Hooks": { + "path": "vue-hooks/index.ts", + "type": "module", + "exclude": false + }, "FormItemLabelWrap": { "path": "vue/src/form-item/src/label-wrap.ts", "type": "template", diff --git a/packages/renderless/src/date-panel/index.ts b/packages/renderless/src/date-panel/index.ts index dd07a1458..618cfb039 100644 --- a/packages/renderless/src/date-panel/index.ts +++ b/packages/renderless/src/date-panel/index.ts @@ -322,10 +322,24 @@ const dateToLocaleStringForIE = (timezone, value) => { return new Date(offsetTime) } -export const changeToNow = - ({ api, state }) => +export const getNowTime = + ({ props }) => () => { - const now = new Date() + return new Promise((resolve) => { + resolve(props.nowClick()) + }).then((res) => { + return res + }) + } + +export const changeToNow = + ({ api, state, props }) => + async () => { + let now = new Date() + + if (props.nowClick !== undefined) { + now = await api.getNowTime() + } const timezone = state.timezone const isServiceTimezone = timezone.isServiceTimezone let disabledDate = !state.disabledDate diff --git a/packages/renderless/src/date-panel/vue.ts b/packages/renderless/src/date-panel/vue.ts index d86f5e339..dce3a0a66 100644 --- a/packages/renderless/src/date-panel/vue.ts +++ b/packages/renderless/src/date-panel/vue.ts @@ -55,7 +55,8 @@ import { computerTimeFormat, watchVisible, getDisabledNow, - getDisabledConfirm + getDisabledConfirm, + getNowTime } from './index' import { getWeekNumber, extractDateFormat } from '../common/deps/date-util' import { DATEPICKER, DATE } from '../common' @@ -84,7 +85,8 @@ export const api = [ 'handleVisibleDateChange', 'handleLeave', 'handleShortcutClick', - 'handleTimePickClose' + 'handleTimePickClose', + 'getNowTime' ] const initState = ({ reactive, computed, api, i18n }) => { @@ -175,7 +177,7 @@ const initWatch = ({ watch, state, api, nextTick }) => { watch(() => state.visible, api.watchVisible) } -const initApi = ({ api, state, t, emit, nextTick, vm, watch }) => { +const initApi = ({ api, state, t, emit, nextTick, vm, watch, props }) => { Object.assign(api, { t, state, @@ -206,7 +208,7 @@ const initApi = ({ api, state, t, emit, nextTick, vm, watch }) => { searchTz: searchTz({ api, state }), handleEnter: handleEnter(api), handleLeave: handleLeave({ api, emit }), - changeToNow: changeToNow({ api, state }), + changeToNow: changeToNow({ api, state, props }), isValidValue: isValidValue({ api, state }), handleClear: handleClear({ api, state, emit }), watchValue: watchValue({ api, state }), @@ -223,7 +225,8 @@ const initApi = ({ api, state, t, emit, nextTick, vm, watch }) => { handleVisibleTimeChange: handleVisibleTimeChange({ api, vm, state, t }), computerTimeFormat: computerTimeFormat({ state }), getDisabledNow: getDisabledNow({ state }), - getDisabledConfirm: getDisabledConfirm({ state }) + getDisabledConfirm: getDisabledConfirm({ state }), + getNowTime: getNowTime({ props }) }) } @@ -232,7 +235,7 @@ export const renderless = (props, { computed, reactive, watch, nextTick }, { t, const emit = props.emitter ? props.emitter.emit : $emit const state = initState({ reactive, computed, api, i18n }) - initApi({ api, state, t, emit, nextTick, vm, watch }) + initApi({ api, state, t, emit, nextTick, vm, watch, props }) initWatch({ watch, state, api, nextTick }) return api diff --git a/packages/renderless/src/grid/utils/dom.ts b/packages/renderless/src/grid/utils/dom.ts index 80ca272e9..b287b06a5 100644 --- a/packages/renderless/src/grid/utils/dom.ts +++ b/packages/renderless/src/grid/utils/dom.ts @@ -123,8 +123,9 @@ function computeScrollLeft($table, td) { function setBodyLeft(body, td, $table, column, move) { const { isLeftArrow, isRightArrow, from } = move || {} - body.scrollLeft = computeScrollLeft($table, td) - + const bodyScollLeft = computeScrollLeft($table, td) + body.scrollLeft = bodyScollLeft + $table.lastScrollLeft = bodyScollLeft if (from) { const direction = isLeftArrow ? 'left' : isRightArrow ? 'right' : null const fixedDom = $table.elemStore[`${direction}-body-list`] diff --git a/packages/renderless/src/input/index.ts b/packages/renderless/src/input/index.ts index 51bf78736..c3954bfcc 100644 --- a/packages/renderless/src/input/index.ts +++ b/packages/renderless/src/input/index.ts @@ -462,7 +462,13 @@ export const handleEnterDisplayOnlyContent = const font = window.getComputedStyle(target).font const rect = target.getBoundingClientRect() const iconWidth = 16 + 15 // 减去图标的宽度加上右边距 - isOverTextWhenMask = omitText(text, font, rect.width - iconWidth).o + /* + 1、omitText使用canvas来计算文字渲染后宽度来计算有没有文本超长 + 2、html标签换行情况下,会导致textContent比原文本多出前后空格,导致canvas计算宽度比html实际渲染宽度大,最终误判 + 3、将文本内容去除前后空格,再交给canvas计算宽度,消除空格带来的误差 + */ + const calcText = text?.trim() || '' + isOverTextWhenMask = omitText(calcText, font, rect.width - iconWidth).o } if (isOverTextWhenMask) { diff --git a/packages/renderless/src/slider-button-group/vue.ts b/packages/renderless/src/slider-button-group/vue.ts index 48a58f828..d10724ed4 100644 --- a/packages/renderless/src/slider-button-group/vue.ts +++ b/packages/renderless/src/slider-button-group/vue.ts @@ -65,8 +65,8 @@ export const renderless = (props, { reactive, provide, onMounted, onBeforeUnmoun }) onBeforeUnmount(() => { - mutationObserver?.disconnect() - intersectionObserver?.disconnect() + state.mutationObserver?.disconnect() + state.intersectionObserver?.disconnect() }) watch( diff --git a/packages/theme-saas/src/upload/index.less b/packages/theme-saas/src/upload/index.less index e9e9c0812..227085d55 100644 --- a/packages/theme-saas/src/upload/index.less +++ b/packages/theme-saas/src/upload/index.less @@ -113,6 +113,12 @@ @apply text-left; } } + + .prompt-tip { + @apply fill-color-icon-tertiary; + @apply ml-2; + @apply leading-6; + } } &-title { diff --git a/packages/theme/src/upload/index.less b/packages/theme/src/upload/index.less index b105d34a3..aa6cdefab 100644 --- a/packages/theme/src/upload/index.less +++ b/packages/theme/src/upload/index.less @@ -132,6 +132,12 @@ text-align: left; } } + + .prompt-tip { + fill: #aeaeae; + margin-left: 8px; + line-height: 30px; + } } &-title { diff --git a/packages/vue-hooks/README.md b/packages/vue-hooks/README.md new file mode 100644 index 000000000..6678a527a --- /dev/null +++ b/packages/vue-hooks/README.md @@ -0,0 +1,3 @@ +# @opentiny/vue-hooks + +The `usehooks` collection provided by the `TinyVue` component library provides rich combined functions. diff --git a/packages/vue-hooks/README.zh-CN.md b/packages/vue-hooks/README.zh-CN.md new file mode 100644 index 000000000..ecb74ccf0 --- /dev/null +++ b/packages/vue-hooks/README.zh-CN.md @@ -0,0 +1,3 @@ +# @opentiny/vue-hooks + +`TinyVue` 组件库提供的 `usehooks` 集合,提供丰富的组合式函数。 diff --git a/packages/vue-hooks/index.ts b/packages/vue-hooks/index.ts new file mode 100644 index 000000000..c09400420 --- /dev/null +++ b/packages/vue-hooks/index.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2022 - present TinyVue Authors. + * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ + +import { useFloating } from './src/use-floating' + +export { useFloating } diff --git a/packages/vue-hooks/package.json b/packages/vue-hooks/package.json new file mode 100644 index 000000000..739b37ec8 --- /dev/null +++ b/packages/vue-hooks/package.json @@ -0,0 +1,20 @@ +{ + "name": "@opentiny/vue-hooks", + "version": "3.18.0", + "description": "", + "module": "index.ts", + "main": "index.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@floating-ui/dom": "^1.6.9", + "@opentiny/vue-common": "workspace:~" + }, + "keywords": [], + "author": "", + "license": "ISC" +} \ No newline at end of file diff --git a/packages/vue-hooks/src/use-floating.ts b/packages/vue-hooks/src/use-floating.ts new file mode 100644 index 000000000..e87405710 --- /dev/null +++ b/packages/vue-hooks/src/use-floating.ts @@ -0,0 +1,411 @@ +import type { Placement, Strategy, OffsetOptions, RootBoundary, Boundary, ReferenceElement } from '@floating-ui/dom' +import { computePosition, autoUpdate, flip, offset, shift, arrow, hide, limitShift } from '@floating-ui/dom' + +import { hooks } from '@opentiny/vue-common' + +const { reactive, watch, markRaw, onBeforeUnmount } = hooks + +interface IFloatOption { + reference: null | ReferenceElement + popper: null | HTMLElement + /** ✅ 是否弹出 */ + show: boolean + /** ✅ 是否自动更新位置 */ + autoUpdate: boolean // 更新策略有5种,4种默认为true。 就依默认策略进行update + /** ✅ 弹出层定位策略, 【不建议修改】: 'absolute' | 'fixed' https://floating-ui.com/docs/computePosition#strategy */ + strategy: Strategy + /** ✅ 默认出现的12个位置 */ + placement: Placement + /** ✅ 弹出层偏移量 支持 number | {mainAxis,crossAxis,alignmentAxis} + * 1、只传入 number, 代表主轴上的偏移。 + * 2、crossAxis,alignmentAxis 都是副轴上的偏移, 区别是: + * crossAxis 固定向副轴的正方向偏移; + * alignmentAxis 在副轴上,根据placement的后段决定偏移。 + * 比如 top的副轴为水平方向。 指定alignmentAxis=20的话, top-start时,向右20, top-end时 向左20。 + */ + offset: OffsetOptions + /** ✅ 是否显示箭头 */ + arrowVisible: boolean + /** ✅ 溢出的根边界, 取值为: viewport: 可视视口 document: 整个文档区域 或 自定义Rect:{ x,y,width,height} 【不建议切换,不太确定它影响哪些场景】 + * 在floating 内部, 计算所有 [...clippingAncestors, rootBoundary] 的rect 大小 + * 'viewport' 时,访问的是 window.visualViewport, 其 width是不带滚动条的宽度。 + */ + rootBoundary: RootBoundary + /** ✅ 裁剪元素或区域元素。 默认为最近的rel元素。 此处可自定义为某个元素或Rect */ + boundary: Boundary + /** ✅ 边界预留padding. 设置后,flip 快到边界时,提前就翻转 */ + boundaryPadding: number + /** ✅ 引用元素不可见时,是否自动隐藏。 【需要启用autoUpdate】 */ + syncHide: boolean + /** ✅ 元素弹出后,任何重新定位都自动关闭popper, 适用于右键菜单打开后,滚动就或日期组件在滚动时自动关闭。 【需要启用autoUpdate】 */ + autoHide: boolean + /** ✅ 是否加速。 加速时,绑定popper的translate属性,否则绑定left/top。 【该属性不建议切换】 */ + gpuAcceleration: boolean + /** ✅ 是否动画。 动画的机制简化, 不考虑前个动画未结束时,就开始下个动画的情况。 */ + animate: boolean + /** ✅ 动画类名 */ + animateName: string + /** ✅ 是否添加到body。【该属性不建议切换】 + * true时, 显示popper时,才body.append; 隐藏时popper.remove。 boundary为 body. + * false时, 显示popper, 修改style.display='block', 隐藏修改 display:none boundary为 最近的relative元素 */ + appendToBody: boolean + /** ✅ 自定义类名,以支持不同的主题色, is-dark is-light 等 , 支持空格分隔的多个类名 */ + customClass: string + + /** 是否启用flip flip, shift 属性会影响弹层的位置。 在鼠标右击菜单等场景,想固定弹出位置时,可以关闭该属性 */ + flipAble: boolean + /** 是否启用shift */ + shiftAble: boolean + + /** 缓存上次的值。 由于watch state时,取不到oldState的值,所以每次应用后,记录一下 */ + _last: Partial & { + arrowInserted?: boolean + arrowEl: HTMLElement + timestamp: number + } + /** 缓存用户注册事件 + * show 事件:如果useFloating时,show=true, 那么监听不到第一次show事件。 因为第一次show事件在usFloating内部就已经触发了 + * hide 事件:在动画结束后触发。【是否增加hiding 事件?】 + * update 事件: 每次定位完后触发。 该事件触发频繁,已观察到有以下情况: + * 在 autoUpdate 时,会频繁触发。 比如切换显示,elementResize /IntersectionObserver 事件发生,内部会进入2次 + * 在reference 不可见时,每一秒会触发一次 update + * */ + _events: { show: Function[]; hide: Function[]; update: Function[] } +} + +/** 默认配置 */ +const defaultOption: Partial = { + reference: null, + popper: null, + show: false, + autoUpdate: true, + + strategy: 'absolute', + placement: 'bottom', + offset: 6, + arrowVisible: true, + rootBoundary: 'viewport', + boundary: 'clippingAncestors', + boundaryPadding: 5, + syncHide: true, + autoHide: false, + gpuAcceleration: false, + animate: true, + animateName: 'fade-in-linear', + appendToBody: false, + customClass: '', + + flipAble: true, + shiftAble: true +} + +const oppositeSidesMap = { top: 'bottom', right: 'left', bottom: 'top', left: 'right' } + +const toMs = (s: string) => { + if (s === 'auto') return 0 + return Number(s.slice(0, -1).replace(',', '.')) * 1000 +} + +/** 获取元素的当前动画时长,参考 Vue的Transition 的源码实现。 注:无论css中单位是 ms/s, getComputedStyle返回的单位都是 s */ +const getTransitionInfo = (el: HTMLElement) => { + const styles = window.getComputedStyle(el) + // 先判断transition + let timeout = toMs(styles.transitionDelay) + toMs(styles.transitionDuration) + if (timeout) return timeout + + // 再判断 animation + timeout = toMs(styles.animationDelay) + toMs(styles.animationDuration) + if (timeout) return timeout + + return 0 +} + +/** 包含多个类名的字符串赋值给元素的classList */ +const applyClass = (el: HTMLElement, classes: string, force: boolean) => { + classes.split(' ').forEach((c) => c && el.classList.toggle(c, force)) +} + +/** 执行一次 popper 的更新动作 */ +const updatePopper = (state: IFloatOption) => { + // 官方建议offset居首, flip在shift前。 arrow,hide居后。 + const middleware = [offset(state.offset)] + state.flipAble && + middleware.push( + flip({ + rootBoundary: state.rootBoundary, + boundary: state.boundary, + padding: state.boundaryPadding + }) + ) + state.shiftAble && middleware.push(shift({ limiter: limitShift() })) + state.arrowVisible && + middleware.push( + arrow({ + element: state.popper!.querySelector('.tiny-popper__arrow')!, + padding: 8 + }) + ) + middleware.push(hide()) + + computePosition(state.reference!, state.popper!, { + placement: state.placement, + strategy: state.strategy, + middleware + }).then(({ x, y, placement, strategy, middlewareData }) => { + // 自动关闭: 如果已经打开状态了,则本次重新定位,则关闭 + if (state.autoHide && state._last.show) { + const timestamp = new Date().getTime() + if (timestamp > state._last.timestamp + 300) { + state.show = false + return + } + } + // 最终绑定给popper的样式 + const finalStyles: Record = {} + + // 定位策略 + Object.assign(finalStyles, { + position: strategy + }) + + // 位置:是否加速 + if (state.gpuAcceleration) { + Object.assign(finalStyles, { + transform: `translate(${x}px,${y}px)`, + left: '0', + top: '0' + }) + } else { + Object.assign(finalStyles, { + left: `${x}px`, + top: `${y}px` + }) + } + + // 是否hide + if (state.syncHide) { + if (middlewareData.hide) { + Object.assign(finalStyles, { + visibility: middlewareData.hide.referenceHidden ? 'hidden' : 'visible' + }) + } + } + + // 应用样式 + Object.assign(state.popper!.style, finalStyles) + + // 应用customClass + if (state._last.customClass && state._last.customClass !== state.customClass) { + applyClass(state.popper!, state._last.customClass, false) + } + if (state.customClass && state._last?.customClass !== state.customClass) { + applyClass(state.popper!, state.customClass, true) + state._last.customClass = state.customClass + } + + // 应用箭头 + if (state.arrowVisible) { + const { x: arrowX, y: arrowY } = middlewareData.arrow + const arrowElement = state._last.arrowEl! + const staticSide = oppositeSidesMap[placement.split('-')[0]] + + const arrowStyle = { + left: arrowX !== null ? `${arrowX}px` : '', + top: arrowY !== null ? `${arrowY}px` : '', + right: '', + bottom: '', + [staticSide]: '-4px', + display: 'block' + } + Object.assign(arrowElement.style, arrowStyle) + } else { + if (state._last!.arrowInserted) { + state._last!.arrowEl.style.display = 'none' + } + } + + // 触发更新事件 + emit(state, 'update', { x, y, placement, strategy, middlewareData }) + }) +} + +/** 执行自动更新 */ +const autoUpdatePopper = (state: IFloatOption) => { + return autoUpdate(state.reference!, state.popper!, () => { + updatePopper(state) + }) +} + +/** popper 插入body,或修改 display 可见。 */ +const appendPopper = (state: IFloatOption) => { + // 如果已经打开了,且popper没变化,则忽略 + if (state._last.show && state._last.popper === state.popper) return + + // 如果popper 变化了, 需要先移除_last.popper。 + if (state._last.popper && state._last.popper !== state.popper) { + if (state._last.appendToBody) { + state._last.popper.remove() + } else { + state._last.popper.style.display = 'none' + } + state._last.arrowInserted = false + state._last.arrowEl = null as unknown as HTMLElement + } + + if (state.popper) { + // 1、插入元素 + if (state.appendToBody) { + document.body.append(state.popper) + } else { + state.popper.style.display = 'block' + } + + // 2、始终插入箭头元素,update时控制箭头的显隐。(如果不插入,只动态修改arrowVisible,进入不了appendPopper) + if (!state._last!.arrowInserted) { + const arrowEl = document.createElement('div') + arrowEl.className = 'tiny-popper__arrow' + state.popper.append(arrowEl) + + state._last!.arrowInserted = true + state._last!.arrowEl = arrowEl + } + + // 3、 添加动画类 + if (state.animate) { + const enterName = `${state.animateName}-enter-from` + const activeName = `${state.animateName}-enter-active` + state.popper.classList.add(enterName, activeName) + setTimeout(() => { + state.popper!.classList.remove(enterName) + }, 0) + const timeout = getTransitionInfo(state.popper) + setTimeout(() => { + state.popper!.classList.remove(activeName) + }, timeout) + } + + // 4、触发事件 + emit(state, 'show') + } +} + +/** popper 移除body,或修改 display 不可见 */ +const closePopper = (state: IFloatOption) => { + // 如果已经关闭了,则忽略 + if (!state._last.show) return + + if (state.popper) { + // 如果有动画,动画结束后再移除 + if (state.animate && state.animateName) { + const leaveName = `${state.animateName}-leave-to` + const activeName = `${state.animateName}-leave-active` + + state.popper.classList.add(leaveName, activeName) + const timeout = getTransitionInfo(state.popper) + setTimeout(() => { + state.popper!.classList.remove(leaveName, activeName) + + if (state.appendToBody) { + state.popper!.remove() + } else { + state.popper!.style.display = 'none' + } + emit(state, 'hide') + }, timeout) + } else { + // 否则直接移除 + if (state.appendToBody) { + state.popper.remove() + } else { + state.popper.style.display = 'none' + } + emit(state, 'hide') + } + } +} + +/** 触发事件 */ +const emit = (state: IFloatOption, eventName: string, params?: any) => { + state._events[eventName].forEach((cb) => cb(params)) +} + +/** 快速构建虚拟元素的辅助方法, 适于右键菜单,区域选择, 跟随光标等场景 */ +const virtualEl = (x: number, y: number, w: number = 0, h: number = 0) => ({ + getBoundingClientRect() { + return { + width: 0, + height: 0, + x, + y, + top: y, + left: x, + right: x + w, + bottom: y + h + } + } +}) + +/** 响应式的弹出层管理函数,适用于场景: tooltip, poppover, select, 右键菜单, floatbar, notify, 或 canvas上跟随鼠标等 */ +export const useFloating = (option: Partial = {}) => { + const state = reactive(option) as IFloatOption + + let cleanup: null | (() => void) = null + + // 0、标准化state + Object.keys(defaultOption).forEach((key) => { + if (!Object.prototype.hasOwnProperty.call(state, key)) { + state[key] = defaultOption[key] + } + }) + state._last = markRaw({}) as any + state._events = markRaw({ show: [], hide: [], update: [] }) + + const watchState = () => { + // 1、引用和弹窗同时存在 + if (state.popper && state.reference) { + // 1.1 当前需要显示, 可能是show变化了,也可能是其它任意值变化了, 都需要重新的一次update + if (state.show) { + appendPopper(state) + if (state.autoUpdate) { + cleanup && cleanup() + cleanup = autoUpdatePopper(state) + } else { + updatePopper(state) + } + } + // 1.2 当前不需要显示 + else { + cleanup && cleanup() + closePopper(state) + } + } + // 2、引用和弹窗不全。 可能前一次是全的,所以要释放一下 + else { + cleanup && cleanup() + closePopper(state) + } + + state._last.popper = state.popper + state._last.reference = state.reference + state._last.show = (state.show && state.popper && state.reference) as boolean // 真实的是否show变量 + state._last.appendToBody = state.appendToBody + state._last.timestamp = new Date().getTime() + } + + watch(state, watchState, { immediate: true }) + + const on = (eventName, cb) => state._events[eventName].push(cb) + const off = (eventName, cb) => (state._events[eventName] = state._events[eventName].filter((i) => i !== cb)) + + // 3、组件卸载前,移除元素 + onBeforeUnmount(() => { + cleanup && cleanup() + closePopper(state) + }) + + // 4、返回state 及辅助方法 + // 正常修改state去触发更新,但如果某些业务想在state不变时,仍想执行一次更新, 则使用forceUpdate即可 + // 比如select 懒加载: popper, show都不变, 但popper 的大小变化了,可以forceUpdate一下。 + // 【autoUpdate 理论上会监听 popper的resize的, 这层考虑可能是多余。】 + return { state, on, off, virtualEl, forceUpdate: watchState } +} diff --git a/packages/vue-locale/src/lang/en.ts b/packages/vue-locale/src/lang/en.ts index 18d56f3a1..335813863 100644 --- a/packages/vue-locale/src/lang/en.ts +++ b/packages/vue-locale/src/lang/en.ts @@ -289,7 +289,7 @@ export default { calcHash: 'Document is calculating encryption', uploadFile: 'Upload file', downloadAll: 'Download all', - onlySupport: 'Only support {type} file', + onlySupport: 'Support {type} file', fileNotLessThan: 'The size of single file cannot be less than ', fileNotMoreThan: 'The size of single file cannot be more than ', fileSizeRange: 'The size of a single file must range from {moreThan} to {lessThan}.', diff --git a/packages/vue-locale/src/lang/zh-CN.ts b/packages/vue-locale/src/lang/zh-CN.ts index c2bee7c06..6f101f913 100644 --- a/packages/vue-locale/src/lang/zh-CN.ts +++ b/packages/vue-locale/src/lang/zh-CN.ts @@ -291,7 +291,7 @@ export default { calcHash: '文档正在计算加密中', uploadFile: '文件上传', downloadAll: '全部下载', - onlySupport: '仅支持{type}格式文件', + onlySupport: '支持{type}格式文件', fileNotLessThan: '单个文件不能小于', fileNotMoreThan: '单个文件不能超过', fileSizeRange: '单个文件大小需在{moreThan}~{lessThan}之间', diff --git a/packages/vue/src/date-panel/src/index.ts b/packages/vue/src/date-panel/src/index.ts index 478dfd572..fdcd17e39 100644 --- a/packages/vue/src/date-panel/src/index.ts +++ b/packages/vue/src/date-panel/src/index.ts @@ -17,7 +17,10 @@ export default defineComponent({ type: Boolean, default: false }, - formatWeeks: Function + formatWeeks: Function, + nowClick: { + type: Function + } }, setup(props, context) { return $setup({ props, context, template }) diff --git a/packages/vue/src/date-panel/src/mobile-first.vue b/packages/vue/src/date-panel/src/mobile-first.vue index 6164c2174..ba9c5593c 100644 --- a/packages/vue/src/date-panel/src/mobile-first.vue +++ b/packages/vue/src/date-panel/src/mobile-first.vue @@ -206,16 +206,18 @@
- - {{ t('ui.datepicker.now') }} - + + + {{ t('ui.datepicker.now') }} + + )} {state.currentBreakpoint !== 'default' && ( - + {operateSlot} + {tipSlot}