@vaadin/tabs 25.2.0-alpha12 → 25.2.0-alpha13

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/tabs",
3
- "version": "25.2.0-alpha12",
3
+ "version": "25.2.0-alpha13",
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-alpha12",
39
- "@vaadin/component-base": "25.2.0-alpha12",
40
- "@vaadin/item": "25.2.0-alpha12",
41
- "@vaadin/vaadin-themable-mixin": "25.2.0-alpha12",
38
+ "@vaadin/a11y-base": "25.2.0-alpha13",
39
+ "@vaadin/component-base": "25.2.0-alpha13",
40
+ "@vaadin/item": "25.2.0-alpha13",
41
+ "@vaadin/vaadin-themable-mixin": "25.2.0-alpha13",
42
42
  "lit": "^3.0.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@vaadin/aura": "25.2.0-alpha12",
46
- "@vaadin/chai-plugins": "25.2.0-alpha12",
47
- "@vaadin/test-runner-commands": "25.2.0-alpha12",
45
+ "@vaadin/aura": "25.2.0-alpha13",
46
+ "@vaadin/chai-plugins": "25.2.0-alpha13",
47
+ "@vaadin/test-runner-commands": "25.2.0-alpha13",
48
48
  "@vaadin/testing-helpers": "^2.0.0",
49
- "@vaadin/vaadin-lumo-styles": "25.2.0-alpha12",
49
+ "@vaadin/vaadin-lumo-styles": "25.2.0-alpha13",
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": "ae1e4373aec3653d63a45b6be18eee36f4b245a1"
57
+ "gitHead": "a1052aee053529ffcef1a1e9be2c855ed3e98cb2"
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
 
@@ -70,6 +70,7 @@ export const TabsMixin = (superClass) =>
70
70
  ready() {
71
71
  super.ready();
72
72
 
73
+ this._updateOverflow();
73
74
  this._scrollerElement.addEventListener('scroll', () => this._updateOverflow());
74
75
 
75
76
  this.setAttribute('role', 'tablist');
@@ -96,92 +97,77 @@ export const TabsMixin = (superClass) =>
96
97
  this._updateOverflow();
97
98
  }
98
99
 
99
- /** @protected */
100
- _scrollForward() {
101
- // Calculations here are performed in order to optimize the loop that checks item visibility.
102
- const forwardButtonVisibleWidth = this._getNavigationButtonVisibleWidth('forward-button');
103
- const backButtonVisibleWidth = this._getNavigationButtonVisibleWidth('back-button');
100
+ _scrollToItem(idx) {
101
+ const item = this.items[idx];
102
+ const itemRect = item.getBoundingClientRect();
104
103
  const scrollerRect = this._scrollerElement.getBoundingClientRect();
105
- const itemToScrollTo = [...this.items]
106
- .reverse()
107
- .find((item) => this._isItemVisible(item, forwardButtonVisibleWidth, backButtonVisibleWidth, scrollerRect));
108
- const itemRect = itemToScrollTo.getBoundingClientRect();
104
+
109
105
  // This hard-coded number accounts for the width of the mask that covers a part of the visible items.
110
106
  // A CSS variable can be introduced to get rid of this value.
111
- const overflowIndicatorCompensation = 20;
112
- const totalCompensation =
113
- overflowIndicatorCompensation + this.shadowRoot.querySelector('[part="back-button"]').clientWidth;
114
- let scrollOffset;
115
- if (this.__isRTL) {
116
- const scrollerRightEdge = scrollerRect.right - totalCompensation;
117
- 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
+ }
118
117
  } else {
119
- const scrollerLeftEdge = scrollerRect.left + totalCompensation;
120
- scrollOffset = itemRect.left - scrollerLeftEdge;
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
+ }
121
131
  }
122
- // It is possible that a scroll offset is calculated to be between 0 and 1. In this case, this offset
123
- // can be rounded down to zero, rendering the button useless. It is also possible that the offset is
124
- // calculated such that it results in scrolling backwards for a wide tab or edge cases. This is a
125
- // workaround for such cases.
126
- if (-this.__direction * scrollOffset < 1) {
127
- scrollOffset = -this.__direction * (this._scrollOffset - totalCompensation);
128
- }
129
- this._scroll(scrollOffset);
130
132
  }
