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