diff --git a/packages/component-base/src/style-props.js b/packages/component-base/src/style-props.js index 77eb760915c..d445e685d71 100644 --- a/packages/component-base/src/style-props.js +++ b/packages/component-base/src/style-props.js @@ -61,12 +61,14 @@ addGlobalThemeStyles( --_vaadin-icon-chevron-down: url('data:image/svg+xml,'); --_vaadin-icon-clock: url('data:image/svg+xml,'); --_vaadin-icon-cross: url('data:image/svg+xml;utf8,'); + --_vaadin-icon-drag: url('data:image/svg+xml;utf8,'); --_vaadin-icon-eye: url('data:image/svg+xml;utf8,'); --_vaadin-icon-eye-slash: url('data:image/svg+xml;utf8,'); --_vaadin-icon-fullscreen: url('data:image/svg+xml;utf8,'); --_vaadin-icon-menu: url('data:image/svg+xml;utf8,'); --_vaadin-icon-minus: url('data:image/svg+xml;utf8,'); --_vaadin-icon-plus: url('data:image/svg+xml;utf8,'); + --_vaadin-icon-resize: url('data:image/svg+xml;utf8,'); --_vaadin-icon-sort: url('data:image/svg+xml;utf8,'); --_vaadin-icon-user: url('data:image/svg+xml;utf8,'); --_vaadin-icon-warn: url('data:image/svg+xml;utf8,'); diff --git a/packages/dashboard/package.json b/packages/dashboard/package.json index 6ec5bbc238f..43cc83dd2db 100644 --- a/packages/dashboard/package.json +++ b/packages/dashboard/package.json @@ -21,6 +21,8 @@ "type": "module", "files": [ "src", + "!src/styles/*-base-styles.d.ts", + "!src/styles/*-base-styles.js", "theme", "vaadin-*.d.ts", "vaadin-*.js", diff --git a/packages/dashboard/src/styles/vaadin-dashboard-base-styles.d.ts b/packages/dashboard/src/styles/vaadin-dashboard-base-styles.d.ts new file mode 100644 index 00000000000..2840d6000eb --- /dev/null +++ b/packages/dashboard/src/styles/vaadin-dashboard-base-styles.d.ts @@ -0,0 +1,13 @@ +/** + * @license + * Copyright (c) 2000 - 2025 Vaadin Ltd. + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * + * See https://vaadin.com/commercial-license-and-service-terms for the full + * license. + */ +import type { CSSResult } from 'lit'; + +export const dashboardStyles: CSSResult; diff --git a/packages/dashboard/src/styles/vaadin-dashboard-base-styles.js b/packages/dashboard/src/styles/vaadin-dashboard-base-styles.js new file mode 100644 index 00000000000..b047d1db944 --- /dev/null +++ b/packages/dashboard/src/styles/vaadin-dashboard-base-styles.js @@ -0,0 +1,25 @@ +/** + * @license + * Copyright (c) 2000 - 2025 Vaadin Ltd. + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * + * See https://vaadin.com/commercial-license-and-service-terms for the full + * license. + */ +import { css } from 'lit'; +import { dashboardLayoutStyles } from './vaadin-dashboard-layout-core-styles.js'; + +const dashboard = css` + #grid[item-resizing] { + -webkit-user-select: none; + user-select: none; + } + + ::slotted(vaadin-dashboard-widget-wrapper) { + display: contents; + } +`; + +export const dashboardStyles = [dashboardLayoutStyles, dashboard]; diff --git a/packages/dashboard/src/styles/vaadin-dashboard-button-base-styles.d.ts b/packages/dashboard/src/styles/vaadin-dashboard-button-base-styles.d.ts new file mode 100644 index 00000000000..09c43aec145 --- /dev/null +++ b/packages/dashboard/src/styles/vaadin-dashboard-button-base-styles.d.ts @@ -0,0 +1,13 @@ +/** + * @license + * Copyright (c) 2000 - 2025 Vaadin Ltd. + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * + * See https://vaadin.com/commercial-license-and-service-terms for the full + * license. + */ +import type { CSSResult } from 'lit'; + +export const dashboardButtonStyles: CSSResult; diff --git a/packages/dashboard/src/styles/vaadin-dashboard-button-base-styles.js b/packages/dashboard/src/styles/vaadin-dashboard-button-base-styles.js new file mode 100644 index 00000000000..3567684324c --- /dev/null +++ b/packages/dashboard/src/styles/vaadin-dashboard-button-base-styles.js @@ -0,0 +1,20 @@ +/** + * @license + * Copyright (c) 2000 - 2025 Vaadin Ltd. + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * + * See https://vaadin.com/commercial-license-and-service-terms for the full + * license. + */ +import { css } from 'lit'; +import { buttonStyles } from '@vaadin/button/src/styles/vaadin-button-core-styles.js'; + +const dashboardButton = css` + :host { + min-width: 1rem; + } +`; + +export const dashboardButtonStyles = [buttonStyles, dashboardButton]; diff --git a/packages/dashboard/src/styles/vaadin-dashboard-layout-base-styles.d.ts b/packages/dashboard/src/styles/vaadin-dashboard-layout-base-styles.d.ts new file mode 100644 index 00000000000..8ac5f349140 --- /dev/null +++ b/packages/dashboard/src/styles/vaadin-dashboard-layout-base-styles.d.ts @@ -0,0 +1,13 @@ +/** + * @license + * Copyright (c) 2000 - 2025 Vaadin Ltd. + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * + * See https://vaadin.com/commercial-license-and-service-terms for the full + * license. + */ +import type { CSSResult } from 'lit'; + +export const dashboardLayoutStyles: CSSResult; diff --git a/packages/dashboard/src/styles/vaadin-dashboard-layout-base-styles.js b/packages/dashboard/src/styles/vaadin-dashboard-layout-base-styles.js new file mode 100644 index 00000000000..246a8c25bc6 --- /dev/null +++ b/packages/dashboard/src/styles/vaadin-dashboard-layout-base-styles.js @@ -0,0 +1,85 @@ +/** + * @license + * Copyright (c) 2000 - 2025 Vaadin Ltd. + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * + * See https://vaadin.com/commercial-license-and-service-terms for the full + * license. + */ +import { css } from 'lit'; + +export const dashboardLayoutStyles = css` + :host { + display: block; + overflow: auto; + box-sizing: border-box; + width: 100%; + } + + :host([hidden]) { + display: none !important; + } + + :host([dense-layout]) #grid { + grid-auto-flow: dense; + } + + #grid { + box-sizing: border-box; + + /* Padding around dashboard edges */ + --_default-padding: 1rem; + --_padding: max(0px, var(--vaadin-dashboard-padding, var(--_default-padding))); + padding: var(--_padding); + + /* Gap between widgets */ + --_default-gap: 1rem; + --_gap: max(0px, var(--vaadin-dashboard-gap, var(--_default-gap))); + gap: var(--_gap); + + /* Default min and max column widths */ + --_default-col-min-width: 25rem; + --_default-col-max-width: 1fr; + + /* Effective min and max column widths */ + --_col-min-width: var(--vaadin-dashboard-col-min-width, var(--_default-col-min-width)); + --_col-max-width: var(--vaadin-dashboard-col-max-width, var(--_default-col-max-width)); + + /* Effective max column count */ + --_col-max-count: var(--vaadin-dashboard-col-max-count, var(--_col-count)); + + /* Effective column count */ + --_effective-col-count: min(var(--_col-count), var(--_col-max-count)); + + /* Default row min height */ + --_default-row-min-height: 12rem; + /* Effective row min height */ + --_row-min-height: var(--vaadin-dashboard-row-min-height, var(--_default-row-min-height)); + /* Effective row height */ + --_row-height: minmax(var(--_row-min-height, auto), auto); + + display: grid; + overflow: hidden; + min-width: calc(var(--_col-min-width) + var(--_padding) * 2); + + grid-template-columns: repeat( + var(--_effective-col-count, auto-fill), + minmax(var(--_col-min-width), var(--_col-max-width)) + ); + + grid-auto-rows: var(--_row-height); + } + + ::slotted(*) { + /* The grid-column value applied to children */ + --_item-column: span min(var(--vaadin-dashboard-widget-colspan, 1), var(--_effective-col-count, var(--_col-count))); + + grid-column: var(--_item-column); + + /* The grid-row value applied to children */ + --_item-row: span var(--vaadin-dashboard-widget-rowspan, 1); + grid-row: var(--_item-row); + } +`; diff --git a/packages/dashboard/src/styles/vaadin-dashboard-section-base-styles.d.ts b/packages/dashboard/src/styles/vaadin-dashboard-section-base-styles.d.ts new file mode 100644 index 00000000000..f2800bc6418 --- /dev/null +++ b/packages/dashboard/src/styles/vaadin-dashboard-section-base-styles.d.ts @@ -0,0 +1,13 @@ +/** + * @license + * Copyright (c) 2000 - 2025 Vaadin Ltd. + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * + * See https://vaadin.com/commercial-license-and-service-terms for the full + * license. + */ +import type { CSSResult } from 'lit'; + +export const dashboardSectionStyles: CSSResult; diff --git a/packages/dashboard/src/styles/vaadin-dashboard-section-base-styles.js b/packages/dashboard/src/styles/vaadin-dashboard-section-base-styles.js new file mode 100644 index 00000000000..2eed48e11c4 --- /dev/null +++ b/packages/dashboard/src/styles/vaadin-dashboard-section-base-styles.js @@ -0,0 +1,75 @@ +/** + * @license + * Copyright (c) 2000 - 2025 Vaadin Ltd. + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * + * See https://vaadin.com/commercial-license-and-service-terms for the full + * license. + */ +import '@vaadin/component-base/src/style-props.js'; +import { css } from 'lit'; +import { dashboardWidgetAndSectionStyles } from './vaadin-dashboard-widget-section-core-styles.js'; + +const sectionStyles = css` + :host { + display: grid; + position: relative; + grid-template-columns: subgrid; + --_section-column: 1 / calc(var(--_effective-col-count) + 1); + grid-column: var(--_section-column) !important; + gap: var(--_gap, 1rem); + /* Dashboard section header height */ + --_section-header-height: minmax(0, auto); + grid-template-rows: var(--_section-header-height) repeat(auto-fill, var(--_row-height)); + grid-auto-rows: var(--_row-height); + border-radius: var(--vaadin-radius-m); + --_section-outline-offset: calc(min(var(--_gap), var(--_padding)) / 3); + --_focus-ring-offset: calc((var(--_section-outline-offset) - var(--_focus-ring-width))); + } + + :host([hidden]) { + display: none !important; + } + + ::slotted(*) { + --_item-column: span min(var(--vaadin-dashboard-widget-colspan, 1), var(--_effective-col-count, var(--_col-count))); + + grid-column: var(--_item-column); + --_item-row: span var(--vaadin-dashboard-widget-rowspan, 1); + grid-row: var(--_item-row); + } + + header { + grid-column: var(--_section-column); + } + + :host::before { + z-index: 2 !important; + } + + ::slotted(vaadin-dashboard-widget-wrapper) { + display: contents; + } + + /* Section states */ + + :host([editable]) { + outline: 1px solid var(--vaadin-border-color); + outline-offset: calc(var(--_section-outline-offset) - 1px); + } + + :host([focused])::after { + content: ''; + display: block; + position: absolute; + inset: 0; + border-radius: var(--vaadin-radius-m); + z-index: 9; + outline: var(--_focus-ring-width) solid var(--_focus-ring-color); + outline-offset: var(--_focus-ring-offset); + } +`; + +export const dashboardSectionStyles = [sectionStyles, dashboardWidgetAndSectionStyles]; diff --git a/packages/dashboard/src/styles/vaadin-dashboard-widget-base-styles.d.ts b/packages/dashboard/src/styles/vaadin-dashboard-widget-base-styles.d.ts new file mode 100644 index 00000000000..db2adeaa9a7 --- /dev/null +++ b/packages/dashboard/src/styles/vaadin-dashboard-widget-base-styles.d.ts @@ -0,0 +1,13 @@ +/** + * @license + * Copyright (c) 2000 - 2025 Vaadin Ltd. + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * + * See https://vaadin.com/commercial-license-and-service-terms for the full + * license. + */ +import type { CSSResult } from 'lit'; + +export const dashboardWidgetStyles: CSSResult; diff --git a/packages/dashboard/src/styles/vaadin-dashboard-widget-base-styles.js b/packages/dashboard/src/styles/vaadin-dashboard-widget-base-styles.js new file mode 100644 index 00000000000..9e2c7965465 --- /dev/null +++ b/packages/dashboard/src/styles/vaadin-dashboard-widget-base-styles.js @@ -0,0 +1,108 @@ +/** + * @license + * Copyright (c) 2000 - 2025 Vaadin Ltd. + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * + * See https://vaadin.com/commercial-license-and-service-terms for the full + * license. + */ +import '@vaadin/component-base/src/style-props.js'; +import { css } from 'lit'; +import { dashboardWidgetAndSectionStyles } from './vaadin-dashboard-widget-section-core-styles.js'; + +const widgetStyles = css` + :host { + display: flex; + flex-direction: column; + grid-column: var(--_item-column); + grid-row: var(--_item-row); + position: relative; + background: var(--_widget-background); + border-radius: var(--_widget-border-radius); + box-shadow: var(--_widget-shadow); + } + + :host::before { + content: ''; + position: absolute; + inset: calc(-1 * var(--_widget-border-width)); + border: var(--_widget-border-width) solid var(--_widget-border-color); + border-radius: calc(var(--_widget-border-radius) + var(--_widget-border-width)); + pointer-events: none; + } + + :host([hidden]) { + display: none !important; + } + + :host(:not([editable])) [part~='resize-button'] { + display: none; + } + + [part~='content'] { + flex: 1; + overflow: hidden; + min-height: 1rem; + } + + [part~='resize-button'] { + position: absolute; + bottom: 0; + inset-inline-end: 0; + z-index: 1; + overflow: hidden; + cursor: nwse-resize; + --icon: var(--_vaadin-icon-resize); + } + + :host([dir='rtl']) [part~='resize-button'] { + cursor: sw-resize; + } + + :host([dir='rtl']) [part~='resize-button'] .icon::before { + transform: scaleX(-1); + } + + /* Widget states */ + + :host([editable]) { + --vaadin-dashboard-widget-shadow: var(--_widget-editable-shadow); + --_widget-border-width: 1px; + } + + :host([focused])::before { + border-width: var(--_focus-ring-width); + border-color: var(--_focus-ring-color); + } + + :host([selected]) { + --vaadin-dashboard-widget-shadow: var(--_widget-selected-shadow); + } + + :host([dragging]) { + box-shadow: none; + background: var(--_drop-target-background-color); + border: var(--_drop-target-border); + } + + :host([resizing])::after { + content: ''; + z-index: 2; + position: absolute; + top: -1px; + width: var(--_widget-resizer-width, 0); + height: var(--_widget-resizer-height, 0); + border-radius: inherit; + background: var(--_drop-target-background-color); + border: var(--_drop-target-border); + } + + /* Widget parts */ + header { + padding: var(--vaadin-padding-container); + } +`; + +export const dashboardWidgetStyles = [widgetStyles, dashboardWidgetAndSectionStyles]; diff --git a/packages/dashboard/src/styles/vaadin-dashboard-widget-section-base-styles.js b/packages/dashboard/src/styles/vaadin-dashboard-widget-section-base-styles.js new file mode 100644 index 00000000000..f8a1845ce1a --- /dev/null +++ b/packages/dashboard/src/styles/vaadin-dashboard-widget-section-base-styles.js @@ -0,0 +1,278 @@ +/** + * @license + * Copyright (c) 2000 - 2025 Vaadin Ltd. + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * + * See https://vaadin.com/commercial-license-and-service-terms for the full + * license. + */ +import '@vaadin/component-base/src/style-props.js'; +import { css } from 'lit'; + +export const dashboardWidgetAndSectionStyles = css` + :host { + box-sizing: border-box; + --_widget-background: var(--vaadin-dashboard-widget-background, var(--vaadin-background-color)); + --_widget-border-radius: var(--vaadin-dashboard-widget-border-radius, var(--vaadin-radius-m)); + --_widget-border-width: var(--vaadin-dashboard-widget-border-width, 1px); + --_widget-border-color: var(--vaadin-dashboard-widget-border-color, var(--vaadin-border-color)); + --_widget-shadow: var(--vaadin-dashboard-widget-shadow, 0 0 0 0 transparent); + --_widget-editable-shadow: 0 1px 4px -1px rgba(0, 0, 0, 0.3); + --_widget-selected-shadow: 0 3px 12px -1px rgba(0, 0, 0, 0.3); + --_drop-target-background-color: var(--vaadin-dashboard-drop-target-background-color, rgba(0, 0, 0, 0.1)); + --_focus-ring-color: var(--vaadin-focus-ring-color); + --_focus-ring-width: var(--vaadin-focus-ring-width); + } + + :host([focused]) { + z-index: 1; + } + + :host([dragging]) * { + visibility: hidden; + } + + :host(:not([editable])) [part~='move-button'], + :host(:not([editable])) [part~='remove-button'], + :host(:not([editable])) #focus-button, + :host(:not([editable])) #focus-button-wrapper, + :host(:not([editable])) .mode-controls { + display: none; + } + + #focustrap { + display: contents; + } + + header { + display: flex; + align-items: center; + box-sizing: border-box; + justify-content: space-between; + overflow: hidden; + } + + [part='title'] { + flex: 1; + color: var(--vaadin-color); + white-space: var(--vaadin-dashboard-widget-title-wrap, wrap); + text-overflow: ellipsis; + overflow: hidden; + margin: 0 0 1px; + align-self: safe center; + } + + vaadin-dashboard-button { + z-index: 1; + padding: 4px; + } + + vaadin-dashboard-button .icon::before { + display: block; + content: ''; + height: var(--vaadin-icon-size, 24px); + width: var(--vaadin-icon-size, 24px); + background: currentColor; + mask-image: var(--icon); + } + + #focus-button-wrapper, + #focus-button { + position: absolute; + inset: 0; + opacity: 0; + } + + #focus-button { + pointer-events: none; + padding: 0; + border: none; + } + + .mode-controls { + position: absolute; + inset: 0; + z-index: 2; + } + + .mode-controls[hidden] { + display: none; + } + + /* Drag handle */ + [part~='move-button'] { + cursor: move; + --icon: var(--_vaadin-icon-drag); + } + + /* Remove button */ + [part~='remove-button'] { + cursor: pointer; + margin-inline-start: 4px; + --icon: var(--_vaadin-icon-cross); + } + + /* Move-mode buttons */ + [part~='move-backward-button'], + [part~='move-forward-button'], + [part~='move-apply-button'] { + position: absolute; + top: 50%; + } + + [part~='move-backward-button'] { + inset-inline-start: 0; + transform: translateY(-50%); + --icon: var(--_vaadin-icon-chevron-down); + } + + [part~='move-forward-button'] { + inset-inline-end: 0; + transform: translateY(-50%); + --icon: var(--_vaadin-icon-chevron-down); + } + + [part~='move-apply-button'] { + left: 50%; + transform: translate(-50%, -50%); + --icon: var(--_vaadin-icon-checkmark); + } + + :host([first-child]) [part~='move-backward-button'], + :host([last-child]) [part~='move-forward-button'] { + display: none; + } + + :host(:not([dir='rtl'])) [part~='move-backward-button'], + :host([dir='rtl']) [part~='move-forward-button'] { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + :host(:not([dir='rtl'])) [part~='move-forward-button'], + :host([dir='rtl']) [part~='move-backward-button'] { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + :host(:not([dir='rtl'])) [part~='move-backward-button'] .icon, + :host([dir='rtl']) [part~='move-forward-button'] .icon { + rotate: 90deg; + } + + :host(:not([dir='rtl'])) [part~='move-forward-button'] .icon, + :host([dir='rtl']) [part~='move-backward-button'] .icon { + rotate: -90deg; + } + + /* Resize-mode buttons */ + [part~='resize-shrink-width-button'], + [part~='resize-shrink-height-button'], + [part~='resize-grow-width-button'], + [part~='resize-grow-height-button'], + [part~='resize-apply-button'] { + position: absolute; + } + + [part~='resize-shrink-width-button'] { + inset-inline-end: 0; + top: 50%; + } + + :host(:not([dir='rtl'])) [part~='resize-shrink-width-button'] { + transform: translateY(-50%) translateX(-100%); + } + + :host([dir='rtl']) [part~='resize-shrink-width-button'] { + transform: translateY(-50%) translateX(100%); + } + + .mode-controls:has([part~='resize-grow-width-button'][hidden]) [part~='resize-shrink-width-button'] { + transform: translateY(-50%); + } + + [part~='resize-grow-width-button'] { + inset-inline-start: 100%; + top: 50%; + } + + :host(:not([dir='rtl'])) [part~='resize-grow-width-button'] { + transform: translateY(-50%) translateX(-100%); + } + + :host([dir='rtl']) [part~='resize-grow-width-button'] { + transform: translateY(-50%) translateX(100%); + } + + [part~='resize-shrink-height-button'] { + bottom: 0; + left: 50%; + transform: translateX(-50%) translateY(-100%); + } + + [part~='resize-grow-height-button'] { + top: 100%; + left: 50%; + transform: translateX(-50%) translateY(-100%); + } + + [part~='resize-grow-height-button'], + [part~='resize-grow-width-button'] { + --icon: var(--_vaadin-icon-plus); + } + + [part~='resize-shrink-height-button'], + [part~='resize-shrink-width-button'] { + --icon: var(--_vaadin-icon-minus); + } + + [part~='resize-apply-button'] { + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + --icon: var(--_vaadin-icon-checkmark); + } + + [part~='resize-shrink-width-button'] + [part~='resize-grow-width-button'] { + margin-left: 1px; + } + + [part~='resize-grow-height-button'], + [part~='resize-shrink-height-button'] { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + + [part~='resize-shrink-height-button']:not([hidden]) + [part~='resize-grow-height-button'] { + border-top-left-radius: 0; + border-top-right-radius: 0; + } + + [part~='resize-shrink-height-button'] + [part~='resize-grow-height-button'] { + margin-top: 1px; + } + + :host(:not([dir='rtl'])) [part~='resize-grow-width-button'], + :host(:not([dir='rtl'])) [part~='resize-shrink-width-button'] { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + :host([dir='rtl']) [part~='resize-grow-width-button'], + :host([dir='rtl']) [part~='resize-shrink-width-button'] { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + :host(:not([dir='rtl'])) [part~='resize-shrink-width-button']:not([hidden]) + [part~='resize-grow-width-button'] { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + :host([dir='rtl']) [part~='resize-shrink-width-button']:not([hidden]) + [part~='resize-grow-width-button'] { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } +`; diff --git a/packages/dashboard/test/visual/base/dashboard.test.ts b/packages/dashboard/test/visual/base/dashboard.test.ts new file mode 100644 index 00000000000..f97a2b9bb97 --- /dev/null +++ b/packages/dashboard/test/visual/base/dashboard.test.ts @@ -0,0 +1,176 @@ +import { sendKeys } from '@vaadin/test-runner-commands'; +import { fixtureSync, nextFrame } from '@vaadin/testing-helpers'; +import { visualDiff } from '@web/test-runner-visual-regression'; +import '../../../src/vaadin-dashboard.js'; +import { html, render } from 'lit'; +import type { Dashboard } from '../../../src/vaadin-dashboard.js'; +import { + describeBidirectional, + fireDragStart, + fireResizeOver, + fireResizeStart, + getDraggable, + getElementFromCell, + getResizeHandle, +} from '../../helpers.js'; + +describe('dashboard', () => { + let focusElement: HTMLInputElement; + let element: Dashboard; + let div: HTMLDivElement; + + beforeEach(() => { + div = document.createElement('div'); + + fixtureSync(` + + `); + + focusElement = fixtureSync(``); + focusElement.focus(); + }); + + describeBidirectional(`widgets and section`, () => { + function getName(name: string) { + return `${document.dir || 'ltr'}-${name}`; + } + + beforeEach(async () => { + element = fixtureSync(``, div); + + element.renderer = (wrapper) => { + render( + html` +
Header content
+
Content
+
`, + wrapper, + ); + }; + + element.items = [ + { title: 'Widget 1', colspan: 1 }, + { title: 'Widget 2', colspan: 1 }, + { + title: 'Section title', + items: [{ colspan: 1, rowspan: 1 }, { colspan: 1 }], + }, + ]; + await nextFrame(); + }); + + it('default', async () => { + await visualDiff(div, getName('default')); + }); + + it('focused widget', async () => { + element.editable = true; + await sendKeys({ press: 'Tab' }); + await visualDiff(div, getName('focused-widget')); + }); + + it('selected widget', async () => { + element.editable = true; + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Enter' }); + await visualDiff(div, getName('selected-widget')); + }); + + it('resize mode', async () => { + element.editable = true; + const firstWidget = getElementFromCell(element, 0, 0)!; + const resizeHandle = getResizeHandle(firstWidget) as HTMLElement; + resizeHandle.click(); + await nextFrame(); + await visualDiff(div, getName('resize-mode')); + }); + + it('move mode', async () => { + element.editable = true; + const firstWidget = getElementFromCell(element, 0, 0)!; + const moveHandle = getDraggable(firstWidget) as HTMLElement; + moveHandle.click(); + await nextFrame(); + await visualDiff(div, getName('move-mode')); + }); + + it('dragged widget', async () => { + element.editable = true; + fireDragStart(getElementFromCell(element, 0, 0)!); + await nextFrame(); + + await visualDiff(div, getName('dragged-widget')); + }); + + it('resized widget', async () => { + element.editable = true; + fireResizeStart(getElementFromCell(element, 0, 0)!); + await nextFrame(); + fireResizeOver(getElementFromCell(element, 0, 0)!, 'end'); + await nextFrame(); + + await visualDiff(div, getName('resized-widget')); + }); + + it('no gap', async () => { + element.style.setProperty('--vaadin-dashboard-gap', '0px'); + await nextFrame(); + await visualDiff(div, getName('no-gap')); + }); + + it('editable', async () => { + element.editable = true; + await visualDiff(div, getName('editable')); + }); + + describe('long title', () => { + beforeEach(async () => { + element.items = [ + { colspan: 1 }, + { colspan: 1 }, + { + title: + 'Section long title: Nunc sit amet suscipit tellus, id fermentum massa. Aliquam vel tellus cursus, sodales ligula sed, iaculis justo.', + items: [{ colspan: 1, rowspan: 1 }, { colspan: 1 }], + }, + ]; + + element.renderer = (wrapper) => { + render( + html` +
Header content
+
Content
+
`, + wrapper, + ); + }; + + await nextFrame(); + }); + + it('title wrap', async () => { + await nextFrame(); + await visualDiff(div, getName('title-wrap')); + }); + + it('no title wrap', async () => { + element.style.setProperty('--vaadin-dashboard-widget-title-wrap', 'nowrap'); + await nextFrame(); + await visualDiff(div, getName('no-title-wrap')); + }); + }); + }); +}); diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-default.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-default.png new file mode 100644 index 00000000000..1df691213a7 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-default.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-dragged-widget.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-dragged-widget.png new file mode 100644 index 00000000000..31042085dc6 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-dragged-widget.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-editable.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-editable.png new file mode 100644 index 00000000000..267b970eb1b Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-editable.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-focused-widget.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-focused-widget.png new file mode 100644 index 00000000000..8bc2c2047a9 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-focused-widget.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-move-mode.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-move-mode.png new file mode 100644 index 00000000000..4a481e2f0b8 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-move-mode.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-no-gap.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-no-gap.png new file mode 100644 index 00000000000..38c949e04cc Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-no-gap.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-no-title-wrap.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-no-title-wrap.png new file mode 100644 index 00000000000..a65c798efcb Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-no-title-wrap.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-resize-mode.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-resize-mode.png new file mode 100644 index 00000000000..689030d3e61 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-resize-mode.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-resized-widget.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-resized-widget.png new file mode 100644 index 00000000000..24f856b5d04 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-resized-widget.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-selected-widget.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-selected-widget.png new file mode 100644 index 00000000000..e760eb0c9d5 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-selected-widget.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-title-wrap.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-title-wrap.png new file mode 100644 index 00000000000..ef8a713f785 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/ltr-title-wrap.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-default.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-default.png new file mode 100644 index 00000000000..2d1f3d77027 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-default.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-dragged-widget.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-dragged-widget.png new file mode 100644 index 00000000000..a1dc4325b92 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-dragged-widget.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-editable.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-editable.png new file mode 100644 index 00000000000..55b1e95c9b7 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-editable.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-focused-widget.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-focused-widget.png new file mode 100644 index 00000000000..78c81aae4f7 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-focused-widget.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-move-mode.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-move-mode.png new file mode 100644 index 00000000000..12d1cb41c37 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-move-mode.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-no-gap.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-no-gap.png new file mode 100644 index 00000000000..863524ffe81 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-no-gap.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-no-title-wrap.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-no-title-wrap.png new file mode 100644 index 00000000000..cc6e3416f79 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-no-title-wrap.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-resize-mode.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-resize-mode.png new file mode 100644 index 00000000000..0bfe60db455 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-resize-mode.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-resized-widget.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-resized-widget.png new file mode 100644 index 00000000000..c4d6764bc73 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-resized-widget.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-selected-widget.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-selected-widget.png new file mode 100644 index 00000000000..150b0e64cd9 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-selected-widget.png differ diff --git a/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-title-wrap.png b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-title-wrap.png new file mode 100644 index 00000000000..6276f3d6335 Binary files /dev/null and b/packages/dashboard/test/visual/base/screenshots/dashboard/baseline/rtl-title-wrap.png differ