From 4ef079ea9dde16e60cd7474a9029248b49807640 Mon Sep 17 00:00:00 2001 From: Ekhorn Date: Thu, 22 Oct 2026 11:41:48 +0200 Subject: [PATCH 1/4] Add tabs and tab component --- .../docs/components/or-vaadin-tabs.mdx | 30 ++++ .../or-vaadin-components/package.json | 1 + .../or-vaadin-components/src/index.ts | 2 + .../or-vaadin-components/src/or-vaadin-tab.ts | 89 +++++++++++ .../src/or-vaadin-tabs.ts | 120 +++++++++++++++ .../stories/or-vaadin-tabs.stories.ts | 140 ++++++++++++++++++ .../test/or-vaadin-tabs.test.ts | 79 ++++++++++ yarn.lock | 62 ++++++++ 8 files changed, 523 insertions(+) create mode 100644 ui/app/storybook/docs/components/or-vaadin-tabs.mdx create mode 100644 ui/component/or-vaadin-components/src/or-vaadin-tab.ts create mode 100644 ui/component/or-vaadin-components/src/or-vaadin-tabs.ts create mode 100644 ui/component/or-vaadin-components/stories/or-vaadin-tabs.stories.ts create mode 100644 ui/component/or-vaadin-components/test/or-vaadin-tabs.test.ts diff --git a/ui/app/storybook/docs/components/or-vaadin-tabs.mdx b/ui/app/storybook/docs/components/or-vaadin-tabs.mdx new file mode 100644 index 0000000000..da5452ae29 --- /dev/null +++ b/ui/app/storybook/docs/components/or-vaadin-tabs.mdx @@ -0,0 +1,30 @@ +{/* prettier-ignore-start */} + +{/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */} + +{/* prettier-ignore-end */} + +import { Meta } from "@storybook/addon-docs/blocks"; +import * as ComponentStory from "../../../../component/or-vaadin-components/stories/or-vaadin-tabs.stories"; // stories need relative imports +import { ComponentDocs } from "../../util/componentdocs.mdx"; + + + + diff --git a/ui/component/or-vaadin-components/package.json b/ui/component/or-vaadin-components/package.json index 426fcc4af9..19cfd72d43 100644 --- a/ui/component/or-vaadin-components/package.json +++ b/ui/component/or-vaadin-components/package.json @@ -56,6 +56,7 @@ "@vaadin/radio-group": "~25.3.2", "@vaadin/select": "~25.3.2", "@vaadin/slider": "~25.3.2", + "@vaadin/tabs": "~25.3.2", "@vaadin/text-area": "~25.3.2", "@vaadin/text-field": "~25.3.2", "@vaadin/time-picker": "~25.3.2", diff --git a/ui/component/or-vaadin-components/src/index.ts b/ui/component/or-vaadin-components/src/index.ts index 4cb3e07569..774b1fe1f0 100644 --- a/ui/component/or-vaadin-components/src/index.ts +++ b/ui/component/or-vaadin-components/src/index.ts @@ -36,6 +36,8 @@ export * from "./or-vaadin-password-field"; export * from "./or-vaadin-radio-group"; export * from "./or-vaadin-select"; export * from "./or-vaadin-slider"; +export * from "./or-vaadin-tab"; +export * from "./or-vaadin-tabs"; export * from "./or-vaadin-text-area"; export * from "./or-vaadin-text-field"; export * from "./or-vaadin-time-picker"; diff --git a/ui/component/or-vaadin-components/src/or-vaadin-tab.ts b/ui/component/or-vaadin-components/src/or-vaadin-tab.ts new file mode 100644 index 0000000000..2a1c0ee72e --- /dev/null +++ b/ui/component/or-vaadin-components/src/or-vaadin-tab.ts @@ -0,0 +1,89 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { customElement } from "lit/decorators.js"; +import type { LitElement } from "lit"; +import { Tab } from "@vaadin/tabs/vaadin-tab.js"; +import { registerStyles, css } from "@vaadin/vaadin-themable-mixin/register-styles.js"; +import type { OrVaadinComponent } from "./util"; + +/* + * The Vaadin base tab styles read the `--vaadin-tab-*` properties set here. The tab has no background of its own in + * either state, as `` draws the selected one as an indicator behind the tabs. + */ +registerStyles( + "or-vaadin-tab", + css` + :host { + --vaadin-tab-background: transparent; + --vaadin-tab-text-color: var(--or-tab-color, var(--lumo-secondary-text-color, #646b64)); + --vaadin-tab-font-size: var(--or-tab-font-size, var(--lumo-font-size-m, 1rem)); + --vaadin-tab-font-weight: var(--or-tab-font-weight, 700); + --vaadin-tab-line-height: var(--or-tab-line-height, 1.5); + --vaadin-tab-padding: var(--or-tab-padding, 0 var(--lumo-space-s, 8px)); + --vaadin-tab-border-radius: var(--or-tabs-radius, 99px); + + /* Equal widths let place its indicator without measuring the tabs */ + flex: 1 1 0; + min-width: 0; + min-height: var(--or-tab-height, var(--lumo-size-l, 44px)); + box-sizing: border-box; + text-align: center; + overflow-wrap: anywhere; + transition: color var(--or-tabs-transition-duration, 0.3s) ease-out; + } + + /* Stacked tabs fit their labels, so the labels need room to clear the rounded ends */ + :host([orientation="vertical"]) { + --vaadin-tab-padding: var(--or-tab-padding, 0 var(--lumo-space-m, 1rem)); + justify-content: center; + } + + :host([selected]) { + --vaadin-tab-background: transparent; + --vaadin-tab-text-color: var(--or-tab-selected-color, var(--lumo-body-text-color, #1f211f)); + } + + @media (prefers-reduced-motion: reduce) { + :host { + transition: none; + } + } + ` +); + +/** + * A tab for ``. It takes an equal share of the tab bar, and wrapping its label in an `` makes the + * whole tab a link. + * + * @customElement "or-vaadin-tab" + * @slot - Label content, such as text, an icon or an `` for navigation. + * @cssprop --or-tab-color - Label color when not selected. + * @cssprop --or-tab-selected-color - Label color when selected. + * @cssprop --or-tab-font-size - Label font size. + * @cssprop --or-tab-font-weight - Label font weight. + * @cssprop --or-tab-line-height - Label line height. + * @cssprop --or-tab-height - Minimum height of the tab. + * @cssprop --or-tab-padding - Padding of the tab. + */ +@customElement("or-vaadin-tab") +export class OrVaadinTab extends (Tab as new () => Tab & LitElement) implements OrVaadinComponent { + static get is() { + return "or-vaadin-tab"; + } +} diff --git a/ui/component/or-vaadin-components/src/or-vaadin-tabs.ts b/ui/component/or-vaadin-components/src/or-vaadin-tabs.ts new file mode 100644 index 0000000000..3bfc4dd88d --- /dev/null +++ b/ui/component/or-vaadin-components/src/or-vaadin-tabs.ts @@ -0,0 +1,120 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { customElement } from "lit/decorators.js"; +import { html, type LitElement } from "lit"; +import { styleMap } from "lit/directives/style-map.js"; +import { Tabs } from "@vaadin/tabs/vaadin-tabs.js"; +import { registerStyles, css } from "@vaadin/vaadin-themable-mixin/register-styles.js"; +import type { OrVaadinComponent } from "./util"; +import "./or-vaadin-tab"; + +/* + * The Vaadin base tabs styles read the `--vaadin-tabs-*` properties set here. The tabs share the bar equally, so the + * indicator is sized from the number of tabs and moved by the index of the selected one. + */ +registerStyles( + "or-vaadin-tabs", + css` + :host { + --_gap: var(--or-tabs-gap, 10px); + --_padding: var(--or-tabs-padding, var(--lumo-space-xs, 4px)); + --_radius: var(--or-tabs-radius, 99px); + --vaadin-tabs-background: var(--or-tabs-background, var(--lumo-contrast-5pct, #f2f3f2)); + --vaadin-tabs-border-radius: var(--_radius); + --vaadin-tabs-gap: var(--_gap); + --vaadin-tabs-padding: var(--_padding); + } + + /* A stacked bar is narrower than it is tall, so round it as much as the horizontal bar instead of fully */ + :host([orientation="vertical"]) { + --_radius: var(--or-tabs-radius, calc(var(--or-tab-height, var(--lumo-size-l, 44px)) / 2 + var(--_padding))); + } + + #indicator { + position: absolute; + inset-block: var(--_padding); + inset-inline-start: var(--_padding); + width: calc((100% - 2 * var(--_padding) - (var(--_count) - 1) * var(--_gap)) / var(--_count)); + border-radius: var(--_radius); + background: var(--or-tabs-indicator-color, var(--lumo-base-color, #ffffff)); + translate: calc(var(--_direction, 1) * var(--_index) * (100% + var(--_gap))) 0; + transition: translate var(--or-tabs-transition-duration, 0.3s) ease-out; + } + + :host([dir="rtl"]) #indicator { + --_direction: -1; + } + + :host([orientation="vertical"]) #indicator { + inset-inline: var(--_padding); + inset-block-end: auto; + width: auto; + height: calc((100% - 2 * var(--_padding) - (var(--_count) - 1) * var(--_gap)) / var(--_count)); + translate: 0 calc(var(--_index) * (100% + var(--_gap))); + } + + @media (prefers-reduced-motion: reduce) { + #indicator { + transition: none; + } + } + ` +); + +/** + * A row of `` elements in a pill-shaped bar, with an indicator that slides behind the selected tab. + * The tabs share the bar equally. Hidden tabs are left out of the layout, but only when they are hidden before they + * are added or the selection changes. + * + * @customElement "or-vaadin-tabs" + * @slot - The `` elements. + * @attr {string} orientation - `horizontal` (default) or `vertical`. + * @fires {CustomEvent} selected-changed - Fired when the `selected` property changes. + * @fires {CustomEvent} items-changed - Fired when the `items` property changes. + * @cssprop --or-tabs-background - Background of the bar. + * @cssprop --or-tabs-indicator-color - Color of the indicator behind the selected tab. + * @cssprop --or-tabs-padding - Space between the bar edge and the tabs. + * @cssprop --or-tabs-gap - Space between the tabs. + * @cssprop --or-tabs-radius - Corner radius of the bar, the indicator and the tabs. By default the tabs and the + * indicator are full pills, and the bar is rounded to match them. + * @cssprop --or-tabs-transition-duration - Duration of the indicator slide and the label color change. + */ +@customElement("or-vaadin-tabs") +export class OrVaadinTabs extends (Tabs as new () => Tabs & LitElement) implements OrVaadinComponent { + static get is() { + return "or-vaadin-tabs"; + } + + protected override render() { + const items = this.items ?? []; + const visible = items.filter((item) => !(item as HTMLElement).hidden); + const selected = this.selected == null ? undefined : items[this.selected]; + const index = selected ? visible.indexOf(selected) : -1; + + return html` + + ${super.render()} + `; + } +} diff --git a/ui/component/or-vaadin-components/stories/or-vaadin-tabs.stories.ts b/ui/component/or-vaadin-components/stories/or-vaadin-tabs.stories.ts new file mode 100644 index 0000000000..3da2160a3e --- /dev/null +++ b/ui/component/or-vaadin-components/stories/or-vaadin-tabs.stories.ts @@ -0,0 +1,140 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { setCustomElementsManifest, type Meta, type StoryObj } from "@storybook/web-components"; +import { getORStorybookHelpers } from "../../storybook-utils"; +import customElements from "../build/custom-elements.json" with { type: "json" }; +import packageJson from "../package.json" with { type: "json" }; +import { html } from "lit"; +import "../src/or-vaadin-tabs"; + +const tagName = "or-vaadin-tabs"; +type Story = StoryObj; +setCustomElementsManifest(customElements); + +const { events, args, argTypes, template } = getORStorybookHelpers(tagName); + +const pages = [ + { name: "Status", content: "Everything is running normally. The last reading came in a minute ago." }, + { name: "Mode", content: "The asset runs automatically and follows its schedule." }, + { name: "Settings", content: "Change the name, location and schedule of the asset." }, +]; + +/** Tabs for the pages, linked to the panels with the same id prefix. */ +const pageTabs = (prefix: string) => + pages.map( + ({ name }) => + html`${name}` + ); + +/** Panels with the content of the pages, of which only the selected one is shown. */ +const pagePanels = (prefix: string, selected: number) => + pages.map( + ({ name, content }, index) => + html`

