@vonage/vivid 4.23.0 → 4.24.0

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.
Files changed (49) hide show
  1. package/custom-elements.json +574 -642
  2. package/lib/file-picker/file-picker.d.ts +2 -2
  3. package/lib/file-picker/locale.d.ts +1 -0
  4. package/lib/rich-text-editor/locale.d.ts +9 -0
  5. package/lib/rich-text-editor/menubar/menubar.d.ts +334 -1
  6. package/lib/searchable-select/locale.d.ts +1 -0
  7. package/lib/searchable-select/searchable-select.d.ts +1 -0
  8. package/lib/tabs/tabs.d.ts +5 -21
  9. package/locales/de-DE.cjs +15 -1
  10. package/locales/de-DE.js +15 -1
  11. package/locales/en-GB.cjs +15 -1
  12. package/locales/en-GB.js +15 -1
  13. package/locales/en-US.cjs +15 -1
  14. package/locales/en-US.js +15 -1
  15. package/locales/ja-JP.cjs +15 -1
  16. package/locales/ja-JP.js +15 -1
  17. package/locales/zh-CN.cjs +15 -1
  18. package/locales/zh-CN.js +15 -1
  19. package/package.json +1 -1
  20. package/shared/button.cjs +6 -3
  21. package/shared/button.js +6 -3
  22. package/shared/definition12.cjs +1 -1
  23. package/shared/definition12.js +1 -1
  24. package/shared/definition17.cjs +4 -1
  25. package/shared/definition17.js +4 -1
  26. package/shared/definition22.cjs +8 -2
  27. package/shared/definition22.js +8 -2
  28. package/shared/definition26.cjs +13 -2
  29. package/shared/definition26.js +13 -2
  30. package/shared/definition43.cjs +96 -32
  31. package/shared/definition43.js +96 -32
  32. package/shared/definition44.cjs +74 -4
  33. package/shared/definition44.js +74 -4
  34. package/shared/definition45.cjs +7 -1
  35. package/shared/definition45.js +7 -1
  36. package/shared/definition50.cjs +1 -1
  37. package/shared/definition50.js +1 -1
  38. package/shared/definition53.cjs +219 -297
  39. package/shared/definition53.js +221 -299
  40. package/shared/localization/Locale.d.ts +2 -0
  41. package/shared/vivid-element.cjs +1 -1
  42. package/shared/vivid-element.js +1 -1
  43. package/styles/core/all.css +1 -1
  44. package/styles/core/theme.css +1 -1
  45. package/styles/core/typography.css +1 -1
  46. package/styles/tokens/theme-dark.css +4 -4
  47. package/styles/tokens/theme-light.css +4 -4
  48. package/styles/tokens/vivid-2-compat.css +1 -1
  49. package/vivid.api.json +33 -3
@@ -9,9 +9,8 @@ const numbers = require('./numbers.cjs');
9
9
  const classNames = require('./class-names.cjs');
10
10
  const ref = require('./ref.cjs');
11
11
  const slotted = require('./slotted.cjs');
12
- const when = require('./when.cjs');
13
12
 
