diff --git a/.eslintrc.json b/.eslintrc.json index f9b5e8b..fea230e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,15 +1,15 @@ { - "extends": "vaadin", + "extends": [ + "eslint:recommended", + "plugin:prettier/recommended" + ], + "plugins": ["prettier"], "env": { "browser": true, - "node": true, "es6": true }, - "plugins": [ - "html" - ], - "globals": { - "Polymer": false, - "Vaadin": false + "parserOptions": { + "sourceType": "module", + "ecmaVersion": 2018 } } diff --git a/.gemini.yml b/.gemini.yml deleted file mode 100644 index 8f3ae2d..0000000 --- a/.gemini.yml +++ /dev/null @@ -1,22 +0,0 @@ -rootUrl: http://localhost:8080/components/vaadin-tabs/test/visual/ -gridUrl: http://localhost:4444/wd/hub -screenshotsDir: ./test/visual/screens - -system: - plugins: - polyserve: - port: 8080 - sauce: true - -browsers: - chrome: - desiredCapabilities: - browserName: "chrome" - version: "67.0" - platform: "Windows 10" - - firefox: - desiredCapabilities: - browserName: "firefox" - version: "47.0" - platform: "Windows 10" diff --git a/.github/workflows/sauce.yml b/.github/workflows/sauce.yml new file mode 100644 index 0000000..3d25b22 --- /dev/null +++ b/.github/workflows/sauce.yml @@ -0,0 +1,47 @@ +name: sauce + +on: + push: + branches: + - '**' + tags-ignore: + - '*.*' + +jobs: + unit-tests: + runs-on: ubuntu-latest + if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12 + + - name: Install dependencies + run: npm install + + - name: SauceLabs tests + env: + SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} + SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} + run: npm run test:sauce + + visual-tests: + runs-on: ubuntu-latest + if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12 + + - name: Install dependencies + run: npm install + + - name: Visual tests + env: + SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} + SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} + run: npm run test:visual diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..65e2568 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,43 @@ +name: tests + +on: [pull_request] + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12 + + - name: Install dependencies + run: npm install + + - name: Check version + run: npm run check-version + + - name: Lint JavaScript + run: npm run lint:js + + - name: Lint CSS + run: npm run lint:css + + - name: Lint typings + run: npm run lint:types + + unit-tests: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12 + + - name: Install dependencies + run: npm install + + - name: Unit tests + run: npm test diff --git a/.gitignore b/.gitignore index fc18d6d..892aade 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules package-lock.json yarn.lock coverage +dist analysis.json diff --git a/.hermione.conf.js b/.hermione.conf.js new file mode 100644 index 0000000..f645592 --- /dev/null +++ b/.hermione.conf.js @@ -0,0 +1,21 @@ +module.exports = { + browsers: { + chrome: { + baseUrl: 'http://localhost:8080/test/visual/', + screenshotsDir: () => 'test/visual/screens/vaadin-tabs', + desiredCapabilities: { + browserName: 'chrome', + version: '85.0', + platform: 'Windows 10' + } + } + }, + plugins: { + 'hermione-esm': { + port: 8080 + }, + 'hermione-sauce': { + verbose: false + } + } +}; diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..3506f0e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "printWidth": 120, + "trailingComma": "none", + "htmlWhitespaceSensitivity": "strict" +} diff --git a/.stylelintrc b/.stylelintrc index b2189e2..5a1a6a7 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -1,3 +1,6 @@ { - "extends": "stylelint-config-vaadin" + "extends": [ + "stylelint-config-vaadin", + "stylelint-config-prettier" + ] } diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index bd5f6d3..0000000 --- a/.travis.yml +++ /dev/null @@ -1,67 +0,0 @@ -sudo: false -dist: xenial -language: node_js -node_js: 10.23 - -cache: - directories: - - node_modules - -addons: - firefox: "66.0" - chrome: stable - -jobs: - include: - - if: type = push - env: TEST_SUITE=unit_tests POLYMER=2 - - if: type = push - env: TEST_SUITE=visual_tests POLYMER=2 - - if: type = push - env: TEST_SUITE=unit_tests POLYMER=3 - - if: type = pull_request - env: POLYMER=2 - addons: - firefox: "66.0" - chrome: stable - - if: type = pull_request - env: POLYMER=3 - addons: - firefox: "66.0" - chrome: stable - - if: type = cron - env: TEST_SUITE=unit_tests POLYMER=2 - -script: - - if [[ "$POLYMER" = "2" ]]; then - npm -q i && npm i -q --no-save bower polymer-cli && bower -q i && - if [[ "$TRAVIS_EVENT_TYPE" != "pull_request" && "$TRAVIS_BRANCH" != quick/* ]]; then - if [[ "$TEST_SUITE" = "visual_tests" ]]; then - npm i -q --no-save gemini@^4.0.0 gemini-sauce gemini-polyserve && - gemini test test/visual; - else - wct --env saucelabs; - fi; - else - npm run check && - npm run lint && - xvfb-run -s '-screen 0 1024x768x24' wct; - fi && - if [[ "$TRAVIS_EVENT_TYPE" = "cron" && "$TEST_SUITE" = "unit_tests" ]]; then - wct --env saucelabs-cron; - fi; - fi - - if [[ "$POLYMER" = "3" ]]; then - npm --no-save -q install -g yarn bower magi-cli web-component-tester polymer-modulizer && - rm -rf node_modules && - magi p3-convert --out . --import-style=name && - yarn install --flat && - if [[ "$TRAVIS_EVENT_TYPE" != "pull_request" && "$TRAVIS_BRANCH" != quick/* ]]; then - wct --npm --env saucelabs; - else - xvfb-run -s '-screen 0 1024x768x24' wct --npm; - fi; - fi - -after_success: - - "cat ${TRAVIS_BUILD_DIR}/coverage/lcov.info | coveralls" diff --git a/README.md b/README.md index c9473d9..0d012cd 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,18 @@ -[![npm version](https://badgen.net/npm/v/@vaadin/vaadin-tabs)](https://www.npmjs.com/package/@vaadin/vaadin-tabs) -[![Bower version](https://badgen.net/github/release/vaadin/vaadin-tabs)](https://github.com/vaadin/vaadin-tabs/releases) -[![Published on webcomponents.org](https://img.shields.io/badge/webcomponents.org-published-blue.svg)](https://webcomponents.org/element/vaadin/vaadin-tabs) -[![Build Status](https://travis-ci.org/vaadin/vaadin-tabs.svg?branch=master)](https://travis-ci.org/vaadin/vaadin-tabs) -[![Coverage Status](https://coveralls.io/repos/github/vaadin/vaadin-tabs/badge.svg?branch=master)](https://coveralls.io/github/vaadin/vaadin-tabs?branch=master) -[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/vaadin/web-components?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) - -[![Published on Vaadin Directory](https://img.shields.io/badge/Vaadin%20Directory-published-00b4f0.svg)](https://vaadin.com/directory/component/vaadinvaadin-tabs) -[![Stars on vaadin.com/directory](https://img.shields.io/vaadin-directory/star/vaadinvaadin-tabs.svg)](https://vaadin.com/directory/component/vaadinvaadin-tabs) - # <vaadin-tabs> [Live Demo ↗](https://vaadin.com/components/vaadin-tabs/html-examples) | [API documentation ↗](https://vaadin.com/components/vaadin-tabs/html-api) - [<vaadin-tabs>](https://vaadin.com/components/vaadin-tabs) is a Web Component providing item navigation part of the [Vaadin components](https://vaadin.com/components). It is designed for menu and tab components. - +[![npm version](https://badgen.net/npm/v/@vaadin/vaadin-tabs)](https://www.npmjs.com/package/@vaadin/vaadin-tabs) +[![Build Status](https://travis-ci.org/vaadin/vaadin-tabs.svg?branch=master)](https://travis-ci.org/vaadin/vaadin-tabs) +[![Published on webcomponents.org](https://img.shields.io/badge/webcomponents.org-published-blue.svg)](https://webcomponents.org/element/vaadin/vaadin-tabs) +[![Published on Vaadin Directory](https://img.shields.io/badge/Vaadin%20Directory-published-00b4f0.svg)](https://vaadin.com/directory/component/vaadinvaadin-tabs) +[![Stars on vaadin.com/directory](https://img.shields.io/vaadin-directory/star/vaadinvaadin-tabs.svg)](https://vaadin.com/directory/component/vaadinvaadin-tabs) +[![Discord](https://img.shields.io/discord/732335336448852018?label=discord)](https://discord.gg/PHmkCKC) + ```html Page 1 @@ -41,28 +26,6 @@ ## Installation -The Vaadin components are distributed as Bower and npm packages. -Please note that the version range is the same, as the API has not changed. -You should not mix Bower and npm versions in the same application, though. - -Unlike the official Polymer Elements, the converted Polymer 3 compatible Vaadin components -are only published on npm, not pushed to GitHub repositories. - -### Polymer 2 and HTML Imports Compatible Version - -Install `vaadin-tabs`: - -```sh -bower i vaadin/vaadin-tabs --save -``` - -Once installed, import it in your application: - -```html - -``` -### Polymer 3 and ES Modules Compatible Version - Install `vaadin-tabs`: ```sh @@ -85,45 +48,50 @@ To use the Material theme, import the correspondent file from the `theme/materia - The components with the Lumo theme: - `theme/lumo/vaadin-tab.html` - `theme/lumo/vaadin-tabs.html` + `theme/lumo/vaadin-tab.js` + `theme/lumo/vaadin-tabs.js` - The components with the Material theme: - `theme/material/vaadin-tab.html` - `theme/material/vaadin-tabs.html` + `theme/material/vaadin-tab.js` + `theme/material/vaadin-tabs.js` -- Alias for `theme/lumo/vaadin-tab.html` - `theme/lumo/vaadin-tabs.html`: +- Alias for `theme/lumo/vaadin-tab.js` + `theme/lumo/vaadin-tabs.js`: - `vaadin-tab.html` - `vaadin-tabs.html` + `vaadin-tab.js` + `vaadin-tabs.js` -## Running demos and tests in a browser +## Running API docs and tests in a browser 1. Fork the `vaadin-tabs` repository and clone it locally. -1. Make sure you have [npm](https://www.npmjs.com/) and [Bower](https://bower.io) installed. +1. Make sure you have [node.js](https://nodejs.org/) 12.x installed. -1. When in the `vaadin-tabs` directory, run `npm install` and then `bower install` to install dependencies. +1. Make sure you have [npm](https://www.npmjs.com/) installed. + +1. When in the `vaadin-tabs` directory, run `npm install` to install dependencies. 1. Run `npm start`, browser will automatically open the component API documentation. -1. You can also open demo or in-browser tests by adding **demo** or **test** to the URL, for example: +1. You can also open visual tests, for example: - - http://127.0.0.1:3000/components/vaadin-tabs/demo - - http://127.0.0.1:3000/components/vaadin-tabs/test + - http://127.0.0.1:3000/test/visual/horizontal-tabs.html ## Running tests from the command line -1. When in the `vaadin-tabs` directory, run `polymer test` +1. When in the `vaadin-tabs` directory, run `npm test` + +## Debugging tests in the browser + +1. Run `npm run debug`, then choose manual mode (M) and open the link in browser. ## Following the coding style -We are using [ESLint](http://eslint.org/) for linting JavaScript code. You can check if your code is following our standards by running `npm run lint`, which will automatically lint all `.js` files as well as JavaScript snippets inside `.html` files. +We are using [ESLint](http://eslint.org/) for linting JavaScript code. You can check if your code is following our standards by running `npm run lint`, which will automatically lint all `.js` files. ## Big Thanks diff --git a/bower.json b/bower.json deleted file mode 100644 index eebfd40..0000000 --- a/bower.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "vaadin-tabs", - "homepage": "https://vaadin.com/components", - "authors": [ - "Vaadin Ltd" - ], - "description": "vaadin-tabs", - "main": [ - "vaadin-tabs.html", - "vaadin-tab.html", - "theme/material/vaadin-tabs.html" - ], - "keywords": [ - "Vaadin", - "vaadin-tabs", - "web-components", - "web-component", - "polymer" - ], - "license": "Apache-2.0", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "package-lock.json", - "wct.conf.js" - ], - "dependencies": { - "polymer": "^2.0.0", - "iron-resizable-behavior": "^2.0.0", - "vaadin-themable-mixin": "vaadin/vaadin-themable-mixin#^1.6.1", - "vaadin-list-mixin": "vaadin/vaadin-list-mixin#^2.5.0", - "vaadin-item": "vaadin/vaadin-item#^2.3.0", - "vaadin-lumo-styles": "vaadin/vaadin-lumo-styles#^1.1.0", - "vaadin-material-styles": "vaadin/vaadin-material-styles#^1.1.0", - "vaadin-element-mixin": "vaadin/vaadin-element-mixin#^2.4.1" - }, - "devDependencies": { - "iron-component-page": "^3.0.0", - "iron-icons": "^2.0.0", - "iron-icon": "^2.0.0", - "iron-pages": "^2.0.0", - "iron-test-helpers": "^2.0.0", - "vaadin-demo-helpers": "vaadin/vaadin-demo-helpers#^3.1.0", - "webcomponentsjs": "^1.0.0", - "web-component-tester": "^6.1.5" - } -} diff --git a/demo/.eslintrc.json b/demo/.eslintrc.json deleted file mode 100644 index fd80920..0000000 --- a/demo/.eslintrc.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "rules": { - }, - "globals": { - "TabsDemo": false, - "DemoReadyEventEmitter": false - } -} diff --git a/demo/demo-iconset.html b/demo/demo-iconset.html deleted file mode 100644 index 8c8c311..0000000 --- a/demo/demo-iconset.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/demo/demos.json b/demo/demos.json deleted file mode 100644 index 9dd6061..0000000 --- a/demo/demos.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "Vaadin Tabs", - "pages": [ - { - "name": "Basics", - "url": "tabs-basic-demos", - "src": "tabs-basic-demos.html", - "meta": { - "title": "vaadin-tabs Basic Examples", - "description": "", - "image": "" - } - } - , - { - "name": "Integration", - "url": "tabs-integration-demos", - "src": "tabs-integration-demos.html", - "meta": { - "title": "vaadin-tabs Integration Examples", - "description": "", - "image": "" - } - } - , - { - "name": "Theme Variants", - "url": "tabs-lumo-theme-demos", - "src": "tabs-lumo-theme-demos.html", - "meta": { - "title": "vaadin-tabs Theme Variants", - "description": "", - "image": "" - } - } - ] -} diff --git a/demo/index.html b/demo/index.html deleted file mode 100644 index d2cd293..0000000 --- a/demo/index.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - vaadin-tabs Examples - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/demo/my-custom-item.html b/demo/my-custom-item.html deleted file mode 100644 index 539d050..0000000 --- a/demo/my-custom-item.html +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - - - - - - - diff --git a/demo/tabs-basic-demos.html b/demo/tabs-basic-demos.html deleted file mode 100644 index c229022..0000000 --- a/demo/tabs-basic-demos.html +++ /dev/null @@ -1,140 +0,0 @@ - - - - diff --git a/demo/tabs-demo.js b/demo/tabs-demo.js deleted file mode 100644 index 0bda414..0000000 --- a/demo/tabs-demo.js +++ /dev/null @@ -1,13 +0,0 @@ -/* @polymerMixin */ -TabsDemo = superClass => { - return class extends superClass { - static get properties() { - return { - }; - } - }; -}; - -window.addEventListener('WebComponentsReady', () => { - document.body.removeAttribute('unresolved'); -}); diff --git a/demo/tabs-integration-demos.html b/demo/tabs-integration-demos.html deleted file mode 100644 index 5f971e0..0000000 --- a/demo/tabs-integration-demos.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - diff --git a/demo/tabs-lumo-theme-demos.html b/demo/tabs-lumo-theme-demos.html deleted file mode 100644 index d8416ce..0000000 --- a/demo/tabs-lumo-theme-demos.html +++ /dev/null @@ -1,172 +0,0 @@ - - - - diff --git a/gen-tsd.json b/gen-tsd.json deleted file mode 100644 index b3b8a28..0000000 --- a/gen-tsd.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "excludeFiles": [ - "wct.conf.js", - "index.html", - "demo/**/*", - "test/**/*", - "theme/**/*" - ], - "autoImport": { - "@vaadin/vaadin-list-mixin/interfaces": [ - "ListOrientation" - ] - } -} diff --git a/index.html b/index.html index 25be02e..d7baca4 100644 --- a/index.html +++ b/index.html @@ -5,10 +5,12 @@ vaadin-tabs - - - + + + diff --git a/magi-p3-post.js b/magi-p3-post.js deleted file mode 100644 index 1725a3d..0000000 --- a/magi-p3-post.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = { - files: [ - 'vaadin-tab.js', - 'vaadin-tabs.js' - ], - from: [ - /import '\.\/theme\/lumo\/vaadin-(.+)\.js';/ - ], - to: [ - `import './theme/lumo/vaadin-$1.js';\nexport * from './src/vaadin-$1.js';` - ] -}; diff --git a/package.json b/package.json index bb0693d..f2c47aa 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "@vaadin/vaadin-tabs", "version": "3.2.0", "description": "vaadin-tabs", - "main": "vaadin-tabs.html", + "main": "vaadin-tabs.js", + "module": "vaadin-tabs.js", "repository": "vaadin/vaadin-tabs", "keywords": [ "Vaadin", @@ -23,26 +24,72 @@ "src", "theme" ], + "scripts": { + "analyze": "polymer analyze vaadin-* > analysis.json", + "check-version": "magi check-version", + "debug": "web-test-runner test/*.test.js --watch", + "dist": "rimraf dist && npm run analyze && rollup -c rollup.config.js && cp analysis.json dist", + "lint": "npm run lint:js && npm run lint:css && npm run lint:types", + "lint:css": "stylelint src/*.js theme/**/*-styles.js", + "lint:js": "eslint src theme test", + "lint:types": "tsc", + "prestart": "npm run analyze", + "preversion": "magi update-version", + "screenshots": "hermione test/visual/test.js --update-refs", + "serve:dist": "web-dev-server --app-index dist/index.html --open", + "start": "web-dev-server --node-resolve --open", + "test": "web-test-runner test/*.test.js --coverage", + "test:sauce": "TEST_ENV=sauce npm test", + "test:visual": "hermione test/visual/test.js" + }, "husky": { "hooks": { - "pre-commit": "npm run lint" + "pre-commit": "lint-staged" } }, - "scripts": { - "test": "wct", - "check": "npm-run-all --parallel check:*", - "check:bower": "magi check-bower", - "check:version": "magi check-version", - "lint": "npm-run-all --parallel lint:*", - "lint:css": "stylelint *.html src/*.html demo/*.html theme/**/*.html test/*html", - "lint:html": "eslint *.html src demo test --ext .html", - "lint:js": "eslint *.js test", - "lint:polymer": "polymer lint --rules polymer-2 --input ./src/*.html ./theme/**/*.html", - "prestart": "polymer analyze vaadin-* > analysis.json", - "start": "polymer serve --port 3000 --open", - "preversion": "magi update-version" + "lint-staged": { + "*.js": [ + "eslint --fix", + "prettier --write" + ] + }, + "dependencies": { + "@polymer/polymer": "^3.0.0", + "@polymer/iron-resizable-behavior": "^3.0.0", + "@vaadin/vaadin-themable-mixin": "^1.6.2", + "@vaadin/vaadin-list-mixin": "^2.5.0", + "@vaadin/vaadin-item": "^3.0.0-alpha1", + "@vaadin/vaadin-lumo-styles": "^1.6.1", + "@vaadin/vaadin-material-styles": "^1.3.2", + "@vaadin/vaadin-element-mixin": "^2.4.2" }, "devDependencies": { - "@vaadin/vaadin-component-dev-dependencies": "^3.0.0" + "@esm-bundle/chai": "^4.1.5", + "@open-wc/rollup-plugin-html": "^1.2.5", + "@open-wc/testing-helpers": "^1.8.12", + "@polymer/iron-component-page": "^4.0.0", + "@polymer/iron-test-helpers": "^3.0.0", + "@rollup/plugin-node-resolve": "^11.0.0", + "@web/dev-server": "~0.0.25", + "@web/test-runner": "^0.10.0", + "@web/test-runner-saucelabs": "^0.1.3", + "eslint": "^7.14.0", + "eslint-config-prettier": "^6.15.0", + "eslint-plugin-prettier": "^3.1.4", + "hermione": "^3.9.0", + "hermione-esm": "^0.4.0", + "hermione-sauce": "^0.1.0", + "husky": "^4.3.0", + "lint-staged": "^10.5.1", + "magi-cli": "^0.29.0", + "prettier": "^2.2.0", + "rimraf": "^3.0.2", + "rollup": "^2.34.1", + "rollup-plugin-terser": "^7.0.2", + "sinon": "^9.2.1", + "stylelint": "^13.8.0", + "stylelint-config-prettier": "^8.0.2", + "stylelint-config-vaadin": "^0.2.7", + "typescript": "^4.1.2" } } diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..b4695e8 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,12 @@ +/* eslint-env node */ +const { nodeResolve } = require('@rollup/plugin-node-resolve'); +const { terser } = require('rollup-plugin-terser'); +const html = require('@open-wc/rollup-plugin-html'); + +module.exports = { + input: './index.html', + output: { + dir: './dist' + }, + plugins: [html(), nodeResolve(), terser()] +}; diff --git a/src/vaadin-tab.d.ts b/src/vaadin-tab.d.ts new file mode 100644 index 0000000..ba500bb --- /dev/null +++ b/src/vaadin-tab.d.ts @@ -0,0 +1,39 @@ +import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; + +import { ItemMixin } from '@vaadin/vaadin-item/src/vaadin-item-mixin.js'; + +import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js'; + +/** + * `` is a Web Component providing an accessible and customizable tab. + * + * ``` + * + * Tab 1 + * + * ``` + * + * The following state attributes are available for styling: + * + * Attribute | Description | Part name + * -----------|-------------|------------ + * `disabled` | Set to a disabled tab | :host + * `focused` | Set when the element is focused | :host + * `focus-ring` | Set when the element is keyboard focused | :host + * `selected` | Set when the tab is selected | :host + * `active` | Set when mousedown or enter/spacebar pressed | :host + * `orientation` | Set to `horizontal` or `vertical` depending on the direction of items | :host + * + * See [ThemableMixin – how to apply styles for shadow parts](https://github.com/vaadin/vaadin-themable-mixin/wiki) + */ +declare class TabElement extends ElementMixin(ThemableMixin(ItemMixin(HTMLElement))) { + _onKeyup(event: KeyboardEvent): void; +} + +declare global { + interface HTMLElementTagNameMap { + 'vaadin-tab': TabElement; + } +} + +export { TabElement }; diff --git a/src/vaadin-tab.html b/src/vaadin-tab.html deleted file mode 100644 index 946e951..0000000 --- a/src/vaadin-tab.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - diff --git a/src/vaadin-tab.js b/src/vaadin-tab.js new file mode 100644 index 0000000..bc537d0 --- /dev/null +++ b/src/vaadin-tab.js @@ -0,0 +1,76 @@ +/** + * @license + * Copyright (c) 2020 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import { PolymerElement, html } from '@polymer/polymer/polymer-element.js'; +import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; +import { ItemMixin } from '@vaadin/vaadin-item/src/vaadin-item-mixin.js'; +import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js'; + +/** + * `` is a Web Component providing an accessible and customizable tab. + * + * ``` + * + * Tab 1 + * + * ``` + * + * The following state attributes are available for styling: + * + * Attribute | Description | Part name + * -----------|-------------|------------ + * `disabled` | Set to a disabled tab | :host + * `focused` | Set when the element is focused | :host + * `focus-ring` | Set when the element is keyboard focused | :host + * `selected` | Set when the tab is selected | :host + * `active` | Set when mousedown or enter/spacebar pressed | :host + * `orientation` | Set to `horizontal` or `vertical` depending on the direction of items | :host + * + * See [ThemableMixin – how to apply styles for shadow parts](https://github.com/vaadin/vaadin-themable-mixin/wiki) + * + * @extends HTMLElement + * @mixes ElementMixin + * @mixes ThemableMixin + * @mixes ItemMixin + */ +class TabElement extends ElementMixin(ThemableMixin(ItemMixin(PolymerElement))) { + static get template() { + return html``; + } + + static get is() { + return 'vaadin-tab'; + } + + static get version() { + return '3.2.0'; + } + + ready() { + super.ready(); + this.setAttribute('role', 'tab'); + } + + /** + * @param {!KeyboardEvent} event + * @protected + */ + _onKeyup(event) { + const willClick = this.hasAttribute('active'); + + super._onKeyup(event); + + if (willClick) { + const anchor = this.querySelector('a'); + if (anchor) { + anchor.click(); + } + } + } +} + +customElements.define(TabElement.is, TabElement); + +export { TabElement }; diff --git a/src/vaadin-tabs.d.ts b/src/vaadin-tabs.d.ts new file mode 100644 index 0000000..7a463e2 --- /dev/null +++ b/src/vaadin-tabs.d.ts @@ -0,0 +1,92 @@ +import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; + +import { ListMixin } from '@vaadin/vaadin-list-mixin/vaadin-list-mixin.js'; + +import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js'; + +import { ListOrientation } from '@vaadin/vaadin-list-mixin/interfaces'; + +/** + * Fired when the `items` property changes. + */ +export type TabsItemsChanged = CustomEvent<{ value: Array }>; + +/** + * Fired when the `selected` property changes. + */ +export type TabsSelectedChanged = CustomEvent<{ value: number }>; + +export interface TabsElementEventMap { + 'items-changed': TabsItemsChanged; + + 'selected-changed': TabsSelectedChanged; +} + +export interface TabsEventMap extends HTMLElementEventMap, TabsElementEventMap {} + +/** + * `` is a Web Component for easy switching between different views. + * + * ``` + * + * Page 1 + * Page 2 + * Page 3 + * Page 4 + * + * ``` + * + * ### Styling + * + * The following shadow DOM parts are available for styling: + * + * Part name | Description + * ------------------|-------------------------------------- + * `back-button` | Button for moving the scroll back + * `tabs` | The tabs container + * `forward-button` | Button for moving the scroll forward + * + * The following state attributes are available for styling: + * + * Attribute | Description | Part name + * -----------|-------------|------------ + * `orientation` | Tabs disposition, valid values are `horizontal` and `vertical`. | :host + * `overflow` | It's set to `start`, `end`, none or both. | :host + * + * See [ThemableMixin – how to apply styles for shadow parts](https://github.com/vaadin/vaadin-themable-mixin/wiki) + */ +declare class TabsElement extends ElementMixin(ListMixin(ThemableMixin(HTMLElement))) { + readonly _scrollerElement: HTMLElement; + + /** + * The index of the selected tab. + */ + selected: number | null | undefined; + + /** + * Set tabs disposition. Possible values are `horizontal|vertical` + */ + orientation: ListOrientation; + + readonly _scrollOffset: number; + + addEventListener( + type: K, + listener: (this: TabsElement, ev: TabsEventMap[K]) => void, + options?: boolean | AddEventListenerOptions + ): void; + + removeEventListener( + type: K, + listener: (this: TabsElement, ev: TabsEventMap[K]) => void, + options?: boolean | EventListenerOptions + ): void; +} + +declare global { + interface HTMLElementTagNameMap { + 'vaadin-tabs': TabsElement; + } +} + +export { TabsElement }; diff --git a/src/vaadin-tabs.html b/src/vaadin-tabs.html deleted file mode 100644 index 55c3560..0000000 --- a/src/vaadin-tabs.html +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/vaadin-tabs.js b/src/vaadin-tabs.js new file mode 100644 index 0000000..a3ff1bd --- /dev/null +++ b/src/vaadin-tabs.js @@ -0,0 +1,242 @@ +/** + * @license + * Copyright (c) 2020 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import { PolymerElement, html } from '@polymer/polymer/polymer-element.js'; +import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js'; +import { mixinBehaviors } from '@polymer/polymer/lib/legacy/class.js'; +import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; +import { ListMixin } from '@vaadin/vaadin-list-mixin/vaadin-list-mixin.js'; +import { IronResizableBehavior } from '@polymer/iron-resizable-behavior/iron-resizable-behavior.js'; +import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js'; +import './vaadin-tab.js'; + +/** + * `` is a Web Component for easy switching between different views. + * + * ``` + * + * Page 1 + * Page 2 + * Page 3 + * Page 4 + * + * ``` + * + * ### Styling + * + * The following shadow DOM parts are available for styling: + * + * Part name | Description + * ------------------|-------------------------------------- + * `back-button` | Button for moving the scroll back + * `tabs` | The tabs container + * `forward-button` | Button for moving the scroll forward + * + * The following state attributes are available for styling: + * + * Attribute | Description | Part name + * -----------|-------------|------------ + * `orientation` | Tabs disposition, valid values are `horizontal` and `vertical`. | :host + * `overflow` | It's set to `start`, `end`, none or both. | :host + * + * See [ThemableMixin – how to apply styles for shadow parts](https://github.com/vaadin/vaadin-themable-mixin/wiki) + * + * @extends HTMLElement + * @mixes ElementMixin + * @mixes ListMixin + * @mixes ThemableMixin + */ +class TabsElement extends ElementMixin( + ListMixin(ThemableMixin(mixinBehaviors([IronResizableBehavior], PolymerElement))) +) { + static get template() { + return html` + +
+ +
+ +
+ +
+ `; + } + + static get is() { + return 'vaadin-tabs'; + } + + static get version() { + return '3.2.0'; + } + + static get properties() { + return { + /** + * Set tabs disposition. Possible values are `horizontal|vertical` + * @type {!ListOrientation} + */ + orientation: { + value: 'horizontal', + type: String + }, + + /** + * The index of the selected tab. + */ + selected: { + value: 0, + type: Number + } + }; + } + + static get observers() { + return ['_updateOverflow(items.*)']; + } + + ready() { + super.ready(); + this.addEventListener('iron-resize', () => this._updateOverflow()); + this._scrollerElement.addEventListener('scroll', () => this._updateOverflow()); + this.setAttribute('role', 'tablist'); + + // Wait for the vaadin-tab elements to upgrade and get styled + afterNextRender(this, () => { + this._updateOverflow(); + }); + } + + /** @private */ + _scrollForward() { + this._scroll(-this.__direction * this._scrollOffset); + } + + /** @private */ + _scrollBack() { + this._scroll(this.__direction * this._scrollOffset); + } + + /** + * @return {number} + * @protected + */ + get _scrollOffset() { + return this._vertical ? this._scrollerElement.offsetHeight : this._scrollerElement.offsetWidth; + } + + /** + * @return {!HTMLElement} + * @protected + */ + get _scrollerElement() { + return this.$.scroll; + } + + /** @private */ + get __direction() { + return !this._vertical && this.getAttribute('dir') === 'rtl' ? 1 : -1; + } + + /** @private */ + _updateOverflow() { + const scrollPosition = this._vertical + ? this._scrollerElement.scrollTop + : this.__getNormalizedScrollLeft(this._scrollerElement); + const scrollSize = this._vertical ? this._scrollerElement.scrollHeight : this._scrollerElement.scrollWidth; + + let overflow = scrollPosition > 0 ? 'start' : ''; + overflow += scrollPosition + this._scrollOffset < scrollSize ? ' end' : ''; + + if (this.__direction == 1) { + overflow = overflow.replace(/start|end/gi, (matched) => { + return matched === 'start' ? 'end' : 'start'; + }); + } + + overflow ? this.setAttribute('overflow', overflow.trim()) : this.removeAttribute('overflow'); + } +} + +customElements.define(TabsElement.is, TabsElement); + +export { TabsElement }; diff --git a/test/.eslintrc.json b/test/.eslintrc.json index 0090a48..7eeefc3 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -1,13 +1,5 @@ { - "globals": { - "WCT": false, - "describe": false, - "beforeEach": false, - "fixture": false, - "it": false, - "expect": false, - "gemini": false, - "sinon": false, - "MockInteractions": false + "env": { + "mocha": true } } diff --git a/test/index.html b/test/index.html deleted file mode 100644 index 9d7f5be..0000000 --- a/test/index.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - vaadin-tabs tests - - - - - - - - - diff --git a/test/items.html b/test/items.html deleted file mode 100644 index 83934fc..0000000 --- a/test/items.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - - vaadin-tabs tests - - - - - - - - - - - - - diff --git a/test/nav.html b/test/nav.html deleted file mode 100644 index 3946a38..0000000 --- a/test/nav.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - - vaadin-tabs tests - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/scroll.test.js b/test/scroll.test.js new file mode 100644 index 0000000..1ee9653 --- /dev/null +++ b/test/scroll.test.js @@ -0,0 +1,121 @@ +import { expect } from '@esm-bundle/chai'; +import { fixtureSync } from '@open-wc/testing-helpers'; +import { keyDownOn } from '@polymer/iron-test-helpers/mock-interactions.js'; +import '../vaadin-tabs.js'; + +describe('scrollable tabs', () => { + let tabs, items, scroller; + + function arrowDown(target) { + keyDownOn(target, 40, [], 'ArrowDown'); + } + + function arrowRight(target) { + keyDownOn(target, 39, [], 'ArrowRight'); + } + + function arrowUp(target) { + keyDownOn(target, 38, [], 'ArrowUp'); + } + + function arrowLeft(target) { + keyDownOn(target, 37, [], 'ArrowLeft'); + } + + beforeEach(() => { + tabs = fixtureSync(` + + Foo + Bar + ___ + Baz + Foo1 + Bar1 + Baz1 + Foo2 + Bar2 + + `); + tabs._observer.flush(); + items = tabs.items; + scroller = tabs.shadowRoot.querySelector('[part="tabs"]'); + }); + + describe('horizontal', () => { + beforeEach(() => { + tabs.orientation = 'horizontal'; + }); + + it('should show one extra item on the right edge of the viewport on "arrow-right" on last visible tab', () => { + tabs.selected = 5; + tabs._focus(5); + // Check the scroller is not scrolled vertically + arrowRight(tabs); + expect(scroller.getBoundingClientRect().right).to.be.closeTo(items[7].getBoundingClientRect().right, 1); + }); + + it('should show one extra item on the left edge of the viewport on "arrow-left" on first visible tab', () => { + // Move scroller so the first tab will be out of visible part + tabs.selected = 7; + tabs._focus(7); + // Check the scroller is not scrolled vertically + expect(scroller.scrollTop).to.be.equal(0); + // Move focus and choose the first visible tab selected + items[2].disabled = false; + tabs.selected = 2; + tabs._focus(2); + + arrowLeft(tabs); + expect(scroller.getBoundingClientRect().left).to.be.closeTo(items[0].getBoundingClientRect().left, 1); + }); + + it('should scroll forward when arrow button is clicked', () => { + const btn = tabs.shadowRoot.querySelector('[part="forward-button"]'); + btn.click(); + expect(scroller.scrollLeft).to.be.closeTo(scroller.scrollWidth - scroller.offsetWidth, 1); + }); + + it('should scroll back when arrow button is clicked', () => { + tabs.selected = 4; + const btn = tabs.shadowRoot.querySelector('[part="back-button"]'); + btn.click(); + expect(scroller.scrollLeft).to.be.equal(0); + }); + }); + + describe('vertical', () => { + beforeEach(() => { + tabs.orientation = 'vertical'; + tabs._updateOverflow(); + }); + + it('should move the scroll vertically to display selected item', () => { + expect(scroller.scrollTop).to.be.equal(0); + tabs.selected = 5; + tabs._focus(5); + expect(scroller.scrollTop).to.be.greaterThan(0); + }); + + it('should show one extra item on the bottom edge of the viewport on "arrow-down" on last visible tab', () => { + tabs.selected = 5; + tabs._focus(5); + + const scrollPosition = items[7].getBoundingClientRect().bottom; + arrowDown(tabs); + expect(items[7].getBoundingClientRect().bottom).to.be.lessThan(scrollPosition); + }); + + it('should show one extra item on the top edge of the viewport on "arrow-up" on first visible tab', () => { + items[2].disabled = false; + tabs.selected = 7; + tabs._focus(7); + + tabs.selected = 2; + tabs._focus(2); + + const scrollPosition = items[7].getBoundingClientRect().bottom; + arrowUp(tabs); + expect(items[7].getBoundingClientRect().bottom).to.be.greaterThan(scrollPosition); + }); + }); +}); diff --git a/test/scrollable-tabs.html b/test/scrollable-tabs.html deleted file mode 100644 index 177a1dd..0000000 --- a/test/scrollable-tabs.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - vaadin-tabs tests - - - - - - - - - - - - - - - diff --git a/test/tab.test.js b/test/tab.test.js new file mode 100644 index 0000000..df116ce --- /dev/null +++ b/test/tab.test.js @@ -0,0 +1,30 @@ +import { expect } from '@esm-bundle/chai'; +import { fixtureSync } from '@open-wc/testing-helpers'; +import '../vaadin-tab.js'; + +describe('tab', () => { + let tab; + + beforeEach(() => { + tab = fixtureSync('text-content'); + }); + + it('should have a correct localName', () => { + expect(tab.localName).to.be.equal('vaadin-tab'); + }); + + it('should have a correct role', () => { + expect(tab.getAttribute('role')).to.be.equal('tab'); + }); + + it('should have an unnamed slot for content', () => { + const slot = tab.shadowRoot.querySelector('slot:not([name])'); + const content = slot.assignedNodes()[0]; + expect(content.nodeType).to.be.equal(3); + expect(content.textContent.trim()).to.be.equal('text-content'); + }); + + it('should extend vaadin-item-mixin', () => { + expect(tab._hasVaadinItemMixin).to.be.true; + }); +}); diff --git a/test/tabs.test.js b/test/tabs.test.js new file mode 100644 index 0000000..c2f752c --- /dev/null +++ b/test/tabs.test.js @@ -0,0 +1,224 @@ +import { expect } from '@esm-bundle/chai'; +import sinon from 'sinon'; +import { fixtureSync } from '@open-wc/testing-helpers'; +import { keyDownOn, keyUpOn } from '@polymer/iron-test-helpers/mock-interactions.js'; +import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js'; +import '../vaadin-tabs.js'; + +describe('tabs', () => { + let tabs; + + function listenOnce(element, eventName, callback) { + const listener = (e) => { + element.removeEventListener(eventName, listener); + callback(e); + }; + element.addEventListener(eventName, listener); + } + + beforeEach(() => { + tabs = fixtureSync(` + + Foo + Bar + + Baz + + Baz + + + `); + tabs._observer.flush(); + }); + + describe('custom element definition', () => { + let tagName; + + beforeEach(() => { + tagName = tabs.tagName.toLowerCase(); + }); + + it('should be defined in custom element registry', () => { + expect(customElements.get(tagName)).to.be.ok; + }); + + it('should have a valid static "is" getter', () => { + expect(customElements.get(tagName).is).to.equal(tagName); + }); + + it('should have a valid version number', () => { + expect(customElements.get(tagName).version).to.match(/^(\d+\.)?(\d+\.)?(\d+)(-(alpha|beta)\d+)?$/); + }); + }); + + describe('items', () => { + it('should only add vaadin-tab components to items', () => { + expect(tabs.items.length).to.equal(4); + tabs.items.forEach((item) => { + expect(item.tagName.toLowerCase()).to.equal('vaadin-tab'); + }); + }); + }); + + ['horizontal', 'vertical'].forEach((orientation) => { + ['ltr', 'rtl'].forEach((direction) => { + describe(`Overflow ${orientation} ${direction}`, () => { + beforeEach(() => { + tabs.orientation = orientation; + document.documentElement.setAttribute('dir', direction); + }); + + afterEach(() => { + document.documentElement.removeAttribute('dir'); + }); + + describe('large viewport', () => { + it(`when orientation=${orientation} should not have overflow`, () => { + expect(tabs.hasAttribute('overflow')).to.be.false; + }); + }); + + describe('small viewport', () => { + const horizontalRtl = orientation === 'horizontal' && direction === 'rtl'; + + beforeEach((done) => { + if (orientation === 'horizontal') { + tabs.style.width = '200px'; + } else { + tabs.style.height = '100px'; + } + tabs._updateOverflow(); + afterNextRender(tabs, done); + }); + + it(`when orientation=${orientation} should have overflow="end" if scroll is at the beginning`, () => { + expect(tabs.getAttribute('overflow')).to.be.equal('end'); + }); + + it(`when orientation=${orientation} should have overflow="start end" if scroll is at the middle`, (done) => { + listenOnce(tabs._scrollerElement, 'scroll', () => { + expect(tabs.getAttribute('overflow')).to.contain('start'); + expect(tabs.getAttribute('overflow')).to.contain('end'); + done(); + }); + tabs._scroll(horizontalRtl ? -2 : 2); + }); + + // TODO: passes locally but fails in GitHub Actions due to 1px difference. + const chrome = /HeadlessChrome/.test(navigator.userAgent); + (horizontalRtl && chrome ? it.skip : it)( + `when orientation=${orientation} should have overflow="start" if scroll is at the end`, + (done) => { + listenOnce(tabs._scrollerElement, 'scroll', () => { + expect(tabs.getAttribute('overflow')).to.be.equal('start'); + done(); + }); + tabs._scroll(horizontalRtl ? -200 : 200); + } + ); + + it(`when orientation=${orientation} should not have overflow="start" when over-scrolling`, () => { + const scroll = tabs._scrollerElement; + + // Cannot set negative values to native scroll, monkey patching the properties + let pixels = 0; + Object.defineProperty(scroll, orientation == 'horizontal' ? 'scrollLeft' : 'scrollTop', { + get: () => pixels, + set: (v) => { + pixels = v; + } + }); + + // Simulate over-scrolling + tabs._scroll(horizontalRtl ? 400 : -400); + scroll.dispatchEvent(new CustomEvent('scroll')); + + expect(tabs.getAttribute('overflow')).to.be.equal('end'); + }); + }); + }); + }); + }); + + describe('slotted anchor', () => { + let anchor, tab, spy; + + beforeEach(() => { + anchor = tabs.querySelector('a'); + tab = anchor.parentElement; + spy = sinon.spy(); + anchor.addEventListener('click', spy); + }); + + it('should propagate click to the anchor element when Enter key pressed', () => { + keyDownOn(tab, 13, [], 'Enter'); + keyUpOn(tab, 13, [], 'Enter'); + expect(spy.calledOnce).to.be.true; + }); + + it('should propagate click to the anchor element when Space key pressed', () => { + keyDownOn(tab, 27, [], ' '); + keyUpOn(tab, 27, [], ' '); + expect(spy.calledOnce).to.be.true; + }); + + it('should not propagate click to the anchor when other key pressed', () => { + keyDownOn(tab, 39, [], 'ArrowRight'); + keyUpOn(tab, 39, [], 'ArrowRight'); + expect(spy.calledOnce).to.be.false; + }); + }); + + describe('ARIA roles', () => { + it('should set "tablist" role on the tabs container', () => { + expect(tabs.getAttribute('role')).to.equal('tablist'); + }); + }); +}); + +describe('flex child tabs', () => { + let wrapper, tabs; + + beforeEach(() => { + wrapper = fixtureSync(` +
+ + Foo + Bar + +
+ `); + tabs = wrapper.querySelector('vaadin-tabs'); + }); + + it('should have width above zero', () => { + expect(tabs.offsetWidth).to.be.above(0); + }); + + it('should not scroll', () => { + expect(tabs.$.scroll.scrollWidth).to.be.equal(tabs.$.scroll.offsetWidth); + }); +}); + +describe('flex equal width tabs', () => { + let wrapper, tabs; + + beforeEach(() => { + wrapper = fixtureSync(` +
+ + Tab one + Tab two with a longer title + Tab three + +
+ `); + tabs = wrapper.querySelector('vaadin-tabs'); + tabs._observer.flush(); + }); + + it('should not cut content', () => { + expect(tabs.items[1].offsetWidth).to.be.above(124); + expect(tabs.offsetWidth).to.be.eql(400); + }); +}); diff --git a/test/test-suites.js b/test/test-suites.js deleted file mode 100644 index a88ccd0..0000000 --- a/test/test-suites.js +++ /dev/null @@ -1,5 +0,0 @@ -window.VaadinTabsSuites = [ - 'items.html', - 'nav.html', - 'scrollable-tabs.html' -]; diff --git a/test/typings/tabs.types.ts b/test/typings/tabs.types.ts new file mode 100644 index 0000000..57bed2e --- /dev/null +++ b/test/typings/tabs.types.ts @@ -0,0 +1,13 @@ +import '../../src/vaadin-tabs'; + +const tabs = document.createElement('vaadin-tabs'); + +const assert = (value: T) => value; + +tabs.addEventListener('items-changed', (event) => { + assert(event.detail.value); +}); + +tabs.addEventListener('selected-changed', (event) => { + assert(event.detail.value); +}); diff --git a/test/visual/anchor-tabs.html b/test/visual/anchor-tabs.html index 5a3808e..6eaf2b5 100644 --- a/test/visual/anchor-tabs.html +++ b/test/visual/anchor-tabs.html @@ -1,18 +1,17 @@ - - + + Anchor tabs visual tests - - + diff --git a/test/visual/common.html b/test/visual/common.html deleted file mode 100644 index 1554411..0000000 --- a/test/visual/common.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - diff --git a/test/visual/horizontal-tabs.html b/test/visual/horizontal-tabs.html index 7aa34c1..08d15b5 100644 --- a/test/visual/horizontal-tabs.html +++ b/test/visual/horizontal-tabs.html @@ -1,30 +1,21 @@ - - - Horizontal nav visual tests - - + + + Horizontal tabs visual tests - -
+
Foo Bar @@ -32,4 +23,15 @@
+ diff --git a/test/visual/screens/vaadin-tabs/anchor-tabs-lumo/anchor-tabs/chrome.png b/test/visual/screens/vaadin-tabs/anchor-tabs-lumo/anchor-tabs/chrome.png deleted file mode 100644 index f59c082..0000000 Binary files a/test/visual/screens/vaadin-tabs/anchor-tabs-lumo/anchor-tabs/chrome.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/anchor-tabs-lumo/anchor-tabs/firefox.png b/test/visual/screens/vaadin-tabs/anchor-tabs-lumo/anchor-tabs/firefox.png deleted file mode 100644 index ba13d40..0000000 Binary files a/test/visual/screens/vaadin-tabs/anchor-tabs-lumo/anchor-tabs/firefox.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/anchor-tabs-material/anchor-tabs/chrome.png b/test/visual/screens/vaadin-tabs/anchor-tabs-material/anchor-tabs/chrome.png deleted file mode 100644 index 5040af9..0000000 Binary files a/test/visual/screens/vaadin-tabs/anchor-tabs-material/anchor-tabs/chrome.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/anchor-tabs-material/anchor-tabs/firefox.png b/test/visual/screens/vaadin-tabs/anchor-tabs-material/anchor-tabs/firefox.png deleted file mode 100644 index 37daf00..0000000 Binary files a/test/visual/screens/vaadin-tabs/anchor-tabs-material/anchor-tabs/firefox.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/anchor-tabs-rtl-lumo/anchor-tabs/chrome.png b/test/visual/screens/vaadin-tabs/anchor-tabs-rtl-lumo/anchor-tabs/chrome.png deleted file mode 100644 index 5126598..0000000 Binary files a/test/visual/screens/vaadin-tabs/anchor-tabs-rtl-lumo/anchor-tabs/chrome.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/anchor-tabs-rtl-lumo/anchor-tabs/firefox.png b/test/visual/screens/vaadin-tabs/anchor-tabs-rtl-lumo/anchor-tabs/firefox.png deleted file mode 100644 index dde5dc5..0000000 Binary files a/test/visual/screens/vaadin-tabs/anchor-tabs-rtl-lumo/anchor-tabs/firefox.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/anchor-tabs-rtl-material/anchor-tabs/chrome.png b/test/visual/screens/vaadin-tabs/anchor-tabs-rtl-material/anchor-tabs/chrome.png deleted file mode 100644 index 142f54a..0000000 Binary files a/test/visual/screens/vaadin-tabs/anchor-tabs-rtl-material/anchor-tabs/chrome.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/anchor-tabs-rtl-material/anchor-tabs/firefox.png b/test/visual/screens/vaadin-tabs/anchor-tabs-rtl-material/anchor-tabs/firefox.png deleted file mode 100644 index 83f56c7..0000000 Binary files a/test/visual/screens/vaadin-tabs/anchor-tabs-rtl-material/anchor-tabs/firefox.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/horizontal-tabs-lumo/horizontal-tabs/chrome.png b/test/visual/screens/vaadin-tabs/horizontal-tabs-lumo/horizontal-tabs/chrome.png deleted file mode 100644 index a62e5b8..0000000 Binary files a/test/visual/screens/vaadin-tabs/horizontal-tabs-lumo/horizontal-tabs/chrome.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/horizontal-tabs-lumo/horizontal-tabs/firefox.png b/test/visual/screens/vaadin-tabs/horizontal-tabs-lumo/horizontal-tabs/firefox.png deleted file mode 100644 index 6beb6cf..0000000 Binary files a/test/visual/screens/vaadin-tabs/horizontal-tabs-lumo/horizontal-tabs/firefox.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/horizontal-tabs-material/horizontal-tabs/chrome.png b/test/visual/screens/vaadin-tabs/horizontal-tabs-material/horizontal-tabs/chrome.png deleted file mode 100644 index 96ba59d..0000000 Binary files a/test/visual/screens/vaadin-tabs/horizontal-tabs-material/horizontal-tabs/chrome.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/horizontal-tabs-material/horizontal-tabs/firefox.png b/test/visual/screens/vaadin-tabs/horizontal-tabs-material/horizontal-tabs/firefox.png deleted file mode 100644 index 4ed7025..0000000 Binary files a/test/visual/screens/vaadin-tabs/horizontal-tabs-material/horizontal-tabs/firefox.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/lumo-anchor-tabs-ltr.png b/test/visual/screens/vaadin-tabs/lumo-anchor-tabs-ltr.png new file mode 100644 index 0000000..18426c2 Binary files /dev/null and b/test/visual/screens/vaadin-tabs/lumo-anchor-tabs-ltr.png differ diff --git a/test/visual/screens/vaadin-tabs/lumo-anchor-tabs-rtl.png b/test/visual/screens/vaadin-tabs/lumo-anchor-tabs-rtl.png new file mode 100644 index 0000000..2090cb5 Binary files /dev/null and b/test/visual/screens/vaadin-tabs/lumo-anchor-tabs-rtl.png differ diff --git a/test/visual/screens/vaadin-tabs/lumo-horizontal-tabs.png b/test/visual/screens/vaadin-tabs/lumo-horizontal-tabs.png new file mode 100644 index 0000000..85c0677 Binary files /dev/null and b/test/visual/screens/vaadin-tabs/lumo-horizontal-tabs.png differ diff --git a/test/visual/screens/vaadin-tabs/lumo-scrollable-tabs-ltr.png b/test/visual/screens/vaadin-tabs/lumo-scrollable-tabs-ltr.png new file mode 100644 index 0000000..07bb34b Binary files /dev/null and b/test/visual/screens/vaadin-tabs/lumo-scrollable-tabs-ltr.png differ diff --git a/test/visual/screens/vaadin-tabs/lumo-scrollable-tabs-rtl.png b/test/visual/screens/vaadin-tabs/lumo-scrollable-tabs-rtl.png new file mode 100644 index 0000000..e76c60b Binary files /dev/null and b/test/visual/screens/vaadin-tabs/lumo-scrollable-tabs-rtl.png differ diff --git a/test/visual/screens/vaadin-tabs/vertical-tabs-lumo/vertical-tabs/chrome.png b/test/visual/screens/vaadin-tabs/lumo-vertical-tabs.png similarity index 89% rename from test/visual/screens/vaadin-tabs/vertical-tabs-lumo/vertical-tabs/chrome.png rename to test/visual/screens/vaadin-tabs/lumo-vertical-tabs.png index 584440c..074af52 100644 Binary files a/test/visual/screens/vaadin-tabs/vertical-tabs-lumo/vertical-tabs/chrome.png and b/test/visual/screens/vaadin-tabs/lumo-vertical-tabs.png differ diff --git a/test/visual/screens/vaadin-tabs/material-anchor-tabs-ltr.png b/test/visual/screens/vaadin-tabs/material-anchor-tabs-ltr.png new file mode 100644 index 0000000..3f9db7e Binary files /dev/null and b/test/visual/screens/vaadin-tabs/material-anchor-tabs-ltr.png differ diff --git a/test/visual/screens/vaadin-tabs/material-anchor-tabs-rtl.png b/test/visual/screens/vaadin-tabs/material-anchor-tabs-rtl.png new file mode 100644 index 0000000..bcc1abf Binary files /dev/null and b/test/visual/screens/vaadin-tabs/material-anchor-tabs-rtl.png differ diff --git a/test/visual/screens/vaadin-tabs/material-horizontal-tabs.png b/test/visual/screens/vaadin-tabs/material-horizontal-tabs.png new file mode 100644 index 0000000..18a0c09 Binary files /dev/null and b/test/visual/screens/vaadin-tabs/material-horizontal-tabs.png differ diff --git a/test/visual/screens/vaadin-tabs/material-scrollable-tabs-ltr.png b/test/visual/screens/vaadin-tabs/material-scrollable-tabs-ltr.png new file mode 100644 index 0000000..4cf53b2 Binary files /dev/null and b/test/visual/screens/vaadin-tabs/material-scrollable-tabs-ltr.png differ diff --git a/test/visual/screens/vaadin-tabs/material-scrollable-tabs-rtl.png b/test/visual/screens/vaadin-tabs/material-scrollable-tabs-rtl.png new file mode 100644 index 0000000..11bdeec Binary files /dev/null and b/test/visual/screens/vaadin-tabs/material-scrollable-tabs-rtl.png differ diff --git a/test/visual/screens/vaadin-tabs/material-vertical-tabs.png b/test/visual/screens/vaadin-tabs/material-vertical-tabs.png new file mode 100644 index 0000000..9a96f0c Binary files /dev/null and b/test/visual/screens/vaadin-tabs/material-vertical-tabs.png differ diff --git a/test/visual/screens/vaadin-tabs/scrollable-tabs-lumo/scrollable-tabs/chrome.png b/test/visual/screens/vaadin-tabs/scrollable-tabs-lumo/scrollable-tabs/chrome.png deleted file mode 100644 index 458ddc4..0000000 Binary files a/test/visual/screens/vaadin-tabs/scrollable-tabs-lumo/scrollable-tabs/chrome.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/scrollable-tabs-lumo/scrollable-tabs/firefox.png b/test/visual/screens/vaadin-tabs/scrollable-tabs-lumo/scrollable-tabs/firefox.png deleted file mode 100644 index f211eb5..0000000 Binary files a/test/visual/screens/vaadin-tabs/scrollable-tabs-lumo/scrollable-tabs/firefox.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/scrollable-tabs-material/scrollable-tabs/chrome.png b/test/visual/screens/vaadin-tabs/scrollable-tabs-material/scrollable-tabs/chrome.png deleted file mode 100644 index 530077f..0000000 Binary files a/test/visual/screens/vaadin-tabs/scrollable-tabs-material/scrollable-tabs/chrome.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/scrollable-tabs-material/scrollable-tabs/firefox.png b/test/visual/screens/vaadin-tabs/scrollable-tabs-material/scrollable-tabs/firefox.png deleted file mode 100644 index f57b67f..0000000 Binary files a/test/visual/screens/vaadin-tabs/scrollable-tabs-material/scrollable-tabs/firefox.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/scrollable-tabs-rtl-lumo/scrollable-tabs/chrome.png b/test/visual/screens/vaadin-tabs/scrollable-tabs-rtl-lumo/scrollable-tabs/chrome.png deleted file mode 100644 index 9a2ae1b..0000000 Binary files a/test/visual/screens/vaadin-tabs/scrollable-tabs-rtl-lumo/scrollable-tabs/chrome.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/scrollable-tabs-rtl-lumo/scrollable-tabs/firefox.png b/test/visual/screens/vaadin-tabs/scrollable-tabs-rtl-lumo/scrollable-tabs/firefox.png deleted file mode 100644 index 751926f..0000000 Binary files a/test/visual/screens/vaadin-tabs/scrollable-tabs-rtl-lumo/scrollable-tabs/firefox.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/scrollable-tabs-rtl-material/scrollable-tabs/chrome.png b/test/visual/screens/vaadin-tabs/scrollable-tabs-rtl-material/scrollable-tabs/chrome.png deleted file mode 100644 index c7b2102..0000000 Binary files a/test/visual/screens/vaadin-tabs/scrollable-tabs-rtl-material/scrollable-tabs/chrome.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/scrollable-tabs-rtl-material/scrollable-tabs/firefox.png b/test/visual/screens/vaadin-tabs/scrollable-tabs-rtl-material/scrollable-tabs/firefox.png deleted file mode 100644 index 0dd66e7..0000000 Binary files a/test/visual/screens/vaadin-tabs/scrollable-tabs-rtl-material/scrollable-tabs/firefox.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/vertical-tabs-lumo/vertical-tabs/firefox.png b/test/visual/screens/vaadin-tabs/vertical-tabs-lumo/vertical-tabs/firefox.png deleted file mode 100644 index cab2ef5..0000000 Binary files a/test/visual/screens/vaadin-tabs/vertical-tabs-lumo/vertical-tabs/firefox.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/vertical-tabs-material/vertical-tabs/chrome.png b/test/visual/screens/vaadin-tabs/vertical-tabs-material/vertical-tabs/chrome.png deleted file mode 100644 index 117dd30..0000000 Binary files a/test/visual/screens/vaadin-tabs/vertical-tabs-material/vertical-tabs/chrome.png and /dev/null differ diff --git a/test/visual/screens/vaadin-tabs/vertical-tabs-material/vertical-tabs/firefox.png b/test/visual/screens/vaadin-tabs/vertical-tabs-material/vertical-tabs/firefox.png deleted file mode 100644 index a891bd9..0000000 Binary files a/test/visual/screens/vaadin-tabs/vertical-tabs-material/vertical-tabs/firefox.png and /dev/null differ diff --git a/test/visual/scrollable-tabs.html b/test/visual/scrollable-tabs.html index 7895761..dd7612c 100644 --- a/test/visual/scrollable-tabs.html +++ b/test/visual/scrollable-tabs.html @@ -1,26 +1,16 @@ - - - Scrollable nav visual tests - - + + + Scrollable tabs visual tests - -
+
Tab-00 Tab-01 @@ -47,8 +37,8 @@ Tab-14 Tab-15 -
- +
+ Tab-00 Tab-01 Tab-02 @@ -68,4 +58,17 @@
+ diff --git a/test/visual/test.js b/test/visual/test.js index 8c5be7a..5479e43 100644 --- a/test/visual/test.js +++ b/test/visual/test.js @@ -1,64 +1,35 @@ -gemini.suite('vaadin-tabs', function(rootSuite) { - - function wait(actions, find) { - return actions - .waitForJSCondition(function(window) { - return !!(window.WebComponents && window.WebComponents.ready); - }, 15000); - } - - function goToAboutBlank(actions, find) { - // Firefox stops responding on socket after a test, workaround: - return actions.executeJS(function(window) { - window.location.href = 'about:blank'; // just go away, please! - }); - } - - rootSuite - .before(wait) - .after(goToAboutBlank); - ['lumo', 'material'].forEach(theme => { - gemini.suite(`horizontal-tabs-${theme}`, (suite) => { - suite - .setUrl(`horizontal-tabs.html?theme=${theme}`) - .setCaptureElements('#horizontal-tabs') - .capture('horizontal-tabs'); - }); - - gemini.suite(`vertical-tabs-${theme}`, (suite) => { - suite - .setUrl(`vertical-tabs.html?theme=${theme}`) - .setCaptureElements('#vertical-tabs') - .capture('vertical-tabs'); +describe('vaadin-tabs', () => { + const locator = '#tabs-tests[data-ready]'; + + ['lumo', 'material'].forEach((theme) => { + it(`${theme}-horizontal-tabs`, function () { + return this.browser + .url(`horizontal-tabs.html?theme=${theme}`) + .waitForVisible(locator, 15000) + .assertView(`${theme}-horizontal-tabs`, locator); }); - gemini.suite(`scrollable-tabs-${theme}`, (suite) => { - suite - .setUrl(`scrollable-tabs.html?theme=${theme}`) - .setCaptureElements('#scrollable-tabs') - .capture('scrollable-tabs'); + it(`${theme}-vertical-tabs`, function () { + return this.browser + .url(`vertical-tabs.html?theme=${theme}`) + .waitForVisible(locator, 15000) + .assertView(`${theme}-vertical-tabs`, locator); }); - gemini.suite(`scrollable-tabs-rtl-${theme}`, (suite) => { - suite - .setUrl(`scrollable-tabs.html?theme=${theme}&dir=rtl`) - .setCaptureElements('#scrollable-tabs') - .capture('scrollable-tabs'); - }); - - gemini.suite(`anchor-tabs-${theme}`, (suite) => { - suite - .setUrl(`anchor-tabs.html?theme=${theme}`) - .setCaptureElements('#anchor-tabs') - .capture('anchor-tabs'); - }); - - gemini.suite(`anchor-tabs-rtl-${theme}`, (suite) => { - suite - .setUrl(`anchor-tabs.html?theme=${theme}&dir=rtl`) - .setCaptureElements('#anchor-tabs') - .capture('anchor-tabs'); + ['ltr', 'rtl'].forEach((dir) => { + it(`${theme}-scrollable-tabs-${dir}`, function () { + return this.browser + .url(`scrollable-tabs.html?theme=${theme}&dir=${dir}`) + .waitForVisible(locator, 15000) + .assertView(`${theme}-scrollable-tabs-${dir}`, locator); + }); + + it(`${theme}-anchor-tabs-${dir}`, function () { + return this.browser + .url(`anchor-tabs.html?theme=${theme}&dir=${dir}`) + .waitForVisible(locator, 15000) + .assertView(`${theme}-anchor-tabs-${dir}`, locator); + }); }); }); - }); diff --git a/test/visual/vertical-tabs.html b/test/visual/vertical-tabs.html index d396068..afae1fb 100644 --- a/test/visual/vertical-tabs.html +++ b/test/visual/vertical-tabs.html @@ -1,27 +1,22 @@ - - - Vertical nav visual tests - - + + + Vertical tabs visual tests - -
- +
+ Foo Bar Baz @@ -31,4 +26,15 @@
+ diff --git a/theme/lumo/vaadin-tab-styles.html b/theme/lumo/vaadin-tab-styles.html deleted file mode 100644 index 3bf8d6f..0000000 --- a/theme/lumo/vaadin-tab-styles.html +++ /dev/null @@ -1,228 +0,0 @@ - - - - - - - - diff --git a/theme/lumo/vaadin-tab-styles.js b/theme/lumo/vaadin-tab-styles.js new file mode 100644 index 0000000..b4966bb --- /dev/null +++ b/theme/lumo/vaadin-tab-styles.js @@ -0,0 +1,229 @@ +import { registerStyles, css } from '@vaadin/vaadin-themable-mixin/register-styles.js'; +import '@vaadin/vaadin-lumo-styles/color.js'; +import '@vaadin/vaadin-lumo-styles/sizing.js'; +import '@vaadin/vaadin-lumo-styles/style.js'; +import '@vaadin/vaadin-lumo-styles/typography.js'; + +registerStyles( + 'vaadin-tab', + css` + :host { + box-sizing: border-box; + padding: 0.5rem 0.75rem; + font-family: var(--lumo-font-family); + font-size: var(--lumo-font-size-m); + line-height: var(--lumo-line-height-xs); + font-weight: 500; + opacity: 1; + color: var(--lumo-contrast-60pct); + transition: 0.15s color, 0.2s transform; + flex-shrink: 0; + display: flex; + align-items: center; + position: relative; + cursor: pointer; + transform-origin: 50% 100%; + outline: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + overflow: hidden; + min-width: var(--lumo-size-m); + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + :host(:not([orientation='vertical'])) { + text-align: center; + } + + :host([orientation='vertical']) { + transform-origin: 0% 50%; + padding: 0.25rem 1rem; + min-height: var(--lumo-size-m); + min-width: 0; + } + + :host(:hover), + :host([focus-ring]) { + color: var(--lumo-body-text-color); + } + + :host([selected]) { + color: var(--lumo-primary-text-color); + transition: 0.6s color; + } + + :host([active]:not([selected])) { + color: var(--lumo-primary-text-color); + transition-duration: 0.1s; + } + + :host::before, + :host::after { + content: ''; + position: absolute; + display: var(--_lumo-tab-marker-display, block); + bottom: 0; + left: 50%; + width: var(--lumo-size-s); + height: 2px; + background-color: var(--lumo-contrast-60pct); + border-radius: var(--lumo-border-radius) var(--lumo-border-radius) 0 0; + transform: translateX(-50%) scale(0); + transform-origin: 50% 100%; + transition: 0.14s transform cubic-bezier(0.12, 0.32, 0.54, 1); + will-change: transform; + } + + :host([selected])::before, + :host([selected])::after { + background-color: var(--lumo-primary-color); + } + + :host([orientation='vertical'])::before, + :host([orientation='vertical'])::after { + left: 0; + bottom: 50%; + transform: translateY(50%) scale(0); + width: 2px; + height: var(--lumo-size-xs); + border-radius: 0 var(--lumo-border-radius) var(--lumo-border-radius) 0; + transform-origin: 100% 50%; + } + + :host::after { + box-shadow: 0 0 0 4px var(--lumo-primary-color); + opacity: 0.15; + transition: 0.15s 0.02s transform, 0.8s 0.17s opacity; + } + + :host([selected])::before, + :host([selected])::after { + transform: translateX(-50%) scale(1); + transition-timing-function: cubic-bezier(0.12, 0.32, 0.54, 1.5); + } + + :host([orientation='vertical'][selected])::before, + :host([orientation='vertical'][selected])::after { + transform: translateY(50%) scale(1); + } + + :host([selected]:not([active]))::after { + opacity: 0; + } + + :host(:not([orientation='vertical'])) ::slotted(a[href]) { + justify-content: center; + } + + :host ::slotted(a) { + display: flex; + width: 100%; + align-items: center; + height: 100%; + margin: -0.5rem -0.75rem; + padding: 0.5rem 0.75rem; + outline: none; + + /* + Override the CSS inherited from \`lumo-color\` and \`lumo-typography\`. + Note: \`!important\` is needed because of the \`:slotted\` specificity. + */ + text-decoration: none !important; + color: inherit !important; + } + + :host ::slotted(iron-icon) { + margin: 0 4px; + width: var(--lumo-icon-size-m); + height: var(--lumo-icon-size-m); + } + + /* Vaadin icons are based on a 16x16 grid (unlike Lumo and Material icons with 24x24), so they look too big by default */ + :host ::slotted(iron-icon[icon^='vaadin:']) { + padding: 0.25rem; + box-sizing: border-box !important; + } + + :host(:not([dir='rtl'])) ::slotted(iron-icon:first-child) { + margin-left: 0; + } + + :host(:not([dir='rtl'])) ::slotted(iron-icon:last-child) { + margin-right: 0; + } + + :host([theme~='icon-on-top']) { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-around; + text-align: center; + padding-bottom: 0.5rem; + padding-top: 0.25rem; + } + + :host([theme~='icon-on-top']) ::slotted(a) { + flex-direction: column; + align-items: center; + margin-top: -0.25rem; + padding-top: 0.25rem; + } + + :host([theme~='icon-on-top']) ::slotted(iron-icon) { + margin: 0; + } + + /* Disabled */ + + :host([disabled]) { + pointer-events: none; + opacity: 1; + color: var(--lumo-disabled-text-color); + } + + /* Focus-ring */ + + :host([focus-ring]) { + box-shadow: inset 0 0 0 2px var(--lumo-primary-color-50pct); + border-radius: var(--lumo-border-radius); + } + + /* RTL specific styles */ + + :host([dir='rtl'])::before, + :host([dir='rtl'])::after { + left: auto; + right: 50%; + transform: translateX(50%) scale(0); + } + + :host([dir='rtl'][selected]:not([orientation='vertical']))::before, + :host([dir='rtl'][selected]:not([orientation='vertical']))::after { + transform: translateX(50%) scale(1); + } + + :host([dir='rtl']) ::slotted(iron-icon:first-child) { + margin-right: 0; + } + + :host([dir='rtl']) ::slotted(iron-icon:last-child) { + margin-left: 0; + } + + :host([orientation='vertical'][dir='rtl']) { + transform-origin: 100% 50%; + } + + :host([dir='rtl'][orientation='vertical'])::before, + :host([dir='rtl'][orientation='vertical'])::after { + left: auto; + right: 0; + border-radius: var(--lumo-border-radius) 0 0 var(--lumo-border-radius); + transform-origin: 0% 50%; + } + `, + { moduleId: 'lumo-tab' } +); diff --git a/theme/lumo/vaadin-tab.html b/theme/lumo/vaadin-tab.html deleted file mode 100644 index 3c95273..0000000 --- a/theme/lumo/vaadin-tab.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/theme/lumo/vaadin-tab.js b/theme/lumo/vaadin-tab.js new file mode 100644 index 0000000..39a0028 --- /dev/null +++ b/theme/lumo/vaadin-tab.js @@ -0,0 +1,2 @@ +import './vaadin-tab-styles.js'; +import '../../src/vaadin-tab.js'; diff --git a/theme/lumo/vaadin-tabs-styles.html b/theme/lumo/vaadin-tabs-styles.html deleted file mode 100644 index ef00642..0000000 --- a/theme/lumo/vaadin-tabs-styles.html +++ /dev/null @@ -1,238 +0,0 @@ - - - - - - - - - - diff --git a/theme/lumo/vaadin-tabs-styles.js b/theme/lumo/vaadin-tabs-styles.js new file mode 100644 index 0000000..08c8c17 --- /dev/null +++ b/theme/lumo/vaadin-tabs-styles.js @@ -0,0 +1,247 @@ +import { registerStyles, css } from '@vaadin/vaadin-themable-mixin/register-styles.js'; +import '@vaadin/vaadin-lumo-styles/font-icons.js'; +import '@vaadin/vaadin-lumo-styles/color.js'; +import '@vaadin/vaadin-lumo-styles/sizing.js'; +import '@vaadin/vaadin-lumo-styles/spacing.js'; +import '@vaadin/vaadin-lumo-styles/style.js'; +import '@vaadin/vaadin-lumo-styles/typography.js'; + +registerStyles( + 'vaadin-tabs', + css` + :host { + -webkit-tap-highlight-color: transparent; + } + + :host(:not([orientation='vertical'])) { + box-shadow: inset 0 -1px 0 0 var(--lumo-contrast-10pct); + position: relative; + min-height: var(--lumo-size-l); + } + + :host([orientation='horizontal']) [part='tabs'] ::slotted(vaadin-tab:not([theme~='icon-on-top'])) { + justify-content: center; + } + + :host([orientation='vertical']) { + box-shadow: -1px 0 0 0 var(--lumo-contrast-10pct); + } + + :host([orientation='horizontal']) [part='tabs'] { + margin: 0 0.75rem; + } + + :host([orientation='vertical']) [part='tabs'] { + width: 100%; + margin: 0.5rem 0; + } + + [part='forward-button'], + [part='back-button'] { + position: absolute; + z-index: 1; + font-family: lumo-icons; + color: var(--lumo-tertiary-text-color); + font-size: var(--lumo-icon-size-m); + display: flex; + align-items: center; + justify-content: center; + width: 1.5em; + height: 100%; + transition: 0.2s opacity; + top: 0; + } + + [part='forward-button']:hover, + [part='back-button']:hover { + color: inherit; + } + + :host(:not([dir='rtl'])) [part='forward-button'] { + right: 0; + } + + [part='forward-button']::after { + content: var(--lumo-icons-angle-right); + } + + [part='back-button']::after { + content: var(--lumo-icons-angle-left); + } + + /* Tabs overflow */ + + [part='tabs'] { + --_lumo-tabs-overflow-mask-image: none; + -webkit-mask-image: var(--_lumo-tabs-overflow-mask-image); + mask-image: var(--_lumo-tabs-overflow-mask-image); + } + + /* Horizontal tabs overflow */ + + /* Both ends overflowing */ + :host([overflow~='start'][overflow~='end']:not([orientation='vertical'])) [part='tabs'] { + --_lumo-tabs-overflow-mask-image: linear-gradient( + 90deg, + transparent 2em, + #000 4em, + #000 calc(100% - 4em), + transparent calc(100% - 2em) + ); + } + + /* End overflowing */ + :host([overflow~='end']:not([orientation='vertical'])) [part='tabs'] { + --_lumo-tabs-overflow-mask-image: linear-gradient(90deg, #000 calc(100% - 4em), transparent calc(100% - 2em)); + } + + /* Start overflowing */ + :host([overflow~='start']:not([orientation='vertical'])) [part='tabs'] { + --_lumo-tabs-overflow-mask-image: linear-gradient(90deg, transparent 2em, #000 4em); + } + + /* Vertical tabs overflow */ + + /* Both ends overflowing */ + :host([overflow~='start'][overflow~='end'][orientation='vertical']) [part='tabs'] { + --_lumo-tabs-overflow-mask-image: linear-gradient(transparent, #000 2em, #000 calc(100% - 2em), transparent); + } + + /* End overflowing */ + :host([overflow~='end'][orientation='vertical']) [part='tabs'] { + --_lumo-tabs-overflow-mask-image: linear-gradient(#000 calc(100% - 2em), transparent); + } + + /* Start overflowing */ + :host([overflow~='start'][orientation='vertical']) [part='tabs'] { + --_lumo-tabs-overflow-mask-image: linear-gradient(transparent, #000 2em); + } + + :host [part='tabs'] ::slotted(:not(vaadin-tab)) { + margin-left: var(--lumo-space-m); + } + + /* Centered */ + + :host([theme~='centered'][orientation='horizontal']) [part='tabs'] { + justify-content: center; + } + + /* Small */ + + :host([theme~='small']), + :host([theme~='small']) [part='tabs'] { + min-height: var(--lumo-size-m); + } + + :host([theme~='small']) [part='tabs'] ::slotted(vaadin-tab) { + font-size: var(--lumo-font-size-s); + } + + /* Minimal */ + + :host([theme~='minimal']) { + box-shadow: none; + --_lumo-tab-marker-display: none; + } + + /* Hide-scroll-buttons */ + + :host([theme~='hide-scroll-buttons']) [part='back-button'], + :host([theme~='hide-scroll-buttons']) [part='forward-button'] { + display: none; + } + + /* prettier-ignore */ + :host([theme~='hide-scroll-buttons'][overflow~='start'][overflow~='end']:not([orientation='vertical'])) [part='tabs'] { + --_lumo-tabs-overflow-mask-image: linear-gradient( + 90deg, + transparent, + #000 2em, + #000 calc(100% - 2em), + transparent 100% + ); + } + + :host([theme~='hide-scroll-buttons'][overflow~='end']:not([orientation='vertical'])) [part='tabs'] { + --_lumo-tabs-overflow-mask-image: linear-gradient(90deg, #000 calc(100% - 2em), transparent 100%); + } + + :host([theme~='hide-scroll-buttons'][overflow~='start']:not([orientation='vertical'])) [part='tabs'] { + --_lumo-tabs-overflow-mask-image: linear-gradient(90deg, transparent, #000 2em); + } + + /* Equal-width tabs */ + :host([theme~='equal-width-tabs']) { + flex: auto; + } + + :host([theme~='equal-width-tabs']) [part='tabs'] ::slotted(vaadin-tab) { + flex: 1 0 0%; + } + + /* RTL specific styles */ + + :host([dir='rtl']) [part='forward-button']::after { + content: var(--lumo-icons-angle-left); + } + + :host([dir='rtl']) [part='back-button']::after { + content: var(--lumo-icons-angle-right); + } + + :host([orientation='vertical'][dir='rtl']) { + box-shadow: 1px 0 0 0 var(--lumo-contrast-10pct); + } + + :host([dir='rtl']) [part='forward-button'] { + left: 0; + } + + :host([dir='rtl']) [part='tabs'] ::slotted(:not(vaadin-tab)) { + margin-left: 0; + margin-right: var(--lumo-space-m); + } + + /* Both ends overflowing */ + :host([dir='rtl'][overflow~='start'][overflow~='end']:not([orientation='vertical'])) [part='tabs'] { + --_lumo-tabs-overflow-mask-image: linear-gradient( + -90deg, + transparent 2em, + #000 4em, + #000 calc(100% - 4em), + transparent calc(100% - 2em) + ); + } + + /* End overflowing */ + :host([dir='rtl'][overflow~='end']:not([orientation='vertical'])) [part='tabs'] { + --_lumo-tabs-overflow-mask-image: linear-gradient(-90deg, #000 calc(100% - 4em), transparent calc(100% - 2em)); + } + + /* Start overflowing */ + :host([dir='rtl'][overflow~='start']:not([orientation='vertical'])) [part='tabs'] { + --_lumo-tabs-overflow-mask-image: linear-gradient(-90deg, transparent 2em, #000 4em); + } + + :host([dir='rtl'][theme~='hide-scroll-buttons'][overflow~='start'][overflow~='end']:not([orientation='vertical'])) + [part='tabs'] { + --_lumo-tabs-overflow-mask-image: linear-gradient( + -90deg, + transparent, + #000 2em, + #000 calc(100% - 2em), + transparent 100% + ); + } + + :host([dir='rtl'][theme~='hide-scroll-buttons'][overflow~='end']:not([orientation='vertical'])) [part='tabs'] { + --_lumo-tabs-overflow-mask-image: linear-gradient(-90deg, #000 calc(100% - 2em), transparent 100%); + } + + :host([dir='rtl'][theme~='hide-scroll-buttons'][overflow~='start']:not([orientation='vertical'])) [part='tabs'] { + --_lumo-tabs-overflow-mask-image: linear-gradient(-90deg, transparent, #000 2em); + } + `, + { moduleId: 'lumo-tabs' } +); diff --git a/theme/lumo/vaadin-tabs.html b/theme/lumo/vaadin-tabs.html deleted file mode 100644 index 734b16d..0000000 --- a/theme/lumo/vaadin-tabs.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/theme/lumo/vaadin-tabs.js b/theme/lumo/vaadin-tabs.js new file mode 100644 index 0000000..4914ad4 --- /dev/null +++ b/theme/lumo/vaadin-tabs.js @@ -0,0 +1,3 @@ +import './vaadin-tab.js'; +import './vaadin-tabs-styles.js'; +import '../../src/vaadin-tabs.js'; diff --git a/theme/material/vaadin-tab-styles.html b/theme/material/vaadin-tab-styles.html deleted file mode 100644 index 0e6179c..0000000 --- a/theme/material/vaadin-tab-styles.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - diff --git a/theme/material/vaadin-tab-styles.js b/theme/material/vaadin-tab-styles.js new file mode 100644 index 0000000..dd99cb5 --- /dev/null +++ b/theme/material/vaadin-tab-styles.js @@ -0,0 +1,142 @@ +import { registerStyles, css } from '@vaadin/vaadin-themable-mixin/register-styles.js'; +import '@vaadin/vaadin-material-styles/color.js'; +import '@vaadin/vaadin-material-styles/typography.js'; + +registerStyles( + 'vaadin-tab', + css` + :host { + display: flex; + flex-direction: column; + flex-shrink: 0; + flex-grow: 1; + align-items: center; + justify-content: center; + text-align: center; + min-width: 90px; + padding: 12px 16px; + box-sizing: border-box; + font-family: var(--material-font-family); + font-size: var(--material-button-font-size); + text-transform: uppercase; + letter-spacing: 0.05em; + white-space: nowrap; + min-height: 48px; + line-height: 1.2; + font-weight: 500; + color: var(--material-secondary-text-color); + overflow: hidden; + position: relative; + cursor: pointer; + outline: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + transition: box-shadow 0.3s; + -webkit-user-select: none; + user-select: none; + } + + /* do not prevent click on slotted links */ + :host::before, + :host::after { + pointer-events: none; + } + + :host::before { + content: ''; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: var(--material-primary-color); + opacity: 0; + transition: opacity 0.1s linear; + } + + :host(:hover)::before { + opacity: 0.04; + } + + :host([focus-ring])::before { + opacity: 0.1; + } + + :host([selected]) { + color: var(--material-primary-text-color); + box-shadow: inset 0 -2px 0 0 var(--material-primary-color); + } + + :host([orientation='vertical'][selected]) { + box-shadow: inset 2px 0 0 0 var(--material-primary-color); + } + + /* Ripple */ + + :host::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 100px; + height: 100px; + border-radius: 50%; + transform: translate(-50%, -50%) scale(0); + background-color: var(--material-primary-color); + opacity: 0; + transition: transform 0s cubic-bezier(0.05, 0.8, 0.5, 1), opacity 0s linear; + } + + :host([focused]:not([focus-ring]))::after, + :host([focused][active])::after, + :host([focus-ring][selected])::after { + transform: translate(-50%, -50%) scale(3); + opacity: 0; + transition-duration: 2s, 0.6s; + } + + :host([active]:not([selected]))::after { + opacity: 0.2; + transition-duration: 2s, 0s; + } + + /* Disabled */ + :host([disabled]) { + pointer-events: none; + opacity: 1; + color: var(--material-disabled-text-color); + } + + :host ::slotted(a) { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + margin: -12px -16px; + padding: 12px 16px; + text-decoration: none; + color: inherit; + outline: none; + } + + /* Touch device adjustments */ + @media (pointer: coarse) { + :host(:hover)::before { + display: none; + } + } + + /* Small space between icon and label */ + ::slotted(iron-icon:not(:only-child)) { + margin-bottom: 8px; + } + + /* RTL specific styles */ + + :host([dir='rtl'][orientation='vertical'][selected]) { + box-shadow: inset -2px 0 0 0 var(--material-primary-color); + } + `, + { moduleId: 'material-tab' } +); diff --git a/theme/material/vaadin-tab.html b/theme/material/vaadin-tab.html deleted file mode 100644 index 3c95273..0000000 --- a/theme/material/vaadin-tab.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/theme/material/vaadin-tab.js b/theme/material/vaadin-tab.js new file mode 100644 index 0000000..39a0028 --- /dev/null +++ b/theme/material/vaadin-tab.js @@ -0,0 +1,2 @@ +import './vaadin-tab-styles.js'; +import '../../src/vaadin-tab.js'; diff --git a/theme/material/vaadin-tabs-styles.html b/theme/material/vaadin-tabs-styles.html deleted file mode 100644 index a812750..0000000 --- a/theme/material/vaadin-tabs-styles.html +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - diff --git a/theme/material/vaadin-tabs-styles.js b/theme/material/vaadin-tabs-styles.js new file mode 100644 index 0000000..7bba5c8 --- /dev/null +++ b/theme/material/vaadin-tabs-styles.js @@ -0,0 +1,91 @@ +import { registerStyles, css } from '@vaadin/vaadin-themable-mixin/register-styles.js'; +import '@vaadin/vaadin-material-styles/font-icons.js'; +import '@vaadin/vaadin-material-styles/color.js'; + +registerStyles( + 'vaadin-tabs', + css` + :host { + -webkit-tap-highlight-color: transparent; + } + + :host { + display: flex; + flex-shrink: 0; + } + + /* Hide scroll buttons when no needed, and on touch devices */ + + :host(:not([overflow])) [part='forward-button'], + :host(:not([overflow])) [part='back-button'] { + display: none; + } + + @media (pointer: coarse) { + [part='back-button'], + [part='forward-button'] { + display: none !important; + } + } + + [part='forward-button'], + [part='back-button'] { + font-family: material-icons; + color: var(--material-secondary-text-color); + font-size: 24px; + display: flex; + flex-shrink: 0; + flex-grow: 0; + align-items: center; + justify-content: center; + width: 48px; + height: 100%; + transition: 0.2s opacity; + top: 0; + } + + [part='forward-button']:hover, + [part='back-button']:hover { + color: inherit; + } + + :host(:not([dir='rtl'])) [part='forward-button'] { + right: 0; + } + + [part='forward-button']::after { + content: var(--material-icons-chevron-right); + } + + [part='back-button']::after { + content: var(--material-icons-chevron-left); + } + + :host([overflow]) [part='tabs']::after { + content: ''; + display: flex; + flex-shrink: 0; + width: 32px; + } + + /* Fixed width tabs */ + :host([theme~='fixed']) [part='tabs'] ::slotted(vaadin-tab) { + flex-basis: 0.0001px; + } + + /* RTL specific styles */ + + :host([dir='rtl']) [part='forward-button'] { + left: 0; + } + + :host([dir='rtl']) [part='forward-button']::after { + content: var(--material-icons-chevron-left); + } + + :host([dir='rtl']) [part='back-button']::after { + content: var(--material-icons-chevron-right); + } + `, + { moduleId: 'material-tabs' } +); diff --git a/theme/material/vaadin-tabs.html b/theme/material/vaadin-tabs.html deleted file mode 100644 index 02af757..0000000 --- a/theme/material/vaadin-tabs.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/theme/material/vaadin-tabs.js b/theme/material/vaadin-tabs.js new file mode 100644 index 0000000..4914ad4 --- /dev/null +++ b/theme/material/vaadin-tabs.js @@ -0,0 +1,3 @@ +import './vaadin-tab.js'; +import './vaadin-tabs-styles.js'; +import '../../src/vaadin-tabs.js'; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..54289ba --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "lib": [ "esnext", "es2018", "dom" ], + "noEmit": true, + "strict": true + }, + "include": [ + "test/typings" + ] +} diff --git a/vaadin-tab.d.ts b/vaadin-tab.d.ts new file mode 100644 index 0000000..d432c49 --- /dev/null +++ b/vaadin-tab.d.ts @@ -0,0 +1 @@ +export * from './src/vaadin-tab.js'; diff --git a/vaadin-tab.html b/vaadin-tab.html deleted file mode 100644 index 87a67ea..0000000 --- a/vaadin-tab.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/vaadin-tab.js b/vaadin-tab.js new file mode 100644 index 0000000..444fc5e --- /dev/null +++ b/vaadin-tab.js @@ -0,0 +1,2 @@ +import './theme/lumo/vaadin-tab.js'; +export * from './src/vaadin-tab.js'; diff --git a/vaadin-tabs.d.ts b/vaadin-tabs.d.ts new file mode 100644 index 0000000..6c095ce --- /dev/null +++ b/vaadin-tabs.d.ts @@ -0,0 +1 @@ +export * from './src/vaadin-tabs.js'; diff --git a/vaadin-tabs.html b/vaadin-tabs.html deleted file mode 100644 index 732b42c..0000000 --- a/vaadin-tabs.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/vaadin-tabs.js b/vaadin-tabs.js new file mode 100644 index 0000000..e5b02f8 --- /dev/null +++ b/vaadin-tabs.js @@ -0,0 +1,2 @@ +import './theme/lumo/vaadin-tabs.js'; +export * from './src/vaadin-tabs.js'; diff --git a/wct.conf.js b/wct.conf.js deleted file mode 100644 index e614862..0000000 --- a/wct.conf.js +++ /dev/null @@ -1,74 +0,0 @@ -var envIndex = process.argv.indexOf('--env') + 1; -var env = envIndex ? process.argv[envIndex] : undefined; - -module.exports = { - testTimeout: 180 * 1000, - verbose: false, - plugins: { - local: { - browserOptions: { - chrome: [ - 'headless', - 'disable-gpu', - 'no-sandbox' - ] - } - }, - // MAGI REMOVE START - istanbul: { - dir: './coverage', - reporters: ['text-summary', 'lcov'], - include: [ - '**/vaadin-tabs/src/*.html' - ], - exclude: [], - thresholds: { - global: { - statements: 85 - } - } - } - // MAGI REMOVE END - }, - - registerHooks: function(context) { - const saucelabsPlatformsMobile = [ - 'iOS Simulator/iphone@12.2', - 'iOS Simulator/iphone@10.3' - ]; - - const saucelabsPlatformsMicrosoft = [ - 'Windows 10/microsoftedge@18', - 'Windows 10/internet explorer@11' - ]; - - const saucelabsPlatformsDesktop = [ - 'macOS 10.13/safari@latest' - ]; - - const saucelabsPlatforms = [ - ...saucelabsPlatformsMobile, - ...saucelabsPlatformsMicrosoft, - ...saucelabsPlatformsDesktop - ]; - - const cronPlatforms = [ - { - deviceName: 'Android GoogleAPI Emulator', - platformName: 'Android', - platformVersion: '8.1', - browserName: 'chrome' - }, - 'iOS Simulator/ipad@12.2', - 'iOS Simulator/iphone@10.3', - 'Windows 10/chrome@latest', - 'Windows 10/firefox@latest' - ]; - - if (env === 'saucelabs') { - context.options.plugins.sauce.browsers = saucelabsPlatforms; - } else if (env === 'saucelabs-cron') { - context.options.plugins.sauce.browsers = cronPlatforms; - } - } -}; diff --git a/web-test-runner.config.js b/web-test-runner.config.js new file mode 100644 index 0000000..f3b3c59 --- /dev/null +++ b/web-test-runner.config.js @@ -0,0 +1,48 @@ +/* eslint-env node */ +const { createSauceLabsLauncher } = require('@web/test-runner-saucelabs'); + +const config = { + nodeResolve: true, + testsFinishTimeout: 60000, + coverageConfig: { + include: ['**/src/*'], + threshold: { + statements: 100, + branches: 88, + functions: 100, + lines: 100 + } + } +}; + +if (process.env.TEST_ENV === 'sauce') { + const sauceLabsLauncher = createSauceLabsLauncher({ + user: process.env.SAUCE_USERNAME, + key: process.env.SAUCE_ACCESS_KEY + }); + + const sharedCapabilities = { + 'sauce:options': { + name: 'vaadin-tabs unit tests', + build: `${process.env.GITHUB_REF || 'local'} build ${process.env.GITHUB_RUN_NUMBER || ''}` + } + }; + + config.concurrency = 2; + config.browsers = [ + sauceLabsLauncher({ + ...sharedCapabilities, + browserName: 'firefox', + platform: 'Windows 10', + browserVersion: 'latest' + }), + sauceLabsLauncher({ + ...sharedCapabilities, + browserName: 'safari', + platform: 'macOS 10.15', + browserVersion: '13.1' + }) + ]; +} + +module.exports = config;