@db-ux/react-core-components 4.7.0-tabs-34782eb → 4.7.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # @db-ux/react-core-components
2
2
 
3
+ ## 4.7.1
4
+
5
+ _version bump_
6
+
7
+
3
8
  ## 4.7.0
4
9
 
5
10
  ### Minor Changes
package/agent/Tabs.md CHANGED
@@ -16,56 +16,56 @@ function Tabs(props: any) {
16
16
  <h2>1. Default Tabs</h2>
17
17
  <DBTabs>
18
18
  <DBTabList>
19
- <DBTabItem>Def Tab 1</DBTabItem>
20
- <DBTabItem>Def Tab 2</DBTabItem>
21
- <DBTabItem>Def Tab 3</DBTabItem>
19
+ <DBTabItem>Tab 1</DBTabItem>
20
+ <DBTabItem>Tab 2</DBTabItem>
21
+ <DBTabItem>Tab 3</DBTabItem>
22
22
  </DBTabList>
23
- <DBTabPanel>Def Panel 1</DBTabPanel>
24
- <DBTabPanel>Def Panel 2</DBTabPanel>
25
- <DBTabPanel>Def Panel 3</DBTabPanel>
23
+ <DBTabPanel>Tab Panel 1</DBTabPanel>
24
+ <DBTabPanel>Tab Panel 2</DBTabPanel>
25
+ <DBTabPanel>Tab Panel 3</DBTabPanel>
26
26
  </DBTabs>
27
27
  <h2>2. Behavior Variants</h2>
28
28
  <DBTabs behavior="scrollbar">
29
29
  <DBTabList>
30
- <DBTabItem>Scroll Tab 1</DBTabItem>
31
- <DBTabItem>Scroll Tab 2</DBTabItem>
32
- <DBTabItem>Scroll Tab 3</DBTabItem>
30
+ <DBTabItem>Tab 1</DBTabItem>
31
+ <DBTabItem>Tab 2</DBTabItem>
32
+ <DBTabItem>Tab 3</DBTabItem>
33
33
  </DBTabList>
34
- <DBTabPanel>Scroll Panel 1</DBTabPanel>
35
- <DBTabPanel>Scroll Panel 2</DBTabPanel>
36
- <DBTabPanel>Scroll Panel 3</DBTabPanel>
34
+ <DBTabPanel>Tab Panel 1</DBTabPanel>
35
+ <DBTabPanel>Tab Panel 2</DBTabPanel>
36
+ <DBTabPanel>Tab Panel 3</DBTabPanel>
37
37
  </DBTabs>
38
38
  <DBTabs behavior="arrows">
39
39
  <DBTabList>
40
- <DBTabItem>Arrow Tab 1</DBTabItem>
41
- <DBTabItem>Arrow Tab 2</DBTabItem>
42
- <DBTabItem>Arrow Tab 3</DBTabItem>
40
+ <DBTabItem>Tab 1</DBTabItem>
41
+ <DBTabItem>Tab 2</DBTabItem>
42
+ <DBTabItem>Tab 3</DBTabItem>
43
43
  </DBTabList>
44
- <DBTabPanel>Arrow Panel 1</DBTabPanel>
45
- <DBTabPanel>Arrow Panel 2</DBTabPanel>
46
- <DBTabPanel>Arrow Panel 3</DBTabPanel>
44
+ <DBTabPanel>Tab Panel 1</DBTabPanel>
45
+ <DBTabPanel>Tab Panel 2</DBTabPanel>
46
+ <DBTabPanel>Tab Panel 3</DBTabPanel>
47
47
  </DBTabs>
48
48
  <h2>3. Initial Selected Index</h2>
49
49
  <DBTabs initialSelectedIndex={1}>
50
50
  <DBTabList>
51
- <DBTabItem>InitIdx Tab 1</DBTabItem>
52
- <DBTabItem>InitIdx Tab 2</DBTabItem>
53
- <DBTabItem>InitIdx Tab 3</DBTabItem>
51
+ <DBTabItem>Tab 1</DBTabItem>
52
+ <DBTabItem>Tab 2</DBTabItem>
53
+ <DBTabItem>Tab 3</DBTabItem>
54
54
  </DBTabList>
55
- <DBTabPanel>InitIdx Panel 1</DBTabPanel>
56
- <DBTabPanel>InitIdx Panel 2</DBTabPanel>
57
- <DBTabPanel>InitIdx Panel 3</DBTabPanel>
55
+ <DBTabPanel>Tab Panel 1</DBTabPanel>
56
+ <DBTabPanel>Tab Panel 2</DBTabPanel>
57
+ <DBTabPanel>Tab Panel 3</DBTabPanel>
58
58
  </DBTabs>
59
59
  <h2>4. Initial Selected Mode</h2>
60
60
  <DBTabs initialSelectedMode="manually">
61
61
  <DBTabList>
62
- <DBTabItem>Manually Tab 1</DBTabItem>
63
- <DBTabItem>Manually Tab 2</DBTabItem>
64
- <DBTabItem>Manually Tab 3</DBTabItem>
62
+ <DBTabItem>Tab 1</DBTabItem>
63
+ <DBTabItem>Tab 2</DBTabItem>
64
+ <DBTabItem>Tab 3</DBTabItem>
65
65
  </DBTabList>
66
- <DBTabPanel>Manually Panel 1</DBTabPanel>
67
- <DBTabPanel>Manually Panel 2</DBTabPanel>
68
- <DBTabPanel>Manually Panel 3</DBTabPanel>
66
+ <DBTabPanel>Tab Panel 1</DBTabPanel>
67
+ <DBTabPanel>Tab Panel 2</DBTabPanel>
68
+ <DBTabPanel>Tab Panel 3</DBTabPanel>
69
69
  </DBTabs>
70
70
  </>
71
71
  );
