#foot 插槽
- 自定义弹窗底部
+ 插槽自定义弹窗底部
{
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('modal#modal-header')
const demo = page.locator('#modal-header')
- const header = page.locator('.tiny-modal__title')
+ const header = page.locator('.tiny-modal.is__visible')
// 自定义弹窗标题
await demo.getByRole('button', { name: '自定义弹窗标题' }).first().click()
- await expect(header.first()).toHaveText(/自定义弹窗标题/)
+ await expect(header).toHaveText(/自定义弹窗标题/)
await page.getByRole('button', { name: '确定' }).click()
})
diff --git a/examples/sites/demos/pc/app/modal/modal-size.spec.ts b/examples/sites/demos/pc/app/modal/modal-size.spec.ts
index 9419f9d178..e75acb0231 100644
--- a/examples/sites/demos/pc/app/modal/modal-size.spec.ts
+++ b/examples/sites/demos/pc/app/modal/modal-size.spec.ts
@@ -3,13 +3,13 @@ import { test, expect } from '@playwright/test'
test('弹窗大小全屏', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('modal#modal-size')
- const modal = page.locator('.tiny-modal')
+ const modal = page.locator('.status__question')
const box = page.locator('.type__confirm > .tiny-modal__box')
await page.getByRole('button', { name: '自定义弹窗大小' }).first().click()
await expect(box.first()).toHaveCSS('width', '800px')
await page.getByRole('button', { name: '确定' }).click()
await page.getByRole('button', { name: '弹窗全屏' }).first().click()
- await expect(modal.nth(2)).toHaveClass(/is__maximize/)
+ await expect(modal).toHaveClass(/is__maximize/)
await page.getByRole('button', { name: '确定' }).click()
})
diff --git a/examples/sites/demos/pc/app/modal/status.spec.ts b/examples/sites/demos/pc/app/modal/status.spec.ts
index 16bae6d4e4..14c139bcb0 100644
--- a/examples/sites/demos/pc/app/modal/status.spec.ts
+++ b/examples/sites/demos/pc/app/modal/status.spec.ts
@@ -3,46 +3,53 @@ import { test, expect } from '@playwright/test'
test('状态和图标', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('modal#status')
- const modal = page.locator('.tiny-modal__status-wrapper svg').first()
- const message = page.locator('.type__message')
+ const modal = page.locator(
+ '.tiny-modal__wrapper.is__visible > .tiny-modal__box > .tiny-modal__header > .tiny-modal__status-wrapper > svg'
+ )
+ const message = page.locator('.type__message.is__visible')
// 信息提示图标
await page.getByRole('button', { name: '信息提示图标' }).first().click()
await expect(modal).toHaveClass(/tiny-modal-svg__info/)
await page.getByRole('button', { name: '确定' }).click()
- // ----消息状态示例----
- await page.getByRole('button', { name: '信息提示图标' }).nth(1).click()
- await expect(message).toHaveClass(/status__info/)
// 成功提示图标
await page.getByRole('button', { name: '成功提示图标' }).first().click()
await expect(modal).toHaveClass(/tiny-modal-svg__success/)
await page.getByRole('button', { name: '确定' }).click()
- // ----消息状态示例----
- await page.getByRole('button', { name: '成功提示图标' }).nth(1).click()
- await expect(message).toHaveClass(/status__success/)
// 警告提示图标
await page.getByRole('button', { name: '警告提示图标' }).first().click()
await expect(modal).toHaveClass(/tiny-modal-svg__warning/)
await page.getByRole('button', { name: '确定' }).click()
- // ----消息状态示例----
- await page.getByRole('button', { name: '警告提示图标' }).nth(1).click()
- await expect(message).toHaveClass(/status__warning/)
// 错误提示图标
await page.getByRole('button', { name: '错误提示图标' }).first().click()
await expect(modal).toHaveClass(/tiny-svg tiny-modal-svg__error/)
await page.getByRole('button', { name: '确定' }).click()
- // ----消息状态示例----
- await page.getByRole('button', { name: '错误提示图标' }).nth(1).click()
- await expect(message).toHaveClass(/status__error/)
// 加载提示图标
await page.getByRole('button', { name: '加载提示图标' }).first().click()
await expect(modal).toHaveClass(/tiny-modal-svg__refresh/)
await page.getByRole('button', { name: '确定' }).click()
+
+ // ----消息状态示例----
+ await page.getByRole('button', { name: '信息提示图标' }).nth(1).click()
+ await expect(message).toHaveClass(/status__info/)
+
+ // ----消息状态示例----
+ await page.getByRole('button', { name: '成功提示图标' }).nth(1).click()
+ await expect(message.nth(1)).toHaveClass(/status__success/)
+
+ // ----消息状态示例----
+ await page.getByRole('button', { name: '警告提示图标' }).nth(1).click()
+ await expect(message.nth(2)).toHaveClass(/status__warning/)
+
+ // ----消息状态示例----
+ await page.getByRole('button', { name: '错误提示图标' }).nth(1).click()
+ await expect(message.nth(3)).toHaveClass(/status__error/)
+
// ----消息状态示例----
await page.getByRole('button', { name: '加载提示图标' }).nth(1).click()
- await expect(message).toHaveClass(/status__loading/)
+ await expect(message.nth(4)).toHaveClass(/status__loading/)
})
diff --git a/packages/renderless/src/modal/vue.ts b/packages/renderless/src/modal/vue.ts
index bc84b1ae81..d7b67099bc 100644
--- a/packages/renderless/src/modal/vue.ts
+++ b/packages/renderless/src/modal/vue.ts
@@ -44,6 +44,7 @@ import {
hideScrollbar,
watchVisible
} from './index'
+import { nanoid } from '@opentiny/utils'
import type { IModalApi, IModalProps, IModalRenderlessParamUtils, ISharedRenderlessParamHooks } from '@/types'
export const api = [
@@ -78,6 +79,8 @@ export const renderless = (
const api = {} as IModalApi
const lockScrollClass = constants.SCROLL_LOCK_CLASS(mode)
const state = reactive({
+ titleId: 'tiny-modal-title-' + nanoid.api.nanoid(8),
+ contentId: 'tiny-modal-content-' + nanoid.api.nanoid(8),
emitter: emitter(),
visible: false,
contentVisible: false,
diff --git a/packages/renderless/types/modal.type.ts b/packages/renderless/types/modal.type.ts
index 04fac1cba0..dc2761ccb4 100644
--- a/packages/renderless/types/modal.type.ts
+++ b/packages/renderless/types/modal.type.ts
@@ -22,6 +22,8 @@ export interface IModalState {
prevEvent: null | Event
options: any[]
theme: string | undefined
+ titleId: string
+ contentId: string
}
export type IModalProps = ExtractPropTypes
diff --git a/packages/vue/src/modal/src/pc.vue b/packages/vue/src/modal/src/pc.vue
index 4b32f2463f..522bf3c77a 100644
--- a/packages/vue/src/modal/src/pc.vue
+++ b/packages/vue/src/modal/src/pc.vue
@@ -160,6 +160,10 @@ export default defineComponent({
h(
'div',
{
+ role: 'dialog',
+ 'aria-modal': 'true',
+ 'aria-labelledby': state.titleId,
+ 'aria-describedby': state.contentId,
class: 'tiny-modal__box',
style: state.boxStyle,
ref: 'modalBox',
@@ -187,6 +191,7 @@ export default defineComponent({
[
typeof status === 'string'
? h(STATUS_MAPPING_COMPINENT[status.toUpperCase()], {
+ 'aria-label': `${constants.STATUS_MAPPING_CLASSS[status.toUpperCase()] || 'default'}-circle`,
class: [constants.STATUS_MAPPING_CLASSS[status.toUpperCase()]]
})
: h(status, {
@@ -199,13 +204,15 @@ export default defineComponent({
? h(
'span',
{
- class: 'tiny-modal__title'
+ class: 'tiny-modal__title',
+ id: state.titleId
},
title || t('ui.alert.title')
)
: null,
resize
? h(zoomLocat ? iconMinscreenRight() : iconFullscreenRight(), {
+ 'aria-label': 'Zoom',
class: ['tiny-modal__zoom-btn', 'trigger__btn'],
on: {
click: this.toggleZoomEvent
@@ -214,6 +221,7 @@ export default defineComponent({
: null,
showClose
? h(iconClose(), {
+ 'aria-label': 'Close',
class: ['tiny-modal__close-btn', 'trigger__btn'],
on: {
click: this.closeEvent
@@ -238,6 +246,7 @@ export default defineComponent({
[
typeof status === 'string'
? h(STATUS_MAPPING_COMPINENT[status.toUpperCase()], {
+ 'aria-label': `${constants.STATUS_MAPPING_CLASSS[status.toUpperCase()] || 'default'}-message-circle`,
class: [constants.STATUS_MAPPING_CLASSS[status.toUpperCase()]]
})
: h(status, {
@@ -249,7 +258,8 @@ export default defineComponent({
h(
'div',
{
- class: 'tiny-modal__content'
+ class: 'tiny-modal__content',
+ id: state.contentId
},
defaultSlot
? [defaultSlot.call(this, { $modal: this }, h)]
@@ -269,6 +279,7 @@ export default defineComponent({
},
[
h(iconClose(), {
+ 'aria-label': 'Close',
class: ['tiny-modal__close-btn'],
on: {
click: this.closeEvent