@vaadin/tabs 25.2.0-alpha8 → 25.2.0-beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -41,6 +41,20 @@
41
41
  "description": "`<vaadin-tab>` is a Web Component providing an accessible and customizable tab.\n\n```html\n<vaadin-tab>Tab 1</vaadin-tab>\n```\n\nThe following state attributes are available for styling:\n\nAttribute | Description\n---------------|---------------------------------\n`disabled` | Set when the element is disabled\n`focused` | Set when the element is focused\n`focus-ring` | Set when the element is keyboard focused\n`selected` | Set when the tab is selected\n`active` | Set when mousedown or enter/spacebar pressed\n`orientation` | Set to `horizontal` or `vertical` depending on the direction of items\n`has-tooltip` | Set when the tab has a slotted tooltip\n\nThe following custom CSS properties are available for styling:\n\nCustom CSS property |\n:------------------------------|\n| `--vaadin-tab-background` |\n| `--vaadin-tab-border-color` |\n| `--vaadin-tab-border-width` |\n| `--vaadin-tab-font-size` |\n| `--vaadin-tab-font-weight` |\n| `--vaadin-tab-gap` |\n| `--vaadin-tab-padding` |\n| `--vaadin-tab-text-color` |\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.",
42
42
  "name": "Tab",
43
43
  "members": [
44
+ {
45
+ "kind": "field",
46
+ "name": "disabled",
47
+ "privacy": "public",
48
+ "type": {
49
+ "text": "boolean"
50
+ },
51
+ "description": "If true, the user cannot interact with this element.",
52
+ "attribute": "disabled",
53
+ "inheritedFrom": {
54
+ "name": "DisabledMixin",
55
+ "package": "@vaadin/a11y-base/src/disabled-mixin.js"
56
+ }
57
+ },
44
58
  {
45
59
  "kind": "field",
46
60
  "name": "selected",
@@ -97,6 +111,18 @@
97
111
  "tagName": "vaadin-tab",
98
112
  "customElement": true,
99
113
  "attributes": [
114
+ {
115
+ "name": "disabled",
116
+ "type": {
117
+ "text": "boolean"
118
+ },
119
+ "description": "If true, the user cannot interact with this element.",
120
+ "fieldName": "disabled",
121
+ "inheritedFrom": {
122
+ "name": "DisabledMixin",
123
+ "package": "@vaadin/a11y-base/src/disabled-mixin.js"
124
+ }
125
+ },
100
126
  {
101
127
  "name": "selected",
102
128
  "type": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/tabs",
3
- "version": "25.2.0-alpha8",
3
+ "version": "25.2.0-beta1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -35,18 +35,18 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "@open-wc/dedupe-mixin": "^1.3.0",
38
- "@vaadin/a11y-base": "25.2.0-alpha8",
39
- "@vaadin/component-base": "25.2.0-alpha8",
40
- "@vaadin/item": "25.2.0-alpha8",
41
- "@vaadin/vaadin-themable-mixin": "25.2.0-alpha8",
38
+ "@vaadin/a11y-base": "25.2.0-beta1",
39
+ "@vaadin/component-base": "25.2.0-beta1",
40
+ "@vaadin/item": "25.2.0-beta1",
41
+ "@vaadin/vaadin-themable-mixin": "25.2.0-beta1",
42
42
  "lit": "^3.0.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@vaadin/aura": "25.2.0-alpha8",
46
- "@vaadin/chai-plugins": "25.2.0-alpha8",
47
- "@vaadin/test-runner-commands": "25.2.0-alpha8",
45
+ "@vaadin/aura": "25.2.0-beta1",
46
+ "@vaadin/chai-plugins": "25.2.0-beta1",
47
+ "@vaadin/test-runner-commands": "25.2.0-beta1",
48
48
  "@vaadin/testing-helpers": "^2.0.0",
49
- "@vaadin/vaadin-lumo-styles": "25.2.0-alpha8",
49
+ "@vaadin/vaadin-lumo-styles": "25.2.0-beta1",
50
50
  "sinon": "^21.0.2"
51
51
  },
52
52
  "customElements": "custom-elements.json",
@@ -54,5 +54,5 @@
54
54
  "web-types.json",
55
55
  "web-types.lit.json"
56
56
  ],
57
- "gitHead": "2b82e20cdfc605b1187e9a24ae42869e1500ab68"
57
+ "gitHead": "471a23f60d1eb725f98a33f62cb9664d9c0a4163"
58
58
  }
@@ -35,6 +35,13 @@ export const tabsStyles = css`
35
35
  display: flex;
36
36
  flex-direction: column;
37
37
  gap: var(--vaadin-tabs-gap, var(--vaadin-gap-s));
38
+ animation: enable-smooth-scroll-after-first-render 1s both;
39
+ }
40
+
41
+ @keyframes enable-smooth-scroll-after-first-render {
42
+ 100% {
43
+ scroll-behavior: smooth;
44
+ }
38
45
  }
39
46
 
40
47
  :host([orientation='horizontal']) [part='tabs'] {
@@ -61,6 +68,8 @@ export const tabsStyles = css`
61
68
  align-items: center;
62
69
  justify-content: center;
63
70
  -webkit-tap-highlight-color: transparent;
71
+ -webkit-user-select: none;
72
+ user-select: none;
64
73
  touch-action: manipulation;
65
74
  }