+ ${content} +

` + ); + +/** Shows the panel of the selected tab, out of the panels next to the tabs. */ +const showPanel = (event: CustomEvent<{ value: number }>) => { + const panels = (event.target as Element).parentElement?.querySelectorAll('[role="tabpanel"]'); + panels?.forEach((panel, index) => (panel.hidden = index !== event.detail.value)); +}; + +const meta: Meta = { + title: "Playground/or-vaadin-components/tabs", + component: tagName, + args, + argTypes, + render: (storyArgs) => + template(storyArgs, html`${pages.map(({ name }) => html`${name}`)}`), + excludeStories: /^[a-z].*/, + parameters: { + actions: { + handles: events, + }, + docs: { + subtitle: `<${tagName}>`, + description: + "Tabs is a more mobile-friendly alternative to `` for switching between views or pages. The `` elements share a pill-shaped bar equally, and an indicator slides behind the selected one. Selection works as in ``, through the `selected` index and the `selected-changed` event, and the colors, sizes and spacing are set through `--or-tabs-*` and `--or-tab-*` properties.", + }, + }, +}; + +export const Primary: Story = { + args: { + selected: 1, + }, +}; + +/** Tabs that switch the content below them. */ +export const Content: Story = { + name: "Content", + parameters: { + title: "Content", + summary: "Listen to `selected-changed` to show the content of the selected tab.", + docs: { + story: { + height: "160px", + }, + }, + }, + render: () => html` +
+ ${pageTabs("content")} + ${pagePanels("content", 0)} +
+ `, +}; + +/** Tabs stacked vertically beside their content. */ +export const Vertical: Story = { + name: "Vertical", + parameters: { + title: "Vertical", + summary: + 'Set `orientation="vertical"` to stack the tabs, and the indicator then slides vertically. The content can then sit beside the tabs.', + docs: { + story: { + height: "200px", + }, + }, + }, + render: () => html` +
+ + ${pageTabs("vertical")} + + ${pagePanels("vertical", 2)} +
+ `, +}; + +export const examples = [Content, Vertical]; + +export { customElements, packageJson }; + +export default meta; diff --git a/ui/component/or-vaadin-components/test/or-vaadin-tabs.test.ts b/ui/component/or-vaadin-components/test/or-vaadin-tabs.test.ts new file mode 100644 index 0000000000..8956032bed --- /dev/null +++ b/ui/component/or-vaadin-components/test/or-vaadin-tabs.test.ts @@ -0,0 +1,79 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { ct, expect } from "./fixtures"; + +import { OrVaadinTab } from "@openremote/or-vaadin-components/or-vaadin-tab"; +import { OrVaadinTabs } from "@openremote/or-vaadin-components/or-vaadin-tabs"; + +const tabs = [ + "Status", + "Mode", + "Settings", +]; + +ct("should select the clicked tab", async ({ mount }) => { + const selected: number[] = []; + const component = await mount(OrVaadinTabs, { + props: { selected: 1 }, + slots: { default: tabs }, + on: { + "selected-changed": (detail: { value: number }) => { + selected.push(detail.value); + }, + }, + hooksConfig: { components: [OrVaadinTab] }, + }); + + await expect(component.getByRole("tab", { selected: true })).toHaveText("Mode"); + + await component.getByRole("tab", { name: "Settings" }).click(); + + await expect(component.getByRole("tab", { selected: true })).toHaveText("Settings"); + await expect.poll(() => selected.at(-1)).toBe(2); +}); + +ct("should move the indicator behind the selected tab", async ({ mount }) => { + const component = await mount(OrVaadinTabs, { + props: { selected: 0 }, + slots: { default: tabs }, + hooksConfig: { components: [OrVaadinTab] }, + }); + const indicator = component.locator("#indicator"); + const settings = component.getByRole("tab", { name: "Settings" }); + + await settings.click(); + + // The indicator slides, so poll until it has settled on the tab + await expect + .poll(async () => { + const [indicatorBox, tabBox] = await Promise.all([indicator.boundingBox(), settings.boundingBox()]); + return indicatorBox && tabBox && [indicatorBox.x - tabBox.x, indicatorBox.width - tabBox.width].map(Math.round); + }) + .toEqual([0, 0]); +}); + +ct("should hide the indicator when no tab is selected", async ({ mount }) => { + const component = await mount(OrVaadinTabs, { + props: { selected: -1 }, + slots: { default: tabs }, + hooksConfig: { components: [OrVaadinTab] }, + }); + + await expect(component.locator("#indicator")).toBeHidden(); +}); diff --git a/yarn.lock b/yarn.lock index 9180e7c33e..d543270e8e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2414,6 +2414,7 @@ __metadata: "@vaadin/radio-group": "npm:~25.3.2" "@vaadin/select": "npm:~25.3.2" "@vaadin/slider": "npm:~25.3.2" + "@vaadin/tabs": "npm:~25.3.2" "@vaadin/text-area": "npm:~25.3.2" "@vaadin/text-field": "npm:~25.3.2" "@vaadin/time-picker": "npm:~25.3.2" @@ -4261,6 +4262,17 @@ __metadata: languageName: node linkType: hard +"@vaadin/a11y-base@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/a11y-base@npm:25.3.3" + dependencies: + "@open-wc/dedupe-mixin": "npm:^1.3.0" + "@vaadin/component-base": "npm:~25.3.3" + lit: "npm:^3.0.0" + checksum: 10c0/b48532417f9610addb5c6179748676de6c95a851724a8da6029615961b78c2e4bbd8fee90daef384386aff206717cb86abdfc391e0658ea4d28ef79d494b8100 + languageName: node + linkType: hard + "@vaadin/badge@npm:~25.3.2": version: 25.3.2 resolution: "@vaadin/badge@npm:25.3.2" @@ -4345,6 +4357,18 @@ __metadata: languageName: node linkType: hard +"@vaadin/component-base@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/component-base@npm:25.3.3" + dependencies: + "@open-wc/dedupe-mixin": "npm:^1.3.0" + "@vaadin/vaadin-development-mode-detector": "npm:^2.0.0" + "@vaadin/vaadin-usage-statistics": "npm:^2.1.0" + lit: "npm:^3.0.0" + checksum: 10c0/e7a7c9280b5d5f99bc9d4691379add39f28012f96308f559f0757704c393501b1a796b9639371e91348302dc2104c1da1955799435e5eed6f2c28f840c511beb + languageName: node + linkType: hard + "@vaadin/confirm-dialog@npm:~25.3.2": version: 25.3.2 resolution: "@vaadin/confirm-dialog@npm:25.3.2" @@ -4473,6 +4497,19 @@ __metadata: languageName: node linkType: hard +"@vaadin/item@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/item@npm:25.3.3" + dependencies: + "@open-wc/dedupe-mixin": "npm:^1.3.0" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" + lit: "npm:^3.0.0" + checksum: 10c0/ee9cb4c4a70a8529c599359de998f6be8e6545dd8a963cf2c2a9b3ec21b70dbfac93576b9c5d2c41fd1db942722b9d7ea3a57600d931897f5601180fb36832e9 + languageName: node + linkType: hard + "@vaadin/list-box@npm:~25.3.2": version: 25.3.2 resolution: "@vaadin/list-box@npm:25.3.2" @@ -4626,6 +4663,20 @@ __metadata: languageName: node linkType: hard +"@vaadin/tabs@npm:~25.3.2": + version: 25.3.3 + resolution: "@vaadin/tabs@npm:25.3.3" + dependencies: + "@open-wc/dedupe-mixin": "npm:^1.3.0" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/item": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" + lit: "npm:^3.0.0" + checksum: 10c0/2982a61f930dcd641496b0614f223f693afc0c29d266e14affc8b07ff83f3642beb69acc941cb8c720004a021054a8ad2dae8831196d8ee9540e5fad3cf5e6bc + languageName: node + linkType: hard + "@vaadin/text-area@npm:~25.3.2": version: 25.3.2 resolution: "@vaadin/text-area@npm:25.3.2" @@ -4703,6 +4754,17 @@ __metadata: languageName: node linkType: hard +"@vaadin/vaadin-themable-mixin@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/vaadin-themable-mixin@npm:25.3.3" + dependencies: + "@open-wc/dedupe-mixin": "npm:^1.3.0" + "@vaadin/component-base": "npm:~25.3.3" + lit: "npm:^3.0.0" + checksum: 10c0/5d355c9461e5549eeac7abe83b84c652b5879875d9e3b37990a15ff3fd9c6d98e7e0b5522fa321d3e3f3d8f389d2b6ea7884f15e5906dcf7490029a992e05c13 + languageName: node + linkType: hard + "@vaadin/vaadin-usage-statistics@npm:^2.1.0": version: 2.1.3 resolution: "@vaadin/vaadin-usage-statistics@npm:2.1.3" From 3be23c8054bfc506a75d23804a0ff1b8b223d3b8 Mon Sep 17 00:00:00 2001 From: Ekhorn Date: Thu, 22 Oct 2026 14:36:35 +0200 Subject: [PATCH 2/4] Bump Vaadin dependencies to 25.3.3 --- .../or-vaadin-components/package.json | 46 +- ui/component/theme/package.json | 2 +- yarn.lock | 581 ++++++++---------- 3 files changed, 291 insertions(+), 338 deletions(-) diff --git a/ui/component/or-vaadin-components/package.json b/ui/component/or-vaadin-components/package.json index 19cfd72d43..e8aac0f78b 100644 --- a/ui/component/or-vaadin-components/package.json +++ b/ui/component/or-vaadin-components/package.json @@ -38,29 +38,29 @@ "@openremote/core": "workspace:*", "@openremote/model": "workspace:*", "@openremote/or-element": "workspace:*", - "@vaadin/badge": "~25.3.2", - "@vaadin/button": "~25.3.2", - "@vaadin/checkbox": "~25.3.2", - "@vaadin/checkbox-group": "~25.3.2", - "@vaadin/combo-box": "~25.3.2", - "@vaadin/confirm-dialog": "~25.3.2", - "@vaadin/date-picker": "~25.3.2", - "@vaadin/date-time-picker": "~25.3.2", - "@vaadin/dialog": "~25.3.2", - "@vaadin/item": "~25.3.2", - "@vaadin/list-box": "~25.3.2", - "@vaadin/menu-bar": "~25.3.2", - "@vaadin/multi-select-combo-box": "~25.3.2", - "@vaadin/number-field": "~25.3.2", - "@vaadin/password-field": "~25.3.2", - "@vaadin/radio-group": "~25.3.2", - "@vaadin/select": "~25.3.2", - "@vaadin/slider": "~25.3.2", - "@vaadin/tabs": "~25.3.2", - "@vaadin/text-area": "~25.3.2", - "@vaadin/text-field": "~25.3.2", - "@vaadin/time-picker": "~25.3.2", - "@vaadin/vaadin-themable-mixin": "~25.3.2", + "@vaadin/badge": "~25.3.3", + "@vaadin/button": "~25.3.3", + "@vaadin/checkbox": "~25.3.3", + "@vaadin/checkbox-group": "~25.3.3", + "@vaadin/combo-box": "~25.3.3", + "@vaadin/confirm-dialog": "~25.3.3", + "@vaadin/date-picker": "~25.3.3", + "@vaadin/date-time-picker": "~25.3.3", + "@vaadin/dialog": "~25.3.3", + "@vaadin/item": "~25.3.3", + "@vaadin/list-box": "~25.3.3", + "@vaadin/menu-bar": "~25.3.3", + "@vaadin/multi-select-combo-box": "~25.3.3", + "@vaadin/number-field": "~25.3.3", + "@vaadin/password-field": "~25.3.3", + "@vaadin/radio-group": "~25.3.3", + "@vaadin/select": "~25.3.3", + "@vaadin/slider": "~25.3.3", + "@vaadin/tabs": "~25.3.3", + "@vaadin/text-area": "~25.3.3", + "@vaadin/text-field": "~25.3.3", + "@vaadin/time-picker": "~25.3.3", + "@vaadin/vaadin-themable-mixin": "~25.3.3", "i18next": "^26.3.6", "lit": "^3.3.1" }, diff --git a/ui/component/theme/package.json b/ui/component/theme/package.json index 144a8bcc02..12f9dd3924 100644 --- a/ui/component/theme/package.json +++ b/ui/component/theme/package.json @@ -22,7 +22,7 @@ "repository": "https://github.com/openremote/core", "license": "AGPL-3.0-or-later", "dependencies": { - "@vaadin/vaadin-lumo-styles": "~25.3.2", + "@vaadin/vaadin-lumo-styles": "~25.3.3", "inter-ui": "^4.1.1" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index d543270e8e..1ae639e91c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2396,29 +2396,29 @@ __metadata: "@openremote/model": "workspace:*" "@openremote/or-element": "workspace:*" "@openremote/util": "workspace:*" - "@vaadin/badge": "npm:~25.3.2" - "@vaadin/button": "npm:~25.3.2" - "@vaadin/checkbox": "npm:~25.3.2" - "@vaadin/checkbox-group": "npm:~25.3.2" - "@vaadin/combo-box": "npm:~25.3.2" - "@vaadin/confirm-dialog": "npm:~25.3.2" - "@vaadin/date-picker": "npm:~25.3.2" - "@vaadin/date-time-picker": "npm:~25.3.2" - "@vaadin/dialog": "npm:~25.3.2" - "@vaadin/item": "npm:~25.3.2" - "@vaadin/list-box": "npm:~25.3.2" - "@vaadin/menu-bar": "npm:~25.3.2" - "@vaadin/multi-select-combo-box": "npm:~25.3.2" - "@vaadin/number-field": "npm:~25.3.2" - "@vaadin/password-field": "npm:~25.3.2" - "@vaadin/radio-group": "npm:~25.3.2" - "@vaadin/select": "npm:~25.3.2" - "@vaadin/slider": "npm:~25.3.2" - "@vaadin/tabs": "npm:~25.3.2" - "@vaadin/text-area": "npm:~25.3.2" - "@vaadin/text-field": "npm:~25.3.2" - "@vaadin/time-picker": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/badge": "npm:~25.3.3" + "@vaadin/button": "npm:~25.3.3" + "@vaadin/checkbox": "npm:~25.3.3" + "@vaadin/checkbox-group": "npm:~25.3.3" + "@vaadin/combo-box": "npm:~25.3.3" + "@vaadin/confirm-dialog": "npm:~25.3.3" + "@vaadin/date-picker": "npm:~25.3.3" + "@vaadin/date-time-picker": "npm:~25.3.3" + "@vaadin/dialog": "npm:~25.3.3" + "@vaadin/item": "npm:~25.3.3" + "@vaadin/list-box": "npm:~25.3.3" + "@vaadin/menu-bar": "npm:~25.3.3" + "@vaadin/multi-select-combo-box": "npm:~25.3.3" + "@vaadin/number-field": "npm:~25.3.3" + "@vaadin/password-field": "npm:~25.3.3" + "@vaadin/radio-group": "npm:~25.3.3" + "@vaadin/select": "npm:~25.3.3" + "@vaadin/slider": "npm:~25.3.3" + "@vaadin/tabs": "npm:~25.3.3" + "@vaadin/text-area": "npm:~25.3.3" + "@vaadin/text-field": "npm:~25.3.3" + "@vaadin/time-picker": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" i18next: "npm:^26.3.6" lit: "npm:^3.3.1" languageName: unknown @@ -2484,7 +2484,7 @@ __metadata: resolution: "@openremote/theme@workspace:ui/component/theme" dependencies: "@openremote/util": "workspace:*" - "@vaadin/vaadin-lumo-styles": "npm:~25.3.2" + "@vaadin/vaadin-lumo-styles": "npm:~25.3.3" inter-ui: "npm:^4.1.1" languageName: unknown linkType: soft @@ -4251,17 +4251,6 @@ __metadata: languageName: node linkType: hard -"@vaadin/a11y-base@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/a11y-base@npm:25.3.2" - dependencies: - "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/component-base": "npm:~25.3.2" - lit: "npm:^3.0.0" - checksum: 10c0/f4b84275703ecc4cb18860b80d2238571557fa78a69d9777fbb5be0fb4dd97cfca662546ff74ac744790a10a471abb4cb664bc9a573b7bfa1759da09c93d02cb - languageName: node - linkType: hard - "@vaadin/a11y-base@npm:~25.3.3": version: 25.3.3 resolution: "@vaadin/a11y-base@npm:25.3.3" @@ -4273,87 +4262,75 @@ __metadata: languageName: node linkType: hard -"@vaadin/badge@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/badge@npm:25.3.2" - dependencies: - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" - lit: "npm:^3.0.0" - checksum: 10c0/9f49d5c202de90b626151585afb5fe3231ba01d825d6e07e4a0cc51cbc32764934bec233bf4b985ebd0797b006567341787d6a4d0ce40e01b93b793266558abe - languageName: node - linkType: hard - -"@vaadin/button@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/button@npm:25.3.2" +"@vaadin/badge@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/badge@npm:25.3.3" dependencies: - "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/4654f9b61c73c3f9c0c75a88c68a7b224aa394044ed30bcf3752b6216bb54f132670ddc826d88b396f0416a104fbcb5b1009741f0d64de06e578af786af65eb3 + checksum: 10c0/44914ded00c30cfcf1e4fb1a89390fb114a6ab852f2dee76b5d93f0dd09afbbd2be1fd0d0f15431c02ca22771d84b5e6367199c67786e9a6ad20264339cfc80b languageName: node linkType: hard -"@vaadin/checkbox-group@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/checkbox-group@npm:25.3.2" +"@vaadin/button@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/button@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/checkbox": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/field-base": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/410d96b5801a9dc73a905670602b462b3a3d6e070b2a34da92647955200ac0e08f74a328750059b30788d0aa4a72b89ea3178ce5eb4d436c82d7caf311b4d964 + checksum: 10c0/6bd44c6dbab554eb69633f0bec47d921a89714435f3081e9d190f291ce88056e9fb876e91071a5f554d86dd6823c363367c148db64bf9a8356c05f35d420974c languageName: node linkType: hard -"@vaadin/checkbox@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/checkbox@npm:25.3.2" +"@vaadin/checkbox-group@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/checkbox-group@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/field-base": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/checkbox": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/field-base": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/72e18b16ab1ea8b0e05476629d4bc11056d01d4268c5fd346f728003fa6793388e0eef1d2e28d0088819fb59f0d543ababaa59d101efc7271840b05e2b842adc + checksum: 10c0/df682514fea9b5c2cb4a8018a625e931fccb110d803f8bfd74d41bf26a4cd430a9241b6f32b8227791d3b0655a195c42813cf2e4f8e714b9991515e98110e6d8 languageName: node linkType: hard -"@vaadin/combo-box@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/combo-box@npm:25.3.2" +"@vaadin/checkbox@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/checkbox@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/field-base": "npm:~25.3.2" - "@vaadin/input-container": "npm:~25.3.2" - "@vaadin/item": "npm:~25.3.2" - "@vaadin/lit-renderer": "npm:~25.3.2" - "@vaadin/overlay": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/field-base": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/5519589f446d6196206067b55c543f60cf4f6f7644a19a18bb9bb099d183bf2a92720a78367b2b370265ccda429bdc75e99865953ba789459b8bf2b957986bb2 + checksum: 10c0/90c0e820a4fd01fd5a9815c98e373fbfc80ccdca17af1211dd0487f6b3c12b506ea5408ee1c72b6c2b846fd96606f538f34780ea780b8280943f1d071caf0698 languageName: node linkType: hard -"@vaadin/component-base@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/component-base@npm:25.3.2" +"@vaadin/combo-box@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/combo-box@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/vaadin-development-mode-detector": "npm:^2.0.0" - "@vaadin/vaadin-usage-statistics": "npm:^2.1.0" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/field-base": "npm:~25.3.3" + "@vaadin/input-container": "npm:~25.3.3" + "@vaadin/item": "npm:~25.3.3" + "@vaadin/lit-renderer": "npm:~25.3.3" + "@vaadin/overlay": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/b2eb290284c6fe97eb916a5abf592e89578432457917a5be3e3c34ed1591639119884dc5aede3f6988ea003e7fe50a8986a4b5c211308ad3e70d4d136cb81a00 + checksum: 10c0/1743a8f7e7144f061c8d9aee7680d4422be81ad377738d6e3a73d9709cdc193ee88a8f3f5926d1fe480d1c3436934fdd8854ce79f4667ee525d876fa324f2079 languageName: node linkType: hard @@ -4369,131 +4346,118 @@ __metadata: languageName: node linkType: hard -"@vaadin/confirm-dialog@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/confirm-dialog@npm:25.3.2" +"@vaadin/confirm-dialog@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/confirm-dialog@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/button": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/dialog": "npm:~25.3.2" - "@vaadin/overlay": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/button": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/dialog": "npm:~25.3.3" + "@vaadin/overlay": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/de87a0ab22e2971f7e481491eac11121fe72823562582d79c76b91bbd364a962d0fe0777ead53e282b8d92b26dfae24c7111cf1ca2106b3f4f80ae93efb8609e + checksum: 10c0/52388b1898e5881ed110cfa55629cf18e96e3d0bc34cbacb3fb701f8045926c3ee3b9868a7f28773160e2ac43cb5541c081f829bd5e02264828984a01ee9b78d languageName: node linkType: hard -"@vaadin/context-menu@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/context-menu@npm:25.3.2" +"@vaadin/context-menu@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/context-menu@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/item": "npm:~25.3.2" - "@vaadin/list-box": "npm:~25.3.2" - "@vaadin/lit-renderer": "npm:~25.3.2" - "@vaadin/overlay": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/item": "npm:~25.3.3" + "@vaadin/list-box": "npm:~25.3.3" + "@vaadin/lit-renderer": "npm:~25.3.3" + "@vaadin/overlay": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/e52fc2658753a0c233b25b306591ccf0f29b89af400df64efed7d5cd4841df034af580fd33cbfed9354584e9234b7a8eef2dd814c61fd85aca12f76991978dcf + checksum: 10c0/f9c5628ab7e13fe812a9fe3cdb7caaf642c21550fd2737cf55d98fded290e5485a618d449fefe20c9a27b8339ab57b1dada7029c84be061605da6331ff79f3ae languageName: node linkType: hard -"@vaadin/date-picker@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/date-picker@npm:25.3.2" +"@vaadin/date-picker@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/date-picker@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/button": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/field-base": "npm:~25.3.2" - "@vaadin/input-container": "npm:~25.3.2" - "@vaadin/overlay": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/button": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/field-base": "npm:~25.3.3" + "@vaadin/input-container": "npm:~25.3.3" + "@vaadin/overlay": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/8628d5bebb1b6552c21d5f5f7c69a7535aa6a689f04659109e67e77249951b268669773d479e38e141d0c4f3c0334092cb7f16079e518b284c4919db7d115139 + checksum: 10c0/0b95383b90ada678db8cd6e29f4f86f91d77a41472d49c13a4f9b25ad5242acc05f8554376f70082400d1f461e30a235905824374a290fbdc177f0e90646f69b languageName: node linkType: hard -"@vaadin/date-time-picker@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/date-time-picker@npm:25.3.2" +"@vaadin/date-time-picker@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/date-time-picker@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/date-picker": "npm:~25.3.2" - "@vaadin/field-base": "npm:~25.3.2" - "@vaadin/time-picker": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/date-picker": "npm:~25.3.3" + "@vaadin/field-base": "npm:~25.3.3" + "@vaadin/time-picker": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/273b0e58c9ed5aac113afd18f2929811821e92f24383c2fb78b1a8a307bc1be727bd330b7b7441dd54706c2c0f67f31ea6e2213c0824439b448fa3796abf6549 + checksum: 10c0/99486f5cb498dde0b58f73d985bff1a08f893547d38acfb3a98073433196a20f606b8610a69242133f1b67e107be4d3f131d09fe31ef414b2dbd5a027b75c589 languageName: node linkType: hard -"@vaadin/dialog@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/dialog@npm:25.3.2" +"@vaadin/dialog@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/dialog@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/lit-renderer": "npm:~25.3.2" - "@vaadin/overlay": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/lit-renderer": "npm:~25.3.3" + "@vaadin/overlay": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/7a3aeb2da88d38b49bc1f6aaae8cdfcf3267decdd39d5cc599df1f50e35080e2f4254fe7cc788b174d0f5645d8893d2fc9f3e88f3f83145e5fcf5adee740f9c7 + checksum: 10c0/0d3da50d2f8d7bd614c5ddad0c82544ba2f9e5a2c79bf2f4836887dfa1384972590cebe509aa7ffd7bd600970dd5ee53c24007dafb79bdc08f21043e1853b920 languageName: node linkType: hard -"@vaadin/field-base@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/field-base@npm:25.3.2" +"@vaadin/field-base@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/field-base@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/5b04ece51b0e0b83bc8941fc75b94b142aceedf23a4728323eb68ea46aa10fa0e5653b6268d293162a421f6e5612b690451ca4ef6d604aaee2f0c8d81dc59807 + checksum: 10c0/10099e083f0e133f0d6e6b455af53cb52c0a74a35b22f86b262ca991cda8950d465c167f810a18f24785aa744d52a85eae237754d7e223bdd907dc617ae7c570 languageName: node linkType: hard -"@vaadin/icon@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/icon@npm:25.3.2" +"@vaadin/icon@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/icon@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" - lit: "npm:^3.0.0" - checksum: 10c0/467aff926f39471f6bf78a61340c821966ac76b979ba5a27fa6ab98554386c049122cf0bf88243f039e34115f97004901a5d8e65e07d8f1f2b0c1a4c8a15ff0b - languageName: node - linkType: hard - -"@vaadin/input-container@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/input-container@npm:25.3.2" - dependencies: - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/e05589de0da765681d5b2609dd2471b3e9e2568f990fb1f610a18db19960cdcf144541659906c5945c72ded36cfe006ea6356ac75b66386a043ccb289f12f25f + checksum: 10c0/200c7f934292624525bde0174dc4b671e4694b86d4a4e9f475b0f695b1498084ce2110fddfeaf7f2330683089753a3829bc6707191115680dd1bc06f78dccb14 languageName: node linkType: hard -"@vaadin/item@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/item@npm:25.3.2" +"@vaadin/input-container@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/input-container@npm:25.3.3" dependencies: - "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/2f50a71a44bf336fe1c1c37b400c08221aaae368852b6a08d6aa185a013a38a7efbb3d23b3049d09912335aaad4bf7bedabaa1c5f4cb8314ac8e417336f0d279 + checksum: 10c0/6a64320f8b424f4f8ee6c438ad71bfac8c1d5eef4c844106b2a8c9b852986ccd96107b08ac2d38b9c91ad57c1a48c8d1c99f39b999478e9c6b2bc307811085fa languageName: node linkType: hard @@ -4510,160 +4474,160 @@ __metadata: languageName: node linkType: hard -"@vaadin/list-box@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/list-box@npm:25.3.2" +"@vaadin/list-box@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/list-box@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/item": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/item": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/0c445ebda2ac9851912a60490875561fb5fd39b43261001f78c340261c6ec3d8213f415f2b451a60e8cff2caf27c7ddd6917e2eb5be378d4b60cdd32ef69016e + checksum: 10c0/fce29664d5c83766940f501fc403e0d7d8bbd99fdc0d64dd437432ac14498686855d615f3160916dd4350e2359aa5765354b019716945098329d2207095a418d languageName: node linkType: hard -"@vaadin/lit-renderer@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/lit-renderer@npm:25.3.2" +"@vaadin/lit-renderer@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/lit-renderer@npm:25.3.3" dependencies: lit: "npm:^3.0.0" - checksum: 10c0/0519ed4133875fd0ecf20184429bde87b1d188d6f8589b493b6d3b47f8ec77f6332e2d43331e74581d7278b7d34ca176c91cb849ba642c3915b9ff92f9f36acf + checksum: 10c0/8fb314ca6ac03df743862a894395f014eded04231aa39090215b0667748a2409b6ce49d56c486ee2b4bd42e7932525b6b14ce70a94320b82ac3f8b046ce9dc2d languageName: node linkType: hard -"@vaadin/menu-bar@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/menu-bar@npm:25.3.2" +"@vaadin/menu-bar@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/menu-bar@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/button": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/context-menu": "npm:~25.3.2" - "@vaadin/item": "npm:~25.3.2" - "@vaadin/list-box": "npm:~25.3.2" - "@vaadin/overlay": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/button": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/context-menu": "npm:~25.3.3" + "@vaadin/item": "npm:~25.3.3" + "@vaadin/list-box": "npm:~25.3.3" + "@vaadin/overlay": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/102485a35fa43cc7092d2f999b603859840850fce005963f35f0fe24ad8653692be89fd7b23bfd8e3cf0b638dcaa0d5d72205f44eb8d74c413adf7b47c265c7c + checksum: 10c0/8448716e1a9639d4b7fb4a60fe0f4e9511a6d990f9fd1aa579b204ec6a4e471bfa8fc1d941da0f050ed8fb798d91dba633141a63d5b3679b96dea9cabe612e27 languageName: node linkType: hard -"@vaadin/multi-select-combo-box@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/multi-select-combo-box@npm:25.3.2" +"@vaadin/multi-select-combo-box@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/multi-select-combo-box@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/combo-box": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/field-base": "npm:~25.3.2" - "@vaadin/input-container": "npm:~25.3.2" - "@vaadin/item": "npm:~25.3.2" - "@vaadin/lit-renderer": "npm:~25.3.2" - "@vaadin/overlay": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/combo-box": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/field-base": "npm:~25.3.3" + "@vaadin/input-container": "npm:~25.3.3" + "@vaadin/item": "npm:~25.3.3" + "@vaadin/lit-renderer": "npm:~25.3.3" + "@vaadin/overlay": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/a346f13096f59374721d9b78afcde04f7e25ef035650e11d3da72c5b8af77039e0fab70dbee4e5f3801f1289cc456261c3f26b214cd2521df91c6d4ad743c599 + checksum: 10c0/99183691bc9a6678c6ae49b14b10685d8bfed2b1ffdabcb31d980f9e061278763e76861e59f48747d8701808feb3fe1199bcc8fb59296cae9a0de732c52db178 languageName: node linkType: hard -"@vaadin/number-field@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/number-field@npm:25.3.2" +"@vaadin/number-field@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/number-field@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/field-base": "npm:~25.3.2" - "@vaadin/input-container": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/field-base": "npm:~25.3.3" + "@vaadin/input-container": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/888c0e6a0a4c5e3a16491cf3c6920866be0e4f8662539dd5491ea98f3989dde8a3d89565498e40f47f1205c8b61581960502d2c5a0d3712bff9baa8b41a1e3ae + checksum: 10c0/00cd6266682013714a4f743fcad86b62d5a9010b9b570d3bb9e7208328821f48a1d4a16fa6088daea38bee7141af6121719f104c0979796d735427a6d3afdeb2 languageName: node linkType: hard -"@vaadin/overlay@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/overlay@npm:25.3.2" +"@vaadin/overlay@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/overlay@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/a793226d83608f333caf1953e4c12ad83e46d2f41a5469b77f0c753bd09d94dc158e81fb6bf38491982bb3b0f3b8c81e83f943168f4969a64e89888df1b6fef7 + checksum: 10c0/96dee9069d01cfbb70c38b5f1004b6a8bc90a79cef5b447174af55e13a006b4a87127b8c7d019fc2bcadd6660f149d4a89a8d63c48d7584da98d74b5567da70d languageName: node linkType: hard -"@vaadin/password-field@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/password-field@npm:25.3.2" +"@vaadin/password-field@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/password-field@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/button": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/field-base": "npm:~25.3.2" - "@vaadin/text-field": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/button": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/field-base": "npm:~25.3.3" + "@vaadin/text-field": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/34080913c364edb7c82febad7d91b98d5655107aef186a5eb2d1679ebc451f3b88b9b73e4aa51601b3887958daded8682b8a336766c7277e7b08c0485c7c48d4 + checksum: 10c0/2ed54995cc948b596584d9ff6c0f7dd8712782ffc90c2af730d947e9f2a94b65e6c14bff9963e6a5b039ca5b308da9d73c32ce2e2ce81e7cdcb8a945870404bd languageName: node linkType: hard -"@vaadin/radio-group@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/radio-group@npm:25.3.2" +"@vaadin/radio-group@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/radio-group@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/field-base": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/field-base": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/039745d9927033d7f10292b09a7164492d237c3972c6dc68971fac1aa2d657a1875eb36fb483b3d6524b71abc63b95fcda7a0aff7daca1b1426c6b8ad211f2eb + checksum: 10c0/ec096a0ffaa569a4f6184945407ce123fc5e5ad0661f654658608cdcaa726dfd8a22437156ddf86e0357c58c4cde47cc26b8d56f7b4e6dccbc867a8775cb0999 languageName: node linkType: hard -"@vaadin/select@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/select@npm:25.3.2" +"@vaadin/select@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/select@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/button": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/field-base": "npm:~25.3.2" - "@vaadin/input-container": "npm:~25.3.2" - "@vaadin/item": "npm:~25.3.2" - "@vaadin/list-box": "npm:~25.3.2" - "@vaadin/lit-renderer": "npm:~25.3.2" - "@vaadin/overlay": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/button": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/field-base": "npm:~25.3.3" + "@vaadin/input-container": "npm:~25.3.3" + "@vaadin/item": "npm:~25.3.3" + "@vaadin/list-box": "npm:~25.3.3" + "@vaadin/lit-renderer": "npm:~25.3.3" + "@vaadin/overlay": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/c0ebc5e33dd3128ab7e498a0e64844f2f8bd07f86aa386fb18601ee80b108b161cf2780964a1b501c278ac5ab72c6e693f9661ca745f8d928778bf63a7a9947a + checksum: 10c0/5d94d4227975ad3c7f3ab273b618471539cce05c48df7961b8405798a5667610a1c0f8414b447e939e79338e8e92cee271a698a8820a33cfe7b2e7b700c1c841 languageName: node linkType: hard -"@vaadin/slider@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/slider@npm:25.3.2" +"@vaadin/slider@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/slider@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/field-base": "npm:~25.3.2" - "@vaadin/overlay": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/field-base": "npm:~25.3.3" + "@vaadin/overlay": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/145c683aa3c1fe4d264295ff9e56ecdc1645793b7346d2315f2876d3add5a7562d68e90474cae01b7b983b7b32c55463c8babab81d017094df7fa56c663b1b05 + checksum: 10c0/a4a4646e214cc2941e23ce36036a2e9161505101ff2379436a1a63bbd58bb736b63e1c947e41cea321bdefec649baf952f8dd34509714b08d64613e0225ec08e languageName: node linkType: hard -"@vaadin/tabs@npm:~25.3.2": +"@vaadin/tabs@npm:~25.3.3": version: 25.3.3 resolution: "@vaadin/tabs@npm:25.3.3" dependencies: @@ -4677,51 +4641,51 @@ __metadata: languageName: node linkType: hard -"@vaadin/text-area@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/text-area@npm:25.3.2" +"@vaadin/text-area@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/text-area@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/field-base": "npm:~25.3.2" - "@vaadin/input-container": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/field-base": "npm:~25.3.3" + "@vaadin/input-container": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/18cb0293e13b2769b233a6d07cda2b1631af6fd4d3bcece5c32f716e6192e31b30059a5ccbd45167bf860b768241d69f562a4813a223e2fda8a9204bcb1e8332 + checksum: 10c0/dd07dac9a6aa3f70d1b97f0cff70b7ccd468ef74164e90e453e49750fef70a05d9d8ecb97261db1d91611633eae3cd86efb2782b8c8601b94472f8bd8110a51a languageName: node linkType: hard -"@vaadin/text-field@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/text-field@npm:25.3.2" +"@vaadin/text-field@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/text-field@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/field-base": "npm:~25.3.2" - "@vaadin/input-container": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/field-base": "npm:~25.3.3" + "@vaadin/input-container": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/e5347b6cde5916805cd5fbc1e51d3a144b1076e2a60cac52fb8b1498dabc73d7e218fef46e819c367519968db0ea7f0582e2b9da6b2ad1548c5aa00dd6bf03ec + checksum: 10c0/c206cbc49f5ec05252c09e034d37d3542b490548874623c99e7b46ca0f4d2f15f86c421a78a2e4e9cfc291e9f7196565bb2a819eb5f4eb00d4feca53731006b4 languageName: node linkType: hard -"@vaadin/time-picker@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/time-picker@npm:25.3.2" +"@vaadin/time-picker@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/time-picker@npm:25.3.3" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/a11y-base": "npm:~25.3.2" - "@vaadin/combo-box": "npm:~25.3.2" - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/field-base": "npm:~25.3.2" - "@vaadin/input-container": "npm:~25.3.2" - "@vaadin/item": "npm:~25.3.2" - "@vaadin/overlay": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" + "@vaadin/a11y-base": "npm:~25.3.3" + "@vaadin/combo-box": "npm:~25.3.3" + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/field-base": "npm:~25.3.3" + "@vaadin/input-container": "npm:~25.3.3" + "@vaadin/item": "npm:~25.3.3" + "@vaadin/overlay": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" lit: "npm:^3.0.0" - checksum: 10c0/6b03cc6d23c380f3bed261afc09460d89ab7f4a7c7aaa22ff17f598fadda59c22364ec21db0a91f44b7315ef8fd54941873a55fb2c93f6310b8f5b75086a2e41 + checksum: 10c0/ef30afece089616d09617822663196dd5fae917cbce5e7231c4c714ae50a7a03bef79bba1b0f6fbe095fe11fbbfe5be27d87da823ba67a6126ccb63086e7e785 languageName: node linkType: hard @@ -4732,25 +4696,14 @@ __metadata: languageName: node linkType: hard -"@vaadin/vaadin-lumo-styles@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/vaadin-lumo-styles@npm:25.3.2" - dependencies: - "@vaadin/component-base": "npm:~25.3.2" - "@vaadin/icon": "npm:~25.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~25.3.2" - checksum: 10c0/fe1c9b0809016df67feafd30371903d62a75817899d880a6f8f665dfa2ac146ab6abd35adc77a99a77f5c4c2fd2e5f786dc84ebc674bf71280110af5066b6aff - languageName: node - linkType: hard - -"@vaadin/vaadin-themable-mixin@npm:~25.3.2": - version: 25.3.2 - resolution: "@vaadin/vaadin-themable-mixin@npm:25.3.2" +"@vaadin/vaadin-lumo-styles@npm:~25.3.3": + version: 25.3.3 + resolution: "@vaadin/vaadin-lumo-styles@npm:25.3.3" dependencies: - "@open-wc/dedupe-mixin": "npm:^1.3.0" - "@vaadin/component-base": "npm:~25.3.2" - lit: "npm:^3.0.0" - checksum: 10c0/b1a4b94c98fe83258527c226d1f9c7f50ffd96519e90403d5aa551378798dee9030298be53c906340475e3bf0a390f32af50fd278d0835d06c54a759103a895c + "@vaadin/component-base": "npm:~25.3.3" + "@vaadin/icon": "npm:~25.3.3" + "@vaadin/vaadin-themable-mixin": "npm:~25.3.3" + checksum: 10c0/dd2531b49612f5c8dbbc94b9adb75b51d331b52804795ac2388248e6e85f802e1370f828306c1c5c436ef447bd1d5672d7e824ac0814e0c3894d238e1b155d9a languageName: node linkType: hard From 57ab8843cfc66b36a376f0ef6128478e4c60bda4 Mon Sep 17 00:00:00 2001 From: Ekhorn Date: Thu, 22 Oct 2026 17:09:45 +0200 Subject: [PATCH 3/4] Use Lumo tabs with a sliding variant - Drop or-vaadin-tab: or-vaadin-tabs takes vaadin-tab children - Give or-vaadin-tabs the Lumo tabs look through the theme - Move the sliding bar into theme="sliding" variant, with matching bar and tab rounding - Add stories for the Lumo variants, sliding and rounding - Add tests for the variants --- .../or-vaadin-components/src/index.ts | 1 - .../or-vaadin-components/src/or-vaadin-tab.ts | 89 ------- .../src/or-vaadin-tabs.ts | 87 +++++-- .../stories/or-vaadin-tabs.stories.ts | 239 +++++++++++++++--- .../test/or-vaadin-tabs.test.ts | 43 +++- ui/component/theme/src/components/index.css | 1 + .../theme/src/components/or-vaadin-tabs.css | 26 ++ 7 files changed, 336 insertions(+), 150 deletions(-) delete mode 100644 ui/component/or-vaadin-components/src/or-vaadin-tab.ts create mode 100644 ui/component/theme/src/components/or-vaadin-tabs.css diff --git a/ui/component/or-vaadin-components/src/index.ts b/ui/component/or-vaadin-components/src/index.ts index 774b1fe1f0..d7cd6d69b0 100644 --- a/ui/component/or-vaadin-components/src/index.ts +++ b/ui/component/or-vaadin-components/src/index.ts @@ -36,7 +36,6 @@ export * from "./or-vaadin-password-field"; export * from "./or-vaadin-radio-group"; export * from "./or-vaadin-select"; export * from "./or-vaadin-slider"; -export * from "./or-vaadin-tab"; export * from "./or-vaadin-tabs"; export * from "./or-vaadin-text-area"; export * from "./or-vaadin-text-field"; diff --git a/ui/component/or-vaadin-components/src/or-vaadin-tab.ts b/ui/component/or-vaadin-components/src/or-vaadin-tab.ts deleted file mode 100644 index 2a1c0ee72e..0000000000 --- a/ui/component/or-vaadin-components/src/or-vaadin-tab.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2026, OpenRemote Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program 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 - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -import { customElement } from "lit/decorators.js"; -import type { LitElement } from "lit"; -import { Tab } from "@vaadin/tabs/vaadin-tab.js"; -import { registerStyles, css } from "@vaadin/vaadin-themable-mixin/register-styles.js"; -import type { OrVaadinComponent } from "./util"; - -/* - * The Vaadin base tab styles read the `--vaadin-tab-*` properties set here. The tab has no background of its own in - * either state, as `` draws the selected one as an indicator behind the tabs. - */ -registerStyles( - "or-vaadin-tab", - css` - :host { - --vaadin-tab-background: transparent; - --vaadin-tab-text-color: var(--or-tab-color, var(--lumo-secondary-text-color, #646b64)); - --vaadin-tab-font-size: var(--or-tab-font-size, var(--lumo-font-size-m, 1rem)); - --vaadin-tab-font-weight: var(--or-tab-font-weight, 700); - --vaadin-tab-line-height: var(--or-tab-line-height, 1.5); - --vaadin-tab-padding: var(--or-tab-padding, 0 var(--lumo-space-s, 8px)); - --vaadin-tab-border-radius: var(--or-tabs-radius, 99px); - - /* Equal widths let place its indicator without measuring the tabs */ - flex: 1 1 0; - min-width: 0; - min-height: var(--or-tab-height, var(--lumo-size-l, 44px)); - box-sizing: border-box; - text-align: center; - overflow-wrap: anywhere; - transition: color var(--or-tabs-transition-duration, 0.3s) ease-out; - } - - /* Stacked tabs fit their labels, so the labels need room to clear the rounded ends */ - :host([orientation="vertical"]) { - --vaadin-tab-padding: var(--or-tab-padding, 0 var(--lumo-space-m, 1rem)); - justify-content: center; - } - - :host([selected]) { - --vaadin-tab-background: transparent; - --vaadin-tab-text-color: var(--or-tab-selected-color, var(--lumo-body-text-color, #1f211f)); - } - - @media (prefers-reduced-motion: reduce) { - :host { - transition: none; - } - } - ` -); - -/** - * A tab for ``. It takes an equal share of the tab bar, and wrapping its label in an `
` makes the - * whole tab a link. - * - * @customElement "or-vaadin-tab" - * @slot - Label content, such as text, an icon or an `` for navigation. - * @cssprop --or-tab-color - Label color when not selected. - * @cssprop --or-tab-selected-color - Label color when selected. - * @cssprop --or-tab-font-size - Label font size. - * @cssprop --or-tab-font-weight - Label font weight. - * @cssprop --or-tab-line-height - Label line height. - * @cssprop --or-tab-height - Minimum height of the tab. - * @cssprop --or-tab-padding - Padding of the tab. - */ -@customElement("or-vaadin-tab") -export class OrVaadinTab extends (Tab as new () => Tab & LitElement) implements OrVaadinComponent { - static get is() { - return "or-vaadin-tab"; - } -} diff --git a/ui/component/or-vaadin-components/src/or-vaadin-tabs.ts b/ui/component/or-vaadin-components/src/or-vaadin-tabs.ts index 3bfc4dd88d..a5b9b3e680 100644 --- a/ui/component/or-vaadin-components/src/or-vaadin-tabs.ts +++ b/ui/component/or-vaadin-components/src/or-vaadin-tabs.ts @@ -22,28 +22,68 @@ import { styleMap } from "lit/directives/style-map.js"; import { Tabs } from "@vaadin/tabs/vaadin-tabs.js"; import { registerStyles, css } from "@vaadin/vaadin-themable-mixin/register-styles.js"; import type { OrVaadinComponent } from "./util"; -import "./or-vaadin-tab"; + +/* Registers the tabs that go in the bar, and exports their class so projects can type them */ +export { Tab } from "@vaadin/tabs/vaadin-tab.js"; /* - * The Vaadin base tabs styles read the `--vaadin-tabs-*` properties set here. The tabs share the bar equally, so the - * indicator is sized from the number of tabs and moved by the index of the selected one. + * Without a variant the theme gives this tag the Lumo tabs look, or the Vaadin base styles apply without it. The + * `sliding` variant replaces either look, and styles the tabs from here as the Lumo tabs variants do. Its tabs share the + * bar equally, so the indicator is sized from the number of tabs and moved by the index of the selected one. */ registerStyles( "or-vaadin-tabs", css` - :host { + :host([theme~="sliding"]) { --_gap: var(--or-tabs-gap, 10px); --_padding: var(--or-tabs-padding, var(--lumo-space-xs, 4px)); - --_radius: var(--or-tabs-radius, 99px); - --vaadin-tabs-background: var(--or-tabs-background, var(--lumo-contrast-5pct, #f2f3f2)); - --vaadin-tabs-border-radius: var(--_radius); - --vaadin-tabs-gap: var(--_gap); - --vaadin-tabs-padding: var(--_padding); + /* At most a full pill, and the bar curves around it so both orientations round alike */ + --_tab-radius: min( + var(--or-tabs-radius, var(--lumo-border-radius-m, 6px)), + var(--or-tab-height, var(--lumo-size-l, 44px)) / 2 + ); + /* The indicator marks the selection instead of the Lumo underline */ + --_lumo-tab-marker-display: none; + position: relative; + display: flex; + box-sizing: border-box; + padding: var(--_padding); + border-radius: calc(var(--_tab-radius) + var(--_padding)); + background: var(--or-tabs-background, var(--lumo-contrast-5pct, #f2f3f2)); + box-shadow: none; + } + + :host([theme~="sliding"][orientation="vertical"]) { + flex-direction: column; + } + + :host([theme~="sliding"]) [part="tabs"] { + display: flex; + flex-direction: row; + gap: var(--_gap); + margin: 0; + } + + :host([theme~="sliding"][orientation="vertical"]) [part="tabs"] { + flex-direction: column; + } + + :host([theme~="sliding"]) [part="tabs"] ::slotted(vaadin-tab) { + flex: 1 1 0; + justify-content: center; + min-width: 0; + min-height: var(--or-tab-height, var(--lumo-size-l, 44px)); + box-sizing: border-box; + margin: 0; + border-radius: var(--_tab-radius); + background: transparent; + text-align: center; + overflow-wrap: anywhere; + transition: color var(--or-tabs-transition-duration, 0.3s) ease-out; } - /* A stacked bar is narrower than it is tall, so round it as much as the horizontal bar instead of fully */ - :host([orientation="vertical"]) { - --_radius: var(--or-tabs-radius, calc(var(--or-tab-height, var(--lumo-size-l, 44px)) / 2 + var(--_padding))); + :host(:not([theme~="sliding"])) #indicator { + display: none; } #indicator { @@ -51,7 +91,7 @@ registerStyles( inset-block: var(--_padding); inset-inline-start: var(--_padding); width: calc((100% - 2 * var(--_padding) - (var(--_count) - 1) * var(--_gap)) / var(--_count)); - border-radius: var(--_radius); + border-radius: var(--_tab-radius); background: var(--or-tabs-indicator-color, var(--lumo-base-color, #ffffff)); translate: calc(var(--_direction, 1) * var(--_index) * (100% + var(--_gap))) 0; transition: translate var(--or-tabs-transition-duration, 0.3s) ease-out; @@ -70,7 +110,8 @@ registerStyles( } @media (prefers-reduced-motion: reduce) { - #indicator { + #indicator, + :host([theme~="sliding"]) [part="tabs"] ::slotted(vaadin-tab) { transition: none; } } @@ -78,22 +119,28 @@ registerStyles( ); /** - * A row of `` elements in a pill-shaped bar, with an indicator that slides behind the selected tab. - * The tabs share the bar equally. Hidden tabs are left out of the layout, but only when they are hidden before they - * are added or the selection changes. + * A row of `` elements, with the Lumo look and variants of ``. Set `theme="sliding"` for a + * more mobile-friendly bar with a contrasting background, where the tabs share the width equally and an indicator + * slides behind the selected tab. The `--or-*` properties style that variant, and its tabs keep the Lumo text styles, + * which plain CSS on `vaadin-tab` overrides. Hidden tabs are left out of its layout, but only when they are hidden + * before they are added or the selection changes. * * @customElement "or-vaadin-tabs" - * @slot - The `` elements. + * @slot - The `` elements. * @attr {string} orientation - `horizontal` (default) or `vertical`. + * @attr {string} theme - Set to `sliding` for the bar with an indicator that slides behind the selected tab, or to a + * Lumo variant of ``. Sliding tabs always fit the bar and take precedence over the Lumo variants they + * conflict with, so `hide-scroll-buttons` and `show-scroll-buttons` have no effect with `sliding`. * @fires {CustomEvent} selected-changed - Fired when the `selected` property changes. * @fires {CustomEvent} items-changed - Fired when the `items` property changes. * @cssprop --or-tabs-background - Background of the bar. * @cssprop --or-tabs-indicator-color - Color of the indicator behind the selected tab. * @cssprop --or-tabs-padding - Space between the bar edge and the tabs. * @cssprop --or-tabs-gap - Space between the tabs. - * @cssprop --or-tabs-radius - Corner radius of the bar, the indicator and the tabs. By default the tabs and the - * indicator are full pills, and the bar is rounded to match them. + * @cssprop --or-tabs-radius - Corner radius of the tabs and the indicator, at most a full pill. The bar's corners + * follow it around the padding. Defaults to the Lumo medium radius. * @cssprop --or-tabs-transition-duration - Duration of the indicator slide and the label color change. + * @cssprop --or-tab-height - Minimum height of the tabs, which also caps their rounding at a full pill. */ @customElement("or-vaadin-tabs") export class OrVaadinTabs extends (Tabs as new () => Tabs & LitElement) implements OrVaadinComponent { diff --git a/ui/component/or-vaadin-components/stories/or-vaadin-tabs.stories.ts b/ui/component/or-vaadin-components/stories/or-vaadin-tabs.stories.ts index 3da2160a3e..6aaf171eb1 100644 --- a/ui/component/or-vaadin-components/stories/or-vaadin-tabs.stories.ts +++ b/ui/component/or-vaadin-components/stories/or-vaadin-tabs.stories.ts @@ -21,7 +21,9 @@ import { getORStorybookHelpers } from "../../storybook-utils"; import customElements from "../build/custom-elements.json" with { type: "json" }; import packageJson from "../package.json" with { type: "json" }; import { html } from "lit"; +import { styleMap } from "lit/directives/style-map.js"; import "../src/or-vaadin-tabs"; +import type { OrVaadinTabs } from "../src/or-vaadin-tabs"; const tagName = "or-vaadin-tabs"; type Story = StoryObj; @@ -35,12 +37,12 @@ const pages = [ { name: "Settings", content: "Change the name, location and schedule of the asset." }, ]; -/** Tabs for the pages, linked to the panels with the same id prefix. */ -const pageTabs = (prefix: string) => +/** Tabs for the pages, linked to the panels with the given id prefix. */ +const pageTabs = (prefix: string, tabPrefix = prefix) => pages.map( ({ name }) => - html`${name}${name}` ); @@ -58,19 +60,54 @@ const pagePanels = (prefix: string, selected: number) =>

` ); -/** Shows the panel of the selected tab, out of the panels next to the tabs. */ +/** Selects the tab in every bar of the `data-tab-group` and shows its panel. */ const showPanel = (event: CustomEvent<{ value: number }>) => { - const panels = (event.target as Element).parentElement?.querySelectorAll('[role="tabpanel"]'); - panels?.forEach((panel, index) => (panel.hidden = index !== event.detail.value)); + const group = (event.target as Element).closest("[data-tab-group]"); + group?.querySelectorAll("or-vaadin-tabs").forEach((tabs) => (tabs.selected = event.detail.value)); + group + ?.querySelectorAll('[role="tabpanel"]') + .forEach((panel, index) => (panel.hidden = index !== event.detail.value)); }; +/** Tabs for the pages, without content. */ +const plainTabs = () => pages.map(({ name }) => html`${name}`); + +/** An example with a caption naming what it sets. */ +const captioned = (caption: string, example: unknown) => html` +
+
+ ${caption} +
+ ${example} +
+`; + +/** More tabs than a narrow bar fits, so that it overflows. */ +const overflowTabs = () => + ["First", "Second", "Third", "Fourth", "Fifth", "Sixth"].map((name) => html`${name} tab`); + +/** Tabs with the given variant, captioned with it. */ +const themed = (variant: string, tabs = plainTabs, style = "") => + captioned( + `theme="${variant}"`, + html`${tabs()}` + ); + +/** Tabs without a variant above tabs with the given one, so that the difference shows. */ +const compared = (variant: string) => html` +
+ ${captioned("Default", html`${plainTabs()}`)} ${themed(variant)} +
+`; + const meta: Meta = { title: "Playground/or-vaadin-components/tabs", component: tagName, args, argTypes, - render: (storyArgs) => - template(storyArgs, html`${pages.map(({ name }) => html`${name}`)}`), + render: (storyArgs) => template(storyArgs, html`${plainTabs()}`), excludeStories: /^[a-z].*/, parameters: { actions: { @@ -79,61 +116,201 @@ const meta: Meta = { docs: { subtitle: `<${tagName}>`, description: - "Tabs is a more mobile-friendly alternative to `` for switching between views or pages. The `` elements share a pill-shaped bar equally, and an indicator slides behind the selected one. Selection works as in ``, through the `selected` index and the `selected-changed` event, and the colors, sizes and spacing are set through `--or-tabs-*` and `--or-tab-*` properties.", + 'Tabs switch between views or pages. They have the Lumo look and variants of ``. Set `theme="sliding"` for a more mobile-friendly bar with a contrasting background, where the `` elements share the width equally and an indicator slides behind the selected one. Selection works as in ``, through the `selected` index and the `selected-changed` event, and the colors, sizes, spacing, rounding and timing of the sliding variant are set through `--or-tabs-*` and `--or-tab-*` properties.', }, }, }; export const Primary: Story = { args: { - selected: 1, + selected: 0, }, }; -/** Tabs that switch the content below them. */ -export const Content: Story = { - name: "Content", +/** Tabs stacked vertically beside their content. */ +export const Vertical: Story = { + name: "Vertical", parameters: { - title: "Content", - summary: "Listen to `selected-changed` to show the content of the selected tab.", + title: "Vertical", + summary: + 'Set `orientation="vertical"` to stack the tabs, so the content can sit beside them. Listen to `selected-changed` to show the content of the selected tab.', docs: { story: { - height: "160px", + height: "170px", }, }, }, render: () => html` -
- ${pageTabs("content")} - ${pagePanels("content", 0)} +
+ + ${pageTabs("vertical")} + + ${pagePanels("vertical", 0)}
`, }; -/** Tabs stacked vertically beside their content. */ -export const Vertical: Story = { - name: "Vertical", +/** Tabs that share the width of the bar equally. */ +export const EqualWidth: Story = { + name: "Equal width", parameters: { - title: "Vertical", + title: "Equal width", + summary: + 'The Lumo `theme="equal-width-tabs"` variant makes the tabs share the width of the bar equally. Sliding tabs always do this.', + docs: { + story: { + height: "210px", + }, + }, + }, + render: () => compared("equal-width-tabs"), +}; + +/** Tabs centered in the bar. */ +export const Centered: Story = { + name: "Centered", + parameters: { + title: "Centered", + summary: 'The Lumo `theme="centered"` variant centers the tabs in the bar.', + docs: { + story: { + height: "210px", + }, + }, + }, + render: () => compared("centered"), +}; + +/** Smaller tabs. */ +export const Small: Story = { + name: "Small", + parameters: { + title: "Small", + summary: 'The Lumo `theme="small"` variant makes the tabs smaller, for where space is limited.', + docs: { + story: { + height: "210px", + }, + }, + }, + render: () => compared("small"), +}; + +/** Tabs with as little styling as possible. */ +export const Minimal: Story = { + name: "Minimal", + parameters: { + title: "Minimal", + summary: + 'The Lumo `theme="minimal"` variant reduces the styling to a minimum, without the line along the bar and the underline below the selected tab.', + docs: { + story: { + height: "210px", + }, + }, + }, + render: () => compared("minimal"), +}; + +/** An overflowing bar with and without its scroll buttons. */ +export const ScrollButtons: Story = { + name: "Scroll buttons", + parameters: { + title: "Scroll buttons", summary: - 'Set `orientation="vertical"` to stack the tabs, and the indicator then slides vertically. The content can then sit beside the tabs.', + 'A bar that overflows shows scroll buttons. The Lumo `theme="hide-scroll-buttons"` variant hides them and fades out the overflowing ends instead, and the tabs can still be scrolled. The `theme="show-scroll-buttons"` variant keeps them on touch devices, which hide them by default in other themes. Lumo shows them whenever the bar overflows, so here it looks like the default.', docs: { story: { - height: "200px", + height: "210px", }, }, }, render: () => html` -
- - ${pageTabs("vertical")} +
+ ${themed("hide-scroll-buttons", overflowTabs, "max-width: 360px;")} + ${themed("show-scroll-buttons", overflowTabs, "max-width: 360px;")} +
+ `, +}; + +/** The sliding variant, horizontally and vertically. */ +export const Sliding: Story = { + name: "Sliding", + parameters: { + title: "Sliding", + summary: + 'The sliding variant is a more mobile-friendly alternative to the default tabs, for switching between a few views on a phone. Set `theme="sliding"` for a bar with a contrasting background, where the tabs share its width as large touch targets and an indicator slides behind the selected one. With `orientation="vertical"` the indicator slides vertically. The two bars here share one set of content, as selecting a tab in one sets `selected` on the other from its `selected-changed` event.', + docs: { + story: { + height: "260px", + }, + }, + }, + render: () => html` +
+ + ${pageTabs("sliding")} - ${pagePanels("vertical", 2)} +
+ + ${pageTabs("sliding", "sliding-vertical")} + + ${pagePanels("sliding", 0)} +
+
+ `, +}; + +const roundings = [ + { caption: "0px", radius: "0px" }, + { caption: "3px", radius: "3px" }, + { caption: "Default, the Lumo medium radius", radius: undefined }, + { caption: "12px", radius: "12px" }, + { caption: "99px, a full pill", radius: "99px" }, +]; + +/** The sliding variant with a range of `--or-tabs-radius` values. */ +export const Rounding: Story = { + name: "Rounding", + parameters: { + title: "Rounding", + summary: + "`--or-tabs-radius` rounds the tabs and the indicator, up to a full pill, and the corners of the bar follow it around the padding. The rounding is the same in either `orientation`.", + docs: { + story: { + height: "760px", + }, + }, + }, + render: () => html` +
+ ${roundings.map(({ caption, radius }) => + captioned( + caption, + html`${plainTabs()}` + ) + )} +
+ ${roundings.map(({ caption, radius }) => + captioned( + caption, + html`${plainTabs()}` + ) + )} +
`, }; -export const examples = [Content, Vertical]; +export const examples = [Vertical, EqualWidth, Centered, Small, Minimal, ScrollButtons, Sliding, Rounding]; export { customElements, packageJson }; diff --git a/ui/component/or-vaadin-components/test/or-vaadin-tabs.test.ts b/ui/component/or-vaadin-components/test/or-vaadin-tabs.test.ts index 8956032bed..939797bf3e 100644 --- a/ui/component/or-vaadin-components/test/or-vaadin-tabs.test.ts +++ b/ui/component/or-vaadin-components/test/or-vaadin-tabs.test.ts @@ -18,14 +18,9 @@ */ import { ct, expect } from "./fixtures"; -import { OrVaadinTab } from "@openremote/or-vaadin-components/or-vaadin-tab"; import { OrVaadinTabs } from "@openremote/or-vaadin-components/or-vaadin-tabs"; -const tabs = [ - "Status", - "Mode", - "Settings", -]; +const tabs = ["Status", "Mode", "Settings"]; ct("should select the clicked tab", async ({ mount }) => { const selected: number[] = []; @@ -37,7 +32,6 @@ ct("should select the clicked tab", async ({ mount }) => { selected.push(detail.value); }, }, - hooksConfig: { components: [OrVaadinTab] }, }); await expect(component.getByRole("tab", { selected: true })).toHaveText("Mode"); @@ -48,12 +42,26 @@ ct("should select the clicked tab", async ({ mount }) => { await expect.poll(() => selected.at(-1)).toBe(2); }); +ct("should show the indicator only with the sliding variant", async ({ mount }) => { + const component = await mount(OrVaadinTabs, { + props: { selected: 0 }, + slots: { default: tabs }, + }); + const indicator = component.locator("#indicator"); + + await expect(indicator).toBeHidden(); + + await component.evaluate((el) => el.setAttribute("theme", "sliding")); + + await expect(indicator).toBeVisible(); +}); + ct("should move the indicator behind the selected tab", async ({ mount }) => { const component = await mount(OrVaadinTabs, { props: { selected: 0 }, slots: { default: tabs }, - hooksConfig: { components: [OrVaadinTab] }, }); + await component.evaluate((el) => el.setAttribute("theme", "sliding")); const indicator = component.locator("#indicator"); const settings = component.getByRole("tab", { name: "Settings" }); @@ -72,8 +80,25 @@ ct("should hide the indicator when no tab is selected", async ({ mount }) => { const component = await mount(OrVaadinTabs, { props: { selected: -1 }, slots: { default: tabs }, - hooksConfig: { components: [OrVaadinTab] }, }); + await component.evaluate((el) => el.setAttribute("theme", "sliding")); await expect(component.locator("#indicator")).toBeHidden(); }); + +ct("should give the tabs equal widths with the equal-width-tabs variant", async ({ mount }) => { + const component = await mount(OrVaadinTabs, { + props: { selected: 0 }, + slots: { default: tabs }, + }); + await component.evaluate((el) => el.setAttribute("theme", "equal-width-tabs")); + + await expect + .poll(async () => { + const widths = await component + .getByRole("tab") + .evaluateAll((elements) => elements.map((el) => Math.round(el.getBoundingClientRect().width))); + return new Set(widths).size; + }) + .toBe(1); +}); diff --git a/ui/component/theme/src/components/index.css b/ui/component/theme/src/components/index.css index 78d1ea3418..b1571eb4ca 100644 --- a/ui/component/theme/src/components/index.css +++ b/ui/component/theme/src/components/index.css @@ -38,6 +38,7 @@ @import "./or-vaadin-radio-group.css"; @import "./or-vaadin-select.css"; @import "./or-vaadin-slider.css"; +@import "./or-vaadin-tabs.css"; @import "./or-vaadin-text-area.css"; @import "./or-vaadin-text-field.css"; @import "./or-vaadin-time-picker.css"; /* Must be after the date-time-picker */ diff --git a/ui/component/theme/src/components/or-vaadin-tabs.css b/ui/component/theme/src/components/or-vaadin-tabs.css new file mode 100644 index 0000000000..abb8cbffe6 --- /dev/null +++ b/ui/component/theme/src/components/or-vaadin-tabs.css @@ -0,0 +1,26 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +@import "@vaadin/vaadin-lumo-styles/components/tabs.css"; + +/* The `` elements inside get the Lumo tab look from the import above */ +:root::before, +:host::before { + --_lumo-or-vaadin-tabs-inject: 1; + --_lumo-or-vaadin-tabs-inject-modules: lumo_components_tabs; +} From 4fed346c54b8a1a4514b736619ea7a8b53d36ca9 Mon Sep 17 00:00:00 2001 From: Ekhorn Date: Mon, 26 Oct 2026 17:01:33 +0100 Subject: [PATCH 4/4] Reduce the tabs stories to what OpenRemote adds Drop the stories that repeat the Vaadin style variants and link the Vaadin docs instead. --- .../stories/or-vaadin-tabs.stories.ts | 176 ++---------------- 1 file changed, 14 insertions(+), 162 deletions(-) diff --git a/ui/component/or-vaadin-components/stories/or-vaadin-tabs.stories.ts b/ui/component/or-vaadin-components/stories/or-vaadin-tabs.stories.ts index 6aaf171eb1..ec2b4baf0f 100644 --- a/ui/component/or-vaadin-components/stories/or-vaadin-tabs.stories.ts +++ b/ui/component/or-vaadin-components/stories/or-vaadin-tabs.stories.ts @@ -37,41 +37,19 @@ const pages = [ { name: "Settings", content: "Change the name, location and schedule of the asset." }, ]; -/** Tabs for the pages, linked to the panels with the given id prefix. */ -const pageTabs = (prefix: string, tabPrefix = prefix) => - pages.map( - ({ name }) => - html`${name}` - ); - -/** Panels with the content of the pages, of which only the selected one is shown. */ -const pagePanels = (prefix: string, selected: number) => - pages.map( - ({ name, content }, index) => - html`

- ${content} -

` - ); +/** Tabs for the pages, without content. */ +const plainTabs = () => pages.map(({ name }) => html`${name}`); -/** Selects the tab in every bar of the `data-tab-group` and shows its panel. */ -const showPanel = (event: CustomEvent<{ value: number }>) => { +/** Selects the tab in every bar of the `data-tab-group` and shows its page in the group's `

`. */ +const showContent = (event: CustomEvent<{ value: number }>) => { const group = (event.target as Element).closest("[data-tab-group]"); group?.querySelectorAll("or-vaadin-tabs").forEach((tabs) => (tabs.selected = event.detail.value)); - group - ?.querySelectorAll('[role="tabpanel"]') - .forEach((panel, index) => (panel.hidden = index !== event.detail.value)); + const content = group?.querySelector("p"); + if (content) { + content.textContent = pages[event.detail.value]?.content ?? ""; + } }; -/** Tabs for the pages, without content. */ -const plainTabs = () => pages.map(({ name }) => html`${name}`); - /** An example with a caption naming what it sets. */ const captioned = (caption: string, example: unknown) => html`

@@ -84,24 +62,6 @@ const captioned = (caption: string, example: unknown) => html`
`; -/** More tabs than a narrow bar fits, so that it overflows. */ -const overflowTabs = () => - ["First", "Second", "Third", "Fourth", "Fifth", "Sixth"].map((name) => html`${name} tab`); - -/** Tabs with the given variant, captioned with it. */ -const themed = (variant: string, tabs = plainTabs, style = "") => - captioned( - `theme="${variant}"`, - html`${tabs()}` - ); - -/** Tabs without a variant above tabs with the given one, so that the difference shows. */ -const compared = (variant: string) => html` -
- ${captioned("Default", html`${plainTabs()}`)} ${themed(variant)} -
-`; - const meta: Meta = { title: "Playground/or-vaadin-components/tabs", component: tagName, @@ -116,7 +76,7 @@ const meta: Meta = { docs: { subtitle: `<${tagName}>`, description: - 'Tabs switch between views or pages. They have the Lumo look and variants of ``. Set `theme="sliding"` for a more mobile-friendly bar with a contrasting background, where the `` elements share the width equally and an indicator slides behind the selected one. Selection works as in ``, through the `selected` index and the `selected-changed` event, and the colors, sizes, spacing, rounding and timing of the sliding variant are set through `--or-tabs-*` and `--or-tab-*` properties.', + 'Tabs switch between views or pages. They have the Lumo look of `` and work the same, so see the [Vaadin Tabs docs](https://vaadin.com/docs/latest/components/tabs) for their usage and [style variants](https://vaadin.com/docs/latest/components/tabs/styling). Set `theme="sliding"` for a more mobile-friendly bar with a contrasting background, where the `` elements share the width equally and an indicator slides behind the selected one. Its colors, sizes, spacing, rounding and timing are set through `--or-tabs-*` and `--or-tab-*` properties.', }, }, }; @@ -127,112 +87,6 @@ export const Primary: Story = { }, }; -/** Tabs stacked vertically beside their content. */ -export const Vertical: Story = { - name: "Vertical", - parameters: { - title: "Vertical", - summary: - 'Set `orientation="vertical"` to stack the tabs, so the content can sit beside them. Listen to `selected-changed` to show the content of the selected tab.', - docs: { - story: { - height: "170px", - }, - }, - }, - render: () => html` -
- - ${pageTabs("vertical")} - - ${pagePanels("vertical", 0)} -
- `, -}; - -/** Tabs that share the width of the bar equally. */ -export const EqualWidth: Story = { - name: "Equal width", - parameters: { - title: "Equal width", - summary: - 'The Lumo `theme="equal-width-tabs"` variant makes the tabs share the width of the bar equally. Sliding tabs always do this.', - docs: { - story: { - height: "210px", - }, - }, - }, - render: () => compared("equal-width-tabs"), -}; - -/** Tabs centered in the bar. */ -export const Centered: Story = { - name: "Centered", - parameters: { - title: "Centered", - summary: 'The Lumo `theme="centered"` variant centers the tabs in the bar.', - docs: { - story: { - height: "210px", - }, - }, - }, - render: () => compared("centered"), -}; - -/** Smaller tabs. */ -export const Small: Story = { - name: "Small", - parameters: { - title: "Small", - summary: 'The Lumo `theme="small"` variant makes the tabs smaller, for where space is limited.', - docs: { - story: { - height: "210px", - }, - }, - }, - render: () => compared("small"), -}; - -/** Tabs with as little styling as possible. */ -export const Minimal: Story = { - name: "Minimal", - parameters: { - title: "Minimal", - summary: - 'The Lumo `theme="minimal"` variant reduces the styling to a minimum, without the line along the bar and the underline below the selected tab.', - docs: { - story: { - height: "210px", - }, - }, - }, - render: () => compared("minimal"), -}; - -/** An overflowing bar with and without its scroll buttons. */ -export const ScrollButtons: Story = { - name: "Scroll buttons", - parameters: { - title: "Scroll buttons", - summary: - 'A bar that overflows shows scroll buttons. The Lumo `theme="hide-scroll-buttons"` variant hides them and fades out the overflowing ends instead, and the tabs can still be scrolled. The `theme="show-scroll-buttons"` variant keeps them on touch devices, which hide them by default in other themes. Lumo shows them whenever the bar overflows, so here it looks like the default.', - docs: { - story: { - height: "210px", - }, - }, - }, - render: () => html` -
- ${themed("hide-scroll-buttons", overflowTabs, "max-width: 360px;")} - ${themed("show-scroll-buttons", overflowTabs, "max-width: 360px;")} -
- `, -}; - /** The sliding variant, horizontally and vertically. */ export const Sliding: Story = { name: "Sliding", @@ -248,14 +102,12 @@ export const Sliding: Story = { }, render: () => html`
- - ${pageTabs("sliding")} - + ${plainTabs()}
- - ${pageTabs("sliding", "sliding-vertical")} + + ${plainTabs()} - ${pagePanels("sliding", 0)} +

${pages[0].content}

`, @@ -310,7 +162,7 @@ export const Rounding: Story = { `, }; -export const examples = [Vertical, EqualWidth, Centered, Small, Minimal, ScrollButtons, Sliding, Rounding]; +export const examples = [Sliding, Rounding]; export { customElements, packageJson };