@@ -1,5 +1,9 @@
1
- import { ActiveProps, ClickEventProps, GlobalProps, GlobalState, IconLeadingProps, IconProps, IconTrailingProps, InitializedState, ShowIconLeadingProps, ShowIconProps, ShowIconTrailingProps, WidthProps } from '../../shared/model';
1
+ import { ActiveProps, ChangeEventProps, ChangeEventState, GlobalProps, GlobalState, IconLeadingProps, IconProps, IconTrailingProps, InitializedState, NameProps, NameState, ShowIconLeadingProps, ShowIconProps, ShowIconTrailingProps } from '../../shared/model';
2
2
  export type DBTabItemDefaultProps = {
3
+ /**
4
+ * To control the component
5
+ */
6
+ checked?: boolean | string;
3
7
  /**
4
8
  * The disabled attribute can be set to keep a user from clicking on the tab-item.
5
9
  */
@@ -12,33 +16,12 @@ export type DBTabItemDefaultProps = {
12
16
  * Define the text next to the icon specified via the icon Property to get hidden.
13
17
  */
14
18
  noText?: boolean | string;
15
- /**
16
- * Set the tabIndex manually (internal use for roving tabindex).
17
- */
18
- tabIndex?: number | string;
19
- /**
20
- * The id of the panel this tab controls (WAI-ARIA).
21
- */
22
- ariaControls?: string;
23
- /**
24
- * Semantic value of this tab item. When set, onIndexChange will emit this value
25
- * (via the onValueChange event) instead of only the numeric index.
26
- * Useful for form binding (e.g. Angular FormControl, React useState).
27
- */
28
- value?: string;
29
19
  };
30
- export type DBTabItemProps = DBTabItemDefaultProps & GlobalProps & ClickEventProps<HTMLButtonElement> & IconProps & ShowIconProps & IconTrailingProps & IconLeadingProps & ShowIconTrailingProps & ShowIconLeadingProps & ActiveProps & WidthProps;
20
+ export type DBTabItemProps = GlobalProps & DBTabItemDefaultProps & IconProps & IconTrailingProps & IconLeadingProps & ShowIconLeadingProps & ShowIconTrailingProps & ActiveProps & ChangeEventProps<HTMLInputElement> & ShowIconProps & NameProps;
31
21
  export type DBTabItemDefaultState = {
32
- internalActive: boolean | undefined;
33
- internalTabIndex: number;
34
- getCurrentTabIndex: () => number;
35
- _resizeObserver: ResizeObserver | null | undefined;
36
- _ariaSelectedListener: {
37
- fn: (event: any) => void;
38
- } | null;
39
- handleClick: (event: any) => void;
40
- isTruncated: boolean;
41
- checkTruncation: () => void;
42
- tooltipText: string;
22
+ _selected: boolean;
23
+ _listenerAdded: boolean;
24
+ boundSetSelectedOnChange?: (event: any) => void;
25
+ setSelectedOnChange: (event: any) => void;
43
26
  };
44
- export type DBTabItemState = DBTabItemDefaultState & GlobalState & InitializedState;
27
+ export type DBTabItemState = DBTabItemDefaultState & GlobalState & ChangeEventState<HTMLInputElement> & InitializedState & NameState;
@@ -1,3 +1,3 @@
1
1
  import * as React from "react";
2
- declare const DBTabItem: React.ForwardRefExoticComponent<Omit<React.InputHTMLAttributes<HTMLButtonElement>, keyof import("../..").GlobalProps | "icon" | "showIcon" | keyof import("../..").ClickEventProps<HTMLButtonElement> | "width" | "showIconLeading" | "showIconTrailing" | "iconLeading" | "iconTrailing" | "active" | keyof import("./model").DBTabItemDefaultProps> & import("./model").DBTabItemDefaultProps & import("../..").GlobalProps & import("../..").ClickEventProps<HTMLButtonElement> & import("../..").IconProps & import("../..").ShowIconProps & import("../..").IconTrailingProps & import("../..").IconLeadingProps & import("../..").ShowIconTrailingProps & import("../..").ShowIconLeadingProps & import("../..").ActiveProps & import("../..").WidthProps & React.RefAttributes<HTMLButtonElement>>;
2
+ declare const DBTabItem: React.ForwardRefExoticComponent<Omit<React.InputHTMLAttributes<any>, keyof import("../..").GlobalProps | "name" | "icon" | "showIcon" | "showIconLeading" | "showIconTrailing" | "iconLeading" | "iconTrailing" | keyof import("../..").ChangeEventProps<HTMLInputElement> | "active" | keyof import("./model").DBTabItemDefaultProps> & import("../..").GlobalProps & import("./model").DBTabItemDefaultProps & import("../..").IconProps & import("../..").IconTrailingProps & import("../..").IconLeadingProps & import("../..").ShowIconLeadingProps & import("../..").ShowIconTrailingProps & import("../..").ActiveProps & import("../..").ChangeEventProps<HTMLInputElement> & import("../..").ShowIconProps & import("../..").NameProps & React.RefAttributes<any>>;
3
3
  export default DBTabItem;
@@ -2,150 +2,77 @@
2
2
  import * as React from "react";
3
3
  import { filterPassingProps, getRootProps } from "../../utils/react";
4
4
  import { useState, useRef, useEffect, forwardRef } from "react";
5
- import { cls, getBoolean } from "../../utils";
6
- import DBTooltip from "../tooltip/tooltip";
5
+ import { cls, getBoolean, getBooleanAsString } from "../../utils";
7
6
  function DBTabItemFn(props, component) {
8
- var _a, _b, _c, _d;
7
+ var _a, _b, _c, _d, _e, _f;
9
8
  const _ref = component || useRef(component);
10
- const _labelRef = useRef(null);
9
+ const [_selected, set_selected] = useState(() => false);
10
+ const [_name, set_name] = useState(() => undefined);
11
11
  const [initialized, setInitialized] = useState(() => false);
12
- const [internalActive, setInternalActive] = useState(() => false);
13
- const [internalTabIndex, setInternalTabIndex] = useState(() => -1);
14
- function getCurrentTabIndex() {
15
- return props.tabIndex !== undefined
16
- ? Number(props.tabIndex)
17
- : internalTabIndex;
12
+ const [_listenerAdded, set_listenerAdded] = useState(() => false);
13
+ const [boundSetSelectedOnChange, setBoundSetSelectedOnChange] = useState(() => undefined);
14
+ function setSelectedOnChange(event) {
15
+ event.stopPropagation();
16
+ set_selected(event.target === _ref.current);
18
17
  }
19
- const [isTruncated, setIsTruncated] = useState(() => false);
20
- const [tooltipText, setTooltipText] = useState(() => "");
21
- const [_resizeObserver, set_resizeObserver] = useState(() => null);
22
- const [_ariaSelectedListener, set_ariaSelectedListener] = useState(() => null);
23
- function handleClick(event) {
24
- if (event && event.preventDefault) {
25
- event.preventDefault();
26
- }
27
- if (!getBoolean(props.disabled) && props.onClick) {
28
- props.onClick(event);
18
+ function handleNameAttribute() {
19
+ if (_ref.current) {
20
+ const setAttribute = _ref.current.setAttribute;
21
+ _ref.current.setAttribute = (attribute, value) => {
22
+ setAttribute.call(_ref.current, attribute, value);
23
+ if (attribute === "name") {
24
+ set_name(value);
25
+ }
26
+ };
29
27
  }
30
28
  }
31
- function checkTruncation() {
32
- if (_labelRef.current) {
33
- const scrollWidth = Math.ceil(_labelRef.current.scrollWidth);
34
- const clientWidth = Math.ceil(_labelRef.current.clientWidth);
35
- const truncated = scrollWidth > clientWidth + 1;
36
- if (isTruncated !== truncated) {
37
- setIsTruncated(truncated);
38
- if (!truncated && _ref.current) {
39
- if (_ref.current.hasAttribute("data-has-tooltip")) {
40
- _ref.current.removeAttribute("data-has-tooltip");
41
- }
42
- if (_ref.current.hasAttribute("aria-describedby")) {
43
- _ref.current.removeAttribute("aria-describedby");
44
- }
45
- }
46
- }
47
- setTooltipText(truncated
48
- ? props.label ||
49
- _labelRef.current.innerText ||
50
- _labelRef.current.textContent ||
51
- ""
52
- : "");
29
+ function handleChange(event) {
30
+ if (props.onChange) {
31
+ props.onChange(event);
53
32
  }
54
33
  }
34
+ useEffect(() => {
35
+ setBoundSetSelectedOnChange(() => setSelectedOnChange);
36
+ setInitialized(true);
37
+ }, []);
55
38
  useEffect(() => {
56
39
  var _a;
57
- setInternalActive(getBoolean(props.active) || false);
58
- setInternalTabIndex(getBoolean(props.active) ? 0 : -1);
59
- if (typeof window !== "undefined") {
60
- const setupObserverAndCheck = () => {
61
- requestAnimationFrame(() => {
62
- checkTruncation();
63
- const labelEl = _labelRef.current;
64
- if (labelEl && !labelEl.dataset.label) {
65
- labelEl.dataset.label =
66
- props.label || labelEl.innerText || labelEl.textContent || "";
67
- }
68
- if (_labelRef.current) {
69
- const resizeObserver = new ResizeObserver(() => {
70
- requestAnimationFrame(() => {
71
- checkTruncation();
72
- });
73
- });
74
- resizeObserver.observe(_labelRef.current);
75
- set_resizeObserver(resizeObserver);
76
- }
77
- });
78
- };
79
- const hasIcon = props.showIcon && props.icon;
80
- if (hasIcon && ((_a = document.fonts) === null || _a === void 0 ? void 0 : _a.ready)) {
81
- document.fonts.ready.then(setupObserverAndCheck);
40
+ if (_ref.current && initialized && boundSetSelectedOnChange) {
41
+ handleNameAttribute();
42
+ setInitialized(false);
43
+ // deselect this tab when another tab in tablist is selected
44
+ if (!_listenerAdded) {
45
+ (_a = _ref.current
46
+ .closest("[role=tablist]")) === null || _a === void 0 ? void 0 : _a.addEventListener("change", boundSetSelectedOnChange);
47
+ set_listenerAdded(true);
82
48
  }
83
- else {
84
- setupObserverAndCheck();
49
+ // Initialize selected state from either active prop (set by parent) or checked attribute
50
+ if (props.active || _ref.current.checked) {
51
+ set_selected(true);
52
+ _ref.current.click();
85
53
  }
86
54
  }
87
- if (_ref.current) {
88
- const listener = (event) => {
89
- setInternalActive(event.detail.selected);
90
- if (props.tabIndex === undefined) {
91
- if (event.detail.tabIndex !== undefined) {
92
- setInternalTabIndex(event.detail.tabIndex);
93
- }
94
- else {
95
- setInternalTabIndex(event.detail.selected ? 0 : -1);
96
- }
97
- }
98
- };
99
- set_ariaSelectedListener({
100
- fn: listener,
101
- });
102
- _ref.current.addEventListener("aria-selected-changed", listener);
103
- }
104
- }, []);
105
- useEffect(() => {
106
- if (props.active !== undefined) {
107
- setInternalActive(getBoolean(props.active) || false);
108
- }
109
- }, [props.active]);
55
+ }, [_ref.current, initialized, boundSetSelectedOnChange]);
110
56
  useEffect(() => {
111
- var _a, _b;
112
- if (_ref.current) {
113
- const isDisabled = getBoolean(props.disabled);
114
- const disabledStr = isDisabled ? "true" : "false";
115
- if (((_a = _ref.current) === null || _a === void 0 ? void 0 : _a.getAttribute("aria-disabled")) !== disabledStr) {
116
- (_b = _ref.current) === null || _b === void 0 ? void 0 : _b.setAttribute("aria-disabled", disabledStr);
117
- }
118
- if (!isTruncated) {
119
- if (_ref.current.hasAttribute("data-has-tooltip")) {
120
- _ref.current.removeAttribute("data-has-tooltip");
121
- }
122
- if (_ref.current.hasAttribute("aria-describedby")) {
123
- _ref.current.removeAttribute("aria-describedby");
124
- }
125
- }
57
+ if (props.name) {
58
+ set_name(props.name);
126
59
  }
127
- });
60
+ }, [props.name]);
128
61
  useEffect(() => {
129
62
  return () => {
130
- _resizeObserver === null || _resizeObserver === void 0 ? void 0 : _resizeObserver.disconnect();
131
- const _listener = _ariaSelectedListener;
132
- if (_ref.current && _listener) {
133
- _ref.current.removeEventListener("aria-selected-changed", _listener.fn);
63
+ var _a;
64
+ if (_listenerAdded && _ref.current && boundSetSelectedOnChange) {
65
+ (_a = _ref.current
66
+ .closest("[role=tablist]")) === null || _a === void 0 ? void 0 : _a.removeEventListener("change", boundSetSelectedOnChange);
67
+ set_listenerAdded(false);
134
68
  }
135
69
  };
136
70
  }, []);
137
- return (React.createElement("button", Object.assign({ type: "button", role: "tab", title: "", ref: _ref }, filterPassingProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font", "data-density"]), getRootProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font", "data-density"]), { className: cls("db-tab-item", props.className), "aria-label": getBoolean(props.noText) ? props.label : undefined, "aria-selected": (props.active !== undefined ? getBoolean(props.active) : internalActive)
138
- ? "true"
139
- : "false", "aria-controls": props.ariaControls, disabled: getBoolean(props.disabled) ? true : undefined, tabIndex: getCurrentTabIndex(), id: props.id, "data-active": props.active !== undefined ? getBoolean(props.active) : internalActive, "data-no-text": getBoolean(props.noText) ? "true" : undefined, "data-value": props.value, onClick: (event) => handleClick(event) }),
140
- !props.noText ? (React.createElement("span", { className: "db-tab-label", title: "", ref: _labelRef, "data-icon": getBoolean((_a = props.showIconLeading) !== null && _a !== void 0 ? _a : props.showIcon)
141
- ? (_b = props.iconLeading) !== null && _b !== void 0 ? _b : props.icon
142
- : undefined, "data-icon-trailing": getBoolean(props.showIconTrailing) ? props.iconTrailing : undefined },
71
+ return (React.createElement("li", Object.assign({ role: "none" }, getRootProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font", "data-density"]), { className: cls("db-tab-item", props.className) }),
72
+ React.createElement("label", { htmlFor: (_a = props.id) !== null && _a !== void 0 ? _a : (_b = props.propOverrides) === null || _b === void 0 ? void 0 : _b.id, "data-icon": (_c = props.iconLeading) !== null && _c !== void 0 ? _c : props.icon, "data-icon-trailing": props.iconTrailing, "data-show-icon": getBooleanAsString((_d = props.showIconLeading) !== null && _d !== void 0 ? _d : props.showIcon), "data-show-icon-trailing": getBooleanAsString(props.showIconTrailing), "data-no-text": getBooleanAsString(props.noText) },
73
+ React.createElement("input", Object.assign({ type: "radio", role: "tab", disabled: getBoolean(props.disabled, "disabled"), "aria-selected": _selected, checked: getBoolean(props.checked, "checked"), ref: _ref }, filterPassingProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font", "data-density"]), { name: _name, id: (_e = props.id) !== null && _e !== void 0 ? _e : (_f = props.propOverrides) === null || _f === void 0 ? void 0 : _f.id, onInput: (event) => handleChange(event) })),
143
74
  props.label ? React.createElement(React.Fragment, null, props.label) : null,
144
- !props.label ? React.createElement(React.Fragment, null, props.children) : null)) : null,
145
- getBoolean(props.noText) ? (React.createElement("span", { className: "db-tab-label", "aria-hidden": "true", "data-icon": getBoolean((_c = props.showIconLeading) !== null && _c !== void 0 ? _c : props.showIcon)
146
- ? (_d = props.iconLeading) !== null && _d !== void 0 ? _d : props.icon
147
- : undefined })) : null,
148
- isTruncated && tooltipText ? (React.createElement(DBTooltip, { placement: "right" }, tooltipText)) : null));
75
+ props.children)));
149
76
  }
150
77
  const DBTabItem = forwardRef(DBTabItemFn);
151
78
  export default DBTabItem;
@@ -1,15 +1,5 @@
1
- import { GlobalProps, GlobalState, OrientationProps } from '../../shared/model';
2
- export type DBTabListDefaultProps = {
3
- /**
4
- * Defines a string value that labels the current element (WAI-ARIA).
5
- */
6
- ariaLabel?: string;
7
- /**
8
- * Identifies the element (or elements) that labels the current element (WAI-ARIA).
9
- */
10
- ariaLabelledby?: string;
11
- };
12
- export type DBTabListProps = DBTabListDefaultProps & GlobalProps & OrientationProps;
13
- export interface DBTabListState extends GlobalState {
14
- _id?: string;
15
- }
1
+ import { GlobalProps } from '../../shared/model';
2
+ export type DBTabListDefaultProps = {};
3
+ export type DBTabListProps = DBTabListDefaultProps & GlobalProps;
4
+ export type DBTabListDefaultState = {};
5
+ export type DBTabListState = DBTabListDefaultState;
@@ -1,3 +1,3 @@
1
1
  import * as React from "react";
2
- declare const DBTabList: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLDivElement>, keyof import("../..").GlobalProps | keyof import("./model").DBTabListDefaultProps | "orientation"> & import("./model").DBTabListDefaultProps & import("../..").GlobalProps & import("../..").OrientationProps & React.RefAttributes<HTMLDivElement>>;
2
+ declare const DBTabList: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<any>, keyof import("../..").GlobalProps> & import("../..").GlobalProps & React.RefAttributes<any>>;
3
3
  export default DBTabList;
@@ -1,17 +1,13 @@
1
1
  "use client";
2
2
  import * as React from "react";
3
3
  import { filterPassingProps, getRootProps } from "../../utils/react";
4
- import { useState, useRef, useEffect, forwardRef } from "react";
4
+ import { useRef, forwardRef } from "react";
5
5
  import { cls } from "../../utils";
6
- import { useId } from "react";
7
6
  function DBTabListFn(props, component) {
8
- const uuid = useId();
7
+ var _a, _b;
9
8
  const _ref = component || useRef(component);
10
- const [_id, set_id] = useState(() => "tab-list-base-id");
11
- useEffect(() => {
12
- set_id(props.id || "tab-list-" + uuid);
13
- }, []);
14
- return (React.createElement("div", Object.assign({ role: "tablist", ref: _ref }, filterPassingProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font", "data-density"]), { id: _id }, getRootProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font", "data-density"]), { className: cls("db-tab-list", props.className), "aria-orientation": props.orientation || "horizontal", "aria-label": props.ariaLabelledby ? undefined : props.ariaLabel, "aria-labelledby": props.ariaLabelledby }), props.children));
9
+ return (React.createElement("div", Object.assign({ ref: _ref }, filterPassingProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font", "data-density"]), { id: (_a = props.id) !== null && _a !== void 0 ? _a : (_b = props.propOverrides) === null || _b === void 0 ? void 0 : _b.id }, getRootProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font", "data-density"]), { className: cls("db-tab-list", props.className) }),
10
+ React.createElement("ul", { role: "tablist" }, props.children)));
15
11
  }
16
12
  const DBTabList = forwardRef(DBTabListFn);
17
13
  export default DBTabList;
@@ -1,20 +1,10 @@
1
- import { GlobalProps } from '../../shared/model';
1
+ import { GlobalProps, GlobalState } from '../../shared/model';
2
2
  export type DBTabPanelDefaultProps = {
3
3
  /**
4
4
  * The content if you don't want to use children.
5
5
  */
6
6
  content?: string;
7
- /**
8
- * If the panel is hidden.
9
- */
10
- hidden?: boolean;
11
- /**
12
- * The id of the tab that labels this panel (WAI-ARIA).
13
- */
14
- ariaLabelledby?: string;
15
- /**
16
- * Accessible label for the panel, overrides ariaLabelledby for the accessible name.
17
- */
18
- ariaLabel?: string;
19
7
  };
20
8
  export type DBTabPanelProps = DBTabPanelDefaultProps & GlobalProps;
9
+ export type DBTabPanelDefaultState = {};
10
+ export type DBTabPanelState = DBTabPanelDefaultState & GlobalState;
@@ -1,3 +1,3 @@
1
1
  import * as React from "react";
2
- declare const DBTabPanel: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLDivElement>, keyof import("../..").GlobalProps | keyof import("./model").DBTabPanelDefaultProps> & import("./model").DBTabPanelDefaultProps & import("../..").GlobalProps & React.RefAttributes<HTMLDivElement>>;
2
+ declare const DBTabPanel: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<any>, "content" | keyof import("../..").GlobalProps> & import("./model").DBTabPanelDefaultProps & import("../..").GlobalProps & React.RefAttributes<any>>;
3
3
  export default DBTabPanel;
@@ -1,12 +1,13 @@
1
1
  "use client";
2
2
  import * as React from "react";
3
3
  import { filterPassingProps, getRootProps } from "../../utils/react";
4
- import { useRef, forwardRef } from "react";
4
+ import { useRef, useEffect, forwardRef } from "react";
5
5
  import { cls } from "../../utils";
6
6
  function DBTabPanelFn(props, component) {
7
7
  var _a, _b;
8
8
  const _ref = component || useRef(component);
9
- return (React.createElement("div", Object.assign({ role: "tabpanel", ref: _ref }, filterPassingProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font", "data-density"]), getRootProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font", "data-density"]), { className: cls("db-tab-panel", props.className), id: (_a = props.id) !== null && _a !== void 0 ? _a : (_b = props.propOverrides) === null || _b === void 0 ? void 0 : _b.id, tabIndex: props.hidden ? -1 : 0, hidden: props.hidden, "aria-label": props.ariaLabel, "aria-labelledby": props.ariaLabel ? undefined : props.ariaLabelledby }),
9
+ useEffect(() => { }, []);
10
+ return (React.createElement("section", Object.assign({ role: "tabpanel", ref: _ref }, filterPassingProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font", "data-density"]), getRootProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font", "data-density"]), { className: cls("db-tab-panel", props.className), id: (_a = props.id) !== null && _a !== void 0 ? _a : (_b = props.propOverrides) === null || _b === void 0 ? void 0 : _b.id }),
10
11
  props.content ? React.createElement(React.Fragment, null, props.content) : null,
11
12
  props.children));
12
13
  }
@@ -1,4 +1,4 @@
1
- import { GlobalProps, InitializedState, OrientationProps, TabItemAlignmentProps, WidthType } from '../../shared/model';
1
+ import { AlignmentProps, GlobalProps, InitializedState, InputEvent, OrientationProps, WidthProps } from '../../shared/model';
2
2
  import { DBTabItemProps } from '../tab-item/model';
3
3
  import { DBTabPanelProps } from '../tab-panel/model';
4
4
  export declare const TabsBehaviorList: readonly ["scrollbar", "arrows"];
@@ -19,12 +19,6 @@ export type DBTabsDefaultProps = {
19
19
  * Default behavior is auto selecting the first tab, change selected tab by index
20
20
  */
21
21
  initialSelectedIndex?: number | string;
22
- /**
23
- * Controlled active tab index. When set, the component becomes controlled:
24
- * the consumer is responsible for updating this value in the onIndexChange handler.
25
- * Takes precedence over initialSelectedIndex after mount.
26
- */
27
- activeIndex?: number | string;
28
22
  /**
29
23
  * Default behavior is auto selecting the first tab, disable it with 'manually'
30
24
  */
@@ -37,18 +31,6 @@ export type DBTabsDefaultProps = {
37
31
  * Provide simple tabs with label + text as content
38
32
  */
39
33
  tabs?: DBSimpleTabProps[] | string;
40
- /**
41
- * Width of the tab-items. Auto width based on tab-item size, full width based on parent elements width.
42
- */
43
- tabItemWidth?: WidthType | string;
44
- /**
45
- * Accessible label for the "scroll towards start" button (i18n). Only used with behavior="arrows".
46
- */
47
- scrollStartLabel?: string;
48
- /**
49
- * Accessible label for the "scroll towards end" button (i18n). Only used with behavior="arrows".
50
- */
51
- scrollEndLabel?: string;
52
34
  };
53
35
  export type DBTabsEventProps = {
54
36
  /**
@@ -60,42 +42,26 @@ export type DBTabsEventProps = {
60
42
  */
61
43
  onIndexChange?: (index?: number) => void;
62
44
  /**
63
- * Fires when the active tab changes and a `value` prop is set on the tab items.
64
- * Payload is the `value` string of the newly active tab item, or undefined
65
- * if the tab item has no `value` prop set.
66
- * Use this for form binding (e.g. Angular FormControl, React controlled state).
45
+ * Informs the user if another tab has been selected.
46
+ */
47
+ onTabSelect?: (event?: InputEvent<HTMLElement>) => void;
48
+ /**
49
+ * Informs the user if another tab has been selected.
67
50
  */
68
- onValueChange?: (value?: string) => void;
51
+ tabSelect?: (event?: InputEvent<HTMLElement>) => void;
69
52
  };
70
- export type DBTabsProps = DBTabsDefaultProps & GlobalProps & OrientationProps & TabItemAlignmentProps & DBTabsEventProps;
53
+ export type DBTabsProps = DBTabsDefaultProps & GlobalProps & OrientationProps & WidthProps & AlignmentProps & DBTabsEventProps;
71
54
  export type DBTabsDefaultState = {
72
- _generatedId: string;
73
- _generatedName: string;
74
- _id: () => string;
75
- _name: () => string;
76
- _getScrollContainer: () => Element | null;
77
- scroll: (toStart?: boolean) => void;
78
- showScrollStart?: boolean;
79
- showScrollEnd?: boolean;
80
- _isRtl: () => boolean;
55
+ _name: string;
56
+ scrollContainer?: Element | null;
57
+ scroll: (left?: boolean) => void;
58
+ showScrollLeft?: boolean;
59
+ showScrollRight?: boolean;
81
60
  evaluateScrollButtons: (tabList: Element) => void;
82
- _cachedTabs: DBSimpleTabProps[];
83
- _updateCachedTabs: () => void;
61
+ convertTabs: () => DBSimpleTabProps[];
84
62
  initTabList: () => void;
85
- initTabs: (activeIndex?: number) => void;
86
- _resizeObserver?: ResizeObserver | null;
87
- _observer?: MutationObserver | null;
88
- _pendingRafId: number | null;
89
- _scrollListener: {
90
- fn: () => void;
91
- } | null;
92
- activeTabIndex: number;
93
- activateTab: (index: number) => void;
94
- getTabId: (index: number | string) => string;
95
- getPanelId: (index: number | string) => string;
96
- handleClick: (event: any) => void;
97
- handleKeyDown: (event: any) => void;
98
- isIndexActive: (index: number | string) => boolean;
99
- getTabItemTabIndex: (index: number | string) => 0 | -1;
63
+ initTabs: (init?: boolean) => void;
64
+ handleChange: (event: InputEvent<HTMLElement>) => void;
65
+ _resizeObserver?: ResizeObserver;
100
66
  };
101
67
  export type DBTabsState = DBTabsDefaultState & InitializedState;
@@ -1,3 +1,3 @@
1
1
  import * as React from "react";
2
- declare const DBTabs: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLDivElement>, keyof import("../..").GlobalProps | "orientation" | keyof import("./model").DBTabsDefaultProps | "tabItemAlignment" | keyof import("./model").DBTabsEventProps> & import("./model").DBTabsDefaultProps & import("../..").GlobalProps & import("../..").OrientationProps & import("../..").TabItemAlignmentProps & import("./model").DBTabsEventProps & React.RefAttributes<HTMLDivElement>>;
2
+ declare const DBTabs: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<any>, keyof import("../../shared/model").GlobalProps | "width" | "alignment" | keyof import("./model").DBTabsDefaultProps | "orientation" | keyof import("./model").DBTabsEventProps> & import("./model").DBTabsDefaultProps & import("../../shared/model").GlobalProps & import("../../shared/model").OrientationProps & import("../../shared/model").WidthProps & import("../../shared/model").AlignmentProps & import("./model").DBTabsEventProps & React.RefAttributes<any>>;
3
3
  export default DBTabs;
@@ -9,414 +9,172 @@ import DBTabList from "../tab-list/tab-list";
9
9
  import DBTabPanel from "../tab-panel/tab-panel";
10
10
  import { useId } from "react";
11
11
  function DBTabsFn(props, component) {
12
- var _a, _b, _c;
13
- props = Object.assign({ tabItemWidth: "auto", tabItemAlignment: "start", scrollStartLabel: "Scroll start", scrollEndLabel: "Scroll end" }, props);
12
+ var _a, _b, _c, _d, _e, _f;
14
13
  const uuid = useId();
15
14
  const _ref = component || useRef(component);
16
- const [_generatedId, set_generatedId] = useState(() => "tabs-" + uuid);
17
- const [_generatedName, set_generatedName] = useState(() => uuid);
18
- const [activeTabIndex, setActiveTabIndex] = useState(() => 0);
15
+ const [_name, set_name] = useState(() => "");
19
16
  const [initialized, setInitialized] = useState(() => false);
20
- const [showScrollStart, setShowScrollStart] = useState(() => false);
21
- const [showScrollEnd, setShowScrollEnd] = useState(() => false);
22
- const [_resizeObserver, set_resizeObserver] = useState(() => null);
23
- const [_observer, set_observer] = useState(() => null);
24
- const [_pendingRafId, set_pendingRafId] = useState(() => null);
25
- const [_scrollListener, set_scrollListener] = useState(() => null);
26
- function _id() {
27
- return props.id || _generatedId;
28
- }
29
- function _name() {
30
- return "tabs-" + (props.name || _generatedName);
31
- }
32
- function getTabId(index) {
33
- return `${_name()}-tab-${index}`;
34
- }
35
- function getPanelId(index) {
36
- return `${_name()}-tab-panel-${index}`;
37
- }
38
- function activateTab(index) {
39
- var _a, _b, _c;
40
- // Prevent activating a disabled tab
41
- if (_ref.current) {
42
- const tabList = _ref.current.querySelector('[role="tablist"]');
43
- if (tabList) {
44
- const tabs = Array.from(tabList.querySelectorAll('[role="tab"]'));
45
- const tab = tabs[index];
46
- if ((tab === null || tab === void 0 ? void 0 : tab.disabled) || (tab === null || tab === void 0 ? void 0 : tab.getAttribute("aria-disabled")) === "true") {
47
- return;
48
- }
49
- }
50
- }
51
- if (activeTabIndex !== index) {
52
- setActiveTabIndex(index);
53
- if (props.onIndexChange) {
54
- props.onIndexChange(index);
55
- }
56
- // Emit value of the newly active tab item if value props are set
57
- if (props.onValueChange) {
58
- const tabList = (_a = _ref.current) === null || _a === void 0 ? void 0 : _a.querySelector('[role="tablist"]');
59
- const tabs = tabList
60
- ? Array.from(tabList.querySelectorAll('[role="tab"]'))
61
- : [];
62
- const value = (_c = (_b = tabs[index]) === null || _b === void 0 ? void 0 : _b.dataset) === null || _c === void 0 ? void 0 : _c["value"];
63
- props.onValueChange(value);
64
- }
65
- initTabs(index);
66
- }
67
- }
68
- function handleClick(event) {
69
- var _a;
70
- // In props-mode (props.tabs), tab activation is handled via onClick on each DBTabItem directly.
71
- // In slot-mode (!props.tabs), clicks bubble up and are handled here via DOM traversal.
72
- if (props.tabs) {
73
- return;
74
- }
75
- const target = event.target;
76
- const button = target.closest('[role="tab"]');
77
- if (!button || !_ref.current)
78
- return;
79
- const tabList = (_a = _ref.current) === null || _a === void 0 ? void 0 : _a.querySelector('[role="tablist"]');
80
- if (!tabList)
81
- return;
82
- const buttons = Array.from(tabList.querySelectorAll('[role="tab"]'));
83
- const index = buttons.indexOf(button);
84
- if (index !== -1) {
85
- event.preventDefault();
86
- activateTab(index);
87
- }
88
- }
89
- function handleKeyDown(event) {
90
- var _a;
91
- if (!_ref.current)
92
- return;
93
- const key = event.key;
94
- const navigationKeys = [
95
- "ArrowRight",
96
- "ArrowDown",
97
- "ArrowLeft",
98
- "ArrowUp",
99
- "Home",
100
- "End",
101
- "Enter",
102
- " ",
103
- ];
104
- if (!navigationKeys.includes(key)) {
105
- return;
106
- }
107
- const tabList = _ref.current.querySelector('[role="tablist"]');
108
- if (!tabList)
109
- return;
110
- const buttons = Array.from(tabList.querySelectorAll('[role="tab"]'));
111
- // find currently focused element within the buttons list
112
- let currentIndex = -1;
113
- if (typeof document !== "undefined") {
114
- // Traverse Shadow DOM boundaries to find the truly focused element.
115
- // document.activeElement only returns the shadow host when focus is inside a Shadow DOM,
116
- // so we must walk through each shadowRoot to reach the actual focused element.
117
- let activeEl = document.activeElement;
118
- while ((_a = activeEl === null || activeEl === void 0 ? void 0 : activeEl.shadowRoot) === null || _a === void 0 ? void 0 : _a.activeElement) {
119
- activeEl = activeEl.shadowRoot.activeElement;
120
- }
121
- if (activeEl) {
122
- const focusedButton = activeEl.closest('[role="tab"]');
123
- if (focusedButton) {
124
- currentIndex = buttons.indexOf(focusedButton);
125
- }
126
- }
127
- }
128
- if (currentIndex === -1) {
129
- currentIndex = activeTabIndex;
130
- }
131
- if (buttons.length > 0) {
132
- // handle activation (enter / space) -> change panel
133
- if (key === "Enter" || key === " ") {
134
- event.preventDefault();
135
- activateTab(currentIndex);
136
- return;
137
- }
138
- // handle navigation (arrows) -> moves focus
139
- let nextIndex;
140
- const length = buttons.length;
141
- if (key === "ArrowRight" || key === "ArrowDown") {
142
- nextIndex = (currentIndex + 1) % length;
143
- }
144
- else if (key === "ArrowLeft" || key === "ArrowUp") {
145
- nextIndex = (currentIndex - 1 + length) % length;
146
- }
147
- else if (key === "Home") {
148
- nextIndex = 0;
149
- }
150
- else if (key === "End") {
151
- nextIndex = length - 1;
152
- }
153
- if (nextIndex !== undefined) {
154
- event.preventDefault();
155
- // Skip disabled tabs when navigating with arrow keys
156
- const isForward = key === "ArrowRight" || key === "ArrowDown";
157
- const maxAttempts = length;
158
- for (let i = 0; i < maxAttempts; i++) {
159
- const candidate = buttons[nextIndex];
160
- if (!(candidate === null || candidate === void 0 ? void 0 : candidate.disabled) &&
161
- (candidate === null || candidate === void 0 ? void 0 : candidate.getAttribute("aria-disabled")) !== "true") {
162
- break;
163
- }
164
- if (isForward) {
165
- nextIndex = (nextIndex + 1) % length;
166
- }
167
- else {
168
- nextIndex = (nextIndex - 1 + length) % length;
169
- }
170
- }
171
- // do not activateTab here for manual activation, just move the focus
172
- const nextButton = buttons[nextIndex];
173
- if (nextButton &&
174
- !nextButton.disabled &&
175
- nextButton.getAttribute("aria-disabled") !== "true") {
176
- nextButton.focus();
177
- }
178
- }
179
- }
180
- }
181
- function isIndexActive(index) {
182
- return activeTabIndex === Number(index);
183
- }
184
- function getTabItemTabIndex(index) {
185
- const i = Number(index);
186
- // only the active tab should be reachable via Tab key
187
- return activeTabIndex === i || (activeTabIndex === -1 && i === 0) ? 0 : -1;
188
- }
189
- const [_cachedTabs, set_cachedTabs] = useState(() => []);
190
- function _updateCachedTabs() {
17
+ const [showScrollLeft, setShowScrollLeft] = useState(() => false);
18
+ const [showScrollRight, setShowScrollRight] = useState(() => false);
19
+ const [scrollContainer, setScrollContainer] = useState(() => null);
20
+ const [_resizeObserver, set_resizeObserver] = useState(() => undefined);
21
+ function convertTabs() {
191
22
  try {
192
23
  if (typeof props.tabs === "string") {
193
- set_cachedTabs(JSON.parse(props.tabs));
194
- }
195
- else if (props.tabs) {
196
- set_cachedTabs(props.tabs);
197
- }
198
- else {
199
- set_cachedTabs([]);
24
+ return JSON.parse(props.tabs);
200
25
  }
26
+ return props.tabs;
201
27
  }
202
28
  catch (error) {
203
29
  console.error(error);
204
- set_cachedTabs([]);
205
30
  }
206
- }
207
- function _getScrollContainer() {
208
- var _a, _b;
209
- return (_b = (_a = _ref.current) === null || _a === void 0 ? void 0 : _a.querySelector('[role="tablist"]')) !== null && _b !== void 0 ? _b : null;
210
- }
211
- function _isRtl() {
212
- const container = _getScrollContainer();
213
- return (!!container &&
214
- typeof getComputedStyle !== "undefined" &&
215
- getComputedStyle(container).direction === "rtl");
31
+ return [];
216
32
  }
217
33
  function evaluateScrollButtons(tList) {
218
34
  const needsScroll = tList.scrollWidth > tList.clientWidth;
219
- if (!needsScroll) {
220
- setShowScrollStart(false);
221
- setShowScrollEnd(false);
222
- return;
223
- }
224
- const scrollPos = Math.abs(tList.scrollLeft);
225
- const maxScroll = tList.scrollWidth - tList.clientWidth;
226
- const tolerance = 2;
227
- // scrollPos=0 means "at inline-start" in both LTR and RTL
228
- setShowScrollStart(scrollPos > tolerance);
229
- setShowScrollEnd(scrollPos < maxScroll - tolerance);
35
+ setShowScrollLeft(needsScroll && tList.scrollLeft > 1);
36
+ setShowScrollRight(needsScroll && tList.scrollLeft < tList.scrollWidth - tList.clientWidth);
230
37
  }
231
- function scroll(toStart) {
232
- const container = _getScrollContainer();
233
- if (!container) {
234
- return;
235
- }
236
- let step = Number(props.arrowScrollDistance) || 120;
237
- const isLeft = !!toStart;
238
- const isRtl = _isRtl();
239
- // Map logical direction (start/end) to physical direction.
240
- // In LTR: toStart=true → scroll left (negative), toEnd → scroll right (positive).
241
- // In RTL: directions are inverted physically.
242
- if (isLeft !== isRtl) {
38
+ function scroll(left) {
39
+ let step = Number(props.arrowScrollDistance) || 100;
40
+ if (left) {
243
41
  step *= -1;
244
42
  }
245
- container.scrollBy({
43
+ scrollContainer === null || scrollContainer === void 0 ? void 0 : scrollContainer.scrollBy({
44
+ top: 0,
246
45
  left: step,
247
46
  behavior: "smooth",
248
47
  });
249
48
  }
250
49
  function initTabList() {
251
- var _a, _b;
252
50
  if (_ref.current) {
253
- const container = _ref.current.querySelector('[role="tablist"]');
254
- if (container) {
255
- if (!container.getAttribute("aria-orientation")) {
256
- container.setAttribute("aria-orientation", (_a = props.orientation) !== null && _a !== void 0 ? _a : "horizontal");
257
- }
258
- if (props.behavior === "arrows") {
259
- evaluateScrollButtons(container);
260
- const _listener = _scrollListener;
261
- if (_listener && container) {
262
- container.removeEventListener("scroll", _listener.fn);
263
- set_scrollListener(null);
264
- }
265
- const onScroll = () => evaluateScrollButtons(container);
266
- set_scrollListener({
267
- fn: onScroll,
268
- });
269
- container.addEventListener("scroll", onScroll);
270
- if (!_resizeObserver) {
271
- const observer = new ResizeObserver(() => {
51
+ const tabList = _ref.current.querySelector(".db-tab-list");
52
+ if (tabList) {
53
+ const container = tabList.querySelector('[role="tablist"]');
54
+ if (container) {
55
+ container.setAttribute("aria-orientation", props.orientation || "horizontal");
56
+ if (props.behavior === "arrows") {
57
+ setScrollContainer(container);
58
+ evaluateScrollButtons(container);
59
+ container.addEventListener("scroll", () => {
272
60
  evaluateScrollButtons(container);
273
61
  });
274
- observer.observe(container);
275
- set_resizeObserver(observer);
62
+ // Use ResizeObserver to re-evaluate scroll buttons because it provides more accurate, container-specific resize detection than global window resize events.
63
+ if (!_resizeObserver) {
64
+ const observer = new ResizeObserver(() => {
65
+ evaluateScrollButtons(container);
66
+ });
67
+ observer.observe(container);
68
+ set_resizeObserver(observer);
69
+ }
276
70
  }
277
71
  }
278
- if (props.name && !container.getAttribute("aria-label")) {
279
- container.setAttribute("aria-label", (_b = props.name) !== null && _b !== void 0 ? _b : "");
280
- }
281
72
  }
282
73
  }
283
74
  }
284
- function initTabs(activeIndex) {
285
- var _a, _b;
286
- const currentIndex = activeIndex !== undefined ? activeIndex : activeTabIndex;
75
+ function initTabs(init) {
287
76
  if (_ref.current) {
288
- const tabListEl = _ref.current.querySelector('[role="tablist"]');
289
- const panels = Array.from((_b = (_a = _ref.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll('[role="tabpanel"]')) !== null && _b !== void 0 ? _b : []).filter((panel) => panel.closest(".db-tabs") === _ref.current);
290
- if (!tabListEl)
291
- return;
292
- const buttons = Array.from(tabListEl.querySelectorAll('[role="tab"]'));
293
- buttons.forEach((button, index) => {
294
- const isSelected = currentIndex === index;
295
- const panel = panels[index];
296
- const tabId = button.id || getTabId(index);
297
- const panelId = (panel === null || panel === void 0 ? void 0 : panel.id) || getPanelId(index);
298
- if (!button.id) {
299
- button.id = tabId;
300
- }
301
- if (!button.getAttribute("aria-controls")) {
302
- button.setAttribute("aria-controls", panelId);
303
- }
304
- button.dispatchEvent(new CustomEvent("aria-selected-changed", {
305
- detail: {
306
- selected: isSelected,
307
- tabIndex: currentIndex === index || (currentIndex === -1 && index === 0)
308
- ? 0
309
- : -1,
310
- },
311
- }));
312
- if (panel) {
313
- if (!panel.id) {
314
- panel.id = panelId;
77
+ const tabItems = Array.from(_ref.current.getElementsByClassName("db-tab-item"));
78
+ const tabPanels = Array.from(_ref.current.querySelectorAll(":is(:scope > .db-tab-panel, :scope > db-tab-panel > .db-tab-panel)"));
79
+ for (const tabItem of tabItems) {
80
+ const index = tabItems.indexOf(tabItem);
81
+ const label = tabItem.querySelector("label");
82
+ const input = tabItem.querySelector("input");
83
+ if (input && label) {
84
+ if (!input.id) {
85
+ const tabId = `${_name}-tab-${index}`;
86
+ label.setAttribute("for", tabId);
87
+ input.id = tabId;
88
+ input.setAttribute("name", _name);
89
+ if (tabPanels.length > index) {
90
+ input.setAttribute("aria-controls", `${_name}-tab-panel-${index}`);
91
+ }
315
92
  }
316
- if (!panel.getAttribute("aria-label") &&
317
- !panel.getAttribute("aria-labelledby")) {
318
- panel.setAttribute("aria-labelledby", tabId);
93
+ if (init) {
94
+ // Auto select
95
+ const autoSelect = !props.initialSelectedMode ||
96
+ props.initialSelectedMode === "auto";
97
+ const shouldAutoSelect = (props.initialSelectedIndex == null && index === 0) ||
98
+ Number(props.initialSelectedIndex) === index;
99
+ if (autoSelect && shouldAutoSelect) {
100
+ input.click();
101
+ }
319
102
  }
320
- // toggle visibility
321
- panel.hidden = !isSelected;
322
103
  }
323
- });
104
+ }
105
+ for (const panel of tabPanels) {
106
+ if (panel.id)
107
+ continue;
108
+ const index = tabPanels.indexOf(panel);
109
+ panel.id = `${_name}-tab-panel-${index}`;
110
+ panel.setAttribute("aria-labelledby", `${_name}-tab-${index}`);
111
+ }
324
112
  }
325
113
  }
326
- useEffect(() => {
327
- // 1. Calculate final start index synchronously to avoid race conditions
328
- let startIndex = 0;
329
- if (props.initialSelectedIndex !== undefined) {
330
- const parsedIndex = Number(props.initialSelectedIndex);
331
- startIndex = isNaN(parsedIndex) ? 0 : parsedIndex;
332
- }
333
- else if (props.initialSelectedMode === "manually") {
334
- startIndex = -1;
335
- }
336
- // 2. Support deep linking: URL hash takes precedence over initial index
337
- if (typeof window !== "undefined" && window.location.hash) {
338
- const hashId = window.location.hash.substring(1);
339
- const name = props.name ? "tabs-" + props.name : _name();
340
- const prefix = `${name}-tab-`;
341
- if (hashId.startsWith(prefix)) {
342
- const indexStr = hashId.replace(prefix, "");
343
- const index = parseInt(indexStr, 10);
344
- if (!isNaN(index)) {
345
- startIndex = index;
114
+ function handleChange(event) {
115
+ var _a;
116
+ event.stopPropagation();
117
+ if (event.target) {
118
+ const target = event.target;
119
+ const parent = target.parentElement;
120
+ if (parent &&
121
+ parent.parentElement &&
122
+ ((_a = parent.parentElement) === null || _a === void 0 ? void 0 : _a.nodeName) === "LI") {
123
+ const tabItem = parent.parentElement;
124
+ if (tabItem) {
125
+ const list = tabItem.parentElement;
126
+ if (list) {
127
+ const tabIndex = Array.from(list.children).indexOf(tabItem);
128
+ if (props.onIndexChange) {
129
+ props.onIndexChange(tabIndex);
130
+ }
131
+ if (props.onTabSelect) {
132
+ props.onTabSelect(event);
133
+ }
134
+ }
346
135
  }
347
136
  }
348
137
  }
349
- // 3. Set initial state synchronously
350
- setActiveTabIndex(startIndex);
138
+ }
139
+ useEffect(() => {
140
+ set_name(`tabs-${props.name || uuid}`);
351
141
  setInitialized(true);
352
- _updateCachedTabs();
353
- // 4. Trigger single initial DOM update after paint
354
- if (typeof window !== "undefined") {
355
- requestAnimationFrame(() => {
356
- initTabList();
357
- initTabs(startIndex);
358
- });
359
- }
360
- if (_ref.current) {
361
- const tabListEl = _ref.current.querySelector('[role="tablist"]');
362
- if (tabListEl) {
363
- const observer = new MutationObserver(() => {
364
- const rafId = _pendingRafId;
365
- if (rafId !== null)
366
- cancelAnimationFrame(rafId);
367
- set_pendingRafId(requestAnimationFrame(() => {
368
- set_pendingRafId(null);
369
- initTabList();
370
- initTabs(activeTabIndex);
371
- }));
142
+ }, []);
143
+ useEffect(() => {
144
+ if (_ref.current && initialized) {
145
+ initTabList();
146
+ initTabs(true);
147
+ const tabList = _ref.current.querySelector(".db-tab-list");
148
+ if (tabList) {
149
+ const observer = new MutationObserver((mutations) => {
150
+ mutations.forEach((mutation) => {
151
+ if (mutation.removedNodes.length || mutation.addedNodes.length) {
152
+ initTabList();
153
+ initTabs();
154
+ }
155
+ });
372
156
  });
373
- // Observe only the tablist (not panel content) to avoid unnecessary
374
- // re-evaluations when user content inside panels changes.
375
- // childList only – attribute changes (set by initTabs) are not observed, preventing infinite loops.
376
- observer.observe(tabListEl, {
157
+ observer.observe(tabList, {
377
158
  childList: true,
378
159
  subtree: true,
379
160
  });
380
- set_observer(observer);
381
161
  }
162
+ setInitialized(false);
382
163
  }
383
- }, []);
384
- useEffect(() => {
385
- _updateCachedTabs();
386
- }, [props.tabs]);
387
- useEffect(() => {
388
- if (props.activeIndex !== undefined) {
389
- const newIndex = Number(props.activeIndex);
390
- if (!isNaN(newIndex) && newIndex !== activeTabIndex) {
391
- activateTab(newIndex);
392
- }
393
- }
394
- }, [props.activeIndex]);
164
+ }, [_ref.current, initialized]);
395
165
  useEffect(() => {
396
166
  return () => {
397
- const rafId = _pendingRafId;
398
- if (rafId !== null) {
399
- cancelAnimationFrame(rafId);
400
- set_pendingRafId(null);
401
- }
402
- const _listener = _scrollListener;
403
- const _container = _getScrollContainer();
404
- if (_listener && _container) {
405
- _container.removeEventListener("scroll", _listener.fn);
406
- }
407
167
  _resizeObserver === null || _resizeObserver === void 0 ? void 0 : _resizeObserver.disconnect();
408
- set_resizeObserver(null);
409
- _observer === null || _observer === void 0 ? void 0 : _observer.disconnect();
410
- set_observer(null);
168
+ set_resizeObserver(undefined);
411
169
  };
412
170
  }, []);
413
- return (React.createElement("div", Object.assign({ ref: _ref }, filterPassingProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font", "data-density", "onTabSelect", "onIndexChange"]), { id: (_c = (_a = props.id) !== null && _a !== void 0 ? _a : (_b = props.propOverrides) === null || _b === void 0 ? void 0 : _b.id) !== null && _c !== void 0 ? _c : _id() }, getRootProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font", "data-density"]), { className: cls("db-tabs", props.className), "data-orientation": props.orientation, "data-scroll-behavior": props.behavior, "data-tab-item-alignment": props.tabItemAlignment, "data-tab-item-width": props.tabItemWidth, onClick: (event) => handleClick(event), onKeyDown: (event) => handleKeyDown(event) }),
414
- showScrollStart ? (React.createElement(DBButton, { className: "tabs-scroll-start", variant: "ghost", icon: "chevron_left", type: "button", noText: true, onClick: (event) => scroll(true) }, props.scrollStartLabel)) : null,
171
+ return (React.createElement("div", Object.assign({ ref: _ref }, filterPassingProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font", "data-density", "onTabSelect", "onIndexChange"]), { id: (_a = props.id) !== null && _a !== void 0 ? _a : (_b = props.propOverrides) === null || _b === void 0 ? void 0 : _b.id }, getRootProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font", "data-density"]), { className: cls("db-tabs", props.className), "data-orientation": props.orientation, "data-scroll-behavior": props.behavior, "data-alignment": (_c = props.alignment) !== null && _c !== void 0 ? _c : "start", "data-width": (_d = props.width) !== null && _d !== void 0 ? _d : "auto", onInput: (event) => handleChange(event), onChange: (event) => handleChange(event) }),
172
+ showScrollLeft ? (React.createElement(DBButton, { className: "tabs-scroll-left", variant: "ghost", icon: "chevron_left", type: "button", noText: true, onClick: (event) => scroll(true) }, "Scroll left")) : null,
415
173
  props.tabs ? (React.createElement(React.Fragment, null,
416
- React.createElement(DBTabList, { orientation: props.orientation, ariaLabel: props.name }, _cachedTabs === null || _cachedTabs === void 0 ? void 0 : _cachedTabs.map((tab, index) => (React.createElement(DBTabItem, { key: props.name + "tab-item" + index, id: getTabId(index), ariaControls: getPanelId(index), active: isIndexActive(index), tabIndex: getTabItemTabIndex(index), label: tab.label, iconTrailing: tab.iconTrailing, icon: tab.icon, noText: tab.noText, onClick: (event) => activateTab(index) })))), _cachedTabs === null || _cachedTabs === void 0 ? void 0 :
417
- _cachedTabs.map((tab, index) => (React.createElement(DBTabPanel, { key: props.name + "tab-panel" + index, id: getPanelId(index), ariaLabelledby: getTabId(index), content: tab.content, hidden: !isIndexActive(index) }, tab.children))))) : null,
418
- !props.tabs ? React.createElement(React.Fragment, null, props.children) : null,
419
- showScrollEnd ? (React.createElement(DBButton, { className: "tabs-scroll-end", variant: "ghost", icon: "chevron_right", type: "button", noText: true, onClick: (event) => scroll(false) }, props.scrollEndLabel)) : null));
174
+ React.createElement(DBTabList, null, (_e = convertTabs()) === null || _e === void 0 ? void 0 : _e.map((tab, index) => (React.createElement(DBTabItem, { key: props.name + "tab-item" + index, active: tab.active, label: tab.label, iconTrailing: tab.iconTrailing, icon: tab.icon, noText: tab.noText })))), (_f = convertTabs()) === null || _f === void 0 ? void 0 :
175
+ _f.map((tab, index) => (React.createElement(DBTabPanel, { key: props.name + "tab-panel" + index, content: tab.content }, tab.children))))) : null,
176
+ showScrollRight ? (React.createElement(DBButton, { className: "tabs-scroll-right", variant: "ghost", icon: "chevron_right", type: "button", noText: true, onClick: (event) => scroll() }, "Scroll right")) : null,
177
+ props.children));
420
178
  }
421
179
  const DBTabs = forwardRef(DBTabsFn);
422
180
  export default DBTabs;
@@ -2,6 +2,7 @@
2
2
  import * as React from "react";
3
3
  import { filterPassingProps, getRootProps } from "../../utils/react";
4
4
  import { useState, useRef, useEffect, forwardRef } from "react";
5
+ import { DEFAULT_ID } from "../../shared/constants";
5
6
  import { cls, getBooleanAsString, delay as utilsDelay } from "../../utils";
6
7
  import { DocumentScrollListener } from "../../utils/document-scroll-listener";
7
8
  import { handleFixedPopover } from "../../utils/floating-components";
@@ -10,7 +11,7 @@ function DBTooltipFn(props, component) {
10
11
  var _a, _b, _c;
11
12
  const uuid = useId();
12
13
  const _ref = component || useRef(component);
13
- const [_id, set_id] = useState(() => "tooltip-" + uuid);
14
+ const [_id, set_id] = useState(() => DEFAULT_ID);
14
15
  const [initialized, setInitialized] = useState(() => false);
15
16
  const [_documentScrollListenerCallbackId, set_documentScrollListenerCallbackId,] = useState(() => undefined);
16
17
  const [_observer, set_observer] = useState(() => undefined);
@@ -461,13 +461,13 @@ export type CloseEventProps<T> = {
461
461
  export type CloseEventState<T> = {
462
462
  handleClose: (event?: T | void, forceClose?: boolean) => void;
463
463
  };
464
- export declare const TabItemAlignmentList: readonly ["start", "center", "end"];
465
- export type TabItemAlignmentType = (typeof TabItemAlignmentList)[number];
466
- export type TabItemAlignmentProps = {
464
+ export declare const AlignmentList: readonly ["start", "center"];
465
+ export type AlignmentType = (typeof AlignmentList)[number];
466
+ export type AlignmentProps = {
467
467
  /**
468
- * Define the tab-item alignment in full width
468
+ * Define the content alignment in full width
469
469
  */
470
- tabItemAlignment?: TabItemAlignmentType | string;
470
+ alignment?: AlignmentType | string;
471
471
  };
472
472
  export type ActiveProps = {
473
473
  /**
@@ -19,4 +19,4 @@ export const LabelVariantHorizontalList = ['leading', 'trailing'];
19
19
  export const AutoCompleteList = ['off', 'on', 'name', 'honorific-prefix', 'given-name', 'additional-name', 'family-name', 'honorific-suffix', 'nickname', 'email', 'username', 'new-password', 'current-password', 'one-time-code', 'organization-title', 'organization', 'street-address', 'shipping', 'billing', 'address-line1', 'address-line2', 'address-line3', 'address-level4', 'address-level3', 'address-level2', 'address-level1', 'country', 'country-name', 'postal-code', 'cc-name', 'cc-given-name', 'cc-additional-name', 'cc-family-name', 'cc-number', 'cc-exp', 'cc-exp-month', 'cc-exp-year', 'cc-csc', 'cc-type', 'transaction-currency', 'transaction-amount', 'language', 'bday', 'bday-day', 'bday-month', 'bday-year', 'sex', 'tel', 'tel-country-code', 'tel-national', 'tel-area-code', 'tel-local', 'tel-extension', 'impp', 'url', 'photo', 'webauthn'];
20
20
  export const LinkTargetList = ['_self', '_blank', '_parent', '_top'];
21
21
  export const LinkReferrerPolicyList = ['no-referrer', 'no-referrer-when-downgrade', 'origin', 'origin-when-cross-origin', 'same-origin', 'strict-origin', 'strict-origin-when-cross-origin', 'unsafe-url'];
22
- export const TabItemAlignmentList = ['start', 'center', 'end'];
22
+ export const AlignmentList = ['start', 'center'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@db-ux/react-core-components",
3
- "version": "4.7.0-tabs-34782eb",
3
+ "version": "4.7.1",
4
4
  "description": "React components for @db-ux/core-components",
5
5
  "repository": {
6
6
  "type": "git",
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "sideEffects": false,
43
43
  "dependencies": {
44
- "@db-ux/core-components": "4.7.0-tabs-34782eb",
45
- "@db-ux/core-foundations": "4.7.0-tabs-34782eb"
44
+ "@db-ux/core-components": "4.7.1",
45
+ "@db-ux/core-foundations": "4.7.1"
46
46
  }
47
47
  }