From db3b5f7157c900b885ade2626014f70c61cb0849 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 21 Oct 2026 07:53:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../appointmentNativeForms.css | 2 + .../ContainerSimulationCreate.test.ts | 28 ++++++- .../components/ContainerSimulationCreate.vue | 1 + .../pages/ContainersView.test.ts | 2 +- .../components/AppDialog.layout.test.ts | 83 +++++++++++++++++-- src/shared/components/DataTable.vue | 30 +++++-- src/styles/adminDensity.css | 11 ++- src/styles/adminDensity.test.ts | 4 +- src/styles/adminWorkbench.css | 16 +++- src/styles/adminWorkbench.test.ts | 6 +- 10 files changed, 161 insertions(+), 22 deletions(-) diff --git a/src/modules/appointment-calendar/appointmentNativeForms.css b/src/modules/appointment-calendar/appointmentNativeForms.css index e7d8c302..fd002e1c 100644 --- a/src/modules/appointment-calendar/appointmentNativeForms.css +++ b/src/modules/appointment-calendar/appointmentNativeForms.css @@ -12,6 +12,8 @@ border: 0; } +.appointment-native-form > fieldset > .el-button { justify-self: start; } + .appointment-native-form label:has(> :is(input, select, textarea, .el-date-editor)) { display: grid; min-width: 0; diff --git a/src/modules/container-loading/components/ContainerSimulationCreate.test.ts b/src/modules/container-loading/components/ContainerSimulationCreate.test.ts index 35da5f4e..548bf045 100644 --- a/src/modules/container-loading/components/ContainerSimulationCreate.test.ts +++ b/src/modules/container-loading/components/ContainerSimulationCreate.test.ts @@ -6,6 +6,8 @@ import { listWarehouseLogisticsChannels } from '@/modules/ocean-bookings' import { createContainerSimulation } from '../api/platformContainerLoading' import AppDialog from '@/shared/components/AppDialog.vue' import ContainerSimulationCreate from './ContainerSimulationCreate.vue' +import DataTable from '@/shared/components/DataTable.vue' +import { h } from 'vue' // Node's supported CJS export keeps the real form validator's constructor intact. const ElementPlus = (createRequire(import.meta.url)('element-plus') as typeof import('element-plus')).default @@ -19,14 +21,26 @@ afterEach(() => { document.body.innerHTML = '' }) describe('ContainerSimulationCreate opted-in form', () => { it.each([true, false])('preserves required fields and the fixed request in dense=%s mode', async (dense) => { - const wrapper = mount(ContainerSimulationCreate, { props: { dense, loadWarehouseOptions: async () => [warehouse] }, global: { plugins: [ElementPlus] }, attachTo: document.body }) + const host = mount(DataTable, { + props: { data: [], columns: [] }, + slots: { 'toolbar-actions': () => h(ContainerSimulationCreate, { dense, loadWarehouseOptions: async () => [warehouse] }) }, + global: { plugins: [ElementPlus] }, + attachTo: document.body, + }) + const wrapper = host.findComponent(ContainerSimulationCreate) + expect(wrapper.get('button').element.closest('.data-table__toolbar-actions')).not.toBeNull() await wrapper.findAll('button').find(button => button.text().includes('新增模拟柜'))!.trigger('click') await flushPromises() const dialog = wrapper.findComponent(AppDialog) + const dialogRoot = document.body.querySelector('.simulation-create-dialog')! + expect(dialogRoot).not.toBeNull() + expect(dialogRoot.closest('.data-table__toolbar')).toBeNull() + expect(dialogRoot.querySelector('.el-dialog__footer button')?.closest('.data-table__toolbar')).toBeNull() expect(dialog.props('width')).toBe('min(800px, calc(100vw - 32px))') dialog.vm.$emit('confirm') await flushPromises() expect(createContainerSimulation).not.toHaveBeenCalled() + await vi.waitFor(() => expect(dialogRoot.querySelectorAll('.el-form-item__error').length).toBeGreaterThan(0)) const fields = dialog.findAllComponents({ name: 'ElFormItem' }) expect(fields).toHaveLength(3) fields.find(field => field.props('prop') === 'warehouseId')!.findComponent({ name: 'ElSelect' }).vm.$emit('update:modelValue', '8') @@ -38,5 +52,17 @@ describe('ContainerSimulationCreate opted-in form', () => { await flushPromises() expect(createContainerSimulation).toHaveBeenCalledExactlyOnceWith({ warehouseId: '8', channelGroupCode: 'MATSON_CLX', containerType: '40HQ', capacityCbm: 68, maxWeightKg: 19500 }) expect(wrapper.emitted('created')).toHaveLength(1) + expect(dialog.props('modelValue')).toBe(false) + await wrapper.get('button').trigger('click') + await flushPromises() + expect(dialog.props('modelValue')).toBe(true) + expect(dialogRoot.closest('.data-table__toolbar')).toBeNull() + expect(dialog.findAllComponents({ name: 'ElFormItem' }).find(field => field.props('prop') === 'warehouseId')!.findComponent({ name: 'ElSelect' }).props('modelValue')).toBe('') + const cancel = Array.from(dialogRoot.querySelectorAll('.el-dialog__footer button')).find(button => button.textContent?.trim() === '取消')! + cancel.click() + await flushPromises() + expect(dialog.props('modelValue')).toBe(false) + expect(host.find('.data-table__toolbar-actions').exists()).toBe(true) + expect(createContainerSimulation).toHaveBeenCalledTimes(1) }) }) diff --git a/src/modules/container-loading/components/ContainerSimulationCreate.vue b/src/modules/container-loading/components/ContainerSimulationCreate.vue index 78ce2aa3..fd93d504 100644 --- a/src/modules/container-loading/components/ContainerSimulationCreate.vue +++ b/src/modules/container-loading/components/ContainerSimulationCreate.vue @@ -101,6 +101,7 @@ async function saveSimulation() { class="simulation-create-dialog" :class="{ 'admin-density-dialog': dense, 'admin-density-dialog--comfortable': dense, 'admin-workbench': dense, 'is-dialog': dense }" title="新增模拟柜" + append-to-body width="min(800px, calc(100vw - 32px))" confirm-text="创建模拟柜" cancel-text="取消" diff --git a/src/modules/container-loading/pages/ContainersView.test.ts b/src/modules/container-loading/pages/ContainersView.test.ts index ab1dd64d..ff969931 100644 --- a/src/modules/container-loading/pages/ContainersView.test.ts +++ b/src/modules/container-loading/pages/ContainersView.test.ts @@ -304,7 +304,7 @@ describe('ContainersView', () => { expect(wrapper.find('button[aria-label="查看 CB-1"]').exists()).toBe(true) await wrapper.findAll('button').find((button) => button.text().includes('新增模拟柜'))!.trigger('click') await flushPromises() - expect(wrapper.text()).toContain('装柜仓库加载失败') + expect(document.body.querySelector('.simulation-create-dialog')?.textContent).toContain('装柜仓库加载失败') }) it('uses stable view labels when cabinetNo is missing', async () => { diff --git a/src/shared/components/AppDialog.layout.test.ts b/src/shared/components/AppDialog.layout.test.ts index 6ce4a0ff..e4a4ed0a 100644 --- a/src/shared/components/AppDialog.layout.test.ts +++ b/src/shared/components/AppDialog.layout.test.ts @@ -7,6 +7,7 @@ import { resolve } from 'node:path' import { compileStyle, parse } from 'vue/compiler-sfc' import AppDialog from './AppDialog.vue' import AppDrawer from './AppDrawer.vue' +import DataTable from './DataTable.vue' let wrapper: VueWrapper | undefined const styles: HTMLStyleElement[] = [] @@ -18,10 +19,10 @@ function installStyles() { document.head.append(style) styles.push(style) } - for (const name of ['base', 'el-dialog', 'el-drawer', 'el-form', 'el-form-item', 'el-button']) { + for (const name of ['base', 'el-dialog', 'el-drawer', 'el-form', 'el-form-item', 'el-select', 'el-button']) { add(readFileSync(resolve(process.cwd(), `node_modules/element-plus/theme-chalk/${name}.css`), 'utf8')) } - for (const [name, component] of [['AppDialog', AppDialog], ['AppDrawer', AppDrawer]] as const) { + for (const [name, component] of [['AppDialog', AppDialog], ['AppDrawer', AppDrawer], ['DataTable', DataTable]] as const) { const source = readFileSync(resolve(process.cwd(), `src/shared/components/${name}.vue`), 'utf8') for (const style of parse(source).descriptor.styles) { const compiled = compileStyle({ source: style.content, filename: `${name}.vue`, scoped: style.scoped, id: component.__scopeId || 'data-v-layout' }) @@ -37,7 +38,7 @@ function installStyles() { async function render(template: string) { installStyles() wrapper = mount(defineComponent({ - components: { AppDialog, AppDrawer }, + components: { AppDialog, AppDrawer, DataTable }, setup: () => ({ open: ref(true), childOpen: ref(true), name: ref(''), valid: ref(false) }), template, }), { attachTo: document.body, global: { plugins: [ElementPlus] } }) @@ -60,23 +61,95 @@ afterEach(() => { }) describe('real Element floating frame boundaries', () => { + it.each(['workbench-table', 'workbench-toolbar'])('isolates %s toolbar actions from nested dialogs while preserving wrappers and dropdowns', async (surface) => { + await render(` +
+ + + + + +
+ `) + expect(element('.toolbar-editor').closest('.data-table__business-actions')).not.toBeNull() + // Check matching against real DOM, not a source-order approximation of the cascade. + const workbench = compileStyle({ source: readFileSync(resolve(process.cwd(), 'src/styles/adminWorkbench.css'), 'utf8'), filename: 'workbench.css', id: 'toolbar-regression' }).rawResult!.root + const toolbarSelectors: string[] = [] + workbench.walkRules(rule => { + if (/data-table__(toolbar|business-actions)/.test(rule.selector) && rule.selector.includes('el-button')) toolbarSelectors.push(...rule.selectors) + }) + for (const selector of ['.direct-action', '.wrapped-action', '.dropdown-action', '.split-actions .el-button', '.grouped-action', '.start-action', '.end-action']) { + expect(toolbarSelectors.some(rule => element(selector).matches(rule)), selector).toBe(true) + } + for (const button of document.querySelectorAll('.toolbar-editor .el-button, .nested-table-action')) { + expect(toolbarSelectors.filter(rule => button.matches(rule))).toEqual([]) + } + const table = parse(readFileSync(resolve(process.cwd(), 'src/shared/components/DataTable.vue'), 'utf8')).descriptor + for (const style of table.styles) { + const rules = compileStyle({ source: style.content, filename: 'DataTable.vue', scoped: style.scoped, id: DataTable.__scopeId || 'data-v-layout' }).rawResult!.root + rules.walkRules(rule => { + if (!/toolbar|business-actions|utility-actions/.test(rule.selector) || !rule.selector.includes('el-button')) return + for (const button of document.querySelectorAll('.toolbar-editor .el-button, .nested-table-action')) { + expect(rule.selectors.some(selector => button.matches(selector)), rule.selector).toBe(false) + } + }) + } + expect(computed('.toolbar-editor .app-dialog__footer > .el-button--primary').height).toBe('38px') + expect(computed('.toolbar-editor .app-dialog__footer > .el-button--primary').boxShadow).not.toContain('4px 12px') + }) + it('keeps a comfortable parent from changing nested dense and native dialog chrome', async () => { await render(` -

子窗口正文

+ + +
+
+ + + +

预览内容

-

独立正文

+ + + +

预览内容

`) expect(element('.inner').closest('.outer')).not.toBeNull() expect(element('.outer').classList.contains('el-dialog')).toBe(true) + for (const field of ['.parent-form > .el-form-item', '.parent-grid > .el-form-item']) { + expect(computed(`.outer ${field}`).marginBottom).toBe('20px') + expect(computed(`.outer ${field} > .el-form-item__label`).marginBottom).toBe('8px') + expect(computed(`.outer ${field} > .el-form-item__label`).fontWeight).toBe('500') + expect(computed(`.outer ${field} .el-select__wrapper`).minHeight).toBe('40px') + } for (const part of ['header', 'body', 'footer']) { expect(computed(`.inner > .el-dialog__${part}`).padding).toBe(computed(`.reference > .el-dialog__${part}`).padding) } expect(computed('.nested-native > .el-dialog__header').padding).toBe(computed('.reference-native > .el-dialog__header').padding) expect(computed('.nested-native > .el-dialog__body').overflowY).toBe(computed('.reference-native > .el-dialog__body').overflowY) expect(computed('.inner > .el-dialog__header > .el-dialog__title').paddingRight).toBe(computed('.reference > .el-dialog__header > .el-dialog__title').paddingRight) + for (const form of ['.density-form', '.el-form--label-top']) { + expect(computed(`.inner ${form} .el-form-item`).marginBottom).toBe(computed(`.reference ${form} .el-form-item`).marginBottom) + expect(computed(`.inner ${form} .el-form-item__label`).marginBottom).toBe(computed(`.reference ${form} .el-form-item__label`).marginBottom) + expect(computed(`.inner ${form} .el-form-item__label`).fontWeight).toBe(computed(`.reference ${form} .el-form-item__label`).fontWeight) + expect(computed(`.inner ${form} .el-select__wrapper`).minHeight).toBe(computed(`.reference ${form} .el-select__wrapper`).minHeight) + } }) it('isolates drawer chrome and honors an explicitly teleported nested dialog', async () => { diff --git a/src/shared/components/DataTable.vue b/src/shared/components/DataTable.vue index 55aa7754..d554ab7b 100644 --- a/src/shared/components/DataTable.vue +++ b/src/shared/components/DataTable.vue @@ -437,7 +437,10 @@ function setPage(value: number) { .data-table__toolbar-actions { display: flex; flex: 0 0 auto; align-items: center; flex-wrap: wrap; justify-content: flex-end; gap: 8px; } .data-table__business-actions, .data-table__utility-actions, .data-table__toolbar-end { display: flex; min-width: 0; align-items: center; flex-wrap: wrap; justify-content: flex-end; gap: 8px; } .data-table__utility-actions { flex: 0 0 auto; } -.data-table__toolbar-actions :deep(.el-button + .el-button) { margin-left: 0; } +.data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(.el-button + .el-button), +.data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button + .el-button), +.data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button + .el-button), +.data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button + .el-button) { margin-left: 0; } .data-table__pagination { display: flex; min-height: 58px; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 18px; border-top: 1px solid var(--cc-border); color: var(--cc-muted); font-size: var(--cc-list-pagination-font-size); } .data-table__pagination-left { display: flex; min-width: 0; flex: 1; align-items: center; gap: 8px; } .data-table__pagination-controls, .data-table__page-size, .data-table__page-buttons { display: flex; align-items: center; gap: 8px; } @@ -465,7 +468,10 @@ function setPage(value: number) { .data-table__page-ellipsis { width: 22px; height: 30px; } } -@media (max-width: 720px) { .data-table__toolbar, .data-table__toolbar.is-compact { min-height: 0; align-items: stretch; flex-direction: column; padding: 14px; } .data-table__toolbar-actions { width: 100%; align-items: flex-start; flex-wrap: wrap; justify-content: flex-start; } .data-table__toolbar-actions.has-business-actions { display: grid; grid-template-columns: minmax(0, 1fr) auto; } .data-table__business-actions { justify-content: flex-start; } .data-table__utility-actions { align-items: flex-start; justify-content: flex-end; } .data-table__toolbar-end { justify-content: flex-start; } .data-table__toolbar-actions :deep(.el-button) { max-width: 100%; min-height: 40px; } .data-table__pagination { align-items: flex-start; flex-direction: column; } .data-table__pagination-left, .data-table__pagination-controls, .data-table__page-buttons { width: 100%; } .data-table__pagination-controls { justify-content: flex-start; } .data-table__page-buttons { flex-wrap: wrap; } .data-table__page-button { width: 44px; min-width: 44px; height: 44px; } .data-table__page-ellipsis { height: 44px; } } +@media (max-width: 720px) { .data-table__toolbar, .data-table__toolbar.is-compact { min-height: 0; align-items: stretch; flex-direction: column; padding: 14px; } .data-table__toolbar-actions { width: 100%; align-items: flex-start; flex-wrap: wrap; justify-content: flex-start; } .data-table__toolbar-actions.has-business-actions { display: grid; grid-template-columns: minmax(0, 1fr) auto; } .data-table__business-actions { justify-content: flex-start; } .data-table__utility-actions { align-items: flex-start; justify-content: flex-end; } .data-table__toolbar-end { justify-content: flex-start; } .data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(.el-button), +.data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button), +.data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button), +.data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button) { max-width: 100%; min-height: 40px; } .data-table__pagination { align-items: flex-start; flex-direction: column; } .data-table__pagination-left, .data-table__pagination-controls, .data-table__page-buttons { width: 100%; } .data-table__pagination-controls { justify-content: flex-start; } .data-table__page-buttons { flex-wrap: wrap; } .data-table__page-button { width: 44px; min-width: 44px; height: 44px; } .data-table__page-ellipsis { height: 44px; } } /* Opt-in list surface: header, filters and results share one continuous card. */ .data-table--integrated { --list-card-border: #e8edf5; @@ -494,19 +500,31 @@ function setPage(value: number) { font-weight: 700; line-height: 1.5; } -.data-table--integrated .data-table__toolbar-actions :deep(.el-button) { +.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(.el-button), +.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button), +.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button), +.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button) { min-height: 36px; border-radius: 10px; } -.data-table--integrated .data-table__utility-actions :deep(.el-button > span) { gap: 6px; } -.data-table--integrated .data-table__business-actions :deep(.el-button--primary:not(:disabled)), +.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__utility-actions > :deep(.el-button > span), +.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__utility-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button > span), +.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__utility-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button > span), +.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__utility-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button > span) { gap: 6px; } +.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :deep(.el-button--primary:not(:disabled)), +.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button--primary:not(:disabled)), +.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button--primary:not(:disabled)), +.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button--primary:not(:disabled)), .data-table--integrated .data-table__page-button[aria-current="page"]:not(:disabled) { border-color: transparent; background: var(--list-card-accent); color: #fff; box-shadow: 0 4px 12px rgb(102 126 234 / 20%); } -.data-table--integrated .data-table__business-actions :deep(.el-button--primary:hover:not(:disabled)), +.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :deep(.el-button--primary:hover:not(:disabled)), +.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button--primary:hover:not(:disabled)), +.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button--primary:hover:not(:disabled)), +.data-table--integrated > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :deep(:where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button--primary:hover:not(:disabled)), .data-table--integrated .data-table__page-button[aria-current="page"]:hover:not(:disabled) { filter: brightness(1.06); } .data-table--integrated > .data-table__filters { min-width: 0; diff --git a/src/styles/adminDensity.css b/src/styles/adminDensity.css index 975d105f..cdf5805a 100644 --- a/src/styles/adminDensity.css +++ b/src/styles/adminDensity.css @@ -76,9 +76,14 @@ .admin-density-dialog.admin-density-dialog--comfortable.el-dialog > .el-dialog__header > .el-dialog__headerbtn { top: 6px; } .admin-density-dialog.admin-density-dialog--comfortable.el-dialog > .el-dialog__body { padding: 24px; } .admin-density-dialog.admin-density-dialog--comfortable.el-dialog > .el-dialog__footer { padding: 16px 24px; background: var(--admin-label-bg); } -.admin-density-dialog.admin-density-dialog--comfortable.el-dialog .el-form-item { margin-bottom: 20px; } -.admin-density-dialog.admin-density-dialog--comfortable.el-dialog .el-form-item__label { margin-bottom: 8px; font-weight: 500; } -.admin-density-dialog.admin-density-dialog--comfortable.el-dialog .el-select__wrapper { min-height: 40px; } +/* Short forms opt in through the body's own form, with direct fields or one grid wrapper. + Never cross a nested overlay/dialog/drawer while styling its fields. */ +.admin-density-dialog--comfortable.el-dialog > .el-dialog__body > .app-dialog__body > .el-form > .el-form-item, +.admin-density-dialog--comfortable.el-dialog > .el-dialog__body > .app-dialog__body > .el-form > :where(div):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper)) > .el-form-item { margin-bottom: 20px; } +.admin-density-dialog--comfortable.el-dialog > .el-dialog__body > .app-dialog__body > .el-form > .el-form-item > .el-form-item__label, +.admin-density-dialog--comfortable.el-dialog > .el-dialog__body > .app-dialog__body > .el-form > :where(div):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper)) > .el-form-item > .el-form-item__label { margin-bottom: 8px; font-weight: 500; } +.admin-density-dialog--comfortable.el-dialog > .el-dialog__body > .app-dialog__body > .el-form > .el-form-item > .el-form-item__content > .el-select > .el-select__wrapper, +.admin-density-dialog--comfortable.el-dialog > .el-dialog__body > .app-dialog__body > .el-form > :where(div):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper)) > .el-form-item > .el-form-item__content > .el-select > .el-select__wrapper { min-height: 40px; } .admin-density-dialog.admin-density-dialog--comfortable.el-dialog > .el-dialog__footer > .app-dialog__footer > .el-button { min-width: 88px; height: 38px; } /* Opted-in detail and editor content reuse the existing compact drawer frame. */ diff --git a/src/styles/adminDensity.test.ts b/src/styles/adminDensity.test.ts index 984cccfe..ac0ad7ef 100644 --- a/src/styles/adminDensity.test.ts +++ b/src/styles/adminDensity.test.ts @@ -1127,7 +1127,9 @@ describe('approved Admin density ownership', () => { expect(source).toContain("'admin-density-dialog--comfortable': dense") expect(source).toContain('width="min(800px, calc(100vw - 32px))"') expect(shared).toContain('.admin-density-dialog.admin-density-dialog--comfortable.el-dialog { --el-component-size: 40px; border-radius: 12px; }') - expect(shared).toContain('.admin-density-dialog.admin-density-dialog--comfortable.el-dialog .el-select__wrapper { min-height: 40px; }') + expect(shared).toContain('> .el-form-item__content > .el-select > .el-select__wrapper { min-height: 40px; }') + expect(shared).not.toContain('.admin-density-dialog.admin-density-dialog--comfortable.el-dialog .el-form-item') + expect(shared).not.toContain('.admin-density-dialog.admin-density-dialog--comfortable.el-dialog .el-select__wrapper') }) it('keeps conditional shared consumers opt-in instead of enabling other menus', () => { diff --git a/src/styles/adminWorkbench.css b/src/styles/adminWorkbench.css index 07a51a87..0dc134ec 100644 --- a/src/styles/adminWorkbench.css +++ b/src/styles/adminWorkbench.css @@ -448,8 +448,15 @@ flex-wrap: wrap; } - .admin-workbench .workbench-table.is-page-surface > .data-table > .data-table__toolbar .el-button, - .admin-workbench .workbench-toolbar.is-page-surface > .data-table > .data-table__toolbar .el-button { + /* Action wrappers and dropdown/button groups are bounded before floating roots. */ + .admin-workbench :is(.workbench-table, .workbench-toolbar).is-page-surface > .data-table > .data-table__toolbar > .data-table__toolbar-start > .el-button, + .admin-workbench :is(.workbench-table, .workbench-toolbar).is-page-surface > .data-table > .data-table__toolbar > .data-table__toolbar-start > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button, + .admin-workbench :is(.workbench-table, .workbench-toolbar).is-page-surface > .data-table > .data-table__toolbar > .data-table__toolbar-start > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button, + .admin-workbench :is(.workbench-table, .workbench-toolbar).is-page-surface > .data-table > .data-table__toolbar > .data-table__toolbar-start > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button, + .admin-workbench :is(.workbench-table, .workbench-toolbar).is-page-surface > .data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > .el-button, + .admin-workbench :is(.workbench-table, .workbench-toolbar).is-page-surface > .data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button, + .admin-workbench :is(.workbench-table, .workbench-toolbar).is-page-surface > .data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button, + .admin-workbench :is(.workbench-table, .workbench-toolbar).is-page-surface > .data-table > .data-table__toolbar > .data-table__toolbar-actions > :is(.data-table__business-actions, .data-table__utility-actions) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button { min-height: var(--admin-control-height, 32px); height: var(--admin-control-height, 32px); } @@ -522,7 +529,10 @@ } .admin-workbench .workbench-table .data-table .data-table__page-button[aria-current='page']:not(:disabled), -.admin-workbench .workbench-table .data-table__business-actions .el-button--primary:not(:disabled) { +.admin-workbench .workbench-table > .data-table > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > .el-button--primary:not(:disabled), +.admin-workbench .workbench-table > .data-table > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button--primary:not(:disabled), +.admin-workbench .workbench-table > .data-table > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button--primary:not(:disabled), +.admin-workbench .workbench-table > .data-table > .data-table__toolbar > .data-table__toolbar-actions > .data-table__business-actions > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > :where(div, span):not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table)) > .el-button--primary:not(:disabled) { box-shadow: var(--workbench-shadow); } diff --git a/src/styles/adminWorkbench.test.ts b/src/styles/adminWorkbench.test.ts index cded8ab9..fdd51e13 100644 --- a/src/styles/adminWorkbench.test.ts +++ b/src/styles/adminWorkbench.test.ts @@ -66,8 +66,10 @@ describe('Admin workbench opt-in styles', () => { for (const selector of rule.selectors) { expect(selector).toMatch(/^\.admin-workbench(?:\s|\.|:|\[|$)/) expect(selector).not.toMatch(/:global|:root/) - if (selector.includes('.el-dialog')) expect(selector).toMatch(/^\.admin-workbench\.is-dialog\.el-dialog/) - if (selector.includes('.el-drawer')) expect(selector).toMatch(/^\.admin-workbench\.is-drawer\.el-drawer/) + // Excluding floating roots from a toolbar is not a selector targeting their chrome. + const targetSelector = selector.replaceAll(':not(:where(.el-overlay, .el-dialog, .el-drawer, .el-popper, .data-table))', '') + if (targetSelector.includes('.el-dialog')) expect(targetSelector).toMatch(/^\.admin-workbench\.is-dialog\.el-dialog/) + if (targetSelector.includes('.el-drawer')) expect(targetSelector).toMatch(/^\.admin-workbench\.is-drawer\.el-drawer/) } }) root(source).walkDecls(declaration => {