66
75
 
package/src/vaadin-tab.js CHANGED
@@ -49,9 +49,6 @@ import { tabStyles } from './styles/vaadin-tab-base-styles.js';
49
49
  *
50
50
  * @customElement vaadin-tab
51
51
  * @extends HTMLElement
52
- * @mixes ElementMixin
53
- * @mixes ItemMixin
54
- * @mixes ThemableMixin
55
52
  */
56
53
  class Tab extends ItemMixin(ThemableMixin(ElementMixin(PolylitMixin(LumoInjectionMixin(LitElement))))) {
57
54
  static get is() {
@@ -7,11 +7,6 @@ import { ListMixin } from '@vaadin/a11y-base/src/list-mixin.js';
7
7
  import { getNormalizedScrollLeft } from '@vaadin/component-base/src/dir-utils.js';
8
8
  import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
9
9
 
10
- /**
11
- * @polymerMixin
12
- * @mixes ListMixin
13
- * @mixes ResizeMixin
14
- */
15
10
  export const TabsMixin = (superClass) =>
16
11
  class TabsMixinClass extends ResizeMixin(ListMixin(superClass)) {
17
12
  static get properties() {
@@ -75,6 +70,7 @@ export const TabsMixin = (superClass) =>
75
70
  ready() {
76
71
  super.ready();
77
72
 
73
+ this._updateOverflow();
78
74
  this._scrollerElement.addEventListener('scroll', () => this._updateOverflow());
79
75
 
80
76
  this.setAttribute('role', 'tablist');
@@ -101,92 +97,77 @@ export const TabsMixin = (superClass) =>
101
97
  this._updateOverflow();
102
98
  }
103
99
 
104
- /** @protected */
105
- _scrollForward() {
106
- // Calculations here are performed in order to optimize the loop that checks item visibility.
107
- const forwardButtonVisibleWidth = this._getNavigationButtonVisibleWidth('forward-button');
108
- const backButtonVisibleWidth = this._getNavigationButtonVisibleWidth('back-button');
100
+ _scrollToItem(idx) {
101
+ const item = this.items[idx];
102
+ const itemRect = item.getBoundingClientRect();
109
103
  const scrollerRect = this._scrollerElement.getBoundingClientRect();
110
- const itemToScrollTo = [...this.items]
111
- .reverse()
112
- .find((item) => this._isItemVisible(item, forwardButtonVisibleWidth, backButtonVisibleWidth, scrollerRect));
113
- const itemRect = itemToScrollTo.getBoundingClientRect();
104
+
114
105
  // This hard-coded number accounts for the width of the mask that covers a part of the visible items.
115
106
  // A CSS variable can be introduced to get rid of this value.
116
- const overflowIndicatorCompensation = 20;
117
- const totalCompensation =
118
- overflowIndicatorCompensation + this.shadowRoot.querySelector('[part="back-button"]').clientWidth;
119
- let scrollOffset;
120
- if (this.__isRTL) {
121
- const scrollerRightEdge = scrollerRect.right - totalCompensation;
122
- scrollOffset = itemRect.right - scrollerRightEdge;
107
+ const overflowIndicatorCompensation = this._vertical ? 10 : 20;
108
+
109
+ if (this._vertical) {
110
+ if (itemRect.bottom > scrollerRect.bottom - overflowIndicatorCompensation) {
111
+ this._scrollerElement.scrollTop =
112
+ item.offsetTop - (scrollerRect.height - itemRect.height) + overflowIndicatorCompensation;
113
+ }
114
+ if (itemRect.top < scrollerRect.top + overflowIndicatorCompensation) {
115
+ this._scrollerElement.scrollTop = item.offsetTop - overflowIndicatorCompensation;
116
+ }
123
117
  } else {
124
- const scrollerLeftEdge = scrollerRect.left + totalCompensation;
125
- scrollOffset = itemRect.left - scrollerLeftEdge;
126
- }
127
- // It is possible that a scroll offset is calculated to be between 0 and 1. In this case, this offset
128
- // can be rounded down to zero, rendering the button useless. It is also possible that the offset is
129
- // calculated such that it results in scrolling backwards for a wide tab or edge cases. This is a
130
- // workaround for such cases.
131
- if (-this.__direction * scrollOffset < 1) {
132
- scrollOffset = -this.__direction * (this._scrollOffset - totalCompensation);
118
+ const backButtonWidth = this.shadowRoot.querySelector(`[part="back-button"]`).offsetWidth;
119
+ const forwardButtonWidth = this.shadowRoot.querySelector(`[part="forward-button"]`).offsetWidth;
120
+
121
+ if (itemRect.right > scrollerRect.right - forwardButtonWidth - overflowIndicatorCompensation) {
122
+ this._scrollerElement.scrollLeft =
123
+ item.offsetLeft -
124
+ (scrollerRect.width - itemRect.width) +
125
+ forwardButtonWidth +
126
+ overflowIndicatorCompensation;
127
+ }
128
+ if (itemRect.left < scrollerRect.left + backButtonWidth + overflowIndicatorCompensation) {
129
+ this._scrollerElement.scrollLeft = item.offsetLeft - backButtonWidth - overflowIndicatorCompensation;
130
+ }
133
131
  }
134
- this._scroll(scrollOffset);
135
132
  }
136
133
 
137
134
  /** @protected */
138
- _scrollBack() {
139
- // Calculations here are performed in order to optimize the loop that checks item visibility.
140
- const forwardButtonVisibleWidth = this._getNavigationButtonVisibleWidth('forward-button');
141
- const backButtonVisibleWidth = this._getNavigationButtonVisibleWidth('back-button');
142
- const scrollerRect = this._scrollerElement.getBoundingClientRect();
143
- const itemToScrollTo = this.items.find((item) =>
144
- this._isItemVisible(item, forwardButtonVisibleWidth, backButtonVisibleWidth, scrollerRect),
145
- );
146
- const itemRect = itemToScrollTo.getBoundingClientRect();
147
- // This hard-coded number accounts for the width of the mask that covers a part of the visible items.
148
- // A CSS variable can be introduced to get rid of this value.
149
- const overflowIndicatorCompensation = 20;
150
- const totalCompensation =
151
- overflowIndicatorCompensation + this.shadowRoot.querySelector('[part="forward-button"]').clientWidth;
152
- let scrollOffset;
153
- if (this.__isRTL) {
154
- const scrollerLeftEdge = scrollerRect.left + totalCompensation;
155
- scrollOffset = itemRect.left - scrollerLeftEdge;
156
- } else {
157
- const scrollerRightEdge = scrollerRect.right - totalCompensation;
158
- scrollOffset = itemRect.right - scrollerRightEdge;
135
+ _scrollForward(e) {
136
+ // Allow setInterval loop to trigger scroll (e is undefine in that case)
137
+ // Allow programmatic click events to trigger scroll (__scrollTimer is undefine in that case)
138
+ if (e === undefined || this.__scrollTimer === undefined) {
139
+ this._scroll(this.__direction * (this._scrollOffset / 2) * -1);
159
140
  }
160
- // It is possible that a scroll offset is calculated to be between 0 and 1. In this case, this offset
161
- // can be rounded down to zero, rendering the button useless. It is also possible that the offset is
162
- // calculated such that it results in scrolling forward for a wide tab or edge cases. This is a
163
- // workaround for such cases.
164
- if (this.__direction * scrollOffset < 1) {
165
- scrollOffset = this.__direction * (this._scrollOffset - totalCompensation);
141
+ }
142
+
143
+ /** @protected */
144
+ _scrollBack(e) {
145
+ // Allow setInterval loop to trigger scroll (e is undefine in that case)
146
+ // Allow programmatic click events to trigger scroll (__scrollTimer is undefine in that case)
147
+ if (e === undefined || this.__scrollTimer === undefined) {
148
+ this._scroll(this.__direction * (this._scrollOffset / 2));
166
149
  }
167
- this._scroll(scrollOffset);
168
150
  }
169
151
 
170
- /** @private */
171
- _isItemVisible(item, forwardButtonVisibleWidth, backButtonVisibleWidth, scrollerRect) {
172
- if (this._vertical) {
173
- throw new Error('Visibility check is only supported for horizontal tabs.');
152
+ /** @protected */
153
+ _startScrollForward(e) {
154
+ if (e.button === 0) {
155
+ this._scrollForward();
156
+ this.__scrollTimer = setInterval(this._scrollForward.bind(this), 300);
174
157
  }
175
- const buttonOnTheRightWidth = this.__isRTL ? backButtonVisibleWidth : forwardButtonVisibleWidth;
176
- const buttonOnTheLeftWidth = this.__isRTL ? forwardButtonVisibleWidth : backButtonVisibleWidth;
177
- const scrollerRightEdge = scrollerRect.right - buttonOnTheRightWidth;
178
- const scrollerLeftEdge = scrollerRect.left + buttonOnTheLeftWidth;
179
- const itemRect = item.getBoundingClientRect();
180
- return scrollerRightEdge > Math.floor(itemRect.left) && scrollerLeftEdge < Math.ceil(itemRect.right);
181
158
  }
182
159
 
183
- /** @private */
184
- _getNavigationButtonVisibleWidth(buttonPartName) {
185
- const navigationButton = this.shadowRoot.querySelector(`[part="${buttonPartName}"]`);
186
- if (window.getComputedStyle(navigationButton).opacity === '0') {
187
- return 0;
160
+ /** @protected */
161
+ _startScrollBack(e) {
162
+ if (e.button === 0) {
163
+ this._scrollBack();
164
+ this.__scrollTimer = setInterval(this._scrollBack.bind(this), 300);
188
165
  }
189
- return navigationButton.clientWidth;
166
+ }
167
+
168
+ /** @protected */
169
+ _stopScroll() {
170
+ clearTimeout(this.__scrollTimer);
190
171
  }
191
172
 
192
173
  /** @private */
@@ -62,9 +62,6 @@ import { TabsMixin } from './vaadin-tabs-mixin.js';
62
62
  *
63
63
  * @customElement vaadin-tabs
64
64
  * @extends HTMLElement
65
- * @mixes ElementMixin
66
- * @mixes TabsMixin
67
- * @mixes ThemableMixin
68
65
  */
69
66
  class Tabs extends TabsMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(LitElement))))) {
70
67
  static get is() {
@@ -78,13 +75,29 @@ class Tabs extends TabsMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInjecti
78
75
  /** @protected */
79
76
  render() {
80
77
  return html`
81
- <div @click="${this._scrollBack}" part="back-button" aria-hidden="true"></div>
78
+ <div
79
+ @pointerdown="${this._startScrollBack}"
80
+ @pointerup="${this._stopScroll}"
81
+ @pointerleave="${this._stopScroll}"
82
+ @pointercancel="${this._stopScroll}"
83
+ @click="${this._scrollBack}"
84
+ part="back-button"
85
+ aria-hidden="true"
86
+ ></div>
82
87
 
83
- <div id="scroll" part="tabs">
88
+ <div id="scroll" part="tabs" tabindex="-1">
84
89
  <slot></slot>
85
90
  </div>
86
91
 
87
- <div @click="${this._scrollForward}" part="forward-button" aria-hidden="true"></div>
92
+ <div
93
+ @pointerdown="${this._startScrollForward}"
94
+ @pointerup="${this._stopScroll}"
95
+ @pointerleave="${this._stopScroll}"
96
+ @pointercancel="${this._stopScroll}"
97
+ @click="${this._scrollForward}"
98
+ part="forward-button"
99
+ aria-hidden="true"
100
+ ></div>
88
101
  `;
89
102
  }
90
103
  }
package/web-types.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/web-types",
3
3
  "name": "@vaadin/tabs",
4
- "version": "25.2.0-alpha8",
4
+ "version": "25.2.0-beta1",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
@@ -15,9 +15,7 @@
15
15
  "description": "If true, the user cannot interact with this element.",
16
16
  "value": {
17
17
  "type": [
18
- "boolean",
19
- "null",
20
- "undefined"
18
+ "boolean"
21
19
  ]
22
20
  }
23
21
  },
@@ -26,9 +24,7 @@
26
24
  "description": "If true, the item is in selected state.",
27
25
  "value": {
28
26
  "type": [
29
- "boolean",
30
- "null",
31
- "undefined"
27
+ "boolean"
32
28
  ]
33
29
  }
34
30
  },
@@ -51,9 +47,7 @@
51
47
  "description": "If true, the user cannot interact with this element.",
52
48
  "value": {
53
49
  "type": [
54
- "boolean",
55
- "null",
56
- "undefined"
50
+ "boolean"
57
51
  ]
58
52
  }
59
53
  },
@@ -62,9 +56,7 @@
62
56
  "description": "If true, the item is in selected state.",
63
57
  "value": {
64
58
  "type": [
65
- "boolean",
66
- "null",
67
- "undefined"
59
+ "boolean"
68
60
  ]
69
61
  }
70
62
  },
@@ -73,7 +65,7 @@
73
65
  "description": "Submittable string value. The default value is the trimmed text content of the element.",
74
66
  "value": {
75
67
  "type": [
76
- "?"
68
+ "string"
77
69
  ]
78
70
  }
79
71
  }
@@ -90,9 +82,7 @@
90
82
  "description": "If true, the user cannot interact with this element.\nWhen the element is disabled, the selected item is\nnot updated when `selected` property is changed.",
91
83
  "value": {
92
84
  "type": [
93
- "boolean",
94
- "null",
95
- "undefined"
85
+ "boolean"
96
86
  ]
97
87
  }
98
88
  },
@@ -101,9 +91,7 @@
101
91
  "description": "Set tabs disposition. Possible values are `horizontal|vertical`",
102
92
  "value": {
103
93
  "type": [
104
- "string",
105
- "null",
106
- "undefined"
94
+ "string"
107
95
  ]
108
96
  }
109
97
  },
@@ -112,9 +100,7 @@
112
100
  "description": "The index of the selected tab.",
113
101
  "value": {
114
102
  "type": [
115
- "number",
116
- "null",
117
- "undefined"
103
+ "number"
118
104
  ]
119
105
  }
120
106
  },
@@ -137,9 +123,7 @@
137
123
  "description": "If true, the user cannot interact with this element.\nWhen the element is disabled, the selected item is\nnot updated when `selected` property is changed.",
138
124
  "value": {
139
125
  "type": [
140
- "boolean",
141
- "null",
142
- "undefined"
126
+ "boolean"
143
127
  ]
144
128
  }
145
129
  },
@@ -148,9 +132,7 @@
148
132
  "description": "Set tabs disposition. Possible values are `horizontal|vertical`",
149
133
  "value": {
150
134
  "type": [
151
- "string",
152
- "null",
153
- "undefined"
135
+ "string"
154
136
  ]
155
137
  }
156
138
  },
@@ -159,9 +141,7 @@
159
141
  "description": "The index of the selected tab.",
160
142
  "value": {
161
143
  "type": [
162
- "number",
163
- "null",
164
- "undefined"
144
+ "number"
165
145
  ]
166
146
  }
167
147
  }
@@ -173,7 +153,7 @@
173
153
  },
174
154
  {
175
155
  "name": "selected-changed",
176
- "description": "Fired when the selection is changed.\nNot fired when used in `multiple` selection mode."
156
+ "description": "Fired when the `selected` property changes."
177
157
  }
178
158
  ]
179
159
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/web-types",
3
3
  "name": "@vaadin/tabs",
4
- "version": "25.2.0-alpha8",
4
+ "version": "25.2.0-beta1",
5
5
  "description-markup": "markdown",
6
6
  "framework": "lit",
7
7
  "framework-config": {
@@ -77,7 +77,7 @@
77
77
  },
78
78
  {
79
79
  "name": "@selected-changed",
80
- "description": "Fired when the selection is changed.\nNot fired when used in `multiple` selection mode.",
80
+ "description": "Fired when the `selected` property changes.",
81
81
  "value": {
82
82
  "kind": "expression"
83
83
  }