14
- const styles = ":host{display:block}.base{--_appearance-color-text: var(--_connotation-color-firm);--_appearance-color-fill: transparent;--_appearance-color-outline: transparent}.base.connotation-cta{--_connotation-color-firm: var(--vvd-tabs-cta-firm, var(--vvd-color-cta-600))}.base:not(.connotation-cta){--_connotation-color-firm: var(--vvd-tabs-accent-firm, var(--vvd-color-canvas-text))}.base{--_tabs-tablist-gutter: 8px;--_tabs-active-indicator-stroke-width: 2px;display:grid;box-sizing:border-box}.base.orientation-vertical{overflow:hidden;block-size:inherit;grid-template-columns:auto 1fr;grid-template-rows:1fr}.base:not(.orientation-vertical){block-size:var(--tabs-block-size, auto);grid-template-columns:1fr;grid-template-rows:auto 1fr;max-block-size:100%}.base.layout-stretch .scroll-shadow{flex:1}.base:not(.orientation-vertical) .scroll-shadow{position:relative;overflow:hidden;isolation:isolate}.base:not(.orientation-vertical) .scroll-shadow.start-scroll:before,.base:not(.orientation-vertical) .scroll-shadow.start-scroll:after,.base:not(.orientation-vertical) .scroll-shadow.end-scroll:before,.base:not(.orientation-vertical) .scroll-shadow.end-scroll:after{position:absolute;content:\"\";inline-size:10px;inset-block:0;opacity:0;transition:opacity .1s}.base:not(.orientation-vertical) .scroll-shadow.start-scroll:before{z-index:1;box-shadow:inset 7px 1px 5px -3px var(--vvd-color-neutral-950);inset-inline-start:0;opacity:.2}.base:not(.orientation-vertical) .scroll-shadow.end-scroll:after{box-shadow:inset -7px 1px 5px -3px var(--vvd-color-neutral-950);inset-inline-end:0;opacity:.2}.tabs{display:flex;overflow:hidden}.base.orientation-vertical .tabs{flex-direction:column;box-shadow:1px 0 0 0 var(--vvd-color-neutral-300)}.base.orientation-horizontal .tabs{flex-direction:row;border-bottom:1px solid var(--vvd-color-neutral-300)}.tablist{position:relative;display:grid;box-sizing:border-box;color:var(--_appearance-color-text)}.base.layout-stretch .tablist{--_tab-justify-content: center;--_tabs-tablist-column: 1fr}.tablist .base:not(.layout-stretch){--_tabs-tablist-column: auto}.base.orientation-vertical .tablist{padding:var(--_tabs-tablist-gutter);grid-row:1/2;grid-template-columns:auto 1fr;grid-template-rows:auto;inline-size:100%;place-self:flex-start end;row-gap:var(--_tabs-tablist-gutter)}.base:not(.orientation-vertical) .tablist{justify-content:flex-start;column-gap:var(--_tabs-tablist-gutter);grid-auto-flow:column;grid-template-rows:auto auto;inline-size:100%;padding-inline:var(--_tabs-tablist-gutter)}.base.has-action-items.orientation-vertical .tablist{padding-block-end:0}.base.has-action-items:not(.orientation-vertical) .tablist{padding-inline-end:0}.tablist-wrapper{--scrollbar-track-color: transparent;--scrollbar-thumb-color: color-mix(in srgb, var(--vvd-color-neutral-950), transparent 70%)}.tablist-wrapper{scrollbar-color:var(--scrollbar-thumb-color) var(--scrollbar-track-color);scrollbar-width:thin}.tablist-wrapper ::-webkit-scrollbar{width:4px}.tablist-wrapper ::-webkit-scrollbar-track{background:var(--scrollbar-track-color)}.tablist-wrapper ::-webkit-scrollbar-thumb{border:0;border-radius:4px;background-color:var(--scrollbar-fallback-track-color, var(--scrollbar-thumb-color))}.base:not(.orientation-vertical) .tablist-wrapper{overflow:auto hidden;align-self:end;inline-size:100%}.base.orientation-vertical .tablist-wrapper{overflow:hidden auto;block-size:100%}.tablist-wrapper::-webkit-scrollbar{display:none}slot[name=action-items]{display:flex}.base.has-action-items.orientation-horizontal slot[name=action-items]{align-items:center;margin-inline:var(--_tabs-tablist-gutter)}.base.has-action-items.orientation-vertical slot[name=action-items]{margin-block:var(--_tabs-tablist-gutter)}.tabpanel{box-sizing:border-box;block-size:100%;min-block-size:0;min-inline-size:0}.base.scroll .tabpanel{overflow-y:auto;--scrollbar-track-color: transparent;--scrollbar-thumb-color: color-mix(in srgb, var(--vvd-color-neutral-950), transparent 70%)}.base.scroll .tabpanel{scrollbar-color:var(--scrollbar-thumb-color) var(--scrollbar-track-color);scrollbar-width:thin}.base.scroll .tabpanel ::-webkit-scrollbar{width:4px}.base.scroll .tabpanel ::-webkit-scrollbar-track{background:var(--scrollbar-track-color)}.base.scroll .tabpanel ::-webkit-scrollbar-thumb{border:0;border-radius:4px;background-color:var(--scrollbar-fallback-track-color, var(--scrollbar-thumb-color))}.base.gutters-small .tabpanel{padding:calc(1px*(40 + 4*clamp(-1,var(--vvd-size-density, 0),2)) - (1px*(24 + 4*clamp(-1,var(--vvd-size-density, 0),2))))}.active-indicator{background:currentColor;margin-inline-start:calc(var(--_tabs-tablist-gutter) * -1)}.base.orientation-vertical .active-indicator{align-self:center;border-radius:2px;block-size:80%;grid-area:1/1/auto/auto;inline-size:var(--_tabs-active-indicator-stroke-width)}.base:not(.orientation-vertical) .active-indicator{position:absolute;z-index:1;border-radius:2px;block-size:var(--_tabs-active-indicator-stroke-width);grid-column:1/auto;inline-size:calc(var(--_tabs-active-tab-inline-size));inset-block-end:0;inset-inline-start:8px}.activeIndicatorTransition{transition:transform .2s ease-out 0s,inline-size .2s ease-out 0s}";
13
+ const styles = ":host{display:block}.base{--_appearance-color-text: var(--_connotation-color-firm);--_appearance-color-fill: transparent;--_appearance-color-outline: transparent}.base.connotation-cta{--_connotation-color-firm: var(--vvd-tabs-cta-firm, var(--vvd-color-cta-600))}.base:not(.connotation-cta){--_connotation-color-firm: var(--vvd-tabs-accent-firm, var(--vvd-color-canvas-text))}.base{--_tabs-tablist-gutter: 8px;--_tabs-active-indicator-stroke-width: 2px;display:grid;box-sizing:border-box}.base.orientation-vertical{overflow:hidden;block-size:inherit;grid-template-columns:auto 1fr;grid-template-rows:1fr}.base:not(.orientation-vertical){block-size:var(--tabs-block-size, auto);grid-template-columns:1fr;grid-template-rows:auto 1fr;max-block-size:100%}.base.layout-stretch .scroll-shadow{flex:1}.base:not(.orientation-vertical) .scroll-shadow{position:relative;overflow:hidden;isolation:isolate}.base:not(.orientation-vertical) .scroll-shadow.start-scroll:before,.base:not(.orientation-vertical) .scroll-shadow.start-scroll:after,.base:not(.orientation-vertical) .scroll-shadow.end-scroll:before,.base:not(.orientation-vertical) .scroll-shadow.end-scroll:after{position:absolute;content:\"\";inline-size:10px;inset-block:0;opacity:0;transition:opacity .1s}.base:not(.orientation-vertical) .scroll-shadow.start-scroll:before{z-index:1;box-shadow:inset 7px 1px 5px -3px var(--vvd-color-neutral-950);inset-inline-start:0;opacity:.2}.base:not(.orientation-vertical) .scroll-shadow.end-scroll:after{box-shadow:inset -7px 1px 5px -3px var(--vvd-color-neutral-950);inset-inline-end:0;opacity:.2}.tabs{display:flex;overflow:hidden}.base.orientation-vertical .tabs{flex-direction:column;box-shadow:1px 0 0 0 var(--vvd-color-neutral-300)}.base.orientation-horizontal .tabs{flex-direction:row;border-bottom:1px solid var(--vvd-color-neutral-300)}.tablist{position:relative;display:grid;overflow:hidden;box-sizing:border-box;color:var(--_appearance-color-text)}.base.layout-stretch .tablist{--_tab-justify-content: center;--_tabs-tablist-column: 1fr}.tablist .base:not(.layout-stretch){--_tabs-tablist-column: auto}.base.orientation-vertical .tablist{padding:var(--_tabs-tablist-gutter);block-size:fit-content;grid-row:1/2;grid-template-columns:auto 1fr;grid-template-rows:auto;inline-size:100%;min-block-size:100%;place-self:flex-start end;row-gap:var(--_tabs-tablist-gutter)}.base:not(.orientation-vertical) .tablist{justify-content:flex-start;column-gap:var(--_tabs-tablist-gutter);grid-auto-columns:var(--_tabs-tablist-column);grid-auto-flow:column;grid-template-rows:auto auto;inline-size:fit-content;min-inline-size:100%;padding-inline:var(--_tabs-tablist-gutter)}.base.has-action-items.orientation-vertical .tablist{padding-block-end:0}.base.has-action-items:not(.orientation-vertical) .tablist{padding-inline-end:0}.tablist-wrapper{--scrollbar-track-color: transparent;--scrollbar-thumb-color: color-mix(in srgb, var(--vvd-color-neutral-950), transparent 70%)}.tablist-wrapper{scrollbar-color:var(--scrollbar-thumb-color) var(--scrollbar-track-color);scrollbar-width:thin}.tablist-wrapper ::-webkit-scrollbar{width:4px}.tablist-wrapper ::-webkit-scrollbar-track{background:var(--scrollbar-track-color)}.tablist-wrapper ::-webkit-scrollbar-thumb{border:0;border-radius:4px;background-color:var(--scrollbar-fallback-track-color, var(--scrollbar-thumb-color))}.base:not(.orientation-vertical) .tablist-wrapper{overflow:auto hidden;align-self:end;inline-size:100%}.base.orientation-vertical .tablist-wrapper{overflow:hidden auto;block-size:100%}.tablist-wrapper::-webkit-scrollbar{display:none}slot[name=action-items]{display:flex}.base.has-action-items.orientation-horizontal slot[name=action-items]{align-items:center;margin-inline:var(--_tabs-tablist-gutter)}.base.has-action-items.orientation-vertical slot[name=action-items]{margin-block:var(--_tabs-tablist-gutter)}.tabpanel{box-sizing:border-box;block-size:100%;min-block-size:0;min-inline-size:0}.base.scroll .tabpanel{overflow-y:auto;--scrollbar-track-color: transparent;--scrollbar-thumb-color: color-mix(in srgb, var(--vvd-color-neutral-950), transparent 70%)}.base.scroll .tabpanel{scrollbar-color:var(--scrollbar-thumb-color) var(--scrollbar-track-color);scrollbar-width:thin}.base.scroll .tabpanel ::-webkit-scrollbar{width:4px}.base.scroll .tabpanel ::-webkit-scrollbar-track{background:var(--scrollbar-track-color)}.base.scroll .tabpanel ::-webkit-scrollbar-thumb{border:0;border-radius:4px;background-color:var(--scrollbar-fallback-track-color, var(--scrollbar-thumb-color))}.base.gutters-small .tabpanel{padding:calc(1px*(40 + 4*clamp(-1,var(--vvd-size-density, 0),2)) - (1px*(24 + 4*clamp(-1,var(--vvd-size-density, 0),2))))}.active-indicator{background:currentColor;margin-inline-start:calc(var(--_tabs-tablist-gutter) * -1)}.base.orientation-vertical .active-indicator{align-self:center;border-radius:2px;block-size:80%;grid-area:1/1;inline-size:var(--_tabs-active-indicator-stroke-width)}.base:not(.orientation-vertical) .active-indicator{position:absolute;z-index:1;border-radius:2px;block-size:var(--_tabs-active-indicator-stroke-width);grid-area:2/1;inline-size:calc(var(--_tabs-active-tab-inline-size));inset-block-end:0;inset-inline-start:8px}.activeIndicatorTransition{transition:transform .2s ease-out 0s,inline-size .2s ease-out 0s}";
15
14
 