131
133
 
132
134
  /** @protected */
133
- _scrollBack() {
134
- // Calculations here are performed in order to optimize the loop that checks item visibility.
135
- const forwardButtonVisibleWidth = this._getNavigationButtonVisibleWidth('forward-button');
136
- const backButtonVisibleWidth = this._getNavigationButtonVisibleWidth('back-button');
137
- const scrollerRect = this._scrollerElement.getBoundingClientRect();
138
- const itemToScrollTo = this.items.find((item) =>
139
- this._isItemVisible(item, forwardButtonVisibleWidth, backButtonVisibleWidth, scrollerRect),
140
- );
141
- const itemRect = itemToScrollTo.getBoundingClientRect();
142
- // This hard-coded number accounts for the width of the mask that covers a part of the visible items.
143
- // A CSS variable can be introduced to get rid of this value.
144
- const overflowIndicatorCompensation = 20;
145
- const totalCompensation =
146
- overflowIndicatorCompensation + this.shadowRoot.querySelector('[part="forward-button"]').clientWidth;
147
- let scrollOffset;
148
- if (this.__isRTL) {
149
- const scrollerLeftEdge = scrollerRect.left + totalCompensation;
150
- scrollOffset = itemRect.left - scrollerLeftEdge;
151
- } else {
152
- const scrollerRightEdge = scrollerRect.right - totalCompensation;
153
- 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);
154
140
  }
155
- // It is possible that a scroll offset is calculated to be between 0 and 1. In this case, this offset
156
- // can be rounded down to zero, rendering the button useless. It is also possible that the offset is
157
- // calculated such that it results in scrolling forward for a wide tab or edge cases. This is a
158
- // workaround for such cases.
159
- if (this.__direction * scrollOffset < 1) {
160
- 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));
161
149
  }
162
- this._scroll(scrollOffset);
163
150
  }
164
151
 
165
- /** @private */
166
- _isItemVisible(item, forwardButtonVisibleWidth, backButtonVisibleWidth, scrollerRect) {
167
- if (this._vertical) {
168
- 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);
169
157
  }
170
- const buttonOnTheRightWidth = this.__isRTL ? backButtonVisibleWidth : forwardButtonVisibleWidth;
171
- const buttonOnTheLeftWidth = this.__isRTL ? forwardButtonVisibleWidth : backButtonVisibleWidth;
172
- const scrollerRightEdge = scrollerRect.right - buttonOnTheRightWidth;
173
- const scrollerLeftEdge = scrollerRect.left + buttonOnTheLeftWidth;
174
- const itemRect = item.getBoundingClientRect();
175
- return scrollerRightEdge > Math.floor(itemRect.left) && scrollerLeftEdge < Math.ceil(itemRect.right);
176
158
  }
177
159
 
178
- /** @private */
179
- _getNavigationButtonVisibleWidth(buttonPartName) {
180
- const navigationButton = this.shadowRoot.querySelector(`[part="${buttonPartName}"]`);
181
- if (window.getComputedStyle(navigationButton).opacity === '0') {
182
- return 0;
160
+ /** @protected */
161
+ _startScrollBack(e) {
162
+ if (e.button === 0) {
163
+ this._scrollBack();
164
+ this.__scrollTimer = setInterval(this._scrollBack.bind(this), 300);
183
165
  }
184
- return navigationButton.clientWidth;
166
+ }
167
+
168
+ /** @protected */
169
+ _stopScroll() {
170
+ clearTimeout(this.__scrollTimer);
185
171
  }
186
172
 
187
173
  /** @private */
@@ -75,13 +75,29 @@ class Tabs extends TabsMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInjecti
75
75
  /** @protected */
76
76
  render() {
77
77
  return html`
78
- <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>
79
87
 
80
- <div id="scroll" part="tabs">
88
+ <div id="scroll" part="tabs" tabindex="-1">
81
89
  <slot></slot>
82
90
  </div>
83
91
 
84
- <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>
85
101
  `;
86
102
  }
87
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-alpha12",
4
+ "version": "25.2.0-alpha13",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
@@ -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-alpha12",
4
+ "version": "25.2.0-alpha13",
5
5
  "description-markup": "markdown",
6
6
  "framework": "lit",
7
7
  "framework-config": {