mtrl 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/badge/_styles.scss +9 -9
- package/src/components/button/_styles.scss +0 -56
- package/src/components/button/button.ts +0 -2
- package/src/components/button/constants.ts +0 -6
- package/src/components/button/index.ts +2 -2
- package/src/components/button/types.ts +1 -7
- package/src/components/card/_styles.scss +67 -25
- package/src/components/card/api.ts +54 -3
- package/src/components/card/card.ts +33 -2
- package/src/components/card/config.ts +143 -21
- package/src/components/card/constants.ts +20 -19
- package/src/components/card/content.ts +299 -2
- package/src/components/card/features.ts +155 -4
- package/src/components/card/index.ts +31 -9
- package/src/components/card/types.ts +138 -15
- package/src/components/chip/chip.ts +1 -9
- package/src/components/chip/constants.ts +0 -10
- package/src/components/chip/index.ts +1 -1
- package/src/components/chip/types.ts +1 -4
- package/src/components/progress/_styles.scss +0 -65
- package/src/components/progress/config.ts +1 -2
- package/src/components/progress/constants.ts +0 -14
- package/src/components/progress/index.ts +1 -1
- package/src/components/progress/progress.ts +1 -4
- package/src/components/progress/types.ts +1 -4
- package/src/components/radios/_styles.scss +0 -45
- package/src/components/radios/api.ts +85 -60
- package/src/components/radios/config.ts +1 -2
- package/src/components/radios/constants.ts +0 -9
- package/src/components/radios/index.ts +1 -1
- package/src/components/radios/radio.ts +34 -11
- package/src/components/radios/radios.ts +2 -1
- package/src/components/radios/types.ts +1 -7
- package/src/components/slider/_styles.scss +149 -155
- package/src/components/slider/accessibility.md +59 -0
- package/src/components/slider/config.ts +4 -6
- package/src/components/slider/features/disabled.ts +41 -16
- package/src/components/slider/features/interactions.ts +153 -18
- package/src/components/slider/features/keyboard.ts +127 -6
- package/src/components/slider/features/structure.ts +32 -5
- package/src/components/slider/features/ui.ts +18 -8
- package/src/components/tabs/_styles.scss +285 -155
- package/src/components/tabs/api.ts +178 -400
- package/src/components/tabs/config.ts +46 -52
- package/src/components/tabs/constants.ts +85 -8
- package/src/components/tabs/features.ts +401 -0
- package/src/components/tabs/index.ts +60 -3
- package/src/components/tabs/indicator.ts +225 -0
- package/src/components/tabs/responsive.ts +144 -0
- package/src/components/tabs/scroll-indicators.ts +149 -0
- package/src/components/tabs/state.ts +186 -0
- package/src/components/tabs/tab-api.ts +258 -0
- package/src/components/tabs/tab.ts +255 -0
- package/src/components/tabs/tabs.ts +50 -31
- package/src/components/tabs/types.ts +324 -128
- package/src/components/tabs/utils.ts +107 -0
- package/src/components/textfield/_styles.scss +0 -98
- package/src/components/textfield/config.ts +2 -3
- package/src/components/textfield/constants.ts +0 -14
- package/src/components/textfield/index.ts +2 -2
- package/src/components/textfield/textfield.ts +0 -2
- package/src/components/textfield/types.ts +1 -4
- package/src/core/compose/component.ts +1 -1
- package/src/core/compose/features/badge.ts +79 -0
- package/src/core/compose/features/index.ts +3 -1
- package/src/styles/abstract/_theme.scss +106 -2
- package/src/components/card/actions.ts +0 -48
- package/src/components/card/header.ts +0 -88
- package/src/components/card/media.ts +0 -52
|
@@ -1,80 +1,74 @@
|
|
|
1
1
|
// src/components/tabs/config.ts
|
|
2
2
|
import {
|
|
3
3
|
createComponentConfig,
|
|
4
|
-
createElementConfig
|
|
4
|
+
createElementConfig,
|
|
5
|
+
BaseComponentConfig
|
|
5
6
|
} from '../../core/config/component-config';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
7
|
+
import { withElement } from '../../core/compose/component';
|
|
8
|
+
import { TabConfig } from './types';
|
|
9
|
+
import { TABS_VARIANTS, TAB_STATES } from './constants';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Default configuration for a Tab
|
|
13
|
+
*/
|
|
14
|
+
export const defaultTabConfig: TabConfig = {
|
|
15
|
+
state: TAB_STATES.INACTIVE,
|
|
16
|
+
componentName: 'tab',
|
|
17
|
+
ripple: true
|
|
18
|
+
};
|
|
8
19
|
|
|
9
20
|
/**
|
|
10
21
|
* Default configuration for the Tabs component
|
|
11
22
|
*/
|
|
12
|
-
export const
|
|
23
|
+
export const defaultTabsConfig = {
|
|
13
24
|
variant: TABS_VARIANTS.PRIMARY,
|
|
14
|
-
showIndicator: true,
|
|
15
|
-
animated: true,
|
|
16
25
|
scrollable: true,
|
|
17
|
-
|
|
26
|
+
showDivider: true,
|
|
27
|
+
componentName: 'tabs'
|
|
18
28
|
};
|
|
19
29
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* @param {TabsConfig} config - User provided configuration
|
|
23
|
-
* @returns {TabsConfig} Complete configuration with defaults applied
|
|
24
|
-
*/
|
|
25
|
-
export const createBaseConfig = (config: TabsConfig = {}): TabsConfig =>
|
|
26
|
-
createComponentConfig(defaultConfig, config, 'tabs') as TabsConfig;
|
|
30
|
+
export const createTabsConfig = (config = {}) =>
|
|
31
|
+
createComponentConfig(defaultTabsConfig, config, 'tabs');
|
|
27
32
|
|
|
28
33
|
/**
|
|
29
|
-
*
|
|
30
|
-
* @param {
|
|
31
|
-
* @returns {
|
|
34
|
+
* Creates the base configuration for a Tab
|
|
35
|
+
* @param {TabConfig} config - User provided configuration
|
|
36
|
+
* @returns {TabConfig} Complete configuration with defaults applied
|
|
32
37
|
*/
|
|
33
|
-
export const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
export const createTabConfig = (config: TabConfig = {}): TabConfig =>
|
|
39
|
+
createComponentConfig(defaultTabConfig, config, 'tab') as TabConfig;
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
export const getTabsElementConfig = (config) => {
|
|
43
|
+
const elementConfig = {
|
|
44
|
+
tag: 'div',
|
|
45
|
+
attrs: {
|
|
46
|
+
role: 'tablist',
|
|
47
|
+
'aria-orientation': 'horizontal'
|
|
48
|
+
},
|
|
49
|
+
className: [
|
|
50
|
+
`${config.prefix}-tabs`,
|
|
51
|
+
`${config.prefix}-tabs--${config.variant || TABS_VARIANTS.PRIMARY}`,
|
|
52
|
+
config.class
|
|
53
|
+
]
|
|
37
54
|
};
|
|
38
55
|
|
|
39
|
-
|
|
40
|
-
if (config.disabled === true) {
|
|
41
|
-
attrs['aria-disabled'] = 'true';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const extraClasses: string[] = [];
|
|
45
|
-
|
|
46
|
-
if (config.scrollable) {
|
|
47
|
-
extraClasses.push('--scrollable');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (config.animated) {
|
|
51
|
-
extraClasses.push('--animated');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return createElementConfig(config, {
|
|
55
|
-
tag: 'div',
|
|
56
|
-
attrs,
|
|
57
|
-
className: config.class,
|
|
58
|
-
extraClasses,
|
|
59
|
-
forwardEvents: {
|
|
60
|
-
keydown: true
|
|
61
|
-
}
|
|
62
|
-
});
|
|
56
|
+
return (component) => withElement(elementConfig)(component);
|
|
63
57
|
};
|
|
64
58
|
|
|
65
59
|
/**
|
|
66
|
-
* Creates API configuration for the
|
|
60
|
+
* Creates API configuration for the Tab component
|
|
67
61
|
* @param {Object} comp - Component with disabled and lifecycle features
|
|
68
62
|
* @returns {Object} API configuration object
|
|
69
63
|
*/
|
|
70
|
-
export const
|
|
64
|
+
export const getTabApiConfig = (comp) => ({
|
|
71
65
|
disabled: {
|
|
72
66
|
enable: () => comp.disabled.enable(),
|
|
73
|
-
disable: () => comp.disabled.disable()
|
|
67
|
+
disable: () => comp.disabled.disable(),
|
|
68
|
+
isDisabled: () => comp.disabled.isDisabled && comp.disabled.isDisabled()
|
|
74
69
|
},
|
|
75
70
|
lifecycle: {
|
|
76
71
|
destroy: () => comp.lifecycle.destroy()
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
export default defaultConfig;
|
|
72
|
+
},
|
|
73
|
+
button: comp.button
|
|
74
|
+
});
|
|
@@ -1,12 +1,89 @@
|
|
|
1
1
|
// src/components/tabs/constants.ts
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Tab variants
|
|
5
|
+
*/
|
|
3
6
|
export const TABS_VARIANTS = {
|
|
7
|
+
/** Primary tabs (standard MD3 style) */
|
|
4
8
|
PRIMARY: 'primary',
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export const
|
|
9
|
+
/** Secondary tabs (less prominent variant) */
|
|
10
|
+
SECONDARY: 'secondary'
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Tab states
|
|
15
|
+
*/
|
|
16
|
+
export const TAB_STATES = {
|
|
17
|
+
/** Active (selected) tab state */
|
|
18
|
+
ACTIVE: 'active',
|
|
19
|
+
/** Inactive (unselected) tab state */
|
|
20
|
+
INACTIVE: 'inactive',
|
|
21
|
+
/** Disabled tab state */
|
|
22
|
+
DISABLED: 'disabled'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Tab layout types
|
|
27
|
+
*/
|
|
28
|
+
export const TAB_LAYOUT = {
|
|
29
|
+
/** Icon-only tab layout */
|
|
30
|
+
ICON_ONLY: 'icon-only',
|
|
31
|
+
/** Text-only tab layout */
|
|
32
|
+
TEXT_ONLY: 'text-only',
|
|
33
|
+
/** Icon and text layout */
|
|
34
|
+
ICON_AND_TEXT: 'icon-and-text'
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tab interaction states (for styling)
|
|
39
|
+
*/
|
|
40
|
+
export const TAB_INTERACTION_STATES = {
|
|
41
|
+
/** Default enabled state */
|
|
42
|
+
ENABLED: 'enabled',
|
|
43
|
+
/** Hover state */
|
|
44
|
+
HOVER: 'hover',
|
|
45
|
+
/** Focus state */
|
|
46
|
+
FOCUS: 'focus',
|
|
47
|
+
/** Pressed/active state */
|
|
48
|
+
PRESSED: 'pressed'
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Tab animation constants
|
|
53
|
+
*/
|
|
54
|
+
export const TAB_ANIMATION = {
|
|
55
|
+
/** Standard transition duration in ms */
|
|
56
|
+
TRANSITION_DURATION: 200,
|
|
57
|
+
/** Standard transition timing function */
|
|
58
|
+
TRANSITION_TIMING: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
59
|
+
/** Ripple animation duration in ms */
|
|
60
|
+
RIPPLE_DURATION: 400
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Tab accessibility roles
|
|
65
|
+
*/
|
|
66
|
+
export const TAB_A11Y = {
|
|
67
|
+
/** Tab role */
|
|
68
|
+
TAB_ROLE: 'tab',
|
|
69
|
+
/** Tablist role */
|
|
70
|
+
TABLIST_ROLE: 'tablist',
|
|
71
|
+
/** Tabpanel role */
|
|
72
|
+
TABPANEL_ROLE: 'tabpanel'
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* MD3 tokens for tab colors
|
|
77
|
+
*/
|
|
78
|
+
export const TAB_COLORS = {
|
|
79
|
+
/** Surface color for container */
|
|
80
|
+
SURFACE: 'surface',
|
|
81
|
+
/** Primary color for active tab and indicator */
|
|
82
|
+
PRIMARY: 'primary',
|
|
83
|
+
/** On-surface color for active secondary tabs */
|
|
84
|
+
ON_SURFACE: 'on-surface',
|
|
85
|
+
/** On-surface-variant for inactive tabs */
|
|
86
|
+
ON_SURFACE_VARIANT: 'on-surface-variant',
|
|
87
|
+
/** Outline variant for divider */
|
|
88
|
+
OUTLINE_VARIANT: 'outline-variant'
|
|
89
|
+
};
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
// src/components/tabs/features.ts
|
|
2
|
+
import { createTab } from './tab';
|
|
3
|
+
import { TabConfig, TabComponent } from './types';
|
|
4
|
+
import { BaseComponent } from '../../core/compose/component';
|
|
5
|
+
import { updateTabPanels, getActiveTab } from './utils';
|
|
6
|
+
import { createTabIndicator, TabIndicator } from './indicator';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for tabs management feature
|
|
10
|
+
*/
|
|
11
|
+
export interface TabsManagementConfig {
|
|
12
|
+
/** Initial tabs to create */
|
|
13
|
+
tabs?: TabConfig[];
|
|
14
|
+
/** Tab variant */
|
|
15
|
+
variant?: string;
|
|
16
|
+
/** Component prefix */
|
|
17
|
+
prefix?: string;
|
|
18
|
+
/** Other configuration properties */
|
|
19
|
+
[key: string]: any;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Component with tabs management capabilities
|
|
24
|
+
*/
|
|
25
|
+
export interface TabsManagementComponent {
|
|
26
|
+
/** Array of tab components */
|
|
27
|
+
tabs: TabComponent[];
|
|
28
|
+
|
|
29
|
+
/** Target container for tabs */
|
|
30
|
+
tabsContainer: HTMLElement;
|
|
31
|
+
|
|
32
|
+
/** Tab click handler */
|
|
33
|
+
handleTabClick: (event: Event, tab: TabComponent) => void;
|
|
34
|
+
|
|
35
|
+
/** Get all tabs */
|
|
36
|
+
getTabs?: () => TabComponent[];
|
|
37
|
+
|
|
38
|
+
/** Get the active tab */
|
|
39
|
+
getActiveTab?: () => TabComponent | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Adds tabs management capabilities to a component
|
|
44
|
+
* @param {TabsManagementConfig} config - Tabs configuration
|
|
45
|
+
* @returns {Function} Component enhancer with tabs management
|
|
46
|
+
*/
|
|
47
|
+
export const withTabsManagement = <T extends TabsManagementConfig>(config: T) =>
|
|
48
|
+
<C extends any>(component: C): C & TabsManagementComponent => {
|
|
49
|
+
const tabs: TabComponent[] = [];
|
|
50
|
+
|
|
51
|
+
// Store the target container for tabs
|
|
52
|
+
const tabsContainer = component.scrollContainer || component.element;
|
|
53
|
+
|
|
54
|
+
// Create initial tabs if provided in config
|
|
55
|
+
if (Array.isArray(config.tabs)) {
|
|
56
|
+
config.tabs.forEach(tabConfig => {
|
|
57
|
+
// Create a merged config that inherits from tabs component
|
|
58
|
+
const mergedConfig = {
|
|
59
|
+
...tabConfig,
|
|
60
|
+
prefix: config.prefix,
|
|
61
|
+
variant: tabConfig.variant || config.variant
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Create the tab
|
|
65
|
+
const tab = createTab(mergedConfig);
|
|
66
|
+
|
|
67
|
+
// Add to internal tabs array
|
|
68
|
+
tabs.push(tab);
|
|
69
|
+
|
|
70
|
+
// Add to DOM
|
|
71
|
+
tabsContainer.appendChild(tab.element);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Gets all tabs
|
|
77
|
+
*/
|
|
78
|
+
const getTabs = () => {
|
|
79
|
+
return [...tabs];
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Gets the active tab
|
|
84
|
+
*/
|
|
85
|
+
const getActiveTab = () => {
|
|
86
|
+
return tabs.find(tab => tab.isActive()) || null;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Handles tab click events
|
|
91
|
+
*/
|
|
92
|
+
const handleTabClick = (event: any, tab: TabComponent) => {
|
|
93
|
+
// Check if event is a DOM event with preventDefault
|
|
94
|
+
if (event && typeof event.preventDefault === 'function') {
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Skip if tab is disabled
|
|
99
|
+
if (tab.disabled && tab.disabled.isDisabled && tab.disabled.isDisabled()) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Deactivate all tabs first
|
|
104
|
+
tabs.forEach(t => t.deactivate());
|
|
105
|
+
|
|
106
|
+
// Activate the clicked tab
|
|
107
|
+
tab.activate();
|
|
108
|
+
|
|
109
|
+
// Get the tab value
|
|
110
|
+
const value = tab.getValue();
|
|
111
|
+
|
|
112
|
+
// Update tab panels
|
|
113
|
+
updateTabPanels({
|
|
114
|
+
tabs,
|
|
115
|
+
getActiveTab: () => tabs.find(t => t.isActive()) || null
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Emit change event if component has emit method
|
|
119
|
+
if (typeof component['emit'] === 'function') {
|
|
120
|
+
component['emit']('change', {
|
|
121
|
+
tab,
|
|
122
|
+
value
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Add click handlers to existing tabs
|
|
128
|
+
tabs.forEach(tab => {
|
|
129
|
+
// Add event listener directly and via API if available
|
|
130
|
+
if (tab.on && typeof tab.on === 'function') {
|
|
131
|
+
tab.on('click', (event) => handleTabClick(event, tab));
|
|
132
|
+
}
|
|
133
|
+
// Also add direct DOM event listener as a fallback
|
|
134
|
+
tab.element.addEventListener('click', (event) => handleTabClick(event, tab));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
...component,
|
|
139
|
+
tabs,
|
|
140
|
+
tabsContainer,
|
|
141
|
+
handleTabClick,
|
|
142
|
+
getTabs,
|
|
143
|
+
getActiveTab
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Configuration for scrollable feature
|
|
149
|
+
*/
|
|
150
|
+
export interface ScrollableConfig {
|
|
151
|
+
/** Whether tabs are scrollable horizontally */
|
|
152
|
+
scrollable?: boolean;
|
|
153
|
+
/** Other configuration properties */
|
|
154
|
+
[key: string]: any;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Component with scrollable capabilities
|
|
159
|
+
*/
|
|
160
|
+
export interface ScrollableComponent {
|
|
161
|
+
/** Scroll container element */
|
|
162
|
+
scrollContainer?: HTMLElement;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Adds scrollable capabilities to a component
|
|
167
|
+
* @param {ScrollableConfig} config - Scrollable configuration
|
|
168
|
+
* @returns {Function} Component enhancer with scrollable container
|
|
169
|
+
*/
|
|
170
|
+
export const withScrollable = <T extends ScrollableConfig>(config: T) =>
|
|
171
|
+
<C extends any>(component: C): C & ScrollableComponent => {
|
|
172
|
+
// Skip if scrollable is explicitly false
|
|
173
|
+
if (config.scrollable === false) {
|
|
174
|
+
return component as C & ScrollableComponent;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Add scrollable class
|
|
178
|
+
component.element.classList.add(`${component.getClass('tabs')}--scrollable`);
|
|
179
|
+
|
|
180
|
+
// Create container for tabs that can scroll
|
|
181
|
+
const scrollContainer = document.createElement('div');
|
|
182
|
+
scrollContainer.className = `${component.getClass('tabs')}-scroll`;
|
|
183
|
+
|
|
184
|
+
// Move any existing children to scroll container
|
|
185
|
+
while (component.element.firstChild) {
|
|
186
|
+
scrollContainer.appendChild(component.element.firstChild);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Add scroll container to the main element
|
|
190
|
+
component.element.appendChild(scrollContainer);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
...component,
|
|
194
|
+
scrollContainer
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Configuration for divider feature
|
|
200
|
+
*/
|
|
201
|
+
export interface DividerConfig {
|
|
202
|
+
/** Whether to show a divider below the tabs */
|
|
203
|
+
showDivider?: boolean;
|
|
204
|
+
/** Other configuration properties */
|
|
205
|
+
[key: string]: any;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Adds a divider to a component
|
|
210
|
+
* @param {DividerConfig} config - Divider configuration
|
|
211
|
+
* @returns {Function} Component enhancer with divider
|
|
212
|
+
*/
|
|
213
|
+
export const withDivider = <T extends DividerConfig>(config: T) =>
|
|
214
|
+
<C extends any>(component: C): C => {
|
|
215
|
+
// Skip if divider is explicitly disabled
|
|
216
|
+
if (config.showDivider === false) {
|
|
217
|
+
return component;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Create the divider element
|
|
221
|
+
const divider = document.createElement('div');
|
|
222
|
+
divider.className = `${component.getClass('tabs')}-divider`;
|
|
223
|
+
|
|
224
|
+
// Add the divider to the main element
|
|
225
|
+
component.element.appendChild(divider);
|
|
226
|
+
|
|
227
|
+
return component;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Configuration for indicator feature
|
|
232
|
+
*/
|
|
233
|
+
export interface IndicatorFeatureConfig {
|
|
234
|
+
/** Component prefix */
|
|
235
|
+
prefix?: string;
|
|
236
|
+
/** Width strategy for the indicator */
|
|
237
|
+
widthStrategy?: 'fixed' | 'dynamic' | 'content';
|
|
238
|
+
/** Height of the indicator in pixels */
|
|
239
|
+
height?: number;
|
|
240
|
+
/** Fixed width in pixels (when using fixed strategy) */
|
|
241
|
+
fixedWidth?: number;
|
|
242
|
+
/** Animation duration in milliseconds */
|
|
243
|
+
animationDuration?: number;
|
|
244
|
+
/** Animation timing function */
|
|
245
|
+
animationTiming?: string;
|
|
246
|
+
/** Custom color for the indicator */
|
|
247
|
+
color?: string;
|
|
248
|
+
/** Legacy height property */
|
|
249
|
+
indicatorHeight?: number;
|
|
250
|
+
/** Legacy width strategy property */
|
|
251
|
+
indicatorWidthStrategy?: 'fixed' | 'dynamic' | 'content';
|
|
252
|
+
/** Indicator configuration object */
|
|
253
|
+
indicator?: {
|
|
254
|
+
widthStrategy?: 'fixed' | 'dynamic' | 'content';
|
|
255
|
+
height?: number;
|
|
256
|
+
fixedWidth?: number;
|
|
257
|
+
animationDuration?: number;
|
|
258
|
+
animationTiming?: string;
|
|
259
|
+
color?: string;
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Component with indicator capability
|
|
265
|
+
*/
|
|
266
|
+
export interface IndicatorComponent {
|
|
267
|
+
/** The indicator instance */
|
|
268
|
+
indicator: TabIndicator;
|
|
269
|
+
/** Get the indicator instance */
|
|
270
|
+
getIndicator: () => TabIndicator;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Enhances a component with tab indicator functionality
|
|
275
|
+
* @param config - Indicator configuration
|
|
276
|
+
* @returns Component enhancer with indicator functionality
|
|
277
|
+
*/
|
|
278
|
+
export const withIndicator = <T extends IndicatorFeatureConfig>(config: T) =>
|
|
279
|
+
<C extends any>(component: C): C & IndicatorComponent => {
|
|
280
|
+
// Create indicator with proper config
|
|
281
|
+
const indicatorConfig = config.indicator || {};
|
|
282
|
+
const indicator: TabIndicator = createTabIndicator({
|
|
283
|
+
prefix: config.prefix,
|
|
284
|
+
// Support both new and legacy config
|
|
285
|
+
widthStrategy: indicatorConfig.widthStrategy || config.indicatorWidthStrategy || 'fixed',
|
|
286
|
+
height: indicatorConfig.height || config.indicatorHeight || 3,
|
|
287
|
+
fixedWidth: indicatorConfig.fixedWidth || 40,
|
|
288
|
+
animationDuration: indicatorConfig.animationDuration || 250,
|
|
289
|
+
animationTiming: indicatorConfig.animationTiming || 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
290
|
+
color: indicatorConfig.color
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Find the scroll container and add the indicator to it
|
|
294
|
+
const scrollContainer = component.scrollContainer || component.element;
|
|
295
|
+
if (!scrollContainer) {
|
|
296
|
+
console.error('No scroll container found - cannot add indicator');
|
|
297
|
+
throw new Error('Failed to create tabs: No scroll container found');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Add the indicator to the scroll container
|
|
301
|
+
scrollContainer.appendChild(indicator.element);
|
|
302
|
+
|
|
303
|
+
// Store the original handlers to enhance
|
|
304
|
+
const originalHandleTabClick = component.handleTabClick;
|
|
305
|
+
|
|
306
|
+
// Replace tab click handler to ensure indicator updates
|
|
307
|
+
component.handleTabClick = function(event, tab) {
|
|
308
|
+
// Skip if tab is disabled
|
|
309
|
+
if (tab.disabled && tab.disabled.isDisabled && tab.disabled.isDisabled()) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Call original handler
|
|
314
|
+
originalHandleTabClick.call(this, event, tab);
|
|
315
|
+
|
|
316
|
+
// Move indicator with a slight delay to ensure DOM updates
|
|
317
|
+
setTimeout(() => {
|
|
318
|
+
indicator.moveToTab(tab);
|
|
319
|
+
}, 10);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Position indicator on initial active tab
|
|
323
|
+
setTimeout(() => {
|
|
324
|
+
const activeTab = component.tabs.find(tab => tab.isActive());
|
|
325
|
+
if (activeTab) {
|
|
326
|
+
indicator.moveToTab(activeTab, true);
|
|
327
|
+
}
|
|
328
|
+
}, 50);
|
|
329
|
+
|
|
330
|
+
// Add scroll event handling
|
|
331
|
+
const scrollHandler = () => {
|
|
332
|
+
const activeTab = component.tabs.find(tab => tab.isActive());
|
|
333
|
+
if (activeTab) {
|
|
334
|
+
indicator.moveToTab(activeTab, true);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
if (scrollContainer) {
|
|
339
|
+
scrollContainer.addEventListener('scroll', scrollHandler);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Watch for window resize to update indicator
|
|
343
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
344
|
+
const activeTab = component.tabs.find(tab => tab.isActive());
|
|
345
|
+
if (activeTab) {
|
|
346
|
+
indicator.moveToTab(activeTab, true);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
resizeObserver.observe(scrollContainer);
|
|
351
|
+
|
|
352
|
+
// Add MutationObserver to detect tab state changes
|
|
353
|
+
const mutationObserver = new MutationObserver((mutations) => {
|
|
354
|
+
for (const mutation of mutations) {
|
|
355
|
+
if (mutation.type === 'attributes' &&
|
|
356
|
+
mutation.attributeName === 'class' &&
|
|
357
|
+
(mutation.target as HTMLElement).classList.contains(`${config.prefix}-tab--active`)) {
|
|
358
|
+
// Find the corresponding tab component
|
|
359
|
+
const tabElement = mutation.target as HTMLElement;
|
|
360
|
+
const activeTab = component.tabs.find(tab => tab.element === tabElement);
|
|
361
|
+
if (activeTab) {
|
|
362
|
+
indicator.moveToTab(activeTab);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Observe all tabs for class changes
|
|
369
|
+
if (Array.isArray(component.tabs)) {
|
|
370
|
+
component.tabs.forEach(tab => {
|
|
371
|
+
if (tab.element) {
|
|
372
|
+
mutationObserver.observe(tab.element, { attributes: true });
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Enhance component's destroy method
|
|
378
|
+
const originalDestroy = component.destroy || (() => {});
|
|
379
|
+
|
|
380
|
+
// Override destroy to clean up resources
|
|
381
|
+
(component as any).destroy = function() {
|
|
382
|
+
indicator.destroy();
|
|
383
|
+
resizeObserver.disconnect();
|
|
384
|
+
mutationObserver.disconnect();
|
|
385
|
+
|
|
386
|
+
if (scrollContainer) {
|
|
387
|
+
scrollContainer.removeEventListener('scroll', scrollHandler);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Call original destroy if it exists
|
|
391
|
+
if (typeof originalDestroy === 'function') {
|
|
392
|
+
originalDestroy.call(this);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
...component,
|
|
398
|
+
indicator,
|
|
399
|
+
getIndicator: () => indicator
|
|
400
|
+
};
|
|
401
|
+
};
|
|
@@ -1,4 +1,61 @@
|
|
|
1
1
|
// src/components/tabs/index.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import createTabs from './tabs';
|
|
3
|
+
import { createTab } from './tab';
|
|
4
|
+
import { addScrollIndicators } from './scroll-indicators';
|
|
5
|
+
import { setupResponsiveBehavior } from './responsive';
|
|
6
|
+
import { createTabsState } from './state';
|
|
7
|
+
import { createTabIndicator } from './indicator';
|
|
8
|
+
import { updateTabPanels, setupKeyboardNavigation } from './utils';
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
// Main component creators
|
|
12
|
+
createTabs,
|
|
13
|
+
createTab,
|
|
14
|
+
|
|
15
|
+
// Constants
|
|
16
|
+
TABS_VARIANTS,
|
|
17
|
+
TAB_STATES,
|
|
18
|
+
TAB_LAYOUT,
|
|
19
|
+
TAB_INTERACTION_STATES,
|
|
20
|
+
TAB_ANIMATION,
|
|
21
|
+
TAB_A11Y,
|
|
22
|
+
TAB_COLORS
|
|
23
|
+
} from './constants';
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
// Types
|
|
27
|
+
TabsConfig,
|
|
28
|
+
TabsComponent,
|
|
29
|
+
TabComponent,
|
|
30
|
+
TabConfig,
|
|
31
|
+
TabChangeEventData,
|
|
32
|
+
IndicatorConfig
|
|
33
|
+
} from './types';
|
|
34
|
+
|
|
35
|
+
// Export enhancers and utilities
|
|
36
|
+
export {
|
|
37
|
+
addScrollIndicators,
|
|
38
|
+
setupResponsiveBehavior,
|
|
39
|
+
createTabsState,
|
|
40
|
+
createTabIndicator,
|
|
41
|
+
updateTabPanels,
|
|
42
|
+
setupKeyboardNavigation
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Export features
|
|
46
|
+
export {
|
|
47
|
+
withTabsManagement,
|
|
48
|
+
withScrollable,
|
|
49
|
+
withDivider,
|
|
50
|
+
withIndicator,
|
|
51
|
+
TabsManagementConfig,
|
|
52
|
+
TabsManagementComponent,
|
|
53
|
+
ScrollableConfig,
|
|
54
|
+
ScrollableComponent,
|
|
55
|
+
DividerConfig,
|
|
56
|
+
IndicatorFeatureConfig,
|
|
57
|
+
IndicatorComponent
|
|
58
|
+
} from './features';
|
|
59
|
+
|
|
60
|
+
// Default export
|
|
61
|
+
export default createTabs;
|