16
15
  var __defProp = Object.defineProperty;
17
16
  var __decorateClass = (decorators, target, key, kind) => {
@@ -23,7 +22,6 @@ var __decorateClass = (decorators, target, key, kind) => {
23
22
  return result;
24
23
  };
25
24
  const ACTIVE_TAB_WIDTH = "--_tabs-active-tab-inline-size";
26
- const TABLIST_COLUMN = "--_tabs-tablist-column";
27
25
  const TabsGutters = {
28
26
  None: "none",
29
27
  Small: "small"
@@ -32,323 +30,284 @@ const TabsOrientation = {
32
30
  vertical: "vertical",
33
31
  horizontal: "horizontal"
34
32
  };
33
+ const oppositeOrientation = (orientation) => orientation === TabsOrientation.horizontal ? TabsOrientation.vertical : TabsOrientation.horizontal;
34
+ const gridProperty = (orientation) => orientation === TabsOrientation.horizontal ? "gridColumn" : "gridRow";
35
+ const translateProperty = (orientation) => orientation === TabsOrientation.horizontal ? "translateX" : "translateY";
36
+ const offsetProperty = (orientation) => orientation === TabsOrientation.horizontal ? "offsetLeft" : "offsetTop";
37
+ const isFocusableElement = (el) => el.getAttribute("aria-disabled") !== "true" && !el.hasAttribute("hidden");
35
38
  class Tabs extends vividElement.VividElement {
36
39
  constructor() {
37
40
  super(...arguments);
38
41
  // eslint-disable-next-line @nrwl/nx/workspace/no-attribute-default-value
39
42
  this.orientation = TabsOrientation.horizontal;
40
- this.#isLastTabSelectedAfterRemove = (oldValue, newValue, isTab = true) => {
41
- return oldValue.length > newValue.length && this.activetab.id === oldValue[oldValue.length - 1].getAttribute(
42
- isTab ? "id" : "aria-labelledby"
43
- );
44
- };
45
- // eslint-disable-next-line @nrwl/nx/workspace/no-attribute-default-value
46
- this.activeindicator = true;
47
- this.showActiveIndicator = true;
48
- this.prevActiveTabIndex = 0;
49
- this.activeTabIndex = 0;
50
- this.tabIds = [];
51
- this.tabpanelIds = [];
52
- this.change = () => {
53
- this.$emit("change", this.activetab);
54
- };
55
- this.isDisabledElement = (el) => {
56
- return el.getAttribute("aria-disabled") === "true";
57
- };
58
- this.isHiddenElement = (el) => {
59
- return el.hasAttribute("hidden");
60
- };
61
- this.isFocusableElement = (el) => {
62
- return !this.isDisabledElement(el) && !this.isHiddenElement(el);
63
- };
64
- this.setTabs = () => {
65
- const gridHorizontalProperty = "gridColumn";
66
- const gridVerticalProperty = "gridRow";
67
- const gridProperty = this.#isHorizontal() ? gridHorizontalProperty : gridVerticalProperty;
68
- this.activeTabIndex = this.getActiveIndex();
69
- this.showActiveIndicator = false;
70
- if (this.#isHorizontal()) {
71
- this.tablist.style.setProperty(
72
- "grid-template-columns",
73
- `repeat(${this.tabs.length}, var(${TABLIST_COLUMN}))`
74
- );
75
- } else {
76
- this.tablist.style.removeProperty("grid-template-columns");
77
- }
78
- this.tabs.forEach((tab, index) => {
79
- if (tab.slot === "tab") {
80
- const isActiveTab = this.activeTabIndex === index && this.isFocusableElement(tab);
81
- if (this.activeindicator && this.isFocusableElement(tab)) {
82
- this.showActiveIndicator = true;
83
- }
84
- const tabId = this.tabIds[index];
85
- const tabpanelId = this.tabpanelIds[index];
86
- tab.setAttribute("id", tabId);
87
- tab.setAttribute("aria-selected", isActiveTab ? "true" : "false");
88
- tab.setAttribute("aria-controls", tabpanelId);
89
- tab.addEventListener("click", this.handleTabClick);
90
- tab.addEventListener("keydown", this.#handleTabKeyDown);
91
- tab.setAttribute("tabindex", isActiveTab ? "0" : "-1");
92
- if (isActiveTab) {
93
- this.activetab = tab;
94
- this.activeid = tabId;
95
- }
96
- }
97
- tab.style[gridHorizontalProperty] = "";
98
- tab.style[gridVerticalProperty] = "";
99
- tab.style[gridProperty] = `${index + 1}`;
100
- !this.#isHorizontal() ? tab.classList.add("vertical") : tab.classList.remove("vertical");
101
- });
102
- this.#updateTabsConnotation();
103
- };
104
- this.setTabPanels = () => {
105
- this.tabpanels.forEach((tabpanel, index) => {
106
- const tabId = this.tabIds[index];
107
- const tabpanelId = this.tabpanelIds[index];
108
- tabpanel.setAttribute("id", tabpanelId);
109
- tabpanel.setAttribute("aria-labelledby", tabId);
110
- this.activeTabIndex !== index ? tabpanel.setAttribute("hidden", "") : tabpanel.removeAttribute("hidden");
111
- });
112
- };
113
- this.handleTabClick = (event) => {
43
+ this.tabs = [];
44
+ this.tabpanels = [];
45
+ this.#isTabsChangeQueued = false;
46
+ this.#lastActiveId = void 0;
47
+ this.#onTabClick = (event) => {
114
48
  const selectedTab = event.currentTarget;
115
- if (selectedTab.nodeType === 1 && this.isFocusableElement(selectedTab)) {
116
- this.prevActiveTabIndex = this.activeTabIndex;
117
- this.activeTabIndex = this.tabs.indexOf(selectedTab);
118
- this.setComponent();
49
+ if (this._validTabs.includes(selectedTab)) {
50
+ this.#setActiveTabDueToUserInteraction(selectedTab);
119
51
  }
120
52
  };
121
- this.#isHorizontal = () => {
122
- return this.orientation === TabsOrientation.horizontal;
123
- };
124
- this.#handleTabKeyDown = (event) => {
125
- if (this.#isHorizontal()) {
126
- switch (event.key) {
127
- case keyCodes.keyArrowLeft:
128
- event.preventDefault();
129
- this.adjustBackward(event);
130
- break;
131
- case keyCodes.keyArrowRight:
132
- event.preventDefault();
133
- this.adjustForward(event);
134
- break;
135
- }
136
- } else {
137
- switch (event.key) {
138
- case keyCodes.keyArrowUp:
139
- event.preventDefault();
140
- this.adjustBackward(event);
141
- break;
142
- case keyCodes.keyArrowDown:
143
- event.preventDefault();
144
- this.adjustForward(event);
145
- break;
146
- }
53
+ this.#onTabKeyDown = (event) => {
54
+ const tabs = this._validTabs;
55
+ const activeTab = this.activetab;
56
+ if (!activeTab) {
57
+ return;
147
58
  }
148
- switch (event.key) {
149
- case keyCodes.keyHome:
150
- event.preventDefault();
151
- this.adjust(-this.activeTabIndex);
152
- break;
153
- case keyCodes.keyEnd:
154
- event.preventDefault();
155
- this.adjust(this.tabs.length - this.activeTabIndex - 1);
156
- break;
59
+ const [arrowKeyPrev, arrowKeyNext] = this.orientation === TabsOrientation.horizontal ? [keyCodes.keyArrowLeft, keyCodes.keyArrowRight] : [keyCodes.keyArrowUp, keyCodes.keyArrowDown];
60
+ const keyToNextTab = {
61
+ [arrowKeyPrev]: () => tabs[(tabs.indexOf(activeTab) - 1 + tabs.length) % tabs.length],
62
+ [arrowKeyNext]: () => tabs[(tabs.indexOf(activeTab) + 1) % tabs.length],
63
+ [keyCodes.keyHome]: () => tabs[0],
64
+ [keyCodes.keyEnd]: () => tabs[tabs.length - 1]
65
+ };
66
+ if (keyToNextTab[event.key]) {
67
+ event.preventDefault();
68
+ this.#setActiveTabDueToUserInteraction(keyToNextTab[event.key]());
157
69
  }
158
70
  };
159
- this.adjustForward = (_) => {
160
- this.#moveToNextTab(1);
161
- };
162
- this.adjustBackward = (_) => {
163
- this.#moveToNextTab(-1);
164
- };
165
- this.#moveToTabByIndex = (group, index) => {
166
- const tab = group[index];
167
- this.activetab = tab;
168
- this.prevActiveTabIndex = this.activeTabIndex;
169
- this.activeTabIndex = index;
170
- tab.focus();
171
- this.setComponent();
172
- };
71
+ this.#isTransitioningTransform = false;
173
72
  this.scrollablePanel = false;
174
73
  this._actionItemsSlottedContent = [];
74
+ // eslint-disable-next-line @nrwl/nx/workspace/no-attribute-default-value
75
+ this.activeindicator = true;
175
76
  }
176
77
  /**
177
78
  * @internal
178
79
  */
179
80
  orientationChanged() {
81
+ this._registerTabsChange();
180
82
  if (this.$fastController.isConnected) {
181
- this.setTabs();
182
- this.setTabPanels();
183
- this.#handleActiveIndicatorPosition();
184
- }
185
- this.#patchIndicatorStyleTransition();
186
- if (!this.activeIndicatorRef) return;
187
- if (this.orientation === TabsOrientation.vertical) {
188
- this.activeIndicatorRef.style.removeProperty(ACTIVE_TAB_WIDTH);
83
+ vividElement.DOM.queueUpdate(() => this.#moveActiveIndicator(false));
189
84
  }
190
85
  }
191
86
  /**
192
87
  * @internal
193
88
  */
194
- activeidChanged(oldValue, _) {
195
- if (this.$fastController.isConnected && this.tabs.length <= this.tabpanels.length) {
196
- this.prevActiveTabIndex = this.tabs.findIndex(
197
- (item) => item.id === oldValue
198
- );
199
- this.setTabs();
200
- this.setTabPanels();
201
- this.#handleActiveIndicatorPosition();
89
+ tabsChanged() {
90
+ for (const tab of this.tabs) {
91
+ if (!tab.id) {
92
+ tab.id = `tab-${strings.uniqueId()}`;
93
+ }
94
+ tab.addEventListener("click", this.#onTabClick);
95
+ tab.addEventListener("keydown", this.#onTabKeyDown);
202
96
  }
203
- this.#patchIndicatorStyleTransition();
204
- this.#scrollToIndex(this.activeTabIndex);
97
+ this._registerTabsChange();
205
98
  }
206
- #isLastTabSelectedAfterRemove;
207
99
  /**
208
100
  * @internal
209
101
  */
210
- tabsChanged(oldValue, newValue) {
211
- if (this.$fastController.isConnected && this.tabs.length <= this.tabpanels.length) {
212
- this.tabIds = this.getTabIds();
213
- this.tabpanelIds = this.getTabPanelIds();
214
- this.setTabs();
215
- this.setTabPanels();
216
- this.#handleActiveIndicatorPosition(
217
- !this.#isLastTabSelectedAfterRemove(oldValue, newValue)
218
- );
102
+ tabpanelsChanged() {
103
+ for (const panel of this.tabpanels) {
104
+ if (!panel.id) {
105
+ panel.id = `panel-${strings.uniqueId()}`;
106
+ }
219
107
  }
220
- this.#patchIndicatorStyleTransition();
221
- this.#updateScrollStatus();
108
+ this._registerTabsChange();
109
+ }
110
+ /**
111
+ * Tabs that are paired with a tabpanel. Ignore any excess tabs or panels.
112
+ */
113
+ get _pairedTabs() {
114
+ return this.tabs.slice(
115
+ 0,
116
+ Math.min(this.tabs.length, this.tabpanels.length)
117
+ );
118
+ }
119
+ /**
120
+ * Tabs that are eligible to become active.
121
+ */
122
+ get _validTabs() {
123
+ return this._pairedTabs.filter(isFocusableElement);
222
124
  }
223
125
  /**
224
126
  * @internal
225
127
  */
226
- tabpanelsChanged(oldValue, newValue) {
227
- if (this.$fastController.isConnected && this.tabpanels.length <= this.tabs.length) {
228
- this.tabIds = this.getTabIds();
229
- this.tabpanelIds = this.getTabPanelIds();
230
- this.setTabs();
231
- this.setTabPanels();
232
- this.#handleActiveIndicatorPosition(
233
- !this.#isLastTabSelectedAfterRemove(oldValue, newValue, false)
234
- );
235
- }
236
- this.#patchIndicatorStyleTransition();
128
+ activeidChanged() {
129
+ this._registerTabsChange();
237
130
  }
238
- getActiveIndex() {
239
- const id = this.activeid;
240
- if (id !== void 0) {
241
- return this.tabIds.indexOf(this.activeid) === -1 ? 0 : this.tabIds.indexOf(this.activeid);
242
- } else {
243
- return 0;
244
- }
131
+ /**
132
+ * A reference to the active tab
133
+ * @public
134
+ */
135
+ get activetab() {
136
+ return this._validTabs.find((tab) => tab.id === this.activeid) ?? null;
245
137
  }
246
- getTabIds() {
247
- return this.tabs.map((tab) => {
248
- return tab.getAttribute("id") ?? `tab-${strings.uniqueId()}`;
249
- });
138
+ #setActiveTabDueToUserInteraction(tab) {
139
+ this.activeid = tab.id;
140
+ tab.focus();
141
+ this.$emit("change", tab);
250
142
  }
251
- getTabPanelIds() {
252
- return this.tabpanels.map((tabPanel) => {
253
- return tabPanel.getAttribute("id") ?? `panel-${strings.uniqueId()}`;
143
+ #isTabsChangeQueued;
144
+ /**
145
+ * Defer actual processing of changes into a microtask to wait for all DOM changes to complete. E.g. when tabs and
146
+ * active id are updated at the same time.
147
+ */
148
+ _registerTabsChange() {
149
+ if (this.#isTabsChangeQueued) {
150
+ return;
151
+ }
152
+ this.#isTabsChangeQueued = true;
153
+ window.queueMicrotask(() => {
154
+ if (this.$fastController.isConnected) {
155
+ this.#handleTabsChange();
156
+ }
157
+ this.#isTabsChangeQueued = false;
254
158
  });
255
159
  }
256
- setComponent() {
257
- if (this.activeTabIndex !== this.prevActiveTabIndex) {
258
- this.activeid = this.tabIds[this.activeTabIndex];
259
- this.focusTab();
260
- this.change();
160
+ #lastActiveId;
161
+ #handleTabsChange() {
162
+ const validTabs = this._validTabs;
163
+ let newActiveId = this.activeid;
164
+ if (!validTabs.length || newActiveId && !validTabs.find((t) => t.id === newActiveId)) {
165
+ newActiveId = void 0;
166
+ }
167
+ if (!newActiveId && validTabs.length) {
168
+ newActiveId = validTabs[0].id;
169
+ }
170
+ if (this.activeid !== newActiveId) {
171
+ this.activeid = newActiveId;
172
+ this.$emit("change", this.activetab);
173
+ }
174
+ this.#updateSlottedChildren();
175
+ if (this.activeid !== this.#lastActiveId) {
176
+ if (this.activetab) {
177
+ const shouldAnimate = this.#lastActiveId !== void 0;
178
+ this.#scrollToTab(this.activetab, shouldAnimate);
179
+ this.#moveActiveIndicator(shouldAnimate);
180
+ }
181
+ this.#lastActiveId = this.activeid;
182
+ } else {
183
+ this.#moveActiveIndicator(this.#isTransitioningTransform);
184
+ }
185
+ }
186
+ /**
187
+ * Updates the tabs and their panels according to the current state of the component.
188
+ */
189
+ #updateSlottedChildren() {
190
+ for (const [index, tab] of this._pairedTabs.entries()) {
191
+ const panel = this.tabpanels[index];
192
+ const isActiveTab = tab.id === this.activeid;
193
+ tab.setAttribute("aria-selected", isActiveTab ? "true" : "false");
194
+ tab.setAttribute("aria-controls", panel.id);
195
+ tab.setAttribute("tabindex", isActiveTab ? "0" : "-1");
196
+ if (isActiveTab && this.connotation) {
197
+ tab.setAttribute("connotation", this.connotation);
198
+ } else {
199
+ tab.removeAttribute("connotation");
200
+ }
201
+ tab.classList.toggle(
202
+ "vertical",
203
+ this.orientation === TabsOrientation.vertical
204
+ );
205
+ tab.style[gridProperty(oppositeOrientation(this.orientation))] = "";
206
+ tab.style[gridProperty(this.orientation)] = `${index + 1}`;
207
+ panel.setAttribute("aria-labelledby", tab.id);
208
+ panel.hidden = !isActiveTab;
261
209
  }
262
210
  }
263
- #isHorizontal;
264
- #handleTabKeyDown;
211
+ #onTabClick;
212
+ #onTabKeyDown;
265
213
  /**
266
- * The adjust method for FASTTabs
214
+ * Adjusts the active index by numerical increments.
215
+ * Only enabled tabs are considered.
267
216
  * @public
268
217
  * @remarks
269
- * This method allows the active index to be adjusted by numerical increments
270
218
  */
271
219
  adjust(adjustment) {
272
- const focusableTabs = this.tabs.filter((t) => this.isFocusableElement(t));
273
- const currentActiveTabIndex = focusableTabs.indexOf(this.activetab);
220
+ const focusableTabs = this._validTabs;
221
+ const currentActiveTabIndex = focusableTabs.findIndex(
222
+ (t) => t.id === this.activeid
223
+ );
224
+ if (currentActiveTabIndex === -1) {
225
+ return;
226
+ }
274
227
  const nextTabIndex = numbers.limit(
275
228
  0,
276
229
  focusableTabs.length - 1,
277
230
  currentActiveTabIndex + adjustment
278
231
  );
279
- const nextIndex = this.tabs.indexOf(focusableTabs[nextTabIndex]);
280
- if (nextIndex > -1) {
281
- this.#moveToTabByIndex(this.tabs, nextIndex);
232
+ this.#setActiveTabDueToUserInteraction(focusableTabs[nextTabIndex]);
233
+ }
234
+ #isTransitioningTransform;
235
+ /**
236
+ * @internal
237
+ */
238
+ _onActiveIndicatorTransitionend(event) {
239
+ if (event.propertyName === "transform") {
240
+ this.#isTransitioningTransform = false;
282
241
  }
283
242
  }
284
- #moveToNextTab(direction) {
285
- const activeIndex = this.tabs.indexOf(this.activetab);
286
- for (let offset = 1; offset < this.tabs.length; offset++) {
287
- const index = (activeIndex + direction * offset + this.tabs.length) % this.tabs.length;
288
- if (this.isFocusableElement(this.tabs[index])) {
289
- this.#moveToTabByIndex(this.tabs, index);
290
- break;
243
+ #cancelAnimationIfNeeded() {
244
+ this.#isTransitioningTransform = false;
245
+ this.activeIndicatorRef.classList.remove("activeIndicatorTransition");
246
+ }
247
+ #moveActiveIndicator(shouldAnimate) {
248
+ const activeTabIndex = this._pairedTabs.findIndex(
249
+ (tab) => tab.id === this.activeid
250
+ );
251
+ if (activeTabIndex === -1) {
252
+ return;
253
+ }
254
+ const indicatorEl = this.activeIndicatorRef;
255
+ const currentOffset = indicatorEl[offsetProperty(this.orientation)];
256
+ indicatorEl.style[gridProperty(this.orientation)] = `${activeTabIndex + 1}`;
257
+ const targetOffset = indicatorEl[offsetProperty(this.orientation)];
258
+ indicatorEl.style[gridProperty(this.orientation)] = "";
259
+ const relativeOffset = targetOffset - currentOffset;
260
+ const currentTransform = indicatorEl.style.transform;
261
+ const targetTransform = `${translateProperty(
262
+ this.orientation
263
+ )}(${relativeOffset}px)`;
264
+ if (shouldAnimate) {
265
+ indicatorEl.classList.add("activeIndicatorTransition");
266
+ if (currentTransform !== targetTransform) {
267
+ this.#isTransitioningTransform = true;
291
268
  }
269
+ } else {
270
+ this.#cancelAnimationIfNeeded();
292
271
  }
293
- }
294
- #moveToTabByIndex;
295
- focusTab() {
296
- this.tabs[this.activeTabIndex].focus();
272
+ indicatorEl.style.transform = targetTransform;
273
+ indicatorEl.style.setProperty(
274
+ ACTIVE_TAB_WIDTH,
275
+ this.tabs[activeTabIndex].getBoundingClientRect().width + "px"
276
+ );
297
277
  }
298
278
  /**
299
279
  * @internal
300
280
  */
301
281
  connotationChanged() {
302
- this.#updateTabsConnotation();
282
+ this._registerTabsChange();
303
283
  }
304
284
  #updateScrollStatus() {
305
- this.tablist.parentElement.dispatchEvent(new Event("scroll"));
306
- }
307
- get #shouldMoveIndicator() {
308
- return !(!this.activetab || !this.activeIndicatorRef || this.orientation === TabsOrientation.vertical || !this.showActiveIndicator);
309
- }
310
- #patchIndicatorStyleTransition() {
311
- if (!this.#shouldMoveIndicator) {
312
- return;
313
- }
314
- const width = this.activetab.getBoundingClientRect().width;
315
- this.activeIndicatorRef.style.setProperty(ACTIVE_TAB_WIDTH, `${width}px`);
285
+ this.#tabListScrollWrapper.dispatchEvent(new Event("scroll"));
316
286
  }
287
+ #resizeObserver;
317
288
  connectedCallback() {
318
289
  super.connectedCallback();
319
- this.tabIds = this.getTabIds();
320
- this.tabpanelIds = this.getTabPanelIds();
321
- this.activeTabIndex = this.getActiveIndex();
290
+ this._registerTabsChange();
322
291
  requestAnimationFrame(() => this.#updateScrollStatus());
323
- const scrollWrapper = this.tablist.parentElement;
324
292
  this.#resizeObserver = new ResizeObserver(() => {
293
+ this.#moveActiveIndicator(this.#isTransitioningTransform);
325
294
  this.#updateScrollStatus();
326
- this.#patchIndicatorStyleTransition();
327
295
  });
328
- this.#resizeObserver.observe(scrollWrapper);
296
+ this.#resizeObserver.observe(this.#tabListScrollWrapper);
297
+ this.#resizeObserver.observe(this.tablist);
329
298
  }
330
- #resizeObserver;
331
299
  disconnectedCallback() {
332
300
  super.disconnectedCallback();
333
301
  this.#resizeObserver.disconnect();
334
302
  }
335
- #updateTabsConnotation() {
336
- if (this.tabs) {
337
- this.tabs.forEach((tab) => {
338
- if (tab.getAttribute("aria-selected") === "true") {
339
- tab.setAttribute("connotation", this.connotation);
340
- } else {
341
- tab.removeAttribute("connotation");
342
- }
343
- });
344
- }
345
- }
346
303
  get #tabListWrapper() {
347
304
  return this.shadowRoot.querySelector(".tablist-wrapper");
348
305
  }
349
- #scrollToIndex(index) {
350
- const tab = this.tabs?.[index];
351
- if (!tab) return;
306
+ get #tabListScrollWrapper() {
307
+ return this.tablist.parentElement;
308
+ }
309
+ #scrollToTab(tab, shouldAnimate = true) {
310
+ const index = this.tabs.findIndex((t) => t === tab);
352
311
  let left = 0;
353
312
  let top = 0;
354
313
  if (this.orientation === TabsOrientation.vertical) {
@@ -366,51 +325,16 @@ class Tabs extends vividElement.VividElement {
366
325
  left = tab.offsetLeft - this.#tabListWrapper.offsetWidth / 2 + tab.offsetWidth / 2;
367
326
  }
368
327
  }
369
- this.#tabListWrapper.scrollTo({ top, left, behavior: "smooth" });
370
- }
371
- #getGridProperty() {
372
- return this.#isHorizontal() ? "gridColumn" : "gridRow";
373
- }
374
- #getTranslateProperty() {
375
- return this.#isHorizontal() ? "translateX" : "translateY";
376
- }
377
- #handleActiveIndicatorPosition(animate = true) {
378
- if (this.showActiveIndicator && this.activeindicator) {
379
- this.#animateActiveIndicator(animate);
380
- }
381
- }
382
- #animateActiveIndicator(animate) {
383
- const offsetProperty = this.#isHorizontal() ? "offsetLeft" : "offsetTop";
384
- const currentOffset = this.activeIndicatorRef[offsetProperty];
385
- const currentGridValue = this.activeIndicatorRef.style[this.#getGridProperty()];
386
- this.activeIndicatorRef.style[this.#getGridProperty()] = `${this.activeTabIndex + 1}`;
387
- const targetOffset = this.activeIndicatorRef[offsetProperty];
388
- this.activeIndicatorRef.style[this.#getGridProperty()] = currentGridValue;
389
- const relativeOffset = targetOffset - currentOffset;
390
- this.activeIndicatorRef.style.transform = `${this.#getTranslateProperty()}(${relativeOffset}px)`;
391
- if (animate) {
392
- this.activeIndicatorRef.classList.add("activeIndicatorTransition");
393
- } else {
394
- this.activeIndicatorRef.classList.remove("activeIndicatorTransition");
395
- }
396
- }
397
- /**
398
- * @internal
399
- */
400
- activeIndicatorRefChanged() {
401
- this.activeIndicatorRef.addEventListener("transitionend", () => {
402
- this.activeIndicatorRef.style[this.#getGridProperty()] = `${this.activeTabIndex + 1}`;
403
- this.activeIndicatorRef.style.transform = `${this.#getTranslateProperty()}(0px)`;
404
- this.activeIndicatorRef.classList.remove("activeIndicatorTransition");
328
+ this.#tabListWrapper.scrollTo({
329
+ top,
330
+ left,
331
+ behavior: shouldAnimate ? "smooth" : "instant"
405
332
  });
406
333
  }
407
334
  }
408
335
  __decorateClass([
409
336
  vividElement.attr
410
337
  ], Tabs.prototype, "orientation");
411
- __decorateClass([
412
- vividElement.attr
413
- ], Tabs.prototype, "activeid");
414
338
  __decorateClass([
415
339
  vividElement.observable
416
340
  ], Tabs.prototype, "tabs");
@@ -418,14 +342,11 @@ __decorateClass([
418
342
  vividElement.observable
419
343
  ], Tabs.prototype, "tabpanels");
420
344
  __decorateClass([
421
- vividElement.attr({ mode: "boolean" })
422
- ], Tabs.prototype, "activeindicator");
345
+ vividElement.attr
346
+ ], Tabs.prototype, "activeid");
423
347
  __decorateClass([
424
348
  vividElement.observable
425
349
  ], Tabs.prototype, "activeIndicatorRef");
426
- __decorateClass([
427
- vividElement.observable
428
- ], Tabs.prototype, "showActiveIndicator");
429
350
  __decorateClass([
430
351
  vividElement.observable
431
352
  ], Tabs.prototype, "tablist");
@@ -444,6 +365,9 @@ __decorateClass([
444
365
  __decorateClass([
445
366
  vividElement.observable
446
367
  ], Tabs.prototype, "_actionItemsSlottedContent");
368
+ __decorateClass([
369
+ vividElement.attr({ mode: "boolean" })
370
+ ], Tabs.prototype, "activeindicator");
447
371
 
448
372
  const getClasses = ({
449
373
  connotation,
@@ -475,7 +399,7 @@ function addStartShadow(scrollShadow, scrollWrapper) {
475
399
  function addEndShadow(scrollShadow, scrollWrapper) {
476
400
  scrollShadow.classList.toggle(
477
401
  "end-scroll",
478
- scrollWrapper.scrollLeft < scrollWrapper.scrollWidth - scrollWrapper.clientWidth
402
+ scrollWrapper.scrollLeft + 1 < scrollWrapper.scrollWidth - scrollWrapper.clientWidth
479
403
  );
480
404
  }
481
405
  function setShadowWhenScrollTabs(_, { event }) {
@@ -495,15 +419,13 @@ const TabsTemplate = vividElement.html`
495
419
  <div class="tablist-wrapper" @scroll="${setShadowWhenScrollTabs}">
496
420
  <div class="tablist" role="tablist" ${ref.ref("tablist")}>
497
421
  <slot name="tab" ${slotted.slotted("tabs")}></slot>
498
- ${when.when(
499
- (x) => x.showActiveIndicator,
500
- vividElement.html`
501
- <div
502
- ${ref.ref("activeIndicatorRef")}
503
- class="active-indicator"
504
- ></div>
505
- `
506
- )}
422
+ <div
423
+ ${ref.ref("activeIndicatorRef")}
424
+ class="active-indicator"
425
+ @transitionend="${(x, c) => x._onActiveIndicatorTransitionend(
426
+ c.event
427
+ )}"
428
+ ></div>
507
429
  </div>
508
430
  </div>
509
431
  </div>